@unifyport/sdk-node 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +107 -91
  2. package/README.zh-CN.md +306 -0
  3. package/package.json +15 -3
package/README.md CHANGED
@@ -1,47 +1,51 @@
1
1
  # `@unifyport/sdk-node`
2
2
 
3
- 面向 Node.js/TypeScript 的 UnifyPort Device API SDK。它提供类型安全的
4
- `UnifyPortDeviceClient`、统一的成功响应与错误模型,以及 timeout、安全重试、取消和 cursor
5
- 分页能力。
3
+ [English](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs/blob/main/packages/sdk/README.md) | [简体中文](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs/blob/main/packages/sdk/README.zh-CN.md) | [Website](https://www.unifyport.ai)
6
4
 
7
- SDK 使用固定的 `X-Api-Key` 认证边界。`/v1/accounts/...` operation 属于 Device API,统一由
8
- `UnifyPortDeviceClient` 提供。
5
+ The UnifyPort Device API SDK for Node.js and TypeScript. It provides the type-safe
6
+ `UnifyPortDeviceClient`, consistent success response and error models, timeouts, safe retries,
7
+ cancellation, and cursor pagination.
9
8
 
10
- ## 功能范围
9
+ The SDK uses a fixed `X-Api-Key` authentication boundary. Operations under `/v1/accounts/...` belong to
10
+ the Device API and are all available through `UnifyPortDeviceClient`.
11
11
 
12
- 当前公开契约中的全部 operation 都有同名 SDK 方法,主要覆盖:
12
+ ## Features
13
13
 
14
- - workspace 与 provider region 查询;
15
- - provider 账号资源、授权流程与运行状态;
16
- - 会话、联系人和群组;
17
- - 消息发送与消息动作;
18
- - API Key 与 webhook endpoint 管理。
14
+ Every operation in the current public contract has a same-named SDK method. Major areas include:
19
15
 
20
- 方法名与 OpenAPI `operationId` 保持一致。每个 operation 的参数、请求体、返回字段和 TypeScript
21
- 示例见 [API Reference](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs/blob/main/docs/api-reference/README.md);
22
- HTTP path、重试和 MCP 策略见
23
- [API 覆盖表](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs/blob/main/docs/api-coverage.md)。
16
+ - workspace and provider region queries;
17
+ - provider account resources, authorization flows, and runtime status;
18
+ - conversations, contacts, and groups;
19
+ - message sending and message actions;
20
+ - API key and webhook endpoint management.
24
21
 
25
- ## 环境要求
22
+ Method names match their OpenAPI `operationId` values. See the
23
+ [API Reference](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs/blob/main/docs/api-reference/README.md)
24
+ for each operation's parameters, request body, response fields, and TypeScript example. See
25
+ [API coverage](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs/blob/main/docs/api-coverage.md) for
26
+ HTTP paths, retry behavior, and MCP policies.
27
+
28
+ ## Requirements
26
29
 
27
30
  - Node.js `>=22.12.0`
28
- - TypeScript 项目或支持 ESM 的 Node.js 项目
31
+ - A TypeScript project or a Node.js project with ESM support
29
32
 
30
- 该 package 只提供 ESM `import` 和 TypeScript 声明文件,不提供 CommonJS `require()` 入口。
33
+ This package provides only ESM `import` exports and TypeScript declarations. It does not provide a
34
+ CommonJS `require()` entry point.
31
35
 
32
- ## 安装
36
+ ## Installation
33
37
 
34
38
  ```bash
35
39
  npm install @unifyport/sdk-node
36
40
  ```
37
41
 
38
- 也可以使用 pnpm:
42
+ Or with pnpm:
39
43
 
40
44
  ```bash
41
45
  pnpm add @unifyport/sdk-node
42
46
  ```
43
47
 
44
- ## 快速开始
48
+ ## Quick start
45
49
 
46
50
  ```ts
47
51
  import { UnifyPortDeviceClient } from "@unifyport/sdk-node";
@@ -54,7 +58,7 @@ function requiredEnv(name: string): string {
54
58
  return value;
55
59
  }
56
60
 
57
- // 凭据只从运行环境读取,避免进入源码、构建产物或版本控制。
61
+ // Read credentials only from the runtime environment so they never enter source or build artifacts.
58
62
  const device = new UnifyPortDeviceClient({
59
63
  baseUrl: requiredEnv("UNIFYPORT_DEVICE_API_BASE_URL"),
60
64
  apiKey: requiredEnv("UNIFYPORT_DEVICE_API_KEY")
@@ -66,9 +70,10 @@ console.log(result.data.data.name);
66
70
  console.log(result.status, result.requestId);
67
71
  ```
68
72
 
69
- `ApiResult.data` 是完整 API JSON envelope,因此资源数据通常位于 `result.data.data`。
73
+ `ApiResult.data` contains the complete API JSON envelope, so resource data is usually available at
74
+ `result.data.data`.
70
75
 
71
- ## Client 配置
76
+ ## Client configuration
72
77
 
73
78
  ```ts
74
79
  const device = new UnifyPortDeviceClient({
@@ -84,29 +89,29 @@ const device = new UnifyPortDeviceClient({
84
89
  });
85
90
  ```
86
91
 
87
- | 配置项 | 必填 | 说明 |
88
- | ------------------- | ---- | --------------------------------------------------------------------- |
89
- | `baseUrl` | 是 | Device API 的绝对 URL;远程地址必须使用 HTTPS |
90
- | `apiKey` | 是 | 字符串,或返回字符串的同步/异步 provider |
91
- | `fetch` | 否 | 自定义 `fetch` 实现,签名为 `(request: Request) => Promise<Response>` |
92
- | `timeoutMs` | 否 | 默认 `30000`;每次 operation 的总超时 |
93
- | `maxResponseBytes` | 否 | 默认 8 MiB,最大 64 MiB;同时限制 wire body 与规范化后的 JSON |
94
- | `retry` | 否 | 安全 operation 的重试次数和 full-jitter 退避配置 |
95
- | `allowInsecureHttp` | 否 | 默认 `false`;仅允许显式开启的 loopback HTTP 开发环境 |
92
+ | Option | Required | Description |
93
+ | ------------------- | -------- | -------------------------------------------------------------------------------- |
94
+ | `baseUrl` | Yes | Absolute Device API URL; remote URLs must use HTTPS |
95
+ | `apiKey` | Yes | A string or a synchronous/asynchronous provider that returns a string |
96
+ | `fetch` | No | Custom `fetch` implementation with `(request: Request) => Promise<Response>` |
97
+ | `timeoutMs` | No | Defaults to `30000`; total timeout for each operation |
98
+ | `maxResponseBytes` | No | Defaults to 8 MiB, maximum 64 MiB; limits both wire body and normalized JSON |
99
+ | `retry` | No | Retry count and full-jitter backoff settings for safe operations |
100
+ | `allowInsecureHttp` | No | Defaults to `false`; allows only explicitly enabled loopback HTTP in development |
96
101
 
97
- `baseUrl` 不应包含 username、password、query 或 fragment。API key 由 client 统一注入,单次 operation
98
- 不能覆盖认证 header。
102
+ `baseUrl` must not contain a username, password, query, or fragment. The client injects the API key
103
+ centrally, and an individual operation cannot override the authentication header.
99
104
 
100
- ## 调用 operation
105
+ ## Calling operations
101
106
 
102
- 方法统一使用 `(request?, execution?)`:
107
+ Every method uses `(request?, execution?)`:
103
108
 
104
- - 没有参数的 operation 可以省略 `request`;
105
- - path 和 query 参数放在 `request.params`;
106
- - JSON 请求体放在 `request.body`;
107
- - 单次 timeout、重试收紧和取消信号放在 `execution`。
109
+ - Omit `request` for operations without parameters.
110
+ - Put path and query parameters in `request.params`.
111
+ - Put a JSON request body in `request.body`.
112
+ - Put per-call timeout, tighter retry settings, and the cancellation signal in `execution`.
108
113
 
109
- ### 无参数读取
114
+ ### No-parameter reads
110
115
 
111
116
  ```ts
112
117
  const workspace = await device.getWorkspace();
@@ -118,7 +123,7 @@ for (const account of accounts.data.data) {
118
123
  }
119
124
  ```
120
125
 
121
- ### Path 参数
126
+ ### Path parameters
122
127
 
123
128
  ```ts
124
129
  const regions = await device.listProviderRegions({
@@ -132,7 +137,7 @@ for (const region of regions.data.data.regions) {
132
137
  }
133
138
  ```
134
139
 
135
- ### Path 与 query 参数
140
+ ### Path and query parameters
136
141
 
137
142
  ```ts
138
143
  const contacts = await device.listContacts({
@@ -146,7 +151,7 @@ console.log(contacts.data.data.items);
146
151
  console.log(contacts.data.data.next_cursor);
147
152
  ```
148
153
 
149
- ### JSON 请求体
154
+ ### JSON request body
150
155
 
151
156
  ```ts
152
157
  const message = await device.sendMessage({
@@ -160,22 +165,24 @@ const message = await device.sendMessage({
160
165
  console.log(message.data.data.message_id, message.data.data.status);
161
166
  ```
162
167
 
163
- 写操作不会因为 HTTP method 或调用方配置而自动获得重试能力。消息正文等敏感输入不应写入日志。
168
+ A write operation does not become retryable because of its HTTP method or caller configuration. Do not
169
+ write sensitive inputs such as message bodies to logs.
164
170
 
165
- ## 成功响应
171
+ ## Successful responses
166
172
 
167
- 所有 operation 都返回 `ApiResult<T>`:
173
+ Every operation returns `ApiResult<T>`:
168
174
 
169
- | 字段 | 说明 |
170
- | ----------- | ------------------------------------------------------- |
171
- | `data` | 按公开契约解析后的完整 API JSON envelope |
172
- | `status` | HTTP status |
173
- | `requestId` | 可选的服务端 request ID |
174
- | `response` | 底层 `Response`,用于读取正常响应的 header 等传输元数据 |
175
+ | Field | Description |
176
+ | ----------- | ------------------------------------------------------------------------- |
177
+ | `data` | Complete API JSON envelope parsed according to the public contract |
178
+ | `status` | HTTP status |
179
+ | `requestId` | Optional server request ID |
180
+ | `response` | Underlying `Response` for headers and other successful transport metadata |
175
181
 
176
- `response` 的 body 已由 SDK 解析,不应依赖再次读取;业务数据使用类型化的 `data`。
182
+ The SDK has already parsed the `response` body; do not depend on reading it again. Use the typed `data`
183
+ for application data.
177
184
 
178
- ## Timeout、取消与重试
185
+ ## Timeouts, cancellation, and retries
179
186
 
180
187
  ```ts
181
188
  const controller = new AbortController();
@@ -190,20 +197,22 @@ const workspace = await device.getWorkspace(
190
197
  );
191
198
  ```
192
199
 
193
- 单次 `execution.retry` 只能关闭或收紧 client 级重试:
200
+ A per-call `execution.retry` setting can only disable or tighten client-level retries:
194
201
 
195
202
  ```ts
196
203
  await device.getWorkspace({}, { retry: false });
197
204
  await device.getWorkspace({}, { retry: { maxRetries: 0 } });
198
205
  ```
199
206
 
200
- 自动重试只适用于策略明确允许的安全 operation,处理网络失败和 `408`、`429`、`502`、`503`、
201
- `504` 等临时状态,并遵守合法的 `Retry-After`。普通写操作、账号授权提交和一次性 secret operation
202
- 不会自动重试。
207
+ Automatic retries apply only to safe operations explicitly allowed by policy. They handle network
208
+ failures and temporary statuses such as `408`, `429`, `502`, `503`, and `504`, and honor a valid
209
+ `Retry-After` value. Ordinary writes, account authorization submissions, and one-time secret operations
210
+ are not retried automatically.
203
211
 
204
- ## Cursor 分页
212
+ ## Cursor pagination
205
213
 
206
- `paginateCursor` 会逐项迭代分页数据,并提供最大页数、重复 cursor 检测与 `AbortSignal` 支持:
214
+ `paginateCursor` iterates page items and supports a maximum page count, repeated cursor detection, and
215
+ `AbortSignal`:
207
216
 
208
217
  ```ts
209
218
  import { paginateCursor } from "@unifyport/sdk-node";
@@ -213,7 +222,7 @@ const controller = new AbortController();
213
222
 
214
223
  for await (const contact of paginateCursor(
215
224
  async (cursor, pageSignal) => {
216
- // exactOptionalPropertyTypes 下不把 undefined 写入可选 query 字段。
225
+ // With exactOptionalPropertyTypes, do not assign undefined to an optional query field.
217
226
  const query = cursor === undefined ? { limit: 100 } : { cursor, limit: 100 };
218
227
  const result = await device.listContacts(
219
228
  {
@@ -232,12 +241,14 @@ for await (const contact of paginateCursor(
232
241
  }
233
242
  ```
234
243
 
235
- 分页 helper 默认最多读取 10,000 页。业务代码通常应根据自身任务量设置更小的 `maxPages`。
244
+ The pagination helper reads at most 10,000 pages by default. Application code should normally set a
245
+ smaller `maxPages` value appropriate to its workload.
236
246
 
237
- ## 错误处理
247
+ ## Error handling
238
248
 
239
- 所有公开错误都继承 `UnifyPortError`。建议只记录经过筛选的 `status`、`code`、`requestId`、`api` 和
240
- `operationId`,不要直接序列化完整错误、请求、响应或 header。
249
+ All public errors extend `UnifyPortError`. Record only reviewed fields such as `status`, `code`,
250
+ `requestId`, `api`, and `operationId`. Do not serialize complete errors, requests, responses, or
251
+ headers.
241
252
 
242
253
  ```ts
243
254
  import { UnifyPortAbortError, UnifyPortApiError, UnifyPortTimeoutError } from "@unifyport/sdk-node";
@@ -262,19 +273,19 @@ try {
262
273
  }
263
274
  ```
264
275
 
265
- | 错误类型 | 场景 |
266
- | ----------------------------- | --------------------------------------- |
267
- | `UnifyPortConfigurationError` | client 配置无效 |
268
- | `UnifyPortNetworkError` | 网络或自定义 credential provider 失败 |
269
- | `UnifyPortTimeoutError` | operation 超时 |
270
- | `UnifyPortAbortError` | 调用方通过 `AbortSignal` 取消 |
271
- | `UnifyPortApiError` | API 返回非成功 HTTP status |
272
- | `UnifyPortResponseParseError` | 成功响应无法安全解析 |
273
- | `UnifyPortPaginationError` | cursor 缺失、重复、超出页数或分页被取消 |
276
+ | Error type | Scenario |
277
+ | ----------------------------- | --------------------------------------------------------------------- |
278
+ | `UnifyPortConfigurationError` | Invalid client configuration |
279
+ | `UnifyPortNetworkError` | Network or custom credential provider failure |
280
+ | `UnifyPortTimeoutError` | Operation timeout |
281
+ | `UnifyPortAbortError` | Caller cancellation through `AbortSignal` |
282
+ | `UnifyPortApiError` | API returned a non-success HTTP status |
283
+ | `UnifyPortResponseParseError` | Successful response could not be parsed safely |
284
+ | `UnifyPortPaginationError` | Missing/repeated cursor, page limit exceeded, or pagination cancelled |
274
285
 
275
- ## TypeScript 类型
286
+ ## TypeScript types
276
287
 
277
- 根入口同时导出公开契约类型:
288
+ The package root also exports the public contract types:
278
289
 
279
290
  ```ts
280
291
  import type { DeviceApiComponents, DeviceApiOperations, DeviceApiPaths } from "@unifyport/sdk-node";
@@ -284,21 +295,26 @@ type SendMessageOperation = DeviceApiOperations["sendMessage"];
284
295
  type DevicePaths = DeviceApiPaths;
285
296
  ```
286
297
 
287
- 为避免 JavaScript 精度丢失,SDK 会把超出安全范围的整数保留为字符串。因此 `uint64`/`int64` 风格
288
- 字段可能是 `number | string`;不要在没有范围保证时无条件调用 `Number(...)`。
298
+ To avoid JavaScript precision loss, the SDK preserves integers outside the safe range as strings.
299
+ Fields modeled like `uint64` or `int64` may therefore be `number | string`. Do not call `Number(...)`
300
+ unconditionally unless the value's range is guaranteed.
289
301
 
290
- ## 安全注意事项
302
+ ## Security considerations
291
303
 
292
- - API key 应从 secret manager 或运行环境提供,不要硬编码或提交到版本控制;
293
- - 生产环境必须使用 HTTPS;`allowInsecureHttp` 只接受显式开启的 loopback 地址;
294
- - 不要记录认证 header、授权 code/password/session、消息正文或一次性 secret;
295
- - 不要从外部输入动态覆盖 `baseUrl`;client 会把凭据限制在初始化时确定的 origin/path;
296
- - 对写操作自行设计业务幂等和失败恢复,不要把读取 operation 的重试假设套用到写入。
304
+ - Provide the API key through a secret manager or runtime environment; never hard-code or commit it.
305
+ - Require HTTPS in production. `allowInsecureHttp` accepts only explicitly enabled loopback addresses.
306
+ - Do not log authentication headers, authorization codes/passwords/sessions, message bodies, or
307
+ one-time secrets.
308
+ - Do not override `baseUrl` dynamically from external input. The client confines credentials to the
309
+ origin and path established during initialization.
310
+ - Design application-level idempotency and failure recovery for writes. Do not apply read-operation
311
+ retry assumptions to writes.
297
312
 
298
- 更多设计细节见[安全边界](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs/blob/main/docs/security.md)。
313
+ For more detail, see the
314
+ [security boundaries](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs/blob/main/docs/security.md).
299
315
 
300
- ## 项目与许可证
316
+ ## Project and license
301
317
 
302
- - [GitHub 仓库](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs)
303
- - [项目 README](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs#readme)
318
+ - [GitHub repository](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs)
319
+ - [Project README](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs#readme)
304
320
  - [MIT License](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs/blob/main/LICENSE)
@@ -0,0 +1,306 @@
1
+ # `@unifyport/sdk-node`
2
+
3
+ [English](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs/blob/main/packages/sdk/README.md) | [简体中文](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs/blob/main/packages/sdk/README.zh-CN.md) | [官方网站](https://www.unifyport.ai)
4
+
5
+ 面向 Node.js/TypeScript 的 UnifyPort Device API SDK。它提供类型安全的
6
+ `UnifyPortDeviceClient`、统一的成功响应与错误模型,以及 timeout、安全重试、取消和 cursor
7
+ 分页能力。
8
+
9
+ SDK 使用固定的 `X-Api-Key` 认证边界。`/v1/accounts/...` operation 属于 Device API,统一由
10
+ `UnifyPortDeviceClient` 提供。
11
+
12
+ ## 功能范围
13
+
14
+ 当前公开契约中的全部 operation 都有同名 SDK 方法,主要覆盖:
15
+
16
+ - workspace 与 provider region 查询;
17
+ - provider 账号资源、授权流程与运行状态;
18
+ - 会话、联系人和群组;
19
+ - 消息发送与消息动作;
20
+ - API Key 与 webhook endpoint 管理。
21
+
22
+ 方法名与 OpenAPI `operationId` 保持一致。每个 operation 的参数、请求体、返回字段和 TypeScript
23
+ 示例见 [API Reference](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs/blob/main/docs/zh-CN/api-reference/README.md);
24
+ HTTP path、重试和 MCP 策略见
25
+ [API 覆盖表](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs/blob/main/docs/zh-CN/api-coverage.md)。
26
+
27
+ ## 环境要求
28
+
29
+ - Node.js `>=22.12.0`
30
+ - TypeScript 项目或支持 ESM 的 Node.js 项目
31
+
32
+ 该 package 只提供 ESM `import` 和 TypeScript 声明文件,不提供 CommonJS `require()` 入口。
33
+
34
+ ## 安装
35
+
36
+ ```bash
37
+ npm install @unifyport/sdk-node
38
+ ```
39
+
40
+ 也可以使用 pnpm:
41
+
42
+ ```bash
43
+ pnpm add @unifyport/sdk-node
44
+ ```
45
+
46
+ ## 快速开始
47
+
48
+ ```ts
49
+ import { UnifyPortDeviceClient } from "@unifyport/sdk-node";
50
+
51
+ function requiredEnv(name: string): string {
52
+ const value = process.env[name];
53
+ if (!value) {
54
+ throw new Error(`Missing required environment variable: ${name}`);
55
+ }
56
+ return value;
57
+ }
58
+
59
+ // 凭据只从运行环境读取,避免进入源码、构建产物或版本控制。
60
+ const device = new UnifyPortDeviceClient({
61
+ baseUrl: requiredEnv("UNIFYPORT_DEVICE_API_BASE_URL"),
62
+ apiKey: requiredEnv("UNIFYPORT_DEVICE_API_KEY")
63
+ });
64
+
65
+ const result = await device.getWorkspace();
66
+
67
+ console.log(result.data.data.name);
68
+ console.log(result.status, result.requestId);
69
+ ```
70
+
71
+ `ApiResult.data` 是完整 API JSON envelope,因此资源数据通常位于 `result.data.data`。
72
+
73
+ ## Client 配置
74
+
75
+ ```ts
76
+ const device = new UnifyPortDeviceClient({
77
+ baseUrl: "https://device.example.com",
78
+ apiKey: () => requiredEnv("UNIFYPORT_DEVICE_API_KEY"),
79
+ timeoutMs: 15_000,
80
+ maxResponseBytes: 4 * 1024 * 1024,
81
+ retry: {
82
+ maxRetries: 2,
83
+ baseDelayMs: 200,
84
+ maxDelayMs: 5_000
85
+ }
86
+ });
87
+ ```
88
+
89
+ | 配置项 | 必填 | 说明 |
90
+ | ------------------- | ---- | --------------------------------------------------------------------- |
91
+ | `baseUrl` | 是 | Device API 的绝对 URL;远程地址必须使用 HTTPS |
92
+ | `apiKey` | 是 | 字符串,或返回字符串的同步/异步 provider |
93
+ | `fetch` | 否 | 自定义 `fetch` 实现,签名为 `(request: Request) => Promise<Response>` |
94
+ | `timeoutMs` | 否 | 默认 `30000`;每次 operation 的总超时 |
95
+ | `maxResponseBytes` | 否 | 默认 8 MiB,最大 64 MiB;同时限制 wire body 与规范化后的 JSON |
96
+ | `retry` | 否 | 安全 operation 的重试次数和 full-jitter 退避配置 |
97
+ | `allowInsecureHttp` | 否 | 默认 `false`;仅允许显式开启的 loopback HTTP 开发环境 |
98
+
99
+ `baseUrl` 不应包含 username、password、query 或 fragment。API key 由 client 统一注入,单次 operation
100
+ 不能覆盖认证 header。
101
+
102
+ ## 调用 operation
103
+
104
+ 方法统一使用 `(request?, execution?)`:
105
+
106
+ - 没有参数的 operation 可以省略 `request`;
107
+ - path 和 query 参数放在 `request.params`;
108
+ - JSON 请求体放在 `request.body`;
109
+ - 单次 timeout、重试收紧和取消信号放在 `execution`。
110
+
111
+ ### 无参数读取
112
+
113
+ ```ts
114
+ const workspace = await device.getWorkspace();
115
+ console.log(workspace.data.data);
116
+
117
+ const accounts = await device.listAccounts();
118
+ for (const account of accounts.data.data) {
119
+ console.log(account.id, account.provider, account.status);
120
+ }
121
+ ```
122
+
123
+ ### Path 参数
124
+
125
+ ```ts
126
+ const regions = await device.listProviderRegions({
127
+ params: {
128
+ path: { provider: "whatsapp" }
129
+ }
130
+ });
131
+
132
+ for (const region of regions.data.data.regions) {
133
+ console.log(region.region, region.supported, region.allocatable);
134
+ }
135
+ ```
136
+
137
+ ### Path 与 query 参数
138
+
139
+ ```ts
140
+ const contacts = await device.listContacts({
141
+ params: {
142
+ path: { account_id: "acc_xxx" },
143
+ query: { limit: 100, q: "Alice" }
144
+ }
145
+ });
146
+
147
+ console.log(contacts.data.data.items);
148
+ console.log(contacts.data.data.next_cursor);
149
+ ```
150
+
151
+ ### JSON 请求体
152
+
153
+ ```ts
154
+ const message = await device.sendMessage({
155
+ body: {
156
+ account_id: "acc_xxx",
157
+ to: { id: "recipient_xxx", type: "user" },
158
+ message: { type: "text", text: "Hello from UnifyPort" }
159
+ }
160
+ });
161
+
162
+ console.log(message.data.data.message_id, message.data.data.status);
163
+ ```
164
+
165
+ 写操作不会因为 HTTP method 或调用方配置而自动获得重试能力。消息正文等敏感输入不应写入日志。
166
+
167
+ ## 成功响应
168
+
169
+ 所有 operation 都返回 `ApiResult<T>`:
170
+
171
+ | 字段 | 说明 |
172
+ | ----------- | ------------------------------------------------------- |
173
+ | `data` | 按公开契约解析后的完整 API JSON envelope |
174
+ | `status` | HTTP status |
175
+ | `requestId` | 可选的服务端 request ID |
176
+ | `response` | 底层 `Response`,用于读取正常响应的 header 等传输元数据 |
177
+
178
+ `response` 的 body 已由 SDK 解析,不应依赖再次读取;业务数据使用类型化的 `data`。
179
+
180
+ ## Timeout、取消与重试
181
+
182
+ ```ts
183
+ const controller = new AbortController();
184
+
185
+ const workspace = await device.getWorkspace(
186
+ {},
187
+ {
188
+ timeoutMs: 5_000,
189
+ retry: { maxRetries: 1 },
190
+ signal: controller.signal
191
+ }
192
+ );
193
+ ```
194
+
195
+ 单次 `execution.retry` 只能关闭或收紧 client 级重试:
196
+
197
+ ```ts
198
+ await device.getWorkspace({}, { retry: false });
199
+ await device.getWorkspace({}, { retry: { maxRetries: 0 } });
200
+ ```
201
+
202
+ 自动重试只适用于策略明确允许的安全 operation,处理网络失败和 `408`、`429`、`502`、`503`、
203
+ `504` 等临时状态,并遵守合法的 `Retry-After`。普通写操作、账号授权提交和一次性 secret operation
204
+ 不会自动重试。
205
+
206
+ ## Cursor 分页
207
+
208
+ `paginateCursor` 会逐项迭代分页数据,并提供最大页数、重复 cursor 检测与 `AbortSignal` 支持:
209
+
210
+ ```ts
211
+ import { paginateCursor } from "@unifyport/sdk-node";
212
+
213
+ const accountId = "acc_xxx";
214
+ const controller = new AbortController();
215
+
216
+ for await (const contact of paginateCursor(
217
+ async (cursor, pageSignal) => {
218
+ // exactOptionalPropertyTypes 下不把 undefined 写入可选 query 字段。
219
+ const query = cursor === undefined ? { limit: 100 } : { cursor, limit: 100 };
220
+ const result = await device.listContacts(
221
+ {
222
+ params: {
223
+ path: { account_id: accountId },
224
+ query
225
+ }
226
+ },
227
+ pageSignal === undefined ? {} : { signal: pageSignal }
228
+ );
229
+ return result.data.data;
230
+ },
231
+ { maxPages: 100, signal: controller.signal }
232
+ )) {
233
+ console.log(contact.id, contact.display_name);
234
+ }
235
+ ```
236
+
237
+ 分页 helper 默认最多读取 10,000 页。业务代码通常应根据自身任务量设置更小的 `maxPages`。
238
+
239
+ ## 错误处理
240
+
241
+ 所有公开错误都继承 `UnifyPortError`。建议只记录经过筛选的 `status`、`code`、`requestId`、`api` 和
242
+ `operationId`,不要直接序列化完整错误、请求、响应或 header。
243
+
244
+ ```ts
245
+ import { UnifyPortAbortError, UnifyPortApiError, UnifyPortTimeoutError } from "@unifyport/sdk-node";
246
+
247
+ try {
248
+ await device.getWorkspace();
249
+ } catch (error) {
250
+ if (error instanceof UnifyPortApiError) {
251
+ console.error({
252
+ status: error.status,
253
+ code: error.code,
254
+ requestId: error.requestId,
255
+ operationId: error.operationId
256
+ });
257
+ } else if (error instanceof UnifyPortTimeoutError) {
258
+ console.error({ timeoutMs: error.timeoutMs, operationId: error.operationId });
259
+ } else if (error instanceof UnifyPortAbortError) {
260
+ console.error({ aborted: true, operationId: error.operationId });
261
+ } else {
262
+ throw error;
263
+ }
264
+ }
265
+ ```
266
+
267
+ | 错误类型 | 场景 |
268
+ | ----------------------------- | --------------------------------------- |
269
+ | `UnifyPortConfigurationError` | client 配置无效 |
270
+ | `UnifyPortNetworkError` | 网络或自定义 credential provider 失败 |
271
+ | `UnifyPortTimeoutError` | operation 超时 |
272
+ | `UnifyPortAbortError` | 调用方通过 `AbortSignal` 取消 |
273
+ | `UnifyPortApiError` | API 返回非成功 HTTP status |
274
+ | `UnifyPortResponseParseError` | 成功响应无法安全解析 |
275
+ | `UnifyPortPaginationError` | cursor 缺失、重复、超出页数或分页被取消 |
276
+
277
+ ## TypeScript 类型
278
+
279
+ 根入口同时导出公开契约类型:
280
+
281
+ ```ts
282
+ import type { DeviceApiComponents, DeviceApiOperations, DeviceApiPaths } from "@unifyport/sdk-node";
283
+
284
+ type Workspace = DeviceApiComponents["schemas"]["Workspace"];
285
+ type SendMessageOperation = DeviceApiOperations["sendMessage"];
286
+ type DevicePaths = DeviceApiPaths;
287
+ ```
288
+
289
+ 为避免 JavaScript 精度丢失,SDK 会把超出安全范围的整数保留为字符串。因此 `uint64`/`int64` 风格
290
+ 字段可能是 `number | string`;不要在没有范围保证时无条件调用 `Number(...)`。
291
+
292
+ ## 安全注意事项
293
+
294
+ - API key 应从 secret manager 或运行环境提供,不要硬编码或提交到版本控制;
295
+ - 生产环境必须使用 HTTPS;`allowInsecureHttp` 只接受显式开启的 loopback 地址;
296
+ - 不要记录认证 header、授权 code/password/session、消息正文或一次性 secret;
297
+ - 不要从外部输入动态覆盖 `baseUrl`;client 会把凭据限制在初始化时确定的 origin/path;
298
+ - 对写操作自行设计业务幂等和失败恢复,不要把读取 operation 的重试假设套用到写入。
299
+
300
+ 更多设计细节见[安全边界](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs/blob/main/docs/zh-CN/security.md)。
301
+
302
+ ## 项目与许可证
303
+
304
+ - [GitHub 仓库](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs)
305
+ - [项目 README](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs/blob/main/README.zh-CN.md)
306
+ - [MIT License](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs/blob/main/LICENSE)
package/package.json CHANGED
@@ -1,14 +1,25 @@
1
1
  {
2
2
  "name": "@unifyport/sdk-node",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "license": "MIT",
5
- "description": "面向 Node.js 的 UnifyPort Device API 类型安全 SDK。",
5
+ "description": "Type-safe Node.js SDK for the UnifyPort Device API.",
6
6
  "type": "module",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "git+https://github.com/Unify-Port/UnifyPort-SDK-Nodejs.git",
10
10
  "directory": "packages/sdk"
11
11
  },
12
+ "homepage": "https://www.unifyport.ai",
13
+ "bugs": {
14
+ "url": "https://github.com/Unify-Port/UnifyPort-SDK-Nodejs/issues"
15
+ },
16
+ "keywords": [
17
+ "unifyport",
18
+ "sdk",
19
+ "device-api",
20
+ "nodejs",
21
+ "typescript"
22
+ ],
12
23
  "publishConfig": {
13
24
  "access": "public",
14
25
  "registry": "https://registry.npmjs.org/"
@@ -28,7 +39,8 @@
28
39
  },
29
40
  "files": [
30
41
  "dist",
31
- "README.md"
42
+ "README.md",
43
+ "README.zh-CN.md"
32
44
  ],
33
45
  "sideEffects": false,
34
46
  "scripts": {