@demlik/tea 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) 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-27N2SPTN.js +222 -0
  18. package/dist/chunk-27N2SPTN.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-3XJWC7VW.js +61 -0
  24. package/dist/chunk-3XJWC7VW.js.map +1 -0
  25. package/dist/chunk-5DEQ5I3U.js +132 -0
  26. package/dist/chunk-5DEQ5I3U.js.map +1 -0
  27. package/dist/chunk-5VNWZADP.js +49 -0
  28. package/dist/chunk-5VNWZADP.js.map +1 -0
  29. package/dist/chunk-6RGBKODV.js +268 -0
  30. package/dist/chunk-6RGBKODV.js.map +1 -0
  31. package/dist/{chunk-ZRUST6BK.js → chunk-7FYQGDMF.js} +2 -2
  32. package/dist/chunk-7FYQGDMF.js.map +1 -0
  33. package/dist/chunk-7W4LMPUW.js +186 -0
  34. package/dist/chunk-7W4LMPUW.js.map +1 -0
  35. package/dist/chunk-BYQUGDD3.js +16 -0
  36. package/dist/chunk-BYQUGDD3.js.map +1 -0
  37. package/dist/{chunk-PYBQREFU.js → chunk-DILMKOZ7.js} +22 -26
  38. package/dist/chunk-DILMKOZ7.js.map +1 -0
  39. package/dist/chunk-EHIX7NYZ.js +93 -0
  40. package/dist/chunk-EHIX7NYZ.js.map +1 -0
  41. package/dist/chunk-HCEVHFJ7.js +54 -0
  42. package/dist/chunk-HCEVHFJ7.js.map +1 -0
  43. package/dist/chunk-HJO6VNQI.js +15 -0
  44. package/dist/chunk-HJO6VNQI.js.map +1 -0
  45. package/dist/chunk-IN33Q4VJ.js +52 -0
  46. package/dist/chunk-IN33Q4VJ.js.map +1 -0
  47. package/dist/chunk-J6527VRQ.js +56 -0
  48. package/dist/chunk-J6527VRQ.js.map +1 -0
  49. package/dist/chunk-JELSZN25.js +47 -0
  50. package/dist/chunk-JELSZN25.js.map +1 -0
  51. package/dist/chunk-K575PT4S.js +58 -0
  52. package/dist/chunk-K575PT4S.js.map +1 -0
  53. package/dist/chunk-K7Q23EQ4.js +20 -0
  54. package/dist/chunk-K7Q23EQ4.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-PYOCUBQJ.js → chunk-LP7VQBRG.js} +22 -15
  58. package/dist/chunk-LP7VQBRG.js.map +1 -0
  59. package/dist/{chunk-CMG2KMWX.js → chunk-MMH7CW5K.js} +220 -189
  60. package/dist/chunk-MMH7CW5K.js.map +1 -0
  61. package/dist/{chunk-Y5JM3UUH.js → chunk-NSDFA6M6.js} +75 -3
  62. package/dist/chunk-NSDFA6M6.js.map +1 -0
  63. package/dist/chunk-ON24RKSE.js +60 -0
  64. package/dist/chunk-ON24RKSE.js.map +1 -0
  65. package/dist/chunk-PS23H6KQ.js +87 -0
  66. package/dist/chunk-PS23H6KQ.js.map +1 -0
  67. package/dist/chunk-QLA76GIT.js +162 -0
  68. package/dist/chunk-QLA76GIT.js.map +1 -0
  69. package/dist/chunk-S3FQN4SL.js +506 -0
  70. package/dist/chunk-S3FQN4SL.js.map +1 -0
  71. package/dist/chunk-SC3FAFZN.js +53 -0
  72. package/dist/chunk-SC3FAFZN.js.map +1 -0
  73. package/dist/chunk-SINXE47Y.js +55 -0
  74. package/dist/chunk-SINXE47Y.js.map +1 -0
  75. package/dist/chunk-SQTCS44W.js +33 -0
  76. package/dist/chunk-SQTCS44W.js.map +1 -0
  77. package/dist/chunk-TGG26XMK.js +52 -0
  78. package/dist/chunk-TGG26XMK.js.map +1 -0
  79. package/dist/chunk-UV6UWGEO.js +147 -0
  80. package/dist/chunk-UV6UWGEO.js.map +1 -0
  81. package/dist/chunk-UYWUVEP6.js +158 -0
  82. package/dist/chunk-UYWUVEP6.js.map +1 -0
  83. package/dist/chunk-VLGARN6H.js +202 -0
  84. package/dist/chunk-VLGARN6H.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-BoB6t7dB.d.ts +188 -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.d.ts +6 -0
  92. package/dist/debounce/index.js +1 -54
  93. package/dist/debounce/index.js.map +1 -1
  94. package/dist/devtools/index.d.ts +1 -1
  95. package/dist/devtools/index.js +18 -6
  96. package/dist/devtools/index.js.map +1 -1
  97. package/dist/do/index.d.ts +1723 -92
  98. package/dist/do/index.js +673 -31
  99. package/dist/do/index.js.map +1 -1
  100. package/dist/durable-effects-CO9Uh971.d.ts +180 -0
  101. package/dist/extension/index.d.ts +31 -13
  102. package/dist/extension/index.js +5 -3
  103. package/dist/extension/index.js.map +1 -1
  104. package/dist/extension/react.js +8 -3
  105. package/dist/extension/react.js.map +1 -1
  106. package/dist/extension/subs/index.d.ts +2 -2
  107. package/dist/extension/subs/index.js.map +1 -1
  108. package/dist/extension/test-utils.js +11 -3
  109. package/dist/extension/test-utils.js.map +1 -1
  110. package/dist/fan-out/index.d.ts +239 -0
  111. package/dist/fan-out/index.js +5 -0
  112. package/dist/fan-out/index.js.map +1 -0
  113. package/dist/idempotency/adapter.d.ts +62 -0
  114. package/dist/idempotency/adapter.js +4 -0
  115. package/dist/idempotency/adapter.js.map +1 -0
  116. package/dist/idempotency/index.js +1 -56
  117. package/dist/idempotency/index.js.map +1 -1
  118. package/dist/idempotent-intake/index.d.ts +186 -0
  119. package/dist/idempotent-intake/index.js +7 -0
  120. package/dist/idempotent-intake/index.js.map +1 -0
  121. package/dist/index.d.ts +341 -191
  122. package/dist/index.js +2 -1
  123. package/dist/llm-call/index.d.ts +309 -0
  124. package/dist/llm-call/index.js +14 -0
  125. package/dist/llm-call/index.js.map +1 -0
  126. package/dist/machine-viz/index.d.ts +4 -4
  127. package/dist/machine-viz/index.js +5 -9
  128. package/dist/machine-viz/index.js.map +1 -1
  129. package/dist/mem/index.d.ts +1 -0
  130. package/dist/monitored-run/index.d.ts +253 -0
  131. package/dist/monitored-run/index.js +10 -0
  132. package/dist/monitored-run/index.js.map +1 -0
  133. package/dist/node/index.d.ts +5 -5
  134. package/dist/node/index.js.map +1 -1
  135. package/dist/paginated-walk/index.d.ts +234 -0
  136. package/dist/paginated-walk/index.js +15 -0
  137. package/dist/paginated-walk/index.js.map +1 -0
  138. package/dist/paginator/index.d.ts +271 -0
  139. package/dist/paginator/index.js +3 -0
  140. package/dist/paginator/index.js.map +1 -0
  141. package/dist/parity/index.d.ts +92 -0
  142. package/dist/parity/index.js +78 -0
  143. package/dist/parity/index.js.map +1 -0
  144. package/dist/pbt/arbitraries/index.d.ts +1 -1
  145. package/dist/pbt/arbitraries/index.js +1 -1
  146. package/dist/pbt/index.d.ts +15 -8
  147. package/dist/pbt/index.js +6 -11
  148. package/dist/pbt/index.js.map +1 -1
  149. package/dist/pbt/runners/index.d.ts +1 -1
  150. package/dist/pbt/runners/index.js +3 -2
  151. package/dist/poller/index.d.ts +288 -0
  152. package/dist/poller/index.js +79 -0
  153. package/dist/poller/index.js.map +1 -0
  154. package/dist/prediction/index.d.ts +130 -0
  155. package/dist/prediction/index.js +4 -0
  156. package/dist/prediction/index.js.map +1 -0
  157. package/dist/protocol-B5YmJr0d.d.ts +45 -0
  158. package/dist/pure/index.d.ts +2 -0
  159. package/dist/pure/index.js +4 -0
  160. package/dist/pure/index.js.map +1 -0
  161. package/dist/raft/demo.d.ts +105 -0
  162. package/dist/raft/demo.js +172 -0
  163. package/dist/raft/demo.js.map +1 -0
  164. package/dist/raft/index.d.ts +548 -0
  165. package/dist/raft/index.js +7 -0
  166. package/dist/raft/index.js.map +1 -0
  167. package/dist/raft/sim.d.ts +222 -0
  168. package/dist/raft/sim.js +8 -0
  169. package/dist/raft/sim.js.map +1 -0
  170. package/dist/raft/timeline.d.ts +46 -0
  171. package/dist/raft/timeline.js +62 -0
  172. package/dist/raft/timeline.js.map +1 -0
  173. package/dist/rate-limit/index.js +1 -43
  174. package/dist/rate-limit/index.js.map +1 -1
  175. package/dist/react/index.d.ts +2 -1
  176. package/dist/react/index.js +43 -20
  177. package/dist/react/index.js.map +1 -1
  178. package/dist/reconciler/index.d.ts +192 -0
  179. package/dist/reconciler/index.js +166 -0
  180. package/dist/reconciler/index.js.map +1 -0
  181. package/dist/recorder/index.d.ts +7 -3
  182. package/dist/recorder/index.js +1 -155
  183. package/dist/recorder/index.js.map +1 -1
  184. package/dist/resilient-call/index.d.ts +294 -0
  185. package/dist/resilient-call/index.js +13 -0
  186. package/dist/resilient-call/index.js.map +1 -0
  187. package/dist/retry-backoff/index.d.ts +42 -5
  188. package/dist/retry-backoff/index.js +1 -35
  189. package/dist/retry-backoff/index.js.map +1 -1
  190. package/dist/retry-to-success/index.d.ts +109 -0
  191. package/dist/retry-to-success/index.js +35 -0
  192. package/dist/retry-to-success/index.js.map +1 -0
  193. package/dist/saga/index.d.ts +238 -0
  194. package/dist/saga/index.js +150 -0
  195. package/dist/saga/index.js.map +1 -0
  196. package/dist/snapshot/index.d.ts +334 -0
  197. package/dist/snapshot/index.js +5 -0
  198. package/dist/snapshot/index.js.map +1 -0
  199. package/dist/subs/index.d.ts +48 -10
  200. package/dist/subs/index.js +1 -1
  201. package/dist/testing/index.d.ts +107 -2
  202. package/dist/testing/index.js +204 -6
  203. package/dist/testing/index.js.map +1 -1
  204. package/dist/throttle/index.d.ts +9 -0
  205. package/dist/throttle/index.js +1 -52
  206. package/dist/throttle/index.js.map +1 -1
  207. package/dist/throttled-input/index.d.ts +380 -0
  208. package/dist/throttled-input/index.js +88 -0
  209. package/dist/throttled-input/index.js.map +1 -0
  210. package/dist/token-refresh/index.d.ts +215 -0
  211. package/dist/token-refresh/index.js +6 -0
  212. package/dist/token-refresh/index.js.map +1 -0
  213. package/dist/trace-replay/index.d.ts +11 -2
  214. package/dist/trace-replay/index.js +3 -55
  215. package/dist/trace-replay/index.js.map +1 -1
  216. package/dist/{types-CwBC5wMn.d.ts → types-B-8bcAp8.d.ts} +1 -1
  217. package/dist/with-deadline/index.d.ts +196 -0
  218. package/dist/with-deadline/index.js +111 -0
  219. package/dist/with-deadline/index.js.map +1 -0
  220. package/dist/with-resilience/index.d.ts +205 -0
  221. package/dist/with-resilience/index.js +251 -0
  222. package/dist/with-resilience/index.js.map +1 -0
  223. package/dist/with-telemetry/index.d.ts +149 -0
  224. package/dist/with-telemetry/index.js +70 -0
  225. package/dist/with-telemetry/index.js.map +1 -0
  226. package/dist/work-queue/adapter.d.ts +82 -0
  227. package/dist/work-queue/adapter.js +4 -0
  228. package/dist/work-queue/adapter.js.map +1 -0
  229. package/dist/work-queue/index.d.ts +25 -11
  230. package/dist/work-queue/index.js +1 -46
  231. package/dist/work-queue/index.js.map +1 -1
  232. package/dist/work-queue/ops.d.ts +69 -0
  233. package/dist/work-queue/ops.js +3 -0
  234. package/dist/work-queue/ops.js.map +1 -0
  235. package/dist/workflow/index.d.ts +450 -0
  236. package/dist/workflow/index.js +225 -0
  237. package/dist/workflow/index.js.map +1 -0
  238. package/package.json +168 -25
  239. package/dist/chunk-CMG2KMWX.js.map +0 -1
  240. package/dist/chunk-PYBQREFU.js.map +0 -1
  241. package/dist/chunk-PYOCUBQJ.js.map +0 -1
  242. package/dist/chunk-Y5JM3UUH.js.map +0 -1
  243. package/dist/chunk-ZRUST6BK.js.map +0 -1
@@ -0,0 +1,288 @@
1
+ import { DeadlineSub } from '../deadline/index.js';
2
+ import { IdempotencyStore } from '../idempotency/index.js';
3
+ import { C as Cmd, S as Sub } from '../core-BoB6t7dB.js';
4
+ import { RetryPolicy, RetryState } from '../retry-backoff/index.js';
5
+ import '../types-B-8bcAp8.js';
6
+
7
+ /**
8
+ * @demlik/tea/poller — "poll a source every N ms until a predicate holds, with
9
+ * backoff on failure" as a TEA knob: one config object + a few pre-wired hooks
10
+ * you spread into your machine.
11
+ *
12
+ * The recipe (which bricks it composes):
13
+ *
14
+ * - `../deadline` — the recurring tick is an ABSOLUTE-deadline Sub.
15
+ * Each successful tick computes the NEXT absolute
16
+ * fire time (`at + everyMs`) and the knob re-arms a
17
+ * deadline against it. Absolute (not relative) so a
18
+ * poller resumed after a DO eviction / page reload
19
+ * fires at the correct wall-clock moment, not
20
+ * `everyMs` after the reload (`../deadline`'s late-
21
+ * subscribe property).
22
+ * - `../retry-backoff` — a failed tick does NOT reschedule on the steady
23
+ * cadence; it backs off. The next attempt is armed
24
+ * `nextDelayMs(retry, policy)` from now, the retry
25
+ * counter advances, and `shouldRetry` decides whether
26
+ * the poller keeps trying or gives up.
27
+ * - `../idempotency` — successive ticks against a flaky source can deliver
28
+ * the SAME observation twice (a retried fetch, a
29
+ * duplicate webhook the poll re-reads). `dedupeKey`
30
+ * lets the consumer key each tick result; a duplicate
31
+ * key is recorded but emits no fresh `onTick` follow-
32
+ * up Cmd, so the side effect runs once per distinct
33
+ * observation.
34
+ *
35
+ * Typical wiring (spread the knob's hooks into your machine):
36
+ *
37
+ * const poll = createPoller<MyState, MyResult>({
38
+ * everyMs: 5_000,
39
+ * until: (s) => s.poll.lastResult?.status === "ready",
40
+ * onTick: () => ({ type: "fetch_status" }),
41
+ * retry: { ...defaultRetryPolicy, maxAttempts: 4 },
42
+ * });
43
+ *
44
+ * // Model slice:
45
+ * init: (loaded) => [loaded ?? { poll: poll.init(), ... }, []],
46
+ *
47
+ * // The deadline Sub fires `deadline_exceeded` on the cadence/backoff target;
48
+ * // route it to `tick`, the ONLY place the `onTick` fetch is emitted:
49
+ * deadline_exceeded: (s) => {
50
+ * const [slice, cmds] = poll.tick(s.poll);
51
+ * return [{ ...s, poll: slice }, cmds];
52
+ * },
53
+ *
54
+ * // update cells (thread `at` in from the firing Msg / the runtime clock):
55
+ * poll_result: (s, m) => {
56
+ * const untilHeld = config.until({ ...s, ...folded });
57
+ * const [slice, cmds] = poll.tickResult(s.poll, m.result, m.at, untilHeld);
58
+ * return [{ ...s, poll: slice }, cmds];
59
+ * },
60
+ * poll_error: (s, m) => {
61
+ * const [slice, cmds] = poll.tickErr(s.poll, m.error, m.at);
62
+ * return [{ ...s, poll: slice }, cmds];
63
+ * },
64
+ *
65
+ * // Subs — the knob owns the interval/backoff deadline:
66
+ * subscriptions: (s) => poll.subs(s.poll),
67
+ *
68
+ * The cadence has exactly ONE source: the `everyMs` deadline Sub. `start`,
69
+ * `tickResult`, and `tickErr` only RE-ARM the deadline (they emit no cadence
70
+ * Cmd); the fetch fires solely from `tick`, which the consumer routes the
71
+ * deadline's `deadline_exceeded` Msg to. No verb performs an immediate
72
+ * "fire now" observation, so the poller never polls faster than `everyMs`.
73
+ *
74
+ * The two non-negotiables every knob preserves hold here:
75
+ * - **Durable** — `tick`, `lastResult`, `retry`, and the next-fire target all
76
+ * live in the Model slice, so a poller survives eviction and resumes its
77
+ * cadence from the persisted target.
78
+ * - **Replayable** — every transition is a pure verb returning new state; the
79
+ * clock is never read inside a verb (the firing Msg carries `at`), and the
80
+ * retry RNG is injected ONCE at `createPoller` and threaded internally (no
81
+ * verb body names the global RNG). `replay` reconstructs a poll run exactly.
82
+ *
83
+ * NOT a substrate primitive: it depends only on sibling subpaths
84
+ * (`../deadline`, `../retry-backoff`, `../idempotency`) and the core `Cmd` /
85
+ * `Sub` types. Consumers reach it via the `@demlik/tea/poller` subpath; the L1
86
+ * escape hatch is those three sibling subpaths threaded by hand.
87
+ */
88
+
89
+ /**
90
+ * The poller knob's config — the single object you hand `createPoller`.
91
+ *
92
+ * `State` is the consumer's WHOLE Model (so `until` can read any field), `R` is
93
+ * the shape of one tick observation (`lastResult` carries it forward).
94
+ */
95
+ interface PollerConfig<State, R> {
96
+ /**
97
+ * The steady cadence, in milliseconds, between successful ticks. The next
98
+ * tick is armed at the absolute target `at + everyMs` (computed from the
99
+ * `at` the prior tick's Msg carried), NOT `everyMs` from when the Sub
100
+ * happens to be reconciled — so the cadence does not drift across resumes.
101
+ */
102
+ readonly everyMs: number;
103
+ /**
104
+ * The stop predicate, read against the consumer's whole Model. When it
105
+ * returns `true`, the poller is DONE: `subs` returns `[]` (the timer
106
+ * disarms) and no further tick is scheduled. Pure — it must not read the
107
+ * clock or mutate; the substrate calls it inside `subscriptions(state)`.
108
+ */
109
+ readonly until: (state: State) => boolean;
110
+ /**
111
+ * The effect to run on each tick — the actual "go observe the source" Cmd
112
+ * (a fetch, a status read, a list call). The knob returns it from
113
+ * `tickResult` when the run continues; the consumer's `interpret` performs
114
+ * it and routes the observation back as the next tick-result Msg.
115
+ *
116
+ * Nullary by contract: a tick carries no per-call argument (the poller
117
+ * re-reads the same source every cadence). Capture any target in the Cmd
118
+ * literal the consumer's `onTick` returns.
119
+ */
120
+ readonly onTick: () => Cmd;
121
+ /**
122
+ * Backoff policy for FAILED ticks. Omit to use `defaultRetryPolicy`
123
+ * (full-jitter, 5 attempts). A success resets the retry counter, so the
124
+ * policy only governs consecutive failures.
125
+ */
126
+ readonly retry?: RetryPolicy;
127
+ /**
128
+ * Optional dedupe key derived from a tick result. When provided, a result
129
+ * whose key was already `seen` records the observation but emits NO fresh
130
+ * `onTick` follow-up — the side effect runs once per distinct observation.
131
+ * Omit to treat every tick result as fresh (no dedupe). The dedupe store is
132
+ * bounded by `dedupeTtlMs` if set; otherwise it grows with distinct keys.
133
+ */
134
+ readonly dedupeKey?: (result: R) => string;
135
+ /**
136
+ * TTL for the dedupe store, in milliseconds. Only meaningful when
137
+ * `dedupeKey` is set. An observation older than `dedupeTtlMs` is forgotten,
138
+ * so a key seen long ago is treated as fresh again. Omit for an unbounded
139
+ * dedupe window (every distinct key remembered for the poller's lifetime).
140
+ */
141
+ readonly dedupeTtlMs?: number;
142
+ }
143
+ /**
144
+ * The Model field the poller knob owns — its visible slice (the knob principle:
145
+ * managed state lives in the Model, never a closure, so it is durable and
146
+ * replayable).
147
+ *
148
+ * - `tick` — count of ticks recorded so far (success or failure). Doubles
149
+ * as an observability cursor and a stable per-tick discriminant.
150
+ * - `lastResult` — the most recent successful observation, or `undefined`
151
+ * before the first success. `until` typically reads it (via the
152
+ * consumer's Model), but the knob never interprets it.
153
+ * - `retry` — the `../retry-backoff` counter for consecutive failures.
154
+ * Reset to `initRetry()` on every success.
155
+ * - `nextAtMs` — the ABSOLUTE target the interval/backoff timer is armed
156
+ * against, or `null` when the poller is done / not yet started.
157
+ * Persisted so a resumed poller re-arms the same moment.
158
+ * - `phase` — `"polling"` while ticks are scheduled, `"done"` once `until`
159
+ * held, `"gave_up"` once backoff exhausted `maxAttempts`.
160
+ * - `dedupe` — the `../idempotency` store backing `dedupeKey`. An empty,
161
+ * unbounded store when `dedupeKey` is unset (never written).
162
+ */
163
+ interface PollerState<R> {
164
+ readonly tick: number;
165
+ readonly lastResult?: R;
166
+ readonly retry: RetryState;
167
+ readonly nextAtMs: number | null;
168
+ readonly phase: "polling" | "done" | "gave_up";
169
+ readonly dedupe: IdempotencyStore<true>;
170
+ }
171
+ /**
172
+ * The poller Sub type — a `../deadline` Sub under a fixed id family. One id per
173
+ * knob instance keeps the reconcile pass from churning the timer: the same id
174
+ * across transitions means "same armed deadline", even as `nextAtMs` advances.
175
+ */
176
+ type PollerSub = DeadlineSub;
177
+ /**
178
+ * The knob handle returned by `createPoller`. Spread its hooks into your
179
+ * machine: `init()` seeds the slice, `start(...)` arms the first tick,
180
+ * `tickResult` / `tickErr` are the two update verbs, and `subs(...)` returns the
181
+ * timer Sub while the poller is live.
182
+ *
183
+ * Pure-ops + subs module: there is no `handlers(ports)` cell. The poller's I/O
184
+ * is the consumer's own `onTick` Cmd run by the consumer's `interpret` — the
185
+ * knob never owns a port. (Compare `../deadline`, which ships a `subscribe`
186
+ * cell; the poller reuses `../deadline`'s, so it has nothing of its own to
187
+ * splice into `interpret`.)
188
+ */
189
+ interface Poller<State, R> {
190
+ /** Seed the Model slice. Idle until `start(...)` arms the first tick. */
191
+ init(): PollerState<R>;
192
+ /**
193
+ * Arm the first tick. `at` is the current clock (carried in from the Msg /
194
+ * runtime that kicks the poller off — never read inside the verb). Returns
195
+ * the slice with the first deadline target set to `at + everyMs` and emits NO
196
+ * Cmd. The cadence has ONE source — the deadline Sub — so `start` does not
197
+ * perform the first observation itself (an immediate `onTick` here would race
198
+ * the timer and poll as fast as the source responds). The first observation
199
+ * fires when the deadline crosses `at + everyMs` and the consumer routes
200
+ * `deadline_exceeded` → `tick`.
201
+ */
202
+ start(state: PollerState<R>, at: number): readonly [PollerState<R>, readonly Cmd[]];
203
+ /**
204
+ * Perform one observation — the cadence verb. The consumer routes the
205
+ * deadline Sub's `deadline_exceeded` Msg here; `tick` emits the single
206
+ * `config.onTick()` Cmd that goes and reads the source. This is the ONLY
207
+ * next-tick mechanism: the timer fires, the consumer calls `tick`, the fetch
208
+ * runs, and the observation comes back through `tickResult` / `tickErr`
209
+ * (which re-arm the next deadline). A no-op on a finished poller — a stale
210
+ * deadline fire racing a terminal transition the reconcile pass has not yet
211
+ * retired emits nothing.
212
+ *
213
+ * Takes no `at`: `tick` does not touch the schedule (the just-fired deadline
214
+ * is exhausted; the next target is set when the result lands), so it needs no
215
+ * clock — keeping it trivially pure.
216
+ */
217
+ tick(state: PollerState<R>): readonly [PollerState<R>, readonly Cmd[]];
218
+ /**
219
+ * Record a successful tick observation at clock `at`.
220
+ *
221
+ * - Resets the retry counter (a success clears consecutive-failure state).
222
+ * - Stores `result` as `lastResult` and bumps `tick`.
223
+ * - Re-arms the next tick at the absolute target `at + everyMs` (the steady
224
+ * cadence). It emits NO cadence `onTick` — the next observation fires from
225
+ * the deadline Sub via `tick`, never immediately from a result.
226
+ * - Dedupe (opt-in, orthogonal to cadence): when `dedupeKey` is set, a FRESH
227
+ * observation emits one `onTick` FOLLOW-UP (the consumer's "run the
228
+ * downstream side effect once per distinct observation" hook) and a
229
+ * DUPLICATE key records the sighting but suppresses the follow-up. With no
230
+ * `dedupeKey`, `tickResult` emits nothing — the cadence `tick` is the only
231
+ * `onTick`, so an unconfigured poller never double-fetches.
232
+ * - UNLESS `untilHeld` is `true` (the consumer evaluated `config.until`
233
+ * against the post-result Model) — then the poller enters `"done"`,
234
+ * disarms (`nextAtMs: null`), and emits no Cmd, dedupe or otherwise.
235
+ *
236
+ * `untilHeld` is passed in rather than computed here because `until` reads
237
+ * the consumer's WHOLE Model, which this verb does not hold — the consumer
238
+ * evaluates it at the call site after folding `result` into its Model and
239
+ * passes the boolean. (`subs` re-checks `phase` to decide arming, so a
240
+ * mis-passed `untilHeld` cannot leave a "done" poller arming timers.)
241
+ */
242
+ tickResult(state: PollerState<R>, result: R, at: number, untilHeld: boolean): readonly [PollerState<R>, readonly Cmd[]];
243
+ /**
244
+ * Record a FAILED tick at clock `at`. Backs off instead of holding the
245
+ * steady cadence:
246
+ *
247
+ * - Advances the retry counter (`recordFailure`).
248
+ * - If `shouldRetry` still permits another attempt, arms the next tick at
249
+ * `at + nextDelayMs(retry, policy)` (the backoff curve) and emits NO Cmd —
250
+ * the retry observation fires when that backoff deadline crosses and the
251
+ * consumer routes `deadline_exceeded` → `tick`. The timer is the single
252
+ * next-tick mechanism, on the backoff curve here instead of `everyMs`.
253
+ * - If backoff is exhausted (`!shouldRetry`), enters `"gave_up"`, disarms
254
+ * (`nextAtMs: null`), and emits no Cmd.
255
+ *
256
+ * The backoff jitter uses the `rng` injected ONCE at `createPoller` (default
257
+ * `Math.random` at the effect boundary, a fixed value in tests); the verb
258
+ * body never names the global RNG, so a poller built with a fixed `rng`
259
+ * replays a failure-and-backoff run bit-for-bit.
260
+ */
261
+ tickErr(state: PollerState<R>, error: unknown, at: number): readonly [PollerState<R>, readonly Cmd[]];
262
+ /**
263
+ * The pre-wired subscriptions cell. Returns the single tick-deadline Sub
264
+ * while the poller is `"polling"` with an armed `nextAtMs`; returns `[]`
265
+ * once `"done"` / `"gave_up"` (the timer disarms) or before `start`. Assign
266
+ * directly: `subscriptions: (s) => poll.subs(s.poll)`.
267
+ *
268
+ * The `DeadlineSub` it returns is consumed by `../deadline`'s
269
+ * `subscribeDeadline` cell, which the consumer wires into `subscribe` —
270
+ * the poller does not redraw the timer lifecycle.
271
+ */
272
+ subs(state: PollerState<R>): readonly Sub[];
273
+ }
274
+ /**
275
+ * Build a poller knob from `config`. Returns the hook bag to spread into a
276
+ * machine (see the module doc-comment for the wiring). Pure factory — no clock
277
+ * read, no timer; everything happens when the consumer calls the returned
278
+ * verbs / `subs` with an injected `at`.
279
+ *
280
+ * `rng` is injected ONCE here (default `Math.random` at the effect boundary, a
281
+ * fixed value in tests) and threaded internally into the backoff jitter. No
282
+ * verb body ever names the global RNG, so a poller built with a fixed `rng`
283
+ * replays a failure-and-backoff run bit-for-bit (invariant 2: the verbs are
284
+ * pure given the injected `rng`).
285
+ */
286
+ declare function createPoller<State, R>(config: PollerConfig<State, R>, rng?: () => number): Poller<State, R>;
287
+
288
+ export { type Poller, type PollerConfig, type PollerState, type PollerSub, createPoller };
@@ -0,0 +1,79 @@
1
+ import { deadlineSub } from '../chunk-K7Q23EQ4.js';
2
+ import { asRng, defaultRetryPolicy, initRetry, recordFailure, shouldRetry, nextDelayMs } from '../chunk-5VNWZADP.js';
3
+ import { idempotencyMemory } from '../chunk-BYQUGDD3.js';
4
+ import '../chunk-K575PT4S.js';
5
+ import '../chunk-2YFLOCWX.js';
6
+ import '../chunk-MMH7CW5K.js';
7
+ import '../chunk-QLA76GIT.js';
8
+
9
+ // src/poller/index.ts
10
+ function pollerSubId(atMs) {
11
+ return `poller:tick:${atMs}`;
12
+ }
13
+ function createPoller(config, rng = Math.random) {
14
+ const rngBranded = asRng(rng);
15
+ const policy = config.retry ?? defaultRetryPolicy;
16
+ const memory = idempotencyMemory();
17
+ function init() {
18
+ return {
19
+ tick: 0,
20
+ retry: initRetry(),
21
+ nextAtMs: null,
22
+ phase: "polling",
23
+ // The dedupe store is created regardless; it is only WRITTEN when
24
+ // `dedupeKey` is set, so an unconfigured poller carries an inert empty
25
+ // store (no per-op cost, JSON-serializable, durable like the rest of
26
+ // the slice).
27
+ dedupe: memory.init({ ttlMs: config.dedupeTtlMs })
28
+ };
29
+ }
30
+ function start(state, at) {
31
+ return [{ ...state, phase: "polling", nextAtMs: at + config.everyMs }, []];
32
+ }
33
+ function tick(state) {
34
+ if (state.phase !== "polling") return [state, []];
35
+ return [state, [config.onTick()]];
36
+ }
37
+ function tickResult(state, result, at, untilHeld) {
38
+ const base = {
39
+ ...state,
40
+ tick: state.tick + 1,
41
+ lastResult: result,
42
+ retry: initRetry()
43
+ };
44
+ if (untilHeld) {
45
+ return [{ ...base, phase: "done", nextAtMs: null }, []];
46
+ }
47
+ const next = {
48
+ ...base,
49
+ phase: "polling",
50
+ nextAtMs: at + config.everyMs
51
+ };
52
+ if (!config.dedupeKey) return [next, []];
53
+ const key = config.dedupeKey(result);
54
+ const suppress = memory.isSeen(next.dedupe, key, at);
55
+ const deduped = {
56
+ ...next,
57
+ dedupe: memory.remember(next.dedupe, key, true, at)
58
+ };
59
+ return [deduped, suppress ? [] : [config.onTick()]];
60
+ }
61
+ function tickErr(state, error, at) {
62
+ const retry = recordFailure(state.retry, error);
63
+ const failed = { ...state, tick: state.tick + 1, retry };
64
+ if (!shouldRetry(retry, policy)) {
65
+ return [{ ...failed, phase: "gave_up", nextAtMs: null }, []];
66
+ }
67
+ const delayMs = nextDelayMs(retry, policy, rngBranded);
68
+ return [{ ...failed, phase: "polling", nextAtMs: at + delayMs }, []];
69
+ }
70
+ function subs(state) {
71
+ if (state.phase !== "polling" || state.nextAtMs === null) return [];
72
+ return [deadlineSub(pollerSubId(state.nextAtMs), state.nextAtMs)];
73
+ }
74
+ return { init, start, tick, tickResult, tickErr, subs };
75
+ }
76
+
77
+ export { createPoller };
78
+ //# sourceMappingURL=index.js.map
79
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/poller/index.ts"],"names":[],"mappings":";;;;;;;;;AA0MA,SAAS,YAAY,IAAA,EAAsB;AACzC,EAAA,OAAO,eAAe,IAAI,CAAA,CAAA;AAC5B;AAqIO,SAAS,YAAA,CACd,MAAA,EACA,GAAA,GAAoB,IAAA,CAAK,MAAA,EACP;AAKlB,EAAA,MAAM,UAAA,GAAa,MAAM,GAAG,CAAA;AAC5B,EAAA,MAAM,MAAA,GAAsB,OAAO,KAAA,IAAS,kBAAA;AAK5C,EAAA,MAAM,SAAS,iBAAA,EAAwB;AAEvC,EAAA,SAAS,IAAA,GAAuB;AAC9B,IAAA,OAAO;AAAA,MACL,IAAA,EAAM,CAAA;AAAA,MACN,OAAO,SAAA,EAAU;AAAA,MACjB,QAAA,EAAU,IAAA;AAAA,MACV,KAAA,EAAO,SAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAKP,QAAQ,MAAA,CAAO,IAAA,CAAK,EAAE,KAAA,EAAO,MAAA,CAAO,aAAa;AAAA,KACnD;AAAA,EACF;AAEA,EAAA,SAAS,KAAA,CACP,OACA,EAAA,EAC2C;AAQ3C,IAAA,OAAO,CAAC,EAAE,GAAG,KAAA,EAAO,KAAA,EAAO,SAAA,EAAW,QAAA,EAAU,EAAA,GAAK,MAAA,CAAO,OAAA,EAAQ,EAAG,EAAE,CAAA;AAAA,EAC3E;AAEA,EAAA,SAAS,KACP,KAAA,EAC2C;AAO3C,IAAA,IAAI,MAAM,KAAA,KAAU,SAAA,SAAkB,CAAC,KAAA,EAAO,EAAE,CAAA;AAChD,IAAA,OAAO,CAAC,KAAA,EAAO,CAAC,MAAA,CAAO,MAAA,EAAQ,CAAC,CAAA;AAAA,EAClC;AAEA,EAAA,SAAS,UAAA,CACP,KAAA,EACA,MAAA,EACA,EAAA,EACA,SAAA,EAC2C;AAE3C,IAAA,MAAM,IAAA,GAAuB;AAAA,MAC3B,GAAG,KAAA;AAAA,MACH,IAAA,EAAM,MAAM,IAAA,GAAO,CAAA;AAAA,MACnB,UAAA,EAAY,MAAA;AAAA,MACZ,OAAO,SAAA;AAAU,KACnB;AAKA,IAAA,IAAI,SAAA,EAAW;AACb,MAAA,OAAO,CAAC,EAAE,GAAG,IAAA,EAAM,KAAA,EAAO,QAAQ,QAAA,EAAU,IAAA,EAAK,EAAG,EAAE,CAAA;AAAA,IACxD;AAOA,IAAA,MAAM,IAAA,GAAuB;AAAA,MAC3B,GAAG,IAAA;AAAA,MACH,KAAA,EAAO,SAAA;AAAA,MACP,QAAA,EAAU,KAAK,MAAA,CAAO;AAAA,KACxB;AAQA,IAAA,IAAI,CAAC,MAAA,CAAO,SAAA,SAAkB,CAAC,IAAA,EAAM,EAAE,CAAA;AAEvC,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,SAAA,CAAU,MAAM,CAAA;AACnC,IAAA,MAAM,WAAW,MAAA,CAAO,MAAA,CAAO,IAAA,CAAK,MAAA,EAAQ,KAAK,EAAE,CAAA;AAKnD,IAAA,MAAM,OAAA,GAA0B;AAAA,MAC9B,GAAG,IAAA;AAAA,MACH,QAAQ,MAAA,CAAO,QAAA,CAAS,KAAK,MAAA,EAAQ,GAAA,EAAK,MAAM,EAAE;AAAA,KACpD;AACA,IAAA,OAAO,CAAC,SAAS,QAAA,GAAW,KAAK,CAAC,MAAA,CAAO,MAAA,EAAQ,CAAC,CAAA;AAAA,EACpD;AAEA,EAAA,SAAS,OAAA,CACP,KAAA,EACA,KAAA,EACA,EAAA,EAC2C;AAC3C,IAAA,MAAM,KAAA,GAAQ,aAAA,CAAc,KAAA,CAAM,KAAA,EAAO,KAAK,CAAA;AAC9C,IAAA,MAAM,MAAA,GAAyB,EAAE,GAAG,KAAA,EAAO,MAAM,KAAA,CAAM,IAAA,GAAO,GAAG,KAAA,EAAM;AAKvE,IAAA,IAAI,CAAC,WAAA,CAAY,KAAA,EAAO,MAAM,CAAA,EAAG;AAC/B,MAAA,OAAO,CAAC,EAAE,GAAG,MAAA,EAAQ,KAAA,EAAO,WAAW,QAAA,EAAU,IAAA,EAAK,EAAG,EAAE,CAAA;AAAA,IAC7D;AASA,IAAA,MAAM,OAAA,GAAU,WAAA,CAAY,KAAA,EAAO,MAAA,EAAQ,UAAU,CAAA;AACrD,IAAA,OAAO,CAAC,EAAE,GAAG,MAAA,EAAQ,KAAA,EAAO,SAAA,EAAW,QAAA,EAAU,EAAA,GAAK,OAAA,EAAQ,EAAG,EAAE,CAAA;AAAA,EACrE;AAEA,EAAA,SAAS,KAAK,KAAA,EAAuC;AAMnD,IAAA,IAAI,MAAM,KAAA,KAAU,SAAA,IAAa,MAAM,QAAA,KAAa,IAAA,SAAa,EAAC;AASlE,IAAA,OAAO,CAAC,YAAY,WAAA,CAAY,KAAA,CAAM,QAAQ,CAAA,EAAG,KAAA,CAAM,QAAQ,CAAC,CAAA;AAAA,EAClE;AAEA,EAAA,OAAO,EAAE,IAAA,EAAM,KAAA,EAAO,IAAA,EAAM,UAAA,EAAY,SAAS,IAAA,EAAK;AACxD","file":"index.js","sourcesContent":["/**\n * @demlik/tea/poller — \"poll a source every N ms until a predicate holds, with\n * backoff on failure\" as a TEA knob: one config object + a few pre-wired hooks\n * you spread into your machine.\n *\n * The recipe (which bricks it composes):\n *\n * - `../deadline` — the recurring tick is an ABSOLUTE-deadline Sub.\n * Each successful tick computes the NEXT absolute\n * fire time (`at + everyMs`) and the knob re-arms a\n * deadline against it. Absolute (not relative) so a\n * poller resumed after a DO eviction / page reload\n * fires at the correct wall-clock moment, not\n * `everyMs` after the reload (`../deadline`'s late-\n * subscribe property).\n * - `../retry-backoff` — a failed tick does NOT reschedule on the steady\n * cadence; it backs off. The next attempt is armed\n * `nextDelayMs(retry, policy)` from now, the retry\n * counter advances, and `shouldRetry` decides whether\n * the poller keeps trying or gives up.\n * - `../idempotency` — successive ticks against a flaky source can deliver\n * the SAME observation twice (a retried fetch, a\n * duplicate webhook the poll re-reads). `dedupeKey`\n * lets the consumer key each tick result; a duplicate\n * key is recorded but emits no fresh `onTick` follow-\n * up Cmd, so the side effect runs once per distinct\n * observation.\n *\n * Typical wiring (spread the knob's hooks into your machine):\n *\n * const poll = createPoller<MyState, MyResult>({\n * everyMs: 5_000,\n * until: (s) => s.poll.lastResult?.status === \"ready\",\n * onTick: () => ({ type: \"fetch_status\" }),\n * retry: { ...defaultRetryPolicy, maxAttempts: 4 },\n * });\n *\n * // Model slice:\n * init: (loaded) => [loaded ?? { poll: poll.init(), ... }, []],\n *\n * // The deadline Sub fires `deadline_exceeded` on the cadence/backoff target;\n * // route it to `tick`, the ONLY place the `onTick` fetch is emitted:\n * deadline_exceeded: (s) => {\n * const [slice, cmds] = poll.tick(s.poll);\n * return [{ ...s, poll: slice }, cmds];\n * },\n *\n * // update cells (thread `at` in from the firing Msg / the runtime clock):\n * poll_result: (s, m) => {\n * const untilHeld = config.until({ ...s, ...folded });\n * const [slice, cmds] = poll.tickResult(s.poll, m.result, m.at, untilHeld);\n * return [{ ...s, poll: slice }, cmds];\n * },\n * poll_error: (s, m) => {\n * const [slice, cmds] = poll.tickErr(s.poll, m.error, m.at);\n * return [{ ...s, poll: slice }, cmds];\n * },\n *\n * // Subs — the knob owns the interval/backoff deadline:\n * subscriptions: (s) => poll.subs(s.poll),\n *\n * The cadence has exactly ONE source: the `everyMs` deadline Sub. `start`,\n * `tickResult`, and `tickErr` only RE-ARM the deadline (they emit no cadence\n * Cmd); the fetch fires solely from `tick`, which the consumer routes the\n * deadline's `deadline_exceeded` Msg to. No verb performs an immediate\n * \"fire now\" observation, so the poller never polls faster than `everyMs`.\n *\n * The two non-negotiables every knob preserves hold here:\n * - **Durable** — `tick`, `lastResult`, `retry`, and the next-fire target all\n * live in the Model slice, so a poller survives eviction and resumes its\n * cadence from the persisted target.\n * - **Replayable** — every transition is a pure verb returning new state; the\n * clock is never read inside a verb (the firing Msg carries `at`), and the\n * retry RNG is injected ONCE at `createPoller` and threaded internally (no\n * verb body names the global RNG). `replay` reconstructs a poll run exactly.\n *\n * NOT a substrate primitive: it depends only on sibling subpaths\n * (`../deadline`, `../retry-backoff`, `../idempotency`) and the core `Cmd` /\n * `Sub` types. Consumers reach it via the `@demlik/tea/poller` subpath; the L1\n * escape hatch is those three sibling subpaths threaded by hand.\n */\n\nimport { type DeadlineSub, deadlineSub } from \"../deadline\";\nimport type { IdempotencyStore } from \"../idempotency\";\nimport { idempotencyMemory } from \"../idempotency/adapter\";\nimport type { Cmd, Sub } from \"../index\";\nimport {\n asRng,\n defaultRetryPolicy,\n initRetry,\n nextDelayMs,\n type RetryPolicy,\n type RetryState,\n recordFailure,\n shouldRetry,\n} from \"../retry-backoff\";\n\n/**\n * The poller knob's config — the single object you hand `createPoller`.\n *\n * `State` is the consumer's WHOLE Model (so `until` can read any field), `R` is\n * the shape of one tick observation (`lastResult` carries it forward).\n */\nexport interface PollerConfig<State, R> {\n /**\n * The steady cadence, in milliseconds, between successful ticks. The next\n * tick is armed at the absolute target `at + everyMs` (computed from the\n * `at` the prior tick's Msg carried), NOT `everyMs` from when the Sub\n * happens to be reconciled — so the cadence does not drift across resumes.\n */\n readonly everyMs: number;\n /**\n * The stop predicate, read against the consumer's whole Model. When it\n * returns `true`, the poller is DONE: `subs` returns `[]` (the timer\n * disarms) and no further tick is scheduled. Pure — it must not read the\n * clock or mutate; the substrate calls it inside `subscriptions(state)`.\n */\n readonly until: (state: State) => boolean;\n /**\n * The effect to run on each tick — the actual \"go observe the source\" Cmd\n * (a fetch, a status read, a list call). The knob returns it from\n * `tickResult` when the run continues; the consumer's `interpret` performs\n * it and routes the observation back as the next tick-result Msg.\n *\n * Nullary by contract: a tick carries no per-call argument (the poller\n * re-reads the same source every cadence). Capture any target in the Cmd\n * literal the consumer's `onTick` returns.\n */\n readonly onTick: () => Cmd;\n /**\n * Backoff policy for FAILED ticks. Omit to use `defaultRetryPolicy`\n * (full-jitter, 5 attempts). A success resets the retry counter, so the\n * policy only governs consecutive failures.\n */\n readonly retry?: RetryPolicy;\n /**\n * Optional dedupe key derived from a tick result. When provided, a result\n * whose key was already `seen` records the observation but emits NO fresh\n * `onTick` follow-up — the side effect runs once per distinct observation.\n * Omit to treat every tick result as fresh (no dedupe). The dedupe store is\n * bounded by `dedupeTtlMs` if set; otherwise it grows with distinct keys.\n */\n readonly dedupeKey?: (result: R) => string;\n /**\n * TTL for the dedupe store, in milliseconds. Only meaningful when\n * `dedupeKey` is set. An observation older than `dedupeTtlMs` is forgotten,\n * so a key seen long ago is treated as fresh again. Omit for an unbounded\n * dedupe window (every distinct key remembered for the poller's lifetime).\n */\n readonly dedupeTtlMs?: number;\n}\n\n/**\n * The Model field the poller knob owns — its visible slice (the knob principle:\n * managed state lives in the Model, never a closure, so it is durable and\n * replayable).\n *\n * - `tick` — count of ticks recorded so far (success or failure). Doubles\n * as an observability cursor and a stable per-tick discriminant.\n * - `lastResult` — the most recent successful observation, or `undefined`\n * before the first success. `until` typically reads it (via the\n * consumer's Model), but the knob never interprets it.\n * - `retry` — the `../retry-backoff` counter for consecutive failures.\n * Reset to `initRetry()` on every success.\n * - `nextAtMs` — the ABSOLUTE target the interval/backoff timer is armed\n * against, or `null` when the poller is done / not yet started.\n * Persisted so a resumed poller re-arms the same moment.\n * - `phase` — `\"polling\"` while ticks are scheduled, `\"done\"` once `until`\n * held, `\"gave_up\"` once backoff exhausted `maxAttempts`.\n * - `dedupe` — the `../idempotency` store backing `dedupeKey`. An empty,\n * unbounded store when `dedupeKey` is unset (never written).\n */\nexport interface PollerState<R> {\n readonly tick: number;\n readonly lastResult?: R;\n readonly retry: RetryState;\n readonly nextAtMs: number | null;\n readonly phase: \"polling\" | \"done\" | \"gave_up\";\n readonly dedupe: IdempotencyStore<true>;\n}\n\n/**\n * The poller Sub type — a `../deadline` Sub under a fixed id family. One id per\n * knob instance keeps the reconcile pass from churning the timer: the same id\n * across transitions means \"same armed deadline\", even as `nextAtMs` advances.\n */\nexport type PollerSub = DeadlineSub;\n\n/**\n * The Sub id the poller arms its tick deadline under, keyed on the absolute\n * target. The id ROTATES per target because a one-shot deadline timer is\n * exhausted once it fires and the substrate reconciles subs by `id` alone: a\n * fixed id would leave the fired timer registered and never re-arm, so the\n * cadence would die after exactly one tick. Encoding `atMs` in the id makes each\n * cadence/backoff target a distinct identity — the reconcile pass retires the\n * spent timer and arms the next — while a resumed poller re-arms the SAME id\n * from its persisted `nextAtMs` (eviction is idempotent).\n *\n * A single poller machine arms exactly one tick timer at a time; a consumer\n * running several pollers on one machine namespaces them by composing the knob\n * under distinct slices (a future multi-poller config would thread a prefix).\n */\nfunction pollerSubId(atMs: number): string {\n return `poller:tick:${atMs}`;\n}\n\n/**\n * The knob handle returned by `createPoller`. Spread its hooks into your\n * machine: `init()` seeds the slice, `start(...)` arms the first tick,\n * `tickResult` / `tickErr` are the two update verbs, and `subs(...)` returns the\n * timer Sub while the poller is live.\n *\n * Pure-ops + subs module: there is no `handlers(ports)` cell. The poller's I/O\n * is the consumer's own `onTick` Cmd run by the consumer's `interpret` — the\n * knob never owns a port. (Compare `../deadline`, which ships a `subscribe`\n * cell; the poller reuses `../deadline`'s, so it has nothing of its own to\n * splice into `interpret`.)\n */\nexport interface Poller<\n // State is a phantom-style parameter — present so consumers can spell\n // `Poller<MyState, MyResult>` matching `createPoller<State, R>`. It is\n // consumed by the config's `until` predicate, not by any handle method, so\n // it does not appear in the interface body. Same discipline as\n // `BoundMachine`'s `Ctx`.\n // biome-ignore lint/correctness/noUnusedVariables: phantom type parameter (see above)\n State,\n R,\n> {\n /** Seed the Model slice. Idle until `start(...)` arms the first tick. */\n init(): PollerState<R>;\n /**\n * Arm the first tick. `at` is the current clock (carried in from the Msg /\n * runtime that kicks the poller off — never read inside the verb). Returns\n * the slice with the first deadline target set to `at + everyMs` and emits NO\n * Cmd. The cadence has ONE source — the deadline Sub — so `start` does not\n * perform the first observation itself (an immediate `onTick` here would race\n * the timer and poll as fast as the source responds). The first observation\n * fires when the deadline crosses `at + everyMs` and the consumer routes\n * `deadline_exceeded` → `tick`.\n */\n start(\n state: PollerState<R>,\n at: number,\n ): readonly [PollerState<R>, readonly Cmd[]];\n /**\n * Perform one observation — the cadence verb. The consumer routes the\n * deadline Sub's `deadline_exceeded` Msg here; `tick` emits the single\n * `config.onTick()` Cmd that goes and reads the source. This is the ONLY\n * next-tick mechanism: the timer fires, the consumer calls `tick`, the fetch\n * runs, and the observation comes back through `tickResult` / `tickErr`\n * (which re-arm the next deadline). A no-op on a finished poller — a stale\n * deadline fire racing a terminal transition the reconcile pass has not yet\n * retired emits nothing.\n *\n * Takes no `at`: `tick` does not touch the schedule (the just-fired deadline\n * is exhausted; the next target is set when the result lands), so it needs no\n * clock — keeping it trivially pure.\n */\n tick(state: PollerState<R>): readonly [PollerState<R>, readonly Cmd[]];\n /**\n * Record a successful tick observation at clock `at`.\n *\n * - Resets the retry counter (a success clears consecutive-failure state).\n * - Stores `result` as `lastResult` and bumps `tick`.\n * - Re-arms the next tick at the absolute target `at + everyMs` (the steady\n * cadence). It emits NO cadence `onTick` — the next observation fires from\n * the deadline Sub via `tick`, never immediately from a result.\n * - Dedupe (opt-in, orthogonal to cadence): when `dedupeKey` is set, a FRESH\n * observation emits one `onTick` FOLLOW-UP (the consumer's \"run the\n * downstream side effect once per distinct observation\" hook) and a\n * DUPLICATE key records the sighting but suppresses the follow-up. With no\n * `dedupeKey`, `tickResult` emits nothing — the cadence `tick` is the only\n * `onTick`, so an unconfigured poller never double-fetches.\n * - UNLESS `untilHeld` is `true` (the consumer evaluated `config.until`\n * against the post-result Model) — then the poller enters `\"done\"`,\n * disarms (`nextAtMs: null`), and emits no Cmd, dedupe or otherwise.\n *\n * `untilHeld` is passed in rather than computed here because `until` reads\n * the consumer's WHOLE Model, which this verb does not hold — the consumer\n * evaluates it at the call site after folding `result` into its Model and\n * passes the boolean. (`subs` re-checks `phase` to decide arming, so a\n * mis-passed `untilHeld` cannot leave a \"done\" poller arming timers.)\n */\n tickResult(\n state: PollerState<R>,\n result: R,\n at: number,\n untilHeld: boolean,\n ): readonly [PollerState<R>, readonly Cmd[]];\n /**\n * Record a FAILED tick at clock `at`. Backs off instead of holding the\n * steady cadence:\n *\n * - Advances the retry counter (`recordFailure`).\n * - If `shouldRetry` still permits another attempt, arms the next tick at\n * `at + nextDelayMs(retry, policy)` (the backoff curve) and emits NO Cmd —\n * the retry observation fires when that backoff deadline crosses and the\n * consumer routes `deadline_exceeded` → `tick`. The timer is the single\n * next-tick mechanism, on the backoff curve here instead of `everyMs`.\n * - If backoff is exhausted (`!shouldRetry`), enters `\"gave_up\"`, disarms\n * (`nextAtMs: null`), and emits no Cmd.\n *\n * The backoff jitter uses the `rng` injected ONCE at `createPoller` (default\n * `Math.random` at the effect boundary, a fixed value in tests); the verb\n * body never names the global RNG, so a poller built with a fixed `rng`\n * replays a failure-and-backoff run bit-for-bit.\n */\n tickErr(\n state: PollerState<R>,\n error: unknown,\n at: number,\n ): readonly [PollerState<R>, readonly Cmd[]];\n /**\n * The pre-wired subscriptions cell. Returns the single tick-deadline Sub\n * while the poller is `\"polling\"` with an armed `nextAtMs`; returns `[]`\n * once `\"done\"` / `\"gave_up\"` (the timer disarms) or before `start`. Assign\n * directly: `subscriptions: (s) => poll.subs(s.poll)`.\n *\n * The `DeadlineSub` it returns is consumed by `../deadline`'s\n * `subscribeDeadline` cell, which the consumer wires into `subscribe` —\n * the poller does not redraw the timer lifecycle.\n */\n subs(state: PollerState<R>): readonly Sub[];\n}\n\n/**\n * Build a poller knob from `config`. Returns the hook bag to spread into a\n * machine (see the module doc-comment for the wiring). Pure factory — no clock\n * read, no timer; everything happens when the consumer calls the returned\n * verbs / `subs` with an injected `at`.\n *\n * `rng` is injected ONCE here (default `Math.random` at the effect boundary, a\n * fixed value in tests) and threaded internally into the backoff jitter. No\n * verb body ever names the global RNG, so a poller built with a fixed `rng`\n * replays a failure-and-backoff run bit-for-bit (invariant 2: the verbs are\n * pure given the injected `rng`).\n */\nexport function createPoller<State, R>(\n config: PollerConfig<State, R>,\n rng: () => number = Math.random,\n): Poller<State, R> {\n // Re-affirm the `[0, 1)` contract at the seam where the injected generator\n // enters 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 where it's branded for `nextDelayMs`.\n const rngBranded = asRng(rng);\n const policy: RetryPolicy = config.retry ?? defaultRetryPolicy;\n\n // The dedupe store seam, bound once. The poller delegates its dedupe-window\n // reads (`memory.isSeen`) and the per-sighting touch (`memory.remember`) to\n // the verbs, so the `IdempotencyStore` shape stays insulated.\n const memory = idempotencyMemory<true>();\n\n function init(): PollerState<R> {\n return {\n tick: 0,\n retry: initRetry(),\n nextAtMs: null,\n phase: \"polling\",\n // The dedupe store is created regardless; it is only WRITTEN when\n // `dedupeKey` is set, so an unconfigured poller carries an inert empty\n // store (no per-op cost, JSON-serializable, durable like the rest of\n // the slice).\n dedupe: memory.init({ ttlMs: config.dedupeTtlMs }),\n };\n }\n\n function start(\n state: PollerState<R>,\n at: number,\n ): readonly [PollerState<R>, readonly Cmd[]] {\n // Arm the first deadline at `at + everyMs` and emit NOTHING. The cadence has\n // ONE source — the `everyMs` deadline Sub. `start` does not perform the\n // first observation itself: that would be a second, immediate next-tick\n // mechanism racing the timer (it would fire as fast as the source responds,\n // never honoring `everyMs`). The first observation fires when the deadline\n // crosses `at + everyMs` and the consumer routes `deadline_exceeded` → the\n // `tick` verb (which emits `onTick`). No drift, no double-fire.\n return [{ ...state, phase: \"polling\", nextAtMs: at + config.everyMs }, []];\n }\n\n function tick(\n state: PollerState<R>,\n ): readonly [PollerState<R>, readonly Cmd[]] {\n // The deadline fired. Perform the observation — this is the ONLY place the\n // `onTick` Cmd is emitted, so the timer is the single next-tick mechanism.\n // No schedule change here: the just-fired deadline is exhausted, and the\n // NEXT target is re-armed when the observation lands (`tickResult` /\n // `tickErr`). A no-op on a finished poller (a stale fire racing a terminal\n // transition the reconcile pass has not yet retired).\n if (state.phase !== \"polling\") return [state, []];\n return [state, [config.onTick()]];\n }\n\n function tickResult(\n state: PollerState<R>,\n result: R,\n at: number,\n untilHeld: boolean,\n ): readonly [PollerState<R>, readonly Cmd[]] {\n // A success always clears consecutive-failure state and records the datum.\n const base: PollerState<R> = {\n ...state,\n tick: state.tick + 1,\n lastResult: result,\n retry: initRetry(),\n };\n\n // Stop condition first: if the consumer's `until` held against the\n // post-result Model, we are DONE — disarm and emit nothing, regardless of\n // dedupe (the final observation is still recorded as `lastResult`).\n if (untilHeld) {\n return [{ ...base, phase: \"done\", nextAtMs: null }, []];\n }\n\n // Re-arm the steady cadence: the next observation fires when THIS deadline\n // (`at + everyMs`) crosses and the consumer routes `deadline_exceeded` →\n // `tick`. `tickResult` itself never emits the cadence `onTick` — the timer\n // is the single next-tick mechanism (recording a result is not a reason to\n // immediately re-fetch; that would poll as fast as the source responds).\n const next: PollerState<R> = {\n ...base,\n phase: \"polling\",\n nextAtMs: at + config.everyMs,\n };\n\n // Dedupe is an OPT-IN follow-up, orthogonal to the cadence above. When\n // `dedupeKey` is set, a FRESH observation emits one `onTick` follow-up (the\n // consumer's \"run the downstream side effect once per distinct observation\"\n // hook); a DUPLICATE key records the sighting but suppresses that follow-up.\n // With no `dedupeKey` there is no follow-up at all — the cadence `tick` is\n // the only `onTick`, so an unconfigured poller never double-fetches.\n if (!config.dedupeKey) return [next, []];\n\n const key = config.dedupeKey(result);\n const suppress = memory.isSeen(next.dedupe, key, at);\n // `remember` refreshes the key's TTL clock on every sighting (the\n // idempotency \"touch\" semantics), so a steadily-repeating observation never\n // ages out mid-poll. The stored value is the unit `true` — the poller only\n // needs presence, not a payload.\n const deduped: PollerState<R> = {\n ...next,\n dedupe: memory.remember(next.dedupe, key, true, at),\n };\n return [deduped, suppress ? [] : [config.onTick()]];\n }\n\n function tickErr(\n state: PollerState<R>,\n error: unknown,\n at: number,\n ): readonly [PollerState<R>, readonly Cmd[]] {\n const retry = recordFailure(state.retry, error);\n const failed: PollerState<R> = { ...state, tick: state.tick + 1, retry };\n\n // Backoff exhausted → give up: disarm and emit nothing. `shouldRetry`\n // reads the just-advanced `retry`, so the (maxAttempts)-th failure is the\n // one that stops the poller.\n if (!shouldRetry(retry, policy)) {\n return [{ ...failed, phase: \"gave_up\", nextAtMs: null }, []];\n }\n\n // Back off: arm the next attempt at `at + backoff(retry)` rather than the\n // steady cadence, and emit NOTHING. The retry observation fires when this\n // backoff deadline crosses and the consumer routes `deadline_exceeded` →\n // `tick` — the timer is the single next-tick mechanism, on the backoff\n // curve here instead of `everyMs`. `rng` is the value injected once at\n // `createPoller`; the verb body never names the global RNG, so a fixed\n // `rng` makes the backoff target replay bit-for-bit.\n const delayMs = nextDelayMs(retry, policy, rngBranded);\n return [{ ...failed, phase: \"polling\", nextAtMs: at + delayMs }, []];\n }\n\n function subs(state: PollerState<R>): readonly Sub[] {\n // Arm the tick deadline only while live. `phase` is the authority (so a\n // mis-passed `untilHeld` into `tickResult` cannot resurrect a finished\n // poller); `nextAtMs` is the absolute target `../deadline` translates to a\n // remaining delay at subscribe time — a resumed poller re-arms the exact\n // persisted moment.\n if (state.phase !== \"polling\" || state.nextAtMs === null) return [];\n // The Sub id ROTATES with the target. A one-shot deadline timer is\n // exhausted once it fires, and the substrate's reconcile pass keys identity\n // by `id` alone — a fixed id would leave the fired timer in the registry and\n // NEVER re-arm for the next cadence (cadence dies after one tick). Folding\n // `nextAtMs` into the id makes each successive target a distinct identity:\n // the reconcile pass retires the spent timer (old id absent from the desired\n // set) and arms the next one (new id). A resumed poller re-arms the SAME id\n // from its persisted `nextAtMs`, so eviction is idempotent.\n return [deadlineSub(pollerSubId(state.nextAtMs), state.nextAtMs)];\n }\n\n return { init, start, tick, tickResult, tickErr, subs };\n}\n"]}
@@ -0,0 +1,130 @@
1
+ import { C as Cmd, S as Sub, M as Machine } from '../core-BoB6t7dB.js';
2
+
3
+ /**
4
+ * @demlik/tea/prediction — the client-prediction ack primitive (epic #186, facet 2).
5
+ *
6
+ * The contract the Gambetta/Valve authoritative-server netcode loop needs:
7
+ * **command-seq-in → last-applied-seq-out.** A client tags each predicted
8
+ * command/`Msg` with a monotonic `seq`; the authoritative side replies with the
9
+ * `lastAppliedSeq` it has processed. `partitionByAck` then splits the client's
10
+ * pending buffer into the inputs the server has ACKED (`seq <= lastAppliedSeq`)
11
+ * and those still PENDING (`seq > lastAppliedSeq`) — the un-acked tail the
12
+ * reconciliation pass (#214) replays over the authoritative snapshot via
13
+ * `foldMsgs` (#211, `@demlik/tea/pure`).
14
+ *
15
+ * Deliberately **Model-shape-agnostic** (ADR 0006): the ack is a standalone
16
+ * value, NOT a field this module requires on the consumer's `State`. It
17
+ * generalizes the `lastAckedSeq`-on-the-Model + `seq`-threaded-through-the-Msg
18
+ * pattern vortex hand-rolled (epic #186). It is a LEAF module — pure and
19
+ * runtime-free, importing nothing from the tea core or host, so it can never
20
+ * drag `run`/the host into a client bundle.
21
+ *
22
+ * This is the read/ack side of the primitive; `tagSeq`/`nextSeq` are the write
23
+ * side, letting a client mint monotonic seqs from its pending buffer alone —
24
+ * again with no counter field on the Model.
25
+ *
26
+ * `reconcile` (the read side's payoff, #214) composes this ack partition with
27
+ * the pure fold seam `foldMsgs` (#211) to perform the whole Gambetta/Valve
28
+ * reconciliation step in one call. The `foldMsgs` import is the ONLY dependency
29
+ * this leaf takes, and it reaches into the pure-core leaf (`../pure/core`) — NOT
30
+ * the runtime root — so the module stays runtime-free and the `@demlik/tea/pure`
31
+ * import-graph guard (ADR 0006, #213) still holds.
32
+ */
33
+
34
+ /**
35
+ * A monotonic, non-negative sequence number tagging one client-predicted
36
+ * command. Monotonicity is the client's contract (mint successive seqs with
37
+ * `nextSeq`); this module only ever *compares* seqs.
38
+ */
39
+ type Seq = number;
40
+ /** A command (or `Msg`) tagged with the `seq` the client assigned it. */
41
+ interface SeqTagged<T> {
42
+ readonly seq: Seq;
43
+ readonly value: T;
44
+ }
45
+ /** Tag a command/`Msg` with its sequence number. */
46
+ declare const tagSeq: <T>(seq: Seq, value: T) => SeqTagged<T>;
47
+ /**
48
+ * The authoritative side's acknowledgement: the highest `seq` it has applied.
49
+ * Everything with `seq <= lastAppliedSeq` is settled; everything above is still
50
+ * in flight. This *is* the whole "ack from the authoritative side" — a value
51
+ * the consumer carries however it likes (a wire field, a message payload),
52
+ * never a shape this module imposes on the Model.
53
+ */
54
+ interface Ack {
55
+ readonly lastAppliedSeq: Seq;
56
+ }
57
+ /** Construct an `Ack` for a last-applied sequence number. */
58
+ declare const ack: (lastAppliedSeq: Seq) => Ack;
59
+ /**
60
+ * The "nothing applied yet" cursor — the ack value for a server that has applied
61
+ * no client input at all. Because `nextSeq` mints **0-based** seqs and
62
+ * `partitionByAck` acks **inclusively** (`seq <= lastAppliedSeq`), the pre-first
63
+ * cursor must sit strictly *below* seq 0: `lastAppliedSeq = 0` would falsely ack
64
+ * the seq-0 input before the server ever saw it. `-1` is "before any seq", and
65
+ * `partitionByAck(buffer, NO_ACK)` therefore leaves the whole buffer PENDING.
66
+ *
67
+ * This names the boundary value on the seam so no consumer has to re-discover it
68
+ * as a magic number: it is the primitive's own design (0-based mint + inclusive
69
+ * partition) that creates the trap, so it is the primitive's job to name the
70
+ * escape. Pair with {@link initAck} when an {@link Ack} is wanted instead of the
71
+ * bare `Seq`.
72
+ */
73
+ declare const NO_ACK: Seq;
74
+ /**
75
+ * The `Ack` for a server that has applied nothing yet — `ack(NO_ACK)`. The `Ack`
76
+ * counterpart to the {@link NO_ACK} `Seq` sentinel, for a consumer that carries
77
+ * an `Ack` rather than a bare `lastAppliedSeq`.
78
+ */
79
+ declare const initAck: () => Ack;
80
+ /** The result of splitting a seq-tagged buffer against an ack. */
81
+ interface AckPartition<T> {
82
+ /** Inputs the authoritative side has applied (`seq <= lastAppliedSeq`). */
83
+ readonly acked: readonly SeqTagged<T>[];
84
+ /** Inputs still in flight / un-acked (`seq > lastAppliedSeq`) — the replay tail. */
85
+ readonly pending: readonly SeqTagged<T>[];
86
+ }
87
+ /**
88
+ * Partition a buffer of seq-tagged commands into ACKED and PENDING against the
89
+ * authoritative `lastAppliedSeq`. Pure and total: every item lands in exactly
90
+ * one bucket, input order is preserved within each bucket, and the boundary
91
+ * `seq == lastAppliedSeq` is ACKED (inclusive `<=`). Routing is by `seq` value,
92
+ * not buffer position, so an out-of-order or duplicate-seq buffer partitions
93
+ * correctly. Accepts either an {@link Ack} or a bare `lastAppliedSeq` number, so
94
+ * a caller passes whichever it holds.
95
+ */
96
+ declare const partitionByAck: <T>(buffer: readonly SeqTagged<T>[], ackOrSeq: Ack | Seq) => AckPartition<T>;
97
+ /**
98
+ * The next sequence number to assign: one past the highest `seq` in the buffer,
99
+ * or `0` for an empty buffer. Lets a client mint monotonic seqs from its pending
100
+ * buffer alone — no counter field on the Model — and is tolerant of an unordered
101
+ * buffer (it takes the max, not the last element).
102
+ */
103
+ declare const nextSeq: <T>(buffer: readonly SeqTagged<T>[]) => Seq;
104
+ /**
105
+ * The client prediction/reconciliation helper — the Gambetta/Valve
106
+ * authoritative-server loop's reconcile step, generalized (#214, epic #186).
107
+ *
108
+ * Given the latest `authoritativeState` from the server, the server's
109
+ * `lastAppliedSeq` ack, and the client's `pending` buffer of seq-tagged inputs,
110
+ * it returns the corrected predicted state: drop the inputs the server has
111
+ * already applied (`seq <= lastAppliedSeq`) and replay ONLY the un-acked tail
112
+ * (`seq > lastAppliedSeq`) over the authoritative snapshot.
113
+ *
114
+ * It is **composed, not re-derived** (ADR 0006): the drop is {@link partitionByAck}
115
+ * (the ack primitive, #212) and the replay is `foldMsgs` (the pure fold seam,
116
+ * #211) — there is no second copy of the partition logic and no second copy of
117
+ * the fold. `lastAppliedSeq` accepts either an {@link Ack} or a bare `Seq`, so a
118
+ * caller passes whichever it holds, matching `partitionByAck`.
119
+ *
120
+ * Pure and host-agnostic: it takes the `machine` (which `foldMsgs` needs to
121
+ * dispatch `update`) but touches no `Store`, `interpret`, or subscription, and
122
+ * imports nothing from the runtime — so it ships on `@demlik/tea/prediction`
123
+ * and the client-safe `@demlik/tea/pure` umbrella without dragging `run` into a
124
+ * client bundle.
125
+ */
126
+ declare function reconcile<S, M extends {
127
+ type: string;
128
+ }, C extends Cmd, U extends Sub, Ctx>(machine: Machine<S, M, C, U, Ctx>, authoritativeState: S, lastAppliedSeq: Ack | Seq, pending: readonly SeqTagged<M>[]): S;
129
+
130
+ export { type Ack, type AckPartition, NO_ACK, type Seq, type SeqTagged, ack, initAck, nextSeq, partitionByAck, reconcile, tagSeq };
@@ -0,0 +1,4 @@
1
+ export { NO_ACK, ack, initAck, nextSeq, partitionByAck, reconcile, tagSeq } from '../chunk-SQTCS44W.js';
2
+ import '../chunk-QLA76GIT.js';
3
+ //# sourceMappingURL=index.js.map
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The closed set of cross-module Msg/Cmd discriminant strings. Declared `as
3
+ * const` so each entry is usable as both a value (construction / reducer keys /
4
+ * switch labels) and, via `typeof MsgType.X`, a literal type (`readonly type:`
5
+ * fields). The single source of truth for issue #60.
6
+ */
7
+ declare const MsgType: {
8
+ /** The "run the port for `key`" effect Cmd resilient-call emits. */
9
+ readonly ResilientRun: "resilient_run";
10
+ /** PRIVATE success settle Msg (read only by `agentEvents`, #47). */
11
+ readonly ResilientOk: "resilient_ok";
12
+ /** Failure settle Msg — routed to the `fail` verb (back off via retry). */
13
+ readonly ResilientErr: "resilient_err";
14
+ /** Begin a run. */
15
+ readonly AgentStart: "agent_start";
16
+ /** PRIVATE tool-fan-out success settle Msg (read only by `agentEvents`, #47). */
17
+ readonly AgentToolOk: "agent_tool_ok";
18
+ /** Tool-fan-out failure settle Msg. */
19
+ readonly AgentToolErr: "agent_tool_err";
20
+ /**
21
+ * Re-fire the one outstanding effect on rehydrate (the do↔agent seam). Fired
22
+ * by `do/host`'s `autoBoot` via the typed `agentBootMsg` constructor, matched
23
+ * by the agent reducer — see `AgentBootPort` in `../agent`.
24
+ */
25
+ readonly AgentBoot: "agent_boot";
26
+ /**
27
+ * The "summarize the oldest N turns" effect Cmd the agent emits when its
28
+ * `CompactionPolicy` asks to compact before a brain call. A DEDICATED
29
+ * round-trip (design B1), distinct from the brain call's `resilient_run`, so
30
+ * compaction is its own honest `Awaiting` state — see `../agent`.
31
+ */
32
+ readonly CompactRun: "compact_run";
33
+ /** Compaction round-trip resolved — carries the summary text the fold-back folds. */
34
+ readonly CompactOk: "compact_ok";
35
+ /** Compaction round-trip failed (exhausted retry) — surfaced as data, not a stall. */
36
+ readonly CompactErr: "compact_err";
37
+ /** Refresh port resolved — carries the fresh `Token`. */
38
+ readonly TokenRefreshed: "token_refreshed";
39
+ /** Refresh port rejected — carries the rejection (errors are data). */
40
+ readonly TokenRefreshFailed: "token_refresh_failed";
41
+ /** The 401 the consumer dispatches when a guarded call comes back unauthorized. */
42
+ readonly Unauthorized: "unauthorized";
43
+ };
44
+
45
+ export { MsgType as M };
@@ -0,0 +1,2 @@
1
+ export { Ack, AckPartition, NO_ACK, Seq, SeqTagged, ack, initAck, nextSeq, partitionByAck, reconcile, tagSeq } from '../prediction/index.js';
2
+ export { C as Cmd, b as ContextFree, I as Interpret, M as Machine, N as NoCtx, P as Port, c as PortEmitter, R as Reducer, S as Sub, a as SubId, d as Subscribe, e as SyncReturn, T as Transitions, U as UpdateForm, g as detectUpdateForm, h as foldMsgs, i as formOf, s as subId } from '../core-BoB6t7dB.js';
@@ -0,0 +1,4 @@
1
+ export { NO_ACK, ack, initAck, nextSeq, partitionByAck, reconcile, tagSeq } from '../chunk-SQTCS44W.js';
2
+ export { Cmd, detectUpdateForm, foldMsgs, formOf, subId } from '../chunk-QLA76GIT.js';
3
+ //# sourceMappingURL=index.js.map
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}