ai-workflows 2.1.3 → 2.4.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 +14 -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,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)
|