关于 experience-ui-bundle-salesforce-data-access
面向 Salesforce UI bundle 开发者的数据访问工作流技能,指导使用 `@salesforce/platform-sdk`。内容涵盖 GraphQL 模式验证、按运行界面采取防护措施、缓存及 REST 使用。
主要功能
- 01SDK 查询与变更工作流
- 02GraphQL 模式验证
- 03按运行界面进行 API 防护
- 04缓存与刷新指导
- 05REST 集成指导
- 06旧版 API 迁移
适用场景
- 01编写 Salesforce 记录查询
- 02为 UI 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 数据访问(UI bundles)
UI bundle 中的所有 Salesforce 数据访问都通过 @salesforce/platform-sdk
数据 SDK 完成。SDK 会处理身份验证、CSRF 和基础 URL 解析;在 WebApp
环境中,默认还会缓存每个 GraphQL 查询。
本文是工作流 + 防护措施的主干。详细内容请参阅链接文档:
- references/graphiti-cli.md —
graphitiCLI(sf-gql-*命令),可将小型 JSON 规范编译为符合 schema 且应用了防护措施的查询 + 变量 + 类型。以下步骤中,首选使用它来编写 GraphQL;不可用时则回退到 schema-grep 脚本。 - references/sdk-api.md —
query/mutate调用接口 + 生成类型的 放置位置;相关行为细节(环境、错误处理方式、QueryResult)以 tier-2b 为准。 - references/caching.md — 默认开启的缓存 + 两种刷新模式;
安装后,相关行为以 tier-2b 的
docs/data/为准;本文提供完整的版本标注回退内容。 - references/graphql-hand-authoring.md — schema 查找、读取 /
修改模板、所有平台防护措施(
@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。查询之前,请根据 schema 验证每个实体和字段:
一个未经验证的字段就会导致整个查询在运行时失败;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
类型,以及 NodeOfConnection<T>(从 Connection 中提取节点类型,以便进行清晰的类型标注)都位于
references/sdk-api.md。
此处已更改(破坏性变更 — PR #502)。 之前的可调用形式
sdk.graphql(...)和 之前的包名都已废弃——上方代码是唯一正确的写法。如果在现有代码(或过时的dist/产物)中遇到 旧 API,请勿复制;请按照处理现有代码中的说明转换。
sdk.graphql!仅适用于 WebApp。 上方的非空断言仅在 bundle 只运行于 WebApp 时才正确。 在其他环境中可能会导致崩溃——请在编写前先作出判断。 请参阅下文的**运行环境 —!与 guard**。
依据已安装的类型确定 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 的说明。 如果此处的签名、类型或默认值与已安装的声明不一致,请遵循声明并注明差异;不要为了匹配说明而“修正”类型。
依据层级(一个模型,两个维度):
| 层级 | 依据 | 回答的问题 | 途径 |
|---|---|---|---|
| tier-1 | GraphQL schema | 有哪些数据 | graphiti / graphql-search.sh(前置条件 #2) |
| tier-2a | SDK 契约 | 如何调用 | 上述已安装的 .d.ts |
| tier-2b | SDK 行为 | 行为方式 | 已安装的 docs/data/ 文件夹(见下文) |
| 主干 | 本 SKILL.md | 协调这三者的工作流和防护措施;当某个层级无法提供依据时作为回退 |
.d.ts 不存在时的回退方案——软件包已安装,但没有提供声明(构建产物过时或已剥离类型)。此时尽可能依据本 SKILL 的说明。此回退方案不适用于软件包缺失的情况:如果未安装 @salesforce/platform-sdk,请停止并安装它(前置条件 #1)——不要根据说明为尚不存在的依赖编写调用。
依据已安装的文档确定 SDK 行为(tier-2b)
同一软件包还会在类型文件旁附带一份编写好的行为指南:
node_modules/@salesforce/platform-sdk/docs/data/(按编号排列的文件,请依序阅读)。
tier-2a 的 .d.ts 确定调用的契约;此文件夹则是契约未明确说明的行为的权威依据——缓存模型、表面层使用 ! 还是守卫的选择、错误处理方式,以及迁移思路。在选择缓存策略、表面断言或错误处理方式之前,请先阅读并以其为准——优先级与 tier-2a 相同(已安装的源码优先于本文说明;若该文件夹存在,其中的内容更完整,也与当前版本一致)。
文件夹不存在时的回退方案(较旧的 SDK 或仅包含类型的构建):本 SKILL 在下文及各个部分保留了精简的逐项回退说明,仅供你继续操作;请据此执行。与 tier-2a 一样,软件包缺失是另一种情况:如果未安装 @salesforce/platform-sdk,请停止并安装它(前置条件 #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 命名空间公开的任何内容,都应优先使用它(参见超越记录 CRUD)。仅当数据确实位于 uiapi 之外(Apex REST、文件上传、Einstein)时才使用 REST——参见 references/rest-and-integration.md。
前置条件 — 编写任何查询之前请先验证
下文中的 <skill-dir> 指此技能的安装位置(加载此 SKILL.md 的目录)。模式查找脚本随此技能一同提供。该脚本不会通过向上遍历目录树来查找 schema.graphql——祖先目录中的模式可能属于不同的 org,导致字段依据错误的模式进行验证。请显式指定模式:从 SFDX 项目根目录(schema.graphql 所在位置)运行,或传入 --schema <path> / 设置 GRAPHQL_SCHEMA=<path>。脚本会显示解析到的模式(在 stderr 上显示 [graphql-search] using schema: …)——请查看确认使用的是正确文件。
| # | 要求 | 验证方式 | 缺少时的处理 |
|---|---|---|---|
| 1 | 已安装 @salesforce/platform-sdk,且已阅读其契约与行为文档 | UI bundle 目录中的 package.json 列出了该依赖;然后阅读 dist/core/data.d.ts + dist/data/index.d.ts(tier-2a)以及 docs/data/ 文件夹(tier-2b),并以这些内容为准,而非本 SKILL 中的说明 | 未安装 → 告知用户安装;无法继续。已安装但缺少 .d.ts / docs/(制品过时或仅包含类型)→ 使用说明文本作为后备 |
| 2 | 可使用模式依据工具 | **首选:**从 UI bundle 目录运行 npx graphiti sf-gql-discover '{"org":"<alias>","mode":"list_objects"}',并返回对象。**后备:**从项目根目录运行 bash <skill-dir>/scripts/graphql-search.sh <Entity>,应输出查找结果,而不是“schema.graphql not found” | 没有 graphiti 依赖 / 无法初始化 org → 使用脚本。脚本找不到 schema.graphql → 传入 --schema <path>,或从 UI bundle 目录运行 npm run graphql:schema。(references/graphiti-cli.md 介绍 CLI 设置) |
| 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}'会返回{ query, variables, types, warnings }信封,其中已应用@optional、value/displayValue、edges/node和first:/pageInfo。确认warnings: [](非空数组表示该对象不在已初始化的架构中 — 查询已降级;不要发布),然后将query原样粘贴到内联gql(简单情况)或外部.graphql文件中(每个文件仅放一个操作,使用打包器的?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 bundle 目录)→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 可能会省略字段)。错误处理策略(严格/宽容/区分类型)以 tier-2b docs/data/ 为依据(回退方案:
防护规则 #1 — 务必检查 result.errors);NodeOfConnection 类型说明见 references/sdk-api.md。
编写工作流
1–3 步同上(查询架构 → 编写 mutation → 代码生成)。要用 graphiti 编译 mutation,请使用
sf-gql-create / sf-gql-update / sf-gql-delete——它们会生成
uiapi { <Object>Create(input: $input) { Record {…} } } 结构;types 字段会告诉你输入结构。
详情:references/graphiti-cli.md。
4. 调用 mutate()——注意选项键是 mutation,不是 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("; "));
这是主干负责的 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() 返回的内容。参见
新鲜度与缓存。
Mutation 语法要求严格:必须包装在 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。
两项相关功能(当前用户记录和布局交付)需要先根据当前 org 架构确认查询结构, 本技能才能记录相关用法——此项已列为后续工作,目前尚未涵盖。
新鲜度与缓存
缓存模型以第 2b 层 docs/data/ 为准——缓存键机制、缓存内容、按 baseUrl 共享的细节、未缓存表面的语义,以及响应式句柄的细微之处都在其中(references/caching.md 将其重述为带版本戳的回退方案)。关键回退方案(即使该文件夹不存在,也足以采取行动):
- WebApp 默认启用缓存——每个
query()的缓存时间都是 300s;无需启用标记、工厂或/cache子路径。不要自行构建缓存(React Query、SWR、localStorage、手写Map)。mutate()永不缓存。 - 按主机和 API 版本共享——来自另一个
createDataSDK()、且目标为相同主机和apiVersion的相同查询与变量会命中缓存,也就是只发起一次网络请求;每个实例的 fetch 流程仍彼此隔离。 - 两种不同的新鲜度工具——不要混为一谈:
- 单次调用的
cacheControl(选项对象上的一次性策略):"no-cache"(绕过缓存,但会写回)/"only-if-cached"/{ type: "max-age", maxAge: <seconds> };默认 300s。将其作为可选参数传给读取函数,并在同一数据层文件中将每种策略公开为精简的具名导出(refreshAccounts→"no-cache"、offlineAccounts→"only-if-cached",……)。"only-if-cached"未命中时,会通过result.errors暴露,且extensions.code === "CACHE_MISS"——渲染空状态,不要回退到网络(那会违背离线优先)。 - 响应式
subscribe/refresh(QueryResult上的实时句柄):subscribe(cb)仅在后续快照触发(拆除时务必调用unsubscribe);refresh()会重新获取数据、绕过缓存,并推送给订阅者——在mutate()之后使用它(mutate()没有refresh)。多订阅者扇出/独立性以第 2b 层docs/data/为准。
- 单次调用的
处理现有代码(迁移)
只有现有代码确实使用旧 API 时,才进入此流程——也就是说,它导入了
@salesforce/sdk-data,或调用了可调用形式 sdk.graphql(query, vars)。对于任何新的
读取/写入,完全忽略迁移,直接使用读取工作流/
写入工作流——其中已经展示了唯一正确的 API。
如果确实需要转换旧代码,请参阅 references/migration.md,查看 转换前后的差异(导入、query/mutate 调用、可选链 → 非空断言、代码生成类型的放置位置)和检查清单。目标 API 与上文读取/写入工作流规定的完全一致——迁移只需将旧形式替换为该形式。
平台防护规则——绝不可回退
这些是 Salesforce GraphQL 平台行为,与 SDK 无关。违反会导致静默运行时故障。(详情和模板:references/graphql-hand-authoring.md。)
- HTTP 200 ≠ 成功 — 始终解析
result.errors;即使失败,Promise 也会正常兑现。 - Schema 是唯一事实来源 — 只验证,绝不臆造。 使用 graphiti
sf-gql-discover(首选)或bash <skill-dir>/scripts/graphql-search.sh <Entity>验证每个实体/字段/类型后再使用。区分大小写;__c/__e;实体后缀_Record(v60+)。graphiti 已完成初始化时,回答“未找到”/为空/Cannot query field(包括来自graphql-codegen/@graphql-eslint的回答,即使消息指向schema.graphql) 都是关于组织的事实 — 表示名称错误或元数据未部署/无法访问,而非工具 故障:修正操作,或部署元数据(使用 platform-metadata-deploy 技能)- 分配权限 + 刷新(
sf-gql-connect --forceRefresh/npm run graphql:schema)。不要 改用脚本、手动绕过,或猜测名称 — 猜错的实体或字段会导致整个查询在运行时静默失败;如果查找一直没有结果,请询问用户, 不要继续陷入循环。schema.graphql和 codegen 输出 (src/api/graphql-operations-types.ts)是只读的生成镜像 — 切勿打开或编辑它们 (遵循任何# DO NOT EDIT标记)。手动添加缺失类型虽然能通过 codegen/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实体后缀无关)— 删除操作仅返回Id。GA v66+。 - 显式分页 — 始终设置
first:,因为服务端会静默限制为 10 条,导致行被丢弃且没有错误提示;仅支持向前分页(first/after,不支持last/before);upperBound(v59+)可提高单次请求的上限,适用于大型数据集(设置后,first必须为 200–2000)。 - SOQL 管控限制同样适用 —
uiapi查询会编译为 SOQL,因此也受相同的管控 限制:≤10 个子查询、≤5 层子到父关系、≤1 层父到子关系、 每个子查询 ≤2,000 条记录。如将超出限制,请拆分为多个请求。 - 字段值包装器 — 通过
.value读取原始值;displayValue是服务端格式化后供 UI 使用的字符串。字段既用于显示又用于操作时(货币、 日期、选项列表),同时选择value和displayValue,避免在 客户端重新格式化。仅用于显示的字段只需选择displayValue。 - 复合字段 — 使用组成字段(
BillingCity)筛选/排序,而不是包装字段(BillingAddress)。 - 仅支持指定 API — GraphQL(
uiapi)、UI API REST、Apex REST、Connect REST、 通过sdk.fetch调用 Einstein LLM。不支持:Enterprise SOQL/query、Aura-enabled Apex、Chatter (使用uiapi.currentUser)。请参阅 references/rest-and-integration.md。
SDK 有一项约定写在工作流中,而不在此列表中(它不是平台行为): 编写操作后,务必运行
npm run graphql:codegen,并使用生成的类型 (阅读工作流第 3 步)。预检清单中也有说明。graphiti 会为你应用其中大部分规则。 使用
sf-gql-*编译针对已预加载到 schema 中的对象的查询时,规则 3(@optional)、4(mutation 的Record输出封套和以实体为键的输入——不是allOrNone,后者仍需自行添加)、5(first:/pageInfo)和 7(value/displayValue封装)都会自动满足——这正是你应当原样粘贴query,而不是重新推导它的原因。规则 1(检查result.errors)、6(Governor 限制)、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 bundle 目录 | 根据在线 org 发现对象/字段(首选的依据来源) |
npx graphiti sf-gql-<list|detail|aggregate|create|update|delete|raw> '{…}' | UI bundle 目录 | 编译已应用防护规则的查询/mutation(references/graphiti-cli.md) |
npx graphiti sf-gql-connect '{"org":"<alias>","forceRefresh":true}' | UI bundle 目录 | 部署后刷新 graphiti 的 schema 缓存 |
bash <skill-dir>/scripts/graphql-search.sh <Entity> | 项目根目录(或传入 --schema <path>;不会向上遍历目录树) | Schema 查找回退方案(在本地 schema.graphql 中使用 grep) |
npm run graphql:schema | UI bundle 目录 | 获取/刷新 schema.graphql(供回退脚本使用) |
npm run graphql:codegen | UI bundle 目录 | 生成操作类型 |
npx eslint <file> | UI bundle 目录 | Lint(可捕获 gql schema 违规) |
预检清单
- 已确定使用界面:仅 WebApp 使用时才用
sdk.graphql!;否则用if (!sdk.graphql) …进行检查(界面) - 已根据已安装的
dist/*.d.ts核实 SDK 契约(类型优先于说明文字)(tier-2a) - 已根据已安装的
docs/data/核实 SDK 行为(文档优先于说明文字;缺失时使用回退方案)(tier-2b) - 已验证每个字段/实体——使用
sf-gql-discover(首选)或graphql-search.sh(回退方案,针对正确的 schema) - 若使用 graphiti 编译:已确认
warnings: [](非空表示查询已降级,不要发布);已原样粘贴query - FLS 限制的字段和关系上有
@optional(而非Id/edges/node/pageInfo);在使用这些数据的代码中使用?./?? - 在读取
result.data前检查了result.errors - 已考虑缓存:确认默认的 300s 没问题,或有意选择
cacheControl/refresh - 已运行
npm run graphql:codegen;已使用生成的类型;npx eslint检查通过