flowcraft 2.0.0 → 2.1.0

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 (86) hide show
  1. package/README.md +1 -1
  2. package/dist/analysis.d.ts +1 -1
  3. package/dist/analysis.js +1 -1
  4. package/dist/{chunk-VFC342WL.js → chunk-4PELJWF7.js} +6 -6
  5. package/dist/chunk-4PELJWF7.js.map +1 -0
  6. package/dist/chunk-5EHIPX23.js +202 -0
  7. package/dist/chunk-5EHIPX23.js.map +1 -0
  8. package/dist/{chunk-DSZSR7UE.js → chunk-5QMPFUKA.js} +2 -2
  9. package/dist/chunk-5QMPFUKA.js.map +1 -0
  10. package/dist/{chunk-6DNEDIIT.js → chunk-5ZWYSKMH.js} +47 -23
  11. package/dist/chunk-5ZWYSKMH.js.map +1 -0
  12. package/dist/{chunk-WXT3YEWU.js → chunk-5ZXV3R5D.js} +2 -2
  13. package/dist/chunk-5ZXV3R5D.js.map +1 -0
  14. package/dist/{chunk-RYTIQZIB.js → chunk-CO5BTPKI.js} +160 -53
  15. package/dist/chunk-CO5BTPKI.js.map +1 -0
  16. package/dist/{chunk-UYPIWXZG.js → chunk-CSZ6EOWG.js} +9 -10
  17. package/dist/chunk-CSZ6EOWG.js.map +1 -0
  18. package/dist/chunk-CYHZ2YVH.js +24 -0
  19. package/dist/chunk-CYHZ2YVH.js.map +1 -0
  20. package/dist/{chunk-J3RNCPED.js → chunk-DSYAC4WB.js} +2 -2
  21. package/dist/chunk-DSYAC4WB.js.map +1 -0
  22. package/dist/{chunk-M23P46ZL.js → chunk-HN72TZY5.js} +10 -5
  23. package/dist/chunk-HN72TZY5.js.map +1 -0
  24. package/dist/{chunk-MICPMOTW.js → chunk-KWQHFT7E.js} +2 -2
  25. package/dist/chunk-KWQHFT7E.js.map +1 -0
  26. package/dist/chunk-PH2IYZHV.js +48 -0
  27. package/dist/chunk-PH2IYZHV.js.map +1 -0
  28. package/dist/{chunk-734J4PTM.js → chunk-QRMUKDSP.js} +56 -15
  29. package/dist/chunk-QRMUKDSP.js.map +1 -0
  30. package/dist/{chunk-RAZXOMZC.js → chunk-UETC63DP.js} +7 -6
  31. package/dist/chunk-UETC63DP.js.map +1 -0
  32. package/dist/{chunk-RW4FH7IL.js → chunk-UMXW3TCY.js} +60 -30
  33. package/dist/chunk-UMXW3TCY.js.map +1 -0
  34. package/dist/context.d.ts +5 -5
  35. package/dist/context.js +1 -1
  36. package/dist/errors.js +1 -1
  37. package/dist/evaluator.d.ts +21 -13
  38. package/dist/evaluator.js +1 -1
  39. package/dist/flow.d.ts +4 -3
  40. package/dist/flow.js +2 -2
  41. package/dist/index.d.ts +2 -1
  42. package/dist/index.js +15 -15
  43. package/dist/linter.d.ts +1 -1
  44. package/dist/linter.js +2 -2
  45. package/dist/logger.d.ts +5 -5
  46. package/dist/logger.js +1 -1
  47. package/dist/node.d.ts +1 -1
  48. package/dist/node.js +1 -1
  49. package/dist/runtime/adapter.d.ts +20 -4
  50. package/dist/runtime/adapter.js +13 -13
  51. package/dist/runtime/executors.d.ts +7 -7
  52. package/dist/runtime/executors.js +2 -2
  53. package/dist/runtime/index.d.ts +1 -1
  54. package/dist/runtime/index.js +13 -13
  55. package/dist/runtime/runtime.d.ts +7 -6
  56. package/dist/runtime/runtime.js +12 -12
  57. package/dist/runtime/state.d.ts +2 -2
  58. package/dist/runtime/state.js +2 -2
  59. package/dist/runtime/traverser.d.ts +4 -3
  60. package/dist/runtime/traverser.js +3 -3
  61. package/dist/runtime/types.d.ts +1 -1
  62. package/dist/sanitizer.d.ts +1 -1
  63. package/dist/sanitizer.js +1 -1
  64. package/dist/serializer.d.ts +2 -1
  65. package/dist/serializer.js +1 -1
  66. package/dist/{types-CZN_FcB6.d.ts → types-lG3xCzp_.d.ts} +27 -22
  67. package/dist/types.d.ts +1 -1
  68. package/package.json +2 -2
  69. package/dist/chunk-6DNEDIIT.js.map +0 -1
  70. package/dist/chunk-734J4PTM.js.map +0 -1
  71. package/dist/chunk-DSZSR7UE.js.map +0 -1
  72. package/dist/chunk-GTZC6PQI.js +0 -22
  73. package/dist/chunk-GTZC6PQI.js.map +0 -1
  74. package/dist/chunk-J3RNCPED.js.map +0 -1
  75. package/dist/chunk-M23P46ZL.js.map +0 -1
  76. package/dist/chunk-MICPMOTW.js.map +0 -1
  77. package/dist/chunk-NPAJNLXQ.js +0 -106
  78. package/dist/chunk-NPAJNLXQ.js.map +0 -1
  79. package/dist/chunk-RAZXOMZC.js.map +0 -1
  80. package/dist/chunk-REH55ZXV.js +0 -13
  81. package/dist/chunk-REH55ZXV.js.map +0 -1
  82. package/dist/chunk-RW4FH7IL.js.map +0 -1
  83. package/dist/chunk-RYTIQZIB.js.map +0 -1
  84. package/dist/chunk-UYPIWXZG.js.map +0 -1
  85. package/dist/chunk-VFC342WL.js.map +0 -1
  86. package/dist/chunk-WXT3YEWU.js.map +0 -1
package/README.md CHANGED
@@ -49,7 +49,7 @@ run()
49
49
 
50
50
  ## Documentation
51
51
 
52
- For a complete overview all features, patterns, examples, and APIs, please see the **[Flowcraft documentation](https://flowcraft.js.org/)**.
52
+ For a complete overview of all features, patterns, examples, and APIs, see the **[Flowcraft documentation](https://flowcraft.js.org/)**.
53
53
 
54
54
  ## License
55
55
 
@@ -1,4 +1,4 @@
1
- import { W as WorkflowBlueprint } from './types-CZN_FcB6.js';
1
+ import { W as WorkflowBlueprint } from './types-lG3xCzp_.js';
2
2
 
3
3
  /**
4
4
  * A list of cycles found in the graph. Each cycle is an array of node IDs.
package/dist/analysis.js CHANGED
@@ -1,3 +1,3 @@
1
- export { analyzeBlueprint, checkForCycles, generateMermaid } from './chunk-M23P46ZL.js';
1
+ export { analyzeBlueprint, checkForCycles, generateMermaid } from './chunk-HN72TZY5.js';
2
2
  //# sourceMappingURL=analysis.js.map
3
3
  //# sourceMappingURL=analysis.js.map
@@ -14,16 +14,16 @@ var ConsoleLogger = class {
14
14
  }
15
15
  };
16
16
  var NullLogger = class {
17
- debug() {
17
+ debug(_message, _meta) {
18
18
  }
19
- info() {
19
+ info(_message, _meta) {
20
20
  }
21
- warn() {
21
+ warn(_message, _meta) {
22
22
  }
23
- error() {
23
+ error(_message, _meta) {
24
24
  }
25
25
  };
26
26
 
27
27
  export { ConsoleLogger, NullLogger };
28
- //# sourceMappingURL=chunk-VFC342WL.js.map
29
- //# sourceMappingURL=chunk-VFC342WL.js.map
28
+ //# sourceMappingURL=chunk-4PELJWF7.js.map
29
+ //# sourceMappingURL=chunk-4PELJWF7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/logger.ts"],"names":[],"mappings":";AAGO,IAAM,gBAAN,MAAuC;AAAA,EAC7C,KAAA,CAAM,SAAiB,IAAA,EAAkC;AACxD,IAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,QAAA,EAAW,OAAO,CAAA,CAAA,EAAI,QAAQ,EAAE,CAAA;AAAA,EAC/C;AAAA,EAEA,IAAA,CAAK,SAAiB,IAAA,EAAkC;AACvD,IAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,OAAA,EAAU,OAAO,CAAA,CAAA,EAAI,QAAQ,EAAE,CAAA;AAAA,EAC7C;AAAA,EAEA,IAAA,CAAK,SAAiB,IAAA,EAAkC;AACvD,IAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,OAAA,EAAU,OAAO,CAAA,CAAA,EAAI,QAAQ,EAAE,CAAA;AAAA,EAC7C;AAAA,EAEA,KAAA,CAAM,SAAiB,IAAA,EAAkC;AACxD,IAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,QAAA,EAAW,OAAO,CAAA,CAAA,EAAI,QAAQ,EAAE,CAAA;AAAA,EAC/C;AACD;AAGO,IAAM,aAAN,MAAoC;AAAA,EAC1C,KAAA,CAAM,UAAkB,KAAA,EAAmC;AAAA,EAAC;AAAA,EAC5D,IAAA,CAAK,UAAkB,KAAA,EAAmC;AAAA,EAAC;AAAA,EAC3D,IAAA,CAAK,UAAkB,KAAA,EAAmC;AAAA,EAAC;AAAA,EAC3D,KAAA,CAAM,UAAkB,KAAA,EAAmC;AAAA,EAAC;AAC7D","file":"chunk-4PELJWF7.js","sourcesContent":["import type { ILogger } from './types'\n\n/** A logger implementation that outputs to the console. */\nexport class ConsoleLogger implements ILogger {\n\tdebug(message: string, meta?: Record<string, any>): void {\n\t\tconsole.debug(`[DEBUG] ${message}`, meta || '')\n\t}\n\n\tinfo(message: string, meta?: Record<string, any>): void {\n\t\tconsole.info(`[INFO] ${message}`, meta || '')\n\t}\n\n\twarn(message: string, meta?: Record<string, any>): void {\n\t\tconsole.warn(`[WARN] ${message}`, meta || '')\n\t}\n\n\terror(message: string, meta?: Record<string, any>): void {\n\t\tconsole.error(`[ERROR] ${message}`, meta || '')\n\t}\n}\n\n/** A logger implementation that does nothing (no-op). */\nexport class NullLogger implements ILogger {\n\tdebug(_message: string, _meta?: Record<string, any>): void {}\n\tinfo(_message: string, _meta?: Record<string, any>): void {}\n\twarn(_message: string, _meta?: Record<string, any>): void {}\n\terror(_message: string, _meta?: Record<string, any>): void {}\n}\n"]}
@@ -0,0 +1,202 @@
1
+ import { FlowRuntime } from './chunk-CO5BTPKI.js';
2
+ import { JsonSerializer } from './chunk-CYHZ2YVH.js';
3
+
4
+ // src/runtime/adapter.ts
5
+ var BaseDistributedAdapter = class {
6
+ runtime;
7
+ store;
8
+ serializer;
9
+ constructor(options) {
10
+ this.runtime = new FlowRuntime(options.runtimeOptions);
11
+ this.store = options.coordinationStore;
12
+ this.serializer = options.runtimeOptions.serializer || new JsonSerializer();
13
+ console.log("[Adapter] BaseDistributedAdapter initialized.");
14
+ }
15
+ /**
16
+ * Starts the worker, which begins listening for and processing jobs from the queue.
17
+ */
18
+ start() {
19
+ console.log("[Adapter] Starting worker...");
20
+ this.processJobs(this.handleJob.bind(this));
21
+ }
22
+ /**
23
+ * Hook called at the start of job processing. Subclasses can override this
24
+ * to perform additional setup (e.g., timestamp tracking for reconciliation).
25
+ */
26
+ async onJobStart(_runId, _blueprintId, _nodeId) {
27
+ }
28
+ /**
29
+ * The main handler for processing a single job from the queue.
30
+ */
31
+ async handleJob(job) {
32
+ const { runId, blueprintId, nodeId } = job;
33
+ await this.onJobStart(runId, blueprintId, nodeId);
34
+ const blueprint = this.runtime.options.blueprints?.[blueprintId];
35
+ if (!blueprint) {
36
+ const reason = `Blueprint with ID '${blueprintId}' not found in the worker's runtime registry.`;
37
+ console.error(`[Adapter] FATAL: ${reason}`);
38
+ await this.publishFinalResult(runId, { status: "failed", reason });
39
+ return;
40
+ }
41
+ const context = this.createContext(runId);
42
+ const hasBlueprintId = await context.has("blueprintId");
43
+ if (!hasBlueprintId) {
44
+ await context.set("blueprintId", blueprintId);
45
+ }
46
+ const workerState = {
47
+ getContext: () => context,
48
+ markFallbackExecuted: () => {
49
+ },
50
+ addError: (nodeId2, error) => {
51
+ console.error(`[Adapter] Error in node ${nodeId2}:`, error);
52
+ }
53
+ };
54
+ try {
55
+ const result = await this.runtime.executeNode(blueprint, nodeId, workerState);
56
+ await context.set(nodeId, result.output);
57
+ const nodeDef = blueprint.nodes.find((n) => n.id === nodeId);
58
+ if (nodeDef?.uses === "output") {
59
+ console.log(`[Adapter] \u2705 Output node '${nodeId}' finished. Declaring workflow complete for Run ID: ${runId}`);
60
+ const finalContext = await context.toJSON();
61
+ const finalResult = {
62
+ context: finalContext,
63
+ serializedContext: this.serializer.serialize(finalContext),
64
+ status: "completed"
65
+ };
66
+ await this.publishFinalResult(runId, {
67
+ status: "completed",
68
+ payload: finalResult
69
+ });
70
+ return;
71
+ }
72
+ const nextNodes = await this.runtime.determineNextNodes(blueprint, nodeId, result, context);
73
+ if (nextNodes.length === 0) {
74
+ console.log(
75
+ `[Adapter] Terminal node '${nodeId}' reached for Run ID '${runId}', but it was not an 'output' node. This branch will now terminate.`
76
+ );
77
+ return;
78
+ }
79
+ for (const { node: nextNodeDef, edge } of nextNodes) {
80
+ await this.runtime.applyEdgeTransform(edge, result, nextNodeDef, context);
81
+ const isReady = await this.isReadyForFanIn(runId, blueprint, nextNodeDef.id);
82
+ if (isReady) {
83
+ console.log(`[Adapter] Node '${nextNodeDef.id}' is ready. Enqueuing job.`);
84
+ await this.enqueueJob({ runId, blueprintId, nodeId: nextNodeDef.id });
85
+ } else {
86
+ console.log(`[Adapter] Node '${nextNodeDef.id}' is waiting for other predecessors to complete.`);
87
+ }
88
+ }
89
+ } catch (error) {
90
+ const reason = error.message || "Unknown execution error";
91
+ console.error(`[Adapter] FATAL: Job for node '${nodeId}' failed for Run ID '${runId}': ${reason}`);
92
+ await this.publishFinalResult(runId, { status: "failed", reason });
93
+ }
94
+ }
95
+ /**
96
+ * Encapsulates the fan-in join logic using the coordination store.
97
+ */
98
+ async isReadyForFanIn(runId, blueprint, targetNodeId) {
99
+ const targetNode = blueprint.nodes.find((n) => n.id === targetNodeId);
100
+ if (!targetNode) {
101
+ throw new Error(`Node '${targetNodeId}' not found in blueprint`);
102
+ }
103
+ const joinStrategy = targetNode.config?.joinStrategy || "all";
104
+ const predecessors = blueprint.edges.filter((e) => e.target === targetNodeId);
105
+ if (predecessors.length <= 1) {
106
+ return true;
107
+ }
108
+ if (joinStrategy === "any") {
109
+ const lockKey = `flowcraft:joinlock:${runId}:${targetNodeId}`;
110
+ return await this.store.setIfNotExist(lockKey, "locked", 3600);
111
+ } else {
112
+ const fanInKey = `flowcraft:fanin:${runId}:${targetNodeId}`;
113
+ const readyCount = await this.store.increment(fanInKey, 3600);
114
+ if (readyCount >= predecessors.length) {
115
+ await this.store.delete(fanInKey);
116
+ return true;
117
+ }
118
+ return false;
119
+ }
120
+ }
121
+ /**
122
+ * Reconciles the state of a workflow run. It inspects the persisted
123
+ * context to find completed nodes, determines the next set of executable
124
+ * nodes (the frontier), and enqueues jobs for them if they aren't
125
+ * already running. This is the core of the resume functionality.
126
+ *
127
+ * @param runId The unique ID of the workflow execution to reconcile.
128
+ * @returns The set of node IDs that were enqueued for execution.
129
+ */
130
+ async reconcile(runId) {
131
+ const context = this.createContext(runId);
132
+ const blueprintId = await context.get("blueprintId");
133
+ if (!blueprintId) {
134
+ throw new Error(`Cannot reconcile runId '${runId}': blueprintId not found in context.`);
135
+ }
136
+ const blueprint = this.runtime.options.blueprints?.[blueprintId];
137
+ if (!blueprint) {
138
+ throw new Error(`Cannot reconcile runId '${runId}': Blueprint with ID '${blueprintId}' not found.`);
139
+ }
140
+ const state = await context.toJSON();
141
+ const completedNodes = new Set(Object.keys(state).filter((k) => blueprint.nodes.some((n) => n.id === k)));
142
+ const frontier = this.calculateResumedFrontier(blueprint, completedNodes);
143
+ const enqueuedNodes = /* @__PURE__ */ new Set();
144
+ for (const nodeId of frontier) {
145
+ const nodeDef = blueprint.nodes.find((n) => n.id === nodeId);
146
+ const joinStrategy = nodeDef?.config?.joinStrategy || "all";
147
+ let shouldEnqueue = false;
148
+ if (joinStrategy === "any") {
149
+ const lockKey = `flowcraft:joinlock:${runId}:${nodeId}`;
150
+ if (await this.store.setIfNotExist(lockKey, "locked-by-reconcile", 3600)) {
151
+ shouldEnqueue = true;
152
+ } else {
153
+ console.log(`[Adapter] Reconciling: Node '${nodeId}' is an 'any' join and is already locked.`, { runId });
154
+ }
155
+ } else {
156
+ const lockKey = `flowcraft:nodelock:${runId}:${nodeId}`;
157
+ if (await this.store.setIfNotExist(lockKey, "locked", 120)) {
158
+ shouldEnqueue = true;
159
+ } else {
160
+ console.log(`[Adapter] Reconciling: Node '${nodeId}' is already locked.`, { runId });
161
+ }
162
+ }
163
+ if (shouldEnqueue) {
164
+ console.log(`[Adapter] Reconciling: Enqueuing ready job for node '${nodeId}'`, { runId });
165
+ await this.enqueueJob({ runId, blueprintId: blueprint.id, nodeId });
166
+ enqueuedNodes.add(nodeId);
167
+ }
168
+ }
169
+ return enqueuedNodes;
170
+ }
171
+ calculateResumedFrontier(blueprint, completedNodes) {
172
+ const newFrontier = /* @__PURE__ */ new Set();
173
+ const allPredecessors = /* @__PURE__ */ new Map();
174
+ for (const node of blueprint.nodes) {
175
+ allPredecessors.set(node.id, /* @__PURE__ */ new Set());
176
+ }
177
+ for (const edge of blueprint.edges) {
178
+ allPredecessors.get(edge.target)?.add(edge.source);
179
+ }
180
+ for (const node of blueprint.nodes) {
181
+ if (completedNodes.has(node.id)) {
182
+ continue;
183
+ }
184
+ const predecessors = allPredecessors.get(node.id) ?? /* @__PURE__ */ new Set();
185
+ if (predecessors.size === 0 && !completedNodes.has(node.id)) {
186
+ newFrontier.add(node.id);
187
+ continue;
188
+ }
189
+ const joinStrategy = node.config?.joinStrategy || "all";
190
+ const completedPredecessors = [...predecessors].filter((p) => completedNodes.has(p));
191
+ const isReady = joinStrategy === "any" ? completedPredecessors.length > 0 : completedPredecessors.length === predecessors.size;
192
+ if (isReady) {
193
+ newFrontier.add(node.id);
194
+ }
195
+ }
196
+ return newFrontier;
197
+ }
198
+ };
199
+
200
+ export { BaseDistributedAdapter };
201
+ //# sourceMappingURL=chunk-5EHIPX23.js.map
202
+ //# sourceMappingURL=chunk-5EHIPX23.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/runtime/adapter.ts"],"names":["nodeId"],"mappings":"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","file":"chunk-5EHIPX23.js","sourcesContent":["import { JsonSerializer } from '../serializer'\nimport type {\n\tIAsyncContext,\n\tISerializer,\n\tNodeResult,\n\tRuntimeOptions,\n\tWorkflowBlueprint,\n\tWorkflowResult,\n} from '../types'\nimport { FlowRuntime } from './runtime'\n\n/**\n * Defines the contract for an atomic, distributed key-value store required by\n * the adapter for coordination tasks like fan-in joins and locking.\n */\nexport interface ICoordinationStore {\n\t/** Atomically increments a key and returns the new value. Ideal for 'all' joins. */\n\tincrement: (key: string, ttlSeconds: number) => Promise<number>\n\t/** Sets a key only if it does not already exist. Ideal for 'any' joins (locking). */\n\tsetIfNotExist: (key: string, value: string, ttlSeconds: number) => Promise<boolean>\n\t/** Deletes a key. Used for cleanup. */\n\tdelete: (key: string) => Promise<void>\n}\n\n/** Configuration options for constructing a BaseDistributedAdapter. */\nexport interface AdapterOptions {\n\truntimeOptions: RuntimeOptions<any>\n\tcoordinationStore: ICoordinationStore\n}\n\n/** The data payload expected for a job in the queue. */\nexport interface JobPayload {\n\trunId: string\n\tblueprintId: string\n\tnodeId: string\n}\n\n/**\n * The base class for all distributed adapters. It handles the technology-agnostic\n * orchestration logic and leaves queue-specific implementation to subclasses.\n */\nexport abstract class BaseDistributedAdapter {\n\tprotected readonly runtime: FlowRuntime<any, any>\n\tprotected readonly store: ICoordinationStore\n\tprotected readonly serializer: ISerializer\n\n\tconstructor(options: AdapterOptions) {\n\t\tthis.runtime = new FlowRuntime(options.runtimeOptions)\n\t\tthis.store = options.coordinationStore\n\t\tthis.serializer = options.runtimeOptions.serializer || new JsonSerializer()\n\t\tconsole.log('[Adapter] BaseDistributedAdapter initialized.')\n\t}\n\n\t/**\n\t * Starts the worker, which begins listening for and processing jobs from the queue.\n\t */\n\tpublic start(): void {\n\t\tconsole.log('[Adapter] Starting worker...')\n\t\tthis.processJobs(this.handleJob.bind(this))\n\t}\n\n\t/**\n\t * Creates a technology-specific distributed context for a given workflow run.\n\t * @param runId The unique ID for the workflow execution.\n\t */\n\tprotected abstract createContext(runId: string): IAsyncContext<Record<string, any>>\n\t/**\n\t * Sets up the listener for the message queue. The implementation should call the\n\t * provided `handler` function for each new job received.\n\t * @param handler The core logic to execute for each job.\n\t */\n\tprotected abstract processJobs(handler: (job: JobPayload) => Promise<void>): void\n\n\t/**\n\t * Enqueues a new job onto the message queue.\n\t * @param job The payload for the job to be enqueued.\n\t */\n\tprotected abstract enqueueJob(job: JobPayload): Promise<void>\n\n\t/**\n\t * Publishes the final result of a completed or failed workflow run.\n\t * @param runId The unique ID of the workflow run.\n\t * @param result The final status and payload of the workflow.\n\t */\n\tprotected abstract publishFinalResult(\n\t\trunId: string,\n\t\tresult: {\n\t\t\tstatus: 'completed' | 'failed'\n\t\t\tpayload?: WorkflowResult\n\t\t\treason?: string\n\t\t},\n\t): Promise<void>\n\n\t/**\n\t * Hook called at the start of job processing. Subclasses can override this\n\t * to perform additional setup (e.g., timestamp tracking for reconciliation).\n\t */\n\tprotected async onJobStart(_runId: string, _blueprintId: string, _nodeId: string): Promise<void> {\n\t\t// default implementation does nothing\n\t}\n\n\t/**\n\t * The main handler for processing a single job from the queue.\n\t */\n\tprotected async handleJob(job: JobPayload): Promise<void> {\n\t\tconst { runId, blueprintId, nodeId } = job\n\n\t\tawait this.onJobStart(runId, blueprintId, nodeId)\n\n\t\tconst blueprint = this.runtime.options.blueprints?.[blueprintId]\n\t\tif (!blueprint) {\n\t\t\tconst reason = `Blueprint with ID '${blueprintId}' not found in the worker's runtime registry.`\n\t\t\tconsole.error(`[Adapter] FATAL: ${reason}`)\n\t\t\tawait this.publishFinalResult(runId, { status: 'failed', reason })\n\t\t\treturn\n\t\t}\n\n\t\tconst context = this.createContext(runId)\n\n\t\t// persist the blueprintId for the reconcile method to find later\n\t\tconst hasBlueprintId = await context.has('blueprintId' as any)\n\t\tif (!hasBlueprintId) {\n\t\t\tawait context.set('blueprintId' as any, blueprintId)\n\t\t}\n\t\tconst workerState = {\n\t\t\tgetContext: () => context,\n\t\t\tmarkFallbackExecuted: () => {},\n\t\t\taddError: (nodeId: string, error: Error) => {\n\t\t\t\tconsole.error(`[Adapter] Error in node ${nodeId}:`, error)\n\t\t\t},\n\t\t} as any\n\n\t\ttry {\n\t\t\tconst result: NodeResult<any, any> = await this.runtime.executeNode(blueprint, nodeId, workerState)\n\t\t\tawait context.set(nodeId as any, result.output)\n\n\t\t\tconst nodeDef = blueprint.nodes.find((n) => n.id === nodeId)\n\t\t\t// workflow is considered complete when the first 'output' node finishes.\n\t\t\tif (nodeDef?.uses === 'output') {\n\t\t\t\tconsole.log(`[Adapter] ✅ Output node '${nodeId}' finished. Declaring workflow complete for Run ID: ${runId}`)\n\t\t\t\tconst finalContext = await context.toJSON()\n\t\t\t\tconst finalResult: WorkflowResult = {\n\t\t\t\t\tcontext: finalContext,\n\t\t\t\t\tserializedContext: this.serializer.serialize(finalContext),\n\t\t\t\t\tstatus: 'completed',\n\t\t\t\t}\n\t\t\t\tawait this.publishFinalResult(runId, {\n\t\t\t\t\tstatus: 'completed',\n\t\t\t\t\tpayload: finalResult,\n\t\t\t\t})\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst nextNodes = await this.runtime.determineNextNodes(blueprint, nodeId, result, context)\n\n\t\t\t// stop if a branch terminates but it wasn't an 'output' node\n\t\t\tif (nextNodes.length === 0) {\n\t\t\t\tconsole.log(\n\t\t\t\t\t`[Adapter] Terminal node '${nodeId}' reached for Run ID '${runId}', but it was not an 'output' node. This branch will now terminate.`,\n\t\t\t\t)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfor (const { node: nextNodeDef, edge } of nextNodes) {\n\t\t\t\tawait this.runtime.applyEdgeTransform(edge, result, nextNodeDef, context)\n\t\t\t\tconst isReady = await this.isReadyForFanIn(runId, blueprint, nextNodeDef.id)\n\t\t\t\tif (isReady) {\n\t\t\t\t\tconsole.log(`[Adapter] Node '${nextNodeDef.id}' is ready. Enqueuing job.`)\n\t\t\t\t\tawait this.enqueueJob({ runId, blueprintId, nodeId: nextNodeDef.id })\n\t\t\t\t} else {\n\t\t\t\t\tconsole.log(`[Adapter] Node '${nextNodeDef.id}' is waiting for other predecessors to complete.`)\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error: any) {\n\t\t\tconst reason = error.message || 'Unknown execution error'\n\t\t\tconsole.error(`[Adapter] FATAL: Job for node '${nodeId}' failed for Run ID '${runId}': ${reason}`)\n\t\t\tawait this.publishFinalResult(runId, { status: 'failed', reason })\n\t\t}\n\t}\n\n\t/**\n\t * Encapsulates the fan-in join logic using the coordination store.\n\t */\n\tprotected async isReadyForFanIn(runId: string, blueprint: WorkflowBlueprint, targetNodeId: string): Promise<boolean> {\n\t\tconst targetNode = blueprint.nodes.find((n) => n.id === targetNodeId)\n\t\tif (!targetNode) {\n\t\t\tthrow new Error(`Node '${targetNodeId}' not found in blueprint`)\n\t\t}\n\t\tconst joinStrategy = targetNode.config?.joinStrategy || 'all'\n\t\tconst predecessors = blueprint.edges.filter((e) => e.target === targetNodeId)\n\n\t\tif (predecessors.length <= 1) {\n\t\t\treturn true\n\t\t}\n\n\t\tif (joinStrategy === 'any') {\n\t\t\tconst lockKey = `flowcraft:joinlock:${runId}:${targetNodeId}`\n\t\t\treturn await this.store.setIfNotExist(lockKey, 'locked', 3600)\n\t\t} else {\n\t\t\tconst fanInKey = `flowcraft:fanin:${runId}:${targetNodeId}`\n\t\t\tconst readyCount = await this.store.increment(fanInKey, 3600)\n\t\t\tif (readyCount >= predecessors.length) {\n\t\t\t\tawait this.store.delete(fanInKey)\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn false\n\t\t}\n\t}\n\n\t/**\n\t * Reconciles the state of a workflow run. It inspects the persisted\n\t * context to find completed nodes, determines the next set of executable\n\t * nodes (the frontier), and enqueues jobs for them if they aren't\n\t * already running. This is the core of the resume functionality.\n\t *\n\t * @param runId The unique ID of the workflow execution to reconcile.\n\t * @returns The set of node IDs that were enqueued for execution.\n\t */\n\tpublic async reconcile(runId: string): Promise<Set<string>> {\n\t\tconst context = this.createContext(runId)\n\t\tconst blueprintId = (await context.get('blueprintId' as any)) as string | undefined\n\n\t\tif (!blueprintId) {\n\t\t\tthrow new Error(`Cannot reconcile runId '${runId}': blueprintId not found in context.`)\n\t\t}\n\t\tconst blueprint = this.runtime.options.blueprints?.[blueprintId]\n\t\tif (!blueprint) {\n\t\t\tthrow new Error(`Cannot reconcile runId '${runId}': Blueprint with ID '${blueprintId}' not found.`)\n\t\t}\n\n\t\tconst state = await context.toJSON()\n\t\t// filter out internal keys\n\t\tconst completedNodes = new Set(Object.keys(state).filter((k) => blueprint.nodes.some((n) => n.id === k)))\n\n\t\tconst frontier = this.calculateResumedFrontier(blueprint, completedNodes)\n\n\t\tconst enqueuedNodes = new Set<string>()\n\t\tfor (const nodeId of frontier) {\n\t\t\tconst nodeDef = blueprint.nodes.find((n) => n.id === nodeId)\n\t\t\tconst joinStrategy = nodeDef?.config?.joinStrategy || 'all'\n\n\t\t\tlet shouldEnqueue = false\n\n\t\t\tif (joinStrategy === 'any') {\n\t\t\t\t// acquire the permanent join lock\n\t\t\t\tconst lockKey = `flowcraft:joinlock:${runId}:${nodeId}`\n\t\t\t\tif (await this.store.setIfNotExist(lockKey, 'locked-by-reconcile', 3600)) {\n\t\t\t\t\tshouldEnqueue = true\n\t\t\t\t} else {\n\t\t\t\t\tconsole.log(`[Adapter] Reconciling: Node '${nodeId}' is an 'any' join and is already locked.`, { runId })\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// 'all' joins and single-predecessor nodes use a temporary lock\n\t\t\t\tconst lockKey = `flowcraft:nodelock:${runId}:${nodeId}`\n\t\t\t\tif (await this.store.setIfNotExist(lockKey, 'locked', 120)) {\n\t\t\t\t\tshouldEnqueue = true\n\t\t\t\t} else {\n\t\t\t\t\tconsole.log(`[Adapter] Reconciling: Node '${nodeId}' is already locked.`, { runId })\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (shouldEnqueue) {\n\t\t\t\tconsole.log(`[Adapter] Reconciling: Enqueuing ready job for node '${nodeId}'`, { runId })\n\t\t\t\tawait this.enqueueJob({ runId, blueprintId: blueprint.id, nodeId })\n\t\t\t\tenqueuedNodes.add(nodeId)\n\t\t\t}\n\t\t}\n\n\t\treturn enqueuedNodes\n\t}\n\n\tprivate calculateResumedFrontier(blueprint: WorkflowBlueprint, completedNodes: Set<string>): Set<string> {\n\t\tconst newFrontier = new Set<string>()\n\t\tconst allPredecessors = new Map<string, Set<string>>()\n\t\t// (logic extracted from the GraphTraverser)\n\t\tfor (const node of blueprint.nodes) {\n\t\t\tallPredecessors.set(node.id, new Set())\n\t\t}\n\t\tfor (const edge of blueprint.edges) {\n\t\t\tallPredecessors.get(edge.target)?.add(edge.source)\n\t\t}\n\n\t\tfor (const node of blueprint.nodes) {\n\t\t\tif (completedNodes.has(node.id)) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tconst predecessors = allPredecessors.get(node.id) ?? new Set()\n\t\t\tif (predecessors.size === 0 && !completedNodes.has(node.id)) {\n\t\t\t\tnewFrontier.add(node.id)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tconst joinStrategy = node.config?.joinStrategy || 'all'\n\t\t\tconst completedPredecessors = [...predecessors].filter((p) => completedNodes.has(p))\n\n\t\t\tconst isReady =\n\t\t\t\tjoinStrategy === 'any' ? completedPredecessors.length > 0 : completedPredecessors.length === predecessors.size\n\n\t\t\tif (isReady) {\n\t\t\t\tnewFrontier.add(node.id)\n\t\t\t}\n\t\t}\n\t\treturn newFrontier\n\t}\n}\n"]}
@@ -36,5 +36,5 @@ var BaseNode = class {
36
36
  };
37
37
 
38
38
  export { BaseNode, isNodeClass };
39
- //# sourceMappingURL=chunk-DSZSR7UE.js.map
40
- //# sourceMappingURL=chunk-DSZSR7UE.js.map
39
+ //# sourceMappingURL=chunk-5QMPFUKA.js.map
40
+ //# sourceMappingURL=chunk-5QMPFUKA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/node.ts"],"names":[],"mappings":";AAGO,SAAS,YAAY,IAAA,EAA8B;AACzD,EAAA,OAAO,OAAO,IAAA,KAAS,UAAA,IAAc,CAAC,CAAC,KAAK,SAAA,EAAW,IAAA;AACxD;AAOO,IAAe,WAAf,MAML;AAAA;AAAA;AAAA;AAAA,EAID,YAAsB,MAAA,EAA8B;AAA9B,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAA,EAA+B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOrD,MAAM,KAAK,OAAA,EAAqE;AAC/E,IAAA,OAAO,OAAA,CAAQ,KAAA;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,MAAM,IAAA,CACL,UAAA,EACA,QAAA,EACwC;AACxC,IAAA,OAAO,UAAA;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,QAAA,CACL,KAAA,EACA,QAAA,EACuD;AAEvD,IAAA,MAAM,KAAA;AAAA,EACP;AACD","file":"chunk-5QMPFUKA.js","sourcesContent":["import type { NodeClass, NodeContext, NodeResult, RuntimeDependencies } from './types'\n\n/** A type guard to reliably distinguish a NodeClass from a NodeFunction. */\nexport function isNodeClass(impl: any): impl is NodeClass {\n\treturn typeof impl === 'function' && !!impl.prototype?.exec\n}\n\n/**\n * A structured, class-based node for complex logic with a safe, granular lifecycle.\n * This class is generic, allowing implementations to specify the exact context\n * and dependency types they expect.\n */\nexport abstract class BaseNode<\n\tTContext extends Record<string, any> = Record<string, any>,\n\tTDependencies extends RuntimeDependencies = RuntimeDependencies,\n\tTInput = any,\n\tTOutput = any,\n\tTAction extends string = string,\n> {\n\t/**\n\t * @param params Static parameters for this node instance, passed from the blueprint.\n\t */\n\tconstructor(protected params?: Record<string, any>) {}\n\n\t/**\n\t * Phase 1: Gathers and prepares data for execution. This phase is NOT retried on failure.\n\t * @param context The node's execution context.\n\t * @returns The data needed for the `exec` phase.\n\t */\n\tasync prep(context: NodeContext<TContext, TDependencies, TInput>): Promise<any> {\n\t\treturn context.input\n\t}\n\n\t/**\n\t * Phase 2: Performs the core, isolated logic. This is the ONLY phase that is retried.\n\t * @param prepResult The data returned from the `prep` phase.\n\t * @param context The node's execution context.\n\t */\n\tabstract exec(\n\t\tprepResult: any,\n\t\tcontext: NodeContext<TContext, TDependencies, TInput>,\n\t): Promise<Omit<NodeResult<TOutput, TAction>, 'error'>>\n\n\t/**\n\t * Phase 3: Processes the result and saves state. This phase is NOT retried.\n\t * @param execResult The successful result from the `exec` or `fallback` phase.\n\t * @param _context The node's execution context.\n\t */\n\tasync post(\n\t\texecResult: Omit<NodeResult<TOutput, TAction>, 'error'>,\n\t\t_context: NodeContext<TContext, TDependencies, TInput>,\n\t): Promise<NodeResult<TOutput, TAction>> {\n\t\treturn execResult\n\t}\n\n\t/**\n\t * An optional safety net that runs if all `exec` retries fail.\n\t * @param error The final error from the last `exec` attempt.\n\t * @param _context The node's execution context.\n\t */\n\tasync fallback(\n\t\terror: Error,\n\t\t_context: NodeContext<TContext, TDependencies, TInput>,\n\t): Promise<Omit<NodeResult<TOutput, TAction>, 'error'>> {\n\t\t// By default, re-throw the error, failing the node.\n\t\tthrow error\n\t}\n}\n"]}
@@ -1,31 +1,43 @@
1
- import { isNodeClass } from './chunk-DSZSR7UE.js';
1
+ import { isNodeClass } from './chunk-5QMPFUKA.js';
2
2
 
3
3
  // src/flow.ts
4
+ function _hashFunction(fn) {
5
+ const source = fn.toString();
6
+ let hash = 0;
7
+ for (let i = 0; i < source.length; i++) {
8
+ const char = source.charCodeAt(i);
9
+ hash = (hash << 5) - hash + char;
10
+ hash = hash & hash;
11
+ }
12
+ return Math.abs(hash).toString(16);
13
+ }
4
14
  var Flow = class {
5
15
  blueprint;
6
16
  functionRegistry;
7
17
  loopControllerIds;
18
+ loopDefinitions;
8
19
  constructor(id) {
9
20
  this.blueprint = { id, nodes: [], edges: [] };
10
21
  this.functionRegistry = /* @__PURE__ */ new Map();
11
22
  this.loopControllerIds = /* @__PURE__ */ new Map();
23
+ this.loopDefinitions = [];
12
24
  }
13
25
  node(id, implementation, options) {
14
26
  let usesKey;
15
27
  if (isNodeClass(implementation)) {
16
- usesKey = implementation.name && implementation.name !== "BaseNode" ? implementation.name : `class_${globalThis.crypto.randomUUID()}`;
28
+ usesKey = implementation.name && implementation.name !== "BaseNode" ? implementation.name : `class_${_hashFunction(implementation)}`;
17
29
  this.functionRegistry.set(usesKey, implementation);
18
30
  } else {
19
- usesKey = `fn_${globalThis.crypto.randomUUID()}`;
31
+ usesKey = `fn_${_hashFunction(implementation)}`;
20
32
  this.functionRegistry.set(usesKey, implementation);
21
33
  }
22
34
  const nodeDef = { id, uses: usesKey, ...options };
23
- this.blueprint.nodes.push(nodeDef);
35
+ this.blueprint.nodes?.push(nodeDef);
24
36
  return this;
25
37
  }
26
38
  edge(source, target, options) {
27
39
  const edgeDef = { source, target, ...options };
28
- this.blueprint.edges.push(edgeDef);
40
+ this.blueprint.edges?.push(edgeDef);
29
41
  return this;
30
42
  }
31
43
  /**
@@ -44,26 +56,26 @@ var Flow = class {
44
56
  const gatherId = `${id}_gather`;
45
57
  let workerUsesKey;
46
58
  if (isNodeClass(worker)) {
47
- workerUsesKey = worker.name && worker.name !== "BaseNode" ? worker.name : `class_batch_worker_${globalThis.crypto.randomUUID()}`;
59
+ workerUsesKey = worker.name && worker.name !== "BaseNode" ? worker.name : `class_batch_worker_${_hashFunction(worker)}`;
48
60
  this.functionRegistry.set(workerUsesKey, worker);
49
61
  } else {
50
- workerUsesKey = `fn_batch_worker_${globalThis.crypto.randomUUID()}`;
62
+ workerUsesKey = `fn_batch_worker_${_hashFunction(worker)}`;
51
63
  this.functionRegistry.set(workerUsesKey, worker);
52
64
  }
53
- this.blueprint.nodes.push({
65
+ this.blueprint.nodes?.push({
54
66
  id: scatterId,
55
67
  uses: "batch-scatter",
56
- // This is a special, built-in node type
68
+ // built-in
57
69
  inputs: inputKey,
58
70
  params: { workerUsesKey, outputKey, gatherNodeId: gatherId }
59
71
  });
60
- this.blueprint.nodes.push({
72
+ this.blueprint.nodes?.push({
61
73
  id: gatherId,
62
74
  uses: "batch-gather",
63
- // built-in node type
75
+ // built-in
64
76
  params: { outputKey },
65
77
  config: { joinStrategy: "all" }
66
- // Important: Must wait for all scattered jobs
78
+ // important: must wait for all scattered jobs
67
79
  });
68
80
  this.edge(scatterId, gatherId);
69
81
  return this;
@@ -80,21 +92,21 @@ var Flow = class {
80
92
  const { startNodeId, endNodeId, condition } = options;
81
93
  const controllerId = `${id}-loop`;
82
94
  this.loopControllerIds.set(id, controllerId);
83
- this.blueprint.nodes.push({
95
+ this.loopDefinitions.push({ id, startNodeId, endNodeId });
96
+ this.blueprint.nodes?.push({
84
97
  id: controllerId,
85
98
  uses: "loop-controller",
86
- // Special built-in node type
99
+ // built-in
87
100
  params: { condition },
88
101
  config: { joinStrategy: "any" }
102
+ // to allow re-execution on each loop iteration
89
103
  });
90
104
  this.edge(endNodeId, controllerId);
91
- this.edge(controllerId, startNodeId, { action: "continue", transform: `context.${endNodeId}` });
92
- const startNode = this.blueprint.nodes.find((n) => n.id === startNodeId);
93
- if (startNode)
94
- startNode.config = { ...startNode.config, joinStrategy: "any" };
95
- const endNode = this.blueprint.nodes.find((n) => n.id === endNodeId);
96
- if (endNode)
97
- endNode.config = { ...endNode.config, joinStrategy: "any" };
105
+ this.edge(controllerId, startNodeId, {
106
+ action: "continue",
107
+ transform: `context.${endNodeId}`
108
+ // pass the end node's value to the start node
109
+ });
98
110
  return this;
99
111
  }
100
112
  getLoopControllerId(id) {
@@ -108,6 +120,18 @@ var Flow = class {
108
120
  if (!this.blueprint.nodes || this.blueprint.nodes.length === 0) {
109
121
  throw new Error("Cannot build a blueprint with no nodes.");
110
122
  }
123
+ for (const loopDef of this.loopDefinitions) {
124
+ const startNode = this.blueprint.nodes?.find((n) => n.id === loopDef.startNodeId);
125
+ const endNode = this.blueprint.nodes?.find((n) => n.id === loopDef.endNodeId);
126
+ if (!startNode) {
127
+ throw new Error(`Loop '${loopDef.id}' references non-existent start node '${loopDef.startNodeId}'.`);
128
+ }
129
+ if (!endNode) {
130
+ throw new Error(`Loop '${loopDef.id}' references non-existent end node '${loopDef.endNodeId}'.`);
131
+ }
132
+ startNode.config = { ...startNode.config, joinStrategy: "any" };
133
+ endNode.config = { ...endNode.config, joinStrategy: "any" };
134
+ }
111
135
  return this.blueprint;
112
136
  }
113
137
  getFunctionRegistry() {
@@ -119,5 +143,5 @@ function createFlow(id) {
119
143
  }
120
144
 
121
145
  export { Flow, createFlow };
122
- //# sourceMappingURL=chunk-6DNEDIIT.js.map
123
- //# sourceMappingURL=chunk-6DNEDIIT.js.map
146
+ //# sourceMappingURL=chunk-5ZWYSKMH.js.map
147
+ //# sourceMappingURL=chunk-5ZWYSKMH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/flow.ts"],"names":[],"mappings":"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","file":"chunk-5ZWYSKMH.js","sourcesContent":["import { isNodeClass } from './node'\nimport type { EdgeDefinition, NodeClass, NodeDefinition, NodeFunction, WorkflowBlueprint } from './types'\n\n/**\n * Generates a deterministic hash for a function based on its source code.\n */\nfunction _hashFunction(fn: NodeFunction<any, any, any, any, any> | NodeClass<any, any, any, any, any>): string {\n\tconst source = fn.toString()\n\tlet hash = 0\n\tfor (let i = 0; i < source.length; i++) {\n\t\tconst char = source.charCodeAt(i)\n\t\thash = (hash << 5) - hash + char\n\t\thash = hash & hash // Convert to 32-bit integer\n\t}\n\treturn Math.abs(hash).toString(16)\n}\n\n/**\n * A fluent API for programmatically constructing a WorkflowBlueprint.\n */\nexport class Flow<\n\tTContext extends Record<string, any> = Record<string, any>,\n\tTDependencies extends Record<string, any> = Record<string, any>,\n> {\n\tprivate blueprint: Partial<WorkflowBlueprint>\n\tprivate functionRegistry: Map<string, NodeFunction | NodeClass>\n\tprivate loopControllerIds: Map<string, string>\n\tprivate loopDefinitions: Array<{\n\t\tid: string\n\t\tstartNodeId: string\n\t\tendNodeId: string\n\t}>\n\n\tconstructor(id: string) {\n\t\tthis.blueprint = { id, nodes: [], edges: [] }\n\t\tthis.functionRegistry = new Map()\n\t\tthis.loopControllerIds = new Map()\n\t\tthis.loopDefinitions = []\n\t}\n\n\tnode<TInput = any, TOutput = any, TAction extends string = string>(\n\t\tid: string,\n\t\timplementation:\n\t\t\t| NodeFunction<TContext, TDependencies, TInput, TOutput, TAction>\n\t\t\t| NodeClass<TContext, TDependencies, TInput, TOutput, TAction>,\n\t\toptions?: Omit<NodeDefinition, 'id' | 'uses'>,\n\t): this {\n\t\tlet usesKey: string\n\n\t\tif (isNodeClass(implementation)) {\n\t\t\tusesKey =\n\t\t\t\timplementation.name && implementation.name !== 'BaseNode'\n\t\t\t\t\t? implementation.name\n\t\t\t\t\t: `class_${_hashFunction(implementation)}`\n\t\t\tthis.functionRegistry.set(usesKey, implementation)\n\t\t} else {\n\t\t\tusesKey = `fn_${_hashFunction(implementation)}`\n\t\t\tthis.functionRegistry.set(usesKey, implementation as unknown as NodeFunction)\n\t\t}\n\n\t\tconst nodeDef: NodeDefinition = { id, uses: usesKey, ...options }\n\t\tthis.blueprint.nodes?.push(nodeDef)\n\t\treturn this\n\t}\n\n\tedge(source: string, target: string, options?: Omit<EdgeDefinition, 'source' | 'target'>): this {\n\t\tconst edgeDef: EdgeDefinition = { source, target, ...options }\n\t\tthis.blueprint.edges?.push(edgeDef)\n\t\treturn this\n\t}\n\n\t/**\n\t * Creates a batch processing pattern.\n\t * It takes an input array, runs a worker node on each item in parallel, and gathers the results.\n\t * @param id The base ID for this batch operation.\n\t * @param worker The node implementation to run on each item.\n\t * @param options Configuration for the batch operation.\n\t * @param options.inputKey The key in the context that holds the input array for the batch.\n\t * @param options.outputKey The key in the context where the array of results will be stored.\n\t * @returns The Flow instance for chaining.\n\t */\n\tbatch<TInput = any, TOutput = any, TAction extends string = string>(\n\t\tid: string,\n\t\tworker:\n\t\t\t| NodeFunction<TContext, TDependencies, TInput, TOutput, TAction>\n\t\t\t| NodeClass<TContext, TDependencies, TInput, TOutput, TAction>,\n\t\toptions: {\n\t\t\t/** The key in the context that holds the input array for the batch. */\n\t\t\tinputKey: string\n\t\t\t/** The key in the context where the array of results will be stored. */\n\t\t\toutputKey: string\n\t\t},\n\t): this {\n\t\tconst { inputKey, outputKey } = options\n\t\tconst scatterId = `${id}_scatter`\n\t\tconst gatherId = `${id}_gather`\n\n\t\t// register worker implementation under a unique key.\n\t\tlet workerUsesKey: string\n\t\tif (isNodeClass(worker)) {\n\t\t\tworkerUsesKey =\n\t\t\t\tworker.name && worker.name !== 'BaseNode' ? worker.name : `class_batch_worker_${_hashFunction(worker)}`\n\t\t\tthis.functionRegistry.set(workerUsesKey, worker)\n\t\t} else {\n\t\t\tworkerUsesKey = `fn_batch_worker_${_hashFunction(worker)}`\n\t\t\tthis.functionRegistry.set(workerUsesKey, worker as unknown as NodeFunction)\n\t\t}\n\n\t\t// scatter node: takes an array and dynamically schedules worker nodes\n\t\tthis.blueprint.nodes?.push({\n\t\t\tid: scatterId,\n\t\t\tuses: 'batch-scatter', // built-in\n\t\t\tinputs: inputKey,\n\t\t\tparams: { workerUsesKey, outputKey, gatherNodeId: gatherId },\n\t\t})\n\n\t\t// gather node: waits for all workers to finish and collects the results\n\t\tthis.blueprint.nodes?.push({\n\t\t\tid: gatherId,\n\t\t\tuses: 'batch-gather', // built-in\n\t\t\tparams: { outputKey },\n\t\t\tconfig: { joinStrategy: 'all' }, // important: must wait for all scattered jobs\n\t\t})\n\n\t\t// edge to connect scatter and gather nodes. orchestrator will manage dynamic workers\n\t\tthis.edge(scatterId, gatherId)\n\n\t\treturn this\n\t}\n\n\t/**\n\t * Creates a loop pattern in the workflow graph.\n\t * @param id A unique identifier for the loop construct.\n\t * @param options Defines the start, end, and continuation condition of the loop.\n\t * @param options.startNodeId The ID of the first node inside the loop body.\n\t * @param options.endNodeId The ID of the last node inside the loop body.\n\t * @param options.condition An expression that, if true, causes the loop to run again.\n\t */\n\tloop(\n\t\tid: string,\n\t\toptions: {\n\t\t\t/** The ID of the first node inside the loop body. */\n\t\t\tstartNodeId: string\n\t\t\t/** The ID of the last node inside the loop body. */\n\t\t\tendNodeId: string\n\t\t\t/** An expression that, if true, causes the loop to run again. */\n\t\t\tcondition: string\n\t\t},\n\t): this {\n\t\tconst { startNodeId, endNodeId, condition } = options\n\t\tconst controllerId = `${id}-loop`\n\n\t\tthis.loopControllerIds.set(id, controllerId)\n\n\t\tthis.loopDefinitions.push({ id, startNodeId, endNodeId })\n\n\t\t// controller node: evaluates the loop condition\n\t\tthis.blueprint.nodes?.push({\n\t\t\tid: controllerId,\n\t\t\tuses: 'loop-controller', // built-in\n\t\t\tparams: { condition },\n\t\t\tconfig: { joinStrategy: 'any' }, // to allow re-execution on each loop iteration\n\t\t})\n\n\t\tthis.edge(endNodeId, controllerId)\n\n\t\tthis.edge(controllerId, startNodeId, {\n\t\t\taction: 'continue',\n\t\t\ttransform: `context.${endNodeId}`, // pass the end node's value to the start node\n\t\t})\n\n\t\treturn this\n\t}\n\n\tgetLoopControllerId(id: string): string {\n\t\tconst controllerId = this.loopControllerIds.get(id)\n\t\tif (!controllerId) {\n\t\t\tthrow new Error(`Loop with id '${id}' not found. Ensure you have defined it using the .loop() method.`)\n\t\t}\n\t\treturn controllerId\n\t}\n\n\ttoBlueprint(): WorkflowBlueprint {\n\t\tif (!this.blueprint.nodes || this.blueprint.nodes.length === 0) {\n\t\t\tthrow new Error('Cannot build a blueprint with no nodes.')\n\t\t}\n\n\t\tfor (const loopDef of this.loopDefinitions) {\n\t\t\tconst startNode = this.blueprint.nodes?.find((n) => n.id === loopDef.startNodeId)\n\t\t\tconst endNode = this.blueprint.nodes?.find((n) => n.id === loopDef.endNodeId)\n\n\t\t\tif (!startNode) {\n\t\t\t\tthrow new Error(`Loop '${loopDef.id}' references non-existent start node '${loopDef.startNodeId}'.`)\n\t\t\t}\n\t\t\tif (!endNode) {\n\t\t\t\tthrow new Error(`Loop '${loopDef.id}' references non-existent end node '${loopDef.endNodeId}'.`)\n\t\t\t}\n\n\t\t\tstartNode.config = { ...startNode.config, joinStrategy: 'any' }\n\t\t\tendNode.config = { ...endNode.config, joinStrategy: 'any' }\n\t\t}\n\n\t\treturn this.blueprint as WorkflowBlueprint\n\t}\n\n\tgetFunctionRegistry() {\n\t\treturn this.functionRegistry\n\t}\n}\n\n/**\n * Helper function to create a new Flow builder instance.\n */\nexport function createFlow<\n\tTContext extends Record<string, any> = Record<string, any>,\n\tTDependencies extends Record<string, any> = Record<string, any>,\n>(id: string): Flow<TContext, TDependencies> {\n\treturn new Flow(id)\n}\n"]}
@@ -24,5 +24,5 @@ var FatalNodeExecutionError = class extends NodeExecutionError {
24
24
  };
25
25
 
26
26
  export { CancelledWorkflowError, FatalNodeExecutionError, NodeExecutionError };
27
- //# sourceMappingURL=chunk-WXT3YEWU.js.map
28
- //# sourceMappingURL=chunk-WXT3YEWU.js.map
27
+ //# sourceMappingURL=chunk-5ZXV3R5D.js.map
28
+ //# sourceMappingURL=chunk-5ZXV3R5D.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts"],"names":[],"mappings":";AACO,IAAM,kBAAA,GAAN,cAAiC,KAAA,CAAM;AAAA,EAC7C,WAAA,CACC,OAAA,EACgB,MAAA,EACA,WAAA,EACA,eACA,WAAA,EACf;AACD,IAAA,KAAA,CAAM,OAAO,CAAA;AALG,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AACA,IAAA,IAAA,CAAA,WAAA,GAAA,WAAA;AACA,IAAA,IAAA,CAAA,aAAA,GAAA,aAAA;AACA,IAAA,IAAA,CAAA,WAAA,GAAA,WAAA;AAGhB,IAAA,IAAA,CAAK,IAAA,GAAO,oBAAA;AAAA,EACb;AACD;AAGO,IAAM,sBAAA,GAAN,cAAqC,KAAA,CAAM;AAAA,EACjD,WAAA,CACC,OAAA,GAAU,mCAAA,EACM,WAAA,EACf;AACD,IAAA,KAAA,CAAM,OAAO,CAAA;AAFG,IAAA,IAAA,CAAA,WAAA,GAAA,WAAA;AAGhB,IAAA,IAAA,CAAK,IAAA,GAAO,wBAAA;AAAA,EACb;AACD;AAGO,IAAM,uBAAA,GAAN,cAAsC,kBAAA,CAAmB;AAAA,EAC/D,WAAA,CAAY,OAAA,EAAiB,MAAA,EAAgB,WAAA,EAAqB,eAAuB,WAAA,EAAsB;AAC9G,IAAA,KAAA,CAAM,OAAA,EAAS,MAAA,EAAQ,WAAA,EAAa,aAAA,EAAe,WAAW,CAAA;AAC9D,IAAA,IAAA,CAAK,IAAA,GAAO,yBAAA;AAAA,EACb;AACD","file":"chunk-5ZXV3R5D.js","sourcesContent":["/** Error thrown when a node fails during execution. */\nexport class NodeExecutionError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly nodeId: string,\n\t\tpublic readonly blueprintId: string,\n\t\tpublic readonly originalError?: Error,\n\t\tpublic readonly executionId?: string,\n\t) {\n\t\tsuper(message)\n\t\tthis.name = 'NodeExecutionError'\n\t}\n}\n\n/** Error thrown when a workflow is gracefully aborted. */\nexport class CancelledWorkflowError extends Error {\n\tconstructor(\n\t\tmessage = 'Workflow execution was cancelled.',\n\t\tpublic readonly executionId?: string,\n\t) {\n\t\tsuper(message)\n\t\tthis.name = 'CancelledWorkflowError'\n\t}\n}\n\n/** Error thrown for a non-recoverable failure that should halt the workflow immediately. */\nexport class FatalNodeExecutionError extends NodeExecutionError {\n\tconstructor(message: string, nodeId: string, blueprintId: string, originalError?: Error, executionId?: string) {\n\t\tsuper(message, nodeId, blueprintId, originalError, executionId)\n\t\tthis.name = 'FatalNodeExecutionError'\n\t}\n}\n"]}