experience-ui-bundle-salesforce-data-access について
Salesforce の UI bundle で `@salesforce/platform-sdk` を使う開発者向けのデータアクセス手順をまとめたスキルです。GraphQL のスキーマ確認、実行環境に応じたガード、キャッシュ、REST の利用方法を扱います。
主な機能
- 01SDK のクエリと変更の手順
- 02GraphQL スキーマの確認
- 03実行環境に応じた API ガード
- 04キャッシュと更新のガイダンス
- 05REST 連携のガイダンス
- 06旧 API からの移行
活用シーン
- 01Salesforce レコードのクエリ作成
- 02UI bundle への変更処理の追加
- 03キャッシュ済み・古い結果への対応
- 04旧 SDK 呼び出しの移行
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ forcedotcom/ sf-skills --skill experience-ui-bundle-salesforce-data-accessインストール後、Claude Code で/experience-ui-bundle-salesforce-data-accessを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/caching.mdgraphiti-cli.mdgraphql-hand-authoring.mdmigration.mdrest-and-integration.mdsdk-api.mdscripts/graphql-search.sh詳細
Salesforce Data Access(UI バンドル)
UI バンドルでの Salesforce データアクセスはすべて、@salesforce/platform-sdk
データ SDK を経由します。SDK は認証、CSRF、ベース URL の解決を処理します。また、WebApp
サーフェスではすべての GraphQL クエリをデフォルトでキャッシュします。
このファイルは、ワークフローとガードレールの中核です。詳細はリンク先のドキュメントに記載されています。
- references/graphiti-cli.md — 小さな JSON 仕様から、スキーマに準拠し、ガードレールが適用されたクエリ、変数、型を生成する
graphitiCLI(sf-gql-*コマンド)。以下の手順で GraphQL を記述する際に推奨される方法です。利用できない場合は、スキーマ検索スクリプトにフォールバックします。 - references/sdk-api.md —
query/mutateの呼び出し方法と生成型の配置先。動作上の詳細(サーフェス、エラー処理方針、QueryResult)はtier-2bに基づいています。 - references/caching.md — デフォルトで有効なキャッシュと、2 種類の更新方法。動作の根拠は、インストール済みの場合はtier-2bの
docs/data/、そうでない場合はこのファイルに記載されたバージョン付きの完全なフォールバックです。 - references/graphql-hand-authoring.md — スキーマの検索、読み取り/変更のテンプレート、プラットフォームのすべてのガードレール(
@optional、ページネーション、制限、セミジョイン、ラッパー、エラーテーブルなど)。 - references/rest-and-integration.md —
sdk.fetch、サポート対象 API の許可リスト、リアクティブな連携/ライフサイクルのパターン。 - references/migration.md — 古い
@salesforce/sdk-dataの呼び出し形式のコードから新しい名前空間への移行。利用可能なコードとして旧 API が登場するのは、ここだけです。
基本的な考え方
const sdk = await createDataSDK()を実行します。次に、sdk.graphqlは関数ではなく名前空間です。読み取りには**sdk.graphql!.query({...})を、書き込みにはsdk.graphql!.mutate({...})**を使用します。WebApp では、すべてのquery()がデフォルトでキャッシュされます(300s)。HTTP 200 は成功を意味するとは限りません。必ずresult.errorsを確認してください。クエリを実行する前に、すべてのエンティティとフィールドがスキーマに存在することを確認してください。未確認のフィールドが 1 つでもあると、実行時にクエリ全体が失敗します。また、schema.graphqlは目視で確認するには大きすぎるため、検索してください。
import { createDataSDK, gql } from "@salesforce/platform-sdk"; // gql tags the query string so codegen + eslint validate it
const sdk = await createDataSDK();
const result = await sdk.graphql!.query({ query: GET_ACCOUNTS, variables });
if (result.errors?.length) throw new Error(result.errors.map((e) => e.message).join("; "));
const rows = result.data?.uiapi?.query?.Account?.edges?.map((e) => e.node) ?? []; // unwrap edges/node; read field values via .value
型付き呼び出しパラメーター(query<GetAccountsQuery, GetAccountsQueryVariables>)、CacheControl型、そして Connection からノード型を抽出して型付けを簡潔にするNodeOfConnection<T>は、すべてreferences/sdk-api.mdに記載されています。
これは変更されました(互換性のない変更 — PR #502)。 以前の呼び出し形式
sdk.graphql(...)と以前のパッケージ名は廃止されています。上記のコードだけが正しい形式です。既存のコード(または古いdist/の成果物)で旧 API を見つけても、コピーせず、既存コードの作業に従って変換してください。
sdk.graphql!は WebApp 専用です。 上記の非 null アサーションが正しいのは、バンドルが WebApp のみで実行される場合に限ります。他のサーフェスではクラッシュする可能性があるため、記述する前に判断してください。詳しくは、以下の**サーフェス —!とガード**を参照してください。
インストール済みの型に基づいて SDK の契約を確認する(tier-2a)
@salesforce/platform-sdk は共有のバージョン系列で強制的に公開され、更新も速いパッケージです。この SKILL の説明は、ある時点での呼び出し契約を示すスナップショットです。実際に使用しているバージョンでは、インストール済みの型宣言が正となります。query/mutate を記述する前に、インストール済みの型を読み、そちらを優先してください。
node_modules/@salesforce/platform-sdk/dist/core/data.d.ts—query/mutateのシグネチャ、QueryResult(subscribe/refreshを持つ)とMutationResult(仕様上、どちらも持たない)、CacheControlのユニオン型、デフォルトの TTL。node_modules/@salesforce/platform-sdk/dist/data/index.d.ts—createDataSDK、gql、NodeOfConnection。
優先順位 — インストール済みの .d.ts は、この SKILL の説明より優先されます。 ここに記載されたシグネチャ、型、またはデフォルト値がインストール済みの宣言と異なる場合は、宣言に従い、相違を記録してください。説明に合わせて型を「修正」しないでください。
根拠の段階(1 つのモデル、2 つの軸):
| Tier | 根拠 | 答える内容 | 手段 |
|---|---|---|---|
| tier-1 | GraphQL スキーマ | どのようなデータが存在するか | graphiti / graphql-search.sh(Precondition #2) |
| tier-2a | SDK 契約 | どのように呼び出すか | 上記のインストール済み .d.ts |
| tier-2b | SDK 動作 | どのように動作するか | インストール済みの docs/data/ フォルダー(以下を参照) |
| spine | この SKILL.md | 3 つすべてを統括するワークフローとガードレール。各 tier で根拠を確認できない場合のフォールバック |
.d.ts がない場合のフォールバック — パッケージはインストールされているものの、型宣言を同梱していない(古い、または型が除去されたビルド成果物)場合は、この SKILL の説明をできる限りの根拠として使用してください。このフォールバックは、パッケージがない場合には適用されません。@salesforce/platform-sdk がインストールされていない場合は、停止してインストールしてください(Precondition #1)。手元にない依存関係に対して、説明だけを根拠に呼び出しを記述しないでください。
インストール済みのドキュメントに基づいて SDK の動作を確認する(tier-2b)
同じパッケージには、型と併せて作成された動作ガイドも含まれています。
node_modules/@salesforce/platform-sdk/docs/data/(番号順にファイルを読みます)。tier-2a の .d.ts が呼び出しの契約を定めるのに対し、このフォルダーは契約に明記されていない動作、すなわちキャッシュモデル、サーフェスでの ! とガードの選択、エラー処理の方針、移行時の考え方についての正本です。キャッシュポリシー、サーフェスのアサーション、またはエラー処理の方針を選ぶ前に読み、記載内容を優先してください。tier-2a と同じ優先順位です(インストール済みのソースはこの説明より優先され、存在する場合は、より詳細でバージョンに即した内容です)。
フォルダーがない場合のフォールバック(古い SDK、または型のみのビルド):この SKILL では、作業を進めるために必要な最小限の動作ごとのフォールバックを以下および各セクションに記載しています。それに従ってください。tier-2a と同様に、パッケージがない場合は別です。@salesforce/platform-sdk がインストールされていない場合は、停止してインストールしてください(Precondition #1)。
サーフェス — sdk.graphql! とガード
sdk.graphql/sdk.fetch は実際にオプショナル(型は graphql?: …)であり、! でアサートしてよいかどうかは、実行時クラッシュに関わる判断です。query/mutate を記述する前に判断してください。**フォールバックルール:WebApp 専用のバンドルなら sdk.graphql! を安全に使用できます。WebApp 以外(Mosaic/OpenAI/MCPApps)で実行される可能性があるバンドルなら、先にガードしてください(if (!sdk.graphql) return …)。その後で呼び出します。**WebApp 専用だと証明できない場合はガードしてください。後に別の場所へ出荷される可能性のある裸の ! は Cannot read properties of undefined を発生させますが、TypeScript では検出されません(sdk.fetch! も同様です)。
サーフェスのマトリックス、ポータブルなガードのスニペット、および詳細な理由は、tier-2b の docs/data/ が根拠です(上記のフォールバックを参照)。ガードのスニペットは references/sdk-api.md にも記載されています。
ステップ 0 — タスクを振り分ける
| タスクの内容 | 移動先 |
|---|---|
| レコードを読み取る | 以下の 読み取りワークフロー |
| レコードを作成/更新/削除する | 以下の 書き込みワークフロー |
| オブジェクト/項目のメタデータ、選択リスト値、関連リストのメタデータ、集計 | 以下の レコード CRUD 以外 |
| データが古い/「更新ボタンを追加」/「キャッシュ期間を延長」 | 以下の 鮮度とキャッシュ |
| GraphQL では表現できないもの(Apex REST、ファイルアップロード、Einstein) | references/rest-and-integration.md |
古い sdk.graphql?.(query, vars) コードを移行する | 以下の 既存コードの作業 |
GraphQL で扱えるのはレコードの読み取りと書き込みだけではありません。uiapi
名前空間で公開されているものなら何でも、GraphQL を優先してください(レコード CRUD 以外を参照)。REST を使うのは、データが実際に uiapi の外部にある場合(Apex REST、ファイルアップロード、Einstein)に限ります。references/rest-and-integration.mdを参照してください。
前提条件 — クエリを書く前に確認する
以下の <skill-dir> は、このスキルがインストールされている場所(この
SKILL.md が読み込まれたディレクトリ)を指します。スキーマ検索スクリプトはその中にあります。このスクリプトは、ディレクトリを上にたどって schema.graphql を探すことはありません。親ディレクトリにあるスキーマは別の組織に属している可能性があり、異なる組織の項目に対して検証してしまうためです。スキーマを明示的に指定してください。schema.graphql がある SFDX プロジェクトのルートから実行するか、--schema <path> を渡すか、GRAPHQL_SCHEMA=<path> を設定してください。スクリプトは解決したスキーマを標準エラー出力に [graphql-search] using schema: … と表示します。確認のため、これをざっと見て、正しいファイルを基準にしていることを確かめてください。
| # | 要件 | 確認方法 | 要件を満たさない場合 |
|---|---|---|---|
| 1 | @salesforce/platform-sdk がインストールされており、その仕様および動作のドキュメントを読んでいる | UI バンドルディレクトリの package.json に記載されていることを確認します。次に dist/core/data.d.ts と dist/data/index.d.ts(tier-2a)および docs/data/ フォルダー(tier-2b)を読み、この SKILL の説明よりもそれらを優先してください | 未インストール → ユーザーにインストールするよう伝え、作業を続行できません。インストール済みだが .d.ts/docs/ がない(古い、または型定義のみの成果物)→ 説明文をフォールバックとして使用します |
| 2 | 基準を確認するツールが解決できる | 推奨: UI バンドルディレクトリから npx graphiti sf-gql-discover '{"org":"<alias>","mode":"list_objects"}' を実行すると、オブジェクトが返されます。代替: プロジェクトのルートから bash <skill-dir>/scripts/graphql-search.sh <Entity> を実行すると、schema.graphql not found ではなく検索結果が出力されます | graphiti の依存関係がない/組織の初期化ができない → スクリプトを使用します。スクリプトで schema.graphql が見つからない → --schema <path> を渡すか、UI バンドルディレクトリから npm run graphql:schema を実行します。(CLI の設定については references/graphiti-cli.md を参照してください) |
| 3 | 対象のオブジェクト/項目がデプロイされている | オブジェクトが sf-gql-discover に表示される(または graphql-search.sh <Entity> が出力を返す)ことを確認します | エンティティが見つからない場合、通常はデプロイされていないか、キャッシュ/スキーマが古くなっています。更新するには、npx graphiti sf-gql-connect '{"org":"<alias>","forceRefresh":true}'(CLI)または npm run graphql:schema(スクリプト)を実行します。それでも見つからない場合は、メタデータをデプロイし(platform-metadata-deploy スキルで対応できます)、権限セットを割り当ててから再確認してください |
前提条件を満たしていなくても、コンポーネント、ルート、レイアウトのひな形は作成できます。ただし、データには空の配列/null を使用し、クエリの箇所に
// TODO: add query after schema verification を記して、後で対応する計画項目を追加してください。スキーマのワークフローが完了するまでは、GraphQL 文字列を書かないでください。
読み取りワークフロー
-
まずスキーマを調べます。名前を推測してはいけません。 推奨(graphiti): API 名が少しでも不確かな場合は、説明する前に一覧表示します —
npx graphiti sf-gql-discover '{"org":"<alias>","mode":"list_objects","search":"<intent>"}'で実際の名前を確認し、続いてnpx graphiti sf-gql-discover '{"org":"<alias>","mode":"describe_object","object":"<Entity>"}'で正確なフィールド名/型名、選択リストの値、フィルター可能/ソート可能かどうかを確認します。空のリストやオブジェクトが見つからないことは組織についての事実(名前が間違っている、またはデプロイされていない)であり、ツールの失敗ではありません — 再度一覧表示するか、forceRefreshを実行してください。この場合にスクリプトへフォールバックしてはいけません(ガードレール 2 を参照)。フォールバックが必要なのは、CLI を本当に実行できない場合のみです(graphiti 依存関係がない/組織をプライムできない): SFDX プロジェクトのルートからbash <skill-dir>/scripts/graphql-search.sh <Entity>を実行します。 (詳細なルール:references/graphql-hand-authoring.md。) -
クエリを作成します。 推奨 — graphiti でコンパイルします:
npx graphiti sf-gql-list '{"org":"<alias>","object":"<Entity>","fields":[…],"first":N}'は、@optional、value/displayValue、edges/node、first:/pageInfoが適用済みの{ query, variables, types, warnings }エンベロープを返します。warnings: []であることを確認してください(空でない配列は、オブジェクトがプライム済みスキーマに含まれていないことを意味し、クエリの機能が低下しています。そのまま使用してはいけません)。その後、queryをそのままインラインのgql(シンプルな場合)または外部の.graphqlファイル(1 ファイルにつき 1 オペレーション。バンドラーの?rawサフィックスを使ってインポートします —import Q from "./q.graphql?raw"により、ファイルがプレーンな文字列として読み込まれます)に貼り付けます。フォールバック — 手動で作成する場合: 選択可能な FLS 制御対象フィールドすべてに@optionalを適用します — スカラーリーフフィールド(Name @optional { value })、親子リレーションシップ、およびそれらの内部のフィールドが対象です。ただし、Id、コネクションの構造要素(edges、node、コネクションフィールド自体)、pageInfoには適用しません。graphiti の出力ではこれらにディレクティブが付かず、これが標準的な配置です。常にfirst:を設定し、ページングされる可能性がある場合はpageInfoを含めてください。いずれの場合も、詳しい仕組みとプライム済み/機能低下時の動作については、references/graphiti-cli.md を参照してください。 -
型を生成します —
npm run graphql:codegen(UI バンドルディレクトリから実行)→src/api/graphql-operations-types.ts。 -
生成された型を使って
query()を呼び出します:import type { GetAccountsQuery, GetAccountsQueryVariables } from "../graphql-operations-types"; const result = await sdk.graphql!.query<GetAccountsQuery, GetAccountsQueryVariables>({ query: GET_ACCOUNTS, variables: { first: 20 }, // cacheControl, // optional — see Freshness & caching }); -
結果を処理します。
result.dataとresult.errorsは初期スナップショット、result.subscribe/result.refreshはリアクティブなハンドルです。dataを読み取る前に、必ずerrorsを確認してください:if (result.errors?.length) throw new Error(result.errors.map((e) => e.message).join("; ")); const rows = result.data?.uiapi?.query?.Account?.edges?.map((e) => e.node) ?? [];
@optional によってフィールドが省略される可能性があるため、?./?? を使って、結果を利用するコードで問題が起きないようにしてください。エラー処理の方針(厳格/許容/判別)はティア 2bの docs/data/ に基づきます(フォールバック:
ガードレール #1 — 必ず result.errors を確認してください)。NodeOfConnection の型定義については、references/sdk-api.md を参照してください。
書き込みワークフロー
上記の1~3(スキーマの参照 → ミューテーションの作成 → コード生成)と同じです。graphiti でミューテーションをコンパイルするには、sf-gql-create / sf-gql-update / sf-gql-delete を使用します。これらは
uiapi { <Object>Create(input: $input) { Record {…} } } という形式を出力します。types フィールドから入力の形式を確認できます。詳細はreferences/graphiti-cli.mdをご覧ください。
4. mutate() を呼び出します — オプションのキーは query ではなく mutation であり、
ミューテーションはキャッシュされないことに注意してください。実行時の variables の形式は操作ごとに異なります —
値は生の値({value} でラップしません。これは読み取り時の形式であり、書き込みが失敗します)で、
エンティティのキーの下にネストします。
// create — input.<Entity> holds the new field values
variables: { input: { Account: { Name: "Acme", Industry: "Technology" } } }
// update — sibling Id alongside the entity key
variables: { input: { Id: "001…", Account: { Industry: "Finance" } } }
// delete — Id only, no entity key (generic RecordDeleteInput)
variables: { input: { Id: "001…" } }
const { data, errors } = await sdk.graphql!.mutate<CreateAccountMutation, CreateAccountMutationVariables>({
mutation: CREATE_ACCOUNT,
variables: { input: { Account: { Name: "Acme" } } },
});
if (errors?.length) throw new Error(errors.map((e) => e.message).join("; "));
これは、spine が管理する variables の形式です。CLI の types フィールドの解釈については
references/graphiti-cli.mdを、GraphQL ドキュメントのフィールド制約
(createable/updateable、ApiName の参照、@{alias} による連鎖)については
references/graphql-hand-authoring.mdをご覧ください。
5. 影響を受ける読み取りを再取得します。 mutate() に refresh はありません。書き込み後にライブリストを
更新するには、先ほどの query() 呼び出しから QueryResult(例:
accountsResult)を保持し、await accountsResult.refresh() を呼び出します(強制的に再取得し、
購読者に変更を通知します) — これは読み取り側のハンドルであり、mutate() の戻り値ではないことに
注意してください。**鮮度とキャッシュ**をご覧ください。
ミューテーションの構文には厳密なルールがあります。uiapi(input: { allOrNone: ... }) の内側にラップし、createable/updateable フィールドのみを使用します。Create/Update の出力は常に Record ですが、Delete には
Record フィールドがありません — Id のみを選択してください。完全なテンプレート、連鎖、制約については、
references/graphql-hand-authoring.mdをご覧ください。
レコードの CRUD 以外
uiapi 名前空間は、レコードの読み書きだけに限りません。REST を使う前に、GraphQL ですでに対応しているか確認してください — 同じ sdk.graphql!.query() 呼び出しで、サブセレクションを変えて利用できます。トップレベルの uiapi フィールドは次のとおりです。
| 必要な機能 | 使用するもの | 返される値 |
|---|---|---|
| レコードのクエリ | uiapi { query { <Entity>(...) } } | レコード(読み取りワークフロー) |
| 行を取得せずに件数、合計、グループ化した集計値を取得 | uiapi { aggregate { <Entity>(groupBy: …) } } | 集計バケット |
オブジェクト/フィールドのメタデータ — ラベル、データ型、createable/updateable、レコードタイプ | uiapi { objectInfos(apiNames: […]) } | ObjectInfo[] |
| 選択リスト値(レコードタイプごと) | uiapi { objectInfos(objectInfoInputs: […]) { fields … on PicklistField { … } } } | 選択リスト値 |
| 関連リストのメタデータ — 親の関連リストに表示する列、並び順 | uiapi { relatedListByName(parentApiName, relatedListName) } | RelatedListInfo |
レコードの読み取りと同じルールに従います。すべての型/フィールドを事前に確認し、FLS が適用される場合は @optional を使用して、result.errors を確認してください。集計は npx graphiti sf-gql-aggregate でコンパイルできます(groupBy と aggregations を渡します)。オブジェクトのメタデータ、選択リスト、関連リストは手動で作成します —
テンプレートはreferences/graphql-hand-authoring.mdをご覧ください。
関連する2つの機能(現在のユーザーのレコードと レイアウトの提供)については、 このスキルでクエリ形式を説明する前に、最新の組織スキーマで確認する必要があります — 追加対応事項として記録されており、現時点ではここでは扱いません。
更新頻度とキャッシュ
キャッシュモデルの根拠は tier-2b の docs/data/ にあります。キャッシュキーの仕組み、何がキャッシュされるか、baseUrl による共有の詳細、キャッシュされないサーフェスの意味、リアクティブハンドルに関する注意点はすべてここに記載されています(references/caching.md では、バージョンスタンプ付きのフォールバックとしてこれを再掲しています)。フォルダーがない場合でも作業できるだけの、重要なフォールバックは次のとおりです。
- WebApp ではキャッシュがデフォルトで有効です。すべての
query()が 300s でキャッシュされます。オプトイン用フラグも、ファクトリーも、/cacheサブパスもありません。独自のキャッシュを実装しないでください(React Query、SWR、localStorage、手書きのMap)。mutate()はキャッシュされません。 - ホストと API バージョンで共有されます。同じホスト と
apiVersionを対象とする別のcreateDataSDK()からの、同じクエリと変数はキャッシュヒットとなり、ネットワークリクエストは 1 回だけ発生します。インスタンスごとのフェッチパイプラインは分離されたままです。 - 2 種類の更新頻度制御ツールは別々のものです。混同しないでください。
- 呼び出しごとの
cacheControl(オプションオブジェクトに指定する一度限りのポリシー):「no-cache」(キャッシュを迂回して、結果をキャッシュに書き戻す)/「only-if-cached」/{ type: "max-age", maxAge: <seconds> }。デフォルトは 300s です。読み取り関数のオプションパラメーターとして渡し、各ポリシーを同じデータレイヤーファイル内の薄い名前付きエクスポートとして公開します(refreshAccounts→ 「no-cache」、offlineAccounts→ 「only-if-cached」など)。「only-if-cached」でキャッシュミスが発生すると、extensions.code === "CACHE_MISS"を含むresult.errorsが返されます。空の状態を表示し、ネットワークにフォールバックしないでください(オフラインファーストの意味がなくなります)。 - リアクティブな
subscribe/refresh(QueryResultのライブハンドル):subscribe(cb)は以降のスナップショットでのみ呼び出されます(破棄時には必ずunsubscribeしてください)。refresh()はキャッシュを迂回して再取得し、サブスクライバーに結果を通知します。mutate()にはrefreshがないため、その後に使用してください。複数のサブスクライバーへの通知と独立性については、tier-2b のdocs/data/を根拠としてください。
- 呼び出しごとの
既存コードの作業(移行)
既存コードが実際に古い API を使用している場合にのみ、この手順に進んでください。つまり、@salesforce/sdk-data をインポートしているか、呼び出し可能な sdk.graphql(query, vars) 形式を使用している場合です。新しい読み取りや書き込みでは、移行については無視し、読み取りワークフロー/書き込みワークフローを使用してください。これらには、すでに唯一正しい API が示されています。
古いコードを変換する場合は、変更前と変更後の差分(インポート、query/mutate の呼び出し、オプショナルチェーンから非 null アサーションへの変更、codegen の型の配置)とチェックリストが記載された references/migration.md を参照してください。移行先の API は、上記の読み取り/書き込みワークフローで指定されているものとまったく同じです。移行では、古い形式を置き換えるだけです。
プラットフォームのガードレール — 決して後退させないでください
これらは SDK とは独立した Salesforce GraphQL プラットフォームの動作です。違反すると、ランタイムでエラーが通知されずに失敗します。(詳細とテンプレート:references/graphql-hand-authoring.md。)
- HTTP 200 ≠ 成功 — 必ず
result.errorsを解析してください。失敗時でも Promise は解決されます。 - スキーマだけが信頼できる情報源です。検証し、決して推測しないでください。 使用前に、graphiti の
sf-gql-discover(推奨)またはbash <skill-dir>/scripts/graphql-search.sh <Entity>を使って、すべての エンティティ/フィールド/型を検証してください。大文字と小文字は区別されます。__c/__e、エンティティのサフィックス_Record(v60+)。graphiti がプライミング済みの場合、 「not found」/空の結果/Cannot query fieldという回答(graphql-codegen/@graphql-eslintからのものも含み、 メッセージがschema.graphqlを示している場合も同様)は、組織についての事実です。名前が誤っているか、 メタデータがデプロイされていない、またはアクセスできないことを意味し、ツールの 失敗ではありません。操作を修正するか、メタデータをデプロイし(platform-metadata-deploy スキル)、 権限を割り当ててから、更新してください(sf-gql-connect --forceRefresh/npm run graphql:schema)。スクリプトにフォールバックしたり、 その場しのぎで手書きしたり、名前を推測したりしないでください。エンティティ名やフィールド名を推測すると、 クエリ全体が実行時にエラーなく失敗します。検索しても解決しない場合は、いつまでも試行を繰り返さず、ユーザーに尋ねてください。schema.graphqlとコード生成の出力 (src/api/graphql-operations-types.ts)は、読み取り専用の生成ミラーです。決して開いたり編集したりしないでください (# DO NOT EDITマーカーがあれば必ず従ってください)。不足している型を手作業で追加すれば、コード生成や lint は通りますが、 組織へのアクセス権は得られません。実行時まで失敗を隠すだけです。スクリプトへのフォールバックが許されるのは、 CLI をまったく実行できない場合(依存関係がない/SCHEMA_PRIME_FAILED)に限られます。 - FLS で制限されるすべてのフィールドに、各ネストレベルで
@optionalを付けてください — スカラーのリーフフィールドに加え、 各親/子リレーションシップと、その内部のフィールドにも付けてください(付けないとクエリ全体が FLS エラーになります。v65+)。Id、コネクションの構成要素(edges、node、コネクションフィールド)、pageInfoには 付けないでください。これらは FLS の対象ではなく、graphiti の出力でも装飾なしのままです。?./??を使って値を受け取ってください。配置ルール:references/graphql-hand-authoring.md。 - ミューテーションは
uiapi(input: { allOrNone: ... })の内側に配置します。allOrNoneを明示的に設定してください。 出力には子/ナビゲート参照フィールドが含まれません。出力フィールド名は文字どおりRecordです(ルール 2 のエンティティサフィックス_Recordとは無関係です)。Delete →Idのみ。GA v66+。 - ページネーションは明示的に指定してください — 必ず
first:を設定してください。サーバーはエラーなく上限を 10 に制限するため、 行が欠落します。前方向のみ対応です(first/after。last/beforeは不可)。upperBound(v59+)を設定すると、1 リクエストあたりの上限を引き上げ、大きなデータセットを扱えます (設定する場合、firstは 200~2000 にする必要があります)。 - SOQL のガバナー制限が適用されます —
uiapiクエリは SOQL にコンパイルされるため、同じガバナー 制限が継承されます。サブクエリは ≤10、子から親への階層は ≤5、親から子への階層は ≤1、 サブクエリあたりのレコード数は ≤2,000 です。制限を超える場合は、複数のリクエストに分割してください。 - フィールド値のラッパー — 生の値は
.valueで取得します。displayValueはサーバーで UI 向けに 書式設定された文字列です。フィールドを表示にも操作にも使う場合(通貨、日付、選択リスト)は、 クライアント側で再書式設定しないよう、valueとdisplayValueの両方を選択してください。 表示専用フィールドではdisplayValueのみで構いません。 - 複合フィールド — ラッパー(
BillingAddress)ではなく、構成要素(BillingCity)で絞り込み/並べ替えを行ってください。 - サポートされている API のみ使用してください — GraphQL(
uiapi)、UI API REST、Apex REST、Connect REST、sdk.fetch経由の Einstein LLM。使用不可:Enterprise SOQL/query、Aura 対応 Apex、Chatter (uiapi.currentUserを使用)。references/rest-and-integration.md を参照してください。
1 つの SDK 規約はこの一覧ではなく、ワークフローに記載されています(プラットフォームの動作ではありません)。 操作を書いた後は必ず
npm run graphql:codegenを実行し、生成された型を使用してください (Read workflow のステップ 3)。Pre-flight checklist にも記載されています。graphiti がこれらの大半を自動で適用します。 プライミング済みスキーマ内のオブジェクトに対して
sf-gql-*でクエリをコンパイルすると、 ルール 3(@optional)、4(ミューテーションのRecord出力 エンベロープとエンティティをキーにした入力 —allOrNoneは対象外。これは引き続き自分で追加します)、 5(first:/pageInfo)、7(value/displayValueのラッパー)はすでに満たされます。 そのため、queryを再導出せず、そのまま貼り付けることが重要です。ルール 1 (result.errorsの確認)、6(ガバナー制限)、8(複合フィールド)、9(サポート対象 API)は引き続き自分で対応してください。 また、自動化が実行されるのはオブジェクトがプライミングされている場合だけです。warnings配列が空でない場合はプライミングされておらず、出力されるクエリは機能が制限された状態(裸のフィールド、ガードレールなし)です。 references/graphiti-cli.md を参照してください。
コマンドと構成
<skill-dir>/ ← wherever this skill is installed
└── scripts/graphql-search.sh ← schema lookup (ships with the skill)
<project-root>/ ← SFDX project root; run the script from here
├── schema.graphql ← generated mirror; grep target (never open or edit; script reads ./schema.graphql)
└── force-app/main/default/uiBundles/<app>/ ← UI bundle dir
├── package.json ← npm scripts
└── src/api/ ← queries, generated types, SDK calls
| コマンド | 実行場所 | 目的 |
|---|---|---|
npx graphiti sf-gql-discover '{…}' | UI バンドルディレクトリ | ライブ組織のオブジェクト/フィールドを検出します(推奨される根拠確認方法) |
npx graphiti sf-gql-<list|detail|aggregate|create|update|delete|raw> '{…}' | UI バンドルディレクトリ | ガードレールが適用されたクエリ/ミューテーションをコンパイルします(references/graphiti-cli.md) |
npx graphiti sf-gql-connect '{"org":"<alias>","forceRefresh":true}' | UI バンドルディレクトリ | デプロイ後に graphiti のスキーマキャッシュを更新します |
bash <skill-dir>/scripts/graphql-search.sh <Entity> | プロジェクトルート(または --schema <path> を指定。ディレクトリ階層をたどった検索はしません) | スキーマ検索のフォールバック(ローカルの schema.graphql を grep します) |
npm run graphql:schema | UI バンドルディレクトリ | schema.graphql を取得/更新します(フォールバックスクリプト用) |
npm run graphql:codegen | UI バンドルディレクトリ | 操作の型を生成します |
npx eslint <file> | UI バンドルディレクトリ | リントします(gql スキーマ違反を検出します) |
事前確認チェックリスト
- 使用するサーフェスを決定済み:WebApp のみの場合に限り
sdk.graphql!を使用し、それ以外ではif (!sdk.graphql) …でガードします(Surfaces) - SDK の契約をインストール済みの
dist/*.d.tsで確認済み(説明文より型を優先します)(tier-2a) - SDK の動作をインストール済みの
docs/data/で確認済み(説明文よりドキュメントを優先し、ない場合はフォールバックします)(tier-2b) - すべてのフィールド/エンティティを確認済み —
sf-gql-discover(推奨)またはgraphql-search.sh(フォールバック。適切なスキーマを対象にします) - graphiti でコンパイルした場合:
warnings: []を確認済み(空でない場合は機能が制限されたクエリのため、リリースしないでください)。queryをそのまま貼り付け済み - FLS で制御されるフィールドとリレーションシップに
@optionalを指定済み(Id/edges/node/pageInfoは対象外)。利用側のコードで?./??を使用済み -
result.dataを読み取る前にresult.errorsを確認済み - キャッシュを検討済み:デフォルトの 300s で問題ないか、
cacheControl/refreshを意図的に選択済み -
npm run graphql:codegenを実行し、生成された型を使用済み。npx eslintに合格済み