n8n-nodes-kingsoft-airscript 0.3.4 → 2.0.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.
@@ -2,6 +2,14 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.KingsoftAirscript = void 0;
4
4
  const n8n_workflow_1 = require("n8n-workflow");
5
+ const API_CONFIG = {
6
+ BASE_URL: 'https://www.kdocs.cn',
7
+ ENDPOINTS: {
8
+ SYNC_TASK: '/api/v3/ide/file/:file_id/script/:script_id/sync_task',
9
+ ASYNC_TASK: '/api/v3/ide/file/:file_id/script/:script_id/task',
10
+ GET_TASK: '/api/v3/script/task'
11
+ }
12
+ };
5
13
  class KingsoftAirscript {
6
14
  constructor() {
7
15
  this.description = {
@@ -11,18 +19,76 @@ class KingsoftAirscript {
11
19
  group: ['transform'],
12
20
  version: 3,
13
21
  subtitle: '={{$parameter["operation"]}}',
14
- description: '一个通用的金山 AirScript 执行器。访问 <a href="https://www.kdocs.cn/l/cho73TDGgMPP">官方指南 & 脚本库</a> 获取更多帮助',
15
- defaults: {
16
- name: 'Kingsoft AirScript',
17
- },
22
+ description: `
23
+ <div style="padding: 16px; background-color: #f8f9fa; border-radius: 8px; margin-bottom: 16px;">
24
+ <h3>📖 金山 AirScript 执行器</h3>
25
+ <p>一个功能强大的通用执行器,支持同步/异步执行脚本、自动分批处理和智能参数生成。</p>
26
+
27
+ <h4>🚀 主要功能</h4>
28
+ <ul>
29
+ <li>支持同步/异步执行脚本</li>
30
+ <li>自动分批处理大量数据</li>
31
+ <li>智能参数模板生成</li>
32
+ <li>完善的错误处理和重试机制</li>
33
+ <li>支持附件、公式、级联字段等复杂数据类型</li>
34
+ </ul>
35
+
36
+ <h4>💡 使用指南</h4>
37
+ <ol>
38
+ <li><strong>选择操作类型</strong>:同步执行/异步执行/获取任务状态</li>
39
+ <li><strong>输入 ID</strong>:通过 Webhook 链接或手动输入 File ID 和 Script ID</li>
40
+ <li><strong>配置参数</strong>:
41
+ <ul>
42
+ <li>选择 <strong>参数模板</strong> 快速生成参数结构</li>
43
+ <li>或在 <strong>脚本参数 (Argv)</strong> 中手动输入 JSON</li>
44
+ </ul>
45
+ </li>
46
+ <li><strong>高级选项</strong>:配置超时、重试、分批等高级设置</li>
47
+ <li><strong>执行工作流</strong>:运行工作流执行脚本</li>
48
+ </ol>
49
+
50
+ <h4>📚 示例库</h4>
51
+ <p>参数模板按功能分类:</p>
52
+
53
+ <h5>🔄 数据录入类</h5>
54
+ <ul>
55
+ <li>基本数据录入:向指定数据表中录入新数据</li>
56
+ <li>使用 N8N 表达式:在 N8N 工作流中动态处理输入数据</li>
57
+ <li>处理附件字段:录入包含图片等附件的数据</li>
58
+ <li>处理级联字段:录入包含级联字段的数据</li>
59
+ </ul>
60
+
61
+ <h5>🔄 数据更新类</h5>
62
+ <ul>
63
+ <li>使用复合唯一键:使用多个字段作为复合唯一键进行更新</li>
64
+ <li>批量迁移数据:将数据从一个表迁移到另一个表</li>
65
+ </ul>
66
+
67
+ <h5>🔄 结构配置类</h5>
68
+ <ul>
69
+ <li>设置公式字段:向表中设置公式字段的表达式</li>
70
+ <li>设置级联选项:配置级联字段的选项结构</li>
71
+ <li>设置关联属性:配置关联字段的属性</li>
72
+ <li>智能创建表格:根据数据结构智能创建表格和字段</li>
73
+ </ul>
74
+
75
+ <h4>❓ 常见问题</h4>
76
+ <ul>
77
+ <li><strong>Q: 节点不出现在 n8n 界面中?</strong><br>A: 请确保运行 <code>npm run build</code> 并正确链接节点,或手动复制构建文件到 <code>%APPDATA%\n8n\nodes</code> 目录。</li>
78
+ <li><strong>Q: 如何获取 Webhook 链接?</strong><br>A: 在金山文档脚本编辑器中,点击 "发布" → "Webhook",复制生成的完整链接。</li>
79
+ <li><strong>Q: 分批处理如何工作?</strong><br>A: 开启 "自动分批执行" 后,节点会根据 "分批字段路径" 从 argv 中提取数组,按批次大小分割并并发执行。</li>
80
+ <li><strong>Q: 如何处理附件字段?</strong><br>A: 使用 "处理附件字段" 模板,在 data 数组中包含 base64 编码的附件数据。</li>
81
+ <li><strong>Q: 如何使用 N8N 表达式?</strong><br>A: 使用 "使用 N8N 表达式" 模板,或在手动输入中使用 <code>{{ JSON.stringify( $input.all().map(i => i.json) ) }}</code> 格式。</li>
82
+ </ul>
83
+
84
+ <h4>🔗 更多资源</h4>
85
+ <p>访问 <a href="https://www.kdocs.cn/l/cho73TDGgMPP" target="_blank">官方指南 & 脚本库</a> 获取更多帮助和示例脚本。</p>
86
+ </div>
87
+ `,
88
+ defaults: { name: 'Kingsoft AirScript' },
18
89
  inputs: ['main'],
19
90
  outputs: ['main'],
20
- credentials: [
21
- {
22
- name: 'kingsoftAirscriptApi',
23
- required: true,
24
- },
25
- ],
91
+ credentials: [{ name: 'kingsoftAirscriptApi', required: true }],
26
92
  properties: [
27
93
  {
28
94
  displayName: '操作',
@@ -42,21 +108,11 @@ class KingsoftAirscript {
42
108
  type: 'options',
43
109
  noDataExpression: true,
44
110
  displayOptions: {
45
- show: {
46
- operation: ['runScriptSync', 'runScriptAsync'],
47
- },
111
+ show: { operation: ['runScriptSync', 'runScriptAsync'] },
48
112
  },
49
113
  options: [
50
- {
51
- name: '通过 Webhook 链接输入',
52
- value: 'url',
53
- description: '粘贴完整的链接,最简单快捷',
54
- },
55
- {
56
- name: '手动输入 ID',
57
- value: 'manual',
58
- description: '分别填写 File ID 和 Script ID',
59
- },
114
+ { name: '通过 Webhook 链接输入', value: 'url', description: '粘贴完整的链接,最简单快捷' },
115
+ { name: '手动输入 ID', value: 'manual', description: '分别填写 File ID 和 Script ID' },
60
116
  ],
61
117
  default: 'url',
62
118
  },
@@ -110,11 +166,7 @@ class KingsoftAirscript {
110
166
  placeholder: '添加可选参数',
111
167
  default: {},
112
168
  description: '设置可选的 Context 参数,会附加到请求中',
113
- displayOptions: {
114
- show: {
115
- operation: ['runScriptSync', 'runScriptAsync'],
116
- },
117
- },
169
+ displayOptions: { show: { operation: ['runScriptSync', 'runScriptAsync'] } },
118
170
  options: [
119
171
  { displayName: '表名 (Sheet Name)', name: 'sheetName', type: 'string', default: '', description: '对应 Context.sheet_name' },
120
172
  { displayName: '范围 (Range)', name: 'range', type: 'string', default: '', description: '对应 Context.range, 例如 "$B$156"' },
@@ -123,21 +175,126 @@ class KingsoftAirscript {
123
175
  { displayName: '选区 (DB Selection)', name: 'dbSelection', type: 'string', default: '', description: '对应 Context.db_selection' },
124
176
  ],
125
177
  },
178
+ {
179
+ displayName: '参数模板',
180
+ name: 'argvTemplate',
181
+ type: 'options',
182
+ noDataExpression: true,
183
+ displayOptions: { show: { operation: ['runScriptSync', 'runScriptAsync'] } },
184
+ options: [
185
+ { name: '处理附件字段', value: 'attachment', description: '录入包含图片等附件的数据' },
186
+ { name: '处理级联字段', value: 'cascade', description: '录入包含级联字段的数据' },
187
+ { name: '基本数据录入', value: 'basicInput', description: '向指定数据表中录入新数据' },
188
+ { name: '批量迁移数据', value: 'migrate', description: '将数据从一个表迁移到另一个表' },
189
+ { name: '设置公式字段', value: 'formula', description: '向表中设置公式字段的表达式' },
190
+ { name: '设置关联属性', value: 'setLinkProperties', description: '配置关联字段的属性' },
191
+ { name: '设置级联选项', value: 'setCascadeOptions', description: '配置级联字段的选项结构' },
192
+ { name: '使用 N8N 表达式', value: 'n8nExpression', description: '在 N8N 工作流中动态处理输入数据' },
193
+ { name: '使用复合唯一键', value: 'compositeKey', description: '使用多个字段作为复合唯一键进行更新' },
194
+ { name: '智能创建表格', value: 'createSheet', description: '根据数据结构智能创建表格和字段' },
195
+ { name: '自定义 (手动输入)', value: 'custom', description: '手动输入 JSON 格式参数' },
196
+ ],
197
+ default: 'custom',
198
+ description: '选择预设模板后,会显示对应的 Argv JSON 输入框(可编辑)',
199
+ },
126
200
  {
127
201
  displayName: '脚本参数 (Argv)',
128
- name: 'argv',
202
+ name: 'argv_custom',
129
203
  type: 'json',
130
204
  default: '{\n "message": "Hello from n8n!"\n}',
131
205
  required: true,
132
- displayOptions: {
133
- show: {
134
- operation: [
135
- 'runScriptSync',
136
- 'runScriptAsync',
137
- ],
138
- },
139
- },
140
- description: '以 JSON 格式向脚本传递动态参数。不知道如何为您的业务场景构建参数?欢迎访问我们的[脚本模板库](https://www.kdocs.cn/l/cho73TDGgMPP)寻找灵感。',
206
+ displayOptions: { show: { operation: ['runScriptSync', 'runScriptAsync'], argvTemplate: ['custom'] } },
207
+ description: '自定义 JSON 参数',
208
+ },
209
+ {
210
+ displayName: '脚本参数 (Argv)',
211
+ name: 'argv_basicInput',
212
+ type: 'json',
213
+ default: `{\n "sheet_name": "客户表",\n "覆盖模式": false,\n "data": [\n {\n "客户名称": "张三",\n "联系方式": "13800138000",\n "邮箱": "zhangsan@example.com"\n }\n ]\n}`,
214
+ required: true,
215
+ displayOptions: { show: { operation: ['runScriptSync', 'runScriptAsync'], argvTemplate: ['basicInput'] } },
216
+ description: '基本数据录入模板参数(可编辑)',
217
+ },
218
+ {
219
+ displayName: '脚本参数 (Argv)',
220
+ name: 'argv_attachment',
221
+ type: 'json',
222
+ default: `{\n "sheet_name": "产品表",\n "覆盖模式": true,\n "唯一键": "产品ID",\n "data": [\n {\n "产品ID": "P001",\n "产品名称": "XXX沙发",\n "图片": "https://example.com/image1.jpg",\n "描述": "舒适的三人沙发"\n }\n ]\n}`,
223
+ required: true,
224
+ displayOptions: { show: { operation: ['runScriptSync', 'runScriptAsync'], argvTemplate: ['attachment'] } },
225
+ description: '处理附件字段模板参数(可编辑)',
226
+ },
227
+ {
228
+ displayName: '脚本参数 (Argv)',
229
+ name: 'argv_cascade',
230
+ type: 'json',
231
+ default: `{\n "sheet_name": "订单表",\n "覆盖模式": true,\n "唯一键": "订单ID",\n "data": [\n {\n "订单ID": "O001",\n "客户": "张三",\n "地区": "广东省",\n "城市": "深圳市",\n "产品": "XXX沙发"\n }\n ]\n}`,
232
+ required: true,
233
+ displayOptions: { show: { operation: ['runScriptSync', 'runScriptAsync'], argvTemplate: ['cascade'] } },
234
+ description: '处理级联字段模板参数(可编辑)',
235
+ },
236
+ {
237
+ displayName: '脚本参数 (Argv)',
238
+ name: 'argv_migrate',
239
+ type: 'json',
240
+ default: `{\n "源表名称": "旧客户表",\n "目标表名称": "新客户表",\n "映射关系": {\n "旧ID": "客户ID",\n "姓名": "客户名称",\n "电话": "联系方式"\n },\n "过滤条件": "状态 = '活跃'"\n}`,
241
+ required: true,
242
+ displayOptions: { show: { operation: ['runScriptSync', 'runScriptAsync'], argvTemplate: ['migrate'] } },
243
+ description: '批量迁移数据模板参数(可编辑)',
244
+ },
245
+ {
246
+ displayName: '脚本参数 (Argv)',
247
+ name: 'argv_formula',
248
+ type: 'json',
249
+ default: `{\n "sheet_name": "销售表",\n "字段配置": [\n {\n "字段名称": "销售额",\n "字段类型": "公式",\n "表达式": "单价 * 数量"\n }\n ]\n}`,
250
+ required: true,
251
+ displayOptions: { show: { operation: ['runScriptSync', 'runScriptAsync'], argvTemplate: ['formula'] } },
252
+ description: '设置公式字段模板参数(可编辑)',
253
+ },
254
+ {
255
+ displayName: '脚本参数 (Argv)',
256
+ name: 'argv_setLinkProperties',
257
+ type: 'json',
258
+ default: `{\n "sheet_name": "订单表",\n "关联字段": "客户ID",\n "目标表": "客户表",\n "显示字段": ["客户名称", "联系方式"]\n}`,
259
+ required: true,
260
+ displayOptions: { show: { operation: ['runScriptSync', 'runScriptAsync'], argvTemplate: ['setLinkProperties'] } },
261
+ description: '设置关联属性模板参数(可编辑)',
262
+ },
263
+ {
264
+ displayName: '脚本参数 (Argv)',
265
+ name: 'argv_setCascadeOptions',
266
+ type: 'json',
267
+ default: `{\n "sheet_name": "地区表",\n "级联字段": "地区",\n "选项结构": {\n "广东省": ["深圳市", "广州市", "东莞市"],\n "浙江省": ["杭州市", "宁波市", "温州市"]\n }\n}`,
268
+ required: true,
269
+ displayOptions: { show: { operation: ['runScriptSync', 'runScriptAsync'], argvTemplate: ['setCascadeOptions'] } },
270
+ description: '设置级联选项模板参数(可编辑)',
271
+ },
272
+ {
273
+ displayName: '脚本参数 (Argv)',
274
+ name: 'argv_n8nExpression',
275
+ type: 'json',
276
+ default: `{\n "sheet_name": "动态表",\n "覆盖模式": true,\n "唯一键": "ID",\n "data": [\n {\n "ID": "={{ $json.id }}",\n "名称": "={{ $json.name }}",\n "值": "={{ $json.value }}"\n }\n ]\n}`,
277
+ required: true,
278
+ displayOptions: { show: { operation: ['runScriptSync', 'runScriptAsync'], argvTemplate: ['n8nExpression'] } },
279
+ description: '使用 N8N 表达式模板参数(可编辑)',
280
+ },
281
+ {
282
+ displayName: '脚本参数 (Argv)',
283
+ name: 'argv_compositeKey',
284
+ type: 'json',
285
+ default: `{\n "sheet_name": "库存表",\n "覆盖模式": true,\n "唯一键": ["产品ID", "仓库ID"],\n "data": [\n {\n "产品ID": "P001",\n "仓库ID": "W001",\n "库存数量": 100,\n "最后更新": "2024-01-01"\n }\n ]\n}`,
286
+ required: true,
287
+ displayOptions: { show: { operation: ['runScriptSync', 'runScriptAsync'], argvTemplate: ['compositeKey'] } },
288
+ description: '使用复合唯一键模板参数(可编辑)',
289
+ },
290
+ {
291
+ displayName: '脚本参数 (Argv)',
292
+ name: 'argv_createSheet',
293
+ type: 'json',
294
+ default: `{\n "sheet_name": "新表格",\n "字段定义": [\n {\n "名称": "ID",\n "类型": "文本",\n "唯一": true\n },\n {\n "名称": "名称",\n "类型": "文本"\n },\n {\n "名称": "数值",\n "类型": "数字"\n },\n {\n "名称": "日期",\n "类型": "日期"\n }\n ],\n "数据": [\n {\n "ID": "1",\n "名称": "测试1",\n "数值": 100,\n "日期": "2024-01-01"\n }\n ]\n}`,
295
+ required: true,
296
+ displayOptions: { show: { operation: ['runScriptSync', 'runScriptAsync'], argvTemplate: ['createSheet'] } },
297
+ description: '智能创建表格模板参数(可编辑)',
141
298
  },
142
299
  {
143
300
  displayName: '任务 ID (Task ID)',
@@ -145,13 +302,7 @@ class KingsoftAirscript {
145
302
  type: 'string',
146
303
  default: '',
147
304
  required: true,
148
- displayOptions: {
149
- show: {
150
- operation: [
151
- 'getTaskStatus',
152
- ],
153
- },
154
- },
305
+ displayOptions: { show: { operation: ['getTaskStatus'] } },
155
306
  description: '从异步执行脚本操作中获取到的任务 ID',
156
307
  },
157
308
  {
@@ -167,7 +318,15 @@ class KingsoftAirscript {
167
318
  name: 'a_parallelExecution',
168
319
  type: 'boolean',
169
320
  default: false,
170
- description: 'Whether开启后,节点将同时处理所有输入项。',
321
+ description: 'Whether enabled, the node will process all input items in parallel',
322
+ },
323
+ {
324
+ displayName: '查询任务超时(秒)',
325
+ name: 'i_statusTimeoutSeconds',
326
+ type: 'number',
327
+ default: 30,
328
+ typeOptions: { minValue: 3 },
329
+ description: '查询 /api/v3/script/task 的 HTTP 超时(秒)。',
171
330
  },
172
331
  {
173
332
  displayName: '超时时间 (秒)',
@@ -176,15 +335,60 @@ class KingsoftAirscript {
176
335
  default: 300,
177
336
  typeOptions: { minValue: 5 },
178
337
  displayOptions: { show: { '/operation': ['runScriptAsync'], '/c_waitForCompletion': [true] } },
338
+ description: '异步等待完成模式下,最多等待的总时长(秒)。',
179
339
  },
180
340
  {
181
341
  displayName: '等待执行完成',
182
342
  name: 'c_waitForCompletion',
183
343
  type: 'boolean',
184
344
  default: false,
185
- description: 'Whether开启后,当使用"异步执行"时,节点将自动轮询,直到任务完成。',
345
+ description: 'Whether enabled, when running async the node will poll until the task finishes',
186
346
  displayOptions: { show: { '/operation': ['runScriptAsync'] } },
187
347
  },
348
+ {
349
+ displayName: '调试模式(输出 _调试 字段)',
350
+ name: 'w_debug',
351
+ type: 'boolean',
352
+ default: false,
353
+ description: 'Whether enabled, include a `_debug` field with latency and retry details in the output',
354
+ },
355
+ {
356
+ displayName: '分批输出模式',
357
+ name: 'v_chunkOutputMode',
358
+ type: 'options',
359
+ default: 'split',
360
+ options: [
361
+ { name: '逐批输出(推荐)', value: 'split' },
362
+ { name: '合并为一个 Item(批次输出列表)', value: 'merge' },
363
+ ],
364
+ displayOptions: { show: { '/r_autoChunkEnabled': [true] } },
365
+ description: '自动分批时的输出策略。',
366
+ },
367
+ {
368
+ displayName: '分批字段路径(相对 Argv)',
369
+ name: 's_chunkFieldPath',
370
+ type: 'string',
371
+ default: '写表行列表',
372
+ description: '例:写表行列表 或 data.写表行列表(从 argv 根开始)',
373
+ displayOptions: { show: { '/r_autoChunkEnabled': [true] } },
374
+ },
375
+ {
376
+ displayName: '分批最大并发数(同一 Item 内)',
377
+ name: 'u_chunkMaxConcurrency',
378
+ type: 'number',
379
+ default: 3,
380
+ typeOptions: { minValue: 1, maxValue: 20 },
381
+ displayOptions: { show: { '/r_autoChunkEnabled': [true] } },
382
+ description: '自动分批时,同一输入 item 内批次执行的最大并发数。',
383
+ },
384
+ {
385
+ displayName: '轮询抖动(毫秒)',
386
+ name: 'o_pollJitterMs',
387
+ type: 'number',
388
+ default: 300,
389
+ typeOptions: { minValue: 0 },
390
+ description: '在轮询间隔上叠加随机抖动,避免同一秒集中刷接口。',
391
+ },
188
392
  {
189
393
  displayName: '轮询间隔 (秒)',
190
394
  name: 'd_pollIntervalSeconds',
@@ -192,6 +396,16 @@ class KingsoftAirscript {
192
396
  default: 5,
193
397
  typeOptions: { minValue: 1 },
194
398
  displayOptions: { show: { '/operation': ['runScriptAsync'], '/c_waitForCompletion': [true] } },
399
+ description: '异步等待完成模式下的轮询间隔(秒)。',
400
+ },
401
+ {
402
+ displayName: '每批条数',
403
+ name: 't_chunkSize',
404
+ type: 'number',
405
+ default: 200,
406
+ typeOptions: { minValue: 1 },
407
+ displayOptions: { show: { '/r_autoChunkEnabled': [true] } },
408
+ description: '自动分批时,每批数组元素数量。',
195
409
  },
196
410
  {
197
411
  displayName: '批处理数量',
@@ -200,6 +414,22 @@ class KingsoftAirscript {
200
414
  default: 10,
201
415
  typeOptions: { minValue: 1 },
202
416
  displayOptions: { show: { '/a_parallelExecution': [true] } },
417
+ description: '并行执行开启时,每批并发处理的输入 item 数量。',
418
+ },
419
+ {
420
+ displayName: '请求超时(秒)',
421
+ name: 'h_requestTimeoutSeconds',
422
+ type: 'number',
423
+ default: 120,
424
+ typeOptions: { minValue: 5 },
425
+ description: '调用 sync_task/task 的 HTTP 超时(秒)。',
426
+ },
427
+ {
428
+ displayName: '失败判定:Finished 但 Error 不为空算失败',
429
+ name: 'n_failOnScriptError',
430
+ type: 'boolean',
431
+ default: true,
432
+ description: 'Whether enabled, treat `status=finished` with non-empty `error` as a failure',
203
433
  },
204
434
  {
205
435
  displayName: '输出格式',
@@ -211,17 +441,55 @@ class KingsoftAirscript {
211
441
  { name: '完整响应', value: 'fullResponse' },
212
442
  { name: '仅结果', value: 'resultOnly' },
213
443
  { name: '拆分所有日志', value: 'splitLogs' },
214
- { name: '仅拆分错误日志', value: 'splitErrors' }
215
- ]
444
+ { name: '仅拆分错误日志', value: 'splitErrors' },
445
+ ],
216
446
  },
217
447
  {
218
448
  displayName: '智能解析 Result',
219
449
  name: 'g_parseResultString',
220
450
  type: 'boolean',
221
451
  default: false,
222
- description: 'Whether开启后,如果 `result` JSON 字符串,会自动解析。',
452
+ description: 'Whether enabled, if `result` is a JSON string it will be parsed automatically',
223
453
  displayOptions: { show: { '/operation': ['runScriptSync', 'runScriptAsync'] } },
224
454
  },
455
+ {
456
+ displayName: '重试初始等待(毫秒)',
457
+ name: 'k_retryInitialDelayMs',
458
+ type: 'number',
459
+ default: 800,
460
+ typeOptions: { minValue: 0 },
461
+ description: '重试的初始等待时间(毫秒)。',
462
+ },
463
+ {
464
+ displayName: '重试退避倍数',
465
+ name: 'l_retryBackoffFactor',
466
+ type: 'number',
467
+ default: 2,
468
+ typeOptions: { minValue: 1 },
469
+ description: '指数退避倍数(>=1)。',
470
+ },
471
+ {
472
+ displayName: '自动分批写入(按 Argv 内数组字段)',
473
+ name: 'r_autoChunkEnabled',
474
+ type: 'boolean',
475
+ default: false,
476
+ description: 'Whether enabled, split the array at the configured field path in argv and run the script in chunks',
477
+ },
478
+ {
479
+ displayName: '最大重试次数',
480
+ name: 'j_maxRetries',
481
+ type: 'number',
482
+ default: 2,
483
+ typeOptions: { minValue: 0, maxValue: 10 },
484
+ description: '对可恢复错误(超时/429/5xx 等)的最大重试次数。',
485
+ },
486
+ {
487
+ displayName: '尊重 Retry-After(429)',
488
+ name: 'm_honorRetryAfter',
489
+ type: 'boolean',
490
+ default: true,
491
+ description: 'Whether enabled, when receiving HTTP 429 with Retry-After header, wait accordingly before retrying',
492
+ },
225
493
  ],
226
494
  },
227
495
  ],
@@ -229,12 +497,211 @@ class KingsoftAirscript {
229
497
  };
230
498
  }
231
499
  async execute() {
232
- var _a;
233
500
  const 输入数据项列表 = this.getInputData();
234
- const 凭证 = await this.getCredentials('kingsoftAirscriptApi');
235
- const 执行脚本 = async (itemIndex, 类型) => {
501
+ const 凭证 = (await this.getCredentials('kingsoftAirscriptApi'));
502
+ const 等待毫秒 = async (ms) => {
503
+ await this.helpers.sleep(ms);
504
+ };
505
+ const 脱敏ID = (id) => {
506
+ const s = (id !== null && id !== void 0 ? id : '').toString();
507
+ if (s.length <= 10)
508
+ return s;
509
+ return `${s.slice(0, 4)}***${s.slice(-4)}`;
510
+ };
511
+ const 解析RetryAfter毫秒 = (v) => {
512
+ if (v === null || v === undefined)
513
+ return null;
514
+ const s = String(v).trim();
515
+ if (/^\d+$/.test(s))
516
+ return Number(s) * 1000;
517
+ const t = Date.parse(s);
518
+ if (Number.isFinite(t)) {
519
+ const diff = t - Date.now();
520
+ return diff > 0 ? diff : 0;
521
+ }
522
+ return null;
523
+ };
524
+ const 提取Http错误 = (e) => { var _a; return (_a = e) !== null && _a !== void 0 ? _a : {}; };
525
+ const 是IDataObject = (v) => {
526
+ return typeof v === 'object' && v !== null && !Array.isArray(v);
527
+ };
528
+ const 提取错误消息 = (err) => {
529
+ var _a;
530
+ const data = (_a = err.response) === null || _a === void 0 ? void 0 : _a.data;
531
+ if (是IDataObject(data)) {
532
+ const errorDetails = data.error_details;
533
+ if (是IDataObject(errorDetails)) {
534
+ const msg = errorDetails.msg;
535
+ if (typeof msg === 'string' && msg.trim())
536
+ return msg.trim();
537
+ }
538
+ const error = data.error;
539
+ if (typeof error === 'string' && error.trim())
540
+ return error.trim();
541
+ }
542
+ if (typeof err.message === 'string' && err.message.trim())
543
+ return err.message.trim();
544
+ return '未知错误';
545
+ };
546
+ const 判断是否可重试 = (e) => {
547
+ var _a;
548
+ const err = 提取Http错误(e);
549
+ const 错误码 = err.code;
550
+ const 状态码 = (_a = err.response) === null || _a === void 0 ? void 0 : _a.status;
551
+ return (['ETIMEDOUT', 'ECONNRESET', 'EAI_AGAIN', 'ECONNREFUSED', 'ECONNABORTED'].includes(String(错误码 !== null && 错误码 !== void 0 ? 错误码 : '')) ||
552
+ [408, 425, 429, 500, 502, 503, 504].includes(Number(状态码 !== null && 状态码 !== void 0 ? 状态码 : 0)));
553
+ };
554
+ const 带重试 = async (执行函数, 配置, 调试) => {
555
+ var _a, _b, _c, _d;
556
+ let 尝试 = 0;
557
+ while (true) {
558
+ try {
559
+ const res = await 执行函数();
560
+ if (调试)
561
+ 调试.重试次数 = 尝试;
562
+ return res;
563
+ }
564
+ catch (e) {
565
+ const err = 提取Http错误(e);
566
+ const 状态码 = (_a = err.response) === null || _a === void 0 ? void 0 : _a.status;
567
+ if (!判断是否可重试(e) || 尝试 >= 配置.最大重试次数) {
568
+ if (调试)
569
+ 调试.重试次数 = 尝试;
570
+ throw e;
571
+ }
572
+ let 等待 = Math.round(配置.初始等待毫秒 * Math.pow(配置.退避倍数, 尝试) + Math.random() * 200);
573
+ if (配置.尊重RetryAfter && 状态码 === 429) {
574
+ const headers = (_c = (_b = err.response) === null || _b === void 0 ? void 0 : _b.headers) !== null && _c !== void 0 ? _c : {};
575
+ const ra = (_d = headers['retry-after']) !== null && _d !== void 0 ? _d : headers['Retry-After'];
576
+ const raMs = 解析RetryAfter毫秒(ra);
577
+ if (raMs !== null)
578
+ 等待 = Math.max(等待, raMs);
579
+ }
580
+ if (调试)
581
+ 调试.最后等待毫秒 = 等待;
582
+ await 等待毫秒(等待);
583
+ 尝试++;
584
+ }
585
+ }
586
+ };
587
+ const 并发执行 = async (任务函数列表, 并发数) => {
588
+ const 结果 = new Array(任务函数列表.length);
589
+ let 下一个索引 = 0;
590
+ const 工人 = async () => {
591
+ while (true) {
592
+ const i = 下一个索引++;
593
+ if (i >= 任务函数列表.length)
594
+ break;
595
+ 结果[i] = await 任务函数列表[i]();
596
+ }
597
+ };
598
+ const 工人数量 = Math.min(Math.max(1, 并发数), 任务函数列表.length);
599
+ await Promise.all(Array.from({ length: 工人数量 }, () => 工人()));
600
+ return 结果;
601
+ };
602
+ const 取路径值 = (obj, path) => {
603
+ if (!obj || !path)
604
+ return undefined;
605
+ const keys = String(path).split('.').filter(Boolean);
606
+ let cur = obj;
607
+ for (const k of keys) {
608
+ if (cur === null || cur === undefined || typeof cur !== 'object')
609
+ return undefined;
610
+ cur = cur[k];
611
+ }
612
+ return cur;
613
+ };
614
+ const 以不可变方式写入路径 = (obj, path, value) => {
615
+ const keys = String(path).split('.').filter(Boolean);
616
+ if (keys.length === 0)
617
+ return obj;
618
+ const 递归 = (cur, idx) => {
619
+ const k = keys[idx];
620
+ const isLast = idx === keys.length - 1;
621
+ const base = (cur && typeof cur === 'object') ? cur : {};
622
+ const clone = { ...base };
623
+ if (isLast) {
624
+ clone[k] = value;
625
+ }
626
+ else {
627
+ clone[k] = 递归(base[k], idx + 1);
628
+ }
629
+ return clone;
630
+ };
631
+ return 递归(obj, 0);
632
+ };
633
+ const 读取高级选项 = (索引) => {
634
+ const raw = this.getNodeParameter('options', 索引, {});
635
+ const 取数 = (k, d) => {
636
+ const v = raw[k];
637
+ const n = typeof v === 'number' ? v : Number(v);
638
+ return Number.isFinite(n) ? n : d;
639
+ };
640
+ const 取布尔 = (k, d) => {
641
+ const v = raw[k];
642
+ return typeof v === 'boolean' ? v : d;
643
+ };
644
+ const 取字符串 = (k, d) => {
645
+ const v = raw[k];
646
+ return typeof v === 'string' ? v : d;
647
+ };
648
+ return {
649
+ a_parallelExecution: 取布尔('a_parallelExecution', false),
650
+ b_timeoutSeconds: 取数('b_timeoutSeconds', 300),
651
+ c_waitForCompletion: 取布尔('c_waitForCompletion', false),
652
+ d_pollIntervalSeconds: 取数('d_pollIntervalSeconds', 5),
653
+ e_batchSize: 取数('e_batchSize', 10),
654
+ f_outputFormat: 取字符串('f_outputFormat', 'fullResponse'),
655
+ g_parseResultString: 取布尔('g_parseResultString', false),
656
+ h_requestTimeoutSeconds: 取数('h_requestTimeoutSeconds', 120),
657
+ i_statusTimeoutSeconds: 取数('i_statusTimeoutSeconds', 30),
658
+ j_maxRetries: 取数('j_maxRetries', 2),
659
+ k_retryInitialDelayMs: 取数('k_retryInitialDelayMs', 800),
660
+ l_retryBackoffFactor: 取数('l_retryBackoffFactor', 2),
661
+ m_honorRetryAfter: 取布尔('m_honorRetryAfter', true),
662
+ n_failOnScriptError: 取布尔('n_failOnScriptError', true),
663
+ o_pollJitterMs: 取数('o_pollJitterMs', 300),
664
+ r_autoChunkEnabled: 取布尔('r_autoChunkEnabled', false),
665
+ s_chunkFieldPath: 取字符串('s_chunkFieldPath', '写表行列表'),
666
+ t_chunkSize: 取数('t_chunkSize', 200),
667
+ u_chunkMaxConcurrency: 取数('u_chunkMaxConcurrency', 3),
668
+ v_chunkOutputMode: 取字符串('v_chunkOutputMode', 'split'),
669
+ w_debug: 取布尔('w_debug', false),
670
+ };
671
+ };
672
+ const 提取脚本错误消息 = (响应) => {
673
+ var _a;
674
+ const error = String((_a = 响应.error) !== null && _a !== void 0 ? _a : '').trim();
675
+ if (error)
676
+ return error;
677
+ const details = 响应.error_details;
678
+ if (details && typeof details === 'object' && !Array.isArray(details)) {
679
+ const msg = details.msg;
680
+ if (typeof msg === 'string' && msg.trim())
681
+ return msg.trim();
682
+ }
683
+ return '';
684
+ };
685
+ const 校验脚本是否失败 = (响应, itemIndex, 开启) => {
686
+ if (!开启)
687
+ return;
688
+ const status = typeof 响应.status === 'string' ? 响应.status : '';
689
+ const errorMsg = 提取脚本错误消息(响应);
690
+ if (status === 'finished' && errorMsg) {
691
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `脚本执行失败:${errorMsg}`, { itemIndex });
692
+ }
693
+ };
694
+ const 提取TaskId = (响应) => {
695
+ var _a, _b;
696
+ const fromData = (_a = 响应.data) === null || _a === void 0 ? void 0 : _a.task_id;
697
+ const fromTop = 响应.task_id;
698
+ return String((_b = fromData !== null && fromData !== void 0 ? fromData : fromTop) !== null && _b !== void 0 ? _b : '').trim();
699
+ };
700
+ const 解析执行信息 = (itemIndex) => {
701
+ var _a;
236
702
  const id输入模式 = this.getNodeParameter('idInputMode', itemIndex, 'url');
237
- let 文件ID = '', 脚本ID = '';
703
+ let 文件ID = '';
704
+ let 脚本ID = '';
238
705
  if (id输入模式 === 'url') {
239
706
  const webhook链接 = this.getNodeParameter('webhookUrl', itemIndex, '');
240
707
  try {
@@ -250,7 +717,8 @@ class KingsoftAirscript {
250
717
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Webhook 链接格式不正确,无法解析出 File ID 和 Script ID。', { itemIndex });
251
718
  }
252
719
  }
253
- catch {
720
+ catch (e) {
721
+ void e;
254
722
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Webhook 链接格式不正确,无法解析出 File ID 和 Script ID。', { itemIndex });
255
723
  }
256
724
  }
@@ -261,214 +729,391 @@ class KingsoftAirscript {
261
729
  if (!文件ID || !脚本ID) {
262
730
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), '未能获取到有效的 File ID 和 Script ID。', { itemIndex });
263
731
  }
264
- const argv字符串 = this.getNodeParameter('argv', itemIndex, {});
732
+ const argvTemplate = this.getNodeParameter('argvTemplate', itemIndex, 'custom');
265
733
  const 可选上下文参数 = this.getNodeParameter('contextParameters', itemIndex, {});
266
- let argv参数;
267
- if (typeof argv字符串 === 'string') {
734
+ const 模板到字段名 = {
735
+ custom: 'argv_custom',
736
+ basicInput: 'argv_basicInput',
737
+ attachment: 'argv_attachment',
738
+ cascade: 'argv_cascade',
739
+ migrate: 'argv_migrate',
740
+ formula: 'argv_formula',
741
+ setLinkProperties: 'argv_setLinkProperties',
742
+ setCascadeOptions: 'argv_setCascadeOptions',
743
+ n8nExpression: 'argv_n8nExpression',
744
+ compositeKey: 'argv_compositeKey',
745
+ createSheet: 'argv_createSheet',
746
+ };
747
+ const argv字段名 = (_a = 模板到字段名[argvTemplate]) !== null && _a !== void 0 ? _a : 'argv_custom';
748
+ const argv原始 = this.getNodeParameter(argv字段名, itemIndex, {});
749
+ let argv参数 = {};
750
+ if (typeof argv原始 === 'string') {
268
751
  try {
269
- argv参数 = JSON.parse(argv字符串);
752
+ argv参数 = JSON.parse(argv原始);
270
753
  }
271
- catch {
272
- argv参数 = { value: argv字符串 };
754
+ catch (e) {
755
+ void e;
756
+ argv参数 = { value: argv原始 };
273
757
  }
274
758
  }
275
- else {
276
- argv参数 = argv字符串;
759
+ else if (argv原始 && typeof argv原始 === 'object' && !Array.isArray(argv原始)) {
760
+ argv参数 = argv原始;
277
761
  }
278
762
  for (const key in argv参数) {
279
- if (Object.prototype.hasOwnProperty.call(argv参数, key)) {
280
- const value = argv参数[key];
281
- if (typeof value === 'string') {
282
- const trimmedValue = value.trim();
283
- if ((trimmedValue.startsWith('{') && trimmedValue.endsWith('}')) || (trimmedValue.startsWith('[') && trimmedValue.endsWith(']'))) {
284
- try {
285
- argv参数[key] = JSON.parse(trimmedValue);
286
- console.log(`[N8N Node] 智能反序列化成功: 字段 '${key}' 已从字符串恢复为对象/数组。`);
287
- }
288
- catch {
289
- console.log(`[N8N Node] 字段 '${key}' 看起来像JSON但解析失败,保持为字符串。`);
290
- }
291
- }
763
+ if (!Object.prototype.hasOwnProperty.call(argv参数, key))
764
+ continue;
765
+ const value = argv参数[key];
766
+ if (typeof value !== 'string')
767
+ continue;
768
+ const trimmed = value.trim();
769
+ if ((trimmed.startsWith('{') && trimmed.endsWith('}')) ||
770
+ (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
771
+ try {
772
+ argv参数[key] = JSON.parse(trimmed);
773
+ }
774
+ catch (e) {
775
+ void e;
292
776
  }
293
777
  }
294
778
  }
779
+ const 规范化字符串 = (v) => (typeof v === 'string' ? v.trim() : '');
780
+ const 从Argv取 = (snake, camel) => {
781
+ const v1 = 规范化字符串(argv参数[snake]);
782
+ if (v1)
783
+ return v1;
784
+ if (camel) {
785
+ const v2 = 规范化字符串(argv参数[camel]);
786
+ if (v2)
787
+ return v2;
788
+ }
789
+ return '';
790
+ };
791
+ const 从UI取 = (key) => 规范化字符串(可选上下文参数[key]);
792
+ const 最终_sheet_name = 从Argv取('sheet_name', 'sheetName') || 从UI取('sheetName');
793
+ const 最终_range = 从Argv取('range') || 从UI取('range');
794
+ const 最终_link_from = 从Argv取('link_from', 'linkFrom') || 从UI取('linkFrom');
795
+ const 最终_db_active_view = 从Argv取('db_active_view', 'dbActiveView') || 从UI取('dbActiveView');
796
+ const 最终_db_selection = 从Argv取('db_selection', 'dbSelection') || 从UI取('dbSelection');
797
+ if (最终_sheet_name)
798
+ argv参数.sheet_name = 最终_sheet_name;
799
+ if (最终_range)
800
+ argv参数.range = 最终_range;
801
+ if (最终_link_from)
802
+ argv参数.link_from = 最终_link_from;
803
+ if (最终_db_active_view)
804
+ argv参数.db_active_view = 最终_db_active_view;
805
+ if (最终_db_selection)
806
+ argv参数.db_selection = 最终_db_selection;
295
807
  const 上下文 = { argv: argv参数 };
296
- if (可选上下文参数.sheetName)
297
- 上下文.sheet_name = 可选上下文参数.sheetName;
298
- if (可选上下文参数.range)
299
- 上下文.range = 可选上下文参数.range;
300
- if (可选上下文参数.linkFrom)
301
- 上下文.link_from = 可选上下文参数.linkFrom;
302
- if (可选上下文参数.dbActiveView)
303
- 上下文.db_active_view = 可选上下文参数.dbActiveView;
304
- if (可选上下文参数.dbSelection)
305
- 上下文.db_selection = 可选上下文参数.dbSelection;
306
- const 接口路径 = 类型 === 'sync' ? 'sync_task' : 'task';
307
- const 请求URL = `https://www.kdocs.cn/api/v3/ide/file/${文件ID}/script/${脚本ID}/${接口路径}`;
308
- return await this.helpers.httpRequest({
808
+ if (最终_sheet_name)
809
+ 上下文.sheet_name = 最终_sheet_name;
810
+ if (最终_range)
811
+ 上下文.range = 最终_range;
812
+ if (最终_link_from)
813
+ 上下文.link_from = 最终_link_from;
814
+ if (最终_db_active_view)
815
+ 上下文.db_active_view = 最终_db_active_view;
816
+ if (最终_db_selection)
817
+ 上下文.db_selection = 最终_db_selection;
818
+ return { 文件ID, 脚本ID, argv参数, 上下文 };
819
+ };
820
+ const 调用脚本接口 = async (参数) => {
821
+ var _a, _b;
822
+ const endpoint = 参数.类型 === 'sync' ? API_CONFIG.ENDPOINTS.SYNC_TASK : API_CONFIG.ENDPOINTS.ASYNC_TASK;
823
+ const 请求URL = `${API_CONFIG.BASE_URL}${endpoint}`
824
+ .replace(':file_id', 参数.文件ID)
825
+ .replace(':script_id', 参数.脚本ID);
826
+ const t0 = Date.now();
827
+ const 调试统计 = {};
828
+ const options = {
309
829
  method: 'POST',
310
830
  url: 请求URL,
311
- headers: { 'Content-Type': 'application/json', 'AirScript-Token': 凭证.apiToken },
312
- body: { Context: 上下文 },
831
+ headers: {
832
+ 'Content-Type': 'application/json',
833
+ 'AirScript-Token': 凭证.apiToken,
834
+ },
835
+ body: { Context: 参数.上下文 },
313
836
  json: true,
314
- });
837
+ timeout: 参数.请求超时毫秒,
838
+ };
839
+ const res = (await 带重试(async () => (await this.helpers.httpRequest(options)), 参数.重试, 调试统计));
840
+ if (参数.调试) {
841
+ 参数.调试['执行脚本'] = {
842
+ url: `/api/v3/ide/file/${脱敏ID(参数.文件ID)}/script/${脱敏ID(参数.脚本ID)}/${参数.类型 === 'sync' ? 'sync_task' : 'task'}`,
843
+ 耗时毫秒: Date.now() - t0,
844
+ 重试次数: (_a = 调试统计.重试次数) !== null && _a !== void 0 ? _a : 0,
845
+ 最后等待毫秒: (_b = 调试统计.最后等待毫秒) !== null && _b !== void 0 ? _b : null,
846
+ };
847
+ }
848
+ return res;
315
849
  };
316
- const 获取任务状态 = async (任务ID) => {
317
- return await this.helpers.httpRequest({
850
+ const 获取任务状态 = async (参数) => {
851
+ var _a, _b;
852
+ const 请求URL = `${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.GET_TASK}`;
853
+ const t0 = Date.now();
854
+ const 调试统计 = {};
855
+ const taskIdEncoded = encodeURIComponent(参数.taskId);
856
+ const options = {
318
857
  method: 'GET',
319
- url: `https://www.kdocs.cn/api/v3/script/task`,
320
- headers: { 'Content-Type': 'application/json' },
321
- qs: { task_id: 任务ID },
858
+ url: 请求URL,
859
+ headers: { 'Content-Type': 'application/json', 'AirScript-Token': 凭证.apiToken },
860
+ qs: { task_id: taskIdEncoded },
322
861
  json: true,
862
+ timeout: 参数.状态超时毫秒,
863
+ };
864
+ const res = (await 带重试(async () => (await this.helpers.httpRequest(options)), 参数.重试, 调试统计));
865
+ if (参数.调试) {
866
+ 参数.调试['获取任务状态'] = {
867
+ task_id: 脱敏ID(参数.taskId),
868
+ 耗时毫秒: Date.now() - t0,
869
+ 重试次数: (_a = 调试统计.重试次数) !== null && _a !== void 0 ? _a : 0,
870
+ 最后等待毫秒: (_b = 调试统计.最后等待毫秒) !== null && _b !== void 0 ? _b : null,
871
+ };
872
+ }
873
+ return res;
874
+ };
875
+ const 等待异步任务完成 = async (参数) => {
876
+ const 截止时间 = Date.now() + 参数.总等待超时毫秒;
877
+ let 首次 = true;
878
+ let 轮询次数 = 0;
879
+ while (Date.now() < 截止时间) {
880
+ if (!首次) {
881
+ const 抖动 = 参数.轮询抖动毫秒 > 0 ? Math.floor(Math.random() * 参数.轮询抖动毫秒) : 0;
882
+ await 等待毫秒(参数.轮询间隔毫秒 + 抖动);
883
+ }
884
+ 首次 = false;
885
+ const 状态响应 = await 获取任务状态({
886
+ taskId: 参数.taskId,
887
+ 状态超时毫秒: 参数.状态超时毫秒,
888
+ 重试: 参数.重试,
889
+ 调试: 参数.调试,
890
+ });
891
+ const 状态 = typeof 状态响应.status === 'string' ? 状态响应.status : '';
892
+ if (状态 === 'finished') {
893
+ 校验脚本是否失败(状态响应, 参数.itemIndex, 参数.finished但error算失败);
894
+ if (参数.调试)
895
+ 参数.调试['轮询次数'] = (轮询次数 + 1);
896
+ return 状态响应;
897
+ }
898
+ 轮询次数++;
899
+ }
900
+ if (参数.调试)
901
+ 参数.调试['轮询次数'] = 轮询次数;
902
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `异步任务等待超时(${Math.round(参数.总等待超时毫秒 / 1000)}秒)`, {
903
+ itemIndex: 参数.itemIndex,
323
904
  });
324
905
  };
325
- const 处理单个项目 = async (索引) => {
326
- var _a, _b, _c, _d, _e, _f;
327
- try {
328
- const 操作类型 = this.getNodeParameter('operation', 索引, 'runScriptSync');
329
- const 高级选项 = this.getNodeParameter('options', 索引, {});
330
- let 响应数据;
331
- if (操作类型 === 'runScriptSync') {
332
- 响应数据 = await 执行脚本(索引, 'sync');
906
+ const 格式化输出 = (响应数据, 索引, opt, 附加) => {
907
+ var _a, _b, _c, _d;
908
+ const data = ((_a = 响应数据.data) !== null && _a !== void 0 ? _a : undefined);
909
+ if (opt.g_parseResultString && data && typeof data.result === 'string') {
910
+ try {
911
+ data.result = JSON.parse(data.result);
333
912
  }
334
- else if (操作类型 === 'runScriptAsync') {
335
- const 初始响应 = await 执行脚本(索引, 'async');
336
- if (!高级选项.c_waitForCompletion) {
337
- 响应数据 = 初始响应;
338
- }
339
- else {
340
- const 任务ID = (((_a = 初始响应.data) === null || _a === void 0 ? void 0 : _a.task_id) || 初始响应.task_id);
341
- if (!任务ID)
342
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), '无法从异步响应中获取 task_id。', { itemIndex: 索引 });
343
- const 轮询间隔 = ((_b = 高级选项.d_pollIntervalSeconds) !== null && _b !== void 0 ? _b : 5) * 1000;
344
- const 超时时间 = ((_c = 高级选项.b_timeoutSeconds) !== null && _c !== void 0 ? _c : 300) * 1000;
345
- let 轮询次数 = 0;
346
- const 最大轮询次数 = Math.floor(超时时间 / 轮询间隔);
347
- while (轮询次数 < 最大轮询次数) {
348
- if (轮询次数 > 0) {
349
- }
350
- const 状态响应 = await 获取任务状态(任务ID);
351
- if (状态响应.status === 'finished') {
352
- 响应数据 = 状态响应;
353
- break;
354
- }
355
- 轮询次数++;
356
- }
357
- if (!响应数据) {
358
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), `异步任务等待超时(${超时时间 / 1000}秒)。`, { itemIndex: 索引 });
913
+ catch (e) {
914
+ void e;
915
+ data.result = data.result.replace(/\\n/g, '\n');
916
+ }
917
+ }
918
+ const out = [];
919
+ if (opt.f_outputFormat === 'resultOnly') {
920
+ out.push({
921
+ json: {
922
+ ...(附加 !== null && 附加 !== void 0 ? 附加 : {}),
923
+ result: (_b = data === null || data === void 0 ? void 0 : data.result) !== null && _b !== void 0 ? _b : null,
924
+ status: ((_c = 响应数据.status) !== null && _c !== void 0 ? _c : null),
925
+ error: ((_d = 响应数据.error) !== null && _d !== void 0 ? _d : ''),
926
+ },
927
+ pairedItem: { item: 索引 },
928
+ });
929
+ return out;
930
+ }
931
+ if (opt.f_outputFormat === 'splitLogs') {
932
+ const logs = data === null || data === void 0 ? void 0 : data.logs;
933
+ if (Array.isArray(logs)) {
934
+ for (const 日志条目 of logs) {
935
+ if (日志条目 && typeof 日志条目 === 'object') {
936
+ out.push({ json: { ...(附加 !== null && 附加 !== void 0 ? 附加 : {}), ...日志条目 }, pairedItem: { item: 索引 } });
359
937
  }
360
938
  }
361
939
  }
362
- else if (操作类型 === 'getTaskStatus') {
363
- const 任务ID = this.getNodeParameter('taskId', 索引, '');
364
- 响应数据 = await 获取任务状态(任务ID);
365
- }
366
- const 单项的返回数据列表 = [];
367
- if (响应数据) {
368
- const data = 响应数据.data;
369
- if (高级选项.g_parseResultString && data && typeof data.result === 'string') {
370
- try {
371
- data.result = JSON.parse(data.result);
372
- }
373
- catch {
374
- if (data.result) {
375
- data.result = data.result.replace(/\\n/g, '\n');
376
- }
940
+ return out;
941
+ }
942
+ if (opt.f_outputFormat === 'splitErrors') {
943
+ const logs = data === null || data === void 0 ? void 0 : data.logs;
944
+ if (Array.isArray(logs)) {
945
+ for (const 日志条目 of logs) {
946
+ if (日志条目 && typeof 日志条目 === 'object') {
947
+ const level = 日志条目.level;
948
+ if (level === 'error')
949
+ out.push({ json: { ...(附加 !== null && 附加 !== void 0 ? 附加 : {}), ...日志条目 }, pairedItem: { item: 索引 } });
377
950
  }
378
951
  }
379
- switch (高级选项.f_outputFormat) {
380
- case 'resultOnly':
381
- 单项的返回数据列表.push({ json: { result: (_d = data === null || data === void 0 ? void 0 : data.result) !== null && _d !== void 0 ? _d : null }, pairedItem: { item: 索引 } });
382
- break;
383
- case 'splitLogs':
384
- if ((data === null || data === void 0 ? void 0 : data.logs) && Array.isArray(data.logs)) {
385
- data.logs.forEach((日志条目) => 单项的返回数据列表.push({ json: 日志条目, pairedItem: { item: 索引 } }));
386
- }
387
- break;
388
- case 'splitErrors':
389
- if ((data === null || data === void 0 ? void 0 : data.logs) && Array.isArray(data.logs)) {
390
- data.logs
391
- .filter((log) => log.level === 'error')
392
- .forEach((日志条目) => 单项的返回数据列表.push({ json: 日志条目, pairedItem: { item: 索引 } }));
393
- }
394
- break;
395
- default:
396
- 单项的返回数据列表.push({ json: 响应数据, pairedItem: { item: 索引 } });
397
- break;
398
- }
399
952
  }
400
- return 单项的返回数据列表;
953
+ return out;
401
954
  }
402
- catch (error) {
403
- if (this.continueOnFail()) {
404
- let 错误消息 = '未知错误';
405
- let 错误详情;
406
- if (error instanceof Error) {
407
- 错误消息 = error.message;
955
+ out.push({ json: { ...(附加 !== null && 附加 !== void 0 ? 附加 : {}), ...响应数据 }, pairedItem: { item: 索引 } });
956
+ return out;
957
+ };
958
+ const 处理单个项目 = async (索引) => {
959
+ const 操作类型 = this.getNodeParameter('operation', 索引, 'runScriptSync');
960
+ const opt = 读取高级选项(索引);
961
+ const 重试 = {
962
+ 最大重试次数: opt.j_maxRetries,
963
+ 初始等待毫秒: opt.k_retryInitialDelayMs,
964
+ 退避倍数: opt.l_retryBackoffFactor,
965
+ 尊重RetryAfter: opt.m_honorRetryAfter,
966
+ };
967
+ const 调试字段 = opt.w_debug ? {} : undefined;
968
+ if (操作类型 === 'getTaskStatus') {
969
+ const taskId = this.getNodeParameter('taskId', 索引, '');
970
+ const 状态响应 = await 获取任务状态({
971
+ taskId,
972
+ 状态超时毫秒: opt.i_statusTimeoutSeconds * 1000,
973
+ 重试,
974
+ 调试: 调试字段,
975
+ });
976
+ 校验脚本是否失败(状态响应, 索引, opt.n_failOnScriptError);
977
+ const 附加 = opt.w_debug ? { _调试: 调试字段 } : undefined;
978
+ return 格式化输出(状态响应, 索引, opt, 附加);
979
+ }
980
+ const { 文件ID, 脚本ID, argv参数, 上下文 } = 解析执行信息(索引);
981
+ const 执行一次 = async (上下文入参, 调试对象) => {
982
+ if (操作类型 === 'runScriptSync') {
983
+ const 响应 = await 调用脚本接口({
984
+ 类型: 'sync',
985
+ 文件ID,
986
+ 脚本ID,
987
+ 上下文: 上下文入参,
988
+ 请求超时毫秒: opt.h_requestTimeoutSeconds * 1000,
989
+ 重试,
990
+ 调试: 调试对象,
991
+ });
992
+ 校验脚本是否失败(响应, 索引, opt.n_failOnScriptError);
993
+ return 响应;
994
+ }
995
+ if (操作类型 === 'runScriptAsync') {
996
+ const 初始响应 = await 调用脚本接口({
997
+ 类型: 'async',
998
+ 文件ID,
999
+ 脚本ID,
1000
+ 上下文: 上下文入参,
1001
+ 请求超时毫秒: opt.h_requestTimeoutSeconds * 1000,
1002
+ 重试,
1003
+ 调试: 调试对象,
1004
+ });
1005
+ if (!opt.c_waitForCompletion) {
1006
+ return 初始响应;
408
1007
  }
409
- if (error && typeof error === 'object' && 'response' in error) {
410
- const httpError = error;
411
- if ((_e = httpError.response) === null || _e === void 0 ? void 0 : _e.data) {
412
- 错误详情 = httpError.response.data;
413
- 错误消息 = ((_f = httpError.response.data.error_details) === null || _f === void 0 ? void 0 : _f.msg) ||
414
- httpError.response.data.error ||
415
- httpError.message ||
416
- 错误消息;
417
- }
1008
+ const taskId = 提取TaskId(初始响应);
1009
+ if (!taskId) {
1010
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), '异步执行返回的响应中未找到 task_id(data.task_id task_id)。', { itemIndex: 索引 });
418
1011
  }
419
- return [{
420
- json: {
421
- error: 错误消息,
422
- details: 错误详情
423
- },
424
- pairedItem: { item: 索引 }
425
- }];
1012
+ return await 等待异步任务完成({
1013
+ itemIndex: 索引,
1014
+ taskId,
1015
+ 轮询间隔毫秒: opt.d_pollIntervalSeconds * 1000,
1016
+ 轮询抖动毫秒: opt.o_pollJitterMs,
1017
+ 总等待超时毫秒: opt.b_timeoutSeconds * 1000,
1018
+ 状态超时毫秒: opt.i_statusTimeoutSeconds * 1000,
1019
+ 重试,
1020
+ finished但error算失败: opt.n_failOnScriptError,
1021
+ 调试: 调试对象,
1022
+ });
1023
+ }
1024
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `未知的操作类型: ${操作类型}`, { itemIndex: 索引 });
1025
+ };
1026
+ if (opt.r_autoChunkEnabled) {
1027
+ const 数组路径 = opt.s_chunkFieldPath;
1028
+ const 目标数组 = 取路径值(argv参数, 数组路径);
1029
+ if (!Array.isArray(目标数组)) {
1030
+ const 响应 = await 执行一次(上下文, 调试字段);
1031
+ const 附加 = opt.w_debug ? { _调试: 调试字段 } : undefined;
1032
+ return 格式化输出(响应, 索引, opt, 附加);
1033
+ }
1034
+ const 批次大小 = Math.max(1, opt.t_chunkSize);
1035
+ const 批次列表 = [];
1036
+ for (let i = 0; i < 目标数组.length; i += 批次大小) {
1037
+ 批次列表.push(目标数组.slice(i, i + 批次大小));
1038
+ }
1039
+ if (批次列表.length === 0) {
1040
+ const 响应 = await 执行一次(上下文, 调试字段);
1041
+ const 附加 = opt.w_debug ? { _调试: 调试字段 } : undefined;
1042
+ return 格式化输出(响应, 索引, opt, 附加);
1043
+ }
1044
+ const 并发数 = Math.max(1, opt.u_chunkMaxConcurrency);
1045
+ const 任务列表 = 批次列表.map((批次, 批次索引) => async () => {
1046
+ const 批次调试对象 = opt.w_debug ? {} : undefined;
1047
+ const 批次上下文 = {
1048
+ ...上下文,
1049
+ argv: 以不可变方式写入路径(argv参数, 数组路径, 批次),
1050
+ };
1051
+ const 响应 = await 执行一次(批次上下文, 批次调试对象);
1052
+ const 批次附加 = opt.w_debug
1053
+ ? { _调试: { ...(批次调试对象 !== null && 批次调试对象 !== void 0 ? 批次调试对象 : {}), 批次索引 } }
1054
+ : { 批次索引 };
1055
+ return 格式化输出(响应, 索引, opt, 批次附加);
1056
+ });
1057
+ const 批次结果 = await 并发执行(任务列表, 并发数);
1058
+ if (opt.v_chunkOutputMode === 'merge') {
1059
+ const 合并结果 = {
1060
+ 批次输出列表: 批次结果.flat().map(x => x.json),
1061
+ ...(opt.w_debug ? { _调试: 调试字段 } : {}),
1062
+ };
1063
+ return [{ json: 合并结果, pairedItem: { item: 索引 } }];
1064
+ }
1065
+ else {
1066
+ return 批次结果.flat();
426
1067
  }
427
- throw error;
1068
+ }
1069
+ else {
1070
+ const 响应 = await 执行一次(上下文, 调试字段);
1071
+ const 附加 = opt.w_debug ? { _调试: 调试字段 } : undefined;
1072
+ return 格式化输出(响应, 索引, opt, 附加);
428
1073
  }
429
1074
  };
430
- const 返回数据列表 = [];
431
- const 性能选项 = this.getNodeParameter('options', 0, {});
432
- if (!性能选项.a_parallelExecution || 输入数据项列表.length <= 1) {
433
- for (let 索引 = 0; 索引 < 输入数据项列表.length; 索引++) {
434
- const 单项的处理结果 = await 处理单个项目(索引);
435
- 返回数据列表.push(...单项的处理结果);
1075
+ const 安全执行单个项目 = async (itemIndex) => {
1076
+ var _a, _b, _c, _d, _e;
1077
+ try {
1078
+ return await 处理单个项目(itemIndex);
1079
+ }
1080
+ catch (e) {
1081
+ if (!this.continueOnFail())
1082
+ throw e;
1083
+ const err = 提取Http错误(e);
1084
+ return [
1085
+ {
1086
+ json: {
1087
+ error: 提取错误消息(err),
1088
+ code: (_a = err.code) !== null && _a !== void 0 ? _a : null,
1089
+ statusCode: (_c = (_b = err.response) === null || _b === void 0 ? void 0 : _b.status) !== null && _c !== void 0 ? _c : null,
1090
+ details: (_e = (_d = err.response) === null || _d === void 0 ? void 0 : _d.data) !== null && _e !== void 0 ? _e : null,
1091
+ },
1092
+ pairedItem: { item: itemIndex },
1093
+ },
1094
+ ];
1095
+ }
1096
+ };
1097
+ const opt = 读取高级选项(0);
1098
+ const 汇总输出 = [];
1099
+ if (opt.a_parallelExecution) {
1100
+ const 批处理数量 = Math.max(1, opt.e_batchSize);
1101
+ for (let i = 0; i < 输入数据项列表.length; i += 批处理数量) {
1102
+ const 索引列表 = 输入数据项列表.slice(i, i + 批处理数量).map((_, localIndex) => i + localIndex);
1103
+ const 任务函数列表 = 索引列表.map((idx) => () => 安全执行单个项目(idx));
1104
+ const 批次结果 = await 并发执行(任务函数列表, 批处理数量);
1105
+ for (const 单项结果 of 批次结果) {
1106
+ 汇总输出.push(...单项结果);
1107
+ }
436
1108
  }
437
1109
  }
438
1110
  else {
439
- const 批处理大小 = (_a = 性能选项.e_batchSize) !== null && _a !== void 0 ? _a : 10;
440
- for (let i = 0; i < 输入数据项列表.length; i += 批处理大小) {
441
- const 当前批次的索引范围 = 输入数据项列表.slice(i, i + 批处理大小).map((_, 本地索引) => i + 本地索引);
442
- const 批处理Promises = 当前批次的索引范围.map(真实索引 => 处理单个项目(真实索引));
443
- const 批处理结果 = await Promise.allSettled(批处理Promises);
444
- 批处理结果.forEach((result, 本地索引) => {
445
- var _a, _b, _c, _d, _e, _f;
446
- const 真实索引 = i + 本地索引;
447
- if (result.status === 'fulfilled') {
448
- 返回数据列表.push(...result.value);
449
- }
450
- else {
451
- if (!this.continueOnFail()) {
452
- const originalError = result.reason;
453
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), originalError, { itemIndex: 真实索引 });
454
- }
455
- const typedReason = result.reason;
456
- const 错误消息 = ((_c = (_b = (_a = typedReason.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.error_details) === null || _c === void 0 ? void 0 : _c.msg) ||
457
- ((_e = (_d = typedReason.response) === null || _d === void 0 ? void 0 : _d.data) === null || _e === void 0 ? void 0 : _e.error) ||
458
- typedReason.message ||
459
- '未知错误';
460
- 返回数据列表.push({
461
- json: {
462
- error: 错误消息,
463
- details: (_f = typedReason.response) === null || _f === void 0 ? void 0 : _f.data
464
- },
465
- pairedItem: { item: 真实索引 }
466
- });
467
- }
468
- });
1111
+ for (let i = 0; i < 输入数据项列表.length; i++) {
1112
+ const 单项结果 = await 安全执行单个项目(i);
1113
+ 汇总输出.push(...单项结果);
469
1114
  }
470
1115
  }
471
- return [this.helpers.returnJsonArray(返回数据列表)];
1116
+ return [汇总输出];
472
1117
  }
473
1118
  }
474
1119
  exports.KingsoftAirscript = KingsoftAirscript;