@objectstack/spec 用同一个名字导出了两个形状不同的 MetadataWatchEvent,分别挂在两个子路径入口上,靠 import 路径区分。其中一个是完全惰性的(零消费方,只有自己的测试在 parse 它),另一个是唯一在用的、且其消费方自己的注释管它叫 "legacy"。
import type { MetadataWatchEvent } from '@objectstack/spec/kernel'; // 一种形状
import type { MetadataWatchEvent } from '@objectstack/spec/system'; // 另一种形状
两份的实际差异
|
@objectstack/spec/kernel |
@objectstack/spec/system |
| 定义 |
kernel/metadata-loader.zod.ts:298(type :536) |
system/metadata-persistence.zod.ts:270(type :338) |
type |
'added' | 'changed' | 'deleted' |
'add' | 'change' | 'unlink' | 'added' | 'changed' | 'deleted' |
metadataType |
string(必填) |
string?(可选) |
name |
string(必填) |
string?(可选) |
path |
string(必填) |
string(必填) |
timestamp |
string(必填,datetime) |
string?(可选) |
stats |
— |
MetadataStats? |
data |
unknown? |
unknown? |
| 运行时消费方 |
0(仅 metadata-loader.test.ts 自测) |
1:packages/metadata/src/metadata-manager.ts |
即:system 版是 kernel 版的宽松超集 —— 多出 chokidar 原生的 add/change/unlink 三个值、多出 stats,并把 metadataType/name/timestamp 放宽为可选。两者不是两个概念,是同一概念的两个严格度。
两个 schema 都没有被任何其他 schema 内嵌引用(唯一引用各自的就是它们自己那行 z.infer),所以拆解面很干净。
命名直觉是反的 —— 这是最容易踩的点
听起来更"正统"的那份(kernel,规范化枚举、字段必填)是死的;真正在用的是那份自称 legacy 的宽松超集。
metadata-manager.ts 自己的措辞:
:1646 — /** Translate a repo event to the legacy MetadataWatchEvent + invalidate caches. */
:1668 — const legacyEvent: MetadataWatchEvent = {
任何按名字或按"哪个看起来更规范"来选的人(包括 AI 补全和自动 import)都会选反。 而且两者字段大量重叠(type 的三个规范值、name、path、data、timestamp 都在),选错未必立刻炸 —— 只在边缘值(add vs added)和必填性上翻车。
是怎么被发现的
#4404(#4251 B3)给 IMetadataService 补 subscribe? 时,我按 watch 的回调类型写了首稿。同一个 PR 刚给 ObjectQL 挂上的 implements 检查链条上,MetadataManager implements IMetadataService 当场拒绝了它 —— subscribe 转发的是 persistence 侧事件,watch 转发的是注册级事件。顺着这个拒绝才发现底下压着两份同名类型。
契约检查上线第一天,抓到的第一个错是新写的契约本身;这条 issue 是那次拒绝的副产品。
和 #4393 的死影子是同一病灶,但更危险
#4393 删掉的 packages/core/src/contracts/(IHttpServer 等)也是同名双源,但那份是死代码:零导入方、非子路径导出、非 tsup entry,只坑 grep 进去的人 —— 危害限于"读到过期契约后得出有依据的错误结论"(#4382 那条错误豁免正是这么来的)。
这一份不同:两份都在公开导出面上(api-surface.json 的 ./kernel 与 ./system 两个入口各列了一次 MetadataWatchEvent (type) + MetadataWatchEventSchema (const)),都可被外部 import,靠路径区分。
建议动作:退役,不是合并
kernel 那份符合 ADR-0049 declared-but-unenforced:声明了、导出了、有测试,但没有任何生产代码消费它。按 enforce-or-remove,应当删除,system 那份留为唯一来源。
删除后顺带:
不建议顺手把 system 版收紧成 kernel 版的严格形状 —— 那会改变 metadata-manager 实际发出的事件(它确实发 add/change/unlink),属于独立的行为变更,不该混进一次命名去重。
已核实
packages/**:kernel 版零运行时消费方(grep 全仓,仅自测命中)
- 兄弟仓
cloud、objectui:均未引用 MetadataWatchEvent(任一版本)
- 两个 schema 均未被其他 schema 内嵌
- 两个 barrel 导出确认:
kernel/index.ts:27、system/index.ts:71(均为 export *)
待裁决
kernel 版是否真的可以直接删 —— 上面只覆盖了本仓 + 两个兄弟仓;@objectstack/spec 是公开发布包,外部消费方无法用 grep 排除。若需保守,可先按弃用标记一个 release 再删。
- 若确认删除,是否值得同时给 spec 加一条"跨 barrel 同名导出"的检查 —— 现在
api-surface.json 里同名跨入口是可见但不报警的。这条检查能防住下一个同类问题;是否值当由维护者定。
关联:#4251(发现路径)、#4404(触发拒绝的 implements 检查)、#4393(同类的死影子,已删)、ADR-0049(enforce-or-remove)
Generated by Claude Code
@objectstack/spec用同一个名字导出了两个形状不同的MetadataWatchEvent,分别挂在两个子路径入口上,靠 import 路径区分。其中一个是完全惰性的(零消费方,只有自己的测试在 parse 它),另一个是唯一在用的、且其消费方自己的注释管它叫 "legacy"。两份的实际差异
@objectstack/spec/kernel@objectstack/spec/systemkernel/metadata-loader.zod.ts:298(type:536)system/metadata-persistence.zod.ts:270(type:338)type'added' | 'changed' | 'deleted''add' | 'change' | 'unlink' | 'added' | 'changed' | 'deleted'metadataTypestring(必填)string?(可选)namestring(必填)string?(可选)pathstring(必填)string(必填)timestampstring(必填,datetime)string?(可选)statsMetadataStats?dataunknown?unknown?metadata-loader.test.ts自测)packages/metadata/src/metadata-manager.ts即:
system版是kernel版的宽松超集 —— 多出 chokidar 原生的add/change/unlink三个值、多出stats,并把metadataType/name/timestamp放宽为可选。两者不是两个概念,是同一概念的两个严格度。两个 schema 都没有被任何其他 schema 内嵌引用(唯一引用各自的就是它们自己那行
z.infer),所以拆解面很干净。命名直觉是反的 —— 这是最容易踩的点
听起来更"正统"的那份(
kernel,规范化枚举、字段必填)是死的;真正在用的是那份自称 legacy 的宽松超集。metadata-manager.ts自己的措辞::1646—/** Translate a repo event to the legacy MetadataWatchEvent + invalidate caches. */:1668—const legacyEvent: MetadataWatchEvent = {任何按名字或按"哪个看起来更规范"来选的人(包括 AI 补全和自动 import)都会选反。 而且两者字段大量重叠(
type的三个规范值、name、path、data、timestamp都在),选错未必立刻炸 —— 只在边缘值(addvsadded)和必填性上翻车。是怎么被发现的
#4404(#4251 B3)给
IMetadataService补subscribe?时,我按watch的回调类型写了首稿。同一个 PR 刚给ObjectQL挂上的implements检查链条上,MetadataManager implements IMetadataService当场拒绝了它 ——subscribe转发的是 persistence 侧事件,watch转发的是注册级事件。顺着这个拒绝才发现底下压着两份同名类型。契约检查上线第一天,抓到的第一个错是新写的契约本身;这条 issue 是那次拒绝的副产品。
和 #4393 的死影子是同一病灶,但更危险
#4393 删掉的
packages/core/src/contracts/(IHttpServer等)也是同名双源,但那份是死代码:零导入方、非子路径导出、非 tsup entry,只坑 grep 进去的人 —— 危害限于"读到过期契约后得出有依据的错误结论"(#4382 那条错误豁免正是这么来的)。这一份不同:两份都在公开导出面上(
api-surface.json的./kernel与./system两个入口各列了一次MetadataWatchEvent (type)+MetadataWatchEventSchema (const)),都可被外部 import,靠路径区分。建议动作:退役,不是合并
kernel那份符合 ADR-0049 declared-but-unenforced:声明了、导出了、有测试,但没有任何生产代码消费它。按 enforce-or-remove,应当删除,system那份留为唯一来源。删除后顺带:
packages/spec/src/contracts/metadata-service.ts里subscribe?的 import 处我留的那块"两个同名类型"的警示注释可以删掉(feat(spec,objectql): IObjectQLEngine —— objectql 槽的契约落地,类 implements 之,七个本地 stand-in 退场 (#4251 B3) #4404 加的)。api-surface快照会少两个./kernel导出 —— 这是 breaking(removed),check:api-surface会要求确认;走正常的 spec 变更流程即可。kernel/metadata-loader.test.ts里对应的 describe 块一并移除。不建议顺手把
system版收紧成kernel版的严格形状 —— 那会改变metadata-manager实际发出的事件(它确实发add/change/unlink),属于独立的行为变更,不该混进一次命名去重。已核实
packages/**:kernel版零运行时消费方(grep 全仓,仅自测命中)cloud、objectui:均未引用MetadataWatchEvent(任一版本)kernel/index.ts:27、system/index.ts:71(均为export *)待裁决
kernel版是否真的可以直接删 —— 上面只覆盖了本仓 + 两个兄弟仓;@objectstack/spec是公开发布包,外部消费方无法用 grep 排除。若需保守,可先按弃用标记一个 release 再删。api-surface.json里同名跨入口是可见但不报警的。这条检查能防住下一个同类问题;是否值当由维护者定。关联:#4251(发现路径)、#4404(触发拒绝的
implements检查)、#4393(同类的死影子,已删)、ADR-0049(enforce-or-remove)Generated by Claude Code