@tier0/sdk-v2 0.1.0 → 0.2.2

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 (170) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +13 -11
  3. package/dist/cjs/app-identity-contract.generated.js.map +1 -1
  4. package/dist/cjs/capabilities.js.map +1 -1
  5. package/dist/cjs/cloud-server.js.map +1 -1
  6. package/dist/cjs/cloud-transport.d.ts +21 -0
  7. package/dist/cjs/cloud-transport.js +81 -1
  8. package/dist/cjs/cloud-transport.js.map +1 -1
  9. package/dist/cjs/controls.js.map +1 -1
  10. package/dist/cjs/data/bootstrap.d.ts +4 -0
  11. package/dist/cjs/data/bootstrap.js +15 -6
  12. package/dist/cjs/data/bootstrap.js.map +1 -1
  13. package/dist/cjs/data/call.d.ts +26 -9
  14. package/dist/cjs/data/call.js +179 -14
  15. package/dist/cjs/data/call.js.map +1 -1
  16. package/dist/cjs/data/client.d.ts +44 -2
  17. package/dist/cjs/data/client.js +87 -4
  18. package/dist/cjs/data/client.js.map +1 -1
  19. package/dist/cjs/data/collection.d.ts +151 -14
  20. package/dist/cjs/data/collection.js +213 -50
  21. package/dist/cjs/data/collection.js.map +1 -1
  22. package/dist/cjs/data/context.d.ts +4 -2
  23. package/dist/cjs/data/context.js +14 -3
  24. package/dist/cjs/data/context.js.map +1 -1
  25. package/dist/cjs/data/define.d.ts +9 -1
  26. package/dist/cjs/data/define.js.map +1 -1
  27. package/dist/cjs/data/error.d.ts +30 -12
  28. package/dist/cjs/data/error.js +61 -63
  29. package/dist/cjs/data/error.js.map +1 -1
  30. package/dist/cjs/data/index.d.ts +6 -3
  31. package/dist/cjs/data/index.js +7 -1
  32. package/dist/cjs/data/index.js.map +1 -1
  33. package/dist/cjs/data/schema.d.ts +7 -1
  34. package/dist/cjs/data/schema.js +54 -4
  35. package/dist/cjs/data/schema.js.map +1 -1
  36. package/dist/cjs/data/subscribe.d.ts +108 -0
  37. package/dist/cjs/data/subscribe.js +270 -0
  38. package/dist/cjs/data/subscribe.js.map +1 -0
  39. package/dist/cjs/mq/broker.js.map +1 -1
  40. package/dist/cjs/mq/client.js.map +1 -1
  41. package/dist/cjs/mq/index.js.map +1 -1
  42. package/dist/cjs/mq/types.js.map +1 -1
  43. package/dist/cjs/runtime-context.js.map +1 -1
  44. package/dist/cjs/runtime-env.js.map +1 -1
  45. package/dist/cjs/uns-v2/client.d.ts +90 -12
  46. package/dist/cjs/uns-v2/client.js +238 -63
  47. package/dist/cjs/uns-v2/client.js.map +1 -1
  48. package/dist/cjs/uns-v2/codec.js.map +1 -1
  49. package/dist/cjs/uns-v2/generated.d.ts +9619 -6923
  50. package/dist/cjs/uns-v2/generated.js +124 -119
  51. package/dist/cjs/uns-v2/generated.js.map +1 -1
  52. package/dist/cjs/uns-v2/index.js.map +1 -1
  53. package/dist/cjs/uns-v2/sse.d.ts +20 -0
  54. package/dist/cjs/uns-v2/sse.js +65 -0
  55. package/dist/cjs/uns-v2/sse.js.map +1 -0
  56. package/dist/esm/app-identity-contract.generated.js.map +1 -1
  57. package/dist/esm/capabilities.js.map +1 -1
  58. package/dist/esm/cloud-server.js.map +1 -1
  59. package/dist/esm/cloud-transport.d.ts +21 -0
  60. package/dist/esm/cloud-transport.js +81 -1
  61. package/dist/esm/cloud-transport.js.map +1 -1
  62. package/dist/esm/controls.js.map +1 -1
  63. package/dist/esm/data/bootstrap.d.ts +4 -0
  64. package/dist/esm/data/bootstrap.js +14 -5
  65. package/dist/esm/data/bootstrap.js.map +1 -1
  66. package/dist/esm/data/call.d.ts +26 -9
  67. package/dist/esm/data/call.js +177 -13
  68. package/dist/esm/data/call.js.map +1 -1
  69. package/dist/esm/data/client.d.ts +44 -2
  70. package/dist/esm/data/client.js +89 -7
  71. package/dist/esm/data/client.js.map +1 -1
  72. package/dist/esm/data/collection.d.ts +151 -14
  73. package/dist/esm/data/collection.js +212 -49
  74. package/dist/esm/data/collection.js.map +1 -1
  75. package/dist/esm/data/context.d.ts +4 -2
  76. package/dist/esm/data/context.js +13 -3
  77. package/dist/esm/data/context.js.map +1 -1
  78. package/dist/esm/data/define.d.ts +9 -1
  79. package/dist/esm/data/define.js.map +1 -1
  80. package/dist/esm/data/error.d.ts +30 -12
  81. package/dist/esm/data/error.js +61 -63
  82. package/dist/esm/data/error.js.map +1 -1
  83. package/dist/esm/data/index.d.ts +6 -3
  84. package/dist/esm/data/index.js +3 -1
  85. package/dist/esm/data/index.js.map +1 -1
  86. package/dist/esm/data/schema.d.ts +7 -1
  87. package/dist/esm/data/schema.js +54 -4
  88. package/dist/esm/data/schema.js.map +1 -1
  89. package/dist/esm/data/subscribe.d.ts +108 -0
  90. package/dist/esm/data/subscribe.js +265 -0
  91. package/dist/esm/data/subscribe.js.map +1 -0
  92. package/dist/esm/mq/broker.js.map +1 -1
  93. package/dist/esm/mq/client.js.map +1 -1
  94. package/dist/esm/mq/index.js.map +1 -1
  95. package/dist/esm/mq/types.js.map +1 -1
  96. package/dist/esm/runtime-context.js.map +1 -1
  97. package/dist/esm/runtime-env.js.map +1 -1
  98. package/dist/esm/uns-v2/client.d.ts +90 -12
  99. package/dist/esm/uns-v2/client.js +236 -63
  100. package/dist/esm/uns-v2/client.js.map +1 -1
  101. package/dist/esm/uns-v2/codec.js.map +1 -1
  102. package/dist/esm/uns-v2/generated.d.ts +9619 -6923
  103. package/dist/esm/uns-v2/generated.js +123 -118
  104. package/dist/esm/uns-v2/generated.js.map +1 -1
  105. package/dist/esm/uns-v2/index.js.map +1 -1
  106. package/dist/esm/uns-v2/sse.d.ts +20 -0
  107. package/dist/esm/uns-v2/sse.js +60 -0
  108. package/dist/esm/uns-v2/sse.js.map +1 -0
  109. package/package.json +29 -16
  110. package/skills/tier0-sdk-v2/SKILL.md +14 -14
  111. package/skills/{tier0-sdk-v2-data → tier0-sdk-v2/tier0-sdk-v2-data}/SKILL.md +3 -2
  112. package/skills/{tier0-sdk-v2-data → tier0-sdk-v2/tier0-sdk-v2-data}/references/consistency.md +3 -3
  113. package/skills/{tier0-sdk-v2-data → tier0-sdk-v2/tier0-sdk-v2-data}/references/errors.md +23 -10
  114. package/skills/{tier0-sdk-v2-data → tier0-sdk-v2/tier0-sdk-v2-data}/references/modeling.md +36 -0
  115. package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/read-write.md +172 -0
  116. package/skills/{tier0-sdk-v2-mq → tier0-sdk-v2/tier0-sdk-v2-mq}/references/quickstart.md +1 -1
  117. package/skills/{tier0-sdk-v2-source → tier0-sdk-v2/tier0-sdk-v2-source}/SKILL.md +3 -3
  118. package/skills/{tier0-sdk-v2-source → tier0-sdk-v2/tier0-sdk-v2-source}/references/create.md +3 -6
  119. package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/delete.md +39 -0
  120. package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/state.md +36 -0
  121. package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/update.md +80 -0
  122. package/skills/{tier0-sdk-v2-stream → tier0-sdk-v2/tier0-sdk-v2-stream}/SKILL.md +6 -6
  123. package/skills/{tier0-sdk-v2-stream → tier0-sdk-v2/tier0-sdk-v2-stream}/references/create.md +15 -14
  124. package/skills/{tier0-sdk-v2-stream → tier0-sdk-v2/tier0-sdk-v2-stream}/references/deploy.md +11 -9
  125. package/skills/{tier0-sdk-v2-stream → tier0-sdk-v2/tier0-sdk-v2-stream}/references/lifecycle.md +2 -1
  126. package/skills/{tier0-sdk-v2-stream → tier0-sdk-v2/tier0-sdk-v2-stream}/references/update.md +5 -6
  127. package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/SKILL.md +55 -0
  128. package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/create.md +57 -0
  129. package/skills/{tier0-sdk-v2-uns → tier0-sdk-v2/tier0-sdk-v2-uns}/references/data-workflows.md +3 -7
  130. package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/delete.md +53 -0
  131. package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/namespace.md +38 -0
  132. package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/read.md +34 -0
  133. package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/restore.md +39 -0
  134. package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/update.md +62 -0
  135. package/skills/tier0-sdk-v2-data/references/read-write.md +0 -72
  136. package/skills/tier0-sdk-v2-source/references/delete.md +0 -47
  137. package/skills/tier0-sdk-v2-source/references/state.md +0 -37
  138. package/skills/tier0-sdk-v2-source/references/update.md +0 -61
  139. package/skills/tier0-sdk-v2-uns/SKILL.md +0 -53
  140. package/skills/tier0-sdk-v2-uns/references/create.md +0 -85
  141. package/skills/tier0-sdk-v2-uns/references/delete.md +0 -69
  142. package/skills/tier0-sdk-v2-uns/references/namespace.md +0 -75
  143. package/skills/tier0-sdk-v2-uns/references/read.md +0 -32
  144. package/skills/tier0-sdk-v2-uns/references/restore.md +0 -41
  145. package/skills/tier0-sdk-v2-uns/references/update.md +0 -86
  146. /package/skills/{tier0-sdk-v2-agent → tier0-sdk-v2/tier0-sdk-v2-agent}/SKILL.md +0 -0
  147. /package/skills/{tier0-sdk-v2-agent → tier0-sdk-v2/tier0-sdk-v2-agent}/references/results.md +0 -0
  148. /package/skills/{tier0-sdk-v2-controls → tier0-sdk-v2/tier0-sdk-v2-controls}/SKILL.md +0 -0
  149. /package/skills/{tier0-sdk-v2-data → tier0-sdk-v2/tier0-sdk-v2-data}/references/schema-evolution.md +0 -0
  150. /package/skills/{tier0-sdk-v2-files → tier0-sdk-v2/tier0-sdk-v2-files}/SKILL.md +0 -0
  151. /package/skills/{tier0-sdk-v2-files → tier0-sdk-v2/tier0-sdk-v2-files}/references/delete.md +0 -0
  152. /package/skills/{tier0-sdk-v2-files → tier0-sdk-v2/tier0-sdk-v2-files}/references/download.md +0 -0
  153. /package/skills/{tier0-sdk-v2-files → tier0-sdk-v2/tier0-sdk-v2-files}/references/multipart.md +0 -0
  154. /package/skills/{tier0-sdk-v2-files → tier0-sdk-v2/tier0-sdk-v2-files}/references/upload.md +0 -0
  155. /package/skills/{tier0-sdk-v2-files → tier0-sdk-v2/tier0-sdk-v2-files}/references/url.md +0 -0
  156. /package/skills/{tier0-sdk-v2-members → tier0-sdk-v2/tier0-sdk-v2-members}/SKILL.md +0 -0
  157. /package/skills/{tier0-sdk-v2-members → tier0-sdk-v2/tier0-sdk-v2-members}/references/app-members.md +0 -0
  158. /package/skills/{tier0-sdk-v2-members → tier0-sdk-v2/tier0-sdk-v2-members}/references/platform-members.md +0 -0
  159. /package/skills/{tier0-sdk-v2-mq → tier0-sdk-v2/tier0-sdk-v2-mq}/SKILL.md +0 -0
  160. /package/skills/{tier0-sdk-v2-notifications → tier0-sdk-v2/tier0-sdk-v2-notifications}/SKILL.md +0 -0
  161. /package/skills/{tier0-sdk-v2-notifications → tier0-sdk-v2/tier0-sdk-v2-notifications}/references/get.md +0 -0
  162. /package/skills/{tier0-sdk-v2-notifications → tier0-sdk-v2/tier0-sdk-v2-notifications}/references/send.md +0 -0
  163. /package/skills/{tier0-sdk-v2-source → tier0-sdk-v2/tier0-sdk-v2-source}/references/changes.md +0 -0
  164. /package/skills/{tier0-sdk-v2-source → tier0-sdk-v2/tier0-sdk-v2-source}/references/protocols.md +0 -0
  165. /package/skills/{tier0-sdk-v2-source → tier0-sdk-v2/tier0-sdk-v2-source}/references/read.md +0 -0
  166. /package/skills/{tier0-sdk-v2-stream → tier0-sdk-v2/tier0-sdk-v2-stream}/references/delete.md +0 -0
  167. /package/skills/{tier0-sdk-v2-stream → tier0-sdk-v2/tier0-sdk-v2-stream}/references/read.md +0 -0
  168. /package/skills/{tier0-sdk-v2-system → tier0-sdk-v2/tier0-sdk-v2-system}/SKILL.md +0 -0
  169. /package/skills/{tier0-sdk-v2-system → tier0-sdk-v2/tier0-sdk-v2-system}/references/info.md +0 -0
  170. /package/skills/{tier0-sdk-v2-system → tier0-sdk-v2/tier0-sdk-v2-system}/references/whoami.md +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/uns-v2/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC","sourcesContent":["export * from './generated.js';\r\nexport * from './client.js';\r\nexport { UnsV2RawNumber } from './codec.js';\r\nexport { createUnsDataClient } from './data-client.js';\r\nexport type { UnsDataClient, UnsDataClientConfig, UnsDataOperationName } from './data-client.js';\r\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/uns-v2/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC","sourcesContent":["export * from './generated.js';\nexport * from './client.js';\nexport { UnsV2RawNumber } from './codec.js';\nexport { createUnsDataClient } from './data-client.js';\nexport type { UnsDataClient, UnsDataClientConfig, UnsDataOperationName } from './data-client.js';\n"]}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Minimal `text/event-stream` parser (WHATWG server-sent events). Unlike
3
+ * EventSource it also dispatches a named event without data lines, and
4
+ * reports comment lines, so a keep-alive in either form is visible.
5
+ */
6
+ export interface ServerSentEvent {
7
+ /** `message` when the server names no event. */
8
+ event: string;
9
+ data: string;
10
+ /** Last `id:` seen on the stream so far, as EventSource keeps it. */
11
+ id?: string;
12
+ }
13
+ /** A line longer than this is not an event stream (e.g. a proxy page without newlines). */
14
+ export declare const SSE_MAX_LINE_BYTES: number;
15
+ export declare class ServerSentEventsError extends Error {
16
+ }
17
+ /** Yields each dispatched event and each comment line; `retry:` is ignored. Throws ServerSentEventsError on an oversized line. */
18
+ export declare function parseServerSentEvents(stream: ReadableStream<Uint8Array>): AsyncGenerator<ServerSentEvent | {
19
+ comment: string;
20
+ }>;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Minimal `text/event-stream` parser (WHATWG server-sent events). Unlike
3
+ * EventSource it also dispatches a named event without data lines, and
4
+ * reports comment lines, so a keep-alive in either form is visible.
5
+ */
6
+ /** A line longer than this is not an event stream (e.g. a proxy page without newlines). */
7
+ export const SSE_MAX_LINE_BYTES = 1 << 20;
8
+ export class ServerSentEventsError extends Error {
9
+ }
10
+ /** Yields each dispatched event and each comment line; `retry:` is ignored. Throws ServerSentEventsError on an oversized line. */
11
+ export async function* parseServerSentEvents(stream) {
12
+ const reader = stream.getReader();
13
+ const decoder = new TextDecoder();
14
+ let buffer = '';
15
+ let event = '';
16
+ let data = [];
17
+ let id;
18
+ try {
19
+ while (true) {
20
+ const { done, value } = await reader.read();
21
+ buffer += done ? decoder.decode() : decoder.decode(value, { stream: true });
22
+ // A trailing "\r" may be the first half of "\r\n": keep it for the next chunk.
23
+ const lines = buffer.split(/\r\n|\n|\r(?!$)/);
24
+ buffer = done ? '' : (lines.pop() ?? '');
25
+ // UTF-16 length bounds the UTF-8 size from below; good enough to stop runaway input.
26
+ if (buffer.length > SSE_MAX_LINE_BYTES)
27
+ throw new ServerSentEventsError('Server-sent event line too long.');
28
+ for (const line of lines) {
29
+ if (line === '') {
30
+ if (data.length > 0 || event)
31
+ yield { event: event || 'message', data: data.join('\n'), ...(id === undefined ? {} : { id }) };
32
+ event = '';
33
+ data = [];
34
+ continue;
35
+ }
36
+ if (line.startsWith(':')) {
37
+ yield { comment: line.slice(1).trimStart() };
38
+ continue;
39
+ }
40
+ const colon = line.indexOf(':');
41
+ const field = colon < 0 ? line : line.slice(0, colon);
42
+ const raw = colon < 0 ? '' : line.slice(colon + 1);
43
+ const text = raw.startsWith(' ') ? raw.slice(1) : raw;
44
+ if (field === 'event')
45
+ event = text;
46
+ else if (field === 'data')
47
+ data.push(text);
48
+ else if (field === 'id' && !text.includes('\u0000'))
49
+ id = text;
50
+ }
51
+ if (done)
52
+ return;
53
+ }
54
+ }
55
+ finally {
56
+ await reader.cancel().catch(() => undefined);
57
+ reader.releaseLock();
58
+ }
59
+ }
60
+ //# sourceMappingURL=sse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse.js","sourceRoot":"","sources":["../../../src/uns-v2/sse.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Minimal `text/event-stream` parser (WHATWG server-sent events). Unlike\n * EventSource it also dispatches a named event without data lines, and\n * reports comment lines, so a keep-alive in either form is visible.\n */\n\nexport interface ServerSentEvent {\n /** `message` when the server names no event. */\n event: string;\n data: string;\n /** Last `id:` seen on the stream so far, as EventSource keeps it. */\n id?: string;\n}\n\n/** A line longer than this is not an event stream (e.g. a proxy page without newlines). */\nexport const SSE_MAX_LINE_BYTES = 1 << 20;\n\nexport class ServerSentEventsError extends Error {}\n\n/** Yields each dispatched event and each comment line; `retry:` is ignored. Throws ServerSentEventsError on an oversized line. */\nexport async function* parseServerSentEvents(stream: ReadableStream<Uint8Array>): AsyncGenerator<ServerSentEvent | { comment: string }> {\n const reader = stream.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n let event = '';\n let data: string[] = [];\n let id: string | undefined;\n try {\n while (true) {\n const { done, value } = await reader.read();\n buffer += done ? decoder.decode() : decoder.decode(value, { stream: true });\n // A trailing \"\\r\" may be the first half of \"\\r\\n\": keep it for the next chunk.\n const lines = buffer.split(/\\r\\n|\\n|\\r(?!$)/);\n buffer = done ? '' : (lines.pop() ?? '');\n // UTF-16 length bounds the UTF-8 size from below; good enough to stop runaway input.\n if (buffer.length > SSE_MAX_LINE_BYTES) throw new ServerSentEventsError('Server-sent event line too long.');\n for (const line of lines) {\n if (line === '') {\n if (data.length > 0 || event) yield { event: event || 'message', data: data.join('\\n'), ...(id === undefined ? {} : { id }) };\n event = '';\n data = [];\n continue;\n }\n if (line.startsWith(':')) {\n yield { comment: line.slice(1).trimStart() };\n continue;\n }\n const colon = line.indexOf(':');\n const field = colon < 0 ? line : line.slice(0, colon);\n const raw = colon < 0 ? '' : line.slice(colon + 1);\n const text = raw.startsWith(' ') ? raw.slice(1) : raw;\n if (field === 'event') event = text;\n else if (field === 'data') data.push(text);\n else if (field === 'id' && !text.includes('\\u0000')) id = text;\n }\n if (done) return;\n }\n } finally {\n await reader.cancel().catch(() => undefined);\n reader.releaseLock();\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tier0/sdk-v2",
3
- "version": "0.1.0",
3
+ "version": "0.2.2",
4
4
  "description": "Tier0 Cloud new-architecture SDK for TypeScript and JavaScript",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -58,12 +58,24 @@
58
58
  }
59
59
  },
60
60
  "./source": {
61
- "import": { "types": "./dist/esm/source.d.ts", "default": "./dist/esm/source.js" },
62
- "require": { "types": "./dist/cjs/source.d.ts", "default": "./dist/cjs/source.js" }
61
+ "import": {
62
+ "types": "./dist/esm/source.d.ts",
63
+ "default": "./dist/esm/source.js"
64
+ },
65
+ "require": {
66
+ "types": "./dist/cjs/source.d.ts",
67
+ "default": "./dist/cjs/source.js"
68
+ }
63
69
  },
64
70
  "./stream": {
65
- "import": { "types": "./dist/esm/stream.d.ts", "default": "./dist/esm/stream.js" },
66
- "require": { "types": "./dist/cjs/stream.d.ts", "default": "./dist/cjs/stream.js" }
71
+ "import": {
72
+ "types": "./dist/esm/stream.d.ts",
73
+ "default": "./dist/esm/stream.js"
74
+ },
75
+ "require": {
76
+ "types": "./dist/cjs/stream.d.ts",
77
+ "default": "./dist/cjs/stream.js"
78
+ }
67
79
  },
68
80
  "./files": {
69
81
  "import": {
@@ -148,6 +160,7 @@
148
160
  "check:skills": "node scripts/check-skills.mjs && node scripts/check-skill-examples.mjs",
149
161
  "typecheck": "tsc --noEmit && tsc -p tsconfig.tests.json",
150
162
  "build": "node scripts/build.mjs",
163
+ "prepare": "npm run build",
151
164
  "test": "vitest run",
152
165
  "check": "npm run check:contracts && npm run check:skills && npm run typecheck && npm test && npm run build && node scripts/check-package.mjs",
153
166
  "prepack": "npm run check:contracts && npm run check:skills && npm run build",
@@ -193,11 +206,11 @@
193
206
  "skills": [
194
207
  {
195
208
  "name": "tier0-sdk-v2-source",
196
- "path": "./skills/tier0-sdk-v2-source"
209
+ "path": "./skills/tier0-sdk-v2/tier0-sdk-v2-source"
197
210
  },
198
211
  {
199
212
  "name": "tier0-sdk-v2-stream",
200
- "path": "./skills/tier0-sdk-v2-stream"
213
+ "path": "./skills/tier0-sdk-v2/tier0-sdk-v2-stream"
201
214
  },
202
215
  {
203
216
  "name": "tier0-sdk-v2",
@@ -205,39 +218,39 @@
205
218
  },
206
219
  {
207
220
  "name": "tier0-sdk-v2-uns",
208
- "path": "./skills/tier0-sdk-v2-uns"
221
+ "path": "./skills/tier0-sdk-v2/tier0-sdk-v2-uns"
209
222
  },
210
223
  {
211
224
  "name": "tier0-sdk-v2-files",
212
- "path": "./skills/tier0-sdk-v2-files"
225
+ "path": "./skills/tier0-sdk-v2/tier0-sdk-v2-files"
213
226
  },
214
227
  {
215
228
  "name": "tier0-sdk-v2-notifications",
216
- "path": "./skills/tier0-sdk-v2-notifications"
229
+ "path": "./skills/tier0-sdk-v2/tier0-sdk-v2-notifications"
217
230
  },
218
231
  {
219
232
  "name": "tier0-sdk-v2-mq",
220
- "path": "./skills/tier0-sdk-v2-mq"
233
+ "path": "./skills/tier0-sdk-v2/tier0-sdk-v2-mq"
221
234
  },
222
235
  {
223
236
  "name": "tier0-sdk-v2-members",
224
- "path": "./skills/tier0-sdk-v2-members"
237
+ "path": "./skills/tier0-sdk-v2/tier0-sdk-v2-members"
225
238
  },
226
239
  {
227
240
  "name": "tier0-sdk-v2-system",
228
- "path": "./skills/tier0-sdk-v2-system"
241
+ "path": "./skills/tier0-sdk-v2/tier0-sdk-v2-system"
229
242
  },
230
243
  {
231
244
  "name": "tier0-sdk-v2-controls",
232
- "path": "./skills/tier0-sdk-v2-controls"
245
+ "path": "./skills/tier0-sdk-v2/tier0-sdk-v2-controls"
233
246
  },
234
247
  {
235
248
  "name": "tier0-sdk-v2-agent",
236
- "path": "./skills/tier0-sdk-v2-agent"
249
+ "path": "./skills/tier0-sdk-v2/tier0-sdk-v2-agent"
237
250
  },
238
251
  {
239
252
  "name": "tier0-sdk-v2-data",
240
- "path": "./skills/tier0-sdk-v2-data"
253
+ "path": "./skills/tier0-sdk-v2/tier0-sdk-v2-data"
241
254
  }
242
255
  ]
243
256
  }
@@ -1,27 +1,27 @@
1
1
  ---
2
2
  name: tier0-sdk-v2
3
- description: 使用 @tier0/sdk-v2 开发或迁移 Tier0 云端 TypeScript/JavaScript 应用,按 Source、Stream、UNS、通知与系统能力选择入口,处理配置、身份和框架集成。
3
+ description: 使用 @tier0/sdk-v2 开发或迁移 Tier0 云端 TypeScript/JavaScript 应用:Source 数据源、Stream 处理程序、UNS 集合与记录、App 自有数据(@tier0/sdk-v2/data)、通知、文件存储、MQTT、成员、系统身份与能力、Controls 和 Agent,按任务进入对应子 Skill,并处理配置、身份和框架集成。
4
4
  ---
5
5
 
6
6
  # Tier0 SDK v2 应用集成
7
7
 
8
- 面向应用代码编写;运行时为 Node.js 20.19+。独立包 `@tier0/sdk-v2` 的当前源码版本为 0.1.0,是否可从 npm 安装须检查实际发布状态;未发布时安装仓库构建的 tarball。不要改回旧包或旧 Flow API。
8
+ 面向应用代码编写;运行时为 Node.js 20.19+。独立包 `@tier0/sdk-v2` 的当前源码版本为 0.2.0(UNS v2 契约 5.0.0),是否可从 npm 安装须检查实际发布状态;未发布时安装仓库构建的 tarball。不要改回旧包或旧 Flow API。
9
9
 
10
10
  ## 选择业务入口
11
11
 
12
12
  | 用户要做什么 | SDK 入口 | 深入说明 |
13
13
  | --- | --- | --- |
14
- | 连接外部系统、维护采集源、检查连接和影响范围 | `client.source` | [Source](../tier0-sdk-v2-source/SKILL.md) |
15
- | 编写处理程序、配置源绑定、校验和发布、查运行记录 | `client.stream` | [Stream](../tier0-sdk-v2-stream/SKILL.md) |
16
- | 集合、目录、Topic、业务记录、历史采样与事务 | `client.uns` | [UNS](../tier0-sdk-v2-uns/SKILL.md) |
17
- | App 自有业务数据:声明集合、Mongo 风格读写与事务 | `@tier0/sdk-v2/data` | [App 数据](../tier0-sdk-v2-data/SKILL.md) |
18
- | 站内信、Web/Desktop 提醒、手机推送 | `client.notifications` | [通知](../tier0-sdk-v2-notifications/SKILL.md) |
19
- | 当前凭证、平台信息、功能是否就绪 | `client.system`、`client.capabilities` | [系统](../tier0-sdk-v2-system/SKILL.md) |
20
- | 附件、头像、导入导出、生成报告 | `client.files` | [文件](../tier0-sdk-v2-files/SKILL.md) |
21
- | 实时主题订阅/发布 | `@tier0/sdk-v2/mq`、`client.mqttAuth` | [MQTT](../tier0-sdk-v2-mq/SKILL.md) |
22
- | Workspace 收件人/成员检索、App 可用成员 | `client.members` | [成员](../tier0-sdk-v2-members/SKILL.md) |
23
- | 调用已发布的 Control | `client.controls` | [Controls](../tier0-sdk-v2-controls/SKILL.md) |
24
- | Agent 作业、运行、分析结果和卡片 | `client.agent` | [Agent](../tier0-sdk-v2-agent/SKILL.md) |
14
+ | 连接外部系统、维护采集源、检查连接和影响范围 | `client.source` | [Source](tier0-sdk-v2-source/SKILL.md) |
15
+ | 编写处理程序、配置源绑定、校验和发布、查运行记录 | `client.stream` | [Stream](tier0-sdk-v2-stream/SKILL.md) |
16
+ | 集合、目录、Topic、业务记录、历史采样与事务 | `client.uns` | [UNS](tier0-sdk-v2-uns/SKILL.md) |
17
+ | App 自有业务数据:声明集合、Mongo 风格读写与事务 | `@tier0/sdk-v2/data` | [App 数据](tier0-sdk-v2-data/SKILL.md) |
18
+ | 站内信、Web/Desktop 提醒、手机推送 | `client.notifications` | [通知](tier0-sdk-v2-notifications/SKILL.md) |
19
+ | 当前凭证、平台信息、功能是否就绪 | `client.system`、`client.capabilities` | [系统](tier0-sdk-v2-system/SKILL.md) |
20
+ | 附件、头像、导入导出、生成报告 | `client.files` | [文件](tier0-sdk-v2-files/SKILL.md) |
21
+ | 实时主题订阅/发布 | `@tier0/sdk-v2/mq`、`client.mqttAuth` | [MQTT](tier0-sdk-v2-mq/SKILL.md) |
22
+ | Workspace 收件人/成员检索、App 可用成员 | `client.members` | [成员](tier0-sdk-v2-members/SKILL.md) |
23
+ | 调用已发布的 Control | `client.controls` | [Controls](tier0-sdk-v2-controls/SKILL.md) |
24
+ | Agent 作业、运行、分析结果和卡片 | `client.agent` | [Agent](tier0-sdk-v2-agent/SKILL.md) |
25
25
 
26
26
  每个领域 Skill 可单独使用,无需先加载本文件。Source → Stream → UNS 是接入、处理与数据存储三个领域;不是要求每个数据读取场景都创建一套流水线。通知和系统能力独立于这条链路。
27
27
 
@@ -52,7 +52,7 @@ export async function inspectConnection() {
52
52
 
53
53
  方法存在不等于目标环境已部署或当前 Key 有权限。整数可能为 `bigint`;UNS 任意 JSON 中还可能含 `UnsV2RawNumber`。不要将标识符强制转为 Number;给浏览器返回数据时显式定义精确数字的序列化格式。
54
54
 
55
- HTTP 不自动重试。带 `outcomeUnknown` 的失败应先对账,保留原意图键;取消请求不代表服务端撤销。Source/Stream 仍使用底层 UNS v2 契约,所以错误类名保留 UNS,不代表它们仍属于数据入口。
55
+ `createClient()` 的 HTTP 不自动重试。UNS 错误的 `execution` 说明执行到哪一步、`retry.safe` 说明能否用同一请求重发;带 `outcomeUnknown` 的失败应先对账,保留原意图键;取消请求不代表服务端撤销。App 数据层(`@tier0/sdk-v2/data`)会按 `retry.safe` 自动重试,见 App 数据 Skill。Source/Stream 仍使用底层 UNS v2 契约,所以错误类名保留 UNS,不代表它们仍属于数据入口。
56
56
 
57
57
  ## 从首版入口迁移
58
58
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: tier0-sdk-v2-data
3
- description: 使用 @tier0/sdk-v2/data 在 App 服务端声明并读写本 App 的 UNS 集合:Zod 建模与种子、Mongo 风格读写、原子批量与事务、员工数据主体、Schema 演进和错误码处理。
3
+ description: 使用 @tier0/sdk-v2/data 在 App 服务端声明并读写本 App 的 UNS 集合:Zod 建模、种子与 TTL、Mongo 风格读写、带总数分页与分组计数、原子批量与事务、变更订阅、员工数据主体、Schema 演进和错误码处理。
4
4
  ---
5
5
 
6
6
  # App 数据层
@@ -14,7 +14,7 @@ description: 使用 @tier0/sdk-v2/data 在 App 服务端声明并读写本 App
14
14
  | 用户任务 | 参考 |
15
15
  | --- | --- |
16
16
  | 声明集合、选字段类型、索引、种子数据、文档建模 | [建模](references/modeling.md) |
17
- | 查询、分页、聚合、写入、批量与原子性 | [读写](references/read-write.md) |
17
+ | 查询、按 ID 读取、一页一次请求(queryBatch)、分页、聚合、写入、批量与原子性、订阅变更 | [读写](references/read-write.md) |
18
18
  | 状态流转、并发编辑、跨文档不变量、计数器、员工身份与后台任务 | [一致性与身份](references/consistency.md) |
19
19
  | 加字段、改类型、改描述、收窄 Schema | [Schema 演进](references/schema-evolution.md) |
20
20
  | DataError 字段、UNS 错误码与处理 | [错误码](references/errors.md) |
@@ -55,3 +55,4 @@ export async function releasedOrders(): Promise<WorkOrder[]> {
55
55
  4. `insertMany` / `bulkWrite` / `transact` 每次最多 200 项且原子;`updateMany` / `deleteMany` 逐条提交、非原子。
56
56
  5. 按 `DataError.code` 与 `reason`(`data.reason`)分支,不匹配 message 文本;`status` 是 App 应回给自己调用方的 HTTP 状态。
57
57
  6. `schema:replace`、`archive`、`restore` 是 worker 通过 tier0 MCP 的显式操作,不在运行时代码里调用。
58
+ 7. 调用策略由数据层负责:每次调用从开始算最多 25 秒(排队、重试都算在内),每进程最多 6 个并发数据请求(其余排队,超时或业务请求中止时不再发出),UNS 标记 `retry.safe` 的失败(过载 429、暂时不可用 503、已回滚的写入冲突等)按 `retry.afterMs` 自动重试最多 2 次。每次写入调用自动带一个新的幂等键并在重试中复用,不需要手写;只有“同一业务意图跨多次调用只生效一次”(如按订单号预留库存)才显式传 `idempotencyKey`。不要在外面再包一层重试循环。
@@ -43,13 +43,13 @@ export async function nextOrderNumber() {
43
43
 
44
44
  每次调用都使用平台注入的 App Key。处理业务请求时,数据层还会转发网关签发的员工断言(`X-Tier0-App-Data-Subject`),让 UNS 的数据规则与记录租约作用于该员工。集合 ensure 与种子写入以 App 身份执行。
45
45
 
46
- App 的请求入口用 `runWithRequestContext(request.headers, next)` 打开请求上下文;脱离请求的工作(定时器、队列、发出即不管的后续任务)放进 `runAsApp(() => ...)`,这些调用不带员工身份;员工身份下取得的租约不能由这类 App 级工作释放。断言格式由 SDK 校验(三段 base64url),格式不合法时调用在发出请求前失败。
46
+ App 的请求入口用 `runWithRequestContext(request, next)` 打开请求上下文(传 `Request` 本身:调用方断开时,还在排队的数据调用不再发出,进行中的被取消;只传 `request.headers` 则没有取消);脱离请求的工作(定时器、队列、发出即不管的后续任务)放进 `runAsApp(() => ...)`,这些调用不带员工身份;员工身份下取得的租约不能由这类 App 级工作释放。断言格式由 SDK 校验(三段 base64url),格式不合法时调用在发出请求前失败。
47
47
 
48
48
  ```ts
49
49
  import { currentDataSubject, runAsApp, runWithRequestContext } from '@tier0/sdk-v2/data';
50
50
 
51
51
  export function handleRequest<T>(request: Request, next: () => Promise<T>): Promise<T> {
52
- return runWithRequestContext(request.headers, next);
52
+ return runWithRequestContext(request, next);
53
53
  }
54
54
 
55
55
  export function scheduleFollowUp(task: () => Promise<void>) {
@@ -63,7 +63,7 @@ export function servingEmployee(): boolean {
63
63
 
64
64
  ## 首次访问与自愈
65
65
 
66
- 进程内第一次数据访问会一次性 ensure 全部声明:缺失的集合会被创建(随后写入种子),更宽的声明会放宽存储,存储永远不会被收窄。ensure 失败不缓存,下一次请求会重试(刚导入的 App 可能短暂返回 “hosted App is not ready”,数据层按可重试的 503 回答)。
66
+ 进程内第一次数据访问会一次性 ensure 全部声明:缺失的集合会被创建(随后写入种子),更宽的声明会放宽存储,存储永远不会被收窄。ensure 失败不缓存,下一次请求会重试(刚导入的 App 可能短暂返回 `APP_NOT_READY`,数据层先自动重试,仍失败时按 503 回答)。
67
67
 
68
68
  运行中的进程在所用集合消失时会自愈(Preview 重置会删除集合,worker 或管理员可能归档集合):UNS 以 `data.reason` 为 `COLLECTION_NOT_FOUND` 或 `COLLECTION_NOT_GRANTED` 拒绝调用时,数据层重新 ensure(重建并重新写入种子),并在集合 ID 确实变化时把该调用重试一次。每个 client 每 30 秒最多重建一次;`APP_GRANT_REVOKED`(管理员决定)从不重试。延续旧集合游标的分页、`updateMany` / `deleteMany` 的续写直接失败,不会重试。
69
69
 
@@ -4,13 +4,16 @@
4
4
 
5
5
  | 字段 | 含义 |
6
6
  | --- | --- |
7
- | `status` | App 应回给自己调用方的 HTTP 状态:UNS 401 → 502(平台凭证问题,不是用户登出)、可重试的 403 → 503、400/403/404/409/413/422/429 原样、5xx → 502/503 |
7
+ | `status` | App 应回给自己调用方的 HTTP 状态:UNS 401 → 502(平台凭证问题,不是用户登出);平台状态 `APP_NOT_READY` / `DEPENDENCY_UNAVAILABLE` / `CREDENTIAL_CHANGED` / `OUTCOME_UNKNOWN` / `TEMPORARILY_UNAVAILABLE` → 503;400/403/404/409/413/422/429 原样;其他 5xx → 502/503 |
8
8
  | `code` | UNS 错误码(`DUPLICATE_KEY`…)或数据层错误码 |
9
9
  | `reason` | UNS 错误体的稳定 `data.reason`,例如 `COLLECTION_NOT_FOUND` |
10
10
  | `upstreamStatus` | UNS 实际返回的 HTTP 状态;未收到响应时为 0 |
11
11
  | `requestId` | UNS 请求 ID,排障时一并记录 |
12
- | `retryable` | 可以用同一请求稍后重试 |
13
- | `outcomeUnknown` | 写入可能已执行:先核对结果,再决定是否用原幂等键重放 |
12
+ | `execution` | 执行到哪一步:`notStarted` / `rolledBack` 没有改动,`committed` 已生效,`unknown`(只出现在写入)可能已生效 |
13
+ | `retry` | `{ safe, afterMs? }`:同一调用能否稍后再发、建议等待多久。数据层已经按它自动重试过(最多 2 次) |
14
+ | `outcomeUnknown` | 即 `execution === 'unknown'`:写入可能已执行,先读回核对,再决定是否再写 |
15
+ | `attempts` | 本次调用实际发出的请求数(含重试);发送前就失败时为 0 |
16
+ | `idempotencyKey` | 写入所用的幂等键(未显式传时为数据层生成的);`outcomeUnknown` 时用同一个键重发同一写入不会重复生效 |
14
17
  | `details` | UNS 错误体的 `data`(如 `conflicts`、`resource`、`missingPermission`),或数据层附带的详情 |
15
18
  | `cause` | 原始 `UnsV2Error` |
16
19
 
@@ -36,17 +39,21 @@ export function toResponse(error: unknown): Response {
36
39
  | code | 含义 |
37
40
  | --- | --- |
38
41
  | `DATA_NOT_CONFIGURED` (503) | 缺少 TIER0_API_HOST / TIER0_API_KEY(或 apiHost / apiKey) |
39
- | `DATA_TIMEOUT` (503) | UNS 未在超时内应答(默认 15 秒,`transportTimeoutMs` 可调);写入时 `outcomeUnknown` 为 true |
40
- | `DATA_UNREACHABLE` (503) | 连不上数据服务;网关地址只在原始错误里,不进 message |
41
- | `HTTP_502` / `HTTP_503` / `HTTP_504` (503) | 网关在滚动发布、Pod 重启时未经 UNS 直接返回;可重试,写入时 `outcomeUnknown` 为 true |
42
+ | `DATA_TIMEOUT` (503) | UNS 未在超时内应答(每次调用从开始算共 25 秒,含排队与重试,`transportTimeoutMs` 可调);写入时 `outcomeUnknown` 为 true、不重试 |
43
+ | `DATA_OVERLOADED` (503) | 本进程 6 个请求名额一直占满,调用在超时前没排到,**没有发出**(`execution: 'notStarted'`,`retry.safe`);减少并发或合并读取 |
44
+ | `DATA_ABORTED` (499) | 业务请求已中止(`runWithRequestContext(request, …)` 的 `request.signal`):排队中的调用不再发出(`notStarted`),已发出的写入 `outcomeUnknown` |
45
+ | `DATA_UNREACHABLE` (503) | 连不上数据服务(读会自动重试);网关地址只在原始错误里,不进 message |
46
+ | `HTTP_502` / `HTTP_503` / `HTTP_504` (503) | 网关在滚动发布、Pod 重启时未经 UNS 直接返回;读会自动重试,写入时 `outcomeUnknown` 为 true、不重试 |
42
47
  | `DATA_BAD_RESPONSE` (502) | UNS 应答不符合契约 |
43
48
  | `BATCH_LIMIT_EXCEEDED` (400) | 原子批量超过 200 项,未发送 |
44
- | `WRITE_ERRORS` (409) | 批量写入中途失败;`details.committed` 是已提交的数量 |
49
+ | `WRITE_ERRORS` (409) | 批量写入有条目被拒;`details.writeErrors` 列出各条,`updateMany` / `deleteMany` 的 `details` 还有累计数量与 `resumeAfter`;`insertMany` / `bulkWrite` 是原子的,整批回滚 |
45
50
  | `TRANSACTION_FAILED` (409) | 事务未提交;`details.results` 列出各步骤 |
46
51
  | `SCHEMA_CONFLICT` (409) | Preview 下声明与存储冲突(部署环境记日志,冲突集合按已有 ID 继续服务) |
47
52
  | `SCHEMA_WARNINGS` (500) | Preview 下 ensure 带警告(`REQUIRED_NOT_ENFORCED`、`INDEX_BUILD_FAILED`) |
48
53
  | `COLLECTION_UNAVAILABLE` (503) | ensure 没有返回该集合(见服务端日志) |
49
54
 
55
+ `updateMany` / `deleteMany` 已提交一部分后失败(之前的页,或 UNS 报告的本页部分提交)时,错误保留失败的 `code` 与 `status`,但 `execution` 为 `'committed'`(失败页结局未知时为 `'unknown'`)、`retry.safe` 为 false;`details` 是整个调用的累计结果 `{ matchedCount, modifiedCount | deletedCount, status: 'partiallyCommitted', hasMore, resumeAfter }`,与 UNS 部分提交的错误体相同。用 `resumeAfter` 续做,不要整体重做。
56
+
50
57
  ## UNS 错误码
51
58
 
52
59
  | code | 含义与处理 |
@@ -58,12 +65,18 @@ export function toResponse(error: unknown): Response {
58
65
  | `UPSERT_REQUIRES_UNIQUE_INDEX` | upsert 过滤条件缺少 `_id` 或已就绪唯一索引的全部字段 |
59
66
  | `DUPLICATE_KEY` (409) | 唯一索引拒绝该值;restore 时表示与活动记录冲突、未恢复任何记录 |
60
67
  | `VERSION_CONFLICT` (409) | 记录在读取后已变化;重新读取后用新 revision 重试 |
61
- | `QUERY_TIMEOUT` | 查询超过 15 秒语句超时;加有索引的条件或缩小范围 |
62
- | `TEMPORARILY_UNAVAILABLE` | 稍后用同一请求重试 |
68
+ | `OVERLOADED` (429) | 本 workspace 的数据请求超过并发份额,`retry.afterMs` 后可重试(数据层已自动重试);页面读取合并成一次 `queryBatch`,不要逐条循环调用 |
69
+ | `APP_NOT_READY` (503) | App 刚导入,平台仍在准备;数据层已自动重试,稍后再试 |
70
+ | `DEPENDENCY_UNAVAILABLE` (503) | 平台鉴权依赖暂不可用,稍后重试 |
71
+ | `CREDENTIAL_CHANGED` (409 → 503) | App Key 在请求执行期间被修改,写入已回滚;网关已自动重试一次 |
72
+ | `OUTCOME_UNKNOWN` (503) | 写入提交阶段连接断开,可能已生效;先读回核对 |
73
+ | `FEATURE_NOT_ENABLED` (403) | 工作区未开通 UNS v2 |
74
+ | `QUERY_TIMEOUT` (504 → 503) | 查询执行超时(`retry.safe` 为 false,不会自动重试);按 `repair` 加有索引的条件或缩小范围 |
75
+ | `TEMPORARILY_UNAVAILABLE` (503) | 暂时性失败,数据层已按 `retry` 自动重试 |
63
76
  | `APP_GRANT_REVOKED` (403) | 管理员撤销了本 App 对集合的授权;ensure 不会自动恢复,请管理员处理 |
64
77
  | `APP_QUOTA_EXCEEDED` (409) | `data.resource` 指出触达的每 App 上限(64):`collections` → 归档不用的集合;`indexes` → 从声明删索引并 `schema:replace`;`archivedCollections` → 恢复一个或请管理员清理 |
65
78
  | `INDEX_QUOTA_EXCEEDED` (409) | Workspace 实际存在的索引已达 200;删除不用的索引 |
66
79
  | `COLLECTION_TOO_LARGE` (409) | archive / restore 最多处理 2000 条记录(`data.maxRecords`);不要归档大集合 |
67
- | `FORBIDDEN` (403) | Key 不能做该操作(其他 App 或共享集合);导入后马上出现的 `hosted App is not ready` 可重试,数据层按 503 回答 |
80
+ | `FORBIDDEN` (403) | Key 不能做该操作(其他 App 或共享集合),或 Key 已吊销 |
68
81
 
69
82
  记录调用上的 `data.reason`:`COLLECTION_NOT_GRANTED`(403,授权不覆盖该集合或操作)与 `COLLECTION_NOT_FOUND`(403/404,已归档或删除)会触发数据层重新 ensure;`APP_PERMISSION_MISSING`(403,`data.missingPermission`)表示 Key 缺少权限,例如删除记录需要 `uns.delete`——业务记录通常以状态结束(`CANCELLED`、`ARCHIVED`)而不是删除。
@@ -61,10 +61,46 @@ export const createSalesOrderInput = salesOrders.schema
61
61
 
62
62
  限制:对象最多嵌套 8 层、每个对象 128 个属性、每个字段 100 个枚举值;`.describe()` 最多 2048 个 UTF-8 字节(约 680 个汉字);集合 `description` 最多 512 个字符。字段与集合描述只在首次创建时写入,之后修改要走 `schema:replace`(见 [Schema 演进](schema-evolution.md))。
63
63
 
64
+ ## 字段类型标注
65
+
66
+ 需要在 Collection 中明确显示 `int`、`float`、`double`、`datetime` 时,通过 Zod 元数据添加格式声明。传输仍使用 JSON Schema 的 `integer` / `number` / `string`:
67
+
68
+ | 声明 | 传输 Schema | 显示类型 |
69
+ | --- | --- | --- |
70
+ | `z.int().meta({ format: 'int32' })` | `{ type: 'integer', format: 'int32' }` | `int` |
71
+ | `z.number().meta({ format: 'float' })` | `{ type: 'number', format: 'float' }` | `float` |
72
+ | `z.number().meta({ format: 'double' })` | `{ type: 'number', format: 'double' }` | `double` |
73
+ | `z.string().meta({ format: 'date-time' })` | `{ type: 'string', format: 'date-time' }` | `datetime` |
74
+
75
+ `format` 当前是类型展示注解,不额外限制数值范围或精度,也不舍入数据。写入仍按原来的 Zod 类型和显式 `.min()` / `.max()` 校验;需要业务范围时应继续声明边界。普通 `z.number()`、`z.int()`、`z.number().int()` 和 `z.string()` 保持原有行为,不会自动追加格式或收窄已有集合。只接受上表四种类型与格式组合。
76
+
77
+ `datetime` 仍保存原始字符串,不校验日期是否有效、不解析或转换时区;`"not a date"` 也能通过字符串类型校验。需要日期合法性保证时,在 API 入参 Schema 单独使用 `z.iso.datetime()`;这类带 `pattern` 的验证器不能直接作为可存储的集合声明。新增注解不改变 Sample 外层 `time` 的毫秒时间戳协议、已有校验和业务时间含义。
78
+
64
79
  ## 索引
65
80
 
66
81
  `{ path }` 或复合 `{ paths }`(最多 3 个不同路径);业务键加 `unique: true`;一个数组/对象字段用 `kind: 'gin'`;每个集合最多 16 个。路径只能沿对象属性走:数组元素里的字段(`lines.sku`)不能直接建索引,改为给 `lines` 建 GIN 或把值放到顶层字段。列表页过滤和排序用到的字段要建索引。
67
82
 
83
+ ### 过期自动删除(TTL)
84
+
85
+ 会话、验证码、临时预约这类到期就该消失的记录,用 TTL 索引让 UNS 自动删除,不要写定时清理任务:
86
+
87
+ ```ts
88
+ import { z } from 'zod';
89
+ import { defineCollection } from '@tier0/sdk-v2/data/define';
90
+
91
+ export const loginCodes = defineCollection({
92
+ alias: 'login_codes',
93
+ schema: z.object({ phone: z.string(), code: z.string(), issuedAt: z.number().int() }),
94
+ // issuedAt 存 Date.now();签发 5 分钟后删除。
95
+ indexes: [{ path: 'phone' }, { path: 'issuedAt', expireAfterSeconds: 300 }],
96
+ });
97
+ ```
98
+
99
+ - 字段声明为 `z.number().int()`,存 Unix 毫秒(通常是 `Date.now()`);记录在该时刻再过 `expireAfterSeconds`(60–31536000 秒)后被删除。删除有分钟级延迟,读取时仍要自己比较时间。
100
+ - 每个集合最多 1 个 TTL 索引,只能是单个 `path`、不能 `unique`。缺这个字段的记录不会过期。
101
+ - 过期删除是**物理删除**,不能 restore;软删除的记录同样按 TTL 清理。
102
+ - ensure 只能新增 TTL 或把时间加长;缩短或去掉要通过 tier0 MCP 的 `schema:replace`。
103
+
68
104
  ## 种子数据
69
105
 
70
106
  种子是基线记录:固定 `_id`,各生命周期状态自洽,相互引用用这些 ID;每个集合最多 1000 条。只在 ensure 报告集合为 `created` 时写入一次(每 200 条一个原子分块,分块带固定幂等键,重试安全);修改 `seed` 不会改动已存在的集合。不要在服务启动或列表为空时从代码插入基线数据。构建期可以对每条种子调用 `validateDocument(definition, document)` 校验。
@@ -0,0 +1,172 @@
1
+ # 读写
2
+
3
+ `data.collection(declaration)` 返回按声明推断类型的句柄(也可用 alias 字符串,类型由调用方指定)。读到的记录是扁平文档 `{ _id, ...字段, _meta }`;过滤、排序、投影都用同样的路径(`status`、`customer.id`、`_id`、`_meta.updatedAt`),不加 `data.` 前缀。
4
+
5
+ ```ts
6
+ import { z } from 'zod';
7
+ import { createDataClient, defineCollection } from '@tier0/sdk-v2/data';
8
+
9
+ const workOrders = defineCollection({
10
+ alias: 'work_orders',
11
+ schema: z.object({
12
+ code: z.string(),
13
+ status: z.enum(['DRAFT', 'RELEASED', 'DONE']),
14
+ line: z.object({ id: z.string(), name: z.string() }),
15
+ targetQty: z.number().int().min(1),
16
+ notes: z.string().optional(),
17
+ }),
18
+ indexes: [{ path: 'code', unique: true }, { path: 'status' }],
19
+ });
20
+ const data = createDataClient({ collections: [workOrders] });
21
+ const orders = data.collection(workOrders);
22
+
23
+ export async function dashboard(page: number) {
24
+ const recent = await orders.find({ status: { $in: ['RELEASED', 'DONE'] } }, { sort: { '_meta.createdAt': -1 }, limit: 50 }).toArray();
25
+ // 一页 + 总数是一次请求;total.capped 为 true 时显示成 "10000+"。
26
+ const listPage = await orders.findPage({ status: 'RELEASED' }, { sort: { code: 1 }, page, pageSize: 20 });
27
+ // 每个状态的数量:一次 $group,不要按状态逐个 countDocuments。
28
+ const byStatus = await orders.countBy('status');
29
+ const totals = await orders
30
+ .aggregate<{ _id: string; total: number }>([{ $match: { status: 'DONE' } }, { $group: { _id: '$line.id', total: { $sum: '$targetQty' } } }])
31
+ .toArray();
32
+ return { recent, listPage, byStatus, totals };
33
+ }
34
+
35
+ /** 角标只需要知道“有没有超过 99 条”。 */
36
+ export async function releasedBadge() {
37
+ const { count, capped } = await orders.countDocuments({ status: 'RELEASED' }, { maxCount: 99 });
38
+ return capped ? '99+' : String(count);
39
+ }
40
+
41
+ export async function addNote(id: string, notes: string, revision: number) {
42
+ return orders.updateOne({ _id: id }, { $set: { notes } }, { expectedRevision: revision });
43
+ }
44
+ ```
45
+
46
+ ## 一个页面一次请求:`data.queryBatch`
47
+
48
+ 页面要同时显示列表、总数、各状态数量、某条记录时,用 `data.queryBatch` 把这些读取合成一次请求(最多 20 个),结果来自同一个快照,不要分别发多个请求:
49
+
50
+ ```ts
51
+ import { z } from 'zod';
52
+ import { createDataClient, defineCollection } from '@tier0/sdk-v2/data';
53
+
54
+ const workOrders = defineCollection({
55
+ alias: 'work_orders',
56
+ schema: z.object({ code: z.string(), status: z.enum(['DRAFT', 'RELEASED', 'DONE']) }),
57
+ indexes: [{ path: 'code', unique: true }, { path: 'status' }],
58
+ });
59
+ const data = createDataClient({ collections: [workOrders] });
60
+ const orders = data.collection(workOrders);
61
+
62
+ export async function ordersScreen(page: number, selectedId: string) {
63
+ const { list, byStatus, selected } = await data.queryBatch({
64
+ list: orders.query.findPage({ status: { $ne: 'DONE' } }, { sort: { code: 1 }, page, pageSize: 20 }),
65
+ byStatus: orders.query.countBy('status'),
66
+ selected: orders.query.get([selectedId]),
67
+ });
68
+ return { list, byStatus, selected: selected.items[0] ?? null };
69
+ }
70
+ ```
71
+
72
+ - 读取用 `collection.query.*` 构造:`find`(首页,可带 `maxCount` 附带总数)、`findPage`、`count`、`countBy`、`distinct`、`aggregate`、`get`;每项只返回一页(最多 200 行),`get` 最多 100 个 ID。
73
+ - 结果按名字返回,类型与对应方法一致。名字只能用字母、数字、`_` `.` `-`。
74
+ - 某一项被拒(过滤条件不合法、无授权)时整个调用抛出该项的 `DataError`;传 `{ settle: true }` 则每项返回 `{ ok: true, value }` 或 `{ ok: false, error }`。语句超时或整批超过 2000 行 / 8 MiB 时整批失败。
75
+
76
+ ## 方法
77
+
78
+ | 读取 | 说明 |
79
+ | --- | --- |
80
+ | `find(filter, { sort, limit, projection })` | 游标,每页最多 200 条;列表读取始终给 `limit` |
81
+ | `findOne(filter)` | 无匹配返回 `null` |
82
+ | `get(ids, { projection })` | 按 `_id` 读取,返回 `{ items, missing }`,`items` 按请求顺序(重复的 ID 只取第一次出现);不存在、已删除或员工无权查看的 ID 一律进 `missing`。超过 100 个自动分批 |
83
+ | `findPage(filter, { sort, page, pageSize, maxCount })` | 页码分页,一次请求返回 `{ items, total: { count, capped }, page, pageSize }`;pageSize 最多 200,总数默认数到 10000(`maxCount` 最多 100000),超过时 `capped: true` |
84
+ | `countDocuments(filter, { maxCount })` | 返回 `{ count, capped }`,默认数到 100000;只要阈值时给小的 `maxCount`(如角标 `99+`) |
85
+ | `countBy(field, filter)` | 每个取值的数量 `[{ value, count }]`,从多到少;一次 `$group` |
86
+ | `distinct(field, filter)` | 会取完所有页 |
87
+ | `aggregate(pipeline)` | `$match` `$sort` `$group` `$project` `$set` `$unwind` `$skip` `$limit` `$count` `$lookup`;`$lookup.from` 写声明或 alias,数据层换成集合 ID。`$exists` / `$distinct` / `$frequencies` 阶段不执行,改用 `countBy`、`distinct` 或 `$group` |
88
+
89
+ | 写入 | 说明 |
90
+ | --- | --- |
91
+ | `insertOne(doc, options)`、`insertMany(docs)` | 可带 `_id`;单条默认返回 `{ insertedId }`,`returnDocument:'after'` 同次写入返回 `{ insertedId, value }`;`insertMany` 最多 200 条、原子 |
92
+ | `updateOne` / `updateMany` / `replaceOne` | 更新用操作符(`$set` `$inc` `$push` `$unset`…);`replaceOne` 会丢掉其他代码版本写入的字段 |
93
+ | `deleteOne` / `deleteMany` | 软删除,App Key 需要 `uns.delete` |
94
+ | `findOneAndUpdate` / `findOneAndReplace` / `findOneAndDelete` | 原子读改写,默认返回更新后的文档 |
95
+ | `bulkWrite(ops)` | `insertOne` / `updateOne` / `replaceOne` / `deleteOne` 混合,最多 200 项、原子 |
96
+ | `data.transact(steps)` | 跨集合原子写,见 [一致性](consistency.md) |
97
+
98
+ 新建后需要显示保存结果或继续按版本编辑时,使用 `insertOne(doc, { returnDocument: 'after', idempotencyKey })`。`value` 与查询结果一样是 `{ _id, ...业务字段, _meta }`,包含服务端生成的数据和 `_meta.revision`,无需额外查询。返回快照需要当前读取权限;`projection` 只能与 `after` 一起提供。重放返回当次创建的快照,不能当成记录当前最新版本;之后的版本冲突仍需显式读取并处理。
99
+
100
+ 带 `projection` 时,返回类型将业务字段递归标为可选,包括嵌套对象和数组元素的字段;读取前需判断是否存在。`_id` 和 `_meta` 仍然必有。不带 `projection` 的 `after` 保持完整文档类型。
101
+
102
+ ## 批量与原子性
103
+
104
+ | 调用 | 上限 | 原子 |
105
+ | --- | --- | --- |
106
+ | `insertMany`、`bulkWrite`、`data.transact` | 每次 ≤200 项,超过在发送前抛 `BATCH_LIMIT_EXCEEDED` | 是,全部或全不 |
107
+ | `updateMany`、`deleteMany` | 无上限,翻页处理所有匹配 | 否,逐条提交;已有提交后失败时错误保留原 `code`,`execution: 'committed'`、`retry.safe: false`,`details` 是累计结果(`matchedCount` 等、`status: 'partiallyCommitted'`、续做位置 `resumeAfter`) |
108
+
109
+ 超过 200 条按 200 分块写入(循环分块即可),每块是一次独立的原子调用:数据层自动给每次调用生成幂等键并在重试中复用,不需要手写;要让“某块写过就不再写”跨进程重启也成立,用固定 `_id`(或按业务意图显式传 `idempotencyKey`)。多条文档必须一起成功或失败时,用一次 `data.transact`(≤200 步),不要用 `updateMany`。不要在循环里逐条调用数据层:读用一次 `get` / `$in` 或 `queryBatch`,写用 `insertMany` / `updateMany` / `bulkWrite` 或 `data.transact`。
110
+
111
+ ### 非原子批量中途失败
112
+
113
+ `updateMany` / `deleteMany` 已经提交了一部分后失败时(`execution === 'committed'`),不要整体重做:`$inc`、`$push` 这类更新会把已提交的记录再改一次。把 `details.resumeAfter` 传回去,从失败处继续:
114
+
115
+ ```ts
116
+ import { DataError, type Collection } from '@tier0/sdk-v2/data';
117
+
118
+ export async function bumpAll(items: Collection<{ status: string; views: number }>) {
119
+ let resumeAfter: string | undefined;
120
+ for (let round = 0; round < 3; round += 1) {
121
+ try {
122
+ return await items.updateMany({ status: 'OPEN' }, { $inc: { views: 1 } }, { resumeAfter });
123
+ } catch (error) {
124
+ // 只续做已证明部分提交、且给了续做位置的失败;结局未知的那一页要先核对。
125
+ const next = error instanceof DataError && error.execution === 'committed' ? error.details.resumeAfter : undefined;
126
+ if (typeof next !== 'string') throw error;
127
+ resumeAfter = next;
128
+ }
129
+ }
130
+ throw new Error('updateMany kept failing');
131
+ }
132
+ ```
133
+
134
+ `execution === 'unknown'` 时失败的那一页可能已经部分生效,续做前先核对。
135
+
136
+ ## 过滤
137
+
138
+ 支持 `$eq $ne $gt $gte $lt $lte $in $nin $exists $all $size $elemMatch $not $and $or $nor` 和 `$contains`。`$contains` 是子串匹配(1–256 字节,无通配符和正则),只对 ASCII 字母忽略大小写;缺失、null、非字符串值不匹配;只能用于声明为字符串的字段,用在数组或数字字段上返回 `INVALID_REQUEST`。它不能使用索引,大集合上可能触发 10 秒执行超时(`QUERY_TIMEOUT`),要和有索引的条件一起用。`$in` 最多 200 个值,排序最多 4 个字段(`toSortPairs` 也导出给自定义查询转换使用)。
139
+
140
+ ## 校验
141
+
142
+ 写入前按声明严格校验:未知字段、类型错误、未声明的更新路径抛 `ZodError`(HTTP 400 语义)。读取不严格解析:记录可能含其他代码版本写入的字段、新的枚举值,或缺少后来新增的字段。枚举渲染要有兜底标签,后加的字段按可能缺失处理。
143
+
144
+ ## 订阅变更
145
+
146
+ `data.subscribe(...)` 返回已提交变更的异步迭代器(UNS `records:subscribe`,服务端推送):断线、超时、服务端结束都自动续传(`Last-Event-ID`),刷新页面不需要轮询。事件只有标识,不带业务数据,收到后按需重新读取。
147
+
148
+ ```ts
149
+ import { z } from 'zod';
150
+ import { createDataClient, defineCollection, runAsApp, type SubscriptionEvent } from '@tier0/sdk-v2/data';
151
+
152
+ const tickets = defineCollection({ alias: 'tickets', schema: z.object({ title: z.string(), status: z.enum(['OPEN', 'DONE']) }) });
153
+ const data = createDataClient({ collections: [tickets] });
154
+
155
+ /** 一个进程一条 App 级订阅,在进程内扇出给浏览器。 */
156
+ export async function pumpChanges(send: (event: SubscriptionEvent) => void, signal: AbortSignal) {
157
+ await runAsApp(async () => {
158
+ for await (const event of data.subscribe({ collections: [tickets], signal })) {
159
+ // reset:续传位置失效或访问范围变化,浏览器应重新读取当前数据。
160
+ send(event);
161
+ }
162
+ });
163
+ }
164
+ ```
165
+
166
+ - 事件:`{ type: 'change', eventId, collection(alias), collectionId, change: 'record' | 'delete', recordId, revision, occurredAt, restricted }`、集合级通知 `{ type: 'change', …, change: 'collection', restricted: true }`(没有 `recordId` / `revision`),或 `{ type: 'reset', reason, message? }`。心跳与重连在 SDK 内部处理;迭代抛出 `DataError` 表示不能再重试(如 403),正常结束只有 `signal` 中止或跳出循环。
167
+ - 事件按写入事务的顺序到达,不是提交顺序,同一记录的 revision 可能乱序。SDK 已丢弃 revision 不大于该记录已投递 revision 的非 `delete` 变更;`delete` 一律投递(迟到的也投递),并清除该记录的 revision(同一 ID 再写入时可能从 1 开始)。收到事件后按 `recordId` 重新读取,不要用事件顺序推断记录的最终状态。
168
+ - `reset`:`CURSOR_EXPIRED`(续传位置已被清理、游标无法解开或租户库已迁移)或 `SCOPE_CHANGED`(访问范围变化,例如集合不再可读)。重新读取当前数据;SDK 随后不带续传位置重新订阅。
169
+ - `restricted: true`:该集合有员工数据规则。以 App 身份打开的订阅能看到所有记录的变更,所以默认把这类变更换成集合级通知,可以直接转发给任何员工的浏览器,浏览器再通过受规则约束的接口重新读取。只有不转发给浏览器的服务端逻辑才传 `restricted: 'record'` 拿完整事件(转发前仍须逐个经 `redactRestricted`)。
170
+ - 默认订阅全部已声明的集合(最多 64 个);`recordIds`(最多 100 个)只能配一个集合。
171
+ - 订阅不占数据请求的并发名额,但每个租户的订阅数有上限(超出时 SDK 按 429 自动退避重连):一个服务端进程只开一条订阅,在进程内扇出,不要每个浏览器连接开一条。App 刚导入还没就绪(`APP_NOT_READY`)时订阅会一直等待,不会结束。
172
+ - 变更保留 24 小时;`lastEventId` 太旧时收到 `reset`。`lastEventId` 必须是收到过的事件 ID(可见 ASCII),否则调用时直接抛 `TypeError`。
@@ -45,7 +45,7 @@ export async function subscribeTelemetry(
45
45
 
46
46
  回调是 (topic,payload),payload 为字符串。subscribe 返回 void,订阅失败通过 error 事件,不把函数返回当 broker SUBACK。此例独占连接;共享连接只退订自己的 handler,不关闭其他消费者。
47
47
 
48
- + 匹配一层,# 匹配多层,订阅范围必须属于任务和主题授权。不能从旧 v1 的 Metric/Action/State 路径、enableHistory 或字段规则推断新 UNS MQTT 映射;先核实目标平台的实际主题和消息契约。新 UNS Record 变更也有 recordChangesPoll,不能照搬“永不轮询”禁令。
48
+ + 匹配一层,# 匹配多层,订阅范围必须属于任务和主题授权。不能从旧 v1 的 Metric/Action/State 路径、enableHistory 或字段规则推断新 UNS MQTT 映射;先核实目标平台的实际主题和消息契约。App 服务端订阅新 UNS Record 变更用 `@tier0/sdk-v2/data` 的 `data.subscribe`(`records:subscribe` 服务端推送),不是 MQTT 主题。
49
49
 
50
50
  ## 发布
51
51