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,518 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example: Integration with ai-database for Persistence
|
|
3
|
+
*
|
|
4
|
+
* This example demonstrates how to integrate ai-workflows with a database
|
|
5
|
+
* for durable event storage, state persistence, and audit logging.
|
|
6
|
+
*
|
|
7
|
+
* Note: This example uses a mock database implementation. In production,
|
|
8
|
+
* you would use ai-database or another persistence layer.
|
|
9
|
+
*
|
|
10
|
+
* Key concepts demonstrated:
|
|
11
|
+
* - DatabaseContext integration with workflows
|
|
12
|
+
* - Durable event storage with $.do
|
|
13
|
+
* - State checkpointing
|
|
14
|
+
* - Audit trail with 5W+H events
|
|
15
|
+
* - Recovery from failures
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```bash
|
|
19
|
+
* npx tsx examples/04-database-persistence.ts
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
Workflow,
|
|
25
|
+
createCascadeContext,
|
|
26
|
+
recordStep,
|
|
27
|
+
type WorkflowContext,
|
|
28
|
+
type DatabaseContext,
|
|
29
|
+
} from '../dist/index.js'
|
|
30
|
+
|
|
31
|
+
// ============================================================================
|
|
32
|
+
// Type Definitions
|
|
33
|
+
// ============================================================================
|
|
34
|
+
|
|
35
|
+
interface StoredEvent {
|
|
36
|
+
id: string
|
|
37
|
+
event: string
|
|
38
|
+
data: unknown
|
|
39
|
+
timestamp: number
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface StoredAction {
|
|
43
|
+
id: string
|
|
44
|
+
actor: string
|
|
45
|
+
object: string
|
|
46
|
+
action: string
|
|
47
|
+
status?: 'pending' | 'active' | 'completed' | 'failed'
|
|
48
|
+
metadata?: Record<string, unknown>
|
|
49
|
+
createdAt: number
|
|
50
|
+
completedAt?: number
|
|
51
|
+
result?: unknown
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface StoredArtifact {
|
|
55
|
+
key: string
|
|
56
|
+
type: string
|
|
57
|
+
sourceHash: string
|
|
58
|
+
content: unknown
|
|
59
|
+
metadata?: Record<string, unknown>
|
|
60
|
+
storedAt: number
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface WorkflowCheckpoint {
|
|
64
|
+
workflowId: string
|
|
65
|
+
state: Record<string, unknown>
|
|
66
|
+
history: Array<{ timestamp: number; type: string; name: string; data?: unknown }>
|
|
67
|
+
createdAt: number
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ============================================================================
|
|
71
|
+
// Mock Database Implementation
|
|
72
|
+
// ============================================================================
|
|
73
|
+
|
|
74
|
+
class MockDatabase {
|
|
75
|
+
private events: StoredEvent[] = []
|
|
76
|
+
private actions: Map<string, StoredAction> = new Map()
|
|
77
|
+
private artifacts: Map<string, StoredArtifact> = new Map()
|
|
78
|
+
private checkpoints: Map<string, WorkflowCheckpoint> = new Map()
|
|
79
|
+
|
|
80
|
+
// Event storage
|
|
81
|
+
async recordEvent(event: string, data: unknown): Promise<string> {
|
|
82
|
+
const id = `evt_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
|
|
83
|
+
const stored: StoredEvent = {
|
|
84
|
+
id,
|
|
85
|
+
event,
|
|
86
|
+
data,
|
|
87
|
+
timestamp: Date.now(),
|
|
88
|
+
}
|
|
89
|
+
this.events.push(stored)
|
|
90
|
+
console.log(` [DB] Recorded event: ${event} (${id})`)
|
|
91
|
+
return id
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async getEvents(filter?: { event?: string; since?: number }): Promise<StoredEvent[]> {
|
|
95
|
+
let result = [...this.events]
|
|
96
|
+
if (filter?.event) {
|
|
97
|
+
result = result.filter((e) => e.event === filter.event)
|
|
98
|
+
}
|
|
99
|
+
if (filter?.since) {
|
|
100
|
+
result = result.filter((e) => e.timestamp >= filter.since)
|
|
101
|
+
}
|
|
102
|
+
return result
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Action storage
|
|
106
|
+
async createAction(action: Omit<StoredAction, 'id' | 'createdAt'>): Promise<string> {
|
|
107
|
+
const id = `act_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
|
|
108
|
+
const stored: StoredAction = {
|
|
109
|
+
...action,
|
|
110
|
+
id,
|
|
111
|
+
status: action.status || 'pending',
|
|
112
|
+
createdAt: Date.now(),
|
|
113
|
+
}
|
|
114
|
+
this.actions.set(id, stored)
|
|
115
|
+
console.log(` [DB] Created action: ${action.action} on ${action.object} (${id})`)
|
|
116
|
+
return id
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async updateAction(
|
|
120
|
+
id: string,
|
|
121
|
+
update: Partial<Pick<StoredAction, 'status' | 'result'>>
|
|
122
|
+
): Promise<void> {
|
|
123
|
+
const action = this.actions.get(id)
|
|
124
|
+
if (action) {
|
|
125
|
+
Object.assign(action, update)
|
|
126
|
+
if (update.status === 'completed' || update.status === 'failed') {
|
|
127
|
+
action.completedAt = Date.now()
|
|
128
|
+
}
|
|
129
|
+
console.log(` [DB] Updated action ${id}: status=${action.status}`)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async getAction(id: string): Promise<StoredAction | null> {
|
|
134
|
+
return this.actions.get(id) || null
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async getPendingActions(): Promise<StoredAction[]> {
|
|
138
|
+
return Array.from(this.actions.values()).filter(
|
|
139
|
+
(a) => a.status === 'pending' || a.status === 'active'
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Artifact storage
|
|
144
|
+
async storeArtifact(artifact: Omit<StoredArtifact, 'storedAt'>): Promise<void> {
|
|
145
|
+
const stored: StoredArtifact = {
|
|
146
|
+
...artifact,
|
|
147
|
+
storedAt: Date.now(),
|
|
148
|
+
}
|
|
149
|
+
this.artifacts.set(artifact.key, stored)
|
|
150
|
+
console.log(` [DB] Stored artifact: ${artifact.key} (${artifact.type})`)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async getArtifact(key: string): Promise<StoredArtifact | null> {
|
|
154
|
+
return this.artifacts.get(key) || null
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Checkpoint storage
|
|
158
|
+
async saveCheckpoint(checkpoint: WorkflowCheckpoint): Promise<void> {
|
|
159
|
+
this.checkpoints.set(checkpoint.workflowId, checkpoint)
|
|
160
|
+
console.log(` [DB] Saved checkpoint for workflow ${checkpoint.workflowId}`)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async getCheckpoint(workflowId: string): Promise<WorkflowCheckpoint | null> {
|
|
164
|
+
return this.checkpoints.get(workflowId) || null
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Stats
|
|
168
|
+
getStats() {
|
|
169
|
+
return {
|
|
170
|
+
events: this.events.length,
|
|
171
|
+
actions: this.actions.size,
|
|
172
|
+
artifacts: this.artifacts.size,
|
|
173
|
+
checkpoints: this.checkpoints.size,
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Dump for inspection
|
|
178
|
+
dump() {
|
|
179
|
+
return {
|
|
180
|
+
events: this.events,
|
|
181
|
+
actions: Array.from(this.actions.values()),
|
|
182
|
+
artifacts: Array.from(this.artifacts.values()),
|
|
183
|
+
checkpoints: Array.from(this.checkpoints.values()),
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ============================================================================
|
|
189
|
+
// Database Context Adapter
|
|
190
|
+
// ============================================================================
|
|
191
|
+
|
|
192
|
+
function createDatabaseContext(db: MockDatabase): DatabaseContext {
|
|
193
|
+
return {
|
|
194
|
+
async recordEvent(event: string, data: unknown): Promise<void> {
|
|
195
|
+
await db.recordEvent(event, data)
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
async createAction(action: {
|
|
199
|
+
actor: string
|
|
200
|
+
object: string
|
|
201
|
+
action: string
|
|
202
|
+
status?: 'pending' | 'active' | 'completed' | 'failed'
|
|
203
|
+
metadata?: Record<string, unknown>
|
|
204
|
+
}): Promise<void> {
|
|
205
|
+
await db.createAction(action)
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
async completeAction(id: string, result: unknown): Promise<void> {
|
|
209
|
+
await db.updateAction(id, { status: 'completed', result })
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
async storeArtifact(artifact: {
|
|
213
|
+
key: string
|
|
214
|
+
type: string
|
|
215
|
+
sourceHash: string
|
|
216
|
+
content: unknown
|
|
217
|
+
metadata?: Record<string, unknown>
|
|
218
|
+
}): Promise<void> {
|
|
219
|
+
await db.storeArtifact(artifact)
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
async getArtifact(key: string): Promise<unknown | null> {
|
|
223
|
+
const artifact = await db.getArtifact(key)
|
|
224
|
+
return artifact?.content ?? null
|
|
225
|
+
},
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ============================================================================
|
|
230
|
+
// Durable Workflow Example
|
|
231
|
+
// ============================================================================
|
|
232
|
+
|
|
233
|
+
interface PaymentRequest {
|
|
234
|
+
orderId: string
|
|
235
|
+
amount: number
|
|
236
|
+
customerId: string
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
interface PaymentResult {
|
|
240
|
+
transactionId: string
|
|
241
|
+
status: 'success' | 'failed'
|
|
242
|
+
processedAt: number
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async function createDurablePaymentWorkflow(db: MockDatabase) {
|
|
246
|
+
const dbContext = createDatabaseContext(db)
|
|
247
|
+
|
|
248
|
+
const workflow = Workflow(
|
|
249
|
+
($) => {
|
|
250
|
+
// Payment processing with durable state
|
|
251
|
+
$.on.Payment.process(async (request: PaymentRequest, $: WorkflowContext) => {
|
|
252
|
+
$.log(`Processing payment for order ${request.orderId}`)
|
|
253
|
+
|
|
254
|
+
// Create cascade context for tracing
|
|
255
|
+
const cascade = createCascadeContext({ name: 'payment-processing' })
|
|
256
|
+
|
|
257
|
+
// Step 1: Validate payment
|
|
258
|
+
const validateStep = recordStep(cascade, 'validate', {
|
|
259
|
+
actor: 'payment-service',
|
|
260
|
+
action: 'validate-payment',
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
// Simulate validation
|
|
265
|
+
await new Promise((resolve) => setTimeout(resolve, 50))
|
|
266
|
+
if (request.amount <= 0) {
|
|
267
|
+
throw new Error('Invalid amount')
|
|
268
|
+
}
|
|
269
|
+
validateStep.complete()
|
|
270
|
+
$.log(' Payment validated')
|
|
271
|
+
} catch (error) {
|
|
272
|
+
validateStep.fail(error instanceof Error ? error : new Error(String(error)))
|
|
273
|
+
throw error
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Step 2: Process payment (durable)
|
|
277
|
+
const processStep = recordStep(cascade, 'process', {
|
|
278
|
+
actor: 'payment-gateway',
|
|
279
|
+
action: 'charge-card',
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
try {
|
|
283
|
+
// This would be $.do in real usage for durable execution
|
|
284
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
285
|
+
const transactionId = `txn_${Date.now()}`
|
|
286
|
+
|
|
287
|
+
// Store transaction artifact
|
|
288
|
+
if ($.db) {
|
|
289
|
+
await $.db.storeArtifact({
|
|
290
|
+
key: `payment:${request.orderId}`,
|
|
291
|
+
type: 'transaction',
|
|
292
|
+
sourceHash: request.orderId,
|
|
293
|
+
content: {
|
|
294
|
+
transactionId,
|
|
295
|
+
amount: request.amount,
|
|
296
|
+
customerId: request.customerId,
|
|
297
|
+
},
|
|
298
|
+
})
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
processStep.complete()
|
|
302
|
+
$.log(` Payment processed: ${transactionId}`)
|
|
303
|
+
|
|
304
|
+
// Emit success event
|
|
305
|
+
$.send('Payment.completed', {
|
|
306
|
+
orderId: request.orderId,
|
|
307
|
+
transactionId,
|
|
308
|
+
amount: request.amount,
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
transactionId,
|
|
313
|
+
status: 'success' as const,
|
|
314
|
+
processedAt: Date.now(),
|
|
315
|
+
}
|
|
316
|
+
} catch (error) {
|
|
317
|
+
processStep.fail(error instanceof Error ? error : new Error(String(error)))
|
|
318
|
+
|
|
319
|
+
$.send('Payment.failed', {
|
|
320
|
+
orderId: request.orderId,
|
|
321
|
+
error: error instanceof Error ? error.message : String(error),
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
throw error
|
|
325
|
+
}
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
$.on.Payment.completed(
|
|
329
|
+
async (data: { orderId: string; transactionId: string }, $: WorkflowContext) => {
|
|
330
|
+
$.log(`Payment ${data.transactionId} completed for order ${data.orderId}`)
|
|
331
|
+
|
|
332
|
+
// Create action to fulfill order
|
|
333
|
+
if ($.db) {
|
|
334
|
+
await $.db.createAction({
|
|
335
|
+
actor: 'fulfillment-service',
|
|
336
|
+
object: `order:${data.orderId}`,
|
|
337
|
+
action: 'fulfill',
|
|
338
|
+
metadata: { transactionId: data.transactionId },
|
|
339
|
+
})
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
$.on.Payment.failed(async (data: { orderId: string; error: string }, $: WorkflowContext) => {
|
|
345
|
+
$.log(`Payment failed for order ${data.orderId}: ${data.error}`)
|
|
346
|
+
|
|
347
|
+
// Create action to notify customer
|
|
348
|
+
if ($.db) {
|
|
349
|
+
await $.db.createAction({
|
|
350
|
+
actor: 'notification-service',
|
|
351
|
+
object: `customer:order:${data.orderId}`,
|
|
352
|
+
action: 'notify-payment-failed',
|
|
353
|
+
metadata: { error: data.error },
|
|
354
|
+
})
|
|
355
|
+
}
|
|
356
|
+
})
|
|
357
|
+
},
|
|
358
|
+
{ db: dbContext }
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
return workflow
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ============================================================================
|
|
365
|
+
// State Recovery Example
|
|
366
|
+
// ============================================================================
|
|
367
|
+
|
|
368
|
+
async function demonstrateStateRecovery(db: MockDatabase) {
|
|
369
|
+
console.log('\n--- State Recovery Demo ---\n')
|
|
370
|
+
|
|
371
|
+
// Simulate saving a checkpoint
|
|
372
|
+
const checkpoint: WorkflowCheckpoint = {
|
|
373
|
+
workflowId: 'workflow-123',
|
|
374
|
+
state: {
|
|
375
|
+
orderId: 'order-456',
|
|
376
|
+
step: 'payment-processing',
|
|
377
|
+
attempts: 2,
|
|
378
|
+
},
|
|
379
|
+
history: [
|
|
380
|
+
{ timestamp: Date.now() - 10000, type: 'event', name: 'Order.placed' },
|
|
381
|
+
{ timestamp: Date.now() - 5000, type: 'action', name: 'validate' },
|
|
382
|
+
{
|
|
383
|
+
timestamp: Date.now() - 1000,
|
|
384
|
+
type: 'action',
|
|
385
|
+
name: 'process-payment',
|
|
386
|
+
data: { attempt: 1 },
|
|
387
|
+
},
|
|
388
|
+
],
|
|
389
|
+
createdAt: Date.now(),
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
await db.saveCheckpoint(checkpoint)
|
|
393
|
+
|
|
394
|
+
// Simulate recovery
|
|
395
|
+
console.log('Recovering workflow state...')
|
|
396
|
+
const recovered = await db.getCheckpoint('workflow-123')
|
|
397
|
+
|
|
398
|
+
if (recovered) {
|
|
399
|
+
console.log(` Recovered workflow: ${recovered.workflowId}`)
|
|
400
|
+
console.log(` State: ${JSON.stringify(recovered.state)}`)
|
|
401
|
+
console.log(` History entries: ${recovered.history.length}`)
|
|
402
|
+
console.log(` Last action: ${recovered.history[recovered.history.length - 1]?.name}`)
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// ============================================================================
|
|
407
|
+
// Audit Trail Example
|
|
408
|
+
// ============================================================================
|
|
409
|
+
|
|
410
|
+
async function demonstrateAuditTrail(db: MockDatabase) {
|
|
411
|
+
console.log('\n--- Audit Trail Demo ---\n')
|
|
412
|
+
|
|
413
|
+
// Record various events
|
|
414
|
+
await db.recordEvent('User.login', {
|
|
415
|
+
userId: 'user-123',
|
|
416
|
+
ip: '192.168.1.1',
|
|
417
|
+
userAgent: 'Mozilla/5.0...',
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
await db.recordEvent('Order.placed', {
|
|
421
|
+
orderId: 'order-789',
|
|
422
|
+
customerId: 'user-123',
|
|
423
|
+
total: 99.99,
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
await db.recordEvent('Payment.processed', {
|
|
427
|
+
orderId: 'order-789',
|
|
428
|
+
transactionId: 'txn-abc',
|
|
429
|
+
amount: 99.99,
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
await db.recordEvent('Order.shipped', {
|
|
433
|
+
orderId: 'order-789',
|
|
434
|
+
trackingNumber: 'TRK123456',
|
|
435
|
+
carrier: 'FastShip',
|
|
436
|
+
})
|
|
437
|
+
|
|
438
|
+
// Query events
|
|
439
|
+
console.log('All recorded events:')
|
|
440
|
+
const events = await db.getEvents()
|
|
441
|
+
for (const event of events) {
|
|
442
|
+
console.log(` ${new Date(event.timestamp).toISOString()} | ${event.event}`)
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Query specific event type
|
|
446
|
+
console.log('\nOrder-related events:')
|
|
447
|
+
const orderEvents = await db.getEvents({ event: 'Order.placed' })
|
|
448
|
+
for (const event of orderEvents) {
|
|
449
|
+
console.log(` ${event.id}: ${JSON.stringify(event.data)}`)
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// ============================================================================
|
|
454
|
+
// Demo Execution
|
|
455
|
+
// ============================================================================
|
|
456
|
+
|
|
457
|
+
async function runDemo() {
|
|
458
|
+
console.log('='.repeat(60))
|
|
459
|
+
console.log('Database Persistence Integration Demo')
|
|
460
|
+
console.log('='.repeat(60))
|
|
461
|
+
console.log()
|
|
462
|
+
|
|
463
|
+
// Create mock database
|
|
464
|
+
const db = new MockDatabase()
|
|
465
|
+
|
|
466
|
+
// Create and start workflow
|
|
467
|
+
const workflow = await createDurablePaymentWorkflow(db)
|
|
468
|
+
await workflow.start()
|
|
469
|
+
|
|
470
|
+
// Process some payments
|
|
471
|
+
console.log('--- Processing Payments ---\n')
|
|
472
|
+
|
|
473
|
+
await workflow.send('Payment.process', {
|
|
474
|
+
orderId: 'order-001',
|
|
475
|
+
amount: 99.99,
|
|
476
|
+
customerId: 'cust-001',
|
|
477
|
+
})
|
|
478
|
+
await new Promise((resolve) => setTimeout(resolve, 200))
|
|
479
|
+
|
|
480
|
+
await workflow.send('Payment.process', {
|
|
481
|
+
orderId: 'order-002',
|
|
482
|
+
amount: 149.99,
|
|
483
|
+
customerId: 'cust-002',
|
|
484
|
+
})
|
|
485
|
+
await new Promise((resolve) => setTimeout(resolve, 200))
|
|
486
|
+
|
|
487
|
+
// Demonstrate state recovery
|
|
488
|
+
await demonstrateStateRecovery(db)
|
|
489
|
+
|
|
490
|
+
// Demonstrate audit trail
|
|
491
|
+
await demonstrateAuditTrail(db)
|
|
492
|
+
|
|
493
|
+
// Show database stats
|
|
494
|
+
console.log('\n--- Database Stats ---\n')
|
|
495
|
+
const stats = db.getStats()
|
|
496
|
+
console.log(` Events: ${stats.events}`)
|
|
497
|
+
console.log(` Actions: ${stats.actions}`)
|
|
498
|
+
console.log(` Artifacts: ${stats.artifacts}`)
|
|
499
|
+
console.log(` Checkpoints: ${stats.checkpoints}`)
|
|
500
|
+
|
|
501
|
+
// Show pending actions
|
|
502
|
+
console.log('\n--- Pending Actions ---\n')
|
|
503
|
+
const pending = await db.getPendingActions()
|
|
504
|
+
for (const action of pending) {
|
|
505
|
+
console.log(` ${action.id}: ${action.action} on ${action.object} (${action.status})`)
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Full dump for inspection
|
|
509
|
+
console.log('\n--- Full Database Dump ---\n')
|
|
510
|
+
const dump = db.dump()
|
|
511
|
+
console.log(JSON.stringify(dump, null, 2))
|
|
512
|
+
|
|
513
|
+
// Clean up
|
|
514
|
+
await workflow.stop()
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Run if executed directly
|
|
518
|
+
runDemo().catch(console.error)
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# ai-workflows Examples
|
|
2
|
+
|
|
3
|
+
This directory contains runnable examples demonstrating the key features of ai-workflows.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# From the package root (or monorepo root)
|
|
9
|
+
npm install
|
|
10
|
+
npm run build # Must build first since examples import from dist/
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Running Examples
|
|
14
|
+
|
|
15
|
+
Each example can be run directly with `tsx`:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# From the ai-workflows package directory
|
|
19
|
+
npx tsx examples/01-ecommerce-order-pipeline.ts
|
|
20
|
+
npx tsx examples/02-content-moderation-cascade.ts
|
|
21
|
+
npx tsx examples/03-scheduled-reporting-dependencies.ts
|
|
22
|
+
npx tsx examples/04-database-persistence.ts
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Note:** Examples import from `../dist/index.js` to use the built package. Ensure you run `npm run build` first.
|
|
26
|
+
|
|
27
|
+
## Examples Overview
|
|
28
|
+
|
|
29
|
+
### 01 - E-commerce Order Processing Pipeline
|
|
30
|
+
|
|
31
|
+
Demonstrates a complete order processing workflow using event-driven architecture.
|
|
32
|
+
|
|
33
|
+
**Key Concepts:**
|
|
34
|
+
- Event handlers with `$.on.Noun.event()`
|
|
35
|
+
- Event chaining (one event triggers another)
|
|
36
|
+
- State management with `$.state`
|
|
37
|
+
- Error handling and compensation patterns
|
|
38
|
+
- Customer notification flow
|
|
39
|
+
|
|
40
|
+
**Flow:**
|
|
41
|
+
```
|
|
42
|
+
Order.placed -> Payment.requested -> Payment.completed -> Order.confirmed
|
|
43
|
+
-> Shipping.requested -> Order.shipped
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 02 - AI Content Moderation with Human Escalation
|
|
47
|
+
|
|
48
|
+
Demonstrates the CascadeExecutor pattern for tiered processing with escalation.
|
|
49
|
+
|
|
50
|
+
**Key Concepts:**
|
|
51
|
+
- `CascadeExecutor` for code -> AI -> agent -> human escalation
|
|
52
|
+
- Per-tier timeouts and retry configuration
|
|
53
|
+
- Skip conditions for tier bypassing
|
|
54
|
+
- 5W+H audit trail for compliance
|
|
55
|
+
- Workflow integration with cascade results
|
|
56
|
+
|
|
57
|
+
**Tiers:**
|
|
58
|
+
1. **Code**: Fast keyword/pattern matching (100ms timeout)
|
|
59
|
+
2. **Generative**: LLM-based analysis (5s timeout)
|
|
60
|
+
3. **Agentic**: Multi-step reasoning with tools (30s timeout)
|
|
61
|
+
4. **Human**: Expert review queue (1 hour timeout)
|
|
62
|
+
|
|
63
|
+
### 03 - Scheduled Reporting with Dependency Chains
|
|
64
|
+
|
|
65
|
+
Demonstrates complex workflows with dependencies and parallel execution.
|
|
66
|
+
|
|
67
|
+
**Key Concepts:**
|
|
68
|
+
- `DependencyGraph` for step ordering
|
|
69
|
+
- `topologicalSort()` for execution planning
|
|
70
|
+
- `getExecutionLevels()` for parallel grouping
|
|
71
|
+
- `waitForAll()` and `Barrier` for synchronization
|
|
72
|
+
- `withConcurrencyLimit()` for controlled parallelism
|
|
73
|
+
|
|
74
|
+
**Pipeline:**
|
|
75
|
+
```
|
|
76
|
+
Level 0: [fetch-sales, fetch-crm, fetch-inventory, fetch-analytics] (parallel)
|
|
77
|
+
Level 1: [aggregate-data] (sequential)
|
|
78
|
+
Level 2: [generate-daily-report, generate-summary] (parallel)
|
|
79
|
+
Level 3: [distribute-email, distribute-slack, archive-s3] (parallel)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 04 - Database Persistence Integration
|
|
83
|
+
|
|
84
|
+
Demonstrates durable event storage and state persistence.
|
|
85
|
+
|
|
86
|
+
**Key Concepts:**
|
|
87
|
+
- `DatabaseContext` integration with workflows
|
|
88
|
+
- Durable event storage with `$.db.recordEvent()`
|
|
89
|
+
- Action tracking with `$.db.createAction()`
|
|
90
|
+
- Artifact storage with `$.db.storeArtifact()`
|
|
91
|
+
- State checkpointing and recovery
|
|
92
|
+
- Audit trail with 5W+H events
|
|
93
|
+
|
|
94
|
+
**Note:** Uses a mock database implementation. In production, integrate with `ai-database` or your persistence layer.
|
|
95
|
+
|
|
96
|
+
## Code Patterns
|
|
97
|
+
|
|
98
|
+
### Basic Workflow Setup
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import { Workflow } from 'ai-workflows'
|
|
102
|
+
|
|
103
|
+
const workflow = Workflow($ => {
|
|
104
|
+
$.on.Entity.event(async (data, $) => {
|
|
105
|
+
$.log('Processing event')
|
|
106
|
+
$.send('Another.event', { result: 'done' })
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
await workflow.start()
|
|
111
|
+
await workflow.send('Entity.event', { input: 'data' })
|
|
112
|
+
await workflow.stop()
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Cascade Executor
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import { CascadeExecutor } from 'ai-workflows'
|
|
119
|
+
|
|
120
|
+
const cascade = new CascadeExecutor({
|
|
121
|
+
tiers: {
|
|
122
|
+
code: { name: 'rules', execute: async (input) => { /* fast rules */ } },
|
|
123
|
+
generative: { name: 'ai', execute: async (input) => { /* AI analysis */ } },
|
|
124
|
+
human: { name: 'review', execute: async (input) => { /* human queue */ } },
|
|
125
|
+
},
|
|
126
|
+
useDefaultTimeouts: true,
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
const result = await cascade.execute(input)
|
|
130
|
+
console.log(`Resolved by ${result.tier} tier`)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Dependency Graph
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
import { DependencyGraph, getExecutionLevels } from 'ai-workflows'
|
|
137
|
+
|
|
138
|
+
const graph = new DependencyGraph()
|
|
139
|
+
graph.addNode('step-a')
|
|
140
|
+
graph.addNode('step-b')
|
|
141
|
+
graph.addNode('step-c', { dependsOn: ['step-a', 'step-b'] })
|
|
142
|
+
|
|
143
|
+
const levels = getExecutionLevels(graph.getNodes())
|
|
144
|
+
// Level 0: [step-a, step-b]
|
|
145
|
+
// Level 1: [step-c]
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Barrier Synchronization
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import { Barrier, waitForAll } from 'ai-workflows'
|
|
152
|
+
|
|
153
|
+
// Simple parallel wait
|
|
154
|
+
const results = await waitForAll([
|
|
155
|
+
fetchA(),
|
|
156
|
+
fetchB(),
|
|
157
|
+
fetchC(),
|
|
158
|
+
], { timeout: 5000 })
|
|
159
|
+
|
|
160
|
+
// Manual barrier with progress
|
|
161
|
+
const barrier = new Barrier(3, {
|
|
162
|
+
onProgress: (p) => console.log(`${p.percentage}% complete`)
|
|
163
|
+
})
|
|
164
|
+
// ... barrier.arrive(result) from parallel workers
|
|
165
|
+
const all = await barrier.wait()
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Related Documentation
|
|
169
|
+
|
|
170
|
+
- [ai-workflows README](../README.md) - Full API documentation
|
|
171
|
+
- [CascadeExecutor](../src/cascade-executor.ts) - Tiered execution pattern
|
|
172
|
+
- [DependencyGraph](../src/dependency-graph.ts) - Step dependency management
|
|
173
|
+
- [Barrier](../src/barrier.ts) - Parallel coordination primitives
|