@workflow/world-testing 5.0.0-beta.4 → 5.0.0-beta.6

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../src/index.mts"],"names":[],"mappings":"AAMA,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,QAM9C"}
1
+ {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../src/index.mts"],"names":[],"mappings":"AAOA,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,QAO9C"}
@@ -2,6 +2,7 @@ import { addition } from './addition.mjs';
2
2
  import { errors } from './errors.mjs';
3
3
  import { hooks } from './hooks.mjs';
4
4
  import { idempotency } from './idempotency.mjs';
5
+ import { inlineExecution } from './inline-execution.mjs';
5
6
  import { nullByte } from './null-byte.mjs';
6
7
  export function createTestSuite(pkgName) {
7
8
  addition(pkgName);
@@ -9,5 +10,6 @@ export function createTestSuite(pkgName) {
9
10
  hooks(pkgName);
10
11
  nullByte(pkgName);
11
12
  errors(pkgName);
13
+ inlineExecution(pkgName);
12
14
  }
13
15
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../../src/index.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE3C,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,QAAQ,CAAC,OAAO,CAAC,CAAC;IAClB,WAAW,CAAC,OAAO,CAAC,CAAC;IACrB,KAAK,CAAC,OAAO,CAAC,CAAC;IACf,QAAQ,CAAC,OAAO,CAAC,CAAC;IAClB,MAAM,CAAC,OAAO,CAAC,CAAC;AAClB,CAAC","sourcesContent":["import { addition } from './addition.mjs';\nimport { errors } from './errors.mjs';\nimport { hooks } from './hooks.mjs';\nimport { idempotency } from './idempotency.mjs';\nimport { nullByte } from './null-byte.mjs';\n\nexport function createTestSuite(pkgName: string) {\n addition(pkgName);\n idempotency(pkgName);\n hooks(pkgName);\n nullByte(pkgName);\n errors(pkgName);\n}\n"]}
1
+ {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../../src/index.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE3C,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,QAAQ,CAAC,OAAO,CAAC,CAAC;IAClB,WAAW,CAAC,OAAO,CAAC,CAAC;IACrB,KAAK,CAAC,OAAO,CAAC,CAAC;IACf,QAAQ,CAAC,OAAO,CAAC,CAAC;IAClB,MAAM,CAAC,OAAO,CAAC,CAAC;IAChB,eAAe,CAAC,OAAO,CAAC,CAAC;AAC3B,CAAC","sourcesContent":["import { addition } from './addition.mjs';\nimport { errors } from './errors.mjs';\nimport { hooks } from './hooks.mjs';\nimport { idempotency } from './idempotency.mjs';\nimport { inlineExecution } from './inline-execution.mjs';\nimport { nullByte } from './null-byte.mjs';\n\nexport function createTestSuite(pkgName: string) {\n addition(pkgName);\n idempotency(pkgName);\n hooks(pkgName);\n nullByte(pkgName);\n errors(pkgName);\n inlineExecution(pkgName);\n}\n"]}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Instrumented test: runs `threeBatchesOfFiveWorkflow` with
3
+ * DEBUG=workflow:runtime:* and prints a summary of V2 handler behavior:
4
+ * - total flow invocations
5
+ * - per-invocation replay iterations
6
+ * - event loads (full vs incremental) and sizes
7
+ * - redundant/skipped step executions
8
+ * - concurrent "all parallel steps done" replay races
9
+ * - unconsumed-event skips
10
+ *
11
+ * Not part of the default suite; run with:
12
+ * pnpm vitest run packages/world-testing/test/inline-batches-debug.test.ts
13
+ */
14
+ export declare function inlineBatchesDebug(world: string): void;
15
+ //# sourceMappingURL=inline-batches-debug.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inline-batches-debug.d.mts","sourceRoot":"","sources":["../../src/inline-batches-debug.mts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,QAuP/C"}
@@ -0,0 +1,208 @@
1
+ import { expect, test, vi } from 'vitest';
2
+ import { hydrateWorkflowReturnValue } from '@workflow/core/serialization';
3
+ import { createFetcher, startServer } from './util.mjs';
4
+ /**
5
+ * Instrumented test: runs `threeBatchesOfFiveWorkflow` with
6
+ * DEBUG=workflow:runtime:* and prints a summary of V2 handler behavior:
7
+ * - total flow invocations
8
+ * - per-invocation replay iterations
9
+ * - event loads (full vs incremental) and sizes
10
+ * - redundant/skipped step executions
11
+ * - concurrent "all parallel steps done" replay races
12
+ * - unconsumed-event skips
13
+ *
14
+ * Not part of the default suite; run with:
15
+ * pnpm vitest run packages/world-testing/test/inline-batches-debug.test.ts
16
+ */
17
+ export function inlineBatchesDebug(world) {
18
+ test('three batches of five parallel steps — report V2 handler behavior', { timeout: 60_000 }, async () => {
19
+ const server = await startServer({
20
+ world,
21
+ env: {
22
+ DEBUG: 'workflow:runtime:*',
23
+ },
24
+ });
25
+ const client = createFetcher(server);
26
+ const result = await client.invoke('workflows/inline-execution.ts', 'threeBatchesOfFiveWorkflow', []);
27
+ const run = await vi.waitFor(async () => {
28
+ const run = await client.getRun(result.runId);
29
+ expect(run.status).toBe('completed');
30
+ return run;
31
+ }, { interval: 200, timeout: 59_000 });
32
+ const output = await hydrateWorkflowReturnValue(run.output, run.runId, undefined);
33
+ // Expected output: 3 batches × [11, 21, 31, 41, 51] | [12, 22, ...] | ...
34
+ expect(output).toEqual([
35
+ 11, 21, 31, 41, 51, 12, 22, 32, 42, 52, 13, 23, 33, 43, 53,
36
+ ]);
37
+ const flowInvocations = await client.getFlowInvocationCount(result.runId);
38
+ // Wait a short moment to flush late log lines
39
+ await new Promise((r) => setTimeout(r, 500));
40
+ const logs = server.getOutput();
41
+ // --- Parse log lines ---
42
+ // 1) Replay iterations + the `eventCount` passed into each replay.
43
+ // The runtime logs: "Starting workflow replay" with `eventCount: N`.
44
+ const replayEventCounts = [];
45
+ for (const line of logs.split('\n')) {
46
+ if (!line.includes(result.runId))
47
+ continue;
48
+ if (!line.includes('Starting workflow replay'))
49
+ continue;
50
+ }
51
+ // eventCount is often on the next line in a multi-line object literal
52
+ // (console.debug prints the second argument with util.inspect). Pull all
53
+ // eventCount occurrences that are within ~6 lines of a replay start
54
+ // line referencing this runId.
55
+ const lines = logs.split('\n');
56
+ for (let i = 0; i < lines.length; i++) {
57
+ if (!lines[i].includes('Starting workflow replay'))
58
+ continue;
59
+ // find the runId in the surrounding 8 lines
60
+ const window = lines.slice(i, i + 8).join('\n');
61
+ if (!window.includes(result.runId))
62
+ continue;
63
+ const m = window.match(/eventCount:\s*(\d+)/);
64
+ if (m)
65
+ replayEventCounts.push(Number.parseInt(m[1], 10));
66
+ }
67
+ const replayStarts = replayEventCounts.length;
68
+ // 3) "All parallel steps done, replaying inline" (per-run)
69
+ const allDoneInlineReplays = matchCount(logs, /All parallel steps done, replaying inline/g, result.runId);
70
+ // 4) "Background step done but other steps pending" (per-run)
71
+ const backgroundStepPendingReturns = matchCount(logs, /Background step done but other steps pending/g, result.runId);
72
+ // 5) Step skipped because step was already in terminal state
73
+ // (background handler arrived after inline path already completed it).
74
+ // This is emitted from the step_started conflict path in step-executor.ts
75
+ // ("Step in terminal state, skipping"). The step_completed 409 path
76
+ // ("Tried completing step, but step has already finished") fires only
77
+ // when two handlers both ran the step body concurrently.
78
+ const stepsSkippedAlreadyDone = matchCount(logs, /Step in terminal state, skipping/g, result.runId);
79
+ const stepCompletedRaces = matchCount(logs, /Tried completing step, but step has already finished/g, result.runId);
80
+ // 6) Unconsumed event skips (debug message emitted by workflow.ts skip logic)
81
+ const unconsumedEventSkips = matchCount(logs, /Skipping unconsumed step event/g, result.runId);
82
+ // 7) Hook/step started with already-running: via concurrent handler
83
+ const maxDeliveries = matchCount(logs, /Max retries reached/g);
84
+ console.log('\n====================================');
85
+ console.log('V2 handler behavior — 3 batches × 5 steps');
86
+ console.log('====================================');
87
+ console.log(`Flow handler invocations: ${flowInvocations}`);
88
+ console.log(`Replay iterations (total): ${replayStarts}`);
89
+ console.log(`Event counts per replay: [${replayEventCounts.join(', ')}]`);
90
+ console.log(`"All steps done, replay inline" hits: ${allDoneInlineReplays}`);
91
+ console.log(`"Background step pending, return" hits: ${backgroundStepPendingReturns}`);
92
+ console.log(`Step skipped (already terminal, step_started 409): ${stepsSkippedAlreadyDone}`);
93
+ console.log(`Step body ran twice (step_completed 409): ${stepCompletedRaces}`);
94
+ console.log(`Unconsumed events skipped by consumer: ${unconsumedEventSkips}`);
95
+ console.log(`Max retries reached (bad): ${maxDeliveries}`);
96
+ console.log('====================================\n');
97
+ // Also print the key timeline: one line per "significant" runtime
98
+ // debug event, in order. Many debug lines put the runId in the
99
+ // metadata object on a following line, so look at a small forward
100
+ // window around each match.
101
+ const timeline = [];
102
+ const sigRegex = /Starting workflow replay|All parallel steps done|Background step done but|Workflow suspended|Workflow replay completed|Run already finished, skipping background step|Step in terminal state|Tried completing step|Max retries reached|Skipping unconsumed step event/;
103
+ for (let i = 0; i < lines.length; i++) {
104
+ if (!sigRegex.test(lines[i]))
105
+ continue;
106
+ const window = lines.slice(i, i + 8).join('\n');
107
+ if (!window.includes(result.runId))
108
+ continue;
109
+ timeline.push(lines[i]);
110
+ }
111
+ console.log('--- Timeline (runtime debug events for this run) ---');
112
+ for (const line of timeline) {
113
+ const short = line.replace(/\s+/g, ' ').slice(0, 180);
114
+ console.log(short);
115
+ }
116
+ console.log(`Timeline entries: ${timeline.length}`);
117
+ console.log('----------------------------------------------------\n');
118
+ // Raw counts — total occurrences in the whole log regardless of runId
119
+ // (for sanity-checking the windowed matcher above).
120
+ const rawUnconsumed = (logs.match(/Skipping unconsumed step event/g) || []).length;
121
+ const rawAllDone = (logs.match(/All parallel steps done, replaying inline/g) || []).length;
122
+ const rawPending = (logs.match(/Background step done but other steps pending/g) || []).length;
123
+ const rawTerminal = (logs.match(/Step in terminal state, skipping/g) || []).length;
124
+ const rawCompleted409 = (logs.match(/Tried completing step, but step has already finished/g) ||
125
+ []).length;
126
+ console.log('--- Raw counts (no runId filter) ---');
127
+ console.log(`Skipping unconsumed step event: ${rawUnconsumed}`);
128
+ console.log(`All parallel steps done, replaying inline: ${rawAllDone}`);
129
+ console.log(`Background step done but other steps pending: ${rawPending}`);
130
+ console.log(`Step in terminal state, skipping: ${rawTerminal}`);
131
+ console.log(`Tried completing step, but step has already finished: ${rawCompleted409}`);
132
+ console.log('-------------------------------------\n');
133
+ // Breakdown of unconsumed event types. The log format emits the
134
+ // message on one line and the metadata object (eventType,
135
+ // correlationId) on the following lines. Walk a 12-line window after
136
+ // each match and extract the fields.
137
+ const skippedByEventType = new Map();
138
+ const skippedEvents = [];
139
+ for (let i = 0; i < lines.length; i++) {
140
+ if (!lines[i].includes('Skipping unconsumed step event'))
141
+ continue;
142
+ const window = lines.slice(i, i + 12).join('\n');
143
+ const et = window.match(/eventType:\s*'([^']+)'/);
144
+ const cid = window.match(/correlationId:\s*'([^']+)'/);
145
+ if (et && cid) {
146
+ skippedByEventType.set(et[1], (skippedByEventType.get(et[1]) ?? 0) + 1);
147
+ skippedEvents.push({ eventType: et[1], correlationId: cid[1] });
148
+ }
149
+ }
150
+ console.log('--- Unconsumed event type breakdown ---');
151
+ for (const [eventType, count] of skippedByEventType) {
152
+ console.log(` ${eventType}: ${count}`);
153
+ }
154
+ // For each skipped event, look up all occurrences of its correlationId
155
+ // in the event log order so we can see whether it's a trailing
156
+ // duplicate lifecycle event after step_completed.
157
+ // We reconstruct event order from the "replaying iteration" logs isn't
158
+ // reliable, so instead ask the server directly.
159
+ const eventsResp = await fetch(`http://localhost:${server.info.port}/runs/${encodeURIComponent(result.runId)}/events`);
160
+ const eventsData = (await eventsResp.json().catch(() => null));
161
+ if (eventsData?.events) {
162
+ console.log(` Total events in log: ${eventsData.events.length}`);
163
+ // For each correlationId that had a skipped event, show the event
164
+ // sequence in the log for that correlationId.
165
+ const shownCids = new Set();
166
+ for (const skip of skippedEvents.slice(0, 6)) {
167
+ if (shownCids.has(skip.correlationId))
168
+ continue;
169
+ shownCids.add(skip.correlationId);
170
+ const seq = eventsData.events
171
+ .filter((e) => e.correlationId === skip.correlationId)
172
+ .map((e) => e.eventType);
173
+ console.log(` cid=${skip.correlationId.slice(0, 20)}... seq=[${seq.join(', ')}]`);
174
+ }
175
+ }
176
+ else {
177
+ console.log(' (events endpoint not available, skipping sequence dump)');
178
+ }
179
+ console.log('-----------------------------------------\n');
180
+ // Sanity: the run did complete. Everything else is diagnostic.
181
+ expect(run.status).toBe('completed');
182
+ // Hard assertion: no step should have hit max retries for this workflow.
183
+ expect(maxDeliveries).toBe(0);
184
+ });
185
+ }
186
+ /**
187
+ * Count matches of `pattern` in `logs`. If `runId` is provided, only count
188
+ * matches where `runId` appears within an 8-line window starting at the
189
+ * match — many debug lines put `workflowRunId` in the metadata object on
190
+ * a following line.
191
+ */
192
+ function matchCount(logs, pattern, runId) {
193
+ const lines = logs.split('\n');
194
+ let n = 0;
195
+ for (let i = 0; i < lines.length; i++) {
196
+ pattern.lastIndex = 0;
197
+ if (!pattern.test(lines[i]))
198
+ continue;
199
+ if (runId) {
200
+ const window = lines.slice(i, i + 8).join('\n');
201
+ if (!window.includes(runId))
202
+ continue;
203
+ }
204
+ n++;
205
+ }
206
+ return n;
207
+ }
208
+ //# sourceMappingURL=inline-batches-debug.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inline-batches-debug.mjs","sourceRoot":"","sources":["../../src/inline-batches-debug.mts"],"names":[],"mappings":"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","sourcesContent":["import { expect, test, vi } from 'vitest';\nimport { hydrateWorkflowReturnValue } from '@workflow/core/serialization';\nimport { createFetcher, startServer } from './util.mjs';\n\n/**\n * Instrumented test: runs `threeBatchesOfFiveWorkflow` with\n * DEBUG=workflow:runtime:* and prints a summary of V2 handler behavior:\n * - total flow invocations\n * - per-invocation replay iterations\n * - event loads (full vs incremental) and sizes\n * - redundant/skipped step executions\n * - concurrent \"all parallel steps done\" replay races\n * - unconsumed-event skips\n *\n * Not part of the default suite; run with:\n * pnpm vitest run packages/world-testing/test/inline-batches-debug.test.ts\n */\nexport function inlineBatchesDebug(world: string) {\n test(\n 'three batches of five parallel steps — report V2 handler behavior',\n { timeout: 60_000 },\n async () => {\n const server = await startServer({\n world,\n env: {\n DEBUG: 'workflow:runtime:*',\n },\n });\n const client = createFetcher(server);\n\n const result = await client.invoke(\n 'workflows/inline-execution.ts',\n 'threeBatchesOfFiveWorkflow',\n []\n );\n\n const run = await vi.waitFor(\n async () => {\n const run = await client.getRun(result.runId);\n expect(run.status).toBe('completed');\n return run;\n },\n { interval: 200, timeout: 59_000 }\n );\n\n const output = await hydrateWorkflowReturnValue(\n run.output!,\n run.runId,\n undefined\n );\n // Expected output: 3 batches × [11, 21, 31, 41, 51] | [12, 22, ...] | ...\n expect(output).toEqual([\n 11, 21, 31, 41, 51, 12, 22, 32, 42, 52, 13, 23, 33, 43, 53,\n ]);\n\n const flowInvocations = await client.getFlowInvocationCount(result.runId);\n\n // Wait a short moment to flush late log lines\n await new Promise((r) => setTimeout(r, 500));\n const logs = server.getOutput();\n\n // --- Parse log lines ---\n\n // 1) Replay iterations + the `eventCount` passed into each replay.\n // The runtime logs: \"Starting workflow replay\" with `eventCount: N`.\n const replayEventCounts: number[] = [];\n for (const line of logs.split('\\n')) {\n if (!line.includes(result.runId)) continue;\n if (!line.includes('Starting workflow replay')) continue;\n }\n // eventCount is often on the next line in a multi-line object literal\n // (console.debug prints the second argument with util.inspect). Pull all\n // eventCount occurrences that are within ~6 lines of a replay start\n // line referencing this runId.\n const lines = logs.split('\\n');\n for (let i = 0; i < lines.length; i++) {\n if (!lines[i]!.includes('Starting workflow replay')) continue;\n // find the runId in the surrounding 8 lines\n const window = lines.slice(i, i + 8).join('\\n');\n if (!window.includes(result.runId)) continue;\n const m = window.match(/eventCount:\\s*(\\d+)/);\n if (m) replayEventCounts.push(Number.parseInt(m[1]!, 10));\n }\n const replayStarts = replayEventCounts.length;\n\n // 3) \"All parallel steps done, replaying inline\" (per-run)\n const allDoneInlineReplays = matchCount(\n logs,\n /All parallel steps done, replaying inline/g,\n result.runId\n );\n\n // 4) \"Background step done but other steps pending\" (per-run)\n const backgroundStepPendingReturns = matchCount(\n logs,\n /Background step done but other steps pending/g,\n result.runId\n );\n\n // 5) Step skipped because step was already in terminal state\n // (background handler arrived after inline path already completed it).\n // This is emitted from the step_started conflict path in step-executor.ts\n // (\"Step in terminal state, skipping\"). The step_completed 409 path\n // (\"Tried completing step, but step has already finished\") fires only\n // when two handlers both ran the step body concurrently.\n const stepsSkippedAlreadyDone = matchCount(\n logs,\n /Step in terminal state, skipping/g,\n result.runId\n );\n const stepCompletedRaces = matchCount(\n logs,\n /Tried completing step, but step has already finished/g,\n result.runId\n );\n\n // 6) Unconsumed event skips (debug message emitted by workflow.ts skip logic)\n const unconsumedEventSkips = matchCount(\n logs,\n /Skipping unconsumed step event/g,\n result.runId\n );\n\n // 7) Hook/step started with already-running: via concurrent handler\n const maxDeliveries = matchCount(logs, /Max retries reached/g);\n\n console.log('\\n====================================');\n console.log('V2 handler behavior — 3 batches × 5 steps');\n console.log('====================================');\n console.log(`Flow handler invocations: ${flowInvocations}`);\n console.log(`Replay iterations (total): ${replayStarts}`);\n console.log(`Event counts per replay: [${replayEventCounts.join(', ')}]`);\n console.log(\n `\"All steps done, replay inline\" hits: ${allDoneInlineReplays}`\n );\n console.log(\n `\"Background step pending, return\" hits: ${backgroundStepPendingReturns}`\n );\n console.log(\n `Step skipped (already terminal, step_started 409): ${stepsSkippedAlreadyDone}`\n );\n console.log(\n `Step body ran twice (step_completed 409): ${stepCompletedRaces}`\n );\n console.log(\n `Unconsumed events skipped by consumer: ${unconsumedEventSkips}`\n );\n console.log(`Max retries reached (bad): ${maxDeliveries}`);\n console.log('====================================\\n');\n\n // Also print the key timeline: one line per \"significant\" runtime\n // debug event, in order. Many debug lines put the runId in the\n // metadata object on a following line, so look at a small forward\n // window around each match.\n const timeline: string[] = [];\n const sigRegex =\n /Starting workflow replay|All parallel steps done|Background step done but|Workflow suspended|Workflow replay completed|Run already finished, skipping background step|Step in terminal state|Tried completing step|Max retries reached|Skipping unconsumed step event/;\n for (let i = 0; i < lines.length; i++) {\n if (!sigRegex.test(lines[i]!)) continue;\n const window = lines.slice(i, i + 8).join('\\n');\n if (!window.includes(result.runId)) continue;\n timeline.push(lines[i]!);\n }\n console.log('--- Timeline (runtime debug events for this run) ---');\n for (const line of timeline) {\n const short = line.replace(/\\s+/g, ' ').slice(0, 180);\n console.log(short);\n }\n console.log(`Timeline entries: ${timeline.length}`);\n console.log('----------------------------------------------------\\n');\n\n // Raw counts — total occurrences in the whole log regardless of runId\n // (for sanity-checking the windowed matcher above).\n const rawUnconsumed = (\n logs.match(/Skipping unconsumed step event/g) || []\n ).length;\n const rawAllDone = (\n logs.match(/All parallel steps done, replaying inline/g) || []\n ).length;\n const rawPending = (\n logs.match(/Background step done but other steps pending/g) || []\n ).length;\n const rawTerminal = (\n logs.match(/Step in terminal state, skipping/g) || []\n ).length;\n const rawCompleted409 = (\n logs.match(/Tried completing step, but step has already finished/g) ||\n []\n ).length;\n console.log('--- Raw counts (no runId filter) ---');\n console.log(`Skipping unconsumed step event: ${rawUnconsumed}`);\n console.log(`All parallel steps done, replaying inline: ${rawAllDone}`);\n console.log(\n `Background step done but other steps pending: ${rawPending}`\n );\n console.log(`Step in terminal state, skipping: ${rawTerminal}`);\n console.log(\n `Tried completing step, but step has already finished: ${rawCompleted409}`\n );\n console.log('-------------------------------------\\n');\n\n // Breakdown of unconsumed event types. The log format emits the\n // message on one line and the metadata object (eventType,\n // correlationId) on the following lines. Walk a 12-line window after\n // each match and extract the fields.\n const skippedByEventType = new Map<string, number>();\n const skippedEvents: { eventType: string; correlationId: string }[] = [];\n for (let i = 0; i < lines.length; i++) {\n if (!lines[i]!.includes('Skipping unconsumed step event')) continue;\n const window = lines.slice(i, i + 12).join('\\n');\n const et = window.match(/eventType:\\s*'([^']+)'/);\n const cid = window.match(/correlationId:\\s*'([^']+)'/);\n if (et && cid) {\n skippedByEventType.set(\n et[1]!,\n (skippedByEventType.get(et[1]!) ?? 0) + 1\n );\n skippedEvents.push({ eventType: et[1]!, correlationId: cid[1]! });\n }\n }\n console.log('--- Unconsumed event type breakdown ---');\n for (const [eventType, count] of skippedByEventType) {\n console.log(` ${eventType}: ${count}`);\n }\n\n // For each skipped event, look up all occurrences of its correlationId\n // in the event log order so we can see whether it's a trailing\n // duplicate lifecycle event after step_completed.\n // We reconstruct event order from the \"replaying iteration\" logs isn't\n // reliable, so instead ask the server directly.\n const eventsResp = await fetch(\n `http://localhost:${server.info.port}/runs/${encodeURIComponent(result.runId)}/events`\n );\n const eventsData = (await eventsResp.json().catch(() => null)) as {\n events: { eventType: string; correlationId?: string }[];\n } | null;\n if (eventsData?.events) {\n console.log(` Total events in log: ${eventsData.events.length}`);\n // For each correlationId that had a skipped event, show the event\n // sequence in the log for that correlationId.\n const shownCids = new Set<string>();\n for (const skip of skippedEvents.slice(0, 6)) {\n if (shownCids.has(skip.correlationId)) continue;\n shownCids.add(skip.correlationId);\n const seq = eventsData.events\n .filter((e) => e.correlationId === skip.correlationId)\n .map((e) => e.eventType);\n console.log(\n ` cid=${skip.correlationId.slice(0, 20)}... seq=[${seq.join(', ')}]`\n );\n }\n } else {\n console.log(\n ' (events endpoint not available, skipping sequence dump)'\n );\n }\n console.log('-----------------------------------------\\n');\n\n // Sanity: the run did complete. Everything else is diagnostic.\n expect(run.status).toBe('completed');\n // Hard assertion: no step should have hit max retries for this workflow.\n expect(maxDeliveries).toBe(0);\n }\n );\n}\n\n/**\n * Count matches of `pattern` in `logs`. If `runId` is provided, only count\n * matches where `runId` appears within an 8-line window starting at the\n * match — many debug lines put `workflowRunId` in the metadata object on\n * a following line.\n */\nfunction matchCount(logs: string, pattern: RegExp, runId?: string): number {\n const lines = logs.split('\\n');\n let n = 0;\n for (let i = 0; i < lines.length; i++) {\n pattern.lastIndex = 0;\n if (!pattern.test(lines[i]!)) continue;\n if (runId) {\n const window = lines.slice(i, i + 8).join('\\n');\n if (!window.includes(runId)) continue;\n }\n n++;\n }\n return n;\n}\n"]}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Tests that the V2 inline execution loop correctly minimizes flow
3
+ * handler invocations. Each test verifies a specific pattern:
4
+ *
5
+ * - Sequential steps (no streams): 1 invocation
6
+ * - Sequential steps with WritableStream: 1 invocation (sync flush)
7
+ * - Sleep + step: 2 invocations (sleep requires queue round-trip)
8
+ * - Parallel steps (Promise.all): 1-3 invocations depending on whether the
9
+ * embedded harness observes the background step and continuation separately
10
+ * - Hook + resume: 2 invocations (hook requires external resume)
11
+ */
12
+ export declare function inlineExecution(world: string): void;
13
+ //# sourceMappingURL=inline-execution.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inline-execution.d.mts","sourceRoot":"","sources":["../../src/inline-execution.mts"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,QA4I5C"}
@@ -0,0 +1,80 @@
1
+ import { expect, test, vi } from 'vitest';
2
+ import { hydrateWorkflowReturnValue } from '@workflow/core/serialization';
3
+ import { createFetcher, startServer } from './util.mjs';
4
+ /**
5
+ * Tests that the V2 inline execution loop correctly minimizes flow
6
+ * handler invocations. Each test verifies a specific pattern:
7
+ *
8
+ * - Sequential steps (no streams): 1 invocation
9
+ * - Sequential steps with WritableStream: 1 invocation (sync flush)
10
+ * - Sleep + step: 2 invocations (sleep requires queue round-trip)
11
+ * - Parallel steps (Promise.all): 1-3 invocations depending on whether the
12
+ * embedded harness observes the background step and continuation separately
13
+ * - Hook + resume: 2 invocations (hook requires external resume)
14
+ */
15
+ export function inlineExecution(world) {
16
+ test('sequential steps complete in a single flow invocation', { timeout: 30_000 }, async () => {
17
+ const server = await startServer({ world }).then(createFetcher);
18
+ const result = await server.invoke('workflows/inline-execution.ts', 'sequentialStepsWorkflow', [10]);
19
+ const run = await vi.waitFor(async () => {
20
+ const run = await server.getRun(result.runId);
21
+ expect(run.status).toBe('completed');
22
+ return run;
23
+ }, { interval: 200, timeout: 29_000 });
24
+ const output = await hydrateWorkflowReturnValue(run.output, run.runId, undefined);
25
+ expect(output).toBe(20);
26
+ const count = await server.getFlowInvocationCount(result.runId);
27
+ expect(count).toBe(1);
28
+ });
29
+ test('sequential steps with stream complete in a single flow invocation', { timeout: 30_000 }, async () => {
30
+ const server = await startServer({ world }).then(createFetcher);
31
+ const result = await server.invoke('workflows/inline-execution.ts', 'sequentialStepsWithStreamWorkflow', []);
32
+ const run = await vi.waitFor(async () => {
33
+ const run = await server.getRun(result.runId);
34
+ expect(run.status).toBe('completed');
35
+ return run;
36
+ }, { interval: 200, timeout: 29_000 });
37
+ const output = await hydrateWorkflowReturnValue(run.output, run.runId, undefined);
38
+ expect(output).toBe('hello world');
39
+ const count = await server.getFlowInvocationCount(result.runId);
40
+ expect(count).toBe(1);
41
+ });
42
+ test('sleep workflow requires exactly 2 flow invocations', { timeout: 30_000 }, async () => {
43
+ const server = await startServer({ world }).then(createFetcher);
44
+ const result = await server.invoke('workflows/inline-execution.ts', 'sleepWorkflow', []);
45
+ const run = await vi.waitFor(async () => {
46
+ const run = await server.getRun(result.runId);
47
+ expect(run.status).toBe('completed');
48
+ return run;
49
+ }, { interval: 200, timeout: 29_000 });
50
+ const output = await hydrateWorkflowReturnValue(run.output, run.runId, undefined);
51
+ expect(output).toBe(3);
52
+ // Invocation 1: replay → sleep → return {timeoutSeconds}
53
+ // Invocation 2: sleep completed → replay → step inline → complete
54
+ const count = await server.getFlowInvocationCount(result.runId);
55
+ expect(count).toBe(2);
56
+ });
57
+ test('parallel steps (Promise.all) complete in 1-3 flow invocations', { timeout: 30_000 }, async () => {
58
+ const server = await startServer({ world }).then(createFetcher);
59
+ const result = await server.invoke('workflows/inline-execution.ts', 'parallelStepsWorkflow', []);
60
+ const run = await vi.waitFor(async () => {
61
+ const run = await server.getRun(result.runId);
62
+ expect(run.status).toBe('completed');
63
+ return run;
64
+ }, { interval: 200, timeout: 29_000 });
65
+ const output = await hydrateWorkflowReturnValue(run.output, run.runId, undefined);
66
+ expect(output).toBe(33); // (10+1) + (20+2)
67
+ // In the embedded harness the background step can finish quickly enough
68
+ // that the run completes before we observe a distinct continuation pass,
69
+ // so the lower bound is 1 even though production often shows 2.
70
+ // With higher queue concurrency, the background step's continuation may
71
+ // also race with the inline handler's loop, adding a 3rd no-op invocation.
72
+ const count = await server.getFlowInvocationCount(result.runId);
73
+ expect(count).toBeGreaterThanOrEqual(1);
74
+ expect(count).toBeLessThanOrEqual(3);
75
+ });
76
+ // Hook invocation counting is tested by the existing hooks test suite.
77
+ // The hook pattern requires external resume, which involves complex
78
+ // timing. The invocation count for hooks is 2: create + resume.
79
+ }
80
+ //# sourceMappingURL=inline-execution.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inline-execution.mjs","sourceRoot":"","sources":["../../src/inline-execution.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC1C,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAExD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,IAAI,CACF,uDAAuD,EACvD,EAAE,OAAO,EAAE,MAAM,EAAE,EACnB,KAAK,IAAI,EAAE;QACT,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAChE,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAChC,+BAA+B,EAC/B,yBAAyB,EACzB,CAAC,EAAE,CAAC,CACL,CAAC;QAEF,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,OAAO,CAC1B,KAAK,IAAI,EAAE;YACT,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC9C,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YACrC,OAAO,GAAG,CAAC;QACb,CAAC,EACD,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CACnC,CAAC;QAEF,MAAM,MAAM,GAAG,MAAM,0BAA0B,CAC7C,GAAG,CAAC,MAAO,EACX,GAAG,CAAC,KAAK,EACT,SAAS,CACV,CAAC;QACF,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAExB,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,sBAAsB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAChE,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACxB,CAAC,CACF,CAAC;IAEF,IAAI,CACF,mEAAmE,EACnE,EAAE,OAAO,EAAE,MAAM,EAAE,EACnB,KAAK,IAAI,EAAE;QACT,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAChE,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAChC,+BAA+B,EAC/B,mCAAmC,EACnC,EAAE,CACH,CAAC;QAEF,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,OAAO,CAC1B,KAAK,IAAI,EAAE;YACT,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC9C,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YACrC,OAAO,GAAG,CAAC;QACb,CAAC,EACD,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CACnC,CAAC;QAEF,MAAM,MAAM,GAAG,MAAM,0BAA0B,CAC7C,GAAG,CAAC,MAAO,EACX,GAAG,CAAC,KAAK,EACT,SAAS,CACV,CAAC;QACF,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAEnC,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,sBAAsB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAChE,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACxB,CAAC,CACF,CAAC;IAEF,IAAI,CACF,oDAAoD,EACpD,EAAE,OAAO,EAAE,MAAM,EAAE,EACnB,KAAK,IAAI,EAAE;QACT,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAChE,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAChC,+BAA+B,EAC/B,eAAe,EACf,EAAE,CACH,CAAC;QAEF,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,OAAO,CAC1B,KAAK,IAAI,EAAE;YACT,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC9C,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YACrC,OAAO,GAAG,CAAC;QACb,CAAC,EACD,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CACnC,CAAC;QAEF,MAAM,MAAM,GAAG,MAAM,0BAA0B,CAC7C,GAAG,CAAC,MAAO,EACX,GAAG,CAAC,KAAK,EACT,SAAS,CACV,CAAC;QACF,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAEvB,yDAAyD;QACzD,kEAAkE;QAClE,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,sBAAsB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAChE,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACxB,CAAC,CACF,CAAC;IAEF,IAAI,CACF,+DAA+D,EAC/D,EAAE,OAAO,EAAE,MAAM,EAAE,EACnB,KAAK,IAAI,EAAE;QACT,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAChE,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAChC,+BAA+B,EAC/B,uBAAuB,EACvB,EAAE,CACH,CAAC;QAEF,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,OAAO,CAC1B,KAAK,IAAI,EAAE;YACT,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC9C,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YACrC,OAAO,GAAG,CAAC;QACb,CAAC,EACD,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CACnC,CAAC;QAEF,MAAM,MAAM,GAAG,MAAM,0BAA0B,CAC7C,GAAG,CAAC,MAAO,EACX,GAAG,CAAC,KAAK,EACT,SAAS,CACV,CAAC;QACF,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,kBAAkB;QAE3C,wEAAwE;QACxE,yEAAyE;QACzE,gEAAgE;QAChE,wEAAwE;QACxE,2EAA2E;QAC3E,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,sBAAsB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAChE,MAAM,CAAC,KAAK,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;QACxC,MAAM,CAAC,KAAK,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;IACvC,CAAC,CACF,CAAC;IAEF,uEAAuE;IACvE,oEAAoE;IACpE,gEAAgE;AAClE,CAAC","sourcesContent":["import { expect, test, vi } from 'vitest';\nimport { hydrateWorkflowReturnValue } from '@workflow/core/serialization';\nimport { createFetcher, startServer } from './util.mjs';\n\n/**\n * Tests that the V2 inline execution loop correctly minimizes flow\n * handler invocations. Each test verifies a specific pattern:\n *\n * - Sequential steps (no streams): 1 invocation\n * - Sequential steps with WritableStream: 1 invocation (sync flush)\n * - Sleep + step: 2 invocations (sleep requires queue round-trip)\n * - Parallel steps (Promise.all): 1-3 invocations depending on whether the\n * embedded harness observes the background step and continuation separately\n * - Hook + resume: 2 invocations (hook requires external resume)\n */\nexport function inlineExecution(world: string) {\n test(\n 'sequential steps complete in a single flow invocation',\n { timeout: 30_000 },\n async () => {\n const server = await startServer({ world }).then(createFetcher);\n const result = await server.invoke(\n 'workflows/inline-execution.ts',\n 'sequentialStepsWorkflow',\n [10]\n );\n\n const run = await vi.waitFor(\n async () => {\n const run = await server.getRun(result.runId);\n expect(run.status).toBe('completed');\n return run;\n },\n { interval: 200, timeout: 29_000 }\n );\n\n const output = await hydrateWorkflowReturnValue(\n run.output!,\n run.runId,\n undefined\n );\n expect(output).toBe(20);\n\n const count = await server.getFlowInvocationCount(result.runId);\n expect(count).toBe(1);\n }\n );\n\n test(\n 'sequential steps with stream complete in a single flow invocation',\n { timeout: 30_000 },\n async () => {\n const server = await startServer({ world }).then(createFetcher);\n const result = await server.invoke(\n 'workflows/inline-execution.ts',\n 'sequentialStepsWithStreamWorkflow',\n []\n );\n\n const run = await vi.waitFor(\n async () => {\n const run = await server.getRun(result.runId);\n expect(run.status).toBe('completed');\n return run;\n },\n { interval: 200, timeout: 29_000 }\n );\n\n const output = await hydrateWorkflowReturnValue(\n run.output!,\n run.runId,\n undefined\n );\n expect(output).toBe('hello world');\n\n const count = await server.getFlowInvocationCount(result.runId);\n expect(count).toBe(1);\n }\n );\n\n test(\n 'sleep workflow requires exactly 2 flow invocations',\n { timeout: 30_000 },\n async () => {\n const server = await startServer({ world }).then(createFetcher);\n const result = await server.invoke(\n 'workflows/inline-execution.ts',\n 'sleepWorkflow',\n []\n );\n\n const run = await vi.waitFor(\n async () => {\n const run = await server.getRun(result.runId);\n expect(run.status).toBe('completed');\n return run;\n },\n { interval: 200, timeout: 29_000 }\n );\n\n const output = await hydrateWorkflowReturnValue(\n run.output!,\n run.runId,\n undefined\n );\n expect(output).toBe(3);\n\n // Invocation 1: replay → sleep → return {timeoutSeconds}\n // Invocation 2: sleep completed → replay → step inline → complete\n const count = await server.getFlowInvocationCount(result.runId);\n expect(count).toBe(2);\n }\n );\n\n test(\n 'parallel steps (Promise.all) complete in 1-3 flow invocations',\n { timeout: 30_000 },\n async () => {\n const server = await startServer({ world }).then(createFetcher);\n const result = await server.invoke(\n 'workflows/inline-execution.ts',\n 'parallelStepsWorkflow',\n []\n );\n\n const run = await vi.waitFor(\n async () => {\n const run = await server.getRun(result.runId);\n expect(run.status).toBe('completed');\n return run;\n },\n { interval: 200, timeout: 29_000 }\n );\n\n const output = await hydrateWorkflowReturnValue(\n run.output!,\n run.runId,\n undefined\n );\n expect(output).toBe(33); // (10+1) + (20+2)\n\n // In the embedded harness the background step can finish quickly enough\n // that the run completes before we observe a distinct continuation pass,\n // so the lower bound is 1 even though production often shows 2.\n // With higher queue concurrency, the background step's continuation may\n // also race with the inline handler's loop, adding a 3rd no-op invocation.\n const count = await server.getFlowInvocationCount(result.runId);\n expect(count).toBeGreaterThanOrEqual(1);\n expect(count).toBeLessThanOrEqual(3);\n }\n );\n\n // Hook invocation counting is tested by the existing hooks test suite.\n // The hook pattern requires external resume, which involves complex\n // timing. The invocation count for hooks is 2: create + resume.\n}\n"]}
@@ -8,7 +8,6 @@ import { POST as flowPOST } from '../.well-known/workflow/v1/flow.mjs';
8
8
  import manifest from '../.well-known/workflow/v1/manifest.json' with {
9
9
  type: 'json'
10
10
  };
11
- import { POST as stepPOST } from '../.well-known/workflow/v1/step.mjs';
12
11
  if (!process.env.WORKFLOW_TARGET_WORLD) {
13
12
  console.error('Error: WORKFLOW_TARGET_WORLD environment variable is not set.');
14
13
  process.exit(1);
@@ -29,12 +28,34 @@ const Invoke = z
29
28
  workflow: manifest.workflows[file][workflow],
30
29
  };
31
30
  });
31
+ // Track flow handler invocations per run for testing inline execution
32
+ const flowInvocationCounts = new Map();
32
33
  const app = new Hono()
33
- .post('/.well-known/workflow/v1/flow', (ctx) => {
34
- return flowPOST(ctx.req.raw);
34
+ .post('/.well-known/workflow/v1/flow', async (ctx) => {
35
+ // Clone the request to read the body for tracking without consuming it
36
+ const cloned = ctx.req.raw.clone();
37
+ const response = await flowPOST(ctx.req.raw);
38
+ // Extract runId from the request body (queue message payload)
39
+ try {
40
+ const body = (await cloned.json());
41
+ const runId = typeof body?.runId === 'string'
42
+ ? body.runId
43
+ : typeof body.payload
44
+ ?.runId === 'string'
45
+ ? body.payload.runId
46
+ : undefined;
47
+ if (runId) {
48
+ flowInvocationCounts.set(runId, (flowInvocationCounts.get(runId) ?? 0) + 1);
49
+ }
50
+ }
51
+ catch {
52
+ // Health check or non-JSON messages — ignore
53
+ }
54
+ return response;
35
55
  })
36
- .post('/.well-known/workflow/v1/step', (ctx) => {
37
- return stepPOST(ctx.req.raw);
56
+ .get('/_flow-invocations/:runId', (ctx) => {
57
+ const count = flowInvocationCounts.get(ctx.req.param('runId')) ?? 0;
58
+ return ctx.json({ count });
38
59
  })
39
60
  .get('/_manifest', (ctx) => ctx.json(manifest))
40
61
  .post('/invoke', async (ctx) => {
@@ -71,6 +92,30 @@ const app = new Hono()
71
92
  const runId = ctx.req.param('runId');
72
93
  const run = getRun(runId);
73
94
  return new Response(run.getReadable());
95
+ })
96
+ .get('/runs/:runId/events', async (ctx) => {
97
+ const runId = ctx.req.param('runId');
98
+ const world = await getWorld();
99
+ const allEvents = [];
100
+ let cursor = undefined;
101
+ while (true) {
102
+ const page = await world.events.list({
103
+ runId,
104
+ pagination: { sortOrder: 'asc', cursor },
105
+ });
106
+ for (const e of page.data) {
107
+ allEvents.push({
108
+ eventType: e.eventType,
109
+ correlationId: e.correlationId,
110
+ });
111
+ }
112
+ if (!page.hasMore)
113
+ break;
114
+ cursor = page.cursor ?? undefined;
115
+ if (!cursor)
116
+ break;
117
+ }
118
+ return ctx.json({ events: allEvents });
74
119
  });
75
120
  serve({
76
121
  fetch: app.fetch,
@@ -1 +1 @@
1
- {"version":3,"file":"server.mjs","sourceRoot":"","sources":["../../src/server.mts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'node:fs';\nimport { serve } from '@hono/node-server';\nimport { Hono } from 'hono';\nimport { getHookByToken, getRun, resumeHook, start } from 'workflow/api';\nimport { getWorld } from 'workflow/runtime';\nimport * as z from 'zod';\nimport { POST as flowPOST } from '../.well-known/workflow/v1/flow.mjs';\nimport manifest from '../.well-known/workflow/v1/manifest.json' with {\n type: 'json',\n};\nimport { POST as stepPOST } from '../.well-known/workflow/v1/step.mjs';\n\nif (!process.env.WORKFLOW_TARGET_WORLD) {\n console.error(\n 'Error: WORKFLOW_TARGET_WORLD environment variable is not set.'\n );\n process.exit(1);\n}\n\ntype Files = keyof typeof manifest.workflows;\ntype Workflows<F extends Files> = keyof (typeof manifest.workflows)[F];\ntype NonEmptyArray<T> = [T, ...T[]];\n\nconst Invoke = z\n .object({\n file: z.enum(Object.keys(manifest.workflows) as NonEmptyArray<Files>),\n workflow: z.string(),\n args: z.unknown().array().default([]),\n })\n .transform((obj) => {\n const file = obj.file as keyof typeof manifest.workflows;\n const workflow = z\n .enum(\n Object.keys(manifest.workflows[file]) as NonEmptyArray<\n Workflows<typeof file>\n >\n )\n .parse(obj.workflow);\n return {\n args: obj.args,\n workflow: manifest.workflows[file][workflow],\n };\n });\n\nconst app = new Hono()\n .post('/.well-known/workflow/v1/flow', (ctx) => {\n return flowPOST(ctx.req.raw);\n })\n .post('/.well-known/workflow/v1/step', (ctx) => {\n return stepPOST(ctx.req.raw);\n })\n .get('/_manifest', (ctx) => ctx.json(manifest))\n .post('/invoke', async (ctx) => {\n const json = await ctx.req.json().then(Invoke.parse);\n const handler = await start(json.workflow, json.args);\n\n return ctx.json({ runId: handler.runId });\n })\n .post('/hooks/:token', async (ctx) => {\n const hook = await getHookByToken(ctx.req.param('token'));\n const { runId } = await resumeHook(hook.token, {\n ...(await ctx.req.json()),\n metadata: hook.metadata,\n });\n return ctx.json({ runId, hookId: hook.hookId });\n })\n .get('/runs/:runId', async (ctx) => {\n const world = await getWorld();\n const run = await world.runs.get(ctx.req.param('runId'));\n // Custom JSON serialization to handle Uint8Array as base64\n const json = JSON.stringify(run, (_key, value) => {\n if (value instanceof Uint8Array) {\n return {\n __type: 'Uint8Array',\n data: Buffer.from(value).toString('base64'),\n };\n }\n return value;\n });\n return new Response(json, {\n headers: { 'Content-Type': 'application/json' },\n });\n })\n .get('/runs/:runId/readable', async (ctx) => {\n const runId = ctx.req.param('runId');\n const run = getRun(runId);\n return new Response(run.getReadable());\n });\n\nserve(\n {\n fetch: app.fetch,\n port: Number(process.env.PORT) || 0,\n },\n async (info) => {\n console.log(`👂 listening on http://${info.address}:${info.port}`);\n console.log('');\n\n process.env.PORT = info.port.toString();\n\n for (const [filename, workflows] of Object.entries(manifest.workflows)) {\n for (const workflowName of Object.keys(\n workflows as Record<string, unknown>\n )) {\n console.log(\n `$ curl -X POST http://localhost:${info.port}/invoke -d '${JSON.stringify(\n {\n file: filename,\n workflow: workflowName,\n }\n )}'`\n );\n }\n }\n\n const world = await getWorld();\n if (world.start) {\n console.log(`starting background tasks...`);\n await world.start().then(\n () => console.log('background tasks started.'),\n (err) => console.error('❗ error starting background tasks:', err)\n );\n }\n\n if (process.env.CONTROL_FD === '3') {\n const control = fs.createWriteStream('', { fd: 3 });\n control.write(`${JSON.stringify({ state: 'listening', info })}\\n`);\n control.end();\n }\n }\n);\n"]}
1
+ {"version":3,"file":"server.mjs","sourceRoot":"","sources":["../../src/server.mts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'node:fs';\nimport { serve } from '@hono/node-server';\nimport { Hono } from 'hono';\nimport { getHookByToken, getRun, resumeHook, start } from 'workflow/api';\nimport { getWorld } from 'workflow/runtime';\nimport * as z from 'zod';\nimport { POST as flowPOST } from '../.well-known/workflow/v1/flow.mjs';\nimport manifest from '../.well-known/workflow/v1/manifest.json' with {\n type: 'json',\n};\n\nif (!process.env.WORKFLOW_TARGET_WORLD) {\n console.error(\n 'Error: WORKFLOW_TARGET_WORLD environment variable is not set.'\n );\n process.exit(1);\n}\n\ntype Files = keyof typeof manifest.workflows;\ntype Workflows<F extends Files> = keyof (typeof manifest.workflows)[F];\ntype NonEmptyArray<T> = [T, ...T[]];\n\nconst Invoke = z\n .object({\n file: z.enum(Object.keys(manifest.workflows) as NonEmptyArray<Files>),\n workflow: z.string(),\n args: z.unknown().array().default([]),\n })\n .transform((obj) => {\n const file = obj.file as keyof typeof manifest.workflows;\n const workflow = z\n .enum(\n Object.keys(manifest.workflows[file]) as NonEmptyArray<\n Workflows<typeof file>\n >\n )\n .parse(obj.workflow);\n return {\n args: obj.args,\n workflow: manifest.workflows[file][workflow],\n };\n });\n\n// Track flow handler invocations per run for testing inline execution\nconst flowInvocationCounts = new Map<string, number>();\n\nconst app = new Hono()\n .post('/.well-known/workflow/v1/flow', async (ctx) => {\n // Clone the request to read the body for tracking without consuming it\n const cloned = ctx.req.raw.clone();\n const response = await flowPOST(ctx.req.raw);\n // Extract runId from the request body (queue message payload)\n try {\n const body = (await cloned.json()) as Record<string, unknown>;\n const runId =\n typeof body?.runId === 'string'\n ? body.runId\n : typeof (body.payload as Record<string, unknown> | undefined)\n ?.runId === 'string'\n ? ((body.payload as Record<string, unknown>).runId as string)\n : undefined;\n if (runId) {\n flowInvocationCounts.set(\n runId,\n (flowInvocationCounts.get(runId) ?? 0) + 1\n );\n }\n } catch {\n // Health check or non-JSON messages — ignore\n }\n return response;\n })\n .get('/_flow-invocations/:runId', (ctx) => {\n const count = flowInvocationCounts.get(ctx.req.param('runId')) ?? 0;\n return ctx.json({ count });\n })\n .get('/_manifest', (ctx) => ctx.json(manifest))\n .post('/invoke', async (ctx) => {\n const json = await ctx.req.json().then(Invoke.parse);\n const handler = await start(json.workflow, json.args);\n\n return ctx.json({ runId: handler.runId });\n })\n .post('/hooks/:token', async (ctx) => {\n const hook = await getHookByToken(ctx.req.param('token'));\n const { runId } = await resumeHook(hook.token, {\n ...(await ctx.req.json()),\n metadata: hook.metadata,\n });\n return ctx.json({ runId, hookId: hook.hookId });\n })\n .get('/runs/:runId', async (ctx) => {\n const world = await getWorld();\n const run = await world.runs.get(ctx.req.param('runId'));\n // Custom JSON serialization to handle Uint8Array as base64\n const json = JSON.stringify(run, (_key, value) => {\n if (value instanceof Uint8Array) {\n return {\n __type: 'Uint8Array',\n data: Buffer.from(value).toString('base64'),\n };\n }\n return value;\n });\n return new Response(json, {\n headers: { 'Content-Type': 'application/json' },\n });\n })\n .get('/runs/:runId/readable', async (ctx) => {\n const runId = ctx.req.param('runId');\n const run = getRun(runId);\n return new Response(run.getReadable());\n })\n .get('/runs/:runId/events', async (ctx) => {\n const runId = ctx.req.param('runId');\n const world = await getWorld();\n const allEvents: { eventType: string; correlationId?: string }[] = [];\n let cursor: string | undefined = undefined;\n while (true) {\n const page = await world.events.list({\n runId,\n pagination: { sortOrder: 'asc', cursor },\n });\n for (const e of page.data) {\n allEvents.push({\n eventType: e.eventType,\n correlationId: e.correlationId,\n });\n }\n if (!page.hasMore) break;\n cursor = page.cursor ?? undefined;\n if (!cursor) break;\n }\n return ctx.json({ events: allEvents });\n });\n\nserve(\n {\n fetch: app.fetch,\n port: Number(process.env.PORT) || 0,\n },\n async (info) => {\n console.log(`👂 listening on http://${info.address}:${info.port}`);\n console.log('');\n\n process.env.PORT = info.port.toString();\n\n for (const [filename, workflows] of Object.entries(manifest.workflows)) {\n for (const workflowName of Object.keys(\n workflows as Record<string, unknown>\n )) {\n console.log(\n `$ curl -X POST http://localhost:${info.port}/invoke -d '${JSON.stringify(\n {\n file: filename,\n workflow: workflowName,\n }\n )}'`\n );\n }\n }\n\n const world = await getWorld();\n if (world.start) {\n console.log(`starting background tasks...`);\n await world.start().then(\n () => console.log('background tasks started.'),\n (err) => console.error('❗ error starting background tasks:', err)\n );\n }\n\n if (process.env.CONTROL_FD === '3') {\n const control = fs.createWriteStream('', { fd: 3 });\n control.write(`${JSON.stringify({ state: 'listening', info })}\\n`);\n control.end();\n }\n }\n);\n"]}