@demlik/tea 0.2.0 → 0.3.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 (196) hide show
  1. package/README.md +49 -0
  2. package/dist/agent/index.d.ts +16 -770
  3. package/dist/agent/index.js +18 -16
  4. package/dist/authed-call/index.d.ts +3 -3
  5. package/dist/authed-call/index.js +22 -36
  6. package/dist/authed-call/index.js.map +1 -1
  7. package/dist/await-terminal/index.d.ts +2 -2
  8. package/dist/await-terminal/index.js +2 -2
  9. package/dist/batch-window/index.d.ts +3 -3
  10. package/dist/batch-window/index.js +3 -3
  11. package/dist/cache/index.d.ts +2 -2
  12. package/dist/cache/index.js +5 -2
  13. package/dist/{chunk-3545BOYL.js → chunk-25LBDCBC.js} +6 -5
  14. package/dist/chunk-25LBDCBC.js.map +1 -0
  15. package/dist/{chunk-IN33Q4VJ.js → chunk-2KNLKQVN.js} +3 -3
  16. package/dist/{chunk-IN33Q4VJ.js.map → chunk-2KNLKQVN.js.map} +1 -1
  17. package/dist/{chunk-6VDCTR7N.js → chunk-BP2R7TPP.js} +203 -141
  18. package/dist/chunk-BP2R7TPP.js.map +1 -0
  19. package/dist/chunk-C6QC273X.js +71 -0
  20. package/dist/chunk-C6QC273X.js.map +1 -0
  21. package/dist/{chunk-ZDRN3YCE.js → chunk-CAXMQJJ3.js} +17 -24
  22. package/dist/chunk-CAXMQJJ3.js.map +1 -0
  23. package/dist/{chunk-UV6UWGEO.js → chunk-D736QUGF.js} +6 -5
  24. package/dist/chunk-D736QUGF.js.map +1 -0
  25. package/dist/chunk-EWJP3YYP.js +21 -0
  26. package/dist/chunk-EWJP3YYP.js.map +1 -0
  27. package/dist/{chunk-TGG26XMK.js → chunk-FV3Z5MBE.js} +10 -5
  28. package/dist/chunk-FV3Z5MBE.js.map +1 -0
  29. package/dist/chunk-G5E74IJB.js +8 -0
  30. package/dist/chunk-G5E74IJB.js.map +1 -0
  31. package/dist/{chunk-6GIGQT6C.js → chunk-IXBKFQH5.js} +12 -11
  32. package/dist/chunk-IXBKFQH5.js.map +1 -0
  33. package/dist/{chunk-G2U7DJHL.js → chunk-JFFVBEDI.js} +3 -3
  34. package/dist/{chunk-G2U7DJHL.js.map → chunk-JFFVBEDI.js.map} +1 -1
  35. package/dist/chunk-LYLDCIPH.js +365 -0
  36. package/dist/chunk-LYLDCIPH.js.map +1 -0
  37. package/dist/{chunk-HJO6VNQI.js → chunk-MU6MCIAC.js} +3 -3
  38. package/dist/{chunk-HJO6VNQI.js.map → chunk-MU6MCIAC.js.map} +1 -1
  39. package/dist/{chunk-UYWUVEP6.js → chunk-MUUROGIH.js} +2 -2
  40. package/dist/chunk-MUUROGIH.js.map +1 -0
  41. package/dist/{chunk-5KVHID6A.js → chunk-MVY5DAMF.js} +4 -4
  42. package/dist/{chunk-5KVHID6A.js.map → chunk-MVY5DAMF.js.map} +1 -1
  43. package/dist/{chunk-EGWTT5XJ.js → chunk-NPERUUUE.js} +76 -95
  44. package/dist/chunk-NPERUUUE.js.map +1 -0
  45. package/dist/{chunk-DYRKDCJI.js → chunk-PBDYW2TU.js} +41 -7
  46. package/dist/chunk-PBDYW2TU.js.map +1 -0
  47. package/dist/{chunk-5VNWZADP.js → chunk-Q2UM7BXU.js} +18 -7
  48. package/dist/chunk-Q2UM7BXU.js.map +1 -0
  49. package/dist/chunk-RMX4EOQK.js +26 -0
  50. package/dist/chunk-RMX4EOQK.js.map +1 -0
  51. package/dist/{chunk-2YOE5PIS.js → chunk-S2EGRPCM.js} +8 -6
  52. package/dist/chunk-S2EGRPCM.js.map +1 -0
  53. package/dist/{chunk-IK4S5DJN.js → chunk-S4O26D7B.js} +7 -14
  54. package/dist/chunk-S4O26D7B.js.map +1 -0
  55. package/dist/{chunk-6OUDPSSG.js → chunk-SK5JGUY7.js} +3 -3
  56. package/dist/{chunk-6OUDPSSG.js.map → chunk-SK5JGUY7.js.map} +1 -1
  57. package/dist/{chunk-FOGL3TC7.js → chunk-TPGASTFF.js} +8 -10
  58. package/dist/chunk-TPGASTFF.js.map +1 -0
  59. package/dist/{chunk-SC3FAFZN.js → chunk-UF4MZSLT.js} +2 -2
  60. package/dist/chunk-UF4MZSLT.js.map +1 -0
  61. package/dist/{chunk-M6UZT7HS.js → chunk-VBUCHVCV.js} +6 -5
  62. package/dist/chunk-VBUCHVCV.js.map +1 -0
  63. package/dist/{chunk-VFGKTGGC.js → chunk-WGGAMW7Y.js} +191 -178
  64. package/dist/chunk-WGGAMW7Y.js.map +1 -0
  65. package/dist/chunk-ZJ5AOYBH.js +8 -0
  66. package/dist/chunk-ZJ5AOYBH.js.map +1 -0
  67. package/dist/{chunk-LEC4U6CX.js → chunk-ZVJZ72HY.js} +3 -3
  68. package/dist/{chunk-LEC4U6CX.js.map → chunk-ZVJZ72HY.js.map} +1 -1
  69. package/dist/circuit-breaker/index.d.ts +1 -0
  70. package/dist/circuit-breaker/index.js +1 -1
  71. package/dist/{core-BmlJOvxM.d.ts → core-42kapW0n.d.ts} +96 -6
  72. package/dist/deadline/index.d.ts +77 -11
  73. package/dist/deadline/index.js +3 -3
  74. package/dist/devtools/index.js +8 -28
  75. package/dist/devtools/index.js.map +1 -1
  76. package/dist/do/index.d.ts +52 -24
  77. package/dist/do/index.js +94 -92
  78. package/dist/do/index.js.map +1 -1
  79. package/dist/{durable-effects-9LxFpmh3.d.ts → durable-effects-CFRngTNA.d.ts} +3 -3
  80. package/dist/extension/index.d.ts +25 -7
  81. package/dist/extension/index.js +4 -3
  82. package/dist/extension/index.js.map +1 -1
  83. package/dist/extension/react.js +4 -3
  84. package/dist/extension/react.js.map +1 -1
  85. package/dist/extension/subs/index.d.ts +2 -2
  86. package/dist/extension/subs/index.js +7 -10
  87. package/dist/extension/subs/index.js.map +1 -1
  88. package/dist/fan-out/index.d.ts +10 -10
  89. package/dist/fan-out/index.js +3 -3
  90. package/dist/idempotent-intake/index.d.ts +2 -2
  91. package/dist/idempotent-intake/index.js +3 -3
  92. package/dist/index.d.ts +50 -489
  93. package/dist/index.js +2 -2
  94. package/dist/llm-call/index.d.ts +3 -2
  95. package/dist/llm-call/index.js +12 -10
  96. package/dist/machine-B_BF4jij.d.ts +804 -0
  97. package/dist/machine-viz/index.d.ts +1 -1
  98. package/dist/machine-viz/index.js +2 -2
  99. package/dist/mem/index.d.ts +3 -2
  100. package/dist/mem/index.js.map +1 -1
  101. package/dist/monitored-run/index.d.ts +54 -30
  102. package/dist/monitored-run/index.js +7 -7
  103. package/dist/node/index.d.ts +3 -2
  104. package/dist/node/index.js +2 -3
  105. package/dist/node/index.js.map +1 -1
  106. package/dist/paginated-walk/index.d.ts +2 -2
  107. package/dist/paginated-walk/index.js +11 -10
  108. package/dist/parity/index.d.ts +6 -5
  109. package/dist/parity/index.js +5 -4
  110. package/dist/parity/index.js.map +1 -1
  111. package/dist/pbt/arbitraries/index.d.ts +1 -1
  112. package/dist/pbt/index.d.ts +1 -1
  113. package/dist/pbt/index.js +3 -3
  114. package/dist/pbt/index.js.map +1 -1
  115. package/dist/pbt/runners/index.d.ts +1 -1
  116. package/dist/pbt/runners/index.js +3 -3
  117. package/dist/poller/index.d.ts +59 -17
  118. package/dist/poller/index.js +56 -20
  119. package/dist/poller/index.js.map +1 -1
  120. package/dist/prediction/index.d.ts +1 -1
  121. package/dist/prediction/index.js +2 -2
  122. package/dist/pure/index.d.ts +1 -1
  123. package/dist/pure/index.js +2 -2
  124. package/dist/rate-limit/index.d.ts +2 -2
  125. package/dist/rate-limit/index.js +1 -1
  126. package/dist/react/index.d.ts +3 -2
  127. package/dist/react/index.js +2 -2
  128. package/dist/react/index.js.map +1 -1
  129. package/dist/reconciler/index.d.ts +2 -2
  130. package/dist/reconciler/index.js +13 -15
  131. package/dist/reconciler/index.js.map +1 -1
  132. package/dist/recorder/index.d.ts +20 -20
  133. package/dist/recorder/index.js +1 -1
  134. package/dist/resilient-call/index.d.ts +31 -6
  135. package/dist/resilient-call/index.js +10 -9
  136. package/dist/retry-backoff/index.d.ts +191 -14
  137. package/dist/retry-backoff/index.js +1 -1
  138. package/dist/retry-to-success/index.js +1 -1
  139. package/dist/runtime-types-gsog27b6.d.ts +337 -0
  140. package/dist/saga/index.d.ts +3 -2
  141. package/dist/saga/index.js +2 -2
  142. package/dist/saga/index.js.map +1 -1
  143. package/dist/snapshot/index.d.ts +1 -1
  144. package/dist/snapshot/index.js +3 -3
  145. package/dist/subs/index.d.ts +372 -4
  146. package/dist/subs/index.js +4 -1
  147. package/dist/testing/index.d.ts +2 -2
  148. package/dist/testing/index.js +2 -2
  149. package/dist/throttled-input/index.d.ts +2 -2
  150. package/dist/throttled-input/index.js +14 -13
  151. package/dist/throttled-input/index.js.map +1 -1
  152. package/dist/token-refresh/index.d.ts +1 -1
  153. package/dist/token-refresh/index.js +3 -3
  154. package/dist/trace-replay/index.d.ts +12 -7
  155. package/dist/trace-replay/index.js +4 -3
  156. package/dist/{types-BoUWgwSm.d.ts → types-KbL3W1mj.d.ts} +1 -1
  157. package/dist/with-deadline/index.d.ts +1 -1
  158. package/dist/with-deadline/index.js +2 -2
  159. package/dist/with-resilience/index.d.ts +21 -2
  160. package/dist/with-resilience/index.js +12 -23
  161. package/dist/with-resilience/index.js.map +1 -1
  162. package/dist/with-telemetry/index.d.ts +1 -1
  163. package/dist/with-telemetry/index.js +2 -2
  164. package/dist/work-queue/adapter.d.ts +2 -2
  165. package/dist/work-queue/adapter.js +2 -2
  166. package/dist/work-queue/index.d.ts +3 -2
  167. package/dist/work-queue/index.js +1 -1
  168. package/dist/work-queue/index.js.map +1 -1
  169. package/dist/work-queue/ops.d.ts +2 -2
  170. package/dist/work-queue/ops.js +1 -1
  171. package/dist/workflow/index.d.ts +51 -19
  172. package/dist/workflow/index.js +71 -52
  173. package/dist/workflow/index.js.map +1 -1
  174. package/package.json +4 -1
  175. package/dist/chunk-2YOE5PIS.js.map +0 -1
  176. package/dist/chunk-3545BOYL.js.map +0 -1
  177. package/dist/chunk-5VNWZADP.js.map +0 -1
  178. package/dist/chunk-6GIGQT6C.js.map +0 -1
  179. package/dist/chunk-6VDCTR7N.js.map +0 -1
  180. package/dist/chunk-DYRKDCJI.js.map +0 -1
  181. package/dist/chunk-EGWTT5XJ.js.map +0 -1
  182. package/dist/chunk-EMJTPHI7.js +0 -91
  183. package/dist/chunk-EMJTPHI7.js.map +0 -1
  184. package/dist/chunk-FOGL3TC7.js.map +0 -1
  185. package/dist/chunk-IK4S5DJN.js.map +0 -1
  186. package/dist/chunk-M6UZT7HS.js.map +0 -1
  187. package/dist/chunk-NSDFA6M6.js +0 -198
  188. package/dist/chunk-NSDFA6M6.js.map +0 -1
  189. package/dist/chunk-SC3FAFZN.js.map +0 -1
  190. package/dist/chunk-SKAMZKTE.js +0 -20
  191. package/dist/chunk-SKAMZKTE.js.map +0 -1
  192. package/dist/chunk-TGG26XMK.js.map +0 -1
  193. package/dist/chunk-UV6UWGEO.js.map +0 -1
  194. package/dist/chunk-UYWUVEP6.js.map +0 -1
  195. package/dist/chunk-VFGKTGGC.js.map +0 -1
  196. package/dist/chunk-ZDRN3YCE.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/fan-out/index.ts"],"names":[],"mappings":"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","file":"chunk-D736QUGF.js","sourcesContent":["/**\n * @demlik/tea/fan-out — scatter-gather over a bounded-concurrency work list.\n *\n * The Level-2 combinator that fans a batch of items out across at most\n * `concurrency` in-flight effects, gathers each item's result (or error), and\n * — once every item has settled — fires an optional `join` over the collected\n * results. It is the \"map a list through an effect, but never run more than N\n * at once\" knob.\n *\n * The recipe (which bricks it composes):\n * - `../work-queue` — fan-out's tracked items ARE work-queue items. Each\n * item carries the same `{ id, status, input }` lifecycle vocabulary\n * (`pending → running → done | failed`). The slice this knob owns is four\n * partitions over that one lifecycle, so the work-queue's status taxonomy\n * is the substrate rather than a parallel invention. We reuse its\n * `QueueItem<I>` / `QueueItemStatus` TYPES and delegate the ledger\n * transitions to the `QueueAdapter` verbs (`../work-queue/adapter`):\n * `scatter` mints each pending record with `queue.enqueue`, and `launchUpTo`\n * flips `pending → running` with `queue.claim` (one positional claim per\n * launch). The lone exception is `settleOne`'s `running → done|failed`\n * flip — it must match \"first id-match that is still `running`\" to settle\n * exactly one of a DUPLICATE id, a status predicate the adapter verbs can't\n * express, so fan-out owns that one index (see `settleOne`). The async\n * `createQueue(store)` adapter is NOT used here because its `load → mutate\n * → save` reads the clock and lives at an I/O boundary, while a combinator's\n * verbs must be pure (invariant 2) — fan-out binds the pure `QueueAdapter`\n * to its in-Model ledger instead, the same route `monitored-run` /\n * `idempotent-intake` / `saga` take. The slice lives in the Model instead —\n * durable + replayable, the two non-negotiables every knob preserves.\n *\n * Typical wiring (combinator form — splice the hooks into your machine):\n *\n * const fan = createFanOut<Url, Report>({\n * concurrency: 4,\n * idOf: (url) => url, // stable, pure identity\n * of: (url) => ({ type: \"crawl\", url }), // the per-item effect, as data\n * join: (results) => ({ type: \"report_done\", results }),\n * });\n *\n * // in `update`:\n * start_crawl: (s, m) => { const [fanOut, cmds] = fan.scatter(s.fanOut, m.urls);\n * return [{ ...s, fanOut }, cmds]; },\n * crawl_ok: (s, m) => { const [fanOut, cmds] = fan.itemOk(s.fanOut, m.id, m.report);\n * return [{ ...s, fanOut }, cmds]; },\n * crawl_err: (s, m) => { const [fanOut, cmds] = fan.itemErr(s.fanOut, m.id, m.error);\n * return [{ ...s, fanOut }, cmds]; },\n *\n * Every verb is PURE: it returns a NEW slice plus the Cmds to perform (the\n * `of(item)` effects for newly-launched items, and at most one `join` Cmd when\n * the batch completes). The reducer never reads a clock or RNG — item identity\n * is supplied by the caller's `idOf` so `scatter` is deterministic, and the\n * launch effects are plain data the runtime performs (invariant 3).\n *\n * `handlers(ports)` is the I/O splice: it wires the runtime side that actually\n * runs each launched `of(item)` Cmd. Because `of` already produces a fully\n * caller-defined Cmd, fan-out does NOT own the interpret handler for it — the\n * consumer's own `interpret` performs the effect and routes Ok/Err back to the\n * `itemOk` / `itemErr` verbs (the same shape resilient-fetch uses with\n * `tryInterpret`). `handlers` therefore exposes the one piece fan-out CAN\n * pre-wire generically: emitting the gathered batch onto a typed Port when the\n * batch completes, for consumers that observe completion out-of-band rather\n * than through the `join` Cmd.\n *\n * NOT a substrate primitive: it depends only on `../work-queue`'s pure types +\n * blessed ops and `../index`'s `Cmd` / `Port` types. Consumers reach it via the\n * `@demlik/tea/fan-out` subpath.\n */\n\nimport { at } from \"../at\";\nimport type { Cmd, Port } from \"../index\";\nimport type { QueueItem, QueueItemStatus } from \"../work-queue\";\nimport { queueAdapter } from \"../work-queue/adapter\";\n\n/**\n * One settled-OK item: the original `input` and the `result` its effect\n * produced. Mirrors the spec slice's `done: {item, result}[]` — `item` is the\n * caller's input value (not the internal `QueueItem` envelope), so a consumer\n * reading `done` gets back exactly what it scattered.\n */\nexport interface FanOutDone<I, R> {\n /** The original scattered input value. */\n readonly item: I;\n /** The result the item's effect produced, routed in via `itemOk`. */\n readonly result: R;\n}\n\n/**\n * One settled-error item: the original `input` and the `error` its effect\n * surfaced. `error` is `unknown` — fan-out carries it forward verbatim (an\n * `Error`, a tagged result, a string) for the consumer's reducer to read,\n * never interpreting it (same discipline as `retry-backoff`'s `lastError`).\n */\nexport interface FanOutFailed<I> {\n /** The original scattered input value. */\n readonly item: I;\n /** The error the item's effect surfaced, routed in via `itemErr`. Carried, never interpreted. */\n readonly error: unknown;\n}\n\n/**\n * The slice this knob owns — four partitions over the work-queue item\n * lifecycle, plus the work-queue records the verbs thread the lifecycle\n * through.\n *\n * Why both the spec's four arrays AND `items`:\n * - The four arrays (`pending` / `running` / `done` / `failed`) are the\n * SPEC's public read surface — what a consumer's view renders and what\n * `isComplete` reads. They hold caller-facing values (`I`, or\n * `FanOutDone` / `FanOutFailed`), not the internal id-bearing envelope.\n * - `items` is the work-queue ledger: one `QueueItem<I>` per scattered item,\n * carrying the stable `id` (from `config.idOf`) that `itemOk` / `itemErr`\n * address and the `status` that decides launch-eligibility. It is the\n * single source of truth; the four arrays are a derived projection kept in\n * lockstep so consumers never re-scan `items` to render a partition.\n *\n * Every field is `readonly` and the whole slice is JSON-serializable (plain\n * arrays + the work-queue's plain `QueueItem` records), so it round-trips\n * through a consumer's `Store<S>` unchanged — durable across DO eviction.\n */\nexport interface FanOutState<I, R> {\n /** Items accepted but not yet launched (queue back-pressure beyond `concurrency`). */\n readonly pending: readonly I[];\n /** Items whose `of(item)` effect is in flight (size never exceeds `concurrency`). */\n readonly running: readonly I[];\n /** Items whose effect resolved OK, with the result it produced. */\n readonly done: readonly FanOutDone<I, R>[];\n /** Items whose effect failed, with the error it surfaced. */\n readonly failed: readonly FanOutFailed<I>[];\n /**\n * The work-queue ledger: one record per scattered item, keyed by the\n * caller's `idOf`. The single source of truth the four arrays project from.\n */\n readonly items: readonly QueueItem<I>[];\n /**\n * Index into `done` where the CURRENT wave's OK results begin. `done` is\n * append-only and accumulates across every wave (so a consumer reading\n * `done` sees the full history), but `join` must fold only the wave that\n * just drained — not the cumulative total. A wave opens when `scatter`\n * enqueues into a slice with nothing in flight; this marks `done.length` at\n * that moment so `join` receives exactly `done.slice(waveDoneFrom)`.\n */\n readonly waveDoneFrom: number;\n /**\n * Index into `failed` where the current wave's failures begin. Symmetric to\n * `waveDoneFrom` — the wave boundary over the failure partition. Carried for\n * a consumer that wants to read only the current wave's failures; `join`\n * itself folds only OK results.\n */\n readonly waveFailedFrom: number;\n /**\n * Whether the current wave has already fired its `join`. `join` fires at most\n * once per wave — set `true` at the completion edge so a stale settle that\n * re-reaches `isComplete` cannot re-fire it. A new `scatter` that opens a wave\n * resets it to `false`.\n */\n readonly joined: boolean;\n}\n\n/**\n * The knob. `concurrency` caps how many `of(item)` effects are in flight at\n * once; `idOf` mints a stable, PURE identity per item (so `scatter` is\n * deterministic and `itemOk` / `itemErr` can address an item by id); `of`\n * turns an item into the launch Cmd; `join` (optional) turns the gathered\n * results into a single completion Cmd fired exactly once when every item has\n * settled.\n *\n * Field optionality follows the resilient-call rule — omit `join`, omit the\n * completion Cmd (a consumer that observes `isComplete` itself, or wires the\n * `handlers` Port, doesn't need one).\n */\nexport interface FanOutConfig<I, R, C extends Cmd, J extends Cmd> {\n /** Max in-flight `of(item)` effects. Clamped to `>= 1` (a 0/negative cap would deadlock). */\n readonly concurrency: number;\n /**\n * Stable, deterministic identity for an item. PURE — no clock, no RNG. The\n * id is what `itemOk` / `itemErr` address, so it must survive a round-trip\n * through the durable slice and match across the launch → settle gap. For\n * value-unique inputs (URLs, ids) this is often the identity function; for\n * structural inputs, a content hash or a caller-assigned key.\n */\n readonly idOf: (item: I) => string;\n /** The per-item effect, as data. Performed by the consumer's own `interpret`. */\n readonly of: (item: I) => C;\n /**\n * Optional: fold the gathered done-results into a single completion Cmd,\n * fired exactly once at the transition that empties `pending` + `running`.\n * Omit to drive completion off `isComplete` (or the `handlers` Port) instead.\n */\n readonly join?: (results: readonly R[]) => J;\n}\n\n/**\n * Clamp the configured concurrency to a usable launch budget. A cap of 0 or\n * negative would launch nothing and the batch would deadlock with items stuck\n * `pending` forever; clamping to 1 degrades gracefully to fully-serial\n * fan-out (still correct, just no parallelism) rather than hanging.\n */\nfunction launchBudget(concurrency: number): number {\n return concurrency < 1 ? 1 : Math.floor(concurrency);\n}\n\n/** The starting slice: nothing scattered yet. */\nexport function initFanOut<I, R>(): FanOutState<I, R> {\n return {\n pending: [],\n running: [],\n done: [],\n failed: [],\n items: [],\n waveDoneFrom: 0,\n waveFailedFrom: 0,\n joined: false,\n };\n}\n\n/**\n * Whether every scattered item has settled (no `pending`, no `running`).\n * Derived — reads the slice, never mutates. The verbs use it to decide when to\n * fire `join`; consumers can read it directly to branch their own phase.\n *\n * An empty fan-out (nothing ever scattered) is NOT complete: completion is \"a\n * non-empty batch fully drained,\" so `isComplete(initFanOut())` is `false` and\n * a `scatter([])` of zero items also reads `false` (there was no batch to\n * complete). This keeps `join` from firing on a degenerate empty scatter.\n */\nexport function isComplete<I, R>(state: FanOutState<I, R>): boolean {\n if (state.items.length === 0) return false;\n return state.pending.length === 0 && state.running.length === 0;\n}\n\n/**\n * Count of items currently in flight. `running.length`, named for the launch\n * arithmetic below (and readable at a call site).\n */\nfunction inFlight<I, R>(state: FanOutState<I, R>): number {\n return state.running.length;\n}\n\n/**\n * Move up to `budget - inFlight` items from `pending` to `running`, emitting\n * `of(item)` for each launched item. PURE — returns the new slice and the\n * launch Cmds. The work-queue `items` ledger flips each launched item's status\n * `pending → running` via the `QueueAdapter`'s `claim`, in lockstep with the\n * projected arrays.\n *\n * Delegation, not re-rolling: each launch is one `queue.claim` call — the same\n * `pending → running` transition `@demlik/tea/work-queue` owns. `queue.claim`\n * flips the FIRST pending ledger record positionally, and fan-out's `pending`\n * array is appended in lockstep with the ledger, so the first `slots` pending\n * inputs map to the first `slots` pending records — claiming one-per-launch\n * settles exactly those. This also makes a DUPLICATE id correct: a positional\n * claim flips ONE record per launch, where a blanket id-set flip would promote\n * every pending record sharing the launched id (over-flipping the ledger past\n * `running.length`). The `at` stamp is `0` — fan-out's verbs are pure and never\n * read a clock; positional order, not the timestamp, drives every decision\n * (same convention as `scatter`'s `enqueuedAt: 0`).\n *\n * Shared by `scatter` (launch on enqueue) and `itemOk` / `itemErr` (launch the\n * next pending item as a slot frees). Factoring it here keeps the concurrency\n * invariant — `running.length <= budget` — in ONE place.\n */\nfunction launchUpTo<I, R, C extends Cmd>(\n state: FanOutState<I, R>,\n config: {\n concurrency: number;\n of: (item: I) => C;\n },\n): readonly [FanOutState<I, R>, readonly C[]] {\n const budget = launchBudget(config.concurrency);\n const slots = budget - inFlight(state);\n if (slots <= 0 || state.pending.length === 0) return [state, []];\n\n const toLaunch = state.pending.slice(0, slots);\n const stillPending = state.pending.slice(slots);\n\n // Flip the ledger records `pending → running` one launch at a time via the\n // `QueueAdapter` (positional first-pending claim per call).\n const queue = queueAdapter<I>();\n let items: readonly QueueItem<I>[] = state.items;\n for (let i = 0; i < toLaunch.length; i++) {\n const claimed = queue.claim(items, 0);\n if (claimed === null) break;\n items = claimed.next;\n }\n\n const next: FanOutState<I, R> = {\n ...state,\n pending: stillPending,\n running: [...state.running, ...toLaunch],\n items,\n };\n const cmds = toLaunch.map((item) => config.of(item));\n return [next, cmds];\n}\n\n/**\n * Settle EXACTLY ONE running entry for `id`: remove the first `running` item\n * whose `idOf` matches, and flip the first ledger record for `id` that is still\n * `running` to `status`. Returns the input that was settled plus the new\n * `running` / `items` arrays, or `undefined` if no running item carries that id\n * (a stale / unknown settle Msg → caller treats as a no-op rather than a crash).\n *\n * Why \"exactly one\": when two scattered items share an `idOf` (a duplicate id),\n * the ledger holds two `running` records under the same id and `running` holds\n * two inputs. A settle Msg carries one result, so it must close out exactly one\n * of them — not all matches. A blanket `filter((x) => idOf(x) !== id)` would\n * drop BOTH running entries while recording only ONE result, silently losing a\n * unit of work and corrupting the done/failed partition. find-index + splice\n * removes a single `running` entry; the matching `findIndex` over the ledger\n * flips a single record. The OTHER duplicate stays `running`, awaiting its own\n * settle Msg.\n *\n * This flip is NOT delegated to the `QueueAdapter`'s `patch`: the verb matches\n * the FIRST record by id, but the record fan-out must flip is \"first id-match\n * that is still `running`\" — and after a dup-id's first settle the first id-match\n * is the already-terminal record. The status predicate is load-bearing here and\n * the adapter verbs can't express it, so fan-out owns this one index. The flipped\n * record is still built the work-queue way (spread + new `status`), and there\n * is no `as` cast: `status` arrives already typed `QueueItemStatus`.\n */\nfunction settleOne<I, R>(\n state: FanOutState<I, R>,\n id: string,\n status: QueueItemStatus,\n idOf: (item: I) => string,\n):\n | { input: I; running: readonly I[]; items: readonly QueueItem<I>[] }\n | undefined {\n const runIdx = state.running.findIndex((item) => idOf(item) === id);\n // `at` collapses \"no running entry for this id\" into an `undefined` early-out,\n // narrowing `input` to `I` below without a non-null assertion (the pattern\n // `ops.ts` uses); `runIdx` stays around for the splice.\n const input = at(state.running, runIdx);\n if (input === undefined) return undefined;\n\n const running = [\n ...state.running.slice(0, runIdx),\n ...state.running.slice(runIdx + 1),\n ];\n\n const ledgerIdx = state.items.findIndex(\n (item) => item.id === id && item.status === \"running\",\n );\n // Same early-out via `at`: `ledger === undefined` narrows the record to\n // `QueueItem<I>` for the spread below without a non-null assertion.\n const ledger = at(state.items, ledgerIdx);\n const items =\n ledger === undefined\n ? state.items\n : [\n ...state.items.slice(0, ledgerIdx),\n { ...ledger, status },\n ...state.items.slice(ledgerIdx + 1),\n ];\n\n return { input, running, items };\n}\n\n/**\n * Optional Port for observing batch completion out-of-band. When a consumer\n * passes `handlers(ports)` a `complete` Port, the runtime side can emit the\n * gathered results onto it the moment `isComplete` flips true — distinct from\n * the in-band `join` Cmd (which threads back through `update`). Use the Port\n * for \"tell the outside world the batch finished\" without folding a completion\n * signal into the consumer's Msg union.\n */\nexport interface FanOutPorts<R> {\n /** Fires once with every gathered done-result when the batch completes. */\n readonly complete: Port<readonly R[]>;\n}\n\n/**\n * Build the fan-out knob from `config`. Returns the uniform combinator\n * contract: `init` for the owned slice, the three settle verbs + the derived\n * `isComplete`, and `handlers` for the optional out-of-band completion Port.\n *\n * No `subs` — fan-out runs no timers (the spec pins \"Subs: none\"). The\n * per-item effect's lifetime is owned by the consumer's `interpret`, and\n * completion is a pure function of the settle Msgs, so there is nothing\n * continuous to subscribe to.\n */\nexport function createFanOut<I, R, C extends Cmd = Cmd, J extends Cmd = Cmd>(\n config: FanOutConfig<I, R, C, J>,\n) {\n /**\n * Enqueue `items` and launch up to `concurrency` of them. PURE.\n *\n * Each item gets a ledger record (`status: \"pending\"`, its `idOf` id) and is\n * appended to `pending`; `launchUpTo` then promotes as many as the free\n * concurrency budget allows, emitting `of(item)` per launch. The returned\n * Cmds are exactly the launch effects — `scatter` never fires `join` (a batch\n * can't complete on the same transition it starts unless it launches zero\n * effects, which `isComplete`'s empty-batch rule already declines to treat as\n * completion).\n *\n * Each pending ledger record is minted by the `QueueAdapter`'s `enqueue` — the\n * same `pending` append `@demlik/tea/work-queue` owns — rather than re-rolled\n * inline, so the `status: \"pending\"` literal lives in exactly one place (no\n * `as QueueItemStatus` cast here).\n *\n * `enqueuedAt` is stamped `0` rather than read from a clock: fan-out's verbs\n * are pure, and fan-out does not use the timestamp for any decision (ordering\n * is positional via the arrays). A consumer that needs real enqueue times\n * threads them through its own Msg `at` and stores them alongside.\n */\n function scatter(\n state: FanOutState<I, R>,\n items: readonly I[],\n ): readonly [FanOutState<I, R>, readonly C[]] {\n // Append one pending ledger record per item via the `QueueAdapter`.\n // `queue.enqueue` returns `status: \"pending\"` already typed — no cast — and\n // the lifecycle's \"new work is pending\" rule stays in work-queue's hands.\n const queue = queueAdapter<I>();\n let ledger: readonly QueueItem<I>[] = state.items;\n for (const input of items) {\n ledger = queue.enqueue(ledger, input, 0, config.idOf(input)).next;\n }\n // A scatter OPENS A FRESH WAVE when nothing is in flight — either the very\n // first scatter, or a re-scatter after the previous wave fully drained. The\n // wave boundary advances to the current `done` / `failed` length so `join`\n // folds only THIS wave's results, never the cumulative history, and\n // `joined` resets so the new wave can fire its own `join` exactly once.\n //\n // A scatter that adds to a wave still in flight (pending or running\n // non-empty — e.g. enqueuing more work mid-batch) does NOT open a wave:\n // those items join the current wave and settle under its single `join`.\n const opensWave = state.pending.length === 0 && state.running.length === 0;\n\n const enqueued: FanOutState<I, R> = {\n ...state,\n pending: [...state.pending, ...items],\n items: ledger,\n ...(opensWave\n ? {\n waveDoneFrom: state.done.length,\n waveFailedFrom: state.failed.length,\n joined: false,\n }\n : {}),\n };\n return launchUpTo(enqueued, config);\n }\n\n /**\n * Record `id`'s effect as succeeded with `result`, then launch the next\n * pending item to backfill the freed slot. PURE.\n *\n * Moves the item out of `running` into `done` (carrying the original input +\n * its result), flips its ledger record `running → done`, and runs\n * `launchUpTo` to keep the pipeline full. If this transition empties both\n * `pending` and `running` AND a `join` is configured, the returned Cmds also\n * include the single `join(results)` Cmd — fired exactly once at the\n * completion edge.\n *\n * A settle for an unknown / already-settled `id` is a no-op (returns the\n * slice unchanged, no Cmds): the runtime's serial dispatch makes a true\n * double-settle impossible, but a stale Msg after a boot/replay is real, and\n * swallowing it is the safe direction (the result is already recorded).\n */\n function itemOk(\n state: FanOutState<I, R>,\n id: string,\n result: R,\n ): readonly [FanOutState<I, R>, readonly (C | J)[]] {\n const settled = settleOne(state, id, \"done\", config.idOf);\n if (settled === undefined) return [state, []];\n\n const next: FanOutState<I, R> = {\n ...state,\n running: settled.running,\n done: [...state.done, { item: settled.input, result }],\n items: settled.items,\n };\n return finishOrLaunch(next);\n }\n\n /**\n * Record `id`'s effect as failed with `error`, then launch the next pending\n * item to backfill the freed slot. PURE.\n *\n * Symmetric to `itemOk`: moves the item out of `running` into `failed`\n * (carrying the original input + the error), flips its ledger record\n * `running → failed`, backfills via `launchUpTo`, and fires `join` at the\n * completion edge if configured. A failed item still counts toward completion\n * — the batch is \"done\" when every item has settled, success or failure;\n * `join` receives only the OK results (failures are read off `failed`).\n *\n * Same unknown-`id` no-op contract as `itemOk`.\n */\n function itemErr(\n state: FanOutState<I, R>,\n id: string,\n error: unknown,\n ): readonly [FanOutState<I, R>, readonly (C | J)[]] {\n const settled = settleOne(state, id, \"failed\", config.idOf);\n if (settled === undefined) return [state, []];\n\n const next: FanOutState<I, R> = {\n ...state,\n running: settled.running,\n failed: [...state.failed, { item: settled.input, error }],\n items: settled.items,\n };\n return finishOrLaunch(next);\n }\n\n /**\n * Shared tail of `itemOk` / `itemErr`: backfill the freed slot, then — if the\n * CURRENT wave is now complete, a `join` is configured, and this wave has not\n * already joined — append the single completion Cmd and mark the wave joined.\n *\n * Two guards keep `join` firing exactly once per wave:\n * - `isComplete(next)` is the completion edge (the transition that drains\n * the last in-flight item; `launchUpTo` cannot re-fill from an empty\n * `pending`, so `running` stays empty and the edge is reached once).\n * - `!next.joined` makes it idempotent even if a stale settle re-reaches a\n * completed slice, and — critically — combined with the per-wave reset in\n * `scatter` it stops `join` re-firing across waves. Without the wave\n * boundary, `done` is append-only, so a SECOND wave draining would see\n * `isComplete` true and re-fold the WHOLE cumulative `done` (wave 1 +\n * wave 2). The fix folds only `done.slice(waveDoneFrom)` — exactly the\n * results this wave produced.\n */\n function finishOrLaunch(\n settled: FanOutState<I, R>,\n ): readonly [FanOutState<I, R>, readonly (C | J)[]] {\n const [launched, launchCmds] = launchUpTo(settled, config);\n if (config.join !== undefined && !launched.joined && isComplete(launched)) {\n const results = launched.done\n .slice(launched.waveDoneFrom)\n .map((d) => d.result);\n const next: FanOutState<I, R> = { ...launched, joined: true };\n return [next, [...launchCmds, config.join(results)]];\n }\n return [launched, launchCmds];\n }\n\n /**\n * The out-of-band completion splice. Returns an observer-shaped function the\n * consumer calls after every settle: when the batch has just completed it\n * emits the gathered results onto `ports.complete`. This is the Port-side\n * analogue of the `join` Cmd — use it when completion should leave the\n * runtime as a typed signal rather than re-enter `update`.\n *\n * Returns the emit-on-complete callback so the consumer wires it into its own\n * runtime `observe` (e.g. `runtime.observe((_, s) => emit(s.fanOut))`); fan-out\n * stays out of the dispatch loop, matching `historyTracker`'s discipline.\n */\n function handlers(ports: FanOutPorts<R>) {\n return {\n /**\n * Emit the gathered done-results onto `ports.complete` iff `state` is a\n * just-completed batch. Idempotency is the caller's concern (call it once\n * per transition via `observe`); emitting to a Port with no subscribers\n * is a no-op at the runtime, so an extra call is harmless.\n */\n emitOnComplete(\n emit: <T>(port: Port<T>, value: T) => void,\n state: FanOutState<I, R>,\n ): void {\n if (!isComplete(state)) return;\n emit(\n ports.complete,\n state.done.slice(state.waveDoneFrom).map((d) => d.result),\n );\n },\n };\n }\n\n return {\n init: initFanOut<I, R>,\n scatter,\n itemOk,\n itemErr,\n isComplete,\n handlers,\n };\n}\n"]}
@@ -0,0 +1,21 @@
1
+ // src/trace-replay/state-walk.ts
2
+ function typeOf(v) {
3
+ if (v === null) return "null";
4
+ if (v === void 0) return "undefined";
5
+ if (Array.isArray(v)) return "array";
6
+ if (typeof v === "object") return "object";
7
+ return "primitive";
8
+ }
9
+ function indexPath(path, i) {
10
+ return `${path}[${i}]`;
11
+ }
12
+ function keyPath(path, key) {
13
+ return `${path}.${key}`;
14
+ }
15
+ function unionKeys(a, b) {
16
+ return [.../* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)])].sort();
17
+ }
18
+
19
+ export { indexPath, keyPath, typeOf, unionKeys };
20
+ //# sourceMappingURL=chunk-EWJP3YYP.js.map
21
+ //# sourceMappingURL=chunk-EWJP3YYP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/trace-replay/state-walk.ts"],"names":[],"mappings":";AAoBO,SAAS,OACd,CAAA,EACyD;AACzD,EAAA,IAAI,CAAA,KAAM,MAAM,OAAO,MAAA;AACvB,EAAA,IAAI,CAAA,KAAM,QAAW,OAAO,WAAA;AAC5B,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAA,EAAG,OAAO,OAAA;AAC7B,EAAA,IAAI,OAAO,CAAA,KAAM,QAAA,EAAU,OAAO,QAAA;AAClC,EAAA,OAAO,WAAA;AACT;AAGO,SAAS,SAAA,CAAU,MAAc,CAAA,EAAmB;AACzD,EAAA,OAAO,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,CAAC,CAAA,CAAA,CAAA;AACrB;AAGO,SAAS,OAAA,CAAQ,MAAc,GAAA,EAAqB;AACzD,EAAA,OAAO,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,GAAG,CAAA,CAAA;AACvB;AAQO,SAAS,SAAA,CACd,GACA,CAAA,EACU;AACV,EAAA,OAAO,CAAC,mBAAG,IAAI,IAAI,CAAC,GAAG,OAAO,IAAA,CAAK,CAAC,CAAA,EAAG,GAAG,OAAO,IAAA,CAAK,CAAC,CAAC,CAAC,CAAC,EAAE,IAAA,EAAK;AACnE","file":"chunk-EWJP3YYP.js","sourcesContent":["// ---------------------------------------------------------------------------\n// state-walk — the structural-walk primitives the record/replay lane owns and\n// devtools' `diffState` reuses.\n//\n// Two walkers cross this substrate: `trace-replay`'s `firstDivergence` (first\n// differing cell, for a regression verdict) and `devtools`' `diffState` (EVERY\n// differing cell, classified add/remove/change, for a human diff). They diverge\n// on PURPOSE at the leaf — first-hit vs collect-all, length-mismatch-at-node vs\n// per-element — but they MUST agree on the path grammar: the same cell has to\n// print the same path in both, so a `divergence.path` from a replay result\n// indexes straight into a `StateChange[]`.\n//\n// That agreement used to be enforced by copy-paste discipline plus a comment.\n// Here it is enforced by construction: both walkers import the same `typeOf`,\n// the same `indexPath`/`keyPath` segment builders, and the same `unionKeys`\n// sorted-union order, so key-sorting and leaf equality cannot silently drift\n// between the two. Pure + zero-dep — it runs anywhere a machine runs.\n// ---------------------------------------------------------------------------\n\n/** Coarse type tag both walkers use to decide how to recurse. */\nexport function typeOf(\n v: unknown,\n): \"null\" | \"undefined\" | \"array\" | \"object\" | \"primitive\" {\n if (v === null) return \"null\";\n if (v === undefined) return \"undefined\";\n if (Array.isArray(v)) return \"array\";\n if (typeof v === \"object\") return \"object\";\n return \"primitive\";\n}\n\n/** Path to the `i`-th element of an array node, e.g. `state.queue[2]`. */\nexport function indexPath(path: string, i: number): string {\n return `${path}[${i}]`;\n}\n\n/** Path to a keyed child of an object node, e.g. `state.user.name`. */\nexport function keyPath(path: string, key: string): string {\n return `${path}.${key}`;\n}\n\n/**\n * Sorted union of both sides' own keys — the deterministic, insertion-order-\n * independent child order both walkers traverse. Shared so two states with the\n * same data in different key order compare equal in BOTH, and the reported path\n * for a real mismatch is reproducible run-to-run and identical across walkers.\n */\nexport function unionKeys(\n a: Record<string, unknown>,\n b: Record<string, unknown>,\n): string[] {\n return [...new Set([...Object.keys(a), ...Object.keys(b)])].sort();\n}\n"]}
@@ -1,3 +1,8 @@
1
+ // src/at.ts
2
+ function at(arr, index) {
3
+ return index < 0 || index >= arr.length ? void 0 : arr[index];
4
+ }
5
+
1
6
  // src/work-queue/ops.ts
2
7
  function enqueueOp(queue, input, now, id) {
3
8
  const item = {
@@ -10,7 +15,7 @@ function enqueueOp(queue, input, now, id) {
10
15
  }
11
16
  function claimNextOp(queue, now) {
12
17
  const idx = queue.findIndex((i) => i.status === "pending");
13
- const target = idx === -1 ? void 0 : queue[idx];
18
+ const target = at(queue, idx);
14
19
  if (target === void 0) return null;
15
20
  const claimed = {
16
21
  ...target,
@@ -27,7 +32,7 @@ function markDoneOp(queue, id) {
27
32
  }
28
33
  function patchItemOp(queue, id, patch) {
29
34
  const idx = queue.findIndex((i) => i.id === id);
30
- const target = idx === -1 ? void 0 : queue[idx];
35
+ const target = at(queue, idx);
31
36
  if (target === void 0) return { next: queue.slice(), changed: false };
32
37
  const next = queue.slice();
33
38
  next[idx] = patch(target);
@@ -47,6 +52,6 @@ function removeOp(queue, id) {
47
52
  return { next, changed: next.length !== queue.length };
48
53
  }
49
54
 
50
- export { claimNextOp, enqueueOp, markDoneOp, patchItemOp, removeOp, resetRunningOp };
51
- //# sourceMappingURL=chunk-TGG26XMK.js.map
52
- //# sourceMappingURL=chunk-TGG26XMK.js.map
55
+ export { at, claimNextOp, enqueueOp, markDoneOp, patchItemOp, removeOp, resetRunningOp };
56
+ //# sourceMappingURL=chunk-FV3Z5MBE.js.map
57
+ //# sourceMappingURL=chunk-FV3Z5MBE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/at.ts","../src/work-queue/ops.ts"],"names":[],"mappings":";AAcO,SAAS,EAAA,CAAM,KAAmB,KAAA,EAA8B;AACrE,EAAA,OAAO,QAAQ,CAAA,IAAK,KAAA,IAAS,IAAI,MAAA,GAAS,MAAA,GAAY,IAAI,KAAK,CAAA;AACjE;;;ACQO,SAAS,SAAA,CACd,KAAA,EACA,KAAA,EACA,GAAA,EACA,EAAA,EAC8C;AAC9C,EAAA,MAAM,IAAA,GAAqB;AAAA,IACzB,EAAA;AAAA,IACA,MAAA,EAAQ,SAAA;AAAA,IACR,KAAA;AAAA,IACA,UAAA,EAAY;AAAA,GACd;AACA,EAAA,OAAO,EAAE,IAAA,EAAM,CAAC,GAAG,KAAA,EAAO,IAAI,GAAG,IAAA,EAAK;AACxC;AAOO,SAAS,WAAA,CACd,OACA,GAAA,EACwD;AACxD,EAAA,MAAM,MAAM,KAAA,CAAM,SAAA,CAAU,CAAC,CAAA,KAAM,CAAA,CAAE,WAAW,SAAS,CAAA;AAIzD,EAAA,MAAM,MAAA,GAAS,EAAA,CAAG,KAAA,EAAO,GAAG,CAAA;AAC5B,EAAA,IAAI,MAAA,KAAW,QAAW,OAAO,IAAA;AACjC,EAAA,MAAM,OAAA,GAAwB;AAAA,IAC5B,GAAG,MAAA;AAAA,IACH,MAAA,EAAQ,SAAA;AAAA,IACR,SAAA,EAAW;AAAA,GACb;AACA,EAAA,MAAM,IAAA,GAAO,MAAM,KAAA,EAAM;AACzB,EAAA,IAAA,CAAK,GAAG,CAAA,GAAI,OAAA;AACZ,EAAA,OAAO,EAAE,MAAM,OAAA,EAAQ;AACzB;AAOO,SAAS,UAAA,CACd,OACA,EAAA,EAC4C;AAC5C,EAAA,MAAM,OAAO,KAAA,CAAM,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,OAAO,EAAE,CAAA;AAC5C,EAAA,OAAO,EAAE,IAAA,EAAM,OAAA,EAAS,IAAA,CAAK,MAAA,KAAW,MAAM,MAAA,EAAO;AACvD;AAQO,SAAS,WAAA,CACd,KAAA,EACA,EAAA,EACA,KAAA,EAC4C;AAC5C,EAAA,MAAM,MAAM,KAAA,CAAM,SAAA,CAAU,CAAC,CAAA,KAAM,CAAA,CAAE,OAAO,EAAE,CAAA;AAI9C,EAAA,MAAM,MAAA,GAAS,EAAA,CAAG,KAAA,EAAO,GAAG,CAAA;AAC5B,EAAA,IAAI,MAAA,KAAW,QAAW,OAAO,EAAE,MAAM,KAAA,CAAM,KAAA,EAAM,EAAG,OAAA,EAAS,KAAA,EAAM;AACvE,EAAA,MAAM,IAAA,GAAO,MAAM,KAAA,EAAM;AACzB,EAAA,IAAA,CAAK,GAAG,CAAA,GAAI,KAAA,CAAM,MAAM,CAAA;AACxB,EAAA,OAAO,EAAE,IAAA,EAAM,OAAA,EAAS,IAAA,EAAK;AAC/B;AAGO,SAAS,eAAkB,KAAA,EAGhC;AACA,EAAA,IAAI,OAAA,GAAU,KAAA;AACd,EAAA,MAAM,IAAA,GAAO,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,KAAoB;AAC1C,IAAA,IAAI,CAAA,CAAE,MAAA,KAAW,SAAA,EAAW,OAAO,CAAA;AACnC,IAAA,OAAA,GAAU,IAAA;AACV,IAAA,OAAO,EAAE,GAAG,CAAA,EAAG,MAAA,EAAQ,SAAA,EAAW,WAAW,MAAA,EAAU;AAAA,EACzD,CAAC,CAAA;AACD,EAAA,OAAO,EAAE,MAAM,OAAA,EAAQ;AACzB;AAGO,SAAS,QAAA,CACd,OACA,EAAA,EAC4C;AAC5C,EAAA,MAAM,OAAO,KAAA,CAAM,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,OAAO,EAAE,CAAA;AAC5C,EAAA,OAAO,EAAE,IAAA,EAAM,OAAA,EAAS,IAAA,CAAK,MAAA,KAAW,MAAM,MAAA,EAAO;AACvD","file":"chunk-FV3Z5MBE.js","sourcesContent":["// ---------------------------------------------------------------------------\n// at — indexed array read that returns `T | undefined`, stating this package's\n// findIndex-narrowing convention once.\n//\n// Without `noUncheckedIndexedAccess`, `arr[idx]` is typed `T` even when `idx`\n// is `-1` (findIndex's \"no match\") or past the end — so the recurring\n// \"locate by findIndex, then use the element\" pattern has to hand-collapse\n// `idx === -1 ? undefined : arr[idx]` to get an honest `T | undefined` and\n// early-out without a non-null assertion. That collapse (plus a justifying\n// comment) was re-derived at every such site (work-queue/ops, fan-out). `at`\n// owns the convention: pass the `findIndex` result straight in, branch on the\n// `undefined` it returns, and keep `idx` around for any subsequent splice.\n// ---------------------------------------------------------------------------\n\nexport function at<T>(arr: readonly T[], index: number): T | undefined {\n return index < 0 || index >= arr.length ? undefined : arr[index];\n}\n","/**\n * Pure queue-lifecycle ops over `QueueItem<I>[]` — the BLESSED delegation\n * surface for this package.\n *\n * Each op is `(queue, ...args) => { next, result? }`. No I/O, no Date.now\n * indirection beyond a plain call site — the adapter in `./index.ts` injects\n * `Store<QueueItem<I>[]>` and orchestrates `load → mutate → save`.\n *\n * Substrate-agnostic by construction: nothing here references chrome.storage,\n * Cloudflare, or any concrete persistence layer. The audit-specific shape\n * (journeyId, projectId, ...) is lifted into the caller's `I` parameter.\n *\n * Importable two ways. The `createQueue(...)` adapter in `./index.ts` binds\n * these ops to a `Store` for in-process use (same one-shape-per-package rule\n * as `@demlik/tea/do`). L2 compositions that own their own Model slice\n * (`monitored-run`, `idempotent-intake`, …) instead reach these ops directly\n * via the `@demlik/tea/work-queue/ops` subpath, so a status flip is delegated\n * here rather than re-rolled per call site.\n */\n\nimport { at } from \"../at\";\nimport type { QueueItem } from \"./index\";\n\n/** Append a new pending item. Returns the new queue and the created item. */\nexport function enqueueOp<I>(\n queue: readonly QueueItem<I>[],\n input: I,\n now: number,\n id: string,\n): { next: QueueItem<I>[]; item: QueueItem<I> } {\n const item: QueueItem<I> = {\n id,\n status: \"pending\",\n input,\n enqueuedAt: now,\n };\n return { next: [...queue, item], item };\n}\n\n/**\n * Claim the first pending item by flipping it to `running` and stamping\n * `startedAt`. Returns the new queue and the claimed item, or `null` if no\n * pending item exists.\n */\nexport function claimNextOp<I>(\n queue: readonly QueueItem<I>[],\n now: number,\n): { next: QueueItem<I>[]; claimed: QueueItem<I> } | null {\n const idx = queue.findIndex((i) => i.status === \"pending\");\n // `at` collapses \"no pending item\" (idx === -1) into the same `undefined`\n // early-out, so the element is typed `QueueItem<I>` below without a non-null\n // assertion; `idx` stays around for the write.\n const target = at(queue, idx);\n if (target === undefined) return null;\n const claimed: QueueItem<I> = {\n ...target,\n status: \"running\",\n startedAt: now,\n };\n const next = queue.slice();\n next[idx] = claimed;\n return { next, claimed };\n}\n\n/**\n * Done items leave the queue — completed runs live wherever the caller\n * persists their output. Returns the new queue (or the original reference if\n * no item matched, so the adapter can skip a save).\n */\nexport function markDoneOp<I>(\n queue: readonly QueueItem<I>[],\n id: string,\n): { next: QueueItem<I>[]; changed: boolean } {\n const next = queue.filter((i) => i.id !== id);\n return { next, changed: next.length !== queue.length };\n}\n\n/**\n * Generic single-item status transition. Used by `markFailed`,\n * `markCancelled`, `retryItem`, `bindOutput`. The patch closure receives the\n * matched item and returns its replacement; returning the same reference is\n * fine (the adapter only saves on `changed`).\n */\nexport function patchItemOp<I>(\n queue: readonly QueueItem<I>[],\n id: string,\n patch: (item: QueueItem<I>) => QueueItem<I>,\n): { next: QueueItem<I>[]; changed: boolean } {\n const idx = queue.findIndex((i) => i.id === id);\n // `at` collapses the no-match case into an `undefined` early-out, narrowing\n // `target` to `QueueItem<I>` for `patch` without a non-null assertion; `idx`\n // stays around for the write.\n const target = at(queue, idx);\n if (target === undefined) return { next: queue.slice(), changed: false };\n const next = queue.slice();\n next[idx] = patch(target);\n return { next, changed: true };\n}\n\n/** Reset every `running` item back to `pending`, clearing `startedAt`. */\nexport function resetRunningOp<I>(queue: readonly QueueItem<I>[]): {\n next: QueueItem<I>[];\n changed: boolean;\n} {\n let changed = false;\n const next = queue.map((i): QueueItem<I> => {\n if (i.status !== \"running\") return i;\n changed = true;\n return { ...i, status: \"pending\", startedAt: undefined };\n });\n return { next, changed };\n}\n\n/** Hard-delete an item regardless of status. */\nexport function removeOp<I>(\n queue: readonly QueueItem<I>[],\n id: string,\n): { next: QueueItem<I>[]; changed: boolean } {\n const next = queue.filter((i) => i.id !== id);\n return { next, changed: next.length !== queue.length };\n}\n"]}
@@ -0,0 +1,8 @@
1
+ // src/describe-error.ts
2
+ function describeError(e) {
3
+ return e instanceof Error ? e.message : String(e);
4
+ }
5
+
6
+ export { describeError };
7
+ //# sourceMappingURL=chunk-G5E74IJB.js.map
8
+ //# sourceMappingURL=chunk-G5E74IJB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/describe-error.ts"],"names":[],"mappings":";AAeO,SAAS,cAAc,CAAA,EAAoB;AAChD,EAAA,OAAO,CAAA,YAAa,KAAA,GAAQ,CAAA,CAAE,OAAA,GAAU,OAAO,CAAC,CAAA;AAClD","file":"chunk-G5E74IJB.js","sourcesContent":["// ---------------------------------------------------------------------------\n// describeError — the one way this package renders an UNKNOWN throw as a\n// human-readable reason string.\n//\n// Every \"errors are data\" boundary that catches a `catch (e: unknown)` (or a\n// settled `{ error: unknown }`) and needs a display/`reason` string faces the\n// same fork: an `Error` carries a `.message`; anything else (a thrown string,\n// a rejected non-Error, `undefined`) must be coerced. `String(e)` alone loses\n// the message for real `Error`s (it stringifies to `\"Error: …\"` at best, or a\n// bare `\"[object Object]\"`); `.message` alone throws on non-objects. The\n// `e instanceof Error ? e.message : String(e)` idiom threads both — hoisted\n// here so the rendering rule lives once instead of re-inlined at every\n// settled-error surface.\n// ---------------------------------------------------------------------------\n\nexport function describeError(e: unknown): string {\n return e instanceof Error ? e.message : String(e);\n}\n"]}
@@ -1,10 +1,11 @@
1
1
  import { MsgType } from './chunk-JELSZN25.js';
2
- import { deadlineSub } from './chunk-SKAMZKTE.js';
3
- import { asRng, recordFailure, shouldRetry, nextDelayMs, initRetry } from './chunk-5VNWZADP.js';
4
- import { initBucket, tryConsume } from './chunk-2YOE5PIS.js';
5
- import { initCircuit, onSuccess, onFailure, canPass } from './chunk-SC3FAFZN.js';
6
- import { initCache, set, get } from './chunk-IN33Q4VJ.js';
7
- import { tryInterpret } from './chunk-6VDCTR7N.js';
2
+ import { deadlineSub } from './chunk-RMX4EOQK.js';
3
+ import { asRng, recordFailure, shouldRetry, nextDelayMs, initRetry } from './chunk-Q2UM7BXU.js';
4
+ import { initBucket, tryConsume } from './chunk-S2EGRPCM.js';
5
+ import { initCircuit, onSuccess, onFailure, canPass } from './chunk-UF4MZSLT.js';
6
+ import { initCache, set, get } from './chunk-2KNLKQVN.js';
7
+ import { tryInterpret } from './chunk-BP2R7TPP.js';
8
+ import { without } from './chunk-PBDYW2TU.js';
8
9
 
9
10
  // src/resilient-call/index.ts
10
11
  function circuitPolicy(config) {
@@ -94,9 +95,9 @@ function createResilientCall(config, rng = Math.random) {
94
95
  if (config.retry === void 0) {
95
96
  return [setCall(s, key, { phase: "failed", error }), []];
96
97
  }
97
- const retry = recordFailure(retryOf(s, key), error);
98
+ const retry = recordFailure(retryOf(s, key), error, at);
98
99
  const withRetry = { ...s, retry: { ...s.retry, [key]: retry } };
99
- if (!shouldRetry(retry, config.retry)) {
100
+ if (!shouldRetry(retry, config.retry, at)) {
100
101
  return [setCall(withRetry, key, { phase: "failed", error }), []];
101
102
  }
102
103
  const retryAtMs = at + nextDelayMs(retry, config.retry, rngBranded);
@@ -119,7 +120,7 @@ function createResilientCall(config, rng = Math.random) {
119
120
  function succeed(s, key, msg) {
120
121
  const circuit = cPolicy !== null ? onSuccess(s.circuit, cPolicy) : s.circuit;
121
122
  const cache = config.cache !== void 0 ? set(s.cache, key, msg.result, msg.at, config.cache.ttlMs) : s.cache;
122
- const { [key]: _dropped, ...retry } = s.retry;
123
+ const retry = without(s.retry, key);
123
124
  return [
124
125
  {
125
126
  ...setCall(s, key, { phase: "succeeded", result: msg.result }),
@@ -218,5 +219,5 @@ function liftResilience(state, [slice, cmds]) {
218
219
  }
219
220
 
220
221
  export { createResilientCall, liftResilience };
221
- //# sourceMappingURL=chunk-6GIGQT6C.js.map
222
- //# sourceMappingURL=chunk-6GIGQT6C.js.map
222
+ //# sourceMappingURL=chunk-IXBKFQH5.js.map
223
+ //# sourceMappingURL=chunk-IXBKFQH5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/resilient-call/index.ts"],"names":[],"mappings":";;;;;;;;;;AAwUA,SAAS,cAAc,MAAA,EAAyB;AAC9C,EAAA,IAAI,MAAA,CAAO,OAAA,KAAY,MAAA,EAAW,OAAO,IAAA;AACzC,EAAA,OAAO;AAAA,IACL,gBAAA,EAAkB,OAAO,OAAA,CAAQ,SAAA;AAAA,IACjC,UAAA,EAAY,OAAO,OAAA,CAAQ,UAAA;AAAA,IAC3B,iBAAA,EAAmB,MAAA,CAAO,OAAA,CAAQ,iBAAA,IAAqB;AAAA,GACzD;AACF;AAIA,SAAS,aAAa,GAAA,EAAqB;AACzC,EAAA,OAAO,mBAAmB,GAAG,CAAA,CAAA;AAC/B;AACA,SAAS,gBAAgB,GAAA,EAAqB;AAC5C,EAAA,OAAO,sBAAsB,GAAG,CAAA,CAAA;AAClC;AAmBO,SAAS,mBAAA,CACd,MAAA,EACA,GAAA,GAAoB,IAAA,CAAK,MAAA,EACzB;AACA,EAAA,MAAM,OAAA,GAAU,cAAc,MAAM,CAAA;AAKpC,EAAA,MAAM,UAAA,GAAa,MAAM,GAAG,CAAA;AAG5B,EAAA,SAAS,IAAA,GAA6B;AACpC,IAAA,OAAO;AAAA,MACL,SAAS,WAAA,EAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMrB,MAAA,EAAQ,OAAO,SAAA,GACX,UAAA;AAAA,QACE,OAAO,SAAA,CAAU,QAAA;AAAA,QACjB,OAAO,SAAA,CAAU,YAAA;AAAA,QACjB;AAAA,OACF,GACA,UAAA,CAAW,CAAA,EAAG,CAAA,EAAG,CAAC,CAAA;AAAA,MACtB,OAAO,EAAC;AAAA,MACR,OAAO,SAAA,EAAa;AAAA,MACpB,OAAO;AAAC,KACV;AAAA,EACF;AAGA,EAAA,SAAS,OAAA,CAAQ,GAAyB,GAAA,EAAyB;AACjE,IAAA,OAAO,CAAA,CAAE,KAAA,CAAM,GAAG,CAAA,IAAK,SAAA,EAAU;AAAA,EACnC;AAGA,EAAA,SAAS,WAAW,EAAA,EAAoB;AACtC,IAAA,OAAO,MAAA,CAAO,QAAA,GAAW,EAAA,GAAK,MAAA,CAAO,SAAS,EAAA,GAAK,CAAA;AAAA,EACrD;AA2BA,EAAA,SAAS,IAAA,CACP,CAAA,EACA,GAAA,EACA,KAAA,EACA,IACA,YAAA,EACuD;AAEvD,IAAA,IAAI,MAAA,CAAO,UAAU,MAAA,EAAW;AAC9B,MAAA,MAAM,MAAA,GAAS,GAAA,CAAS,CAAA,CAAE,KAAA,EAAO,KAAK,EAAE,CAAA;AACxC,MAAA,IAAI,WAAW,MAAA,EAAW;AACxB,QAAA,OAAO,CAAC,OAAA,CAAQ,CAAA,EAAG,GAAA,EAAK,EAAE,KAAA,EAAO,WAAA,EAAa,MAAA,EAAQ,MAAA,EAAQ,CAAA,EAAG,EAAE,CAAA;AAAA,MACrE;AAAA,IACF;AAMA,IAAA,IAAI,SAAS,CAAA,CAAE,MAAA;AACf,IAAA,IAAI,MAAA,CAAO,cAAc,MAAA,EAAW;AAClC,MAAA,MAAM,CAAC,IAAA,EAAM,QAAQ,CAAA,GAAI,UAAA,CAAW,QAAQ,EAAE,CAAA;AAC9C,MAAA,MAAA,GAAS,IAAA;AACT,MAAA,IAAI,CAAC,QAAA,EAAU;AAGb,QAAA,OAAO,OAAA;AAAA,UACL,EAAE,GAAG,CAAA,EAAG,MAAA,EAAO;AAAA,UACf,GAAA;AAAA,UACA,KAAA;AAAA,UACA,cAAA;AAAA,UACA,EAAA;AAAA,UACA;AAAA,SACF;AAAA,MACF;AAAA,IACF;AAKA,IAAA,IAAI,UAAU,CAAA,CAAE,OAAA;AAChB,IAAA,IAAI,YAAY,IAAA,EAAM;AACpB,MAAA,MAAM,CAAC,IAAA,EAAM,EAAE,IAAI,OAAA,CAAQ,OAAA,EAAS,SAAS,EAAE,CAAA;AAC/C,MAAA,OAAA,GAAU,IAAA;AACV,MAAA,IAAI,CAAC,EAAA,EAAI;AACP,QAAA,OAAO;AAAA,UACL,EAAE,GAAG,OAAA,CAAQ,CAAA,EAAG,GAAA,EAAK,EAAE,KAAA,EAAO,cAAA,EAAgB,CAAA,EAAG,MAAA,EAAQ,OAAA,EAAQ;AAAA,UACjE;AAAC,SACH;AAAA,MACF;AAAA,IACF;AAGA,IAAA,OAAO;AAAA,MACL;AAAA,QACE,GAAG,QAAQ,CAAA,EAAG,GAAA,EAAK,EAAE,KAAA,EAAO,SAAA,EAAW,KAAA,EAAO,YAAA,EAAc,CAAA;AAAA,QAC5D,OAAA;AAAA,QACA;AAAA,OACF;AAAA,MACA,CAAC,EAAE,IAAA,EAAM,QAAQ,YAAA,EAAc,GAAA,EAAK,OAAO;AAAA,KAC7C;AAAA,EACF;AAeA,EAAA,SAAS,QACP,CAAA,EACA,GAAA,EACA,KAAA,EACA,KAAA,EACA,IACA,YAAA,EACuD;AACvD,IAAA,IAAI,MAAA,CAAO,UAAU,MAAA,EAAW;AAC9B,MAAA,OAAO,CAAC,OAAA,CAAQ,CAAA,EAAG,GAAA,EAAK,EAAE,KAAA,EAAO,QAAA,EAAU,KAAA,EAAO,CAAA,EAAG,EAAE,CAAA;AAAA,IACzD;AACA,IAAA,MAAM,QAAQ,aAAA,CAAc,OAAA,CAAQ,GAAG,GAAG,CAAA,EAAG,OAAO,EAAE,CAAA;AACtD,IAAA,MAAM,SAAA,GAAY,EAAE,GAAG,CAAA,EAAG,KAAA,EAAO,EAAE,GAAG,CAAA,CAAE,KAAA,EAAO,CAAC,GAAG,GAAG,OAAM,EAAE;AAC9D,IAAA,IAAI,CAAC,WAAA,CAAY,KAAA,EAAO,MAAA,CAAO,KAAA,EAAO,EAAE,CAAA,EAAG;AACzC,MAAA,OAAO,CAAC,OAAA,CAAQ,SAAA,EAAW,GAAA,EAAK,EAAE,KAAA,EAAO,QAAA,EAAU,KAAA,EAAO,CAAA,EAAG,EAAE,CAAA;AAAA,IACjE;AACA,IAAA,MAAM,YAAY,EAAA,GAAK,WAAA,CAAY,KAAA,EAAO,MAAA,CAAO,OAAO,UAAU,CAAA;AAClE,IAAA,OAAO;AAAA,MACL,OAAA,CAAQ,WAAW,GAAA,EAAK;AAAA,QACtB,KAAA,EAAO,eAAA;AAAA,QACP,KAAA;AAAA,QACA,SAAA;AAAA,QACA;AAAA,OACD,CAAA;AAAA,MACD;AAAC,KACH;AAAA,EACF;AAGA,EAAA,SAAS,OAAA,CACP,CAAA,EACA,GAAA,EACA,KAAA,EACsB;AACtB,IAAA,OAAO,EAAE,GAAG,CAAA,EAAG,KAAA,EAAO,EAAE,GAAG,CAAA,CAAE,KAAA,EAAO,CAAC,GAAG,GAAG,KAAA,EAAM,EAAE;AAAA,EACrD;AAUA,EAAA,SAAS,OAAA,CACP,CAAA,EACA,GAAA,EACA,KAAA,EACA,EAAA,EACuD;AACvD,IAAA,OAAO,KAAK,CAAA,EAAG,GAAA,EAAK,OAAO,EAAA,EAAI,UAAA,CAAW,EAAE,CAAC,CAAA;AAAA,EAC/C;AASA,EAAA,SAAS,OAAA,CACP,CAAA,EACA,GAAA,EACA,GAAA,EACuD;AACvD,IAAA,MAAM,OAAA,GACJ,YAAY,IAAA,GAAO,SAAA,CAAU,EAAE,OAAA,EAAS,OAAO,IAAI,CAAA,CAAE,OAAA;AACvD,IAAA,MAAM,QACJ,MAAA,CAAO,KAAA,KAAU,MAAA,GACb,GAAA,CAAS,EAAE,KAAA,EAAO,GAAA,EAAK,GAAA,CAAI,MAAA,EAAQ,IAAI,EAAA,EAAI,MAAA,CAAO,KAAA,CAAM,KAAK,IAC7D,CAAA,CAAE,KAAA;AAER,IAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,CAAA,CAAE,KAAA,EAAO,GAAG,CAAA;AAClC,IAAA,OAAO;AAAA,MACL;AAAA,QACE,GAAG,OAAA,CAAQ,CAAA,EAAG,GAAA,EAAK,EAAE,OAAO,WAAA,EAAa,MAAA,EAAQ,GAAA,CAAI,MAAA,EAAQ,CAAA;AAAA,QAC7D,OAAA;AAAA,QACA,KAAA;AAAA,QACA;AAAA,OACF;AAAA,MACA;AAAC,KACH;AAAA,EACF;AAUA,EAAA,SAAS,IAAA,CACP,CAAA,EACA,GAAA,EACA,GAAA,EACuD;AACvD,IAAA,MAAM,OAAA,GACJ,OAAA,KAAY,IAAA,GAAO,SAAA,CAAU,CAAA,CAAE,SAAS,OAAA,EAAS,GAAA,CAAI,EAAE,CAAA,GAAI,CAAA,CAAE,OAAA;AAC/D,IAAA,MAAM,WAAA,GAAc,EAAE,GAAG,CAAA,EAAG,OAAA,EAAQ;AACpC,IAAA,MAAM,IAAA,GAAO,CAAA,CAAE,KAAA,CAAM,GAAG,CAAA;AACxB,IAAA,MAAM,KAAA,GAAQ,IAAA,EAAM,KAAA,KAAU,SAAA,GAAY,KAAK,KAAA,GAAQ,MAAA;AACvD,IAAA,MAAM,YAAA,GACJ,MAAM,KAAA,KAAU,SAAA,GAAY,KAAK,YAAA,GAAe,UAAA,CAAW,IAAI,EAAE,CAAA;AAGnE,IAAA,IAAI,UAAU,MAAA,EAAW;AACvB,MAAA,OAAO;AAAA,QACL,OAAA,CAAQ,aAAa,GAAA,EAAK,EAAE,OAAO,QAAA,EAAU,KAAA,EAAO,GAAA,CAAI,KAAA,EAAO,CAAA;AAAA,QAC/D;AAAC,OACH;AAAA,IACF;AACA,IAAA,OAAO,OAAA,CAAQ,aAAa,GAAA,EAAK,KAAA,EAAO,IAAI,KAAA,EAAO,GAAA,CAAI,IAAI,YAAY,CAAA;AAAA,EACzE;AA6BA,EAAA,SAAS,YAAA,CACP,CAAA,EACA,GAAA,EACA,KAAA,EACuD;AACvD,IAAA,OAAO,CAAC,OAAA,CAAQ,CAAA,EAAG,GAAA,EAAK,EAAE,KAAA,EAAO,QAAA,EAAU,KAAA,EAAO,CAAA,EAAG,EAAE,CAAA;AAAA,EACzD;AAgBA,EAAA,SAAS,OAAA,CACP,GACA,GAAA,EACuD;AACvD,IAAA,KAAA,MAAW,CAAC,KAAK,IAAI,CAAA,IAAK,OAAO,OAAA,CAAQ,CAAA,CAAE,KAAK,CAAA,EAAG;AACjD,MAAA,IAAI,GAAA,CAAI,EAAA,KAAO,YAAA,CAAa,GAAG,CAAA,EAAG;AAChC,QAAA,IAAI,KAAK,KAAA,KAAU,eAAA,SAAwB,CAAC,CAAA,EAAG,EAAE,CAAA;AACjD,QAAA,OAAO,IAAA,CAAK,GAAG,GAAA,EAAK,IAAA,CAAK,OAAO,GAAA,CAAI,IAAA,EAAM,KAAK,YAAY,CAAA;AAAA,MAC7D;AACA,MAAA,IAAI,GAAA,CAAI,EAAA,KAAO,eAAA,CAAgB,GAAG,CAAA,EAAG;AAGnC,QAAA,IAAI,IAAA,CAAK,KAAA,KAAU,SAAA,IAAa,IAAA,CAAK,KAAA,KAAU,eAAA;AAC7C,UAAA,OAAO,CAAC,CAAA,EAAG,EAAE,CAAA;AAMf,QAAA,OAAO;AAAA,UACL,OAAA,CAAQ,GAAG,GAAA,EAAK;AAAA,YACd,KAAA,EAAO,QAAA;AAAA,YACP,KAAA,EAAO;AAAA,cACL,IAAA,EAAM,mBAAA;AAAA,cACN,IAAI,GAAA,CAAI,EAAA;AAAA,cACR,MAAM,GAAA,CAAI;AAAA;AACZ,WACD,CAAA;AAAA,UACD;AAAC,SACH;AAAA,MACF;AAAA,IACF;AACA,IAAA,OAAO,CAAC,CAAA,EAAG,EAAE,CAAA;AAAA,EACf;AAWA,EAAA,SAAS,KAAK,CAAA,EAAiD;AAC7D,IAAA,MAAM,MAAqB,EAAC;AAC5B,IAAA,KAAA,MAAW,CAAC,KAAK,IAAI,CAAA,IAAK,OAAO,OAAA,CAAQ,CAAA,CAAE,KAAK,CAAA,EAAG;AACjD,MAAA,IAAI,IAAA,CAAK,UAAU,eAAA,EAAiB;AAClC,QAAA,GAAA,CAAI,KAAK,WAAA,CAAY,YAAA,CAAa,GAAG,CAAA,EAAG,IAAA,CAAK,SAAS,CAAC,CAAA;AAAA,MACzD;AACA,MAAA,IACE,MAAA,CAAO,QAAA,KAAa,MAAA,KACnB,IAAA,CAAK,KAAA,KAAU,SAAA,IAAa,IAAA,CAAK,KAAA,KAAU,eAAA,CAAA,IAC5C,IAAA,CAAK,YAAA,GAAe,CAAA,EACpB;AACA,QAAA,GAAA,CAAI,KAAK,WAAA,CAAY,eAAA,CAAgB,GAAG,CAAA,EAAG,IAAA,CAAK,YAAY,CAAC,CAAA;AAAA,MAC/D;AAAA,IACF;AACA,IAAA,OAAO,GAAA;AAAA,EACT;AAaA,EAAA,SAAS,SAAS,KAAA,EAA6B;AAC7C,IAAA,OAAO;AAAA,MACL,aAAA,EAAe,YAAA;AAAA,QASb,CAAC,GAAA,KAAQ,KAAA,CAAM,IAAI,GAAA,CAAI,KAAA,EAAO,IAAI,GAAG,CAAA;AAAA,QACrC,CAAC,QAAQ,GAAA,MAAwB;AAAA,UAC/B,MAAM,OAAA,CAAQ,WAAA;AAAA,UACd,KAAK,GAAA,CAAI,GAAA;AAAA,UACT,MAAA;AAAA,UACA,EAAA,EAAI,KAAK,GAAA;AAAI,SACf,CAAA;AAAA,QACA,CAAC,OAAO,GAAA,MAAkB;AAAA,UACxB,MAAM,OAAA,CAAQ,YAAA;AAAA,UACd,KAAK,GAAA,CAAI,GAAA;AAAA,UACT,KAAA;AAAA,UACA,EAAA,EAAI,KAAK,GAAA;AAAI,SACf;AAAA;AACF,KACF;AAAA,EACF;AAEA,EAAA,OAAO;AAAA,IACL,IAAA;AAAA,IACA,OAAA;AAAA,IACA,OAAA;AAAA,IACA,IAAA;AAAA,IACA,YAAA;AAAA,IACA,OAAA;AAAA,IACA,IAAA;AAAA,IACA;AAAA,GACF;AACF;AAYO,SAAS,cAAA,CAMd,KAAA,EACA,CAAC,KAAA,EAAO,IAAI,CAAA,EACgB;AAC5B,EAAA,OAAO,CAAC,EAAE,GAAG,OAAO,UAAA,EAAY,KAAA,IAAS,IAAI,CAAA;AAC/C","file":"chunk-IXBKFQH5.js","sourcesContent":["/**\n * @packageDocumentation\n *\n * @deprecated `@demlik/tea/resilient-call` is deprecated in favor of\n * `@demlik/tea/with-resilience` — the higher-order `withResilience(base,\n * config)` wrapper (TEA-idiomatic: it wraps any existing machine instead of\n * forcing a bespoke embedded slice; epic consolidation verdict, survivor\n * `with-resilience`). The APIs are NOT drop-in; migration map:\n *\n * - `createResilientCall(config, rng)` + hand-wiring `init` / the verbs /\n * `subs` / `handlers` into your machine → `withResilience(base, { target,\n * ...bricks }, rng)`. Nothing is wired by hand anymore: the wrapper\n * intercepts the `target` base Cmd and routes it through the gate.\n * - `ResilientConfig` bricks (`retry` / `circuit` / `rateLimit` / `cache` /\n * `deadline`) → unchanged; `ResilienceConfig` extends the same knob and\n * adds `target` (required), `keyOf?` (per-call key — was `attempt`'s `key`\n * argument), and `at?` (time-as-data reader — REQUIRED with any\n * time-sensitive brick; `withResilience` throws at construction without it).\n * - The slice at your chosen Model field + `liftResilience` → the wrapper\n * owns `model.$resilience`; there is nothing to lift.\n * - Verbs `attempt` / `succeed` / `fail` / `onTimer` → internal to the\n * wrapper; results route as `$resilience:ok` / `$resilience:err` /\n * `$resilience:timer` Msgs through the merged `update` (the old\n * `resilient_run` / `resilient_ok` / `resilient_err` vocabulary is renamed\n * into the `$resilience:` namespace).\n * - `settleFailed` → NO equivalent. It exists for embedded-slice consumers\n * (`authed-call`, `paginated-walk`) that settle a call without touching the\n * breaker; the wrapper exposes no caller-facing settle verb.\n * - `handlers(ports)` / `subs(state)` / the `subscribeDeadline` re-export →\n * handled by the wrapper; run the returned machine like any other.\n *\n * Removal window (\"deprecate, don't delete\"): the `./resilient-call` subpath\n * export survives one minor release after this deprecation ships, then is\n * removed. Internal sibling modules (`with-resilience` itself, `agent`,\n * `llm-call`, `authed-call`, `reconciler`, `paginated-walk`) intentionally keep\n * importing `../resilient-call` — the deprecation targets the PUBLISHED export,\n * not the module's internal role.\n *\n * @demlik/tea/resilient-call — the ROOT composition: a single resilience knob\n * that bundles cache → circuit-breaker → rate-limit → exponential-backoff retry\n * (with a deadline-driven retry timer) around any fallible `(input) => Promise`\n * port.\n *\n * This is `examples/resilient-fetch.ts`'s hand-wired `attempt()` generalized:\n *\n * - It is **keyed**. The example tracked one URL at a time; this tracks N\n * concurrent logical calls, each under a string `key`. The cache, the\n * waiting-retry bookkeeping, and the retry timers are all per-key, while the\n * circuit breaker and token bucket are shared (one downstream target, one\n * breaker / one bucket — the example's implicit single-target assumption\n * made explicit).\n * - It is **config-driven**, not hand-wired. You hand `createResilientCall`\n * a `ResilientConfig` and get back the uniform knob contract every L2\n * composition exposes: `init()`, the four verbs (`attempt` / `succeed` /\n * `fail` / `onTimer`), `subs(state)`, and `handlers(ports)`.\n * - Every brick is **optional**. Omit `cache` and the cache gate disappears\n * (`get` is never consulted); omit `circuit` and every call passes the\n * breaker; omit `rateLimit` and the bucket never throttles; omit `retry`\n * and a failure is terminal (no backoff, no timer); omit `deadline` and no\n * overall wall-clock cap is armed. \"Omit a brick → omit its gate\" is the\n * whole config story — there are no other modes.\n *\n * Four later compositions (`llm-call`, `paginated-walk`, `authed-call`,\n * `reconciler`) inherit THIS slice + verb shape, so the contract here is\n * load-bearing: the slice is a flat record of brick states, every verb returns\n * `readonly [State, Cmd[]]`, time is always an `at` / `now` parameter, and the\n * RNG used for jitter is injected on the config (never read inside a verb).\n *\n * ## The two non-negotiables (canon)\n *\n * - **Durable** — the slice is a plain-data Model field (brick states are all\n * JSON-serializable), so it survives DO eviction / reload.\n * - **Replayable** — every transition is a verb returning new state + Cmds;\n * `replay` reconstructs it exactly. Nothing here closes over time or RNG.\n *\n * ## Where the clock / RNG live\n *\n * Inside the verbs: nowhere. Time arrives as `at` on `attempt` / `onTimer` and\n * is carried on the result / timer Msgs the consumer dispatches. Jitter RNG is\n * injected once at `createResilientCall(config, rng)` and threaded into\n * `nextDelayMs` at the verb boundary — deterministic in tests, `Math.random`\n * by default. The ONLY clock read is `Date.now()` inside the `handlers` port\n * (the effect boundary), exactly as `resilient-fetch.ts` stamps its result Msgs.\n *\n * That same `at` is what makes a DURATION-bounded retry budget reachable here:\n * `recordFailure` / `shouldRetry` are fed the failure's observation instant,\n * so `config.retry` accepts any `AnyRetryPolicy` — a count, a wall-clock outage\n * budget (`maxElapsedMs`), or explicit `unbounded: true` — with no new argument\n * on any verb and no clock read inside one.\n *\n * ## Typical wiring\n *\n * const rc = createResilientCall<string, Result>({\n * cache: { ttlMs: 60_000 },\n * circuit: { threshold: 5, cooldownMs: 30_000 },\n * rateLimit: { capacity: 10, refillPerSec: 5 },\n * retry: defaultRetryPolicy,\n * deadline: { ms: 5_000 },\n * });\n *\n * // in the machine:\n * init: () => [{ resilience: rc.init() }, []],\n * update: {\n * fetch: (s, m) => lift(s, rc.attempt(s.resilience, m.key, m.input, m.at)),\n * call_ok: (s, m) => lift(s, rc.succeed(s.resilience, m.key, m)),\n * call_err:(s, m) => lift(s, rc.fail(s.resilience, m.key, m)),\n * retry_due:(s, m) => lift(s, rc.onTimer(s.resilience, m)),\n * },\n * subscriptions: (s) => rc.subs(s.resilience),\n * subscribe: { deadline: subscribeDeadline },\n * interpret: rc.handlers({ run: ctx.call }),\n */\n\nimport {\n get as cacheGet,\n set as cacheSet,\n initCache,\n type TtlCache,\n} from \"../cache\";\nimport {\n type CircuitState,\n canPass,\n initCircuit,\n onFailure,\n onSuccess,\n} from \"../circuit-breaker\";\nimport {\n type DeadlineExceeded,\n type DeadlineSub,\n deadlineSub,\n subscribeDeadline,\n} from \"../deadline\";\nimport { type Cmd, type NoCtx, tryInterpret } from \"../index\";\nimport { MsgType } from \"../protocol\";\nimport { without } from \"../pure/core\";\nimport { initBucket, type TokenBucket, tryConsume } from \"../rate-limit\";\nimport {\n type AnyRetryPolicy,\n asRng,\n initRetry,\n nextDelayMs,\n type RetryState,\n recordFailure,\n shouldRetry,\n} from \"../retry-backoff\";\n\n// ===========================================================================\n// Config — the knob. Every brick optional; omit a brick → omit its gate.\n// ===========================================================================\n\n/** Circuit-breaker knob — only the two numbers a consumer ever tunes. */\nexport interface CircuitConfig {\n /** Consecutive failures that trip the breaker open. */\n readonly threshold: number;\n /** How long the breaker stays open before admitting a probe, in ms. */\n readonly cooldownMs: number;\n /**\n * Probes admitted in `half_open` before fast-failing the rest of the round.\n * Defaults to 1 (a single probe decides recovery) — matches the circuit-\n * breaker module's `defaultCircuitPolicy`.\n */\n readonly halfOpenMaxProbes?: number;\n}\n\n/** Token-bucket rate-limit knob. */\nexport interface RateLimitConfig {\n /** Burst size — the bucket starts full at `capacity`. */\n readonly capacity: number;\n /** Steady-state refill rate, tokens per second. */\n readonly refillPerSec: number;\n}\n\n/** Per-entry TTL cache knob. */\nexport interface CacheConfig {\n /** How long a cached success stays fresh, in ms. */\n readonly ttlMs: number;\n}\n\n/** Overall wall-clock deadline knob — an absolute cap per in-flight call. */\nexport interface DeadlineConfig {\n /** Budget from the moment a call starts, in ms. */\n readonly ms: number;\n}\n\n/**\n * The resilience knob. EVERY field is optional: omit a brick and its gate is\n * skipped entirely (the slice still carries a default brick state so the shape\n * stays uniform across configs, but the gate is never consulted). An empty\n * config `{}` is a valid pass-through — a bare effect with no resilience at all.\n */\nexport interface ResilientConfig {\n /**\n * The backoff brick. Omit and a failure is terminal (no backoff, no timer).\n *\n * Any bound the `../retry-backoff` union admits: a count (`RetryPolicy`), a\n * wall-clock outage budget (`DurationRetryPolicy`), or explicit\n * `unbounded: true`. A duration bound needs no extra wiring: every path that\n * records a failure already holds the instant it was observed as DATA —\n * `fail`'s `msg.at` (stamped at the interpret boundary) and `gate`'s `at`\n * (the caller's `attempt` / the retry timer's `atMs`) — so the streak clock\n * is fed from a Msg, never from a `Date.now()` inside a verb (invariant 2).\n */\n readonly retry?: AnyRetryPolicy;\n readonly circuit?: CircuitConfig;\n readonly rateLimit?: RateLimitConfig;\n readonly cache?: CacheConfig;\n readonly deadline?: DeadlineConfig;\n}\n\n// ===========================================================================\n// Slice — the Model field this knob owns. A flat record of brick states.\n// ===========================================================================\n\n/**\n * Per-key call phase. `key` indexes a logical call; several can be in flight at\n * once (the generalization of the example's single `url`). Discriminated on\n * `phase` so each phase carries only its own data (pattern 11):\n *\n * - `idle` — never attempted, or fully settled and forgotten.\n * - `running` — the effect Cmd is out; awaiting `succeed` / `fail`.\n * Carries `deadlineAtMs` (absolute cap, `0` if no deadline\n * brick) and the last `input` so a retry can re-issue it.\n * - `waiting_retry` — a transient failure backed off; the retry timer is armed\n * for `retryAtMs`. Carries `input` for the re-attempt.\n * - `circuit_open` — the breaker fast-failed this attempt. Terminal for this\n * call until the consumer re-attempts after cooldown.\n * - `succeeded` — settled OK. `result` is the value the port produced.\n * - `failed` — settled Err (out of retries, or no retry brick). `error`\n * carries the last failure.\n */\nexport type CallPhase<I, R> =\n | { readonly phase: \"idle\" }\n | {\n readonly phase: \"running\";\n readonly input: I;\n readonly deadlineAtMs: number;\n }\n | {\n readonly phase: \"waiting_retry\";\n readonly input: I;\n readonly retryAtMs: number;\n readonly deadlineAtMs: number;\n }\n | { readonly phase: \"circuit_open\" }\n | { readonly phase: \"succeeded\"; readonly result: R }\n | { readonly phase: \"failed\"; readonly error: unknown };\n\n/**\n * The slice. `circuit` / `bucket` are SHARED across keys (one downstream target\n * → one breaker, one bucket). `retry` and `cache` are PER KEY (each logical call\n * has its own backoff count + cached result), as is the per-call `CallPhase`.\n * Every field is plain data → the whole slice serializes into a `Store<S>`.\n *\n * `retry` / `cache` keep a default brick value even when the corresponding\n * config brick is omitted, so the slice shape is identical regardless of which\n * bricks the consumer enabled — the verbs simply never consult the unused brick.\n */\nexport interface ResilientState<I, R> {\n readonly circuit: CircuitState;\n readonly bucket: TokenBucket;\n /**\n * Per-key backoff counter. Entries minted by `backoff` are `TimedRetryState`s\n * (they carry the streak's `firstFailureAtMs`, since every failure path holds\n * the observation instant); the field is typed at the `RetryState` supertype\n * because a key that has never failed has no streak, and a slice persisted\n * before the origin existed rehydrates without one. Still plain data.\n */\n readonly retry: Readonly<Record<string, RetryState>>;\n readonly cache: TtlCache<R>;\n readonly calls: Readonly<Record<string, CallPhase<I, R>>>;\n}\n\n// ===========================================================================\n// Cmds + Msgs the knob speaks. Generic over the port input `I`.\n// ===========================================================================\n\n/**\n * The one effect this knob emits: \"run the port for `key` with `input`\". The\n * consumer's `handlers(ports)` interprets it; it carries no closure — `input`\n * is plain data (invariant 3).\n */\nexport type RunCmd<I> = Cmd<typeof MsgType.ResilientRun> & {\n readonly key: string;\n readonly input: I;\n};\n\n/** Settle Msgs the `handlers` port dispatches back. `at` is stamped at the boundary. */\nexport type SucceedMsg<R> = {\n readonly type: typeof MsgType.ResilientOk;\n readonly key: string;\n readonly result: R;\n readonly at: number;\n};\nexport type FailMsg = {\n readonly type: typeof MsgType.ResilientErr;\n readonly key: string;\n readonly error: unknown;\n readonly at: number;\n};\n\n/** The retry / deadline timer Msg — `DeadlineExceeded`, keyed by the call `key`. */\nexport type ResilientTimerMsg = DeadlineExceeded;\n\n/**\n * The plain-data error a deadline-failed call settles with. A `{_tag, ...}`\n * sentinel — NEVER a `new Error(...)` — so the slice stays JSON-serializable\n * (Error objects round-trip to `{}` and carry a stack trace, both of which break\n * the slice's durability invariant). `id` / `atMs` echo the timer that fired so\n * a consumer can attribute the failure to its deadline.\n */\nexport type DeadlineExceededError = {\n readonly _tag: \"deadline_exceeded\";\n readonly id: string;\n readonly atMs: number;\n};\n\n/** Ports the consumer supplies to `handlers`. */\nexport interface ResilientPorts<I, R> {\n /** The fallible work this knob wraps. Throws on failure → routed to `fail`. */\n readonly run: (input: I, key: string) => Promise<R>;\n}\n\n// ===========================================================================\n// Internal brick-policy derivation — config → the L1 policy shapes.\n// ===========================================================================\n\n// A circuit policy is only built when the brick is configured; absence is\n// represented as `null` so the gate can short-circuit to \"always pass\".\nfunction circuitPolicy(config: ResilientConfig) {\n if (config.circuit === undefined) return null;\n return {\n failureThreshold: config.circuit.threshold,\n cooldownMs: config.circuit.cooldownMs,\n halfOpenMaxProbes: config.circuit.halfOpenMaxProbes ?? 1,\n };\n}\n\n// Sub-id families. Each call's retry timer is a deadline keyed by `key`, so a\n// machine running many concurrent calls reconciles each independently.\nfunction retryTimerId(key: string): string {\n return `resilient:retry:${key}`;\n}\nfunction deadlineTimerId(key: string): string {\n return `resilient:deadline:${key}`;\n}\n\n// ===========================================================================\n// The knob factory.\n// ===========================================================================\n\n/**\n * Build a resilient-call knob from `config`. `rng` is injected for retry\n * jitter — pass a fixed `() => 0.5` in tests to pin backoff; defaults to\n * `Math.random` (read at the verb boundary, never inside a brick op). It is\n * curried onto the factory rather than threaded through every verb so callers\n * spell the determinism choice once.\n *\n * Returns the uniform L2 knob contract. `I` is the port input type, `R` the\n * port result type.\n *\n * @deprecated Use `withResilience` from `@demlik/tea/with-resilience` — not\n * drop-in; see the migration map at the top of this module.\n */\nexport function createResilientCall<I, R>(\n config: ResilientConfig,\n rng: () => number = Math.random,\n) {\n const cPolicy = circuitPolicy(config);\n // Re-affirm the `[0, 1)` contract at the seam where the injected generator\n // feeds the backoff math (brand from `../retry-backoff`). The factory's\n // public param stays a plain `() => number` so transitive callers are\n // unchanged; `asRng` is the single point it's branded for `nextDelayMs`.\n const rngBranded = asRng(rng);\n\n /** The starting slice. Bricks not in `config` still get a default value. */\n function init(): ResilientState<I, R> {\n return {\n circuit: initCircuit(),\n // The bucket carries a default value even without the rateLimit brick so\n // the slice shape is uniform, but the rate-limit GATE is skipped entirely\n // when the brick is absent (see `gate`) — the bucket is simply never\n // consulted. A `0/0` placeholder bucket would be touched by `tryConsume`;\n // a `1-cap` default is harmless because the gate never calls it.\n bucket: config.rateLimit\n ? initBucket(\n config.rateLimit.capacity,\n config.rateLimit.refillPerSec,\n 0,\n )\n : initBucket(1, 1, 0),\n retry: {},\n cache: initCache<R>(),\n calls: {},\n };\n }\n\n /** Read this key's retry state, defaulting to a fresh one. */\n function retryOf(s: ResilientState<I, R>, key: string): RetryState {\n return s.retry[key] ?? initRetry();\n }\n\n /** Compute the absolute deadline for a call starting at `at` (0 = no cap). */\n function deadlineAt(at: number): number {\n return config.deadline ? at + config.deadline.ms : 0;\n }\n\n /**\n * The shared decision: cache → rate-limit → circuit gate, then either emit\n * the effect or schedule a retry. `deadlineAtMs` is threaded so a re-attempt\n * (from `onTimer`) keeps the ORIGINAL overall deadline rather than restarting\n * it. PURE — `at` is the only clock; `rng` is the injected jitter source.\n *\n * Used by both `attempt` (fresh call) and `onTimer` (retry timer fired) so\n * the gate logic lives in exactly one place — the example's shared `attempt`\n * helper, lifted to a keyed knob.\n *\n * ## Why rate-limit is gated BEFORE the circuit probe\n *\n * The circuit-breaker's `canPass` is not a passive read: in `half_open` it\n * *consumes* a probe slot (advancing `probes`), and the consumed slot is only\n * released when a probe RESOLVES via `onSuccess` / `onFailure`. So a probe\n * slot may only be taken when this attempt is actually going to reach the\n * backend (i.e. emit the `resilient_run` Cmd). The rate-limit gate can reject\n * an attempt and return a backoff WITHOUT any Cmd — no backend call, so no\n * settle Msg ever comes back. If the circuit probe were consumed first, that\n * rate-limited attempt would burn the half-open probe budget on a call that\n * never happened, wedging the breaker `half_open` at its probe cap forever\n * (nothing moves it out of `half_open` but a resolved probe). Running the\n * rate-limit gate first means the circuit probe is consumed ONLY on the path\n * that emits the effect.\n */\n function gate(\n s: ResilientState<I, R>,\n key: string,\n input: I,\n at: number,\n deadlineAtMs: number,\n ): readonly [ResilientState<I, R>, readonly RunCmd<I>[]] {\n // 1) Serve from cache if fresh — no effect at all. Skipped when no cache brick.\n if (config.cache !== undefined) {\n const cached = cacheGet(s.cache, key, at);\n if (cached !== undefined) {\n return [setCall(s, key, { phase: \"succeeded\", result: cached }), []];\n }\n }\n\n // 2) Rate-limit gate FIRST — it may reject with no Cmd, so it must run\n // before any circuit probe is consumed (see header note). Skipped entirely\n // when no rateLimit brick — the bucket is never consulted, so the default\n // placeholder bucket stays untouched.\n let bucket = s.bucket;\n if (config.rateLimit !== undefined) {\n const [next, hasToken] = tryConsume(bucket, at);\n bucket = next;\n if (!hasToken) {\n // Rate-limited: treat as a transient failure and back off via retry.\n // The circuit is untouched — no probe is spent on a call that never ran.\n return backoff(\n { ...s, bucket },\n key,\n input,\n \"rate_limited\",\n at,\n deadlineAtMs,\n );\n }\n }\n\n // 3) Circuit-breaker gate. `null` policy → no breaker → always pass. Reached\n // only once a token is secured, so a half-open probe is consumed exclusively\n // on the path that emits the effect below.\n let circuit = s.circuit;\n if (cPolicy !== null) {\n const [next, ok] = canPass(circuit, cPolicy, at);\n circuit = next;\n if (!ok) {\n return [\n { ...setCall(s, key, { phase: \"circuit_open\" }), bucket, circuit },\n [],\n ];\n }\n }\n\n // 4) All gates passed — emit the effect as data.\n return [\n {\n ...setCall(s, key, { phase: \"running\", input, deadlineAtMs }),\n circuit,\n bucket,\n },\n [{ type: MsgType.ResilientRun, key, input }],\n ];\n }\n\n /**\n * Back off after a transient failure: record it, and if `retry` permits\n * another attempt, enter `waiting_retry` with the retry timer armed; else\n * settle `failed`. When no `retry` brick is configured the call fails on the\n * first failure (no backoff, no timer). PURE.\n *\n * `at` is both the backoff anchor AND the streak clock: it is passed to\n * `recordFailure` (which starts `firstFailureAtMs` on the streak's first\n * failure and preserves it on every later one) and to `shouldRetry`, so a\n * `DurationRetryPolicy` on `config.retry` is honoured with no extra wiring.\n * A success drops `retry[key]` entirely, which drops the origin with it —\n * only an unbroken run of failures grows toward a duration budget.\n */\n function backoff(\n s: ResilientState<I, R>,\n key: string,\n input: I,\n error: unknown,\n at: number,\n deadlineAtMs: number,\n ): readonly [ResilientState<I, R>, readonly RunCmd<I>[]] {\n if (config.retry === undefined) {\n return [setCall(s, key, { phase: \"failed\", error }), []];\n }\n const retry = recordFailure(retryOf(s, key), error, at);\n const withRetry = { ...s, retry: { ...s.retry, [key]: retry } };\n if (!shouldRetry(retry, config.retry, at)) {\n return [setCall(withRetry, key, { phase: \"failed\", error }), []];\n }\n const retryAtMs = at + nextDelayMs(retry, config.retry, rngBranded);\n return [\n setCall(withRetry, key, {\n phase: \"waiting_retry\",\n input,\n retryAtMs,\n deadlineAtMs,\n }),\n [],\n ];\n }\n\n // Set the phase for one key, leaving every other field untouched.\n function setCall(\n s: ResilientState<I, R>,\n key: string,\n phase: CallPhase<I, R>,\n ): ResilientState<I, R> {\n return { ...s, calls: { ...s.calls, [key]: phase } };\n }\n\n // === Verb: attempt =======================================================\n\n /**\n * Start (or restart) the call for `key` with `input` at time `at`. Runs the\n * cache → circuit → rate-limit gate and either emits the `resilient_run`\n * effect, schedules a retry, fast-fails (circuit open), or serves the cache.\n * PURE.\n */\n function attempt(\n s: ResilientState<I, R>,\n key: string,\n input: I,\n at: number,\n ): readonly [ResilientState<I, R>, readonly RunCmd<I>[]] {\n return gate(s, key, input, at, deadlineAt(at));\n }\n\n // === Verb: succeed =======================================================\n\n /**\n * Record a success for `key`: close the breaker, fill the cache (if a cache\n * brick exists), reset this key's retry count, and settle `succeeded`. PURE —\n * `msg.at` is the cache write clock.\n */\n function succeed(\n s: ResilientState<I, R>,\n key: string,\n msg: SucceedMsg<R>,\n ): readonly [ResilientState<I, R>, readonly RunCmd<I>[]] {\n const circuit =\n cPolicy !== null ? onSuccess(s.circuit, cPolicy) : s.circuit;\n const cache =\n config.cache !== undefined\n ? cacheSet(s.cache, key, msg.result, msg.at, config.cache.ttlMs)\n : s.cache;\n // Drop this key's retry counter — a success ends the retry run.\n const retry = without(s.retry, key);\n return [\n {\n ...setCall(s, key, { phase: \"succeeded\", result: msg.result }),\n circuit,\n cache,\n retry,\n },\n [],\n ];\n }\n\n // === Verb: fail ==========================================================\n\n /**\n * Record a failure for `key`: trip the breaker (it may open), then back off —\n * schedule a retry if `retry` permits, else settle `failed`. PURE — `msg.at`\n * stamps the breaker trip + the retry delay base. Re-issues from the call's\n * remembered `input` and preserves the original overall `deadlineAtMs`.\n */\n function fail(\n s: ResilientState<I, R>,\n key: string,\n msg: FailMsg,\n ): readonly [ResilientState<I, R>, readonly RunCmd<I>[]] {\n const circuit =\n cPolicy !== null ? onFailure(s.circuit, cPolicy, msg.at) : s.circuit;\n const withCircuit = { ...s, circuit };\n const call = s.calls[key];\n const input = call?.phase === \"running\" ? call.input : undefined;\n const deadlineAtMs =\n call?.phase === \"running\" ? call.deadlineAtMs : deadlineAt(msg.at);\n // No remembered input (e.g. a stray fail for a key not running) → just\n // settle failed after tripping the breaker; nothing to re-issue.\n if (input === undefined) {\n return [\n setCall(withCircuit, key, { phase: \"failed\", error: msg.error }),\n [],\n ];\n }\n return backoff(withCircuit, key, input, msg.error, msg.at, deadlineAtMs);\n }\n\n // === Verb: settleFailed ==================================================\n\n /**\n * Settle `key` to terminal `failed` with `error` WITHOUT touching the circuit\n * breaker or this key's retry counter. PURE.\n *\n * This is the verb for a failure that is NOT a downstream-health signal: the\n * call must end now, but the breaker must not trip and the backoff run must\n * not advance. `fail` is the opposite — it records the failure against the\n * breaker (it may open) and feeds the retry policy (it may back off). Use\n * `settleFailed` when the *reason* the call cannot proceed has nothing to do\n * with downstream health:\n *\n * - `authed-call` settles a 401 it cannot fix (e.g. the refresh budget is\n * spent): a bad token is the caller's problem, not the backend's —\n * tripping the breaker on it would punish a healthy target, and backing\n * off would just retry a request that will 401 again.\n * - `paginated-walk` settles a terminal walk error (e.g. a malformed page\n * the consumer rejects) that should stop the walk without poisoning the\n * shared breaker the next page fetch passes through.\n *\n * Because it leaves `circuit` and `retry[key]` untouched, the breaker keeps\n * its health view of the actual backend and a later `attempt` for this key\n * starts a fresh retry run. Both sibling knobs previously hand-rolled this by\n * splicing `calls` directly; this verb gives them one named, tested entry\n * point so the slice's settle invariants live in exactly one place.\n */\n function settleFailed(\n s: ResilientState<I, R>,\n key: string,\n error: unknown,\n ): readonly [ResilientState<I, R>, readonly RunCmd<I>[]] {\n return [setCall(s, key, { phase: \"failed\", error }), []];\n }\n\n // === Verb: onTimer =======================================================\n\n /**\n * A timer fired for some key. Two kinds, disambiguated by the Sub id:\n *\n * - retry timer → re-run the gate for that key's remembered input,\n * re-checking the circuit + bucket at THIS time.\n * - deadline timer → the overall budget elapsed → settle `failed` with a\n * deadline error, no matter the current phase.\n *\n * A timer for a key no longer `waiting_retry` (retry) / no longer in flight\n * (deadline) is a no-op — the reconcile pass races the result; the verb must\n * tolerate a stale fire. PURE.\n */\n function onTimer(\n s: ResilientState<I, R>,\n msg: ResilientTimerMsg,\n ): readonly [ResilientState<I, R>, readonly RunCmd<I>[]] {\n for (const [key, call] of Object.entries(s.calls)) {\n if (msg.id === retryTimerId(key)) {\n if (call.phase !== \"waiting_retry\") return [s, []];\n return gate(s, key, call.input, msg.atMs, call.deadlineAtMs);\n }\n if (msg.id === deadlineTimerId(key)) {\n // Deadline only fires for a still-active call. A settled call has no\n // armed deadline Sub, but tolerate a stale fire defensively.\n if (call.phase !== \"running\" && call.phase !== \"waiting_retry\")\n return [s, []];\n // Plain-data sentinel, NOT a `new Error(...)`: an Error object is not\n // JSON-serializable (it round-trips to `{}`) and captures a stack trace,\n // both of which break the slice's durability invariant once it is\n // persisted into a `Store<S>` and reloaded. A `{_tag, ...}` record keeps\n // the slice round-trip-equal under JSON.parse(JSON.stringify(...)).\n return [\n setCall(s, key, {\n phase: \"failed\",\n error: {\n _tag: \"deadline_exceeded\",\n id: msg.id,\n atMs: msg.atMs,\n } satisfies DeadlineExceededError,\n }),\n [],\n ];\n }\n }\n return [s, []];\n }\n\n // === Subs ================================================================\n\n /**\n * Pre-wired subscriptions: a retry timer for every `waiting_retry` call, and\n * (when the `deadline` brick is configured) an overall deadline timer for\n * every still-active call. Both are `DeadlineSub`s reconciled by id, so a\n * phase change cancels the matching timer automatically — no manual\n * `clearTimeout`. Wire `subscribe: { deadline: subscribeDeadline }`.\n */\n function subs(s: ResilientState<I, R>): readonly DeadlineSub[] {\n const out: DeadlineSub[] = [];\n for (const [key, call] of Object.entries(s.calls)) {\n if (call.phase === \"waiting_retry\") {\n out.push(deadlineSub(retryTimerId(key), call.retryAtMs));\n }\n if (\n config.deadline !== undefined &&\n (call.phase === \"running\" || call.phase === \"waiting_retry\") &&\n call.deadlineAtMs > 0\n ) {\n out.push(deadlineSub(deadlineTimerId(key), call.deadlineAtMs));\n }\n }\n return out;\n }\n\n // === Handlers ============================================================\n\n /**\n * Pre-wired interpret handler for `resilient_run`. Wraps the consumer's\n * `run` port via `tryInterpret` (Railway): success → `resilient_ok`, failure\n * → `resilient_err`, each stamped with `Date.now()` at the effect boundary —\n * the ONE permitted clock read (interpret is impure; the reducer never reads\n * the clock). Assign to the machine's `interpret`:\n *\n * interpret: rc.handlers({ run: (input, key) => ctx.callBackend(input) })\n */\n function handlers(ports: ResilientPorts<I, R>) {\n return {\n resilient_run: tryInterpret<\n RunCmd<I>,\n R,\n SucceedMsg<R> | FailMsg,\n // The work fn reads nothing from ctx — it forwards `cmd.input` to the\n // consumer-supplied `run` port. `NoCtx` (not `unknown`) marks this as a\n // DELIBERATE context-free seam, so callers see intent, not looseness.\n NoCtx\n >(\n (cmd) => ports.run(cmd.input, cmd.key),\n (result, cmd): SucceedMsg<R> => ({\n type: MsgType.ResilientOk,\n key: cmd.key,\n result,\n at: Date.now(),\n }),\n (error, cmd): FailMsg => ({\n type: MsgType.ResilientErr,\n key: cmd.key,\n error,\n at: Date.now(),\n }),\n ),\n };\n }\n\n return {\n init,\n attempt,\n succeed,\n fail,\n settleFailed,\n onTimer,\n subs,\n handlers,\n };\n}\n\n/**\n * Lift a knob result `[slice, cmds]` into a host `[State, cmds]` where the\n * slice lives at `state.resilience`. A convenience for the common single-slice\n * host; consumers with a differently-named field spread by hand. Pure — a thin\n * record rebuild, no clock / RNG.\n *\n * @deprecated Use `withResilience` from `@demlik/tea/with-resilience` — the\n * wrapper owns `model.$resilience`, so there is nothing to lift; see the\n * migration map at the top of this module.\n */\nexport function liftResilience<\n S extends { resilience: ResilientState<I, R> },\n I,\n R,\n C extends Cmd,\n>(\n state: S,\n [slice, cmds]: readonly [ResilientState<I, R>, readonly C[]],\n): readonly [S, readonly C[]] {\n return [{ ...state, resilience: slice }, cmds];\n}\n\n/**\n * Re-export the deadline Sub primitives so consumers (and tests) wire one\n * import: `subscribeDeadline` is the `subscribe` cell, `deadlineSub` builds the\n * Sub literal this knob's `subs` emits.\n */\nexport { subscribeDeadline, deadlineSub };\nexport type { DeadlineSub, DeadlineExceeded };\n"]}
@@ -1,4 +1,4 @@
1
- import { foldMsgs } from './chunk-DYRKDCJI.js';
1
+ import { foldMsgs } from './chunk-PBDYW2TU.js';
2
2
 
3
3
  // src/prediction/index.ts
4
4
  var tagSeq = (seq, value) => ({ seq, value });
@@ -29,5 +29,5 @@ function reconcile(machine, authoritativeState, lastAppliedSeq, pending) {
29
29
  }
30
30
 
31
31
  export { NO_ACK, ack, initAck, nextSeq, partitionByAck, reconcile, tagSeq };
32
- //# sourceMappingURL=chunk-G2U7DJHL.js.map
33
- //# sourceMappingURL=chunk-G2U7DJHL.js.map
32
+ //# sourceMappingURL=chunk-JFFVBEDI.js.map
33
+ //# sourceMappingURL=chunk-JFFVBEDI.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/prediction/index.ts"],"names":[],"mappings":";;;AAgDO,IAAM,SAAS,CAAI,GAAA,EAAU,KAAA,MAA4B,EAAE,KAAK,KAAA,EAAM;AActE,IAAM,GAAA,GAAM,CAAC,cAAA,MAA8B,EAAE,cAAA,EAAe;AAgB5D,IAAM,MAAA,GAAc;AAOpB,IAAM,OAAA,GAAU,MAAW,GAAA,CAAI,MAAM;AAmBrC,IAAM,cAAA,GAAiB,CAC5B,MAAA,EACA,QAAA,KACoB;AACpB,EAAA,MAAM,cAAA,GACJ,OAAO,QAAA,KAAa,QAAA,GAAW,WAAW,QAAA,CAAS,cAAA;AACrD,EAAA,MAAM,QAAwB,EAAC;AAC/B,EAAA,MAAM,UAA0B,EAAC;AACjC,EAAA,KAAA,MAAW,QAAQ,MAAA,EAAQ;AACzB,IAAA,IAAI,IAAA,CAAK,OAAO,cAAA,EAAgB;AAC9B,MAAA,KAAA,CAAM,KAAK,IAAI,CAAA;AAAA,IACjB,CAAA,MAAO;AACL,MAAA,OAAA,CAAQ,KAAK,IAAI,CAAA;AAAA,IACnB;AAAA,EACF;AACA,EAAA,OAAO,EAAE,OAAO,OAAA,EAAQ;AAC1B;AAQO,IAAM,OAAA,GAAU,CAAI,MAAA,KACzB,MAAA,CAAO,OAAO,CAAC,GAAA,EAAK,IAAA,KAAU,IAAA,CAAK,MAAM,GAAA,GAAM,IAAA,CAAK,GAAA,GAAM,GAAA,EAAM,EAAE,CAAA,GAAI;AAwBjE,SAAS,SAAA,CAOd,OAAA,EACA,kBAAA,EACA,cAAA,EACA,OAAA,EACG;AACH,EAAA,MAAM,EAAE,OAAA,EAAS,OAAA,EAAQ,GAAI,cAAA,CAAe,SAAS,cAAc,CAAA;AACnE,EAAA,OAAO,QAAA;AAAA,IACL,OAAA;AAAA,IACA,kBAAA;AAAA,IACA,OAAA,CAAQ,GAAA,CAAI,CAAC,MAAA,KAAW,OAAO,KAAK;AAAA,GACtC;AACF","file":"chunk-G2U7DJHL.js","sourcesContent":["/**\n * @demlik/tea/prediction — the client-prediction ack primitive (epic #186, facet 2).\n *\n * The contract the Gambetta/Valve authoritative-server netcode loop needs:\n * **command-seq-in → last-applied-seq-out.** A client tags each predicted\n * command/`Msg` with a monotonic `seq`; the authoritative side replies with the\n * `lastAppliedSeq` it has processed. `partitionByAck` then splits the client's\n * pending buffer into the inputs the server has ACKED (`seq <= lastAppliedSeq`)\n * and those still PENDING (`seq > lastAppliedSeq`) — the un-acked tail the\n * reconciliation pass (#214) replays over the authoritative snapshot via\n * `foldMsgs` (#211, `@demlik/tea/pure`).\n *\n * Deliberately **Model-shape-agnostic** (ADR 0006): the ack is a standalone\n * value, NOT a field this module requires on the consumer's `State`. It\n * generalizes the `lastAckedSeq`-on-the-Model + `seq`-threaded-through-the-Msg\n * pattern vortex hand-rolled (epic #186). It is a LEAF module — pure and\n * runtime-free, importing nothing from the tea core or host, so it can never\n * drag `run`/the host into a client bundle.\n *\n * This is the read/ack side of the primitive; `tagSeq`/`nextSeq` are the write\n * side, letting a client mint monotonic seqs from its pending buffer alone —\n * again with no counter field on the Model.\n *\n * `reconcile` (the read side's payoff, #214) composes this ack partition with\n * the pure fold seam `foldMsgs` (#211) to perform the whole Gambetta/Valve\n * reconciliation step in one call. The `foldMsgs` import is the ONLY dependency\n * this leaf takes, and it reaches into the pure-core leaf (`../pure/core`) — NOT\n * the runtime root — so the module stays runtime-free and the `@demlik/tea/pure`\n * import-graph guard (ADR 0006, #213) still holds.\n */\n\nimport type { Cmd, Machine, Sub } from \"../pure/core\";\nimport { foldMsgs } from \"../pure/core\";\n\n/**\n * A monotonic, non-negative sequence number tagging one client-predicted\n * command. Monotonicity is the client's contract (mint successive seqs with\n * `nextSeq`); this module only ever *compares* seqs.\n */\nexport type Seq = number;\n\n/** A command (or `Msg`) tagged with the `seq` the client assigned it. */\nexport interface SeqTagged<T> {\n readonly seq: Seq;\n readonly value: T;\n}\n\n/** Tag a command/`Msg` with its sequence number. */\nexport const tagSeq = <T>(seq: Seq, value: T): SeqTagged<T> => ({ seq, value });\n\n/**\n * The authoritative side's acknowledgement: the highest `seq` it has applied.\n * Everything with `seq <= lastAppliedSeq` is settled; everything above is still\n * in flight. This *is* the whole \"ack from the authoritative side\" — a value\n * the consumer carries however it likes (a wire field, a message payload),\n * never a shape this module imposes on the Model.\n */\nexport interface Ack {\n readonly lastAppliedSeq: Seq;\n}\n\n/** Construct an `Ack` for a last-applied sequence number. */\nexport const ack = (lastAppliedSeq: Seq): Ack => ({ lastAppliedSeq });\n\n/**\n * The \"nothing applied yet\" cursor — the ack value for a server that has applied\n * no client input at all. Because `nextSeq` mints **0-based** seqs and\n * `partitionByAck` acks **inclusively** (`seq <= lastAppliedSeq`), the pre-first\n * cursor must sit strictly *below* seq 0: `lastAppliedSeq = 0` would falsely ack\n * the seq-0 input before the server ever saw it. `-1` is \"before any seq\", and\n * `partitionByAck(buffer, NO_ACK)` therefore leaves the whole buffer PENDING.\n *\n * This names the boundary value on the seam so no consumer has to re-discover it\n * as a magic number: it is the primitive's own design (0-based mint + inclusive\n * partition) that creates the trap, so it is the primitive's job to name the\n * escape. Pair with {@link initAck} when an {@link Ack} is wanted instead of the\n * bare `Seq`.\n */\nexport const NO_ACK: Seq = -1;\n\n/**\n * The `Ack` for a server that has applied nothing yet — `ack(NO_ACK)`. The `Ack`\n * counterpart to the {@link NO_ACK} `Seq` sentinel, for a consumer that carries\n * an `Ack` rather than a bare `lastAppliedSeq`.\n */\nexport const initAck = (): Ack => ack(NO_ACK);\n\n/** The result of splitting a seq-tagged buffer against an ack. */\nexport interface AckPartition<T> {\n /** Inputs the authoritative side has applied (`seq <= lastAppliedSeq`). */\n readonly acked: readonly SeqTagged<T>[];\n /** Inputs still in flight / un-acked (`seq > lastAppliedSeq`) — the replay tail. */\n readonly pending: readonly SeqTagged<T>[];\n}\n\n/**\n * Partition a buffer of seq-tagged commands into ACKED and PENDING against the\n * authoritative `lastAppliedSeq`. Pure and total: every item lands in exactly\n * one bucket, input order is preserved within each bucket, and the boundary\n * `seq == lastAppliedSeq` is ACKED (inclusive `<=`). Routing is by `seq` value,\n * not buffer position, so an out-of-order or duplicate-seq buffer partitions\n * correctly. Accepts either an {@link Ack} or a bare `lastAppliedSeq` number, so\n * a caller passes whichever it holds.\n */\nexport const partitionByAck = <T>(\n buffer: readonly SeqTagged<T>[],\n ackOrSeq: Ack | Seq,\n): AckPartition<T> => {\n const lastAppliedSeq =\n typeof ackOrSeq === \"number\" ? ackOrSeq : ackOrSeq.lastAppliedSeq;\n const acked: SeqTagged<T>[] = [];\n const pending: SeqTagged<T>[] = [];\n for (const item of buffer) {\n if (item.seq <= lastAppliedSeq) {\n acked.push(item);\n } else {\n pending.push(item);\n }\n }\n return { acked, pending };\n};\n\n/**\n * The next sequence number to assign: one past the highest `seq` in the buffer,\n * or `0` for an empty buffer. Lets a client mint monotonic seqs from its pending\n * buffer alone — no counter field on the Model — and is tolerant of an unordered\n * buffer (it takes the max, not the last element).\n */\nexport const nextSeq = <T>(buffer: readonly SeqTagged<T>[]): Seq =>\n buffer.reduce((max, item) => (item.seq > max ? item.seq : max), -1) + 1;\n\n/**\n * The client prediction/reconciliation helper — the Gambetta/Valve\n * authoritative-server loop's reconcile step, generalized (#214, epic #186).\n *\n * Given the latest `authoritativeState` from the server, the server's\n * `lastAppliedSeq` ack, and the client's `pending` buffer of seq-tagged inputs,\n * it returns the corrected predicted state: drop the inputs the server has\n * already applied (`seq <= lastAppliedSeq`) and replay ONLY the un-acked tail\n * (`seq > lastAppliedSeq`) over the authoritative snapshot.\n *\n * It is **composed, not re-derived** (ADR 0006): the drop is {@link partitionByAck}\n * (the ack primitive, #212) and the replay is `foldMsgs` (the pure fold seam,\n * #211) — there is no second copy of the partition logic and no second copy of\n * the fold. `lastAppliedSeq` accepts either an {@link Ack} or a bare `Seq`, so a\n * caller passes whichever it holds, matching `partitionByAck`.\n *\n * Pure and host-agnostic: it takes the `machine` (which `foldMsgs` needs to\n * dispatch `update`) but touches no `Store`, `interpret`, or subscription, and\n * imports nothing from the runtime — so it ships on `@demlik/tea/prediction`\n * and the client-safe `@demlik/tea/pure` umbrella without dragging `run` into a\n * client bundle.\n */\nexport function reconcile<\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 authoritativeState: S,\n lastAppliedSeq: Ack | Seq,\n pending: readonly SeqTagged<M>[],\n): S {\n const { pending: unacked } = partitionByAck(pending, lastAppliedSeq);\n return foldMsgs(\n machine,\n authoritativeState,\n unacked.map((tagged) => tagged.value),\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/prediction/index.ts"],"names":[],"mappings":";;;AAgDO,IAAM,SAAS,CAAI,GAAA,EAAU,KAAA,MAA4B,EAAE,KAAK,KAAA,EAAM;AActE,IAAM,GAAA,GAAM,CAAC,cAAA,MAA8B,EAAE,cAAA,EAAe;AAgB5D,IAAM,MAAA,GAAc;AAOpB,IAAM,OAAA,GAAU,MAAW,GAAA,CAAI,MAAM;AAmBrC,IAAM,cAAA,GAAiB,CAC5B,MAAA,EACA,QAAA,KACoB;AACpB,EAAA,MAAM,cAAA,GACJ,OAAO,QAAA,KAAa,QAAA,GAAW,WAAW,QAAA,CAAS,cAAA;AACrD,EAAA,MAAM,QAAwB,EAAC;AAC/B,EAAA,MAAM,UAA0B,EAAC;AACjC,EAAA,KAAA,MAAW,QAAQ,MAAA,EAAQ;AACzB,IAAA,IAAI,IAAA,CAAK,OAAO,cAAA,EAAgB;AAC9B,MAAA,KAAA,CAAM,KAAK,IAAI,CAAA;AAAA,IACjB,CAAA,MAAO;AACL,MAAA,OAAA,CAAQ,KAAK,IAAI,CAAA;AAAA,IACnB;AAAA,EACF;AACA,EAAA,OAAO,EAAE,OAAO,OAAA,EAAQ;AAC1B;AAQO,IAAM,OAAA,GAAU,CAAI,MAAA,KACzB,MAAA,CAAO,OAAO,CAAC,GAAA,EAAK,IAAA,KAAU,IAAA,CAAK,MAAM,GAAA,GAAM,IAAA,CAAK,GAAA,GAAM,GAAA,EAAM,EAAE,CAAA,GAAI;AAwBjE,SAAS,SAAA,CAOd,OAAA,EACA,kBAAA,EACA,cAAA,EACA,OAAA,EACG;AACH,EAAA,MAAM,EAAE,OAAA,EAAS,OAAA,EAAQ,GAAI,cAAA,CAAe,SAAS,cAAc,CAAA;AACnE,EAAA,OAAO,QAAA;AAAA,IACL,OAAA;AAAA,IACA,kBAAA;AAAA,IACA,OAAA,CAAQ,GAAA,CAAI,CAAC,MAAA,KAAW,OAAO,KAAK;AAAA,GACtC;AACF","file":"chunk-JFFVBEDI.js","sourcesContent":["/**\n * @demlik/tea/prediction — the client-prediction ack primitive (epic #186, facet 2).\n *\n * The contract the Gambetta/Valve authoritative-server netcode loop needs:\n * **command-seq-in → last-applied-seq-out.** A client tags each predicted\n * command/`Msg` with a monotonic `seq`; the authoritative side replies with the\n * `lastAppliedSeq` it has processed. `partitionByAck` then splits the client's\n * pending buffer into the inputs the server has ACKED (`seq <= lastAppliedSeq`)\n * and those still PENDING (`seq > lastAppliedSeq`) — the un-acked tail the\n * reconciliation pass (#214) replays over the authoritative snapshot via\n * `foldMsgs` (#211, `@demlik/tea/pure`).\n *\n * Deliberately **Model-shape-agnostic** (ADR 0006): the ack is a standalone\n * value, NOT a field this module requires on the consumer's `State`. It\n * generalizes the `lastAckedSeq`-on-the-Model + `seq`-threaded-through-the-Msg\n * pattern vortex hand-rolled (epic #186). It is a LEAF module — pure and\n * runtime-free, importing nothing from the tea core or host, so it can never\n * drag `run`/the host into a client bundle.\n *\n * This is the read/ack side of the primitive; `tagSeq`/`nextSeq` are the write\n * side, letting a client mint monotonic seqs from its pending buffer alone —\n * again with no counter field on the Model.\n *\n * `reconcile` (the read side's payoff, #214) composes this ack partition with\n * the pure fold seam `foldMsgs` (#211) to perform the whole Gambetta/Valve\n * reconciliation step in one call. The `foldMsgs` import is the ONLY dependency\n * this leaf takes, and it reaches into the pure-core leaf (`../pure/core`) — NOT\n * the runtime root — so the module stays runtime-free and the `@demlik/tea/pure`\n * import-graph guard (ADR 0006, #213) still holds.\n */\n\nimport type { Cmd, Machine, Sub } from \"../pure/core\";\nimport { foldMsgs } from \"../pure/core\";\n\n/**\n * A monotonic, non-negative sequence number tagging one client-predicted\n * command. Monotonicity is the client's contract (mint successive seqs with\n * `nextSeq`); this module only ever *compares* seqs.\n */\nexport type Seq = number;\n\n/** A command (or `Msg`) tagged with the `seq` the client assigned it. */\nexport interface SeqTagged<T> {\n readonly seq: Seq;\n readonly value: T;\n}\n\n/** Tag a command/`Msg` with its sequence number. */\nexport const tagSeq = <T>(seq: Seq, value: T): SeqTagged<T> => ({ seq, value });\n\n/**\n * The authoritative side's acknowledgement: the highest `seq` it has applied.\n * Everything with `seq <= lastAppliedSeq` is settled; everything above is still\n * in flight. This *is* the whole \"ack from the authoritative side\" — a value\n * the consumer carries however it likes (a wire field, a message payload),\n * never a shape this module imposes on the Model.\n */\nexport interface Ack {\n readonly lastAppliedSeq: Seq;\n}\n\n/** Construct an `Ack` for a last-applied sequence number. */\nexport const ack = (lastAppliedSeq: Seq): Ack => ({ lastAppliedSeq });\n\n/**\n * The \"nothing applied yet\" cursor — the ack value for a server that has applied\n * no client input at all. Because `nextSeq` mints **0-based** seqs and\n * `partitionByAck` acks **inclusively** (`seq <= lastAppliedSeq`), the pre-first\n * cursor must sit strictly *below* seq 0: `lastAppliedSeq = 0` would falsely ack\n * the seq-0 input before the server ever saw it. `-1` is \"before any seq\", and\n * `partitionByAck(buffer, NO_ACK)` therefore leaves the whole buffer PENDING.\n *\n * This names the boundary value on the seam so no consumer has to re-discover it\n * as a magic number: it is the primitive's own design (0-based mint + inclusive\n * partition) that creates the trap, so it is the primitive's job to name the\n * escape. Pair with {@link initAck} when an {@link Ack} is wanted instead of the\n * bare `Seq`.\n */\nexport const NO_ACK: Seq = -1;\n\n/**\n * The `Ack` for a server that has applied nothing yet — `ack(NO_ACK)`. The `Ack`\n * counterpart to the {@link NO_ACK} `Seq` sentinel, for a consumer that carries\n * an `Ack` rather than a bare `lastAppliedSeq`.\n */\nexport const initAck = (): Ack => ack(NO_ACK);\n\n/** The result of splitting a seq-tagged buffer against an ack. */\nexport interface AckPartition<T> {\n /** Inputs the authoritative side has applied (`seq <= lastAppliedSeq`). */\n readonly acked: readonly SeqTagged<T>[];\n /** Inputs still in flight / un-acked (`seq > lastAppliedSeq`) — the replay tail. */\n readonly pending: readonly SeqTagged<T>[];\n}\n\n/**\n * Partition a buffer of seq-tagged commands into ACKED and PENDING against the\n * authoritative `lastAppliedSeq`. Pure and total: every item lands in exactly\n * one bucket, input order is preserved within each bucket, and the boundary\n * `seq == lastAppliedSeq` is ACKED (inclusive `<=`). Routing is by `seq` value,\n * not buffer position, so an out-of-order or duplicate-seq buffer partitions\n * correctly. Accepts either an {@link Ack} or a bare `lastAppliedSeq` number, so\n * a caller passes whichever it holds.\n */\nexport const partitionByAck = <T>(\n buffer: readonly SeqTagged<T>[],\n ackOrSeq: Ack | Seq,\n): AckPartition<T> => {\n const lastAppliedSeq =\n typeof ackOrSeq === \"number\" ? ackOrSeq : ackOrSeq.lastAppliedSeq;\n const acked: SeqTagged<T>[] = [];\n const pending: SeqTagged<T>[] = [];\n for (const item of buffer) {\n if (item.seq <= lastAppliedSeq) {\n acked.push(item);\n } else {\n pending.push(item);\n }\n }\n return { acked, pending };\n};\n\n/**\n * The next sequence number to assign: one past the highest `seq` in the buffer,\n * or `0` for an empty buffer. Lets a client mint monotonic seqs from its pending\n * buffer alone — no counter field on the Model — and is tolerant of an unordered\n * buffer (it takes the max, not the last element).\n */\nexport const nextSeq = <T>(buffer: readonly SeqTagged<T>[]): Seq =>\n buffer.reduce((max, item) => (item.seq > max ? item.seq : max), -1) + 1;\n\n/**\n * The client prediction/reconciliation helper — the Gambetta/Valve\n * authoritative-server loop's reconcile step, generalized (#214, epic #186).\n *\n * Given the latest `authoritativeState` from the server, the server's\n * `lastAppliedSeq` ack, and the client's `pending` buffer of seq-tagged inputs,\n * it returns the corrected predicted state: drop the inputs the server has\n * already applied (`seq <= lastAppliedSeq`) and replay ONLY the un-acked tail\n * (`seq > lastAppliedSeq`) over the authoritative snapshot.\n *\n * It is **composed, not re-derived** (ADR 0006): the drop is {@link partitionByAck}\n * (the ack primitive, #212) and the replay is `foldMsgs` (the pure fold seam,\n * #211) — there is no second copy of the partition logic and no second copy of\n * the fold. `lastAppliedSeq` accepts either an {@link Ack} or a bare `Seq`, so a\n * caller passes whichever it holds, matching `partitionByAck`.\n *\n * Pure and host-agnostic: it takes the `machine` (which `foldMsgs` needs to\n * dispatch `update`) but touches no `Store`, `interpret`, or subscription, and\n * imports nothing from the runtime — so it ships on `@demlik/tea/prediction`\n * and the client-safe `@demlik/tea/pure` umbrella without dragging `run` into a\n * client bundle.\n */\nexport function reconcile<\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 authoritativeState: S,\n lastAppliedSeq: Ack | Seq,\n pending: readonly SeqTagged<M>[],\n): S {\n const { pending: unacked } = partitionByAck(pending, lastAppliedSeq);\n return foldMsgs(\n machine,\n authoritativeState,\n unacked.map((tagged) => tagged.value),\n );\n}\n"]}