mcpField
E
experience-ui-bundle-salesforce-data-access

experience-ui-bundle-salesforce-data-access

github.com/forcedotcom开发1.1k5.2k 次安装1 天前更新
代码仓库

关于 experience-ui-bundle-salesforce-data-access

面向 Salesforce UI bundle 开发者的数据访问工作流技能,指导使用 `@salesforce/platform-sdk`。内容涵盖 GraphQL 模式验证、按运行界面采取防护措施、缓存及 REST 使用。

主要功能

  1. 01SDK 查询与变更工作流
  2. 02GraphQL 模式验证
  3. 03按运行界面进行 API 防护
  4. 04缓存与刷新指导
  5. 05REST 集成指导
  6. 06旧版 API 迁移

适用场景

  1. 01编写 Salesforce 记录查询
  2. 02为 UI bundle 添加变更操作
  3. 03处理缓存或过期结果
  4. 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.md
graphiti-cli.md
graphql-hand-authoring.md
migration.md
rest-and-integration.md
sdk-api.md
scripts/
graphql-search.sh

详细介绍

Salesforce 数据访问(UI bundles)

UI bundle 中的所有 Salesforce 数据访问都通过 @salesforce/platform-sdk 数据 SDK 完成。SDK 会处理身份验证、CSRF 和基础 URL 解析;在 WebApp 环境中,默认还会缓存每个 GraphQL 查询。

本文是工作流 + 防护措施的主干。详细内容请参阅链接文档:

  • references/graphiti-cli.md — graphiti CLI(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-1GraphQL schema有哪些数据graphiti / graphql-search.sh(前置条件 #2)
tier-2aSDK 契约如何调用上述已安装的 .d.ts
tier-2bSDK 行为行为方式已安装的 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 字符串。


读取工作流

  1. 先查找架构,绝不猜名称。 首选方式(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。)

  2. 编写查询。 首选方式 — 使用 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。

  3. 生成类型 — 运行 npm run graphql:codegen(从 UI bundle 目录)→ src/api/graphql-operations-types.ts。

  4. 使用生成的类型调用 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
    });
    
  5. 处理结果。 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 流程仍彼此隔离。
  • 两种不同的新鲜度工具——不要混为一谈:
    1. 单次调用的 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"——渲染空状态,不要回退到网络(那会违背离线优先)。
    2. 响应式 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。)

  1. HTTP 200 ≠ 成功 — 始终解析 result.errors;即使失败,Promise 也会正常兑现。
  2. 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), 才回退使用脚本。
  3. 每个嵌套层级中受 FLS 限制的字段都要加上 @optional — 标量叶字段,以及每个 父/子关系和其中的字段(否则整个查询都会因 FLS 失败,v65+)。 不要给 Id、连接结构字段(edges、node、连接字段)或 pageInfo 添加修饰 — 这些字段不受 FLS 限制,graphiti 输出中也不会为它们添加修饰。使用 ?./?? 消费。 添加位置规则:references/graphql-hand-authoring.md。
  4. 变更操作放在 uiapi(input: { allOrNone: ... }) 下;显式设置 allOrNone; 输出不包含子字段/导航引用字段;输出字段的名称就是 Record(与规则 2 中的 _Record 实体后缀无关)— 删除操作仅返回 Id。GA v66+。
  5. 显式分页 — 始终设置 first:,因为服务端会静默限制为 10 条,导致行被丢弃且没有错误提示;仅支持向前分页(first/after,不支持 last/before); upperBound(v59+)可提高单次请求的上限,适用于大型数据集(设置后,first 必须为 200–2000)。
  6. SOQL 管控限制同样适用 — uiapi 查询会编译为 SOQL,因此也受相同的管控 限制:≤10 个子查询、≤5 层子到父关系、≤1 层父到子关系、 每个子查询 ≤2,000 条记录。如将超出限制,请拆分为多个请求。
  7. 字段值包装器 — 通过 .value 读取原始值;displayValue 是服务端格式化后供 UI 使用的字符串。字段既用于显示又用于操作时(货币、 日期、选项列表),同时选择 value 和 displayValue,避免在 客户端重新格式化。仅用于显示的字段只需选择 displayValue。
  8. 复合字段 — 使用组成字段(BillingCity)筛选/排序,而不是包装字段(BillingAddress)。
  9. 仅支持指定 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:schemaUI bundle 目录获取/刷新 schema.graphql(供回退脚本使用)
npm run graphql:codegenUI 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 检查通过

评论

支持 Markdown