@tier0/sdk-v2 0.9.8 → 0.9.10
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/CHANGELOG.md +16 -4
- package/README.md +18 -6
- package/dist/cjs/connection-diagnostics.d.ts +2 -0
- package/dist/cjs/connection-diagnostics.js +20 -0
- package/dist/cjs/connection-diagnostics.js.map +1 -0
- package/dist/cjs/data/error.d.ts +1 -1
- package/dist/cjs/data/error.js +2 -2
- package/dist/cjs/data/error.js.map +1 -1
- package/dist/cjs/openapi/client.js +3 -2
- package/dist/cjs/openapi/client.js.map +1 -1
- package/dist/cjs/openapi/types.generated.d.ts +1 -1
- package/dist/cjs/openapi/types.generated.js.map +1 -1
- package/dist/cjs/uns-v2/client.js +3 -2
- package/dist/cjs/uns-v2/client.js.map +1 -1
- package/dist/cjs/uns-v2/generated.d.ts +65 -19
- package/dist/cjs/uns-v2/generated.js +21 -20
- package/dist/cjs/uns-v2/generated.js.map +1 -1
- package/dist/esm/connection-diagnostics.d.ts +2 -0
- package/dist/esm/connection-diagnostics.js +17 -0
- package/dist/esm/connection-diagnostics.js.map +1 -0
- package/dist/esm/data/error.d.ts +1 -1
- package/dist/esm/data/error.js +2 -2
- package/dist/esm/data/error.js.map +1 -1
- package/dist/esm/openapi/client.js +3 -2
- package/dist/esm/openapi/client.js.map +1 -1
- package/dist/esm/openapi/types.generated.d.ts +1 -1
- package/dist/esm/openapi/types.generated.js.map +1 -1
- package/dist/esm/uns-v2/client.js +3 -2
- package/dist/esm/uns-v2/client.js.map +1 -1
- package/dist/esm/uns-v2/generated.d.ts +65 -19
- package/dist/esm/uns-v2/generated.js +21 -20
- package/dist/esm/uns-v2/generated.js.map +1 -1
- package/package.json +1 -1
- package/skills/tier0-sdk-v2/SKILL.md +2 -0
- package/skills/tier0-sdk-v2/references/local-connection.md +61 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/consistency.md +2 -2
- package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/errors.md +4 -2
- package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/modeling.md +3 -1
- package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/schema-evolution.md +2 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/SKILL.md +5 -2
- package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/delete.md +51 -19
- package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/namespace.md +17 -1
- package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/read.md +28 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/restore.md +12 -13
package/package.json
CHANGED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# 本地连接与排错
|
|
2
|
+
|
|
3
|
+
个人 Key 可用于本地脚本、数据分析和调试。SDK 与 MCP 是两种调用入口;MCP 已连接不代表本地 SDK 已配置地址或凭证。读取已有普通 collection 不要求创建或部署平台 App,也不需要 App ID。实际访问范围由个人 Key 及其所属用户的权限决定。
|
|
4
|
+
|
|
5
|
+
## 确定 API 地址
|
|
6
|
+
|
|
7
|
+
`apiHost` / `TIER0_API_HOST` 填写当前环境的 **OpenAPI 网关基础地址**,不附加 `/openapi/v2`、页面路径或 Workspace ID;SDK 自动添加接口路径。平台网页地址、MCP 地址与网关地址不能互换。
|
|
8
|
+
|
|
9
|
+
| 已知平台环境 | OpenAPI 网关基础地址 |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| 阿里云 ACK 开发环境 `https://dev.tier0.cn` | `https://api-dev.tier0.cn` |
|
|
12
|
+
|
|
13
|
+
其他环境或私有化部署:从该环境的部署/接入指南取得 OpenAPI 公网入口;若指南未提供,向环境管理员确认“对本机可达、暴露 `/openapi/v2` 的网关基础地址”。不要靠网页域名拼接或猜测地址。若没有对外暴露网关,需由管理员提供入口或网络接入方式,SDK 无法补齐部署。
|
|
14
|
+
|
|
15
|
+
私有化地址通过 `apiHost` 或环境变量显式配置,支持已有的基础路径前缀。本地 HTTP 网关需写明 `http://`,裸域名默认 HTTPS。SDK 不从 Key 推断地址,不探测候选域名、不自动切换 Host,也不跟随 API 重定向传递密钥。
|
|
16
|
+
|
|
17
|
+
## 个人 Key 读取已有集合
|
|
18
|
+
|
|
19
|
+
安装 SDK,使用 Node.js 20.19+。在本机创建 `.env.local`,将它加入 `.gitignore`,填入已经确认的网关、个人 Key 和 collection ID。密钥只留在本地受控配置或服务端,不写进代码、浏览器变量、日志或提交记录。
|
|
20
|
+
|
|
21
|
+
```dotenv
|
|
22
|
+
TIER0_API_HOST=https://api-dev.tier0.cn
|
|
23
|
+
TIER0_API_KEY=填入该环境的个人Key
|
|
24
|
+
TIER0_COLLECTION_ID=填入已有collection的ID
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
将以下代码保存为 `read-records.mjs`,按已知集合 ID 直接读取。
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { createClient } from '@tier0/sdk-v2';
|
|
31
|
+
|
|
32
|
+
const collectionId = process.env.TIER0_COLLECTION_ID;
|
|
33
|
+
if (!collectionId) throw new Error('请设置 TIER0_COLLECTION_ID');
|
|
34
|
+
const client = createClient();
|
|
35
|
+
const result = await client.uns.recordFind({ collectionId, limit: 20 });
|
|
36
|
+
console.dir(result.data, { depth: null });
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
node --env-file=.env.local read-records.mjs
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
示例只读第一页,后续按 `pageInfo` 分页;不会创建集合、修改 Schema 或写入种子数据。collection 由 MCP、页面或其他应用创建不影响此调用方式,知道 ID 不代表取得权限。长期团队应用使用独立 App 身份,避免依赖某个员工的个人 Key。
|
|
44
|
+
|
|
45
|
+
个人 Key 也可通过数据层的 `data.collection({ collectionId })` 引用已有集合、跳过 ensure(见[已有集合用法](../tier0-sdk-v2-data/references/modeling.md));`defineCollection` 声明集合的自动 ensure 需要平台签发的 App Key。
|
|
46
|
+
|
|
47
|
+
## 按响应排错
|
|
48
|
+
|
|
49
|
+
| 响应或错误 | 下一步 |
|
|
50
|
+
| --- | --- |
|
|
51
|
+
| HTML / 非 JSON 404、405 | 核对网关基础地址及 `/openapi/v2` 路由;不要先更换 Key。网页域名可能只提供前端页面。 |
|
|
52
|
+
| 401 | 先确认目标网关,再检查 Key 是否提供、是否有效、是否属于该环境;网关前的代理也可能要求认证。 |
|
|
53
|
+
| 403 | 检查当前凭证的资源/操作权限与网关访问限制;不等同于 Key 无效。 |
|
|
54
|
+
| 502、503、504 | 检查网关和上游服务健康;重发写入前确认执行结果,避免重复写入。 |
|
|
55
|
+
| 200 但返回 HTML / `SDK_PROTOCOL` | 核对网关地址、代理转发及响应格式;页面可打开不代表 OpenAPI 可用。 |
|
|
56
|
+
| 网络错误或超时 | 检查 DNS、网络接入、TLS 和服务连通性;不能仅据此判断凭证无效。 |
|
|
57
|
+
| 正常 JSON 业务错误 | 按服务端错误码、权限信息和请求 ID 排查;JSON 404 也可能表示业务资源不存在。 |
|
|
58
|
+
|
|
59
|
+
身份或 Workspace 不明确时,可单独调用 `client.system.whoami()` 核对返回的 `workspaceID`;它不是读取记录的前置步骤。无密钥请求返回 401 只证明已到达认证入口,不能证明某个 Key 有权读取目标集合。
|
|
60
|
+
|
|
61
|
+
错误提示不回显 Key、配置 URL 或原始 HTML;SDK 保留原有错误类型、HTTP 状态和执行/重试语义,不自动更换凭证或重试。
|
|
@@ -63,8 +63,8 @@ export function servingEmployee(): boolean {
|
|
|
63
63
|
|
|
64
64
|
## 首次访问与自愈
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
进程内第一次访问声明集合时一次性 ensure 全部声明:缺失的集合会被创建(随后写入种子),更宽的声明会放宽存储,存储永远不会被收窄。ensure 失败不缓存,下一次请求会重试(刚导入的 App 可能短暂返回 `APP_NOT_READY`,数据层先自动重试,仍失败时按 503 回答)。已有 ID 引用 `data.collection({ collectionId })` 跳过 ensure,可按原授权[读取回收集合历史](../../tier0-sdk-v2-uns/references/read.md#回收集合的历史读取)。
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
声明集合的调用被 `COLLECTION_NOT_FOUND` 或 `COLLECTION_NOT_GRANTED` 拒绝时,数据层重新 ensure(重建并重新写入种子),并在集合 ID 确实变化时把该调用重试一次。每个 client 每 30 秒最多重建一次;已有 ID 引用不触发重建。历史读取成功、`APP_GRANT_REVOKED` 或 `OWNER_APP_TRASHED` 均不触发自愈。延续旧集合游标的分页、`updateMany` / `deleteMany` 的续写直接失败,不会重试。
|
|
69
69
|
|
|
70
70
|
Preview(`environment: 'preview'`,`NODE_ENV` 不是 production 时的默认值)下,Schema 冲突或 ensure 警告会让请求失败并带上详情;部署环境只记录日志、继续服务。`ignored` 提示从不让请求失败:存储比声明更宽的部分记为 info,`ANNOTATION_NOT_APPLIED`(ensure 不会改写的描述)记为指向 `schema:replace` 的 warning。
|
|
@@ -80,7 +80,9 @@ export function toResponse(error: unknown): Response {
|
|
|
80
80
|
| `APP_GRANT_REVOKED` (403) | 管理员撤销了本 App 对集合的授权;ensure 不会自动恢复,请管理员处理 |
|
|
81
81
|
| `APP_QUOTA_EXCEEDED` (409) | `data.resource` 指出触达的每 App 上限(64):`collections` → 归档不用的集合;`indexes` → 从声明删索引并 `schema:replace`;`archivedCollections` → 恢复一个或请管理员清理 |
|
|
82
82
|
| `INDEX_QUOTA_EXCEEDED` (409) | Workspace 实际存在的索引已达 200;删除不用的索引 |
|
|
83
|
-
| `COLLECTION_TOO_LARGE` (409) |
|
|
83
|
+
| `COLLECTION_TOO_LARGE` (409) | 仅旧 recordsArchivedAt 墓碑恢复有 2000 条上限(`data.maxRecords`);当前 archive / restore 保留记录,不受该上限限制 |
|
|
84
|
+
| `OWNER_APP_TRASHED`(`data.reason`) | 所属 App 在回收站;恢复 App 后再写入,不触发自动重建 |
|
|
85
|
+
| `DIRECTORY_IN_TRASH`(`data.reason`) | 子项随目录回收,需恢复原目录;不逐个恢复子项 |
|
|
84
86
|
| `FORBIDDEN` (403) | Key 不能做该操作(其他 App 或共享集合),或 Key 已吊销 |
|
|
85
87
|
|
|
86
|
-
记录调用上的 `data.reason`:`COLLECTION_NOT_GRANTED`(403,授权不覆盖该集合或操作)与 `COLLECTION_NOT_FOUND`(403/404
|
|
88
|
+
记录调用上的 `data.reason`:`COLLECTION_NOT_GRANTED`(403,授权不覆盖该集合或操作)与 `COLLECTION_NOT_FOUND`(403/404,集合不存在或写入目标已回收)仅对声明集合触发[自愈](consistency.md#首次访问与自愈),已有 ID 引用直接报告错误;回收不使已有授权的历史读取失效。`APP_PERMISSION_MISSING`(403,`data.missingPermission`)表示 Key 缺少权限,例如删除记录需要 `uns.delete`——业务记录通常以状态结束(`CANCELLED`、`ARCHIVED`)而不是删除。
|
|
@@ -150,9 +150,11 @@ export const loginCodes = defineCollection({
|
|
|
150
150
|
|
|
151
151
|
- 字段声明为 `z.number().int()`,存 Unix 毫秒(通常是 `Date.now()`);记录在该时刻再过 `expireAfterSeconds`(60–31536000 秒)后被删除。删除有分钟级延迟,读取时仍要自己比较时间。
|
|
152
152
|
- 每个集合最多 1 个 TTL 索引,只能是单个 `path`、不能 `unique`。缺这个字段的记录不会过期。
|
|
153
|
-
- 过期删除是**物理删除**,不能 restore
|
|
153
|
+
- 过期删除是**物理删除**,不能 restore;集合有效时,软删除的记录同样按 TTL 清理,集合回收后停止 TTL 清理。
|
|
154
154
|
- ensure 只能新增 TTL 或把时间加长;缩短或去掉要通过 tier0 MCP 的 `schema:replace`。
|
|
155
155
|
|
|
156
|
+
集合软删除和 App 回收保留查询索引,物理索引仍占配额;归档不释放索引空间,见[回收规则](../../tier0-sdk-v2-uns/references/delete.md#回收规则)。
|
|
157
|
+
|
|
156
158
|
## 种子数据
|
|
157
159
|
|
|
158
160
|
种子是基线记录:固定 `_id`,各生命周期状态自洽,相互引用用这些 ID;每个集合最多 1000 条。只在 ensure 报告集合为 `created` 时写入一次(每 200 条一个原子分块,分块带固定幂等键,重试安全);修改 `seed` 不会改动已存在的集合。不要在服务启动或列表为空时从代码插入基线数据。构建期可以对每条种子调用 `validateDocument(definition, document)` 校验。
|
|
@@ -38,4 +38,6 @@ export function replaceBody(expectedRevision: number) {
|
|
|
38
38
|
|
|
39
39
|
Schema 中 `ignored`、`warnings`、`conflicts`、`violations` 的路径用 `.` 连接属性,用 `[]` 表示数组元素(`lines.[].qty`)。
|
|
40
40
|
|
|
41
|
+
集合 archive 的数据与授权变化见[回收规则](../../tier0-sdk-v2-uns/references/delete.md#回收规则),restore 的范围与授权区别见[恢复](../../tier0-sdk-v2-uns/references/restore.md)。
|
|
42
|
+
|
|
41
43
|
新声明的 number/integer 默认带 double/int32 格式;ensure 不改写已有字段注解,原无格式字段仍保持 legacy 显示。已有字段格式迁移需读取并保留完整 Schema,先通过 schema:replace dryRun 检查,再按明确迁移授权执行。
|
|
@@ -7,7 +7,9 @@ description: 使用 @tier0/sdk-v2 的 client.uns 完成新架构集合、Schema
|
|
|
7
7
|
|
|
8
8
|
UNS 管数据命名空间、集合、Record 和 Sample;外部连接与脚本分别用 [Source](../tier0-sdk-v2-source/SKILL.md)、[Stream](../tier0-sdk-v2-stream/SKILL.md)。服务端 `createClient()` 读取 TIER0_API_HOST / TIER0_API_KEY,或显式传 apiHost/apiKey。数据权限不自动授予管理权限:管理操作中 App 凭证只能调用本 App 集合的 appCollectionsEnsure / SchemaReplace / Archive / Restore;appDataPolicyGet/Update 的调用方必须是持有 uns.write 的登录会话、持有 uns.write 且 full_access、不绑定 App 的 personal / agent Key,或治理会话;运行时 App 凭证不能管理数据授权。
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
先按[命名空间指导](references/namespace.md)区分凭证:`namespace()` 走 OpenAPI,但当前不对 App Key 开放;App 查询可用集合走 `dataAppContext`,不能改用同样受限的 `appCollectionsList`。Agent / Personal Key 也须满足当前工作空间权限;不要从“使用 SDK / MCP”推定权限。
|
|
11
|
+
|
|
12
|
+
工业场景中,有管理权限的调用方创建目录或集合前,先查询并复用已有 ISA-95 目录,只创建业务需要且缺失的层级,用现有 `parentId` 挂接;App Key 根据上下文和已知业务范围声明集合的 `directoryPath`。不要要求 App Key 先调用管理查询,也不要按 App 重建一套工厂目录。
|
|
11
13
|
|
|
12
14
|
## 按任务加载参考
|
|
13
15
|
|
|
@@ -17,6 +19,7 @@ UNS 管数据命名空间、集合、Record 和 Sample;外部连接与脚本
|
|
|
17
19
|
| 查询集合、筛选/排序记录 | [查询](references/read.md) | dataAppContext、metadataFind/FindOne(非 App 凭证)、collectionDescribe、recordFind |
|
|
18
20
|
| 改记录、集合名称、扩展 Schema | [修改](references/update.md) | recordUpdateOne、metadataUpdateOne/Many |
|
|
19
21
|
| 删记录、软删除集合 | [删除](references/delete.md) | recordDeleteOne、metadataDeleteOne/Many |
|
|
22
|
+
| 永久删除回收站集合 | [删除](references/delete.md) | metadataPurge、jobs |
|
|
20
23
|
| 恢复集合或记录 | [恢复](references/restore.md) | metadataRestore、recordRestore |
|
|
21
24
|
| 分页、历史采样、事务、写入结果 | [数据工作流](references/data-workflows.md) | sampleFind、recordTransactionExecute |
|
|
22
25
|
| 目录、Topic 元数据及组合事务 | [命名空间](references/namespace.md) | metadata*、metadataBulkWrite、metadataTransact |
|
|
@@ -43,7 +46,7 @@ export async function readAppRecords(collectionId: string) {
|
|
|
43
46
|
1. 记录行是 id/data/revision;filter 与 update 的业务路径用 name、customer.name,不加 data.;系统路径用 _id、_meta.revision。
|
|
44
47
|
2. 改/删使用最近写回执或读取取得的 revision 作为 expectedRevision;创建时可用 returnDocument:'after' 取得快照。历史重放不是当前最新版本,冲突时显式处理;不同操作使用独立意图键,长度上限以当前操作契约为准。
|
|
45
48
|
3. metadataUpdateOne 的 $set.recordSchema 替换完整 Schema,不迁移已有记录;未更新的 sampleSchema/indexes 保留。先预检兼容性。
|
|
46
|
-
4.
|
|
49
|
+
4. metadataDeleteOne/Many 软删除保留数据与索引,历史按既有授权只读;目录原子回收有效子树,恢复只覆盖同次批次,详见[回收规则](references/delete.md#回收规则)。metadataPurge 永久清理明确 ID 的回收站集合,accepted 只表示任务已受理,须用 jobs 查询逐项结果。Record 删除会物理删除关联 Sample,恢复 Record 不会恢复这些 Sample。
|
|
47
50
|
5. recordFind 使用 pageInfo.hasMore/nextCursor,limit 1–200;sort 为 [['field',1]]。单页不是全量,跨页不是稳定快照。
|
|
48
51
|
6. Sample 的时间是 Unix 毫秒,查询范围 [from,to)。Record 代表业务实体,不用覆盖它来保存历史。
|
|
49
52
|
|
|
@@ -1,53 +1,85 @@
|
|
|
1
1
|
# 删除记录与元数据
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
记录删除使用专属数据接口,`expectedRevision` 取自已有记录或最近写回执;没有版本时再读取:
|
|
4
4
|
|
|
5
5
|
```ts
|
|
6
|
-
import { createClient, type Tier0Client } from '@tier0/sdk-v2';
|
|
6
|
+
import { createClient, type Tier0Client, type UnsV2Integer } from '@tier0/sdk-v2';
|
|
7
7
|
|
|
8
8
|
export async function deleteDevice(
|
|
9
|
-
collectionId: string, id: string,
|
|
9
|
+
collectionId: string, id: string, expectedRevision: UnsV2Integer,
|
|
10
|
+
intentKey: string, client: Tier0Client = createClient(),
|
|
10
11
|
) {
|
|
11
|
-
const current = (await client.uns.recordFind({ collectionId, filter: { _id: id }, limit: 1 })).data.items[0];
|
|
12
|
-
if (!current) throw new Error('记录不存在或不可见,未执行删除');
|
|
13
12
|
const receipt = await client.uns.recordDeleteOne({
|
|
14
|
-
collectionId, filter: { _id: id }, expectedRevision
|
|
13
|
+
collectionId, filter: { _id: id }, expectedRevision,
|
|
15
14
|
dryRun: false, idempotencyKey: intentKey,
|
|
16
15
|
});
|
|
17
16
|
if (receipt.data.status !== 'committed' || String(receipt.data.deletedCount) !== '1') {
|
|
18
17
|
return { state: 'unconfirmed' as const, receipt };
|
|
19
18
|
}
|
|
20
|
-
const after = await client.uns.recordFind({ collectionId, filter: { _id: id }, limit: 1 });
|
|
21
|
-
if (after.data.items.length) throw new Error('删除有回执,但记录仍可见或已被恢复');
|
|
22
19
|
return { state: 'committed' as const, receipt };
|
|
23
20
|
}
|
|
24
21
|
```
|
|
25
22
|
|
|
26
|
-
元数据使用 metadataDeleteOne/Many
|
|
23
|
+
元数据使用 metadataDeleteOne/Many;需要删除前对象时传 returnDocument:before。已知 revision 可直接提交,不要求重新读取或先 dryRun。
|
|
27
24
|
|
|
28
25
|
```ts
|
|
29
|
-
import { createClient, type Tier0Client } from '@tier0/sdk-v2';
|
|
26
|
+
import { createClient, type Tier0Client, type UnsV2Integer } from '@tier0/sdk-v2';
|
|
30
27
|
|
|
31
28
|
export async function deleteCollection(
|
|
32
|
-
id: string,
|
|
29
|
+
id: string, expectedRevision: UnsV2Integer,
|
|
30
|
+
intentKey: string, client: Tier0Client = createClient(),
|
|
33
31
|
) {
|
|
34
|
-
const current = (await client.uns.metadataFind({ kind: 'collection', filter: { _id: id }, limit: 1 })).data.items[0];
|
|
35
|
-
if (!current) throw new Error('集合不存在或不可见,未执行删除');
|
|
36
32
|
const receipt = await client.uns.metadataDeleteOne({
|
|
37
|
-
kind: 'collection', filter: { _id: id }, expectedRevision
|
|
33
|
+
kind: 'collection', filter: { _id: id }, expectedRevision,
|
|
38
34
|
dryRun: false, idempotencyKey: intentKey,
|
|
39
35
|
});
|
|
40
36
|
if (receipt.data.status !== 'committed' || String(receipt.data.deletedCount) !== '1') {
|
|
41
37
|
return { state: 'unconfirmed' as const, receipt };
|
|
42
38
|
}
|
|
43
|
-
|
|
44
|
-
if (!deleted.data.items[0]) throw new Error('删除有回执,但删除状态读回未确认');
|
|
45
|
-
return { state: 'deleted' as const, receipt, deleted: deleted.data.items[0] };
|
|
39
|
+
return { state: 'deleted' as const, receipt };
|
|
46
40
|
}
|
|
47
41
|
```
|
|
48
42
|
|
|
49
|
-
metadataDeleteMany 使用明确 filter 和 maxDocs
|
|
43
|
+
metadataDeleteMany 使用明确 filter 和 maxDocs。默认原子;超过上限整体拒绝。目录删除的 maxDocs 与 deletedCount 只计显式匹配项,不计隐式级联后代;服务端会去重父子项,无需逐个删除后代。需要预览时可 dryRun。state 默认 active,回收对象通过 state=deleted 查询;all 包含两种状态。
|
|
44
|
+
|
|
45
|
+
## 回收规则
|
|
46
|
+
|
|
47
|
+
软删除增加 revision,保留 Record、Sample 与查询索引;历史按既有授权只读,数据写入和后台 TTL 清理停止。外部引用不阻止回收,引用方配置保留;删除 Topic 不删除其引用的 Collection。
|
|
48
|
+
|
|
49
|
+
| 操作 | 范围与授权 |
|
|
50
|
+
| --- | --- |
|
|
51
|
+
| 元数据软删除 | 目录在同一事务内回收有效子树;此前独立回收的对象不加入本次批次。既有授权保留。 |
|
|
52
|
+
| App 集合 archive | 归档指定自有集合,撤销调用 App 对它的 grant;其他未撤销授权仍可读取历史。 |
|
|
53
|
+
| App 移入回收站 | 停用该 App,联动回收其自有集合和明确专属目录;借用集合、共享目录保留,其他活跃授权主体仍可读取历史。 |
|
|
54
|
+
|
|
55
|
+
恢复范围和授权区别见[恢复](restore.md)。保留的物理索引仍占配额,归档不释放索引空间。
|
|
50
56
|
|
|
51
57
|
业务记录使用 recordDeleteOne/Many,传 collectionId 和明确 filter,编辑型流程同时传 expectedRevision。不要把元数据软删除解释成逐条删除该集合的 Record。Sample 的删除是独立的显式时间范围操作,具体影响遵循当前数据 API 契约。
|
|
52
58
|
|
|
53
|
-
|
|
59
|
+
成功回执已确认本次操作,不再固定读回验证。结果未知时保留原意图键,按对象身份核对;需要展示最新状态时再刷新。版本冲突应重新读取并由调用方处理,不自动覆盖。
|
|
60
|
+
|
|
61
|
+
## 永久删除回收站集合
|
|
62
|
+
|
|
63
|
+
Workspace 管理调用方使用 `metadataPurge`,传 `kind:'collection'` 和 1–200 个明确 `ids`。
|
|
64
|
+
只允许回收站集合;清空时先查全分页并固定这次选中的 ID,再按批调用同一个接口。
|
|
65
|
+
不另传 `force`、`cascade`、确认 token,也不要求先清空记录或执行 dryRun。
|
|
66
|
+
运行时 App 凭证不开放此操作;权限由后端统一校验。
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { createClient, type Tier0Client } from '@tier0/sdk-v2';
|
|
70
|
+
|
|
71
|
+
export async function purgeCollections(
|
|
72
|
+
ids: string[], intentKey: string, client: Tier0Client = createClient(),
|
|
73
|
+
) {
|
|
74
|
+
const receipt = await client.uns.metadataPurge({
|
|
75
|
+
kind: 'collection', ids, idempotencyKey: intentKey,
|
|
76
|
+
});
|
|
77
|
+
return { state: 'submitted' as const, receipt };
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`status:accepted` 和 `jobId` 表示已受理,不表示已永久删除。通过 `client.uns.jobs`
|
|
82
|
+
查询任务并检查终态和逐项结果;部分失败不能报告整批成功,中断后继续查询原任务或复用原意图键。
|
|
83
|
+
永久删除包括全部 Record、Sample、集合元数据、索引以及指向集合的 App 授权;不连带删除 Topic、Source、Stream。
|
|
84
|
+
清理开始后不能恢复。索引由后台回收,不能仅凭已受理或数据清理回执声称物理空间已全部释放。
|
|
85
|
+
可选 `dryRun:true` 只返回校验结果,不创建清理任务。
|
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# 目录、Topic 与元数据事务
|
|
2
2
|
|
|
3
|
+
## 先确定凭证与查询目的
|
|
4
|
+
|
|
5
|
+
`client.uns.namespace()` 对应 `POST /openapi/v2/uns/namespace/query`,不是内部接口。SDK 保留该方法供有权限的工作空间调用方使用;SDK 方法目录不是当前凭证的授权清单。
|
|
6
|
+
|
|
7
|
+
| 凭证 | Namespace / metadata 查询 | 发现并读取集合 |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| 未绑定 App 的 Agent / Personal Key | 有当前工作空间权限及 Key scope 时可用;Agent Key 还会复核真实负责人的状态和权限 | 已知集合 ID 时直接调用 `recordFind`;需要发现集合时按工作空间权限查询元数据 |
|
|
10
|
+
| App Key | 当前不开放;`appCollectionsList` 也不能调用 | 已知集合 ID 时直接调用 `recordFind`;需要发现集合及结构时使用 `dataAppContext({})` |
|
|
11
|
+
| 平台系统 Service Key | 按实际系统凭证授权,不按“Agent 正在调用”推定权限 | 带 Builder 目标时,指定 App 操作可使用目标 App 视角;不代表所有查询都变成 App 视角 |
|
|
12
|
+
|
|
13
|
+
目录位置不产生数据授权。App 自有集合的正常创建流程提供相应授权,但读取仍受凭证权限、App 状态及数据策略约束;不能仅凭 `ownerAppId` 推定任何接口都可调用。SDK 和 MCP 使用相同凭证时仍受服务端授权约束。
|
|
14
|
+
|
|
15
|
+
收到 `403 FORBIDDEN` 且原因为 `operation is not available to App credentials` 时,先核对调用接口与服务端确认的凭证身份。这是操作准入拒绝,不能据此认定 Collection 不可读或没有数据。App 的正确流程见[读取示例](read.md);不要重试同一不允许的操作、改走内部接口或自动换成权限更大的 Key。记录 `requestId`;错误文案仅供诊断,不在程序中匹配文案作权限判断。
|
|
16
|
+
|
|
17
|
+
## 工作空间目录管理
|
|
18
|
+
|
|
3
19
|
目录、Topic、Collection 使用同一套 metadata* 方法,通过 kind 指定类型。Source/Stream 的启停、连接和发布仍使用各自专用生命周期接口;不允许把系统任务、凭据或发布历史当作普通元数据修改。
|
|
4
20
|
|
|
5
21
|
目录、Collection 和 Topic 直接以 name 展示业务名称,不提供 displayName。系统引用用 ID,App 程序标识用独立 alias;名称改动不影响它们。不要用随机串、时间戳或 ID 拼接名称,也不自动用无意义后缀解决重名。
|
|
@@ -44,4 +60,4 @@ export async function createDirectoryAndTopic(
|
|
|
44
60
|
|
|
45
61
|
transact 始终原子,结果通过 opId 对应输入;dryRun=true 校验,不预留 ID 或名称。正式执行仍重新检查。创建结果引用仅用于契约支持的关系字段,不能把任意字符串或脚本当引用。
|
|
46
62
|
|
|
47
|
-
名称、父目录、Schema、引用与权限由服务端校验。移动不改变对象 ID
|
|
63
|
+
名称、父目录、Schema、引用与权限由服务端校验。移动不改变对象 ID;删除目录原子回收有效子树,删除 Topic 不删除所引用的 Collection。外部引用不阻止回收;范围、授权和恢复规则见[删除](delete.md#回收规则)与[恢复](restore.md)。引用查询仍使用 references 专用方法。
|
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# 查询集合与记录
|
|
2
2
|
|
|
3
|
+
## App Key:从授权上下文读取
|
|
4
|
+
|
|
5
|
+
App Key 已知集合 ID 时直接调用 `recordFind`;需要发现集合时使用 `dataAppContext`,不调用 `namespace`、`metadataFind` 或 `appCollectionsList`。上下文包含可访问集合的名称、目录路径、Schema 和有效权限;按已配置 alias 精确定位,不能随意选择列表第一项。
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { createClient, type Tier0Client } from '@tier0/sdk-v2';
|
|
9
|
+
|
|
10
|
+
// 在服务端使用 App Key;alias 是应用配置中的稳定集合别名。
|
|
11
|
+
export async function readAppRecords(alias: string, client: Tier0Client = createClient()) {
|
|
12
|
+
const context = await client.uns.dataAppContext({});
|
|
13
|
+
const collection = context.data.collections.find(item => item.alias === alias);
|
|
14
|
+
if (!collection || !collection.permissions.includes('record:read')) {
|
|
15
|
+
throw new Error('当前 App 上下文没有该别名的可读集合,请核对别名、授权和凭证');
|
|
16
|
+
}
|
|
17
|
+
return client.uns.recordFind({ collectionId: collection.collectionId, limit: 20 });
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
未找到别名不等于集合不存在;共享授权可能没有 alias,应使用明确配置的集合 ID。上下文只是发现结果,后续请求仍由服务端重新检查权限。网络或权限错误应传播,不转换成“零条记录”。此例只读取第一页,后续按返回的 `pageInfo` 继续查询。
|
|
22
|
+
|
|
23
|
+
## 工作空间凭证:查询元数据与记录
|
|
24
|
+
|
|
3
25
|
| 方法 | 必要输入 | 返回 data |
|
|
4
26
|
| --- | --- | --- |
|
|
5
27
|
| metadataFind | kinds;可选 filter、sort、projection、page、state | items、pageInfo |
|
|
@@ -33,3 +55,9 @@ export async function listCollections(page = 1, client: Tier0Client = createClie
|
|
|
33
55
|
按 pageInfo.hasMore/nextPage 继续元数据分页;不要把第一页当全量。recordFind 的游标循环见[数据工作流](data-workflows.md)。sort 是数组元组。
|
|
34
56
|
|
|
35
57
|
以上 metadataFind 示例用于 Workspace 集成(会话、personal / agent Key)。App 凭证不能调用 metadataFind:用 dataAppContext 精确选择授权 alias,集合的 revision、indexStatus、ttl 也在其中。数据授权不等于元数据管理授权。网络/权限错误必须传播,不能用空数组掩盖错误。
|
|
58
|
+
|
|
59
|
+
## 回收集合的历史读取
|
|
60
|
+
|
|
61
|
+
按原 collectionId 读取 Record、Sample、count 或 aggregate;既有授权、员工行规则和字段权限仍生效。活跃 Topic 可继续读取其引用的回收集合。活跃 App 的 dataAppContext 保留仍可读的回收集合,但不授予新增能力;回收中的 App 自身不能运行。
|
|
62
|
+
|
|
63
|
+
已知 ID 直接读取,不必先恢复集合或查询元数据。数据层也可用 `data.collection({ collectionId })`,不会 ensure、写种子或补授权。只有 Workspace 集成确需回收对象定义时,才用 metadataFind 的 state=deleted 或 all;App Key 仍不调用该接口。[回收规则](delete.md#回收规则)说明写入和恢复边界。
|
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
# 恢复元数据与记录
|
|
2
2
|
|
|
3
|
-
metadataRestore 接收 kind、items[{id,expectedRevision?}]
|
|
3
|
+
metadataRestore 接收 kind、items[{id,expectedRevision?}]。编辑流程复用回收列表中的 revision,没有版本时再用 state=deleted 读取。恢复由服务端校验名称、父目录、Schema 与引用,冲突时原子批次回滚。
|
|
4
|
+
|
|
5
|
+
恢复目录会一并恢复同次回收的子树,此前独立回收的对象保持原状态。随目录回收的子项应通过原目录恢复;单独恢复返回 CONDITION_FAILED / DIRECTORY_IN_TRASH。无需枚举子项逐个恢复;若请求同时列出父子项,服务端仍检查每项 expectedRevision。
|
|
6
|
+
|
|
7
|
+
随 App 回收的对象通过恢复原 App 处理,此前独立回收的对象保持原状态;OWNER_APP_TRASHED 不应触发重建或写入重试。通用元数据恢复不补回撤销的 grant;App 集合 restore 则恢复该 alias 最近归档的集合并重新授权。此前独立删除的记录不会随集合恢复。当前 archive / restore 不逐条重写记录;2000 条上限仅适用于旧 recordsArchivedAt 墓碑恢复。
|
|
4
8
|
|
|
5
9
|
```ts
|
|
6
|
-
import { createClient, type Tier0Client } from '@tier0/sdk-v2';
|
|
10
|
+
import { createClient, type Tier0Client, type UnsV2Integer } from '@tier0/sdk-v2';
|
|
7
11
|
|
|
8
12
|
export async function restoreCollection(
|
|
9
|
-
id: string,
|
|
13
|
+
id: string, expectedRevision: UnsV2Integer,
|
|
14
|
+
intentKey: string, client: Tier0Client = createClient(),
|
|
10
15
|
) {
|
|
11
|
-
const deleted = (await client.uns.metadataFind({ kind: 'collection', filter: { _id: id }, state: 'deleted', limit: 1 })).data.items[0];
|
|
12
|
-
if (!deleted) throw new Error('未找到待恢复集合');
|
|
13
16
|
const receipt = await client.uns.metadataRestore({
|
|
14
|
-
kind: 'collection', items: [{ id, expectedRevision
|
|
17
|
+
kind: 'collection', items: [{ id, expectedRevision }], idempotencyKey: intentKey,
|
|
15
18
|
});
|
|
16
19
|
if (receipt.data.status !== 'committed' || String(receipt.data.restoredCount) !== '1') {
|
|
17
20
|
return { state: 'unconfirmed' as const, receipt };
|
|
18
21
|
}
|
|
19
|
-
|
|
20
|
-
if (!current) throw new Error('恢复有回执,读回未确认');
|
|
21
|
-
return { state: 'committed' as const, receipt, current };
|
|
22
|
+
return { state: 'committed' as const, receipt };
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export async function restoreDevice(
|
|
@@ -30,10 +31,8 @@ export async function restoreDevice(
|
|
|
30
31
|
if (receipt.data.status !== 'committed' || !receipt.data.restoredIds.includes(id)) {
|
|
31
32
|
return { state: 'unconfirmed' as const, receipt };
|
|
32
33
|
}
|
|
33
|
-
|
|
34
|
-
if (!found.data.items[0]) throw new Error('恢复有回执,读回未确认');
|
|
35
|
-
return { state: 'committed' as const, receipt, current: found.data.items[0] };
|
|
34
|
+
return { state: 'committed' as const, receipt };
|
|
36
35
|
}
|
|
37
36
|
```
|
|
38
37
|
|
|
39
|
-
Record 恢复使用 recordRestore 的 ids(最多 100 条),重新校验当前 Schema 和有效唯一索引,冲突整批回滚。新增同 ID 或 upsert
|
|
38
|
+
Record 恢复使用 recordRestore 的 ids(最多 100 条),重新校验当前 Schema 和有效唯一索引,冲突整批回滚。新增同 ID 或 upsert 不会隐式恢复对象。幂等键每个新意图独立;结果未知时核对原对象,正常成功无需额外读回。需要最新快照时,元数据恢复可使用回执 items,记录恢复可按需查询。
|