@fsai-flow/workflow 0.0.2 → 0.1.0

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 (136) hide show
  1. package/dist/README.md +31 -0
  2. package/dist/package.json +42 -0
  3. package/dist/src/index.d.ts +22 -0
  4. package/dist/src/index.d.ts.map +1 -0
  5. package/dist/src/index.js +68 -0
  6. package/dist/src/index.js.map +1 -0
  7. package/dist/src/lib/Constants.d.ts +69 -0
  8. package/dist/src/lib/Constants.d.ts.map +1 -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 +7 -0
  12. package/dist/src/lib/DeferredPromise.d.ts.map +1 -0
  13. package/dist/src/lib/DeferredPromise.js +11 -0
  14. package/dist/src/lib/DeferredPromise.js.map +1 -0
  15. package/dist/src/lib/Expression.d.ts +66 -0
  16. package/dist/src/lib/Expression.d.ts.map +1 -0
  17. package/dist/src/lib/Expression.js +247 -0
  18. package/dist/src/lib/Expression.js.map +1 -0
  19. package/dist/src/lib/Interfaces.d.ts +1623 -0
  20. package/dist/src/lib/Interfaces.d.ts.map +1 -0
  21. package/dist/src/lib/Interfaces.js +44 -0
  22. package/dist/src/lib/Interfaces.js.map +1 -0
  23. package/dist/src/lib/LoggerProxy.d.ts +10 -0
  24. package/dist/src/lib/LoggerProxy.d.ts.map +1 -0
  25. package/dist/src/lib/LoggerProxy.js +40 -0
  26. package/dist/src/lib/LoggerProxy.js.map +1 -0
  27. package/dist/src/lib/MetadataUtils.d.ts +5 -0
  28. package/dist/src/lib/MetadataUtils.d.ts.map +1 -0
  29. package/dist/src/lib/MetadataUtils.js +27 -0
  30. package/dist/src/lib/MetadataUtils.js.map +1 -0
  31. package/dist/src/lib/NodeErrors.d.ts +83 -0
  32. package/dist/src/lib/NodeErrors.d.ts.map +1 -0
  33. package/dist/src/lib/NodeErrors.js +284 -0
  34. package/dist/src/lib/NodeErrors.js.map +1 -0
  35. package/dist/src/lib/NodeHelpers.d.ts +199 -0
  36. package/dist/src/lib/NodeHelpers.d.ts.map +1 -0
  37. package/dist/src/lib/NodeHelpers.js +1335 -0
  38. package/dist/src/lib/NodeHelpers.js.map +1 -0
  39. package/dist/src/lib/ObservableObject.d.ts +6 -0
  40. package/dist/src/lib/ObservableObject.d.ts.map +1 -0
  41. package/dist/src/lib/ObservableObject.js +61 -0
  42. package/dist/src/lib/ObservableObject.js.map +1 -0
  43. package/dist/src/lib/RoutingNode.d.ts +24 -0
  44. package/dist/src/lib/RoutingNode.d.ts.map +1 -0
  45. package/dist/src/lib/RoutingNode.js +528 -0
  46. package/dist/src/lib/RoutingNode.js.map +1 -0
  47. package/dist/src/lib/TelemetryHelpers.d.ts +4 -0
  48. package/dist/src/lib/TelemetryHelpers.d.ts.map +1 -0
  49. package/dist/src/lib/TelemetryHelpers.js +67 -0
  50. package/dist/src/lib/TelemetryHelpers.js.map +1 -0
  51. package/dist/src/lib/TypeValidation.d.ts +22 -0
  52. package/dist/src/lib/TypeValidation.d.ts.map +1 -0
  53. package/dist/src/lib/TypeValidation.js +376 -0
  54. package/dist/src/lib/TypeValidation.js.map +1 -0
  55. package/dist/src/lib/VersionedNodeType.d.ts +10 -0
  56. package/dist/src/lib/VersionedNodeType.d.ts.map +1 -0
  57. package/dist/src/lib/VersionedNodeType.js +24 -0
  58. package/dist/src/lib/VersionedNodeType.js.map +1 -0
  59. package/dist/src/lib/Workflow.d.ts +249 -0
  60. package/dist/src/lib/Workflow.d.ts.map +1 -0
  61. package/dist/src/lib/Workflow.js +899 -0
  62. package/dist/src/lib/Workflow.js.map +1 -0
  63. package/dist/src/lib/WorkflowDataProxy.d.ts +88 -0
  64. package/dist/src/lib/WorkflowDataProxy.d.ts.map +1 -0
  65. package/dist/src/lib/WorkflowDataProxy.js +583 -0
  66. package/dist/src/lib/WorkflowDataProxy.js.map +1 -0
  67. package/dist/src/lib/WorkflowErrors.d.ts +10 -0
  68. package/dist/src/lib/WorkflowErrors.d.ts.map +1 -0
  69. package/dist/src/lib/WorkflowErrors.js +18 -0
  70. package/dist/src/lib/WorkflowErrors.js.map +1 -0
  71. package/dist/src/lib/WorkflowHooks.d.ts +12 -0
  72. package/dist/src/lib/WorkflowHooks.d.ts.map +1 -0
  73. package/dist/src/lib/WorkflowHooks.js +32 -0
  74. package/dist/src/lib/WorkflowHooks.js.map +1 -0
  75. package/dist/src/lib/errors/base/base.error.d.ts +30 -0
  76. package/dist/src/lib/errors/base/base.error.d.ts.map +1 -0
  77. package/dist/src/lib/errors/base/base.error.js +47 -0
  78. package/dist/src/lib/errors/base/base.error.js.map +1 -0
  79. package/dist/src/lib/errors/base/operational.error.d.ts +16 -0
  80. package/dist/src/lib/errors/base/operational.error.d.ts.map +1 -0
  81. package/dist/src/lib/errors/base/operational.error.js +19 -0
  82. package/dist/src/lib/errors/base/operational.error.js.map +1 -0
  83. package/dist/src/lib/errors/error.types.d.ts +14 -0
  84. package/dist/src/lib/errors/error.types.d.ts.map +1 -0
  85. package/dist/src/lib/errors/error.types.js +3 -0
  86. package/dist/src/lib/errors/error.types.js.map +1 -0
  87. package/dist/src/lib/errors/index.d.ts +2 -0
  88. package/dist/src/lib/errors/index.d.ts.map +1 -0
  89. package/dist/src/lib/errors/index.js +6 -0
  90. package/dist/src/lib/errors/index.js.map +1 -0
  91. package/dist/src/lib/result.d.ts +20 -0
  92. package/dist/src/lib/result.d.ts.map +1 -0
  93. package/dist/src/lib/result.js +36 -0
  94. package/dist/src/lib/result.js.map +1 -0
  95. package/dist/src/lib/utils.d.ts +51 -0
  96. package/dist/src/lib/utils.d.ts.map +1 -0
  97. package/dist/src/lib/utils.js +119 -0
  98. package/dist/src/lib/utils.js.map +1 -0
  99. package/package.json +49 -35
  100. package/.eslintrc.json +0 -33
  101. package/eslint.config.js +0 -19
  102. package/jest.config.ts +0 -10
  103. package/project.json +0 -19
  104. package/src/index.ts +0 -33
  105. package/src/lib/Constants.ts +0 -124
  106. package/src/lib/DeferredPromise.ts +0 -14
  107. package/src/lib/Expression.ts +0 -375
  108. package/src/lib/Interfaces.ts +0 -2262
  109. package/src/lib/LoggerProxy.ts +0 -43
  110. package/src/lib/MetadataUtils.ts +0 -34
  111. package/src/lib/NodeErrors.ts +0 -332
  112. package/src/lib/NodeHelpers.ts +0 -1666
  113. package/src/lib/ObservableObject.ts +0 -77
  114. package/src/lib/RoutingNode.ts +0 -862
  115. package/src/lib/TelemetryHelpers.ts +0 -86
  116. package/src/lib/TypeValidation.ts +0 -431
  117. package/src/lib/VersionedNodeType.ts +0 -30
  118. package/src/lib/Workflow.ts +0 -1270
  119. package/src/lib/WorkflowDataProxy.ts +0 -708
  120. package/src/lib/WorkflowErrors.ts +0 -18
  121. package/src/lib/WorkflowHooks.ts +0 -51
  122. package/src/lib/errors/base/base.error.ts +0 -68
  123. package/src/lib/errors/base/operational.error.ts +0 -21
  124. package/src/lib/errors/error.types.ts +0 -14
  125. package/src/lib/errors/index.ts +0 -1
  126. package/src/lib/result.ts +0 -34
  127. package/src/lib/utils.ts +0 -166
  128. package/tests/Helpers.ts +0 -667
  129. package/tests/NodeHelpers.test.ts +0 -3053
  130. package/tests/ObservableObject.test.ts +0 -171
  131. package/tests/RoutingNode.test.ts +0 -1680
  132. package/tests/Workflow.test.ts +0 -1284
  133. package/tests/WorkflowDataProxy.test.ts +0 -199
  134. package/tsconfig.json +0 -27
  135. package/tsconfig.lib.json +0 -11
  136. package/tsconfig.spec.json +0 -14
@@ -0,0 +1,899 @@
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 (!Object.hasOwn(connections, sourceNode)) {
81
+ continue;
82
+ }
83
+ for (const type in connections[sourceNode]) {
84
+ if (!Object.hasOwn(connections[sourceNode], type)) {
85
+ continue;
86
+ }
87
+ for (const inputIndex in connections[sourceNode][type]) {
88
+ if (!Object.hasOwn(connections[sourceNode][type], inputIndex)) {
89
+ continue;
90
+ }
91
+ for (connectionInfo of connections[sourceNode][type][inputIndex]) {
92
+ if (connectionInfo.node === "__proto__" ||
93
+ connectionInfo.node === "constructor" ||
94
+ connectionInfo.node === "prototype") {
95
+ throw new Error("Prototype-polluting assignment detected");
96
+ }
97
+ if (!Object.hasOwn(returnConnection, connectionInfo.node)) {
98
+ returnConnection[connectionInfo.node] = {};
99
+ }
100
+ if (!Object.hasOwn(returnConnection[connectionInfo.node], connectionInfo.type)) {
101
+ returnConnection[connectionInfo.node][connectionInfo.type] = [];
102
+ }
103
+ maxIndex = returnConnection[connectionInfo.node][connectionInfo.type].length - 1;
104
+ for (let j = maxIndex; j < connectionInfo.index; j++) {
105
+ returnConnection[connectionInfo.node][connectionInfo.type].push([]);
106
+ }
107
+ returnConnection[connectionInfo.node][connectionInfo.type][connectionInfo.index].push({
108
+ node: sourceNode,
109
+ type,
110
+ index: Number.parseInt(inputIndex, 10),
111
+ });
112
+ }
113
+ }
114
+ }
115
+ }
116
+ return returnConnection;
117
+ }
118
+ /**
119
+ * A workflow can only be activated if it has a node which has either triggers
120
+ * or webhooks defined.
121
+ *
122
+ * @param {string[]} [ignoreNodeTypes] Node-types to ignore in the check
123
+ * @returns {boolean}
124
+ * @memberof Workflow
125
+ */
126
+ checkIfWorkflowCanBeActivated(ignoreNodeTypes) {
127
+ let node;
128
+ let nodeType;
129
+ for (const nodeName of Object.keys(this.nodes)) {
130
+ node = this.nodes[nodeName];
131
+ if (node.disabled === true) {
132
+ // Deactivated nodes can not trigger a run so ignore
133
+ continue;
134
+ }
135
+ // eslint-disable-next-line @typescript-eslint/prefer-optional-chain
136
+ if (ignoreNodeTypes?.includes(node.type)) {
137
+ continue;
138
+ }
139
+ nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
140
+ if (nodeType === undefined) {
141
+ // Type is not known so check is not possible
142
+ continue;
143
+ }
144
+ if (nodeType.poll !== undefined || nodeType.trigger !== undefined || 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 (Object.hasOwn(this.nodes, 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
+ const returnArray = [];
318
+ for (const currentValue of parameterValue) {
319
+ returnArray.push(this.renameNodeInExpressions(currentValue, currentName, newName));
320
+ }
321
+ return returnArray;
322
+ }
323
+ const returnData = {};
324
+ for (const parameterName of Object.keys(parameterValue || {})) {
325
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
326
+ returnData[parameterName] = this.renameNodeInExpressions(parameterValue[parameterName], currentName, newName);
327
+ }
328
+ return returnData;
329
+ }
330
+ /**
331
+ * Rename a node in the workflow
332
+ *
333
+ * @param {string} currentName The current name of the node
334
+ * @param {string} newName The new name
335
+ * @memberof Workflow
336
+ */
337
+ renameNode(currentName, newName) {
338
+ // Rename the node itself
339
+ if (this.nodes[currentName] !== undefined) {
340
+ this.nodes[newName] = this.nodes[currentName];
341
+ this.nodes[newName].name = newName;
342
+ delete this.nodes[currentName];
343
+ }
344
+ // Update the expressions which reference the node
345
+ // with its old name
346
+ for (const node of Object.values(this.nodes)) {
347
+ node.parameters = this.renameNodeInExpressions(node.parameters, currentName, newName);
348
+ }
349
+ // Change all source connections
350
+ if (Object.hasOwn(this.connectionsBySourceNode, currentName)) {
351
+ this.connectionsBySourceNode[newName] = this.connectionsBySourceNode[currentName];
352
+ delete this.connectionsBySourceNode[currentName];
353
+ }
354
+ // Change all destination connections
355
+ let sourceNode;
356
+ let type;
357
+ let sourceIndex;
358
+ let connectionIndex;
359
+ let connectionData;
360
+ for (sourceNode of Object.keys(this.connectionsBySourceNode)) {
361
+ for (type of Object.keys(this.connectionsBySourceNode[sourceNode])) {
362
+ for (sourceIndex of Object.keys(this.connectionsBySourceNode[sourceNode][type])) {
363
+ for (connectionIndex of Object.keys(this.connectionsBySourceNode[sourceNode][type][Number.parseInt(sourceIndex, 10)])) {
364
+ connectionData =
365
+ this.connectionsBySourceNode[sourceNode][type][Number.parseInt(sourceIndex, 10)][Number.parseInt(connectionIndex, 10)];
366
+ if (connectionData.node === currentName) {
367
+ connectionData.node = newName;
368
+ }
369
+ }
370
+ }
371
+ }
372
+ }
373
+ // Use the updated connections to create updated connections by destionation nodes
374
+ this.connectionsByDestinationNode = this.__getConnectionsByDestination(this.connectionsBySourceNode);
375
+ }
376
+ /**
377
+ * Finds the highest parent nodes of the node with the given name
378
+ *
379
+ * @param {string} nodeName
380
+ * @param {string} [type='main']
381
+ * @param {number} [nodeConnectionIndex]
382
+ * @returns {string[]}
383
+ * @memberof Workflow
384
+ */
385
+ getHighestNode(nodeName, type = "main", nodeConnectionIndex, checkedNodes) {
386
+ const currentHighest = [];
387
+ if (this.nodes[nodeName].disabled === false) {
388
+ // If the current node is not disabled itself is the highest
389
+ currentHighest.push(nodeName);
390
+ }
391
+ if (!Object.hasOwn(this.connectionsByDestinationNode, nodeName)) {
392
+ // Node does not have incoming connections
393
+ return currentHighest;
394
+ }
395
+ if (!Object.hasOwn(this.connectionsByDestinationNode[nodeName], type)) {
396
+ // Node does not have incoming connections of given type
397
+ return currentHighest;
398
+ }
399
+ checkedNodes = checkedNodes || [];
400
+ if (checkedNodes.includes(nodeName)) {
401
+ // Node got checked already before
402
+ return currentHighest;
403
+ }
404
+ checkedNodes.push(nodeName);
405
+ const returnNodes = [];
406
+ let addNodes;
407
+ let connectionsByIndex;
408
+ for (let connectionIndex = 0; connectionIndex < this.connectionsByDestinationNode[nodeName][type].length; connectionIndex++) {
409
+ if (nodeConnectionIndex !== undefined && nodeConnectionIndex !== connectionIndex) {
410
+ // If a connection-index is given ignore all other ones
411
+ continue;
412
+ }
413
+ connectionsByIndex = this.connectionsByDestinationNode[nodeName][type][connectionIndex];
414
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
415
+ connectionsByIndex.forEach((connection) => {
416
+ if (checkedNodes.includes(connection.node)) {
417
+ // Node got checked already before
418
+ return;
419
+ }
420
+ addNodes = this.getHighestNode(connection.node, type, undefined, checkedNodes);
421
+ if (addNodes.length === 0) {
422
+ // The checked node does not have any further parents so add it
423
+ // if it is not disabled
424
+ if (this.nodes[connection.node].disabled !== true) {
425
+ addNodes = [connection.node];
426
+ }
427
+ }
428
+ addNodes.forEach((name) => {
429
+ // Only add if node is not on the list already anyway
430
+ if (returnNodes.indexOf(name) === -1) {
431
+ returnNodes.push(name);
432
+ }
433
+ });
434
+ });
435
+ }
436
+ return returnNodes;
437
+ }
438
+ /**
439
+ * Returns all the after the given one
440
+ *
441
+ * @param {string} nodeName
442
+ * @param {string} [type='main']
443
+ * @param {*} [depth=-1]
444
+ * @returns {string[]}
445
+ * @memberof Workflow
446
+ */
447
+ getChildNodes(nodeName, type = "main", depth = -1) {
448
+ return this.getConnectedNodes(this.connectionsBySourceNode, nodeName, type, depth);
449
+ }
450
+ /**
451
+ * Returns all the nodes before the given one
452
+ *
453
+ * @param {string} nodeName
454
+ * @param {string} [type='main']
455
+ * @param {*} [depth=-1]
456
+ * @returns {string[]}
457
+ * @memberof Workflow
458
+ */
459
+ getParentNodes(nodeName, type = "main", depth = -1) {
460
+ return this.getConnectedNodes(this.connectionsByDestinationNode, nodeName, type, depth);
461
+ }
462
+ /**
463
+ * Gets all the nodes which are connected nodes starting from
464
+ * the given one
465
+ *
466
+ * @param {IConnections} connections
467
+ * @param {string} nodeName
468
+ * @param {string} [type='main']
469
+ * @param {*} [depth=-1]
470
+ * @param {string[]} [checkedNodes]
471
+ * @returns {string[]}
472
+ * @memberof Workflow
473
+ */
474
+ getConnectedNodes(connections, nodeName, type = "main", depth = -1, checkedNodes) {
475
+ depth = depth === -1 ? -1 : depth;
476
+ const newDepth = depth === -1 ? depth : depth - 1;
477
+ if (depth === 0) {
478
+ // Reached max depth
479
+ return [];
480
+ }
481
+ if (!Object.hasOwn(connections, nodeName)) {
482
+ // Node does not have incoming connections
483
+ return [];
484
+ }
485
+ if (!Object.hasOwn(connections[nodeName], type)) {
486
+ // Node does not have incoming connections of given type
487
+ return [];
488
+ }
489
+ checkedNodes = checkedNodes || [];
490
+ if (checkedNodes.includes(nodeName)) {
491
+ // Node got checked already before
492
+ return [];
493
+ }
494
+ checkedNodes.push(nodeName);
495
+ const returnNodes = [];
496
+ let addNodes;
497
+ let nodeIndex;
498
+ let i;
499
+ let parentNodeName;
500
+ connections[nodeName][type].forEach((connectionsByIndex) => {
501
+ connectionsByIndex.forEach((connection) => {
502
+ if (checkedNodes.includes(connection.node)) {
503
+ // Node got checked already before
504
+ return;
505
+ }
506
+ returnNodes.unshift(connection.node);
507
+ addNodes = this.getConnectedNodes(connections, connection.node, type, newDepth, checkedNodes);
508
+ for (i = addNodes.length; i--; i > 0) {
509
+ // Because nodes can have multiple parents it is possible that
510
+ // parts of the tree is parent of both and to not add nodes
511
+ // twice check first if they already got added before.
512
+ parentNodeName = addNodes[i];
513
+ nodeIndex = returnNodes.indexOf(parentNodeName);
514
+ if (nodeIndex !== -1) {
515
+ // Node got found before so remove it from current location
516
+ // that node-order stays correct
517
+ returnNodes.splice(nodeIndex, 1);
518
+ }
519
+ returnNodes.unshift(parentNodeName);
520
+ }
521
+ });
522
+ });
523
+ return returnNodes;
524
+ }
525
+ /**
526
+ * Returns via which output of the parent-node the node
527
+ * is connected to.
528
+ *
529
+ * @param {string} nodeName The node to check how it is connected with parent node
530
+ * @param {string} parentNodeName The parent node to get the output index of
531
+ * @param {string} [type='main']
532
+ * @param {*} [depth=-1]
533
+ * @param {string[]} [checkedNodes]
534
+ * @returns {(number | undefined)}
535
+ * @memberof Workflow
536
+ */
537
+ getNodeConnectionOutputIndex(nodeName, parentNodeName, type = "main", depth = -1, checkedNodes) {
538
+ const node = this.getNode(parentNodeName);
539
+ if (node === null) {
540
+ return undefined;
541
+ }
542
+ const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
543
+ if (nodeType.description.outputs.length === 1) {
544
+ // If the parent node has only one output, it can only be connected
545
+ // to that one. So no further checking is required.
546
+ return 0;
547
+ }
548
+ depth = depth === -1 ? -1 : depth;
549
+ const newDepth = depth === -1 ? depth : depth - 1;
550
+ if (depth === 0) {
551
+ // Reached max depth
552
+ return undefined;
553
+ }
554
+ if (!Object.hasOwn(this.connectionsByDestinationNode, nodeName)) {
555
+ // Node does not have incoming connections
556
+ return undefined;
557
+ }
558
+ if (!Object.hasOwn(this.connectionsByDestinationNode[nodeName], type)) {
559
+ // Node does not have incoming connections of given type
560
+ return undefined;
561
+ }
562
+ checkedNodes = checkedNodes || [];
563
+ if (checkedNodes.includes(nodeName)) {
564
+ // Node got checked already before
565
+ return undefined;
566
+ }
567
+ checkedNodes.push(nodeName);
568
+ let outputIndex;
569
+ for (const connectionsByIndex of this.connectionsByDestinationNode[nodeName][type]) {
570
+ for (const connection of connectionsByIndex) {
571
+ if (parentNodeName === connection.node) {
572
+ return connection.index;
573
+ }
574
+ if (checkedNodes.includes(connection.node)) {
575
+ // Node got checked already before so continue with the next one
576
+ continue;
577
+ }
578
+ outputIndex = this.getNodeConnectionOutputIndex(connection.node, parentNodeName, type, newDepth, checkedNodes);
579
+ if (outputIndex !== undefined) {
580
+ return outputIndex;
581
+ }
582
+ }
583
+ }
584
+ return undefined;
585
+ }
586
+ /**
587
+ * Returns from which of the given nodes the workflow should get started from
588
+ *
589
+ * @param {string[]} nodeNames The potential start nodes
590
+ * @returns {(INode | undefined)}
591
+ * @memberof Workflow
592
+ */
593
+ __getStartNode(nodeNames) {
594
+ // Check if there are any trigger or poll nodes and then return the first one
595
+ let node;
596
+ let nodeType;
597
+ for (const nodeName of nodeNames) {
598
+ node = this.nodes[nodeName];
599
+ nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
600
+ if (nodeType.trigger !== undefined || nodeType.poll !== undefined) {
601
+ if (node.disabled === true) {
602
+ continue;
603
+ }
604
+ return node;
605
+ }
606
+ }
607
+ // Check if there is the actual "start" node
608
+ const startNodeType = "n8n-nodes-base.start";
609
+ for (const nodeName of nodeNames) {
610
+ node = this.nodes[nodeName];
611
+ if (node.type === startNodeType) {
612
+ return node;
613
+ }
614
+ }
615
+ return undefined;
616
+ }
617
+ /**
618
+ * Returns the start node to start the worfklow from
619
+ *
620
+ * @param {string} [destinationNode]
621
+ * @returns {(INode | undefined)}
622
+ * @memberof Workflow
623
+ */
624
+ getStartNode(destinationNode) {
625
+ if (destinationNode) {
626
+ // Find the highest parent nodes of the given one
627
+ const nodeNames = this.getHighestNode(destinationNode);
628
+ if (nodeNames.length === 0) {
629
+ // If no parent nodes have been found then only the destination-node
630
+ // is in the tree so add that one
631
+ nodeNames.push(destinationNode);
632
+ }
633
+ // Check which node to return as start node
634
+ const node = this.__getStartNode(nodeNames);
635
+ if (node !== undefined) {
636
+ return node;
637
+ }
638
+ // If none of the above did find anything simply return the
639
+ // first parent node in the list
640
+ return this.nodes[nodeNames[0]];
641
+ }
642
+ return this.__getStartNode(Object.keys(this.nodes));
643
+ }
644
+ /**
645
+ * Executes the Webhooks method of the node
646
+ *
647
+ * @param {WebhookSetupMethodNames} method The name of the method to execute
648
+ * @param {IWebhookData} webhookData
649
+ * @param {INodeExecuteFunctions} nodeExecuteFunctions
650
+ * @param {WorkflowExecuteMode} mode
651
+ * @returns {(Promise<boolean | undefined>)}
652
+ * @memberof Workflow
653
+ */
654
+ async runWebhookMethod(method, webhookData, nodeExecuteFunctions, mode, activation, isTest) {
655
+ const node = this.getNode(webhookData.node);
656
+ const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
657
+ if (nodeType.webhookMethods === undefined) {
658
+ return;
659
+ }
660
+ if (nodeType.webhookMethods[webhookData.webhookDescription.name] === undefined) {
661
+ return;
662
+ }
663
+ if (nodeType.webhookMethods[webhookData.webhookDescription.name][method] === undefined) {
664
+ return;
665
+ }
666
+ const thisArgs = nodeExecuteFunctions.getExecuteHookFunctions(this, node, webhookData.workflowExecuteAdditionalData, mode, activation, isTest, webhookData);
667
+ // eslint-disable-next-line consistent-return
668
+ return nodeType.webhookMethods[webhookData.webhookDescription.name][method].call(thisArgs);
669
+ }
670
+ /**
671
+ * Runs the given trigger node so that it can trigger the workflow
672
+ * when the node has data.
673
+ *
674
+ * @param {INode} node
675
+ * @param {IGetExecuteTriggerFunctions} getTriggerFunctions
676
+ * @param {IWorkflowExecuteAdditionalData} additionalData
677
+ * @param {WorkflowExecuteMode} mode
678
+ * @returns {(Promise<ITriggerResponse | undefined>)}
679
+ * @memberof Workflow
680
+ */
681
+ async runTrigger(node, getTriggerFunctions, additionalData, mode, activation) {
682
+ const triggerFunctions = getTriggerFunctions(this, node, additionalData, mode, activation);
683
+ const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
684
+ if (nodeType === undefined) {
685
+ throw new Error(`The node type "${node.type}" of node "${node.name}" is not known.`);
686
+ }
687
+ if (!nodeType.trigger) {
688
+ throw new Error(`The node type "${node.type}" of node "${node.name}" does not have a trigger function defined.`);
689
+ }
690
+ if (mode === "manual") {
691
+ // In manual mode we do not just start the trigger function we also
692
+ // want to be able to get informed as soon as the first data got emitted
693
+ const triggerResponse = await nodeType.trigger.call(triggerFunctions);
694
+ // Add the manual trigger response which resolves when the first time data got emitted
695
+ triggerResponse.manualTriggerResponse = new Promise((resolve) => {
696
+ triggerFunctions.emit = ((resolveEmit) => (data, responsePromise) => {
697
+ additionalData.hooks.hookFunctions.sendResponse = [
698
+ async (response) => {
699
+ if (responsePromise) {
700
+ responsePromise.resolve(response);
701
+ }
702
+ },
703
+ ];
704
+ resolveEmit(data);
705
+ })(resolve);
706
+ });
707
+ return triggerResponse;
708
+ }
709
+ // In all other modes simply start the trigger
710
+ return nodeType.trigger.call(triggerFunctions);
711
+ }
712
+ /**
713
+ * Runs the given trigger node so that it can trigger the workflow
714
+ * when the node has data.
715
+ *
716
+ * @param {INode} node
717
+ * @param {IPollFunctions} pollFunctions
718
+ * @returns
719
+ * @memberof Workflow
720
+ */
721
+ async runPoll(node, pollFunctions) {
722
+ const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
723
+ if (nodeType === undefined) {
724
+ throw new Error(`The node type "${node.type}" of node "${node.name}" is not known.`);
725
+ }
726
+ if (!nodeType.poll) {
727
+ throw new Error(`The node type "${node.type}" of node "${node.name}" does not have a poll function defined.`);
728
+ }
729
+ return nodeType.poll.call(pollFunctions);
730
+ }
731
+ /**
732
+ * Executes the webhook data to see what it should return and if the
733
+ * workflow should be started or not
734
+ *
735
+ * @param {INode} node
736
+ * @param {IWorkflowExecuteAdditionalData} additionalData
737
+ * @param {INodeExecuteFunctions} nodeExecuteFunctions
738
+ * @param {WorkflowExecuteMode} mode
739
+ * @param {IRunExecutionData | undefined} runExecutionData
740
+ * @returns {Promise<IWebhookResponseData>}
741
+ * @memberof Workflow
742
+ */
743
+ async runWebhook(webhookData, node, additionalData, nodeExecuteFunctions, mode, runExecutionData, workflow) {
744
+ const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
745
+ if (nodeType === undefined) {
746
+ throw new Error(`The type of the webhook node "${node.name}" is not known.`);
747
+ }
748
+ if (nodeType.webhook === undefined) {
749
+ throw new Error(`The node "${node.name}" does not have any webhooks defined.`);
750
+ }
751
+ const thisArgs = nodeExecuteFunctions.getExecuteWebhookFunctions(this, node, additionalData, mode, webhookData, runExecutionData);
752
+ return nodeType.webhook.call(thisArgs, workflow);
753
+ }
754
+ /**
755
+ * Executes the given node.
756
+ *
757
+ * @param {INode} node
758
+ * @param {ITaskDataConnections} inputData
759
+ * @param {IRunExecutionData} runExecutionData
760
+ * @param {number} runIndex
761
+ * @param {IWorkflowExecuteAdditionalData} additionalData
762
+ * @param {INodeExecuteFunctions} nodeExecuteFunctions
763
+ * @param {WorkflowExecuteMode} mode
764
+ * @returns {(Promise<INodeExecutionData[][] | null>)}
765
+ * @memberof Workflow
766
+ */
767
+ async runNode(node, inputData, runExecutionData, runIndex, additionalData, nodeExecuteFunctions, mode) {
768
+ if (node.disabled === true) {
769
+ // If node is disabled simply pass the data through
770
+ // return NodeRunHelpers.
771
+ if (Object.hasOwn(inputData, "main") && inputData.main.length > 0) {
772
+ // If the node is disabled simply return the data from the first main input
773
+ if (inputData.main[0] === null) {
774
+ return undefined;
775
+ }
776
+ return [inputData.main[0]];
777
+ }
778
+ return undefined;
779
+ }
780
+ const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
781
+ if (nodeType === undefined) {
782
+ throw new Error(`Node type "${node.type}" is not known so can not run it!`);
783
+ }
784
+ let connectionInputData = [];
785
+ if (nodeType.execute || nodeType.executeSingle || (!nodeType.poll && !nodeType.trigger && !nodeType.webhook)) {
786
+ // Only stop if first input is empty for execute & executeSingle runs. For all others run anyways
787
+ // because then it is a trigger node. As they only pass data through and so the input-data
788
+ // becomes output-data it has to be possible.
789
+ if (Object.hasOwn(inputData, "main") && inputData.main.length > 0) {
790
+ // We always use the data of main input and the first input for executeSingle
791
+ connectionInputData = inputData.main[0];
792
+ }
793
+ if (connectionInputData.length === 0) {
794
+ // No data for node so return
795
+ return undefined;
796
+ }
797
+ }
798
+ if (runExecutionData.resultData.lastNodeExecuted === node.name && runExecutionData.resultData.error !== undefined) {
799
+ // The node did already fail. So throw an error here that it displays and logs it correctly.
800
+ // Does get used by webhook and trigger nodes in case they throw an error that it is possible
801
+ // to log the error and display in Editor-UI.
802
+ const error = new Error(runExecutionData.resultData.error.message);
803
+ error.stack = runExecutionData.resultData.error.stack;
804
+ throw error;
805
+ }
806
+ if (node.executeOnce === true) {
807
+ // If node should be executed only once so use only the first input item
808
+ connectionInputData = connectionInputData.slice(0, 1);
809
+ const newInputData = {};
810
+ for (const inputName of Object.keys(inputData)) {
811
+ newInputData[inputName] = inputData[inputName].map((input) => {
812
+ // eslint-disable-next-line @typescript-eslint/prefer-optional-chain
813
+ return input?.slice(0, 1) ?? null;
814
+ });
815
+ }
816
+ inputData = newInputData;
817
+ }
818
+ if (nodeType.executeSingle) {
819
+ const returnPromises = [];
820
+ for (let itemIndex = 0; itemIndex < connectionInputData.length; itemIndex++) {
821
+ const thisArgs = nodeExecuteFunctions.getExecuteSingleFunctions(this, runExecutionData, runIndex, connectionInputData, inputData, node, itemIndex, additionalData, mode);
822
+ returnPromises.push(nodeType.executeSingle.call(thisArgs));
823
+ }
824
+ if (returnPromises.length === 0) {
825
+ return null;
826
+ }
827
+ let promiseResults;
828
+ try {
829
+ promiseResults = await Promise.all(returnPromises);
830
+ }
831
+ catch (error) {
832
+ return Promise.reject(error);
833
+ }
834
+ if (promiseResults) {
835
+ return [promiseResults];
836
+ }
837
+ }
838
+ else if (nodeType.execute) {
839
+ const closeFunctions = [];
840
+ const thisArgs = nodeExecuteFunctions.getExecuteFunctions(this, runExecutionData, runIndex, connectionInputData, inputData, node, additionalData, mode, nodeType, closeFunctions);
841
+ const data = await nodeType.execute.call(thisArgs);
842
+ const closeFunctionsResults = await Promise.allSettled(closeFunctions.map(async (fn) => await fn()));
843
+ const closingErrors = closeFunctionsResults
844
+ .filter((result) => result.status === "rejected")
845
+ .map((result) => result.reason);
846
+ if (closingErrors.length > 0) {
847
+ if (closingErrors[0] instanceof Error)
848
+ throw closingErrors[0];
849
+ throw new Error(`Error on execution node's close function(s). Node Name: ${node.name}, Node Type: ${node.type}, Errors: ${closingErrors.join(", ")}`);
850
+ }
851
+ return data;
852
+ }
853
+ else if (nodeType.poll) {
854
+ if (mode === "manual") {
855
+ // In manual mode run the poll function
856
+ const thisArgs = nodeExecuteFunctions.getExecutePollFunctions(this, node, additionalData, mode, "manual");
857
+ return nodeType.poll.call(thisArgs);
858
+ }
859
+ // In any other mode pass data through as it already contains the result of the poll
860
+ return inputData.main;
861
+ }
862
+ else if (nodeType.trigger) {
863
+ if (mode === "manual") {
864
+ // In manual mode start the trigger
865
+ const triggerResponse = await this.runTrigger(node, nodeExecuteFunctions.getExecuteTriggerFunctions, additionalData, mode, "manual");
866
+ if (triggerResponse === undefined) {
867
+ return null;
868
+ }
869
+ if (triggerResponse.manualTriggerFunction !== undefined) {
870
+ // If a manual trigger function is defined call it and wait till it did run
871
+ await triggerResponse.manualTriggerFunction();
872
+ }
873
+ const response = await triggerResponse.manualTriggerResponse;
874
+ // And then close it again after it did execute
875
+ if (triggerResponse.closeFunction) {
876
+ await triggerResponse.closeFunction();
877
+ }
878
+ if (response.length === 0) {
879
+ return null;
880
+ }
881
+ return response;
882
+ }
883
+ // For trigger nodes in any mode except "manual" do we simply pass the data through
884
+ return inputData.main;
885
+ }
886
+ else if (nodeType.webhook) {
887
+ // For webhook nodes always simply pass the data through
888
+ return inputData.main;
889
+ }
890
+ else {
891
+ // For nodes which have routing information on properties
892
+ const routingNode = new __1.RoutingNode(this, node, connectionInputData, runExecutionData ?? null, additionalData, mode);
893
+ return routingNode.runNode(inputData, runIndex, nodeType, nodeExecuteFunctions);
894
+ }
895
+ return null;
896
+ }
897
+ }
898
+ exports.Workflow = Workflow;
899
+ //# sourceMappingURL=Workflow.js.map