Skip to content

观察:@objectstack/spec 每个 entry 各打一份 schema 实例 —— 跨 entry 的 === 恒为 false,registerMetadataTypeSchema 的可见范围也被这条边界圈死 #5379

Description

@baozhoutao

#5000 的核实工作里撞到的(写 CLI 侧 pin 测试时)。观察类:今天没有用户能碰到的故障,但它让一类看起来天经地义的断言/推理不成立,值得记下来。

事实

packages/specpackage.json 声明了 16 个 entry(. / ./kernel / ./ui / …),tsup 按 entry 分别打包,同一个 schema 在不同 entry 的产物里是不同的对象:

// 在已 build 的 workspace 里
const root = require('@objectstack/spec');
const kernel = require('@objectstack/spec/kernel');
const ui = require('@objectstack/spec/ui');

// root 的 ObjectStackDefinitionSchema.pages 的 element
const el = root.ObjectStackDefinitionSchema._zod.def.shape.pages._zod.def.innerType._zod.def.element;

el === kernel.getMetadataTypeSchema('page')   // false
ui.PageSchema === kernel.getMetadataTypeSchema('page')   // false

源码里它们是同一个模块绑定(stack.zod.tsmetadata-type-schemas.ts 都 import ui/page.zod.tsPageSchema),只有从 src 直接跑(tsx / vitest 走源码)时 === 才成立;跨包消费的是 dist,就是各自一份。

为什么记下来

  1. 它把「同一道门」这件事变成不可断言。 objectstack build / validate 从不按 PageSchema 解析页面元数据:ADR-0089 D3a 早就该拒绝的键一路通过,#4001 的「三个示例应用 validate 全过」对 page 面是空证 #5000 问的正是「CLI 走的 schema 是不是写路径那一个」。最自然的证明是实例同一性;跨 entry 它恒为 false,所以只能退回到行为等价(同一个未声明键、两边同样拒绝)。我在 PR 里就是这么写的,并把原因写进了测试注释 —— 否则下一个人会先写一遍 identity 断言、看着它红、再怀疑是自己的改动。

  2. registerMetadataTypeSchema() 的可见范围被这条边界圈死。 运行时 overlay 存在 kernel bundle 里的 EXTRA_METADATA_TYPE_SCHEMAS。今天没事,因为注册表的读写 API 只从 ./kernel 导出,所有消费者拿的是同一份;但这是个巧合而不是保证 —— 哪天某个 entry 再导出一个内部会读注册表的 helper,插件注册的类型对它就是不存在的,而且这种「看得见声明、读不到注册」的失败非常难查。

  3. 顺带的:同一份 zod schema 在多个 entry 里各存一份,内存/体积上是重复的(analyze-bundle-size.ts 应该能量到)。

不建议顺手改

改动面(entry 拆分 / chunk 共享 / external 策略)属于 spec 打包契约,不是随手能定的;记在这里让后来者不必重新推一遍即可。

相关

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions