@demlik/tea 0.1.0 → 0.1.1

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 (238) hide show
  1. package/LICENSE +21 -0
  2. package/dist/agent/index.d.ts +907 -0
  3. package/dist/agent/index.js +20 -0
  4. package/dist/agent/index.js.map +1 -0
  5. package/dist/authed-call/index.d.ts +119 -0
  6. package/dist/authed-call/index.js +148 -0
  7. package/dist/authed-call/index.js.map +1 -0
  8. package/dist/await-terminal/index.d.ts +99 -0
  9. package/dist/await-terminal/index.js +75 -0
  10. package/dist/await-terminal/index.js.map +1 -0
  11. package/dist/batch-window/index.d.ts +259 -0
  12. package/dist/batch-window/index.js +43 -0
  13. package/dist/batch-window/index.js.map +1 -0
  14. package/dist/cache/index.d.ts +2 -2
  15. package/dist/cache/index.js +2 -50
  16. package/dist/cache/index.js.map +1 -1
  17. package/dist/{chunk-CMG2KMWX.js → chunk-2WZHS4P2.js} +220 -177
  18. package/dist/chunk-2WZHS4P2.js.map +1 -0
  19. package/dist/chunk-2YOE5PIS.js +45 -0
  20. package/dist/chunk-2YOE5PIS.js.map +1 -0
  21. package/dist/chunk-3545BOYL.js +65 -0
  22. package/dist/chunk-3545BOYL.js.map +1 -0
  23. package/dist/chunk-5VNWZADP.js +49 -0
  24. package/dist/chunk-5VNWZADP.js.map +1 -0
  25. package/dist/{chunk-ZRUST6BK.js → chunk-7FYQGDMF.js} +2 -2
  26. package/dist/chunk-7FYQGDMF.js.map +1 -0
  27. package/dist/chunk-7VEG4KHB.js +202 -0
  28. package/dist/chunk-7VEG4KHB.js.map +1 -0
  29. package/dist/{chunk-PYBQREFU.js → chunk-A56GNPI7.js} +20 -6
  30. package/dist/chunk-A56GNPI7.js.map +1 -0
  31. package/dist/chunk-BNJYE4AU.js +157 -0
  32. package/dist/chunk-BNJYE4AU.js.map +1 -0
  33. package/dist/chunk-BYQUGDD3.js +16 -0
  34. package/dist/chunk-BYQUGDD3.js.map +1 -0
  35. package/dist/chunk-D5EQDLDV.js +186 -0
  36. package/dist/chunk-D5EQDLDV.js.map +1 -0
  37. package/dist/chunk-EDGDR5XX.js +60 -0
  38. package/dist/chunk-EDGDR5XX.js.map +1 -0
  39. package/dist/chunk-EIZJTYKR.js +93 -0
  40. package/dist/chunk-EIZJTYKR.js.map +1 -0
  41. package/dist/chunk-EQRUAP2A.js +56 -0
  42. package/dist/chunk-EQRUAP2A.js.map +1 -0
  43. package/dist/chunk-FH5RUIIL.js +61 -0
  44. package/dist/chunk-FH5RUIIL.js.map +1 -0
  45. package/dist/chunk-HJO6VNQI.js +15 -0
  46. package/dist/chunk-HJO6VNQI.js.map +1 -0
  47. package/dist/chunk-IFBQJYGI.js +132 -0
  48. package/dist/chunk-IFBQJYGI.js.map +1 -0
  49. package/dist/chunk-IN33Q4VJ.js +52 -0
  50. package/dist/chunk-IN33Q4VJ.js.map +1 -0
  51. package/dist/chunk-JELSZN25.js +47 -0
  52. package/dist/chunk-JELSZN25.js.map +1 -0
  53. package/dist/chunk-K575PT4S.js +58 -0
  54. package/dist/chunk-K575PT4S.js.map +1 -0
  55. package/dist/chunk-LEC4U6CX.js +91 -0
  56. package/dist/chunk-LEC4U6CX.js.map +1 -0
  57. package/dist/{chunk-Y5JM3UUH.js → chunk-NSDFA6M6.js} +75 -3
  58. package/dist/chunk-NSDFA6M6.js.map +1 -0
  59. package/dist/chunk-QGZW4W3E.js +20 -0
  60. package/dist/chunk-QGZW4W3E.js.map +1 -0
  61. package/dist/{chunk-PYOCUBQJ.js → chunk-RGTPPF4U.js} +22 -15
  62. package/dist/chunk-RGTPPF4U.js.map +1 -0
  63. package/dist/chunk-RVJFGRGK.js +222 -0
  64. package/dist/chunk-RVJFGRGK.js.map +1 -0
  65. package/dist/chunk-RVU3RBE6.js +31 -0
  66. package/dist/chunk-RVU3RBE6.js.map +1 -0
  67. package/dist/chunk-SC3FAFZN.js +53 -0
  68. package/dist/chunk-SC3FAFZN.js.map +1 -0
  69. package/dist/chunk-SINXE47Y.js +55 -0
  70. package/dist/chunk-SINXE47Y.js.map +1 -0
  71. package/dist/chunk-TAO4YOLO.js +87 -0
  72. package/dist/chunk-TAO4YOLO.js.map +1 -0
  73. package/dist/chunk-TC4HWABA.js +506 -0
  74. package/dist/chunk-TC4HWABA.js.map +1 -0
  75. package/dist/chunk-TGG26XMK.js +52 -0
  76. package/dist/chunk-TGG26XMK.js.map +1 -0
  77. package/dist/chunk-UV6UWGEO.js +147 -0
  78. package/dist/chunk-UV6UWGEO.js.map +1 -0
  79. package/dist/chunk-UYWUVEP6.js +158 -0
  80. package/dist/chunk-UYWUVEP6.js.map +1 -0
  81. package/dist/chunk-WUV3PSCZ.js +54 -0
  82. package/dist/chunk-WUV3PSCZ.js.map +1 -0
  83. package/dist/chunk-XRXKH7KA.js +268 -0
  84. package/dist/chunk-XRXKH7KA.js.map +1 -0
  85. package/dist/circuit-breaker/index.js +1 -51
  86. package/dist/circuit-breaker/index.js.map +1 -1
  87. package/dist/core-CymXZCeY.d.ts +178 -0
  88. package/dist/deadline/index.d.ts +24 -5
  89. package/dist/deadline/index.js +4 -18
  90. package/dist/deadline/index.js.map +1 -1
  91. package/dist/debounce/index.js +1 -54
  92. package/dist/debounce/index.js.map +1 -1
  93. package/dist/devtools/index.d.ts +1 -1
  94. package/dist/devtools/index.js +18 -6
  95. package/dist/devtools/index.js.map +1 -1
  96. package/dist/do/index.d.ts +1732 -92
  97. package/dist/do/index.js +677 -31
  98. package/dist/do/index.js.map +1 -1
  99. package/dist/durable-effects-2MDxDs45.d.ts +180 -0
  100. package/dist/extension/index.d.ts +31 -13
  101. package/dist/extension/index.js +5 -3
  102. package/dist/extension/index.js.map +1 -1
  103. package/dist/extension/react.js +8 -3
  104. package/dist/extension/react.js.map +1 -1
  105. package/dist/extension/subs/index.d.ts +2 -2
  106. package/dist/extension/subs/index.js.map +1 -1
  107. package/dist/extension/test-utils.js +11 -3
  108. package/dist/extension/test-utils.js.map +1 -1
  109. package/dist/fan-out/index.d.ts +239 -0
  110. package/dist/fan-out/index.js +5 -0
  111. package/dist/fan-out/index.js.map +1 -0
  112. package/dist/idempotency/adapter.d.ts +62 -0
  113. package/dist/idempotency/adapter.js +4 -0
  114. package/dist/idempotency/adapter.js.map +1 -0
  115. package/dist/idempotency/index.js +1 -56
  116. package/dist/idempotency/index.js.map +1 -1
  117. package/dist/idempotent-intake/index.d.ts +186 -0
  118. package/dist/idempotent-intake/index.js +7 -0
  119. package/dist/idempotent-intake/index.js.map +1 -0
  120. package/dist/index.d.ts +341 -191
  121. package/dist/index.js +2 -1
  122. package/dist/llm-call/index.d.ts +309 -0
  123. package/dist/llm-call/index.js +14 -0
  124. package/dist/llm-call/index.js.map +1 -0
  125. package/dist/machine-viz/index.d.ts +1 -1
  126. package/dist/machine-viz/index.js +1 -1
  127. package/dist/machine-viz/index.js.map +1 -1
  128. package/dist/mem/index.d.ts +1 -0
  129. package/dist/monitored-run/index.d.ts +253 -0
  130. package/dist/monitored-run/index.js +10 -0
  131. package/dist/monitored-run/index.js.map +1 -0
  132. package/dist/node/index.d.ts +5 -5
  133. package/dist/node/index.js.map +1 -1
  134. package/dist/paginated-walk/index.d.ts +234 -0
  135. package/dist/paginated-walk/index.js +15 -0
  136. package/dist/paginated-walk/index.js.map +1 -0
  137. package/dist/paginator/index.d.ts +271 -0
  138. package/dist/paginator/index.js +3 -0
  139. package/dist/paginator/index.js.map +1 -0
  140. package/dist/parity/index.d.ts +86 -0
  141. package/dist/parity/index.js +77 -0
  142. package/dist/parity/index.js.map +1 -0
  143. package/dist/pbt/arbitraries/index.d.ts +1 -1
  144. package/dist/pbt/arbitraries/index.js +1 -1
  145. package/dist/pbt/index.d.ts +1 -1
  146. package/dist/pbt/index.js +5 -4
  147. package/dist/pbt/index.js.map +1 -1
  148. package/dist/pbt/runners/index.d.ts +1 -1
  149. package/dist/pbt/runners/index.js +3 -2
  150. package/dist/poller/index.d.ts +288 -0
  151. package/dist/poller/index.js +79 -0
  152. package/dist/poller/index.js.map +1 -0
  153. package/dist/prediction/index.d.ts +109 -0
  154. package/dist/prediction/index.js +4 -0
  155. package/dist/prediction/index.js.map +1 -0
  156. package/dist/protocol-B5YmJr0d.d.ts +45 -0
  157. package/dist/pure/index.d.ts +2 -0
  158. package/dist/pure/index.js +4 -0
  159. package/dist/pure/index.js.map +1 -0
  160. package/dist/raft/demo.d.ts +105 -0
  161. package/dist/raft/demo.js +172 -0
  162. package/dist/raft/demo.js.map +1 -0
  163. package/dist/raft/index.d.ts +548 -0
  164. package/dist/raft/index.js +7 -0
  165. package/dist/raft/index.js.map +1 -0
  166. package/dist/raft/sim.d.ts +222 -0
  167. package/dist/raft/sim.js +8 -0
  168. package/dist/raft/sim.js.map +1 -0
  169. package/dist/raft/timeline.d.ts +46 -0
  170. package/dist/raft/timeline.js +62 -0
  171. package/dist/raft/timeline.js.map +1 -0
  172. package/dist/rate-limit/index.js +1 -43
  173. package/dist/rate-limit/index.js.map +1 -1
  174. package/dist/react/index.d.ts +2 -1
  175. package/dist/react/index.js +39 -20
  176. package/dist/react/index.js.map +1 -1
  177. package/dist/reconciler/index.d.ts +192 -0
  178. package/dist/reconciler/index.js +166 -0
  179. package/dist/reconciler/index.js.map +1 -0
  180. package/dist/recorder/index.d.ts +7 -3
  181. package/dist/recorder/index.js +1 -155
  182. package/dist/recorder/index.js.map +1 -1
  183. package/dist/resilient-call/index.d.ts +251 -0
  184. package/dist/resilient-call/index.js +13 -0
  185. package/dist/resilient-call/index.js.map +1 -0
  186. package/dist/retry-backoff/index.d.ts +42 -5
  187. package/dist/retry-backoff/index.js +1 -35
  188. package/dist/retry-backoff/index.js.map +1 -1
  189. package/dist/saga/index.d.ts +225 -0
  190. package/dist/saga/index.js +150 -0
  191. package/dist/saga/index.js.map +1 -0
  192. package/dist/snapshot/index.d.ts +334 -0
  193. package/dist/snapshot/index.js +5 -0
  194. package/dist/snapshot/index.js.map +1 -0
  195. package/dist/subs/index.d.ts +48 -10
  196. package/dist/subs/index.js +1 -1
  197. package/dist/testing/index.d.ts +107 -2
  198. package/dist/testing/index.js +204 -6
  199. package/dist/testing/index.js.map +1 -1
  200. package/dist/throttle/index.js +1 -52
  201. package/dist/throttle/index.js.map +1 -1
  202. package/dist/throttled-input/index.d.ts +372 -0
  203. package/dist/throttled-input/index.js +88 -0
  204. package/dist/throttled-input/index.js.map +1 -0
  205. package/dist/token-refresh/index.d.ts +215 -0
  206. package/dist/token-refresh/index.js +6 -0
  207. package/dist/token-refresh/index.js.map +1 -0
  208. package/dist/trace-replay/index.d.ts +11 -2
  209. package/dist/trace-replay/index.js +3 -55
  210. package/dist/trace-replay/index.js.map +1 -1
  211. package/dist/{types-CwBC5wMn.d.ts → types-N9LZbdnr.d.ts} +1 -1
  212. package/dist/with-deadline/index.d.ts +196 -0
  213. package/dist/with-deadline/index.js +130 -0
  214. package/dist/with-deadline/index.js.map +1 -0
  215. package/dist/with-resilience/index.d.ts +205 -0
  216. package/dist/with-resilience/index.js +274 -0
  217. package/dist/with-resilience/index.js.map +1 -0
  218. package/dist/with-telemetry/index.d.ts +149 -0
  219. package/dist/with-telemetry/index.js +95 -0
  220. package/dist/with-telemetry/index.js.map +1 -0
  221. package/dist/work-queue/adapter.d.ts +82 -0
  222. package/dist/work-queue/adapter.js +4 -0
  223. package/dist/work-queue/adapter.js.map +1 -0
  224. package/dist/work-queue/index.d.ts +25 -11
  225. package/dist/work-queue/index.js +1 -46
  226. package/dist/work-queue/index.js.map +1 -1
  227. package/dist/work-queue/ops.d.ts +69 -0
  228. package/dist/work-queue/ops.js +3 -0
  229. package/dist/work-queue/ops.js.map +1 -0
  230. package/dist/workflow/index.d.ts +444 -0
  231. package/dist/workflow/index.js +225 -0
  232. package/dist/workflow/index.js.map +1 -0
  233. package/package.json +162 -25
  234. package/dist/chunk-CMG2KMWX.js.map +0 -1
  235. package/dist/chunk-PYBQREFU.js.map +0 -1
  236. package/dist/chunk-PYOCUBQJ.js.map +0 -1
  237. package/dist/chunk-Y5JM3UUH.js.map +0 -1
  238. package/dist/chunk-ZRUST6BK.js.map +0 -1
@@ -0,0 +1,334 @@
1
+ import { C as Cmd, I as Interpret } from '../core-CymXZCeY.js';
2
+
3
+ /**
4
+ * @demlik/tea/snapshot — periodic state checkpoint to a host store (R2/KV-shaped).
5
+ *
6
+ * The problem it solves: a long-running machine (a multi-stage run, a paginated
7
+ * crawl, a saga) lives in TEA's in-memory `State` and is persisted as ONE blob
8
+ * by the substrate's `Store<S>` on every transition. That blob is fine for
9
+ * crash recovery of the *runtime* — but it is NOT a coarse, named checkpoint a
10
+ * human (or a separate recovery job) can point a restart at. `snapshot` adds
11
+ * the "every N units of progress, write a durable checkpoint to R2/KV"
12
+ * discipline on top, so a crashed long run restarts from the last checkpoint
13
+ * instead of from zero (the field-guide pain row).
14
+ *
15
+ * Shape: this is the same knob contract every L2 composition follows —
16
+ * `createSnapshot(config)` hands back a `{ init, <verbs>, handlers }` bundle you
17
+ * splice into your machine. The state it owns is a Model slice (durable +
18
+ * replayable, the two non-negotiables); the verbs are PURE decisions; the I/O
19
+ * (the actual `put` to the store) lives only in `handlers`.
20
+ *
21
+ * It is a LEAF module: it imports only the core substrate (`../index`, for
22
+ * `Cmd` / `Interpret`) and no sibling subpath. (It does NOT reuse
23
+ * `@demlik/tea/retry-backoff` — that is an even-leafier utility that depends on
24
+ * nothing at all, not even the core; the kinship here is "leaf with injected
25
+ * time", not a shared dependency.)
26
+ * Nothing here reads the clock or the RNG on its own behalf — time enters as an
27
+ * `at`/`now` parameter at the verb boundary and the store I/O is realized only
28
+ * when the runtime performs the emitted `Cmd`. That keeps every verb
29
+ * deterministic (invariant 2) and the checkpoint write a `Cmd` the runtime
30
+ * interprets (invariant 3).
31
+ *
32
+ * The Store port is deliberately ABSTRACT — `{ get, put }`, the lowest common
33
+ * shape of R2 and KV — so this module never imports `@cloudflare/workers-types`
34
+ * and can be checkpointed to anything (R2, KV, a DO sub-key, an in-memory map in
35
+ * tests). The host binds the concrete adapter at `handlers({ store })`.
36
+ *
37
+ * Typical wiring:
38
+ *
39
+ * const snap = createSnapshot<RunState>({ every: 25, key: "run/checkpoint" });
40
+ *
41
+ * // machine state:
42
+ * type State = { run: RunState; snapshot: SnapshotState };
43
+ * init: (loaded) => loaded ? [loaded, []] : [{ run: initRun(), snapshot: snap.init() }, []],
44
+ *
45
+ * // in `update`, on each progress Msg, fold the snapshot slice:
46
+ * progress: (s, msg) => {
47
+ * const run = advance(s.run, msg);
48
+ * const [snapshot, cmds] = snap.record(s.snapshot, run, msg.at);
49
+ * return [{ ...s, run, snapshot }, cmds];
50
+ * },
51
+ * // the write completed → durable ack folds back through update:
52
+ * snapshot_saved: (s, msg) => {
53
+ * const [snapshot, cmds] = snap.confirm(s.snapshot, msg);
54
+ * return [{ ...s, snapshot }, cmds];
55
+ * },
56
+ *
57
+ * // RECOVERY — the half that makes a checkpoint a restart point, not just a
58
+ * // backup. A `boot` Msg the host dispatches once after `run(...)` asks the
59
+ * // store for the durable checkpoint; the answer folds the recovered payload
60
+ * // back into the run slice:
61
+ * boot: (s) => snap.requestLoad(s), // emits snapshot_load Cmd
62
+ * snapshot_loaded: (s, msg) => {
63
+ * if (msg.payload === null) return [s, []]; // no checkpoint → keep fresh init
64
+ * const [snapshot, cmds] = snap.boot(s.snapshot);
65
+ * return [{ ...s, run: msg.payload, snapshot }, cmds];
66
+ * },
67
+ * snapshot_load_failed: (s) => [s, []], // errors are data — decide here
68
+ *
69
+ * // splice the I/O — both effect cells (write + load):
70
+ * interpret: { ...snap.handlers({ store: r2Adapter }) },
71
+ */
72
+
73
+ /**
74
+ * The snapshot knob.
75
+ *
76
+ * `every` is the cadence: a checkpoint is written once `every` units of
77
+ * progress have accumulated since the last one (see `record`). It is the
78
+ * `snapshotEvery` field `monitored-run` threads through to this module.
79
+ *
80
+ * `key` is the store key the checkpoint is written under. A single machine
81
+ * keeps a single rolling checkpoint, so the key is fixed at config time rather
82
+ * than passed per write — the latest checkpoint overwrites the previous one
83
+ * (R2/KV are last-write-wins on a key). Defaults to `"@@snapshot"`, mirroring
84
+ * `doStore`'s `@@state` default-key convention.
85
+ */
86
+ interface SnapshotConfig {
87
+ /**
88
+ * Write a checkpoint once this many progress units have accumulated since the
89
+ * last write. `every: 1` checkpoints on every `record`; a value `<= 0` is
90
+ * treated as `1` (every progress unit triggers a write) — a non-positive
91
+ * cadence has no other sensible meaning, and clamping keeps `record` from
92
+ * never checkpointing on a misconfigured `0`.
93
+ */
94
+ readonly every: number;
95
+ /** Store key the rolling checkpoint is written under. Defaults to `"@@snapshot"`. */
96
+ readonly key?: string;
97
+ }
98
+ /**
99
+ * The snapshot bookkeeping slice. Plain data — JSON-serializable so it rides
100
+ * inside the consumer's `Store<S>` blob and survives DO eviction / reload (the
101
+ * "durable" non-negotiable). It carries NO payload: the thing being
102
+ * checkpointed lives elsewhere in the machine's state; this slice only tracks
103
+ * *when* to checkpoint and *what has been acknowledged durable*.
104
+ *
105
+ * - `sinceLast` — progress units accumulated since the last checkpoint
106
+ * DECISION (reset to 0 the moment `record` decides to write, not when the
107
+ * write lands). The cadence counter.
108
+ * - `seq` — monotonic checkpoint sequence. Bumped each time a write is
109
+ * emitted, so the write Cmd and its later acknowledgement can be matched
110
+ * even if several are in flight. The "version" of the latest emitted
111
+ * checkpoint.
112
+ * - `lastSavedSeq` — the highest `seq` whose write the host has confirmed
113
+ * durable (via `confirm`). `null` until the first confirmed write. The gap
114
+ * `seq - lastSavedSeq` is the "writes emitted but not yet acknowledged"
115
+ * depth — observable for backpressure / health.
116
+ * - `lastSavedAt` — the `at` of the most recent confirmed write (epoch ms),
117
+ * `null` until the first. Carried for observability, never compared inside
118
+ * a verb (time is injected, never read here).
119
+ */
120
+ interface SnapshotState {
121
+ readonly sinceLast: number;
122
+ readonly seq: number;
123
+ readonly lastSavedSeq: number | null;
124
+ readonly lastSavedAt: number | null;
125
+ }
126
+ /**
127
+ * The abstract checkpoint sink — the intersection of R2's and KV's surface,
128
+ * narrowed to exactly the two methods a rolling checkpoint needs. Deliberately
129
+ * NOT the substrate's `Store<S>` (`{ load, save, migrate }`): that is the
130
+ * runtime's whole-state persistence boundary keyed at a fixed cell, whereas a
131
+ * checkpoint store is keyed, value-typed, and host-supplied.
132
+ *
133
+ * - `put(key, value)` — write the checkpoint. Last-write-wins on `key`.
134
+ * - `get(key)` — read the latest checkpoint back, or `null` if none. Used by
135
+ * a recovery path to seed `init`'s `loaded` from the durable checkpoint
136
+ * when the runtime's own `Store<S>` blob is gone.
137
+ *
138
+ * No `@cloudflare/workers-types` import — an R2 bucket, a KV namespace, a DO
139
+ * sub-key, or an in-memory `Map` all satisfy this shape with a 3-line adapter
140
+ * at the host.
141
+ */
142
+ interface SnapshotStore<V> {
143
+ get(key: string): Promise<V | null>;
144
+ put(key: string, value: V): Promise<void>;
145
+ }
146
+ /**
147
+ * The ports `handlers` binds. Just the store today — named so a future port
148
+ * (a metrics sink, a clock) can grow without breaking the `handlers(ports)`
149
+ * call shape.
150
+ */
151
+ interface SnapshotPorts<V> {
152
+ readonly store: SnapshotStore<V>;
153
+ }
154
+ /**
155
+ * The checkpoint-write Cmd a `record` / `force` decision emits. Plain data
156
+ * (invariant 1 — Cmds are data, not closures): it carries the `payload` to
157
+ * write, the `key` to write it under, the `seq` it represents, and the `at` it
158
+ * was decided. The handler performs the `put` and routes the result back to a
159
+ * `confirm` / fail Msg.
160
+ *
161
+ * `payload` is `V` — the consumer's serializable checkpoint value (the run
162
+ * state, the cursor, whatever the machine wants restartable). The verb takes it
163
+ * as an argument so the snapshot slice never has to hold a copy of the
164
+ * machine's domain state.
165
+ */
166
+ type SnapshotWriteCmd<V> = Cmd<"snapshot_write"> & {
167
+ readonly key: string;
168
+ readonly seq: number;
169
+ readonly at: number;
170
+ readonly payload: V;
171
+ };
172
+ /**
173
+ * The Msg the write handler dispatches on a SUCCESSFUL `put`. Fold it back
174
+ * through `confirm(state, msg)` to advance `lastSavedSeq` / `lastSavedAt`. The
175
+ * `seq` matches the emitting Cmd so a late acknowledgement of an older write
176
+ * can never regress a newer confirmed one.
177
+ */
178
+ type SnapshotSavedMsg = {
179
+ readonly type: "snapshot_saved";
180
+ readonly seq: number;
181
+ readonly at: number;
182
+ };
183
+ /**
184
+ * The Msg the write handler dispatches on a FAILED `put`. Errors are data
185
+ * (never swallowed): the consumer's reducer decides whether a failed checkpoint
186
+ * is fatal, retryable (compose `@demlik/tea/retry-backoff`), or merely logged.
187
+ * `seq` identifies which checkpoint failed; `error` is carried, never
188
+ * interpreted here.
189
+ */
190
+ type SnapshotFailedMsg = {
191
+ readonly type: "snapshot_failed";
192
+ readonly seq: number;
193
+ readonly error: unknown;
194
+ };
195
+ /**
196
+ * The checkpoint-READ Cmd a `requestLoad` decision emits — the recovery half of
197
+ * the module. Plain data (invariant 1): it carries only the `key` to read. The
198
+ * handler performs `store.get(key)` and routes the result back to a
199
+ * `snapshot_loaded` (Ok, payload-or-null) / `snapshot_load_failed` (Err) Msg.
200
+ *
201
+ * Distinct from `snapshot_write`: a write carries a payload OUT to the store; a
202
+ * load asks the store to hand a payload BACK. The consumer dispatches the
203
+ * triggering Msg once on resume (a `boot` Msg, never `init`'s rehydrate branch —
204
+ * invariant 2), folds the recovered payload into its run slice, then continues.
205
+ */
206
+ type SnapshotLoadCmd = Cmd<"snapshot_load"> & {
207
+ readonly key: string;
208
+ };
209
+ /**
210
+ * The Msg the load handler dispatches when `store.get` RESOLVES. `payload` is
211
+ * the recovered checkpoint value, or `null` when the store has no checkpoint
212
+ * under `key` (fresh boot — never checkpointed, or a wiped store). The consumer
213
+ * folds this into `init`'s loaded branch: `payload === null` → keep the fresh
214
+ * `init` state; non-null → seed the run slice from the durable checkpoint and
215
+ * `boot` the snapshot slice's cadence counter.
216
+ *
217
+ * A `null` payload is NOT an error — "no checkpoint yet" is a normal first-run
218
+ * state, distinct from "the read failed" (`snapshot_load_failed`).
219
+ */
220
+ type SnapshotLoadedMsg<V> = {
221
+ readonly type: "snapshot_loaded";
222
+ readonly payload: V | null;
223
+ };
224
+ /**
225
+ * The Msg the load handler dispatches when `store.get` REJECTS. Errors are data
226
+ * (never swallowed — the field-guide rule): a failed recovery read is a visible
227
+ * signal the consumer's reducer decides on (fatal-abort, retry via
228
+ * `@demlik/tea/retry-backoff`, or boot-fresh-and-log). `error` is carried, never
229
+ * interpreted here. Kept separate from `snapshot_loaded`'s `null` so "the store
230
+ * has no checkpoint" can never masquerade as "the read failed".
231
+ */
232
+ type SnapshotLoadFailedMsg = {
233
+ readonly type: "snapshot_load_failed";
234
+ readonly error: unknown;
235
+ };
236
+ /** Construct a `snapshot_saved` Msg. Shared by the handler and tests so the wire shape can't drift. */
237
+ declare function snapshotSaved(seq: number, at: number): SnapshotSavedMsg;
238
+ /** Construct a `snapshot_failed` Msg. Shared by the handler and tests so the wire shape can't drift. */
239
+ declare function snapshotFailed(seq: number, error: unknown): SnapshotFailedMsg;
240
+ /** Construct a `snapshot_loaded` Msg. Shared by the handler and tests so the wire shape can't drift. */
241
+ declare function snapshotLoaded<V>(payload: V | null): SnapshotLoadedMsg<V>;
242
+ /** Construct a `snapshot_load_failed` Msg. Shared by the handler and tests so the wire shape can't drift. */
243
+ declare function snapshotLoadFailed(error: unknown): SnapshotLoadFailedMsg;
244
+ /**
245
+ * The bundle `createSnapshot` returns — the uniform knob contract. `subs` is
246
+ * omitted: a checkpoint is driven by progress (a `record` call from the
247
+ * consumer's reducer), not by a timer, so this module has no subscriptions.
248
+ */
249
+ interface SnapshotKnob<V> {
250
+ /** The initial slice — zero progress, no checkpoint emitted or confirmed yet. */
251
+ init(): SnapshotState;
252
+ /**
253
+ * Account one unit of progress. PURE. Increments `sinceLast`; once the
254
+ * accumulated count reaches the configured cadence, DECIDES to checkpoint:
255
+ * resets `sinceLast` to 0, bumps `seq`, and emits a single `snapshot_write`
256
+ * Cmd carrying `payload` + `key` + the new `seq` + `at`. Below the cadence it
257
+ * returns the bumped slice and NO Cmd.
258
+ *
259
+ * `payload` is the checkpoint value to persist (the consumer's domain state);
260
+ * `at` is the caller-stamped time the progress occurred (epoch ms) — injected,
261
+ * never read from the clock here.
262
+ */
263
+ record(state: SnapshotState, payload: V, at: number): readonly [SnapshotState, readonly Cmd[]];
264
+ /**
265
+ * Force a checkpoint regardless of the cadence counter. PURE. Always bumps
266
+ * `seq`, resets `sinceLast`, and emits the write Cmd. Use on a phase boundary
267
+ * the cadence would otherwise miss — e.g. "the run just finished, checkpoint
268
+ * the terminal state now" — so the last durable checkpoint is the final state,
269
+ * not whatever the modulo happened to land on.
270
+ */
271
+ force(state: SnapshotState, payload: V, at: number): readonly [SnapshotState, readonly Cmd[]];
272
+ /**
273
+ * Record a confirmed durable write. PURE. Advances `lastSavedSeq` /
274
+ * `lastSavedAt` to the acknowledged `seq` / `at`, but only forward: an
275
+ * out-of-order acknowledgement of an OLDER `seq` (a slow write landing after a
276
+ * newer one) is ignored, so the confirmed watermark is monotonic. Fold the
277
+ * `snapshot_saved` Msg here.
278
+ *
279
+ * Returns the reducer-cell shape `readonly [SnapshotState, readonly Cmd[]]`
280
+ * (with `Cmd.none` — an ack emits nothing) for uniformity with every sibling
281
+ * knob, so it drops straight into a `snapshot_saved` cell without the caller
282
+ * re-wrapping a bare State.
283
+ */
284
+ confirm(state: SnapshotState, msg: SnapshotSavedMsg): readonly [SnapshotState, readonly Cmd[]];
285
+ /**
286
+ * Resume after a reload. PURE. Resets `sinceLast` to 0 because any
287
+ * un-checkpointed progress accumulated before the crash is moot — recovery
288
+ * resumes from the last DURABLE checkpoint (`get` from the store), so the
289
+ * cadence counter starts fresh from there. `seq` / `lastSavedSeq` /
290
+ * `lastSavedAt` are preserved (they describe what is durable, which a reload
291
+ * does not change). Call from a `boot` Msg the host dispatches once after
292
+ * `run(...)`, never from `init`'s rehydrate branch (invariant 2).
293
+ *
294
+ * Returns the reducer-cell shape `readonly [SnapshotState, readonly Cmd[]]`
295
+ * (with `Cmd.none` — a resume emits nothing) for uniformity with every sibling
296
+ * knob, so it drops straight into a `snapshot_loaded` cell without the caller
297
+ * re-wrapping a bare State.
298
+ */
299
+ boot(state: SnapshotState): readonly [SnapshotState, readonly Cmd[]];
300
+ /**
301
+ * Ask the store for the durable checkpoint. PURE. Emits a single
302
+ * `snapshot_load` Cmd carrying the configured `key`; returns the slice
303
+ * UNCHANGED (a read decides nothing about the cadence bookkeeping — only the
304
+ * later `snapshot_loaded` fold, via `boot`, touches the slice). This is the
305
+ * entry point of the recovery loop: the consumer dispatches a `boot` Msg once
306
+ * after `run(...)`, folds `requestLoad` here, and the emitted Cmd's handler
307
+ * reads `store.get(key)` and dispatches `snapshot_loaded` / `snapshot_load_failed`.
308
+ *
309
+ * Returns `readonly [SnapshotState, readonly Cmd[]]` (the reducer-cell shape)
310
+ * so it drops straight into a `boot: (s) => snap.requestLoad(s)` cell.
311
+ */
312
+ requestLoad(state: SnapshotState): readonly [SnapshotState, readonly Cmd[]];
313
+ /**
314
+ * The `interpret` map for this knob's two effect Cmds:
315
+ *
316
+ * - `snapshot_write` — binds the store and performs the `put`, routing
317
+ * Ok → `snapshot_saved` / Err → `snapshot_failed`.
318
+ * - `snapshot_load` — binds the store and performs the `get`, routing
319
+ * Ok → `snapshot_loaded` (payload-or-null) / Err → `snapshot_load_failed`.
320
+ *
321
+ * Railway throughout — neither effect throws into the runtime; the outcome is
322
+ * always a Msg. Spread into your machine's `interpret`.
323
+ */
324
+ handlers(ports: SnapshotPorts<V>): Interpret<SnapshotSavedMsg | SnapshotFailedMsg | SnapshotLoadedMsg<V> | SnapshotLoadFailedMsg, SnapshotWriteCmd<V> | SnapshotLoadCmd, unknown>;
325
+ }
326
+ /**
327
+ * Create a snapshot knob from a `SnapshotConfig`. The returned bundle is spliced
328
+ * into a consumer machine: `init()` seeds the slice, `record` / `force` are
329
+ * folded in reducer cells, `confirm` / `boot` handle the acknowledgement +
330
+ * resume Msgs, and `handlers({ store })` supplies the write `interpret` cell.
331
+ */
332
+ declare function createSnapshot<V>(config: SnapshotConfig): SnapshotKnob<V>;
333
+
334
+ export { type SnapshotConfig, type SnapshotFailedMsg, type SnapshotKnob, type SnapshotLoadCmd, type SnapshotLoadFailedMsg, type SnapshotLoadedMsg, type SnapshotPorts, type SnapshotSavedMsg, type SnapshotState, type SnapshotStore, type SnapshotWriteCmd, createSnapshot, snapshotFailed, snapshotLoadFailed, snapshotLoaded, snapshotSaved };
@@ -0,0 +1,5 @@
1
+ export { createSnapshot, snapshotFailed, snapshotLoadFailed, snapshotLoaded, snapshotSaved } from '../chunk-EIZJTYKR.js';
2
+ import '../chunk-2WZHS4P2.js';
3
+ import '../chunk-BNJYE4AU.js';
4
+ //# sourceMappingURL=index.js.map
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
@@ -1,5 +1,6 @@
1
- import { Sub, Runtime, Port } from '../index.js';
2
- import { S as SubscribeHandler } from '../types-CwBC5wMn.js';
1
+ import { S as Sub, P as Port } from '../core-CymXZCeY.js';
2
+ import { S as SubscribeHandler } from '../types-N9LZbdnr.js';
3
+ import { Runtime } from '../index.js';
3
4
 
4
5
  interface MinimalEvent {
5
6
  readonly type: string;
@@ -11,6 +12,17 @@ interface MinimalEventTarget {
11
12
  addEventListener(type: string, listener: (event: MinimalEvent) => void): void;
12
13
  removeEventListener(type: string, listener: (event: MinimalEvent) => void): void;
13
14
  }
15
+ interface MinimalCloseEvent extends MinimalEvent {
16
+ readonly code: number;
17
+ readonly reason: string;
18
+ }
19
+ interface MinimalWebSocket {
20
+ close(code?: number, reason?: string): void;
21
+ onopen: ((event: MinimalEvent) => void) | null;
22
+ onmessage: ((event: MinimalMessageEvent) => void) | null;
23
+ onerror: ((event: MinimalEvent) => void) | null;
24
+ onclose: ((event: MinimalCloseEvent) => void) | null;
25
+ }
14
26
 
15
27
  type BroadcastSubData = {
16
28
  channelName: string;
@@ -34,14 +46,11 @@ type IntervalSubData = {
34
46
  };
35
47
  declare function fromInterval<S extends Sub & IntervalSubData, M>(msgFn: (sub: S) => M): SubscribeHandler<S, M, unknown>;
36
48
 
37
- declare function fromPort<S extends Sub, M, Ctx, T>(selectRuntime: (ctx: Ctx) => Runtime<unknown, {
49
+ declare function fromPort<S extends Sub, M, Ctx, T, RS = unknown, RM extends {
38
50
  type: string;
39
- }>, port: Port<T>, msgFn: (value: T, sub: S) => M | null): SubscribeHandler<S, M, Ctx>;
40
-
41
- type TimeoutSubData = {
42
- delayMs: number;
43
- };
44
- declare function fromTimeout<S extends Sub & TimeoutSubData, M>(msgFn: (sub: S) => M): SubscribeHandler<S, M, unknown>;
51
+ } = {
52
+ type: string;
53
+ }>(selectRuntime: (ctx: Ctx) => Runtime<RS, RM>, port: Port<T>, msgFn: (value: T, sub: S) => M | null): SubscribeHandler<S, M, Ctx>;
45
54
 
46
55
  type WebSocketSubData = {
47
56
  wsUrl: string;
@@ -54,4 +63,33 @@ interface WebSocketFactoryOpts<S, M> {
54
63
  }
55
64
  declare function fromWebSocket<S extends Sub & WebSocketSubData, M>(opts: WebSocketFactoryOpts<S, M>): SubscribeHandler<S, M, unknown>;
56
65
 
57
- export { type EventSourceFactoryOpts, SubscribeHandler, type WebSocketFactoryOpts, type WebSocketSubData, fromBroadcastChannel, fromEventSource, fromEventTarget, fromInterval, fromPort, fromTimeout, fromWebSocket };
66
+ /** Cancels a scheduled reconnect timer (the inverse of `schedule`). */
67
+ type CancelTimer = () => void;
68
+ interface ReconnectingWebSocketFactoryOpts<S, M> {
69
+ onMessage: (data: unknown, sub: S) => M | null;
70
+ onOpen?: (sub: S) => M | null;
71
+ onError?: (event: MinimalEvent, sub: S) => M | null;
72
+ onClose?: (code: number, reason: string, sub: S) => M | null;
73
+ /**
74
+ * Fires when a reconnection (any open after the first) succeeds, carrying the
75
+ * 1-based reconnect count — the resync hook. Omitting drops reconnect events
76
+ * silently.
77
+ */
78
+ onReconnect?: (attempt: number, sub: S) => M | null;
79
+ /** First backoff delay in ms (doubles each failed attempt). Default 250. */
80
+ backoffBaseMs?: number;
81
+ /** Backoff ceiling in ms. Default 5000. */
82
+ backoffMaxMs?: number;
83
+ /** Inject the socket constructor (defaults to the global `WebSocket`); the test seam. */
84
+ connect?: (url: string) => MinimalWebSocket;
85
+ /** Inject the timer (defaults to `setTimeout`/`clearTimeout`); the test seam. */
86
+ schedule?: (fn: () => void, ms: number) => CancelTimer;
87
+ }
88
+ declare function fromReconnectingWebSocket<S extends Sub & WebSocketSubData, M>(opts: ReconnectingWebSocketFactoryOpts<S, M>): SubscribeHandler<S, M, unknown>;
89
+
90
+ type TimeoutSubData = {
91
+ delayMs: number;
92
+ };
93
+ declare function fromTimeout<S extends Sub & TimeoutSubData, M>(msgFn: (sub: S) => M): SubscribeHandler<S, M, unknown>;
94
+
95
+ export { type CancelTimer, type EventSourceFactoryOpts, type ReconnectingWebSocketFactoryOpts, SubscribeHandler, type WebSocketFactoryOpts, type WebSocketSubData, fromBroadcastChannel, fromEventSource, fromEventTarget, fromInterval, fromPort, fromReconnectingWebSocket, fromTimeout, fromWebSocket };
@@ -1,4 +1,4 @@
1
- export { fromBroadcastChannel, fromEventSource, fromEventTarget, fromInterval, fromPort, fromWebSocket } from '../chunk-Y5JM3UUH.js';
1
+ export { fromBroadcastChannel, fromEventSource, fromEventTarget, fromInterval, fromPort, fromReconnectingWebSocket, fromWebSocket } from '../chunk-NSDFA6M6.js';
2
2
  export { fromTimeout } from '../chunk-2YFLOCWX.js';
3
3
  //# sourceMappingURL=index.js.map
4
4
  //# sourceMappingURL=index.js.map
@@ -1,4 +1,109 @@
1
- import { Cmd, Sub, Machine, Runtime } from '../index.js';
1
+ import { C as Cmd, S as Sub, M as Machine } from '../core-CymXZCeY.js';
2
+ import { Runtime } from '../index.js';
3
+
4
+ /**
5
+ * The composed Model shape every `withX` wrapper produces: the base machine's
6
+ * state nested under `base`, and the wrapper's own NAMED, serializable slice
7
+ * under a single `$`-prefixed key. This helper does not care WHICH `$`-key the
8
+ * wrapper uses — it discovers it structurally (the one non-`base` field).
9
+ */
10
+ type WrapperModel<S> = {
11
+ readonly base: S;
12
+ } & {
13
+ readonly [K: `$${string}`]: unknown;
14
+ };
15
+ /** Options for the conformance replay. Mirrors `replay`'s second argument. */
16
+ interface AssertWrapperFaithfulOpts<S, WM, BaseM, BaseCtx, WrapperCtx> {
17
+ /** The Msg sequence to fold through BOTH machines. Use the base's Msgs. */
18
+ readonly msgs: readonly BaseM[];
19
+ /** Ctx for the WRAPPED machine (base ctx + wrapper ports). */
20
+ readonly ctx: WrapperCtx;
21
+ /** Ctx for the BARE base machine. Defaults to `ctx` when omitted. */
22
+ readonly baseCtx?: BaseCtx;
23
+ /** Optional pre-`init` snapshot of the COMPOSED Model (wrapped rehydrate). */
24
+ readonly loaded?: WM | null;
25
+ /**
26
+ * Optional pre-`init` snapshot of the BARE base state. Supply when the
27
+ * wrapped `loaded` is non-null so the base-equivalence check rehydrates the
28
+ * base machine from the matching nested slice. Defaults to `loaded?.base`.
29
+ */
30
+ readonly baseLoaded?: S | null;
31
+ /**
32
+ * Opt-in relaxation for an INTERCEPTING wrapper (e.g. `withResilience`).
33
+ *
34
+ * DEFAULT (`undefined` / `false`) — the OBSERVE-ONLY contract that
35
+ * `withTelemetry` and `withDeadline` satisfy: every bare base Cmd must appear
36
+ * UNCHANGED, in order, as a subsequence of the wrapped Cmd stream (properties
37
+ * 1 and 3b). The wrapper may APPEND its own Cmds but may NOT alter, drop, or
38
+ * retime a base Cmd. This is the strict default and it is NOT weakened.
39
+ *
40
+ * WHEN SET — for a wrapper whose whole job is to INTERCEPT a chosen base Cmd
41
+ * (retag it into a wrapper-owned carrier so the concern — cache / breaker /
42
+ * rate-limit / retry — can gate it), a target base Cmd LEGITIMATELY does not
43
+ * appear raw at the top level of the wrapped stream. The contract is still
44
+ * **retag-and-re-emit, never swallow** (rule 2): the Cmd must be RECORDED, not
45
+ * vanished. So when a bare base Cmd is ABSENT from the top-level wrapped
46
+ * subsequence, this option accepts it IFF, in that SAME transition window, it
47
+ * is recorded by the wrapper as one of:
48
+ *
49
+ * - the inner payload of a `$x:*` "run" carrier Cmd (`carrierCmdType` +
50
+ * `innerField`) — the retag path; OR
51
+ * - any other `$x:*` divergence Cmd whose `recordsField` deep-equals the
52
+ * base Cmd — the gate/defer path (e.g. a "fast-failed" or "deferred"
53
+ * record Cmd that carries the base Cmd it diverged on).
54
+ *
55
+ * A target base Cmd that is NEITHER present raw NOR recorded by any `$x:*`
56
+ * Cmd in its window is a SWALLOW betrayal and still fails the gate.
57
+ *
58
+ * Provide the field names the carrier uses so the gate can unwrap it
59
+ * structurally without hard-coding `withResilience`'s vocabulary.
60
+ */
61
+ readonly intercepting?: InterceptingOpt;
62
+ }
63
+ /**
64
+ * Configures the intercepting relaxation (see
65
+ * `AssertWrapperFaithfulOpts.intercepting`). All fields name the carrier /
66
+ * divergence shape so the gate can structurally find a retagged base Cmd
67
+ * inside a `$x:*` Cmd. The gate only EVER inspects `$x:*`-prefixed wrapped
68
+ * Cmds for a recorded base Cmd — it never relaxes the check for a base Cmd
69
+ * that appears under any non-wrapper Cmd type.
70
+ */
71
+ interface InterceptingOpt {
72
+ /**
73
+ * The `Cmd.type` of the wrapper's run-carrier (e.g. `"$resilience:run"`).
74
+ * A bare base Cmd that is retagged appears as `carrier[innerField]`.
75
+ */
76
+ readonly carrierCmdType: string;
77
+ /** The field on the carrier Cmd that holds the original base Cmd. */
78
+ readonly innerField: string;
79
+ /**
80
+ * OPTIONAL field on ANY OTHER `$x:*` Cmd that records a base Cmd the wrapper
81
+ * gated/deferred without running it (e.g. a fast-fail record). When present
82
+ * on a `$x:*` Cmd and deep-equal to the missing base Cmd, it counts as a
83
+ * recorded divergence. Omit when the wrapper only ever retags via the
84
+ * carrier.
85
+ */
86
+ readonly recordsField?: string;
87
+ }
88
+ /**
89
+ * Assert a `withX` wrapper is TEA-faithful. Replays the wrapped machine and the
90
+ * bare base machine over the same Msgs and checks the four conformance
91
+ * properties (see file header).
92
+ *
93
+ * @param base the bare base `Machine`.
94
+ * @param makeWrapped a thunk that wraps `base` and returns the composed machine
95
+ * (e.g. `() => withTelemetry(base, cfg)`). A thunk — not the wrapped value —
96
+ * so a wrapper that registers process-scoped ports (`definePort`) is
97
+ * constructed inside the assertion's control, and so the same betrayal-
98
+ * detector reads as `assertWrapperFaithful(base, () => withX(base, cfg), ...)`
99
+ * at every wrapper's test site.
100
+ * @param opts the conformance replay options.
101
+ */
102
+ declare function assertWrapperFaithful<S, BaseM extends {
103
+ type: string;
104
+ }, BaseC extends Cmd, BaseU extends Sub, BaseCtx, WM extends WrapperModel<S>, WrapperM extends {
105
+ type: string;
106
+ }, WrapperC extends Cmd, WrapperU extends Sub, WrapperCtx>(base: Machine<S, BaseM, BaseC, BaseU, BaseCtx>, makeWrapped: () => Machine<WM, WrapperM, WrapperC, WrapperU, WrapperCtx>, opts: AssertWrapperFaithfulOpts<S, WM, BaseM, BaseCtx, WrapperCtx>): void;
2
107
 
3
108
  /**
4
109
  * Shared options shape for every test assertion below. Mirrors the
@@ -217,4 +322,4 @@ declare function stateFactory<S extends {
217
322
  type: string;
218
323
  }>(defaults: StateFactoryDefaults<S>): StateFactoryAPI<S>;
219
324
 
220
- export { type BoundMachine, type ReplayOpts, type StateFactoryAPI, type StateFactoryDefaults, bindMachine, expectActiveSubs, expectCmdEmitted, expectCmdSequence, expectFinalState, noopRuntime, stateFactory, step };
325
+ export { type AssertWrapperFaithfulOpts, type BoundMachine, type InterceptingOpt, type ReplayOpts, type StateFactoryAPI, type StateFactoryDefaults, type WrapperModel, assertWrapperFaithful, bindMachine, expectActiveSubs, expectCmdEmitted, expectCmdSequence, expectFinalState, noopRuntime, stateFactory, step };