Skip to content

refactor(client)!: subscribeMetadata 的 type 收窄为 MetadataEventSubject,订阅无合同类型改为编译报错 (#4627) - #6156

Merged
qq9340100 merged 1 commit into
mainfrom
claude/issue-4627-subscribe-metadata-narrow
Aug 7, 2026
Merged

refactor(client)!: subscribeMetadata 的 type 收窄为 MetadataEventSubject,订阅无合同类型改为编译报错 (#4627)#6156
qq9340100 merged 1 commit into
mainfrom
claude/issue-4627-subscribe-metadata-narrow

Conversation

@qq9340100

Copy link
Copy Markdown
Collaborator

Fixes #4627

执行维护者 2026-08-06 最终确认的裁决:只做轴 1(收窄消费端),轴 2(扩枚举覆盖面)不预答、不做。枚举本体一个成员都没动。

问题

MetadataEventTypepackages/spec/src/api/events.zod.ts)是封闭枚举:13 个 metadata 类型 × 3 个动作。#4602 已经把生产端钉成 declared = enforced —— 枚举外的类型不发布任何 realtime 事件,因为不存在能合法交付给 (event: MetadataEvent) => void 回调的事件形状。

消费端却一直是宽的 string。于是这行代码编译全绿、运行永盲,类型系统一个字都没说:

client.events.subscribeMetadata('translation', onEvent);  // 回调永远不会被调用

translation 不是杜撰的:它和 datasource / page / hook / trigger / validation 一样,都是 DEFAULT_METADATA_TYPE_REGISTRY 里可注册的真实类型。这正是 AI 写订阅代码最容易踩的形状 —— 它看起来订阅上了

改动

新增导出packages/spec/src/api/events.zod.ts,紧跟 MetadataEventType,避开 #6072 在飞的 kernel/events/*.zod.ts 别名区):

type MetadataEventSubjectOf< T extends string > =
  T extends `metadata.${infer Subject}.${MetadataEventAction}` ? Subject : never;

export type MetadataEventSubject = MetadataEventSubjectOf< MetadataEventType >;

派生,不是在旁边重抄一份 —— 枚举加一个成员,这个联合自动跟着长,两者不可能各说各话。中间那个类型参数是必需的:条件类型只对裸类型参数分发,直接把整个联合写进模式会得到一个答案而不是 13 个。两个 helper 都是模块私有(导出面只多这一个类型,见下)。

签名收窄(三处,全部只是 string → 这个联合,无运行时改动):

符号
@objectstack/client RealtimeAPI.subscribeMetadata(type, …)
@objectstack/client-react useMetadataSubscription(type, …)
@objectstack/client-react useMetadataSubscriptionCallback(type, …)

两个 hook 是被倒逼改的,不是夹带:它们只是把实参转发给 subscribeMetadata,不能比它更松。

顺带把实现里三条手拼的事件名从 string[] 标注成 MetadataEventType[] —— type 一旦收窄,这三条模板就是可证的枚举成员,说出来让 tsc 复核一遍。这条在反向验证里意外自己变成了第四个 pin(见下)。

排查:仓内有没有枚举外订阅

零命中,因此无需修正或删除任何调用点。按要求做了邻近词反查 —— 扫全仓源码里所有 metadata.{type}.{action} 形状的字面量:

$ git grep -rhoE "'metadata\.[a-z_]+\.(created|updated|deleted)'" -- packages apps examples | sort -u | wc -l
39

恰好 39 = 13 × 3,与枚举逐字相符,无一条枚举外的名字。本仓 6 处 subscribeMetadata / useMetadataSubscription 调用点传的都是 'object' / 'view'零迁移content/examples/ 无任何相关示例(releases/ 未碰)。

反向验证(方向事先声明)

声明的预期:把 type 退回 string → 两条 @ts-expect-error 变成未使用(TS2578),且参数精确性 pin 解析为 never(TS2322)。实测两条都落地

src/realtime-api.test.ts(191,11): error TS2322: Type '"exact"' is not assignable to type 'never'.
src/realtime-api.test.ts(212,5): error TS2578: Unused '@ts-expect-error' directive.
src/realtime-api.test.ts(225,5): error TS2578: Unused '@ts-expect-error' directive.

还有一条没预测到的红,如实记录而不是抹掉:实现文件自己也红了三处 —— const eventTypes: MetadataEventType[]type 变宽后无法证明,`metadata.${string}.created` 不可赋值给枚举(TS2322 × 3)。也就是说这次的 pin 不止在测试里,实现比签名低一层也自带一个。

防 phantom check

@ts-expect-error 单独用是不够的 —— 它对该行任何错误都放行。所以每条都由类型级断言夹逼,把「只可能是第一个实参错」坐实:

  • 参数类型双向钉死为 MetadataEventSubjectParam extends … 抓变宽,… extends Param 抓过窄,单向都不是精确);
  • 正例证明 callback / options 两条胳膊照常编译;
  • 具体错误码是删掉指令实测出来的,不是猜的,逐字写回注释:
src/realtime-api.test.ts(212,39): error TS2345: Argument of type '"translation"' is not assignable to parameter of type 'MetadataEventSubject'.
src/realtime-api.test.ts(224,39): error TS2345: Argument of type 'string' is not assignable to parameter of type 'MetadataEventSubject'.

spec 侧还钉了派生本身的忠实性,其中一条专防空洞通过MetadataEventSubject extends Covered 在派生塌成 never 时会真空为真never extends X 恒真),所以反方向 Covered extends MetadataEventSubject 才是抓塌陷的那条,两条都留。另加一条回程精确性 —— subject × action 重组必须恰好等于枚举,于是「只加 metadata.translation.created 不加另两个」这种半拉扩展没法悄悄落地。

两个 pin 文件在各自的 test-typecheck-debt.json都没有条目,按该台账「未列出的文件必须零错误」的规则,零就是这些 pin 的可测基线。

验证

  • pnpm --filter @objectstack/spec test326 files / 8367 tests passed
  • pnpm --filter @objectstack/client test19 files / 241 tests passed
  • pnpm --filter @objectstack/client-react test3 files / 34 tests passed
  • 三个包 typecheck 全绿(spec / client 含 check:test-typecheck,债务数字未变:spec 79 files / 691 errors,client 3 files / 6 errors)
  • pnpm lint 绿;node scripts/check-nul-bytes.mjs 绿(5861 文件),改动文件另做了超出该门的控制字节自扫
  • check:generatedcheck:api-surface0 breaking / 1 added,按纪律用 gen:api-surface 重生成(未手改),只动 api-surface/api.json 一行 —— spec 生成物按 category 分片:拆掉三个单体 ratchet 文件的合并队列串行税(维护者 2026-08-06 已拍板) #5837 承诺的分片 locality 成立;重跑后 10/10 绿

changeset

spec: minor(纯新增导出)/ client: major + client-react: major。标 major 的理由与 #5181 同一条先例:源码级破坏、运行时零变化仍走 major。FROM → TO 写明了「原来传 string 的代码怎么改」两种情形各自的一行修复。

边界

未扩枚举、未改 MetadataEventType 本体、未动发布端(#4602 已 pin)、未碰 content/docs/releases/


Generated by Claude Code

…4627)

#4602 已把生产端钉成 declared = enforced —— MetadataEventType 枚举外的
metadata 类型不发布任何 realtime 事件。消费端却仍是宽的 string,于是
subscribeMetadata('translation', cb) 编译全绿、运行永盲。

本次把消费端也钉上:新增 spec 派生类型 MetadataEventSubject(从
MetadataEventType 用模板字面量 + 分发式条件类型解出 {type} 半边,不是
重抄一份),并收窄三处签名 —— client 的 subscribeMetadata、client-react
的 useMetadataSubscription / useMetadataSubscriptionCallback。

轴 2(扩枚举覆盖面)不预答,枚举一个成员都没动。

Refs #4627

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 7, 2026 3:52am

Request Review

@github-actions github-actions Bot added the size/m label Aug 7, 2026
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/client-react, @objectstack/client, @objectstack/spec.

115 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via packages/client-react, packages/client, @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/client-react, @objectstack/client, @objectstack/spec)
  • content/docs/api/data-flow.mdx (via @objectstack/client)
  • content/docs/api/environment-routing.mdx (via @objectstack/client, @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/client, @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/client, @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx (via @objectstack/client, @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/client, packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authentication.mdx (via @objectstack/client)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/client-react, @objectstack/client, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/realtime-protocol.mdx (via @objectstack/client)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/client, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/client, @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/client-react, @objectstack/client, @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/field-grouping-and-order.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 7, 2026
@qq9340100
qq9340100 marked this pull request as ready for review August 7, 2026 04:06
@qq9340100
qq9340100 added this pull request to the merge queue Aug 7, 2026
Merged via the queue into main with commit def5919 Aug 7, 2026
25 checks passed
@qq9340100
qq9340100 deleted the claude/issue-4627-subscribe-metadata-narrow branch August 7, 2026 04:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants