Skip to content

client realtime declared ≠ enforced:subscribeData 回调把 RealtimeEventPayload 硬铸成 DataEvent,顶层 recordId/changes/id 运行时是 undefined #4626

Description

@os-zhuang

#4602(subscribeMetadata/MetadataEvent 同病)修复时的范围外发现,按第十条军规立案,unassigned。#4602 的 PR 只修 metadata 侧;data 侧是同构缺陷但生产者不同,需单独裁决与实施。

现象

@objectstack/spec/api 的 DataEventSchema 声明顶层字段 id(uuid,必填)、object、recordId(必填)、changes?、before?、after?、userId?、timestamp。但:

  • 生产侧(packages/objectql/src/engine.ts 的 data.record.* 发布)发的是 RealtimeEventPayload 信封,record 数据嵌在 payload({ recordId, after, changes }),从不生成 id/userId。
  • 消费侧 packages/client/src/realtime-api.ts subscribeData 把信封 callback(event as any as DataEvent);client-react 的 useDataSubscription / useDataSubscriptionCallback / useAutoRefresh 同源。

订阅者写 event.recordId / event.changes(类型系统说存在)运行时是 undefined;必填的 event.id 不存在。

处置方向

与 #4602 的裁决同族(生产者履约,消费端边界 DataEventSchema.safeParse 响亮拒绝,删除 as any as 双铸)。注意 data 侧的生产者是 ObjectQL engine(热路径,每次 insert/update/delete),且 webhook auto-enqueuer 等服务端消费者直接读信封 payload(packages/plugins/plugin-webhooks/src/auto-enqueuer.ts 读 payload.recordId ?? payload.id ?? …)——迁移时需要同时核对这些信封依赖,成本比 metadata 侧高,需单独评审。

另注意 DataEventType 里的 data.field.changed 疑似无生产者(declared ≠ enforced 同族,cf. #3197)。

参照:#4602 的实施(producer 生成完整 spec 事件放入信封 payload,client 边界 parse)。

Activity

  1. self-assigned this
    on Aug 2, 2026
  2. os-zhuang commented on Aug 2, 2026

    @os-zhuang
    ContributorAuthor

    🔒 CLAIM — pm-dispatch round 7
    branch: claude/issue-4626-data-event-contract
    worktree: objectstack-issue-4626

    PM 裁决:同 #4602,方案 1 生产者履约——已声明的 DataEvent 合同必须成真,不在消费侧留双铸或容错。与 #4602 的差别在于本题确有信封读者(webhook auto-enqueuer 直读 data.record.*),因此:

    1. 该 enqueuer 在同一个 PR 内迁移到履约后的事件形状——它在仓内,不构成"不可动的外部依赖";
    2. 生产者是 ObjectQL engine 热路径:每事件生成 uuid + zod parse 的开销必须实测(不是估计)。若实测代价确实可观,停手按 needs_decision 回报并附数据,由维护者裁是否需要 fast-path(例如仅在有订阅者时构造完整事件);
    3. 边界校验与 pin 测试规格同 fix(metadata,client): subscribeMetadata 交付真正的 MetadataEvent——生产者履约 + 边界校验 (#4602) #4628(PR 已合并,照抄形状,保持词汇一致)。

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions