flowcraft 2.9.2 → 2.10.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.
- package/README.md +1 -1
- package/dist/{index-DUPpyNvU.d.mts → index-BXRN44Qf.d.mts} +22 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +119 -74
- package/dist/index.mjs.map +1 -1
- package/dist/{runtime-CRwlRW2p.mjs → runtime-ChsWirQN.mjs} +46 -11
- package/dist/runtime-ChsWirQN.mjs.map +1 -0
- package/dist/testing/index.d.mts +1 -1
- package/dist/testing/index.mjs +1 -1
- package/package.json +3 -3
- package/dist/runtime-CRwlRW2p.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -98,7 +98,7 @@ The `FlowRuntime` can be configured with pluggable components to tailor its beha
|
|
|
98
98
|
|
|
99
99
|
## Distributed Execution
|
|
100
100
|
|
|
101
|
-
Flowcraft's architecture is designed for progressive scalability. The `BaseDistributedAdapter` provides a foundation for running workflows across multiple machines. Flowcraft provides official adapters for [BullMQ](https://www.npmjs.com/package/@flowcraft/bullmq-adapter), [AWS](https://www.npmjs.com/package/@flowcraft/sqs-adapter), [GCP](https://www.npmjs.com/package/@flowcraft/gcp-adapter), [Azure](https://www.npmjs.com/package/@flowcraft/azure-adapter), [RabbitMQ](https://www.npmjs.com/package/@flowcraft/rabbitmq-adapter), [Kafka](https://www.npmjs.com/package/@flowcraft/kafka-adapter), and [Cloudflare](https://www.npmjs.com/package/@flowcraft/cloudflare-adapter).
|
|
101
|
+
Flowcraft's architecture is designed for progressive scalability. The `BaseDistributedAdapter` provides a foundation for running workflows across multiple machines. Flowcraft provides official adapters for [BullMQ](https://www.npmjs.com/package/@flowcraft/bullmq-adapter), [AWS](https://www.npmjs.com/package/@flowcraft/sqs-adapter), [GCP](https://www.npmjs.com/package/@flowcraft/gcp-adapter), [Azure](https://www.npmjs.com/package/@flowcraft/azure-adapter), [RabbitMQ](https://www.npmjs.com/package/@flowcraft/rabbitmq-adapter), [Kafka](https://www.npmjs.com/package/@flowcraft/kafka-adapter), [Vercel](https://www.npmjs.com/package/@flowcraft/vercel-adapter), and [Cloudflare](https://www.npmjs.com/package/@flowcraft/cloudflare-adapter).
|
|
102
102
|
|
|
103
103
|
## Documentation
|
|
104
104
|
|
|
@@ -125,6 +125,7 @@ declare class WorkflowState<TContext extends Record<string, any>> {
|
|
|
125
125
|
private _isAwaiting;
|
|
126
126
|
private _awaitingNodeIds;
|
|
127
127
|
private _awaitingDetails;
|
|
128
|
+
isLastAttempt?: boolean;
|
|
128
129
|
constructor(initialData: Partial<TContext>, context?: IAsyncContext<TContext>);
|
|
129
130
|
/**
|
|
130
131
|
* Configure the context to emit events when modified.
|
|
@@ -1141,6 +1142,10 @@ interface JobPayload {
|
|
|
1141
1142
|
runId: string;
|
|
1142
1143
|
blueprintId: string;
|
|
1143
1144
|
nodeId: string;
|
|
1145
|
+
/** Used to suppress intermediate failures and fallback triggers when delegating to Queue retries */
|
|
1146
|
+
isLastAttempt?: boolean;
|
|
1147
|
+
/** Optional attempt tracking metadata */
|
|
1148
|
+
attempt?: number;
|
|
1144
1149
|
}
|
|
1145
1150
|
/**
|
|
1146
1151
|
* The base class for all distributed adapters. It handles the technology-agnostic
|
|
@@ -1157,6 +1162,17 @@ declare abstract class BaseDistributedAdapter {
|
|
|
1157
1162
|
* Starts the worker, which begins listening for and processing jobs from the queue.
|
|
1158
1163
|
*/
|
|
1159
1164
|
start(): void;
|
|
1165
|
+
/**
|
|
1166
|
+
* Hook called by the execution factory to determine if a node's automatic
|
|
1167
|
+
* retries should be executed synchronously in-process (true) or delegated
|
|
1168
|
+
* to the Queue backoff behavior configured by the adapter (false).
|
|
1169
|
+
*/
|
|
1170
|
+
protected shouldRetryInProcess(_nodeDef: NodeDefinition): boolean;
|
|
1171
|
+
/**
|
|
1172
|
+
* Returns queue-level retry options for enqueuing successor jobs.
|
|
1173
|
+
* Only used when shouldRetryInProcess() returns false.
|
|
1174
|
+
*/
|
|
1175
|
+
protected getQueueRetryOptions(_nodeDef: NodeDefinition): Record<string, any> | undefined;
|
|
1160
1176
|
/**
|
|
1161
1177
|
* Creates a technology-specific distributed context for a given workflow run.
|
|
1162
1178
|
* @param runId The unique ID for the workflow execution.
|
|
@@ -1202,6 +1218,11 @@ declare abstract class BaseDistributedAdapter {
|
|
|
1202
1218
|
* The main handler for processing a single job from the queue.
|
|
1203
1219
|
*/
|
|
1204
1220
|
protected handleJob(job: JobPayload): Promise<void>;
|
|
1221
|
+
/**
|
|
1222
|
+
* Handles post-execution logic: terminal node checks, successor determination,
|
|
1223
|
+
* and enqueueing of ready nodes. Extracted to support the idempotency guard.
|
|
1224
|
+
*/
|
|
1225
|
+
private handleNodeCompletion;
|
|
1205
1226
|
/**
|
|
1206
1227
|
* Encapsulates the fan-in join logic using the coordination store.
|
|
1207
1228
|
*/
|
|
@@ -1323,4 +1344,4 @@ declare class JsonSerializer implements ISerializer {
|
|
|
1323
1344
|
}
|
|
1324
1345
|
//#endregion
|
|
1325
1346
|
export { NodeConfig as $, TrackedAsyncContext as A, DIContainer as At, InMemoryEventStore as B, FlowBuilder as C, ClassNodeExecutor as Ct, createErrorMapper as D, NodeExecutor as Dt, UnsafeEvaluator as E, NodeExecutionResult as Et, analyzeBlueprint as F, FlowcraftError as Ft, IAsyncContext as G, ContextImplementation as H, checkForCycles as I, ILogger as J, IEvaluator as K, generateMermaid as L, createDefaultContainer as M, ServiceTokens as Mt, BlueprintAnalysis as N, BaseNode as Nt, AsyncContextView as O, NodeExecutorConfig as Ot, Cycles as P, isNodeClass as Pt, NodeClass as Q, generateMermaidForRun as R, lintBlueprint as S, ReadyNode as St, PropertyEvaluator as T, FunctionNodeExecutor as Tt, EdgeDefinition as U, PersistentEventBusAdapter as V, FlowcraftEvent as W, ISyncContext as X, ISerializer as Y, Middleware as Z, ConsoleLogger as _, ExecutionServices as _t, ReplayOrchestrator as a, NodeResult as at, LinterIssueCode as b, NodeExecutorFactory as bt, BaseDistributedAdapter as c, RuntimeOptions as ct, WebhookNode as d, WorkflowBlueprint as dt, NodeContext as et, WaitNode as f, WorkflowBlueprintMetadata as ft, BatchGatherNode as g, FlowRuntime as gt, BatchScatterNode as h, WorkflowStatus as ht, processResults as i, NodeRegistry as it, ContainerOptions as j, ServiceToken as jt, Context as k, WorkflowState as kt, ICoordinationStore as l, SourceLocation as lt, SleepNode as m, WorkflowResult as mt, sanitizeBlueprint as n, NodeFunction as nt, DefaultOrchestrator as o, PatchOperation as ot, SubflowNode as p, WorkflowError as pt, IEventBus as q, executeBatch as r, NodeImplementation as rt, AdapterOptions as s, RuntimeDependencies as st, JsonSerializer as t, NodeDefinition as tt, JobPayload as u, UIGraph as ut, NullLogger as v, IOrchestrator as vt, createFlow as w, ExecutionStrategy as wt, LinterResult as x, GraphTraverser as xt, LinterIssue as y, IRuntime as yt, IEventStore as z };
|
|
1326
|
-
//# sourceMappingURL=index-
|
|
1347
|
+
//# sourceMappingURL=index-BXRN44Qf.d.mts.map
|
package/dist/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { $ as NodeConfig, A as TrackedAsyncContext, At as DIContainer, B as InMemoryEventStore, C as FlowBuilder, Ct as ClassNodeExecutor, D as createErrorMapper, Dt as NodeExecutor, E as UnsafeEvaluator, Et as NodeExecutionResult, F as analyzeBlueprint, Ft as FlowcraftError, G as IAsyncContext, H as ContextImplementation, I as checkForCycles, J as ILogger, K as IEvaluator, L as generateMermaid, M as createDefaultContainer, Mt as ServiceTokens, N as BlueprintAnalysis, Nt as BaseNode, O as AsyncContextView, Ot as NodeExecutorConfig, P as Cycles, Pt as isNodeClass, Q as NodeClass, R as generateMermaidForRun, S as lintBlueprint, St as ReadyNode, T as PropertyEvaluator, Tt as FunctionNodeExecutor, U as EdgeDefinition, V as PersistentEventBusAdapter, W as FlowcraftEvent, X as ISyncContext, Y as ISerializer, Z as Middleware, _ as ConsoleLogger, _t as ExecutionServices, a as ReplayOrchestrator, at as NodeResult, b as LinterIssueCode, bt as NodeExecutorFactory, c as BaseDistributedAdapter, ct as RuntimeOptions, d as WebhookNode, dt as WorkflowBlueprint, et as NodeContext, f as WaitNode, ft as WorkflowBlueprintMetadata, g as BatchGatherNode, gt as FlowRuntime, h as BatchScatterNode, ht as WorkflowStatus, i as processResults, it as NodeRegistry, j as ContainerOptions, jt as ServiceToken, k as Context, kt as WorkflowState, l as ICoordinationStore, lt as SourceLocation, m as SleepNode, mt as WorkflowResult, n as sanitizeBlueprint, nt as NodeFunction, o as DefaultOrchestrator, ot as PatchOperation, p as SubflowNode, pt as WorkflowError, q as IEventBus, r as executeBatch, rt as NodeImplementation, s as AdapterOptions, st as RuntimeDependencies, t as JsonSerializer, tt as NodeDefinition, u as JobPayload, ut as UIGraph, v as NullLogger, vt as IOrchestrator, w as createFlow, wt as ExecutionStrategy, x as LinterResult, xt as GraphTraverser, y as LinterIssue, yt as IRuntime, z as IEventStore } from "./index-
|
|
1
|
+
import { $ as NodeConfig, A as TrackedAsyncContext, At as DIContainer, B as InMemoryEventStore, C as FlowBuilder, Ct as ClassNodeExecutor, D as createErrorMapper, Dt as NodeExecutor, E as UnsafeEvaluator, Et as NodeExecutionResult, F as analyzeBlueprint, Ft as FlowcraftError, G as IAsyncContext, H as ContextImplementation, I as checkForCycles, J as ILogger, K as IEvaluator, L as generateMermaid, M as createDefaultContainer, Mt as ServiceTokens, N as BlueprintAnalysis, Nt as BaseNode, O as AsyncContextView, Ot as NodeExecutorConfig, P as Cycles, Pt as isNodeClass, Q as NodeClass, R as generateMermaidForRun, S as lintBlueprint, St as ReadyNode, T as PropertyEvaluator, Tt as FunctionNodeExecutor, U as EdgeDefinition, V as PersistentEventBusAdapter, W as FlowcraftEvent, X as ISyncContext, Y as ISerializer, Z as Middleware, _ as ConsoleLogger, _t as ExecutionServices, a as ReplayOrchestrator, at as NodeResult, b as LinterIssueCode, bt as NodeExecutorFactory, c as BaseDistributedAdapter, ct as RuntimeOptions, d as WebhookNode, dt as WorkflowBlueprint, et as NodeContext, f as WaitNode, ft as WorkflowBlueprintMetadata, g as BatchGatherNode, gt as FlowRuntime, h as BatchScatterNode, ht as WorkflowStatus, i as processResults, it as NodeRegistry, j as ContainerOptions, jt as ServiceToken, k as Context, kt as WorkflowState, l as ICoordinationStore, lt as SourceLocation, m as SleepNode, mt as WorkflowResult, n as sanitizeBlueprint, nt as NodeFunction, o as DefaultOrchestrator, ot as PatchOperation, p as SubflowNode, pt as WorkflowError, q as IEventBus, r as executeBatch, rt as NodeImplementation, s as AdapterOptions, st as RuntimeDependencies, t as JsonSerializer, tt as NodeDefinition, u as JobPayload, ut as UIGraph, v as NullLogger, vt as IOrchestrator, w as createFlow, wt as ExecutionStrategy, x as LinterResult, xt as GraphTraverser, y as LinterIssue, yt as IRuntime, z as IEventStore } from "./index-BXRN44Qf.mjs";
|
|
2
2
|
export { AdapterOptions, AsyncContextView, BaseDistributedAdapter, BaseNode, BatchGatherNode, BatchScatterNode, BlueprintAnalysis, ClassNodeExecutor, ConsoleLogger, ContainerOptions, Context, ContextImplementation, Cycles, DIContainer, DefaultOrchestrator, EdgeDefinition, ExecutionServices, ExecutionStrategy, FlowBuilder, FlowRuntime, FlowcraftError, FlowcraftEvent, FunctionNodeExecutor, GraphTraverser, IAsyncContext, ICoordinationStore, IEvaluator, IEventBus, IEventStore, ILogger, IOrchestrator, IRuntime, ISerializer, ISyncContext, InMemoryEventStore, JobPayload, JsonSerializer, LinterIssue, LinterIssueCode, LinterResult, Middleware, NodeClass, NodeConfig, NodeContext, NodeDefinition, NodeExecutionResult, NodeExecutor, NodeExecutorConfig, NodeExecutorFactory, NodeFunction, NodeImplementation, NodeRegistry, NodeResult, NullLogger, PatchOperation, PersistentEventBusAdapter, PropertyEvaluator, ReadyNode, ReplayOrchestrator, RuntimeDependencies, RuntimeOptions, ServiceToken, ServiceTokens, SleepNode, SourceLocation, SubflowNode, TrackedAsyncContext, UIGraph, UnsafeEvaluator, WaitNode, WebhookNode, WorkflowBlueprint, WorkflowBlueprintMetadata, WorkflowError, WorkflowResult, WorkflowState, WorkflowStatus, analyzeBlueprint, checkForCycles, createDefaultContainer, createErrorMapper, createFlow, executeBatch, generateMermaid, generateMermaidForRun, isNodeClass, lintBlueprint, processResults, sanitizeBlueprint };
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as ReplayOrchestrator } from "./replay-BB11M6K1.mjs";
|
|
2
|
-
import { A as ServiceTokens, C as TrackedAsyncContext, D as PropertyEvaluator, E as NullLogger, F as InMemoryEventStore, I as PersistentEventBusAdapter, M as checkForCycles, N as generateMermaid, O as UnsafeEvaluator, P as generateMermaidForRun, S as Context, T as ConsoleLogger, _ as executeBatch, a as sanitizeBlueprint, b as WorkflowState, c as SubflowNode, d as BatchScatterNode, f as BatchGatherNode, g as DefaultOrchestrator, h as JsonSerializer, i as NodeExecutor, j as analyzeBlueprint, k as DIContainer, l as GraphTraverser, m as isNodeClass, n as ClassNodeExecutor, o as WebhookNode, p as BaseNode, r as FunctionNodeExecutor, s as WaitNode, t as FlowRuntime, u as SleepNode, v as processResults, w as FlowcraftError, x as AsyncContextView } from "./runtime-
|
|
2
|
+
import { A as ServiceTokens, C as TrackedAsyncContext, D as PropertyEvaluator, E as NullLogger, F as InMemoryEventStore, I as PersistentEventBusAdapter, M as checkForCycles, N as generateMermaid, O as UnsafeEvaluator, P as generateMermaidForRun, S as Context, T as ConsoleLogger, _ as executeBatch, a as sanitizeBlueprint, b as WorkflowState, c as SubflowNode, d as BatchScatterNode, f as BatchGatherNode, g as DefaultOrchestrator, h as JsonSerializer, i as NodeExecutor, j as analyzeBlueprint, k as DIContainer, l as GraphTraverser, m as isNodeClass, n as ClassNodeExecutor, o as WebhookNode, p as BaseNode, r as FunctionNodeExecutor, s as WaitNode, t as FlowRuntime, u as SleepNode, v as processResults, w as FlowcraftError, x as AsyncContextView } from "./runtime-ChsWirQN.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/container-factory.ts
|
|
5
5
|
function createDefaultContainer(options = {}) {
|
|
@@ -215,7 +215,7 @@ var FlowBuilder = class {
|
|
|
215
215
|
this.edge(endNodeId, id);
|
|
216
216
|
this.edge(id, startNodeId, {
|
|
217
217
|
action: "continue",
|
|
218
|
-
transform: `context
|
|
218
|
+
transform: `context["${endNodeId}"]`
|
|
219
219
|
});
|
|
220
220
|
return this;
|
|
221
221
|
}
|
|
@@ -239,7 +239,7 @@ var FlowBuilder = class {
|
|
|
239
239
|
finalEdges.push({
|
|
240
240
|
...edge,
|
|
241
241
|
action: edge.action || "break",
|
|
242
|
-
transform: `context
|
|
242
|
+
transform: `context["${loopDef.endNodeId}"]`
|
|
243
243
|
});
|
|
244
244
|
processedOriginalEdges.add(edge);
|
|
245
245
|
}
|
|
@@ -324,7 +324,7 @@ var FlowBuilder = class {
|
|
|
324
324
|
const incomingEdges = blueprint.edges.filter((edge) => edge.target === id && edge.source !== loopDef.endNodeId);
|
|
325
325
|
for (const incomingEdge of incomingEdges) uiEdges.push({
|
|
326
326
|
...incomingEdge,
|
|
327
|
-
|
|
327
|
+
target: loopDef.startNodeId
|
|
328
328
|
});
|
|
329
329
|
}
|
|
330
330
|
const scatterNodes = blueprint.nodes.filter((n) => n.uses === "batch-scatter");
|
|
@@ -480,6 +480,19 @@ var BaseDistributedAdapter = class {
|
|
|
480
480
|
this.processJobs(this.handleJob.bind(this));
|
|
481
481
|
}
|
|
482
482
|
/**
|
|
483
|
+
* Hook called by the execution factory to determine if a node's automatic
|
|
484
|
+
* retries should be executed synchronously in-process (true) or delegated
|
|
485
|
+
* to the Queue backoff behavior configured by the adapter (false).
|
|
486
|
+
*/
|
|
487
|
+
shouldRetryInProcess(_nodeDef) {
|
|
488
|
+
return true;
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Returns queue-level retry options for enqueuing successor jobs.
|
|
492
|
+
* Only used when shouldRetryInProcess() returns false.
|
|
493
|
+
*/
|
|
494
|
+
getQueueRetryOptions(_nodeDef) {}
|
|
495
|
+
/**
|
|
483
496
|
* Hook called at the start of job processing. Subclasses can override this
|
|
484
497
|
* to perform additional setup (e.g., timestamp tracking for reconciliation).
|
|
485
498
|
*/
|
|
@@ -526,82 +539,40 @@ var BaseDistributedAdapter = class {
|
|
|
526
539
|
}, 18e5);
|
|
527
540
|
try {
|
|
528
541
|
const state = new WorkflowState(await context.toJSON(), context);
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
542
|
+
state.isLastAttempt = job.isLastAttempt !== false;
|
|
543
|
+
let result;
|
|
544
|
+
if (await context.has(`_outputs.${nodeId}`)) {
|
|
545
|
+
this.logger.info(`[Adapter] Node '${nodeId}' already completed, skipping re-execution.`);
|
|
546
|
+
result = { output: await context.get(`_outputs.${nodeId}`) };
|
|
547
|
+
} else {
|
|
548
|
+
const nodeDef = blueprint.nodes.find((n) => n.id === nodeId);
|
|
549
|
+
if (nodeDef && !this.shouldRetryInProcess(nodeDef)) {
|
|
550
|
+
if (nodeDef.config?.maxRetries && nodeDef.config.maxRetries > 1) nodeDef.config.maxRetries = 1;
|
|
537
551
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
const finalContext = await context.toJSON();
|
|
548
|
-
const finalResult = {
|
|
549
|
-
context: finalContext,
|
|
550
|
-
serializedContext: this.serializer.serialize(finalContext),
|
|
551
|
-
status: "completed"
|
|
552
|
-
};
|
|
553
|
-
await this.publishFinalResult(runId, {
|
|
554
|
-
status: "completed",
|
|
555
|
-
payload: finalResult
|
|
556
|
-
});
|
|
557
|
-
clearInterval(heartbeatInterval);
|
|
558
|
-
return;
|
|
559
|
-
} else this.logger.info(`[Adapter] Terminal node '${nodeId}' completed for Run ID '${runId}', but other terminal nodes are still running.`);
|
|
560
|
-
}
|
|
561
|
-
const nextNodes = await this.runtime.determineNextNodes(blueprint, nodeId, result, context, runId);
|
|
562
|
-
if (nextNodes.length === 0 && !isTerminalNode) {
|
|
563
|
-
this.logger.info(`[Adapter] Non-terminal node '${nodeId}' reached end of branch for Run ID '${runId}'. This branch will now terminate.`);
|
|
564
|
-
clearInterval(heartbeatInterval);
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
for (const { node: nextNodeDef, edge } of nextNodes) {
|
|
568
|
-
await this.runtime.applyEdgeTransform(edge, result, nextNodeDef, context, void 0, runId);
|
|
569
|
-
if (await this.isReadyForFanIn(runId, blueprint, nextNodeDef.id)) {
|
|
570
|
-
this.logger.info(`[Adapter] Node '${nextNodeDef.id}' is ready. Enqueuing job.`);
|
|
571
|
-
await this.enqueueJob({
|
|
572
|
-
runId,
|
|
573
|
-
blueprintId,
|
|
574
|
-
nodeId: nextNodeDef.id
|
|
575
|
-
});
|
|
576
|
-
if (this.eventBus) await this.eventBus.emit({
|
|
577
|
-
type: "job:enqueued",
|
|
578
|
-
payload: {
|
|
579
|
-
runId,
|
|
580
|
-
blueprintId,
|
|
581
|
-
nodeId: nextNodeDef.id
|
|
582
|
-
}
|
|
583
|
-
});
|
|
584
|
-
} else this.logger.info(`[Adapter] Node '${nextNodeDef.id}' is waiting for other predecessors to complete.`);
|
|
585
|
-
}
|
|
586
|
-
const duration = Date.now() - startTime;
|
|
587
|
-
if (this.eventBus) await this.eventBus.emit({
|
|
588
|
-
type: "job:processed",
|
|
589
|
-
payload: {
|
|
590
|
-
runId,
|
|
591
|
-
blueprintId,
|
|
592
|
-
nodeId,
|
|
593
|
-
duration,
|
|
594
|
-
success: true
|
|
552
|
+
result = await this.runtime.executeNode(blueprint, nodeId, state);
|
|
553
|
+
await context.set(`_outputs.${nodeId}`, result.output);
|
|
554
|
+
const stateContext = state.getContext();
|
|
555
|
+
if (stateContext instanceof TrackedAsyncContext) {
|
|
556
|
+
const deltas = stateContext.getDeltas();
|
|
557
|
+
if (deltas.length > 0) {
|
|
558
|
+
await stateContext.patch(deltas);
|
|
559
|
+
stateContext.clearDeltas();
|
|
560
|
+
}
|
|
595
561
|
}
|
|
562
|
+
}
|
|
563
|
+
await this.handleNodeCompletion({
|
|
564
|
+
runId,
|
|
565
|
+
blueprintId,
|
|
566
|
+
nodeId,
|
|
567
|
+
blueprint,
|
|
568
|
+
result,
|
|
569
|
+
context,
|
|
570
|
+
startTime,
|
|
571
|
+
heartbeatInterval
|
|
596
572
|
});
|
|
597
573
|
} catch (error) {
|
|
598
574
|
const reason = error.message || "Unknown execution error";
|
|
599
575
|
this.logger.error(`[Adapter] FATAL: Job for node '${nodeId}' failed for Run ID '${runId}': ${reason}`);
|
|
600
|
-
await this.publishFinalResult(runId, {
|
|
601
|
-
status: "failed",
|
|
602
|
-
reason
|
|
603
|
-
});
|
|
604
|
-
await this.writePoisonPillForSuccessors(runId, blueprint, nodeId);
|
|
605
576
|
if (this.eventBus) await this.eventBus.emit({
|
|
606
577
|
type: "job:failed",
|
|
607
578
|
payload: {
|
|
@@ -611,11 +582,85 @@ var BaseDistributedAdapter = class {
|
|
|
611
582
|
error
|
|
612
583
|
}
|
|
613
584
|
});
|
|
585
|
+
if (job.isLastAttempt === false) {
|
|
586
|
+
this.logger.warn(`[Adapter] Job for node '${nodeId}' failed (queue attempt ${job.attempt || 1}), delegating retry to queue.`);
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
await this.publishFinalResult(runId, {
|
|
590
|
+
status: "failed",
|
|
591
|
+
reason
|
|
592
|
+
});
|
|
593
|
+
await this.writePoisonPillForSuccessors(runId, blueprint, nodeId);
|
|
614
594
|
} finally {
|
|
615
595
|
clearInterval(heartbeatInterval);
|
|
616
596
|
}
|
|
617
597
|
}
|
|
618
598
|
/**
|
|
599
|
+
* Handles post-execution logic: terminal node checks, successor determination,
|
|
600
|
+
* and enqueueing of ready nodes. Extracted to support the idempotency guard.
|
|
601
|
+
*/
|
|
602
|
+
async handleNodeCompletion(params) {
|
|
603
|
+
const { runId, blueprintId, nodeId, blueprint, result, context, startTime, heartbeatInterval } = params;
|
|
604
|
+
const analysis = analyzeBlueprint(blueprint);
|
|
605
|
+
const isTerminalNode = analysis.terminalNodeIds.includes(nodeId);
|
|
606
|
+
if (isTerminalNode) {
|
|
607
|
+
const allContextKeys = Object.keys(await context.toJSON());
|
|
608
|
+
const completedNodes = /* @__PURE__ */ new Set();
|
|
609
|
+
for (const key of allContextKeys) if (key.startsWith("_outputs.")) completedNodes.add(key.substring(9));
|
|
610
|
+
if (analysis.terminalNodeIds.every((terminalId) => completedNodes.has(terminalId))) {
|
|
611
|
+
this.logger.info(`[Adapter] All terminal nodes completed for Run ID: ${runId}. Declaring workflow complete.`);
|
|
612
|
+
const finalContext = await context.toJSON();
|
|
613
|
+
const finalResult = {
|
|
614
|
+
context: finalContext,
|
|
615
|
+
serializedContext: this.serializer.serialize(finalContext),
|
|
616
|
+
status: "completed"
|
|
617
|
+
};
|
|
618
|
+
await this.publishFinalResult(runId, {
|
|
619
|
+
status: "completed",
|
|
620
|
+
payload: finalResult
|
|
621
|
+
});
|
|
622
|
+
clearInterval(heartbeatInterval);
|
|
623
|
+
return;
|
|
624
|
+
} else this.logger.info(`[Adapter] Terminal node '${nodeId}' completed for Run ID '${runId}', but other terminal nodes are still running.`);
|
|
625
|
+
}
|
|
626
|
+
const nextNodes = await this.runtime.determineNextNodes(blueprint, nodeId, result, context, runId);
|
|
627
|
+
if (nextNodes.length === 0 && !isTerminalNode) {
|
|
628
|
+
this.logger.info(`[Adapter] Non-terminal node '${nodeId}' reached end of branch for Run ID '${runId}'. This branch will now terminate.`);
|
|
629
|
+
clearInterval(heartbeatInterval);
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
for (const { node: nextNodeDef, edge } of nextNodes) {
|
|
633
|
+
await this.runtime.applyEdgeTransform(edge, result, nextNodeDef, context, void 0, runId);
|
|
634
|
+
if (await this.isReadyForFanIn(runId, blueprint, nextNodeDef.id)) {
|
|
635
|
+
this.logger.info(`[Adapter] Node '${nextNodeDef.id}' is ready. Enqueuing job.`);
|
|
636
|
+
await this.enqueueJob({
|
|
637
|
+
runId,
|
|
638
|
+
blueprintId,
|
|
639
|
+
nodeId: nextNodeDef.id
|
|
640
|
+
});
|
|
641
|
+
if (this.eventBus) await this.eventBus.emit({
|
|
642
|
+
type: "job:enqueued",
|
|
643
|
+
payload: {
|
|
644
|
+
runId,
|
|
645
|
+
blueprintId,
|
|
646
|
+
nodeId: nextNodeDef.id
|
|
647
|
+
}
|
|
648
|
+
});
|
|
649
|
+
} else this.logger.info(`[Adapter] Node '${nextNodeDef.id}' is waiting for other predecessors to complete.`);
|
|
650
|
+
}
|
|
651
|
+
const duration = Date.now() - startTime;
|
|
652
|
+
if (this.eventBus) await this.eventBus.emit({
|
|
653
|
+
type: "job:processed",
|
|
654
|
+
payload: {
|
|
655
|
+
runId,
|
|
656
|
+
blueprintId,
|
|
657
|
+
nodeId,
|
|
658
|
+
duration,
|
|
659
|
+
success: true
|
|
660
|
+
}
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
619
664
|
* Encapsulates the fan-in join logic using the coordination store.
|
|
620
665
|
*/
|
|
621
666
|
async isReadyForFanIn(runId, blueprint, targetNodeId) {
|