@tier0/sdk-v2 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE.md +7 -0
- package/README.md +201 -2
- package/dist/cjs/app-identity-contract.generated.d.ts +22 -0
- package/dist/cjs/app-identity-contract.generated.js +51 -0
- package/dist/cjs/app-identity-contract.generated.js.map +1 -0
- package/dist/cjs/capabilities.d.ts +54 -0
- package/dist/cjs/capabilities.js +134 -0
- package/dist/cjs/capabilities.js.map +1 -0
- package/dist/cjs/client.d.ts +604 -0
- package/dist/cjs/client.js +35 -0
- package/dist/cjs/client.js.map +1 -0
- package/dist/cjs/cloud-server.d.ts +31 -0
- package/dist/cjs/cloud-server.js +114 -0
- package/dist/cjs/cloud-server.js.map +1 -0
- package/dist/cjs/cloud-transport.d.ts +46 -0
- package/dist/cjs/cloud-transport.js +192 -0
- package/dist/cjs/cloud-transport.js.map +1 -0
- package/dist/cjs/controls.d.ts +93 -0
- package/dist/cjs/controls.js +169 -0
- package/dist/cjs/controls.js.map +1 -0
- package/dist/cjs/data/bootstrap.d.ts +66 -0
- package/dist/cjs/data/bootstrap.js +282 -0
- package/dist/cjs/data/bootstrap.js.map +1 -0
- package/dist/cjs/data/call.d.ts +16 -0
- package/dist/cjs/data/call.js +51 -0
- package/dist/cjs/data/call.js.map +1 -0
- package/dist/cjs/data/client.d.ts +92 -0
- package/dist/cjs/data/client.js +102 -0
- package/dist/cjs/data/client.js.map +1 -0
- package/dist/cjs/data/collection.d.ts +238 -0
- package/dist/cjs/data/collection.js +441 -0
- package/dist/cjs/data/collection.js.map +1 -0
- package/dist/cjs/data/context.d.ts +10 -0
- package/dist/cjs/data/context.js +39 -0
- package/dist/cjs/data/context.js.map +1 -0
- package/dist/cjs/data/declarations.d.ts +11 -0
- package/dist/cjs/data/declarations.js +18 -0
- package/dist/cjs/data/declarations.js.map +1 -0
- package/dist/cjs/data/define.d.ts +69 -0
- package/dist/cjs/data/define.js +31 -0
- package/dist/cjs/data/define.js.map +1 -0
- package/dist/cjs/data/error.d.ts +60 -0
- package/dist/cjs/data/error.js +184 -0
- package/dist/cjs/data/error.js.map +1 -0
- package/dist/cjs/data/index.d.ts +21 -0
- package/dist/cjs/data/index.js +43 -0
- package/dist/cjs/data/index.js.map +1 -0
- package/dist/cjs/data/schema.d.ts +82 -0
- package/dist/cjs/data/schema.js +607 -0
- package/dist/cjs/data/schema.js.map +1 -0
- package/dist/cjs/data/sort.d.ts +8 -0
- package/dist/cjs/data/sort.js +21 -0
- package/dist/cjs/data/sort.js.map +1 -0
- package/dist/cjs/files.d.ts +67 -0
- package/dist/cjs/files.js +119 -0
- package/dist/cjs/files.js.map +1 -0
- package/dist/cjs/framework-scope.d.ts +1 -0
- package/dist/cjs/framework-scope.js +14 -0
- package/dist/cjs/framework-scope.js.map +1 -0
- package/dist/cjs/index.d.ts +14 -0
- package/dist/cjs/index.js +33 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/mq/broker.d.ts +65 -0
- package/dist/cjs/mq/broker.js +115 -0
- package/dist/cjs/mq/broker.js.map +1 -0
- package/dist/cjs/mq/client.d.ts +37 -0
- package/dist/cjs/mq/client.js +321 -0
- package/dist/cjs/mq/client.js.map +1 -0
- package/dist/cjs/mq/index.d.ts +5 -0
- package/dist/cjs/mq/index.js +11 -0
- package/dist/cjs/mq/index.js.map +1 -0
- package/dist/cjs/mq/types.d.ts +48 -0
- package/dist/cjs/mq/types.js +3 -0
- package/dist/cjs/mq/types.js.map +1 -0
- package/dist/cjs/openapi/api.generated.d.ts +820 -0
- package/dist/cjs/openapi/api.generated.js +607 -0
- package/dist/cjs/openapi/api.generated.js.map +1 -0
- package/dist/cjs/openapi/client.d.ts +56 -0
- package/dist/cjs/openapi/client.js +219 -0
- package/dist/cjs/openapi/client.js.map +1 -0
- package/dist/cjs/openapi/codecs.generated.d.ts +2234 -0
- package/dist/cjs/openapi/codecs.generated.js +6 -0
- package/dist/cjs/openapi/codecs.generated.js.map +1 -0
- package/dist/cjs/openapi/index.d.ts +5 -0
- package/dist/cjs/openapi/index.js +12 -0
- package/dist/cjs/openapi/index.js.map +1 -0
- package/dist/cjs/openapi/types.generated.d.ts +3227 -0
- package/dist/cjs/openapi/types.generated.js +3 -0
- package/dist/cjs/openapi/types.generated.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/react.d.ts +9 -0
- package/dist/cjs/react.js +16 -0
- package/dist/cjs/react.js.map +1 -0
- package/dist/cjs/runtime-context.d.ts +9 -0
- package/dist/cjs/runtime-context.js +25 -0
- package/dist/cjs/runtime-context.js.map +1 -0
- package/dist/cjs/runtime-env.d.ts +1 -0
- package/dist/cjs/runtime-env.js +18 -0
- package/dist/cjs/runtime-env.js.map +1 -0
- package/dist/cjs/source.d.ts +39 -0
- package/dist/cjs/source.js +29 -0
- package/dist/cjs/source.js.map +1 -0
- package/dist/cjs/stream.d.ts +40 -0
- package/dist/cjs/stream.js +33 -0
- package/dist/cjs/stream.js.map +1 -0
- package/dist/cjs/uns-v2/client.d.ts +54 -0
- package/dist/cjs/uns-v2/client.js +177 -0
- package/dist/cjs/uns-v2/client.js.map +1 -0
- package/dist/cjs/uns-v2/codec.d.ts +11 -0
- package/dist/cjs/uns-v2/codec.js +290 -0
- package/dist/cjs/uns-v2/codec.js.map +1 -0
- package/dist/cjs/uns-v2/data-client.d.ts +10 -0
- package/dist/cjs/uns-v2/data-client.js +18 -0
- package/dist/cjs/uns-v2/data-client.js.map +1 -0
- package/dist/cjs/uns-v2/generated.d.ts +15551 -0
- package/dist/cjs/uns-v2/generated.js +139 -0
- package/dist/cjs/uns-v2/generated.js.map +1 -0
- package/dist/cjs/uns-v2/index.d.ts +5 -0
- package/dist/cjs/uns-v2/index.js +24 -0
- package/dist/cjs/uns-v2/index.js.map +1 -0
- package/dist/cjs/vue.d.ts +9 -0
- package/dist/cjs/vue.js +39 -0
- package/dist/cjs/vue.js.map +1 -0
- package/dist/esm/app-identity-contract.generated.d.ts +22 -0
- package/dist/esm/app-identity-contract.generated.js +48 -0
- package/dist/esm/app-identity-contract.generated.js.map +1 -0
- package/dist/esm/capabilities.d.ts +54 -0
- package/dist/esm/capabilities.js +129 -0
- package/dist/esm/capabilities.js.map +1 -0
- package/dist/esm/client.d.ts +604 -0
- package/dist/esm/client.js +32 -0
- package/dist/esm/client.js.map +1 -0
- package/dist/esm/cloud-server.d.ts +31 -0
- package/dist/esm/cloud-server.js +109 -0
- package/dist/esm/cloud-server.js.map +1 -0
- package/dist/esm/cloud-transport.d.ts +46 -0
- package/dist/esm/cloud-transport.js +187 -0
- package/dist/esm/cloud-transport.js.map +1 -0
- package/dist/esm/controls.d.ts +93 -0
- package/dist/esm/controls.js +163 -0
- package/dist/esm/controls.js.map +1 -0
- package/dist/esm/data/bootstrap.d.ts +66 -0
- package/dist/esm/data/bootstrap.js +277 -0
- package/dist/esm/data/bootstrap.js.map +1 -0
- package/dist/esm/data/call.d.ts +16 -0
- package/dist/esm/data/call.js +47 -0
- package/dist/esm/data/call.js.map +1 -0
- package/dist/esm/data/client.d.ts +92 -0
- package/dist/esm/data/client.js +99 -0
- package/dist/esm/data/client.js.map +1 -0
- package/dist/esm/data/collection.d.ts +238 -0
- package/dist/esm/data/collection.js +434 -0
- package/dist/esm/data/collection.js.map +1 -0
- package/dist/esm/data/context.d.ts +10 -0
- package/dist/esm/data/context.js +34 -0
- package/dist/esm/data/context.js.map +1 -0
- package/dist/esm/data/declarations.d.ts +11 -0
- package/dist/esm/data/declarations.js +10 -0
- package/dist/esm/data/declarations.js.map +1 -0
- package/dist/esm/data/define.d.ts +69 -0
- package/dist/esm/data/define.js +27 -0
- package/dist/esm/data/define.js.map +1 -0
- package/dist/esm/data/error.d.ts +60 -0
- package/dist/esm/data/error.js +178 -0
- package/dist/esm/data/error.js.map +1 -0
- package/dist/esm/data/index.d.ts +21 -0
- package/dist/esm/data/index.js +18 -0
- package/dist/esm/data/index.js.map +1 -0
- package/dist/esm/data/schema.d.ts +82 -0
- package/dist/esm/data/schema.js +596 -0
- package/dist/esm/data/schema.js.map +1 -0
- package/dist/esm/data/sort.d.ts +8 -0
- package/dist/esm/data/sort.js +18 -0
- package/dist/esm/data/sort.js.map +1 -0
- package/dist/esm/files.d.ts +67 -0
- package/dist/esm/files.js +113 -0
- package/dist/esm/files.js.map +1 -0
- package/dist/esm/framework-scope.d.ts +1 -0
- package/dist/esm/framework-scope.js +11 -0
- package/dist/esm/framework-scope.js.map +1 -0
- package/dist/esm/index.d.ts +14 -0
- package/dist/esm/index.js +10 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/mq/broker.d.ts +65 -0
- package/dist/esm/mq/broker.js +109 -0
- package/dist/esm/mq/broker.js.map +1 -0
- package/dist/esm/mq/client.d.ts +37 -0
- package/dist/esm/mq/client.js +314 -0
- package/dist/esm/mq/client.js.map +1 -0
- package/dist/esm/mq/index.d.ts +5 -0
- package/dist/esm/mq/index.js +3 -0
- package/dist/esm/mq/index.js.map +1 -0
- package/dist/esm/mq/types.d.ts +48 -0
- package/dist/esm/mq/types.js +2 -0
- package/dist/esm/mq/types.js.map +1 -0
- package/dist/esm/openapi/api.generated.d.ts +820 -0
- package/dist/esm/openapi/api.generated.js +603 -0
- package/dist/esm/openapi/api.generated.js.map +1 -0
- package/dist/esm/openapi/client.d.ts +56 -0
- package/dist/esm/openapi/client.js +210 -0
- package/dist/esm/openapi/client.js.map +1 -0
- package/dist/esm/openapi/codecs.generated.d.ts +2234 -0
- package/dist/esm/openapi/codecs.generated.js +3 -0
- package/dist/esm/openapi/codecs.generated.js.map +1 -0
- package/dist/esm/openapi/index.d.ts +5 -0
- package/dist/esm/openapi/index.js +3 -0
- package/dist/esm/openapi/index.js.map +1 -0
- package/dist/esm/openapi/types.generated.d.ts +3227 -0
- package/dist/esm/openapi/types.generated.js +2 -0
- package/dist/esm/openapi/types.generated.js.map +1 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/react.d.ts +9 -0
- package/dist/esm/react.js +12 -0
- package/dist/esm/react.js.map +1 -0
- package/dist/esm/runtime-context.d.ts +9 -0
- package/dist/esm/runtime-context.js +22 -0
- package/dist/esm/runtime-context.js.map +1 -0
- package/dist/esm/runtime-env.d.ts +1 -0
- package/dist/esm/runtime-env.js +15 -0
- package/dist/esm/runtime-env.js.map +1 -0
- package/dist/esm/source.d.ts +39 -0
- package/dist/esm/source.js +25 -0
- package/dist/esm/source.js.map +1 -0
- package/dist/esm/stream.d.ts +40 -0
- package/dist/esm/stream.js +29 -0
- package/dist/esm/stream.js.map +1 -0
- package/dist/esm/uns-v2/client.d.ts +54 -0
- package/dist/esm/uns-v2/client.js +170 -0
- package/dist/esm/uns-v2/client.js.map +1 -0
- package/dist/esm/uns-v2/codec.d.ts +11 -0
- package/dist/esm/uns-v2/codec.js +284 -0
- package/dist/esm/uns-v2/codec.js.map +1 -0
- package/dist/esm/uns-v2/data-client.d.ts +10 -0
- package/dist/esm/uns-v2/data-client.js +14 -0
- package/dist/esm/uns-v2/data-client.js.map +1 -0
- package/dist/esm/uns-v2/generated.d.ts +15551 -0
- package/dist/esm/uns-v2/generated.js +136 -0
- package/dist/esm/uns-v2/generated.js.map +1 -0
- package/dist/esm/uns-v2/index.d.ts +5 -0
- package/dist/esm/uns-v2/index.js +5 -0
- package/dist/esm/uns-v2/index.js.map +1 -0
- package/dist/esm/vue.d.ts +9 -0
- package/dist/esm/vue.js +36 -0
- package/dist/esm/vue.js.map +1 -0
- package/docs/acceptance-2026-10-22.md +88 -0
- package/package.json +242 -4
- package/skills/tier0-sdk-v2/SKILL.md +66 -0
- package/skills/tier0-sdk-v2/references/integration.md +72 -0
- package/skills/tier0-sdk-v2-agent/SKILL.md +73 -0
- package/skills/tier0-sdk-v2-agent/references/results.md +45 -0
- package/skills/tier0-sdk-v2-controls/SKILL.md +63 -0
- package/skills/tier0-sdk-v2-data/SKILL.md +57 -0
- package/skills/tier0-sdk-v2-data/references/consistency.md +70 -0
- package/skills/tier0-sdk-v2-data/references/errors.md +69 -0
- package/skills/tier0-sdk-v2-data/references/modeling.md +77 -0
- package/skills/tier0-sdk-v2-data/references/read-write.md +72 -0
- package/skills/tier0-sdk-v2-data/references/schema-evolution.md +36 -0
- package/skills/tier0-sdk-v2-files/SKILL.md +85 -0
- package/skills/tier0-sdk-v2-files/references/delete.md +22 -0
- package/skills/tier0-sdk-v2-files/references/download.md +49 -0
- package/skills/tier0-sdk-v2-files/references/multipart.md +42 -0
- package/skills/tier0-sdk-v2-files/references/upload.md +27 -0
- package/skills/tier0-sdk-v2-files/references/url.md +22 -0
- package/skills/tier0-sdk-v2-members/SKILL.md +56 -0
- package/skills/tier0-sdk-v2-members/references/app-members.md +41 -0
- package/skills/tier0-sdk-v2-members/references/platform-members.md +56 -0
- package/skills/tier0-sdk-v2-mq/SKILL.md +78 -0
- package/skills/tier0-sdk-v2-mq/references/quickstart.md +69 -0
- package/skills/tier0-sdk-v2-notifications/SKILL.md +42 -0
- package/skills/tier0-sdk-v2-notifications/references/get.md +34 -0
- package/skills/tier0-sdk-v2-notifications/references/send.md +72 -0
- package/skills/tier0-sdk-v2-source/SKILL.md +38 -0
- package/skills/tier0-sdk-v2-source/references/changes.md +40 -0
- package/skills/tier0-sdk-v2-source/references/create.md +63 -0
- package/skills/tier0-sdk-v2-source/references/delete.md +47 -0
- package/skills/tier0-sdk-v2-source/references/protocols.md +36 -0
- package/skills/tier0-sdk-v2-source/references/read.md +34 -0
- package/skills/tier0-sdk-v2-source/references/state.md +37 -0
- package/skills/tier0-sdk-v2-source/references/update.md +61 -0
- package/skills/tier0-sdk-v2-stream/SKILL.md +43 -0
- package/skills/tier0-sdk-v2-stream/references/create.md +55 -0
- package/skills/tier0-sdk-v2-stream/references/delete.md +35 -0
- package/skills/tier0-sdk-v2-stream/references/deploy.md +63 -0
- package/skills/tier0-sdk-v2-stream/references/lifecycle.md +25 -0
- package/skills/tier0-sdk-v2-stream/references/read.md +35 -0
- package/skills/tier0-sdk-v2-stream/references/update.md +35 -0
- package/skills/tier0-sdk-v2-system/SKILL.md +64 -0
- package/skills/tier0-sdk-v2-system/references/info.md +23 -0
- package/skills/tier0-sdk-v2-system/references/whoami.md +21 -0
- package/skills/tier0-sdk-v2-uns/SKILL.md +53 -0
- package/skills/tier0-sdk-v2-uns/references/create.md +85 -0
- package/skills/tier0-sdk-v2-uns/references/data-workflows.md +73 -0
- package/skills/tier0-sdk-v2-uns/references/delete.md +69 -0
- package/skills/tier0-sdk-v2-uns/references/namespace.md +75 -0
- package/skills/tier0-sdk-v2-uns/references/read.md +32 -0
- package/skills/tier0-sdk-v2-uns/references/restore.md +41 -0
- package/skills/tier0-sdk-v2-uns/references/update.md +86 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# 下载与应用附件保存
|
|
2
|
+
|
|
3
|
+
v2 方法:`files.download({filePath,…}, {signal}?) → Promise<Response>`。与 v1 不同,返回值本身就是 Response,不再取 result.response。
|
|
4
|
+
|
|
5
|
+
v2 先请求文件 URL,再匿名读取对象存储;不沿用带 Key 的重定向下载链。平台认证信息不能转发给存储。
|
|
6
|
+
|
|
7
|
+
## 同源服务端下载
|
|
8
|
+
|
|
9
|
+
应用路由先校验登录和业务权限,再按业务记录 ID 从数据库取可信 filePath。下例只负责已经授权后的转发,不可直接暴露为接受任意 filePath 的公共接口。
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { createClient, type Tier0Client } from '@tier0/sdk-v2';
|
|
13
|
+
|
|
14
|
+
export async function streamAuthorizedAttachment(
|
|
15
|
+
filePath: string, client: Tier0Client = createClient(), signal?: AbortSignal,
|
|
16
|
+
) {
|
|
17
|
+
const upstream = await client.files.download({ filePath }, { signal });
|
|
18
|
+
return new Response(upstream.body, {
|
|
19
|
+
headers: {
|
|
20
|
+
'Content-Type': upstream.headers.get('content-type') ?? 'application/octet-stream',
|
|
21
|
+
'Content-Disposition': 'attachment',
|
|
22
|
+
'Cache-Control': 'private, no-store',
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
大文件直接转发 response.body,避免服务端先完整 buffer。返回响应头后的流消费/取消由应用管理;SDK 请求超时不覆盖后续所有流读取。
|
|
29
|
+
|
|
30
|
+
## 浏览器保存
|
|
31
|
+
|
|
32
|
+
浏览器访问自己的业务下载路由,不持有平台 Key。下面的路由是应用需要实现的接口。
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
export async function saveAttachment(recordId: string, fileName: string) {
|
|
36
|
+
const response = await fetch('/api/attachments/' + encodeURIComponent(recordId) + '/download');
|
|
37
|
+
if (!response.ok) throw new Error('附件下载失败');
|
|
38
|
+
const url = URL.createObjectURL(await response.blob());
|
|
39
|
+
const anchor = document.createElement('a');
|
|
40
|
+
anchor.href = url;
|
|
41
|
+
anchor.download = fileName;
|
|
42
|
+
document.body.appendChild(anchor);
|
|
43
|
+
anchor.click();
|
|
44
|
+
anchor.remove();
|
|
45
|
+
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Blob 方案会占用浏览器内存;超大文件按应用支持的流式保存能力设计。不能将权限失败当空文件下载,也不能将文件不存在当成网络异常重试。
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# 分片上传、进度和中断
|
|
2
|
+
|
|
3
|
+
| 调用 | 行为 |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| files.upload(file) | 大于 100 MiB 自动分片 |
|
|
6
|
+
| files.uploadMultipart(file, options) | 非空文件显式分片,可报告进度 |
|
|
7
|
+
| files.multipart.init/partUrls/complete/abort | 原子接口,普通业务上传使用上面两个助手 |
|
|
8
|
+
|
|
9
|
+
| options 字段 | 类型 | 说明 |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| partSize | number | 默认 8 MiB,至少 5 MiB;实际采用服务器返回值 |
|
|
12
|
+
| onProgress | function | 收到 uploadedBytes、totalBytes |
|
|
13
|
+
| signal | AbortSignal | 取消等待/传输 |
|
|
14
|
+
| 其余上传元数据 | string | 与 upload 相同 |
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { createClient, MultipartUploadError, type Tier0Client } from '@tier0/sdk-v2';
|
|
18
|
+
|
|
19
|
+
export async function uploadWithProgress(
|
|
20
|
+
file: File, report: (uploadedBytes: number, totalBytes: number) => void,
|
|
21
|
+
client: Tier0Client = createClient(), signal?: AbortSignal,
|
|
22
|
+
) {
|
|
23
|
+
try {
|
|
24
|
+
const saved = await client.files.uploadMultipart(file, {
|
|
25
|
+
partSize: 8 * 1024 * 1024, signal,
|
|
26
|
+
onProgress: progress => report(progress.uploadedBytes, progress.totalBytes),
|
|
27
|
+
});
|
|
28
|
+
return { state: 'completed' as const, filePath: saved.filePath };
|
|
29
|
+
} catch (error) {
|
|
30
|
+
if (error instanceof MultipartUploadError) {
|
|
31
|
+
return { state: 'interrupted' as const, session: error.session };
|
|
32
|
+
}
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
成功后才持久化 filePath。中断 session 包含 fileKey、uploadId、parts(partNumber/etag)、completionAttempted;session 不是完成回执,不公开到日志或无关用户。
|
|
39
|
+
|
|
40
|
+
v2 首版顺序上传,无自动重试、并发参数或一键续传。不要复制 v1 的 resumeMultipartUpload、multipartSession 等不存在的助手。部分 PUT 可能成功但回执丢失,不能仅凭本地 parts 判断远端完整状态。跨域存储需要暴露 ETag 响应头。
|
|
41
|
+
|
|
42
|
+
completionAttempted=true 表示 complete 可能已提交,先对账最终文件,不能直接 abort 或重复 complete。明确放弃未完成会话时才调用 abort;初始化请求本身结果未知且无 session 时,保存错误信息交由平台对账,不猜 uploadId。
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# 上传并保存文件引用
|
|
2
|
+
|
|
3
|
+
从 v1 的 uploadFile 流程迁移,v2 使用 `client.files.upload(file, options)`;独立入口为 `createFilesClient`(@tier0/sdk-v2/files)。Node.js 20.19+。浏览器只调用应用自己的上传接口,平台 Key 留在服务端。
|
|
4
|
+
|
|
5
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
6
|
+
| --- | --- | --- | --- |
|
|
7
|
+
| file | File | 是 | 名称、MIME、大小从 File 读取;不是磁盘路径字符串 |
|
|
8
|
+
| options.business / useBy | string | 否 | 平台定义的业务归属,不猜枚举 |
|
|
9
|
+
| options.visibility | string | 否 | 根据业务授权选择公开或私有,不自动公开 |
|
|
10
|
+
| options.sessionId / appInstanceId | string | 否 | 真实运行上下文,不用 projectId 替代 |
|
|
11
|
+
| options.signal | AbortSignal | 否 | 取消等待/上传,不保证服务端回滚 |
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { createClient, type Tier0Client } from '@tier0/sdk-v2';
|
|
15
|
+
|
|
16
|
+
export async function uploadAttachment(file: File, client: Tier0Client = createClient()) {
|
|
17
|
+
const saved = await client.files.upload(file);
|
|
18
|
+
if (!saved.filePath) throw new Error('上传回执缺少 filePath');
|
|
19
|
+
return { filePath: saved.filePath };
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
上传流程:平台 prepareUpload 返回 postUrl/postFields/filePath → SDK 按签名字段在前、file 在后构建表单 → 匿名上传存储。不要给对象存储附带平台 Authorization。上传失败时不能将准备阶段返回的 filePath 当成功附件保存。
|
|
24
|
+
|
|
25
|
+
业务表持久化 filePath;临时签名 URL 不作为长期引用。文件写入和业务记录写入不是原子事务,业务保存失败时记录待清理对象,只在任务授权范围内清理。
|
|
26
|
+
|
|
27
|
+
文件大于 100 MiB 时自动分片;阈值不是单文件产品上限,套餐配额/文件类型限制由后端决定。需要进度时显式用 [uploadMultipart](multipart.md)。v2 没有 v1 的 options.multipart/concurrency/retry/resume 控件,不照抄旧参数。
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# 获取文件访问地址
|
|
2
|
+
|
|
3
|
+
方法:`files.url({filePath,expiredSec?,responseContentDisposition?}, {signal}?)`;GET /openapi/v1/assets/files/url。
|
|
4
|
+
|
|
5
|
+
| 字段 | 类型 | 必填 | 说明 |
|
|
6
|
+
| --- | --- | --- | --- |
|
|
7
|
+
| filePath | string | 是 | 上传返回的持久化引用 |
|
|
8
|
+
| expiredSec | number / bigint | 否 | 请求有效期(秒),由后端决定实际期限 |
|
|
9
|
+
| responseContentDisposition | string | 否 | 底层响应头提示,不代替浏览器保存动作 |
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { createClient, type Tier0Client } from '@tier0/sdk-v2';
|
|
13
|
+
|
|
14
|
+
export async function getPreview(filePath: string, client: Tier0Client = createClient()) {
|
|
15
|
+
const result = await client.files.url({ filePath, expiredSec: 300 });
|
|
16
|
+
return { url: result.fileUrl, expiresAt: result.expiresAt };
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
返回已解包的 fileUrl、可选 expiresAt。私有文件是短期签名 URL;公开文件可能是公开地址且无过期字段。不能对缺失 expiresAt 猜时间,也不能用普通 Number 强转大整数。
|
|
21
|
+
|
|
22
|
+
URL 适合图片预览、媒体引用或明确要求的临时分享。业务表仍保存 filePath。附件“下载”用[下载流程](download.md),避免直接导航签名 URL 进入 PDF 预览器。分享链接不改变文件权限,也不允许把 API Key 拼进 URL。
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tier0-sdk-v2-members
|
|
3
|
+
description: 使用 @tier0/sdk-v2 查询 Tier0 Workspace 成员、搜索用户和解析通知收件人,并按 appId 查询能使用某个 App 的成员;区分 Workspace 身份与 App 访问授权。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Workspace 成员查询
|
|
7
|
+
|
|
8
|
+
参数表、成员选择和有界增量分页见 [Workspace 成员](references/platform-members.md)。v1 的项目成员接口未迁入 v2,不能用 Workspace 列表代替 App 授权名单。
|
|
9
|
+
|
|
10
|
+
公开入口有两个:`client.members.listWorkspace` 只用于 Workspace 级的成员选择;需要能使用某个 App 的成员时用 `client.members.listApp`。没有 project 成员迁移别名,也不在客户端推断 App 成员。使用服务端 API Key;平台授权决定可查询范围。
|
|
11
|
+
|
|
12
|
+
## 搜索与展示候选人
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { createClient } from '@tier0/sdk-v2';
|
|
16
|
+
|
|
17
|
+
export async function searchMembers(keyword: string, page = 1) {
|
|
18
|
+
const result = await createClient().members.listWorkspace({
|
|
19
|
+
keyword, statuses: ['active'], page, size: 20,
|
|
20
|
+
});
|
|
21
|
+
return {
|
|
22
|
+
list: result.list.map(member => ({
|
|
23
|
+
userId: member.userId,
|
|
24
|
+
label: member.nickName || member.userName || member.email || '未命名成员',
|
|
25
|
+
email: member.email,
|
|
26
|
+
status: member.status,
|
|
27
|
+
roles: member.roles,
|
|
28
|
+
})),
|
|
29
|
+
total: result.total, page: result.page, size: result.size,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
返回值已解包,不读 result.data。userId 始终作为字符串传给通知或业务表;不要转换成 Number。名称不唯一,用户意图不能唯一定位时返回候选人,不能静默选第一个。
|
|
35
|
+
|
|
36
|
+
## 筛选与分页
|
|
37
|
+
|
|
38
|
+
请求可带 keyword、page、size、roleKey、roles、statuses、updatedAtStart、updatedAtEnd。角色与状态取自实际平台定义,不造枚举或假定固定管理员角色。时间字段为字符串,按服务端认可格式使用;不要将 number 毫秒直接填进去。
|
|
39
|
+
|
|
40
|
+
返回 list/total/page/size;total/page/size 可能为 number 或 bigint。批量同步按服务端返回分页继续,并限定本次任务的范围,不把一页或受限视图宣称为完整目录。按更新时间增量同步时保存已确认的水位并处理边界重复。
|
|
41
|
+
|
|
42
|
+
## 用于通知
|
|
43
|
+
|
|
44
|
+
1. 确定查询的是当前 Workspace,按姓名/邮箱等输入取得候选人。
|
|
45
|
+
2. 使用选定的 userId 调用通知,展示名只用于 UI。
|
|
46
|
+
3. 需要“这个 App 的所有用户”时用 `listApp` 取访问名单,不能直接遍历 Workspace 成员群发。
|
|
47
|
+
|
|
48
|
+
[通知 Skill](../tier0-sdk-v2-notifications/SKILL.md)说明渠道与幂等。成员查询不会替你建立 App 访问权限,也不能证明收件人可以打开通知里的业务页面。
|
|
49
|
+
|
|
50
|
+
## App 可用成员
|
|
51
|
+
|
|
52
|
+
能使用某个 App 的成员用 `members.listApp`,参数、示例与错误处理见 [App 可用成员](references/app-members.md)。`appId` 必须显式传入,SDK 不隐式读取 `APP_ID`;查询当前 App 时传 `getCurrentAppId()`。接口只接受 API Key,登录会话不能调用;绑定 App 的凭证只能查自己的 appId,查其他 App 返回 403。3003016 / 3003017 都是 HTTP 503,表示访问名单暂时无法给出,不是空名单:按 `ApiError.code` 分支,不匹配 message 文本;3003016 需要重新部署 App,3003017 可稍后重试。
|
|
53
|
+
|
|
54
|
+
## 当前边界
|
|
55
|
+
|
|
56
|
+
whoami 是 Key 身份查询,不是成员列表;它也不是浏览器当前员工身份。401/403 应核对 Workspace 和权限,不自动切换到更高权限凭证。只回传界面实际需要的成员字段,避免把整个目录和角色数据泄露给无关调用方。
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# App 可用成员
|
|
2
|
+
|
|
3
|
+
方法:`members.listApp(body)`;POST /openapi/v1/apps/access-members/list。返回能使用该 App 线上版本的有效成员。`appId` 必填且由调用方显式传入,SDK 不隐式读取 `APP_ID`;查询当前 App 时传 `getCurrentAppId()`。接口只接受 API Key,登录会话不能调用;绑定 App 的凭证只能查自己的 appId,查其他 App 返回 403。
|
|
4
|
+
|
|
5
|
+
| 字段 | 类型 | 说明 |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| appId | string | 必填,agent-platform App UUID |
|
|
8
|
+
| roleKeys | string[] | 任一命中即可,大小写不敏感 |
|
|
9
|
+
| grantedAtStart / grantedAtEnd | number / bigint | 授权时间,Unix 毫秒,闭区间,精度为秒 |
|
|
10
|
+
| page / size | number / bigint | 分页 |
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { ApiError, createClient, getCurrentAppId, type Tier0Client } from '@tier0/sdk-v2';
|
|
14
|
+
|
|
15
|
+
/** 线上版本的角色事实未落库:重试无效,需要重新部署 App。 */
|
|
16
|
+
const APP_ACCESS_LIST_UNDETERMINED = 3003016;
|
|
17
|
+
/** 成员有效状态暂时取不到:稍后重试。 */
|
|
18
|
+
const APP_ACCESS_LIST_UNAVAILABLE = 3003017;
|
|
19
|
+
|
|
20
|
+
export async function appMembers(page = 1, client: Tier0Client = createClient(), appId = getCurrentAppId()) {
|
|
21
|
+
try {
|
|
22
|
+
const result = await client.members.listApp({ appId, page, size: 20 });
|
|
23
|
+
return {
|
|
24
|
+
list: result.list.map(member => ({
|
|
25
|
+
userId: member.userId,
|
|
26
|
+
label: member.userName || member.email || '未命名成员',
|
|
27
|
+
roles: member.roles.map(role => role.roleName),
|
|
28
|
+
})),
|
|
29
|
+
total: result.total, page: result.page, size: result.size,
|
|
30
|
+
};
|
|
31
|
+
} catch (error) {
|
|
32
|
+
if (error instanceof ApiError && error.code === APP_ACCESS_LIST_UNDETERMINED) throw new Error('App 访问名单无法确定,请重新部署 App');
|
|
33
|
+
if (error instanceof ApiError && error.code === APP_ACCESS_LIST_UNAVAILABLE) throw new Error('App 访问名单暂不可用,请稍后重试');
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
返回已解包的 appId、published(1 已发布 / 2 未发布)、allActiveMembers(1 所有有效成员都可使用 / 2 按角色授权)、roles、list、total、page、size。成员的 roles[].sources 区分直接授予(direct)和经成员组授予(group)。userId 保持字符串。
|
|
40
|
+
|
|
41
|
+
3003016 / 3003017 都是 HTTP 503:访问名单暂时无法给出,不是空名单,也不代表所有人可用。按 `ApiError.code` 分支,不匹配 message 文本。3003016 是线上版本角色事实未落库,重试无效,需要重新部署 App;3003017 是成员有效状态暂时取不到,可稍后重试。无权限(403)时报告配置问题,不改用 Workspace 成员列表代替。
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Workspace 成员查询与选择
|
|
2
|
+
|
|
3
|
+
方法:`members.listWorkspace(body)`;POST /openapi/v1/platform/getMembers。查询当前 Key 所属 Workspace;云端需要 uns:read。通知读写权限不自动包含成员查询。
|
|
4
|
+
|
|
5
|
+
| 字段 | 类型 | 说明 |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| keyword | string | 姓名、昵称、邮箱关键词 |
|
|
8
|
+
| roleKey / roles | string / string[] | 单个或多个角色筛选,组合为 OR |
|
|
9
|
+
| statuses | string[] | active / disabled;选择通知收件人通常 active |
|
|
10
|
+
| updatedAtStart / updatedAtEnd | string | RFC3339,包含边界 |
|
|
11
|
+
| page | number / bigint | 从 1 开始,默认 1 |
|
|
12
|
+
| size | number / bigint | 默认 20,最大 100 |
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { createClient, type Tier0Client } from '@tier0/sdk-v2';
|
|
16
|
+
|
|
17
|
+
export async function memberOptions(keyword: string, page = 1, client: Tier0Client = createClient()) {
|
|
18
|
+
const result = await client.members.listWorkspace({ keyword, statuses: ['active'], page, size: 20 });
|
|
19
|
+
return {
|
|
20
|
+
options: result.list.map(member => ({
|
|
21
|
+
value: member.userId,
|
|
22
|
+
label: member.nickName || member.userName || member.email || '未命名成员',
|
|
23
|
+
email: member.email, roles: member.roles.map(role => role.roleName || role.roleKey),
|
|
24
|
+
})),
|
|
25
|
+
total: result.total, page: result.page, size: result.size,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
v2 返回已解包的 list/total/page/size,不读 result.data。userId、roleId 保持字符串;value 留作内部选项值,用户看到姓名/邮箱/角色,不让用户填写 ID。不确定匹配时展示候选人,不默认选第一项。
|
|
31
|
+
|
|
32
|
+
内置公开角色 owner/builder/operator;筛选兼容性以服务端为准,匹配后仍返回成员全部角色。不要根据某个角色名自行推断 App 访问授权。
|
|
33
|
+
|
|
34
|
+
## 有界分页同步
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { createClient, type Tier0Client } from '@tier0/sdk-v2';
|
|
38
|
+
|
|
39
|
+
export async function* scanMembers(
|
|
40
|
+
updatedAtStart: string, updatedAtEnd: string, maxPages = 20,
|
|
41
|
+
client: Tier0Client = createClient(),
|
|
42
|
+
) {
|
|
43
|
+
if (!Number.isSafeInteger(maxPages) || maxPages < 1) throw new Error('无效页数上限');
|
|
44
|
+
for (let page = 1; page <= maxPages; page++) {
|
|
45
|
+
const result = await client.members.listWorkspace({ page, size: 100, updatedAtStart, updatedAtEnd });
|
|
46
|
+
yield result.list;
|
|
47
|
+
if (BigInt(result.page) * BigInt(result.size) >= BigInt(result.total)) return;
|
|
48
|
+
if (!result.list.length) throw new Error('分页结果与总数不一致');
|
|
49
|
+
}
|
|
50
|
+
throw new Error('达到同步页数上限,结果未完整');
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
增量时间包含边界,按 userId/updatedAt 去重,成功完成后再保存水位;跨页没有固定快照保证。只向界面返回实际需要字段。
|
|
55
|
+
|
|
56
|
+
v1 project-members 不迁移:v2 没有 getCurrentProjectId 或项目成员接口。Workspace 列表只用于 Workspace 级选择,不能冒充某 App 的可访问用户;需要 App 可用成员时用 `members.listApp`(见 [App 可用成员](app-members.md))。无权限时报告配置问题,不以用户手填 ID 绕过。
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tier0-sdk-v2-mq
|
|
3
|
+
description: 使用 @tier0/sdk-v2/mq 连接 Tier0 MQTT WebSocket Broker,管理凭证、订阅和发布消息、清理连接及处理背压;不替代 Source 配置或 Stream 执行。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# MQTT 实时消息
|
|
7
|
+
|
|
8
|
+
连接配置、完整订阅生命周期、通配符、发布及鉴权排查见 [Quickstart](references/quickstart.md)。v1 的旧 UNS 路径/字段规则不适用于新架构;实际 MQTT 映射需按目标环境确认。
|
|
9
|
+
|
|
10
|
+
`Tier0MQClient` 从独立子入口导入。它面向消息客户端;配置平台采集连接用 Source,运行处理逻辑用 Stream。HTTP SDK 的“不自动重试”不等于 MQTT 禁用重连或 QoS 重投递。
|
|
11
|
+
|
|
12
|
+
## 使用已有凭证订阅
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { Tier0MQClient, toWebSocketUrl, type TopicHandler } from '@tier0/sdk-v2/mq';
|
|
16
|
+
|
|
17
|
+
export async function watchTopic(
|
|
18
|
+
broker: string,
|
|
19
|
+
credentials: { clientID: string; username: string; password: string },
|
|
20
|
+
topic: string,
|
|
21
|
+
handle: TopicHandler,
|
|
22
|
+
) {
|
|
23
|
+
const mq = new Tier0MQClient({
|
|
24
|
+
host: toWebSocketUrl(broker),
|
|
25
|
+
clientId: credentials.clientID,
|
|
26
|
+
username: credentials.username,
|
|
27
|
+
password: credentials.password,
|
|
28
|
+
reconnectPeriod: 1000,
|
|
29
|
+
});
|
|
30
|
+
mq.on('error', error => console.error(error.name));
|
|
31
|
+
await mq.connect();
|
|
32
|
+
mq.subscribe(topic, handle);
|
|
33
|
+
return () => {
|
|
34
|
+
mq.unsubscribe(topic, handle);
|
|
35
|
+
mq.disconnect();
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
broker 可来自 `client.system.info().mqttBroker`,先检查字段存在。保留已有 ws/wss URL 的端口和路径;HTTP 页面与 TLS/混合内容策略按实际部署配置,不能简单把 TCP 端口当 WebSocket 端口。
|
|
41
|
+
|
|
42
|
+
subscribe 返回 void,错误通过 error 事件报告;回调参数是 topic 和字符串 payload。按业务格式解析 JSON,非法消息应隔离处理,避免一条坏消息打断消费。多个组件共享连接时,只清理自己的 handler,不直接 disconnect 其他订阅。
|
|
43
|
+
|
|
44
|
+
## 凭证生命周期
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { createClient } from '@tier0/sdk-v2';
|
|
48
|
+
|
|
49
|
+
export async function provisionMqttCredential(name: string) {
|
|
50
|
+
return createClient().mqttAuth.create({ name });
|
|
51
|
+
}
|
|
52
|
+
export async function revokeMqttCredential(id: number | bigint) {
|
|
53
|
+
return createClient().mqttAuth.delete({ id });
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
仅在任务需要创建凭证时使用 create,普通订阅重用已有受控凭证。完整 username/password/clientID 只在创建时返回一次,按秘密管理,别写日志。SDK 配置的 clientId 小写 d,接口回执的 clientID 大写 ID。
|
|
58
|
+
|
|
59
|
+
也可从编码了 Workspace 的平台 Key 推导 MQTT 身份;无法解析的 Key 会拒绝连接,不回退到 Enterprise 身份。浏览器场景须使用针对当前用户/主题授权的凭证方案,不能为了省事下发长期 App Key。
|
|
60
|
+
|
|
61
|
+
## 发布与确认边界
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { Tier0MQClient } from '@tier0/sdk-v2/mq';
|
|
65
|
+
|
|
66
|
+
export async function publishMeasurement(mq: Tier0MQClient, topic: string, value: number) {
|
|
67
|
+
if (!Number.isFinite(value)) throw new Error('非法测量值');
|
|
68
|
+
await mq.publish(topic, { value, time: Date.now() }, { qos: 1, retain: false });
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
publish 目前在建立连接并调用 MQTT 库发布后返回,不等待业务执行确认,也不保证已收到 broker PUBACK。需要闭环时使用业务 ACK 主题/关联 ID 或 Control 契约,不能把 Promise resolve 当成设备执行完成。
|
|
73
|
+
|
|
74
|
+
订阅内部 QoS 为 1;发布默认 QoS 0、retain=false。业务消费者需考虑重复消息。对象 payload 经 JSON.stringify,不能直接包含 bigint;按协议约定编码精确值,不假设 MQTT 自动使用 UNS 的无损 codec。
|
|
75
|
+
|
|
76
|
+
## 清理与背压
|
|
77
|
+
|
|
78
|
+
组件卸载/任务结束时用原 handler 退订,连接归当前任务独占时再 disconnect。可配置 maxBackpressuredDeliveries;handler 连续同步返回严格 false 达到阈值后会移除订阅并触发 subscriptionDropped。异步 Promise 的 false 不等同同步背压信号。通过 isConnected/subscribedTopics 观察本地连接状态,不把它当作服务器完整订阅清单。
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# MQTT 连接、订阅与清理
|
|
2
|
+
|
|
3
|
+
入口:@tier0/sdk-v2/mq。使用 Tier0 平台 Broker 和已授权的真实主题;配置外部采集源转 Source,处理脚本转 Stream。
|
|
4
|
+
|
|
5
|
+
| 配置 | 类型 | 说明 |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| host | string | 完整 ws/wss URL,或通过 toWebSocketUrl 解析 |
|
|
8
|
+
| clientId / username / password | string | 显式凭据;不要暴露到日志 |
|
|
9
|
+
| reconnectPeriod | number | MQTT 重连间隔,与 HTTP 无重试不同 |
|
|
10
|
+
| connectTimeout / keepAlive | number | 连接超时/保活配置 |
|
|
11
|
+
| maxBackpressuredDeliveries | number | handler 连续同步返回 false 的退订阈值 |
|
|
12
|
+
|
|
13
|
+
Broker 可从 system.info().mqttBroker 获取。用 parseMqttBroker/toWebSocketUrl,不手工解析 scheme 或把 TCP 1883 当 WebSocket 端口;显式环境地址优先。平台 Key 可派生身份,但浏览器不得接收长期 App Key。
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { Tier0MQClient, toWebSocketUrl, type TopicHandler } from '@tier0/sdk-v2/mq';
|
|
17
|
+
|
|
18
|
+
export async function subscribeTelemetry(
|
|
19
|
+
broker: string, credentials: { clientID: string; username: string; password: string },
|
|
20
|
+
filter: string, onValue: (topic: string, value: unknown) => void,
|
|
21
|
+
onError: (error: Error) => void,
|
|
22
|
+
) {
|
|
23
|
+
const mq = new Tier0MQClient({
|
|
24
|
+
host: toWebSocketUrl(broker), clientId: credentials.clientID,
|
|
25
|
+
username: credentials.username, password: credentials.password,
|
|
26
|
+
reconnectPeriod: 1000, maxBackpressuredDeliveries: 10,
|
|
27
|
+
});
|
|
28
|
+
const handler: TopicHandler = (topic, payload) => {
|
|
29
|
+
let value: unknown;
|
|
30
|
+
try { value = JSON.parse(payload); }
|
|
31
|
+
catch { onError(new Error('消息不是有效 JSON')); return; }
|
|
32
|
+
onValue(topic, value);
|
|
33
|
+
};
|
|
34
|
+
mq.on('error', onError);
|
|
35
|
+
try {
|
|
36
|
+
await mq.connect();
|
|
37
|
+
mq.subscribe(filter, handler);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
mq.disconnect();
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
return () => { mq.unsubscribe(filter, handler); mq.disconnect(); };
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
回调是 (topic,payload),payload 为字符串。subscribe 返回 void,订阅失败通过 error 事件,不把函数返回当 broker SUBACK。此例独占连接;共享连接只退订自己的 handler,不关闭其他消费者。
|
|
47
|
+
|
|
48
|
+
+ 匹配一层,# 匹配多层,订阅范围必须属于任务和主题授权。不能从旧 v1 的 Metric/Action/State 路径、enableHistory 或字段规则推断新 UNS MQTT 映射;先核实目标平台的实际主题和消息契约。新 UNS Record 变更也有 recordChangesPoll,不能照搬“永不轮询”禁令。
|
|
49
|
+
|
|
50
|
+
## 发布
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { Tier0MQClient } from '@tier0/sdk-v2/mq';
|
|
54
|
+
|
|
55
|
+
export async function publishEvent(mq: Tier0MQClient, topic: string, eventId: string, value: number) {
|
|
56
|
+
if (!Number.isFinite(value)) throw new Error('测量值非法');
|
|
57
|
+
await mq.publish(topic, { eventId, value }, { qos: 1, retain: false });
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
payload 必须符合实际主题契约;对象用 JSON.stringify,不可包含 bigint。publish resolve 不证明已收 PUBACK,更不证明设备执行;业务闭环用关联 ID/ACK 或 Control。订阅 QoS 1,发布默认 QoS 0/retain=false,消费端考虑重复消息。
|
|
62
|
+
|
|
63
|
+
## 事件、背压和鉴权
|
|
64
|
+
|
|
65
|
+
connect/disconnect/error/message/subscriptionDropped 反映客户端状态。isConnected/subscribedTopics 不是服务器全量状态。连续同步返回严格 false 才触发背压计数,Promise<false> 不等价;消费者不应无限缓存。
|
|
66
|
+
|
|
67
|
+
创建凭据用 client.mqttAuth.create({name}),撤销用 delete({id}),检查 success。回执的 clientID 对应配置 clientId;完整密码只在创建时返回一次,受控保存。
|
|
68
|
+
|
|
69
|
+
Not authorized/CONNACK 5/重连循环时核对注入的 Key 类型、过期/撤销、授权和 Broker ws/wss 地址。v2 不回退 Enterprise 身份,也不套用旧包升级到 @tier0/sdk@latest 的建议;不因连接失败自动新建凭据。
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tier0-sdk-v2-notifications
|
|
3
|
+
description: 使用 @tier0/sdk-v2 发送和查询 Tier0 站内通知、Web/Desktop 提醒及 Mobile 推送,解析收件人,处理发送者、渠道、业务幂等与投递状态。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 通知
|
|
7
|
+
|
|
8
|
+
服务端使用 `createClient()` 读取 TIER0_API_HOST / TIER0_API_KEY,或显式传 apiHost/apiKey。发送必须符合用户已授权的收件人和内容范围,查询状态不自动补发。
|
|
9
|
+
|
|
10
|
+
| 任务 | 参考 | 方法 |
|
|
11
|
+
| --- | --- | --- |
|
|
12
|
+
| 收件人选择、完整发送、渠道、模式、错误 | [发送](references/send.md) | notifications.send |
|
|
13
|
+
| 查询回执、有界等待投递完成 | [查询](references/get.md) | notifications.get |
|
|
14
|
+
| 按姓名或邮箱查收件人 | [成员](../tier0-sdk-v2-members/SKILL.md) | members.listWorkspace |
|
|
15
|
+
|
|
16
|
+
## 核心约束
|
|
17
|
+
|
|
18
|
+
- type 固定 inbox;Swagger 的 string 不代表接受任意类型。
|
|
19
|
+
- 用人读姓名/邮箱选择收件人,ID 由成员数据解析并保持字符串,不让用户填写 ID。Workspace 成员不等于 App 可访问用户。
|
|
20
|
+
- title 最多50字符,content 最多800字符。mode 按真实发布环境选择 test/live;test 会创建测试消息,不能当预检。
|
|
21
|
+
- App sender 使用 getCurrentAppId() 的真实 APP_ID,v2 不注入 project 上下文,不复制旧 getCurrentProjectId。导航和名称展示按目标前端支持核实。
|
|
22
|
+
- sender/link/meta 是展示提示,不作为授权。新代码不使用废弃 source 字段。
|
|
23
|
+
- 同一业务事件、收件人、内容使用稳定 idempotencyKey;新事件使用新键,不因超时随机换键。
|
|
24
|
+
|
|
25
|
+
## 渠道
|
|
26
|
+
|
|
27
|
+
| channels | 提醒范围 |
|
|
28
|
+
| --- | --- |
|
|
29
|
+
| 省略 | SDK 默认 web + mobile |
|
|
30
|
+
| ['web'] | Web/Desktop |
|
|
31
|
+
| ['mobile'] | 手机 |
|
|
32
|
+
| [] | 只建站内信 |
|
|
33
|
+
|
|
34
|
+
Desktop 属于 web,不存在 desktop 渠道。显式单渠道不补齐,重复项会去重。SDK 默认和裸后端省略参数的行为不同,不混用。
|
|
35
|
+
|
|
36
|
+
## 返回与失败
|
|
37
|
+
|
|
38
|
+
send 返回直接对象 {messageId,status,createdAt},get 返回直接状态对象,没有 data 外层。accepted 表示受理,sent 表示已发送,不是用户已读;failed 检查 errorCode/message。
|
|
39
|
+
|
|
40
|
+
v2 ApiError 提供 status/errorCode/msg;不要沿用 v1 JSON.parse(error.msg) 的旧解析方式。未知结果先对账;已有 messageId 按 ID 查,没有按 idempotencyKey 查询的 SDK 入口。HTTP SDK 不自动重试。
|
|
41
|
+
|
|
42
|
+
完整示例与参数边界均在操作参考中。Skill 示例不会自动发送消息,也不授予真实发送权限。
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# 查询通知投递状态
|
|
2
|
+
|
|
3
|
+
方法:`notifications.get({messageId})`;POST /openapi/v1/notifications/get。需要 notifications:read,与 send 权限独立;仅查询当前发送 Key 可见的消息。
|
|
4
|
+
|
|
5
|
+
| 参数/结果 | 类型 | 说明 |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| messageId | string | 发送回执 ID,不转 Number |
|
|
8
|
+
| status | string | accepted / sent / failed |
|
|
9
|
+
| createdAt / completedAt | string | ISO 时间,completedAt 可省略 |
|
|
10
|
+
| errorCode / message | string | 失败原因,可省略 |
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { createClient, type Tier0Client } from '@tier0/sdk-v2';
|
|
14
|
+
|
|
15
|
+
export async function waitForNotification(
|
|
16
|
+
messageId: string, client: Tier0Client = createClient(), maxAttempts = 10,
|
|
17
|
+
) {
|
|
18
|
+
if (!Number.isInteger(maxAttempts) || maxAttempts < 1 || maxAttempts > 20) {
|
|
19
|
+
throw new Error('查询次数应为 1–20');
|
|
20
|
+
}
|
|
21
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
22
|
+
const result = await client.notifications.get({ messageId });
|
|
23
|
+
if (result.status === 'sent' || result.status === 'failed') return result;
|
|
24
|
+
if (result.status !== 'accepted') throw new Error('未识别的通知状态');
|
|
25
|
+
if (attempt === maxAttempts - 1) return result;
|
|
26
|
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
27
|
+
}
|
|
28
|
+
throw new Error('未取得通知状态');
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
accepted 表示仍处理,达到次数上限就返回处理中;sent 表示站内信已创建、推送交给渠道,不代表用户收到或阅读。failed 显示 errorCode/message,不继续当成功。
|
|
33
|
+
|
|
34
|
+
返回直接对象,不读取 result.data。404 MESSAGE_NOT_FOUND 也可能是发送 Key 不同,不能直接断言消息丢失;先核对原 messageId 与发送身份。查询任务不自动补发。
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# 发送通知
|
|
2
|
+
|
|
3
|
+
方法:`notifications.send(body)`;POST /openapi/v1/notifications/send。需要 notifications:send;收件人为当前 Workspace 内可用的成员,按[成员查询](../../tier0-sdk-v2-members/references/platform-members.md)解析。
|
|
4
|
+
|
|
5
|
+
| 字段 | 类型 | 必填 | 说明 |
|
|
6
|
+
| --- | --- | --- | --- |
|
|
7
|
+
| recipientUserId | string | 是 | 已解析的用户 ID,保持字符串 |
|
|
8
|
+
| type | inbox | 是 | 后端只接受 inbox;不是 business |
|
|
9
|
+
| title | string | 是 | 1–50 字符 |
|
|
10
|
+
| content | string | 是 | 1–800 字符 |
|
|
11
|
+
| idempotencyKey | string | 是 | 最多 128 字符;同一次业务事件固定 |
|
|
12
|
+
| mode | test/live | 否 | 根据实际发布环境显式选择;省略服务端默认 live |
|
|
13
|
+
| channels | array | 否 | SDK 省略补 web+mobile;[] 仅站内信 |
|
|
14
|
+
| sender | object | 否 | app/other;system 仅平台内部服务 Key |
|
|
15
|
+
| link | string | 否 | https:// 或单斜杠开头,最多 500 字符 |
|
|
16
|
+
|
|
17
|
+
## 完整 App 示例
|
|
18
|
+
|
|
19
|
+
下面函数供应用在已授权的业务事件中调用;写示例不意味着发送真实消息。intentKey 应包含收件人、业务对象和事件版本,不因网络重试换键。
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { createClient, getCurrentAppId, type Tier0Client } from '@tier0/sdk-v2';
|
|
23
|
+
|
|
24
|
+
export async function sendCompletedOrder(
|
|
25
|
+
recipientUserId: string, orderId: string, intentKey: string, mode: 'test' | 'live',
|
|
26
|
+
client: Tier0Client = createClient(),
|
|
27
|
+
) {
|
|
28
|
+
return client.notifications.send({
|
|
29
|
+
recipientUserId, type: 'inbox', title: '工单已完成',
|
|
30
|
+
content: '工单 ' + orderId + ' 已完成,请查看处理结果。',
|
|
31
|
+
idempotencyKey: intentKey, mode, channels: ['web', 'mobile'],
|
|
32
|
+
sender: { type: 'app', id: getCurrentAppId() },
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
sender.id 来自真实 APP_ID UUID。v2 不注入 project 上下文,也没有 getCurrentProjectId;不要把 Folder/Workspace ID 填进 meta.projectId。旧 UI 的 App 名称解析/打开 App 按钮依赖 appId+projectId,不能将旧导航规则原样承诺到新架构。需带链接时传已验证可访问的业务 https 地址,或确认目标前端支持的相对路由;缺少旧上下文时不猜拼地址。
|
|
38
|
+
|
|
39
|
+
other 发送者应给人读名称,如 `{type:'other',name:'库存同步服务'}`。app 不重复硬编码名称。sender/meta/link 都是展示导航提示,不是授权证明。meta 值为字符串,序列化最多 500 字节;sender.name 最多100字符。source 是废弃别名,不用于新代码。
|
|
40
|
+
|
|
41
|
+
## 渠道、模式与幂等
|
|
42
|
+
|
|
43
|
+
| channels | 提醒范围 |
|
|
44
|
+
| --- | --- |
|
|
45
|
+
| 省略 | SDK 显式发送 web+mobile |
|
|
46
|
+
| ['web'] | Web/Desktop |
|
|
47
|
+
| ['mobile'] | 手机 |
|
|
48
|
+
| [] | 只建站内信 |
|
|
49
|
+
| 重复项 | SDK 去重 |
|
|
50
|
+
|
|
51
|
+
没有 desktop 渠道。不要将 SDK 默认值与裸后端省略渠道的静默语义混淆。test 也会创建测试消息,不是 dryRun;仅因 NODE_ENV=production 不能证明 App 已发布。
|
|
52
|
+
|
|
53
|
+
同键同内容用于重放原意图;同键改内容产生冲突。新事件/不同收件人使用不同键。SDK 不自动重试,结果未知先对账;已有 messageId 用 get,没有按意图键查询的 SDK 方法。
|
|
54
|
+
|
|
55
|
+
## 回执与错误
|
|
56
|
+
|
|
57
|
+
成功为直接 {messageId,status,createdAt},没有 data。accepted 是异步受理,sent 不是已读,详见[查询](get.md)。
|
|
58
|
+
|
|
59
|
+
v2 的 ApiError 已提供 status/errorCode/msg,不再 JSON.parse(error.msg) 提取旧版原始错误。
|
|
60
|
+
|
|
61
|
+
| HTTP / errorCode | 处理 |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| 400 INVALID_NOTIFICATION_TYPE | type 必须 inbox |
|
|
64
|
+
| 400 INVALID_REQUEST | 检查字段、枚举和长度 |
|
|
65
|
+
| 401 INVALID_CREDENTIAL | 核对注入身份 |
|
|
66
|
+
| 403 NOTIFICATION_NOT_ALLOWED | 核对 send 权限 |
|
|
67
|
+
| 404 RECIPIENT_NOT_AVAILABLE | 刷新成员并重新选择 |
|
|
68
|
+
| 409 IDEMPOTENCY_KEY_CONFLICT | 区分原事件与新事件,不静默换键重发 |
|
|
69
|
+
| 422 CONTENT_LIMIT_EXCEEDED | 缩短内容 |
|
|
70
|
+
| 429 NOTIFICATION_RATE_LIMITED | 有界退避;保留原键,不自动广播重发 |
|
|
71
|
+
|
|
72
|
+
link 拒绝 //host、反斜杠、Unicode 空白/控制/不可见格式字符。省略 link 不保证无按钮,实际导航还取决于发送者与目标前端。
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tier0-sdk-v2-source
|
|
3
|
+
description: 使用 @tier0/sdk-v2 的 client.source 查询、创建、修改、启停和删除新架构 Source,配置协议连接、处理影响分析、版本冲突与运行观测。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Source 数据接入
|
|
7
|
+
|
|
8
|
+
Source 维护连接配置、认证引用与源状态。脚本处理用 [Stream](../tier0-sdk-v2-stream/SKILL.md),数据存储用 [UNS](../tier0-sdk-v2-uns/SKILL.md)。服务端使用 `createClient({apiHost,apiKey})`,或注入 TIER0_API_HOST / TIER0_API_KEY 后 `createClient()`;密钥不进入浏览器。
|
|
9
|
+
|
|
10
|
+
## 按任务加载参考
|
|
11
|
+
|
|
12
|
+
| 用户任务 | 参考 | SDK 方法 |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| 选协议、准备连接配置 | [协议配置](references/protocols.md) | protocols.list |
|
|
15
|
+
| 创建 HTTP/带凭据源 | [创建](references/create.md) | documents.create |
|
|
16
|
+
| 列表、详情、运行状态 | [查询](references/read.md) | documents.list/get、observations.get、runtime.get |
|
|
17
|
+
| 修改名称、端点或配置 | [修改](references/update.md) | documents.update |
|
|
18
|
+
| 查看修改/启停/删除影响 | [影响分析](references/changes.md) | impact |
|
|
19
|
+
| 启用、停用 | [状态变更](references/state.md) | state.update |
|
|
20
|
+
| 删除并核验 | [删除](references/delete.md) | delete |
|
|
21
|
+
|
|
22
|
+
先加载对应操作文档,使用其中的参数表、完整请求及返回判断。只执行用户任务所需步骤;示例函数调用才会执行写入,导入不执行。新建→查询→修改→删除可组成生命周期,但查询任务不触发其他操作。
|
|
23
|
+
|
|
24
|
+
## 核心约束
|
|
25
|
+
|
|
26
|
+
1. 原生文档 `{name,protocol,config}` 使用 documents.*;通用投影使用 source.list/get/create/update。http 原生协议与 http-api adapter 不可互换。
|
|
27
|
+
2. 编辑前读取完整 documents.get;安全列表摘要不能无损回写。原生 ID 是十进制字符串,revision 原样传递,不转 Number。
|
|
28
|
+
3. 现有 Source 修改、启停、删除都要 expectedRevision + confirmedImpactRevision。先形成精确意图,再读取全部影响分页;回执不可合成、跨意图复用或静默刷新。
|
|
29
|
+
4. 配置中保留认证/TLS 引用,不写明文秘密;HTTP 新凭据走创建专用 credentials。
|
|
30
|
+
5. desiredState 不代表实际健康,保存配置不代表 Stream 已切换至新 configRevision。运行状态另行读取。
|
|
31
|
+
|
|
32
|
+
## 返回值与异常
|
|
33
|
+
|
|
34
|
+
所有调用返回完整 code/msg/requestId/data。documents.create/update 的预检返回 validated/sideEffects,提交返回完整 Source view;批量状态/删除返回 Outcome。只有 committed 且无项错误才可报告提交成功;accepted/jobId 继续查询作业。
|
|
35
|
+
|
|
36
|
+
需要查看历史用 source.history({id,beforeRevision,size}),按 nextBeforeRevision 继续;探测用 source.probe,其外部系统访问应属于当前任务。source.connection.update 属于现有源变更,同样遵守影响确认契约,不能作为绕过文档校验的入口。
|
|
37
|
+
|
|
38
|
+
UnsV2Error 表示已知拒绝;UnsV2OutcomeUnknownError 表示可能已写入。保存 requestId、原请求和意图键,先查详情/历史/作业对账。取消等待不撤销服务端操作;SDK 不自动重试。权限不足不能解释为资源不存在。
|