@fsai-flow/workflow 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/.eslintrc.json +33 -0
  2. package/README.md +11 -0
  3. package/dist/README.md +11 -0
  4. package/dist/package.json +42 -0
  5. package/dist/src/index.d.ts +21 -0
  6. package/dist/src/index.js +33 -0
  7. package/dist/src/index.js.map +1 -0
  8. package/dist/src/lib/Constants.d.ts +68 -0
  9. package/dist/src/lib/Constants.js +106 -0
  10. package/dist/src/lib/Constants.js.map +1 -0
  11. package/dist/src/lib/DeferredPromise.d.ts +6 -0
  12. package/dist/src/lib/DeferredPromise.js +11 -0
  13. package/dist/src/lib/DeferredPromise.js.map +1 -0
  14. package/dist/src/lib/Expression.d.ts +65 -0
  15. package/dist/src/lib/Expression.js +215 -0
  16. package/dist/src/lib/Expression.js.map +1 -0
  17. package/dist/src/lib/Interfaces.d.ts +1569 -0
  18. package/dist/src/lib/Interfaces.js +44 -0
  19. package/dist/src/lib/Interfaces.js.map +1 -0
  20. package/dist/src/lib/LoggerProxy.d.ts +9 -0
  21. package/dist/src/lib/LoggerProxy.js +40 -0
  22. package/dist/src/lib/LoggerProxy.js.map +1 -0
  23. package/dist/src/lib/MetadataUtils.d.ts +4 -0
  24. package/dist/src/lib/MetadataUtils.js +27 -0
  25. package/dist/src/lib/MetadataUtils.js.map +1 -0
  26. package/dist/src/lib/NodeErrors.d.ts +82 -0
  27. package/dist/src/lib/NodeErrors.js +289 -0
  28. package/dist/src/lib/NodeErrors.js.map +1 -0
  29. package/dist/src/lib/NodeHelpers.d.ts +198 -0
  30. package/dist/src/lib/NodeHelpers.js +1348 -0
  31. package/dist/src/lib/NodeHelpers.js.map +1 -0
  32. package/dist/src/lib/ObservableObject.d.ts +5 -0
  33. package/dist/src/lib/ObservableObject.js +61 -0
  34. package/dist/src/lib/ObservableObject.js.map +1 -0
  35. package/dist/src/lib/RoutingNode.d.ts +18 -0
  36. package/dist/src/lib/RoutingNode.js +508 -0
  37. package/dist/src/lib/RoutingNode.js.map +1 -0
  38. package/dist/src/lib/TelemetryHelpers.d.ts +3 -0
  39. package/dist/src/lib/TelemetryHelpers.js +69 -0
  40. package/dist/src/lib/TelemetryHelpers.js.map +1 -0
  41. package/dist/src/lib/TypeValidation.d.ts +21 -0
  42. package/dist/src/lib/TypeValidation.js +385 -0
  43. package/dist/src/lib/TypeValidation.js.map +1 -0
  44. package/dist/src/lib/VersionedNodeType.d.ts +9 -0
  45. package/dist/src/lib/VersionedNodeType.js +26 -0
  46. package/dist/src/lib/VersionedNodeType.js.map +1 -0
  47. package/dist/src/lib/Workflow.d.ts +248 -0
  48. package/dist/src/lib/Workflow.js +901 -0
  49. package/dist/src/lib/Workflow.js.map +1 -0
  50. package/dist/src/lib/WorkflowDataProxy.d.ts +87 -0
  51. package/dist/src/lib/WorkflowDataProxy.js +556 -0
  52. package/dist/src/lib/WorkflowDataProxy.js.map +1 -0
  53. package/dist/src/lib/WorkflowErrors.d.ts +9 -0
  54. package/dist/src/lib/WorkflowErrors.js +18 -0
  55. package/dist/src/lib/WorkflowErrors.js.map +1 -0
  56. package/dist/src/lib/WorkflowHooks.d.ts +11 -0
  57. package/dist/src/lib/WorkflowHooks.js +34 -0
  58. package/dist/src/lib/WorkflowHooks.js.map +1 -0
  59. package/dist/src/lib/errors/base/base.error.d.ts +30 -0
  60. package/dist/src/lib/errors/base/base.error.js +45 -0
  61. package/dist/src/lib/errors/base/base.error.js.map +1 -0
  62. package/dist/src/lib/errors/base/operational.error.d.ts +15 -0
  63. package/dist/src/lib/errors/base/operational.error.js +19 -0
  64. package/dist/src/lib/errors/base/operational.error.js.map +1 -0
  65. package/dist/src/lib/errors/error.types.d.ts +11 -0
  66. package/dist/src/lib/errors/error.types.js +3 -0
  67. package/dist/src/lib/errors/error.types.js.map +1 -0
  68. package/dist/src/lib/errors/index.d.ts +1 -0
  69. package/dist/src/lib/errors/index.js +6 -0
  70. package/dist/src/lib/errors/index.js.map +1 -0
  71. package/dist/src/lib/result.d.ts +19 -0
  72. package/dist/src/lib/result.js +36 -0
  73. package/dist/src/lib/result.js.map +1 -0
  74. package/dist/src/lib/utils.d.ts +50 -0
  75. package/dist/src/lib/utils.js +110 -0
  76. package/dist/src/lib/utils.js.map +1 -0
  77. package/eslint.config.js +19 -0
  78. package/jest.config.ts +10 -0
  79. package/package.json +40 -0
  80. package/project.json +19 -0
  81. package/src/index.ts +33 -0
  82. package/src/lib/Constants.ts +124 -0
  83. package/src/lib/DeferredPromise.ts +14 -0
  84. package/src/lib/Expression.ts +375 -0
  85. package/src/lib/Interfaces.ts +2229 -0
  86. package/src/lib/LoggerProxy.ts +43 -0
  87. package/src/lib/MetadataUtils.ts +34 -0
  88. package/src/lib/NodeErrors.ts +332 -0
  89. package/src/lib/NodeHelpers.ts +1666 -0
  90. package/src/lib/ObservableObject.ts +77 -0
  91. package/src/lib/RoutingNode.ts +862 -0
  92. package/src/lib/TelemetryHelpers.ts +86 -0
  93. package/src/lib/TypeValidation.ts +431 -0
  94. package/src/lib/VersionedNodeType.ts +30 -0
  95. package/src/lib/Workflow.ts +1266 -0
  96. package/src/lib/WorkflowDataProxy.ts +708 -0
  97. package/src/lib/WorkflowErrors.ts +18 -0
  98. package/src/lib/WorkflowHooks.ts +51 -0
  99. package/src/lib/errors/base/base.error.ts +68 -0
  100. package/src/lib/errors/base/operational.error.ts +21 -0
  101. package/src/lib/errors/error.types.ts +14 -0
  102. package/src/lib/errors/index.ts +1 -0
  103. package/src/lib/result.ts +34 -0
  104. package/src/lib/utils.ts +132 -0
  105. package/tests/Helpers.ts +667 -0
  106. package/tests/NodeHelpers.test.ts +3053 -0
  107. package/tests/ObservableObject.test.ts +171 -0
  108. package/tests/RoutingNode.test.ts +1680 -0
  109. package/tests/Workflow.test.ts +1284 -0
  110. package/tests/WorkflowDataProxy.test.ts +199 -0
  111. package/tsconfig.json +27 -0
  112. package/tsconfig.lib.json +11 -0
  113. package/tsconfig.spec.json +14 -0
@@ -0,0 +1,862 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-call */
2
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
3
+ /* eslint-disable import/no-cycle */
4
+ /* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
5
+ /* eslint-disable no-param-reassign */
6
+ /* eslint-disable no-continue */
7
+ /* eslint-disable @typescript-eslint/no-unsafe-return */
8
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
9
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
10
+ /* eslint-disable no-await-in-loop */
11
+ /* eslint-disable no-restricted-syntax */
12
+ const get = require('lodash.get');
13
+ const merge = require('lodash.merge');
14
+
15
+ import {
16
+ ICredentialDataDecryptedObject,
17
+ ICredentialsDecrypted,
18
+ IHttpRequestOptions,
19
+ IN8nHttpFullResponse,
20
+ INode,
21
+ INodeExecuteFunctions,
22
+ INodeExecutionData,
23
+ INodeParameters,
24
+ INodePropertyOptions,
25
+ INodeType,
26
+ IRequestOptionsFromParameters,
27
+ IRunExecutionData,
28
+ ITaskDataConnections,
29
+ IWorkflowDataProxyAdditionalKeys,
30
+ IWorkflowExecuteAdditionalData,
31
+ NodeHelpers,
32
+ NodeParameterValue,
33
+ Workflow,
34
+ WorkflowExecuteMode,
35
+ } from '..';
36
+
37
+ import {
38
+ CloseFunction,
39
+ IDataObject,
40
+ IExecuteSingleFunctions,
41
+ IN8nRequestOperations,
42
+ INodeProperties,
43
+ INodePropertyCollection,
44
+ PostReceiveAction,
45
+ } from './Interfaces';
46
+
47
+ export class RoutingNode {
48
+ additionalData: IWorkflowExecuteAdditionalData;
49
+
50
+ connectionInputData: INodeExecutionData[];
51
+
52
+ node: INode;
53
+
54
+ mode: WorkflowExecuteMode;
55
+
56
+ runExecutionData: IRunExecutionData;
57
+
58
+ workflow: Workflow;
59
+
60
+ constructor(
61
+ workflow: Workflow,
62
+ node: INode,
63
+ connectionInputData: INodeExecutionData[],
64
+ runExecutionData: IRunExecutionData,
65
+ additionalData: IWorkflowExecuteAdditionalData,
66
+ mode: WorkflowExecuteMode,
67
+ ) {
68
+ this.additionalData = additionalData;
69
+ this.connectionInputData = connectionInputData;
70
+ this.runExecutionData = runExecutionData;
71
+ this.mode = mode;
72
+ this.node = node;
73
+ this.workflow = workflow;
74
+ }
75
+
76
+ async runNode(
77
+ inputData: ITaskDataConnections,
78
+ runIndex: number,
79
+ nodeType: INodeType,
80
+ nodeExecuteFunctions: INodeExecuteFunctions,
81
+ credentialsDecrypted?: ICredentialsDecrypted,
82
+ ): Promise<INodeExecutionData[][] | null | undefined> {
83
+ const items = inputData['main'][0] as INodeExecutionData[];
84
+ const returnData: INodeExecutionData[] = [];
85
+ const closeFunctions: CloseFunction[] = [];
86
+ let responseData;
87
+
88
+ let credentialType: string | undefined;
89
+
90
+ if (nodeType.description.credentials?.length) {
91
+ credentialType = nodeType.description.credentials[0].name;
92
+ }
93
+ const executeFunctions = nodeExecuteFunctions.getExecuteFunctions(
94
+ this.workflow,
95
+ this.runExecutionData,
96
+ runIndex,
97
+ this.connectionInputData,
98
+ inputData,
99
+ this.node,
100
+ this.additionalData,
101
+ this.mode,
102
+ nodeType,
103
+ closeFunctions,
104
+ );
105
+
106
+ let credentials: ICredentialDataDecryptedObject | undefined;
107
+ if (credentialsDecrypted) {
108
+ credentials = credentialsDecrypted.data;
109
+ } else if (credentialType) {
110
+ credentials = (await executeFunctions.getCredentials(credentialType)) || {};
111
+ }
112
+
113
+ // TODO: Think about how batching could be handled for REST APIs which support it
114
+ for (let i = 0; i < items.length; i++) {
115
+ try {
116
+ const thisArgs = nodeExecuteFunctions.getExecuteSingleFunctions(
117
+ this.workflow,
118
+ this.runExecutionData,
119
+ runIndex,
120
+ this.connectionInputData,
121
+ inputData,
122
+ this.node,
123
+ i,
124
+ this.additionalData,
125
+ this.mode,
126
+ );
127
+
128
+ const requestData: IRequestOptionsFromParameters = {
129
+ options: {
130
+ qs: {},
131
+ body: {},
132
+ },
133
+ preSend: [],
134
+ postReceive: [],
135
+ requestOperations: {},
136
+ };
137
+
138
+ if (nodeType.description.requestOperations) {
139
+ requestData.requestOperations = { ...nodeType.description.requestOperations };
140
+ }
141
+
142
+ if (nodeType.description.requestDefaults) {
143
+ for (const key of Object.keys(nodeType.description.requestDefaults)) {
144
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
145
+ let value = (nodeType.description.requestDefaults as Record<string, any>)[key];
146
+ // If the value is an expression resolve it
147
+ value = this.getParameterValue(
148
+ value,
149
+ i,
150
+ runIndex,
151
+ { $credentials: credentials },
152
+ true,
153
+ ) as string;
154
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
155
+ (requestData.options as Record<string, any>)[key] = value;
156
+ }
157
+ }
158
+
159
+ for (const property of nodeType.description.properties) {
160
+ let value = get(this.node.parameters, property.name, []) as string | NodeParameterValue;
161
+ // If the value is an expression resolve it
162
+ value = this.getParameterValue(
163
+ value,
164
+ i,
165
+ runIndex,
166
+ { $credentials: credentials },
167
+ true,
168
+ ) as string | NodeParameterValue;
169
+
170
+ const tempOptions = this.getRequestOptionsFromParameters(
171
+ thisArgs,
172
+ property,
173
+ i,
174
+ runIndex,
175
+ '',
176
+ { $credentials: credentials, $value: value },
177
+ );
178
+
179
+ this.mergeOptions(requestData, tempOptions);
180
+ }
181
+
182
+ // TODO: Change to handle some requests in parallel (should be configurable)
183
+ responseData = await this.makeRoutingRequest(
184
+ requestData,
185
+ thisArgs,
186
+ i,
187
+ runIndex,
188
+ credentialType,
189
+ requestData.requestOperations,
190
+ credentialsDecrypted,
191
+ );
192
+
193
+ if (requestData.maxResults) {
194
+ // Remove not needed items in case APIs return to many
195
+ responseData.splice(requestData.maxResults as number);
196
+ }
197
+
198
+ returnData.push(...responseData);
199
+ } catch (error: any) {
200
+ if (get(this.node, 'continueOnFail', false)) {
201
+ returnData.push({ json: {}, error: error.message });
202
+ continue;
203
+ }
204
+
205
+ const closeFunctionsResults = await Promise.allSettled(
206
+ closeFunctions.map(async (fn) => await fn()),
207
+ );
208
+
209
+ const closingErrors = closeFunctionsResults
210
+ .filter((result): result is PromiseRejectedResult => result.status === 'rejected')
211
+ .map((result) => result.reason);
212
+
213
+ if (closingErrors.length > 0) {
214
+ if (closingErrors[0] instanceof Error) throw closingErrors[0];
215
+ throw new Error(`Error on execution node's close function(s). Node Name: ${this.node.name}, Node Type: ${this.node.type}, Errors: ${closingErrors.join(', ')}`);
216
+ }
217
+
218
+ throw error;
219
+ }
220
+ }
221
+
222
+ const closeFunctionsResults = await Promise.allSettled(
223
+ closeFunctions.map(async (fn) => await fn()),
224
+ );
225
+
226
+ const closingErrors = closeFunctionsResults
227
+ .filter((result): result is PromiseRejectedResult => result.status === 'rejected')
228
+ .map((result) => result.reason);
229
+
230
+ if (closingErrors.length > 0) {
231
+ if (closingErrors[0] instanceof Error) throw closingErrors[0];
232
+ throw new Error(`Error on execution node's close function(s). Node Name: ${this.node.name}, Node Type: ${this.node.type}, Errors: ${closingErrors.join(', ')}`);
233
+ }
234
+
235
+ return [returnData];
236
+ }
237
+
238
+ mergeOptions(
239
+ destinationOptions: IRequestOptionsFromParameters,
240
+ sourceOptions?: IRequestOptionsFromParameters,
241
+ ): void {
242
+ if (sourceOptions) {
243
+ destinationOptions.paginate = destinationOptions.paginate ?? sourceOptions.paginate;
244
+ destinationOptions.maxResults = sourceOptions.maxResults
245
+ ? sourceOptions.maxResults
246
+ : destinationOptions.maxResults;
247
+ merge(destinationOptions.options, sourceOptions.options);
248
+ destinationOptions.preSend.push(...sourceOptions.preSend);
249
+ destinationOptions.postReceive.push(...sourceOptions.postReceive);
250
+ if (sourceOptions.requestOperations) {
251
+ destinationOptions.requestOperations = Object.assign(
252
+ destinationOptions.requestOperations || {},
253
+ sourceOptions.requestOperations || {},
254
+ );
255
+ }
256
+ }
257
+ }
258
+
259
+ async runPostReceiveAction(
260
+ executeSingleFunctions: IExecuteSingleFunctions,
261
+ action: PostReceiveAction,
262
+ inputData: INodeExecutionData[],
263
+ responseData: IN8nHttpFullResponse,
264
+ parameterValue: string | IDataObject | undefined,
265
+ itemIndex: number,
266
+ runIndex: number,
267
+ ): Promise<INodeExecutionData[]> {
268
+ if (typeof action === 'function') {
269
+ return action.call(executeSingleFunctions, inputData, responseData);
270
+ }
271
+ if (action.type === 'rootProperty') {
272
+ try {
273
+ return inputData.flatMap((item) => {
274
+ // let itemContent = item.json[action.properties.property];
275
+ let itemContent = get(item.json, action.properties.property);
276
+
277
+ if (!Array.isArray(itemContent)) {
278
+ itemContent = [itemContent];
279
+ }
280
+ return (itemContent as IDataObject[]).map((json) => {
281
+ return {
282
+ json,
283
+ };
284
+ });
285
+ });
286
+ } catch (e) {
287
+ throw new Error(
288
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
289
+ `The rootProperty "${action.properties.property}" could not be found on item.`,
290
+ );
291
+ }
292
+ }
293
+ if (action.type === 'set') {
294
+ const { value } = action.properties;
295
+ // If the value is an expression resolve it
296
+ return [
297
+ {
298
+ json: this.getParameterValue(
299
+ value,
300
+ itemIndex,
301
+ runIndex,
302
+ { $response: responseData, $value: parameterValue },
303
+ false,
304
+ ) as IDataObject,
305
+ },
306
+ ];
307
+ }
308
+ if (action.type === 'sort') {
309
+ // Sort the returned options
310
+ const sortKey = action.properties.key;
311
+ inputData.sort((a, b) => {
312
+ const aSortValue = a.json[sortKey]
313
+ ? (a.json[sortKey]?.toString().toLowerCase() as string)
314
+ : '';
315
+ const bSortValue = b.json[sortKey]
316
+ ? (b.json[sortKey]?.toString().toLowerCase() as string)
317
+ : '';
318
+ if (aSortValue < bSortValue) {
319
+ return -1;
320
+ }
321
+ if (aSortValue > bSortValue) {
322
+ return 1;
323
+ }
324
+ return 0;
325
+ });
326
+
327
+ return inputData;
328
+ }
329
+ if (action.type === 'setKeyValue') {
330
+ const returnData: INodeExecutionData[] = [];
331
+
332
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
333
+ inputData.forEach((item) => {
334
+ const returnItem: IDataObject = {};
335
+ for (const key of Object.keys(action.properties)) {
336
+ let propertyValue = (
337
+ action.properties as Record<
338
+ string,
339
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
340
+ any
341
+ >
342
+ )[key];
343
+ // If the value is an expression resolve it
344
+ propertyValue = this.getParameterValue(
345
+ propertyValue,
346
+ itemIndex,
347
+ runIndex,
348
+ {
349
+ $response: responseData,
350
+ $responseItem: item.json,
351
+ $value: parameterValue,
352
+ },
353
+ true,
354
+ ) as string;
355
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
356
+ (returnItem as Record<string, any>)[key] = propertyValue;
357
+ }
358
+ returnData.push({ json: returnItem });
359
+ });
360
+
361
+ return returnData;
362
+ }
363
+ if (action.type === 'binaryData') {
364
+ responseData.body = Buffer.from(responseData.body as string);
365
+ let { destinationProperty } = action.properties;
366
+
367
+ destinationProperty = this.getParameterValue(
368
+ destinationProperty,
369
+ itemIndex,
370
+ runIndex,
371
+ { $response: responseData, $value: parameterValue },
372
+ false,
373
+ ) as string;
374
+
375
+ const binaryData = await executeSingleFunctions.helpers['prepareBinaryData'](responseData.body);
376
+
377
+ return inputData.map((item) => {
378
+ if (typeof item.json === 'string') {
379
+ // By default is probably the binary data as string set, in this case remove it
380
+ item.json = {};
381
+ }
382
+
383
+ item.binary = {
384
+ [destinationProperty]: binaryData,
385
+ };
386
+
387
+ return item;
388
+ });
389
+ }
390
+
391
+ return [];
392
+ }
393
+
394
+ async rawRoutingRequest(
395
+ executeSingleFunctions: IExecuteSingleFunctions,
396
+ requestData: IRequestOptionsFromParameters,
397
+ itemIndex: number,
398
+ runIndex: number,
399
+ credentialType?: string,
400
+ credentialsDecrypted?: ICredentialsDecrypted,
401
+ ): Promise<INodeExecutionData[]> {
402
+ let responseData: IN8nHttpFullResponse;
403
+ requestData.options.returnFullResponse = true;
404
+
405
+ if (credentialType) {
406
+ responseData = (await executeSingleFunctions.helpers.httpRequestWithAuthentication.call(
407
+ executeSingleFunctions,
408
+ credentialType,
409
+ requestData.options as IHttpRequestOptions,
410
+ { credentialsDecrypted },
411
+ )) as IN8nHttpFullResponse;
412
+ } else {
413
+ responseData = (await executeSingleFunctions.helpers.httpRequest(
414
+ requestData.options as IHttpRequestOptions,
415
+ )) as IN8nHttpFullResponse;
416
+ }
417
+
418
+ let returnData: INodeExecutionData[] = [
419
+ {
420
+ json: responseData.body as IDataObject,
421
+ },
422
+ ];
423
+
424
+ if (requestData.postReceive.length) {
425
+ // If postReceive functionality got defined execute all of them
426
+ for (const postReceiveMethod of requestData.postReceive) {
427
+ for (const action of postReceiveMethod.actions) {
428
+ returnData = await this.runPostReceiveAction(
429
+ executeSingleFunctions,
430
+ action,
431
+ returnData,
432
+ responseData,
433
+ postReceiveMethod.data.parameterValue,
434
+ itemIndex,
435
+ runIndex,
436
+ );
437
+ }
438
+ }
439
+ } else {
440
+ // No postReceive functionality got defined so simply add data as it is
441
+ // eslint-disable-next-line no-lonely-if
442
+ if (Array.isArray(responseData.body)) {
443
+ returnData = responseData.body.map((json) => {
444
+ return {
445
+ json,
446
+ } as INodeExecutionData;
447
+ });
448
+ } else {
449
+ returnData[0].json = responseData.body as IDataObject;
450
+ }
451
+ }
452
+
453
+ return returnData;
454
+ }
455
+
456
+ async makeRoutingRequest(
457
+ requestData: IRequestOptionsFromParameters,
458
+ executeSingleFunctions: IExecuteSingleFunctions,
459
+ itemIndex: number,
460
+ runIndex: number,
461
+ credentialType?: string,
462
+ requestOperations?: IN8nRequestOperations,
463
+ credentialsDecrypted?: ICredentialsDecrypted,
464
+ ): Promise<INodeExecutionData[]> {
465
+ let responseData: INodeExecutionData[];
466
+ for (const preSendMethod of requestData.preSend) {
467
+ requestData.options = await preSendMethod.call(
468
+ executeSingleFunctions,
469
+ requestData.options as IHttpRequestOptions,
470
+ );
471
+ }
472
+
473
+ const executePaginationFunctions = {
474
+ ...executeSingleFunctions,
475
+ makeRoutingRequest: async (requestOptions: IRequestOptionsFromParameters) => {
476
+ return this.rawRoutingRequest(
477
+ executeSingleFunctions,
478
+ requestOptions,
479
+ itemIndex,
480
+ runIndex,
481
+ credentialType,
482
+ credentialsDecrypted,
483
+ );
484
+ },
485
+ };
486
+
487
+ if (requestData.paginate && requestOperations?.pagination) {
488
+ // Has pagination
489
+
490
+ if (typeof requestOperations.pagination === 'function') {
491
+ // Pagination via function
492
+ responseData = await requestOperations.pagination.call(
493
+ executePaginationFunctions,
494
+ requestData,
495
+ );
496
+ } else {
497
+ // Pagination via JSON properties
498
+ const { properties } = requestOperations.pagination;
499
+ responseData = [];
500
+ if (!requestData.options.qs) {
501
+ requestData.options.qs = {};
502
+ }
503
+
504
+ // Different predefined pagination types
505
+ if (requestOperations.pagination.type === 'offset') {
506
+ const optionsType = properties.type === 'body' ? 'body' : 'qs';
507
+ if (properties.type === 'body' && !requestData.options.body) {
508
+ requestData.options.body = {};
509
+ }
510
+
511
+ (requestData.options[optionsType] as IDataObject)[properties.limitParameter] =
512
+ properties.pageSize;
513
+ (requestData.options[optionsType] as IDataObject)[properties.offsetParameter] = 0;
514
+ let tempResponseData: INodeExecutionData[];
515
+ do {
516
+ if (requestData?.maxResults) {
517
+ // Only request as many results as needed
518
+ const resultsMissing = (requestData?.maxResults as number) - responseData.length;
519
+ if (resultsMissing < 1) {
520
+ break;
521
+ }
522
+ (requestData.options[optionsType] as IDataObject)[properties.limitParameter] =
523
+ Math.min(properties.pageSize, resultsMissing);
524
+ }
525
+
526
+ tempResponseData = await this.rawRoutingRequest(
527
+ executeSingleFunctions,
528
+ requestData,
529
+ itemIndex,
530
+ runIndex,
531
+ credentialType,
532
+ credentialsDecrypted,
533
+ );
534
+
535
+ (requestData.options[optionsType] as IDataObject)[properties.offsetParameter] =
536
+ ((requestData.options[optionsType] as IDataObject)[
537
+ properties.offsetParameter
538
+ ] as number) + properties.pageSize;
539
+
540
+ if (properties.rootProperty) {
541
+ const tempResponseValue = get(tempResponseData[0].json, properties.rootProperty) as
542
+ | IDataObject[]
543
+ | undefined;
544
+ if (tempResponseValue === undefined) {
545
+ throw new Error(
546
+ `The rootProperty "${properties.rootProperty}" could not be found on item.`,
547
+ );
548
+ }
549
+
550
+ tempResponseData = tempResponseValue.map((item) => {
551
+ return {
552
+ json: item,
553
+ };
554
+ });
555
+ }
556
+
557
+ responseData.push(...tempResponseData);
558
+ } while (tempResponseData.length && tempResponseData.length === properties.pageSize);
559
+ }
560
+ }
561
+ } else {
562
+ // No pagination
563
+ responseData = await this.rawRoutingRequest(
564
+ executeSingleFunctions,
565
+ requestData,
566
+ itemIndex,
567
+ runIndex,
568
+ credentialType,
569
+ credentialsDecrypted,
570
+ );
571
+ }
572
+ return responseData;
573
+ }
574
+
575
+ getParameterValue(
576
+ parameterValue: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[],
577
+ itemIndex: number,
578
+ runIndex: number,
579
+ additionalKeys?: IWorkflowDataProxyAdditionalKeys,
580
+ returnObjectAsString = false,
581
+ ): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | string {
582
+ if (typeof parameterValue === 'string' && parameterValue.charAt(0) === '=') {
583
+ return this.workflow.expression.getParameterValue(
584
+ parameterValue,
585
+ this.runExecutionData ?? null,
586
+ runIndex,
587
+ itemIndex,
588
+ this.node.name,
589
+ this.connectionInputData,
590
+ this.mode,
591
+ additionalKeys ?? {},
592
+ returnObjectAsString,
593
+ );
594
+ }
595
+
596
+ return parameterValue;
597
+ }
598
+
599
+ getRequestOptionsFromParameters(
600
+ executeSingleFunctions: IExecuteSingleFunctions,
601
+ nodeProperties: INodeProperties | INodePropertyOptions,
602
+ itemIndex: number,
603
+ runIndex: number,
604
+ path: string,
605
+ additionalKeys?: IWorkflowDataProxyAdditionalKeys,
606
+ ): IRequestOptionsFromParameters | undefined {
607
+ const returnData: IRequestOptionsFromParameters = {
608
+ options: {
609
+ qs: {},
610
+ body: {},
611
+ },
612
+ preSend: [],
613
+ postReceive: [],
614
+ requestOperations: {},
615
+ };
616
+ let basePath = path ? `${path}.` : '';
617
+
618
+ if (!NodeHelpers.displayParameter(this.node.parameters, nodeProperties, this.node.parameters)) {
619
+ return undefined;
620
+ }
621
+ if (nodeProperties.routing) {
622
+ let parameterValue: IDataObject | undefined;
623
+ if (basePath + nodeProperties.name && 'type' in nodeProperties) {
624
+ parameterValue = executeSingleFunctions.getNodeParameter(
625
+ basePath + nodeProperties.name,
626
+ ) as IDataObject;
627
+ }
628
+
629
+ if (nodeProperties.routing.operations) {
630
+ returnData.requestOperations = { ...nodeProperties.routing.operations };
631
+ }
632
+
633
+ if (nodeProperties.routing.request) {
634
+ for (const key of Object.keys(nodeProperties.routing.request)) {
635
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
636
+ let propertyValue = (nodeProperties.routing.request as Record<string, any>)[key];
637
+ // If the value is an expression resolve it
638
+ propertyValue = this.getParameterValue(
639
+ propertyValue,
640
+ itemIndex,
641
+ runIndex,
642
+ { ...additionalKeys, $value: parameterValue },
643
+ true,
644
+ ) as string;
645
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
646
+ (returnData.options as Record<string, any>)[key] = propertyValue;
647
+ }
648
+ }
649
+
650
+ if (nodeProperties.routing.send) {
651
+ let propertyName = nodeProperties.routing.send.property;
652
+ if (propertyName !== undefined) {
653
+ // If the propertyName is an expression resolve it
654
+ propertyName = this.getParameterValue(
655
+ propertyName,
656
+ itemIndex,
657
+ runIndex,
658
+ additionalKeys,
659
+ true,
660
+ ) as string;
661
+
662
+ let value = parameterValue;
663
+
664
+ if (nodeProperties.routing.send.value) {
665
+ const valueString = nodeProperties.routing.send.value;
666
+ // Special value got set
667
+ // If the valueString is an expression resolve it
668
+ value = this.getParameterValue(
669
+ valueString,
670
+ itemIndex,
671
+ runIndex,
672
+ { ...additionalKeys, $value: value },
673
+ true,
674
+ ) as IDataObject;
675
+ }
676
+
677
+ if (nodeProperties.routing.send.type === 'body') {
678
+ // Send in "body"
679
+ // eslint-disable-next-line no-lonely-if
680
+ if (nodeProperties.routing.send.propertyInDotNotation === false) {
681
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
682
+ (returnData.options.body as Record<string, any>)![propertyName] = value;
683
+ } else {
684
+ const keys = propertyName.split('.');
685
+ let obj = returnData.options.body as object;
686
+ while (keys.length > 1) {
687
+ const key = keys.shift()!;
688
+ if (!(key in obj)) {
689
+ (obj as any)[key] = {};
690
+ }
691
+ obj = (obj as any)[key];
692
+ }
693
+ (obj as any)[keys[0]] = value;
694
+ }
695
+ } else {
696
+ // Send in "query"
697
+ // eslint-disable-next-line no-lonely-if
698
+ if (nodeProperties.routing.send.propertyInDotNotation === false) {
699
+ returnData.options.qs![propertyName] = value;
700
+ } else {
701
+ const keys = propertyName.split('.');
702
+ let obj = returnData.options.qs as object;
703
+ while (keys.length > 1) {
704
+ const key = keys.shift()!;
705
+ if (!(key in obj)) {
706
+ (obj as any)[key] = {};
707
+ }
708
+ obj = (obj as any)[key];
709
+ }
710
+ (obj as any)[keys[0]] = value;
711
+ }
712
+ }
713
+ }
714
+
715
+ if (nodeProperties.routing.send.paginate !== undefined) {
716
+ let paginateValue = nodeProperties.routing.send.paginate;
717
+ if (typeof paginateValue === 'string' && paginateValue.charAt(0) === '=') {
718
+ // If the propertyName is an expression resolve it
719
+ paginateValue = this.getParameterValue(
720
+ paginateValue,
721
+ itemIndex,
722
+ runIndex,
723
+ { ...additionalKeys, $value: parameterValue },
724
+ true,
725
+ ) as string;
726
+ }
727
+
728
+ returnData.paginate = !!paginateValue;
729
+ }
730
+
731
+ if (nodeProperties.routing.send.preSend) {
732
+ returnData.preSend.push(...nodeProperties.routing.send.preSend);
733
+ }
734
+ }
735
+ if (nodeProperties.routing.output) {
736
+ if (nodeProperties.routing.output.maxResults !== undefined) {
737
+ let maxResultsValue = nodeProperties.routing.output.maxResults;
738
+ if (typeof maxResultsValue === 'string' && maxResultsValue.charAt(0) === '=') {
739
+ // If the propertyName is an expression resolve it
740
+ maxResultsValue = this.getParameterValue(
741
+ maxResultsValue,
742
+ itemIndex,
743
+ runIndex,
744
+ { ...additionalKeys, $value: parameterValue },
745
+ true,
746
+ ) as string;
747
+ }
748
+
749
+ returnData.maxResults = maxResultsValue;
750
+ }
751
+
752
+ if (nodeProperties.routing.output.postReceive) {
753
+ returnData.postReceive.push({
754
+ data: {
755
+ parameterValue,
756
+ },
757
+ actions: nodeProperties.routing.output.postReceive,
758
+ });
759
+ }
760
+ }
761
+ }
762
+
763
+ // Check if there are any child properties
764
+ if (!Object.prototype.hasOwnProperty.call(nodeProperties, 'options')) {
765
+ // There are none so nothing else to check
766
+ return returnData;
767
+ }
768
+
769
+ // Everything after this point can only be of type INodeProperties
770
+ nodeProperties = nodeProperties as INodeProperties;
771
+
772
+ // Check the child parameters
773
+ let value;
774
+ if (nodeProperties.type === 'options') {
775
+ const optionValue = NodeHelpers.getParameterValueByPath(
776
+ this.node.parameters,
777
+ nodeProperties.name,
778
+ basePath.slice(0, -1),
779
+ );
780
+
781
+ // Find the selected option
782
+ const selectedOption = (nodeProperties.options as INodePropertyOptions[]).filter(
783
+ (option) => option.value === optionValue,
784
+ );
785
+
786
+ if (selectedOption.length) {
787
+ // Check only if option is set and if of type INodeProperties
788
+ const tempOptions = this.getRequestOptionsFromParameters(
789
+ executeSingleFunctions,
790
+ selectedOption[0],
791
+ itemIndex,
792
+ runIndex,
793
+ `${basePath}${nodeProperties.name}`,
794
+ { $value: optionValue },
795
+ );
796
+
797
+ this.mergeOptions(returnData, tempOptions);
798
+ }
799
+ } else if (nodeProperties.type === 'collection') {
800
+ value = NodeHelpers.getParameterValueByPath(
801
+ this.node.parameters,
802
+ nodeProperties.name,
803
+ basePath.slice(0, -1),
804
+ );
805
+
806
+ for (const propertyOption of nodeProperties.options as INodeProperties[]) {
807
+ if (
808
+ Object.keys(value as IDataObject).includes(propertyOption.name) &&
809
+ propertyOption.type !== undefined
810
+ ) {
811
+ // Check only if option is set and if of type INodeProperties
812
+ const tempOptions = this.getRequestOptionsFromParameters(
813
+ executeSingleFunctions,
814
+ propertyOption,
815
+ itemIndex,
816
+ runIndex,
817
+ `${basePath}${nodeProperties.name}`,
818
+ );
819
+
820
+ this.mergeOptions(returnData, tempOptions);
821
+ }
822
+ }
823
+ } else if (nodeProperties.type === 'fixedCollection') {
824
+ basePath = `${basePath}${nodeProperties.name}.`;
825
+ for (const propertyOptions of nodeProperties.options as INodePropertyCollection[]) {
826
+ // Check if the option got set and if not skip it
827
+ value = NodeHelpers.getParameterValueByPath(
828
+ this.node.parameters,
829
+ propertyOptions.name,
830
+ basePath.slice(0, -1),
831
+ );
832
+
833
+ if (value === undefined) {
834
+ continue;
835
+ }
836
+
837
+ // Make sure that it is always an array to be able to use the same code for multi and single
838
+ if (!Array.isArray(value)) {
839
+ value = [value];
840
+ }
841
+
842
+ const loopBasePath = `${basePath}${propertyOptions.name}`;
843
+ for (let i = 0; i < (value as INodeParameters[]).length; i++) {
844
+ for (const option of propertyOptions.values) {
845
+ const tempOptions = this.getRequestOptionsFromParameters(
846
+ executeSingleFunctions,
847
+ option,
848
+ itemIndex,
849
+ runIndex,
850
+ nodeProperties.typeOptions?.multipleValues ? `${loopBasePath}[${i}]` : loopBasePath,
851
+ { ...(additionalKeys || {}), $index: i, $parent: value[i] },
852
+ );
853
+
854
+ this.mergeOptions(returnData, tempOptions);
855
+ }
856
+ }
857
+ }
858
+ }
859
+
860
+ return returnData;
861
+ }
862
+ }