Skip to content

fix(client)!: DeleteDataResult 声明它自称的 schema —— success,不是 deleted (#5638) - #5657

Merged
baozhoutao merged 2 commits into
mainfrom
claude/issue-5638-client-delete-result-type
Aug 5, 2026
Merged

fix(client)!: DeleteDataResult 声明它自称的 schema —— success,不是 deleted (#5638)#5657
baozhoutao merged 2 commits into
mainfrom
claude/issue-5638-client-delete-result-type

Conversation

@baozhoutao

@baozhoutao baozhoutao commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Fixes #5638

首轮 CI 红过一次,根因是我漏扫了 packages/cli 这个消费方。 复盘、修法与「为什么 CLI 输出键名不改」写在 这条评论 里,下文的「改动」「测绘」「验证」三节已按第二轮更新。

前提复核:两半都成立

基线 origin/main = 30b784363,git merge-base --is-ancestor 7bf3d1ce2 origin/main 确认已含 PR #5641

复核项 实测
类型仍声明 deleted? 是。packages/client/src/index.ts:235-239,注释 Spec: DeleteDataResponseSchema 之下第三个键是 deleted: boolean
schema 声明的是 success? 是。packages/spec/src/api/protocol.zod.ts:472DeleteDataResponseSchema = { object, id, success }
生产方两条路径已同形? 是。protocol 侧 deleteData 一直如此;ObjectQL 兜底 packages/runtime/src/action-execution.ts:260 现为 return { object: params.object, id: params.id, success: true }(#5641 落的那一行)
有没有第三条真在运行时给出 deleted 的路径? 没有。REST 的 DELETE /data/:object/:id(packages/rest/src/rest-server.ts:5046)直接 res.json(await p.deleteData(...)),不经过任何改写

第四行是本单的停手条件(「若某处真的在运行时拿到过 deleted,前提可能有变」)。它没有触发 —— 相反,测绘找到的两个下游读取方拿到的正是 undefined,反过来加固了前提。

改动

  1. packages/client/src/index.ts —— DeleteDataResult.deletedsuccess,注释保持指向 schema 并写清「这是对响应体的一句声明、不是改写」。⛔ 不留 deprecated 双键、不加 deleted?: boolean 过渡:消费端同时认两种拼写正是 contract-first 禁止的形状,而且旧键在任何部署上运行时都恒 undefined,改名是揭错而非破坏在用行为。
  2. packages/client/src/data-delete-result-shape.test.ts(新) —— 见下。
  3. packages/client/src/client.hono.test.ts —— 这套 live server 套件此前有 create / get / find,唯独没有 delete;补上真实 HTTP DELETE 用例。
  4. packages/cli/src/commands/data/delete.ts —— deleted: result.deletedresult.success。该处恒 undefined,而 JSON.stringify 丢弃 undefined,所以这个命令声明已久的 deleted在任何一次 os data delete --format json 里都没出现过输出键名保持 deleted 不变(理由见上方评论:同 payload 顶层的 success 是 CLI 信封的另一件事,撞名会把两个不同事实揉成一个;改键名属输出契约决定,留给维护者)。与 os doctor 把「cloud-connection 没装」和「装了但加载不了」当成同一件事 —— ledger 目录在场也照打 clean bill(#5412 假 PASS 的上一层) #5644 无交集 —— 那单整单落在 commands/doctor.ts
  5. content/docs/kernel/runtime-services/data-service.mdx —— delete 的返回值由含糊的「success marker / deleted ID payload」写实为 { object, id, success }。这一句正是 AI 作者会照着猜键名的地方。未触碰 content/docs/releases/

changeset:@objectstack/client major(公开导出接口的破坏性重命名)+ @objectstack/cli patch,升级须知点明「旧键从未在运行时有值,迁移就是把 r.deleted 改成 r.success,服务端无需升级」,并写清 CLI 输出的可观察变化。

测试怎么钉的:三层,且必须说清哪一层会动

#5641 的三层先例,但本单的层次分工与它相反,这一点不能含糊:#5641 改的是运行时构造的字面量,vitest 抓得住;本单改的是类型声明,而类型在 vitest 跑起来之前就被擦掉了。所以:

CLI 侧未新增用例:改名之后,data/delete.ts 已经不可能再拼错这个键 —— 拼错就是 @objectstack/cli#build 编译错误(首轮 CI 演示的正是这一点)。结构性阻断比再加一条断言更强。

反向验证:方向先判后跑,而预判的方向不是模板里的那一种

预判:把 success 改回 deleted 后 ——

  • pnpm --filter @objectstack/client typecheck(新 pin 文件 2 处 + hono 用例 1 处);
  • vitest run仍然全绿。因为类型被擦除,而 client 对响应体不做任何改写:mock 与真实服务端给什么,断言就读到什么,与声明成哪个键无关。

实测,与预判一致:

check:test-typecheck: 2 problem(s)
  • src/client.hono.test.ts: 3 type error(s), ledger records 2 — the debt GREW. Fix the 1 new one(s) …
  • src/data-delete-result-shape.test.ts: 2 type error(s) in a file the ledger does not cover …
 ELIFECYCLE  Command failed with exit code 1.
(同一次回退下的 vitest)
 Test Files  2 passed (2)
      Tests  10 passed (10)

这一层要如实说:本单没有「回退后测试变红」这种常见方向可写 —— 单元测试在原理上看不见这个缺陷,这恰恰是它能在三个面上活到今天的原因(client 自己没有 delete 响应体的用例;CLI 那两行没有任何用例;objectui 的用例喂的是自己编的键)。会动的只有类型门。把 vitest 的绿写成「验证通过」会是一份形状对、内容假的证据,所以这里写明它不动、以及为什么不动。

.deleted 读取方测绘(跨仓;首轮漏过一处,已补全)

DeleteDataResult 全仓 5 处引用(类型定义 + 两个面各 2 处)+ 文档一处引用其名。具名字段读取方仓内只有一个:packages/cli/src/commands/data/delete.ts —— 首轮被我漏掉,CI 抓出,已修(见上)。补扫全仓确认再无第三处(meta/delete.ts:61/63result.deletedmeta.deleteItem,另一个类型、不受影响)。

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,未动client-reactdata-hooks.tsx:291as any 直通、不读键。cloud 仓零命中。

objectui 命中一个真实受害者,已另立单(见下),本 PR 不跨仓改:它的修复依赖本包发版后类型才对得上,今天改会因已发布的 client 类型仍声明 deleted 而 TS 红,而绕过它只剩 as any —— 正是本单禁止的形状。

界外发现

验证(第二轮,CI 24 项 0 失败)

  • pnpm --filter @objectstack/client test:18 files / 229 tests 全绿,exit 0(新文件 6 条 + hono delete 1 条)
  • pnpm --filter @objectstack/client typecheck:绿 —— tsc --noEmit 无输出,check:test-typecheck: OK(债务台账仍是既有的 3 files / 6 errors,未增)
  • pnpm --filter @objectstack/cli test:82 files / 812 tests 全绿,exit 0;typecheck 绿
  • pnpm --filter '...@objectstack/client' build:client + 全部 6 个下游(cli、client-react、app-todo、app-crm、app-showcase、qa/dogfood)全绿 —— 这次是按类型的消费半径扫的,不是按被改的包
  • node scripts/check-nul-bytes.mjs:OK;另对全部改动文件做 grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]' 自扫,全净
  • 未触碰 packages/runtimepackages/specpackages/cli/src/commands/doctor.ts(os doctor 把「cloud-connection 没装」和「装了但加载不了」当成同一件事 —— ledger 目录在场也照打 clean bill(#5412 假 PASS 的上一层) #5644 在飞)、content/docs/releases/

🤖 Generated with Claude Code

https://claude.ai/code/session_016FNvXhtSdnEGEfLEsMmvxh

…ed` (#5638)

`DeleteDataResult` 顶着 `Spec: DeleteDataResponseSchema` 的注释,却把该 schema
的 `success` 声明成了 `deleted`。两个 delete 面(`client.data.delete` 与
project 作用域下的同名方法)都是 `unwrapResponse` / `_unwrap` 纯直通,这个接口
是对服务端响应体的一句声明、而非改写,所以声明必须是 schema 的那一句。

`deleted` 从未被任何 schema 声明、也从未被 `/data/:object/:id` 的任何服务端路径
返回:#5581 / PR #5641 之后 protocol 与 ObjectQL 兜底两条路径同形返回
`{object, id, success}`。因此 `r.deleted` 编译通过、运行时恒 `undefined` ——
改名是揭错,不是破坏在用行为。不留 deprecated 双键(消费端同时认两种拼写正是
contract-first 禁止的形状)。

- 新增 `data-delete-result-shape.test.ts`:类型层钉 `DeleteDataResult` 与 spec
  `DeleteDataResponse` 双向可赋值,加两个面的直通形状 + 「不合成 `success`」反钉。
- `client.hono.test.ts` 补上这套 live server 套件一直缺的 delete 用例:真实
  HTTP DELETE,读 `deleted.success`,并逐字断言键集(`z.object` 会剥未知键,
  单靠 parse 证不了没有残留的 `deleted`)。
- `data-service.mdx` 的 delete 返回值由「success marker / deleted ID payload」
  写实为 `{ object, id, success }`。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016FNvXhtSdnEGEfLEsMmvxh
@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 5, 2026 10:50pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 5, 2026
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/cli, @objectstack/client.

21 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/skills-reference.mdx (via packages/cli, packages/client)
  • content/docs/api/client-sdk.mdx (via @objectstack/cli, @objectstack/client)
  • content/docs/api/data-flow.mdx (via @objectstack/cli, @objectstack/client)
  • content/docs/api/environment-routing.mdx (via @objectstack/cli, @objectstack/client)
  • content/docs/api/error-catalog.mdx (via @objectstack/cli, @objectstack/client)
  • content/docs/automation/hook-bodies.mdx (via packages/cli)
  • content/docs/deployment/backup-restore.mdx (via @objectstack/cli)
  • content/docs/deployment/cli.mdx (via @objectstack/cli)
  • content/docs/deployment/self-hosting.mdx (via @objectstack/cli)
  • content/docs/deployment/validating-metadata.mdx (via packages/cli)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/cli, @objectstack/client)
  • content/docs/kernel/runtime-services/data-service.mdx (via packages/cli, packages/client)
  • content/docs/kernel/runtime-services/index.mdx (via packages/cli, packages/client)
  • content/docs/permissions/authentication.mdx (via @objectstack/cli, @objectstack/client)
  • content/docs/plugins/index.mdx (via @objectstack/cli)
  • content/docs/plugins/packages.mdx (via @objectstack/cli, @objectstack/client)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/cli)
  • content/docs/protocol/kernel/realtime-protocol.mdx (via @objectstack/cli, @objectstack/client)
  • content/docs/releases/implementation-status.mdx (via @objectstack/cli, @objectstack/client)
  • content/docs/releases/v16.mdx (via @objectstack/cli, @objectstack/client)
  • content/docs/releases/v17.mdx (via @objectstack/cli, @objectstack/client)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

…5638)

CI 抓到的漏网消费方 —— 我的读取方测绘按派单枚举的路径(client 自身测试、
client-react、examples、docs)扫,没有覆盖 `packages/cli`,而
`packages/cli/src/commands/data/delete.ts:65/68` 正是 `DeleteDataResult` 在
仓内唯一的具名字段读取方,`@objectstack/cli#build` 因此红。

该处 `deleted: result.deleted` 恒为 `undefined`,`JSON.stringify` 会把
undefined 丢掉 —— 也就是说这个命令声明了很久的 `deleted` 键,在
`os data delete --format json` 的任何一次运行里**都没有出现过**。现改读
`result.success`。

输出键名保持 `deleted` 不变:它是本命令自己的输出键,不是 protocol 的键;
而同一个 payload 顶层的 `success` 表达的是另一件事(CLI 信封的「命令完成」)。
把两者拼成同一个名字正是 #5641 点过的 `body.success` / `body.data.success`
混淆风险。改名属输出契约决定,已在 PR 里交维护者裁定。

与 #5644 无交集:那单整单落在 `packages/cli/src/commands/doctor.ts`。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016FNvXhtSdnEGEfLEsMmvxh

Copy link
Copy Markdown
Contributor Author

首轮 CI 红,原因是我漏扫了一个消费方 —— 已修,如实记录

8 个失败 job 同一个根因:@objectstack/cli#build

src/commands/data/delete.ts(65,27): error TS2339: Property 'deleted' does not exist on type 'DeleteDataResult'.
src/commands/data/delete.ts(68,99): error TS2339: Property 'deleted' does not exist on type 'DeleteDataResult'.
Failed:    @objectstack/cli#build

Test Core (3/3)、Build Core、Dogfood 各腿都是在这一步倒的,没有第二个失败点(逐个 job 拉日志核过)。

我错在哪

读取方测绘我是按派单枚举的路径扫的(packages/client 自身测试、client-reactexamplescontent),而不是按这个类型的消费半径扫全仓 —— packages/cli 不在我的路径清单里,于是 DeleteDataResult 在仓内唯一的具名字段读取方被整个跳过了。这正是 PR #5046 复盘里写下的那条(「按规则的消费半径扫,不要按被改的包扫」),我照着犯了一遍,CI 替我抓住。补扫全仓后确认:packages/cli/src/commands/data/delete.ts唯一遗漏(meta/delete.ts:61/63result.deletedmeta.deleteItem,另一个类型、未受影响)。

修法,以及为什么键名不动

data/delete.tsdeleted: result.deletedundefined,而 JSON.stringify 会丢弃 undefined —— 这个命令声明了很久的 deleted 键,在 os data delete --format json 的任何一次运行里从未出现过。又一个被本单前提解释的死读取。现改读 result.success

输出键名保持 deleted:它是 CLI 自己的输出键,而同一 payload 顶层已经有一个 success,表达的是另一件事(CLI 信封的「命令完成」——这条分支只有成功才走到)。写成 success: result.success 会直接撞键,并把两个不同的事实揉成一个名字,正是 #5641 点过的 body.success / body.data.success 混淆风险。改这个键的名字属于机器可读输出的契约决定,我不替维护者做 —— 已列入报告的 open_questions,PR 保持 draft。

#5644 的关系:无交集

#5644 整单落在 packages/cli/src/commands/doctor.ts(readInstalledPackageEntries() 的 try/catch),本改动落在 commands/data/delete.ts。派单里「不动 packages/cli/src/commands」的理由是 doctor 面在飞,该理由不覆盖本文件;而重命名一个类型却把仓里编译不过的直接消费方留着,PR 本身就不成立。

复验

  • pnpm --filter '...@objectstack/client' build(client + 全部 6 个下游:cli、client-react、app-todo、app-crm、app-showcase、qa/dogfood)—— 全绿,exit 0
  • pnpm --filter @objectstack/cli typecheck —— 绿(tsc --noEmit 无输出)
  • pnpm --filter @objectstack/cli test —— 82 files / 812 tests 全绿,exit 0
  • pnpm --filter @objectstack/client test / typecheck —— 仍绿(client 侧本轮未改动)
  • changeset 增列 @objectstack/cli: patch,写清可观察变化(deleted: true 从「从不出现」变成出现)

另:本轮再次确认 pnpm --filter ... build 会改写受版本控制的 packages/spec/authorable-surface.base.json(已知 #5358 / #5370),两次都已 git checkout -- 丢弃,提交里不含它。


Generated by Claude Code

@baozhoutao
baozhoutao marked this pull request as ready for review August 5, 2026 22:59
@baozhoutao
baozhoutao added this pull request to the merge queue Aug 5, 2026
Merged via the queue into main with commit aa25a81 Aug 5, 2026
26 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-5638-client-delete-result-type branch August 5, 2026 23:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants