@cloudbase/manager-node 5.8.9-beta.0 → 5.9.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.
Files changed (49) hide show
  1. package/.codebuddy/rules/error-code-contract.mdc +13 -0
  2. package/CHANGELOG.md +1 -0
  3. package/lib/agent/index.js +6 -5
  4. package/lib/aiModel/index.js +21 -9
  5. package/lib/cloudApp/index.js +4 -4
  6. package/lib/cloudrun/index.js +16 -15
  7. package/lib/common/index.js +1 -1
  8. package/lib/database/index.js +23 -23
  9. package/lib/deploy/DatabaseDeployer.js +19 -24
  10. package/lib/deploy/DeployOrchestrator.js +2 -2
  11. package/lib/deploy/FunctionDeployer.js +19 -1
  12. package/lib/deploy/GatewayDeployer.js +5 -5
  13. package/lib/deploy/StaticDeployer.js +2 -2
  14. package/lib/deploy/framework.js +2 -1
  15. package/lib/deploy/function/config-guard.js +23 -3
  16. package/lib/deploy/function/enterprise-tcr.js +18 -2
  17. package/lib/docs/index.js +4 -3
  18. package/lib/env/index.js +16 -16
  19. package/lib/environment.js +4 -4
  20. package/lib/environmentManager.js +5 -1
  21. package/lib/error-docs.js +385 -0
  22. package/lib/error-types.js +21 -0
  23. package/lib/error.js +239 -2
  24. package/lib/function/index.js +135 -41
  25. package/lib/function/packer.js +1 -1
  26. package/lib/hosting/index.js +1 -1
  27. package/lib/index.js +2 -1
  28. package/lib/integration/crypto.js +4 -3
  29. package/lib/integration/index.js +1 -1
  30. package/lib/integration/secretKey.js +4 -4
  31. package/lib/monitor/index.js +1 -1
  32. package/lib/mysql/index.js +2 -1
  33. package/lib/permission/index.js +56 -49
  34. package/lib/platform/index.js +2 -2
  35. package/lib/sandbox/index.js +68 -61
  36. package/lib/storage/index.js +164 -162
  37. package/lib/user/index.js +32 -31
  38. package/lib/utils/cloud-api-request.js +13 -5
  39. package/lib/utils/cloudbase-request.js +21 -0
  40. package/lib/utils/fs.js +3 -3
  41. package/lib/utils/http-request.js +2 -2
  42. package/lib/utils/index.js +4 -3
  43. package/lib/utils/storage-config.js +3 -3
  44. package/package.json +2 -2
  45. package/types/error-docs.d.ts +57 -0
  46. package/types/error-types.d.ts +32 -0
  47. package/types/error.d.ts +148 -2
  48. package/types/function/index.d.ts +14 -1
  49. package/types/utils/cloudbase-request.d.ts +22 -0
@@ -10,12 +10,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.FunctionService = exports.ENTERPRISE_IMAGE_REGION_GUIDANCE = void 0;
13
+ exports.formatImageDeploymentErrorMessage = formatImageDeploymentErrorMessage;
13
14
  const fs_1 = __importDefault(require("fs"));
14
15
  const path_1 = __importDefault(require("path"));
15
16
  const lodash_1 = __importDefault(require("lodash"));
16
17
  const cos_nodejs_sdk_v5_1 = __importDefault(require("cos-nodejs-sdk-v5"));
17
18
  const packer_1 = require("./packer");
18
19
  const error_1 = require("../error");
20
+ const error_types_1 = require("../error-types");
19
21
  const utils_1 = require("../utils");
20
22
  const constant_1 = require("../constant");
21
23
  /**
@@ -115,23 +117,46 @@ function getFieldIgnoreCase(obj, fieldName) {
115
117
  }
116
118
  /** 企业版 TCR 镜像对 SCF 不可见时的排查指引 */
117
119
  exports.ENTERPRISE_IMAGE_REGION_GUIDANCE = '请确认 TCR 企业版实例已在当前函数地域提供可访问的实例或复制实例,并检查 RegistryId、镜像地址及拉取权限。';
120
+ /**
121
+ * 幂等缓存:错误对象 → 已增强的文本。
122
+ *
123
+ * 为什么不用「给错误对象挂标记位 / 写回 message」:
124
+ * 1) **不改入参** ⇒ 无副作用,不污染调用方传入的数据源;
125
+ * 2) 冻结 / 只读 / 不可扩展对象同样有效(此前要靠 try/catch 兜底);
126
+ * 3) WeakMap 为弱引用 ⇒ 不阻碍错误对象被回收。
127
+ */
128
+ const ENHANCED_MESSAGES = new WeakMap();
129
+ /** 取出可安全取属性的错误对象(null / 原始值 ⇒ undefined) */
130
+ function errorCarrierOf(error) {
131
+ return typeof error === 'object' && error !== null ? error : undefined;
132
+ }
118
133
  /**
119
134
  * 为企业版镜像的典型不可见错误补充排查指引,同时保留原始错误和 requestId。
120
135
  * 仅匹配 SCF 已进入 enterprise 镜像查询阶段的错误,避免给普通网络或鉴权错误误导提示。
136
+ *
137
+ * 纯函数:只读入参,**不修改**传进来的错误对象;幂等由 WeakMap 缓存保证。
121
138
  */
122
139
  function formatImageDeploymentErrorMessage(error, imageConfig) {
123
- const message = typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' ? error.message : String(error);
124
- const code = typeof (error === null || error === void 0 ? void 0 : error.code) === 'string' ? error.code : '';
140
+ const carrier = errorCarrierOf(error);
141
+ const message = typeof (carrier === null || carrier === void 0 ? void 0 : carrier.message) === 'string' ? carrier.message : String(error);
142
+ const code = typeof (carrier === null || carrier === void 0 ? void 0 : carrier.code) === 'string' ? carrier.code : '';
125
143
  const haystack = `${code} ${message}`.toLowerCase();
126
144
  const isEnterprise = (imageConfig === null || imageConfig === void 0 ? void 0 : imageConfig.imageType) === 'enterprise';
127
145
  const isEnterpriseImageNotFound = haystack.includes('resourcenotfound.image') ||
128
146
  haystack.includes('describe images enterprise resource not found');
129
- if (!isEnterprise ||
130
- !isEnterpriseImageNotFound ||
131
- message.includes(exports.ENTERPRISE_IMAGE_REGION_GUIDANCE)) {
147
+ // 幂等只看缓存,不看文本(文本被中间层改动也不影响判定)
148
+ const applied = carrier ? ENHANCED_MESSAGES.get(carrier) : undefined;
149
+ if (applied !== undefined) {
150
+ return applied;
151
+ }
152
+ if (!isEnterprise || !isEnterpriseImageNotFound) {
132
153
  return message;
133
154
  }
134
- return `${message}\n提示:${exports.ENTERPRISE_IMAGE_REGION_GUIDANCE}`;
155
+ const enhanced = `${message}\n提示:${exports.ENTERPRISE_IMAGE_REGION_GUIDANCE}`;
156
+ if (carrier) {
157
+ ENHANCED_MESSAGES.set(carrier, enhanced);
158
+ }
159
+ return enhanced;
135
160
  }
136
161
  // 解析函数配置,换成请求参数(用于 CreateFunction)
137
162
  function configToParams(options) {
@@ -267,6 +292,63 @@ function configToParams(options) {
267
292
  const runtime = params.Runtime || 'Nodejs20.19';
268
293
  return Object.assign({ Handler: func.handler || 'index.main', Runtime: 'Nodejs20.19', MemorySize: 256, InstallDependency: isNodeFunction(runtime) ? 'TRUE' : 'FALSE' }, params);
269
294
  }
295
+ /**
296
+ * 判断错误是否**可重试**(AIP-194:只重试 `UNAVAILABLE` 类瞬时错误)。
297
+ *
298
+ * ⚠️ 默认**不可重试**(default-deny):权限不足、参数非法、资源冲突等**确定性失败**立即抛出 ——
299
+ * 对它们重试只会浪费时间,更会把「必然失败」伪装成「偶发失败」,掩盖真实原因。
300
+ */
301
+ /** 安全取字符串字段(不依赖 `as any` 断言 —— 代码评审:类型安全) */
302
+ function pickStringField(value, key) {
303
+ if (typeof value !== 'object' || value === null) {
304
+ return '';
305
+ }
306
+ const field = value[key];
307
+ return typeof field === 'string' ? field : '';
308
+ }
309
+ /** 可重试的**精确错误码**(小写比较;AIP-194 的 UNAVAILABLE 家族 + 本层网络 errno) */
310
+ const RETRYABLE_ERROR_CODES = new Set([
311
+ 'resourceunavailable',
312
+ 'serviceunavailable',
313
+ 'internalerror',
314
+ 'requestlimitexceeded',
315
+ 'etimedout',
316
+ 'econnreset',
317
+ 'econnrefused'
318
+ ]);
319
+ /** 仅在 ① 未命中时用于**文案**回退的瞬时信号(不含 `not found` 等语义模糊项) */
320
+ const RETRYABLE_ERROR_TEXT_HINTS = [
321
+ 'unavailable',
322
+ 'internal error',
323
+ 'timeout',
324
+ 'connection reset',
325
+ 'network error'
326
+ ];
327
+ function isRetryableError(e) {
328
+ const code = pickStringField(e, 'code').toLowerCase();
329
+ const message = pickStringField(e, 'message').toLowerCase();
330
+ // ① 精确码优先(含 `ResourceUnavailable.xxx` 这类带子码的形态)
331
+ if (RETRYABLE_ERROR_CODES.has(code) || RETRYABLE_ERROR_CODES.has(code.split('.').pop() || '')) {
332
+ return true;
333
+ }
334
+ // ② 退一步只看瞬时信号文案
335
+ return RETRYABLE_ERROR_TEXT_HINTS.some(hint => message.includes(hint));
336
+ }
337
+ /**
338
+ * 判断错误是否为**创建/更新期的「资源尚不可见」**(NOT_FOUND 家族)。
339
+ *
340
+ * 场景:`CreateFunction` 已提交成功,但 `GetFunction` 短时间内仍返回 NOT_FOUND。
341
+ * 这类错误应**容忍并继续轮询**,而不是把正常的「创建中」误报为失败。
342
+ *
343
+ * ⚠️ 与 `isRetryableError` 分工不同:NOT_FOUND 的**竞态容忍**只在这里(轮询场景),
344
+ * 不进「可重试」白名单 —— 避免把「资源 ID 写错」这类确定性失败也当成可重试。
345
+ */
346
+ function isNotYetVisibleError(e) {
347
+ const code = pickStringField(e, 'code').toLowerCase();
348
+ const message = pickStringField(e, 'message').toLowerCase();
349
+ const haystack = `${code} ${message}`;
350
+ return haystack.includes('notfound') || haystack.includes('not_found') || haystack.includes('not found');
351
+ }
270
352
  class FunctionService {
271
353
  constructor(environment) {
272
354
  this.environment = environment;
@@ -307,7 +389,7 @@ class FunctionService {
307
389
  await packer.build();
308
390
  base64 = await packer.getBase64Code();
309
391
  if (!base64) {
310
- throw new error_1.CloudBaseError('函数不存在!');
392
+ (0, error_1.throwNotFound)('函数不存在!');
311
393
  }
312
394
  params.AddFiles = base64;
313
395
  }
@@ -357,7 +439,7 @@ class FunctionService {
357
439
  params.Code = customCode;
358
440
  }
359
441
  if (!((_c = (_b = params.Code) === null || _b === void 0 ? void 0 : _b.ImageConfig) === null || _c === void 0 ? void 0 : _c.ImageUri)) {
360
- throw new error_1.CloudBaseError('镜像部署需要配置 imageConfig.imageUri');
442
+ (0, error_1.throwInvalidParam)('镜像部署需要配置 imageConfig.imageUri');
361
443
  }
362
444
  // 镜像函数不需要 Handler / InstallDependency
363
445
  delete params.Handler;
@@ -544,7 +626,7 @@ class FunctionService {
544
626
  currentOffset += pageSize;
545
627
  }
546
628
  catch (error) {
547
- throw new error_1.CloudBaseError(`获取函数列表失败: ${error.message}`);
629
+ (0, error_1.throwApiError)(`获取函数列表失败: ${error.message}`);
548
630
  }
549
631
  }
550
632
  // 格式化数据
@@ -601,7 +683,7 @@ class FunctionService {
601
683
  (0, utils_1.successLog)(`[${name}] 函数删除成功!`);
602
684
  }
603
685
  catch (e) {
604
- throw new error_1.CloudBaseError(e.message);
686
+ (0, error_1.throwApiError)(e.message);
605
687
  }
606
688
  })());
607
689
  await Promise.all(promises);
@@ -664,7 +746,7 @@ class FunctionService {
664
746
  data.push(info);
665
747
  }
666
748
  catch (e) {
667
- throw new error_1.CloudBaseError(`${name} 获取信息失败:${e.message}`);
749
+ (0, error_1.throwApiError)(`${name} 获取信息失败:${e.message}`);
668
750
  }
669
751
  })());
670
752
  await Promise.all(promises);
@@ -686,7 +768,7 @@ class FunctionService {
686
768
  * @returns {Promise<IFunctionLogRes>}
687
769
  */
688
770
  async getFunctionLogs(_options) {
689
- throw new error_1.CloudBaseError('getFunctionLogs 已废弃:底层 GetFunctionLogs 接口已下线,请使用 env.getLogService().searchClsLog() 查询云函数日志');
771
+ (0, error_1.throwApiError)('getFunctionLogs 已废弃:底层 GetFunctionLogs 接口已下线,请使用 env.getLogService().searchClsLog() 查询云函数日志');
690
772
  }
691
773
  /**
692
774
  * 获取函数日志 ID 列表
@@ -703,7 +785,7 @@ class FunctionService {
703
785
  * @returns {Promise<IFunctionLogResV2>}
704
786
  */
705
787
  async getFunctionLogsV2(_options) {
706
- throw new error_1.CloudBaseError('getFunctionLogsV2 已废弃:底层 GetFunctionLogs 接口已下线,请使用 env.getLogService().searchClsLog() 查询云函数日志');
788
+ (0, error_1.throwApiError)('getFunctionLogsV2 已废弃:底层 GetFunctionLogs 接口已下线,请使用 env.getLogService().searchClsLog() 查询云函数日志');
707
789
  }
708
790
  /**
709
791
  * 根据函数日志 ID 查询日志详情
@@ -716,7 +798,7 @@ class FunctionService {
716
798
  * @returns {Promise<IFunctionLogDetailRes>}
717
799
  */
718
800
  async getFunctionLogDetail(_options) {
719
- throw new error_1.CloudBaseError('getFunctionLogDetail 已废弃:底层 GetFunctionLogDetail 接口已下线,请使用 env.getLogService().searchClsLog() 查询云函数日志');
801
+ (0, error_1.throwApiError)('getFunctionLogDetail 已废弃:底层 GetFunctionLogDetail 接口已下线,请使用 env.getLogService().searchClsLog() 查询云函数日志');
720
802
  }
721
803
  /**
722
804
  * 获取函数的完整调用日志
@@ -726,7 +808,7 @@ class FunctionService {
726
808
  * @returns {Promise<IFunctionLogDetailRes[]>} 返回包含完整日志详情的数组
727
809
  */
728
810
  async getCompleteFunctionLogs(_options) {
729
- throw new error_1.CloudBaseError('getCompleteFunctionLogs 已废弃:底层 GetFunctionLogs/GetFunctionLogDetail 接口已下线,请使用 env.getLogService().searchClsLog() 查询云函数日志');
811
+ (0, error_1.throwApiError)('getCompleteFunctionLogs 已废弃:底层 GetFunctionLogs/GetFunctionLogDetail 接口已下线,请使用 env.getLogService().searchClsLog() 查询云函数日志');
730
812
  }
731
813
  /**
732
814
  * 更新云函数配置
@@ -885,7 +967,7 @@ class FunctionService {
885
967
  }
886
968
  };
887
969
  if (!params.Code.ImageConfig.ImageUri) {
888
- throw new error_1.CloudBaseError('镜像部署需要配置 imageConfig.imageUri');
970
+ (0, error_1.throwInvalidParam)('镜像部署需要配置 imageConfig.imageUri');
889
971
  }
890
972
  try {
891
973
  // 等待函数状态正常
@@ -1023,7 +1105,7 @@ class FunctionService {
1023
1105
  return Object.assign({ RequestId }, Result);
1024
1106
  }
1025
1107
  catch (e) {
1026
- throw new error_1.CloudBaseError(`[${name}] 调用失败:${e.message}`, {
1108
+ throw new error_1.CloudBaseError(`[${name}] 调用失败:${e.message}`, { code: 'API_ERROR', source: 'manager-node',
1027
1109
  requestId: e.requestId
1028
1110
  });
1029
1111
  }
@@ -1045,7 +1127,7 @@ class FunctionService {
1045
1127
  return result;
1046
1128
  }
1047
1129
  catch (e) {
1048
- throw new error_1.CloudBaseError(`${func.name} 函数调用失败:${e.message}`);
1130
+ (0, error_1.throwApiError)(`${func.name} 函数调用失败:${e.message}`);
1049
1131
  }
1050
1132
  })());
1051
1133
  return Promise.all(promises);
@@ -1062,7 +1144,7 @@ class FunctionService {
1062
1144
  async copyFunction(name, newFunctionName, targetEnvId, force = false) {
1063
1145
  const { namespace } = this.getFunctionConfig();
1064
1146
  if (!namespace || !name || !newFunctionName) {
1065
- throw new error_1.CloudBaseError('参数缺失');
1147
+ (0, error_1.throwInvalidParam)('参数缺失');
1066
1148
  }
1067
1149
  return this.scfService.request('CopyFunction', {
1068
1150
  FunctionName: name,
@@ -1084,7 +1166,7 @@ class FunctionService {
1084
1166
  const { namespace } = this.getFunctionConfig();
1085
1167
  const parsedTriggers = triggers.map(item => {
1086
1168
  if (item.type !== 'timer') {
1087
- throw new error_1.CloudBaseError(`不支持的触发器类型 [${item.type}],目前仅支持定时触发器(timer)!`);
1169
+ (0, error_1.throwInvalidParam)(`不支持的触发器类型 [${item.type}],目前仅支持定时触发器(timer)!`);
1088
1170
  }
1089
1171
  return {
1090
1172
  TriggerName: item.name,
@@ -1117,7 +1199,7 @@ class FunctionService {
1117
1199
  (0, utils_1.successLog)(`[${func.name}] 创建云函数触发器成功!`);
1118
1200
  }
1119
1201
  catch (e) {
1120
- throw new error_1.CloudBaseError(e.message);
1202
+ (0, error_1.throwApiError)(e.message);
1121
1203
  }
1122
1204
  })());
1123
1205
  await Promise.all(promises);
@@ -1139,7 +1221,7 @@ class FunctionService {
1139
1221
  });
1140
1222
  }
1141
1223
  catch (e) {
1142
- throw new error_1.CloudBaseError(`[${name}] 删除触发器失败:${e.message}`);
1224
+ (0, error_1.throwApiError)(`[${name}] 删除触发器失败:${e.message}`);
1143
1225
  }
1144
1226
  }
1145
1227
  async batchDeleteTriggers(options) {
@@ -1150,7 +1232,7 @@ class FunctionService {
1150
1232
  (0, utils_1.successLog)(`[${func.name}] 删除云函数触发器成功!`);
1151
1233
  }
1152
1234
  catch (e) {
1153
- throw new error_1.CloudBaseError(e.message);
1235
+ (0, error_1.throwApiError)(e.message);
1154
1236
  }
1155
1237
  })());
1156
1238
  await Promise.all(promises);
@@ -1202,7 +1284,7 @@ class FunctionService {
1202
1284
  return { Url, RequestId, CodeSha256 };
1203
1285
  }
1204
1286
  catch (e) {
1205
- throw new error_1.CloudBaseError(`[${functionName}] 获取函数代码下载链接失败:${e.message}`, {
1287
+ throw new error_1.CloudBaseError(`[${functionName}] 获取函数代码下载链接失败:${e.message}`, { code: 'API_ERROR', source: 'manager-node',
1206
1288
  requestId: e.requestId
1207
1289
  });
1208
1290
  }
@@ -1231,7 +1313,7 @@ class FunctionService {
1231
1313
  Layers = Layers.map(item => lodash_1.default.pick(item, ['LayerName', 'LayerVersion']));
1232
1314
  const index = Layers.findIndex(item => item.LayerName === layerName && item.LayerVersion === layerVersion);
1233
1315
  if (index === -1) {
1234
- throw new error_1.CloudBaseError('层不存在');
1316
+ (0, error_1.throwNotFound)('层不存在');
1235
1317
  }
1236
1318
  // 删除指定的层
1237
1319
  Layers.splice(index, 1);
@@ -1268,7 +1350,7 @@ class FunctionService {
1268
1350
  const url = res === null || res === void 0 ? void 0 : res.Location;
1269
1351
  const zipPath = path_1.default.join(destPath, `${name}-${version}.zip`);
1270
1352
  if ((0, utils_1.checkFullAccess)(zipPath)) {
1271
- throw new error_1.CloudBaseError(`文件已存在:${zipPath}`);
1353
+ (0, error_1.throwInvalidParam)(`文件已存在:${zipPath}`);
1272
1354
  }
1273
1355
  // 下载文件
1274
1356
  return (0, utils_1.downloadAndExtractRemoteZip)(url, destPath);
@@ -1301,7 +1383,7 @@ class FunctionService {
1301
1383
  else {
1302
1384
  const fileType = path_1.default.extname(contentPath);
1303
1385
  if (fileType !== '.zip') {
1304
- throw new error_1.CloudBaseError('文件类型不正确,目前只支持 ZIP 文件!');
1386
+ (0, error_1.throwInvalidParam)('文件类型不正确,目前只支持 ZIP 文件!');
1305
1387
  }
1306
1388
  const fileBuffer = await fs_1.default.promises.readFile(contentPath);
1307
1389
  base64 = fileBuffer.toString('base64');
@@ -1370,7 +1452,9 @@ class FunctionService {
1370
1452
  timer = setTimeout(() => {
1371
1453
  clearInterval(ticker);
1372
1454
  if (!resolved) {
1373
- reject(new error_1.CloudBaseError('函数状态异常,检查超时'));
1455
+ // ⚠️ 不能写 reject(throwNetError(...)):该 helper 必抛 ⇒ 异常抛在定时器回调里、
1456
+ // Promise 永不 settle ⇒ 调用方挂死。**值位置**必须构造错误对象。
1457
+ reject(new error_1.CloudBaseError('函数状态异常,检查超时', { type: error_types_1.ERROR_TYPES.netError }));
1374
1458
  }
1375
1459
  }, 300000);
1376
1460
  ticker = setInterval(async () => {
@@ -1384,7 +1468,7 @@ class FunctionService {
1384
1468
  const errorDetails = (StatusReasons === null || StatusReasons === void 0 ? void 0 : StatusReasons.map(item => `[${item.ErrorCode}] ${item.ErrorMessage}`).join('\n')) || '';
1385
1469
  const errorMsg = `云函数创建失败${StatusDesc ? `\n状态描述: ${StatusDesc}` : ''}${errorDetails ? `\n失败信息: ${errorDetails}` : ''}`;
1386
1470
  // 注意:这里不传递 RequestId,因为这是 GetFunction 的 RequestId,不是导致失败的 CreateFunction/UpdateFunctionCode 的 RequestId
1387
- throw new error_1.CloudBaseError(errorMsg);
1471
+ (0, error_1.throwApiError)(errorMsg);
1388
1472
  }
1389
1473
  // 函数状态正常
1390
1474
  clearInterval(ticker);
@@ -1392,6 +1476,10 @@ class FunctionService {
1392
1476
  resolve();
1393
1477
  }
1394
1478
  catch (e) {
1479
+ // 容忍创建/更新期的「资源尚不可见」:`GetFunction` 在函数刚提交后会短暂返回 NOT_FOUND,
1480
+ // 此时继续轮询即可(超时仍由 5 分钟定时器兜底)—— 否则会把正常的「创建中」误报为失败。
1481
+ if (isNotYetVisibleError(e))
1482
+ return;
1395
1483
  clearInterval(ticker);
1396
1484
  clearTimeout(timer);
1397
1485
  reject(e);
@@ -1523,7 +1611,7 @@ class FunctionService {
1523
1611
  const { func, functionPath, functionRootPath } = options;
1524
1612
  const { env, appId } = this.getFunctionConfig();
1525
1613
  if (!appId) {
1526
- throw new error_1.CloudBaseError('无法获取 AppId,请确认环境初始化完成或已开通存储服务');
1614
+ (0, error_1.throwApiError)('无法获取 AppId,请确认环境初始化完成或已开通存储服务');
1527
1615
  }
1528
1616
  const objectPath = `${appId}/${env}/${func.name}.zip`;
1529
1617
  // 1. 生成存放函数包的临时 Cos 目录
@@ -1565,7 +1653,8 @@ class FunctionService {
1565
1653
  // 添加超时保护
1566
1654
  const timeout = setTimeout(() => {
1567
1655
  packer.clean();
1568
- reject(new error_1.CloudBaseError(`[${func.name}] COS 上传超时(60秒)`));
1656
+ // 同上:值位置不能调用必抛 helper
1657
+ reject(new error_1.CloudBaseError(`[${func.name}] COS 上传超时(60秒)`, { type: error_types_1.ERROR_TYPES.netError }));
1569
1658
  }, 60000);
1570
1659
  cos.sliceUploadFile(uploadParams, async (err, data) => {
1571
1660
  clearTimeout(timeout);
@@ -1640,7 +1729,7 @@ class FunctionService {
1640
1729
  catch (e) {
1641
1730
  // 忽略响应体解析错误
1642
1731
  }
1643
- throw new error_1.CloudBaseError(`上传失败: ${response.status} ${response.statusText}${errorDetail}`);
1732
+ (0, error_1.throwApiError)(`上传失败: ${response.status} ${response.statusText}${errorDetail}`);
1644
1733
  }
1645
1734
  // 清理临时文件
1646
1735
  await packer.clean();
@@ -1655,7 +1744,7 @@ class FunctionService {
1655
1744
  const MAX_ZIP_SIZE = 1.5 * 1024 * 1024; // 1.5MB
1656
1745
  const MAX_BASE64_LENGTH = Math.floor((MAX_ZIP_SIZE * 4) / 3); // ≈ 2097152
1657
1746
  if ((base64Code === null || base64Code === void 0 ? void 0 : base64Code.length) > MAX_BASE64_LENGTH) {
1658
- throw new error_1.CloudBaseError('ZipFile 上传不能大于 1.5MB,请使用 COS 上传方式');
1747
+ (0, error_1.throwInvalidParam)('ZipFile 上传不能大于 1.5MB,请使用 COS 上传方式');
1659
1748
  }
1660
1749
  if (base64Code === null || base64Code === void 0 ? void 0 : base64Code.length) {
1661
1750
  return {
@@ -1680,11 +1769,11 @@ class FunctionService {
1680
1769
  // 判断是否超过 ZipFile 上传大小限制(1.5MB)
1681
1770
  const reachMax = await packer.isReachMaxSize();
1682
1771
  if (reachMax) {
1683
- throw new error_1.CloudBaseError('ZipFile 上传不能大于 1.5MB,请使用 COS 上传方式(deployMode: "cos")');
1772
+ (0, error_1.throwInvalidParam)('ZipFile 上传不能大于 1.5MB,请使用 COS 上传方式(deployMode: "cos")');
1684
1773
  }
1685
1774
  const base64 = await packer.getBase64Code();
1686
1775
  if (!(base64 === null || base64 === void 0 ? void 0 : base64.length)) {
1687
- throw new error_1.CloudBaseError('文件不能为空');
1776
+ (0, error_1.throwInvalidParam)('文件不能为空');
1688
1777
  }
1689
1778
  console.log(`[${func.name}] 部署方式: ZIP base64 上传`);
1690
1779
  return {
@@ -1736,20 +1825,25 @@ class FunctionService {
1736
1825
  ObjectPath: `${appId}/${env}/${name}.zip"`
1737
1826
  });
1738
1827
  }
1828
+ /**
1829
+ * 创建触发器(带重试)。
1830
+ *
1831
+ * ⚠️ AIP-194:**只重试可重试错误**。旧实现是 catch-all 盲重试 —— 权限不足 / 参数非法这类
1832
+ * **必然失败**的错误也会被重试 3 次,既浪费时间,又把确定性失败伪装成偶发失败、掩盖真实原因。
1833
+ */
1739
1834
  async retryCreateTrigger(name, triggers, count = 0) {
1740
1835
  try {
1741
1836
  const res = await this.createFunctionTriggers(name, triggers);
1742
1837
  return res;
1743
1838
  }
1744
1839
  catch (e) {
1745
- if (count < 3) {
1746
- await (0, utils_1.sleep)(500);
1747
- const res = await this.retryCreateTrigger(name, triggers, count + 1);
1748
- return res;
1749
- }
1750
- else {
1840
+ // 不可重试(权限 / 参数 / 资源冲突等)或已达上限 → 立即抛出真实错误
1841
+ if (!isRetryableError(e) || count >= 3) {
1751
1842
  throw e;
1752
1843
  }
1844
+ console.warn(`[${name}] 创建触发器遇到可重试错误(第 ${count + 1}/3 次),500ms 后重试:${pickStringField(e, 'message') || String(e)}`);
1845
+ await (0, utils_1.sleep)(500);
1846
+ return this.retryCreateTrigger(name, triggers, count + 1);
1753
1847
  }
1754
1848
  }
1755
1849
  /**
@@ -100,7 +100,7 @@ class FunctionPacker {
100
100
  const jarExist = (0, utils_1.checkFullAccess)(`${filePath}.jar`);
101
101
  const zipExist = (0, utils_1.checkFullAccess)(`${filePath}.zip`);
102
102
  if (!jarExist && !zipExist) {
103
- throw new error_1.CloudBaseError('未找到部署函数的 Jar 或者 ZIP 格式文件!');
103
+ (0, error_1.throwNotFound)('未找到部署函数的 Jar 或者 ZIP 格式文件!');
104
104
  }
105
105
  this.zipFilePath = jarExist ? `${filePath}.jar` : `${filePath}.zip`;
106
106
  }
@@ -54,7 +54,7 @@ class HostingService {
54
54
  const website = hostings[0];
55
55
  // offline 状态的服务可重新开启
56
56
  if (website.Status !== 'offline') {
57
- throw new error_1.CloudBaseError('静态网站服务已开启,请勿重复操作!');
57
+ (0, error_1.throwApiError)('静态网站服务已开启,请勿重复操作!');
58
58
  }
59
59
  }
60
60
  const res = await this.tcbService.request('CreateStaticStore', {
package/lib/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  "use strict";
2
+ const error_1 = require("./error");
2
3
  const context_1 = require("./context");
3
4
  const environmentManager_1 = require("./environmentManager");
4
5
  const docs_1 = require("./docs");
@@ -25,7 +26,7 @@ class CloudBase {
25
26
  let { secretId, secretKey, token, envId, proxy, region, envType, useInternalEndpoint, internalEndpointRegion, requestFn } = config;
26
27
  // config 中传入的 secretId secretkey 必须同时存在(有 requestFn 时可以不传)
27
28
  if (!requestFn && ((secretId && !secretKey) || (!secretId && secretKey))) {
28
- throw new Error('secretId and secretKey must be a pair');
29
+ (0, error_1.throwInvalidParam)('secretId and secretKey must be a pair');
29
30
  }
30
31
  this.cloudBaseConfig = {
31
32
  secretId: secretId ? secretId.trim() : secretId,
@@ -49,6 +49,7 @@ exports.aesEncryptWithIv = aesEncryptWithIv;
49
49
  * 使用 Node 内置 crypto 复刻,无需引入额外依赖。
50
50
  */
51
51
  const crypto = __importStar(require("crypto"));
52
+ const error_1 = require("../error");
52
53
  const ALGORITHM = 'aes-256-cbc';
53
54
  const KEY_BYTE_LENGTH = 32;
54
55
  const IV_BYTE_LENGTH = 16;
@@ -56,7 +57,7 @@ const IV_BYTE_LENGTH = 16;
56
57
  const IV_BASE64_LENGTH = 24;
57
58
  function assertKey(key) {
58
59
  if (Buffer.byteLength(key, 'utf8') !== KEY_BYTE_LENGTH) {
59
- throw new Error('AES key 必须是 32 字节长度');
60
+ (0, error_1.throwInvalidParam)('AES key 必须是 32 字节长度');
60
61
  }
61
62
  }
62
63
  /**
@@ -65,7 +66,7 @@ function assertKey(key) {
65
66
  function aesEncryptWithIv(plaintext, key, iv) {
66
67
  assertKey(key);
67
68
  if (iv.length !== IV_BYTE_LENGTH) {
68
- throw new Error('IV 必须是 16 字节长度');
69
+ (0, error_1.throwInvalidParam)('IV 必须是 16 字节长度');
69
70
  }
70
71
  const cipher = crypto.createCipheriv(ALGORITHM, Buffer.from(key, 'utf8'), iv);
71
72
  const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
@@ -89,7 +90,7 @@ class AESCryptoImpl {
89
90
  const ciphertextBase64 = encryptedData.slice(IV_BASE64_LENGTH);
90
91
  const iv = Buffer.from(ivBase64, 'base64');
91
92
  if (iv.length !== IV_BYTE_LENGTH) {
92
- throw new Error('密文格式错误:IV 长度非法');
93
+ (0, error_1.throwInvalidParam)('密文格式错误:IV 长度非法');
93
94
  }
94
95
  const decipher = crypto.createDecipheriv(ALGORITHM, Buffer.from(key, 'utf8'), iv);
95
96
  const decrypted = Buffer.concat([
@@ -170,7 +170,7 @@ class IntegrationService {
170
170
  validateRequiredParams(params, fields) {
171
171
  for (const field of fields) {
172
172
  if (!params[field]) {
173
- throw new error_1.CloudBaseError(`${field} 是必填参数`);
173
+ (0, error_1.throwInvalidParam)(`${field} 是必填参数`);
174
174
  }
175
175
  }
176
176
  }
@@ -60,24 +60,24 @@ async function getAesKey(params = {}) {
60
60
  });
61
61
  }
62
62
  catch (e) {
63
- throw new error_1.CloudBaseError('获取集成加解密密钥失败:七彩石配置拉取请求异常');
63
+ (0, error_1.throwApiError)('获取集成加解密密钥失败:七彩石配置拉取请求异常');
64
64
  }
65
65
  const data = (_a = resJson === null || resJson === void 0 ? void 0 : resJson.Response) === null || _a === void 0 ? void 0 : _a.Data;
66
66
  if (!data || data.ErrorCode !== '0') {
67
- throw new error_1.CloudBaseError(`获取集成加解密密钥失败:七彩石返回异常(ErrorCode=${data === null || data === void 0 ? void 0 : data.ErrorCode})`);
67
+ (0, error_1.throwApiError)(`获取集成加解密密钥失败:七彩石返回异常(ErrorCode=${data === null || data === void 0 ? void 0 : data.ErrorCode})`);
68
68
  }
69
69
  let config;
70
70
  try {
71
71
  config = JSON.parse(data.ConfigValue)[0];
72
72
  }
73
73
  catch (e) {
74
- throw new error_1.CloudBaseError('获取集成加解密密钥失败:七彩石配置内容解析失败');
74
+ (0, error_1.throwApiError)('获取集成加解密密钥失败:七彩石配置内容解析失败');
75
75
  }
76
76
  const kvs = ((_b = config === null || config === void 0 ? void 0 : config.kvs) === null || _b === void 0 ? void 0 : _b.kvs) || [];
77
77
  const target = kvs.find(kv => kv.key === RAINBOW_CONFIG.CRYPTO_KEY_FIELD);
78
78
  const key = target === null || target === void 0 ? void 0 : target.value;
79
79
  if (!key) {
80
- throw new error_1.CloudBaseError(`获取集成加解密密钥失败:七彩石配置中缺少 ${RAINBOW_CONFIG.CRYPTO_KEY_FIELD} 字段`);
80
+ (0, error_1.throwApiError)(`获取集成加解密密钥失败:七彩石配置中缺少 ${RAINBOW_CONFIG.CRYPTO_KEY_FIELD} 字段`);
81
81
  }
82
82
  keyCache[group] = key;
83
83
  return key;
@@ -37,7 +37,7 @@ function validatePeriod(startStr, endStr, period) {
37
37
  const rangeMs = endMs - startMs;
38
38
  const rule = PERIOD_RULES.find(r => rangeMs <= r.maxRangeMs);
39
39
  if (rule && !rule.allowed.includes(period)) {
40
- throw new error_1.CloudBaseError(`Period=${period} 不适用于当前时间范围。` +
40
+ (0, error_1.throwInvalidParam)(`Period=${period} 不适用于当前时间范围。` +
41
41
  `时间范围 ${startStr} ~ ${endStr}(约 ${(rangeMs / ONE_DAY_MS).toFixed(1)} 天)` +
42
42
  `仅支持 Period: [${rule.allowed.join(', ')}]`);
43
43
  }
@@ -22,6 +22,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
22
22
  Object.defineProperty(exports, "__esModule", { value: true });
23
23
  exports.MysqlService = void 0;
24
24
  const utils_1 = require("../utils");
25
+ const error_1 = require("../error");
25
26
  __exportStar(require("./types/index"), exports);
26
27
  class MysqlService {
27
28
  constructor(environment) {
@@ -416,7 +417,7 @@ class MysqlService {
416
417
  const res = await this.describeClusterInstanceGroups();
417
418
  const groups = res.InstanceGroupInfoList;
418
419
  if (!groups || groups.length === 0) {
419
- throw new Error('当前集群没有实例组,无法执行外网操作');
420
+ (0, error_1.throwApiError)('当前集群没有实例组,无法执行外网操作');
420
421
  }
421
422
  this._instanceGroupId = groups[0].InstanceGroupId;
422
423
  }