@tier0/sdk-v2 0.9.10 → 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.
- package/CHANGELOG.md +96 -0
- package/LICENSE +21 -21
- package/README.md +160 -24
- package/dist/cjs/client.d.ts +79 -3
- package/dist/cjs/client.js +4 -6
- package/dist/cjs/client.js.map +1 -1
- package/dist/cjs/cloud-server.d.ts +33 -26
- package/dist/cjs/cloud-server.js +102 -100
- package/dist/cjs/cloud-server.js.map +1 -1
- package/dist/cjs/cloud-transport.d.ts +15 -20
- package/dist/cjs/cloud-transport.js +87 -68
- package/dist/cjs/cloud-transport.js.map +1 -1
- package/dist/cjs/data/bootstrap.js +3 -4
- package/dist/cjs/data/bootstrap.js.map +1 -1
- package/dist/cjs/data/call.d.ts +1 -3
- package/dist/cjs/data/call.js +8 -12
- package/dist/cjs/data/call.js.map +1 -1
- package/dist/cjs/data/client.d.ts +8 -7
- package/dist/cjs/data/client.js +24 -11
- package/dist/cjs/data/client.js.map +1 -1
- package/dist/cjs/data/collection.d.ts +3 -5
- package/dist/cjs/data/collection.js +3 -4
- package/dist/cjs/data/collection.js.map +1 -1
- package/dist/cjs/data/context.d.ts +5 -11
- package/dist/cjs/data/context.js +21 -36
- package/dist/cjs/data/context.js.map +1 -1
- package/dist/cjs/data/error.js +1 -1
- package/dist/cjs/data/error.js.map +1 -1
- package/dist/cjs/data/index.d.ts +5 -3
- package/dist/cjs/data/index.js +5 -5
- package/dist/cjs/data/index.js.map +1 -1
- package/dist/cjs/data/relay.d.ts +51 -0
- package/dist/cjs/data/relay.js +371 -0
- package/dist/cjs/data/relay.js.map +1 -0
- package/dist/cjs/data/subscribe.d.ts +22 -38
- package/dist/cjs/data/subscribe.js +22 -28
- package/dist/cjs/data/subscribe.js.map +1 -1
- package/dist/cjs/event-stream.d.ts +28 -0
- package/dist/cjs/event-stream.js +222 -0
- package/dist/cjs/event-stream.js.map +1 -0
- package/dist/cjs/mq/client.d.ts +32 -1
- package/dist/cjs/mq/client.js +404 -69
- package/dist/cjs/mq/client.js.map +1 -1
- package/dist/cjs/mq/index.d.ts +1 -0
- package/dist/cjs/mq/index.js +3 -1
- package/dist/cjs/mq/index.js.map +1 -1
- package/dist/cjs/mq/topic.d.ts +29 -0
- package/dist/cjs/mq/topic.js +48 -0
- package/dist/cjs/mq/topic.js.map +1 -0
- package/dist/cjs/mq/types.d.ts +21 -1
- package/dist/cjs/mq/types.js.map +1 -1
- package/dist/cjs/openapi/api.generated.d.ts +20 -0
- package/dist/cjs/openapi/api.generated.js +11 -0
- package/dist/cjs/openapi/api.generated.js.map +1 -1
- package/dist/cjs/openapi/client.d.ts +46 -0
- package/dist/cjs/openapi/client.js +158 -10
- package/dist/cjs/openapi/client.js.map +1 -1
- package/dist/cjs/openapi/codecs.generated.d.ts +74 -0
- package/dist/cjs/openapi/codecs.generated.js +1 -1
- package/dist/cjs/openapi/codecs.generated.js.map +1 -1
- package/dist/cjs/openapi/types.generated.d.ts +205 -1
- package/dist/cjs/openapi/types.generated.js.map +1 -1
- package/dist/cjs/page-runtime.d.ts +48 -0
- package/dist/cjs/page-runtime.js +74 -0
- package/dist/cjs/page-runtime.js.map +1 -0
- package/dist/cjs/platform-proxy.d.ts +17 -0
- package/dist/cjs/platform-proxy.js +52 -0
- package/dist/cjs/platform-proxy.js.map +1 -0
- package/dist/cjs/runtime-context.d.ts +2 -1
- package/dist/cjs/runtime-context.js +6 -2
- package/dist/cjs/runtime-context.js.map +1 -1
- package/dist/cjs/runtime-env.js.map +1 -1
- package/dist/cjs/source.d.ts +19 -1
- package/dist/cjs/source.js +9 -3
- package/dist/cjs/source.js.map +1 -1
- package/dist/cjs/stream.js +3 -2
- package/dist/cjs/stream.js.map +1 -1
- package/dist/cjs/streams/contract.generated.d.ts +7 -1
- package/dist/cjs/streams/contract.generated.js.map +1 -1
- package/dist/cjs/streams/messages.js +1 -10
- package/dist/cjs/streams/messages.js.map +1 -1
- package/dist/cjs/streams/requests.d.ts +3 -0
- package/dist/cjs/streams/requests.js +5 -2
- package/dist/cjs/streams/requests.js.map +1 -1
- package/dist/cjs/streams/transport.d.ts +1 -13
- package/dist/cjs/streams/transport.js +2 -18
- package/dist/cjs/streams/transport.js.map +1 -1
- package/dist/cjs/uns-v2/client.d.ts +1 -6
- package/dist/cjs/uns-v2/client.js +30 -6
- package/dist/cjs/uns-v2/client.js.map +1 -1
- package/dist/cjs/uns-v2/data-client.d.ts +5 -2
- package/dist/cjs/uns-v2/data-client.js +7 -4
- package/dist/cjs/uns-v2/data-client.js.map +1 -1
- package/dist/cjs/uns-v2/generated.d.ts +6783 -4655
- package/dist/cjs/uns-v2/generated.js +221 -65
- package/dist/cjs/uns-v2/generated.js.map +1 -1
- package/dist/cjs/uns-v2/sse.d.ts +7 -1
- package/dist/cjs/uns-v2/sse.js +12 -2
- package/dist/cjs/uns-v2/sse.js.map +1 -1
- package/dist/esm/client.d.ts +79 -3
- package/dist/esm/client.js +5 -7
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/cloud-server.d.ts +33 -26
- package/dist/esm/cloud-server.js +99 -97
- package/dist/esm/cloud-server.js.map +1 -1
- package/dist/esm/cloud-transport.d.ts +15 -20
- package/dist/esm/cloud-transport.js +86 -67
- package/dist/esm/cloud-transport.js.map +1 -1
- package/dist/esm/data/bootstrap.js +3 -4
- package/dist/esm/data/bootstrap.js.map +1 -1
- package/dist/esm/data/call.d.ts +1 -3
- package/dist/esm/data/call.js +9 -13
- package/dist/esm/data/call.js.map +1 -1
- package/dist/esm/data/client.d.ts +8 -7
- package/dist/esm/data/client.js +22 -9
- package/dist/esm/data/client.js.map +1 -1
- package/dist/esm/data/collection.d.ts +3 -5
- package/dist/esm/data/collection.js +3 -4
- package/dist/esm/data/collection.js.map +1 -1
- package/dist/esm/data/context.d.ts +5 -11
- package/dist/esm/data/context.js +19 -33
- package/dist/esm/data/context.js.map +1 -1
- package/dist/esm/data/error.js +1 -1
- package/dist/esm/data/error.js.map +1 -1
- package/dist/esm/data/index.d.ts +5 -3
- package/dist/esm/data/index.js +3 -2
- package/dist/esm/data/index.js.map +1 -1
- package/dist/esm/data/relay.d.ts +51 -0
- package/dist/esm/data/relay.js +368 -0
- package/dist/esm/data/relay.js.map +1 -0
- package/dist/esm/data/subscribe.d.ts +22 -38
- package/dist/esm/data/subscribe.js +22 -27
- package/dist/esm/data/subscribe.js.map +1 -1
- package/dist/esm/event-stream.d.ts +28 -0
- package/dist/esm/event-stream.js +217 -0
- package/dist/esm/event-stream.js.map +1 -0
- package/dist/esm/mq/client.d.ts +32 -1
- package/dist/esm/mq/client.js +404 -69
- package/dist/esm/mq/client.js.map +1 -1
- package/dist/esm/mq/index.d.ts +1 -0
- package/dist/esm/mq/index.js +1 -0
- package/dist/esm/mq/index.js.map +1 -1
- package/dist/esm/mq/topic.d.ts +29 -0
- package/dist/esm/mq/topic.js +44 -0
- package/dist/esm/mq/topic.js.map +1 -0
- package/dist/esm/mq/types.d.ts +21 -1
- package/dist/esm/mq/types.js.map +1 -1
- package/dist/esm/openapi/api.generated.d.ts +20 -0
- package/dist/esm/openapi/api.generated.js +11 -0
- package/dist/esm/openapi/api.generated.js.map +1 -1
- package/dist/esm/openapi/client.d.ts +46 -0
- package/dist/esm/openapi/client.js +157 -10
- package/dist/esm/openapi/client.js.map +1 -1
- package/dist/esm/openapi/codecs.generated.d.ts +74 -0
- package/dist/esm/openapi/codecs.generated.js +1 -1
- package/dist/esm/openapi/codecs.generated.js.map +1 -1
- package/dist/esm/openapi/types.generated.d.ts +205 -1
- package/dist/esm/openapi/types.generated.js.map +1 -1
- package/dist/esm/page-runtime.d.ts +48 -0
- package/dist/esm/page-runtime.js +65 -0
- package/dist/esm/page-runtime.js.map +1 -0
- package/dist/esm/platform-proxy.d.ts +17 -0
- package/dist/esm/platform-proxy.js +47 -0
- package/dist/esm/platform-proxy.js.map +1 -0
- package/dist/esm/runtime-context.d.ts +2 -1
- package/dist/esm/runtime-context.js +6 -2
- package/dist/esm/runtime-context.js.map +1 -1
- package/dist/esm/runtime-env.js.map +1 -1
- package/dist/esm/source.d.ts +19 -1
- package/dist/esm/source.js +10 -4
- package/dist/esm/source.js.map +1 -1
- package/dist/esm/stream.js +4 -3
- package/dist/esm/stream.js.map +1 -1
- package/dist/esm/streams/contract.generated.d.ts +7 -1
- package/dist/esm/streams/contract.generated.js.map +1 -1
- package/dist/esm/streams/messages.js +1 -10
- package/dist/esm/streams/messages.js.map +1 -1
- package/dist/esm/streams/requests.d.ts +3 -0
- package/dist/esm/streams/requests.js +5 -2
- package/dist/esm/streams/requests.js.map +1 -1
- package/dist/esm/streams/transport.d.ts +1 -13
- package/dist/esm/streams/transport.js +1 -17
- package/dist/esm/streams/transport.js.map +1 -1
- package/dist/esm/uns-v2/client.d.ts +1 -6
- package/dist/esm/uns-v2/client.js +30 -6
- package/dist/esm/uns-v2/client.js.map +1 -1
- package/dist/esm/uns-v2/data-client.d.ts +5 -2
- package/dist/esm/uns-v2/data-client.js +8 -5
- package/dist/esm/uns-v2/data-client.js.map +1 -1
- package/dist/esm/uns-v2/generated.d.ts +6783 -4655
- package/dist/esm/uns-v2/generated.js +221 -65
- package/dist/esm/uns-v2/generated.js.map +1 -1
- package/dist/esm/uns-v2/sse.d.ts +7 -1
- package/dist/esm/uns-v2/sse.js +12 -2
- package/dist/esm/uns-v2/sse.js.map +1 -1
- package/package.json +25 -12
- package/skills/tier0-sdk-v2/SKILL.md +11 -5
- package/skills/tier0-sdk-v2/references/integration.md +14 -24
- package/skills/tier0-sdk-v2/tier0-sdk-v2-data/SKILL.md +11 -5
- package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/consistency.md +6 -10
- package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/errors.md +20 -3
- package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/read-write.md +46 -13
- package/skills/tier0-sdk-v2/tier0-sdk-v2-members/SKILL.md +2 -2
- package/skills/tier0-sdk-v2/tier0-sdk-v2-members/references/app-members.md +5 -1
- package/skills/tier0-sdk-v2/tier0-sdk-v2-mq/SKILL.md +25 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-mq/references/quickstart.md +1 -1
- package/skills/tier0-sdk-v2/tier0-sdk-v2-notifications/SKILL.md +3 -3
- package/skills/tier0-sdk-v2/tier0-sdk-v2-notifications/references/send.md +6 -6
- package/skills/tier0-sdk-v2/tier0-sdk-v2-source/SKILL.md +4 -4
- package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/changes.md +2 -2
- package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/create.md +26 -36
- package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/delete.md +9 -23
- package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/protocols.md +12 -29
- package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/read.md +4 -5
- package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/update.md +5 -5
- package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/SKILL.md +3 -3
- package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/references/create.md +23 -9
- package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/references/deploy.md +14 -12
- package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/references/http-server.md +4 -4
- package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/references/read.md +4 -3
- package/skills/tier0-sdk-v2/tier0-sdk-v2-streams/SKILL.md +12 -12
- package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/SKILL.md +2 -1
- package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/create.md +2 -2
- package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/delete.md +22 -1
- package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/read.md +5 -6
- package/dist/cjs/app-identity-contract.generated.d.ts +0 -22
- package/dist/cjs/app-identity-contract.generated.js +0 -51
- package/dist/cjs/app-identity-contract.generated.js.map +0 -1
- package/dist/cjs/runtime.d.ts +0 -13
- package/dist/cjs/runtime.js +0 -51
- package/dist/cjs/runtime.js.map +0 -1
- package/dist/esm/app-identity-contract.generated.d.ts +0 -22
- package/dist/esm/app-identity-contract.generated.js +0 -48
- package/dist/esm/app-identity-contract.generated.js.map +0 -1
- package/dist/esm/runtime.d.ts +0 -13
- package/dist/esm/runtime.js +0 -46
- package/dist/esm/runtime.js.map +0 -1
package/dist/esm/uns-v2/sse.d.ts
CHANGED
|
@@ -14,7 +14,13 @@ export interface ServerSentEvent {
|
|
|
14
14
|
export declare const SSE_MAX_LINE_BYTES: number;
|
|
15
15
|
export declare class ServerSentEventsError extends Error {
|
|
16
16
|
}
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* Yields each dispatched event and each comment line, plus `{ id }` for a frame
|
|
19
|
+
* that only moves the position; `retry:` is ignored. Throws
|
|
20
|
+
* ServerSentEventsError on an oversized line.
|
|
21
|
+
*/
|
|
18
22
|
export declare function parseServerSentEvents(stream: ReadableStream<Uint8Array>): AsyncGenerator<ServerSentEvent | {
|
|
19
23
|
comment: string;
|
|
24
|
+
} | {
|
|
25
|
+
id: string;
|
|
20
26
|
}>;
|
package/dist/esm/uns-v2/sse.js
CHANGED
|
@@ -7,7 +7,11 @@
|
|
|
7
7
|
export const SSE_MAX_LINE_BYTES = 1 << 20;
|
|
8
8
|
export class ServerSentEventsError extends Error {
|
|
9
9
|
}
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Yields each dispatched event and each comment line, plus `{ id }` for a frame
|
|
12
|
+
* that only moves the position; `retry:` is ignored. Throws
|
|
13
|
+
* ServerSentEventsError on an oversized line.
|
|
14
|
+
*/
|
|
11
15
|
export async function* parseServerSentEvents(stream) {
|
|
12
16
|
const reader = stream.getReader();
|
|
13
17
|
const decoder = new TextDecoder();
|
|
@@ -15,6 +19,7 @@ export async function* parseServerSentEvents(stream) {
|
|
|
15
19
|
let event = '';
|
|
16
20
|
let data = [];
|
|
17
21
|
let id;
|
|
22
|
+
let moved = false;
|
|
18
23
|
try {
|
|
19
24
|
while (true) {
|
|
20
25
|
const { done, value } = await reader.read();
|
|
@@ -29,6 +34,9 @@ export async function* parseServerSentEvents(stream) {
|
|
|
29
34
|
if (line === '') {
|
|
30
35
|
if (data.length > 0 || event)
|
|
31
36
|
yield { event: event || 'message', data: data.join('\n'), ...(id === undefined ? {} : { id }) };
|
|
37
|
+
else if (moved && id !== undefined)
|
|
38
|
+
yield { id };
|
|
39
|
+
moved = false;
|
|
32
40
|
event = '';
|
|
33
41
|
data = [];
|
|
34
42
|
continue;
|
|
@@ -45,8 +53,10 @@ export async function* parseServerSentEvents(stream) {
|
|
|
45
53
|
event = text;
|
|
46
54
|
else if (field === 'data')
|
|
47
55
|
data.push(text);
|
|
48
|
-
else if (field === 'id' && !text.includes('\u0000'))
|
|
56
|
+
else if (field === 'id' && !text.includes('\u0000')) {
|
|
49
57
|
id = text;
|
|
58
|
+
moved = true;
|
|
59
|
+
}
|
|
50
60
|
}
|
|
51
61
|
if (done)
|
|
52
62
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sse.js","sourceRoot":"","sources":["../../../src/uns-v2/sse.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAUH,2FAA2F;AAC3F,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,IAAI,EAAE,CAAC;AAE1C,MAAM,OAAO,qBAAsB,SAAQ,KAAK;CAAG;AAEnD
|
|
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/**\n * Yields each dispatched event and each comment line, plus `{ id }` for a frame\n * that only moves the position; `retry:` is ignored. Throws\n * ServerSentEventsError on an oversized line.\n */\nexport async function* parseServerSentEvents(stream: ReadableStream<Uint8Array>): AsyncGenerator<ServerSentEvent | { comment: string } | { id: 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 let moved = false;\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 else if (moved && id !== undefined) yield { id };\n moved = false;\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')) {\n id = text;\n moved = true;\n }\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.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Tier0 Cloud new-architecture SDK for TypeScript and JavaScript",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -97,6 +97,26 @@
|
|
|
97
97
|
"default": "./dist/cjs/mq/index.js"
|
|
98
98
|
}
|
|
99
99
|
},
|
|
100
|
+
"./page-runtime": {
|
|
101
|
+
"import": {
|
|
102
|
+
"types": "./dist/esm/page-runtime.d.ts",
|
|
103
|
+
"default": "./dist/esm/page-runtime.js"
|
|
104
|
+
},
|
|
105
|
+
"require": {
|
|
106
|
+
"types": "./dist/cjs/page-runtime.d.ts",
|
|
107
|
+
"default": "./dist/cjs/page-runtime.js"
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
"./event-stream": {
|
|
111
|
+
"import": {
|
|
112
|
+
"types": "./dist/esm/event-stream.d.ts",
|
|
113
|
+
"default": "./dist/esm/event-stream.js"
|
|
114
|
+
},
|
|
115
|
+
"require": {
|
|
116
|
+
"types": "./dist/cjs/event-stream.d.ts",
|
|
117
|
+
"default": "./dist/cjs/event-stream.js"
|
|
118
|
+
}
|
|
119
|
+
},
|
|
100
120
|
"./server": {
|
|
101
121
|
"import": {
|
|
102
122
|
"types": "./dist/esm/cloud-server.d.ts",
|
|
@@ -152,16 +172,6 @@
|
|
|
152
172
|
}
|
|
153
173
|
},
|
|
154
174
|
"./package.json": "./package.json",
|
|
155
|
-
"./runtime": {
|
|
156
|
-
"import": {
|
|
157
|
-
"types": "./dist/esm/runtime.d.ts",
|
|
158
|
-
"default": "./dist/esm/runtime.js"
|
|
159
|
-
},
|
|
160
|
-
"require": {
|
|
161
|
-
"types": "./dist/cjs/runtime.d.ts",
|
|
162
|
-
"default": "./dist/cjs/runtime.js"
|
|
163
|
-
}
|
|
164
|
-
},
|
|
165
175
|
"./streams": {
|
|
166
176
|
"import": {
|
|
167
177
|
"types": "./dist/esm/streams/index.d.ts",
|
|
@@ -175,13 +185,15 @@
|
|
|
175
185
|
},
|
|
176
186
|
"scripts": {
|
|
177
187
|
"sync:contracts": "node scripts/sync-contracts.mjs",
|
|
188
|
+
"sync:source-stream-contract": "node scripts/sync-source-stream-contract.mjs",
|
|
178
189
|
"generate": "node scripts/generate-openapi.mjs && node scripts/generate-uns-v2.mjs",
|
|
179
|
-
"check:contracts": "node scripts/check-contracts.mjs && npm run check:acquisition-contract && node scripts/sync-capability-contract.mjs --check && node --test scripts/retired-stream-probe.test.mjs scripts/retired-http-push.test.mjs scripts/retired-controls.test.mjs scripts/retired-public-labels.test.mjs scripts/platform-schema-projection.test.mjs scripts/data-capability-vector.test.mjs scripts/sync-contracts.test.mjs scripts/sync-capability-contract.test.mjs scripts/live-program-contract.test.mjs scripts/sync-http-run-observations.test.mjs && node scripts/generate-openapi.mjs --check && node scripts/generate-uns-v2.mjs --check && npm run check:stream-contract",
|
|
190
|
+
"check:contracts": "node scripts/check-contracts.mjs && npm run check:acquisition-contract && node scripts/sync-capability-contract.mjs --check && node --test scripts/retired-stream-probe.test.mjs scripts/retired-http-push.test.mjs scripts/retired-controls.test.mjs scripts/retired-public-labels.test.mjs scripts/platform-schema-projection.test.mjs scripts/data-capability-vector.test.mjs scripts/sync-contracts.test.mjs scripts/sync-capability-contract.test.mjs scripts/live-program-contract.test.mjs scripts/sync-http-run-observations.test.mjs scripts/member-list-proxy-guidance.test.mjs && node scripts/generate-openapi.mjs --check && node scripts/generate-uns-v2.mjs --check && npm run check:stream-contract",
|
|
180
191
|
"check:skills": "node scripts/check-skills.mjs && node scripts/check-skill-examples.mjs",
|
|
181
192
|
"typecheck": "tsc --noEmit && tsc -p tsconfig.tests.json",
|
|
182
193
|
"build": "node scripts/build.mjs",
|
|
183
194
|
"prepare": "npm run build",
|
|
184
195
|
"test": "vitest run",
|
|
196
|
+
"test:gateway-enforce": "node scripts/test-gateway-enforce.mjs",
|
|
185
197
|
"check": "npm run check:contracts && npm run check:skills && npm run typecheck && npm test && npm run build && node scripts/check-package.mjs",
|
|
186
198
|
"prepack": "node scripts/sync-stream-contract.mjs --check --committed && node scripts/check-contracts.mjs --committed && npm run check:contracts && npm run check:skills && npm run build",
|
|
187
199
|
"prepublishOnly": "node scripts/sync-stream-contract.mjs --check --committed && node scripts/check-contracts.mjs --committed && npm run check",
|
|
@@ -226,6 +238,7 @@
|
|
|
226
238
|
"ajv": "^8.20.0",
|
|
227
239
|
"ajv-formats": "^3.0.1",
|
|
228
240
|
"esbuild": "^0.25.0",
|
|
241
|
+
"mqtt-v530": "npm:mqtt@5.3.0",
|
|
229
242
|
"openapi-typescript": "^7.0.0",
|
|
230
243
|
"react": "^19.0.0",
|
|
231
244
|
"typescript": "~5.9.3",
|
|
@@ -32,16 +32,22 @@ description: 使用 @tier0/sdk-v2 开发或迁移 Tier0 云端 TypeScript/JavaSc
|
|
|
32
32
|
```ts
|
|
33
33
|
import { createClient } from '@tier0/sdk-v2';
|
|
34
34
|
|
|
35
|
-
//
|
|
35
|
+
// 平台注入 TIER0_API_HOST 和 App 的 Key TIER0_API_KEY;本地开发自己设这两个。
|
|
36
36
|
export const client = createClient({ transportTimeoutMs: 30_000 });
|
|
37
37
|
export async function inspectConnection() {
|
|
38
38
|
return client.system.info();
|
|
39
39
|
}
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
也可传 `apiHost`、`apiKey` 或 `getApiHost` / `getApiKey` 回调。API Key 留在服务端;不要生成 `VITE_*KEY` 或让页面携带 App Key。浏览器通过自己的服务端接口访问平台;App Stream
|
|
42
|
+
也可传 `apiHost`、`apiKey` 或 `getApiHost` / `getApiKey` 回调。API Key 留在服务端;不要生成 `VITE_*KEY` 或让页面携带 App Key。浏览器通过自己的服务端接口访问平台;App Stream 交互(`client.streams`)也只在服务端用带 Key 的客户端。消息要求 Node.js 22+(WebSocket)。
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
凭证来源按属性存在性选择,优先顺序为 `apiKeyProvider`、`apiKey`、`getApiKey`;只有都未提供才读取 `TIER0_API_KEY`。显式 `undefined` 或 getter 返回 `undefined` 不会回退到环境 Key。每个用户请求使用独立客户端,不要把全局 `configureClient` 当作请求身份。
|
|
45
|
+
|
|
46
|
+
**Tier0 预览和部署里,App 的 Key 在运行进程的环境里**:平台注入 `TIER0_API_KEY`(这个 App 的 Key,秘密;预览是 preview Key,部署是 deployed Key)和 `TIER0_API_HOST`,`createClient()` 直接用它们,只发一个 `X-API-Key`。App 代码不要自己读、复制、打印或下发这个 Key,也不要生成或硬编码 Key;直接调平台时也通过 SDK。身份读平台设置的 `TIER0_WORKSPACE_ID`、`TIER0_APP_ID`。
|
|
47
|
+
|
|
48
|
+
App 的 Key 只能调 `/openapi/v2/uns/**`、`assets/files/**`(`client.files`;`assets/upload` 不在内)、`notifications/send`、`platform/getMembers`(`members.listWorkspace()`)、`apps/access-members/list`(`members.listApp()`)、`stream-requests/discover` 和 `stream-requests/<code>/describe|invoke`、`stream-messages/connect`(`client.streams`)、`capabilities/discover`、`info`;其他接口(`system.whoami()`、`notifications.get`、`mqttAuth`、`agent`)回 403 `hosted_app_key_route_forbidden`;Source / Stream 管理接口 App 的 Key 无权调用,Backend 回 403。App 的平台能力固定 11 项(`openapi.base`、`platform.users.read`、`notifications.send`、文件上传 / 下载 / 删除、`stream.requests.read` / `invoke` / `write`、`stream.messages.publish` / `subscribe`),每个 App 都有、不在 App 设置里配置,生效的是它们和 Key 权限、Key 模式、工作区套餐的交集。外部集合只由工作区 Owner/Admin 在 App 设置里授权,删掉后下一个请求起生效。MQTT 主题只能用 App 自己的命名空间 `apps/<TIER0_APP_ID>/<TIER0_RUNTIME>/…`,用 `appTopic()` 拼,见 [MQTT](tier0-sdk-v2-mq/SKILL.md)。没有 `TIER0_API_KEY` 而 `TIER0_RUNTIME` 为 `deployed` / `preview` 时 SDK 进入平台代理模式(不发凭据、带 `X-Tier0-SDK: 1`),当前平台不会出现这种运行时,见 [错误码](tier0-sdk-v2-data/references/errors.md#平台代理模式的错误)。
|
|
49
|
+
|
|
50
|
+
不同身份使用独立实例。App 调平台只有一种身份:App 自身。当前用户和角色从前门注入的 `X-Tier0-Visitor` 读(`parseVisitorHeader`),按角色决定谁能看、能改什么由 App 在服务端自己做,见 [身份与框架集成](references/integration.md)。
|
|
45
51
|
|
|
46
52
|
## 返回值与错误
|
|
47
53
|
|
|
@@ -54,7 +60,7 @@ export async function inspectConnection() {
|
|
|
54
60
|
|
|
55
61
|
方法存在不等于目标环境已部署或当前 Key 有权限。整数可能为 `bigint`;UNS 任意 JSON 中还可能含 `UnsV2RawNumber`。不要将标识符强制转为 Number;给浏览器返回数据时显式定义精确数字的序列化格式。
|
|
56
62
|
|
|
57
|
-
`createClient()` 的 HTTP
|
|
63
|
+
`createClient()` 的 HTTP 不自动重试,唯一例外是平台代理模式下代理自己产生的 `503` / `429`(带 `X-Tier0-Proxy: not-forwarded`,请求没有到达平台),SDK 按 `retry_after_ms` 最多重试 3 次;平台返回的 5xx 不重试。UNS 错误的 `execution` 说明执行到哪一步、`retry.safe` 说明能否用同一请求重发;带 `outcomeUnknown` 的失败应先对账,保留原意图键;取消请求不代表服务端撤销。App 数据层(`@tier0/sdk-v2/data`)会按 `retry.safe` 自动重试,见 App 数据 Skill。Source/Stream 仍使用底层 UNS v2 契约,所以错误类名保留 UNS,不代表它们仍属于数据入口。
|
|
58
64
|
|
|
59
65
|
## 从首版入口迁移
|
|
60
66
|
|
|
@@ -65,7 +71,7 @@ export async function inspectConnection() {
|
|
|
65
71
|
- `client.uns.recordFind(body)` 保持不变。
|
|
66
72
|
- 低层 `createUnsV2Client` 保留完整生成契约,供契约工具使用;业务主入口不再把 Source/Stream 管理混入 `client.uns`。
|
|
67
73
|
|
|
68
|
-
React/Vue
|
|
74
|
+
访问者身份与 React/Vue 示例见 [集成参考](references/integration.md)。完成接入时验证目标入口、真实身份、响应解包和错误分支;SDK 本地测试通过不代表环境联调完成。
|
|
69
75
|
|
|
70
76
|
Stream 请求与消息交互统一使用本包 `client.streams.requests` / `client.streams.messages`;[交互说明](tier0-sdk-v2-streams/SKILL.md)。Scaffold 只依赖本包;`client.stream` 管理接口与运行时交互用途不同。
|
|
71
77
|
|
|
@@ -2,36 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
仅在接入服务端身份或 React/Vue 时阅读本文件。基本分域调用见入口 Skill。
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 访问者身份(X-Tier0-Visitor)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
平台里 App 的每个请求都经过前门:前门先剥掉客户端带来的全部 `X-Tier0-*`,再注入明文的 `X-Tier0-Visitor`(UTF-8 JSON 对象的无填充 base64url,不签名)。服务端用 `parseVisitorHeader` 解析,它严格校验字段、类型、长度,并要求 `app` 等于 `TIER0_APP_ID`:
|
|
8
8
|
|
|
9
9
|
```ts
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
10
|
+
import { parseVisitorHeader, VISITOR_HEADER, VisitorError, type Visitor } from '@tier0/sdk-v2/server';
|
|
11
|
+
|
|
12
|
+
export function visitorOf(request: Request): Visitor | null {
|
|
13
|
+
try {
|
|
14
|
+
return parseVisitorHeader(request.headers.get(VISITOR_HEADER));
|
|
15
|
+
} catch (error) {
|
|
16
|
+
if (error instanceof VisitorError && error.code === 'VISITOR_HEADER_INVALID') return null; // 回 401
|
|
17
|
+
throw error; // VISITOR_CONFIGURATION_INVALID:没有 TIER0_APP_ID,回 503
|
|
18
|
+
}
|
|
19
19
|
}
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
返回 `{ subType: 'employee' | 'machine', subject, grant, workspaceID, appID, runtime: 'deployed' | 'preview', appRoles, activeRole? }`。`appRoles` 是访问者在 App 里的角色(预览里是「以某角色查看」选中的角色),`activeRole` 只在预览的「以某角色查看」里出现,只用于展示。
|
|
23
23
|
|
|
24
|
-
App
|
|
25
|
-
|
|
26
|
-
```ts
|
|
27
|
-
import { createClient } from '@tier0/sdk-v2';
|
|
28
|
-
|
|
29
|
-
export function clientForTrustedGatewayRequest(apiHost: string, apiKey: string, trustedDataSubject: string) {
|
|
30
|
-
return createClient({ apiHost, apiKey, unsV2AppDataSubject: trustedDataSubject });
|
|
31
|
-
}
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
SDK 仅检查委托的紧凑格式,不自行信任/验证其 claims。不要直接转发公开入口任意同名 header,部署边界必须保证它来自可信网关。不要跨员工复用这个实例或令牌。
|
|
24
|
+
App 调平台只有一种身份:App 自身(App Key,平台注入在运行进程的 `TIER0_API_KEY` 里,SDK 直接用)。平台不按访问者收窄 App 的数据,按角色决定当前用户能看、能改哪些记录是 App 自己的事:在服务端按 `appRoles` 过滤查询条件、拒绝写入,返回给浏览器前裁掉不该看的字段。不要把访问者 ID 或角色当成平台调用的参数去「代表」用户。
|
|
35
25
|
|
|
36
26
|
## React
|
|
37
27
|
|
|
@@ -69,4 +59,4 @@ export function useRecords(client: Tier0Client) {
|
|
|
69
59
|
|
|
70
60
|
## 联调证据
|
|
71
61
|
|
|
72
|
-
|
|
62
|
+
访问者头的字段以平台前门实际注入的 `X-Tier0-Visitor` 为准。契约生成、类型与本地测试证明包内部一致,不替代环境联调。确认身份、权限与字段返回后再切换真实业务流量。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: tier0-sdk-v2-data
|
|
3
|
-
description: 使用 @tier0/sdk-v2/data 在 App 服务端声明并读写本 App 的 UNS 集合:Zod 建模、种子与 TTL、Mongo
|
|
3
|
+
description: 使用 @tier0/sdk-v2/data 在 App 服务端声明并读写本 App 的 UNS 集合:Zod 建模、种子与 TTL、Mongo 风格读写、带总数分页与分组计数、原子批量与事务、变更订阅与转发给浏览器、Schema 演进和错误码处理。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# App 数据层
|
|
@@ -9,9 +9,15 @@ description: 使用 @tier0/sdk-v2/data 在 App 服务端声明并读写本 App
|
|
|
9
9
|
|
|
10
10
|
`@tier0/sdk-v2/data` 是 App 业务数据的服务端入口:用 `defineCollection` 声明集合,`createDataClient` 在首次访问时一次性 ensure 全部声明,然后用 Mongo 驱动风格的集合句柄读写 UNS 记录。没有 SQL、ORM、连接串或迁移步骤;Preview 与部署后的 App 共用同一批集合和记录。
|
|
11
11
|
|
|
12
|
+
所有数据调用都是 App 自身:平台不按当前用户收窄。按用户角色决定谁能看、能改哪些记录由 App 在服务端自己做,当前用户和角色从访问者头读(`@tier0/sdk-v2/server` 的 `parseVisitorHeader`,见[身份与框架集成](../references/integration.md))。
|
|
13
|
+
|
|
12
14
|
这里创建的集合以 `ownerAppId` 标识唯一所属 App。使用已有或其他 App 共享的集合时,用 `data.collection({ collectionId })` 获取句柄;可选 `schema` 仅做本地写入校验,不创建或修改集合。已有 ID 引用同样用于 `queryBatch`、`transact` 和 `subscribe`,不放入 `createDataClient` 的 `collections` 声明数组,不执行 ensure 或写种子。无需服务端 alias,仍须当前 App 已有相应集合授权;SDK 不授予权限,复用不改变所属关系。底层 [UNS](../tier0-sdk-v2-uns/SKILL.md) 的记录接口仍可直接按 ID 使用。
|
|
13
15
|
|
|
14
|
-
数据读写只在服务端代码(业务 service)里用 `@tier0/sdk-v2/data`:该入口依赖 Node 内置模块,浏览器打包会直接失败。组件和 route 的客户端代码只能从 `@tier0/sdk-v2/data/define` 取集合声明和类型(`defineCollection`、`Doc`、`NewDoc` 等),它不加载数据运行时,可以进浏览器包。需要 `zod` 4。底层调用 SDK 生成的 UNS 操作(`appCollectionsEnsure`、`recordFind`、`recordTransactionExecute`
|
|
16
|
+
数据读写只在服务端代码(业务 service)里用 `@tier0/sdk-v2/data`:该入口依赖 Node 内置模块,浏览器打包会直接失败。组件和 route 的客户端代码只能从 `@tier0/sdk-v2/data/define` 取集合声明和类型(`defineCollection`、`Doc`、`NewDoc` 等),它不加载数据运行时,可以进浏览器包。需要 `zod` 4。底层调用 SDK 生成的 UNS 操作(`appCollectionsEnsure`、`recordFind`、`recordTransactionExecute` 等),Tier0 预览和部署里读平台注入的 TIER0_API_HOST 和 App 的 Key TIER0_API_KEY(种子在 ensure 时以 App 身份写入);本地开发自己设这两个。
|
|
17
|
+
|
|
18
|
+
Data 客户端创建时固定 Host 和 Key(含环境值),不支持 `getApiKey` 或 `apiKeyProvider`;切换身份、Host 或环境凭证时须新建客户端,不能让 bootstrap、重试或订阅状态跨身份复用。
|
|
19
|
+
|
|
20
|
+
服务端已从可信身份链取得某主体的有效 Key 时,可用 `createDataClient({ apiHost, apiKey })` 为该主体创建独立实例,通过 `collection({ collectionId })` 访问其已获授权的现有集合;不要跨主体共享该实例。Key 轮换后创建新实例。声明集合的 ensure/seed 仍由服务端判断凭证是否有权限,不因创建客户端而获得授权。
|
|
15
21
|
|
|
16
22
|
集合声明必须分别填写业务名称 `name` 与稳定程序标识 `alias`,例如 `name: '生产工单'`、`alias: 'production_orders'`。name 是唯一界面名称,没有 displayName 或 alias 回退;ensure 通过 alias 找回原集合,不覆盖用户改名。
|
|
17
23
|
|
|
@@ -20,14 +26,14 @@ description: 使用 @tier0/sdk-v2/data 在 App 服务端声明并读写本 App
|
|
|
20
26
|
| 用户任务 | 参考 |
|
|
21
27
|
| --- | --- |
|
|
22
28
|
| 声明集合、选字段类型、索引、种子数据、文档建模 | [建模](references/modeling.md) |
|
|
23
|
-
| 查询、按 ID 读取、一页一次请求(queryBatch
|
|
24
|
-
|
|
|
29
|
+
| 查询、按 ID 读取、一页一次请求(queryBatch)、分页、聚合、写入、批量与原子性、订阅变更并转发给浏览器 | [读写](references/read-write.md) |
|
|
30
|
+
| 状态流转、并发编辑、跨文档不变量、计数器、请求中止与后台任务 | [一致性与请求上下文](references/consistency.md) |
|
|
25
31
|
| 加字段、改类型、改描述、收窄 Schema | [Schema 演进](references/schema-evolution.md) |
|
|
26
32
|
| DataError 字段、UNS 错误码与处理 | [错误码](references/errors.md) |
|
|
27
33
|
|
|
28
34
|
## 最小用法
|
|
29
35
|
|
|
30
|
-
新 App 没有集合时是正常空态:`uns.dataAppContext({})` 返回 `dataState: "uninitialized"` 和空集合,不创建任何数据。无需先调用 context 或手工激活数据策略;声明真实业务集合后,第一次数据访问会自动初始化。`dataState: "ready"` 也不代表可访问整个工作空间,仍以返回的集合授权为准。引用已有 WMS
|
|
36
|
+
新 App 没有集合时是正常空态:`uns.dataAppContext({})` 返回 `dataState: "uninitialized"` 和空集合,不创建任何数据。无需先调用 context 或手工激活数据策略;声明真实业务集合后,第一次数据访问会自动初始化。`dataState: "ready"` 也不代表可访问整个工作空间,仍以返回的集合授权为准。引用已有 WMS 等外部集合要由工作区 Owner/Admin 在 App 设置里授权(Builder agent 加不了),不能创建同名空集合代替;授权删掉后下一个请求起被拒绝。管理员停用返回 `FORBIDDEN`、reason `APP_DATA_POLICY_DISABLED`,不得自动恢复。
|
|
31
37
|
|
|
32
38
|
```ts
|
|
33
39
|
import { z } from 'zod';
|
|
@@ -39,25 +39,21 @@ export async function nextOrderNumber() {
|
|
|
39
39
|
|
|
40
40
|
`transact` 的结果状态不是 `committed`(`dryRun` 时为 `validated`)会抛 `TRANSACTION_FAILED`,`details.results` 带每一步的错误。
|
|
41
41
|
|
|
42
|
-
##
|
|
42
|
+
## 调用身份与请求上下文
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
每次调用都是 App 自身,使用平台注入的 App Key;集合 ensure、种子写入、记录租约、幂等键都只按 App 区分,不按当前用户区分。按用户角色决定谁能看、能改什么由 App 在服务端自己做(当前用户和角色见[身份与框架集成](../../references/integration.md)的访问者头)。
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
请求入口用 `runWithAbortSignal(request.signal, next)` 把请求的中止信号交给数据层:调用方断开时,这个请求还在排队的数据调用不再发出(`DATA_ABORTED`,`notStarted`),进行中的被取消。脱离请求的工作(定时器、队列、发出即不管的后续任务)放进 `runDetached(() => ...)`,不随请求中止。ensure、种子写入和 `data.subscribe` 本来就不受请求中止影响。
|
|
47
47
|
|
|
48
48
|
```ts
|
|
49
|
-
import {
|
|
49
|
+
import { runDetached, runWithAbortSignal } from '@tier0/sdk-v2/data';
|
|
50
50
|
|
|
51
51
|
export function handleRequest<T>(request: Request, next: () => Promise<T>): Promise<T> {
|
|
52
|
-
return
|
|
52
|
+
return runWithAbortSignal(request.signal, next);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
export function scheduleFollowUp(task: () => Promise<void>) {
|
|
56
|
-
setTimeout(() => void
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function servingEmployee(): boolean {
|
|
60
|
-
return currentDataSubject() !== undefined;
|
|
56
|
+
setTimeout(() => void runDetached(task), 1000);
|
|
61
57
|
}
|
|
62
58
|
```
|
|
63
59
|
|
|
@@ -38,10 +38,10 @@ export function toResponse(error: unknown): Response {
|
|
|
38
38
|
|
|
39
39
|
| code | 含义 |
|
|
40
40
|
| --- | --- |
|
|
41
|
-
| `DATA_NOT_CONFIGURED` (503) | 缺少 TIER0_API_HOST
|
|
41
|
+
| `DATA_NOT_CONFIGURED` (503) | 缺少 TIER0_API_HOST(或 apiHost),或缺少 TIER0_API_KEY(或 apiKey)。平台里两者都由平台注入;没绑 App 的会话两者都没有 |
|
|
42
42
|
| `DATA_TIMEOUT` (503) | UNS 未在超时内应答(每次调用从开始算共 25 秒,含排队与重试,`transportTimeoutMs` 可调);写入时 `outcomeUnknown` 为 true、不重试 |
|
|
43
43
|
| `DATA_OVERLOADED` (503) | 本进程 6 个请求名额一直占满,调用在超时前没排到,**没有发出**(`execution: 'notStarted'`,`retry.safe`);减少并发或合并读取 |
|
|
44
|
-
| `DATA_ABORTED` (499) | 业务请求已中止(`
|
|
44
|
+
| `DATA_ABORTED` (499) | 业务请求已中止(`runWithAbortSignal(request.signal, …)` 传入的信号):排队中的调用不再发出(`notStarted`),已发出的写入 `outcomeUnknown` |
|
|
45
45
|
| `DATA_UNREACHABLE` (503) | 连不上数据服务(读会自动重试);网关地址只在原始错误里,不进 message |
|
|
46
46
|
| `HTTP_502` / `HTTP_503` / `HTTP_504` (503) | 网关在滚动发布、Pod 重启时未经 UNS 直接返回;读会自动重试,写入时 `outcomeUnknown` 为 true、不重试 |
|
|
47
47
|
| `DATA_BAD_RESPONSE` (502) | UNS 应答不符合契约 |
|
|
@@ -58,7 +58,7 @@ export function toResponse(error: unknown): Response {
|
|
|
58
58
|
|
|
59
59
|
| code | 含义与处理 |
|
|
60
60
|
| --- | --- |
|
|
61
|
-
| `SCHEMA_CONFLICT` (409) | 声明无法与存储合并,没有任何改动。`data.conflicts` 列出 `{ alias, path, reason, current?, declared? }`:`TYPE_CONFLICT` → 换新字段名;`LIMIT_EXCEEDED` → 拆分结构;`JOIN_UNSUPPORTED` → 存储中有声明无法放宽的节点,用 `schema:replace` 修复;`
|
|
61
|
+
| `SCHEMA_CONFLICT` (409) | 声明无法与存储合并,没有任何改动。`data.conflicts` 列出 `{ alias, path, reason, current?, declared? }`:`TYPE_CONFLICT` → 换新字段名;`LIMIT_EXCEEDED` → 拆分结构;`JOIN_UNSUPPORTED` → 存储中有声明无法放宽的节点,用 `schema:replace` 修复;`OPEN_OBJECT` → 用 `z.object`;`ALIAS_IN_USE` → 选择未占用的 alias;`NAME_IN_USE` → 选择同目录下不同且有业务含义的 name,保留 alias,不拼随机后缀;`INVALID_INDEX` → 修正索引 |
|
|
62
62
|
| `INVALID_REQUEST` (400),`path=collections/<i>/directoryPath` | 新建集合缺少目录或路径非法。声明实际业务路径后再调用;不回退到缺少业务范围的根级 App 目录,不添加虚构层级 |
|
|
63
63
|
| `SCHEMA_CONFLICT`,`data.reason=DIRECTORY_PATH_CONFLICT` | 路径被已归档目录或同名非目录对象占用;核对真实目录,不能自动恢复、改名或扩大授权 |
|
|
64
64
|
| `DIRECTORY_PATH_NOT_APPLIED`(`ignored` 提示) | 已有 alias 的目录与声明不同;保留当前位置继续服务。需要移动时走明确授权的元数据操作,ensure 不迁移 |
|
|
@@ -86,3 +86,20 @@ export function toResponse(error: unknown): Response {
|
|
|
86
86
|
| `FORBIDDEN` (403) | Key 不能做该操作(其他 App 或共享集合),或 Key 已吊销 |
|
|
87
87
|
|
|
88
88
|
记录调用上的 `data.reason`:`COLLECTION_NOT_GRANTED`(403,授权不覆盖该集合或操作)与 `COLLECTION_NOT_FOUND`(403/404,集合不存在或写入目标已回收)仅对声明集合触发[自愈](consistency.md#首次访问与自愈),已有 ID 引用直接报告错误;回收不使已有授权的历史读取失效。`APP_PERMISSION_MISSING`(403,`data.missingPermission`)表示 Key 缺少权限,例如删除记录需要 `uns.delete`——业务记录通常以状态结束(`CANCELLED`、`ARCHIVED`)而不是删除。
|
|
89
|
+
|
|
90
|
+
## 平台代理模式的错误
|
|
91
|
+
|
|
92
|
+
只在平台代理模式下出现:`TIER0_RUNTIME` 为 `deployed` / `preview` 而进程里没有 `TIER0_API_KEY`,SDK 不发凭据、请求经节点代理(`sbx-api-proxy`)由它附加 App 的 Key。当前平台给 App 注入 `TIER0_API_KEY`,不会进入这个模式。代理自己产生的错误带响应头 `X-Tier0-Proxy: not-forwarded`,响应体 `{code, message, retry_after_ms?}`,请求**没有到达平台**,所以 `execution` 是 `notStarted`,写入重发也安全。`DataError.code`、`UnsV2Error.code`、`ApiError.errorCode` 都是下表的 `code`。
|
|
93
|
+
|
|
94
|
+
| code | 含义与处理 |
|
|
95
|
+
| --- | --- |
|
|
96
|
+
| `sbx_proxy_not_ready` (503) | 代理刚启动,还没拿到这台 VM 的绑定。SDK 已按 `retry_after_ms` 重试 3 次,仍失败就稍后再试 |
|
|
97
|
+
| `sbx_proxy_limited` (429) | 超过这台 VM 的上限(64 个并发请求、50 rps、8 个 SSE、16 条 MQTT 等)。SDK 已重试 3 次;减少并发 |
|
|
98
|
+
| `sbx_proxy_credential_pending` (503) | 绑定在,但 App 的 Key 还没送到代理。SDK 已按 `retry_after_ms` 重试 3 次,仍失败就稍后再试 |
|
|
99
|
+
| `sbx_proxy_no_credential` (403) | 这台 VM 永远没有 Key:会话没有绑定 App,只能调 `/openapi/v2/info`。不重试 |
|
|
100
|
+
| `sbx_proxy_unbound` (403) | 这台 VM 当前没有绑定:实例正在启动、停止或被替换,等平台恢复 |
|
|
101
|
+
| `sbx_proxy_bad_request` (400) | 请求格式被拒:absolute-form、`CONNECT`、`Upgrade`、路径含 `.` / `..` / `//` / `%2f` / `%2e` / `%5c` / `;`、请求头超过 32 KiB 或 100 个 |
|
|
102
|
+
| `sbx_proxy_forbidden` (403) | 缺 `X-Tier0-SDK: 1`,或带了 `Forwarded` / `X-Forwarded-*` / `X-Real-IP`。直接用 `fetch` / `curl` 时自己加 `X-Tier0-SDK: 1` |
|
|
103
|
+
| `sbx_proxy_not_found` (404) | 路径不在代理白名单里:只转发 `/openapi/v2/uns/**`、`assets/**`、`notifications/send`、`notifications/get`、`platform/getMembers`、`apps/access-members/list`、`info`(白名单内但 App 的 Key 无权的接口,如 Source / Stream 管理、`assets/upload`,由 Backend 回 403;App 的 Key 在 `assets/` 下只能调 `assets/files/**`) |
|
|
104
|
+
|
|
105
|
+
只有带 `X-Tier0-Proxy: not-forwarded` 的 `503` / `429` 会被 SDK 自动重试;平台返回的 5xx 不重试。
|
|
@@ -91,7 +91,7 @@ export async function ordersScreen(page: number, selectedId: string) {
|
|
|
91
91
|
| --- | --- |
|
|
92
92
|
| `find(filter, { sort, limit, projection })` | 游标,每页最多 200 条;列表读取始终给 `limit` |
|
|
93
93
|
| `findOne(filter)` | 无匹配返回 `null` |
|
|
94
|
-
| `get(ids, { projection })` | 按 `_id` 读取,返回 `{ items, missing }`,`items` 按请求顺序(重复的 ID
|
|
94
|
+
| `get(ids, { projection })` | 按 `_id` 读取,返回 `{ items, missing }`,`items` 按请求顺序(重复的 ID 只取第一次出现);不存在或已删除的 ID 一律进 `missing`。超过 100 个自动分批 |
|
|
95
95
|
| `findPage(filter, { sort, page, pageSize, maxCount })` | 页码分页,一次请求返回 `{ items, total: { count, capped }, page, pageSize }`;pageSize 最多 200,总数默认数到 10000(`maxCount` 最多 100000),超过时 `capped: true` |
|
|
96
96
|
| `countDocuments(filter, { maxCount })` | 返回 `{ count, capped }`,默认数到 100000;只要阈值时给小的 `maxCount`(如角标 `99+`) |
|
|
97
97
|
| `countBy(field, filter)` | 每个取值的数量 `[{ value, count }]`,从多到少;一次 `$group` |
|
|
@@ -161,26 +161,59 @@ export async function bumpAll(items: Collection<{ status: string; views: number
|
|
|
161
161
|
|
|
162
162
|
```ts
|
|
163
163
|
import { z } from 'zod';
|
|
164
|
-
import { createDataClient, defineCollection,
|
|
164
|
+
import { createDataClient, defineCollection, type SubscriptionEvent } from '@tier0/sdk-v2/data';
|
|
165
165
|
|
|
166
166
|
const tickets = defineCollection({ alias: 'tickets', name: '问题工单', directoryPath: ['问题管理'], schema: z.object({ title: z.string(), status: z.enum(['OPEN', 'DONE']) }) });
|
|
167
167
|
const data = createDataClient({ collections: [tickets] });
|
|
168
168
|
|
|
169
|
-
/**
|
|
170
|
-
export async function pumpChanges(
|
|
171
|
-
await
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
});
|
|
169
|
+
/** 服务端自己消费变更(例如刷新缓存)。 */
|
|
170
|
+
export async function pumpChanges(handle: (event: SubscriptionEvent) => void, signal: AbortSignal) {
|
|
171
|
+
for await (const event of data.subscribe({ collections: [tickets], signal })) {
|
|
172
|
+
// reset:续传位置失效或访问范围变化,重新读取当前数据。
|
|
173
|
+
handle(event);
|
|
174
|
+
}
|
|
177
175
|
}
|
|
178
176
|
```
|
|
179
177
|
|
|
180
|
-
- 事件:`{ type: 'change', eventId, collection(alias), collectionId, change: 'record' | 'delete', recordId, revision, occurredAt
|
|
178
|
+
- 事件:`{ type: 'change', eventId, collection(alias), collectionId, change: 'record' | 'delete', recordId, revision, occurredAt }` 或 `{ type: 'reset', reason, message? }`。心跳与重连在 SDK 内部处理;迭代抛出 `DataError` 表示不能再重试(如 403),正常结束只有 `signal` 中止或跳出循环。
|
|
181
179
|
- 事件按写入事务的顺序到达,不是提交顺序,同一记录的 revision 可能乱序。SDK 已丢弃 revision 不大于该记录已投递 revision 的非 `delete` 变更;`delete` 一律投递(迟到的也投递),并清除该记录的 revision(同一 ID 再写入时可能从 1 开始)。收到事件后按 `recordId` 重新读取,不要用事件顺序推断记录的最终状态。
|
|
182
180
|
- `reset`:`CURSOR_EXPIRED`(续传位置已被清理、游标无法解开或租户库已迁移)或 `SCOPE_CHANGED`(访问范围变化,例如集合不再可读)。重新读取当前数据;SDK 随后不带续传位置重新订阅。
|
|
183
|
-
-
|
|
181
|
+
- 订阅以 App 身份运行,看得到 App 被授权集合的全部变更,没有按当前用户的过滤。转发给浏览器时由 App 按访问者角色决定每个浏览器能知道哪些变更(见下一节)。
|
|
184
182
|
- 默认订阅全部已声明的集合(最多 64 个);`recordIds`(最多 100 个)只能配一个集合。
|
|
185
|
-
-
|
|
183
|
+
- 订阅不占数据请求的并发名额,但每个工作区的订阅数有上限(超出时 SDK 按 429 自动退避重连):一个服务端进程只开一条订阅,在进程内扇出,不要每个浏览器连接开一条。App 刚导入还没就绪(`APP_NOT_READY`)时订阅会一直等待,不会结束。
|
|
186
184
|
- 变更保留 24 小时;`lastEventId` 太旧时收到 `reset`。`lastEventId` 必须是收到过的事件 ID(可见 ASCII),否则调用时直接抛 `TypeError`。
|
|
185
|
+
|
|
186
|
+
## 把变更推给浏览器
|
|
187
|
+
|
|
188
|
+
浏览器不直连 UNS 或 MQTT。`createChangeRelay(data)` 在服务端进程里开一条 `data.subscribe`,转成可以挂在 App 路由上的 SSE 响应,`filter` 由 App 按访问者角色决定每个浏览器收到哪些变更:
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
import { z } from 'zod';
|
|
192
|
+
import { createChangeRelay, createDataClient, defineCollection, type RecordChange } from '@tier0/sdk-v2/data';
|
|
193
|
+
import { parseVisitorHeader, VISITOR_HEADER, type Visitor } from '@tier0/sdk-v2/server';
|
|
194
|
+
|
|
195
|
+
const tickets = defineCollection({ alias: 'tickets', name: '问题工单', directoryPath: ['问题管理'], schema: z.object({ title: z.string() }) });
|
|
196
|
+
const data = createDataClient({ collections: [tickets] });
|
|
197
|
+
const relay = createChangeRelay(data); // 每进程一个
|
|
198
|
+
|
|
199
|
+
/** App 自己的规则:哪些角色能看哪个集合。 */
|
|
200
|
+
function canSee(visitor: Visitor, change: RecordChange): boolean {
|
|
201
|
+
return change.collection !== 'tickets' || visitor.appRoles.includes('support');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** GET /api/changes */
|
|
205
|
+
export function changes(request: Request): Response {
|
|
206
|
+
const visitor = parseVisitorHeader(request.headers.get(VISITOR_HEADER));
|
|
207
|
+
return relay.stream({
|
|
208
|
+
filter: (change) => canSee(visitor, change),
|
|
209
|
+
lastEventId: request.headers.get('Last-Event-ID'),
|
|
210
|
+
signal: request.signal,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
- 帧:`event: change`(`{ eventId, collection, change, recordId, revision, occurredAt }`,不带记录数据)、`event: reset`(`{ reason }`)、`event: unavailable`(`{ reason }`,上游连不上、仍在重试),每 15 秒一个只带 `id` 的帧推进位置(还没有位置时是注释)。浏览器用 `@tier0/sdk-v2/event-stream` 的 `openEventStream` 读(页面隐藏时断开、可见时带 `Last-Event-ID` 续传,转发器补发或回 `reset`),收到 `change` 后经 App 自己的接口重新读取(那里同样按角色过滤),收到 `reset` 重新加载整个视图。转发器不发 `retry:`,重连节奏由 `openEventStream` 决定。
|
|
216
|
+
- `id` 是转发器自己的位置(`<epoch>.<seq>`),不是 UNS 游标,浏览器拿不到上游位置;只在上游能送达其后所有变更时才给。冷启动时上游第一次连上之前不给位置,连上后先给 `reset`(`upstream-started`)再给位置。上游失败或空闲关闭(最后一个浏览器离开 30 秒)后从最后的 UNS 位置重开,epoch 不变,不漏、不 reset;只有 UNS 回 `reset` 才换 epoch,上游重新连上后给浏览器发 `reset`。重连带 `Last-Event-ID`:在当前 epoch 最近 1000 条里找得到就补发之后的(同样经过 `filter`),找不到就只给这个浏览器一个 `reset`。
|
|
217
|
+
- 上游连不上(超过订阅上限的 429、`APP_NOT_READY` 等)时 `relay.status` 是 `unavailable`,`onStatus(status, error)` 回调,`logger.warn` 记一次,浏览器收到 `event: unavailable`;上游恢复后状态回到 `ready`。
|
|
218
|
+
- `filter` 同步调用,对实时和补发的变更都生效;抛错的变更不发。事件只有标识,但 `recordId` 本身也可能透露信息:规则按记录的,过滤时就把这个浏览器无权知道的记录去掉。
|
|
219
|
+
- 每个浏览器连接 10 分钟(±10%)后结束,浏览器重连时路由重新认证。选项:`collections`、`bufferSize`、`idleCloseMs`、`restartIntervalMs`、`maxRestartIntervalMs`、`heartbeatMs`、`maxStreamMs`、`onStatus`、`logger`。
|
|
@@ -7,7 +7,7 @@ description: 使用 @tier0/sdk-v2 查询 Tier0 Workspace 成员、搜索用户
|
|
|
7
7
|
|
|
8
8
|
参数表、成员选择和有界增量分页见 [Workspace 成员](references/platform-members.md)。v1 的项目成员接口未迁入 v2,不能用 Workspace 列表代替 App 授权名单。
|
|
9
9
|
|
|
10
|
-
公开入口有两个:`client.members.listWorkspace` 只用于 Workspace 级的成员选择;需要能使用某个 App 的成员时用 `client.members.listApp`。App 成员由服务端授权决定。使用服务端 API Key;平台授权决定可查询范围。
|
|
10
|
+
公开入口有两个:`client.members.listWorkspace` 只用于 Workspace 级的成员选择;需要能使用某个 App 的成员时用 `client.members.listApp`。Tier0 App 里两者都可用:`listApp`(`/openapi/v2/apps/access-members/list`)和 `listWorkspace`(`/openapi/v2/platform/getMembers`)每个 App 都能调(`platform.users.read` 是 App 的固定能力,不用在 App 设置里开);托管 App Key 的 `listApp` 响应不带邮箱(`email` 为空字符串,`userName` 和角色保留)。App 成员由服务端授权决定。使用服务端 API Key;平台授权决定可查询范围。
|
|
11
11
|
|
|
12
12
|
## 搜索与展示候选人
|
|
13
13
|
|
|
@@ -49,7 +49,7 @@ export async function searchMembers(keyword: string, page = 1) {
|
|
|
49
49
|
|
|
50
50
|
## App 可用成员
|
|
51
51
|
|
|
52
|
-
能使用某个 App 的成员用 `members.listApp
|
|
52
|
+
能使用某个 App 的成员用 `members.listApp`(Tier0 App 里可用,每个 App 都有 `platform.users.read`),参数、示例与错误处理见 [App 可用成员](references/app-members.md)。`appId` 必须显式传入,SDK 不隐式读取 `APP_ID`;查询当前 App 时传 `getCurrentAppId()`。Builder 预览里查到的是当前草稿角色的成员,部署后是线上版本的成员;这个 `scope` 由 SDK 自动带上,App 代码不要传。接口只接受 API Key,登录会话不能调用;绑定 App 的凭证只能查自己的 appId,查其他 App 返回 403。3003016 / 3003017 都是 HTTP 503,表示访问名单暂时无法给出,不是空名单:按 `ApiError.code` 分支,不匹配 message 文本;3003016 需要重新部署 App,3003017 可稍后重试。
|
|
53
53
|
|
|
54
54
|
## 当前边界
|
|
55
55
|
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# App 可用成员
|
|
2
2
|
|
|
3
|
-
方法:`members.listApp(body)`;POST /openapi/v2/apps/access-members/list
|
|
3
|
+
方法:`members.listApp(body)`;POST /openapi/v2/apps/access-members/list。
|
|
4
|
+
|
|
5
|
+
> Tier0 App 里可用,和 `listWorkspace` 同一套规则:`platform.users.read` 是每个 App 的固定能力,不用配置;回 403 时报告问题(例如 Key 被收窄),不改用 Workspace 成员列表代替;托管 App Key 的响应不带邮箱(`email` 为空字符串),展示用 `userName`。
|
|
6
|
+
|
|
7
|
+
返回能使用该 App 的有效成员:在 Builder 预览里按当前草稿的角色计算(从没发布过的 App 也一样),部署后按线上版本计算。口径由请求的 `scope` 决定,SDK 按平台注入的 `TIER0_RUNTIME` 自动带上(`preview` → `draft`,`deployed` → `production`);App 代码不要传 `scope`,也不要在 `.env` 或 `artifact.toml` 里设置 `TIER0_RUNTIME`。`appId` 必填且由调用方显式传入,SDK 不隐式读取 `APP_ID`;查询当前 App 时传 `getCurrentAppId()`。接口只接受 API Key,登录会话不能调用;绑定 App 的凭证只能查自己的 appId,查其他 App 返回 403。
|
|
4
8
|
|
|
5
9
|
| 字段 | 类型 | 说明 |
|
|
6
10
|
| --- | --- | --- |
|
|
@@ -9,6 +9,29 @@ description: 使用 @tier0/sdk-v2/mq 连接 Tier0 MQTT WebSocket Broker,管理
|
|
|
9
9
|
|
|
10
10
|
`Tier0MQClient` 从独立子入口导入。它面向消息客户端;配置平台采集连接用 Source,运行处理逻辑用 Stream。HTTP SDK 的“不自动重试”不等于 MQTT 禁用重连或 QoS 重投递。
|
|
11
11
|
|
|
12
|
+
## 在 Tier0 App 里
|
|
13
|
+
|
|
14
|
+
预览和部署里直接 `new Tier0MQClient()`,**什么都不传**:broker 取自平台注入的 `TIER0_MQTT_HOST` / `TIER0_MQTT_PORT`,password 是平台注入的 App Key `TIER0_API_KEY`,username 为 `<ws>&open`(ws 从 Key 解析),clientId 为托管格式 `<ws>&h.<app>.<16 位随机>`(app 即 `TIER0_APP_ID`;Backend 只接受这种 clientId 的 App Key)。显式传 `clientId` / `username` / `password` 时按传入的来,托管格式不再生效。断线后 SDK 一直重连,持久会话保留 QoS 1 消息。App 里也不能调 `mqttAuth`。
|
|
15
|
+
|
|
16
|
+
App 的 Key 只能收发 App 自己命名空间里的主题 `apps/<TIER0_APP_ID>/<preview|deployed>/…`:预览的 Key 只能用 `preview` 段,部署的 Key 只能用 `deployed` 段,可发布可订阅,命名空间内可以用 `+` / `#`;别的 App、另一个运行环境的段和其他主题都会被 broker 拒绝。主题不在 App 设置里配置。Backend 按 Key 自己的运行环境定段名,没有运行环境的旧 Key 按 `deployed`。
|
|
17
|
+
|
|
18
|
+
主题一律用 `appTopic(suffix)` 拼:它读平台注入的 `TIER0_APP_ID`、`TIER0_RUNTIME`(`preview` / `deployed`),返回 `apps/<TIER0_APP_ID>/<TIER0_RUNTIME>/<suffix>`;缺一个或值不对时抛错,不猜段名。`suffix` 不能为空、不能以 `/` 开头。`appTopic` 只在平台里(预览 / 部署)用,平台外会抛错;本地开发不要设 `TIER0_RUNTIME`,需要本地调试 MQTT 时自己写完整主题。
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { Tier0MQClient, appTopic } from '@tier0/sdk-v2/mq';
|
|
22
|
+
|
|
23
|
+
export const appMq = new Tier0MQClient();
|
|
24
|
+
|
|
25
|
+
export async function watchAlarms(onAlarm: (topic: string, payload: string) => void) {
|
|
26
|
+
await appMq.connect();
|
|
27
|
+
appMq.subscribe(appTopic('alarms/#'), onAlarm);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function raiseAlarm(line: string, level: string) {
|
|
31
|
+
await appMq.publish(appTopic(`alarms/${line}`), { level }, { qos: 1 });
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
12
35
|
## 使用已有凭证订阅
|
|
13
36
|
|
|
14
37
|
```ts
|
|
@@ -56,6 +79,8 @@ export async function revokeMqttCredential(id: number | bigint) {
|
|
|
56
79
|
|
|
57
80
|
仅在任务需要创建凭证时使用 create,普通订阅重用已有受控凭证。完整 username/password/clientID 只在创建时返回一次,按秘密管理,别写日志。SDK 配置的 clientId 小写 d,接口回执的 clientID 大写 ID。
|
|
58
81
|
|
|
82
|
+
仅在未提供 `password` 时使用进程环境 Key。`connect({ password: undefined })` 会拒绝,不使用已保存的身份;省略该字段仍沿用现有配置。拒绝不会改变现有连接或其自动重连身份,切换身份前须断开并创建新客户端。
|
|
83
|
+
|
|
59
84
|
也可从编码了 Workspace 的平台 Key 推导 MQTT 身份;无法解析的 Key 会拒绝连接,不回退到 Enterprise 身份。浏览器场景须使用针对当前用户/主题授权的凭证方案,不能为了省事下发长期 App Key。
|
|
60
85
|
|
|
61
86
|
## 发布与确认边界
|
|
@@ -45,7 +45,7 @@ export async function subscribeTelemetry(
|
|
|
45
45
|
|
|
46
46
|
回调是 (topic,payload),payload 为字符串。subscribe 返回 void,订阅失败通过 error 事件,不把函数返回当 broker SUBACK。此例独占连接;共享连接只退订自己的 handler,不关闭其他消费者。
|
|
47
47
|
|
|
48
|
-
+ 匹配一层,#
|
|
48
|
+
+ 匹配一层,# 匹配多层,订阅范围必须属于任务和主题授权。在 Tier0 App 里用平台注入的 App Key 时,主题只能在 App 自己的命名空间里:`filter` / `topic` 用 `appTopic('telemetry/#')` 这样拼(`apps/<TIER0_APP_ID>/<TIER0_RUNTIME>/…`),其他主题会被拒绝。主题和消息格式以目标平台的实际契约为准。App 服务端订阅新 UNS Record 变更用 `@tier0/sdk-v2/data` 的 `data.subscribe`(`records:subscribe` 服务端推送),不是 MQTT 主题。
|
|
49
49
|
|
|
50
50
|
## 发布
|
|
51
51
|
|
|
@@ -5,7 +5,7 @@ description: 使用 @tier0/sdk-v2 发送和查询 Tier0 站内通知、Web/Deskt
|
|
|
5
5
|
|
|
6
6
|
# 通知
|
|
7
7
|
|
|
8
|
-
服务端使用 `createClient()
|
|
8
|
+
服务端使用 `createClient()`:读 TIER0_API_HOST / TIER0_API_KEY(平台里由平台注入 App 的 Key,本地开发自己设),或显式传 apiHost/apiKey。发送必须符合用户已授权的收件人和内容范围,查询状态不自动补发。
|
|
9
9
|
|
|
10
10
|
| 任务 | 参考 | 方法 |
|
|
11
11
|
| --- | --- | --- |
|
|
@@ -17,8 +17,8 @@ description: 使用 @tier0/sdk-v2 发送和查询 Tier0 站内通知、Web/Deskt
|
|
|
17
17
|
|
|
18
18
|
- type 固定 inbox;Swagger 的 string 不代表接受任意类型。
|
|
19
19
|
- 用人读姓名/邮箱选择收件人,ID 由成员数据解析并保持字符串,不让用户填写 ID。Workspace 成员不等于 App 可访问用户。
|
|
20
|
-
- title 最多50字符,content 最多800
|
|
21
|
-
- App sender 使用 getCurrentAppId()
|
|
20
|
+
- title 最多50字符,content 最多800字符。不传 mode:平台按来源判定,预览和沙箱发出的是 test,只进该 App 的预览铃铛,线上 App 发出的是 live;test 也是真实消息,不能当预检。
|
|
21
|
+
- App sender 使用 getCurrentAppId()(平台设置的 TIER0_APP_ID,其次旧的 APP_ID),导航和名称展示按目标前端支持核实。
|
|
22
22
|
- sender/link/meta 是展示提示,不作为授权。新代码不使用废弃 source 字段。
|
|
23
23
|
- 同一业务事件、收件人、内容使用稳定 idempotencyKey;新事件使用新键,不因超时随机换键。
|
|
24
24
|
|