@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/internal/idempotency/idempotency/index.ts","../src/internal/idempotency/idempotency/adapter.ts"],"names":[],"mappings":"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","file":"chunk-PR6TB3BL.js","sourcesContent":["/**\n * internal/idempotency/idempotency — dedupe-by-key + last-result cache as pure state + ops.\n *\n * Same shape as `../../work-queue`'s `ops.ts` and `../../resilience/rate-limit`:\n * a state type plus pure transition functions. Host-agnostic by construction —\n * no `Date.now()`, no timers, no I/O. The caller injects the clock (`nowMs`)\n * and wires the result into its own reducer / interpret. This keeps the store\n * inside TEA's invariant 2 (transitions are pure: same inputs → same outputs,\n * no `Date.now`) so a consumer can fold it through `update` and exercise it\n * with `replay` / the pbt fold without a fake clock.\n *\n * Every op returns a NEW store value and never mutates its input (invariant 1 —\n * state is a value, not a place). `seen` / `recall` are read-shaped predicates;\n * `remember` / `evictExpired` return a new store.\n *\n * The use case: a webhook receiver, a retried queue message, or a repeated user\n * action arrives carrying an idempotency key. `seen(store, key, now)` answers\n * \"did I already process this?\"; `recall(store, key, now)` returns the cached\n * result so the duplicate request gets the SAME response the original produced\n * rather than re-running the side effect. `remember(store, key, value, now)`\n * records the key + its result after the first run.\n *\n * Two eviction policies keep the store bounded, both injected at `initStore`:\n *\n * - **`ttlMs`** — an entry expires `ttlMs` after the `atMs` it was remembered\n * at. `seen` / `recall` treat an expired entry as absent; `evictExpired`\n * (and every `remember`) physically drops it. Without a TTL, entries live\n * until evicted by capacity. The cutoff is half-open: an entry exactly\n * `ttlMs` old is expired (`age >= ttlMs` drops it), matching `rate-limit`'s\n * window edge — a key remembered at `t` no longer counts at `t + ttlMs`.\n * - **`capacity`** — at most `capacity` entries are retained. When a\n * `remember` would push the count past `capacity` (after expiry eviction),\n * the OLDEST entries by insertion `order` are dropped until the count fits.\n * Without a capacity, the store grows unbounded (TTL alone bounds it).\n *\n * `order` is the insertion-order index that capacity eviction reads — the keys\n * oldest-first. It is the store's own LRU-by-insertion ledger, kept in lockstep\n * with `entries` so `entries` never needs to be sorted to find the oldest.\n *\n * **Overwrite semantics** (decided + pinned by tests): re-`remember`ing an\n * existing key is an UPDATE, not a no-op. It (1) overwrites `value`, (2)\n * refreshes `atMs` to the new `nowMs` — so the TTL clock restarts and a\n * frequently-seen key never expires — and (3) moves the key to the NEWEST\n * position in `order` — so capacity eviction treats a re-remembered key as\n * fresh, not as a stale candidate for eviction. This makes `remember` behave\n * like \"touch\": a key actively being re-seen is kept alive on both the TTL and\n * capacity axes.\n *\n * Internal since #47 — not published on any subpath; reached from inside the\n * package as `internal/idempotency/idempotency`. Same one-shape-per-package rule\n * as `work-queue`, `rate-limit`, and `do`.\n */\n\n/**\n * A cached entry: the result `value` the original request produced and the\n * clock reading `atMs` it was remembered at. `atMs` is the TTL anchor — expiry\n * is measured from here, and a re-`remember` refreshes it. Both fields are\n * plain data so the whole store is trivially JSON-serializable into a\n * consumer's `Store<S>`.\n */\nexport interface IdempotencyEntry<V> {\n readonly value: V;\n readonly atMs: number;\n}\n\n/**\n * The dedupe store: a map of `key → entry`, an insertion-`order` ledger\n * (oldest-first) that capacity eviction reads, and the two optional bounds.\n *\n * `entries` and `order` are kept in lockstep — every key in `order` has an\n * entry and vice versa. `capacity` / `ttlMs` are carried on the store (not\n * passed per-op) so a single value fully describes the policy; a consumer\n * persists the store and rehydrates the same bounds without re-deriving them.\n */\nexport interface IdempotencyStore<V> {\n readonly entries: Readonly<Record<string, IdempotencyEntry<V>>>;\n readonly order: readonly string[];\n readonly capacity?: number;\n readonly ttlMs?: number;\n}\n\n/**\n * Create an empty store. `capacity` and `ttlMs` are both optional — omit both\n * for an unbounded cache, supply either or both to bound by count, age, or\n * both. Stored on the returned value so every later op reads the policy off\n * the store rather than from a re-passed argument.\n */\nexport function initStore<V>(opts?: {\n capacity?: number;\n ttlMs?: number;\n}): IdempotencyStore<V> {\n return {\n entries: {},\n order: [],\n capacity: opts?.capacity,\n ttlMs: opts?.ttlMs,\n };\n}\n\n/**\n * Whether `entry` (remembered at `entry.atMs`) is expired at `nowMs` under\n * `ttlMs`. Half-open: an entry exactly `ttlMs` old is expired (`age >= ttlMs`),\n * matching `rate-limit`'s window-edge cutoff. A `nowMs` before `atMs` (clock\n * skewed backwards) yields a negative age — never expired, the safe direction\n * (a duplicate is still deduped rather than re-run). When `ttlMs` is undefined\n * the entry never expires by age.\n */\nfunction isExpired<V>(\n entry: IdempotencyEntry<V>,\n nowMs: number,\n ttlMs: number | undefined,\n): boolean {\n if (ttlMs === undefined) return false;\n return nowMs - entry.atMs >= ttlMs;\n}\n\n/**\n * True iff `key` is present AND not expired at `nowMs`. This is the dedupe\n * predicate: a webhook / retried message / repeated action whose key returns\n * `true` has already been processed and should be short-circuited (paired with\n * `recall` to return the original result). PURE — reads only, never mutates.\n */\nexport function seen<V>(\n store: IdempotencyStore<V>,\n key: string,\n nowMs: number,\n): boolean {\n const entry = store.entries[key];\n if (entry === undefined) return false;\n return !isExpired(entry, nowMs, store.ttlMs);\n}\n\n/**\n * The cached `value` for `key` if present and unexpired at `nowMs`, else\n * `undefined`. Use after `seen` returns `true` to replay the original result to\n * a duplicate request. An expired entry reads as absent (returns `undefined`)\n * even though it is physically still in `entries` until the next `remember` /\n * `evictExpired` drops it. PURE — reads only, never mutates.\n */\nexport function recall<V>(\n store: IdempotencyStore<V>,\n key: string,\n nowMs: number,\n): V | undefined {\n const entry = store.entries[key];\n if (entry === undefined || isExpired(entry, nowMs, store.ttlMs))\n return undefined;\n return entry.value;\n}\n\n/**\n * Drop every entry expired at `nowMs` (older than `ttlMs`). Returns a NEW store\n * with the survivors; `order` is rebuilt to drop the same keys, preserving the\n * relative insertion order of the survivors. A no-TTL store returns an\n * equivalent store (nothing ages out). PURE — the input store is never mutated.\n */\nexport function evictExpired<V>(\n store: IdempotencyStore<V>,\n nowMs: number,\n): IdempotencyStore<V> {\n if (store.ttlMs === undefined) return store;\n const nextEntries: Record<string, IdempotencyEntry<V>> = {};\n const nextOrder: string[] = [];\n // Walk `order` (not `Object.keys`) so the surviving order ledger keeps the\n // exact relative insertion order capacity eviction depends on.\n for (const key of store.order) {\n const entry = store.entries[key];\n if (entry === undefined) continue; // defensive: order/entries kept in lockstep\n if (isExpired(entry, nowMs, store.ttlMs)) continue;\n nextEntries[key] = entry;\n nextOrder.push(key);\n }\n return { ...store, entries: nextEntries, order: nextOrder };\n}\n\n/**\n * Record `key → value` at `nowMs`, then enforce both bounds. PURE — returns a\n * new store; the input is untouched.\n *\n * Order of operations (each step matters):\n * 1. **Expiry eviction** — drop everything older than `ttlMs` first, so an\n * expired key never occupies a capacity slot the new entry could take, and\n * a stale entry under the same `key` is cleared before the overwrite.\n * 2. **Insert / overwrite** — write the new entry. If `key` already exists,\n * this is an UPDATE: `value` is overwritten, `atMs` is refreshed to\n * `nowMs` (restarting the TTL clock), and the key is moved to the NEWEST\n * position in `order` (removed from wherever it sat, appended at the end).\n * See the module-level \"Overwrite semantics\" note.\n * 3. **Capacity eviction** — if the entry count now exceeds `capacity`, drop\n * the OLDEST keys by `order` until the count fits. Because step 2 moves a\n * re-remembered key to the newest position, an actively re-seen key is\n * never the eviction victim.\n */\nexport function remember<V>(\n store: IdempotencyStore<V>,\n key: string,\n value: V,\n nowMs: number,\n): IdempotencyStore<V> {\n // Step 1 — expire first so stale entries free capacity slots before insert.\n const pruned = evictExpired(store, nowMs);\n\n // Step 2 — insert/overwrite. Drop any prior position of `key` from order,\n // then append it as newest. `delete` on a fresh shallow copy keeps the input\n // store untouched (invariant 1).\n const nextEntries: Record<string, IdempotencyEntry<V>> = {\n ...pruned.entries,\n };\n nextEntries[key] = { value, atMs: nowMs };\n const nextOrder = pruned.order.filter((k) => k !== key);\n nextOrder.push(key);\n\n // Step 3 — capacity eviction. Drop oldest (front of `order`) until the count\n // fits. `capacity <= 0` evicts everything just inserted down to the bound;\n // `undefined` capacity skips eviction entirely.\n if (store.capacity !== undefined) {\n while (nextOrder.length > store.capacity) {\n const oldest = nextOrder.shift();\n if (oldest === undefined) break; // order emptied — nothing left to evict\n delete nextEntries[oldest];\n }\n }\n\n return { ...store, entries: nextEntries, order: nextOrder };\n}\n","/**\n * internal/idempotency/idempotency/adapter — the verb seam over the pure idempotency ops.\n *\n * The functions in `./index` (`seen` / `recall` / `remember` / `evictExpired`\n * / `initStore`) are the BLESSED transitions, but their names and the\n * `IdempotencyStore<V>` shape they read/return are the STORE's vocabulary. A\n * consumer that owns an `IdempotencyStore<V>` slice (`idempotent-intake`,\n * `poller`) used to import those functions by name and call them directly, so\n * a change to the op surface — or to `IdempotencyStore`'s fields — cascaded to\n * every call site.\n *\n * `IdempotencyMemory<V>` is the insulating interface: five VERBS (`isSeen` /\n * `recall` / `remember` / `evict` / `init`) consumers depend on instead of the\n * raw functions. The verbs keep the ops' pure contract — no I/O, no `Date.now`\n * (the clock is injected as `now`) — so a consumer folding the adapter through\n * its reducer stays inside invariant 2 (pure transitions). `isSeen` renames the\n * blessed `seen` predicate (a noun-y `seen` reads ambiguously as a slice field;\n * the verb form states the question it answers), and `evict` is the verb form\n * of `evictExpired`.\n *\n * `idempotencyMemory<V>()` is the canonical implementation, a thin binding of\n * the blessed ops. Because the verbs are the only surface consumers touch, an\n * `IdempotencyStore` shape change is absorbed HERE rather than at the consumer\n * call sites.\n *\n * INTERNAL seam (no public-API port): consumers import the adapter from this\n * subpath the same way they imported the ops. The ops remain exported from\n * `./index` for any call site that genuinely wants a function directly.\n */\n\nimport {\n evictExpired,\n type IdempotencyStore,\n initStore,\n recall,\n remember,\n seen,\n} from \"./index\";\n\n/**\n * The verb interface over an `IdempotencyStore<V>`. Each verb mirrors a blessed\n * op's pure contract but speaks the consumer's dedupe vocabulary, so the\n * consumer never names a store function or reaches into an `IdempotencyStore`\n * field it did not put there.\n */\nexport interface IdempotencyMemory<V> {\n /** Create an empty store, optionally bounded by `capacity` and/or `ttlMs`. */\n init(opts?: { capacity?: number; ttlMs?: number }): IdempotencyStore<V>;\n\n /** True iff `key` is present AND not expired at `now`. Read-only. */\n isSeen(store: IdempotencyStore<V>, key: string, now: number): boolean;\n\n /** The cached value for `key` if present and unexpired at `now`. Read-only. */\n recall(store: IdempotencyStore<V>, key: string, now: number): V | undefined;\n\n /** Record `key → value` at `now`, enforcing the store's bounds. Returns a new store. */\n remember(\n store: IdempotencyStore<V>,\n key: string,\n value: V,\n now: number,\n ): IdempotencyStore<V>;\n\n /** Drop every entry expired at `now`. Returns a new store. */\n evict(store: IdempotencyStore<V>, now: number): IdempotencyStore<V>;\n}\n\n/**\n * The canonical `IdempotencyMemory<V>`, binding each verb to its blessed op.\n * Pure and stateless — call once at module scope in a consumer and fold its\n * verbs through the reducer. An `IdempotencyStore` shape change is absorbed by\n * re-binding a verb here, not at the consumers.\n */\nexport function idempotencyMemory<V>(): IdempotencyMemory<V> {\n return {\n init: (opts) => initStore<V>(opts),\n isSeen: (store, key, now) => seen<V>(store, key, now),\n recall: (store, key, now) => recall<V>(store, key, now),\n remember: (store, key, value, now) => remember<V>(store, key, value, now),\n evict: (store, now) => evictExpired<V>(store, now),\n };\n}\n"]}
@@ -1,8 +1,58 @@
1
- import { initPaginator, start, recordPage, drain, resume, isDone } from './chunk-SINXE47Y.js';
2
- import { createResilientCall } from './chunk-4KLG43GY.js';
3
- import { MsgType } from './chunk-JELSZN25.js';
1
+ import { createResilientCall, MsgType } from './chunk-AYS5N6WY.js';
4
2
 
5
- // src/paginated-walk/index.ts
3
+ // src/internal/paginate/paginator/index.ts
4
+ var defaultPaginatorPolicy = {
5
+ firstCursor: 0,
6
+ highWaterMark: 1e3
7
+ };
8
+ function initPaginator() {
9
+ return { phase: "idle", seen: 0, pages: 0 };
10
+ }
11
+ function start(state, policy) {
12
+ if (state.phase !== "idle") return state;
13
+ return {
14
+ phase: "fetching",
15
+ cursor: policy.firstCursor,
16
+ seen: state.seen,
17
+ pages: state.pages
18
+ };
19
+ }
20
+ function recordPage(state, count, next, policy) {
21
+ if (state.phase !== "fetching") return state;
22
+ const safeCount = Number.isFinite(count) ? Math.max(0, count) : 0;
23
+ const seen = state.seen + safeCount;
24
+ const pages = state.pages + 1;
25
+ if (next == null) {
26
+ return { phase: "done", seen, pages };
27
+ }
28
+ if (policy.highWaterMark > 0 && seen >= policy.highWaterMark) {
29
+ return { phase: "paused", cursor: next, seen, pages };
30
+ }
31
+ return { phase: "fetching", cursor: next, seen, pages };
32
+ }
33
+ function drain(state, n) {
34
+ if (n <= 0) return state;
35
+ const seen = Math.max(0, state.seen - n);
36
+ if (seen === state.seen) return state;
37
+ return { ...state, seen };
38
+ }
39
+ function resume(state, policy) {
40
+ if (state.phase !== "paused") return state;
41
+ if (state.seen < policy.highWaterMark) {
42
+ return {
43
+ phase: "fetching",
44
+ cursor: state.cursor,
45
+ seen: state.seen,
46
+ pages: state.pages
47
+ };
48
+ }
49
+ return state;
50
+ }
51
+ function isDone(state) {
52
+ return state.phase === "done";
53
+ }
54
+
55
+ // src/internal/paginate/paginated-walk/index.ts
6
56
  var PAGE_KEY = "page";
7
57
  function createPaginatedWalk(config, rng = Math.random) {
8
58
  const rc = createResilientCall(
@@ -125,6 +175,6 @@ function liftWalk(state, [slice, cmds]) {
125
175
  return [{ ...state, walk: slice }, cmds];
126
176
  }
127
177
 
128
- export { PAGE_KEY, createPaginatedWalk, liftWalk };
129
- //# sourceMappingURL=chunk-YUXP55T2.js.map
130
- //# sourceMappingURL=chunk-YUXP55T2.js.map
178
+ export { PAGE_KEY, createPaginatedWalk, defaultPaginatorPolicy, drain, initPaginator, isDone, liftWalk, recordPage, resume, start };
179
+ //# sourceMappingURL=chunk-Q5CQTBFA.js.map
180
+ //# sourceMappingURL=chunk-Q5CQTBFA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/internal/paginate/paginator/index.ts","../src/internal/paginate/paginated-walk/index.ts"],"names":["start","drain","resume"],"mappings":"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","file":"chunk-Q5CQTBFA.js","sourcesContent":["/**\n * internal/paginate/paginator — the cursor/offset/page-token walk loop as pure\n * state + ops.\n *\n * Same shape as `../../../retry-backoff`, `../../resilience/circuit-breaker`,\n * and `../../resilience/rate-limit`: a state type plus pure transition functions,\n * host-agnostic by construction. Nothing here reads the clock, the RNG, or any\n * I/O on its own behalf — the consumer fetches a page out-of-band and folds the\n * *result* back in through `recordPage`. That keeps every function inside TEA's\n * invariant 2 (transitions are pure: same inputs → same outputs) so a consumer\n * can thread the walk through `update` and exercise it with `replay` / the pbt\n * fold without a fake network.\n *\n * Every op returns a NEW state value and never mutates its input (invariant 1 —\n * state is a value, not a place). All fields are `readonly` and plain data, so\n * the whole walk is trivially JSON-serializable into a consumer's `Store<S>`\n * and survives a Durable-Object eviction / reload mid-walk: the cursor lives in\n * the Model, not a closure (the durability + replay non-negotiables every TEA\n * knob preserves).\n *\n * ## The cursor is opaque, and the consumer advances it\n *\n * A paginated API hands back a *next cursor* however it likes: an offset\n * (`?offset=40`), a page token (`?pageToken=ey…`), a \"next\" URL, or `null` at\n * the end. This module is generic in `Cursor` and never inspects it — it only\n * carries it forward. The consumer (who alone knows the API's shape) reads the\n * next cursor off the just-fetched page and passes it to `recordPage`:\n *\n * const next = page.nextPageToken ?? null; // consumer-specific extraction\n * const walk = recordPage(state.walk, page.items.length, next);\n *\n * Passing `next: null` (or a missing `undefined` cursor — the op compares with\n * loose `== null`) means \"the API said there is no next page\" → the walk\n * finishes (`done`). Passing a `Cursor` means \"fetch this one next\" → the walk\n * either keeps `fetching` or `pauses` for backpressure (below). Decoupling the\n * extraction from the op is the same discipline `circuit-breaker` uses for the\n * clock: the impure / domain-specific bit (`nextCursor(page)`) lives at the\n * call site; the op stays a pure fold over the extracted value.\n *\n * ## The phase machine\n *\n * Four phases, each a discriminated-union arm carrying ONLY the data that phase\n * needs so impossible states are unrepresentable (pattern 11):\n *\n * - **idle** — created, not started. No cursor is armed yet; `start` arms the\n * first one. Carries the running `seen` / `pages` counters (both 0 here, but\n * present on every phase so a consumer reads them uniformly).\n *\n * - **fetching** — a page request for `cursor` is outstanding. The walk sits\n * here while the consumer's fetch is in flight; `recordPage` resolves it.\n * Carries the `cursor` being fetched so a post-eviction resume re-issues the\n * SAME request (idempotent retry) rather than skipping a page.\n *\n * - **paused** — backpressure tripped. The API still has more pages (a non-null\n * next cursor), but `seen` has reached `highWaterMark` and the consumer has\n * not drained yet. Carries the `cursor` to resume from. No fetch is\n * outstanding — the loop is deliberately idle until `resume`. This is the\n * valve that stops a fast producer (the API) from outrunning a slow consumer\n * (whatever processes each page) and blowing memory.\n *\n * - **done** — the API returned a null next cursor; the walk is exhausted. A\n * terminal phase: `start` / `recordPage` / `resume` are all no-ops on it\n * (idempotent completion — a late duplicate result can't un-finish a walk).\n *\n * ### Transition table\n *\n * Rows are the current phase; columns are the op. `→` denotes the next phase;\n * `self` means unchanged (a no-op for that (phase × op) pair). `next` is the\n * cursor the consumer extracted from the page; `hwm` is `highWaterMark`.\n *\n * | from \\ op | start | recordPage(count, next) | resume |\n * | ----------- | -------------- | -------------------------------------------------------------------- | ------------------------------ |\n * | idle | → fetching(c0) | self *(¹)* | self |\n * | fetching | self | seen=max(0,seen+count), pages+=1; next==null → done; seen≥hwm → paused(next); else → fetching(next) | self |\n * | paused | self | self *(¹)* | seen<hwm → fetching(cursor) *(²)* |\n * | done | self | self | self |\n *\n * *(¹)* `recordPage` only makes sense while `fetching` (a page was outstanding).\n * On any other phase it is a no-op: a result that arrives when no fetch was in\n * flight (a late duplicate, a double-dispatch) must not advance the cursor or\n * double-count `seen`. The walk is a coordination aid, not a correctness gate —\n * it absorbs the stray rather than corrupting the count.\n *\n * *(²)* `resume` re-arms the paused cursor ONLY once `seen` has dropped below\n * `highWaterMark` (the consumer drained, lowering `seen` via `drain`). Calling\n * `resume` while still over the mark is a no-op — the valve stays shut until\n * there is real headroom, so a consumer that resumes optimistically can't\n * defeat the backpressure. Resuming a walk that isn't `paused` is likewise a\n * no-op.\n *\n * ## Backpressure\n *\n * `seen` is the count of items the API has produced that the consumer has not\n * yet acknowledged as processed. `recordPage` adds each page's item count to it;\n * `drain(n)` subtracts what the consumer finished. When `seen` reaches\n * `highWaterMark`, `recordPage` parks the walk in `paused` instead of issuing\n * the next fetch — the producer stops until `drain` + `resume` re-open the\n * valve. A `highWaterMark` of `0` (or any non-positive value) disables\n * backpressure: the comparison `seen >= hwm` is satisfied vacuously only when\n * `hwm <= 0` would pause immediately, so we treat `hwm <= 0` as \"unbounded\" and\n * never pause. The walk then runs flat-out, `fetching → fetching` until `done`.\n *\n * Internal since #47 — not published on any subpath; reached from inside the\n * package as `internal/paginate/paginator`. Same one-shape-per-package rule as `work-queue`, `retry-backoff`,\n * `circuit-breaker`, and `rate-limit`. It is the L1 escape hatch the\n * `paginated-walk` composition builds its walk loop on.\n */\n\n/**\n * Paginator policy — pure configuration, no mutable state. One policy is shared\n * across the whole walk (often a module-scope const). `PaginatorState` carries\n * the cursor + counters; this carries the knobs they're compared against.\n *\n * Generic in `Cursor` so `firstCursor` matches whatever the API's cursor shape\n * is — an offset `number`, a page-token `string`, a `{ after: string }` record,\n * etc. The module never inspects the cursor; the type just threads through.\n */\nexport interface PaginatorPolicy<Cursor> {\n /**\n * The cursor the walk fetches first, armed by `start`. For an offset API this\n * is typically `0`; for a token API, `null`-or-empty-meaning-\"first page\" is\n * the API's call, so pass whatever sentinel the API treats as page one.\n */\n readonly firstCursor: Cursor;\n /**\n * Backpressure threshold: the number of un-drained items at which the walk\n * pauses instead of fetching the next page. `recordPage` parks in `paused`\n * once `seen >= highWaterMark`; `drain` + `resume` re-open the valve.\n *\n * A non-positive value (`<= 0`) disables backpressure — the walk never pauses\n * and runs `fetching → fetching` until the API exhausts. Use that when the\n * consumer processes each page synchronously inside `recordPage` and there is\n * nothing to back up.\n */\n readonly highWaterMark: number;\n}\n\n/**\n * Sensible defaults: start from a numeric offset of `0`, pause after 1000\n * un-drained items. Tuned for an offset-paginated REST API whose consumer\n * processes pages asynchronously; override per call site (especially\n * `firstCursor`, which is API-specific) rather than mutating this shared const.\n *\n * Typed as `PaginatorPolicy<number>` because the only honest default cursor is\n * the numeric offset `0`; a token-based API supplies its own `firstCursor` and\n * its own `PaginatorPolicy<string>`.\n */\nexport const defaultPaginatorPolicy: PaginatorPolicy<number> = {\n firstCursor: 0,\n highWaterMark: 1000,\n};\n\n/**\n * The walk's phase. A discriminated union on `phase` so each phase carries ONLY\n * its own data and impossible combinations are unrepresentable (pattern 11):\n * there is no `done` state that still holds a pending cursor, no `idle` state\n * mid-fetch. Every field is `readonly` so the whole value is trivially\n * JSON-serializable into a consumer's `Store<S>` and safe to thread through a\n * pure reducer / a Durable-Object reload.\n *\n * Two counters ride on every phase so a consumer reads them uniformly without\n * narrowing first:\n * - `seen` — items produced by the API but not yet drained (backpressure gauge).\n * - `pages` — total pages recorded so far (progress / observability).\n *\n * - `idle` — not started; `start` arms `firstCursor`.\n * - `fetching` — a page request for `cursor` is outstanding.\n * - `paused` — backpressure tripped; `cursor` is the page to resume from.\n * - `done` — API exhausted (null next cursor); terminal.\n */\nexport type PaginatorState<Cursor> =\n | { readonly phase: \"idle\"; readonly seen: number; readonly pages: number }\n | {\n readonly phase: \"fetching\";\n readonly cursor: Cursor;\n readonly seen: number;\n readonly pages: number;\n }\n | {\n readonly phase: \"paused\";\n readonly cursor: Cursor;\n readonly seen: number;\n readonly pages: number;\n }\n | { readonly phase: \"done\"; readonly seen: number; readonly pages: number };\n\n/** The starting state: idle, nothing seen, no pages recorded. */\nexport function initPaginator<Cursor>(): PaginatorState<Cursor> {\n return { phase: \"idle\", seen: 0, pages: 0 };\n}\n\n/**\n * Arm the first fetch. PURE — returns a new state; the input is never mutated.\n *\n * - `idle` → `fetching(firstCursor)`. This is the only phase `start` acts on.\n * - any other phase → unchanged. Re-arming a walk that has already begun (or\n * finished) would skip back to page one and double-count; a stray `start`\n * after launch is absorbed, not honored. Use `initPaginator()` to begin a\n * genuinely fresh walk.\n */\nexport function start<Cursor>(\n state: PaginatorState<Cursor>,\n policy: PaginatorPolicy<Cursor>,\n): PaginatorState<Cursor> {\n if (state.phase !== \"idle\") return state;\n return {\n phase: \"fetching\",\n cursor: policy.firstCursor,\n seen: state.seen,\n pages: state.pages,\n };\n}\n\n/**\n * Record the result of the outstanding fetch and decide what comes next. PURE —\n * returns a new state; the input is never mutated.\n *\n * `count` is how many items the just-fetched page yielded (added to `seen` for\n * backpressure accounting). `next` is the cursor the consumer extracted from the\n * page — a `Cursor` to fetch next, or `null`/`undefined` (a missing optional\n * cursor field) if the API signalled the end.\n *\n * - `fetching` → `seen = max(0, seen + count)`, `pages += 1`, then branch on\n * `next`:\n * - `next == null` → `done` (API exhausted; loose-null so a\n * missing/`undefined` cursor also finishes).\n * - `seen >= highWaterMark` → `paused(next)` (backpressure; `hwm <= 0`\n * disables this branch — see policy docs).\n * - otherwise → `fetching(next)` (keep walking).\n * - any other phase → unchanged. A result with no fetch outstanding (a late\n * duplicate, a double-dispatch) must not advance the cursor or double-count;\n * it is absorbed. See the module header's note ¹.\n *\n * `count` is floored into the gauge: a non-finite `count` (`NaN`/`Infinity`) is\n * treated as `0`, and the additive sum is clamped at `0`. A record can only hold\n * or raise `seen`; only `drain` lowers it. This keeps a stray negative or\n * non-finite count from poisoning the `seen >= highWaterMark` comparison and\n * silently defeating backpressure.\n *\n * The new `seen` total — not the pre-page value — is what the backpressure check\n * reads, so a single page large enough to cross the mark pauses immediately\n * rather than fetching one more first.\n */\nexport function recordPage<Cursor>(\n state: PaginatorState<Cursor>,\n count: number,\n next: Cursor | null | undefined,\n policy: PaginatorPolicy<Cursor>,\n): PaginatorState<Cursor> {\n if (state.phase !== \"fetching\") return state;\n // A page's item count must be a finite, non-negative addition to `seen`. A\n // negative count would drive `seen` down (a record can never lower the gauge —\n // only `drain` does) and a non-finite count (`NaN` / `Infinity`) would poison\n // every later `seen >= highWaterMark` comparison, permanently defeating\n // backpressure (NaN compares false, so the valve never trips). Floor the\n // CONTRIBUTION (not just the sum) at 0: clamp a non-finite count to 0\n // (mirroring retry-backoff's `Number.isNaN` discipline at backoffDelay ~:119)\n // and a negative count to 0. Flooring the contribution — rather than the sum —\n // is what makes a record strictly monotonic: `Math.max(0, seen + count)` only\n // catches a count that drags the *sum* below zero, so a small negative count\n // (e.g. `seen=10, count=-3 → 7`) would still silently lower the gauge and\n // break \"only `drain` lowers `seen`\". Clamping `count` first guarantees the\n // additive step can only hold or raise the gauge.\n const safeCount = Number.isFinite(count) ? Math.max(0, count) : 0;\n const seen = state.seen + safeCount;\n const pages = state.pages + 1;\n // Loose `== null` so a *missing* next cursor (`undefined`, common for an\n // optional API field the consumer extracted as `page.nextPageToken`) finishes\n // the walk exactly like an explicit `null`. Strict `=== null` would route an\n // `undefined` cursor into the fetching/paused branch and carry a cursor-less\n // (malformed) state forward.\n if (next == null) {\n return { phase: \"done\", seen, pages };\n }\n // `hwm <= 0` disables backpressure entirely (unbounded walk); only a positive\n // mark can park the loop. `seen >= hwm` is the half-open edge — reaching the\n // mark exactly pauses, matching the `>=` window edges `rate-limit` /\n // `circuit-breaker` use.\n if (policy.highWaterMark > 0 && seen >= policy.highWaterMark) {\n return { phase: \"paused\", cursor: next, seen, pages };\n }\n return { phase: \"fetching\", cursor: next, seen, pages };\n}\n\n/**\n * Acknowledge that the consumer finished processing `n` items, lowering the\n * backpressure gauge. PURE — returns a new state; the input is never mutated.\n *\n * Subtracts `n` from `seen`, floored at 0 (a consumer that over-acks, or acks a\n * page recorded before a reload, can never drive `seen` negative). `n <= 0` is a\n * no-op on the count. `drain` does NOT change the phase — it only frees\n * headroom; `resume` is the separate, explicit decision to re-issue a fetch once\n * the headroom exists. Splitting \"I drained\" from \"I want to continue\" lets a\n * consumer drain in several steps and resume once.\n *\n * Works on every phase: a consumer may drain while still `fetching` (the gauge\n * just drops, no pause was reached) or while `paused` (freeing room for a later\n * `resume`). On `done` the gauge can still be drained for accurate final\n * accounting, though it no longer gates anything.\n */\nexport function drain<Cursor>(\n state: PaginatorState<Cursor>,\n n: number,\n): PaginatorState<Cursor> {\n if (n <= 0) return state;\n const seen = Math.max(0, state.seen - n);\n if (seen === state.seen) return state;\n return { ...state, seen };\n}\n\n/**\n * Re-open the backpressure valve after a `drain`. PURE — returns a new state;\n * the input is never mutated.\n *\n * - `paused` AND `seen < highWaterMark` → `fetching(cursor)`: the consumer has\n * drained below the mark, so re-issue the parked fetch.\n * - `paused` but `seen >= highWaterMark` → unchanged. There is still no\n * headroom; the valve stays shut until a `drain` lowers `seen`. A consumer\n * that resumes optimistically can't defeat the backpressure. (When `hwm <=\n * 0` backpressure is disabled, so the walk never reaches `paused` and this\n * branch is unreachable — but the guard reads `< hwm` consistently, and a\n * non-positive `hwm` makes `seen < hwm` false for any non-negative `seen`,\n * correctly refusing to resume a state that should never have paused.)\n * - any other phase → unchanged. Resuming a walk that isn't paused (idle,\n * fetching, done) is a no-op.\n *\n * See the module header's note ².\n */\nexport function resume<Cursor>(\n state: PaginatorState<Cursor>,\n policy: PaginatorPolicy<Cursor>,\n): PaginatorState<Cursor> {\n if (state.phase !== \"paused\") return state;\n if (state.seen < policy.highWaterMark) {\n return {\n phase: \"fetching\",\n cursor: state.cursor,\n seen: state.seen,\n pages: state.pages,\n };\n }\n return state;\n}\n\n/**\n * Whether the walk has finished (the API returned a null next cursor). PURE,\n * read-only. A derived predicate so a consumer's reducer can branch\n * (`if (isDone(walk)) return [doneState, [joinCmd]]`) without re-checking the\n * `phase` discriminant by hand at every call site.\n */\nexport function isDone<Cursor>(state: PaginatorState<Cursor>): boolean {\n return state.phase === \"done\";\n}\n","/**\n * internal/paginate/paginated-walk — traverse a paginated API / sitemap end to end\n * WITHOUT a fake clock, without 429s, and resumable across a Durable-Object\n * eviction. The L2 composition that bolts `../resilient-call`'s resilience onto\n * `../paginator`'s cursor walk: each page is fetched as ONE keyed resilient\n * call (retry + backoff + rate-limit gate), and the cursor advances only once a\n * page settles `ok`.\n *\n * ## The recipe (the two siblings it composes)\n *\n * - **`../paginator`** owns the walk loop: the opaque `Cursor`, the `seen` /\n * `pages` counters, and the `idle → fetching → … → done` phase machine.\n * `paginated-walk` never re-implements cursor advancement — it folds each\n * settled page through `recordPage`, letting the paginator decide\n * `done` / `fetching(next)` / `paused`. Backpressure is a TWO-way valve:\n * `recordPage` parks the walk in `paused` once `seen >= highWaterMark`, and\n * the `drain` / `resume` verbs (delegating to the paginator's own\n * `drain` / `resume`) lower the gauge and re-open the parked fetch — without\n * them `highWaterMark` would be a one-way valve that strands a paused walk.\n * - **`../resilient-call`** owns the page fetch: the single `resilient_run`\n * effect per page is gated through that knob's cache → circuit → rate-limit\n * → retry pipeline. A transient page failure backs off and re-issues the\n * SAME cursor (idempotent retry); the walk does not advance until the page\n * genuinely settles.\n *\n * Those are the only two siblings this module calls into. The resilience knobs\n * (`retry` / `circuit` / `rateLimit` / `deadline`) are NOT separate bricks\n * `paginated-walk` composes — they belong to `resilient-call`, which owns the\n * rate-limit gate internally. This module merely passes them through to\n * `createResilientCall` and re-uses two config TYPES at the boundary:\n * `RateLimitConfig` (re-exported by `resilient-call`) and `RetryPolicy` (from\n * `../retry-backoff`). Pass a `rateLimit` and `resilient-call`'s token bucket\n * throttles page fetches (\"don't 429 the upstream\"); omit it and the walk runs\n * as fast as the upstream answers.\n *\n * Because the page-fetch resilience IS a `resilient-call` slice, this knob\n * **inherits that knob's contract verbatim**: the slice is a flat record, every\n * verb returns `readonly [State, Cmd[]]`, time is always an `at` parameter, and\n * the jitter RNG is injected once at `createPaginatedWalk(config, rng)` — never\n * read inside a verb (per the spec, `paginated-walk` *extends* `resilient-call`).\n *\n * ## The two non-negotiables (canon)\n *\n * - **Durable** — the slice is plain data: the paginator phase + counters, and\n * the resilient-call brick states (all JSON-serializable). The cursor lives\n * in the Model, not a closure, so a walk survives eviction mid-traversal and\n * resumes from the exact page it was fetching.\n * - **Replayable** — every transition is a verb returning new state + Cmds.\n * `nextCursor` / `onPage` are pure config functions applied at the verb\n * boundary; the Cmds they yield are plain data. `replay` reconstructs the\n * whole walk exactly.\n *\n * ## Where the clock / RNG live\n *\n * Inside the verbs: nowhere. `at` arrives on `start` / `pageOk` / `pageErr` /\n * `onTimer` and is threaded straight into the underlying resilient-call gate (so\n * a rate-limited or retried page measures backoff from that `at`). The jitter\n * RNG is injected at construction. The only clock read is `Date.now()` inside\n * the resilient-call `handlers` port (the effect boundary), inherited unchanged.\n *\n * ## The fixed fetch key\n *\n * A walk fetches one page at a time, so the resilient-call layer tracks exactly\n * one logical call under the constant key `PAGE_KEY`. The cursor being fetched\n * is the resilient call's `input`, so a post-eviction resume re-issues the same\n * page request. (`resilient-call` is keyed to support N concurrent calls; a walk\n * deliberately uses a single key — the paginator is the sequencer.)\n *\n * ## Typical wiring\n *\n * const walk = createPaginatedWalk<PageToken, ApiPage>({\n * firstPage: null, // the API's \"page one\" sentinel\n * nextCursor: (page) => page.nextToken ?? null,\n * onPage: (page) => [{ type: \"index_rows\", rows: page.items }],\n * rateLimit: { capacity: 10, refillPerSec: 5 },\n * retry: defaultRetryPolicy,\n * });\n *\n * // in the machine:\n * init: () => [{ walk: walk.init() }, []],\n * update: {\n * start: (s, m) => lift(s, walk.start(s.walk, m.at)),\n * page_ok: (s, m) => lift(s, walk.pageOk(s.walk, m.result, m.at)),\n * page_err: (s, m) => lift(s, walk.pageErr(s.walk, m.error, m.at)),\n * retry_due:(s, m) => lift(s, walk.onTimer(s.walk, m)),\n * drained: (s, m) => lift(s, walk.drain(s.walk, m.n)),\n * resume: (s, m) => lift(s, walk.resume(s.walk, m.at)),\n * },\n * subscriptions: (s) => walk.subs(s.walk),\n * subscribe: { deadline: subscribeDeadline },\n * interpret: walk.handlers({ run: (cursor) => api.fetchPage(cursor) }),\n */\n\nimport type { Cmd } from \"../../../index\";\nimport { MsgType } from \"../../../protocol\";\nimport type { RetryPolicy } from \"../../../retry-backoff\";\nimport {\n type CircuitConfig,\n createResilientCall,\n type DeadlineConfig,\n type DeadlineSub,\n deadlineSub,\n type FailMsg,\n type RateLimitConfig,\n type ResilientPorts,\n type ResilientState,\n type ResilientTimerMsg,\n type RunCmd,\n type SucceedMsg,\n subscribeDeadline,\n} from \"../../resilience/resilient-call\";\nimport {\n drain as drainWalk,\n initPaginator,\n isDone,\n type PaginatorPolicy,\n type PaginatorState,\n recordPage,\n resume as resumeWalk,\n start as startWalk,\n} from \"../paginator\";\n\n// ===========================================================================\n// Config — the knob. `firstPage` / `nextCursor` / `onPage` describe the walk;\n// the resilience bricks are inherited from resilient-call (every one optional —\n// omit a brick, omit its gate).\n// ===========================================================================\n\n/**\n * The paginated-walk knob. Generic in `Cursor` (the opaque page handle the\n * upstream API hands back — offset, page token, \"next\" URL, …) and `Page` (the\n * shape your fetch port resolves with).\n *\n * `nextCursor` and `onPage` are PURE config functions applied at the verb\n * boundary, never stored in the slice or in a Cmd (invariant 3 — Cmds are\n * plain data, not closures). `nextCursor(page)` extracts the API's next-page\n * handle (or `null` at the end); `onPage(page)` yields the Cmds the consumer\n * wants emitted for each successfully fetched page (index rows, write to R2,\n * dispatch a downstream Msg — whatever; an empty array is fine).\n *\n * The resilience bricks (`retry` / `circuit` / `rateLimit` / `deadline`) are\n * the SAME optional knobs `resilient-call` exposes — paginated-walk extends it.\n * `rateLimit` is the brick that keeps the walk from 429-ing the upstream.\n */\nexport interface PaginatedWalkConfig<Cursor, Page, EmittedCmd extends Cmd> {\n /**\n * The cursor the walk fetches first. For an offset API this is typically `0`;\n * for a token API, whatever sentinel the API treats as \"page one\" (often\n * `null` or `\"\"`). Threaded into `paginator`'s `firstCursor`.\n */\n readonly firstPage: Cursor;\n /**\n * Extract the next cursor from a just-fetched page. Return a `Cursor` to keep\n * walking, or `null` when the API signalled the end (the walk finishes). PURE\n * + consumer-specific (only the consumer knows the API's pagination shape).\n */\n readonly nextCursor: (page: Page) => Cursor | null;\n /**\n * The Cmds to emit for each successfully fetched page — the consumer's work\n * per page (index, persist, fan out, …). Returns plain-data Cmds; `[]` is a\n * valid \"I process the page synchronously elsewhere\" answer. PURE.\n */\n readonly onPage: (page: Page) => readonly EmittedCmd[];\n /**\n * How many items a page contributes to the paginator's backpressure gauge\n * (`seen`). Defaults to `1` per page (count pages, not items) when omitted; a\n * consumer that wants per-item backpressure returns `page.items.length`. PURE.\n */\n readonly pageSize?: (page: Page) => number;\n /**\n * Backpressure threshold handed to the paginator. `seen >= highWaterMark`\n * parks the walk in `paused` instead of fetching the next page. Non-positive\n * (the default `0`) disables backpressure — the walk runs flat out until the\n * API exhausts. See `../paginator`'s `highWaterMark` docs.\n */\n readonly highWaterMark?: number;\n\n // --- resilience bricks, inherited from resilient-call (all optional) ---\n /** Exponential-backoff retry policy for a transient page-fetch failure. */\n readonly retry?: RetryPolicy;\n /** Circuit breaker around the page fetch (one upstream → one breaker). */\n readonly circuit?: CircuitConfig;\n /** Token-bucket rate limit — the \"don't 429 the upstream\" knob. */\n readonly rateLimit?: RateLimitConfig;\n /** Overall wall-clock deadline per individual page fetch. */\n readonly deadline?: DeadlineConfig;\n}\n\n// ===========================================================================\n// Slice — the Model field this knob owns. The paginator walk state + the\n// resilient-call slice for the page fetch. Both are flat plain data.\n// ===========================================================================\n\n/**\n * The slice. `walk` is the cursor/phase/counters from `../paginator`;\n * `resilience` is the `../resilient-call` slice that gates the page fetch.\n * Every field is JSON-serializable, so the whole walk survives a reload.\n *\n * The two move in lockstep: `walk` decides WHICH cursor to fetch next, while\n * `resilience` decides HOW that fetch is attempted (gated, retried, backed\n * off). `pageOk` advances `walk`; `pageErr` defers to `resilience` and leaves\n * `walk` parked on the same cursor until the page truly settles.\n */\nexport interface PaginatedWalkState<Cursor, Page> {\n readonly walk: PaginatorState<Cursor>;\n readonly resilience: ResilientState<Cursor, Page>;\n}\n\n// ===========================================================================\n// Cmds + Msgs the knob speaks.\n// ===========================================================================\n\n/**\n * The page-fetch effect this knob emits: the inherited `resilient_run` Cmd from\n * resilient-call, whose `input` is the `Cursor` to fetch and whose `key` is the\n * fixed `PAGE_KEY`. The consumer's `handlers(ports)` interprets it.\n */\nexport type FetchPageCmd<Cursor> = RunCmd<Cursor>;\n\n/** Page-settled Msgs the `handlers` port dispatches back (inherited verbatim). */\nexport type PageOkMsg<Page> = SucceedMsg<Page>;\nexport type PageErrMsg = FailMsg;\n\n/** The retry / deadline timer Msg — inherited from resilient-call. */\nexport type PaginatedWalkTimerMsg = ResilientTimerMsg;\n\n/**\n * Ports the consumer supplies to `handlers`. `run(cursor)` fetches ONE page for\n * the given cursor; throwing routes to `pageErr` (and thus the resilience\n * backoff). Same shape as resilient-call's port, specialized to the page fetch.\n */\nexport interface PaginatedWalkPorts<Cursor, Page>\n extends ResilientPorts<Cursor, Page> {}\n\n// ===========================================================================\n// The fixed key. A walk fetches one page at a time → exactly one logical\n// resilient call. The paginator is the sequencer; resilient-call's keying is\n// pinned to this single key.\n// ===========================================================================\n\n/** The single resilient-call key every page fetch runs under. */\nexport const PAGE_KEY = \"page\";\n\n// ===========================================================================\n// The knob factory.\n// ===========================================================================\n\n/**\n * Build a paginated-walk knob from `config`. `rng` is injected for the\n * underlying retry jitter — pass a fixed `() => 0` in tests to pin backoff;\n * defaults to `Math.random` (read at the verb boundary inside resilient-call,\n * never in a `paginated-walk` verb). Inherited straight from resilient-call.\n *\n * Returns the uniform L2 knob contract: `init()`, the verbs `start` / `pageOk`\n * / `pageErr` / `onTimer`, `subs(state)`, and `handlers(ports)`. `Cursor` is\n * the page handle, `Page` the fetch result, `EmittedCmd` the consumer's\n * per-page Cmds plus the inherited `FetchPageCmd`.\n */\nexport function createPaginatedWalk<Cursor, Page, EmittedCmd extends Cmd = Cmd>(\n config: PaginatedWalkConfig<Cursor, Page, EmittedCmd>,\n rng: () => number = Math.random,\n) {\n // The page fetch is a resilient call keyed by Cursor input → Page result.\n // We feed it only the resilience bricks; the walk-loop knobs are ours.\n const rc = createResilientCall<Cursor, Page>(\n {\n retry: config.retry,\n circuit: config.circuit,\n rateLimit: config.rateLimit,\n deadline: config.deadline,\n },\n rng,\n );\n\n // The paginator policy: our firstPage is the paginator's firstCursor; the\n // high-water mark gates backpressure (0 / omitted → unbounded).\n const paginatorPolicy: PaginatorPolicy<Cursor> = {\n firstCursor: config.firstPage,\n highWaterMark: config.highWaterMark ?? 0,\n };\n\n // The combined Cmd type the verbs can emit: the inherited page-fetch effect\n // plus whatever `onPage` yields.\n type OutCmd = FetchPageCmd<Cursor> | EmittedCmd;\n\n /** Replace the resilience slice, leaving `walk` untouched. */\n function withResilience(\n s: PaginatedWalkState<Cursor, Page>,\n resilience: ResilientState<Cursor, Page>,\n ): PaginatedWalkState<Cursor, Page> {\n return { ...s, resilience };\n }\n\n /** The starting slice: an idle paginator + a fresh resilient-call slice. */\n function init(): PaginatedWalkState<Cursor, Page> {\n return {\n walk: initPaginator<Cursor>(),\n resilience: rc.init(),\n };\n }\n\n /**\n * Issue a resilient page fetch for `cursor` at `at`. The cursor is the\n * resilient call's `input` (so a retry / resume re-fetches the same page) and\n * `PAGE_KEY` is the fixed key. Returns the advanced resilience slice + the\n * `resilient_run` Cmd (or none, if the gate cached / fast-failed / backed off).\n * PURE — `at` is the only clock. Shared by `start` and `pageOk`'s advance.\n */\n function fetch(\n s: PaginatedWalkState<Cursor, Page>,\n cursor: Cursor,\n at: number,\n ): readonly [\n PaginatedWalkState<Cursor, Page>,\n readonly FetchPageCmd<Cursor>[],\n ] {\n const [resilience, cmds] = rc.attempt(s.resilience, PAGE_KEY, cursor, at);\n return [withResilience(s, resilience), cmds];\n }\n\n /**\n * Issue the page fetch for a freshly-armed paginator transition, else a pure\n * no-op. `walk` is the result of a paginator verb (`startWalk` / `resumeWalk`)\n * applied to `s.walk`; this states the arm-and-fetch contract ONCE for the two\n * verbs that share it (`start`, `resume`).\n *\n * A paginator verb is a pure no-op on any phase it does not act on — it returns\n * the SAME state object. Reference identity (not phase) is the honest \"did the\n * verb arm a fresh fetch?\" check: a walk already `fetching` would also satisfy\n * `phase === \"fetching\"` yet must NOT re-issue a fetch (page-one skip /\n * double-fetch). So we proceed ONLY when the verb produced a fresh state. The\n * `phase === \"fetching\"` narrow that follows is therefore always true here\n * (both verbs only ever produce a `fetching` arm), and also narrows `.cursor`\n * for the type checker. PURE — `at` threads into the fetch gate.\n */\n function armAndFetch(\n s: PaginatedWalkState<Cursor, Page>,\n walk: PaginatorState<Cursor>,\n at: number,\n ): readonly [PaginatedWalkState<Cursor, Page>, readonly OutCmd[]] {\n if (walk === s.walk || walk.phase !== \"fetching\") {\n return [s, []];\n }\n return fetch({ ...s, walk }, walk.cursor, at);\n }\n\n // === Verb: start =========================================================\n\n /**\n * Begin the walk: arm the paginator's first cursor and issue the first page\n * fetch (gated through resilience). On any non-`idle` paginator phase this is\n * a no-op — re-starting a walk in flight would skip back to page one and\n * double-count, so a stray `start` after launch is absorbed (the paginator's\n * own `start` guard). PURE — `at` threads into the fetch gate.\n */\n function start(\n s: PaginatedWalkState<Cursor, Page>,\n at: number,\n ): readonly [PaginatedWalkState<Cursor, Page>, readonly OutCmd[]] {\n // `startWalk` is a pure no-op on any non-`idle` phase (same reference); a\n // fresh `fetching(firstCursor)` arm → issue the first page fetch.\n return armAndFetch(s, startWalk(s.walk, paginatorPolicy), at);\n }\n\n // === Verb: pageOk ========================================================\n\n /**\n * Record a successfully fetched `page` and decide what comes next. PURE —\n * `at` is the cache / next-fetch clock.\n *\n * 1. Settle the resilient call OK (closes the breaker, fills the cache,\n * resets the page-fetch retry counter) — inherited from resilient-call.\n * 2. Fold the page through the paginator: add `pageSize(page)` to `seen`,\n * bump `pages`, and branch on `nextCursor(page)`:\n * - `null` → the API is exhausted → paginator goes `done`.\n * - a cursor → advance, then either fetch it (backpressure room) or\n * park in `paused` (high-water mark reached).\n * 3. Emit the consumer's `onPage(page)` Cmds either way, PLUS the next\n * page-fetch effect when the paginator stays `fetching`.\n *\n * A `pageOk` that arrives when the paginator is not `fetching` (a late\n * duplicate, a double-dispatch) is absorbed by `recordPage` — the cursor does\n * not advance and `seen` is not double-counted; only the resilient settle +\n * `onPage` Cmds happen. The walk is a coordination aid, not a correctness\n * gate.\n */\n function pageOk(\n s: PaginatedWalkState<Cursor, Page>,\n page: Page,\n at: number,\n ): readonly [PaginatedWalkState<Cursor, Page>, readonly OutCmd[]] {\n // 1) Settle the underlying resilient call as a success.\n const [resilience] = rc.succeed(s.resilience, PAGE_KEY, {\n type: MsgType.ResilientOk,\n key: PAGE_KEY,\n result: page,\n at,\n });\n\n // 2) Advance the paginator with the page's contribution + the next cursor.\n const count = config.pageSize ? config.pageSize(page) : 1;\n const next = config.nextCursor(page);\n const walk = recordPage(s.walk, count, next, paginatorPolicy);\n\n const advanced: PaginatedWalkState<Cursor, Page> = { walk, resilience };\n\n // 3) The consumer's per-page work, always emitted on a successful page.\n const pageCmds: readonly EmittedCmd[] = config.onPage(page);\n\n // If the paginator stayed `fetching`, issue the next page fetch (gated).\n if (walk.phase === \"fetching\") {\n const [withFetch, fetchCmds] = fetch(advanced, walk.cursor, at);\n return [withFetch, [...pageCmds, ...fetchCmds]];\n }\n\n // `done` (exhausted) or `paused` (backpressure) → no further fetch now.\n return [advanced, pageCmds];\n }\n\n // === Verb: pageErr =======================================================\n\n /**\n * Record a failed page fetch and back off via resilience. PURE — `at` stamps\n * the breaker trip + the retry-delay base.\n *\n * Defers to the resilient-call `fail` verb: it trips the breaker and either\n * schedules a retry (paginator stays parked on the SAME cursor — no advance)\n * or settles the resilient call `failed` once retries are exhausted. The\n * paginator is deliberately untouched: a failed fetch must NOT advance the\n * cursor or skip a page. When the retry timer later fires, `onTimer` re-issues\n * the same page fetch.\n *\n * GUARDED by the paginator phase, exactly as `pageOk` is guarded by\n * `recordPage`'s `fetching`-only check. A `pageErr` only makes sense while a\n * page fetch is outstanding (`walk.phase === \"fetching\"`). A stray `pageErr`\n * that arrives after the walk has settled — `done` (exhausted), `paused`\n * (backpressure), or `idle` (not started) — has NO outstanding fetch to fail.\n * Without this guard it would still call `rc.fail`, which trips the SHARED\n * circuit breaker (dinging a healthy upstream) and overwrites the settled\n * `PAGE_KEY` resilient slot with `failed` (clobbering the succeeded result a\n * resume would otherwise observe). So a stray `pageErr` is absorbed as a pure\n * no-op — identity preserved — matching `pageOk`'s \"the walk is a coordination\n * aid, not a correctness gate\" stance.\n */\n function pageErr(\n s: PaginatedWalkState<Cursor, Page>,\n error: unknown,\n at: number,\n ): readonly [PaginatedWalkState<Cursor, Page>, readonly OutCmd[]] {\n // Only a `fetching` paginator has an outstanding page fetch to fail. On any\n // other phase the fail would corrupt shared resilience state for a fetch\n // that is not in flight — absorb it as a pure no-op.\n if (s.walk.phase !== \"fetching\") {\n return [s, []];\n }\n const [resilience, cmds] = rc.fail(s.resilience, PAGE_KEY, {\n type: MsgType.ResilientErr,\n key: PAGE_KEY,\n error,\n at,\n });\n return [withResilience(s, resilience), cmds];\n }\n\n // === Verb: onTimer =======================================================\n\n /**\n * A retry / deadline timer fired. Defers to resilient-call's `onTimer`: a\n * retry timer re-runs the gate for the page-fetch's remembered cursor (re-\n * issuing the SAME page); a deadline timer settles the resilient call failed.\n * The paginator stays parked on its cursor throughout — only `pageOk` advances\n * it. A stale fire is a no-op (inherited). PURE.\n *\n * GUARDED by the paginator phase, for the same reason as `pageErr`. A timer is\n * only meaningful while a page fetch is outstanding (`walk.phase ===\n * \"fetching\"`). A stray timer that fires after the walk has settled (`done` /\n * `paused` / `idle`) would otherwise re-enter `rc.onTimer` — re-issuing a fetch\n * the paginator no longer wants (a deadline timer could even overwrite the\n * settled `PAGE_KEY` slot with a deadline `failed`). On any non-`fetching`\n * phase the timer is absorbed as a pure no-op.\n */\n function onTimer(\n s: PaginatedWalkState<Cursor, Page>,\n msg: PaginatedWalkTimerMsg,\n ): readonly [PaginatedWalkState<Cursor, Page>, readonly OutCmd[]] {\n if (s.walk.phase !== \"fetching\") {\n return [s, []];\n }\n const [resilience, cmds] = rc.onTimer(s.resilience, msg);\n if (resilience === s.resilience) {\n // Inherited no-op identity → keep our slice identity too (pure no-op).\n return [s, []];\n }\n return [withResilience(s, resilience), cmds];\n }\n\n // === Verb: drain =========================================================\n\n /**\n * Acknowledge that the consumer finished processing `n` items, lowering the\n * paginator's backpressure gauge (`seen`). PURE — no clock. Delegates to\n * `paginator.drain`: subtracts `n` (floored at 0), leaves the phase untouched.\n *\n * The OTHER half of the backpressure valve `highWaterMark` opens: `recordPage`\n * parks the walk in `paused` once `seen >= highWaterMark`, and `drain` is how a\n * consumer lowers `seen` back below the mark so `resume` can re-open the fetch.\n * Without it `highWaterMark` would be a one-way valve — a walk could pause but\n * never continue. Emits no Cmds: draining only frees headroom; `resume` is the\n * separate, explicit \"continue now\" decision.\n */\n function drain(\n s: PaginatedWalkState<Cursor, Page>,\n n: number,\n ): readonly [PaginatedWalkState<Cursor, Page>, readonly OutCmd[]] {\n const walk = drainWalk(s.walk, n);\n if (walk === s.walk) {\n // Pure no-op identity (n <= 0, or seen already at floor).\n return [s, []];\n }\n return [{ ...s, walk }, []];\n }\n\n // === Verb: resume ========================================================\n\n /**\n * Re-open the backpressure valve after a `drain`: if the walk is `paused` and\n * `seen` has dropped below `highWaterMark`, re-arm the parked cursor and issue\n * its page fetch (gated through resilience). PURE — `at` threads into the fetch\n * gate. Delegates the phase decision to `paginator.resume`:\n *\n * - `paused` AND `seen < highWaterMark` → `fetching(cursor)` → emit the fetch.\n * - `paused` but still over the mark → no-op (the valve stays shut until a\n * further `drain` frees real headroom).\n * - any other phase → no-op (nothing to resume).\n *\n * Together with `drain` this completes the valve: `recordPage` shuts it\n * (`paused`), `drain` lowers the gauge, `resume` re-opens it and fires the next\n * page fetch — the same `fetch` helper `start` / `pageOk` use, so a resumed\n * fetch is gated identically (cache / circuit / rate-limit / retry).\n */\n function resume(\n s: PaginatedWalkState<Cursor, Page>,\n at: number,\n ): readonly [PaginatedWalkState<Cursor, Page>, readonly OutCmd[]] {\n // `resumeWalk` is a pure no-op (same reference) on any phase but `paused`, or\n // when `seen` is still at/over the mark. Only a fresh `fetching` arm means\n // the valve actually re-opened — issue the page fetch only then.\n return armAndFetch(s, resumeWalk(s.walk, paginatorPolicy), at);\n }\n\n // === Derived predicates ==================================================\n\n /**\n * Whether the walk has finished — the API returned a null next cursor and the\n * paginator is `done`. PURE, read-only. Lets a consumer's reducer fire a\n * \"walk complete\" Cmd (`if (walk.isDone(s.walk)) …`) without re-checking the\n * paginator phase by hand.\n */\n function isComplete(s: PaginatedWalkState<Cursor, Page>): boolean {\n return isDone(s.walk);\n }\n\n /**\n * The terminal page-fetch failure that stranded the walk, or `undefined` if no\n * page has terminally failed. PURE, read-only. Errors are data: a page that\n * exhausts its retries (or trips the deadline) settles the `PAGE_KEY` resilient\n * slot to `failed` while the paginator stays parked on its cursor — the walk is\n * stuck and will never advance on its own. Without a way to read that phase the\n * failure is unobservable: the walk simply stops emitting fetches with no\n * signal. `failure(s)` surfaces the settled error (the same plain-data sentinel\n * resilient-call records) so a consumer can fire a \"walk dead\" Cmd, alert, or\n * re-`start` a fresh walk.\n */\n function failure(s: PaginatedWalkState<Cursor, Page>): unknown {\n const call = s.resilience.calls[PAGE_KEY];\n return call?.phase === \"failed\" ? call.error : undefined;\n }\n\n /**\n * Whether the walk is STUCK: a page fetch has terminally `failed` yet the\n * paginator is still parked on a cursor (not `done`), so no further fetch will\n * ever be issued without intervention. PURE, read-only.\n *\n * This is the dead-walk predicate the consumer polls (`if (walk.isStuck(s)) …`)\n * to distinguish a healthy finish (`isComplete`) from a silent death. A walk is\n * stuck precisely when the resilient slot settled `failed` AND the paginator\n * has NOT reached `done` — a terminal failure on the final page that also\n * finished the walk is `isComplete`, not stuck. Pairs with `failure(s)`, which\n * hands back the error that stuck it.\n */\n function isStuck(s: PaginatedWalkState<Cursor, Page>): boolean {\n return s.resilience.calls[PAGE_KEY]?.phase === \"failed\" && !isDone(s.walk);\n }\n\n // === Subs ================================================================\n\n /**\n * Pre-wired subscriptions: exactly the resilient-call subs for the page\n * fetch — a retry timer while the fetch is `waiting_retry`, and (with the\n * `deadline` brick) a per-fetch deadline timer while it is active. Reconciled\n * by id, so a phase change cancels the matching timer automatically. Wire\n * `subscribe: { deadline: subscribeDeadline }` (re-exported below).\n */\n function subs(s: PaginatedWalkState<Cursor, Page>): readonly DeadlineSub[] {\n return rc.subs(s.resilience);\n }\n\n // === Handlers ============================================================\n\n /**\n * Pre-wired interpret handler for the page-fetch effect. Inherited from\n * resilient-call (`resilient_run`): wraps the consumer's `run(cursor)` port\n * via `tryInterpret` (Railway) — success → `resilient_ok` (handled by\n * `pageOk`), failure → `resilient_err` (handled by `pageErr`), each stamped\n * with `Date.now()` at the effect boundary (the ONE permitted clock read).\n *\n * interpret: walk.handlers({ run: (cursor) => api.fetchPage(cursor) })\n *\n * The consumer maps the inherited Msg types to their `pageOk` / `pageErr`\n * reducer cells (the wire shape is `resilient_ok` / `resilient_err`).\n */\n function handlers(ports: PaginatedWalkPorts<Cursor, Page>) {\n return rc.handlers(ports);\n }\n\n return {\n init,\n start,\n pageOk,\n pageErr,\n onTimer,\n drain,\n resume,\n isComplete,\n failure,\n isStuck,\n subs,\n handlers,\n };\n}\n\n/**\n * Lift a knob result `[slice, cmds]` into a host `[State, cmds]` where the slice\n * lives at `state.walk`. A convenience for the common single-slice host;\n * consumers with a differently-named field spread by hand. Pure — a thin record\n * rebuild, no clock / RNG.\n */\nexport function liftWalk<\n S extends { walk: PaginatedWalkState<Cursor, Page> },\n Cursor,\n Page,\n C extends Cmd,\n>(\n state: S,\n [slice, cmds]: readonly [PaginatedWalkState<Cursor, Page>, readonly C[]],\n): readonly [S, readonly C[]] {\n return [{ ...state, walk: slice }, cmds];\n}\n\n/**\n * Re-export the deadline Sub primitives (inherited from resilient-call) so\n * consumers wire one import: `subscribeDeadline` is the `subscribe` cell,\n * `deadlineSub` builds the Sub literal this knob's `subs` emits.\n */\nexport { subscribeDeadline, deadlineSub };\nexport type { DeadlineSub };\n"]}
@@ -1,10 +1,6 @@
1
- export { debounce } from '../chunk-J6527VRQ.js';
2
- import { subscribeDeadline, deadlineExceeded, deadlineSub } from '../chunk-DGE4PV4A.js';
3
- import '../chunk-2YFLOCWX.js';
4
- import '../chunk-ZSUUOEPL.js';
5
- import '../chunk-OOYD44JI.js';
1
+ import { subscribeDeadline, deadlineExceeded, deadlineSub } from './chunk-22POWGFA.js';
6
2
 
7
- // src/batch-window/index.ts
3
+ // src/internal/flow/batch-window/index.ts
8
4
  function initBatchWindow() {
9
5
  return { buffer: [], openedAt: null };
10
6
  }
@@ -39,5 +35,5 @@ function createBatchWindow(config) {
39
35
  }
40
36
 
41
37
  export { addItem, batchWindowExpired, createBatchWindow, initBatchWindow, onWindow, subsFor, subscribeBatchWindow };
42
- //# sourceMappingURL=index.js.map
43
- //# sourceMappingURL=index.js.map
38
+ //# sourceMappingURL=chunk-QPEVCJ5Q.js.map
39
+ //# sourceMappingURL=chunk-QPEVCJ5Q.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/internal/flow/batch-window/index.ts"],"names":[],"mappings":";;;AA2IO,SAAS,eAAA,GAAqC;AACnD,EAAA,OAAO,EAAE,MAAA,EAAQ,EAAC,EAAG,UAAU,IAAA,EAAK;AACtC;AAqBO,SAAS,kBAAA,CACd,IACA,IAAA,EACoB;AACpB,EAAA,OAAO,gBAAA,CAAiB,IAAI,IAAI,CAAA;AAClC;AAkEO,SAAS,OAAA,CACd,MAAA,EACA,KAAA,EACA,IAAA,EACA,EAAA,EACyC;AACzC,EAAA,MAAM,MAAA,GAAS,CAAC,GAAG,KAAA,CAAM,QAAQ,IAAI,CAAA;AAGrC,EAAA,MAAM,QAAA,GAAW,MAAM,QAAA,IAAY,EAAA;AAInC,EAAA,IAAI,OAAO,MAAA,IAAU,IAAA,CAAK,IAAI,CAAA,EAAG,MAAA,CAAO,QAAQ,CAAA,EAAG;AACjD,IAAA,OAAO,CAAC,iBAAmB,EAAG,CAAC,OAAO,KAAA,CAAM,MAAM,CAAC,CAAC,CAAA;AAAA,EACtD;AAEA,EAAA,OAAO,CAAC,EAAE,MAAA,EAAQ,QAAA,EAAS,EAAG,EAAE,CAAA;AAClC;AAuBO,SAAS,QAAA,CACd,MAAA,EACA,KAAA,EACA,GAAA,EACyC;AAGzC,EAAA,IAAI,KAAA,CAAM,OAAO,MAAA,KAAW,CAAA,SAAU,CAAC,KAAA,EAAO,EAAE,CAAA;AAChD,EAAA,OAAO,CAAC,iBAAmB,EAAG,CAAC,OAAO,KAAA,CAAM,KAAA,CAAM,MAAM,CAAC,CAAC,CAAA;AAC5D;AAqBO,SAAS,OAAA,CACd,MAAA,EACA,KAAA,EACA,EAAA,GAAK,cAAA,EACsB;AAK3B,EAAA,IAAI,KAAA,CAAM,QAAA,KAAa,IAAA,EAAM,OAAO,EAAC;AACrC,EAAA,OAAO,CAAC,WAAA,CAAY,EAAA,EAAI,MAAM,QAAA,GAAW,MAAA,CAAO,KAAK,CAAC,CAAA;AACxD;AAQO,IAAM,oBAAA,GAIT;AAYG,SAAS,kBACd,MAAA,EACuB;AACvB,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,MAAM,eAAA,EAAmB;AAAA,IAC/B,GAAA,EAAK,CAAC,KAAA,EAAO,IAAA,EAAM,OAAO,OAAA,CAAQ,MAAA,EAAQ,KAAA,EAAO,IAAA,EAAM,EAAE,CAAA;AAAA,IACzD,UAAU,CAAC,KAAA,EAAO,OAAO,QAAA,CAAS,MAAA,EAAQ,KAAS,CAAA;AAAA,IACnD,MAAM,CAAC,KAAA,EAAO,OAAO,OAAA,CAAQ,MAAA,EAAQ,OAAO,EAAE,CAAA;AAAA,IAC9C,QAAA,EAAU,OAAO,EAAE,QAAA,EAAU,oBAAA,EAAqB;AAAA,GACpD;AACF","file":"chunk-QPEVCJ5Q.js","sourcesContent":["/**\n * internal/flow/batch-window — coalesce a stream of items into size- or\n * time-bounded BATCHES, then flush each batch as a single Cmd.\n *\n * What this module actually DELEGATES to, vs. what it merely RE-EXPORTS:\n *\n * - `../deadline` — REAL delegation (the only one). The window timer IS a\n * `deadline` Sub: `subsFor` returns `deadlineSub` keyed on the window's\n * identity, the subscribe cell is `subscribeDeadline` verbatim, and the\n * expiry Msg is `deadlineExceeded`. Nothing about the timer is redrawn here.\n * \"Fire at an absolute instant, even across a rehydrate\" is exactly\n * `deadline`'s absolute-target lifecycle: a late subscribe (post-`Store`\n * rehydrate) recomputes the remaining delay from the current clock and still\n * fires at the correct moment.\n * - `../work-queue` — TYPE re-export only, NOT a sink this module calls. A\n * flush emits a Cmd; the CONSUMER's interpret handler is what enqueues onto a\n * `createQueue(store)`. This module never touches queue status — it has no\n * queue state and no status flip to delegate. It re-exports `EnqueueInput<I>`\n * purely so the consumer's flush Cmd and queue agree on the item shape; any\n * actual status flip belongs in the consumer and is delegated to\n * `../work-queue/ops` there, not here.\n * - `../debounce` — VALUE/TYPE re-export only, NOT wired into `update`. A\n * bursty source (keystrokes, scroll, websocket frames) can be `debounce`d at\n * the host BEFORE it ever becomes an `add` Msg, so the batch window only sees\n * settled items. `debounce` is a sink transformer OUTSIDE `update`;\n * batch-window is the durable, replayable half inside it. Re-exported for\n * one-import ergonomics — this module does not call it.\n *\n * Why this is a knob and not hand-wired logic: the managed state (the buffer +\n * the window-open instant) lives in the Model, so it survives DO eviction /\n * reload (durable) and every transition is still a Msg through `update`\n * (replayable). A closure holding a `setTimeout` and a mutable array would break\n * both — banned. `add` / `onWindow` are PURE: time arrives as an `at`\n * parameter, never `Date.now()`; they return NEW slice values, never mutate.\n *\n * Typical wiring (combinator form):\n *\n * const bw = createBatchWindow<LogLine, Msg>({\n * maxItems: 100,\n * maxMs: 5_000,\n * flush: (items) => ({ type: \"FlushBatch\", items }),\n * });\n *\n * // Model: { batch: BatchWindow<LogLine>, ... }\n * // init: (loaded) => [loaded ?? { batch: bw.init(), ... }, []]\n * // update cells:\n * // LogArrived: (s, m) => mapBatch(s, bw.add(s.batch, m.line, m.at))\n * // WindowFired: (s, m) => mapBatch(s, bw.onWindow(s.batch, m.atMs))\n * // subscriptions: (s) => bw.subs(s.batch)\n * // subscribe: { ...bw.handlers() }\n *\n * where `mapBatch(s, [batch, cmds]) => [{ ...s, batch }, cmds]` lifts the slice\n * transition into the consumer's Model. The `FlushBatch` Cmd's interpret handler\n * is the consumer's — it is where the batch meets I/O (an `enqueue`, a network\n * POST, an R2 write).\n *\n * Internal since #48 — not published on any subpath; reached from inside the\n * package as `internal/flow/batch-window`. Same one-shape-per-package rule as `deadline`, `cache`, and\n * `work-queue`.\n */\n\nimport type { Cmd } from \"../../../index\";\n\n// `../debounce` re-export (value + type) for one-import ergonomics — see file\n// header. This module does NOT call debounce; a consumer that wants to debounce\n// a bursty source BEFORE it becomes an `add` Msg reaches it from the same import\n// as the batch window.\nexport { type Debounced, debounce } from \"../../timing/debounce\";\n\nimport type { SubscribeHandler } from \"../../../subs/types\";\nimport {\n type DeadlineExceeded,\n type DeadlineSub,\n deadlineExceeded,\n deadlineSub,\n subscribeDeadline,\n} from \"../../resilience/deadline\";\n\n// `../work-queue` TYPE re-export only (see file header). This module emits a\n// flush Cmd; the CONSUMER's interpret handler enqueues onto a\n// `createQueue(store)`. Re-export the caller-facing enqueue payload type so the\n// consumer's flush Cmd and queue agree on the item shape.\nexport type { EnqueueInput } from \"../../work-queue\";\n\n/**\n * Configuration for a batch window — the knob. Pure config, no mutable state\n * (the `BatchWindow<I>` slice carries that). One config object is shared across\n * every window of one logical stream (often a module-scope const).\n *\n * `flush` is a pure Cmd CONSTRUCTOR, not a closure-with-state: it maps the\n * buffered items to the single Cmd the runtime performs when a window closes.\n * Keeping it a `(items) => Cmd` (not a `(items) => void`) is what keeps the\n * trigger replayable — the flush is a Cmd in the transition's output, never a\n * side effect inside the verb.\n */\nexport interface BatchWindowConfig<I, C extends Cmd> {\n /**\n * Size trigger. A batch flushes the instant its buffer reaches `maxItems`,\n * WITHOUT waiting for the time window — the classic \"send as soon as the\n * page is full\" cutoff. Must be `>= 1`; a config of `maxItems: 0` would flush\n * an empty batch on every `add`, which is never useful, so callers treat `1`\n * as the floor (the verb still behaves: with `maxItems <= 1` every item\n * flushes on arrival, a degenerate but legal \"no batching\" window).\n */\n readonly maxItems: number;\n /**\n * Time trigger, in milliseconds. A batch flushes no later than `openedAt +\n * maxMs` — `openedAt` being the `at` of the item that opened the window. This\n * is the latency ceiling: an item never waits longer than `maxMs` to ship,\n * even if the buffer never reaches `maxItems`.\n */\n readonly maxMs: number;\n /**\n * Map a flushed batch to the single Cmd the runtime performs. Called with the\n * buffered items in arrival order; the returned Cmd is what `add` / `onWindow`\n * emit when a window closes. PURE constructor — no I/O, no clock; the I/O\n * happens later in the consumer's interpret handler for the returned Cmd.\n */\n readonly flush: (items: readonly I[]) => C;\n}\n\n/**\n * The Model slice a batch window owns. `buffer` is the items accumulated for the\n * current open window; `openedAt` is the `at` of the item that OPENED it (the\n * anchor the time trigger measures against), or `null` when the window is\n * closed (buffer empty). The pair is the single source of truth for \"is a\n * window open, and when must it flush by?\" — both plain data so the whole slice\n * is trivially JSON-serializable into a consumer's `Store<S>`.\n *\n * Invariant the verbs preserve: `buffer.length === 0` IFF `openedAt === null`.\n * A non-empty buffer always has an `openedAt`; an empty buffer never does. The\n * window timer Sub keys its arm/disarm off exactly this invariant.\n */\nexport interface BatchWindow<I> {\n readonly buffer: readonly I[];\n readonly openedAt: number | null;\n}\n\n/** The starting slice: closed window, empty buffer. */\nexport function initBatchWindow<I>(): BatchWindow<I> {\n return { buffer: [], openedAt: null };\n}\n\n/**\n * The Sub a batch window's open timer produces: it IS `../deadline`'s\n * `DeadlineSub`, not a re-tagged copy. `atMs` is the absolute flush-by instant\n * (`openedAt + maxMs`). Aliasing the deadline type (rather than redeclaring an\n * identical shape) is what lets `subsFor` return `deadlineSub(...)` with no\n * cast — the value's static type already satisfies this name. Consumers running\n * several windows route each one by the Sub's `id`, not by a distinct type.\n */\nexport type BatchWindowSub = DeadlineSub;\n\n/** The Msg a closed-by-time window dispatches. Alias of `deadline`'s Msg. */\nexport type BatchWindowExpired = DeadlineExceeded;\n\n/**\n * Construct the window-expired Msg for the window identified by `id`, flushing\n * by `atMs`. Re-exported alias of `deadlineExceeded` so the subscribe cell and\n * the consumer's reducer share one constructor rather than two literals that\n * can drift. The consumer handles it in a reducer cell by calling `onWindow`.\n */\nexport function batchWindowExpired(\n id: string,\n atMs: number,\n): BatchWindowExpired {\n return deadlineExceeded(id, atMs);\n}\n\n/**\n * The bound knob returned by `createBatchWindow`. `init` + the two pure verbs +\n * `subs` + `handlers`, all closing over one `BatchWindowConfig`. The verbs\n * return `readonly [BatchWindow<I>, readonly C[]]`: the next slice and the Cmds\n * to emit (one `flush` Cmd when a window closes, none otherwise).\n */\nexport interface BatchWindowKnob<I, C extends Cmd> {\n /** The starting slice — `initBatchWindow()`. */\n init(): BatchWindow<I>;\n /**\n * Buffer `item` (arrival-stamped `at`), flushing immediately if that brings\n * the buffer to `maxItems`. See `addItem`.\n */\n add(\n state: BatchWindow<I>,\n item: I,\n at: number,\n ): readonly [BatchWindow<I>, readonly C[]];\n /**\n * Flush whatever is buffered because the time window closed. See `onWindow`.\n */\n onWindow(\n state: BatchWindow<I>,\n at: number,\n ): readonly [BatchWindow<I>, readonly C[]];\n /**\n * The window timer Sub, armed only while a window is open. See `subsFor`.\n * `id` keys the deadline so several windows on one machine route distinctly.\n */\n subs(state: BatchWindow<I>, id?: string): readonly BatchWindowSub[];\n /**\n * The `subscribe` cells this knob splices into `machine.subscribe`. One cell,\n * keyed `\"deadline\"`, delegating to `../deadline`'s subscribe handler. Spread\n * into the consumer's subscribe map: `subscribe: { ...bw.handlers() }`.\n */\n handlers(): {\n deadline: SubscribeHandler<BatchWindowSub, BatchWindowExpired, unknown>;\n };\n}\n\n/**\n * Buffer `item` and decide whether the size trigger fires. PURE.\n *\n * - Empty (closed) window → `item` OPENS it: `openedAt` becomes `at`, buffer\n * becomes `[item]`. This is the anchor the time window measures against.\n * - Open window → `item` appends; `openedAt` is left untouched (the window's\n * latency clock keeps running from the FIRST item, not this one).\n *\n * After buffering, if the buffer has reached `maxItems` the window flushes\n * immediately: emit `config.flush(buffer)` and RESET to the closed slice. The\n * size trigger pre-empts the time trigger — a full page ships now, the window\n * Sub for the (now closed) window disarms on the next reconcile because `subs`\n * returns `[]` for an empty buffer.\n *\n * `maxItems <= 1` is the degenerate \"no batching\" case: every item flushes on\n * arrival (buffer reaches 1 immediately). Legal and useful as a feature flag to\n * disable batching without restructuring the wiring.\n *\n * @param config the knob (maxItems / flush constructor)\n * @param state the current slice\n * @param item the item to buffer\n * @param at the arrival instant (epoch ms) — the caller stamps it; the verb\n * never reads the clock\n */\nexport function addItem<I, C extends Cmd>(\n config: BatchWindowConfig<I, C>,\n state: BatchWindow<I>,\n item: I,\n at: number,\n): readonly [BatchWindow<I>, readonly C[]] {\n const buffer = [...state.buffer, item];\n // A closed window opens at THIS item's `at`; an already-open window keeps its\n // original `openedAt` (the latency clock runs from the first item).\n const openedAt = state.openedAt ?? at;\n\n // Size trigger: a full page ships now, regardless of the time window. The\n // floor of 1 means `maxItems <= 1` flushes every item on arrival.\n if (buffer.length >= Math.max(1, config.maxItems)) {\n return [initBatchWindow<I>(), [config.flush(buffer)]];\n }\n\n return [{ buffer, openedAt }, []];\n}\n\n/**\n * Flush the open window because its time bound was reached. PURE.\n *\n * - Open window (buffer non-empty) → emit `config.flush(buffer)` and RESET to\n * the closed slice.\n * - Closed window (buffer empty) → no-op: return the same slice and no Cmds.\n * This guards the race where the size trigger already flushed and reset the\n * window before the time Sub's Msg lands — a stale `onWindow` after a\n * size-flush must NOT emit an empty batch.\n *\n * `at` is accepted for symmetry with `add` and so the consumer's reducer cell\n * has a uniform `(state, at)` shape; the verb doesn't need it for the flush\n * decision (the buffer's emptiness is the whole signal), but taking it keeps a\n * future \"flush only if the window is genuinely past `openedAt + maxMs`\"\n * tightening a non-breaking change.\n *\n * @param config the knob (flush constructor)\n * @param state the current slice\n * @param at the firing instant (epoch ms) — the caller stamps it; the verb\n * never reads the clock\n */\nexport function onWindow<I, C extends Cmd>(\n config: BatchWindowConfig<I, C>,\n state: BatchWindow<I>,\n _at: number,\n): readonly [BatchWindow<I>, readonly C[]] {\n // Empty buffer = the size trigger already flushed (or nothing arrived). A\n // stale timer Msg must not ship an empty batch — return the slice untouched.\n if (state.buffer.length === 0) return [state, []];\n return [initBatchWindow<I>(), [config.flush(state.buffer)]];\n}\n\n/**\n * The window timer Sub, derived from the slice. PURE.\n *\n * Returns the single `deadline` Sub targeting `openedAt + maxMs` while a window\n * is open (buffer non-empty), or `[]` while it is closed. The substrate's\n * reconcile pass arms the timer when the array gains the Sub and disarms it\n * (clearing the pending `setTimeout`) when the array loses it — so a size-flush\n * that empties the buffer auto-cancels the now-irrelevant time trigger on the\n * next transition.\n *\n * `id` defaults to `\"batch-window\"`; pass a distinct id per window when a\n * machine runs several so the dispatched `BatchWindowExpired` Msg (and its\n * reconcile identity) routes to the right one.\n *\n * @param config the knob (maxMs)\n * @param state the current slice\n * @param id stable identity for this window's timer (default\n * `\"batch-window\"`)\n */\nexport function subsFor<I, C extends Cmd>(\n config: BatchWindowConfig<I, C>,\n state: BatchWindow<I>,\n id = \"batch-window\",\n): readonly BatchWindowSub[] {\n // Closed window → no timer. The invariant (empty buffer IFF openedAt null)\n // means either check suffices; we read `openedAt` so the `atMs` arithmetic\n // below is non-null without a `!`. `deadlineSub` already returns a\n // `DeadlineSub`, which IS `BatchWindowSub` — no cast.\n if (state.openedAt === null) return [];\n return [deadlineSub(id, state.openedAt + config.maxMs)];\n}\n\n/**\n * The `subscribe[\"deadline\"]` cell for a batch window's timer — the exact\n * `../deadline` handler. Arms a one-shot timer for `max(0, atMs - now)` and\n * dispatches `batchWindowExpired(...)`; returns a cleanup that clears it. The\n * consumer assigns it via the knob's `handlers()`.\n */\nexport const subscribeBatchWindow: SubscribeHandler<\n BatchWindowSub,\n BatchWindowExpired,\n unknown\n> = subscribeDeadline;\n\n/**\n * Build a batch window knob from a config. Combinator form (the default L2\n * ergonomic): hand it the config, splice the ~4 returned hooks into your\n * machine. The pure verbs (`add` / `onWindow`) close over the config so the\n * consumer's reducer cells stay a uniform `(state, ...args)` shape; `subs` /\n * `handlers` pre-wire the window timer.\n *\n * @typeParam I the item type buffered into each batch\n * @typeParam C the Cmd type `config.flush` returns\n */\nexport function createBatchWindow<I, C extends Cmd>(\n config: BatchWindowConfig<I, C>,\n): BatchWindowKnob<I, C> {\n return {\n init: () => initBatchWindow<I>(),\n add: (state, item, at) => addItem(config, state, item, at),\n onWindow: (state, at) => onWindow(config, state, at),\n subs: (state, id) => subsFor(config, state, id),\n handlers: () => ({ deadline: subscribeBatchWindow }),\n };\n}\n"]}