ai-workflows 2.1.1 → 2.1.3
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/.turbo/turbo-test.log +165 -3
- package/CHANGELOG.md +10 -1
- package/LICENSE +21 -0
- package/README.md +303 -184
- package/dist/barrier.d.ts +153 -0
- package/dist/barrier.d.ts.map +1 -0
- package/dist/barrier.js +339 -0
- package/dist/barrier.js.map +1 -0
- package/dist/cascade-context.d.ts +149 -0
- package/dist/cascade-context.d.ts.map +1 -0
- package/dist/cascade-context.js +324 -0
- package/dist/cascade-context.js.map +1 -0
- package/dist/cascade-executor.d.ts +196 -0
- package/dist/cascade-executor.d.ts.map +1 -0
- package/dist/cascade-executor.js +384 -0
- package/dist/cascade-executor.js.map +1 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +4 -1
- package/dist/context.js.map +1 -1
- package/dist/dependency-graph.d.ts +157 -0
- package/dist/dependency-graph.d.ts.map +1 -0
- package/dist/dependency-graph.js +382 -0
- package/dist/dependency-graph.js.map +1 -0
- package/dist/every.d.ts +31 -2
- package/dist/every.d.ts.map +1 -1
- package/dist/every.js +63 -32
- package/dist/every.js.map +1 -1
- package/dist/graph/index.d.ts +8 -0
- package/dist/graph/index.d.ts.map +1 -0
- package/dist/graph/index.js +8 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/topological-sort.d.ts +121 -0
- package/dist/graph/topological-sort.d.ts.map +1 -0
- package/dist/graph/topological-sort.js +292 -0
- package/dist/graph/topological-sort.js.map +1 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/on.d.ts +35 -10
- package/dist/on.d.ts.map +1 -1
- package/dist/on.js +52 -18
- package/dist/on.js.map +1 -1
- package/dist/timer-registry.d.ts +52 -0
- package/dist/timer-registry.d.ts.map +1 -0
- package/dist/timer-registry.js +120 -0
- package/dist/timer-registry.js.map +1 -0
- package/dist/types.d.ts +88 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +17 -1
- package/dist/types.js.map +1 -1
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.js +15 -11
- package/dist/workflow.js.map +1 -1
- package/package.json +11 -11
- package/src/barrier.ts +466 -0
- package/src/cascade-context.ts +488 -0
- package/src/cascade-executor.ts +587 -0
- package/src/context.ts +12 -7
- package/src/dependency-graph.ts +518 -0
- package/src/every.ts +104 -35
- package/src/graph/index.ts +19 -0
- package/src/graph/topological-sort.ts +414 -0
- package/src/index.ts +78 -0
- package/src/on.ts +81 -25
- package/src/timer-registry.ts +145 -0
- package/src/types.ts +121 -0
- package/src/workflow.ts +23 -16
- package/test/barrier-join.test.ts +434 -0
- package/test/barrier-unhandled-rejections.test.ts +359 -0
- package/test/cascade-context.test.ts +390 -0
- package/test/cascade-executor.test.ts +859 -0
- package/test/dependency-graph.test.ts +512 -0
- package/test/graph/topological-sort.test.ts +586 -0
- package/test/schedule-timer-cleanup.test.ts +344 -0
- package/test/send-race-conditions.test.ts +410 -0
- package/test/type-safety-every.test.ts +303 -0
package/README.md
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
# ai-workflows
|
|
2
2
|
|
|
3
|
-
Event-driven workflows
|
|
3
|
+
**Event-driven AI workflows shouldn't require a PhD in distributed systems.**
|
|
4
|
+
|
|
5
|
+
You have business logic that needs to react to events, run on schedules, and coordinate parallel tasks. Traditional workflow engines make you wade through XML configs, learn proprietary DSLs, and debug mysterious state machines. You just want to write `$.on.Order.placed(handler)` and have it work.
|
|
4
6
|
|
|
5
7
|
```typescript
|
|
6
8
|
import { Workflow } from 'ai-workflows'
|
|
7
9
|
|
|
8
10
|
const workflow = Workflow($ => {
|
|
9
11
|
$.on.Customer.created(async (customer, $) => {
|
|
10
|
-
$.log('New customer:', customer.name)
|
|
11
12
|
await $.send('Email.welcome', { to: customer.email })
|
|
12
13
|
})
|
|
13
14
|
|
|
@@ -17,214 +18,351 @@ const workflow = Workflow($ => {
|
|
|
17
18
|
})
|
|
18
19
|
|
|
19
20
|
await workflow.start()
|
|
20
|
-
await workflow.send('Customer.created', { name: 'John', email: 'john@example.com' })
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
That's it. No YAML. No state machine diagrams. Just JavaScript.
|
|
24
|
+
|
|
23
25
|
## Installation
|
|
24
26
|
|
|
25
27
|
```bash
|
|
26
|
-
|
|
28
|
+
npm install ai-workflows
|
|
27
29
|
```
|
|
28
30
|
|
|
29
|
-
##
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
### Event Handlers
|
|
30
34
|
|
|
31
|
-
|
|
35
|
+
React to events with the `$.on` pattern. Events follow `Noun.verb` naming:
|
|
32
36
|
|
|
33
37
|
```typescript
|
|
34
38
|
Workflow($ => {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
await $.send('
|
|
38
|
-
$.
|
|
39
|
+
$.on.Order.placed(async (order, $) => {
|
|
40
|
+
$.log('Processing order', order.id)
|
|
41
|
+
await $.send('Inventory.reserve', { items: order.items })
|
|
42
|
+
await $.send('Payment.charge', { amount: order.total })
|
|
39
43
|
})
|
|
40
44
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
$.on.Payment.completed(async (payment, $) => {
|
|
46
|
+
await $.send('Order.fulfill', { orderId: payment.orderId })
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
$.on.Payment.failed(async (payment, $) => {
|
|
50
|
+
await $.send('Customer.notify', {
|
|
51
|
+
message: 'Payment failed',
|
|
52
|
+
orderId: payment.orderId
|
|
53
|
+
})
|
|
44
54
|
})
|
|
45
55
|
})
|
|
46
56
|
```
|
|
47
57
|
|
|
48
|
-
|
|
58
|
+
### Scheduled Tasks
|
|
49
59
|
|
|
50
|
-
|
|
60
|
+
Natural scheduling with `$.every`:
|
|
51
61
|
|
|
52
62
|
```typescript
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
$.
|
|
56
|
-
$.
|
|
63
|
+
Workflow($ => {
|
|
64
|
+
// Simple intervals
|
|
65
|
+
$.every.hour(async ($) => {
|
|
66
|
+
$.log('Hourly health check')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
// Day + time combinations
|
|
70
|
+
$.every.Monday.at9am(async ($) => {
|
|
71
|
+
const report = await $.do('Analytics.weeklyReport', {})
|
|
72
|
+
await $.send('Slack.post', { channel: '#metrics', report })
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
$.every.weekday.at8am(async ($) => {
|
|
76
|
+
$.log('Good morning! Time to standup.')
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
// Precise intervals
|
|
80
|
+
$.every.minutes(30)(async ($) => {
|
|
81
|
+
await $.send('Cache.refresh', {})
|
|
82
|
+
})
|
|
83
|
+
})
|
|
57
84
|
```
|
|
58
85
|
|
|
59
|
-
|
|
86
|
+
**Available schedules:**
|
|
87
|
+
|
|
88
|
+
| Intervals | Days | Times |
|
|
89
|
+
|-----------|------|-------|
|
|
90
|
+
| `$.every.second` | `$.every.Monday` | `.at6am` `.at7am` `.at8am` |
|
|
91
|
+
| `$.every.minute` | `$.every.Tuesday` | `.at9am` `.at10am` `.at11am` |
|
|
92
|
+
| `$.every.hour` | `$.every.Wednesday` | `.at12pm` `.atnoon` |
|
|
93
|
+
| `$.every.day` | `$.every.Thursday` | `.at1pm` `.at2pm` `.at3pm` |
|
|
94
|
+
| `$.every.week` | `$.every.Friday` | `.at4pm` `.at5pm` `.at6pm` |
|
|
95
|
+
| `$.every.month` | `$.every.Saturday` | `.at7pm` `.at8pm` `.at9pm` |
|
|
96
|
+
| `$.every.minutes(n)` | `$.every.Sunday` | `.atmidnight` |
|
|
97
|
+
| `$.every.hours(n)` | `$.every.weekday` | |
|
|
98
|
+
| | `$.every.weekend` | |
|
|
99
|
+
|
|
100
|
+
## The Cascade Pattern
|
|
101
|
+
|
|
102
|
+
Not every problem can be solved with code. Some need AI. Some need human judgment. The cascade executor tries each tier in sequence, escalating only when needed:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
Code -> Generative AI -> Agentic AI -> Human
|
|
106
|
+
```
|
|
60
107
|
|
|
61
108
|
```typescript
|
|
62
|
-
|
|
63
|
-
|
|
109
|
+
import { CascadeExecutor } from 'ai-workflows'
|
|
110
|
+
|
|
111
|
+
const processRefund = new CascadeExecutor({
|
|
112
|
+
cascadeName: 'refund-processor',
|
|
113
|
+
|
|
114
|
+
tiers: {
|
|
115
|
+
// Tier 1: Deterministic rules (fastest, cheapest)
|
|
116
|
+
code: {
|
|
117
|
+
name: 'rule-based-refund',
|
|
118
|
+
execute: async (request) => {
|
|
119
|
+
if (request.amount < 50 && request.reason === 'defective') {
|
|
120
|
+
return { approved: true, method: 'original-payment' }
|
|
121
|
+
}
|
|
122
|
+
throw new Error('Rules inconclusive')
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
// Tier 2: AI analysis for complex cases
|
|
127
|
+
generative: {
|
|
128
|
+
name: 'ai-refund-analysis',
|
|
129
|
+
execute: async (request, ctx) => {
|
|
130
|
+
const analysis = await analyzeRefundRequest(request)
|
|
131
|
+
if (analysis.confidence > 0.9) {
|
|
132
|
+
return { approved: analysis.shouldApprove, reason: analysis.explanation }
|
|
133
|
+
}
|
|
134
|
+
throw new Error('Confidence too low')
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
// Tier 3: Agent with tool access
|
|
139
|
+
agentic: {
|
|
140
|
+
name: 'refund-agent',
|
|
141
|
+
execute: async (request, ctx) => {
|
|
142
|
+
return await refundAgent.process(request)
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
// Tier 4: Human review for edge cases
|
|
147
|
+
human: {
|
|
148
|
+
name: 'human-review',
|
|
149
|
+
execute: async (request) => {
|
|
150
|
+
return await createHumanTask({
|
|
151
|
+
type: 'refund-review',
|
|
152
|
+
data: request,
|
|
153
|
+
assignTo: 'support-team'
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
},
|
|
64
158
|
|
|
65
|
-
//
|
|
66
|
-
|
|
159
|
+
// Default timeouts per tier
|
|
160
|
+
useDefaultTimeouts: true, // code: 5s, generative: 30s, agentic: 5m, human: 24h
|
|
161
|
+
})
|
|
67
162
|
|
|
68
|
-
|
|
69
|
-
|
|
163
|
+
const result = await processRefund.execute(refundRequest)
|
|
164
|
+
console.log(`Resolved by ${result.tier} tier in ${result.metrics.totalDuration}ms`)
|
|
70
165
|
```
|
|
71
166
|
|
|
72
|
-
|
|
167
|
+
### Cascade Features
|
|
168
|
+
|
|
169
|
+
- **Automatic escalation** - Failed tiers escalate to the next level
|
|
170
|
+
- **Tier timeouts** - Each tier has configurable time limits
|
|
171
|
+
- **Retry support** - Configure retries with exponential backoff per tier
|
|
172
|
+
- **Skip conditions** - Skip tiers based on input characteristics
|
|
173
|
+
- **5W+H audit trail** - Full event log: who, what, when, where, why, how
|
|
73
174
|
|
|
74
|
-
|
|
175
|
+
## Dependency Graphs
|
|
176
|
+
|
|
177
|
+
For complex workflows with interdependent steps, use the dependency graph to ensure correct execution order:
|
|
75
178
|
|
|
76
179
|
```typescript
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// Natural language (requires AI converter)
|
|
100
|
-
$.every('first Monday of the month at 9am', handler)
|
|
101
|
-
$.every('every 15 minutes during business hours', handler)
|
|
180
|
+
import { DependencyGraph, getExecutionLevels } from 'ai-workflows'
|
|
181
|
+
|
|
182
|
+
const graph = new DependencyGraph()
|
|
183
|
+
|
|
184
|
+
// Steps with no dependencies run first (level 0)
|
|
185
|
+
graph.addNode('fetch-user')
|
|
186
|
+
graph.addNode('fetch-products')
|
|
187
|
+
|
|
188
|
+
// Dependent steps run after their dependencies complete
|
|
189
|
+
graph.addNode('validate-cart', { dependsOn: ['fetch-user', 'fetch-products'] })
|
|
190
|
+
graph.addNode('calculate-shipping', { dependsOn: 'fetch-products' })
|
|
191
|
+
graph.addNode('apply-discounts', { dependsOn: 'validate-cart' })
|
|
192
|
+
graph.addNode('process-payment', { dependsOn: ['apply-discounts', 'calculate-shipping'] })
|
|
193
|
+
|
|
194
|
+
// Automatic cycle detection
|
|
195
|
+
try {
|
|
196
|
+
graph.addNode('bad-step', { dependsOn: 'process-payment' })
|
|
197
|
+
graph.addEdge('bad-step', 'fetch-user') // Would create a cycle!
|
|
198
|
+
} catch (e) {
|
|
199
|
+
console.log('Caught circular dependency:', e.cyclePath)
|
|
200
|
+
}
|
|
102
201
|
```
|
|
103
202
|
|
|
104
|
-
###
|
|
203
|
+
### Topological Sort
|
|
204
|
+
|
|
205
|
+
Execute steps in dependency order:
|
|
105
206
|
|
|
106
207
|
```typescript
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
208
|
+
import { topologicalSort, getExecutionLevels } from 'ai-workflows'
|
|
209
|
+
|
|
210
|
+
const steps = [
|
|
211
|
+
{ id: 'A', dependencies: [] },
|
|
212
|
+
{ id: 'B', dependencies: ['A'] },
|
|
213
|
+
{ id: 'C', dependencies: ['A'] },
|
|
214
|
+
{ id: 'D', dependencies: ['B', 'C'] },
|
|
215
|
+
]
|
|
216
|
+
|
|
217
|
+
// Linear execution order
|
|
218
|
+
const { order } = topologicalSort(steps)
|
|
219
|
+
// => ['A', 'B', 'C', 'D']
|
|
220
|
+
|
|
221
|
+
// Parallel execution groups
|
|
222
|
+
const levels = getExecutionLevels(steps)
|
|
223
|
+
// => [
|
|
224
|
+
// { level: 0, nodes: ['A'] }, // Run first
|
|
225
|
+
// { level: 1, nodes: ['B', 'C'] }, // Run in parallel
|
|
226
|
+
// { level: 2, nodes: ['D'] } // Run after B and C complete
|
|
227
|
+
// ]
|
|
110
228
|
```
|
|
111
229
|
|
|
112
|
-
##
|
|
230
|
+
## Barriers and Joins
|
|
113
231
|
|
|
114
|
-
|
|
232
|
+
Coordinate parallel operations with barrier semantics:
|
|
115
233
|
|
|
116
234
|
```typescript
|
|
117
|
-
import {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
235
|
+
import { waitForAll, waitForAny, Barrier, withConcurrencyLimit } from 'ai-workflows'
|
|
236
|
+
|
|
237
|
+
// Wait for all parallel tasks
|
|
238
|
+
const results = await waitForAll([
|
|
239
|
+
fetchUserData(userId),
|
|
240
|
+
fetchOrderHistory(userId),
|
|
241
|
+
fetchRecommendations(userId),
|
|
242
|
+
], { timeout: 5000 })
|
|
243
|
+
|
|
244
|
+
// Wait for N of M (e.g., 2 of 3 replicas)
|
|
245
|
+
const { completed, pending } = await waitForAny(2, [
|
|
246
|
+
writeToReplica1(data),
|
|
247
|
+
writeToReplica2(data),
|
|
248
|
+
writeToReplica3(data),
|
|
249
|
+
])
|
|
250
|
+
|
|
251
|
+
// Manual barrier for complex coordination
|
|
252
|
+
const barrier = new Barrier(3, {
|
|
253
|
+
timeout: 10000,
|
|
254
|
+
onProgress: ({ arrived, expected, percentage }) => {
|
|
255
|
+
console.log(`${arrived}/${expected} (${percentage}%)`)
|
|
256
|
+
}
|
|
121
257
|
})
|
|
122
258
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
259
|
+
// In parallel handlers...
|
|
260
|
+
barrier.arrive(resultFromWorker1)
|
|
261
|
+
barrier.arrive(resultFromWorker2)
|
|
262
|
+
barrier.arrive(resultFromWorker3)
|
|
126
263
|
|
|
127
|
-
|
|
264
|
+
// Wait for all to arrive
|
|
265
|
+
const allResults = await barrier.wait()
|
|
128
266
|
```
|
|
129
267
|
|
|
130
|
-
|
|
268
|
+
### Concurrency Control
|
|
131
269
|
|
|
132
|
-
|
|
270
|
+
Limit parallel executions to prevent overwhelming downstream services:
|
|
133
271
|
|
|
134
272
|
```typescript
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
273
|
+
const urls = [/* 100 URLs */]
|
|
274
|
+
|
|
275
|
+
// Process 5 at a time
|
|
276
|
+
const results = await withConcurrencyLimit(
|
|
277
|
+
urls.map(url => () => fetch(url)),
|
|
278
|
+
5, // max concurrent
|
|
279
|
+
{ collectErrors: true } // don't fail fast
|
|
280
|
+
)
|
|
281
|
+
```
|
|
138
282
|
|
|
139
|
-
|
|
140
|
-
const valid = await $.do('Inventory.check', order.items)
|
|
141
|
-
if (!valid) {
|
|
142
|
-
await $.send('Order.cancelled', { orderId: order.id, reason: 'Out of stock' })
|
|
143
|
-
return
|
|
144
|
-
}
|
|
283
|
+
## Standalone API
|
|
145
284
|
|
|
146
|
-
|
|
147
|
-
const payment = await $.do('Payment.charge', {
|
|
148
|
-
amount: order.total,
|
|
149
|
-
customer: order.customerId,
|
|
150
|
-
})
|
|
285
|
+
Use `on`, `every`, and `send` for global registration outside of a workflow:
|
|
151
286
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
})
|
|
287
|
+
```typescript
|
|
288
|
+
import { on, every, send } from 'ai-workflows'
|
|
155
289
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
})
|
|
290
|
+
// Register handlers
|
|
291
|
+
on.Customer.created(async (customer, $) => {
|
|
292
|
+
await $.send('Email.welcome', { to: customer.email })
|
|
159
293
|
})
|
|
160
|
-
```
|
|
161
294
|
|
|
162
|
-
|
|
295
|
+
every.hour(async ($) => {
|
|
296
|
+
$.log('Background task running')
|
|
297
|
+
})
|
|
163
298
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
await $.send('Email.welcome', { to: customer.email })
|
|
168
|
-
await $.send('Slack.notify', { message: `New signup: ${customer.name}` })
|
|
169
|
-
})
|
|
299
|
+
// Emit events from anywhere
|
|
300
|
+
await send('Customer.created', { name: 'Alice', email: 'alice@example.com' })
|
|
301
|
+
```
|
|
170
302
|
|
|
171
|
-
|
|
172
|
-
await $.send('Email.upgradeConfirmation', { to: customer.email })
|
|
173
|
-
await $.send('Analytics.track', { event: 'upgrade', plan: customer.plan })
|
|
174
|
-
})
|
|
303
|
+
## Configuration
|
|
175
304
|
|
|
176
|
-
|
|
177
|
-
$.every.day.at9am(async ($) => {
|
|
178
|
-
const inactive = await $.do('Customer.findInactive', { days: 30 })
|
|
179
|
-
for (const customer of inactive) {
|
|
180
|
-
await $.send('Email.reengagement', { to: customer.email })
|
|
181
|
-
}
|
|
182
|
-
})
|
|
183
|
-
})
|
|
184
|
-
```
|
|
305
|
+
### Custom Cron Converter
|
|
185
306
|
|
|
186
|
-
|
|
307
|
+
Enable natural language scheduling with an AI-powered cron converter:
|
|
187
308
|
|
|
188
309
|
```typescript
|
|
189
|
-
|
|
190
|
-
$.every.Monday.at9am(async ($) => {
|
|
191
|
-
const report = await $.do('Analytics.weeklyReport', {})
|
|
192
|
-
await $.send('Email.report', {
|
|
193
|
-
to: 'team@company.com',
|
|
194
|
-
report,
|
|
195
|
-
})
|
|
196
|
-
})
|
|
310
|
+
import { setCronConverter } from 'ai-workflows'
|
|
197
311
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
312
|
+
setCronConverter(async (description) => {
|
|
313
|
+
// Use your AI service to convert natural language to cron
|
|
314
|
+
const response = await ai.complete(`Convert to cron: "${description}"`)
|
|
315
|
+
return response.cron
|
|
202
316
|
})
|
|
317
|
+
|
|
318
|
+
// Now you can use natural language
|
|
319
|
+
$.every('first Monday of the month at 9am', handler)
|
|
320
|
+
$.every('every 15 minutes during business hours', handler)
|
|
203
321
|
```
|
|
204
322
|
|
|
205
|
-
|
|
323
|
+
### Cascade Timeouts
|
|
206
324
|
|
|
207
|
-
|
|
325
|
+
Configure per-tier and total timeouts:
|
|
208
326
|
|
|
209
327
|
```typescript
|
|
210
|
-
const
|
|
328
|
+
const executor = new CascadeExecutor({
|
|
329
|
+
tiers: { /* ... */ },
|
|
330
|
+
|
|
331
|
+
// Custom timeouts per tier (milliseconds)
|
|
332
|
+
timeouts: {
|
|
333
|
+
code: 2000, // 2 seconds
|
|
334
|
+
generative: 15000, // 15 seconds
|
|
335
|
+
agentic: 60000, // 1 minute
|
|
336
|
+
human: 3600000, // 1 hour
|
|
337
|
+
},
|
|
338
|
+
|
|
339
|
+
// Or use defaults
|
|
340
|
+
useDefaultTimeouts: true, // code: 5s, generative: 30s, agentic: 5m, human: 24h
|
|
341
|
+
|
|
342
|
+
// Total cascade timeout
|
|
343
|
+
totalTimeout: 300000, // 5 minutes max for entire cascade
|
|
344
|
+
})
|
|
345
|
+
```
|
|
211
346
|
|
|
212
|
-
|
|
213
|
-
workflow.definition // Event and schedule registrations
|
|
214
|
-
workflow.state // Current state and history
|
|
215
|
-
workflow.$ // The $ context
|
|
347
|
+
### Retry Configuration
|
|
216
348
|
|
|
217
|
-
|
|
218
|
-
await workflow.start() // Begin processing schedules
|
|
219
|
-
await workflow.stop() // Stop all schedules
|
|
349
|
+
Add retries with exponential backoff:
|
|
220
350
|
|
|
221
|
-
|
|
222
|
-
|
|
351
|
+
```typescript
|
|
352
|
+
const executor = new CascadeExecutor({
|
|
353
|
+
tiers: { /* ... */ },
|
|
354
|
+
|
|
355
|
+
retryConfig: {
|
|
356
|
+
code: { maxRetries: 2, baseDelay: 100 },
|
|
357
|
+
generative: { maxRetries: 3, baseDelay: 1000, multiplier: 2 },
|
|
358
|
+
agentic: { maxRetries: 1, baseDelay: 5000 },
|
|
359
|
+
}
|
|
360
|
+
})
|
|
223
361
|
```
|
|
224
362
|
|
|
225
363
|
## Testing
|
|
226
364
|
|
|
227
|
-
Create isolated
|
|
365
|
+
Create isolated contexts for testing:
|
|
228
366
|
|
|
229
367
|
```typescript
|
|
230
368
|
import { createTestContext } from 'ai-workflows'
|
|
@@ -232,75 +370,56 @@ import { createTestContext } from 'ai-workflows'
|
|
|
232
370
|
const $ = createTestContext()
|
|
233
371
|
|
|
234
372
|
// Call your handler
|
|
235
|
-
await
|
|
373
|
+
await orderHandler({ id: '123', total: 99.99 }, $)
|
|
236
374
|
|
|
237
|
-
//
|
|
375
|
+
// Assert on emitted events
|
|
238
376
|
expect($.emittedEvents).toContainEqual({
|
|
239
|
-
event: '
|
|
240
|
-
data: {
|
|
377
|
+
event: 'Payment.charge',
|
|
378
|
+
data: { amount: 99.99 },
|
|
241
379
|
})
|
|
242
380
|
```
|
|
243
381
|
|
|
244
382
|
## API Reference
|
|
245
383
|
|
|
246
|
-
### Workflow
|
|
384
|
+
### Core Workflow
|
|
247
385
|
|
|
248
386
|
| Export | Description |
|
|
249
387
|
|--------|-------------|
|
|
250
388
|
| `Workflow($)` | Create a workflow with $ context |
|
|
251
|
-
| `on` | Standalone event registration |
|
|
252
|
-
| `every` | Standalone schedule registration |
|
|
253
|
-
| `send` | Emit events globally |
|
|
389
|
+
| `on` | Standalone event registration proxy |
|
|
390
|
+
| `every` | Standalone schedule registration proxy |
|
|
391
|
+
| `send(event, data)` | Emit events globally |
|
|
254
392
|
| `createTestContext()` | Create isolated $ for testing |
|
|
255
393
|
|
|
256
|
-
###
|
|
394
|
+
### Cascade Executor
|
|
257
395
|
|
|
258
|
-
|
|
|
396
|
+
| Export | Description |
|
|
259
397
|
|--------|-------------|
|
|
260
|
-
|
|
|
261
|
-
|
|
|
262
|
-
|
|
|
263
|
-
| `$.do(event, data)` | Execute handler (durable) |
|
|
264
|
-
| `$.try(event, data)` | Execute handler (non-durable) |
|
|
265
|
-
| `$.log(message, data?)` | Log with history |
|
|
266
|
-
| `$.state` | Access workflow state |
|
|
398
|
+
| `CascadeExecutor` | Tiered execution: code -> AI -> agent -> human |
|
|
399
|
+
| `TIER_ORDER` | `['code', 'generative', 'agentic', 'human']` |
|
|
400
|
+
| `DEFAULT_TIER_TIMEOUTS` | Default timeout per tier |
|
|
267
401
|
|
|
268
|
-
###
|
|
402
|
+
### Dependency Graph
|
|
269
403
|
|
|
270
404
|
| Export | Description |
|
|
271
405
|
|--------|-------------|
|
|
272
|
-
| `
|
|
273
|
-
| `
|
|
274
|
-
| `
|
|
275
|
-
| `
|
|
406
|
+
| `DependencyGraph` | DAG for workflow step dependencies |
|
|
407
|
+
| `topologicalSort(nodes)` | Sort nodes in dependency order |
|
|
408
|
+
| `getExecutionLevels(nodes)` | Group nodes for parallel execution |
|
|
409
|
+
| `CircularDependencyError` | Thrown when cycle detected |
|
|
276
410
|
|
|
277
|
-
|
|
411
|
+
### Barriers
|
|
278
412
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
try<T, R>(event: string, data: T): Promise<R>
|
|
286
|
-
log(message: string, data?: unknown): void
|
|
287
|
-
state: Record<string, unknown>
|
|
288
|
-
db?: DatabaseContext
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
interface WorkflowInstance {
|
|
292
|
-
definition: WorkflowDefinition
|
|
293
|
-
state: WorkflowState
|
|
294
|
-
$: WorkflowContext
|
|
295
|
-
send<T>(event: string, data: T): Promise<void>
|
|
296
|
-
start(): Promise<void>
|
|
297
|
-
stop(): Promise<void>
|
|
298
|
-
}
|
|
299
|
-
```
|
|
413
|
+
| Export | Description |
|
|
414
|
+
|--------|-------------|
|
|
415
|
+
| `Barrier` | Manual synchronization point |
|
|
416
|
+
| `waitForAll(promises)` | Wait for all with timeout support |
|
|
417
|
+
| `waitForAny(n, promises)` | Wait for N of M to complete |
|
|
418
|
+
| `withConcurrencyLimit(tasks, n)` | Limit parallel executions |
|
|
300
419
|
|
|
301
420
|
## Related Packages
|
|
302
421
|
|
|
303
|
-
- [`ai-functions`](../ai-functions)
|
|
304
|
-
- [`ai-database`](../ai-database)
|
|
305
|
-
- [`human-in-the-loop`](../human-in-the-loop)
|
|
306
|
-
- [`digital-
|
|
422
|
+
- [`ai-functions`](../ai-functions) - AI-powered functions with type safety
|
|
423
|
+
- [`ai-database`](../ai-database) - Durable event storage
|
|
424
|
+
- [`human-in-the-loop`](../human-in-the-loop) - Human workflow steps
|
|
425
|
+
- [`digital-workers`](../digital-workers) - Autonomous AI agents
|