@fsai-flow/core 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/README.md +11 -0
  2. package/dist/README.md +11 -0
  3. package/dist/package.json +44 -0
  4. package/dist/src/index.d.ts +15 -0
  5. package/dist/src/index.js +29 -0
  6. package/dist/src/index.js.map +1 -0
  7. package/dist/src/lib/ActiveWebhooks.d.ts +59 -0
  8. package/dist/src/lib/ActiveWebhooks.js +184 -0
  9. package/dist/src/lib/ActiveWebhooks.js.map +1 -0
  10. package/dist/src/lib/ActiveWorkflows.d.ts +58 -0
  11. package/dist/src/lib/ActiveWorkflows.js +244 -0
  12. package/dist/src/lib/ActiveWorkflows.js.map +1 -0
  13. package/dist/src/lib/BinaryDataManager/FileSystem.d.ts +26 -0
  14. package/dist/src/lib/BinaryDataManager/FileSystem.js +179 -0
  15. package/dist/src/lib/BinaryDataManager/FileSystem.js.map +1 -0
  16. package/dist/src/lib/BinaryDataManager/index.d.ts +21 -0
  17. package/dist/src/lib/BinaryDataManager/index.js +146 -0
  18. package/dist/src/lib/BinaryDataManager/index.js.map +1 -0
  19. package/dist/src/lib/ChangeCase.d.ts +9 -0
  20. package/dist/src/lib/ChangeCase.js +43 -0
  21. package/dist/src/lib/ChangeCase.js.map +1 -0
  22. package/dist/src/lib/Constants.d.ts +14 -0
  23. package/dist/src/lib/Constants.js +19 -0
  24. package/dist/src/lib/Constants.js.map +1 -0
  25. package/dist/src/lib/Credentials.d.ts +27 -0
  26. package/dist/src/lib/Credentials.js +89 -0
  27. package/dist/src/lib/Credentials.js.map +1 -0
  28. package/dist/src/lib/FileSystem.d.ts +26 -0
  29. package/dist/src/lib/FileSystem.js +179 -0
  30. package/dist/src/lib/FileSystem.js.map +1 -0
  31. package/dist/src/lib/InputConnectionDataLegacy.d.ts +2 -0
  32. package/dist/src/lib/InputConnectionDataLegacy.js +79 -0
  33. package/dist/src/lib/InputConnectionDataLegacy.js.map +1 -0
  34. package/dist/src/lib/Interfaces.d.ts +148 -0
  35. package/dist/src/lib/Interfaces.js +3 -0
  36. package/dist/src/lib/Interfaces.js.map +1 -0
  37. package/dist/src/lib/LoadNodeParameterOptions.d.ts +39 -0
  38. package/dist/src/lib/LoadNodeParameterOptions.js +150 -0
  39. package/dist/src/lib/LoadNodeParameterOptions.js.map +1 -0
  40. package/dist/src/lib/NodeExecuteFunctions.d.ts +226 -0
  41. package/dist/src/lib/NodeExecuteFunctions.js +2483 -0
  42. package/dist/src/lib/NodeExecuteFunctions.js.map +1 -0
  43. package/dist/src/lib/NodesLoader/constants.d.ts +5 -0
  44. package/dist/src/lib/NodesLoader/constants.js +106 -0
  45. package/dist/src/lib/NodesLoader/constants.js.map +1 -0
  46. package/dist/src/lib/NodesLoader/custom-directory-loader.d.ts +9 -0
  47. package/dist/src/lib/NodesLoader/custom-directory-loader.js +36 -0
  48. package/dist/src/lib/NodesLoader/custom-directory-loader.js.map +1 -0
  49. package/dist/src/lib/NodesLoader/directory-loader.d.ts +66 -0
  50. package/dist/src/lib/NodesLoader/directory-loader.js +325 -0
  51. package/dist/src/lib/NodesLoader/directory-loader.js.map +1 -0
  52. package/dist/src/lib/NodesLoader/index.d.ts +5 -0
  53. package/dist/src/lib/NodesLoader/index.js +12 -0
  54. package/dist/src/lib/NodesLoader/index.js.map +1 -0
  55. package/dist/src/lib/NodesLoader/lazy-package-directory-loader.d.ts +7 -0
  56. package/dist/src/lib/NodesLoader/lazy-package-directory-loader.js +52 -0
  57. package/dist/src/lib/NodesLoader/lazy-package-directory-loader.js.map +1 -0
  58. package/dist/src/lib/NodesLoader/load-class-in-isolation.d.ts +1 -0
  59. package/dist/src/lib/NodesLoader/load-class-in-isolation.js +22 -0
  60. package/dist/src/lib/NodesLoader/load-class-in-isolation.js.map +1 -0
  61. package/dist/src/lib/NodesLoader/package-directory-loader.d.ts +17 -0
  62. package/dist/src/lib/NodesLoader/package-directory-loader.js +100 -0
  63. package/dist/src/lib/NodesLoader/package-directory-loader.js.map +1 -0
  64. package/dist/src/lib/NodesLoader/types.d.ts +14 -0
  65. package/dist/src/lib/NodesLoader/types.js +3 -0
  66. package/dist/src/lib/NodesLoader/types.js.map +1 -0
  67. package/dist/src/lib/UserSettings.d.ts +80 -0
  68. package/dist/src/lib/UserSettings.js +261 -0
  69. package/dist/src/lib/UserSettings.js.map +1 -0
  70. package/dist/src/lib/WorkflowExecute.d.ts +53 -0
  71. package/dist/src/lib/WorkflowExecute.js +835 -0
  72. package/dist/src/lib/WorkflowExecute.js.map +1 -0
  73. package/dist/src/lib/index.d.ts +21 -0
  74. package/dist/src/lib/index.js +146 -0
  75. package/dist/src/lib/index.js.map +1 -0
  76. package/dist/src/utils/crypto.d.ts +1 -0
  77. package/dist/src/utils/crypto.js +8 -0
  78. package/dist/src/utils/crypto.js.map +1 -0
  79. package/eslint.config.js +19 -0
  80. package/jest.config.ts +10 -0
  81. package/package.json +44 -0
  82. package/project.json +19 -0
  83. package/src/index.ts +27 -0
  84. package/src/lib/ActiveWebhooks.ts +245 -0
  85. package/src/lib/ActiveWorkflows.ts +304 -0
  86. package/src/lib/BinaryDataManager/FileSystem.ts +214 -0
  87. package/src/lib/BinaryDataManager/index.ts +187 -0
  88. package/src/lib/ChangeCase.ts +45 -0
  89. package/src/lib/Constants.ts +16 -0
  90. package/src/lib/Credentials.ts +108 -0
  91. package/src/lib/FileSystem.ts +214 -0
  92. package/src/lib/InputConnectionDataLegacy.ts +123 -0
  93. package/src/lib/Interfaces.ts +338 -0
  94. package/src/lib/LoadNodeParameterOptions.ts +235 -0
  95. package/src/lib/NodeExecuteFunctions.ts +3704 -0
  96. package/src/lib/NodesLoader/constants.ts +112 -0
  97. package/src/lib/NodesLoader/custom-directory-loader.ts +31 -0
  98. package/src/lib/NodesLoader/directory-loader.ts +458 -0
  99. package/src/lib/NodesLoader/index.ts +5 -0
  100. package/src/lib/NodesLoader/lazy-package-directory-loader.ts +55 -0
  101. package/src/lib/NodesLoader/load-class-in-isolation.ts +19 -0
  102. package/src/lib/NodesLoader/package-directory-loader.ts +107 -0
  103. package/src/lib/NodesLoader/types.ts +14 -0
  104. package/src/lib/UserSettings.ts +292 -0
  105. package/src/lib/WorkflowExecute.ts +1108 -0
  106. package/src/lib/index.ts +187 -0
  107. package/src/utils/crypto.ts +5 -0
  108. package/tests/Credentials.test.ts +88 -0
  109. package/tests/Helpers.ts +808 -0
  110. package/tests/WorkflowExecute.test.ts +1242 -0
  111. package/tsconfig.json +42 -0
  112. package/tsconfig.lib.json +10 -0
  113. package/tsconfig.spec.json +14 -0
@@ -0,0 +1,835 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WorkflowExecute = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const PCancelable = require("p-cancelable");
6
+ const workflow_1 = require("@fsai-flow/workflow");
7
+ const lodash_1 = require("lodash");
8
+ const src_1 = require("../../src");
9
+ class WorkflowExecute {
10
+ constructor(additionalData, mode, runExecutionData) {
11
+ this.additionalData = additionalData;
12
+ this.mode = mode;
13
+ this.runExecutionData = runExecutionData || {
14
+ startData: {},
15
+ resultData: {
16
+ runData: {},
17
+ },
18
+ executionData: {
19
+ contextData: {},
20
+ nodeExecutionStack: [],
21
+ waitingExecution: {},
22
+ },
23
+ };
24
+ }
25
+ /**
26
+ * Executes the given workflow.
27
+ *
28
+ * @param {Workflow} workflow The workflow to execute
29
+ * @param {INode[]} [startNodes] Node to start execution from
30
+ * @param {string} [destinationNode] Node to stop execution at
31
+ * @returns {(Promise<string>)}
32
+ * @memberof WorkflowExecute
33
+ */
34
+ // IMPORTANT: Do not add "async" to this function, it will then convert the
35
+ // PCancelable to a regular Promise and does so not allow canceling
36
+ // active executions anymore
37
+ // eslint-disable-next-line @typescript-eslint/promise-function-async
38
+ run(workflow, startNode, destinationNode) {
39
+ // Get the nodes to start workflow execution from
40
+ startNode = startNode || workflow.getStartNode(destinationNode);
41
+ if (startNode === undefined) {
42
+ throw new Error('No node to start the workflow from could be found!');
43
+ }
44
+ // If a destination node is given we only run the direct parent nodes and no others
45
+ let runNodeFilter;
46
+ if (destinationNode) {
47
+ runNodeFilter = workflow.getParentNodes(destinationNode);
48
+ runNodeFilter.push(destinationNode);
49
+ }
50
+ // Initialize the data of the start nodes
51
+ const nodeExecutionStack = [
52
+ {
53
+ node: startNode,
54
+ data: {
55
+ main: [
56
+ [
57
+ {
58
+ json: {},
59
+ },
60
+ ],
61
+ ],
62
+ },
63
+ },
64
+ ];
65
+ this.runExecutionData = {
66
+ startData: {
67
+ destinationNode,
68
+ runNodeFilter,
69
+ },
70
+ resultData: {
71
+ runData: {},
72
+ },
73
+ executionData: {
74
+ contextData: {},
75
+ nodeExecutionStack,
76
+ waitingExecution: {},
77
+ },
78
+ };
79
+ return this.processRunExecutionData(workflow);
80
+ }
81
+ /**
82
+ * Executes the given workflow but only
83
+ *
84
+ * @param {Workflow} workflow The workflow to execute
85
+ * @param {IRunData} runData
86
+ * @param {string[]} startNodes Nodes to start execution from
87
+ * @param {string} destinationNode Node to stop execution at
88
+ * @returns {(Promise<string>)}
89
+ * @memberof WorkflowExecute
90
+ */
91
+ // IMPORTANT: Do not add "async" to this function, it will then convert the
92
+ // PCancelable to a regular Promise and does so not allow canceling
93
+ // active executions anymore
94
+ // eslint-disable-next-line @typescript-eslint/promise-function-async
95
+ runPartialWorkflow(workflow, runData, startNodes, destinationNode) {
96
+ let incomingNodeConnections;
97
+ let connection;
98
+ const runIndex = 0;
99
+ // Initialize the nodeExecutionStack and waitingExecution with
100
+ // the data from runData
101
+ const nodeExecutionStack = [];
102
+ const waitingExecution = {};
103
+ for (const startNode of startNodes) {
104
+ incomingNodeConnections = workflow.connectionsByDestinationNode[startNode];
105
+ const incomingData = [];
106
+ if (incomingNodeConnections === undefined) {
107
+ // If it has no incoming data add the default empty data
108
+ incomingData.push([
109
+ {
110
+ json: {},
111
+ },
112
+ ]);
113
+ }
114
+ else {
115
+ // Get the data of the incoming connections
116
+ for (const connections of incomingNodeConnections['main']) {
117
+ for (let inputIndex = 0; inputIndex < connections.length; inputIndex++) {
118
+ connection = connections[inputIndex];
119
+ incomingData.push(
120
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
121
+ runData[connection.node][runIndex].data[connection.type][connection.index]);
122
+ }
123
+ }
124
+ }
125
+ const executeData = {
126
+ node: workflow.getNode(startNode),
127
+ data: {
128
+ main: incomingData,
129
+ },
130
+ };
131
+ nodeExecutionStack.push(executeData);
132
+ // Check if the destinationNode has to be added as waiting
133
+ // because some input data is already fully available
134
+ incomingNodeConnections = workflow.connectionsByDestinationNode[destinationNode];
135
+ if (incomingNodeConnections !== undefined) {
136
+ for (const connections of incomingNodeConnections['main']) {
137
+ for (let inputIndex = 0; inputIndex < connections.length; inputIndex++) {
138
+ connection = connections[inputIndex];
139
+ if (waitingExecution[destinationNode] === undefined) {
140
+ waitingExecution[destinationNode] = {};
141
+ }
142
+ if (waitingExecution[destinationNode][runIndex] === undefined) {
143
+ waitingExecution[destinationNode][runIndex] = {};
144
+ }
145
+ if (waitingExecution[destinationNode][runIndex][connection.type] === undefined) {
146
+ waitingExecution[destinationNode][runIndex][connection.type] = [];
147
+ }
148
+ if (runData[connection.node] !== undefined) {
149
+ // Input data exists so add as waiting
150
+ // incomingDataDestination.push(runData[connection.node!][runIndex].data![connection.type][connection.index]);
151
+ waitingExecution[destinationNode][runIndex][connection.type].push(runData[connection.node][runIndex].data[connection.type][connection.index]);
152
+ }
153
+ else {
154
+ waitingExecution[destinationNode][runIndex][connection.type].push(null);
155
+ }
156
+ }
157
+ }
158
+ }
159
+ }
160
+ // Only run the parent nodes and no others
161
+ let runNodeFilter;
162
+ // eslint-disable-next-line prefer-const
163
+ runNodeFilter = workflow.getParentNodes(destinationNode);
164
+ runNodeFilter.push(destinationNode);
165
+ this.runExecutionData = {
166
+ startData: {
167
+ destinationNode,
168
+ runNodeFilter,
169
+ },
170
+ resultData: {
171
+ runData,
172
+ },
173
+ executionData: {
174
+ contextData: {},
175
+ nodeExecutionStack,
176
+ waitingExecution,
177
+ },
178
+ };
179
+ return this.processRunExecutionData(workflow);
180
+ }
181
+ /**
182
+ * Executes the hook with the given name
183
+ *
184
+ * @param {string} hookName
185
+ * @param {any[]} parameters
186
+ * @returns {Promise<IRun>}
187
+ * @memberof WorkflowExecute
188
+ */
189
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
190
+ executeHook(hookName, parameters) {
191
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
192
+ // tslint:disable-line:no-any
193
+ if (this.additionalData.hooks === undefined) {
194
+ return;
195
+ }
196
+ // eslint-disable-next-line consistent-return
197
+ return this.additionalData.hooks.executeHookFunctions(hookName, parameters);
198
+ });
199
+ }
200
+ /**
201
+ * Checks the incoming connection does not receive any data
202
+ */
203
+ incomingConnectionIsEmpty(runData, inputConnections, runIndex) {
204
+ // for (const inputConnection of workflow.connectionsByDestinationNode[nodeToAdd].main[0]) {
205
+ for (const inputConnection of inputConnections) {
206
+ const nodeIncomingData = (0, lodash_1.get)(runData, `[${inputConnection.node}][${runIndex}].data.main[${inputConnection.index}]`);
207
+ if (nodeIncomingData !== undefined && nodeIncomingData.length !== 0) {
208
+ return false;
209
+ }
210
+ }
211
+ return true;
212
+ }
213
+ addNodeToBeExecuted(workflow, connectionData, outputIndex, parentNodeName, nodeSuccessData, runIndex) {
214
+ let stillDataMissing = false;
215
+ // Check if node has multiple inputs as then we have to wait for all input data
216
+ // to be present before we can add it to the node-execution-stack
217
+ if (workflow.connectionsByDestinationNode[connectionData.node]['main'].length > 1) {
218
+ // Node has multiple inputs
219
+ let nodeWasWaiting = true;
220
+ // Check if there is already data for the node
221
+ if (this.runExecutionData.executionData.waitingExecution[connectionData.node] === undefined) {
222
+ // Node does not have data yet so create a new empty one
223
+ this.runExecutionData.executionData.waitingExecution[connectionData.node] = {};
224
+ nodeWasWaiting = false;
225
+ }
226
+ if (this.runExecutionData.executionData.waitingExecution[connectionData.node][runIndex] ===
227
+ undefined) {
228
+ // Node does not have data for runIndex yet so create also empty one and init it
229
+ this.runExecutionData.executionData.waitingExecution[connectionData.node][runIndex] = {
230
+ main: [],
231
+ };
232
+ for (let i = 0; i < workflow.connectionsByDestinationNode[connectionData.node]['main'].length; i++) {
233
+ this.runExecutionData.executionData.waitingExecution[connectionData.node][runIndex]['main'].push(null);
234
+ }
235
+ }
236
+ // Add the new data
237
+ if (nodeSuccessData === null) {
238
+ this.runExecutionData.executionData.waitingExecution[connectionData.node][runIndex]['main'][connectionData.index] = null;
239
+ }
240
+ else {
241
+ this.runExecutionData.executionData.waitingExecution[connectionData.node][runIndex]['main'][connectionData.index] = nodeSuccessData[outputIndex];
242
+ }
243
+ // Check if all data exists now
244
+ let thisExecutionData;
245
+ let allDataFound = true;
246
+ for (let i = 0; i <
247
+ this.runExecutionData.executionData.waitingExecution[connectionData.node][runIndex]['main']
248
+ .length; i++) {
249
+ thisExecutionData =
250
+ this.runExecutionData.executionData.waitingExecution[connectionData.node][runIndex]['main'][i];
251
+ if (thisExecutionData === null) {
252
+ allDataFound = false;
253
+ break;
254
+ }
255
+ }
256
+ if (allDataFound) {
257
+ // All data exists for node to be executed
258
+ // So add it to the execution stack
259
+ this.runExecutionData.executionData.nodeExecutionStack.push({
260
+ node: workflow.nodes[connectionData.node],
261
+ data: this.runExecutionData.executionData.waitingExecution[connectionData.node][runIndex],
262
+ });
263
+ // Remove the data from waiting
264
+ delete this.runExecutionData.executionData.waitingExecution[connectionData.node][runIndex];
265
+ if (Object.keys(this.runExecutionData.executionData.waitingExecution[connectionData.node])
266
+ .length === 0) {
267
+ // No more data left for the node so also delete that one
268
+ delete this.runExecutionData.executionData.waitingExecution[connectionData.node];
269
+ }
270
+ return;
271
+ }
272
+ stillDataMissing = true;
273
+ if (!nodeWasWaiting) {
274
+ // Get a list of all the output nodes that we can check for siblings easier
275
+ const checkOutputNodes = [];
276
+ // eslint-disable-next-line @typescript-eslint/no-for-in-array
277
+ for (const outputIndexParent in workflow.connectionsBySourceNode[parentNodeName]['main']) {
278
+ if (
279
+ // eslint-disable-next-line no-prototype-builtins
280
+ !workflow.connectionsBySourceNode[parentNodeName]['main'].hasOwnProperty(outputIndexParent)) {
281
+ continue;
282
+ }
283
+ for (const connectionDataCheck of workflow.connectionsBySourceNode[parentNodeName]['main'][outputIndexParent]) {
284
+ checkOutputNodes.push(connectionDataCheck.node);
285
+ }
286
+ }
287
+ // Node was not on "waitingExecution" so it is the first time it gets
288
+ // checked. So we have to go through all the inputs and check if they
289
+ // are already on the list to be processed.
290
+ // If that is not the case add it.
291
+ for (let inputIndex = 0; inputIndex < workflow.connectionsByDestinationNode[connectionData.node]['main'].length; inputIndex++) {
292
+ for (const inputData of workflow.connectionsByDestinationNode[connectionData.node]['main'][inputIndex]) {
293
+ if (inputData.node === parentNodeName) {
294
+ // Is the node we come from so its data will be available for sure
295
+ continue;
296
+ }
297
+ const executionStackNodes = this.runExecutionData.executionData.nodeExecutionStack.map((stackData) => stackData.node.name);
298
+ // Check if that node is also an output connection of the
299
+ // previously processed one
300
+ if (inputData.node !== parentNodeName && checkOutputNodes.includes(inputData.node)) {
301
+ // So the parent node will be added anyway which
302
+ // will then process this node next. So nothing to do
303
+ // unless the incoming data of the node is empty
304
+ // because then it would not be executed
305
+ if (!this.incomingConnectionIsEmpty(this.runExecutionData.resultData.runData, workflow.connectionsByDestinationNode[inputData.node]['main'][0], runIndex)) {
306
+ continue;
307
+ }
308
+ }
309
+ // Check if it is already in the execution stack
310
+ if (executionStackNodes.includes(inputData.node)) {
311
+ // Node is already on the list to be executed
312
+ // so there is nothing to do
313
+ continue;
314
+ }
315
+ // Check if node got processed already
316
+ if (this.runExecutionData.resultData.runData[inputData.node] !== undefined) {
317
+ // Node got processed already so no need to add it
318
+ continue;
319
+ }
320
+ // Check if any of the parent nodes does not have any inputs. That
321
+ // would mean that it has to get added to the list of nodes to process.
322
+ const parentNodes = workflow.getParentNodes(inputData.node, 'main', -1);
323
+ let nodeToAdd = inputData.node;
324
+ parentNodes.push(inputData.node);
325
+ parentNodes.reverse();
326
+ for (const parentNode of parentNodes) {
327
+ // Check if that node is also an output connection of the
328
+ // previously processed one
329
+ if (inputData.node !== parentNode && checkOutputNodes.includes(parentNode)) {
330
+ // So the parent node will be added anyway which
331
+ // will then process this node next. So nothing to do.
332
+ nodeToAdd = undefined;
333
+ break;
334
+ }
335
+ // Check if it is already in the execution stack
336
+ if (executionStackNodes.includes(parentNode)) {
337
+ // Node is already on the list to be executed
338
+ // so there is nothing to do
339
+ nodeToAdd = undefined;
340
+ break;
341
+ }
342
+ // Check if node got processed already
343
+ if (this.runExecutionData.resultData.runData[parentNode] !== undefined) {
344
+ // Node got processed already so we can use the
345
+ // output data as input of this node
346
+ break;
347
+ }
348
+ nodeToAdd = parentNode;
349
+ }
350
+ const parentNodesNodeToAdd = workflow.getParentNodes(nodeToAdd);
351
+ if (parentNodesNodeToAdd.includes(parentNodeName) &&
352
+ nodeSuccessData[outputIndex].length === 0) {
353
+ // We do not add the node if there is no input data and the node that should be connected
354
+ // is a child of the parent node. Because else it would run a node even though it should be
355
+ // specifically not run, as it did not receive any data.
356
+ nodeToAdd = undefined;
357
+ }
358
+ if (nodeToAdd === undefined) {
359
+ // No node has to get added so process
360
+ continue;
361
+ }
362
+ let addEmptyItem = false;
363
+ if (workflow.connectionsByDestinationNode[nodeToAdd] === undefined) {
364
+ // Add empty item if the node does not have any input connections
365
+ addEmptyItem = true;
366
+ }
367
+ else if (this.incomingConnectionIsEmpty(this.runExecutionData.resultData.runData, workflow.connectionsByDestinationNode[nodeToAdd]['main'][0], runIndex)) {
368
+ // Add empty item also if the input data is empty
369
+ addEmptyItem = true;
370
+ }
371
+ if (addEmptyItem) {
372
+ // Add only node if it does not have any inputs because else it will
373
+ // be added by its input node later anyway.
374
+ this.runExecutionData.executionData.nodeExecutionStack.push({
375
+ node: workflow.getNode(nodeToAdd),
376
+ data: {
377
+ main: [
378
+ [
379
+ {
380
+ json: {},
381
+ },
382
+ ],
383
+ ],
384
+ },
385
+ });
386
+ }
387
+ }
388
+ }
389
+ }
390
+ }
391
+ // Make sure the array has all the values
392
+ const connectionDataArray = [];
393
+ for (let i = connectionData.index; i >= 0; i--) {
394
+ connectionDataArray[i] = null;
395
+ }
396
+ // Add the data of the current execution
397
+ if (nodeSuccessData === null) {
398
+ connectionDataArray[connectionData.index] = null;
399
+ }
400
+ else {
401
+ connectionDataArray[connectionData.index] = nodeSuccessData[outputIndex];
402
+ }
403
+ if (stillDataMissing) {
404
+ // Additional data is needed to run node so add it to waiting
405
+ if (
406
+ // eslint-disable-next-line no-prototype-builtins
407
+ !this.runExecutionData.executionData.waitingExecution.hasOwnProperty(connectionData.node)) {
408
+ this.runExecutionData.executionData.waitingExecution[connectionData.node] = {};
409
+ }
410
+ this.runExecutionData.executionData.waitingExecution[connectionData.node][runIndex] = {
411
+ main: connectionDataArray,
412
+ };
413
+ }
414
+ else {
415
+ // All data is there so add it directly to stack
416
+ this.runExecutionData.executionData.nodeExecutionStack.push({
417
+ node: workflow.nodes[connectionData.node],
418
+ data: {
419
+ main: connectionDataArray,
420
+ },
421
+ });
422
+ }
423
+ }
424
+ /**
425
+ * Runs the given execution data.
426
+ *
427
+ * @param {Workflow} workflow
428
+ * @returns {Promise<string>}
429
+ * @memberof WorkflowExecute
430
+ */
431
+ // IMPORTANT: Do not add "async" to this function, it will then convert the
432
+ // PCancelable to a regular Promise and does so not allow canceling
433
+ // active executions anymore
434
+ // eslint-disable-next-line @typescript-eslint/promise-function-async
435
+ processRunExecutionData(workflow) {
436
+ workflow_1.LoggerProxy.verbose('Workflow execution started', { workflowId: workflow.id });
437
+ console.log(`Workflow execution started: ID: ${workflow.id} - Name: ${workflow.name} - Active: ${workflow.active ? 'active' : 'inactive'}`);
438
+ const startedAt = new Date();
439
+ const startNode = this.runExecutionData.executionData.nodeExecutionStack[0].node.name;
440
+ let destinationNode;
441
+ if (this.runExecutionData.startData && this.runExecutionData.startData.destinationNode) {
442
+ destinationNode = this.runExecutionData.startData.destinationNode;
443
+ }
444
+ const workflowIssues = workflow.checkReadyForExecution({ startNode, destinationNode });
445
+ if (workflowIssues !== null) {
446
+ throw new Error('The workflow has issues and can for that reason not be executed. Please fix them first.');
447
+ }
448
+ // Variables which hold temporary data for each node-execution
449
+ let executionData;
450
+ let executionError;
451
+ let executionNode;
452
+ let nodeSuccessData;
453
+ let runIndex;
454
+ let startTime;
455
+ let taskData;
456
+ if (this.runExecutionData.startData === undefined) {
457
+ this.runExecutionData.startData = {};
458
+ }
459
+ if (this.runExecutionData.waitTill) {
460
+ const lastNodeExecuted = this.runExecutionData.resultData.lastNodeExecuted;
461
+ this.runExecutionData.executionData.nodeExecutionStack[0].node.disabled = true;
462
+ this.runExecutionData.waitTill = undefined;
463
+ this.runExecutionData.resultData.runData[lastNodeExecuted].pop();
464
+ }
465
+ let currentExecutionTry = '';
466
+ let lastExecutionTry = '';
467
+ return new PCancelable((resolve, reject, onCancel) => {
468
+ let gotCancel = false;
469
+ onCancel.shouldReject = false;
470
+ onCancel(() => {
471
+ gotCancel = true;
472
+ });
473
+ const returnPromise = (() => tslib_1.__awaiter(this, void 0, void 0, function* () {
474
+ try {
475
+ yield this.executeHook('workflowExecuteBefore', [workflow]);
476
+ }
477
+ catch (error) {
478
+ // Set the error that it can be saved correctly
479
+ executionError = Object.assign(Object.assign({}, error), { message: error.message, stack: error.stack });
480
+ // Set the incoming data of the node that it can be saved correctly
481
+ // eslint-disable-next-line prefer-destructuring
482
+ executionData = this.runExecutionData.executionData.nodeExecutionStack[0];
483
+ this.runExecutionData.resultData = {
484
+ runData: {
485
+ [executionData.node.name]: [
486
+ {
487
+ startTime,
488
+ executionTime: new Date().getTime() - startTime,
489
+ data: {
490
+ main: executionData.data['main'],
491
+ },
492
+ },
493
+ ],
494
+ },
495
+ lastNodeExecuted: executionData.node.name,
496
+ error: executionError,
497
+ };
498
+ throw error;
499
+ }
500
+ executionLoop: while (this.runExecutionData.executionData.nodeExecutionStack.length !== 0) {
501
+ if (this.additionalData.executionTimeoutTimestamp !== undefined &&
502
+ Date.now() >= this.additionalData.executionTimeoutTimestamp) {
503
+ gotCancel = true;
504
+ }
505
+ if (gotCancel) {
506
+ return Promise.resolve();
507
+ }
508
+ nodeSuccessData = null;
509
+ executionError = undefined;
510
+ executionData =
511
+ this.runExecutionData.executionData.nodeExecutionStack.shift();
512
+ executionNode = executionData.node;
513
+ workflow_1.LoggerProxy.debug(`Start processing node "${executionNode.name}"`, {
514
+ node: executionNode.name,
515
+ workflowId: workflow.id,
516
+ });
517
+ yield this.executeHook('nodeExecuteBefore', [executionNode.name]);
518
+ // Get the index of the current run
519
+ runIndex = 0;
520
+ // eslint-disable-next-line no-prototype-builtins
521
+ if (this.runExecutionData.resultData.runData.hasOwnProperty(executionNode.name)) {
522
+ runIndex = this.runExecutionData.resultData.runData[executionNode.name].length;
523
+ }
524
+ currentExecutionTry = `${executionNode.name}:${runIndex}`;
525
+ if (currentExecutionTry === lastExecutionTry) {
526
+ throw new Error('Did stop execution because execution seems to be in endless loop.');
527
+ }
528
+ if (this.runExecutionData.startData.runNodeFilter !== undefined &&
529
+ this.runExecutionData.startData.runNodeFilter.indexOf(executionNode.name) === -1) {
530
+ // If filter is set and node is not on filter skip it, that avoids the problem that it executes
531
+ // leafs that are parallel to a selected destinationNode. Normally it would execute them because
532
+ // they have the same parent and it executes all child nodes.
533
+ continue;
534
+ }
535
+ // Check if all the data which is needed to run the node is available
536
+ if (Object.prototype.hasOwnProperty.call(workflow.connectionsByDestinationNode, executionNode.name)) {
537
+ // Check if the node has incoming connections
538
+ if (Object.prototype.hasOwnProperty.call(workflow.connectionsByDestinationNode[executionNode.name], 'main')) {
539
+ let inputConnections;
540
+ let connectionIndex;
541
+ // eslint-disable-next-line prefer-const
542
+ inputConnections = workflow.connectionsByDestinationNode[executionNode.name]['main'];
543
+ for (connectionIndex = 0; connectionIndex < inputConnections.length; connectionIndex++) {
544
+ if (workflow.getHighestNode(executionNode.name, 'main', connectionIndex).length === 0) {
545
+ // If there is no valid incoming node (if all are disabled)
546
+ // then ignore that it has inputs and simply execute it as it is without
547
+ // any data
548
+ continue;
549
+ }
550
+ if (!Object.prototype.hasOwnProperty.call(executionData.data, 'main')) {
551
+ // ExecutionData does not even have the connection set up so can
552
+ // not have that data, so add it again to be executed later
553
+ this.runExecutionData.executionData.nodeExecutionStack.push(executionData);
554
+ lastExecutionTry = currentExecutionTry;
555
+ continue executionLoop;
556
+ }
557
+ // Check if it has the data for all the inputs
558
+ // The most nodes just have one but merge node for example has two and data
559
+ // of both inputs has to be available to be able to process the node.
560
+ if (executionData.data['main'].length < connectionIndex ||
561
+ executionData.data['main'][connectionIndex] === null) {
562
+ // Does not have the data of the connections so add back to stack
563
+ this.runExecutionData.executionData.nodeExecutionStack.push(executionData);
564
+ lastExecutionTry = currentExecutionTry;
565
+ continue executionLoop;
566
+ }
567
+ }
568
+ }
569
+ }
570
+ // Clone input data that nodes can not mess up data of parallel nodes which receive the same data
571
+ // TODO: Should only clone if multiple nodes get the same data or when it gets returned to frontned
572
+ // is very slow so only do if needed
573
+ startTime = new Date().getTime();
574
+ let maxTries = 1;
575
+ if (executionData.node.retryOnFail === true) {
576
+ // TODO: Remove the hardcoded default-values here and also in NodeSettings.vue
577
+ maxTries = Math.min(5, Math.max(2, executionData.node.maxTries || 3));
578
+ }
579
+ let waitBetweenTries = 0;
580
+ if (executionData.node.retryOnFail === true) {
581
+ // TODO: Remove the hardcoded default-values here and also in NodeSettings.vue
582
+ waitBetweenTries = Math.min(5000, Math.max(0, executionData.node.waitBetweenTries || 1000));
583
+ }
584
+ for (let tryIndex = 0; tryIndex < maxTries; tryIndex++) {
585
+ if (gotCancel) {
586
+ return Promise.resolve();
587
+ }
588
+ try {
589
+ if (tryIndex !== 0) {
590
+ // Reset executionError from previous error try
591
+ executionError = undefined;
592
+ if (waitBetweenTries !== 0) {
593
+ // TODO: Improve that in the future and check if other nodes can
594
+ // be executed in the meantime
595
+ // eslint-disable-next-line @typescript-eslint/no-shadow
596
+ yield new Promise((resolve) => {
597
+ setTimeout(() => {
598
+ resolve(undefined);
599
+ }, waitBetweenTries);
600
+ });
601
+ }
602
+ }
603
+ workflow_1.LoggerProxy.debug(`Running node "${executionNode.name}" started`, {
604
+ node: executionNode.name,
605
+ workflowId: workflow.id,
606
+ });
607
+ nodeSuccessData = yield workflow.runNode(executionData.node, executionData.data, this.runExecutionData, runIndex, this.additionalData, src_1.NodeExecuteFunctions, this.mode);
608
+ workflow_1.LoggerProxy.debug(`Running node "${executionNode.name}" finished successfully`, {
609
+ node: executionNode.name,
610
+ workflowId: workflow.id,
611
+ });
612
+ if (nodeSuccessData === undefined) {
613
+ // Node did not get executed
614
+ nodeSuccessData = null;
615
+ }
616
+ else {
617
+ this.runExecutionData.resultData.lastNodeExecuted = executionData.node.name;
618
+ }
619
+ if (nodeSuccessData === null || nodeSuccessData[0][0] === undefined) {
620
+ if (executionData.node.alwaysOutputData === true) {
621
+ nodeSuccessData = nodeSuccessData || [];
622
+ nodeSuccessData[0] = [
623
+ {
624
+ json: {},
625
+ },
626
+ ];
627
+ }
628
+ }
629
+ if (nodeSuccessData === null && !this.runExecutionData.waitTill) {
630
+ // If null gets returned it means that the node did succeed
631
+ // but did not have any data. So the branch should end
632
+ // (meaning the nodes afterwards should not be processed)
633
+ continue executionLoop;
634
+ }
635
+ break;
636
+ }
637
+ catch (error) {
638
+ this.runExecutionData.resultData.lastNodeExecuted = executionData.node.name;
639
+ executionError = Object.assign(Object.assign({}, error), { message: error.message, stack: error.stack });
640
+ workflow_1.LoggerProxy.debug(`Running node "${executionNode.name}" finished with error`, {
641
+ node: executionNode.name,
642
+ workflowId: workflow.id,
643
+ });
644
+ }
645
+ }
646
+ // Add the data to return to the user
647
+ // (currently does not get cloned as data does not get changed, maybe later we should do that?!?!)
648
+ if (!Object.prototype.hasOwnProperty.call(this.runExecutionData.resultData.runData, executionNode.name)) {
649
+ this.runExecutionData.resultData.runData[executionNode.name] = [];
650
+ }
651
+ taskData = {
652
+ startTime,
653
+ executionTime: new Date().getTime() - startTime,
654
+ };
655
+ if (executionError !== undefined) {
656
+ taskData.error = executionError;
657
+ if (executionData.node.continueOnFail === true) {
658
+ // Workflow should continue running even if node errors
659
+ if (Object.prototype.hasOwnProperty.call(executionData.data, 'main') && executionData.data['main'].length > 0) {
660
+ // Simply get the input data of the node if it has any and pass it through
661
+ // to the next node
662
+ if (executionData.data['main'][0] !== null) {
663
+ nodeSuccessData = [executionData.data['main'][0]];
664
+ }
665
+ }
666
+ }
667
+ else {
668
+ // Node execution did fail so add error and stop execution
669
+ this.runExecutionData.resultData.runData[executionNode.name].push(taskData);
670
+ // Add the execution data again so that it can get restarted
671
+ this.runExecutionData.executionData.nodeExecutionStack.unshift(executionData);
672
+ yield this.executeHook('nodeExecuteAfter', [
673
+ executionNode.name,
674
+ taskData,
675
+ this.runExecutionData,
676
+ ]);
677
+ break;
678
+ }
679
+ }
680
+ // Merge error information to default output for now
681
+ // As the new nodes can report the errors in
682
+ // the `error` property.
683
+ for (const execution of nodeSuccessData) {
684
+ for (const lineResult of execution) {
685
+ if (lineResult.json !== undefined &&
686
+ lineResult.json['$error'] !== undefined &&
687
+ lineResult.json['$error'] !== undefined) {
688
+ lineResult.error = lineResult.json['$error'];
689
+ lineResult.json = {
690
+ error: lineResult.json['$error'].message,
691
+ };
692
+ }
693
+ else if (lineResult.error !== undefined) {
694
+ lineResult.json = { error: lineResult.error.message };
695
+ }
696
+ }
697
+ }
698
+ // Node executed successfully. So add data and go on.
699
+ taskData.data = {
700
+ main: nodeSuccessData,
701
+ };
702
+ this.runExecutionData.resultData.runData[executionNode.name].push(taskData);
703
+ if (this.runExecutionData.waitTill) {
704
+ yield this.executeHook('nodeExecuteAfter', [
705
+ executionNode.name,
706
+ taskData,
707
+ this.runExecutionData,
708
+ ]);
709
+ // Add the node back to the stack that the workflow can start to execute again from that node
710
+ this.runExecutionData.executionData.nodeExecutionStack.unshift(executionData);
711
+ break;
712
+ }
713
+ if (this.runExecutionData.startData &&
714
+ this.runExecutionData.startData.destinationNode &&
715
+ this.runExecutionData.startData.destinationNode === executionNode.name) {
716
+ // Before stopping, make sure we are executing hooks so
717
+ // That frontend is notified for example for manual executions.
718
+ yield this.executeHook('nodeExecuteAfter', [
719
+ executionNode.name,
720
+ taskData,
721
+ this.runExecutionData,
722
+ ]);
723
+ // If destination node is defined and got executed stop execution
724
+ continue;
725
+ }
726
+ // Add the nodes to which the current node has an output connection to that they can
727
+ // be executed next
728
+ if (Object.prototype.hasOwnProperty.call(workflow.connectionsBySourceNode, executionNode.name)) {
729
+ if (Object.prototype.hasOwnProperty.call(workflow.connectionsBySourceNode[executionNode.name], 'main')) {
730
+ let outputIndex;
731
+ let connectionData;
732
+ // Iterate over all the outputs
733
+ // Add the nodes to be executed
734
+ // eslint-disable-next-line @typescript-eslint/no-for-in-array
735
+ for (outputIndex in workflow.connectionsBySourceNode[executionNode.name]['main']) {
736
+ if (!Object.prototype.hasOwnProperty.call(workflow.connectionsBySourceNode[executionNode.name]['main'], outputIndex)) {
737
+ continue;
738
+ }
739
+ // Iterate over all the different connections of this output
740
+ for (connectionData of workflow.connectionsBySourceNode[executionNode.name]['main'][outputIndex]) {
741
+ if (!Object.prototype.hasOwnProperty.call(workflow.nodes, connectionData.node)) {
742
+ return Promise.reject(new Error(`The node "${executionNode.name}" connects to not found node "${connectionData.node}"`));
743
+ }
744
+ if (nodeSuccessData[outputIndex] &&
745
+ (nodeSuccessData[outputIndex].length !== 0 || connectionData.index > 0)) {
746
+ // Add the node only if it did execute or if connected to second "optional" input
747
+ this.addNodeToBeExecuted(workflow, connectionData, parseInt(outputIndex, 10), executionNode.name, nodeSuccessData, runIndex);
748
+ }
749
+ }
750
+ }
751
+ }
752
+ }
753
+ // If we got here, it means that we did not stop executing from manual executions / destination.
754
+ // Execute hooks now to make sure that all hooks are executed properly
755
+ // Await is needed to make sure that we don't fall into concurrency problems
756
+ // When saving node execution data
757
+ yield this.executeHook('nodeExecuteAfter', [
758
+ executionNode.name,
759
+ taskData,
760
+ this.runExecutionData,
761
+ ]);
762
+ }
763
+ return Promise.resolve();
764
+ }))()
765
+ .then(() => tslib_1.__awaiter(this, void 0, void 0, function* () {
766
+ if (gotCancel && executionError === undefined) {
767
+ return this.processSuccessExecution(startedAt, workflow, new workflow_1.WorkflowOperationError('Workflow has been canceled or timed out!'));
768
+ }
769
+ return this.processSuccessExecution(startedAt, workflow, executionError);
770
+ }))
771
+ .catch((error) => tslib_1.__awaiter(this, void 0, void 0, function* () {
772
+ const fullRunData = this.getFullRunData(startedAt);
773
+ fullRunData.data.resultData.error = Object.assign(Object.assign({}, error), { message: error.message, stack: error.stack });
774
+ // Check if static data changed
775
+ let newStaticData;
776
+ // eslint-disable-next-line no-underscore-dangle
777
+ if (workflow.staticData['__dataChanged'] === true) {
778
+ // Static data of workflow changed
779
+ newStaticData = workflow.staticData;
780
+ }
781
+ yield this.executeHook('workflowExecuteAfter', [fullRunData, newStaticData]).catch(
782
+ // eslint-disable-next-line @typescript-eslint/no-shadow
783
+ (error) => {
784
+ // eslint-disable-next-line no-console
785
+ console.error('There was a problem running hook "workflowExecuteAfter"', error);
786
+ });
787
+ return fullRunData;
788
+ }));
789
+ return returnPromise.then(resolve);
790
+ });
791
+ }
792
+ processSuccessExecution(startedAt, workflow, executionError) {
793
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
794
+ const fullRunData = this.getFullRunData(startedAt);
795
+ if (executionError !== undefined) {
796
+ workflow_1.LoggerProxy.verbose(`Workflow execution finished with error`, {
797
+ error: executionError,
798
+ workflowId: workflow.id,
799
+ });
800
+ fullRunData.data.resultData.error = Object.assign(Object.assign({}, executionError), { message: executionError.message, stack: executionError.stack });
801
+ }
802
+ else if (this.runExecutionData.waitTill) {
803
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
804
+ workflow_1.LoggerProxy.verbose(`Workflow execution will wait until ${this.runExecutionData.waitTill}`, {
805
+ workflowId: workflow.id,
806
+ });
807
+ fullRunData.waitTill = this.runExecutionData.waitTill;
808
+ }
809
+ else {
810
+ workflow_1.LoggerProxy.verbose(`Workflow execution finished successfully`, { workflowId: workflow.id });
811
+ fullRunData.finished = true;
812
+ }
813
+ // Check if static data changed
814
+ let newStaticData;
815
+ // eslint-disable-next-line no-underscore-dangle
816
+ if (workflow.staticData['__dataChanged'] === true) {
817
+ // Static data of workflow changed
818
+ newStaticData = workflow.staticData;
819
+ }
820
+ yield this.executeHook('workflowExecuteAfter', [fullRunData, newStaticData]);
821
+ return fullRunData;
822
+ });
823
+ }
824
+ getFullRunData(startedAt) {
825
+ const fullRunData = {
826
+ data: this.runExecutionData,
827
+ mode: this.mode,
828
+ startedAt,
829
+ stoppedAt: new Date(),
830
+ };
831
+ return fullRunData;
832
+ }
833
+ }
834
+ exports.WorkflowExecute = WorkflowExecute;
835
+ //# sourceMappingURL=WorkflowExecute.js.map