@demlik/tea 0.21.0 → 0.22.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 (126) hide show
  1. package/dist/agent/index.d.ts +32 -19
  2. package/dist/agent/index.js +12 -10
  3. package/dist/{chunk-VM7PMFV5.js → chunk-2A5XTKQQ.js} +142 -19
  4. package/dist/chunk-2A5XTKQQ.js.map +1 -0
  5. package/dist/{chunk-EB5OMOXX.js → chunk-7K7SEYCH.js} +16 -15
  6. package/dist/chunk-7K7SEYCH.js.map +1 -0
  7. package/dist/{chunk-GAVLND5C.js → chunk-A3QPMX5M.js} +17 -10
  8. package/dist/chunk-A3QPMX5M.js.map +1 -0
  9. package/dist/{chunk-VHKDUBXC.js → chunk-BVFS3ICC.js} +3 -3
  10. package/dist/{chunk-VHKDUBXC.js.map → chunk-BVFS3ICC.js.map} +1 -1
  11. package/dist/{chunk-4I2LCQKB.js → chunk-CPE5ZQXQ.js} +3 -3
  12. package/dist/{chunk-4I2LCQKB.js.map → chunk-CPE5ZQXQ.js.map} +1 -1
  13. package/dist/chunk-DEAPNMKO.js +11 -0
  14. package/dist/chunk-DEAPNMKO.js.map +1 -0
  15. package/dist/{chunk-BFSZ4L6Y.js → chunk-ETRNSJCR.js} +4 -4
  16. package/dist/{chunk-BFSZ4L6Y.js.map → chunk-ETRNSJCR.js.map} +1 -1
  17. package/dist/chunk-FXWDDNGD.js +98 -0
  18. package/dist/chunk-FXWDDNGD.js.map +1 -0
  19. package/dist/chunk-GFGOQIEC.js +10 -0
  20. package/dist/chunk-GFGOQIEC.js.map +1 -0
  21. package/dist/{chunk-YDF6P74H.js → chunk-IAHEYPYJ.js} +3 -3
  22. package/dist/{chunk-YDF6P74H.js.map → chunk-IAHEYPYJ.js.map} +1 -1
  23. package/dist/{chunk-YCJPLPQQ.js → chunk-J5JNLXO7.js} +3 -3
  24. package/dist/{chunk-YCJPLPQQ.js.map → chunk-J5JNLXO7.js.map} +1 -1
  25. package/dist/{chunk-MBQR3LFW.js → chunk-NFOGYDRS.js} +5 -5
  26. package/dist/{chunk-MBQR3LFW.js.map → chunk-NFOGYDRS.js.map} +1 -1
  27. package/dist/{chunk-Z7IL735U.js → chunk-R2YNDRGL.js} +3 -3
  28. package/dist/{chunk-Z7IL735U.js.map → chunk-R2YNDRGL.js.map} +1 -1
  29. package/dist/{chunk-ZV4WGW74.js → chunk-R5NXOPL2.js} +3 -3
  30. package/dist/{chunk-ZV4WGW74.js.map → chunk-R5NXOPL2.js.map} +1 -1
  31. package/dist/chunk-RMBZ7YH7.js +52 -0
  32. package/dist/chunk-RMBZ7YH7.js.map +1 -0
  33. package/dist/{chunk-HU3Y2AZY.js → chunk-SHXUSTHM.js} +62 -15
  34. package/dist/chunk-SHXUSTHM.js.map +1 -0
  35. package/dist/{chunk-QIE4Y6BK.js → chunk-UGTC33VH.js} +4 -4
  36. package/dist/{chunk-QIE4Y6BK.js.map → chunk-UGTC33VH.js.map} +1 -1
  37. package/dist/{chunk-6NIQIUGR.js → chunk-VH5MYJ2K.js} +4 -4
  38. package/dist/{chunk-6NIQIUGR.js.map → chunk-VH5MYJ2K.js.map} +1 -1
  39. package/dist/{chunk-W6XWI6QE.js → chunk-WZEDIUWR.js} +8 -7
  40. package/dist/chunk-WZEDIUWR.js.map +1 -0
  41. package/dist/{chunk-TMYUDZLE.js → chunk-ZRRET3RP.js} +11 -11
  42. package/dist/chunk-ZRRET3RP.js.map +1 -0
  43. package/dist/{core-BKJW1Kmq.d.ts → core-DNDAEhO2.d.ts} +7 -10
  44. package/dist/devtools/index.d.ts +4 -4
  45. package/dist/do/index.d.ts +98 -35
  46. package/dist/do/index.js +40 -21
  47. package/dist/do/index.js.map +1 -1
  48. package/dist/{drive-loop-Dc6Z8Vbs.d.ts → drive-loop-DlOEv49c.d.ts} +2 -2
  49. package/dist/{durable-effects-CgrLNccA.d.ts → durable-effects-wLWEdy9H.d.ts} +1 -1
  50. package/dist/effect/index.d.ts +52 -26
  51. package/dist/effect/index.js +7 -31
  52. package/dist/effect/index.js.map +1 -1
  53. package/dist/extension/index.d.ts +3 -3
  54. package/dist/extension/index.js +2 -2
  55. package/dist/flow/index.d.ts +13 -13
  56. package/dist/flow/index.js +13 -11
  57. package/dist/flow/index.js.map +1 -1
  58. package/dist/idempotency/index.d.ts +3 -3
  59. package/dist/idempotency/index.js +3 -3
  60. package/dist/{index-1hit3dJ9.d.ts → index-9P1PaXIV.d.ts} +2 -2
  61. package/dist/{index-Cuy1_xTh.d.ts → index-BBLI2WWN.d.ts} +1 -1
  62. package/dist/{index-Z6Uw7C9t.d.ts → index-C1WhBZ6a.d.ts} +4 -5
  63. package/dist/{index-B-VC4qjx.d.ts → index-C92SgNWz.d.ts} +2 -2
  64. package/dist/{index-CYrUWi1c.d.ts → index-CAsUE_d1.d.ts} +4 -4
  65. package/dist/{index-BjwRcjBi.d.ts → index-CO34esWT.d.ts} +2 -2
  66. package/dist/{index-DB8Yn0-K.d.ts → index-COeX9Bmz.d.ts} +11 -9
  67. package/dist/{index-Diyo8WBO.d.ts → index-Cg0QXnzf.d.ts} +2 -2
  68. package/dist/index.d.ts +5 -5
  69. package/dist/index.js +2 -2
  70. package/dist/jev/index.d.ts +8 -8
  71. package/dist/jev/index.js +5 -5
  72. package/dist/laws-DTtCDDlU.d.ts +135 -0
  73. package/dist/loop-DReKGV1X.d.ts +70 -0
  74. package/dist/{machine-BuPTS1QH.d.ts → machine-C0WhOAyB.d.ts} +5 -5
  75. package/dist/machine-viz/index.d.ts +1 -1
  76. package/dist/machine-viz/index.js +2 -2
  77. package/dist/mem/index.d.ts +4 -3
  78. package/dist/mem/index.js +2 -2
  79. package/dist/mem/index.js.map +1 -1
  80. package/dist/node/index.d.ts +57 -9
  81. package/dist/node/index.js +175 -68
  82. package/dist/node/index.js.map +1 -1
  83. package/dist/otel/index.d.ts +28 -12
  84. package/dist/otel/index.js +25 -21
  85. package/dist/otel/index.js.map +1 -1
  86. package/dist/paginate/index.d.ts +4 -4
  87. package/dist/paginate/index.js +5 -5
  88. package/dist/parity/index.d.ts +11 -15
  89. package/dist/parity/index.js +4 -9
  90. package/dist/parity/index.js.map +1 -1
  91. package/dist/pbt/index.d.ts +279 -67
  92. package/dist/pbt/index.js +299 -58
  93. package/dist/pbt/index.js.map +1 -1
  94. package/dist/persistence/index.d.ts +10 -12
  95. package/dist/persistence/index.js +4 -4
  96. package/dist/promise/index.d.ts +95 -13
  97. package/dist/promise/index.js +6 -4
  98. package/dist/react/index.d.ts +34 -22
  99. package/dist/react/index.js +271 -42
  100. package/dist/react/index.js.map +1 -1
  101. package/dist/resilience/index.d.ts +6 -6
  102. package/dist/resilience/index.js +5 -5
  103. package/dist/{run-D0sxbyno.d.ts → run-Cnw4qXj2.d.ts} +86 -17
  104. package/dist/{runtime-types-BPKp2bGo.d.ts → runtime-types-CP-ykwgf.d.ts} +120 -31
  105. package/dist/testing/effect/index.d.ts +162 -9
  106. package/dist/testing/effect/index.js +110 -10
  107. package/dist/testing/effect/index.js.map +1 -1
  108. package/dist/testing/index.d.ts +6 -8
  109. package/dist/testing/index.js +4 -24
  110. package/dist/testing/index.js.map +1 -1
  111. package/dist/testing/promise/index.d.ts +4 -4
  112. package/dist/testing/promise/index.js +5 -5
  113. package/dist/timing/index.d.ts +4 -4
  114. package/dist/timing/index.js +4 -4
  115. package/dist/{types-BiXj7k_k.d.ts → types-S53WDy_h.d.ts} +1 -1
  116. package/dist/work-queue/index.d.ts +4 -4
  117. package/dist/work-queue/index.js +2 -2
  118. package/package.json +18 -18
  119. package/dist/chunk-7Z2IDZ7R.js +0 -34
  120. package/dist/chunk-7Z2IDZ7R.js.map +0 -1
  121. package/dist/chunk-EB5OMOXX.js.map +0 -1
  122. package/dist/chunk-GAVLND5C.js.map +0 -1
  123. package/dist/chunk-HU3Y2AZY.js.map +0 -1
  124. package/dist/chunk-TMYUDZLE.js.map +0 -1
  125. package/dist/chunk-VM7PMFV5.js.map +0 -1
  126. package/dist/chunk-W6XWI6QE.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/testing/effect/drive.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// ---------------------------------------------------------------------------\n// `drive(machine, initial, msg, interpret, opts)` for the Effect engine: the\n// Promise `drive`'s rounds, trace and bound, over the Effect engine's handlers\n// and Subs (#321).\n//\n// There is no second loop. Each Effect cell is adapted into the shared loop's\n// cell with the settle rule the engine itself uses (`settleCellExit` in\n// `src/effect/run.ts`), run with the services the caller provided, so a cell\n// settles here exactly as it does under `run`. The Subs the machine wants are\n// started and stopped after every fold, as the engine reconciles them, and\n// their Msgs are folded once the Cmds in flight have settled.\n// ---------------------------------------------------------------------------\n\nimport { Cause, Effect, Exit, Fiber, Stream } from \"effect\";\nimport { CellFailure } from \"../../effect/failures\";\nimport {\n builtinRunners,\n type CellErrors,\n cellResult,\n type EffectInterpret,\n type EffectSubscribe,\n type InterpretServices,\n type SubscribeOption,\n type SubscribeServices,\n settleCellExit,\n} from \"../../effect/run\";\nimport type { Cmd, Machine, Sub } from \"../../pure/core\";\nimport {\n annotateTrace,\n DEFAULT_MAX_ROUNDS,\n type DriveCell,\n DriveNoHandlerError,\n type DriveOptions,\n type DriveResult,\n DriveRoundsExceededError,\n type DriveSubs,\n desiredSubs,\n driveContractOf,\n driveLoop,\n driveTraceOf,\n} from \"../drive-loop\";\n\n/**\n * The Effect `drive`'s options: the Promise `drive`'s, plus `subscribe` — the\n * runners the engine's `run` takes, required exactly when `run` requires them.\n */\nexport type EffectDriveOptions<U extends Sub, Ctx, B> = DriveOptions<Ctx> &\n SubscribeOption<U, B>;\n\n/** The failures a drive ends with: the loop's own, and a hand-written cell's. */\nexport type EffectDriveError<M, C extends Cmd, I> =\n | DriveRoundsExceededError<M, C>\n | DriveNoHandlerError<M, C>\n | CellErrors<C, I>;\n\n/** The drive was interrupted; nothing is left to run. */\nclass DriveInterrupted {}\n\ntype AnyCell = (cmd: unknown) => Effect.Effect<unknown, unknown>;\ntype AnyRunner = (sub: Sub) => Stream.Stream<unknown, unknown>;\ntype RunEffect = (\n effect: Effect.Effect<unknown, unknown>,\n options: { readonly signal: AbortSignal },\n) => Promise<Exit.Exit<unknown, unknown>>;\ntype ForkEffect = (\n effect: Effect.Effect<unknown, unknown>,\n) => Fiber.Fiber<unknown, unknown>;\n\n/**\n * Drive `machine` from `initial` through `msg` against the REAL Effect\n * `interpret` map — the one a host hands the Effect engine's `run` — and the\n * Subs the machine wants, feeding every Msg back until the machine goes quiet.\n * Yields the settled state together with the whole history, `{ state, trace }`.\n *\n * The rounds, the trace, `maxRounds` and `clock` are the Promise `drive`'s\n * (`@demlik/tea/testing/promise`). What is the Effect engine's:\n *\n * - a cell returns an Effect. A `Cmd.define`d Cmd's success mints `_ok` and\n * its declared failure `_err`; a hand-written Cmd's success is its\n * follow-up — a Msg, a list of them folded in order, or nothing (#324) —\n * and its failure fails the drive, typed. A defect dies. A cell takes no\n * `dispatch`, so a defined Cmd's handler cannot send its own `_ok` /\n * `_err`: the engine's rule holds by construction;\n * - the services the cells and runners read are this Effect's requirements,\n * so a test provides them the way a host does: `Effect.provide(layer)`;\n * - the Subs the machine wants run, through `opts.subscribe` or the engine's\n * built-in runner, started and stopped after every fold. Their Msgs are\n * folded once no Cmd's follow-up is pending. The drive settles once no Msg\n * is pending and no Sub is still running, so hand it a runner whose Stream\n * ends — a Sub that never ends keeps it waiting. A Stream that fails with\n * an error it did not map to a Msg dies, as it stops the engine's run.\n *\n * Interrupting the drive interrupts every cell and Sub still running.\n *\n * ```ts\n * const { state, trace } = await Effect.runPromise(\n * drive(machine, initial, { type: \"go\" }, {\n * fetch: (cmd) =>\n * Effect.gen(function* () {\n * const api = yield* Api;\n * return yield* api.get(cmd.id);\n * }),\n * }).pipe(Effect.provide(ApiTest)),\n * );\n * ```\n *\n * Fails with {@link DriveRoundsExceededError} when the machine is still\n * emitting work after `opts.maxRounds` rounds, {@link DriveNoHandlerError}\n * for a Cmd no cell answers, or a hand-written cell's own failure — each with\n * the partial trace, readable via `driveTraceOf`.\n */\nexport function drive<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n // The defaults read no services, so an omitted map adds none to `R`.\n I extends EffectInterpret<M, C> = EffectInterpret<M, C, never>,\n B extends EffectSubscribe<M, U> = EffectSubscribe<M, U, never>,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n initial: S,\n msg: M,\n interpret: I,\n ...[opts]: Record<never, never> extends EffectDriveOptions<U, Ctx, B>\n ? [opts?: EffectDriveOptions<U, Ctx, B>]\n : [opts: EffectDriveOptions<U, Ctx, B>]\n): Effect.Effect<\n DriveResult<S, M, C>,\n EffectDriveError<M, C, I>,\n InterpretServices<I> | SubscribeServices<B>\n> {\n const options = (opts ?? {}) as {\n ctx?: Ctx;\n maxRounds?: number;\n clock?: () => number;\n subscribe?: Readonly<Record<string, AnyRunner | undefined>>;\n };\n return Effect.gen(function* () {\n const services = yield* Effect.context<\n InterpretServices<I> | SubscribeServices<B>\n >();\n const runEffect = Effect.runPromiseExitWith(services) as RunEffect;\n const fork = Effect.runForkWith(services) as ForkEffect;\n\n return yield* Effect.callback<\n DriveResult<S, M, C>,\n EffectDriveError<M, C, I>\n >((resume, signal) => {\n const cells = interpret as unknown as Readonly<\n Record<string, AnyCell | undefined>\n >;\n const defined = new Set((machine.cmds ?? []).map((def) => def.cmdType));\n const cellFor = (type: string): DriveCell<M, C> | undefined => {\n const cell = cells[type];\n if (cell === undefined) return undefined;\n return async (cmd) => {\n const exit = await runEffect(cellResult(cell(cmd)), { signal });\n if (signal.aborted) throw new DriveInterrupted();\n return settleCellExit(exit, defined.has(type));\n };\n };\n\n driveLoop(\n machine,\n initial,\n msg,\n options.ctx as Ctx,\n options.maxRounds ?? DEFAULT_MAX_ROUNDS,\n {\n contract: driveContractOf(machine, options.clock ?? Date.now),\n cellFor,\n subs: effectSubs<S, M>(\n machine,\n options.subscribe ?? {},\n fork,\n signal,\n ),\n },\n ).then(\n (result) => resume(Effect.succeed(result)),\n (error: unknown) => resume(failureOf<M, C, I>(error)),\n );\n });\n });\n}\n\n/**\n * Sort what left the loop: its own failures and a hand-written cell's failure\n * are typed, everything else — a defect, a contract breach, a failed Sub — dies.\n */\nfunction failureOf<M, C extends Cmd, I>(\n error: unknown,\n): Effect.Effect<never, EffectDriveError<M, C, I>> {\n if (\n error instanceof DriveRoundsExceededError ||\n error instanceof DriveNoHandlerError\n ) {\n return Effect.fail(error as EffectDriveError<M, C, I>);\n }\n if (error instanceof CellFailure) {\n annotateTrace(error.failure, driveTraceOf(error) ?? []);\n return Effect.fail(error.failure as EffectDriveError<M, C, I>);\n }\n if (error instanceof DriveInterrupted) return Effect.interrupt;\n return Effect.die(error);\n}\n\n/**\n * One started Sub. It stays registered while the machine wants it, even after\n * its Stream ends, so a Sub that finished is not started again — the engine\n * keeps it the same way. `stopped` drops any Msg that arrives after a fold\n * turned it off; `ended` says its Stream no longer runs.\n */\ninterface StartedSub {\n stopped: boolean;\n ended: boolean;\n fiber?: Fiber.Fiber<unknown, unknown>;\n}\n\n/**\n * The Effect engine's Subs as the shared loop runs them: each wanted Sub's\n * Stream is drained on its own fiber into a buffer the loop reads between\n * rounds.\n */\nfunction effectSubs<S, M>(\n machine: { readonly subs?: ReadonlyArray<unknown> },\n subscribe: Readonly<Record<string, AnyRunner | undefined>>,\n fork: ForkEffect,\n signal: AbortSignal,\n): DriveSubs<S, M> {\n const builtins = builtinRunners as unknown as Readonly<\n Record<string, AnyRunner | undefined>\n >;\n const started = new Map<string, StartedSub>();\n let buffered: M[] = [];\n let failed: Cause.Cause<unknown> | undefined;\n let wake: (() => void) | undefined;\n const notify = (): void => {\n const woken = wake;\n wake = undefined;\n woken?.();\n };\n signal.addEventListener(\"abort\", notify, { once: true });\n\n const stop = (sub: StartedSub): Promise<void> => {\n sub.stopped = true;\n return sub.ended || sub.fiber === undefined\n ? Promise.resolve()\n : Effect.runPromise(Fiber.interrupt(sub.fiber));\n };\n const anyRunning = (): boolean =>\n [...started.values()].some((sub) => !sub.ended);\n\n return {\n reconcile(state) {\n const desired = desiredSubs(machine, state);\n for (const [id, sub] of started) {\n if (desired.has(id)) continue;\n started.delete(id);\n void stop(sub);\n }\n for (const [id, sub] of desired) {\n if (started.has(id)) continue;\n const make = subscribe[sub.type] ?? builtins[sub.type];\n if (make === undefined) {\n throw new Error(\n `@demlik/tea: no subscribe runner for Sub type \"${sub.type}\". ` +\n \"Pass one to drive in `subscribe`.\",\n );\n }\n const entry: StartedSub = { stopped: false, ended: false };\n started.set(id, entry);\n entry.fiber = fork(\n Stream.runForEach(make(sub), (next) =>\n Effect.sync(() => {\n if (entry.stopped) return;\n buffered.push(next as M);\n notify();\n }),\n ).pipe(\n Effect.onExit((exit) =>\n Effect.sync(() => {\n entry.ended = true;\n if (\n !entry.stopped &&\n Exit.isFailure(exit) &&\n !Cause.hasInterruptsOnly(exit.cause)\n ) {\n failed ??= exit.cause;\n }\n notify();\n }),\n ),\n ),\n );\n }\n },\n async next() {\n for (;;) {\n if (signal.aborted) throw new DriveInterrupted();\n if (buffered.length > 0) {\n const out = buffered;\n buffered = [];\n return out;\n }\n if (failed !== undefined) throw Cause.squash(failed);\n if (!anyRunning()) return undefined;\n await new Promise<void>((resolve) => {\n wake = resolve;\n });\n }\n },\n async close() {\n signal.removeEventListener(\"abort\", notify);\n const stopping = [...started.values()].map(stop);\n started.clear();\n await Promise.all(stopping);\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../../../src/testing/effect/answers.ts","../../../src/testing/effect/drive.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// ---------------------------------------------------------------------------\n// `arbSchemaGuidedSequence(machine, ctx, opts)`: Msg lists for Effect's\n// `Arbitrary.checkEffect`, whose answers come from each Cmd's own Effect schema\n// (#749). A Cmd made with `Cmd.define` from `@demlik/tea/effect` already holds\n// its `ok` schema and its `err` tags, so nobody writes its answers a second time.\n//\n// The lists are drawn as blind events and folded through the machine by the\n// same rule `arbGuidedSequence` uses (`src/pbt/arbitraries/guided-fold.ts`), so\n// every answer answers a Cmd that was sent and not yet answered. Only the\n// generator differs: Effect's `Arbitrary` here, fast-check there. Nothing here\n// imports fast-check, and nothing here runs a check: the caller runs\n// `Arbitrary.checkEffect`, folds with `foldEvents` and judges with a law.\n// The ruling: https://github.com/kamp-us/demlik/issues/749#issuecomment-6564369809\n// ---------------------------------------------------------------------------\n\nimport { Arbitrary, Data, Result, Schema } from \"effect\";\nimport {\n type Answerer,\n type BlindEvent,\n MAX_PICK,\n resolveGuided,\n} from \"../../pbt/arbitraries/guided-fold\";\nimport type { AnyCmdDef, Cmd, Machine, Sub } from \"../../pure/core\";\n\n/**\n * The machine has no `cmds` list, so there is no schema to draw an answer from.\n *\n * @experimental\n */\nexport class NoCmdsError extends Data.TaggedError(\"NoCmdsError\")<{\n readonly message: string;\n}> {}\n\n/**\n * A Cmd's `ok` schema is not an Effect schema, so no answer can be drawn from\n * it. A Cmd made with `Cmd.define` from `@demlik/tea` over zod, or any other\n * Standard Schema, lands here. `cmdType` names that Cmd.\n *\n * @experimental\n */\nexport class NotEffectSchemaError extends Data.TaggedError(\n \"NotEffectSchemaError\",\n)<{\n readonly cmdType: string;\n readonly message: string;\n}> {}\n\n/**\n * `machine.cmds` is empty and no `outside` Msg was given, so every list drawn\n * would be empty.\n *\n * @experimental\n */\nexport class NothingToDrawError extends Data.TaggedError(\"NothingToDrawError\")<{\n readonly message: string;\n}> {}\n\n/**\n * Why {@link arbSchemaGuidedSequence} drew no lists.\n *\n * @experimental\n */\nexport type SchemaAnswersError =\n | NoCmdsError\n | NotEffectSchemaError\n | NothingToDrawError;\n\n/**\n * What {@link arbSchemaGuidedSequence} takes beside the machine and its ctx.\n *\n * @experimental\n */\nexport interface SchemaGuidedOptions<S, M extends { type: string }> {\n /**\n * The Msgs nothing sends a Cmd for, by Msg type: a user's click, a timer. A\n * Msg type here and in no Cmd's answers is drawn only from here.\n */\n readonly outside?: {\n readonly [K in M[\"type\"]]?: Arbitrary.Arbitrary<Extract<M, { type: K }>>;\n };\n /**\n * Caps the events drawn. Default 60. `checkEffect` and `sampleEffect` cap a\n * list at their `size` too, which defaults to 10, so pass a `size` of at least\n * this to draw lists this long.\n */\n readonly maxLength?: number;\n /**\n * The saved state the fold starts from. Give `foldEvents` the same one, or it\n * reads the list from a different start. Default `null`.\n */\n readonly loaded?: S | null;\n}\n\n/** What one answer event drew for one Cmd: its `ok` value, or an `err` tag. */\ntype Outcome =\n | { readonly outcome: \"ok\"; readonly value: unknown }\n | { readonly outcome: \"err\"; readonly tag: string };\n\n/** The minting half of a `CmdDef`, which `AnyCmdDef` leaves out. */\ninterface Minting {\n readonly ok: (cmd: Cmd, value: unknown) => unknown;\n readonly err: (cmd: Cmd, error: { readonly _tag: string }) => unknown;\n}\n\nconst PREFIX = \"@demlik/tea/testing/effect: arbSchemaGuidedSequence\";\n\n/** An arbitrary of the integers from 0 to `max`, both included. */\nconst upTo = (max: number): Arbitrary.Arbitrary<number> =>\n Arbitrary.schema(\n Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: max })),\n );\n\n/** Draw one of `arms`, each as likely as the next. */\nconst oneOf = <A>(\n arms: readonly Arbitrary.Arbitrary<A>[],\n): Arbitrary.Arbitrary<A> =>\n arms.length === 1\n ? (arms[0] as Arbitrary.Arbitrary<A>)\n : Arbitrary.flatMap(\n upTo(arms.length - 1),\n (at) => arms[at] as Arbitrary.Arbitrary<A>,\n );\n\n/**\n * The outcomes one Cmd's answer is drawn from: its `ok` schema, and each of its\n * `errTags`. `null` when that `ok` schema is not an Effect schema.\n */\nfunction outcomesOf(def: AnyCmdDef): Arbitrary.Arbitrary<Outcome> | null {\n const ok = def.schema.ok;\n if (!Schema.isSchema(ok)) return null;\n const okArm = Schema.Struct({ outcome: Schema.Literal(\"ok\"), value: ok });\n const outcome =\n def.errTags.length === 0\n ? okArm\n : Schema.Union([\n okArm,\n Schema.Struct({\n outcome: Schema.Literal(\"err\"),\n tag: Schema.Literals(def.errTags),\n }),\n ]);\n return Arbitrary.schema(outcome) as Arbitrary.Arbitrary<Outcome>;\n}\n\n/**\n * Draw Msg lists for `Arbitrary.checkEffect` in which every answer answers a Cmd\n * that was sent and not yet answered, and comes from that Cmd's own schema: an\n * `ok` answer is drawn from its `schema.ok`, an `err` answer carries one of its\n * `errTags`. Edit a schema and what is drawn changes with it, with no change to\n * the test.\n *\n * Each list is drawn as blind events folded through the machine. An answer event\n * names one waiting Cmd and is minted with that Cmd's def, `def.ok(cmd, value)`\n * or `def.err(cmd, { _tag })`. With nothing waiting the answer is dropped, and an\n * `outside` Msg the event drew goes in its place, or nothing when there is no\n * `outside`. An event whose Msg the state has no cell for is dropped too, and a\n * refused answer leaves its Cmd waiting. A Cmd no def in `machine.cmds` builds is\n * never answered.\n *\n * It runs no check. Fold each list with `foldEvents` and judge it with a law,\n * `lawInvariant`, `lawTerminates` or `lawTrace`, inside `Arbitrary.checkEffect`.\n * A list it finds there shrinks by deleting events and shrinking them, and what\n * is left still folds by the same rule. With `outside` given and every Msg\n * accepted in every state, each event means one Msg, so a shrunk list is never\n * longer than the one first found. A dropped event breaks that: deleting an\n * answer before it can let it back in.\n *\n * It returns a failed `Result`, never an empty arbitrary, on a machine with no\n * `machine.cmds` ({@link NoCmdsError}), on a Cmd whose `ok` schema is not an\n * Effect schema ({@link NotEffectSchemaError}, naming it), and when there is\n * nothing at all to draw ({@link NothingToDrawError}).\n *\n * @example\n * ```ts\n * import type { CmdOf, Machine, Settled } from \"@demlik/tea\";\n * import { Cmd } from \"@demlik/tea/effect\";\n * import {\n * arbSchemaGuidedSequence,\n * foldEvents,\n * lawInvariant,\n * } from \"@demlik/tea/testing/effect\";\n * import { Arbitrary, Effect, Result, Schema } from \"effect\";\n *\n * const fetchCount = Cmd.define(\"fetch_count\", {\n * input: Schema.Struct({}),\n * ok: Schema.Struct({ n: Schema.Int }),\n * err: [\"offline\"],\n * });\n * type Msg = { type: \"start\" } | Settled<typeof fetchCount>;\n * declare const machine: Machine<\n * { readonly total: number },\n * Msg,\n * CmdOf<typeof fetchCount>,\n * never,\n * object\n * >;\n *\n * const lists = Result.getOrThrow(\n * arbSchemaGuidedSequence(machine, {}, {\n * outside: { start: Arbitrary.Constant({ type: \"start\" as const }) },\n * }),\n * );\n * const result = await Effect.runPromise(\n * Arbitrary.checkEffect(\n * lists,\n * (msgs) =>\n * lawInvariant(foldEvents(machine, {}, null, msgs), ({ next }) =>\n * Number.isFinite(next.total),\n * ) === null,\n * { runs: 500, size: 60 },\n * ),\n * );\n * ```\n *\n * @experimental\n */\nexport function arbSchemaGuidedSequence<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n ctx: NoInfer<Ctx>,\n opts?: SchemaGuidedOptions<NoInfer<S>, M>,\n): Result.Result<Arbitrary.Arbitrary<readonly M[]>, SchemaAnswersError> {\n const defs = machine.cmds;\n if (defs === undefined) {\n return Result.fail(\n new NoCmdsError({\n message: `${PREFIX}: the machine has no \\`cmds\\`, so no Cmd has a schema to draw its answer from. List its \\`Cmd.define\\`d Cmds in \\`defineMachine({ cmds })\\`.`,\n }),\n );\n }\n\n const answerers: Answerer<C, M>[] = [];\n const outcomes: Arbitrary.Arbitrary<Outcome>[] = [];\n for (const def of defs) {\n const drawn = outcomesOf(def);\n if (drawn === null) {\n return Result.fail(\n new NotEffectSchemaError({\n cmdType: def.cmdType,\n message: `${PREFIX}: the \"${def.cmdType}\" Cmd's ok schema is not an Effect schema, so no answer can be drawn from it. Define it with \\`Cmd.define\\` from \\`@demlik/tea/effect\\`.`,\n }),\n );\n }\n const mint = def as unknown as Minting;\n answerers.push({\n cmdType: def.cmdType,\n make: (cmd, value) => {\n const drew = value as Outcome;\n return (\n drew.outcome === \"ok\"\n ? mint.ok(cmd, drew.value)\n : mint.err(cmd, { _tag: drew.tag })\n ) as M;\n },\n });\n outcomes.push(drawn);\n }\n\n const outsides = Object.values(\n opts?.outside ?? {},\n ) as Arbitrary.Arbitrary<M>[];\n const outside = outsides.length === 0 ? null : oneOf(outsides);\n // An answer event also draws the outside Msg that stands in for it when nothing\n // is waiting. Then every event means one Msg, so a shrink, which only deletes\n // events and shrinks them, can never grow a list past the one first found.\n const answer =\n outcomes.length === 0\n ? null\n : Arbitrary.map(\n Arbitrary.all([\n upTo(MAX_PICK),\n Arbitrary.all(outcomes),\n outside ?? Arbitrary.Constant(undefined),\n ]),\n ([pick, values, otherwise]): BlindEvent<M> =>\n otherwise === undefined\n ? { kind: \"answer\", pick, values }\n : { kind: \"answer\", pick, values, otherwise },\n );\n\n // An answer is twice as likely as an outside Msg, as in `arbGuidedSequence`.\n // The answer arms come first, so a shrink turns an outside Msg into an answer,\n // which sends less, and never the other way.\n const arms: Arbitrary.Arbitrary<BlindEvent<M>>[] = [];\n if (answer !== null) arms.push(answer, answer);\n if (outside !== null) {\n arms.push(\n Arbitrary.map(\n outside,\n (msg): BlindEvent<M> => ({ kind: \"outside\", msg }),\n ),\n );\n }\n if (arms.length === 0) {\n return Result.fail(\n new NothingToDrawError({\n message: `${PREFIX}: \\`machine.cmds\\` is empty and no \\`outside\\` Msg was given, so every list would be empty.`,\n }),\n );\n }\n\n const loaded = opts?.loaded ?? null;\n return Result.succeed(\n Arbitrary.map(\n Arbitrary.array(oneOf(arms), { maxLength: opts?.maxLength ?? 60 }),\n (events) => resolveGuided(machine, ctx, loaded, answerers, events),\n ),\n );\n}\n","// ---------------------------------------------------------------------------\n// `drive(machine, initial, msg, interpret, opts)` for the Effect engine: the\n// Promise `drive`'s rounds, trace and bound, over the Effect engine's handlers\n// and Subs (#321).\n//\n// There is no second loop. Each Effect cell is adapted into the shared loop's\n// cell with the settle rule the engine itself uses (`settleCellExit` in\n// `src/effect/run.ts`), run with the services the caller provided, so a cell\n// settles here exactly as it does under `run`. The Subs the machine wants are\n// started and stopped after every fold, as the engine reconciles them, and\n// their Msgs are folded once the Cmds in flight have settled.\n// ---------------------------------------------------------------------------\n\nimport { Cause, Effect, Exit, Fiber, Stream } from \"effect\";\nimport { CellFailure } from \"../../effect/failures\";\nimport {\n builtinRunners,\n type CellErrors,\n cellResult,\n type EffectInterpret,\n type EffectSubscribe,\n type InterpretServices,\n type SubscribeOption,\n type SubscribeServices,\n settleCellExit,\n} from \"../../effect/run\";\nimport type { Cmd, Machine, Sub } from \"../../pure/core\";\nimport {\n annotateTrace,\n DEFAULT_MAX_ROUNDS,\n type DriveCell,\n DriveNoHandlerError,\n type DriveOptions,\n type DriveResult,\n DriveRoundsExceededError,\n type DriveSubs,\n desiredSubs,\n driveContractOf,\n driveLoop,\n driveTraceOf,\n} from \"../drive-loop\";\n\n/**\n * The Effect `drive`'s options: the Promise `drive`'s, plus `subscribe` — the\n * runners the engine's `run` takes, required exactly when `run` requires them.\n */\nexport type EffectDriveOptions<U extends Sub, Ctx, B> = DriveOptions<Ctx> &\n SubscribeOption<U, B>;\n\n/** The failures a drive ends with: the loop's own, and a hand-written cell's. */\nexport type EffectDriveError<M, C extends Cmd, I> =\n | DriveRoundsExceededError<M, C>\n | DriveNoHandlerError<M, C>\n | CellErrors<C, I>;\n\n/** The drive was interrupted; nothing is left to run. */\nclass DriveInterrupted {}\n\ntype AnyCell = (cmd: unknown) => Effect.Effect<unknown, unknown>;\ntype AnyRunner = (sub: Sub) => Stream.Stream<unknown, unknown>;\ntype RunEffect = (\n effect: Effect.Effect<unknown, unknown>,\n options: { readonly signal: AbortSignal },\n) => Promise<Exit.Exit<unknown, unknown>>;\ntype ForkEffect = (\n effect: Effect.Effect<unknown, unknown>,\n) => Fiber.Fiber<unknown, unknown>;\n\n/**\n * Drive `machine` from `initial` through `msg` against the REAL Effect\n * `interpret` map — the one a host hands the Effect engine's `run` — and the\n * Subs the machine wants, feeding every Msg back until the machine goes quiet.\n * Yields the settled state together with the whole history, `{ state, trace }`.\n *\n * The rounds, the trace, `maxRounds` and `clock` are the Promise `drive`'s\n * (`@demlik/tea/testing/promise`). What is the Effect engine's:\n *\n * - a cell returns an Effect. A `Cmd.define`d Cmd's success mints `_ok` and\n * its declared failure `_err`; a hand-written Cmd's success is its\n * follow-up — a Msg, a list of them folded in order, or nothing (#324) —\n * and its failure fails the drive, typed. A defect dies. A cell takes no\n * `dispatch`, so a defined Cmd's handler cannot send its own `_ok` /\n * `_err`: the engine's rule holds by construction;\n * - the services the cells and runners read are this Effect's requirements,\n * so a test provides them the way a host does: `Effect.provide(layer)`;\n * - the Subs the machine wants run, through `opts.subscribe` or the engine's\n * built-in runner, started and stopped after every fold. Their Msgs are\n * folded once no Cmd's follow-up is pending. The drive settles once no Msg\n * is pending and no Sub is still running, so hand it a runner whose Stream\n * ends — a Sub that never ends keeps it waiting. A Stream that fails with\n * an error it did not map to a Msg dies, as it stops the engine's run.\n *\n * Interrupting the drive interrupts every cell and Sub still running.\n *\n * ```ts\n * const { state, trace } = await Effect.runPromise(\n * drive(machine, initial, { type: \"go\" }, {\n * fetch: (cmd) =>\n * Effect.gen(function* () {\n * const api = yield* Api;\n * return yield* api.get(cmd.id);\n * }),\n * }).pipe(Effect.provide(ApiTest)),\n * );\n * ```\n *\n * Fails with {@link DriveRoundsExceededError} when the machine is still\n * emitting work after `opts.maxRounds` rounds, {@link DriveNoHandlerError}\n * for a Cmd no cell answers, or a hand-written cell's own failure — each with\n * the partial trace, readable via `driveTraceOf`.\n */\nexport function drive<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n // The defaults read no services, so an omitted map adds none to `R`.\n I extends EffectInterpret<M, C> = EffectInterpret<M, C, never>,\n B extends EffectSubscribe<M, U> = EffectSubscribe<M, U, never>,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n initial: S,\n msg: M,\n interpret: I,\n ...[opts]: Record<never, never> extends EffectDriveOptions<U, Ctx, B>\n ? [opts?: EffectDriveOptions<U, Ctx, B>]\n : [opts: EffectDriveOptions<U, Ctx, B>]\n): Effect.Effect<\n DriveResult<S, M, C>,\n EffectDriveError<M, C, I>,\n InterpretServices<I> | SubscribeServices<B>\n> {\n const options = (opts ?? {}) as {\n ctx?: Ctx;\n maxRounds?: number;\n clock?: () => number;\n subscribe?: Readonly<Record<string, AnyRunner | undefined>>;\n };\n return Effect.gen(function* () {\n const services = yield* Effect.context<\n InterpretServices<I> | SubscribeServices<B>\n >();\n const runEffect = Effect.runPromiseExitWith(services) as RunEffect;\n const fork = Effect.runForkWith(services) as ForkEffect;\n\n return yield* Effect.callback<\n DriveResult<S, M, C>,\n EffectDriveError<M, C, I>\n >((resume, signal) => {\n const cells = interpret as unknown as Readonly<\n Record<string, AnyCell | undefined>\n >;\n const defined = new Set((machine.cmds ?? []).map((def) => def.cmdType));\n const cellFor = (type: string): DriveCell<M, C> | undefined => {\n const cell = cells[type];\n if (cell === undefined) return undefined;\n return async (cmd) => {\n const exit = await runEffect(cellResult(cell(cmd)), { signal });\n if (signal.aborted) throw new DriveInterrupted();\n return settleCellExit(exit, defined.has(type));\n };\n };\n\n driveLoop(\n machine,\n initial,\n msg,\n options.ctx as Ctx,\n options.maxRounds ?? DEFAULT_MAX_ROUNDS,\n {\n contract: driveContractOf(machine, options.clock ?? Date.now),\n cellFor,\n subs: effectSubs<S, M>(\n machine,\n options.subscribe ?? {},\n fork,\n signal,\n ),\n },\n ).then(\n (result) => resume(Effect.succeed(result)),\n (error: unknown) => resume(failureOf<M, C, I>(error)),\n );\n });\n });\n}\n\n/**\n * Sort what left the loop: its own failures and a hand-written cell's failure\n * are typed, everything else — a defect, a contract breach, a failed Sub — dies.\n */\nfunction failureOf<M, C extends Cmd, I>(\n error: unknown,\n): Effect.Effect<never, EffectDriveError<M, C, I>> {\n if (\n error instanceof DriveRoundsExceededError ||\n error instanceof DriveNoHandlerError\n ) {\n return Effect.fail(error as EffectDriveError<M, C, I>);\n }\n if (error instanceof CellFailure) {\n annotateTrace(error.failure, driveTraceOf(error) ?? []);\n return Effect.fail(error.failure as EffectDriveError<M, C, I>);\n }\n if (error instanceof DriveInterrupted) return Effect.interrupt;\n return Effect.die(error);\n}\n\n/**\n * One started Sub. It stays registered while the machine wants it, even after\n * its Stream ends, so a Sub that finished is not started again — the engine\n * keeps it the same way. `stopped` drops any Msg that arrives after a fold\n * turned it off; `ended` says its Stream no longer runs.\n */\ninterface StartedSub {\n stopped: boolean;\n ended: boolean;\n fiber?: Fiber.Fiber<unknown, unknown>;\n}\n\n/**\n * The Effect engine's Subs as the shared loop runs them: each wanted Sub's\n * Stream is drained on its own fiber into a buffer the loop reads between\n * rounds.\n */\nfunction effectSubs<S, M>(\n machine: { readonly subs?: ReadonlyArray<unknown> },\n subscribe: Readonly<Record<string, AnyRunner | undefined>>,\n fork: ForkEffect,\n signal: AbortSignal,\n): DriveSubs<S, M> {\n const builtins = builtinRunners as unknown as Readonly<\n Record<string, AnyRunner | undefined>\n >;\n const started = new Map<string, StartedSub>();\n let buffered: M[] = [];\n let failed: Cause.Cause<unknown> | undefined;\n let wake: (() => void) | undefined;\n const notify = (): void => {\n const woken = wake;\n wake = undefined;\n woken?.();\n };\n signal.addEventListener(\"abort\", notify, { once: true });\n\n const stop = (sub: StartedSub): Promise<void> => {\n sub.stopped = true;\n return sub.ended || sub.fiber === undefined\n ? Promise.resolve()\n : Effect.runPromise(Fiber.interrupt(sub.fiber));\n };\n const anyRunning = (): boolean =>\n [...started.values()].some((sub) => !sub.ended);\n\n return {\n reconcile(state) {\n const desired = desiredSubs(machine, state);\n for (const [id, sub] of started) {\n if (desired.has(id)) continue;\n started.delete(id);\n void stop(sub);\n }\n for (const [id, sub] of desired) {\n if (started.has(id)) continue;\n const make = subscribe[sub.type] ?? builtins[sub.type];\n if (make === undefined) {\n throw new Error(\n `@demlik/tea: no subscribe runner for Sub type \"${sub.type}\". ` +\n \"Pass one to drive in `subscribe`.\",\n );\n }\n const entry: StartedSub = { stopped: false, ended: false };\n started.set(id, entry);\n entry.fiber = fork(\n Stream.runForEach(make(sub), (next) =>\n Effect.sync(() => {\n if (entry.stopped) return;\n buffered.push(next as M);\n notify();\n }),\n ).pipe(\n Effect.onExit((exit) =>\n Effect.sync(() => {\n entry.ended = true;\n if (\n !entry.stopped &&\n Exit.isFailure(exit) &&\n !Cause.hasInterruptsOnly(exit.cause)\n ) {\n failed ??= exit.cause;\n }\n notify();\n }),\n ),\n ),\n );\n }\n },\n async next() {\n for (;;) {\n if (signal.aborted) throw new DriveInterrupted();\n if (buffered.length > 0) {\n const out = buffered;\n buffered = [];\n return out;\n }\n if (failed !== undefined) throw Cause.squash(failed);\n if (!anyRunning()) return undefined;\n await new Promise<void>((resolve) => {\n wake = resolve;\n });\n }\n },\n async close() {\n signal.removeEventListener(\"abort\", notify);\n const stopping = [...started.values()].map(stop);\n started.clear();\n await Promise.all(stopping);\n },\n };\n}\n"]}
@@ -1,5 +1,5 @@
1
- import { C as Cmd, c as Sub, M as Machine, B as BuiltinSub } from '../core-BKJW1Kmq.js';
2
- import { R as Runtime } from '../runtime-types-BPKp2bGo.js';
1
+ import { C as Cmd, c as Sub, M as Machine, B as BuiltinSub } from '../core-DNDAEhO2.js';
2
+ import { R as Runtime } from '../runtime-types-CP-ykwgf.js';
3
3
  import '@standard-schema/spec';
4
4
 
5
5
  /**
@@ -75,10 +75,8 @@ declare function expectActiveSubs<S, M extends {
75
75
  * `[next, cmds]` can swap `reduce(state, msg)` for `step(machine, state, msg,
76
76
  * ctx)` without changing the assertion code.
77
77
  *
78
- * Internally a `replay` call with `loaded: prevState` and a single-element
79
- * `msgs` array. The cmds returned are exactly the cmds emitted by THIS msg
80
- * (init emits no cmds when `loaded` is provided, so the slice is the entire
81
- * array).
78
+ * Calls the update cell directly, without init or subscription derivation.
79
+ * The returned Cmds belong only to this message.
82
80
  *
83
81
  * Use when consumers want to walk a machine one msg at a time and assert per
84
82
  * step. For one-shot "final state after N msgs" assertions, prefer
@@ -86,7 +84,7 @@ declare function expectActiveSubs<S, M extends {
86
84
  */
87
85
  declare function step<S, M extends {
88
86
  type: string;
89
- }, C extends Cmd, U extends Sub, Ctx>(machine: Machine<S, M, C, U, Ctx>, loaded: NoInfer<S>, msg: NoInfer<M>, ctx: NoInfer<Ctx>): readonly [S, readonly C[]];
87
+ }, C extends Cmd, U extends Sub, Ctx>(machine: Machine<S, M, C, U, Ctx>, loaded: NoInfer<S>, msg: NoInfer<M>, _ctx: NoInfer<Ctx>): readonly [S, readonly C[]];
90
88
 
91
89
  /**
92
90
  * Replay options shape used by every bound method. `ctx` is captured by
@@ -148,7 +146,7 @@ interface BoundMachine<S, M extends {
148
146
  }
149
147
  /**
150
148
  * Bind a machine + ctx into a small bag of 2-arg helpers. Each method
151
- * delegates to `replay(machine, { ...opts, ctx })` and pipes the result
149
+ * delegates to `step` or `replay(machine, { ...opts, ctx })` and pipes the result
152
150
  * through a single `expect(...)` matcher (or returns the raw tuple, for
153
151
  * `step` and `replay`).
154
152
  *
@@ -1,28 +1,8 @@
1
- export { bindMachine } from '../chunk-7Z2IDZ7R.js';
2
- import '../chunk-VHKDUBXC.js';
3
- import { replay } from '../chunk-GAVLND5C.js';
1
+ export { bindMachine, expectActiveSubs, expectCmdEmitted, expectCmdSequence, expectFinalState, step } from '../chunk-RMBZ7YH7.js';
2
+ import '../chunk-BVFS3ICC.js';
3
+ import { replay } from '../chunk-A3QPMX5M.js';
4
4
  import { expect, vi } from 'vitest';
5
5
 
6
- function expectFinalState(machine, opts, expected) {
7
- const { state } = replay(machine, opts);
8
- expect(state).toEqual(expected);
9
- }
10
- function expectCmdEmitted(machine, opts, cmd) {
11
- const { cmds } = replay(machine, opts);
12
- expect(cmds).toContainEqual(cmd);
13
- }
14
- function expectCmdSequence(machine, opts, expected) {
15
- const { cmds } = replay(machine, opts);
16
- expect(cmds).toEqual(expected);
17
- }
18
- function expectActiveSubs(machine, opts, expected) {
19
- const { subs } = replay(machine, opts);
20
- expect(subs).toEqual(expected);
21
- }
22
- function step(machine, loaded, msg, ctx) {
23
- const { state, cmds } = replay(machine, { msgs: [msg], ctx, loaded });
24
- return [state, cmds];
25
- }
26
6
  var AMBIENT_A = { now: 1e6, rng: 0.123 };
27
7
  var AMBIENT_B = { now: 9876543, rng: 0.987 };
28
8
  function expectReplayDeterministic(machine, opts) {
@@ -122,6 +102,6 @@ function stateFactory(defaults) {
122
102
  return api;
123
103
  }
124
104
 
125
- export { expectActiveSubs, expectCmdEmitted, expectCmdSequence, expectFinalState, expectReplayDeterministic, noopRuntime, stateFactory, step };
105
+ export { expectReplayDeterministic, noopRuntime, stateFactory };
126
106
  //# sourceMappingURL=index.js.map
127
107
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/testing/assertions.ts","../../src/testing/expect-replay-deterministic.ts","../../src/testing/noop-runtime.ts","../../src/testing/state-factory.ts"],"names":["expect"],"mappings":"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","file":"index.js","sourcesContent":["// ---------------------------------------------------------------------------\n// Test-side assertions over `replay`. Each helper composes the replay call\n// with a single `expect(...)` so consumers stop hand-rolling the 5-line\n// \"destructure { state, cmds } → expect.toBe / toEqual / toContainEqual\"\n// boilerplate at every test site.\n//\n// Each helper is a thin wrapper that:\n// 1. Calls `replay(machine, opts)` once.\n// 2. Pipes the result into a single vitest `expect(...)` matcher.\n//\n// All helpers return `void` — assertions throw on failure (vitest contract).\n// No mutation of the machine or opts. Pure test ergonomics.\n//\n// Type-inference note: each helper anchors its generic parameters to the\n// `machine` argument. The `expected` (and `opts.msgs`) arguments are wrapped\n// in `NoInfer<...>` so they do NOT participate in inference — otherwise a\n// `[]`-shaped `expected` would force `C = never` and reject a machine whose\n// real Cmd union is non-empty. Anchoring inference to the machine is the\n// same discipline `replay` uses (it has nothing but the machine and opts).\n//\n// Strengthens invariant 9 (the testing surface is named, small, and forks\n// nothing of the substrate's purity guarantees).\n// ---------------------------------------------------------------------------\n\nimport { expect } from \"vitest\";\nimport { type Cmd, type Machine, replay, type Sub } from \"../index\";\n\n// `NoInfer<T>` is the built-in TypeScript 5.4+ intrinsic that blocks\n// inference from a generic parameter at a position. We use it on `expected`\n// and `opts` so inference flows purely FROM the machine — `[]`-shaped\n// `expected` no longer drags `C` down to `never` and rejects the machine.\n\n/**\n * Shared options shape for every test assertion below. Mirrors the\n * second argument of `replay(machine, opts)` — `msgs` is the sequence to\n * fold over (may be empty for init-only assertions), `ctx` is the\n * substrate-required ctx value, `loaded` is the optional pre-`init`\n * snapshot (omit / pass `null` for fresh boot).\n */\nexport interface ReplayOpts<S, M, Ctx> {\n readonly msgs: readonly M[];\n readonly ctx: Ctx;\n readonly loaded?: S | null;\n}\n\n/**\n * Assert the final state after replaying `opts.msgs` equals `expected`.\n * Replaces the boilerplate:\n *\n * const { state } = replay(machine, opts);\n * expect(state).toEqual(expected);\n *\n * Uses `toEqual` (deep structural). For type-narrowing assertions that\n * branch on `state.type` afterwards, fall back to plain `replay` —\n * `expectFinalState` is the \"exact match\" tool.\n */\nexport function expectFinalState<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: NoInfer<ReplayOpts<S, M, Ctx>>,\n expected: NoInfer<S>,\n): void {\n const { state } = replay(machine, opts);\n expect(state).toEqual(expected);\n}\n\n/**\n * Assert that `cmd` appears at least once in the cmds array produced by\n * replaying `opts.msgs`. Uses `toContainEqual` (deep structural). Order\n * and surrounding cmds are not asserted — use `expectCmdSequence` for\n * exact ordered match.\n *\n * Replaces:\n *\n * const { cmds } = replay(machine, opts);\n * expect(cmds).toContainEqual(cmd);\n */\nexport function expectCmdEmitted<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: NoInfer<ReplayOpts<S, M, Ctx>>,\n cmd: NoInfer<C>,\n): void {\n const { cmds } = replay(machine, opts);\n expect(cmds).toContainEqual(cmd);\n}\n\n/**\n * Assert the exact ordered sequence of cmds emitted by replaying\n * `opts.msgs`. Uses `toEqual` against the full cmds array — extra cmds\n * fail the assertion, missing cmds fail the assertion, wrong order\n * fails the assertion.\n *\n * Replaces:\n *\n * const { cmds } = replay(machine, opts);\n * expect(cmds).toEqual(expected);\n */\nexport function expectCmdSequence<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: NoInfer<ReplayOpts<S, M, Ctx>>,\n expected: NoInfer<readonly C[]>,\n): void {\n const { cmds } = replay(machine, opts);\n expect(cmds).toEqual(expected);\n}\n\n/**\n * Assert the exact set of subs desired at the final state after\n * replaying `opts.msgs`. Uses `toEqual` against the full subs array.\n *\n * Replaces:\n *\n * const { subs } = replay(machine, opts);\n * expect(subs).toEqual(expected);\n */\nexport function expectActiveSubs<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: NoInfer<ReplayOpts<S, M, Ctx>>,\n expected: NoInfer<readonly U[]>,\n): void {\n const { subs } = replay(machine, opts);\n expect(subs).toEqual(expected);\n}\n\n/**\n * Single-msg step helper — feeds `loaded → msg → next state + cmds emitted by\n * that msg`. Returns a readonly tuple in the shape consumers used before the\n * substrate (`[state, cmds]`), so existing tests that destructure with\n * `[next, cmds]` can swap `reduce(state, msg)` for `step(machine, state, msg,\n * ctx)` without changing the assertion code.\n *\n * Internally a `replay` call with `loaded: prevState` and a single-element\n * `msgs` array. The cmds returned are exactly the cmds emitted by THIS msg\n * (init emits no cmds when `loaded` is provided, so the slice is the entire\n * array).\n *\n * Use when consumers want to walk a machine one msg at a time and assert per\n * step. For one-shot \"final state after N msgs\" assertions, prefer\n * `expectFinalState`.\n */\nexport function step<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n loaded: NoInfer<S>,\n msg: NoInfer<M>,\n ctx: NoInfer<Ctx>,\n): readonly [S, readonly C[]] {\n const { state, cmds } = replay(machine, { msgs: [msg], ctx, loaded });\n return [state, cmds] as const;\n}\n","import { expect, vi } from \"vitest\";\nimport { type Cmd, type Machine, replay, type Sub } from \"../index\";\nimport type { ReplayOpts } from \"./assertions\";\n\ninterface Ambient {\n readonly now: number;\n readonly rng: number;\n}\n\nconst AMBIENT_A: Ambient = { now: 1_000_000, rng: 0.123 };\nconst AMBIENT_B: Ambient = { now: 9_876_543, rng: 0.987 };\n\n/**\n * Assert that replaying `opts.msgs` is a pure function of the Msg log: the\n * final state and every emitted Cmd come out the same under two different\n * global wall-clocks and RNG seeds.\n *\n * `init` and `update` never receive a clock, so a cell that reads\n * `Date.now()` or `Math.random()` reaches for the global one, and a replay of\n * the same log on another day or another host lands somewhere else. Two\n * back-to-back replays under one clock would hide that; this helper stubs\n * two distinct ambients and compares. Time belongs in the Msg (an `at`\n * field), where it is identical across replays.\n *\n * Subs are not compared: they are derived from the final state, so a state\n * that matches yields the same Subs.\n */\nexport function expectReplayDeterministic<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: NoInfer<ReplayOpts<S, M, Ctx>>,\n): void {\n const a = underAmbient(AMBIENT_A, () => replay(machine, opts));\n const b = underAmbient(AMBIENT_B, () => replay(machine, opts));\n expect(\n b.state,\n \"final state changed under a different clock/RNG: init or update reads \" +\n \"Date.now()/Math.random(); time must enter as Msg data\",\n ).toEqual(a.state);\n expect(\n b.cmds,\n \"emitted Cmds changed under a different clock/RNG: a Cmd payload reads \" +\n \"Date.now()/Math.random(); time must enter as Msg data\",\n ).toEqual(a.cmds);\n}\n\n/** Run `fn` with the global `Date.now` and `Math.random` pinned, then restore them. */\nfunction underAmbient<T>(ambient: Ambient, fn: () => T): T {\n const nowSpy = vi.spyOn(Date, \"now\").mockReturnValue(ambient.now);\n const rngSpy = vi.spyOn(Math, \"random\").mockReturnValue(ambient.rng);\n try {\n return fn();\n } finally {\n nowSpy.mockRestore();\n rngSpy.mockRestore();\n }\n}\n","// ---------------------------------------------------------------------------\n// noopRuntime — a Runtime<S, M>-shaped value whose every method is inert.\n//\n// The motivating callsite: a machine's `ctx` requires a sibling Runtime<S,\n// M> reference (e.g. the queue runtime in extension/lib/queue-machine\n// reaches into an `auditRuntime: Runtime<...>` slot on its ctx so its\n// interpret handlers can dispatch into the audit machine). When the test\n// replays such a machine, `replay` never actually invokes `interpret`, so\n// the runtime reference is never dereferenced — but the type system\n// still requires a value of `Runtime<S, M>` shape at the ctx slot. Tests\n// today work around this with a hand-rolled 12-line NOOP_AUDIT_RUNTIME\n// const cast through `as unknown as Runtime<...>`.\n//\n// `noopRuntime<S, M>()` is that hand-roll, named once. It returns a real\n// Runtime<S, M> value whose every method is a structurally-correct no-op:\n//\n// - `dispatch`: resolves immediately, never advances state.\n// - `getState`: returns the optional `initialState` argument (or a\n// deliberate `undefined` cast as S — the function lies about its\n// return type when no initial state is provided; this is the trade-off\n// we accept for an ergonomic test substrate).\n// - `subscribe` / `observe` / `subscribePort`: register the listener,\n// return a no-op unsubscribe. The listener is never fired because the\n// state never changes.\n// - `emitPort`: no-op.\n// - `ready`: a Promise that resolves to the no-op runtime itself (the #45\n// `ready: Promise<Runtime<S, M>>` shape — already booted, nothing to wait\n// for).\n// - `stop`: resolved Promise<void>.\n//\n// Strengthens invariant 9 (the testing surface is named and small).\n// ---------------------------------------------------------------------------\n\nimport type { Port, Runtime } from \"../index\";\n\n/**\n * Construct an inert `Runtime<S, M>` value. Use at ctx sites that require\n * a Runtime reference but where the test never invokes `interpret` (so\n * the runtime is never dereferenced in practice).\n *\n * @param opts.initialState Optional value returned by `getState()`. Omit\n * when the test never calls `getState()` on the no-op runtime — most\n * tests only need the type-level slot filled.\n */\nexport function noopRuntime<S, M extends { type: string }>(opts?: {\n initialState?: S;\n}): Runtime<S, M> {\n const initialState = opts?.initialState;\n // Single source of truth for the \"do-nothing\" unsubscribe so multiple\n // subscribe call sites share the same function instance — purely cosmetic\n // (the function would be a no-op either way).\n const noopUnsubscribe = (): void => {};\n const runtime: Runtime<S, M> = {\n async dispatch(_msg: M): Promise<void> {\n // Resolves immediately. State never advances; listeners never fire.\n // Quiescent by default (#50), but the no-op has no follow-up chain to\n // drain, so single-step and run-to-quiescence collapse to the same no-op.\n },\n async dispatchOnce(_msg: M): Promise<void> {\n // Single-step sibling of `dispatch` (#50). Same no-op for the no-op\n // runtime — there is no tail to leave in flight.\n },\n async idle(): Promise<void> {\n // No tail to drain — the no-op runtime never enqueues a follow-up.\n },\n result(): S | undefined {\n // The no-op runtime never advances, so it is never terminal (#46).\n return undefined;\n },\n done(): Promise<S> {\n // Never terminal → the awaitable never settles, matching `run()` with no\n // `terminal` predicate. Returned as a never-resolving promise.\n return new Promise<S>(() => {});\n },\n getState(): S {\n // Lie if no initial state was provided. The caller's contract is\n // \"you accept the lie because you know replay never invokes\n // interpret and therefore never reads this.\" When the caller DOES\n // pass an initialState the lie collapses to truth.\n return initialState as S;\n },\n subscribe(_listener: () => void): () => void {\n return noopUnsubscribe;\n },\n observe(_observer: (msg: M, state: S) => void): () => void {\n return noopUnsubscribe;\n },\n onBoot(_handler: (state: S) => void): () => void {\n // Boot never fires on the inert runtime — it is \"already booted\" with no\n // initial fanout, so the handler is registered and never called (#47).\n return noopUnsubscribe;\n },\n on<K extends never>(\n _type: K,\n _handler: (event: never) => void,\n ): () => void {\n // The no-op runtime's event surface is `never` (no `events` projector),\n // so `on` is structurally present but uncallable — there is no `K` to\n // pass. The body is unreachable; it returns the shared no-op unsubscribe.\n return noopUnsubscribe;\n },\n subscribePort<T>(\n _port: Port<T>,\n _listener: (value: T) => void,\n ): () => void {\n return noopUnsubscribe;\n },\n emitPort<T>(_port: Port<T>, _value: T): void {\n // No-op. No subscribers to fan out to; nothing to do.\n },\n // Assigned just below — `ready` must resolve to `runtime` itself, which\n // can't be referenced inside its own object literal.\n ready: undefined as unknown as Promise<Runtime<S, M>>,\n async stop(): Promise<void> {\n // Resolves immediately. No in-flight tail to drain, no subs to clean.\n },\n };\n // Already \"booted\" — `ready` hands back the same runtime synchronously.\n runtime.ready = Promise.resolve(runtime);\n return runtime;\n}\n","// ---------------------------------------------------------------------------\n// stateFactory — typed phase-constructor API for a discriminated-union State.\n//\n// The motivating callsite: every reducer test today opens with ~60–80 lines\n// of hand-rolled factory functions, one per phase, each one's defaults\n// duplicated across files:\n//\n// function idleState() { return { type: \"idle\" }; }\n// function creatingWindowState(overrides?: Partial<...>) {\n// return { type: \"creating_window\", auditId: ..., wsUrl: ..., ...overrides };\n// }\n// ...\n//\n// `stateFactory<BackgroundState>({...})` collapses the whole block to one\n// declaration: pass each phase's REQUIRED defaults once (the type system\n// enforces every phase has an entry), and the helper builds an API of\n// `factory.idle()`, `factory.creating_window({...})`, etc.\n//\n// Two type-level guarantees:\n//\n// 1. Every phase has a defaults entry — `StateFactoryDefaults<S>` is a\n// mapped type keyed by every `S[\"type\"]`. Missing an entry is a\n// compile error (the @ts-expect-error in testing.spec.ts pins this).\n//\n// 2. Each phase's defaults match that phase's required shape minus the\n// `type` discriminator. Passing wrong fields fails to compile.\n//\n// The runtime is pure data:\n//\n// api[k] = (overrides) => ({\n// type: k,\n// ...defaults[k],\n// ...stripUndefined(overrides ?? {}),\n// })\n//\n// The `stripUndefined` pass is what preserves `exactOptionalPropertyTypes`\n// assignability — without it, calling `factory.creating_window({\n// queueItemId: undefined })` would land an explicit `queueItemId: undefined`\n// on the object, which is NOT assignable to `queueItemId?: string` under\n// `exactOptionalPropertyTypes`. Stripping undefineds before spread keeps\n// the boundary clean.\n//\n// `stripUndefined` is inlined (not imported from a shared stdlib) so this\n// package can stay dep-free of stdlib.\n// ---------------------------------------------------------------------------\n\n/** A factory-constructed phase: `S` narrowed to the arm whose discriminator equals `K`. */\ntype StateOf<S, K> = Extract<S, { type: K }>;\n\n/** A factory-constructed phase with the `type` discriminator removed — the \"fields the caller cares about\" half. */\ntype RequiredFields<T> = Omit<T, \"type\">;\n\n/**\n * Defaults shape passed to `stateFactory`. Mapped type over the discriminant\n * set: every phase MUST have an entry, and that entry MUST match the phase's\n * required fields minus the `type` discriminator.\n */\nexport type StateFactoryDefaults<S extends { type: string }> = {\n readonly [K in S[\"type\"]]: RequiredFields<StateOf<S, K>>;\n};\n\n/**\n * The API returned by `stateFactory`. One method per phase; calling it with\n * no args returns the default snapshot, calling it with a partial overrides\n * object merges those overrides in (with undefineds stripped — see\n * stripUndefined below).\n */\nexport type StateFactoryAPI<S extends { type: string }> = {\n readonly [K in S[\"type\"]]: (\n overrides?: Partial<RequiredFields<StateOf<S, K>>>,\n ) => StateOf<S, K>;\n};\n\n/**\n * Strip own-enumerable properties whose value is `undefined`. Used inside\n * the factory's spread chain so callers can pass `{ queueItemId: undefined }`\n * without the explicit-undefined landing on the result object (which would\n * fail to assign to a `queueItemId?: string` field under\n * `exactOptionalPropertyTypes`).\n *\n * Inlined (not imported from `a shared stdlib`) so `@demlik/tea/testing` can stay\n * dependency-free of stdlib. The 8-line cost of duplication is cheaper than\n * a package-graph edge.\n */\nfunction stripUndefined<T extends object>(obj: T): Partial<T> {\n const out: Partial<T> = {};\n for (const key of Object.keys(obj) as (keyof T)[]) {\n const value = obj[key];\n if (value !== undefined) {\n out[key] = value;\n }\n }\n return out;\n}\n\n/**\n * Build a typed phase-constructor API from per-phase defaults. The defaults\n * object MUST have one entry per `S[\"type\"]` discriminant; the returned API\n * has one method per phase. Each method merges the defaults with optional\n * overrides (undefineds stripped), then stamps the `type` discriminator.\n *\n * @example\n * ```ts\n * import { stateFactory } from \"@demlik/tea/testing\";\n *\n * type S = { type: \"a\"; n: number } | { type: \"b\"; s: string };\n * const f = stateFactory<S>({ a: { n: 0 }, b: { s: \"\" } });\n * f.a(); // { type: \"a\", n: 0 }\n * f.a({ n: 42 }); // { type: \"a\", n: 42 }\n * f.b({ s: \"hi\" }); // { type: \"b\", s: \"hi\" }\n * ```\n */\nexport function stateFactory<S extends { type: string }>(\n defaults: StateFactoryDefaults<S>,\n): StateFactoryAPI<S> {\n // Build the API by walking the defaults' own keys. Each key corresponds\n // to one phase discriminator; the constructor closes over both the key\n // (for the `type` stamp) and that phase's defaults snapshot.\n const api = {} as { [k: string]: (overrides?: object) => unknown };\n for (const key of Object.keys(\n defaults,\n ) as (keyof StateFactoryDefaults<S>)[]) {\n const phaseDefaults = defaults[key];\n api[key as string] = (overrides?: object) => {\n // The strip-undefined pass lives here, not at the caller. See the\n // module header for the `exactOptionalPropertyTypes` rationale.\n const cleanedOverrides = overrides ? stripUndefined(overrides) : {};\n return { type: key, ...phaseDefaults, ...cleanedOverrides };\n };\n }\n return api as StateFactoryAPI<S>;\n}\n"]}
1
+ {"version":3,"sources":["../../src/testing/expect-replay-deterministic.ts","../../src/testing/noop-runtime.ts","../../src/testing/state-factory.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { expect, vi } from \"vitest\";\nimport { type Cmd, type Machine, replay, type Sub } from \"../index\";\nimport type { ReplayOpts } from \"./assertions\";\n\ninterface Ambient {\n readonly now: number;\n readonly rng: number;\n}\n\nconst AMBIENT_A: Ambient = { now: 1_000_000, rng: 0.123 };\nconst AMBIENT_B: Ambient = { now: 9_876_543, rng: 0.987 };\n\n/**\n * Assert that replaying `opts.msgs` is a pure function of the Msg log: the\n * final state and every emitted Cmd come out the same under two different\n * global wall-clocks and RNG seeds.\n *\n * `init` and `update` never receive a clock, so a cell that reads\n * `Date.now()` or `Math.random()` reaches for the global one, and a replay of\n * the same log on another day or another host lands somewhere else. Two\n * back-to-back replays under one clock would hide that; this helper stubs\n * two distinct ambients and compares. Time belongs in the Msg (an `at`\n * field), where it is identical across replays.\n *\n * Subs are not compared: they are derived from the final state, so a state\n * that matches yields the same Subs.\n */\nexport function expectReplayDeterministic<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: NoInfer<ReplayOpts<S, M, Ctx>>,\n): void {\n const a = underAmbient(AMBIENT_A, () => replay(machine, opts));\n const b = underAmbient(AMBIENT_B, () => replay(machine, opts));\n expect(\n b.state,\n \"final state changed under a different clock/RNG: init or update reads \" +\n \"Date.now()/Math.random(); time must enter as Msg data\",\n ).toEqual(a.state);\n expect(\n b.cmds,\n \"emitted Cmds changed under a different clock/RNG: a Cmd payload reads \" +\n \"Date.now()/Math.random(); time must enter as Msg data\",\n ).toEqual(a.cmds);\n}\n\n/** Run `fn` with the global `Date.now` and `Math.random` pinned, then restore them. */\nfunction underAmbient<T>(ambient: Ambient, fn: () => T): T {\n const nowSpy = vi.spyOn(Date, \"now\").mockReturnValue(ambient.now);\n const rngSpy = vi.spyOn(Math, \"random\").mockReturnValue(ambient.rng);\n try {\n return fn();\n } finally {\n nowSpy.mockRestore();\n rngSpy.mockRestore();\n }\n}\n","// ---------------------------------------------------------------------------\n// noopRuntime — a Runtime<S, M>-shaped value whose every method is inert.\n//\n// The motivating callsite: a machine's `ctx` requires a sibling Runtime<S,\n// M> reference (e.g. the queue runtime in extension/lib/queue-machine\n// reaches into an `auditRuntime: Runtime<...>` slot on its ctx so its\n// interpret handlers can dispatch into the audit machine). When the test\n// replays such a machine, `replay` never actually invokes `interpret`, so\n// the runtime reference is never dereferenced — but the type system\n// still requires a value of `Runtime<S, M>` shape at the ctx slot. Tests\n// today work around this with a hand-rolled 12-line NOOP_AUDIT_RUNTIME\n// const cast through `as unknown as Runtime<...>`.\n//\n// `noopRuntime<S, M>()` is that hand-roll, named once. It returns a real\n// Runtime<S, M> value whose every method is a structurally-correct no-op:\n//\n// - `dispatch`: resolves immediately, never advances state.\n// - `getState`: returns the optional `initialState` argument (or a\n// deliberate `undefined` cast as S — the function lies about its\n// return type when no initial state is provided; this is the trade-off\n// we accept for an ergonomic test substrate).\n// - `subscribe` / `observe` / `subscribePort`: register the listener,\n// return a no-op unsubscribe. The listener is never fired because the\n// state never changes.\n// - `emitPort`: no-op.\n// - `ready`: a Promise that resolves to the no-op runtime itself (the #45\n// `ready: Promise<Runtime<S, M>>` shape — already booted, nothing to wait\n// for).\n// - `stop`: resolved Promise<void>.\n//\n// Strengthens invariant 9 (the testing surface is named and small).\n// ---------------------------------------------------------------------------\n\nimport type { Port, Runtime } from \"../index\";\n\n/**\n * Construct an inert `Runtime<S, M>` value. Use at ctx sites that require\n * a Runtime reference but where the test never invokes `interpret` (so\n * the runtime is never dereferenced in practice).\n *\n * @param opts.initialState Optional value returned by `getState()`. Omit\n * when the test never calls `getState()` on the no-op runtime — most\n * tests only need the type-level slot filled.\n */\nexport function noopRuntime<S, M extends { type: string }>(opts?: {\n initialState?: S;\n}): Runtime<S, M> {\n const initialState = opts?.initialState;\n // Single source of truth for the \"do-nothing\" unsubscribe so multiple\n // subscribe call sites share the same function instance — purely cosmetic\n // (the function would be a no-op either way).\n const noopUnsubscribe = (): void => {};\n const runtime: Runtime<S, M> = {\n async dispatch(_msg: M): Promise<void> {\n // Resolves immediately. State never advances; listeners never fire.\n // Quiescent by default (#50), but the no-op has no follow-up chain to\n // drain, so single-step and run-to-quiescence collapse to the same no-op.\n },\n async dispatchOnce(_msg: M): Promise<void> {\n // Single-step sibling of `dispatch` (#50). Same no-op for the no-op\n // runtime — there is no tail to leave in flight.\n },\n async idle(): Promise<void> {\n // No tail to drain — the no-op runtime never enqueues a follow-up.\n },\n result(): S | undefined {\n // The no-op runtime never advances, so it is never terminal (#46).\n return undefined;\n },\n done(): Promise<S> {\n // Never terminal → the awaitable never settles, matching `run()` with no\n // `terminal` predicate. Returned as a never-resolving promise.\n return new Promise<S>(() => {});\n },\n getState(): S {\n // Lie if no initial state was provided. The caller's contract is\n // \"you accept the lie because you know replay never invokes\n // interpret and therefore never reads this.\" When the caller DOES\n // pass an initialState the lie collapses to truth.\n return initialState as S;\n },\n subscribe(_listener: () => void): () => void {\n return noopUnsubscribe;\n },\n observe(_observer: (msg: M, state: S) => void): () => void {\n return noopUnsubscribe;\n },\n onBoot(_handler: (state: S) => void): () => void {\n // Boot never fires on the inert runtime — it is \"already booted\" with no\n // initial fanout, so the handler is registered and never called (#47).\n return noopUnsubscribe;\n },\n on<K extends never>(\n _type: K,\n _handler: (event: never) => void,\n ): () => void {\n // The no-op runtime's event surface is `never` (no `events` projector),\n // so `on` is structurally present but uncallable — there is no `K` to\n // pass. The body is unreachable; it returns the shared no-op unsubscribe.\n return noopUnsubscribe;\n },\n subscribePort<T>(\n _port: Port<T>,\n _listener: (value: T) => void,\n ): () => void {\n return noopUnsubscribe;\n },\n emitPort<T>(_port: Port<T>, _value: T): void {\n // No-op. No subscribers to fan out to; nothing to do.\n },\n // Assigned just below — `ready` must resolve to `runtime` itself, which\n // can't be referenced inside its own object literal.\n ready: undefined as unknown as Promise<Runtime<S, M>>,\n async stop(): Promise<void> {\n // Resolves immediately. No in-flight tail to drain, no subs to clean.\n },\n };\n // Already \"booted\" — `ready` hands back the same runtime synchronously.\n runtime.ready = Promise.resolve(runtime);\n return runtime;\n}\n","// ---------------------------------------------------------------------------\n// stateFactory — typed phase-constructor API for a discriminated-union State.\n//\n// The motivating callsite: every reducer test today opens with ~60–80 lines\n// of hand-rolled factory functions, one per phase, each one's defaults\n// duplicated across files:\n//\n// function idleState() { return { type: \"idle\" }; }\n// function creatingWindowState(overrides?: Partial<...>) {\n// return { type: \"creating_window\", auditId: ..., wsUrl: ..., ...overrides };\n// }\n// ...\n//\n// `stateFactory<BackgroundState>({...})` collapses the whole block to one\n// declaration: pass each phase's REQUIRED defaults once (the type system\n// enforces every phase has an entry), and the helper builds an API of\n// `factory.idle()`, `factory.creating_window({...})`, etc.\n//\n// Two type-level guarantees:\n//\n// 1. Every phase has a defaults entry — `StateFactoryDefaults<S>` is a\n// mapped type keyed by every `S[\"type\"]`. Missing an entry is a\n// compile error (the @ts-expect-error in testing.spec.ts pins this).\n//\n// 2. Each phase's defaults match that phase's required shape minus the\n// `type` discriminator. Passing wrong fields fails to compile.\n//\n// The runtime is pure data:\n//\n// api[k] = (overrides) => ({\n// type: k,\n// ...defaults[k],\n// ...stripUndefined(overrides ?? {}),\n// })\n//\n// The `stripUndefined` pass is what preserves `exactOptionalPropertyTypes`\n// assignability — without it, calling `factory.creating_window({\n// queueItemId: undefined })` would land an explicit `queueItemId: undefined`\n// on the object, which is NOT assignable to `queueItemId?: string` under\n// `exactOptionalPropertyTypes`. Stripping undefineds before spread keeps\n// the boundary clean.\n//\n// `stripUndefined` is inlined (not imported from a shared stdlib) so this\n// package can stay dep-free of stdlib.\n// ---------------------------------------------------------------------------\n\n/** A factory-constructed phase: `S` narrowed to the arm whose discriminator equals `K`. */\ntype StateOf<S, K> = Extract<S, { type: K }>;\n\n/** A factory-constructed phase with the `type` discriminator removed — the \"fields the caller cares about\" half. */\ntype RequiredFields<T> = Omit<T, \"type\">;\n\n/**\n * Defaults shape passed to `stateFactory`. Mapped type over the discriminant\n * set: every phase MUST have an entry, and that entry MUST match the phase's\n * required fields minus the `type` discriminator.\n */\nexport type StateFactoryDefaults<S extends { type: string }> = {\n readonly [K in S[\"type\"]]: RequiredFields<StateOf<S, K>>;\n};\n\n/**\n * The API returned by `stateFactory`. One method per phase; calling it with\n * no args returns the default snapshot, calling it with a partial overrides\n * object merges those overrides in (with undefineds stripped — see\n * stripUndefined below).\n */\nexport type StateFactoryAPI<S extends { type: string }> = {\n readonly [K in S[\"type\"]]: (\n overrides?: Partial<RequiredFields<StateOf<S, K>>>,\n ) => StateOf<S, K>;\n};\n\n/**\n * Strip own-enumerable properties whose value is `undefined`. Used inside\n * the factory's spread chain so callers can pass `{ queueItemId: undefined }`\n * without the explicit-undefined landing on the result object (which would\n * fail to assign to a `queueItemId?: string` field under\n * `exactOptionalPropertyTypes`).\n *\n * Inlined (not imported from `a shared stdlib`) so `@demlik/tea/testing` can stay\n * dependency-free of stdlib. The 8-line cost of duplication is cheaper than\n * a package-graph edge.\n */\nfunction stripUndefined<T extends object>(obj: T): Partial<T> {\n const out: Partial<T> = {};\n for (const key of Object.keys(obj) as (keyof T)[]) {\n const value = obj[key];\n if (value !== undefined) {\n out[key] = value;\n }\n }\n return out;\n}\n\n/**\n * Build a typed phase-constructor API from per-phase defaults. The defaults\n * object MUST have one entry per `S[\"type\"]` discriminant; the returned API\n * has one method per phase. Each method merges the defaults with optional\n * overrides (undefineds stripped), then stamps the `type` discriminator.\n *\n * @example\n * ```ts\n * import { stateFactory } from \"@demlik/tea/testing\";\n *\n * type S = { type: \"a\"; n: number } | { type: \"b\"; s: string };\n * const f = stateFactory<S>({ a: { n: 0 }, b: { s: \"\" } });\n * f.a(); // { type: \"a\", n: 0 }\n * f.a({ n: 42 }); // { type: \"a\", n: 42 }\n * f.b({ s: \"hi\" }); // { type: \"b\", s: \"hi\" }\n * ```\n */\nexport function stateFactory<S extends { type: string }>(\n defaults: StateFactoryDefaults<S>,\n): StateFactoryAPI<S> {\n // Build the API by walking the defaults' own keys. Each key corresponds\n // to one phase discriminator; the constructor closes over both the key\n // (for the `type` stamp) and that phase's defaults snapshot.\n const api = {} as { [k: string]: (overrides?: object) => unknown };\n for (const key of Object.keys(\n defaults,\n ) as (keyof StateFactoryDefaults<S>)[]) {\n const phaseDefaults = defaults[key];\n api[key as string] = (overrides?: object) => {\n // The strip-undefined pass lives here, not at the caller. See the\n // module header for the `exactOptionalPropertyTypes` rationale.\n const cleanedOverrides = overrides ? stripUndefined(overrides) : {};\n return { type: key, ...phaseDefaults, ...cleanedOverrides };\n };\n }\n return api as StateFactoryAPI<S>;\n}\n"]}
@@ -1,7 +1,7 @@
1
- import { b as DriveOptions, c as DriveResult } from '../../drive-loop-Dc6Z8Vbs.js';
2
- export { d as DEFAULT_MAX_ROUNDS, e as DriveCtxArg, a as DriveNoHandlerError, D as DriveRoundsExceededError, f as DriveTraceEntry, g as driveTraceOf } from '../../drive-loop-Dc6Z8Vbs.js';
3
- import { C as Cmd, c as Sub, M as Machine, I as Interpret } from '../../core-BKJW1Kmq.js';
4
- import '../../runtime-types-BPKp2bGo.js';
1
+ import { D as DriveOptions, a as DriveResult } from '../../drive-loop-DlOEv49c.js';
2
+ export { b as DEFAULT_MAX_ROUNDS, c as DriveCtxArg, d as DriveNoHandlerError, e as DriveRoundsExceededError, f as DriveTraceEntry, g as driveTraceOf } from '../../drive-loop-DlOEv49c.js';
3
+ import { C as Cmd, c as Sub, M as Machine, I as Interpret } from '../../core-DNDAEhO2.js';
4
+ import '../../runtime-types-CP-ykwgf.js';
5
5
  import '@standard-schema/spec';
6
6
 
7
7
  /**
@@ -1,8 +1,8 @@
1
- import { driveContractOf, driveLoop, DEFAULT_MAX_ROUNDS } from '../../chunk-BFSZ4L6Y.js';
2
- export { DEFAULT_MAX_ROUNDS, DriveNoHandlerError, DriveRoundsExceededError, driveTraceOf } from '../../chunk-BFSZ4L6Y.js';
3
- import '../../chunk-7Z2IDZ7R.js';
4
- import '../../chunk-VHKDUBXC.js';
5
- import { cmdEdge, Outcome } from '../../chunk-GAVLND5C.js';
1
+ import { driveContractOf, driveLoop, DEFAULT_MAX_ROUNDS } from '../../chunk-ETRNSJCR.js';
2
+ export { DEFAULT_MAX_ROUNDS, DriveNoHandlerError, DriveRoundsExceededError, driveTraceOf } from '../../chunk-ETRNSJCR.js';
3
+ import '../../chunk-RMBZ7YH7.js';
4
+ import '../../chunk-BVFS3ICC.js';
5
+ import { cmdEdge, Outcome } from '../../chunk-A3QPMX5M.js';
6
6
 
7
7
  // src/testing/promise/drive.ts
8
8
  async function drive(machine, initial, msg, handlers, ...[opts]) {
@@ -1,9 +1,9 @@
1
1
  export { D as Debounced, d as debounce } from '../index-DHSUmQTR.js';
2
- import { C as Cmd } from '../core-BKJW1Kmq.js';
3
- import { T as TtlCache, a as DeadlineSub } from '../index-Diyo8WBO.js';
4
- export { D as ThrottledInputSettled, s as subscribeThrottledInput, d as throttledInputSettled } from '../index-Diyo8WBO.js';
2
+ import { C as Cmd } from '../core-DNDAEhO2.js';
3
+ import { T as TtlCache, a as DeadlineSub } from '../index-Cg0QXnzf.js';
4
+ export { D as ThrottledInputSettled, s as subscribeThrottledInput, d as throttledInputSettled } from '../index-Cg0QXnzf.js';
5
5
  import '@standard-schema/spec';
6
- import '../types-BiXj7k_k.js';
6
+ import '../types-S53WDy_h.js';
7
7
 
8
8
  /**
9
9
  * internal/timing/throttle — a timer-based call transformer that caps invocation
@@ -1,8 +1,8 @@
1
1
  export { debounce } from '../chunk-LFZUK6J2.js';
2
- import { deadlineSub, initCache, get, set } from '../chunk-QIE4Y6BK.js';
3
- export { subscribeDeadline as subscribeThrottledInput, deadlineExceeded as throttledInputSettled } from '../chunk-QIE4Y6BK.js';
4
- import '../chunk-VHKDUBXC.js';
5
- import '../chunk-GAVLND5C.js';
2
+ import { deadlineSub, initCache, get, set } from '../chunk-UGTC33VH.js';
3
+ export { subscribeDeadline as subscribeThrottledInput, deadlineExceeded as throttledInputSettled } from '../chunk-UGTC33VH.js';
4
+ import '../chunk-BVFS3ICC.js';
5
+ import '../chunk-A3QPMX5M.js';
6
6
 
7
7
  // src/internal/timing/throttle/index.ts
8
8
  function throttle(fn, ms, opts) {
@@ -1,4 +1,4 @@
1
- import { c as Sub, D as Dispose } from './core-BKJW1Kmq.js';
1
+ import { c as Sub, D as Dispose } from './core-DNDAEhO2.js';
2
2
 
3
3
  /**
4
4
  * A Sub runner: given the Sub, the `ctx` and a `dispatch`, it opens the
@@ -1,7 +1,7 @@
1
- import { Q as QueueItem } from '../index-Cuy1_xTh.js';
2
- export { E as EnqueueInput, a as QueueItemStatus, c as createQueue } from '../index-Cuy1_xTh.js';
3
- import '../runtime-types-BPKp2bGo.js';
4
- import '../core-BKJW1Kmq.js';
1
+ import { Q as QueueItem } from '../index-BBLI2WWN.js';
2
+ export { E as EnqueueInput, a as QueueItemStatus, c as createQueue } from '../index-BBLI2WWN.js';
3
+ import '../runtime-types-CP-ykwgf.js';
4
+ import '../core-DNDAEhO2.js';
5
5
  import '@standard-schema/spec';
6
6
 
7
7
  /**
@@ -1,7 +1,7 @@
1
1
  import { enqueueOp, claimNextOp, markDoneOp, patchItemOp, resetRunningOp, removeOp } from '../chunk-TXN2KFHF.js';
2
2
  export { claimNextOp, enqueueOp, markDoneOp, patchItemOp, queueAdapter, removeOp, resetRunningOp } from '../chunk-TXN2KFHF.js';
3
- import { restore } from '../chunk-Z7IL735U.js';
4
- import '../chunk-GAVLND5C.js';
3
+ import { restore } from '../chunk-R2YNDRGL.js';
4
+ import '../chunk-A3QPMX5M.js';
5
5
 
6
6
  // src/internal/work-queue/index.ts
7
7
  function genId() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@demlik/tea",
3
- "version": "0.21.0",
3
+ "version": "0.22.0",
4
4
  "description": "TEA-faithful state machine substrate — one package, every host adapter. Core + subs + testing, plus React, Durable Object, Chrome extension, Node, in-memory, devtools, and property-testing modules as subpaths.",
5
5
  "keywords": [
6
6
  "tea",
@@ -136,11 +136,11 @@
136
136
  "./package.json": "./package.json"
137
137
  },
138
138
  "dependencies": {
139
- "@standard-schema/spec": "^1.1.0"
139
+ "@standard-schema/spec": "1.1.0"
140
140
  },
141
141
  "peerDependencies": {
142
142
  "@opentelemetry/api": "^1.9.0",
143
- "effect": "^4.0.0-rc.0",
143
+ "effect": "~4.1.1",
144
144
  "fast-check": "^3 || ^4",
145
145
  "react": "^18 || ^19",
146
146
  "react-dom": "^18 || ^19",
@@ -176,25 +176,25 @@
176
176
  "@langfuse/otel": "5.11.1",
177
177
  "@opentelemetry/api": "1.9.1",
178
178
  "@opentelemetry/sdk-trace-base": "2.11.0",
179
- "@types/chrome": "^0.1.42",
180
- "@types/node": "^22",
181
- "@types/react": "19.0.8",
182
- "@types/react-dom": "19.0.3",
179
+ "@types/chrome": "0.1.43",
180
+ "@types/node": "22.20.1",
181
+ "@types/react": "19.3.0",
182
+ "@types/react-dom": "19.3.0",
183
183
  "@types/ws": "8.5.14",
184
- "effect": "4.0.0-rc.117",
185
- "fast-check": "^3.23.0",
186
- "happy-dom": "^20.9.0",
184
+ "effect": "4.1.1",
185
+ "fast-check": "3.23.2",
186
+ "happy-dom": "20.11.2",
187
187
  "openai": "7.23.0",
188
- "react": "19.0.0",
189
- "react-dom": "19.0.0",
190
- "tsup": "^8.3.5",
188
+ "react": "19.3.1",
189
+ "react-dom": "19.3.1",
190
+ "tsup": "8.5.1",
191
191
  "typedoc": "0.28.20",
192
192
  "typescript": "5.7.3",
193
- "vite": "^8.2.1",
194
- "vitest": "~5.0.1",
195
- "ws": "^8.21.0",
196
- "zod": "^4.5.4",
197
- "@demlik/code-graph": "0.8.0"
193
+ "vite": "8.2.2",
194
+ "vitest": "5.0.1",
195
+ "ws": "8.21.3",
196
+ "zod": "4.5.4",
197
+ "@demlik/code-graph": "0.13.0"
198
198
  },
199
199
  "publishConfig": {
200
200
  "access": "public"
@@ -1,34 +0,0 @@
1
- import { replay } from './chunk-GAVLND5C.js';
2
- import { expect } from 'vitest';
3
-
4
- function bindMachine(machine, ctx) {
5
- return {
6
- step(loaded, msg) {
7
- const { state, cmds } = replay(machine, { msgs: [msg], ctx, loaded });
8
- return [state, cmds];
9
- },
10
- expectFinalState(opts, expected) {
11
- const { state } = replay(machine, { ...opts, ctx });
12
- expect(state).toEqual(expected);
13
- },
14
- expectCmdEmitted(opts, cmd) {
15
- const { cmds } = replay(machine, { ...opts, ctx });
16
- expect(cmds).toContainEqual(cmd);
17
- },
18
- expectCmdSequence(opts, expected) {
19
- const { cmds } = replay(machine, { ...opts, ctx });
20
- expect(cmds).toEqual(expected);
21
- },
22
- expectActiveSubs(opts, expected) {
23
- const { subs } = replay(machine, { ...opts, ctx });
24
- expect(subs).toEqual(expected);
25
- },
26
- replay(opts) {
27
- return replay(machine, { ...opts, ctx });
28
- }
29
- };
30
- }
31
-
32
- export { bindMachine };
33
- //# sourceMappingURL=chunk-7Z2IDZ7R.js.map
34
- //# sourceMappingURL=chunk-7Z2IDZ7R.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/testing/bind-machine.ts"],"names":[],"mappings":";;;AAmHO,SAAS,WAAA,CAMd,SAAmC,GAAA,EAAyC;AAC5E,EAAA,OAAO;AAAA,IACL,IAAA,CAAK,QAAQ,GAAA,EAAK;AAChB,MAAA,MAAM,EAAE,KAAA,EAAO,IAAA,EAAK,GAAI,MAAA,CAAO,OAAA,EAAS,EAAE,IAAA,EAAM,CAAC,GAAG,CAAA,EAAG,GAAA,EAAK,QAAQ,CAAA;AACpE,MAAA,OAAO,CAAC,OAAO,IAAI,CAAA;AAAA,IACrB,CAAA;AAAA,IACA,gBAAA,CAAiB,MAAM,QAAA,EAAU;AAC/B,MAAA,MAAM,EAAE,OAAM,GAAI,MAAA,CAAO,SAAS,EAAE,GAAG,IAAA,EAAM,GAAA,EAAK,CAAA;AAClD,MAAA,MAAA,CAAO,KAAK,CAAA,CAAE,OAAA,CAAQ,QAAQ,CAAA;AAAA,IAChC,CAAA;AAAA,IACA,gBAAA,CAAiB,MAAM,GAAA,EAAK;AAC1B,MAAA,MAAM,EAAE,MAAK,GAAI,MAAA,CAAO,SAAS,EAAE,GAAG,IAAA,EAAM,GAAA,EAAK,CAAA;AACjD,MAAA,MAAA,CAAO,IAAI,CAAA,CAAE,cAAA,CAAe,GAAG,CAAA;AAAA,IACjC,CAAA;AAAA,IACA,iBAAA,CAAkB,MAAM,QAAA,EAAU;AAChC,MAAA,MAAM,EAAE,MAAK,GAAI,MAAA,CAAO,SAAS,EAAE,GAAG,IAAA,EAAM,GAAA,EAAK,CAAA;AACjD,MAAA,MAAA,CAAO,IAAI,CAAA,CAAE,OAAA,CAAQ,QAAQ,CAAA;AAAA,IAC/B,CAAA;AAAA,IACA,gBAAA,CAAiB,MAAM,QAAA,EAAU;AAC/B,MAAA,MAAM,EAAE,MAAK,GAAI,MAAA,CAAO,SAAS,EAAE,GAAG,IAAA,EAAM,GAAA,EAAK,CAAA;AACjD,MAAA,MAAA,CAAO,IAAI,CAAA,CAAE,OAAA,CAAQ,QAAQ,CAAA;AAAA,IAC/B,CAAA;AAAA,IACA,OAAO,IAAA,EAAM;AACX,MAAA,OAAO,OAAO,OAAA,EAAS,EAAE,GAAG,IAAA,EAAM,KAAK,CAAA;AAAA,IACzC;AAAA,GACF;AACF","file":"chunk-7Z2IDZ7R.js","sourcesContent":["// ---------------------------------------------------------------------------\n// `bindMachine(machine, ctx)` — bound test harness over the free-function\n// assertions in `./assertions.ts`. Closes over the `machine` and `ctx` once\n// so per-test callsites stop repeating both at every invocation.\n//\n// Why this exists: every consumer test file ends up hand-rolling a local\n// two-arg `step(state, msg)` wrapper around `step(machine, state, msg, ctx)`,\n// and the four `expect*` assertions repeat `machine` + `ctx` at every\n// callsite. The bound form pulls both into one place and gives every\n// per-test call its tightest possible shape (2-arg max).\n//\n// `bindMachine` is built ON TOP of the free helpers — it does not replace\n// them. Both forms coexist; pick the one that fits the call site:\n//\n// - Free form → one-off call inside a helper that isn't bound to one\n// machine (e.g. cross-machine integration test).\n// - Bound form → a `describe` block runs many assertions against one\n// machine + ctx. The bound form is the common case.\n//\n// Strengthens invariant 9 (the testing surface is named, small, and forks\n// nothing of the substrate's purity guarantees).\n// ---------------------------------------------------------------------------\n\nimport { expect } from \"vitest\";\nimport {\n type BuiltinSub,\n type Cmd,\n type Machine,\n replay,\n type Sub,\n} from \"../index\";\n\n/**\n * Replay options shape used by every bound method. `ctx` is captured by\n * `bindMachine`; the per-call opts only need `msgs` and an optional\n * `loaded` snapshot. (Compare `ReplayOpts` in `./assertions.ts`, which\n * still takes `ctx` because the free helpers cannot capture it.)\n */\ninterface BoundOpts<S, M> {\n readonly msgs: readonly M[];\n readonly loaded?: S | null;\n}\n\n/**\n * The bound testing surface. Every method is 2-arg max — `ctx` and\n * `machine` are captured by `bindMachine`.\n *\n * `NoInfer<...>` on `expected` / `cmd` arguments mirrors the discipline\n * the free helpers use (see `./assertions.ts`): inference flows purely\n * FROM the machine, so a `[]`-shaped expected does not drag `C` down to\n * `never` and reject the machine.\n */\nexport interface BoundMachine<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n // `Ctx` is a phantom-style parameter — present for consumers that want to\n // spell `BoundMachine<S, M, C, U, Ctx>` explicitly, mirroring `Machine`.\n // biome-ignore lint/correctness/noUnusedVariables: phantom type param (see above)\n Ctx,\n> {\n /**\n * Single-msg step — feeds `loaded → msg → [next state, cmds emitted by\n * that msg]`. Mirrors the free `step(machine, loaded, msg, ctx)`.\n */\n step(loaded: S, msg: M): readonly [S, readonly C[]];\n /**\n * Assert the final state after replaying `opts.msgs` deep-equals\n * `expected`. Mirrors the free `expectFinalState(machine, opts, expected)`.\n */\n expectFinalState(opts: BoundOpts<S, M>, expected: NoInfer<S>): void;\n /**\n * Assert `cmd` appears at least once in the emitted cmd list. Order is\n * not asserted. Mirrors the free `expectCmdEmitted(machine, opts, cmd)`.\n */\n expectCmdEmitted(opts: BoundOpts<S, M>, cmd: NoInfer<C>): void;\n /**\n * Assert the exact ordered sequence of cmds emitted by replaying\n * `opts.msgs`. Mirrors the free `expectCmdSequence(machine, opts, expected)`.\n */\n expectCmdSequence(\n opts: BoundOpts<S, M>,\n expected: readonly NoInfer<C>[],\n ): void;\n /**\n * Assert the exact set of subs desired at the final state. Mirrors the\n * free `expectActiveSubs(machine, opts, expected)`.\n */\n expectActiveSubs(\n opts: BoundOpts<S, M>,\n expected: readonly NoInfer<U | BuiltinSub<M>>[],\n ): void;\n /**\n * Bound `replay` — returns `{ state, cmds, subs }` for the given opts.\n * Use for the \"narrow-then-assert\" pattern where the test inspects a\n * specific field after `state.type === \"...\"` discrimination.\n */\n replay(opts: BoundOpts<S, M>): {\n state: S;\n cmds: readonly C[];\n subs: readonly (U | BuiltinSub<M>)[];\n };\n}\n\n/**\n * Bind a machine + ctx into a small bag of 2-arg helpers. Each method\n * delegates to `replay(machine, { ...opts, ctx })` and pipes the result\n * through a single `expect(...)` matcher (or returns the raw tuple, for\n * `step` and `replay`).\n *\n * Use one bind per `describe` block (or per file) when many assertions\n * share the same machine + ctx. For one-off cross-machine calls, stick\n * with the free helpers from `./assertions.ts`.\n */\nexport function bindMachine<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(machine: Machine<S, M, C, U, Ctx>, ctx: Ctx): BoundMachine<S, M, C, U, Ctx> {\n return {\n step(loaded, msg) {\n const { state, cmds } = replay(machine, { msgs: [msg], ctx, loaded });\n return [state, cmds] as const;\n },\n expectFinalState(opts, expected) {\n const { state } = replay(machine, { ...opts, ctx });\n expect(state).toEqual(expected);\n },\n expectCmdEmitted(opts, cmd) {\n const { cmds } = replay(machine, { ...opts, ctx });\n expect(cmds).toContainEqual(cmd);\n },\n expectCmdSequence(opts, expected) {\n const { cmds } = replay(machine, { ...opts, ctx });\n expect(cmds).toEqual(expected);\n },\n expectActiveSubs(opts, expected) {\n const { subs } = replay(machine, { ...opts, ctx });\n expect(subs).toEqual(expected);\n },\n replay(opts) {\n return replay(machine, { ...opts, ctx });\n },\n };\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/promise/builtin-runners.ts","../src/promise/run.ts"],"names":[],"mappings":"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","file":"chunk-EB5OMOXX.js","sourcesContent":["/**\n * The Promise engine's built-in sub runners (#270 R1.1). A machine declares a\n * built-in Sub by type alone — `{ type: \"timer\", deps: (s) => ({ ms, msg }) }`\n * — and `run` starts it with the runner here unless `subscribe` names one of\n * the same type, which replaces it (#270 R2.1).\n */\n\nimport type { BuiltinSubType, Dispose, TimerSub } from \"../pure/core\";\n\n/**\n * `timer`: dispatch `deps.msg` once, `deps.ms` after the Sub starts. The engine\n * restarts it when `ms` or `msg` changes and clears it when the Sub stops.\n */\nfunction timer<M>(\n sub: TimerSub<M>,\n _ctx: unknown,\n dispatch: (msg: M) => void,\n): Dispose {\n const handle = setTimeout(() => dispatch(sub.deps.msg), sub.deps.ms);\n return () => clearTimeout(handle);\n}\n\n/** Every built-in runner, keyed by the Sub type it runs. */\nexport const builtinRunners = { timer } as const satisfies Record<\n BuiltinSubType,\n unknown\n>;\n","/**\n * @demlik/tea runtime — `run`: boot a machine and drive its serial dispatch loop\n * on Promises; `driveToDone`: run one to its terminal State.\n *\n * `run` is assembly. The loop itself is `src/internal/engine/loop.ts` — a small\n * core with no special case for any built-in, shared with the Effect engine —\n * and every built-in is an extension of it (`src/internal/engine/builtins.ts`),\n * built from `run`'s options in the order both engines share.\n */\n\nimport { builtinExtensions } from \"../internal/engine/builtins\";\nimport {\n type LiveWorkProbe,\n type LoopHandler,\n type LoopRunner,\n liveWork,\n startLoop,\n} from \"../internal/engine/loop\";\nimport type {\n Dispose,\n Interpret,\n Machine,\n RunHandlers,\n Sub,\n} from \"../pure/core\";\nimport { applyCell, type Cmd, subEntriesOf } from \"../pure/core\";\nimport type {\n BootingRuntime,\n CtxArg,\n OnError,\n Store,\n Supervision,\n TelemetrySink,\n} from \"../runtime-types\";\nimport { DriveFailedError, DriveStalledError } from \"../runtime-types\";\nimport { builtinRunners } from \"./builtin-runners\";\n\n// === run ===\n//\n// Boot runs as the FIRST entry on the serial dispatch tail, which is how every\n// public method of the returned `BootingRuntime<S, M>` waits for it.\n//\n// Save-then-effects ordering is structural: every transition mutates state,\n// awaits `store.save(newState)`, then reconciles subscriptions, then runs\n// `interpret` for emitted cmds, then fires external listeners. The listeners\n// fire even when a Cmd handler throws: the dispatch rejects with the handler's\n// error, and every listener, observer, `on` handler and `done()` waiter still\n// hears the State that was installed and saved (#311). A throw in any effect\n// phase leaves the persisted state ahead of the host's belief about what\n// executed — the Railway discipline (`tryInterpret` in handlers) makes that\n// safe in practice.\n/**\n * Start a machine on the Promise engine and return its runtime. Pass the Cmd\n * handlers, the Sub runners, `ctx` and an optional `Store`; the runtime is\n * returned at once and every method waits for boot.\n */\nexport function run<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n E extends { type: string } = never,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n // `ctx` is a plain object (ADR 0020): tea does no dependency injection, so\n // a handler's services are whatever the host put on it. The Cmd handlers\n // arrive here, beside the machine and never on it (#251 R1.1). `NoInfer`\n // keeps them a check against the machine's `M` / `C` rather than a second\n // inference site that could widen either.\n opts: CtxArg<Ctx> &\n NoInfer<RunHandlers<M, C, U, Ctx>> & {\n store?: Store<S>;\n onError?: OnError;\n /**\n * The clock that stamps `at` on a `Cmd.define`d effect's settled Msg at\n * the interpret edge, and on each `telemetry` event. Defaults to\n * `Date.now`. Inject a fixed one for a deterministic run; a `replay` log\n * carries its own `at`s and never reads this.\n */\n clock?: () => number;\n /**\n * The SEMANTIC event projector. Maps one APPLIED transition `(msg, state)`\n * to zero-or-more public events of `E`; `[]` skips the transition. Maps the\n * machine's PRIVATE Msg vocabulary to NAMED events — the private names never\n * reach `on`'s `E` surface. Omit → `E = never` and `on` is uncallable. PURE.\n */\n events?: (msg: M, state: S) => readonly E[];\n /**\n * Declared policy for a reducer (`update`) throw. Always surfaced via\n * `onError` (`phase: \"reduce\"`); the strategy decides what the runtime does\n * next. Defaults to `\"stop\"`. See `Supervision`.\n */\n supervision?: Supervision<S, M>;\n /**\n * The run-terminality predicate — makes the run's outcome first-class. Fed to\n * `Runtime.result()` and `Runtime.done()`. PURE. Omit → never terminal.\n */\n terminal?: (state: S) => boolean;\n /**\n * A sink handed `{ seq, msgType, at }` for every applied transition —\n * the observe-only telemetry `withTelemetry` used to add by wrapping the\n * machine (#268). Fire-and-forget; see {@link TelemetrySink}.\n */\n telemetry?: TelemetrySink;\n /**\n * How long `stop()` waits for work it cannot interrupt before giving up\n * on it. Defaults to 5_000ms. It bounds two waits, one after the other:\n * the queue, where a Cmd handler may still be running, and then the\n * teardown work that returned a Promise (an async `release` in\n * `defineManagedResource`, an async Sub cleanup).\n *\n * `stop()` awaits those disposals so a host doing\n * `await runtime.stop(); env.evict()` cannot drop the isolate mid-release —\n * the leak the managed-resource battery exists to prevent, relocated to\n * shutdown. The bound is what keeps a handler that never returns, or a\n * release that never settles, from hanging the host: on expiry `stop()`\n * reports a `DrainTimeoutNotice` or a `DisposeTimeoutNotice` (warn-only,\n * like every `RuntimeDiscardNotice`) and resolves anyway, because `stop()`\n * resolving is a contract (ADR 0025). The Subs are still stopped and the\n * final save still runs. A handler `stop()` gave up on keeps running;\n * what it returns later is dropped.\n *\n * tea starts no store call after `stop()` has returned. A store call\n * already under way when `stop()` gave up is not cancelled and can\n * finish after `stop()` has returned. On a plain store that late save\n * can overwrite what a newer run saved; a fenced store refuses it with\n * `StoreConflictError`. So a host that starts a new run on the same\n * store after a `stop()` that gave up needs a fenced store. A run\n * stopped while its `store.load()` was pending saves nothing when the\n * load returns: `ready` resolves with the loaded State, unsaved.\n */\n disposeTimeoutMs?: number;\n /**\n * Iteration cap for `idle()`'s quiescence wait. Defaults to 100_000. Test\n * seam only. Production code must not set it.\n *\n * @internal test-only\n */\n __idleCap?: number;\n },\n): BootingRuntime<S, M, E> {\n // `interpret` is optional when `C extends Cmd<never>`; default a missing map\n // to `{}` — a Cmd with no handler is skipped, invariant 6's forward progress\n // for a miswired consumer.\n const interpretMap = ((opts as { interpret?: Interpret<M, C, Ctx> })\n .interpret ?? {}) as Readonly<Record<string, LoopHandler<M> | undefined>>;\n\n // The runner for a Sub type: the one handed to `run` in `subscribe`, else\n // the engine's built-in of that name (#270 — a user entry overrides a\n // built-in). Looked up per start rather than merged once, so a handler table\n // that resolves its cells lazily (`/react` reads the latest render's) is\n // honoured.\n const subscribeTable = (opts as { subscribe?: unknown }).subscribe as\n | Readonly<Record<string, UserRunner<Ctx, M> | undefined>>\n | undefined;\n const builtins = builtinRunners as Readonly<\n Record<string, UserRunner<Ctx, M> | undefined>\n >;\n\n // A Promise runner has no failure channel of its own: the one failure the\n // engine sees is a throw while it starts, which the loop turns into a stopped\n // run (#309). So the runner is called with the three arguments it declares.\n function runnerFor(type: string): LoopRunner<Ctx, M> | undefined {\n const runner = subscribeTable?.[type] ?? builtins[type];\n if (runner === undefined) return undefined;\n return (sub, ctx, dispatch) => runner(sub, ctx, dispatch);\n }\n\n const handle = startLoop<S, M, C, Ctx>({\n init: machine.init,\n reduce: (state, msg) => applyCell<S, M, C>(machine, state, msg),\n subs: subEntriesOf<S>(machine),\n runnerFor,\n handlerFor: (type) => interpretMap[type],\n store: opts.store,\n // `ctx` is conditionally optional (see `CtxArg`); default the nullish case\n // to `{}` so the handler ctx and `init(loaded, ctx)` get a value.\n ctx: (opts.ctx ?? {}) as Ctx,\n onError: opts.onError,\n disposeTimeoutMs: opts.disposeTimeoutMs ?? 5_000,\n idleCap: opts.__idleCap ?? 100_000,\n extensions: builtinExtensions<S, M, C, E>(machine, opts),\n // Deferred: a runner that throws during a storeless boot fails before\n // `startLoop` has returned the handle.\n stopOnSubFailure: () => {\n queueMicrotask(() => void handle.stop());\n },\n });\n return handle as unknown as BootingRuntime<S, M, E> & LiveWorkProbe;\n}\n\n/** A runner as a caller hands it to `run` in `subscribe`. */\ntype UserRunner<Ctx, M> = (\n sub: Sub,\n ctx: Ctx,\n dispatch: (msg: M) => void,\n) => Dispose;\n\n/**\n * Is the runtime provably out of ways to transition on its own? True only when\n * the handle carries the `liveWork` read and it counts zero live Subs and zero\n * in-flight Cmds. A handle without the read (a wrapper that rebuilt the object\n * without spreading it) cannot be proven stalled, so the drive keeps waiting —\n * the pre-#68 contract, never a false stall.\n */\nfunction isStalled(runtime: object): boolean {\n const probe = (runtime as Partial<LiveWorkProbe>)[liveWork];\n if (probe === undefined) return false;\n const live = probe();\n return live.subs === 0 && live.cmds === 0;\n}\n\n/**\n * The outside-in stop, as a PAIR. A signal is only cancellation if something can\n * turn \"aborted\" into a transition this machine understands, and the kernel has\n * no built-in cancel Msg any more than it has a built-in terminal set — so the\n * `signal` and the `cancel` that reads it are one option, present together or\n * absent together. A discriminated union rather than two optional fields, on the\n * same reasoning as `AgentCompactionConfig`: a signal with no `cancel` is a stop\n * button wired to nothing, and that is a state worth making unrepresentable\n * rather than checking for.\n */\ntype DriveCancellation<S, M> =\n | {\n readonly signal?: undefined;\n readonly cancel?: undefined;\n }\n | {\n /**\n * Abort the run through this signal. On abort the drive dispatches\n * `cancel`'s Msg and resolves on the State that lands — it does NOT reject,\n * and a cancellation is never a `DriveFailedError`. Already aborted when\n * the drive is called → the cancel Msg goes in place of `start`, so the\n * run ends without the start's effects ever firing.\n */\n readonly signal: AbortSignal;\n /**\n * The Msg that settles this machine as cancelled, chosen off the current\n * State exactly as `start` is. PURE. It must land a State `isTerminal`\n * holds for — the drive resolves on the terminal predicate, not on the\n * abort — and that State should be DURABLE, or a reload resumes the run\n * its caller stopped.\n */\n readonly cancel: (state: S) => M;\n };\n\n/** Options for `driveToDone`. */\nexport type DriveToDoneOptions<\n S,\n M extends { type: string } = never,\n> = DriveCancellation<S, M> & {\n /**\n * Marks a terminal State as a FAILURE. A State this holds for ends the drive\n * like any terminal one — the runtime is stopped — but the drive REJECTS with\n * `DriveFailedError` carrying it instead of resolving. A failed State is\n * terminal by definition; it need not also satisfy `isTerminal`. Omit → the\n * drive never rejects on State, only on a runtime error.\n */\n readonly failed?: (state: S) => boolean;\n};\n\n/**\n * Drive a machine from `start` to its terminal State in one call, then tear the\n * runtime down. The one-shot shape every \"run this machine to done\" test and\n * caller boundary otherwise hand-wires as six steps — `await ready`, `observe`,\n * park a promise, `dispatch(start)`, `getState()`, `stop()` — with the observer\n * leak and the forgotten `stop` those six steps invite.\n *\n * Takes the handle `run()` returns (a `Runtime` extends it, so a booted one is\n * accepted too), awaits `ready`, dispatches `start`, and resolves with the first\n * State for which `isTerminal` holds. A machine that boots already terminal (a\n * rehydrated finished run) resolves on its boot State and `start` is never\n * dispatched — a finished run has nothing to set in motion. `start` may be a\n * function of the boot State instead of a Msg: a machine rehydrated MID-run\n * needs its resume Msg, not its start Msg, and only the boot State says which\n * (the agent's `agent_boot` vs `agent_start` is the case in point). The\n * observer is detached and `stop()` awaited on EVERY exit: resolve, `failed`,\n * a stall, a boot or dispatch throw, the quiescence cap.\n *\n * Rejections are typed, never silent:\n * - `DriveFailedError<S>` when `opts.failed` marks the final State — the State\n * rides on `error.state`.\n * - `DriveStalledError<S>` when `start`'s follow-up chain quiesces on a State\n * that is neither terminal nor `failed` and the runtime has no live Sub and\n * no Cmd in flight — nothing inside it can deliver another transition, so\n * waiting would be a hang with the runtime leaked. The stalled State rides\n * on `error.state`. A machine that CAN still move is not stalled: a live Sub\n * that delivers the terminal Msg after the dispatch\n * quiesces keeps the drive waiting, and it resolves on that State. What the\n * runtime cannot see — a dispatch from outside it after quiescence — does\n * not count as live; a machine that depends on one declares it as a Sub.\n * - `QuiescenceTimeoutError` when `start`'s follow-up chain never settles —\n * the SAME cap `dispatch` and `idle()` already reject on (invariant 6), not a\n * second clock. A livelocking machine surfaces as its own failure class.\n * - the boot error, or whatever `dispatch(start)` rejects with, otherwise.\n *\n * `isTerminal` is caller-supplied, exactly as `run()`'s `terminal` option is —\n * the kernel has no built-in terminal-set concept and this does not add one.\n *\n * Cancellation is the same story: `{ signal, cancel }` stops the drive from\n * outside, and it settles the run rather than escaping it. On abort the drive\n * dispatches `cancel`'s Msg and RESOLVES on the terminal State that transition\n * lands — no rejection, no `DriveFailedError`, no `AbortError`. That is the whole\n * point of routing a stop through the Model: the outcome is durable, so a killed\n * process resumes reading a run that ended instead of restarting one someone\n * stopped. A cancel that THROWS is the one abort that rejects: it lands\n * no State, so its error is what the drive has to report — and that holds\n * whether the `cancel` function throws synchronously or the fold it produced\n * throws later, since both leave the run with no State to settle on. A State the\n * caller's `failed` predicate marks rejects on an abort exit exactly as it does\n * anywhere else. In-flight effects are not recalled — a promise cannot be cancelled —\n * so they settle to their own end and `stop()` drains them, up to\n * `disposeTimeoutMs`;\n * keeping their results off the public channels is the machine's own business.\n * Omit the pair and every path here behaves exactly as it did before.\n *\n * @param handle the handle `run(machine, opts)` returned.\n * @param start the Msg that sets the run in motion, or a function choosing\n * it off the boot State. PURE.\n * @param isTerminal the terminal predicate over the machine's State. PURE.\n * @param opts an optional `failed` predicate and an optional\n * `{ signal, cancel }` pair (see {@link DriveToDoneOptions}).\n */\nexport async function driveToDone<\n S,\n M extends { type: string },\n E extends { type: string } = never,\n>(\n handle: BootingRuntime<S, M, E>,\n start: M | ((booted: S) => M),\n isTerminal: (state: S) => boolean,\n opts: DriveToDoneOptions<S, M> = {},\n): Promise<S> {\n const failed = opts.failed ?? (() => false);\n const settles = (state: S): boolean => isTerminal(state) || failed(state);\n let detach: (() => void) | undefined;\n let unlisten: (() => void) | undefined;\n try {\n const runtime = await handle.ready;\n // A run that rehydrated already terminal has nothing to start: resolve on\n // the boot State without dispatching, so `start`'s Cmds are never left in\n // flight for `stop()` to discard. Read BEFORE the signal so a run that\n // already ended keeps the outcome it ended on — an abort arriving after the\n // fact does not restate a finished run as cancelled.\n const booted = runtime.getState();\n // The one exit check every branch below shares: a State the caller's\n // `failed` predicate marks rejects, wherever the drive reached it.\n const outcome = (state: S): S => {\n if (failed(state)) throw new DriveFailedError(state);\n return state;\n };\n if (settles(booted)) return outcome(booted);\n // Attach BEFORE dispatching so a terminal transition landing inside the\n // start dispatch is caught.\n const terminal = new Promise<S>((resolve) => {\n detach = runtime.observe((_msg, state) => {\n if (settles(state)) resolve(state);\n });\n });\n // An abort mid-run enqueues the cancel Msg on the SAME serial tail every\n // other dispatch uses, so it lands between transitions rather than inside\n // one. Work already enqueued when the abort arrives still folds; work the\n // machine would have enqueued after it does not, because the cancelled State\n // is terminal and its verbs stop emitting. `dispatchOnce` (not `dispatch`)\n // because the cancel has no follow-up chain to drain. Its rejection is NOT\n // swallowed: only the cancel fold can land the terminal State on an abort,\n // so a cancel that throws leaves `terminal` with nothing to resolve it and\n // the drive would park forever (#170). `cancelFailed` carries that rejection\n // into whichever race the branch below awaits.\n let cancelFailed: Promise<never> | undefined;\n if (opts.signal !== undefined) {\n const { signal, cancel } = opts;\n let onCancelError!: (error: unknown) => void;\n cancelFailed = new Promise<never>((_resolve, reject) => {\n onCancelError = reject;\n });\n // The try covers the SYNCHRONOUS call site too, not just the dispatch it\n // returns: a caller-supplied `cancel` that throws before it ever produces\n // a Msg would otherwise escape this listener, and a listener's throw goes\n // nowhere — no caller frame to catch it, `terminal` still unresolved, the\n // drive parked exactly as #170 described (#174). Both throws are the same\n // fact — the cancel fold could not land a State — so both take the one\n // route out through `onCancelError`.\n const onAbort = () => {\n try {\n runtime.dispatchOnce(cancel(runtime.getState())).catch(onCancelError);\n } catch (error) {\n onCancelError(error);\n }\n };\n // Already aborted → cancel INSTEAD of starting, so `start`'s effects (a\n // model call, for the agent) never fire at all.\n if (signal.aborted) {\n onAbort();\n return outcome(await Promise.race([terminal, cancelFailed]));\n }\n signal.addEventListener(\"abort\", onAbort, { once: true });\n unlisten = () => signal.removeEventListener(\"abort\", onAbort);\n }\n // The race lets a dispatch rejection (reducer / interpret throw, the\n // quiescence cap) surface here instead of floating as an unhandled rejection\n // while the terminal await parks forever.\n const msg = typeof start === \"function\" ? start(booted) : start;\n const started = runtime.dispatch(msg).then(() => {\n // Quiesced. A settling State already went through the observer, so\n // `terminal` is resolved; otherwise the wait is legitimate only while the\n // runtime itself can still transition. With no live Sub and no Cmd in\n // flight nothing is coming, and the wait becomes the hang #68 names.\n const parked = runtime.getState();\n if (!settles(parked) && isStalled(runtime)) {\n throw new DriveStalledError(parked);\n }\n return terminal;\n });\n // `cancelFailed` rides the race for the mid-run abort: `started` cannot\n // carry the cancel's rejection, having usually resolved long before the\n // abort arrives.\n const racers: Promise<S>[] = [terminal, started];\n if (cancelFailed !== undefined) racers.push(cancelFailed);\n return outcome(await Promise.race(racers));\n } finally {\n detach?.();\n // The abort listener outlives this call unless it is removed: a signal a\n // caller reuses across runs would otherwise accumulate one dispatch per run\n // it has already finished.\n unlisten?.();\n // `stop()` resolves by contract, so awaiting it here cannot mask the error\n // a rejecting branch above is carrying. It can delay it, by up to\n // `disposeTimeoutMs` per wait, while a Cmd handler has not returned.\n await handle.stop();\n }\n}\n"]}