@ldsg/workflow 0.3.33 → 0.3.35
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/__tests__/unit/multi-predecessor.test.d.ts +2 -0
- package/dist/__tests__/unit/multi-predecessor.test.d.ts.map +1 -0
- package/dist/__tests__/unit/multi-predecessor.test.js +450 -0
- package/dist/utils/execute-workflow.d.ts.map +1 -1
- package/dist/utils/execute-workflow.js +113 -45
- package/package.json +6 -6
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi-predecessor.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/multi-predecessor.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const resource_1 = require("../../resource");
|
|
4
|
+
describe("workflow BFS level scheduling with multiple predecessors", () => {
|
|
5
|
+
test("conditional branch with merge: only activated branch reaches merge (OR-join)", async () => {
|
|
6
|
+
const executionOrder = [];
|
|
7
|
+
let mergeInput = null;
|
|
8
|
+
const startNode = {
|
|
9
|
+
id: "start",
|
|
10
|
+
propertiesSchema: {
|
|
11
|
+
type: "object",
|
|
12
|
+
properties: {
|
|
13
|
+
input: { type: "object", properties: {} },
|
|
14
|
+
output: { type: "object", properties: {} },
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
execute: async () => {
|
|
18
|
+
executionOrder.push("start");
|
|
19
|
+
return { variables: {}, portId: "true" };
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
const branchTrue = {
|
|
23
|
+
id: "branchTrue",
|
|
24
|
+
propertiesSchema: {
|
|
25
|
+
type: "object",
|
|
26
|
+
properties: {
|
|
27
|
+
input: { type: "object", properties: {} },
|
|
28
|
+
output: {
|
|
29
|
+
type: "object",
|
|
30
|
+
properties: { value: { type: "string", const: "fromTrue" } },
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
execute: async () => {
|
|
35
|
+
executionOrder.push("branchTrue");
|
|
36
|
+
return { variables: { value: "fromTrue" } };
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
const branchFalse = {
|
|
40
|
+
id: "branchFalse",
|
|
41
|
+
propertiesSchema: {
|
|
42
|
+
type: "object",
|
|
43
|
+
properties: {
|
|
44
|
+
input: { type: "object", properties: {} },
|
|
45
|
+
output: {
|
|
46
|
+
type: "object",
|
|
47
|
+
properties: { value: { type: "string", const: "fromFalse" } },
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
execute: async () => {
|
|
52
|
+
executionOrder.push("branchFalse");
|
|
53
|
+
return { variables: { value: "fromFalse" } };
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
const mergeNode = {
|
|
57
|
+
id: "merge",
|
|
58
|
+
propertiesSchema: {
|
|
59
|
+
type: "object",
|
|
60
|
+
properties: {
|
|
61
|
+
input: {
|
|
62
|
+
type: "object",
|
|
63
|
+
properties: {
|
|
64
|
+
trueValue: {
|
|
65
|
+
type: "string",
|
|
66
|
+
"x-expression": '$."branchTrue".value',
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
output: { type: "object", properties: { result: { type: "string", const: "" } } },
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
execute: async (params) => {
|
|
74
|
+
executionOrder.push("merge");
|
|
75
|
+
mergeInput = params.variables;
|
|
76
|
+
return { variables: { result: params.variables.trueValue || "none" } };
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
const endNode = {
|
|
80
|
+
id: "end",
|
|
81
|
+
propertiesSchema: {
|
|
82
|
+
type: "object",
|
|
83
|
+
properties: {
|
|
84
|
+
input: { type: "object", properties: {} },
|
|
85
|
+
output: { type: "object", properties: {} },
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
execute: async (params) => {
|
|
89
|
+
executionOrder.push("end");
|
|
90
|
+
return { variables: params.variables };
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
const workflowData = {
|
|
94
|
+
nodes: [startNode, branchTrue, branchFalse, mergeNode, endNode],
|
|
95
|
+
edges: [
|
|
96
|
+
{
|
|
97
|
+
sourceWorkflowNodeResourceId: "start",
|
|
98
|
+
sourceWorkflowNodePortId: "true",
|
|
99
|
+
targetWorkflowNodeResourceId: "branchTrue",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
sourceWorkflowNodeResourceId: "start",
|
|
103
|
+
sourceWorkflowNodePortId: "false",
|
|
104
|
+
targetWorkflowNodeResourceId: "branchFalse",
|
|
105
|
+
},
|
|
106
|
+
{ sourceWorkflowNodeResourceId: "branchTrue", targetWorkflowNodeResourceId: "merge" },
|
|
107
|
+
{ sourceWorkflowNodeResourceId: "branchFalse", targetWorkflowNodeResourceId: "merge" },
|
|
108
|
+
{ sourceWorkflowNodeResourceId: "merge", targetWorkflowNodeResourceId: "end" },
|
|
109
|
+
],
|
|
110
|
+
};
|
|
111
|
+
class TestWorkflowResource extends resource_1.WorkflowResource {
|
|
112
|
+
constructor() {
|
|
113
|
+
super(...arguments);
|
|
114
|
+
this.getWorkflowData = () => ({ workflowData });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const workflow = new TestWorkflowResource({
|
|
118
|
+
id: "test",
|
|
119
|
+
kind: "workflow",
|
|
120
|
+
parentId: "root",
|
|
121
|
+
settings: {
|
|
122
|
+
title: "",
|
|
123
|
+
description: "",
|
|
124
|
+
startWorkflowNodeResourceId: "start",
|
|
125
|
+
endWorkflowNodeResourceId: "end",
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
await workflow.execute({
|
|
129
|
+
context: {},
|
|
130
|
+
variables: {},
|
|
131
|
+
});
|
|
132
|
+
expect(executionOrder).toContain("start");
|
|
133
|
+
expect(executionOrder).toContain("branchTrue");
|
|
134
|
+
expect(executionOrder).not.toContain("branchFalse");
|
|
135
|
+
expect(executionOrder).toContain("merge");
|
|
136
|
+
expect(executionOrder).toContain("end");
|
|
137
|
+
const mergeIdx = executionOrder.indexOf("merge");
|
|
138
|
+
expect(executionOrder.indexOf("branchTrue")).toBeLessThan(mergeIdx);
|
|
139
|
+
expect(executionOrder.indexOf("end")).toBeGreaterThan(mergeIdx);
|
|
140
|
+
expect(mergeInput.trueValue).toBe("fromTrue");
|
|
141
|
+
});
|
|
142
|
+
test("diamond with extra direct edge: merge waits for longest path", async () => {
|
|
143
|
+
const executionOrder = [];
|
|
144
|
+
let mergeInput = null;
|
|
145
|
+
const makeNode = (id, value) => ({
|
|
146
|
+
id,
|
|
147
|
+
propertiesSchema: {
|
|
148
|
+
type: "object",
|
|
149
|
+
properties: {
|
|
150
|
+
input: { type: "object", properties: {} },
|
|
151
|
+
output: {
|
|
152
|
+
type: "object",
|
|
153
|
+
properties: { value: { type: "string", const: value } },
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
execute: async () => {
|
|
158
|
+
executionOrder.push(id);
|
|
159
|
+
return { variables: { value } };
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
const start = makeNode("start", "S");
|
|
163
|
+
const middle = makeNode("middle", "M");
|
|
164
|
+
const mergeNode = {
|
|
165
|
+
id: "merge",
|
|
166
|
+
propertiesSchema: {
|
|
167
|
+
type: "object",
|
|
168
|
+
properties: {
|
|
169
|
+
input: {
|
|
170
|
+
type: "object",
|
|
171
|
+
properties: {
|
|
172
|
+
startVal: { type: "string", "x-expression": '$."start".value' },
|
|
173
|
+
middleVal: { type: "string", "x-expression": '$."middle".value' },
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
output: { type: "object", properties: { result: { type: "string", const: "" } } },
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
execute: async (params) => {
|
|
180
|
+
executionOrder.push("merge");
|
|
181
|
+
mergeInput = params.variables;
|
|
182
|
+
return {
|
|
183
|
+
variables: { result: `${params.variables.startVal}-${params.variables.middleVal}` },
|
|
184
|
+
};
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
const end = makeNode("end", "E");
|
|
188
|
+
const workflowData = {
|
|
189
|
+
nodes: [start, middle, mergeNode, end],
|
|
190
|
+
edges: [
|
|
191
|
+
{ sourceWorkflowNodeResourceId: "start", targetWorkflowNodeResourceId: "middle" },
|
|
192
|
+
{ sourceWorkflowNodeResourceId: "start", targetWorkflowNodeResourceId: "merge" },
|
|
193
|
+
{ sourceWorkflowNodeResourceId: "middle", targetWorkflowNodeResourceId: "merge" },
|
|
194
|
+
{ sourceWorkflowNodeResourceId: "merge", targetWorkflowNodeResourceId: "end" },
|
|
195
|
+
],
|
|
196
|
+
};
|
|
197
|
+
class TestWorkflowResource extends resource_1.WorkflowResource {
|
|
198
|
+
constructor() {
|
|
199
|
+
super(...arguments);
|
|
200
|
+
this.getWorkflowData = () => ({ workflowData });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const workflow = new TestWorkflowResource({
|
|
204
|
+
id: "test",
|
|
205
|
+
kind: "workflow",
|
|
206
|
+
parentId: "root",
|
|
207
|
+
settings: {
|
|
208
|
+
title: "",
|
|
209
|
+
description: "",
|
|
210
|
+
startWorkflowNodeResourceId: "start",
|
|
211
|
+
endWorkflowNodeResourceId: "end",
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
await workflow.execute({
|
|
215
|
+
context: {},
|
|
216
|
+
variables: {},
|
|
217
|
+
});
|
|
218
|
+
const startIdx = executionOrder.indexOf("start");
|
|
219
|
+
const middleIdx = executionOrder.indexOf("middle");
|
|
220
|
+
const mergeIdx = executionOrder.indexOf("merge");
|
|
221
|
+
const endIdx = executionOrder.indexOf("end");
|
|
222
|
+
expect(startIdx).toBe(0);
|
|
223
|
+
expect(middleIdx).toBeGreaterThan(startIdx);
|
|
224
|
+
expect(mergeIdx).toBeGreaterThan(middleIdx);
|
|
225
|
+
expect(endIdx).toBeGreaterThan(mergeIdx);
|
|
226
|
+
expect(mergeInput.startVal).toBe("S");
|
|
227
|
+
expect(mergeInput.middleVal).toBe("M");
|
|
228
|
+
});
|
|
229
|
+
test("merge node with two parallel predecessors runs after both complete (diamond pattern)", async () => {
|
|
230
|
+
const executionOrder = [];
|
|
231
|
+
let mergeInput = null;
|
|
232
|
+
const makeNode = (id, outputValue) => ({
|
|
233
|
+
id,
|
|
234
|
+
propertiesSchema: {
|
|
235
|
+
type: "object",
|
|
236
|
+
properties: {
|
|
237
|
+
input: { type: "object", properties: {} },
|
|
238
|
+
output: {
|
|
239
|
+
type: "object",
|
|
240
|
+
properties: {
|
|
241
|
+
value: { type: "string", const: outputValue },
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
execute: async () => {
|
|
247
|
+
executionOrder.push(id);
|
|
248
|
+
return { variables: { value: outputValue } };
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
const start = makeNode("start", "start");
|
|
252
|
+
const branchA = makeNode("branchA", "A");
|
|
253
|
+
const branchB = makeNode("branchB", "B");
|
|
254
|
+
const mergeNode = {
|
|
255
|
+
id: "merge",
|
|
256
|
+
propertiesSchema: {
|
|
257
|
+
type: "object",
|
|
258
|
+
properties: {
|
|
259
|
+
input: {
|
|
260
|
+
type: "object",
|
|
261
|
+
properties: {
|
|
262
|
+
aValue: { type: "string", "x-expression": '$."branchA".value' },
|
|
263
|
+
bValue: { type: "string", "x-expression": '$."branchB".value' },
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
output: {
|
|
267
|
+
type: "object",
|
|
268
|
+
properties: {
|
|
269
|
+
result: { type: "string", const: "" },
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
execute: async (params) => {
|
|
275
|
+
executionOrder.push("merge");
|
|
276
|
+
mergeInput = params.variables;
|
|
277
|
+
return { variables: { result: `${params.variables.aValue}+${params.variables.bValue}` } };
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
const endNode = {
|
|
281
|
+
id: "end",
|
|
282
|
+
propertiesSchema: {
|
|
283
|
+
type: "object",
|
|
284
|
+
properties: {
|
|
285
|
+
input: { type: "object", properties: {} },
|
|
286
|
+
output: { type: "object", properties: {} },
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
execute: async (params) => {
|
|
290
|
+
executionOrder.push("end");
|
|
291
|
+
return { variables: params.variables };
|
|
292
|
+
},
|
|
293
|
+
};
|
|
294
|
+
const workflowData = {
|
|
295
|
+
nodes: [start, branchA, branchB, mergeNode, endNode],
|
|
296
|
+
edges: [
|
|
297
|
+
{ sourceWorkflowNodeResourceId: "start", targetWorkflowNodeResourceId: "branchA" },
|
|
298
|
+
{ sourceWorkflowNodeResourceId: "start", targetWorkflowNodeResourceId: "branchB" },
|
|
299
|
+
{ sourceWorkflowNodeResourceId: "branchA", targetWorkflowNodeResourceId: "merge" },
|
|
300
|
+
{ sourceWorkflowNodeResourceId: "branchB", targetWorkflowNodeResourceId: "merge" },
|
|
301
|
+
{ sourceWorkflowNodeResourceId: "merge", targetWorkflowNodeResourceId: "end" },
|
|
302
|
+
],
|
|
303
|
+
};
|
|
304
|
+
class TestWorkflowResource extends resource_1.WorkflowResource {
|
|
305
|
+
constructor() {
|
|
306
|
+
super(...arguments);
|
|
307
|
+
this.getWorkflowData = () => ({ workflowData });
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
const workflow = new TestWorkflowResource({
|
|
311
|
+
id: "test",
|
|
312
|
+
kind: "workflow",
|
|
313
|
+
parentId: "root",
|
|
314
|
+
settings: {
|
|
315
|
+
title: "",
|
|
316
|
+
description: "",
|
|
317
|
+
startWorkflowNodeResourceId: "start",
|
|
318
|
+
endWorkflowNodeResourceId: "end",
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
await workflow.execute({
|
|
322
|
+
context: {},
|
|
323
|
+
variables: {},
|
|
324
|
+
});
|
|
325
|
+
const startIdx = executionOrder.indexOf("start");
|
|
326
|
+
const aIdx = executionOrder.indexOf("branchA");
|
|
327
|
+
const bIdx = executionOrder.indexOf("branchB");
|
|
328
|
+
const mergeIdx = executionOrder.indexOf("merge");
|
|
329
|
+
const endIdx = executionOrder.indexOf("end");
|
|
330
|
+
expect(startIdx).toBe(0);
|
|
331
|
+
expect(aIdx).toBeGreaterThan(startIdx);
|
|
332
|
+
expect(bIdx).toBeGreaterThan(startIdx);
|
|
333
|
+
expect(mergeIdx).toBeGreaterThan(aIdx);
|
|
334
|
+
expect(mergeIdx).toBeGreaterThan(bIdx);
|
|
335
|
+
expect(endIdx).toBeGreaterThan(mergeIdx);
|
|
336
|
+
expect(mergeInput.aValue).toBe("A");
|
|
337
|
+
expect(mergeInput.bValue).toBe("B");
|
|
338
|
+
});
|
|
339
|
+
test("merge node with three parallel predecessors runs after all three complete", async () => {
|
|
340
|
+
const executionOrder = [];
|
|
341
|
+
let mergeInput = null;
|
|
342
|
+
const makeNode = (id, outputValue) => ({
|
|
343
|
+
id,
|
|
344
|
+
propertiesSchema: {
|
|
345
|
+
type: "object",
|
|
346
|
+
properties: {
|
|
347
|
+
input: { type: "object", properties: {} },
|
|
348
|
+
output: {
|
|
349
|
+
type: "object",
|
|
350
|
+
properties: {
|
|
351
|
+
value: { type: "number", const: outputValue },
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
execute: async () => {
|
|
357
|
+
executionOrder.push(id);
|
|
358
|
+
return { variables: { value: outputValue } };
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
const start = makeNode("start", 0);
|
|
362
|
+
const branchA = makeNode("branchA", 10);
|
|
363
|
+
const branchB = makeNode("branchB", 20);
|
|
364
|
+
const branchC = makeNode("branchC", 30);
|
|
365
|
+
const mergeNode = {
|
|
366
|
+
id: "merge",
|
|
367
|
+
propertiesSchema: {
|
|
368
|
+
type: "object",
|
|
369
|
+
properties: {
|
|
370
|
+
input: {
|
|
371
|
+
type: "object",
|
|
372
|
+
properties: {
|
|
373
|
+
a: { type: "number", "x-expression": '$."branchA".value' },
|
|
374
|
+
b: { type: "number", "x-expression": '$."branchB".value' },
|
|
375
|
+
c: { type: "number", "x-expression": '$."branchC".value' },
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
output: {
|
|
379
|
+
type: "object",
|
|
380
|
+
properties: {
|
|
381
|
+
total: { type: "number", const: 0 },
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
execute: async (params) => {
|
|
387
|
+
executionOrder.push("merge");
|
|
388
|
+
mergeInput = params.variables;
|
|
389
|
+
return {
|
|
390
|
+
variables: { total: params.variables.a + params.variables.b + params.variables.c },
|
|
391
|
+
};
|
|
392
|
+
},
|
|
393
|
+
};
|
|
394
|
+
const endNode = {
|
|
395
|
+
id: "end",
|
|
396
|
+
propertiesSchema: {
|
|
397
|
+
type: "object",
|
|
398
|
+
properties: {
|
|
399
|
+
input: { type: "object", properties: {} },
|
|
400
|
+
output: { type: "object", properties: {} },
|
|
401
|
+
},
|
|
402
|
+
},
|
|
403
|
+
execute: async (params) => {
|
|
404
|
+
executionOrder.push("end");
|
|
405
|
+
return { variables: params.variables };
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
const workflowData = {
|
|
409
|
+
nodes: [start, branchA, branchB, branchC, mergeNode, endNode],
|
|
410
|
+
edges: [
|
|
411
|
+
{ sourceWorkflowNodeResourceId: "start", targetWorkflowNodeResourceId: "branchA" },
|
|
412
|
+
{ sourceWorkflowNodeResourceId: "start", targetWorkflowNodeResourceId: "branchB" },
|
|
413
|
+
{ sourceWorkflowNodeResourceId: "start", targetWorkflowNodeResourceId: "branchC" },
|
|
414
|
+
{ sourceWorkflowNodeResourceId: "branchA", targetWorkflowNodeResourceId: "merge" },
|
|
415
|
+
{ sourceWorkflowNodeResourceId: "branchB", targetWorkflowNodeResourceId: "merge" },
|
|
416
|
+
{ sourceWorkflowNodeResourceId: "branchC", targetWorkflowNodeResourceId: "merge" },
|
|
417
|
+
{ sourceWorkflowNodeResourceId: "merge", targetWorkflowNodeResourceId: "end" },
|
|
418
|
+
],
|
|
419
|
+
};
|
|
420
|
+
class TestWorkflowResource extends resource_1.WorkflowResource {
|
|
421
|
+
constructor() {
|
|
422
|
+
super(...arguments);
|
|
423
|
+
this.getWorkflowData = () => ({ workflowData });
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
const workflow = new TestWorkflowResource({
|
|
427
|
+
id: "test",
|
|
428
|
+
kind: "workflow",
|
|
429
|
+
parentId: "root",
|
|
430
|
+
settings: {
|
|
431
|
+
title: "",
|
|
432
|
+
description: "",
|
|
433
|
+
startWorkflowNodeResourceId: "start",
|
|
434
|
+
endWorkflowNodeResourceId: "end",
|
|
435
|
+
},
|
|
436
|
+
});
|
|
437
|
+
await workflow.execute({
|
|
438
|
+
context: {},
|
|
439
|
+
variables: {},
|
|
440
|
+
});
|
|
441
|
+
const mergeIdx = executionOrder.indexOf("merge");
|
|
442
|
+
expect(executionOrder.indexOf("branchA")).toBeLessThan(mergeIdx);
|
|
443
|
+
expect(executionOrder.indexOf("branchB")).toBeLessThan(mergeIdx);
|
|
444
|
+
expect(executionOrder.indexOf("branchC")).toBeLessThan(mergeIdx);
|
|
445
|
+
expect(executionOrder.indexOf("end")).toBeGreaterThan(mergeIdx);
|
|
446
|
+
expect(mergeInput.a).toBe(10);
|
|
447
|
+
expect(mergeInput.b).toBe(20);
|
|
448
|
+
expect(mergeInput.c).toBe(30);
|
|
449
|
+
});
|
|
450
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"execute-workflow.d.ts","sourceRoot":"","sources":["../../src/utils/execute-workflow.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EAElB,MAAM,UAAU,CAAC;AAqBlB,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,GAAG,CAAC;IACb,yBAAyB,EAAE,MAAM,CAAC;IAClC,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC;IAC7B,uBAAuB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAC5D,2BAA2B,EAAE,MAAM,CAAC;IACpC,SAAS,EAAE,GAAG,CAAC;IACf,YAAY,EAAE,YAAY,CAAC;IAC3B,YAAY,CAAC,EAAE,GAAG,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,GAAG,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;
|
|
1
|
+
{"version":3,"file":"execute-workflow.d.ts","sourceRoot":"","sources":["../../src/utils/execute-workflow.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EAElB,MAAM,UAAU,CAAC;AAqBlB,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,GAAG,CAAC;IACb,yBAAyB,EAAE,MAAM,CAAC;IAClC,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC;IAC7B,uBAAuB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAC5D,2BAA2B,EAAE,MAAM,CAAC;IACpC,SAAS,EAAE,GAAG,CAAC;IACf,YAAY,EAAE,YAAY,CAAC;IAC3B,YAAY,CAAC,EAAE,GAAG,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,GAAG,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAgSD;;GAEG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAuJhG"}
|
|
@@ -58,6 +58,8 @@ async function executeNode(node, context, workflowVariablesMap, isFirstLevel, wo
|
|
|
58
58
|
}
|
|
59
59
|
let consoleDirInfo = {
|
|
60
60
|
nodeId: id,
|
|
61
|
+
inputSchema: workflowNodePropertiesInputSchema,
|
|
62
|
+
inputExpressionEvaluateInput: workflowNodeInputExpressionEvaluateInput,
|
|
61
63
|
inputVariables: workflowNodeInputVariables,
|
|
62
64
|
status: "processing",
|
|
63
65
|
};
|
|
@@ -82,6 +84,8 @@ async function executeNode(node, context, workflowVariablesMap, isFirstLevel, wo
|
|
|
82
84
|
...consoleDirInfo,
|
|
83
85
|
status: "success",
|
|
84
86
|
portId,
|
|
87
|
+
outputSchema: workflowNodePropertiesOutputSchema,
|
|
88
|
+
outputExpressionEvaluateInput: workflowNodeOutputExpressionEvaluateInput,
|
|
85
89
|
outputVariables: workflowNodeOutputVariables,
|
|
86
90
|
};
|
|
87
91
|
const res = {
|
|
@@ -101,80 +105,114 @@ async function executeNode(node, context, workflowVariablesMap, isFirstLevel, wo
|
|
|
101
105
|
}
|
|
102
106
|
finally {
|
|
103
107
|
if (process.env.DEBUG) {
|
|
104
|
-
console.dir(consoleDirInfo
|
|
108
|
+
console.dir(consoleDirInfo, {
|
|
109
|
+
depth: 5,
|
|
110
|
+
});
|
|
105
111
|
}
|
|
106
112
|
}
|
|
107
113
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
function findNextNodeIds(levelResults, edges, visitedNodes, completedNodePredecessors, activatedEdges, endNodeId) {
|
|
114
|
+
function findNextNodeIds(params) {
|
|
115
|
+
var _a;
|
|
116
|
+
const { levelResults, deadNodes, visitedNodes, completedNodePredecessors, pendingPredecessorCount, activatedEdges, requiredPredecessorCount, outgoingEdgesMap, incomingEdgesMap, endNodeId, } = params;
|
|
112
117
|
const nextNodeIds = [];
|
|
113
118
|
const nextLevelNodesSet = new Set();
|
|
114
119
|
let workflowOutputVariables;
|
|
120
|
+
const newlyAffectedTargets = new Set();
|
|
115
121
|
levelResults.forEach((result) => {
|
|
122
|
+
var _a;
|
|
116
123
|
if (!result) {
|
|
117
124
|
return;
|
|
118
125
|
}
|
|
119
126
|
const { nodeId, variables, portId } = result;
|
|
120
|
-
// 标记节点为已访问
|
|
121
127
|
visitedNodes.add(nodeId);
|
|
122
|
-
// 保存变量
|
|
123
|
-
// workflowVariablesMap.set(nodeId, variables);
|
|
124
|
-
// 如果是结束节点,保存输出变量
|
|
125
128
|
if (nodeId === endNodeId) {
|
|
126
129
|
workflowOutputVariables = variables;
|
|
127
130
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
+
const allOutgoing = (_a = outgoingEdgesMap.get(nodeId)) !== null && _a !== void 0 ? _a : [];
|
|
132
|
+
allOutgoing.forEach((edge) => {
|
|
133
|
+
var _a;
|
|
134
|
+
const targetNodeId = edge.targetWorkflowNodeResourceId;
|
|
135
|
+
if (visitedNodes.has(targetNodeId) || deadNodes.has(targetNodeId)) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const pending = ((_a = pendingPredecessorCount.get(targetNodeId)) !== null && _a !== void 0 ? _a : 0) - 1;
|
|
139
|
+
pendingPredecessorCount.set(targetNodeId, pending);
|
|
140
|
+
newlyAffectedTargets.add(targetNodeId);
|
|
141
|
+
});
|
|
131
142
|
const matchedEdges = portId
|
|
132
|
-
?
|
|
133
|
-
:
|
|
134
|
-
// 处理匹配的连线的目标节点
|
|
143
|
+
? allOutgoing.filter((edge) => edge.sourceWorkflowNodePortId === portId)
|
|
144
|
+
: allOutgoing.filter((edge) => !edge.sourceWorkflowNodePortId);
|
|
135
145
|
matchedEdges.forEach((edge) => {
|
|
146
|
+
var _a;
|
|
136
147
|
const targetNodeId = edge.targetWorkflowNodeResourceId;
|
|
137
|
-
|
|
138
|
-
if (visitedNodes.has(targetNodeId)) {
|
|
148
|
+
if (visitedNodes.has(targetNodeId) || deadNodes.has(targetNodeId)) {
|
|
139
149
|
return;
|
|
140
150
|
}
|
|
141
|
-
// 记录该目标节点已完成的前驱节点
|
|
142
151
|
if (!completedNodePredecessors.has(targetNodeId)) {
|
|
143
152
|
completedNodePredecessors.set(targetNodeId, new Set());
|
|
144
153
|
}
|
|
145
154
|
completedNodePredecessors.get(targetNodeId).add(nodeId);
|
|
146
|
-
// 记录激活的边(源节点 -> 目标节点)
|
|
147
155
|
if (!activatedEdges.has(targetNodeId)) {
|
|
148
156
|
activatedEdges.set(targetNodeId, new Set());
|
|
149
157
|
}
|
|
150
|
-
activatedEdges.get(targetNodeId)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
158
|
+
const targetActivatedEdges = activatedEdges.get(targetNodeId);
|
|
159
|
+
if (!targetActivatedEdges.has(nodeId)) {
|
|
160
|
+
targetActivatedEdges.add(nodeId);
|
|
161
|
+
requiredPredecessorCount.set(targetNodeId, ((_a = requiredPredecessorCount.get(targetNodeId)) !== null && _a !== void 0 ? _a : 0) + 1);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
const propagationQueue = [];
|
|
166
|
+
newlyAffectedTargets.forEach((targetId) => {
|
|
167
|
+
var _a;
|
|
168
|
+
const pending = (_a = pendingPredecessorCount.get(targetId)) !== null && _a !== void 0 ? _a : 0;
|
|
169
|
+
if (pending !== 0) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const targetActivated = activatedEdges.get(targetId);
|
|
173
|
+
if (targetActivated && targetActivated.size > 0) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
deadNodes.add(targetId);
|
|
177
|
+
propagationQueue.push(targetId);
|
|
178
|
+
});
|
|
179
|
+
while (propagationQueue.length > 0) {
|
|
180
|
+
const deadNodeId = propagationQueue.shift();
|
|
181
|
+
const outgoing = (_a = outgoingEdgesMap.get(deadNodeId)) !== null && _a !== void 0 ? _a : [];
|
|
182
|
+
outgoing.forEach((edge) => {
|
|
183
|
+
var _a;
|
|
184
|
+
const targetId = edge.targetWorkflowNodeResourceId;
|
|
185
|
+
if (visitedNodes.has(targetId) || deadNodes.has(targetId)) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const pending = ((_a = pendingPredecessorCount.get(targetId)) !== null && _a !== void 0 ? _a : 0) - 1;
|
|
189
|
+
pendingPredecessorCount.set(targetId, pending);
|
|
190
|
+
if (pending === 0) {
|
|
191
|
+
const targetActivated = activatedEdges.get(targetId);
|
|
192
|
+
if (!targetActivated || targetActivated.size === 0) {
|
|
193
|
+
deadNodes.add(targetId);
|
|
194
|
+
propagationQueue.push(targetId);
|
|
175
195
|
}
|
|
176
196
|
}
|
|
177
197
|
});
|
|
198
|
+
}
|
|
199
|
+
newlyAffectedTargets.forEach((targetNodeId) => {
|
|
200
|
+
var _a, _b;
|
|
201
|
+
if (visitedNodes.has(targetNodeId) || deadNodes.has(targetNodeId)) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const pending = (_a = pendingPredecessorCount.get(targetNodeId)) !== null && _a !== void 0 ? _a : 0;
|
|
205
|
+
if (pending > 0) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const completed = completedNodePredecessors.get(targetNodeId).size;
|
|
209
|
+
const required = (_b = requiredPredecessorCount.get(targetNodeId)) !== null && _b !== void 0 ? _b : 0;
|
|
210
|
+
if (completed === required) {
|
|
211
|
+
if (!nextLevelNodesSet.has(targetNodeId)) {
|
|
212
|
+
nextLevelNodesSet.add(targetNodeId);
|
|
213
|
+
nextNodeIds.push(targetNodeId);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
178
216
|
});
|
|
179
217
|
return { nextNodeIds, workflowOutputVariables };
|
|
180
218
|
}
|
|
@@ -193,12 +231,30 @@ async function executeWorkflow(params) {
|
|
|
193
231
|
throw new Error("invalid workflow nodes");
|
|
194
232
|
}
|
|
195
233
|
let workflowOutputVariables;
|
|
234
|
+
const outgoingEdgesMap = new Map();
|
|
235
|
+
const incomingEdgesMap = new Map();
|
|
236
|
+
edges.forEach((edge) => {
|
|
237
|
+
if (!outgoingEdgesMap.has(edge.sourceWorkflowNodeResourceId)) {
|
|
238
|
+
outgoingEdgesMap.set(edge.sourceWorkflowNodeResourceId, []);
|
|
239
|
+
}
|
|
240
|
+
outgoingEdgesMap.get(edge.sourceWorkflowNodeResourceId).push(edge);
|
|
241
|
+
if (!incomingEdgesMap.has(edge.targetWorkflowNodeResourceId)) {
|
|
242
|
+
incomingEdgesMap.set(edge.targetWorkflowNodeResourceId, []);
|
|
243
|
+
}
|
|
244
|
+
incomingEdgesMap.get(edge.targetWorkflowNodeResourceId).push(edge);
|
|
245
|
+
});
|
|
246
|
+
const pendingPredecessorCount = new Map();
|
|
247
|
+
incomingEdgesMap.forEach((incoming, nodeId) => {
|
|
248
|
+
pendingPredecessorCount.set(nodeId, incoming.length);
|
|
249
|
+
});
|
|
196
250
|
// 使用动态执行,根据 portId 决定下一步执行哪些节点
|
|
197
251
|
let currentNodeIds = [startNode.id];
|
|
198
252
|
const visitedNodes = new Set();
|
|
253
|
+
const deadNodes = new Set();
|
|
199
254
|
const completedNodePredecessors = new Map();
|
|
200
255
|
// 记录每个节点被激活的入边(源节点ID -> 目标节点ID的集合)
|
|
201
256
|
const activatedEdges = new Map();
|
|
257
|
+
const requiredPredecessorCount = new Map();
|
|
202
258
|
let isFirstLevel = true;
|
|
203
259
|
while (currentNodeIds.length > 0) {
|
|
204
260
|
// 去重当前节点集合(避免重复执行)
|
|
@@ -240,7 +296,19 @@ async function executeWorkflow(params) {
|
|
|
240
296
|
});
|
|
241
297
|
}
|
|
242
298
|
// 根据执行结果和 portId 查找下一个节点集合
|
|
243
|
-
const { nextNodeIds, workflowOutputVariables: tempOutputVariables } = findNextNodeIds(
|
|
299
|
+
const { nextNodeIds, workflowOutputVariables: tempOutputVariables } = findNextNodeIds({
|
|
300
|
+
levelResults,
|
|
301
|
+
edges,
|
|
302
|
+
visitedNodes,
|
|
303
|
+
deadNodes,
|
|
304
|
+
completedNodePredecessors,
|
|
305
|
+
pendingPredecessorCount,
|
|
306
|
+
activatedEdges,
|
|
307
|
+
requiredPredecessorCount,
|
|
308
|
+
outgoingEdgesMap,
|
|
309
|
+
incomingEdgesMap,
|
|
310
|
+
endNodeId: endNode.id,
|
|
311
|
+
});
|
|
244
312
|
if (tempOutputVariables !== undefined) {
|
|
245
313
|
workflowOutputVariables = tempOutputVariables;
|
|
246
314
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ldsg/workflow",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.35",
|
|
4
4
|
"description": "",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
"jsonata": "2.2.2",
|
|
21
21
|
"lodash": "^4.17.21",
|
|
22
22
|
"zod": "^4.1.11",
|
|
23
|
-
"@ldsg/constants": "0.3.
|
|
24
|
-
"@ldsg/
|
|
25
|
-
"@ldsg/
|
|
26
|
-
"@ldsg/resource": "0.3.
|
|
23
|
+
"@ldsg/constants": "0.3.35",
|
|
24
|
+
"@ldsg/handler": "0.3.35",
|
|
25
|
+
"@ldsg/resource": "0.3.35",
|
|
26
|
+
"@ldsg/resource-definition": "0.3.35"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/fs-extra": "^11.0.4",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"ts-jest": "^29.1.5",
|
|
39
39
|
"ts-node": "^10.9.2",
|
|
40
40
|
"typescript": "^5.8.3",
|
|
41
|
-
"@ldsg/types": "0.3.
|
|
41
|
+
"@ldsg/types": "0.3.35"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "tsc --project tsconfig.build.json",
|