@tier0/sdk-v2 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE.md +7 -0
- package/README.md +201 -2
- package/dist/cjs/app-identity-contract.generated.d.ts +22 -0
- package/dist/cjs/app-identity-contract.generated.js +51 -0
- package/dist/cjs/app-identity-contract.generated.js.map +1 -0
- package/dist/cjs/capabilities.d.ts +54 -0
- package/dist/cjs/capabilities.js +134 -0
- package/dist/cjs/capabilities.js.map +1 -0
- package/dist/cjs/client.d.ts +604 -0
- package/dist/cjs/client.js +35 -0
- package/dist/cjs/client.js.map +1 -0
- package/dist/cjs/cloud-server.d.ts +31 -0
- package/dist/cjs/cloud-server.js +114 -0
- package/dist/cjs/cloud-server.js.map +1 -0
- package/dist/cjs/cloud-transport.d.ts +46 -0
- package/dist/cjs/cloud-transport.js +192 -0
- package/dist/cjs/cloud-transport.js.map +1 -0
- package/dist/cjs/controls.d.ts +93 -0
- package/dist/cjs/controls.js +169 -0
- package/dist/cjs/controls.js.map +1 -0
- package/dist/cjs/data/bootstrap.d.ts +66 -0
- package/dist/cjs/data/bootstrap.js +282 -0
- package/dist/cjs/data/bootstrap.js.map +1 -0
- package/dist/cjs/data/call.d.ts +16 -0
- package/dist/cjs/data/call.js +51 -0
- package/dist/cjs/data/call.js.map +1 -0
- package/dist/cjs/data/client.d.ts +92 -0
- package/dist/cjs/data/client.js +102 -0
- package/dist/cjs/data/client.js.map +1 -0
- package/dist/cjs/data/collection.d.ts +238 -0
- package/dist/cjs/data/collection.js +441 -0
- package/dist/cjs/data/collection.js.map +1 -0
- package/dist/cjs/data/context.d.ts +10 -0
- package/dist/cjs/data/context.js +39 -0
- package/dist/cjs/data/context.js.map +1 -0
- package/dist/cjs/data/declarations.d.ts +11 -0
- package/dist/cjs/data/declarations.js +18 -0
- package/dist/cjs/data/declarations.js.map +1 -0
- package/dist/cjs/data/define.d.ts +69 -0
- package/dist/cjs/data/define.js +31 -0
- package/dist/cjs/data/define.js.map +1 -0
- package/dist/cjs/data/error.d.ts +60 -0
- package/dist/cjs/data/error.js +184 -0
- package/dist/cjs/data/error.js.map +1 -0
- package/dist/cjs/data/index.d.ts +21 -0
- package/dist/cjs/data/index.js +43 -0
- package/dist/cjs/data/index.js.map +1 -0
- package/dist/cjs/data/schema.d.ts +82 -0
- package/dist/cjs/data/schema.js +607 -0
- package/dist/cjs/data/schema.js.map +1 -0
- package/dist/cjs/data/sort.d.ts +8 -0
- package/dist/cjs/data/sort.js +21 -0
- package/dist/cjs/data/sort.js.map +1 -0
- package/dist/cjs/files.d.ts +67 -0
- package/dist/cjs/files.js +119 -0
- package/dist/cjs/files.js.map +1 -0
- package/dist/cjs/framework-scope.d.ts +1 -0
- package/dist/cjs/framework-scope.js +14 -0
- package/dist/cjs/framework-scope.js.map +1 -0
- package/dist/cjs/index.d.ts +14 -0
- package/dist/cjs/index.js +33 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/mq/broker.d.ts +65 -0
- package/dist/cjs/mq/broker.js +115 -0
- package/dist/cjs/mq/broker.js.map +1 -0
- package/dist/cjs/mq/client.d.ts +37 -0
- package/dist/cjs/mq/client.js +321 -0
- package/dist/cjs/mq/client.js.map +1 -0
- package/dist/cjs/mq/index.d.ts +5 -0
- package/dist/cjs/mq/index.js +11 -0
- package/dist/cjs/mq/index.js.map +1 -0
- package/dist/cjs/mq/types.d.ts +48 -0
- package/dist/cjs/mq/types.js +3 -0
- package/dist/cjs/mq/types.js.map +1 -0
- package/dist/cjs/openapi/api.generated.d.ts +820 -0
- package/dist/cjs/openapi/api.generated.js +607 -0
- package/dist/cjs/openapi/api.generated.js.map +1 -0
- package/dist/cjs/openapi/client.d.ts +56 -0
- package/dist/cjs/openapi/client.js +219 -0
- package/dist/cjs/openapi/client.js.map +1 -0
- package/dist/cjs/openapi/codecs.generated.d.ts +2234 -0
- package/dist/cjs/openapi/codecs.generated.js +6 -0
- package/dist/cjs/openapi/codecs.generated.js.map +1 -0
- package/dist/cjs/openapi/index.d.ts +5 -0
- package/dist/cjs/openapi/index.js +12 -0
- package/dist/cjs/openapi/index.js.map +1 -0
- package/dist/cjs/openapi/types.generated.d.ts +3227 -0
- package/dist/cjs/openapi/types.generated.js +3 -0
- package/dist/cjs/openapi/types.generated.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/react.d.ts +9 -0
- package/dist/cjs/react.js +16 -0
- package/dist/cjs/react.js.map +1 -0
- package/dist/cjs/runtime-context.d.ts +9 -0
- package/dist/cjs/runtime-context.js +25 -0
- package/dist/cjs/runtime-context.js.map +1 -0
- package/dist/cjs/runtime-env.d.ts +1 -0
- package/dist/cjs/runtime-env.js +18 -0
- package/dist/cjs/runtime-env.js.map +1 -0
- package/dist/cjs/source.d.ts +39 -0
- package/dist/cjs/source.js +29 -0
- package/dist/cjs/source.js.map +1 -0
- package/dist/cjs/stream.d.ts +40 -0
- package/dist/cjs/stream.js +33 -0
- package/dist/cjs/stream.js.map +1 -0
- package/dist/cjs/uns-v2/client.d.ts +54 -0
- package/dist/cjs/uns-v2/client.js +177 -0
- package/dist/cjs/uns-v2/client.js.map +1 -0
- package/dist/cjs/uns-v2/codec.d.ts +11 -0
- package/dist/cjs/uns-v2/codec.js +290 -0
- package/dist/cjs/uns-v2/codec.js.map +1 -0
- package/dist/cjs/uns-v2/data-client.d.ts +10 -0
- package/dist/cjs/uns-v2/data-client.js +18 -0
- package/dist/cjs/uns-v2/data-client.js.map +1 -0
- package/dist/cjs/uns-v2/generated.d.ts +15551 -0
- package/dist/cjs/uns-v2/generated.js +139 -0
- package/dist/cjs/uns-v2/generated.js.map +1 -0
- package/dist/cjs/uns-v2/index.d.ts +5 -0
- package/dist/cjs/uns-v2/index.js +24 -0
- package/dist/cjs/uns-v2/index.js.map +1 -0
- package/dist/cjs/vue.d.ts +9 -0
- package/dist/cjs/vue.js +39 -0
- package/dist/cjs/vue.js.map +1 -0
- package/dist/esm/app-identity-contract.generated.d.ts +22 -0
- package/dist/esm/app-identity-contract.generated.js +48 -0
- package/dist/esm/app-identity-contract.generated.js.map +1 -0
- package/dist/esm/capabilities.d.ts +54 -0
- package/dist/esm/capabilities.js +129 -0
- package/dist/esm/capabilities.js.map +1 -0
- package/dist/esm/client.d.ts +604 -0
- package/dist/esm/client.js +32 -0
- package/dist/esm/client.js.map +1 -0
- package/dist/esm/cloud-server.d.ts +31 -0
- package/dist/esm/cloud-server.js +109 -0
- package/dist/esm/cloud-server.js.map +1 -0
- package/dist/esm/cloud-transport.d.ts +46 -0
- package/dist/esm/cloud-transport.js +187 -0
- package/dist/esm/cloud-transport.js.map +1 -0
- package/dist/esm/controls.d.ts +93 -0
- package/dist/esm/controls.js +163 -0
- package/dist/esm/controls.js.map +1 -0
- package/dist/esm/data/bootstrap.d.ts +66 -0
- package/dist/esm/data/bootstrap.js +277 -0
- package/dist/esm/data/bootstrap.js.map +1 -0
- package/dist/esm/data/call.d.ts +16 -0
- package/dist/esm/data/call.js +47 -0
- package/dist/esm/data/call.js.map +1 -0
- package/dist/esm/data/client.d.ts +92 -0
- package/dist/esm/data/client.js +99 -0
- package/dist/esm/data/client.js.map +1 -0
- package/dist/esm/data/collection.d.ts +238 -0
- package/dist/esm/data/collection.js +434 -0
- package/dist/esm/data/collection.js.map +1 -0
- package/dist/esm/data/context.d.ts +10 -0
- package/dist/esm/data/context.js +34 -0
- package/dist/esm/data/context.js.map +1 -0
- package/dist/esm/data/declarations.d.ts +11 -0
- package/dist/esm/data/declarations.js +10 -0
- package/dist/esm/data/declarations.js.map +1 -0
- package/dist/esm/data/define.d.ts +69 -0
- package/dist/esm/data/define.js +27 -0
- package/dist/esm/data/define.js.map +1 -0
- package/dist/esm/data/error.d.ts +60 -0
- package/dist/esm/data/error.js +178 -0
- package/dist/esm/data/error.js.map +1 -0
- package/dist/esm/data/index.d.ts +21 -0
- package/dist/esm/data/index.js +18 -0
- package/dist/esm/data/index.js.map +1 -0
- package/dist/esm/data/schema.d.ts +82 -0
- package/dist/esm/data/schema.js +596 -0
- package/dist/esm/data/schema.js.map +1 -0
- package/dist/esm/data/sort.d.ts +8 -0
- package/dist/esm/data/sort.js +18 -0
- package/dist/esm/data/sort.js.map +1 -0
- package/dist/esm/files.d.ts +67 -0
- package/dist/esm/files.js +113 -0
- package/dist/esm/files.js.map +1 -0
- package/dist/esm/framework-scope.d.ts +1 -0
- package/dist/esm/framework-scope.js +11 -0
- package/dist/esm/framework-scope.js.map +1 -0
- package/dist/esm/index.d.ts +14 -0
- package/dist/esm/index.js +10 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/mq/broker.d.ts +65 -0
- package/dist/esm/mq/broker.js +109 -0
- package/dist/esm/mq/broker.js.map +1 -0
- package/dist/esm/mq/client.d.ts +37 -0
- package/dist/esm/mq/client.js +314 -0
- package/dist/esm/mq/client.js.map +1 -0
- package/dist/esm/mq/index.d.ts +5 -0
- package/dist/esm/mq/index.js +3 -0
- package/dist/esm/mq/index.js.map +1 -0
- package/dist/esm/mq/types.d.ts +48 -0
- package/dist/esm/mq/types.js +2 -0
- package/dist/esm/mq/types.js.map +1 -0
- package/dist/esm/openapi/api.generated.d.ts +820 -0
- package/dist/esm/openapi/api.generated.js +603 -0
- package/dist/esm/openapi/api.generated.js.map +1 -0
- package/dist/esm/openapi/client.d.ts +56 -0
- package/dist/esm/openapi/client.js +210 -0
- package/dist/esm/openapi/client.js.map +1 -0
- package/dist/esm/openapi/codecs.generated.d.ts +2234 -0
- package/dist/esm/openapi/codecs.generated.js +3 -0
- package/dist/esm/openapi/codecs.generated.js.map +1 -0
- package/dist/esm/openapi/index.d.ts +5 -0
- package/dist/esm/openapi/index.js +3 -0
- package/dist/esm/openapi/index.js.map +1 -0
- package/dist/esm/openapi/types.generated.d.ts +3227 -0
- package/dist/esm/openapi/types.generated.js +2 -0
- package/dist/esm/openapi/types.generated.js.map +1 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/react.d.ts +9 -0
- package/dist/esm/react.js +12 -0
- package/dist/esm/react.js.map +1 -0
- package/dist/esm/runtime-context.d.ts +9 -0
- package/dist/esm/runtime-context.js +22 -0
- package/dist/esm/runtime-context.js.map +1 -0
- package/dist/esm/runtime-env.d.ts +1 -0
- package/dist/esm/runtime-env.js +15 -0
- package/dist/esm/runtime-env.js.map +1 -0
- package/dist/esm/source.d.ts +39 -0
- package/dist/esm/source.js +25 -0
- package/dist/esm/source.js.map +1 -0
- package/dist/esm/stream.d.ts +40 -0
- package/dist/esm/stream.js +29 -0
- package/dist/esm/stream.js.map +1 -0
- package/dist/esm/uns-v2/client.d.ts +54 -0
- package/dist/esm/uns-v2/client.js +170 -0
- package/dist/esm/uns-v2/client.js.map +1 -0
- package/dist/esm/uns-v2/codec.d.ts +11 -0
- package/dist/esm/uns-v2/codec.js +284 -0
- package/dist/esm/uns-v2/codec.js.map +1 -0
- package/dist/esm/uns-v2/data-client.d.ts +10 -0
- package/dist/esm/uns-v2/data-client.js +14 -0
- package/dist/esm/uns-v2/data-client.js.map +1 -0
- package/dist/esm/uns-v2/generated.d.ts +15551 -0
- package/dist/esm/uns-v2/generated.js +136 -0
- package/dist/esm/uns-v2/generated.js.map +1 -0
- package/dist/esm/uns-v2/index.d.ts +5 -0
- package/dist/esm/uns-v2/index.js +5 -0
- package/dist/esm/uns-v2/index.js.map +1 -0
- package/dist/esm/vue.d.ts +9 -0
- package/dist/esm/vue.js +36 -0
- package/dist/esm/vue.js.map +1 -0
- package/docs/acceptance-2026-10-22.md +88 -0
- package/package.json +242 -4
- package/skills/tier0-sdk-v2/SKILL.md +66 -0
- package/skills/tier0-sdk-v2/references/integration.md +72 -0
- package/skills/tier0-sdk-v2-agent/SKILL.md +73 -0
- package/skills/tier0-sdk-v2-agent/references/results.md +45 -0
- package/skills/tier0-sdk-v2-controls/SKILL.md +63 -0
- package/skills/tier0-sdk-v2-data/SKILL.md +57 -0
- package/skills/tier0-sdk-v2-data/references/consistency.md +70 -0
- package/skills/tier0-sdk-v2-data/references/errors.md +69 -0
- package/skills/tier0-sdk-v2-data/references/modeling.md +77 -0
- package/skills/tier0-sdk-v2-data/references/read-write.md +72 -0
- package/skills/tier0-sdk-v2-data/references/schema-evolution.md +36 -0
- package/skills/tier0-sdk-v2-files/SKILL.md +85 -0
- package/skills/tier0-sdk-v2-files/references/delete.md +22 -0
- package/skills/tier0-sdk-v2-files/references/download.md +49 -0
- package/skills/tier0-sdk-v2-files/references/multipart.md +42 -0
- package/skills/tier0-sdk-v2-files/references/upload.md +27 -0
- package/skills/tier0-sdk-v2-files/references/url.md +22 -0
- package/skills/tier0-sdk-v2-members/SKILL.md +56 -0
- package/skills/tier0-sdk-v2-members/references/app-members.md +41 -0
- package/skills/tier0-sdk-v2-members/references/platform-members.md +56 -0
- package/skills/tier0-sdk-v2-mq/SKILL.md +78 -0
- package/skills/tier0-sdk-v2-mq/references/quickstart.md +69 -0
- package/skills/tier0-sdk-v2-notifications/SKILL.md +42 -0
- package/skills/tier0-sdk-v2-notifications/references/get.md +34 -0
- package/skills/tier0-sdk-v2-notifications/references/send.md +72 -0
- package/skills/tier0-sdk-v2-source/SKILL.md +38 -0
- package/skills/tier0-sdk-v2-source/references/changes.md +40 -0
- package/skills/tier0-sdk-v2-source/references/create.md +63 -0
- package/skills/tier0-sdk-v2-source/references/delete.md +47 -0
- package/skills/tier0-sdk-v2-source/references/protocols.md +36 -0
- package/skills/tier0-sdk-v2-source/references/read.md +34 -0
- package/skills/tier0-sdk-v2-source/references/state.md +37 -0
- package/skills/tier0-sdk-v2-source/references/update.md +61 -0
- package/skills/tier0-sdk-v2-stream/SKILL.md +43 -0
- package/skills/tier0-sdk-v2-stream/references/create.md +55 -0
- package/skills/tier0-sdk-v2-stream/references/delete.md +35 -0
- package/skills/tier0-sdk-v2-stream/references/deploy.md +63 -0
- package/skills/tier0-sdk-v2-stream/references/lifecycle.md +25 -0
- package/skills/tier0-sdk-v2-stream/references/read.md +35 -0
- package/skills/tier0-sdk-v2-stream/references/update.md +35 -0
- package/skills/tier0-sdk-v2-system/SKILL.md +64 -0
- package/skills/tier0-sdk-v2-system/references/info.md +23 -0
- package/skills/tier0-sdk-v2-system/references/whoami.md +21 -0
- package/skills/tier0-sdk-v2-uns/SKILL.md +53 -0
- package/skills/tier0-sdk-v2-uns/references/create.md +85 -0
- package/skills/tier0-sdk-v2-uns/references/data-workflows.md +73 -0
- package/skills/tier0-sdk-v2-uns/references/delete.md +69 -0
- package/skills/tier0-sdk-v2-uns/references/namespace.md +75 -0
- package/skills/tier0-sdk-v2-uns/references/read.md +32 -0
- package/skills/tier0-sdk-v2-uns/references/restore.md +41 -0
- package/skills/tier0-sdk-v2-uns/references/update.md +86 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Agent 结果与并发编辑
|
|
2
|
+
|
|
3
|
+
## 按原意图查回保存结果
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { createClient, type Tier0Client } from '@tier0/sdk-v2';
|
|
7
|
+
|
|
8
|
+
type SaveInput = Parameters<Tier0Client['agent']['results']['create']>[0];
|
|
9
|
+
export async function saveResult(input: SaveInput) {
|
|
10
|
+
return createClient().agent.results.create(input);
|
|
11
|
+
}
|
|
12
|
+
export async function reconcileResult(operationId: string) {
|
|
13
|
+
return createClient().agent.results.get({ operationId });
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
保存必填 resultSchemaVersion/title/missingData/operationId/source;source 至少含 platformSessionId/agentId。版本和 manifest 字段以实际业务契约为准。保存前持久化 operationId;结果未知时按同一个 operationId 查回,禁止换 ID 重复创建。
|
|
18
|
+
|
|
19
|
+
results.get 的 resultId 与 operationId 二选一。results.pin({ resultId, canvasId }) 是写操作,canvasId 从 canvases.list 取得,不能因查看结果而自动固定到画布。
|
|
20
|
+
|
|
21
|
+
## 卡片乐观并发
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { createClient, type Tier0Client } from '@tier0/sdk-v2';
|
|
25
|
+
|
|
26
|
+
type CardUpdate = Parameters<Tier0Client['agent']['cards']['update']>[0];
|
|
27
|
+
export async function submitCardEdit(input: CardUpdate) {
|
|
28
|
+
// expectedCardSpec 是读取时的完整原值,cardSpec 是编辑后的新值。
|
|
29
|
+
return createClient().agent.cards.update(input);
|
|
30
|
+
}
|
|
31
|
+
type LibraryUpdate = Parameters<Tier0Client['agent']['library']['update']>[0];
|
|
32
|
+
export async function submitLibraryEdit(input: LibraryUpdate) {
|
|
33
|
+
return createClient().agent.library.update(input);
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
cardId/cardSpec/expectedCardSpec 必填;ord 省略表示不改,不能为了凑参数默认传 0。资料库更新要求 libraryItemId、expectedItem、cardSpec,expectedItem 包括原 cardKey/cardSpec/tags/sourceSessionId。
|
|
38
|
+
|
|
39
|
+
409 时重新读取并展示冲突,保留用户本次编辑意图后再合并。不要用刚读的新内容填充 expected*,却仍提交覆盖别人修改的旧全文。
|
|
40
|
+
|
|
41
|
+
## 作业查询与触发边界
|
|
42
|
+
|
|
43
|
+
jobs.trigger 回执中的 jobRunId 用于 jobRuns.get;dispatchState 不是执行结果。查询列表可以按业务需要有界轮询,不自动再次触发。更新 notifyEnabled=2 时省略 notify;notifyEnabled=1 时提供当前认可的通知结构。
|
|
44
|
+
|
|
45
|
+
nodes.indexSync 会改变索引状态,relations.teach 会新增关系;用户只要搜索或查关系列表时分别用 searchSemantic 和 relations.list。
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tier0-sdk-v2-controls
|
|
3
|
+
description: 使用 @tier0/sdk-v2 发现、描述和调用 Tier0 Control 业务契约,区分读写副作用、契约版本、幂等要求和执行结果未知。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Control 契约调用
|
|
7
|
+
|
|
8
|
+
Control 是已发布的业务调用入口;它可由 Stream 提供,但调用方不选择内部 streamId、entryId 或部署版本。使用服务端 client.controls,不重建旧 Flow 调用。
|
|
9
|
+
|
|
10
|
+
## 发现和读取契约
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { createClient } from '@tier0/sdk-v2';
|
|
14
|
+
|
|
15
|
+
export async function discoverControls(search: string) {
|
|
16
|
+
return createClient().controls.discover({ search, page: 1, size: 20 });
|
|
17
|
+
}
|
|
18
|
+
export async function describeControl(code: string) {
|
|
19
|
+
return createClient().controls.use({ code }).describe();
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
discover 返回 total/list,分页从 1 开始,size 为 1–200。code 取自发现结果,不由显示名称猜测。describe 返回 inputSchema、outputSchema、sideEffect、idempotency、contractVersion、timeoutMs 和 status。
|
|
24
|
+
|
|
25
|
+
## 构造调用
|
|
26
|
+
|
|
27
|
+
1. 依据 inputSchema 填写输入,业务 JSON 不等于任意控制参数。SDK 的泛型只提供静态提示,不能替代契约校验。
|
|
28
|
+
2. 判断 sideEffect=read/write,与用户本次任务的授权范围一致。
|
|
29
|
+
3. 当 idempotency=required 时提供稳定的业务意图键;optional/none 按契约决定,不自动生成 UUID 重试。
|
|
30
|
+
4. 调用后检查业务 result 与 status,记录 runID/contractVersion,不能只看请求未抛错。
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import {
|
|
34
|
+
createClient, ControlResultUnknownError, type ControlJSON,
|
|
35
|
+
} from '@tier0/sdk-v2';
|
|
36
|
+
|
|
37
|
+
export async function invokePreparedControl(code: string, input: ControlJSON, intentKey?: string) {
|
|
38
|
+
const handle = createClient().controls.use({ code });
|
|
39
|
+
const definition = await handle.describe();
|
|
40
|
+
if (definition.idempotency === 'required' && !intentKey) {
|
|
41
|
+
throw new Error('该 Control 要求业务幂等键');
|
|
42
|
+
}
|
|
43
|
+
// 调用方已经按 definition.inputSchema 校验输入,并确认本次业务意图。
|
|
44
|
+
try {
|
|
45
|
+
return await handle.invoke(input, intentKey ? { idempotencyKey: intentKey } : {});
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (error instanceof ControlResultUnknownError) {
|
|
48
|
+
return { outcomeUnknown: true as const, runID: error.runID, errorCode: error.errorCode };
|
|
49
|
+
}
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
幂等键非空、不能有首尾空白,最多 128 UTF-8 字节。输入只能是普通 JSON,不传 Date、函数、循环对象、bigint、NaN/Infinity。业务 input 内名为 timeoutMs/streamId 的字段仍只是业务输入;不能借它覆盖服务端绑定、版本或执行超时。
|
|
56
|
+
|
|
57
|
+
## 返回与失败
|
|
58
|
+
|
|
59
|
+
成功值为直接对象:code(Control 标识)、contractVersion、result、runID、status、replayed。这里的 code 不是 HTTP 业务状态码;replayed 只有服务端明确报告重放时才为 true。
|
|
60
|
+
|
|
61
|
+
`ControlError` 含 status/errorCode/runID 等信息,SDK 不自动重试。`ControlResultUnknownError` 表示执行可能已经发生,包括调用响应无法证明最终结果的情况;按业务或平台运行记录对账。本 SDK 未提供通用 `controls.getRun`,不要虚构方法。若业务明确允许重放,保留原 key 和原输入,不能换新 key 重做。
|
|
62
|
+
|
|
63
|
+
`client.capabilities.discover()` 的 control.invoke.v1 就绪只是入口条件,不等于有权调用每一个 Control。执行源发布和内部绑定管理请使用 [Stream Skill](../tier0-sdk-v2-stream/SKILL.md)。
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tier0-sdk-v2-data
|
|
3
|
+
description: 使用 @tier0/sdk-v2/data 在 App 服务端声明并读写本 App 的 UNS 集合:Zod 建模与种子、Mongo 风格读写、原子批量与事务、员工数据主体、Schema 演进和错误码处理。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# App 数据层
|
|
7
|
+
|
|
8
|
+
`@tier0/sdk-v2/data` 是 App 自有业务数据的服务端入口:用 `defineCollection` 声明集合,`createDataClient` 在首次访问时一次性 ensure 全部声明,然后用 Mongo 驱动风格的集合句柄读写 UNS 记录。没有 SQL、ORM、连接串或迁移步骤;Preview 与部署后的 App 共用同一批集合和记录。
|
|
9
|
+
|
|
10
|
+
数据读写只在服务端代码(业务 service)里用 `@tier0/sdk-v2/data`:该入口依赖 Node 内置模块,浏览器打包会直接失败。组件和 route 的客户端代码只能从 `@tier0/sdk-v2/data/define` 取集合声明和类型(`defineCollection`、`Doc`、`NewDoc` 等),它不加载数据运行时,可以进浏览器包。需要 `zod` 4。底层调用 SDK 生成的 UNS 操作(`appCollectionsEnsure`、`recordFind`、`recordTransactionExecute` 等),凭证读取平台注入的 TIER0_API_HOST / TIER0_API_KEY。
|
|
11
|
+
|
|
12
|
+
## 按任务加载参考
|
|
13
|
+
|
|
14
|
+
| 用户任务 | 参考 |
|
|
15
|
+
| --- | --- |
|
|
16
|
+
| 声明集合、选字段类型、索引、种子数据、文档建模 | [建模](references/modeling.md) |
|
|
17
|
+
| 查询、分页、聚合、写入、批量与原子性 | [读写](references/read-write.md) |
|
|
18
|
+
| 状态流转、并发编辑、跨文档不变量、计数器、员工身份与后台任务 | [一致性与身份](references/consistency.md) |
|
|
19
|
+
| 加字段、改类型、改描述、收窄 Schema | [Schema 演进](references/schema-evolution.md) |
|
|
20
|
+
| DataError 字段、UNS 错误码与处理 | [错误码](references/errors.md) |
|
|
21
|
+
|
|
22
|
+
## 最小用法
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { z } from 'zod';
|
|
26
|
+
import { createDataClient } from '@tier0/sdk-v2/data';
|
|
27
|
+
import { defineCollection, type Doc } from '@tier0/sdk-v2/data/define';
|
|
28
|
+
|
|
29
|
+
export const workOrders = defineCollection({
|
|
30
|
+
alias: 'work_orders',
|
|
31
|
+
description: 'Production work orders',
|
|
32
|
+
schema: z.object({
|
|
33
|
+
code: z.string(),
|
|
34
|
+
status: z.enum(['DRAFT', 'RELEASED', 'DONE', 'CANCELLED']),
|
|
35
|
+
targetQty: z.number().int().min(1),
|
|
36
|
+
dueAt: z.string(),
|
|
37
|
+
}),
|
|
38
|
+
indexes: [{ path: 'code', unique: true }, { path: 'status' }],
|
|
39
|
+
});
|
|
40
|
+
export type WorkOrder = Doc<typeof workOrders>;
|
|
41
|
+
|
|
42
|
+
// 每个服务端进程一个 client:ensure 结果按 client 缓存。
|
|
43
|
+
export const data = createDataClient({ collections: [workOrders] });
|
|
44
|
+
|
|
45
|
+
export async function releasedOrders(): Promise<WorkOrder[]> {
|
|
46
|
+
return data.collection(workOrders).find({ status: 'RELEASED' }, { sort: { '_meta.createdAt': -1 }, limit: 50 }).toArray();
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## 核心约束
|
|
51
|
+
|
|
52
|
+
1. 声明文件只从 `@tier0/sdk-v2/data/define` 导入,客户端代码可以安全引用它;全部集合在一个 `collections` 列表里声明(每个 App 最多 64 个),只建一个进程级 client;不要手动 ensure、不要在 import 时访问数据、不要自建“已初始化”标记。
|
|
53
|
+
2. 集合 Schema 是存储 Schema:只能用可存储的 Zod 子集,输入规则(格式、长度、默认值、transform)放在 API 入参 Schema。
|
|
54
|
+
3. 写入前按声明严格校验,未知字段、类型错误、未声明的更新路径直接抛 `ZodError`;读取不做严格解析,旧版本写入的字段、新枚举值或缺字段都可能出现。
|
|
55
|
+
4. `insertMany` / `bulkWrite` / `transact` 每次最多 200 项且原子;`updateMany` / `deleteMany` 逐条提交、非原子。
|
|
56
|
+
5. 按 `DataError.code` 与 `reason`(`data.reason`)分支,不匹配 message 文本;`status` 是 App 应回给自己调用方的 HTTP 状态。
|
|
57
|
+
6. `schema:replace`、`archive`、`restore` 是 worker 通过 tier0 MCP 的显式操作,不在运行时代码里调用。
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# 一致性与调用身份
|
|
2
|
+
|
|
3
|
+
## 一致性写法
|
|
4
|
+
|
|
5
|
+
- 单个文档的写入是原子的:必须一起变化的数据放在同一个文档里。
|
|
6
|
+
- 状态流转把期望状态写进过滤条件:`findOneAndUpdate({ _id, status: 'DRAFT' }, { $set: { status: 'RELEASED' } })`;返回 `null` 表示记录不存在或已被别人改走,回 409。
|
|
7
|
+
- 并发编辑:编辑表单带回 `_meta.revision`,作为 `expectedRevision` 发送;`VERSION_CONFLICT`(409)表示重新读取后再试。
|
|
8
|
+
- 跨文档不变量(库存 + 预留、订单 + 审计记录):一次 `data.transact([...])`(≤200 步),用 `record.check` / `record.update` / `record.create` 步骤、`dependsOn` 与 `bindings`;`record.query` 步骤可断言 `expectCount: { min, max }`。整体全部提交或全不提交;它不是会话,不能在里面“先读再决定”。步骤的 `data` 不能含 `_id`(在步骤上设 `id`);由 `bindings` 填充的字段在 UNS 提交时校验,其余字面量字段在发送前校验。
|
|
9
|
+
- 计数器与流水号:声明一个计数器集合,`findOneAndUpdate({ _id: 'work_orders' }, { $inc: { value: 1 } }, { upsert: true })`。
|
|
10
|
+
- upsert 的过滤条件必须含 `_id` 或某个已就绪唯一索引的全部字段,否则 `UPSERT_REQUIRES_UNIQUE_INDEX`;新声明的唯一索引是异步构建的。
|
|
11
|
+
- `DUPLICATE_KEY`(409)表示唯一索引拒绝了该值,按字段错误展示。
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { z } from 'zod';
|
|
15
|
+
import { createDataClient, defineCollection } from '@tier0/sdk-v2/data';
|
|
16
|
+
|
|
17
|
+
const stock = defineCollection({ alias: 'stock', schema: z.object({ sku: z.string(), available: z.number().int().min(0) }) });
|
|
18
|
+
const reservations = defineCollection({ alias: 'reservations', schema: z.object({ sku: z.string(), qty: z.number().int().min(1), orderId: z.string() }) });
|
|
19
|
+
const counters = defineCollection({ alias: 'counters', schema: z.object({ value: z.number().int() }) });
|
|
20
|
+
const data = createDataClient({ collections: [stock, reservations, counters] });
|
|
21
|
+
|
|
22
|
+
/** 扣减库存并写预留:库存不足时整体不提交。 */
|
|
23
|
+
export async function reserve(sku: string, qty: number, orderId: string) {
|
|
24
|
+
return data.transact(
|
|
25
|
+
[
|
|
26
|
+
{ opId: 'check', action: 'record.check', collection: stock, filter: { _id: sku, available: { $gte: qty } } },
|
|
27
|
+
{ opId: 'take', action: 'record.update', collection: stock, id: sku, update: { $inc: { available: -qty } }, dependsOn: ['check'] },
|
|
28
|
+
{ opId: 'hold', action: 'record.create', collection: reservations, data: { sku, qty, orderId }, dependsOn: ['take'] },
|
|
29
|
+
],
|
|
30
|
+
{ idempotencyKey: `reserve-${orderId}` },
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function nextOrderNumber() {
|
|
35
|
+
const counter = await data.collection(counters).findOneAndUpdate({ _id: 'sales_orders' }, { $inc: { value: 1 } }, { upsert: true });
|
|
36
|
+
return counter?.value;
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`transact` 的结果状态不是 `committed`(`dryRun` 时为 `validated`)会抛 `TRANSACTION_FAILED`,`details.results` 带每一步的错误。
|
|
41
|
+
|
|
42
|
+
## 调用身份
|
|
43
|
+
|
|
44
|
+
每次调用都使用平台注入的 App Key。处理业务请求时,数据层还会转发网关签发的员工断言(`X-Tier0-App-Data-Subject`),让 UNS 的数据规则与记录租约作用于该员工。集合 ensure 与种子写入以 App 身份执行。
|
|
45
|
+
|
|
46
|
+
App 的请求入口用 `runWithRequestContext(request.headers, next)` 打开请求上下文;脱离请求的工作(定时器、队列、发出即不管的后续任务)放进 `runAsApp(() => ...)`,这些调用不带员工身份;员工身份下取得的租约不能由这类 App 级工作释放。断言格式由 SDK 校验(三段 base64url),格式不合法时调用在发出请求前失败。
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { currentDataSubject, runAsApp, runWithRequestContext } from '@tier0/sdk-v2/data';
|
|
50
|
+
|
|
51
|
+
export function handleRequest<T>(request: Request, next: () => Promise<T>): Promise<T> {
|
|
52
|
+
return runWithRequestContext(request.headers, next);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function scheduleFollowUp(task: () => Promise<void>) {
|
|
56
|
+
setTimeout(() => void runAsApp(task), 1000);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function servingEmployee(): boolean {
|
|
60
|
+
return currentDataSubject() !== undefined;
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## 首次访问与自愈
|
|
65
|
+
|
|
66
|
+
进程内第一次数据访问会一次性 ensure 全部声明:缺失的集合会被创建(随后写入种子),更宽的声明会放宽存储,存储永远不会被收窄。ensure 失败不缓存,下一次请求会重试(刚导入的 App 可能短暂返回 “hosted App is not ready”,数据层按可重试的 503 回答)。
|
|
67
|
+
|
|
68
|
+
运行中的进程在所用集合消失时会自愈(Preview 重置会删除集合,worker 或管理员可能归档集合):UNS 以 `data.reason` 为 `COLLECTION_NOT_FOUND` 或 `COLLECTION_NOT_GRANTED` 拒绝调用时,数据层重新 ensure(重建并重新写入种子),并在集合 ID 确实变化时把该调用重试一次。每个 client 每 30 秒最多重建一次;`APP_GRANT_REVOKED`(管理员决定)从不重试。延续旧集合游标的分页、`updateMany` / `deleteMany` 的续写直接失败,不会重试。
|
|
69
|
+
|
|
70
|
+
Preview(`environment: 'preview'`,`NODE_ENV` 不是 production 时的默认值)下,Schema 冲突或 ensure 警告会让请求失败并带上详情;部署环境只记录日志、继续服务。`ignored` 提示从不让请求失败:存储比声明更宽的部分记为 info,`ANNOTATION_NOT_APPLIED`(ensure 不会改写的描述)记为指向 `schema:replace` 的 warning。
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# 错误处理
|
|
2
|
+
|
|
3
|
+
数据调用的每一种失败都是 `DataError`;写入校验失败是 `ZodError`,不能存储的声明是 `CollectionSchemaError`。
|
|
4
|
+
|
|
5
|
+
| 字段 | 含义 |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| `status` | App 应回给自己调用方的 HTTP 状态:UNS 401 → 502(平台凭证问题,不是用户登出)、可重试的 403 → 503、400/403/404/409/413/422/429 原样、5xx → 502/503 |
|
|
8
|
+
| `code` | UNS 错误码(`DUPLICATE_KEY`…)或数据层错误码 |
|
|
9
|
+
| `reason` | UNS 错误体的稳定 `data.reason`,例如 `COLLECTION_NOT_FOUND` |
|
|
10
|
+
| `upstreamStatus` | UNS 实际返回的 HTTP 状态;未收到响应时为 0 |
|
|
11
|
+
| `requestId` | UNS 请求 ID,排障时一并记录 |
|
|
12
|
+
| `retryable` | 可以用同一请求稍后重试 |
|
|
13
|
+
| `outcomeUnknown` | 写入可能已执行:先核对结果,再决定是否用原幂等键重放 |
|
|
14
|
+
| `details` | UNS 错误体的 `data`(如 `conflicts`、`resource`、`missingPermission`),或数据层附带的详情 |
|
|
15
|
+
| `cause` | 原始 `UnsV2Error` |
|
|
16
|
+
|
|
17
|
+
按 `code` / `reason` 分支,不匹配 message 文本。
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { DataError } from '@tier0/sdk-v2/data';
|
|
21
|
+
import { ZodError } from 'zod';
|
|
22
|
+
|
|
23
|
+
/** 把数据层错误转成 App 自己的 HTTP 响应。 */
|
|
24
|
+
export function toResponse(error: unknown): Response {
|
|
25
|
+
if (error instanceof DataError) {
|
|
26
|
+
if (error.code === 'DUPLICATE_KEY') return Response.json({ error: '编号已存在', code: error.code }, { status: 409 });
|
|
27
|
+
return Response.json({ error: error.message, code: error.code, requestId: error.requestId }, { status: error.status });
|
|
28
|
+
}
|
|
29
|
+
if (error instanceof ZodError) return Response.json({ error: error.issues[0]?.message ?? 'Validation failed' }, { status: 400 });
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 数据层错误码
|
|
35
|
+
|
|
36
|
+
| code | 含义 |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| `DATA_NOT_CONFIGURED` (503) | 缺少 TIER0_API_HOST / TIER0_API_KEY(或 apiHost / apiKey) |
|
|
39
|
+
| `DATA_TIMEOUT` (503) | UNS 未在超时内应答(默认 15 秒,`transportTimeoutMs` 可调);写入时 `outcomeUnknown` 为 true |
|
|
40
|
+
| `DATA_UNREACHABLE` (503) | 连不上数据服务;网关地址只在原始错误里,不进 message |
|
|
41
|
+
| `HTTP_502` / `HTTP_503` / `HTTP_504` (503) | 网关在滚动发布、Pod 重启时未经 UNS 直接返回;可重试,写入时 `outcomeUnknown` 为 true |
|
|
42
|
+
| `DATA_BAD_RESPONSE` (502) | UNS 应答不符合契约 |
|
|
43
|
+
| `BATCH_LIMIT_EXCEEDED` (400) | 原子批量超过 200 项,未发送 |
|
|
44
|
+
| `WRITE_ERRORS` (409) | 批量写入中途失败;`details.committed` 是已提交的数量 |
|
|
45
|
+
| `TRANSACTION_FAILED` (409) | 事务未提交;`details.results` 列出各步骤 |
|
|
46
|
+
| `SCHEMA_CONFLICT` (409) | Preview 下声明与存储冲突(部署环境记日志,冲突集合按已有 ID 继续服务) |
|
|
47
|
+
| `SCHEMA_WARNINGS` (500) | Preview 下 ensure 带警告(`REQUIRED_NOT_ENFORCED`、`INDEX_BUILD_FAILED`) |
|
|
48
|
+
| `COLLECTION_UNAVAILABLE` (503) | ensure 没有返回该集合(见服务端日志) |
|
|
49
|
+
|
|
50
|
+
## UNS 错误码
|
|
51
|
+
|
|
52
|
+
| code | 含义与处理 |
|
|
53
|
+
| --- | --- |
|
|
54
|
+
| `SCHEMA_CONFLICT` (409) | 声明无法与存储合并,没有任何改动。`data.conflicts` 列出 `{ alias, path, reason, current?, declared? }`:`TYPE_CONFLICT` → 换新字段名;`LIMIT_EXCEEDED` → 拆分结构;`JOIN_UNSUPPORTED` → 存储中有声明无法放宽的节点,用 `schema:replace` 修复;`DATA_RULE_FIELD` → 员工数据规则(owner/state 字段)在用该路径,保持必填且类型不变,或请管理员修改规则;`OPEN_OBJECT` → 用 `z.object`;`ALIAS_IN_USE` / `NAME_IN_USE` → 换 alias;`INVALID_INDEX` → 修正索引 |
|
|
55
|
+
| `ANNOTATION_NOT_APPLIED`(`ignored` 提示) | 不是错误:ensure 保留已存储的描述,用 `schema:replace` 应用修改 |
|
|
56
|
+
| `REQUIRED_NOT_ENFORCED`(警告) | 声明要求的字段在存储中仍是可选;改回可选,或回填后用 `schema:replace` 收窄 |
|
|
57
|
+
| `INDEX_BUILD_FAILED`(警告) | 已有记录违反新唯一索引;修正重复数据,下次 ensure 会重试建索引 |
|
|
58
|
+
| `UPSERT_REQUIRES_UNIQUE_INDEX` | upsert 过滤条件缺少 `_id` 或已就绪唯一索引的全部字段 |
|
|
59
|
+
| `DUPLICATE_KEY` (409) | 唯一索引拒绝该值;restore 时表示与活动记录冲突、未恢复任何记录 |
|
|
60
|
+
| `VERSION_CONFLICT` (409) | 记录在读取后已变化;重新读取后用新 revision 重试 |
|
|
61
|
+
| `QUERY_TIMEOUT` | 查询超过 15 秒语句超时;加有索引的条件或缩小范围 |
|
|
62
|
+
| `TEMPORARILY_UNAVAILABLE` | 稍后用同一请求重试 |
|
|
63
|
+
| `APP_GRANT_REVOKED` (403) | 管理员撤销了本 App 对集合的授权;ensure 不会自动恢复,请管理员处理 |
|
|
64
|
+
| `APP_QUOTA_EXCEEDED` (409) | `data.resource` 指出触达的每 App 上限(64):`collections` → 归档不用的集合;`indexes` → 从声明删索引并 `schema:replace`;`archivedCollections` → 恢复一个或请管理员清理 |
|
|
65
|
+
| `INDEX_QUOTA_EXCEEDED` (409) | Workspace 实际存在的索引已达 200;删除不用的索引 |
|
|
66
|
+
| `COLLECTION_TOO_LARGE` (409) | archive / restore 最多处理 2000 条记录(`data.maxRecords`);不要归档大集合 |
|
|
67
|
+
| `FORBIDDEN` (403) | Key 不能做该操作(其他 App 或共享集合);导入后马上出现的 `hosted App is not ready` 可重试,数据层按 503 回答 |
|
|
68
|
+
|
|
69
|
+
记录调用上的 `data.reason`:`COLLECTION_NOT_GRANTED`(403,授权不覆盖该集合或操作)与 `COLLECTION_NOT_FOUND`(403/404,已归档或删除)会触发数据层重新 ensure;`APP_PERMISSION_MISSING`(403,`data.missingPermission`)表示 Key 缺少权限,例如删除记录需要 `uns.delete`——业务记录通常以状态结束(`CANCELLED`、`ARCHIVED`)而不是删除。
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# 声明集合与建模
|
|
2
|
+
|
|
3
|
+
`defineCollection({ alias, schema, indexes?, description?, seed? })` 返回冻结的声明;全部声明放进一个数组传给 `createDataClient({ collections })`。声明文件从 `@tier0/sdk-v2/data/define` 导入:它不含数据运行时,组件和 route 也能引用这些声明与 `Doc` 类型。alias 在 App 内唯一、不区分大小写,匹配 `^[A-Za-z][A-Za-z0-9_-]{0,62}$`。
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { defineCollection, type Doc } from '@tier0/sdk-v2/data/define';
|
|
8
|
+
|
|
9
|
+
export const customers = defineCollection({
|
|
10
|
+
alias: 'customers',
|
|
11
|
+
schema: z.object({ name: z.string(), tier: z.enum(['A', 'B', 'C']) }),
|
|
12
|
+
indexes: [{ path: 'name' }],
|
|
13
|
+
seed: [
|
|
14
|
+
{ _id: 'cust-1', name: 'Acme', tier: 'A' },
|
|
15
|
+
{ _id: 'cust-2', name: 'Globex', tier: 'B' },
|
|
16
|
+
],
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export const salesOrders = defineCollection({
|
|
20
|
+
alias: 'sales_orders',
|
|
21
|
+
description: 'Customer sales orders',
|
|
22
|
+
schema: z.object({
|
|
23
|
+
code: z.string(),
|
|
24
|
+
status: z.enum(['DRAFT', 'CONFIRMED', 'SHIPPED', 'CANCELLED']),
|
|
25
|
+
customer: z.object({ id: z.string(), name: z.string() }),
|
|
26
|
+
lines: z.array(z.object({ sku: z.string(), qty: z.number().int().min(1) })),
|
|
27
|
+
notes: z.string().optional(),
|
|
28
|
+
orderedOn: z.string().describe('YYYY-MM-DD'),
|
|
29
|
+
confirmedAt: z.string().optional().describe('ISO-8601 UTC from toISOString()'),
|
|
30
|
+
}),
|
|
31
|
+
indexes: [{ path: 'code', unique: true }, { paths: ['status', 'orderedOn'] }, { path: 'lines', kind: 'gin' }],
|
|
32
|
+
seed: [
|
|
33
|
+
{ _id: 'so-1', code: 'SO-1', status: 'DRAFT', customer: { id: 'cust-1', name: 'Acme' }, lines: [], orderedOn: '2026-01-05' },
|
|
34
|
+
],
|
|
35
|
+
});
|
|
36
|
+
export type SalesOrder = Doc<typeof salesOrders>;
|
|
37
|
+
|
|
38
|
+
export const collections = [customers, salesOrders] as const;
|
|
39
|
+
|
|
40
|
+
// API 入参 Schema 从声明派生,再追加输入规则。
|
|
41
|
+
export const createSalesOrderInput = salesOrders.schema
|
|
42
|
+
.omit({ status: true, confirmedAt: true })
|
|
43
|
+
.extend({ code: z.string().trim().min(1).max(32) });
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 可存储的 Zod 子集
|
|
47
|
+
|
|
48
|
+
集合 Schema 会转成 UNS 记录 Schema 子集;不能存储的写法在 `compileCollection` / 首次 ensure 时抛 `CollectionSchemaError`,并给出替代写法。
|
|
49
|
+
|
|
50
|
+
| 使用 | 不要用 |
|
|
51
|
+
| --- | --- |
|
|
52
|
+
| `z.string()`、`z.number()`、`z.number().int()`、`z.boolean()` | `z.date()`、`z.bigint()`、`z.any()`、`z.unknown()` |
|
|
53
|
+
| 数字上的 `.min()` / `.max()`(闭区间) | `.positive()`、`.gt()`、`.lt()`、`.multipleOf()` |
|
|
54
|
+
| `z.enum([...])` | `z.literal()`、union、`.nullable()`、`.nullish()` |
|
|
55
|
+
| `.optional()`(字段可缺省) | 存 `null`:不写该字段,清空用 `$unset` |
|
|
56
|
+
| `z.object({...})`(始终封闭)、`z.array(item)` | `z.record()`、`z.looseObject()`、`.catchall()`、`z.tuple()` |
|
|
57
|
+
| 普通 `z.string()`,格式与长度在 API 入参校验 | `z.email()`、`z.uuid()`、`.regex()`、字符串 `.min(1)` |
|
|
58
|
+
| 在 service 里补默认值 | `.default()`、`.catch()`、`.transform()` |
|
|
59
|
+
|
|
60
|
+
日期存 `"YYYY-MM-DD"` 字符串,时刻存 `toISOString()` 的 ISO-8601 UTC 字符串,两者都能按字符串排序和范围过滤。`_id` 与 `_meta`(`revision`、`createdAt`、`updatedAt`,Unix 毫秒)是系统字段,不要声明。
|
|
61
|
+
|
|
62
|
+
限制:对象最多嵌套 8 层、每个对象 128 个属性、每个字段 100 个枚举值;`.describe()` 最多 2048 个 UTF-8 字节(约 680 个汉字);集合 `description` 最多 512 个字符。字段与集合描述只在首次创建时写入,之后修改要走 `schema:replace`(见 [Schema 演进](schema-evolution.md))。
|
|
63
|
+
|
|
64
|
+
## 索引
|
|
65
|
+
|
|
66
|
+
`{ path }` 或复合 `{ paths }`(最多 3 个不同路径);业务键加 `unique: true`;一个数组/对象字段用 `kind: 'gin'`;每个集合最多 16 个。路径只能沿对象属性走:数组元素里的字段(`lines.sku`)不能直接建索引,改为给 `lines` 建 GIN 或把值放到顶层字段。列表页过滤和排序用到的字段要建索引。
|
|
67
|
+
|
|
68
|
+
## 种子数据
|
|
69
|
+
|
|
70
|
+
种子是基线记录:固定 `_id`,各生命周期状态自洽,相互引用用这些 ID;每个集合最多 1000 条。只在 ensure 报告集合为 `created` 时写入一次(每 200 条一个原子分块,分块带固定幂等键,重试安全);修改 `seed` 不会改动已存在的集合。不要在服务启动或列表为空时从代码插入基线数据。构建期可以对每条种子调用 `validateDocument(definition, document)` 校验。
|
|
71
|
+
|
|
72
|
+
## 按文档建模,而不是按表
|
|
73
|
+
|
|
74
|
+
- 属于同一记录、随它一起变化的内容(订单行、检查步骤、审批历史)嵌在该文档的数组里,一次写入保持一致;数组保持有界。
|
|
75
|
+
- 共享实体按 ID 引用,并快照需要展示的字段(`customer: { id, name }`),不要在读取时 join。`aggregate` 有 `$lookup`,但有行数限制,不要用它搭列表页。
|
|
76
|
+
- 每种业务对象一个集合,而不是每个页面一个集合。
|
|
77
|
+
- “最新优先”用 `_meta.createdAt` / `_meta.updatedAt`,由 UNS 维护。
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# 读写
|
|
2
|
+
|
|
3
|
+
`data.collection(declaration)` 返回按声明推断类型的句柄(也可用 alias 字符串,类型由调用方指定)。读到的记录是扁平文档 `{ _id, ...字段, _meta }`;过滤、排序、投影都用同样的路径(`status`、`customer.id`、`_id`、`_meta.updatedAt`),不加 `data.` 前缀。
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { createDataClient, defineCollection } from '@tier0/sdk-v2/data';
|
|
8
|
+
|
|
9
|
+
const workOrders = defineCollection({
|
|
10
|
+
alias: 'work_orders',
|
|
11
|
+
schema: z.object({
|
|
12
|
+
code: z.string(),
|
|
13
|
+
status: z.enum(['DRAFT', 'RELEASED', 'DONE']),
|
|
14
|
+
line: z.object({ id: z.string(), name: z.string() }),
|
|
15
|
+
targetQty: z.number().int().min(1),
|
|
16
|
+
notes: z.string().optional(),
|
|
17
|
+
}),
|
|
18
|
+
indexes: [{ path: 'code', unique: true }, { path: 'status' }],
|
|
19
|
+
});
|
|
20
|
+
const data = createDataClient({ collections: [workOrders] });
|
|
21
|
+
const orders = data.collection(workOrders);
|
|
22
|
+
|
|
23
|
+
export async function dashboard(page: number) {
|
|
24
|
+
const recent = await orders.find({ status: { $in: ['RELEASED', 'DONE'] } }, { sort: { '_meta.createdAt': -1 }, limit: 50 }).toArray();
|
|
25
|
+
const listPage = await orders.findPage({ status: 'RELEASED' }, { sort: { code: 1 }, page, pageSize: 20 });
|
|
26
|
+
const released = await orders.countDocuments({ status: 'RELEASED' });
|
|
27
|
+
const totals = await orders
|
|
28
|
+
.aggregate<{ _id: string; total: number }>([{ $match: { status: 'DONE' } }, { $group: { _id: '$line.id', total: { $sum: '$targetQty' } } }])
|
|
29
|
+
.toArray();
|
|
30
|
+
return { recent, listPage, released, totals };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function addNote(id: string, notes: string, revision: number) {
|
|
34
|
+
return orders.updateOne({ _id: id }, { $set: { notes } }, { expectedRevision: revision });
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## 方法
|
|
39
|
+
|
|
40
|
+
| 读取 | 说明 |
|
|
41
|
+
| --- | --- |
|
|
42
|
+
| `find(filter, { sort, limit, projection })` | 游标,每页最多 200 条;列表读取始终给 `limit` |
|
|
43
|
+
| `findOne(filter)` | 无匹配返回 `null` |
|
|
44
|
+
| `findPage(filter, { sort, page, pageSize })` | 带总数的页码分页 `{ items, total, page, pageSize }`,pageSize 最多 200 |
|
|
45
|
+
| `countDocuments(filter)`、`distinct(field, filter)` | `distinct` 会取完所有页 |
|
|
46
|
+
| `aggregate(pipeline)` | `$match` `$sort` `$group` `$project` `$set` `$unwind` `$skip` `$limit` `$count` `$lookup`;`$lookup.from` 写声明或 alias,数据层换成集合 ID。`$exists` / `$distinct` / `$frequencies` 阶段不执行,改用 `countDocuments`、`distinct` 或 `$group` |
|
|
47
|
+
|
|
48
|
+
| 写入 | 说明 |
|
|
49
|
+
| --- | --- |
|
|
50
|
+
| `insertOne(doc)`、`insertMany(docs)` | 可带 `_id`;`insertMany` 最多 200 条、原子 |
|
|
51
|
+
| `updateOne` / `updateMany` / `replaceOne` | 更新用操作符(`$set` `$inc` `$push` `$unset`…);`replaceOne` 会丢掉其他代码版本写入的字段 |
|
|
52
|
+
| `deleteOne` / `deleteMany` | 软删除,App Key 需要 `uns.delete` |
|
|
53
|
+
| `findOneAndUpdate` / `findOneAndReplace` / `findOneAndDelete` | 原子读改写,默认返回更新后的文档 |
|
|
54
|
+
| `bulkWrite(ops)` | `insertOne` / `updateOne` / `replaceOne` / `deleteOne` 混合,最多 200 项、原子 |
|
|
55
|
+
| `data.transact(steps)` | 跨集合原子写,见 [一致性](consistency.md) |
|
|
56
|
+
|
|
57
|
+
## 批量与原子性
|
|
58
|
+
|
|
59
|
+
| 调用 | 上限 | 原子 |
|
|
60
|
+
| --- | --- | --- |
|
|
61
|
+
| `insertMany`、`bulkWrite`、`data.transact` | 每次 ≤200 项,超过在发送前抛 `BATCH_LIMIT_EXCEEDED` | 是,全部或全不 |
|
|
62
|
+
| `updateMany`、`deleteMany` | 无上限,翻页处理所有匹配 | 否,逐条提交;失败抛 `WRITE_ERRORS` 并报告已提交的数量 |
|
|
63
|
+
|
|
64
|
+
超过 200 条按 200 分块写入(循环分块即可),每块用固定 `_id` 或独立 `idempotencyKey`,重试不会重复。多条文档必须一起成功或失败时,用一次 `data.transact`(≤200 步),不要用 `updateMany`。不要在循环里逐条调用数据层:读用一次 `$in`,写用 `insertMany` / `updateMany` / `bulkWrite` 或 `data.transact`。
|
|
65
|
+
|
|
66
|
+
## 过滤
|
|
67
|
+
|
|
68
|
+
支持 `$eq $ne $gt $gte $lt $lte $in $nin $exists $all $size $elemMatch $not $and $or $nor` 和 `$contains`。`$contains` 是子串匹配(1–256 字节,无通配符和正则),只对 ASCII 字母忽略大小写;缺失、null、非字符串值不匹配;只能用于声明为字符串的字段,用在数组或数字字段上返回 `INVALID_REQUEST`。它不能使用索引,大集合上可能触发 15 秒语句超时(`QUERY_TIMEOUT`),要和有索引的条件一起用。`$in` 最多 200 个值,排序最多 4 个字段(`toSortPairs` 也导出给自定义查询转换使用)。
|
|
69
|
+
|
|
70
|
+
## 校验
|
|
71
|
+
|
|
72
|
+
写入前按声明严格校验:未知字段、类型错误、未声明的更新路径抛 `ZodError`(HTTP 400 语义)。读取不严格解析:记录可能含其他代码版本写入的字段、新的枚举值,或缺少后来新增的字段。枚举渲染要有兜底标签,后加的字段按可能缺失处理。
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Schema 演进
|
|
2
|
+
|
|
3
|
+
Preview 与部署后的 App 共用集合,较旧的部署版本随时可能用它的旧声明冷启动。因此 ensure 只会**放宽**:存储是所有运行过的声明的并集,每个版本的写入都保持有效。按变更类型选择做法:
|
|
4
|
+
|
|
5
|
+
| 变更 | 做法 |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| 新增可选字段(任意层级,含数组元素内)、新增枚举值、字段改为可选、`int` → `number`、放宽或去掉 `min`/`max`、新增索引 | 修改声明;下次访问时 ensure 生效 |
|
|
8
|
+
| 修改 `.describe()` 文本或集合 `description` | 修改声明,然后通过 tier0 MCP 执行 `schema:replace`,请求体用 `compileCollection(declaration)` 的结果(先 `dryRun`:期望 `widened`;`narrowed` 说明存储里还有该声明之外的内容,例如其他版本的字段——合并进请求体或停止)。ensure 保留已存储的描述并报告 `ANNOTATION_NOT_APPLIED`;新字段和新集合在创建时带上描述 |
|
|
9
|
+
| 新增每条记录都必须有的字段 | 先以 `.optional()` 加入,所有写入路径都写它,回填旧记录,等部署版本也写入后再用 `schema:replace` 改为必填 |
|
|
10
|
+
| 删除或重命名字段 | 代码不再使用旧字段(它可以留在存储里);新名字就是新的可选字段。通过 tier0 MCP 一次性回填(`records:updateMany` 配 `$rename` / `$set`) |
|
|
11
|
+
| 修改字段类型 | 换一个新字段名,回填,读取时以旧字段兜底。ensure 从不转换类型(`SCHEMA_CONFLICT`) |
|
|
12
|
+
| 删除枚举值、可选改必填、从存储删除字段、删除索引、收紧 `min`/`max` | 只能在部署版本不再写旧形态之后:通过 tier0 MCP `schema:replace`,先 `dryRun` |
|
|
13
|
+
|
|
14
|
+
不要为了“重置” Schema 而 `deleteMany` 或重建集合:部署版本的数据会一起丢失。修改 `seed` 不会重新写种子。
|
|
15
|
+
|
|
16
|
+
## 运行时代码与 worker 操作的边界
|
|
17
|
+
|
|
18
|
+
`schema:replace`、`archive`、`restore` 由 worker 通过 tier0 MCP 显式执行(`/uns/app/collections/schema:replace`、`archive`、`restore`),App 运行时代码不调用。数据层导出的 `compileCollection(declaration)` 给出与运行时 ensure 完全一致的条目 `{ alias, recordSchema, indexes, description? }`,可作为 ensure `dryRun` 或 `schema:replace` 请求体的基础。
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { z } from 'zod';
|
|
22
|
+
import { compileCollection, defineCollection } from '@tier0/sdk-v2/data';
|
|
23
|
+
|
|
24
|
+
const tickets = defineCollection({
|
|
25
|
+
alias: 'tickets',
|
|
26
|
+
schema: z.object({ title: z.string().describe('Short summary'), severity: z.enum(['LOW', 'HIGH']).optional() }),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/** schema:replace 请求体的基础;expectedRevision 取自 ensure 结果的集合 revision。 */
|
|
30
|
+
export function replaceBody(expectedRevision: number) {
|
|
31
|
+
const { alias, recordSchema, indexes } = compileCollection(tickets);
|
|
32
|
+
return { alias, recordSchema, indexes, expectedRevision, dryRun: true };
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Schema 中 `ignored`、`warnings`、`conflicts`、`violations` 的路径用 `.` 连接属性,用 `[]` 表示数组元素(`lines.[].qty`)。
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tier0-sdk-v2-files
|
|
3
|
+
description: 使用 @tier0/sdk-v2 实现 Tier0 应用附件、头像、导入导出、生成报告、上传下载和持久化文件引用,包括分片上传及中断处理。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 文件与附件
|
|
7
|
+
|
|
8
|
+
## 按任务读取操作文档
|
|
9
|
+
|
|
10
|
+
| 任务 | 参考 |
|
|
11
|
+
| --- | --- |
|
|
12
|
+
| 上传并保存 filePath | [上传](references/upload.md) |
|
|
13
|
+
| 大文件、进度、中断会话 | [分片上传](references/multipart.md) |
|
|
14
|
+
| 图片预览、临时访问地址 | [访问 URL](references/url.md) |
|
|
15
|
+
| 服务端转发、浏览器保存附件 | [下载](references/download.md) |
|
|
16
|
+
| 删除对象及核验回执 | [删除](references/delete.md) |
|
|
17
|
+
|
|
18
|
+
沿用 v1 的业务流程,调用已适配 v2;不提供旧版自动续传/并发助手。
|
|
19
|
+
|
|
20
|
+
业务文件使用平台对象存储,数据库保存返回的 filePath。App 本地目录可用于临时处理和构建,但不能替代持久化上传。使用服务端 client.files,API Key 不下发给浏览器或对象存储。
|
|
21
|
+
|
|
22
|
+
## 上传并保存引用
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { createClient } from '@tier0/sdk-v2';
|
|
26
|
+
|
|
27
|
+
export async function uploadReport(text: string) {
|
|
28
|
+
const client = createClient();
|
|
29
|
+
const file = new File([text], 'report.txt', { type: 'text/plain;charset=utf-8' });
|
|
30
|
+
const saved = await client.files.upload(file);
|
|
31
|
+
// 将 filePath 保存到业务记录;临时 URL 到期后重新获取。
|
|
32
|
+
return { filePath: saved.filePath };
|
|
33
|
+
}
|
|
34
|
+
export async function downloadFile(filePath: string) {
|
|
35
|
+
const response = await createClient().files.download({ filePath });
|
|
36
|
+
return response.arrayBuffer();
|
|
37
|
+
}
|
|
38
|
+
export async function getTemporaryLink(filePath: string) {
|
|
39
|
+
return createClient().files.url({ filePath, expiredSec: 300 });
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
upload 接收 Web File;Node 20.19+ 可直接构造。若文件来自磁盘,先读取内容构造 File,别把本地路径字符串当文件参数。大文件避免全部读入内存后重复复制;按业务运行环境决定流转方式。
|
|
44
|
+
|
|
45
|
+
## 上传协议与参数
|
|
46
|
+
|
|
47
|
+
- `upload(file, options)` 自动填写 fileName/contentType/size;options 支持平台定义的 business/useBy/visibility/sessionId/appInstanceId 与 signal,不猜测这些字段的合法业务枚举。
|
|
48
|
+
- 普通上传先向平台申请 postUrl/postFields,再向对象存储提交表单,file 必须排在签名字段之后。SDK 已处理,不能手工附带平台 Authorization。
|
|
49
|
+
- filePath 为持久化引用;私有文件的 fileUrl 为临时地址,公开文件可能返回公开 URL。发通知或生成页面时,按当前权限请求访问 URL。
|
|
50
|
+
- 下载通过平台申请签名地址后匿名读取,使用 `client.files.download`,不要手动跟随携带 API Key 的下载重定向。
|
|
51
|
+
|
|
52
|
+
## 分片上传
|
|
53
|
+
|
|
54
|
+
超过 100 MiB 自动分片,也可显式调用:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { createClient, MultipartUploadError } from '@tier0/sdk-v2';
|
|
58
|
+
|
|
59
|
+
export async function uploadLargeFile(file: File, signal?: AbortSignal) {
|
|
60
|
+
try {
|
|
61
|
+
return await createClient().files.uploadMultipart(file, {
|
|
62
|
+
partSize: 8 * 1024 * 1024, signal,
|
|
63
|
+
onProgress: ({ uploadedBytes, totalBytes }) => {
|
|
64
|
+
console.info({ uploadedBytes, totalBytes });
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (error instanceof MultipartUploadError) {
|
|
69
|
+
// 交给受控的作业存储保留 session;不要自动 complete/abort。
|
|
70
|
+
return { interrupted: true as const, session: error.session };
|
|
71
|
+
}
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
partSize 至少 5 MiB;SDK 使用服务端返回的实际分片大小,首版顺序上传,不自动重试/续传。中断 session 含 fileKey、uploadId、已成功 parts/ETag、completionAttempted;它是恢复依据,不是上传成功回执。
|
|
78
|
+
|
|
79
|
+
`files.multipart` 提供 init/partUrls/complete/abort 原子接口。继续已有分片时保留服务端 ETag;跨域直传还需要存储暴露 ETag 响应头。completionAttempted=true 时先核对最终文件是否存在,不能盲目重新完成或终止。当前 SDK 未提供自动列举远端分片的恢复助手。
|
|
80
|
+
|
|
81
|
+
## 删除与失败
|
|
82
|
+
|
|
83
|
+
`files.delete({ filePath })` 返回 `{ deleted }`,deleted=false 不能显示“已删除”。删除前按业务引用关系处理,读取/下载请求不附带删除。
|
|
84
|
+
|
|
85
|
+
平台阶段抛 ApiError/TransportError,存储阶段还可能抛 fetch/Abort 错误;不要吞掉上传失败后仍保存“成功”记录。上传/下载超时覆盖取得响应头阶段,返回 Response 的后续流由调用方消费和管理。
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# 删除文件
|
|
2
|
+
|
|
3
|
+
方法:`files.delete({filePath}, {signal}?)`;POST /openapi/v1/assets/files/delete。
|
|
4
|
+
|
|
5
|
+
| 字段 | 类型 | 必填 | 说明 |
|
|
6
|
+
| --- | --- | --- | --- |
|
|
7
|
+
| filePath | string | 是 | 业务记录中已确认目标的上传引用 |
|
|
8
|
+
| signal | AbortSignal | 否 | 第二参数;取消客户端等待 |
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { createClient, type Tier0Client } from '@tier0/sdk-v2';
|
|
12
|
+
|
|
13
|
+
export async function deleteAttachment(filePath: string, client: Tier0Client = createClient()) {
|
|
14
|
+
const result = await client.files.delete({ filePath });
|
|
15
|
+
if (!result.deleted) throw new Error('平台未确认文件已删除');
|
|
16
|
+
return { deleted: true };
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
删除前明确目标及业务引用,读取/下载不附带删除。返回已解包的 {deleted};不要复制 v1 把 204/空体也无条件视为成功的逻辑。v2 要按实际回执判断。
|
|
21
|
+
|
|
22
|
+
对象删除不提供 SDK 恢复功能。401/403 处理身份与授权;网络中断/结果未知时先对账,不把错误当“已经不存在”,不自动换身份重试。
|