实现 #5581(兜底 delete 成功体向 spec 收敛)时做 deleted 键读取方全仓测绘撞见,不在该单范围内(#5581 的裁定面明确排除 packages/client,且这一条是 client 公开导出接口的形状问题,blast radius 与 semver 判断都不同),故另立。
事实
packages/client/src/index.ts:235:
/** Spec: DeleteDataResponseSchema */
export interface DeleteDataResult {
object: string;
id: string;
deleted: boolean;
}
注释指名 DeleteDataResponseSchema 为其规范来源,但该 schema(packages/spec/src/api/protocol.zod.ts:472)声明的第三个键是 success,不是 deleted:
export const DeleteDataResponseSchema = lazySchema(() => z.object({
object: z.string().describe('Object name'),
id: z.string().describe('Deleted record ID'),
success: z.boolean().describe('Whether deletion succeeded'),
}));
DeleteDataResult 是 client.data.delete(object, id)(index.ts:4300)与 project 作用域下同名方法(index.ts:4851)的返回类型。两者都是 unwrapResponse / _unwrap 直通,没有任何运行时改写 —— 服务端回什么就是什么,类型只是一句声明。
影响(#5581 之前就已成立)
服务端 DELETE /api/v1/data/:object/:id 在装了 MetadataPlugin 的部署(即常规部署)上回的是 { object, id, success: true }。所以:
- TS 使用方写
const r = await client.data.delete('task', id); if (r.deleted) … —— 编译通过,运行时 r.deleted === undefined,分支恒假;
- 反过来写
r.success 则被编译器判为不存在的属性。
即类型声明与它自称的规范、与服务端实际返回的形状都相反,且相反的方向是「编译器为错的那一种背书」。这不是 #5581 引入的:#5581 之前 protocol 路径就已经回 success;#5581 只是把兜底路径也对齐,使这条类型声明从「在精简装配上碰巧对上」变成两条路径都对不上。
建议修点
DeleteDataResult.deleted → success,与 DeleteDataResponseSchema 一致。属 @objectstack/client 公开导出接口的破坏性重命名,需要 changeset 与升级须知;是否同时保留一个 deprecated 的 deleted?: boolean 过渡期,请分诊裁定 —— 注意 contract-first 的口径下「消费端同时认两种键」正是被禁止的形状,#5581 的生产方侧就是照这个口径收敛的。
已确认不涉及的面
packages/client 另有五处 if (res.status === 204) return { deleted: true } 归一化(index.ts:3416 / 3467 / 3536 / 3590 / 3799)。逐条核过路由,分别是 shares revoke、sharing rules delete、reports delete、report schedules unschedule、ai conversations delete —— 没有一条走 /data/:object/:id,与本条及 #5581 的收敛面无交集,不应被一并改动。
Related: #5581 (packages/runtime 侧同一规范的收敛)、#5138(同族的 not-found 侧)
Blocked-by: #5581
Generated by Claude Code
实现 #5581(兜底 delete 成功体向 spec 收敛)时做
deleted键读取方全仓测绘撞见,不在该单范围内(#5581 的裁定面明确排除packages/client,且这一条是 client 公开导出接口的形状问题,blast radius 与 semver 判断都不同),故另立。事实
packages/client/src/index.ts:235:注释指名
DeleteDataResponseSchema为其规范来源,但该 schema(packages/spec/src/api/protocol.zod.ts:472)声明的第三个键是success,不是deleted:DeleteDataResult是client.data.delete(object, id)(index.ts:4300)与 project 作用域下同名方法(index.ts:4851)的返回类型。两者都是unwrapResponse/_unwrap直通,没有任何运行时改写 —— 服务端回什么就是什么,类型只是一句声明。影响(#5581 之前就已成立)
服务端
DELETE /api/v1/data/:object/:id在装了MetadataPlugin的部署(即常规部署)上回的是{ object, id, success: true }。所以:const r = await client.data.delete('task', id); if (r.deleted) …—— 编译通过,运行时r.deleted === undefined,分支恒假;r.success则被编译器判为不存在的属性。即类型声明与它自称的规范、与服务端实际返回的形状都相反,且相反的方向是「编译器为错的那一种背书」。这不是 #5581 引入的:#5581 之前 protocol 路径就已经回
success;#5581 只是把兜底路径也对齐,使这条类型声明从「在精简装配上碰巧对上」变成两条路径都对不上。建议修点
DeleteDataResult.deleted→success,与DeleteDataResponseSchema一致。属@objectstack/client公开导出接口的破坏性重命名,需要 changeset 与升级须知;是否同时保留一个 deprecated 的deleted?: boolean过渡期,请分诊裁定 —— 注意 contract-first 的口径下「消费端同时认两种键」正是被禁止的形状,#5581 的生产方侧就是照这个口径收敛的。已确认不涉及的面
packages/client另有五处if (res.status === 204) return { deleted: true }归一化(index.ts:3416 / 3467 / 3536 / 3590 / 3799)。逐条核过路由,分别是 shares revoke、sharing rules delete、reports delete、report schedules unschedule、ai conversations delete —— 没有一条走/data/:object/:id,与本条及 #5581 的收敛面无交集,不应被一并改动。Related: #5581 (
packages/runtime侧同一规范的收敛)、#5138(同族的 not-found 侧)Blocked-by: #5581
Generated by Claude Code