@demlik/tea 0.21.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/dist/agent/index.d.ts +32 -19
  2. package/dist/agent/index.js +12 -10
  3. package/dist/{chunk-VM7PMFV5.js → chunk-2A5XTKQQ.js} +142 -19
  4. package/dist/chunk-2A5XTKQQ.js.map +1 -0
  5. package/dist/{chunk-EB5OMOXX.js → chunk-7K7SEYCH.js} +16 -15
  6. package/dist/chunk-7K7SEYCH.js.map +1 -0
  7. package/dist/{chunk-GAVLND5C.js → chunk-A3QPMX5M.js} +17 -10
  8. package/dist/chunk-A3QPMX5M.js.map +1 -0
  9. package/dist/{chunk-VHKDUBXC.js → chunk-BVFS3ICC.js} +3 -3
  10. package/dist/{chunk-VHKDUBXC.js.map → chunk-BVFS3ICC.js.map} +1 -1
  11. package/dist/{chunk-4I2LCQKB.js → chunk-CPE5ZQXQ.js} +3 -3
  12. package/dist/{chunk-4I2LCQKB.js.map → chunk-CPE5ZQXQ.js.map} +1 -1
  13. package/dist/chunk-DEAPNMKO.js +11 -0
  14. package/dist/chunk-DEAPNMKO.js.map +1 -0
  15. package/dist/{chunk-BFSZ4L6Y.js → chunk-ETRNSJCR.js} +4 -4
  16. package/dist/{chunk-BFSZ4L6Y.js.map → chunk-ETRNSJCR.js.map} +1 -1
  17. package/dist/chunk-FXWDDNGD.js +98 -0
  18. package/dist/chunk-FXWDDNGD.js.map +1 -0
  19. package/dist/chunk-GFGOQIEC.js +10 -0
  20. package/dist/chunk-GFGOQIEC.js.map +1 -0
  21. package/dist/{chunk-YDF6P74H.js → chunk-IAHEYPYJ.js} +3 -3
  22. package/dist/{chunk-YDF6P74H.js.map → chunk-IAHEYPYJ.js.map} +1 -1
  23. package/dist/{chunk-YCJPLPQQ.js → chunk-J5JNLXO7.js} +3 -3
  24. package/dist/{chunk-YCJPLPQQ.js.map → chunk-J5JNLXO7.js.map} +1 -1
  25. package/dist/{chunk-MBQR3LFW.js → chunk-NFOGYDRS.js} +5 -5
  26. package/dist/{chunk-MBQR3LFW.js.map → chunk-NFOGYDRS.js.map} +1 -1
  27. package/dist/{chunk-Z7IL735U.js → chunk-R2YNDRGL.js} +3 -3
  28. package/dist/{chunk-Z7IL735U.js.map → chunk-R2YNDRGL.js.map} +1 -1
  29. package/dist/{chunk-ZV4WGW74.js → chunk-R5NXOPL2.js} +3 -3
  30. package/dist/{chunk-ZV4WGW74.js.map → chunk-R5NXOPL2.js.map} +1 -1
  31. package/dist/chunk-RMBZ7YH7.js +52 -0
  32. package/dist/chunk-RMBZ7YH7.js.map +1 -0
  33. package/dist/{chunk-HU3Y2AZY.js → chunk-SHXUSTHM.js} +62 -15
  34. package/dist/chunk-SHXUSTHM.js.map +1 -0
  35. package/dist/{chunk-QIE4Y6BK.js → chunk-UGTC33VH.js} +4 -4
  36. package/dist/{chunk-QIE4Y6BK.js.map → chunk-UGTC33VH.js.map} +1 -1
  37. package/dist/{chunk-6NIQIUGR.js → chunk-VH5MYJ2K.js} +4 -4
  38. package/dist/{chunk-6NIQIUGR.js.map → chunk-VH5MYJ2K.js.map} +1 -1
  39. package/dist/{chunk-W6XWI6QE.js → chunk-WZEDIUWR.js} +8 -7
  40. package/dist/chunk-WZEDIUWR.js.map +1 -0
  41. package/dist/{chunk-TMYUDZLE.js → chunk-ZRRET3RP.js} +11 -11
  42. package/dist/chunk-ZRRET3RP.js.map +1 -0
  43. package/dist/{core-BKJW1Kmq.d.ts → core-DNDAEhO2.d.ts} +7 -10
  44. package/dist/devtools/index.d.ts +4 -4
  45. package/dist/do/index.d.ts +98 -35
  46. package/dist/do/index.js +40 -21
  47. package/dist/do/index.js.map +1 -1
  48. package/dist/{drive-loop-Dc6Z8Vbs.d.ts → drive-loop-DlOEv49c.d.ts} +2 -2
  49. package/dist/{durable-effects-CgrLNccA.d.ts → durable-effects-wLWEdy9H.d.ts} +1 -1
  50. package/dist/effect/index.d.ts +52 -26
  51. package/dist/effect/index.js +7 -31
  52. package/dist/effect/index.js.map +1 -1
  53. package/dist/extension/index.d.ts +3 -3
  54. package/dist/extension/index.js +2 -2
  55. package/dist/flow/index.d.ts +13 -13
  56. package/dist/flow/index.js +13 -11
  57. package/dist/flow/index.js.map +1 -1
  58. package/dist/idempotency/index.d.ts +3 -3
  59. package/dist/idempotency/index.js +3 -3
  60. package/dist/{index-1hit3dJ9.d.ts → index-9P1PaXIV.d.ts} +2 -2
  61. package/dist/{index-Cuy1_xTh.d.ts → index-BBLI2WWN.d.ts} +1 -1
  62. package/dist/{index-Z6Uw7C9t.d.ts → index-C1WhBZ6a.d.ts} +4 -5
  63. package/dist/{index-B-VC4qjx.d.ts → index-C92SgNWz.d.ts} +2 -2
  64. package/dist/{index-CYrUWi1c.d.ts → index-CAsUE_d1.d.ts} +4 -4
  65. package/dist/{index-BjwRcjBi.d.ts → index-CO34esWT.d.ts} +2 -2
  66. package/dist/{index-DB8Yn0-K.d.ts → index-COeX9Bmz.d.ts} +11 -9
  67. package/dist/{index-Diyo8WBO.d.ts → index-Cg0QXnzf.d.ts} +2 -2
  68. package/dist/index.d.ts +5 -5
  69. package/dist/index.js +2 -2
  70. package/dist/jev/index.d.ts +8 -8
  71. package/dist/jev/index.js +5 -5
  72. package/dist/laws-DTtCDDlU.d.ts +135 -0
  73. package/dist/loop-DReKGV1X.d.ts +70 -0
  74. package/dist/{machine-BuPTS1QH.d.ts → machine-C0WhOAyB.d.ts} +5 -5
  75. package/dist/machine-viz/index.d.ts +1 -1
  76. package/dist/machine-viz/index.js +2 -2
  77. package/dist/mem/index.d.ts +4 -3
  78. package/dist/mem/index.js +2 -2
  79. package/dist/mem/index.js.map +1 -1
  80. package/dist/node/index.d.ts +57 -9
  81. package/dist/node/index.js +175 -68
  82. package/dist/node/index.js.map +1 -1
  83. package/dist/otel/index.d.ts +28 -12
  84. package/dist/otel/index.js +25 -21
  85. package/dist/otel/index.js.map +1 -1
  86. package/dist/paginate/index.d.ts +4 -4
  87. package/dist/paginate/index.js +5 -5
  88. package/dist/parity/index.d.ts +11 -15
  89. package/dist/parity/index.js +4 -9
  90. package/dist/parity/index.js.map +1 -1
  91. package/dist/pbt/index.d.ts +279 -67
  92. package/dist/pbt/index.js +299 -58
  93. package/dist/pbt/index.js.map +1 -1
  94. package/dist/persistence/index.d.ts +10 -12
  95. package/dist/persistence/index.js +4 -4
  96. package/dist/promise/index.d.ts +95 -13
  97. package/dist/promise/index.js +6 -4
  98. package/dist/react/index.d.ts +34 -22
  99. package/dist/react/index.js +271 -42
  100. package/dist/react/index.js.map +1 -1
  101. package/dist/resilience/index.d.ts +6 -6
  102. package/dist/resilience/index.js +5 -5
  103. package/dist/{run-D0sxbyno.d.ts → run-Cnw4qXj2.d.ts} +86 -17
  104. package/dist/{runtime-types-BPKp2bGo.d.ts → runtime-types-CP-ykwgf.d.ts} +120 -31
  105. package/dist/testing/effect/index.d.ts +162 -9
  106. package/dist/testing/effect/index.js +110 -10
  107. package/dist/testing/effect/index.js.map +1 -1
  108. package/dist/testing/index.d.ts +6 -8
  109. package/dist/testing/index.js +4 -24
  110. package/dist/testing/index.js.map +1 -1
  111. package/dist/testing/promise/index.d.ts +4 -4
  112. package/dist/testing/promise/index.js +5 -5
  113. package/dist/timing/index.d.ts +4 -4
  114. package/dist/timing/index.js +4 -4
  115. package/dist/{types-BiXj7k_k.d.ts → types-S53WDy_h.d.ts} +1 -1
  116. package/dist/work-queue/index.d.ts +4 -4
  117. package/dist/work-queue/index.js +2 -2
  118. package/package.json +18 -18
  119. package/dist/chunk-7Z2IDZ7R.js +0 -34
  120. package/dist/chunk-7Z2IDZ7R.js.map +0 -1
  121. package/dist/chunk-EB5OMOXX.js.map +0 -1
  122. package/dist/chunk-GAVLND5C.js.map +0 -1
  123. package/dist/chunk-HU3Y2AZY.js.map +0 -1
  124. package/dist/chunk-TMYUDZLE.js.map +0 -1
  125. package/dist/chunk-VM7PMFV5.js.map +0 -1
  126. package/dist/chunk-W6XWI6QE.js.map +0 -1
@@ -0,0 +1,52 @@
1
+ import { replay, applyCellChecked } from './chunk-A3QPMX5M.js';
2
+ import { expect } from 'vitest';
3
+
4
+ function expectFinalState(machine, opts, expected) {
5
+ const { state } = replay(machine, opts);
6
+ expect(state).toEqual(expected);
7
+ }
8
+ function expectCmdEmitted(machine, opts, cmd) {
9
+ const { cmds } = replay(machine, opts);
10
+ expect(cmds).toContainEqual(cmd);
11
+ }
12
+ function expectCmdSequence(machine, opts, expected) {
13
+ const { cmds } = replay(machine, opts);
14
+ expect(cmds).toEqual(expected);
15
+ }
16
+ function expectActiveSubs(machine, opts, expected) {
17
+ const { subs } = replay(machine, opts);
18
+ expect(subs).toEqual(expected);
19
+ }
20
+ function step(machine, loaded, msg, _ctx) {
21
+ return applyCellChecked(machine, loaded, msg);
22
+ }
23
+ function bindMachine(machine, ctx) {
24
+ return {
25
+ step(loaded, msg) {
26
+ return step(machine, loaded, msg);
27
+ },
28
+ expectFinalState(opts, expected) {
29
+ const { state } = replay(machine, { ...opts, ctx });
30
+ expect(state).toEqual(expected);
31
+ },
32
+ expectCmdEmitted(opts, cmd) {
33
+ const { cmds } = replay(machine, { ...opts, ctx });
34
+ expect(cmds).toContainEqual(cmd);
35
+ },
36
+ expectCmdSequence(opts, expected) {
37
+ const { cmds } = replay(machine, { ...opts, ctx });
38
+ expect(cmds).toEqual(expected);
39
+ },
40
+ expectActiveSubs(opts, expected) {
41
+ const { subs } = replay(machine, { ...opts, ctx });
42
+ expect(subs).toEqual(expected);
43
+ },
44
+ replay(opts) {
45
+ return replay(machine, { ...opts, ctx });
46
+ }
47
+ };
48
+ }
49
+
50
+ export { bindMachine, expectActiveSubs, expectCmdEmitted, expectCmdSequence, expectFinalState, step };
51
+ //# sourceMappingURL=chunk-RMBZ7YH7.js.map
52
+ //# sourceMappingURL=chunk-RMBZ7YH7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/testing/assertions.ts","../src/testing/bind-machine.ts"],"names":["expect"],"mappings":";;;AA8DO,SAAS,gBAAA,CAOd,OAAA,EACA,IAAA,EACA,QAAA,EACM;AACN,EAAA,MAAM,EAAE,KAAA,EAAM,GAAI,MAAA,CAAO,SAAS,IAAI,CAAA;AACtC,EAAA,MAAA,CAAO,KAAK,CAAA,CAAE,OAAA,CAAQ,QAAQ,CAAA;AAChC;AAaO,SAAS,gBAAA,CAOd,OAAA,EACA,IAAA,EACA,GAAA,EACM;AACN,EAAA,MAAM,EAAE,IAAA,EAAK,GAAI,MAAA,CAAO,SAAS,IAAI,CAAA;AACrC,EAAA,MAAA,CAAO,IAAI,CAAA,CAAE,cAAA,CAAe,GAAG,CAAA;AACjC;AAaO,SAAS,iBAAA,CAOd,OAAA,EACA,IAAA,EACA,QAAA,EACM;AACN,EAAA,MAAM,EAAE,IAAA,EAAK,GAAI,MAAA,CAAO,SAAS,IAAI,CAAA;AACrC,EAAA,MAAA,CAAO,IAAI,CAAA,CAAE,OAAA,CAAQ,QAAQ,CAAA;AAC/B;AAWO,SAAS,gBAAA,CAOd,OAAA,EACA,IAAA,EACA,QAAA,EACM;AACN,EAAA,MAAM,EAAE,IAAA,EAAK,GAAI,MAAA,CAAO,SAAS,IAAI,CAAA;AACrC,EAAA,MAAA,CAAO,IAAI,CAAA,CAAE,OAAA,CAAQ,QAAQ,CAAA;AAC/B;AAgBO,SAAS,IAAA,CAOd,OAAA,EACA,MAAA,EACA,GAAA,EACA,IAAA,EAC4B;AAC5B,EAAA,OAAO,gBAAA,CAA0B,OAAA,EAAS,MAAA,EAAQ,GAAG,CAAA;AACvD;AChEO,SAAS,WAAA,CAMd,SAAmC,GAAA,EAAyC;AAC5E,EAAA,OAAO;AAAA,IACL,IAAA,CAAK,QAAQ,GAAA,EAAK;AAChB,MAAA,OAAO,IAAA,CAAK,OAAA,EAAS,MAAA,EAAQ,GAAQ,CAAA;AAAA,IACvC,CAAA;AAAA,IACA,gBAAA,CAAiB,MAAM,QAAA,EAAU;AAC/B,MAAA,MAAM,EAAE,OAAM,GAAI,MAAA,CAAO,SAAS,EAAE,GAAG,IAAA,EAAM,GAAA,EAAK,CAAA;AAClD,MAAAA,MAAAA,CAAO,KAAK,CAAA,CAAE,OAAA,CAAQ,QAAQ,CAAA;AAAA,IAChC,CAAA;AAAA,IACA,gBAAA,CAAiB,MAAM,GAAA,EAAK;AAC1B,MAAA,MAAM,EAAE,MAAK,GAAI,MAAA,CAAO,SAAS,EAAE,GAAG,IAAA,EAAM,GAAA,EAAK,CAAA;AACjD,MAAAA,MAAAA,CAAO,IAAI,CAAA,CAAE,cAAA,CAAe,GAAG,CAAA;AAAA,IACjC,CAAA;AAAA,IACA,iBAAA,CAAkB,MAAM,QAAA,EAAU;AAChC,MAAA,MAAM,EAAE,MAAK,GAAI,MAAA,CAAO,SAAS,EAAE,GAAG,IAAA,EAAM,GAAA,EAAK,CAAA;AACjD,MAAAA,MAAAA,CAAO,IAAI,CAAA,CAAE,OAAA,CAAQ,QAAQ,CAAA;AAAA,IAC/B,CAAA;AAAA,IACA,gBAAA,CAAiB,MAAM,QAAA,EAAU;AAC/B,MAAA,MAAM,EAAE,MAAK,GAAI,MAAA,CAAO,SAAS,EAAE,GAAG,IAAA,EAAM,GAAA,EAAK,CAAA;AACjD,MAAAA,MAAAA,CAAO,IAAI,CAAA,CAAE,OAAA,CAAQ,QAAQ,CAAA;AAAA,IAC/B,CAAA;AAAA,IACA,OAAO,IAAA,EAAM;AACX,MAAA,OAAO,OAAO,OAAA,EAAS,EAAE,GAAG,IAAA,EAAM,KAAK,CAAA;AAAA,IACzC;AAAA,GACF;AACF","file":"chunk-RMBZ7YH7.js","sourcesContent":["// ---------------------------------------------------------------------------\n// Test-side assertions over `replay`. Each helper composes the replay call\n// with a single `expect(...)` so consumers stop hand-rolling the 5-line\n// \"destructure { state, cmds } → expect.toBe / toEqual / toContainEqual\"\n// boilerplate at every test site.\n//\n// Each helper is a thin wrapper that:\n// 1. Calls `replay(machine, opts)` once.\n// 2. Pipes the result into a single vitest `expect(...)` matcher.\n//\n// All helpers return `void` — assertions throw on failure (vitest contract).\n// No mutation of the machine or opts. Pure test ergonomics.\n//\n// Type-inference note: each helper anchors its generic parameters to the\n// `machine` argument. The `expected` (and `opts.msgs`) arguments are wrapped\n// in `NoInfer<...>` so they do NOT participate in inference — otherwise a\n// `[]`-shaped `expected` would force `C = never` and reject a machine whose\n// real Cmd union is non-empty. Anchoring inference to the machine is the\n// same discipline `replay` uses (it has nothing but the machine and opts).\n//\n// Strengthens invariant 9 (the testing surface is named, small, and forks\n// nothing of the substrate's purity guarantees).\n// ---------------------------------------------------------------------------\n\nimport { expect } from \"vitest\";\nimport {\n applyCellChecked,\n type Cmd,\n type Machine,\n replay,\n type Sub,\n} from \"../index\";\n\n// `NoInfer<T>` is the built-in TypeScript 5.4+ intrinsic that blocks\n// inference from a generic parameter at a position. We use it on `expected`\n// and `opts` so inference flows purely FROM the machine — `[]`-shaped\n// `expected` no longer drags `C` down to `never` and rejects the machine.\n\n/**\n * Shared options shape for every test assertion below. Mirrors the\n * second argument of `replay(machine, opts)` — `msgs` is the sequence to\n * fold over (may be empty for init-only assertions), `ctx` is the\n * substrate-required ctx value, `loaded` is the optional pre-`init`\n * snapshot (omit / pass `null` for fresh boot).\n */\nexport interface ReplayOpts<S, M, Ctx> {\n readonly msgs: readonly M[];\n readonly ctx: Ctx;\n readonly loaded?: S | null;\n}\n\n/**\n * Assert the final state after replaying `opts.msgs` equals `expected`.\n * Replaces the boilerplate:\n *\n * const { state } = replay(machine, opts);\n * expect(state).toEqual(expected);\n *\n * Uses `toEqual` (deep structural). For type-narrowing assertions that\n * branch on `state.type` afterwards, fall back to plain `replay` —\n * `expectFinalState` is the \"exact match\" tool.\n */\nexport function expectFinalState<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: NoInfer<ReplayOpts<S, M, Ctx>>,\n expected: NoInfer<S>,\n): void {\n const { state } = replay(machine, opts);\n expect(state).toEqual(expected);\n}\n\n/**\n * Assert that `cmd` appears at least once in the cmds array produced by\n * replaying `opts.msgs`. Uses `toContainEqual` (deep structural). Order\n * and surrounding cmds are not asserted — use `expectCmdSequence` for\n * exact ordered match.\n *\n * Replaces:\n *\n * const { cmds } = replay(machine, opts);\n * expect(cmds).toContainEqual(cmd);\n */\nexport function expectCmdEmitted<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: NoInfer<ReplayOpts<S, M, Ctx>>,\n cmd: NoInfer<C>,\n): void {\n const { cmds } = replay(machine, opts);\n expect(cmds).toContainEqual(cmd);\n}\n\n/**\n * Assert the exact ordered sequence of cmds emitted by replaying\n * `opts.msgs`. Uses `toEqual` against the full cmds array — extra cmds\n * fail the assertion, missing cmds fail the assertion, wrong order\n * fails the assertion.\n *\n * Replaces:\n *\n * const { cmds } = replay(machine, opts);\n * expect(cmds).toEqual(expected);\n */\nexport function expectCmdSequence<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: NoInfer<ReplayOpts<S, M, Ctx>>,\n expected: NoInfer<readonly C[]>,\n): void {\n const { cmds } = replay(machine, opts);\n expect(cmds).toEqual(expected);\n}\n\n/**\n * Assert the exact set of subs desired at the final state after\n * replaying `opts.msgs`. Uses `toEqual` against the full subs array.\n *\n * Replaces:\n *\n * const { subs } = replay(machine, opts);\n * expect(subs).toEqual(expected);\n */\nexport function expectActiveSubs<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: NoInfer<ReplayOpts<S, M, Ctx>>,\n expected: NoInfer<readonly U[]>,\n): void {\n const { subs } = replay(machine, opts);\n expect(subs).toEqual(expected);\n}\n\n/**\n * Single-msg step helper — feeds `loaded → msg → next state + cmds emitted by\n * that msg`. Returns a readonly tuple in the shape consumers used before the\n * substrate (`[state, cmds]`), so existing tests that destructure with\n * `[next, cmds]` can swap `reduce(state, msg)` for `step(machine, state, msg,\n * ctx)` without changing the assertion code.\n *\n * Calls the update cell directly, without init or subscription derivation.\n * The returned Cmds belong only to this message.\n *\n * Use when consumers want to walk a machine one msg at a time and assert per\n * step. For one-shot \"final state after N msgs\" assertions, prefer\n * `expectFinalState`.\n */\nexport function step<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n loaded: NoInfer<S>,\n msg: NoInfer<M>,\n _ctx: NoInfer<Ctx>,\n): readonly [S, readonly C[]] {\n return applyCellChecked<S, M, C>(machine, loaded, msg);\n}\n","// ---------------------------------------------------------------------------\n// `bindMachine(machine, ctx)` — bound test harness over the free-function\n// assertions in `./assertions.ts`. Closes over the `machine` and `ctx` once\n// so per-test callsites stop repeating both at every invocation.\n//\n// Why this exists: every consumer test file ends up hand-rolling a local\n// two-arg `step(state, msg)` wrapper around `step(machine, state, msg, ctx)`,\n// and the four `expect*` assertions repeat `machine` + `ctx` at every\n// callsite. The bound form pulls both into one place and gives every\n// per-test call its tightest possible shape (2-arg max).\n//\n// `bindMachine` is built ON TOP of the free helpers — it does not replace\n// them. Both forms coexist; pick the one that fits the call site:\n//\n// - Free form → one-off call inside a helper that isn't bound to one\n// machine (e.g. cross-machine integration test).\n// - Bound form → a `describe` block runs many assertions against one\n// machine + ctx. The bound form is the common case.\n//\n// Strengthens invariant 9 (the testing surface is named, small, and forks\n// nothing of the substrate's purity guarantees).\n// ---------------------------------------------------------------------------\n\nimport { expect } from \"vitest\";\nimport {\n type BuiltinSub,\n type Cmd,\n type Machine,\n replay,\n type Sub,\n} from \"../index\";\nimport { step } from \"./assertions\";\n\n/**\n * Replay options shape used by every bound method. `ctx` is captured by\n * `bindMachine`; the per-call opts only need `msgs` and an optional\n * `loaded` snapshot. (Compare `ReplayOpts` in `./assertions.ts`, which\n * still takes `ctx` because the free helpers cannot capture it.)\n */\ninterface BoundOpts<S, M> {\n readonly msgs: readonly M[];\n readonly loaded?: S | null;\n}\n\n/**\n * The bound testing surface. Every method is 2-arg max — `ctx` and\n * `machine` are captured by `bindMachine`.\n *\n * `NoInfer<...>` on `expected` / `cmd` arguments mirrors the discipline\n * the free helpers use (see `./assertions.ts`): inference flows purely\n * FROM the machine, so a `[]`-shaped expected does not drag `C` down to\n * `never` and reject the machine.\n */\nexport interface BoundMachine<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n // `Ctx` is a phantom-style parameter — present for consumers that want to\n // spell `BoundMachine<S, M, C, U, Ctx>` explicitly, mirroring `Machine`.\n // biome-ignore lint/correctness/noUnusedVariables: phantom type param (see above)\n Ctx,\n> {\n /**\n * Single-msg step — feeds `loaded → msg → [next state, cmds emitted by\n * that msg]`. Mirrors the free `step(machine, loaded, msg, ctx)`.\n */\n step(loaded: S, msg: M): readonly [S, readonly C[]];\n /**\n * Assert the final state after replaying `opts.msgs` deep-equals\n * `expected`. Mirrors the free `expectFinalState(machine, opts, expected)`.\n */\n expectFinalState(opts: BoundOpts<S, M>, expected: NoInfer<S>): void;\n /**\n * Assert `cmd` appears at least once in the emitted cmd list. Order is\n * not asserted. Mirrors the free `expectCmdEmitted(machine, opts, cmd)`.\n */\n expectCmdEmitted(opts: BoundOpts<S, M>, cmd: NoInfer<C>): void;\n /**\n * Assert the exact ordered sequence of cmds emitted by replaying\n * `opts.msgs`. Mirrors the free `expectCmdSequence(machine, opts, expected)`.\n */\n expectCmdSequence(\n opts: BoundOpts<S, M>,\n expected: readonly NoInfer<C>[],\n ): void;\n /**\n * Assert the exact set of subs desired at the final state. Mirrors the\n * free `expectActiveSubs(machine, opts, expected)`.\n */\n expectActiveSubs(\n opts: BoundOpts<S, M>,\n expected: readonly NoInfer<U | BuiltinSub<M>>[],\n ): void;\n /**\n * Bound `replay` — returns `{ state, cmds, subs }` for the given opts.\n * Use for the \"narrow-then-assert\" pattern where the test inspects a\n * specific field after `state.type === \"...\"` discrimination.\n */\n replay(opts: BoundOpts<S, M>): {\n state: S;\n cmds: readonly C[];\n subs: readonly (U | BuiltinSub<M>)[];\n };\n}\n\n/**\n * Bind a machine + ctx into a small bag of 2-arg helpers. Each method\n * delegates to `step` or `replay(machine, { ...opts, ctx })` and pipes the result\n * through a single `expect(...)` matcher (or returns the raw tuple, for\n * `step` and `replay`).\n *\n * Use one bind per `describe` block (or per file) when many assertions\n * share the same machine + ctx. For one-off cross-machine calls, stick\n * with the free helpers from `./assertions.ts`.\n */\nexport function bindMachine<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(machine: Machine<S, M, C, U, Ctx>, ctx: Ctx): BoundMachine<S, M, C, U, Ctx> {\n return {\n step(loaded, msg) {\n return step(machine, loaded, msg, ctx);\n },\n expectFinalState(opts, expected) {\n const { state } = replay(machine, { ...opts, ctx });\n expect(state).toEqual(expected);\n },\n expectCmdEmitted(opts, cmd) {\n const { cmds } = replay(machine, { ...opts, ctx });\n expect(cmds).toContainEqual(cmd);\n },\n expectCmdSequence(opts, expected) {\n const { cmds } = replay(machine, { ...opts, ctx });\n expect(cmds).toEqual(expected);\n },\n expectActiveSubs(opts, expected) {\n const { subs } = replay(machine, { ...opts, ctx });\n expect(subs).toEqual(expected);\n },\n replay(opts) {\n return replay(machine, { ...opts, ctx });\n },\n };\n}\n"]}
@@ -1,7 +1,28 @@
1
- import { defaultOnError, startLoop, builtinExtensions, liveWork, RuntimeStoppedError } from './chunk-VM7PMFV5.js';
2
- import { Outcome, subEntriesOf, applyCell, StoreRefusedError, DispatchDiscardedError } from './chunk-GAVLND5C.js';
3
- import { Data, Stream, Effect, Exit, Cause, Result, Scope, Fiber } from 'effect';
1
+ import { tellRun, refuseFencedRunWithoutSink, defaultOnError, startLoop, builtinExtensions, post, liveWork, RuntimeStoppedError } from './chunk-2A5XTKQQ.js';
2
+ import { Outcome, subEntriesOf, applyCell, StoreRefusedError, DispatchDiscardedError, SubFailedError } from './chunk-A3QPMX5M.js';
3
+ import { Data, Effect, Scope, Exit, Stream, Cause, Result, Context, Fiber } from 'effect';
4
4
 
5
+ var tell = (machine, runtime, msg) => Effect.sync(() => tellRun(machine, runtime, msg));
6
+ var spawnedScopes = /* @__PURE__ */ new WeakMap();
7
+ var spawnedScope = (scope) => spawnedScopes.get(scope);
8
+ var spawn = (parentScope, steps) => Effect.uninterruptible(
9
+ Effect.gen(function* () {
10
+ const scope = yield* Scope.fork(parentScope);
11
+ spawnedScopes.set(scope, scope);
12
+ yield* Scope.addFinalizer(
13
+ scope,
14
+ Effect.andThen(steps.remove, Effect.forkDetach(steps.notify))
15
+ );
16
+ const child = yield* steps.start(scope).pipe(
17
+ Scope.provide(scope),
18
+ Effect.onError((cause) => Scope.close(scope, Exit.failCause(cause)))
19
+ );
20
+ yield* steps.enrol(child);
21
+ if (scope.state._tag === "Closed") yield* steps.remove;
22
+ return child;
23
+ })
24
+ );
25
+ var stop = (scope) => Scope.close(scope, Exit.void);
5
26
  var Stopped = class extends Data.TaggedError("Stopped") {
6
27
  get message() {
7
28
  return `@demlik/tea: Msg "${this.msgType}" was refused \u2014 the run is ${this.when}.`;
@@ -12,6 +33,11 @@ var StoreFailed = class extends Data.TaggedError("StoreFailed") {
12
33
  return `@demlik/tea: store ${this.operation} failed.`;
13
34
  }
14
35
  };
36
+ var SubFailed = class extends Data.TaggedError("SubFailed") {
37
+ get message() {
38
+ return "@demlik/tea: a Sub failed and stopped the run.";
39
+ }
40
+ };
15
41
  var CellFailure = class {
16
42
  constructor(failure) {
17
43
  this.failure = failure;
@@ -62,6 +88,12 @@ function exitOf(error) {
62
88
  }
63
89
  return Exit.die(error);
64
90
  }
91
+ function doneExitOf(error) {
92
+ if (error instanceof SubFailedError) {
93
+ return Exit.fail(new SubFailed({ cause: error.cause }));
94
+ }
95
+ return exitOf(error);
96
+ }
65
97
  function settled(call) {
66
98
  return Effect.flatten(
67
99
  Effect.promise(
@@ -75,6 +107,7 @@ function settled(call) {
75
107
  function effectHandle(loop) {
76
108
  const handle = {
77
109
  [liveWork]: loop[liveWork],
110
+ [post]: loop[post],
78
111
  dispatch: (msg, opts) => settled(() => loop.dispatch(msg, opts)),
79
112
  dispatchOnce: (msg) => settled(() => loop.dispatchOnce(msg)),
80
113
  subscribe: (listener) => loop.subscribe(listener),
@@ -90,10 +123,13 @@ function effectHandle(loop) {
90
123
  ready: Effect.suspend(
91
124
  () => settled(() => loop.ready).pipe(Effect.as(handle))
92
125
  ),
93
- // `idle` rejects only on a livelock, and `done` and `stop` never do: a
94
- // rejection here is a defect.
126
+ // `idle` rejects only on a livelock, and `stop` never does.
95
127
  idle: () => Effect.promise(() => loop.idle()),
96
- done: () => Effect.promise(() => loop.done()),
128
+ done: () => Effect.flatten(
129
+ Effect.promise(
130
+ () => loop.done().then((state) => Effect.succeed(state), doneExitOf)
131
+ )
132
+ ),
97
133
  stop: () => Effect.promise(() => loop.stop())
98
134
  };
99
135
  return handle;
@@ -121,25 +157,30 @@ function settleCellExit(exit, defined) {
121
157
  }
122
158
  function run(machine, opts) {
123
159
  return Effect.gen(function* () {
160
+ yield* Effect.sync(
161
+ () => refuseFencedRunWithoutSink(opts.store, opts.onError)
162
+ );
124
163
  const services = yield* Effect.context();
125
- const scope = yield* Scope.Scope;
126
- const fork = Effect.runForkWith(services);
164
+ const callerScope = yield* Scope.Scope;
165
+ const scope = yield* Scope.fork(callerScope);
166
+ const runServices = Context.add(services, Scope.Scope, scope);
167
+ const fork = Effect.runForkWith(runServices);
127
168
  return yield* Effect.acquireRelease(
128
169
  Effect.sync(
129
170
  () => effectHandle(
130
171
  start(
131
172
  machine,
132
173
  opts,
133
- Effect.runPromiseExitWith(services),
174
+ Effect.runPromiseExitWith(runServices),
134
175
  fork,
135
176
  // Closing the Scope runs the release below, which stops the run.
136
177
  // A Scope that closes before that release is added runs it on add.
137
- (exit) => fork(Scope.close(scope, exit))
178
+ (exit) => fork(Scope.close(spawnedScope(callerScope) ?? scope, exit))
138
179
  )
139
180
  )
140
181
  ),
141
182
  (runtime) => runtime.stop()
142
- );
183
+ ).pipe(Scope.provide(scope));
143
184
  });
144
185
  }
145
186
  function start(machine, opts, runEffect, fork, closeScope) {
@@ -197,7 +238,13 @@ function start(machine, opts, runEffect, fork, closeScope) {
197
238
  saveFailures(),
198
239
  ...builtinExtensions(machine, opts)
199
240
  ],
200
- stopOnSubFailure: (error) => closeScope(Exit.failCause(subFailure ?? Cause.die(error)))
241
+ // A Sub failure closes the run's Scope with the Stream's own Cause, and a
242
+ // halting reducer throw with what was thrown as a defect (ADR 0033).
243
+ stopOnFailure: (failure) => closeScope(
244
+ Exit.failCause(
245
+ (failure.kind === "sub" ? subFailure : void 0) ?? Cause.die(failure.error)
246
+ )
247
+ )
201
248
  });
202
249
  const stopLoop = handle.stop;
203
250
  handle.stop = () => {
@@ -208,6 +255,6 @@ function start(machine, opts, runEffect, fork, closeScope) {
208
255
  return handle;
209
256
  }
210
257
 
211
- export { CellFailure, Stopped, StoreFailed, builtinRunners, cellResult, run, settleCellExit };
212
- //# sourceMappingURL=chunk-HU3Y2AZY.js.map
213
- //# sourceMappingURL=chunk-HU3Y2AZY.js.map
258
+ export { CellFailure, Stopped, StoreFailed, SubFailed, builtinRunners, cellResult, run, settleCellExit, spawn, stop, tell };
259
+ //# sourceMappingURL=chunk-SHXUSTHM.js.map
260
+ //# sourceMappingURL=chunk-SHXUSTHM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/effect/children.ts","../src/effect/failures.ts","../src/effect/handle.ts","../src/effect/run.ts"],"names":["Exit","Effect","Scope"],"mappings":"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","file":"chunk-SHXUSTHM.js","sourcesContent":["/**\n * Helpers for running child machines under a parent on the Effect engine\n * (#556, ruling R1.1 of #554).\n *\n * tea still runs one machine per `run` and keeps no table of runs (#312). A\n * host that builds a tree of runs keeps its own table, ids and Msg names, and\n * hands the steps that race to these functions: `spawn` starts a child and\n * enrols it as one step, `stop` stops one child, and `tell` puts a Msg on a\n * run's line without waiting for it.\n */\n\nimport { Effect, Exit, Scope } from \"effect\";\nimport type { TellAnswer } from \"../internal/engine/loop\";\nimport { tellRun } from \"../internal/engine/tell\";\nimport type { Cmd, Machine, Sub } from \"../pure/core\";\nimport type { EffectRun } from \"./handle\";\n\n/**\n * Put `msg` on a run's line and answer at once. The answer means queued, not\n * handled: the receiver's step has not run yet, and any result comes back later\n * as its own Msg. So two machines can tell each other from their Cmd handlers\n * and neither stops. `machine` is the machine `runtime` runs.\n *\n * The answer is `\"queued\"`, `\"stopped\"` for a run that is stopping or has\n * stopped, or `\"no_cell\"` when its current State has no cell for the Msg. Msgs\n * already in line have not folded yet, so a Msg that only a queued one makes\n * acceptable answers `\"no_cell\"`.\n *\n * `runtime` is what `ready` succeeded with, or a spread of it. Those carry the\n * run's line, and the type asks for it, so an `EffectRuntime` built by hand is a\n * compile error. So is the handle `run` yielded, with a store or without one: wait\n * for `ready`.\n *\n * `tell` carries no failure in its type. A hand-built object cast past the type\n * has no line to put a Msg on, and `tell` dies with a defect there.\n *\n * What happens after `\"queued\"` is the receiver's. Two `tell`s from one sender\n * fold in the order they were sent. A failed save or a cell's failure goes to\n * the receiver's `onError`, never back to the sender. A Msg whose cell is gone\n * by its turn is dropped without a word, and a `stop()` that gives up drops\n * what is still in line and counts it, per Msg type, in its\n * `DrainTimeoutNotice`.\n *\n * Two hand-written waiting `runtime.dispatch(...)` calls into each other, one\n * from each machine's Cmd handler, still stop both machines: `dispatch` waits,\n * and tea has no lock-up detector. Use `tell` there.\n *\n * ```ts\n * const answer = yield* tell(parent, parentRun, { type: \"child_stopped\", id });\n * ```\n */\nexport const tell = <\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n E extends { type: string } = never,\n Err = never,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n runtime: EffectRun<S, M, E, Err>,\n msg: NoInfer<M>,\n): Effect.Effect<TellAnswer> =>\n Effect.sync(() => tellRun<S, M>(machine, runtime, msg));\n\n/**\n * The host's own steps of a {@link spawn}. The host owns the table of\n * children, their ids and the parent's Msg names; `spawn` owns the order the\n * steps run in and keeps an interrupt from landing between them.\n */\nexport interface SpawnSteps<A, E = never, R = never> {\n /**\n * Start the child and return what the host keeps for it. It runs with the\n * child's scope provided, so a `run(machine, opts)` in it belongs to that\n * scope, and so does anything else it acquires. `scope` is the same scope,\n * for a host that keeps it to stop the child with `stop`. A failure\n * closes the child's scope, which runs `remove` and `notify`.\n */\n readonly start: (scope: Scope.Closeable) => Effect.Effect<A, E, R>;\n /** Add the started child to the host's table. */\n readonly enrol: (child: A) => Effect.Effect<unknown>;\n /**\n * Take the child out of the host's table. It runs when the child's scope\n * closes, after its run has stopped, whether or not `enrol` ran, so it must\n * be safe on a child the table does not hold.\n */\n readonly remove: Effect.Effect<unknown>;\n /**\n * Say the child stopped, usually a {@link tell} to the parent. It runs\n * after `remove`, on a fiber of its own that the closing scope never waits\n * for, so even a step that does wait cannot hold the close.\n *\n * It cannot fail: nothing reads that fiber, so the step itself handles what\n * its notice can fail with. A `tell` has nothing to handle. Its argument is the\n * handle the parent's `ready` gave: a parent handle built by hand, or one straight\n * from `run`, does not compile. A defect in this step, such as a `tell` on a\n * hand-built object cast past that type, is not observed by `spawn` and ends the\n * fiber unseen.\n */\n readonly notify: Effect.Effect<unknown>;\n}\n\nconst spawnedScopes = new WeakMap<Scope.Scope, Scope.Closeable>();\n\n/**\n * The child scope `spawn` forked, when `scope` is one: the host's unit of work,\n * so a run inside it that a Sub failure or a reducer throw under `stop` stops\n * closes it, and `remove` and `notify` run (ADR 0033). Every other Scope\n * belongs to whoever opened it.\n */\nexport const spawnedScope = (scope: Scope.Scope): Scope.Closeable | undefined =>\n spawnedScopes.get(scope);\n\n/**\n * Start a child in a scope forked from `parentScope` and enrol it in the\n * host's table, as one uninterruptible step. Stop a child with `stop`,\n * or by closing its scope; closing `parentScope` stops every child spawned\n * under it.\n *\n * What it guarantees, with no guard in host code:\n *\n * - the table never holds a child whose scope has closed. The fork, the\n * removal finalizer, `start` and `enrol` all happen or none do, and a\n * child whose scope closed before `enrol` is removed again;\n * - closing a child never waits for the parent. `notify` runs on a detached\n * fiber, so a parent Cmd handler can stop a child;\n * - `remove` runs after the child's run has stopped.\n *\n * Nothing reads the fiber `notify` runs on, so `spawn` reports no failure of\n * it and `notify` is typed to have none. A `tell` carries no failure, and a\n * failed save of the parent reaches the parent's own `onError`. A defect in\n * `notify`, such as a `tell` on a handle cast past its type, ends that fiber\n * unseen.\n *\n * A child whose run fails stops the same way, with no host call: a Sub failure\n * the Sub did not map to a Msg, or a reducer throw under the default `stop`\n * strategy, closes the child's scope, so `remove` and `notify` run.\n *\n * `child.run.stop()` is not one of the two ways to stop a child. It stops the\n * run and leaves the scope open, so the entry stays in the table and the\n * parent is not told.\n *\n * ```ts\n * const child = yield* spawn(parentScope, {\n * start: (scope) =>\n * Effect.map(run(worker, {}), (run) => ({ run, scope })),\n * enrol: (child) => Effect.sync(() => children.set(id, child)),\n * remove: Effect.sync(() => children.delete(id)),\n * notify: tell(parent, parentRun, { type: \"child_stopped\", id }),\n * });\n * ```\n */\nexport const spawn = <A, E = never, R = never>(\n parentScope: Scope.Scope,\n steps: SpawnSteps<A, E, R>,\n): Effect.Effect<A, E, Exclude<R, Scope.Scope>> =>\n Effect.uninterruptible(\n Effect.gen(function* () {\n const scope = yield* Scope.fork(parentScope);\n spawnedScopes.set(scope, scope);\n // Added before `start`, so it runs after everything `start` put in the\n // scope has been released, the child's run included.\n yield* Scope.addFinalizer(\n scope,\n Effect.andThen(steps.remove, Effect.forkDetach(steps.notify)),\n );\n const child = yield* steps.start(scope).pipe(\n Scope.provide(scope),\n Effect.onError((cause) => Scope.close(scope, Exit.failCause(cause))),\n );\n yield* steps.enrol(child);\n // A scope that closed under `start` ran `remove` before `enrol`.\n if (scope.state._tag === \"Closed\") yield* steps.remove;\n return child;\n }),\n );\n\n/**\n * Stop one child that `spawn` started. `scope` is the child's scope,\n * the one its `start` was handed.\n *\n * It closes that scope, so the child's run stops, `remove` takes the entry\n * out of the host's table and `notify` tells the parent. It does not wait for\n * `notify`, so a parent Cmd handler can call it. Stopping a child that has\n * already stopped succeeds and sends no second notice.\n *\n * ```ts\n * yield* stop(child.scope);\n * ```\n */\nexport const stop = (scope: Scope.Closeable): Effect.Effect<void> =>\n Scope.close(scope, Exit.void);\n","/**\n * The Effect engine's typed failures (#308, ruling R4.1 of #325), and the edge\n * that turns what the shared loop rejects with into them.\n *\n * The loop is engine-neutral: it rejects a dispatch with whatever was thrown.\n * Two throws it cannot tell apart from a bug — a hand-written cell's declared\n * failure and a store's failed save — are wrapped here on the way in, so the\n * edge can read them on the way out. The wrappers never reach the caller: a\n * handle verb fails with the typed value, and the `onError` sink is handed the\n * value it was always handed.\n */\n\nimport { Data, Exit } from \"effect\";\nimport {\n type ExtensionFactory,\n RuntimeStoppedError,\n} from \"../internal/engine/loop\";\nimport {\n DispatchDiscardedError,\n type Store,\n StoreRefusedError,\n SubFailedError,\n} from \"../runtime-types\";\n\n/**\n * A dispatch the run refused because it is stopping or has stopped. The Msg\n * was never folded.\n *\n * `when` is `\"stopping\"` for a Msg that arrived while `stop()` (or a closing\n * Scope) drained the run's in-flight work, and `\"stopped\"` once it had.\n */\nexport class Stopped extends Data.TaggedError(\"Stopped\")<{\n readonly msgType: string;\n readonly when: \"stopping\" | \"stopped\";\n}> {\n override get message(): string {\n return (\n `@demlik/tea: Msg \"${this.msgType}\" was refused — the run is ` +\n `${this.when}.`\n );\n }\n}\n\n/**\n * The run's store failed. `operation` is `\"load\"` when the saved state could\n * not be restored at boot — `cause` is then the `StoreRefusedError` that says\n * why — and `\"save\"` when a transition's save threw, a fenced store's\n * `StoreConflictError` included; `cause` is then the store's own throw.\n */\nexport class StoreFailed extends Data.TaggedError(\"StoreFailed\")<{\n readonly operation: \"load\" | \"save\";\n readonly cause: unknown;\n}> {\n override get message(): string {\n return `@demlik/tea: store ${this.operation} failed.`;\n }\n}\n\n/**\n * A Sub failed with an error it did not map to a Msg, and that stopped the run\n * before it reached a terminal State. `cause` is what the Sub failed with, the\n * value `onError` heard under `\"sub\"`. Only `done()` fails with it.\n */\nexport class SubFailed extends Data.TaggedError(\"SubFailed\")<{\n readonly cause: unknown;\n}> {\n override get message(): string {\n return \"@demlik/tea: a Sub failed and stopped the run.\";\n }\n}\n\n/** A hand-written cell's declared failure, on its way through the loop. */\nexport class CellFailure {\n constructor(readonly failure: unknown) {}\n}\n\n/** A store's failed save, on its way through the loop. */\nclass SaveFailure {\n constructor(readonly cause: unknown) {}\n}\n\n/** What was thrown, with the edge's wrapper taken off. */\nexport function unwrapped(error: unknown): unknown {\n if (error instanceof CellFailure) return error.failure;\n if (error instanceof SaveFailure) return error.cause;\n return error;\n}\n\n/**\n * The outermost store wrapper: every save that throws is marked as a store\n * failure. Outermost, so it wraps the fenced compare-and-swap too, and its\n * `StoreConflictError` fails as a `StoreFailed`.\n */\nexport function saveFailures<S, M, C>(): ExtensionFactory<S, M, C> {\n return () => ({\n store: (store: Store<S>): Store<S> => ({\n load: () => store.load(),\n async save(state) {\n try {\n await store.save(state);\n } catch (cause) {\n throw new SaveFailure(cause);\n }\n },\n migrate: (raw) => store.migrate(raw),\n }),\n });\n}\n\n/**\n * The Exit a handle verb ends with when the loop rejected with `error`: a\n * typed failure for a refused dispatch, a failed store and a cell's declared\n * failure, and a defect for anything else — a reducer throw, a Msg with no\n * cell, a livelock — which ADR 0011 keeps a contract breach.\n */\nexport function exitOf<Err>(\n error: unknown,\n): Exit.Exit<never, Err | Stopped | StoreFailed> {\n if (error instanceof CellFailure) return Exit.fail(error.failure as Err);\n if (error instanceof SaveFailure) {\n return Exit.fail(\n new StoreFailed({ operation: \"save\", cause: error.cause }),\n );\n }\n if (error instanceof StoreRefusedError) {\n return Exit.fail(new StoreFailed({ operation: \"load\", cause: error }));\n }\n if (error instanceof DispatchDiscardedError) {\n return Exit.fail(new Stopped({ msgType: error.msgType, when: \"stopping\" }));\n }\n if (error instanceof RuntimeStoppedError) {\n return Exit.fail(new Stopped({ msgType: error.msgType, when: \"stopped\" }));\n }\n return Exit.die(error);\n}\n\n/**\n * The Exit `done()` ends with when the loop rejected with `error`: it rejects at\n * the terminal save boundary and on a failure that closed the run, never at the\n * gate. A Sub's failure is typed, because a Sub's failure is part of the program.\n * A reducer throw that halted the run is a defect, the same contract breach it is\n * on the dispatch that hit it (ADR 0011).\n */\nexport function doneExitOf(\n error: unknown,\n): Exit.Exit<never, StoreFailed | SubFailed> {\n if (error instanceof SubFailedError) {\n return Exit.fail(new SubFailed({ cause: error.cause }));\n }\n return exitOf<never>(error) as Exit.Exit<never, StoreFailed>;\n}\n","/**\n * The Effect engine's run handle (#308, ruling R4.1 of #325). Its members have\n * the Promise engine's names (the shared-handle ruling of #250); only the\n * return types differ. Each member that returns a Promise there returns an\n * Effect here, with a typed error channel a host reads with `Effect.catchTags`.\n * Members that neither wait nor fail — `getState`, `result` and the listener\n * registrations — are the same functions on both engines.\n */\n\nimport { Effect } from \"effect\";\nimport {\n type LiveWorkProbe,\n liveWork,\n type Postable,\n post,\n} from \"../internal/engine/loop\";\nimport type { Port } from \"../pure/core\";\nimport type { DispatchSettle, Runtime } from \"../runtime-types\";\nimport {\n doneExitOf,\n exitOf,\n type Stopped,\n type StoreFailed,\n type SubFailed,\n} from \"./failures\";\n\n/**\n * What the Effect engine's `run` yields while boot is in flight. `Err` is the\n * union of the declared failures of the run's hand-written cells: a dispatch\n * whose Cmd's cell fails with one fails with it. Reading State and waiting for\n * quiescence need boot to have run, so they live on the {@link EffectRuntime}\n * that `ready` succeeds with.\n */\nexport interface EffectBootingRuntime<\n S,\n M extends { type: string },\n E extends { type: string } = never,\n Err = never,\n> {\n /**\n * Fold `msg` and run to quiescence, as the Promise engine's `dispatch` does.\n * Fails with `Stopped` once the run is stopping or stopped, `StoreFailed`\n * when the transition's save fails, and a hand-written cell's declared\n * failure when the transition's Cmd fails with one.\n */\n dispatch(\n msg: M,\n opts?: { readonly settle?: DispatchSettle },\n ): Effect.Effect<void, Err | Stopped | StoreFailed>;\n /** `dispatch(msg, { settle: \"once\" })`: one transition, no follow-up drain. */\n dispatchOnce(msg: M): Effect.Effect<void, Err | Stopped | StoreFailed>;\n /**\n * A zero-arg change notifier, fired for each applied transition after its\n * save and before its first Cmd's handler starts.\n */\n subscribe(listener: () => void): () => void;\n /** A `(msg, state)` hook, fired after each applied transition's Cmds. */\n observe(observer: (msg: M, state: S) => void): () => void;\n /** Fires once with the initial State — at once if boot already ran. */\n onBoot(handler: (state: S) => void): () => void;\n /** Subscribe to the semantic event of `type` the run's `events` projects. */\n on<K extends E[\"type\"]>(\n type: K,\n handler: (event: Extract<E, { type: K }>) => void,\n ): () => void;\n /** Subscribe to a typed Port. */\n subscribePort<T>(port: Port<T>, listener: (value: T) => void): () => void;\n /** Emit a value on a Port from outside a Cmd handler. */\n emitPort<T>(port: Port<T>, value: T): void;\n /**\n * Succeeds with the booted handle once boot completes. Fails with\n * `StoreFailed` when the saved state could not be restored (`\"load\"`) or the\n * initial save failed (`\"save\"`), and with a hand-written cell's declared\n * failure when a boot Cmd fails with one.\n */\n readonly ready: Effect.Effect<EffectRuntime<S, M, E, Err>, Err | StoreFailed>;\n /**\n * Stop the run: drain in-flight work, up to `disposeTimeoutMs`, stop its\n * Subs, flush the final State. `stop()` interrupts every handler still in\n * flight, so the limit is reached only by a handler that cannot be\n * interrupted. Closing the run's Scope stops the run the same way, and\n * `stop()` leaves that Scope open. So for a child under `spawn` the two\n * differ: `stop()` does not take the child out of the host's table or tell\n * the parent. Stop such a child with the `stop` helper.\n */\n stop(): Effect.Effect<void>;\n}\n\n/**\n * The Effect engine's booted handle. `ready` on a real run succeeds with an\n * {@link EffectRun}, which is one of these and carries what `tell` asks for.\n */\nexport interface EffectRuntime<\n S,\n M extends { type: string },\n E extends { type: string } = never,\n Err = never,\n> extends EffectBootingRuntime<S, M, E, Err> {\n /** The current State. Total. */\n getState(): S;\n readonly ready: Effect.Effect<EffectRuntime<S, M, E, Err>, Err | StoreFailed>;\n /** Succeeds once every dispatched Msg and its follow-ups are processed. */\n idle(): Effect.Effect<void>;\n /**\n * The saved terminal State, or `undefined` while its save is pending or failed.\n * Without a store, answers as soon as State is set. A run a failure closed\n * reads `undefined` here; `done()` says why.\n */\n result(): S | undefined;\n /**\n * Succeeds with the terminal State only once it is saved. When a failure\n * closes the run short of a terminal State, it fails with that failure, for\n * waiting and later callers: a failed terminal save with `StoreFailed`\n * (`operation: \"save\"`), a Sub failure with `SubFailed`, and a reducer throw\n * under the `stop` strategy with a defect, the thrown value itself. The defect\n * is outside the error channel, so `Effect.catchTags` does not see it and\n * `Effect.catchDefect` or the fiber's `Exit` does. The Msgs already in line\n * fold first, so one of them reaching a terminal State makes that State the\n * answer. A terminal State already saved keeps its answer whatever fails\n * after it. A `stop()` with no failure leaves this waiting. Without a store,\n * answers as soon as State is set.\n */\n done(): Effect.Effect<S, StoreFailed | SubFailed>;\n}\n\n/**\n * The handle `run` yields: an {@link EffectBootingRuntime} whose `ready` succeeds\n * with an {@link EffectRun}. It does not compile as `tell`'s argument: wait for\n * `ready`.\n */\nexport interface EffectBootingRun<\n S,\n M extends { type: string },\n E extends { type: string } = never,\n Err = never,\n> extends EffectBootingRuntime<S, M, E, Err> {\n readonly ready: Effect.Effect<EffectRun<S, M, E, Err>, Err | StoreFailed>;\n}\n\n/**\n * What an {@link EffectBootingRun}'s `ready` succeeds with: a booted\n * {@link EffectRuntime} that also carries the run's line, which is what `tell`\n * asks for. A spread keeps the line. An object built by hand cannot have one,\n * because tea does not export its key, so an {@link EffectRuntime} that is not one\n * of these does not compile as `tell`'s argument. It fits anywhere an\n * `EffectRuntime` is asked for.\n */\nexport interface EffectRun<\n S,\n M extends { type: string },\n E extends { type: string } = never,\n Err = never,\n> extends EffectRuntime<S, M, E, Err>,\n Postable<S, M> {\n readonly ready: Effect.Effect<EffectRun<S, M, E, Err>, Err | StoreFailed>;\n}\n\n/** Run a loop call, ending with the typed Exit its rejection maps to. */\nfunction settled<A, Err>(\n call: () => Promise<A>,\n): Effect.Effect<A, Err | Stopped | StoreFailed> {\n return Effect.flatten(\n Effect.promise(() =>\n call().then(\n (value) => Effect.succeed(value),\n (error: unknown) => exitOf<Err>(error),\n ),\n ),\n );\n}\n\n/** Build the Effect handle over the loop's Promise one. */\nexport function effectHandle<\n S,\n M extends { type: string },\n E extends { type: string },\n Err,\n>(\n loop: Runtime<S, M, E> & LiveWorkProbe & Postable<S, M>,\n): EffectRun<S, M, E, Err> & LiveWorkProbe {\n const handle: EffectRun<S, M, E, Err> & LiveWorkProbe = {\n [liveWork]: loop[liveWork],\n [post]: loop[post],\n dispatch: (msg, opts) => settled(() => loop.dispatch(msg, opts)),\n dispatchOnce: (msg) => settled(() => loop.dispatchOnce(msg)),\n subscribe: (listener) => loop.subscribe(listener),\n observe: (observer) => loop.observe(observer),\n onBoot: (handler) => loop.onBoot(handler),\n on: (type, handler) => loop.on(type, handler),\n subscribePort: (port, listener) => loop.subscribePort(port, listener),\n emitPort: (port, value) => loop.emitPort(port, value),\n getState: () => loop.getState(),\n result: () => loop.result(),\n // Boot never passes the dispatch gate, so `ready` never fails `Stopped`.\n // Suspended: `handle` is not built yet when this line runs.\n ready: Effect.suspend(() =>\n settled<unknown, Err>(() => loop.ready).pipe(Effect.as(handle)),\n ) as Effect.Effect<EffectRun<S, M, E, Err>, Err | StoreFailed>,\n // `idle` rejects only on a livelock, and `stop` never does.\n idle: () => Effect.promise(() => loop.idle()),\n done: () =>\n Effect.flatten(\n Effect.promise(() =>\n loop.done().then((state) => Effect.succeed(state), doneExitOf),\n ),\n ),\n stop: () => Effect.promise(() => loop.stop()),\n };\n return handle;\n}\n","/**\n * The Effect engine's `run` (#283): the same machine the Promise engine runs,\n * with Effect handlers, Effect sub runners, the caller's Layers and\n * interruption on stop.\n *\n * It runs the one core loop both engines share (`src/internal/engine/loop.ts`)\n * with the same built-ins in the same order, so a machine folds the same way on\n * either engine. What this file adds is the Effect edge:\n *\n * - an `interpret` cell returns `Effect<Ok, E, R>`, read with\n * `Effect.result`: a success settles `_ok`, a declared failure settles\n * `_err`, a defect goes to the error sink;\n * - a `subscribe` runner returns a `Stream<Msg>`, drained on its own fiber;\n * - every handler and runner fiber runs with the services the caller\n * provided, so `R` reaches them from the caller's Layers;\n * - stopping interrupts every handler fiber still in flight;\n * - the handle's verbs are Effects with a typed error channel\n * (`./handle`, `./failures`).\n */\n\nimport {\n Cause,\n Context,\n Effect,\n Exit,\n Fiber,\n Result,\n Scope,\n Stream,\n} from \"effect\";\nimport {\n builtinExtensions,\n refuseFencedRunWithoutSink,\n} from \"../internal/engine/builtins\";\nimport {\n defaultOnError,\n type LiveWorkProbe,\n type LoopHandler,\n type LoopRunner,\n type Postable,\n startLoop,\n} from \"../internal/engine/loop\";\nimport type {\n BuiltinSub,\n BuiltinSubType,\n Cmd,\n DeclaredErrorsOf,\n ErrorsOf,\n Machine,\n OkOfCmd,\n Sub,\n TimerSub,\n} from \"../pure/core\";\nimport { applyCell, Outcome, subEntriesOf } from \"../pure/core\";\nimport type {\n CtxArg,\n Runtime,\n StoreAndOnError,\n Supervision,\n TelemetrySink,\n} from \"../runtime-types\";\nimport { spawnedScope } from \"./children\";\nimport { CellFailure, saveFailures, unwrapped } from \"./failures\";\nimport { type EffectBootingRun, effectHandle } from \"./handle\";\n\n// === The handler and runner shapes ===\n\n/**\n * One Effect `interpret` cell. For a `Cmd.define`d Cmd it succeeds with the\n * Cmd's `Ok` and fails with one of its declared tags, and the engine mints\n * `<name>_ok` / `<name>_err` from that (ADR 0021). For a hand-written Cmd it\n * succeeds with a follow-up Msg, a list of them (dispatched in order), or\n * nothing, and its failure fails the dispatch with that error, typed on the\n * handle (see {@link CellErrors}). `R` is whatever services it reads.\n */\nexport type EffectInterpretCell<\n M extends { type: string },\n C extends Cmd,\n R = unknown,\n> =\n unknown extends ErrorsOf<C>\n ? // biome-ignore lint/suspicious/noConfusingVoidType: a hand-written Cmd's handler may settle with nothing\n (cmd: C) => Effect.Effect<M | readonly M[] | void, unknown, R>\n : (cmd: C) => Effect.Effect<OkOfCmd<C>, DeclaredErrorsOf<C>, R>;\n\n/**\n * The Effect engine's `interpret` map: one cell per Cmd variant, each reading\n * services within `R`.\n */\nexport type EffectInterpret<\n M extends { type: string },\n C extends Cmd,\n R = unknown,\n> = {\n readonly [K in C[\"type\"]]: EffectInterpretCell<M, Extract<C, { type: K }>, R>;\n};\n\n/**\n * One Effect sub runner: the Sub in, a `Stream` of Msgs out. The engine drains\n * it on its own fiber and interrupts that fiber when the Sub stops.\n *\n * A Sub maps the errors it expects into Msgs itself, the way an Elm `Sub msg`\n * carries no error type: `Stream.catchTag(\"Closed\", () => Stream.make({ type:\n * \"disconnected\" }))`. A stream that still fails reaches the error sink under\n * `\"sub\"` and stops the run (#309), and `done()` fails with `SubFailed`.\n */\nexport type EffectRunner<\n M extends { type: string },\n U extends Sub,\n R = unknown,\n> = (sub: U) => Stream.Stream<M, unknown, R>;\n\n/**\n * The Effect engine's `subscribe` map: a runner for every Sub type the machine\n * declares beyond the built-ins, and optionally one for a built-in, which\n * replaces the engine's own (#270 R2.1).\n */\nexport type EffectSubscribe<\n M extends { type: string },\n U extends Sub,\n R = unknown,\n> = {\n readonly [K in Exclude<U[\"type\"], BuiltinSubType>]: EffectRunner<\n M,\n Extract<U, { type: K }>,\n R\n >;\n} & {\n readonly [K in BuiltinSubType]?: EffectRunner<\n M,\n Extract<BuiltinSub<M>, { type: K }>,\n R\n >;\n};\n\n/** The services every cell of an `interpret` map reads. */\nexport type InterpretServices<I> = {\n [K in keyof I]: ServicesOfCell<I[K]>;\n}[keyof I];\n\ntype ServicesOfCell<F> = F extends (\n cmd: never,\n) => Effect.Effect<unknown, unknown, infer R>\n ? R\n : never;\n\n/**\n * The failures a dispatch on the handle can end with from an `interpret` map:\n * the error type of every hand-written Cmd's cell. A `Cmd.define`d Cmd's\n * declared failure settles its `_err` Msg instead, so it adds nothing here.\n */\nexport type CellErrors<C extends Cmd, I> = {\n [K in keyof I & C[\"type\"]]: unknown extends ErrorsOf<Extract<C, { type: K }>>\n ? ErrorOfCell<I[K]>\n : never;\n}[keyof I & C[\"type\"]];\n\ntype ErrorOfCell<F> = F extends (\n cmd: never,\n) => Effect.Effect<unknown, infer Err, unknown>\n ? Err\n : never;\n\n/** The services every runner of a `subscribe` map reads. */\nexport type SubscribeServices<B> = {\n [K in keyof B]-?: ServicesOfRunner<NonNullable<B[K]>>;\n}[keyof B];\n\ntype ServicesOfRunner<F> = F extends (\n sub: never,\n) => Stream.Stream<unknown, unknown, infer R>\n ? R\n : never;\n\n// `interpret` is optional for a machine that emits no Cmd, and `subscribe` for\n// one that declares only built-in Subs — the same requiredness the Promise\n// engine's `RunHandlers` carries.\ntype InterpretOption<C extends Cmd, I> = [C] extends [Cmd<never>]\n ? { readonly interpret?: I }\n : { readonly interpret: I };\n\nexport type SubscribeOption<U extends Sub, B> = [\n Exclude<U[\"type\"], BuiltinSubType>,\n] extends [never]\n ? { readonly subscribe?: B }\n : { readonly subscribe: B };\n\n/**\n * The options of the Effect engine's `run`. Beside `interpret` and\n * `subscribe`, they are the Promise engine's, with the same meaning; see `run`\n * in `@demlik/tea/promise` for each one's contract.\n */\nexport type EffectRunOptions<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n E extends { type: string },\n I,\n B,\n> = CtxArg<Ctx> &\n InterpretOption<C, I> &\n SubscribeOption<U, B> &\n StoreAndOnError<S> & {\n /** Stamps `at` on minted Msgs and telemetry. Defaults to `Date.now`. */\n readonly clock?: () => number;\n /** Projects each applied transition to the public events `on` serves. */\n readonly events?: (msg: M, state: S) => readonly E[];\n /** The policy for a reducer throw. Defaults to `\"stop\"`. */\n readonly supervision?: Supervision<S, M>;\n /** The terminal predicate `result()` and `done()` read. */\n readonly terminal?: (state: S) => boolean;\n /** A fire-and-forget sink for every applied transition. */\n readonly telemetry?: TelemetrySink;\n /**\n * How long `stop()` waits for the queue, and then for async teardown,\n * before giving up on each. Defaults to 5_000ms. `stop()` interrupts every\n * handler here, so the first wait ends early unless a handler cannot be\n * interrupted.\n *\n * A store call is not interrupted. tea starts none after `stop()` has\n * returned, but one already under way when `stop()` gave up can finish\n * after it. On a plain store that late save can overwrite what a newer\n * run saved; a fenced store refuses it. So a host that starts a new run on\n * the same store after a `stop()` that gave up needs a fenced store.\n */\n readonly disposeTimeoutMs?: number;\n };\n\n// === Built-in runners ===\n\n/** `timer`: emit `deps.msg` once, `deps.ms` after the Sub starts. */\nfunction timer<M>(sub: TimerSub<M>): Stream.Stream<M> {\n return Stream.fromEffect(Effect.as(Effect.sleep(sub.deps.ms), sub.deps.msg));\n}\n\n/** The Effect engine's built-in runners, keyed by the Sub type each runs. */\nexport const builtinRunners = { timer } as const satisfies Record<\n BuiltinSubType,\n unknown\n>;\n\n// === Settling a cell ===\n\n/**\n * The Effect a cell's Effect is run as: `Effect.result` makes a declared\n * failure a value, so only a defect or an interruption is left in the Exit's\n * cause. Read the Exit back with {@link settleCellExit}.\n */\nexport function cellResult(\n effect: Effect.Effect<unknown, unknown>,\n): Effect.Effect<Result.Result<unknown, unknown>> {\n return Effect.result(effect);\n}\n\n/**\n * What one cell's run settles to, the rule the engine and the Effect `drive`\n * share so they cannot drift:\n *\n * - interrupted — `undefined`, nothing to dispatch;\n * - a defect — thrown;\n * - a `Cmd.define`d Cmd (`defined`) — its success as `Outcome.ok`, its\n * declared failure as `Outcome.err`, for the `Cmd.define` edge to mint;\n * - a hand-written Cmd — its success (the follow-up Msgs) as is, its failure\n * thrown as a {@link CellFailure}, so a caller types it.\n */\nexport function settleCellExit(\n exit: Exit.Exit<unknown, unknown>,\n defined: boolean,\n): unknown {\n if (Exit.isFailure(exit)) {\n if (Cause.hasInterruptsOnly(exit.cause)) return undefined;\n throw Cause.squash(exit.cause);\n }\n const result = exit.value as Result.Result<unknown, unknown>;\n if (defined) {\n return Result.isSuccess(result)\n ? Outcome.ok(result.success)\n : Outcome.err(result.failure);\n }\n if (Result.isFailure(result)) throw new CellFailure(result.failure);\n return result.success;\n}\n\n// === run ===\n\n/**\n * Run `machine` on the Effect engine. The returned Effect needs a `Scope` and\n * the services its handlers and runners read, and yields the run handle. The\n * handle's members have the Promise engine's names; the ones that return a\n * Promise there return an Effect here, failing with `Stopped`, `StoreFailed`\n * or a hand-written cell's declared failure, so a host sorts them with\n * `Effect.catchTags`.\n *\n * Closing the scope stops the run and its Subs: every handler still in flight\n * is interrupted (its finalizers run), its Msg is never dispatched, and a\n * dispatch after stop fails with `Stopped`. The handle's `stop()` does the\n * same.\n *\n * A Sub whose Stream fails with an error it did not map to a Msg stops this run\n * alone. The run forks its own child of that scope and closes only the child,\n * so the rest of the scope stays open, and `done()` fails with `SubFailed`. A\n * handler or runner that reads `Scope.Scope` gets that child, so what it acquires\n * is released with the run. With no `onError`, the default sink still rethrows\n * the failure and ends the process. A reducer throw under the default `stop`\n * strategy closes that same child, so the run stops and `done()` fails with\n * what was thrown, as a defect (ADR 0033).\n *\n * A fenced store is fenced here exactly as on the Promise engine: the check is\n * one of the built-ins both engines run. A run on a store that is fenced at\n * runtime with no `onError` dies at start, before anything is loaded, even when\n * the store is typed plain `Store<S>` (ADR 0031).\n *\n * ```ts\n * const program = Effect.gen(function* () {\n * const handle = yield* run(machine, {\n * interpret: {\n * fetch: (cmd) =>\n * Effect.gen(function* () {\n * const api = yield* Api;\n * return yield* api.get(cmd.id);\n * }),\n * },\n * });\n * const runtime = yield* handle.ready;\n * yield* runtime.dispatch({ type: \"go\", id: \"7\" });\n * return runtime.getState();\n * });\n * Effect.runPromise(program.pipe(Effect.scoped, Effect.provide(ApiLive)));\n * ```\n */\nexport function run<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n // The defaults read no services, so an omitted map adds none to `R`.\n I extends EffectInterpret<M, C> = EffectInterpret<M, C, never>,\n B extends EffectSubscribe<M, U> = EffectSubscribe<M, U, never>,\n E extends { type: string } = never,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: EffectRunOptions<S, M, C, U, Ctx, E, I, B>,\n): Effect.Effect<\n EffectBootingRun<S, M, E, CellErrors<C, I>>,\n never,\n Scope.Scope | InterpretServices<I> | SubscribeServices<B>\n> {\n return Effect.gen(function* () {\n // A defect, before the run's scope exists, so nothing is loaded or saved.\n // It reads the caller's own `onError`: the loop only ever sees the wrapped one.\n yield* Effect.sync(() =>\n refuseFencedRunWithoutSink(opts.store, opts.onError),\n );\n const services = yield* Effect.context<\n InterpretServices<I> | SubscribeServices<B>\n >();\n const callerScope = yield* Scope.Scope;\n // The run's own scope: the caller's closing closes it, and a Sub failure\n // closes it and leaves the caller's open (ADR 0030).\n const scope = yield* Scope.fork(callerScope);\n // Handlers and runners read that scope as `Scope.Scope`, so what they acquire\n // is released with this run and not left in the caller's.\n const runServices = Context.add(services, Scope.Scope, scope);\n const fork = Effect.runForkWith(runServices) as ForkEffect;\n return yield* Effect.acquireRelease(\n Effect.sync(() =>\n effectHandle<S, M, E, CellErrors<C, I>>(\n start<S, M, C, U, Ctx, E>(\n machine,\n opts as EffectRunOptions<S, M, C, U, Ctx, E, unknown, unknown>,\n Effect.runPromiseExitWith(runServices) as RunEffect,\n fork,\n // Closing the Scope runs the release below, which stops the run.\n // A Scope that closes before that release is added runs it on add.\n (exit) =>\n fork(Scope.close(spawnedScope(callerScope) ?? scope, exit)),\n ),\n ),\n ),\n (runtime) => runtime.stop(),\n ).pipe(Scope.provide(scope));\n });\n}\n\n/** Run an erased Effect to its Exit, interruptible through `signal`. */\ntype RunEffect = (\n effect: Effect.Effect<unknown, unknown>,\n options: { readonly signal: AbortSignal },\n) => Promise<Exit.Exit<unknown, unknown>>;\n\n/** Fork an erased Effect onto its own fiber. */\ntype ForkEffect = (\n effect: Effect.Effect<unknown, unknown>,\n) => Fiber.Fiber<unknown, unknown>;\n\ntype AnyCell = (cmd: unknown) => Effect.Effect<unknown, unknown>;\ntype AnyRunner = (sub: Sub) => Stream.Stream<unknown, unknown>;\n\nfunction start<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n E extends { type: string },\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: EffectRunOptions<S, M, C, U, Ctx, E, unknown, unknown>,\n runEffect: RunEffect,\n fork: ForkEffect,\n closeScope: (exit: Exit.Exit<never, unknown>) => void,\n): Runtime<S, M, E> & LiveWorkProbe & Postable<S, M> {\n const interpret = ((opts as { interpret?: unknown }).interpret ??\n {}) as Readonly<Record<string, AnyCell | undefined>>;\n const subscribe = ((opts as { subscribe?: unknown }).subscribe ??\n {}) as Readonly<Record<string, AnyRunner | undefined>>;\n const builtins = builtinRunners as unknown as Readonly<\n Record<string, AnyRunner | undefined>\n >;\n const defined = new Set((machine.cmds ?? []).map((def) => def.cmdType));\n\n // Aborted on stop: every handler fiber runs under this signal, so aborting\n // it interrupts all of them at once.\n const interruption = new AbortController();\n\n // Interrupted by stop, a cell settles to nothing. A defect throws: for a\n // defined Cmd the `Cmd.define` edge routes it to the error sink, for a\n // hand-written one it rejects the dispatch. A hand-written cell's failure is\n // marked, so the handle fails the dispatch with it as a typed error.\n async function settle(cell: AnyCell, cmd: unknown): Promise<unknown> {\n const type = (cmd as { type: string }).type;\n const exit = await runEffect(cellResult(cell(cmd)), {\n signal: interruption.signal,\n });\n return settleCellExit(exit, defined.has(type));\n }\n\n function handlerFor(type: string): LoopHandler<M> | undefined {\n const cell = interpret[type];\n if (cell === undefined) return undefined;\n return (cmd) => settle(cell, cmd);\n }\n\n // The Cause the run's own Scope closes with when a Sub fails: the Stream's own\n // when a Stream failed, else the runner's throw as a defect.\n let subFailure: Cause.Cause<unknown> | undefined;\n\n // A runner's Msgs reach the loop through its `dispatch`, which queues each\n // one behind the step in progress — never a transition on the stream's own\n // fiber (spike #260). A Stream that fails with anything but an interrupt\n // failed with an error the Sub did not map to a Msg, and the run stops\n // (#309).\n function runnerFor(type: string): LoopRunner<Ctx, M> | undefined {\n const make = subscribe[type] ?? builtins[type];\n if (make === undefined) return undefined;\n return (sub, _ctx, dispatch, fail) => {\n const drain = Stream.runForEach(make(sub), (msg) =>\n Effect.sync(() => dispatch(msg as M)),\n ).pipe(\n Effect.catchCause((cause) =>\n Cause.hasInterruptsOnly(cause)\n ? Effect.void\n : Effect.sync(() => {\n subFailure ??= cause;\n fail(Cause.squash(cause));\n }),\n ),\n );\n const fiber = fork(drain);\n return () => Effect.runPromise(Fiber.interrupt(fiber));\n };\n }\n\n // The sink is handed what was thrown, never the edge's marker around it.\n const sink = opts.onError ?? defaultOnError;\n\n const handle = startLoop<S, M, C, Ctx>({\n init: machine.init,\n reduce: (state, msg) => applyCell<S, M, C>(machine, state, msg),\n subs: subEntriesOf<S>(machine),\n runnerFor,\n handlerFor,\n store: opts.store,\n ctx: (opts.ctx ?? {}) as Ctx,\n onError: (error, context) => sink(unwrapped(error), context),\n disposeTimeoutMs: opts.disposeTimeoutMs ?? 5_000,\n idleCap: 100_000,\n // The save marker wraps outermost, around the fence the built-ins add.\n extensions: [\n saveFailures<S, M, C>(),\n ...builtinExtensions<S, M, C, E>(machine, opts),\n ],\n // A Sub failure closes the run's Scope with the Stream's own Cause, and a\n // halting reducer throw with what was thrown as a defect (ADR 0033).\n stopOnFailure: (failure) =>\n closeScope(\n Exit.failCause(\n (failure.kind === \"sub\" ? subFailure : undefined) ??\n Cause.die(failure.error),\n ),\n ),\n });\n\n // Stop closes the gate first, so nothing an interrupted handler leaves\n // behind is dispatched, then interrupts every handler still in flight so the\n // drain it waits on can finish.\n const stopLoop = handle.stop;\n handle.stop = () => {\n const stopped = stopLoop();\n interruption.abort();\n return stopped;\n };\n return handle as unknown as Runtime<S, M, E> & LiveWorkProbe & Postable<S, M>;\n}\n"]}
@@ -1,5 +1,5 @@
1
- import { fromInterval } from './chunk-VHKDUBXC.js';
2
- import { subId } from './chunk-GAVLND5C.js';
1
+ import { fromInterval } from './chunk-BVFS3ICC.js';
2
+ import { subId } from './chunk-A3QPMX5M.js';
3
3
 
4
4
  // src/internal/resilience/deadline/index.ts
5
5
  function deadlines(list) {
@@ -100,5 +100,5 @@ function cacheEvictionSub(name, everyMs) {
100
100
  var cacheEvictionSubscribe = fromInterval((sub) => cacheEvictMsg(sub.deps.name));
101
101
 
102
102
  export { cacheEvictMsg, cacheEvictionSub, cacheEvictionSubscribe, deadlineExceeded, deadlineMsgType, deadlineSub, deadlines, deadlinesSub, evictExpired, get, has, initCache, nextTimer, remove, set, setTimeoutArmTimer, subscribeDeadline, subscribeWith };
103
- //# sourceMappingURL=chunk-QIE4Y6BK.js.map
104
- //# sourceMappingURL=chunk-QIE4Y6BK.js.map
103
+ //# sourceMappingURL=chunk-UGTC33VH.js.map
104
+ //# sourceMappingURL=chunk-UGTC33VH.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/internal/resilience/deadline/index.ts","../src/internal/resilience/cache/index.ts"],"names":[],"mappings":";;;;AA8FO,SAAS,UACd,IAAA,EACkC;AAClC,EAAA,OAAO,IAAA,CAAK,MAAA,KAAW,CAAA,GAAI,IAAA,GAAO,IAAA;AACpC;AAQO,SAAS,aACd,MAAA,EACiC;AACjC,EAAA,OAAO,EAAE,IAAA,EAAM,UAAA,EAAY,IAAA,EAAM,CAAC,UAAU,SAAA,CAAU,MAAA,CAAO,KAAK,CAAC,CAAA,EAAE;AACvE;AAiFO,SAAS,WAAA,CACd,EAAA,EACA,IAAA,EACA,IAAA,EACgB;AAChB,EAAA,OAAO,EAAE,GAAG,IAAA,EAAM,EAAA,EAAI,MAAM,EAAE,CAAA,EAAG,IAAA,EAAM,UAAA,EAAY,IAAA,EAAK;AAC1D;AA+CO,SAAS,cACd,QAAA,EAKW;AACX,EAAA,OAAO,CAAC,GAAA,EAAK,IAAA,EAAM,QAAA,KAAa;AAC9B,IAAA,MAAM,OAAA,GAAU,IAAI,IAAA,CAAK,GAAA;AAAA,MAAI,CAAC,QAAA,KAC5B,QAAA;AAAA,QACE,QAAA,CAAS,EAAA;AAAA,QACT,QAAA,CAAS,IAAA;AAAA,QACT,iBAAiB,QAAA,CAAS,EAAA,EAAI,QAAA,CAAS,IAAA,EAAM,SAAS,IAAI,CAAA;AAAA,QAC1D;AAAA;AACF,KACF;AACA,IAAA,OAAO,MAAM;AACX,MAAA,KAAA,MAAW,MAAA,IAAU,SAAS,MAAA,EAAO;AAAA,IACvC,CAAA;AAAA,EACF,CAAA;AACF;AAWO,SAAS,kBAAA,GAEmB;AACjC,EAAA,OAAO,CAAC,GAAA,EAAK,IAAA,EAAM,GAAA,EAAK,QAAA,KAAa;AAGnC,IAAA,MAAM,MAAA,GAAS,UAAA;AAAA,MACb,MAAM,SAAS,GAAG,CAAA;AAAA,MAClB,KAAK,GAAA,CAAI,CAAA,EAAG,IAAA,GAAO,IAAA,CAAK,KAAK;AAAA,KAC/B;AACA,IAAA,OAAO,MAAM,aAAa,MAAM,CAAA;AAAA,EAClC,CAAA;AACF;AAkBO,IAAM,iBAAA,GAIE,CACb,GAAA,EACA,GAAA,EACA,QAAA,KACG,aAAA,CAAiB,kBAAA,EAAuB,CAAA,CAAE,GAAA,EAAK,GAAA,EAAK,QAAQ;AAO1D,SAAS,gBAAA,CACd,EAAA,EACA,IAAA,EACA,IAAA,EACqB;AACrB,EAAA,OAAO,EAAE,IAAA,EAAM,eAAA,CAAgB,IAAI,CAAA,EAAG,IAAI,IAAA,EAAK;AACjD;AASO,SAAS,gBACd,IAAA,EACoB;AACpB,EAAA,OACE,IAAA,KAAS,MAAA,GAAY,mBAAA,GAAsB,CAAA,EAAG,IAAI,CAAA,SAAA,CAAA;AAEtD;AAiBO,SAAS,SAAA,CACd,MACA,KAAA,EACuC;AACvC,EAAA,IAAI,OAAA;AACJ,EAAA,KAAA,MAAW,YAAY,IAAA,EAAM;AAC3B,IAAA,IAAI,OAAA,KAAY,MAAA,IAAa,QAAA,CAAS,IAAA,GAAO,QAAQ,IAAA,EAAM;AACzD,MAAA,OAAA,GAAU,QAAA;AAAA,IACZ;AAAA,EACF;AACA,EAAA,IAAI,OAAA,KAAY,QAAW,OAAO,IAAA;AAClC,EAAA,OAAO;AAAA,IACL,IAAI,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,OAAA,CAAQ,OAAO,KAAK,CAAA;AAAA,IACpC,KAAK,gBAAA,CAAiB,OAAA,CAAQ,IAAI,OAAA,CAAQ,IAAA,EAAM,QAAQ,IAAI;AAAA,GAC9D;AACF;;;AC9SO,SAAS,SAAA,GAA4B;AAC1C,EAAA,OAAO,EAAE,OAAA,EAAS,EAAC,EAAE;AACvB;AAQA,SAAS,SAAA,CAAa,OAAsB,KAAA,EAAwB;AAClE,EAAA,OAAO,SAAS,KAAA,CAAM,WAAA;AACxB;AAQO,SAAS,GAAA,CACd,KAAA,EACA,GAAA,EACA,KAAA,EACe;AACf,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA;AAC/B,EAAA,IAAI,UAAU,MAAA,IAAa,SAAA,CAAU,KAAA,EAAO,KAAK,GAAG,OAAO,MAAA;AAC3D,EAAA,OAAO,KAAA,CAAM,KAAA;AACf;AAMO,SAAS,GAAA,CACd,KAAA,EACA,GAAA,EACA,KAAA,EACS;AACT,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA;AAC/B,EAAA,IAAI,KAAA,KAAU,QAAW,OAAO,KAAA;AAChC,EAAA,OAAO,CAAC,SAAA,CAAU,KAAA,EAAO,KAAK,CAAA;AAChC;AAQO,SAAS,GAAA,CACd,KAAA,EACA,GAAA,EACA,KAAA,EACA,OACA,KAAA,EACa;AACb,EAAA,MAAM,WAAA,GAA6C,EAAE,GAAG,KAAA,CAAM,OAAA,EAAQ;AACtE,EAAA,WAAA,CAAY,GAAG,CAAA,GAAI,EAAE,KAAA,EAAO,WAAA,EAAa,QAAQ,KAAA,EAAM;AACvD,EAAA,OAAO,EAAE,GAAG,KAAA,EAAO,OAAA,EAAS,WAAA,EAAY;AAC1C;AAOO,SAAS,MAAA,CAAU,OAAoB,GAAA,EAA0B;AACtE,EAAA,MAAM,WAAA,GAA6C,EAAE,GAAG,KAAA,CAAM,OAAA,EAAQ;AACtE,EAAA,OAAO,YAAY,GAAG,CAAA;AACtB,EAAA,OAAO,EAAE,GAAG,KAAA,EAAO,OAAA,EAAS,WAAA,EAAY;AAC1C;AASO,SAAS,YAAA,CACd,OACA,KAAA,EACa;AACb,EAAA,MAAM,cAA6C,EAAC;AACpD,EAAA,KAAA,MAAW,CAAC,KAAK,KAAK,CAAA,IAAK,OAAO,OAAA,CAAQ,KAAA,CAAM,OAAO,CAAA,EAAG;AACxD,IAAA,IAAI,SAAA,CAAU,KAAA,EAAO,KAAK,CAAA,EAAG;AAC7B,IAAA,WAAA,CAAY,GAAG,CAAA,GAAI,KAAA;AAAA,EACrB;AACA,EAAA,OAAO,EAAE,GAAG,KAAA,EAAO,OAAA,EAAS,WAAA,EAAY;AAC1C;AAmCO,SAAS,cAAc,EAAA,EAA2B;AACvD,EAAA,OAAO,EAAE,IAAA,EAAM,aAAA,EAAe,EAAA,EAAG;AACnC;AA8BO,SAAS,gBAAA,CACd,MACA,OAAA,EACwC;AACxC,EAAA,OAAO,EAAE,MAAM,OAAA,EAAS,IAAA,EAAM,OAAO,EAAE,IAAA,EAAM,UAAA,EAAY,OAAA,EAAQ,CAAA,EAAG;AACtE;AAYO,IAAM,sBAAA,GAIT,aAAa,CAAC,GAAA,KAAQ,cAAc,GAAA,CAAI,IAAA,CAAK,IAAI,CAAC","file":"chunk-QIE4Y6BK.js","sourcesContent":["/**\n * internal/resilience/deadline — a one-shot Sub that fires when an ABSOLUTE wall-clock\n * deadline is reached.\n *\n * Generalizes the recurring \"auto-fail a machine at T+N\" / \"15-minute stale\n * guard\" pattern: a deadline a battery lists only while it should be armed,\n * which dispatches a single `DeadlineExceeded` Msg when the wall clock crosses\n * `atMs`.\n *\n * A battery's `subs(slice)` returns its deadlines as a LIST (`DeadlineSub[]`,\n * one per retry timer, say). A machine declares that list as ONE Sub of type\n * `\"deadline\"` whose `deps` is the list (`deadlinesSub(select)`), and the\n * `deadline` runner arms every deadline in it. A change to the list restarts\n * the runner, which re-arms each deadline for its REMAINING time — `atMs` is\n * absolute, so a restart never moves a deadline.\n *\n * Difference from `fromTimeout` (relative — \"fire after N ms\"): a deadline is\n * an ABSOLUTE target. The delay is computed at subscribe time as\n * `max(0, atMs - Date.now())`. The consequence that matters: subscribing LATE —\n * e.g. after a `Store` rehydrate that resumes a machine whose deadline was set\n * before the page reloaded — still fires at the correct absolute moment,\n * because the remaining delay is recomputed from the current clock, not stored\n * in the Sub. A deadline already in the past at subscribe time yields delay `0`,\n * so `setTimeout(fn, 0)` fires on the NEXT tick (never synchronously inside the\n * reconcile pass — the substrate must finish wiring all subs before any Msg\n * lands).\n *\n * `deadlineSub` produces one deadline literal carrying `atMs`; the runner\n * (`subscribeDeadline`) translates each `atMs` → a relative delay and arms a\n * `setTimeout` / `clearTimeout` pair per deadline. The clock\n * read (`Date.now()`) lives only in the subscribe handler — never in a reducer\n * (invariant 2) — and is exercised in tests via vitest fake timers, matching\n * the no-injection convention of the `fromTimeout` / `fromInterval` exemplars.\n *\n * WHAT BACKS THE TIMER IS THE HOST'S CHOICE. `setTimeout` is the universal\n * default, but it is not universal: a Durable Object that hibernates has no\n * live `setTimeout` to wake it, so its deadline must be backed by a `do_alarm`\n * registered on the alarm registry; a test host wants a fake-timer schedule.\n * That backing is the `ArmTimer` seam — `subscribeWith(armTimer)` builds the\n * `subscribe.deadline` handler from a host-plugged timer, and `subscribeDeadline`\n * IS `subscribeWith(setTimeoutArmTimer())`. One deadline surface, three hosts;\n * there is no second Sub type and no second `atMs` anchor to keep in sync.\n *\n * The anchor is what makes the seam safe. `atMs` is ABSOLUTE, so whatever backs\n * it arms to a fixed wall-clock instant: the host computes the gap itself\n * (`atMs - Date.now()`), and on a host that just rehydrated after hibernation\n * that gap is the SHRUNKEN remainder — the deadline honours the original target\n * rather than resetting to a fresh full-length window.\n *\n * Strengthens invariant 4 (external time is a subscription — the deadline is a\n * `Sub` the runtime reconciles, never a `setTimeout` leaked into a reducer) and\n * invariant 7 (identity is explicit — the Sub carries a stable `SubId`, so the\n * reconcile pass leaves it running across transitions instead of churning it).\n */\n\nimport type {\n DepKeyedSub,\n Dispose,\n Sub,\n SubId,\n TimerDeps,\n} from \"../../../index\";\nimport { subId } from \"../../../index\";\n\n/**\n * One deadline, as a battery lists it. `atMs` is the absolute target — epoch\n * milliseconds (the `Date.now()` / `Date.parse(...)` scale), NOT a relative\n * delay. `id` names the deadline inside its list: it rides on the dispatched\n * Msg so a reducer can tell WHICH deadline fired, and a registry-backed timer\n * (a DO alarm slot) keys its entry on it.\n *\n * It is plain data — it becomes part of the `deadline` Sub's `deps`, which the\n * engine hashes — and is not itself a running Sub.\n */\nexport type DeadlineSub<N extends string | undefined = undefined> = {\n readonly id: SubId;\n readonly type: \"deadline\";\n readonly atMs: number;\n} & DeadlineOpts<N>;\n\n/**\n * The running `\"deadline\"` Sub: its `deps` is the non-empty list of deadlines\n * to arm. Name it in `types.sub` for a machine that arms deadlines.\n */\nexport type DeadlinesSub<N extends string | undefined = undefined> = Sub<\n \"deadline\",\n readonly DeadlineSub<N>[]\n>;\n\n/**\n * The `deps` of a `deadline` Sub: the list, or `null` when it is empty — an\n * empty list is a Sub with nothing to arm, and an off Sub is not a live one\n * (`driveToDone` reads live Subs to tell a waiting machine from a stalled one).\n */\nexport function deadlines<N extends string | undefined = undefined>(\n list: readonly DeadlineSub<N>[],\n): readonly DeadlineSub<N>[] | null {\n return list.length === 0 ? null : list;\n}\n\n/**\n * The `subs` entry that arms whatever deadlines `select` lists at a state:\n *\n * subs: [deadlinesSub((s: State) => rc.subs(s.resilience))],\n * // run(machine, { subscribe: { deadline: subscribeDeadline } })\n */\nexport function deadlinesSub<S, N extends string | undefined = undefined>(\n select: (state: S) => readonly DeadlineSub<N>[],\n): DepKeyedSub<S, DeadlinesSub<N>> {\n return { type: \"deadline\", deps: (state) => deadlines(select(state)) };\n}\n\n/**\n * Additive options the `deadlineSub` factory folds onto the Sub literal. This\n * is the extension seam (invariant 5 — composition by reduction): future fields\n * — `repeatMs` for a re-arming deadline, `jitterMs` for spread, etc. — land HERE\n * as optional members, and every existing call site keeps compiling because the\n * factory's third parameter is optional and the fields are `readonly` optionals\n * on `DeadlineSub`. Callers are insulated from the concrete `DeadlineSub` shape:\n * they ask for what they need by name, never positionally, so a shape extension\n * never rewrites the ~13 `deadlineSub(id, atMs)` call sites.\n *\n * The first field to land here is {@link DeadlineOpts.name} — the knob name the\n * dispatched Msg tag is derived from — and it landed exactly that way: one\n * optional member, zero rewritten call sites.\n */\nexport type DeadlineOpts<N extends string | undefined = undefined> = {\n /**\n * The knob this deadline belongs to. Present → the dispatched Msg's tag is\n * `` `${name}_deadline` ``; absent → the tag stays the bare\n * `\"deadline_exceeded\"` every machine wired before this field is already\n * handling. `N` is the name as a TYPE as well as a value, so a consumer's\n * cell key and the Msg type are derived from one place and cannot drift.\n *\n * Name a deadline when a machine arms more than one family of them. The Sub\n * *id* was already scoped (`` `${name}:deadline:${key}` ``); this scopes the\n * Msg the Sub dispatches, which is what a reducer cell keys off.\n */\n readonly name?: N;\n};\n\n/**\n * The Msg the deadline dispatches when the wall clock crosses `atMs`. Exported\n * as a tagged shape so consumers can union it into their machine's Msg type and\n * handle it in a reducer cell. `id` lets a reducer disambiguate WHICH deadline\n * fired when a machine arms several at once; `atMs` echoes the target the timer\n * was scheduled against (useful for assertions / observability).\n *\n * Snake-shaped `\"deadline_exceeded\"` matches the tagged-Msg style this module's\n * orchestrator requested; the actor-in-name PascalCase convention\n * (`TimerFiredDeadline`) is the consumer's to apply at their own boundary if\n * they prefer — `deadlineExceeded(...)` only fixes the wire shape, not the\n * consumer's local dialect.\n */\nexport type DeadlineExceeded<N extends string | undefined = undefined> = {\n readonly type: DeadlineMsgType<N>;\n readonly id: string;\n readonly atMs: number;\n};\n\n/**\n * The dispatched Msg's tag, derived from the deadline's optional name. A named\n * deadline speaks `` `${N}_deadline` ``; an unnamed one speaks the bare\n * `\"deadline_exceeded\"` literal, which is what `DeadlineExceeded` with no type\n * argument still means — so every machine wired before the name existed keeps\n * the exact type it had.\n */\nexport type DeadlineMsgType<N extends string | undefined> = N extends string\n ? `${N}_deadline`\n : \"deadline_exceeded\";\n\n/**\n * Build a deadline literal. Pure data — no clock read, no timer; the timer\n * is armed later by the `deadline` runner. `id` is branded via `subId(...)` so\n * accidental raw-string drift fails at the type level (invariant 7).\n *\n * List it from a battery's `subs(slice)` while the deadline should be armed;\n * drop it to disarm — the engine restarts the `deadline` Sub, whose cleanup\n * clears the pending timer before it fires. That is the \"cancel on state exit\"\n * lifecycle.\n *\n * @param id Identity for this deadline, echoed on the Msg it fires. Use\n * distinct ids for distinct deadlines on the same machine.\n * @param atMs Absolute target in epoch milliseconds (e.g. `Date.now() + 900_000`\n * for a 15-minute guard, or a persisted `expiresAt`).\n * @param opts Optional additive fields folded onto the Sub literal (see\n * {@link DeadlineOpts}). Omit it for the common case; pass it when a\n * future field (e.g. `repeatMs`) needs to ride on the deadline. The\n * optionality is the future-proofing seam — extending the shape\n * never breaks an existing `deadlineSub(id, atMs)` call site.\n */\nexport function deadlineSub<N extends string | undefined = undefined>(\n id: string,\n atMs: number,\n opts?: DeadlineOpts<N>,\n): DeadlineSub<N> {\n return { ...opts, id: subId(id), type: \"deadline\", atMs };\n}\n\n/**\n * The host-plugged timer backing. Given the deadline's stable `id`, its\n * absolute `atMs`, and the Msg to fire, arm the platform-appropriate timer\n * (DO alarm registry, `setTimeout`, fake timer) and return a cleanup that\n * cancels the pending fire.\n *\n * `id` is the deadline's own id — a registry-backed host (the DO alarm slot)\n * keys its entry on `id` so the cleanup deletes the EXACT entry it armed. A\n * `setTimeout`-backed host ignores `id` (the closure holds the handle).\n *\n * The host computes the gap itself: `atMs - Date.now()` is the REMAINING time.\n * On a host that just rehydrated after hibernation that gap is the shrunken\n * remainder — the deadline is NOT reset to its full length. That recomputation\n * is the reason `atMs` rides on the Sub as an absolute instant rather than a\n * delay: the anchor is what every backing agrees on.\n */\nexport type ArmTimer<M> = (\n id: SubId,\n atMs: number,\n msg: M,\n dispatch: (msg: M) => void,\n) => () => void;\n\n/**\n * Build the `deadline` runner from a host-plugged `armTimer`. This module owns\n * the deadline shape, the anchor, and the Msg; the HOST owns what backs the\n * timer. The runner arms every deadline in the Sub's `deps` list — reading\n * `id` + `atMs`, building the `deadlineExceeded(...)` Msg and handing all three\n * to `armTimer` — and its cleanup cancels them all.\n *\n * Use it when `setTimeout` is the wrong backing — most concretely a Durable\n * Object, which hibernates and must register a `do_alarm` instead:\n *\n * run(machine, {\n * subscribe: {\n * deadline: subscribeWith((id, atMs, msg, dispatch) =>\n * alarms.register(id, atMs, () => dispatch(msg)),\n * ),\n * },\n * })\n *\n * For the `setTimeout` default, use {@link subscribeDeadline} — it is exactly\n * `subscribeWith(setTimeoutArmTimer())`, so there is one deadline surface and\n * one anchor, not a second one per host.\n */\nexport function subscribeWith<N extends string | undefined = undefined>(\n armTimer: ArmTimer<DeadlineExceeded<N>>,\n): (\n sub: DeadlinesSub<N>,\n ctx: unknown,\n dispatch: (msg: DeadlineExceeded<N>) => void,\n) => Dispose {\n return (sub, _ctx, dispatch) => {\n const cancels = sub.deps.map((deadline) =>\n armTimer(\n deadline.id,\n deadline.atMs,\n deadlineExceeded(deadline.id, deadline.atMs, deadline.name),\n dispatch,\n ),\n );\n return () => {\n for (const cancel of cancels) cancel();\n };\n };\n}\n\n/**\n * The `setTimeout` timer backing — for node / browser / any host whose timer is\n * a plain `setTimeout`. Arms for the REMAINING time (`max(0, atMs - Date.now())`,\n * floored at 0 so an already-past deadline fires on the NEXT tick rather than\n * synchronously inside the reconcile pass), so a deadline re-derived after a\n * rehydrate — or re-armed by a restart — fires at the original instant, not a\n * fresh full window. A hibernating host does NOT use this — it plugs its own\n * `armTimer` (a `do_alarm` registration) into {@link subscribeWith}.\n */\nexport function setTimeoutArmTimer<\n N extends string | undefined = undefined,\n>(): ArmTimer<DeadlineExceeded<N>> {\n return (_id, atMs, msg, dispatch) => {\n // Recompute the remaining delay from the CURRENT clock so a late start\n // (post-rehydrate, or a restart) still targets the same absolute moment.\n const handle = setTimeout(\n () => dispatch(msg),\n Math.max(0, atMs - Date.now()),\n );\n return () => clearTimeout(handle);\n };\n}\n\n/**\n * The `deadline` runner for the DEFAULT `setTimeout` backing. Arms a one-shot\n * timer per listed deadline for its remaining delay `max(0, atMs - Date.now())`\n * and dispatches `deadlineExceeded(...)` when each fires; the cleanup clears\n * every pending timer.\n *\n * It is `subscribeWith(setTimeoutArmTimer())` — the default backing named, not\n * a separate implementation, so the host-plugged path and the default path can\n * never disagree about the anchor.\n *\n * run(machine, { subscribe: { deadline: subscribeDeadline } })\n *\n * Generic in the deadline's name so ONE runner serves every knob in a machine:\n * the tag it dispatches is read off each deadline's own `name`, so a machine\n * listing a named knob's deadlines beside an unnamed one's wires this once.\n */\nexport const subscribeDeadline: <N extends string | undefined = undefined>(\n sub: DeadlinesSub<N>,\n ctx: unknown,\n dispatch: (msg: DeadlineExceeded<N>) => void,\n) => Dispose = <N extends string | undefined>(\n sub: DeadlinesSub<N>,\n ctx: unknown,\n dispatch: (msg: DeadlineExceeded<N>) => void,\n) => subscribeWith<N>(setTimeoutArmTimer<N>())(sub, ctx, dispatch);\n\n/**\n * Construct the Msg the deadline dispatches. Exported so consumers can build /\n * assert the same shape (and so the runner and tests share one\n * constructor rather than two literals that can drift).\n */\nexport function deadlineExceeded<N extends string | undefined = undefined>(\n id: string,\n atMs: number,\n name?: N,\n): DeadlineExceeded<N> {\n return { type: deadlineMsgType(name), id, atMs };\n}\n\n/**\n * The one place the named tag is spelled. `undefined` → the bare\n * `\"deadline_exceeded\"` literal; a name → `` `${name}_deadline` ``. Exported so\n * a consumer keying a reducer cell off a named deadline derives the key from\n * the same function the Sub dispatches through, instead of re-spelling the\n * template and drifting.\n */\nexport function deadlineMsgType<N extends string | undefined>(\n name?: N,\n): DeadlineMsgType<N> {\n return (\n name === undefined ? \"deadline_exceeded\" : `${name}_deadline`\n ) as DeadlineMsgType<N>;\n}\n\n/**\n * The built-in `timer` Sub's deps for the SOONEST deadline in `list`, counted\n * from `nowMs`; `null` when the list is empty (no timer armed).\n *\n * This is how a battery arms a whole deadline list through the engine's\n * built-in `timer` instead of a `deadline` runner: a machine declares ONE\n * `{ type: \"timer\", deps: (s) => nextTimer(list(s), clock(s)) }` entry. The\n * timer fires the soonest deadline's `DeadlineExceeded` Msg; the transition it\n * causes drops that deadline from the list, and the next soonest is armed.\n *\n * `nowMs` is the instant the list was last computed against — the battery's\n * own record of its latest transition, never a clock read (invariant 2). The\n * countdown is `atMs - nowMs`, clamped at `0`, and because `ms` is part of the\n * deps, every new `nowMs` restarts the countdown from that instant. PURE.\n */\nexport function nextTimer<N extends string | undefined = undefined>(\n list: readonly DeadlineSub<N>[],\n nowMs: number,\n): TimerDeps<DeadlineExceeded<N>> | null {\n let soonest: DeadlineSub<N> | undefined;\n for (const deadline of list) {\n if (soonest === undefined || deadline.atMs < soonest.atMs) {\n soonest = deadline;\n }\n }\n if (soonest === undefined) return null;\n return {\n ms: Math.max(0, soonest.atMs - nowMs),\n msg: deadlineExceeded(soonest.id, soonest.atMs, soonest.name),\n };\n}\n","/**\n * internal/resilience/cache — a TTL cache as pure state, PLUS a periodic eviction Sub.\n *\n * Same pure-state-ops shape as `../../idempotency/` and\n * `../../work-queue/ops.ts`: a state type plus pure transition functions. Host-\n * agnostic by construction — no `Date.now()`, no timers, no I/O inside the ops.\n * The caller injects the clock (`nowMs`) and folds the result through its own\n * reducer, keeping the store inside TEA's invariant 2 (transitions are pure:\n * same inputs → same outputs) so a consumer can exercise it with `replay`\n * without a fake clock.\n *\n * Every op returns a NEW cache value and never mutates its input (invariant 1 —\n * state is a value, not a place). `get` / `has` are read-shaped; `set` /\n * `remove` / `evictExpired` return a new cache.\n *\n * **Distinct from `/idempotency`.** Both are TTL-bounded key maps, but they\n * answer different questions and so carry different shapes:\n *\n * - `/idempotency` is DEDUPE-focused — \"have I already processed this key?\"\n * TTL is a single store-level policy (`ttlMs`), each entry stores the\n * `atMs` it was remembered at, and expiry is derived (`now - atMs >=\n * ttlMs`). It carries an `order` ledger + `capacity` because the use case\n * (webhook receivers, retried messages) needs bounded LRU eviction.\n * - `/cache` is RETRIEVAL-focused — \"what is the value for this key right\n * now?\" TTL is set PER ENTRY at write time: `set(key, value, nowMs,\n * ttlMs)` bakes the absolute `expiresAtMs = nowMs + ttlMs` into the entry.\n * Different keys can carry different lifetimes (a hot key cached for 1s, a\n * cold one for 1h) — the dimension idempotency's single store-level\n * `ttlMs` cannot express. No `order`, no `capacity`: a cache bounds itself\n * by TTL + periodic eviction, not by LRU count.\n *\n * The eviction Sub is the \"eviction is a Msg\" half: a `setInterval`-shaped Sub\n * (composed directly on `fromInterval`, from `src/subs/`) that periodically\n * dispatches a `cache_evict` Msg the consumer's reducer handles by calling\n * `evictExpired`. Time still never enters the reducer — the Sub fires the Msg,\n * the reducer reads `nowMs` off the Msg (the consumer stamps it at the Sub\n * boundary), and `evictExpired` does the pure drop.\n *\n * Internal to the package (`src/internal/resilience/`), not a published\n * subpath (#46); same one-shape-per-module rule as `work-queue`, `idempotency`,\n * and `rate-limit`.\n */\n\nimport type { DepKeyedSub, Sub } from \"../../../index\";\nimport { fromInterval } from \"../../../subs\";\nimport type { SubscribeHandler } from \"../../../subs/types\";\n\n/**\n * A cached entry: the `value` and the absolute clock reading `expiresAtMs` it\n * expires at. Unlike `/idempotency`'s entry (which stores `atMs` and derives\n * expiry from a store-level `ttlMs`), the expiry instant is baked in at write\n * time — so per-entry TTLs are a first-class capability. Both fields are plain\n * data so the whole cache is trivially JSON-serializable into a consumer's\n * `Store<S>`.\n */\nexport interface CacheEntry<V> {\n readonly value: V;\n readonly expiresAtMs: number;\n}\n\n/**\n * The cache: a map of `key → entry`. No `order` ledger and no `capacity` (cf.\n * `/idempotency`) — a TTL cache bounds itself by expiry + periodic eviction,\n * not by LRU count. Each entry carries its own `expiresAtMs`, so the cache\n * needs no store-level TTL policy field.\n */\nexport interface TtlCache<V> {\n readonly entries: Readonly<Record<string, CacheEntry<V>>>;\n}\n\n/** Create an empty cache. */\nexport function initCache<V>(): TtlCache<V> {\n return { entries: {} };\n}\n\n/**\n * Whether `entry` is expired at `nowMs`. Half-open: an entry whose\n * `expiresAtMs` exactly equals `nowMs` is expired (`nowMs >= expiresAtMs`),\n * matching `/idempotency` and `/rate-limit`'s window-edge cutoff — a value\n * cached at `t` with a `ttlMs` lifetime is gone at exactly `t + ttlMs`.\n */\nfunction isExpired<V>(entry: CacheEntry<V>, nowMs: number): boolean {\n return nowMs >= entry.expiresAtMs;\n}\n\n/**\n * The cached `value` for `key` iff present AND unexpired at `nowMs`, else\n * `undefined`. An expired entry reads as absent even though it is physically\n * still in `entries` until the next `evictExpired` (or an overwriting `set`)\n * drops it. PURE — reads only, never mutates.\n */\nexport function get<V>(\n cache: TtlCache<V>,\n key: string,\n nowMs: number,\n): V | undefined {\n const entry = cache.entries[key];\n if (entry === undefined || isExpired(entry, nowMs)) return undefined;\n return entry.value;\n}\n\n/**\n * True iff `key` is present AND unexpired at `nowMs`. The boolean sibling of\n * `get` for callers that only need presence, not the value. PURE.\n */\nexport function has<V>(\n cache: TtlCache<V>,\n key: string,\n nowMs: number,\n): boolean {\n const entry = cache.entries[key];\n if (entry === undefined) return false;\n return !isExpired(entry, nowMs);\n}\n\n/**\n * Write `key → value` with an absolute expiry of `nowMs + ttlMs`. Re-`set`ting\n * an existing key overwrites both the value and the expiry — the TTL clock\n * restarts from the new `nowMs`. PURE — returns a new cache; the input is\n * untouched (a fresh shallow copy of `entries`).\n */\nexport function set<V>(\n cache: TtlCache<V>,\n key: string,\n value: V,\n nowMs: number,\n ttlMs: number,\n): TtlCache<V> {\n const nextEntries: Record<string, CacheEntry<V>> = { ...cache.entries };\n nextEntries[key] = { value, expiresAtMs: nowMs + ttlMs };\n return { ...cache, entries: nextEntries };\n}\n\n/**\n * Drop `key` regardless of expiry. PURE — returns a new cache; the input is\n * untouched. Removing an absent key returns an equivalent cache (the shallow\n * copy still happens, but no entry is dropped).\n */\nexport function remove<V>(cache: TtlCache<V>, key: string): TtlCache<V> {\n const nextEntries: Record<string, CacheEntry<V>> = { ...cache.entries };\n delete nextEntries[key];\n return { ...cache, entries: nextEntries };\n}\n\n/**\n * Physically drop every entry expired at `nowMs` (`nowMs >= expiresAtMs`).\n * Returns a NEW cache with the survivors. This is the pure work the eviction\n * Sub's `cache_evict` Msg triggers — `get` / `has` already treat expired\n * entries as absent, so eviction is purely a memory-reclaim pass, not a\n * correctness one. PURE — the input cache is never mutated.\n */\nexport function evictExpired<V>(\n cache: TtlCache<V>,\n nowMs: number,\n): TtlCache<V> {\n const nextEntries: Record<string, CacheEntry<V>> = {};\n for (const [key, entry] of Object.entries(cache.entries)) {\n if (isExpired(entry, nowMs)) continue;\n nextEntries[key] = entry;\n }\n return { ...cache, entries: nextEntries };\n}\n\n// ---------------------------------------------------------------------------\n// Eviction Sub — \"eviction is a Msg\".\n//\n// A periodic timer Sub that dispatches `cache_evict` so the consumer's reducer\n// can call `evictExpired`. It is `fromInterval` specialized to a fixed Msg\n// shape — the interval rides in the Sub's `deps` (which the engine keys the\n// Sub on) and the runner dispatches a `cache_evict` Msg tagged with the cache's\n// name, so a consumer running several caches can route each tick to the right\n// cache by `msg.id`.\n//\n// Mirrors `fromInterval` exactly (factory + SubscribeHandler cell + exported\n// Msg constructor); it does NOT reimplement the timer lifecycle — it composes\n// directly on `fromInterval`, which owns `setInterval` / `clearInterval`. That\n// keeps the timer-handle bookkeeping (and its cleanup) in one place and this\n// module purely declarative.\n//\n// Strengthens invariant 4 (external time is a Sub the engine reconciles) and\n// invariant 9 (named, small Sub surface composed on the shared factory).\n// ---------------------------------------------------------------------------\n\n/**\n * The Msg the eviction Sub dispatches each tick. `id` is the cache's name so a\n * consumer with multiple caches can route the tick to the matching cache. The\n * reducer reads `Date.now()`-free time by stamping `nowMs` itself at the Sub\n * boundary if needed; the canonical reducer cell is\n * `cache_evict: (s) => [{ ...s, cache: evictExpired(s.cache, nowMs) }, []]`.\n */\nexport interface CacheEvictMsg {\n readonly type: \"cache_evict\";\n readonly id: string;\n}\n\n/** Construct a `cache_evict` Msg for the cache identified by `id`. */\nexport function cacheEvictMsg(id: string): CacheEvictMsg {\n return { type: \"cache_evict\", id };\n}\n\n/**\n * The `deps` of an eviction Sub: which cache it ticks for (`name`, echoed as\n * the Msg's `id`) and the tick period (`intervalMs`, which `fromInterval`\n * reads).\n */\nexport type CacheEvictionDeps = {\n readonly name: string;\n readonly intervalMs: number;\n};\n\n/**\n * The running eviction Sub: a `setInterval`-shaped Sub whose `deps` carry the\n * cache's name and tick period. `type: \"cache\"` is the lowercase source-noun\n * Sub convention (canon §2.5 / naming-style).\n */\nexport type CacheEvictionSub = Sub<\"cache\", CacheEvictionDeps>;\n\n/**\n * The `subs` entry for an eviction tick on the cache named `name`, every\n * `everyMs`. It is on for the machine's life:\n *\n * subs: [cacheEvictionSub(\"session-cache\", 30_000)],\n * // run(machine, { subscribe: { cache: cacheEvictionSubscribe } })\n *\n * The engine keys the Sub on its `deps`, so a machine that declares the same\n * name and period keeps one running interval, and a changed period restarts\n * it at the new one.\n */\nexport function cacheEvictionSub(\n name: string,\n everyMs: number,\n): DepKeyedSub<unknown, CacheEvictionSub> {\n return { type: \"cache\", deps: () => ({ name, intervalMs: everyMs }) };\n}\n\n/**\n * The `cache` runner for the eviction Sub. Hand it to `run`:\n *\n * run(machine, { subscribe: { cache: cacheEvictionSubscribe } })\n *\n * Composed directly on `fromInterval` — it owns the `setInterval` /\n * `clearInterval` lifecycle; this runner only pins the Msg the tick dispatches\n * (`cacheEvictMsg(sub.deps.name)`). The cleanup `fromInterval` returns stops\n * the interval when the Sub stops.\n */\nexport const cacheEvictionSubscribe: SubscribeHandler<\n CacheEvictionSub,\n CacheEvictMsg,\n unknown\n> = fromInterval((sub) => cacheEvictMsg(sub.deps.name));\n"]}
1
+ {"version":3,"sources":["../src/internal/resilience/deadline/index.ts","../src/internal/resilience/cache/index.ts"],"names":[],"mappings":"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","file":"chunk-UGTC33VH.js","sourcesContent":["/**\n * internal/resilience/deadline — a one-shot Sub that fires when an ABSOLUTE wall-clock\n * deadline is reached.\n *\n * Generalizes the recurring \"auto-fail a machine at T+N\" / \"15-minute stale\n * guard\" pattern: a deadline a battery lists only while it should be armed,\n * which dispatches a single `DeadlineExceeded` Msg when the wall clock crosses\n * `atMs`.\n *\n * A battery's `subs(slice)` returns its deadlines as a LIST (`DeadlineSub[]`,\n * one per retry timer, say). A machine declares that list as ONE Sub of type\n * `\"deadline\"` whose `deps` is the list (`deadlinesSub(select)`), and the\n * `deadline` runner arms every deadline in it. A change to the list restarts\n * the runner, which re-arms each deadline for its REMAINING time — `atMs` is\n * absolute, so a restart never moves a deadline.\n *\n * Difference from `fromTimeout` (relative — \"fire after N ms\"): a deadline is\n * an ABSOLUTE target. The delay is computed at subscribe time as\n * `max(0, atMs - Date.now())`. The consequence that matters: subscribing LATE —\n * e.g. after a `Store` rehydrate that resumes a machine whose deadline was set\n * before the page reloaded — still fires at the correct absolute moment,\n * because the remaining delay is recomputed from the current clock, not stored\n * in the Sub. A deadline already in the past at subscribe time yields delay `0`,\n * so `setTimeout(fn, 0)` fires on the NEXT tick (never synchronously inside the\n * reconcile pass — the substrate must finish wiring all subs before any Msg\n * lands).\n *\n * `deadlineSub` produces one deadline literal carrying `atMs`; the runner\n * (`subscribeDeadline`) translates each `atMs` → a relative delay and arms a\n * `setTimeout` / `clearTimeout` pair per deadline. The clock\n * read (`Date.now()`) lives only in the subscribe handler — never in a reducer\n * (invariant 2) — and is exercised in tests via vitest fake timers, matching\n * the no-injection convention of the `fromTimeout` / `fromInterval` exemplars.\n *\n * WHAT BACKS THE TIMER IS THE HOST'S CHOICE. `setTimeout` is the universal\n * default, but it is not universal: a Durable Object that hibernates has no\n * live `setTimeout` to wake it, so its deadline must be backed by a `do_alarm`\n * registered on the alarm registry; a test host wants a fake-timer schedule.\n * That backing is the `ArmTimer` seam — `subscribeWith(armTimer)` builds the\n * `subscribe.deadline` handler from a host-plugged timer, and `subscribeDeadline`\n * IS `subscribeWith(setTimeoutArmTimer())`. One deadline surface, three hosts;\n * there is no second Sub type and no second `atMs` anchor to keep in sync.\n *\n * The anchor is what makes the seam safe. `atMs` is ABSOLUTE, so whatever backs\n * it arms to a fixed wall-clock instant: the host computes the gap itself\n * (`atMs - Date.now()`), and on a host that just rehydrated after hibernation\n * that gap is the SHRUNKEN remainder — the deadline honours the original target\n * rather than resetting to a fresh full-length window.\n *\n * Strengthens invariant 4 (external time is a subscription — the deadline is a\n * `Sub` the runtime reconciles, never a `setTimeout` leaked into a reducer) and\n * invariant 7 (identity is explicit — the Sub carries a stable `SubId`, so the\n * reconcile pass leaves it running across transitions instead of churning it).\n */\n\nimport type {\n DepKeyedSub,\n Dispose,\n Sub,\n SubId,\n TimerDeps,\n} from \"../../../index\";\nimport { subId } from \"../../../index\";\n\n/**\n * One deadline, as a battery lists it. `atMs` is the absolute target — epoch\n * milliseconds (the `Date.now()` / `Date.parse(...)` scale), NOT a relative\n * delay. `id` names the deadline inside its list: it rides on the dispatched\n * Msg so a reducer can tell WHICH deadline fired, and a registry-backed timer\n * (a DO alarm slot) keys its entry on it.\n *\n * It is plain data — it becomes part of the `deadline` Sub's `deps`, which the\n * engine hashes — and is not itself a running Sub.\n */\nexport type DeadlineSub<N extends string | undefined = undefined> = {\n readonly id: SubId;\n readonly type: \"deadline\";\n readonly atMs: number;\n} & DeadlineOpts<N>;\n\n/**\n * The running `\"deadline\"` Sub: its `deps` is the non-empty list of deadlines\n * to arm. Name it in `types.sub` for a machine that arms deadlines.\n */\nexport type DeadlinesSub<N extends string | undefined = undefined> = Sub<\n \"deadline\",\n readonly DeadlineSub<N>[]\n>;\n\n/**\n * The `deps` of a `deadline` Sub: the list, or `null` when it is empty — an\n * empty list is a Sub with nothing to arm, and an off Sub is not a live one\n * (`driveToDone` reads live Subs to tell a waiting machine from a stalled one).\n */\nexport function deadlines<N extends string | undefined = undefined>(\n list: readonly DeadlineSub<N>[],\n): readonly DeadlineSub<N>[] | null {\n return list.length === 0 ? null : list;\n}\n\n/**\n * The `subs` entry that arms whatever deadlines `select` lists at a state:\n *\n * subs: [deadlinesSub((s: State) => rc.subs(s.resilience))],\n * // run(machine, { subscribe: { deadline: subscribeDeadline } })\n */\nexport function deadlinesSub<S, N extends string | undefined = undefined>(\n select: (state: S) => readonly DeadlineSub<N>[],\n): DepKeyedSub<S, DeadlinesSub<N>> {\n return { type: \"deadline\", deps: (state) => deadlines(select(state)) };\n}\n\n/**\n * Additive options the `deadlineSub` factory folds onto the Sub literal. This\n * is the extension seam (invariant 5 — composition by reduction): future fields\n * — `repeatMs` for a re-arming deadline, `jitterMs` for spread, etc. — land HERE\n * as optional members, and every existing call site keeps compiling because the\n * factory's third parameter is optional and the fields are `readonly` optionals\n * on `DeadlineSub`. Callers are insulated from the concrete `DeadlineSub` shape:\n * they ask for what they need by name, never positionally, so a shape extension\n * never rewrites the ~13 `deadlineSub(id, atMs)` call sites.\n *\n * The first field to land here is {@link DeadlineOpts.name} — the knob name the\n * dispatched Msg tag is derived from — and it landed exactly that way: one\n * optional member, zero rewritten call sites.\n */\nexport type DeadlineOpts<N extends string | undefined = undefined> = {\n /**\n * The knob this deadline belongs to. Present → the dispatched Msg's tag is\n * `` `${name}_deadline` ``; absent → the tag stays the bare\n * `\"deadline_exceeded\"` every machine wired before this field is already\n * handling. `N` is the name as a TYPE as well as a value, so a consumer's\n * cell key and the Msg type are derived from one place and cannot drift.\n *\n * Name a deadline when a machine arms more than one family of them. The Sub\n * *id* was already scoped (`` `${name}:deadline:${key}` ``); this scopes the\n * Msg the Sub dispatches, which is what a reducer cell keys off.\n */\n readonly name?: N;\n};\n\n/**\n * The Msg the deadline dispatches when the wall clock crosses `atMs`. Exported\n * as a tagged shape so consumers can union it into their machine's Msg type and\n * handle it in a reducer cell. `id` lets a reducer disambiguate WHICH deadline\n * fired when a machine arms several at once; `atMs` echoes the target the timer\n * was scheduled against (useful for assertions / observability).\n *\n * Snake-shaped `\"deadline_exceeded\"` matches the tagged-Msg style this module's\n * orchestrator requested; the actor-in-name PascalCase convention\n * (`TimerFiredDeadline`) is the consumer's to apply at their own boundary if\n * they prefer — `deadlineExceeded(...)` only fixes the wire shape, not the\n * consumer's local dialect.\n */\nexport type DeadlineExceeded<N extends string | undefined = undefined> = {\n readonly type: DeadlineMsgType<N>;\n readonly id: string;\n readonly atMs: number;\n};\n\n/**\n * The dispatched Msg's tag, derived from the deadline's optional name. A named\n * deadline speaks `` `${N}_deadline` ``; an unnamed one speaks the bare\n * `\"deadline_exceeded\"` literal, which is what `DeadlineExceeded` with no type\n * argument still means — so every machine wired before the name existed keeps\n * the exact type it had.\n */\nexport type DeadlineMsgType<N extends string | undefined> = N extends string\n ? `${N}_deadline`\n : \"deadline_exceeded\";\n\n/**\n * Build a deadline literal. Pure data — no clock read, no timer; the timer\n * is armed later by the `deadline` runner. `id` is branded via `subId(...)` so\n * accidental raw-string drift fails at the type level (invariant 7).\n *\n * List it from a battery's `subs(slice)` while the deadline should be armed;\n * drop it to disarm — the engine restarts the `deadline` Sub, whose cleanup\n * clears the pending timer before it fires. That is the \"cancel on state exit\"\n * lifecycle.\n *\n * @param id Identity for this deadline, echoed on the Msg it fires. Use\n * distinct ids for distinct deadlines on the same machine.\n * @param atMs Absolute target in epoch milliseconds (e.g. `Date.now() + 900_000`\n * for a 15-minute guard, or a persisted `expiresAt`).\n * @param opts Optional additive fields folded onto the Sub literal (see\n * {@link DeadlineOpts}). Omit it for the common case; pass it when a\n * future field (e.g. `repeatMs`) needs to ride on the deadline. The\n * optionality is the future-proofing seam — extending the shape\n * never breaks an existing `deadlineSub(id, atMs)` call site.\n */\nexport function deadlineSub<N extends string | undefined = undefined>(\n id: string,\n atMs: number,\n opts?: DeadlineOpts<N>,\n): DeadlineSub<N> {\n return { ...opts, id: subId(id), type: \"deadline\", atMs };\n}\n\n/**\n * The host-plugged timer backing. Given the deadline's stable `id`, its\n * absolute `atMs`, and the Msg to fire, arm the platform-appropriate timer\n * (DO alarm registry, `setTimeout`, fake timer) and return a cleanup that\n * cancels the pending fire.\n *\n * `id` is the deadline's own id — a registry-backed host (the DO alarm slot)\n * keys its entry on `id` so the cleanup deletes the EXACT entry it armed. A\n * `setTimeout`-backed host ignores `id` (the closure holds the handle).\n *\n * The host computes the gap itself: `atMs - Date.now()` is the REMAINING time.\n * On a host that just rehydrated after hibernation that gap is the shrunken\n * remainder — the deadline is NOT reset to its full length. That recomputation\n * is the reason `atMs` rides on the Sub as an absolute instant rather than a\n * delay: the anchor is what every backing agrees on.\n */\nexport type ArmTimer<M> = (\n id: SubId,\n atMs: number,\n msg: M,\n dispatch: (msg: M) => void,\n) => () => void;\n\n/**\n * Build the `deadline` runner from a host-plugged `armTimer`. This module owns\n * the deadline shape, the anchor, and the Msg; the HOST owns what backs the\n * timer. The runner arms every deadline in the Sub's `deps` list — reading\n * `id` + `atMs`, building the `deadlineExceeded(...)` Msg and handing all three\n * to `armTimer` — and its cleanup cancels them all.\n *\n * Use it when `setTimeout` is the wrong backing — most concretely a Durable\n * Object, which hibernates and must register a `do_alarm` instead:\n *\n * run(machine, {\n * subscribe: {\n * deadline: subscribeWith((id, atMs, msg, dispatch) =>\n * alarms.register(id, atMs, () => dispatch(msg)),\n * ),\n * },\n * })\n *\n * For the `setTimeout` default, use {@link subscribeDeadline} — it is exactly\n * `subscribeWith(setTimeoutArmTimer())`, so there is one deadline surface and\n * one anchor, not a second one per host.\n */\nexport function subscribeWith<N extends string | undefined = undefined>(\n armTimer: ArmTimer<DeadlineExceeded<N>>,\n): (\n sub: DeadlinesSub<N>,\n ctx: unknown,\n dispatch: (msg: DeadlineExceeded<N>) => void,\n) => Dispose {\n return (sub, _ctx, dispatch) => {\n const cancels = sub.deps.map((deadline) =>\n armTimer(\n deadline.id,\n deadline.atMs,\n deadlineExceeded(deadline.id, deadline.atMs, deadline.name),\n dispatch,\n ),\n );\n return () => {\n for (const cancel of cancels) cancel();\n };\n };\n}\n\n/**\n * The `setTimeout` timer backing — for node / browser / any host whose timer is\n * a plain `setTimeout`. Arms for the REMAINING time (`max(0, atMs - Date.now())`,\n * floored at 0 so an already-past deadline fires on the NEXT tick rather than\n * synchronously inside the reconcile pass), so a deadline re-derived after a\n * rehydrate — or re-armed by a restart — fires at the original instant, not a\n * fresh full window. A hibernating host does NOT use this — it plugs its own\n * `armTimer` (a `do_alarm` registration) into {@link subscribeWith}.\n */\nexport function setTimeoutArmTimer<\n N extends string | undefined = undefined,\n>(): ArmTimer<DeadlineExceeded<N>> {\n return (_id, atMs, msg, dispatch) => {\n // Recompute the remaining delay from the CURRENT clock so a late start\n // (post-rehydrate, or a restart) still targets the same absolute moment.\n const handle = setTimeout(\n () => dispatch(msg),\n Math.max(0, atMs - Date.now()),\n );\n return () => clearTimeout(handle);\n };\n}\n\n/**\n * The `deadline` runner for the DEFAULT `setTimeout` backing. Arms a one-shot\n * timer per listed deadline for its remaining delay `max(0, atMs - Date.now())`\n * and dispatches `deadlineExceeded(...)` when each fires; the cleanup clears\n * every pending timer.\n *\n * It is `subscribeWith(setTimeoutArmTimer())` — the default backing named, not\n * a separate implementation, so the host-plugged path and the default path can\n * never disagree about the anchor.\n *\n * run(machine, { subscribe: { deadline: subscribeDeadline } })\n *\n * Generic in the deadline's name so ONE runner serves every knob in a machine:\n * the tag it dispatches is read off each deadline's own `name`, so a machine\n * listing a named knob's deadlines beside an unnamed one's wires this once.\n */\nexport const subscribeDeadline: <N extends string | undefined = undefined>(\n sub: DeadlinesSub<N>,\n ctx: unknown,\n dispatch: (msg: DeadlineExceeded<N>) => void,\n) => Dispose = <N extends string | undefined>(\n sub: DeadlinesSub<N>,\n ctx: unknown,\n dispatch: (msg: DeadlineExceeded<N>) => void,\n) => subscribeWith<N>(setTimeoutArmTimer<N>())(sub, ctx, dispatch);\n\n/**\n * Construct the Msg the deadline dispatches. Exported so consumers can build /\n * assert the same shape (and so the runner and tests share one\n * constructor rather than two literals that can drift).\n */\nexport function deadlineExceeded<N extends string | undefined = undefined>(\n id: string,\n atMs: number,\n name?: N,\n): DeadlineExceeded<N> {\n return { type: deadlineMsgType(name), id, atMs };\n}\n\n/**\n * The one place the named tag is spelled. `undefined` → the bare\n * `\"deadline_exceeded\"` literal; a name → `` `${name}_deadline` ``. Exported so\n * a consumer keying a reducer cell off a named deadline derives the key from\n * the same function the Sub dispatches through, instead of re-spelling the\n * template and drifting.\n */\nexport function deadlineMsgType<N extends string | undefined>(\n name?: N,\n): DeadlineMsgType<N> {\n return (\n name === undefined ? \"deadline_exceeded\" : `${name}_deadline`\n ) as DeadlineMsgType<N>;\n}\n\n/**\n * The built-in `timer` Sub's deps for the SOONEST deadline in `list`, counted\n * from `nowMs`; `null` when the list is empty (no timer armed).\n *\n * This is how a battery arms a whole deadline list through the engine's\n * built-in `timer` instead of a `deadline` runner: a machine declares ONE\n * `{ type: \"timer\", deps: (s) => nextTimer(list(s), clock(s)) }` entry. The\n * timer fires the soonest deadline's `DeadlineExceeded` Msg; the transition it\n * causes drops that deadline from the list, and the next soonest is armed.\n *\n * `nowMs` is the instant the list was last computed against — the battery's\n * own record of its latest transition, never a clock read (invariant 2). The\n * countdown is `atMs - nowMs`, clamped at `0`, and because `ms` is part of the\n * deps, every new `nowMs` restarts the countdown from that instant. PURE.\n */\nexport function nextTimer<N extends string | undefined = undefined>(\n list: readonly DeadlineSub<N>[],\n nowMs: number,\n): TimerDeps<DeadlineExceeded<N>> | null {\n let soonest: DeadlineSub<N> | undefined;\n for (const deadline of list) {\n if (soonest === undefined || deadline.atMs < soonest.atMs) {\n soonest = deadline;\n }\n }\n if (soonest === undefined) return null;\n return {\n ms: Math.max(0, soonest.atMs - nowMs),\n msg: deadlineExceeded(soonest.id, soonest.atMs, soonest.name),\n };\n}\n","/**\n * internal/resilience/cache — a TTL cache as pure state, PLUS a periodic eviction Sub.\n *\n * Same pure-state-ops shape as `../../idempotency/` and\n * `../../work-queue/ops.ts`: a state type plus pure transition functions. Host-\n * agnostic by construction — no `Date.now()`, no timers, no I/O inside the ops.\n * The caller injects the clock (`nowMs`) and folds the result through its own\n * reducer, keeping the store inside TEA's invariant 2 (transitions are pure:\n * same inputs → same outputs) so a consumer can exercise it with `replay`\n * without a fake clock.\n *\n * Every op returns a NEW cache value and never mutates its input (invariant 1 —\n * state is a value, not a place). `get` / `has` are read-shaped; `set` /\n * `remove` / `evictExpired` return a new cache.\n *\n * **Distinct from `/idempotency`.** Both are TTL-bounded key maps, but they\n * answer different questions and so carry different shapes:\n *\n * - `/idempotency` is DEDUPE-focused — \"have I already processed this key?\"\n * TTL is a single store-level policy (`ttlMs`), each entry stores the\n * `atMs` it was remembered at, and expiry is derived (`now - atMs >=\n * ttlMs`). It carries an `order` ledger + `capacity` because the use case\n * (webhook receivers, retried messages) needs bounded LRU eviction.\n * - `/cache` is RETRIEVAL-focused — \"what is the value for this key right\n * now?\" TTL is set PER ENTRY at write time: `set(key, value, nowMs,\n * ttlMs)` bakes the absolute `expiresAtMs = nowMs + ttlMs` into the entry.\n * Different keys can carry different lifetimes (a hot key cached for 1s, a\n * cold one for 1h) — the dimension idempotency's single store-level\n * `ttlMs` cannot express. No `order`, no `capacity`: a cache bounds itself\n * by TTL + periodic eviction, not by LRU count.\n *\n * The eviction Sub is the \"eviction is a Msg\" half: a `setInterval`-shaped Sub\n * (composed directly on `fromInterval`, from `src/subs/`) that periodically\n * dispatches a `cache_evict` Msg the consumer's reducer handles by calling\n * `evictExpired`. Time still never enters the reducer — the Sub fires the Msg,\n * the reducer reads `nowMs` off the Msg (the consumer stamps it at the Sub\n * boundary), and `evictExpired` does the pure drop.\n *\n * Internal to the package (`src/internal/resilience/`), not a published\n * subpath (#46); same one-shape-per-module rule as `work-queue`, `idempotency`,\n * and `rate-limit`.\n */\n\nimport type { DepKeyedSub, Sub } from \"../../../index\";\nimport { fromInterval } from \"../../../subs\";\nimport type { SubscribeHandler } from \"../../../subs/types\";\n\n/**\n * A cached entry: the `value` and the absolute clock reading `expiresAtMs` it\n * expires at. Unlike `/idempotency`'s entry (which stores `atMs` and derives\n * expiry from a store-level `ttlMs`), the expiry instant is baked in at write\n * time — so per-entry TTLs are a first-class capability. Both fields are plain\n * data so the whole cache is trivially JSON-serializable into a consumer's\n * `Store<S>`.\n */\nexport interface CacheEntry<V> {\n readonly value: V;\n readonly expiresAtMs: number;\n}\n\n/**\n * The cache: a map of `key → entry`. No `order` ledger and no `capacity` (cf.\n * `/idempotency`) — a TTL cache bounds itself by expiry + periodic eviction,\n * not by LRU count. Each entry carries its own `expiresAtMs`, so the cache\n * needs no store-level TTL policy field.\n */\nexport interface TtlCache<V> {\n readonly entries: Readonly<Record<string, CacheEntry<V>>>;\n}\n\n/** Create an empty cache. */\nexport function initCache<V>(): TtlCache<V> {\n return { entries: {} };\n}\n\n/**\n * Whether `entry` is expired at `nowMs`. Half-open: an entry whose\n * `expiresAtMs` exactly equals `nowMs` is expired (`nowMs >= expiresAtMs`),\n * matching `/idempotency` and `/rate-limit`'s window-edge cutoff — a value\n * cached at `t` with a `ttlMs` lifetime is gone at exactly `t + ttlMs`.\n */\nfunction isExpired<V>(entry: CacheEntry<V>, nowMs: number): boolean {\n return nowMs >= entry.expiresAtMs;\n}\n\n/**\n * The cached `value` for `key` iff present AND unexpired at `nowMs`, else\n * `undefined`. An expired entry reads as absent even though it is physically\n * still in `entries` until the next `evictExpired` (or an overwriting `set`)\n * drops it. PURE — reads only, never mutates.\n */\nexport function get<V>(\n cache: TtlCache<V>,\n key: string,\n nowMs: number,\n): V | undefined {\n const entry = cache.entries[key];\n if (entry === undefined || isExpired(entry, nowMs)) return undefined;\n return entry.value;\n}\n\n/**\n * True iff `key` is present AND unexpired at `nowMs`. The boolean sibling of\n * `get` for callers that only need presence, not the value. PURE.\n */\nexport function has<V>(\n cache: TtlCache<V>,\n key: string,\n nowMs: number,\n): boolean {\n const entry = cache.entries[key];\n if (entry === undefined) return false;\n return !isExpired(entry, nowMs);\n}\n\n/**\n * Write `key → value` with an absolute expiry of `nowMs + ttlMs`. Re-`set`ting\n * an existing key overwrites both the value and the expiry — the TTL clock\n * restarts from the new `nowMs`. PURE — returns a new cache; the input is\n * untouched (a fresh shallow copy of `entries`).\n */\nexport function set<V>(\n cache: TtlCache<V>,\n key: string,\n value: V,\n nowMs: number,\n ttlMs: number,\n): TtlCache<V> {\n const nextEntries: Record<string, CacheEntry<V>> = { ...cache.entries };\n nextEntries[key] = { value, expiresAtMs: nowMs + ttlMs };\n return { ...cache, entries: nextEntries };\n}\n\n/**\n * Drop `key` regardless of expiry. PURE — returns a new cache; the input is\n * untouched. Removing an absent key returns an equivalent cache (the shallow\n * copy still happens, but no entry is dropped).\n */\nexport function remove<V>(cache: TtlCache<V>, key: string): TtlCache<V> {\n const nextEntries: Record<string, CacheEntry<V>> = { ...cache.entries };\n delete nextEntries[key];\n return { ...cache, entries: nextEntries };\n}\n\n/**\n * Physically drop every entry expired at `nowMs` (`nowMs >= expiresAtMs`).\n * Returns a NEW cache with the survivors. This is the pure work the eviction\n * Sub's `cache_evict` Msg triggers — `get` / `has` already treat expired\n * entries as absent, so eviction is purely a memory-reclaim pass, not a\n * correctness one. PURE — the input cache is never mutated.\n */\nexport function evictExpired<V>(\n cache: TtlCache<V>,\n nowMs: number,\n): TtlCache<V> {\n const nextEntries: Record<string, CacheEntry<V>> = {};\n for (const [key, entry] of Object.entries(cache.entries)) {\n if (isExpired(entry, nowMs)) continue;\n nextEntries[key] = entry;\n }\n return { ...cache, entries: nextEntries };\n}\n\n// ---------------------------------------------------------------------------\n// Eviction Sub — \"eviction is a Msg\".\n//\n// A periodic timer Sub that dispatches `cache_evict` so the consumer's reducer\n// can call `evictExpired`. It is `fromInterval` specialized to a fixed Msg\n// shape — the interval rides in the Sub's `deps` (which the engine keys the\n// Sub on) and the runner dispatches a `cache_evict` Msg tagged with the cache's\n// name, so a consumer running several caches can route each tick to the right\n// cache by `msg.id`.\n//\n// Mirrors `fromInterval` exactly (factory + SubscribeHandler cell + exported\n// Msg constructor); it does NOT reimplement the timer lifecycle — it composes\n// directly on `fromInterval`, which owns `setInterval` / `clearInterval`. That\n// keeps the timer-handle bookkeeping (and its cleanup) in one place and this\n// module purely declarative.\n//\n// Strengthens invariant 4 (external time is a Sub the engine reconciles) and\n// invariant 9 (named, small Sub surface composed on the shared factory).\n// ---------------------------------------------------------------------------\n\n/**\n * The Msg the eviction Sub dispatches each tick. `id` is the cache's name so a\n * consumer with multiple caches can route the tick to the matching cache. The\n * reducer reads `Date.now()`-free time by stamping `nowMs` itself at the Sub\n * boundary if needed; the canonical reducer cell is\n * `cache_evict: (s) => [{ ...s, cache: evictExpired(s.cache, nowMs) }, []]`.\n */\nexport interface CacheEvictMsg {\n readonly type: \"cache_evict\";\n readonly id: string;\n}\n\n/** Construct a `cache_evict` Msg for the cache identified by `id`. */\nexport function cacheEvictMsg(id: string): CacheEvictMsg {\n return { type: \"cache_evict\", id };\n}\n\n/**\n * The `deps` of an eviction Sub: which cache it ticks for (`name`, echoed as\n * the Msg's `id`) and the tick period (`intervalMs`, which `fromInterval`\n * reads).\n */\nexport type CacheEvictionDeps = {\n readonly name: string;\n readonly intervalMs: number;\n};\n\n/**\n * The running eviction Sub: a `setInterval`-shaped Sub whose `deps` carry the\n * cache's name and tick period. `type: \"cache\"` is the lowercase source-noun\n * Sub convention (canon §2.5 / naming-style).\n */\nexport type CacheEvictionSub = Sub<\"cache\", CacheEvictionDeps>;\n\n/**\n * The `subs` entry for an eviction tick on the cache named `name`, every\n * `everyMs`. It is on for the machine's life:\n *\n * subs: [cacheEvictionSub(\"session-cache\", 30_000)],\n * // run(machine, { subscribe: { cache: cacheEvictionSubscribe } })\n *\n * The engine keys the Sub on its `deps`, so a machine that declares the same\n * name and period keeps one running interval, and a changed period restarts\n * it at the new one.\n */\nexport function cacheEvictionSub(\n name: string,\n everyMs: number,\n): DepKeyedSub<unknown, CacheEvictionSub> {\n return { type: \"cache\", deps: () => ({ name, intervalMs: everyMs }) };\n}\n\n/**\n * The `cache` runner for the eviction Sub. Hand it to `run`:\n *\n * run(machine, { subscribe: { cache: cacheEvictionSubscribe } })\n *\n * Composed directly on `fromInterval` — it owns the `setInterval` /\n * `clearInterval` lifecycle; this runner only pins the Msg the tick dispatches\n * (`cacheEvictMsg(sub.deps.name)`). The cleanup `fromInterval` returns stops\n * the interval when the Sub stops.\n */\nexport const cacheEvictionSubscribe: SubscribeHandler<\n CacheEvictionSub,\n CacheEvictMsg,\n unknown\n> = fromInterval((sub) => cacheEvictMsg(sub.deps.name));\n"]}
@@ -1,5 +1,5 @@
1
- import { createSnapshot } from './chunk-YDF6P74H.js';
2
- import { deadlineSub, nextTimer } from './chunk-QIE4Y6BK.js';
1
+ import { createSnapshot } from './chunk-IAHEYPYJ.js';
2
+ import { deadlineSub, nextTimer } from './chunk-UGTC33VH.js';
3
3
  import { queueAdapter } from './chunk-TXN2KFHF.js';
4
4
 
5
5
  // src/internal/flow/monitored-run/index.ts
@@ -194,5 +194,5 @@ function liftRun(state, [slice, cmds]) {
194
194
  }
195
195
 
196
196
  export { createMonitoredRun, liftRun };
197
- //# sourceMappingURL=chunk-6NIQIUGR.js.map
198
- //# sourceMappingURL=chunk-6NIQIUGR.js.map
197
+ //# sourceMappingURL=chunk-VH5MYJ2K.js.map
198
+ //# sourceMappingURL=chunk-VH5MYJ2K.js.map