@tier0/sdk-v2 0.7.2 → 0.8.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.
Files changed (34) hide show
  1. package/README.md +5 -1
  2. package/dist/cjs/data/bootstrap.js +5 -5
  3. package/dist/cjs/data/bootstrap.js.map +1 -1
  4. package/dist/cjs/data/define.d.ts +6 -0
  5. package/dist/cjs/data/define.js +11 -1
  6. package/dist/cjs/data/define.js.map +1 -1
  7. package/dist/cjs/data/schema.d.ts +1 -0
  8. package/dist/cjs/data/schema.js +7 -0
  9. package/dist/cjs/data/schema.js.map +1 -1
  10. package/dist/cjs/uns-v2/generated.d.ts +8 -7
  11. package/dist/cjs/uns-v2/generated.js +7 -7
  12. package/dist/cjs/uns-v2/generated.js.map +1 -1
  13. package/dist/esm/data/bootstrap.js +5 -5
  14. package/dist/esm/data/bootstrap.js.map +1 -1
  15. package/dist/esm/data/define.d.ts +6 -0
  16. package/dist/esm/data/define.js +9 -0
  17. package/dist/esm/data/define.js.map +1 -1
  18. package/dist/esm/data/schema.d.ts +1 -0
  19. package/dist/esm/data/schema.js +8 -1
  20. package/dist/esm/data/schema.js.map +1 -1
  21. package/dist/esm/uns-v2/generated.d.ts +8 -7
  22. package/dist/esm/uns-v2/generated.js +7 -7
  23. package/dist/esm/uns-v2/generated.js.map +1 -1
  24. package/package.json +1 -1
  25. package/skills/tier0-sdk-v2/tier0-sdk-v2-data/SKILL.md +3 -0
  26. package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/consistency.md +3 -3
  27. package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/errors.md +1 -1
  28. package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/modeling.md +6 -1
  29. package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/read-write.md +3 -1
  30. package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/schema-evolution.md +6 -4
  31. package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/references/create.md +6 -0
  32. package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/references/read.md +2 -0
  33. package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/create.md +1 -1
  34. 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.7.2",
3
+ "version": "0.8.0",
4
4
  "description": "Tier0 Cloud new-architecture SDK for TypeScript and JavaScript",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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` / `NAME_IN_USE` → 换 alias;`INVALID_INDEX` → 修正索引 |
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
- | 修改 `.describe()` 文本或集合 `description` | 修改声明,然后通过 tier0 MCP 执行 `schema:replace`,请求体用 `compileCollection(declaration)` 的结果(先 `dryRun`:期望 `widened`;`narrowed` 说明存储里还有该声明之外的内容,例如其他版本的字段——合并进请求体或停止)。ensure 保留已存储的描述并报告 `ANNOTATION_NOT_APPLIED`;新字段和新集合在创建时带上描述 |
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? }`,可作为 ensure `dryRun` 或 `schema:replace` 请求体的基础。
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
 
@@ -37,6 +37,8 @@ export async function createHttpStream(
37
37
  ' const upstream = api.sources.use({ id: ' + JSON.stringify(sourceId) + ', resources: [] });',
38
38
  ' api.stream.every({ id: "tick", intervalMs: 60000 }, async () => {',
39
39
  ' const response = await upstream.request({ method: "GET", path: "inventory", query: { limit: "5000" } });',
40
+ ' if (response.status !== 200) throw new Error("Upstream inventory HTTP request failed");',
41
+ ' if (!Array.isArray(response.data)) throw new Error("Unexpected upstream inventory response format");',
40
42
  ' return response.data;',
41
43
  ' });',
42
44
  '};',
@@ -52,6 +54,10 @@ export async function createHttpStream(
52
54
 
53
55
  将 path="inventory"、query 和 60 秒周期改为业务实际值。示例保存草稿,不部署,也不将返回值自动写入 UNS;需要写入集合时按执行器能力定义 api.uns 操作。resources=[] 是空冻结资源集,不是通配授权。源地址和凭据留在 Source,不能拷进 acquisition 或脚本。
54
56
 
57
+ `upstream.request` 收到完整的 401/403/429/5xx 响应时仍返回 `{status,headers,data}`,不会仅因非 2xx 自动抛错;必须先检查状态,再校验业务数据,最后才缓冲 UNS 写入。此库存示例约定 200 和数组;其他接口按其合同调整,不能把错误响应当业务数据。脚本异常只使用固定文案,不拼接响应正文、认证头或 URL。
58
+
59
+ 运行记录与 SDK 管理请求异常是两层:创建/发布成功不代表上游成功。支持 HTTP 响应诊断的后端可在失败或未知运行中返回 `SOURCE_HTTP_401`、`stage:source_response`;401 应核对或更新上游拒绝的凭据,403 核对上游授权,429 核对采集频率。不要自动刷新凭据、扩大权限或立即重试。旧后端可能仍只返回 `RESULT_UNKNOWN`;仅修改 throw 文案不能让它保留 HTTP 状态。详见[运行诊断](read.md)。
60
+
55
61
  创建 data 为 CloudStreamDocumentView 或预检结果,实际保存返回 id、revision 和完整 document;预检不生成 ID。需要展示预检时单独调用 documents.validate({program});普通保存无需先校验再保存。当前后端创建/校验需要注册 Worker 可用。TEMPORARILY_UNAVAILABLE 不代表脚本一定错误。
56
62
 
57
63
  validate 检查配置、程序和可确定的绑定问题,不执行任意业务分支,也不证明上游可达。尤其是运行时拼接的 path/query,仍需在授权范围内执行代表性采集,并检查本次发布的 runs 与业务结果;一次成功也只覆盖实际执行的请求。Source 连接探测和 isolated-sample 都不能替代真实采集验证。
@@ -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 错误。
@@ -4,7 +4,7 @@
4
4
 
5
5
  集合 data 必须包含 name、recordSchema,可选 description、parentId、sampleSchema、indexes、semantics。parentId=null 表示根目录。索引创建后仍需确认状态为 ready。
6
6
 
7
- 目录、集合和主题使用现有 name 填写易读的真实业务名称,语言遵循用户偏好和已有术语;不要用程序 alias、随机串或 ID 代替名称,也不新增展示名称字段。工业目录优先复用已有 ISA-95 设备层级,只创建业务已知的层级;修改既有名称须有用户授权,不借整理名称改写 ID、alias、权限或移动数据。
7
+ 目录、集合和主题使用现有 name 填写易读的真实业务名称,语言遵循用户偏好和已有术语;不要用程序 alias、随机串或 ID 代替名称,不提供 displayName 或名称回退。name 为 1–63 个 Unicode 字符,无首尾空白,不含 `/` 或空字符;同目录重名时选择明确的业务名称,不自动拼接随机后缀。工业目录按[命名空间](namespace.md)指导复用已有 ISA-95 设备层级,只创建业务已知的层级,非工业业务不强套工厂结构;修改既有名称须有用户授权,不借整理名称改写 ID、alias、权限或移动数据。
8
8
 
9
9
  ```ts
10
10
  import { createClient, type Tier0Client, type UnsV2Request } from '@tier0/sdk-v2';
@@ -2,6 +2,10 @@
2
2
 
3
3
  目录、Topic、Collection 使用同一套 metadata* 方法,通过 kind 指定类型。Source/Stream 的启停、连接和发布仍使用各自专用生命周期接口;不允许把系统任务、凭据或发布历史当作普通元数据修改。
4
4
 
5
+ 目录、Collection 和 Topic 直接以 name 展示业务名称,不提供 displayName。系统引用用 ID,App 程序标识用独立 alias;名称改动不影响它们。不要用随机串、时间戳或 ID 拼接名称,也不自动用无意义后缀解决重名。
6
+
7
+ 工业目录优先按实际 ISA-95 设备层级组织:企业 → 工厂/站点 → 区域/车间 → 工作中心(产线等)→ 工作单元。先查询并复用已有目录,只建立业务已知且有意义的层级;通过 parentId 挂接,跨区域共享数据放到共同祖先。不要编造工厂设备,也不用 Level 0–4 控制功能层级替代设备层级;非工业业务按真实业务结构组织。整理时不擅自移动已有数据或更改权限。
8
+
5
9
  | 场景 | 方法 |
6
10
  | --- | --- |
7
11
  | 单条/批量新增 | metadataInsertOne / metadataBulkWrite 的 insertOne 项 |