ai-workflows 2.1.3 → 2.4.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +14 -1
- package/README.md +2 -0
- package/dist/barrier.d.ts +6 -0
- package/dist/barrier.d.ts.map +1 -1
- package/dist/barrier.js +45 -7
- package/dist/barrier.js.map +1 -1
- package/dist/cascade-context.d.ts.map +1 -1
- package/dist/cascade-context.js +25 -25
- package/dist/cascade-context.js.map +1 -1
- package/dist/cascade-executor.d.ts.map +1 -1
- package/dist/cascade-executor.js +1 -1
- package/dist/cascade-executor.js.map +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +23 -7
- package/dist/context.js.map +1 -1
- package/dist/cron-parser.d.ts +65 -0
- package/dist/cron-parser.d.ts.map +1 -0
- package/dist/cron-parser.js +294 -0
- package/dist/cron-parser.js.map +1 -0
- package/dist/cron-scheduler.d.ts +117 -0
- package/dist/cron-scheduler.d.ts.map +1 -0
- package/dist/cron-scheduler.js +176 -0
- package/dist/cron-scheduler.js.map +1 -0
- package/dist/database-context.d.ts +184 -0
- package/dist/database-context.d.ts.map +1 -0
- package/dist/database-context.js +428 -0
- package/dist/database-context.js.map +1 -0
- package/dist/digital-objects-adapter.d.ts +159 -0
- package/dist/digital-objects-adapter.d.ts.map +1 -0
- package/dist/digital-objects-adapter.js +229 -0
- package/dist/digital-objects-adapter.js.map +1 -0
- package/dist/durable-execution-cloudflare.d.ts +427 -0
- package/dist/durable-execution-cloudflare.d.ts.map +1 -0
- package/dist/durable-execution-cloudflare.js +510 -0
- package/dist/durable-execution-cloudflare.js.map +1 -0
- package/dist/durable-execution.d.ts +482 -0
- package/dist/durable-execution.d.ts.map +1 -0
- package/dist/durable-execution.js +594 -0
- package/dist/durable-execution.js.map +1 -0
- package/dist/durable-workflow.d.ts +176 -0
- package/dist/durable-workflow.d.ts.map +1 -0
- package/dist/durable-workflow.js +552 -0
- package/dist/durable-workflow.js.map +1 -0
- package/dist/graph/topological-sort.d.ts.map +1 -1
- package/dist/graph/topological-sort.js +5 -5
- package/dist/graph/topological-sort.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +101 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +115 -0
- package/dist/logger.js.map +1 -0
- package/dist/on.d.ts.map +1 -1
- package/dist/on.js +3 -3
- package/dist/on.js.map +1 -1
- package/dist/runtime.d.ts +169 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +275 -0
- package/dist/runtime.js.map +1 -0
- package/dist/send.d.ts.map +1 -1
- package/dist/send.js +4 -3
- package/dist/send.js.map +1 -1
- package/dist/telemetry.d.ts +150 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +388 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/timer-registry.d.ts +25 -0
- package/dist/timer-registry.d.ts.map +1 -1
- package/dist/timer-registry.js +42 -8
- package/dist/timer-registry.js.map +1 -1
- package/dist/types.d.ts +17 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/dist/worker/durable-step.d.ts +481 -0
- package/dist/worker/durable-step.d.ts.map +1 -0
- package/dist/worker/durable-step.js +606 -0
- package/dist/worker/durable-step.js.map +1 -0
- package/dist/worker/index.d.ts +106 -0
- package/dist/worker/index.d.ts.map +1 -0
- package/dist/worker/index.js +124 -0
- package/dist/worker/index.js.map +1 -0
- package/dist/worker/state-adapter.d.ts +230 -0
- package/dist/worker/state-adapter.d.ts.map +1 -0
- package/dist/worker/state-adapter.js +409 -0
- package/dist/worker/state-adapter.js.map +1 -0
- package/dist/worker/topological-executor.d.ts +282 -0
- package/dist/worker/topological-executor.d.ts.map +1 -0
- package/dist/worker/topological-executor.js +396 -0
- package/dist/worker/topological-executor.js.map +1 -0
- package/dist/worker/workflow-builder.d.ts +286 -0
- package/dist/worker/workflow-builder.d.ts.map +1 -0
- package/dist/worker/workflow-builder.js +565 -0
- package/dist/worker/workflow-builder.js.map +1 -0
- package/dist/worker.d.ts +800 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +2428 -0
- package/dist/worker.js.map +1 -0
- package/dist/workflow-builder.d.ts +287 -0
- package/dist/workflow-builder.d.ts.map +1 -0
- package/dist/workflow-builder.js +762 -0
- package/dist/workflow-builder.js.map +1 -0
- package/dist/workflow.d.ts +14 -30
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.js +132 -292
- package/dist/workflow.js.map +1 -1
- package/examples/01-ecommerce-order-pipeline.ts +358 -0
- package/examples/02-content-moderation-cascade.ts +454 -0
- package/examples/03-scheduled-reporting-dependencies.ts +479 -0
- package/examples/04-database-persistence.ts +518 -0
- package/examples/README.md +173 -0
- package/package.json +30 -13
- package/src/__tests__/digital-objects-adapter.test.ts +274 -0
- package/src/__tests__/durable-workflow.test.ts +297 -0
- package/src/barrier.ts +48 -7
- package/src/cascade-context.ts +36 -29
- package/src/cascade-executor.ts +3 -2
- package/src/context.ts +41 -12
- package/src/cron-parser.ts +347 -0
- package/src/cron-scheduler.ts +239 -0
- package/src/database-context.ts +658 -0
- package/src/digital-objects-adapter.ts +351 -0
- package/src/durable-execution-cloudflare.ts +855 -0
- package/src/durable-execution.ts +1042 -0
- package/src/durable-workflow.ts +717 -0
- package/src/graph/topological-sort.ts +6 -8
- package/src/index.ts +69 -0
- package/src/logger.ts +148 -0
- package/src/on.ts +8 -9
- package/src/runtime.ts +436 -0
- package/src/send.ts +4 -5
- package/src/telemetry.ts +577 -0
- package/src/timer-registry.ts +44 -10
- package/src/types.ts +32 -17
- package/src/worker/durable-step.ts +976 -0
- package/src/worker/index.ts +216 -0
- package/src/worker/state-adapter.ts +589 -0
- package/src/worker/topological-executor.ts +625 -0
- package/src/worker/workflow-builder.ts +871 -0
- package/src/worker.ts +2906 -0
- package/src/workflow-builder.ts +1068 -0
- package/src/workflow.ts +188 -351
- package/test/barrier-join.test.ts +32 -24
- package/test/cascade-executor.test.ts +9 -16
- package/test/cron-parser.test.ts +314 -0
- package/test/cron-scheduler.test.ts +291 -0
- package/test/database-context.test.ts +770 -0
- package/test/db-provider-adapter.test.ts +862 -0
- package/test/durable-execution-cloudflare.test.ts +606 -0
- package/test/durable-execution-in-process.test.ts +286 -0
- package/test/durable-execution.test.ts +247 -0
- package/test/e2e/workflow-scenarios.e2e.test.ts +1039 -0
- package/test/integration.test.ts +442 -0
- package/test/rpc-surface.test.ts +946 -0
- package/test/runtime.test.ts +262 -0
- package/test/schedule-timer-cleanup.test.ts +30 -21
- package/test/send-race-conditions.test.ts +30 -40
- package/test/worker/durable-cascade.test.ts +1117 -0
- package/test/worker/durable-step.test.ts +723 -0
- package/test/worker/topological-executor.test.ts +1240 -0
- package/test/worker/workflow-builder.test.ts +1067 -0
- package/test/worker.test.ts +608 -0
- package/test/workflow-builder.test.ts +1670 -0
- package/test/workflow-cron.test.ts +256 -0
- package/test/workflow-state-adapter.test.ts +923 -0
- package/test/workflow.test.ts +25 -22
- package/tsconfig.json +3 -1
- package/vitest.config.ts +38 -1
- package/vitest.workers.config.ts +44 -0
- package/wrangler.jsonc +22 -0
- package/.turbo/turbo-test.log +0 -169
- package/LICENSE +0 -21
- package/src/context.js +0 -83
- package/src/every.js +0 -267
- package/src/index.js +0 -71
- package/src/on.js +0 -79
- package/src/send.js +0 -111
- package/src/types.js +0 -4
- package/src/workflow.js +0 -455
- package/test/context.test.js +0 -116
- package/test/every.test.js +0 -282
- package/test/on.test.js +0 -80
- package/test/send.test.js +0 -89
- package/test/workflow.test.js +0 -224
- package/vitest.config.js +0 -7
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TopologicalExecutor - Parallel step execution based on dependency graph
|
|
3
|
+
*
|
|
4
|
+
* Executes workflow steps in parallel while respecting dependency ordering.
|
|
5
|
+
* Uses topological sort to determine execution levels and runs steps
|
|
6
|
+
* within each level concurrently.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Parallel execution of independent steps
|
|
10
|
+
* - Durable execution via Cloudflare Workflows step.do()
|
|
11
|
+
* - Progress callbacks for monitoring
|
|
12
|
+
* - Error handling with rollback support
|
|
13
|
+
* - Visualization helpers for debugging
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* import { TopologicalExecutor, DurableGraph } from 'ai-workflows/worker'
|
|
18
|
+
*
|
|
19
|
+
* // Create executor with steps
|
|
20
|
+
* const executor = new TopologicalExecutor()
|
|
21
|
+
* .addStep('fetch-user', [], async (input) => {
|
|
22
|
+
* return { userId: input.id }
|
|
23
|
+
* })
|
|
24
|
+
* .addStep('fetch-orders', ['fetch-user'], async (input, results) => {
|
|
25
|
+
* return { orders: [] }
|
|
26
|
+
* })
|
|
27
|
+
* .addStep('fetch-prefs', ['fetch-user'], async (input, results) => {
|
|
28
|
+
* return { prefs: {} }
|
|
29
|
+
* })
|
|
30
|
+
* .addStep('aggregate', ['fetch-orders', 'fetch-prefs'], async (input, results) => {
|
|
31
|
+
* return { ...results['fetch-orders'], ...results['fetch-prefs'] }
|
|
32
|
+
* })
|
|
33
|
+
*
|
|
34
|
+
* // Execute with durable workflow
|
|
35
|
+
* const result = await executor.run(step, { id: 'user-123' })
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @packageDocumentation
|
|
39
|
+
*/
|
|
40
|
+
import { DependencyGraph, type DependencyType } from '../dependency-graph.js';
|
|
41
|
+
import { type ExecutionLevel } from '../graph/topological-sort.js';
|
|
42
|
+
import type { WorkflowStep, StepConfig } from './durable-step.js';
|
|
43
|
+
/**
|
|
44
|
+
* Function signature for step execution
|
|
45
|
+
*/
|
|
46
|
+
export type StepExecutor<TInput = unknown, TOutput = unknown> = (input: TInput, previousResults: Record<string, unknown>, context: ExecutionContext) => Promise<TOutput>;
|
|
47
|
+
/**
|
|
48
|
+
* Definition of a step in the execution graph
|
|
49
|
+
*/
|
|
50
|
+
export interface StepDefinition<TInput = unknown, TOutput = unknown> {
|
|
51
|
+
/** Unique step identifier */
|
|
52
|
+
id: string;
|
|
53
|
+
/** IDs of steps this step depends on */
|
|
54
|
+
dependencies: string[];
|
|
55
|
+
/** The step function to execute */
|
|
56
|
+
executor: StepExecutor<TInput, TOutput>;
|
|
57
|
+
/** Optional step configuration (retries, timeout) */
|
|
58
|
+
config: StepConfig | undefined;
|
|
59
|
+
/** Dependency types (hard/soft) */
|
|
60
|
+
dependencyTypes: Record<string, DependencyType> | undefined;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Context provided during step execution
|
|
64
|
+
*/
|
|
65
|
+
export interface ExecutionContext {
|
|
66
|
+
/** Current step ID */
|
|
67
|
+
stepId: string;
|
|
68
|
+
/** Current execution level */
|
|
69
|
+
level: number;
|
|
70
|
+
/** Total number of levels */
|
|
71
|
+
totalLevels: number;
|
|
72
|
+
/** Steps completed so far */
|
|
73
|
+
completedSteps: string[];
|
|
74
|
+
/** Attempt number for this step */
|
|
75
|
+
attempt: number;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Progress callback for monitoring execution
|
|
79
|
+
*/
|
|
80
|
+
export interface ProgressCallback {
|
|
81
|
+
/** Called when a level starts */
|
|
82
|
+
onLevelStart?: (level: number, steps: string[]) => void;
|
|
83
|
+
/** Called when a step starts */
|
|
84
|
+
onStepStart?: (stepId: string, level: number) => void;
|
|
85
|
+
/** Called when a step completes */
|
|
86
|
+
onStepComplete?: (stepId: string, level: number, result: unknown, duration: number) => void;
|
|
87
|
+
/** Called when a step fails */
|
|
88
|
+
onStepError?: (stepId: string, level: number, error: Error) => void;
|
|
89
|
+
/** Called when a level completes */
|
|
90
|
+
onLevelComplete?: (level: number, results: Record<string, unknown>) => void;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Result of executing the graph
|
|
94
|
+
*/
|
|
95
|
+
export interface ExecutionResult<T = Record<string, unknown>> {
|
|
96
|
+
/** All step results keyed by step ID */
|
|
97
|
+
results: T;
|
|
98
|
+
/** Execution metrics */
|
|
99
|
+
metrics: {
|
|
100
|
+
totalDuration: number;
|
|
101
|
+
levelDurations: number[];
|
|
102
|
+
stepDurations: Record<string, number>;
|
|
103
|
+
parallelEfficiency: number;
|
|
104
|
+
};
|
|
105
|
+
/** Steps that were skipped due to soft dependency failures */
|
|
106
|
+
skippedSteps: string[];
|
|
107
|
+
/** Execution order (actual order steps ran in) */
|
|
108
|
+
executionOrder: string[];
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Configuration for TopologicalExecutor
|
|
112
|
+
*/
|
|
113
|
+
export interface ExecutorConfig {
|
|
114
|
+
/** Continue execution on step failure (for soft dependencies) */
|
|
115
|
+
continueOnError?: boolean;
|
|
116
|
+
/** Maximum concurrency per level (default: unlimited) */
|
|
117
|
+
maxConcurrency?: number;
|
|
118
|
+
/** Progress callbacks */
|
|
119
|
+
progress?: ProgressCallback;
|
|
120
|
+
/** Default step config */
|
|
121
|
+
defaultStepConfig?: StepConfig;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* TopologicalExecutor - Execute steps in parallel based on dependency graph
|
|
125
|
+
*
|
|
126
|
+
* Provides parallel execution of workflow steps while respecting dependencies.
|
|
127
|
+
* Steps at the same execution level run concurrently, while levels execute
|
|
128
|
+
* sequentially.
|
|
129
|
+
*/
|
|
130
|
+
export declare class TopologicalExecutor<TInput = unknown> {
|
|
131
|
+
private steps;
|
|
132
|
+
private config;
|
|
133
|
+
constructor(config?: ExecutorConfig);
|
|
134
|
+
/**
|
|
135
|
+
* Add a step to the executor
|
|
136
|
+
*
|
|
137
|
+
* @param id - Unique step identifier
|
|
138
|
+
* @param dependencies - IDs of steps this step depends on
|
|
139
|
+
* @param executor - Function to execute
|
|
140
|
+
* @param config - Optional step configuration
|
|
141
|
+
* @returns this (for chaining)
|
|
142
|
+
*/
|
|
143
|
+
addStep<TOutput>(id: string, dependencies: string[], executor: StepExecutor<TInput, TOutput>, config?: StepConfig): this;
|
|
144
|
+
/**
|
|
145
|
+
* Add a step with dependency type specification
|
|
146
|
+
*/
|
|
147
|
+
addStepWithTypes<TOutput>(id: string, dependencies: Array<{
|
|
148
|
+
id: string;
|
|
149
|
+
type?: DependencyType;
|
|
150
|
+
}>, executor: StepExecutor<TInput, TOutput>, config?: StepConfig): this;
|
|
151
|
+
/**
|
|
152
|
+
* Get the dependency graph
|
|
153
|
+
*/
|
|
154
|
+
getGraph(): DependencyGraph;
|
|
155
|
+
/**
|
|
156
|
+
* Get execution levels for parallel execution
|
|
157
|
+
*/
|
|
158
|
+
getExecutionLevels(): ExecutionLevel[];
|
|
159
|
+
/**
|
|
160
|
+
* Validate the graph (check for cycles, missing deps)
|
|
161
|
+
*/
|
|
162
|
+
validate(): {
|
|
163
|
+
valid: boolean;
|
|
164
|
+
errors: string[];
|
|
165
|
+
};
|
|
166
|
+
/**
|
|
167
|
+
* Execute the graph with durable semantics
|
|
168
|
+
*
|
|
169
|
+
* @param workflowStep - Cloudflare Workflows step object
|
|
170
|
+
* @param input - Input to pass to all steps
|
|
171
|
+
* @returns Execution result with all step outputs
|
|
172
|
+
*/
|
|
173
|
+
run(workflowStep: WorkflowStep, input: TInput): Promise<ExecutionResult<Record<string, unknown>>>;
|
|
174
|
+
/**
|
|
175
|
+
* Execute a single step with durability
|
|
176
|
+
*/
|
|
177
|
+
private executeStep;
|
|
178
|
+
/**
|
|
179
|
+
* Visualize the execution graph as DOT format
|
|
180
|
+
*/
|
|
181
|
+
toDot(): string;
|
|
182
|
+
/**
|
|
183
|
+
* Get graph as JSON for debugging
|
|
184
|
+
*/
|
|
185
|
+
toJSON(): {
|
|
186
|
+
steps: StepDefinition[];
|
|
187
|
+
levels: ExecutionLevel[];
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* DurableGraph - Dependency graph with durable execution support
|
|
192
|
+
*
|
|
193
|
+
* Extends DependencyGraph with execution capabilities using Cloudflare
|
|
194
|
+
* Workflows durability. Provides a higher-level API for building and
|
|
195
|
+
* executing workflow step graphs.
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* ```typescript
|
|
199
|
+
* const graph = new DurableGraph<{ userId: string }>()
|
|
200
|
+
* .node('fetch-user', async (input) => {
|
|
201
|
+
* return await fetchUser(input.userId)
|
|
202
|
+
* })
|
|
203
|
+
* .node('fetch-orders', ['fetch-user'], async (input, results) => {
|
|
204
|
+
* return await fetchOrders(results['fetch-user'].id)
|
|
205
|
+
* })
|
|
206
|
+
*
|
|
207
|
+
* const result = await graph.execute(step, { userId: '123' })
|
|
208
|
+
* ```
|
|
209
|
+
*/
|
|
210
|
+
export declare class DurableGraph<TInput = unknown> {
|
|
211
|
+
private executor;
|
|
212
|
+
private nodeMetadata;
|
|
213
|
+
constructor(config?: ExecutorConfig);
|
|
214
|
+
/**
|
|
215
|
+
* Add a node (step) to the graph
|
|
216
|
+
*
|
|
217
|
+
* @param id - Unique node identifier
|
|
218
|
+
* @param executor - Function to execute (if no dependencies)
|
|
219
|
+
*/
|
|
220
|
+
node<TOutput>(id: string, executor: StepExecutor<TInput, TOutput>): this;
|
|
221
|
+
/**
|
|
222
|
+
* Add a node with dependencies
|
|
223
|
+
*
|
|
224
|
+
* @param id - Unique node identifier
|
|
225
|
+
* @param dependencies - IDs of nodes this node depends on
|
|
226
|
+
* @param executor - Function to execute
|
|
227
|
+
*/
|
|
228
|
+
node<TOutput>(id: string, dependencies: string[], executor: StepExecutor<TInput, TOutput>): this;
|
|
229
|
+
/**
|
|
230
|
+
* Add a node with dependencies and config
|
|
231
|
+
*
|
|
232
|
+
* @param id - Unique node identifier
|
|
233
|
+
* @param dependencies - IDs of nodes this node depends on
|
|
234
|
+
* @param executor - Function to execute
|
|
235
|
+
* @param config - Step configuration
|
|
236
|
+
*/
|
|
237
|
+
node<TOutput>(id: string, dependencies: string[], executor: StepExecutor<TInput, TOutput>, config: StepConfig): this;
|
|
238
|
+
/**
|
|
239
|
+
* Add metadata to a node
|
|
240
|
+
*/
|
|
241
|
+
describe(id: string, metadata: {
|
|
242
|
+
description?: string;
|
|
243
|
+
tags?: string[];
|
|
244
|
+
}): this;
|
|
245
|
+
/**
|
|
246
|
+
* Get execution levels
|
|
247
|
+
*/
|
|
248
|
+
levels(): ExecutionLevel[];
|
|
249
|
+
/**
|
|
250
|
+
* Validate the graph
|
|
251
|
+
*/
|
|
252
|
+
validate(): {
|
|
253
|
+
valid: boolean;
|
|
254
|
+
errors: string[];
|
|
255
|
+
};
|
|
256
|
+
/**
|
|
257
|
+
* Execute the graph with durable semantics
|
|
258
|
+
*/
|
|
259
|
+
execute(workflowStep: WorkflowStep, input: TInput): Promise<ExecutionResult<Record<string, unknown>>>;
|
|
260
|
+
/**
|
|
261
|
+
* Get DOT visualization
|
|
262
|
+
*/
|
|
263
|
+
toDot(): string;
|
|
264
|
+
/**
|
|
265
|
+
* Get as JSON
|
|
266
|
+
*/
|
|
267
|
+
toJSON(): ReturnType<TopologicalExecutor<TInput>['toJSON']> & {
|
|
268
|
+
metadata: Record<string, {
|
|
269
|
+
description?: string;
|
|
270
|
+
tags?: string[];
|
|
271
|
+
}>;
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Create a new TopologicalExecutor
|
|
276
|
+
*/
|
|
277
|
+
export declare function createExecutor<TInput = unknown>(config?: ExecutorConfig): TopologicalExecutor<TInput>;
|
|
278
|
+
/**
|
|
279
|
+
* Create a new DurableGraph
|
|
280
|
+
*/
|
|
281
|
+
export declare function createGraph<TInput = unknown>(config?: ExecutorConfig): DurableGraph<TInput>;
|
|
282
|
+
//# sourceMappingURL=topological-executor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"topological-executor.d.ts","sourceRoot":"","sources":["../../src/worker/topological-executor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TopologicalExecutor - Parallel step execution based on dependency graph
|
|
3
|
+
*
|
|
4
|
+
* Executes workflow steps in parallel while respecting dependency ordering.
|
|
5
|
+
* Uses topological sort to determine execution levels and runs steps
|
|
6
|
+
* within each level concurrently.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Parallel execution of independent steps
|
|
10
|
+
* - Durable execution via Cloudflare Workflows step.do()
|
|
11
|
+
* - Progress callbacks for monitoring
|
|
12
|
+
* - Error handling with rollback support
|
|
13
|
+
* - Visualization helpers for debugging
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* import { TopologicalExecutor, DurableGraph } from 'ai-workflows/worker'
|
|
18
|
+
*
|
|
19
|
+
* // Create executor with steps
|
|
20
|
+
* const executor = new TopologicalExecutor()
|
|
21
|
+
* .addStep('fetch-user', [], async (input) => {
|
|
22
|
+
* return { userId: input.id }
|
|
23
|
+
* })
|
|
24
|
+
* .addStep('fetch-orders', ['fetch-user'], async (input, results) => {
|
|
25
|
+
* return { orders: [] }
|
|
26
|
+
* })
|
|
27
|
+
* .addStep('fetch-prefs', ['fetch-user'], async (input, results) => {
|
|
28
|
+
* return { prefs: {} }
|
|
29
|
+
* })
|
|
30
|
+
* .addStep('aggregate', ['fetch-orders', 'fetch-prefs'], async (input, results) => {
|
|
31
|
+
* return { ...results['fetch-orders'], ...results['fetch-prefs'] }
|
|
32
|
+
* })
|
|
33
|
+
*
|
|
34
|
+
* // Execute with durable workflow
|
|
35
|
+
* const result = await executor.run(step, { id: 'user-123' })
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @packageDocumentation
|
|
39
|
+
*/
|
|
40
|
+
import { DependencyGraph, CircularDependencyError, MissingDependencyError, } from '../dependency-graph.js';
|
|
41
|
+
import { topologicalSort, getExecutionLevels, CycleDetectedError, } from '../graph/topological-sort.js';
|
|
42
|
+
// ============================================================================
|
|
43
|
+
// TopologicalExecutor
|
|
44
|
+
// ============================================================================
|
|
45
|
+
/**
|
|
46
|
+
* TopologicalExecutor - Execute steps in parallel based on dependency graph
|
|
47
|
+
*
|
|
48
|
+
* Provides parallel execution of workflow steps while respecting dependencies.
|
|
49
|
+
* Steps at the same execution level run concurrently, while levels execute
|
|
50
|
+
* sequentially.
|
|
51
|
+
*/
|
|
52
|
+
export class TopologicalExecutor {
|
|
53
|
+
steps = new Map();
|
|
54
|
+
config;
|
|
55
|
+
constructor(config = {}) {
|
|
56
|
+
this.config = config;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Add a step to the executor
|
|
60
|
+
*
|
|
61
|
+
* @param id - Unique step identifier
|
|
62
|
+
* @param dependencies - IDs of steps this step depends on
|
|
63
|
+
* @param executor - Function to execute
|
|
64
|
+
* @param config - Optional step configuration
|
|
65
|
+
* @returns this (for chaining)
|
|
66
|
+
*/
|
|
67
|
+
addStep(id, dependencies, executor, config) {
|
|
68
|
+
this.steps.set(id, {
|
|
69
|
+
id,
|
|
70
|
+
dependencies,
|
|
71
|
+
executor: executor,
|
|
72
|
+
config: config ?? this.config.defaultStepConfig,
|
|
73
|
+
dependencyTypes: undefined,
|
|
74
|
+
});
|
|
75
|
+
return this;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Add a step with dependency type specification
|
|
79
|
+
*/
|
|
80
|
+
addStepWithTypes(id, dependencies, executor, config) {
|
|
81
|
+
const depIds = dependencies.map((d) => d.id);
|
|
82
|
+
const depTypes = {};
|
|
83
|
+
for (const dep of dependencies) {
|
|
84
|
+
depTypes[dep.id] = dep.type ?? 'hard';
|
|
85
|
+
}
|
|
86
|
+
this.steps.set(id, {
|
|
87
|
+
id,
|
|
88
|
+
dependencies: depIds,
|
|
89
|
+
executor: executor,
|
|
90
|
+
config: config ?? this.config.defaultStepConfig,
|
|
91
|
+
dependencyTypes: depTypes,
|
|
92
|
+
});
|
|
93
|
+
return this;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Get the dependency graph
|
|
97
|
+
*/
|
|
98
|
+
getGraph() {
|
|
99
|
+
const graph = new DependencyGraph();
|
|
100
|
+
// Add nodes in topological order to ensure dependencies exist
|
|
101
|
+
const nodes = Array.from(this.steps.values()).map((s) => ({
|
|
102
|
+
id: s.id,
|
|
103
|
+
dependencies: s.dependencies,
|
|
104
|
+
}));
|
|
105
|
+
const sorted = topologicalSort(nodes);
|
|
106
|
+
if (sorted.hasCycle) {
|
|
107
|
+
throw new CycleDetectedError(sorted.cyclePath ?? ['unknown']);
|
|
108
|
+
}
|
|
109
|
+
// Add nodes without dependencies first
|
|
110
|
+
for (const id of sorted.order) {
|
|
111
|
+
const step = this.steps.get(id);
|
|
112
|
+
if (step.dependencies.length === 0) {
|
|
113
|
+
graph.addNode(id);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Add nodes with dependencies
|
|
117
|
+
for (const id of sorted.order) {
|
|
118
|
+
const step = this.steps.get(id);
|
|
119
|
+
if (step.dependencies.length > 0) {
|
|
120
|
+
const firstDep = step.dependencies[0];
|
|
121
|
+
const depType = firstDep !== undefined ? step.dependencyTypes?.[firstDep] ?? 'hard' : 'hard';
|
|
122
|
+
graph.addNode(id, {
|
|
123
|
+
dependsOn: step.dependencies,
|
|
124
|
+
type: depType,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return graph;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Get execution levels for parallel execution
|
|
132
|
+
*/
|
|
133
|
+
getExecutionLevels() {
|
|
134
|
+
const nodes = Array.from(this.steps.values()).map((s) => ({
|
|
135
|
+
id: s.id,
|
|
136
|
+
dependencies: s.dependencies,
|
|
137
|
+
}));
|
|
138
|
+
return getExecutionLevels(nodes);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Validate the graph (check for cycles, missing deps)
|
|
142
|
+
*/
|
|
143
|
+
validate() {
|
|
144
|
+
const errors = [];
|
|
145
|
+
const nodes = Array.from(this.steps.values()).map((s) => ({
|
|
146
|
+
id: s.id,
|
|
147
|
+
dependencies: s.dependencies,
|
|
148
|
+
}));
|
|
149
|
+
// Check for cycles
|
|
150
|
+
const result = topologicalSort(nodes);
|
|
151
|
+
if (result.hasCycle) {
|
|
152
|
+
errors.push(`Circular dependency detected: ${result.cyclePath?.join(' -> ')}`);
|
|
153
|
+
}
|
|
154
|
+
// Check for missing dependencies
|
|
155
|
+
const stepIds = new Set(this.steps.keys());
|
|
156
|
+
for (const step of this.steps.values()) {
|
|
157
|
+
for (const dep of step.dependencies) {
|
|
158
|
+
if (!stepIds.has(dep)) {
|
|
159
|
+
errors.push(`Step '${step.id}' depends on missing step '${dep}'`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
valid: errors.length === 0,
|
|
165
|
+
errors,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Execute the graph with durable semantics
|
|
170
|
+
*
|
|
171
|
+
* @param workflowStep - Cloudflare Workflows step object
|
|
172
|
+
* @param input - Input to pass to all steps
|
|
173
|
+
* @returns Execution result with all step outputs
|
|
174
|
+
*/
|
|
175
|
+
async run(workflowStep, input) {
|
|
176
|
+
const validation = this.validate();
|
|
177
|
+
if (!validation.valid) {
|
|
178
|
+
throw new Error(`Invalid execution graph: ${validation.errors.join(', ')}`);
|
|
179
|
+
}
|
|
180
|
+
const levels = this.getExecutionLevels();
|
|
181
|
+
const results = {};
|
|
182
|
+
const stepDurations = {};
|
|
183
|
+
const levelDurations = [];
|
|
184
|
+
const skippedSteps = [];
|
|
185
|
+
const executionOrder = [];
|
|
186
|
+
const completedSteps = [];
|
|
187
|
+
const failedSteps = new Set();
|
|
188
|
+
const startTime = Date.now();
|
|
189
|
+
for (let levelIndex = 0; levelIndex < levels.length; levelIndex++) {
|
|
190
|
+
const level = levels[levelIndex];
|
|
191
|
+
const levelStartTime = Date.now();
|
|
192
|
+
this.config.progress?.onLevelStart?.(level.level, level.nodes);
|
|
193
|
+
// Filter steps that can run (all hard deps satisfied)
|
|
194
|
+
const runnableSteps = level.nodes.filter((stepId) => {
|
|
195
|
+
const step = this.steps.get(stepId);
|
|
196
|
+
const hardDeps = step.dependencies.filter((dep) => (step.dependencyTypes?.[dep] ?? 'hard') === 'hard');
|
|
197
|
+
return hardDeps.every((dep) => completedSteps.includes(dep));
|
|
198
|
+
});
|
|
199
|
+
// Skip steps whose hard dependencies failed
|
|
200
|
+
const stepsToSkip = level.nodes.filter((stepId) => !runnableSteps.includes(stepId));
|
|
201
|
+
for (const stepId of stepsToSkip) {
|
|
202
|
+
skippedSteps.push(stepId);
|
|
203
|
+
failedSteps.add(stepId);
|
|
204
|
+
}
|
|
205
|
+
// Execute runnable steps in parallel
|
|
206
|
+
const stepPromises = runnableSteps.map(async (stepId) => {
|
|
207
|
+
const step = this.steps.get(stepId);
|
|
208
|
+
const stepStartTime = Date.now();
|
|
209
|
+
this.config.progress?.onStepStart?.(stepId, level.level);
|
|
210
|
+
const context = {
|
|
211
|
+
stepId,
|
|
212
|
+
level: level.level,
|
|
213
|
+
totalLevels: levels.length,
|
|
214
|
+
completedSteps: [...completedSteps],
|
|
215
|
+
attempt: 1,
|
|
216
|
+
};
|
|
217
|
+
try {
|
|
218
|
+
const result = await this.executeStep(workflowStep, step, input, results, context);
|
|
219
|
+
const duration = Date.now() - stepStartTime;
|
|
220
|
+
results[stepId] = result;
|
|
221
|
+
stepDurations[stepId] = duration;
|
|
222
|
+
executionOrder.push(stepId);
|
|
223
|
+
completedSteps.push(stepId);
|
|
224
|
+
this.config.progress?.onStepComplete?.(stepId, level.level, result, duration);
|
|
225
|
+
return { stepId, success: true, result };
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
229
|
+
const duration = Date.now() - stepStartTime;
|
|
230
|
+
stepDurations[stepId] = duration;
|
|
231
|
+
failedSteps.add(stepId);
|
|
232
|
+
this.config.progress?.onStepError?.(stepId, level.level, err);
|
|
233
|
+
if (!this.config.continueOnError) {
|
|
234
|
+
throw err;
|
|
235
|
+
}
|
|
236
|
+
return { stepId, success: false, error: err };
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
// Wait for all steps in this level
|
|
240
|
+
const levelResults = await Promise.all(stepPromises);
|
|
241
|
+
const levelDuration = Date.now() - levelStartTime;
|
|
242
|
+
levelDurations.push(levelDuration);
|
|
243
|
+
this.config.progress?.onLevelComplete?.(level.level, results);
|
|
244
|
+
}
|
|
245
|
+
const totalDuration = Date.now() - startTime;
|
|
246
|
+
// Calculate parallel efficiency
|
|
247
|
+
const sequentialTime = Object.values(stepDurations).reduce((a, b) => a + b, 0);
|
|
248
|
+
const parallelEfficiency = sequentialTime > 0 ? sequentialTime / totalDuration : 1;
|
|
249
|
+
return {
|
|
250
|
+
results,
|
|
251
|
+
metrics: {
|
|
252
|
+
totalDuration,
|
|
253
|
+
levelDurations,
|
|
254
|
+
stepDurations,
|
|
255
|
+
parallelEfficiency,
|
|
256
|
+
},
|
|
257
|
+
skippedSteps,
|
|
258
|
+
executionOrder,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Execute a single step with durability
|
|
263
|
+
*/
|
|
264
|
+
async executeStep(workflowStep, step, input, previousResults, context) {
|
|
265
|
+
if (step.config) {
|
|
266
|
+
return workflowStep.do(step.id, step.config, async () => {
|
|
267
|
+
return step.executor(input, previousResults, context);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
return workflowStep.do(step.id, async () => {
|
|
272
|
+
return step.executor(input, previousResults, context);
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Visualize the execution graph as DOT format
|
|
278
|
+
*/
|
|
279
|
+
toDot() {
|
|
280
|
+
const graph = this.getGraph();
|
|
281
|
+
return graph.toDot();
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Get graph as JSON for debugging
|
|
285
|
+
*/
|
|
286
|
+
toJSON() {
|
|
287
|
+
return {
|
|
288
|
+
steps: Array.from(this.steps.values()),
|
|
289
|
+
levels: this.getExecutionLevels(),
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
// ============================================================================
|
|
294
|
+
// DurableGraph
|
|
295
|
+
// ============================================================================
|
|
296
|
+
/**
|
|
297
|
+
* DurableGraph - Dependency graph with durable execution support
|
|
298
|
+
*
|
|
299
|
+
* Extends DependencyGraph with execution capabilities using Cloudflare
|
|
300
|
+
* Workflows durability. Provides a higher-level API for building and
|
|
301
|
+
* executing workflow step graphs.
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* ```typescript
|
|
305
|
+
* const graph = new DurableGraph<{ userId: string }>()
|
|
306
|
+
* .node('fetch-user', async (input) => {
|
|
307
|
+
* return await fetchUser(input.userId)
|
|
308
|
+
* })
|
|
309
|
+
* .node('fetch-orders', ['fetch-user'], async (input, results) => {
|
|
310
|
+
* return await fetchOrders(results['fetch-user'].id)
|
|
311
|
+
* })
|
|
312
|
+
*
|
|
313
|
+
* const result = await graph.execute(step, { userId: '123' })
|
|
314
|
+
* ```
|
|
315
|
+
*/
|
|
316
|
+
export class DurableGraph {
|
|
317
|
+
executor;
|
|
318
|
+
nodeMetadata = new Map();
|
|
319
|
+
constructor(config = {}) {
|
|
320
|
+
this.executor = new TopologicalExecutor(config);
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Implementation of node() overloads
|
|
324
|
+
*/
|
|
325
|
+
node(id, depsOrExecutor, executorOrConfig, maybeConfig) {
|
|
326
|
+
if (typeof depsOrExecutor === 'function') {
|
|
327
|
+
// node(id, executor)
|
|
328
|
+
this.executor.addStep(id, [], depsOrExecutor);
|
|
329
|
+
}
|
|
330
|
+
else if (typeof executorOrConfig === 'function') {
|
|
331
|
+
// node(id, deps, executor) or node(id, deps, executor, config)
|
|
332
|
+
this.executor.addStep(id, depsOrExecutor, executorOrConfig, maybeConfig);
|
|
333
|
+
}
|
|
334
|
+
return this;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Add metadata to a node
|
|
338
|
+
*/
|
|
339
|
+
describe(id, metadata) {
|
|
340
|
+
this.nodeMetadata.set(id, metadata);
|
|
341
|
+
return this;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Get execution levels
|
|
345
|
+
*/
|
|
346
|
+
levels() {
|
|
347
|
+
return this.executor.getExecutionLevels();
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Validate the graph
|
|
351
|
+
*/
|
|
352
|
+
validate() {
|
|
353
|
+
return this.executor.validate();
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Execute the graph with durable semantics
|
|
357
|
+
*/
|
|
358
|
+
async execute(workflowStep, input) {
|
|
359
|
+
return this.executor.run(workflowStep, input);
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Get DOT visualization
|
|
363
|
+
*/
|
|
364
|
+
toDot() {
|
|
365
|
+
return this.executor.toDot();
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Get as JSON
|
|
369
|
+
*/
|
|
370
|
+
toJSON() {
|
|
371
|
+
const metadata = {};
|
|
372
|
+
for (const [id, meta] of this.nodeMetadata) {
|
|
373
|
+
metadata[id] = meta;
|
|
374
|
+
}
|
|
375
|
+
return {
|
|
376
|
+
...this.executor.toJSON(),
|
|
377
|
+
metadata,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
// ============================================================================
|
|
382
|
+
// Convenience functions
|
|
383
|
+
// ============================================================================
|
|
384
|
+
/**
|
|
385
|
+
* Create a new TopologicalExecutor
|
|
386
|
+
*/
|
|
387
|
+
export function createExecutor(config) {
|
|
388
|
+
return new TopologicalExecutor(config);
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Create a new DurableGraph
|
|
392
|
+
*/
|
|
393
|
+
export function createGraph(config) {
|
|
394
|
+
return new DurableGraph(config);
|
|
395
|
+
}
|
|
396
|
+
//# sourceMappingURL=topological-executor.js.map
|