@demlik/tea 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/README.md +30 -7
  2. package/dist/agent/index.d.ts +49 -36
  3. package/dist/agent/index.js +12 -8
  4. package/dist/chunk-4LPY67E4.js +18 -0
  5. package/dist/chunk-4LPY67E4.js.map +1 -0
  6. package/dist/chunk-5XU6ZJO2.js +533 -0
  7. package/dist/chunk-5XU6ZJO2.js.map +1 -0
  8. package/dist/{chunk-AQ6ZIMEA.js → chunk-6NG4V5SB.js} +89 -115
  9. package/dist/chunk-6NG4V5SB.js.map +1 -0
  10. package/dist/{chunk-HYDWG35Q.js → chunk-7MGXQZPW.js} +11 -9
  11. package/dist/chunk-7MGXQZPW.js.map +1 -0
  12. package/dist/{chunk-BLJ2JQSH.js → chunk-BCTEOVOU.js} +3 -3
  13. package/dist/{chunk-BLJ2JQSH.js.map → chunk-BCTEOVOU.js.map} +1 -1
  14. package/dist/chunk-CH5KTOEZ.js +710 -0
  15. package/dist/chunk-CH5KTOEZ.js.map +1 -0
  16. package/dist/chunk-CIHSHLQX.js +64 -0
  17. package/dist/chunk-CIHSHLQX.js.map +1 -0
  18. package/dist/{chunk-Z2L3LSNV.js → chunk-EHUDXAKC.js} +246 -210
  19. package/dist/chunk-EHUDXAKC.js.map +1 -0
  20. package/dist/chunk-GOX2CYIM.js +380 -0
  21. package/dist/chunk-GOX2CYIM.js.map +1 -0
  22. package/dist/chunk-IVJP3GKJ.js +104 -0
  23. package/dist/chunk-IVJP3GKJ.js.map +1 -0
  24. package/dist/{chunk-F7FMAWLA.js → chunk-MVZS5FDM.js} +13 -6
  25. package/dist/chunk-MVZS5FDM.js.map +1 -0
  26. package/dist/{chunk-LYLTKJWM.js → chunk-MYNLY3HP.js} +9 -8
  27. package/dist/chunk-MYNLY3HP.js.map +1 -0
  28. package/dist/{chunk-I5MQMJDW.js → chunk-NSIAORRY.js} +20 -30
  29. package/dist/chunk-NSIAORRY.js.map +1 -0
  30. package/dist/{chunk-MYXFNWAJ.js → chunk-QNGYQMZB.js} +4 -4
  31. package/dist/chunk-QNGYQMZB.js.map +1 -0
  32. package/dist/chunk-WT5PBPLB.js +98 -0
  33. package/dist/chunk-WT5PBPLB.js.map +1 -0
  34. package/dist/{core-CTzMJ82M.d.ts → core-BbgtiXdg.d.ts} +231 -139
  35. package/dist/do/index.d.ts +37 -32
  36. package/dist/do/index.js +21 -14
  37. package/dist/do/index.js.map +1 -1
  38. package/dist/{durable-effects-B0-qGQDy.d.ts → durable-effects-7MTK_jnj.d.ts} +1 -1
  39. package/dist/effect/index.d.ts +151 -0
  40. package/dist/effect/index.js +98 -0
  41. package/dist/effect/index.js.map +1 -0
  42. package/dist/extension/index.d.ts +6 -7
  43. package/dist/extension/index.js +3 -2
  44. package/dist/extension/index.js.map +1 -1
  45. package/dist/flow/index.d.ts +68 -70
  46. package/dist/flow/index.js +32 -27
  47. package/dist/flow/index.js.map +1 -1
  48. package/dist/idempotency/index.d.ts +6 -7
  49. package/dist/idempotency/index.js +4 -2
  50. package/dist/{index-DJWIER_3.d.ts → index-7vV8xmsD.d.ts} +1 -1
  51. package/dist/{index-NjrT3fL2.d.ts → index-Bz5Rh9Ga.d.ts} +19 -96
  52. package/dist/index-CjFDUBeq.d.ts +207 -0
  53. package/dist/{index-6A7dB8f6.d.ts → index-CkFYA9Y6.d.ts} +14 -16
  54. package/dist/{index-B5BZ-_cT.d.ts → index-Cou86-GX.d.ts} +189 -108
  55. package/dist/{index-TvRb1KVf.d.ts → index-DU6JpQ8G.d.ts} +1 -1
  56. package/dist/{index-q0hXFeZ0.d.ts → index-Xufl55W8.d.ts} +286 -215
  57. package/dist/{index-4vZzljCD.d.ts → index-_dOR3xqA.d.ts} +57 -45
  58. package/dist/index.d.ts +181 -385
  59. package/dist/index.js +2 -1
  60. package/dist/jev/index.d.ts +147 -99
  61. package/dist/jev/index.js +329 -233
  62. package/dist/jev/index.js.map +1 -1
  63. package/dist/{machine-CdIcN2Xm.d.ts → machine-BQzTV8On.d.ts} +171 -280
  64. package/dist/machine-viz/index.d.ts +5 -5
  65. package/dist/machine-viz/index.js +8 -4
  66. package/dist/machine-viz/index.js.map +1 -1
  67. package/dist/mem/index.d.ts +3 -4
  68. package/dist/mem/index.js +2 -1
  69. package/dist/mem/index.js.map +1 -1
  70. package/dist/node/index.d.ts +119 -78
  71. package/dist/node/index.js +52 -38
  72. package/dist/node/index.js.map +1 -1
  73. package/dist/paginate/index.d.ts +27 -33
  74. package/dist/paginate/index.js +6 -4
  75. package/dist/parity/index.d.ts +4 -5
  76. package/dist/parity/index.js +3 -2
  77. package/dist/parity/index.js.map +1 -1
  78. package/dist/pbt/index.d.ts +2 -2
  79. package/dist/pbt/index.js +2 -1
  80. package/dist/pbt/index.js.map +1 -1
  81. package/dist/persistence/index.d.ts +6 -7
  82. package/dist/persistence/index.js +5 -3
  83. package/dist/promise/index.d.ts +186 -0
  84. package/dist/promise/index.js +5 -0
  85. package/dist/promise/index.js.map +1 -0
  86. package/dist/protocol-ClRZBhHQ.d.ts +53 -0
  87. package/dist/react/index.d.ts +36 -17
  88. package/dist/react/index.js +27 -4
  89. package/dist/react/index.js.map +1 -1
  90. package/dist/resilience/index.d.ts +63 -455
  91. package/dist/resilience/index.js +37 -483
  92. package/dist/resilience/index.js.map +1 -1
  93. package/dist/{runtime-types-LWDoi943.d.ts → runtime-types-CiVpC0HN.d.ts} +131 -388
  94. package/dist/testing/index.d.ts +165 -15
  95. package/dist/testing/index.js +104 -3
  96. package/dist/testing/index.js.map +1 -1
  97. package/dist/timing/index.d.ts +25 -22
  98. package/dist/timing/index.js +4 -3
  99. package/dist/timing/index.js.map +1 -1
  100. package/dist/{types-DP3f5NIl.d.ts → types-LxZxGYda.d.ts} +2 -2
  101. package/dist/work-queue/index.d.ts +5 -6
  102. package/package.json +18 -5
  103. package/dist/chunk-AQ6ZIMEA.js.map +0 -1
  104. package/dist/chunk-F2US5BW6.js +0 -72
  105. package/dist/chunk-F2US5BW6.js.map +0 -1
  106. package/dist/chunk-F7FMAWLA.js.map +0 -1
  107. package/dist/chunk-HUKZQX6U.js +0 -97
  108. package/dist/chunk-HUKZQX6U.js.map +0 -1
  109. package/dist/chunk-HYDWG35Q.js.map +0 -1
  110. package/dist/chunk-I5MQMJDW.js.map +0 -1
  111. package/dist/chunk-LYLTKJWM.js.map +0 -1
  112. package/dist/chunk-MYXFNWAJ.js.map +0 -1
  113. package/dist/chunk-RQULHGYH.js +0 -1804
  114. package/dist/chunk-RQULHGYH.js.map +0 -1
  115. package/dist/chunk-Z2L3LSNV.js.map +0 -1
  116. package/dist/index-DMCeMPMJ.d.ts +0 -272
@@ -1,7 +1,6 @@
1
- import { C as Cmd, S as Sub, M as Machine } from '../core-CTzMJ82M.js';
2
- import { R as Runtime } from '../runtime-types-LWDoi943.js';
3
- import 'zod';
4
- import 'better-result';
1
+ import { C as Cmd, S as Sub, M as Machine, a as BuiltinSub, p as Interpret } from '../core-BbgtiXdg.js';
2
+ import { C as CtxArg, R as Runtime } from '../runtime-types-CiVpC0HN.js';
3
+ import '@standard-schema/spec';
5
4
 
6
5
  /**
7
6
  * The composed Model shape every `withX` wrapper produces: the base machine's
@@ -31,10 +30,10 @@ interface AssertWrapperFaithfulOpts<S, WM, BaseM, BaseCtx, WrapperCtx> {
31
30
  */
32
31
  readonly baseLoaded?: S | null;
33
32
  /**
34
- * Opt-in relaxation for an INTERCEPTING wrapper (e.g. `withResilience`).
33
+ * Opt-in relaxation for an INTERCEPTING wrapper (e.g. a retry wrapper).
35
34
  *
36
- * DEFAULT (`undefined` / `false`) — the OBSERVE-ONLY contract that
37
- * `withTelemetry` and `withDeadline` satisfy: every bare base Cmd must appear
35
+ * DEFAULT (`undefined` / `false`) — the OBSERVE-ONLY contract that a
36
+ * deadline wrapper satisfies: every bare base Cmd must appear
38
37
  * UNCHANGED, in order, as a subsequence of the wrapped Cmd stream (properties
39
38
  * 1 and 3b). The wrapper may APPEND its own Cmds but may NOT alter, drop, or
40
39
  * retime a base Cmd. This is the strict default and it is NOT weakened.
@@ -58,7 +57,7 @@ interface AssertWrapperFaithfulOpts<S, WM, BaseM, BaseCtx, WrapperCtx> {
58
57
  * Cmd in its window is a SWALLOW betrayal and still fails the gate.
59
58
  *
60
59
  * Provide the field names the carrier uses so the gate can unwrap it
61
- * structurally without hard-coding `withResilience`'s vocabulary.
60
+ * structurally without hard-coding any one wrapper's vocabulary.
62
61
  */
63
62
  readonly intercepting?: InterceptingOpt;
64
63
  }
@@ -94,10 +93,11 @@ interface InterceptingOpt {
94
93
  *
95
94
  * @param base the bare base `Machine`.
96
95
  * @param makeWrapped a thunk that wraps `base` and returns the composed machine
97
- * (e.g. `() => withTelemetry(base, cfg)`). A thunk — not the wrapped value —
98
- * so a wrapper that registers process-scoped ports (`definePort`) is
99
- * constructed inside the assertion's control, and so the same betrayal-
100
- * detector reads as `assertWrapperFaithful(base, () => withX(base, cfg), ...)`
96
+ * (e.g. `() => withTimeout(wired, cfg).machine`, where `wired.machine` is
97
+ * `base`). A thunk — not the wrapped value — so a wrapper that registers
98
+ * process-scoped ports (`definePort`) is constructed inside the assertion's
99
+ * control, and so the same betrayal-detector reads as
100
+ * `assertWrapperFaithful(wired.machine, () => withX(wired, cfg).machine, ...)`
101
101
  * at every wrapper's test site.
102
102
  * @param opts the conformance replay options.
103
103
  */
@@ -239,7 +239,7 @@ interface BoundMachine<S, M extends {
239
239
  * Assert the exact set of subs desired at the final state. Mirrors the
240
240
  * free `expectActiveSubs(machine, opts, expected)`.
241
241
  */
242
- expectActiveSubs(opts: BoundOpts<S, M>, expected: readonly NoInfer<U>[]): void;
242
+ expectActiveSubs(opts: BoundOpts<S, M>, expected: readonly NoInfer<U | BuiltinSub<M>>[]): void;
243
243
  /**
244
244
  * Bound `replay` — returns `{ state, cmds, subs }` for the given opts.
245
245
  * Use for the "narrow-then-assert" pattern where the test inspects a
@@ -248,7 +248,7 @@ interface BoundMachine<S, M extends {
248
248
  replay(opts: BoundOpts<S, M>): {
249
249
  state: S;
250
250
  cmds: readonly C[];
251
- subs: readonly U[];
251
+ subs: readonly (U | BuiltinSub<M>)[];
252
252
  };
253
253
  }
254
254
  /**
@@ -265,6 +265,156 @@ declare function bindMachine<S, M extends {
265
265
  type: string;
266
266
  }, C extends Cmd, U extends Sub, Ctx>(machine: Machine<S, M, C, U, Ctx>, ctx: Ctx): BoundMachine<S, M, C, U, Ctx>;
267
267
 
268
+ /**
269
+ * The default round bound when `opts.maxRounds` is omitted: **100**.
270
+ *
271
+ * A round is one drain of the pending Msgs plus one drain of the Cmds those
272
+ * folds emitted, so a settling machine costs a handful; 100 is loose enough
273
+ * that a legitimate retry ladder finishes inside it and tight enough that a
274
+ * genuine cycle stops rather than hangs.
275
+ */
276
+ declare const DEFAULT_MAX_ROUNDS = 100;
277
+ /**
278
+ * One entry of a driven run's history, in dispatch order.
279
+ *
280
+ * - `msg` — a Msg folded through the machine's reducer. The seed Msg is the
281
+ * first entry of every trace; every later one is a settle Msg a handler
282
+ * returned (or fired through its injected `dispatch`).
283
+ * - `cmd` — a Cmd handed to an interpret handler. It is recorded BEFORE the
284
+ * handler is awaited, so a trace recovered from a rejected handler names the
285
+ * Cmd that rejected as its last entry.
286
+ */
287
+ type DriveTraceEntry<M, C> = {
288
+ readonly kind: "msg";
289
+ readonly msg: M;
290
+ } | {
291
+ readonly kind: "cmd";
292
+ readonly cmd: C;
293
+ };
294
+ /** What a settled {@link drive} hands back. */
295
+ interface DriveResult<S, M, C> {
296
+ /** The state after the machine went quiet — no Msg pending, no Cmd unperformed. */
297
+ readonly state: S;
298
+ /**
299
+ * Every Msg folded and every Cmd dispatched, in order. Filtering it to its
300
+ * `msg` entries and replaying those through `replay` from the same initial
301
+ * state reproduces {@link DriveResult.state}.
302
+ */
303
+ readonly trace: readonly DriveTraceEntry<M, C>[];
304
+ }
305
+ /**
306
+ * {@link drive}'s `ctx` field. `CtxArg` widened by exactly one case: a machine
307
+ * declaring `ctx: undefined` in its `types` block — the pure shape every
308
+ * how-to on this package writes — may omit it too. `CtxArg` alone keeps `ctx`
309
+ * REQUIRED there, because `{}` is not assignable to `undefined`, which would
310
+ * make the common pure call site spell `{ ctx: undefined }` to say nothing.
311
+ * A machine whose `Ctx` carries a field a handler reads still requires it.
312
+ */
313
+ type DriveCtxArg<Ctx> = [undefined] extends [Ctx] ? {
314
+ readonly ctx?: Ctx;
315
+ } : CtxArg<Ctx>;
316
+ /**
317
+ * {@link drive}'s options. `ctx` is conditionally optional (see
318
+ * {@link DriveCtxArg}).
319
+ */
320
+ type DriveOptions<Ctx> = DriveCtxArg<Ctx> & {
321
+ /**
322
+ * The round bound. Exceeding it throws {@link DriveRoundsExceededError};
323
+ * `drive` never returns a half-driven state. Defaults to
324
+ * {@link DEFAULT_MAX_ROUNDS} (100).
325
+ */
326
+ readonly maxRounds?: number;
327
+ /**
328
+ * The clock that stamps `at` on a `Cmd.define`d Cmd's minted `_ok` / `_err`
329
+ * Msg — the one `run` takes. Defaults to `Date.now`; pin it for a test that
330
+ * asserts on `at`.
331
+ */
332
+ readonly clock?: () => number;
333
+ };
334
+ /**
335
+ * Raised when a driven machine is still emitting work after `maxRounds` rounds.
336
+ * Follows the tea error dialect (`override readonly name`) and carries a `_tag`
337
+ * discriminant, so a test branches on it by `instanceof` or on the tag.
338
+ *
339
+ * It carries the round count it stopped at and the PARTIAL trace up to that
340
+ * point, which is what a test needs to see the cycle — the bound alone says
341
+ * only that one exists.
342
+ */
343
+ declare class DriveRoundsExceededError<M, C> extends Error {
344
+ /** The bound that was exceeded. */
345
+ readonly maxRounds: number;
346
+ /** The round the driver stopped at — always `maxRounds + 1`. */
347
+ readonly rounds: number;
348
+ /** Every Msg folded and Cmd dispatched before the driver gave up. */
349
+ readonly trace: readonly DriveTraceEntry<M, C>[];
350
+ readonly name = "DriveRoundsExceededError";
351
+ readonly _tag: "DriveRoundsExceededError";
352
+ constructor(
353
+ /** The bound that was exceeded. */
354
+ maxRounds: number,
355
+ /** The round the driver stopped at — always `maxRounds + 1`. */
356
+ rounds: number,
357
+ /** Every Msg folded and Cmd dispatched before the driver gave up. */
358
+ trace: readonly DriveTraceEntry<M, C>[]);
359
+ }
360
+ /**
361
+ * Raised when a Cmd reaches {@link drive} with no handler for its `type` in the
362
+ * `handlers` record. `Interpret`'s mapped type makes that a compile error at a
363
+ * well-typed call site; this is the runtime arm for a handler record assembled
364
+ * dynamically or cast.
365
+ */
366
+ declare class DriveNoHandlerError<M, C> extends Error {
367
+ /** The Cmd `type` nothing in the handler record answered. */
368
+ readonly cmdType: string;
369
+ /** Every Msg folded and Cmd dispatched before the miss. */
370
+ readonly trace: readonly DriveTraceEntry<M, C>[];
371
+ readonly name = "DriveNoHandlerError";
372
+ readonly _tag: "DriveNoHandlerError";
373
+ constructor(
374
+ /** The Cmd `type` nothing in the handler record answered. */
375
+ cmdType: string,
376
+ /** Every Msg folded and Cmd dispatched before the miss. */
377
+ trace: readonly DriveTraceEntry<M, C>[]);
378
+ }
379
+ /**
380
+ * Read back the partial trace `drive` attached to an error a handler rejected
381
+ * with. Returns `undefined` for anything `drive` did not annotate.
382
+ *
383
+ * A rejecting handler's error propagates out of `drive` UNSWALLOWED — same
384
+ * value, same `instanceof`, same `_tag` — so a test that already branches on
385
+ * the handler's own error type keeps doing so. The trace rides along on a
386
+ * non-enumerable property so it is recoverable without changing how the error
387
+ * prints or serializes.
388
+ */
389
+ declare function driveTraceOf<M, C>(err: unknown): readonly DriveTraceEntry<M, C>[] | undefined;
390
+ /**
391
+ * Drive `machine` from `initial` through `msg` against the REAL interpret
392
+ * `handlers`, feeding every settle Msg back until the machine goes quiet, and
393
+ * hand back the settled state together with the whole history.
394
+ *
395
+ * `handlers` is the same table a host hands `run` as its `interpret` (e.g.
396
+ * a jev knob's `resilient_run` handler), so the test drives the real interpreter and
397
+ * mocks only the port beneath it.
398
+ *
399
+ * One round is: fold every pending Msg, then await a handler for every Cmd
400
+ * those folds emitted. A Msg a handler returns — or fires through its injected
401
+ * `dispatch` — is pending for the next round. A machine that emits no Cmd for
402
+ * its seed Msg therefore settles in ONE round and never awaits a handler at
403
+ * all.
404
+ *
405
+ * The `ctx` handed to each handler is `opts.ctx` augmented with a NO-OP `emit`,
406
+ * so a handler that emits to a `Port` runs without a live runtime; nothing here
407
+ * observes ports. Supply your own `emit` on `ctx` to capture them.
408
+ *
409
+ * @throws {DriveRoundsExceededError} when the machine is still emitting work
410
+ * after `opts.maxRounds` rounds. It never returns a half-driven state.
411
+ * @throws whatever a handler rejects with, unchanged — annotated with the
412
+ * partial trace, readable via {@link driveTraceOf}.
413
+ */
414
+ declare function drive<S, M extends {
415
+ type: string;
416
+ }, C extends Cmd, U extends Sub, Ctx>(machine: Machine<S, M, C, U, Ctx>, initial: S, msg: M, handlers: Interpret<M, C, Ctx>, ...[opts]: Record<never, never> extends DriveOptions<Ctx> ? [opts?: DriveOptions<Ctx>] : [opts: DriveOptions<Ctx>]): Promise<DriveResult<S, M, C>>;
417
+
268
418
  /**
269
419
  * Construct an inert `Runtime<S, M>` value. Use at ctx sites that require
270
420
  * a Runtime reference but where the test never invokes `interpret` (so
@@ -324,4 +474,4 @@ declare function stateFactory<S extends {
324
474
  type: string;
325
475
  }>(defaults: StateFactoryDefaults<S>): StateFactoryAPI<S>;
326
476
 
327
- export { type AssertWrapperFaithfulOpts, type BoundMachine, type InterceptingOpt, type ReplayOpts, type StateFactoryAPI, type StateFactoryDefaults, type WrapperModel, assertWrapperFaithful, bindMachine, expectActiveSubs, expectCmdEmitted, expectCmdSequence, expectFinalState, noopRuntime, stateFactory, step };
477
+ export { type AssertWrapperFaithfulOpts, type BoundMachine, DEFAULT_MAX_ROUNDS, type DriveCtxArg, DriveNoHandlerError, type DriveOptions, type DriveResult, DriveRoundsExceededError, type DriveTraceEntry, type InterceptingOpt, type ReplayOpts, type StateFactoryAPI, type StateFactoryDefaults, type WrapperModel, assertWrapperFaithful, bindMachine, drive, driveTraceOf, expectActiveSubs, expectCmdEmitted, expectCmdSequence, expectFinalState, noopRuntime, stateFactory, step };
@@ -1,4 +1,5 @@
1
- import { replay } from '../chunk-RQULHGYH.js';
1
+ import '../chunk-GOX2CYIM.js';
2
+ import { replay, cmdEdgeOver, Outcome, cmdEdge } from '../chunk-CH5KTOEZ.js';
2
3
  import { expect, vi } from 'vitest';
3
4
 
4
5
  function assertWrapperFaithful(base, makeWrapped, opts) {
@@ -116,7 +117,7 @@ function wrapperSliceKey(model) {
116
117
  const dollarKeys = Object.keys(model).filter((k) => k.startsWith("$"));
117
118
  if (dollarKeys.length !== 1) {
118
119
  throw new Error(
119
- `assertWrapperFaithful: expected exactly one "$"-prefixed wrapper slice on the composed Model, found ${dollarKeys.length} (${dollarKeys.join(", ") || "none"}). Every withX wrapper owns exactly one named slice (e.g. "$telemetry").`
120
+ `assertWrapperFaithful: expected exactly one "$"-prefixed wrapper slice on the composed Model, found ${dollarKeys.length} (${dollarKeys.join(", ") || "none"}). Every withX wrapper owns exactly one named slice (e.g. "$deadline").`
120
121
  );
121
122
  }
122
123
  return dollarKeys[0];
@@ -223,6 +224,106 @@ function bindMachine(machine, ctx) {
223
224
  };
224
225
  }
225
226
 
227
+ // src/testing/drive.ts
228
+ var DEFAULT_MAX_ROUNDS = 100;
229
+ var DriveRoundsExceededError = class extends Error {
230
+ constructor(maxRounds, rounds, trace) {
231
+ super(
232
+ `@demlik/tea: drive did not settle within ${maxRounds} round(s) \u2014 the machine was still emitting work at round ${rounds}. The partial trace (${trace.length} entr${trace.length === 1 ? "y" : "ies"}) is on the error's \`trace\` field.`
233
+ );
234
+ this.maxRounds = maxRounds;
235
+ this.rounds = rounds;
236
+ this.trace = trace;
237
+ }
238
+ maxRounds;
239
+ rounds;
240
+ trace;
241
+ name = "DriveRoundsExceededError";
242
+ _tag = "DriveRoundsExceededError";
243
+ };
244
+ var DriveNoHandlerError = class extends Error {
245
+ constructor(cmdType, trace) {
246
+ super(
247
+ `@demlik/tea: drive found no interpret handler for Cmd type "${cmdType}" \u2014 the handlers record must answer every Cmd the machine emits.`
248
+ );
249
+ this.cmdType = cmdType;
250
+ this.trace = trace;
251
+ }
252
+ cmdType;
253
+ trace;
254
+ name = "DriveNoHandlerError";
255
+ _tag = "DriveNoHandlerError";
256
+ };
257
+ var TRACE_KEY = "driveTrace";
258
+ function driveTraceOf(err) {
259
+ if (typeof err !== "object" || err === null) return void 0;
260
+ const carried = err[TRACE_KEY];
261
+ return Array.isArray(carried) ? carried : void 0;
262
+ }
263
+ async function drive(machine, initial, msg, handlers, ...[opts]) {
264
+ const options = opts ?? {};
265
+ const maxRounds = options.maxRounds ?? DEFAULT_MAX_ROUNDS;
266
+ const bound = bindMachine(machine, options.ctx);
267
+ const settle = cmdEdgeOver(machine.cmds ?? [], options.clock ?? Date.now);
268
+ const noopEmit = { emit: () => {
269
+ } };
270
+ const handlerCtx = {
271
+ ...noopEmit,
272
+ ok: Outcome.ok,
273
+ err: Outcome.err,
274
+ [cmdEdge]: settle,
275
+ ...options.ctx
276
+ };
277
+ const cells = handlers;
278
+ const trace = [];
279
+ let state = initial;
280
+ let pending = [msg];
281
+ let round = 0;
282
+ while (pending.length > 0) {
283
+ round += 1;
284
+ if (round > maxRounds) {
285
+ throw new DriveRoundsExceededError(maxRounds, round, trace);
286
+ }
287
+ const folding = pending;
288
+ pending = [];
289
+ const emitted = [];
290
+ for (const next of folding) {
291
+ trace.push({ kind: "msg", msg: next });
292
+ const [advanced, cmds] = bound.step(state, next);
293
+ state = advanced;
294
+ emitted.push(...cmds);
295
+ }
296
+ for (const cmd of emitted) {
297
+ trace.push({ kind: "cmd", cmd });
298
+ const cell = cells[cmd.type];
299
+ if (cell === void 0) {
300
+ throw new DriveNoHandlerError(cmd.type, trace);
301
+ }
302
+ let settled;
303
+ try {
304
+ settled = settle(
305
+ cmd,
306
+ await cell(cmd, handlerCtx, (fired) => {
307
+ pending.push(fired);
308
+ })
309
+ );
310
+ } catch (err) {
311
+ if (typeof err === "object" && err !== null) {
312
+ Object.defineProperty(err, TRACE_KEY, {
313
+ value: [...trace],
314
+ enumerable: false,
315
+ configurable: true,
316
+ writable: true
317
+ });
318
+ }
319
+ throw err;
320
+ }
321
+ if (settled !== void 0) pending.push(settled);
322
+ }
323
+ }
324
+ return { state, trace };
325
+ }
326
+
226
327
  // src/testing/noop-runtime.ts
227
328
  function noopRuntime(opts) {
228
329
  const initialState = opts?.initialState;
@@ -297,6 +398,6 @@ function stateFactory(defaults) {
297
398
  return api;
298
399
  }
299
400
 
300
- export { assertWrapperFaithful, bindMachine, expectActiveSubs, expectCmdEmitted, expectCmdSequence, expectFinalState, noopRuntime, stateFactory, step };
401
+ export { DEFAULT_MAX_ROUNDS, DriveNoHandlerError, DriveRoundsExceededError, assertWrapperFaithful, bindMachine, drive, driveTraceOf, expectActiveSubs, expectCmdEmitted, expectCmdSequence, expectFinalState, noopRuntime, stateFactory, step };
301
402
  //# sourceMappingURL=index.js.map
302
403
  //# sourceMappingURL=index.js.map