You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
GET /meta/:type/:name answers two different body shapes on the same request — the cached branch (the DEFAULT) returns the bare document, the non-cached branch returns the spec-declared { type, name, item } envelope #5563
Detect the getMetaItem response envelope ({ type, name, item, lock, … }) … The cached read path and getMetaItems element shape hand back the already-unwrapped document instead, so translation helpers must distinguish the two
CACHED BRANCH BODY = {"name":"customer","label":"Customer","fields":{}}
NONCACHED BRANCH BODY = {"type":"object","name":"customer","item":{"name":"customer","label":"Customer","fields":{}}}
发现于 #5545 的前提复核(要给
client.meta.getItem补返回类型注解,先去 spec 锚定响应形状)。基线origin/main@229d29ea4。事实
packages/spec/src/api/plugin-rest-api.zod.ts:773为GET /meta/:type/:name声明了唯一的响应 schema:packages/spec/src/api/protocol.zod.ts:193里它是三键信封:但同一条路由在
packages/rest/src/rest-server.ts:4020的 handler 里有两条互斥分支,响应体形状不同:rest-server.ts:4076的条件成立时)→res.json(... result.data ...)(:4128)。result来自getMetaItemCached,而packages/metadata-protocol/src/protocol.ts:5409-5411里它做的正是const item = (result as any)?.item;、:5441return { data: item, ... }—— 也就是说result.data是已经拆掉信封的裸元数据文档。res.json(... visible ...)(:4238),visible = await p.getMetaItem(...),即{ type, name, item, lock, ... }信封,与 spec 一致。关键在于缓存分支是默认路径:
packages/spec/src/api/rest-server.zod.ts:285是enableCache: z.boolean().default(true),rest-server.ts:2796也是metadata.enableCache ?? true。于是 spec 声明的那个形状,在默认配置下反而是取不到的那一个;信封只在关掉缓存、或app/doc/book/?state=draft/?preview=draft/?package=这些绕过缓存的读法下才出现。第三种形状还有?layers=true(rest-server.ts:4031-4044的getMetaItemLayered),与前两者都不同。仓内早就在注释里承认了这件事,只是当作运行时事实处理而非缺陷 ——
packages/rest/src/import-prepare.ts:18-32的isMetaEnvelope:rest-server.ts:2631-2637同一段话再写了一遍。isMetaEnvelope在rest-server.ts有 5 处调用,全部是在为「这次拿到的到底是哪种形状」做运行时嗅探。runtime 那条服务面同病:
packages/runtime/src/domains/meta.ts:247命中 protocol 时deps.success(data)送信封,而:216-218的 registry 兜底送qlService.registry.getObject(name)(裸文档),:258-260的 metaSvc 兜底同样是裸文档。实测
在 worktree 里对着真
RestServer跑同一条请求(GET /api/v1/meta/object/customer,无 query),只切换 protocol 是否提供getMetaItemCached,捕获res.json的实参:(探针脚本是一次性的,未提交。)
影响
as any。这正是 client SDK 的meta.getItem没有声明返回类型(载荷为unknown),而并排的meta.getItems有 —— 同一表面上相邻两个方法的类型化不对等 #5545 卡住的原因:client.meta.getItem想补一个诚实的返回类型注解,而现在没有一个类型是对的 —— 标信封则默认配置下撒谎,标裸文档则与 spec 声明矛盾。packages/client-react/src/metadata-hooks.tsx:113-140把meta.getItem(...)的结果和getCached().data(裸文档)喂进同一个datastate,而useObject自己的 docstring(:52-64)读的是schema.label/schema.fields。若服务端走到非缓存分支,这些读取全是undefined,且不报错。packages/client/src/client.test.ts:96-99的 mock 同样是裸文档。??与as any。处置(需定案,勿直接猜)
统一到一种形状是不可避免的,但选哪一种是公开契约决定,应由 maintainer 拍板:
{ type, name, item }信封 —— 契约优先,plugin-rest-api.zod.ts:773本来就这么声明。代价:对今天默认配置(缓存分支)的所有读者是破坏性变更。client-react/ CLI / client 自测的既有预期。代价:丢掉type/name/lock,而lock是 ADR-0008 OCC(If-Match)那条链在用的,得另找承载;且「裸元数据文档」本身是个很弱的类型,收窄价值有限。无论哪条,都应在同一次里把
isMetaEnvelope的运行时嗅探删掉 —— 它存在本身就是这个分裂的度量。关联:#5545(被本单阻塞)、#3847(同族:声明了富形状、两个 surface 都发裸值)。