yaml-flow 3.0.0 → 3.1.1

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 (59) hide show
  1. package/README.md +44 -23
  2. package/dist/{constants-B_ftYTTE.d.ts → constants-B2zqu10b.d.ts} +7 -57
  3. package/dist/{constants-CiyHX8L-.d.cts → constants-DJZU1pwJ.d.cts} +7 -57
  4. package/dist/continuous-event-graph/index.cjs +1161 -182
  5. package/dist/continuous-event-graph/index.cjs.map +1 -1
  6. package/dist/continuous-event-graph/index.d.cts +567 -48
  7. package/dist/continuous-event-graph/index.d.ts +567 -48
  8. package/dist/continuous-event-graph/index.js +1151 -183
  9. package/dist/continuous-event-graph/index.js.map +1 -1
  10. package/dist/event-graph/index.cjs +35 -11
  11. package/dist/event-graph/index.cjs.map +1 -1
  12. package/dist/event-graph/index.d.cts +14 -5
  13. package/dist/event-graph/index.d.ts +14 -5
  14. package/dist/event-graph/index.js +34 -11
  15. package/dist/event-graph/index.js.map +1 -1
  16. package/dist/index.cjs +945 -414
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +5 -4
  19. package/dist/index.d.ts +5 -4
  20. package/dist/index.js +936 -415
  21. package/dist/index.js.map +1 -1
  22. package/dist/inference/index.cjs +31 -7
  23. package/dist/inference/index.cjs.map +1 -1
  24. package/dist/inference/index.d.cts +2 -2
  25. package/dist/inference/index.d.ts +2 -2
  26. package/dist/inference/index.js +31 -7
  27. package/dist/inference/index.js.map +1 -1
  28. package/dist/{types-CxJg9Jrt.d.cts → types-BwvgvlOO.d.cts} +2 -2
  29. package/dist/{types-BuEo3wVG.d.ts → types-ClRA8hzC.d.ts} +2 -2
  30. package/dist/{types-BpWrH1sf.d.cts → types-DEj7OakX.d.cts} +14 -4
  31. package/dist/{types-BpWrH1sf.d.ts → types-DEj7OakX.d.ts} +14 -4
  32. package/dist/validate-DEZ2Ymdb.d.ts +53 -0
  33. package/dist/validate-DqKTZg_o.d.cts +53 -0
  34. package/examples/batch/batch-step-machine.ts +121 -0
  35. package/examples/browser/index.html +367 -0
  36. package/examples/continuous-event-graph/live-cards-board.ts +215 -0
  37. package/examples/continuous-event-graph/live-portfolio-dashboard.ts +555 -0
  38. package/examples/continuous-event-graph/portfolio-tracker.ts +287 -0
  39. package/examples/continuous-event-graph/reactive-monitoring.ts +265 -0
  40. package/examples/continuous-event-graph/reactive-pipeline.ts +168 -0
  41. package/examples/continuous-event-graph/soc-incident-board.ts +287 -0
  42. package/examples/continuous-event-graph/stock-dashboard.ts +229 -0
  43. package/examples/event-graph/ci-cd-pipeline.ts +243 -0
  44. package/examples/event-graph/executor-diamond.ts +165 -0
  45. package/examples/event-graph/executor-pipeline.ts +161 -0
  46. package/examples/event-graph/research-pipeline.ts +137 -0
  47. package/examples/flows/ai-conversation.yaml +116 -0
  48. package/examples/flows/order-processing.yaml +143 -0
  49. package/examples/flows/simple-greeting.yaml +54 -0
  50. package/examples/graph-of-graphs/multi-stage-etl.ts +307 -0
  51. package/examples/graph-of-graphs/url-processing-pipeline.ts +254 -0
  52. package/examples/inference/azure-deployment.ts +149 -0
  53. package/examples/inference/copilot-cli.ts +138 -0
  54. package/examples/inference/data-pipeline.ts +145 -0
  55. package/examples/inference/pluggable-adapters.ts +254 -0
  56. package/examples/ingest.js +733 -0
  57. package/examples/node/ai-conversation.ts +195 -0
  58. package/examples/node/simple-greeting.ts +101 -0
  59. package/package.json +3 -2
@@ -0,0 +1,116 @@
1
+ # yaml-language-server: $schema=../schema/flow.schema.json
2
+
3
+ # AI Conversation Flow with Validation Loop
4
+ # Demonstrates retry logic, circuit breakers, and multi-step AI workflows
5
+
6
+ settings:
7
+ start_step: generate_response
8
+ max_total_steps: 50
9
+ timeout_ms: 60000
10
+
11
+ steps:
12
+ generate_response:
13
+ description: "Generate AI response based on user input"
14
+ expects_data:
15
+ - user_message
16
+ - conversation_history
17
+ produces_data:
18
+ - ai_response
19
+ - confidence_score
20
+ transitions:
21
+ success: validate_response
22
+ failure: error_state
23
+ retry:
24
+ max_attempts: 3
25
+ delay_ms: 1000
26
+ backoff_multiplier: 2
27
+
28
+ validate_response:
29
+ description: "Validate the AI response meets quality criteria"
30
+ expects_data:
31
+ - ai_response
32
+ - confidence_score
33
+ produces_data:
34
+ - validation_result
35
+ - validation_feedback
36
+ transitions:
37
+ valid: check_approval
38
+ needs_refinement: refine_response
39
+ failure: error_state
40
+
41
+ refine_response:
42
+ description: "Refine AI response based on validation feedback"
43
+ expects_data:
44
+ - ai_response
45
+ - validation_feedback
46
+ - user_message
47
+ produces_data:
48
+ - ai_response
49
+ - confidence_score
50
+ transitions:
51
+ success: validate_response
52
+ failure: error_state
53
+ circuit_breaker:
54
+ max_iterations: 3
55
+ on_open: max_refinements
56
+
57
+ check_approval:
58
+ description: "Present response and check for user approval"
59
+ expects_data:
60
+ - ai_response
61
+ produces_data:
62
+ - user_approved
63
+ - user_feedback
64
+ transitions:
65
+ approved: success_state
66
+ rejected: incorporate_feedback
67
+ timeout: timeout_state
68
+
69
+ incorporate_feedback:
70
+ description: "Incorporate user feedback into response"
71
+ expects_data:
72
+ - ai_response
73
+ - user_feedback
74
+ - user_message
75
+ produces_data:
76
+ - ai_response
77
+ - confidence_score
78
+ transitions:
79
+ success: validate_response
80
+ failure: error_state
81
+ circuit_breaker:
82
+ max_iterations: 5
83
+ on_open: max_feedback_loops
84
+
85
+ terminal_states:
86
+ success_state:
87
+ description: "User approved the AI response"
88
+ return_intent: "approved"
89
+ return_artifacts:
90
+ - ai_response
91
+ - conversation_history
92
+
93
+ error_state:
94
+ description: "Flow encountered an error"
95
+ return_intent: "error"
96
+ return_artifacts: false
97
+
98
+ timeout_state:
99
+ description: "User did not respond in time"
100
+ return_intent: "timeout"
101
+ return_artifacts:
102
+ - ai_response
103
+
104
+ max_refinements:
105
+ description: "Maximum refinement attempts reached"
106
+ return_intent: "max_refinements"
107
+ return_artifacts:
108
+ - ai_response
109
+ - validation_feedback
110
+
111
+ max_feedback_loops:
112
+ description: "Maximum feedback incorporation attempts reached"
113
+ return_intent: "max_feedback"
114
+ return_artifacts:
115
+ - ai_response
116
+ - user_feedback
@@ -0,0 +1,143 @@
1
+ # yaml-language-server: $schema=../schema/flow.schema.json
2
+
3
+ # Order Processing Workflow
4
+ # Demonstrates a practical e-commerce order flow with inventory, payment, and shipping
5
+
6
+ settings:
7
+ start_step: validate_order
8
+ max_total_steps: 20
9
+ timeout_ms: 120000
10
+
11
+ steps:
12
+ validate_order:
13
+ description: "Validate order details"
14
+ expects_data:
15
+ - order
16
+ produces_data:
17
+ - validated_order
18
+ - validation_errors
19
+ transitions:
20
+ valid: check_inventory
21
+ invalid: order_invalid
22
+ failure: error_state
23
+
24
+ check_inventory:
25
+ description: "Check product availability"
26
+ expects_data:
27
+ - validated_order
28
+ produces_data:
29
+ - inventory_status
30
+ - reserved_items
31
+ transitions:
32
+ available: process_payment
33
+ partial: partial_availability
34
+ unavailable: out_of_stock
35
+ failure: error_state
36
+ retry:
37
+ max_attempts: 2
38
+ delay_ms: 500
39
+
40
+ partial_availability:
41
+ description: "Handle partial availability - ask user to proceed or cancel"
42
+ expects_data:
43
+ - validated_order
44
+ - inventory_status
45
+ produces_data:
46
+ - user_decision
47
+ - adjusted_order
48
+ transitions:
49
+ proceed: process_payment
50
+ cancel: order_cancelled
51
+ failure: error_state
52
+
53
+ process_payment:
54
+ description: "Process payment for the order"
55
+ expects_data:
56
+ - validated_order
57
+ - reserved_items
58
+ produces_data:
59
+ - payment_result
60
+ - transaction_id
61
+ transitions:
62
+ success: create_shipment
63
+ declined: payment_failed
64
+ failure: error_state
65
+ retry:
66
+ max_attempts: 3
67
+ delay_ms: 2000
68
+ backoff_multiplier: 1.5
69
+
70
+ create_shipment:
71
+ description: "Create shipping label and schedule pickup"
72
+ expects_data:
73
+ - validated_order
74
+ - transaction_id
75
+ produces_data:
76
+ - shipment_id
77
+ - tracking_number
78
+ - estimated_delivery
79
+ transitions:
80
+ success: order_complete
81
+ failure: shipment_error
82
+
83
+ shipment_error:
84
+ description: "Handle shipment creation failure - refund and notify"
85
+ expects_data:
86
+ - transaction_id
87
+ - validated_order
88
+ produces_data:
89
+ - refund_status
90
+ transitions:
91
+ refunded: order_refunded
92
+ failure: manual_intervention
93
+
94
+ terminal_states:
95
+ order_complete:
96
+ description: "Order successfully processed"
97
+ return_intent: "success"
98
+ return_artifacts:
99
+ - transaction_id
100
+ - tracking_number
101
+ - estimated_delivery
102
+
103
+ order_invalid:
104
+ description: "Order validation failed"
105
+ return_intent: "invalid_order"
106
+ return_artifacts:
107
+ - validation_errors
108
+
109
+ out_of_stock:
110
+ description: "Products out of stock"
111
+ return_intent: "out_of_stock"
112
+ return_artifacts:
113
+ - inventory_status
114
+
115
+ order_cancelled:
116
+ description: "Order cancelled by user"
117
+ return_intent: "cancelled"
118
+ return_artifacts: false
119
+
120
+ payment_failed:
121
+ description: "Payment was declined"
122
+ return_intent: "payment_declined"
123
+ return_artifacts:
124
+ - payment_result
125
+
126
+ order_refunded:
127
+ description: "Order refunded due to shipment failure"
128
+ return_intent: "refunded"
129
+ return_artifacts:
130
+ - refund_status
131
+ - transaction_id
132
+
133
+ manual_intervention:
134
+ description: "Requires manual intervention"
135
+ return_intent: "manual_required"
136
+ return_artifacts:
137
+ - transaction_id
138
+ - validated_order
139
+
140
+ error_state:
141
+ description: "System error"
142
+ return_intent: "error"
143
+ return_artifacts: false
@@ -0,0 +1,54 @@
1
+ # yaml-language-server: $schema=../schema/flow.schema.json
2
+
3
+ # Simple Greeting Flow
4
+ # Demonstrates basic flow structure with validation and error handling
5
+
6
+ settings:
7
+ start_step: greet
8
+ max_total_steps: 10
9
+
10
+ steps:
11
+ greet:
12
+ description: "Generate a greeting message"
13
+ produces_data:
14
+ - greeting
15
+ - user_name
16
+ transitions:
17
+ success: validate
18
+ failure: error_state
19
+
20
+ validate:
21
+ description: "Validate the greeting was generated correctly"
22
+ expects_data:
23
+ - greeting
24
+ - user_name
25
+ produces_data:
26
+ - is_valid
27
+ transitions:
28
+ success: personalize
29
+ invalid: error_state
30
+ failure: error_state
31
+
32
+ personalize:
33
+ description: "Create personalized response"
34
+ expects_data:
35
+ - greeting
36
+ - user_name
37
+ produces_data:
38
+ - final_message
39
+ transitions:
40
+ success: success_state
41
+ failure: error_state
42
+
43
+ terminal_states:
44
+ success_state:
45
+ description: "Flow completed successfully"
46
+ return_intent: "success"
47
+ return_artifacts:
48
+ - final_message
49
+ - user_name
50
+
51
+ error_state:
52
+ description: "Flow encountered an error"
53
+ return_intent: "error"
54
+ return_artifacts: false
@@ -0,0 +1,307 @@
1
+ /**
2
+ * Graph-of-Graphs Example: Multi-Stage ETL Pipeline
3
+ *
4
+ * An outer event-graph orchestrates an ETL pipeline where:
5
+ * - "extract" stage: batch × inner EVENT-GRAPH (parallel source extraction)
6
+ * - "transform" stage: batch × inner STEP-MACHINE (sequential validation pipeline)
7
+ * - "load" + "validate" stages: run in parallel after transform
8
+ *
9
+ * Demonstrates:
10
+ * - Mixed sub-graph modes (event-graph + step-machine in same pipeline)
11
+ * - Config templates shared across both inner configs
12
+ * - Variables resolved per-item in the batch
13
+ * - Fan-out / fan-in in the outer graph
14
+ *
15
+ * Outer graph:
16
+ * discover-sources → extract-batch → transform-batch → [load ∥ validate] → finalize
17
+ *
18
+ * Inner extract graph (event-graph, per source):
19
+ * connect → [fetch-metadata ∥ fetch-schema] → snapshot-data
20
+ *
21
+ * Inner transform flow (step-machine, per record):
22
+ * parse → validate → normalize → enrich → (accept | reject)
23
+ *
24
+ * Run with: npx tsx examples/graph-of-graphs/multi-stage-etl.ts
25
+ */
26
+
27
+ import {
28
+ next, apply, createInitialExecutionState,
29
+ } from '../../src/event-graph/index.js';
30
+ import { createStepMachine } from '../../src/step-machine/index.js';
31
+ import { batch } from '../../src/batch/index.js';
32
+ import { resolveVariables, resolveConfigTemplates } from '../../src/config/index.js';
33
+ import type { GraphConfig } from '../../src/event-graph/types.js';
34
+ import type { StepFlowConfig, StepHandler } from '../../src/step-machine/types.js';
35
+
36
+ // ============================================================================
37
+ // 1. Inner configs
38
+ // ============================================================================
39
+
40
+ // --- Extract: event-graph (parallel metadata + schema fetch) ---
41
+ const extractGraphTemplate: Record<string, unknown> = {
42
+ id: 'source-extractor',
43
+ 'config-templates': {
44
+ DB_CONN: { driver: 'pg', timeout: 10000, host: '${DB_HOST}' },
45
+ },
46
+ settings: {
47
+ completion: 'all-tasks-complete' as const,
48
+ },
49
+ tasks: {
50
+ connect: {
51
+ provides: ['connected'],
52
+ config: { 'config-template': 'DB_CONN', database: '${SOURCE_DB}' },
53
+ },
54
+ 'fetch-metadata': {
55
+ requires: ['connected'],
56
+ provides: ['metadata-ready'],
57
+ config: { 'config-template': 'DB_CONN', query: 'SELECT * FROM information_schema.tables' },
58
+ },
59
+ 'fetch-schema': {
60
+ requires: ['connected'],
61
+ provides: ['schema-ready'],
62
+ config: { 'config-template': 'DB_CONN', query: 'SELECT * FROM information_schema.columns' },
63
+ },
64
+ 'snapshot-data': {
65
+ requires: ['metadata-ready', 'schema-ready'],
66
+ provides: ['snapshot-complete'],
67
+ config: { 'config-template': 'DB_CONN', 'cmd-args': 'pg_dump ${SOURCE_DB}' },
68
+ },
69
+ },
70
+ };
71
+
72
+ // --- Transform: step-machine (sequential validation pipeline) ---
73
+ const transformFlow: StepFlowConfig = {
74
+ id: 'record-transformer',
75
+ settings: { start_step: 'parse', max_total_steps: 10 },
76
+ steps: {
77
+ parse: {
78
+ produces_data: ['parsed_record'],
79
+ transitions: { success: 'validate', error: 'reject' },
80
+ },
81
+ validate: {
82
+ expects_data: ['parsed_record'],
83
+ produces_data: ['validation_result'],
84
+ transitions: { valid: 'normalize', invalid: 'reject' },
85
+ },
86
+ normalize: {
87
+ expects_data: ['parsed_record'],
88
+ produces_data: ['normalized_record'],
89
+ transitions: { done: 'enrich' },
90
+ },
91
+ enrich: {
92
+ expects_data: ['normalized_record'],
93
+ produces_data: ['enriched_record'],
94
+ transitions: { done: 'accept' },
95
+ },
96
+ },
97
+ terminal_states: {
98
+ accept: { return_intent: 'accepted', return_artifacts: ['enriched_record'] },
99
+ reject: { return_intent: 'rejected', return_artifacts: ['validation_result'] },
100
+ },
101
+ };
102
+
103
+ const transformHandlers: Record<string, StepHandler> = {
104
+ parse: async (input) => {
105
+ const raw = input.raw_data as string || '';
106
+ if (!raw) return { result: 'error' };
107
+ return { result: 'success', data: { parsed_record: JSON.parse(raw) } };
108
+ },
109
+ validate: async (input) => {
110
+ const rec = input.parsed_record as Record<string, unknown>;
111
+ if (!rec.id || !rec.name) return { result: 'invalid', data: { validation_result: 'missing required fields' } };
112
+ return { result: 'valid', data: { validation_result: 'ok' } };
113
+ },
114
+ normalize: async (input) => {
115
+ const rec = input.parsed_record as Record<string, unknown>;
116
+ return { result: 'done', data: { normalized_record: { ...rec, name: (rec.name as string).trim().toLowerCase() } } };
117
+ },
118
+ enrich: async (input) => {
119
+ const rec = input.normalized_record as Record<string, unknown>;
120
+ return { result: 'done', data: { enriched_record: { ...rec, enriched_at: new Date().toISOString() } } };
121
+ },
122
+ };
123
+
124
+ // ============================================================================
125
+ // 2. Outer graph
126
+ // ============================================================================
127
+
128
+ const outerGraph: GraphConfig = {
129
+ id: 'etl-pipeline',
130
+ settings: { completion: 'all-tasks-complete' },
131
+ tasks: {
132
+ 'discover-sources': {
133
+ provides: ['sources-discovered'],
134
+ },
135
+ 'extract-batch': {
136
+ requires: ['sources-discovered'],
137
+ provides: ['extraction-complete'],
138
+ },
139
+ 'transform-batch': {
140
+ requires: ['extraction-complete'],
141
+ provides: ['transform-complete'],
142
+ },
143
+ 'load-to-warehouse': {
144
+ requires: ['transform-complete'],
145
+ provides: ['load-complete'],
146
+ },
147
+ 'validate-integrity': {
148
+ requires: ['transform-complete'],
149
+ provides: ['validation-complete'],
150
+ },
151
+ 'finalize': {
152
+ requires: ['load-complete', 'validation-complete'],
153
+ provides: ['pipeline-done'],
154
+ },
155
+ },
156
+ };
157
+
158
+ // ============================================================================
159
+ // 3. Sub-graph drivers
160
+ // ============================================================================
161
+
162
+ /** Drive one source through the extract event-graph */
163
+ async function runExtractGraph(source: { id: string; db: string }) {
164
+ const config = resolveVariables(
165
+ resolveConfigTemplates(extractGraphTemplate),
166
+ { SOURCE_DB: source.db, DB_HOST: 'db.internal' },
167
+ ) as unknown as GraphConfig;
168
+
169
+ let state = createInitialExecutionState(config, `extract-${source.id}`);
170
+ while (true) {
171
+ const { eligibleTasks, isComplete } = next(config, state);
172
+ if (isComplete || eligibleTasks.length === 0) break;
173
+ await Promise.all(
174
+ eligibleTasks.map(async (taskName) => {
175
+ state = apply(state, { type: 'task-started', taskName, timestamp: new Date().toISOString() }, config);
176
+ await new Promise((r) => setTimeout(r, 5 + Math.random() * 15)); // simulate work
177
+ state = apply(state, { type: 'task-completed', taskName, timestamp: new Date().toISOString() }, config);
178
+ }),
179
+ );
180
+ }
181
+ return { sourceId: source.id, tokens: state.availableOutputs };
182
+ }
183
+
184
+ /** Drive one record through the transform step-machine */
185
+ async function runTransformFlow(record: { id: string; raw_data: string }) {
186
+ const machine = createStepMachine(transformFlow, transformHandlers);
187
+ return machine.run({ raw_data: record.raw_data });
188
+ }
189
+
190
+ // ============================================================================
191
+ // 4. Sample data
192
+ // ============================================================================
193
+
194
+ const sources = [
195
+ { id: 'src-orders', db: 'orders_db' },
196
+ { id: 'src-users', db: 'users_db' },
197
+ { id: 'src-products', db: 'products_db' },
198
+ ];
199
+
200
+ const records = [
201
+ { id: 'rec-1', raw_data: '{"id": 1, "name": " Alice "}' },
202
+ { id: 'rec-2', raw_data: '{"id": 2, "name": " Bob "}' },
203
+ { id: 'rec-3', raw_data: '{"name": "no-id"}' }, // will be rejected (no id)
204
+ { id: 'rec-4', raw_data: '' }, // will fail to parse
205
+ { id: 'rec-5', raw_data: '{"id": 5, "name": " Charlie "}' },
206
+ { id: 'rec-6', raw_data: '{"id": 6, "name": " Diana "}' },
207
+ ];
208
+
209
+ // ============================================================================
210
+ // 5. Outer graph handlers
211
+ // ============================================================================
212
+
213
+ const outerHandlers: Record<string, () => Promise<void>> = {
214
+ 'discover-sources': async () => {
215
+ console.log(` Found ${sources.length} data sources`);
216
+ },
217
+
218
+ 'extract-batch': async () => {
219
+ console.log(` Extracting from ${sources.length} sources (concurrency: 2, mode: event-graph)`);
220
+ const result = await batch(sources, {
221
+ concurrency: 2,
222
+ processor: runExtractGraph,
223
+ onItemComplete: (src, res) => {
224
+ console.log(` ✓ ${src.id} (${src.db}): [${res.tokens.join(', ')}]`);
225
+ },
226
+ });
227
+ console.log(` Extract done: ${result.completed}/${result.total} in ${result.durationMs}ms`);
228
+ },
229
+
230
+ 'transform-batch': async () => {
231
+ console.log(` Transforming ${records.length} records (concurrency: 4, mode: step-machine)`);
232
+ const result = await batch(records, {
233
+ concurrency: 4,
234
+ processor: runTransformFlow,
235
+ onItemComplete: (rec, res) => {
236
+ console.log(` ✓ ${rec.id}: ${res.intent} — [${res.stepHistory.join(' → ')}]`);
237
+ },
238
+ onItemError: (rec, err) => {
239
+ console.log(` ✗ ${rec.id}: ${err.message}`);
240
+ },
241
+ });
242
+ console.log(` Transform done: ${result.completed} ok, ${result.failed} failed in ${result.durationMs}ms`);
243
+
244
+ // Show accepted vs rejected
245
+ const accepted = result.items.filter((i) => i.status === 'completed' && i.result?.intent === 'accepted');
246
+ const rejected = result.items.filter((i) => i.status === 'completed' && i.result?.intent === 'rejected');
247
+ console.log(` Accepted: ${accepted.length}, Rejected: ${rejected.length}`);
248
+ },
249
+
250
+ 'load-to-warehouse': async () => {
251
+ console.log(' Loading accepted records to data warehouse');
252
+ await new Promise((r) => setTimeout(r, 20));
253
+ },
254
+
255
+ 'validate-integrity': async () => {
256
+ console.log(' Running integrity checks on loaded data');
257
+ await new Promise((r) => setTimeout(r, 15));
258
+ },
259
+
260
+ 'finalize': async () => {
261
+ console.log(' Generating ETL summary report');
262
+ },
263
+ };
264
+
265
+ // ============================================================================
266
+ // 6. Drive outer graph
267
+ // ============================================================================
268
+
269
+ async function main() {
270
+ console.log('=== Multi-Stage ETL Pipeline (Graph-of-Graphs) ===');
271
+ console.log('Outer: event-graph | Extract sub: event-graph | Transform sub: step-machine\n');
272
+
273
+ let state = createInitialExecutionState(outerGraph, 'etl-run-1');
274
+ const now = () => new Date().toISOString();
275
+
276
+ while (true) {
277
+ const { eligibleTasks, isComplete } = next(outerGraph, state);
278
+ if (isComplete) break;
279
+ if (eligibleTasks.length === 0) {
280
+ console.log('\nPipeline stuck!');
281
+ break;
282
+ }
283
+
284
+ // Note: load-to-warehouse and validate-integrity will run in PARALLEL
285
+ // because they both only require transform-complete
286
+ if (eligibleTasks.length > 1) {
287
+ console.log(`\n▶ [parallel] ${eligibleTasks.join(' + ')}`);
288
+ }
289
+ await Promise.all(
290
+ eligibleTasks.map(async (taskName) => {
291
+ if (eligibleTasks.length === 1) console.log(`\n▶ ${taskName}`);
292
+ state = apply(state, { type: 'task-started', taskName, timestamp: now() }, outerGraph);
293
+ try {
294
+ await outerHandlers[taskName]();
295
+ state = apply(state, { type: 'task-completed', taskName, timestamp: now() }, outerGraph);
296
+ } catch (err: any) {
297
+ state = apply(state, { type: 'task-failed', taskName, error: err.message, timestamp: now() }, outerGraph);
298
+ }
299
+ }),
300
+ );
301
+ }
302
+
303
+ console.log('\n=== ETL Pipeline Complete ===');
304
+ console.log('Final tokens:', state.availableOutputs);
305
+ }
306
+
307
+ main().catch(console.error);