@tier0/sdk-v2 0.9.9 → 0.10.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 (250) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/LICENSE +21 -21
  3. package/README.md +165 -27
  4. package/dist/cjs/client.d.ts +79 -3
  5. package/dist/cjs/client.js +4 -6
  6. package/dist/cjs/client.js.map +1 -1
  7. package/dist/cjs/cloud-server.d.ts +33 -26
  8. package/dist/cjs/cloud-server.js +102 -100
  9. package/dist/cjs/cloud-server.js.map +1 -1
  10. package/dist/cjs/cloud-transport.d.ts +15 -20
  11. package/dist/cjs/cloud-transport.js +87 -68
  12. package/dist/cjs/cloud-transport.js.map +1 -1
  13. package/dist/cjs/connection-diagnostics.d.ts +2 -0
  14. package/dist/cjs/connection-diagnostics.js +20 -0
  15. package/dist/cjs/connection-diagnostics.js.map +1 -0
  16. package/dist/cjs/data/bootstrap.js +3 -4
  17. package/dist/cjs/data/bootstrap.js.map +1 -1
  18. package/dist/cjs/data/call.d.ts +1 -3
  19. package/dist/cjs/data/call.js +8 -12
  20. package/dist/cjs/data/call.js.map +1 -1
  21. package/dist/cjs/data/client.d.ts +8 -7
  22. package/dist/cjs/data/client.js +24 -11
  23. package/dist/cjs/data/client.js.map +1 -1
  24. package/dist/cjs/data/collection.d.ts +3 -5
  25. package/dist/cjs/data/collection.js +3 -4
  26. package/dist/cjs/data/collection.js.map +1 -1
  27. package/dist/cjs/data/context.d.ts +5 -11
  28. package/dist/cjs/data/context.js +21 -36
  29. package/dist/cjs/data/context.js.map +1 -1
  30. package/dist/cjs/data/error.d.ts +1 -1
  31. package/dist/cjs/data/error.js +3 -3
  32. package/dist/cjs/data/error.js.map +1 -1
  33. package/dist/cjs/data/index.d.ts +5 -3
  34. package/dist/cjs/data/index.js +5 -5
  35. package/dist/cjs/data/index.js.map +1 -1
  36. package/dist/cjs/data/relay.d.ts +51 -0
  37. package/dist/cjs/data/relay.js +371 -0
  38. package/dist/cjs/data/relay.js.map +1 -0
  39. package/dist/cjs/data/subscribe.d.ts +22 -38
  40. package/dist/cjs/data/subscribe.js +22 -28
  41. package/dist/cjs/data/subscribe.js.map +1 -1
  42. package/dist/cjs/event-stream.d.ts +28 -0
  43. package/dist/cjs/event-stream.js +222 -0
  44. package/dist/cjs/event-stream.js.map +1 -0
  45. package/dist/cjs/mq/client.d.ts +32 -1
  46. package/dist/cjs/mq/client.js +404 -69
  47. package/dist/cjs/mq/client.js.map +1 -1
  48. package/dist/cjs/mq/index.d.ts +1 -0
  49. package/dist/cjs/mq/index.js +3 -1
  50. package/dist/cjs/mq/index.js.map +1 -1
  51. package/dist/cjs/mq/topic.d.ts +29 -0
  52. package/dist/cjs/mq/topic.js +48 -0
  53. package/dist/cjs/mq/topic.js.map +1 -0
  54. package/dist/cjs/mq/types.d.ts +21 -1
  55. package/dist/cjs/mq/types.js.map +1 -1
  56. package/dist/cjs/openapi/api.generated.d.ts +20 -0
  57. package/dist/cjs/openapi/api.generated.js +11 -0
  58. package/dist/cjs/openapi/api.generated.js.map +1 -1
  59. package/dist/cjs/openapi/client.d.ts +46 -0
  60. package/dist/cjs/openapi/client.js +161 -12
  61. package/dist/cjs/openapi/client.js.map +1 -1
  62. package/dist/cjs/openapi/codecs.generated.d.ts +74 -0
  63. package/dist/cjs/openapi/codecs.generated.js +1 -1
  64. package/dist/cjs/openapi/codecs.generated.js.map +1 -1
  65. package/dist/cjs/openapi/types.generated.d.ts +205 -1
  66. package/dist/cjs/openapi/types.generated.js.map +1 -1
  67. package/dist/cjs/page-runtime.d.ts +48 -0
  68. package/dist/cjs/page-runtime.js +74 -0
  69. package/dist/cjs/page-runtime.js.map +1 -0
  70. package/dist/cjs/platform-proxy.d.ts +17 -0
  71. package/dist/cjs/platform-proxy.js +52 -0
  72. package/dist/cjs/platform-proxy.js.map +1 -0
  73. package/dist/cjs/runtime-context.d.ts +2 -1
  74. package/dist/cjs/runtime-context.js +6 -2
  75. package/dist/cjs/runtime-context.js.map +1 -1
  76. package/dist/cjs/runtime-env.js.map +1 -1
  77. package/dist/cjs/source.d.ts +19 -1
  78. package/dist/cjs/source.js +9 -3
  79. package/dist/cjs/source.js.map +1 -1
  80. package/dist/cjs/stream.js +3 -2
  81. package/dist/cjs/stream.js.map +1 -1
  82. package/dist/cjs/streams/contract.generated.d.ts +7 -1
  83. package/dist/cjs/streams/contract.generated.js.map +1 -1
  84. package/dist/cjs/streams/messages.js +1 -10
  85. package/dist/cjs/streams/messages.js.map +1 -1
  86. package/dist/cjs/streams/requests.d.ts +3 -0
  87. package/dist/cjs/streams/requests.js +5 -2
  88. package/dist/cjs/streams/requests.js.map +1 -1
  89. package/dist/cjs/streams/transport.d.ts +1 -13
  90. package/dist/cjs/streams/transport.js +2 -18
  91. package/dist/cjs/streams/transport.js.map +1 -1
  92. package/dist/cjs/uns-v2/client.d.ts +1 -6
  93. package/dist/cjs/uns-v2/client.js +33 -8
  94. package/dist/cjs/uns-v2/client.js.map +1 -1
  95. package/dist/cjs/uns-v2/data-client.d.ts +5 -2
  96. package/dist/cjs/uns-v2/data-client.js +7 -4
  97. package/dist/cjs/uns-v2/data-client.js.map +1 -1
  98. package/dist/cjs/uns-v2/generated.d.ts +6783 -4655
  99. package/dist/cjs/uns-v2/generated.js +221 -65
  100. package/dist/cjs/uns-v2/generated.js.map +1 -1
  101. package/dist/cjs/uns-v2/sse.d.ts +7 -1
  102. package/dist/cjs/uns-v2/sse.js +12 -2
  103. package/dist/cjs/uns-v2/sse.js.map +1 -1
  104. package/dist/esm/client.d.ts +79 -3
  105. package/dist/esm/client.js +5 -7
  106. package/dist/esm/client.js.map +1 -1
  107. package/dist/esm/cloud-server.d.ts +33 -26
  108. package/dist/esm/cloud-server.js +99 -97
  109. package/dist/esm/cloud-server.js.map +1 -1
  110. package/dist/esm/cloud-transport.d.ts +15 -20
  111. package/dist/esm/cloud-transport.js +86 -67
  112. package/dist/esm/cloud-transport.js.map +1 -1
  113. package/dist/esm/connection-diagnostics.d.ts +2 -0
  114. package/dist/esm/connection-diagnostics.js +17 -0
  115. package/dist/esm/connection-diagnostics.js.map +1 -0
  116. package/dist/esm/data/bootstrap.js +3 -4
  117. package/dist/esm/data/bootstrap.js.map +1 -1
  118. package/dist/esm/data/call.d.ts +1 -3
  119. package/dist/esm/data/call.js +9 -13
  120. package/dist/esm/data/call.js.map +1 -1
  121. package/dist/esm/data/client.d.ts +8 -7
  122. package/dist/esm/data/client.js +22 -9
  123. package/dist/esm/data/client.js.map +1 -1
  124. package/dist/esm/data/collection.d.ts +3 -5
  125. package/dist/esm/data/collection.js +3 -4
  126. package/dist/esm/data/collection.js.map +1 -1
  127. package/dist/esm/data/context.d.ts +5 -11
  128. package/dist/esm/data/context.js +19 -33
  129. package/dist/esm/data/context.js.map +1 -1
  130. package/dist/esm/data/error.d.ts +1 -1
  131. package/dist/esm/data/error.js +3 -3
  132. package/dist/esm/data/error.js.map +1 -1
  133. package/dist/esm/data/index.d.ts +5 -3
  134. package/dist/esm/data/index.js +3 -2
  135. package/dist/esm/data/index.js.map +1 -1
  136. package/dist/esm/data/relay.d.ts +51 -0
  137. package/dist/esm/data/relay.js +368 -0
  138. package/dist/esm/data/relay.js.map +1 -0
  139. package/dist/esm/data/subscribe.d.ts +22 -38
  140. package/dist/esm/data/subscribe.js +22 -27
  141. package/dist/esm/data/subscribe.js.map +1 -1
  142. package/dist/esm/event-stream.d.ts +28 -0
  143. package/dist/esm/event-stream.js +217 -0
  144. package/dist/esm/event-stream.js.map +1 -0
  145. package/dist/esm/mq/client.d.ts +32 -1
  146. package/dist/esm/mq/client.js +404 -69
  147. package/dist/esm/mq/client.js.map +1 -1
  148. package/dist/esm/mq/index.d.ts +1 -0
  149. package/dist/esm/mq/index.js +1 -0
  150. package/dist/esm/mq/index.js.map +1 -1
  151. package/dist/esm/mq/topic.d.ts +29 -0
  152. package/dist/esm/mq/topic.js +44 -0
  153. package/dist/esm/mq/topic.js.map +1 -0
  154. package/dist/esm/mq/types.d.ts +21 -1
  155. package/dist/esm/mq/types.js.map +1 -1
  156. package/dist/esm/openapi/api.generated.d.ts +20 -0
  157. package/dist/esm/openapi/api.generated.js +11 -0
  158. package/dist/esm/openapi/api.generated.js.map +1 -1
  159. package/dist/esm/openapi/client.d.ts +46 -0
  160. package/dist/esm/openapi/client.js +160 -12
  161. package/dist/esm/openapi/client.js.map +1 -1
  162. package/dist/esm/openapi/codecs.generated.d.ts +74 -0
  163. package/dist/esm/openapi/codecs.generated.js +1 -1
  164. package/dist/esm/openapi/codecs.generated.js.map +1 -1
  165. package/dist/esm/openapi/types.generated.d.ts +205 -1
  166. package/dist/esm/openapi/types.generated.js.map +1 -1
  167. package/dist/esm/page-runtime.d.ts +48 -0
  168. package/dist/esm/page-runtime.js +65 -0
  169. package/dist/esm/page-runtime.js.map +1 -0
  170. package/dist/esm/platform-proxy.d.ts +17 -0
  171. package/dist/esm/platform-proxy.js +47 -0
  172. package/dist/esm/platform-proxy.js.map +1 -0
  173. package/dist/esm/runtime-context.d.ts +2 -1
  174. package/dist/esm/runtime-context.js +6 -2
  175. package/dist/esm/runtime-context.js.map +1 -1
  176. package/dist/esm/runtime-env.js.map +1 -1
  177. package/dist/esm/source.d.ts +19 -1
  178. package/dist/esm/source.js +10 -4
  179. package/dist/esm/source.js.map +1 -1
  180. package/dist/esm/stream.js +4 -3
  181. package/dist/esm/stream.js.map +1 -1
  182. package/dist/esm/streams/contract.generated.d.ts +7 -1
  183. package/dist/esm/streams/contract.generated.js.map +1 -1
  184. package/dist/esm/streams/messages.js +1 -10
  185. package/dist/esm/streams/messages.js.map +1 -1
  186. package/dist/esm/streams/requests.d.ts +3 -0
  187. package/dist/esm/streams/requests.js +5 -2
  188. package/dist/esm/streams/requests.js.map +1 -1
  189. package/dist/esm/streams/transport.d.ts +1 -13
  190. package/dist/esm/streams/transport.js +1 -17
  191. package/dist/esm/streams/transport.js.map +1 -1
  192. package/dist/esm/uns-v2/client.d.ts +1 -6
  193. package/dist/esm/uns-v2/client.js +33 -8
  194. package/dist/esm/uns-v2/client.js.map +1 -1
  195. package/dist/esm/uns-v2/data-client.d.ts +5 -2
  196. package/dist/esm/uns-v2/data-client.js +8 -5
  197. package/dist/esm/uns-v2/data-client.js.map +1 -1
  198. package/dist/esm/uns-v2/generated.d.ts +6783 -4655
  199. package/dist/esm/uns-v2/generated.js +221 -65
  200. package/dist/esm/uns-v2/generated.js.map +1 -1
  201. package/dist/esm/uns-v2/sse.d.ts +7 -1
  202. package/dist/esm/uns-v2/sse.js +12 -2
  203. package/dist/esm/uns-v2/sse.js.map +1 -1
  204. package/package.json +25 -12
  205. package/skills/tier0-sdk-v2/SKILL.md +13 -5
  206. package/skills/tier0-sdk-v2/references/integration.md +14 -24
  207. package/skills/tier0-sdk-v2/references/local-connection.md +61 -0
  208. package/skills/tier0-sdk-v2/tier0-sdk-v2-data/SKILL.md +11 -5
  209. package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/consistency.md +8 -12
  210. package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/errors.md +24 -5
  211. package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/modeling.md +3 -1
  212. package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/read-write.md +46 -13
  213. package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/schema-evolution.md +2 -0
  214. package/skills/tier0-sdk-v2/tier0-sdk-v2-members/SKILL.md +2 -2
  215. package/skills/tier0-sdk-v2/tier0-sdk-v2-members/references/app-members.md +5 -1
  216. package/skills/tier0-sdk-v2/tier0-sdk-v2-mq/SKILL.md +25 -0
  217. package/skills/tier0-sdk-v2/tier0-sdk-v2-mq/references/quickstart.md +1 -1
  218. package/skills/tier0-sdk-v2/tier0-sdk-v2-notifications/SKILL.md +3 -3
  219. package/skills/tier0-sdk-v2/tier0-sdk-v2-notifications/references/send.md +6 -6
  220. package/skills/tier0-sdk-v2/tier0-sdk-v2-source/SKILL.md +4 -4
  221. package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/changes.md +2 -2
  222. package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/create.md +26 -36
  223. package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/delete.md +9 -23
  224. package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/protocols.md +12 -29
  225. package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/read.md +4 -5
  226. package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/update.md +5 -5
  227. package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/SKILL.md +3 -3
  228. package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/references/create.md +23 -9
  229. package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/references/deploy.md +14 -12
  230. package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/references/http-server.md +4 -4
  231. package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/references/read.md +4 -3
  232. package/skills/tier0-sdk-v2/tier0-sdk-v2-streams/SKILL.md +12 -12
  233. package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/SKILL.md +3 -2
  234. package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/create.md +2 -2
  235. package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/delete.md +47 -20
  236. package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/namespace.md +3 -3
  237. package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/read.md +11 -6
  238. package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/restore.md +12 -13
  239. package/dist/cjs/app-identity-contract.generated.d.ts +0 -22
  240. package/dist/cjs/app-identity-contract.generated.js +0 -51
  241. package/dist/cjs/app-identity-contract.generated.js.map +0 -1
  242. package/dist/cjs/runtime.d.ts +0 -13
  243. package/dist/cjs/runtime.js +0 -51
  244. package/dist/cjs/runtime.js.map +0 -1
  245. package/dist/esm/app-identity-contract.generated.d.ts +0 -22
  246. package/dist/esm/app-identity-contract.generated.js +0 -48
  247. package/dist/esm/app-identity-contract.generated.js.map +0 -1
  248. package/dist/esm/runtime.d.ts +0 -13
  249. package/dist/esm/runtime.js +0 -46
  250. package/dist/esm/runtime.js.map +0 -1
@@ -6,14 +6,14 @@
6
6
  | --- | --- | --- | --- |
7
7
  | dryRun | boolean | 否 | 默认 false 提交;true 仅预检 |
8
8
  | program.name | string | 是 | 1–63 字符 |
9
- | program.script | string | 是 | 完整 module.exports 程序 |
10
- | program.timeoutMs | integer | 否 | 当前云端上限 30000 ms |
9
+ | program.script | object | 是 | code 为完整 module.exports 程序,timeoutMs 为超时 |
10
+ | program.script.timeoutMs | integer | 是 | 当前云端上限 30000 ms |
11
11
  | program.sourceBindings | array | 否 | sourceId、configRevision、acquisition |
12
12
  | idempotencyKey | string | 否 | 最多 128 字符,本次创建的固定意图键 |
13
13
 
14
14
  ## HTTP Client Source 绑定示例
15
15
 
16
- 先按[Source 查询](../../tier0-sdk-v2-source/references/read.md)选定真实 HTTP Client Source。脚本的 api.sources 属于平台执行器,与应用中的 client.source 不同。sourceId 保持字符串,configRevision 使用 Source 创建、更新回执或完整查询得到的修订。已有回执时直接传入 source;没有快照时再调用 source.documents.get({id,view:"full"}) 并检查 data.document。省略 `config.role` 时按正式协议目录默认为 client;Server 走入站语义,不能创建为主动请求程序。
16
+ 先按[Source 查询](../../tier0-sdk-v2-source/references/read.md)选定真实 HTTP Client Source。脚本的 api.sources 属于平台执行器,与应用中的 client.source 不同。sourceId 保持字符串,configRevision 使用 Source 创建、更新回执或完整查询得到的修订。已有回执时直接传入 source;没有快照时再调用 source.documents.get({id,view:"full"}) 并检查 data.document。省略 `role` 时按正式协议目录默认为 client;Server 走入站语义,不能创建为主动请求程序。
17
17
 
18
18
  Source 负责基址和认证,Stream 负责请求的 method/path/query。以下示例与 [Source Bearer 创建](../../tier0-sdk-v2-source/references/create.md#http-client-bearer-凭据)配合:Source endpoint 为 `https://inventory.example.com/api/v1`,凭据使用默认基址绑定;实际请求是 `GET https://inventory.example.com/api/v1/inventory?limit=5000`。已有 Source 的凭据若只绑定精确 URL `/api/v1` 的 GET,不能用于此子路径;不要通过改写请求为 REQUEST 或自动扩大凭据授权来绕过拒绝。
19
19
 
@@ -26,14 +26,14 @@ export async function createHttpStream(
26
26
  if (!collectionId.trim()) throw new Error('需要实际 Collection ID');
27
27
  const sourceId = source.id;
28
28
  if (!/^[1-9][0-9]*$/.test(sourceId)) throw new Error('Source ID 必须是正十进制字符串');
29
- if (source.document.protocol !== 'http-api' || 'sourceType' in source.document.config ||
30
- (source.document.config.role ?? 'client') !== 'client') {
29
+ if (source.document.protocol !== 'http-api' || 'sourceType' in source.document ||
30
+ (source.document.role ?? 'client') !== 'client') {
31
31
  throw new Error('此主动请求示例需要 HTTP API Client Source');
32
32
  }
33
33
  const document: RuntimeStreamDefinition = {
34
- name, timeoutMs: 5000,
34
+ name,
35
35
  sourceBindings: [{ sourceId, configRevision: source.revision, acquisition: {} }],
36
- script: [
36
+ script: { timeoutMs: 5000, code: [
37
37
  'module.exports = api => {',
38
38
  ' const upstream = api.sources.use({ id: ' + JSON.stringify(sourceId) + ', resources: [] });',
39
39
  ' const output = api.uns.use({ collectionId: ' + JSON.stringify(collectionId) + ', access: "write" });',
@@ -49,7 +49,7 @@ export async function createHttpStream(
49
49
  ' return { acceptedRows: records.length };',
50
50
  ' });',
51
51
  '};',
52
- ].join('\n'),
52
+ ].join('\n') },
53
53
  };
54
54
  const saved = await client.stream.documents.create({
55
55
  dryRun: false, program: document, idempotencyKey: intentKey,
@@ -73,4 +73,18 @@ validate 检查配置、程序和可确定的绑定问题,不执行任意业
73
73
 
74
74
  创建结局未知时先按已知 ID 或业务标识核对状态;允许重试时保留原请求与幂等键。
75
75
 
76
- 原生编辑器形态 `documents.create({dryRun, document})` 的 document 为 name/model/script对象/sources/entries,script 为 `{code,timeoutMs}`,sources 用 alias/sourceId/configRevision/resources/acquisition。不要把上面的 script 字符串直接塞入原生文档。
76
+ 原生编辑器形态 `documents.create({dryRun, document})` 的 document 为 name/model/script对象/sources/entries,script 为 `{code,timeoutMs}`,sources 用 alias/sourceId/configRevision/resources/acquisition。两种入口均使用 script 对象;不接受 script 字符串。
77
+
78
+ 远端 Edge 绑定使用 `target: {fleetNodeId, sourceId, configRevision}` 替代同一绑定上的 sourceId/configRevision。fleetNodeId 是发现返回的复合节点字符串,sourceId 是该节点上的原生十进制 ID。服务端验证并创建引用,调用方不拼装 edge:// 引用,也不创建另一种 Source 文档。
79
+
80
+ ```ts
81
+ import type { RuntimeStreamDefinition, StreamSourceTarget } from '@tier0/sdk-v2';
82
+
83
+ export function bindApprovedEdgeSource(
84
+ program: RuntimeStreamDefinition, target: StreamSourceTarget,
85
+ ): RuntimeStreamDefinition {
86
+ return { ...program, sourceBindings: [{ target, acquisition: {} }] };
87
+ }
88
+ ```
89
+
90
+ 该函数只构造绑定;脚本仍须使用声明资源并通过目标目录授权。厂商凭据在 Edge 本地预配,target 不携带秘密。配置成功不证明端到端采集完成。
@@ -4,18 +4,18 @@
4
4
  | --- | --- | --- |
5
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
- | documents.disable | /openapi/v2/uns/streams/documents/disable | id、expectedRevision、expectedActiveRelease、dryRun |
7
+ | documents.disable | /openapi/v2/uns/streams/documents/disable | id、expectedRevision、expectedActiveVersion、dryRun |
8
8
 
9
9
  全部写操作使用各自的 idempotencyKey。expectedSourceRevision 只适用于恰好一个 Source 依赖,且同时保护草稿冻结修订与当前 Source head;多源不能塞入单个修订值。原生文档没有 documents.deploy,当前云端 program 模型发布走 programs.deploy。
10
10
 
11
- 发布和回滚有可选并发守卫:有生效发布时传入正整数 `expectedActiveRelease`;没有生效发布时传入 `expectedReleaseState: 'inactive'`,两者不能同时使用。服务端在事务内比较当前状态,变化时返回版本冲突;重新读取并决策后才能使用新的业务意图。守卫比较当前值,不记录中间发生的全部切换。
11
+ 发布、停用和回滚使用 `expectedActiveVersion` 并发守卫:传服务端回执的十进制字符串 activeVersion,'0' 表示无生效发布。它是不可推算的发布 ID,不是 activeRelease 序号;不再传 expectedReleaseState。服务端在事务内比较当前状态,变化时返回版本冲突;重新读取并决策后才能使用新的业务意图。守卫比较当前值,不记录中间发生的全部切换。
12
12
 
13
13
  发布、停用和回滚需要 uns.stream.manage;相关 Source 访问还受各自授权约束。保存回执中的 revision、发布回执中的 activeVersion 与 activeRelease 分别用于不同目的:
14
14
 
15
15
  | 字段 | 含义与用途 |
16
16
  | --- | --- |
17
17
  | revision | 草稿修订;传给下次写入的 expectedRevision,冲突时显式处理 |
18
- | activeVersion | 不可变发布版本 ID;回滚的 versionId 使用这个 ID,不使用发布序号 |
18
+ | activeVersion | 十进制字符串形式的不可变发布 ID;用于 expectedActiveVersion 与回滚 versionId,不由 activeRelease 序号推算 |
19
19
  | activeRelease | 本次事务记录的正整数发布序号;与 runs.list 中的 release 对照;无活跃发布或回执未记录时省略 |
20
20
 
21
21
  programs 回执的 activeVersion:'0' 表示无活跃版本;programs 的 activeRelease 此时省略;documents 的 activeRelease 此时为 0。
@@ -24,11 +24,12 @@ programs 回执的 activeVersion:'0' 表示无活跃版本;programs 的 active
24
24
  import { createClient, type Tier0Client, type CloudStreamDocumentView, type CloudStreamStopView } from '@tier0/sdk-v2';
25
25
 
26
26
  export async function deployStream(current: CloudStreamDocumentView, intentKey: string, client: Tier0Client = createClient()) {
27
+ if (typeof current.activeVersion !== 'string' || !/^(0|[1-9][0-9]*)$/.test(current.activeVersion)) throw new Error('缺少权威 activeVersion;读取当前文档后再决定');
27
28
  const sources = current.document.sources ?? [];
28
29
  const request = {
29
30
  id: current.id, expectedRevision: current.revision,
30
- ...(String(current.activeRelease) === '0' ? { expectedReleaseState: 'inactive' as const } : { expectedActiveRelease: current.activeRelease }),
31
- ...(sources.length === 1 ? { expectedSourceRevision: sources[0]!.configRevision } : {}),
31
+ expectedActiveVersion: current.activeVersion,
32
+ ...(sources.length === 1 && !sources[0]!.target ? { expectedSourceRevision: sources[0]!.configRevision } : {}),
32
33
  };
33
34
  const receipt = await client.stream.programs.deploy({
34
35
  ...request, dryRun: false, idempotencyKey: intentKey,
@@ -44,22 +45,22 @@ export async function deployStream(current: CloudStreamDocumentView, intentKey:
44
45
  }
45
46
 
46
47
  export async function disableStream(
47
- current: Pick<CloudStreamStopView, 'id' | 'revision' | 'activeRelease'>,
48
+ current: Pick<CloudStreamStopView, 'id' | 'revision' | 'activeVersion'>,
48
49
  intentKey: string, client: Tier0Client = createClient(),
49
50
  ) {
50
- if (BigInt(String(current.activeRelease)) <= 0n) throw new Error('没有活跃发布可停用');
51
+ if (typeof current.activeVersion !== 'string' || !/^[1-9][0-9]*$/.test(current.activeVersion)) throw new Error('没有已确认的活跃版本可停用');
51
52
  const receipt = await client.stream.documents.disable({
52
- id: current.id, expectedRevision: current.revision, expectedActiveRelease: current.activeRelease,
53
+ id: current.id, expectedRevision: current.revision, expectedActiveVersion: current.activeVersion,
53
54
  dryRun: false, idempotencyKey: intentKey,
54
55
  });
55
- if (!('id' in receipt.data) || String(receipt.data.activeRelease) !== '0') {
56
+ if (!('id' in receipt.data) || receipt.data.activeVersion !== '0') {
56
57
  throw new Error('未返回停用回执');
57
58
  }
58
59
  return { receipt, current: receipt.data };
59
60
  }
60
61
  ```
61
62
 
62
- 将创建或更新回执的 current 传给 deployStream,只发一次发布请求。只有手上没有草稿快照时才先 documents.get。使用 [按发布号查询运行记录](read.md#观察本次发布) 才能观察这次发布,观察窗口汇总不能证明该版本运行。发布成功不表示外部连接健康或执行成功。停用使用已确认的 id/revision/activeRelease(可来自发布回执),返回的 current 可直接用于删除;不需要前后各 get。停用保留草稿。仅修改草稿的任务不额外发布/停用。
63
+ 将创建或更新回执的 current 传给 deployStream,只发一次发布请求。只有手上没有草稿快照时才先 documents.get。使用 [按发布号查询运行记录](read.md#观察本次发布) 才能观察这次发布,观察窗口汇总不能证明该版本运行。发布成功不表示外部连接健康或执行成功。停用使用已确认的 id/revision/activeVersion(可来自发布回执),返回的 current 可直接用于删除;不需要前后各 get。停用保留草稿。仅修改草稿的任务不额外发布/停用。
63
64
 
64
65
  ## 回滚到明确版本
65
66
 
@@ -67,12 +68,13 @@ export async function disableStream(
67
68
  import { createClient, type Tier0Client, type CloudStreamStopView } from '@tier0/sdk-v2';
68
69
 
69
70
  export async function rollbackStream(
70
- current: Pick<CloudStreamStopView, 'id' | 'revision' | 'activeRelease'>, versionId: string, intentKey: string, client: Tier0Client = createClient(),
71
+ current: Pick<CloudStreamStopView, 'id' | 'revision' | 'activeVersion'>, versionId: string, intentKey: string, client: Tier0Client = createClient(),
71
72
  ) {
72
73
  // versionId 使用已知发布回执的 activeVersion,或从 programs.versions.list 选定。
74
+ if (typeof current.activeVersion !== 'string' || !/^(0|[1-9][0-9]*)$/.test(current.activeVersion)) throw new Error('缺少权威 activeVersion;读取当前文档后再决定');
73
75
  return client.stream.programs.rollback({
74
76
  id: current.id, versionId, expectedRevision: current.revision,
75
- ...(String(current.activeRelease) === '0' ? { expectedReleaseState: 'inactive' as const } : { expectedActiveRelease: current.activeRelease }),
77
+ expectedActiveVersion: current.activeVersion,
76
78
  dryRun: false, idempotencyKey: intentKey,
77
79
  });
78
80
  }
@@ -1,12 +1,12 @@
1
1
  # HTTPServer 固定前缀与 Stream 路由
2
2
 
3
- 先用 [Source 创建](../../tier0-sdk-v2-source/references/create.md)保存 `http-api` + `config.role: server`。Source 使用 `http://managed` 和空 options;业务路径在 Stream 注册。
3
+ 先用 [Source 创建](../../tier0-sdk-v2-source/references/create.md)保存 `http-api` + `role: server`。Source 使用 `http://managed` ;业务路径在 Stream 注册。
4
4
 
5
5
  固定前缀为 `/ingest/v1/{workspaceId}/{sourceId}`。下例路由为 `POST /orders/:id`,完整请求路径为 `POST /ingest/v1/{workspaceId}/{sourceId}/orders/A-001`。前缀由部署环境和已认证的工作空间、Source 身份确定,不从请求 body 推断工作空间。Bearer 凭据由 Source 的既有管理合同取得并在调用时注入,不写入脚本。
6
6
 
7
7
  ## 保存原生程序草稿
8
8
 
9
- 使用完整原生 `document`:script 是 `{code,timeoutMs}`,sources 使用 alias。这与 `program` 的 script 字符串形态不同。
9
+ 使用完整原生 `document`:script 是 `{code,timeoutMs}`,sources 使用 alias。这与 `program` 的 script 对象形态不同。
10
10
 
11
11
  ```ts
12
12
  import { createClient, type Tier0Client, type CloudStreamDocument } from '@tier0/sdk-v2';
@@ -18,7 +18,7 @@ export async function createHttpServerStream(
18
18
  if (!/^[1-9][0-9]*$/.test(sourceId)) throw new Error('Source ID 必须是正十进制字符串');
19
19
  const source = await client.source.documents.get({ id: sourceId, view: 'full' });
20
20
  if (!('document' in source.data)) throw new Error('需要完整 Source 文档');
21
- const config = source.data.document.config;
21
+ const config = source.data.document;
22
22
  if (source.data.document.protocol !== 'http-api' || config.role !== 'server') {
23
23
  throw new Error('此入站路由示例需要 HTTP API Server Source');
24
24
  }
@@ -60,7 +60,7 @@ export async function createHttpServerEarlyReplyStream(
60
60
  if (!collectionId) throw new Error('需要实际 Collection ID');
61
61
  const source = await client.source.documents.get({ id: sourceId, view: 'full' });
62
62
  if (!('document' in source.data)) throw new Error('需要完整 Source 文档');
63
- const config = source.data.document.config;
63
+ const config = source.data.document;
64
64
  if (source.data.document.protocol !== 'http-api' || config.role !== 'server') {
65
65
  throw new Error('此入站路由示例需要 HTTP API Server Source');
66
66
  }
@@ -14,9 +14,8 @@ page 从 1 开始,size 最大 200。summary 只需 uns.stream.view 或 uns.str
14
14
  import { createClient, type Tier0Client } from '@tier0/sdk-v2';
15
15
 
16
16
  export async function readStream(id: string, client: Tier0Client = createClient()) {
17
- const draft = await client.stream.documents.get({ id });
18
- const observation = await client.stream.observations.get({ id });
19
- return { draft: draft.data, observation: observation.data };
17
+ const current = await client.stream.documents.get({ id, view: 'summary' });
18
+ return { current: current.data };
20
19
  }
21
20
  export async function* listStreams(client: Tier0Client = createClient()) {
22
21
  let page = 1;
@@ -30,6 +29,8 @@ export async function* listStreams(client: Tier0Client = createClient()) {
30
29
  }
31
30
  ```
32
31
 
32
+ 查询摘要只需一次请求。编辑完整草稿时显式使用 `documents.get({ id, view: 'full' })`;只看运行观测时直接 `observations.get({ id })`,不先读取草稿。已有写回执时可直接消费其中的状态和修订。
33
+
33
34
  保存、发布、某次执行成功是三个事实。显示实际 revision/activeVersion/release,不用旧 runs 证明新版本生效。版本列表不保证与普通列表相同分页结构;按其 list/total/page/size 读取,versionId 不是 release 序号。
34
35
 
35
36
  ## 观察本次发布
@@ -1,11 +1,13 @@
1
1
  ---
2
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."
3
+ description: "Tier0 App/Stream requests and live business messages: client.streams.requests describe/invoke/discover, client.streams.messages publish/subscribe on a server client with its own key, and disposable subscriptions. Use for Stream v3 interactions; UNS history, device MQTT and persisted notifications have their own Skills."
4
4
  ---
5
5
 
6
6
  # App / Stream Interactions
7
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.
8
+ Read the [root Skill](../SKILL.md) first.
9
+
10
+ **In a Tier0 App's server code** the App's key (`TIER0_API_KEY`) can use both. Stream requests and messages are fixed capabilities of every App (`stream.requests.read` / `invoke` / `write`, `stream.messages.publish` / `subscribe`), not configured per App and not approved by an admin. They are still bounded by the workspace plan (a plan without Stream refuses them) and, for requests, by the Stream request grants: the request code must be granted to this App. Message topics come from published Stream manifests: the App may subscribe to a topic a published Stream declares with `api.stream.messages.use({ topic, access: 'publish' })`, and publish to a topic an active Stream subscribes to. There is no per-App topic list. These APIs require an artifact built from the unified SDK implementation: pin its commit and SHA-256 until the corresponding version is published.
9
11
 
10
12
  ## Select the interaction
11
13
 
@@ -16,13 +18,11 @@ Read the [root Skill](../SKILL.md) first. These APIs require an artifact built f
16
18
 
17
19
  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
20
 
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.
21
+ ## Server only
22
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.
23
+ Use `client.streams` from server code with a client that holds a key: `createClient()` reads `TIER0_API_HOST` / `TIER0_API_KEY`, or pass `apiHost` / `apiKey`. Never put that key in a browser bundle. Browser code does not call the platform directly; it calls the App's own server routes, which use `client.streams`. There is no browser host bridge.
24
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.
25
+ Callers know only public request codes, logical topics and business payloads. Do not add broker URLs, physical subjects, JWTs, nkey seeds, workspace/principal headers or message identity claims to configuration; the SDK obtains a short-lived broker ticket per connection.
26
26
 
27
27
  ## Public caller
28
28
 
@@ -31,12 +31,12 @@ import { createClient, type StreamMessageError } from '@tier0/sdk-v2';
31
31
 
32
32
  export async function openOrders(
33
33
  reportError: (error: StreamMessageError) => void,
34
- renderOrderUpdate: (order: { orderId: string }) => void,
34
+ onOrderUpdate: (order: { orderId: string }) => void,
35
35
  ) {
36
- const sdk = createClient(); // automatically uses the trusted App runtime
36
+ const sdk = createClient(); // server: TIER0_API_HOST / TIER0_API_KEY
37
37
  const removeErrorHandler = sdk.streams.messages.onError(reportError);
38
38
  const subscription = await sdk.streams.messages.use<{ orderId: string }>({ topic: 'orders/accepted' })
39
- .subscribe(message => renderOrderUpdate(message.payload));
39
+ .subscribe(message => onOrderUpdate(message.payload));
40
40
  const request = sdk.streams.requests.use<{ orderId: string }, { accepted: boolean }>({ code: 'orders.approve' });
41
41
  return {
42
42
  describe: () => request.describe(),
@@ -56,9 +56,9 @@ Persist one caller-owned idempotency key per business intent. A deliberate recon
56
56
 
57
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
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.
59
+ The SDK uses native Core NATS over WSS, loaded only when messaging starts. Messages require Node.js 22+ (WebSocket); 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
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.
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 service stops.
62
62
 
63
63
  ## Stream-side code
64
64
 
@@ -5,7 +5,7 @@ description: 使用 @tier0/sdk-v2 的 client.uns 完成新架构集合、Schema
5
5
 
6
6
  # UNS 数据操作
7
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 凭证不能管理数据授权。
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(平台里由平台注入 App 的 Key),或显式传 apiHost/apiKey。数据权限不自动授予管理权限:管理操作中 App 凭证只能调用本 App 集合的 appCollectionsEnsure / SchemaReplace / Archive / Restore;appDataPolicyGet/Update 的调用方必须是持有 uns.write 的登录会话、持有 uns.write 且 full_access、不绑定 App 的 personal / agent Key,或治理会话;新增授权(新集合、放宽权限、从 disabled 重新启用)只接受工作区 Owner/Admin 的人工会话或平台导入,其他调用方回 403(reason `app_grant_admin_required` / `app_grant_agent_forbidden`),收窄和移除不受此限;运行时 App 凭证不能管理数据授权。
9
9
 
10
10
  先按[命名空间指导](references/namespace.md)区分凭证:`namespace()` 走 OpenAPI,但当前不对 App Key 开放;App 查询可用集合走 `dataAppContext`,不能改用同样受限的 `appCollectionsList`。Agent / Personal Key 也须满足当前工作空间权限;不要从“使用 SDK / MCP”推定权限。
11
11
 
@@ -19,6 +19,7 @@ UNS 管数据命名空间、集合、Record 和 Sample;外部连接与脚本
19
19
  | 查询集合、筛选/排序记录 | [查询](references/read.md) | dataAppContext、metadataFind/FindOne(非 App 凭证)、collectionDescribe、recordFind |
20
20
  | 改记录、集合名称、扩展 Schema | [修改](references/update.md) | recordUpdateOne、metadataUpdateOne/Many |
21
21
  | 删记录、软删除集合 | [删除](references/delete.md) | recordDeleteOne、metadataDeleteOne/Many |
22
+ | 精确删除选中的历史样本 | [删除](references/delete.md#精确删除历史样本) | sampleFind、sampleDeleteExact(非 App 凭证) |
22
23
  | 永久删除回收站集合 | [删除](references/delete.md) | metadataPurge、jobs |
23
24
  | 恢复集合或记录 | [恢复](references/restore.md) | metadataRestore、recordRestore |
24
25
  | 分页、历史采样、事务、写入结果 | [数据工作流](references/data-workflows.md) | sampleFind、recordTransactionExecute |
@@ -46,7 +47,7 @@ export async function readAppRecords(collectionId: string) {
46
47
  1. 记录行是 id/data/revision;filter 与 update 的业务路径用 name、customer.name,不加 data.;系统路径用 _id、_meta.revision。
47
48
  2. 改/删使用最近写回执或读取取得的 revision 作为 expectedRevision;创建时可用 returnDocument:'after' 取得快照。历史重放不是当前最新版本,冲突时显式处理;不同操作使用独立意图键,长度上限以当前操作契约为准。
48
49
  3. metadataUpdateOne 的 $set.recordSchema 替换完整 Schema,不迁移已有记录;未更新的 sampleSchema/indexes 保留。先预检兼容性。
49
- 4. metadataDeleteOne/Many 软删除集合并保留 Record/Sample;metadataPurge 永久清理明确 ID 的回收站集合。accepted 只表示任务已受理,须用 jobs 查询逐项结果。Record 删除会物理删除关联 Sample,恢复 Record 不会恢复这些 Sample。
50
+ 4. metadataDeleteOne/Many 软删除保留数据与索引,历史按既有授权只读;目录原子回收有效子树,恢复只覆盖同次批次,详见[回收规则](references/delete.md#回收规则)。metadataPurge 永久清理明确 ID 的回收站集合,accepted 只表示任务已受理,须用 jobs 查询逐项结果。Record 删除会物理删除关联 Sample,恢复 Record 不会恢复这些 Sample。
50
51
  5. recordFind 使用 pageInfo.hasMore/nextCursor,limit 1–200;sort 为 [['field',1]]。单页不是全量,跨页不是稳定快照。
51
52
  6. Sample 的时间是 Unix 毫秒,查询范围 [from,to)。Record 代表业务实体,不用覆盖它来保存历史。
52
53
 
@@ -29,8 +29,6 @@ export async function createDeviceCollection(
29
29
  },
30
30
  },
31
31
  };
32
- const preview = await client.uns.metadataInsertOne({ ...request, dryRun: true });
33
- if (preview.data.status !== 'validated') throw new Error('集合预检未通过');
34
32
  const receipt = await client.uns.metadataInsertOne({ ...request, dryRun: false, idempotencyKey: intentKey });
35
33
  if (receipt.data.status !== 'committed') return { state: 'unconfirmed' as const, receipt };
36
34
  const current = await client.uns.metadataFind({ kind: 'collection', filter: { _id: collectionId }, limit: 1 });
@@ -39,6 +37,8 @@ export async function createDeviceCollection(
39
37
  }
40
38
  ```
41
39
 
40
+ 普通创建直接提交,服务端在提交时校验。只有需要展示预检结果时才单独使用 `dryRun: true`,预检不锁定提交条件。示例提交后读取一次,是为了返回服务端补全的集合结构和 revision;仅需创建回执时可省略这次读取。
41
+
42
42
  单条 Record 新增默认返回 insertedId/status/replayed。需要创建后的数据、revision 和时间戳时,传 returnDocument:'after',同一次写入返回 value,无需再查询;可配合 projection,调用者必须同时具有读取权限。幂等重放返回原始创建快照,后续读取最新状态仍用 recordGet/recordFind。dryRun 的 value 是未提交预览。
43
43
 
44
44
  returnDocument 只允许 none(默认)或 after,不支持 before;projection 只能和 after 一起传。新增仍是 insert,已存在的 ID 不会变成更新。after 的 value 包括服务端补入的字段,但只返回当前授权可见的数据:普通数据权限需 uns.write 和 uns.read,App 还要满足集合的 record:write / record:read 及当前员工、行和字段策略。服务端在提交前校验读取权限,重放时也重新校验;旧回执不能绕过已撤销的授权。
@@ -1,56 +1,83 @@
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, intentKey: string, client: Tier0Client = createClient(),
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: current.revision,
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,或 metadataDeleteOne + returnDocument:before 返回删除前对象。它们执行软删除,增加 revision,不隐式级联目录后代、引用对象或集合数据。集合有 Record/Sample 或配置引用不阻止明确删除;引用方保留并得到目标已删除的结果。目录和 Topic 保留原有约束,不能自行扩展删除范围。已知 revision 可直接提交,不要求每次重新读取或先 dryRun。
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, intentKey: string, client: Tier0Client = createClient(),
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: current.revision,
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
- const deleted = await client.uns.metadataFind({ kind: 'collection', filter: { _id: id }, state: 'deleted', limit: 1 });
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。默认原子;超过上限整体拒绝,不会静默只删第一批。批量删除前可 dryRun。state 默认 active,删除对象通过 state=deleted 查询;all 同时包含两种状态。
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)。保留的物理索引仍占配额,归档不释放索引空间。
56
+
57
+ 业务记录使用 recordDeleteOne/Many,传 collectionId 和明确 filter,编辑型流程同时传 expectedRevision。不要把元数据软删除解释成逐条删除该集合的 Record。Sample 使用独立的精确清单或显式时间范围删除接口;清理选定样本使用下面的 sampleDeleteExact。
58
+
59
+ 成功回执已确认本次操作,不再固定读回验证。结果未知时保留原意图键,按对象身份核对;需要展示最新状态时再刷新。版本冲突应重新读取并由调用方处理,不自动覆盖。
60
+
61
+ ## 精确删除历史样本
62
+
63
+ Workspace 管理调用方通过 `client.uns.sampleDeleteExact` 删除当前集合内的明确样本清单,最多 200 条,需要 `uns.delete` 权限;MCP 凭证另需 `uns.write` 授权。运行时 App Key 不支持此操作,也没有对应的 App 数据层方法。
64
+
65
+ 从 `sampleFind` 结果复制 `{recordId,id,time}`,保留原始 Unix 毫秒时间;`id` 不能替代完整身份,`receivedAt` 不能替代 `time`。不接受空清单、重复身份、filter 或时间范围。清理重复样本时,由业务判断哪些样本重复、保留哪条,只把多余项放入清单;接口本身不判断重复,也不改变采集逻辑。
66
+
67
+ ```ts
68
+ import { createClient, type Tier0Client, type UnsV2SampleDeleteExactReq } from '@tier0/sdk-v2';
69
+
70
+ export async function deleteSelectedSamples(
71
+ collectionId: string, samples: UnsV2SampleDeleteExactReq['samples'],
72
+ intentKey: string, dryRun = true, client: Tier0Client = createClient(),
73
+ ) {
74
+ return client.uns.sampleDeleteExact({ collectionId, samples, dryRun, idempotencyKey: intentKey });
75
+ }
76
+ ```
50
77
 
51
- 业务记录使用 recordDeleteOne/Many,传 collectionId 和明确 filter,编辑型流程同时传 expectedRevision。不要把元数据软删除解释成逐条删除该集合的 Record。Sample 的删除是独立的显式时间范围操作,具体影响遵循当前数据 API 契约。
78
+ `dryRun:true` 仅返回当时的匹配数量,不删除或预留样本。执行时使用同一清单和 `dryRun:false`;省略 dryRun 也会执行。返回 `{deletedCount,missingCount,dryRun}`:deletedCount 在预览时是预计删除数,执行时是实际删除数;missingCount 是未匹配到的完整键数。整批精确删除与回执同事务,不修改父 Record 或未列出的样本。
52
79
 
53
- 恢复见[恢复](restore.md)。未知写入结果保留原意图键,先按对象身份核对,不自动换键重试。
80
+ 相同执行请求重试复用相同 `idempotencyKey`,修改清单需要新键。SDK 不自动重试;结局未知时按完整键查询核对,遵循错误中的 execution/retry,不改用范围删除或重建集合。`sampleDeleteRange` 仍不向 MCP 开放。
54
81
 
55
82
  ## 永久删除回收站集合
56
83
 
@@ -6,8 +6,8 @@
6
6
 
7
7
  | 凭证 | Namespace / metadata 查询 | 发现并读取集合 |
8
8
  | --- | --- | --- |
9
- | 未绑定 App 的 Agent / Personal Key | 有当前工作空间权限及 Key scope 时可用;Agent Key 还会复核真实负责人的状态和权限 | 按当前工作空间授权查询,再调用 `recordFind` |
10
- | App Key | 当前不开放;`appCollectionsList` 也不能调用 | `dataAppContext({})` 返回自有及已授权集合、目录路径、结构和有效权限,再按集合 ID 调 `recordFind` |
9
+ | 未绑定 App 的 Agent / Personal Key | 有当前工作空间权限及 Key scope 时可用;Agent Key 还会复核真实负责人的状态和权限 | 已知集合 ID 时直接调用 `recordFind`;需要发现集合时按工作空间权限查询元数据 |
10
+ | App Key | 当前不开放;`appCollectionsList` 也不能调用 | 已知集合 ID 时直接调用 `recordFind`;需要发现集合及结构时使用 `dataAppContext({})` |
11
11
  | 平台系统 Service Key | 按实际系统凭证授权,不按“Agent 正在调用”推定权限 | 带 Builder 目标时,指定 App 操作可使用目标 App 视角;不代表所有查询都变成 App 视角 |
12
12
 
13
13
  目录位置不产生数据授权。App 自有集合的正常创建流程提供相应授权,但读取仍受凭证权限、App 状态及数据策略约束;不能仅凭 `ownerAppId` 推定任何接口都可调用。SDK 和 MCP 使用相同凭证时仍受服务端授权约束。
@@ -60,4 +60,4 @@ export async function createDirectoryAndTopic(
60
60
 
61
61
  transact 始终原子,结果通过 opId 对应输入;dryRun=true 校验,不预留 ID 或名称。正式执行仍重新检查。创建结果引用仅用于契约支持的关系字段,不能把任意字符串或脚本当引用。
62
62
 
63
- 名称、父目录、Schema、引用与权限由服务端校验。移动不改变对象 ID;删除目录不隐式清空后代;删除 Topic 不删除所引用的 Collection。引用查询仍使用 references 专用方法。
63
+ 名称、父目录、Schema、引用与权限由服务端校验。移动不改变对象 ID;删除目录原子回收有效子树,删除 Topic 不删除所引用的 Collection。外部引用不阻止回收;范围、授权和恢复规则见[删除](delete.md#回收规则)与[恢复](restore.md)。引用查询仍使用 references 专用方法。
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## App Key:从授权上下文读取
4
4
 
5
- App Key 使用 `dataAppContext`,不调用 `namespace`、`metadataFind` 或 `appCollectionsList`。上下文包含可访问集合的名称、目录路径、Schema 和有效权限;按已配置 alias 精确定位,不能随意选择列表第一项。已知集合 ID 时可直接调用 `recordFind`,不要求每次预先加载上下文。
5
+ App Key 已知集合 ID 时直接调用 `recordFind`;需要发现集合时使用 `dataAppContext`,不调用 `namespace`、`metadataFind` 或 `appCollectionsList`。上下文包含可访问集合的名称、目录路径、Schema 和有效权限;按已配置 alias 精确定位,不能随意选择列表第一项。
6
6
 
7
7
  ```ts
8
8
  import { createClient, type Tier0Client } from '@tier0/sdk-v2';
@@ -36,13 +36,10 @@ export async function readAppRecords(alias: string, client: Tier0Client = create
36
36
  import { createClient, type Tier0Client } from '@tier0/sdk-v2';
37
37
 
38
38
  export async function readDevices(collectionId: string, client: Tier0Client = createClient()) {
39
- const collection = await client.uns.metadataFind({ kind: 'collection', filter: { _id: collectionId }, limit: 1 });
40
- const definition = collection.data.items[0];
41
- if (!definition) throw new Error('集合不存在或不可见');
42
39
  const records = await client.uns.recordFind({
43
40
  collectionId, filter: { enabled: true }, sort: [['name', 1]], limit: 20,
44
41
  });
45
- return { definition: definition.data, items: records.data.items, pageInfo: records.data.pageInfo };
42
+ return { items: records.data.items, pageInfo: records.data.pageInfo };
46
43
  }
47
44
 
48
45
  export async function listCollections(page = 1, client: Tier0Client = createClient()) {
@@ -54,4 +51,12 @@ export async function listCollections(page = 1, client: Tier0Client = createClie
54
51
 
55
52
  按 pageInfo.hasMore/nextPage 继续元数据分页;不要把第一页当全量。recordFind 的游标循环见[数据工作流](data-workflows.md)。sort 是数组元组。
56
53
 
57
- 以上 metadataFind 示例用于 Workspace 集成(会话、personal / agent Key)。App 凭证不能调用 metadataFind:用 dataAppContext 精确选择授权 alias,集合的 revision、indexStatus、ttl 也在其中。数据授权不等于元数据管理授权。网络/权限错误必须传播,不能用空数组掩盖错误。
54
+ 已知 collectionId 时直接读取记录,由服务端校验集合及操作权限;不先调用 metadataFind、dataAppContext、collectionDescribe 或 ensure。需要选择集合或查看结构时,再单独读取对应上下文。
55
+
56
+ listCollections 的 metadataFind 仅用于 Workspace 集成(会话、personal / agent Key)。App 凭证不能调用 metadataFind;未知 collectionId 时用 dataAppContext 选择已授权集合,集合的 revision、indexStatus、ttl 也在其中。readDevices 只需要目标集合的记录读取权限。网络/权限错误必须传播,不能用空数组掩盖错误。
57
+
58
+ ## 回收集合的历史读取
59
+
60
+ 按原 collectionId 读取 Record、Sample、count 或 aggregate;既有授权和字段权限仍生效。活跃 Topic 可继续读取其引用的回收集合。活跃 App 的 dataAppContext 保留仍可读的回收集合,但不授予新增能力;回收中的 App 自身不能运行。
61
+
62
+ 已知 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?}],支持一个或多个明确 ID。revision 应从 state=deleted 的当前对象读取。恢复重新校验名称、父目录、Schema 与引用;不会恢复其他对象,冲突时原子批次回滚。
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, intentKey: string, client: Tier0Client = createClient(),
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: deleted.revision }], idempotencyKey: intentKey,
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
- const current = (await client.uns.metadataFind({ kind: 'collection', filter: { _id: id }, limit: 1 })).data.items[0];
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
- const found = await client.uns.recordFind({ collectionId, filter: { _id: id }, limit: 1 });
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,记录恢复可按需查询。
@@ -1,22 +0,0 @@
1
- export declare const APP_REQUEST_IDENTITY_CONTRACT: {
2
- readonly version: "tier0.app-request-identity/v1";
3
- readonly header: "X-Tier0-App-Identity";
4
- readonly type: "tier0-app-request+jwt";
5
- readonly algorithm: "EdDSA";
6
- readonly issuer: "tier0-app-gateway";
7
- readonly audience: "tier0-app-runtime";
8
- readonly maxTokenBytes: 8192;
9
- readonly maxBodyBytes: 65536;
10
- readonly maxURIBytes: 8192;
11
- readonly maxTTLSeconds: 30;
12
- readonly clockSkewSeconds: 5;
13
- readonly maxRoles: 32;
14
- readonly maxRoleBytes: 128;
15
- readonly maxKeys: 8;
16
- readonly keyIDPattern: "^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$";
17
- readonly appIDPattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$";
18
- readonly requiredClaims: readonly ["ver", "iss", "aud", "sub", "workspaceID", "appID", "method", "requestURI", "bodySHA256", "iat", "exp", "jti", "roles"];
19
- readonly methodPattern: "^[A-Z]{1,16}$";
20
- readonly requiredHeaderFields: readonly ["alg", "typ", "kid"];
21
- readonly publicKeyFields: readonly ["kty", "crv", "x", "kid"];
22
- };