Skip to content

Commit f192981

Browse files
hotlongclaude
andauthored
docs(kernel): services.data 页的 Example 去掉 hook 语境混搭,并说清 Canonical source 为何是 SDK (#5944) (#5995)
原 Example 把「hook 才有的 `ctx.input.contact_id`」和「hook 拿不到的 `services.data`」拼在一起。实测该块不是「在 hook 里跑得慢」而是根本不成立: 按 check:skill-examples 同款 tsconfig 编译,`services` 与 `ctx` 双双 TS2304 (Cannot find name),因为这两个名字在本运行时里没有任何一处同时在作用域内 —— hook 有 `ctx` 没 `services`,持有绑定的代码有 `services` 没 `ctx`。 复核 #5720 的结论在 origin/main 未变:engine.ts 四处 `hookContext` 构造点 (4796/4929/5505/6059)逐键构造 object/event/input/session/provenance/user/ api/transaction/ql,`buildSandboxContext`(runtime/src/sandbox/body-runner.ts) 同样只产 input/previous/user/session/event/object/result/api/log/crypto —— 两条路径都没有 `services` 键。 照 PR #5938 定下的模式改(取「去掉 `ctx.` 的 hook 味道」那一支): - Example 改写为**持有该绑定的代码**在调用(形状对齐 sharing-service.mdx 的 `mayEditContract(sharing: ISharingService, …)`):记录 id 作为普通入参传入, 全块无 `ctx`。顺带修掉两处误读 —— `get` 返回的是信封 `{ object, id, record }` 而非行本身(原文 `contact.id` 是撞对的),`find` 返回 `{ records, … }`。 - 新增 binding note Callout:hook 没有 `services` 键,其跨对象通道是 `ctx.api`, 并指向 examples.mdx 第 2 节(#5938 已进 os:check 真编译的那块),不在本页重复。 - 新增 `## Canonical source` 一节,答分诊点名的措辞问题:本页是全章唯一不指向 `contracts/*-service.ts` 的页,因为 spec 根本没有 `IDataService`;最近邻 `IDataEngine` 是形状不同的更低一层面(`find(objectName, query, options) : Promise<any[]>`)。故签名取 `ObjectStackClient.data`,返回体对应 spec 的 `*DataResponseSchema`(protocol.zod.ts),托管运行时按同一形状绑定。 os:check 标记:不补,且与 #5945 无关 —— 反向验证给出的是另一条硬约束。给该块 加标记后实测: content/docs/kernel/runtime-services/data-service.mdx:88:40 error TS2307: Cannot find module '@objectstack/client' or its corresponding type declarations. 仅此一条诊断。check:skill-examples 的 paths 由 `@objectstack/spec` 自己的 exports 派生,而 spec 不依赖 client,所以标记后只能改成手写 `DataService` 局部类型 —— 那样这块就只钉住它自己。此约束已写进正文,待本面有 spec 侧契约 可 import 时再补标记。本块从不触碰 `ctx.api`,故不受 #5945 裁决影响。 门禁:check:doc-authoring 362 文件干净;check:nul-bytes 5745 文件干净; check:docs-audit-scope 绿;check:skill-examples 207 块全绿(未变,本页不贡献 标记块也不产生 orphan)。Example 块单独对已构建的 @objectstack/client 声明 编译:tsc --noEmit 退出 0。MDX 以 @mdx-js/mdx 编译通过(examples.mdx / sharing-service.mdx 作阳性对照)。 Fixes #5944 Claude-Session: https://claude.ai/code/session_01BDmDsu2575gDxeMCxXhDE3 Co-authored-by: Claude <noreply@anthropic.com>
1 parent dd98cba commit f192981

1 file changed

Lines changed: 70 additions & 7 deletions

File tree

content/docs/kernel/runtime-services/data-service.mdx

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,28 @@ description: CRUD runtime helper API for records (`get`, `find`, `create`, `upda
66
# `services.data`
77

88
- **Stability:** `stable`
9-
- **Canonical source:** `packages/client/src/index.ts`
9+
- **Canonical source:** `packages/client/src/index.ts` — the `ObjectStackClient.data`
10+
surface (see [Canonical source](#canonical-source) for why this page names the SDK
11+
rather than a `contracts/*-service.ts` interface)
12+
13+
<Callout type="warn" title="Who holds this binding — a hook does not">
14+
15+
This page documents the `services.data` **contract surface**: the signatures, not a
16+
binding every runtime surface receives (see the
17+
[binding note](/docs/kernel/runtime-services)). A **data hook never gets one.** The engine
18+
builds a hook context key by key — `object` / `event` / `input` / `session` / `provenance`
19+
/ `user` / `api` / `transaction` / `ql` — and sets no `services` key at any of its
20+
construction sites, so `services.data.get(…)` written beside a `ctx.input.…` read throws
21+
on `services` at the first call rather than reading anything
22+
([#5720](https://github.com/objectstack-ai/objectstack/issues/5720)).
23+
24+
A hook's own cross-object channel is `ctx.api`: see
25+
[Examples §2](/docs/kernel/runtime-services/examples) for a
26+
`ctx.api.object('crm_account').findOne(…)` read inside a real `beforeInsert` /
27+
`beforeUpdate` handler. The [Example](#example) below is written the other way round — as
28+
the code that **holds** the binding calls it, with plain arguments and no `ctx` in sight.
29+
30+
</Callout>
1031

1132
## Methods
1233

@@ -18,6 +39,22 @@ services.data.update<T = any>(object: string, id: string, data: Partial<T>): Pro
1839
services.data.delete(object: string, id: string): Promise<DeleteDataResult>
1940
```
2041
42+
## Canonical source
43+
44+
Every sibling page in this chapter names a contract interface
45+
(`packages/spec/src/contracts/sharing-service.ts`, `queue-service.ts`, …). This one names
46+
the **client SDK** instead, and the difference is real rather than an oversight: the spec
47+
declares no `IDataService`. Its nearest neighbour, `IDataEngine`
48+
(`packages/spec/src/contracts/data-engine.ts`), is a *lower* surface with a different
49+
shape — `find(objectName, query, options): Promise<any[]>` straight at the engine — not
50+
the object-name-plus-options protocol call documented above.
51+
52+
So the signatures come from `ObjectStackClient.data` (`packages/client/src/index.ts`), and
53+
the payloads they resolve to are the spec's wire schemas — `GetDataResponseSchema`,
54+
`CreateDataResponseSchema`, `UpdateDataResponseSchema`, `DeleteDataResponseSchema` in
55+
`packages/spec/src/api/protocol.zod.ts` — which the SDK's `*DataResult` interfaces mirror
56+
key for key. A managed runtime binds `services.data` to this same shape.
57+
2158
## Parameters
2259
2360
- `object`: short object name (for example `task`, `account`)
@@ -41,11 +78,37 @@ services.data.delete(object: string, id: string): Promise<DeleteDataResult>
4178
4279
## Example
4380
81+
Call these methods from code that **holds** the binding — a managed runtime hands it in as
82+
`services.data` — so the record id arrives as an ordinary argument. It is deliberately not
83+
a hook body: a hook has no `services` key to reach through (see above), and reads other
84+
objects via `ctx.api`.
85+
4486
```ts
45-
const contact = await services.data.get('contact', ctx.input.contact_id);
46-
const orders = await services.data.find('sales_order', {
47-
filter: { contact_id: contact.id },
48-
sort: [{ field: 'created_at', order: 'desc' }],
49-
top: 20,
50-
});
87+
import type { ObjectStackClient } from '@objectstack/client';
88+
89+
/** The `services.data` binding, exactly as this page's Canonical source declares it. */
90+
type DataService = ObjectStackClient['data'];
91+
92+
export async function recentOrdersForContact(data: DataService, contactId: string) {
93+
// `get` resolves the response envelope `{ object, id, record }` — the row is `record`.
94+
const { record: contact } = await data.get<{ id: string; name: string }>('contact', contactId);
95+
96+
// `find` resolves `{ records, total?, hasMore? }`.
97+
const { records: orders } = await data.find<{ id: string; amount: number }>('sales_order', {
98+
filter: { contact_id: contact.id },
99+
sort: [{ field: 'created_at', order: 'desc' }],
100+
top: 20,
101+
});
102+
103+
return { contact, orders };
104+
}
51105
```
106+
107+
The block carries no `{/* os:check */}` marker, and that is a measurement rather than an
108+
omission: `check:skill-examples` compiles marked blocks against the built
109+
`@objectstack/spec` declarations only — its `paths` map is derived from that package's own
110+
`exports`, and `@objectstack/spec` does not depend on `@objectstack/client`. A marked
111+
block here would therefore have to hand-declare `DataService` instead of importing it,
112+
which pins the example to itself and nothing else. The marker becomes worth adding the day
113+
this surface has a spec-side contract to import (see the
114+
[Canonical source](#canonical-source) note).

0 commit comments

Comments
 (0)