ai-workflows 2.1.3 → 2.3.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 +8 -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,871 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WorkflowBuilder DSL - Declarative workflow definition with fluent API
|
|
3
|
+
*
|
|
4
|
+
* Provides a builder pattern for creating durable workflows with:
|
|
5
|
+
* - Step definitions with dependencies
|
|
6
|
+
* - Event triggers
|
|
7
|
+
* - Scheduled execution
|
|
8
|
+
* - Type-safe execution context
|
|
9
|
+
*
|
|
10
|
+
* ## Features
|
|
11
|
+
*
|
|
12
|
+
* - **Fluent API**: Chain methods for intuitive workflow definition
|
|
13
|
+
* - **Step Dependencies**: Declare execution order with `.dependsOn()`
|
|
14
|
+
* - **Event Triggers**: React to domain events with `.on('Noun.event')`
|
|
15
|
+
* - **Schedule Triggers**: Run on intervals with `.every()`
|
|
16
|
+
* - **Error Handling**: Per-step error handlers with `.onError()`
|
|
17
|
+
* - **Parallel Execution**: Steps without dependencies run concurrently
|
|
18
|
+
* - **Cycle Detection**: Automatically detects circular dependencies
|
|
19
|
+
*
|
|
20
|
+
* ## Basic Usage
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* import { WorkflowBuilder } from 'ai-workflows/worker'
|
|
25
|
+
*
|
|
26
|
+
* const workflow = WorkflowBuilder.create('order-process')
|
|
27
|
+
* .step('validate', async (input) => {
|
|
28
|
+
* return { valid: true }
|
|
29
|
+
* })
|
|
30
|
+
* .step('charge', async (input, ctx) => {
|
|
31
|
+
* const validation = ctx.getStepResult<{ valid: boolean }>('validate')
|
|
32
|
+
* if (!validation.valid) throw new Error('Invalid order')
|
|
33
|
+
* return { charged: true }
|
|
34
|
+
* }).dependsOn('validate')
|
|
35
|
+
* .step('fulfill', fulfillOrder).dependsOn('charge')
|
|
36
|
+
* .on('Order.placed').do('validate')
|
|
37
|
+
* .build()
|
|
38
|
+
*
|
|
39
|
+
* // Execute the workflow
|
|
40
|
+
* const results = await workflow.execute({ orderId: 'order-123' })
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* ## With DurableStep
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* import { WorkflowBuilder, DurableStep } from 'ai-workflows/worker'
|
|
48
|
+
*
|
|
49
|
+
* const fetchData = new DurableStep('fetch-data', async ({ url }) => {
|
|
50
|
+
* return fetch(url).then(r => r.json())
|
|
51
|
+
* })
|
|
52
|
+
*
|
|
53
|
+
* const workflow = WorkflowBuilder.create('data-pipeline')
|
|
54
|
+
* .step(fetchData)
|
|
55
|
+
* .step('process', processData).dependsOn('fetch-data')
|
|
56
|
+
* .build()
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* ## Scheduled Workflows
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```typescript
|
|
63
|
+
* const workflow = WorkflowBuilder.create('daily-report')
|
|
64
|
+
* .step('generate', generateReport)
|
|
65
|
+
* .step('send', sendReport).dependsOn('generate')
|
|
66
|
+
* .every('day').at('9am').do('generate')
|
|
67
|
+
* .build()
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* @packageDocumentation
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
import { DurableStep, type StepConfig, type RetryConfig, StepContext } from './durable-step.js'
|
|
74
|
+
|
|
75
|
+
// ============================================================================
|
|
76
|
+
// Type Definitions
|
|
77
|
+
// ============================================================================
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Configuration for WorkflowBuilder
|
|
81
|
+
*/
|
|
82
|
+
export interface WorkflowBuilderConfig {
|
|
83
|
+
/** Human-readable description of the workflow */
|
|
84
|
+
description?: string
|
|
85
|
+
/** Version string (semver recommended) */
|
|
86
|
+
version?: string
|
|
87
|
+
/** Default timeout for all steps */
|
|
88
|
+
timeout?: string | number
|
|
89
|
+
/** Default retry configuration for all steps */
|
|
90
|
+
retries?: RetryConfig
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Step definition stored in the builder
|
|
95
|
+
*/
|
|
96
|
+
export interface StepDefinition<TInput = unknown, TOutput = unknown> {
|
|
97
|
+
/** Unique step name */
|
|
98
|
+
name: string
|
|
99
|
+
/** The step function */
|
|
100
|
+
fn: (input: TInput, ctx: ExecutionContext) => Promise<TOutput>
|
|
101
|
+
/** Step configuration (retries, timeout) */
|
|
102
|
+
config?: StepConfig
|
|
103
|
+
/** Dependencies (step names this step depends on) */
|
|
104
|
+
dependencies: Array<{ name: string; options?: DependencyOptions }>
|
|
105
|
+
/** Error handler */
|
|
106
|
+
errorHandler?: (error: Error, ctx: ExecutionContext) => unknown | Promise<unknown>
|
|
107
|
+
/** DurableStep wrapper */
|
|
108
|
+
durableStep?: DurableStep<TInput, TOutput>
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Options for dependency declarations
|
|
113
|
+
*/
|
|
114
|
+
export interface DependencyOptions {
|
|
115
|
+
/** Dependency type: 'hard' (default) or 'soft' (can proceed on failure) */
|
|
116
|
+
type?: 'hard' | 'soft'
|
|
117
|
+
/** Timeout for waiting on dependency */
|
|
118
|
+
timeout?: string | number
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Event trigger configuration
|
|
123
|
+
*/
|
|
124
|
+
export interface TriggerConfig {
|
|
125
|
+
/** Event name (Noun.event format) */
|
|
126
|
+
event: string
|
|
127
|
+
/** Step name to trigger (or inline function was converted to implicit step) */
|
|
128
|
+
stepName: string
|
|
129
|
+
/** Filter function */
|
|
130
|
+
filter?: (event: unknown) => boolean
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Schedule trigger configuration
|
|
135
|
+
*/
|
|
136
|
+
export interface ScheduleConfig {
|
|
137
|
+
/** Schedule expression (interval, cron, or natural language) */
|
|
138
|
+
schedule: string
|
|
139
|
+
/** Numeric value for interval (e.g., 5 for every(5).minutes()) */
|
|
140
|
+
value?: number
|
|
141
|
+
/** Time of day (e.g., '9am') */
|
|
142
|
+
time?: string
|
|
143
|
+
/** Timezone (e.g., 'America/New_York') */
|
|
144
|
+
timezone?: string
|
|
145
|
+
/** Step name to execute */
|
|
146
|
+
stepName: string
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Built workflow definition (immutable)
|
|
151
|
+
*/
|
|
152
|
+
export interface BuiltWorkflow<TInput = unknown, TResults = Record<string, unknown>> {
|
|
153
|
+
/** Workflow name */
|
|
154
|
+
readonly name: string
|
|
155
|
+
/** All registered steps */
|
|
156
|
+
readonly steps: ReadonlyArray<StepDefinition>
|
|
157
|
+
/** Workflow triggers */
|
|
158
|
+
readonly triggers: {
|
|
159
|
+
readonly events: ReadonlyArray<TriggerConfig>
|
|
160
|
+
readonly schedules: ReadonlyArray<ScheduleConfig>
|
|
161
|
+
}
|
|
162
|
+
/** Dependency graph (step -> dependencies) */
|
|
163
|
+
readonly dependencyGraph: Map<string, string[]>
|
|
164
|
+
/** Topologically sorted execution order */
|
|
165
|
+
readonly executionOrder: string[]
|
|
166
|
+
/** Workflow metadata */
|
|
167
|
+
readonly metadata?: WorkflowBuilderConfig
|
|
168
|
+
/** Whether compatible with Cloudflare Workflows */
|
|
169
|
+
readonly isCloudflareCompatible: boolean
|
|
170
|
+
/** Execute the workflow */
|
|
171
|
+
execute: (input?: TInput) => Promise<TResults>
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Execution context passed to step functions
|
|
176
|
+
*/
|
|
177
|
+
export interface ExecutionContext {
|
|
178
|
+
/** Get result of a previously executed step */
|
|
179
|
+
getStepResult: <T = unknown>(stepName: string) => T
|
|
180
|
+
/** Current workflow input */
|
|
181
|
+
readonly input: unknown
|
|
182
|
+
/** All step results so far */
|
|
183
|
+
readonly results: Record<string, unknown>
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Step chain for configuring a step after adding it
|
|
188
|
+
*/
|
|
189
|
+
export interface StepChain {
|
|
190
|
+
/** Declare dependencies for this step */
|
|
191
|
+
dependsOn(step: string, options?: DependencyOptions): StepChain
|
|
192
|
+
dependsOn(steps: string[]): StepChain
|
|
193
|
+
/** Set timeout for this step */
|
|
194
|
+
timeout(duration: string | number): StepChain
|
|
195
|
+
/** Set retry configuration */
|
|
196
|
+
retries(config: RetryConfig): StepChain
|
|
197
|
+
/** Set error handler */
|
|
198
|
+
onError(handler: (error: Error, ctx: ExecutionContext) => unknown | Promise<unknown>): StepChain
|
|
199
|
+
/** Add another step (returns to builder) */
|
|
200
|
+
step<TI = unknown, TO = unknown>(
|
|
201
|
+
name: string,
|
|
202
|
+
fn: (input: TI, ctx: ExecutionContext) => Promise<TO>
|
|
203
|
+
): StepChain
|
|
204
|
+
step<TI = unknown, TO = unknown>(
|
|
205
|
+
name: string,
|
|
206
|
+
config: StepConfig,
|
|
207
|
+
fn: (input: TI, ctx: ExecutionContext) => Promise<TO>
|
|
208
|
+
): StepChain
|
|
209
|
+
step<TI = unknown, TO = unknown>(durableStep: DurableStep<TI, TO>): StepChain
|
|
210
|
+
/** Register event trigger */
|
|
211
|
+
on<T = unknown>(event: string): EventTriggerChain<T>
|
|
212
|
+
/** Register schedule trigger */
|
|
213
|
+
every(schedule: string): ScheduleTriggerChain
|
|
214
|
+
every(value: number): NumericScheduleChain
|
|
215
|
+
/** Build the workflow */
|
|
216
|
+
build(): BuiltWorkflow
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Event trigger chain for configuring event-triggered steps
|
|
221
|
+
*/
|
|
222
|
+
export interface EventTriggerChain<T = unknown> {
|
|
223
|
+
/** Add a filter function */
|
|
224
|
+
filter(predicate: (event: T) => boolean): EventTriggerChain<T>
|
|
225
|
+
/** Execute a step when event fires */
|
|
226
|
+
do(stepName: string): StepChain
|
|
227
|
+
do(fn: (event: T) => Promise<unknown>): StepChain
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Schedule trigger chain for configuring scheduled steps
|
|
232
|
+
*/
|
|
233
|
+
export interface ScheduleTriggerChain {
|
|
234
|
+
/** Set time of day */
|
|
235
|
+
at(time: string): ScheduleTriggerChain
|
|
236
|
+
/** Set timezone */
|
|
237
|
+
timezone(tz: string): ScheduleTriggerChain
|
|
238
|
+
/** Execute a step on schedule */
|
|
239
|
+
do(stepName: string): StepChain
|
|
240
|
+
do(fn: () => Promise<unknown>): StepChain
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Numeric schedule chain (for every(5).minutes() pattern)
|
|
245
|
+
*/
|
|
246
|
+
export interface NumericScheduleChain {
|
|
247
|
+
minutes(): ScheduleTriggerChain
|
|
248
|
+
hours(): ScheduleTriggerChain
|
|
249
|
+
days(): ScheduleTriggerChain
|
|
250
|
+
seconds(): ScheduleTriggerChain
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ============================================================================
|
|
254
|
+
// Implementation
|
|
255
|
+
// ============================================================================
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Internal class implementing StepChain, EventTriggerChain, ScheduleTriggerChain
|
|
259
|
+
*/
|
|
260
|
+
class WorkflowBuilderImpl
|
|
261
|
+
implements StepChain, EventTriggerChain, ScheduleTriggerChain, NumericScheduleChain
|
|
262
|
+
{
|
|
263
|
+
private _name: string
|
|
264
|
+
private _config?: WorkflowBuilderConfig
|
|
265
|
+
private _steps: Map<string, StepDefinition> = new Map()
|
|
266
|
+
private _eventTriggers: TriggerConfig[] = []
|
|
267
|
+
private _scheduleTriggers: ScheduleConfig[] = []
|
|
268
|
+
private _currentStep: string | null = null
|
|
269
|
+
private _implicitStepCounter = 0
|
|
270
|
+
|
|
271
|
+
// For event trigger chain
|
|
272
|
+
private _currentEvent: string | null = null
|
|
273
|
+
private _currentFilter: ((event: unknown) => boolean) | null = null
|
|
274
|
+
|
|
275
|
+
// For schedule trigger chain
|
|
276
|
+
private _currentSchedule: string | null = null
|
|
277
|
+
private _currentScheduleValue: number | null = null
|
|
278
|
+
private _currentTime: string | null = null
|
|
279
|
+
private _currentTimezone: string | null = null
|
|
280
|
+
|
|
281
|
+
constructor(name: string, config?: WorkflowBuilderConfig) {
|
|
282
|
+
if (!name || name.trim() === '') {
|
|
283
|
+
throw new Error('Workflow name is required')
|
|
284
|
+
}
|
|
285
|
+
this._name = name
|
|
286
|
+
if (config !== undefined) {
|
|
287
|
+
this._config = config
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
get name(): string {
|
|
292
|
+
return this._name
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
get config(): WorkflowBuilderConfig | undefined {
|
|
296
|
+
return this._config
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// ==================== Step Methods ====================
|
|
300
|
+
|
|
301
|
+
step<TI = unknown, TO = unknown>(
|
|
302
|
+
nameOrStep: string | DurableStep<TI, TO>,
|
|
303
|
+
configOrFn?: StepConfig | ((input: TI, ctx: ExecutionContext) => Promise<TO>),
|
|
304
|
+
maybeFn?: (input: TI, ctx: ExecutionContext) => Promise<TO>
|
|
305
|
+
): StepChain {
|
|
306
|
+
let name: string
|
|
307
|
+
let fn: (input: TI, ctx: ExecutionContext) => Promise<TO>
|
|
308
|
+
let stepConfig: StepConfig | undefined
|
|
309
|
+
let durableStep: DurableStep<TI, TO> | undefined
|
|
310
|
+
|
|
311
|
+
if (nameOrStep instanceof DurableStep) {
|
|
312
|
+
name = nameOrStep.name
|
|
313
|
+
fn = nameOrStep.fn as unknown as (input: TI, ctx: ExecutionContext) => Promise<TO>
|
|
314
|
+
stepConfig = nameOrStep.config
|
|
315
|
+
durableStep = nameOrStep
|
|
316
|
+
} else {
|
|
317
|
+
name = nameOrStep
|
|
318
|
+
if (typeof configOrFn === 'function') {
|
|
319
|
+
fn = configOrFn
|
|
320
|
+
} else {
|
|
321
|
+
stepConfig = configOrFn
|
|
322
|
+
fn = maybeFn!
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Check for duplicate step names
|
|
327
|
+
if (this._steps.has(name)) {
|
|
328
|
+
throw new Error(`Step "${name}" already exists`)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const stepDef: StepDefinition<TI, TO> = {
|
|
332
|
+
name,
|
|
333
|
+
fn,
|
|
334
|
+
...(stepConfig !== undefined && { config: stepConfig }),
|
|
335
|
+
dependencies: [],
|
|
336
|
+
durableStep:
|
|
337
|
+
durableStep ??
|
|
338
|
+
new DurableStep(
|
|
339
|
+
name,
|
|
340
|
+
stepConfig ?? {},
|
|
341
|
+
fn as unknown as (input: TI, ctx?: StepContext) => Promise<TO>
|
|
342
|
+
),
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
this._steps.set(name, stepDef as StepDefinition)
|
|
346
|
+
this._currentStep = name
|
|
347
|
+
|
|
348
|
+
return this
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
dependsOn(stepOrSteps: string | string[], options?: DependencyOptions): StepChain
|
|
352
|
+
dependsOn(...steps: (string | string[] | DependencyOptions | undefined)[]): StepChain {
|
|
353
|
+
if (!this._currentStep) {
|
|
354
|
+
throw new Error('No current step to add dependencies to')
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const currentStepDef = this._steps.get(this._currentStep)
|
|
358
|
+
if (!currentStepDef) {
|
|
359
|
+
throw new Error(`Step "${this._currentStep}" not found`)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Parse arguments
|
|
363
|
+
let dependencyNames: string[] = []
|
|
364
|
+
let dependencyOptions: DependencyOptions | undefined
|
|
365
|
+
|
|
366
|
+
for (const arg of steps) {
|
|
367
|
+
if (arg === undefined) continue
|
|
368
|
+
if (typeof arg === 'string') {
|
|
369
|
+
dependencyNames.push(arg)
|
|
370
|
+
} else if (Array.isArray(arg)) {
|
|
371
|
+
dependencyNames.push(...arg)
|
|
372
|
+
} else if (typeof arg === 'object' && ('type' in arg || 'timeout' in arg)) {
|
|
373
|
+
dependencyOptions = arg as DependencyOptions
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Add dependencies
|
|
378
|
+
for (const depName of dependencyNames) {
|
|
379
|
+
currentStepDef.dependencies.push({
|
|
380
|
+
name: depName,
|
|
381
|
+
...(dependencyOptions !== undefined && { options: dependencyOptions }),
|
|
382
|
+
})
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return this
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
timeout(duration: string | number): StepChain {
|
|
389
|
+
if (!this._currentStep) {
|
|
390
|
+
throw new Error('No current step to set timeout for')
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const stepDef = this._steps.get(this._currentStep)
|
|
394
|
+
if (stepDef) {
|
|
395
|
+
stepDef.config = { ...stepDef.config, timeout: duration }
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return this
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
retries(config: RetryConfig): StepChain {
|
|
402
|
+
if (!this._currentStep) {
|
|
403
|
+
throw new Error('No current step to set retries for')
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const stepDef = this._steps.get(this._currentStep)
|
|
407
|
+
if (stepDef) {
|
|
408
|
+
stepDef.config = { ...stepDef.config, retries: config }
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return this
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
onError(handler: (error: Error, ctx: ExecutionContext) => unknown | Promise<unknown>): StepChain {
|
|
415
|
+
if (!this._currentStep) {
|
|
416
|
+
throw new Error('No current step to set error handler for')
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const stepDef = this._steps.get(this._currentStep)
|
|
420
|
+
if (stepDef) {
|
|
421
|
+
stepDef.errorHandler = handler
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return this
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// ==================== Event Trigger Methods ====================
|
|
428
|
+
|
|
429
|
+
on<T = unknown>(event: string): EventTriggerChain<T> {
|
|
430
|
+
// Validate event format (Noun.event)
|
|
431
|
+
const eventRegex = /^[A-Z][a-zA-Z]*\.[a-z][a-zA-Z]*$/
|
|
432
|
+
if (!eventRegex.test(event)) {
|
|
433
|
+
// Store invalid event - will throw when .do() is called
|
|
434
|
+
this._currentEvent = event
|
|
435
|
+
this._currentFilter = null
|
|
436
|
+
return this as unknown as EventTriggerChain<T>
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
this._currentEvent = event
|
|
440
|
+
this._currentFilter = null
|
|
441
|
+
return this as unknown as EventTriggerChain<T>
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
filter<T = unknown>(predicate: (event: T) => boolean): EventTriggerChain<T> {
|
|
445
|
+
this._currentFilter = predicate as (event: unknown) => boolean
|
|
446
|
+
return this as unknown as EventTriggerChain<T>
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// ==================== Schedule Trigger Methods ====================
|
|
450
|
+
|
|
451
|
+
every(scheduleOrValue: string | number): ScheduleTriggerChain & NumericScheduleChain {
|
|
452
|
+
if (typeof scheduleOrValue === 'number') {
|
|
453
|
+
this._currentScheduleValue = scheduleOrValue
|
|
454
|
+
this._currentSchedule = null
|
|
455
|
+
} else {
|
|
456
|
+
this._currentSchedule = scheduleOrValue
|
|
457
|
+
this._currentScheduleValue = null
|
|
458
|
+
}
|
|
459
|
+
this._currentTime = null
|
|
460
|
+
this._currentTimezone = null
|
|
461
|
+
return this as unknown as ScheduleTriggerChain & NumericScheduleChain
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
minutes(): ScheduleTriggerChain {
|
|
465
|
+
if (this._currentScheduleValue !== null) {
|
|
466
|
+
this._currentSchedule = `${this._currentScheduleValue} minutes`
|
|
467
|
+
}
|
|
468
|
+
return this
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
hours(): ScheduleTriggerChain {
|
|
472
|
+
if (this._currentScheduleValue !== null) {
|
|
473
|
+
this._currentSchedule = `${this._currentScheduleValue} hours`
|
|
474
|
+
}
|
|
475
|
+
return this
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
days(): ScheduleTriggerChain {
|
|
479
|
+
if (this._currentScheduleValue !== null) {
|
|
480
|
+
this._currentSchedule = `${this._currentScheduleValue} days`
|
|
481
|
+
}
|
|
482
|
+
return this
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
seconds(): ScheduleTriggerChain {
|
|
486
|
+
if (this._currentScheduleValue !== null) {
|
|
487
|
+
this._currentSchedule = `${this._currentScheduleValue} seconds`
|
|
488
|
+
}
|
|
489
|
+
return this
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
at(time: string): ScheduleTriggerChain {
|
|
493
|
+
this._currentTime = time
|
|
494
|
+
return this
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
timezone(tz: string): ScheduleTriggerChain {
|
|
498
|
+
this._currentTimezone = tz
|
|
499
|
+
return this
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Execute a step when event fires or on schedule
|
|
504
|
+
*/
|
|
505
|
+
do(stepNameOrFn: string | ((event?: unknown) => Promise<unknown>)): StepChain {
|
|
506
|
+
// Handle event trigger
|
|
507
|
+
if (this._currentEvent !== null) {
|
|
508
|
+
const eventRegex = /^[A-Z][a-zA-Z]*\.[a-z][a-zA-Z]*$/
|
|
509
|
+
if (!eventRegex.test(this._currentEvent)) {
|
|
510
|
+
throw new Error(
|
|
511
|
+
`Invalid event name format: "${this._currentEvent}". Expected Noun.event format`
|
|
512
|
+
)
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
let stepName: string
|
|
516
|
+
if (typeof stepNameOrFn === 'function') {
|
|
517
|
+
// Create implicit step
|
|
518
|
+
stepName = `__implicit_${this._currentEvent.replace('.', '_')}_${++this
|
|
519
|
+
._implicitStepCounter}`
|
|
520
|
+
this._steps.set(stepName, {
|
|
521
|
+
name: stepName,
|
|
522
|
+
fn: stepNameOrFn as (input: unknown, ctx: ExecutionContext) => Promise<unknown>,
|
|
523
|
+
dependencies: [],
|
|
524
|
+
})
|
|
525
|
+
} else {
|
|
526
|
+
stepName = stepNameOrFn
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
this._eventTriggers.push({
|
|
530
|
+
event: this._currentEvent,
|
|
531
|
+
stepName,
|
|
532
|
+
...(this._currentFilter !== null && { filter: this._currentFilter }),
|
|
533
|
+
})
|
|
534
|
+
|
|
535
|
+
this._currentEvent = null
|
|
536
|
+
this._currentFilter = null
|
|
537
|
+
return this
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Handle schedule trigger
|
|
541
|
+
if (this._currentSchedule !== null) {
|
|
542
|
+
let stepName: string
|
|
543
|
+
if (typeof stepNameOrFn === 'function') {
|
|
544
|
+
// Create implicit step
|
|
545
|
+
stepName = `__implicit_schedule_${++this._implicitStepCounter}`
|
|
546
|
+
this._steps.set(stepName, {
|
|
547
|
+
name: stepName,
|
|
548
|
+
fn: stepNameOrFn as (input: unknown, ctx: ExecutionContext) => Promise<unknown>,
|
|
549
|
+
dependencies: [],
|
|
550
|
+
})
|
|
551
|
+
} else {
|
|
552
|
+
stepName = stepNameOrFn
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
this._scheduleTriggers.push({
|
|
556
|
+
schedule: this._currentSchedule,
|
|
557
|
+
...(this._currentScheduleValue !== null && { value: this._currentScheduleValue }),
|
|
558
|
+
...(this._currentTime !== null && { time: this._currentTime }),
|
|
559
|
+
...(this._currentTimezone !== null && { timezone: this._currentTimezone }),
|
|
560
|
+
stepName,
|
|
561
|
+
})
|
|
562
|
+
|
|
563
|
+
this._currentSchedule = null
|
|
564
|
+
this._currentScheduleValue = null
|
|
565
|
+
this._currentTime = null
|
|
566
|
+
this._currentTimezone = null
|
|
567
|
+
return this
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
throw new Error('No event or schedule to trigger')
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// ==================== Build Method ====================
|
|
574
|
+
|
|
575
|
+
build(): BuiltWorkflow {
|
|
576
|
+
// Validate event triggers reference existing steps
|
|
577
|
+
for (const trigger of this._eventTriggers) {
|
|
578
|
+
if (!this._steps.has(trigger.stepName)) {
|
|
579
|
+
throw new Error(`Step "${trigger.stepName}" not found for event trigger`)
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// Validate schedule triggers reference existing steps
|
|
584
|
+
for (const trigger of this._scheduleTriggers) {
|
|
585
|
+
if (!this._steps.has(trigger.stepName)) {
|
|
586
|
+
throw new Error(`Step "${trigger.stepName}" not found for schedule trigger`)
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Build dependency graph
|
|
591
|
+
const dependencyGraph = new Map<string, string[]>()
|
|
592
|
+
for (const [name, stepDef] of this._steps) {
|
|
593
|
+
const deps = stepDef.dependencies.map((d) => d.name)
|
|
594
|
+
dependencyGraph.set(name, deps)
|
|
595
|
+
|
|
596
|
+
// Validate dependencies exist
|
|
597
|
+
for (const dep of deps) {
|
|
598
|
+
if (!this._steps.has(dep)) {
|
|
599
|
+
throw new Error(`Dependency "${dep}" not found for step "${name}"`)
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Check for circular dependencies
|
|
605
|
+
this.detectCircularDependencies(dependencyGraph)
|
|
606
|
+
|
|
607
|
+
// Compute topological sort
|
|
608
|
+
const executionOrder = this.topologicalSort(dependencyGraph)
|
|
609
|
+
|
|
610
|
+
// Create immutable copies
|
|
611
|
+
const steps = Array.from(this._steps.values()).map((s) => ({ ...s }))
|
|
612
|
+
const eventTriggers = [...this._eventTriggers]
|
|
613
|
+
const scheduleTriggers = [...this._scheduleTriggers]
|
|
614
|
+
|
|
615
|
+
const workflow: BuiltWorkflow = {
|
|
616
|
+
name: this._name,
|
|
617
|
+
steps: Object.freeze(steps),
|
|
618
|
+
triggers: Object.freeze({
|
|
619
|
+
events: Object.freeze(eventTriggers),
|
|
620
|
+
schedules: Object.freeze(scheduleTriggers),
|
|
621
|
+
}),
|
|
622
|
+
dependencyGraph: new Map(dependencyGraph),
|
|
623
|
+
executionOrder,
|
|
624
|
+
...(this._config !== undefined && { metadata: this._config }),
|
|
625
|
+
isCloudflareCompatible: true,
|
|
626
|
+
execute: async (input?: unknown) => {
|
|
627
|
+
return this.executeWorkflow(steps, dependencyGraph, executionOrder, input)
|
|
628
|
+
},
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return workflow
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// ==================== Private Helpers ====================
|
|
635
|
+
|
|
636
|
+
private detectCircularDependencies(graph: Map<string, string[]>): void {
|
|
637
|
+
const visited = new Set<string>()
|
|
638
|
+
const recursionStack = new Set<string>()
|
|
639
|
+
|
|
640
|
+
const dfs = (node: string, path: string[]): void => {
|
|
641
|
+
visited.add(node)
|
|
642
|
+
recursionStack.add(node)
|
|
643
|
+
|
|
644
|
+
const deps = graph.get(node) || []
|
|
645
|
+
for (const dep of deps) {
|
|
646
|
+
if (!visited.has(dep)) {
|
|
647
|
+
dfs(dep, [...path, node])
|
|
648
|
+
} else if (recursionStack.has(dep)) {
|
|
649
|
+
// Check if it's self-referential
|
|
650
|
+
if (dep === node) {
|
|
651
|
+
throw new Error(
|
|
652
|
+
`Circular dependency detected: step "${node}" depends on itself (self-referential)`
|
|
653
|
+
)
|
|
654
|
+
}
|
|
655
|
+
throw new Error(`Circular dependency detected: ${[...path, node, dep].join(' -> ')}`)
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
recursionStack.delete(node)
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
for (const node of graph.keys()) {
|
|
663
|
+
if (!visited.has(node)) {
|
|
664
|
+
dfs(node, [])
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
private topologicalSort(graph: Map<string, string[]>): string[] {
|
|
670
|
+
const result: string[] = []
|
|
671
|
+
const visited = new Set<string>()
|
|
672
|
+
const temp = new Set<string>()
|
|
673
|
+
|
|
674
|
+
const visit = (node: string): void => {
|
|
675
|
+
if (temp.has(node)) {
|
|
676
|
+
throw new Error('Circular dependency detected')
|
|
677
|
+
}
|
|
678
|
+
if (visited.has(node)) return
|
|
679
|
+
|
|
680
|
+
temp.add(node)
|
|
681
|
+
|
|
682
|
+
const deps = graph.get(node) || []
|
|
683
|
+
for (const dep of deps) {
|
|
684
|
+
visit(dep)
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
temp.delete(node)
|
|
688
|
+
visited.add(node)
|
|
689
|
+
result.push(node)
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
for (const node of graph.keys()) {
|
|
693
|
+
visit(node)
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
return result
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
private async executeWorkflow(
|
|
700
|
+
steps: StepDefinition[],
|
|
701
|
+
dependencyGraph: Map<string, string[]>,
|
|
702
|
+
executionOrder: string[],
|
|
703
|
+
input?: unknown
|
|
704
|
+
): Promise<Record<string, unknown>> {
|
|
705
|
+
const results: Record<string, unknown> = {}
|
|
706
|
+
const stepMap = new Map(steps.map((s) => [s.name, s]))
|
|
707
|
+
|
|
708
|
+
const ctx: ExecutionContext = {
|
|
709
|
+
input,
|
|
710
|
+
results,
|
|
711
|
+
getStepResult: <T = unknown>(stepName: string): T => {
|
|
712
|
+
if (!(stepName in results)) {
|
|
713
|
+
throw new Error(`Step "${stepName}" has not been executed yet`)
|
|
714
|
+
}
|
|
715
|
+
return results[stepName] as T
|
|
716
|
+
},
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// Track which steps are completed
|
|
720
|
+
const completed = new Set<string>()
|
|
721
|
+
const inProgress = new Set<string>()
|
|
722
|
+
const pending = new Set(executionOrder)
|
|
723
|
+
|
|
724
|
+
// Execute steps respecting dependencies
|
|
725
|
+
while (pending.size > 0) {
|
|
726
|
+
// Find steps that can be executed (all dependencies satisfied)
|
|
727
|
+
const ready: string[] = []
|
|
728
|
+
for (const stepName of pending) {
|
|
729
|
+
const deps = dependencyGraph.get(stepName) || []
|
|
730
|
+
if (deps.every((d) => completed.has(d)) && !inProgress.has(stepName)) {
|
|
731
|
+
ready.push(stepName)
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
if (ready.length === 0 && pending.size > 0) {
|
|
736
|
+
throw new Error('Deadlock detected: no steps can be executed')
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// Execute ready steps in parallel
|
|
740
|
+
const execPromises = ready.map(async (stepName) => {
|
|
741
|
+
inProgress.add(stepName)
|
|
742
|
+
const step = stepMap.get(stepName)!
|
|
743
|
+
|
|
744
|
+
try {
|
|
745
|
+
const result = await step.fn(input, ctx)
|
|
746
|
+
results[stepName] = result
|
|
747
|
+
} catch (error) {
|
|
748
|
+
if (step.errorHandler) {
|
|
749
|
+
results[stepName] = await step.errorHandler(error as Error, ctx)
|
|
750
|
+
} else {
|
|
751
|
+
throw error
|
|
752
|
+
}
|
|
753
|
+
} finally {
|
|
754
|
+
inProgress.delete(stepName)
|
|
755
|
+
completed.add(stepName)
|
|
756
|
+
pending.delete(stepName)
|
|
757
|
+
}
|
|
758
|
+
})
|
|
759
|
+
|
|
760
|
+
await Promise.all(execPromises)
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
return results
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Register workflow with a service
|
|
768
|
+
*/
|
|
769
|
+
registerWorkflow(workflow: BuiltWorkflow): { id: string } {
|
|
770
|
+
// This is handled by WorkflowServiceCore
|
|
771
|
+
return { id: workflow.name }
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* WorkflowBuilder - Static factory for creating workflow definitions
|
|
777
|
+
*
|
|
778
|
+
* @example
|
|
779
|
+
* ```ts
|
|
780
|
+
* const workflow = WorkflowBuilder.create('order-process')
|
|
781
|
+
* .step('validate', validateOrder)
|
|
782
|
+
* .step('charge', chargePayment).dependsOn('validate')
|
|
783
|
+
* .on('Order.placed').do('validate')
|
|
784
|
+
* .build()
|
|
785
|
+
* ```
|
|
786
|
+
*/
|
|
787
|
+
export class WorkflowBuilder {
|
|
788
|
+
/** Workflow name */
|
|
789
|
+
readonly name: string
|
|
790
|
+
/** Workflow configuration */
|
|
791
|
+
readonly config?: WorkflowBuilderConfig
|
|
792
|
+
|
|
793
|
+
private impl: WorkflowBuilderImpl
|
|
794
|
+
|
|
795
|
+
private constructor(name: string, config?: WorkflowBuilderConfig) {
|
|
796
|
+
this.name = name
|
|
797
|
+
if (config !== undefined) {
|
|
798
|
+
this.config = config
|
|
799
|
+
}
|
|
800
|
+
this.impl = new WorkflowBuilderImpl(name, config)
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/**
|
|
804
|
+
* Create a new workflow builder
|
|
805
|
+
*
|
|
806
|
+
* @param name - Unique workflow name
|
|
807
|
+
* @param config - Optional workflow configuration
|
|
808
|
+
* @returns WorkflowBuilder instance
|
|
809
|
+
*/
|
|
810
|
+
static create(name: string, config?: WorkflowBuilderConfig): WorkflowBuilder {
|
|
811
|
+
return new WorkflowBuilder(name, config)
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* Add a step to the workflow
|
|
816
|
+
*/
|
|
817
|
+
step<TI = unknown, TO = unknown>(
|
|
818
|
+
name: string,
|
|
819
|
+
fn: (input: TI, ctx: ExecutionContext) => Promise<TO>
|
|
820
|
+
): StepChain
|
|
821
|
+
step<TI = unknown, TO = unknown>(
|
|
822
|
+
name: string,
|
|
823
|
+
config: StepConfig,
|
|
824
|
+
fn: (input: TI, ctx: ExecutionContext) => Promise<TO>
|
|
825
|
+
): StepChain
|
|
826
|
+
step<TI = unknown, TO = unknown>(durableStep: DurableStep<TI, TO>): StepChain
|
|
827
|
+
step<TI = unknown, TO = unknown>(
|
|
828
|
+
nameOrStep: string | DurableStep<TI, TO>,
|
|
829
|
+
configOrFn?: StepConfig | ((input: TI, ctx: ExecutionContext) => Promise<TO>),
|
|
830
|
+
maybeFn?: (input: TI, ctx: ExecutionContext) => Promise<TO>
|
|
831
|
+
): StepChain {
|
|
832
|
+
return this.impl.step(nameOrStep, configOrFn, maybeFn)
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/**
|
|
836
|
+
* Register an event trigger
|
|
837
|
+
*/
|
|
838
|
+
on<T = unknown>(event: string): EventTriggerChain<T> {
|
|
839
|
+
return this.impl.on<T>(event)
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/**
|
|
843
|
+
* Register a schedule trigger
|
|
844
|
+
*/
|
|
845
|
+
every(schedule: string): ScheduleTriggerChain
|
|
846
|
+
every(value: number): NumericScheduleChain
|
|
847
|
+
every(scheduleOrValue: string | number): ScheduleTriggerChain & NumericScheduleChain {
|
|
848
|
+
return this.impl.every(scheduleOrValue)
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/**
|
|
852
|
+
* Declare dependencies for the most recently added step
|
|
853
|
+
* This allows for: builder.step('a', fn).step('b', fn).dependsOn('a')
|
|
854
|
+
*/
|
|
855
|
+
dependsOn(step: string, options?: DependencyOptions): WorkflowBuilder
|
|
856
|
+
dependsOn(steps: string[]): WorkflowBuilder
|
|
857
|
+
dependsOn(stepOrSteps: string | string[], options?: DependencyOptions): WorkflowBuilder {
|
|
858
|
+
this.impl.dependsOn(stepOrSteps, options)
|
|
859
|
+
return this
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/**
|
|
863
|
+
* Build the workflow definition
|
|
864
|
+
*/
|
|
865
|
+
build(): BuiltWorkflow {
|
|
866
|
+
return this.impl.build()
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// Re-export for convenience
|
|
871
|
+
export { DurableStep, type StepConfig, type RetryConfig }
|