@demlik/tea 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/agent/index.d.ts +907 -0
- package/dist/agent/index.js +20 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/authed-call/index.d.ts +119 -0
- package/dist/authed-call/index.js +148 -0
- package/dist/authed-call/index.js.map +1 -0
- package/dist/await-terminal/index.d.ts +99 -0
- package/dist/await-terminal/index.js +75 -0
- package/dist/await-terminal/index.js.map +1 -0
- package/dist/batch-window/index.d.ts +259 -0
- package/dist/batch-window/index.js +43 -0
- package/dist/batch-window/index.js.map +1 -0
- package/dist/cache/index.d.ts +2 -2
- package/dist/cache/index.js +2 -50
- package/dist/cache/index.js.map +1 -1
- package/dist/chunk-27N2SPTN.js +222 -0
- package/dist/chunk-27N2SPTN.js.map +1 -0
- package/dist/chunk-2YOE5PIS.js +45 -0
- package/dist/chunk-2YOE5PIS.js.map +1 -0
- package/dist/chunk-3545BOYL.js +65 -0
- package/dist/chunk-3545BOYL.js.map +1 -0
- package/dist/chunk-3XJWC7VW.js +61 -0
- package/dist/chunk-3XJWC7VW.js.map +1 -0
- package/dist/chunk-5DEQ5I3U.js +132 -0
- package/dist/chunk-5DEQ5I3U.js.map +1 -0
- package/dist/chunk-5VNWZADP.js +49 -0
- package/dist/chunk-5VNWZADP.js.map +1 -0
- package/dist/chunk-6RGBKODV.js +268 -0
- package/dist/chunk-6RGBKODV.js.map +1 -0
- package/dist/{chunk-ZRUST6BK.js → chunk-7FYQGDMF.js} +2 -2
- package/dist/chunk-7FYQGDMF.js.map +1 -0
- package/dist/chunk-7W4LMPUW.js +186 -0
- package/dist/chunk-7W4LMPUW.js.map +1 -0
- package/dist/chunk-BYQUGDD3.js +16 -0
- package/dist/chunk-BYQUGDD3.js.map +1 -0
- package/dist/{chunk-PYBQREFU.js → chunk-DILMKOZ7.js} +22 -26
- package/dist/chunk-DILMKOZ7.js.map +1 -0
- package/dist/chunk-EHIX7NYZ.js +93 -0
- package/dist/chunk-EHIX7NYZ.js.map +1 -0
- package/dist/chunk-HCEVHFJ7.js +54 -0
- package/dist/chunk-HCEVHFJ7.js.map +1 -0
- package/dist/chunk-HJO6VNQI.js +15 -0
- package/dist/chunk-HJO6VNQI.js.map +1 -0
- package/dist/chunk-IN33Q4VJ.js +52 -0
- package/dist/chunk-IN33Q4VJ.js.map +1 -0
- package/dist/chunk-J6527VRQ.js +56 -0
- package/dist/chunk-J6527VRQ.js.map +1 -0
- package/dist/chunk-JELSZN25.js +47 -0
- package/dist/chunk-JELSZN25.js.map +1 -0
- package/dist/chunk-K575PT4S.js +58 -0
- package/dist/chunk-K575PT4S.js.map +1 -0
- package/dist/chunk-K7Q23EQ4.js +20 -0
- package/dist/chunk-K7Q23EQ4.js.map +1 -0
- package/dist/chunk-LEC4U6CX.js +91 -0
- package/dist/chunk-LEC4U6CX.js.map +1 -0
- package/dist/{chunk-PYOCUBQJ.js → chunk-LP7VQBRG.js} +22 -15
- package/dist/chunk-LP7VQBRG.js.map +1 -0
- package/dist/{chunk-CMG2KMWX.js → chunk-MMH7CW5K.js} +220 -189
- package/dist/chunk-MMH7CW5K.js.map +1 -0
- package/dist/{chunk-Y5JM3UUH.js → chunk-NSDFA6M6.js} +75 -3
- package/dist/chunk-NSDFA6M6.js.map +1 -0
- package/dist/chunk-ON24RKSE.js +60 -0
- package/dist/chunk-ON24RKSE.js.map +1 -0
- package/dist/chunk-PS23H6KQ.js +87 -0
- package/dist/chunk-PS23H6KQ.js.map +1 -0
- package/dist/chunk-QLA76GIT.js +162 -0
- package/dist/chunk-QLA76GIT.js.map +1 -0
- package/dist/chunk-S3FQN4SL.js +506 -0
- package/dist/chunk-S3FQN4SL.js.map +1 -0
- package/dist/chunk-SC3FAFZN.js +53 -0
- package/dist/chunk-SC3FAFZN.js.map +1 -0
- package/dist/chunk-SINXE47Y.js +55 -0
- package/dist/chunk-SINXE47Y.js.map +1 -0
- package/dist/chunk-SQTCS44W.js +33 -0
- package/dist/chunk-SQTCS44W.js.map +1 -0
- package/dist/chunk-TGG26XMK.js +52 -0
- package/dist/chunk-TGG26XMK.js.map +1 -0
- package/dist/chunk-UV6UWGEO.js +147 -0
- package/dist/chunk-UV6UWGEO.js.map +1 -0
- package/dist/chunk-UYWUVEP6.js +158 -0
- package/dist/chunk-UYWUVEP6.js.map +1 -0
- package/dist/chunk-VLGARN6H.js +202 -0
- package/dist/chunk-VLGARN6H.js.map +1 -0
- package/dist/circuit-breaker/index.js +1 -51
- package/dist/circuit-breaker/index.js.map +1 -1
- package/dist/core-BoB6t7dB.d.ts +188 -0
- package/dist/deadline/index.d.ts +24 -5
- package/dist/deadline/index.js +4 -18
- package/dist/deadline/index.js.map +1 -1
- package/dist/debounce/index.d.ts +6 -0
- package/dist/debounce/index.js +1 -54
- package/dist/debounce/index.js.map +1 -1
- package/dist/devtools/index.d.ts +1 -1
- package/dist/devtools/index.js +18 -6
- package/dist/devtools/index.js.map +1 -1
- package/dist/do/index.d.ts +1723 -92
- package/dist/do/index.js +673 -31
- package/dist/do/index.js.map +1 -1
- package/dist/durable-effects-CO9Uh971.d.ts +180 -0
- package/dist/extension/index.d.ts +31 -13
- package/dist/extension/index.js +5 -3
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/react.js +8 -3
- package/dist/extension/react.js.map +1 -1
- package/dist/extension/subs/index.d.ts +2 -2
- package/dist/extension/subs/index.js.map +1 -1
- package/dist/extension/test-utils.js +11 -3
- package/dist/extension/test-utils.js.map +1 -1
- package/dist/fan-out/index.d.ts +239 -0
- package/dist/fan-out/index.js +5 -0
- package/dist/fan-out/index.js.map +1 -0
- package/dist/idempotency/adapter.d.ts +62 -0
- package/dist/idempotency/adapter.js +4 -0
- package/dist/idempotency/adapter.js.map +1 -0
- package/dist/idempotency/index.js +1 -56
- package/dist/idempotency/index.js.map +1 -1
- package/dist/idempotent-intake/index.d.ts +186 -0
- package/dist/idempotent-intake/index.js +7 -0
- package/dist/idempotent-intake/index.js.map +1 -0
- package/dist/index.d.ts +341 -191
- package/dist/index.js +2 -1
- package/dist/llm-call/index.d.ts +309 -0
- package/dist/llm-call/index.js +14 -0
- package/dist/llm-call/index.js.map +1 -0
- package/dist/machine-viz/index.d.ts +4 -4
- package/dist/machine-viz/index.js +5 -9
- package/dist/machine-viz/index.js.map +1 -1
- package/dist/mem/index.d.ts +1 -0
- package/dist/monitored-run/index.d.ts +253 -0
- package/dist/monitored-run/index.js +10 -0
- package/dist/monitored-run/index.js.map +1 -0
- package/dist/node/index.d.ts +5 -5
- package/dist/node/index.js.map +1 -1
- package/dist/paginated-walk/index.d.ts +234 -0
- package/dist/paginated-walk/index.js +15 -0
- package/dist/paginated-walk/index.js.map +1 -0
- package/dist/paginator/index.d.ts +271 -0
- package/dist/paginator/index.js +3 -0
- package/dist/paginator/index.js.map +1 -0
- package/dist/parity/index.d.ts +92 -0
- package/dist/parity/index.js +78 -0
- package/dist/parity/index.js.map +1 -0
- package/dist/pbt/arbitraries/index.d.ts +1 -1
- package/dist/pbt/arbitraries/index.js +1 -1
- package/dist/pbt/index.d.ts +15 -8
- package/dist/pbt/index.js +6 -11
- package/dist/pbt/index.js.map +1 -1
- package/dist/pbt/runners/index.d.ts +1 -1
- package/dist/pbt/runners/index.js +3 -2
- package/dist/poller/index.d.ts +288 -0
- package/dist/poller/index.js +79 -0
- package/dist/poller/index.js.map +1 -0
- package/dist/prediction/index.d.ts +130 -0
- package/dist/prediction/index.js +4 -0
- package/dist/prediction/index.js.map +1 -0
- package/dist/protocol-B5YmJr0d.d.ts +45 -0
- package/dist/pure/index.d.ts +2 -0
- package/dist/pure/index.js +4 -0
- package/dist/pure/index.js.map +1 -0
- package/dist/raft/demo.d.ts +105 -0
- package/dist/raft/demo.js +172 -0
- package/dist/raft/demo.js.map +1 -0
- package/dist/raft/index.d.ts +548 -0
- package/dist/raft/index.js +7 -0
- package/dist/raft/index.js.map +1 -0
- package/dist/raft/sim.d.ts +222 -0
- package/dist/raft/sim.js +8 -0
- package/dist/raft/sim.js.map +1 -0
- package/dist/raft/timeline.d.ts +46 -0
- package/dist/raft/timeline.js +62 -0
- package/dist/raft/timeline.js.map +1 -0
- package/dist/rate-limit/index.js +1 -43
- package/dist/rate-limit/index.js.map +1 -1
- package/dist/react/index.d.ts +2 -1
- package/dist/react/index.js +43 -20
- package/dist/react/index.js.map +1 -1
- package/dist/reconciler/index.d.ts +192 -0
- package/dist/reconciler/index.js +166 -0
- package/dist/reconciler/index.js.map +1 -0
- package/dist/recorder/index.d.ts +7 -3
- package/dist/recorder/index.js +1 -155
- package/dist/recorder/index.js.map +1 -1
- package/dist/resilient-call/index.d.ts +294 -0
- package/dist/resilient-call/index.js +13 -0
- package/dist/resilient-call/index.js.map +1 -0
- package/dist/retry-backoff/index.d.ts +42 -5
- package/dist/retry-backoff/index.js +1 -35
- package/dist/retry-backoff/index.js.map +1 -1
- package/dist/retry-to-success/index.d.ts +109 -0
- package/dist/retry-to-success/index.js +35 -0
- package/dist/retry-to-success/index.js.map +1 -0
- package/dist/saga/index.d.ts +238 -0
- package/dist/saga/index.js +150 -0
- package/dist/saga/index.js.map +1 -0
- package/dist/snapshot/index.d.ts +334 -0
- package/dist/snapshot/index.js +5 -0
- package/dist/snapshot/index.js.map +1 -0
- package/dist/subs/index.d.ts +48 -10
- package/dist/subs/index.js +1 -1
- package/dist/testing/index.d.ts +107 -2
- package/dist/testing/index.js +204 -6
- package/dist/testing/index.js.map +1 -1
- package/dist/throttle/index.d.ts +9 -0
- package/dist/throttle/index.js +1 -52
- package/dist/throttle/index.js.map +1 -1
- package/dist/throttled-input/index.d.ts +380 -0
- package/dist/throttled-input/index.js +88 -0
- package/dist/throttled-input/index.js.map +1 -0
- package/dist/token-refresh/index.d.ts +215 -0
- package/dist/token-refresh/index.js +6 -0
- package/dist/token-refresh/index.js.map +1 -0
- package/dist/trace-replay/index.d.ts +11 -2
- package/dist/trace-replay/index.js +3 -55
- package/dist/trace-replay/index.js.map +1 -1
- package/dist/{types-CwBC5wMn.d.ts → types-B-8bcAp8.d.ts} +1 -1
- package/dist/with-deadline/index.d.ts +196 -0
- package/dist/with-deadline/index.js +111 -0
- package/dist/with-deadline/index.js.map +1 -0
- package/dist/with-resilience/index.d.ts +205 -0
- package/dist/with-resilience/index.js +251 -0
- package/dist/with-resilience/index.js.map +1 -0
- package/dist/with-telemetry/index.d.ts +149 -0
- package/dist/with-telemetry/index.js +70 -0
- package/dist/with-telemetry/index.js.map +1 -0
- package/dist/work-queue/adapter.d.ts +82 -0
- package/dist/work-queue/adapter.js +4 -0
- package/dist/work-queue/adapter.js.map +1 -0
- package/dist/work-queue/index.d.ts +25 -11
- package/dist/work-queue/index.js +1 -46
- package/dist/work-queue/index.js.map +1 -1
- package/dist/work-queue/ops.d.ts +69 -0
- package/dist/work-queue/ops.js +3 -0
- package/dist/work-queue/ops.js.map +1 -0
- package/dist/workflow/index.d.ts +450 -0
- package/dist/workflow/index.js +225 -0
- package/dist/workflow/index.js.map +1 -0
- package/package.json +168 -25
- package/dist/chunk-CMG2KMWX.js.map +0 -1
- package/dist/chunk-PYBQREFU.js.map +0 -1
- package/dist/chunk-PYOCUBQJ.js.map +0 -1
- package/dist/chunk-Y5JM3UUH.js.map +0 -1
- package/dist/chunk-ZRUST6BK.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/workflow/index.ts"],"names":["id"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @demlik/tea/workflow — the durable-workflow runtime core (#124, the first\n * Phase-1 slice of the Temporal-style durable-workflow engine, epic #118).\n *\n * A workflow is a long-running, multi-step transaction whose every step (an\n * **activity**) is a side effect that may evict the actor mid-flight. The\n * substrate already solves \"a state survives eviction\" (the event-sourced\n * store) and \"an owed effect survives eviction\" (the `do/` pending-effects\n * ledger, #67). This module composes those two into the third primitive: a\n * **pure TEA reducer over a workflow's progress**, where each step is dispatched\n * as a durable owed effect and the next step is decided only after the previous\n * one's result is folded back in. That is exactly Temporal's contract — a\n * deterministic workflow function whose activities are the only impurity, so a\n * replay of the event log re-decides every step identically.\n *\n * The mapping onto TEA, mirroring `../raft`:\n *\n * - **Reducer** — {@link createWorkflow}'s verbs (`init`, `onActivityOk`,\n * `onActivityErr`) are the transition function over {@link WorkflowState}.\n * PURE: they read no clock and no RNG. Wall time, randomness, retries — all\n * impurity lives in the consumer's injected *interpret cell* (the thing that\n * actually performs the activity `Cmd` and dispatches its result `Msg`).\n * A workflow driven by a recorded result schedule re-decides bit-for-bit —\n * the headline replay-determinism payoff (acceptance criterion 4).\n * - **Cmd** — the activity dispatch is an {@link ActivityCmd}, recorded on the\n * #67 ledger as an `effect_owed` BEFORE it is performed and `effect_confirmed`\n * on its result. This module emits the Cmd literals; it performs NO side\n * effects. The owed/confirmed events are persisted into the same log the\n * reducer folds, so the ledger (and thus \"which activity is still owed\")\n * rebuilds for free on cold wake — re-emit-on-wake is idempotent by the\n * ledger's monotonic delivery id (acceptance criterion 3).\n * - **Msg** — an activity result (`activity_ok` / `activity_err`) is a `Msg`\n * the consumer routes back into a verb. The verb folds the result, records\n * the completed step, and advances to the next activity (or to `completed`\n * when the sequence is exhausted).\n *\n * **Invalid states unrepresentable.** The in-flight activity (`current`, with\n * its delivery id) lives ONLY on the `running` variant of {@link WorkflowState}.\n * A `completed` workflow carries its final output and no current activity; a\n * `failed` one carries its failure and no current activity. So \"the current\n * activity exists only while running\" is a type-level guarantee, not a runtime\n * convention (acceptance criterion 1).\n *\n * **Boundary with `@demlik/tea/saga` — the compensation test.** Both drive an\n * ordered multi-step transaction with reverse-order compensation (#125 here);\n * the line between them is whether every step carries a true inverse. Here\n * {@link WorkflowStep.compensation} is OPTIONAL — an irreversible step (a sent\n * email) is simply SKIPPED during the unwind, and the run still reaches a\n * terminal state. Saga's `SagaStep<D, U>` REQUIRES both `do` and `undo` — a\n * step without its inverse is unrepresentable, so full reversibility is a\n * type-level guarantee. Pick by the test: **every step must be reversible →\n * saga; some steps are irreversible but the run must finish → workflow.**\n * Cross to saga when you want the type system to enforce that every step can\n * be undone; stay here when the transaction includes steps that can't be.\n *\n * This module is the pure reducer only — like `../raft/index.ts` is the pure\n * Raft reducer with the DO grain deferred to its `./do` sibling (here #126).\n *\n * Generic over three opaque payloads, like raft's command generic: the activity\n * descriptor `A` (what a step asks to do — the consumer's interpret cell knows\n * how to perform it), the activity result `R`, and the failure `F`. The reducer\n * never inspects them; it only sequences them.\n *\n * NOT a substrate primitive: it depends only on the core `Cmd` type and the\n * sibling `../do` durable-effects ledger. Consumers reach it via the\n * `@demlik/tea/workflow` subpath.\n */\n\nimport {\n type DeliveryId,\n type EffectConfirmed,\n type EffectLedgerEvent,\n type EffectOwed,\n emptyLedger,\n foldLedger,\n type PendingEffectsLedger,\n pendingEffectsLedger,\n survivingEffects,\n} from \"../do/durable-effects\";\nimport type { Cmd } from \"../index\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Steps — the static definition of a workflow.\n//\n// A workflow is defined by an ordered sequence of steps. A step is an activity\n// descriptor: a stable `name` (for readability + correlation in the event log)\n// plus the opaque `activity` payload the consumer's interpret cell performs.\n// Steps are inert data; the reducer walks them in order.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * One step of a workflow: a named activity descriptor. `A` is the opaque\n * payload the consumer's interpret cell knows how to perform (an HTTP call, a\n * sibling-grain message, a tool round-trip). The reducer never inspects\n * `activity` — it only sequences the steps and correlates results.\n *\n * A step may declare a **compensation** — the inverse activity that undoes the\n * forward one (cancel the reservation, refund the charge). Compensation is the\n * `{ do, undo }` pair of `@demlik/tea/saga` mapped onto the workflow: the step's\n * `activity` is the `do`, its `compensation` is the `undo`. The pair is the unit\n * of reversibility — a step that completed (its forward activity succeeded) is\n * exactly a step whose `compensation` must run, in reverse order, if a LATER\n * step fails (#125). A step WITHOUT a compensation contributes nothing to the\n * unwind: it is skipped during the reverse walk (an irreversible step — a sent\n * email — that the saga model treats as a no-op `undo`).\n */\nexport interface WorkflowStep<A> {\n /** Stable, human-readable step name — appears in the completed-step record. */\n readonly name: string;\n /** The opaque activity this step performs. Interpreted by the consumer. */\n readonly activity: A;\n /**\n * The opaque compensating (inverse) activity, performed in reverse order on a\n * downstream failure (#125). Optional: a step with no compensation is skipped\n * during the unwind. Interpreted by the consumer's interpret cell, exactly\n * like {@link activity}.\n */\n readonly compensation?: A;\n}\n\n/**\n * A completed step: the step that ran plus the result its activity produced.\n * The `running` state accumulates these in execution order, so the full\n * forward history is available to the consumer (and to #125's compensation,\n * which will walk it in reverse).\n */\nexport interface CompletedStep<A, R> {\n readonly step: WorkflowStep<A>;\n readonly result: R;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// The in-flight activity — the bridge between the reducer and the #67 ledger.\n//\n// While `running`, exactly one activity is in flight. It carries the step it is\n// performing AND the monotonic `deliveryId` the #67 ledger issued for it. The\n// delivery id is the single correlation key: an `activity_ok`/`activity_err`\n// Msg names the id it answers, the reducer matches it against `current.id`, and\n// the ledger confirms that same id. Re-emit-on-wake re-fires the SAME id, so a\n// duplicate result is a no-op (idempotent by delivery id, acceptance crit. 3).\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** The activity currently in flight on a `running` workflow. */\nexport interface InFlightActivity<A> {\n /** The 0-based index of this step in the workflow's step sequence. */\n readonly index: number;\n /** The step whose activity is in flight. */\n readonly step: WorkflowStep<A>;\n /** The #67 ledger delivery id this activity was owed under (the dedup key). */\n readonly id: DeliveryId;\n}\n\n/**\n * The compensation currently in flight on a `compensating` workflow (#125). The\n * mirror of {@link InFlightActivity} for the reverse walk: exactly one\n * compensation is owed at a time, on the SAME #67 ledger, with its own monotonic\n * delivery id. `index` is the index (into `completed`/`steps`) of the step being\n * undone — the unwind walks these from high to low (strict reverse order).\n */\nexport interface InFlightCompensation<A> {\n /** The 0-based index (into the step sequence) of the step being compensated. */\n readonly index: number;\n /** The step whose compensation is in flight. */\n readonly step: WorkflowStep<A>;\n /** The #67 ledger delivery id this compensation was owed under (the dedup key). */\n readonly id: DeliveryId;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// WorkflowState — the discriminated union. Invalid states unrepresentable.\n//\n// `status` is the discriminant. The in-flight `current` activity exists ONLY on\n// `running` and `compensating`; `completed` carries the final `output`;\n// `failed` / `failed_compensated` / `compensation_failed` carry the failure.\n// There is no representable state with a current activity that is neither\n// running nor compensating, nor a terminal state still holding one\n// (acceptance crit. 1).\n//\n// The failure path (#125), mirroring `@demlik/tea/saga`'s phases:\n//\n// running ──activity_err with completed steps──▶ compensating\n// running ──activity_err with NO completed steps──▶ failed (empty rollback)\n// compensating ──all compensations confirmed──▶ failed_compensated\n// compensating ──a compensation itself fails──▶ compensation_failed\n//\n// `compensating` is the analogue of saga's `compensating`; `failed_compensated`\n// of saga's `aborted` (fully unwound); `compensation_failed` of saga's\n// `compensation_failed` (an `undo` bounced, partially unwound — visible state,\n// never a workflow wedged in `compensating` forever).\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * A workflow in progress. Carries the ordered `completed` steps (forward\n * history), the `steps` it is sequencing, and the `current` in-flight activity\n * (with its ledger delivery id). `completed.length === current.index` always\n * holds — the current activity is the next step after the last completed one.\n */\nexport interface RunningWorkflow<A, R> {\n readonly status: \"running\";\n /** The full, static step sequence this workflow runs. */\n readonly steps: readonly WorkflowStep<A>[];\n /** Steps that have produced a result, in execution order. */\n readonly completed: readonly CompletedStep<A, R>[];\n /** The single activity in flight right now. */\n readonly current: InFlightActivity<A>;\n}\n\n/**\n * A workflow that ran every step to completion. `output` is the result of the\n * final step — the workflow's overall output. No activity is in flight.\n */\nexport interface CompletedWorkflow<A, R> {\n readonly status: \"completed\";\n readonly steps: readonly WorkflowStep<A>[];\n /** Every step, in execution order, with its result. */\n readonly completed: readonly CompletedStep<A, R>[];\n /** The final step's result — the workflow's output. */\n readonly output: R;\n}\n\n/**\n * A workflow that failed on an activity with NOTHING to compensate — the\n * forward failure happened with zero completed steps (the first activity\n * failed). `failedStep` is the step whose activity reported the failure;\n * `failure` is the opaque failure payload. `completed` is empty by construction:\n * a failure WITH completed steps pivots into {@link CompensatingWorkflow}\n * instead, so `failed` is precisely the empty-rollback terminal (the analogue\n * of saga's `aborted` reached straight from the first step). No activity is in\n * flight; this is terminal (#125 acceptance criterion 3).\n */\nexport interface FailedWorkflow<A, R, F> {\n readonly status: \"failed\";\n readonly steps: readonly WorkflowStep<A>[];\n /** Empty by construction — a failure with completed steps compensates instead. */\n readonly completed: readonly CompletedStep<A, R>[];\n /** The step whose activity failed. */\n readonly failedStep: WorkflowStep<A>;\n /** The opaque failure the activity reported. */\n readonly failure: F;\n}\n\n/**\n * A workflow unwinding after a forward failure (#125): the compensations of the\n * `completed` steps are being emitted in STRICT REVERSE order, one at a time, on\n * the same #67 ledger. The analogue of saga's `compensating` phase.\n *\n * `current` is the single compensation in flight (the mirror of `running`'s\n * in-flight activity). `compensated` records, in unwind order, the completed\n * steps whose compensation has confirmed — the reverse-walk's progress log, so\n * a cold wake resumes mid-rollback exactly. `failedStep`/`failure` carry the\n * forward failure that triggered the unwind (verbatim, never interpreted),\n * preserved for the consumer to read and to fold onto the terminal state.\n *\n * Invariant: the unwind walks `completed` from its tail toward index 0, skipping\n * steps with no compensation, so `current.index` is strictly below the index of\n * every entry in `compensated`.\n */\nexport interface CompensatingWorkflow<A, R, F> {\n readonly status: \"compensating\";\n readonly steps: readonly WorkflowStep<A>[];\n /** The completed forward steps, in execution order — the history being unwound. */\n readonly completed: readonly CompletedStep<A, R>[];\n /** The step whose forward activity failed, triggering the unwind. */\n readonly failedStep: WorkflowStep<A>;\n /** The opaque forward failure that triggered the unwind. Carried, never interpreted. */\n readonly failure: F;\n /** The single compensation in flight right now. */\n readonly current: InFlightCompensation<A>;\n /** Completed steps whose compensation has confirmed, in unwind (reverse) order. */\n readonly compensated: readonly CompletedStep<A, R>[];\n}\n\n/**\n * A workflow that failed forward and then fully unwound: every completed step's\n * compensation confirmed, in reverse order. Terminal. The analogue of saga's\n * `aborted` (fully unwound) — named `failed_compensated` to align with #125's\n * vocabulary. No activity or compensation is in flight.\n */\nexport interface FailedCompensatedWorkflow<A, R, F> {\n readonly status: \"failed_compensated\";\n readonly steps: readonly WorkflowStep<A>[];\n /** The forward steps that completed (and were each compensated), in execution order. */\n readonly completed: readonly CompletedStep<A, R>[];\n /** The step whose forward activity failed, triggering the (now-finished) unwind. */\n readonly failedStep: WorkflowStep<A>;\n /** The opaque forward failure that triggered compensation. Carried, never interpreted. */\n readonly failure: F;\n}\n\n/**\n * A workflow whose ROLLBACK itself failed: a compensation activity reported a\n * failure mid-unwind. Terminal, PARTIALLY unwound — the analogue of saga's\n * `compensation_failed`. A failed compensation is a real-world fact (a refund\n * that bounced, a hold that won't release) that must become visible terminal\n * state, never a workflow wedged in `compensating` forever.\n *\n * `compensated` is left intact (the steps already rolled back); `uncompensated`\n * is the step whose compensation bounced PLUS the steps still owed a\n * compensation behind it — exactly what the consumer must reconcile by hand.\n * `compensationFailure` is the rollback failure (distinct from `failure`, the\n * forward failure that started the unwind), carried verbatim.\n */\nexport interface CompensationFailedWorkflow<A, R, F> {\n readonly status: \"compensation_failed\";\n readonly steps: readonly WorkflowStep<A>[];\n /** The full completed-forward history, in execution order. */\n readonly completed: readonly CompletedStep<A, R>[];\n /** The step whose forward activity failed, triggering the unwind. */\n readonly failedStep: WorkflowStep<A>;\n /** The opaque forward failure that triggered compensation. Carried, never interpreted. */\n readonly failure: F;\n /** Completed steps successfully compensated before the rollback broke, in unwind order. */\n readonly compensated: readonly CompletedStep<A, R>[];\n /** The step whose compensation bounced. Its forward effect stands un-reversed. */\n readonly failedCompensationStep: WorkflowStep<A>;\n /** The opaque rollback failure (distinct from {@link failure}). Carried, never interpreted. */\n readonly compensationFailure: F;\n}\n\n/**\n * The workflow's state — a discriminated union on `status`. The current\n * in-flight activity is reachable ONLY through `running`, and the in-flight\n * compensation ONLY through `compensating`, making \"an in-flight effect exists\n * only while the workflow is actively driving one\" unrepresentable to violate.\n */\nexport type WorkflowState<A, R, F> =\n | RunningWorkflow<A, R>\n | CompletedWorkflow<A, R>\n | FailedWorkflow<A, R, F>\n | CompensatingWorkflow<A, R, F>\n | FailedCompensatedWorkflow<A, R, F>\n | CompensationFailedWorkflow<A, R, F>;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Cmds — the activity dispatch. Recorded on the #67 ledger as an owed effect.\n//\n// `ActivityCmd` is the single Cmd this module emits: \"perform the activity for\n// step `index`, and the result you dispatch back must carry delivery id `id`\".\n// The consumer's interpret cell performs `activity` and dispatches an\n// `ActivityOk`/`ActivityErr` Msg carrying the SAME id. The owed/confirmed\n// ledger events ride alongside (see {@link WorkflowStep0}/the step tuple).\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Dispatch the in-flight activity. Routed through the consumer's `interpret`\n * over an injected activity port. The reducer emits this literal; it performs\n * no side effect. The result MUST be dispatched back as a Msg carrying `id`.\n */\nexport interface ActivityCmd<A> extends Cmd<\"workflow_activity\"> {\n /** The 0-based step index this activity belongs to. */\n readonly index: number;\n /** The #67 delivery id the result Msg must echo (the dedup key). */\n readonly id: DeliveryId;\n /** The opaque activity to perform. */\n readonly activity: A;\n}\n\n/**\n * Dispatch a compensation (#125). Same shape + ledger discipline as\n * {@link ActivityCmd} — a compensation is just another durable owed effect on\n * the same #67 ledger, with its own delivery id — but the consumer's interpret\n * cell performs the inverse `compensation` payload, and the result is dispatched\n * back as a {@link CompensationOk}/{@link CompensationErr} carrying the SAME id.\n * Distinguished by `type` so the consumer routes forward vs. inverse work to the\n * right port, and the re-emit-on-wake stays type-correct.\n */\nexport interface CompensationCmd<A> extends Cmd<\"workflow_compensation\"> {\n /** The 0-based step index whose compensation this is. */\n readonly index: number;\n /** The #67 delivery id the result Msg must echo (the dedup key). */\n readonly id: DeliveryId;\n /** The opaque compensating (inverse) activity to perform. */\n readonly compensation: A;\n}\n\n/** The Cmd union this module emits: forward activity dispatches AND (#125)\n * reverse compensation dispatches. The owed/confirmed ledger events are not\n * `WorkflowCmd`s — they are Msg variants persisted into the event log (see\n * {@link EffectLedgerEvent}); the {@link WorkflowStep0} tuple carries them out\n * alongside the dispatch Cmd so the host persists owed-before-dispatch. Both\n * Cmd kinds are owed effects on the same ledger, so `survivingActivities`\n * re-emits either kind on cold wake. */\nexport type WorkflowCmd<A> = ActivityCmd<A> | CompensationCmd<A>;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Msgs — activity results the consumer routes back into the reducer.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** An activity succeeded: `id` echoes the {@link ActivityCmd} it answers; the\n * reducer matches it against `current.id`, records the completed step, and\n * advances. */\nexport interface ActivityOk<R> {\n readonly type: \"activity_ok\";\n readonly id: DeliveryId;\n readonly result: R;\n}\n\n/** An activity failed (retries already exhausted by the consumer's interpret\n * cell — this module does not retry). `id` echoes the {@link ActivityCmd};\n * the reducer begins compensation (or settles `failed` when nothing has\n * completed). */\nexport interface ActivityErr<F> {\n readonly type: \"activity_err\";\n readonly id: DeliveryId;\n readonly failure: F;\n}\n\n/** A compensation succeeded (#125): the inverse activity took. `id` echoes the\n * {@link CompensationCmd} it answers; the reducer matches it against the\n * in-flight compensation's id, records the step as compensated, and either\n * emits the next compensation (continuing in reverse) or settles\n * `failed_compensated` when the unwind is complete. A compensation produces no\n * result the workflow carries — its only signal is \"the undo took\". */\nexport interface CompensationOk {\n readonly type: \"compensation_ok\";\n readonly id: DeliveryId;\n}\n\n/** A compensation itself failed (#125): the inverse activity bounced (a refund\n * that won't go through). `id` echoes the {@link CompensationCmd}; the reducer\n * halts the rollback at the terminal `compensation_failed` state — never wedged\n * in `compensating` forever. */\nexport interface CompensationErr<F> {\n readonly type: \"compensation_err\";\n readonly id: DeliveryId;\n readonly failure: F;\n}\n\n/** The Msg union the reducer folds: forward activity results AND (#125) reverse\n * compensation results. */\nexport type WorkflowMsg<R, F> =\n | ActivityOk<R>\n | ActivityErr<F>\n | CompensationOk\n | CompensationErr<F>;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// The step tuple — the reducer's output shape.\n//\n// A verb returns the next `WorkflowState` PLUS the side-effect intents the host\n// must enact, in the ORDER they must be enacted (owed-before-dispatch):\n//\n// - `ledger` — the `effect_owed` / `effect_confirmed` events to PERSIST into\n// the same event log the reducer folds. A `running`-emitting verb owes the\n// new activity (and a verb that folded a prior result confirms it). Persist\n// these BEFORE the dispatch Cmd (the #67 \"persist the intent before deliver\"\n// rule) so an eviction between persist and dispatch re-fires on wake.\n// - `cmds` — the activity dispatch Cmd(s). Empty on a terminal transition.\n//\n// This is the same `readonly [state, cmds]` discipline as raft's `RaftStep`,\n// extended with the ledger-events channel because activities ARE owed effects.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** A workflow reducer step: the next state, the ledger events to persist\n * (owed-before-dispatch), and the activity Cmds to dispatch. */\nexport interface WorkflowStep0<A, R, F> {\n readonly state: WorkflowState<A, R, F>;\n /** Ledger events to persist into the event log, in order, BEFORE `cmds`. */\n readonly ledger: readonly EffectLedgerEvent<WorkflowCmd<A>>[];\n /** Activity dispatch Cmds, after the ledger events are durable. */\n readonly cmds: readonly WorkflowCmd<A>[];\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// createWorkflow — the factory. Returns the hook bag (init/verbs), mirroring\n// how `createRaftNode` / `createAgent` shape their knobs. The factory owns the\n// #67 monotonic-id recorder so every dispatched activity gets a gap-free\n// delivery id; the recorder is rehydrated from the persisted ledger events on\n// cold wake (see `restore`) so ids resume without gaps.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** The hook bag returned by {@link createWorkflow}. Spread its verbs into a\n * machine; `init(steps)` seeds a fresh workflow and emits the first activity,\n * the verbs fold results and advance, and `survivingActivities` re-emits the\n * owed-but-unconfirmed dispatch on cold wake (idempotent by delivery id). */\nexport interface Workflow<A, R, F> {\n /**\n * Seed a fresh workflow over `steps` and dispatch its first activity.\n *\n * - Empty `steps` ⇒ a workflow with nothing to do completes only when it has\n * an output; with no steps there is no output, so an empty sequence is\n * rejected as a misuse (a workflow must have at least one step). The reducer\n * never reaches `completed` without a final result to carry.\n * - Otherwise → `running` with `current` = step 0, owed on the ledger.\n */\n init(steps: readonly WorkflowStep<A>[]): WorkflowStep0<A, R, F>;\n\n /**\n * Fold an activity success. If `msg.id` does not match the in-flight\n * activity's id (a stale/duplicate result from re-emit-on-wake, or a result\n * for an already-advanced step), the state is returned UNCHANGED with no\n * effects — idempotent by delivery id (acceptance criterion 3). Otherwise:\n * record the completed step, confirm the owed activity on the ledger, and\n * either advance to the next step (owing its activity) or transition to\n * `completed` carrying the final result.\n *\n * Only meaningful on `running`; a result arriving for a terminal workflow is\n * a no-op (the workflow is done — at-least-once tolerates the late echo).\n */\n onActivityOk(\n state: WorkflowState<A, R, F>,\n msg: ActivityOk<R>,\n ): WorkflowStep0<A, R, F>;\n\n /**\n * Fold an activity failure (#125). Same id-match idempotency guard as\n * {@link onActivityOk}. On a match: confirm the owed activity on the ledger,\n * then PIVOT into compensation — walk the completed-step history in strict\n * reverse and owe the first declared compensation as a durable effect on the\n * same ledger, transitioning to `compensating`. If NO completed step declares\n * a compensation (the empty-rollback edge — the first activity failed, or\n * every completed step is irreversible), settle `failed` directly with\n * nothing to unwind.\n */\n onActivityErr(\n state: WorkflowState<A, R, F>,\n msg: ActivityErr<F>,\n ): WorkflowStep0<A, R, F>;\n\n /**\n * Fold a compensation success (#125). Same id-match idempotency guard against\n * the in-flight compensation. On a match: confirm the owed compensation,\n * record the step as compensated, and continue the reverse walk — owe the\n * next declared compensation strictly below the step just undone, or settle\n * `failed_compensated` when the unwind is complete. Only meaningful on\n * `compensating`; a result for a terminal/forward workflow is a no-op.\n */\n onCompensationOk(\n state: WorkflowState<A, R, F>,\n msg: CompensationOk,\n ): WorkflowStep0<A, R, F>;\n\n /**\n * Fold a compensation failure (#125). Same id-match idempotency guard. On a\n * match: confirm the owed compensation and HALT the rollback at the terminal\n * `compensation_failed` state — a bounced compensation is visible terminal\n * state to reconcile by hand, never a workflow wedged in `compensating`\n * forever. Mirrors `@demlik/tea/saga`'s `undoErr`.\n */\n onCompensationErr(\n state: WorkflowState<A, R, F>,\n msg: CompensationErr<F>,\n ): WorkflowStep0<A, R, F>;\n\n /**\n * The owed-but-unconfirmed dispatch(es) to re-emit on activation — a forward\n * activity OR (#125) a compensation, rebuilt by folding the persisted ledger\n * events. On a fresh / fully-settled workflow this is empty. The re-fired Cmd\n * carries the SAME delivery id, so a duplicate result is a no-op at the\n * reducer (the id-match guard). This is the cold-wake re-emit that makes both\n * activities AND compensations exactly-once-observable despite the\n * at-least-once transport (acceptance criterion 2 & 3) — including a\n * compensation owed mid-rollback when the actor was evicted.\n */\n survivingActivities(\n ledgerEvents: Iterable<EffectLedgerEvent<WorkflowCmd<A>>>,\n ): readonly WorkflowCmd<A>[];\n}\n\n/**\n * Build a workflow hook bag. The factory captures NO clock and NO RNG — pure\n * bookkeeping over the injected #67 recorder, exactly like\n * `pendingEffectsLedger`. `restore` rehydrates the monotonic delivery-id\n * counter from the persisted ledger events on cold wake, so re-dispatched\n * activities resume their ids without gaps.\n *\n * @param restore - the persisted ledger events (read back from the log) and/or\n * the last issued delivery id, so the id counter resumes gap-free. Omit for a\n * fresh workflow.\n */\nexport function createWorkflow<A, R, F>(restore?: {\n readonly events?: Iterable<EffectLedgerEvent<WorkflowCmd<A>>>;\n readonly lastId?: DeliveryId;\n}): Workflow<A, R, F> {\n // The #67 monotonic-id recorder. It owns the gap-free `deliveryId` and folds\n // an in-memory mirror of the ledger from the same owed/confirmed events the\n // host persists. Reused verbatim — activities AND compensations ARE owed\n // effects on this one ledger (do NOT reinvent the ledger).\n const recorder = pendingEffectsLedger<WorkflowCmd<A>>(restore);\n\n /**\n * Owe + dispatch the activity for `step` at `index`: allocate its delivery\n * id, build the dispatch Cmd, and emit BOTH the `effect_owed` ledger event\n * (to persist first) and the dispatch Cmd. The owed effect IS the dispatch\n * Cmd, so a cold wake re-emits the identical dispatch by folding the ledger.\n *\n * Returns the running state's `current` plus the owed event + dispatch Cmd.\n */\n /** Index into a step sequence with a bounds-check narrowing away `undefined`\n * (`noUncheckedIndexedAccess`). Every call site has already proven the index\n * in range — this converts that proof into the non-optional type, throwing\n * only on a logic error that should be unreachable. */\n function stepAt(\n steps: readonly WorkflowStep<A>[],\n index: number,\n ): WorkflowStep<A> {\n const step = steps[index];\n if (step === undefined) {\n throw new Error(\n `createWorkflow: step index ${index} out of range (length ` +\n `${steps.length}) — a reducer bounds proof was violated.`,\n );\n }\n return step;\n }\n\n /**\n * Predict the next delivery id, build the FINAL dispatch Cmd with it via\n * `build`, owe that exact Cmd on the #67 ledger, and assert the issued id\n * equals the prediction.\n *\n * The dispatch Cmd needs the delivery id, and the ledger event carries that\n * Cmd as its `effect` payload — so the id and the Cmd are mutually dependent.\n * Resolve the cycle by predicting the next id (`lastId() + 1`, the recorder's\n * gap-free monotonic rule), building the final Cmd, then handing that exact\n * Cmd to `recorder.owe`. The recorder issues the predicted id and folds the\n * final Cmd into its mirror — so the mirror, the persisted `effect_owed`\n * event, and the dispatch Cmd all carry one identical id and payload (no\n * placeholder, no rebuild). Shared by the forward owe ({@link oweActivity})\n * and the reverse owe ({@link oweCompensation}) so both halves of the\n * transaction allocate ids on the SAME monotonic ledger.\n */\n function oweOnLedger<C extends WorkflowCmd<A>>(\n build: (id: DeliveryId) => C,\n ): {\n readonly id: DeliveryId;\n readonly owed: EffectOwed<WorkflowCmd<A>>;\n readonly cmd: C;\n } {\n const id = (recorder.lastId() + 1) as DeliveryId;\n const cmd = build(id);\n const { id: issued, event } = recorder.owe(cmd);\n // Defensive: the prediction must equal the issued id, or the ledger mirror\n // and the dispatch Cmd would diverge. This holds by the recorder's gap-free\n // monotonic contract; assert it rather than silently carry a mismatch.\n if (issued !== id) {\n throw new Error(\n `createWorkflow: delivery-id prediction (${id}) diverged from the ` +\n `recorder's issued id (${issued}). The #67 recorder's monotonic ` +\n \"counter is not gap-free as assumed — this is a substrate invariant \" +\n \"violation, not a recoverable condition.\",\n );\n }\n return { id, owed: event, cmd };\n }\n\n function oweActivity(\n step: WorkflowStep<A>,\n index: number,\n ): {\n readonly current: InFlightActivity<A>;\n readonly owed: EffectOwed<WorkflowCmd<A>>;\n readonly cmd: ActivityCmd<A>;\n } {\n const { id, owed, cmd } = oweOnLedger<ActivityCmd<A>>((id) => ({\n type: \"workflow_activity\",\n index,\n id,\n activity: step.activity,\n }));\n const current: InFlightActivity<A> = { index, step, id };\n return { current, owed, cmd };\n }\n\n /**\n * Owe + dispatch the COMPENSATION for the completed step at `index` (#125):\n * the reverse-walk mirror of {@link oweActivity}. The compensation is a\n * durable owed effect on the SAME #67 ledger, so a cold wake mid-rollback\n * re-emits the identical compensation dispatch by folding the ledger\n * (acceptance criterion 2). `step.compensation` is required to be present —\n * the caller (the reverse walk) only owes compensations for steps that\n * declared one, skipping the rest.\n */\n function oweCompensation(\n step: WorkflowStep<A>,\n compensation: A,\n index: number,\n ): {\n readonly current: InFlightCompensation<A>;\n readonly owed: EffectOwed<WorkflowCmd<A>>;\n readonly cmd: CompensationCmd<A>;\n } {\n const { id, owed, cmd } = oweOnLedger<CompensationCmd<A>>((id) => ({\n type: \"workflow_compensation\",\n index,\n id,\n compensation,\n }));\n const current: InFlightCompensation<A> = { index, step, id };\n return { current, owed, cmd };\n }\n\n /**\n * Begin (or continue) the reverse walk from `completed`, owing the next\n * compensation strictly BELOW `belowIndex` (exclusive) that declares one. The\n * single source of truth for \"what to undo next\", shared by the failure pivot\n * ({@link Workflow.onActivityErr}) and each `compensation_ok` step\n * ({@link Workflow.onCompensationOk}). Steps with no compensation are SKIPPED\n * (they contribute nothing to the unwind — saga's no-op `undo`).\n *\n * Returns the next compensation to owe (its in-flight record + ledger event +\n * dispatch Cmd), or `null` when no compensable step remains below `belowIndex`\n * — the unwind is complete and the caller settles `failed_compensated`.\n */\n function oweNextCompensation(\n completed: readonly CompletedStep<A, R>[],\n belowIndex: number,\n ): {\n readonly current: InFlightCompensation<A>;\n readonly owed: EffectOwed<WorkflowCmd<A>>;\n readonly cmd: CompensationCmd<A>;\n } | null {\n // Walk the completed history from just below `belowIndex` toward 0 (strict\n // reverse order), stopping at the first step that declares a compensation.\n for (let i = belowIndex - 1; i >= 0; i--) {\n const entry = completed[i];\n // `completed[i]` is the i-th completed step; `i` is also its step index\n // (completed is in execution order, contiguous from 0 up to the failure).\n if (entry !== undefined && entry.step.compensation !== undefined) {\n return oweCompensation(entry.step, entry.step.compensation, i);\n }\n }\n return null;\n }\n\n return {\n init(steps) {\n if (steps.length === 0) {\n throw new Error(\n \"createWorkflow.init: a workflow must have at least one step. An \" +\n \"empty step sequence has no final result to carry into `completed`, \" +\n \"so it is unrepresentable as a completed workflow — reject it at the \" +\n \"boundary rather than admit a resultless terminal state.\",\n );\n }\n const { current, owed, cmd } = oweActivity(stepAt(steps, 0), 0);\n const state: RunningWorkflow<A, R> = {\n status: \"running\",\n steps,\n completed: [],\n current,\n };\n return { state, ledger: [owed], cmds: [cmd] };\n },\n\n onActivityOk(state, msg) {\n // A result for a terminal workflow, or one whose id does not match the\n // in-flight activity, is a no-op — idempotent by delivery id. This is the\n // guard that makes re-emit-on-wake safe: a re-fired (already-folded)\n // activity's result arrives with an id that is no longer `current.id`.\n if (state.status !== \"running\" || msg.id !== state.current.id) {\n return { state, ledger: [], cmds: [] };\n }\n\n // Record the completed step and confirm the owed activity on the ledger.\n const confirmed: EffectConfirmed = confirm(state.current.id);\n const completedStep: CompletedStep<A, R> = {\n step: state.current.step,\n result: msg.result,\n };\n const completed = [...state.completed, completedStep];\n\n const nextIndex = state.current.index + 1;\n if (nextIndex >= state.steps.length) {\n // The final step's result is the workflow's output → `completed`.\n const done: CompletedWorkflow<A, R> = {\n status: \"completed\",\n steps: state.steps,\n completed,\n output: msg.result,\n };\n return { state: done, ledger: [confirmed], cmds: [] };\n }\n\n // Advance: owe + dispatch the next activity.\n const { current, owed, cmd } = oweActivity(\n stepAt(state.steps, nextIndex),\n nextIndex,\n );\n const running: RunningWorkflow<A, R> = {\n status: \"running\",\n steps: state.steps,\n completed,\n current,\n };\n // Persist the confirm of the prior activity AND the owe of the next, in\n // that order, before dispatching the next. (Confirm-then-owe keeps the\n // ledger holding at most one in-flight activity per workflow.)\n return { state: running, ledger: [confirmed, owed], cmds: [cmd] };\n },\n\n onActivityErr(state, msg) {\n if (state.status !== \"running\" || msg.id !== state.current.id) {\n return { state, ledger: [], cmds: [] };\n }\n const confirmed: EffectConfirmed = confirm(state.current.id);\n\n // #125: the forward failure PIVOTS into compensation. Mirroring saga's\n // `stepErr`: walk the completed history in strict reverse, owing the\n // first declared compensation as a durable effect on this SAME ledger.\n // `state.completed` is preserved precisely so this reverse walk has its\n // history.\n const next = oweNextCompensation(state.completed, state.completed.length);\n\n if (next === null) {\n // Empty rollback: no completed step declares a compensation (the first\n // activity failed, OR every completed step is irreversible). The\n // analogue of saga aborting straight from the first step — settle\n // `failed` directly, nothing to unwind (acceptance criterion 3).\n const failed: FailedWorkflow<A, R, F> = {\n status: \"failed\",\n steps: state.steps,\n completed: state.completed,\n failedStep: state.current.step,\n failure: msg.failure,\n };\n return { state: failed, ledger: [confirmed], cmds: [] };\n }\n\n // Pivot to `compensating`, emitting the first compensation. Confirm the\n // failed forward activity AND owe the first compensation, in that order,\n // before dispatching it (the same confirm-then-owe ledger discipline as\n // the forward advance — at most one in-flight effect per workflow).\n const compensating: CompensatingWorkflow<A, R, F> = {\n status: \"compensating\",\n steps: state.steps,\n completed: state.completed,\n failedStep: state.current.step,\n failure: msg.failure,\n current: next.current,\n compensated: [],\n };\n return {\n state: compensating,\n ledger: [confirmed, next.owed],\n cmds: [next.cmd],\n };\n },\n\n onCompensationOk(state, msg) {\n // A compensation result for a non-compensating workflow, or one whose id\n // does not match the in-flight compensation, is a no-op — idempotent by\n // delivery id, the same re-emit-on-wake guard as the forward verbs.\n if (state.status !== \"compensating\" || msg.id !== state.current.id) {\n return { state, ledger: [], cmds: [] };\n }\n\n // Confirm the owed compensation and record the step as compensated.\n const confirmed: EffectConfirmed = confirm(state.current.id);\n const undoneStep = state.completed[state.current.index];\n if (undoneStep === undefined) {\n throw new Error(\n `createWorkflow.onCompensationOk: compensation index ` +\n `${state.current.index} has no completed step — a reverse-walk ` +\n \"bounds proof was violated.\",\n );\n }\n const compensated = [...state.compensated, undoneStep];\n\n // Continue the reverse walk strictly below the step just undone, skipping\n // steps with no compensation.\n const next = oweNextCompensation(state.completed, state.current.index);\n\n if (next === null) {\n // Fully unwound → `failed_compensated` (saga's `aborted`).\n const settled: FailedCompensatedWorkflow<A, R, F> = {\n status: \"failed_compensated\",\n steps: state.steps,\n completed: state.completed,\n failedStep: state.failedStep,\n failure: state.failure,\n };\n return { state: settled, ledger: [confirmed], cmds: [] };\n }\n\n const compensating: CompensatingWorkflow<A, R, F> = {\n status: \"compensating\",\n steps: state.steps,\n completed: state.completed,\n failedStep: state.failedStep,\n failure: state.failure,\n current: next.current,\n compensated,\n };\n return {\n state: compensating,\n ledger: [confirmed, next.owed],\n cmds: [next.cmd],\n };\n },\n\n onCompensationErr(state, msg) {\n // Same id-match idempotency guard as the other verbs.\n if (state.status !== \"compensating\" || msg.id !== state.current.id) {\n return { state, ledger: [], cmds: [] };\n }\n // Confirm the owed compensation (the dispatch is no longer owed — it was\n // delivered and answered, just with a failure), then HALT the rollback at\n // the terminal `compensation_failed` state. Mirroring saga's `undoErr`: a\n // bounced compensation is a real-world fact the consumer must reconcile by\n // hand, never a workflow wedged in `compensating` forever. `compensated`\n // (already rolled back) is left intact for the consumer to read.\n const confirmed: EffectConfirmed = confirm(state.current.id);\n const halted: CompensationFailedWorkflow<A, R, F> = {\n status: \"compensation_failed\",\n steps: state.steps,\n completed: state.completed,\n failedStep: state.failedStep,\n failure: state.failure,\n compensated: state.compensated,\n failedCompensationStep: state.current.step,\n compensationFailure: msg.failure,\n };\n return { state: halted, ledger: [confirmed], cmds: [] };\n },\n\n survivingActivities(ledgerEvents) {\n // Rebuild the ledger by folding the persisted events (NOT a side table),\n // then the surviving owed effects ARE the dispatch Cmds to re-fire — each\n // owed effect's payload is its dispatch Cmd (a forward activity OR a\n // compensation), carrying the same id.\n const ledger: PendingEffectsLedger<WorkflowCmd<A>> =\n foldLedger(ledgerEvents);\n return survivingEffects(ledger).map((owed) => owed.effect);\n },\n };\n\n /** Confirm an owed activity (or compensation) on the recorder's ledger,\n * returning the `effect_confirmed` event to persist. The boolean (was-owed)\n * is not needed by the reducer — the id-match guard above already gates on\n * the in-flight effect — so it is discarded here. */\n function confirm(id: DeliveryId): EffectConfirmed {\n return recorder.confirm(id).event;\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// foldWorkflow — the pure replay fold. The determinism contract (acceptance\n// criterion 4) in one function: fold a workflow's full event log — its `init`\n// steps plus the ordered activity-result Msgs — back into a `WorkflowState`,\n// reading no clock and no RNG. Two folds of the same log produce byte-identical\n// state, because every decision is a pure function of (state, msg) and the\n// delivery ids are reissued in the same order from the same fresh recorder.\n//\n// This is the workflow-level twin of `foldLedger`: the host's `replay()` over\n// the event-sourced store re-runs exactly this fold to rebuild a workflow on\n// cold wake. Kept here (not only in the host, #126) so the determinism contract\n// is provable against the pure core alone.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Replay a workflow from its event log: seed over `steps`, then fold each\n * activity-result `Msg` in order. Returns the final {@link WorkflowState}.\n *\n * Determinism: a freshly-constructed {@link createWorkflow} reissues delivery\n * ids monotonically from 0, and the reducer reads no ambient state — so two\n * calls with the same `steps` + `msgs` return deeply-equal states (and equal\n * `JSON.stringify`). This is the byte-identity property #124 pins.\n *\n * Note this folds ONLY the result Msgs; the `init` dispatch + per-step\n * owe/confirm ledger events are an internal effect channel reconstructed by the\n * fresh recorder, not part of the replayed Msg log — exactly as the activity\n * results are the only thing the consumer records and replays.\n */\nexport function foldWorkflow<A, R, F>(\n steps: readonly WorkflowStep<A>[],\n msgs: Iterable<WorkflowMsg<R, F>>,\n): WorkflowState<A, R, F> {\n const wf = createWorkflow<A, R, F>();\n let { state } = wf.init(steps);\n for (const msg of msgs) {\n // Route each result Msg to its verb. The forward results (`activity_*`) and\n // the reverse compensation results (`compensation_*`, #125) are folded by\n // the same fresh recorder, so a replay across a failure+compensation\n // sequence re-issues every delivery id in identical order — byte-identity\n // holds end to end (acceptance criterion 4).\n let step: WorkflowStep0<A, R, F>;\n switch (msg.type) {\n case \"activity_ok\":\n step = wf.onActivityOk(state, msg);\n break;\n case \"activity_err\":\n step = wf.onActivityErr(state, msg);\n break;\n case \"compensation_ok\":\n step = wf.onCompensationOk(state, msg);\n break;\n case \"compensation_err\":\n step = wf.onCompensationErr(state, msg);\n break;\n }\n state = step.state;\n }\n return state;\n}\n\n// Re-export the durable-effects types a consumer needs to persist + replay a\n// workflow's ledger (the owed/confirmed events, the delivery id, the empty\n// seed) without reaching into the `../do` subpath directly.\nexport {\n type DeliveryId,\n type EffectConfirmed,\n type EffectLedgerEvent,\n type EffectOwed,\n emptyLedger,\n};\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@demlik/tea",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
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, work-queue, devtools, and property-testing modules as subpaths.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"tea",
|
|
@@ -77,6 +77,14 @@
|
|
|
77
77
|
"types": "./dist/work-queue/index.d.ts",
|
|
78
78
|
"import": "./dist/work-queue/index.js"
|
|
79
79
|
},
|
|
80
|
+
"./work-queue/ops": {
|
|
81
|
+
"types": "./dist/work-queue/ops.d.ts",
|
|
82
|
+
"import": "./dist/work-queue/ops.js"
|
|
83
|
+
},
|
|
84
|
+
"./work-queue/adapter": {
|
|
85
|
+
"types": "./dist/work-queue/adapter.d.ts",
|
|
86
|
+
"import": "./dist/work-queue/adapter.js"
|
|
87
|
+
},
|
|
80
88
|
"./node": {
|
|
81
89
|
"types": "./dist/node/index.d.ts",
|
|
82
90
|
"import": "./dist/node/index.js"
|
|
@@ -105,6 +113,10 @@
|
|
|
105
113
|
"types": "./dist/idempotency/index.d.ts",
|
|
106
114
|
"import": "./dist/idempotency/index.js"
|
|
107
115
|
},
|
|
116
|
+
"./idempotency/adapter": {
|
|
117
|
+
"types": "./dist/idempotency/adapter.d.ts",
|
|
118
|
+
"import": "./dist/idempotency/adapter.js"
|
|
119
|
+
},
|
|
108
120
|
"./circuit-breaker": {
|
|
109
121
|
"types": "./dist/circuit-breaker/index.d.ts",
|
|
110
122
|
"import": "./dist/circuit-breaker/index.js"
|
|
@@ -117,6 +129,14 @@
|
|
|
117
129
|
"types": "./dist/deadline/index.d.ts",
|
|
118
130
|
"import": "./dist/deadline/index.js"
|
|
119
131
|
},
|
|
132
|
+
"./await-terminal": {
|
|
133
|
+
"types": "./dist/await-terminal/index.d.ts",
|
|
134
|
+
"import": "./dist/await-terminal/index.js"
|
|
135
|
+
},
|
|
136
|
+
"./retry-to-success": {
|
|
137
|
+
"types": "./dist/retry-to-success/index.d.ts",
|
|
138
|
+
"import": "./dist/retry-to-success/index.js"
|
|
139
|
+
},
|
|
120
140
|
"./machine-viz": {
|
|
121
141
|
"types": "./dist/machine-viz/index.d.ts",
|
|
122
142
|
"import": "./dist/machine-viz/index.js"
|
|
@@ -137,15 +157,116 @@
|
|
|
137
157
|
"types": "./dist/trace-replay/index.d.ts",
|
|
138
158
|
"import": "./dist/trace-replay/index.js"
|
|
139
159
|
},
|
|
160
|
+
"./paginator": {
|
|
161
|
+
"types": "./dist/paginator/index.d.ts",
|
|
162
|
+
"import": "./dist/paginator/index.js"
|
|
163
|
+
},
|
|
164
|
+
"./snapshot": {
|
|
165
|
+
"types": "./dist/snapshot/index.d.ts",
|
|
166
|
+
"import": "./dist/snapshot/index.js"
|
|
167
|
+
},
|
|
168
|
+
"./token-refresh": {
|
|
169
|
+
"types": "./dist/token-refresh/index.d.ts",
|
|
170
|
+
"import": "./dist/token-refresh/index.js"
|
|
171
|
+
},
|
|
172
|
+
"./resilient-call": {
|
|
173
|
+
"types": "./dist/resilient-call/index.d.ts",
|
|
174
|
+
"import": "./dist/resilient-call/index.js"
|
|
175
|
+
},
|
|
176
|
+
"./with-telemetry": {
|
|
177
|
+
"types": "./dist/with-telemetry/index.d.ts",
|
|
178
|
+
"import": "./dist/with-telemetry/index.js"
|
|
179
|
+
},
|
|
180
|
+
"./fan-out": {
|
|
181
|
+
"types": "./dist/fan-out/index.d.ts",
|
|
182
|
+
"import": "./dist/fan-out/index.js"
|
|
183
|
+
},
|
|
184
|
+
"./idempotent-intake": {
|
|
185
|
+
"types": "./dist/idempotent-intake/index.d.ts",
|
|
186
|
+
"import": "./dist/idempotent-intake/index.js"
|
|
187
|
+
},
|
|
188
|
+
"./poller": {
|
|
189
|
+
"types": "./dist/poller/index.d.ts",
|
|
190
|
+
"import": "./dist/poller/index.js"
|
|
191
|
+
},
|
|
192
|
+
"./raft": {
|
|
193
|
+
"types": "./dist/raft/index.d.ts",
|
|
194
|
+
"import": "./dist/raft/index.js"
|
|
195
|
+
},
|
|
196
|
+
"./raft/sim": {
|
|
197
|
+
"types": "./dist/raft/sim.d.ts",
|
|
198
|
+
"import": "./dist/raft/sim.js"
|
|
199
|
+
},
|
|
200
|
+
"./raft/demo": {
|
|
201
|
+
"types": "./dist/raft/demo.d.ts",
|
|
202
|
+
"import": "./dist/raft/demo.js"
|
|
203
|
+
},
|
|
204
|
+
"./raft/timeline": {
|
|
205
|
+
"types": "./dist/raft/timeline.d.ts",
|
|
206
|
+
"import": "./dist/raft/timeline.js"
|
|
207
|
+
},
|
|
208
|
+
"./workflow": {
|
|
209
|
+
"types": "./dist/workflow/index.d.ts",
|
|
210
|
+
"import": "./dist/workflow/index.js"
|
|
211
|
+
},
|
|
212
|
+
"./batch-window": {
|
|
213
|
+
"types": "./dist/batch-window/index.d.ts",
|
|
214
|
+
"import": "./dist/batch-window/index.js"
|
|
215
|
+
},
|
|
216
|
+
"./saga": {
|
|
217
|
+
"types": "./dist/saga/index.d.ts",
|
|
218
|
+
"import": "./dist/saga/index.js"
|
|
219
|
+
},
|
|
220
|
+
"./throttled-input": {
|
|
221
|
+
"types": "./dist/throttled-input/index.d.ts",
|
|
222
|
+
"import": "./dist/throttled-input/index.js"
|
|
223
|
+
},
|
|
224
|
+
"./monitored-run": {
|
|
225
|
+
"types": "./dist/monitored-run/index.d.ts",
|
|
226
|
+
"import": "./dist/monitored-run/index.js"
|
|
227
|
+
},
|
|
228
|
+
"./llm-call": {
|
|
229
|
+
"types": "./dist/llm-call/index.d.ts",
|
|
230
|
+
"import": "./dist/llm-call/index.js"
|
|
231
|
+
},
|
|
232
|
+
"./paginated-walk": {
|
|
233
|
+
"types": "./dist/paginated-walk/index.d.ts",
|
|
234
|
+
"import": "./dist/paginated-walk/index.js"
|
|
235
|
+
},
|
|
236
|
+
"./authed-call": {
|
|
237
|
+
"types": "./dist/authed-call/index.d.ts",
|
|
238
|
+
"import": "./dist/authed-call/index.js"
|
|
239
|
+
},
|
|
240
|
+
"./reconciler": {
|
|
241
|
+
"types": "./dist/reconciler/index.d.ts",
|
|
242
|
+
"import": "./dist/reconciler/index.js"
|
|
243
|
+
},
|
|
244
|
+
"./agent": {
|
|
245
|
+
"types": "./dist/agent/index.d.ts",
|
|
246
|
+
"import": "./dist/agent/index.js"
|
|
247
|
+
},
|
|
248
|
+
"./with-deadline": {
|
|
249
|
+
"types": "./dist/with-deadline/index.d.ts",
|
|
250
|
+
"import": "./dist/with-deadline/index.js"
|
|
251
|
+
},
|
|
252
|
+
"./with-resilience": {
|
|
253
|
+
"types": "./dist/with-resilience/index.d.ts",
|
|
254
|
+
"import": "./dist/with-resilience/index.js"
|
|
255
|
+
},
|
|
256
|
+
"./prediction": {
|
|
257
|
+
"types": "./dist/prediction/index.d.ts",
|
|
258
|
+
"import": "./dist/prediction/index.js"
|
|
259
|
+
},
|
|
260
|
+
"./pure": {
|
|
261
|
+
"types": "./dist/pure/index.d.ts",
|
|
262
|
+
"import": "./dist/pure/index.js"
|
|
263
|
+
},
|
|
264
|
+
"./parity": {
|
|
265
|
+
"types": "./dist/parity/index.d.ts",
|
|
266
|
+
"import": "./dist/parity/index.js"
|
|
267
|
+
},
|
|
140
268
|
"./package.json": "./package.json"
|
|
141
269
|
},
|
|
142
|
-
"scripts": {
|
|
143
|
-
"build": "tsup",
|
|
144
|
-
"test": "vitest run",
|
|
145
|
-
"typecheck": "tsc --noEmit",
|
|
146
|
-
"typecheck:examples": "tsc -p examples/tsconfig.json",
|
|
147
|
-
"prepublishOnly": "tsup"
|
|
148
|
-
},
|
|
149
270
|
"dependencies": {
|
|
150
271
|
"better-result": "^2.5.2"
|
|
151
272
|
},
|
|
@@ -157,28 +278,50 @@
|
|
|
157
278
|
"ws": "^8"
|
|
158
279
|
},
|
|
159
280
|
"peerDependenciesMeta": {
|
|
160
|
-
"fast-check": {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
"
|
|
164
|
-
|
|
281
|
+
"fast-check": {
|
|
282
|
+
"optional": true
|
|
283
|
+
},
|
|
284
|
+
"react": {
|
|
285
|
+
"optional": true
|
|
286
|
+
},
|
|
287
|
+
"react-dom": {
|
|
288
|
+
"optional": true
|
|
289
|
+
},
|
|
290
|
+
"vitest": {
|
|
291
|
+
"optional": true
|
|
292
|
+
},
|
|
293
|
+
"ws": {
|
|
294
|
+
"optional": true
|
|
295
|
+
}
|
|
165
296
|
},
|
|
166
297
|
"devDependencies": {
|
|
167
|
-
"@cloudflare/workers-types": "
|
|
168
|
-
"@types/chrome": "
|
|
169
|
-
"@types/node": "
|
|
170
|
-
"@types/react": "
|
|
171
|
-
"@types/react-dom": "
|
|
172
|
-
"@types/ws": "
|
|
298
|
+
"@cloudflare/workers-types": "4.20260312.1",
|
|
299
|
+
"@types/chrome": "^0.1.42",
|
|
300
|
+
"@types/node": "^22",
|
|
301
|
+
"@types/react": "19.0.8",
|
|
302
|
+
"@types/react-dom": "19.0.3",
|
|
303
|
+
"@types/ws": "8.5.14",
|
|
173
304
|
"fast-check": "^3.23.0",
|
|
174
|
-
"
|
|
175
|
-
"react
|
|
305
|
+
"happy-dom": "^20.9.0",
|
|
306
|
+
"react": "19.0.0",
|
|
307
|
+
"react-dom": "19.0.0",
|
|
176
308
|
"tsup": "^8.3.5",
|
|
177
|
-
"typescript": "
|
|
178
|
-
"vitest": "
|
|
179
|
-
"ws": "
|
|
309
|
+
"typescript": "5.7.3",
|
|
310
|
+
"vitest": "~3.0.7",
|
|
311
|
+
"ws": "8.18.1"
|
|
180
312
|
},
|
|
181
313
|
"publishConfig": {
|
|
182
314
|
"access": "public"
|
|
315
|
+
},
|
|
316
|
+
"scripts": {
|
|
317
|
+
"build": "NODE_OPTIONS=--max-old-space-size=8192 tsup",
|
|
318
|
+
"lint": "biome check src package.json",
|
|
319
|
+
"test": "vitest run",
|
|
320
|
+
"demo:raft": "vitest run src/raft/demo.test.ts",
|
|
321
|
+
"demo:raft:viz": "vitest run src/raft/timeline.test.ts",
|
|
322
|
+
"demo:saga": "vitest run src/workflow/demo.test.ts",
|
|
323
|
+
"typecheck": "tsc --noEmit",
|
|
324
|
+
"typecheck:examples": "tsc -p examples/tsconfig.json",
|
|
325
|
+
"verify-exports": "node scripts/verify-exports.mjs"
|
|
183
326
|
}
|
|
184
|
-
}
|
|
327
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":["state"],"mappings":"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","file":"chunk-CMG2KMWX.js","sourcesContent":["/**\n * @demlik/tea — TEA-faithful state machine substrate.\n *\n * This module exposes the full Locked API Surface from the PRD as types and\n * implements `defineMachine` (identity, task 1), `run` (task 2), and\n * `replay` + `tryInterpret` (task 3).\n *\n * `replay` is the pure unit-test tool: it composes `init` + `update` only,\n * never touches `Store`, never calls `interpret`, never starts subscriptions.\n *\n * `tryInterpret` is Railway sugar — wraps a fallible async function and routes\n * Ok/Err to two Msg constructors via better-result.\n */\n\nimport { Result } from \"better-result\";\n\n// === Dev-mode invariant enforcement ===\n//\n// Three runtime guards that catch TEA invariant violations during development.\n// Dead-code-eliminated in production by any bundler that replaces\n// `import.meta.env.DEV` (Vite) or `process.env.NODE_ENV` (webpack/esbuild).\n//\n// 1. deepFreeze(state) before passing to update — catches mutation\n// 2. Thenable check on return value — catches async update\n// 3. Walk cmd fields for function types — catches closures-as-data\n//\n// These enforce invariants 1 and 2 at runtime where the type system cannot.\nconst __DEV__: boolean = (() => {\n try {\n const meta = import.meta as unknown as { env?: { DEV?: boolean } };\n if (meta.env?.DEV !== undefined) {\n return meta.env.DEV;\n }\n } catch {\n // import.meta not available (Node CJS, service workers)\n }\n try {\n return typeof process !== \"undefined\" && process.env?.NODE_ENV !== \"production\";\n } catch {\n return false;\n }\n})();\n\nfunction deepFreeze<T>(obj: T, seen?: WeakSet<object>): T {\n if (obj === null || typeof obj !== \"object\") return obj;\n const proto = Object.getPrototypeOf(obj);\n if (proto !== Object.prototype && proto !== Array.prototype && proto !== null) return obj;\n const visited = seen ?? new WeakSet();\n if (visited.has(obj as object)) return obj;\n visited.add(obj as object);\n Object.freeze(obj);\n for (const v of Object.values(obj as Record<string, unknown>)) deepFreeze(v, visited);\n return obj;\n}\n\nfunction hasFunctionValues(obj: unknown): string | null {\n if (obj === null || typeof obj !== \"object\") return null;\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n if (typeof value === \"function\") return key;\n }\n return null;\n}\n\nfunction assertPureResult(result: unknown, msgType: string): void {\n if (result && typeof (result as { then?: unknown }).then === \"function\") {\n throw new Error(\n `@demlik/tea: update cell \"${msgType}\" returned a Promise. ` +\n `update must be synchronous. Move async work to interpret.`,\n );\n }\n if (!Array.isArray(result) || result.length < 2 || !Array.isArray(result[1])) {\n throw new Error(\n `@demlik/tea: update cell \"${msgType}\" returned a non-tuple. ` +\n `Expected [State, Cmd[]], got ${typeof result}.`,\n );\n }\n const cmds = result[1] as readonly { type: string }[];\n for (const cmd of cmds) {\n const fnField = hasFunctionValues(cmd);\n if (fnField !== null) {\n throw new Error(\n `@demlik/tea: Cmd \"${cmd.type}\" has function field \"${fnField}\". ` +\n `Cmds must be plain data, not closures.`,\n );\n }\n }\n}\n\n// === Cmd: tagged-union, one-shot effect ===\nexport type Cmd<T extends string = string> = { type: T };\n\n// === Reducer<S, M, C>: record-of-handlers form of `update` ===\n//\n// Flat dispatch table keyed by `Msg.type`. Each cell is a pure transition for\n// a single Msg variant, narrowed via `Extract<M, { type: K }>`. The mapped type\n// is *load-bearing*: adding a Msg variant without a matching key in the\n// Reducer record is a compile error — no `absurd()` helper at call sites, no\n// silent fall-through, no default branch hiding impurity.\n//\n// Strengthens invariant 2 (pure transitions — the record form has no\n// fall-through default to hide impurity behind) and invariant 7 (identity is\n// explicit — the Msg variant set is load-bearing at the type level).\n//\n// `defineMachine` accepts the Reducer record form via overload. The runtime\n// dispatches via `update[msg.type](state, msg)`.\nexport type Reducer<S, M extends { type: string }, C extends Cmd> = {\n [K in M[\"type\"]]: (state: S, msg: Extract<M, { type: K }>) => readonly [S, readonly C[]];\n};\n\n// === Transitions<S, M, C>: table form of `update` for state-machine-shaped machines ===\n//\n// When `State` is itself a discriminated union (`State.type` is the active\n// phase), the table form makes every (state.type × msg.type) cell explicit at\n// the type level. Missing a cell — e.g. forgetting how `phase: \"settling\"`\n// handles `msg.type: \"tab_stepped\"` — fails to compile. No nested switch, no\n// fall-through, no `_ -> (state, [])` default branch hiding regressions.\n//\n// Each cell receives the narrowed `State` for its phase and the narrowed\n// `Msg` for its variant via two `Extract` lookups. Phantom-narrow at the\n// type level; pure data at runtime.\n//\n// `defineMachine` accepts a third `update` form for `Transitions<S, M, C>` —\n// the runtime dispatches via `update[state.type][msg.type](state, msg)`.\n//\n// Strengthens invariant 2 (table form has no fall-through default),\n// invariant 6 (runtime walks the table predictably, no hidden dispatch\n// fallback), and invariant 7 (both state.type and msg.type are load-bearing\n// at the type level).\nexport type Transitions<S extends { type: string }, M extends { type: string }, C extends Cmd> = {\n [P in S[\"type\"]]: {\n [K in M[\"type\"]]: (\n state: Extract<S, { type: P }>,\n msg: Extract<M, { type: K }>,\n ) => readonly [S, readonly C[]];\n };\n};\n\n// === absurd: compile-time exhaustiveness assertion ===\n//\n// Use in default branches of switches over discriminated unions where every\n// case should be handled explicitly. TS narrows the operand to `never` only\n// if every variant has been covered upstream; adding a new variant produces\n// a compile error at the `absurd(x)` site.\n//\n// The substrate's `Reducer<S, M, C>` and `Transitions<S, M, C>` forms cover\n// the reducer-side exhaustiveness with mapped types — no `absurd` needed\n// there. This helper exists for the OTHER boundaries where a discriminated\n// union is narrowed by hand: Sub handlers mapping a wire-protocol\n// discriminant to flat Msg variants, message-bridge dispatchers, port\n// fanouts, etc. Without it, consumers redeclare the same 3-line helper\n// across files; with it, the substrate owns the one canonical version.\n//\n// Strengthens invariant 7 (identity is explicit).\n/**\n * Compile-time exhaustiveness assertion. Use in default branches of\n * switches over discriminated unions where every case should be handled\n * explicitly — TS narrows the type to `never` only if every variant has\n * been covered. Strengthens invariant 7 (identity is explicit).\n *\n * @example\n * switch (msg.type) {\n * case \"a\": return ...;\n * case \"b\": return ...;\n * default: return absurd(msg); // compile error if a \"c\" is added\n * }\n */\nexport function absurd(x: never): never {\n throw new Error(`unreachable: ${JSON.stringify(x)}`);\n}\n\n// === noop: the explicit \"this cell ignores this msg\" helper ===\n//\n// Elm's fallback for unhandled (state × msg) pairs is implicit:\n// `_ -> (model, Cmd.none)` in a case expression. We make it explicit because\n// implicit no-ops are silent regressions waiting to happen — a future Msg\n// variant that *should* be handled gets swallowed by the wildcard.\n//\n// Naming the no-op forces the author to *decide*: \"this cell does nothing\"\n// is a real decision, distinct from \"I forgot to write this cell.\" The\n// mapped type rejects the second case; `noop` names the first.\n//\n// Assignable to any Transitions cell regardless of S/M/C concrete types\n// (the type parameters are free) — drop it into any cell without per-type\n// casts.\nexport const noop = <S, M, C extends Cmd>(state: S, _msg: M): readonly [S, readonly C[]] => [\n state,\n [],\n];\n\n// === Cmd: namespace for conditional Cmd emission ===\n//\n// Two recurring frictions in reducer cells:\n//\n// 1. \"Emit cmd X only when condition Y holds.\"\n// Hand-rolled: `cond ? [{ type: \"x\", ... }] : []`\n// With Cmd: `Cmd.when(cond, { type: \"x\", ... })`\n//\n// 2. \"Emit cmd X carrying a value that may be undefined; if undefined,\n// emit nothing.\"\n// Hand-rolled: `value !== undefined ? [{ type: \"x\", value }] : []`\n// With Cmd: `Cmd.whenDefined(value, (v) => ({ type: \"x\", value: v }))`\n//\n// Both forms return `readonly C[]` so they spread cleanly into the cmds\n// array returned by a Transitions cell:\n//\n// return [next, [\n// ...Cmd.when(state.tabId !== undefined, { type: \"detach_debugger\", tabId: state.tabId }),\n// ...Cmd.whenDefined(state.queueItemId, (id) => ({\n// type: \"queue:complete\", queueItemId: id, status: \"done\",\n// })),\n// ]];\n//\n// `Cmd` as a value namespace coexists with `Cmd<T>` as a type — TypeScript's\n// declaration merging puts them in different name spaces (type vs. value).\n// The reader sees `Cmd<...>` in type position and `Cmd.when(...)` in\n// expression position; no ambiguity at use sites.\n//\n// Why a namespace and not top-level `when` / `whenDefined`: `when` at module\n// scope reads as a generic conditional, but these helpers are specifically\n// for *Cmd emission* (return `readonly C[]`, the cmds-array contract of\n// every Transitions cell). The namespace pins that intent at the call site.\nexport const Cmd = {\n /**\n * The empty Cmd array. Typed `readonly never[]` so it's assignable to any\n * `readonly C[]` for any `C extends Cmd`. Use in `init` returns and\n * Transitions cells that emit zero effects:\n *\n * init: (loaded) => [loaded ?? initial, Cmd.none],\n * tick: (state) => [state, Cmd.none],\n *\n * Elm's `Cmd.none` analogue. Cultural signal alongside the runtime help:\n * `[state, Cmd.none]` reads as intent (\"this transition emits nothing\"),\n * `[state, []]` reads as \"empty array of what\".\n *\n * Frozen at runtime so a downstream consumer can't `.push()` into the\n * shared reference.\n */\n none: Object.freeze([]) as readonly never[],\n\n /**\n * Flat concat of cmd arrays. Use when a cell composes effects from\n * multiple conditional sources:\n *\n * return [next, Cmd.batch(\n * Cmd.whenDefined(state.queueItemId, (id) => ({ type: \"complete\", id })),\n * Cmd.when(state.windowId !== undefined, { type: \"close_window\", windowId }),\n * [{ type: \"detach_debugger\", tabId: state.tabId }],\n * )];\n *\n * `<const C>` keeps inline cmd literals' discriminants narrow (same\n * rationale as `when` / `whenDefined` above).\n */\n batch: <const C extends Cmd>(...arrs: readonly (readonly C[])[]): readonly C[] => {\n const out: C[] = [];\n for (const arr of arrs) out.push(...arr);\n return out;\n },\n\n /**\n * Emit `cmd` wrapped in a single-element array iff `cond` is true.\n * Otherwise return the empty array. Spreads cleanly into the cmds array\n * returned by a Transitions cell.\n *\n * `const C` (TypeScript 5.0+) keeps the inferred `type:` discriminator\n * literal narrow when called with an object literal — so `Cmd.when(b,\n * { type: \"x\", ... })` infers as `{ type: \"x\", ... }`, not `{ type:\n * string, ... }`, and stays assignable to a discriminated-union arm\n * without `as const` at the call site.\n */\n when: <const C extends Cmd>(cond: boolean, cmd: C): readonly C[] => (cond ? [cmd] : []),\n\n /**\n * If `value` is defined, call `build(value)` and emit the resulting cmd\n * wrapped in a single-element array. If `value` is `undefined`, return\n * the empty array. `value` is narrowed to `T` inside `build`.\n *\n * `const C` on the return type keeps the callback's object-literal\n * `type:` field narrow (see `when` above for the full rationale).\n */\n whenDefined: <T, const C extends Cmd>(\n value: T | undefined,\n build: (value: T) => C,\n ): readonly C[] => (value !== undefined ? [build(value)] : []),\n} as const;\n\n// === SubId: branded string for Sub identity ===\n//\n// Subs are reconciled by id on every transition (canon §2.5, invariant 7).\n// Before branding, `Sub.id` was a raw `string` — a typo at one call site and a\n// different typo at another would silently produce two distinct subs that\n// looked identical to a human reader. The brand makes id construction explicit\n// (`subId(\"...\")`) and catches accidental string-where-SubId at the type\n// level.\n//\n// The brand is structural: `string & { __brand: \"SubId\" }`. The `subId(s)`\n// constructor is the ONE permitted cast in the substrate — every other call\n// site must go through it.\nexport type SubId = string & { readonly __brand: \"SubId\" };\n\n/**\n * Construct a `SubId` from a string. Use this everywhere a Sub literal is\n * built — including ids derived from runtime data (`subId(\\`ws:${auditId}\\`)`).\n * The brand is structural, not nominal, so dynamic ids are permitted; the\n * constructor's job is to make the identity decision explicit at every call\n * site so the type system can catch accidental raw-string drift.\n *\n * Strengthens invariant 7 (identity is explicit).\n */\nexport function subId(s: string): SubId {\n return s as SubId;\n}\n\n// === Sub: tagged-union, continuous source of msgs; stable id used for diff/reconcile ===\nexport type Sub<T extends string = string> = { id: SubId; type: T };\n\n// === Port<T>: typed escape hatch for \"data leaving the runtime\" ===\n//\n// A Port is a named, typed channel that Cmd handlers can `emit` to via the\n// augmented `ctx.emit(port, value)`. Subscribers attach via\n// `runtime.subscribePort(port, listener)` and receive every emitted value\n// synchronously (same fanout discipline as `runtime.observe`).\n//\n// Why Ports are a substrate primitive, distinct from State and Observe:\n// - **State** is \"the world as the program sees it.\" Folding outgoing\n// announcements into State turns ephemeral signals into persisted facts —\n// the classic anti-pattern that motivated this primitive.\n// - **Observe** sees every transition `(msg, state)`. That's the right channel\n// for devtools and logging, but it forces every consumer to filter the\n// entire firehose for a single signal.\n// - **Ports** are typed and selective: one channel per concept, subscribers\n// only see what the handler chose to emit. Mirrors Elm's outgoing-port\n// semantics — a typed declaration with `port` keyword whose values flow\n// out of the program to the host.\n//\n// Port identity is **by reference** — the returned object IS the identity.\n// Name is metadata for debugging. To prevent the \"two modules definePort the\n// same name expecting to share a channel, silently get distinct ports\" class\n// of bug, `definePort` asserts the name has not been seen in this process —\n// the symmetric runtime check to `SubId` (canon §2.12, invariant 7). Each\n// definePort call must use a unique name; if two modules need the same port,\n// one module exports it and the other imports it.\nexport interface Port<T> {\n readonly __brand: \"port\";\n readonly name: string;\n // Phantom field — never assigned, never read at runtime. Carries `T` for\n // inference at `ctx.emit` and `subscribePort` call sites. Using `?` so\n // `definePort` can produce a Port without constructing a value of `T`.\n readonly __t?: T;\n}\n\n// Module-level registry — process-scope, identity-by-name. The set is the\n// runtime witness that every port name in this process is unique. Cleared\n// only by the test-only `__resetPortRegistry()` helper.\nconst definedPortNames = new Set<string>();\n\n/**\n * Thrown by `definePort` when a name has already been registered in the\n * current process. Symmetric with the `Sub.id` collision assert in\n * `reconcileSubs` — both mechanize invariant 7 (identity is explicit) at the\n * runtime layer the type system cannot reach (string names compared at\n * runtime, not at compile time).\n */\nexport class PortNameCollisionError extends Error {\n override readonly name = \"PortNameCollisionError\";\n constructor(portName: string) {\n super(\n `definePort: a port named \"${portName}\" was already defined. ` +\n `Each definePort call must use a unique name. ` +\n `If two modules need the same port, export it from one module and import it.`,\n );\n }\n}\n\n/**\n * Test-only escape hatch — clears the module-level port-name registry so the\n * collision assert in `definePort` doesn't false-positive across vitest's\n * non-isolated runs. Production code MUST NOT call this; the registry is the\n * invariant-7 witness for the process lifetime.\n *\n * @internal test-only\n */\nexport function __resetPortRegistry(): void {\n definedPortNames.clear();\n}\n\n/**\n * Define a typed port. `name` is metadata (shown in devtools / debug logs);\n * port identity is by reference. Each definePort call must use a unique\n * `name` — a second call with the same name throws\n * `PortNameCollisionError`. If two modules need the same port, export it\n * from one module and import it from the other.\n *\n * Use ports for \"data leaving the runtime selectively\" — e.g. cursor\n * announcements, DOM-mutation broadcasts, telemetry events that should NOT be\n * folded into State and should NOT be observed by every transition listener.\n *\n * Strengthens invariant 7 (identity is explicit) — the runtime assert is the\n * Port-side counterpart of `SubId`'s id collision check in `reconcileSubs`.\n */\nexport function definePort<T>(name: string): Port<T> {\n // HMR skip — Vite / dev servers re-import the module on hot reload, which\n // would otherwise false-positive on every definePort across reloads. The\n // try/catch is load-bearing for service-worker bundles: Vite polyfills\n // `import.meta.url` via `document.baseURI`, which throws ReferenceError in\n // an MV3 service worker. A throw here would abort SW install (Chrome\n // surfaces it as \"registration failed status 15\"). Catching it = \"no HMR\n // detected\" = the collision check applies, which is the correct SW\n // behavior (SWs reload via chrome.runtime.reload, not module HMR).\n let isHmr = false;\n try {\n const meta = import.meta as { hot?: unknown };\n isHmr = meta?.hot !== undefined && meta?.hot !== null;\n } catch {\n isHmr = false;\n }\n if (!isHmr && definedPortNames.has(name)) {\n throw new PortNameCollisionError(name);\n }\n definedPortNames.add(name);\n return { __brand: \"port\", name } as Port<T>;\n}\n\n/**\n * Augmentation injected onto `ctx` inside Cmd handlers. Handlers receive\n * `ctx & PortEmitter` so they can call `ctx.emit(port, value)` synchronously.\n * Subscribers registered via `runtime.subscribePort(port, listener)` receive\n * the value immediately.\n *\n * Emitting to a port with no subscribers is a no-op (does not throw).\n */\nexport interface PortEmitter {\n emit<T>(port: Port<T>, value: T): void;\n}\n\n// === Interpret<M, C, Ctx>: record-of-handlers form of `interpret` ===\n//\n// Flat dispatch table keyed by `Cmd.type`. Each cell receives the narrowed Cmd\n// and the runtime-augmented Ctx (`Ctx & PortEmitter`) and resolves to a\n// follow-up Msg or `void` (fire-and-forget). The mapped type makes a missing\n// handler a compile error — `defineMachine` cannot accept the dictionary until\n// every Cmd variant has one.\n//\n// Hoisted out of `Machine.interpret` so consumers can type a free-standing\n// handler dictionary with `Interpret<MyMsg, MyCmd, MyCtx>` instead of\n// re-declaring the mapped type at every effects module.\n//\n// Strengthens invariant 2 (the record form has no fall-through default to\n// hide impurity behind) and invariant 7 (identity is explicit — the Cmd\n// variant set is load-bearing at the type level).\nexport type Interpret<M extends { type: string }, C extends Cmd, Ctx> = {\n [K in C[\"type\"]]: (cmd: Extract<C, { type: K }>, ctx: Ctx & PortEmitter) => Promise<M | void>;\n};\n\n// === Subscribe<M, U, Ctx>: record-of-handlers form of `subscribe` ===\n//\n// Flat dispatch table keyed by `Sub.type`. Each cell receives the narrowed\n// Sub, the Ctx, and a `dispatch` to fire follow-up Msgs. Returns a cleanup\n// function the substrate calls when the Sub is reconciled out (state\n// transitioned away). The mapped type guarantees every Sub variant has a\n// handler at the type level; runtime dispatch is a single property lookup.\n//\n// Hoisted out of `Machine.subscribe` so consumers can type a free-standing\n// handler dictionary with `Subscribe<MyMsg, MySub, MyCtx>` instead of\n// re-declaring the mapped type at every subs module.\n//\n// Strengthens invariant 7 (identity is explicit — the Sub variant set is\n// load-bearing at the type level).\nexport type Subscribe<M extends { type: string }, U extends Sub, Ctx> = {\n [K in U[\"type\"]]: (\n sub: Extract<U, { type: K }>,\n ctx: Ctx,\n dispatch: (msg: M) => void,\n ) => () => void;\n};\n\n// === Machine: pure data, host-agnostic ===\n//\n// `update` is stored as a union of the two record forms — the runtime branches\n// on shape at dispatch time. The public `defineMachine` overloads enforce one\n// shape per call so consumers never see the union themselves.\n//\n// - Reducer form: `Reducer<S, M, C>` — flat record keyed by Msg.type;\n// exhaustiveness enforced by the mapped type.\n// - Transitions form: `Transitions<S, M, C>` — 2D table keyed by\n// `state.type` then `msg.type`; only available when `S extends { type:\n// string }`. Conditional in the union so machines without a discriminated\n// State don't widen to include this branch.\n//\n// `M` is constrained to `{ type: string }` because both record forms require\n// a string discriminant.\n//\n// `interpret` is conditionally optional: when `C` is `Cmd<never>` the\n// interpret map is keyed by `never`, so the only valid value is `{}`. Forcing\n// every cmdless machine to write `interpret: {} as never` is ceremony for a\n// shape the type already pins. The tuple-wrap (`[C] extends [Cmd<never>]`)\n// disables distributive conditional so a real cmd union (e.g.\n// `Cmd<\"a\"> | Cmd<\"b\">`) doesn't degrade to optional just because one arm\n// happens to be `Cmd<never>`. Same trick the `update` field uses.\nexport type Machine<S, M extends { type: string }, C extends Cmd, U extends Sub, Ctx> = {\n /**\n * Boot the runtime. Called once by `run(...)` with whatever `Store.load()`\n * returned: `null` on fresh boot, the persisted state on rehydrate.\n *\n * **Contract:** when `loaded !== null`, init MUST return `[loaded, []]` —\n * no Cmds. Init's rehydrate branch is the migration / parse boundary, not\n * the boot-effect hook. See Invariant 2 in `.patterns/tea/tea-invariants.md`.\n *\n * Boot effects routes:\n * - Stateless infrastructure → host module top, outside TEA.\n * - State-conditional resume → a `boot` Msg the host dispatches once\n * after `run(...)` returns.\n *\n * Violations are caught at runtime by `replay` (which throws with a\n * pointer to the alternatives).\n */\n init: (loaded: S | null, ctx: Ctx) => readonly [S, readonly C[]];\n update:\n | Reducer<S, M, C>\n // Wrap in tuple to disable distributive conditional behavior. Without\n // `[S]`, TS distributes the conditional across `S`'s union members,\n // producing `Transitions<A,...> | Transitions<B,...>` instead of the\n // desired `Transitions<A|B,...>` — the table form needs a single\n // mapping over the full union of state types, not separate tables per\n // member.\n | ([S] extends [{ type: string }] ? Transitions<S, M, C> : never);\n subscriptions?: (state: S) => readonly U[];\n subscribe?: Subscribe<M, U, Ctx>;\n} & ([C] extends [Cmd<never>]\n ? { interpret?: Interpret<M, C, Ctx> }\n : { interpret: Interpret<M, C, Ctx> });\n\n// === Store: pluggable persistence adapter ===\n//\n// `load()` returns `unknown` because storage genuinely doesn't know `S` —\n// chrome.storage returns whatever JSON lives at the key, DurableObjectStorage\n// returns whatever bytes were serialized. Casting raw storage to `S` at the\n// adapter boundary is the exact `as S` move forbidden by invariant 8\n// everywhere else in the substrate. `load()` returning `unknown` makes the\n// substrate stop pretending.\n//\n// `migrate(raw)` is the boundary parse. Returns `S` on a recognized shape —\n// the substrate passes it to `init` as `loaded`. Returns `null` on any\n// unrecognized shape — the substrate passes `null` to `init`, fresh-boot\n// path. Must NOT throw: if a future shape is unrecognizable, returning\n// `null` boots clean from a known-good path. Throwing here would\n// indistinguishably collapse \"storage corruption\" and \"schema migration not\n// yet written\" — both are runtime decisions, not panics.\n//\n// Strengthens invariant 8 (the boundary parses; the core trusts) — the\n// substrate now enforces the parse instead of relying on every adapter\n// remembering to.\nexport interface Store<S> {\n load(): Promise<unknown>;\n save(state: S): Promise<void>;\n migrate(raw: unknown): S | null;\n}\n\n// === RuntimeRef<M>: typed sibling-runtime handle ===\n//\n// A `RuntimeRef<M>` exposes only the inbox of a Runtime — `dispatch(msg)`.\n// Use it as the field type when one runtime holds a reference to a *sibling*\n// runtime (composition by reduction across orthogonal lifecycles — invariant\n// 5). The holder learns the Msg shape it can send, nothing about the\n// referenced runtime's State, Cmd, Sub, Ctx, observers, listeners, or Port\n// fanout. That asymmetry is the point: cross-runtime coupling collapses to\n// \"I can put a Msg in your inbox\", which is the smallest coordination\n// surface that preserves typed cross-runtime calls.\n//\n// `Runtime<S, M>` extends `RuntimeRef<M>` structurally — every Runtime is\n// trivially a RuntimeRef. The narrowing happens at the *consumer's* Ctx\n// type, not via a runtime cast.\n//\n// Strengthens invariant 5 (composition by reduction — sibling runtimes\n// coordinate through the narrowest typed surface) and invariant 6 (runtime\n// is small and inspectable — what a holder *can* do to a sibling runtime is\n// exactly one method, not the full surface).\nexport interface RuntimeRef<M extends { type: string }> {\n dispatch(msg: M): Promise<void>;\n}\n\n// === Runtime: handle returned from run() ===\n//\n// `subscribe(listener)` is the React-shaped change notifier (zero-arg, paired\n// with `getState()` via `useSyncExternalStore`). `observe(observer)` is the\n// devtools-shaped trace hook: it receives `(msg, state)` for every completed\n// transition, including the boot transition where `msg` is `null`. Both fire\n// from the same point in the dispatch loop (after save → reconcile →\n// interpret), so what an observer sees is consistent with what a subscriber\n// sees via `getState()`.\n//\n// Observe is separate from subscribe because the contracts differ: React\n// consumers want \"something changed, re-read state\"; devtools consumers want\n// \"here is the exact (msg, state) pair, append it to a log\". Folding them\n// would force every React subscriber to type-check a `msg | null` arg they\n// will never use, and would couple `useSyncExternalStore` to a particular Msg\n// type at the substrate.\nexport interface Runtime<S, M extends { type: string }> extends RuntimeRef<M> {\n dispatch(msg: M): Promise<void>;\n getState(): S;\n subscribe(listener: () => void): () => void;\n observe(observer: (msg: M | null, state: S) => void): () => void;\n /**\n * Subscribe to a typed Port. Returns a cleanup function that removes the\n * listener. Multiple listeners per port are supported; fanout is synchronous\n * (same shape as `observe`) and isolated — one listener throw does not strand\n * the others.\n *\n * Ports are for \"data leaving the runtime selectively\" — see `definePort`.\n * Distinct from `observe` (every transition) and from State (the world).\n */\n subscribePort<T>(port: Port<T>, listener: (value: T) => void): () => void;\n /**\n * Emit a value on a Port from OUTSIDE an interpret handler. Same synchronous\n * fanout discipline as `ctx.emit` inside interpret — every subscriber\n * receives the value immediately; emitting to a port with no subscribers is\n * a no-op; listener throws are isolated.\n *\n * Use this for `observe`-driven Port emission: the canonical case is one\n * runtime publishing a state-derived signal (e.g. \"am I idle?\") that\n * another runtime subscribes to via `subscribePort`. Without\n * `runtime.emitPort`, the only way to emit a Port is from inside an\n * `interpret` handler — which forces the reducer to spawn a \"tell the\n * outside world\" Cmd on every transition that should signal. With it,\n * `runtime.observe((msg, state) => runtime.emitPort(port, derive(state)))`\n * keeps the reducer free of Port-plumbing Cmds and matches the \"every\n * transition is observable\" contract this invariant set already commits to.\n *\n * Strengthens invariant 6 (runtime is small and inspectable — Port emission\n * is no longer privileged to interpret handlers; `observe`-driven emission\n * is structurally supported).\n */\n emitPort<T>(port: Port<T>, value: T): void;\n /**\n * Resolves after boot completes — i.e. after `store.load()` (if any), `init`,\n * `store.save()` of the initial state, `reconcileSubs`, `interpret` of any\n * init-emitted cmds, and the initial listener / observer fanout. Rejects with\n * the boot error if boot fails (the same error every subsequent `dispatch`\n * call rejects with).\n *\n * Idempotent — subsequent reads return the same settled promise. The runtime\n * holds exactly one `ready` promise per `run()` call.\n *\n * Closes the v1 boot-await gap previously documented in `stepBootEffects`:\n * components that need synchronous initial state under a store (e.g.,\n * `useSyncExternalStore` consumers) can `await runtime.ready` instead of\n * `runtime.dispatch(noopMsg)`.\n *\n * Expresses canon §2.3 (the `init` contract — boot is a named, awaitable\n * moment). Strengthens invariant 6 (runtime is small and inspectable).\n */\n ready: Promise<void>;\n stop(): Promise<void>;\n}\n\n// === defineMachine: identity-typed pass-through ===\n//\n// Overloads enforce exactly one `update` shape per call so consumers see a\n// concrete type for `update`, not the substrate-internal union. Implementation\n// is identity at runtime — the runtime (`run`) branches on the structural\n// shape of `update` (Reducer vs Transitions) when dispatching.\n//\n// - Reducer-form overload: `update: Reducer<S, M, C>` — flat record keyed by\n// Msg.type. Exhaustiveness is enforced by the mapped type; adding a Msg\n// variant without a matching key is a compile error.\n// - Transitions-form overload: `update: Transitions<S, M, C>` — 2D table\n// keyed by State.type then Msg.type. Only available when `S extends {\n// type: string }`.\n//\n// Distinguishing the two: TS overload resolution checks the literal shape of\n// the argument. Reducer values are functions; Transitions values are records\n// (of functions). Structurally distinct, so overload resolution picks the\n// right branch.\n//\n// Strengthens invariant 2 (record forms have no fall-through default) and\n// invariant 7 (the Msg variant set — and, for Transitions, the State variant\n// set — is load-bearing at the type level).\n\n// Transitions-form overload — `update` is a 2D table keyed by State.type\n// then Msg.type. Declared FIRST (most specific): the value shape (record of\n// records of functions) is structurally distinct from Reducer (record of\n// functions), so TS overload resolution picks this branch only when the\n// literal matches the table shape.\n//\n// The S constraint is expressed via a conditional `S extends { type: string\n// } ? Transitions<S, M, C> : never` instead of a generic-parameter\n// constraint. The conditional approach keeps `S` unconstrained at the\n// overload signature, so `Parameters<typeof defineMachine<NonDiscriminatedS,\n// ...>>` (the public `satisfies` pattern) doesn't fail at the first\n// overload — it just skips to the next one because `update` becomes\n// `never`.\nexport function defineMachine<S, M extends { type: string }, C extends Cmd, U extends Sub, Ctx>(\n m: Omit<Machine<S, M, C, U, Ctx>, \"update\"> & {\n // `[S]` tuple-wrap disables distributive conditional behavior — see\n // the comment on `Machine.update` for why distribution is wrong here.\n update: [S] extends [{ type: string }] ? Transitions<S, M, C> : never;\n },\n): Machine<S, M, C, U, Ctx>;\n// Reducer-form overload — `update` is a flat record keyed by Msg.type.\n// Object literals where every property is a function match this shape.\nexport function defineMachine<S, M extends { type: string }, C extends Cmd, U extends Sub, Ctx>(\n m: Omit<Machine<S, M, C, U, Ctx>, \"update\"> & {\n update: Reducer<S, M, C>;\n },\n): Machine<S, M, C, U, Ctx>;\n// Implementation signature — accepts both via the union on `Machine.update`.\nexport function defineMachine<S, M extends { type: string }, C extends Cmd, U extends Sub, Ctx>(\n m: Machine<S, M, C, U, Ctx>,\n): Machine<S, M, C, U, Ctx> {\n return m;\n}\n\n// === run: implemented in task 2 ===\n//\n// Returns a `Runtime<S, M>` synchronously. Boot (`store?.load()` + `init`) runs\n// asynchronously as the FIRST entry on the serial dispatch tail; every public\n// runtime method (`dispatch`, `stop`, `getState`) awaits the boot phase\n// implicitly through the queue. Failures inside `store.load` propagate via the\n// boot promise — subsequent calls reject/throw with that error. There is no\n// \"synchronous load\" branch because `Store#load` is async by contract.\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. A throw in any\n// effect phase leaves the persisted state ahead of the host's belief about\n// what executed — the Railway discipline (`tryInterpret` in handlers) makes\n// that safe in practice.\nexport function run<S, M extends { type: string }, C extends Cmd, U extends Sub, Ctx>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: { ctx: Ctx; store?: Store<S> },\n): Runtime<S, M> {\n const { ctx, store } = opts;\n\n // Holders are intentionally late-initialized: `state` is set inside the boot\n // step, which runs as the head of the tail. `getState()` before boot throws.\n let state: S | undefined;\n let bootError: unknown = null;\n let stopped = false;\n\n const subRegistry = new Map<string, () => void>();\n const listeners = new Set<() => void>();\n // Observers receive (msg, state) for every completed transition. Boot fires\n // once with msg = null. Same throw-isolation contract as listeners.\n const observers = new Set<(msg: M | null, state: S) => void>();\n\n // Port subscriber registry. Keyed by Port reference (NOT name) — port\n // identity is nominal/by-reference per Elm semantics. Each port can have\n // many listeners; emissions fan out synchronously to all of them.\n // `Set<(value: unknown) => void>` is the storage shape; per-port type\n // safety is enforced at the `subscribePort` / `emit` call sites via the\n // phantom `__t` field on `Port<T>`.\n const portRegistry = new Map<Port<unknown>, Set<(value: unknown) => void>>();\n\n // The `emit` function injected onto ctx. Synchronous fanout — same shape as\n // `observe`. No-op when the port has no subscribers. Listener throws are\n // isolated + logged so one bad subscriber does not strand the others.\n function portEmit<T>(port: Port<T>, value: T): void {\n const subscribers = portRegistry.get(port as Port<unknown>);\n if (!subscribers || subscribers.size === 0) return;\n for (const listener of subscribers) {\n try {\n listener(value);\n } catch (err) {\n console.error(err);\n }\n }\n }\n\n // Augmented ctx passed to every Cmd handler. `ctx` is intentionally spread\n // into a fresh object so handlers receive a Ctx & PortEmitter without\n // mutating the caller's ctx (which may be shared across runtimes / tests).\n const augmentedCtx: Ctx & PortEmitter = { ...ctx, emit: portEmit };\n\n // === update dispatch ===\n //\n // The substrate accepts two `update` shapes (see `defineMachine` overloads):\n //\n // 1. Reducer: `{ [msg.type]: (state, msg) => [S, C[]] }`\n // 2. Transitions: `{ [state.type]: { [msg.type]: (state, msg) => [S, C[]] } }`\n //\n // Branch is structural: an object's first inspected handler tells us\n // flat-vs-nested — if `update[msg.type]` is itself a function it's a\n // Reducer; if it's an object whose value is a function it's a Transitions\n // table. No runtime tag added to the Machine — the form is detectable from\n // the value shape, and the type system guarantees `update` matches one of\n // the two shapes at the `defineMachine` boundary.\n //\n // The form is detected ONCE per `run()` call (here, at runtime setup) and\n // cached as `updateMode`. The per-dispatch path is then a single branch +\n // 1-or-2 property lookups — no per-call shape inspection.\n type CellFn = (state: S, msg: M) => readonly [S, readonly C[]];\n type ReducerRecord = Reducer<S, M, C>;\n type TransitionsTable = {\n [stateType: string]: { [msgType: string]: CellFn };\n };\n const updateForm = machine.update;\n type UpdateMode = \"reducer\" | \"transitions\";\n const updateMode: UpdateMode = (() => {\n // Inspect a single value to disambiguate. Both Reducer and Transitions\n // are non-null objects; their values differ: Reducer's values are\n // functions, Transitions's values are objects (of functions). Pick the\n // first own key — order doesn't matter because the record forms are\n // uniform (every cell has the same value shape).\n const firstKey = Object.keys(updateForm as object)[0];\n if (firstKey === undefined) {\n // Empty object literal: M is `never`. No msg can ever be dispatched,\n // so the mode is irrelevant — pick \"reducer\" arbitrarily. Pre-boot\n // this never executes because the runtime never dispatches `never`.\n return \"reducer\";\n }\n const firstValue = (updateForm as Record<string, unknown>)[firstKey];\n return typeof firstValue === \"function\" ? \"reducer\" : \"transitions\";\n })();\n function applyUpdate(state: S, msg: M): readonly [S, readonly C[]] {\n if (__DEV__) deepFreeze(state);\n\n let result: readonly [S, readonly C[]];\n if (updateMode === \"reducer\") {\n // Flat record dispatch. Mapped type (`Reducer<S, M, C>`) guarantees\n // every Msg variant has a handler at the type level; runtime is a\n // single property lookup with no fallback default branch.\n const record = updateForm as ReducerRecord;\n const handler = record[msg.type as M[\"type\"]];\n result = handler(state, msg as Extract<M, { type: M[\"type\"] }>);\n } else {\n // Transitions table dispatch. `state` is constrained to\n // `{ type: string }` by the `defineMachine` Transitions overload; the\n // table guarantees every (state.type × msg.type) cell exists at the\n // type level. Runtime is two property lookups + the call.\n const table = updateForm as unknown as TransitionsTable;\n const stateKey = (state as unknown as { type: string }).type;\n // The Transitions overload guarantees every (state.type × msg.type) cell\n // exists at the type level; the runtime lookup cannot miss.\n const handler = table[stateKey]![msg.type]!;\n result = handler(state, msg);\n }\n\n if (__DEV__) assertPureResult(result, msg.type);\n return result;\n }\n\n // The serial dispatch queue: every step is chained onto `tail`. New dispatch\n // calls (and re-entrant ones from interpret follow-ups) wait for the\n // previous step to fully resolve before running their reducer. This is the\n // single concurrency gate.\n let tail: Promise<void> = Promise.resolve();\n\n /**\n * Reconcile subscriptions against `state`. Called after every save.\n *\n * - Same id present in old + new → leave running.\n * - Removed id → call cleanup. Swallow cleanup throws + `console.error`.\n * - New id → call subscribe[type]; on success store cleanup. On throw, the\n * sub is NOT registered; throw is remembered and re-thrown after the loop\n * so all other new subs still register.\n */\n function reconcileSubs(): void {\n if (!machine.subscriptions) return;\n const desired = machine.subscriptions(state as S);\n const desiredIds = new Set<string>();\n\n // Collision assert: within a single desired set, two subs sharing an id\n // but declaring different types is the silent class of bug that Elm's\n // sub diffing relies on the user to avoid — we catch it explicitly here.\n // (Same id + same type within the desired set is a redundant declaration\n // — harmless, and the `subRegistry.has` check below dedupes it on the\n // start path. Same id across transitions is the no-churn case the\n // reconcile pass is designed for and MUST NOT throw.)\n const desiredTypeById = new Map<string, string>();\n for (const sub of desired) {\n const existing = desiredTypeById.get(sub.id);\n if (existing !== undefined && existing !== sub.type) {\n throw new Error(\n `@demlik/tea: Sub.id collision: id=\"${sub.id}\" declared as type=\"${existing}\" and type=\"${sub.type}\"`,\n );\n }\n desiredTypeById.set(sub.id, sub.type);\n }\n\n // Removals first — anything in the registry not in `desired` should stop.\n for (const sub of desired) desiredIds.add(sub.id);\n for (const [id, cleanup] of subRegistry) {\n if (!desiredIds.has(id)) {\n try {\n cleanup();\n } catch (err) {\n console.error(err);\n }\n subRegistry.delete(id);\n }\n }\n\n // Additions — anything in `desired` not in the registry should start.\n let firstStartError: unknown = null;\n for (const sub of desired) {\n if (subRegistry.has(sub.id)) continue;\n const handler = machine.subscribe?.[sub.type as U[\"type\"]];\n if (!handler) {\n // No handler for this sub type — programmer error; skip. (TS would\n // normally forbid this at compile time when `subscribe` is provided\n // and U has variants, but a runtime guard is cheap.)\n continue;\n }\n try {\n const cleanup = handler(sub as Extract<U, { type: U[\"type\"] }>, ctx, enqueueDispatch);\n subRegistry.set(sub.id, cleanup);\n } catch (err) {\n if (firstStartError === null) firstStartError = err;\n // Do NOT register; continue to next sub so other starts still run.\n }\n }\n if (firstStartError !== null) throw firstStartError;\n }\n\n // `interpret` is optional at the type level when `C extends Cmd<never>`\n // (see `Machine<...>`). The runtime safely defaults a missing map to `{}`\n // because such a machine never emits cmds — the cmd loop below never\n // dereferences a handler. The defensive default also guards against a\n // miswired non-Cmd<never> consumer reaching this code path; the per-cmd\n // `if (!handler) continue` then preserves invariant-6 forward progress\n // instead of throwing on `undefined.foo`.\n type InterpretMap = {\n [K in C[\"type\"]]: (cmd: Extract<C, { type: K }>, ctx: Ctx & PortEmitter) => Promise<M | void>;\n };\n const interpretMap: InterpretMap =\n (machine as { interpret?: InterpretMap }).interpret ?? ({} as InterpretMap);\n\n /**\n * Run `interpret` for each emitted cmd. Each handler may return a follow-up\n * Msg — that Msg is enqueued onto the tail (NOT dispatched re-entrantly\n * inside the current transition).\n *\n * Throws propagate to the caller of `step`. The first error stops further\n * handlers in this transition.\n */\n async function runInterpret(cmds: readonly C[]): Promise<void> {\n for (const cmd of cmds) {\n const handler = interpretMap[cmd.type as C[\"type\"]];\n if (!handler) continue;\n const follow = await handler(cmd as Extract<C, { type: C[\"type\"] }>, augmentedCtx);\n if (follow !== undefined && follow !== null) {\n // Schedule follow-up Msg on the tail. The current step resolves\n // first; the follow-up runs after, as a fresh transition. Swallow the\n // returned rejection — fire-and-forget at this site; the runtime\n // never re-throws follow-up errors at the dispatcher of the original\n // cmd. If you need observable error handling, name a failure Msg via\n // `tryInterpret` (Railway).\n enqueueDispatch(follow as M).catch(() => {});\n }\n }\n }\n\n /**\n * Fire every external listener. Listener throws are isolated — one bad\n * listener does not strand the others. This is the dual of cleanup\n * semantics: the runtime guarantees forward progress for unrelated\n * observers.\n */\n function fireListeners(): void {\n for (const listener of listeners) {\n try {\n listener();\n } catch (err) {\n console.error(err);\n }\n }\n }\n\n /**\n * Fire every observer with the just-applied msg + post-transition state.\n * Same throw-isolation contract as `fireListeners`. Called immediately\n * after `fireListeners` so observers see exactly what subscribers see.\n */\n function fireObservers(msg: M | null): void {\n if (observers.size === 0 || state === undefined) return;\n const snapshot = state;\n for (const observer of observers) {\n try {\n observer(msg, snapshot);\n } catch (err) {\n console.error(err);\n }\n }\n }\n\n /**\n * One full transition: update → save → reconcile subs → interpret → fire\n * listeners. Save-before-effects is the hard ordering; tests pin it.\n *\n * Throws from `update` propagate immediately (state unchanged). Throws from\n * `save` propagate after the in-memory `state` is advanced (PRD row: \"state\n * advanced; persisted state did not\"). Throws from reconcile or interpret\n * propagate after `save` has succeeded (PRD row: \"state already saved\").\n */\n async function stepDispatch(msg: M): Promise<void> {\n if (state === undefined) {\n // This should never happen — boot always runs before any dispatch.\n throw new Error(\"@demlik/tea: runtime not booted\");\n }\n const [next, cmds] = applyUpdate(state, msg);\n state = next;\n if (store) await store.save(state);\n // Subscriptions reconcile against the new state; throws propagate AFTER\n // the entire diff pass completes (so other subs still register / clean\n // up correctly). Cleanup throws are swallowed inside reconcileSubs.\n reconcileSubs();\n await runInterpret(cmds);\n fireListeners();\n fireObservers(msg);\n }\n\n /**\n * Boot is split into two phases.\n *\n * - **Synchronous (`run()` call):** when `store` is absent, we know `loaded\n * = null` without an await — run `init(null, ctx)` IMMEDIATELY so that\n * `getState()` is observable synchronously. This is what\n * `useSyncExternalStore` consumers (e.g. `@demlik/tea/react`) need to render\n * on the first commit without an undefined-state flicker.\n * - **Asynchronous (`stepBootEffects` on the tail):** save + sub reconcile\n * + interpret-of-init-cmds + listener fire. These run as the first tail\n * entry; subsequent dispatches queue behind them.\n *\n * When `store` IS present we cannot do `init` synchronously (we'd be\n * inventing a loaded value), so we keep the full async path. Components\n * that want synchronous initial state in that case should omit the store\n * for the React mount and persist via a different mechanism (or accept the\n * default-snapshot story in a future v2).\n *\n * `store.load` throws propagate via the boot promise; subsequent calls\n * reject with the load error. (The PRD row \"run() throws synchronously\" is\n * imprecise relative to Store#load being async — we honor the intent: no\n * usable runtime is ever observable on load failure.)\n */\n // Synchronous init when there is NO store: `getState()` is observable\n // synchronously by `useSyncExternalStore` consumers. When a store IS\n // present, `init` must run AFTER `store.load()` (the contract: `init`\n // receives the loaded snapshot), and callers must await boot before\n // calling `getState()` — they do so implicitly via the first `dispatch()`\n // (which queues behind the boot step).\n //\n // The asymmetry is the substrate being honest about a real constraint: a\n // store is, by definition, an async boundary. Components that need\n // synchronous initial state under a store should await the runtime's boot\n // before mounting — `await runtime.ready` (not provided in v1; use `await\n // runtime.dispatch(noopMsg)` if needed, or a parent-level loading state).\n let pendingInitCmds: readonly C[] = [];\n if (!store) {\n const [initialState, initCmds] = machine.init(null, ctx);\n state = initialState;\n pendingInitCmds = initCmds;\n }\n\n async function stepBootEffects(): Promise<void> {\n if (store) {\n // Boundary parse (invariant 8). `store.load()` returns `unknown`\n // because storage doesn't know `S`; `store.migrate(raw)` is the\n // substrate's required parse — `S` on recognized shape, `null` on\n // unrecognized (which boots fresh, a known-good path). `migrate` MUST\n // NOT throw per its contract; if it does we surface the error via the\n // boot promise (same as a `load` throw — `runtime.ready` rejects and\n // every subsequent `dispatch` surfaces `bootError`).\n const raw = await store.load();\n const parsed = store.migrate(raw);\n const [initialState, initCmds] = machine.init(parsed, ctx);\n state = initialState;\n pendingInitCmds = initCmds;\n await store.save(state);\n }\n reconcileSubs();\n await runInterpret(pendingInitCmds);\n fireListeners();\n fireObservers(null);\n }\n\n /**\n * Enqueue a dispatch on the tail. Re-entrant calls from interpret or\n * subscribe handlers go through this same gate — guaranteeing serial\n * execution across the entire system.\n *\n * `dispatch` rejects when:\n * - the runtime is stopped (next dispatch always rejects after stop);\n * - boot failed (every dispatch surfaces the boot error);\n * - the reducer, save, sub start, or interpret throws.\n */\n function enqueueDispatch(msg: M): Promise<void> {\n if (stopped) return Promise.reject(new Error(\"@demlik/tea: runtime stopped\"));\n const next = tail.then(() => {\n if (bootError !== null) throw bootError;\n return stepDispatch(msg);\n });\n // Swallow the rejection on the tail so a single failing dispatch does NOT\n // poison every subsequent one. The original `next` promise still rejects\n // for the dispatch caller.\n tail = next.catch(() => {});\n return next;\n }\n\n // Kick off boot-effects immediately. Boot rejections are remembered on\n // `bootError`; they surface on every subsequent dispatch call. Note: when\n // `store` is absent the synchronous-init step above already set `state` —\n // `stepBootEffects` only runs save/sub/interpret/listener-fire. When\n // `store` is present, the full async path runs here.\n //\n // `bootPromise` is the original (un-swallowed) promise — it is what\n // `runtime.ready` exposes so callers see the boot error directly. `tail`\n // gets the swallowed branch so a boot failure does NOT poison every\n // subsequent dispatch's `.then` chain (each dispatch surfaces `bootError`\n // explicitly inside `enqueueDispatch`).\n const bootPromise = stepBootEffects();\n tail = bootPromise.catch((err) => {\n bootError = err;\n });\n\n const runtime: Runtime<S, M> = {\n dispatch: enqueueDispatch,\n getState(): S {\n if (bootError !== null) throw bootError;\n // Synchronous-init path (no store): `state` is set inside `run()`\n // before this getter can ever be called → returns immediately. Resumed\n // path (store present): `state` becomes defined after\n // `stepBootEffects` awaits `store.load()`; calls before that throw.\n // The `booted` flag is no longer a precondition — `state !==\n // undefined` is the single source of truth that init information is\n // available.\n if (state === undefined) {\n throw new Error(\"@demlik/tea: getState() called before runtime booted\");\n }\n return state;\n },\n subscribe(listener: () => void): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n observe(observer: (msg: M | null, state: S) => void): () => void {\n observers.add(observer);\n return () => {\n observers.delete(observer);\n };\n },\n subscribePort<T>(port: Port<T>, listener: (value: T) => void): () => void {\n const key = port as Port<unknown>;\n let bucket = portRegistry.get(key);\n if (!bucket) {\n bucket = new Set<(value: unknown) => void>();\n portRegistry.set(key, bucket);\n }\n // The bucket stores `(value: unknown) => void` to keep the registry\n // monomorphic; per-port type safety lives at this call site (the\n // listener's `T` is bound to the port's phantom `__t`).\n const erased = listener as (value: unknown) => void;\n bucket.add(erased);\n return () => {\n const set = portRegistry.get(key);\n if (!set) return;\n set.delete(erased);\n if (set.size === 0) portRegistry.delete(key);\n };\n },\n emitPort<T>(port: Port<T>, value: T): void {\n // Same closure as `ctx.emit` inside interpret — synchronous fanout,\n // no-op on no subscribers, listener-throw isolation. Exposing it on\n // the Runtime interface lets `observe`-driven Port emission work\n // without spawning a \"tell the outside world\" Cmd per transition.\n portEmit(port, value);\n },\n // `ready` is the original (un-swallowed) boot promise. Idempotency\n // comes for free — promises settle exactly once; every read returns the\n // same settled value. See the boot-promise note above for why this is\n // distinct from `tail`.\n ready: bootPromise,\n async stop(): Promise<void> {\n stopped = true;\n // Drain whatever is in flight before tearing down. Any rejections in\n // the tail were already swallowed at enqueue time, so this await\n // always resolves.\n await tail;\n // Run every active sub cleanup. Throws are swallowed + logged so one\n // bad cleanup does not strand the others.\n for (const [id, cleanup] of subRegistry) {\n try {\n cleanup();\n } catch (err) {\n console.error(err);\n }\n subRegistry.delete(id);\n }\n // Flush final state. A save throw here is logged but does not reject\n // `stop()` — the contract is \"returns a resolved Promise\".\n if (store && state !== undefined && bootError === null) {\n try {\n await store.save(state);\n } catch (err) {\n console.error(err);\n }\n }\n },\n };\n\n return runtime;\n}\n\n// === historyTracker: composable observability helper ===\n//\n// A bounded ring buffer of recent `(msg, state)` transitions, built on the\n// Runtime's public `observe` API. The substrate itself stays minimal — it\n// has no notion of history, no opt to enable one, no method on the Runtime\n// interface to read one. Anything that wants \"recent transitions\" composes\n// via this helper. The substrate provides the primitive (`observe`); this\n// is the bookkeeping that builds on it.\n//\n// Use cases:\n// - Bridge backlog replay for late subscribers (see `@demlik/tea/extension`).\n// - Debug inspectors showing recent traffic on attach.\n// - Sentry breadcrumb collection.\n// - Property/contract tests that want to assert \"these N transitions\n// happened in this order.\"\n//\n// Discipline:\n// - One-line creation: `const tr = historyTracker(runtime, 100);`\n// - Snapshot at any time: `tr.snapshot()` — shallow copy, oldest first.\n// - Detach when done: `tr.stop()` — unsubscribes the observer; the buffer\n// freezes at its current contents (snapshot() still works, useful for\n// post-hoc inspection in tests).\n//\n// Why not put this on the Runtime interface as `runtime.history()`?\n// Because history is derivable. Putting it on the Runtime privileges one\n// use case (debug inspectors) at the substrate level and grows the\n// interface for everyone. The composition shape stays out of the dispatch\n// loop entirely — the substrate's hot path doesn't pay any cost for a\n// feature it doesn't need to provide.\n// =============================================================================\n\nexport interface HistoryTracker<S, M extends { type: string }> {\n /**\n * Snapshot of recorded transitions, oldest first. Each entry is\n * `{ msg, state }` exactly as the underlying `observe` callback received\n * it — `msg` is `null` for the boot transition, a Msg for every other.\n *\n * Returns a shallow copy: the array is fresh on every call (callers may\n * iterate, slice, or replay without affecting the tracker's buffer).\n * Entry values are references to the originals (TEA states/msgs are\n * conventionally immutable; do not mutate them if your S/M is not).\n */\n snapshot(): readonly { readonly msg: M | null; readonly state: S }[];\n /**\n * Detach the underlying observer. After `stop()`:\n * - No new transitions are recorded.\n * - `snapshot()` continues to work and returns the buffer's contents\n * at the moment of stop (useful for post-hoc inspection in tests).\n * - The tracker holds no references that would prevent GC of the\n * `runtime` argument other than the entries already buffered.\n *\n * Idempotent — subsequent calls are no-ops.\n */\n stop(): void;\n}\n\n/**\n * Create a bounded history tracker over a Runtime. The tracker subscribes\n * to the runtime via `observe(...)` and retains the last `size` `(msg,\n * state)` transitions in a FIFO ring buffer.\n *\n * @param runtime Any `Runtime<S, M>`. The tracker uses only `observe`.\n * @param size Buffer cap. `size <= 0` produces a tracker whose\n * snapshot is always `[]` (the observer is still attached\n * but pushes a no-op; cheaper to skip the tracker entirely\n * in that case).\n *\n * Memory cost: O(size × avg(msg + state size)). For audit-extension's\n * inspector use case (size 100, audit state ~5KB), ~500KB max — held\n * in-memory only, no persistence.\n */\nexport function historyTracker<S, M extends { type: string }>(\n runtime: Runtime<S, M>,\n size: number,\n): HistoryTracker<S, M> {\n const buffer: { msg: M | null; state: S }[] = [];\n let stopped = false;\n\n const unobserve =\n size <= 0\n ? (): void => {}\n : runtime.observe((msg, state) => {\n buffer.push({ msg, state });\n if (buffer.length > size) buffer.shift();\n });\n\n return {\n snapshot() {\n // Shallow copy — callers may freely iterate or mutate the returned\n // array. Entry values are references; see HistoryTracker docs.\n return buffer.slice();\n },\n stop() {\n if (stopped) return;\n stopped = true;\n unobserve();\n },\n };\n}\n\n// === replay: pure unit-test helper ===\n//\n// Composes `init(loaded ?? null, ctx)` then `update(state, msg)` for each msg.\n// Returns the final state plus the cmds that *would* have been emitted and the\n// subs that *would* have been desired at the final state.\n//\n// Crucially:\n// - does NOT call any `interpret[type]` handler\n// - does NOT touch `Store`\n// - does NOT start any subscription via `machine.subscribe?.[type]`\n//\n// `subscriptions` (the description function) IS called to derive the returned\n// `subs` array. That's intentional — it lets tests assert what would be wired\n// up without actually wiring it.\nexport function replay<S, M extends { type: string }, C extends Cmd, U extends Sub, Ctx>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: { msgs: readonly M[]; ctx: Ctx; loaded?: S | null },\n): { state: S; cmds: C[]; subs: U[] } {\n // `init` is `(loaded: S | null, ctx) => ...`; coerce undefined → null so the\n // AC \"`replay` with `loaded: undefined` calls `init(null, ctx)`\" holds.\n const loaded = opts.loaded ?? null;\n const [initialState, initCmds] = machine.init(loaded, opts.ctx);\n\n if (loaded !== null && initCmds.length > 0) {\n throw new Error(\n \"TEA contract violation: machine.init must return [state, []] (no Cmds) \" +\n \"when `loaded` is non-null. Init's rehydrate branch is a pure state \" +\n \"passthrough — the migration/parse boundary, not the boot-effect hook.\\n\\n\" +\n \"Routes for boot effects:\\n\" +\n \" - Stateless infrastructure (e.g. chrome.alarms ensure) → host module \" +\n \"top level, outside TEA.\\n\" +\n \" - State-conditional resume (e.g. re-attach a session) → a `boot` Msg \" +\n \"dispatched once from the host after `run(...)` returns, handled in update.\\n\\n\" +\n \"See .patterns/tea/tea-invariants.md (Invariant 2).\",\n );\n }\n\n let state: S = initialState;\n const cmds: C[] = [...initCmds];\n\n // Mirror the same form-branching the runtime does (see `applyUpdate` in\n // `run`). Detected once; reused per-msg. Keeping it inline (not factored\n // into a shared helper) so `replay` stays standalone — it is the pure\n // unit-test tool and must not depend on `run`'s internal closures.\n type CellFn = (state: S, msg: M) => readonly [S, readonly C[]];\n type ReducerRecord = Reducer<S, M, C>;\n type TransitionsTable = { [stateType: string]: { [msgType: string]: CellFn } };\n const updateForm = machine.update;\n type UpdateMode = \"reducer\" | \"transitions\";\n const updateMode: UpdateMode = (() => {\n const firstKey = Object.keys(updateForm as object)[0];\n if (firstKey === undefined) return \"reducer\";\n const firstValue = (updateForm as Record<string, unknown>)[firstKey];\n return typeof firstValue === \"function\" ? \"reducer\" : \"transitions\";\n })();\n\n for (const msg of opts.msgs) {\n if (__DEV__) deepFreeze(state);\n\n let result: readonly [S, readonly C[]];\n if (updateMode === \"reducer\") {\n result = (updateForm as ReducerRecord)[msg.type as M[\"type\"]](\n state,\n msg as Extract<M, { type: M[\"type\"] }>,\n );\n } else {\n const table = updateForm as unknown as TransitionsTable;\n const stateKey = (state as unknown as { type: string }).type;\n result = table[stateKey]![msg.type]!(state, msg);\n }\n\n if (__DEV__) assertPureResult(result, msg.type);\n\n const [next, emitted] = result;\n state = next;\n cmds.push(...emitted);\n }\n\n const subs: U[] = machine.subscriptions ? [...machine.subscriptions(state)] : [];\n\n return { state, cmds, subs };\n}\n\n// === tryInterpret: Railway sugar over `Result.tryPromise` ===\n//\n// Wraps a fallible `(cmd, ctx) => Promise<Ok>` and returns a handler suitable\n// for `interpret[type]`. On success the returned function resolves with\n// `onOk(value, cmd)`; on rejection it resolves with `onErr(error, cmd)`. It\n// NEVER rejects (assuming `onOk`/`onErr` are total — that's the caller's\n// contract).\n//\n// Implementation note: we use the `{try, catch: (e) => e}` form of\n// `Result.tryPromise` rather than the one-arg `(thunk)` form so the original\n// error passes through untouched. The one-arg form wraps errors in\n// `UnhandledException`, which would prevent callers from `instanceof`-checking\n// their own error types inside `onErr`.\n//\n// M is a Msg type (not a Cmd) — the helper routes Ok/Err to two Msg constructors.\nexport function tryInterpret<C extends Cmd, Ok, M, Ctx>(\n work: (cmd: C, ctx: Ctx) => Promise<Ok>,\n onOk: (value: Ok, cmd: C) => M,\n onErr: (error: unknown, cmd: C) => M,\n): (cmd: C, ctx: Ctx) => Promise<M> {\n return async (cmd, ctx) => {\n const result = await Result.tryPromise({\n try: () => work(cmd, ctx),\n catch: (error: unknown): unknown => error,\n });\n return result.match({\n ok: (value) => onOk(value, cmd),\n err: (error) => onErr(error, cmd),\n });\n };\n}\n"]}
|