flowcraft 2.0.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +51 -5
  2. package/dist/analysis.d.ts +1 -1
  3. package/dist/analysis.js +1 -1
  4. package/dist/{chunk-RYTIQZIB.js → chunk-3XVVR2SR.js} +167 -53
  5. package/dist/chunk-3XVVR2SR.js.map +1 -0
  6. package/dist/{chunk-6DNEDIIT.js → chunk-4A627Q6L.js} +47 -23
  7. package/dist/chunk-4A627Q6L.js.map +1 -0
  8. package/dist/{chunk-VFC342WL.js → chunk-4PELJWF7.js} +6 -6
  9. package/dist/chunk-4PELJWF7.js.map +1 -0
  10. package/dist/{chunk-WXT3YEWU.js → chunk-5ZXV3R5D.js} +2 -2
  11. package/dist/chunk-5ZXV3R5D.js.map +1 -0
  12. package/dist/{chunk-UYPIWXZG.js → chunk-CSZ6EOWG.js} +9 -10
  13. package/dist/chunk-CSZ6EOWG.js.map +1 -0
  14. package/dist/chunk-CYHZ2YVH.js +24 -0
  15. package/dist/chunk-CYHZ2YVH.js.map +1 -0
  16. package/dist/{chunk-J3RNCPED.js → chunk-DSYAC4WB.js} +2 -2
  17. package/dist/chunk-DSYAC4WB.js.map +1 -0
  18. package/dist/{chunk-M23P46ZL.js → chunk-HN72TZY5.js} +10 -5
  19. package/dist/chunk-HN72TZY5.js.map +1 -0
  20. package/dist/{chunk-MICPMOTW.js → chunk-KWQHFT7E.js} +2 -2
  21. package/dist/chunk-KWQHFT7E.js.map +1 -0
  22. package/dist/chunk-M2FRTT2K.js +144 -0
  23. package/dist/chunk-M2FRTT2K.js.map +1 -0
  24. package/dist/{chunk-RW4FH7IL.js → chunk-NBIRTKZ7.js} +89 -32
  25. package/dist/chunk-NBIRTKZ7.js.map +1 -0
  26. package/dist/chunk-O3XD45IL.js +236 -0
  27. package/dist/chunk-O3XD45IL.js.map +1 -0
  28. package/dist/chunk-PH2IYZHV.js +48 -0
  29. package/dist/chunk-PH2IYZHV.js.map +1 -0
  30. package/dist/{chunk-DSZSR7UE.js → chunk-U5V5O5MN.js} +11 -2
  31. package/dist/chunk-U5V5O5MN.js.map +1 -0
  32. package/dist/{chunk-RAZXOMZC.js → chunk-UETC63DP.js} +7 -6
  33. package/dist/chunk-UETC63DP.js.map +1 -0
  34. package/dist/context.d.ts +5 -5
  35. package/dist/context.js +1 -1
  36. package/dist/errors.js +1 -1
  37. package/dist/evaluator.d.ts +21 -13
  38. package/dist/evaluator.js +1 -1
  39. package/dist/flow.d.ts +6 -5
  40. package/dist/flow.js +2 -2
  41. package/dist/index.d.ts +2 -1
  42. package/dist/index.js +15 -15
  43. package/dist/linter.d.ts +1 -1
  44. package/dist/linter.js +2 -2
  45. package/dist/logger.d.ts +5 -5
  46. package/dist/logger.js +1 -1
  47. package/dist/node.d.ts +1 -1
  48. package/dist/node.js +1 -1
  49. package/dist/runtime/adapter.d.ts +24 -4
  50. package/dist/runtime/adapter.js +13 -13
  51. package/dist/runtime/executors.d.ts +7 -7
  52. package/dist/runtime/executors.js +2 -2
  53. package/dist/runtime/index.d.ts +1 -1
  54. package/dist/runtime/index.js +13 -13
  55. package/dist/runtime/runtime.d.ts +9 -7
  56. package/dist/runtime/runtime.js +12 -12
  57. package/dist/runtime/state.d.ts +2 -2
  58. package/dist/runtime/state.js +2 -2
  59. package/dist/runtime/traverser.d.ts +5 -3
  60. package/dist/runtime/traverser.js +3 -3
  61. package/dist/runtime/types.d.ts +2 -1
  62. package/dist/sanitizer.d.ts +1 -1
  63. package/dist/sanitizer.js +1 -1
  64. package/dist/serializer.d.ts +2 -1
  65. package/dist/serializer.js +1 -1
  66. package/dist/{types-CZN_FcB6.d.ts → types-CQCe_nBM.d.ts} +35 -22
  67. package/dist/types.d.ts +1 -1
  68. package/package.json +2 -2
  69. package/dist/chunk-6DNEDIIT.js.map +0 -1
  70. package/dist/chunk-734J4PTM.js +0 -100
  71. package/dist/chunk-734J4PTM.js.map +0 -1
  72. package/dist/chunk-DSZSR7UE.js.map +0 -1
  73. package/dist/chunk-GTZC6PQI.js +0 -22
  74. package/dist/chunk-GTZC6PQI.js.map +0 -1
  75. package/dist/chunk-J3RNCPED.js.map +0 -1
  76. package/dist/chunk-M23P46ZL.js.map +0 -1
  77. package/dist/chunk-MICPMOTW.js.map +0 -1
  78. package/dist/chunk-NPAJNLXQ.js +0 -106
  79. package/dist/chunk-NPAJNLXQ.js.map +0 -1
  80. package/dist/chunk-RAZXOMZC.js.map +0 -1
  81. package/dist/chunk-REH55ZXV.js +0 -13
  82. package/dist/chunk-REH55ZXV.js.map +0 -1
  83. package/dist/chunk-RW4FH7IL.js.map +0 -1
  84. package/dist/chunk-RYTIQZIB.js.map +0 -1
  85. package/dist/chunk-UYPIWXZG.js.map +0 -1
  86. package/dist/chunk-VFC342WL.js.map +0 -1
  87. package/dist/chunk-WXT3YEWU.js.map +0 -1
@@ -1,26 +1,29 @@
1
- import { CancelledWorkflowError } from './chunk-WXT3YEWU.js';
2
- import { analyzeBlueprint } from './chunk-M23P46ZL.js';
1
+ import { CancelledWorkflowError, NodeExecutionError } from './chunk-5ZXV3R5D.js';
2
+ import { analyzeBlueprint } from './chunk-HN72TZY5.js';
3
3
 
4
4
  // src/runtime/traverser.ts
5
5
  var GraphTraverser = class {
6
- constructor(blueprint, runtime, state, functionRegistry, executionId, signal) {
7
- this.blueprint = blueprint;
6
+ constructor(blueprint, runtime, state, functionRegistry, executionId, signal, concurrency) {
8
7
  this.runtime = runtime;
9
8
  this.state = state;
10
9
  this.functionRegistry = functionRegistry;
11
10
  this.executionId = executionId;
12
11
  this.signal = signal;
13
- this.dynamicBlueprint = JSON.parse(JSON.stringify(blueprint));
12
+ this.concurrency = concurrency;
13
+ this.dynamicBlueprint = structuredClone(blueprint);
14
14
  this.allPredecessors = /* @__PURE__ */ new Map();
15
- this.dynamicBlueprint.nodes.forEach((node) => this.allPredecessors.set(node.id, /* @__PURE__ */ new Set()));
16
- this.dynamicBlueprint.edges.forEach((edge) => this.allPredecessors.get(edge.target)?.add(edge.source));
15
+ for (const node of this.dynamicBlueprint.nodes) {
16
+ this.allPredecessors.set(node.id, /* @__PURE__ */ new Set());
17
+ }
18
+ for (const edge of this.dynamicBlueprint.edges) {
19
+ this.allPredecessors.get(edge.target)?.add(edge.source);
20
+ }
17
21
  const analysis = analyzeBlueprint(blueprint);
18
22
  this.frontier = new Set(analysis.startNodeIds.filter((id) => !this.isFallbackNode(id)));
19
23
  if (this.frontier.size === 0 && analysis.cycles.length > 0 && this.runtime.options.strict !== true) {
20
24
  const uniqueStartNodes = /* @__PURE__ */ new Set();
21
25
  for (const cycle of analysis.cycles) {
22
- if (cycle.length > 0)
23
- uniqueStartNodes.add(cycle[0]);
26
+ if (cycle.length > 0) uniqueStartNodes.add(cycle[0]);
24
27
  }
25
28
  this.frontier = new Set(uniqueStartNodes);
26
29
  }
@@ -31,6 +34,23 @@ var GraphTraverser = class {
31
34
  isFallbackNode(nodeId) {
32
35
  return this.dynamicBlueprint.nodes.some((n) => n.config?.fallback === nodeId);
33
36
  }
37
+ getEffectiveJoinStrategy(nodeId) {
38
+ const node = this.dynamicBlueprint.nodes.find((n) => n.id === nodeId);
39
+ const baseJoinStrategy = node?.config?.joinStrategy || "all";
40
+ if (node?.uses === "loop-controller") {
41
+ return "any";
42
+ }
43
+ const predecessors = this.allPredecessors.get(nodeId);
44
+ if (predecessors) {
45
+ for (const predecessorId of predecessors) {
46
+ const predecessorNode = this.dynamicBlueprint.nodes.find((n) => n.id === predecessorId);
47
+ if (predecessorNode?.uses === "loop-controller") {
48
+ return "any";
49
+ }
50
+ }
51
+ }
52
+ return baseJoinStrategy;
53
+ }
34
54
  async traverse() {
35
55
  try {
36
56
  this.signal?.throwIfAborted();
@@ -42,52 +62,49 @@ var GraphTraverser = class {
42
62
  let iterations = 0;
43
63
  const maxIterations = 1e4;
44
64
  while (this.frontier.size > 0) {
45
- if (++iterations > maxIterations)
46
- throw new Error("Traversal exceeded maximum iterations, possible infinite loop");
65
+ if (++iterations > maxIterations) throw new Error("Traversal exceeded maximum iterations, possible infinite loop");
47
66
  try {
48
67
  this.signal?.throwIfAborted();
49
68
  const currentJobs = Array.from(this.frontier);
50
69
  this.frontier.clear();
51
- const promises = currentJobs.map(
52
- (nodeId) => this.runtime.executeNode(this.dynamicBlueprint, nodeId, this.state, this.allPredecessors, this.functionRegistry, this.executionId, this.signal).then((result) => ({ status: "fulfilled", value: { nodeId, result } })).catch((error) => ({ status: "rejected", reason: { nodeId, error } }))
53
- );
54
- const settledResults = await Promise.all(promises);
70
+ const settledResults = await this.executeWithConcurrency(currentJobs);
55
71
  const completedThisTurn = /* @__PURE__ */ new Set();
56
72
  for (const promiseResult of settledResults) {
57
73
  if (promiseResult.status === "rejected") {
58
74
  const { nodeId: nodeId2, error } = promiseResult.reason;
59
- if (error instanceof CancelledWorkflowError)
60
- throw error;
75
+ if (error instanceof CancelledWorkflowError) throw error;
61
76
  this.state.addError(nodeId2, error);
62
77
  continue;
63
78
  }
64
79
  const { nodeId, result } = promiseResult.value;
65
80
  this.state.addCompletedNode(nodeId, result.output);
66
81
  completedThisTurn.add(nodeId);
67
- if (result._fallbackExecuted)
68
- this.state.markFallbackExecuted();
82
+ if (result._fallbackExecuted) this.state.markFallbackExecuted();
69
83
  await this.handleDynamicNodes(nodeId, result);
70
84
  if (!result._fallbackExecuted) {
71
- const matched = await this.runtime.determineNextNodes(this.dynamicBlueprint, nodeId, result, this.state.getContext());
85
+ const matched = await this.runtime.determineNextNodes(
86
+ this.dynamicBlueprint,
87
+ nodeId,
88
+ result,
89
+ this.state.getContext()
90
+ );
72
91
  const loopControllerMatch = matched.find((m) => m.node.uses === "loop-controller");
73
92
  const finalMatched = loopControllerMatch ? [loopControllerMatch] : matched;
74
93
  for (const { node, edge } of finalMatched) {
75
- const joinStrategy = node.config?.joinStrategy || "all";
76
- if (joinStrategy !== "any" && this.state.getCompletedNodes().has(node.id))
77
- continue;
94
+ const joinStrategy = this.getEffectiveJoinStrategy(node.id);
95
+ if (joinStrategy !== "any" && this.state.getCompletedNodes().has(node.id)) continue;
78
96
  await this.runtime.applyEdgeTransform(edge, result, node, this.state.getContext(), this.allPredecessors);
79
97
  const requiredPredecessors = this.allPredecessors.get(node.id);
98
+ if (!requiredPredecessors) continue;
80
99
  const isReady = joinStrategy === "any" ? [...requiredPredecessors].some((p) => completedThisTurn.has(p)) : [...requiredPredecessors].every((p) => this.state.getCompletedNodes().has(p));
81
- if (isReady)
82
- this.frontier.add(node.id);
100
+ if (isReady) this.frontier.add(node.id);
83
101
  }
84
102
  if (matched.length === 0) {
85
103
  for (const [potentialNextId, predecessors] of this.allPredecessors) {
86
104
  if (predecessors.has(nodeId) && !this.state.getCompletedNodes().has(potentialNextId)) {
87
- const joinStrategy = this.dynamicBlueprint.nodes.find((n) => n.id === potentialNextId)?.config?.joinStrategy || "all";
105
+ const joinStrategy = this.getEffectiveJoinStrategy(potentialNextId);
88
106
  const isReady = joinStrategy === "any" ? [...predecessors].some((p) => completedThisTurn.has(p)) : [...predecessors].every((p) => this.state.getCompletedNodes().has(p));
89
- if (isReady)
90
- this.frontier.add(potentialNextId);
107
+ if (isReady) this.frontier.add(potentialNextId);
91
108
  }
92
109
  }
93
110
  }
@@ -101,10 +118,51 @@ var GraphTraverser = class {
101
118
  }
102
119
  }
103
120
  }
121
+ async executeWithConcurrency(nodeIds) {
122
+ const maxConcurrency = this.concurrency || nodeIds.length;
123
+ const results = [];
124
+ for (let i = 0; i < nodeIds.length; i += maxConcurrency) {
125
+ const batch = nodeIds.slice(i, i + maxConcurrency);
126
+ const batchPromises = batch.map(async (nodeId) => {
127
+ try {
128
+ const result = await this.runtime.executeNode(
129
+ this.dynamicBlueprint,
130
+ nodeId,
131
+ this.state,
132
+ this.allPredecessors,
133
+ this.functionRegistry,
134
+ this.executionId,
135
+ this.signal
136
+ );
137
+ results.push({
138
+ status: "fulfilled",
139
+ value: { nodeId, result }
140
+ });
141
+ } catch (error) {
142
+ results.push({
143
+ status: "rejected",
144
+ reason: { nodeId, error }
145
+ });
146
+ }
147
+ });
148
+ await Promise.all(batchPromises);
149
+ }
150
+ return results;
151
+ }
104
152
  async handleDynamicNodes(nodeId, result) {
105
153
  if (result.dynamicNodes && result.dynamicNodes.length > 0) {
106
154
  const gatherNodeId = result.output?.gatherNodeId;
107
155
  for (const dynamicNode of result.dynamicNodes) {
156
+ const implementation = this.functionRegistry?.get(dynamicNode.uses) || this.runtime.registry[dynamicNode.uses];
157
+ if (!implementation) {
158
+ throw new NodeExecutionError(
159
+ `Implementation for '${dynamicNode.uses}' not found for dynamic node '${dynamicNode.id}' generated by node '${nodeId}'.`,
160
+ dynamicNode.id,
161
+ this.dynamicBlueprint.id,
162
+ void 0,
163
+ this.executionId
164
+ );
165
+ }
108
166
  this.dynamicBlueprint.nodes.push(dynamicNode);
109
167
  this.allPredecessors.set(dynamicNode.id, /* @__PURE__ */ new Set([nodeId]));
110
168
  if (gatherNodeId) {
@@ -120,8 +178,7 @@ var GraphTraverser = class {
120
178
  getFallbackNodeIds() {
121
179
  const fallbackNodeIds = /* @__PURE__ */ new Set();
122
180
  for (const node of this.dynamicBlueprint.nodes) {
123
- if (node.config?.fallback)
124
- fallbackNodeIds.add(node.config.fallback);
181
+ if (node.config?.fallback) fallbackNodeIds.add(node.config.fallback);
125
182
  }
126
183
  return fallbackNodeIds;
127
184
  }
@@ -131,5 +188,5 @@ var GraphTraverser = class {
131
188
  };
132
189
 
133
190
  export { GraphTraverser };
134
- //# sourceMappingURL=chunk-RW4FH7IL.js.map
135
- //# sourceMappingURL=chunk-RW4FH7IL.js.map
191
+ //# sourceMappingURL=chunk-NBIRTKZ7.js.map
192
+ //# sourceMappingURL=chunk-NBIRTKZ7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/runtime/traverser.ts"],"names":["nodeId"],"mappings":"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","file":"chunk-NBIRTKZ7.js","sourcesContent":["import { analyzeBlueprint } from '../analysis'\nimport { CancelledWorkflowError, NodeExecutionError } from '../errors'\nimport type { NodeResult, WorkflowBlueprint } from '../types'\nimport type { WorkflowState } from './state'\nimport type { IRuntime } from './types'\n\nexport class GraphTraverser<TContext extends Record<string, any>, TDependencies extends Record<string, any>> {\n\tprivate frontier = new Set<string>()\n\tprivate allPredecessors: Map<string, Set<string>>\n\tprivate dynamicBlueprint: WorkflowBlueprint\n\n\tconstructor(\n\t\tblueprint: WorkflowBlueprint,\n\t\tprivate runtime: IRuntime<TContext, TDependencies>,\n\t\tprivate state: WorkflowState<TContext>,\n\t\tprivate functionRegistry: Map<string, any> | undefined,\n\t\tprivate executionId: string,\n\t\tprivate signal?: AbortSignal,\n\t\tprivate concurrency?: number,\n\t) {\n\t\tthis.dynamicBlueprint = structuredClone(blueprint) as WorkflowBlueprint\n\t\tthis.allPredecessors = new Map<string, Set<string>>()\n\t\tfor (const node of this.dynamicBlueprint.nodes) {\n\t\t\tthis.allPredecessors.set(node.id, new Set())\n\t\t}\n\t\tfor (const edge of this.dynamicBlueprint.edges) {\n\t\t\tthis.allPredecessors.get(edge.target)?.add(edge.source)\n\t\t}\n\t\tconst analysis = analyzeBlueprint(blueprint)\n\t\tthis.frontier = new Set(analysis.startNodeIds.filter((id) => !this.isFallbackNode(id)))\n\t\tif (this.frontier.size === 0 && analysis.cycles.length > 0 && this.runtime.options.strict !== true) {\n\t\t\tconst uniqueStartNodes = new Set<string>()\n\t\t\tfor (const cycle of analysis.cycles) {\n\t\t\t\tif (cycle.length > 0) uniqueStartNodes.add(cycle[0])\n\t\t\t}\n\t\t\tthis.frontier = new Set(uniqueStartNodes)\n\t\t}\n\t}\n\n\tprivate isFallbackNode(nodeId: string): boolean {\n\t\treturn this.dynamicBlueprint.nodes.some((n) => n.config?.fallback === nodeId)\n\t}\n\n\tprivate getEffectiveJoinStrategy(nodeId: string): 'any' | 'all' {\n\t\tconst node = this.dynamicBlueprint.nodes.find((n) => n.id === nodeId)\n\t\tconst baseJoinStrategy = node?.config?.joinStrategy || 'all'\n\n\t\tif (node?.uses === 'loop-controller') {\n\t\t\treturn 'any'\n\t\t}\n\n\t\tconst predecessors = this.allPredecessors.get(nodeId)\n\t\tif (predecessors) {\n\t\t\tfor (const predecessorId of predecessors) {\n\t\t\t\tconst predecessorNode = this.dynamicBlueprint.nodes.find((n) => n.id === predecessorId)\n\t\t\t\tif (predecessorNode?.uses === 'loop-controller') {\n\t\t\t\t\treturn 'any'\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn baseJoinStrategy\n\t}\n\n\tasync traverse(): Promise<void> {\n\t\ttry {\n\t\t\tthis.signal?.throwIfAborted()\n\t\t} catch (error) {\n\t\t\tif (error instanceof DOMException && error.name === 'AbortError')\n\t\t\t\tthrow new CancelledWorkflowError('Workflow cancelled')\n\t\t\tthrow error\n\t\t}\n\t\tlet iterations = 0\n\t\tconst maxIterations = 10000\n\t\twhile (this.frontier.size > 0) {\n\t\t\tif (++iterations > maxIterations) throw new Error('Traversal exceeded maximum iterations, possible infinite loop')\n\n\t\t\ttry {\n\t\t\t\tthis.signal?.throwIfAborted()\n\t\t\t\tconst currentJobs = Array.from(this.frontier)\n\t\t\t\tthis.frontier.clear()\n\t\t\t\tconst settledResults = await this.executeWithConcurrency(currentJobs)\n\t\t\t\tconst completedThisTurn = new Set<string>()\n\t\t\t\tfor (const promiseResult of settledResults) {\n\t\t\t\t\tif (promiseResult.status === 'rejected') {\n\t\t\t\t\t\tconst { nodeId, error } = promiseResult.reason\n\t\t\t\t\t\tif (error instanceof CancelledWorkflowError) throw error\n\t\t\t\t\t\tthis.state.addError(nodeId, error as Error)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tconst { nodeId, result } = promiseResult.value\n\t\t\t\t\tthis.state.addCompletedNode(nodeId, result.output)\n\t\t\t\t\tcompletedThisTurn.add(nodeId)\n\t\t\t\t\tif (result._fallbackExecuted) this.state.markFallbackExecuted()\n\t\t\t\t\tawait this.handleDynamicNodes(nodeId, result)\n\t\t\t\t\tif (!result._fallbackExecuted) {\n\t\t\t\t\t\tconst matched = await this.runtime.determineNextNodes(\n\t\t\t\t\t\t\tthis.dynamicBlueprint,\n\t\t\t\t\t\t\tnodeId,\n\t\t\t\t\t\t\tresult,\n\t\t\t\t\t\t\tthis.state.getContext(),\n\t\t\t\t\t\t)\n\n\t\t\t\t\t\t// if one of the next nodes is a loop controller, prioritize it to avoid ambiguity from manual cycle edges.\n\t\t\t\t\t\tconst loopControllerMatch = matched.find((m) => m.node.uses === 'loop-controller')\n\t\t\t\t\t\tconst finalMatched = loopControllerMatch ? [loopControllerMatch] : matched\n\n\t\t\t\t\t\tfor (const { node, edge } of finalMatched) {\n\t\t\t\t\t\t\tconst joinStrategy = this.getEffectiveJoinStrategy(node.id)\n\t\t\t\t\t\t\tif (joinStrategy !== 'any' && this.state.getCompletedNodes().has(node.id)) continue\n\t\t\t\t\t\t\tawait this.runtime.applyEdgeTransform(edge, result, node, this.state.getContext(), this.allPredecessors)\n\t\t\t\t\t\t\tconst requiredPredecessors = this.allPredecessors.get(node.id)\n\t\t\t\t\t\t\tif (!requiredPredecessors) continue\n\t\t\t\t\t\t\tconst isReady =\n\t\t\t\t\t\t\t\tjoinStrategy === 'any'\n\t\t\t\t\t\t\t\t\t? [...requiredPredecessors].some((p) => completedThisTurn.has(p))\n\t\t\t\t\t\t\t\t\t: [...requiredPredecessors].every((p) => this.state.getCompletedNodes().has(p))\n\t\t\t\t\t\t\tif (isReady) this.frontier.add(node.id)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (matched.length === 0) {\n\t\t\t\t\t\t\tfor (const [potentialNextId, predecessors] of this.allPredecessors) {\n\t\t\t\t\t\t\t\tif (predecessors.has(nodeId) && !this.state.getCompletedNodes().has(potentialNextId)) {\n\t\t\t\t\t\t\t\t\tconst joinStrategy = this.getEffectiveJoinStrategy(potentialNextId)\n\t\t\t\t\t\t\t\t\tconst isReady =\n\t\t\t\t\t\t\t\t\t\tjoinStrategy === 'any'\n\t\t\t\t\t\t\t\t\t\t\t? [...predecessors].some((p) => completedThisTurn.has(p))\n\t\t\t\t\t\t\t\t\t\t\t: [...predecessors].every((p) => this.state.getCompletedNodes().has(p))\n\t\t\t\t\t\t\t\t\tif (isReady) this.frontier.add(potentialNextId)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tif (error instanceof DOMException && error.name === 'AbortError') {\n\t\t\t\t\tthrow new CancelledWorkflowError('Workflow cancelled')\n\t\t\t\t}\n\t\t\t\tthrow error\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async executeWithConcurrency(\n\t\tnodeIds: string[],\n\t): Promise<\n\t\tArray<\n\t\t\t| { status: 'fulfilled'; value: { nodeId: string; result: NodeResult<any, any> } }\n\t\t\t| { status: 'rejected'; reason: { nodeId: string; error: unknown } }\n\t\t>\n\t> {\n\t\tconst maxConcurrency = this.concurrency || nodeIds.length\n\t\tconst results: Array<\n\t\t\t| { status: 'fulfilled'; value: { nodeId: string; result: NodeResult<any, any> } }\n\t\t\t| { status: 'rejected'; reason: { nodeId: string; error: unknown } }\n\t\t> = []\n\n\t\tfor (let i = 0; i < nodeIds.length; i += maxConcurrency) {\n\t\t\tconst batch = nodeIds.slice(i, i + maxConcurrency)\n\t\t\tconst batchPromises = batch.map(async (nodeId) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await this.runtime.executeNode(\n\t\t\t\t\t\tthis.dynamicBlueprint,\n\t\t\t\t\t\tnodeId,\n\t\t\t\t\t\tthis.state,\n\t\t\t\t\t\tthis.allPredecessors,\n\t\t\t\t\t\tthis.functionRegistry,\n\t\t\t\t\t\tthis.executionId,\n\t\t\t\t\t\tthis.signal,\n\t\t\t\t\t)\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\tstatus: 'fulfilled' as const,\n\t\t\t\t\t\tvalue: { nodeId, result },\n\t\t\t\t\t})\n\t\t\t\t} catch (error) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\tstatus: 'rejected' as const,\n\t\t\t\t\t\treason: { nodeId, error },\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tawait Promise.all(batchPromises)\n\t\t}\n\n\t\treturn results\n\t}\n\n\tprivate async handleDynamicNodes(nodeId: string, result: NodeResult<any, any>) {\n\t\tif (result.dynamicNodes && result.dynamicNodes.length > 0) {\n\t\t\tconst gatherNodeId = result.output?.gatherNodeId\n\t\t\tfor (const dynamicNode of result.dynamicNodes) {\n\t\t\t\tconst implementation = this.functionRegistry?.get(dynamicNode.uses) || this.runtime.registry[dynamicNode.uses]\n\t\t\t\tif (!implementation) {\n\t\t\t\t\tthrow new NodeExecutionError(\n\t\t\t\t\t\t`Implementation for '${dynamicNode.uses}' not found for dynamic node '${dynamicNode.id}' generated by node '${nodeId}'.`,\n\t\t\t\t\t\tdynamicNode.id,\n\t\t\t\t\t\tthis.dynamicBlueprint.id,\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\tthis.executionId,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tthis.dynamicBlueprint.nodes.push(dynamicNode)\n\t\t\t\tthis.allPredecessors.set(dynamicNode.id, new Set([nodeId]))\n\t\t\t\tif (gatherNodeId) {\n\t\t\t\t\tthis.allPredecessors.get(gatherNodeId)?.add(dynamicNode.id)\n\t\t\t\t}\n\t\t\t\tthis.frontier.add(dynamicNode.id)\n\t\t\t}\n\t\t}\n\t}\n\n\tgetAllNodeIds(): Set<string> {\n\t\treturn new Set(this.dynamicBlueprint.nodes.map((n) => n.id))\n\t}\n\n\tgetFallbackNodeIds(): Set<string> {\n\t\tconst fallbackNodeIds = new Set<string>()\n\t\tfor (const node of this.dynamicBlueprint.nodes) {\n\t\t\tif (node.config?.fallback) fallbackNodeIds.add(node.config.fallback)\n\t\t}\n\t\treturn fallbackNodeIds\n\t}\n\n\tgetDynamicBlueprint(): WorkflowBlueprint {\n\t\treturn this.dynamicBlueprint\n\t}\n}\n"]}
@@ -0,0 +1,236 @@
1
+ import { FlowRuntime } from './chunk-3XVVR2SR.js';
2
+ import { JsonSerializer } from './chunk-CYHZ2YVH.js';
3
+ import { analyzeBlueprint } from './chunk-HN72TZY5.js';
4
+
5
+ // src/runtime/adapter.ts
6
+ var BaseDistributedAdapter = class {
7
+ runtime;
8
+ store;
9
+ serializer;
10
+ constructor(options) {
11
+ this.runtime = new FlowRuntime(options.runtimeOptions);
12
+ this.store = options.coordinationStore;
13
+ this.serializer = options.runtimeOptions.serializer || new JsonSerializer();
14
+ console.log("[Adapter] BaseDistributedAdapter initialized.");
15
+ }
16
+ /**
17
+ * Starts the worker, which begins listening for and processing jobs from the queue.
18
+ */
19
+ start() {
20
+ console.log("[Adapter] Starting worker...");
21
+ this.processJobs(this.handleJob.bind(this));
22
+ }
23
+ /**
24
+ * Hook called at the start of job processing. Subclasses can override this
25
+ * to perform additional setup (e.g., timestamp tracking for reconciliation).
26
+ */
27
+ async onJobStart(_runId, _blueprintId, _nodeId) {
28
+ }
29
+ /**
30
+ * The main handler for processing a single job from the queue.
31
+ */
32
+ async handleJob(job) {
33
+ const { runId, blueprintId, nodeId } = job;
34
+ await this.onJobStart(runId, blueprintId, nodeId);
35
+ const blueprint = this.runtime.options.blueprints?.[blueprintId];
36
+ if (!blueprint) {
37
+ const reason = `Blueprint with ID '${blueprintId}' not found in the worker's runtime registry.`;
38
+ console.error(`[Adapter] FATAL: ${reason}`);
39
+ await this.publishFinalResult(runId, { status: "failed", reason });
40
+ return;
41
+ }
42
+ const context = this.createContext(runId);
43
+ const hasBlueprintId = await context.has("blueprintId");
44
+ if (!hasBlueprintId) {
45
+ await context.set("blueprintId", blueprintId);
46
+ }
47
+ const workerState = {
48
+ getContext: () => context,
49
+ markFallbackExecuted: () => {
50
+ },
51
+ addError: (nodeId2, error) => {
52
+ console.error(`[Adapter] Error in node ${nodeId2}:`, error);
53
+ }
54
+ };
55
+ try {
56
+ const result = await this.runtime.executeNode(blueprint, nodeId, workerState);
57
+ await context.set(nodeId, result.output);
58
+ const analysis = analyzeBlueprint(blueprint);
59
+ const isTerminalNode = analysis.terminalNodeIds.includes(nodeId);
60
+ if (isTerminalNode) {
61
+ const completedNodes = new Set(
62
+ Object.keys(await context.toJSON()).filter((k) => blueprint.nodes.some((n) => n.id === k))
63
+ );
64
+ const allTerminalNodesCompleted = analysis.terminalNodeIds.every((terminalId) => completedNodes.has(terminalId));
65
+ if (allTerminalNodesCompleted) {
66
+ console.log(`[Adapter] \u2705 All terminal nodes completed for Run ID: ${runId}. Declaring workflow complete.`);
67
+ const finalContext = await context.toJSON();
68
+ const finalResult = {
69
+ context: finalContext,
70
+ serializedContext: this.serializer.serialize(finalContext),
71
+ status: "completed"
72
+ };
73
+ await this.publishFinalResult(runId, {
74
+ status: "completed",
75
+ payload: finalResult
76
+ });
77
+ return;
78
+ } else {
79
+ console.log(
80
+ `[Adapter] Terminal node '${nodeId}' completed for Run ID '${runId}', but other terminal nodes are still running.`
81
+ );
82
+ }
83
+ }
84
+ const nextNodes = await this.runtime.determineNextNodes(blueprint, nodeId, result, context);
85
+ if (nextNodes.length === 0 && !isTerminalNode) {
86
+ console.log(
87
+ `[Adapter] Non-terminal node '${nodeId}' reached end of branch for Run ID '${runId}'. This branch will now terminate.`
88
+ );
89
+ return;
90
+ }
91
+ for (const { node: nextNodeDef, edge } of nextNodes) {
92
+ await this.runtime.applyEdgeTransform(edge, result, nextNodeDef, context);
93
+ const isReady = await this.isReadyForFanIn(runId, blueprint, nextNodeDef.id);
94
+ if (isReady) {
95
+ console.log(`[Adapter] Node '${nextNodeDef.id}' is ready. Enqueuing job.`);
96
+ await this.enqueueJob({ runId, blueprintId, nodeId: nextNodeDef.id });
97
+ } else {
98
+ console.log(`[Adapter] Node '${nextNodeDef.id}' is waiting for other predecessors to complete.`);
99
+ }
100
+ }
101
+ } catch (error) {
102
+ const reason = error.message || "Unknown execution error";
103
+ console.error(`[Adapter] FATAL: Job for node '${nodeId}' failed for Run ID '${runId}': ${reason}`);
104
+ await this.publishFinalResult(runId, { status: "failed", reason });
105
+ await this.writePoisonPillForSuccessors(runId, blueprint, nodeId);
106
+ }
107
+ }
108
+ /**
109
+ * Encapsulates the fan-in join logic using the coordination store.
110
+ */
111
+ async isReadyForFanIn(runId, blueprint, targetNodeId) {
112
+ const targetNode = blueprint.nodes.find((n) => n.id === targetNodeId);
113
+ if (!targetNode) {
114
+ throw new Error(`Node '${targetNodeId}' not found in blueprint`);
115
+ }
116
+ const joinStrategy = targetNode.config?.joinStrategy || "all";
117
+ const predecessors = blueprint.edges.filter((e) => e.target === targetNodeId);
118
+ if (predecessors.length <= 1) {
119
+ return true;
120
+ }
121
+ const poisonKey = `flowcraft:fanin:poison:${runId}:${targetNodeId}`;
122
+ const isPoisoned = !await this.store.setIfNotExist(poisonKey, "poisoned", 3600);
123
+ if (isPoisoned) {
124
+ console.log(`[Adapter] Node '${targetNodeId}' is poisoned due to failed predecessor. Failing immediately.`);
125
+ throw new Error(`Node '${targetNodeId}' failed due to poisoned predecessor in run '${runId}'`);
126
+ }
127
+ if (joinStrategy === "any") {
128
+ const lockKey = `flowcraft:joinlock:${runId}:${targetNodeId}`;
129
+ return await this.store.setIfNotExist(lockKey, "locked", 3600);
130
+ } else {
131
+ const fanInKey = `flowcraft:fanin:${runId}:${targetNodeId}`;
132
+ const readyCount = await this.store.increment(fanInKey, 3600);
133
+ if (readyCount >= predecessors.length) {
134
+ await this.store.delete(fanInKey);
135
+ return true;
136
+ }
137
+ return false;
138
+ }
139
+ }
140
+ /**
141
+ * Reconciles the state of a workflow run. It inspects the persisted
142
+ * context to find completed nodes, determines the next set of executable
143
+ * nodes (the frontier), and enqueues jobs for them if they aren't
144
+ * already running. This is the core of the resume functionality.
145
+ *
146
+ * @param runId The unique ID of the workflow execution to reconcile.
147
+ * @returns The set of node IDs that were enqueued for execution.
148
+ */
149
+ async reconcile(runId) {
150
+ const context = this.createContext(runId);
151
+ const blueprintId = await context.get("blueprintId");
152
+ if (!blueprintId) {
153
+ throw new Error(`Cannot reconcile runId '${runId}': blueprintId not found in context.`);
154
+ }
155
+ const blueprint = this.runtime.options.blueprints?.[blueprintId];
156
+ if (!blueprint) {
157
+ throw new Error(`Cannot reconcile runId '${runId}': Blueprint with ID '${blueprintId}' not found.`);
158
+ }
159
+ const state = await context.toJSON();
160
+ const completedNodes = new Set(Object.keys(state).filter((k) => blueprint.nodes.some((n) => n.id === k)));
161
+ const frontier = this.calculateResumedFrontier(blueprint, completedNodes);
162
+ const enqueuedNodes = /* @__PURE__ */ new Set();
163
+ for (const nodeId of frontier) {
164
+ const nodeDef = blueprint.nodes.find((n) => n.id === nodeId);
165
+ const joinStrategy = nodeDef?.config?.joinStrategy || "all";
166
+ let shouldEnqueue = false;
167
+ if (joinStrategy === "any") {
168
+ const lockKey = `flowcraft:joinlock:${runId}:${nodeId}`;
169
+ if (await this.store.setIfNotExist(lockKey, "locked-by-reconcile", 3600)) {
170
+ shouldEnqueue = true;
171
+ } else {
172
+ console.log(`[Adapter] Reconciling: Node '${nodeId}' is an 'any' join and is already locked.`, { runId });
173
+ }
174
+ } else {
175
+ const lockKey = `flowcraft:nodelock:${runId}:${nodeId}`;
176
+ if (await this.store.setIfNotExist(lockKey, "locked", 120)) {
177
+ shouldEnqueue = true;
178
+ } else {
179
+ console.log(`[Adapter] Reconciling: Node '${nodeId}' is already locked.`, { runId });
180
+ }
181
+ }
182
+ if (shouldEnqueue) {
183
+ console.log(`[Adapter] Reconciling: Enqueuing ready job for node '${nodeId}'`, { runId });
184
+ await this.enqueueJob({ runId, blueprintId: blueprint.id, nodeId });
185
+ enqueuedNodes.add(nodeId);
186
+ }
187
+ }
188
+ return enqueuedNodes;
189
+ }
190
+ calculateResumedFrontier(blueprint, completedNodes) {
191
+ const newFrontier = /* @__PURE__ */ new Set();
192
+ const allPredecessors = /* @__PURE__ */ new Map();
193
+ for (const node of blueprint.nodes) {
194
+ allPredecessors.set(node.id, /* @__PURE__ */ new Set());
195
+ }
196
+ for (const edge of blueprint.edges) {
197
+ allPredecessors.get(edge.target)?.add(edge.source);
198
+ }
199
+ for (const node of blueprint.nodes) {
200
+ if (completedNodes.has(node.id)) {
201
+ continue;
202
+ }
203
+ const predecessors = allPredecessors.get(node.id) ?? /* @__PURE__ */ new Set();
204
+ if (predecessors.size === 0 && !completedNodes.has(node.id)) {
205
+ newFrontier.add(node.id);
206
+ continue;
207
+ }
208
+ const joinStrategy = node.config?.joinStrategy || "all";
209
+ const completedPredecessors = [...predecessors].filter((p) => completedNodes.has(p));
210
+ const isReady = joinStrategy === "any" ? completedPredecessors.length > 0 : completedPredecessors.length === predecessors.size;
211
+ if (isReady) {
212
+ newFrontier.add(node.id);
213
+ }
214
+ }
215
+ return newFrontier;
216
+ }
217
+ /**
218
+ * Writes a poison pill for 'all' join successors of a failed node to prevent stalling.
219
+ */
220
+ async writePoisonPillForSuccessors(runId, blueprint, failedNodeId) {
221
+ const successors = blueprint.edges.filter((edge) => edge.source === failedNodeId).map((edge) => edge.target).map((targetId) => blueprint.nodes.find((node) => node.id === targetId)).filter((node) => node && (node.config?.joinStrategy || "all") === "all");
222
+ for (const successor of successors) {
223
+ if (successor) {
224
+ const poisonKey = `flowcraft:fanin:poison:${runId}:${successor.id}`;
225
+ await this.store.setIfNotExist(poisonKey, "poisoned", 3600);
226
+ console.log(
227
+ `[Adapter] Wrote poison pill for join node '${successor.id}' due to failed predecessor '${failedNodeId}'`
228
+ );
229
+ }
230
+ }
231
+ }
232
+ };
233
+
234
+ export { BaseDistributedAdapter };
235
+ //# sourceMappingURL=chunk-O3XD45IL.js.map
236
+ //# sourceMappingURL=chunk-O3XD45IL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/runtime/adapter.ts"],"names":["nodeId"],"mappings":"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","file":"chunk-O3XD45IL.js","sourcesContent":["import { analyzeBlueprint } from '../analysis'\nimport { JsonSerializer } from '../serializer'\nimport type {\n\tIAsyncContext,\n\tISerializer,\n\tNodeResult,\n\tRuntimeOptions,\n\tWorkflowBlueprint,\n\tWorkflowResult,\n} from '../types'\nimport { FlowRuntime } from './runtime'\n\n/**\n * Defines the contract for an atomic, distributed key-value store required by\n * the adapter for coordination tasks like fan-in joins and locking.\n */\nexport interface ICoordinationStore {\n\t/** Atomically increments a key and returns the new value. Ideal for 'all' joins. */\n\tincrement: (key: string, ttlSeconds: number) => Promise<number>\n\t/** Sets a key only if it does not already exist. Ideal for 'any' joins (locking). */\n\tsetIfNotExist: (key: string, value: string, ttlSeconds: number) => Promise<boolean>\n\t/** Deletes a key. Used for cleanup. */\n\tdelete: (key: string) => Promise<void>\n}\n\n/** Configuration options for constructing a BaseDistributedAdapter. */\nexport interface AdapterOptions {\n\truntimeOptions: RuntimeOptions<any>\n\tcoordinationStore: ICoordinationStore\n}\n\n/** The data payload expected for a job in the queue. */\nexport interface JobPayload {\n\trunId: string\n\tblueprintId: string\n\tnodeId: string\n}\n\n/**\n * The base class for all distributed adapters. It handles the technology-agnostic\n * orchestration logic and leaves queue-specific implementation to subclasses.\n */\nexport abstract class BaseDistributedAdapter {\n\tprotected readonly runtime: FlowRuntime<any, any>\n\tprotected readonly store: ICoordinationStore\n\tprotected readonly serializer: ISerializer\n\n\tconstructor(options: AdapterOptions) {\n\t\tthis.runtime = new FlowRuntime(options.runtimeOptions)\n\t\tthis.store = options.coordinationStore\n\t\tthis.serializer = options.runtimeOptions.serializer || new JsonSerializer()\n\t\tconsole.log('[Adapter] BaseDistributedAdapter initialized.')\n\t}\n\n\t/**\n\t * Starts the worker, which begins listening for and processing jobs from the queue.\n\t */\n\tpublic start(): void {\n\t\tconsole.log('[Adapter] Starting worker...')\n\t\tthis.processJobs(this.handleJob.bind(this))\n\t}\n\n\t/**\n\t * Creates a technology-specific distributed context for a given workflow run.\n\t * @param runId The unique ID for the workflow execution.\n\t */\n\tprotected abstract createContext(runId: string): IAsyncContext<Record<string, any>>\n\t/**\n\t * Sets up the listener for the message queue. The implementation should call the\n\t * provided `handler` function for each new job received.\n\t * @param handler The core logic to execute for each job.\n\t */\n\tprotected abstract processJobs(handler: (job: JobPayload) => Promise<void>): void\n\n\t/**\n\t * Enqueues a new job onto the message queue.\n\t * @param job The payload for the job to be enqueued.\n\t */\n\tprotected abstract enqueueJob(job: JobPayload): Promise<void>\n\n\t/**\n\t * Publishes the final result of a completed or failed workflow run.\n\t * @param runId The unique ID of the workflow run.\n\t * @param result The final status and payload of the workflow.\n\t */\n\tprotected abstract publishFinalResult(\n\t\trunId: string,\n\t\tresult: {\n\t\t\tstatus: 'completed' | 'failed'\n\t\t\tpayload?: WorkflowResult\n\t\t\treason?: string\n\t\t},\n\t): Promise<void>\n\n\t/**\n\t * Hook called at the start of job processing. Subclasses can override this\n\t * to perform additional setup (e.g., timestamp tracking for reconciliation).\n\t */\n\tprotected async onJobStart(_runId: string, _blueprintId: string, _nodeId: string): Promise<void> {\n\t\t// default implementation does nothing\n\t}\n\n\t/**\n\t * The main handler for processing a single job from the queue.\n\t */\n\tprotected async handleJob(job: JobPayload): Promise<void> {\n\t\tconst { runId, blueprintId, nodeId } = job\n\n\t\tawait this.onJobStart(runId, blueprintId, nodeId)\n\n\t\tconst blueprint = this.runtime.options.blueprints?.[blueprintId]\n\t\tif (!blueprint) {\n\t\t\tconst reason = `Blueprint with ID '${blueprintId}' not found in the worker's runtime registry.`\n\t\t\tconsole.error(`[Adapter] FATAL: ${reason}`)\n\t\t\tawait this.publishFinalResult(runId, { status: 'failed', reason })\n\t\t\treturn\n\t\t}\n\n\t\tconst context = this.createContext(runId)\n\n\t\t// persist the blueprintId for the reconcile method to find later\n\t\tconst hasBlueprintId = await context.has('blueprintId' as any)\n\t\tif (!hasBlueprintId) {\n\t\t\tawait context.set('blueprintId' as any, blueprintId)\n\t\t}\n\t\tconst workerState = {\n\t\t\tgetContext: () => context,\n\t\t\tmarkFallbackExecuted: () => {},\n\t\t\taddError: (nodeId: string, error: Error) => {\n\t\t\t\tconsole.error(`[Adapter] Error in node ${nodeId}:`, error)\n\t\t\t},\n\t\t} as any\n\n\t\ttry {\n\t\t\tconst result: NodeResult<any, any> = await this.runtime.executeNode(blueprint, nodeId, workerState)\n\t\t\tawait context.set(nodeId as any, result.output)\n\n\t\t\tconst analysis = analyzeBlueprint(blueprint)\n\t\t\tconst isTerminalNode = analysis.terminalNodeIds.includes(nodeId)\n\n\t\t\tif (isTerminalNode) {\n\t\t\t\tconst completedNodes = new Set(\n\t\t\t\t\tObject.keys(await context.toJSON()).filter((k) => blueprint.nodes.some((n) => n.id === k)),\n\t\t\t\t)\n\t\t\t\tconst allTerminalNodesCompleted = analysis.terminalNodeIds.every((terminalId) => completedNodes.has(terminalId))\n\n\t\t\t\tif (allTerminalNodesCompleted) {\n\t\t\t\t\tconsole.log(`[Adapter] ✅ All terminal nodes completed for Run ID: ${runId}. Declaring workflow complete.`)\n\t\t\t\t\tconst finalContext = await context.toJSON()\n\t\t\t\t\tconst finalResult: WorkflowResult = {\n\t\t\t\t\t\tcontext: finalContext,\n\t\t\t\t\t\tserializedContext: this.serializer.serialize(finalContext),\n\t\t\t\t\t\tstatus: 'completed',\n\t\t\t\t\t}\n\t\t\t\t\tawait this.publishFinalResult(runId, {\n\t\t\t\t\t\tstatus: 'completed',\n\t\t\t\t\t\tpayload: finalResult,\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t} else {\n\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t`[Adapter] Terminal node '${nodeId}' completed for Run ID '${runId}', but other terminal nodes are still running.`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst nextNodes = await this.runtime.determineNextNodes(blueprint, nodeId, result, context)\n\n\t\t\t// stop if a branch terminates but it wasn't a terminal node\n\t\t\tif (nextNodes.length === 0 && !isTerminalNode) {\n\t\t\t\tconsole.log(\n\t\t\t\t\t`[Adapter] Non-terminal node '${nodeId}' reached end of branch for Run ID '${runId}'. This branch will now terminate.`,\n\t\t\t\t)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfor (const { node: nextNodeDef, edge } of nextNodes) {\n\t\t\t\tawait this.runtime.applyEdgeTransform(edge, result, nextNodeDef, context)\n\t\t\t\tconst isReady = await this.isReadyForFanIn(runId, blueprint, nextNodeDef.id)\n\t\t\t\tif (isReady) {\n\t\t\t\t\tconsole.log(`[Adapter] Node '${nextNodeDef.id}' is ready. Enqueuing job.`)\n\t\t\t\t\tawait this.enqueueJob({ runId, blueprintId, nodeId: nextNodeDef.id })\n\t\t\t\t} else {\n\t\t\t\t\tconsole.log(`[Adapter] Node '${nextNodeDef.id}' is waiting for other predecessors to complete.`)\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error: any) {\n\t\t\tconst reason = error.message || 'Unknown execution error'\n\t\t\tconsole.error(`[Adapter] FATAL: Job for node '${nodeId}' failed for Run ID '${runId}': ${reason}`)\n\t\t\tawait this.publishFinalResult(runId, { status: 'failed', reason })\n\t\t\tawait this.writePoisonPillForSuccessors(runId, blueprint, nodeId)\n\t\t}\n\t}\n\n\t/**\n\t * Encapsulates the fan-in join logic using the coordination store.\n\t */\n\tprotected async isReadyForFanIn(runId: string, blueprint: WorkflowBlueprint, targetNodeId: string): Promise<boolean> {\n\t\tconst targetNode = blueprint.nodes.find((n) => n.id === targetNodeId)\n\t\tif (!targetNode) {\n\t\t\tthrow new Error(`Node '${targetNodeId}' not found in blueprint`)\n\t\t}\n\t\tconst joinStrategy = targetNode.config?.joinStrategy || 'all'\n\t\tconst predecessors = blueprint.edges.filter((e) => e.target === targetNodeId)\n\n\t\tif (predecessors.length <= 1) {\n\t\t\treturn true\n\t\t}\n\n\t\tconst poisonKey = `flowcraft:fanin:poison:${runId}:${targetNodeId}`\n\t\tconst isPoisoned = !(await this.store.setIfNotExist(poisonKey, 'poisoned', 3600))\n\t\tif (isPoisoned) {\n\t\t\tconsole.log(`[Adapter] Node '${targetNodeId}' is poisoned due to failed predecessor. Failing immediately.`)\n\t\t\tthrow new Error(`Node '${targetNodeId}' failed due to poisoned predecessor in run '${runId}'`)\n\t\t}\n\n\t\tif (joinStrategy === 'any') {\n\t\t\tconst lockKey = `flowcraft:joinlock:${runId}:${targetNodeId}`\n\t\t\treturn await this.store.setIfNotExist(lockKey, 'locked', 3600)\n\t\t} else {\n\t\t\tconst fanInKey = `flowcraft:fanin:${runId}:${targetNodeId}`\n\t\t\tconst readyCount = await this.store.increment(fanInKey, 3600)\n\t\t\tif (readyCount >= predecessors.length) {\n\t\t\t\tawait this.store.delete(fanInKey)\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn false\n\t\t}\n\t}\n\n\t/**\n\t * Reconciles the state of a workflow run. It inspects the persisted\n\t * context to find completed nodes, determines the next set of executable\n\t * nodes (the frontier), and enqueues jobs for them if they aren't\n\t * already running. This is the core of the resume functionality.\n\t *\n\t * @param runId The unique ID of the workflow execution to reconcile.\n\t * @returns The set of node IDs that were enqueued for execution.\n\t */\n\tpublic async reconcile(runId: string): Promise<Set<string>> {\n\t\tconst context = this.createContext(runId)\n\t\tconst blueprintId = (await context.get('blueprintId' as any)) as string | undefined\n\n\t\tif (!blueprintId) {\n\t\t\tthrow new Error(`Cannot reconcile runId '${runId}': blueprintId not found in context.`)\n\t\t}\n\t\tconst blueprint = this.runtime.options.blueprints?.[blueprintId]\n\t\tif (!blueprint) {\n\t\t\tthrow new Error(`Cannot reconcile runId '${runId}': Blueprint with ID '${blueprintId}' not found.`)\n\t\t}\n\n\t\tconst state = await context.toJSON()\n\t\t// filter out internal keys\n\t\tconst completedNodes = new Set(Object.keys(state).filter((k) => blueprint.nodes.some((n) => n.id === k)))\n\n\t\tconst frontier = this.calculateResumedFrontier(blueprint, completedNodes)\n\n\t\tconst enqueuedNodes = new Set<string>()\n\t\tfor (const nodeId of frontier) {\n\t\t\tconst nodeDef = blueprint.nodes.find((n) => n.id === nodeId)\n\t\t\tconst joinStrategy = nodeDef?.config?.joinStrategy || 'all'\n\n\t\t\tlet shouldEnqueue = false\n\n\t\t\tif (joinStrategy === 'any') {\n\t\t\t\t// acquire the permanent join lock\n\t\t\t\tconst lockKey = `flowcraft:joinlock:${runId}:${nodeId}`\n\t\t\t\tif (await this.store.setIfNotExist(lockKey, 'locked-by-reconcile', 3600)) {\n\t\t\t\t\tshouldEnqueue = true\n\t\t\t\t} else {\n\t\t\t\t\tconsole.log(`[Adapter] Reconciling: Node '${nodeId}' is an 'any' join and is already locked.`, { runId })\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// 'all' joins and single-predecessor nodes use a temporary lock\n\t\t\t\tconst lockKey = `flowcraft:nodelock:${runId}:${nodeId}`\n\t\t\t\tif (await this.store.setIfNotExist(lockKey, 'locked', 120)) {\n\t\t\t\t\tshouldEnqueue = true\n\t\t\t\t} else {\n\t\t\t\t\tconsole.log(`[Adapter] Reconciling: Node '${nodeId}' is already locked.`, { runId })\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (shouldEnqueue) {\n\t\t\t\tconsole.log(`[Adapter] Reconciling: Enqueuing ready job for node '${nodeId}'`, { runId })\n\t\t\t\tawait this.enqueueJob({ runId, blueprintId: blueprint.id, nodeId })\n\t\t\t\tenqueuedNodes.add(nodeId)\n\t\t\t}\n\t\t}\n\n\t\treturn enqueuedNodes\n\t}\n\n\tprivate calculateResumedFrontier(blueprint: WorkflowBlueprint, completedNodes: Set<string>): Set<string> {\n\t\tconst newFrontier = new Set<string>()\n\t\tconst allPredecessors = new Map<string, Set<string>>()\n\t\t// (logic extracted from the GraphTraverser)\n\t\tfor (const node of blueprint.nodes) {\n\t\t\tallPredecessors.set(node.id, new Set())\n\t\t}\n\t\tfor (const edge of blueprint.edges) {\n\t\t\tallPredecessors.get(edge.target)?.add(edge.source)\n\t\t}\n\n\t\tfor (const node of blueprint.nodes) {\n\t\t\tif (completedNodes.has(node.id)) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tconst predecessors = allPredecessors.get(node.id) ?? new Set()\n\t\t\tif (predecessors.size === 0 && !completedNodes.has(node.id)) {\n\t\t\t\tnewFrontier.add(node.id)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tconst joinStrategy = node.config?.joinStrategy || 'all'\n\t\t\tconst completedPredecessors = [...predecessors].filter((p) => completedNodes.has(p))\n\n\t\t\tconst isReady =\n\t\t\t\tjoinStrategy === 'any' ? completedPredecessors.length > 0 : completedPredecessors.length === predecessors.size\n\n\t\t\tif (isReady) {\n\t\t\t\tnewFrontier.add(node.id)\n\t\t\t}\n\t\t}\n\t\treturn newFrontier\n\t}\n\n\t/**\n\t * Writes a poison pill for 'all' join successors of a failed node to prevent stalling.\n\t */\n\tprivate async writePoisonPillForSuccessors(\n\t\trunId: string,\n\t\tblueprint: WorkflowBlueprint,\n\t\tfailedNodeId: string,\n\t): Promise<void> {\n\t\tconst successors = blueprint.edges\n\t\t\t.filter((edge) => edge.source === failedNodeId)\n\t\t\t.map((edge) => edge.target)\n\t\t\t.map((targetId) => blueprint.nodes.find((node) => node.id === targetId))\n\t\t\t.filter((node) => node && (node.config?.joinStrategy || 'all') === 'all')\n\n\t\tfor (const successor of successors) {\n\t\t\tif (successor) {\n\t\t\t\tconst poisonKey = `flowcraft:fanin:poison:${runId}:${successor.id}`\n\t\t\t\tawait this.store.setIfNotExist(poisonKey, 'poisoned', 3600)\n\t\t\t\tconsole.log(\n\t\t\t\t\t`[Adapter] Wrote poison pill for join node '${successor.id}' due to failed predecessor '${failedNodeId}'`,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
@@ -0,0 +1,48 @@
1
+ // src/evaluator.ts
2
+ var PropertyEvaluator = class {
3
+ evaluate(expression, context) {
4
+ try {
5
+ if (!/^[a-zA-Z0-9_$.]+$/.test(expression)) {
6
+ console.error(`Error evaluating expression: "${expression}" contains invalid characters.`);
7
+ return void 0;
8
+ }
9
+ const parts = expression.split(".");
10
+ const startKey = parts[0];
11
+ if (!Object.hasOwn(context, startKey)) {
12
+ return void 0;
13
+ }
14
+ let current = context[startKey];
15
+ for (let i = 1; i < parts.length; i++) {
16
+ if (current === null || current === void 0) {
17
+ return void 0;
18
+ }
19
+ current = current[parts[i]];
20
+ }
21
+ return current;
22
+ } catch (error) {
23
+ console.error(`Error evaluating property expression "${expression}":`, error);
24
+ return void 0;
25
+ }
26
+ }
27
+ };
28
+ var UnsafeEvaluator = class {
29
+ evaluate(expression, context) {
30
+ try {
31
+ const validIdentifierRegex = /^[a-z_$][\w$]*$/i;
32
+ const validKeys = Object.keys(context).filter((key) => validIdentifierRegex.test(key));
33
+ const validContext = {};
34
+ for (const key of validKeys) {
35
+ validContext[key] = context[key];
36
+ }
37
+ const sandbox = new Function(...validKeys, `return ${expression}`);
38
+ return sandbox(...validKeys.map((k) => validContext[k]));
39
+ } catch (error) {
40
+ console.error(`Error evaluating expression "${expression}":`, error);
41
+ return void 0;
42
+ }
43
+ }
44
+ };
45
+
46
+ export { PropertyEvaluator, UnsafeEvaluator };
47
+ //# sourceMappingURL=chunk-PH2IYZHV.js.map
48
+ //# sourceMappingURL=chunk-PH2IYZHV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/evaluator.ts"],"names":[],"mappings":";AAWO,IAAM,oBAAN,MAA8C;AAAA,EACpD,QAAA,CAAS,YAAoB,OAAA,EAAmC;AAC/D,IAAA,IAAI;AAEH,MAAA,IAAI,CAAC,mBAAA,CAAoB,IAAA,CAAK,UAAU,CAAA,EAAG;AAC1C,QAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,8BAAA,EAAiC,UAAU,CAAA,8BAAA,CAAgC,CAAA;AACzF,QAAA,OAAO,KAAA,CAAA;AAAA,MACR;AAEA,MAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,KAAA,CAAM,GAAG,CAAA;AAClC,MAAA,MAAM,QAAA,GAAW,MAAM,CAAC,CAAA;AAExB,MAAA,IAAI,CAAC,MAAA,CAAO,MAAA,CAAO,OAAA,EAAS,QAAQ,CAAA,EAAG;AACtC,QAAA,OAAO,KAAA,CAAA;AAAA,MACR;AAEA,MAAA,IAAI,OAAA,GAAU,QAAQ,QAAQ,CAAA;AAC9B,MAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AACtC,QAAA,IAAI,OAAA,KAAY,IAAA,IAAQ,OAAA,KAAY,KAAA,CAAA,EAAW;AAC9C,UAAA,OAAO,KAAA,CAAA;AAAA,QACR;AACA,QAAA,OAAA,GAAU,OAAA,CAAQ,KAAA,CAAM,CAAC,CAAC,CAAA;AAAA,MAC3B;AACA,MAAA,OAAO,OAAA;AAAA,IACR,SAAS,KAAA,EAAO;AACf,MAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,sCAAA,EAAyC,UAAU,CAAA,EAAA,CAAA,EAAM,KAAK,CAAA;AAC5E,MAAA,OAAO,MAAA;AAAA,IACR;AAAA,EACD;AACD;AAaO,IAAM,kBAAN,MAA4C;AAAA,EAClD,QAAA,CAAS,YAAoB,OAAA,EAAmC;AAC/D,IAAA,IAAI;AAEH,MAAA,MAAM,oBAAA,GAAuB,kBAAA;AAC7B,MAAA,MAAM,SAAA,GAAY,MAAA,CAAO,IAAA,CAAK,OAAO,CAAA,CAAE,MAAA,CAAO,CAAC,GAAA,KAAQ,oBAAA,CAAqB,IAAA,CAAK,GAAG,CAAC,CAAA;AACrF,MAAA,MAAM,eAAoC,EAAC;AAC3C,MAAA,KAAA,MAAW,OAAO,SAAA,EAAW;AAC5B,QAAA,YAAA,CAAa,GAAG,CAAA,GAAI,OAAA,CAAQ,GAAG,CAAA;AAAA,MAChC;AAGA,MAAA,MAAM,UAAU,IAAI,QAAA,CAAS,GAAG,SAAA,EAAW,CAAA,OAAA,EAAU,UAAU,CAAA,CAAE,CAAA;AACjE,MAAA,OAAO,OAAA,CAAQ,GAAG,SAAA,CAAU,GAAA,CAAI,CAAC,CAAA,KAAM,YAAA,CAAa,CAAC,CAAC,CAAC,CAAA;AAAA,IACxD,SAAS,KAAA,EAAO;AACf,MAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,6BAAA,EAAgC,UAAU,CAAA,EAAA,CAAA,EAAM,KAAK,CAAA;AAEnE,MAAA,OAAO,MAAA;AAAA,IACR;AAAA,EACD;AACD","file":"chunk-PH2IYZHV.js","sourcesContent":["import type { IEvaluator } from './types'\n\n/**\n * A safe evaluator that only allows simple property access.\n * It cannot execute arbitrary code and is secure for untrusted inputs.\n *\n * Example expressions:\n * - \"result.output.status\"\n * - \"context.user.isAdmin\"\n * - \"input.value\"\n */\nexport class PropertyEvaluator implements IEvaluator {\n\tevaluate(expression: string, context: Record<string, any>): any {\n\t\ttry {\n\t\t\t// Basic validation to ensure it's a simple path\n\t\t\tif (!/^[a-zA-Z0-9_$.]+$/.test(expression)) {\n\t\t\t\tconsole.error(`Error evaluating expression: \"${expression}\" contains invalid characters.`)\n\t\t\t\treturn undefined\n\t\t\t}\n\n\t\t\tconst parts = expression.split('.')\n\t\t\tconst startKey = parts[0]\n\n\t\t\tif (!Object.hasOwn(context, startKey)) {\n\t\t\t\treturn undefined\n\t\t\t}\n\n\t\t\tlet current = context[startKey]\n\t\t\tfor (let i = 1; i < parts.length; i++) {\n\t\t\t\tif (current === null || current === undefined) {\n\t\t\t\t\treturn undefined\n\t\t\t\t}\n\t\t\t\tcurrent = current[parts[i]]\n\t\t\t}\n\t\t\treturn current\n\t\t} catch (error) {\n\t\t\tconsole.error(`Error evaluating property expression \"${expression}\":`, error)\n\t\t\treturn undefined\n\t\t}\n\t}\n}\n\n/**\n * @warning This evaluator uses `new Function()` and can execute arbitrary\n * JavaScript code. It poses a significant security risk if the expressions\n * are not from a trusted source (e.g., user input).\n *\n * It should only be used in controlled environments where all workflow\n * definitions are static and authored by trusted developers.\n *\n * For safer evaluation, use the default `PropertyEvaluator` or install a\n * sandboxed library like `jsep` to create a custom, secure evaluator.\n */\nexport class UnsafeEvaluator implements IEvaluator {\n\tevaluate(expression: string, context: Record<string, any>): any {\n\t\ttry {\n\t\t\t// filter out keys that aren't valid JavaScript identifiers\n\t\t\tconst validIdentifierRegex = /^[a-z_$][\\w$]*$/i\n\t\t\tconst validKeys = Object.keys(context).filter((key) => validIdentifierRegex.test(key))\n\t\t\tconst validContext: Record<string, any> = {}\n\t\t\tfor (const key of validKeys) {\n\t\t\t\tvalidContext[key] = context[key]\n\t\t\t}\n\n\t\t\t// sandboxed function prevents access to global scope (e.g., `window`, `process`).\n\t\t\tconst sandbox = new Function(...validKeys, `return ${expression}`)\n\t\t\treturn sandbox(...validKeys.map((k) => validContext[k]))\n\t\t} catch (error) {\n\t\t\tconsole.error(`Error evaluating expression \"${expression}\":`, error)\n\t\t\t// default to a \"falsy\" value.\n\t\t\treturn undefined\n\t\t}\n\t}\n}\n"]}
@@ -33,8 +33,17 @@ var BaseNode = class {
33
33
  async fallback(error, _context) {
34
34
  throw error;
35
35
  }
36
+ /**
37
+ * An optional cleanup phase for non-retriable errors that occur outside the main `exec` method.
38
+ * This method is invoked in a `finally` block or equivalent construct if a fatal, unhandled exception occurs in the `prep`, `exec`, or `post` phases.
39
+ * Allows nodes to perform crucial cleanup, such as closing database connections or releasing locks.
40
+ * @param _error The error that caused the failure.
41
+ * @param _context The node's execution context.
42
+ */
43
+ async recover(_error, _context) {
44
+ }
36
45
  };
37
46
 
38
47
  export { BaseNode, isNodeClass };
39
- //# sourceMappingURL=chunk-DSZSR7UE.js.map
40
- //# sourceMappingURL=chunk-DSZSR7UE.js.map
48
+ //# sourceMappingURL=chunk-U5V5O5MN.js.map
49
+ //# sourceMappingURL=chunk-U5V5O5MN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/node.ts"],"names":[],"mappings":";AAGO,SAAS,YAAY,IAAA,EAA8B;AACzD,EAAA,OAAO,OAAO,IAAA,KAAS,UAAA,IAAc,CAAC,CAAC,KAAK,SAAA,EAAW,IAAA;AACxD;AAOO,IAAe,WAAf,MAML;AAAA;AAAA;AAAA;AAAA,EAID,YAAsB,MAAA,EAA8B;AAA9B,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAA,EAA+B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOrD,MAAM,KAAK,OAAA,EAAqE;AAC/E,IAAA,OAAO,OAAA,CAAQ,KAAA;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,MAAM,IAAA,CACL,UAAA,EACA,QAAA,EACwC;AACxC,IAAA,OAAO,UAAA;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,QAAA,CACL,KAAA,EACA,QAAA,EACuD;AAEvD,IAAA,MAAM,KAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,OAAA,CAAQ,MAAA,EAAe,QAAA,EAAuE;AAAA,EAEpG;AACD","file":"chunk-U5V5O5MN.js","sourcesContent":["import type { NodeClass, NodeContext, NodeResult, RuntimeDependencies } from './types'\n\n/** A type guard to reliably distinguish a NodeClass from a NodeFunction. */\nexport function isNodeClass(impl: any): impl is NodeClass {\n\treturn typeof impl === 'function' && !!impl.prototype?.exec\n}\n\n/**\n * A structured, class-based node for complex logic with a safe, granular lifecycle.\n * This class is generic, allowing implementations to specify the exact context\n * and dependency types they expect.\n */\nexport abstract class BaseNode<\n\tTContext extends Record<string, any> = Record<string, any>,\n\tTDependencies extends RuntimeDependencies = RuntimeDependencies,\n\tTInput = any,\n\tTOutput = any,\n\tTAction extends string = string,\n> {\n\t/**\n\t * @param params Static parameters for this node instance, passed from the blueprint.\n\t */\n\tconstructor(protected params?: Record<string, any>) {}\n\n\t/**\n\t * Phase 1: Gathers and prepares data for execution. This phase is NOT retried on failure.\n\t * @param context The node's execution context.\n\t * @returns The data needed for the `exec` phase.\n\t */\n\tasync prep(context: NodeContext<TContext, TDependencies, TInput>): Promise<any> {\n\t\treturn context.input\n\t}\n\n\t/**\n\t * Phase 2: Performs the core, isolated logic. This is the ONLY phase that is retried.\n\t * @param prepResult The data returned from the `prep` phase.\n\t * @param context The node's execution context.\n\t */\n\tabstract exec(\n\t\tprepResult: any,\n\t\tcontext: NodeContext<TContext, TDependencies, TInput>,\n\t): Promise<Omit<NodeResult<TOutput, TAction>, 'error'>>\n\n\t/**\n\t * Phase 3: Processes the result and saves state. This phase is NOT retried.\n\t * @param execResult The successful result from the `exec` or `fallback` phase.\n\t * @param _context The node's execution context.\n\t */\n\tasync post(\n\t\texecResult: Omit<NodeResult<TOutput, TAction>, 'error'>,\n\t\t_context: NodeContext<TContext, TDependencies, TInput>,\n\t): Promise<NodeResult<TOutput, TAction>> {\n\t\treturn execResult\n\t}\n\n\t/**\n\t * An optional safety net that runs if all `exec` retries fail.\n\t * @param error The final error from the last `exec` attempt.\n\t * @param _context The node's execution context.\n\t */\n\tasync fallback(\n\t\terror: Error,\n\t\t_context: NodeContext<TContext, TDependencies, TInput>,\n\t): Promise<Omit<NodeResult<TOutput, TAction>, 'error'>> {\n\t\t// By default, re-throw the error, failing the node.\n\t\tthrow error\n\t}\n\n\t/**\n\t * An optional cleanup phase for non-retriable errors that occur outside the main `exec` method.\n\t * This method is invoked in a `finally` block or equivalent construct if a fatal, unhandled exception occurs in the `prep`, `exec`, or `post` phases.\n\t * Allows nodes to perform crucial cleanup, such as closing database connections or releasing locks.\n\t * @param _error The error that caused the failure.\n\t * @param _context The node's execution context.\n\t */\n\tasync recover(_error: Error, _context: NodeContext<TContext, TDependencies, TInput>): Promise<void> {\n\t\t// Default no-op implementation. Subclasses can override for cleanup.\n\t}\n}\n"]}