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.
Files changed (211) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +17 -1
  3. package/README.md +305 -184
  4. package/dist/barrier.d.ts +159 -0
  5. package/dist/barrier.d.ts.map +1 -0
  6. package/dist/barrier.js +377 -0
  7. package/dist/barrier.js.map +1 -0
  8. package/dist/cascade-context.d.ts +149 -0
  9. package/dist/cascade-context.d.ts.map +1 -0
  10. package/dist/cascade-context.js +324 -0
  11. package/dist/cascade-context.js.map +1 -0
  12. package/dist/cascade-executor.d.ts +196 -0
  13. package/dist/cascade-executor.d.ts.map +1 -0
  14. package/dist/cascade-executor.js +384 -0
  15. package/dist/cascade-executor.js.map +1 -0
  16. package/dist/context.d.ts.map +1 -1
  17. package/dist/context.js +27 -8
  18. package/dist/context.js.map +1 -1
  19. package/dist/cron-parser.d.ts +65 -0
  20. package/dist/cron-parser.d.ts.map +1 -0
  21. package/dist/cron-parser.js +294 -0
  22. package/dist/cron-parser.js.map +1 -0
  23. package/dist/cron-scheduler.d.ts +117 -0
  24. package/dist/cron-scheduler.d.ts.map +1 -0
  25. package/dist/cron-scheduler.js +176 -0
  26. package/dist/cron-scheduler.js.map +1 -0
  27. package/dist/database-context.d.ts +184 -0
  28. package/dist/database-context.d.ts.map +1 -0
  29. package/dist/database-context.js +428 -0
  30. package/dist/database-context.js.map +1 -0
  31. package/dist/dependency-graph.d.ts +157 -0
  32. package/dist/dependency-graph.d.ts.map +1 -0
  33. package/dist/dependency-graph.js +382 -0
  34. package/dist/dependency-graph.js.map +1 -0
  35. package/dist/digital-objects-adapter.d.ts +159 -0
  36. package/dist/digital-objects-adapter.d.ts.map +1 -0
  37. package/dist/digital-objects-adapter.js +229 -0
  38. package/dist/digital-objects-adapter.js.map +1 -0
  39. package/dist/durable-execution-cloudflare.d.ts +427 -0
  40. package/dist/durable-execution-cloudflare.d.ts.map +1 -0
  41. package/dist/durable-execution-cloudflare.js +510 -0
  42. package/dist/durable-execution-cloudflare.js.map +1 -0
  43. package/dist/durable-execution.d.ts +482 -0
  44. package/dist/durable-execution.d.ts.map +1 -0
  45. package/dist/durable-execution.js +594 -0
  46. package/dist/durable-execution.js.map +1 -0
  47. package/dist/durable-workflow.d.ts +176 -0
  48. package/dist/durable-workflow.d.ts.map +1 -0
  49. package/dist/durable-workflow.js +552 -0
  50. package/dist/durable-workflow.js.map +1 -0
  51. package/dist/every.d.ts +31 -2
  52. package/dist/every.d.ts.map +1 -1
  53. package/dist/every.js +63 -32
  54. package/dist/every.js.map +1 -1
  55. package/dist/graph/index.d.ts +8 -0
  56. package/dist/graph/index.d.ts.map +1 -0
  57. package/dist/graph/index.js +8 -0
  58. package/dist/graph/index.js.map +1 -0
  59. package/dist/graph/topological-sort.d.ts +121 -0
  60. package/dist/graph/topological-sort.d.ts.map +1 -0
  61. package/dist/graph/topological-sort.js +292 -0
  62. package/dist/graph/topological-sort.js.map +1 -0
  63. package/dist/index.d.ts +10 -1
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +25 -0
  66. package/dist/index.js.map +1 -1
  67. package/dist/logger.d.ts +101 -0
  68. package/dist/logger.d.ts.map +1 -0
  69. package/dist/logger.js +115 -0
  70. package/dist/logger.js.map +1 -0
  71. package/dist/on.d.ts +35 -10
  72. package/dist/on.d.ts.map +1 -1
  73. package/dist/on.js +53 -19
  74. package/dist/on.js.map +1 -1
  75. package/dist/runtime.d.ts +169 -0
  76. package/dist/runtime.d.ts.map +1 -0
  77. package/dist/runtime.js +275 -0
  78. package/dist/runtime.js.map +1 -0
  79. package/dist/send.d.ts.map +1 -1
  80. package/dist/send.js +4 -3
  81. package/dist/send.js.map +1 -1
  82. package/dist/telemetry.d.ts +150 -0
  83. package/dist/telemetry.d.ts.map +1 -0
  84. package/dist/telemetry.js +388 -0
  85. package/dist/telemetry.js.map +1 -0
  86. package/dist/timer-registry.d.ts +77 -0
  87. package/dist/timer-registry.d.ts.map +1 -0
  88. package/dist/timer-registry.js +154 -0
  89. package/dist/timer-registry.js.map +1 -0
  90. package/dist/types.d.ts +105 -6
  91. package/dist/types.d.ts.map +1 -1
  92. package/dist/types.js +17 -1
  93. package/dist/types.js.map +1 -1
  94. package/dist/worker/durable-step.d.ts +481 -0
  95. package/dist/worker/durable-step.d.ts.map +1 -0
  96. package/dist/worker/durable-step.js +606 -0
  97. package/dist/worker/durable-step.js.map +1 -0
  98. package/dist/worker/index.d.ts +106 -0
  99. package/dist/worker/index.d.ts.map +1 -0
  100. package/dist/worker/index.js +124 -0
  101. package/dist/worker/index.js.map +1 -0
  102. package/dist/worker/state-adapter.d.ts +230 -0
  103. package/dist/worker/state-adapter.d.ts.map +1 -0
  104. package/dist/worker/state-adapter.js +409 -0
  105. package/dist/worker/state-adapter.js.map +1 -0
  106. package/dist/worker/topological-executor.d.ts +282 -0
  107. package/dist/worker/topological-executor.d.ts.map +1 -0
  108. package/dist/worker/topological-executor.js +396 -0
  109. package/dist/worker/topological-executor.js.map +1 -0
  110. package/dist/worker/workflow-builder.d.ts +286 -0
  111. package/dist/worker/workflow-builder.d.ts.map +1 -0
  112. package/dist/worker/workflow-builder.js +565 -0
  113. package/dist/worker/workflow-builder.js.map +1 -0
  114. package/dist/worker.d.ts +800 -0
  115. package/dist/worker.d.ts.map +1 -0
  116. package/dist/worker.js +2428 -0
  117. package/dist/worker.js.map +1 -0
  118. package/dist/workflow-builder.d.ts +287 -0
  119. package/dist/workflow-builder.d.ts.map +1 -0
  120. package/dist/workflow-builder.js +762 -0
  121. package/dist/workflow-builder.js.map +1 -0
  122. package/dist/workflow.d.ts +14 -30
  123. package/dist/workflow.d.ts.map +1 -1
  124. package/dist/workflow.js +136 -292
  125. package/dist/workflow.js.map +1 -1
  126. package/examples/01-ecommerce-order-pipeline.ts +358 -0
  127. package/examples/02-content-moderation-cascade.ts +454 -0
  128. package/examples/03-scheduled-reporting-dependencies.ts +479 -0
  129. package/examples/04-database-persistence.ts +518 -0
  130. package/examples/README.md +173 -0
  131. package/package.json +21 -4
  132. package/src/__tests__/digital-objects-adapter.test.ts +274 -0
  133. package/src/__tests__/durable-workflow.test.ts +297 -0
  134. package/src/barrier.ts +507 -0
  135. package/src/cascade-context.ts +495 -0
  136. package/src/cascade-executor.ts +588 -0
  137. package/src/context.ts +51 -17
  138. package/src/cron-parser.ts +347 -0
  139. package/src/cron-scheduler.ts +239 -0
  140. package/src/database-context.ts +658 -0
  141. package/src/dependency-graph.ts +518 -0
  142. package/src/digital-objects-adapter.ts +351 -0
  143. package/src/durable-execution-cloudflare.ts +855 -0
  144. package/src/durable-execution.ts +1042 -0
  145. package/src/durable-workflow.ts +717 -0
  146. package/src/every.ts +104 -35
  147. package/src/graph/index.ts +19 -0
  148. package/src/graph/topological-sort.ts +412 -0
  149. package/src/index.ts +147 -0
  150. package/src/logger.ts +148 -0
  151. package/src/on.ts +81 -26
  152. package/src/runtime.ts +436 -0
  153. package/src/send.ts +4 -5
  154. package/src/telemetry.ts +577 -0
  155. package/src/timer-registry.ts +179 -0
  156. package/src/types.ts +146 -10
  157. package/src/worker/durable-step.ts +976 -0
  158. package/src/worker/index.ts +216 -0
  159. package/src/worker/state-adapter.ts +589 -0
  160. package/src/worker/topological-executor.ts +625 -0
  161. package/src/worker/workflow-builder.ts +871 -0
  162. package/src/worker.ts +2906 -0
  163. package/src/workflow-builder.ts +1068 -0
  164. package/src/workflow.ts +199 -355
  165. package/test/barrier-join.test.ts +442 -0
  166. package/test/barrier-unhandled-rejections.test.ts +359 -0
  167. package/test/cascade-context.test.ts +390 -0
  168. package/test/cascade-executor.test.ts +852 -0
  169. package/test/cron-parser.test.ts +314 -0
  170. package/test/cron-scheduler.test.ts +291 -0
  171. package/test/database-context.test.ts +770 -0
  172. package/test/db-provider-adapter.test.ts +862 -0
  173. package/test/dependency-graph.test.ts +512 -0
  174. package/test/durable-execution-cloudflare.test.ts +606 -0
  175. package/test/durable-execution-in-process.test.ts +286 -0
  176. package/test/durable-execution.test.ts +247 -0
  177. package/test/e2e/workflow-scenarios.e2e.test.ts +1039 -0
  178. package/test/graph/topological-sort.test.ts +586 -0
  179. package/test/integration.test.ts +442 -0
  180. package/test/rpc-surface.test.ts +946 -0
  181. package/test/runtime.test.ts +262 -0
  182. package/test/schedule-timer-cleanup.test.ts +353 -0
  183. package/test/send-race-conditions.test.ts +400 -0
  184. package/test/type-safety-every.test.ts +303 -0
  185. package/test/worker/durable-cascade.test.ts +1117 -0
  186. package/test/worker/durable-step.test.ts +723 -0
  187. package/test/worker/topological-executor.test.ts +1240 -0
  188. package/test/worker/workflow-builder.test.ts +1067 -0
  189. package/test/worker.test.ts +608 -0
  190. package/test/workflow-builder.test.ts +1670 -0
  191. package/test/workflow-cron.test.ts +256 -0
  192. package/test/workflow-state-adapter.test.ts +923 -0
  193. package/test/workflow.test.ts +25 -22
  194. package/tsconfig.json +3 -1
  195. package/vitest.config.ts +38 -1
  196. package/vitest.workers.config.ts +44 -0
  197. package/wrangler.jsonc +22 -0
  198. package/.turbo/turbo-test.log +0 -7
  199. package/src/context.js +0 -83
  200. package/src/every.js +0 -267
  201. package/src/index.js +0 -71
  202. package/src/on.js +0 -79
  203. package/src/send.js +0 -111
  204. package/src/types.js +0 -4
  205. package/src/workflow.js +0 -455
  206. package/test/context.test.js +0 -116
  207. package/test/every.test.js +0 -282
  208. package/test/on.test.js +0 -80
  209. package/test/send.test.js +0 -89
  210. package/test/workflow.test.js +0 -224
  211. package/vitest.config.js +0 -7
@@ -0,0 +1,479 @@
1
+ /**
2
+ * Example: Scheduled Reporting with Dependency Chains
3
+ *
4
+ * This example demonstrates scheduled tasks with complex dependencies using
5
+ * the DependencyGraph for execution ordering and barriers for synchronization.
6
+ *
7
+ * Workflow:
8
+ * 1. Fetch data from multiple sources (parallel)
9
+ * 2. Transform and aggregate data (depends on fetch)
10
+ * 3. Generate reports (depends on aggregation)
11
+ * 4. Distribute reports (depends on generation)
12
+ *
13
+ * Key concepts demonstrated:
14
+ * - $.every for scheduled execution
15
+ * - DependencyGraph for step ordering
16
+ * - topologicalSort for execution planning
17
+ * - getExecutionLevels for parallel grouping
18
+ * - waitForAll and Barrier for synchronization
19
+ * - withConcurrencyLimit for controlled parallelism
20
+ *
21
+ * @example
22
+ * ```bash
23
+ * npx tsx examples/03-scheduled-reporting-dependencies.ts
24
+ * ```
25
+ */
26
+
27
+ import {
28
+ Workflow,
29
+ DependencyGraph,
30
+ topologicalSort,
31
+ getExecutionLevels,
32
+ waitForAll,
33
+ Barrier,
34
+ withConcurrencyLimit,
35
+ type WorkflowContext,
36
+ } from '../dist/index.js'
37
+
38
+ // ============================================================================
39
+ // Type Definitions
40
+ // ============================================================================
41
+
42
+ interface DataSource {
43
+ id: string
44
+ name: string
45
+ type: 'database' | 'api' | 'file'
46
+ }
47
+
48
+ interface FetchedData {
49
+ sourceId: string
50
+ records: Array<{ id: string; value: number; timestamp: number }>
51
+ fetchedAt: number
52
+ }
53
+
54
+ interface AggregatedMetrics {
55
+ period: string
56
+ totalRecords: number
57
+ sumValues: number
58
+ avgValue: number
59
+ sources: string[]
60
+ }
61
+
62
+ interface Report {
63
+ id: string
64
+ type: 'daily' | 'weekly' | 'monthly'
65
+ metrics: AggregatedMetrics
66
+ generatedAt: number
67
+ }
68
+
69
+ interface DistributionResult {
70
+ reportId: string
71
+ channel: string
72
+ success: boolean
73
+ deliveredAt?: number
74
+ }
75
+
76
+ // ============================================================================
77
+ // Mock Data Sources
78
+ // ============================================================================
79
+
80
+ const dataSources: DataSource[] = [
81
+ { id: 'sales-db', name: 'Sales Database', type: 'database' },
82
+ { id: 'crm-api', name: 'CRM API', type: 'api' },
83
+ { id: 'inventory-file', name: 'Inventory Export', type: 'file' },
84
+ { id: 'analytics-api', name: 'Analytics Service', type: 'api' },
85
+ ]
86
+
87
+ // ============================================================================
88
+ // Mock Services
89
+ // ============================================================================
90
+
91
+ const dataService = {
92
+ async fetchFromSource(source: DataSource): Promise<FetchedData> {
93
+ // Simulate varying fetch times
94
+ const delay = 50 + Math.random() * 100
95
+ await new Promise((resolve) => setTimeout(resolve, delay))
96
+
97
+ console.log(` [Fetch] ${source.name} (${delay.toFixed(0)}ms)`)
98
+
99
+ return {
100
+ sourceId: source.id,
101
+ records: Array.from({ length: 10 + Math.floor(Math.random() * 20) }, (_, i) => ({
102
+ id: `${source.id}-${i}`,
103
+ value: Math.random() * 1000,
104
+ timestamp: Date.now() - Math.random() * 86400000,
105
+ })),
106
+ fetchedAt: Date.now(),
107
+ }
108
+ },
109
+ }
110
+
111
+ const aggregationService = {
112
+ async aggregate(data: FetchedData[], period: string): Promise<AggregatedMetrics> {
113
+ await new Promise((resolve) => setTimeout(resolve, 30))
114
+
115
+ const allRecords = data.flatMap((d) => d.records)
116
+ const sum = allRecords.reduce((acc, r) => acc + r.value, 0)
117
+
118
+ return {
119
+ period,
120
+ totalRecords: allRecords.length,
121
+ sumValues: sum,
122
+ avgValue: sum / allRecords.length,
123
+ sources: data.map((d) => d.sourceId),
124
+ }
125
+ },
126
+ }
127
+
128
+ const reportService = {
129
+ async generateReport(
130
+ type: 'daily' | 'weekly' | 'monthly',
131
+ metrics: AggregatedMetrics
132
+ ): Promise<Report> {
133
+ await new Promise((resolve) => setTimeout(resolve, 50))
134
+
135
+ return {
136
+ id: `report-${type}-${Date.now()}`,
137
+ type,
138
+ metrics,
139
+ generatedAt: Date.now(),
140
+ }
141
+ },
142
+ }
143
+
144
+ const distributionService = {
145
+ async distribute(report: Report, channel: 'email' | 'slack' | 's3'): Promise<DistributionResult> {
146
+ await new Promise((resolve) => setTimeout(resolve, 30))
147
+
148
+ return {
149
+ reportId: report.id,
150
+ channel,
151
+ success: true,
152
+ deliveredAt: Date.now(),
153
+ }
154
+ },
155
+ }
156
+
157
+ // ============================================================================
158
+ // Dependency Graph Setup
159
+ // ============================================================================
160
+
161
+ function createReportingPipeline(): DependencyGraph {
162
+ const graph = new DependencyGraph()
163
+
164
+ // Level 0: Data fetching (no dependencies, run in parallel)
165
+ graph.addNode('fetch-sales')
166
+ graph.addNode('fetch-crm')
167
+ graph.addNode('fetch-inventory')
168
+ graph.addNode('fetch-analytics')
169
+
170
+ // Level 1: Aggregation (depends on all fetches)
171
+ graph.addNode('aggregate-data', {
172
+ dependsOn: ['fetch-sales', 'fetch-crm', 'fetch-inventory', 'fetch-analytics'],
173
+ })
174
+
175
+ // Level 2: Report generation (depends on aggregation)
176
+ graph.addNode('generate-daily-report', { dependsOn: 'aggregate-data' })
177
+ graph.addNode('generate-summary', { dependsOn: 'aggregate-data' })
178
+
179
+ // Level 3: Distribution (depends on report generation)
180
+ graph.addNode('distribute-email', { dependsOn: 'generate-daily-report' })
181
+ graph.addNode('distribute-slack', { dependsOn: 'generate-daily-report' })
182
+ graph.addNode('archive-s3', { dependsOn: ['generate-daily-report', 'generate-summary'] })
183
+
184
+ return graph
185
+ }
186
+
187
+ // ============================================================================
188
+ // Pipeline Executor
189
+ // ============================================================================
190
+
191
+ async function executePipeline(
192
+ graph: DependencyGraph,
193
+ $: WorkflowContext
194
+ ): Promise<Map<string, unknown>> {
195
+ const results = new Map<string, unknown>()
196
+ const levels = getExecutionLevels(
197
+ graph.getNodes().map((n) => ({ id: n.id, dependencies: n.dependencies }))
198
+ )
199
+
200
+ $.log(`Executing pipeline with ${levels.length} levels`)
201
+
202
+ for (const level of levels) {
203
+ $.log(`Level ${level.level}: ${level.nodes.join(', ')}`)
204
+
205
+ // Execute all nodes at this level in parallel
206
+ const levelResults = await Promise.all(
207
+ level.nodes.map(async (nodeId) => {
208
+ const result = await executeStep(nodeId, results, $)
209
+ return { nodeId, result }
210
+ })
211
+ )
212
+
213
+ // Store results
214
+ for (const { nodeId, result } of levelResults) {
215
+ results.set(nodeId, result)
216
+ }
217
+ }
218
+
219
+ return results
220
+ }
221
+
222
+ async function executeStep(
223
+ stepId: string,
224
+ previousResults: Map<string, unknown>,
225
+ $: WorkflowContext
226
+ ): Promise<unknown> {
227
+ switch (stepId) {
228
+ // Fetch steps
229
+ case 'fetch-sales':
230
+ return dataService.fetchFromSource(dataSources[0]!)
231
+ case 'fetch-crm':
232
+ return dataService.fetchFromSource(dataSources[1]!)
233
+ case 'fetch-inventory':
234
+ return dataService.fetchFromSource(dataSources[2]!)
235
+ case 'fetch-analytics':
236
+ return dataService.fetchFromSource(dataSources[3]!)
237
+
238
+ // Aggregation
239
+ case 'aggregate-data': {
240
+ const fetchedData = [
241
+ previousResults.get('fetch-sales'),
242
+ previousResults.get('fetch-crm'),
243
+ previousResults.get('fetch-inventory'),
244
+ previousResults.get('fetch-analytics'),
245
+ ].filter(Boolean) as FetchedData[]
246
+
247
+ $.log(` Aggregating ${fetchedData.length} data sources`)
248
+ return aggregationService.aggregate(fetchedData, 'daily')
249
+ }
250
+
251
+ // Report generation
252
+ case 'generate-daily-report': {
253
+ const metrics = previousResults.get('aggregate-data') as AggregatedMetrics
254
+ $.log(` Generating daily report`)
255
+ return reportService.generateReport('daily', metrics)
256
+ }
257
+ case 'generate-summary': {
258
+ const metrics = previousResults.get('aggregate-data') as AggregatedMetrics
259
+ $.log(` Generating summary report`)
260
+ return reportService.generateReport('weekly', metrics)
261
+ }
262
+
263
+ // Distribution
264
+ case 'distribute-email': {
265
+ const report = previousResults.get('generate-daily-report') as Report
266
+ $.log(` Distributing via email`)
267
+ return distributionService.distribute(report, 'email')
268
+ }
269
+ case 'distribute-slack': {
270
+ const report = previousResults.get('generate-daily-report') as Report
271
+ $.log(` Distributing via Slack`)
272
+ return distributionService.distribute(report, 'slack')
273
+ }
274
+ case 'archive-s3': {
275
+ const report = previousResults.get('generate-daily-report') as Report
276
+ $.log(` Archiving to S3`)
277
+ return distributionService.distribute(report, 's3')
278
+ }
279
+
280
+ default:
281
+ throw new Error(`Unknown step: ${stepId}`)
282
+ }
283
+ }
284
+
285
+ // ============================================================================
286
+ // Barrier Example: Parallel Data Collection
287
+ // ============================================================================
288
+
289
+ async function collectDataWithBarrier($: WorkflowContext): Promise<FetchedData[]> {
290
+ $.log('Starting parallel data collection with barrier...')
291
+
292
+ const barrier = new Barrier<FetchedData>(dataSources.length, {
293
+ timeout: 5000,
294
+ onProgress: (progress) => {
295
+ $.log(
296
+ ` Collection progress: ${progress.arrived}/${progress.expected} (${progress.percentage}%)`
297
+ )
298
+ },
299
+ })
300
+
301
+ // Start all fetches
302
+ const fetchPromises = dataSources.map(async (source) => {
303
+ const data = await dataService.fetchFromSource(source)
304
+ barrier.arrive(data)
305
+ return data
306
+ })
307
+
308
+ // Wait for all to complete
309
+ const results = await barrier.wait()
310
+ $.log(`Barrier complete: collected ${results.length} datasets`)
311
+
312
+ return results
313
+ }
314
+
315
+ // ============================================================================
316
+ // Concurrency Control Example
317
+ // ============================================================================
318
+
319
+ async function distributeToBulkRecipients(
320
+ report: Report,
321
+ recipients: string[],
322
+ $: WorkflowContext
323
+ ): Promise<DistributionResult[]> {
324
+ $.log(`Distributing to ${recipients.length} recipients with concurrency limit...`)
325
+
326
+ // Create distribution tasks
327
+ const tasks = recipients.map((recipient) => async () => {
328
+ await new Promise((resolve) => setTimeout(resolve, 20)) // Simulate network
329
+ return {
330
+ reportId: report.id,
331
+ channel: 'email' as const,
332
+ success: true,
333
+ deliveredAt: Date.now(),
334
+ }
335
+ })
336
+
337
+ // Execute with max 3 concurrent operations
338
+ const results = await withConcurrencyLimit<DistributionResult>(tasks, 3, {
339
+ collectErrors: true,
340
+ })
341
+
342
+ const successful = results.filter((r) => !(r instanceof Error)).length
343
+ $.log(`Distributed to ${successful}/${recipients.length} recipients`)
344
+
345
+ return results as DistributionResult[]
346
+ }
347
+
348
+ // ============================================================================
349
+ // Scheduled Workflow
350
+ // ============================================================================
351
+
352
+ function createScheduledReportingWorkflow() {
353
+ const graph = createReportingPipeline()
354
+
355
+ const workflow = Workflow(($) => {
356
+ // Show graph structure
357
+ $.log('Pipeline dependency graph created:')
358
+ const levels = getExecutionLevels(
359
+ graph.getNodes().map((n) => ({ id: n.id, dependencies: n.dependencies }))
360
+ )
361
+ for (const level of levels) {
362
+ $.log(` Level ${level.level}: ${level.nodes.join(', ')}`)
363
+ }
364
+
365
+ // Manual trigger for demo
366
+ $.on.Report.triggerDaily(async (_data: unknown, $: WorkflowContext) => {
367
+ $.log('')
368
+ $.log('=== Daily Report Pipeline Started ===')
369
+ const startTime = Date.now()
370
+
371
+ try {
372
+ const results = await executePipeline(graph, $)
373
+
374
+ const duration = Date.now() - startTime
375
+ $.log(`=== Pipeline complete in ${duration}ms ===`)
376
+
377
+ // Emit completion event
378
+ $.send('Report.completed', {
379
+ type: 'daily',
380
+ duration,
381
+ steps: results.size,
382
+ })
383
+ } catch (error) {
384
+ $.log(`Pipeline failed: ${error}`)
385
+ $.send('Report.failed', {
386
+ type: 'daily',
387
+ error: error instanceof Error ? error.message : String(error),
388
+ })
389
+ }
390
+ })
391
+
392
+ // Barrier-based collection trigger
393
+ $.on.Data.collectWithBarrier(async (_data: unknown, $: WorkflowContext) => {
394
+ $.log('')
395
+ $.log('=== Barrier-based Data Collection ===')
396
+ const data = await collectDataWithBarrier($)
397
+ $.log(`Collected ${data.length} datasets`)
398
+ })
399
+
400
+ // Bulk distribution trigger
401
+ $.on.Report.bulkDistribute(
402
+ async (data: { report: Report; recipients: string[] }, $: WorkflowContext) => {
403
+ $.log('')
404
+ $.log('=== Bulk Distribution with Concurrency Control ===')
405
+ await distributeToBulkRecipients(data.report, data.recipients, $)
406
+ }
407
+ )
408
+
409
+ $.on.Report.completed(
410
+ async (data: { type: string; duration: number; steps: number }, $: WorkflowContext) => {
411
+ $.log(`Report ${data.type} completed: ${data.steps} steps in ${data.duration}ms`)
412
+ }
413
+ )
414
+ })
415
+
416
+ return workflow
417
+ }
418
+
419
+ // ============================================================================
420
+ // Demo Execution
421
+ // ============================================================================
422
+
423
+ async function runDemo() {
424
+ console.log('='.repeat(60))
425
+ console.log('Scheduled Reporting with Dependency Chains Demo')
426
+ console.log('='.repeat(60))
427
+ console.log()
428
+
429
+ // Show topological sort
430
+ console.log('Topological Sort of Pipeline:')
431
+ const graph = createReportingPipeline()
432
+ const { order } = topologicalSort(
433
+ graph.getNodes().map((n) => ({ id: n.id, dependencies: n.dependencies }))
434
+ )
435
+ console.log(' Execution order:', order.join(' -> '))
436
+ console.log()
437
+
438
+ // Show DOT representation
439
+ console.log('Graph visualization (DOT format):')
440
+ console.log(graph.toDot())
441
+ console.log()
442
+
443
+ // Create and run workflow
444
+ const workflow = createScheduledReportingWorkflow()
445
+ await workflow.start()
446
+
447
+ // Trigger daily report
448
+ await workflow.send('Report.triggerDaily', {})
449
+ await new Promise((resolve) => setTimeout(resolve, 500))
450
+
451
+ // Trigger barrier-based collection
452
+ await workflow.send('Data.collectWithBarrier', {})
453
+ await new Promise((resolve) => setTimeout(resolve, 500))
454
+
455
+ // Trigger bulk distribution
456
+ const mockReport: Report = {
457
+ id: 'report-demo',
458
+ type: 'daily',
459
+ metrics: {
460
+ period: 'daily',
461
+ totalRecords: 100,
462
+ sumValues: 50000,
463
+ avgValue: 500,
464
+ sources: ['demo'],
465
+ },
466
+ generatedAt: Date.now(),
467
+ }
468
+ await workflow.send('Report.bulkDistribute', {
469
+ report: mockReport,
470
+ recipients: Array.from({ length: 10 }, (_, i) => `user${i}@example.com`),
471
+ })
472
+ await new Promise((resolve) => setTimeout(resolve, 300))
473
+
474
+ // Clean up
475
+ await workflow.stop()
476
+ }
477
+
478
+ // Run if executed directly
479
+ runDemo().catch(console.error)