@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,1266 @@
1
+ /* eslint-disable no-await-in-loop */
2
+ /* eslint-disable @typescript-eslint/no-unsafe-call */
3
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
4
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
5
+ /* eslint-disable no-param-reassign */
6
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
7
+ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
8
+ /* eslint-disable @typescript-eslint/no-unsafe-return */
9
+ /* eslint-disable @typescript-eslint/no-for-in-array */
10
+ /* eslint-disable no-prototype-builtins */
11
+ /* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
12
+ /* eslint-disable no-underscore-dangle */
13
+ /* eslint-disable no-continue */
14
+ /* eslint-disable no-restricted-syntax */
15
+ /* eslint-disable import/no-cycle */
16
+
17
+ import {
18
+ Expression,
19
+ IConnections,
20
+ IDeferredPromise,
21
+ IExecuteResponsePromiseData,
22
+ IGetExecuteTriggerFunctions,
23
+ INode,
24
+ INodeExecuteFunctions,
25
+ INodeExecutionData,
26
+ INodeIssues,
27
+ INodeParameters,
28
+ INodes,
29
+ INodeType,
30
+ INodeTypes,
31
+ IPollFunctions,
32
+ IRunExecutionData,
33
+ ITaskDataConnections,
34
+ ITriggerResponse,
35
+ IWebhookData,
36
+ IWebhookResponseData,
37
+ IWorfklowIssues,
38
+ IWorkflowExecuteAdditionalData,
39
+ IWorkflowSettings,
40
+ NodeHelpers,
41
+ NodeParameterValue,
42
+ ObservableObject,
43
+ RoutingNode,
44
+ WebhookSetupMethodNames,
45
+ WorkflowActivateMode,
46
+ WorkflowExecuteMode,
47
+ } from '..';
48
+
49
+ import { CloseFunction, IConnection, IDataObject, IObservableObject } from './Interfaces';
50
+
51
+ export class Workflow {
52
+ id: string | undefined;
53
+
54
+ name: string | undefined;
55
+
56
+ nodes: INodes = {};
57
+
58
+ connectionsBySourceNode: IConnections;
59
+
60
+ connectionsByDestinationNode: IConnections;
61
+
62
+ nodeTypes: INodeTypes;
63
+
64
+ expression: Expression;
65
+
66
+ active: boolean;
67
+
68
+ settings: IWorkflowSettings;
69
+
70
+ // To save workflow specific static data like for example
71
+ // ids of registred webhooks of nodes
72
+ staticData: IDataObject;
73
+
74
+ // constructor(id: string | undefined, nodes: INode[], connections: IConnections, active: boolean, nodeTypes: INodeTypes, staticData?: IDataObject, settings?: IWorkflowSettings) {
75
+ constructor(parameters: {
76
+ id?: string;
77
+ name?: string;
78
+ nodes: INode[];
79
+ connections: IConnections;
80
+ active: boolean;
81
+ nodeTypes: INodeTypes;
82
+ staticData?: IDataObject;
83
+ settings?: IWorkflowSettings;
84
+ }) {
85
+ this.id = parameters?.id;
86
+ this.name = parameters?.name;
87
+ this.nodeTypes = parameters.nodeTypes;
88
+
89
+ // Save nodes in workflow as object to be able to get the
90
+ // nodes easily by its name.
91
+ // Also directly add the default values of the node type.
92
+ let nodeType: INodeType | undefined;
93
+ for (const node of parameters.nodes) {
94
+ this.nodes[node.name] = node;
95
+
96
+ nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
97
+
98
+ if (nodeType === undefined) {
99
+ // Go on to next node when its type is not known.
100
+ // For now do not error because that causes problems with
101
+ // expression resolution also then when the unknown node
102
+ // does not get used.
103
+ continue;
104
+ // throw new Error(`The node type "${node.type}" of node "${node.name}" is not known.`);
105
+ }
106
+
107
+ // Add default values
108
+ const nodeParameters = NodeHelpers.getNodeParameters(
109
+ nodeType.description.properties,
110
+ node.parameters,
111
+ true,
112
+ false,
113
+ );
114
+ node.parameters = nodeParameters !== null ? nodeParameters : {};
115
+ }
116
+ this.connectionsBySourceNode = parameters.connections;
117
+
118
+ // Save also the connections by the destionation nodes
119
+ this.connectionsByDestinationNode = this.__getConnectionsByDestination(parameters.connections);
120
+
121
+ this.active = parameters.active || false;
122
+
123
+ this.staticData = ObservableObject.create(parameters.staticData || {}, undefined, {
124
+ ignoreEmptyOnFirstChild: true,
125
+ });
126
+
127
+ this.settings = parameters.settings || {};
128
+
129
+ this.expression = new Expression(this);
130
+ }
131
+
132
+ /**
133
+ * The default connections are by source node. This function rewrites them by destination nodes
134
+ * to easily find parent nodes.
135
+ *
136
+ * @param {IConnections} connections
137
+ * @returns {IConnections}
138
+ * @memberof Workflow
139
+ */
140
+ __getConnectionsByDestination(connections: IConnections): IConnections {
141
+ const returnConnection: IConnections = {};
142
+
143
+ let connectionInfo;
144
+ let maxIndex: number;
145
+ for (const sourceNode in connections) {
146
+ if (!connections.hasOwnProperty(sourceNode)) {
147
+ continue;
148
+ }
149
+ for (const type in connections[sourceNode]) {
150
+ if (!connections[sourceNode].hasOwnProperty(type)) {
151
+ continue;
152
+ }
153
+ for (const inputIndex in connections[sourceNode][type]) {
154
+ if (!connections[sourceNode][type].hasOwnProperty(inputIndex)) {
155
+ continue;
156
+ }
157
+ for (connectionInfo of connections[sourceNode][type][inputIndex]) {
158
+ if (!returnConnection.hasOwnProperty(connectionInfo.node)) {
159
+ returnConnection[connectionInfo.node] = {};
160
+ }
161
+ if (!returnConnection[connectionInfo.node].hasOwnProperty(connectionInfo.type)) {
162
+ returnConnection[connectionInfo.node][connectionInfo.type] = [];
163
+ }
164
+
165
+ maxIndex = returnConnection[connectionInfo.node][connectionInfo.type].length - 1;
166
+ for (let j = maxIndex; j < connectionInfo.index; j++) {
167
+ returnConnection[connectionInfo.node][connectionInfo.type].push([]);
168
+ }
169
+
170
+ returnConnection[connectionInfo.node][connectionInfo.type][connectionInfo.index].push({
171
+ node: sourceNode,
172
+ type,
173
+ index: parseInt(inputIndex, 10),
174
+ });
175
+ }
176
+ }
177
+ }
178
+ }
179
+
180
+ return returnConnection;
181
+ }
182
+
183
+ /**
184
+ * A workflow can only be activated if it has a node which has either triggers
185
+ * or webhooks defined.
186
+ *
187
+ * @param {string[]} [ignoreNodeTypes] Node-types to ignore in the check
188
+ * @returns {boolean}
189
+ * @memberof Workflow
190
+ */
191
+ checkIfWorkflowCanBeActivated(ignoreNodeTypes?: string[]): boolean {
192
+ let node: INode;
193
+ let nodeType: INodeType | undefined;
194
+
195
+ for (const nodeName of Object.keys(this.nodes)) {
196
+ node = this.nodes[nodeName];
197
+
198
+ if (node.disabled === true) {
199
+ // Deactivated nodes can not trigger a run so ignore
200
+ continue;
201
+ }
202
+
203
+ // eslint-disable-next-line @typescript-eslint/prefer-optional-chain
204
+ if (ignoreNodeTypes !== undefined && ignoreNodeTypes.includes(node.type)) {
205
+ continue;
206
+ }
207
+
208
+ nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
209
+
210
+ if (nodeType === undefined) {
211
+ // Type is not known so check is not possible
212
+ continue;
213
+ }
214
+
215
+ if (
216
+ nodeType.poll !== undefined ||
217
+ nodeType.trigger !== undefined ||
218
+ nodeType.webhook !== undefined
219
+ ) {
220
+ // Is a trigger node. So workflow can be activated.
221
+ return true;
222
+ }
223
+ }
224
+
225
+ return false;
226
+ }
227
+
228
+ /**
229
+ * Checks if everything in the workflow is complete
230
+ * and ready to be executed. If it returns null everything
231
+ * is fine. If there are issues it returns the issues
232
+ * which have been found for the different nodes.
233
+ * TODO: Does currently not check for credential issues!
234
+ *
235
+ * @returns {(IWorfklowIssues | null)}
236
+ * @memberof Workflow
237
+ */
238
+ checkReadyForExecution(inputData: {
239
+ startNode?: string;
240
+ destinationNode?: string;
241
+ }): IWorfklowIssues | null {
242
+ let node: INode;
243
+ let nodeType: INodeType | undefined;
244
+ let nodeIssues: INodeIssues | null = null;
245
+ const workflowIssues: IWorfklowIssues = {};
246
+
247
+ let checkNodes: string[] = [];
248
+ if (inputData.destinationNode) {
249
+ // If a destination node is given we have to check all the nodes
250
+ // leading up to it
251
+ checkNodes = this.getParentNodes(inputData.destinationNode);
252
+ checkNodes.push(inputData.destinationNode);
253
+ } else if (inputData.startNode) {
254
+ // If a start node is given we have to check all nodes which
255
+ // come after it
256
+ checkNodes = this.getChildNodes(inputData.startNode);
257
+ checkNodes.push(inputData.startNode);
258
+ }
259
+
260
+ for (const nodeName of checkNodes) {
261
+ nodeIssues = null;
262
+ node = this.nodes[nodeName];
263
+
264
+ if (node.disabled === true) {
265
+ continue;
266
+ }
267
+
268
+ nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
269
+
270
+ if (nodeType === undefined) {
271
+ // Node type is not known
272
+ nodeIssues = {
273
+ typeUnknown: true,
274
+ };
275
+ } else {
276
+ nodeIssues = NodeHelpers.getNodeParametersIssues(nodeType.description.properties, node);
277
+ }
278
+
279
+ if (nodeIssues !== null) {
280
+ workflowIssues[node.name] = nodeIssues;
281
+ }
282
+ }
283
+
284
+ if (Object.keys(workflowIssues).length === 0) {
285
+ return null;
286
+ }
287
+
288
+ return workflowIssues;
289
+ }
290
+
291
+ /**
292
+ * Returns the static data of the workflow.
293
+ * It gets saved with the workflow and will be the same for
294
+ * all workflow-executions.
295
+ *
296
+ * @param {string} type The type of data to return ("global"|"node")
297
+ * @param {INode} [node] If type is set to "node" then the node has to be provided
298
+ * @returns {IDataObject}
299
+ * @memberof Workflow
300
+ */
301
+ getStaticData(type: string, node?: INode): IDataObject {
302
+ let key: string;
303
+ if (type === 'global') {
304
+ key = 'global';
305
+ } else if (type === 'node') {
306
+ if (node === undefined) {
307
+ throw new Error(
308
+ `The request data of context type "node" the node parameter has to be set!`,
309
+ );
310
+ }
311
+ key = `node:${node.name}`;
312
+ } else {
313
+ throw new Error(
314
+ `The context type "${type}" is not know. Only "global" and node" are supported!`,
315
+ );
316
+ }
317
+
318
+ if (this.staticData[key] === undefined) {
319
+ // Create it as ObservableObject that we can easily check if the data changed
320
+ // to know if the workflow with its data has to be saved afterwards or not.
321
+ this.staticData[key] = ObservableObject.create({}, this.staticData as IObservableObject);
322
+ }
323
+
324
+ return this.staticData[key] as IDataObject;
325
+ }
326
+
327
+ /**
328
+ * Returns all the trigger nodes in the workflow.
329
+ *
330
+ * @returns {INode[]}
331
+ * @memberof Workflow
332
+ */
333
+ getTriggerNodes(): INode[] {
334
+ return this.queryNodes((nodeType: INodeType) => !!nodeType.trigger);
335
+ }
336
+
337
+ /**
338
+ * Returns all the poll nodes in the workflow
339
+ *
340
+ * @returns {INode[]}
341
+ * @memberof Workflow
342
+ */
343
+ getPollNodes(): INode[] {
344
+ return this.queryNodes((nodeType: INodeType) => !!nodeType.poll);
345
+ }
346
+
347
+ /**
348
+ * Returns all the nodes in the workflow for which the given
349
+ * checkFunction return true
350
+ *
351
+ * @param {(nodeType: INodeType) => boolean} checkFunction
352
+ * @returns {INode[]}
353
+ * @memberof Workflow
354
+ */
355
+ queryNodes(checkFunction: (nodeType: INodeType) => boolean): INode[] {
356
+ const returnNodes: INode[] = [];
357
+
358
+ // Check if it has any of them
359
+ let node: INode;
360
+ let nodeType: INodeType | undefined;
361
+
362
+ for (const nodeName of Object.keys(this.nodes)) {
363
+ node = this.nodes[nodeName];
364
+
365
+ if (node.disabled === true) {
366
+ continue;
367
+ }
368
+
369
+ nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
370
+
371
+ if (nodeType !== undefined && checkFunction(nodeType)) {
372
+ returnNodes.push(node);
373
+ }
374
+ }
375
+
376
+ return returnNodes;
377
+ }
378
+
379
+ /**
380
+ * Returns the node with the given name if it exists else null
381
+ *
382
+ * @param {string} nodeName Name of the node to return
383
+ * @returns {(INode | null)}
384
+ * @memberof Workflow
385
+ */
386
+ getNode(nodeName: string): INode | null {
387
+ if (this.nodes.hasOwnProperty(nodeName)) {
388
+ return this.nodes[nodeName];
389
+ }
390
+
391
+ return null;
392
+ }
393
+
394
+ /**
395
+ * Renames nodes in expressions
396
+ *
397
+ * @param {(NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[])} parameterValue The parameters to check for expressions
398
+ * @param {string} currentName The current name of the node
399
+ * @param {string} newName The new name
400
+ * @returns {(NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[])}
401
+ * @memberof Workflow
402
+ */
403
+ renameNodeInExpressions(
404
+ parameterValue: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[],
405
+ currentName: string,
406
+ newName: string,
407
+ ): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] {
408
+ if (typeof parameterValue !== 'object') {
409
+ // Reached the actual value
410
+ if (typeof parameterValue === 'string' && parameterValue.charAt(0) === '=') {
411
+ // Is expression so has to be rewritten
412
+
413
+ // To not run the "expensive" regex stuff when it is not needed
414
+ // make a simple check first if it really contains the the node-name
415
+ if (parameterValue.includes(currentName)) {
416
+ // Really contains node-name (even though we do not know yet if really as $node-expression)
417
+
418
+ // In case some special characters are used in name escape them
419
+ const currentNameEscaped = currentName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
420
+
421
+ parameterValue = parameterValue.replace(
422
+ new RegExp(`(\\$node(\\.|\\["|\\['))${currentNameEscaped}((\\.|"\\]|'\\]))`, 'g'),
423
+ `$1${newName}$3`,
424
+ );
425
+ }
426
+ }
427
+
428
+ return parameterValue;
429
+ }
430
+
431
+ if (Array.isArray(parameterValue)) {
432
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
433
+ const returnArray: any[] = [];
434
+
435
+ for (const currentValue of parameterValue) {
436
+ returnArray.push(this.renameNodeInExpressions(currentValue, currentName, newName));
437
+ }
438
+
439
+ return returnArray;
440
+ }
441
+
442
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
443
+ const returnData: any = {};
444
+
445
+ for (const parameterName of Object.keys(parameterValue || {})) {
446
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
447
+ returnData[parameterName] = this.renameNodeInExpressions(
448
+ parameterValue![parameterName],
449
+ currentName,
450
+ newName,
451
+ );
452
+ }
453
+
454
+ return returnData;
455
+ }
456
+
457
+ /**
458
+ * Rename a node in the workflow
459
+ *
460
+ * @param {string} currentName The current name of the node
461
+ * @param {string} newName The new name
462
+ * @memberof Workflow
463
+ */
464
+ renameNode(currentName: string, newName: string) {
465
+ // Rename the node itself
466
+ if (this.nodes[currentName] !== undefined) {
467
+ this.nodes[newName] = this.nodes[currentName];
468
+ this.nodes[newName].name = newName;
469
+ delete this.nodes[currentName];
470
+ }
471
+
472
+ // Update the expressions which reference the node
473
+ // with its old name
474
+ for (const node of Object.values(this.nodes)) {
475
+ node.parameters = this.renameNodeInExpressions(
476
+ node.parameters,
477
+ currentName,
478
+ newName,
479
+ ) as INodeParameters;
480
+ }
481
+
482
+ // Change all source connections
483
+ if (this.connectionsBySourceNode.hasOwnProperty(currentName)) {
484
+ this.connectionsBySourceNode[newName] = this.connectionsBySourceNode[currentName];
485
+ delete this.connectionsBySourceNode[currentName];
486
+ }
487
+
488
+ // Change all destination connections
489
+ let sourceNode: string;
490
+ let type: string;
491
+ let sourceIndex: string;
492
+ let connectionIndex: string;
493
+ let connectionData: IConnection;
494
+ for (sourceNode of Object.keys(this.connectionsBySourceNode)) {
495
+ for (type of Object.keys(this.connectionsBySourceNode[sourceNode])) {
496
+ for (sourceIndex of Object.keys(this.connectionsBySourceNode[sourceNode][type])) {
497
+ for (connectionIndex of Object.keys(
498
+ this.connectionsBySourceNode[sourceNode][type][parseInt(sourceIndex, 10)],
499
+ )) {
500
+ connectionData =
501
+ this.connectionsBySourceNode[sourceNode][type][parseInt(sourceIndex, 10)][
502
+ parseInt(connectionIndex, 10)
503
+ ];
504
+ if (connectionData.node === currentName) {
505
+ connectionData.node = newName;
506
+ }
507
+ }
508
+ }
509
+ }
510
+ }
511
+
512
+ // Use the updated connections to create updated connections by destionation nodes
513
+ this.connectionsByDestinationNode = this.__getConnectionsByDestination(
514
+ this.connectionsBySourceNode,
515
+ );
516
+ }
517
+
518
+ /**
519
+ * Finds the highest parent nodes of the node with the given name
520
+ *
521
+ * @param {string} nodeName
522
+ * @param {string} [type='main']
523
+ * @param {number} [nodeConnectionIndex]
524
+ * @returns {string[]}
525
+ * @memberof Workflow
526
+ */
527
+ getHighestNode(
528
+ nodeName: string,
529
+ type = 'main',
530
+ nodeConnectionIndex?: number,
531
+ checkedNodes?: string[],
532
+ ): string[] {
533
+ const currentHighest: string[] = [];
534
+ if (this.nodes[nodeName].disabled === false) {
535
+ // If the current node is not disabled itself is the highest
536
+ currentHighest.push(nodeName);
537
+ }
538
+
539
+ if (!this.connectionsByDestinationNode.hasOwnProperty(nodeName)) {
540
+ // Node does not have incoming connections
541
+ return currentHighest;
542
+ }
543
+
544
+ if (!this.connectionsByDestinationNode[nodeName].hasOwnProperty(type)) {
545
+ // Node does not have incoming connections of given type
546
+ return currentHighest;
547
+ }
548
+
549
+ checkedNodes = checkedNodes || [];
550
+
551
+ if (checkedNodes.includes(nodeName)) {
552
+ // Node got checked already before
553
+ return currentHighest;
554
+ }
555
+
556
+ checkedNodes.push(nodeName);
557
+
558
+ const returnNodes: string[] = [];
559
+ let addNodes: string[];
560
+
561
+ let connectionsByIndex: IConnection[];
562
+ for (
563
+ let connectionIndex = 0;
564
+ connectionIndex < this.connectionsByDestinationNode[nodeName][type].length;
565
+ connectionIndex++
566
+ ) {
567
+ if (nodeConnectionIndex !== undefined && nodeConnectionIndex !== connectionIndex) {
568
+ // If a connection-index is given ignore all other ones
569
+ continue;
570
+ }
571
+ connectionsByIndex = this.connectionsByDestinationNode[nodeName][type][connectionIndex];
572
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
573
+ connectionsByIndex.forEach((connection) => {
574
+ if (checkedNodes!.includes(connection.node)) {
575
+ // Node got checked already before
576
+ return;
577
+ }
578
+
579
+ addNodes = this.getHighestNode(connection.node, type, undefined, checkedNodes);
580
+
581
+ if (addNodes.length === 0) {
582
+ // The checked node does not have any further parents so add it
583
+ // if it is not disabled
584
+ if (this.nodes[connection.node].disabled !== true) {
585
+ addNodes = [connection.node];
586
+ }
587
+ }
588
+
589
+ addNodes.forEach((name) => {
590
+ // Only add if node is not on the list already anyway
591
+ if (returnNodes.indexOf(name) === -1) {
592
+ returnNodes.push(name);
593
+ }
594
+ });
595
+ });
596
+ }
597
+
598
+ return returnNodes;
599
+ }
600
+
601
+ /**
602
+ * Returns all the after the given one
603
+ *
604
+ * @param {string} nodeName
605
+ * @param {string} [type='main']
606
+ * @param {*} [depth=-1]
607
+ * @returns {string[]}
608
+ * @memberof Workflow
609
+ */
610
+ getChildNodes(nodeName: string, type = 'main', depth = -1): string[] {
611
+ return this.getConnectedNodes(this.connectionsBySourceNode, nodeName, type, depth);
612
+ }
613
+
614
+ /**
615
+ * Returns all the nodes before the given one
616
+ *
617
+ * @param {string} nodeName
618
+ * @param {string} [type='main']
619
+ * @param {*} [depth=-1]
620
+ * @returns {string[]}
621
+ * @memberof Workflow
622
+ */
623
+ getParentNodes(nodeName: string, type = 'main', depth = -1): string[] {
624
+ return this.getConnectedNodes(this.connectionsByDestinationNode, nodeName, type, depth);
625
+ }
626
+
627
+ /**
628
+ * Gets all the nodes which are connected nodes starting from
629
+ * the given one
630
+ *
631
+ * @param {IConnections} connections
632
+ * @param {string} nodeName
633
+ * @param {string} [type='main']
634
+ * @param {*} [depth=-1]
635
+ * @param {string[]} [checkedNodes]
636
+ * @returns {string[]}
637
+ * @memberof Workflow
638
+ */
639
+ getConnectedNodes(
640
+ connections: IConnections,
641
+ nodeName: string,
642
+ type = 'main',
643
+ depth = -1,
644
+ checkedNodes?: string[],
645
+ ): string[] {
646
+ depth = depth === -1 ? -1 : depth;
647
+ const newDepth = depth === -1 ? depth : depth - 1;
648
+ if (depth === 0) {
649
+ // Reached max depth
650
+ return [];
651
+ }
652
+
653
+ if (!connections.hasOwnProperty(nodeName)) {
654
+ // Node does not have incoming connections
655
+ return [];
656
+ }
657
+
658
+ if (!connections[nodeName].hasOwnProperty(type)) {
659
+ // Node does not have incoming connections of given type
660
+ return [];
661
+ }
662
+
663
+ checkedNodes = checkedNodes || [];
664
+
665
+ if (checkedNodes.includes(nodeName)) {
666
+ // Node got checked already before
667
+ return [];
668
+ }
669
+
670
+ checkedNodes.push(nodeName);
671
+
672
+ const returnNodes: string[] = [];
673
+ let addNodes: string[];
674
+ let nodeIndex: number;
675
+ let i: number;
676
+ let parentNodeName: string;
677
+ connections[nodeName][type].forEach((connectionsByIndex: IConnection[]) => {
678
+ connectionsByIndex.forEach((connection: IConnection) => {
679
+ if (checkedNodes!.includes(connection.node)) {
680
+ // Node got checked already before
681
+ return;
682
+ }
683
+
684
+ returnNodes.unshift(connection.node);
685
+
686
+ addNodes = this.getConnectedNodes(
687
+ connections,
688
+ connection.node,
689
+ type,
690
+ newDepth,
691
+ checkedNodes,
692
+ );
693
+
694
+ for (i = addNodes.length; i--; i > 0) {
695
+ // Because nodes can have multiple parents it is possible that
696
+ // parts of the tree is parent of both and to not add nodes
697
+ // twice check first if they already got added before.
698
+ parentNodeName = addNodes[i];
699
+ nodeIndex = returnNodes.indexOf(parentNodeName);
700
+
701
+ if (nodeIndex !== -1) {
702
+ // Node got found before so remove it from current location
703
+ // that node-order stays correct
704
+ returnNodes.splice(nodeIndex, 1);
705
+ }
706
+
707
+ returnNodes.unshift(parentNodeName);
708
+ }
709
+ });
710
+ });
711
+
712
+ return returnNodes;
713
+ }
714
+
715
+ /**
716
+ * Returns via which output of the parent-node the node
717
+ * is connected to.
718
+ *
719
+ * @param {string} nodeName The node to check how it is connected with parent node
720
+ * @param {string} parentNodeName The parent node to get the output index of
721
+ * @param {string} [type='main']
722
+ * @param {*} [depth=-1]
723
+ * @param {string[]} [checkedNodes]
724
+ * @returns {(number | undefined)}
725
+ * @memberof Workflow
726
+ */
727
+ getNodeConnectionOutputIndex(
728
+ nodeName: string,
729
+ parentNodeName: string,
730
+ type = 'main',
731
+ depth = -1,
732
+ checkedNodes?: string[],
733
+ ): number | undefined {
734
+ const node = this.getNode(parentNodeName);
735
+ if (node === null) {
736
+ return undefined;
737
+ }
738
+ const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion) as INodeType;
739
+ if (nodeType.description.outputs.length === 1) {
740
+ // If the parent node has only one output, it can only be connected
741
+ // to that one. So no further checking is required.
742
+ return 0;
743
+ }
744
+
745
+ depth = depth === -1 ? -1 : depth;
746
+ const newDepth = depth === -1 ? depth : depth - 1;
747
+ if (depth === 0) {
748
+ // Reached max depth
749
+ return undefined;
750
+ }
751
+
752
+ if (!this.connectionsByDestinationNode.hasOwnProperty(nodeName)) {
753
+ // Node does not have incoming connections
754
+ return undefined;
755
+ }
756
+
757
+ if (!this.connectionsByDestinationNode[nodeName].hasOwnProperty(type)) {
758
+ // Node does not have incoming connections of given type
759
+ return undefined;
760
+ }
761
+
762
+ checkedNodes = checkedNodes || [];
763
+
764
+ if (checkedNodes.includes(nodeName)) {
765
+ // Node got checked already before
766
+ return undefined;
767
+ }
768
+
769
+ checkedNodes.push(nodeName);
770
+
771
+ let outputIndex: number | undefined;
772
+ for (const connectionsByIndex of this.connectionsByDestinationNode[nodeName][type]) {
773
+ for (const connection of connectionsByIndex) {
774
+ if (parentNodeName === connection.node) {
775
+ return connection.index;
776
+ }
777
+
778
+ if (checkedNodes.includes(connection.node)) {
779
+ // Node got checked already before so continue with the next one
780
+ continue;
781
+ }
782
+
783
+ outputIndex = this.getNodeConnectionOutputIndex(
784
+ connection.node,
785
+ parentNodeName,
786
+ type,
787
+ newDepth,
788
+ checkedNodes,
789
+ );
790
+
791
+ if (outputIndex !== undefined) {
792
+ return outputIndex;
793
+ }
794
+ }
795
+ }
796
+
797
+ return undefined;
798
+ }
799
+
800
+ /**
801
+ * Returns from which of the given nodes the workflow should get started from
802
+ *
803
+ * @param {string[]} nodeNames The potential start nodes
804
+ * @returns {(INode | undefined)}
805
+ * @memberof Workflow
806
+ */
807
+ __getStartNode(nodeNames: string[]): INode | undefined {
808
+ // Check if there are any trigger or poll nodes and then return the first one
809
+ let node: INode;
810
+ let nodeType: INodeType;
811
+ for (const nodeName of nodeNames) {
812
+ node = this.nodes[nodeName];
813
+
814
+ nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion) as INodeType;
815
+
816
+ if (nodeType.trigger !== undefined || nodeType.poll !== undefined) {
817
+ if (node.disabled === true) {
818
+ continue;
819
+ }
820
+ return node;
821
+ }
822
+ }
823
+
824
+ // Check if there is the actual "start" node
825
+ const startNodeType = 'n8n-nodes-base.start';
826
+ for (const nodeName of nodeNames) {
827
+ node = this.nodes[nodeName];
828
+ if (node.type === startNodeType) {
829
+ return node;
830
+ }
831
+ }
832
+
833
+ return undefined;
834
+ }
835
+
836
+ /**
837
+ * Returns the start node to start the worfklow from
838
+ *
839
+ * @param {string} [destinationNode]
840
+ * @returns {(INode | undefined)}
841
+ * @memberof Workflow
842
+ */
843
+ getStartNode(destinationNode?: string): INode | undefined {
844
+ if (destinationNode) {
845
+ // Find the highest parent nodes of the given one
846
+ const nodeNames = this.getHighestNode(destinationNode);
847
+
848
+ if (nodeNames.length === 0) {
849
+ // If no parent nodes have been found then only the destination-node
850
+ // is in the tree so add that one
851
+ nodeNames.push(destinationNode);
852
+ }
853
+
854
+ // Check which node to return as start node
855
+ const node = this.__getStartNode(nodeNames);
856
+ if (node !== undefined) {
857
+ return node;
858
+ }
859
+
860
+ // If none of the above did find anything simply return the
861
+ // first parent node in the list
862
+ return this.nodes[nodeNames[0]];
863
+ }
864
+
865
+ return this.__getStartNode(Object.keys(this.nodes));
866
+ }
867
+
868
+ /**
869
+ * Executes the Webhooks method of the node
870
+ *
871
+ * @param {WebhookSetupMethodNames} method The name of the method to execute
872
+ * @param {IWebhookData} webhookData
873
+ * @param {INodeExecuteFunctions} nodeExecuteFunctions
874
+ * @param {WorkflowExecuteMode} mode
875
+ * @returns {(Promise<boolean | undefined>)}
876
+ * @memberof Workflow
877
+ */
878
+ async runWebhookMethod(
879
+ method: WebhookSetupMethodNames,
880
+ webhookData: IWebhookData,
881
+ nodeExecuteFunctions: INodeExecuteFunctions,
882
+ mode: WorkflowExecuteMode,
883
+ activation: WorkflowActivateMode,
884
+ isTest?: boolean,
885
+ ): Promise<boolean | undefined> {
886
+ const node = this.getNode(webhookData.node) as INode;
887
+ const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion) as INodeType;
888
+
889
+ if (nodeType.webhookMethods === undefined) {
890
+ return;
891
+ }
892
+
893
+ if (nodeType.webhookMethods[webhookData.webhookDescription.name] === undefined) {
894
+ return;
895
+ }
896
+
897
+ if (nodeType.webhookMethods[webhookData.webhookDescription.name][method] === undefined) {
898
+ return;
899
+ }
900
+
901
+ const thisArgs = nodeExecuteFunctions.getExecuteHookFunctions(
902
+ this,
903
+ node,
904
+ webhookData.workflowExecuteAdditionalData,
905
+ mode,
906
+ activation,
907
+ isTest,
908
+ webhookData,
909
+ );
910
+ // eslint-disable-next-line consistent-return
911
+ return nodeType.webhookMethods[webhookData.webhookDescription.name][method]!.call(thisArgs);
912
+ }
913
+
914
+ /**
915
+ * Runs the given trigger node so that it can trigger the workflow
916
+ * when the node has data.
917
+ *
918
+ * @param {INode} node
919
+ * @param {IGetExecuteTriggerFunctions} getTriggerFunctions
920
+ * @param {IWorkflowExecuteAdditionalData} additionalData
921
+ * @param {WorkflowExecuteMode} mode
922
+ * @returns {(Promise<ITriggerResponse | undefined>)}
923
+ * @memberof Workflow
924
+ */
925
+ async runTrigger(
926
+ node: INode,
927
+ getTriggerFunctions: IGetExecuteTriggerFunctions,
928
+ additionalData: IWorkflowExecuteAdditionalData,
929
+ mode: WorkflowExecuteMode,
930
+ activation: WorkflowActivateMode,
931
+ ): Promise<ITriggerResponse | undefined> {
932
+ const triggerFunctions = getTriggerFunctions(this, node, additionalData, mode, activation);
933
+
934
+ const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
935
+
936
+ if (nodeType === undefined) {
937
+ throw new Error(`The node type "${node.type}" of node "${node.name}" is not known.`);
938
+ }
939
+
940
+ if (!nodeType.trigger) {
941
+ throw new Error(
942
+ `The node type "${node.type}" of node "${node.name}" does not have a trigger function defined.`,
943
+ );
944
+ }
945
+
946
+ if (mode === 'manual') {
947
+ // In manual mode we do not just start the trigger function we also
948
+ // want to be able to get informed as soon as the first data got emitted
949
+ const triggerResponse = await nodeType.trigger.call(triggerFunctions);
950
+
951
+ // Add the manual trigger response which resolves when the first time data got emitted
952
+ triggerResponse!.manualTriggerResponse = new Promise((resolve) => {
953
+ triggerFunctions.emit = (
954
+ (resolveEmit) =>
955
+ (
956
+ data: INodeExecutionData[][],
957
+ responsePromise?: IDeferredPromise<IExecuteResponsePromiseData>,
958
+ ) => {
959
+ additionalData.hooks!.hookFunctions.sendResponse = [
960
+ async (response: IExecuteResponsePromiseData): Promise<void> => {
961
+ if (responsePromise) {
962
+ responsePromise.resolve(response);
963
+ }
964
+ },
965
+ ];
966
+
967
+ resolveEmit(data);
968
+ }
969
+ )(resolve);
970
+ });
971
+
972
+ return triggerResponse;
973
+ }
974
+ // In all other modes simply start the trigger
975
+ return nodeType.trigger.call(triggerFunctions);
976
+ }
977
+
978
+ /**
979
+ * Runs the given trigger node so that it can trigger the workflow
980
+ * when the node has data.
981
+ *
982
+ * @param {INode} node
983
+ * @param {IPollFunctions} pollFunctions
984
+ * @returns
985
+ * @memberof Workflow
986
+ */
987
+
988
+ async runPoll(
989
+ node: INode,
990
+ pollFunctions: IPollFunctions,
991
+ ): Promise<INodeExecutionData[][] | null> {
992
+ const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
993
+
994
+ if (nodeType === undefined) {
995
+ throw new Error(`The node type "${node.type}" of node "${node.name}" is not known.`);
996
+ }
997
+
998
+ if (!nodeType.poll) {
999
+ throw new Error(
1000
+ `The node type "${node.type}" of node "${node.name}" does not have a poll function defined.`,
1001
+ );
1002
+ }
1003
+
1004
+ return nodeType.poll.call(pollFunctions);
1005
+ }
1006
+
1007
+ /**
1008
+ * Executes the webhook data to see what it should return and if the
1009
+ * workflow should be started or not
1010
+ *
1011
+ * @param {INode} node
1012
+ * @param {IWorkflowExecuteAdditionalData} additionalData
1013
+ * @param {INodeExecuteFunctions} nodeExecuteFunctions
1014
+ * @param {WorkflowExecuteMode} mode
1015
+ * @param {IRunExecutionData | undefined} runExecutionData
1016
+ * @returns {Promise<IWebhookResponseData>}
1017
+ * @memberof Workflow
1018
+ */
1019
+ async runWebhook(
1020
+ webhookData: IWebhookData,
1021
+ node: INode,
1022
+ additionalData: IWorkflowExecuteAdditionalData,
1023
+ nodeExecuteFunctions: INodeExecuteFunctions,
1024
+ mode: WorkflowExecuteMode,
1025
+ runExecutionData: IRunExecutionData | undefined,
1026
+ workflow?: Workflow,
1027
+ ): Promise<IWebhookResponseData> {
1028
+ const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
1029
+ if (nodeType === undefined) {
1030
+ throw new Error(`The type of the webhook node "${node.name}" is not known.`);
1031
+ } else if (nodeType.webhook === undefined) {
1032
+ throw new Error(`The node "${node.name}" does not have any webhooks defined.`);
1033
+ }
1034
+
1035
+ const thisArgs = nodeExecuteFunctions.getExecuteWebhookFunctions(
1036
+ this,
1037
+ node,
1038
+ additionalData,
1039
+ mode,
1040
+ webhookData,
1041
+ runExecutionData,
1042
+ );
1043
+
1044
+ return nodeType.webhook.call(thisArgs, workflow);
1045
+ }
1046
+
1047
+ /**
1048
+ * Executes the given node.
1049
+ *
1050
+ * @param {INode} node
1051
+ * @param {ITaskDataConnections} inputData
1052
+ * @param {IRunExecutionData} runExecutionData
1053
+ * @param {number} runIndex
1054
+ * @param {IWorkflowExecuteAdditionalData} additionalData
1055
+ * @param {INodeExecuteFunctions} nodeExecuteFunctions
1056
+ * @param {WorkflowExecuteMode} mode
1057
+ * @returns {(Promise<INodeExecutionData[][] | null>)}
1058
+ * @memberof Workflow
1059
+ */
1060
+ async runNode(
1061
+ node: INode,
1062
+ inputData: ITaskDataConnections,
1063
+ runExecutionData: IRunExecutionData,
1064
+ runIndex: number,
1065
+ additionalData: IWorkflowExecuteAdditionalData,
1066
+ nodeExecuteFunctions: INodeExecuteFunctions,
1067
+ mode: WorkflowExecuteMode,
1068
+ ): Promise<INodeExecutionData[][] | null | undefined> {
1069
+ if (node.disabled === true) {
1070
+ // If node is disabled simply pass the data through
1071
+ // return NodeRunHelpers.
1072
+ if (inputData.hasOwnProperty('main') && inputData['main'].length > 0) {
1073
+ // If the node is disabled simply return the data from the first main input
1074
+ if (inputData['main'][0] === null) {
1075
+ return undefined;
1076
+ }
1077
+ return [inputData['main'][0]];
1078
+ }
1079
+ return undefined;
1080
+ }
1081
+
1082
+ const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
1083
+ if (nodeType === undefined) {
1084
+ throw new Error(`Node type "${node.type}" is not known so can not run it!`);
1085
+ }
1086
+
1087
+ let connectionInputData: INodeExecutionData[] = [];
1088
+ if (
1089
+ nodeType.execute ||
1090
+ nodeType.executeSingle ||
1091
+ (!nodeType.poll && !nodeType.trigger && !nodeType.webhook)
1092
+ ) {
1093
+ // Only stop if first input is empty for execute & executeSingle runs. For all others run anyways
1094
+ // because then it is a trigger node. As they only pass data through and so the input-data
1095
+ // becomes output-data it has to be possible.
1096
+
1097
+ if (inputData.hasOwnProperty('main') && inputData['main'].length > 0) {
1098
+ // We always use the data of main input and the first input for executeSingle
1099
+ connectionInputData = inputData['main'][0] as INodeExecutionData[];
1100
+ }
1101
+
1102
+ if (connectionInputData.length === 0) {
1103
+ // No data for node so return
1104
+ return undefined;
1105
+ }
1106
+ }
1107
+
1108
+ if (
1109
+ runExecutionData.resultData.lastNodeExecuted === node.name &&
1110
+ runExecutionData.resultData.error !== undefined
1111
+ ) {
1112
+ // The node did already fail. So throw an error here that it displays and logs it correctly.
1113
+ // Does get used by webhook and trigger nodes in case they throw an error that it is possible
1114
+ // to log the error and display in Editor-UI.
1115
+
1116
+ const error = new Error(runExecutionData.resultData.error.message);
1117
+ error.stack = runExecutionData.resultData.error.stack;
1118
+ throw error;
1119
+ }
1120
+
1121
+ if (node.executeOnce === true) {
1122
+ // If node should be executed only once so use only the first input item
1123
+ connectionInputData = connectionInputData.slice(0, 1);
1124
+ const newInputData: ITaskDataConnections = {};
1125
+ for (const inputName of Object.keys(inputData)) {
1126
+ newInputData[inputName] = inputData[inputName].map((input: any) => {
1127
+ // eslint-disable-next-line @typescript-eslint/prefer-optional-chain
1128
+ return input && input.slice(0, 1);
1129
+ });
1130
+ }
1131
+ inputData = newInputData;
1132
+ }
1133
+
1134
+ if (nodeType.executeSingle) {
1135
+ const returnPromises: Array<Promise<INodeExecutionData>> = [];
1136
+
1137
+ for (let itemIndex = 0; itemIndex < connectionInputData.length; itemIndex++) {
1138
+ const thisArgs = nodeExecuteFunctions.getExecuteSingleFunctions(
1139
+ this,
1140
+ runExecutionData,
1141
+ runIndex,
1142
+ connectionInputData,
1143
+ inputData,
1144
+ node,
1145
+ itemIndex,
1146
+ additionalData,
1147
+ mode,
1148
+ );
1149
+
1150
+ returnPromises.push(nodeType.executeSingle.call(thisArgs));
1151
+ }
1152
+
1153
+ if (returnPromises.length === 0) {
1154
+ return null;
1155
+ }
1156
+
1157
+ let promiseResults;
1158
+ try {
1159
+ promiseResults = await Promise.all(returnPromises);
1160
+ } catch (error) {
1161
+ return Promise.reject(error);
1162
+ }
1163
+
1164
+ if (promiseResults) {
1165
+ return [promiseResults];
1166
+ }
1167
+ } else if (nodeType.execute) {
1168
+ const closeFunctions: CloseFunction[] = [];
1169
+ const thisArgs = nodeExecuteFunctions.getExecuteFunctions(
1170
+ this,
1171
+ runExecutionData,
1172
+ runIndex,
1173
+ connectionInputData,
1174
+ inputData,
1175
+ node,
1176
+ additionalData,
1177
+ mode,
1178
+ nodeType,
1179
+ closeFunctions,
1180
+ );
1181
+
1182
+ const data = await nodeType.execute.call(thisArgs);
1183
+ const closeFunctionsResults = await Promise.allSettled(
1184
+ closeFunctions.map(async (fn) => await fn()),
1185
+ );
1186
+
1187
+ const closingErrors = closeFunctionsResults
1188
+ .filter((result): result is PromiseRejectedResult => result.status === 'rejected')
1189
+ .map((result) => result.reason);
1190
+
1191
+ if (closingErrors.length > 0) {
1192
+ if (closingErrors[0] instanceof Error) throw closingErrors[0];
1193
+ throw new Error(`Error on execution node's close function(s). Node Name: ${node.name}, Node Type: ${node.type}, Errors: ${closingErrors.join(', ')}`);
1194
+ }
1195
+
1196
+ return data;
1197
+ } else if (nodeType.poll) {
1198
+ if (mode === 'manual') {
1199
+ // In manual mode run the poll function
1200
+ const thisArgs = nodeExecuteFunctions.getExecutePollFunctions(
1201
+ this,
1202
+ node,
1203
+ additionalData,
1204
+ mode,
1205
+ 'manual',
1206
+ );
1207
+ return nodeType.poll.call(thisArgs);
1208
+ }
1209
+ // In any other mode pass data through as it already contains the result of the poll
1210
+ return inputData['main'] as INodeExecutionData[][];
1211
+ } else if (nodeType.trigger) {
1212
+ if (mode === 'manual') {
1213
+ // In manual mode start the trigger
1214
+ const triggerResponse = await this.runTrigger(
1215
+ node,
1216
+ nodeExecuteFunctions.getExecuteTriggerFunctions,
1217
+ additionalData,
1218
+ mode,
1219
+ 'manual',
1220
+ );
1221
+
1222
+ if (triggerResponse === undefined) {
1223
+ return null;
1224
+ }
1225
+
1226
+ if (triggerResponse.manualTriggerFunction !== undefined) {
1227
+ // If a manual trigger function is defined call it and wait till it did run
1228
+ await triggerResponse.manualTriggerFunction();
1229
+ }
1230
+
1231
+ const response = await triggerResponse.manualTriggerResponse!;
1232
+
1233
+ // And then close it again after it did execute
1234
+ if (triggerResponse.closeFunction) {
1235
+ await triggerResponse.closeFunction();
1236
+ }
1237
+
1238
+ if (response.length === 0) {
1239
+ return null;
1240
+ }
1241
+
1242
+ return response;
1243
+ }
1244
+ // For trigger nodes in any mode except "manual" do we simply pass the data through
1245
+ return inputData['main'] as INodeExecutionData[][];
1246
+ } else if (nodeType.webhook) {
1247
+ // For webhook nodes always simply pass the data through
1248
+ return inputData['main'] as INodeExecutionData[][];
1249
+ } else {
1250
+ // For nodes which have routing information on properties
1251
+
1252
+ const routingNode = new RoutingNode(
1253
+ this,
1254
+ node,
1255
+ connectionInputData,
1256
+ runExecutionData ?? null,
1257
+ additionalData,
1258
+ mode,
1259
+ );
1260
+
1261
+ return routingNode.runNode(inputData, runIndex, nodeType, nodeExecuteFunctions);
1262
+ }
1263
+
1264
+ return null;
1265
+ }
1266
+ }