@fluidgrids/workflows 2026.1027.1 → 2026.1028.2

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 (166) hide show
  1. package/README.md +144 -13
  2. package/dist/activation-BBh1p82z.d.cts +57 -0
  3. package/dist/activation-DnhG7SmF.d.ts +57 -0
  4. package/dist/assemble-BLqQ8lSu.d.cts +39 -0
  5. package/dist/assemble-DWgaXHd-.d.ts +39 -0
  6. package/dist/chunk-2J6TF7YI.cjs +86 -0
  7. package/dist/chunk-4LYSEANA.js +199 -0
  8. package/dist/chunk-623FVQSN.cjs +251 -0
  9. package/dist/{chunk-W32BSO3M.js → chunk-6FSHT5IL.js} +2 -2
  10. package/dist/{chunk-E3APHA6O.cjs → chunk-7AAZGVKD.js} +1 -64
  11. package/dist/chunk-ABFZWYHT.js +612 -0
  12. package/dist/{chunk-6U446SKY.js → chunk-BD7ZPG53.js} +2 -2
  13. package/dist/chunk-BO6OVILK.js +79 -0
  14. package/dist/chunk-C5OLWZ42.js +245 -0
  15. package/dist/{chunk-OSPTJCDI.cjs → chunk-CPDK3K5N.cjs} +6 -6
  16. package/dist/chunk-DHTLALBL.cjs +80 -0
  17. package/dist/chunk-DJYMDLV5.js +227 -0
  18. package/dist/{chunk-IHKXLG6I.js → chunk-E2VK7J2C.js} +1 -1
  19. package/dist/{chunk-3M34AUIY.cjs → chunk-FEW3JBYA.cjs} +3 -3
  20. package/dist/chunk-FMSW2HDF.cjs +267 -0
  21. package/dist/chunk-FR7KYWRO.cjs +41 -0
  22. package/dist/{chunk-7YR7DI7B.cjs → chunk-GBQ33SIZ.cjs} +44 -44
  23. package/dist/{chunk-6AIAC2FP.cjs → chunk-GVTZMA5C.cjs} +4 -4
  24. package/dist/{chunk-EEAIM35V.js → chunk-HWJZVOG7.js} +64 -23
  25. package/dist/chunk-I5LR3D2K.js +869 -0
  26. package/dist/chunk-IISBRUWJ.js +13 -0
  27. package/dist/chunk-JD2VQIDZ.cjs +15 -0
  28. package/dist/{chunk-UB2BHU2M.js → chunk-JZ4TOOLN.js} +1 -1
  29. package/dist/chunk-L24YIZY7.cjs +207 -0
  30. package/dist/chunk-LNUQE6D6.js +39 -0
  31. package/dist/chunk-M4BBAERY.cjs +1635 -0
  32. package/dist/chunk-N23SKIG4.js +73 -0
  33. package/dist/{chunk-TWOYB4Y7.cjs → chunk-NEIUQASE.cjs} +13 -13
  34. package/dist/chunk-P6TA5EZT.cjs +634 -0
  35. package/dist/chunk-PMCM2OH4.js +46 -0
  36. package/dist/chunk-PZCVF76Q.cjs +868 -0
  37. package/dist/{chunk-6BEW4QD6.js → chunk-QAN5MZEM.js} +1 -1
  38. package/dist/{chunk-CSYBNN3V.cjs → chunk-QKZL3ONM.cjs} +2 -2
  39. package/dist/chunk-QQ6ULXHI.cjs +885 -0
  40. package/dist/chunk-SAENRNBJ.cjs +52 -0
  41. package/dist/{chunk-TQF7MYEN.cjs → chunk-SAOBPYYJ.cjs} +134 -96
  42. package/dist/{chunk-XBIID5DY.js → chunk-SMCANK3F.js} +5 -4
  43. package/dist/{chunk-UWQWD7BP.cjs → chunk-SX2PNKKT.cjs} +18 -17
  44. package/dist/{chunk-UJNHTS6D.js → chunk-TBDXTV22.cjs} +8 -45
  45. package/dist/chunk-UK3WM7YZ.cjs +75 -0
  46. package/dist/chunk-VOKMAAJP.js +848 -0
  47. package/dist/chunk-VRHHBPT6.js +1622 -0
  48. package/dist/{chunk-5AW745OG.js → chunk-XHXIDVEC.js} +1 -1
  49. package/dist/chunk-XKJA7KX3.js +72 -0
  50. package/dist/chunk-Z2Y65YOY.cjs +7 -0
  51. package/dist/codegen/index.cjs +9 -7
  52. package/dist/codegen/index.d.cts +2 -2
  53. package/dist/codegen/index.d.ts +2 -2
  54. package/dist/codegen/index.js +6 -5
  55. package/dist/file-BOoHZgoQ.d.cts +1557 -0
  56. package/dist/file-yuPCFU5b.d.ts +1557 -0
  57. package/dist/history/index.cjs +287 -0
  58. package/dist/history/index.d.cts +157 -0
  59. package/dist/history/index.d.ts +157 -0
  60. package/dist/history/index.js +5 -0
  61. package/dist/index.cjs +134 -36
  62. package/dist/index.d.cts +137 -9
  63. package/dist/index.d.ts +137 -9
  64. package/dist/index.js +8 -3
  65. package/dist/ir/index.cjs +152 -158
  66. package/dist/ir/index.d.cts +14 -4
  67. package/dist/ir/index.d.ts +14 -4
  68. package/dist/ir/index.js +41 -50
  69. package/dist/lower-BzmUz0sq.d.cts +129 -0
  70. package/dist/lower-_-iT4HYs.d.ts +129 -0
  71. package/dist/lower-types-DQNBfWQn.d.cts +37 -0
  72. package/dist/lower-types-DQNBfWQn.d.ts +37 -0
  73. package/dist/{manifest-Duf-gHdO.d.cts → manifest-0OLw0i7k.d.cts} +1 -1
  74. package/dist/{manifest-Bp9ZJlav.d.ts → manifest-C8N6HDYl.d.ts} +1 -1
  75. package/dist/node-evaluator-4MSDKWOG.cjs +22 -0
  76. package/dist/node-evaluator-SX4LEYQC.js +12 -0
  77. package/dist/quickjs/index.cjs +236 -12
  78. package/dist/quickjs/index.d.cts +46 -5
  79. package/dist/quickjs/index.d.ts +46 -5
  80. package/dist/quickjs/index.js +230 -9
  81. package/dist/roundtrip/index.cjs +333 -131
  82. package/dist/roundtrip/index.d.cts +39 -7
  83. package/dist/roundtrip/index.d.ts +39 -7
  84. package/dist/roundtrip/index.js +185 -13
  85. package/dist/runtime/index.cjs +90 -0
  86. package/dist/runtime/index.d.cts +297 -0
  87. package/dist/runtime/index.d.ts +297 -0
  88. package/dist/runtime/index.js +8 -0
  89. package/dist/testing/index.cjs +1057 -0
  90. package/dist/testing/index.d.cts +305 -0
  91. package/dist/testing/index.d.ts +305 -0
  92. package/dist/testing/index.js +1037 -0
  93. package/dist/{types-Dr2Fn1Ax.d.ts → types-BGoPzgcl.d.cts} +1 -1
  94. package/dist/{types-Dr2Fn1Ax.d.cts → types-BGoPzgcl.d.ts} +1 -1
  95. package/dist/types-CpBBjaGX.d.cts +380 -0
  96. package/dist/types-DV-HX_Gt.d.ts +380 -0
  97. package/dist/{assemble-CrpUyd_3.d.ts → workflow-Bpr3vnA_.d.ts} +40 -160
  98. package/dist/workflow-C2a05WUJ.d.ts +444 -0
  99. package/dist/{assemble-DEf3znGD.d.cts → workflow-FXUkpIBc.d.cts} +40 -160
  100. package/dist/workflow-J-yYixny.d.cts +444 -0
  101. package/fixtures/README.md +128 -0
  102. package/fixtures/histories/activity-failure-fails-run.history.json +163 -0
  103. package/fixtures/histories/activity-retry-then-success.history.json +147 -0
  104. package/fixtures/histories/cancel-activity-abandon.history.json +126 -0
  105. package/fixtures/histories/cancel-activity-compensate.history.json +307 -0
  106. package/fixtures/histories/cancel-concurrent-non-cancellable.history.json +234 -0
  107. package/fixtures/histories/cancel-concurrent-scope-timeout.history.json +184 -0
  108. package/fixtures/histories/cancel-condition-after-cancel.history.json +200 -0
  109. package/fixtures/histories/cancel-during-timer.history.json +123 -0
  110. package/fixtures/histories/cancel-saga-compensations.history.json +325 -0
  111. package/fixtures/histories/cancel-scope-timeout.history.json +148 -0
  112. package/fixtures/histories/child-abandon-outlives-parent.history.json +191 -0
  113. package/fixtures/histories/child-cancel-default.history.json +234 -0
  114. package/fixtures/histories/child-cancel-request.history.json +206 -0
  115. package/fixtures/histories/child-durable-child-side.history.json +124 -0
  116. package/fixtures/histories/child-durable-result.history.json +163 -0
  117. package/fixtures/histories/child-failure-caught.history.json +166 -0
  118. package/fixtures/histories/child-graph-by-key.history.json +138 -0
  119. package/fixtures/histories/child-terminated-by-parent-close.history.json +85 -0
  120. package/fixtures/histories/continue-as-new-first-run.history.json +146 -0
  121. package/fixtures/histories/continue-as-new-history-limit.history.json +489 -0
  122. package/fixtures/histories/continue-as-new-last-run.history.json +147 -0
  123. package/fixtures/histories/continue-as-new-suggested.history.json +352 -0
  124. package/fixtures/histories/details-and-context.history.json +176 -0
  125. package/fixtures/histories/deterministic-values.history.json +128 -0
  126. package/fixtures/histories/engine-builtins.history.json +172 -0
  127. package/fixtures/histories/gather-activities.history.json +291 -0
  128. package/fixtures/histories/gather-all-settled.history.json +219 -0
  129. package/fixtures/histories/gather-mixed.history.json +281 -0
  130. package/fixtures/histories/input-during-running-task.history.json +111 -0
  131. package/fixtures/histories/nondeterminism-reordered.history.json +218 -0
  132. package/fixtures/histories/patched-deprecated.history.json +271 -0
  133. package/fixtures/histories/patched-new-run.history.json +270 -0
  134. package/fixtures/histories/patched-old-history.history.json +190 -0
  135. package/fixtures/histories/race-activity-wins.history.json +152 -0
  136. package/fixtures/histories/race-signal-beats-child.history.json +161 -0
  137. package/fixtures/histories/race-timeout-wins.history.json +118 -0
  138. package/fixtures/histories/side-effect-recorded.history.json +153 -0
  139. package/fixtures/histories/signal-buffered-at-start.history.json +157 -0
  140. package/fixtures/histories/signal-default-handler.history.json +187 -0
  141. package/fixtures/histories/signal-handler.history.json +191 -0
  142. package/fixtures/histories/signal-parent-child-side.history.json +132 -0
  143. package/fixtures/histories/signal-parent.history.json +202 -0
  144. package/fixtures/histories/signal-run-delivered.history.json +128 -0
  145. package/fixtures/histories/signal-run-undelivered.history.json +134 -0
  146. package/fixtures/histories/signal-wait.history.json +111 -0
  147. package/fixtures/histories/task-failure-code-error.history.json +137 -0
  148. package/fixtures/histories/task-failure-then-transient-attempt.history.json +140 -0
  149. package/fixtures/histories/timer-condition-met.history.json +201 -0
  150. package/fixtures/histories/timer-condition-timeout.history.json +118 -0
  151. package/fixtures/histories/timer-sleep-once.history.json +118 -0
  152. package/fixtures/histories/timer-sleep-sequence.history.json +210 -0
  153. package/fixtures/histories/update-accepted-completed.history.json +149 -0
  154. package/fixtures/histories/update-with-start-rejected-validation.history.json +91 -0
  155. package/package.json +44 -5
  156. package/schema/history-file.schema.json +2544 -0
  157. package/schema/ir-v2.schema.json +16 -0
  158. package/schema/run-event.schema.json +2374 -0
  159. package/schema/workflow-command.schema.json +1095 -0
  160. package/schema/workflow-task.schema.json +3560 -0
  161. package/dist/chunk-MBBKGD7K.js +0 -440
  162. package/dist/chunk-Y7NWR3WH.cjs +0 -450
  163. package/dist/lower-CXggJTIo.d.ts +0 -67
  164. package/dist/lower-DAzvsJCo.d.cts +0 -67
  165. package/dist/node-evaluator-CH3XCJND.cjs +0 -17
  166. package/dist/node-evaluator-TULXA3WE.js +0 -8
@@ -0,0 +1,1037 @@
1
+ import { failedTaskResult } from '../chunk-IISBRUWJ.js';
2
+ import { isTier1Workflow, toIR } from '../chunk-HWJZVOG7.js';
3
+ import { irHash, CODE_WORKFLOW_NODE_TYPE, normalizeDefinition } from '../chunk-BD7ZPG53.js';
4
+ import '../chunk-5LUIDMF4.js';
5
+ import '../chunk-JZ4TOOLN.js';
6
+ import '../chunk-E2VK7J2C.js';
7
+ import '../chunk-7AAZGVKD.js';
8
+ import { createHistoryFile, validateCommands, parseHistoryFile } from '../chunk-VOKMAAJP.js';
9
+ import { isRecord } from '../chunk-PMCM2OH4.js';
10
+ import { buildWorkflowRegistry, createWorkflowRuntime } from '../chunk-VRHHBPT6.js';
11
+ import { failureToError, signalName, toMs, errorToFailure, ApplicationError, CancelledFailure, DeterminismViolation } from '../chunk-C5OLWZ42.js';
12
+ import '../chunk-LNUQE6D6.js';
13
+ import { TASK_PRELUDE } from '../chunk-4LYSEANA.js';
14
+ import { isDurableWorkflow } from '../chunk-N23SKIG4.js';
15
+ import { initialRunState, applyEvent, eventsForCommands, isActivityOpen, computeRetryDelay, isChildOpen } from '../chunk-ABFZWYHT.js';
16
+ import { isInputEvent, HISTORY_PROTOCOL, TERMINAL_COMMANDS } from '../chunk-DJYMDLV5.js';
17
+ import '../chunk-BO6OVILK.js';
18
+ import { createContext, Script } from 'vm';
19
+
20
+ // src/testing/host.ts
21
+ function drainJobs() {
22
+ return new Promise((resolve) => setImmediate(resolve));
23
+ }
24
+ function registryOf(workflows) {
25
+ return buildWorkflowRegistry([["in-process", { ...workflows }]]);
26
+ }
27
+ async function hostLoop(abi, requestJson) {
28
+ abi.start(requestJson);
29
+ await drainJobs();
30
+ while (abi.step() === "more") await drainJobs();
31
+ await drainJobs();
32
+ return abi.complete();
33
+ }
34
+ async function runWorkflowTaskInProcess(registry, request) {
35
+ const abi = createWorkflowRuntime(registry);
36
+ const json = await hostLoop(abi, JSON.stringify(request));
37
+ return JSON.parse(json);
38
+ }
39
+
40
+ // src/testing/environment.ts
41
+ var WorkflowFailedError = class extends Error {
42
+ constructor(runId, failure) {
43
+ super(`Run ${runId} failed: ${failure.message}`);
44
+ this.runId = runId;
45
+ this.failure = failure;
46
+ this.name = "WorkflowFailedError";
47
+ Object.defineProperty(this, "cause", { value: failureToError(failure), enumerable: false });
48
+ }
49
+ runId;
50
+ failure;
51
+ };
52
+ var WorkflowTaskFailedError = class extends Error {
53
+ constructor(runId, result) {
54
+ super(
55
+ `A workflow task of run ${runId} failed (${result.failure?.cause ?? "INTERNAL"}): ${result.failure?.message ?? ""}`
56
+ );
57
+ this.runId = runId;
58
+ this.result = result;
59
+ this.name = "WorkflowTaskFailedError";
60
+ }
61
+ runId;
62
+ result;
63
+ };
64
+ var RunBlockedError = class extends Error {
65
+ constructor(runId) {
66
+ super(`Run ${runId} is waiting (for a signal, a timer without time skipping, or a child)`);
67
+ this.runId = runId;
68
+ this.name = "RunBlockedError";
69
+ }
70
+ runId;
71
+ };
72
+ var TestRunHandle = class {
73
+ constructor(env, runId) {
74
+ this.env = env;
75
+ this.runId = runId;
76
+ }
77
+ env;
78
+ runId;
79
+ /** The run's history so far. */
80
+ history() {
81
+ return this.env.historyOf(this.runId);
82
+ }
83
+ /** The history as a history file. */
84
+ historyFile(extra = {}) {
85
+ return this.env.historyFileOf(this.runId, extra);
86
+ }
87
+ /** Deliver a signal and run until idle (without skipping time). */
88
+ async signal(signal, payload) {
89
+ this.env.deliverSignal(this.runId, signalName(signal), payload);
90
+ await this.env.runUntilIdle(false);
91
+ }
92
+ /** Request cancellation and run until idle (without skipping time). */
93
+ async cancel(reason) {
94
+ this.env.requestCancel(this.runId, reason);
95
+ await this.env.runUntilIdle(false);
96
+ }
97
+ /** Terminate the run without running code. */
98
+ async terminate(reason = "terminated") {
99
+ this.env.terminate(this.runId, reason);
100
+ await this.env.runUntilIdle(false);
101
+ }
102
+ /** Run until this run (or the last run of its continue-as-new chain) ends; its output. */
103
+ async result() {
104
+ return this.env.resultOf(this.runId);
105
+ }
106
+ /** The projected state of the run. */
107
+ state() {
108
+ return this.env.stateOf(this.runId);
109
+ }
110
+ };
111
+ function toDate(value) {
112
+ if (value === void 0) return Date.parse("2026-01-01T00:00:00.000Z");
113
+ if (value instanceof Date) return value.getTime();
114
+ return typeof value === "number" ? value : Date.parse(value);
115
+ }
116
+ function toJsonValue(value) {
117
+ if (value === void 0) return null;
118
+ const text = JSON.stringify(value);
119
+ return text === void 0 ? null : JSON.parse(text);
120
+ }
121
+ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
122
+ clock;
123
+ timeSkipping;
124
+ enabled;
125
+ workspaceId;
126
+ runs = /* @__PURE__ */ new Map();
127
+ runOrder = [];
128
+ mocks = /* @__PURE__ */ new Map();
129
+ childMocks = /* @__PURE__ */ new Map();
130
+ workflows;
131
+ registry;
132
+ attempts = [];
133
+ timers = [];
134
+ mockedChildren = [];
135
+ runCounter = 0;
136
+ executor;
137
+ workflowKeys;
138
+ constructor(options) {
139
+ this.clock = toDate(options.startTime);
140
+ this.timeSkipping = options.timeSkipping !== false;
141
+ this.enabled = options.enabled ?? "parity";
142
+ this.workspaceId = options.workspaceId ?? "test-workspace";
143
+ this.workflows = { ...options.workflows ?? {} };
144
+ this.registry = registryOf(this.workflows);
145
+ this.executor = options.taskExecutor;
146
+ this.workflowKeys = { ...options.workflowKeys ?? {} };
147
+ }
148
+ /** Create an environment. */
149
+ static async create(options = {}) {
150
+ return new _TestWorkflowEnvironment(options);
151
+ }
152
+ /** The fake clock, in epoch milliseconds. */
153
+ now() {
154
+ return this.clock;
155
+ }
156
+ /** Mock a node operation (by node type, and operation when given). */
157
+ mock(operation, impl, operationName) {
158
+ const nodeType = typeof operation === "string" ? operation : operation.nodeType;
159
+ const op = operationName ?? (typeof operation === "string" ? void 0 : operation.operation);
160
+ this.mocks.set(
161
+ op === void 0 ? nodeType : `${nodeType}#${op}`,
162
+ impl
163
+ );
164
+ }
165
+ /** Mock a child workflow started by key (graph or durable). */
166
+ mockChild(workflowKey, impl) {
167
+ this.childMocks.set(workflowKey, impl);
168
+ }
169
+ /** Start a run of a tier-2 function (by declaration, or by export name with a task executor). */
170
+ async start(definition, options = {}) {
171
+ const workflowFn = typeof definition === "string" ? definition : (
172
+ // A declared workflow is stored by its declaration; its input type is the caller's.
173
+ this.ensureRegistered(definition)
174
+ );
175
+ const runId = this.createRun(
176
+ workflowFn,
177
+ toJsonValue(options.input),
178
+ { ...options.info, ...options.runId !== void 0 ? { runId: options.runId } : {} },
179
+ void 0
180
+ );
181
+ for (const signal of options.signals ?? [])
182
+ this.deliverSignal(runId, signal.name, signal.payload);
183
+ await this.runUntilIdle(false);
184
+ return new TestRunHandle(this, runId);
185
+ }
186
+ /** Advance the clock by `duration`, firing timers and running work as it comes due. */
187
+ async sleep(duration) {
188
+ const until = this.clock + toMs(duration, "sleep duration");
189
+ await this.runUntilIdle(false);
190
+ for (; ; ) {
191
+ const next = this.nextDue();
192
+ if (next === void 0 || next > until) break;
193
+ this.clock = Math.max(this.clock, next);
194
+ this.fireDue();
195
+ await this.runUntilIdle(false);
196
+ }
197
+ this.clock = until;
198
+ this.fireDue();
199
+ await this.runUntilIdle(false);
200
+ }
201
+ /** Every run, in start order. */
202
+ runIds() {
203
+ return [...this.runOrder];
204
+ }
205
+ historyOf(runId) {
206
+ return this.run(runId).history.map((event) => ({ ...event, payload: { ...event.payload } }));
207
+ }
208
+ historyFileOf(runId, extra = {}) {
209
+ const run = this.run(runId);
210
+ return createHistoryFile(run.workflowFn, this.historyOf(runId), {
211
+ info: { ...run.info },
212
+ ...extra
213
+ });
214
+ }
215
+ /** The failed workflow task that stopped a run, if one did. */
216
+ taskFailureOf(runId) {
217
+ return this.run(runId).taskFailure;
218
+ }
219
+ stateOf(runId) {
220
+ return this.run(runId).state;
221
+ }
222
+ // ---- runs -------------------------------------------------------------------
223
+ ensureRegistered(definition) {
224
+ for (const [name2, value] of Object.entries(this.workflows))
225
+ if (value === definition) return name2;
226
+ const name = Object.hasOwn(this.workflows, definition.name) ? `${definition.name}#${this.runCounter}` : definition.name;
227
+ this.workflows[name] = definition;
228
+ this.registry = registryOf(this.workflows);
229
+ return name;
230
+ }
231
+ run(runId) {
232
+ const run = this.runs.get(runId);
233
+ if (!run) throw new Error(`Unknown run ${runId}`);
234
+ return run;
235
+ }
236
+ createRun(workflowFn, input, info, parent, extraStarted = {}) {
237
+ this.runCounter += 1;
238
+ const runId = info.runId ?? `run-${this.runCounter}`;
239
+ const snapshot = {
240
+ workspaceId: this.workspaceId,
241
+ taskQueue: "managed",
242
+ attempt: 1,
243
+ startedAt: new Date(this.clock).toISOString(),
244
+ runStartedAt: new Date(this.clock).toISOString(),
245
+ ...info,
246
+ runId,
247
+ ...parent ? { parentRunId: parent.runId } : {}
248
+ };
249
+ const run = {
250
+ runId,
251
+ workflowFn,
252
+ history: [],
253
+ state: initialRunState(),
254
+ taskCounter: 0,
255
+ info: snapshot,
256
+ children: /* @__PURE__ */ new Map(),
257
+ ...parent ? { parent } : {}
258
+ };
259
+ this.runs.set(runId, run);
260
+ this.runOrder.push(runId);
261
+ this.append(run, {
262
+ type: "WORKFLOW_STARTED",
263
+ // The service writes the seed of ctx.random()/ctx.uuid() into the start
264
+ // event so the history replays on its own; it uses the run id.
265
+ payload: { workflowFn, input, taskQueue: "managed", randomnessSeed: runId, ...extraStarted }
266
+ });
267
+ return runId;
268
+ }
269
+ append(run, draft) {
270
+ const event = {
271
+ seq: run.history.length + 1,
272
+ type: draft.type,
273
+ payload: draft.payload,
274
+ createdAt: new Date(this.clock).toISOString(),
275
+ ...draft.taskId !== void 0 ? { taskId: draft.taskId } : {}
276
+ };
277
+ applyEvent(run.state, event);
278
+ run.history.push(event);
279
+ if (isInputEvent(event) && run.state.status === "RUNNING" && run.state.task.state === "NONE") {
280
+ run.taskCounter += 1;
281
+ const taskId = `${run.runId}-task-${run.taskCounter}`;
282
+ const scheduled = {
283
+ seq: run.history.length + 1,
284
+ type: "WORKFLOW_TASK_SCHEDULED",
285
+ payload: { taskId },
286
+ createdAt: event.createdAt
287
+ };
288
+ applyEvent(run.state, scheduled);
289
+ run.history.push(scheduled);
290
+ }
291
+ return event;
292
+ }
293
+ deliverSignal(runId, name, payload) {
294
+ const run = this.runs.get(runId);
295
+ if (!run || run.state.status !== "RUNNING") return false;
296
+ this.append(run, {
297
+ type: "SIGNAL_RECEIVED",
298
+ payload: {
299
+ name,
300
+ payload: toJsonValue(payload),
301
+ receivedAt: new Date(this.clock).toISOString()
302
+ }
303
+ });
304
+ return true;
305
+ }
306
+ requestCancel(runId, reason) {
307
+ const run = this.runs.get(runId);
308
+ if (!run || run.state.status !== "RUNNING") return false;
309
+ if (run.state.cancelRequested) return true;
310
+ this.append(run, {
311
+ type: "WORKFLOW_CANCEL_REQUESTED",
312
+ payload: reason !== void 0 ? { reason } : {}
313
+ });
314
+ return true;
315
+ }
316
+ terminate(runId, reason) {
317
+ const run = this.runs.get(runId);
318
+ if (!run || run.state.status !== "RUNNING") return;
319
+ this.append(run, { type: "WORKFLOW_TERMINATED", payload: { reason } });
320
+ this.onClosed(run);
321
+ }
322
+ // ---- the loop ---------------------------------------------------------------
323
+ /** Run every runnable task and activity; skip time when allowed and idle. */
324
+ async runUntilIdle(skipTime) {
325
+ for (let guard = 0; guard < 1e5; guard++) {
326
+ const task = this.runOrder.map((id) => this.runs.get(id)).find(
327
+ (run) => run !== void 0 && !run.taskFailure && run.state.status === "RUNNING" && run.state.task.state === "SCHEDULED"
328
+ );
329
+ if (task) {
330
+ await this.runTask(task);
331
+ continue;
332
+ }
333
+ const attempt = this.attempts.find((entry) => entry.dueAt <= this.clock);
334
+ if (attempt) {
335
+ this.attempts = this.attempts.filter((entry) => entry !== attempt);
336
+ await this.runAttempt(attempt);
337
+ continue;
338
+ }
339
+ const mocked = this.mockedChildren.shift();
340
+ if (mocked) {
341
+ await this.runMockedChild(mocked);
342
+ continue;
343
+ }
344
+ if (this.fireDue()) continue;
345
+ if (!skipTime) return;
346
+ const next = this.nextDue();
347
+ if (next === void 0) return;
348
+ this.clock = Math.max(this.clock, next);
349
+ }
350
+ throw new Error("The test environment did not settle after 100000 steps");
351
+ }
352
+ nextDue() {
353
+ const due = [
354
+ ...this.timers.map((timer) => timer.fireAt),
355
+ ...this.attempts.map((entry) => entry.dueAt)
356
+ ];
357
+ return due.length === 0 ? void 0 : Math.min(...due);
358
+ }
359
+ fireDue() {
360
+ const due = this.timers.filter((timer) => timer.fireAt <= this.clock).sort((a, b) => a.fireAt - b.fireAt);
361
+ if (due.length === 0) return false;
362
+ this.timers = this.timers.filter((timer) => !due.includes(timer));
363
+ for (const timer of due) {
364
+ const run = this.runs.get(timer.runId);
365
+ if (!run || run.state.status !== "RUNNING" || run.state.timers[timer.timerId]?.status !== "PENDING")
366
+ continue;
367
+ this.append(run, { type: "TIMER_FIRED", payload: { timerId: timer.timerId } });
368
+ }
369
+ return true;
370
+ }
371
+ async runTask(run) {
372
+ const taskId = run.state.task.taskId ?? `${run.runId}-task-${run.taskCounter}`;
373
+ this.append(run, { type: "WORKFLOW_TASK_STARTED", payload: { taskId, attempt: 1 } });
374
+ const request = {
375
+ protocol: HISTORY_PROTOCOL,
376
+ kind: "task",
377
+ workflowFn: run.workflowFn,
378
+ info: run.info,
379
+ history: run.history,
380
+ nowMs: this.clock
381
+ };
382
+ const result = this.executor ? await this.executor(request) : await runWorkflowTaskInProcess(this.registry, request);
383
+ const fail = (cause, message) => {
384
+ this.append(run, {
385
+ type: "WORKFLOW_TASK_FAILED",
386
+ payload: { taskId, attempt: 1, cause, message }
387
+ });
388
+ run.taskFailure = { ...result, outcome: "failed", commands: [], failure: { cause, message } };
389
+ };
390
+ if (result.outcome !== "commands") {
391
+ fail(result.failure?.cause ?? "INTERNAL", result.failure?.message ?? "The task failed");
392
+ run.taskFailure = result;
393
+ return;
394
+ }
395
+ const checked = validateCommands(result.commands, { enabled: this.enabled });
396
+ if (!checked.ok) {
397
+ fail(checked.cause, checked.issues.map((issue) => issue.message).join("; "));
398
+ return;
399
+ }
400
+ const drafts = eventsForCommands(run.state, checked.commands, {
401
+ taskId,
402
+ nowMs: this.clock,
403
+ enabled: this.enabled
404
+ });
405
+ if (!drafts.ok) {
406
+ fail(drafts.cause, drafts.issues.map((issue) => issue.message).join("; "));
407
+ return;
408
+ }
409
+ this.append(run, { type: "WORKFLOW_TASK_COMPLETED", taskId, payload: { taskId } });
410
+ for (const draft of drafts.events) this.append(run, draft);
411
+ for (const command of checked.commands)
412
+ if (run.state.status === "RUNNING" || TERMINAL_COMMANDS.has(command.type))
413
+ this.afterCommand(run, command);
414
+ }
415
+ afterCommand(run, command) {
416
+ switch (command.type) {
417
+ case "SCHEDULE_ACTIVITY": {
418
+ const attempt = {
419
+ runId: run.runId,
420
+ activityId: command.activityId,
421
+ attempt: 1,
422
+ dueAt: this.clock,
423
+ scheduledAt: this.clock,
424
+ args: command.args,
425
+ nodeType: command.nodeType,
426
+ operation: command.operation,
427
+ retry: command.retry,
428
+ cancelRequested: false,
429
+ ...command.connection !== void 0 ? { connection: command.connection } : {},
430
+ ...command.scheduleToCloseTimeout !== void 0 ? { scheduleToClose: command.scheduleToCloseTimeout } : {}
431
+ };
432
+ this.attempts.push(attempt);
433
+ return;
434
+ }
435
+ case "REQUEST_CANCEL_ACTIVITY": {
436
+ const pending = this.attempts.find(
437
+ (entry) => entry.runId === run.runId && entry.activityId === command.activityId
438
+ );
439
+ if (pending) {
440
+ this.attempts = this.attempts.filter((entry) => entry !== pending);
441
+ this.append(run, {
442
+ type: "ACTIVITY_CANCELLED",
443
+ payload: { activityId: command.activityId }
444
+ });
445
+ }
446
+ return;
447
+ }
448
+ case "START_TIMER":
449
+ this.timers.push({
450
+ runId: run.runId,
451
+ timerId: command.timerId,
452
+ fireAt: this.clock + command.durationMs
453
+ });
454
+ return;
455
+ case "CANCEL_TIMER":
456
+ this.timers = this.timers.filter(
457
+ (timer) => !(timer.runId === run.runId && timer.timerId === command.timerId)
458
+ );
459
+ return;
460
+ case "START_CHILD_RUN":
461
+ this.startChild(run, command);
462
+ return;
463
+ case "SIGNAL_RUN": {
464
+ const target = this.resolveTarget(run, command.target);
465
+ const ok = target !== void 0 && this.deliverSignal(target, command.name, command.payload);
466
+ this.append(run, {
467
+ type: "EXTERNAL_RUN_SIGNALED",
468
+ payload: {
469
+ externalId: command.externalId,
470
+ resolved: true,
471
+ ok,
472
+ ...target !== void 0 ? { targetRunId: target } : {},
473
+ ...ok ? {} : {
474
+ failure: {
475
+ errorType: "VALIDATION",
476
+ message: "The target run is not open",
477
+ type: "ServerFailure"
478
+ }
479
+ }
480
+ }
481
+ });
482
+ return;
483
+ }
484
+ case "REQUEST_CANCEL_EXTERNAL_RUN": {
485
+ const target = this.resolveTarget(run, command.target);
486
+ const ok = target !== void 0 && this.requestCancel(target);
487
+ this.append(run, {
488
+ type: "EXTERNAL_RUN_CANCEL_RESOLVED",
489
+ payload: {
490
+ externalId: command.externalId,
491
+ ok,
492
+ ...target !== void 0 ? { targetRunId: target } : {},
493
+ ...ok ? {} : {
494
+ failure: {
495
+ errorType: "VALIDATION",
496
+ message: "The target run is not open",
497
+ type: "ServerFailure"
498
+ }
499
+ }
500
+ }
501
+ });
502
+ return;
503
+ }
504
+ case "CONTINUE_AS_NEW": {
505
+ const next = this.createRun(
506
+ run.workflowFn,
507
+ command.input,
508
+ {
509
+ ...run.info,
510
+ runId: void 0,
511
+ continuedFromRunId: run.runId,
512
+ firstRunId: run.info.firstRunId ?? run.runId
513
+ },
514
+ run.parent
515
+ );
516
+ run.continuedTo = next;
517
+ this.onClosed(run);
518
+ return;
519
+ }
520
+ case "COMPLETE_WORKFLOW":
521
+ case "FAIL_WORKFLOW":
522
+ case "CANCEL_WORKFLOW":
523
+ this.onClosed(run);
524
+ return;
525
+ default:
526
+ return;
527
+ }
528
+ }
529
+ resolveTarget(run, target) {
530
+ if ("runId" in target) return target.runId;
531
+ if ("parent" in target) return run.parent?.runId ?? run.info.parentRunId ?? void 0;
532
+ if ("childId" in target) return run.children.get(target.childId);
533
+ return void 0;
534
+ }
535
+ startChild(run, command) {
536
+ const byFn = command.workflowFn !== void 0 && (this.executor !== void 0 || this.registry.byName.has(command.workflowFn));
537
+ const keyed = command.workflowKey === void 0 ? void 0 : this.workflowKeys[command.workflowKey] ?? Object.entries(this.workflows).find(
538
+ ([, definition]) => definition.name === command.workflowKey
539
+ )?.[0];
540
+ const mock = command.workflowKey !== void 0 ? this.childMocks.get(command.workflowKey) : void 0;
541
+ if (mock) {
542
+ run.children.set(command.childId, void 0);
543
+ this.mockedChildren.push({
544
+ parentRunId: run.runId,
545
+ childId: command.childId,
546
+ input: command.input,
547
+ key: command.workflowKey ?? ""
548
+ });
549
+ return;
550
+ }
551
+ const workflowFn = byFn ? command.workflowFn : keyed;
552
+ if (workflowFn === void 0) {
553
+ this.append(run, {
554
+ type: "CHILD_RUN_FAILED",
555
+ payload: {
556
+ childId: command.childId,
557
+ failure: {
558
+ errorType: "VALIDATION",
559
+ message: `No workflow "${command.workflowFn ?? command.workflowKey ?? ""}" in the test environment (register it or mockChild it)`,
560
+ type: "ServerFailure"
561
+ }
562
+ }
563
+ });
564
+ return;
565
+ }
566
+ const childRunId = this.createRun(
567
+ workflowFn,
568
+ command.input,
569
+ { rootRunId: run.info.rootRunId ?? run.runId },
570
+ { runId: run.runId, childId: command.childId },
571
+ {
572
+ parentRunId: run.runId,
573
+ parentClosePolicy: command.options.parentClosePolicy
574
+ }
575
+ );
576
+ run.children.set(command.childId, childRunId);
577
+ this.append(run, {
578
+ type: "CHILD_RUN_STARTED",
579
+ payload: { childId: command.childId, childRunId, executionKind: "DURABLE_FN" }
580
+ });
581
+ }
582
+ async runMockedChild(entry) {
583
+ const parent = this.runs.get(entry.parentRunId);
584
+ const mock = this.childMocks.get(entry.key);
585
+ if (!parent || !mock || parent.state.status !== "RUNNING") return;
586
+ const childRunId = `${entry.parentRunId}-${entry.childId}`;
587
+ this.append(parent, {
588
+ type: "CHILD_RUN_STARTED",
589
+ payload: { childId: entry.childId, childRunId, executionKind: "GRAPH" }
590
+ });
591
+ try {
592
+ const result = toJsonValue(
593
+ await mock(entry.input, { runId: entry.parentRunId, childId: entry.childId })
594
+ );
595
+ if (parent.state.status === "RUNNING")
596
+ this.append(parent, {
597
+ type: "CHILD_RUN_COMPLETED",
598
+ payload: { childId: entry.childId, childRunId, result }
599
+ });
600
+ } catch (error) {
601
+ if (parent.state.status === "RUNNING")
602
+ this.append(parent, {
603
+ type: "CHILD_RUN_FAILED",
604
+ payload: {
605
+ childId: entry.childId,
606
+ childRunId,
607
+ failure: toJsonValue(errorToFailure(error, "engine"))
608
+ }
609
+ });
610
+ }
611
+ }
612
+ async runAttempt(attempt) {
613
+ const run = this.runs.get(attempt.runId);
614
+ if (!run || run.state.status !== "RUNNING") return;
615
+ const activity = run.state.activities[attempt.activityId];
616
+ if (!activity || !isActivityOpen(activity)) return;
617
+ if (attempt.scheduleToClose !== void 0 && this.clock - attempt.scheduledAt > attempt.scheduleToClose) {
618
+ this.append(run, {
619
+ type: "ACTIVITY_TIMED_OUT",
620
+ payload: {
621
+ activityId: attempt.activityId,
622
+ attempt: Math.max(1, attempt.attempt - 1),
623
+ timeoutType: "SCHEDULE_TO_CLOSE"
624
+ }
625
+ });
626
+ return;
627
+ }
628
+ const mock = this.mocks.get(`${attempt.nodeType}#${attempt.operation}`) ?? this.mocks.get(attempt.nodeType);
629
+ let failure;
630
+ let result = null;
631
+ try {
632
+ if (!mock)
633
+ throw new ApplicationError(
634
+ `No mock for ${attempt.nodeType} ${attempt.operation} (use env.mock)`,
635
+ {
636
+ errorType: "VALIDATION",
637
+ nonRetryable: true,
638
+ type: "UnmockedActivity"
639
+ }
640
+ );
641
+ result = toJsonValue(
642
+ await mock(attempt.args, {
643
+ runId: attempt.runId,
644
+ activityId: attempt.activityId,
645
+ attempt: attempt.attempt,
646
+ nodeType: attempt.nodeType,
647
+ operation: attempt.operation,
648
+ ...attempt.connection !== void 0 ? { connection: attempt.connection } : {}
649
+ })
650
+ );
651
+ } catch (error) {
652
+ failure = errorToFailure(error, `node:${attempt.nodeType}`);
653
+ delete failure.stackTrace;
654
+ }
655
+ if (run.state.status !== "RUNNING" || !isActivityOpen(run.state.activities[attempt.activityId] ?? activity))
656
+ return;
657
+ const started = () => {
658
+ this.append(run, {
659
+ type: "ACTIVITY_STARTED",
660
+ payload: { activityId: attempt.activityId, attempt: attempt.attempt }
661
+ });
662
+ };
663
+ if (!failure) {
664
+ started();
665
+ this.append(run, {
666
+ type: "ACTIVITY_COMPLETED",
667
+ payload: { activityId: attempt.activityId, attempt: attempt.attempt, result }
668
+ });
669
+ return;
670
+ }
671
+ if (failure.timeoutType !== void 0 || failure.type === "TimeoutFailure") {
672
+ const decision2 = computeRetryDelay(attempt.retry, attempt.attempt, failure);
673
+ if (!decision2.retry) {
674
+ started();
675
+ this.append(run, {
676
+ type: "ACTIVITY_TIMED_OUT",
677
+ payload: {
678
+ activityId: attempt.activityId,
679
+ attempt: attempt.attempt,
680
+ timeoutType: failure.timeoutType ?? "START_TO_CLOSE",
681
+ failure: toJsonValue(failure)
682
+ }
683
+ });
684
+ return;
685
+ }
686
+ this.attempts.push({
687
+ ...attempt,
688
+ attempt: attempt.attempt + 1,
689
+ dueAt: this.clock + decision2.delayMs
690
+ });
691
+ return;
692
+ }
693
+ const decision = computeRetryDelay(attempt.retry, attempt.attempt, failure);
694
+ if (decision.retry) {
695
+ this.attempts.push({
696
+ ...attempt,
697
+ attempt: attempt.attempt + 1,
698
+ dueAt: this.clock + decision.delayMs
699
+ });
700
+ return;
701
+ }
702
+ started();
703
+ this.append(run, {
704
+ type: "ACTIVITY_FAILED",
705
+ payload: {
706
+ activityId: attempt.activityId,
707
+ attempt: attempt.attempt,
708
+ failure: toJsonValue(failure)
709
+ }
710
+ });
711
+ }
712
+ onClosed(run) {
713
+ this.timers = this.timers.filter((timer) => timer.runId !== run.runId);
714
+ this.attempts = this.attempts.filter((entry) => entry.runId !== run.runId);
715
+ for (const [childId2, childRunId] of run.children) {
716
+ const child = run.state.children[childId2];
717
+ if (!child || !isChildOpen(child) || childRunId === void 0) continue;
718
+ if (child.parentClosePolicy === "TERMINATE") this.terminate(childRunId, "parent closed");
719
+ else if (child.parentClosePolicy === "REQUEST_CANCEL")
720
+ this.requestCancel(childRunId, "parent closed");
721
+ }
722
+ if (!run.parent) return;
723
+ const parent = this.runs.get(run.parent.runId);
724
+ if (!parent || parent.state.status !== "RUNNING") return;
725
+ const childId = run.parent.childId;
726
+ if (!parent.state.children[childId] || !isChildOpen(parent.state.children[childId])) return;
727
+ const last = run.history[run.history.length - 1];
728
+ if (!last) return;
729
+ if (run.continuedTo !== void 0) {
730
+ const next = this.runs.get(run.continuedTo);
731
+ if (next) {
732
+ next.parent = run.parent;
733
+ parent.children.set(childId, next.runId);
734
+ }
735
+ return;
736
+ }
737
+ const base = { childId, childRunId: run.runId };
738
+ switch (last.type) {
739
+ case "WORKFLOW_COMPLETED":
740
+ this.append(parent, {
741
+ type: "CHILD_RUN_COMPLETED",
742
+ payload: { ...base, result: last.payload["output"] ?? null }
743
+ });
744
+ return;
745
+ case "WORKFLOW_FAILED":
746
+ this.append(parent, {
747
+ type: "CHILD_RUN_FAILED",
748
+ payload: {
749
+ ...base,
750
+ failure: last.payload["failure"] ?? { errorType: "UNKNOWN", message: "failed" }
751
+ }
752
+ });
753
+ return;
754
+ case "WORKFLOW_CANCELLED":
755
+ this.append(parent, { type: "CHILD_RUN_CANCELLED", payload: base });
756
+ return;
757
+ case "WORKFLOW_TERMINATED":
758
+ this.append(parent, {
759
+ type: "CHILD_RUN_TERMINATED",
760
+ payload: {
761
+ ...base,
762
+ reason: typeof last.payload["reason"] === "string" ? last.payload["reason"] : "terminated"
763
+ }
764
+ });
765
+ return;
766
+ default:
767
+ this.append(parent, {
768
+ type: "CHILD_RUN_TIMED_OUT",
769
+ payload: { ...base, timeoutType: "WORKFLOW_RUN" }
770
+ });
771
+ }
772
+ }
773
+ async resultOf(runId) {
774
+ await this.runUntilIdle(this.timeSkipping);
775
+ let run = this.run(runId);
776
+ while (run.continuedTo !== void 0) run = this.run(run.continuedTo);
777
+ if (run.taskFailure) throw new WorkflowTaskFailedError(run.runId, run.taskFailure);
778
+ const last = run.history[run.history.length - 1];
779
+ switch (run.state.status) {
780
+ case "COMPLETED":
781
+ return run.state.output ?? null;
782
+ case "FAILED":
783
+ throw new WorkflowFailedError(
784
+ run.runId,
785
+ run.state.failure ?? { errorType: "UNKNOWN", message: "failed" }
786
+ );
787
+ case "CANCELLED":
788
+ throw new CancelledFailure(`Run ${run.runId} was cancelled`);
789
+ case "TERMINATED":
790
+ throw new WorkflowFailedError(run.runId, {
791
+ errorType: "PERMANENT",
792
+ message: typeof last?.payload["reason"] === "string" ? last.payload["reason"] : "terminated",
793
+ type: "TerminatedFailure"
794
+ });
795
+ default:
796
+ throw new RunBlockedError(run.runId);
797
+ }
798
+ }
799
+ /** Every tier-2 function registered, by export name. */
800
+ registered() {
801
+ return { ...this.workflows };
802
+ }
803
+ };
804
+
805
+ // src/testing/replay.ts
806
+ var ReplayError = class extends Error {
807
+ constructor(result) {
808
+ super(
809
+ `Replay failed (${result.failure?.cause ?? "INTERNAL"}): ${result.failure?.message ?? ""}`
810
+ );
811
+ this.result = result;
812
+ this.name = "ReplayError";
813
+ }
814
+ result;
815
+ };
816
+ function replayRequest(workflowFn, history, info = {}, nowMs) {
817
+ const last = history[history.length - 1];
818
+ return {
819
+ protocol: HISTORY_PROTOCOL,
820
+ kind: "verify",
821
+ workflowFn,
822
+ info: { runId: "replay", workspaceId: "replay", ...info },
823
+ history,
824
+ nowMs: nowMs ?? (last ? Date.parse(last.createdAt) : 0)
825
+ };
826
+ }
827
+ function eventsOf(input) {
828
+ if (Array.isArray(input)) return { events: input };
829
+ const file = typeof input === "string" ? parseHistoryFile(input) : input;
830
+ return { events: file.events, file };
831
+ }
832
+ async function replay(workflows, input, options = {}) {
833
+ const { events, file } = eventsOf(input);
834
+ const started = events[0]?.type === "WORKFLOW_STARTED" ? events[0].payload["workflowFn"] : void 0;
835
+ const recordedFn = options.workflowFn ?? file?.workflowFn ?? (typeof started === "string" ? started : void 0);
836
+ const map = isDurableWorkflow(workflows) ? { [recordedFn ?? workflows.name]: workflows } : { ...workflows };
837
+ const workflowFn = recordedFn ?? Object.keys(map)[0] ?? "";
838
+ const result = await runWorkflowTaskInProcess(
839
+ registryOf(map),
840
+ replayRequest(
841
+ workflowFn,
842
+ events,
843
+ { ...file?.info, ...options.info },
844
+ options.nowMs ?? file?.nowMs
845
+ )
846
+ );
847
+ if (result.outcome === "failed") {
848
+ if (result.failure?.cause === "NON_DETERMINISM")
849
+ throw new DeterminismViolation(result.failure.message);
850
+ throw new ReplayError(result);
851
+ }
852
+ return result;
853
+ }
854
+
855
+ // src/testing/assert.ts
856
+ var HistoryAssertionError = class extends Error {
857
+ constructor(message) {
858
+ super(message);
859
+ this.name = "HistoryAssertionError";
860
+ }
861
+ };
862
+ function contains(actual, expected) {
863
+ if (isRecord(expected)) {
864
+ if (!isRecord(actual)) return false;
865
+ return Object.entries(expected).every(([key, value]) => contains(actual[key], value));
866
+ }
867
+ if (Array.isArray(expected)) {
868
+ return Array.isArray(actual) && actual.length === expected.length && expected.every((item, index) => contains(actual[index], item));
869
+ }
870
+ return Object.is(actual, expected);
871
+ }
872
+ function assertHistory(history, expected, options = {}) {
873
+ const ignored = new Set(options.ignore ?? []);
874
+ const events = (Array.isArray(history) ? history : history.history()).filter((event) => !ignored.has(event.type));
875
+ let position = 0;
876
+ for (const [index, want] of expected.entries()) {
877
+ let found = -1;
878
+ for (let cursor = position; cursor < events.length; cursor++) {
879
+ const event = events[cursor];
880
+ if (event && event.type === want.type && (want.payload === void 0 || contains(event.payload, want.payload))) {
881
+ found = cursor;
882
+ break;
883
+ }
884
+ if (options.exact) break;
885
+ }
886
+ if (found < 0)
887
+ throw new HistoryAssertionError(
888
+ `Expected event ${index + 1} (${want.type}${want.payload ? ` ${JSON.stringify(want.payload)}` : ""}) after seq ${events[position - 1]?.seq ?? 0}; the history has ${events.slice(position).map((event) => event.type).join(", ") || "nothing more"}`
889
+ );
890
+ position = found + 1;
891
+ }
892
+ if (options.exact && position !== events.length)
893
+ throw new HistoryAssertionError(
894
+ `The history has ${events.length - position} more events than expected`
895
+ );
896
+ }
897
+ function definitionOf(value) {
898
+ return normalizeDefinition(isTier1Workflow(value) ? toIR(value) : value);
899
+ }
900
+ function expectIR(value) {
901
+ const definition = definitionOf(value);
902
+ const hash = irHash(definition);
903
+ return {
904
+ definition,
905
+ irHash: hash,
906
+ toEqualIR(other) {
907
+ const otherHash = irHash(definitionOf(other));
908
+ if (otherHash !== hash)
909
+ throw new HistoryAssertionError(`The IR differs: ${hash} versus ${otherHash}`);
910
+ }
911
+ };
912
+ }
913
+ function jsonCopy(value) {
914
+ return JSON.parse(JSON.stringify(value ?? null));
915
+ }
916
+
917
+ // src/testing/simulate.ts
918
+ function isHandleResult(value) {
919
+ return isRecord(value) && Object.hasOwn(value, "output") && Object.keys(value).every((key) => key === "output" || key === "handle");
920
+ }
921
+ async function simulate(definition, options = {}) {
922
+ const nodes = definition.data.nodes;
923
+ const edges = definition.data.edges;
924
+ const byId = new Map(nodes.map((node) => [node.id, node]));
925
+ const incoming = new Set(edges.map((edge) => edge.target));
926
+ const maxVisits = options.maxVisits ?? 1;
927
+ const visits = /* @__PURE__ */ new Map();
928
+ const steps = [];
929
+ const outputs = {};
930
+ const queue = nodes.filter((node) => !incoming.has(node.id)).map((node) => ({ nodeId: node.id, input: options.trigger ?? {} }));
931
+ const mocks = options.mocks ?? {};
932
+ let env;
933
+ for (let guard = 0; queue.length > 0 && guard < 1e4; guard++) {
934
+ const next = queue.shift();
935
+ if (!next) break;
936
+ const node = byId.get(next.nodeId);
937
+ if (!node) continue;
938
+ const count = visits.get(node.id) ?? 0;
939
+ if (count >= maxVisits) continue;
940
+ visits.set(node.id, count + 1);
941
+ const mock = mocks[node.id] ?? mocks[node.type];
942
+ let output = next.input;
943
+ let handle;
944
+ if (mock) {
945
+ const produced = await mock(next.input, node);
946
+ if (isHandleResult(produced)) {
947
+ output = produced.output;
948
+ handle = produced.handle;
949
+ } else output = produced;
950
+ } else if (node.type === CODE_WORKFLOW_NODE_TYPE) {
951
+ const state = node.args?.state;
952
+ const workflowFn = isRecord(state) && typeof state["workflowFn"] === "string" ? state["workflowFn"] : "";
953
+ const fn = options.workflows?.[workflowFn];
954
+ if (!fn)
955
+ throw new Error(
956
+ `simulate: code-workflow node "${node.id}" runs "${workflowFn}"; pass it in workflows or mock the node`
957
+ );
958
+ env ??= await TestWorkflowEnvironment.create({ workflows: options.workflows ?? {} });
959
+ const handleRun = await env.start(fn, { input: next.input });
960
+ const result = jsonCopy(await handleRun.result());
961
+ output = result !== null && typeof result === "object" && !Array.isArray(result) ? result : { result };
962
+ }
963
+ outputs[node.id] = output;
964
+ steps.push({
965
+ nodeId: node.id,
966
+ type: node.type,
967
+ input: next.input,
968
+ output,
969
+ ...handle !== void 0 ? { handle } : {}
970
+ });
971
+ for (const edge of edges) {
972
+ if (edge.source !== node.id) continue;
973
+ if (handle !== void 0 && edge.sourceHandle !== void 0 && edge.sourceHandle !== handle)
974
+ continue;
975
+ queue.push({ nodeId: edge.target, input: output });
976
+ }
977
+ }
978
+ return { steps, outputs };
979
+ }
980
+ async function bundleToScript(bundle, esbuild) {
981
+ const api = esbuild ?? await import('esbuild');
982
+ const result = await api.transform(bundle, {
983
+ format: "iife",
984
+ globalName: "__fluidgridsBundle",
985
+ target: "es2023",
986
+ platform: "neutral",
987
+ charset: "utf8",
988
+ legalComments: "none",
989
+ logLevel: "silent"
990
+ });
991
+ return result.code;
992
+ }
993
+ function settle() {
994
+ return new Promise((resolve) => setImmediate(resolve));
995
+ }
996
+ async function runWorkflowTaskInVm(script, request, options = {}) {
997
+ const timeout = options.timeoutMs ?? 1e4;
998
+ const context = createContext(/* @__PURE__ */ Object.create(null), {
999
+ codeGeneration: { strings: false, wasm: false },
1000
+ microtaskMode: "afterEvaluate",
1001
+ name: "fluidgrids-workflow-task"
1002
+ });
1003
+ const run = (source, filename) => new Script(source, { filename }).runInContext(context, { timeout });
1004
+ try {
1005
+ run(TASK_PRELUDE, "fluidgrids-task-prelude.js");
1006
+ run(script, "workflow-bundle.js");
1007
+ run(
1008
+ `globalThis.__fluidgridsRuntime.start(${JSON.stringify(JSON.stringify(request))})`,
1009
+ "start.js"
1010
+ );
1011
+ await settle();
1012
+ const deadline = Date.now() + timeout;
1013
+ for (; ; ) {
1014
+ const step = run("globalThis.__fluidgridsRuntime.step()", "step.js");
1015
+ await settle();
1016
+ if (step !== "more") break;
1017
+ if (Date.now() > deadline)
1018
+ return failedTaskResult(
1019
+ "TASK_TIMEOUT",
1020
+ `The workflow task ran past its ${timeout} ms budget`
1021
+ );
1022
+ }
1023
+ const json = run("globalThis.__fluidgridsRuntime.complete()", "complete.js");
1024
+ if (typeof json !== "string")
1025
+ return failedTaskResult("INTERNAL", "The runtime returned no result");
1026
+ return JSON.parse(json);
1027
+ } catch (error) {
1028
+ const fields = typeof error === "object" && error !== null ? error : {};
1029
+ const message = typeof fields.message === "string" ? fields.message : String(error);
1030
+ const name = typeof fields.name === "string" ? fields.name : "";
1031
+ if (/timed out/i.test(message)) return failedTaskResult("TASK_TIMEOUT", message);
1032
+ if (name === "DeterminismViolation") return failedTaskResult("NON_DETERMINISM", message);
1033
+ return failedTaskResult("WORKFLOW_CODE_ERROR", `${name || "Error"}: ${message}`);
1034
+ }
1035
+ }
1036
+
1037
+ export { HistoryAssertionError, ReplayError, RunBlockedError, TestRunHandle, TestWorkflowEnvironment, WorkflowFailedError, WorkflowTaskFailedError, assertHistory, bundleToScript, drainJobs, expectIR, jsonCopy, registryOf, replay, replayRequest, runWorkflowTaskInProcess, runWorkflowTaskInVm, simulate };