
Sanity Variant 定义与文档 Actions API 实战指南基于 Sanity Studio 源码的完整解析【免费下载链接】sanitySanity Studio – Rapidly configure content workspaces powered by structured content项目地址: https://gitcode.com/GitHub_Trending/sa/sanity本文以 Sanity Studio 仓库中packages/sanity/src/core/variants/ACTIONS.md为骨架系统讲解 Sanity 文档变体Document Variants体系中所有写入操作的 Actions API从变体定义文档system.variant的创建、编辑、删除到变体作用域版本文档variant-scoped version documents的发布、取消发布与删除。全文结合仓库内variants模块的源码实现、常量定义与单元测试逐字段、逐行为地说明每个 Action 的入参、校验规则、错误语义与 Studio 内部的实际接线方式帮助你在集成、脚本化或二次开发时准确使用这套 API而不是直接对系统文档做裸 mutation。一、为什么变体写入要使用 Actions API在 Sanity 中变体定义文档与变体作用域版本文档都带有服务器端管理的系统语义系统路径、范围哈希、引用完整性、权限与配额校验。因此官方明确要求变体定义写入必须走 Actions API而不是直接对文档做 mutation见ACTIONS.md开篇。Actions 面向的是持久化在_.variants.*路径下的system.variant定义文档由 API 端统一完成校验与完整性约束。这一设计在 Studio 源码中得到印证store/createVariantOperationsStore.ts中的写操作全部通过client.action(...)发出分别对应三个定义类 ActionStudio 操作底层 Action请求 tagcreateVariantsanity.action.variant.definition.createvariants.createupdateVariantsanity.action.variant.definition.editvariants.editdeleteVariantsanity.action.variant.definition.deletevariants.delete相关的架构背景可继续阅读README.md工具架构与USER_GUIDE.md面向内容编辑者的使用说明。二、变体定义文档持久化模型与路径约定2.1 文档形状与 ID 约定所有定义类 Action 都围绕同一个持久化文档形状展开interface VariantDefinitionDocument { _id: _.variants.${string} _type: system.variant name: string conditions: Recordstring, string priority: number metadata?: Recordstring, unknown }关键约定variantId是生成的短 ID 后缀而不是完整文档 ID。例如variantId为Ab12cd34时实际存储的文档 ID 是_.variants.Ab12cd34。所有 Action 的入参都用variantId短后缀API 端负责拼装为完整系统路径。这一路径在源码中是集中定义的常量而不是散落的字面量store/constants.ts定义VARIANT_DOCUMENT_TYPE: system.variant与VARIANT_DOCUMENTS_PATH: _.variants文档 ID 通过${VARIANT_DOCUMENTS_PATH}.suffix拼装types.ts中的SystemVariant接口即持久化形状的 TS 表达conditions、priority默认0以及可选的metadata目前承载title与 Portable Textdescription同时保留[key: string]: unknown供 UI 存放任意附加信息types.ts的isVariantId用正则/^_\.variants\.[a-zA-Z0-9._-]$/判定一个 ID 是否为变体系统 ID。2.2 短 ID 如何生成短 ID 后缀由util/createVariantId.ts生成基于nanoid的customAlphabet使用大小写字母加数字62 字符集生成长度 8 的随机后缀。注释给出的碰撞概率说明是若每小时生成 10 个 ID约需 24 年7.54e8 秒才有 1% 概率出现一次碰撞。该实现刻意与 release ID 生成方式保持一致。2.3 条件conditions校验规则无论是创建还是编辑condition 的 key/value 都受同一套规则约束详见util/conditionValidation.tsKey必须小写、以字母开头且匹配[a-z][a-z0-9_-]{0,63}即长度 1–64保留前缀以_或$开头的 key 被拒绝reserved错误Key 合法性包含:分隔符或不匹配上述正则的 key 判为invalidValue必须是非空的合法 UTF-8 字符串源码中进一步规定 value 不能全为空白empty且不能包含逗号,invalid。需要说明的是条件采用精确匹配语义请求方给出条件键值对只有完全一致的变体才命中空条件空对象被接受但这样的定义为空转inert——它永远不会匹配任何请求。三、sanity.action.variant.definition.create创建变体定义3.1 入参说明必填字段字段说明actionType固定为sanity.action.variant.definition.createvariantId短变体名用于拼装_.variants.{variantId}可选字段字段说明conditions精确匹配条件映射允许为空但空的定义为空转priority数值优先级省略时默认0当多个变体同时命中时优先级最高者胜出metadata自由格式元数据用于 Studio UI 展示如title与description3.2 调用示例await client.action({ actionType: sanity.action.variant.definition.create, variantId: Ab12cd34, conditions: {audience: loyal}, priority: 0, metadata: {title: Loyal customers}, })3.3 行为与校验创建_.variants.{variantId}_type为system.variantname等于variantId若同 ID 的变体定义已存在Action 失败幂等性由 API 端保证Studio 侧不做先查后写校验变体 ID 必须是单一路径段single path segment校验 condition 的 key 与 value见 2.3 节规则在 API 端应用变体定义的功能开关feature flag与数量上限count-limit检查。3.4 源码佐证Studio 如何发出该 ActioncreateVariantOperationsStore.ts中handleCreateVariant的实现与文档示例完全对应先从完整文档 ID 中剥离出短variantIdgetVariantId见tool/util.ts然后构造 Actionmetadata仅在存在时才放入载荷const action { actionType: sanity.action.variant.definition.create as const, variantId, conditions: variant.conditions, priority: variant.priority, ...(variant.metadata ? {metadata: variant.metadata} : {}), } return await client.action(action, {tag: variants.create})对应的单元测试store/__tests__/createVariantOperationsStore.test.ts断言了 Action 载荷的精确形状。变体文档创建入口createVariantDocument同样通过useVariantDocumentOperations见hooks/useVariantDocumentOperations.ts走 Actions API 实现其细节记录在创建流程相关文档中。四、sanity.action.variant.definition.edit编辑变体定义4.1 入参说明必填字段字段说明actionType固定为sanity.action.variant.definition.editvariantId_.variants.{variantId}的短变体名patch不含_id的 patchAction 将其应用到匹配的变体定义文档上可选字段字段说明ifRevisionId乐观并发守卫若当前文档 revision 不匹配则 Action 失败4.2 调用示例await client.action({ actionType: sanity.action.variant.definition.edit, variantId: Ab12cd34, patch: { set: { conditions: {audience: loyal, locale: en-US}, priority: 10, metadata.title: Loyal customers in the US, }, }, })4.3 可变路径与不可变字段可变路径仅允许conditions/conditions.*、priority、metadata/metadata.*不可变字段_id、_type、name以及系统时间戳system timestamps的 patch 一律拒绝应用 patch 之后API 会对结果文档重新做一次完整校验包括 2.3 节的 condition 规则。4.4 常用 patch 模式// 只更新 conditions整体覆盖 await client.action({ actionType: sanity.action.variant.definition.edit, variantId: Ab12cd34, patch: { set: {conditions: {audience: loyal}}, }, }) // 移除整个 metadata await client.action({ actionType: sanity.action.variant.definition.edit, variantId: Ab12cd34, patch: { unset: [metadata], }, })4.5 源码佐证updateVariant 的完整语义Studio 的handleUpdateVariantcreateVariantOperationsStore.ts在编辑时总是setconditions与priority若编辑后的变体不含metadata则同时unset: [metadata]——这保证了清空元数据是显式、可持久化的操作。对应测试createVariantOperationsStore.test.ts验证了带 metadata 与不带 metadata 两种载荷分支。五、sanity.action.variant.definition.delete删除变体定义5.1 入参说明必填字段字段说明actionType固定为sanity.action.variant.definition.deletevariantId_.variants.{variantId}的短变体名可选字段字段说明ifRevisionId乐观并发守卫revision 不匹配则失败5.2 调用示例await client.action({ actionType: sanity.action.variant.definition.delete, variantId: Ab12cd34, })5.3 行为与完整性约束若变体定义不存在Action 失败ifRevisionId提供了乐观并发保护提交对_.variants.{variantId}的删除不级联删除变体文档variant documents 是独立文档删除定义不会连带清除它们当仍有变体文档强引用strong reference该定义时mutation 引擎会阻止删除Action 会把该完整性错误documentHasExistingReferencesError原样暴露给调用方。5.4 源码佐证错误解析与权限预检store/variantActionErrors.ts展示了 Studio 侧如何处理这些失败Actions API 的失败既可能出现在details顶层也可能出现在details.items[].errormutation 错误按 action 分条因此getActionErrorEntries把两层拍平统一供调用方查询isInsufficientPermissionsError识别insufficientPermissionsError类型用于权限不足提示getReferencingDocumentCount识别documentHasExistingReferencesError并从referencingIDs中按 published ID 去重后统计引用文档组数量服务器会按 bundle 列出每个引用版本Studio 则按文档组计数因此去重是必须的。删除前权限预检通过dryRun完成deleteVariant的第二个参数opts会被原样转发给 Action 请求见createVariantOperationsStore.ts权限存储createVariantPermissionsStore借此以 dry-run 方式判断当前用户是否允许删除每个 workspace 共享一份预检结果见store/useVariantPermissions.ts。六、变体文档 Actions为什么用三元组寻址变体文档的生命周期写入除创建流程中记录的sanity.action.document.variant.create之外都由以下三个 Action 承担。它们不使用原始 ID 寻址而是用(publishedId, variantId, bundleId)三元组原因是变体版本 IDversions.scopeId.publishedId中携带了服务器生成的不透明 scope 哈希客户端无法可靠地推导或拼接这些 ID必须把寻址职责交给 API 端。publishedId分组基础 published文档 IDvariantId_.variants.{variantId}的短变体名bundleIddrafts、release 名称或在取消发布与删除时省略以指向 variant-of-published 文档。七、sanity.action.document.variant.publish发布变体版本将变体作用域的版本发布进 variant-of-published 文档即 API 对外服务变体内容的载体。必填字段字段说明actionType固定为sanity.action.document.variant.publishpublishedId分组基础 published文档 IDvariantId_.variants.{variantId}的短变体名bundleId被发布的源 bundledrafts或 release 名称传入published会被拒绝源等于目标可选字段字段说明ifSourceRevisionId对源变体文档 revision 的乐观锁ifPublishedVariantRevisionId对 variant-of-published 目标 revision 的乐观锁行为将源变体文档的内容复制进 variant-of-published 文档不存在则创建已存在则覆盖——与基础/release 发布的语义一致随后删除源文档基础 published 文档永远不被触碰。这一语义与用户侧行为对应发布变体草稿后API 在匹配条件下开始返回变体内容而基础已发布文档保持不变见USER_GUIDE.md。八、sanity.action.document.variant.unpublish取消发布变体取消发布的行为取决于bundleId指向的是哪个变体版本对应 CLDX-5781 / SAPP-4012。必填字段字段说明actionType固定为sanity.action.document.variant.unpublishpublishedId分组基础 published文档 IDvariantId短变体名bundleId被取消发布的变体版本所在 bundle省略/undefined表示 variant-of-published 文档release 名称表示 release 作用域的变体drafts不是合法目标草稿作用域的变体没有已发布内容可取消发布行为bundleId省略variant-of-published→ 硬取消发布删除已发布的变体并把其内容重建为变体草稿镜像基础文档的 unpublish 语义——内容不丢失bundleId release 名称 → 软取消发布给 release 作用域的变体打上_system.delete: true标记与 release 版本使用的待取消发布标记相同发布该 release 时才完成真正的取消发布在此之前可撤销排定的取消发布无论哪种方式基础 published 与 draft 文档都不受影响。九、sanity.action.document.variant.delete删除变体版本删除一个变体作用域的版本文档。必填字段字段说明actionType固定为sanity.action.document.variant.deletepublishedId分组基础 published文档 IDvariantId短变体名可选字段字段说明bundleId要删除的变体文档所在 bundledrafts或 release 名称省略则指向 variant-of-published 文档purge是否同时从 translog 中移除历史记录默认false行为只删除被寻址的那个变体版本文档其他 bundle 的变体文档以及基础文档对base pair都不受影响Studio 用它实现discard changes对 drafts 与 release 作用域的变体版本丢弃变更 删除该 bundle 中的变体文档。通用的sanity.action.document.discard按原始 ID 寻址草稿保留给基础/release 路径使用不允许丢弃 variant-of-published 文档——移除它的职责属于 unpublish删除 Action 会拒绝这类调用。十、验证与测试如何在仓库中核对这套 API单元测试createVariantOperationsStore.test.tsstore/__tests__/逐一断言 create/edit/delete 三个 Action 的载荷与请求 tag以及dryRun透传condition 校验规则在util/__tests__/conditionValidation.test.ts中有完整用例ID 生成与路由编解码分别在createVariantId.test.ts与 tool 层测试中覆盖架构总览README.md提供了 Variants 工具的读状态createVariantsStorelistenQuery与写状态createVariantOperationsStore指向 ACTIONS.md的全貌运行聚焦测试pnpm vitest run --projectsanity packages/sanity/src/core/variants/E2E浏览器级流程创建、校验、自动补全、删除等位于e2e/tests/variants/variantTool.spec.ts聚焦运行命令为pnpm test:e2e -- --projectchromium e2e/tests/variants/variantTool.spec.ts小结变体写入的 Actions API 通过短 ID 寻址 服务器端完整校验 显式错误语义把复杂的系统文档管理从客户端剥离定义层create/edit/delete负责维护_.variants.*下匹配规则与优先级文档层publish/unpublish/delete负责以(publishedId, variantId, bundleId)三元组安全地操作带 scope 哈希的变体版本。理解这些 Action 的入参、可变路径与失败行为既是安全集成的前提也是深入阅读variants模块源码的钥匙。【免费下载链接】sanitySanity Studio – Rapidly configure content workspaces powered by structured content项目地址: https://gitcode.com/GitHub_Trending/sa/sanity创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考