@tier0/sdk-v2 0.7.2 → 0.9.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/NOTICE.md +3 -1
- package/README.md +48 -10
- package/dist/cjs/capabilities.d.ts +8 -17
- package/dist/cjs/capabilities.js +14 -27
- package/dist/cjs/capabilities.js.map +1 -1
- package/dist/cjs/client.d.ts +524 -3
- package/dist/cjs/client.js +29 -6
- package/dist/cjs/client.js.map +1 -1
- package/dist/cjs/cloud-transport.d.ts +4 -2
- package/dist/cjs/cloud-transport.js +27 -18
- package/dist/cjs/cloud-transport.js.map +1 -1
- package/dist/cjs/data/bootstrap.js +5 -5
- package/dist/cjs/data/bootstrap.js.map +1 -1
- package/dist/cjs/data/define.d.ts +6 -0
- package/dist/cjs/data/define.js +11 -1
- package/dist/cjs/data/define.js.map +1 -1
- package/dist/cjs/data/schema.d.ts +1 -0
- package/dist/cjs/data/schema.js +7 -0
- package/dist/cjs/data/schema.js.map +1 -1
- package/dist/cjs/files.d.ts +26 -6
- package/dist/cjs/files.js +151 -28
- package/dist/cjs/files.js.map +1 -1
- package/dist/cjs/index.d.ts +2 -1
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/openapi/api.generated.d.ts +1 -1
- package/dist/cjs/openapi/codecs.generated.d.ts +30 -161
- package/dist/cjs/openapi/codecs.generated.js +1 -1
- package/dist/cjs/openapi/codecs.generated.js.map +1 -1
- package/dist/cjs/openapi/types.generated.d.ts +33 -85
- package/dist/cjs/openapi/types.generated.js.map +1 -1
- package/dist/cjs/runtime.d.ts +13 -0
- package/dist/cjs/runtime.js +51 -0
- package/dist/cjs/runtime.js.map +1 -0
- package/dist/cjs/streams/capabilities.d.ts +8 -0
- package/dist/cjs/streams/capabilities.js +19 -0
- package/dist/cjs/streams/capabilities.js.map +1 -0
- package/dist/cjs/streams/contract.generated.d.ts +802 -0
- package/dist/cjs/streams/contract.generated.js +7 -0
- package/dist/cjs/streams/contract.generated.js.map +1 -0
- package/dist/cjs/streams/index.d.ts +3 -0
- package/dist/cjs/streams/index.js +20 -0
- package/dist/cjs/streams/index.js.map +1 -0
- package/dist/cjs/streams/messages.d.ts +42 -0
- package/dist/cjs/streams/messages.js +514 -0
- package/dist/cjs/streams/messages.js.map +1 -0
- package/dist/cjs/streams/requests.d.ts +93 -0
- package/dist/cjs/streams/requests.js +167 -0
- package/dist/cjs/streams/requests.js.map +1 -0
- package/dist/cjs/streams/transport.d.ts +16 -0
- package/dist/cjs/streams/transport.js +24 -0
- package/dist/cjs/streams/transport.js.map +1 -0
- package/dist/cjs/uns-v2/generated.d.ts +88 -22
- package/dist/cjs/uns-v2/generated.js +43 -8
- package/dist/cjs/uns-v2/generated.js.map +1 -1
- package/dist/esm/capabilities.d.ts +8 -17
- package/dist/esm/capabilities.js +14 -27
- package/dist/esm/capabilities.js.map +1 -1
- package/dist/esm/client.d.ts +524 -3
- package/dist/esm/client.js +27 -5
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/cloud-transport.d.ts +4 -2
- package/dist/esm/cloud-transport.js +27 -18
- package/dist/esm/cloud-transport.js.map +1 -1
- package/dist/esm/data/bootstrap.js +5 -5
- package/dist/esm/data/bootstrap.js.map +1 -1
- package/dist/esm/data/define.d.ts +6 -0
- package/dist/esm/data/define.js +9 -0
- package/dist/esm/data/define.js.map +1 -1
- package/dist/esm/data/schema.d.ts +1 -0
- package/dist/esm/data/schema.js +8 -1
- package/dist/esm/data/schema.js.map +1 -1
- package/dist/esm/files.d.ts +26 -6
- package/dist/esm/files.js +151 -28
- package/dist/esm/files.js.map +1 -1
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.js +2 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/openapi/api.generated.d.ts +1 -1
- package/dist/esm/openapi/codecs.generated.d.ts +30 -161
- package/dist/esm/openapi/codecs.generated.js +1 -1
- package/dist/esm/openapi/codecs.generated.js.map +1 -1
- package/dist/esm/openapi/types.generated.d.ts +33 -85
- package/dist/esm/openapi/types.generated.js.map +1 -1
- package/dist/esm/runtime.d.ts +13 -0
- package/dist/esm/runtime.js +46 -0
- package/dist/esm/runtime.js.map +1 -0
- package/dist/esm/streams/capabilities.d.ts +8 -0
- package/dist/esm/streams/capabilities.js +15 -0
- package/dist/esm/streams/capabilities.js.map +1 -0
- package/dist/esm/streams/contract.generated.d.ts +802 -0
- package/dist/esm/streams/contract.generated.js +6 -0
- package/dist/esm/streams/contract.generated.js.map +1 -0
- package/dist/esm/streams/index.d.ts +3 -0
- package/dist/esm/streams/index.js +4 -0
- package/dist/esm/streams/index.js.map +1 -0
- package/dist/esm/streams/messages.d.ts +42 -0
- package/dist/esm/streams/messages.js +475 -0
- package/dist/esm/streams/messages.js.map +1 -0
- package/dist/esm/streams/requests.d.ts +93 -0
- package/dist/esm/streams/requests.js +161 -0
- package/dist/esm/streams/requests.js.map +1 -0
- package/dist/esm/streams/transport.d.ts +16 -0
- package/dist/esm/streams/transport.js +20 -0
- package/dist/esm/streams/transport.js.map +1 -0
- package/dist/esm/uns-v2/generated.d.ts +88 -22
- package/dist/esm/uns-v2/generated.js +43 -8
- package/dist/esm/uns-v2/generated.js.map +1 -1
- package/package.json +46 -12
- package/skills/tier0-sdk-v2/SKILL.md +3 -2
- package/skills/tier0-sdk-v2/tier0-sdk-v2-data/SKILL.md +3 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/consistency.md +3 -3
- package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/errors.md +1 -1
- package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/modeling.md +6 -1
- package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/read-write.md +3 -1
- package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/schema-evolution.md +6 -4
- package/skills/tier0-sdk-v2/tier0-sdk-v2-files/references/multipart.md +23 -8
- package/skills/tier0-sdk-v2/tier0-sdk-v2-source/SKILL.md +3 -3
- package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/create.md +9 -3
- package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/state.md +1 -1
- package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/update.md +1 -1
- package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/SKILL.md +2 -1
- package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/references/create.md +19 -4
- package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/references/deploy.md +1 -1
- package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/references/read.md +2 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/references/update.md +1 -1
- package/skills/tier0-sdk-v2/tier0-sdk-v2-streams/SKILL.md +67 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-system/SKILL.md +5 -9
- package/skills/tier0-sdk-v2/tier0-sdk-v2-system/references/info.md +1 -1
- package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/create.md +1 -1
- package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/namespace.md +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tier0/sdk-v2",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Tier0 Cloud new-architecture SDK for TypeScript and JavaScript",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -150,22 +150,52 @@
|
|
|
150
150
|
"default": "./dist/cjs/vue.js"
|
|
151
151
|
}
|
|
152
152
|
},
|
|
153
|
-
"./package.json": "./package.json"
|
|
153
|
+
"./package.json": "./package.json",
|
|
154
|
+
"./runtime": {
|
|
155
|
+
"import": {
|
|
156
|
+
"types": "./dist/esm/runtime.d.ts",
|
|
157
|
+
"default": "./dist/esm/runtime.js"
|
|
158
|
+
},
|
|
159
|
+
"require": {
|
|
160
|
+
"types": "./dist/cjs/runtime.d.ts",
|
|
161
|
+
"default": "./dist/cjs/runtime.js"
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
"./streams": {
|
|
165
|
+
"import": {
|
|
166
|
+
"types": "./dist/esm/streams/index.d.ts",
|
|
167
|
+
"default": "./dist/esm/streams/index.js"
|
|
168
|
+
},
|
|
169
|
+
"require": {
|
|
170
|
+
"types": "./dist/cjs/streams/index.d.ts",
|
|
171
|
+
"default": "./dist/cjs/streams/index.js"
|
|
172
|
+
}
|
|
173
|
+
}
|
|
154
174
|
},
|
|
155
175
|
"scripts": {
|
|
156
176
|
"sync:contracts": "node scripts/sync-contracts.mjs",
|
|
157
177
|
"generate": "node scripts/generate-openapi.mjs && node scripts/generate-uns-v2.mjs",
|
|
158
|
-
"check:contracts": "node scripts/check-contracts.mjs && node --test scripts/retired-stream-probe.test.mjs scripts/retired-http-push.test.mjs scripts/retired-controls.test.mjs scripts/retired-public-labels.test.mjs scripts/platform-schema-projection.test.mjs scripts/data-capability-vector.test.mjs scripts/sync-contracts.test.mjs scripts/live-program-contract.test.mjs scripts/sync-http-run-observations.test.mjs && node scripts/generate-openapi.mjs --check && node scripts/generate-uns-v2.mjs --check",
|
|
178
|
+
"check:contracts": "node scripts/check-contracts.mjs && npm run check:acquisition-contract && node scripts/sync-capability-contract.mjs --check && node --test scripts/retired-stream-probe.test.mjs scripts/retired-http-push.test.mjs scripts/retired-controls.test.mjs scripts/retired-public-labels.test.mjs scripts/platform-schema-projection.test.mjs scripts/data-capability-vector.test.mjs scripts/sync-contracts.test.mjs scripts/sync-capability-contract.test.mjs scripts/live-program-contract.test.mjs scripts/sync-http-run-observations.test.mjs && node scripts/generate-openapi.mjs --check && node scripts/generate-uns-v2.mjs --check && npm run check:stream-contract",
|
|
159
179
|
"check:skills": "node scripts/check-skills.mjs && node scripts/check-skill-examples.mjs",
|
|
160
180
|
"typecheck": "tsc --noEmit && tsc -p tsconfig.tests.json",
|
|
161
181
|
"build": "node scripts/build.mjs",
|
|
162
182
|
"prepare": "npm run build",
|
|
163
183
|
"test": "vitest run",
|
|
164
184
|
"check": "npm run check:contracts && npm run check:skills && npm run typecheck && npm test && npm run build && node scripts/check-package.mjs",
|
|
165
|
-
"prepack": "npm run check:contracts && npm run check:skills && npm run build",
|
|
166
|
-
"prepublishOnly": "npm run check"
|
|
185
|
+
"prepack": "node scripts/sync-stream-contract.mjs --check --committed && node scripts/check-contracts.mjs --committed && npm run check:contracts && npm run check:skills && npm run build",
|
|
186
|
+
"prepublishOnly": "node scripts/sync-stream-contract.mjs --check --committed && node scripts/check-contracts.mjs --committed && npm run check",
|
|
187
|
+
"sync:capability-contract": "node scripts/sync-capability-contract.mjs",
|
|
188
|
+
"check:capability-contract": "node scripts/sync-capability-contract.mjs --check",
|
|
189
|
+
"sync:acquisition-contract": "node scripts/sync-acquisition-contract.mjs",
|
|
190
|
+
"check:acquisition-contract": "node scripts/sync-acquisition-contract.mjs --check && node --test scripts/sync-acquisition-contract.node.mjs",
|
|
191
|
+
"sync:stream-contract": "node scripts/sync-stream-contract.mjs",
|
|
192
|
+
"check:stream-contract": "node scripts/sync-stream-contract.mjs --check && openapi-typescript contracts/openapi/streams.swagger.json -o src/streams/contract.generated.ts --default-non-nullable=false --check",
|
|
193
|
+
"test:streams:nats": "node scripts/test-stream-messages-nats.mjs",
|
|
194
|
+
"test:streams:artifact": "npm run build && node scripts/test-stream-messages-nats.mjs --artifact"
|
|
167
195
|
},
|
|
168
196
|
"dependencies": {
|
|
197
|
+
"@nats-io/nats-core": "3.3.1",
|
|
198
|
+
"@nats-io/nkeys": "2.0.3",
|
|
169
199
|
"mqtt": "^5.3.0"
|
|
170
200
|
},
|
|
171
201
|
"peerDependencies": {
|
|
@@ -189,19 +219,19 @@
|
|
|
189
219
|
}
|
|
190
220
|
},
|
|
191
221
|
"devDependencies": {
|
|
222
|
+
"@tanstack/react-query": "^5.0.0",
|
|
192
223
|
"@types/node": "^22.0.0",
|
|
193
224
|
"@types/react": "^19.0.0",
|
|
194
|
-
"
|
|
225
|
+
"ajv": "^8.20.0",
|
|
226
|
+
"ajv-formats": "^3.0.1",
|
|
227
|
+
"esbuild": "^0.25.0",
|
|
228
|
+
"openapi-typescript": "^7.0.0",
|
|
195
229
|
"react": "^19.0.0",
|
|
196
|
-
"vue": "^3.4.0",
|
|
197
230
|
"typescript": "~5.9.3",
|
|
198
|
-
"openapi-typescript": "^7.0.0",
|
|
199
231
|
"vitest": "^4.1.7",
|
|
200
|
-
"
|
|
232
|
+
"vue": "^3.4.0",
|
|
201
233
|
"yaml": "^2.8.0",
|
|
202
|
-
"zod": "^4.0.0"
|
|
203
|
-
"ajv": "^8.20.0",
|
|
204
|
-
"ajv-formats": "^3.0.1"
|
|
234
|
+
"zod": "^4.0.0"
|
|
205
235
|
},
|
|
206
236
|
"agents": {
|
|
207
237
|
"skills": [
|
|
@@ -248,6 +278,10 @@
|
|
|
248
278
|
{
|
|
249
279
|
"name": "tier0-sdk-v2-data",
|
|
250
280
|
"path": "./skills/tier0-sdk-v2/tier0-sdk-v2-data"
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
"name": "tier0-sdk-v2-streams",
|
|
284
|
+
"path": "./skills/tier0-sdk-v2/tier0-sdk-v2-streams"
|
|
251
285
|
}
|
|
252
286
|
]
|
|
253
287
|
}
|
|
@@ -13,6 +13,7 @@ description: 使用 @tier0/sdk-v2 开发或迁移 Tier0 云端 TypeScript/JavaSc
|
|
|
13
13
|
| --- | --- | --- |
|
|
14
14
|
| 连接外部系统、维护采集源、检查连接和影响范围 | `client.source` | [Source](tier0-sdk-v2-source/SKILL.md) |
|
|
15
15
|
| 编写处理程序、配置源绑定、校验和发布、查运行记录 | `client.stream` | [Stream](tier0-sdk-v2-stream/SKILL.md) |
|
|
16
|
+
| 调用业务请求、发布或订阅业务消息、查询 App 交互能力 | `client.streams` | [App Stream 交互](tier0-sdk-v2-streams/SKILL.md) |
|
|
16
17
|
| 集合、目录、Topic、业务记录、历史采样与事务 | `client.uns` | [UNS](tier0-sdk-v2-uns/SKILL.md) |
|
|
17
18
|
| App 自有业务数据:声明集合、Mongo 风格读写与事务 | `@tier0/sdk-v2/data` | [App 数据](tier0-sdk-v2-data/SKILL.md) |
|
|
18
19
|
| 站内信、Web/Desktop 提醒、手机推送 | `client.notifications` | [通知](tier0-sdk-v2-notifications/SKILL.md) |
|
|
@@ -36,7 +37,7 @@ export async function inspectConnection() {
|
|
|
36
37
|
}
|
|
37
38
|
```
|
|
38
39
|
|
|
39
|
-
也可传 `apiHost`、`apiKey` 或 `getApiHost` / `getApiKey` 回调。API Key 留在服务端;不要生成 `VITE_*KEY` 或让页面携带 App Key
|
|
40
|
+
也可传 `apiHost`、`apiKey` 或 `getApiHost` / `getApiKey` 回调。API Key 留在服务端;不要生成 `VITE_*KEY` 或让页面携带 App Key。浏览器通过自己的服务端接口访问平台;App Stream 交互使用根入口 `client` 和受信宿主注入,无需设置密钥。消息要求 WebSocket(Node.js 22+ 或浏览器)。
|
|
40
41
|
|
|
41
42
|
不同身份使用独立实例。含员工数据委托时,每个入站请求新建实例,使用受信 App Gateway 提供的 `unsV2AppDataSubject`;它不是用户 ID,也不是 App 请求验签断言。不要从请求 JSON 拼造委托。身份验签流程见 [身份与框架集成](references/integration.md)。
|
|
42
43
|
|
|
@@ -64,6 +65,6 @@ export async function inspectConnection() {
|
|
|
64
65
|
|
|
65
66
|
React/Vue 示例与验签见 [集成参考](references/integration.md)。完成接入时验证目标入口、真实身份、响应解包和错误分支;SDK 本地测试通过不代表环境联调完成。
|
|
66
67
|
|
|
67
|
-
Stream
|
|
68
|
+
Stream 请求与消息交互统一使用本包 `client.streams.requests` / `client.streams.messages`;[交互说明](tier0-sdk-v2-streams/SKILL.md)。Scaffold 只依赖本包;`client.stream` 管理接口与运行时交互用途不同。
|
|
68
69
|
|
|
69
70
|
本包公开 HTTP 入口统一为 `/openapi/v2`。旧的重复操作已退役,不生成兼容调用或回退。Source 配置用 documents.*,Stream 草稿用 documents.*、发布用 programs.deploy;App 数据层仍提供 insertMany/findOneAnd*/distinct 等便捷方法。历史旧写结果未知时先核对业务状态,不能转换路由后盲目重放。
|
|
@@ -9,6 +9,8 @@ description: 使用 @tier0/sdk-v2/data 在 App 服务端声明并读写本 App
|
|
|
9
9
|
|
|
10
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
11
|
|
|
12
|
+
集合声明必须分别填写业务名称 `name` 与稳定程序标识 `alias`,例如 `name: '生产工单'`、`alias: 'production_orders'`。name 是唯一界面名称,没有 displayName 或 alias 回退;ensure 通过 alias 找回原集合,不覆盖用户改名。
|
|
13
|
+
|
|
12
14
|
## 按任务加载参考
|
|
13
15
|
|
|
14
16
|
| 用户任务 | 参考 |
|
|
@@ -30,6 +32,7 @@ import { defineCollection, type Doc } from '@tier0/sdk-v2/data/define';
|
|
|
30
32
|
|
|
31
33
|
export const workOrders = defineCollection({
|
|
32
34
|
alias: 'work_orders',
|
|
35
|
+
name: '生产工单',
|
|
33
36
|
description: 'Production work orders',
|
|
34
37
|
schema: z.object({
|
|
35
38
|
code: z.string(),
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
import { z } from 'zod';
|
|
15
15
|
import { createDataClient, defineCollection } from '@tier0/sdk-v2/data';
|
|
16
16
|
|
|
17
|
-
const stock = defineCollection({ alias: 'stock', schema: z.object({ sku: z.string(), available: z.number().int().min(0).meta({ format: 'int32' }) }) });
|
|
18
|
-
const reservations = defineCollection({ alias: 'reservations', schema: z.object({ sku: z.string(), qty: z.number().int().min(1).meta({ format: 'int32' }), orderId: z.string() }) });
|
|
19
|
-
const counters = defineCollection({ alias: 'counters', schema: z.object({ value: z.number().int().meta({ format: 'int32' }) }) });
|
|
17
|
+
const stock = defineCollection({ alias: 'stock', name: '库存', schema: z.object({ sku: z.string(), available: z.number().int().min(0).meta({ format: 'int32' }) }) });
|
|
18
|
+
const reservations = defineCollection({ alias: 'reservations', name: '库存预留', schema: z.object({ sku: z.string(), qty: z.number().int().min(1).meta({ format: 'int32' }), orderId: z.string() }) });
|
|
19
|
+
const counters = defineCollection({ alias: 'counters', name: '业务计数器', schema: z.object({ value: z.number().int().meta({ format: 'int32' }) }) });
|
|
20
20
|
const data = createDataClient({ collections: [stock, reservations, counters] });
|
|
21
21
|
|
|
22
22
|
/** 扣减库存并写预留:库存不足时整体不提交。 */
|
|
@@ -58,7 +58,7 @@ export function toResponse(error: unknown): Response {
|
|
|
58
58
|
|
|
59
59
|
| code | 含义与处理 |
|
|
60
60
|
| --- | --- |
|
|
61
|
-
| `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`
|
|
61
|
+
| `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` → 选择未占用的 alias;`NAME_IN_USE` → 选择同目录下不同且有业务含义的 name,保留 alias,不拼随机后缀;`INVALID_INDEX` → 修正索引 |
|
|
62
62
|
| `ANNOTATION_NOT_APPLIED`(`ignored` 提示) | 不是错误:ensure 保留已存储的描述,用 `schema:replace` 应用修改 |
|
|
63
63
|
| `REQUIRED_NOT_ENFORCED`(警告) | 声明要求的字段在存储中仍是可选;改回可选,或回填后用 `schema:replace` 收窄 |
|
|
64
64
|
| `INDEX_BUILD_FAILED`(警告) | 已有记录违反新唯一索引;修正重复数据,下次 ensure 会重试建索引 |
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# 声明集合与建模
|
|
2
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}$`。
|
|
3
|
+
`defineCollection({ alias, name, 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
|
+
`name` 是必填的业务名称,界面直接展示,例如“生产工单”;1–63 个 Unicode 字符,无首尾空白,不含 `/` 或空字符。`alias` 是独立、稳定的程序标识,例如 `production_orders`,改业务名称不改 alias 或集合 ID。名称应表达实际用途,语言遵循用户偏好和已有业务术语,不附加无业务含义的随机串、时间戳或 ID。没有 displayName,也不从 alias 回退生成名称。ensure 只在首次创建时写入 name;命中已有 alias 时保留用户当前名称,显式改名走 MCP 元数据更新。
|
|
4
6
|
|
|
5
7
|
```ts
|
|
6
8
|
import { z } from 'zod';
|
|
@@ -8,6 +10,7 @@ import { defineCollection, type Doc } from '@tier0/sdk-v2/data/define';
|
|
|
8
10
|
|
|
9
11
|
export const customers = defineCollection({
|
|
10
12
|
alias: 'customers',
|
|
13
|
+
name: '客户',
|
|
11
14
|
schema: z.object({ name: z.string(), tier: z.enum(['A', 'B', 'C']) }),
|
|
12
15
|
indexes: [{ path: 'name' }],
|
|
13
16
|
seed: [
|
|
@@ -18,6 +21,7 @@ export const customers = defineCollection({
|
|
|
18
21
|
|
|
19
22
|
export const salesOrders = defineCollection({
|
|
20
23
|
alias: 'sales_orders',
|
|
24
|
+
name: '销售订单',
|
|
21
25
|
description: 'Customer sales orders',
|
|
22
26
|
schema: z.object({
|
|
23
27
|
code: z.string(),
|
|
@@ -90,6 +94,7 @@ import { defineCollection } from '@tier0/sdk-v2/data/define';
|
|
|
90
94
|
|
|
91
95
|
export const loginCodes = defineCollection({
|
|
92
96
|
alias: 'login_codes',
|
|
97
|
+
name: '登录验证码',
|
|
93
98
|
schema: z.object({ phone: z.string(), code: z.string(), issuedAt: z.number().int().meta({ format: 'int32' }) }),
|
|
94
99
|
// issuedAt 存 Date.now();签发 5 分钟后删除。
|
|
95
100
|
indexes: [{ path: 'phone' }, { path: 'issuedAt', expireAfterSeconds: 300 }],
|
|
@@ -8,6 +8,7 @@ import { createDataClient, defineCollection } from '@tier0/sdk-v2/data';
|
|
|
8
8
|
|
|
9
9
|
const workOrders = defineCollection({
|
|
10
10
|
alias: 'work_orders',
|
|
11
|
+
name: '生产工单',
|
|
11
12
|
schema: z.object({
|
|
12
13
|
code: z.string(),
|
|
13
14
|
status: z.enum(['DRAFT', 'RELEASED', 'DONE']),
|
|
@@ -63,6 +64,7 @@ import { createDataClient, defineCollection } from '@tier0/sdk-v2/data';
|
|
|
63
64
|
|
|
64
65
|
const workOrders = defineCollection({
|
|
65
66
|
alias: 'work_orders',
|
|
67
|
+
name: '生产工单',
|
|
66
68
|
schema: z.object({ code: z.string(), status: z.enum(['DRAFT', 'RELEASED', 'DONE']) }),
|
|
67
69
|
indexes: [{ path: 'code', unique: true }, { path: 'status' }],
|
|
68
70
|
});
|
|
@@ -161,7 +163,7 @@ export async function bumpAll(items: Collection<{ status: string; views: number
|
|
|
161
163
|
import { z } from 'zod';
|
|
162
164
|
import { createDataClient, defineCollection, runAsApp, type SubscriptionEvent } from '@tier0/sdk-v2/data';
|
|
163
165
|
|
|
164
|
-
const tickets = defineCollection({ alias: 'tickets', schema: z.object({ title: z.string(), status: z.enum(['OPEN', 'DONE']) }) });
|
|
166
|
+
const tickets = defineCollection({ alias: 'tickets', name: '问题工单', schema: z.object({ title: z.string(), status: z.enum(['OPEN', 'DONE']) }) });
|
|
165
167
|
const data = createDataClient({ collections: [tickets] });
|
|
166
168
|
|
|
167
169
|
/** 一个进程一条 App 级订阅,在进程内扇出给浏览器。 */
|
|
@@ -5,7 +5,8 @@ Preview 与部署后的 App 共用集合,较旧的部署版本随时可能用
|
|
|
5
5
|
| 变更 | 做法 |
|
|
6
6
|
| --- | --- |
|
|
7
7
|
| 新增可选字段(任意层级,含数组元素内)、新增枚举值、字段改为可选、`int` → `number`、放宽或去掉 `min`/`max`、新增索引 | 修改声明;下次访问时 ensure 生效 |
|
|
8
|
-
|
|
|
8
|
+
| 修改集合名称 | 通过 tier0 MCP 的 metadata:updateOne 显式修改 name,保留 alias/ID;修改声明后重复 ensure 不覆盖已有名称 |
|
|
9
|
+
| 修改 `.describe()` 文本或集合 `description` | 修改声明,然后通过 tier0 MCP 执行 `schema:replace`,请求体从 `compileCollection(declaration)` 中只选 `alias`、`recordSchema`、`indexes`、`description`,不传 `name`(先 `dryRun`:期望 `widened`;`narrowed` 说明存储里还有该声明之外的内容,例如其他版本的字段——合并进请求体或停止)。ensure 保留已存储的描述并报告 `ANNOTATION_NOT_APPLIED`;新字段和新集合在创建时带上描述 |
|
|
9
10
|
| 新增每条记录都必须有的字段 | 先以 `.optional()` 加入,所有写入路径都写它,回填旧记录,等部署版本也写入后再用 `schema:replace` 改为必填 |
|
|
10
11
|
| 删除或重命名字段 | 代码不再使用旧字段(它可以留在存储里);新名字就是新的可选字段。通过 tier0 MCP 一次性回填(`records:updateMany` 配 `$rename` / `$set`) |
|
|
11
12
|
| 修改字段类型 | 换一个新字段名,回填,读取时以旧字段兜底。ensure 从不转换类型(`SCHEMA_CONFLICT`) |
|
|
@@ -15,7 +16,7 @@ Preview 与部署后的 App 共用集合,较旧的部署版本随时可能用
|
|
|
15
16
|
|
|
16
17
|
## 运行时代码与 worker 操作的边界
|
|
17
18
|
|
|
18
|
-
`schema:replace`、`archive`、`restore` 由 worker 通过 tier0 MCP 显式执行(`/uns/app/collections/schema:replace`、`archive`、`restore`),App 运行时代码不调用。数据层导出的 `compileCollection(declaration)` 给出与运行时 ensure 完全一致的条目 `{ alias, recordSchema, indexes, description? }
|
|
19
|
+
`schema:replace`、`archive`、`restore` 由 worker 通过 tier0 MCP 显式执行(`/uns/app/collections/schema:replace`、`archive`、`restore`),App 运行时代码不调用。数据层导出的 `compileCollection(declaration)` 给出与运行时 ensure 完全一致的条目 `{ alias, name, recordSchema, indexes, description? }`,可用于 ensure `dryRun`。`schema:replace` 只接收其中的 alias、recordSchema、indexes、description,不接收 name;不能把 compileCollection 的完整结果直接作为替换请求。
|
|
19
20
|
|
|
20
21
|
```ts
|
|
21
22
|
import { z } from 'zod';
|
|
@@ -23,13 +24,14 @@ import { compileCollection, defineCollection } from '@tier0/sdk-v2/data';
|
|
|
23
24
|
|
|
24
25
|
const tickets = defineCollection({
|
|
25
26
|
alias: 'tickets',
|
|
27
|
+
name: '问题工单',
|
|
26
28
|
schema: z.object({ title: z.string().describe('Short summary'), severity: z.enum(['LOW', 'HIGH']).optional() }),
|
|
27
29
|
});
|
|
28
30
|
|
|
29
31
|
/** schema:replace 请求体的基础;expectedRevision 取自 ensure 结果的集合 revision。 */
|
|
30
32
|
export function replaceBody(expectedRevision: number) {
|
|
31
|
-
const { alias, recordSchema, indexes } = compileCollection(tickets);
|
|
32
|
-
return { alias, recordSchema, indexes, expectedRevision, dryRun: true };
|
|
33
|
+
const { alias, recordSchema, indexes, description } = compileCollection(tickets);
|
|
34
|
+
return { alias, recordSchema, indexes, ...(description ? { description } : {}), expectedRevision, dryRun: true };
|
|
33
35
|
}
|
|
34
36
|
```
|
|
35
37
|
|
|
@@ -1,20 +1,26 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 分片上传、进度和续传
|
|
2
2
|
|
|
3
3
|
| 调用 | 行为 |
|
|
4
4
|
| --- | --- |
|
|
5
5
|
| files.upload(file) | 大于 100 MiB 自动分片 |
|
|
6
|
+
| files.upload(file, { multipart: options }) | 任意非空文件显式分片;业务元数据仍放在 multipart 外层 |
|
|
6
7
|
| files.uploadMultipart(file, options) | 非空文件显式分片,可报告进度 |
|
|
7
|
-
| files.
|
|
8
|
+
| files.resumeMultipart(file, session, options) | 复用会话,跳过已确认的分片,再提交完成 |
|
|
9
|
+
| files.multipart.init/partUrls/complete/abort | 原子接口,普通业务上传使用上面的助手 |
|
|
8
10
|
|
|
9
11
|
| options 字段 | 类型 | 说明 |
|
|
10
12
|
| --- | --- | --- |
|
|
11
13
|
| partSize | number | 默认 8 MiB,至少 5 MiB;实际采用服务器返回值 |
|
|
12
|
-
|
|
|
14
|
+
| concurrency | number | 默认 4,同时上传的分片上限,至少 1 |
|
|
15
|
+
| maxRetries | number | 默认 3,每个存储 PUT 的额外重试次数,可设为 0 |
|
|
16
|
+
| onProgress | function | 收到 uploadedBytes、totalBytes、doneParts、totalParts、percent(0–100) |
|
|
13
17
|
| signal | AbortSignal | 取消等待/传输 |
|
|
14
18
|
| 其余上传元数据 | string | 与 upload 相同 |
|
|
15
19
|
|
|
20
|
+
resumeMultipart 只接收 concurrency、maxRetries、onProgress、signal;文件元数据和服务器 partSize 来自 session。进度按收到 ETag 的已完成分片计算,续传时包含此前已完成的分片。
|
|
21
|
+
|
|
16
22
|
```ts
|
|
17
|
-
import { createClient, MultipartUploadError, type Tier0Client } from '@tier0/sdk-v2';
|
|
23
|
+
import { createClient, MultipartUploadError, type MultipartSession, type Tier0Client } from '@tier0/sdk-v2';
|
|
18
24
|
|
|
19
25
|
export async function uploadWithProgress(
|
|
20
26
|
file: File, report: (uploadedBytes: number, totalBytes: number) => void,
|
|
@@ -22,7 +28,7 @@ export async function uploadWithProgress(
|
|
|
22
28
|
) {
|
|
23
29
|
try {
|
|
24
30
|
const saved = await client.files.uploadMultipart(file, {
|
|
25
|
-
partSize: 8 * 1024 * 1024, signal,
|
|
31
|
+
partSize: 8 * 1024 * 1024, concurrency: 4, maxRetries: 3, signal,
|
|
26
32
|
onProgress: progress => report(progress.uploadedBytes, progress.totalBytes),
|
|
27
33
|
});
|
|
28
34
|
return { state: 'completed' as const, filePath: saved.filePath };
|
|
@@ -33,10 +39,19 @@ export async function uploadWithProgress(
|
|
|
33
39
|
throw error;
|
|
34
40
|
}
|
|
35
41
|
}
|
|
42
|
+
|
|
43
|
+
export async function resumeUpload(
|
|
44
|
+
originalFile: File, session: MultipartSession,
|
|
45
|
+
client: Tier0Client = createClient(), signal?: AbortSignal,
|
|
46
|
+
) {
|
|
47
|
+
return client.files.resumeMultipart(originalFile, session, { concurrency: 4, signal });
|
|
48
|
+
}
|
|
36
49
|
```
|
|
37
50
|
|
|
38
|
-
成功后才持久化 filePath。中断 session 包含 fileKey、uploadId、parts(partNumber/etag)、completionAttempted
|
|
51
|
+
成功后才持久化 filePath。中断 session 包含 fileKey、uploadId、parts(partNumber/etag)、completionAttempted,以及续传所需的 fileName、fileSize、服务器 partSize。会话不是完成回执,不公开到日志或无关用户。
|
|
52
|
+
|
|
53
|
+
失败时停止领取新分片,等待在途分片结束后返回稳定的 session;不会自动 abort 或在失败后暗中 complete。续传使用原文件(名称和大小必须匹配),保留已确认的 ETag,只重新传输缺少回执的分片。旧 session 若缺少文件信息或服务器 partSize,会明确拒绝续传,不推测分片边界。跨域存储需要暴露 ETag 响应头。
|
|
39
54
|
|
|
40
|
-
|
|
55
|
+
自动重试仅限同一签名地址、同一分片内容的存储 PUT 网络中断、超时或 HTTP 408/429/500/502/503/504;同一 uploadId/partNumber 的 PUT 可覆盖。401/403、用户取消以及平台 init/partUrls/complete 请求不会自动重试。存储请求不携带平台密钥。签名过期可再次 resumeMultipart 获取新的分片地址。
|
|
41
56
|
|
|
42
|
-
completionAttempted=true 表示 complete
|
|
57
|
+
completionAttempted=true 表示 complete 已尝试提交,结果可能未知,resumeMultipart 会拒绝重放;先对账最终文件,不手动清除该标记或直接 abort。明确放弃未完成会话时才调用 abort;初始化请求本身结果未知且无 session 时,保存错误信息交由平台对账,不猜 uploadId。
|
|
@@ -12,7 +12,7 @@ Source 维护连接配置、认证引用与源状态。脚本处理用 [Stream](
|
|
|
12
12
|
| 用户任务 | 参考 | SDK 方法 |
|
|
13
13
|
| --- | --- | --- |
|
|
14
14
|
| 选协议、准备连接配置 | [协议配置](references/protocols.md) | protocols.list |
|
|
15
|
-
| 创建 HTTP
|
|
15
|
+
| 创建 HTTP/SQL 带凭据源 | [创建](references/create.md) | documents.create |
|
|
16
16
|
| 列表、详情、运行状态 | [查询](references/read.md) | documents.list/get、observations.get、runtime.get |
|
|
17
17
|
| 修改名称、端点或配置 | [修改](references/update.md) | documents.update |
|
|
18
18
|
| 查看修改/启停/删除影响 | [影响分析](references/changes.md) | impact |
|
|
@@ -26,12 +26,12 @@ Source 维护连接配置、认证引用与源状态。脚本处理用 [Stream](
|
|
|
26
26
|
1. Source 配置统一使用 documents.*,原生文档为 `{name,protocol,config}`,HTTP API 显式指定 `config.role`。HTTP 统一为 `protocol: http-api` + `config.role: client/server`;Client 主动请求,Server 接收入站请求,不能互换。HTTPServer 统一接收入站请求,固定前缀后的路由和响应时机属于 Stream。
|
|
27
27
|
2. 已有 ID 和 revision 时优先用 documents.update 的 patch,避免额外 get;完整替换 document 前读取完整文档,安全列表摘要不能无损回写。原生 ID 是十进制字符串,revision 原样传递,不转 Number。
|
|
28
28
|
3. 现有 Source 修改、启停、删除都要 expectedRevision。没有任何 Stream 引用时可直接提交;有草稿、活动、历史或不可见引用时仍需 confirmedImpactRevision。先形成精确意图,再读取全部影响分页;回执不可合成、跨意图复用或静默刷新。提供的回执始终严格校验,不自动获取并同意影响。
|
|
29
|
-
4. 配置中保留认证/TLS 引用,不写明文秘密;HTTP Client 新凭据走创建专用 credentials
|
|
29
|
+
4. 配置中保留认证/TLS 引用,不写明文秘密;HTTP Client 与 SQL 新凭据走创建专用 credentials(SQL 使用 usernamePassword,省略 method),普通创建省略 credentials.method(服务端默认 REQUEST 基址绑定),真实 method/path/query 由 Stream 定义。Basic/Bearer 显式凭据方法限制精确 URL 和方法,OAuth 旧 GET 保留既有兼容语义,不自动扩大旧授权;Server 不携带 Client auth/TLS 配置。
|
|
30
30
|
5. desiredState 不代表实际健康,保存配置不代表 Stream 已切换至新 configRevision。运行状态另行读取。
|
|
31
31
|
|
|
32
32
|
## 返回值与异常
|
|
33
33
|
|
|
34
|
-
所有调用返回完整 code/msg/requestId/data。documents.create/update 的预检返回 validated/sideEffects,提交返回完整 Source view,可直接使用其中的文档和 revision,无需额外 get;批量状态/删除返回 Outcome。只有 committed 且无项错误才可报告提交成功;accepted/jobId
|
|
34
|
+
所有调用返回完整 code/msg/requestId/data。documents.create/update 的预检返回 validated/sideEffects,提交返回完整 Source view,可直接使用其中的文档和 revision,无需额外 get;批量状态/删除返回 Outcome。只有 committed 且无项错误才可报告提交成功;accepted/jobId 继续查询作业。创建、更新、状态变更的 dryRun 默认 false;预检按需使用,不是每次提交的必经步骤。
|
|
35
35
|
|
|
36
36
|
需要查看历史用 source.history({id,beforeRevision,size}),按 nextBeforeRevision 继续;探测用 source.probe,其外部系统访问应属于当前任务。配置校验或连接探测成功不证明某个 Stream 请求已获授权或采集成功,实际结果看对应发布的 runs。连接配置修改使用 documents.update,遵守同一影响确认契约。
|
|
37
37
|
|
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
| 字段 | 类型 | 必填 | 说明 |
|
|
6
6
|
| --- | --- | --- | --- |
|
|
7
|
-
| dryRun | boolean |
|
|
7
|
+
| dryRun | boolean | 否 | 默认 false 提交;true 仅预检 |
|
|
8
8
|
| document.name | string | 是 | 1–128 字符 |
|
|
9
9
|
| document.protocol | string | 是 | 原生协议,如 http-api、mqtt、modbus |
|
|
10
10
|
| document.config.role | client / server | HTTP API 显式指定 | client 主动请求;server 接收入站请求 |
|
|
11
11
|
| document.config | object | 是 | endpoint、options;见[协议配置](protocols.md) |
|
|
12
12
|
| document.description | string | 否 | 最多 512 字符 |
|
|
13
|
-
| credentials | object | 否 |
|
|
13
|
+
| credentials | object | 否 | HTTP Client 或 SQL 的只写新凭据;与已有 auth 引用互斥 |
|
|
14
14
|
| idempotencyKey | string | 否 | 建议提供,最多 128 字符,同一业务意图保持不变 |
|
|
15
15
|
|
|
16
16
|
## 完整示例:HTTP Client 数据源
|
|
@@ -90,7 +90,7 @@ Server 的固定前缀 `/ingest/v1/{workspaceId}/{sourceId}` 和 Bearer 要求
|
|
|
90
90
|
|
|
91
91
|
## 四类 inline 认证与 REQUEST 绑定
|
|
92
92
|
|
|
93
|
-
单条创建可通过 `credentials` 提供以下只写材料;与已有 auth/TLS
|
|
93
|
+
HTTP Client 单条创建可通过 `credentials` 提供以下只写材料;与已有 auth 引用互斥,HTTP/SQL 当前不支持 TLS 凭据引用,并要求本次创建的 `idempotencyKey`。材料从调用方已授权的密钥提供者注入,不在示例中读取文件或环境中的秘密,不记录或返回材料。
|
|
94
94
|
|
|
95
95
|
| kind | material | method |
|
|
96
96
|
| --- | --- | --- |
|
|
@@ -120,3 +120,9 @@ export async function createHttpSourceWithCredentials(
|
|
|
120
120
|
return { receipt: saved, current: saved.data };
|
|
121
121
|
}
|
|
122
122
|
```
|
|
123
|
+
|
|
124
|
+
## SQL 用户名和密码
|
|
125
|
+
|
|
126
|
+
SQL 单条创建可直接传入 `credentials: { kind: "usernamePassword", material: { username, password } }`,不再预先调用独立凭据接口。`document.protocol` 为 `sql`,`config.endpoint` 只包含数据库地址(不包含用户名或密码),`credentials.method` 必须省略。仍需 `idempotencyKey`。秘密由 Vault 保存,Source 文档仅含引用;预检不写入凭据。
|
|
127
|
+
|
|
128
|
+
`dryRun` 省略时直接提交;需要预检才设为 true。后续启用 Source、部署 Stream,并通过运行记录和 Collection 读回确认采集成功。
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
| 字段 | 类型 | 必填 | 说明 |
|
|
6
6
|
| --- | --- | --- | --- |
|
|
7
|
-
| dryRun | boolean |
|
|
7
|
+
| dryRun | boolean | 否 | 默认 false 提交;true 仅预检 |
|
|
8
8
|
| items[].id / expectedRevision | string / integer | 是 | 当前对象和已知修订 |
|
|
9
9
|
| items[].desiredState | enabled/disabled | 是 | 目标状态 |
|
|
10
10
|
| items[].confirmedImpactRevision | string | 有引用时 | enable/disable 对应的影响回执 |
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
| patch / document | object / CloudSourceDocument | 二选一 | patch 保留未提供的字段;document 完整替换 |
|
|
9
9
|
| expectedRevision | integer | 是 | 已读取或上次提交返回的 revision,原样传回 |
|
|
10
10
|
| confirmedImpactRevision | string | 有引用时 | 相同修改意图和修订的 impact 回执 |
|
|
11
|
-
| dryRun | boolean |
|
|
11
|
+
| dryRun | boolean | 否 | 默认 false 提交;true 仅预检 |
|
|
12
12
|
| idempotencyKey | string | 否 | 最多 128 字符 |
|
|
13
13
|
|
|
14
14
|
## 示例:一次请求修改名称
|
|
@@ -26,7 +26,7 @@ description: 使用 @tier0/sdk-v2 的 client.stream 创建、查询、修改和
|
|
|
26
26
|
| Runtime program | name、script 字符串、sourceBindings、timeoutMs | documents.create/update/validate({program}) |
|
|
27
27
|
| 云端原生文档 | name、model、script 对象、sources、entries | documents.create/get/update/validate |
|
|
28
28
|
|
|
29
|
-
程序必须是完整 `module.exports = api => { ... }`,执行器 API 是 api.sources/api.uns/api.stream,与 npm 包的 client.source 不同。Runtime sourceBindings 为 sourceId/configRevision/acquisition;原生 sources 还使用 alias/resources。连接凭据不进脚本或 acquisition,秘密材料由 Source 执行宿主管理,不通过脚本读取、记录或返回。定时入口使用 api.stream.every,不传播旧注册别名;App
|
|
29
|
+
程序必须是完整 `module.exports = api => { ... }`,执行器 API 是 api.sources/api.uns/api.stream,与 npm 包的 client.source 不同。Runtime sourceBindings 为 sourceId/configRevision/acquisition;原生 sources 还使用 alias/resources。连接凭据不进脚本或 acquisition,秘密材料由 Source 执行宿主管理,不通过脚本读取、记录或返回。定时入口使用 api.stream.every,不传播旧注册别名;App 请求与消息交互由本包 @tier0/sdk-v2 的 client.streams.requests/messages 提供,不作为 Goja 注册参数。
|
|
30
30
|
|
|
31
31
|
## 核心约束
|
|
32
32
|
|
|
@@ -37,6 +37,7 @@ description: 使用 @tier0/sdk-v2 的 client.stream 创建、查询、修改和
|
|
|
37
37
|
5. resources=[] 是空冻结资源集,不是通配符。写入本身由服务端校验,需要预览时单独 validate;当前云端程序 timeoutMs 上限 30000。
|
|
38
38
|
6. HTTPServer 响应时机在 Stream:普通返回等处理/提交完成;HTTP onRequest 内 await ctx.reply 可先回复一次再继续,仍受超时/并发/权限约束,断线不取消但 shutdown 可取消,不保证崩溃重放。
|
|
39
39
|
7. 仅执行用户要求的生命周期步骤;修改脚本不自动授予发布、停用或删除权限。
|
|
40
|
+
8. 用户要求采集落库时,先创建或复用 Collection 并确定字段映射与稳定 recordId,再绑定已启用 Source 的最新修订;脚本显式 api.uns.use(...).write。部署后核对实际运行并读回记录,返回上游结果、保存草稿或样例预演均不代表已落库。
|
|
40
41
|
|
|
41
42
|
## 成功与异常
|
|
42
43
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
| 字段 | 类型 | 必填 | 说明 |
|
|
6
6
|
| --- | --- | --- | --- |
|
|
7
|
-
| dryRun | boolean |
|
|
7
|
+
| dryRun | boolean | 否 | 默认 false 提交;true 仅预检 |
|
|
8
8
|
| program.name | string | 是 | 1–63 字符 |
|
|
9
9
|
| program.script | string | 是 | 完整 module.exports 程序 |
|
|
10
10
|
| program.timeoutMs | integer | 否 | 当前云端上限 30000 ms |
|
|
@@ -21,8 +21,9 @@ Source 负责基址和认证,Stream 负责请求的 method/path/query。以下
|
|
|
21
21
|
import { createClient, type Tier0Client, type RuntimeStreamDefinition, type CloudSourceDocumentView } from '@tier0/sdk-v2';
|
|
22
22
|
|
|
23
23
|
export async function createHttpStream(
|
|
24
|
-
source: CloudSourceDocumentView, name: string, intentKey: string, client: Tier0Client = createClient(),
|
|
24
|
+
source: CloudSourceDocumentView, collectionId: string, name: string, intentKey: string, client: Tier0Client = createClient(),
|
|
25
25
|
) {
|
|
26
|
+
if (!collectionId.trim()) throw new Error('需要实际 Collection ID');
|
|
26
27
|
const sourceId = source.id;
|
|
27
28
|
if (!/^[1-9][0-9]*$/.test(sourceId)) throw new Error('Source ID 必须是正十进制字符串');
|
|
28
29
|
if (source.document.protocol !== 'http-api' || 'sourceType' in source.document.config ||
|
|
@@ -35,9 +36,17 @@ export async function createHttpStream(
|
|
|
35
36
|
script: [
|
|
36
37
|
'module.exports = api => {',
|
|
37
38
|
' const upstream = api.sources.use({ id: ' + JSON.stringify(sourceId) + ', resources: [] });',
|
|
39
|
+
' const output = api.uns.use({ collectionId: ' + JSON.stringify(collectionId) + ', access: "write" });',
|
|
38
40
|
' api.stream.every({ id: "tick", intervalMs: 60000 }, async () => {',
|
|
39
41
|
' const response = await upstream.request({ method: "GET", path: "inventory", query: { limit: "5000" } });',
|
|
40
|
-
'
|
|
42
|
+
' if (response.status !== 200) throw new Error("Upstream inventory HTTP request failed");',
|
|
43
|
+
' if (!Array.isArray(response.data)) throw new Error("Unexpected upstream inventory response format");',
|
|
44
|
+
' const records = response.data.map(row => {',
|
|
45
|
+
' if (!row || typeof row.sku !== "string" || !row.sku.trim() || typeof row.quantity !== "number" || !Number.isFinite(row.quantity)) throw new Error("Invalid inventory row");',
|
|
46
|
+
' return { recordId: row.sku, record: { sku: row.sku, quantity: row.quantity } };',
|
|
47
|
+
' });',
|
|
48
|
+
' for (const record of records) await output.write(record);',
|
|
49
|
+
' return { acceptedRows: records.length };',
|
|
41
50
|
' });',
|
|
42
51
|
'};',
|
|
43
52
|
].join('\n'),
|
|
@@ -50,7 +59,13 @@ export async function createHttpStream(
|
|
|
50
59
|
}
|
|
51
60
|
```
|
|
52
61
|
|
|
53
|
-
|
|
62
|
+
先创建或复用真实 Collection,再传入 collectionId;此库存示例的 recordSchema 必须包含 sku(string)与 quantity(number),以业务 SKU 作为稳定 recordId。按真实上游合同修改字段映射、path="inventory"、query 和 60 秒周期;不能只返回 response.data 就声称已经落库。resources=[] 是空冻结资源集,不是通配授权。源地址和凭据留在 Source,不能拷进 acquisition 或脚本。
|
|
63
|
+
|
|
64
|
+
此函数保存草稿。完整采集还需启用 Source,取得最新 revision 后更新绑定并部署 Stream;核对本次发布的运行成功,再通过 records:find 读回目标 Collection 的业务记录。acceptedRows 只表示处理器已接受写入,隔离 sampleTest 的 writePreview 不会落库;未完成真实运行和读回前,不得报告采集完成。
|
|
65
|
+
|
|
66
|
+
`upstream.request` 收到完整的 401/403/429/5xx 响应时仍返回 `{status,headers,data}`,不会仅因非 2xx 自动抛错;必须先检查状态,再校验业务数据,最后才缓冲 UNS 写入。此库存示例约定 200 和数组;其他接口按其合同调整,不能把错误响应当业务数据。脚本异常只使用固定文案,不拼接响应正文、认证头或 URL。
|
|
67
|
+
|
|
68
|
+
运行记录与 SDK 管理请求异常是两层:创建/发布成功不代表上游成功。支持 HTTP 响应诊断的后端可在失败或未知运行中返回 `SOURCE_HTTP_401`、`stage:source_response`;401 应核对或更新上游拒绝的凭据,403 核对上游授权,429 核对采集频率。不要自动刷新凭据、扩大权限或立即重试。旧后端可能仍只返回 `RESULT_UNKNOWN`;仅修改 throw 文案不能让它保留 HTTP 状态。详见[运行诊断](read.md)。
|
|
54
69
|
|
|
55
70
|
创建 data 为 CloudStreamDocumentView 或预检结果,实际保存返回 id、revision 和完整 document;预检不生成 ID。需要展示预检时单独调用 documents.validate({program});普通保存无需先校验再保存。当前后端创建/校验需要注册 Worker 可用。TEMPORARILY_UNAVAILABLE 不代表脚本一定错误。
|
|
56
71
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
| 方法 | POST 路径 | 必要参数 |
|
|
4
4
|
| --- | --- | --- |
|
|
5
|
-
| programs.deploy | /openapi/v2/uns/streams/program/deploy | id
|
|
5
|
+
| programs.deploy | /openapi/v2/uns/streams/program/deploy | id;dryRun 默认 false,建议 expectedRevision |
|
|
6
6
|
| programs.rollback | /openapi/v2/uns/streams/program/rollback | id、versionId、dryRun;建议 expectedRevision |
|
|
7
7
|
| documents.disable | /openapi/v2/uns/streams/documents/disable | id、expectedRevision、expectedActiveRelease、dryRun |
|
|
8
8
|
|
|
@@ -57,3 +57,5 @@ export async function readPublishedRuns(
|
|
|
57
57
|
失败或结果未知的运行可带 `error:{code,stage,message,repair,sourceRequest?}` 安全诊断。`SOURCE_TARGET_SCOPE_MISMATCH`、`stage:validate_binding` 表示请求超出 Source 凭据范围,应检查绑定基址与实际 method/path/query,保留原授权限制。`sourceRequest:notDispatched` 只证明失败的这一次 Source 请求未发送,不能推断整个 Stream 未开始或此前没有写入。
|
|
58
58
|
|
|
59
59
|
`EXECUTION_FAILED` 和 `RESULT_UNKNOWN` 是安全通用诊断,分别保留失败和结果未知的区别。按 code/stage 判断,展示服务端 message/repair;没有 error 的旧记录仍按 status 解释,不补造原因,也不因可重试猜测而自动重新执行。运行诊断不包含 token、凭据引用或完整请求 URL。
|
|
60
|
+
|
|
61
|
+
支持 HTTP 响应诊断的后端还会保留 `SOURCE_HTTP_<三位状态码>`(300–599)、`stage:source_response`,例如 `SOURCE_HTTP_401`。这是可信宿主在本轮观察到的非 2xx 响应;不是脚本错误文案,也不证明它是所有后续失败的唯一原因。`status:unknown` 可以同时带此诊断:已知上游返回 401,不等于整轮外部副作用已确定或可以安全重试。只读查询 runs 的 HTTP 200 也不等于采集成功。运行最终成功时不会附加此前已被脚本处理的 HTTP 错误。
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
| id | string | 是 | 十进制字符串 |
|
|
8
8
|
| document | CloudStreamDocument | 是 | name、model、script、sources、entries 完整文档 |
|
|
9
9
|
| expectedRevision | integer | 建议始终提供 | 当前回执或查询的修订,防止覆盖他人修改 |
|
|
10
|
-
| dryRun | boolean |
|
|
10
|
+
| dryRun | boolean | 否 | 默认 false 提交;true 仅预检 |
|
|
11
11
|
| idempotencyKey | string | 否 | 本次更新独立意图键 |
|
|
12
12
|
|
|
13
13
|
## 示例:更新描述,保持脚本和绑定
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tier0-sdk-v2-streams
|
|
3
|
+
description: "Tier0 App/Stream requests and live business messages: client.streams.requests describe/invoke/discover, client.streams.messages publish/subscribe, trusted App runtime bootstrap and disposable subscriptions. Use for Stream v3 interactions; UNS history, device MQTT and persisted notifications have their own Skills."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# App / Stream Interactions
|
|
7
|
+
|
|
8
|
+
Read the [root Skill](../SKILL.md) first. These APIs require an artifact built from the unified SDK implementation: pin its commit and SHA-256 until the corresponding version is published.
|
|
9
|
+
|
|
10
|
+
## Select the interaction
|
|
11
|
+
|
|
12
|
+
- A public request code with an input/output contract: `client.streams.requests.use({ code })`. Use `describe()` to inspect it; use `client.streams.requests.discover()` to find visible contracts.
|
|
13
|
+
- App request capability discovery: `client.streams.capabilities.discover()`. It validates request/invoke contracts independently of `client.capabilities.discover()`, which covers Namespace/Source/Stream management. Neither grants access or requires a mandatory preflight before every call.
|
|
14
|
+
- A live logical business topic: `client.streams.messages.use({ topic })`. Use `publish(payload)` or `subscribe(handler)` and dispose the returned subscription.
|
|
15
|
+
- Persisted business data belongs to UNS, device-topic MQTT belongs to the MQ Skill, and user-facing notification records belong to the Notifications Skill. Live Stream messages have no retention or replay.
|
|
16
|
+
|
|
17
|
+
Import `client` or `createClient` from `@tier0/sdk-v2`. The same client exposes `uns`, `source`, `stream`, `capabilities`, `files` and `notifications`. The singular `stream` manages documents, programs and runs; plural `streams` performs App requests and live messaging. MQTT stays in the separate `/mq` entry.
|
|
18
|
+
|
|
19
|
+
## Trusted App bootstrap
|
|
20
|
+
|
|
21
|
+
The SWE host installs `globalThis[Symbol.for('tier0.sdk.app-runtime.v1')]` before the App script. The SDK detects it automatically; App business code must not install a replacement provider or resolve credentials itself. The host's provider returns a `Response` for its authenticated request bridge.
|
|
22
|
+
|
|
23
|
+
Browser App code knows only public request codes, logical topics and business payloads. Do not add broker URLs, physical subjects, API keys, JWTs, nkey seeds, workspace/principal headers or message identity claims to App configuration. `@tier0/sdk-v2/runtime` is a trusted host integration entry point, not App setup.
|
|
24
|
+
|
|
25
|
+
For a standalone Node service, the deployment supplies its existing HTTP credential and real App context. Call `createClient()` without literals; never put these server credentials in a browser bundle.
|
|
26
|
+
|
|
27
|
+
## Public caller
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { createClient, type StreamMessageError } from '@tier0/sdk-v2';
|
|
31
|
+
|
|
32
|
+
export async function openOrders(
|
|
33
|
+
reportError: (error: StreamMessageError) => void,
|
|
34
|
+
renderOrderUpdate: (order: { orderId: string }) => void,
|
|
35
|
+
) {
|
|
36
|
+
const sdk = createClient(); // automatically uses the trusted App runtime
|
|
37
|
+
const removeErrorHandler = sdk.streams.messages.onError(reportError);
|
|
38
|
+
const subscription = await sdk.streams.messages.use<{ orderId: string }>({ topic: 'orders/accepted' })
|
|
39
|
+
.subscribe(message => renderOrderUpdate(message.payload));
|
|
40
|
+
const request = sdk.streams.requests.use<{ orderId: string }, { accepted: boolean }>({ code: 'orders.approve' });
|
|
41
|
+
return {
|
|
42
|
+
describe: () => request.describe(),
|
|
43
|
+
approve: (orderId: string, savedIntentId: string) => request.invoke({ orderId }, { idempotencyKey: savedIntentId }),
|
|
44
|
+
async dispose() {
|
|
45
|
+
await subscription.dispose();
|
|
46
|
+
removeErrorHandler();
|
|
47
|
+
await sdk.dispose();
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Persist one caller-owned idempotency key per business intent. A deliberate reconciliation/replay reuses that key. The SDK never generates a request idempotency key, selects deployment versions or retries a write. An unknown result means execution may have happened; reconcile the returned run metadata before any further business action.
|
|
54
|
+
|
|
55
|
+
## Live message lifecycle
|
|
56
|
+
|
|
57
|
+
Messages use a versioned `{ version, messageId, correlationId?, payload }` envelope. Caller-supplied identity in a payload is business data, not authenticated publisher metadata; authorized broker subjects establish identity internally.
|
|
58
|
+
|
|
59
|
+
The SDK uses native Core NATS over WSS, loaded only when messaging starts. Messages require a browser with WebSocket or Node.js 22+; HTTP-only clients remain usable on Node.js 20.19+. Each initial connection, reconnect and renewal gets a new in-memory user key and a public-key-bound JWT. The old socket closes and its private key is wiped before replacement. No private key leaves the SDK. A successful publish means broker flush, not receiver acknowledgement.
|
|
60
|
+
|
|
61
|
+
Register `messages.onError` (or subscription `onError`) to handle asynchronous broker, decoding and handler failures. After an established session disconnects, publish fails immediately: no offline buffering, write retry or message replay. Credential refresh and reconnection restore subscriptions only. Dispose subscriptions and the owned client when the view/service stops.
|
|
62
|
+
|
|
63
|
+
## Stream-side code
|
|
64
|
+
|
|
65
|
+
Current Cloud Source/Stream documents omit `sdkVersion` and `configVersion` selectors. When the coordinated request/message runtime is deployed, request registration uses `api.stream.onRequest({ id, request }, handler)`; business-message consumers use `api.stream.messages.subscribe({ id, topic, inputSchema }, handler)`. Declare each outgoing logical topic with `api.stream.messages.use({ topic, access: 'publish' })` before calling `api.stream.messages.publish({ topic, payload })`. The current `api.stream.onMessage` remains a Source-event handler, not a business-message subscription.
|
|
66
|
+
|
|
67
|
+
Use the [Stream management Skill](../tier0-sdk-v2-stream/SKILL.md) for creating and deploying a program. Historical documents may be displayed as immutable history; do not submit them as current write/validation fixtures or infer that current deployments provide the new message runtime.
|