@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.
- package/dist/README.md +31 -0
- package/dist/package.json +42 -0
- package/dist/src/index.d.ts +21 -0
- package/dist/src/index.js +33 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lib/Constants.d.ts +68 -0
- package/dist/src/lib/Constants.js +106 -0
- package/dist/src/lib/Constants.js.map +1 -0
- package/dist/src/lib/DeferredPromise.d.ts +6 -0
- package/dist/src/lib/DeferredPromise.js +11 -0
- package/dist/src/lib/DeferredPromise.js.map +1 -0
- package/dist/src/lib/Expression.d.ts +65 -0
- package/dist/src/lib/Expression.js +215 -0
- package/dist/src/lib/Expression.js.map +1 -0
- package/dist/src/lib/Interfaces.d.ts +1579 -0
- package/dist/src/lib/Interfaces.js +44 -0
- package/dist/src/lib/Interfaces.js.map +1 -0
- package/dist/src/lib/LoggerProxy.d.ts +9 -0
- package/dist/src/lib/LoggerProxy.js +40 -0
- package/dist/src/lib/LoggerProxy.js.map +1 -0
- package/dist/src/lib/MetadataUtils.d.ts +4 -0
- package/dist/src/lib/MetadataUtils.js +27 -0
- package/dist/src/lib/MetadataUtils.js.map +1 -0
- package/dist/src/lib/NodeErrors.d.ts +82 -0
- package/dist/src/lib/NodeErrors.js +289 -0
- package/dist/src/lib/NodeErrors.js.map +1 -0
- package/dist/src/lib/NodeHelpers.d.ts +198 -0
- package/dist/src/lib/NodeHelpers.js +1348 -0
- package/dist/src/lib/NodeHelpers.js.map +1 -0
- package/dist/src/lib/ObservableObject.d.ts +5 -0
- package/dist/src/lib/ObservableObject.js +61 -0
- package/dist/src/lib/ObservableObject.js.map +1 -0
- package/dist/src/lib/RoutingNode.d.ts +18 -0
- package/dist/src/lib/RoutingNode.js +508 -0
- package/dist/src/lib/RoutingNode.js.map +1 -0
- package/dist/src/lib/TelemetryHelpers.d.ts +3 -0
- package/dist/src/lib/TelemetryHelpers.js +69 -0
- package/dist/src/lib/TelemetryHelpers.js.map +1 -0
- package/dist/src/lib/TypeValidation.d.ts +21 -0
- package/dist/src/lib/TypeValidation.js +385 -0
- package/dist/src/lib/TypeValidation.js.map +1 -0
- package/dist/src/lib/VersionedNodeType.d.ts +9 -0
- package/dist/src/lib/VersionedNodeType.js +26 -0
- package/dist/src/lib/VersionedNodeType.js.map +1 -0
- package/dist/src/lib/Workflow.d.ts +248 -0
- package/dist/src/lib/Workflow.js +904 -0
- package/dist/src/lib/Workflow.js.map +1 -0
- package/dist/src/lib/WorkflowDataProxy.d.ts +87 -0
- package/dist/src/lib/WorkflowDataProxy.js +556 -0
- package/dist/src/lib/WorkflowDataProxy.js.map +1 -0
- package/dist/src/lib/WorkflowErrors.d.ts +9 -0
- package/dist/src/lib/WorkflowErrors.js +18 -0
- package/dist/src/lib/WorkflowErrors.js.map +1 -0
- package/dist/src/lib/WorkflowHooks.d.ts +11 -0
- package/dist/src/lib/WorkflowHooks.js +34 -0
- package/dist/src/lib/WorkflowHooks.js.map +1 -0
- package/dist/src/lib/errors/base/base.error.d.ts +30 -0
- package/dist/src/lib/errors/base/base.error.js +45 -0
- package/dist/src/lib/errors/base/base.error.js.map +1 -0
- package/dist/src/lib/errors/base/operational.error.d.ts +15 -0
- package/dist/src/lib/errors/base/operational.error.js +19 -0
- package/dist/src/lib/errors/base/operational.error.js.map +1 -0
- package/dist/src/lib/errors/error.types.d.ts +11 -0
- package/dist/src/lib/errors/error.types.js +3 -0
- package/dist/src/lib/errors/error.types.js.map +1 -0
- package/dist/src/lib/errors/index.d.ts +1 -0
- package/dist/src/lib/errors/index.js +6 -0
- package/dist/src/lib/errors/index.js.map +1 -0
- package/dist/src/lib/result.d.ts +19 -0
- package/dist/src/lib/result.js +36 -0
- package/dist/src/lib/result.js.map +1 -0
- package/dist/src/lib/utils.d.ts +50 -0
- package/dist/src/lib/utils.js +119 -0
- package/dist/src/lib/utils.js.map +1 -0
- package/package.json +5 -1
- package/src/lib/Interfaces.ts +28 -33
- package/src/lib/Workflow.ts +1 -1
- package/src/lib/WorkflowDataProxy.ts +1 -1
- 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
|