@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.
Files changed (298) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +7 -0
  3. package/README.md +201 -2
  4. package/dist/cjs/app-identity-contract.generated.d.ts +22 -0
  5. package/dist/cjs/app-identity-contract.generated.js +51 -0
  6. package/dist/cjs/app-identity-contract.generated.js.map +1 -0
  7. package/dist/cjs/capabilities.d.ts +54 -0
  8. package/dist/cjs/capabilities.js +134 -0
  9. package/dist/cjs/capabilities.js.map +1 -0
  10. package/dist/cjs/client.d.ts +604 -0
  11. package/dist/cjs/client.js +35 -0
  12. package/dist/cjs/client.js.map +1 -0
  13. package/dist/cjs/cloud-server.d.ts +31 -0
  14. package/dist/cjs/cloud-server.js +114 -0
  15. package/dist/cjs/cloud-server.js.map +1 -0
  16. package/dist/cjs/cloud-transport.d.ts +46 -0
  17. package/dist/cjs/cloud-transport.js +192 -0
  18. package/dist/cjs/cloud-transport.js.map +1 -0
  19. package/dist/cjs/controls.d.ts +93 -0
  20. package/dist/cjs/controls.js +169 -0
  21. package/dist/cjs/controls.js.map +1 -0
  22. package/dist/cjs/data/bootstrap.d.ts +66 -0
  23. package/dist/cjs/data/bootstrap.js +282 -0
  24. package/dist/cjs/data/bootstrap.js.map +1 -0
  25. package/dist/cjs/data/call.d.ts +16 -0
  26. package/dist/cjs/data/call.js +51 -0
  27. package/dist/cjs/data/call.js.map +1 -0
  28. package/dist/cjs/data/client.d.ts +92 -0
  29. package/dist/cjs/data/client.js +102 -0
  30. package/dist/cjs/data/client.js.map +1 -0
  31. package/dist/cjs/data/collection.d.ts +238 -0
  32. package/dist/cjs/data/collection.js +441 -0
  33. package/dist/cjs/data/collection.js.map +1 -0
  34. package/dist/cjs/data/context.d.ts +10 -0
  35. package/dist/cjs/data/context.js +39 -0
  36. package/dist/cjs/data/context.js.map +1 -0
  37. package/dist/cjs/data/declarations.d.ts +11 -0
  38. package/dist/cjs/data/declarations.js +18 -0
  39. package/dist/cjs/data/declarations.js.map +1 -0
  40. package/dist/cjs/data/define.d.ts +69 -0
  41. package/dist/cjs/data/define.js +31 -0
  42. package/dist/cjs/data/define.js.map +1 -0
  43. package/dist/cjs/data/error.d.ts +60 -0
  44. package/dist/cjs/data/error.js +184 -0
  45. package/dist/cjs/data/error.js.map +1 -0
  46. package/dist/cjs/data/index.d.ts +21 -0
  47. package/dist/cjs/data/index.js +43 -0
  48. package/dist/cjs/data/index.js.map +1 -0
  49. package/dist/cjs/data/schema.d.ts +82 -0
  50. package/dist/cjs/data/schema.js +607 -0
  51. package/dist/cjs/data/schema.js.map +1 -0
  52. package/dist/cjs/data/sort.d.ts +8 -0
  53. package/dist/cjs/data/sort.js +21 -0
  54. package/dist/cjs/data/sort.js.map +1 -0
  55. package/dist/cjs/files.d.ts +67 -0
  56. package/dist/cjs/files.js +119 -0
  57. package/dist/cjs/files.js.map +1 -0
  58. package/dist/cjs/framework-scope.d.ts +1 -0
  59. package/dist/cjs/framework-scope.js +14 -0
  60. package/dist/cjs/framework-scope.js.map +1 -0
  61. package/dist/cjs/index.d.ts +14 -0
  62. package/dist/cjs/index.js +33 -0
  63. package/dist/cjs/index.js.map +1 -0
  64. package/dist/cjs/mq/broker.d.ts +65 -0
  65. package/dist/cjs/mq/broker.js +115 -0
  66. package/dist/cjs/mq/broker.js.map +1 -0
  67. package/dist/cjs/mq/client.d.ts +37 -0
  68. package/dist/cjs/mq/client.js +321 -0
  69. package/dist/cjs/mq/client.js.map +1 -0
  70. package/dist/cjs/mq/index.d.ts +5 -0
  71. package/dist/cjs/mq/index.js +11 -0
  72. package/dist/cjs/mq/index.js.map +1 -0
  73. package/dist/cjs/mq/types.d.ts +48 -0
  74. package/dist/cjs/mq/types.js +3 -0
  75. package/dist/cjs/mq/types.js.map +1 -0
  76. package/dist/cjs/openapi/api.generated.d.ts +820 -0
  77. package/dist/cjs/openapi/api.generated.js +607 -0
  78. package/dist/cjs/openapi/api.generated.js.map +1 -0
  79. package/dist/cjs/openapi/client.d.ts +56 -0
  80. package/dist/cjs/openapi/client.js +219 -0
  81. package/dist/cjs/openapi/client.js.map +1 -0
  82. package/dist/cjs/openapi/codecs.generated.d.ts +2234 -0
  83. package/dist/cjs/openapi/codecs.generated.js +6 -0
  84. package/dist/cjs/openapi/codecs.generated.js.map +1 -0
  85. package/dist/cjs/openapi/index.d.ts +5 -0
  86. package/dist/cjs/openapi/index.js +12 -0
  87. package/dist/cjs/openapi/index.js.map +1 -0
  88. package/dist/cjs/openapi/types.generated.d.ts +3227 -0
  89. package/dist/cjs/openapi/types.generated.js +3 -0
  90. package/dist/cjs/openapi/types.generated.js.map +1 -0
  91. package/dist/cjs/package.json +1 -0
  92. package/dist/cjs/react.d.ts +9 -0
  93. package/dist/cjs/react.js +16 -0
  94. package/dist/cjs/react.js.map +1 -0
  95. package/dist/cjs/runtime-context.d.ts +9 -0
  96. package/dist/cjs/runtime-context.js +25 -0
  97. package/dist/cjs/runtime-context.js.map +1 -0
  98. package/dist/cjs/runtime-env.d.ts +1 -0
  99. package/dist/cjs/runtime-env.js +18 -0
  100. package/dist/cjs/runtime-env.js.map +1 -0
  101. package/dist/cjs/source.d.ts +39 -0
  102. package/dist/cjs/source.js +29 -0
  103. package/dist/cjs/source.js.map +1 -0
  104. package/dist/cjs/stream.d.ts +40 -0
  105. package/dist/cjs/stream.js +33 -0
  106. package/dist/cjs/stream.js.map +1 -0
  107. package/dist/cjs/uns-v2/client.d.ts +54 -0
  108. package/dist/cjs/uns-v2/client.js +177 -0
  109. package/dist/cjs/uns-v2/client.js.map +1 -0
  110. package/dist/cjs/uns-v2/codec.d.ts +11 -0
  111. package/dist/cjs/uns-v2/codec.js +290 -0
  112. package/dist/cjs/uns-v2/codec.js.map +1 -0
  113. package/dist/cjs/uns-v2/data-client.d.ts +10 -0
  114. package/dist/cjs/uns-v2/data-client.js +18 -0
  115. package/dist/cjs/uns-v2/data-client.js.map +1 -0
  116. package/dist/cjs/uns-v2/generated.d.ts +15551 -0
  117. package/dist/cjs/uns-v2/generated.js +139 -0
  118. package/dist/cjs/uns-v2/generated.js.map +1 -0
  119. package/dist/cjs/uns-v2/index.d.ts +5 -0
  120. package/dist/cjs/uns-v2/index.js +24 -0
  121. package/dist/cjs/uns-v2/index.js.map +1 -0
  122. package/dist/cjs/vue.d.ts +9 -0
  123. package/dist/cjs/vue.js +39 -0
  124. package/dist/cjs/vue.js.map +1 -0
  125. package/dist/esm/app-identity-contract.generated.d.ts +22 -0
  126. package/dist/esm/app-identity-contract.generated.js +48 -0
  127. package/dist/esm/app-identity-contract.generated.js.map +1 -0
  128. package/dist/esm/capabilities.d.ts +54 -0
  129. package/dist/esm/capabilities.js +129 -0
  130. package/dist/esm/capabilities.js.map +1 -0
  131. package/dist/esm/client.d.ts +604 -0
  132. package/dist/esm/client.js +32 -0
  133. package/dist/esm/client.js.map +1 -0
  134. package/dist/esm/cloud-server.d.ts +31 -0
  135. package/dist/esm/cloud-server.js +109 -0
  136. package/dist/esm/cloud-server.js.map +1 -0
  137. package/dist/esm/cloud-transport.d.ts +46 -0
  138. package/dist/esm/cloud-transport.js +187 -0
  139. package/dist/esm/cloud-transport.js.map +1 -0
  140. package/dist/esm/controls.d.ts +93 -0
  141. package/dist/esm/controls.js +163 -0
  142. package/dist/esm/controls.js.map +1 -0
  143. package/dist/esm/data/bootstrap.d.ts +66 -0
  144. package/dist/esm/data/bootstrap.js +277 -0
  145. package/dist/esm/data/bootstrap.js.map +1 -0
  146. package/dist/esm/data/call.d.ts +16 -0
  147. package/dist/esm/data/call.js +47 -0
  148. package/dist/esm/data/call.js.map +1 -0
  149. package/dist/esm/data/client.d.ts +92 -0
  150. package/dist/esm/data/client.js +99 -0
  151. package/dist/esm/data/client.js.map +1 -0
  152. package/dist/esm/data/collection.d.ts +238 -0
  153. package/dist/esm/data/collection.js +434 -0
  154. package/dist/esm/data/collection.js.map +1 -0
  155. package/dist/esm/data/context.d.ts +10 -0
  156. package/dist/esm/data/context.js +34 -0
  157. package/dist/esm/data/context.js.map +1 -0
  158. package/dist/esm/data/declarations.d.ts +11 -0
  159. package/dist/esm/data/declarations.js +10 -0
  160. package/dist/esm/data/declarations.js.map +1 -0
  161. package/dist/esm/data/define.d.ts +69 -0
  162. package/dist/esm/data/define.js +27 -0
  163. package/dist/esm/data/define.js.map +1 -0
  164. package/dist/esm/data/error.d.ts +60 -0
  165. package/dist/esm/data/error.js +178 -0
  166. package/dist/esm/data/error.js.map +1 -0
  167. package/dist/esm/data/index.d.ts +21 -0
  168. package/dist/esm/data/index.js +18 -0
  169. package/dist/esm/data/index.js.map +1 -0
  170. package/dist/esm/data/schema.d.ts +82 -0
  171. package/dist/esm/data/schema.js +596 -0
  172. package/dist/esm/data/schema.js.map +1 -0
  173. package/dist/esm/data/sort.d.ts +8 -0
  174. package/dist/esm/data/sort.js +18 -0
  175. package/dist/esm/data/sort.js.map +1 -0
  176. package/dist/esm/files.d.ts +67 -0
  177. package/dist/esm/files.js +113 -0
  178. package/dist/esm/files.js.map +1 -0
  179. package/dist/esm/framework-scope.d.ts +1 -0
  180. package/dist/esm/framework-scope.js +11 -0
  181. package/dist/esm/framework-scope.js.map +1 -0
  182. package/dist/esm/index.d.ts +14 -0
  183. package/dist/esm/index.js +10 -0
  184. package/dist/esm/index.js.map +1 -0
  185. package/dist/esm/mq/broker.d.ts +65 -0
  186. package/dist/esm/mq/broker.js +109 -0
  187. package/dist/esm/mq/broker.js.map +1 -0
  188. package/dist/esm/mq/client.d.ts +37 -0
  189. package/dist/esm/mq/client.js +314 -0
  190. package/dist/esm/mq/client.js.map +1 -0
  191. package/dist/esm/mq/index.d.ts +5 -0
  192. package/dist/esm/mq/index.js +3 -0
  193. package/dist/esm/mq/index.js.map +1 -0
  194. package/dist/esm/mq/types.d.ts +48 -0
  195. package/dist/esm/mq/types.js +2 -0
  196. package/dist/esm/mq/types.js.map +1 -0
  197. package/dist/esm/openapi/api.generated.d.ts +820 -0
  198. package/dist/esm/openapi/api.generated.js +603 -0
  199. package/dist/esm/openapi/api.generated.js.map +1 -0
  200. package/dist/esm/openapi/client.d.ts +56 -0
  201. package/dist/esm/openapi/client.js +210 -0
  202. package/dist/esm/openapi/client.js.map +1 -0
  203. package/dist/esm/openapi/codecs.generated.d.ts +2234 -0
  204. package/dist/esm/openapi/codecs.generated.js +3 -0
  205. package/dist/esm/openapi/codecs.generated.js.map +1 -0
  206. package/dist/esm/openapi/index.d.ts +5 -0
  207. package/dist/esm/openapi/index.js +3 -0
  208. package/dist/esm/openapi/index.js.map +1 -0
  209. package/dist/esm/openapi/types.generated.d.ts +3227 -0
  210. package/dist/esm/openapi/types.generated.js +2 -0
  211. package/dist/esm/openapi/types.generated.js.map +1 -0
  212. package/dist/esm/package.json +1 -0
  213. package/dist/esm/react.d.ts +9 -0
  214. package/dist/esm/react.js +12 -0
  215. package/dist/esm/react.js.map +1 -0
  216. package/dist/esm/runtime-context.d.ts +9 -0
  217. package/dist/esm/runtime-context.js +22 -0
  218. package/dist/esm/runtime-context.js.map +1 -0
  219. package/dist/esm/runtime-env.d.ts +1 -0
  220. package/dist/esm/runtime-env.js +15 -0
  221. package/dist/esm/runtime-env.js.map +1 -0
  222. package/dist/esm/source.d.ts +39 -0
  223. package/dist/esm/source.js +25 -0
  224. package/dist/esm/source.js.map +1 -0
  225. package/dist/esm/stream.d.ts +40 -0
  226. package/dist/esm/stream.js +29 -0
  227. package/dist/esm/stream.js.map +1 -0
  228. package/dist/esm/uns-v2/client.d.ts +54 -0
  229. package/dist/esm/uns-v2/client.js +170 -0
  230. package/dist/esm/uns-v2/client.js.map +1 -0
  231. package/dist/esm/uns-v2/codec.d.ts +11 -0
  232. package/dist/esm/uns-v2/codec.js +284 -0
  233. package/dist/esm/uns-v2/codec.js.map +1 -0
  234. package/dist/esm/uns-v2/data-client.d.ts +10 -0
  235. package/dist/esm/uns-v2/data-client.js +14 -0
  236. package/dist/esm/uns-v2/data-client.js.map +1 -0
  237. package/dist/esm/uns-v2/generated.d.ts +15551 -0
  238. package/dist/esm/uns-v2/generated.js +136 -0
  239. package/dist/esm/uns-v2/generated.js.map +1 -0
  240. package/dist/esm/uns-v2/index.d.ts +5 -0
  241. package/dist/esm/uns-v2/index.js +5 -0
  242. package/dist/esm/uns-v2/index.js.map +1 -0
  243. package/dist/esm/vue.d.ts +9 -0
  244. package/dist/esm/vue.js +36 -0
  245. package/dist/esm/vue.js.map +1 -0
  246. package/docs/acceptance-2026-10-22.md +88 -0
  247. package/package.json +242 -4
  248. package/skills/tier0-sdk-v2/SKILL.md +66 -0
  249. package/skills/tier0-sdk-v2/references/integration.md +72 -0
  250. package/skills/tier0-sdk-v2-agent/SKILL.md +73 -0
  251. package/skills/tier0-sdk-v2-agent/references/results.md +45 -0
  252. package/skills/tier0-sdk-v2-controls/SKILL.md +63 -0
  253. package/skills/tier0-sdk-v2-data/SKILL.md +57 -0
  254. package/skills/tier0-sdk-v2-data/references/consistency.md +70 -0
  255. package/skills/tier0-sdk-v2-data/references/errors.md +69 -0
  256. package/skills/tier0-sdk-v2-data/references/modeling.md +77 -0
  257. package/skills/tier0-sdk-v2-data/references/read-write.md +72 -0
  258. package/skills/tier0-sdk-v2-data/references/schema-evolution.md +36 -0
  259. package/skills/tier0-sdk-v2-files/SKILL.md +85 -0
  260. package/skills/tier0-sdk-v2-files/references/delete.md +22 -0
  261. package/skills/tier0-sdk-v2-files/references/download.md +49 -0
  262. package/skills/tier0-sdk-v2-files/references/multipart.md +42 -0
  263. package/skills/tier0-sdk-v2-files/references/upload.md +27 -0
  264. package/skills/tier0-sdk-v2-files/references/url.md +22 -0
  265. package/skills/tier0-sdk-v2-members/SKILL.md +56 -0
  266. package/skills/tier0-sdk-v2-members/references/app-members.md +41 -0
  267. package/skills/tier0-sdk-v2-members/references/platform-members.md +56 -0
  268. package/skills/tier0-sdk-v2-mq/SKILL.md +78 -0
  269. package/skills/tier0-sdk-v2-mq/references/quickstart.md +69 -0
  270. package/skills/tier0-sdk-v2-notifications/SKILL.md +42 -0
  271. package/skills/tier0-sdk-v2-notifications/references/get.md +34 -0
  272. package/skills/tier0-sdk-v2-notifications/references/send.md +72 -0
  273. package/skills/tier0-sdk-v2-source/SKILL.md +38 -0
  274. package/skills/tier0-sdk-v2-source/references/changes.md +40 -0
  275. package/skills/tier0-sdk-v2-source/references/create.md +63 -0
  276. package/skills/tier0-sdk-v2-source/references/delete.md +47 -0
  277. package/skills/tier0-sdk-v2-source/references/protocols.md +36 -0
  278. package/skills/tier0-sdk-v2-source/references/read.md +34 -0
  279. package/skills/tier0-sdk-v2-source/references/state.md +37 -0
  280. package/skills/tier0-sdk-v2-source/references/update.md +61 -0
  281. package/skills/tier0-sdk-v2-stream/SKILL.md +43 -0
  282. package/skills/tier0-sdk-v2-stream/references/create.md +55 -0
  283. package/skills/tier0-sdk-v2-stream/references/delete.md +35 -0
  284. package/skills/tier0-sdk-v2-stream/references/deploy.md +63 -0
  285. package/skills/tier0-sdk-v2-stream/references/lifecycle.md +25 -0
  286. package/skills/tier0-sdk-v2-stream/references/read.md +35 -0
  287. package/skills/tier0-sdk-v2-stream/references/update.md +35 -0
  288. package/skills/tier0-sdk-v2-system/SKILL.md +64 -0
  289. package/skills/tier0-sdk-v2-system/references/info.md +23 -0
  290. package/skills/tier0-sdk-v2-system/references/whoami.md +21 -0
  291. package/skills/tier0-sdk-v2-uns/SKILL.md +53 -0
  292. package/skills/tier0-sdk-v2-uns/references/create.md +85 -0
  293. package/skills/tier0-sdk-v2-uns/references/data-workflows.md +73 -0
  294. package/skills/tier0-sdk-v2-uns/references/delete.md +69 -0
  295. package/skills/tier0-sdk-v2-uns/references/namespace.md +75 -0
  296. package/skills/tier0-sdk-v2-uns/references/read.md +32 -0
  297. package/skills/tier0-sdk-v2-uns/references/restore.md +41 -0
  298. package/skills/tier0-sdk-v2-uns/references/update.md +86 -0
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: tier0-sdk-v2-system
3
+ description: 使用 @tier0/sdk-v2 查询 Tier0 凭证身份、平台版本、MQTT Broker 与能力就绪状态,区分身份、授权、发布开关和实际可用性。
4
+ ---
5
+
6
+ # 系统与能力发现
7
+
8
+ | 任务 | 参考 |
9
+ | --- | --- |
10
+ | 诊断 Key 所属 Workspace 和权限 | [whoami](references/whoami.md) |
11
+ | 查看平台版本、解析 Broker 地址 | [info](references/info.md) |
12
+
13
+ v1 的内部 reload 接口不属于 v2 公共 SDK,不能在普通诊断时调用。
14
+
15
+ 使用服务端 `createClient()` 或显式传入 apiHost/apiKey。系统排查从只读信息开始,不因功能不可用自动创建 Key、修改权限或部署服务。
16
+
17
+ ## 最小诊断
18
+
19
+ ```ts
20
+ import { createClient } from '@tier0/sdk-v2';
21
+
22
+ export async function diagnose() {
23
+ const client = createClient();
24
+ const identity = await client.system.whoami();
25
+ const info = await client.system.info();
26
+ const discovery = await client.capabilities.discover();
27
+ return {
28
+ workspaceId: String(identity.workspaceID),
29
+ keyType: identity.keyType,
30
+ platformVersion: info.version,
31
+ capabilities: discovery.capabilities.map(item => ({
32
+ code: item.code, state: item.state, allowed: item.allowed,
33
+ availability: item.availability,
34
+ })),
35
+ };
36
+ }
37
+ ```
38
+
39
+ whoami 返回 Key 关联的主体与 Workspace,包括 userID、workspaceID、permissions、roles、keyType;它不是当前浏览器员工的身份证明。不要将关联 userID 当作 App 请求的当前访问者。
40
+
41
+ info 返回 name/version/mqttBroker/capabilities,字段可能省略。MQTT broker 地址先用 `toWebSocketUrl` 转换,详见 [MQTT](../tier0-sdk-v2-mq/SKILL.md)。info 中的 capabilities 字符串列表不替代完整能力发现。
42
+
43
+ ## 读懂能力结果
44
+
45
+ | 字段/状态 | 判断 |
46
+ | --- | --- |
47
+ | releaseEnabled | 平台/Workspace 发布开关,不代表运行时已准备好 |
48
+ | allowed | 当前身份的授权摘要,不是所有具体 Control 的执行许可 |
49
+ | enabled、state=ready | 服务端评估就绪 |
50
+ | availability=available | 能力可用性,不是设备在线、业务执行或 ACK |
51
+ | disabled / not_supported / unavailable | 展示具体不可用原因,不强制调用 |
52
+ | readiness_unknown | 保留未知状态,不能按成功处理 |
53
+
54
+ 能力目录当前识别 namespace.v2、source.v1、stream.v2、control.v1、control.invoke.v1。contractVersions、apiContractVersions、sdkContractVersions 是契约标签,不是 npm 包版本;productVersion 未配置时可缺省。
55
+
56
+ ## 定位问题
57
+
58
+ - 401/403:核对目标 Host、Key 对应 Workspace 和所需权限;不输出完整 Key。
59
+ - 方法存在但能力关闭:记录 code/state/availability,检查该环境的部署与配置,不能凭本地生成文件推断线上已支持。
60
+ - `CapabilityError`:保留 status/errorCode;不支持的契约版本与错误响应格式需要排查服务端/SDK 配套版本。
61
+ - 系统普通 API 使用 `ApiError`,HTTP 200 的业务失败仍会抛错。
62
+ - 仅上报排查需要的字段;whoami 中的邮箱、角色和 Key 前缀不应无差别返回给浏览器。
63
+
64
+ 需要 Source/Stream 运行健康信息时,进一步使用对应领域的 observations/runs;系统能力就绪只说明可以进入该领域流程。
@@ -0,0 +1,23 @@
1
+ # 服务信息与 MQTT 地址
2
+
3
+ 方法:`system.info()`;POST /openapi/v1/info,无业务参数。
4
+
5
+ ```ts
6
+ import { createClient, type Tier0Client } from '@tier0/sdk-v2';
7
+ import { parseMqttBroker, toWebSocketUrl } from '@tier0/sdk-v2/mq';
8
+
9
+ export async function inspectService(client: Tier0Client = createClient()) {
10
+ const info = await client.system.info();
11
+ const endpoint = parseMqttBroker(info.mqttBroker);
12
+ return {
13
+ name: info.name, version: info.version, capabilities: info.capabilities,
14
+ mqtt: endpoint && info.mqttBroker ? toWebSocketUrl(info.mqttBroker) : undefined,
15
+ };
16
+ }
17
+ ```
18
+
19
+ 返回已解包对象,name/version/capabilities/mqttBroker 均可能省略。mqttBroker 是连接串,可能裸 host、host:port、tcp://host:1883 或完整 ws/wss,不能直接拼 wss:// 前缀。toWebSocketUrl 保留已有 WebSocket URL;TCP 端口不直接复用为 WebSocket 端口。实际端口/路径由环境决定。
20
+
21
+ info.capabilities 的简略字符串列表不是就绪证明。进一步读取 client.capabilities.discover 的 releaseEnabled、allowed、state、availability,区分开关、授权与运行能力;详见[系统 Skill](../SKILL.md)。
22
+
23
+ v1 的 /gw/reload 是内部管理操作,未纳入 v2 公共 SDK,不复制旧 reload 示例,也不在连通性诊断中尝试重载服务。
@@ -0,0 +1,21 @@
1
+ # API Key 身份诊断
2
+
3
+ 方法:`system.whoami()`;POST /openapi/v1/auth/whoami,无业务参数。不是调用其他 API 的强制前置步骤。
4
+
5
+ ```ts
6
+ import { createClient, type Tier0Client } from '@tier0/sdk-v2';
7
+
8
+ export async function inspectCredential(client: Tier0Client = createClient()) {
9
+ const identity = await client.system.whoami();
10
+ return {
11
+ workspaceId: String(identity.workspaceID), workspaceName: identity.workspaceName,
12
+ keyType: identity.keyType, permissions: identity.permissions, roles: identity.roles,
13
+ };
14
+ }
15
+ ```
16
+
17
+ 返回已解包的 userID/userName/email/workspaceID/workspaceName/apiKeyName/keyPrefix/permissions/roles/keyType。v2 对大整数保留 number|bigint,不能套用 v1 “一定已经丢精度”的描述;需要字符串时用 String,不先转 Number。
18
+
19
+ App Key 是共享业务凭证,不代表访问 App 的自然人。whoami 不能用于登录、当前用户、操作人审计或选择通知收件人;当前人来自应用认证会话,收件人从成员数据解析。
20
+
21
+ 401 核对 Host/Key,403 核对权限;不要输出完整 Key、无差别展示邮箱或所有角色,也不自动换更高权限身份。
@@ -0,0 +1,53 @@
1
+ ---
2
+ name: tier0-sdk-v2-uns
3
+ description: 使用 @tier0/sdk-v2 的 client.uns 完成新架构集合、Schema、目录、Topic、Record 增删改查、归档恢复、采样历史与事务,区分 App 和 Workspace 数据权限。
4
+ ---
5
+
6
+ # UNS 数据操作
7
+
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
+
10
+ ## 按任务加载参考
11
+
12
+ | 用户任务 | 参考 | 方法 |
13
+ | --- | --- | --- |
14
+ | 创建集合、定义 Schema、新增记录 | [创建](references/create.md) | collectionCreate、recordInsertOne |
15
+ | 查询集合、筛选/排序记录 | [查询](references/read.md) | collectionGet/List/Describe、recordFind |
16
+ | 改记录、集合名称、扩展 Schema | [修改](references/update.md) | recordUpdateOne、collectionUpdate/SchemaUpdate |
17
+ | 删记录、归档集合 | [删除](references/delete.md) | recordDeleteOne、collectionArchive |
18
+ | 恢复集合或记录 | [恢复](references/restore.md) | collectionRestore、recordRestore |
19
+ | 分页、历史采样、事务、变更轮询 | [数据工作流](references/data-workflows.md) | sampleFind、recordTransactionExecute、recordChangesPoll |
20
+ | 目录与 Topic 元数据 | [命名空间](references/namespace.md) | directory*、topic* |
21
+
22
+ ## 找到正确数据上下文
23
+
24
+ 托管 App 使用 dataAppContext,按业务 alias 精确选择授权集合;Workspace 集成用 dataDiscover/collectionList,不强制 App 上下文。
25
+
26
+ ```ts
27
+ import { createClient } from '@tier0/sdk-v2';
28
+
29
+ export async function findAppCollection(alias: string) {
30
+ const client = createClient();
31
+ const context = await client.uns.dataAppContext({});
32
+ const collection = context.data.collections.find(item => item.alias === alias);
33
+ if (!collection) throw new Error('当前 App 未获得目标集合授权');
34
+ return client.uns.collectionDescribe({ collectionId: collection.collectionId });
35
+ }
36
+ ```
37
+
38
+ 不要把 alias/名称当 collectionId,不猜第一个集合是业务目标。
39
+
40
+ ## 核心约束
41
+
42
+ 1. 记录行是 id/data/revision;filter 与 update 的业务路径用 name、customer.name,不加 data.;系统路径用 _id、_meta.revision。
43
+ 2. 改/删前读取 expectedRevision;普通 Record 意图键上限 96 字符,集合管理为 128。不同操作使用独立键。
44
+ 3. collectionSchemaUpdate 替换完整 Schema,不迁移已有记录;sampleSchema/indexes 省略保留。先预检兼容性。
45
+ 4. 集合无硬删除入口,用 archive;Record 删除会物理删除关联 Sample,恢复 Record 不会恢复这些 Sample。
46
+ 5. recordFind 使用 pageInfo.hasMore/nextCursor,limit 1–200;sort 为 [['field',1]]。单页不是全量,跨页不是稳定快照。
47
+ 6. Sample 的时间是 Unix 毫秒,查询范围 [from,to)。Record 代表业务实体,不用覆盖它来保存历史。
48
+
49
+ ## 结果与异常
50
+
51
+ 响应为 code/msg/requestId/data;集合写入 Outcome 与记录写入计数回执不同。检查 committed、每项错误和实际计数;accepted/jobId 先对账,不当成完成。allowPartial 不等于事务,recordBulkWrite 不保证跨集合事务。
52
+
53
+ 大整数与 JSON 精确数字可能为 bigint/UnsV2RawNumber;保留读取到的 revision 类型,不用 Number 或普通 JSON.stringify 做无损往返。UnsV2OutcomeUnknownError 先读回核对内容或查询 jobs;取消请求不撤销写入,SDK 不自动重试。
@@ -0,0 +1,85 @@
1
+ # 创建集合与记录
2
+
3
+ | 方法 | POST 路径 | 用途 |
4
+ | --- | --- | --- |
5
+ | uns.collectionCreate | /openapi/v2/uns/collections/create | 创建集合及 Schema |
6
+ | uns.recordInsertOne | /openapi/v2/uns/collections/{collectionId}/records | 新增业务记录 |
7
+
8
+ ## 集合参数
9
+
10
+ | 字段 | 类型 | 必填 | 说明 |
11
+ | --- | --- | --- | --- |
12
+ | dryRun | boolean | 是 | true 预检;false 提交 |
13
+ | items[].id | string | 否 | 可指定稳定业务 ID,不能与已有集合冲突 |
14
+ | items[].name | string | 是 | 1–63 字符 |
15
+ | items[].recordSchema | Schema | 是 | 至少 type;对象可含 properties/required |
16
+ | items[].sampleSchema | Schema | 否 | 需要采样时配置 |
17
+ | items[].parentId | string/null | 否 | 目录 ID;null 或创建时省略表示根 |
18
+ | items[].indexes | IndexSpec[] | 否 | 索引定义,不能把索引创建当成已 ready |
19
+ | mode / allowPartial | string / boolean | 否 | 示例 sync / false |
20
+ | idempotencyKey | string | 否 | 集合管理最多 128 字符 |
21
+
22
+ ## 完整示例:设备集合
23
+
24
+ ```ts
25
+ import { createClient, type Tier0Client, type UnsV2Request } from '@tier0/sdk-v2';
26
+
27
+ export async function createDeviceCollection(
28
+ collectionId: string, name: string, intentKey: string, client: Tier0Client = createClient(),
29
+ ) {
30
+ const item: UnsV2Request<'collectionCreate'>['items'][number] = {
31
+ id: collectionId, name, parentId: null,
32
+ recordSchema: {
33
+ type: 'object', additionalProperties: false, required: ['name', 'enabled'],
34
+ properties: { name: { type: 'string' }, enabled: { type: 'boolean' } },
35
+ },
36
+ sampleSchema: {
37
+ type: 'object', additionalProperties: false, required: ['temperature'],
38
+ properties: { temperature: { type: 'number', unit: '°C' } },
39
+ },
40
+ };
41
+ const preview = await client.uns.collectionCreate({ dryRun: true, mode: 'sync', items: [item] });
42
+ if (preview.data.status !== 'validated') throw new Error('集合预检未通过');
43
+ const receipt = await client.uns.collectionCreate({
44
+ dryRun: false, mode: 'sync', allowPartial: false, items: [item], idempotencyKey: intentKey,
45
+ });
46
+ if (receipt.data.status !== 'committed' || receipt.data.results?.some(item => item.error)) {
47
+ return { state: 'unconfirmed' as const, receipt };
48
+ }
49
+ return { state: 'committed' as const, receipt,
50
+ current: (await client.uns.collectionGet({ id: collectionId })).data };
51
+ }
52
+ ```
53
+
54
+ 返回 Outcome:status、results、replayed,可含 jobId/summary。accepted 需要作业对账;validated 不表示已创建。省略自定义 ID 时从成功 results[].id 取得实际 ID,不能用集合名称代替。
55
+
56
+ ## 新增设备记录
57
+
58
+ | 字段 | 类型 | 必填 | 说明 |
59
+ | --- | --- | --- | --- |
60
+ | collectionId | string | 是 | 已存在且有权限的集合 |
61
+ | id | string | 否 | 可指定稳定记录 ID |
62
+ | data | object | 是 | 符合 recordSchema 的业务字段 |
63
+ | dryRun | boolean | 否 | 默认 false,示例显式给出 |
64
+ | idempotencyKey | string | 否 | 记录写入最多 96 字符 |
65
+
66
+ ```ts
67
+ import { createClient, type Tier0Client } from '@tier0/sdk-v2';
68
+
69
+ export async function insertDevice(
70
+ collectionId: string, id: string, name: string, intentKey: string,
71
+ client: Tier0Client = createClient(),
72
+ ) {
73
+ const receipt = await client.uns.recordInsertOne({
74
+ collectionId, id, data: { name, enabled: true }, dryRun: false, idempotencyKey: intentKey,
75
+ });
76
+ if (receipt.data.status !== 'committed') return { state: 'unconfirmed' as const, receipt };
77
+ const found = await client.uns.recordFind({
78
+ collectionId, filter: { _id: receipt.data.insertedId }, limit: 1,
79
+ });
80
+ if (!found.data.items[0]) throw new Error('新增有回执,但读回未找到记录');
81
+ return { state: 'committed' as const, receipt, current: found.data.items[0] };
82
+ }
83
+ ```
84
+
85
+ 成功 data:insertedId、status、replayed;不能把 replayed 误解为本次重复插入。记录创建与集合创建用不同意图键。未知结果先按 ID 核对业务内容,不仅检查“存在”。
@@ -0,0 +1,73 @@
1
+ # UNS 分页、采样与写入恢复
2
+
3
+ ## 完整游标遍历
4
+
5
+ ```ts
6
+ import { createClient } from '@tier0/sdk-v2';
7
+
8
+ export async function* scanRecords(collectionId: string, signal?: AbortSignal) {
9
+ const client = createClient();
10
+ let cursor: string | undefined;
11
+ const seen = new Set<string>();
12
+ while (true) {
13
+ const { data } = await client.uns.recordFind({ collectionId, limit: 200, cursor }, { signal });
14
+ yield data.items;
15
+ if (!data.pageInfo.hasMore) return;
16
+ const next = data.pageInfo.nextCursor;
17
+ if (!next || seen.has(next)) throw new Error('服务端未返回可继续的游标');
18
+ seen.add(next);
19
+ cursor = next;
20
+ }
21
+ }
22
+ ```
23
+
24
+ 消费方逐批处理以避免无限堆积内存;需要截止时用 AbortSignal。分页保证按返回游标继续,不保证跨请求保留相同行版本;导出审计要求一致快照时必须另行确认后端能力。
25
+
26
+ ## 历史采样
27
+
28
+ ```ts
29
+ import { createClient } from '@tier0/sdk-v2';
30
+
31
+ export async function readHistory(collectionId: string, recordId: string, from: number, to: number) {
32
+ if (!Number.isSafeInteger(from) || !Number.isSafeInteger(to) || from < 0 || to <= from) {
33
+ throw new Error('时间应为有效的 Unix 毫秒范围');
34
+ }
35
+ return createClient().uns.sampleFind({
36
+ collectionId, recordIds: [recordId], from, to, limit: 200,
37
+ });
38
+ }
39
+ ```
40
+
41
+ 返回可能分页;page 模式与 limit/cursor 互斥。Record 是业务实体,Sample 是该实体的时间序列,不用覆盖 Record 伪装保存历史。sampleDeleteRange 删除明确的时间范围,与删除 Record 的操作不可混淆。
42
+
43
+ ## 写入、事务与恢复
44
+
45
+ ```ts
46
+ import { createClient, UnsV2Error, UnsV2OutcomeUnknownError, type UnsV2JSONValue } from '@tier0/sdk-v2';
47
+
48
+ export async function insertRecord(collectionId: string, id: string, data: Record<string, UnsV2JSONValue>, intentKey: string) {
49
+ try {
50
+ return await createClient().uns.recordInsertOne({
51
+ collectionId, id, data, dryRun: false, idempotencyKey: intentKey,
52
+ });
53
+ } catch (error) {
54
+ if (error instanceof UnsV2OutcomeUnknownError) {
55
+ return { state: 'unknown' as const, requestId: error.requestId };
56
+ }
57
+ if (error instanceof UnsV2Error) {
58
+ return { state: 'rejected' as const, code: error.code, retryAdvice: error.retryAdvice };
59
+ }
60
+ throw error;
61
+ }
62
+ }
63
+ ```
64
+
65
+ 该函数执行真实写入,仅用于已授权的新增意图。unknown 时按指定 id 读取并核对业务内容;不能只凭记录存在就判定本次成功。
66
+
67
+ recordTransactionExecute 用 opId/dependsOn 表达依赖;不要拆成若干普通请求后声称具备事务性。allowPartial 适用于支持它的普通数组写入,事务不接受。部分成功检查每项结果,修正后的失败项是新的业务请求,不能原样重放整个批次。
68
+
69
+ recordLock 返回的 token 是租约凭据,更新/续租/解锁按原记录匹配;超时或租约失效不能继续假定持锁。只读响应中的 lock 状态不包含可用于写入的令牌。
70
+
71
+ ## 变更轮询
72
+
73
+ recordChangesPoll 使用 collectionId、可选 recordIds/cursor/limit/waitMs;limit 最大 100,waitMs 最大 5000。保存返回游标并在取消或目标完成后停止。收到变更后按事件语义重新读取需要的记录;这不是任意 SQL CDC,也不代表有 Source/Stream 管理权限。
@@ -0,0 +1,69 @@
1
+ # 删除记录与归档集合
2
+
3
+ 集合没有 collectionDelete;使用 collectionArchive。记录使用 recordDeleteOne/Many。两种操作的影响不可互换。
4
+
5
+ ## 删除一个明确 ID 的记录
6
+
7
+ | 字段 | 类型 | 必填 | 说明 |
8
+ | --- | --- | --- | --- |
9
+ | collectionId | string | 是 | 所属集合 |
10
+ | filter | object | 是 | 示例用 _id 精确等值 |
11
+ | expectedRevision | integer | 建议提供 | 刚读取的记录修订 |
12
+ | dryRun | boolean | 否 | 默认 false,示例显式指定 |
13
+ | idempotencyKey | string | 否 | 最多 96 字符 |
14
+
15
+ ```ts
16
+ import { createClient, type Tier0Client } from '@tier0/sdk-v2';
17
+
18
+ export async function deleteDevice(
19
+ collectionId: string, id: string, intentKey: string, client: Tier0Client = createClient(),
20
+ ) {
21
+ const found = await client.uns.recordFind({ collectionId, filter: { _id: id }, limit: 1 });
22
+ const current = found.data.items[0];
23
+ if (!current) throw new Error('记录不存在或不可见,未执行删除');
24
+ const receipt = await client.uns.recordDeleteOne({
25
+ collectionId, filter: { _id: id }, expectedRevision: current.revision,
26
+ dryRun: false, idempotencyKey: intentKey,
27
+ });
28
+ if (receipt.data.status !== 'committed' || String(receipt.data.deletedCount) !== '1') {
29
+ return { state: 'unconfirmed' as const, receipt };
30
+ }
31
+ const after = await client.uns.recordFind({ collectionId, filter: { _id: id }, limit: 1 });
32
+ if (after.data.items.length) throw new Error('删除有回执,但记录仍可见或已被恢复');
33
+ return { state: 'committed' as const, receipt };
34
+ }
35
+ ```
36
+
37
+ 删除 Record 时关联 Sample 被物理删除;恢复记录不会重建这些 Sample。批量删除先明确筛选范围,不能用空 filter 代替未知 ID。删除回执含 deletedCount/status/replayed。
38
+
39
+ ## 归档集合
40
+
41
+ 输入 dryRun、items[{id,expectedRevision}],可选 mode/allowPartial/idempotencyKey(最多128字符)。归档不等于逐条删除 Record,也不承诺物理擦除。
42
+
43
+ ```ts
44
+ import { createClient, type Tier0Client } from '@tier0/sdk-v2';
45
+
46
+ export async function archiveCollection(
47
+ id: string, intentKey: string, client: Tier0Client = createClient(),
48
+ ) {
49
+ const { data: current } = await client.uns.collectionGet({ id });
50
+ const receipt = await client.uns.collectionArchive({
51
+ dryRun: false, mode: 'sync', allowPartial: false, idempotencyKey: intentKey,
52
+ items: [{ id, expectedRevision: current.revision }],
53
+ });
54
+ if (receipt.data.status !== 'committed' || receipt.data.results?.some(item => item.error)) {
55
+ return { state: 'unconfirmed' as const, receipt };
56
+ }
57
+ let page = 1;
58
+ while (true) {
59
+ const { data } = await client.uns.collectionList({ archived: true, page, size: 200 });
60
+ const archived = data.list.find(item => item.id === id);
61
+ if (archived) return { state: 'archived' as const, receipt, archived };
62
+ if (!data.hasMore) return { state: 'committed-readback-unconfirmed' as const, receipt };
63
+ if (typeof data.nextPage !== 'number' || data.nextPage <= page) throw new Error('无有效下一页');
64
+ page = data.nextPage;
65
+ }
66
+ }
67
+ ```
68
+
69
+ 返回 Outcome,accepted 需要 jobs 对账;不能提前宣称完成。引用约束或版本冲突应停止并评估,不自动连带删除 Topic/Source/Stream。恢复见[恢复](restore.md)。
@@ -0,0 +1,75 @@
1
+ # 目录和 Topic
2
+
3
+ 目录组织资源,Topic 是集合的 records/fields 视图,不是旧架构 Metric。增删改查使用 client.uns 的 directory*/topic*。
4
+
5
+ | 操作 | 目录 | Topic | 关键参数 |
6
+ | --- | --- | --- | --- |
7
+ | 创建 | directoryCreate | topicCreate | dryRun、items;名称必填,Topic 另需 viewMode |
8
+ | 查询 | directoryList/Get | topicList/Get | list 的 page/size/search/archived;get 的 id |
9
+ | 改元数据 | directoryUpdate | topicUpdate | items[].id、expectedRevision、name/description/parentId |
10
+ | 归档 | directoryArchive | topicArchive | items[].id、expectedRevision |
11
+ | 恢复 | directoryRestore | topicRestore | 归档对象的 id 和当前 revision |
12
+ | 改字段映射 | — | topicBindingsUpdate | 按 Topic 字段绑定契约 |
13
+
14
+ 创建/更新/归档/恢复均返回 Outcome;检查 committed、results[].error。目录/Topic 的删除语义是归档,不存在 directoryDelete/topicDelete。parentId=null 表示根;更新时省略则保持原位置。归档不能自行扩展成递归清空所有后代或关联集合。
15
+
16
+ ## 创建目录和集合视图
17
+
18
+ 以下函数独立调用;不要把创建目录后创建 Topic 说成一个原子事务。collectionId 先从集合查询取得。
19
+
20
+ ```ts
21
+ import { createClient, type Tier0Client } from '@tier0/sdk-v2';
22
+
23
+ export async function createDirectory(
24
+ id: string, name: string, intentKey: string, client: Tier0Client = createClient(),
25
+ ) {
26
+ const receipt = await client.uns.directoryCreate({
27
+ dryRun: false, mode: 'sync', allowPartial: false, idempotencyKey: intentKey,
28
+ items: [{ id, name, parentId: null }],
29
+ });
30
+ if (receipt.data.status !== 'committed' || receipt.data.results?.some(item => item.error)) {
31
+ return { state: 'unconfirmed' as const, receipt };
32
+ }
33
+ return { state: 'committed' as const, receipt, current: (await client.uns.directoryGet({ id })).data };
34
+ }
35
+ export async function createRecordTopic(
36
+ id: string, name: string, collectionId: string, parentId: string, intentKey: string,
37
+ client: Tier0Client = createClient(),
38
+ ) {
39
+ const receipt = await client.uns.topicCreate({
40
+ dryRun: false, mode: 'sync', allowPartial: false, idempotencyKey: intentKey,
41
+ items: [{ id, name, viewMode: 'records', collectionId, parentId }],
42
+ });
43
+ if (receipt.data.status !== 'committed' || receipt.data.results?.some(item => item.error)) {
44
+ return { state: 'unconfirmed' as const, receipt };
45
+ }
46
+ return { state: 'committed' as const, receipt, current: (await client.uns.topicGet({ id })).data };
47
+ }
48
+ ```
49
+
50
+ ## 修改与归档 Topic
51
+
52
+ ```ts
53
+ import { createClient, type Tier0Client } from '@tier0/sdk-v2';
54
+
55
+ export async function renameTopic(
56
+ id: string, name: string, intentKey: string, client: Tier0Client = createClient(),
57
+ ) {
58
+ const current = await client.uns.topicGet({ id });
59
+ return client.uns.topicUpdate({
60
+ dryRun: false, mode: 'sync', allowPartial: false, idempotencyKey: intentKey,
61
+ items: [{ id, name, expectedRevision: current.data.revision }],
62
+ });
63
+ }
64
+ export async function archiveTopic(
65
+ id: string, intentKey: string, client: Tier0Client = createClient(),
66
+ ) {
67
+ const current = await client.uns.topicGet({ id });
68
+ return client.uns.topicArchive({
69
+ dryRun: false, mode: 'sync', allowPartial: false, idempotencyKey: intentKey,
70
+ items: [{ id, expectedRevision: current.data.revision }],
71
+ });
72
+ }
73
+ ```
74
+
75
+ 调用后检查 Outcome;修改后 get 核对名称,归档后 topicList({archived:true}) 遍历核对 ID。目录使用对应 directoryGet/Update/Archive;恢复从归档列表取得当前修订再 Restore,最后 get 核对。不要把 Topic 归档解释成底层 Record 已删除。
@@ -0,0 +1,32 @@
1
+ # 查询集合与记录
2
+
3
+ | 方法 | 必要输入 | 返回 data |
4
+ | --- | --- | --- |
5
+ | collectionList | 可选 page、size、search、archived | list、total、hasMore、nextPage |
6
+ | collectionGet | id | kind、id、revision、data(Schema/元数据) |
7
+ | collectionDescribe | collectionId | 集合结构和数据能力 |
8
+ | recordFind | collectionId;可选 filter、sort、limit、cursor | items、pageInfo |
9
+ | recordCount | collectionId;可选 filter | 计数结果 |
10
+
11
+ 集合详情有两层 data:响应.data 是资源,响应.data.data 才是集合定义。记录行是 `{id,data,revision,…}`,过滤业务路径用 name,系统字段用 _id、_meta.revision;不要写 data.name。
12
+
13
+ ```ts
14
+ import { createClient, type Tier0Client } from '@tier0/sdk-v2';
15
+
16
+ export async function readDevices(collectionId: string, client: Tier0Client = createClient()) {
17
+ const collection = await client.uns.collectionGet({ id: collectionId });
18
+ const records = await client.uns.recordFind({
19
+ collectionId, filter: { enabled: true }, sort: [['name', 1]], limit: 20,
20
+ });
21
+ return { schema: collection.data.data.recordSchema, items: records.data.items,
22
+ pageInfo: records.data.pageInfo };
23
+ }
24
+ export async function readDevice(collectionId: string, id: string, client: Tier0Client = createClient()) {
25
+ const result = await client.uns.recordFind({ collectionId, filter: { _id: id }, limit: 1 });
26
+ return result.data.items[0] ?? null;
27
+ }
28
+ ```
29
+
30
+ recordFind 的 limit 为 1–200;按 pageInfo.hasMore/nextCursor 继续,完整循环见[数据工作流](data-workflows.md)。不要把第一页当全量,sort 是数组,不是 MongoDB 对象。
31
+
32
+ App 使用 dataAppContext 精确选择授权 alias;Workspace 集成使用 dataDiscover/collectionList。数据访问授权不等于集合管理授权。网络/权限错误必须传播,不能返回空数组掩盖错误。
@@ -0,0 +1,41 @@
1
+ # 恢复集合与记录
2
+
3
+ | 方法 | 输入 | 返回 |
4
+ | --- | --- | --- |
5
+ | collectionRestore | dryRun、items[{id,expectedRevision}]、可选 mode/idempotencyKey | Outcome |
6
+ | recordRestore | collectionId、ids(1–100)、可选 dryRun/idempotencyKey | restoredCount、restoredIds、status、replayed |
7
+
8
+ 集合 ID 和修订从 collectionList({archived:true,…}) 的实际归档项取得;不要使用归档前的旧修订。
9
+
10
+ ```ts
11
+ import { createClient, type Tier0Client, type UnsV2Request } from '@tier0/sdk-v2';
12
+
13
+ export async function restoreCollection(
14
+ id: string, expectedRevision: NonNullable<UnsV2Request<'collectionRestore'>['items'][number]['expectedRevision']>,
15
+ intentKey: string, client: Tier0Client = createClient(),
16
+ ) {
17
+ const receipt = await client.uns.collectionRestore({
18
+ dryRun: false, mode: 'sync', allowPartial: false, idempotencyKey: intentKey,
19
+ items: [{ id, expectedRevision }],
20
+ });
21
+ if (receipt.data.status !== 'committed' || receipt.data.results?.some(item => item.error)) {
22
+ return { state: 'unconfirmed' as const, receipt };
23
+ }
24
+ return { state: 'committed' as const, receipt, current: (await client.uns.collectionGet({ id })).data };
25
+ }
26
+ export async function restoreDevice(
27
+ collectionId: string, id: string, intentKey: string, client: Tier0Client = createClient(),
28
+ ) {
29
+ const receipt = await client.uns.recordRestore({
30
+ collectionId, ids: [id], dryRun: false, idempotencyKey: intentKey,
31
+ });
32
+ if (receipt.data.status !== 'committed' || !receipt.data.restoredIds.includes(id)) {
33
+ return { state: 'unconfirmed' as const, receipt };
34
+ }
35
+ const found = await client.uns.recordFind({ collectionId, filter: { _id: id }, limit: 1 });
36
+ if (!found.data.items[0]) throw new Error('恢复有回执,读回未确认');
37
+ return { state: 'committed' as const, receipt, current: found.data.items[0] };
38
+ }
39
+ ```
40
+
41
+ Record 恢复重新校验当前 Schema 和有效唯一索引,冲突整批回滚;revision 递增。删除记录时关联 Sample 已物理删除,恢复不会重建历史。幂等键每个新意图独立,未知结果不要换键重试。
@@ -0,0 +1,86 @@
1
+ # 修改记录、集合元数据和 Schema
2
+
3
+ | 方法 | 关键输入 | 语义 |
4
+ | --- | --- | --- |
5
+ | recordUpdateOne | collectionId、filter、update、expectedRevision | 修改一个精确目标 |
6
+ | collectionUpdate | dryRun、items[].id/name/description/parentId/expectedRevision | 修改元数据 |
7
+ | collectionSchemaUpdate | dryRun、items[].id/recordSchema/expectedRevision | 替换 recordSchema |
8
+
9
+ 写前读取 revision;冲突后重新比较并决策。省略 expectedRevision 可能覆盖最新状态,示例始终携带。
10
+
11
+ ## 修改一条设备记录
12
+
13
+ ```ts
14
+ import { createClient, type Tier0Client } from '@tier0/sdk-v2';
15
+
16
+ export async function renameDevice(
17
+ collectionId: string, id: string, name: string, intentKey: string,
18
+ client: Tier0Client = createClient(),
19
+ ) {
20
+ const found = await client.uns.recordFind({ collectionId, filter: { _id: id }, limit: 1 });
21
+ const current = found.data.items[0];
22
+ if (!current) throw new Error('记录不存在或不可见');
23
+ const receipt = await client.uns.recordUpdateOne({
24
+ collectionId, filter: { _id: id }, expectedRevision: current.revision,
25
+ update: { $set: { name } }, upsert: false, dryRun: false, idempotencyKey: intentKey,
26
+ });
27
+ if (receipt.data.status !== 'committed' || String(receipt.data.matchedCount) !== '1') {
28
+ return { state: 'unconfirmed' as const, receipt };
29
+ }
30
+ const after = await client.uns.recordFind({ collectionId, filter: { _id: id }, limit: 1 });
31
+ if (after.data.items[0]?.data.name !== name) throw new Error('读回名称与目标不一致');
32
+ return { state: 'committed' as const, receipt, current: after.data.items[0] };
33
+ }
34
+ ```
35
+
36
+ data 返回 matchedCount、modifiedCount、status、replayed。modifiedCount=0 可能是值未变化;不能据此断言请求失败。upsert=false 避免不存在时意外创建。批量修改只在明确筛选范围和授权内使用。
37
+
38
+ ## 修改集合名称
39
+
40
+ ```ts
41
+ import { createClient, type Tier0Client } from '@tier0/sdk-v2';
42
+
43
+ export async function renameCollection(
44
+ id: string, name: string, intentKey: string, client: Tier0Client = createClient(),
45
+ ) {
46
+ const { data: current } = await client.uns.collectionGet({ id });
47
+ const receipt = await client.uns.collectionUpdate({
48
+ dryRun: false, mode: 'sync', allowPartial: false, idempotencyKey: intentKey,
49
+ items: [{ id, name, expectedRevision: current.revision }],
50
+ });
51
+ if (receipt.data.status !== 'committed' || receipt.data.results?.some(item => item.error)) {
52
+ return { state: 'unconfirmed' as const, receipt };
53
+ }
54
+ return { state: 'committed' as const, receipt, current: (await client.uns.collectionGet({ id })).data };
55
+ }
56
+ ```
57
+
58
+ ## 扩展 Schema:添加可选位置字段
59
+
60
+ ```ts
61
+ import { createClient, type Tier0Client, type Schema } from '@tier0/sdk-v2';
62
+
63
+ export async function addDeviceLocation(
64
+ id: string, intentKey: string, client: Tier0Client = createClient(),
65
+ ) {
66
+ const { data: current } = await client.uns.collectionGet({ id });
67
+ if (current.data.recordSchema.type !== 'object') throw new Error('此示例仅支持对象 Schema');
68
+ if (current.data.recordSchema.properties?.location) throw new Error('location 已定义,应先比较原 Schema');
69
+ const recordSchema: Schema = {
70
+ ...current.data.recordSchema,
71
+ properties: { ...current.data.recordSchema.properties, location: { type: 'string' } },
72
+ };
73
+ const item = { id, expectedRevision: current.revision, recordSchema };
74
+ const preview = await client.uns.collectionSchemaUpdate({ dryRun: true, mode: 'sync', items: [item] });
75
+ if (preview.data.status !== 'validated') throw new Error('Schema 预检未通过');
76
+ const receipt = await client.uns.collectionSchemaUpdate({
77
+ dryRun: false, mode: 'sync', allowPartial: false, items: [item], idempotencyKey: intentKey,
78
+ });
79
+ if (receipt.data.status !== 'committed' || receipt.data.results?.some(item => item.error)) {
80
+ return { state: 'unconfirmed' as const, receipt };
81
+ }
82
+ return { state: 'committed' as const, receipt, current: (await client.uns.collectionGet({ id })).data };
83
+ }
84
+ ```
85
+
86
+ recordSchema 是完整替换,不是 merge patch,也不自动迁移记录。新 Schema 必须接受已有数据并满足相关引用约束。sampleSchema/indexes 省略时保留;不要为了添加一个字段顺手清空它们。收紧类型、增加必填或删除采样 Schema 需额外评估已有数据。