flowcraft 2.10.0 → 2.10.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 (127) hide show
  1. package/README.md +3 -3
  2. package/dist/adapter-DzeZVjSE.d.mts +133 -0
  3. package/dist/adapters/index.d.mts +2 -0
  4. package/dist/adapters/index.mjs +3 -0
  5. package/dist/adapters/persistent-event-bus.d.mts +2 -0
  6. package/dist/adapters/persistent-event-bus.mjs +59 -0
  7. package/dist/analysis-B5Twr7sD.d.mts +52 -0
  8. package/dist/analysis.d.mts +2 -0
  9. package/dist/analysis.mjs +164 -0
  10. package/dist/batch-gather-BhF-IzQR.d.mts +8 -0
  11. package/dist/batch-scatter-DD8TU0Wm.d.mts +8 -0
  12. package/dist/container-BKdd-9wf.d.mts +24 -0
  13. package/dist/container-factory-fDY2kkxt.d.mts +17 -0
  14. package/dist/container-factory.d.mts +2 -0
  15. package/dist/container-factory.mjs +23 -0
  16. package/dist/container.d.mts +2 -0
  17. package/dist/container.mjs +43 -0
  18. package/dist/context-ZVtzXuZu.d.mts +64 -0
  19. package/dist/context.d.mts +2 -0
  20. package/dist/context.mjs +145 -0
  21. package/dist/error-mapper-BAv_YQMQ.d.mts +14 -0
  22. package/dist/error-mapper.d.mts +2 -0
  23. package/dist/error-mapper.mjs +37 -0
  24. package/dist/errors-CyyIj3OO.d.mts +21 -0
  25. package/dist/errors.d.mts +2 -0
  26. package/dist/errors.mjs +24 -0
  27. package/dist/evaluator-Dnj5qJ92.d.mts +31 -0
  28. package/dist/evaluator.d.mts +2 -0
  29. package/dist/evaluator.mjs +80 -0
  30. package/dist/flow-CZGpYpl-.d.mts +94 -0
  31. package/dist/flow.d.mts +2 -0
  32. package/dist/flow.mjs +328 -0
  33. package/dist/index-9iG2qHLe.d.mts +1 -0
  34. package/dist/index-Bk0eNZmQ.d.mts +1 -0
  35. package/dist/index-CNgSR_kt.d.mts +1 -0
  36. package/dist/index-CW2WHUXP.d.mts +1 -0
  37. package/dist/index.d.mts +24 -1
  38. package/dist/index.mjs +31 -791
  39. package/dist/linter-B8KALEae.d.mts +25 -0
  40. package/dist/linter.d.mts +2 -0
  41. package/dist/linter.mjs +74 -0
  42. package/dist/logger-BvDgvNHQ.d.mts +19 -0
  43. package/dist/logger.d.mts +2 -0
  44. package/dist/logger.mjs +26 -0
  45. package/dist/node.d.mts +2 -0
  46. package/dist/node.mjs +55 -0
  47. package/dist/nodes/batch-gather.d.mts +2 -0
  48. package/dist/nodes/batch-gather.mjs +47 -0
  49. package/dist/nodes/batch-scatter.d.mts +2 -0
  50. package/dist/nodes/batch-scatter.mjs +52 -0
  51. package/dist/nodes/index.d.mts +7 -0
  52. package/dist/nodes/index.mjs +8 -0
  53. package/dist/nodes/sleep.d.mts +2 -0
  54. package/dist/nodes/sleep.mjs +41 -0
  55. package/dist/nodes/subflow.d.mts +2 -0
  56. package/dist/nodes/subflow.mjs +64 -0
  57. package/dist/nodes/wait.d.mts +2 -0
  58. package/dist/nodes/wait.mjs +12 -0
  59. package/dist/nodes/webhook.d.mts +2 -0
  60. package/dist/nodes/webhook.mjs +24 -0
  61. package/dist/orchestrator-DwMIJRFI.d.mts +8 -0
  62. package/dist/persistent-event-bus-COiQOpWh.d.mts +68 -0
  63. package/dist/replay-CVOy6d_L.d.mts +44 -0
  64. package/dist/runtime/adapter.d.mts +2 -0
  65. package/dist/runtime/adapter.mjs +349 -0
  66. package/dist/runtime/builtin-keys.d.mts +37 -0
  67. package/dist/runtime/builtin-keys.mjs +12 -0
  68. package/dist/runtime/execution-context.d.mts +2 -0
  69. package/dist/runtime/execution-context.mjs +26 -0
  70. package/dist/runtime/executors.d.mts +2 -0
  71. package/dist/runtime/executors.mjs +259 -0
  72. package/dist/runtime/index.d.mts +6 -0
  73. package/dist/runtime/index.mjs +10 -0
  74. package/dist/runtime/node-executor-factory.d.mts +11 -0
  75. package/dist/runtime/node-executor-factory.mjs +41 -0
  76. package/dist/runtime/orchestrator.d.mts +2 -0
  77. package/dist/runtime/orchestrator.mjs +41 -0
  78. package/dist/runtime/orchestrators/replay.d.mts +2 -0
  79. package/dist/{replay-BB11M6K1.mjs → runtime/orchestrators/replay.mjs} +1 -20
  80. package/dist/runtime/orchestrators/step-by-step.d.mts +15 -0
  81. package/dist/runtime/orchestrators/step-by-step.mjs +41 -0
  82. package/dist/runtime/orchestrators/utils.d.mts +2 -0
  83. package/dist/runtime/orchestrators/utils.mjs +79 -0
  84. package/dist/runtime/runtime.d.mts +2 -0
  85. package/dist/runtime/runtime.mjs +425 -0
  86. package/dist/runtime/scheduler.d.mts +2 -0
  87. package/dist/runtime/scheduler.mjs +64 -0
  88. package/dist/runtime/state.d.mts +2 -0
  89. package/dist/runtime/state.mjs +127 -0
  90. package/dist/runtime/traverser.d.mts +2 -0
  91. package/dist/runtime/traverser.mjs +213 -0
  92. package/dist/runtime/types.d.mts +2 -0
  93. package/dist/runtime/types.mjs +1 -0
  94. package/dist/runtime/workflow-logic-handler.d.mts +16 -0
  95. package/dist/runtime/workflow-logic-handler.mjs +159 -0
  96. package/dist/sanitizer-Bi00YjvO.d.mts +11 -0
  97. package/dist/sanitizer.d.mts +2 -0
  98. package/dist/sanitizer.mjs +37 -0
  99. package/dist/sdk.d.mts +1 -2
  100. package/dist/sdk.mjs +1 -2
  101. package/dist/serializer-BnmJr13R.d.mts +17 -0
  102. package/dist/serializer.d.mts +2 -0
  103. package/dist/serializer.mjs +34 -0
  104. package/dist/sleep-DpwYaY5b.d.mts +8 -0
  105. package/dist/subflow-n2IMsRe2.d.mts +8 -0
  106. package/dist/testing/event-logger.d.mts +62 -0
  107. package/dist/testing/event-logger.mjs +98 -0
  108. package/dist/testing/index.d.mts +5 -172
  109. package/dist/testing/index.mjs +6 -276
  110. package/dist/testing/run-with-trace.d.mts +37 -0
  111. package/dist/testing/run-with-trace.mjs +49 -0
  112. package/dist/testing/stepper.d.mts +78 -0
  113. package/dist/testing/stepper.mjs +100 -0
  114. package/dist/types-BcrXJEPI.d.mts +687 -0
  115. package/dist/types.d.mts +2 -0
  116. package/dist/types.mjs +1 -0
  117. package/dist/utils-BUEgr9V2.d.mts +34 -0
  118. package/dist/wait-2Q-LA7V7.d.mts +8 -0
  119. package/dist/webhook-BiCm-HLx.d.mts +12 -0
  120. package/package.json +4 -4
  121. package/dist/index-BXRN44Qf.d.mts +0 -1347
  122. package/dist/index.mjs.map +0 -1
  123. package/dist/replay-BB11M6K1.mjs.map +0 -1
  124. package/dist/runtime-ChsWirQN.mjs +0 -2256
  125. package/dist/runtime-ChsWirQN.mjs.map +0 -1
  126. package/dist/sdk.mjs.map +0 -1
  127. package/dist/testing/index.mjs.map +0 -1
@@ -1,2256 +0,0 @@
1
- //#region src/adapters/persistent-event-bus.ts
2
- /**
3
- * A pluggable event bus adapter that persists all workflow events
4
- * to a configurable storage backend, enabling time-travel debugging and replay.
5
- *
6
- * @example
7
- * ```typescript
8
- * // Using a database-backed store
9
- * const eventStore = new DatabaseEventStore(dbConnection)
10
- * const eventBus = new PersistentEventBusAdapter(eventStore)
11
- * const runtime = new FlowRuntime({ eventBus })
12
- *
13
- * // Later, replay the execution
14
- * const events = await eventStore.retrieve(executionId)
15
- * const finalState = await runtime.replay(blueprint, events)
16
- * ```
17
- */
18
- var PersistentEventBusAdapter = class {
19
- constructor(store) {
20
- this.store = store;
21
- }
22
- /**
23
- * Emit an event by storing it persistently.
24
- * Also emits to console for debugging (can be made configurable).
25
- */
26
- async emit(event) {
27
- let executionId = "unknown";
28
- if ("executionId" in event.payload) executionId = event.payload.executionId;
29
- await this.store.store(event, executionId);
30
- }
31
- };
32
- /**
33
- * Simple in-memory event store for testing and development.
34
- * Not suitable for production use.
35
- */
36
- var InMemoryEventStore = class {
37
- events = /* @__PURE__ */ new Map();
38
- async store(event, executionId) {
39
- if (!this.events.has(executionId)) this.events.set(executionId, []);
40
- this.events.get(executionId)?.push(event);
41
- }
42
- async retrieve(executionId) {
43
- return this.events.get(executionId) || [];
44
- }
45
- async retrieveMultiple(executionIds) {
46
- const result = /* @__PURE__ */ new Map();
47
- for (const id of executionIds) result.set(id, await this.retrieve(id));
48
- return result;
49
- }
50
- /**
51
- * Clear all stored events (useful for testing).
52
- */
53
- clear() {
54
- this.events.clear();
55
- }
56
- };
57
-
58
- //#endregion
59
- //#region src/analysis.ts
60
- /**
61
- * Analyzes a workflow blueprint to detect cycles using an iterative DFS algorithm.
62
- * This avoids stack overflow issues for deep graphs compared to the recursive version.
63
- * @param blueprint The WorkflowBlueprint object containing nodes and edges.
64
- * @returns An array of cycles found. Each cycle is represented as an array of node IDs.
65
- */
66
- function checkForCycles(blueprint) {
67
- const cycles = [];
68
- if (!blueprint?.nodes || blueprint.nodes.length === 0) return cycles;
69
- const allNodeIds = blueprint.nodes.map((node) => node.id);
70
- const adj = /* @__PURE__ */ new Map();
71
- for (const id of allNodeIds) adj.set(id, []);
72
- for (const edge of blueprint.edges) adj.get(edge.source)?.push(edge.target);
73
- const state = /* @__PURE__ */ new Map();
74
- for (const id of allNodeIds) state.set(id, 0);
75
- for (const startNode of allNodeIds) {
76
- if (state.get(startNode) !== 0) continue;
77
- const stack = [{
78
- node: startNode,
79
- path: []
80
- }];
81
- const pathSet = /* @__PURE__ */ new Set();
82
- while (stack.length > 0) {
83
- const { node, path } = stack[stack.length - 1];
84
- if (state.get(node) === 0) {
85
- state.set(node, 1);
86
- pathSet.add(node);
87
- path.push(node);
88
- }
89
- const neighbors = adj.get(node) || [];
90
- let foundUnvisited = false;
91
- for (const neighbor of neighbors) if (state.get(neighbor) === 1) {
92
- const cycleStartIndex = path.indexOf(neighbor);
93
- const cycle = path.slice(cycleStartIndex);
94
- cycles.push([...cycle, neighbor]);
95
- } else if (state.get(neighbor) === 0) {
96
- stack.push({
97
- node: neighbor,
98
- path: [...path]
99
- });
100
- foundUnvisited = true;
101
- break;
102
- }
103
- if (!foundUnvisited) {
104
- state.set(node, 2);
105
- stack.pop();
106
- pathSet.delete(node);
107
- }
108
- }
109
- }
110
- return cycles;
111
- }
112
- /**
113
- * Generates Mermaid diagram syntax from a WorkflowBlueprint
114
- * @param blueprint The WorkflowBlueprint object containing nodes and edges
115
- * @returns Mermaid syntax string for the flowchart
116
- */
117
- function generateMermaid(blueprint) {
118
- if (!blueprint?.nodes || blueprint.nodes.length === 0) return "flowchart TD\n empty[Empty Blueprint]";
119
- let mermaid = "flowchart TD\n";
120
- for (const node of blueprint.nodes) {
121
- const paramsString = node.params ? `<br/>params: ${JSON.stringify(node.params)}` : "";
122
- const nodeLabel = `${node.id}${paramsString}`;
123
- mermaid += ` ${node.id}["${nodeLabel}"]\n`;
124
- }
125
- for (const edge of blueprint.edges || []) {
126
- const labelParts = [];
127
- if (edge.action) labelParts.push(edge.action);
128
- if (edge.condition) labelParts.push(edge.condition);
129
- if (edge.transform) labelParts.push(edge.transform);
130
- if (labelParts.length > 0) {
131
- const edgeLabel = labelParts.join(" | ");
132
- mermaid += ` ${edge.source} -- "${edgeLabel}" --> ${edge.target}\n`;
133
- } else mermaid += ` ${edge.source} --> ${edge.target}\n`;
134
- }
135
- return mermaid;
136
- }
137
- /**
138
- * Generates Mermaid diagram syntax from a WorkflowBlueprint with execution history styling
139
- * @param blueprint The WorkflowBlueprint object containing nodes and edges
140
- * @param events Array of FlowcraftEvent objects from the workflow execution
141
- * @returns Mermaid syntax string for the flowchart with execution path highlighting
142
- */
143
- function generateMermaidForRun(blueprint, events) {
144
- if (!blueprint?.nodes || blueprint.nodes.length === 0) return "flowchart TD\n empty[Empty Blueprint]";
145
- let mermaid = "flowchart TD\n";
146
- const successfulNodes = /* @__PURE__ */ new Set();
147
- const failedNodes = /* @__PURE__ */ new Set();
148
- const takenEdges = /* @__PURE__ */ new Set();
149
- for (const event of events) switch (event.type) {
150
- case "node:finish":
151
- successfulNodes.add(event.payload.nodeId);
152
- break;
153
- case "node:error":
154
- failedNodes.add(event.payload.nodeId);
155
- break;
156
- case "edge:evaluate":
157
- if (event.payload.result) {
158
- const edgeKey = `${event.payload.source}->${event.payload.target}`;
159
- takenEdges.add(edgeKey);
160
- }
161
- break;
162
- }
163
- for (const node of blueprint.nodes) {
164
- const paramsString = node.params ? `<br/>params: ${JSON.stringify(node.params)}` : "";
165
- const nodeLabel = `${node.id}${paramsString}`;
166
- mermaid += ` ${node.id}["${nodeLabel}"]\n`;
167
- }
168
- for (const node of blueprint.nodes) if (successfulNodes.has(node.id)) mermaid += ` style ${node.id} fill:#d4edda,stroke:#c3e6cb\n`;
169
- else if (failedNodes.has(node.id)) mermaid += ` style ${node.id} fill:#f8d7da,stroke:#f5c6cb\n`;
170
- let edgeIndex = 0;
171
- for (const edge of blueprint.edges || []) {
172
- const labelParts = [];
173
- if (edge.action) labelParts.push(edge.action);
174
- if (edge.condition) labelParts.push(edge.condition);
175
- if (edge.transform) labelParts.push(edge.transform);
176
- const edgeKey = `${edge.source}->${edge.target}`;
177
- const isTaken = takenEdges.has(edgeKey);
178
- let edgeLine;
179
- if (labelParts.length > 0) {
180
- const edgeLabel = labelParts.join(" | ");
181
- edgeLine = ` ${edge.source} -- "${edgeLabel}" --> ${edge.target}\n`;
182
- } else edgeLine = ` ${edge.source} --> ${edge.target}\n`;
183
- mermaid += edgeLine;
184
- if (isTaken) mermaid += ` linkStyle ${edgeIndex} stroke:#007bff,stroke-width:3px\n`;
185
- edgeIndex++;
186
- }
187
- return mermaid;
188
- }
189
- /**
190
- * Analyzes a workflow blueprint and returns comprehensive analysis
191
- * @param blueprint The WorkflowBlueprint object containing nodes and edges
192
- * @returns Analysis result with cycles, start nodes, terminal nodes, and other metrics
193
- */
194
- function analyzeBlueprint(blueprint) {
195
- if (!blueprint?.nodes || blueprint.nodes.length === 0) return {
196
- cycles: [],
197
- startNodeIds: [],
198
- terminalNodeIds: [],
199
- nodeCount: 0,
200
- edgeCount: 0,
201
- isDag: true
202
- };
203
- const cycles = checkForCycles(blueprint);
204
- const nodeCount = blueprint.nodes.length;
205
- const edgeCount = blueprint.edges?.length || 0;
206
- const nodesWithIncoming = /* @__PURE__ */ new Set();
207
- for (const edge of blueprint.edges || []) nodesWithIncoming.add(edge.target);
208
- const startNodeIds = blueprint.nodes.map((node) => node.id).filter((nodeId) => !nodesWithIncoming.has(nodeId));
209
- const nodesWithOutgoing = /* @__PURE__ */ new Set();
210
- for (const edge of blueprint.edges || []) nodesWithOutgoing.add(edge.source);
211
- return {
212
- cycles,
213
- startNodeIds,
214
- terminalNodeIds: blueprint.nodes.map((node) => node.id).filter((nodeId) => !nodesWithOutgoing.has(nodeId)),
215
- nodeCount,
216
- edgeCount,
217
- isDag: cycles.length === 0
218
- };
219
- }
220
-
221
- //#endregion
222
- //#region src/container.ts
223
- var DIContainer = class DIContainer {
224
- services = /* @__PURE__ */ new Map();
225
- factories = /* @__PURE__ */ new Map();
226
- register(token, implementation) {
227
- this.services.set(token, implementation);
228
- }
229
- registerFactory(token, factory) {
230
- this.factories.set(token, factory);
231
- }
232
- resolve(token) {
233
- if (this.services.has(token)) return this.services.get(token);
234
- if (this.factories.has(token)) {
235
- const instance = this.factories.get(token)?.(this);
236
- this.services.set(token, instance);
237
- return instance;
238
- }
239
- throw new Error(`Service not found for token: ${String(token)}`);
240
- }
241
- has(token) {
242
- return this.services.has(token) || this.factories.has(token);
243
- }
244
- createChild() {
245
- const child = new DIContainer();
246
- child.services = new Map(this.services);
247
- child.factories = new Map(this.factories);
248
- return child;
249
- }
250
- };
251
- const ServiceTokens = {
252
- Logger: Symbol.for("flowcraft:logger"),
253
- Serializer: Symbol.for("flowcraft:serializer"),
254
- Evaluator: Symbol.for("flowcraft:evaluator"),
255
- EventBus: Symbol.for("flowcraft:eventBus"),
256
- Orchestrator: Symbol.for("flowcraft:orchestrator"),
257
- Middleware: Symbol.for("flowcraft:middleware"),
258
- NodeRegistry: Symbol.for("flowcraft:nodeRegistry"),
259
- BlueprintRegistry: Symbol.for("flowcraft:blueprintRegistry"),
260
- Dependencies: Symbol.for("flowcraft:dependencies")
261
- };
262
-
263
- //#endregion
264
- //#region src/evaluator.ts
265
- /**
266
- * A safe evaluator that only allows simple property access.
267
- * It cannot execute arbitrary code and is secure for untrusted inputs.
268
- *
269
- * Example expressions:
270
- * - "result.output.status"
271
- * - "context.user.isAdmin"
272
- * - "input.value"
273
- */
274
- var PropertyEvaluator = class {
275
- evaluate(expression, context) {
276
- try {
277
- if (!/^[a-zA-Z0-9_$.]+$/.test(expression)) {
278
- console.error(`Error evaluating expression: "${expression}" contains invalid characters.`);
279
- return;
280
- }
281
- const parts = expression.split(".");
282
- const startKey = parts[0];
283
- if (!Object.hasOwn(context, startKey)) return;
284
- let current = context[startKey];
285
- for (let i = 1; i < parts.length; i++) {
286
- if (current === null || current === void 0) return;
287
- current = current[parts[i]];
288
- }
289
- return current;
290
- } catch (error) {
291
- console.error(`Error evaluating property expression "${expression}":`, error);
292
- return;
293
- }
294
- }
295
- };
296
- /**
297
- * Rewrites an expression so that hyphenated identifiers use bracket notation.
298
- * E.g. "foo-bar.total" → 'context["foo-bar"].total'
299
- */
300
- function rewriteHyphenatedIdentifiers(expression, hyphenatedKeys) {
301
- let result = expression;
302
- const sortedKeys = [...hyphenatedKeys].toSorted((a, b) => b.length - a.length);
303
- for (const key of sortedKeys) {
304
- if (!key.includes("-")) continue;
305
- const regex = new RegExp(`(?<![\\w$])${escapeRegex(key)}(?![\\w$-])`, "g");
306
- result = result.replace(regex, `context["${key}"]`);
307
- }
308
- return result;
309
- }
310
- function escapeRegex(str) {
311
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
312
- }
313
- /**
314
- * @warning This evaluator uses `new Function()` and can execute arbitrary
315
- * JavaScript code. It poses a significant security risk if the expressions
316
- * are not from a trusted source (e.g., user input).
317
- *
318
- * It should only be used in controlled environments where all workflow
319
- * definitions are static and authored by trusted developers.
320
- *
321
- * For safer evaluation, use the default `PropertyEvaluator` or install a
322
- * sandboxed library like `jsep` to create a custom, secure evaluator.
323
- */
324
- var UnsafeEvaluator = class {
325
- evaluate(expression, context) {
326
- try {
327
- const validIdentifierRegex = /^[a-z_$][\w$]*$/i;
328
- const validKeys = Object.keys(context).filter((key) => validIdentifierRegex.test(key));
329
- const hyphenatedKeys = Object.keys(context).filter((key) => !validIdentifierRegex.test(key) && /^[a-zA-Z0-9_$-]+$/.test(key));
330
- const validContext = {};
331
- for (const key of validKeys) validContext[key] = context[key];
332
- for (const key of hyphenatedKeys) validContext[key] = context[key];
333
- let rewrittenExpression = rewriteHyphenatedIdentifiers(expression, hyphenatedKeys);
334
- return new Function("context", ...validKeys, `return ${rewrittenExpression}`)(validContext, ...validKeys.map((k) => validContext[k]));
335
- } catch (error) {
336
- console.error(`Error evaluating expression "${expression}":`, error);
337
- return;
338
- }
339
- }
340
- };
341
-
342
- //#endregion
343
- //#region src/logger.ts
344
- /** A logger implementation that outputs to the console. */
345
- var ConsoleLogger = class {
346
- debug(message, meta) {
347
- console.debug(`[DEBUG] ${message}`, meta || "");
348
- }
349
- info(message, meta) {
350
- console.info(`[INFO] ${message}`, meta || "");
351
- }
352
- warn(message, meta) {
353
- console.warn(`[WARN] ${message}`, meta || "");
354
- }
355
- error(message, meta) {
356
- console.error(`[ERROR] ${message}`, meta || "");
357
- }
358
- };
359
- /** A logger implementation that does nothing (no-op). */
360
- var NullLogger = class {
361
- debug(_message, _meta) {}
362
- info(_message, _meta) {}
363
- warn(_message, _meta) {}
364
- error(_message, _meta) {}
365
- };
366
-
367
- //#endregion
368
- //#region src/errors.ts
369
- /**
370
- * A single, comprehensive error class for the framework.
371
- * Use this for all errors to ensure consistent structure and easy debugging.
372
- */
373
- var FlowcraftError = class extends Error {
374
- message;
375
- nodeId;
376
- blueprintId;
377
- executionId;
378
- isFatal;
379
- constructor(message, options = {}) {
380
- super(message, { cause: options.cause });
381
- this.name = "FlowcraftError";
382
- this.message = message;
383
- this.nodeId = options.nodeId;
384
- this.blueprintId = options.blueprintId;
385
- this.executionId = options.executionId;
386
- this.isFatal = options.isFatal ?? false;
387
- }
388
- };
389
-
390
- //#endregion
391
- //#region src/context.ts
392
- /**
393
- * A default, high-performance, in-memory implementation of ISyncContext using a Map.
394
- */
395
- var Context = class {
396
- type = "sync";
397
- data;
398
- constructor(initialData = {}) {
399
- this.data = new Map(Object.entries(initialData));
400
- }
401
- get(key) {
402
- return this.data.get(key);
403
- }
404
- set(key, value) {
405
- this.data.set(key, value);
406
- }
407
- has(key) {
408
- return this.data.has(key);
409
- }
410
- delete(key) {
411
- return this.data.delete(key);
412
- }
413
- toJSON() {
414
- return Object.fromEntries(this.data);
415
- }
416
- };
417
- /**
418
- * An adapter that provides a consistent, Promise-based view of a synchronous context.
419
- * This is created by the runtime and is transparent to the node author.
420
- */
421
- var AsyncContextView = class {
422
- type = "async";
423
- constructor(syncContext) {
424
- this.syncContext = syncContext;
425
- }
426
- get(key) {
427
- return Promise.resolve(this.syncContext.get(key));
428
- }
429
- set(key, value) {
430
- this.syncContext.set(key, value);
431
- return Promise.resolve();
432
- }
433
- has(key) {
434
- return Promise.resolve(this.syncContext.has(key));
435
- }
436
- delete(key) {
437
- return Promise.resolve(this.syncContext.delete(key));
438
- }
439
- toJSON() {
440
- return Promise.resolve(this.syncContext.toJSON());
441
- }
442
- async patch(_operations) {
443
- throw new Error("Patch operations not supported by AsyncContextView");
444
- }
445
- };
446
- /**
447
- * A proxy wrapper that tracks changes to an async context for delta-based persistence.
448
- * Records all mutations (set/delete operations) to enable efficient partial updates.
449
- */
450
- var TrackedAsyncContext = class {
451
- type = "async";
452
- deltas = [];
453
- innerContext;
454
- eventBus;
455
- executionId;
456
- sourceNode;
457
- constructor(innerContext, eventBus, executionId, sourceNode) {
458
- this.innerContext = innerContext;
459
- this.eventBus = eventBus;
460
- this.executionId = executionId;
461
- this.sourceNode = sourceNode;
462
- }
463
- async get(key) {
464
- return this.innerContext.get(key);
465
- }
466
- async set(key, value) {
467
- this.deltas.push({
468
- op: "set",
469
- key,
470
- value
471
- });
472
- await this.innerContext.set(key, value);
473
- if (this.eventBus && this.executionId) await this.eventBus.emit({
474
- type: "context:change",
475
- payload: {
476
- sourceNode: this.sourceNode || "unknown",
477
- key,
478
- op: "set",
479
- value,
480
- executionId: this.executionId
481
- }
482
- });
483
- }
484
- async has(key) {
485
- return this.innerContext.has(key);
486
- }
487
- async delete(key) {
488
- this.deltas.push({
489
- op: "delete",
490
- key
491
- });
492
- const result = await this.innerContext.delete(key);
493
- if (this.eventBus && this.executionId && result) await this.eventBus.emit({
494
- type: "context:change",
495
- payload: {
496
- sourceNode: this.sourceNode || "unknown",
497
- key,
498
- op: "delete",
499
- executionId: this.executionId
500
- }
501
- });
502
- return result;
503
- }
504
- toJSON() {
505
- return this.innerContext.toJSON();
506
- }
507
- async patch(operations) {
508
- if (this.innerContext.patch) return this.innerContext.patch(operations);
509
- for (const op of operations) if (op.op === "set") await this.innerContext.set(op.key, op.value);
510
- else if (op.op === "delete") await this.innerContext.delete(op.key);
511
- }
512
- getDeltas() {
513
- return [...this.deltas];
514
- }
515
- clearDeltas() {
516
- this.deltas = [];
517
- }
518
- /**
519
- * Configures the event emitter for tracking context changes.
520
- * This enables the context to emit events when set/delete operations occur,
521
- * allowing for external monitoring and persistence of context mutations.
522
- *
523
- * @param eventBus - The event bus instance to emit context change events
524
- * @param executionId - The unique identifier for the current workflow execution
525
- * @param sourceNode - Optional identifier for the node that triggered the context change
526
- */
527
- configureEventEmitter(eventBus, executionId, sourceNode) {
528
- this.eventBus = eventBus;
529
- this.executionId = executionId;
530
- this.sourceNode = sourceNode;
531
- }
532
- };
533
-
534
- //#endregion
535
- //#region src/runtime/state.ts
536
- var WorkflowState = class {
537
- _completedNodes = /* @__PURE__ */ new Set();
538
- errors = [];
539
- anyFallbackExecuted = false;
540
- context;
541
- _isAwaiting = false;
542
- _awaitingNodeIds = /* @__PURE__ */ new Set();
543
- _awaitingDetails = /* @__PURE__ */ new Map();
544
- isLastAttempt;
545
- constructor(initialData, context) {
546
- if (context) this.context = context instanceof TrackedAsyncContext ? context : new TrackedAsyncContext(context);
547
- else this.context = new TrackedAsyncContext(new AsyncContextView(new Context(initialData)));
548
- if (initialData._awaitingNodeIds) {
549
- this._isAwaiting = true;
550
- const awaitingIds = initialData._awaitingNodeIds;
551
- if (Array.isArray(awaitingIds)) for (const id of awaitingIds) this._awaitingNodeIds.add(id);
552
- }
553
- if (initialData._awaitingDetails) this._awaitingDetails = new Map(Object.entries(initialData._awaitingDetails));
554
- for (const key of Object.keys(initialData)) if (key.startsWith("_outputs.")) {
555
- const nodeId = key.substring(9);
556
- this._completedNodes.add(nodeId);
557
- }
558
- }
559
- /**
560
- * Configure the context to emit events when modified.
561
- * This is called after the ExecutionContext is created.
562
- */
563
- setEventEmitter(eventBus, executionId, sourceNode) {
564
- if (this.context instanceof TrackedAsyncContext) this.context.configureEventEmitter(eventBus, executionId, sourceNode);
565
- }
566
- async addCompletedNode(nodeId, output) {
567
- this._completedNodes.add(nodeId);
568
- await this.context.set(`_outputs.${nodeId}`, output);
569
- await this.context.set(nodeId, output);
570
- }
571
- addError(nodeId, error) {
572
- const flowcraftError = new FlowcraftError(error.message, {
573
- cause: error,
574
- nodeId,
575
- isFatal: false
576
- });
577
- this.errors.push({
578
- ...flowcraftError,
579
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
580
- originalError: error
581
- });
582
- }
583
- clearError(nodeId) {
584
- this.errors = this.errors.filter((err) => err.nodeId !== nodeId);
585
- }
586
- markFallbackExecuted() {
587
- this.anyFallbackExecuted = true;
588
- }
589
- getContext() {
590
- return this.context;
591
- }
592
- getCompletedNodes() {
593
- return new Set(this._completedNodes);
594
- }
595
- getErrors() {
596
- return this.errors;
597
- }
598
- getAnyFallbackExecuted() {
599
- return this.anyFallbackExecuted;
600
- }
601
- async markAsAwaiting(nodeId, details) {
602
- this._isAwaiting = true;
603
- this._awaitingNodeIds.add(nodeId);
604
- if (details) this._awaitingDetails.set(nodeId, details);
605
- await this.context.set("_awaitingNodeIds", Array.from(this._awaitingNodeIds));
606
- await this.context.set("_awaitingDetails", Object.fromEntries(this._awaitingDetails));
607
- }
608
- isAwaiting() {
609
- return this._isAwaiting && this._awaitingNodeIds.size > 0;
610
- }
611
- getAwaitingNodeIds() {
612
- return Array.from(this._awaitingNodeIds);
613
- }
614
- getAwaitingDetails(nodeId) {
615
- return this._awaitingDetails.get(nodeId);
616
- }
617
- clearAwaiting(nodeId) {
618
- if (nodeId) {
619
- this._awaitingNodeIds.delete(nodeId);
620
- this._awaitingDetails.delete(nodeId);
621
- } else {
622
- this._awaitingNodeIds.clear();
623
- this._awaitingDetails.clear();
624
- }
625
- this._isAwaiting = this._awaitingNodeIds.size > 0;
626
- if (this._awaitingNodeIds.size > 0) {
627
- this.context.set("_awaitingNodeIds", Array.from(this._awaitingNodeIds));
628
- this.context.set("_awaitingDetails", Object.fromEntries(this._awaitingDetails));
629
- } else {
630
- this.context.delete("_awaitingNodeIds");
631
- this.context.delete("_awaitingDetails");
632
- }
633
- }
634
- getStatus(isTraversalComplete = false) {
635
- if (this._isAwaiting) return "awaiting";
636
- if (this.anyFallbackExecuted) return "completed";
637
- if (this.errors.length > 0) return "failed";
638
- if (isTraversalComplete) return "completed";
639
- return "stalled";
640
- }
641
- async toResult(serializer, executionId) {
642
- const contextJSON = await this.context.toJSON();
643
- if (!this._isAwaiting && contextJSON._awaitingNodeIds) {
644
- delete contextJSON._awaitingNodeIds;
645
- delete contextJSON._awaitingDetails;
646
- }
647
- if (executionId) contextJSON._executionId = executionId;
648
- return {
649
- context: contextJSON,
650
- serializedContext: serializer.serialize(contextJSON),
651
- status: this.getStatus(),
652
- errors: this.errors.length > 0 ? this.errors : void 0
653
- };
654
- }
655
- };
656
-
657
- //#endregion
658
- //#region src/runtime/execution-context.ts
659
- /**
660
- * A container for all state and dependencies of a single workflow execution.
661
- * This object is created once per `run` and passed through the execution stack.
662
- */
663
- var ExecutionContext = class ExecutionContext {
664
- constructor(blueprint, state, nodeRegistry, executionId, runtime, services, signal, concurrency) {
665
- this.blueprint = blueprint;
666
- this.state = state;
667
- this.nodeRegistry = nodeRegistry;
668
- this.executionId = executionId;
669
- this.runtime = runtime;
670
- this.services = services;
671
- this.signal = signal;
672
- this.concurrency = concurrency;
673
- this.state.setEventEmitter(this.services.eventBus, this.executionId);
674
- }
675
- createForSubflow(subBlueprint, initialSubState) {
676
- return new ExecutionContext(subBlueprint, new WorkflowState(initialSubState), this.nodeRegistry, this.executionId, this.runtime, this.services, this.signal, this.concurrency);
677
- }
678
- };
679
-
680
- //#endregion
681
- //#region src/runtime/orchestrators/utils.ts
682
- async function executeBatch(readyNodes, blueprint, state, executorFactory, runtime, maxConcurrency) {
683
- const concurrency = maxConcurrency || readyNodes.length;
684
- const results = [];
685
- for (let i = 0; i < readyNodes.length; i += concurrency) {
686
- const batchPromises = readyNodes.slice(i, i + concurrency).map(async ({ nodeId }) => {
687
- try {
688
- const executor = executorFactory(nodeId);
689
- if (!executor) throw new Error(`No executor for node ${nodeId}`);
690
- const executionResult = await executor.execute(await runtime.resolveNodeInput(nodeId, blueprint, state.getContext()));
691
- results.push({
692
- status: "fulfilled",
693
- value: {
694
- nodeId,
695
- executionResult
696
- }
697
- });
698
- } catch (error) {
699
- results.push({
700
- status: "rejected",
701
- reason: {
702
- nodeId,
703
- error
704
- }
705
- });
706
- }
707
- });
708
- await Promise.all(batchPromises);
709
- }
710
- return results;
711
- }
712
- async function processResults(settledResults, traverser, state, runtime, _blueprint, executionId) {
713
- for (const promiseResult of settledResults) {
714
- if (promiseResult.status === "rejected") {
715
- const { nodeId, error } = promiseResult.reason;
716
- if (error instanceof FlowcraftError && error.message.includes("cancelled")) throw error;
717
- state.addError(nodeId, error);
718
- continue;
719
- }
720
- const { nodeId, executionResult } = promiseResult.value;
721
- if (executionResult.status === "success") {
722
- const result = executionResult.result;
723
- if (result) {
724
- await state.addCompletedNode(nodeId, result.output);
725
- if (result._fallbackExecuted) state.markFallbackExecuted();
726
- if (result.dynamicNodes && result.dynamicNodes.length > 0) {
727
- const gatherNodeId = result.output?.gatherNodeId;
728
- for (const dynamicNode of result.dynamicNodes) traverser.addDynamicNode(dynamicNode.id, dynamicNode, nodeId, gatherNodeId);
729
- }
730
- }
731
- const matched = await runtime.determineNextNodes(traverser.getDynamicBlueprint(), nodeId, result, state.getContext(), executionId);
732
- for (const { node, edge } of matched) await runtime.applyEdgeTransform(edge, result, node, state.getContext(), traverser.getAllPredecessors(), executionId);
733
- traverser.markNodeCompleted(nodeId, result, matched.map((m) => m.node));
734
- } else if (executionResult.status === "failed_with_fallback") {
735
- const { fallbackNodeId, error } = executionResult;
736
- const fallbackNodeDef = traverser.getDynamicBlueprint().nodes.find((n) => n.id === fallbackNodeId);
737
- if (!fallbackNodeDef) {
738
- const notFoundError = new FlowcraftError(`Fallback node '${fallbackNodeId}' not found in blueprint.`, {
739
- nodeId,
740
- cause: error
741
- });
742
- state.addError(nodeId, notFoundError);
743
- } else {
744
- state.addCompletedNode(nodeId, null);
745
- state.markFallbackExecuted();
746
- traverser.markNodeCompleted(nodeId, {
747
- action: "fallback",
748
- output: null,
749
- _fallbackExecuted: true
750
- }, [fallbackNodeDef]);
751
- }
752
- } else state.addError(nodeId, executionResult.error);
753
- }
754
- }
755
-
756
- //#endregion
757
- //#region src/runtime/orchestrator.ts
758
- var DefaultOrchestrator = class {
759
- async run(context, traverser) {
760
- const hardwareConcurrency = globalThis.navigator?.hardwareConcurrency || 4;
761
- const maxConcurrency = context.concurrency != null && context.concurrency > 0 ? context.concurrency : Math.min(hardwareConcurrency, 10);
762
- try {
763
- context.signal?.throwIfAborted();
764
- } catch (error) {
765
- if (error instanceof DOMException && error.name === "AbortError") throw new FlowcraftError("Workflow cancelled", { isFatal: false });
766
- throw error;
767
- }
768
- let iterations = 0;
769
- const maxIterations = 1e4;
770
- while (traverser.hasMoreWork()) {
771
- if (++iterations > maxIterations) throw new Error("Traversal exceeded maximum iterations, possible infinite loop");
772
- try {
773
- context.signal?.throwIfAborted();
774
- } catch (error) {
775
- if (error instanceof DOMException && error.name === "AbortError") throw new FlowcraftError("Workflow cancelled", { isFatal: false });
776
- throw error;
777
- }
778
- const readyNodes = traverser.getReadyNodes();
779
- const dynamicBlueprint = traverser.getDynamicBlueprint();
780
- const updatedContext = new ExecutionContext(dynamicBlueprint, context.state, context.nodeRegistry, context.executionId, context.runtime, context.services, context.signal, context.concurrency);
781
- await processResults(await executeBatch(readyNodes, dynamicBlueprint, context.state, (nodeId) => context.runtime.getExecutorForNode(nodeId, updatedContext), context.runtime, maxConcurrency), traverser, context.state, context.runtime, context.blueprint, context.executionId);
782
- if (context.state.isAwaiting()) break;
783
- }
784
- const isTraversalComplete = !traverser.hasMoreWork();
785
- const status = context.state.getStatus(isTraversalComplete);
786
- const result = await context.state.toResult(context.services.serializer, context.executionId);
787
- result.status = status;
788
- return result;
789
- }
790
- };
791
-
792
- //#endregion
793
- //#region src/serializer.ts
794
- /**
795
- * A default serializer using standard JSON.
796
- *
797
- * @warning This implementation is lossy and does not handle complex data types
798
- * like `Date`, `Map`, `Set`, `undefined`, etc. It is recommended to provide a robust
799
- * serializer like `superjson` if working with complex data types.
800
- */
801
- var JsonSerializer = class {
802
- hasWarned = false;
803
- serialize(data) {
804
- for (const value of Object.values(data)) if (value instanceof Map || value instanceof Set || value instanceof Date) {
805
- if (!this.hasWarned) {
806
- console.warn("[Flowcraft] Warning: Default JsonSerializer does not support Map, Set, or Date types. Data may be lost. Consider providing a custom ISerializer (e.g., using superjson).");
807
- this.hasWarned = true;
808
- }
809
- }
810
- try {
811
- return JSON.stringify(data);
812
- } catch {
813
- console.warn("[Flowcraft] Warning: Circular reference detected in context. Using safe serialization.");
814
- return JSON.stringify({
815
- _circularReference: true,
816
- message: "Context contains circular references"
817
- });
818
- }
819
- }
820
- deserialize(text) {
821
- return JSON.parse(text);
822
- }
823
- };
824
-
825
- //#endregion
826
- //#region src/node.ts
827
- /** A type guard to reliably distinguish a NodeClass from a NodeFunction. */
828
- function isNodeClass(impl) {
829
- return typeof impl === "function" && !!impl.prototype?.exec;
830
- }
831
- /**
832
- * A structured, class-based node for complex logic with a safe, granular lifecycle.
833
- * This class is generic, allowing implementations to specify the exact context
834
- * and dependency types they expect.
835
- */
836
- var BaseNode = class {
837
- /**
838
- * @param params Static parameters for this node instance, passed from the blueprint.
839
- * @param nodeId The ID of the node in the blueprint.
840
- */
841
- constructor(params, nodeId) {
842
- this.params = params;
843
- this.nodeId = nodeId;
844
- }
845
- /**
846
- * Phase 1: Gathers and prepares data for execution. This phase is NOT retried on failure.
847
- * @param context The node's execution context.
848
- * @returns The data needed for the `exec` phase.
849
- */
850
- async prep(context) {
851
- return context.input;
852
- }
853
- /**
854
- * Phase 3: Processes the result and saves state. This phase is NOT retried.
855
- * @param execResult The successful result from the `exec` or `fallback` phase.
856
- * @param _context The node's execution context.
857
- */
858
- async post(execResult, _context) {
859
- return execResult;
860
- }
861
- /**
862
- * An optional safety net that runs if all `exec` retries fail.
863
- * @param error The final error from the last `exec` attempt.
864
- * @param _context The node's execution context.
865
- */
866
- async fallback(error, _context) {
867
- throw error;
868
- }
869
- /**
870
- * An optional cleanup phase for non-retriable errors that occur outside the main `exec` method.
871
- * This method is invoked in a `finally` block or equivalent construct if a fatal, unhandled exception occurs in the `prep`, `exec`, or `post` phases.
872
- * Allows nodes to perform crucial cleanup, such as closing database connections or releasing locks.
873
- * @param _error The error that caused the failure.
874
- * @param _context The node's execution context.
875
- */
876
- async recover(_error, _context) {}
877
- };
878
-
879
- //#endregion
880
- //#region src/nodes/batch-gather.ts
881
- var BatchGatherNode = class extends BaseNode {
882
- async exec(_prepResult, context) {
883
- const { gatherNodeId, outputKey } = this.params || {};
884
- const hasMore = await context.context.get(`${gatherNodeId}_hasMore`) || false;
885
- const dynamicNodes = [];
886
- let results = [];
887
- if (hasMore) {
888
- const newScatterId = `${gatherNodeId}_scatter_next`;
889
- dynamicNodes.push({
890
- id: newScatterId,
891
- uses: "batch-scatter",
892
- inputs: context.input,
893
- params: {
894
- ...this.params,
895
- gatherNodeId
896
- }
897
- });
898
- } else {
899
- const allWorkerIds = await context.context.get(`${gatherNodeId}_allWorkerIds`) || [];
900
- results = [];
901
- for (const workerId of allWorkerIds) {
902
- const result = await context.context.get(`_outputs.${workerId}`);
903
- if (result !== void 0) results.push(result);
904
- }
905
- await context.context.set(outputKey, results);
906
- const parentBatchId = gatherNodeId.replace("_gather", "");
907
- await context.dependencies.runtime.services.eventBus.emit({
908
- type: "batch:finish",
909
- payload: {
910
- batchId: parentBatchId,
911
- gatherNodeId,
912
- results
913
- }
914
- });
915
- }
916
- return {
917
- dynamicNodes,
918
- output: results
919
- };
920
- }
921
- };
922
-
923
- //#endregion
924
- //#region src/nodes/batch-scatter.ts
925
- var BatchScatterNode = class extends BaseNode {
926
- async exec(_prepResult, context) {
927
- const inputArray = context.input || [];
928
- if (!Array.isArray(inputArray)) throw new Error(`Input for batch-scatter node '${this.nodeId}' must be an array.`);
929
- const { chunkSize = inputArray.length, workerUsesKey, gatherNodeId } = this.params || {};
930
- if (!workerUsesKey || !gatherNodeId) throw new Error(`BatchScatterNode requires 'workerUsesKey' and 'gatherNodeId' parameters.`);
931
- const batchId = globalThis.crypto.randomUUID();
932
- const currentIndex = await context.context.get(`${this.nodeId}_currentIndex`) || 0;
933
- const endIndex = Math.min(currentIndex + chunkSize, inputArray.length);
934
- const dynamicNodes = [];
935
- const workerIds = [];
936
- for (let i = currentIndex; i < endIndex; i++) {
937
- const item = inputArray[i];
938
- const itemInputKey = `_batch.${this.nodeId}_${batchId}_item_${i}`;
939
- await context.context.set(itemInputKey, item);
940
- const workerId = `${workerUsesKey}_${batchId}_${i}`;
941
- workerIds.push(workerId);
942
- dynamicNodes.push({
943
- id: workerId,
944
- uses: workerUsesKey,
945
- inputs: itemInputKey
946
- });
947
- }
948
- const parentBatchId = this.nodeId?.replace("_scatter", "") || "";
949
- await context.dependencies.runtime.services.eventBus.emit({
950
- type: "batch:start",
951
- payload: {
952
- batchId: parentBatchId,
953
- scatterNodeId: this.nodeId,
954
- workerNodeIds: workerIds
955
- }
956
- });
957
- await context.context.set(`${this.nodeId}_currentIndex`, endIndex);
958
- const hasMore = endIndex < inputArray.length;
959
- await context.context.set(`${gatherNodeId}_hasMore`, hasMore);
960
- const allWorkerIds = [...await context.context.get(`${gatherNodeId}_allWorkerIds`) || [], ...workerIds];
961
- await context.context.set(`${gatherNodeId}_allWorkerIds`, allWorkerIds);
962
- return {
963
- dynamicNodes,
964
- output: {
965
- gatherNodeId,
966
- hasMore
967
- }
968
- };
969
- }
970
- };
971
-
972
- //#endregion
973
- //#region src/nodes/sleep.ts
974
- var SleepNode = class extends BaseNode {
975
- async exec(prepResult, context) {
976
- const durationParam = this.params?.duration;
977
- let durationMs;
978
- if (typeof durationParam === "string") {
979
- const match = durationParam.match(/^(\d+)([smhd])$/);
980
- if (!match) throw new Error(`SleepNode '${this.nodeId}' received an invalid duration string: '${durationParam}'. Expected format: '5m', '10s', '1h', '2d'`);
981
- const [, numStr, unit] = match;
982
- const num = parseInt(numStr, 10);
983
- switch (unit) {
984
- case "s":
985
- durationMs = num * 1e3;
986
- break;
987
- case "m":
988
- durationMs = num * 60 * 1e3;
989
- break;
990
- case "h":
991
- durationMs = num * 60 * 60 * 1e3;
992
- break;
993
- case "d":
994
- durationMs = num * 24 * 60 * 60 * 1e3;
995
- break;
996
- default: throw new Error(`Invalid duration unit: ${unit}`);
997
- }
998
- } else if (typeof durationParam === "number") durationMs = durationParam;
999
- else throw new Error(`SleepNode '${this.nodeId}' received an invalid duration type: ${typeof durationParam}`);
1000
- if (durationMs < 0) throw new Error(`SleepNode '${this.nodeId}' received a negative duration.`);
1001
- const wakeUpAt = new Date(Date.now() + durationMs).toISOString();
1002
- await context.dependencies.workflowState.markAsAwaiting(this.nodeId ?? "", {
1003
- reason: "timer",
1004
- wakeUpAt
1005
- });
1006
- return { output: prepResult };
1007
- }
1008
- };
1009
-
1010
- //#endregion
1011
- //#region src/runtime/traverser.ts
1012
- var GraphTraverser = class GraphTraverser {
1013
- frontier = /* @__PURE__ */ new Set();
1014
- allPredecessors;
1015
- allSuccessors;
1016
- dynamicBlueprint;
1017
- completedNodes = /* @__PURE__ */ new Set();
1018
- nodesInLoops;
1019
- constructor(blueprint, isStrictMode = false) {
1020
- this.dynamicBlueprint = structuredClone(blueprint);
1021
- this.allPredecessors = /* @__PURE__ */ new Map();
1022
- this.allSuccessors = /* @__PURE__ */ new Map();
1023
- this.nodesInLoops = /* @__PURE__ */ new Map();
1024
- for (const node of this.dynamicBlueprint.nodes) {
1025
- this.allPredecessors.set(node.id, /* @__PURE__ */ new Set());
1026
- this.allSuccessors.set(node.id, /* @__PURE__ */ new Set());
1027
- }
1028
- for (const edge of this.dynamicBlueprint.edges) this.getPredecessors(edge.target).add(edge.source);
1029
- for (const edge of this.dynamicBlueprint.edges) this.getSuccessors(edge.source).add(edge.target);
1030
- const analysis = analyzeBlueprint(blueprint);
1031
- this.filterNodesInLoops(blueprint);
1032
- this.frontier = new Set(analysis.startNodeIds.filter((id) => !this.isFallbackNode(id)));
1033
- if (this.frontier.size === 0 && analysis.cycles.length > 0 && !isStrictMode) {
1034
- const uniqueStartNodes = /* @__PURE__ */ new Set();
1035
- const cycleEntryPoints = new Set(blueprint.metadata?.cycleEntryPoints || []);
1036
- for (const cycle of analysis.cycles) if (cycle.length > 0) {
1037
- const entryPoint = cycle.find((node) => cycleEntryPoints.has(node));
1038
- uniqueStartNodes.add(entryPoint || cycle[0]);
1039
- }
1040
- this.frontier = new Set(uniqueStartNodes);
1041
- }
1042
- }
1043
- /**
1044
- * Clears all nodes from the execution frontier.
1045
- */
1046
- clearFrontier() {
1047
- this.frontier.clear();
1048
- }
1049
- /**
1050
- * Creates and initializes a GraphTraverser from a saved workflow state.
1051
- * This is the correct way to prepare a traverser for a `resume` operation.
1052
- * @param blueprint The workflow blueprint.
1053
- * @param state The workflow state being resumed.
1054
- * @returns A configured GraphTraverser instance.
1055
- */
1056
- static fromState(blueprint, state) {
1057
- const traverser = new GraphTraverser(blueprint);
1058
- traverser.clearFrontier();
1059
- const completedNodes = state.getCompletedNodes();
1060
- traverser.completedNodes = new Set(completedNodes);
1061
- const hasOnlyConditionalIncomingEdges = (nodeId) => {
1062
- const incomingEdges = blueprint.edges.filter((e) => e.target === nodeId);
1063
- if (incomingEdges.length === 0) return false;
1064
- const fromCompletedPredecessors = incomingEdges.filter((e) => completedNodes.has(e.source));
1065
- if (fromCompletedPredecessors.length === 0) return false;
1066
- return fromCompletedPredecessors.every((e) => e.condition !== void 0);
1067
- };
1068
- for (const node of traverser.dynamicBlueprint.nodes) {
1069
- if (traverser.completedNodes.has(node.id)) continue;
1070
- if (hasOnlyConditionalIncomingEdges(node.id)) continue;
1071
- const requiredPredecessors = traverser.allPredecessors.get(node.id);
1072
- const joinStrategy = traverser.getJoinStrategy(node.id);
1073
- if (!requiredPredecessors || requiredPredecessors.size === 0) {
1074
- traverser.frontier.add(node.id);
1075
- continue;
1076
- }
1077
- const completedPredecessors = [...requiredPredecessors].filter((p) => traverser.completedNodes.has(p));
1078
- if (joinStrategy === "any" ? completedPredecessors.length > 0 : completedPredecessors.length === requiredPredecessors.size) traverser.frontier.add(node.id);
1079
- }
1080
- return traverser;
1081
- }
1082
- isFallbackNode(nodeId) {
1083
- return this.dynamicBlueprint.nodes.some((n) => n.config?.fallback === nodeId);
1084
- }
1085
- getJoinStrategy(nodeId) {
1086
- return this.dynamicBlueprint.nodes.find((n) => n.id === nodeId)?.config?.joinStrategy || "all";
1087
- }
1088
- filterNodesInLoops(blueprint) {
1089
- blueprint.nodes.forEach((node) => {
1090
- if (node.uses !== "loop-controller") return;
1091
- const nextInLoopId = blueprint.edges.find((e) => e.source === node.id && e.action === "continue")?.target;
1092
- if (!nextInLoopId) throw new FlowcraftError(`Loop '${node.id}' has no continue edge to start node. Ensure edges are wired inside the loop and incoming/breaking edges point to the loop controller.`, {
1093
- nodeId: node.id,
1094
- blueprintId: blueprint.id
1095
- });
1096
- const set = /* @__PURE__ */ new Set();
1097
- set.add(nextInLoopId);
1098
- this.nodesInLoops.set(node.id, this.getAllLoopSuccessors(nextInLoopId, blueprint, set));
1099
- });
1100
- }
1101
- getAllLoopSuccessors(nodeId, blueprint, set) {
1102
- this.getSuccessors(nodeId).forEach((successor) => {
1103
- if (set.has(successor)) return;
1104
- const node = this.getNode(successor, blueprint);
1105
- if (!node || node.uses === "loop-controller") return;
1106
- set.add(successor);
1107
- this.getAllLoopSuccessors(successor, blueprint, set);
1108
- });
1109
- return set;
1110
- }
1111
- getReadyNodes() {
1112
- const readyNodes = [];
1113
- for (const nodeId of this.frontier) {
1114
- const nodeDef = this.dynamicBlueprint.nodes.find((n) => n.id === nodeId);
1115
- if (nodeDef) readyNodes.push({
1116
- nodeId,
1117
- nodeDef
1118
- });
1119
- }
1120
- this.frontier.clear();
1121
- return readyNodes;
1122
- }
1123
- hasMoreWork() {
1124
- return this.frontier.size > 0;
1125
- }
1126
- markNodeCompleted(nodeId, result, nextNodes) {
1127
- this.completedNodes.add(nodeId);
1128
- if (result?.dynamicNodes && result.dynamicNodes.length > 0) {
1129
- const gatherNodeId = result.output?.gatherNodeId;
1130
- for (const dynamicNode of result.dynamicNodes) {
1131
- this.dynamicBlueprint.nodes.push(dynamicNode);
1132
- this.allPredecessors.set(dynamicNode.id, new Set([nodeId]));
1133
- if (gatherNodeId) this.getPredecessors(gatherNodeId).add(dynamicNode.id);
1134
- this.frontier.add(dynamicNode.id);
1135
- }
1136
- }
1137
- for (const node of nextNodes) {
1138
- const joinStrategy = this.getJoinStrategy(node.id);
1139
- if (joinStrategy !== "any" && this.completedNodes.has(node.id)) continue;
1140
- const requiredPredecessors = this.getPredecessors(node.id);
1141
- if (joinStrategy === "any" ? requiredPredecessors.has(nodeId) : [...requiredPredecessors].every((p) => this.completedNodes.has(p))) {
1142
- this.frontier.add(node.id);
1143
- if (node.uses === "loop-controller") this.getNodesInLoop(node.id).forEach((id) => {
1144
- this.resetNodeCompletion(id);
1145
- });
1146
- }
1147
- }
1148
- if (nextNodes.length === 0) {
1149
- for (const [potentialNextId, predecessors] of this.allPredecessors) if (predecessors.has(nodeId) && !this.completedNodes.has(potentialNextId)) {
1150
- if (this.dynamicBlueprint.edges.some((e) => e.target === potentialNextId && e.condition)) continue;
1151
- if (this.getJoinStrategy(potentialNextId) === "any" ? predecessors.has(nodeId) : [...predecessors].every((p) => this.completedNodes.has(p))) {
1152
- this.frontier.add(potentialNextId);
1153
- const node = this.getNode(potentialNextId, this.dynamicBlueprint);
1154
- if (!node) continue;
1155
- if (node.uses === "loop-controller") this.getNodesInLoop(node.id).forEach((id) => {
1156
- this.resetNodeCompletion(id);
1157
- });
1158
- }
1159
- }
1160
- }
1161
- }
1162
- getAllNodeIds() {
1163
- return new Set(this.dynamicBlueprint.nodes.map((n) => n.id));
1164
- }
1165
- getFallbackNodeIds() {
1166
- const fallbackNodeIds = /* @__PURE__ */ new Set();
1167
- for (const node of this.dynamicBlueprint.nodes) if (node.config?.fallback) fallbackNodeIds.add(node.config.fallback);
1168
- return fallbackNodeIds;
1169
- }
1170
- getCompletedNodes() {
1171
- return new Set(this.completedNodes);
1172
- }
1173
- getDynamicBlueprint() {
1174
- return this.dynamicBlueprint;
1175
- }
1176
- getAllPredecessors() {
1177
- return this.allPredecessors;
1178
- }
1179
- getAllSuccessors() {
1180
- return this.allSuccessors;
1181
- }
1182
- getPredecessors(nodeId) {
1183
- const predecessors = this.allPredecessors.get(nodeId);
1184
- if (!predecessors) return /* @__PURE__ */ new Set();
1185
- return predecessors;
1186
- }
1187
- getSuccessors(nodeId) {
1188
- const successors = this.allSuccessors.get(nodeId);
1189
- if (!successors) return /* @__PURE__ */ new Set();
1190
- return successors;
1191
- }
1192
- getNodesInLoop(id) {
1193
- const loopNodes = this.nodesInLoops.get(id);
1194
- if (!loopNodes) return /* @__PURE__ */ new Set();
1195
- return loopNodes;
1196
- }
1197
- resetNodeCompletion(nodeId) {
1198
- this.completedNodes.delete(nodeId);
1199
- }
1200
- getNode(nodeId, blueprint) {
1201
- return blueprint.nodes.find((n) => n.id === nodeId);
1202
- }
1203
- addDynamicNode(_nodeId, dynamicNode, predecessorId, gatherNodeId) {
1204
- this.dynamicBlueprint.nodes.push(dynamicNode);
1205
- this.allPredecessors.set(dynamicNode.id, new Set([predecessorId]));
1206
- if (gatherNodeId) this.allPredecessors.get(gatherNodeId)?.add(dynamicNode.id);
1207
- this.frontier.add(dynamicNode.id);
1208
- }
1209
- /**
1210
- * Manually adds a node ID back to the execution frontier.
1211
- * Used by orchestrators that need fine-grained control over steps.
1212
- * @param nodeId The ID of the node to add to the frontier.
1213
- */
1214
- addToFrontier(nodeId) {
1215
- this.frontier.add(nodeId);
1216
- }
1217
- };
1218
-
1219
- //#endregion
1220
- //#region src/nodes/subflow.ts
1221
- var SubflowNode = class extends BaseNode {
1222
- async exec(_prepResult, context) {
1223
- const { blueprintId, inputs, outputs } = this.params ?? {};
1224
- const { runtime, workflowState } = context.dependencies;
1225
- if (!blueprintId) throw new FlowcraftError(`Subflow node '${this.nodeId}' is missing 'blueprintId' parameter.`, { isFatal: true });
1226
- const subBlueprint = runtime.blueprints?.[blueprintId] || runtime.runtime?.blueprints?.[blueprintId];
1227
- if (!subBlueprint) throw new FlowcraftError(`Sub-blueprint '${blueprintId}' not found in runtime registry.`, { isFatal: true });
1228
- const subflowInitialContext = {};
1229
- if (inputs) for (const [targetKey, sourceKey] of Object.entries(inputs)) {
1230
- let value = await context.context.get(sourceKey);
1231
- if (value === void 0) value = await context.context.get(`_outputs.${sourceKey}`);
1232
- subflowInitialContext[targetKey] = value;
1233
- }
1234
- else if (context.input !== void 0) {
1235
- const subAnalysis = analyzeBlueprint(subBlueprint);
1236
- for (const startNodeId of subAnalysis.startNodeIds) {
1237
- const inputKey = `_inputs.${startNodeId}`;
1238
- subflowInitialContext[inputKey] = context.input;
1239
- }
1240
- }
1241
- const subflowExecContext = new ExecutionContext(subBlueprint, new WorkflowState(subflowInitialContext), runtime.nodeRegistry, runtime.executionId, runtime.runtime, runtime.services, runtime.signal, runtime.concurrency);
1242
- const subflowTraverser = new GraphTraverser(subBlueprint);
1243
- const subflowResult = await runtime.runtime.orchestrator.run(subflowExecContext, subflowTraverser);
1244
- if (subflowResult.status === "awaiting") {
1245
- await workflowState.markAsAwaiting(this.nodeId ?? "");
1246
- const subflowStateKey = `_subflowState.${this.nodeId}`;
1247
- await context.context.set(subflowStateKey, subflowResult.serializedContext);
1248
- return { output: void 0 };
1249
- }
1250
- if (subflowResult.status !== "completed") {
1251
- const firstError = subflowResult.errors?.[0];
1252
- const errorMessage = firstError?.message || "Unknown error";
1253
- throw new FlowcraftError(`Sub-workflow '${blueprintId}' did not complete successfully. Status: ${subflowResult.status}. Error: ${errorMessage}`, {
1254
- cause: firstError,
1255
- nodeId: this.nodeId,
1256
- blueprintId
1257
- });
1258
- }
1259
- const subflowFinalContext = subflowResult.context;
1260
- if (outputs) {
1261
- for (const [parentKey, subKey] of Object.entries(outputs)) {
1262
- const value = subflowFinalContext[`_outputs.${subKey}`] ?? subflowFinalContext[subKey];
1263
- await context.context.set(parentKey, value);
1264
- }
1265
- return { output: subflowFinalContext };
1266
- }
1267
- const subAnalysis = analyzeBlueprint(subBlueprint);
1268
- if (subAnalysis.terminalNodeIds.length === 1) return { output: subflowFinalContext[`_outputs.${subAnalysis.terminalNodeIds[0]}`] };
1269
- const terminalOutputs = {};
1270
- for (const terminalId of subAnalysis.terminalNodeIds) terminalOutputs[terminalId] = subflowFinalContext[`_outputs.${terminalId}`];
1271
- return { output: terminalOutputs };
1272
- }
1273
- };
1274
-
1275
- //#endregion
1276
- //#region src/nodes/wait.ts
1277
- var WaitNode = class extends BaseNode {
1278
- async exec(_prepResult, context) {
1279
- await context.dependencies.workflowState.markAsAwaiting(this.nodeId ?? "", { reason: "external_event" });
1280
- return { output: void 0 };
1281
- }
1282
- };
1283
-
1284
- //#endregion
1285
- //#region src/nodes/webhook.ts
1286
- var WebhookNode = class extends BaseNode {
1287
- async prep(context) {
1288
- const runId = context.dependencies.runtime.executionId;
1289
- const nodeId = this.nodeId ?? "";
1290
- const { url, event } = await context.dependencies.adapter.registerWebhookEndpoint(runId, nodeId);
1291
- return {
1292
- url,
1293
- event
1294
- };
1295
- }
1296
- async exec(prepResult, _context) {
1297
- return { output: {
1298
- url: prepResult.url,
1299
- event: prepResult.event,
1300
- request: new Promise(() => {})
1301
- } };
1302
- }
1303
- };
1304
-
1305
- //#endregion
1306
- //#region src/sanitizer.ts
1307
- /**
1308
- * Sanitizes a raw workflow blueprint by removing extra properties
1309
- * added by UI tools (e.g., position, style) and keeping only the
1310
- * properties defined in NodeDefinition and EdgeDefinition.
1311
- */
1312
- function sanitizeBlueprint(raw) {
1313
- let nodesArray = [];
1314
- if (Array.isArray(raw.nodes)) nodesArray = raw.nodes;
1315
- else if (typeof raw.nodes === "object" && raw.nodes !== null) nodesArray = Object.values(raw.nodes);
1316
- const nodes = nodesArray.map((node) => ({
1317
- id: node.id,
1318
- uses: node.uses,
1319
- params: node.params,
1320
- inputs: node.inputs,
1321
- config: node.config
1322
- }));
1323
- let edgesArray = [];
1324
- if (Array.isArray(raw.edges)) edgesArray = raw.edges;
1325
- else if (typeof raw.edges === "object" && raw.edges !== null) edgesArray = Object.values(raw.edges);
1326
- const edges = edgesArray.map((edge) => ({
1327
- source: edge.source,
1328
- target: edge.target,
1329
- action: edge.action,
1330
- condition: edge.condition,
1331
- transform: edge.transform
1332
- }));
1333
- return {
1334
- id: raw.id,
1335
- nodes,
1336
- edges,
1337
- metadata: raw.metadata
1338
- };
1339
- }
1340
-
1341
- //#endregion
1342
- //#region src/runtime/executors.ts
1343
- async function withRetries(executor, maxRetries, nodeDef, context, executionId, signal, eventBus) {
1344
- let lastError;
1345
- for (let attempt = 1; attempt <= maxRetries; attempt++) try {
1346
- signal?.throwIfAborted();
1347
- const result = await executor();
1348
- if (attempt > 1) context.dependencies.logger.info(`Node execution succeeded after retry`, {
1349
- nodeId: nodeDef.id,
1350
- attempt,
1351
- executionId
1352
- });
1353
- return result;
1354
- } catch (error) {
1355
- lastError = error;
1356
- if (error instanceof DOMException && error.name === "AbortError") throw new FlowcraftError("Workflow cancelled", { isFatal: false });
1357
- if (error instanceof FlowcraftError && error.isFatal) break;
1358
- if (attempt < maxRetries) {
1359
- context.dependencies.logger.warn(`Node execution failed, retrying`, {
1360
- nodeId: nodeDef.id,
1361
- attempt,
1362
- maxRetries,
1363
- error: error instanceof Error ? error.message : String(error),
1364
- executionId
1365
- });
1366
- if (eventBus) await eventBus.emit({
1367
- type: "node:retry",
1368
- payload: {
1369
- nodeId: nodeDef.id,
1370
- attempt,
1371
- executionId: executionId || "",
1372
- blueprintId: context.dependencies.blueprint?.id || ""
1373
- }
1374
- });
1375
- } else context.dependencies.logger.error(`Node execution failed after all retries`, {
1376
- nodeId: nodeDef.id,
1377
- attempts: maxRetries,
1378
- error: error instanceof Error ? error.message : String(error),
1379
- executionId
1380
- });
1381
- }
1382
- throw lastError;
1383
- }
1384
- var FunctionNodeExecutor = class {
1385
- constructor(implementation, maxRetries, eventBus) {
1386
- this.implementation = implementation;
1387
- this.maxRetries = maxRetries;
1388
- this.eventBus = eventBus;
1389
- }
1390
- async execute(nodeDef, context, executionId, signal) {
1391
- return withRetries(() => this.implementation(context), this.maxRetries, nodeDef, context, executionId, signal, this.eventBus);
1392
- }
1393
- };
1394
- var ClassNodeExecutor = class {
1395
- constructor(implementation, maxRetries, eventBus) {
1396
- this.implementation = implementation;
1397
- this.maxRetries = maxRetries;
1398
- this.eventBus = eventBus;
1399
- }
1400
- async execute(nodeDef, context, executionId, signal) {
1401
- const instance = new this.implementation(nodeDef.params || {}, nodeDef.id);
1402
- let lastError;
1403
- try {
1404
- signal?.throwIfAborted();
1405
- const prepResult = await instance.prep(context);
1406
- let execResult;
1407
- try {
1408
- execResult = await withRetries(() => instance.exec(prepResult, context), this.maxRetries, nodeDef, context, executionId, signal, this.eventBus);
1409
- } catch (error) {
1410
- lastError = error instanceof Error ? error : new Error(String(error));
1411
- if (error instanceof DOMException && error.name === "AbortError") throw new FlowcraftError("Workflow cancelled", { isFatal: false });
1412
- if (error instanceof FlowcraftError && error.isFatal) throw error;
1413
- }
1414
- if (lastError) {
1415
- signal?.throwIfAborted();
1416
- execResult = await instance.fallback(lastError, context);
1417
- }
1418
- signal?.throwIfAborted();
1419
- if (!execResult) throw new Error("Execution failed after all retries");
1420
- return await instance.post(execResult, context);
1421
- } catch (error) {
1422
- lastError = error instanceof Error ? error : new Error(String(error));
1423
- if (error instanceof DOMException && error.name === "AbortError") throw new FlowcraftError("Workflow cancelled", { isFatal: false });
1424
- throw error;
1425
- } finally {
1426
- if (lastError) try {
1427
- await instance.recover(lastError, context);
1428
- } catch (recoverError) {
1429
- context.dependencies.logger.warn(`Recover phase failed`, {
1430
- nodeId: nodeDef.id,
1431
- originalError: lastError.message,
1432
- recoverError: recoverError instanceof Error ? recoverError.message : String(recoverError),
1433
- executionId
1434
- });
1435
- }
1436
- }
1437
- }
1438
- };
1439
- var NodeExecutor = class {
1440
- context;
1441
- nodeDef;
1442
- strategy;
1443
- constructor(config) {
1444
- this.context = config.context;
1445
- this.nodeDef = config.nodeDef;
1446
- this.strategy = config.strategy;
1447
- }
1448
- async execute(input) {
1449
- const nodeContext = {
1450
- context: this.context.state.getContext(),
1451
- input,
1452
- params: this.nodeDef.params || {},
1453
- dependencies: {
1454
- ...this.context.services.dependencies,
1455
- logger: this.context.services.logger,
1456
- runtime: this.context,
1457
- workflowState: this.context.state
1458
- },
1459
- signal: this.context.signal
1460
- };
1461
- const beforeHooks = this.context.services.middleware.map((m) => m.beforeNode).filter((hook) => !!hook);
1462
- const afterHooks = this.context.services.middleware.map((m) => m.afterNode).filter((hook) => !!hook);
1463
- const aroundHooks = this.context.services.middleware.map((m) => m.aroundNode).filter((hook) => !!hook);
1464
- const coreExecution = async () => {
1465
- let result;
1466
- let error;
1467
- try {
1468
- for (const hook of beforeHooks) await hook(nodeContext.context, this.nodeDef.id);
1469
- result = await this.strategy.execute(this.nodeDef, nodeContext, this.context.executionId, this.context.signal);
1470
- return {
1471
- status: "success",
1472
- result
1473
- };
1474
- } catch (e) {
1475
- error = e instanceof Error ? e : new Error(String(e));
1476
- const flowcraftError = error instanceof FlowcraftError ? error : new FlowcraftError(`Node '${this.nodeDef.id}' execution failed`, {
1477
- cause: error,
1478
- nodeId: this.nodeDef.id,
1479
- blueprintId: this.context.blueprint.id,
1480
- executionId: this.context.executionId,
1481
- isFatal: false
1482
- });
1483
- const fallbackNodeId = this.nodeDef.config?.fallback;
1484
- const isLastAttempt = this.context.state.isLastAttempt ?? true;
1485
- if (fallbackNodeId && !flowcraftError.isFatal && isLastAttempt) {
1486
- this.context.services.logger.warn(`Node failed, fallback required`, {
1487
- nodeId: this.nodeDef.id,
1488
- fallbackNodeId,
1489
- error: error.message,
1490
- executionId: this.context.executionId
1491
- });
1492
- await this.context.services.eventBus.emit({
1493
- type: "node:fallback",
1494
- payload: {
1495
- nodeId: this.nodeDef.id,
1496
- executionId: this.context.executionId || "",
1497
- fallback: fallbackNodeId,
1498
- blueprintId: this.context.blueprint.id
1499
- }
1500
- });
1501
- return {
1502
- status: "failed_with_fallback",
1503
- fallbackNodeId,
1504
- error: flowcraftError
1505
- };
1506
- }
1507
- return {
1508
- status: "failed",
1509
- error: flowcraftError
1510
- };
1511
- } finally {
1512
- for (const hook of afterHooks) await hook(nodeContext.context, this.nodeDef.id, result, error);
1513
- }
1514
- };
1515
- let executionChain = coreExecution;
1516
- for (let i = aroundHooks.length - 1; i >= 0; i--) {
1517
- const hook = aroundHooks[i];
1518
- const next = executionChain;
1519
- executionChain = async () => {
1520
- let capturedResult;
1521
- const middlewareResult = await hook(nodeContext.context, this.nodeDef.id, async () => {
1522
- capturedResult = await next();
1523
- if (capturedResult.status === "success") return capturedResult.result;
1524
- throw capturedResult.error;
1525
- });
1526
- if (!capturedResult && middlewareResult) return {
1527
- status: "success",
1528
- result: middlewareResult
1529
- };
1530
- if (!capturedResult) throw new Error("Middleware did not call next() and did not return a result");
1531
- return capturedResult;
1532
- };
1533
- }
1534
- try {
1535
- await this.context.services.eventBus.emit({
1536
- type: "node:start",
1537
- payload: {
1538
- nodeId: this.nodeDef.id,
1539
- executionId: this.context.executionId || "",
1540
- input: nodeContext.input,
1541
- blueprintId: this.context.blueprint.id
1542
- }
1543
- });
1544
- const executionResult = await executionChain();
1545
- if (executionResult.status === "success") await this.context.services.eventBus.emit({
1546
- type: "node:finish",
1547
- payload: {
1548
- nodeId: this.nodeDef.id,
1549
- result: executionResult.result,
1550
- executionId: this.context.executionId || "",
1551
- blueprintId: this.context.blueprint.id
1552
- }
1553
- });
1554
- else await this.context.services.eventBus.emit({
1555
- type: "node:error",
1556
- payload: {
1557
- nodeId: this.nodeDef.id,
1558
- error: executionResult.error,
1559
- executionId: this.context.executionId || "",
1560
- blueprintId: this.context.blueprint.id
1561
- }
1562
- });
1563
- return executionResult;
1564
- } catch (error) {
1565
- const err = error instanceof Error ? error : new Error(String(error));
1566
- const flowcraftError = err instanceof FlowcraftError ? err : new FlowcraftError(`Node '${this.nodeDef.id}' failed execution.`, {
1567
- cause: err,
1568
- nodeId: this.nodeDef.id,
1569
- blueprintId: this.context.blueprint.id,
1570
- executionId: this.context.executionId,
1571
- isFatal: false
1572
- });
1573
- await this.context.services.eventBus.emit({
1574
- type: "node:error",
1575
- payload: {
1576
- nodeId: this.nodeDef.id,
1577
- error: flowcraftError,
1578
- executionId: this.context.executionId || "",
1579
- blueprintId: this.context.blueprint.id
1580
- }
1581
- });
1582
- if (error instanceof DOMException && error.name === "AbortError") throw new FlowcraftError("Workflow cancelled", {
1583
- executionId: this.context.executionId,
1584
- isFatal: false
1585
- });
1586
- throw error instanceof FlowcraftError && !error.isFatal ? error : new FlowcraftError(`Node '${this.nodeDef.id}' failed execution.`, {
1587
- cause: error,
1588
- nodeId: this.nodeDef.id,
1589
- blueprintId: this.context.blueprint.id,
1590
- executionId: this.context.executionId,
1591
- isFatal: false
1592
- });
1593
- }
1594
- }
1595
- };
1596
-
1597
- //#endregion
1598
- //#region src/runtime/node-executor-factory.ts
1599
- var NodeExecutorFactory = class {
1600
- constructor(eventBus) {
1601
- this.eventBus = eventBus;
1602
- }
1603
- createExecutorForNode(nodeId, context) {
1604
- const nodeDef = context.blueprint.nodes.find((n) => n.id === nodeId);
1605
- if (!nodeDef) throw new FlowcraftError(`Node '${nodeId}' not found in blueprint.`, {
1606
- nodeId,
1607
- blueprintId: context.blueprint.id,
1608
- executionId: context.executionId,
1609
- isFatal: false
1610
- });
1611
- return new NodeExecutor({
1612
- context,
1613
- nodeDef,
1614
- strategy: this.getExecutionStrategy(nodeDef, context)
1615
- });
1616
- }
1617
- getExecutionStrategy(nodeDef, context) {
1618
- const implementation = context.nodeRegistry.get(nodeDef.uses);
1619
- if (!implementation) throw new FlowcraftError(`Implementation for '${nodeDef.uses}' not found.`, {
1620
- nodeId: nodeDef.id,
1621
- blueprintId: "",
1622
- isFatal: true
1623
- });
1624
- let maxRetries = nodeDef.config?.maxRetries ?? 1;
1625
- const adapter = context.services.dependencies?.adapter;
1626
- if (adapter && typeof adapter.shouldRetryInProcess === "function") {
1627
- if (!adapter.shouldRetryInProcess(nodeDef)) maxRetries = 1;
1628
- }
1629
- return isNodeClass(implementation) ? new ClassNodeExecutor(implementation, maxRetries, this.eventBus) : new FunctionNodeExecutor(implementation, maxRetries, this.eventBus);
1630
- }
1631
- };
1632
-
1633
- //#endregion
1634
- //#region src/runtime/scheduler.ts
1635
- var WorkflowScheduler = class {
1636
- runtime;
1637
- activeWorkflows = /* @__PURE__ */ new Map();
1638
- resumeResults = /* @__PURE__ */ new Map();
1639
- intervalId;
1640
- checkIntervalMs;
1641
- constructor(runtime, checkIntervalMs = 1e3) {
1642
- this.runtime = runtime;
1643
- this.checkIntervalMs = checkIntervalMs;
1644
- }
1645
- start() {
1646
- if (this.intervalId) return;
1647
- this.intervalId = setInterval(() => {
1648
- this.checkAndResumeWorkflows();
1649
- }, this.checkIntervalMs);
1650
- }
1651
- stop() {
1652
- if (this.intervalId) {
1653
- clearInterval(this.intervalId);
1654
- this.intervalId = void 0;
1655
- }
1656
- }
1657
- registerAwaitingWorkflow(executionId, blueprintId, serializedContext, awaitingNodeId, wakeUpAt, functionRegistry) {
1658
- this.activeWorkflows.set(executionId, {
1659
- executionId,
1660
- blueprintId,
1661
- serializedContext,
1662
- awaitingNodeId,
1663
- wakeUpAt,
1664
- functionRegistry
1665
- });
1666
- }
1667
- unregisterWorkflow(executionId) {
1668
- this.activeWorkflows.delete(executionId);
1669
- }
1670
- async checkAndResumeWorkflows() {
1671
- const now = /* @__PURE__ */ new Date();
1672
- const toResume = [];
1673
- for (const [_executionId, workflow] of this.activeWorkflows) if (new Date(workflow.wakeUpAt) <= now) toResume.push(workflow);
1674
- for (const workflow of toResume) try {
1675
- const blueprint = this.runtime.getBlueprint(workflow.blueprintId);
1676
- if (!blueprint) {
1677
- console.warn(`Blueprint ${workflow.blueprintId} not found, skipping resumption`);
1678
- continue;
1679
- }
1680
- const result = await this.runtime.resume(blueprint, workflow.serializedContext, { output: void 0 }, workflow.awaitingNodeId, { functionRegistry: workflow.functionRegistry });
1681
- this.resumeResults.set(workflow.executionId, result);
1682
- if (result.status === "completed" || result.status === "failed") this.unregisterWorkflow(workflow.executionId);
1683
- } catch (error) {
1684
- console.error(`Failed to resume workflow ${workflow.executionId}:`, error);
1685
- this.unregisterWorkflow(workflow.executionId);
1686
- }
1687
- }
1688
- getActiveWorkflows() {
1689
- return Array.from(this.activeWorkflows.values());
1690
- }
1691
- getResumeResult(executionId) {
1692
- return this.resumeResults.get(executionId);
1693
- }
1694
- };
1695
-
1696
- //#endregion
1697
- //#region src/runtime/workflow-logic-handler.ts
1698
- var WorkflowLogicHandler = class {
1699
- constructor(evaluator, eventBus) {
1700
- this.evaluator = evaluator;
1701
- this.eventBus = eventBus;
1702
- }
1703
- async determineNextNodes(blueprint, completedNodeId, result, context, executionId) {
1704
- if (!result) return [];
1705
- const effectiveSourceNodeId = completedNodeId;
1706
- const directOutgoingEdges = blueprint.edges.filter((edge) => edge.source === effectiveSourceNodeId);
1707
- const inheritedOutgoingEdges = blueprint.nodes.filter((n) => n.config?.fallback === completedNodeId).flatMap((originalNode) => blueprint.edges.filter((edge) => edge.source === originalNode.id));
1708
- const allPossibleEdges = [...directOutgoingEdges, ...inheritedOutgoingEdges];
1709
- const outgoingEdges = [...new Map(allPossibleEdges.map((edge) => [`${edge.source}-${edge.target}-${edge.action || ""}-${edge.condition || ""}`, edge])).values()];
1710
- const matched = [];
1711
- const evaluateEdge = async (edge) => {
1712
- if (!edge.condition) return true;
1713
- const contextData = context.type === "sync" ? context.toJSON() : await context.toJSON();
1714
- const evaluationResult = !!this.evaluator.evaluate(edge.condition, {
1715
- ...contextData,
1716
- result
1717
- });
1718
- await this.eventBus.emit({
1719
- type: "edge:evaluate",
1720
- payload: {
1721
- source: edge.source,
1722
- target: edge.target,
1723
- condition: edge.condition,
1724
- result: evaluationResult
1725
- }
1726
- });
1727
- return evaluationResult;
1728
- };
1729
- if (blueprint.nodes.find((n) => n.id === completedNodeId)?.uses === "loop-controller") {
1730
- const conditionalEdges = outgoingEdges.filter((edge) => edge.condition);
1731
- for (const edge of conditionalEdges) if (await evaluateEdge(edge)) {
1732
- const targetNode = blueprint.nodes.find((n) => n.id === edge.target);
1733
- if (targetNode) matched.push({
1734
- node: targetNode,
1735
- edge
1736
- });
1737
- } else await this.eventBus.emit({
1738
- type: "node:skipped",
1739
- payload: {
1740
- nodeId: completedNodeId,
1741
- edge,
1742
- executionId: executionId || "",
1743
- blueprintId: blueprint.id
1744
- }
1745
- });
1746
- if (matched.length > 0) return matched;
1747
- }
1748
- if (result.action) {
1749
- const actionEdges = outgoingEdges.filter((edge) => edge.action === result.action);
1750
- for (const edge of actionEdges) if (await evaluateEdge(edge)) {
1751
- const targetNode = blueprint.nodes.find((n) => n.id === edge.target);
1752
- if (targetNode) matched.push({
1753
- node: targetNode,
1754
- edge
1755
- });
1756
- } else await this.eventBus.emit({
1757
- type: "node:skipped",
1758
- payload: {
1759
- nodeId: completedNodeId,
1760
- edge,
1761
- executionId: executionId || "",
1762
- blueprintId: blueprint.id
1763
- }
1764
- });
1765
- }
1766
- if (matched.length === 0) {
1767
- const defaultEdges = outgoingEdges.filter((edge) => !edge.action);
1768
- for (const edge of defaultEdges) if (await evaluateEdge(edge)) {
1769
- const targetNode = blueprint.nodes.find((n) => n.id === edge.target);
1770
- if (targetNode) matched.push({
1771
- node: targetNode,
1772
- edge
1773
- });
1774
- } else await this.eventBus.emit({
1775
- type: "node:skipped",
1776
- payload: {
1777
- nodeId: completedNodeId,
1778
- edge,
1779
- executionId: executionId || "",
1780
- blueprintId: blueprint.id
1781
- }
1782
- });
1783
- }
1784
- return matched;
1785
- }
1786
- async applyEdgeTransform(edge, sourceResult, targetNode, context, allPredecessors, executionId) {
1787
- const asyncContext = context.type === "sync" ? new AsyncContextView(context) : context;
1788
- const predecessors = allPredecessors?.get(targetNode.id);
1789
- predecessors && predecessors.size;
1790
- const hasExplicitInputs = targetNode.inputs !== void 0;
1791
- const hasEdgeTransform = edge.transform !== void 0;
1792
- let sourceOutput = sourceResult.output;
1793
- if (hasEdgeTransform && hasExplicitInputs && typeof targetNode.inputs === "string") {
1794
- const inputsKey = targetNode.inputs;
1795
- const resolvedKey = inputsKey.startsWith("_") ? inputsKey : `_outputs.${inputsKey}`;
1796
- if (await asyncContext.has(resolvedKey) && inputsKey !== edge.source) sourceOutput = await asyncContext.get(resolvedKey);
1797
- }
1798
- const finalInput = hasEdgeTransform ? this.evaluator.evaluate(edge.transform, {
1799
- input: sourceOutput,
1800
- context: await asyncContext.toJSON()
1801
- }) : sourceOutput;
1802
- const inputKey = `_inputs.${targetNode.id}`;
1803
- await asyncContext.set(inputKey, finalInput);
1804
- await this.eventBus.emit({
1805
- type: "context:change",
1806
- payload: {
1807
- sourceNode: edge.source,
1808
- key: inputKey,
1809
- op: "set",
1810
- value: finalInput,
1811
- executionId: executionId || "unknown"
1812
- }
1813
- });
1814
- if (!hasExplicitInputs || hasEdgeTransform) targetNode.inputs = inputKey;
1815
- }
1816
- async resolveNodeInput(nodeId, blueprint, context) {
1817
- const nodeDef = blueprint.nodes.find((n) => n.id === nodeId);
1818
- if (!nodeDef) throw new FlowcraftError(`Node '${nodeId}' not found in blueprint.`, {
1819
- nodeId,
1820
- blueprintId: blueprint.id,
1821
- isFatal: false
1822
- });
1823
- const asyncContext = context.type === "sync" ? new AsyncContextView(context) : context;
1824
- if (nodeDef.inputs) {
1825
- if (typeof nodeDef.inputs === "string") {
1826
- const key = nodeDef.inputs;
1827
- if (key.startsWith("_")) return await asyncContext.get(key);
1828
- const outputKey = `_outputs.${key}`;
1829
- if (await asyncContext.has(outputKey)) return await asyncContext.get(outputKey);
1830
- return await asyncContext.get(key);
1831
- }
1832
- if (typeof nodeDef.inputs === "object") {
1833
- const input = {};
1834
- for (const key in nodeDef.inputs) {
1835
- const contextKey = nodeDef.inputs[key];
1836
- if (contextKey.startsWith("_")) input[key] = await asyncContext.get(contextKey);
1837
- else {
1838
- const outputKey = `_outputs.${contextKey}`;
1839
- if (await asyncContext.has(outputKey)) input[key] = await asyncContext.get(outputKey);
1840
- else input[key] = await asyncContext.get(contextKey);
1841
- }
1842
- }
1843
- return input;
1844
- }
1845
- }
1846
- const inputKey = `_inputs.${nodeDef.id}`;
1847
- return await asyncContext.get(inputKey);
1848
- }
1849
- };
1850
-
1851
- //#endregion
1852
- //#region src/runtime/runtime.ts
1853
- var FlowRuntime = class {
1854
- container;
1855
- registry;
1856
- blueprints;
1857
- dependencies;
1858
- logger;
1859
- eventBus;
1860
- serializer;
1861
- middleware;
1862
- evaluator;
1863
- analysisCache;
1864
- orchestrator;
1865
- options;
1866
- logicHandler;
1867
- executorFactory;
1868
- scheduler;
1869
- getBlueprint(id) {
1870
- return this.blueprints[id];
1871
- }
1872
- constructor(containerOrOptions, legacyOptions) {
1873
- let userRegistry;
1874
- if (containerOrOptions instanceof DIContainer) {
1875
- this.container = containerOrOptions;
1876
- this.logger = this.container.resolve(ServiceTokens.Logger);
1877
- this.serializer = this.container.resolve(ServiceTokens.Serializer);
1878
- this.evaluator = this.container.resolve(ServiceTokens.Evaluator);
1879
- this.eventBus = this.container.resolve(ServiceTokens.EventBus) || { emit: async () => {} };
1880
- this.middleware = this.container.resolve(ServiceTokens.Middleware) || [];
1881
- userRegistry = this.container.resolve(ServiceTokens.NodeRegistry);
1882
- this.blueprints = this.container.resolve(ServiceTokens.BlueprintRegistry);
1883
- this.dependencies = this.container.resolve(ServiceTokens.Dependencies);
1884
- this.options = legacyOptions || {};
1885
- this.orchestrator = this.container.resolve(ServiceTokens.Orchestrator);
1886
- this.scheduler = new WorkflowScheduler(this);
1887
- } else {
1888
- const options = containerOrOptions || {};
1889
- this.logger = options.logger || new NullLogger();
1890
- this.serializer = options.serializer || new JsonSerializer();
1891
- this.evaluator = options.evaluator || new PropertyEvaluator();
1892
- this.eventBus = options.eventBus || { emit: async () => {} };
1893
- this.middleware = options.middleware || [];
1894
- userRegistry = options.registry || {};
1895
- this.blueprints = options.blueprints || {};
1896
- this.scheduler = new WorkflowScheduler(this);
1897
- this.dependencies = options.dependencies || {};
1898
- this.options = options;
1899
- this.container = null;
1900
- }
1901
- const loopControllerFunction = async (context) => {
1902
- const condition = context.params.condition;
1903
- const contextData = await context.context.toJSON();
1904
- if (this.evaluator.evaluate(condition, contextData)) return { action: "continue" };
1905
- else return {
1906
- action: "break",
1907
- output: null
1908
- };
1909
- };
1910
- this.registry = new Map(Object.entries({
1911
- wait: WaitNode,
1912
- sleep: SleepNode,
1913
- webhook: WebhookNode,
1914
- subflow: SubflowNode,
1915
- "batch-scatter": BatchScatterNode,
1916
- "batch-gather": BatchGatherNode,
1917
- "loop-controller": loopControllerFunction,
1918
- ...userRegistry
1919
- }));
1920
- this.orchestrator = this.container?.has(ServiceTokens.Orchestrator) ? this.container.resolve(ServiceTokens.Orchestrator) : new DefaultOrchestrator();
1921
- this.analysisCache = /* @__PURE__ */ new WeakMap();
1922
- this.logicHandler = new WorkflowLogicHandler(this.evaluator, this.eventBus);
1923
- this.executorFactory = new NodeExecutorFactory(this.eventBus);
1924
- }
1925
- _setupExecutionContext(blueprint, initialState, options) {
1926
- const executionId = globalThis.crypto?.randomUUID();
1927
- const contextData = typeof initialState === "string" ? this.serializer.deserialize(initialState) : initialState;
1928
- blueprint = sanitizeBlueprint(blueprint);
1929
- const state = new WorkflowState(contextData);
1930
- const nodeRegistry = this._createExecutionRegistry(options?.functionRegistry);
1931
- return new ExecutionContext(blueprint, state, nodeRegistry, executionId, this, {
1932
- logger: this.logger,
1933
- eventBus: this.eventBus,
1934
- serializer: this.serializer,
1935
- evaluator: this.evaluator,
1936
- middleware: this.middleware,
1937
- dependencies: this.dependencies
1938
- }, options?.signal, options?.concurrency);
1939
- }
1940
- async run(blueprint, initialState = {}, options) {
1941
- const startTime = Date.now();
1942
- const executionContext = this._setupExecutionContext(blueprint, initialState, options);
1943
- this.logger.info(`Starting workflow execution`, {
1944
- blueprintId: executionContext.blueprint.id,
1945
- executionId: executionContext.executionId
1946
- });
1947
- try {
1948
- await this.eventBus.emit({
1949
- type: "workflow:start",
1950
- payload: {
1951
- blueprintId: executionContext.blueprint.id,
1952
- executionId: executionContext.executionId
1953
- }
1954
- });
1955
- await this.eventBus.emit({
1956
- type: "workflow:resume",
1957
- payload: {
1958
- blueprintId: executionContext.blueprint.id,
1959
- executionId: executionContext.executionId
1960
- }
1961
- });
1962
- const analysis = this.analysisCache.get(executionContext.blueprint) ?? (() => {
1963
- const computed = analyzeBlueprint(executionContext.blueprint);
1964
- this.analysisCache.set(executionContext.blueprint, computed);
1965
- return computed;
1966
- })();
1967
- if (options?.strict && !analysis.isDag) throw new Error(`Workflow '${executionContext.blueprint.id}' failed strictness check: Cycles are not allowed.`);
1968
- if (!analysis.isDag) this.logger.warn(`Workflow contains cycles`, { blueprintId: executionContext.blueprint.id });
1969
- const traverser = new GraphTraverser(executionContext.blueprint, options?.strict === true);
1970
- const result = await this.orchestrator.run(executionContext, traverser);
1971
- const duration = Date.now() - startTime;
1972
- if (result.status === "stalled") {
1973
- await this.eventBus.emit({
1974
- type: "workflow:stall",
1975
- payload: {
1976
- blueprintId: executionContext.blueprint.id,
1977
- executionId: executionContext.executionId,
1978
- remainingNodes: traverser.getAllNodeIds().size - executionContext.state.getCompletedNodes().size
1979
- }
1980
- });
1981
- await this.eventBus.emit({
1982
- type: "workflow:pause",
1983
- payload: {
1984
- blueprintId: executionContext.blueprint.id,
1985
- executionId: executionContext.executionId
1986
- }
1987
- });
1988
- }
1989
- this.logger.info(`Workflow execution completed`, {
1990
- blueprintId: executionContext.blueprint.id,
1991
- executionId: executionContext.executionId,
1992
- status: result.status,
1993
- duration,
1994
- errors: result.errors?.length || 0
1995
- });
1996
- await this.eventBus.emit({
1997
- type: "workflow:finish",
1998
- payload: {
1999
- blueprintId: executionContext.blueprint.id,
2000
- executionId: executionContext.executionId,
2001
- status: result.status,
2002
- errors: result.errors
2003
- }
2004
- });
2005
- if (result.status === "awaiting") {
2006
- const awaitingNodeIds = executionContext.state.getAwaitingNodeIds();
2007
- for (const nodeId of awaitingNodeIds) {
2008
- const details = executionContext.state.getAwaitingDetails(nodeId);
2009
- if (details?.reason === "timer") this.scheduler.registerAwaitingWorkflow(executionContext.executionId, executionContext.blueprint.id, result.serializedContext, nodeId, details.wakeUpAt, options?.functionRegistry);
2010
- }
2011
- }
2012
- return result;
2013
- } catch (error) {
2014
- const duration = Date.now() - startTime;
2015
- const workflowError = {
2016
- message: error instanceof Error ? error.message : String(error),
2017
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2018
- isFatal: false,
2019
- name: "WorkflowError"
2020
- };
2021
- await this.eventBus.emit({
2022
- type: "workflow:finish",
2023
- payload: {
2024
- blueprintId: executionContext.blueprint.id,
2025
- executionId: executionContext.executionId,
2026
- status: "cancelled",
2027
- errors: [workflowError]
2028
- }
2029
- });
2030
- if (error instanceof DOMException ? error.name === "AbortError" : error instanceof FlowcraftError && error.message.includes("cancelled")) {
2031
- this.logger.info(`Workflow execution cancelled`, {
2032
- blueprintId: executionContext.blueprint.id,
2033
- executionId: executionContext.executionId,
2034
- duration
2035
- });
2036
- await this.eventBus.emit({
2037
- type: "workflow:pause",
2038
- payload: {
2039
- blueprintId: executionContext.blueprint.id,
2040
- executionId: executionContext.executionId
2041
- }
2042
- });
2043
- await this.eventBus.emit({
2044
- type: "workflow:finish",
2045
- payload: {
2046
- blueprintId: executionContext.blueprint.id,
2047
- executionId: executionContext.executionId,
2048
- status: "cancelled",
2049
- errors: [workflowError]
2050
- }
2051
- });
2052
- return {
2053
- context: {},
2054
- serializedContext: "{}",
2055
- status: "cancelled"
2056
- };
2057
- }
2058
- this.logger.error(`Workflow execution failed`, {
2059
- blueprintId: executionContext.blueprint.id,
2060
- executionId: executionContext.executionId,
2061
- duration,
2062
- error: error instanceof Error ? error.message : String(error)
2063
- });
2064
- throw error;
2065
- }
2066
- }
2067
- startScheduler(checkIntervalMs) {
2068
- if (checkIntervalMs !== void 0) this.scheduler = new WorkflowScheduler(this, checkIntervalMs);
2069
- this.scheduler.start();
2070
- }
2071
- stopScheduler() {
2072
- this.scheduler.stop();
2073
- }
2074
- _setupResumedExecutionContext(blueprint, workflowState, options) {
2075
- const executionId = globalThis.crypto?.randomUUID();
2076
- return new ExecutionContext(blueprint, workflowState, this._createExecutionRegistry(options?.functionRegistry), executionId, this, {
2077
- logger: this.logger,
2078
- eventBus: this.eventBus,
2079
- serializer: this.serializer,
2080
- evaluator: this.evaluator,
2081
- middleware: this.middleware,
2082
- dependencies: this.dependencies
2083
- }, options?.signal);
2084
- }
2085
- async resume(blueprint, serializedContext, resumeData, nodeId, options) {
2086
- const executionId = globalThis.crypto?.randomUUID();
2087
- const workflowState = new WorkflowState(this.serializer.deserialize(serializedContext));
2088
- const awaitingNodeIds = workflowState.getAwaitingNodeIds();
2089
- if (awaitingNodeIds.length === 0) throw new FlowcraftError("Cannot resume: The provided context is not in an awaiting state.", { isFatal: true });
2090
- const awaitingNodeId = nodeId || awaitingNodeIds[0];
2091
- if (!awaitingNodeIds.includes(awaitingNodeId)) throw new FlowcraftError(`Cannot resume: Node '${awaitingNodeId}' is not in an awaiting state.`, { isFatal: true });
2092
- const awaitingNodeDef = blueprint.nodes.find((n) => n.id === awaitingNodeId);
2093
- if (!awaitingNodeDef) throw new FlowcraftError(`Awaiting node '${awaitingNodeId}' not found in blueprint.`, {
2094
- nodeId: awaitingNodeId,
2095
- blueprintId: blueprint.id,
2096
- isFatal: true
2097
- });
2098
- const contextImpl = workflowState.getContext();
2099
- if (awaitingNodeDef.uses === "SubflowNode") {
2100
- const subflowStateKey = `_subflowState.${awaitingNodeId}`;
2101
- const subflowContext = await contextImpl.get(subflowStateKey);
2102
- if (!subflowContext) throw new FlowcraftError(`Cannot resume: Subflow state for node '${awaitingNodeId}' not found.`, {
2103
- nodeId: awaitingNodeId,
2104
- blueprintId: blueprint.id,
2105
- isFatal: true
2106
- });
2107
- const blueprintId = awaitingNodeDef.params?.blueprintId;
2108
- if (!blueprintId) throw new FlowcraftError(`Subflow node '${awaitingNodeId}' is missing the 'blueprintId' parameter.`, {
2109
- nodeId: awaitingNodeId,
2110
- blueprintId: blueprint.id,
2111
- isFatal: true
2112
- });
2113
- const subBlueprint = this.blueprints[blueprintId];
2114
- if (!subBlueprint) throw new FlowcraftError(`Sub-blueprint with ID '${blueprintId}' not found in runtime registry.`, {
2115
- nodeId: awaitingNodeId,
2116
- blueprintId: blueprint.id,
2117
- isFatal: true
2118
- });
2119
- const subflowResumeResult = await this.resume(subBlueprint, subflowContext, resumeData, void 0, options);
2120
- if (subflowResumeResult.status !== "completed") throw new FlowcraftError(`Resumed subflow '${subBlueprint.id}' did not complete. Status: ${subflowResumeResult.status}`, {
2121
- nodeId: awaitingNodeId,
2122
- blueprintId: blueprint.id,
2123
- isFatal: false
2124
- });
2125
- const subflowFinalContext = subflowResumeResult.context;
2126
- let finalSubflowOutput;
2127
- const subAnalysis = analyzeBlueprint(subBlueprint);
2128
- if (awaitingNodeDef.params?.outputs) finalSubflowOutput = subflowFinalContext;
2129
- else if (subAnalysis.terminalNodeIds.length === 1) finalSubflowOutput = subflowFinalContext[`_outputs.${subAnalysis.terminalNodeIds[0]}`];
2130
- else {
2131
- const terminalOutputs = {};
2132
- for (const terminalId of subAnalysis.terminalNodeIds) terminalOutputs[terminalId] = subflowFinalContext[`_outputs.${terminalId}`];
2133
- finalSubflowOutput = terminalOutputs;
2134
- }
2135
- resumeData = { output: finalSubflowOutput };
2136
- await contextImpl.delete(subflowStateKey);
2137
- }
2138
- const existingOutput = await workflowState.getContext().get(`_outputs.${awaitingNodeId}`);
2139
- const nodeOutput = resumeData.output !== void 0 ? resumeData.output : existingOutput;
2140
- workflowState.addCompletedNode(awaitingNodeId, nodeOutput);
2141
- const nodeResult = { output: nodeOutput };
2142
- const nextSteps = await this.determineNextNodes(blueprint, awaitingNodeId, nodeResult, contextImpl, executionId);
2143
- if (nextSteps.length === 0) {
2144
- workflowState.clearAwaiting(awaitingNodeId);
2145
- const result = await workflowState.toResult(this.serializer, executionId);
2146
- result.status = "completed";
2147
- return result;
2148
- }
2149
- const allPredecessors = new GraphTraverser(blueprint).getAllPredecessors();
2150
- for (const { node, edge } of nextSteps) await this.applyEdgeTransform(edge, nodeResult, node, contextImpl, allPredecessors, executionId);
2151
- const traverser = GraphTraverser.fromState(blueprint, workflowState);
2152
- const nextNodeDefs = nextSteps.map((s) => s.node);
2153
- for (const nodeDef of nextNodeDefs) traverser.addToFrontier(nodeDef.id);
2154
- workflowState.clearAwaiting(awaitingNodeId);
2155
- const executionContext = this._setupResumedExecutionContext(blueprint, workflowState, options);
2156
- return await this.orchestrator.run(executionContext, traverser);
2157
- }
2158
- _createExecutionRegistry(dynamicRegistry) {
2159
- const executionRegistry = new Map(this.registry);
2160
- if (dynamicRegistry) for (const [key, func] of dynamicRegistry.entries()) executionRegistry.set(key, func);
2161
- return executionRegistry;
2162
- }
2163
- async executeNode(blueprint, nodeId, state, _allPredecessors, functionRegistry, executionId, signal) {
2164
- const nodeDef = blueprint.nodes.find((n) => n.id === nodeId);
2165
- if (!nodeDef) throw new FlowcraftError(`Node '${nodeId}' not found in blueprint.`, {
2166
- nodeId,
2167
- blueprintId: blueprint.id,
2168
- executionId,
2169
- isFatal: false
2170
- });
2171
- const asyncContext = state.getContext();
2172
- const input = await this.resolveNodeInput(nodeDef.id, blueprint, asyncContext);
2173
- const nodeRegistry = new Map([...this.registry, ...functionRegistry || /* @__PURE__ */ new Map()]);
2174
- const services = {
2175
- logger: this.logger,
2176
- eventBus: this.eventBus,
2177
- serializer: this.serializer,
2178
- evaluator: this.evaluator,
2179
- middleware: this.middleware,
2180
- dependencies: this.dependencies
2181
- };
2182
- const context = new ExecutionContext(blueprint, state, nodeRegistry, executionId || "", this, services, signal);
2183
- const executionResult = await this.executorFactory.createExecutorForNode(nodeId, context).execute(input);
2184
- if (executionResult.status === "success") return executionResult.result;
2185
- if (executionResult.status === "failed_with_fallback") {
2186
- const fallbackNode = blueprint.nodes.find((n) => n.id === executionResult.fallbackNodeId);
2187
- if (!fallbackNode) throw new FlowcraftError(`Fallback node '${executionResult.fallbackNodeId}' not found in blueprint.`, {
2188
- nodeId: nodeDef.id,
2189
- blueprintId: blueprint.id,
2190
- executionId,
2191
- isFatal: false
2192
- });
2193
- const fallbackInput = await this.resolveNodeInput(fallbackNode.id, blueprint, asyncContext);
2194
- const fallbackResult = await this.executorFactory.createExecutorForNode(fallbackNode.id, context).execute(fallbackInput);
2195
- if (fallbackResult.status === "success") {
2196
- state.markFallbackExecuted();
2197
- state.addCompletedNode(executionResult.fallbackNodeId, fallbackResult.result.output);
2198
- this.logger.info(`Fallback execution completed`, {
2199
- nodeId: nodeDef.id,
2200
- fallbackNodeId: executionResult.fallbackNodeId,
2201
- executionId
2202
- });
2203
- return {
2204
- ...fallbackResult.result,
2205
- _fallbackExecuted: true
2206
- };
2207
- }
2208
- throw fallbackResult.error;
2209
- }
2210
- throw executionResult.error;
2211
- }
2212
- getExecutorForNode(nodeId, context) {
2213
- return this.executorFactory.createExecutorForNode(nodeId, context);
2214
- }
2215
- async determineNextNodes(blueprint, nodeId, result, context, executionId) {
2216
- return this.logicHandler.determineNextNodes(blueprint, nodeId, result, context, executionId);
2217
- }
2218
- async applyEdgeTransform(edge, sourceResult, targetNode, context, allPredecessors, executionId) {
2219
- return this.logicHandler.applyEdgeTransform(edge, sourceResult, targetNode, context, allPredecessors, executionId);
2220
- }
2221
- async resolveNodeInput(nodeId, blueprint, context) {
2222
- return this.logicHandler.resolveNodeInput(nodeId, blueprint, context);
2223
- }
2224
- /**
2225
- * Replay a workflow execution from a pre-recorded event history.
2226
- * This reconstructs the final workflow state without executing any node logic,
2227
- * enabling time-travel debugging and post-mortem analysis.
2228
- *
2229
- * @param blueprint The workflow blueprint
2230
- * @param events The recorded event history for the execution
2231
- * @param executionId Optional execution ID to filter events (if events contain multiple executions)
2232
- * @returns The reconstructed workflow result
2233
- */
2234
- async replay(blueprint, events, executionId) {
2235
- let filteredEvents = events;
2236
- if (executionId) filteredEvents = events.filter((event) => {
2237
- if ("executionId" in event.payload) return event.payload.executionId === executionId;
2238
- return false;
2239
- });
2240
- if (!executionId) {
2241
- const workflowStartEvent = filteredEvents.find((e) => e.type === "workflow:start");
2242
- if (workflowStartEvent && "executionId" in workflowStartEvent.payload) executionId = workflowStartEvent.payload.executionId;
2243
- else throw new FlowcraftError("Cannot determine execution ID from events", { isFatal: true });
2244
- }
2245
- const tempContext = this._setupExecutionContext(blueprint, {}, { strict: false });
2246
- const executionContext = new ExecutionContext(blueprint, tempContext.state, tempContext.nodeRegistry, executionId, this, tempContext.services, tempContext.signal, tempContext.concurrency);
2247
- const { ReplayOrchestrator } = await import("./replay-BB11M6K1.mjs").then((n) => n.n);
2248
- const replayOrchestrator = new ReplayOrchestrator(filteredEvents);
2249
- const traverser = new GraphTraverser(blueprint);
2250
- return await replayOrchestrator.run(executionContext, traverser);
2251
- }
2252
- };
2253
-
2254
- //#endregion
2255
- export { ServiceTokens as A, TrackedAsyncContext as C, PropertyEvaluator as D, NullLogger as E, InMemoryEventStore as F, PersistentEventBusAdapter as I, checkForCycles as M, generateMermaid as N, UnsafeEvaluator as O, generateMermaidForRun as P, Context as S, ConsoleLogger as T, executeBatch as _, sanitizeBlueprint as a, WorkflowState as b, SubflowNode as c, BatchScatterNode as d, BatchGatherNode as f, DefaultOrchestrator as g, JsonSerializer as h, NodeExecutor as i, analyzeBlueprint as j, DIContainer as k, GraphTraverser as l, isNodeClass as m, ClassNodeExecutor as n, WebhookNode as o, BaseNode as p, FunctionNodeExecutor as r, WaitNode as s, FlowRuntime as t, SleepNode as u, processResults as v, FlowcraftError as w, AsyncContextView as x, ExecutionContext as y };
2256
- //# sourceMappingURL=runtime-ChsWirQN.mjs.map