@fsai-flow/workflow 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/README.md +31 -0
  2. package/dist/package.json +42 -0
  3. package/dist/src/index.d.ts +21 -0
  4. package/dist/src/index.js +33 -0
  5. package/dist/src/index.js.map +1 -0
  6. package/dist/src/lib/Constants.d.ts +68 -0
  7. package/dist/src/lib/Constants.js +106 -0
  8. package/dist/src/lib/Constants.js.map +1 -0
  9. package/dist/src/lib/DeferredPromise.d.ts +6 -0
  10. package/dist/src/lib/DeferredPromise.js +11 -0
  11. package/dist/src/lib/DeferredPromise.js.map +1 -0
  12. package/dist/src/lib/Expression.d.ts +65 -0
  13. package/dist/src/lib/Expression.js +215 -0
  14. package/dist/src/lib/Expression.js.map +1 -0
  15. package/dist/src/lib/Interfaces.d.ts +1579 -0
  16. package/dist/src/lib/Interfaces.js +44 -0
  17. package/dist/src/lib/Interfaces.js.map +1 -0
  18. package/dist/src/lib/LoggerProxy.d.ts +9 -0
  19. package/dist/src/lib/LoggerProxy.js +40 -0
  20. package/dist/src/lib/LoggerProxy.js.map +1 -0
  21. package/dist/src/lib/MetadataUtils.d.ts +4 -0
  22. package/dist/src/lib/MetadataUtils.js +27 -0
  23. package/dist/src/lib/MetadataUtils.js.map +1 -0
  24. package/dist/src/lib/NodeErrors.d.ts +82 -0
  25. package/dist/src/lib/NodeErrors.js +289 -0
  26. package/dist/src/lib/NodeErrors.js.map +1 -0
  27. package/dist/src/lib/NodeHelpers.d.ts +198 -0
  28. package/dist/src/lib/NodeHelpers.js +1348 -0
  29. package/dist/src/lib/NodeHelpers.js.map +1 -0
  30. package/dist/src/lib/ObservableObject.d.ts +5 -0
  31. package/dist/src/lib/ObservableObject.js +61 -0
  32. package/dist/src/lib/ObservableObject.js.map +1 -0
  33. package/dist/src/lib/RoutingNode.d.ts +18 -0
  34. package/dist/src/lib/RoutingNode.js +508 -0
  35. package/dist/src/lib/RoutingNode.js.map +1 -0
  36. package/dist/src/lib/TelemetryHelpers.d.ts +3 -0
  37. package/dist/src/lib/TelemetryHelpers.js +69 -0
  38. package/dist/src/lib/TelemetryHelpers.js.map +1 -0
  39. package/dist/src/lib/TypeValidation.d.ts +21 -0
  40. package/dist/src/lib/TypeValidation.js +385 -0
  41. package/dist/src/lib/TypeValidation.js.map +1 -0
  42. package/dist/src/lib/VersionedNodeType.d.ts +9 -0
  43. package/dist/src/lib/VersionedNodeType.js +26 -0
  44. package/dist/src/lib/VersionedNodeType.js.map +1 -0
  45. package/dist/src/lib/Workflow.d.ts +248 -0
  46. package/dist/src/lib/Workflow.js +904 -0
  47. package/dist/src/lib/Workflow.js.map +1 -0
  48. package/dist/src/lib/WorkflowDataProxy.d.ts +87 -0
  49. package/dist/src/lib/WorkflowDataProxy.js +556 -0
  50. package/dist/src/lib/WorkflowDataProxy.js.map +1 -0
  51. package/dist/src/lib/WorkflowErrors.d.ts +9 -0
  52. package/dist/src/lib/WorkflowErrors.js +18 -0
  53. package/dist/src/lib/WorkflowErrors.js.map +1 -0
  54. package/dist/src/lib/WorkflowHooks.d.ts +11 -0
  55. package/dist/src/lib/WorkflowHooks.js +34 -0
  56. package/dist/src/lib/WorkflowHooks.js.map +1 -0
  57. package/dist/src/lib/errors/base/base.error.d.ts +30 -0
  58. package/dist/src/lib/errors/base/base.error.js +45 -0
  59. package/dist/src/lib/errors/base/base.error.js.map +1 -0
  60. package/dist/src/lib/errors/base/operational.error.d.ts +15 -0
  61. package/dist/src/lib/errors/base/operational.error.js +19 -0
  62. package/dist/src/lib/errors/base/operational.error.js.map +1 -0
  63. package/dist/src/lib/errors/error.types.d.ts +11 -0
  64. package/dist/src/lib/errors/error.types.js +3 -0
  65. package/dist/src/lib/errors/error.types.js.map +1 -0
  66. package/dist/src/lib/errors/index.d.ts +1 -0
  67. package/dist/src/lib/errors/index.js +6 -0
  68. package/dist/src/lib/errors/index.js.map +1 -0
  69. package/dist/src/lib/result.d.ts +19 -0
  70. package/dist/src/lib/result.js +36 -0
  71. package/dist/src/lib/result.js.map +1 -0
  72. package/dist/src/lib/utils.d.ts +50 -0
  73. package/dist/src/lib/utils.js +119 -0
  74. package/dist/src/lib/utils.js.map +1 -0
  75. package/package.json +5 -1
  76. package/src/lib/Interfaces.ts +28 -33
  77. package/src/lib/Workflow.ts +1 -1
  78. package/src/lib/WorkflowDataProxy.ts +1 -1
  79. package/src/lib/utils.ts +99 -120
@@ -0,0 +1,904 @@
1
+ "use strict";
2
+ /* eslint-disable no-await-in-loop */
3
+ /* eslint-disable @typescript-eslint/no-unsafe-call */
4
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
5
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
6
+ /* eslint-disable no-param-reassign */
7
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
8
+ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
9
+ /* eslint-disable @typescript-eslint/no-unsafe-return */
10
+ /* eslint-disable @typescript-eslint/no-for-in-array */
11
+ /* eslint-disable no-prototype-builtins */
12
+ /* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
13
+ /* eslint-disable no-underscore-dangle */
14
+ /* eslint-disable no-continue */
15
+ /* eslint-disable no-restricted-syntax */
16
+ /* eslint-disable import/no-cycle */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.Workflow = void 0;
19
+ const __1 = require("..");
20
+ class Workflow {
21
+ id;
22
+ name;
23
+ nodes = {};
24
+ connectionsBySourceNode;
25
+ connectionsByDestinationNode;
26
+ nodeTypes;
27
+ expression;
28
+ active;
29
+ settings;
30
+ // To save workflow specific static data like for example
31
+ // ids of registred webhooks of nodes
32
+ staticData;
33
+ // constructor(id: string | undefined, nodes: INode[], connections: IConnections, active: boolean, nodeTypes: INodeTypes, staticData?: IDataObject, settings?: IWorkflowSettings) {
34
+ constructor(parameters) {
35
+ this.id = parameters?.id;
36
+ this.name = parameters?.name;
37
+ this.nodeTypes = parameters.nodeTypes;
38
+ // Save nodes in workflow as object to be able to get the
39
+ // nodes easily by its name.
40
+ // Also directly add the default values of the node type.
41
+ let nodeType;
42
+ for (const node of parameters.nodes) {
43
+ this.nodes[node.name] = node;
44
+ nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
45
+ if (nodeType === undefined) {
46
+ // Go on to next node when its type is not known.
47
+ // For now do not error because that causes problems with
48
+ // expression resolution also then when the unknown node
49
+ // does not get used.
50
+ continue;
51
+ // throw new Error(`The node type "${node.type}" of node "${node.name}" is not known.`);
52
+ }
53
+ // Add default values
54
+ const nodeParameters = __1.NodeHelpers.getNodeParameters(nodeType.description.properties, node.parameters, true, false);
55
+ node.parameters = nodeParameters !== null ? nodeParameters : {};
56
+ }
57
+ this.connectionsBySourceNode = parameters.connections;
58
+ // Save also the connections by the destionation nodes
59
+ this.connectionsByDestinationNode = this.__getConnectionsByDestination(parameters.connections);
60
+ this.active = parameters.active || false;
61
+ this.staticData = __1.ObservableObject.create(parameters.staticData || {}, undefined, {
62
+ ignoreEmptyOnFirstChild: true,
63
+ });
64
+ this.settings = parameters.settings || {};
65
+ this.expression = new __1.Expression(this);
66
+ }
67
+ /**
68
+ * The default connections are by source node. This function rewrites them by destination nodes
69
+ * to easily find parent nodes.
70
+ *
71
+ * @param {IConnections} connections
72
+ * @returns {IConnections}
73
+ * @memberof Workflow
74
+ */
75
+ __getConnectionsByDestination(connections) {
76
+ const returnConnection = {};
77
+ let connectionInfo;
78
+ let maxIndex;
79
+ for (const sourceNode in connections) {
80
+ if (!connections.hasOwnProperty(sourceNode)) {
81
+ continue;
82
+ }
83
+ for (const type in connections[sourceNode]) {
84
+ if (!connections[sourceNode].hasOwnProperty(type)) {
85
+ continue;
86
+ }
87
+ for (const inputIndex in connections[sourceNode][type]) {
88
+ if (!connections[sourceNode][type].hasOwnProperty(inputIndex)) {
89
+ continue;
90
+ }
91
+ for (connectionInfo of connections[sourceNode][type][inputIndex]) {
92
+ if (connectionInfo.node === '__proto__' || connectionInfo.node === 'constructor' || connectionInfo.node === 'prototype') {
93
+ throw new Error('Prototype-polluting assignment detected');
94
+ }
95
+ if (!returnConnection.hasOwnProperty(connectionInfo.node)) {
96
+ returnConnection[connectionInfo.node] = {};
97
+ }
98
+ if (!returnConnection[connectionInfo.node].hasOwnProperty(connectionInfo.type)) {
99
+ returnConnection[connectionInfo.node][connectionInfo.type] = [];
100
+ }
101
+ maxIndex = returnConnection[connectionInfo.node][connectionInfo.type].length - 1;
102
+ for (let j = maxIndex; j < connectionInfo.index; j++) {
103
+ returnConnection[connectionInfo.node][connectionInfo.type].push([]);
104
+ }
105
+ returnConnection[connectionInfo.node][connectionInfo.type][connectionInfo.index].push({
106
+ node: sourceNode,
107
+ type,
108
+ index: parseInt(inputIndex, 10),
109
+ });
110
+ }
111
+ }
112
+ }
113
+ }
114
+ return returnConnection;
115
+ }
116
+ /**
117
+ * A workflow can only be activated if it has a node which has either triggers
118
+ * or webhooks defined.
119
+ *
120
+ * @param {string[]} [ignoreNodeTypes] Node-types to ignore in the check
121
+ * @returns {boolean}
122
+ * @memberof Workflow
123
+ */
124
+ checkIfWorkflowCanBeActivated(ignoreNodeTypes) {
125
+ let node;
126
+ let nodeType;
127
+ for (const nodeName of Object.keys(this.nodes)) {
128
+ node = this.nodes[nodeName];
129
+ if (node.disabled === true) {
130
+ // Deactivated nodes can not trigger a run so ignore
131
+ continue;
132
+ }
133
+ // eslint-disable-next-line @typescript-eslint/prefer-optional-chain
134
+ if (ignoreNodeTypes !== undefined && ignoreNodeTypes.includes(node.type)) {
135
+ continue;
136
+ }
137
+ nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
138
+ if (nodeType === undefined) {
139
+ // Type is not known so check is not possible
140
+ continue;
141
+ }
142
+ if (nodeType.poll !== undefined ||
143
+ nodeType.trigger !== undefined ||
144
+ nodeType.webhook !== undefined) {
145
+ // Is a trigger node. So workflow can be activated.
146
+ return true;
147
+ }
148
+ }
149
+ return false;
150
+ }
151
+ /**
152
+ * Checks if everything in the workflow is complete
153
+ * and ready to be executed. If it returns null everything
154
+ * is fine. If there are issues it returns the issues
155
+ * which have been found for the different nodes.
156
+ * TODO: Does currently not check for credential issues!
157
+ *
158
+ * @returns {(IWorfklowIssues | null)}
159
+ * @memberof Workflow
160
+ */
161
+ checkReadyForExecution(inputData) {
162
+ let node;
163
+ let nodeType;
164
+ let nodeIssues = null;
165
+ const workflowIssues = {};
166
+ let checkNodes = [];
167
+ if (inputData.destinationNode) {
168
+ // If a destination node is given we have to check all the nodes
169
+ // leading up to it
170
+ checkNodes = this.getParentNodes(inputData.destinationNode);
171
+ checkNodes.push(inputData.destinationNode);
172
+ }
173
+ else if (inputData.startNode) {
174
+ // If a start node is given we have to check all nodes which
175
+ // come after it
176
+ checkNodes = this.getChildNodes(inputData.startNode);
177
+ checkNodes.push(inputData.startNode);
178
+ }
179
+ for (const nodeName of checkNodes) {
180
+ nodeIssues = null;
181
+ node = this.nodes[nodeName];
182
+ if (node.disabled === true) {
183
+ continue;
184
+ }
185
+ nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
186
+ if (nodeType === undefined) {
187
+ // Node type is not known
188
+ nodeIssues = {
189
+ typeUnknown: true,
190
+ };
191
+ }
192
+ else {
193
+ nodeIssues = __1.NodeHelpers.getNodeParametersIssues(nodeType.description.properties, node);
194
+ }
195
+ if (nodeIssues !== null) {
196
+ workflowIssues[node.name] = nodeIssues;
197
+ }
198
+ }
199
+ if (Object.keys(workflowIssues).length === 0) {
200
+ return null;
201
+ }
202
+ return workflowIssues;
203
+ }
204
+ /**
205
+ * Returns the static data of the workflow.
206
+ * It gets saved with the workflow and will be the same for
207
+ * all workflow-executions.
208
+ *
209
+ * @param {string} type The type of data to return ("global"|"node")
210
+ * @param {INode} [node] If type is set to "node" then the node has to be provided
211
+ * @returns {IDataObject}
212
+ * @memberof Workflow
213
+ */
214
+ getStaticData(type, node) {
215
+ let key;
216
+ if (type === 'global') {
217
+ key = 'global';
218
+ }
219
+ else if (type === 'node') {
220
+ if (node === undefined) {
221
+ throw new Error(`The request data of context type "node" the node parameter has to be set!`);
222
+ }
223
+ key = `node:${node.name}`;
224
+ }
225
+ else {
226
+ throw new Error(`The context type "${type}" is not know. Only "global" and node" are supported!`);
227
+ }
228
+ if (this.staticData[key] === undefined) {
229
+ // Create it as ObservableObject that we can easily check if the data changed
230
+ // to know if the workflow with its data has to be saved afterwards or not.
231
+ this.staticData[key] = __1.ObservableObject.create({}, this.staticData);
232
+ }
233
+ return this.staticData[key];
234
+ }
235
+ /**
236
+ * Returns all the trigger nodes in the workflow.
237
+ *
238
+ * @returns {INode[]}
239
+ * @memberof Workflow
240
+ */
241
+ getTriggerNodes() {
242
+ return this.queryNodes((nodeType) => !!nodeType.trigger);
243
+ }
244
+ /**
245
+ * Returns all the poll nodes in the workflow
246
+ *
247
+ * @returns {INode[]}
248
+ * @memberof Workflow
249
+ */
250
+ getPollNodes() {
251
+ return this.queryNodes((nodeType) => !!nodeType.poll);
252
+ }
253
+ /**
254
+ * Returns all the nodes in the workflow for which the given
255
+ * checkFunction return true
256
+ *
257
+ * @param {(nodeType: INodeType) => boolean} checkFunction
258
+ * @returns {INode[]}
259
+ * @memberof Workflow
260
+ */
261
+ queryNodes(checkFunction) {
262
+ const returnNodes = [];
263
+ // Check if it has any of them
264
+ let node;
265
+ let nodeType;
266
+ for (const nodeName of Object.keys(this.nodes)) {
267
+ node = this.nodes[nodeName];
268
+ if (node.disabled === true) {
269
+ continue;
270
+ }
271
+ nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
272
+ if (nodeType !== undefined && checkFunction(nodeType)) {
273
+ returnNodes.push(node);
274
+ }
275
+ }
276
+ return returnNodes;
277
+ }
278
+ /**
279
+ * Returns the node with the given name if it exists else null
280
+ *
281
+ * @param {string} nodeName Name of the node to return
282
+ * @returns {(INode | null)}
283
+ * @memberof Workflow
284
+ */
285
+ getNode(nodeName) {
286
+ if (this.nodes.hasOwnProperty(nodeName)) {
287
+ return this.nodes[nodeName];
288
+ }
289
+ return null;
290
+ }
291
+ /**
292
+ * Renames nodes in expressions
293
+ *
294
+ * @param {(NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[])} parameterValue The parameters to check for expressions
295
+ * @param {string} currentName The current name of the node
296
+ * @param {string} newName The new name
297
+ * @returns {(NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[])}
298
+ * @memberof Workflow
299
+ */
300
+ renameNodeInExpressions(parameterValue, currentName, newName) {
301
+ if (typeof parameterValue !== 'object') {
302
+ // Reached the actual value
303
+ if (typeof parameterValue === 'string' && parameterValue.charAt(0) === '=') {
304
+ // Is expression so has to be rewritten
305
+ // To not run the "expensive" regex stuff when it is not needed
306
+ // make a simple check first if it really contains the the node-name
307
+ if (parameterValue.includes(currentName)) {
308
+ // Really contains node-name (even though we do not know yet if really as $node-expression)
309
+ // In case some special characters are used in name escape them
310
+ const currentNameEscaped = currentName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
311
+ parameterValue = parameterValue.replace(new RegExp(`(\\$node(\\.|\\["|\\['))${currentNameEscaped}((\\.|"\\]|'\\]))`, 'g'), `$1${newName}$3`);
312
+ }
313
+ }
314
+ return parameterValue;
315
+ }
316
+ if (Array.isArray(parameterValue)) {
317
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
318
+ const returnArray = [];
319
+ for (const currentValue of parameterValue) {
320
+ returnArray.push(this.renameNodeInExpressions(currentValue, currentName, newName));
321
+ }
322
+ return returnArray;
323
+ }
324
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
325
+ const returnData = {};
326
+ for (const parameterName of Object.keys(parameterValue || {})) {
327
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
328
+ returnData[parameterName] = this.renameNodeInExpressions(parameterValue[parameterName], currentName, newName);
329
+ }
330
+ return returnData;
331
+ }
332
+ /**
333
+ * Rename a node in the workflow
334
+ *
335
+ * @param {string} currentName The current name of the node
336
+ * @param {string} newName The new name
337
+ * @memberof Workflow
338
+ */
339
+ renameNode(currentName, newName) {
340
+ // Rename the node itself
341
+ if (this.nodes[currentName] !== undefined) {
342
+ this.nodes[newName] = this.nodes[currentName];
343
+ this.nodes[newName].name = newName;
344
+ delete this.nodes[currentName];
345
+ }
346
+ // Update the expressions which reference the node
347
+ // with its old name
348
+ for (const node of Object.values(this.nodes)) {
349
+ node.parameters = this.renameNodeInExpressions(node.parameters, currentName, newName);
350
+ }
351
+ // Change all source connections
352
+ if (this.connectionsBySourceNode.hasOwnProperty(currentName)) {
353
+ this.connectionsBySourceNode[newName] = this.connectionsBySourceNode[currentName];
354
+ delete this.connectionsBySourceNode[currentName];
355
+ }
356
+ // Change all destination connections
357
+ let sourceNode;
358
+ let type;
359
+ let sourceIndex;
360
+ let connectionIndex;
361
+ let connectionData;
362
+ for (sourceNode of Object.keys(this.connectionsBySourceNode)) {
363
+ for (type of Object.keys(this.connectionsBySourceNode[sourceNode])) {
364
+ for (sourceIndex of Object.keys(this.connectionsBySourceNode[sourceNode][type])) {
365
+ for (connectionIndex of Object.keys(this.connectionsBySourceNode[sourceNode][type][parseInt(sourceIndex, 10)])) {
366
+ connectionData =
367
+ this.connectionsBySourceNode[sourceNode][type][parseInt(sourceIndex, 10)][parseInt(connectionIndex, 10)];
368
+ if (connectionData.node === currentName) {
369
+ connectionData.node = newName;
370
+ }
371
+ }
372
+ }
373
+ }
374
+ }
375
+ // Use the updated connections to create updated connections by destionation nodes
376
+ this.connectionsByDestinationNode = this.__getConnectionsByDestination(this.connectionsBySourceNode);
377
+ }
378
+ /**
379
+ * Finds the highest parent nodes of the node with the given name
380
+ *
381
+ * @param {string} nodeName
382
+ * @param {string} [type='main']
383
+ * @param {number} [nodeConnectionIndex]
384
+ * @returns {string[]}
385
+ * @memberof Workflow
386
+ */
387
+ getHighestNode(nodeName, type = 'main', nodeConnectionIndex, checkedNodes) {
388
+ const currentHighest = [];
389
+ if (this.nodes[nodeName].disabled === false) {
390
+ // If the current node is not disabled itself is the highest
391
+ currentHighest.push(nodeName);
392
+ }
393
+ if (!this.connectionsByDestinationNode.hasOwnProperty(nodeName)) {
394
+ // Node does not have incoming connections
395
+ return currentHighest;
396
+ }
397
+ if (!this.connectionsByDestinationNode[nodeName].hasOwnProperty(type)) {
398
+ // Node does not have incoming connections of given type
399
+ return currentHighest;
400
+ }
401
+ checkedNodes = checkedNodes || [];
402
+ if (checkedNodes.includes(nodeName)) {
403
+ // Node got checked already before
404
+ return currentHighest;
405
+ }
406
+ checkedNodes.push(nodeName);
407
+ const returnNodes = [];
408
+ let addNodes;
409
+ let connectionsByIndex;
410
+ for (let connectionIndex = 0; connectionIndex < this.connectionsByDestinationNode[nodeName][type].length; connectionIndex++) {
411
+ if (nodeConnectionIndex !== undefined && nodeConnectionIndex !== connectionIndex) {
412
+ // If a connection-index is given ignore all other ones
413
+ continue;
414
+ }
415
+ connectionsByIndex = this.connectionsByDestinationNode[nodeName][type][connectionIndex];
416
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
417
+ connectionsByIndex.forEach((connection) => {
418
+ if (checkedNodes.includes(connection.node)) {
419
+ // Node got checked already before
420
+ return;
421
+ }
422
+ addNodes = this.getHighestNode(connection.node, type, undefined, checkedNodes);
423
+ if (addNodes.length === 0) {
424
+ // The checked node does not have any further parents so add it
425
+ // if it is not disabled
426
+ if (this.nodes[connection.node].disabled !== true) {
427
+ addNodes = [connection.node];
428
+ }
429
+ }
430
+ addNodes.forEach((name) => {
431
+ // Only add if node is not on the list already anyway
432
+ if (returnNodes.indexOf(name) === -1) {
433
+ returnNodes.push(name);
434
+ }
435
+ });
436
+ });
437
+ }
438
+ return returnNodes;
439
+ }
440
+ /**
441
+ * Returns all the after the given one
442
+ *
443
+ * @param {string} nodeName
444
+ * @param {string} [type='main']
445
+ * @param {*} [depth=-1]
446
+ * @returns {string[]}
447
+ * @memberof Workflow
448
+ */
449
+ getChildNodes(nodeName, type = 'main', depth = -1) {
450
+ return this.getConnectedNodes(this.connectionsBySourceNode, nodeName, type, depth);
451
+ }
452
+ /**
453
+ * Returns all the nodes before the given one
454
+ *
455
+ * @param {string} nodeName
456
+ * @param {string} [type='main']
457
+ * @param {*} [depth=-1]
458
+ * @returns {string[]}
459
+ * @memberof Workflow
460
+ */
461
+ getParentNodes(nodeName, type = 'main', depth = -1) {
462
+ return this.getConnectedNodes(this.connectionsByDestinationNode, nodeName, type, depth);
463
+ }
464
+ /**
465
+ * Gets all the nodes which are connected nodes starting from
466
+ * the given one
467
+ *
468
+ * @param {IConnections} connections
469
+ * @param {string} nodeName
470
+ * @param {string} [type='main']
471
+ * @param {*} [depth=-1]
472
+ * @param {string[]} [checkedNodes]
473
+ * @returns {string[]}
474
+ * @memberof Workflow
475
+ */
476
+ getConnectedNodes(connections, nodeName, type = 'main', depth = -1, checkedNodes) {
477
+ depth = depth === -1 ? -1 : depth;
478
+ const newDepth = depth === -1 ? depth : depth - 1;
479
+ if (depth === 0) {
480
+ // Reached max depth
481
+ return [];
482
+ }
483
+ if (!connections.hasOwnProperty(nodeName)) {
484
+ // Node does not have incoming connections
485
+ return [];
486
+ }
487
+ if (!connections[nodeName].hasOwnProperty(type)) {
488
+ // Node does not have incoming connections of given type
489
+ return [];
490
+ }
491
+ checkedNodes = checkedNodes || [];
492
+ if (checkedNodes.includes(nodeName)) {
493
+ // Node got checked already before
494
+ return [];
495
+ }
496
+ checkedNodes.push(nodeName);
497
+ const returnNodes = [];
498
+ let addNodes;
499
+ let nodeIndex;
500
+ let i;
501
+ let parentNodeName;
502
+ connections[nodeName][type].forEach((connectionsByIndex) => {
503
+ connectionsByIndex.forEach((connection) => {
504
+ if (checkedNodes.includes(connection.node)) {
505
+ // Node got checked already before
506
+ return;
507
+ }
508
+ returnNodes.unshift(connection.node);
509
+ addNodes = this.getConnectedNodes(connections, connection.node, type, newDepth, checkedNodes);
510
+ for (i = addNodes.length; i--; i > 0) {
511
+ // Because nodes can have multiple parents it is possible that
512
+ // parts of the tree is parent of both and to not add nodes
513
+ // twice check first if they already got added before.
514
+ parentNodeName = addNodes[i];
515
+ nodeIndex = returnNodes.indexOf(parentNodeName);
516
+ if (nodeIndex !== -1) {
517
+ // Node got found before so remove it from current location
518
+ // that node-order stays correct
519
+ returnNodes.splice(nodeIndex, 1);
520
+ }
521
+ returnNodes.unshift(parentNodeName);
522
+ }
523
+ });
524
+ });
525
+ return returnNodes;
526
+ }
527
+ /**
528
+ * Returns via which output of the parent-node the node
529
+ * is connected to.
530
+ *
531
+ * @param {string} nodeName The node to check how it is connected with parent node
532
+ * @param {string} parentNodeName The parent node to get the output index of
533
+ * @param {string} [type='main']
534
+ * @param {*} [depth=-1]
535
+ * @param {string[]} [checkedNodes]
536
+ * @returns {(number | undefined)}
537
+ * @memberof Workflow
538
+ */
539
+ getNodeConnectionOutputIndex(nodeName, parentNodeName, type = 'main', depth = -1, checkedNodes) {
540
+ const node = this.getNode(parentNodeName);
541
+ if (node === null) {
542
+ return undefined;
543
+ }
544
+ const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
545
+ if (nodeType.description.outputs.length === 1) {
546
+ // If the parent node has only one output, it can only be connected
547
+ // to that one. So no further checking is required.
548
+ return 0;
549
+ }
550
+ depth = depth === -1 ? -1 : depth;
551
+ const newDepth = depth === -1 ? depth : depth - 1;
552
+ if (depth === 0) {
553
+ // Reached max depth
554
+ return undefined;
555
+ }
556
+ if (!this.connectionsByDestinationNode.hasOwnProperty(nodeName)) {
557
+ // Node does not have incoming connections
558
+ return undefined;
559
+ }
560
+ if (!this.connectionsByDestinationNode[nodeName].hasOwnProperty(type)) {
561
+ // Node does not have incoming connections of given type
562
+ return undefined;
563
+ }
564
+ checkedNodes = checkedNodes || [];
565
+ if (checkedNodes.includes(nodeName)) {
566
+ // Node got checked already before
567
+ return undefined;
568
+ }
569
+ checkedNodes.push(nodeName);
570
+ let outputIndex;
571
+ for (const connectionsByIndex of this.connectionsByDestinationNode[nodeName][type]) {
572
+ for (const connection of connectionsByIndex) {
573
+ if (parentNodeName === connection.node) {
574
+ return connection.index;
575
+ }
576
+ if (checkedNodes.includes(connection.node)) {
577
+ // Node got checked already before so continue with the next one
578
+ continue;
579
+ }
580
+ outputIndex = this.getNodeConnectionOutputIndex(connection.node, parentNodeName, type, newDepth, checkedNodes);
581
+ if (outputIndex !== undefined) {
582
+ return outputIndex;
583
+ }
584
+ }
585
+ }
586
+ return undefined;
587
+ }
588
+ /**
589
+ * Returns from which of the given nodes the workflow should get started from
590
+ *
591
+ * @param {string[]} nodeNames The potential start nodes
592
+ * @returns {(INode | undefined)}
593
+ * @memberof Workflow
594
+ */
595
+ __getStartNode(nodeNames) {
596
+ // Check if there are any trigger or poll nodes and then return the first one
597
+ let node;
598
+ let nodeType;
599
+ for (const nodeName of nodeNames) {
600
+ node = this.nodes[nodeName];
601
+ nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
602
+ if (nodeType.trigger !== undefined || nodeType.poll !== undefined) {
603
+ if (node.disabled === true) {
604
+ continue;
605
+ }
606
+ return node;
607
+ }
608
+ }
609
+ // Check if there is the actual "start" node
610
+ const startNodeType = 'n8n-nodes-base.start';
611
+ for (const nodeName of nodeNames) {
612
+ node = this.nodes[nodeName];
613
+ if (node.type === startNodeType) {
614
+ return node;
615
+ }
616
+ }
617
+ return undefined;
618
+ }
619
+ /**
620
+ * Returns the start node to start the worfklow from
621
+ *
622
+ * @param {string} [destinationNode]
623
+ * @returns {(INode | undefined)}
624
+ * @memberof Workflow
625
+ */
626
+ getStartNode(destinationNode) {
627
+ if (destinationNode) {
628
+ // Find the highest parent nodes of the given one
629
+ const nodeNames = this.getHighestNode(destinationNode);
630
+ if (nodeNames.length === 0) {
631
+ // If no parent nodes have been found then only the destination-node
632
+ // is in the tree so add that one
633
+ nodeNames.push(destinationNode);
634
+ }
635
+ // Check which node to return as start node
636
+ const node = this.__getStartNode(nodeNames);
637
+ if (node !== undefined) {
638
+ return node;
639
+ }
640
+ // If none of the above did find anything simply return the
641
+ // first parent node in the list
642
+ return this.nodes[nodeNames[0]];
643
+ }
644
+ return this.__getStartNode(Object.keys(this.nodes));
645
+ }
646
+ /**
647
+ * Executes the Webhooks method of the node
648
+ *
649
+ * @param {WebhookSetupMethodNames} method The name of the method to execute
650
+ * @param {IWebhookData} webhookData
651
+ * @param {INodeExecuteFunctions} nodeExecuteFunctions
652
+ * @param {WorkflowExecuteMode} mode
653
+ * @returns {(Promise<boolean | undefined>)}
654
+ * @memberof Workflow
655
+ */
656
+ async runWebhookMethod(method, webhookData, nodeExecuteFunctions, mode, activation, isTest) {
657
+ const node = this.getNode(webhookData.node);
658
+ const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
659
+ if (nodeType.webhookMethods === undefined) {
660
+ return;
661
+ }
662
+ if (nodeType.webhookMethods[webhookData.webhookDescription.name] === undefined) {
663
+ return;
664
+ }
665
+ if (nodeType.webhookMethods[webhookData.webhookDescription.name][method] === undefined) {
666
+ return;
667
+ }
668
+ const thisArgs = nodeExecuteFunctions.getExecuteHookFunctions(this, node, webhookData.workflowExecuteAdditionalData, mode, activation, isTest, webhookData);
669
+ // eslint-disable-next-line consistent-return
670
+ return nodeType.webhookMethods[webhookData.webhookDescription.name][method].call(thisArgs);
671
+ }
672
+ /**
673
+ * Runs the given trigger node so that it can trigger the workflow
674
+ * when the node has data.
675
+ *
676
+ * @param {INode} node
677
+ * @param {IGetExecuteTriggerFunctions} getTriggerFunctions
678
+ * @param {IWorkflowExecuteAdditionalData} additionalData
679
+ * @param {WorkflowExecuteMode} mode
680
+ * @returns {(Promise<ITriggerResponse | undefined>)}
681
+ * @memberof Workflow
682
+ */
683
+ async runTrigger(node, getTriggerFunctions, additionalData, mode, activation) {
684
+ const triggerFunctions = getTriggerFunctions(this, node, additionalData, mode, activation);
685
+ const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
686
+ if (nodeType === undefined) {
687
+ throw new Error(`The node type "${node.type}" of node "${node.name}" is not known.`);
688
+ }
689
+ if (!nodeType.trigger) {
690
+ throw new Error(`The node type "${node.type}" of node "${node.name}" does not have a trigger function defined.`);
691
+ }
692
+ if (mode === 'manual') {
693
+ // In manual mode we do not just start the trigger function we also
694
+ // want to be able to get informed as soon as the first data got emitted
695
+ const triggerResponse = await nodeType.trigger.call(triggerFunctions);
696
+ // Add the manual trigger response which resolves when the first time data got emitted
697
+ triggerResponse.manualTriggerResponse = new Promise((resolve) => {
698
+ triggerFunctions.emit = ((resolveEmit) => (data, responsePromise) => {
699
+ additionalData.hooks.hookFunctions.sendResponse = [
700
+ async (response) => {
701
+ if (responsePromise) {
702
+ responsePromise.resolve(response);
703
+ }
704
+ },
705
+ ];
706
+ resolveEmit(data);
707
+ })(resolve);
708
+ });
709
+ return triggerResponse;
710
+ }
711
+ // In all other modes simply start the trigger
712
+ return nodeType.trigger.call(triggerFunctions);
713
+ }
714
+ /**
715
+ * Runs the given trigger node so that it can trigger the workflow
716
+ * when the node has data.
717
+ *
718
+ * @param {INode} node
719
+ * @param {IPollFunctions} pollFunctions
720
+ * @returns
721
+ * @memberof Workflow
722
+ */
723
+ async runPoll(node, pollFunctions) {
724
+ const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
725
+ if (nodeType === undefined) {
726
+ throw new Error(`The node type "${node.type}" of node "${node.name}" is not known.`);
727
+ }
728
+ if (!nodeType.poll) {
729
+ throw new Error(`The node type "${node.type}" of node "${node.name}" does not have a poll function defined.`);
730
+ }
731
+ return nodeType.poll.call(pollFunctions);
732
+ }
733
+ /**
734
+ * Executes the webhook data to see what it should return and if the
735
+ * workflow should be started or not
736
+ *
737
+ * @param {INode} node
738
+ * @param {IWorkflowExecuteAdditionalData} additionalData
739
+ * @param {INodeExecuteFunctions} nodeExecuteFunctions
740
+ * @param {WorkflowExecuteMode} mode
741
+ * @param {IRunExecutionData | undefined} runExecutionData
742
+ * @returns {Promise<IWebhookResponseData>}
743
+ * @memberof Workflow
744
+ */
745
+ async runWebhook(webhookData, node, additionalData, nodeExecuteFunctions, mode, runExecutionData, workflow) {
746
+ const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
747
+ if (nodeType === undefined) {
748
+ throw new Error(`The type of the webhook node "${node.name}" is not known.`);
749
+ }
750
+ else if (nodeType.webhook === undefined) {
751
+ throw new Error(`The node "${node.name}" does not have any webhooks defined.`);
752
+ }
753
+ const thisArgs = nodeExecuteFunctions.getExecuteWebhookFunctions(this, node, additionalData, mode, webhookData, runExecutionData);
754
+ return nodeType.webhook.call(thisArgs, workflow);
755
+ }
756
+ /**
757
+ * Executes the given node.
758
+ *
759
+ * @param {INode} node
760
+ * @param {ITaskDataConnections} inputData
761
+ * @param {IRunExecutionData} runExecutionData
762
+ * @param {number} runIndex
763
+ * @param {IWorkflowExecuteAdditionalData} additionalData
764
+ * @param {INodeExecuteFunctions} nodeExecuteFunctions
765
+ * @param {WorkflowExecuteMode} mode
766
+ * @returns {(Promise<INodeExecutionData[][] | null>)}
767
+ * @memberof Workflow
768
+ */
769
+ async runNode(node, inputData, runExecutionData, runIndex, additionalData, nodeExecuteFunctions, mode) {
770
+ if (node.disabled === true) {
771
+ // If node is disabled simply pass the data through
772
+ // return NodeRunHelpers.
773
+ if (inputData.hasOwnProperty('main') && inputData['main'].length > 0) {
774
+ // If the node is disabled simply return the data from the first main input
775
+ if (inputData['main'][0] === null) {
776
+ return undefined;
777
+ }
778
+ return [inputData['main'][0]];
779
+ }
780
+ return undefined;
781
+ }
782
+ const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
783
+ if (nodeType === undefined) {
784
+ throw new Error(`Node type "${node.type}" is not known so can not run it!`);
785
+ }
786
+ let connectionInputData = [];
787
+ if (nodeType.execute ||
788
+ nodeType.executeSingle ||
789
+ (!nodeType.poll && !nodeType.trigger && !nodeType.webhook)) {
790
+ // Only stop if first input is empty for execute & executeSingle runs. For all others run anyways
791
+ // because then it is a trigger node. As they only pass data through and so the input-data
792
+ // becomes output-data it has to be possible.
793
+ if (inputData.hasOwnProperty('main') && inputData['main'].length > 0) {
794
+ // We always use the data of main input and the first input for executeSingle
795
+ connectionInputData = inputData['main'][0];
796
+ }
797
+ if (connectionInputData.length === 0) {
798
+ // No data for node so return
799
+ return undefined;
800
+ }
801
+ }
802
+ if (runExecutionData.resultData.lastNodeExecuted === node.name &&
803
+ runExecutionData.resultData.error !== undefined) {
804
+ // The node did already fail. So throw an error here that it displays and logs it correctly.
805
+ // Does get used by webhook and trigger nodes in case they throw an error that it is possible
806
+ // to log the error and display in Editor-UI.
807
+ const error = new Error(runExecutionData.resultData.error.message);
808
+ error.stack = runExecutionData.resultData.error.stack;
809
+ throw error;
810
+ }
811
+ if (node.executeOnce === true) {
812
+ // If node should be executed only once so use only the first input item
813
+ connectionInputData = connectionInputData.slice(0, 1);
814
+ const newInputData = {};
815
+ for (const inputName of Object.keys(inputData)) {
816
+ newInputData[inputName] = inputData[inputName].map((input) => {
817
+ // eslint-disable-next-line @typescript-eslint/prefer-optional-chain
818
+ return input && input.slice(0, 1);
819
+ });
820
+ }
821
+ inputData = newInputData;
822
+ }
823
+ if (nodeType.executeSingle) {
824
+ const returnPromises = [];
825
+ for (let itemIndex = 0; itemIndex < connectionInputData.length; itemIndex++) {
826
+ const thisArgs = nodeExecuteFunctions.getExecuteSingleFunctions(this, runExecutionData, runIndex, connectionInputData, inputData, node, itemIndex, additionalData, mode);
827
+ returnPromises.push(nodeType.executeSingle.call(thisArgs));
828
+ }
829
+ if (returnPromises.length === 0) {
830
+ return null;
831
+ }
832
+ let promiseResults;
833
+ try {
834
+ promiseResults = await Promise.all(returnPromises);
835
+ }
836
+ catch (error) {
837
+ return Promise.reject(error);
838
+ }
839
+ if (promiseResults) {
840
+ return [promiseResults];
841
+ }
842
+ }
843
+ else if (nodeType.execute) {
844
+ const closeFunctions = [];
845
+ const thisArgs = nodeExecuteFunctions.getExecuteFunctions(this, runExecutionData, runIndex, connectionInputData, inputData, node, additionalData, mode, nodeType, closeFunctions);
846
+ const data = await nodeType.execute.call(thisArgs);
847
+ const closeFunctionsResults = await Promise.allSettled(closeFunctions.map(async (fn) => await fn()));
848
+ const closingErrors = closeFunctionsResults
849
+ .filter((result) => result.status === 'rejected')
850
+ .map((result) => result.reason);
851
+ if (closingErrors.length > 0) {
852
+ if (closingErrors[0] instanceof Error)
853
+ throw closingErrors[0];
854
+ throw new Error(`Error on execution node's close function(s). Node Name: ${node.name}, Node Type: ${node.type}, Errors: ${closingErrors.join(', ')}`);
855
+ }
856
+ return data;
857
+ }
858
+ else if (nodeType.poll) {
859
+ if (mode === 'manual') {
860
+ // In manual mode run the poll function
861
+ const thisArgs = nodeExecuteFunctions.getExecutePollFunctions(this, node, additionalData, mode, 'manual');
862
+ return nodeType.poll.call(thisArgs);
863
+ }
864
+ // In any other mode pass data through as it already contains the result of the poll
865
+ return inputData['main'];
866
+ }
867
+ else if (nodeType.trigger) {
868
+ if (mode === 'manual') {
869
+ // In manual mode start the trigger
870
+ const triggerResponse = await this.runTrigger(node, nodeExecuteFunctions.getExecuteTriggerFunctions, additionalData, mode, 'manual');
871
+ if (triggerResponse === undefined) {
872
+ return null;
873
+ }
874
+ if (triggerResponse.manualTriggerFunction !== undefined) {
875
+ // If a manual trigger function is defined call it and wait till it did run
876
+ await triggerResponse.manualTriggerFunction();
877
+ }
878
+ const response = await triggerResponse.manualTriggerResponse;
879
+ // And then close it again after it did execute
880
+ if (triggerResponse.closeFunction) {
881
+ await triggerResponse.closeFunction();
882
+ }
883
+ if (response.length === 0) {
884
+ return null;
885
+ }
886
+ return response;
887
+ }
888
+ // For trigger nodes in any mode except "manual" do we simply pass the data through
889
+ return inputData['main'];
890
+ }
891
+ else if (nodeType.webhook) {
892
+ // For webhook nodes always simply pass the data through
893
+ return inputData['main'];
894
+ }
895
+ else {
896
+ // For nodes which have routing information on properties
897
+ const routingNode = new __1.RoutingNode(this, node, connectionInputData, runExecutionData ?? null, additionalData, mode);
898
+ return routingNode.runNode(inputData, runIndex, nodeType, nodeExecuteFunctions);
899
+ }
900
+ return null;
901
+ }
902
+ }
903
+ exports.Workflow = Workflow;
904
+ //# sourceMappingURL=Workflow.js.map