@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.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=multi-predecessor.test.d.ts.map
@@ -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;AAyND;;GAEG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA6HhG"}
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
- const outgoingEdges = edges.filter((edge) => edge.sourceWorkflowNodeResourceId === nodeId);
130
- // 根据 portId 过滤连线
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
- ? outgoingEdges.filter((edge) => edge.sourceWorkflowNodePortId === portId)
133
- : outgoingEdges.filter((edge) => !edge.sourceWorkflowNodePortId);
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).add(nodeId);
151
- // 查找目标节点的所有可能的入边(用于计算需要等待多少个前驱节点)
152
- const incomingEdges = edges.filter((edge) => edge.targetWorkflowNodeResourceId === targetNodeId);
153
- // 计算实际需要等待的前驱节点数量
154
- // 对于每个前驱节点,检查是否有至少一条激活的入边
155
- const allPredecessorIds = new Set(incomingEdges.map((edge) => edge.sourceWorkflowNodeResourceId));
156
- let requiredPredecessorCount = 0;
157
- allPredecessorIds.forEach((predecessorId) => {
158
- // 检查该前驱节点是否有激活的入边指向当前节点
159
- const hasActivatedEdge = incomingEdges.some((e) => {
160
- var _a;
161
- return e.sourceWorkflowNodeResourceId === predecessorId &&
162
- ((_a = activatedEdges.get(targetNodeId)) === null || _a === void 0 ? void 0 : _a.has(predecessorId));
163
- });
164
- if (hasActivatedEdge) {
165
- requiredPredecessorCount++;
166
- }
167
- });
168
- // 只有当所有需要等待的前驱节点都完成时,才加入下一轮执行
169
- const completedPredecessors = completedNodePredecessors.get(targetNodeId);
170
- if (completedPredecessors.size === requiredPredecessorCount) {
171
- // 避免重复添加同一个节点到下一层
172
- if (!nextLevelNodesSet.has(targetNodeId)) {
173
- nextLevelNodesSet.add(targetNodeId);
174
- nextNodeIds.push(targetNodeId);
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(levelResults, edges, visitedNodes, completedNodePredecessors, activatedEdges, endNode.id);
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.33",
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.33",
24
- "@ldsg/resource-definition": "0.3.33",
25
- "@ldsg/handler": "0.3.33",
26
- "@ldsg/resource": "0.3.33"
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.33"
41
+ "@ldsg/types": "0.3.35"
42
42
  },
43
43
  "scripts": {
44
44
  "build": "tsc --project tsconfig.build.json",