@cloudbase/manager-node 5.8.9-beta.1 → 5.9.1

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 (52) hide show
  1. package/.codebuddy/rules/error-code-contract.mdc +13 -0
  2. package/.coding-ci-publish-beta.yaml +19 -12
  3. package/CHANGELOG.md +8 -0
  4. package/changelog_content_en.md +16 -0
  5. package/changelog_content_zh.md +16 -0
  6. package/lib/agent/index.js +6 -5
  7. package/lib/aiModel/index.js +21 -9
  8. package/lib/cloudApp/index.js +4 -4
  9. package/lib/cloudrun/index.js +16 -15
  10. package/lib/common/index.js +1 -1
  11. package/lib/database/index.js +23 -23
  12. package/lib/deploy/DatabaseDeployer.js +19 -24
  13. package/lib/deploy/DeployOrchestrator.js +2 -2
  14. package/lib/deploy/FunctionDeployer.js +19 -1
  15. package/lib/deploy/GatewayDeployer.js +5 -5
  16. package/lib/deploy/StaticDeployer.js +2 -2
  17. package/lib/deploy/framework.js +2 -1
  18. package/lib/deploy/function/config-guard.js +23 -3
  19. package/lib/deploy/function/enterprise-tcr.js +18 -2
  20. package/lib/docs/index.js +4 -3
  21. package/lib/env/index.js +16 -16
  22. package/lib/environment.js +4 -4
  23. package/lib/environmentManager.js +5 -1
  24. package/lib/error-docs.js +385 -0
  25. package/lib/error-types.js +21 -0
  26. package/lib/error.js +239 -2
  27. package/lib/function/index.js +454 -90
  28. package/lib/function/packer.js +1 -1
  29. package/lib/hosting/index.js +1 -1
  30. package/lib/index.js +2 -1
  31. package/lib/integration/crypto.js +4 -3
  32. package/lib/integration/index.js +1 -1
  33. package/lib/integration/secretKey.js +4 -4
  34. package/lib/monitor/index.js +1 -1
  35. package/lib/mysql/index.js +2 -1
  36. package/lib/permission/index.js +56 -49
  37. package/lib/platform/index.js +2 -2
  38. package/lib/sandbox/index.js +68 -61
  39. package/lib/storage/index.js +164 -162
  40. package/lib/user/index.js +32 -31
  41. package/lib/utils/cloud-api-request.js +13 -5
  42. package/lib/utils/cloudbase-request.js +21 -0
  43. package/lib/utils/fs.js +3 -3
  44. package/lib/utils/http-request.js +2 -2
  45. package/lib/utils/index.js +4 -3
  46. package/lib/utils/storage-config.js +3 -3
  47. package/package.json +2 -2
  48. package/types/error-docs.d.ts +57 -0
  49. package/types/error-types.d.ts +32 -0
  50. package/types/error.d.ts +148 -2
  51. package/types/function/index.d.ts +166 -8
  52. package/types/utils/cloudbase-request.d.ts +22 -0
package/lib/user/index.js CHANGED
@@ -8,6 +8,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.UserService = void 0;
10
10
  const utils_1 = require("../utils");
11
+ const error_1 = require("../error");
11
12
  class UserService {
12
13
  constructor(environment) {
13
14
  this.environment = environment;
@@ -119,25 +120,25 @@ class UserService {
119
120
  const { name, uid, type, password, userStatus, nickName, phone, email, avatarUrl, description } = options;
120
121
  // name 校验:1-64,字母/数字开头,仅字母数字._-
121
122
  if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(name)) {
122
- throw new Error('Invalid name');
123
+ (0, error_1.throwInvalidParam)('Invalid name');
123
124
  }
124
125
  // uid 校验:<=64
125
126
  if (uid && uid.length > 64) {
126
- throw new Error('Invalid uid');
127
+ (0, error_1.throwInvalidParam)('Invalid uid');
127
128
  }
128
129
  // type / status 白名单
129
130
  if (type && !['internalUser', 'externalUser'].includes(type)) {
130
- throw new Error('Invalid type');
131
+ (0, error_1.throwInvalidParam)('Invalid type');
131
132
  }
132
133
  if (userStatus && !['ACTIVE', 'BLOCKED'].includes(userStatus)) {
133
- throw new Error('Invalid userStatus');
134
+ (0, error_1.throwInvalidParam)('Invalid userStatus');
134
135
  }
135
136
  if (password) {
136
137
  if (password.length < 8 || password.length > 32) {
137
- throw new Error('Invalid password length');
138
+ (0, error_1.throwInvalidParam)('Invalid password length');
138
139
  }
139
140
  if (!/^[A-Za-z0-9]/.test(password)) {
140
- throw new Error('Password cannot start with special character');
141
+ (0, error_1.throwInvalidParam)('Password cannot start with special character');
141
142
  }
142
143
  const kinds = [
143
144
  /[a-z]/.test(password),
@@ -146,21 +147,21 @@ class UserService {
146
147
  /[()!@#$%^&*|?><_-]/.test(password)
147
148
  ].filter(Boolean).length;
148
149
  if (kinds < 3) {
149
- throw new Error('Password must include at least 3 character categories');
150
+ (0, error_1.throwInvalidParam)('Password must include at least 3 character categories');
150
151
  }
151
152
  }
152
153
  // 其他字段校验
153
154
  if (nickName && (nickName.length < 2 || nickName.length > 64)) {
154
- throw new Error('Invalid nickName');
155
+ (0, error_1.throwInvalidParam)('Invalid nickName');
155
156
  }
156
157
  if (description && description.length > 200) {
157
- throw new Error('Invalid description');
158
+ (0, error_1.throwInvalidParam)('Invalid description');
158
159
  }
159
160
  if (phone && !/^1\d{10}$/.test(phone)) {
160
- throw new Error('Invalid phone');
161
+ (0, error_1.throwInvalidParam)('Invalid phone');
161
162
  }
162
163
  if (email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
163
- throw new Error('Invalid email');
164
+ (0, error_1.throwInvalidParam)('Invalid email');
164
165
  }
165
166
  const reqData = {
166
167
  EnvId,
@@ -190,10 +191,10 @@ class UserService {
190
191
  const { EnvId } = this.environment.lazyEnvironmentConfig;
191
192
  const { pageNo = 1, pageSize = 20, name, nickName, phone, email, uidList } = options;
192
193
  if (!Number.isInteger(pageNo) || pageNo < 1) {
193
- throw new Error('Invalid pageNo');
194
+ (0, error_1.throwInvalidParam)('Invalid pageNo');
194
195
  }
195
196
  if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > 100) {
196
- throw new Error('Invalid pageSize');
197
+ (0, error_1.throwInvalidParam)('Invalid pageSize');
197
198
  }
198
199
  const reqData = {
199
200
  EnvId,
@@ -216,26 +217,26 @@ class UserService {
216
217
  const { EnvId } = this.environment.lazyEnvironmentConfig;
217
218
  const { uid, name, type, password, userStatus, nickName, phone, email, avatarUrl, description } = options;
218
219
  if (!this.isValidStr(uid) || uid.length > 64) {
219
- throw new Error('Invalid uid');
220
+ (0, error_1.throwInvalidParam)('Invalid uid');
220
221
  }
221
222
  // Name: 不传或空字符串 => 不修改;传了非空则校验
222
223
  if (name !== undefined && name !== '' && !/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(name)) {
223
- throw new Error('Invalid name');
224
+ (0, error_1.throwInvalidParam)('Invalid name');
224
225
  }
225
226
  // Type / UserStatus: 不传或空字符串 => 不修改;非空时白名单校验
226
- if (type !== undefined && !['internalUser', 'externalUser'].includes(type)) {
227
- throw new Error('Invalid type');
227
+ if (type && !['internalUser', 'externalUser'].includes(type)) {
228
+ (0, error_1.throwInvalidParam)('Invalid type');
228
229
  }
229
- if (userStatus !== undefined && !['ACTIVE', 'BLOCKED'].includes(userStatus)) {
230
- throw new Error('Invalid userStatus');
230
+ if (userStatus && !['ACTIVE', 'BLOCKED'].includes(userStatus)) {
231
+ (0, error_1.throwInvalidParam)('Invalid userStatus');
231
232
  }
232
233
  // Password: 不传或空字符串 => 不修改;非空时校验复杂度
233
234
  if (password !== undefined && password !== '') {
234
235
  if (password.length < 8 || password.length > 32) {
235
- throw new Error('Invalid password length');
236
+ (0, error_1.throwInvalidParam)('Invalid password length');
236
237
  }
237
238
  if (!/^[A-Za-z0-9]/.test(password)) {
238
- throw new Error('Password cannot start with special character');
239
+ (0, error_1.throwInvalidParam)('Password cannot start with special character');
239
240
  }
240
241
  const kinds = [
241
242
  /[a-z]/.test(password),
@@ -244,21 +245,21 @@ class UserService {
244
245
  /[()!@#$%^&*|?><_-]/.test(password)
245
246
  ].filter(Boolean).length;
246
247
  if (kinds < 3) {
247
- throw new Error('Password must include at least 3 character categories');
248
+ (0, error_1.throwInvalidParam)('Password must include at least 3 character categories');
248
249
  }
249
250
  }
250
251
  // 这些字段:不传 => 不修改;传空字符串 => 清空(允许)
251
252
  if (nickName !== undefined && nickName !== '' && (nickName.length < 2 || nickName.length > 64)) {
252
- throw new Error('Invalid nickName');
253
+ (0, error_1.throwInvalidParam)('Invalid nickName');
253
254
  }
254
255
  if (phone !== undefined && phone !== '' && !/^1\d{10}$/.test(phone)) {
255
- throw new Error('Invalid phone');
256
+ (0, error_1.throwInvalidParam)('Invalid phone');
256
257
  }
257
258
  if (email !== undefined && email !== '' && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
258
- throw new Error('Invalid email');
259
+ (0, error_1.throwInvalidParam)('Invalid email');
259
260
  }
260
261
  if (description !== undefined && description !== '' && description.length > 200) {
261
- throw new Error('Invalid description');
262
+ (0, error_1.throwInvalidParam)('Invalid description');
262
263
  }
263
264
  const reqData = {
264
265
  EnvId,
@@ -267,11 +268,11 @@ class UserService {
267
268
  // Name/Type/Password/UserStatus:空字符串视为不修改
268
269
  if (name !== undefined && name !== '')
269
270
  reqData.Name = name;
270
- if (type !== undefined)
271
+ if (type)
271
272
  reqData.Type = type;
272
273
  if (password !== undefined && password !== '')
273
274
  reqData.Password = password;
274
- if (userStatus !== undefined)
275
+ if (userStatus)
275
276
  reqData.UserStatus = userStatus;
276
277
  // 这些字段支持传空字符串清空:只要“传了”就下发
277
278
  if (Object.prototype.hasOwnProperty.call(options, 'nickName'))
@@ -290,14 +291,14 @@ class UserService {
290
291
  const { EnvId } = this.environment.lazyEnvironmentConfig;
291
292
  const { uids } = options;
292
293
  if (!Array.isArray(uids) || uids.length === 0) {
293
- throw new Error('uids is required');
294
+ (0, error_1.throwInvalidParam)('uids is required');
294
295
  }
295
296
  if (uids.length > 100) {
296
- throw new Error('uids length must be <= 100');
297
+ (0, error_1.throwInvalidParam)('uids length must be <= 100');
297
298
  }
298
299
  const invalidUid = uids.find(uid => !this.isValidStr(uid));
299
300
  if (invalidUid) {
300
- throw new Error('Invalid uid in uids');
301
+ (0, error_1.throwInvalidParam)('Invalid uid in uids');
301
302
  }
302
303
  return this.tcbService.request('DeleteUsers', {
303
304
  EnvId,
@@ -111,9 +111,12 @@ class CloudService {
111
111
  if (e.name === 'CloudBaseError') {
112
112
  throw e;
113
113
  }
114
+ // requestFn 注入模式:错误来自外部请求实现,保留 cause 链
114
115
  throw new error_1.CloudBaseError(e.message, {
115
116
  action,
116
- code: e.code
117
+ code: e.code,
118
+ original: e,
119
+ source: 'cloud-api'
117
120
  });
118
121
  }
119
122
  }
@@ -128,10 +131,10 @@ class CloudService {
128
131
  const envToken = (0, utils_1.getEnvVar)(constant_1.ENV_NAME.ENV_SESSIONTOKEN);
129
132
  if (!envSecretId || !envSecretKey) {
130
133
  if ((0, utils_1.getRuntime)() === constant_1.RUN_ENV.SCF) {
131
- throw new Error('missing authoration key, redeploy the function');
134
+ (0, error_1.throwCredentialError)('missing authorization key, redeploy the function');
132
135
  }
133
136
  else {
134
- throw new Error('missing secretId or secretKey of tencent cloud');
137
+ (0, error_1.throwCredentialError)('missing secretId or secretKey of tencent cloud');
135
138
  }
136
139
  }
137
140
  else {
@@ -146,11 +149,13 @@ class CloudService {
146
149
  try {
147
150
  const data = await this.requestWithSign();
148
151
  if (data.Response.Error) {
152
+ // 云 API 返回的错误:code 来自控制面官方错误码(876/34823),标记 source 便于分层追溯
149
153
  const tcError = new error_1.CloudBaseError(data.Response.Error.Message, {
150
154
  action,
151
155
  requestId: data.Response.RequestId,
152
156
  code: data.Response.Error.Code,
153
- original: data.Response.Error
157
+ original: data.Response.Error,
158
+ source: 'cloud-api'
154
159
  });
155
160
  throw tcError;
156
161
  }
@@ -163,9 +168,12 @@ class CloudService {
163
168
  throw e;
164
169
  }
165
170
  else {
171
+ // 请求过程异常(网络/签名等),非云 API 业务错误
166
172
  throw new error_1.CloudBaseError(e.message, {
167
173
  action,
168
- code: e.code
174
+ code: e.code,
175
+ original: e,
176
+ source: 'manager-node'
169
177
  });
170
178
  }
171
179
  }
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.cloudBaseRequest = cloudBaseRequest;
4
+ exports.assertDataPlaneOk = assertDataPlaneOk;
5
+ const error_1 = require("../error");
4
6
  const auth_1 = require("./auth");
5
7
  const http_request_1 = require("./http-request");
6
8
  const SUPPORT_REGIONS = ['ap-shanghai', 'ap-guangzhou', 'ap-singapore'];
@@ -66,3 +68,22 @@ async function cloudBaseRequest(options) {
66
68
  }, process.env.TCB_ADMIN_PROXY || proxy);
67
69
  return res;
68
70
  }
71
+ /**
72
+ * 校验数据面(/admin)响应,命中错误时抛出带 code 的 CloudBaseError。
73
+ *
74
+ * 必须把 `res.code` 落到 `CloudBaseError.code` 并标记 `source: 'runtime'`:
75
+ * 否则 CLI 侧 `resolveErrorDoc` 拿不到 code → 文档链接哑火,
76
+ * `resolveExitCode` 也会兜底成 1,灯塔上报退化为 `ERROR`。
77
+ * 这是「错误码治理方案」D11 的修复点。
78
+ */
79
+ function assertDataPlaneOk(res, action) {
80
+ if (!res || !res.code) {
81
+ return;
82
+ }
83
+ throw new error_1.CloudBaseError(`${res.code}: ${res.message || ''}`, {
84
+ code: res.code,
85
+ requestId: res.requestId,
86
+ source: 'runtime',
87
+ action
88
+ });
89
+ }
package/lib/utils/fs.js CHANGED
@@ -19,7 +19,7 @@ function checkFullAccess(dest, throwError = false) {
19
19
  }
20
20
  catch (e) {
21
21
  if (throwError) {
22
- throw new error_1.CloudBaseError(`路径不存在:${dest}`);
22
+ (0, error_1.throwNotFound)(`路径不存在:${dest}`);
23
23
  }
24
24
  return false;
25
25
  }
@@ -29,7 +29,7 @@ function checkFullAccess(dest, throwError = false) {
29
29
  }
30
30
  catch (e) {
31
31
  if (throwError) {
32
- throw new error_1.CloudBaseError(`路径无读写权限:${dest}`);
32
+ (0, error_1.throwFileError)(`路径无读写权限:${dest}`);
33
33
  }
34
34
  return false;
35
35
  }
@@ -46,7 +46,7 @@ function checkReadable(dest, throwError = false) {
46
46
  }
47
47
  catch (e) {
48
48
  if (throwError) {
49
- throw new error_1.CloudBaseError(`路径不存在或无读权限:${dest}`);
49
+ (0, error_1.throwNotFound)(`路径不存在或无读权限:${dest}`);
50
50
  }
51
51
  else {
52
52
  return false;
@@ -31,9 +31,9 @@ async function fetch(url, config = {}, proxy = null) {
31
31
  // 某些情况下回返回 HTML 文本异常
32
32
  // JSON 解析错误,抛出原响应文本
33
33
  if (e.name === 'SyntaxError') {
34
- throw new error_1.CloudBaseError(text);
34
+ (0, error_1.throwApiError)(text);
35
35
  }
36
- throw new error_1.CloudBaseError(e);
36
+ (0, error_1.throwApiError)(e.message || String(e));
37
37
  }
38
38
  return json;
39
39
  }
@@ -39,6 +39,7 @@ const path_1 = __importDefault(require("path"));
39
39
  const unzipper_1 = __importDefault(require("unzipper"));
40
40
  const constant_1 = require("../constant");
41
41
  const http_request_1 = require("./http-request");
42
+ const error_1 = require("../error");
42
43
  __exportStar(require("./auth"), exports);
43
44
  __exportStar(require("./cloud-api-request"), exports);
44
45
  __exportStar(require("./cloudbase-request"), exports);
@@ -172,10 +173,10 @@ function shouldSkipEntry(entryPath) {
172
173
  async function downloadAndExtractRemoteZip(downloadUrl, targetPath) {
173
174
  const downloadResponse = await (0, http_request_1.fetchStream)(downloadUrl);
174
175
  if (!downloadResponse.ok) {
175
- throw new Error(`下载失败,状态码: ${downloadResponse.status}`);
176
+ (0, error_1.throwNetError)(`下载失败,状态码: ${downloadResponse.status}`);
176
177
  }
177
178
  if (!downloadResponse.body) {
178
- throw new Error('下载响应中没有数据流');
179
+ (0, error_1.throwNetError)('下载响应中没有数据流');
179
180
  }
180
181
  // 创建目标目录
181
182
  const dirPath = path_1.default.resolve(targetPath);
@@ -264,7 +265,7 @@ function upload({ url, file, method = 'POST', headers = {}, withCredentials = fa
264
265
  // If parsing fails, keep data as null
265
266
  }
266
267
  if (!response.ok) {
267
- throw new Error(`${response.status} ${response.statusText} HTTP ERROR`);
268
+ (0, error_1.throwNetError)(`${response.status} ${response.statusText} HTTP ERROR`);
268
269
  }
269
270
  return dataMeta;
270
271
  })
@@ -39,11 +39,11 @@ function resolveStorageConfig(envConfig) {
39
39
  var _a;
40
40
  // 环境信息由带 @preLazy 的方法首次调用时加载,直接调用 getStorageConfig 可能早于加载
41
41
  if (!envConfig) {
42
- throw new error_1.CloudBaseError('环境信息尚未加载,无法获取存储配置,请先调用任一存储方法(如 listDirectoryFiles)或 environment.lazyInit() 完成初始化');
42
+ (0, error_1.throwApiError)('环境信息尚未加载,无法获取存储配置,请先调用任一存储方法(如 listDirectoryFiles)或 environment.lazyInit() 完成初始化');
43
43
  }
44
44
  const storage = (_a = envConfig.Storages) === null || _a === void 0 ? void 0 : _a[0];
45
45
  if (!storage) {
46
- throw new error_1.CloudBaseError(`环境 ${envConfig === null || envConfig === void 0 ? void 0 : envConfig.EnvId} 未查询到云存储资源(Storages 为空),请确认环境 ID 正确且环境已开通云存储`);
46
+ (0, error_1.throwNotFound)(`环境 ${envConfig === null || envConfig === void 0 ? void 0 : envConfig.EnvId} 未查询到云存储资源(Storages 为空),请确认环境 ID 正确且环境已开通云存储`);
47
47
  }
48
48
  return Object.assign({ env: envConfig.EnvId }, resolveBucketConfig({
49
49
  envId: envConfig.EnvId,
@@ -67,7 +67,7 @@ function resolveBucketConfig(options) {
67
67
  const effectiveBucket = bucket || externalStorage.BucketName;
68
68
  // 线上存在已启用外部存储但未返回桶名的环境,继续执行会在 COS 调用时报 missing param Bucket
69
69
  if (!effectiveBucket) {
70
- throw new error_1.CloudBaseError(`环境 ${envId} 已启用外部存储(ExternalStorage),但未返回存储桶名称(BucketName),无法访问存储桶,请联系平台检查该环境的共享桶配置`);
70
+ (0, error_1.throwNotFound)(`环境 ${envId} 已启用外部存储(ExternalStorage),但未返回存储桶名称(BucketName),无法访问存储桶,请联系平台检查该环境的共享桶配置`);
71
71
  }
72
72
  return {
73
73
  bucket: effectiveBucket,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudbase/manager-node",
3
- "version": "5.8.9-beta.1",
3
+ "version": "5.9.1",
4
4
  "description": "The node manage service api for cloudbase.",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -42,7 +42,7 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "@cloudbase/signature-nodejs": "^1.0.0",
45
- "@cloudbase/toolbox": "~0.8.4",
45
+ "@cloudbase/toolbox": "~0.9.0",
46
46
  "@cloudbase/database": "^0.6.2",
47
47
  "archiver": "^3.1.1",
48
48
  "cos-nodejs-sdk-v5": "^2.14.0",
@@ -0,0 +1,57 @@
1
+ /**
2
+ * 错误码 → 文档链接 映射
3
+ *
4
+ * 用途:报错时自动关联到对应的错误码指引文档,让用户能直接跳转查看。
5
+ * 在 CloudBaseError 构造与 errorHandler 汇聚处统一调用 resolveErrorDoc(code, source),
6
+ * 避免在每个命令里散落手写 docs 链接(容易覆盖不全、口径不一致)。
7
+ *
8
+ * ## 来源分流(source)
9
+ *
10
+ * 消费方收到四类来源的 code,必须按来源选择不同的文档:
11
+ *
12
+ * | source | 含义 | 文档 |
13
+ * |---|---|---|
14
+ * | `cloud-api` | 控制面云 API 原始 code(876/34823) | 腾讯云《云开发 CloudBase 错误码》 |
15
+ * | `runtime` | 数据面错误码(`tcberror.go`,源自 `error-code/basic`) | 单码页 `error-code/{CODE}` |
16
+ * | `manager-node` | SDK 本地校验/构建自定义 code | 部署模块页(未登记时软降级) |
17
+ * | `cli` / `toolbox` | CLI / toolbox 本地语义码 | 待登记 |
18
+ *
19
+ * **为什么必须分流**:CLI 本地码与数据面码大量重名(`INVALID_PARAM` / `INVALID_OPERATION` /
20
+ * `RESOURCE_NOT_FOUND` / `CHECK_LOGIN_FAILED` / `FUNCTION_NOT_FOUND` 共 5 个),
21
+ * 单靠 code 无法判断该指向哪张文档。这是 AIP-193 `(reason, domain)` 二元组的落地
22
+ * (见《错误码治理方案》§4.3)。同时也解释了为何不能把 `INVALID_` / `AUTH_`
23
+ * 这类宽泛前缀加进「无 source」的宽松匹配路径。
24
+ *
25
+ * ## 数据面码表来源
26
+ *
27
+ * `RUNTIME_ERROR_CODES` 逐条摘录自 `cloudbase-docs/docs/error-code/basic.md`,
28
+ * 与服务端事实源 `tcblinker/internal/tcberror/tcberror.go` 对齐。
29
+ * 建议在 CI 中加入与 `basic.md` 的 diff 校验(见治理方案 §6.4),防止再次漂移。
30
+ */
31
+ import type { ErrorSource } from './error';
32
+ /** 控制面云 API 错误码文档(腾讯云云开发 CloudBase 错误码) */
33
+ export declare const CONTROL_PLANE_ERROR_DOC = "https://cloud.tencent.com/document/product/876/34823";
34
+ /** 数据面错误码总览页(分类索引) */
35
+ export declare const RUNTIME_ERROR_DOC = "https://docs.cloudbase.net/error-code/basic";
36
+ /** 数据面单码页 URL 前缀:最终形如 `${BASE}/${CODE}` */
37
+ export declare const RUNTIME_ERROR_CODE_DOC_BASE = "https://docs.cloudbase.net/error-code";
38
+ /**
39
+ * 数据面(运行时)错误码全量清单。
40
+ *
41
+ * 来源:`cloudbase-docs/docs/error-code/basic.md`(131 个),
42
+ * 与服务端事实源 `tcblinker/internal/tcberror/tcberror.go` 对齐。
43
+ * 每个码在文档站都有独立页面:`https://docs.cloudbase.net/error-code/{CODE}`。
44
+ *
45
+ * ⚠️ 仅在 `source === 'runtime'` 时使用本表。因为其中 `INVALID_*`(21 个)、
46
+ * `AUTH_*` 等与 CLI 本地语义码重名,无 source 时不可参与匹配。
47
+ */
48
+ export declare const RUNTIME_ERROR_CODES: Set<string>;
49
+ /**
50
+ * 根据错误码解析对应的文档链接。
51
+ *
52
+ * @param code 错误码(可能是语义 code、云 API code,或带 '.' 的子错误码)
53
+ * @param source 错误来源层(见 ErrorSource)。传入时按来源精确分流;
54
+ * 不传时保持历史宽松匹配(控制面精确 + 6 个模块前缀),向后兼容。
55
+ * @returns 文档链接;无法归类时返回空串(由调用方决定是否兜底)
56
+ */
57
+ export declare function resolveErrorDoc(code?: string | number, source?: ErrorSource | ''): string;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * 抛错「标准错误类型」表(manager-node 层)
3
+ *
4
+ * ⚠️ **自有码的唯一来源**:code 字面量只允许出现在本文件(helper 与显式 throw 都引用这里)。
5
+ * source **不在本表**:由各仓构造器按本层注入(避免跨仓复制时串层)。
6
+ *
7
+ * 用法(两种写法语义等价):
8
+ * throw new CloudBaseError(message, { type: ERROR_TYPES.invalidParam }) // 显式 throw ✓
9
+ * throwInvalidParam(message) // 短形态 ✓
10
+ */
11
+ export declare const ERROR_TYPES: {
12
+ readonly invalidParam: {
13
+ readonly code: "INVALID_PARAM";
14
+ };
15
+ readonly notFound: {
16
+ readonly code: "NOT_FOUND";
17
+ };
18
+ readonly netError: {
19
+ readonly code: "NETWORK_ERROR";
20
+ };
21
+ readonly apiError: {
22
+ readonly code: "API_ERROR";
23
+ };
24
+ readonly credentialError: {
25
+ readonly code: "MISSING_CREDENTIAL";
26
+ };
27
+ readonly fileError: {
28
+ readonly code: "FILE_READ_ERROR";
29
+ };
30
+ };
31
+ export type ErrorTypeName = keyof typeof ERROR_TYPES;
32
+ export type ErrorType = (typeof ERROR_TYPES)[ErrorTypeName];
package/types/error.d.ts CHANGED
@@ -1,13 +1,60 @@
1
+ import { ErrorType } from './error-types';
2
+ export type ErrorSource = 'cloud-api' | 'runtime' | 'manager-node' | 'cli' | 'toolbox';
3
+ /**
4
+ * `error.meta.failures` 的单项(**多项失败的统一表达**)。
5
+ *
6
+ * 用于**多项失败**场景:`code` 只能承载**一个**码(兼容既有消费方),其余失败项
7
+ * 的码会在结构层面丢失 —— 埋点因此统计不到真实分布。`meta.failures` 承载**全量**失败项。
8
+ *
9
+ * ⚠️ 它不是「替代 code」:`code` 仍取第一项,`meta.failures` 是**附加**信息。
10
+ * ⚠️ 与 `meta` 的其他 key 一样,新增字段必须 ① 抽具名 interface ② 补文档登记表 ③ 补契约测试。
11
+ */
12
+ export interface IMetaFailure {
13
+ /** 精确错误码 */
14
+ code: string;
15
+ /** 简述(同 ICheckResult.summary) */
16
+ summary?: string;
17
+ /** 可执行的修复建议(同 ICheckResult.remediation) */
18
+ remediation?: string;
19
+ }
1
20
  interface Options {
2
- exit?: number;
3
21
  original?: Error | undefined;
4
22
  code?: string | number;
23
+ /** 标准错误类型:自有码的唯一来源;传入后 code / source 由它决定,且**不可被调用方覆盖** */
24
+ type?: ErrorType;
5
25
  requestId?: string;
6
26
  action?: string;
7
27
  meta?: Record<string, any>;
28
+ /** 错误来源层(对齐 CLI CloudBaseError.source) */
29
+ source?: ErrorSource;
30
+ /** 文档链接覆盖(缺省按 `(code, source)` 派生) */
31
+ docs?: string;
8
32
  }
33
+ /**
34
+ * ## `code` 与 `source` 的两条硬规则(2026-09-20 确立)
35
+ *
36
+ * **规则 1:原本无 `code` → 必须补;已有 `code` → 一律不变。**
37
+ * `code` 是机器契约(驱动消费层 `exitCode`、在 `--json` 暴露、是埋点与文档锚点的键),
38
+ * 改它等于破坏下游判断。
39
+ *
40
+ * > **批量改 `code` 前必跑「已发布影响审计」**:被改动的**原始行**是否存在于**上一个发布 tag**
41
+ * > 的同名文件中 ⇒ 判断是否动了**已发布给用户**的行为。
42
+ *
43
+ * **规则 2:`source` 必须与 `code` 的`归属`配套。**
44
+ * - 包装点**自造** `code`(本层语义)→ `source: 'manager-node'`(本层即 domain)
45
+ * - 包装点**沿用上游** `code`(`e.code || '<FALLBACK>'`)→ `source` 继承上游来源层
46
+ *
47
+ * 反例(禁止):自造的 `code` 配上**上游**的 `source` —— `(reason, domain)` 二元组失真(AIP-193)。
48
+ */
49
+ /**
50
+ * CloudBase SDK(manager-node)标准化错误类。
51
+ *
52
+ * 对齐 CLI 的 CloudBaseError 对象模型:code(稳定语义码)+ message(调试信息)+
53
+ * source(来源层)+ original(cause 链)+ requestId/action(诊断信息)+
54
+ * meta(结构化载荷 —— 多项失败明细见 `meta.failures`)。
55
+ * 差异:SDK 层无 exitCode(进程退出码是 CLI 概念),message 自动加 [action] 前缀(CLI 依赖)。
56
+ */
9
57
  export declare class CloudBaseError extends Error {
10
- readonly exit: number;
11
58
  readonly message: string;
12
59
  readonly name = "CloudBaseError";
13
60
  readonly original: Error | undefined;
@@ -15,6 +62,105 @@ export declare class CloudBaseError extends Error {
15
62
  readonly requestId: string;
16
63
  readonly action: string;
17
64
  readonly meta: Record<string, any> | undefined;
65
+ readonly source: ErrorSource | '';
66
+ /** 错误码文档链接(按 `(code, source)` 派生;`opts.docs` 可覆盖) */
67
+ readonly docs: string;
18
68
  constructor(message: string, options?: Options);
69
+ /**
70
+ * 标准 `Error.cause` 视图(ES2022)。
71
+ *
72
+ * 与 `original` **同源**(不重复存储,仅暴露标准视图):
73
+ * - `original` **保留**,因为已有 8 处调用点依赖它访问上游 SDK 错误
74
+ * (`.Code` / `.Message` / `.RequestId` / `.Response`);
75
+ * - 额外提供 `cause` 是为了对齐标准 —— Node 等工具链会**自动打印 cause 链**,
76
+ * 且与 `AggregateError` 兼容。
77
+ */
78
+ get cause(): Error | undefined;
79
+ /**
80
+ * 转为 JSON 错误结构(对齐 CLI CloudBaseError.toJSON)
81
+ * 格式:{ error: { code, message, stack?, requestId?, action?, source?, docs?, meta? } }
82
+ */
83
+ toJSON(): Record<string, unknown>;
19
84
  }
85
+ /**
86
+ * 判断一个值是否为 CloudBaseError(**跨仓 / 跨进程安全**)。
87
+ *
88
+ * ⚠️ **不要用 `instanceof CloudBaseError` 做跨仓判断**:CLI / manager-node / toolbox
89
+ * 各有**独立的** CloudBaseError 类(三个不同的构造函数),跨仓时 `instanceof`
90
+ * 必然为 false;本仓为第三方消费方保留了 ES5 原型兜底(`Object.setPrototypeOf`,见构造函数),
91
+ * 降级编译下同仓内 `instanceof` 也可能失效。因此判断**只依赖结构特征**:
92
+ *
93
+ * 1. 是 Error 实例
94
+ * 2. `name === 'CloudBaseError'`
95
+ * 3. 有 `toJSON()` 方法
96
+ *
97
+ * ⚠️ **不要依赖其他字段**(`requestId` / `source` / `params` / `original` …)做判断 ——
98
+ * 它们都是**按需可选字段**,未来增删不应影响「是不是 CloudBaseError」。
99
+ *
100
+ * 三仓各持一份**同名同实现**的副本(约定相同,不共享包)。
101
+ */
102
+ export declare function isCloudBaseError(value: unknown): value is CloudBaseError;
103
+ /**
104
+ * 判断错误码是否满足**正规化**规则(**我们自定义的码**适用)。
105
+ *
106
+ * 规则(对齐 AIP-193 `ErrorInfo.reason`):
107
+ * 1. `UPPER_SNAKE_CASE` —— `^[A-Z][A-Z0-9_]*$`
108
+ * 2. 长度 ≤ **63** 字符
109
+ * 3. **不以 `_` 结尾**(`_` 结尾表示「前缀」,不是完整码)
110
+ *
111
+ * ⚠️ **仅适用于我们自定义的码**(`source` 为 `manager-node` / `cli` / `toolbox`)。
112
+ * **平台码不适用**:控制面是驼峰(如 `InvalidParameter`),数据面由平台规范约束。
113
+ *
114
+ * 用法:单测断言码表全员合规;新增码时先过这里,再落库。
115
+ */
116
+ export declare function isValidErrorCode(code: unknown): code is string;
117
+ /**
118
+ * 判断值是否属于 `ErrorSource` 闭集。
119
+ *
120
+ * 存在的理由:`source` 在**跨仓**传递时是普通字符串(另一仓的声明无法约束本仓类型),
121
+ * 直接 `return e.source` 会丢类型 ⇒ 用守卫**收窄**,而不是 `as` 强转。
122
+ */
123
+ export declare function isErrorSource(value: unknown): value is ErrorSource;
124
+ /**
125
+ * **重包装时继承上游错误的来源层**(R3:`source` 与 `code` 归属配套)。
126
+ *
127
+ * - 上游错误带**合法** `source` ⇒ 沿用(`code` 也应同时沿用它,不可自造)
128
+ * - 否则 ⇒ 落到**本层** `'manager-node'`
129
+ *
130
+ * ⚠️ 只在**沿用上游 `code`** 时使用;自造 `code` 时必须硬写本层 `source`。
131
+ */
132
+ export declare function errorSourceOf(e: unknown): ErrorSource;
133
+ /**
134
+ * 参数校验错误:统一 code = INVALID_PARAM + source = manager-node。
135
+ *
136
+ * 用于替换散落的 `throw new Error('参数校验消息')`,让参数校验类错误
137
+ * 带上稳定 code(CLI 据此映射 exitCode=3)与来源标识,而非裸 Error 兜底。
138
+ */
139
+ export declare function throwInvalidParam(message: string): never;
140
+ /**
141
+ * 资源不存在错误:统一 code = NOT_FOUND + source = manager-node。
142
+ * 用于替换 `throw new Error('xxx 不存在')` 类错误。
143
+ */
144
+ export declare function throwNotFound(message: string): never;
145
+ /**
146
+ * 网络/请求错误:统一 code = NETWORK_ERROR + source = manager-node。
147
+ * 用于替换 `throw new Error('下载失败' / 'HTTP ERROR')` 类错误。
148
+ */
149
+ export declare function throwNetError(message: string): never;
150
+ /**
151
+ * 服务端/业务操作错误:统一 code = API_ERROR + source = manager-node。
152
+ * 用于替换「服务端返回异常 / 业务状态不满足」类错误。
153
+ */
154
+ export declare function throwApiError(message: string): never;
155
+ /**
156
+ * 缺凭证错误:统一 code = MISSING_CREDENTIAL + source = manager-node。
157
+ * 用于替换 `throw new Error('missing secretId ...')` 类错误(SDK 初始化缺密钥)。
158
+ */
159
+ export declare function throwCredentialError(message: string): never;
160
+ /**
161
+ * 本地文件读写错误:统一 code = FILE_READ_ERROR + source = manager-node。
162
+ * 用于替换本地文件系统操作(读取 / 写入 / 计算摘要 / 路径处理)失败类错误。
163
+ * 与 CLI / toolbox 的同名 helper 一致(三仓同名同语义)。
164
+ */
165
+ export declare function throwFileError(message: string): never;
20
166
  export {};