@demlik/tea 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (373) hide show
  1. package/README.md +11 -9
  2. package/dist/agent/index.d.ts +638 -45
  3. package/dist/agent/index.js +9 -19
  4. package/dist/{chunk-RH5EFRVJ.js → chunk-22POWGFA.js} +31 -5
  5. package/dist/chunk-22POWGFA.js.map +1 -0
  6. package/dist/{chunk-DMYGQB4O.js → chunk-3B5X7SBB.js} +23 -6
  7. package/dist/chunk-3B5X7SBB.js.map +1 -0
  8. package/dist/chunk-66PY5WYF.js +1816 -0
  9. package/dist/chunk-66PY5WYF.js.map +1 -0
  10. package/dist/{chunk-25LBDCBC.js → chunk-6IQPODUF.js} +2 -2
  11. package/dist/{chunk-25LBDCBC.js.map → chunk-6IQPODUF.js.map} +1 -1
  12. package/dist/chunk-AYS5N6WY.js +505 -0
  13. package/dist/chunk-AYS5N6WY.js.map +1 -0
  14. package/dist/{chunk-EWJP3YYP.js → chunk-HWVW4AXC.js} +3 -3
  15. package/dist/chunk-HWVW4AXC.js.map +1 -0
  16. package/dist/{chunk-O4MVQTEJ.js → chunk-L3TND7LA.js} +20 -16
  17. package/dist/chunk-L3TND7LA.js.map +1 -0
  18. package/dist/{chunk-J6527VRQ.js → chunk-LFZUK6J2.js} +3 -3
  19. package/dist/chunk-LFZUK6J2.js.map +1 -0
  20. package/dist/{chunk-D736QUGF.js → chunk-MYXFNWAJ.js} +4 -5
  21. package/dist/chunk-MYXFNWAJ.js.map +1 -0
  22. package/dist/{chunk-K575PT4S.js → chunk-PR6TB3BL.js} +15 -4
  23. package/dist/chunk-PR6TB3BL.js.map +1 -0
  24. package/dist/{chunk-YUXP55T2.js → chunk-Q5CQTBFA.js} +57 -7
  25. package/dist/chunk-Q5CQTBFA.js.map +1 -0
  26. package/dist/{batch-window/index.js → chunk-QPEVCJ5Q.js} +4 -8
  27. package/dist/chunk-QPEVCJ5Q.js.map +1 -0
  28. package/dist/chunk-TCLBCERH.js +1480 -0
  29. package/dist/chunk-TCLBCERH.js.map +1 -0
  30. package/dist/{chunk-FV3Z5MBE.js → chunk-TXN2KFHF.js} +14 -4
  31. package/dist/chunk-TXN2KFHF.js.map +1 -0
  32. package/dist/{chunk-MUUROGIH.js → chunk-V46HSVCK.js} +62 -12
  33. package/dist/chunk-V46HSVCK.js.map +1 -0
  34. package/dist/{chunk-Q2UM7BXU.js → chunk-VBDU4JL6.js} +2 -2
  35. package/dist/chunk-VBDU4JL6.js.map +1 -0
  36. package/dist/{chunk-ZVJZ72HY.js → chunk-VTQI2NHP.js} +25 -11
  37. package/dist/chunk-VTQI2NHP.js.map +1 -0
  38. package/dist/{core-Co0LWkIA.d.ts → core-DnzqWy6u.d.ts} +72 -23
  39. package/dist/devtools/index.d.ts +2 -2
  40. package/dist/devtools/index.js +207 -2
  41. package/dist/devtools/index.js.map +1 -1
  42. package/dist/do/index.d.ts +43 -29
  43. package/dist/do/index.js +36 -25
  44. package/dist/do/index.js.map +1 -1
  45. package/dist/{durable-effects-C6uwzjb5.d.ts → durable-effects-CWzA-o2B.d.ts} +2 -2
  46. package/dist/extension/index.d.ts +300 -3
  47. package/dist/extension/index.js +596 -5
  48. package/dist/extension/index.js.map +1 -1
  49. package/dist/flow/index.d.ts +1284 -0
  50. package/dist/flow/index.js +732 -0
  51. package/dist/flow/index.js.map +1 -0
  52. package/dist/idempotency/index.d.ts +90 -116
  53. package/dist/idempotency/index.js +4 -1
  54. package/dist/index-Bc40JWVl.d.ts +131 -0
  55. package/dist/{batch-window/index.d.ts → index-C2DwkjlP.d.ts} +7 -12
  56. package/dist/index-CA_7I5g9.d.ts +806 -0
  57. package/dist/index-CSyMYDAB.d.ts +69 -0
  58. package/dist/{fan-out/index.d.ts → index-CuNO9m8i.d.ts} +7 -9
  59. package/dist/{debounce/index.d.ts → index-DHSUmQTR.d.ts} +7 -7
  60. package/dist/{recorder/index.d.ts → index-DrIRb_Ch.d.ts} +4 -7
  61. package/dist/index-HSYgG2Yt.d.ts +380 -0
  62. package/dist/{monitored-run/index.d.ts → index-KXqTRJyb.d.ts} +37 -13
  63. package/dist/{snapshot/index.d.ts → index-ZCMhnVKN.d.ts} +15 -78
  64. package/dist/index.d.ts +771 -6
  65. package/dist/index.js +1 -2
  66. package/dist/jev/index.d.ts +646 -0
  67. package/dist/jev/index.js +485 -0
  68. package/dist/jev/index.js.map +1 -0
  69. package/dist/machine-DGlPWCWI.d.ts +1781 -0
  70. package/dist/machine-viz/index.d.ts +1 -1
  71. package/dist/machine-viz/index.js +12 -5
  72. package/dist/machine-viz/index.js.map +1 -1
  73. package/dist/mem/index.d.ts +18 -3
  74. package/dist/mem/index.js +22 -2
  75. package/dist/mem/index.js.map +1 -1
  76. package/dist/node/index.d.ts +104 -6
  77. package/dist/node/index.js +132 -9
  78. package/dist/node/index.js.map +1 -1
  79. package/dist/{paginator → paginate}/index.d.ts +140 -6
  80. package/dist/paginate/index.js +7 -0
  81. package/dist/parity/index.d.ts +4 -10
  82. package/dist/parity/index.js +4 -7
  83. package/dist/parity/index.js.map +1 -1
  84. package/dist/pbt/index.d.ts +247 -5
  85. package/dist/pbt/index.js +124 -5
  86. package/dist/pbt/index.js.map +1 -1
  87. package/dist/{trace-replay → persistence}/index.d.ts +7 -5
  88. package/dist/persistence/index.js +6 -0
  89. package/dist/{protocol-B5YmJr0d.d.ts → protocol-D8VbH-VH.d.ts} +10 -1
  90. package/dist/react/index.d.ts +2 -2
  91. package/dist/react/index.js +1 -2
  92. package/dist/react/index.js.map +1 -1
  93. package/dist/resilience/index.d.ts +744 -0
  94. package/dist/resilience/index.js +657 -0
  95. package/dist/resilience/index.js.map +1 -0
  96. package/dist/retry-backoff/index.d.ts +3 -2
  97. package/dist/retry-backoff/index.js +1 -1
  98. package/dist/runtime-types-CMF13BBh.d.ts +998 -0
  99. package/dist/testing/index.d.ts +147 -3
  100. package/dist/testing/index.js +95 -3
  101. package/dist/testing/index.js.map +1 -1
  102. package/dist/{throttled-input → timing}/index.d.ts +143 -12
  103. package/dist/{throttled-input → timing}/index.js +57 -12
  104. package/dist/timing/index.js.map +1 -0
  105. package/dist/{types-XS_miRnB.d.ts → types-BseNPIhr.d.ts} +1 -1
  106. package/dist/work-queue/index.d.ts +130 -54
  107. package/dist/work-queue/index.js +3 -2
  108. package/dist/work-queue/index.js.map +1 -1
  109. package/package.json +45 -218
  110. package/dist/authed-call/index.d.ts +0 -120
  111. package/dist/authed-call/index.js +0 -134
  112. package/dist/authed-call/index.js.map +0 -1
  113. package/dist/await-terminal/index.d.ts +0 -101
  114. package/dist/await-terminal/index.js +0 -76
  115. package/dist/await-terminal/index.js.map +0 -1
  116. package/dist/batch-window/index.js.map +0 -1
  117. package/dist/cache/index.d.ts +0 -152
  118. package/dist/cache/index.js +0 -8
  119. package/dist/chart/index.d.ts +0 -218
  120. package/dist/chart/index.js +0 -16
  121. package/dist/chart/index.js.map +0 -1
  122. package/dist/chart/inspect/index.d.ts +0 -86
  123. package/dist/chart/inspect/index.js +0 -5
  124. package/dist/chart/inspect/react.d.ts +0 -113
  125. package/dist/chart/inspect/react.js +0 -11
  126. package/dist/chart/inspect/react.js.map +0 -1
  127. package/dist/chart/inspect/styles.css +0 -286
  128. package/dist/chart/lane/index.d.ts +0 -362
  129. package/dist/chart/lane/index.js +0 -9
  130. package/dist/chart/lane/index.js.map +0 -1
  131. package/dist/chart/lane/react.d.ts +0 -84
  132. package/dist/chart/lane/react.js +0 -559
  133. package/dist/chart/lane/react.js.map +0 -1
  134. package/dist/chart/lane/server.d.ts +0 -148
  135. package/dist/chart/lane/server.js +0 -209
  136. package/dist/chart/lane/server.js.map +0 -1
  137. package/dist/chart/lane/styles.css +0 -498
  138. package/dist/chart/lane/viewer/assets/abnfDiagram-N423BO3Z-C729LI31.js +0 -1
  139. package/dist/chart/lane/viewer/assets/architectureDiagram-T3A2C74G-C729LI31.js +0 -1
  140. package/dist/chart/lane/viewer/assets/blockDiagram-VBNYF7ZC-C729LI31.js +0 -1
  141. package/dist/chart/lane/viewer/assets/c4Diagram-5PPSVZJV-C729LI31.js +0 -1
  142. package/dist/chart/lane/viewer/assets/chunk-2GRJ4B5K-HdDf8SI3.js +0 -1
  143. package/dist/chart/lane/viewer/assets/chunk-5RXB4S5H-CcNtpUd7.js +0 -231
  144. package/dist/chart/lane/viewer/assets/chunk-5VM5RSS4-BP4fRS_5.js +0 -15
  145. package/dist/chart/lane/viewer/assets/chunk-KBJHAD2P-DZOw7xU6.js +0 -1
  146. package/dist/chart/lane/viewer/assets/chunk-NSK5VX7P-CBCyTZMs.js +0 -128
  147. package/dist/chart/lane/viewer/assets/classDiagram-JCYQIIEL-C729LI31.js +0 -1
  148. package/dist/chart/lane/viewer/assets/classDiagram-v2-OCEON4UE-D4uAiFr8.js +0 -206
  149. package/dist/chart/lane/viewer/assets/cose-bilkent-JH36ORCC-DOwCrGXH.js +0 -1
  150. package/dist/chart/lane/viewer/assets/cynefinDiagram-MW4NZA55-C729LI31.js +0 -1
  151. package/dist/chart/lane/viewer/assets/dagre-K_PewWfS.js +0 -1
  152. package/dist/chart/lane/viewer/assets/dagre-VZM6K2ZE-831qZcA8.js +0 -4
  153. package/dist/chart/lane/viewer/assets/diagram-7IWD3JNH-C729LI31.js +0 -1
  154. package/dist/chart/lane/viewer/assets/diagram-B4RE2ZJO-C729LI31.js +0 -1
  155. package/dist/chart/lane/viewer/assets/diagram-LBJQPF4R-C729LI31.js +0 -1
  156. package/dist/chart/lane/viewer/assets/diagram-Q27KOJAE-C729LI31.js +0 -1
  157. package/dist/chart/lane/viewer/assets/diagram-UB23O5K3-C729LI31.js +0 -1
  158. package/dist/chart/lane/viewer/assets/ebnfDiagram-BXEA7PRR-C729LI31.js +0 -1
  159. package/dist/chart/lane/viewer/assets/erDiagram-JOGREHBK-C729LI31.js +0 -1
  160. package/dist/chart/lane/viewer/assets/flowDiagram-UKHOOZJN-C729LI31.js +0 -1
  161. package/dist/chart/lane/viewer/assets/ganttDiagram-PKOTCBZU-C729LI31.js +0 -1
  162. package/dist/chart/lane/viewer/assets/gitGraphDiagram-DS77QQ5N-C729LI31.js +0 -1
  163. package/dist/chart/lane/viewer/assets/index-9SkH5NA3.css +0 -1
  164. package/dist/chart/lane/viewer/assets/index-Bgt4sXpW.js +0 -195
  165. package/dist/chart/lane/viewer/assets/infoDiagram-6WML65LV-C729LI31.js +0 -1
  166. package/dist/chart/lane/viewer/assets/ishikawaDiagram-WSZJBQD7-C729LI31.js +0 -1
  167. package/dist/chart/lane/viewer/assets/journeyDiagram-NVQOT4AX-C729LI31.js +0 -1
  168. package/dist/chart/lane/viewer/assets/kanban-definition-27J2QSJJ-G7PaPzy-.js +0 -89
  169. package/dist/chart/lane/viewer/assets/katex-DOwCrGXH.js +0 -1
  170. package/dist/chart/lane/viewer/assets/mindmap-definition-FAOFIHXS-CGj9nrVk.js +0 -96
  171. package/dist/chart/lane/viewer/assets/path-fybaL0A-.js +0 -1
  172. package/dist/chart/lane/viewer/assets/pegDiagram-VL7TDLO6-C729LI31.js +0 -1
  173. package/dist/chart/lane/viewer/assets/pieDiagram-7S7Q4E2Y-C729LI31.js +0 -1
  174. package/dist/chart/lane/viewer/assets/quadrantDiagram-CIZ2JOQS-C729LI31.js +0 -1
  175. package/dist/chart/lane/viewer/assets/railroadDiagram-AXF67PYL-C729LI31.js +0 -1
  176. package/dist/chart/lane/viewer/assets/requirementDiagram-LRYGKXZP-C729LI31.js +0 -1
  177. package/dist/chart/lane/viewer/assets/sankeyDiagram-W5VNT64P-C729LI31.js +0 -1
  178. package/dist/chart/lane/viewer/assets/sequenceDiagram-SI44F4Z6-C729LI31.js +0 -1
  179. package/dist/chart/lane/viewer/assets/stateDiagram-OKZ733FA-B_88AzBS.js +0 -1
  180. package/dist/chart/lane/viewer/assets/stateDiagram-v2-UEYNNEHI-CR5gcgq6.js +0 -1
  181. package/dist/chart/lane/viewer/assets/swimlanes-SLNWSIFB-B4xK-rwg.js +0 -1
  182. package/dist/chart/lane/viewer/assets/swimlanesDiagram-ULZ7WXOC-C729LI31.js +0 -1
  183. package/dist/chart/lane/viewer/assets/timeline-definition-Z64GVDOM-CCv52MfY.js +0 -120
  184. package/dist/chart/lane/viewer/assets/vennDiagram-T6HMQDX7-C729LI31.js +0 -1
  185. package/dist/chart/lane/viewer/assets/wardleyDiagram-T6FBY63Y-C729LI31.js +0 -1
  186. package/dist/chart/lane/viewer/assets/xychartDiagram-ELKLHX3M-C729LI31.js +0 -1
  187. package/dist/chart/lane/viewer/index.html +0 -15
  188. package/dist/chart/report/index.d.ts +0 -117
  189. package/dist/chart/report/index.js +0 -32
  190. package/dist/chart/report/index.js.map +0 -1
  191. package/dist/chunk-2YFLOCWX.js +0 -15
  192. package/dist/chunk-2YFLOCWX.js.map +0 -1
  193. package/dist/chunk-3I52QZZ5.js +0 -519
  194. package/dist/chunk-3I52QZZ5.js.map +0 -1
  195. package/dist/chunk-3ZAFFRRB.js +0 -457
  196. package/dist/chunk-3ZAFFRRB.js.map +0 -1
  197. package/dist/chunk-4EQZ76JC.js +0 -33
  198. package/dist/chunk-4EQZ76JC.js.map +0 -1
  199. package/dist/chunk-4KLG43GY.js +0 -223
  200. package/dist/chunk-4KLG43GY.js.map +0 -1
  201. package/dist/chunk-7FRZ2CT2.js +0 -424
  202. package/dist/chunk-7FRZ2CT2.js.map +0 -1
  203. package/dist/chunk-AZZLE7XR.js +0 -62
  204. package/dist/chunk-AZZLE7XR.js.map +0 -1
  205. package/dist/chunk-BYQUGDD3.js +0 -16
  206. package/dist/chunk-BYQUGDD3.js.map +0 -1
  207. package/dist/chunk-CIQG5L42.js +0 -685
  208. package/dist/chunk-CIQG5L42.js.map +0 -1
  209. package/dist/chunk-CLM6ERNO.js +0 -61
  210. package/dist/chunk-CLM6ERNO.js.map +0 -1
  211. package/dist/chunk-COS5MSTO.js +0 -88
  212. package/dist/chunk-COS5MSTO.js.map +0 -1
  213. package/dist/chunk-CRPZJVD2.js +0 -71
  214. package/dist/chunk-CRPZJVD2.js.map +0 -1
  215. package/dist/chunk-D62FDQKE.js +0 -53
  216. package/dist/chunk-D62FDQKE.js.map +0 -1
  217. package/dist/chunk-D736QUGF.js.map +0 -1
  218. package/dist/chunk-DGE4PV4A.js +0 -26
  219. package/dist/chunk-DGE4PV4A.js.map +0 -1
  220. package/dist/chunk-DMYGQB4O.js.map +0 -1
  221. package/dist/chunk-EWJP3YYP.js.map +0 -1
  222. package/dist/chunk-FV3Z5MBE.js.map +0 -1
  223. package/dist/chunk-HCEVHFJ7.js +0 -54
  224. package/dist/chunk-HCEVHFJ7.js.map +0 -1
  225. package/dist/chunk-J6527VRQ.js.map +0 -1
  226. package/dist/chunk-JELSZN25.js +0 -47
  227. package/dist/chunk-JELSZN25.js.map +0 -1
  228. package/dist/chunk-K575PT4S.js.map +0 -1
  229. package/dist/chunk-L57ZOT5K.js +0 -184
  230. package/dist/chunk-L57ZOT5K.js.map +0 -1
  231. package/dist/chunk-MU6MCIAC.js +0 -15
  232. package/dist/chunk-MU6MCIAC.js.map +0 -1
  233. package/dist/chunk-MUUROGIH.js.map +0 -1
  234. package/dist/chunk-NR7BU22N.js +0 -758
  235. package/dist/chunk-NR7BU22N.js.map +0 -1
  236. package/dist/chunk-O4MVQTEJ.js.map +0 -1
  237. package/dist/chunk-OOYD44JI.js +0 -317
  238. package/dist/chunk-OOYD44JI.js.map +0 -1
  239. package/dist/chunk-Q2UM7BXU.js.map +0 -1
  240. package/dist/chunk-RH5EFRVJ.js.map +0 -1
  241. package/dist/chunk-S2EGRPCM.js +0 -47
  242. package/dist/chunk-S2EGRPCM.js.map +0 -1
  243. package/dist/chunk-SINXE47Y.js +0 -55
  244. package/dist/chunk-SINXE47Y.js.map +0 -1
  245. package/dist/chunk-UF4MZSLT.js +0 -53
  246. package/dist/chunk-UF4MZSLT.js.map +0 -1
  247. package/dist/chunk-UOC2PL46.js +0 -13
  248. package/dist/chunk-UOC2PL46.js.map +0 -1
  249. package/dist/chunk-VYFAZN3F.js +0 -372
  250. package/dist/chunk-VYFAZN3F.js.map +0 -1
  251. package/dist/chunk-WRAA7GVZ.js +0 -209
  252. package/dist/chunk-WRAA7GVZ.js.map +0 -1
  253. package/dist/chunk-XGELA764.js +0 -163
  254. package/dist/chunk-XGELA764.js.map +0 -1
  255. package/dist/chunk-YUXP55T2.js.map +0 -1
  256. package/dist/chunk-YWIKDKHY.js +0 -307
  257. package/dist/chunk-YWIKDKHY.js.map +0 -1
  258. package/dist/chunk-ZJ5AOYBH.js +0 -8
  259. package/dist/chunk-ZJ5AOYBH.js.map +0 -1
  260. package/dist/chunk-ZSUUOEPL.js +0 -731
  261. package/dist/chunk-ZSUUOEPL.js.map +0 -1
  262. package/dist/chunk-ZVJZ72HY.js.map +0 -1
  263. package/dist/circuit-breaker/index.d.ts +0 -197
  264. package/dist/circuit-breaker/index.js +0 -3
  265. package/dist/circuit-breaker/index.js.map +0 -1
  266. package/dist/deadline/index.d.ts +0 -199
  267. package/dist/deadline/index.js +0 -6
  268. package/dist/deadline/index.js.map +0 -1
  269. package/dist/debounce/index.js +0 -3
  270. package/dist/debounce/index.js.map +0 -1
  271. package/dist/extension/react.d.ts +0 -121
  272. package/dist/extension/react.js +0 -75
  273. package/dist/extension/react.js.map +0 -1
  274. package/dist/extension/subs/index.d.ts +0 -45
  275. package/dist/extension/subs/index.js +0 -80
  276. package/dist/extension/subs/index.js.map +0 -1
  277. package/dist/extension/test-utils.d.ts +0 -128
  278. package/dist/extension/test-utils.js +0 -277
  279. package/dist/extension/test-utils.js.map +0 -1
  280. package/dist/fan-out/index.js +0 -5
  281. package/dist/fan-out/index.js.map +0 -1
  282. package/dist/fold-BJPdjc9w.d.ts +0 -435
  283. package/dist/graph-B3L5e0N1.d.ts +0 -923
  284. package/dist/idempotency/adapter.d.ts +0 -62
  285. package/dist/idempotency/adapter.js +0 -4
  286. package/dist/idempotency/adapter.js.map +0 -1
  287. package/dist/idempotent-intake/index.d.ts +0 -188
  288. package/dist/idempotent-intake/index.js +0 -7
  289. package/dist/idempotent-intake/index.js.map +0 -1
  290. package/dist/journal/index.d.ts +0 -373
  291. package/dist/journal/index.js +0 -3
  292. package/dist/journal/index.js.map +0 -1
  293. package/dist/llm-call/index.d.ts +0 -311
  294. package/dist/llm-call/index.js +0 -16
  295. package/dist/llm-call/index.js.map +0 -1
  296. package/dist/machine-BtFWQmmq.d.ts +0 -804
  297. package/dist/monitored-run/index.js +0 -10
  298. package/dist/monitored-run/index.js.map +0 -1
  299. package/dist/paginated-walk/index.d.ts +0 -235
  300. package/dist/paginated-walk/index.js +0 -16
  301. package/dist/paginated-walk/index.js.map +0 -1
  302. package/dist/paginator/index.js +0 -3
  303. package/dist/paginator/index.js.map +0 -1
  304. package/dist/pbt/arbitraries/index.d.ts +0 -136
  305. package/dist/pbt/arbitraries/index.js +0 -3
  306. package/dist/pbt/arbitraries/index.js.map +0 -1
  307. package/dist/pbt/runners/index.d.ts +0 -118
  308. package/dist/pbt/runners/index.js +0 -5
  309. package/dist/pbt/runners/index.js.map +0 -1
  310. package/dist/poller/index.d.ts +0 -352
  311. package/dist/poller/index.js +0 -115
  312. package/dist/poller/index.js.map +0 -1
  313. package/dist/prediction/index.d.ts +0 -131
  314. package/dist/prediction/index.js +0 -4
  315. package/dist/prediction/index.js.map +0 -1
  316. package/dist/pure/index.d.ts +0 -3
  317. package/dist/pure/index.js +0 -4
  318. package/dist/pure/index.js.map +0 -1
  319. package/dist/rate-limit/index.d.ts +0 -115
  320. package/dist/rate-limit/index.js +0 -3
  321. package/dist/rate-limit/index.js.map +0 -1
  322. package/dist/reconciler/index.d.ts +0 -193
  323. package/dist/reconciler/index.js +0 -164
  324. package/dist/reconciler/index.js.map +0 -1
  325. package/dist/recorder/index.js +0 -3
  326. package/dist/recorder/index.js.map +0 -1
  327. package/dist/reducer-4GNR6b8B.d.ts +0 -447
  328. package/dist/resilient-call/index.d.ts +0 -320
  329. package/dist/resilient-call/index.js +0 -14
  330. package/dist/resilient-call/index.js.map +0 -1
  331. package/dist/retry-to-success/index.d.ts +0 -109
  332. package/dist/retry-to-success/index.js +0 -37
  333. package/dist/retry-to-success/index.js.map +0 -1
  334. package/dist/run-pAcmBpah.d.ts +0 -713
  335. package/dist/runtime-types-CNLmhWif.d.ts +0 -517
  336. package/dist/saga/index.d.ts +0 -241
  337. package/dist/saga/index.js +0 -150
  338. package/dist/saga/index.js.map +0 -1
  339. package/dist/samples-B3My5oh2.d.ts +0 -59
  340. package/dist/snapshot/index.js +0 -5
  341. package/dist/snapshot/index.js.map +0 -1
  342. package/dist/subs/index.d.ts +0 -472
  343. package/dist/subs/index.js +0 -7
  344. package/dist/subs/index.js.map +0 -1
  345. package/dist/throttle/index.d.ts +0 -134
  346. package/dist/throttle/index.js +0 -3
  347. package/dist/throttle/index.js.map +0 -1
  348. package/dist/throttled-input/index.js.map +0 -1
  349. package/dist/token-refresh/index.d.ts +0 -216
  350. package/dist/token-refresh/index.js +0 -6
  351. package/dist/token-refresh/index.js.map +0 -1
  352. package/dist/trace-replay/index.js +0 -6
  353. package/dist/trace-replay/index.js.map +0 -1
  354. package/dist/with-deadline/index.d.ts +0 -197
  355. package/dist/with-deadline/index.js +0 -111
  356. package/dist/with-deadline/index.js.map +0 -1
  357. package/dist/with-resilience/index.d.ts +0 -225
  358. package/dist/with-resilience/index.js +0 -240
  359. package/dist/with-resilience/index.js.map +0 -1
  360. package/dist/with-telemetry/index.d.ts +0 -150
  361. package/dist/with-telemetry/index.js +0 -70
  362. package/dist/with-telemetry/index.js.map +0 -1
  363. package/dist/work-queue/adapter.d.ts +0 -84
  364. package/dist/work-queue/adapter.js +0 -4
  365. package/dist/work-queue/adapter.js.map +0 -1
  366. package/dist/work-queue/ops.d.ts +0 -71
  367. package/dist/work-queue/ops.js +0 -3
  368. package/dist/work-queue/ops.js.map +0 -1
  369. package/dist/workflow/index.d.ts +0 -485
  370. package/dist/workflow/index.js +0 -244
  371. package/dist/workflow/index.js.map +0 -1
  372. /package/dist/{cache → paginate}/index.js.map +0 -0
  373. /package/dist/{chart/inspect → persistence}/index.js.map +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/protocol.ts","../src/internal/resilience/circuit-breaker/index.ts","../src/internal/resilience/rate-limit/index.ts","../src/internal/resilience/resilient-call/index.ts"],"names":[],"mappings":"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","file":"chunk-AYS5N6WY.js","sourcesContent":["// ===========================================================================\n// Protocol — the ONE typed source of truth for the cross-module Msg/Cmd\n// discriminant strings (issue #60).\n// ===========================================================================\n//\n// The resilient/agent/auth plumbing speaks a small fixed vocabulary of\n// discriminant strings on `{ type: ... }` Msgs and Cmds. Before this module\n// those literals were retyped inline across ~8 modules — `resilient_ok` alone\n// appeared as a hand-written `\"resilient_ok\"` in resilient-call, with-resilience,\n// llm-call, agent, do/host, paginated-walk, reconciler. Adding a variant or\n// renaming one was shotgun surgery with no compiler to catch a missed site.\n//\n// `MsgType` is that vocabulary, declared ONCE `as const`. Each entry is BOTH:\n// - a value — `MsgType.ResilientOk` is the string `\"resilient_ok\"`, used at\n// construction sites (`{ type: MsgType.ResilientOk, ... }`), reducer keys\n// (`[MsgType.ResilientOk]: ...`), and switch labels (`case MsgType.ResilientOk:`).\n// - a type — `typeof MsgType.ResilientOk` is the literal type `\"resilient_ok\"`,\n// used in the `readonly type:` field of every Msg/Cmd interface.\n//\n// Because the value and the type both derive from the single `as const`\n// declaration, a new variant or a rename is a ONE-LINE change here that\n// propagates with full exhaustiveness — not an 8-file edit.\n//\n// This module is BEHAVIOR-PRESERVING: every string is byte-identical to the\n// literal it replaces. It is internal plumbing vocabulary, NOT a public event\n// surface — `resilient_ok` / `agent_tool_ok` stay PRIVATE (the agent's\n// `agentEvents` projector is the ONE place they are read; #47). Centralizing\n// the literal here does NOT re-expose them to consumers.\n\n/**\n * The closed set of cross-module Msg/Cmd discriminant strings. Declared `as\n * const` so each entry is usable as both a value (construction / reducer keys /\n * switch labels) and, via `typeof MsgType.X`, a literal type (`readonly type:`\n * fields). The single source of truth for issue #60.\n */\nexport const MsgType = {\n // --- resilient-call: the run Cmd + its two settle Msgs -------------------\n /** The \"run the port for `key`\" effect Cmd resilient-call emits. */\n ResilientRun: \"resilient_run\",\n /** PRIVATE success settle Msg (read only by `agentEvents`, #47). */\n ResilientOk: \"resilient_ok\",\n /** Failure settle Msg — routed to the `fail` verb (back off via retry). */\n ResilientErr: \"resilient_err\",\n\n // --- agent: the wired machine's own entry-point Msgs ---------------------\n /** Begin a run. */\n AgentStart: \"agent_start\",\n /** PRIVATE tool-fan-out success settle Msg (read only by `agentEvents`, #47). */\n AgentToolOk: \"agent_tool_ok\",\n /** Tool-fan-out failure settle Msg. */\n AgentToolErr: \"agent_tool_err\",\n /**\n * Re-fire the one outstanding effect on rehydrate (the do↔agent seam). Fired\n * by `do/host`'s `autoBoot` via the typed `agentBootMsg` constructor, matched\n * by the agent reducer — see `AgentBootPort` in `../agent`.\n */\n AgentBoot: \"agent_boot\",\n /**\n * Stop the run from outside and settle it `cancelled` — what an `AbortSignal`\n * handed to `driveToDone` / a defined agent's `run` dispatches on abort. A\n * transition, never a throw: the outcome lands in the Model, so a reload reads\n * a run that ended rather than one to resume.\n */\n AgentCancel: \"agent_cancel\",\n\n // --- agent: the context-compaction round-trip (#85) ----------------------\n /**\n * The \"summarize the oldest N turns\" effect Cmd the agent emits when its\n * `CompactionPolicy` asks to compact before a brain call. A DEDICATED\n * round-trip (design B1), distinct from the brain call's `resilient_run`, so\n * compaction is its own honest `Awaiting` state — see `../agent`.\n */\n CompactRun: \"compact_run\",\n /** Compaction round-trip resolved — carries the summary text the fold-back folds. */\n CompactOk: \"compact_ok\",\n /** Compaction round-trip failed (exhausted retry) — surfaced as data, not a stall. */\n CompactErr: \"compact_err\",\n\n // --- token-refresh: the two refresh-result Msgs --------------------------\n /** Refresh port resolved — carries the fresh `Token`. */\n TokenRefreshed: \"token_refreshed\",\n /** Refresh port rejected — carries the rejection (errors are data). */\n TokenRefreshFailed: \"token_refresh_failed\",\n\n // --- authed-call: the 401 trigger ----------------------------------------\n /** The 401 the consumer dispatches when a guarded call comes back unauthorized. */\n Unauthorized: \"unauthorized\",\n} as const;\n\n/** The union of every discriminant string in the protocol. */\nexport type MsgTypeValue = (typeof MsgType)[keyof typeof MsgType];\n","/**\n * @packageDocumentation\n * internal/resilience/circuit-breaker — per-target failure tracking as pure state + ops.\n *\n * Same shape as `../../work-queue/ops.ts`, `../rate-limit/`, and\n * `../../idempotency/`: a state type plus pure transition functions.\n * Host-agnostic by construction — no `Date.now()`, no timers, no I/O. The\n * caller injects the clock (`nowMs`) and wires the result into its own reducer\n * / interpret. This keeps the breaker inside TEA's invariant 2 (transitions are\n * pure: same inputs → same outputs, no `Date.now`) so a consumer can fold it\n * through `update` and exercise it with `replay` / the pbt fold without a fake\n * clock.\n *\n * Every op returns a NEW state value and never mutates its input (invariant 1 —\n * state is a value, not a place). `canPass` returns a `[next, allowed]` tuple so\n * the caller branches on the decision while threading the advanced state forward\n * — same convention as `rate-limit`'s `tryConsume` / `record`. `onSuccess` /\n * `onFailure` are the outcome-recording transitions the caller folds in once the\n * guarded call resolves.\n *\n * ## The phase machine\n *\n * A circuit breaker guards a fallible downstream target (an HTTP backend, a DB,\n * a flaky tool call). Instead of hammering a target that is already failing, it\n * trips OPEN after enough failures and fast-fails subsequent calls for a\n * cooldown, then probes with a few trial calls before fully closing again.\n * Three phases, each carrying ONLY its own data so impossible states are\n * unrepresentable (pattern 11):\n *\n * - **closed** — the healthy steady state. Calls pass through. Carries\n * `failures`: the count of *consecutive* failures since the last success.\n * A success resets it to 0; the `failureThreshold`-th failure trips the\n * breaker OPEN. Only `closed` has a failure counter — `open` and\n * `half_open` don't, because in those phases the count is irrelevant\n * (the breaker has already tripped / is already probing).\n *\n * - **open** — the tripped state. All calls fast-fail (no downstream load).\n * Carries `openedAtMs`: the clock reading at which the breaker tripped.\n * `canPass` compares `nowMs - openedAtMs` against `cooldownMs` to decide\n * when to start probing. Only `open` carries a timestamp — neither other\n * phase is timing anything.\n *\n * - **half_open** — the probing state. A limited number of trial calls are\n * allowed through to test whether the target has recovered. Carries\n * `probes`: the count of probes admitted so far this probing round.\n * `canPass` admits while `probes < halfOpenMaxProbes`, then fast-fails the\n * rest (so a slow target can't be flooded by every queued caller at once).\n * A probe success closes the breaker; a probe failure re-opens it (and\n * resets the cooldown clock). Only `half_open` counts in-flight probes.\n *\n * ### Transition table\n *\n * Rows are the current phase; columns are the event. `→` denotes the next\n * phase; `self` means unchanged. `nowMs` is the injected clock.\n *\n * | from \\ event | onSuccess | onFailure | canPass (→ allowed?) |\n * | ------------ | -------------- | -------------------------------------- | ------------------------------------------------------------------------------------ |\n * | closed | closed(0) | failures+1; trip → open(nowMs) at thr. | self → true (always) |\n * | open | self *(¹)* | self | elapsed ≥ cooldown → half_open(1), true; else self → false |\n * | half_open | → closed(0) | → open(nowMs) (probe failed) | probes < max → half_open(probes+1), true; else self → false |\n *\n * *(¹)* `onSuccess` on `open` \"shouldn't happen\": no call was admitted while\n * open, so no success can be reported for one. We leave the state unchanged\n * rather than throw — the breaker is a coordination aid, not a correctness\n * gate, and a spurious success report (e.g. a late-arriving result from before\n * the trip) must not silently un-trip the breaker. `onFailure` on `open` is\n * likewise a no-op: the breaker is already tripped; the `open` clock is anchored\n * at the trip, not bumped by late failures.\n *\n * ### The probe-admission asymmetry\n *\n * Entering `half_open` happens inside `canPass`, not `onSuccess` / `onFailure`,\n * because the cooldown is a *time* condition and only `canPass` reads the clock\n * against `openedAtMs`. The first `canPass` after the cooldown elapses both\n * transitions the phase AND admits the probe (`[half_open(1), true]`) — one\n * caller drives the recovery attempt. Subsequent `canPass` calls in the same\n * round admit up to `halfOpenMaxProbes` total, then fast-fail until a probe\n * resolves and `onSuccess` / `onFailure` moves the phase out of `half_open`.\n *\n * This module is a textbook three-state machine — a clean `/machine-viz` demo.\n *\n * Internal to the package (`src/internal/resilience/`), not a published\n * subpath (#46); same one-shape-per-module rule as `work-queue`, `rate-limit`,\n * and `idempotency`.\n */\n\n/**\n * Circuit-breaker policy — pure configuration, no mutable state. One policy is\n * shared across all the calls guarded by one logical breaker (often a\n * module-scope const). `CircuitState` carries the mutable counts; this carries\n * the thresholds those counts are compared against.\n */\nexport interface CircuitPolicy {\n /**\n * Consecutive failures in `closed` that trip the breaker `open`. The\n * `failureThreshold`-th failure is the one that trips — with a threshold of\n * 5, the breaker survives 4 failures and opens on the 5th.\n */\n readonly failureThreshold: number;\n /**\n * How long the breaker stays `open` before `canPass` starts admitting\n * probes, in milliseconds. Measured from `openedAtMs`; the cutoff is\n * half-open (`elapsed >= cooldownMs` admits), matching `rate-limit` /\n * `idempotency` window edges — a breaker opened at `t` first probes at\n * `t + cooldownMs`.\n */\n readonly cooldownMs: number;\n /**\n * How many probe calls `half_open` admits before fast-failing the rest of\n * the round. Caps the load on a recovering target — a single slow probe\n * can't be joined by every queued caller. The round ends when a probe\n * resolves: success closes the breaker, failure re-opens it.\n */\n readonly halfOpenMaxProbes: number;\n}\n\n/**\n * Sensible defaults: trip after 5 consecutive failures, cool down for 30s,\n * admit a single probe before deciding. Tuned for guarding a network backend;\n * override per call site rather than mutating this shared const.\n */\nexport const defaultCircuitPolicy: CircuitPolicy = {\n failureThreshold: 5,\n cooldownMs: 30_000,\n halfOpenMaxProbes: 1,\n};\n\n/**\n * The breaker's phase. A discriminated union on `phase` so each phase carries\n * ONLY its own data and impossible combinations are unrepresentable (pattern\n * 11): there is no state with both a failure count AND an open timestamp,\n * because such a state is meaningless. `phase` is the discriminant; every field\n * is `readonly` so the whole value is trivially JSON-serializable into a\n * consumer's `Store<S>` and safe to thread through a pure reducer.\n *\n * - `closed` carries `failures` — consecutive failures since last success.\n * - `open` carries `openedAtMs` — clock reading when it tripped.\n * - `half_open` carries `probes` — probes admitted this round.\n */\nexport type CircuitState =\n | { readonly phase: \"closed\"; readonly failures: number }\n | { readonly phase: \"open\"; readonly openedAtMs: number }\n | { readonly phase: \"half_open\"; readonly probes: number };\n\n/** The starting state: closed with zero recorded failures. */\nexport function initCircuit(): CircuitState {\n return { phase: \"closed\", failures: 0 };\n}\n\n/**\n * Record a successful guarded call. PURE — returns a new state; the input is\n * never mutated.\n *\n * - `closed` → reset `failures` to 0 (a success breaks the consecutive run).\n * - `half_open` → the probe succeeded; the target has recovered → `closed`.\n * - `open` → unchanged. A success \"shouldn't happen\" while open (no call\n * was admitted to produce one); a late/spurious success must\n * not silently un-trip the breaker. See module header note ¹.\n */\nexport function onSuccess(\n state: CircuitState,\n _policy: CircuitPolicy,\n): CircuitState {\n switch (state.phase) {\n case \"closed\":\n // Already closed at 0 → return the same shape (cheap; the consumer's\n // store only re-persists on a real change if it dedupes).\n return state.failures === 0 ? state : { phase: \"closed\", failures: 0 };\n case \"half_open\":\n return { phase: \"closed\", failures: 0 };\n case \"open\":\n return state;\n }\n}\n\n/**\n * Record a failed guarded call. PURE — returns a new state; the input is never\n * mutated. `nowMs` is the injected clock, stamped onto the `open` phase when the\n * breaker trips so `canPass` can measure the cooldown later.\n *\n * - `closed` → `failures + 1`; if that reaches `failureThreshold`, trip to\n * `open(nowMs)`. The threshold-th failure is the one that trips.\n * - `half_open` → the probe failed; the target is still unhealthy →\n * `open(nowMs)`, restarting the cooldown clock so a fresh\n * cooldown elapses before the next probe round.\n * - `open` → unchanged. The breaker is already tripped; the `open` clock\n * is anchored at the original trip, not bumped by late\n * failures. See module header note ¹.\n */\nexport function onFailure(\n state: CircuitState,\n policy: CircuitPolicy,\n nowMs: number,\n): CircuitState {\n switch (state.phase) {\n case \"closed\": {\n const failures = state.failures + 1;\n // `>=` not `===`: a threshold of 0 (or any already-exceeded count from a\n // policy change) trips immediately rather than over-counting forever.\n return failures >= policy.failureThreshold\n ? { phase: \"open\", openedAtMs: nowMs }\n : { phase: \"closed\", failures };\n }\n case \"half_open\":\n // A failed probe re-opens and resets the cooldown clock to nowMs.\n return { phase: \"open\", openedAtMs: nowMs };\n case \"open\":\n return state;\n }\n}\n\n/**\n * Decide whether a call may pass, advancing the phase as a side effect of the\n * decision. PURE — returns `[next, allowed]`; the input is never mutated.\n * Branch on the boolean, thread the state forward regardless:\n *\n * const [c1, allowed] = canPass(state.circuit, policy, nowMs);\n * if (!allowed) return [{ ...state, circuit: c1 }, [fastFailCmd]];\n * return [{ ...state, circuit: c1 }, [doGuardedCallCmd]];\n *\n * - `closed` → `[state, true]` — always passes; failures are recorded by\n * `onFailure` once the call resolves, not gated here.\n * - `open` → if `nowMs - openedAtMs >= cooldownMs` the cooldown has\n * elapsed → enter `half_open` and admit this caller as the\n * first probe: `[half_open(1), true]`. Otherwise still cooling\n * down → `[state, false]` (fast-fail, no downstream load).\n * - `half_open` → admit while `probes < halfOpenMaxProbes`, incrementing the\n * count: `[half_open(probes+1), true]`. Once the cap is hit →\n * `[state, false]` until a probe resolves and `onSuccess` /\n * `onFailure` moves the phase out of `half_open`.\n *\n * Entering `half_open` lives here (not in `onSuccess` / `onFailure`) because the\n * cooldown is a time condition and only `canPass` reads `nowMs` against\n * `openedAtMs`. See the module header's probe-admission note.\n */\nexport function canPass(\n state: CircuitState,\n policy: CircuitPolicy,\n nowMs: number,\n): readonly [CircuitState, boolean] {\n switch (state.phase) {\n case \"closed\":\n return [state, true];\n case \"open\": {\n const elapsed = nowMs - state.openedAtMs;\n // Half-open cutoff: elapsed exactly == cooldownMs admits the first probe,\n // matching the >= edge `rate-limit` / `idempotency` use for their windows.\n if (elapsed >= policy.cooldownMs) {\n return [{ phase: \"half_open\", probes: 1 }, true];\n }\n return [state, false];\n }\n case \"half_open\":\n if (state.probes < policy.halfOpenMaxProbes) {\n return [{ phase: \"half_open\", probes: state.probes + 1 }, true];\n }\n return [state, false];\n }\n}\n","/**\n * internal/resilience/rate-limit — two rate limiters as pure state + ops.\n *\n * Same shape as `../../work-queue/ops.ts`: a state type plus pure\n * transition functions. Host-agnostic by construction — no `Date.now()`, no\n * timers, no I/O. The caller injects the clock (`nowMs`) and wires the result\n * into its own reducer / interpret. This keeps the limiters inside TEA's\n * invariant 2 (transitions are pure: same inputs → same outputs, no `Date.now`)\n * so a consumer can fold them through `update` and exercise them with `replay`\n * / the pbt fold without a fake clock.\n *\n * Every op returns a NEW state value and never mutates its input (invariant 1 —\n * state is a value, not a place). The `tryConsume` / `record` ops return a\n * `[next, ok]` tuple so the caller branches on the decision while threading the\n * advanced state forward; `refill` / `remaining` are read-shaped helpers.\n *\n * Two complementary algorithms, picked by the cost the caller wants to pay:\n *\n * - **Token bucket** — O(1) state (four numbers), smooths bursts against a\n * steady refill rate. Allows a burst up to `capacity`, then throttles to\n * `refillPerSec`. Cheapest; the default choice.\n * - **Sliding window log** — O(limit) state (one timestamp per in-window\n * hit), exact \"no more than `limit` events per `windowMs`\". Pay the memory\n * for precise window edges when a token bucket's continuous refill would\n * let through too many at a boundary.\n *\n * Internal to the package (`src/internal/resilience/`), not a published\n * subpath (#46); same one-shape-per-module rule as `work-queue` and `do`.\n */\n\n// ===========================================================================\n// Token bucket\n// ===========================================================================\n\n/**\n * A token bucket: `tokens` of `capacity` available now, replenished at\n * `refillPerSec` tokens per second. `lastRefillMs` is the clock reading at\n * which `tokens` was last computed — `refill` advances it. All four fields are\n * plain numbers so the whole bucket is trivially JSON-serializable into a\n * consumer's `Store<S>`.\n */\nexport interface TokenBucket {\n readonly tokens: number;\n readonly capacity: number;\n readonly refillPerSec: number;\n readonly lastRefillMs: number;\n}\n\n/**\n * Create a full bucket. Starts at `capacity` tokens (the conventional \"a fresh\n * limiter lets the first burst through\" behavior) and stamps `lastRefillMs`\n * with the caller's clock so the first `refill` measures elapsed time from\n * boot, not from epoch 0.\n */\nexport function initBucket(\n capacity: number,\n refillPerSec: number,\n nowMs: number,\n): TokenBucket {\n return {\n tokens: capacity,\n capacity,\n refillPerSec,\n lastRefillMs: nowMs,\n };\n}\n\n/**\n * Add the tokens accrued since `lastRefillMs`, clamped to `capacity`, and\n * advance `lastRefillMs` to `nowMs`. PURE — returns a new bucket; the input is\n * untouched.\n *\n * A `nowMs` at or before `lastRefillMs` (clock not advanced, or skewed\n * backwards) adds a non-positive amount; `Math.max(0, …)` floors the elapsed\n * term so a backwards clock can never *remove* tokens. `lastRefillMs` still\n * advances to `nowMs` so the bucket tracks the latest reading the caller\n * presented — the next forward step measures from there, not from a stale\n * future timestamp.\n */\nexport function refill(bucket: TokenBucket, nowMs: number): TokenBucket {\n const elapsedSec = Math.max(0, nowMs - bucket.lastRefillMs) / 1000;\n const replenished = bucket.tokens + elapsedSec * bucket.refillPerSec;\n return {\n ...bucket,\n tokens: Math.min(bucket.capacity, replenished),\n lastRefillMs: nowMs,\n };\n}\n\n/**\n * Refill against `nowMs`, then attempt to take `n` tokens (default 1).\n *\n * On success returns `[next, true]` with `n` subtracted; on failure returns\n * `[refilled, false]` — the refilled bucket (so the caller keeps the advanced\n * clock + accrued tokens) with no tokens spent. Branch on the boolean, thread\n * the bucket forward regardless:\n *\n * const [b1, ok] = tryConsume(bucket, nowMs);\n * return ok ? [{ ...state, bucket: b1 }, [doWork]] : [{ ...state, bucket: b1 }, []];\n *\n * PURE — input bucket is never mutated.\n */\nexport function tryConsume(\n bucket: TokenBucket,\n nowMs: number,\n n = 1,\n): readonly [TokenBucket, boolean] {\n const refilled = refill(bucket, nowMs);\n if (refilled.tokens >= n) {\n return [{ ...refilled, tokens: refilled.tokens - n }, true];\n }\n return [refilled, false];\n}\n\n// ===========================================================================\n// Sliding window log\n// ===========================================================================\n\n/**\n * A sliding-window log: the timestamps of every hit still inside the trailing\n * `windowMs`, capped at `limit` events per window. `hits` is `readonly` and\n * kept oldest-first (pruning slices the stale prefix off the front, recording\n * appends to the back), so the array is its own sorted index. Exact at the\n * window edge — unlike a token bucket's continuous refill, a hit leaves the\n * count the instant it ages past `windowMs`.\n */\nexport interface SlidingWindow {\n readonly hits: readonly number[];\n readonly windowMs: number;\n readonly limit: number;\n}\n\n/** Create an empty window. No hits recorded yet. */\nexport function initWindow(windowMs: number, limit: number): SlidingWindow {\n return { hits: [], windowMs, limit };\n}\n\n/**\n * The window-edge rule, in one place: keep only the hits still live at `nowMs`.\n * The cutoff is half-open — a hit exactly `windowMs` old is dropped (`> cutoff`,\n * not `>=`), so a hit recorded at `t` no longer counts at `t + windowMs` and the\n * window's capacity fully recovers `windowMs` after the last hit. `record` (the\n * writer) and `remaining` (the read helper) both prune through here so a change\n * to the edge semantics can't leave the reader disagreeing with the writer.\n */\nfunction prune(window: SlidingWindow, nowMs: number): readonly number[] {\n const cutoff = nowMs - window.windowMs;\n return window.hits.filter((t) => t > cutoff);\n}\n\n/**\n * Prune hits older than `nowMs - windowMs`, then record `nowMs` if there is\n * room. Returns `[next, true]` with the hit appended when `hits.length < limit`\n * after pruning, else `[pruned, false]` — the pruned window (capacity recovers\n * as old hits age out) with nothing appended.\n *\n * The cutoff is half-open (see {@link prune}): a hit exactly `windowMs` old is\n * pruned, so a hit recorded at `t` no longer counts at `t + windowMs`. That\n * makes the window's capacity fully recover `windowMs` after the last hit, the\n * behavior tests pin.\n *\n * PURE — input window is never mutated; `hits` is rebuilt by `filter` +\n * spread.\n */\nexport function record(\n window: SlidingWindow,\n nowMs: number,\n): readonly [SlidingWindow, boolean] {\n const pruned = prune(window, nowMs);\n if (pruned.length < window.limit) {\n return [{ ...window, hits: [...pruned, nowMs] }, true];\n }\n return [{ ...window, hits: pruned }, false];\n}\n\n/**\n * How many more hits `record` would accept at `nowMs` without blocking —\n * `limit` minus the live (post-prune) hit count, floored at 0. Read-only: it\n * prunes a local copy to answer but returns no new window. Use it to render a\n * \"N requests remaining\" affordance without committing a hit.\n */\nexport function remaining(window: SlidingWindow, nowMs: number): number {\n const live = prune(window, nowMs).length;\n return Math.max(0, window.limit - live);\n}\n","/**\n * @packageDocumentation\n *\n * Internal to the package (`src/internal/resilience/`, #46). The published\n * `./resilient-call` door is closed; the consumer-facing shape is the\n * higher-order `withResilience(base, config)` wrapper in `../with-resilience`\n * (TEA-idiomatic: it wraps any existing machine instead of forcing a bespoke\n * embedded slice), and this module is its implementation. The APIs are NOT\n * drop-in; migration map, for the in-tree embedded-slice consumers:\n *\n * - `createResilientCall(config, rng)` + hand-wiring `init` / the verbs /\n * `subs` / `handlers` into your machine → `withResilience(base, { target,\n * ...bricks }, rng)`. Nothing is wired by hand anymore: the wrapper\n * intercepts the `target` base Cmd and routes it through the gate.\n * - `ResilientConfig` bricks (`retry` / `circuit` / `rateLimit` / `cache` /\n * `deadline`) → unchanged; `ResilienceConfig` extends the same knob and\n * adds `target` (required), `keyOf?` (per-call key — was `attempt`'s `key`\n * argument), and `at?` (time-as-data reader — REQUIRED with any\n * time-sensitive brick; `withResilience` throws at construction without it).\n * - The slice at your chosen Model field + `liftResilience` → the wrapper\n * owns `model.$resilience`; there is nothing to lift.\n * - Verbs `attempt` / `succeed` / `fail` / `onTimer` → internal to the\n * wrapper; results route as `$resilience:ok` / `$resilience:err` /\n * `$resilience:timer` Msgs through the merged `update` (the old\n * `resilient_run` / `resilient_ok` / `resilient_err` vocabulary is renamed\n * into the `$resilience:` namespace).\n * - `settleFailed` → NO equivalent. It exists for embedded-slice consumers\n * (`authed-call`, `paginated-walk`) that settle a call without touching the\n * breaker; the wrapper exposes no caller-facing settle verb.\n * - `handlers(ports)` / `subs(state)` / the `subscribeDeadline` re-export →\n * handled by the wrapper; run the returned machine like any other.\n *\n * The in-tree modules that embed the slice (`with-resilience` itself, `agent`,\n * `llm-call`, `authed-call`, `reconciler`, `paginated-walk`) import this module\n * directly — that is its role.\n *\n * internal/resilience/resilient-call — the ROOT composition: a single resilience knob\n * that bundles cache → circuit-breaker → rate-limit → exponential-backoff retry\n * (with a deadline-driven retry timer) around any fallible `(input) => Promise`\n * port.\n *\n * This is `examples/resilient-fetch.ts`'s hand-wired `attempt()` generalized:\n *\n * - It is **keyed**. The example tracked one URL at a time; this tracks N\n * concurrent logical calls, each under a string `key`. The cache, the\n * waiting-retry bookkeeping, and the retry timers are all per-key, while the\n * circuit breaker and token bucket are shared (one downstream target, one\n * breaker / one bucket — the example's implicit single-target assumption\n * made explicit).\n * - It is **config-driven**, not hand-wired. You hand `createResilientCall`\n * a `ResilientConfig` and get back the uniform knob contract every L2\n * composition exposes: `init()`, the four verbs (`attempt` / `succeed` /\n * `fail` / `onTimer`), `subs(state)`, and `handlers(ports)`.\n * - Every brick is **optional**. Omit `cache` and the cache gate disappears\n * (`get` is never consulted); omit `circuit` and every call passes the\n * breaker; omit `rateLimit` and the bucket never throttles; omit `retry`\n * and a failure is terminal (no backoff, no timer); omit `deadline` and no\n * overall time budget is armed. \"Omit a brick → omit its gate\" is the\n * whole config story — there are no other modes.\n *\n * Four later compositions (`llm-call`, `paginated-walk`, `authed-call`,\n * `reconciler`) inherit THIS slice + verb shape, so the contract here is\n * load-bearing: the slice is a flat record of brick states, every verb returns\n * `readonly [State, Cmd[]]`, time is always an `at` / `now` parameter, and the\n * RNG used for jitter is injected on the config (never read inside a verb).\n *\n * ## The two non-negotiables (canon)\n *\n * - **Durable** — the slice is a plain-data Model field (brick states are all\n * JSON-serializable), so it survives DO eviction / reload.\n * - **Replayable** — every transition is a verb returning new state + Cmds;\n * `replay` reconstructs it exactly. Nothing here closes over time or RNG.\n *\n * ## Where the clock / RNG live\n *\n * Inside the verbs: nowhere. Time arrives as `at` on `attempt` / `onTimer` and\n * is carried on the result / timer Msgs the consumer dispatches. Jitter RNG is\n * injected once at `createResilientCall(config, rng)` and threaded into\n * `nextDelayMs` at the verb boundary — deterministic in tests, `Math.random`\n * by default. The ONLY clock read is `Date.now()` inside the `handlers` port\n * (the effect boundary), exactly as `resilient-fetch.ts` stamps its result Msgs.\n *\n * That same `at` is what makes a DURATION-bounded retry budget reachable here:\n * `recordFailure` / `shouldRetry` are fed the failure's observation instant,\n * so `config.retry` accepts any `AnyRetryPolicy` — a count, a wall-clock outage\n * budget (`maxElapsedMs`), or explicit `unbounded: true` — with no new argument\n * on any verb and no clock read inside one.\n *\n * ## Typical wiring\n *\n * const rc = createResilientCall<string, Result>({\n * cache: { ttlMs: 60_000 },\n * circuit: { threshold: 5, cooldownMs: 30_000 },\n * rateLimit: { capacity: 10, refillPerSec: 5 },\n * retry: defaultRetryPolicy,\n * deadline: { ms: 5_000 },\n * });\n *\n * // `mountResilientCall` pre-assembles the wiring; the one cell it cannot\n * // write is `attempt`, whose arguments only the consumer's Msg knows. This\n * // knob's `handlers` takes ports, so it is bound before the mount reads it —\n * // `../../jev/ask` and `../../llm-call` expose a nullary `handlers()` and\n * // are passed straight in.\n * const mounted = mountResilientCall({\n * ...rc,\n * handlers: () => rc.handlers({ run: ctx.call }),\n * }, {\n * slice: \"resilience\",\n * attempt: {\n * on: \"fetch\",\n * run: (slice, m: Fetch) => rc.attempt(slice, m.key, m.input, m.at),\n * },\n * onOk: (model, m) => [{ ...model, result: m.result }, []],\n * });\n *\n * // in the machine:\n * init: () => [{ ...mounted.init(), result: null }, []],\n * update: { ...mounted.update },\n * subscriptions: mounted.subscriptions,\n * subscribe: mounted.subscribe,\n * interpret: mounted.interpret,\n *\n * The six verbs stay exported and callable by hand for a consumer that wants a\n * cell the mount cannot express (ADR 0015's escape hatch): `rc.succeed(...)`,\n * `rc.fail(...)`, `rc.onTimer(...)` and `liftResilience` splice exactly as\n * before.\n *\n * ## Naming a knob, when a machine mounts more than one\n *\n * Every knob above speaks `resilient_run` / `resilient_ok` / `resilient_err`,\n * so TWO of them in one machine meet in one `resilient_ok` cell whose `result`\n * is the union of both payloads — discriminated by hand on `key`, which the\n * type checker cannot grade. `config.name` renames the whole family, Cmd and\n * both settle Msgs together:\n *\n * const jev = createResilientCall<JevRequest, JevOk, \"jev\">({ name: \"jev\" });\n * const llm = createResilientCall<LlmCall, LlmOk, \"llm\">({ name: \"llm\" });\n *\n * update: {\n * jev_ok: (s, m) => …, // m.result is JevOk — no `key` switch\n * llm_ok: (s, m) => …, // m.result is LlmOk\n * }\n *\n * The name leads the retry / deadline Sub ids too (`jev:retry:<key>`), because\n * Subs reconcile by id and two knobs would otherwise share one timer per key.\n * `N` is not inferable from `I` / `R`, so an opted-in knob spells all three\n * type arguments; `config.name` is typed at `N`, so value and type cannot\n * drift. Omit `name` and every one of those strings is `resilient*` exactly as\n * it has always been — no existing machine changes.\n *\n * `mountResilientCall` reads the same name off the knob, so a mounted named\n * knob's settle cells are `jev_ok` / `jev_err` too — the mount never spells the\n * `resilient_*` literals, and two named knobs mounted into one machine spread\n * into four distinct cells.\n */\n\nimport { z } from \"zod\";\nimport { liftSlice } from \"../../../compose\";\nimport { Cmd, type CmdOf, type NoCtx, tryInterpret } from \"../../../index\";\nimport type { MsgType } from \"../../../protocol\";\nimport { without } from \"../../../pure/core\";\nimport {\n type AnyRetryPolicy,\n asRng,\n initRetry,\n nextDelayMs,\n type RetryState,\n recordFailure,\n shouldRetry,\n} from \"../../../retry-backoff\";\nimport {\n get as cacheGet,\n set as cacheSet,\n initCache,\n type TtlCache,\n} from \"../cache\";\nimport {\n type CircuitState,\n canPass,\n initCircuit,\n onFailure,\n onSuccess,\n} from \"../circuit-breaker\";\nimport {\n type DeadlineExceeded,\n type DeadlineSub,\n deadlineMsgType,\n deadlineSub,\n subscribeDeadline,\n} from \"../deadline\";\nimport { initBucket, type TokenBucket, tryConsume } from \"../rate-limit\";\n\n// ===========================================================================\n// Config — the knob. Every brick optional; omit a brick → omit its gate.\n// ===========================================================================\n\n/** Circuit-breaker knob — only the two numbers a consumer ever tunes. */\nexport interface CircuitConfig {\n /** Consecutive failures that trip the breaker open. */\n readonly threshold: number;\n /** How long the breaker stays open before admitting a probe, in ms. */\n readonly cooldownMs: number;\n /**\n * Probes admitted in `half_open` before fast-failing the rest of the round.\n * Defaults to 1 (a single probe decides recovery) — matches the circuit-\n * breaker module's `defaultCircuitPolicy`.\n */\n readonly halfOpenMaxProbes?: number;\n}\n\n/** Token-bucket rate-limit knob. */\nexport interface RateLimitConfig {\n /** Burst size — the bucket starts full at `capacity`. */\n readonly capacity: number;\n /** Steady-state refill rate, tokens per second. */\n readonly refillPerSec: number;\n}\n\n/** Per-entry TTL cache knob. */\nexport interface CacheConfig {\n /** How long a cached success stays fresh, in ms. */\n readonly ttlMs: number;\n}\n\n/**\n * Overall deadline knob — a budget of IN-PROCESS time per in-flight call.\n *\n * The budget is charged in elapsed time this process actually observed, not by\n * a position on the host's wall clock. Time in which no process exists — a DO\n * eviction, a crash between attempts, a redeploy — is NOT charged: `resume`\n * re-anchors every live call's charging clock, so downtime lands outside every\n * segment. A timeout therefore means \"the tool took too long\", never \"the host\n * was away too long\" (#144).\n */\nexport interface DeadlineConfig {\n /** Budget of in-process time from the moment a call starts, in ms. */\n readonly ms: number;\n}\n\n// ===========================================================================\n// The Msg-name family — one `name` derives the Cmd and both settle Msgs.\n// ===========================================================================\n\n/**\n * The family every unnamed knob speaks: `resilient_run` / `resilient_ok` /\n * `resilient_err`. Identical to the `MsgType.Resilient*` literals, which stay\n * the vocabulary of every knob that passes no name.\n */\nexport const DEFAULT_RESILIENT_NAME = \"resilient\";\nexport type DefaultResilientName = typeof DEFAULT_RESILIENT_NAME;\n\n// The default family IS the protocol's `MsgType.Resilient*` vocabulary, not a\n// parallel spelling of it. These three lines fail to compile the moment the two\n// drift, which is the only thing keeping \"omit the name and nothing changes\"\n// true for every machine wired before the name existed.\nconst _runIsProtocol: typeof MsgType.ResilientRun =\n `${DEFAULT_RESILIENT_NAME}_run` as const;\nconst _okIsProtocol: typeof MsgType.ResilientOk =\n `${DEFAULT_RESILIENT_NAME}_ok` as const;\nconst _errIsProtocol: typeof MsgType.ResilientErr =\n `${DEFAULT_RESILIENT_NAME}_err` as const;\nvoid [_runIsProtocol, _okIsProtocol, _errIsProtocol];\n\n/** The run Cmd's `type` for the `N` family. */\nexport type ResilientRunType<N extends string> = `${N}_run`;\n/** The success settle Msg's `type` for the `N` family. */\nexport type ResilientOkType<N extends string> = `${N}_ok`;\n/** The failure settle Msg's `type` for the `N` family. */\nexport type ResilientErrType<N extends string> = `${N}_err`;\n\n/**\n * The resilience knob. EVERY field is optional: omit a brick and its gate is\n * skipped entirely (the slice still carries a default brick state so the shape\n * stays uniform across configs, but the gate is never consulted). An empty\n * config `{}` is a valid pass-through — a bare effect with no resilience at all.\n *\n * `N` is the knob's Msg-name family (see {@link ResilientName}); it is carried on\n * the config so the run Cmd and the two settle Msgs are named from ONE place.\n */\nexport interface ResilientConfig<N extends string = DefaultResilientName> {\n /**\n * What this knob's Cmd and settle Msgs are called: `<name>_run`, `<name>_ok`,\n * `<name>_err`. Omit it and the family is `resilient` — `resilient_run` /\n * `resilient_ok` / `resilient_err`, exactly as before this parameter existed.\n *\n * Name a knob when a machine mounts more than one of the resilient family.\n * Two unnamed knobs meet in ONE `resilient_ok` cell whose payload is the union\n * of both results, and the consumer discriminates by hand on `key` — which the\n * type checker cannot see. Under distinct names each knob settles into its own\n * cell, already narrowed to its own payload.\n *\n * The name is a value AND a type: the settle Msg types are generic in `N`, and\n * `N` is not inferable from `I` / `R`, so an opted-in knob spells all three\n * type arguments — `createResilientCall<In, Out, \"jev\">({ name: \"jev\", … })`.\n * The `name` field is typed at `N`, so the two cannot drift apart.\n */\n readonly name?: N;\n /**\n * The backoff brick. Omit and a failure is terminal (no backoff, no timer).\n *\n * Any bound the `../retry-backoff` union admits: a count (`RetryPolicy`), a\n * wall-clock outage budget (`DurationRetryPolicy`), or explicit\n * `unbounded: true`. A duration bound needs no extra wiring: every path that\n * records a failure already holds the instant it was observed as DATA —\n * `fail`'s `msg.at` (stamped at the interpret boundary) and `gate`'s `at`\n * (the caller's `attempt` / the retry timer's `atMs`) — so the streak clock\n * is fed from a Msg, never from a `Date.now()` inside a verb (invariant 2).\n */\n readonly retry?: AnyRetryPolicy;\n readonly circuit?: CircuitConfig;\n readonly rateLimit?: RateLimitConfig;\n readonly cache?: CacheConfig;\n readonly deadline?: DeadlineConfig;\n}\n\n// ===========================================================================\n// Slice — the Model field this knob owns. A flat record of brick states.\n// ===========================================================================\n\n/**\n * What is left of one call's deadline budget, and where the currently open\n * charging segment starts. Plain data, so it persists with the rest of the slice\n * — but the DURABLE fact is `remainingMs`, a duration, never an instant.\n *\n * The accounting has one rule, applied at every transition that carries a clock\n * (`attempt`'s `at`, a settle Msg's `at`, a timer's `atMs`): charge the segment\n * that just closed and open a new one at the same instant —\n * `remainingMs -= at - chargingSinceMs; chargingSinceMs = at`. See {@link charge}.\n *\n * `chargingSinceMs` is an IN-PROCESS anchor, not a persisted deadline. It is\n * meaningful only while the process that wrote it is alive, and `resume` re-bases\n * it on every live call before anything reads it after a reload — which is what\n * keeps downtime out of the budget. Nothing derives a timeout from it across a\n * resume; the only durable answer to \"how much budget is left\" is `remainingMs`.\n *\n * `remainingMs` is `0` when the `deadline` brick is absent, which is also the\n * \"no cap armed\" reading `subs` uses — a call with a real budget always has\n * `remainingMs > 0`, because `gate` settles a call whose budget reached zero\n * rather than letting it enter `running`.\n */\nexport interface CallBudget {\n /** In-process ms left when the open segment started. `0` = no deadline brick. */\n readonly remainingMs: number;\n /** The instant the open segment started, on the clock of the process that opened it. */\n readonly chargingSinceMs: number;\n}\n\n/**\n * Per-key call phase. `key` indexes a logical call; several can be in flight at\n * once (the generalization of the example's single `url`). Discriminated on\n * `phase` so each phase carries only its own data (pattern 11):\n *\n * - `idle` — never attempted, or fully settled and forgotten.\n * - `running` — the effect Cmd is out; awaiting `succeed` / `fail`.\n * Carries the deadline budget ({@link CallBudget}) and the\n * last `input` so a retry can re-issue it.\n * - `waiting_retry` — a transient failure backed off; the retry timer is armed\n * for `retryAtMs`. Carries `input` for the re-attempt, and\n * the same budget — backoff is in-process time, so it is\n * charged like an attempt.\n * - `circuit_open` — the breaker fast-failed this attempt. Terminal for this\n * call until the consumer re-attempts after cooldown.\n * - `succeeded` — settled OK. `result` is the value the port produced.\n * - `failed` — settled Err (out of retries, or no retry brick). `error`\n * carries the last failure.\n */\nexport type CallPhase<I, R> =\n | { readonly phase: \"idle\" }\n | {\n readonly phase: \"running\";\n readonly input: I;\n readonly budget: CallBudget;\n }\n | {\n readonly phase: \"waiting_retry\";\n readonly input: I;\n readonly retryAtMs: number;\n readonly budget: CallBudget;\n }\n | { readonly phase: \"circuit_open\" }\n | { readonly phase: \"succeeded\"; readonly result: R }\n | { readonly phase: \"failed\"; readonly error: unknown };\n\n/**\n * The slice. `circuit` / `bucket` are SHARED across keys (one downstream target\n * → one breaker, one bucket). `retry` and `cache` are PER KEY (each logical call\n * has its own backoff count + cached result), as is the per-call `CallPhase`.\n * Every field is plain data → the whole slice serializes into a `Store<S>`.\n *\n * `retry` / `cache` keep a default brick value even when the corresponding\n * config brick is omitted, so the slice shape is identical regardless of which\n * bricks the consumer enabled — the verbs simply never consult the unused brick.\n */\nexport interface ResilientState<I, R> {\n readonly circuit: CircuitState;\n readonly bucket: TokenBucket;\n /**\n * Per-key backoff counter. Entries minted by `backoff` are `TimedRetryState`s\n * (they carry the streak's `firstFailureAtMs`, since every failure path holds\n * the observation instant); the field is typed at the `RetryState` supertype\n * because a key that has never failed has no streak, and a slice persisted\n * before the origin existed rehydrates without one. Still plain data.\n */\n readonly retry: Readonly<Record<string, RetryState>>;\n readonly cache: TtlCache<R>;\n readonly calls: Readonly<Record<string, CallPhase<I, R>>>;\n}\n\n// ===========================================================================\n// Cmds + Msgs the knob speaks. Generic over the port input `I`.\n// ===========================================================================\n\n/**\n * The one effect this knob emits: \"run the port for `key` with `input`\". The\n * consumer's `handlers(ports)` interprets it; it carries no closure — `input`\n * is plain data (invariant 3).\n */\nexport function runCmdDef<I, R, N extends string = DefaultResilientName>(\n name: N = DEFAULT_RESILIENT_NAME as N,\n) {\n return Cmd.define(`${name}_run` as ResilientRunType<N>, {\n input: z.custom<{ readonly key: string; readonly input: I }>(),\n ok: z.custom<R>(),\n err: [\"port_rejected\", \"deadline_exceeded\"],\n });\n}\nexport type RunCmdDef<\n I,\n R,\n N extends string = DefaultResilientName,\n> = ReturnType<typeof runCmdDef<I, R, N>>;\nexport type RunCmd<I, N extends string = DefaultResilientName> = CmdOf<\n RunCmdDef<I, unknown, N>\n>;\n\n/**\n * Settle Msgs the `handlers` port dispatches back. `at` is stamped at the\n * boundary. Both are generic in the knob's name family `N`, so two knobs named\n * apart land in two `update` cells whose payloads are already narrowed — the\n * `key` switch a single shared cell forces is gone.\n */\nexport type SucceedMsg<R, N extends string = DefaultResilientName> = {\n readonly type: ResilientOkType<N>;\n readonly key: string;\n readonly result: R;\n readonly at: number;\n};\nexport type FailMsg<N extends string = DefaultResilientName> = {\n readonly type: ResilientErrType<N>;\n readonly key: string;\n readonly error: unknown;\n readonly at: number;\n};\n\n/**\n * The deadline Msg tag this knob's timers dispatch, derived from its name the\n * same way `<name>_ok` / `<name>_err` are. The DEFAULT family keeps the bare\n * `\"deadline_exceeded\"` literal — that is the tag every machine wired before\n * the name existed already handles, and `mountResilientCall` still returns a\n * `deadline_exceeded` cell for an unnamed knob.\n */\nexport type ResilientDeadlineType<N extends string = DefaultResilientName> =\n N extends DefaultResilientName ? \"deadline_exceeded\" : `${N}_deadline`;\n\n/**\n * The name the deadline Sub carries for the `N` family — `undefined` for the\n * default family (no name at all, so the bare literal is dispatched), the name\n * itself otherwise. One conditional, so the value and {@link\n * ResilientDeadlineType} can never disagree about which family is unnamed.\n */\nexport type DeadlineNameOf<N extends string = DefaultResilientName> =\n N extends DefaultResilientName ? undefined : N;\n\n/**\n * The retry / deadline timer Msg — a `DeadlineExceeded` whose tag is this\n * knob's ({@link ResilientDeadlineType}), keyed by the call `key` through the\n * Sub `id`. Bare `ResilientTimerMsg` is the default family's, so it is the same\n * `DeadlineExceeded` it has always been.\n */\nexport type ResilientTimerMsg<N extends string = DefaultResilientName> =\n DeadlineExceeded<DeadlineNameOf<N>>;\n\n/**\n * The plain-data error a deadline-failed call settles with. A `{_tag, ...}`\n * sentinel — NEVER a `new Error(...)` — so the slice stays JSON-serializable\n * (Error objects round-trip to `{}` and carry a stack trace, both of which break\n * the slice's durability invariant). `id` / `atMs` echo the timer that fired so\n * a consumer can attribute the failure to its deadline.\n */\nexport type DeadlineExceededError = {\n readonly _tag: \"deadline_exceeded\";\n readonly id: string;\n readonly atMs: number;\n};\n\n/**\n * What `handlers(ports)` returns: the interpret cell for this knob's run Cmd,\n * under that Cmd's own name. A named knob contributes `<name>_run`, so two\n * knobs' handler records merge into one `interpret` without either shadowing\n * the other.\n */\nexport type ResilientHandlers<I, R, N extends string = DefaultResilientName> = {\n readonly [K in ResilientRunType<N>]: (\n cmd: RunCmd<I, N>,\n ctx: NoCtx,\n ) => Promise<SucceedMsg<R, N> | FailMsg<N>>;\n};\n\n/** Ports the consumer supplies to `handlers`. */\nexport interface ResilientPorts<I, R> {\n /** The fallible work this knob wraps. Throws on failure → routed to `fail`. */\n readonly run: (input: I, key: string) => Promise<R>;\n}\n\n// ===========================================================================\n// Internal brick-policy derivation — config → the L1 policy shapes.\n// ===========================================================================\n\n// A circuit policy is only built when the brick is configured; absence is\n// represented as `null` so the gate can short-circuit to \"always pass\".\nfunction circuitPolicy(config: ResilientConfig<string>) {\n if (config.circuit === undefined) return null;\n return {\n failureThreshold: config.circuit.threshold,\n cooldownMs: config.circuit.cooldownMs,\n halfOpenMaxProbes: config.circuit.halfOpenMaxProbes ?? 1,\n };\n}\n\n/**\n * Close the open charging segment at `at` and open the next one there: the ONE\n * place a {@link CallBudget} is debited. Every caller holds `at` as data (a\n * verb's argument, a Msg's stamp), so the budget advances without any verb\n * reading a clock. PURE.\n *\n * A budget with no deadline brick (`remainingMs` 0) is left alone — there is\n * nothing to spend, and debiting it would push it negative and read as \"spent\".\n * `at` before the anchor cannot happen on a monotonic feed, but a clamped charge\n * keeps a skewed one from CREDITING budget back.\n */\nfunction charge(budget: CallBudget, at: number): CallBudget {\n if (budget.remainingMs <= 0) return { ...budget, chargingSinceMs: at };\n const elapsed = Math.max(0, at - budget.chargingSinceMs);\n return { remainingMs: budget.remainingMs - elapsed, chargingSinceMs: at };\n}\n\n// Sub-id families. Each call's retry timer is a deadline keyed by `key`, so a\n// machine running many concurrent calls reconciles each independently. The\n// knob's `name` leads the id for the same reason it leads the Msg types: Subs\n// reconcile by id, so two knobs sharing a key would otherwise share one timer.\n// The default `resilient` name reproduces the ids exactly as they have always\n// read.\nfunction retryTimerId(name: string, key: string): string {\n return `${name}:retry:${key}`;\n}\nfunction deadlineTimerId(name: string, key: string): string {\n return `${name}:deadline:${key}`;\n}\n\n/**\n * The deadline Sub's `name` for a knob's family — the value side of\n * {@link DeadlineNameOf}. The default family maps to `undefined` (no name),\n * which is what keeps an unnamed knob dispatching the bare `deadline_exceeded`\n * literal rather than a `resilient_deadline` nobody is wired for.\n */\nfunction deadlineNameOf<N extends string>(name: N): DeadlineNameOf<N> {\n return (\n name === DEFAULT_RESILIENT_NAME ? undefined : name\n ) as DeadlineNameOf<N>;\n}\n\n// ===========================================================================\n// The knob factory.\n// ===========================================================================\n\n/**\n * Build a resilient-call knob from `config`. `rng` is injected for retry\n * jitter — pass a fixed `() => 0.5` in tests to pin backoff; defaults to\n * `Math.random` (read at the verb boundary, never inside a brick op). It is\n * curried onto the factory rather than threaded through every verb so callers\n * spell the determinism choice once.\n *\n * Returns the uniform L2 knob contract. `I` is the port input type, `R` the\n * port result type. Consumers outside the package reach this through\n * `withResilience` (`../with-resilience`) — see the migration map at the top\n * of this module.\n */\nexport function createResilientCall<\n I,\n R,\n N extends string = DefaultResilientName,\n>(config: ResilientConfig<N>, rng: () => number = Math.random) {\n const name = config.name ?? (DEFAULT_RESILIENT_NAME as N);\n const cPolicy = circuitPolicy(config);\n // Re-affirm the `[0, 1)` contract at the seam where the injected generator\n // feeds the backoff math (brand from `../retry-backoff`). The factory's\n // public param stays a plain `() => number` so transitive callers are\n // unchanged; `asRng` is the single point it's branded for `nextDelayMs`.\n const rngBranded = asRng(rng);\n const runCmd = runCmdDef<I, R, N>(name);\n // The two settle names, derived once from the same `name` the Cmd def is\n // built from — so the Cmd and the Msgs it settles into can never disagree.\n const okType = `${name}_ok` as ResilientOkType<N>;\n const errType = `${name}_err` as ResilientErrType<N>;\n // The name the deadline Sub carries, which is what fixes the Msg tag it\n // dispatches. `undefined` for the DEFAULT family: an unnamed knob must keep\n // dispatching the bare `deadline_exceeded` literal every existing machine is\n // wired against, so the default is not merely spelled the same — it is the\n // absence of a name, exactly as before this parameter existed.\n const deadlineName = deadlineNameOf(name);\n\n /** The starting slice. Bricks not in `config` still get a default value. */\n function init(): ResilientState<I, R> {\n return {\n circuit: initCircuit(),\n // The bucket carries a default value even without the rateLimit brick so\n // the slice shape is uniform, but the rate-limit GATE is skipped entirely\n // when the brick is absent (see `gate`) — the bucket is simply never\n // consulted. A `0/0` placeholder bucket would be touched by `tryConsume`;\n // a `1-cap` default is harmless because the gate never calls it.\n bucket: config.rateLimit\n ? initBucket(\n config.rateLimit.capacity,\n config.rateLimit.refillPerSec,\n 0,\n )\n : initBucket(1, 1, 0),\n retry: {},\n cache: initCache<R>(),\n calls: {},\n };\n }\n\n /** Read this key's retry state, defaulting to a fresh one. */\n function retryOf(s: ResilientState<I, R>, key: string): RetryState {\n return s.retry[key] ?? initRetry();\n }\n\n /** A fresh full budget for a call starting at `at` (`remainingMs` 0 = no cap). */\n function freshBudget(at: number): CallBudget {\n return {\n remainingMs: config.deadline ? config.deadline.ms : 0,\n chargingSinceMs: at,\n };\n }\n\n /**\n * The budget a call already live under `key` carries forward, charged up to\n * `at`; a fresh full budget when the key has no live call. This is what makes a\n * re-attempt share ONE budget with the attempts before it rather than buying a\n * new one — the in-process invariant the deadline brick has always had.\n */\n function budgetFor(\n s: ResilientState<I, R>,\n key: string,\n at: number,\n ): CallBudget {\n const call = s.calls[key];\n if (call?.phase === \"running\" || call?.phase === \"waiting_retry\") {\n return charge(call.budget, at);\n }\n return freshBudget(at);\n }\n\n /**\n * The shared decision: budget → cache → rate-limit → circuit gate, then either\n * emit the effect or schedule a retry. `budget` is threaded so a re-attempt\n * (from `onTimer`) keeps the ORIGINAL overall budget rather than restarting it.\n * PURE — `at` is the only clock; `rng` is the injected jitter source.\n *\n * ## Why the budget is checked BEFORE the effect is emitted\n *\n * The check is step 0 and it settles rather than dispatches. A call whose\n * budget is spent must not reach the port at all: the port is a real side\n * effect, and re-issuing an attempt only to settle `deadline_exceeded` when it\n * returns spends that effect for an answer nobody reads. This is exactly the\n * resume path's shape — a process that comes back to a spent budget settles\n * here, with zero further port calls (#144).\n *\n * Used by both `attempt` (fresh call) and `onTimer` (retry timer fired) so\n * the gate logic lives in exactly one place — the example's shared `attempt`\n * helper, lifted to a keyed knob.\n *\n * ## Why rate-limit is gated BEFORE the circuit probe\n *\n * The circuit-breaker's `canPass` is not a passive read: in `half_open` it\n * *consumes* a probe slot (advancing `probes`), and the consumed slot is only\n * released when a probe RESOLVES via `onSuccess` / `onFailure`. So a probe\n * slot may only be taken when this attempt is actually going to reach the\n * backend (i.e. emit the `resilient_run` Cmd). The rate-limit gate can reject\n * an attempt and return a backoff WITHOUT any Cmd — no backend call, so no\n * settle Msg ever comes back. If the circuit probe were consumed first, that\n * rate-limited attempt would burn the half-open probe budget on a call that\n * never happened, wedging the breaker `half_open` at its probe cap forever\n * (nothing moves it out of `half_open` but a resolved probe). Running the\n * rate-limit gate first means the circuit probe is consumed ONLY on the path\n * that emits the effect.\n */\n function gate(\n s: ResilientState<I, R>,\n key: string,\n input: I,\n at: number,\n budget: CallBudget,\n ): readonly [ResilientState<I, R>, readonly RunCmd<I, N>[]] {\n // 0) Budget gate. Skipped without the deadline brick (`remainingMs` stays 0\n // and never moves). A spent budget settles here — before the cache read, the\n // token spend and the circuit probe, none of which a dead call should touch.\n if (config.deadline !== undefined && budget.remainingMs <= 0) {\n return [\n setCall(s, key, {\n phase: \"failed\",\n error: {\n _tag: \"deadline_exceeded\",\n id: deadlineTimerId(name, key),\n atMs: at,\n } satisfies DeadlineExceededError,\n }),\n [],\n ];\n }\n\n // 1) Serve from cache if fresh — no effect at all. Skipped when no cache brick.\n if (config.cache !== undefined) {\n const cached = cacheGet(s.cache, key, at);\n if (cached !== undefined) {\n return [setCall(s, key, { phase: \"succeeded\", result: cached }), []];\n }\n }\n\n // 2) Rate-limit gate FIRST — it may reject with no Cmd, so it must run\n // before any circuit probe is consumed (see header note). Skipped entirely\n // when no rateLimit brick — the bucket is never consulted, so the default\n // placeholder bucket stays untouched.\n let bucket = s.bucket;\n if (config.rateLimit !== undefined) {\n const [next, hasToken] = tryConsume(bucket, at);\n bucket = next;\n if (!hasToken) {\n // Rate-limited: treat as a transient failure and back off via retry.\n // The circuit is untouched — no probe is spent on a call that never ran.\n return backoff(\n { ...s, bucket },\n key,\n input,\n \"rate_limited\",\n at,\n budget,\n );\n }\n }\n\n // 3) Circuit-breaker gate. `null` policy → no breaker → always pass. Reached\n // only once a token is secured, so a half-open probe is consumed exclusively\n // on the path that emits the effect below.\n let circuit = s.circuit;\n if (cPolicy !== null) {\n const [next, ok] = canPass(circuit, cPolicy, at);\n circuit = next;\n if (!ok) {\n return [\n { ...setCall(s, key, { phase: \"circuit_open\" }), bucket, circuit },\n [],\n ];\n }\n }\n\n // 4) All gates passed — emit the effect as data.\n return [\n {\n ...setCall(s, key, { phase: \"running\", input, budget }),\n circuit,\n bucket,\n },\n [runCmd({ key, input })],\n ];\n }\n\n /**\n * Back off after a transient failure: record it, and if `retry` permits\n * another attempt, enter `waiting_retry` with the retry timer armed; else\n * settle `failed`. When no `retry` brick is configured the call fails on the\n * first failure (no backoff, no timer). PURE.\n *\n * `at` is both the backoff anchor AND the streak clock: it is passed to\n * `recordFailure` (which starts `firstFailureAtMs` on the streak's first\n * failure and preserves it on every later one) and to `shouldRetry`, so a\n * `DurationRetryPolicy` on `config.retry` is honoured with no extra wiring.\n * A success drops `retry[key]` entirely, which drops the origin with it —\n * only an unbroken run of failures grows toward a duration budget.\n */\n function backoff(\n s: ResilientState<I, R>,\n key: string,\n input: I,\n error: unknown,\n at: number,\n budget: CallBudget,\n ): readonly [ResilientState<I, R>, readonly RunCmd<I, N>[]] {\n if (config.retry === undefined) {\n return [setCall(s, key, { phase: \"failed\", error }), []];\n }\n const retry = recordFailure(retryOf(s, key), error, at);\n const withRetry = { ...s, retry: { ...s.retry, [key]: retry } };\n if (!shouldRetry(retry, config.retry, at)) {\n return [setCall(withRetry, key, { phase: \"failed\", error }), []];\n }\n const retryAtMs = at + nextDelayMs(retry, config.retry, rngBranded);\n return [\n setCall(withRetry, key, {\n phase: \"waiting_retry\",\n input,\n retryAtMs,\n budget,\n }),\n [],\n ];\n }\n\n // Set the phase for one key, leaving every other field untouched.\n function setCall(\n s: ResilientState<I, R>,\n key: string,\n phase: CallPhase<I, R>,\n ): ResilientState<I, R> {\n return { ...s, calls: { ...s.calls, [key]: phase } };\n }\n\n // === Verb: attempt =======================================================\n\n /**\n * Start (or restart) the call for `key` with `input` at time `at`. Runs the\n * cache → circuit → rate-limit gate and either emits the `resilient_run`\n * effect, schedules a retry, fast-fails (circuit open), or serves the cache.\n * PURE.\n *\n * A key already live keeps the budget it is under (charged up to `at`) rather\n * than being handed a fresh one, so re-issuing an in-flight call — the boot\n * path's move — cannot silently extend its deadline. Only a key with no live\n * call starts a full budget.\n */\n function attempt(\n s: ResilientState<I, R>,\n key: string,\n input: I,\n at: number,\n ): readonly [ResilientState<I, R>, readonly RunCmd<I, N>[]] {\n return gate(s, key, input, at, budgetFor(s, key, at));\n }\n\n // === Verb: resume ========================================================\n\n /**\n * Re-anchor every live call's charging clock to `at`: the verb a host calls\n * once on the boot path, before any Sub is armed or any attempt re-issued.\n *\n * This is what keeps DOWNTIME out of the budget. `chargingSinceMs` is an\n * instant on the clock of the process that wrote it; after an eviction, a crash\n * or a redeploy, the gap between it and now is time in which no attempt ran and\n * no port was called, so charging it would time a call out on the host's\n * absence rather than on the tool's slowness. `remainingMs` — the durable half\n * of the budget — is left exactly as the last live transition set it. PURE.\n */\n function resume(s: ResilientState<I, R>, at: number): ResilientState<I, R> {\n const calls: Record<string, CallPhase<I, R>> = { ...s.calls };\n for (const [key, call] of Object.entries(calls)) {\n if (call.phase !== \"running\" && call.phase !== \"waiting_retry\") continue;\n calls[key] = {\n ...call,\n budget: { ...call.budget, chargingSinceMs: at },\n };\n }\n return { ...s, calls };\n }\n\n // === Verb: succeed =======================================================\n\n /**\n * Record a success for `key`: close the breaker, fill the cache (if a cache\n * brick exists), reset this key's retry count, and settle `succeeded`. PURE —\n * `msg.at` is the cache write clock.\n */\n function succeed(\n s: ResilientState<I, R>,\n key: string,\n msg: SucceedMsg<R, N>,\n ): readonly [ResilientState<I, R>, readonly RunCmd<I, N>[]] {\n const circuit =\n cPolicy !== null ? onSuccess(s.circuit, cPolicy) : s.circuit;\n const cache =\n config.cache !== undefined\n ? cacheSet(s.cache, key, msg.result, msg.at, config.cache.ttlMs)\n : s.cache;\n // Drop this key's retry counter — a success ends the retry run.\n const retry = without(s.retry, key);\n return [\n {\n ...setCall(s, key, { phase: \"succeeded\", result: msg.result }),\n circuit,\n cache,\n retry,\n },\n [],\n ];\n }\n\n // === Verb: fail ==========================================================\n\n /**\n * Record a failure for `key`: trip the breaker (it may open), then back off —\n * schedule a retry if `retry` permits, else settle `failed`. PURE — `msg.at`\n * stamps the breaker trip + the retry delay base. Re-issues from the call's\n * remembered `input` and carries the original budget forward, charged up to\n * `msg.at` — the attempt that just failed spent in-process time, and a retry\n * ladder shares ONE budget across its attempts rather than getting a fresh one\n * per attempt.\n */\n function fail(\n s: ResilientState<I, R>,\n key: string,\n msg: FailMsg<N>,\n ): readonly [ResilientState<I, R>, readonly RunCmd<I, N>[]] {\n const circuit =\n cPolicy !== null ? onFailure(s.circuit, cPolicy, msg.at) : s.circuit;\n const withCircuit = { ...s, circuit };\n const call = s.calls[key];\n const input = call?.phase === \"running\" ? call.input : undefined;\n const budget = budgetFor(s, key, msg.at);\n // No remembered input (e.g. a stray fail for a key not running) → just\n // settle failed after tripping the breaker; nothing to re-issue.\n if (input === undefined) {\n return [\n setCall(withCircuit, key, { phase: \"failed\", error: msg.error }),\n [],\n ];\n }\n return backoff(withCircuit, key, input, msg.error, msg.at, budget);\n }\n\n // === Verb: settleFailed ==================================================\n\n /**\n * Settle `key` to terminal `failed` with `error` WITHOUT touching the circuit\n * breaker or this key's retry counter. PURE.\n *\n * This is the verb for a failure that is NOT a downstream-health signal: the\n * call must end now, but the breaker must not trip and the backoff run must\n * not advance. `fail` is the opposite — it records the failure against the\n * breaker (it may open) and feeds the retry policy (it may back off). Use\n * `settleFailed` when the *reason* the call cannot proceed has nothing to do\n * with downstream health:\n *\n * - `authed-call` settles a 401 it cannot fix (e.g. the refresh budget is\n * spent): a bad token is the caller's problem, not the backend's —\n * tripping the breaker on it would punish a healthy target, and backing\n * off would just retry a request that will 401 again.\n * - `paginated-walk` settles a terminal walk error (e.g. a malformed page\n * the consumer rejects) that should stop the walk without poisoning the\n * shared breaker the next page fetch passes through.\n *\n * Because it leaves `circuit` and `retry[key]` untouched, the breaker keeps\n * its health view of the actual backend and a later `attempt` for this key\n * starts a fresh retry run. Both sibling knobs previously hand-rolled this by\n * splicing `calls` directly; this verb gives them one named, tested entry\n * point so the slice's settle invariants live in exactly one place.\n */\n function settleFailed(\n s: ResilientState<I, R>,\n key: string,\n error: unknown,\n ): readonly [ResilientState<I, R>, readonly RunCmd<I, N>[]] {\n return [setCall(s, key, { phase: \"failed\", error }), []];\n }\n\n // === Verb: onTimer =======================================================\n\n /**\n * A timer fired for some key. Two kinds, disambiguated by the Sub id:\n *\n * - retry timer → re-run the gate for that key's remembered input,\n * re-checking the circuit + bucket at THIS time.\n * - deadline timer → the overall budget elapsed → settle `failed` with a\n * deadline error, no matter the current phase.\n *\n * A timer for a key no longer `waiting_retry` (retry) / no longer in flight\n * (deadline) is a no-op — the reconcile pass races the result; the verb must\n * tolerate a stale fire. PURE.\n */\n function onTimer(\n s: ResilientState<I, R>,\n msg: ResilientTimerMsg,\n ): readonly [ResilientState<I, R>, readonly RunCmd<I, N>[]] {\n for (const [key, call] of Object.entries(s.calls)) {\n if (msg.id === retryTimerId(name, key)) {\n if (call.phase !== \"waiting_retry\") return [s, []];\n // The backoff wait was in-process time, so it is charged before the gate\n // reads the budget — and the gate settles rather than dispatching when\n // that leaves nothing, which is how a resumed ladder ends without one\n // more port call.\n return gate(\n s,\n key,\n call.input,\n msg.atMs,\n charge(call.budget, msg.atMs),\n );\n }\n if (msg.id === deadlineTimerId(name, key)) {\n // Deadline only fires for a still-active call. A settled call has no\n // armed deadline Sub, but tolerate a stale fire defensively.\n if (call.phase !== \"running\" && call.phase !== \"waiting_retry\")\n return [s, []];\n // Plain-data sentinel, NOT a `new Error(...)`: an Error object is not\n // JSON-serializable (it round-trips to `{}`) and captures a stack trace,\n // both of which break the slice's durability invariant once it is\n // persisted into a `Store<S>` and reloaded. A `{_tag, ...}` record keeps\n // the slice round-trip-equal under JSON.parse(JSON.stringify(...)).\n return [\n setCall(s, key, {\n phase: \"failed\",\n error: {\n _tag: \"deadline_exceeded\",\n id: msg.id,\n atMs: msg.atMs,\n } satisfies DeadlineExceededError,\n }),\n [],\n ];\n }\n }\n return [s, []];\n }\n\n // === Subs ================================================================\n\n /**\n * Pre-wired subscriptions: a retry timer for every `waiting_retry` call, and\n * (when the `deadline` brick is configured) an overall deadline timer for\n * every still-active call. Both are `DeadlineSub`s reconciled by id, so a\n * phase change cancels the matching timer automatically — no manual\n * `clearTimeout`. Wire `subscribe: { deadline: subscribeDeadline }`.\n */\n function subs(\n s: ResilientState<I, R>,\n ): readonly DeadlineSub<DeadlineNameOf<N>>[] {\n const out: DeadlineSub<DeadlineNameOf<N>>[] = [];\n for (const [key, call] of Object.entries(s.calls)) {\n if (call.phase === \"waiting_retry\") {\n out.push(\n deadlineSub(retryTimerId(name, key), call.retryAtMs, {\n name: deadlineName,\n }),\n );\n }\n if (\n config.deadline !== undefined &&\n (call.phase === \"running\" || call.phase === \"waiting_retry\") &&\n call.budget.remainingMs > 0\n ) {\n // The absolute instant the timer arms at is DERIVED here, from the open\n // segment's anchor plus what is left — it is never persisted, so a slice\n // that came back from storage arms off the anchor `resume` just re-based\n // rather than off a stamp the downtime ran past.\n out.push(\n deadlineSub(\n deadlineTimerId(name, key),\n call.budget.chargingSinceMs + call.budget.remainingMs,\n { name: deadlineName },\n ),\n );\n }\n }\n return out;\n }\n\n // === Handlers ============================================================\n\n /**\n * Pre-wired interpret handler for `resilient_run`. Wraps the consumer's\n * `run` port via `tryInterpret` (Railway): success → `resilient_ok`, failure\n * → `resilient_err`, each stamped with `Date.now()` at the effect boundary —\n * the ONE permitted clock read (interpret is impure; the reducer never reads\n * the clock). Assign to the machine's `interpret`:\n *\n * interpret: rc.handlers({ run: (input, key) => ctx.callBackend(input) })\n */\n function handlers(ports: ResilientPorts<I, R>): ResilientHandlers<I, R, N> {\n const handle = tryInterpret<\n RunCmd<I, N>,\n R,\n SucceedMsg<R, N> | FailMsg<N>,\n // The work fn reads nothing from ctx — it forwards `cmd.input` to the\n // consumer-supplied `run` port. `NoCtx` (not `unknown`) marks this as a\n // DELIBERATE context-free seam, so callers see intent, not looseness.\n NoCtx\n >(\n (cmd) => ports.run(cmd.input, cmd.key),\n (result, cmd): SucceedMsg<R, N> => ({\n type: okType,\n key: cmd.key,\n result,\n at: Date.now(),\n }),\n (error, cmd): FailMsg<N> => ({\n type: errType,\n key: cmd.key,\n error,\n at: Date.now(),\n }),\n );\n // The key is `${name}_run`, a template-literal type TS cannot see through\n // in an object literal; the mapped return type above is the declaration\n // that carries it to the caller.\n return { [runCmd.cmdType]: handle } as ResilientHandlers<I, R, N>;\n }\n\n return {\n // The Msg-name family this knob speaks, as a VALUE typed at `N`. `mount`\n // keys its settle cells off this rather than off the `resilient_*`\n // literals, so a named knob mounts into its own cells (#229) and the Cmd,\n // the Msgs, the Sub ids and the mounted cells all derive from one name.\n name,\n init,\n attempt,\n resume,\n succeed,\n fail,\n settleFailed,\n onTimer,\n subs,\n handlers,\n };\n}\n\n/**\n * Lift a knob result `[slice, cmds]` into a host `[State, cmds]` where the\n * slice lives at `state.resilience`. A convenience for the common single-slice\n * host; consumers with a differently-named field spread by hand. Pure — a thin\n * record rebuild, no clock / RNG. Under `withResilience` the wrapper owns\n * `model.$resilience`, so there is nothing to lift; this is for the embedded\n * slice consumers only.\n *\n * The record rebuild itself is `liftSlice` from the root door (#231) — this\n * stays as the named, pre-keyed convenience for the `state.resilience` field,\n * unchanged in name, signature and subpath.\n */\nexport function liftResilience<\n S extends { resilience: ResilientState<I, R> },\n I,\n R,\n C extends Cmd,\n>(\n state: S,\n result: readonly [ResilientState<I, R>, readonly C[]],\n): readonly [S, readonly C[]] {\n return liftSlice(\"resilience\", state, result);\n}\n\n// ===========================================================================\n// mount — the eight hand-spliced wiring points, pre-assembled.\n// ===========================================================================\n\n/** What every verb of a knob in this family hands back. */\nexport type Settle<I, R> = readonly [\n ResilientState<I, R>,\n readonly RunCmd<I>[],\n];\n\n/**\n * The part of a resilient-call knob {@link mountResilientCall} needs. This knob\n * satisfies it, and so does every knob that delegates these verbs to it —\n * `internal/jev/ask` and `internal/llm-call` both do, which is why the mount is\n * written against the shape rather than against either door.\n *\n * `OkMsg` / `ErrMsg` are parameters rather than {@link SucceedMsg} /\n * {@link FailMsg} because an inheriting knob narrows them: jev-ask's `fail`\n * takes a `JevFailMsg` whose `error` is a typed `JevAskErr`, and a mount that\n * fixed the supertype would hand that verb a widened Msg.\n */\nexport interface MountableKnob<\n I,\n R,\n OkMsg,\n ErrMsg,\n Handlers,\n N extends string = DefaultResilientName,\n> {\n /**\n * The Msg-name family this knob settles into — `<name>_ok` / `<name>_err`\n * (#229). The mount reads it rather than assuming `resilient_*`, so a named\n * knob mounts into the cells it actually emits; an unnamed one is\n * `resilient`, so nothing a machine already spread changes.\n */\n readonly name: N;\n init(): ResilientState<I, R>;\n succeed(s: ResilientState<I, R>, key: string, msg: OkMsg): Settle<I, R>;\n fail(s: ResilientState<I, R>, key: string, msg: ErrMsg): Settle<I, R>;\n onTimer(s: ResilientState<I, R>, msg: ResilientTimerMsg<N>): Settle<I, R>;\n subs(s: ResilientState<I, R>): readonly DeadlineSub<DeadlineNameOf<N>>[];\n handlers(): Handlers;\n}\n\n/**\n * The consumer's half of a settle cell: fold the settled answer into the\n * machine's own Model.\n *\n * It is handed the Model the inherited verb ALREADY settled — the slice at\n * `slice` is the post-`succeed` / post-`fail` one — so the ordering that wedges\n * a call at `running` is not something this callback can express. It returns\n * the repo's universal cell shape so a fold that needs to chain an effect\n * (book the answer, then write it somewhere) returns that Cmd like any other\n * reducer cell would.\n */\nexport type SettleFold<Model, M, C extends Cmd> = (\n model: Model,\n msg: M,\n) => readonly [Model, readonly C[]];\n\n/** One cell of the update fragment {@link mountResilientCall} returns. */\nexport type MountedCell<Model, M, I, C extends Cmd> = (\n model: Model,\n msg: M,\n) => readonly [Model, readonly (RunCmd<I> | C)[]];\n\n/**\n * What to mount, and where. `slice` names the Model field the knob's state\n * lives on — a plain, readable field, per\n * [ADR 0015](../../../../.decisions/0015-hide-the-wiring-never-the-state.md):\n * the mount absorbs the assembly and hides none of the durable state.\n */\nexport interface MountConfig<\n Model,\n Slice extends string,\n I,\n R,\n AttemptType extends string,\n AttemptMsg,\n OkMsg,\n ErrMsg,\n C extends Cmd,\n N extends string = DefaultResilientName,\n> {\n /** The Model field carrying the knob's slice. */\n readonly slice: Slice;\n /**\n * The cell that starts a call. Its shape is the one wiring point the mount\n * cannot supply: each door's `attempt` takes its own arguments (jev-ask reads\n * a key, a state and an instant; llm-call reads a request), and only the\n * consumer knows which Msg field carries each. `run` is that one line.\n */\n readonly attempt: {\n /** The Msg type that starts a call — the key the cell lands on. */\n readonly on: AttemptType;\n readonly run: (\n slice: ResilientState<I, R>,\n msg: AttemptMsg,\n ) => Settle<I, R>;\n };\n /** Fold a settled success into the Model. Omit and only the slice advances. */\n readonly onOk?: SettleFold<Model, OkMsg, C>;\n /** Fold a settled failure into the Model. Omit and only the slice advances. */\n readonly onErr?: SettleFold<Model, ErrMsg, C>;\n /**\n * Fold a call the TIMER settled into the Model. This is the third failure\n * class, and it never reaches {@link MountConfig.onErr}: a deadline fire\n * settles `failed` inside the slice and emits no settle Msg, so without this\n * callback a call that dies on its budget advances the slice and folds\n * nothing — the shape that used to be a documented caveat.\n *\n * It runs once per call the knob's deadline cell moved INTO `failed`:\n * the deadline fire itself, and the resumed retry whose gate found the\n * budget spent. A plain backoff tick, a stale fire and an already-`failed`\n * call fold nothing. `error` is the slice's own settled error — a\n * {@link DeadlineExceededError} on the deadline path.\n *\n * Omit and only the slice advances, exactly as the other two do.\n */\n readonly onDeadline?: SettleFold<Model, DeadlineSettled<N>, C>;\n}\n\n/**\n * What {@link MountConfig.onDeadline} is handed: the one call the timer cell\n * just settled `failed`, named by its `key`, carrying the error the slice\n * settled with and the timer Msg that produced it. It is not a Msg the reducer\n * ever sees — no settle Msg exists for this failure class, which is the whole\n * reason the callback is here.\n */\nexport interface DeadlineSettled<N extends string = DefaultResilientName> {\n /** The call key that settled. */\n readonly key: string;\n /** The error now on the slice — a {@link DeadlineExceededError} on the deadline path. */\n readonly error: unknown;\n /**\n * The timer Msg whose fire settled it — this knob's own tag, so a fold on a\n * knob named `jev` is handed a `jev_deadline` and one on an unnamed knob a\n * `deadline_exceeded`.\n */\n readonly msg: ResilientTimerMsg<N>;\n}\n\n/**\n * The four fragments a consumer spreads into `defineMachine`. Together with\n * `init` they are the eight wiring points; none of them is optional at the type\n * level, so a consumer that mounts cannot omit `subscribe` (a backed-off retry\n * that never fires) or re-implement `interpret` as a dispatching handler.\n */\nexport interface MountedResilientCall<\n Model,\n Slice extends string,\n I,\n R,\n AttemptType extends string,\n AttemptMsg,\n OkMsg,\n ErrMsg,\n C extends Cmd,\n Handlers,\n N extends string = DefaultResilientName,\n> {\n /** The starting slice under its field name — spread into the machine's `init`. */\n init(): { readonly [K in Slice]: ResilientState<I, R> };\n /**\n * The three settle cells plus the attempt cell, to spread into `update`.\n *\n * The two settle keys are the knob's OWN Msg names (#229), so a knob built as\n * `name: \"jev\"` mounts into `jev_ok` / `jev_err` and two named knobs in one\n * machine spread into four distinct cells instead of colliding on one. An\n * unnamed knob is `resilient`, so the keys read `resilient_ok` /\n * `resilient_err` exactly as they always have.\n *\n * The deadline cell is keyed the same way (#238). A knob's deadline Sub\n * carries its name, so a knob built as `name: \"jev\"` dispatches\n * `jev_deadline` and mounts into a `jev_deadline` cell; an unnamed knob still\n * dispatches and mounts `deadline_exceeded`. Before that, both mounts wrote\n * the one unqualified `deadline_exceeded` key and the last spread silently\n * dropped the other knob's deadline fold. The cell folds through\n * {@link MountConfig.onDeadline}, since a call the timer settles emits no\n * settle Msg and so reaches no `onErr`.\n */\n readonly update: Readonly<\n Record<AttemptType, MountedCell<Model, AttemptMsg, I, C>>\n > & {\n readonly [K in ResilientOkType<N>]: MountedCell<Model, OkMsg, I, C>;\n } & {\n readonly [K in ResilientErrType<N>]: MountedCell<Model, ErrMsg, I, C>;\n } & {\n readonly [K in ResilientDeadlineType<N>]: MountedCell<\n Model,\n ResilientTimerMsg<N>,\n I,\n C\n >;\n };\n subscriptions(model: Model): readonly DeadlineSub<DeadlineNameOf<N>>[];\n readonly subscribe: { readonly deadline: typeof subscribeDeadline };\n readonly interpret: Handlers;\n}\n\n/**\n * Pre-assemble a resilient-call knob into the fragments a machine definition\n * spreads, so mounting one is a spread instead of eight hand-spliced points.\n *\n * The three points that used to fail only at runtime are gone at the type\n * level rather than documented:\n *\n * - The settle cells run the inherited `succeed` / `fail` verb and hand the\n * already-settled Model to {@link SettleFold}. There is no cell for a\n * consumer to write in the wrong order.\n * - `interpret` is `knob.handlers()` — the form that RETURNS the settle Msg\n * so it re-enters the reducer. A consumer that spreads the fragment cannot\n * substitute a dispatching handler for it.\n * - `subscribe` carries `subscribeDeadline`, so the retry timer is armed by\n * construction rather than by remembering.\n *\n * A deadline-exceeded call settles inside the slice and emits no settle Msg, so\n * it reaches no `onErr`. {@link MountConfig.onDeadline} is that failure class's\n * fold — omit it and the slice still advances, exactly as omitting `onErr`\n * does.\n *\n * Nothing is taken away. The slice stays a plain field at `slice` that the\n * consumer reads, `replay` sees and the journal prints, and every verb the\n * fragments call is still exported and callable by hand — a consumer that\n * wants a settle cell this shape cannot express writes that one cell itself and\n * spreads the rest. PURE: a record of closures over `knob` and `config`, no\n * clock and no RNG.\n */\nexport function mountResilientCall<\n Slice extends string,\n I,\n R,\n Model extends { readonly [K in Slice]: ResilientState<I, R> },\n AttemptType extends string,\n AttemptMsg,\n OkMsg extends { readonly key: string },\n ErrMsg extends { readonly key: string },\n Handlers,\n C extends Cmd = never,\n N extends string = DefaultResilientName,\n>(\n knob: MountableKnob<I, R, OkMsg, ErrMsg, Handlers, N>,\n config: MountConfig<\n Model,\n Slice,\n I,\n R,\n AttemptType,\n AttemptMsg,\n OkMsg,\n ErrMsg,\n C,\n N\n >,\n): MountedResilientCall<\n Model,\n Slice,\n I,\n R,\n AttemptType,\n AttemptMsg,\n OkMsg,\n ErrMsg,\n C,\n Handlers,\n N\n> {\n const { slice, attempt, onOk, onErr, onDeadline } = config;\n\n // A computed key of a generic literal type widens to a string index\n // signature, which is the one place this file asserts: the assertion says\n // only what `Slice extends string` already pins.\n function put(model: Model, next: ResilientState<I, R>): Model {\n return {\n ...model,\n ...({ [slice]: next } as { readonly [K in Slice]: ResilientState<I, R> }),\n };\n }\n\n // The settle shape both `resilient_ok` and `resilient_err` have: inherited\n // verb, then the consumer's fold over the Model it settled.\n function settle<M extends { readonly key: string }>(\n verb: (s: ResilientState<I, R>, key: string, msg: M) => Settle<I, R>,\n fold: SettleFold<Model, M, C> | undefined,\n ): MountedCell<Model, M, I, C> {\n return (model, msg) => {\n const [next, cmds] = verb(model[slice], msg.key, msg);\n const settled = put(model, next);\n if (fold === undefined) return [settled, cmds];\n const [folded, extra] = fold(settled, msg);\n return [folded, [...cmds, ...extra]];\n };\n }\n\n const update = {\n [attempt.on]: (model: Model, msg: AttemptMsg) => {\n const [next, cmds] = attempt.run(model[slice], msg);\n return [put(model, next), cmds] as const;\n },\n // Keyed off the knob's own name, not the `resilient_*` literals: a knob\n // built with `name: \"jev\"` emits `jev_ok` / `jev_err`, and a mount that\n // spelled the literals would spread cells nothing ever dispatches to.\n [`${knob.name}_ok`]: settle<OkMsg>(\n (s, key, msg) => knob.succeed(s, key, msg),\n onOk,\n ),\n [`${knob.name}_err`]: settle<ErrMsg>(\n (s, key, msg) => knob.fail(s, key, msg),\n onErr,\n ),\n // The one settle path that carries no settle Msg: `onTimer` writes\n // `failed` straight into the slice, so a mounted `onErr` never sees it. The\n // cell reads the transition off the two slices instead of inventing a Msg —\n // whichever key moved INTO `failed` is the call that just died.\n //\n // Keyed off the knob's name for the same reason the two settle cells are\n // (#238): the knob's deadline Sub dispatches `<name>_deadline`, so a cell\n // spelling the bare literal would be one two named knobs both claim — and\n // the second spread into `defineMachine` would silently win.\n [deadlineMsgType(deadlineNameOf(knob.name))]: (\n model: Model,\n msg: ResilientTimerMsg<N>,\n ) => {\n const before = model[slice];\n const [next, cmds] = knob.onTimer(before, msg);\n const advanced = put(model, next);\n if (onDeadline === undefined) return [advanced, cmds] as const;\n let folded = advanced;\n const extra: C[] = [];\n for (const [key, call] of Object.entries(next.calls)) {\n if (call.phase !== \"failed\") continue;\n if (before.calls[key]?.phase === \"failed\") continue;\n const [m, c] = onDeadline(folded, { key, error: call.error, msg });\n folded = m;\n extra.push(...c);\n }\n return [folded, [...cmds, ...extra]] as const;\n },\n } as MountedResilientCall<\n Model,\n Slice,\n I,\n R,\n AttemptType,\n AttemptMsg,\n OkMsg,\n ErrMsg,\n C,\n Handlers,\n N\n >[\"update\"];\n\n return {\n init: () =>\n ({ [slice]: knob.init() }) as {\n readonly [K in Slice]: ResilientState<I, R>;\n },\n update,\n subscriptions: (model) => knob.subs(model[slice]),\n subscribe: { deadline: subscribeDeadline },\n interpret: knob.handlers(),\n };\n}\n\n/**\n * Re-export the deadline Sub primitives so consumers (and tests) wire one\n * import: `subscribeDeadline` is the `subscribe` cell, `deadlineSub` builds the\n * Sub literal this knob's `subs` emits.\n */\nexport { subscribeDeadline, deadlineSub };\nexport type { DeadlineSub, DeadlineExceeded };\n"]}
@@ -1,4 +1,4 @@
1
- // src/trace-replay/state-walk.ts
1
+ // src/internal/persistence/trace-replay/state-walk.ts
2
2
  function typeOf(v) {
3
3
  if (v === null) return "null";
4
4
  if (v === void 0) return "undefined";
@@ -17,5 +17,5 @@ function unionKeys(a, b) {
17
17
  }
18
18
 
19
19
  export { indexPath, keyPath, typeOf, unionKeys };
20
- //# sourceMappingURL=chunk-EWJP3YYP.js.map
21
- //# sourceMappingURL=chunk-EWJP3YYP.js.map
20
+ //# sourceMappingURL=chunk-HWVW4AXC.js.map
21
+ //# sourceMappingURL=chunk-HWVW4AXC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/internal/persistence/trace-replay/state-walk.ts"],"names":[],"mappings":";AAoBO,SAAS,OACd,CAAA,EACyD;AACzD,EAAA,IAAI,CAAA,KAAM,MAAM,OAAO,MAAA;AACvB,EAAA,IAAI,CAAA,KAAM,QAAW,OAAO,WAAA;AAC5B,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAA,EAAG,OAAO,OAAA;AAC7B,EAAA,IAAI,OAAO,CAAA,KAAM,QAAA,EAAU,OAAO,QAAA;AAClC,EAAA,OAAO,WAAA;AACT;AAGO,SAAS,SAAA,CAAU,MAAc,CAAA,EAAmB;AACzD,EAAA,OAAO,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,CAAC,CAAA,CAAA,CAAA;AACrB;AAGO,SAAS,OAAA,CAAQ,MAAc,GAAA,EAAqB;AACzD,EAAA,OAAO,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,GAAG,CAAA,CAAA;AACvB;AAQO,SAAS,SAAA,CACd,GACA,CAAA,EACU;AACV,EAAA,OAAO,CAAC,mBAAG,IAAI,IAAI,CAAC,GAAG,OAAO,IAAA,CAAK,CAAC,CAAA,EAAG,GAAG,OAAO,IAAA,CAAK,CAAC,CAAC,CAAC,CAAC,EAAE,IAAA,EAAK;AACnE","file":"chunk-HWVW4AXC.js","sourcesContent":["// ---------------------------------------------------------------------------\n// state-walk — the structural-walk primitives the record/replay lane owns and\n// devtools' `diffState` reuses.\n//\n// Two walkers cross this substrate: `trace-replay`'s `firstDivergence` (first\n// differing cell, for a regression verdict) and `devtools`' `diffState` (EVERY\n// differing cell, classified add/remove/change, for a human diff). They diverge\n// on PURPOSE at the leaf — first-hit vs collect-all, length-mismatch-at-node vs\n// per-element — but they MUST agree on the path grammar: the same cell has to\n// print the same path in both, so a `divergence.path` from a replay result\n// indexes straight into a `StateChange[]`.\n//\n// That agreement used to be enforced by copy-paste discipline plus a comment.\n// Here it is enforced by construction: both walkers import the same `typeOf`,\n// the same `indexPath`/`keyPath` segment builders, and the same `unionKeys`\n// sorted-union order, so key-sorting and leaf equality cannot silently drift\n// between the two. Pure + zero-dep — it runs anywhere a machine runs.\n// ---------------------------------------------------------------------------\n\n/** Coarse type tag both walkers use to decide how to recurse. */\nexport function typeOf(\n v: unknown,\n): \"null\" | \"undefined\" | \"array\" | \"object\" | \"primitive\" {\n if (v === null) return \"null\";\n if (v === undefined) return \"undefined\";\n if (Array.isArray(v)) return \"array\";\n if (typeof v === \"object\") return \"object\";\n return \"primitive\";\n}\n\n/** Path to the `i`-th element of an array node, e.g. `state.queue[2]`. */\nexport function indexPath(path: string, i: number): string {\n return `${path}[${i}]`;\n}\n\n/** Path to a keyed child of an object node, e.g. `state.user.name`. */\nexport function keyPath(path: string, key: string): string {\n return `${path}.${key}`;\n}\n\n/**\n * Sorted union of both sides' own keys — the deterministic, insertion-order-\n * independent child order both walkers traverse. Shared so two states with the\n * same data in different key order compare equal in BOTH, and the reported path\n * for a real mismatch is reproducible run-to-run and identical across walkers.\n */\nexport function unionKeys(\n a: Record<string, unknown>,\n b: Record<string, unknown>,\n): string[] {\n return [...new Set([...Object.keys(a), ...Object.keys(b)])].sort();\n}\n"]}
@@ -1,8 +1,19 @@
1
- import { tryInterpret } from './chunk-ZSUUOEPL.js';
2
- import { Cmd } from './chunk-OOYD44JI.js';
1
+ import { Cmd, tryInterpret } from './chunk-66PY5WYF.js';
2
+ import { z } from 'zod';
3
3
 
4
- // src/snapshot/index.ts
5
4
  var DEFAULT_SNAPSHOT_KEY = "@@snapshot";
5
+ function snapshotWriteDef() {
6
+ return Cmd.define("snapshot_write", {
7
+ input: z.custom(),
8
+ ok: z.void(),
9
+ err: []
10
+ });
11
+ }
12
+ var snapshotLoad = Cmd.define("snapshot_load", {
13
+ input: z.object({ key: z.string() }),
14
+ ok: z.void(),
15
+ err: []
16
+ });
6
17
  function snapshotSaved(seq, at) {
7
18
  return { type: "snapshot_saved", seq, at };
8
19
  }
@@ -18,17 +29,11 @@ function snapshotLoadFailed(error) {
18
29
  function createSnapshot(config) {
19
30
  const every = config.every >= 1 ? config.every : 1;
20
31
  const key = config.key ?? DEFAULT_SNAPSHOT_KEY;
32
+ const snapshotWrite = snapshotWriteDef();
21
33
  function emitWrite(state, payload, at) {
22
34
  const seq = state.seq + 1;
23
35
  const next = { ...state, sinceLast: 0, seq };
24
- const cmd = {
25
- type: "snapshot_write",
26
- key,
27
- seq,
28
- at,
29
- payload
30
- };
31
- return [next, [cmd]];
36
+ return [next, [snapshotWrite({ key, seq, at, payload })]];
32
37
  }
33
38
  return {
34
39
  init() {
@@ -57,8 +62,7 @@ function createSnapshot(config) {
57
62
  return [{ ...state, sinceLast: 0 }, Cmd.none];
58
63
  },
59
64
  requestLoad(state) {
60
- const cmd = { type: "snapshot_load", key };
61
- return [state, [cmd]];
65
+ return [state, [snapshotLoad({ key })]];
62
66
  },
63
67
  handlers(ports) {
64
68
  const { store } = ports;
@@ -88,6 +92,6 @@ function createSnapshot(config) {
88
92
  };
89
93
  }
90
94
 
91
- export { createSnapshot, snapshotFailed, snapshotLoadFailed, snapshotLoaded, snapshotSaved };
92
- //# sourceMappingURL=chunk-O4MVQTEJ.js.map
93
- //# sourceMappingURL=chunk-O4MVQTEJ.js.map
95
+ export { createSnapshot, snapshotFailed, snapshotLoad, snapshotLoadFailed, snapshotLoaded, snapshotSaved, snapshotWriteDef };
96
+ //# sourceMappingURL=chunk-L3TND7LA.js.map
97
+ //# sourceMappingURL=chunk-L3TND7LA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/internal/persistence/snapshot/index.ts"],"names":[],"mappings":"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","file":"chunk-L3TND7LA.js","sourcesContent":["/**\n * internal/persistence/snapshot — periodic state checkpoint to a host store\n * (R2/KV-shaped). Internal since #49 — not published on any subpath.\n *\n * The problem it solves: a long-running machine (a multi-stage run, a paginated\n * crawl, a saga) lives in TEA's in-memory `State` and is persisted as ONE blob\n * by the substrate's `Store<S>` on every transition. That blob is fine for\n * crash recovery of the *runtime* — but it is NOT a coarse, named checkpoint a\n * human (or a separate recovery job) can point a restart at. `snapshot` adds\n * the \"every N units of progress, write a durable checkpoint to R2/KV\"\n * discipline on top, so a crashed long run restarts from the last checkpoint\n * instead of from zero (the field-guide pain row).\n *\n * Shape: this is the same knob contract every L2 composition follows —\n * `createSnapshot(config)` hands back a `{ init, <verbs>, handlers }` bundle you\n * splice into your machine. The state it owns is a Model slice (durable +\n * replayable, the two non-negotiables); the verbs are PURE decisions; the I/O\n * (the actual `put` to the store) lives only in `handlers`.\n *\n * It is a LEAF module: it imports only the core substrate (`../index`, for\n * `Cmd` / `Interpret`) and no sibling subpath. (It does NOT reuse\n * `@demlik/tea/retry-backoff` — that is an even-leafier utility that depends on\n * nothing at all, not even the core; the kinship here is \"leaf with injected\n * time\", not a shared dependency.)\n * Nothing here reads the clock or the RNG on its own behalf — time enters as an\n * `at`/`now` parameter at the verb boundary and the store I/O is realized only\n * when the runtime performs the emitted `Cmd`. That keeps every verb\n * deterministic (invariant 2) and the checkpoint write a `Cmd` the runtime\n * interprets (invariant 3).\n *\n * The Store port is deliberately ABSTRACT — `{ get, put }`, the lowest common\n * shape of R2 and KV — so this module never imports `@cloudflare/workers-types`\n * and can be checkpointed to anything (R2, KV, a DO sub-key, an in-memory map in\n * tests). The host binds the concrete adapter at `handlers({ store })`.\n *\n * Typical wiring:\n *\n * const snap = createSnapshot<RunState>({ every: 25, key: \"run/checkpoint\" });\n *\n * // machine state:\n * type State = { run: RunState; snapshot: SnapshotState };\n * init: (loaded) => loaded ? [loaded, []] : [{ run: initRun(), snapshot: snap.init() }, []],\n *\n * // in `update`, on each progress Msg, fold the snapshot slice:\n * progress: (s, msg) => {\n * const run = advance(s.run, msg);\n * const [snapshot, cmds] = snap.record(s.snapshot, run, msg.at);\n * return [{ ...s, run, snapshot }, cmds];\n * },\n * // the write completed → durable ack folds back through update:\n * snapshot_saved: (s, msg) => {\n * const [snapshot, cmds] = snap.confirm(s.snapshot, msg);\n * return [{ ...s, snapshot }, cmds];\n * },\n *\n * // RECOVERY — the half that makes a checkpoint a restart point, not just a\n * // backup. A `boot` Msg the host dispatches once after `run(...)` asks the\n * // store for the durable checkpoint; the answer folds the recovered payload\n * // back into the run slice:\n * boot: (s) => snap.requestLoad(s), // emits snapshot_load Cmd\n * snapshot_loaded: (s, msg) => {\n * if (msg.payload === null) return [s, []]; // no checkpoint → keep fresh init\n * const [snapshot, cmds] = snap.boot(s.snapshot);\n * return [{ ...s, run: msg.payload, snapshot }, cmds];\n * },\n * snapshot_load_failed: (s) => [s, []], // errors are data — decide here\n *\n * // splice the I/O — both effect cells (write + load):\n * interpret: { ...snap.handlers({ store: r2Adapter }) },\n */\n\nimport { z } from \"zod\";\nimport { Cmd, type CmdOf, type Interpret, tryInterpret } from \"../../../index\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Config — the knob.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The snapshot knob.\n *\n * `every` is the cadence: a checkpoint is written once `every` units of\n * progress have accumulated since the last one (see `record`). It is the\n * `snapshotEvery` field `monitored-run` threads through to this module.\n *\n * `key` is the store key the checkpoint is written under. A single machine\n * keeps a single rolling checkpoint, so the key is fixed at config time rather\n * than passed per write — the latest checkpoint overwrites the previous one\n * (R2/KV are last-write-wins on a key). Defaults to `\"@@snapshot\"`, mirroring\n * `doStore`'s `@@state` default-key convention.\n */\nexport interface SnapshotConfig {\n /**\n * Write a checkpoint once this many progress units have accumulated since the\n * last write. `every: 1` checkpoints on every `record`; a value `<= 0` is\n * treated as `1` (every progress unit triggers a write) — a non-positive\n * cadence has no other sensible meaning, and clamping keeps `record` from\n * never checkpointing on a misconfigured `0`.\n */\n readonly every: number;\n /** Store key the rolling checkpoint is written under. Defaults to `\"@@snapshot\"`. */\n readonly key?: string;\n}\n\nconst DEFAULT_SNAPSHOT_KEY = \"@@snapshot\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Slice — the Model field this knob owns.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The snapshot bookkeeping slice. Plain data — JSON-serializable so it rides\n * inside the consumer's `Store<S>` blob and survives DO eviction / reload (the\n * \"durable\" non-negotiable). It carries NO payload: the thing being\n * checkpointed lives elsewhere in the machine's state; this slice only tracks\n * *when* to checkpoint and *what has been acknowledged durable*.\n *\n * - `sinceLast` — progress units accumulated since the last checkpoint\n * DECISION (reset to 0 the moment `record` decides to write, not when the\n * write lands). The cadence counter.\n * - `seq` — monotonic checkpoint sequence. Bumped each time a write is\n * emitted, so the write Cmd and its later acknowledgement can be matched\n * even if several are in flight. The \"version\" of the latest emitted\n * checkpoint.\n * - `lastSavedSeq` — the highest `seq` whose write the host has confirmed\n * durable (via `confirm`). `null` until the first confirmed write. The gap\n * `seq - lastSavedSeq` is the \"writes emitted but not yet acknowledged\"\n * depth — observable for backpressure / health.\n * - `lastSavedAt` — the `at` of the most recent confirmed write (epoch ms),\n * `null` until the first. Carried for observability, never compared inside\n * a verb (time is injected, never read here).\n */\nexport interface SnapshotState {\n readonly sinceLast: number;\n readonly seq: number;\n readonly lastSavedSeq: number | null;\n readonly lastSavedAt: number | null;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Store port — abstract R2/KV-shaped checkpoint sink.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The abstract checkpoint sink — the intersection of R2's and KV's surface,\n * narrowed to exactly the two methods a rolling checkpoint needs. Deliberately\n * NOT the substrate's `Store<S>` (`{ load, save, migrate }`): that is the\n * runtime's whole-state persistence boundary keyed at a fixed cell, whereas a\n * checkpoint store is keyed, value-typed, and host-supplied.\n *\n * - `put(key, value)` — write the checkpoint. Last-write-wins on `key`.\n * - `get(key)` — read the latest checkpoint back, or `null` if none. Used by\n * a recovery path to seed `init`'s `loaded` from the durable checkpoint\n * when the runtime's own `Store<S>` blob is gone.\n *\n * No `@cloudflare/workers-types` import — an R2 bucket, a KV namespace, a DO\n * sub-key, or an in-memory `Map` all satisfy this shape with a 3-line adapter\n * at the host.\n */\nexport interface SnapshotStore<V> {\n get(key: string): Promise<V | null>;\n put(key: string, value: V): Promise<void>;\n}\n\n/**\n * The ports `handlers` binds. Just the store today — named so a future port\n * (a metrics sink, a clock) can grow without breaking the `handlers(ports)`\n * call shape.\n */\nexport interface SnapshotPorts<V> {\n readonly store: SnapshotStore<V>;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Cmd + Msg — the write effect and its durable acknowledgement.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The checkpoint-write Cmd a `record` / `force` decision emits. Plain data\n * (invariant 1 — Cmds are data, not closures): it carries the `payload` to\n * write, the `key` to write it under, the `seq` it represents, and the `at` it\n * was decided. The handler performs the `put` and routes the result back to a\n * `confirm` / fail Msg.\n *\n * `payload` is `V` — the consumer's serializable checkpoint value (the run\n * state, the cursor, whatever the machine wants restartable). The verb takes it\n * as an argument so the snapshot slice never has to hold a copy of the\n * machine's domain state.\n *\n * `Cmd.define`d (ADR 0014), minted per `V` by a factory the way\n * `workflowActivityDef<A>()` is, since the payload type is the knob's own\n * parameter. The handler answers through this module's OWN Msgs\n * (`snapshot_saved` / `snapshot_failed`), never the minted `_ok` / `_err`,\n * hence `ok: z.void()` and an empty `err` list.\n */\nexport function snapshotWriteDef<V>() {\n return Cmd.define(\"snapshot_write\", {\n input: z.custom<{\n readonly key: string;\n readonly seq: number;\n readonly at: number;\n readonly payload: V;\n }>(),\n ok: z.void(),\n err: [],\n });\n}\nexport type SnapshotWriteCmd<V> = CmdOf<ReturnType<typeof snapshotWriteDef<V>>>;\n\n/**\n * The Msg the write handler dispatches on a SUCCESSFUL `put`. Fold it back\n * through `confirm(state, msg)` to advance `lastSavedSeq` / `lastSavedAt`. The\n * `seq` matches the emitting Cmd so a late acknowledgement of an older write\n * can never regress a newer confirmed one.\n */\nexport type SnapshotSavedMsg = {\n readonly type: \"snapshot_saved\";\n readonly seq: number;\n readonly at: number;\n};\n\n/**\n * The Msg the write handler dispatches on a FAILED `put`. Errors are data\n * (never swallowed): the consumer's reducer decides whether a failed checkpoint\n * is fatal, retryable (compose `@demlik/tea/retry-backoff`), or merely logged.\n * `seq` identifies which checkpoint failed; `error` is carried, never\n * interpreted here.\n */\nexport type SnapshotFailedMsg = {\n readonly type: \"snapshot_failed\";\n readonly seq: number;\n readonly error: unknown;\n};\n\n/**\n * The checkpoint-READ Cmd a `requestLoad` decision emits — the recovery half of\n * the module. Plain data (invariant 1): it carries only the `key` to read. The\n * handler performs `store.get(key)` and routes the result back to a\n * `snapshot_loaded` (Ok, payload-or-null) / `snapshot_load_failed` (Err) Msg.\n *\n * Distinct from `snapshot_write`: a write carries a payload OUT to the store; a\n * load asks the store to hand a payload BACK. The consumer dispatches the\n * triggering Msg once on resume (a `boot` Msg, never `init`'s rehydrate branch —\n * invariant 2), folds the recovered payload into its run slice, then continues.\n */\nexport const snapshotLoad = Cmd.define(\"snapshot_load\", {\n input: z.object({ key: z.string() }),\n ok: z.void(),\n err: [],\n});\nexport type SnapshotLoadCmd = CmdOf<typeof snapshotLoad>;\n\n/**\n * The Msg the load handler dispatches when `store.get` RESOLVES. `payload` is\n * the recovered checkpoint value, or `null` when the store has no checkpoint\n * under `key` (fresh boot — never checkpointed, or a wiped store). The consumer\n * folds this into `init`'s loaded branch: `payload === null` → keep the fresh\n * `init` state; non-null → seed the run slice from the durable checkpoint and\n * `boot` the snapshot slice's cadence counter.\n *\n * A `null` payload is NOT an error — \"no checkpoint yet\" is a normal first-run\n * state, distinct from \"the read failed\" (`snapshot_load_failed`).\n */\nexport type SnapshotLoadedMsg<V> = {\n readonly type: \"snapshot_loaded\";\n readonly payload: V | null;\n};\n\n/**\n * The Msg the load handler dispatches when `store.get` REJECTS. Errors are data\n * (never swallowed — the field-guide rule): a failed recovery read is a visible\n * signal the consumer's reducer decides on (fatal-abort, retry via\n * `@demlik/tea/retry-backoff`, or boot-fresh-and-log). `error` is carried, never\n * interpreted here. Kept separate from `snapshot_loaded`'s `null` so \"the store\n * has no checkpoint\" can never masquerade as \"the read failed\".\n */\nexport type SnapshotLoadFailedMsg = {\n readonly type: \"snapshot_load_failed\";\n readonly error: unknown;\n};\n\n/** Construct a `snapshot_saved` Msg. Shared by the handler and tests so the wire shape can't drift. */\nexport function snapshotSaved(seq: number, at: number): SnapshotSavedMsg {\n return { type: \"snapshot_saved\", seq, at };\n}\n\n/** Construct a `snapshot_failed` Msg. Shared by the handler and tests so the wire shape can't drift. */\nexport function snapshotFailed(seq: number, error: unknown): SnapshotFailedMsg {\n return { type: \"snapshot_failed\", seq, error };\n}\n\n/** Construct a `snapshot_loaded` Msg. Shared by the handler and tests so the wire shape can't drift. */\nexport function snapshotLoaded<V>(payload: V | null): SnapshotLoadedMsg<V> {\n return { type: \"snapshot_loaded\", payload };\n}\n\n/** Construct a `snapshot_load_failed` Msg. Shared by the handler and tests so the wire shape can't drift. */\nexport function snapshotLoadFailed(error: unknown): SnapshotLoadFailedMsg {\n return { type: \"snapshot_load_failed\", error };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// The combinator.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The bundle `createSnapshot` returns — the uniform knob contract. `subs` is\n * omitted: a checkpoint is driven by progress (a `record` call from the\n * consumer's reducer), not by a timer, so this module has no subscriptions.\n */\nexport interface SnapshotKnob<V> {\n /** The initial slice — zero progress, no checkpoint emitted or confirmed yet. */\n init(): SnapshotState;\n /**\n * Account one unit of progress. PURE. Increments `sinceLast`; once the\n * accumulated count reaches the configured cadence, DECIDES to checkpoint:\n * resets `sinceLast` to 0, bumps `seq`, and emits a single `snapshot_write`\n * Cmd carrying `payload` + `key` + the new `seq` + `at`. Below the cadence it\n * returns the bumped slice and NO Cmd.\n *\n * `payload` is the checkpoint value to persist (the consumer's domain state);\n * `at` is the caller-stamped time the progress occurred (epoch ms) — injected,\n * never read from the clock here.\n */\n record(\n state: SnapshotState,\n payload: V,\n at: number,\n ): readonly [SnapshotState, readonly Cmd[]];\n /**\n * Force a checkpoint regardless of the cadence counter. PURE. Always bumps\n * `seq`, resets `sinceLast`, and emits the write Cmd. Use on a phase boundary\n * the cadence would otherwise miss — e.g. \"the run just finished, checkpoint\n * the terminal state now\" — so the last durable checkpoint is the final state,\n * not whatever the modulo happened to land on.\n */\n force(\n state: SnapshotState,\n payload: V,\n at: number,\n ): readonly [SnapshotState, readonly Cmd[]];\n /**\n * Record a confirmed durable write. PURE. Advances `lastSavedSeq` /\n * `lastSavedAt` to the acknowledged `seq` / `at`, but only forward: an\n * out-of-order acknowledgement of an OLDER `seq` (a slow write landing after a\n * newer one) is ignored, so the confirmed watermark is monotonic. Fold the\n * `snapshot_saved` Msg here.\n *\n * Returns the reducer-cell shape `readonly [SnapshotState, readonly Cmd[]]`\n * (with `Cmd.none` — an ack emits nothing) for uniformity with every sibling\n * knob, so it drops straight into a `snapshot_saved` cell without the caller\n * re-wrapping a bare State.\n */\n confirm(\n state: SnapshotState,\n msg: SnapshotSavedMsg,\n ): readonly [SnapshotState, readonly Cmd[]];\n /**\n * Resume after a reload. PURE. Resets `sinceLast` to 0 because any\n * un-checkpointed progress accumulated before the crash is moot — recovery\n * resumes from the last DURABLE checkpoint (`get` from the store), so the\n * cadence counter starts fresh from there. `seq` / `lastSavedSeq` /\n * `lastSavedAt` are preserved (they describe what is durable, which a reload\n * does not change). Call from a `boot` Msg the host dispatches once after\n * `run(...)`, never from `init`'s rehydrate branch (invariant 2).\n *\n * Returns the reducer-cell shape `readonly [SnapshotState, readonly Cmd[]]`\n * (with `Cmd.none` — a resume emits nothing) for uniformity with every sibling\n * knob, so it drops straight into a `snapshot_loaded` cell without the caller\n * re-wrapping a bare State.\n */\n boot(state: SnapshotState): readonly [SnapshotState, readonly Cmd[]];\n /**\n * Ask the store for the durable checkpoint. PURE. Emits a single\n * `snapshot_load` Cmd carrying the configured `key`; returns the slice\n * UNCHANGED (a read decides nothing about the cadence bookkeeping — only the\n * later `snapshot_loaded` fold, via `boot`, touches the slice). This is the\n * entry point of the recovery loop: the consumer dispatches a `boot` Msg once\n * after `run(...)`, folds `requestLoad` here, and the emitted Cmd's handler\n * reads `store.get(key)` and dispatches `snapshot_loaded` / `snapshot_load_failed`.\n *\n * Returns `readonly [SnapshotState, readonly Cmd[]]` (the reducer-cell shape)\n * so it drops straight into a `boot: (s) => snap.requestLoad(s)` cell.\n */\n requestLoad(state: SnapshotState): readonly [SnapshotState, readonly Cmd[]];\n /**\n * The `interpret` map for this knob's two effect Cmds:\n *\n * - `snapshot_write` — binds the store and performs the `put`, routing\n * Ok → `snapshot_saved` / Err → `snapshot_failed`.\n * - `snapshot_load` — binds the store and performs the `get`, routing\n * Ok → `snapshot_loaded` (payload-or-null) / Err → `snapshot_load_failed`.\n *\n * Railway throughout — neither effect throws into the runtime; the outcome is\n * always a Msg. Spread into your machine's `interpret`.\n */\n handlers(\n ports: SnapshotPorts<V>,\n ): Interpret<\n | SnapshotSavedMsg\n | SnapshotFailedMsg\n | SnapshotLoadedMsg<V>\n | SnapshotLoadFailedMsg,\n SnapshotWriteCmd<V> | SnapshotLoadCmd,\n unknown\n >;\n}\n\n/**\n * Create a snapshot knob from a `SnapshotConfig`. The returned bundle is spliced\n * into a consumer machine: `init()` seeds the slice, `record` / `force` are\n * folded in reducer cells, `confirm` / `boot` handle the acknowledgement +\n * resume Msgs, and `handlers({ store })` supplies the write `interpret` cell.\n */\nexport function createSnapshot<V>(config: SnapshotConfig): SnapshotKnob<V> {\n // Clamp the cadence to >= 1 at construction so `record`'s threshold check is\n // branchless and a misconfigured `0` / negative `every` checkpoints on every\n // progress unit instead of never.\n const every = config.every >= 1 ? config.every : 1;\n const key = config.key ?? DEFAULT_SNAPSHOT_KEY;\n const snapshotWrite = snapshotWriteDef<V>();\n\n /**\n * Emit the write Cmd for a checkpoint decision. Shared by `record`'s\n * cadence-hit branch and `force`. Bumps `seq`, resets `sinceLast`, returns the\n * new slice + the single `snapshot_write` Cmd. The input slice is never\n * mutated (invariant 1).\n */\n function emitWrite(\n state: SnapshotState,\n payload: V,\n at: number,\n ): readonly [SnapshotState, readonly Cmd[]] {\n const seq = state.seq + 1;\n const next: SnapshotState = { ...state, sinceLast: 0, seq };\n return [next, [snapshotWrite({ key, seq, at, payload })]];\n }\n\n return {\n init(): SnapshotState {\n return { sinceLast: 0, seq: 0, lastSavedSeq: null, lastSavedAt: null };\n },\n\n record(state, payload, at) {\n const sinceLast = state.sinceLast + 1;\n // Below the cadence: accumulate, emit nothing.\n if (sinceLast < every) {\n return [{ ...state, sinceLast }, Cmd.none];\n }\n // Cadence reached: checkpoint. `emitWrite` resets the counter + bumps seq.\n return emitWrite(state, payload, at);\n },\n\n force(state, payload, at) {\n return emitWrite(state, payload, at);\n },\n\n confirm(state, msg) {\n // Forward-only watermark: ignore a stale ack of an older seq so a slow\n // write landing after a newer one can't regress `lastSavedSeq`.\n if (state.lastSavedSeq !== null && msg.seq <= state.lastSavedSeq) {\n return [state, Cmd.none];\n }\n return [\n { ...state, lastSavedSeq: msg.seq, lastSavedAt: msg.at },\n Cmd.none,\n ];\n },\n\n boot(state) {\n return [{ ...state, sinceLast: 0 }, Cmd.none];\n },\n\n requestLoad(state) {\n // A read decides nothing about the cadence bookkeeping — return the slice\n // unchanged and emit the single read Cmd. The recovered payload folds back\n // through `snapshot_loaded` (the consumer's reducer), not here.\n return [state, [snapshotLoad({ key })]];\n },\n\n handlers(ports) {\n const { store } = ports;\n return {\n // The write effect. Performs the `put`; on success the saved `seq`/`at`\n // ride the `snapshot_saved` Msg back into `confirm`; on failure the\n // error rides `snapshot_failed`. Railway via the core's `tryInterpret`\n // (matching every other module's handler) — the handler always resolves\n // with a Msg, never rejects into the runtime.\n snapshot_write: tryInterpret<\n SnapshotWriteCmd<V>,\n void,\n SnapshotSavedMsg | SnapshotFailedMsg,\n unknown\n >(\n (cmd) => store.put(cmd.key, cmd.payload),\n (_value, cmd) => snapshotSaved(cmd.seq, cmd.at),\n (error, cmd) => snapshotFailed(cmd.seq, error),\n ),\n // The read effect — the recovery half. Performs the `get`; on success\n // the recovered payload (or `null` when no checkpoint exists) rides the\n // `snapshot_loaded` Msg the consumer folds into `init`'s loaded branch;\n // on failure the error rides `snapshot_load_failed`. Same Railway\n // discipline via `tryInterpret` — the read never rejects into the runtime.\n snapshot_load: tryInterpret<\n SnapshotLoadCmd,\n V | null,\n SnapshotLoadedMsg<V> | SnapshotLoadFailedMsg,\n unknown\n >(\n (cmd) => store.get(cmd.key),\n (payload) => snapshotLoaded(payload),\n (error) => snapshotLoadFailed(error),\n ),\n };\n },\n };\n}\n"]}
@@ -1,4 +1,4 @@
1
- // src/debounce/index.ts
1
+ // src/internal/timing/debounce/index.ts
2
2
  function debounce(fn, ms, opts) {
3
3
  const leading = opts?.leading ?? false;
4
4
  const trailing = opts?.trailing ?? true;
@@ -52,5 +52,5 @@ function debounce(fn, ms, opts) {
52
52
  }
53
53
 
54
54
  export { debounce };
55
- //# sourceMappingURL=chunk-J6527VRQ.js.map
56
- //# sourceMappingURL=chunk-J6527VRQ.js.map
55
+ //# sourceMappingURL=chunk-LFZUK6J2.js.map
56
+ //# sourceMappingURL=chunk-LFZUK6J2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/internal/timing/debounce/index.ts"],"names":[],"mappings":";AAoHO,SAAS,QAAA,CACd,EAAA,EACA,EAAA,EACA,IAAA,EACc;AACd,EAAA,MAAM,OAAA,GAAU,MAAM,OAAA,IAAW,KAAA;AACjC,EAAA,MAAM,QAAA,GAAW,MAAM,QAAA,IAAY,IAAA;AAMnC,EAAA,IAAI,MAAA,GAA+C,IAAA;AAOnD,EAAA,IAAI,WAAA,GAAwB,IAAA;AAK5B,EAAA,IAAI,YAAA,GAAe,KAAA;AAEnB,EAAA,SAAS,WAAA,GAAoB;AAC3B,IAAA,IAAI,WAAW,IAAA,EAAM;AACnB,MAAA,YAAA,CAAa,MAAM,CAAA;AACnB,MAAA,MAAA,GAAS,IAAA;AAAA,IACX;AAAA,EACF;AAKA,EAAA,SAAS,cAAA,GAAuB;AAC9B,IAAA,MAAA,GAAS,IAAA;AAQT,IAAA,IAAI,QAAA,IAAY,gBAAgB,IAAA,EAAM;AACpC,MAAA,MAAM,IAAA,GAAO,WAAA;AACb,MAAA,WAAA,GAAc,IAAA;AACd,MAAA,EAAA,CAAG,GAAG,IAAI,CAAA;AAAA,IACZ;AAEA,IAAA,YAAA,GAAe,KAAA;AAAA,EACjB;AAEA,EAAA,MAAM,SAAA,GAAY,IAAI,IAAA,KAAkB;AAItC,IAAA,MAAM,eAAe,MAAA,KAAW,IAAA;AAChC,IAAA,IAAI,OAAA,IAAW,YAAA,IAAgB,CAAC,YAAA,EAAc;AAC5C,MAAA,YAAA,GAAe,IAAA;AACf,MAAA,EAAA,CAAG,GAAG,IAAI,CAAA;AAAA,IACZ,WAAW,QAAA,EAAU;AAGnB,MAAA,WAAA,GAAc,IAAA;AAAA,IAChB;AAKA,IAAA,WAAA,EAAY;AACZ,IAAA,MAAA,GAAS,UAAA,CAAW,gBAAgB,EAAE,CAAA;AAAA,EACxC,CAAA;AAEA,EAAA,SAAA,CAAU,SAAS,MAAY;AAC7B,IAAA,WAAA,EAAY;AACZ,IAAA,WAAA,GAAc,IAAA;AACd,IAAA,YAAA,GAAe,KAAA;AAAA,EACjB,CAAA;AAEA,EAAA,SAAA,CAAU,QAAQ,MAAY;AAI5B,IAAA,IAAI,gBAAgB,IAAA,EAAM;AACxB,MAAA,MAAM,IAAA,GAAO,WAAA;AACb,MAAA,WAAA,GAAc,IAAA;AACd,MAAA,WAAA,EAAY;AAEZ,MAAA,YAAA,GAAe,KAAA;AACf,MAAA,EAAA,CAAG,GAAG,IAAI,CAAA;AAAA,IACZ,CAAA,MAAO;AAGL,MAAA,WAAA,EAAY;AACZ,MAAA,YAAA,GAAe,KAAA;AAAA,IACjB;AAAA,EACF,CAAA;AAEA,EAAA,OAAO,SAAA;AACT","file":"chunk-LFZUK6J2.js","sourcesContent":["/**\n * internal/timing/debounce — a timer-based call transformer that coalesces a burst\n * of calls into a single fire.\n *\n * Sibling of `../throttle` — the two share a SYMMETRIC surface\n * (`Debounced<A>` / `Throttled<A>`, both callable + `.cancel()` + `.flush()`,\n * both `{ leading?, trailing? }`) so they read as a pair. They are kept in\n * separate files with NO cross-import: each is a self-contained timer, and the\n * coalesce-vs-drop policies are different enough that sharing code would hide\n * the distinction. Read one to understand the other; do not couple them.\n *\n * Layering with `../throttled-input`: `debounce` and `throttle` are\n * the two host-boundary PRIMITIVES — two distinct algorithms (collapse-a-burst\n * vs at-most-once-per-window) behind one signature shape. `throttled-input` is\n * NOT a third primitive: it is a TEA machine composed OVER them that\n * re-exports both (see its module doc for the full layering map).\n *\n * WHAT THIS IS NOT — and why it lives outside `update`:\n *\n * - NOT a reducer / Transitions cell. Reducers are PURE (invariant 2 — same\n * inputs → same outputs, no `Date.now`, no timers). A debounce holds a live\n * `setTimeout` handle and mutable pending-args state; folding it into\n * `update` would smuggle a timer into the pure core.\n * - NOT a Sub. A Sub is a continuous SOURCE of Msgs the substrate reconciles\n * by id every transition (invariant 4). A debounce is a SINK transformer:\n * it wraps a `void`-returning handler (the canonical one is\n * `Runtime.dispatch`) so bursty HOST events coalesce BEFORE they ever\n * become a Msg.\n *\n * It is a higher-order function at the host boundary (pattern 14 — TEA owns\n * state, JS owns timing). The host attaches the wrapped function to a noisy\n * event source (`scroll`, `resize`, `input`, a ResizeObserver) and the\n * substrate downstream only ever sees the coalesced Msg:\n *\n * const onResized = debounce(\n * () => runtime.dispatch({ type: \"BrowserResizedWindow\" }),\n * 150,\n * );\n * window.addEventListener(\"resize\", onResized);\n * // cleanup: onResized.cancel(); window.removeEventListener(\"resize\", onResized);\n *\n * Internal since #47 — not published on any subpath; reached from inside the\n * package as `internal/timing/debounce`. Same one-shape-per-package rule as\n * `rate-limit`, `deadline`, and `work-queue`.\n *\n * Uses the universal `setTimeout` / `clearTimeout` timer idiom (same discipline\n * as `subs/from-timeout.ts`): exactly one live handle at a time, every arm\n * clears the previous, every fire / cancel clears the handle and nulls it so a\n * stale callback can never run.\n */\n\n/**\n * A debounced wrapper around `fn`. Callable with the SAME argument tuple as\n * `fn` — `(...args: A) => void` — plus two control methods that share names and\n * shapes with `Throttled<A>` so the two modules read as a symmetric pair.\n */\nexport interface Debounced<A extends readonly unknown[]> {\n /** Schedule (or re-schedule) a fire of the wrapped `fn`. See `debounce`. */\n (...args: A): void;\n /**\n * Drop any pending trailing fire WITHOUT invoking `fn`. Clears the timer and\n * forgets the captured args. Idempotent — calling it with nothing pending is\n * a no-op. The leading-edge latch (if `leading` is enabled) also resets, so\n * the next call after `cancel` is treated as a fresh burst.\n *\n * The host-cleanup partner of `removeEventListener`: cancel the pending fire\n * when the component unmounts / the listener detaches, so a queued\n * `dispatch` can't land after teardown.\n */\n cancel(): void;\n /**\n * Fire any pending trailing call IMMEDIATELY with its captured args, then\n * clear the timer. No-op when nothing is pending. Use to force the last\n * coalesced call out early — e.g. flush a debounced save on `beforeunload`,\n * or flush a debounced search when the user presses Enter.\n */\n flush(): void;\n}\n\n/**\n * Wrap `fn` so a BURST of calls collapses to a single invocation.\n *\n * Trailing edge (the default): each call (re)starts a `ms` timer; `fn` fires\n * once with the LAST args after `ms` of quiet. Every call within `ms` of the\n * previous resets the timer — the classic \"wait until the user stops typing\"\n * behavior.\n *\n * @param fn The handler to coalesce. Typically `(...) => runtime.dispatch(...)`,\n * but any `(...args: A) => void` works. The return value is\n * ignored — debounce is a fire transformer, not a value pipe.\n * @param ms Quiet window in milliseconds. Calls closer together than `ms`\n * coalesce; a gap of at least `ms` lets the trailing fire through.\n * @param opts.leading Fire on the FIRST call of a burst (the leading edge),\n * with that first call's args. Default `false`.\n * @param opts.trailing Fire on the trailing edge with the LAST call's args.\n * Default `true`.\n *\n * Edge combinations (matching lodash's settled semantics, the de-facto\n * standard the ecosystem rediscovered):\n *\n * - `{ trailing: true }` (default): one fire, after the burst, last args.\n * - `{ leading: true, trailing: false }`: one fire, at the burst START,\n * first args. Subsequent calls within the window are swallowed; the timer\n * only re-opens the \"can lead again\" latch after `ms` of quiet.\n * - `{ leading: true, trailing: true }`: fires at the start AND end of a\n * burst — BUT the trailing fire is SUPPRESSED for a burst of exactly ONE\n * call (the leading fire already covered it, so a lone call doesn't\n * double-fire). This matches lodash and is the behavior tests pin.\n * - `{ leading: false, trailing: false }`: never fires. Degenerate but legal;\n * we don't throw — the caller asked for a no-op transformer.\n *\n * WHY a default of trailing-only: a debounce's whole job is \"act after the\n * activity settles.\" The trailing edge IS that semantic — fire with the final\n * state of the burst (the last keystroke, the final scroll position). Leading\n * is the opt-in for \"respond instantly, then go quiet.\"\n */\nexport function debounce<A extends readonly unknown[]>(\n fn: (...args: A) => void,\n ms: number,\n opts?: { leading?: boolean; trailing?: boolean },\n): Debounced<A> {\n const leading = opts?.leading ?? false;\n const trailing = opts?.trailing ?? true;\n\n // Exactly one live handle at a time (the from-timeout discipline). `null`\n // when no fire is pending — the single source of truth for \"is a timer\n // armed?\". Every arm clears the previous handle before opening a new one, so\n // we never leak overlapping timers across a burst.\n let handle: ReturnType<typeof setTimeout> | null = null;\n // The most recent args, held only while a trailing fire is pending. Captured\n // on every call (the trailing edge fires with the LAST args), cleared the\n // instant we fire / cancel so a stale tuple can never leak into a later\n // burst. `null` (not `undefined`) is the empty sentinel because `A` may\n // itself contain `undefined` elements — a present-but-empty tuple is still a\n // pending call, distinct from \"nothing pending\".\n let pendingArgs: A | null = null;\n // Leading-edge latch: true once a leading fire has happened for the current\n // burst, reset to false after `ms` of quiet (the timer's expiry) or on\n // `cancel`. Gates the \"fire on first call of a burst, then stay quiet\" rule\n // so mid-burst calls don't re-trigger the leading fire.\n let leadingFired = false;\n\n function clearHandle(): void {\n if (handle !== null) {\n clearTimeout(handle);\n handle = null;\n }\n }\n\n // Timer expiry: the burst has gone quiet for `ms`. Fire the trailing edge if\n // one is owed, then reset all burst state so the NEXT call starts a fresh\n // burst (re-arming the leading latch, clearing pending args).\n function onTimerExpired(): void {\n handle = null;\n // Suppress the trailing fire for a lone leading-only-eligible call:\n // when `leadingFired` is true AND trailing is enabled, a burst of exactly\n // one call already fired on the leading edge — `pendingArgs` was cleared\n // at that point, so there is nothing to fire here. A burst of 2+ calls\n // re-captured `pendingArgs` after the leading fire, so the trailing fire\n // lands with the last args. The `pendingArgs !== null` guard encodes\n // exactly that distinction; no separate call-count needed.\n if (trailing && pendingArgs !== null) {\n const args = pendingArgs;\n pendingArgs = null;\n fn(...args);\n }\n // Burst over — re-open the leading latch for the next burst.\n leadingFired = false;\n }\n\n const debounced = (...args: A): void => {\n // Leading edge: the FIRST call of a burst (no timer armed yet, latch open).\n // Fire immediately with this call's args; do NOT stash them as pending so a\n // lone call won't also fire on the trailing edge.\n const isBurstStart = handle === null;\n if (leading && isBurstStart && !leadingFired) {\n leadingFired = true;\n fn(...args);\n } else if (trailing) {\n // Capture the latest args for the trailing fire. Overwrites any prior\n // pending tuple — the trailing edge fires with the LAST args, by design.\n pendingArgs = args;\n }\n\n // (Re)arm the quiet-window timer. Clearing the previous handle is what\n // makes the window SLIDE: every call within `ms` pushes the trailing fire\n // out to `ms` after THIS call.\n clearHandle();\n handle = setTimeout(onTimerExpired, ms);\n };\n\n debounced.cancel = (): void => {\n clearHandle();\n pendingArgs = null;\n leadingFired = false;\n };\n\n debounced.flush = (): void => {\n // Force the pending trailing fire out now. Mirrors `onTimerExpired`'s fire\n // branch but is caller-driven rather than timer-driven, so it also tears\n // down the armed timer (a timer-driven expiry already nulled `handle`).\n if (pendingArgs !== null) {\n const args = pendingArgs;\n pendingArgs = null;\n clearHandle();\n // A flushed trailing fire ends the burst, same as a natural expiry.\n leadingFired = false;\n fn(...args);\n } else {\n // Nothing pending — still tear down a lingering leading-only timer so a\n // post-flush call leads cleanly.\n clearHandle();\n leadingFired = false;\n }\n };\n\n return debounced;\n}\n"]}
@@ -1,7 +1,6 @@
1
- import { queueAdapter } from './chunk-MU6MCIAC.js';
2
- import { at } from './chunk-FV3Z5MBE.js';
1
+ import { queueAdapter, at } from './chunk-TXN2KFHF.js';
3
2
 
4
- // src/fan-out/index.ts
3
+ // src/internal/flow/fan-out/index.ts
5
4
  function launchBudget(concurrency) {
6
5
  return concurrency < 1 ? 1 : Math.floor(concurrency);
7
6
  }
@@ -144,5 +143,5 @@ function createFanOut(config) {
144
143
  }
145
144
 
146
145
  export { createFanOut, initFanOut, isComplete };
147
- //# sourceMappingURL=chunk-D736QUGF.js.map
148
- //# sourceMappingURL=chunk-D736QUGF.js.map
146
+ //# sourceMappingURL=chunk-MYXFNWAJ.js.map
147
+ //# sourceMappingURL=chunk-MYXFNWAJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/internal/flow/fan-out/index.ts"],"names":[],"mappings":"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","file":"chunk-MYXFNWAJ.js","sourcesContent":["/**\n * internal/flow/fan-out — scatter-gather over a bounded-concurrency work list.\n *\n * The Level-2 combinator that fans a batch of items out across at most\n * `concurrency` in-flight effects, gathers each item's result (or error), and\n * — once every item has settled — fires an optional `join` over the collected\n * results. It is the \"map a list through an effect, but never run more than N\n * at once\" knob.\n *\n * The recipe (which bricks it composes):\n * - `../work-queue` — fan-out's tracked items ARE work-queue items. Each\n * item carries the same `{ id, status, input }` lifecycle vocabulary\n * (`pending → running → done | failed`). The slice this knob owns is four\n * partitions over that one lifecycle, so the work-queue's status taxonomy\n * is the substrate rather than a parallel invention. We reuse its\n * `QueueItem<I>` / `QueueItemStatus` TYPES and delegate the ledger\n * transitions to the `QueueAdapter` verbs (`../work-queue/adapter`):\n * `scatter` mints each pending record with `queue.enqueue`, and `launchUpTo`\n * flips `pending → running` with `queue.claim` (one positional claim per\n * launch). The lone exception is `settleOne`'s `running → done|failed`\n * flip — it must match \"first id-match that is still `running`\" to settle\n * exactly one of a DUPLICATE id, a status predicate the adapter verbs can't\n * express, so fan-out owns that one index (see `settleOne`). The async\n * `createQueue(store)` adapter is NOT used here because its `load → mutate\n * → save` reads the clock and lives at an I/O boundary, while a combinator's\n * verbs must be pure (invariant 2) — fan-out binds the pure `QueueAdapter`\n * to its in-Model ledger instead, the same route `monitored-run` /\n * `idempotent-intake` / `saga` take. The slice lives in the Model instead —\n * durable + replayable, the two non-negotiables every knob preserves.\n *\n * Typical wiring (combinator form — splice the hooks into your machine):\n *\n * const fan = createFanOut<Url, Report>({\n * concurrency: 4,\n * idOf: (url) => url, // stable, pure identity\n * of: (url) => ({ type: \"crawl\", url }), // the per-item effect, as data\n * join: (results) => ({ type: \"report_done\", results }),\n * });\n *\n * // in `update`:\n * start_crawl: (s, m) => { const [fanOut, cmds] = fan.scatter(s.fanOut, m.urls);\n * return [{ ...s, fanOut }, cmds]; },\n * crawl_ok: (s, m) => { const [fanOut, cmds] = fan.itemOk(s.fanOut, m.id, m.report);\n * return [{ ...s, fanOut }, cmds]; },\n * crawl_err: (s, m) => { const [fanOut, cmds] = fan.itemErr(s.fanOut, m.id, m.error);\n * return [{ ...s, fanOut }, cmds]; },\n *\n * Every verb is PURE: it returns a NEW slice plus the Cmds to perform (the\n * `of(item)` effects for newly-launched items, and at most one `join` Cmd when\n * the batch completes). The reducer never reads a clock or RNG — item identity\n * is supplied by the caller's `idOf` so `scatter` is deterministic, and the\n * launch effects are plain data the runtime performs (invariant 3).\n *\n * `handlers(ports)` is the I/O splice: it wires the runtime side that actually\n * runs each launched `of(item)` Cmd. Because `of` already produces a fully\n * caller-defined Cmd, fan-out does NOT own the interpret handler for it — the\n * consumer's own `interpret` performs the effect and routes Ok/Err back to the\n * `itemOk` / `itemErr` verbs (the same shape resilient-fetch uses with\n * `tryInterpret`). `handlers` therefore exposes the one piece fan-out CAN\n * pre-wire generically: emitting the gathered batch onto a typed Port when the\n * batch completes, for consumers that observe completion out-of-band rather\n * than through the `join` Cmd.\n *\n * NOT a substrate primitive: it depends only on `../work-queue`'s pure types +\n * blessed ops and the core `Cmd` / `Port` types. Internal since #48 — not\n * published on any subpath; reached from inside the package as\n * `internal/flow/fan-out`.\n */\n\nimport { at } from \"../../../at\";\nimport type { Cmd, Port } from \"../../../index\";\nimport type { QueueItem, QueueItemStatus } from \"../../work-queue\";\nimport { queueAdapter } from \"../../work-queue/adapter\";\n\n/**\n * One settled-OK item: the original `input` and the `result` its effect\n * produced. Mirrors the spec slice's `done: {item, result}[]` — `item` is the\n * caller's input value (not the internal `QueueItem` envelope), so a consumer\n * reading `done` gets back exactly what it scattered.\n */\nexport interface FanOutDone<I, R> {\n /** The original scattered input value. */\n readonly item: I;\n /** The result the item's effect produced, routed in via `itemOk`. */\n readonly result: R;\n}\n\n/**\n * One settled-error item: the original `input` and the `error` its effect\n * surfaced. `error` is `unknown` — fan-out carries it forward verbatim (an\n * `Error`, a tagged result, a string) for the consumer's reducer to read,\n * never interpreting it (same discipline as `retry-backoff`'s `lastError`).\n */\nexport interface FanOutFailed<I> {\n /** The original scattered input value. */\n readonly item: I;\n /** The error the item's effect surfaced, routed in via `itemErr`. Carried, never interpreted. */\n readonly error: unknown;\n}\n\n/**\n * The slice this knob owns — four partitions over the work-queue item\n * lifecycle, plus the work-queue records the verbs thread the lifecycle\n * through.\n *\n * Why both the spec's four arrays AND `items`:\n * - The four arrays (`pending` / `running` / `done` / `failed`) are the\n * SPEC's public read surface — what a consumer's view renders and what\n * `isComplete` reads. They hold caller-facing values (`I`, or\n * `FanOutDone` / `FanOutFailed`), not the internal id-bearing envelope.\n * - `items` is the work-queue ledger: one `QueueItem<I>` per scattered item,\n * carrying the stable `id` (from `config.idOf`) that `itemOk` / `itemErr`\n * address and the `status` that decides launch-eligibility. It is the\n * single source of truth; the four arrays are a derived projection kept in\n * lockstep so consumers never re-scan `items` to render a partition.\n *\n * Every field is `readonly` and the whole slice is JSON-serializable (plain\n * arrays + the work-queue's plain `QueueItem` records), so it round-trips\n * through a consumer's `Store<S>` unchanged — durable across DO eviction.\n */\nexport interface FanOutState<I, R> {\n /** Items accepted but not yet launched (queue back-pressure beyond `concurrency`). */\n readonly pending: readonly I[];\n /** Items whose `of(item)` effect is in flight (size never exceeds `concurrency`). */\n readonly running: readonly I[];\n /** Items whose effect resolved OK, with the result it produced. */\n readonly done: readonly FanOutDone<I, R>[];\n /** Items whose effect failed, with the error it surfaced. */\n readonly failed: readonly FanOutFailed<I>[];\n /**\n * The work-queue ledger: one record per scattered item, keyed by the\n * caller's `idOf`. The single source of truth the four arrays project from.\n */\n readonly items: readonly QueueItem<I>[];\n /**\n * Index into `done` where the CURRENT wave's OK results begin. `done` is\n * append-only and accumulates across every wave (so a consumer reading\n * `done` sees the full history), but `join` must fold only the wave that\n * just drained — not the cumulative total. A wave opens when `scatter`\n * enqueues into a slice with nothing in flight; this marks `done.length` at\n * that moment so `join` receives exactly `done.slice(waveDoneFrom)`.\n */\n readonly waveDoneFrom: number;\n /**\n * Index into `failed` where the current wave's failures begin. Symmetric to\n * `waveDoneFrom` — the wave boundary over the failure partition. Carried for\n * a consumer that wants to read only the current wave's failures; `join`\n * itself folds only OK results.\n */\n readonly waveFailedFrom: number;\n /**\n * Whether the current wave has already fired its `join`. `join` fires at most\n * once per wave — set `true` at the completion edge so a stale settle that\n * re-reaches `isComplete` cannot re-fire it. A new `scatter` that opens a wave\n * resets it to `false`.\n */\n readonly joined: boolean;\n}\n\n/**\n * The knob. `concurrency` caps how many `of(item)` effects are in flight at\n * once; `idOf` mints a stable, PURE identity per item (so `scatter` is\n * deterministic and `itemOk` / `itemErr` can address an item by id); `of`\n * turns an item into the launch Cmd; `join` (optional) turns the gathered\n * results into a single completion Cmd fired exactly once when every item has\n * settled.\n *\n * Field optionality follows the resilient-call rule — omit `join`, omit the\n * completion Cmd (a consumer that observes `isComplete` itself, or wires the\n * `handlers` Port, doesn't need one).\n */\nexport interface FanOutConfig<I, R, C extends Cmd, J extends Cmd> {\n /** Max in-flight `of(item)` effects. Clamped to `>= 1` (a 0/negative cap would deadlock). */\n readonly concurrency: number;\n /**\n * Stable, deterministic identity for an item. PURE — no clock, no RNG. The\n * id is what `itemOk` / `itemErr` address, so it must survive a round-trip\n * through the durable slice and match across the launch → settle gap. For\n * value-unique inputs (URLs, ids) this is often the identity function; for\n * structural inputs, a content hash or a caller-assigned key.\n */\n readonly idOf: (item: I) => string;\n /** The per-item effect, as data. Performed by the consumer's own `interpret`. */\n readonly of: (item: I) => C;\n /**\n * Optional: fold the gathered done-results into a single completion Cmd,\n * fired exactly once at the transition that empties `pending` + `running`.\n * Omit to drive completion off `isComplete` (or the `handlers` Port) instead.\n */\n readonly join?: (results: readonly R[]) => J;\n}\n\n/**\n * Clamp the configured concurrency to a usable launch budget. A cap of 0 or\n * negative would launch nothing and the batch would deadlock with items stuck\n * `pending` forever; clamping to 1 degrades gracefully to fully-serial\n * fan-out (still correct, just no parallelism) rather than hanging.\n */\nfunction launchBudget(concurrency: number): number {\n return concurrency < 1 ? 1 : Math.floor(concurrency);\n}\n\n/** The starting slice: nothing scattered yet. */\nexport function initFanOut<I, R>(): FanOutState<I, R> {\n return {\n pending: [],\n running: [],\n done: [],\n failed: [],\n items: [],\n waveDoneFrom: 0,\n waveFailedFrom: 0,\n joined: false,\n };\n}\n\n/**\n * Whether every scattered item has settled (no `pending`, no `running`).\n * Derived — reads the slice, never mutates. The verbs use it to decide when to\n * fire `join`; consumers can read it directly to branch their own phase.\n *\n * An empty fan-out (nothing ever scattered) is NOT complete: completion is \"a\n * non-empty batch fully drained,\" so `isComplete(initFanOut())` is `false` and\n * a `scatter([])` of zero items also reads `false` (there was no batch to\n * complete). This keeps `join` from firing on a degenerate empty scatter.\n */\nexport function isComplete<I, R>(state: FanOutState<I, R>): boolean {\n if (state.items.length === 0) return false;\n return state.pending.length === 0 && state.running.length === 0;\n}\n\n/**\n * Count of items currently in flight. `running.length`, named for the launch\n * arithmetic below (and readable at a call site).\n */\nfunction inFlight<I, R>(state: FanOutState<I, R>): number {\n return state.running.length;\n}\n\n/**\n * Move up to `budget - inFlight` items from `pending` to `running`, emitting\n * `of(item)` for each launched item. PURE — returns the new slice and the\n * launch Cmds. The work-queue `items` ledger flips each launched item's status\n * `pending → running` via the `QueueAdapter`'s `claim`, in lockstep with the\n * projected arrays.\n *\n * Delegation, not re-rolling: each launch is one `queue.claim` call — the same\n * `pending → running` transition `src/internal/work-queue/` owns. `queue.claim`\n * flips the FIRST pending ledger record positionally, and fan-out's `pending`\n * array is appended in lockstep with the ledger, so the first `slots` pending\n * inputs map to the first `slots` pending records — claiming one-per-launch\n * settles exactly those. This also makes a DUPLICATE id correct: a positional\n * claim flips ONE record per launch, where a blanket id-set flip would promote\n * every pending record sharing the launched id (over-flipping the ledger past\n * `running.length`). The `at` stamp is `0` — fan-out's verbs are pure and never\n * read a clock; positional order, not the timestamp, drives every decision\n * (same convention as `scatter`'s `enqueuedAt: 0`).\n *\n * Shared by `scatter` (launch on enqueue) and `itemOk` / `itemErr` (launch the\n * next pending item as a slot frees). Factoring it here keeps the concurrency\n * invariant — `running.length <= budget` — in ONE place.\n */\nfunction launchUpTo<I, R, C extends Cmd>(\n state: FanOutState<I, R>,\n config: {\n concurrency: number;\n of: (item: I) => C;\n },\n): readonly [FanOutState<I, R>, readonly C[]] {\n const budget = launchBudget(config.concurrency);\n const slots = budget - inFlight(state);\n if (slots <= 0 || state.pending.length === 0) return [state, []];\n\n const toLaunch = state.pending.slice(0, slots);\n const stillPending = state.pending.slice(slots);\n\n // Flip the ledger records `pending → running` one launch at a time via the\n // `QueueAdapter` (positional first-pending claim per call).\n const queue = queueAdapter<I>();\n let items: readonly QueueItem<I>[] = state.items;\n for (let i = 0; i < toLaunch.length; i++) {\n const claimed = queue.claim(items, 0);\n if (claimed === null) break;\n items = claimed.next;\n }\n\n const next: FanOutState<I, R> = {\n ...state,\n pending: stillPending,\n running: [...state.running, ...toLaunch],\n items,\n };\n const cmds = toLaunch.map((item) => config.of(item));\n return [next, cmds];\n}\n\n/**\n * Settle EXACTLY ONE running entry for `id`: remove the first `running` item\n * whose `idOf` matches, and flip the first ledger record for `id` that is still\n * `running` to `status`. Returns the input that was settled plus the new\n * `running` / `items` arrays, or `undefined` if no running item carries that id\n * (a stale / unknown settle Msg → caller treats as a no-op rather than a crash).\n *\n * Why \"exactly one\": when two scattered items share an `idOf` (a duplicate id),\n * the ledger holds two `running` records under the same id and `running` holds\n * two inputs. A settle Msg carries one result, so it must close out exactly one\n * of them — not all matches. A blanket `filter((x) => idOf(x) !== id)` would\n * drop BOTH running entries while recording only ONE result, silently losing a\n * unit of work and corrupting the done/failed partition. find-index + splice\n * removes a single `running` entry; the matching `findIndex` over the ledger\n * flips a single record. The OTHER duplicate stays `running`, awaiting its own\n * settle Msg.\n *\n * This flip is NOT delegated to the `QueueAdapter`'s `patch`: the verb matches\n * the FIRST record by id, but the record fan-out must flip is \"first id-match\n * that is still `running`\" — and after a dup-id's first settle the first id-match\n * is the already-terminal record. The status predicate is load-bearing here and\n * the adapter verbs can't express it, so fan-out owns this one index. The flipped\n * record is still built the work-queue way (spread + new `status`), and there\n * is no `as` cast: `status` arrives already typed `QueueItemStatus`.\n */\nfunction settleOne<I, R>(\n state: FanOutState<I, R>,\n id: string,\n status: QueueItemStatus,\n idOf: (item: I) => string,\n):\n | { input: I; running: readonly I[]; items: readonly QueueItem<I>[] }\n | undefined {\n const runIdx = state.running.findIndex((item) => idOf(item) === id);\n // `at` collapses \"no running entry for this id\" into an `undefined` early-out,\n // narrowing `input` to `I` below without a non-null assertion (the pattern\n // `ops.ts` uses); `runIdx` stays around for the splice.\n const input = at(state.running, runIdx);\n if (input === undefined) return undefined;\n\n const running = [\n ...state.running.slice(0, runIdx),\n ...state.running.slice(runIdx + 1),\n ];\n\n const ledgerIdx = state.items.findIndex(\n (item) => item.id === id && item.status === \"running\",\n );\n // Same early-out via `at`: `ledger === undefined` narrows the record to\n // `QueueItem<I>` for the spread below without a non-null assertion.\n const ledger = at(state.items, ledgerIdx);\n const items =\n ledger === undefined\n ? state.items\n : [\n ...state.items.slice(0, ledgerIdx),\n { ...ledger, status },\n ...state.items.slice(ledgerIdx + 1),\n ];\n\n return { input, running, items };\n}\n\n/**\n * Optional Port for observing batch completion out-of-band. When a consumer\n * passes `handlers(ports)` a `complete` Port, the runtime side can emit the\n * gathered results onto it the moment `isComplete` flips true — distinct from\n * the in-band `join` Cmd (which threads back through `update`). Use the Port\n * for \"tell the outside world the batch finished\" without folding a completion\n * signal into the consumer's Msg union.\n */\nexport interface FanOutPorts<R> {\n /** Fires once with every gathered done-result when the batch completes. */\n readonly complete: Port<readonly R[]>;\n}\n\n/**\n * Build the fan-out knob from `config`. Returns the uniform combinator\n * contract: `init` for the owned slice, the three settle verbs + the derived\n * `isComplete`, and `handlers` for the optional out-of-band completion Port.\n *\n * No `subs` — fan-out runs no timers (the spec pins \"Subs: none\"). The\n * per-item effect's lifetime is owned by the consumer's `interpret`, and\n * completion is a pure function of the settle Msgs, so there is nothing\n * continuous to subscribe to.\n */\nexport function createFanOut<I, R, C extends Cmd = Cmd, J extends Cmd = Cmd>(\n config: FanOutConfig<I, R, C, J>,\n) {\n /**\n * Enqueue `items` and launch up to `concurrency` of them. PURE.\n *\n * Each item gets a ledger record (`status: \"pending\"`, its `idOf` id) and is\n * appended to `pending`; `launchUpTo` then promotes as many as the free\n * concurrency budget allows, emitting `of(item)` per launch. The returned\n * Cmds are exactly the launch effects — `scatter` never fires `join` (a batch\n * can't complete on the same transition it starts unless it launches zero\n * effects, which `isComplete`'s empty-batch rule already declines to treat as\n * completion).\n *\n * Each pending ledger record is minted by the `QueueAdapter`'s `enqueue` — the\n * same `pending` append `src/internal/work-queue/` owns — rather than re-rolled\n * inline, so the `status: \"pending\"` literal lives in exactly one place (no\n * `as QueueItemStatus` cast here).\n *\n * `enqueuedAt` is stamped `0` rather than read from a clock: fan-out's verbs\n * are pure, and fan-out does not use the timestamp for any decision (ordering\n * is positional via the arrays). A consumer that needs real enqueue times\n * threads them through its own Msg `at` and stores them alongside.\n */\n function scatter(\n state: FanOutState<I, R>,\n items: readonly I[],\n ): readonly [FanOutState<I, R>, readonly C[]] {\n // Append one pending ledger record per item via the `QueueAdapter`.\n // `queue.enqueue` returns `status: \"pending\"` already typed — no cast — and\n // the lifecycle's \"new work is pending\" rule stays in work-queue's hands.\n const queue = queueAdapter<I>();\n let ledger: readonly QueueItem<I>[] = state.items;\n for (const input of items) {\n ledger = queue.enqueue(ledger, input, 0, config.idOf(input)).next;\n }\n // A scatter OPENS A FRESH WAVE when nothing is in flight — either the very\n // first scatter, or a re-scatter after the previous wave fully drained. The\n // wave boundary advances to the current `done` / `failed` length so `join`\n // folds only THIS wave's results, never the cumulative history, and\n // `joined` resets so the new wave can fire its own `join` exactly once.\n //\n // A scatter that adds to a wave still in flight (pending or running\n // non-empty — e.g. enqueuing more work mid-batch) does NOT open a wave:\n // those items join the current wave and settle under its single `join`.\n const opensWave = state.pending.length === 0 && state.running.length === 0;\n\n const enqueued: FanOutState<I, R> = {\n ...state,\n pending: [...state.pending, ...items],\n items: ledger,\n ...(opensWave\n ? {\n waveDoneFrom: state.done.length,\n waveFailedFrom: state.failed.length,\n joined: false,\n }\n : {}),\n };\n return launchUpTo(enqueued, config);\n }\n\n /**\n * Record `id`'s effect as succeeded with `result`, then launch the next\n * pending item to backfill the freed slot. PURE.\n *\n * Moves the item out of `running` into `done` (carrying the original input +\n * its result), flips its ledger record `running → done`, and runs\n * `launchUpTo` to keep the pipeline full. If this transition empties both\n * `pending` and `running` AND a `join` is configured, the returned Cmds also\n * include the single `join(results)` Cmd — fired exactly once at the\n * completion edge.\n *\n * A settle for an unknown / already-settled `id` is a no-op (returns the\n * slice unchanged, no Cmds): the runtime's serial dispatch makes a true\n * double-settle impossible, but a stale Msg after a boot/replay is real, and\n * swallowing it is the safe direction (the result is already recorded).\n */\n function itemOk(\n state: FanOutState<I, R>,\n id: string,\n result: R,\n ): readonly [FanOutState<I, R>, readonly (C | J)[]] {\n const settled = settleOne(state, id, \"done\", config.idOf);\n if (settled === undefined) return [state, []];\n\n const next: FanOutState<I, R> = {\n ...state,\n running: settled.running,\n done: [...state.done, { item: settled.input, result }],\n items: settled.items,\n };\n return finishOrLaunch(next);\n }\n\n /**\n * Record `id`'s effect as failed with `error`, then launch the next pending\n * item to backfill the freed slot. PURE.\n *\n * Symmetric to `itemOk`: moves the item out of `running` into `failed`\n * (carrying the original input + the error), flips its ledger record\n * `running → failed`, backfills via `launchUpTo`, and fires `join` at the\n * completion edge if configured. A failed item still counts toward completion\n * — the batch is \"done\" when every item has settled, success or failure;\n * `join` receives only the OK results (failures are read off `failed`).\n *\n * Same unknown-`id` no-op contract as `itemOk`.\n */\n function itemErr(\n state: FanOutState<I, R>,\n id: string,\n error: unknown,\n ): readonly [FanOutState<I, R>, readonly (C | J)[]] {\n const settled = settleOne(state, id, \"failed\", config.idOf);\n if (settled === undefined) return [state, []];\n\n const next: FanOutState<I, R> = {\n ...state,\n running: settled.running,\n failed: [...state.failed, { item: settled.input, error }],\n items: settled.items,\n };\n return finishOrLaunch(next);\n }\n\n /**\n * Shared tail of `itemOk` / `itemErr`: backfill the freed slot, then — if the\n * CURRENT wave is now complete, a `join` is configured, and this wave has not\n * already joined — append the single completion Cmd and mark the wave joined.\n *\n * Two guards keep `join` firing exactly once per wave:\n * - `isComplete(next)` is the completion edge (the transition that drains\n * the last in-flight item; `launchUpTo` cannot re-fill from an empty\n * `pending`, so `running` stays empty and the edge is reached once).\n * - `!next.joined` makes it idempotent even if a stale settle re-reaches a\n * completed slice, and — critically — combined with the per-wave reset in\n * `scatter` it stops `join` re-firing across waves. Without the wave\n * boundary, `done` is append-only, so a SECOND wave draining would see\n * `isComplete` true and re-fold the WHOLE cumulative `done` (wave 1 +\n * wave 2). The fix folds only `done.slice(waveDoneFrom)` — exactly the\n * results this wave produced.\n */\n function finishOrLaunch(\n settled: FanOutState<I, R>,\n ): readonly [FanOutState<I, R>, readonly (C | J)[]] {\n const [launched, launchCmds] = launchUpTo(settled, config);\n if (config.join !== undefined && !launched.joined && isComplete(launched)) {\n const results = launched.done\n .slice(launched.waveDoneFrom)\n .map((d) => d.result);\n const next: FanOutState<I, R> = { ...launched, joined: true };\n return [next, [...launchCmds, config.join(results)]];\n }\n return [launched, launchCmds];\n }\n\n /**\n * The out-of-band completion splice. Returns an observer-shaped function the\n * consumer calls after every settle: when the batch has just completed it\n * emits the gathered results onto `ports.complete`. This is the Port-side\n * analogue of the `join` Cmd — use it when completion should leave the\n * runtime as a typed signal rather than re-enter `update`.\n *\n * Returns the emit-on-complete callback so the consumer wires it into its own\n * runtime `observe` (e.g. `runtime.observe((_, s) => emit(s.fanOut))`); fan-out\n * stays out of the dispatch loop, matching `historyTracker`'s discipline.\n */\n function handlers(ports: FanOutPorts<R>) {\n return {\n /**\n * Emit the gathered done-results onto `ports.complete` iff `state` is a\n * just-completed batch. Idempotency is the caller's concern (call it once\n * per transition via `observe`); emitting to a Port with no subscribers\n * is a no-op at the runtime, so an extra call is harmless.\n */\n emitOnComplete(\n emit: <T>(port: Port<T>, value: T) => void,\n state: FanOutState<I, R>,\n ): void {\n if (!isComplete(state)) return;\n emit(\n ports.complete,\n state.done.slice(state.waveDoneFrom).map((d) => d.result),\n );\n },\n };\n }\n\n return {\n init: initFanOut<I, R>,\n scatter,\n itemOk,\n itemErr,\n isComplete,\n handlers,\n };\n}\n"]}
@@ -1,4 +1,4 @@
1
- // src/idempotency/index.ts
1
+ // src/internal/idempotency/idempotency/index.ts
2
2
  function initStore(opts) {
3
3
  return {
4
4
  entries: {},
@@ -53,6 +53,17 @@ function remember(store, key, value, nowMs) {
53
53
  return { ...store, entries: nextEntries, order: nextOrder };
54
54
  }
55
55
 
56
- export { evictExpired, initStore, recall, remember, seen };
57
- //# sourceMappingURL=chunk-K575PT4S.js.map
58
- //# sourceMappingURL=chunk-K575PT4S.js.map
56
+ // src/internal/idempotency/idempotency/adapter.ts
57
+ function idempotencyMemory() {
58
+ return {
59
+ init: (opts) => initStore(opts),
60
+ isSeen: (store, key, now) => seen(store, key, now),
61
+ recall: (store, key, now) => recall(store, key, now),
62
+ remember: (store, key, value, now) => remember(store, key, value, now),
63
+ evict: (store, now) => evictExpired(store, now)
64
+ };
65
+ }
66
+
67
+ export { evictExpired, idempotencyMemory, initStore, recall, remember, seen };
68
+ //# sourceMappingURL=chunk-PR6TB3BL.js.map
69
+ //# sourceMappingURL=chunk-PR6TB3BL.js.map