ai-workflows 2.1.1 → 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 +17 -1
- package/README.md +305 -184
- package/dist/barrier.d.ts +159 -0
- package/dist/barrier.d.ts.map +1 -0
- package/dist/barrier.js +377 -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 +27 -8
- 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/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/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/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 +10 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -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 +35 -10
- package/dist/on.d.ts.map +1 -1
- package/dist/on.js +53 -19
- 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 +77 -0
- package/dist/timer-registry.d.ts.map +1 -0
- package/dist/timer-registry.js +154 -0
- package/dist/timer-registry.js.map +1 -0
- package/dist/types.d.ts +105 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +17 -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 +136 -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 +21 -4
- package/src/__tests__/digital-objects-adapter.test.ts +274 -0
- package/src/__tests__/durable-workflow.test.ts +297 -0
- package/src/barrier.ts +507 -0
- package/src/cascade-context.ts +495 -0
- package/src/cascade-executor.ts +588 -0
- package/src/context.ts +51 -17
- package/src/cron-parser.ts +347 -0
- package/src/cron-scheduler.ts +239 -0
- package/src/database-context.ts +658 -0
- package/src/dependency-graph.ts +518 -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/every.ts +104 -35
- package/src/graph/index.ts +19 -0
- package/src/graph/topological-sort.ts +412 -0
- package/src/index.ts +147 -0
- package/src/logger.ts +148 -0
- package/src/on.ts +81 -26
- package/src/runtime.ts +436 -0
- package/src/send.ts +4 -5
- package/src/telemetry.ts +577 -0
- package/src/timer-registry.ts +179 -0
- package/src/types.ts +146 -10
- 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 +199 -355
- package/test/barrier-join.test.ts +442 -0
- package/test/barrier-unhandled-rejections.test.ts +359 -0
- package/test/cascade-context.test.ts +390 -0
- package/test/cascade-executor.test.ts +852 -0
- 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/dependency-graph.test.ts +512 -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/graph/topological-sort.test.ts +586 -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 +353 -0
- package/test/send-race-conditions.test.ts +400 -0
- package/test/type-safety-every.test.ts +303 -0
- 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 -7
- 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
package/.turbo/turbo-build.log
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# ai-workflows
|
|
2
2
|
|
|
3
|
+
## 2.3.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- @org.ai/types@2.3.0
|
|
8
|
+
|
|
9
|
+
## 2.1.3
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Documentation and testing improvements
|
|
14
|
+
- Add deterministic AI testing suite with self-validating patterns
|
|
15
|
+
- Apply StoryBrand narrative to all package READMEs
|
|
16
|
+
- Update TESTING.md with four principles of deterministic AI testing
|
|
17
|
+
- Fix duplicate examples package name conflict
|
|
18
|
+
|
|
3
19
|
## 2.1.1
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
|
@@ -9,7 +25,7 @@
|
|
|
9
25
|
- ai-functions: Add tests for AIFunction<Output, Input> generic order flip
|
|
10
26
|
- ai-workflows: Add tests for EventHandler<TOutput, TInput> order and OnProxy/EveryProxy autocomplete
|
|
11
27
|
- ai-database: Existing package - no changes in this release
|
|
12
|
-
- @
|
|
28
|
+
- @org.ai/types: New shared types package with failing tests for RED phase
|
|
13
29
|
|
|
14
30
|
These tests document the expected behavior for the GREEN phase implementation where generic type parameters will be reordered to put Output first (matching Promise<T> convention).
|
|
15
31
|
|
package/README.md
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
# ai-workflows
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+

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