n8n-nodes-kingsoft-airscript 0.3.4 → 2.0.4

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