Repository navigation
fix(hooks): give ctx.api.update the engine's real (document, options) shape (#616) - #619
Merged
Merged
Conversation
… shape (#616) `HookObjectApi.update` was declared `(id: string, doc)` while both surfaces the runtime can inject as `ctx.api` — ObjectRepository and the sandbox repo facade — forward to `engine.update(object, data, options)`. The second positional argument is the OPTIONS bag, so every hook-side derived write threw "update('crm_opportunity') does not recognise option 'amount'" (#4371) on every invocation: 96 throws on one boot of a freshly seeded install. All nine call sites are `onError: 'log'`, so the only symptom was a parent record that never moved. The declaration was the contract as far as the compiler was concerned (`HookContext.api` is `unknown`), and both hook stand-ins implemented the declaration rather than the engine, so the suite was green throughout. - `_hook-api.ts` now describes the real surface: `update(HookUpdateDoc, HookUpdateOptions)` with the id inside the document, `delete({ where })`, and no `updateMany` — a method neither injected shape has. - Migrated all nine call sites (rollups, campaign snapshot, account promotion, signed_date, case rollup, quote close-out, activity bubble). - `hook-harness.ts` rejects the broken shape, a missing/disagreeing row scope and a document without an id, instead of quietly honouring them; the second, more permissive ad-hoc stand-in in `hooks-runtime.test.ts` is deleted in favour of it. - New `test/hook-write-shape.test.ts` asserts the ARGUMENT LIST reaching the engine for all nine writes, running each hook's shipped body through the real QuickJS sandbox, plus a static scan so a future `.update(id, …)` fails. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019SS7C5SXpniKeCApxgARyf
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
os-zhuang
marked this pull request as ready for review
August 2, 2026 12:15
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #616
问题
src/objects/_hook-api.ts把update声明成(id: string, doc),而运行时注入到ctx.api的两种实现(@objectstack/objectql的ObjectRepository、@objectstack/runtime的buildEngineRepoFacade)都直接转发到engine.update(object, data, options)—— 第二个位置参数是 options,不是文档。于是钩子侧全部 9 处派生写入在每次调用时都抛:这 9 处都是
onError: 'log',所以唯一的症状是「父记录永远不动」:商机金额不随明细行重算、报价合计不重算、活动完成快照、客户升级、signed_date盖章、个案服务汇总、报价接受后的商机关闭、任务活动冒泡——全是死的。关键不在签名写错,而在于这个类型是一份编译器无法校验的手写描述(
HookContext.api是unknown),所以声明本身就成了事实上的契约:编译器为 9 处调用背书,两个测试替身又照着声明而不是照着引擎实现,于是测试全绿而功能全死。改动(按 issue 的 contract-first 顺序)
_hook-api.ts,让它描述真实 API,由编译器把其余调用点全部暴露出来:update(doc: HookUpdateDoc, options: HookUpdateOptions),HookUpdateDoc = Doc & { id: string }—— id 走文档内部(引擎优先读data.id)。把 id 设成必填,是让旧写法从运行时错误变成编译错误的关键:字符串不是文档。delete({ where });HookUpdateOptions只开放where(必填),不开放multi—— 钩子里的派生写入不该退化成批量写。多余的键由 excess-property check 在调用点拒掉。updateMany:两种注入实现都没有这个方法,声明它等于宣传一个运行时并不提供的能力(Prime Directive chore(deps-dev): bump eslint from 8.57.1 to 9.39.2 #10),真调用会is not a function。update({ id, …fields }, { where: { id } })(src/actions/contact.actions.ts一直是这么写的,并已由test/action-sandbox.test.ts钉住)——一种写法,不是两种方言。hook-harness.ts现在会对「id 当文档传」「文档没有 id」「缺少行作用域」「where.id与doc.id不一致」直接抛错;hooks-runtime.test.ts里那个更宽松的第二份手写makeApi(连filter都接受)删除,改用同一个 harness。新测试:断言到达引擎的参数形状
test/hook-write-shape.test.ts。这条是本 issue 的重点——只验证"处理器 resolve 了"或"替身里的行变了"的测试,对着错误签名一样会绿。所以它断言的是引擎收到的参数列表:每个钩子的 shipped body 经由真实的hookBodyRunnerFactory+ QuickJS + 运行时自己的 repo facade 执行,然后逐条检查args.length === 2,第一个参数是对象而非 id;doc.id等于目标行;args[1]严格等于{ where: { id } };覆盖 9 处写入全部,并有一条计数断言:
src/objects/里的.update(调用点数量必须等于本文件演练的数量,新增一处而不写用例会红。另有静态扫描:src/objects/**里.update(/.delete(后面第一个非空白字符必须是{。反向验证(把
opportunity_line_item.hook.ts临时改回旧写法):运行时验证(同一份 seed 的 A/B)
在 clean
.objectstack/data上各启一次 fresh 实例,同为 277 行 seed:[hook] handler failedopportunity_amount_rollup+quote_total_rollup,与 issue 记录一致)并且真的会动了 —— REST 改一条明细行:
Vertex Enterprise Rollout:明细 quantity 1 → 11,amount675,000 → 1,425,000(= 预期 +750,000);presented状态:明细 quantity 2 → 4,subtotal500,000 → 550,000、discount_amount→ 27,500、total_price→ 563,000。验证
pnpm validate && pnpm typecheck && pnpm lint && pnpm hygiene && pnpm build && pnpm test全绿;Test Files 36 passed / Tests 745 passed | 1 skipped。build 仍报all 24 callables are body-only(钩子照旧能纯元数据下发)。范围说明
mass_update_stage的同类误用)不在本 PR 范围:action body 是元数据里的 JS 源码字符串,与钩子不共用任何类型声明,因此不满足 issue 里"两侧共用一份声明才一并修"的条件。它的错误形状目前由test/action-sandbox.test.ts钉住,复活那个按钮的人会看到。content/docs/releases/;已附.changeset/hook-api-update-document-shape.md。🤖 Generated with Claude Code
https://claude.ai/code/session_019SS7C5SXpniKeCApxgARyf
Generated by Claude Code