@demlik/tea 0.13.0 → 0.14.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.
- package/README.md +11 -9
- package/dist/agent/index.d.ts +637 -45
- package/dist/agent/index.js +9 -19
- package/dist/{chunk-25LBDCBC.js → chunk-6IQPODUF.js} +2 -2
- package/dist/{chunk-25LBDCBC.js.map → chunk-6IQPODUF.js.map} +1 -1
- package/dist/chunk-AQ6ZIMEA.js +421 -0
- package/dist/chunk-AQ6ZIMEA.js.map +1 -0
- package/dist/{chunk-MUUROGIH.js → chunk-BLJ2JQSH.js} +62 -12
- package/dist/chunk-BLJ2JQSH.js.map +1 -0
- package/dist/{chunk-RH5EFRVJ.js → chunk-F2US5BW6.js} +25 -5
- package/dist/chunk-F2US5BW6.js.map +1 -0
- package/dist/{batch-window/index.js → chunk-F7FMAWLA.js} +4 -8
- package/dist/chunk-F7FMAWLA.js.map +1 -0
- package/dist/{chunk-O4MVQTEJ.js → chunk-HUKZQX6U.js} +20 -16
- package/dist/chunk-HUKZQX6U.js.map +1 -0
- package/dist/{chunk-EWJP3YYP.js → chunk-HWVW4AXC.js} +3 -3
- package/dist/chunk-HWVW4AXC.js.map +1 -0
- package/dist/{chunk-DMYGQB4O.js → chunk-HYDWG35Q.js} +23 -6
- package/dist/chunk-HYDWG35Q.js.map +1 -0
- package/dist/{chunk-YUXP55T2.js → chunk-I5MQMJDW.js} +57 -7
- package/dist/chunk-I5MQMJDW.js.map +1 -0
- package/dist/{chunk-J6527VRQ.js → chunk-LFZUK6J2.js} +3 -3
- package/dist/chunk-LFZUK6J2.js.map +1 -0
- package/dist/{chunk-ZVJZ72HY.js → chunk-LYLTKJWM.js} +25 -11
- package/dist/chunk-LYLTKJWM.js.map +1 -0
- package/dist/{chunk-D736QUGF.js → chunk-MYXFNWAJ.js} +4 -5
- package/dist/chunk-MYXFNWAJ.js.map +1 -0
- package/dist/{chunk-K575PT4S.js → chunk-PR6TB3BL.js} +15 -4
- package/dist/chunk-PR6TB3BL.js.map +1 -0
- package/dist/chunk-RQULHGYH.js +1804 -0
- package/dist/chunk-RQULHGYH.js.map +1 -0
- package/dist/{chunk-FV3Z5MBE.js → chunk-TXN2KFHF.js} +14 -4
- package/dist/chunk-TXN2KFHF.js.map +1 -0
- package/dist/{chunk-Q2UM7BXU.js → chunk-VBDU4JL6.js} +2 -2
- package/dist/chunk-VBDU4JL6.js.map +1 -0
- package/dist/chunk-Z2L3LSNV.js +1470 -0
- package/dist/chunk-Z2L3LSNV.js.map +1 -0
- package/dist/{core-Co0LWkIA.d.ts → core-CTzMJ82M.d.ts} +72 -23
- package/dist/devtools/index.d.ts +2 -2
- package/dist/devtools/index.js +207 -2
- package/dist/devtools/index.js.map +1 -1
- package/dist/do/index.d.ts +42 -29
- package/dist/do/index.js +36 -25
- package/dist/do/index.js.map +1 -1
- package/dist/{durable-effects-C6uwzjb5.d.ts → durable-effects-B0-qGQDy.d.ts} +2 -2
- package/dist/extension/index.d.ts +300 -3
- package/dist/extension/index.js +596 -5
- package/dist/extension/index.js.map +1 -1
- package/dist/flow/index.d.ts +1295 -0
- package/dist/flow/index.js +732 -0
- package/dist/flow/index.js.map +1 -0
- package/dist/idempotency/index.d.ts +90 -116
- package/dist/idempotency/index.js +4 -1
- package/dist/{batch-window/index.d.ts → index-4vZzljCD.d.ts} +7 -12
- package/dist/{fan-out/index.d.ts → index-6A7dB8f6.d.ts} +7 -9
- package/dist/index-B5BZ-_cT.d.ts +616 -0
- package/dist/index-Bc40JWVl.d.ts +131 -0
- package/dist/{debounce/index.d.ts → index-DHSUmQTR.d.ts} +7 -7
- package/dist/index-DJWIER_3.d.ts +69 -0
- package/dist/{snapshot/index.d.ts → index-DMCeMPMJ.d.ts} +15 -78
- package/dist/{monitored-run/index.d.ts → index-NjrT3fL2.d.ts} +37 -13
- package/dist/{recorder/index.d.ts → index-TvRb1KVf.d.ts} +4 -7
- package/dist/{deadline/index.d.ts → index-q0hXFeZ0.d.ts} +155 -9
- package/dist/index.d.ts +711 -6
- package/dist/index.js +1 -2
- package/dist/jev/index.d.ts +634 -0
- package/dist/jev/index.js +433 -0
- package/dist/jev/index.js.map +1 -0
- package/dist/machine-CdIcN2Xm.d.ts +1773 -0
- package/dist/machine-viz/index.d.ts +1 -1
- package/dist/machine-viz/index.js +12 -5
- package/dist/machine-viz/index.js.map +1 -1
- package/dist/mem/index.d.ts +18 -3
- package/dist/mem/index.js +22 -2
- package/dist/mem/index.js.map +1 -1
- package/dist/node/index.d.ts +104 -6
- package/dist/node/index.js +132 -9
- package/dist/node/index.js.map +1 -1
- package/dist/{paginator → paginate}/index.d.ts +151 -6
- package/dist/paginate/index.js +7 -0
- package/dist/parity/index.d.ts +4 -10
- package/dist/parity/index.js +4 -7
- package/dist/parity/index.js.map +1 -1
- package/dist/pbt/index.d.ts +247 -5
- package/dist/pbt/index.js +124 -5
- package/dist/pbt/index.js.map +1 -1
- package/dist/{trace-replay → persistence}/index.d.ts +7 -5
- package/dist/persistence/index.js +6 -0
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +1 -2
- package/dist/react/index.js.map +1 -1
- package/dist/resilience/index.d.ts +743 -0
- package/dist/resilience/index.js +657 -0
- package/dist/resilience/index.js.map +1 -0
- package/dist/retry-backoff/index.d.ts +3 -2
- package/dist/retry-backoff/index.js +1 -1
- package/dist/runtime-types-LWDoi943.d.ts +998 -0
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +1 -2
- package/dist/testing/index.js.map +1 -1
- package/dist/{throttled-input → timing}/index.d.ts +143 -12
- package/dist/{throttled-input → timing}/index.js +57 -12
- package/dist/timing/index.js.map +1 -0
- package/dist/{types-XS_miRnB.d.ts → types-DP3f5NIl.d.ts} +1 -1
- package/dist/work-queue/index.d.ts +130 -54
- package/dist/work-queue/index.js +3 -2
- package/dist/work-queue/index.js.map +1 -1
- package/package.json +45 -218
- package/dist/authed-call/index.d.ts +0 -120
- package/dist/authed-call/index.js +0 -134
- package/dist/authed-call/index.js.map +0 -1
- package/dist/await-terminal/index.d.ts +0 -101
- package/dist/await-terminal/index.js +0 -76
- package/dist/await-terminal/index.js.map +0 -1
- package/dist/batch-window/index.js.map +0 -1
- package/dist/cache/index.d.ts +0 -152
- package/dist/cache/index.js +0 -8
- package/dist/chart/index.d.ts +0 -218
- package/dist/chart/index.js +0 -16
- package/dist/chart/index.js.map +0 -1
- package/dist/chart/inspect/index.d.ts +0 -86
- package/dist/chart/inspect/index.js +0 -5
- package/dist/chart/inspect/react.d.ts +0 -113
- package/dist/chart/inspect/react.js +0 -11
- package/dist/chart/inspect/react.js.map +0 -1
- package/dist/chart/inspect/styles.css +0 -286
- package/dist/chart/lane/index.d.ts +0 -362
- package/dist/chart/lane/index.js +0 -9
- package/dist/chart/lane/index.js.map +0 -1
- package/dist/chart/lane/react.d.ts +0 -84
- package/dist/chart/lane/react.js +0 -559
- package/dist/chart/lane/react.js.map +0 -1
- package/dist/chart/lane/server.d.ts +0 -148
- package/dist/chart/lane/server.js +0 -209
- package/dist/chart/lane/server.js.map +0 -1
- package/dist/chart/lane/styles.css +0 -498
- package/dist/chart/lane/viewer/assets/abnfDiagram-N423BO3Z-C729LI31.js +0 -1
- package/dist/chart/lane/viewer/assets/architectureDiagram-T3A2C74G-C729LI31.js +0 -1
- package/dist/chart/lane/viewer/assets/blockDiagram-VBNYF7ZC-C729LI31.js +0 -1
- package/dist/chart/lane/viewer/assets/c4Diagram-5PPSVZJV-C729LI31.js +0 -1
- package/dist/chart/lane/viewer/assets/chunk-2GRJ4B5K-HdDf8SI3.js +0 -1
- package/dist/chart/lane/viewer/assets/chunk-5RXB4S5H-CcNtpUd7.js +0 -231
- package/dist/chart/lane/viewer/assets/chunk-5VM5RSS4-BP4fRS_5.js +0 -15
- package/dist/chart/lane/viewer/assets/chunk-KBJHAD2P-DZOw7xU6.js +0 -1
- package/dist/chart/lane/viewer/assets/chunk-NSK5VX7P-CBCyTZMs.js +0 -128
- package/dist/chart/lane/viewer/assets/classDiagram-JCYQIIEL-C729LI31.js +0 -1
- package/dist/chart/lane/viewer/assets/classDiagram-v2-OCEON4UE-D4uAiFr8.js +0 -206
- package/dist/chart/lane/viewer/assets/cose-bilkent-JH36ORCC-DOwCrGXH.js +0 -1
- package/dist/chart/lane/viewer/assets/cynefinDiagram-MW4NZA55-C729LI31.js +0 -1
- package/dist/chart/lane/viewer/assets/dagre-K_PewWfS.js +0 -1
- package/dist/chart/lane/viewer/assets/dagre-VZM6K2ZE-831qZcA8.js +0 -4
- package/dist/chart/lane/viewer/assets/diagram-7IWD3JNH-C729LI31.js +0 -1
- package/dist/chart/lane/viewer/assets/diagram-B4RE2ZJO-C729LI31.js +0 -1
- package/dist/chart/lane/viewer/assets/diagram-LBJQPF4R-C729LI31.js +0 -1
- package/dist/chart/lane/viewer/assets/diagram-Q27KOJAE-C729LI31.js +0 -1
- package/dist/chart/lane/viewer/assets/diagram-UB23O5K3-C729LI31.js +0 -1
- package/dist/chart/lane/viewer/assets/ebnfDiagram-BXEA7PRR-C729LI31.js +0 -1
- package/dist/chart/lane/viewer/assets/erDiagram-JOGREHBK-C729LI31.js +0 -1
- package/dist/chart/lane/viewer/assets/flowDiagram-UKHOOZJN-C729LI31.js +0 -1
- package/dist/chart/lane/viewer/assets/ganttDiagram-PKOTCBZU-C729LI31.js +0 -1
- package/dist/chart/lane/viewer/assets/gitGraphDiagram-DS77QQ5N-C729LI31.js +0 -1
- package/dist/chart/lane/viewer/assets/index-9SkH5NA3.css +0 -1
- package/dist/chart/lane/viewer/assets/index-Bgt4sXpW.js +0 -195
- package/dist/chart/lane/viewer/assets/infoDiagram-6WML65LV-C729LI31.js +0 -1
- package/dist/chart/lane/viewer/assets/ishikawaDiagram-WSZJBQD7-C729LI31.js +0 -1
- package/dist/chart/lane/viewer/assets/journeyDiagram-NVQOT4AX-C729LI31.js +0 -1
- package/dist/chart/lane/viewer/assets/kanban-definition-27J2QSJJ-G7PaPzy-.js +0 -89
- package/dist/chart/lane/viewer/assets/katex-DOwCrGXH.js +0 -1
- package/dist/chart/lane/viewer/assets/mindmap-definition-FAOFIHXS-CGj9nrVk.js +0 -96
- package/dist/chart/lane/viewer/assets/path-fybaL0A-.js +0 -1
- package/dist/chart/lane/viewer/assets/pegDiagram-VL7TDLO6-C729LI31.js +0 -1
- package/dist/chart/lane/viewer/assets/pieDiagram-7S7Q4E2Y-C729LI31.js +0 -1
- package/dist/chart/lane/viewer/assets/quadrantDiagram-CIZ2JOQS-C729LI31.js +0 -1
- package/dist/chart/lane/viewer/assets/railroadDiagram-AXF67PYL-C729LI31.js +0 -1
- package/dist/chart/lane/viewer/assets/requirementDiagram-LRYGKXZP-C729LI31.js +0 -1
- package/dist/chart/lane/viewer/assets/sankeyDiagram-W5VNT64P-C729LI31.js +0 -1
- package/dist/chart/lane/viewer/assets/sequenceDiagram-SI44F4Z6-C729LI31.js +0 -1
- package/dist/chart/lane/viewer/assets/stateDiagram-OKZ733FA-B_88AzBS.js +0 -1
- package/dist/chart/lane/viewer/assets/stateDiagram-v2-UEYNNEHI-CR5gcgq6.js +0 -1
- package/dist/chart/lane/viewer/assets/swimlanes-SLNWSIFB-B4xK-rwg.js +0 -1
- package/dist/chart/lane/viewer/assets/swimlanesDiagram-ULZ7WXOC-C729LI31.js +0 -1
- package/dist/chart/lane/viewer/assets/timeline-definition-Z64GVDOM-CCv52MfY.js +0 -120
- package/dist/chart/lane/viewer/assets/vennDiagram-T6HMQDX7-C729LI31.js +0 -1
- package/dist/chart/lane/viewer/assets/wardleyDiagram-T6FBY63Y-C729LI31.js +0 -1
- package/dist/chart/lane/viewer/assets/xychartDiagram-ELKLHX3M-C729LI31.js +0 -1
- package/dist/chart/lane/viewer/index.html +0 -15
- package/dist/chart/report/index.d.ts +0 -117
- package/dist/chart/report/index.js +0 -32
- package/dist/chart/report/index.js.map +0 -1
- package/dist/chunk-2YFLOCWX.js +0 -15
- package/dist/chunk-2YFLOCWX.js.map +0 -1
- package/dist/chunk-3I52QZZ5.js +0 -519
- package/dist/chunk-3I52QZZ5.js.map +0 -1
- package/dist/chunk-3ZAFFRRB.js +0 -457
- package/dist/chunk-3ZAFFRRB.js.map +0 -1
- package/dist/chunk-4EQZ76JC.js +0 -33
- package/dist/chunk-4EQZ76JC.js.map +0 -1
- package/dist/chunk-4KLG43GY.js +0 -223
- package/dist/chunk-4KLG43GY.js.map +0 -1
- package/dist/chunk-7FRZ2CT2.js +0 -424
- package/dist/chunk-7FRZ2CT2.js.map +0 -1
- package/dist/chunk-AZZLE7XR.js +0 -62
- package/dist/chunk-AZZLE7XR.js.map +0 -1
- package/dist/chunk-BYQUGDD3.js +0 -16
- package/dist/chunk-BYQUGDD3.js.map +0 -1
- package/dist/chunk-CIQG5L42.js +0 -685
- package/dist/chunk-CIQG5L42.js.map +0 -1
- package/dist/chunk-CLM6ERNO.js +0 -61
- package/dist/chunk-CLM6ERNO.js.map +0 -1
- package/dist/chunk-COS5MSTO.js +0 -88
- package/dist/chunk-COS5MSTO.js.map +0 -1
- package/dist/chunk-CRPZJVD2.js +0 -71
- package/dist/chunk-CRPZJVD2.js.map +0 -1
- package/dist/chunk-D62FDQKE.js +0 -53
- package/dist/chunk-D62FDQKE.js.map +0 -1
- package/dist/chunk-D736QUGF.js.map +0 -1
- package/dist/chunk-DGE4PV4A.js +0 -26
- package/dist/chunk-DGE4PV4A.js.map +0 -1
- package/dist/chunk-DMYGQB4O.js.map +0 -1
- package/dist/chunk-EWJP3YYP.js.map +0 -1
- package/dist/chunk-FV3Z5MBE.js.map +0 -1
- package/dist/chunk-HCEVHFJ7.js +0 -54
- package/dist/chunk-HCEVHFJ7.js.map +0 -1
- package/dist/chunk-J6527VRQ.js.map +0 -1
- package/dist/chunk-JELSZN25.js +0 -47
- package/dist/chunk-JELSZN25.js.map +0 -1
- package/dist/chunk-K575PT4S.js.map +0 -1
- package/dist/chunk-L57ZOT5K.js +0 -184
- package/dist/chunk-L57ZOT5K.js.map +0 -1
- package/dist/chunk-MU6MCIAC.js +0 -15
- package/dist/chunk-MU6MCIAC.js.map +0 -1
- package/dist/chunk-MUUROGIH.js.map +0 -1
- package/dist/chunk-NR7BU22N.js +0 -758
- package/dist/chunk-NR7BU22N.js.map +0 -1
- package/dist/chunk-O4MVQTEJ.js.map +0 -1
- package/dist/chunk-OOYD44JI.js +0 -317
- package/dist/chunk-OOYD44JI.js.map +0 -1
- package/dist/chunk-Q2UM7BXU.js.map +0 -1
- package/dist/chunk-RH5EFRVJ.js.map +0 -1
- package/dist/chunk-S2EGRPCM.js +0 -47
- package/dist/chunk-S2EGRPCM.js.map +0 -1
- package/dist/chunk-SINXE47Y.js +0 -55
- package/dist/chunk-SINXE47Y.js.map +0 -1
- package/dist/chunk-UF4MZSLT.js +0 -53
- package/dist/chunk-UF4MZSLT.js.map +0 -1
- package/dist/chunk-UOC2PL46.js +0 -13
- package/dist/chunk-UOC2PL46.js.map +0 -1
- package/dist/chunk-VYFAZN3F.js +0 -372
- package/dist/chunk-VYFAZN3F.js.map +0 -1
- package/dist/chunk-WRAA7GVZ.js +0 -209
- package/dist/chunk-WRAA7GVZ.js.map +0 -1
- package/dist/chunk-XGELA764.js +0 -163
- package/dist/chunk-XGELA764.js.map +0 -1
- package/dist/chunk-YUXP55T2.js.map +0 -1
- package/dist/chunk-YWIKDKHY.js +0 -307
- package/dist/chunk-YWIKDKHY.js.map +0 -1
- package/dist/chunk-ZJ5AOYBH.js +0 -8
- package/dist/chunk-ZJ5AOYBH.js.map +0 -1
- package/dist/chunk-ZSUUOEPL.js +0 -731
- package/dist/chunk-ZSUUOEPL.js.map +0 -1
- package/dist/chunk-ZVJZ72HY.js.map +0 -1
- package/dist/circuit-breaker/index.d.ts +0 -197
- package/dist/circuit-breaker/index.js +0 -3
- package/dist/circuit-breaker/index.js.map +0 -1
- package/dist/deadline/index.js +0 -6
- package/dist/deadline/index.js.map +0 -1
- package/dist/debounce/index.js +0 -3
- package/dist/debounce/index.js.map +0 -1
- package/dist/extension/react.d.ts +0 -121
- package/dist/extension/react.js +0 -75
- package/dist/extension/react.js.map +0 -1
- package/dist/extension/subs/index.d.ts +0 -45
- package/dist/extension/subs/index.js +0 -80
- package/dist/extension/subs/index.js.map +0 -1
- package/dist/extension/test-utils.d.ts +0 -128
- package/dist/extension/test-utils.js +0 -277
- package/dist/extension/test-utils.js.map +0 -1
- package/dist/fan-out/index.js +0 -5
- package/dist/fan-out/index.js.map +0 -1
- package/dist/fold-BJPdjc9w.d.ts +0 -435
- package/dist/graph-B3L5e0N1.d.ts +0 -923
- package/dist/idempotency/adapter.d.ts +0 -62
- package/dist/idempotency/adapter.js +0 -4
- package/dist/idempotency/adapter.js.map +0 -1
- package/dist/idempotent-intake/index.d.ts +0 -188
- package/dist/idempotent-intake/index.js +0 -7
- package/dist/idempotent-intake/index.js.map +0 -1
- package/dist/journal/index.d.ts +0 -373
- package/dist/journal/index.js +0 -3
- package/dist/journal/index.js.map +0 -1
- package/dist/llm-call/index.d.ts +0 -311
- package/dist/llm-call/index.js +0 -16
- package/dist/llm-call/index.js.map +0 -1
- package/dist/machine-BtFWQmmq.d.ts +0 -804
- package/dist/monitored-run/index.js +0 -10
- package/dist/monitored-run/index.js.map +0 -1
- package/dist/paginated-walk/index.d.ts +0 -235
- package/dist/paginated-walk/index.js +0 -16
- package/dist/paginated-walk/index.js.map +0 -1
- package/dist/paginator/index.js +0 -3
- package/dist/paginator/index.js.map +0 -1
- package/dist/pbt/arbitraries/index.d.ts +0 -136
- package/dist/pbt/arbitraries/index.js +0 -3
- package/dist/pbt/arbitraries/index.js.map +0 -1
- package/dist/pbt/runners/index.d.ts +0 -118
- package/dist/pbt/runners/index.js +0 -5
- package/dist/pbt/runners/index.js.map +0 -1
- package/dist/poller/index.d.ts +0 -352
- package/dist/poller/index.js +0 -115
- package/dist/poller/index.js.map +0 -1
- package/dist/prediction/index.d.ts +0 -131
- package/dist/prediction/index.js +0 -4
- package/dist/prediction/index.js.map +0 -1
- package/dist/protocol-B5YmJr0d.d.ts +0 -45
- package/dist/pure/index.d.ts +0 -3
- package/dist/pure/index.js +0 -4
- package/dist/pure/index.js.map +0 -1
- package/dist/rate-limit/index.d.ts +0 -115
- package/dist/rate-limit/index.js +0 -3
- package/dist/rate-limit/index.js.map +0 -1
- package/dist/reconciler/index.d.ts +0 -193
- package/dist/reconciler/index.js +0 -164
- package/dist/reconciler/index.js.map +0 -1
- package/dist/recorder/index.js +0 -3
- package/dist/recorder/index.js.map +0 -1
- package/dist/reducer-4GNR6b8B.d.ts +0 -447
- package/dist/resilient-call/index.d.ts +0 -320
- package/dist/resilient-call/index.js +0 -14
- package/dist/resilient-call/index.js.map +0 -1
- package/dist/retry-to-success/index.d.ts +0 -109
- package/dist/retry-to-success/index.js +0 -37
- package/dist/retry-to-success/index.js.map +0 -1
- package/dist/run-pAcmBpah.d.ts +0 -713
- package/dist/runtime-types-CNLmhWif.d.ts +0 -517
- package/dist/saga/index.d.ts +0 -241
- package/dist/saga/index.js +0 -150
- package/dist/saga/index.js.map +0 -1
- package/dist/samples-B3My5oh2.d.ts +0 -59
- package/dist/snapshot/index.js +0 -5
- package/dist/snapshot/index.js.map +0 -1
- package/dist/subs/index.d.ts +0 -472
- package/dist/subs/index.js +0 -7
- package/dist/subs/index.js.map +0 -1
- package/dist/throttle/index.d.ts +0 -134
- package/dist/throttle/index.js +0 -3
- package/dist/throttle/index.js.map +0 -1
- package/dist/throttled-input/index.js.map +0 -1
- package/dist/token-refresh/index.d.ts +0 -216
- package/dist/token-refresh/index.js +0 -6
- package/dist/token-refresh/index.js.map +0 -1
- package/dist/trace-replay/index.js +0 -6
- package/dist/trace-replay/index.js.map +0 -1
- package/dist/with-deadline/index.d.ts +0 -197
- package/dist/with-deadline/index.js +0 -111
- package/dist/with-deadline/index.js.map +0 -1
- package/dist/with-resilience/index.d.ts +0 -225
- package/dist/with-resilience/index.js +0 -240
- package/dist/with-resilience/index.js.map +0 -1
- package/dist/with-telemetry/index.d.ts +0 -150
- package/dist/with-telemetry/index.js +0 -70
- package/dist/with-telemetry/index.js.map +0 -1
- package/dist/work-queue/adapter.d.ts +0 -84
- package/dist/work-queue/adapter.js +0 -4
- package/dist/work-queue/adapter.js.map +0 -1
- package/dist/work-queue/ops.d.ts +0 -71
- package/dist/work-queue/ops.js +0 -3
- package/dist/work-queue/ops.js.map +0 -1
- package/dist/workflow/index.d.ts +0 -485
- package/dist/workflow/index.js +0 -244
- package/dist/workflow/index.js.map +0 -1
- /package/dist/{cache → paginate}/index.js.map +0 -0
- /package/dist/{chart/inspect → persistence}/index.js.map +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/pure/core.ts"],"names":["cell"],"mappings":"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","file":"chunk-OOYD44JI.js","sourcesContent":["/**\n * @demlik/tea — pure-core leaf (ADR 0006).\n *\n * This is the runtime-free pure core of the substrate. It holds the\n * client-prediction fold seam (`foldMsgs`), the form reader\n * (`formOf`/`detectUpdateForm`), and the pure type vocabulary (`Machine`,\n * `Reducer`, `Transitions`, `Cmd`, `Sub`, `Port`, …).\n *\n * **Dependency direction (the actual decoupling):** this module imports\n * NOTHING from the runtime — no `better-result`, no `run`/host/`Store`. The\n * runtime (`run`, the host, interpret, `Store`, subscribe — all in\n * `../index.ts`) imports *from* here; never the reverse. The\n * `@demlik/tea/pure` subpath re-exports this surface, and\n * `pure/import-graph.test.ts` is the regression fence asserting the pure\n * entrypoint's import graph never reaches `run`.\n *\n * The one external name it reaches for is zod's TYPE surface (`import type`),\n * erased at compile time — `Cmd.define` accepts zod schemas, and the parse\n * against them happens at the interpret edge in `../run.ts`, never here.\n */\n\nimport type { z } from \"zod\";\n\n// === Dev-mode invariant enforcement ===\n//\n// Three runtime guards that catch TEA invariant violations during development.\n// Dead-code-eliminated in production by any bundler that replaces\n// `import.meta.env.DEV` (Vite) or `process.env.NODE_ENV` (webpack/esbuild).\n//\n// 1. deepFreeze(state) before passing to update — catches mutation\n// 2. Thenable check on return value — catches async update\n// 3. Walk cmd fields for function types — catches closures-as-data\n//\n// These enforce invariants 1 and 2 at runtime where the type system cannot.\nexport const __DEV__: boolean = (() => {\n try {\n const meta = import.meta as unknown as { env?: { DEV?: boolean } };\n if (meta.env?.DEV !== undefined) {\n return meta.env.DEV;\n }\n } catch {\n // import.meta not available (Node CJS, service workers)\n }\n try {\n return (\n typeof process !== \"undefined\" && process.env?.NODE_ENV !== \"production\"\n );\n } catch {\n return false;\n }\n})();\n\nexport function deepFreeze<T>(obj: T, seen?: WeakSet<object>): T {\n if (obj === null || typeof obj !== \"object\") return obj;\n const proto = Object.getPrototypeOf(obj);\n if (proto !== Object.prototype && proto !== Array.prototype && proto !== null)\n return obj;\n const visited = seen ?? new WeakSet();\n if (visited.has(obj as object)) return obj;\n visited.add(obj as object);\n Object.freeze(obj);\n for (const v of Object.values(obj as Record<string, unknown>))\n deepFreeze(v, visited);\n return obj;\n}\n\nexport function hasFunctionValues(obj: unknown): string | null {\n if (obj === null || typeof obj !== \"object\") return null;\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n if (typeof value === \"function\") return key;\n }\n return null;\n}\n\nexport function assertPureResult(result: unknown, msgType: string): void {\n if (result && typeof (result as { then?: unknown }).then === \"function\") {\n throw new Error(\n `@demlik/tea: update cell \"${msgType}\" returned a Promise. ` +\n `update must be synchronous. Move async work to interpret.`,\n );\n }\n if (\n !Array.isArray(result) ||\n result.length < 2 ||\n !Array.isArray(result[1])\n ) {\n throw new Error(\n `@demlik/tea: update cell \"${msgType}\" returned a non-tuple. ` +\n `Expected [State, Cmd[]], got ${typeof result}.`,\n );\n }\n const cmds = result[1] as readonly { type: string }[];\n for (const cmd of cmds) {\n const fnField = hasFunctionValues(cmd);\n if (fnField !== null) {\n throw new Error(\n `@demlik/tea: Cmd \"${cmd.type}\" has function field \"${fnField}\". ` +\n `Cmds must be plain data, not closures.`,\n );\n }\n }\n}\n\n/**\n * Drop one `key` from a `Record` immutably — returns a new record with `key`\n * omitted, leaving the input untouched. The immutable-record-key-drop the L2\n * resilience bricks reach for whenever a settled/restarted call must forget its\n * per-key bookkeeping (a retry counter, a parked input). A plain\n * object-rest spread, lifted here so `resilient-call` and `authed-call` share\n * one definition instead of open-coding `const { [key]: _drop, ...rest } = rec`\n * at each site.\n */\nexport function without<V>(\n rec: Readonly<Record<string, V>>,\n key: string,\n): Readonly<Record<string, V>> {\n const { [key]: _dropped, ...rest } = rec;\n return rest;\n}\n\n// === Cmd: tagged-union, one-shot effect ===\n//\n// `E` and `R` are Effect's error and requirements channels, carried as PHANTOM\n// type parameters (ADR 0014): the runtime value stays `{ readonly type: T }` —\n// JSON-plain, hashable, replayable — and the two channels ride on optional\n// fields that are never assigned, exactly as `Port.__t` carries its `T`.\n//\n// - `E` — the `_tag` union this Cmd can settle with (`{ _tag: \"timeout\" } |\n// …`). A reducer's `_err` cell reads it, so a new failure mode is a compile\n// error at the cell, not a runtime surprise. Defaults to `unknown`, the\n// \"untyped\" reading every hand-written battery Cmd has today.\n// - `R` — the slice of `Ctx` this Cmd's interpret handler needs. `run` demands\n// the intersection of every Cmd's `R` (`RequiredCtx`), so a missing\n// dependency fails at `run`, not at 3 a.m. Defaults to `unknown` — \"needs\n// nothing\", and the identity of `&`, so `Ctx & RequiredCtx<C>` is exactly\n// `Ctx` for a machine with no typed Cmds. (`{}` would read the same in\n// prose but is NOT the identity: `Ctx & {}` refuses an unconstrained\n// `Ctx`, which every host adapter has.)\n//\n// Both channels are named by `Cmd.define`; a Cmd literal never spells them.\n// Every existing `Cmd<A>` and every battery's local Cmd union compiles unchanged\n// — the phantoms are optional, so a `{ type }` literal still satisfies `Cmd`.\nexport type Cmd<T extends string = string, E = unknown, R = unknown> = {\n readonly type: T;\n /** Phantom — the `_tag` union this Cmd can settle with. Never assigned. */\n readonly __e?: E;\n /** Phantom — the `Ctx` slice this Cmd's handler needs. Never assigned. */\n readonly __r?: R;\n};\n\n// === Typed effect channels: reading `E` and `R` back off a Cmd (ADR 0014) ===\n//\n// `NeedsOf<C>` is one Cmd's `R`; `RequiredCtx<C>` is the intersection over a\n// whole Cmd union — what `run` demands of `ctx` beside the machine's own `Ctx`.\n// An untyped Cmd (`{ type }`, `__r` absent) infers `unknown` — \"needs\n// nothing\", the identity of `&` — so a machine of hand-written Cmds demands\n// exactly what it demanded before. `never` (a cmdless machine) intersects to\n// `unknown` too.\nexport type NeedsOf<C> = C extends { readonly __r?: infer R } ? R : unknown;\n\n/** The `E` union one Cmd can settle with; `unknown` for an untyped Cmd. */\nexport type ErrorsOf<C> = C extends { readonly __e?: infer E } ? E : unknown;\n\ntype UnionToIntersection<U> = (\n U extends unknown\n ? (u: U) => void\n : never\n) extends (u: infer I) => void\n ? I\n : never;\n\n/**\n * The `ctx` a machine's whole Cmd union requires: every Cmd's `R`, intersected.\n * `run` types its `ctx` as `Ctx & RequiredCtx<C>`, so handing a machine whose\n * Cmds need `{ http }` to a `run` whose ctx lacks it is a compile error.\n */\nexport type RequiredCtx<C> = UnionToIntersection<NeedsOf<C>>;\n\n// === Cmd.define: the typed Cmd constructor (ADR 0014 §1, 0015 §1) ===\n//\n// \"Types on the constructor, data in the record.\" A Cmd built by hand carries\n// no `E` and no `R`; one built by `Cmd.define` carries both, and the minted\n// settled Msgs (`<name>_ok` / `<name>_err`) are derived from the same\n// declaration — so the reducer, the interpret handler and the runtime edge all\n// read ONE source for what this effect can produce.\n//\n// Everything a constructor names is a TYPE or a SCHEMA; the value it returns is\n// still the dead record `{ type, ...input }`. `input` and `ok` are zod schemas\n// (zod is the one runtime dependency this adds); `err` is the `_tag` list a\n// handler may settle with; `needs` is the `Ctx` slice the handler reads.\n//\n// The failure union always carries one kernel tag beside the declared ones:\n// `MalformedResult`, minted at the interpret edge when a handler's `_ok` value\n// fails the `ok` schema. Invariant 8 (the boundary parses, the core trusts): a\n// corrupt result becomes a typed `_err` the reducer already has a cell for, and\n// never reaches Model.\n\n/** The shape every settled failure has (ADR 0011): a plain `_tag` record. */\nexport type Tagged = { readonly _tag: string };\n\n/**\n * One declared failure per tag. Distributive, so `TaggedError<\"a\" | \"b\">` is\n * the two-arm union a `switch (error._tag)` narrows; the index signature lets a\n * settle site carry detail beside the tag (`{ _tag: \"timeout\", afterMs }`).\n */\nexport type TaggedError<Tag extends string> = Tag extends string\n ? { readonly _tag: Tag; readonly [detail: string]: unknown }\n : never;\n\n/**\n * The kernel-minted failure: a handler returned a `_ok` value the Cmd's `ok`\n * schema rejects. Plain data, so it folds into Model like any settled error;\n * `issues` is zod's issue list flattened to `path` + `message` strings.\n */\nexport type MalformedResult = {\n readonly _tag: \"malformed_result\";\n readonly issues: ReadonlyArray<{\n readonly path: string;\n readonly message: string;\n }>;\n};\n\n/** Render zod's issue list into the JSON-plain `MalformedResult`. */\nexport function malformedResult(\n issues: ReadonlyArray<{\n readonly path: ReadonlyArray<PropertyKey>;\n readonly message: string;\n }>,\n): MalformedResult {\n return {\n _tag: \"malformed_result\",\n issues: issues.map((issue) => ({\n path: issue.path.map(String).join(\".\"),\n message: issue.message,\n })),\n };\n}\n\n/**\n * The payload a constructor accepts: a plain record spread beside `type`. A\n * `type` key is refused at the type level — it would overwrite the discriminant.\n */\nexport type CmdInput = { readonly type?: never } & Record<string, unknown>;\n\n/**\n * Phantom carrier for a Cmd's `R`. `Cmd.needs<{ http: Http }>()` is how a\n * declaration names the `Ctx` slice its handler reads — a type, not a value, so\n * nothing is constructed and nothing is checked at runtime.\n */\nexport type Needs<R> = { readonly __r?: R };\n\n/** The value `Cmd.define(\"fetch\", …)` builds: `{ type: \"fetch\", ...input }`. */\nexport type CmdValue<\n Name extends string,\n Input extends CmdInput,\n E extends Tagged,\n R,\n> = Cmd<Name, E | MalformedResult, R> & Readonly<Input>;\n\nexport type SettledOk<Name extends string, C, Ok> = {\n readonly type: `${Name}_ok`;\n readonly cmd: C;\n readonly value: Ok;\n /** Stamped by the runtime at the interpret edge (`run`'s `clock`). */\n readonly at: number;\n};\n\nexport type SettledErr<Name extends string, C, E extends Tagged> = {\n readonly type: `${Name}_err`;\n readonly cmd: C;\n readonly error: E | MalformedResult;\n /** Stamped by the runtime at the interpret edge (`run`'s `clock`). */\n readonly at: number;\n};\n\n/**\n * What `Cmd.define` returns: the Cmd builder itself (`fetch({ url })`), with\n * the minted Msg builders and the declaration hung on it. `E` here is the\n * DECLARED tag union; the settled `_err` arm widens it by `MalformedResult`.\n *\n * `ok` / `err` take an optional `at` for minting OUTSIDE the runtime (a\n * `replay` log, a unit test); inside `run` the interpret edge stamps it from\n * the clock, so a handler never reads `Date.now()` itself.\n */\nexport interface CmdDef<\n Name extends string,\n Input extends CmdInput,\n Ok,\n E extends Tagged,\n R,\n> {\n (input: Input): CmdValue<Name, Input, E, R>;\n /** The `type` discriminant of every Cmd this builds. */\n readonly cmdType: Name;\n readonly okType: `${Name}_ok`;\n readonly errType: `${Name}_err`;\n readonly ok: (\n cmd: CmdValue<Name, Input, E, R>,\n value: Ok,\n at?: number,\n ) => SettledOk<Name, CmdValue<Name, Input, E, R>, Ok>;\n readonly err: (\n cmd: CmdValue<Name, Input, E, R>,\n error: E,\n at?: number,\n ) => SettledErr<Name, CmdValue<Name, Input, E, R>, E>;\n readonly schema: {\n readonly input: z.ZodType<Input>;\n readonly ok: z.ZodType<Ok>;\n };\n /** The declared `_tag` list, verbatim. */\n readonly errTags: ReadonlyArray<E[\"_tag\"]>;\n}\n\n/**\n * The declaration-erased view the runtime reads: which `type` a def builds,\n * which two Msg types it settles with, and the `ok` schema the edge parses\n * against. Every `CmdDef<…>` is one of these structurally.\n */\nexport type AnyCmdDef = {\n readonly cmdType: string;\n readonly okType: string;\n readonly errType: string;\n readonly schema: { readonly ok: z.ZodType };\n};\n\n/** The Cmd value a def (or a union of defs) builds. */\n// (`infer C extends Cmd` keeps the derived union inside `Machine`'s `C extends\n// Cmd` constraint — without it the inference site is unconstrained.)\nexport type CmdOf<D extends AnyCmdDef> = D extends ((\n input: never,\n) => infer C extends Cmd)\n ? C\n : never;\n\n/**\n * The settled-Msg union a def (or a union of defs) mints: `<name>_ok` carrying\n * `value`, `<name>_err` carrying `error`. `defineMachine({ cmds })` folds this\n * into the machine's `M` so the user never spells the effect half of it.\n */\nexport type Settled<D extends AnyCmdDef> =\n D extends CmdDef<infer Name, infer Input, infer Ok, infer E, infer R>\n ?\n | SettledOk<Name, CmdValue<Name, Input, E, R>, Ok>\n | SettledErr<Name, CmdValue<Name, Input, E, R>, E>\n : never;\n\n// Both read one slot of a `CmdDef`; every OTHER slot is `infer`red too, because\n// a fixed `string` / `CmdInput` there would sit in a contravariant position (the\n// call signature's `input`) and refuse every concrete def.\n\n/** The `Ok` a def's handler must produce. */\nexport type OkOf<D extends AnyCmdDef> =\n D extends CmdDef<infer _Name, infer _Input, infer Ok, infer _E, infer _R>\n ? Ok\n : never;\n\n/** The DECLARED failure union a def's handler may settle with. */\nexport type ErrOf<D extends AnyCmdDef> =\n D extends CmdDef<infer _Name, infer _Input, infer _Ok, infer E, infer _R>\n ? E\n : never;\n\nfunction defineCmd<\n const Name extends string,\n Input extends CmdInput,\n Ok,\n const Tags extends readonly string[],\n R = unknown,\n>(\n name: Name,\n spec: {\n readonly input: z.ZodType<Input>;\n readonly ok: z.ZodType<Ok>;\n readonly err: Tags;\n readonly needs?: Needs<R>;\n },\n): CmdDef<Name, Input, Ok, TaggedError<Tags[number]>, R> {\n type E = TaggedError<Tags[number]>;\n type C = CmdValue<Name, Input, E, R>;\n const okType = `${name}_ok` as const;\n const errType = `${name}_err` as const;\n const build = (input: Input): C => ({ ...input, type: name });\n return Object.assign(build, {\n cmdType: name,\n okType,\n errType,\n ok: (cmd: C, value: Ok, at?: number) =>\n stamp({ type: okType, cmd, value }, at),\n err: (cmd: C, error: E, at?: number) =>\n stamp({ type: errType, cmd, error }, at),\n schema: { input: spec.input, ok: spec.ok },\n errTags: spec.err,\n });\n}\n\n// The builders leave `at` ABSENT unless given one; the runtime fills it at the\n// interpret edge. The return type carries `at: number` because that is what\n// every reducer sees — no Msg reaches `update` unstamped.\nfunction stamp<T extends object>(\n msg: T,\n at: number | undefined,\n): T & { at: number } {\n return (at === undefined ? msg : { ...msg, at }) as T & { at: number };\n}\n\n// === SyncReturn<S, C>: the compile-time reentrancy guard (ADR 0003 #5) ===\n//\n// The canon rule (`.patterns/tea-do/reentrancy.md`,\n// `.patterns/tea-do/reentrancy-deadlock.md`): a reducer must never\n// inline-`await` a re-entrant result. \"Non-reentrant by default\" ≡ \"the reducer\n// is pure AND synchronous\": a reducer that returns a `Promise` (because it is\n// `async`, or inline-`await`s) is the suspended-but-still-blocking turn Orleans\n// serializes against — it occupies the single-writer slot across a suspension\n// point and can be re-entered mid-flight → deadlock.\n//\n// So the guard is purely type-level: every reducer/transition cell returns\n// `SyncReturn<S, C>` — the synchronous result tuple, intersected with `{ then?:\n// never }` to make it *non-thenable*. A `Promise<readonly [S, readonly C[]]>`\n// is an object with a `then` method, so it fails to satisfy `then?: never`; an\n// `async (s, m) => [...]` (or one that inline-`await`s) is therefore\n// unrepresentable at compile time — `tsc` rejects it *at the cell*, not at a\n// runtime hang. A plain synchronous tuple `[next, cmds]` has no `then` property,\n// so the optional-absent `then?: never` is satisfied and existing pure reducers\n// compile unchanged. `SyncReturn<S, C>` is also assignable *to* `readonly [S,\n// readonly C[]]` (the intersection is narrower), so the runtime dispatch in\n// `run`/`replay` consumes a cell result exactly as before.\n//\n// This mirrors `retry-backoff`'s `Rng` brand (#63): the obligation lives in the\n// type at the construction boundary (\"parse, don't validate\"), not in a comment.\nexport type SyncReturn<S, C extends Cmd> = readonly [S, readonly C[]] & {\n // A thenable carries a callable `then`; forbidding it rejects every Promise.\n // Optional + `never` means \"absent on a sync tuple, impossible on a Promise\".\n readonly then?: never;\n};\n\n// === update form: Reducer vs Transitions, tagged once ===\n//\n// The substrate accepts two `update` shapes (see the `Machine.update` union and\n// the `defineMachine` overloads):\n//\n// - \"reducer\" — flat record keyed by Msg.type, every cell a function.\n// - \"transitions\" — 2D table keyed by State.type then Msg.type, every cell a\n// record of functions.\n//\n// Which one a given machine is must be known at runtime by `run`, `replay`, and\n// the `withX` wrappers (their dispatch / key-enumeration / reserved-namespace\n// scans differ per form). Rather than re-derive it structurally at every reader\n// — a `typeof update[firstKey] === \"function\"` heuristic that breaks the day a\n// reducer cell is itself an object-with-a-call — `defineMachine` computes the\n// form ONCE at construction and stamps it on the machine as a non-enumerable\n// `__form` (so it never serializes, never collides with a Msg.type key, never\n// shows up in `Object.keys(machine)`). Every reader calls `formOf(machine)`.\nexport type UpdateForm = \"reducer\" | \"transitions\";\n\n// The single structural heuristic, defined ONCE. Used only by `defineMachine`\n// (and `formOf`'s fallback for machines built without it, e.g. a plain object\n// literal annotated as `Machine`). A Reducer's first own value is a function; a\n// Transitions table's first own value is a record (of functions). An empty\n// record (`M` is `never`) can never dispatch a Msg, so the form is irrelevant —\n// \"reducer\" is returned arbitrarily.\nexport function detectUpdateForm(update: object): UpdateForm {\n const firstKey = Object.keys(update)[0];\n if (firstKey === undefined) return \"reducer\";\n const firstValue = (update as Record<string, unknown>)[firstKey];\n return typeof firstValue === \"function\" ? \"reducer\" : \"transitions\";\n}\n\n// The single reader every form-sensitive site goes through. Prefers the\n// `__form` tag stamped by `defineMachine` (authoritative — computed once at the\n// typed construction boundary); falls back to `detectUpdateForm` only for a\n// machine that never passed through `defineMachine`. No reader re-implements the\n// heuristic.\nexport function formOf(machine: {\n update: object;\n __form?: UpdateForm;\n}): UpdateForm {\n return machine.__form ?? detectUpdateForm(machine.update);\n}\n\n// === NoCellError: the named cell-lookup failure (#276) ===\n//\n// An unknown `msg.type` (wire data reaching dispatch) or a type-bypassed\n// missing cell used to surface as a bare `TypeError: ... is not a function`\n// deep inside dispatch — no msg.type, no state name, nothing actionable. The\n// guard lives in `applyCell` because it is the single dispatch primitive\n// every stepping site goes through (#275), so one guard covers `run`,\n// replay/foldMsgs, the PBT fold runner, and the withX wrappers.\nexport class NoCellError extends Error {\n override readonly name = \"NoCellError\";\n readonly _tag = \"NoCellError\" as const;\n constructor(\n public readonly msgType: string,\n public readonly stateName: string,\n ) {\n super(\n `@demlik/tea: no update cell for msg.type \"${msgType}\" in state ` +\n `\"${stateName}\" — the machine's update does not handle this Msg ` +\n `(an unknown wire msg.type, or a missing cell reached by bypassing ` +\n `the mapped types).`,\n );\n }\n}\n\n// Reducer-form State carries no mandatory discriminant; best-effort read of a\n// string `state.type` for the error, else a placeholder.\nfunction stateNameOf(state: unknown): string {\n if (typeof state === \"object\" && state !== null && \"type\" in state) {\n const t = (state as { type: unknown }).type;\n if (typeof t === \"string\") return t;\n }\n return \"(untagged state)\";\n}\n\n// === lookupCell: THE single cell SELECTION, split from the invocation ===\n//\n// The form-branching (`reducer` → flat `update[msg.type]`; `transitions` →\n// `update[state.type][msg.type]`) lives here and NOWHERE else. `applyCell`\n// (throwing) and `tryApplyCell` (`Result`-returning, in `../runtime-types`)\n// are both thin skins over this one selection, so the two error disciplines\n// can never disagree about WHICH cell a `(machine, state, msg)` triple picks\n// — the failure mode a second hand-written copy of the branching would have.\n//\n// Returns the selected cell (or `undefined` when there is none) together with\n// the `stateName` the `NoCellError` message needs — the caller decides whether\n// that absence becomes a throw or an `Err`.\n//\n// Pure and allocation-light: one small record per lookup, never a closure.\nexport function lookupCell<S, M extends { type: string }, C extends Cmd>(\n machine: { update: object; __form?: UpdateForm },\n state: S,\n msg: M,\n): {\n readonly cell: ((state: S, msg: M) => readonly [S, readonly C[]]) | undefined;\n readonly stateName: string;\n} {\n type CellFn = (state: S, msg: M) => readonly [S, readonly C[]];\n if (formOf(machine) === \"reducer\") {\n const record = machine.update as Record<string, CellFn | undefined>;\n const cell = record[msg.type];\n return {\n cell: typeof cell === \"function\" ? cell : undefined,\n stateName: stateNameOf(state),\n };\n }\n const table = machine.update as Record<\n string,\n Record<string, CellFn | undefined> | undefined\n >;\n // The ONE sanctioned `state as unknown as { type }` read: the Transitions\n // overload constrains S to `{ type: string }` at the `defineMachine`\n // boundary, but that constraint is erased on the runtime-facing `object`\n // here — every former per-site copy of this double-cast collapsed into\n // this line (#275).\n const stateKey = (state as unknown as { type: string }).type;\n const cell = table[stateKey]?.[msg.type];\n return {\n cell: typeof cell === \"function\" ? cell : undefined,\n stateName: String(stateKey),\n };\n}\n\n// === applyCell: THE single reducer-vs-transitions dispatch primitive ===\n//\n// Applies the one update cell selected by `lookupCell(machine, state, msg)` and\n// returns its `[nextState, cmds]` verbatim. Every site that steps a machine —\n// `run`'s applyUpdate, `foldUpdates` (replay/foldMsgs), the PBT fold runner,\n// and the withX wrappers — dispatches through THIS function, so production and\n// the verification tools agree on the update form by construction (#275).\n// Pure and dev-check-free: `deepFreeze`/`assertPureResult` stay at the call\n// sites that want them. A missing cell throws `NoCellError` (#276), never a\n// bare TypeError.\n//\n// The `Result`-returning twin is `tryApplyCell` (`@demlik/tea` root; it needs\n// `better-result`, which this pure leaf must not import). Both read the SAME\n// `lookupCell`, so \"which cell\" is decided once.\nexport function applyCell<S, M extends { type: string }, C extends Cmd>(\n machine: { update: object; __form?: UpdateForm },\n state: S,\n msg: M,\n): readonly [S, readonly C[]] {\n const { cell, stateName } = lookupCell<S, M, C>(machine, state, msg);\n if (cell === undefined) throw new NoCellError(msg.type, stateName);\n return cell(state, msg);\n}\n\n// === applyCellChecked: `applyCell` wrapped in the DEV pre/post invariant pair ===\n//\n// The dev-mode discipline every fold site shares: `deepFreeze` the input `state`\n// so a reducer that mutates it in place trips synchronously, then\n// `assertPureResult` the returned `[state, cmds]` shape. Both guards compile out\n// of production (`__DEV__`). `run`'s dispatch loop and `foldUpdates` both step\n// through THIS wrapper so the invariant enforcement lives in exactly one place\n// and cannot drift between the runtime and the pure fold.\nexport function applyCellChecked<S, M extends { type: string }, C extends Cmd>(\n machine: { update: object; __form?: UpdateForm },\n state: S,\n msg: M,\n): readonly [S, readonly C[]] {\n if (__DEV__) deepFreeze(state);\n const result = applyCell<S, M, C>(machine, state, msg);\n if (__DEV__) assertPureResult(result, msg.type);\n return result;\n}\n\n// === msgKeysOf: recover the Msg.type set from either update form ===\n//\n// A Reducer's own keys ARE the Msg.type set. A Transitions table's keys are\n// state.type; its INNER keys are the Msg.type set, so the UNION of every row's\n// inner keys is read — first-seen order, deduped. An empty update (`M` is\n// `never`) yields `[]`. Keyed on `formOf` — the withX wrappers and the PBT\n// `msgTypeKeys` all read through this one helper (#275).\n//\n// Why the union and not the first row's keys (the former reading): the\n// mapped-type `Transitions<S, M, C>` contract makes the Msg key set uniform\n// across phases, so for a hand-written TOTAL table the first row already IS\n// the union and this is a no-op. But the contract only binds where the types\n// bind. A table assembled DYNAMICALLY — the discriminants widened to plain\n// `string`, rows pushed in a loop — is structurally ragged, and reading row\n// zero then under-reports the Msg union. That under-report is not cosmetic:\n// all three withX wrappers build their flat merged Reducer by iterating\n// `msgKeysOf(base)`, so a Msg missing from row zero got NO cell in the wrapped\n// machine and threw `NoCellError` at dispatch for a Msg the base handles\n// perfectly well; and `withDeadline`'s reserved-namespace scan silently missed\n// a `$deadline:`-prefixed base Msg that appeared only in a later row.\n//\n// The widening is pure: for any total table the returned array is identical\n// (same keys, same order). Cost goes from O(msgs) to O(states × msgs), paid\n// ONCE per wrapper construction — never inside the dispatch loop.\nexport function msgKeysOf(machine: {\n update: object;\n __form?: UpdateForm;\n}): readonly string[] {\n const keys = Object.keys(machine.update);\n if (keys.length === 0) return [];\n if (formOf(machine) === \"reducer\") return keys;\n const table = machine.update as Record<string, object | undefined>;\n const seen = new Set<string>();\n const union: string[] = [];\n for (const stateKey of keys) {\n const row = table[stateKey];\n if (row === null || row === undefined) continue;\n for (const msgKey of Object.keys(row)) {\n if (seen.has(msgKey)) continue;\n seen.add(msgKey);\n union.push(msgKey);\n }\n }\n return union;\n}\n\n// === describeMachine / acceptsOf: the per-state accept-sets, as a reading ===\n//\n// \"Which Msgs does this machine accept, and in which state?\" — the question a\n// tool asks when it drives a machine ITSELF instead of through `run`: a CLI\n// rendering the legal next moves, a doc generator, a log validator. Before\n// this the only answer was to cast `machine.update as Record<string,\n// Record<string, unknown>>` at the call site and hand-branch the form — one\n// private copy of the form-branching per consumer, drifting from the kernel's.\n//\n// This is a DERIVED READING over the table, deliberately NOT a property on the\n// machine. It has to be: every `withX` wrapper builds a fresh flat\n// `Record<string, Cell>`, casts it to `Reducer`, and returns a NEW object\n// literal carrying only `init`/`update`/`subscriptions`/`subscribe`/\n// `interpret`. Any property hung on a machine is therefore destroyed by the\n// first wrap, and the wrapped table is reducer-form regardless of the base's.\n// A function over `(update, formOf)` survives wrapping and tells the truth\n// about the machine it is actually handed.\n//\n// The return type is a DISCRIMINATED union on `form` because the two forms\n// genuinely answer different questions, and faking the missing one would be a\n// lie the type system would then propagate:\n// - `transitions` — carries `states` and `accepts` (state.type → Msg.types).\n// - `reducer` — has NO per-state accept-sets AT ALL. Its dispatch does\n// not consult the state, so `msgs` is the whole answer and there is no\n// `accepts` field to read. Reaching for one is a compile error, not an\n// empty object.\nexport type MachineShape =\n | {\n readonly form: \"reducer\";\n /** Every `Msg.type` the flat reducer has a cell for. */\n readonly msgs: readonly string[];\n }\n | {\n readonly form: \"transitions\";\n /** The union of every row's `Msg.type` keys (see `msgKeysOf`). */\n readonly msgs: readonly string[];\n /** Every `state.type` the table has a row for, in table order. */\n readonly states: readonly string[];\n /** `state.type` → the `Msg.type`s that state has a cell for. */\n readonly accepts: Readonly<Record<string, readonly string[]>>;\n };\n\nexport function describeMachine(machine: {\n update: object;\n __form?: UpdateForm;\n}): MachineShape {\n const msgs = msgKeysOf(machine);\n if (formOf(machine) === \"reducer\") return { form: \"reducer\", msgs };\n const table = machine.update as Record<string, object | undefined>;\n const states = Object.keys(table);\n const accepts: Record<string, readonly string[]> = {};\n for (const stateKey of states) {\n const row = table[stateKey];\n accepts[stateKey] =\n row === null || row === undefined ? [] : Object.keys(row);\n }\n return { form: \"transitions\", msgs, states, accepts };\n}\n\n// The one-state shorthand over `describeMachine`. Reducer-form is not a\n// special case being papered over: a flat reducer's dispatch never reads the\n// state, so EVERY state accepts the full Msg set and returning it is the true\n// answer, not a stand-in. A `stateType` with no row in a Transitions table\n// accepts nothing, so `[]` — equally true.\nexport function acceptsOf(\n machine: { update: object; __form?: UpdateForm },\n stateType: string,\n): readonly string[] {\n const shape = describeMachine(machine);\n if (shape.form === \"reducer\") return shape.msgs;\n return shape.accepts[stateType] ?? [];\n}\n\n// === Reducer<S, M, C>: record-of-handlers form of `update` ===\n//\n// Flat dispatch table keyed by `Msg.type`. Each cell is a pure transition for\n// a single Msg variant, narrowed via `Extract<M, { type: K }>`. The mapped type\n// is *load-bearing*: adding a Msg variant without a matching key in the\n// Reducer record is a compile error — no `absurd()` helper at call sites, no\n// silent fall-through, no default branch hiding impurity.\n//\n// Strengthens invariant 2 (pure transitions — the record form has no\n// fall-through default to hide impurity behind) and invariant 7 (identity is\n// explicit — the Msg variant set is load-bearing at the type level).\n//\n// `defineMachine` accepts the Reducer record form via overload. The runtime\n// dispatches via `update[msg.type](state, msg)`.\nexport type Reducer<S, M extends { type: string }, C extends Cmd> = {\n [K in M[\"type\"]]: (\n state: S,\n msg: Extract<M, { type: K }>,\n // `SyncReturn<S, C>` (not `readonly [S, readonly C[]]`) is the reentrancy\n // guard: a non-thenable return type, so an `async`/inline-`await`ing cell —\n // which returns `Promise<...>` — fails to compile at the cell. See the\n // `SyncReturn` doc above.\n ) => SyncReturn<S, C>;\n};\n\n// === Transitions<S, M, C>: table form of `update` for state-machine-shaped machines ===\n//\n// When `State` is itself a discriminated union (`State.type` is the active\n// phase), the table form makes every (state.type × msg.type) cell explicit at\n// the type level. Missing a cell — e.g. forgetting how `phase: \"settling\"`\n// handles `msg.type: \"tab_stepped\"` — fails to compile. No nested switch, no\n// fall-through, no `_ -> (state, [])` default branch hiding regressions.\n//\n// Each cell receives the narrowed `State` for its phase and the narrowed\n// `Msg` for its variant via two `Extract` lookups. Phantom-narrow at the\n// type level; pure data at runtime.\n//\n// `defineMachine` accepts a third `update` form for `Transitions<S, M, C>` —\n// the runtime dispatches via `update[state.type][msg.type](state, msg)`.\n//\n// Strengthens invariant 2 (table form has no fall-through default),\n// invariant 6 (runtime walks the table predictably, no hidden dispatch\n// fallback), and invariant 7 (both state.type and msg.type are load-bearing\n// at the type level).\nexport type Transitions<\n S extends { type: string },\n M extends { type: string },\n C extends Cmd,\n> = {\n [P in S[\"type\"]]: {\n [K in M[\"type\"]]: (\n state: Extract<S, { type: P }>,\n msg: Extract<M, { type: K }>,\n // Same reentrancy guard as `Reducer`: a transition cell that returns a\n // Promise (async / inline-await) is the suspended-blocking turn the canon\n // forbids, so its return is the non-thenable `SyncReturn<S, C>`.\n ) => SyncReturn<S, C>;\n };\n};\n\n// === Cmd: namespace for conditional Cmd emission ===\n//\n// Two recurring frictions in reducer cells:\n//\n// 1. \"Emit cmd X only when condition Y holds.\"\n// Hand-rolled: `cond ? [{ type: \"x\", ... }] : []`\n// With Cmd: `Cmd.when(cond, { type: \"x\", ... })`\n//\n// 2. \"Emit cmd X carrying a value that may be undefined; if undefined,\n// emit nothing.\"\n// Hand-rolled: `value !== undefined ? [{ type: \"x\", value }] : []`\n// With Cmd: `Cmd.whenDefined(value, (v) => ({ type: \"x\", value: v }))`\n//\n// Both forms return `readonly C[]` so they spread cleanly into the cmds\n// array returned by a Transitions cell:\n//\n// return [next, [\n// ...Cmd.when(state.tabId !== undefined, { type: \"detach_debugger\", tabId: state.tabId }),\n// ...Cmd.whenDefined(state.queueItemId, (id) => ({\n// type: \"queue:complete\", queueItemId: id, status: \"done\",\n// })),\n// ]];\n//\n// `Cmd` as a value namespace coexists with `Cmd<T>` as a type — TypeScript's\n// declaration merging puts them in different name spaces (type vs. value).\n// The reader sees `Cmd<...>` in type position and `Cmd.when(...)` in\n// expression position; no ambiguity at use sites.\n//\n// Why a namespace and not top-level `when` / `whenDefined`: `when` at module\n// scope reads as a generic conditional, but these helpers are specifically\n// for *Cmd emission* (return `readonly C[]`, the cmds-array contract of\n// every Transitions cell). The namespace pins that intent at the call site.\nexport const Cmd = {\n /**\n * Declare a typed Cmd constructor (ADR 0014). Returns the builder —\n * `fetch({ url })` yields `{ type: \"fetch\", url }` — carrying the minted Msg\n * builders `fetch.ok(cmd, value)` / `fetch.err(cmd, error)` and the\n * declaration the runtime edge parses against. `Settled<typeof fetch>` is\n * the two-arm Msg union it settles with; `defineMachine({ cmds: [fetch] })`\n * folds that union into the machine's `M`.\n *\n * const fetch = Cmd.define(\"fetch\", {\n * input: z.object({ url: z.string() }),\n * ok: z.object({ status: z.number(), body: z.string() }),\n * err: [\"not_found\", \"timeout\"],\n * needs: Cmd.needs<{ http: Http }>(),\n * });\n *\n * `err` is the `_tag` list the handler may settle with; the runtime adds\n * `malformed_result` for an `_ok` value the `ok` schema rejects. `needs`\n * names the `Ctx` slice the handler reads — `run` refuses a ctx without it.\n */\n define: defineCmd,\n\n /**\n * Name a Cmd's `R` — the `Ctx` slice its handler reads. A phantom: nothing\n * is built, nothing is checked at runtime; the type is what `run` reads.\n */\n needs: <R>(): Needs<R> => ({}),\n\n /**\n * The empty Cmd array. Typed `readonly never[]` so it's assignable to any\n * `readonly C[]` for any `C extends Cmd`. Use in `init` returns and\n * Transitions cells that emit zero effects:\n *\n * init: (loaded) => [loaded ?? initial, Cmd.none],\n * tick: (state) => [state, Cmd.none],\n *\n * Elm's `Cmd.none` analogue. Cultural signal alongside the runtime help:\n * `[state, Cmd.none]` reads as intent (\"this transition emits nothing\"),\n * `[state, []]` reads as \"empty array of what\".\n *\n * Frozen at runtime so a downstream consumer can't `.push()` into the\n * shared reference.\n */\n none: Object.freeze([]) as readonly never[],\n\n /**\n * Flat concat of cmd arrays. Use when a cell composes effects from\n * multiple conditional sources:\n *\n * return [next, Cmd.batch(\n * Cmd.whenDefined(state.queueItemId, (id) => ({ type: \"complete\", id })),\n * Cmd.when(state.windowId !== undefined, { type: \"close_window\", windowId }),\n * [{ type: \"detach_debugger\", tabId: state.tabId }],\n * )];\n *\n * `<const C>` keeps inline cmd literals' discriminants narrow (same\n * rationale as `when` / `whenDefined` above).\n */\n batch: <const C extends Cmd>(\n ...arrs: readonly (readonly C[])[]\n ): readonly C[] => {\n const out: C[] = [];\n for (const arr of arrs) out.push(...arr);\n return out;\n },\n\n /**\n * Emit `cmd` wrapped in a single-element array iff `cond` is true.\n * Otherwise return the empty array. Spreads cleanly into the cmds array\n * returned by a Transitions cell.\n *\n * `const C` (TypeScript 5.0+) keeps the inferred `type:` discriminator\n * literal narrow when called with an object literal — so `Cmd.when(b,\n * { type: \"x\", ... })` infers as `{ type: \"x\", ... }`, not `{ type:\n * string, ... }`, and stays assignable to a discriminated-union arm\n * without `as const` at the call site.\n */\n when: <const C extends Cmd>(cond: boolean, cmd: C): readonly C[] =>\n cond ? [cmd] : [],\n\n /**\n * If `value` is defined, call `build(value)` and emit the resulting cmd\n * wrapped in a single-element array. If `value` is `undefined`, return\n * the empty array. `value` is narrowed to `T` inside `build`.\n *\n * `const C` on the return type keeps the callback's object-literal\n * `type:` field narrow (see `when` above for the full rationale).\n */\n whenDefined: <T, const C extends Cmd>(\n value: T | undefined,\n build: (value: T) => C,\n ): readonly C[] => (value !== undefined ? [build(value)] : []),\n} as const;\n\n// === SubId: branded string for Sub identity ===\n//\n// Subs are reconciled by id on every transition (canon §2.5, invariant 7).\n// Before branding, `Sub.id` was a raw `string` — a typo at one call site and a\n// different typo at another would silently produce two distinct subs that\n// looked identical to a human reader. The brand makes id construction explicit\n// (`subId(\"...\")`) and catches accidental string-where-SubId at the type\n// level.\n//\n// The brand is structural: `string & { __brand: \"SubId\" }`. The `subId(s)`\n// constructor is the ONE permitted cast in the substrate — every other call\n// site must go through it.\nexport type SubId = string & { readonly __brand: \"SubId\" };\n\n/**\n * Construct a `SubId` from a string. Use this everywhere a Sub literal is\n * built — including ids derived from runtime data (`subId(\\`ws:${auditId}\\`)`).\n * The brand is structural, not nominal, so dynamic ids are permitted; the\n * constructor's job is to make the identity decision explicit at every call\n * site so the type system can catch accidental raw-string drift.\n *\n * Strengthens invariant 7 (identity is explicit).\n */\nexport function subId(s: string): SubId {\n return s as SubId;\n}\n\n// === Sub: tagged-union, continuous source of msgs; stable id used for diff/reconcile ===\nexport type Sub<T extends string = string> = {\n readonly id: SubId;\n readonly type: T;\n};\n\n// === Dispose: the cleanup a dep-keyed Sub's source returns ===\n//\n// A `source` opens a resource and returns the function that closes it. The\n// substrate's reconcile calls it when the Sub's `deps` go null (torn down) or\n// change (re-armed: old `Dispose` then a fresh `source`). Same contract as the\n// `() => void` cleanup every `subscribe[type]` handler returns — named so a\n// dep-keyed Sub's `source` reads as \"open → returns close\".\nexport type Dispose = () => void | Promise<void>;\n\n// === depsInactive: the dep-keyed gate, defined ONCE ===\n//\n// \"This Sub has no slice in this state\" is one fact read by two places — the\n// runtime's `reconcileDepSubs` and `replay`'s desired-set projection — so it\n// gets one definition rather than a `deps === null` written twice.\n//\n// It reads NULLISH, not `null`. `(s) => s.runId` over an optional field is the\n// natural projection an author writes, and it yields `undefined` on the states\n// that mean \"inactive\"; a `=== null` gate armed the Sub there and hashed the\n// slice to the string `\"undefined\"` — one shared key under which a resource\n// was acquired for a state that had none. The typed alternative (`deps: (s) =>\n// {} | null`) would reject that projection at compile time, but it also rejects\n// every battery's `(state) => TKey | null` for an unconstrained `TKey`, so the\n// gate is widened instead of the type narrowed.\nexport function depsInactive(deps: unknown): boolean {\n return deps === null || deps === undefined;\n}\n\n// === structuralHash: deterministic, order-independent id from a deps value ===\n//\n// The id of a dep-keyed Sub (invariant 7 — identity is explicit) is DERIVED\n// from the slice of state the Sub depends on, never hand-authored. So adding a\n// field to `deps` changes the id exactly when that field changes, and a\n// churning id — the named #1 Sub bug (a Sub that remounts every tick, so its\n// timer never fires) — becomes impossible by construction: the author cannot\n// build the id wrong because the author does not build it.\n//\n// Determinism is load-bearing (invariant 2 — the reconcile pass runs inside\n// the pure-transition path's reconcile step; the same `deps` must always hash\n// to the same id). So: NO `Date`, NO random, NO insertion-order dependence.\n// Object keys are sorted so `{ runId, phase }` and `{ phase, runId }` hash\n// identically — the canonical \"name the slice → stable key\" adaptation FoldKit\n// makes for TS (its `modelToDependencies`), which Elm gets free from its\n// structural compare.\n//\n// The supported `deps` shape is plain JSON-compatible data (string / number /\n// boolean / null / array / plain object). A function, class instance, symbol,\n// or bigint in `deps` is a programming error — `deps` names a state SLICE, and\n// state is a value (invariant 1); such a value would also break the\n// determinism this hash promises. The walk throws on all of them to surface\n// that loudly rather than silently producing an unstable — or, worse, a\n// COLLIDING — id.\n//\n// The non-plain OBJECT case is the one that bites: `Object.keys` reports no own\n// enumerable property on a `Date`, `Map`, `Set`, `Error`, or a typical class\n// instance, so a walk that trusts it renders every one of them — and `{}` —\n// as `\"{}\"`. That is not an unstable id, it is ONE id for every value, which\n// defeats the identity filter (a foreign run's message compares equal), pins a\n// dep-keyed Sub to its first slice forever, and makes a battery's handle table\n// return the previous key's handle. The guard is therefore on the PROTOTYPE\n// (`Object.prototype` or `null`), not on a list of known classes: a\n// user-defined `RunKey` is refused by the same rule as `Date`, and no future\n// exotic builtin slips through.\n//\n// Rendering `Date`/`Map`/`Set` structurally was the alternative. It was\n// rejected: it buys a second rule to remember for a value the author can\n// project in one call (`startedAt.toISOString()`), and `Map`/`Set` iteration is\n// INSERTION-ordered, so any faithful rendering is order-sensitive — exactly the\n// churn this hash exists to prevent. One rule, stated once: plain data only.\n//\n// It is also the ONE \"turn this key value into a stable string\" primitive the\n// `@demlik/tea/subs` batteries address their interpret-local handle tables\n// with, so a battery's key rendering and the kernel's Sub identity can never\n// drift into two hashes for one fact.\nexport function structuralHash(deps: unknown): string {\n return stableStringify(deps);\n}\n\nfunction stableStringify(value: unknown): string {\n if (value === null) return \"null\";\n const t = typeof value;\n if (t === \"string\") return JSON.stringify(value);\n if (t === \"number\" || t === \"boolean\") return String(value);\n if (t === \"undefined\") return \"undefined\";\n if (t === \"function\") {\n throw new Error(\n \"@demlik/tea: structuralHash received a function in `deps`. \" +\n \"A dep-keyed Sub's `deps` must be a plain state slice (string / number / \" +\n \"boolean / null / array / object), not a closure — see invariant 1.\",\n );\n }\n if (Array.isArray(value)) {\n return `[${value.map(stableStringify).join(\",\")}]`;\n }\n if (t === \"object\") {\n // Plain-object gate. `Object.keys` is only a faithful reading of a value\n // whose prototype is `Object.prototype` (or `null` — a bag built with\n // `Object.create(null)` is plain by every measure that matters here).\n // Anything else keeps its data somewhere `Object.keys` cannot see, and\n // would render as `\"{}\"` — one id for every such value.\n const proto = Object.getPrototypeOf(value) as object | null;\n if (proto !== Object.prototype && proto !== null) {\n const ctor = (value as { constructor?: { name?: string } }).constructor;\n const label = ctor?.name ?? \"object\";\n throw new Error(\n `@demlik/tea: structuralHash received a non-plain object (${label}) in \\`deps\\`. ` +\n \"A `Date`, `Map`, `Set`, `Error` or class instance carries no own enumerable \" +\n \"properties, so every one of them would hash to the same id as `{}` — a silent \" +\n \"collision, not a wrong-looking key. Project it to plain data first \" +\n \"(e.g. `startedAt.toISOString()`, `[...set].sort()`) — see invariant 1.\",\n );\n }\n const obj = value as Record<string, unknown>;\n const keys = Object.keys(obj).sort();\n return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`).join(\",\")}}`;\n }\n // bigint / symbol — not JSON-representable; same class of error as function.\n throw new Error(\n `@demlik/tea: structuralHash received an unsupported \\`deps\\` value of type \"${t}\". ` +\n \"Use plain JSON-compatible data for a dep-keyed Sub's `deps`.\",\n );\n}\n\n// === DepKeyedSub<S, M, Ctx>: a Sub whose identity AND gate fall out of `deps` ===\n//\n// The author declares the slice of state the Sub depends on (`deps`) and how\n// to open the resource for that slice (`source`). The substrate derives BOTH:\n//\n// - the **id** = `structuralHash(deps(state))` — changes exactly when the\n// slice changes (re-arm), stable otherwise (no churn). The author never\n// writes `subId(...)` — the hand-written subId was the drift away from\n// Elm's structural identity, which this restores.\n// - the **gate** = `deps(state)` returning `null` ⇒ inactive in this state\n// (dispose if running); non-null ⇒ active. The substrate folds every\n// `DepKeyedSub` into its own active set, so the author deletes the central\n// `subscriptions(state)` aggregate — there is no list to forget to edit\n// when a new phase is added (a per-Sub gate travels with the Sub).\n//\n// This is the FoldKit `modelToDependencies` shape: a Sub names the state slice\n// it depends on, the kernel keys on that slice. The manual `subscriptions?:`\n// field stays as the documented escape hatch (see `Machine`).\n//\n// `source` returns a `Dispose`. On a `deps` change the substrate runs the old\n// `Dispose` then re-runs `source` with the new deps (re-arm); on `deps` going\n// null it runs `Dispose` (teardown). The same reconcile machinery the manual\n// `Sub` path uses (`reconcileSubs` in `../run.ts`) — no second loop.\n//\n// Strengthens invariant 4 (external lifecycle owned by the substrate — the\n// dep-keyed Sub is reconciled, never hand-driven) and invariant 7 (identity is\n// explicit — derived deterministically from `deps`, never an ad-hoc string).\nexport interface DepKeyedSub<S, M, Ctx = unknown> {\n /**\n * The slice of state this Sub depends on. `null` OR `undefined` ⇒ the Sub is\n * inactive in this state (the substrate disposes it if it was running) — see\n * `depsInactive`, so `(s) => s.optionalRunId` gates correctly. Otherwise the\n * Sub is active, keyed on `structuralHash(deps)`.\n *\n * Pure (invariant 2). Plain JSON-compatible data only (invariant 1) — the\n * structural hash walks it deterministically and THROWS on a `Date`, `Map`,\n * `Set`, `Error` or class instance rather than collapsing them all onto one\n * id. Project such a value first (`startedAt.toISOString()`).\n */\n readonly deps: (state: S) => unknown;\n /**\n * Open the resource for the current `state` (whose `deps` the kernel just\n * found non-null). Receives `dispatch` to fire follow-up Msgs (a timer's\n * fire, an inbound frame) and the machine's `ctx` (the host's transport\n * factory, storage handles). Returns the `Dispose` the substrate runs on\n * teardown / re-arm. Same role as a `subscribe[type]` handler's returned\n * cleanup.\n *\n * `source` takes `state` (not the deps slice) so the slice type `D` never\n * escapes the entry — each battery's `.depKeyed` closes over its own typed\n * deps internally (cast-free existential: the entry that produced the deps\n * is the entry that consumes them).\n */\n readonly source: (state: S, dispatch: (msg: M) => void, ctx: Ctx) => Dispose;\n}\n\n// === Identity<S, M>: declare the instance's identity once; the kernel drops\n// mis-addressed messages before they reach `update` ===\n//\n// Without this: the substrate dispatches EVERY message to `update`, so an\n// author who needs \"this message is for THIS run\" must repeat\n// `if (msg.runId !== state.runId) return [state, []]` in every reducer cell.\n// Forget one cell and a stale or foreign run's message is applied → silent\n// corruption. That per-cell check is a rung-4 runtime stand-in for invariant 7\n// (identity is explicit): the identity invariant the host already holds (one\n// instance per run — `idFromName(runId)`) is re-asserted, cell by cell, where\n// it can be forgotten.\n//\n// `Identity<S, M>` lifts that to ONE declaration the kernel enforces. The\n// machine names two projections:\n//\n// - `ofState(state)` → the identity THIS instance owns, derived from state.\n// - `ofMsg(msg)` → the identity a message is ADDRESSED to, or\n// `undefined` when the message carries no identity\n// (lifecycle messages like `boot`, or `start_audit`\n// before the run exists — those are never dropped).\n//\n// Per transition the kernel compares `ofMsg(msg)` to `ofState(state)` by\n// `structuralHash` (so the identity can be any plain value, same machinery the\n// dep-keyed Sub id uses). A message addressed to a DIFFERENT identity is\n// dropped BEFORE `update` runs — the reducer never sees it, so no cell needs a\n// guard. A message with `ofMsg === undefined` is identity-agnostic and always\n// reaches `update`.\n//\n// Two projections rather than a single `(state) => state.runId`: the kernel\n// needs BOTH the state's identity AND the message's to compare, and splitting\n// them keeps the comparison `K`-to-`K` with no `as` cast (a message union\n// carries its identity field on only SOME arms — `ofMsg` narrows per arm in\n// author-land, returning `undefined` on the arms that have none, instead of\n// the substrate reaching into `msg.runId` it cannot type).\n//\n// Opt-in (escape hatch): a machine that declares no `identity` skips the filter\n// entirely — every message reaches `update`, exactly as before.\n//\n// Strengthens invariant 7 (identity is explicit — declared once, derived\n// deterministically, kernel-enforced rather than re-checked per cell) and\n// invariant 6 (the runtime is small and inspectable — a mis-addressed message\n// is dropped at ONE observable point, not silently mishandled in N cells).\nexport interface Identity<S, M> {\n /** The identity THIS instance owns. Pure (invariant 2); plain value. */\n readonly ofState: (state: S) => unknown;\n /**\n * The identity a message is addressed to, or `undefined` when the message\n * carries no identity (lifecycle / pre-identity messages — never dropped).\n * Pure (invariant 2); plain value when defined.\n */\n readonly ofMsg: (msg: M) => unknown;\n}\n\n// === Port<T>: typed escape hatch for \"data leaving the runtime\" ===\n//\n// A Port is a named, typed channel that Cmd handlers can `emit` to via the\n// augmented `ctx.emit(port, value)`. Subscribers attach via\n// `runtime.subscribePort(port, listener)` and receive every emitted value\n// synchronously (same fanout discipline as `runtime.observe`).\n//\n// Why Ports are a substrate primitive, distinct from State and Observe:\n// - **State** is \"the world as the program sees it.\" Folding outgoing\n// announcements into State turns ephemeral signals into persisted facts —\n// the classic anti-pattern that motivated this primitive.\n// - **Observe** sees every transition `(msg, state)`. That's the right channel\n// for devtools and logging, but it forces every consumer to filter the\n// entire firehose for a single signal.\n// - **Ports** are typed and selective: one channel per concept, subscribers\n// only see what the handler chose to emit. Mirrors Elm's outgoing-port\n// semantics — a typed declaration with `port` keyword whose values flow\n// out of the program to the host.\n//\n// Port identity is **by reference** — the returned object IS the identity.\n// Name is metadata for debugging. To prevent the \"two modules definePort the\n// same name expecting to share a channel, silently get distinct ports\" class\n// of bug, `definePort` asserts the name has not been seen in this process —\n// the symmetric runtime check to `SubId` (canon §2.12, invariant 7). Each\n// definePort call must use a unique name; if two modules need the same port,\n// one module exports it and the other imports it.\nexport interface Port<T> {\n readonly __brand: \"port\";\n readonly name: string;\n // Phantom field — never assigned, never read at runtime. Carries `T` for\n // inference at `ctx.emit` and `subscribePort` call sites. Using `?` so\n // `definePort` can produce a Port without constructing a value of `T`.\n readonly __t?: T;\n}\n\n/**\n * Augmentation injected onto `ctx` inside Cmd handlers. Handlers receive\n * `ctx & PortEmitter` so they can call `ctx.emit(port, value)` synchronously.\n * Subscribers registered via `runtime.subscribePort(port, listener)` receive\n * the value immediately.\n *\n * Emitting to a port with no subscribers is a no-op (does not throw).\n */\nexport interface PortEmitter {\n emit<T>(port: Port<T>, value: T): void;\n}\n\n// === NoCtx: the context-free marker ===\n//\n// A handler, Sub, or machine that reads NOTHING from `ctx` says so by using\n// `NoCtx` for its Ctx slot. This is the DELIBERATE absence of context — not\n// the accidental `unknown` looseness that erodes a boundary (see issue #64).\n//\n// `unknown` at a Ctx seam is ambiguous: it can mean \"I intentionally need no\n// context\" OR \"I gave up tightening this type.\" `NoCtx` resolves that\n// ambiguity at the type level. A reader (and the next agent) sees `NoCtx` and\n// knows the context-free-ness is a choice the author made, not a hole.\n//\n// Shape: an empty readonly record. A `Ctx & PortEmitter` is-a `NoCtx`, so a\n// context-free handler still composes at a richer call site. It carries no\n// own fields, so the name's claim (\"reads nothing from ctx\") is what callers\n// read at the seam.\n//\n// Strengthens invariant 6 (no silent looseness — a context-free seam is named,\n// not inferred) and invariant 8 (the boundary is legible; `unknown` stays\n// reserved for genuine wire-edge erasure, not for \"didn't bother\").\nexport type NoCtx = Readonly<Record<never, never>>;\n\n/**\n * Spelled-out alias for `NoCtx`.\n *\n * @deprecated Use {@link NoCtx}. One Way to Do Each Thing — `NoCtx` is the\n * single non-deprecated name for the context-free-ctx marker (this alias has\n * no in-tree uses). Kept as a re-export for one minor per the deprecate-don't-\n * delete policy in `MAINTAINING.md`, then removed.\n */\nexport type ContextFree = NoCtx;\n\n// === Interpret<M, C, Ctx>: record-of-handlers form of `interpret` ===\n//\n// Flat dispatch table keyed by `Cmd.type`. Each cell receives the narrowed Cmd\n// and the runtime-augmented Ctx (`Ctx & PortEmitter`) and resolves to a\n// follow-up Msg or `void` (fire-and-forget). The mapped type makes a missing\n// handler a compile error — `defineMachine` cannot accept the dictionary until\n// every Cmd variant has one.\n//\n// **The injected `dispatch` — the typed Cmd→Msg edge.** A leaf handler that\n// resolves on its own returns its follow-up Msg (`Promise<M | void>`) and\n// IGNORES the third argument. A DETACHED handler (one that hands long-running\n// work to `ctx.waitUntil` and CANNOT return its terminal Msg inline — awaiting\n// it would deadlock the serial dispatch tail) fires its terminal Msg through\n// this injected `dispatch` instead. Today such a handler reaches for a\n// host-wired `ctx.dispatch` typed to the FULL Msg union — so a typo'd or wrong\n// terminal Msg from a detached site compiles silently (rung 5: the allowed set\n// lives only in a comment). `wrapDetached<C, Allowed>` (in `../runtime-types`)\n// narrows THIS injected `dispatch` to the Cmd's declared result-Msg set, so a\n// wrong terminal Msg fails to compile (rung 2).\n//\n// Additive: the third arg is OPTIONAL (`dispatch?: (msg: M) => void`), so a\n// handler declaring only `(cmd, ctx)` stays assignable, and a unit test that\n// invokes a handler directly with two args still typechecks. The kernel ALWAYS\n// passes the dispatch (see `runInterpret` in `../run.ts`); the optionality is\n// purely a backward-compatibility affordance on the TYPE, not a runtime \"maybe\n// absent\". A handler authored via `wrapDetached` receives a NARROWER view of\n// this dispatch (only its declared result-Msg set).\n//\n// Hoisted out of `Machine.interpret` so consumers can type a free-standing\n// handler dictionary with `Interpret<MyMsg, MyCmd, MyCtx>` instead of\n// re-declaring the mapped type at every effects module.\n//\n// **The `R` channel lands here.** A cell's `ctx` is the machine's `Ctx`\n// intersected with ITS Cmd's `NeedsOf` — so a `Cmd.define`d effect's handler\n// reads `ctx.http` typed, while `run` (via `RequiredCtx`) is what guarantees\n// the slice was actually supplied. An untyped Cmd's `NeedsOf` is `unknown`, so\n// every hand-written handler's `ctx` is exactly what it was.\n//\n// Strengthens invariant 2 (the record form has no fall-through default to\n// hide impurity behind) and invariant 7 (identity is explicit — the Cmd\n// variant set is load-bearing at the type level).\nexport type Interpret<M extends { type: string }, C extends Cmd, Ctx> = {\n [K in C[\"type\"]]: (\n cmd: Extract<C, { type: K }>,\n ctx: Ctx & NeedsOf<Extract<C, { type: K }>> & PortEmitter,\n dispatch?: (msg: M) => void,\n // biome-ignore lint/suspicious/noConfusingVoidType: an interpret handler returns a follow-up Msg or nothing; `void` permits no-return bodies that `M | undefined` would reject\n ) => Promise<M | void>;\n};\n\n// === InterpretDetached<C, Allowed, Ctx>: a detached interpret handler ===\n//\n// A handler that detaches long-running work (`ctx.waitUntil(...)`) and therefore\n// CANNOT return its terminal Msg inline (awaiting it would deadlock the serial\n// dispatch tail). It returns `Promise<void>` and fires its terminal Msg through\n// the kernel-injected `dispatch`, which is NARROWED to `Allowed` — the subset\n// of the Msg union this Cmd is permitted to produce. A wrong / typo'd terminal\n// Msg then fails to compile.\n//\n// `Allowed extends { type: string }` is the declared result-Msg set (e.g.\n// `GraphFinished | GraphFailed`). `dispatch: (msg: Allowed) => void` is the\n// narrowed edge. The handler still receives `ctx` (carrying `waitUntil`).\n//\n// Authored via `wrapDetached` (in `../runtime-types`), which adapts this shape\n// back into a plain `Interpret` cell so it drops into the existing `interpret`\n// dictionary with no kernel change at the call site.\nexport type InterpretDetached<\n C extends Cmd,\n Allowed extends { type: string },\n Ctx,\n> = (\n cmd: C,\n ctx: Ctx & PortEmitter,\n dispatch: (msg: Allowed) => void,\n) => Promise<void>;\n\n// === Subscribe<M, U, Ctx>: record-of-handlers form of `subscribe` ===\n//\n// Flat dispatch table keyed by `Sub.type`. Each cell receives the narrowed\n// Sub, the Ctx, and a `dispatch` to fire follow-up Msgs. Returns a cleanup\n// function the substrate calls when the Sub is reconciled out (state\n// transitioned away). The mapped type guarantees every Sub variant has a\n// handler at the type level; runtime dispatch is a single property lookup.\n//\n// The cleanup shares `Dispose`'s shape: a returned Promise is AWAITED by\n// `stop()` (bounded), so an async teardown a host relies on before evicting the\n// isolate actually completes. Mid-run reconcile does not await it — the\n// reconcile pass is synchronous by construction (invariant 2) — but the promise\n// is tracked from the moment it exists, so `stop()` catches it either way.\n//\n// Hoisted out of `Machine.subscribe` so consumers can type a free-standing\n// handler dictionary with `Subscribe<MyMsg, MySub, MyCtx>` instead of\n// re-declaring the mapped type at every subs module.\n//\n// Strengthens invariant 7 (identity is explicit — the Sub variant set is\n// load-bearing at the type level).\nexport type Subscribe<M extends { type: string }, U extends Sub, Ctx> = {\n [K in U[\"type\"]]: (\n sub: Extract<U, { type: K }>,\n ctx: Ctx,\n dispatch: (msg: M) => void,\n ) => Dispose;\n};\n\n// === Machine: pure data, host-agnostic ===\n//\n// `update` is stored as a union of the two record forms — the runtime branches\n// on shape at dispatch time. The public `defineMachine` overloads enforce one\n// shape per call so consumers never see the union themselves.\n//\n// - Reducer form: `Reducer<S, M, C>` — flat record keyed by Msg.type;\n// exhaustiveness enforced by the mapped type.\n// - Transitions form: `Transitions<S, M, C>` — 2D table keyed by\n// `state.type` then `msg.type`; only available when `S extends { type:\n// string }`. Conditional in the union so machines without a discriminated\n// State don't widen to include this branch.\n//\n// `M` is constrained to `{ type: string }` because both record forms require\n// a string discriminant.\n//\n// `interpret` is conditionally optional: when `C` is `Cmd<never>` the\n// interpret map is keyed by `never`, so the only valid value is `{}`. Forcing\n// every cmdless machine to write `interpret: {} as never` is ceremony for a\n// shape the type already pins. The tuple-wrap (`[C] extends [Cmd<never>]`)\n// disables distributive conditional so a real cmd union (e.g.\n// `Cmd<\"a\"> | Cmd<\"b\">`) doesn't degrade to optional just because one arm\n// happens to be `Cmd<never>`. Same trick the `update` field uses.\nexport type Machine<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n> = {\n /**\n * Boot the runtime. Called once by `run(...)` with whatever `Store.load()`\n * returned: `null` on fresh boot, the persisted state on rehydrate.\n *\n * **Contract:** when `loaded !== null`, init MUST return `[loaded, []]` —\n * no Cmds. Init's rehydrate branch is the migration / parse boundary, not\n * the boot-effect hook. See Invariant 2 in `.patterns/tea/tea-invariants.md`.\n *\n * Boot effects routes:\n * - Stateless infrastructure → host module top, outside TEA.\n * - State-conditional resume → a `boot` Msg the host dispatches once\n * after `run(...)` returns.\n *\n * Violations are caught at runtime by `replay` (which throws with a\n * pointer to the alternatives).\n */\n init: (loaded: S | null, ctx: Ctx) => readonly [S, readonly C[]];\n /**\n * The typed Cmd constructors this machine emits (`Cmd.define`). Declared\n * through `defineMachine({ cmds })`, which derives `C` and the settled half\n * of `M` from them; the runtime reads the list to parse each handler's\n * `_ok` value against its `ok` schema and stamp `at` at the interpret edge.\n * A machine of hand-written Cmds omits it and runs exactly as before.\n */\n readonly cmds?: readonly AnyCmdDef[];\n update:\n | Reducer<S, M, C>\n // Wrap in tuple to disable distributive conditional behavior. Without\n // `[S]`, TS distributes the conditional across `S`'s union members,\n // producing `Transitions<A,...> | Transitions<B,...>` instead of the\n // desired `Transitions<A|B,...>` — the table form needs a single\n // mapping over the full union of state types, not separate tables per\n // member.\n | ([S] extends [{ type: string }] ? Transitions<S, M, C> : never);\n /**\n * Dep-keyed Subs. Each declares the state slice it depends on; the substrate\n * derives the id (`structuralHash(deps)`) and the gate (`deps` non-null) and\n * folds them into the SAME reconcile loop `subscriptions` feeds. The author\n * never writes `subId(...)` and never lists Subs in a central\n * `subscriptions(state)` aggregate — a per-Sub gate travels with the Sub, so\n * a new phase can't silently forget to arm it.\n *\n * The deps slice type is erased to `unknown` per entry — each Sub's slice\n * differs, but the slice type never crosses this field (the kernel only needs\n * to hash it and pass `state` back to that same entry's `source`).\n *\n * Optional and independent of `subscriptions` / `subscribe`: a machine that\n * omits `subs` reconciles exactly as before.\n *\n * Strengthens invariant 4 (lifecycle owned by the substrate) and invariant 7\n * (identity derived, not hand-authored).\n */\n subs?: ReadonlyArray<DepKeyedSub<S, M, Ctx>>;\n /**\n * Instance-identity filter. Declares THIS instance's identity once; the\n * substrate drops any message addressed to a DIFFERENT identity before it\n * reaches `update`. Replaces the per-cell `if (msg.runId !== state.runId)`\n * guard entirely — the reducer never sees a foreign-instance message, so no\n * cell needs to check.\n *\n * Opt-in: a machine that omits `identity` skips the filter (every message\n * reaches `update`, exactly as before). A message whose `ofMsg` returns\n * `undefined` is identity-agnostic and always reaches `update` (lifecycle /\n * pre-identity messages).\n *\n * Strengthens invariant 7 (identity is explicit — declared once, enforced by\n * the substrate) and invariant 6 (mis-addressed messages dropped at one\n * observable point, not per cell).\n */\n identity?: Identity<S, M>;\n /**\n * Manual Sub aggregate — the documented escape hatch, KEPT alongside `subs`.\n * The machine that needs cross-Sub logic the per-Sub `deps` fold can't\n * express lists Subs here; the substrate reconciles them via `subscribe`.\n * Both paths feed ONE reconcile pass.\n */\n subscriptions?: (state: S) => readonly U[];\n subscribe?: Subscribe<M, U, Ctx>;\n /**\n * The update form (\"reducer\" | \"transitions\"), stamped non-enumerably by\n * `defineMachine` at construction (see `UpdateForm` / `formOf`). Optional in\n * the type so the structural `Machine` annotation form keeps accepting plain\n * object literals; readers go through `formOf`, which falls back to\n * `detectUpdateForm` when the tag is absent. Never written by hand.\n */\n readonly __form?: UpdateForm;\n} & ([C] extends [Cmd<never>]\n ? { interpret?: Interpret<M, C, Ctx> }\n : { interpret: Interpret<M, C, Ctx> }) &\n // `subscribe`/`subscriptions` are conditionally REQUIRED the same way\n // `interpret` is (#276): a machine declaring a real Sub union without a\n // subscribe map compiled and silently wired no subs (`reconcileSubs` skips\n // undefined handlers) — the exact silent-failure class the `interpret`\n // conditional prevents. The optional declarations above stay as U's\n // inference sites (a conditional type is not an inference site); this\n // intersection only adds requiredness when U is a real union. Same\n // tuple-wrap trick as `interpret` to disable distribution.\n ([U] extends [Sub<never>]\n ? unknown\n : {\n subscriptions: (state: S) => readonly U[];\n subscribe: Subscribe<M, U, Ctx>;\n });\n\n// === foldUpdates: the single internal fold `replay` and `foldMsgs` share ===\n//\n// Folds `machine.update` over `msgs` from `initialState`, dispatching each\n// Msg through `applyCell` — the same primitive `run` uses — so every fold site\n// agrees on the reducer-vs-transitions form by construction (no second copy of\n// the dispatch to drift). Returns the final state plus the Cmds the cells emitted\n// along the way; the caller keeps them (`replay`) or discards them (`foldMsgs`).\n//\n// Touches no `Store`, no `interpret` handler, and starts no subscription — it\n// only calls `update` cells. The two public folds differ only in how they\n// enter and what they return: `replay` enters via `init` and returns\n// `{ state, cmds, subs }`; `foldMsgs` enters from a base state and returns `S`.\nexport function foldUpdates<S, M extends { type: string }, C extends Cmd>(\n machine: { update: object; __form?: UpdateForm },\n initialState: S,\n msgs: readonly M[],\n): { state: S; cmds: C[] } {\n let state: S = initialState;\n const cmds: C[] = [];\n\n for (const msg of msgs) {\n const [next, emitted] = applyCellChecked<S, M, C>(machine, state, msg);\n state = next;\n cmds.push(...emitted);\n }\n\n return { state, cmds };\n}\n\n// === foldMsgs: runtime-free client-prediction fold seam (ADR 0006, #211) ===\n//\n// Folds `machine.update` over an ordered `Msg[]` starting from a caller-supplied\n// `base` state and returns the resulting state ONLY. This is the client-side\n// replay primitive a prediction loop needs (the Gambetta/Valve\n// authoritative-server reconcile step): re-simulate a queue of un-acked inputs\n// on top of an authoritative snapshot —\n// `foldMsgs(machine, snapshot, pendingInputs)`.\n//\n// Distinct from `replay` (the test idiom), by design (ADR 0006):\n// - Enters from a direct `base` parameter, NOT via `init`/`loaded` — so\n// reconciliation correctness never depends on the machine author's `init`\n// rehydrate discipline. Takes no `ctx` (the fold calls `update` only).\n// - Returns just the final `S` — not `{ state, cmds, subs }`. During prediction\n// the inputs' effects were already sent to the server; re-emitting their Cmds\n// on the client would double-fire, and `subs` are a runtime concern. Returning\n// only `S` makes \"replay fires no effects\" structural, not caller discipline.\n// A caller that wants the emitted Cmds for assertions uses `replay`.\n//\n// Invokes no `Store`, no `interpret` handler, and starts no subscription — it\n// shares `replay`'s `foldUpdates` fold, which only calls `update` cells, and\n// reads the reducer-vs-transitions form via the same `formOf(machine)` reader\n// `run`/`replay` use, so it agrees with them by construction.\n//\n// Per ADR 0006 the runtime-free *guarantee* lives on a dedicated\n// `@demlik/tea/pure` subpath whose module graph never reaches `run`; that\n// subpath export + import-graph guard are #213's scope. Here `foldMsgs` ships\n// as a reachable public API from root; #213 formalizes the boundary.\nexport function foldMsgs<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(machine: Machine<S, M, C, U, Ctx>, base: S, msgs: readonly M[]): S {\n return foldUpdates<S, M, C>(machine, base, msgs).state;\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/retry-backoff/index.ts"],"names":[],"mappings":"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","file":"chunk-Q2UM7BXU.js","sourcesContent":["/**\n * @packageDocumentation\n * @demlik/tea/retry-backoff — exponential backoff with jitter + cap, and the\n * retry-attempt state every fallible `interpret` handler folds over.\n *\n * This is a \"pure state + ops\" module in the same mold as\n * `@demlik/tea/work-queue`: a state type plus pure transition functions,\n * storage- and host-agnostic, that a consumer wires into their own reducer /\n * interpret. Nothing here reads the clock or the RNG on its own behalf —\n * randomness is *injected* (`rng`) and time is *computed by the caller* from\n * the millisecond delays this module returns. That keeps every function\n * deterministic and unit-testable, and keeps the substrate's purity invariants\n * intact when the result is threaded through `update` (invariant 2) and the\n * delay is realized as a `Cmd` the runtime performs (invariant 3).\n *\n * Typical wiring:\n *\n * // in `update`, on a failure Msg:\n * const retry = recordFailure(state.retry, msg.error);\n * if (!shouldRetry(retry, policy)) return [toGaveUp(state), []];\n * const delayMs = nextDelayMs(retry, policy); // pure: rng injected at call site\n * return [{ ...state, retry }, [{ type: \"ScheduleRetry\", delayMs, into: TimerFiredRetry }]];\n *\n * and the same wiring under a wall-clock outage budget, where the failure Msg\n * carries the instant it was observed:\n *\n * // policy: { baseMs: 250, factor: 2, capMs: 4_000, maxElapsedMs: 1_050_000, jitter: \"full\" }\n * const retry = recordFailure(state.retry, msg.error, msg.at);\n * if (!shouldRetry(retry, policy, msg.at)) return [toGaveUp(state, retryElapsedMs(retry, msg.at)), []];\n *\n * The reducer stays pure: `nextDelayMs` is called with the runtime's RNG only\n * at the emit boundary (or the caller defers the whole delay computation to the\n * `interpret` handler and passes `rng` there). Either way the substrate never\n * sees `Math.random()` inside a transition body.\n *\n * NOT a substrate primitive: it depends on nothing from `../index`. It is a\n * leaf utility consumers reach via the `@demlik/tea/retry-backoff` subpath.\n *\n * ── THE BOUND MAY BE A DURATION, NOT ONLY A COUNT ──────────────────────────\n * `maxAttempts` was the module's only terminal bound, and a count is the wrong\n * bound whenever this ladder nests inside somebody else's ladder. A downstream\n * consumer proved it in production: 4 restarts over a 250ms→4s ladder is ~3.75s\n * of backoff, which nested under a client's own ~2.5s ladder came to roughly\n * SIXTEEN SECONDS of total patience — against a peer that waits ~17.5 MINUTES\n * before it gives up on the other side. A routine deploy outran the budget and\n * killed 170 runs in 7 days that the peer would have resumed from a checkpoint.\n *\n * The failure a count cannot express is that the QUESTION is temporal. \"How\n * long do we tolerate an unreachable peer\" has an answer in seconds, usually\n * derived from the peer's own give-up window; what a count of attempts costs in\n * seconds depends on how long each attempt takes, which is the carrier's\n * business and not the policy's. So a policy may now declare `maxElapsedMs` —\n * a wall-clock outage budget measured from the FIRST failure of the streak —\n * instead of, or in addition to, `maxAttempts`. When both are declared, retry\n * continues only while EVERY declared bound still permits it.\n *\n * TIME IS AN INPUT, NOT AN AMBIENT READ (invariant 2). The streak's origin is\n * supplied by the caller to `recordFailure`, and `shouldRetry` is told `nowMs`;\n * this module still never reads a clock, exactly as it never reads the RNG.\n * The types make the wiring the only wiring that compiles: a duration-bounded\n * policy only satisfies the `shouldRetry` overload that demands a\n * {@link TimedRetryState} *and* a `nowMs`, so \"declared an outage budget, never\n * fed it a clock\" is a type error rather than a bound that quietly never fires.\n *\n * A policy with NO bound at all is likewise unrepresentable by accident — the\n * {@link RetryBudget} union has no \"neither\" arm; forever-retry must be spelled\n * `unbounded: true` ({@link UnboundedRetryPolicy}).\n *\n * Strengthens invariant 2 (transitions are pure — the clock enters as an\n * argument, never as a `Date.now()` inside the fold) and invariant 5\n * (composition is by reduction — the new bound is an additive arm on the policy\n * union and an optional argument on the verbs, so every existing count-bounded\n * call site keeps compiling untouched).\n */\n\n/**\n * Jitter strategy applied to the computed backoff delay.\n *\n * Names follow the AWS \"Exponential Backoff And Jitter\" taxonomy (Brooker,\n * 2015), the same vocabulary used by the AWS SDKs and most retry libraries:\n *\n * - `\"none\"` — no jitter; the delay is the raw capped exponential `d`.\n * Deterministic, but synchronizes retries across clients\n * (thundering herd). Use only when callers are already\n * de-correlated by other means.\n * - `\"full\"` — uniform random in `[0, d]`. Maximum spread; best herd\n * avoidance, highest variance in latency.\n * - `\"equal\"` — `d/2 + random(0, d/2)`, i.e. uniform in `[d/2, d]`. Half the\n * delay is fixed, half is jittered: keeps a latency floor while\n * still de-correlating clients. A common default.\n */\nexport type Jitter = \"none\" | \"full\" | \"equal\";\n\ndeclare const RngBrand: unique symbol;\n\n/**\n * A source of uniform randomness in `[0, 1)` — the `Math.random` contract.\n *\n * Nominally branded so the `[0, 1)` range is enforced at *construction*, not\n * by a comment. A raw `() => number` does NOT satisfy `Rng`: a stray `() => 5`\n * or `() => -1` is rejected by the type before it can ever feed the jitter\n * math and breach the `0 <= delay <= capMs` postcondition. The only way to\n * mint an `Rng` is {@link asRng} (or the {@link defaultRng} const), so the\n * range obligation lives at the boundary where randomness enters the module —\n * \"parse, don't validate\" applied to a function value.\n */\nexport type Rng = (() => number) & { readonly [RngBrand]: true };\n\n/**\n * Brand a `[0, 1)` generator as an {@link Rng}. The single entry point for\n * randomness into this module: callers (production wiring and tests alike)\n * pass their raw generator through here, which optionally asserts the contract\n * and tags the value. The probe is a cheap guard against the most common\n * misuse (returning a non-unit value); it does not — and cannot — prove\n * uniformity.\n *\n * PURE with respect to the module: it neither reads the clock nor calls the\n * generator on the module's behalf unless `probe` is set. The probe is opt-in\n * (default off) so wrapping a hot generator stays allocation- and call-free.\n *\n * @throws RangeError if `probe` is enabled and a sampled value is outside `[0, 1)`.\n */\nexport function asRng(fn: () => number, probe = false): Rng {\n if (probe) {\n const sample = fn();\n if (!(sample >= 0 && sample < 1)) {\n throw new RangeError(\n `asRng: generator returned ${sample}, outside the [0, 1) contract`,\n );\n }\n }\n return fn as Rng;\n}\n\n/**\n * The production default: `Math.random`, branded. Used wherever an `Rng` is\n * required but the caller omits one — at the effect boundary, never inside a\n * reducer.\n */\nexport const defaultRng: Rng = Math.random as Rng;\n\n/**\n * The shape of the delay ladder, with no terminal bound attached — `baseMs`,\n * `factor`, `capMs`, `jitter`. Split out from the policy because the curve and\n * the bound answer different questions: the curve is \"how fast do we come\n * back\", the bound is \"when do we stop\". `backoffDelay` needs only the curve.\n */\nexport interface BackoffCurve {\n /** Delay for attempt 0, in milliseconds, before any jitter or cap. */\n readonly baseMs: number;\n /** Geometric growth factor. `2` doubles the delay each attempt. */\n readonly factor: number;\n /** Hard ceiling, in milliseconds. The capped exponential never exceeds this. */\n readonly capMs: number;\n /** Jitter strategy applied to the capped exponential delay. */\n readonly jitter: Jitter;\n}\n\n/**\n * Bound by ATTEMPT COUNT — the original bound, and the right one when each\n * attempt costs about the same and the caller is the outermost ladder.\n *\n * `maxElapsedMs: never` is what keeps this arm honest: a count-bounded policy\n * is *declared* to have no outage budget, so it satisfies the clock-free\n * `shouldRetry` overload. Add `maxElapsedMs` and it becomes a\n * {@link DurationRetryPolicy}, which no longer does.\n */\nexport interface CountBound {\n /**\n * Maximum number of attempts. `shouldRetry` allows attempts `0 .. maxAttempts-1`,\n * so `maxAttempts: 3` means \"try once, then retry at most twice\" (attempts 0, 1, 2).\n */\n readonly maxAttempts: number;\n readonly maxElapsedMs?: never;\n readonly unbounded?: never;\n}\n\n/**\n * Bound by WALL-CLOCK OUTAGE DURATION, optionally also by count.\n *\n * `maxElapsedMs` is measured from the FIRST failure of the current streak\n * ({@link TimedRetryState.firstFailureAtMs}), not from the last attempt: the\n * question it answers is \"how long has the far side been unreachable\", which is\n * the question a nesting caller's own patience is denominated in. Derive it\n * from the peer's give-up window rather than restating a guess — two sides\n * holding different answers to one ordered fact is the defect this bound\n * exists to prevent.\n *\n * `maxAttempts` stays available here, and when both are present retry continues\n * only while BOTH still permit it (see {@link shouldRetry}). Adding a count to a\n * duration budget is a belt-and-braces guard against a pathologically fast\n * failure loop, not a substitute for the duration.\n */\nexport interface DurationBound {\n /**\n * Outage budget in wall-clock milliseconds. `shouldRetry` permits another\n * attempt while `nowMs - firstFailureAtMs < maxElapsedMs`, however many\n * attempts that takes.\n */\n readonly maxElapsedMs: number;\n /** Optional secondary count bound; omitted means \"no count bound\". */\n readonly maxAttempts?: number;\n readonly unbounded?: never;\n}\n\n/**\n * No bound at all — retry forever. Deliberately verbose and deliberately the\n * only way to say it: {@link RetryBudget} has no \"neither bound declared\" arm,\n * so an infinite retry loop is something a policy author must write down, never\n * something a forgotten field produces.\n */\nexport interface Unbounded {\n readonly unbounded: true;\n readonly maxAttempts?: never;\n readonly maxElapsedMs?: never;\n}\n\n/**\n * The terminal bound a policy declares: a count, a duration (optionally with a\n * count), or an explicit opt-in to neither. Exactly one arm applies, and there\n * is no arm for \"I forgot\".\n */\nexport type RetryBudget = CountBound | DurationBound | Unbounded;\n\n/**\n * A retry policy is pure configuration — the knobs of the backoff curve plus\n * the bound that ends the retrying. It carries no mutable state; `RetryState`\n * does. One policy is shared across all the attempts of one logical operation\n * (often a module-scope const).\n *\n * `RetryPolicy` is the COUNT-bounded shape, unchanged since the module's first\n * release: every existing `{ baseMs, factor, capMs, maxAttempts, jitter }`\n * literal still satisfies it, `policy.maxAttempts` is still a `number` (not\n * `number | undefined`), and `shouldRetry(state, policy)` still takes no clock.\n * Reach for {@link DurationRetryPolicy} when the bound should be temporal, or\n * {@link AnyRetryPolicy} when writing code generic over the bound.\n */\nexport type RetryPolicy = BackoffCurve & CountBound;\n\n/** A curve bounded by wall-clock outage duration (and optionally by count). */\nexport type DurationRetryPolicy = BackoffCurve & DurationBound;\n\n/** A curve with the explicit opt-in to unbounded retry. */\nexport type UnboundedRetryPolicy = BackoffCurve & Unbounded;\n\n/**\n * Any well-formed policy. The parameter type for code that must work whatever\n * bound the caller declared — `shouldRetry`'s implementation, wrappers, and\n * consumers that take a policy from configuration.\n */\nexport type AnyRetryPolicy = BackoffCurve & RetryBudget;\n\n/**\n * Sensible defaults: 100ms base, doubling, capped at 30s, up to 5 attempts,\n * full jitter (the herd-avoidance default). Tuned for network calls; override\n * per call site rather than mutating this shared const.\n */\nexport const defaultRetryPolicy: RetryPolicy = {\n baseMs: 100,\n factor: 2,\n capMs: 30_000,\n maxAttempts: 5,\n jitter: \"full\",\n};\n\n/**\n * Compute the delay for a given 0-based `attempt` under `policy`.\n *\n * PURE. The exponential is `baseMs * factor ** attempt`, clamped to `capMs`,\n * then jittered. `rng` is injected so the result is deterministic in tests —\n * pass an `rng` returning a fixed value to pin the jitter; the production\n * default reads `Math.random` only when the caller omits it (at the effect\n * boundary, never inside a reducer).\n *\n * `rng` is an {@link Rng} — a `[0, 1)` generator branded at construction via\n * {@link asRng}, so a raw `() => 5` can't be passed here. The returned delay\n * is always in `[0, capMs]` regardless of jitter strategy:\n * - `\"none\"` → exactly the capped exponential `d`.\n * - `\"full\"` → `rng() * d`, uniform in `[0, d]`.\n * - `\"equal\"` → `d/2 + rng() * d/2`, uniform in `[d/2, d]`.\n *\n * `attempt` is treated as `>= 0`; a negative attempt is clamped to 0 so a\n * caller's off-by-one never produces a fractional exponent or a delay below\n * `baseMs`. `capMs` is also the floor of the clamp, so a misconfigured\n * `capMs < baseMs` still yields a delay within `[0, capMs]`.\n */\nexport function backoffDelay(\n attempt: number,\n policy: BackoffCurve,\n rng: Rng = defaultRng,\n): number {\n // Clamp the attempt to >= 0 so factor ** attempt is never a fractional /\n // negative exponent. The exponential can overflow to Infinity for large\n // attempts; Math.min with capMs collapses that back to the ceiling.\n const safeAttempt = attempt < 0 ? 0 : attempt;\n const exponential = policy.baseMs * policy.factor ** safeAttempt;\n // `factor ** attempt` overflows to Infinity for large attempts. With\n // `baseMs > 0` that's `Infinity`, which Math.min collapses back to `capMs`.\n // With `baseMs === 0` it's `0 * Infinity === NaN`; a zero base means zero\n // delay, so treat NaN as 0 (otherwise NaN poisons the comparison and breaks\n // the `0 <= delay <= capMs` postcondition).\n const d = Number.isNaN(exponential) ? 0 : Math.min(policy.capMs, exponential);\n\n switch (policy.jitter) {\n case \"none\":\n return d;\n case \"full\":\n // Uniform in [0, d]. Math.min re-pins the upper bound: at subnormal /\n // extreme-precision `d`, `rng() * d` can round one ULP above `d`, which\n // would breach the documented `delay <= capMs` postcondition. Clamping to\n // `d` (already <= capMs) makes the invariant hold by construction.\n return Math.min(d, rng() * d);\n case \"equal\":\n // Fixed half + jittered half → uniform in [d/2, d]. Same FP-rounding clamp\n // as \"full\": `d/2 + rng()*(d/2)` can round above `d` for subnormal `d`.\n return Math.min(d, d / 2 + rng() * (d / 2));\n }\n}\n\n/**\n * Per-operation retry state: how many attempts have failed so far, and the most\n * recent error. `lastError` is `unknown` because the failure can be anything a\n * boundary surfaces (an `Error`, a tagged result, a string) — this module never\n * inspects it, it only carries it forward for the consumer's reducer to read.\n */\nexport interface RetryState {\n /** Count of failures recorded so far. Also the 0-based index of the next attempt. */\n readonly attempt: number;\n /** The error from the most recent failure, if any. Carried, never interpreted. */\n readonly lastError?: unknown;\n}\n\n/**\n * A retry state that also knows WHEN its failure streak began — the origin a\n * duration bound is measured from. Minted by passing the observation instant to\n * {@link recordFailure}; there is no other way to obtain one, so a streak with\n * an outage budget but no origin is not a value that can be written down.\n *\n * The origin is the FIRST failure of the streak and is preserved across\n * subsequent failures. A success resets the streak the way it always did — by\n * returning to `initRetry()` — which drops the origin with it, so an operation\n * that fails occasionally never accumulates outage and pays nothing for a wide\n * `maxElapsedMs`; only an unbroken run of failures grows toward the bound.\n */\nexport interface TimedRetryState extends RetryState {\n /** When the streak's first failure was observed (caller's clock, epoch ms). */\n readonly firstFailureAtMs: number;\n}\n\n/** The starting state: zero attempts, no error yet, no streak. */\nexport function initRetry(): RetryState {\n return { attempt: 0 };\n}\n\n/**\n * Record a failed attempt. PURE — returns a *new* state with `attempt`\n * incremented and `lastError` set; the input `state` is never mutated.\n *\n * `atMs` is the instant the failure was OBSERVED, supplied by the caller (the\n * `interpret` handler owns the clock; this module never reads one — invariant\n * 2). Passing it starts the streak clock on the first failure and preserves the\n * original origin on every failure after it, yielding a {@link TimedRetryState}\n * that a duration-bounded policy can be evaluated against. Omit it and the\n * count-bounded behaviour is bit-for-bit what it always was.\n */\nexport function recordFailure(\n state: TimedRetryState,\n error: unknown,\n atMs?: number,\n): TimedRetryState;\nexport function recordFailure(\n state: RetryState,\n error: unknown,\n atMs: number,\n): TimedRetryState;\nexport function recordFailure(state: RetryState, error: unknown): RetryState;\nexport function recordFailure(\n state: RetryState & { readonly firstFailureAtMs?: number },\n error: unknown,\n atMs?: number,\n): RetryState | TimedRetryState {\n const attempt = state.attempt + 1;\n // The streak's origin is set once, by whichever failure started it.\n const firstFailureAtMs = state.firstFailureAtMs ?? atMs;\n return firstFailureAtMs === undefined\n ? { attempt, lastError: error }\n : { attempt, lastError: error, firstFailureAtMs };\n}\n\n/**\n * How long the current failure streak has lasted, in milliseconds. PURE.\n *\n * The honest number for a give-up log — a count of attempts says nothing about\n * how much patience it actually spent. Clamped at 0 so a caller's clock going\n * backwards (NTP step, a rehydrated state whose origin is from a different\n * host) reads as \"no outage yet\" rather than a negative duration.\n */\nexport function retryElapsedMs(state: TimedRetryState, nowMs: number): number {\n const elapsed = nowMs - state.firstFailureAtMs;\n return elapsed > 0 ? elapsed : 0;\n}\n\n/**\n * Whether another attempt is permitted under `policy`. PURE.\n *\n * Every bound the policy DECLARES must still permit the attempt; a bound it\n * does not declare is not consulted. Concretely:\n *\n * - `maxAttempts` — `attempt` is the number of failures recorded so far (= the\n * index of the next attempt); we may retry while that index is below\n * `maxAttempts`, i.e. attempts `0 .. maxAttempts-1` are allowed and the\n * `maxAttempts`-th is refused. Unchanged.\n * - `maxElapsedMs` — we may retry while `nowMs - firstFailureAtMs` is below\n * the budget, *however many attempts that takes*. A streak's first failure\n * measures a zero-length outage and is therefore always retried, unless the\n * budget is itself `0` (the \"no patience\" setting).\n * - `unbounded: true` — always permitted.\n *\n * The overloads are the wiring contract. A count-bounded or explicitly\n * unbounded policy needs no clock, so the two-argument call every existing\n * caller already writes keeps type-checking. A policy carrying `maxElapsedMs`\n * matches only the second overload, which demands a {@link TimedRetryState} and\n * a `nowMs` — so declaring an outage budget and then never feeding it a clock\n * fails to compile instead of silently producing a bound that never fires.\n */\nexport function shouldRetry(\n state: RetryState,\n policy: BackoffCurve & (CountBound | Unbounded),\n nowMs?: number,\n): boolean;\nexport function shouldRetry(\n state: TimedRetryState,\n policy: AnyRetryPolicy,\n nowMs: number,\n): boolean;\nexport function shouldRetry(\n state: RetryState & { readonly firstFailureAtMs?: number },\n policy: AnyRetryPolicy,\n nowMs?: number,\n): boolean {\n if (policy.maxAttempts !== undefined && state.attempt >= policy.maxAttempts) {\n return false;\n }\n if (policy.maxElapsedMs === undefined) return true;\n // Reachable only from JS callers or an `as`-cast: the overloads make the\n // origin and the clock mandatory whenever `maxElapsedMs` is declared. Fail to\n // the visibly-broken side — refuse the retry — rather than treating an\n // unmeasurable outage as \"still inside the budget\" and retrying forever.\n if (state.firstFailureAtMs === undefined || nowMs === undefined) return false;\n return nowMs - state.firstFailureAtMs < policy.maxElapsedMs;\n}\n\n/**\n * The delay to wait before the next attempt, given the current state. PURE.\n *\n * Bridges `RetryState` → `backoffDelay` by using `state.attempt` as the 0-based\n * attempt index, so the delay grows with each recorded failure. `rng` is\n * injected for determinism, same contract as `backoffDelay`.\n */\nexport function nextDelayMs(\n state: RetryState,\n policy: BackoffCurve,\n rng: Rng = defaultRng,\n): number {\n return backoffDelay(state.attempt, policy, rng);\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cache/index.ts"],"names":[],"mappings":";;;AAuEO,SAAS,SAAA,GAA4B;AAC1C,EAAA,OAAO,EAAE,OAAA,EAAS,EAAC,EAAE;AACvB;AAQA,SAAS,SAAA,CAAa,OAAsB,KAAA,EAAwB;AAClE,EAAA,OAAO,SAAS,KAAA,CAAM,WAAA;AACxB;AAQO,SAAS,GAAA,CACd,KAAA,EACA,GAAA,EACA,KAAA,EACe;AACf,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA;AAC/B,EAAA,IAAI,UAAU,MAAA,IAAa,SAAA,CAAU,KAAA,EAAO,KAAK,GAAG,OAAO,MAAA;AAC3D,EAAA,OAAO,KAAA,CAAM,KAAA;AACf;AAMO,SAAS,GAAA,CACd,KAAA,EACA,GAAA,EACA,KAAA,EACS;AACT,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA;AAC/B,EAAA,IAAI,KAAA,KAAU,QAAW,OAAO,KAAA;AAChC,EAAA,OAAO,CAAC,SAAA,CAAU,KAAA,EAAO,KAAK,CAAA;AAChC;AAQO,SAAS,GAAA,CACd,KAAA,EACA,GAAA,EACA,KAAA,EACA,OACA,KAAA,EACa;AACb,EAAA,MAAM,WAAA,GAA6C,EAAE,GAAG,KAAA,CAAM,OAAA,EAAQ;AACtE,EAAA,WAAA,CAAY,GAAG,CAAA,GAAI,EAAE,KAAA,EAAO,WAAA,EAAa,QAAQ,KAAA,EAAM;AACvD,EAAA,OAAO,EAAE,GAAG,KAAA,EAAO,OAAA,EAAS,WAAA,EAAY;AAC1C;AAOO,SAAS,MAAA,CAAU,OAAoB,GAAA,EAA0B;AACtE,EAAA,MAAM,WAAA,GAA6C,EAAE,GAAG,KAAA,CAAM,OAAA,EAAQ;AACtE,EAAA,OAAO,YAAY,GAAG,CAAA;AACtB,EAAA,OAAO,EAAE,GAAG,KAAA,EAAO,OAAA,EAAS,WAAA,EAAY;AAC1C;AASO,SAAS,YAAA,CACd,OACA,KAAA,EACa;AACb,EAAA,MAAM,cAA6C,EAAC;AACpD,EAAA,KAAA,MAAW,CAAC,KAAK,KAAK,CAAA,IAAK,OAAO,OAAA,CAAQ,KAAA,CAAM,OAAO,CAAA,EAAG;AACxD,IAAA,IAAI,SAAA,CAAU,KAAA,EAAO,KAAK,CAAA,EAAG;AAC7B,IAAA,WAAA,CAAY,GAAG,CAAA,GAAI,KAAA;AAAA,EACrB;AACA,EAAA,OAAO,EAAE,GAAG,KAAA,EAAO,OAAA,EAAS,WAAA,EAAY;AAC1C;AAmCO,SAAS,cAAc,EAAA,EAA2B;AACvD,EAAA,OAAO,EAAE,IAAA,EAAM,aAAA,EAAe,EAAA,EAAG;AACnC;AAqBO,SAAS,gBAAA,CACd,IACA,OAAA,EACkB;AAClB,EAAA,OAAO;AAAA,IACL,EAAA;AAAA,IACA,IAAA,EAAM,OAAA;AAAA,IACN,UAAA,EAAY;AAAA,GACd;AACF;AAcO,IAAM,yBAIT,YAAA,CAAa,CAAC,QAAQ,aAAA,CAAc,GAAA,CAAI,EAAE,CAAC","file":"chunk-RH5EFRVJ.js","sourcesContent":["/**\n * @demlik/tea/cache — a TTL cache as pure state, PLUS a periodic eviction Sub.\n *\n * Same pure-state-ops shape as `@demlik/tea/idempotency` and\n * `@demlik/tea/work-queue`: a state type plus pure transition functions. Host-\n * agnostic by construction — no `Date.now()`, no timers, no I/O inside the ops.\n * The caller injects the clock (`nowMs`) and folds the result through its own\n * reducer, keeping the store inside TEA's invariant 2 (transitions are pure:\n * same inputs → same outputs) so a consumer can exercise it with `replay`\n * without a fake clock.\n *\n * Every op returns a NEW cache value and never mutates its input (invariant 1 —\n * state is a value, not a place). `get` / `has` are read-shaped; `set` /\n * `remove` / `evictExpired` return a new cache.\n *\n * **Distinct from `/idempotency`.** Both are TTL-bounded key maps, but they\n * answer different questions and so carry different shapes:\n *\n * - `/idempotency` is DEDUPE-focused — \"have I already processed this key?\"\n * TTL is a single store-level policy (`ttlMs`), each entry stores the\n * `atMs` it was remembered at, and expiry is derived (`now - atMs >=\n * ttlMs`). It carries an `order` ledger + `capacity` because the use case\n * (webhook receivers, retried messages) needs bounded LRU eviction.\n * - `/cache` is RETRIEVAL-focused — \"what is the value for this key right\n * now?\" TTL is set PER ENTRY at write time: `set(key, value, nowMs,\n * ttlMs)` bakes the absolute `expiresAtMs = nowMs + ttlMs` into the entry.\n * Different keys can carry different lifetimes (a hot key cached for 1s, a\n * cold one for 1h) — the dimension idempotency's single store-level\n * `ttlMs` cannot express. No `order`, no `capacity`: a cache bounds itself\n * by TTL + periodic eviction, not by LRU count.\n *\n * The eviction Sub is the \"eviction is a Msg\" half: a `setInterval`-shaped Sub\n * (composed directly on `@demlik/tea/subs`' `fromInterval`) that periodically\n * dispatches a `cache_evict` Msg the consumer's reducer handles by calling\n * `evictExpired`. Time still never enters the reducer — the Sub fires the Msg,\n * the reducer reads `nowMs` off the Msg (the consumer stamps it at the Sub\n * boundary), and `evictExpired` does the pure drop.\n *\n * NOT exported from the package root — reached via the `@demlik/tea/cache`\n * subpath, same one-shape-per-package rule as `work-queue`, `idempotency`, and\n * `rate-limit`.\n */\n\nimport type { Sub } from \"../index\";\nimport { fromInterval } from \"../subs\";\nimport type { SubscribeHandler } from \"../subs/types\";\n\n/**\n * A cached entry: the `value` and the absolute clock reading `expiresAtMs` it\n * expires at. Unlike `/idempotency`'s entry (which stores `atMs` and derives\n * expiry from a store-level `ttlMs`), the expiry instant is baked in at write\n * time — so per-entry TTLs are a first-class capability. Both fields are plain\n * data so the whole cache is trivially JSON-serializable into a consumer's\n * `Store<S>`.\n */\nexport interface CacheEntry<V> {\n readonly value: V;\n readonly expiresAtMs: number;\n}\n\n/**\n * The cache: a map of `key → entry`. No `order` ledger and no `capacity` (cf.\n * `/idempotency`) — a TTL cache bounds itself by expiry + periodic eviction,\n * not by LRU count. Each entry carries its own `expiresAtMs`, so the cache\n * needs no store-level TTL policy field.\n */\nexport interface TtlCache<V> {\n readonly entries: Readonly<Record<string, CacheEntry<V>>>;\n}\n\n/** Create an empty cache. */\nexport function initCache<V>(): TtlCache<V> {\n return { entries: {} };\n}\n\n/**\n * Whether `entry` is expired at `nowMs`. Half-open: an entry whose\n * `expiresAtMs` exactly equals `nowMs` is expired (`nowMs >= expiresAtMs`),\n * matching `/idempotency` and `/rate-limit`'s window-edge cutoff — a value\n * cached at `t` with a `ttlMs` lifetime is gone at exactly `t + ttlMs`.\n */\nfunction isExpired<V>(entry: CacheEntry<V>, nowMs: number): boolean {\n return nowMs >= entry.expiresAtMs;\n}\n\n/**\n * The cached `value` for `key` iff present AND unexpired at `nowMs`, else\n * `undefined`. An expired entry reads as absent even though it is physically\n * still in `entries` until the next `evictExpired` (or an overwriting `set`)\n * drops it. PURE — reads only, never mutates.\n */\nexport function get<V>(\n cache: TtlCache<V>,\n key: string,\n nowMs: number,\n): V | undefined {\n const entry = cache.entries[key];\n if (entry === undefined || isExpired(entry, nowMs)) return undefined;\n return entry.value;\n}\n\n/**\n * True iff `key` is present AND unexpired at `nowMs`. The boolean sibling of\n * `get` for callers that only need presence, not the value. PURE.\n */\nexport function has<V>(\n cache: TtlCache<V>,\n key: string,\n nowMs: number,\n): boolean {\n const entry = cache.entries[key];\n if (entry === undefined) return false;\n return !isExpired(entry, nowMs);\n}\n\n/**\n * Write `key → value` with an absolute expiry of `nowMs + ttlMs`. Re-`set`ting\n * an existing key overwrites both the value and the expiry — the TTL clock\n * restarts from the new `nowMs`. PURE — returns a new cache; the input is\n * untouched (a fresh shallow copy of `entries`).\n */\nexport function set<V>(\n cache: TtlCache<V>,\n key: string,\n value: V,\n nowMs: number,\n ttlMs: number,\n): TtlCache<V> {\n const nextEntries: Record<string, CacheEntry<V>> = { ...cache.entries };\n nextEntries[key] = { value, expiresAtMs: nowMs + ttlMs };\n return { ...cache, entries: nextEntries };\n}\n\n/**\n * Drop `key` regardless of expiry. PURE — returns a new cache; the input is\n * untouched. Removing an absent key returns an equivalent cache (the shallow\n * copy still happens, but no entry is dropped).\n */\nexport function remove<V>(cache: TtlCache<V>, key: string): TtlCache<V> {\n const nextEntries: Record<string, CacheEntry<V>> = { ...cache.entries };\n delete nextEntries[key];\n return { ...cache, entries: nextEntries };\n}\n\n/**\n * Physically drop every entry expired at `nowMs` (`nowMs >= expiresAtMs`).\n * Returns a NEW cache with the survivors. This is the pure work the eviction\n * Sub's `cache_evict` Msg triggers — `get` / `has` already treat expired\n * entries as absent, so eviction is purely a memory-reclaim pass, not a\n * correctness one. PURE — the input cache is never mutated.\n */\nexport function evictExpired<V>(\n cache: TtlCache<V>,\n nowMs: number,\n): TtlCache<V> {\n const nextEntries: Record<string, CacheEntry<V>> = {};\n for (const [key, entry] of Object.entries(cache.entries)) {\n if (isExpired(entry, nowMs)) continue;\n nextEntries[key] = entry;\n }\n return { ...cache, entries: nextEntries };\n}\n\n// ---------------------------------------------------------------------------\n// Eviction Sub — \"eviction is a Msg\".\n//\n// A periodic timer Sub that dispatches `cache_evict` so the consumer's reducer\n// can call `evictExpired`. It is `fromInterval` specialized to a fixed Msg\n// shape — the factory carries the interval ON the Sub (per the substrate's\n// reconcile-by-id contract) and dispatches a `cache_evict` Msg tagged with the\n// Sub's own `id`, so a consumer running several caches can route each tick to\n// the right cache by `msg.id`.\n//\n// Mirrors `fromInterval` exactly (factory + SubscribeHandler cell + exported\n// Msg constructor); it does NOT reimplement the timer lifecycle — it composes\n// directly on `fromInterval`, which owns `setInterval` / `clearInterval`. That\n// keeps the timer-handle bookkeeping (and its cleanup) in one place and this\n// module purely declarative.\n//\n// Strengthens invariant 4 (external time is a Sub, reconciled by id) and\n// invariant 9 (named, small Sub surface composed on the shared factory).\n// ---------------------------------------------------------------------------\n\n/**\n * The Msg the eviction Sub dispatches each tick. `id` is the Sub's own id so a\n * consumer with multiple caches can route the tick to the matching cache. The\n * reducer reads `Date.now()`-free time by stamping `nowMs` itself at the Sub\n * boundary if needed; the canonical reducer cell is\n * `cache_evict: (s) => [{ ...s, cache: evictExpired(s.cache, nowMs) }, []]`.\n */\nexport interface CacheEvictMsg {\n readonly type: \"cache_evict\";\n readonly id: string;\n}\n\n/** Construct a `cache_evict` Msg for the cache identified by `id`. */\nexport function cacheEvictMsg(id: string): CacheEvictMsg {\n return { type: \"cache_evict\", id };\n}\n\n/**\n * The Sub shape the eviction factory installs: a `setInterval`-shaped Sub\n * carrying its tick period (`intervalMs`) per the substrate's\n * reconcile-by-id-with-data-on-the-Sub contract. `type: \"cache\"` is the\n * lowercase source-noun Sub convention (canon §2.5 / naming-style).\n */\nexport type CacheEvictionSub = Sub<\"cache\"> & { readonly intervalMs: number };\n\n/**\n * Declare an eviction Sub for the cache identified by `id`, ticking every\n * `everyMs`. Drop the result into `subscriptions(state)`:\n *\n * subscriptions: (s) => [cacheEvictionSub(\"session-cache\", 30_000)]\n *\n * Same id ⇒ same running interval across transitions (no reconcile churn);\n * changing `everyMs` alone will NOT re-arm the timer (the substrate leaves a\n * same-id Sub running) — change the `id` too if the period must change. This\n * mirrors `fromInterval`'s data-on-the-Sub tradeoff exactly.\n */\nexport function cacheEvictionSub(\n id: string,\n everyMs: number,\n): CacheEvictionSub {\n return {\n id: id as CacheEvictionSub[\"id\"],\n type: \"cache\",\n intervalMs: everyMs,\n };\n}\n\n/**\n * The `subscribe` cell for the eviction Sub. Assign it to\n * `machine.subscribe.cache`:\n *\n * subscribe: { cache: cacheEvictionSubscribe }\n *\n * Composed directly on `fromInterval` — it owns the `setInterval` /\n * `clearInterval` lifecycle; this cell only pins the Msg the tick dispatches\n * (`cacheEvictMsg(sub.id)`). The cleanup `fromInterval` returns stops the\n * interval when the Sub is reconciled out (the cache's state exits the phase\n * that declared it).\n */\nexport const cacheEvictionSubscribe: SubscribeHandler<\n CacheEvictionSub,\n CacheEvictMsg,\n unknown\n> = fromInterval((sub) => cacheEvictMsg(sub.id));\n"]}
|
package/dist/chunk-S2EGRPCM.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
// src/rate-limit/index.ts
|
|
2
|
-
function initBucket(capacity, refillPerSec, nowMs) {
|
|
3
|
-
return {
|
|
4
|
-
tokens: capacity,
|
|
5
|
-
capacity,
|
|
6
|
-
refillPerSec,
|
|
7
|
-
lastRefillMs: nowMs
|
|
8
|
-
};
|
|
9
|
-
}
|
|
10
|
-
function refill(bucket, nowMs) {
|
|
11
|
-
const elapsedSec = Math.max(0, nowMs - bucket.lastRefillMs) / 1e3;
|
|
12
|
-
const replenished = bucket.tokens + elapsedSec * bucket.refillPerSec;
|
|
13
|
-
return {
|
|
14
|
-
...bucket,
|
|
15
|
-
tokens: Math.min(bucket.capacity, replenished),
|
|
16
|
-
lastRefillMs: nowMs
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
function tryConsume(bucket, nowMs, n = 1) {
|
|
20
|
-
const refilled = refill(bucket, nowMs);
|
|
21
|
-
if (refilled.tokens >= n) {
|
|
22
|
-
return [{ ...refilled, tokens: refilled.tokens - n }, true];
|
|
23
|
-
}
|
|
24
|
-
return [refilled, false];
|
|
25
|
-
}
|
|
26
|
-
function initWindow(windowMs, limit) {
|
|
27
|
-
return { hits: [], windowMs, limit };
|
|
28
|
-
}
|
|
29
|
-
function prune(window, nowMs) {
|
|
30
|
-
const cutoff = nowMs - window.windowMs;
|
|
31
|
-
return window.hits.filter((t) => t > cutoff);
|
|
32
|
-
}
|
|
33
|
-
function record(window, nowMs) {
|
|
34
|
-
const pruned = prune(window, nowMs);
|
|
35
|
-
if (pruned.length < window.limit) {
|
|
36
|
-
return [{ ...window, hits: [...pruned, nowMs] }, true];
|
|
37
|
-
}
|
|
38
|
-
return [{ ...window, hits: pruned }, false];
|
|
39
|
-
}
|
|
40
|
-
function remaining(window, nowMs) {
|
|
41
|
-
const live = prune(window, nowMs).length;
|
|
42
|
-
return Math.max(0, window.limit - live);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export { initBucket, initWindow, record, refill, remaining, tryConsume };
|
|
46
|
-
//# sourceMappingURL=chunk-S2EGRPCM.js.map
|
|
47
|
-
//# sourceMappingURL=chunk-S2EGRPCM.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/rate-limit/index.ts"],"names":[],"mappings":";AAsDO,SAAS,UAAA,CACd,QAAA,EACA,YAAA,EACA,KAAA,EACa;AACb,EAAA,OAAO;AAAA,IACL,MAAA,EAAQ,QAAA;AAAA,IACR,QAAA;AAAA,IACA,YAAA;AAAA,IACA,YAAA,EAAc;AAAA,GAChB;AACF;AAcO,SAAS,MAAA,CAAO,QAAqB,KAAA,EAA4B;AACtE,EAAA,MAAM,aAAa,IAAA,CAAK,GAAA,CAAI,GAAG,KAAA,GAAQ,MAAA,CAAO,YAAY,CAAA,GAAI,GAAA;AAC9D,EAAA,MAAM,WAAA,GAAc,MAAA,CAAO,MAAA,GAAS,UAAA,GAAa,MAAA,CAAO,YAAA;AACxD,EAAA,OAAO;AAAA,IACL,GAAG,MAAA;AAAA,IACH,MAAA,EAAQ,IAAA,CAAK,GAAA,CAAI,MAAA,CAAO,UAAU,WAAW,CAAA;AAAA,IAC7C,YAAA,EAAc;AAAA,GAChB;AACF;AAeO,SAAS,UAAA,CACd,MAAA,EACA,KAAA,EACA,CAAA,GAAI,CAAA,EAC6B;AACjC,EAAA,MAAM,QAAA,GAAW,MAAA,CAAO,MAAA,EAAQ,KAAK,CAAA;AACrC,EAAA,IAAI,QAAA,CAAS,UAAU,CAAA,EAAG;AACxB,IAAA,OAAO,CAAC,EAAE,GAAG,QAAA,EAAU,QAAQ,QAAA,CAAS,MAAA,GAAS,CAAA,EAAE,EAAG,IAAI,CAAA;AAAA,EAC5D;AACA,EAAA,OAAO,CAAC,UAAU,KAAK,CAAA;AACzB;AAqBO,SAAS,UAAA,CAAW,UAAkB,KAAA,EAA8B;AACzE,EAAA,OAAO,EAAE,IAAA,EAAM,EAAC,EAAG,UAAU,KAAA,EAAM;AACrC;AAUA,SAAS,KAAA,CAAM,QAAuB,KAAA,EAAkC;AACtE,EAAA,MAAM,MAAA,GAAS,QAAQ,MAAA,CAAO,QAAA;AAC9B,EAAA,OAAO,OAAO,IAAA,CAAK,MAAA,CAAO,CAAC,CAAA,KAAM,IAAI,MAAM,CAAA;AAC7C;AAgBO,SAAS,MAAA,CACd,QACA,KAAA,EACmC;AACnC,EAAA,MAAM,MAAA,GAAS,KAAA,CAAM,MAAA,EAAQ,KAAK,CAAA;AAClC,EAAA,IAAI,MAAA,CAAO,MAAA,GAAS,MAAA,CAAO,KAAA,EAAO;AAChC,IAAA,OAAO,CAAC,EAAE,GAAG,MAAA,EAAQ,IAAA,EAAM,CAAC,GAAG,MAAA,EAAQ,KAAK,CAAA,EAAE,EAAG,IAAI,CAAA;AAAA,EACvD;AACA,EAAA,OAAO,CAAC,EAAE,GAAG,QAAQ,IAAA,EAAM,MAAA,IAAU,KAAK,CAAA;AAC5C;AAQO,SAAS,SAAA,CAAU,QAAuB,KAAA,EAAuB;AACtE,EAAA,MAAM,IAAA,GAAO,KAAA,CAAM,MAAA,EAAQ,KAAK,CAAA,CAAE,MAAA;AAClC,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,MAAA,CAAO,QAAQ,IAAI,CAAA;AACxC","file":"chunk-S2EGRPCM.js","sourcesContent":["/**\n * @demlik/tea/rate-limit — two rate limiters as pure state + ops.\n *\n * Same shape as `@demlik/tea/work-queue`'s `ops.ts`: a state type plus pure\n * transition functions. Host-agnostic by construction — no `Date.now()`, no\n * timers, no I/O. The caller injects the clock (`nowMs`) and wires the result\n * into its own reducer / interpret. This keeps the limiters inside TEA's\n * invariant 2 (transitions are pure: same inputs → same outputs, no `Date.now`)\n * so a consumer can fold them through `update` and exercise them with `replay`\n * / the pbt fold without a fake clock.\n *\n * Every op returns a NEW state value and never mutates its input (invariant 1 —\n * state is a value, not a place). The `tryConsume` / `record` ops return a\n * `[next, ok]` tuple so the caller branches on the decision while threading the\n * advanced state forward; `refill` / `remaining` are read-shaped helpers.\n *\n * Two complementary algorithms, picked by the cost the caller wants to pay:\n *\n * - **Token bucket** — O(1) state (four numbers), smooths bursts against a\n * steady refill rate. Allows a burst up to `capacity`, then throttles to\n * `refillPerSec`. Cheapest; the default choice.\n * - **Sliding window log** — O(limit) state (one timestamp per in-window\n * hit), exact \"no more than `limit` events per `windowMs`\". Pay the memory\n * for precise window edges when a token bucket's continuous refill would\n * let through too many at a boundary.\n *\n * NOT exported from the package root — reached via the `@demlik/tea/rate-limit`\n * subpath, same one-shape-per-package rule as `work-queue` and `do`.\n */\n\n// ===========================================================================\n// Token bucket\n// ===========================================================================\n\n/**\n * A token bucket: `tokens` of `capacity` available now, replenished at\n * `refillPerSec` tokens per second. `lastRefillMs` is the clock reading at\n * which `tokens` was last computed — `refill` advances it. All four fields are\n * plain numbers so the whole bucket is trivially JSON-serializable into a\n * consumer's `Store<S>`.\n */\nexport interface TokenBucket {\n readonly tokens: number;\n readonly capacity: number;\n readonly refillPerSec: number;\n readonly lastRefillMs: number;\n}\n\n/**\n * Create a full bucket. Starts at `capacity` tokens (the conventional \"a fresh\n * limiter lets the first burst through\" behavior) and stamps `lastRefillMs`\n * with the caller's clock so the first `refill` measures elapsed time from\n * boot, not from epoch 0.\n */\nexport function initBucket(\n capacity: number,\n refillPerSec: number,\n nowMs: number,\n): TokenBucket {\n return {\n tokens: capacity,\n capacity,\n refillPerSec,\n lastRefillMs: nowMs,\n };\n}\n\n/**\n * Add the tokens accrued since `lastRefillMs`, clamped to `capacity`, and\n * advance `lastRefillMs` to `nowMs`. PURE — returns a new bucket; the input is\n * untouched.\n *\n * A `nowMs` at or before `lastRefillMs` (clock not advanced, or skewed\n * backwards) adds a non-positive amount; `Math.max(0, …)` floors the elapsed\n * term so a backwards clock can never *remove* tokens. `lastRefillMs` still\n * advances to `nowMs` so the bucket tracks the latest reading the caller\n * presented — the next forward step measures from there, not from a stale\n * future timestamp.\n */\nexport function refill(bucket: TokenBucket, nowMs: number): TokenBucket {\n const elapsedSec = Math.max(0, nowMs - bucket.lastRefillMs) / 1000;\n const replenished = bucket.tokens + elapsedSec * bucket.refillPerSec;\n return {\n ...bucket,\n tokens: Math.min(bucket.capacity, replenished),\n lastRefillMs: nowMs,\n };\n}\n\n/**\n * Refill against `nowMs`, then attempt to take `n` tokens (default 1).\n *\n * On success returns `[next, true]` with `n` subtracted; on failure returns\n * `[refilled, false]` — the refilled bucket (so the caller keeps the advanced\n * clock + accrued tokens) with no tokens spent. Branch on the boolean, thread\n * the bucket forward regardless:\n *\n * const [b1, ok] = tryConsume(bucket, nowMs);\n * return ok ? [{ ...state, bucket: b1 }, [doWork]] : [{ ...state, bucket: b1 }, []];\n *\n * PURE — input bucket is never mutated.\n */\nexport function tryConsume(\n bucket: TokenBucket,\n nowMs: number,\n n = 1,\n): readonly [TokenBucket, boolean] {\n const refilled = refill(bucket, nowMs);\n if (refilled.tokens >= n) {\n return [{ ...refilled, tokens: refilled.tokens - n }, true];\n }\n return [refilled, false];\n}\n\n// ===========================================================================\n// Sliding window log\n// ===========================================================================\n\n/**\n * A sliding-window log: the timestamps of every hit still inside the trailing\n * `windowMs`, capped at `limit` events per window. `hits` is `readonly` and\n * kept oldest-first (pruning slices the stale prefix off the front, recording\n * appends to the back), so the array is its own sorted index. Exact at the\n * window edge — unlike a token bucket's continuous refill, a hit leaves the\n * count the instant it ages past `windowMs`.\n */\nexport interface SlidingWindow {\n readonly hits: readonly number[];\n readonly windowMs: number;\n readonly limit: number;\n}\n\n/** Create an empty window. No hits recorded yet. */\nexport function initWindow(windowMs: number, limit: number): SlidingWindow {\n return { hits: [], windowMs, limit };\n}\n\n/**\n * The window-edge rule, in one place: keep only the hits still live at `nowMs`.\n * The cutoff is half-open — a hit exactly `windowMs` old is dropped (`> cutoff`,\n * not `>=`), so a hit recorded at `t` no longer counts at `t + windowMs` and the\n * window's capacity fully recovers `windowMs` after the last hit. `record` (the\n * writer) and `remaining` (the read helper) both prune through here so a change\n * to the edge semantics can't leave the reader disagreeing with the writer.\n */\nfunction prune(window: SlidingWindow, nowMs: number): readonly number[] {\n const cutoff = nowMs - window.windowMs;\n return window.hits.filter((t) => t > cutoff);\n}\n\n/**\n * Prune hits older than `nowMs - windowMs`, then record `nowMs` if there is\n * room. Returns `[next, true]` with the hit appended when `hits.length < limit`\n * after pruning, else `[pruned, false]` — the pruned window (capacity recovers\n * as old hits age out) with nothing appended.\n *\n * The cutoff is half-open (see {@link prune}): a hit exactly `windowMs` old is\n * pruned, so a hit recorded at `t` no longer counts at `t + windowMs`. That\n * makes the window's capacity fully recover `windowMs` after the last hit, the\n * behavior tests pin.\n *\n * PURE — input window is never mutated; `hits` is rebuilt by `filter` +\n * spread.\n */\nexport function record(\n window: SlidingWindow,\n nowMs: number,\n): readonly [SlidingWindow, boolean] {\n const pruned = prune(window, nowMs);\n if (pruned.length < window.limit) {\n return [{ ...window, hits: [...pruned, nowMs] }, true];\n }\n return [{ ...window, hits: pruned }, false];\n}\n\n/**\n * How many more hits `record` would accept at `nowMs` without blocking —\n * `limit` minus the live (post-prune) hit count, floored at 0. Read-only: it\n * prunes a local copy to answer but returns no new window. Use it to render a\n * \"N requests remaining\" affordance without committing a hit.\n */\nexport function remaining(window: SlidingWindow, nowMs: number): number {\n const live = prune(window, nowMs).length;\n return Math.max(0, window.limit - live);\n}\n"]}
|
package/dist/chunk-SINXE47Y.js
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
// src/paginator/index.ts
|
|
2
|
-
var defaultPaginatorPolicy = {
|
|
3
|
-
firstCursor: 0,
|
|
4
|
-
highWaterMark: 1e3
|
|
5
|
-
};
|
|
6
|
-
function initPaginator() {
|
|
7
|
-
return { phase: "idle", seen: 0, pages: 0 };
|
|
8
|
-
}
|
|
9
|
-
function start(state, policy) {
|
|
10
|
-
if (state.phase !== "idle") return state;
|
|
11
|
-
return {
|
|
12
|
-
phase: "fetching",
|
|
13
|
-
cursor: policy.firstCursor,
|
|
14
|
-
seen: state.seen,
|
|
15
|
-
pages: state.pages
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
function recordPage(state, count, next, policy) {
|
|
19
|
-
if (state.phase !== "fetching") return state;
|
|
20
|
-
const safeCount = Number.isFinite(count) ? Math.max(0, count) : 0;
|
|
21
|
-
const seen = state.seen + safeCount;
|
|
22
|
-
const pages = state.pages + 1;
|
|
23
|
-
if (next == null) {
|
|
24
|
-
return { phase: "done", seen, pages };
|
|
25
|
-
}
|
|
26
|
-
if (policy.highWaterMark > 0 && seen >= policy.highWaterMark) {
|
|
27
|
-
return { phase: "paused", cursor: next, seen, pages };
|
|
28
|
-
}
|
|
29
|
-
return { phase: "fetching", cursor: next, seen, pages };
|
|
30
|
-
}
|
|
31
|
-
function drain(state, n) {
|
|
32
|
-
if (n <= 0) return state;
|
|
33
|
-
const seen = Math.max(0, state.seen - n);
|
|
34
|
-
if (seen === state.seen) return state;
|
|
35
|
-
return { ...state, seen };
|
|
36
|
-
}
|
|
37
|
-
function resume(state, policy) {
|
|
38
|
-
if (state.phase !== "paused") return state;
|
|
39
|
-
if (state.seen < policy.highWaterMark) {
|
|
40
|
-
return {
|
|
41
|
-
phase: "fetching",
|
|
42
|
-
cursor: state.cursor,
|
|
43
|
-
seen: state.seen,
|
|
44
|
-
pages: state.pages
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
return state;
|
|
48
|
-
}
|
|
49
|
-
function isDone(state) {
|
|
50
|
-
return state.phase === "done";
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export { defaultPaginatorPolicy, drain, initPaginator, isDone, recordPage, resume, start };
|
|
54
|
-
//# sourceMappingURL=chunk-SINXE47Y.js.map
|
|
55
|
-
//# sourceMappingURL=chunk-SINXE47Y.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/paginator/index.ts"],"names":[],"mappings":";AAmJO,IAAM,sBAAA,GAAkD;AAAA,EAC7D,WAAA,EAAa,CAAA;AAAA,EACb,aAAA,EAAe;AACjB;AAqCO,SAAS,aAAA,GAAgD;AAC9D,EAAA,OAAO,EAAE,KAAA,EAAO,MAAA,EAAQ,IAAA,EAAM,CAAA,EAAG,OAAO,CAAA,EAAE;AAC5C;AAWO,SAAS,KAAA,CACd,OACA,MAAA,EACwB;AACxB,EAAA,IAAI,KAAA,CAAM,KAAA,KAAU,MAAA,EAAQ,OAAO,KAAA;AACnC,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,UAAA;AAAA,IACP,QAAQ,MAAA,CAAO,WAAA;AAAA,IACf,MAAM,KAAA,CAAM,IAAA;AAAA,IACZ,OAAO,KAAA,CAAM;AAAA,GACf;AACF;AAgCO,SAAS,UAAA,CACd,KAAA,EACA,KAAA,EACA,IAAA,EACA,MAAA,EACwB;AACxB,EAAA,IAAI,KAAA,CAAM,KAAA,KAAU,UAAA,EAAY,OAAO,KAAA;AAcvC,EAAA,MAAM,SAAA,GAAY,OAAO,QAAA,CAAS,KAAK,IAAI,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,KAAK,CAAA,GAAI,CAAA;AAChE,EAAA,MAAM,IAAA,GAAO,MAAM,IAAA,GAAO,SAAA;AAC1B,EAAA,MAAM,KAAA,GAAQ,MAAM,KAAA,GAAQ,CAAA;AAM5B,EAAA,IAAI,QAAQ,IAAA,EAAM;AAChB,IAAA,OAAO,EAAE,KAAA,EAAO,MAAA,EAAQ,IAAA,EAAM,KAAA,EAAM;AAAA,EACtC;AAKA,EAAA,IAAI,MAAA,CAAO,aAAA,GAAgB,CAAA,IAAK,IAAA,IAAQ,OAAO,aAAA,EAAe;AAC5D,IAAA,OAAO,EAAE,KAAA,EAAO,QAAA,EAAU,MAAA,EAAQ,IAAA,EAAM,MAAM,KAAA,EAAM;AAAA,EACtD;AACA,EAAA,OAAO,EAAE,KAAA,EAAO,UAAA,EAAY,MAAA,EAAQ,IAAA,EAAM,MAAM,KAAA,EAAM;AACxD;AAkBO,SAAS,KAAA,CACd,OACA,CAAA,EACwB;AACxB,EAAA,IAAI,CAAA,IAAK,GAAG,OAAO,KAAA;AACnB,EAAA,MAAM,OAAO,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,KAAA,CAAM,OAAO,CAAC,CAAA;AACvC,EAAA,IAAI,IAAA,KAAS,KAAA,CAAM,IAAA,EAAM,OAAO,KAAA;AAChC,EAAA,OAAO,EAAE,GAAG,KAAA,EAAO,IAAA,EAAK;AAC1B;AAoBO,SAAS,MAAA,CACd,OACA,MAAA,EACwB;AACxB,EAAA,IAAI,KAAA,CAAM,KAAA,KAAU,QAAA,EAAU,OAAO,KAAA;AACrC,EAAA,IAAI,KAAA,CAAM,IAAA,GAAO,MAAA,CAAO,aAAA,EAAe;AACrC,IAAA,OAAO;AAAA,MACL,KAAA,EAAO,UAAA;AAAA,MACP,QAAQ,KAAA,CAAM,MAAA;AAAA,MACd,MAAM,KAAA,CAAM,IAAA;AAAA,MACZ,OAAO,KAAA,CAAM;AAAA,KACf;AAAA,EACF;AACA,EAAA,OAAO,KAAA;AACT;AAQO,SAAS,OAAe,KAAA,EAAwC;AACrE,EAAA,OAAO,MAAM,KAAA,KAAU,MAAA;AACzB","file":"chunk-SINXE47Y.js","sourcesContent":["/**\n * @demlik/tea/paginator — the cursor/offset/page-token walk loop as pure\n * state + ops.\n *\n * Same shape as `@demlik/tea/retry-backoff`, `@demlik/tea/circuit-breaker`,\n * and `@demlik/tea/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 * NOT exported from the package root — reached via the `@demlik/tea/paginator`\n * subpath, 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"]}
|
package/dist/chunk-UF4MZSLT.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
// src/circuit-breaker/index.ts
|
|
2
|
-
var defaultCircuitPolicy = {
|
|
3
|
-
failureThreshold: 5,
|
|
4
|
-
cooldownMs: 3e4,
|
|
5
|
-
halfOpenMaxProbes: 1
|
|
6
|
-
};
|
|
7
|
-
function initCircuit() {
|
|
8
|
-
return { phase: "closed", failures: 0 };
|
|
9
|
-
}
|
|
10
|
-
function onSuccess(state, _policy) {
|
|
11
|
-
switch (state.phase) {
|
|
12
|
-
case "closed":
|
|
13
|
-
return state.failures === 0 ? state : { phase: "closed", failures: 0 };
|
|
14
|
-
case "half_open":
|
|
15
|
-
return { phase: "closed", failures: 0 };
|
|
16
|
-
case "open":
|
|
17
|
-
return state;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
function onFailure(state, policy, nowMs) {
|
|
21
|
-
switch (state.phase) {
|
|
22
|
-
case "closed": {
|
|
23
|
-
const failures = state.failures + 1;
|
|
24
|
-
return failures >= policy.failureThreshold ? { phase: "open", openedAtMs: nowMs } : { phase: "closed", failures };
|
|
25
|
-
}
|
|
26
|
-
case "half_open":
|
|
27
|
-
return { phase: "open", openedAtMs: nowMs };
|
|
28
|
-
case "open":
|
|
29
|
-
return state;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
function canPass(state, policy, nowMs) {
|
|
33
|
-
switch (state.phase) {
|
|
34
|
-
case "closed":
|
|
35
|
-
return [state, true];
|
|
36
|
-
case "open": {
|
|
37
|
-
const elapsed = nowMs - state.openedAtMs;
|
|
38
|
-
if (elapsed >= policy.cooldownMs) {
|
|
39
|
-
return [{ phase: "half_open", probes: 1 }, true];
|
|
40
|
-
}
|
|
41
|
-
return [state, false];
|
|
42
|
-
}
|
|
43
|
-
case "half_open":
|
|
44
|
-
if (state.probes < policy.halfOpenMaxProbes) {
|
|
45
|
-
return [{ phase: "half_open", probes: state.probes + 1 }, true];
|
|
46
|
-
}
|
|
47
|
-
return [state, false];
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export { canPass, defaultCircuitPolicy, initCircuit, onFailure, onSuccess };
|
|
52
|
-
//# sourceMappingURL=chunk-UF4MZSLT.js.map
|
|
53
|
-
//# sourceMappingURL=chunk-UF4MZSLT.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/circuit-breaker/index.ts"],"names":[],"mappings":";AAyHO,IAAM,oBAAA,GAAsC;AAAA,EACjD,gBAAA,EAAkB,CAAA;AAAA,EAClB,UAAA,EAAY,GAAA;AAAA,EACZ,iBAAA,EAAmB;AACrB;AAoBO,SAAS,WAAA,GAA4B;AAC1C,EAAA,OAAO,EAAE,KAAA,EAAO,QAAA,EAAU,QAAA,EAAU,CAAA,EAAE;AACxC;AAYO,SAAS,SAAA,CACd,OACA,OAAA,EACc;AACd,EAAA,QAAQ,MAAM,KAAA;AAAO,IACnB,KAAK,QAAA;AAGH,MAAA,OAAO,KAAA,CAAM,aAAa,CAAA,GAAI,KAAA,GAAQ,EAAE,KAAA,EAAO,QAAA,EAAU,UAAU,CAAA,EAAE;AAAA,IACvE,KAAK,WAAA;AACH,MAAA,OAAO,EAAE,KAAA,EAAO,QAAA,EAAU,QAAA,EAAU,CAAA,EAAE;AAAA,IACxC,KAAK,MAAA;AACH,MAAA,OAAO,KAAA;AAAA;AAEb;AAgBO,SAAS,SAAA,CACd,KAAA,EACA,MAAA,EACA,KAAA,EACc;AACd,EAAA,QAAQ,MAAM,KAAA;AAAO,IACnB,KAAK,QAAA,EAAU;AACb,MAAA,MAAM,QAAA,GAAW,MAAM,QAAA,GAAW,CAAA;AAGlC,MAAA,OAAO,QAAA,IAAY,MAAA,CAAO,gBAAA,GACtB,EAAE,KAAA,EAAO,MAAA,EAAQ,UAAA,EAAY,KAAA,EAAM,GACnC,EAAE,KAAA,EAAO,QAAA,EAAU,QAAA,EAAS;AAAA,IAClC;AAAA,IACA,KAAK,WAAA;AAEH,MAAA,OAAO,EAAE,KAAA,EAAO,MAAA,EAAQ,UAAA,EAAY,KAAA,EAAM;AAAA,IAC5C,KAAK,MAAA;AACH,MAAA,OAAO,KAAA;AAAA;AAEb;AA0BO,SAAS,OAAA,CACd,KAAA,EACA,MAAA,EACA,KAAA,EACkC;AAClC,EAAA,QAAQ,MAAM,KAAA;AAAO,IACnB,KAAK,QAAA;AACH,MAAA,OAAO,CAAC,OAAO,IAAI,CAAA;AAAA,IACrB,KAAK,MAAA,EAAQ;AACX,MAAA,MAAM,OAAA,GAAU,QAAQ,KAAA,CAAM,UAAA;AAG9B,MAAA,IAAI,OAAA,IAAW,OAAO,UAAA,EAAY;AAChC,QAAA,OAAO,CAAC,EAAE,KAAA,EAAO,aAAa,MAAA,EAAQ,CAAA,IAAK,IAAI,CAAA;AAAA,MACjD;AACA,MAAA,OAAO,CAAC,OAAO,KAAK,CAAA;AAAA,IACtB;AAAA,IACA,KAAK,WAAA;AACH,MAAA,IAAI,KAAA,CAAM,MAAA,GAAS,MAAA,CAAO,iBAAA,EAAmB;AAC3C,QAAA,OAAO,CAAC,EAAE,KAAA,EAAO,WAAA,EAAa,QAAQ,KAAA,CAAM,MAAA,GAAS,CAAA,EAAE,EAAG,IAAI,CAAA;AAAA,MAChE;AACA,MAAA,OAAO,CAAC,OAAO,KAAK,CAAA;AAAA;AAE1B","file":"chunk-UF4MZSLT.js","sourcesContent":["/**\n * @packageDocumentation\n * @demlik/tea/circuit-breaker — per-target failure tracking as pure state + ops.\n *\n * Same shape as `@demlik/tea/work-queue`'s `ops.ts`, `@demlik/tea/rate-limit`,\n * and `@demlik/tea/idempotency`: a state type plus pure transition functions.\n * Host-agnostic by construction — no `Date.now()`, no timers, no I/O. The\n * caller injects the clock (`nowMs`) and wires the result into its own reducer\n * / interpret. This keeps the breaker inside TEA's invariant 2 (transitions are\n * pure: same inputs → same outputs, no `Date.now`) so a consumer can fold it\n * through `update` and exercise it with `replay` / the pbt fold without a fake\n * clock.\n *\n * Every op returns a NEW state value and never mutates its input (invariant 1 —\n * state is a value, not a place). `canPass` returns a `[next, allowed]` tuple so\n * the caller branches on the decision while threading the advanced state forward\n * — same convention as `rate-limit`'s `tryConsume` / `record`. `onSuccess` /\n * `onFailure` are the outcome-recording transitions the caller folds in once the\n * guarded call resolves.\n *\n * ## The phase machine\n *\n * A circuit breaker guards a fallible downstream target (an HTTP backend, a DB,\n * a flaky tool call). Instead of hammering a target that is already failing, it\n * trips OPEN after enough failures and fast-fails subsequent calls for a\n * cooldown, then probes with a few trial calls before fully closing again.\n * Three phases, each carrying ONLY its own data so impossible states are\n * unrepresentable (pattern 11):\n *\n * - **closed** — the healthy steady state. Calls pass through. Carries\n * `failures`: the count of *consecutive* failures since the last success.\n * A success resets it to 0; the `failureThreshold`-th failure trips the\n * breaker OPEN. Only `closed` has a failure counter — `open` and\n * `half_open` don't, because in those phases the count is irrelevant\n * (the breaker has already tripped / is already probing).\n *\n * - **open** — the tripped state. All calls fast-fail (no downstream load).\n * Carries `openedAtMs`: the clock reading at which the breaker tripped.\n * `canPass` compares `nowMs - openedAtMs` against `cooldownMs` to decide\n * when to start probing. Only `open` carries a timestamp — neither other\n * phase is timing anything.\n *\n * - **half_open** — the probing state. A limited number of trial calls are\n * allowed through to test whether the target has recovered. Carries\n * `probes`: the count of probes admitted so far this probing round.\n * `canPass` admits while `probes < halfOpenMaxProbes`, then fast-fails the\n * rest (so a slow target can't be flooded by every queued caller at once).\n * A probe success closes the breaker; a probe failure re-opens it (and\n * resets the cooldown clock). Only `half_open` counts in-flight probes.\n *\n * ### Transition table\n *\n * Rows are the current phase; columns are the event. `→` denotes the next\n * phase; `self` means unchanged. `nowMs` is the injected clock.\n *\n * | from \\ event | onSuccess | onFailure | canPass (→ allowed?) |\n * | ------------ | -------------- | -------------------------------------- | ------------------------------------------------------------------------------------ |\n * | closed | closed(0) | failures+1; trip → open(nowMs) at thr. | self → true (always) |\n * | open | self *(¹)* | self | elapsed ≥ cooldown → half_open(1), true; else self → false |\n * | half_open | → closed(0) | → open(nowMs) (probe failed) | probes < max → half_open(probes+1), true; else self → false |\n *\n * *(¹)* `onSuccess` on `open` \"shouldn't happen\": no call was admitted while\n * open, so no success can be reported for one. We leave the state unchanged\n * rather than throw — the breaker is a coordination aid, not a correctness\n * gate, and a spurious success report (e.g. a late-arriving result from before\n * the trip) must not silently un-trip the breaker. `onFailure` on `open` is\n * likewise a no-op: the breaker is already tripped; the `open` clock is anchored\n * at the trip, not bumped by late failures.\n *\n * ### The probe-admission asymmetry\n *\n * Entering `half_open` happens inside `canPass`, not `onSuccess` / `onFailure`,\n * because the cooldown is a *time* condition and only `canPass` reads the clock\n * against `openedAtMs`. The first `canPass` after the cooldown elapses both\n * transitions the phase AND admits the probe (`[half_open(1), true]`) — one\n * caller drives the recovery attempt. Subsequent `canPass` calls in the same\n * round admit up to `halfOpenMaxProbes` total, then fast-fail until a probe\n * resolves and `onSuccess` / `onFailure` moves the phase out of `half_open`.\n *\n * This module is a textbook three-state machine — a clean `/machine-viz` demo.\n *\n * NOT exported from the package root — reached via the\n * `@demlik/tea/circuit-breaker` subpath, same one-shape-per-package rule as\n * `work-queue`, `rate-limit`, and `idempotency`.\n */\n\n/**\n * Circuit-breaker policy — pure configuration, no mutable state. One policy is\n * shared across all the calls guarded by one logical breaker (often a\n * module-scope const). `CircuitState` carries the mutable counts; this carries\n * the thresholds those counts are compared against.\n */\nexport interface CircuitPolicy {\n /**\n * Consecutive failures in `closed` that trip the breaker `open`. The\n * `failureThreshold`-th failure is the one that trips — with a threshold of\n * 5, the breaker survives 4 failures and opens on the 5th.\n */\n readonly failureThreshold: number;\n /**\n * How long the breaker stays `open` before `canPass` starts admitting\n * probes, in milliseconds. Measured from `openedAtMs`; the cutoff is\n * half-open (`elapsed >= cooldownMs` admits), matching `rate-limit` /\n * `idempotency` window edges — a breaker opened at `t` first probes at\n * `t + cooldownMs`.\n */\n readonly cooldownMs: number;\n /**\n * How many probe calls `half_open` admits before fast-failing the rest of\n * the round. Caps the load on a recovering target — a single slow probe\n * can't be joined by every queued caller. The round ends when a probe\n * resolves: success closes the breaker, failure re-opens it.\n */\n readonly halfOpenMaxProbes: number;\n}\n\n/**\n * Sensible defaults: trip after 5 consecutive failures, cool down for 30s,\n * admit a single probe before deciding. Tuned for guarding a network backend;\n * override per call site rather than mutating this shared const.\n */\nexport const defaultCircuitPolicy: CircuitPolicy = {\n failureThreshold: 5,\n cooldownMs: 30_000,\n halfOpenMaxProbes: 1,\n};\n\n/**\n * The breaker's phase. A discriminated union on `phase` so each phase carries\n * ONLY its own data and impossible combinations are unrepresentable (pattern\n * 11): there is no state with both a failure count AND an open timestamp,\n * because such a state is meaningless. `phase` is the discriminant; every field\n * is `readonly` so the whole value is trivially JSON-serializable into a\n * consumer's `Store<S>` and safe to thread through a pure reducer.\n *\n * - `closed` carries `failures` — consecutive failures since last success.\n * - `open` carries `openedAtMs` — clock reading when it tripped.\n * - `half_open` carries `probes` — probes admitted this round.\n */\nexport type CircuitState =\n | { readonly phase: \"closed\"; readonly failures: number }\n | { readonly phase: \"open\"; readonly openedAtMs: number }\n | { readonly phase: \"half_open\"; readonly probes: number };\n\n/** The starting state: closed with zero recorded failures. */\nexport function initCircuit(): CircuitState {\n return { phase: \"closed\", failures: 0 };\n}\n\n/**\n * Record a successful guarded call. PURE — returns a new state; the input is\n * never mutated.\n *\n * - `closed` → reset `failures` to 0 (a success breaks the consecutive run).\n * - `half_open` → the probe succeeded; the target has recovered → `closed`.\n * - `open` → unchanged. A success \"shouldn't happen\" while open (no call\n * was admitted to produce one); a late/spurious success must\n * not silently un-trip the breaker. See module header note ¹.\n */\nexport function onSuccess(\n state: CircuitState,\n _policy: CircuitPolicy,\n): CircuitState {\n switch (state.phase) {\n case \"closed\":\n // Already closed at 0 → return the same shape (cheap; the consumer's\n // store only re-persists on a real change if it dedupes).\n return state.failures === 0 ? state : { phase: \"closed\", failures: 0 };\n case \"half_open\":\n return { phase: \"closed\", failures: 0 };\n case \"open\":\n return state;\n }\n}\n\n/**\n * Record a failed guarded call. PURE — returns a new state; the input is never\n * mutated. `nowMs` is the injected clock, stamped onto the `open` phase when the\n * breaker trips so `canPass` can measure the cooldown later.\n *\n * - `closed` → `failures + 1`; if that reaches `failureThreshold`, trip to\n * `open(nowMs)`. The threshold-th failure is the one that trips.\n * - `half_open` → the probe failed; the target is still unhealthy →\n * `open(nowMs)`, restarting the cooldown clock so a fresh\n * cooldown elapses before the next probe round.\n * - `open` → unchanged. The breaker is already tripped; the `open` clock\n * is anchored at the original trip, not bumped by late\n * failures. See module header note ¹.\n */\nexport function onFailure(\n state: CircuitState,\n policy: CircuitPolicy,\n nowMs: number,\n): CircuitState {\n switch (state.phase) {\n case \"closed\": {\n const failures = state.failures + 1;\n // `>=` not `===`: a threshold of 0 (or any already-exceeded count from a\n // policy change) trips immediately rather than over-counting forever.\n return failures >= policy.failureThreshold\n ? { phase: \"open\", openedAtMs: nowMs }\n : { phase: \"closed\", failures };\n }\n case \"half_open\":\n // A failed probe re-opens and resets the cooldown clock to nowMs.\n return { phase: \"open\", openedAtMs: nowMs };\n case \"open\":\n return state;\n }\n}\n\n/**\n * Decide whether a call may pass, advancing the phase as a side effect of the\n * decision. PURE — returns `[next, allowed]`; the input is never mutated.\n * Branch on the boolean, thread the state forward regardless:\n *\n * const [c1, allowed] = canPass(state.circuit, policy, nowMs);\n * if (!allowed) return [{ ...state, circuit: c1 }, [fastFailCmd]];\n * return [{ ...state, circuit: c1 }, [doGuardedCallCmd]];\n *\n * - `closed` → `[state, true]` — always passes; failures are recorded by\n * `onFailure` once the call resolves, not gated here.\n * - `open` → if `nowMs - openedAtMs >= cooldownMs` the cooldown has\n * elapsed → enter `half_open` and admit this caller as the\n * first probe: `[half_open(1), true]`. Otherwise still cooling\n * down → `[state, false]` (fast-fail, no downstream load).\n * - `half_open` → admit while `probes < halfOpenMaxProbes`, incrementing the\n * count: `[half_open(probes+1), true]`. Once the cap is hit →\n * `[state, false]` until a probe resolves and `onSuccess` /\n * `onFailure` moves the phase out of `half_open`.\n *\n * Entering `half_open` lives here (not in `onSuccess` / `onFailure`) because the\n * cooldown is a time condition and only `canPass` reads `nowMs` against\n * `openedAtMs`. See the module header's probe-admission note.\n */\nexport function canPass(\n state: CircuitState,\n policy: CircuitPolicy,\n nowMs: number,\n): readonly [CircuitState, boolean] {\n switch (state.phase) {\n case \"closed\":\n return [state, true];\n case \"open\": {\n const elapsed = nowMs - state.openedAtMs;\n // Half-open cutoff: elapsed exactly == cooldownMs admits the first probe,\n // matching the >= edge `rate-limit` / `idempotency` use for their windows.\n if (elapsed >= policy.cooldownMs) {\n return [{ phase: \"half_open\", probes: 1 }, true];\n }\n return [state, false];\n }\n case \"half_open\":\n if (state.probes < policy.halfOpenMaxProbes) {\n return [{ phase: \"half_open\", probes: state.probes + 1 }, true];\n }\n return [state, false];\n }\n}\n"]}
|
package/dist/chunk-UOC2PL46.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
// src/machine-viz/mermaid-id.ts
|
|
2
|
-
function safeId(raw) {
|
|
3
|
-
const cleaned = raw.replace(/[^A-Za-z0-9_]/g, "_");
|
|
4
|
-
if (cleaned === "") return "_empty";
|
|
5
|
-
return /^[0-9]/.test(cleaned) ? `s_${cleaned}` : cleaned;
|
|
6
|
-
}
|
|
7
|
-
function safeLabel(raw) {
|
|
8
|
-
return raw.replace(/["\n\r;:]/g, " ").trim();
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export { safeId, safeLabel };
|
|
12
|
-
//# sourceMappingURL=chunk-UOC2PL46.js.map
|
|
13
|
-
//# sourceMappingURL=chunk-UOC2PL46.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/machine-viz/mermaid-id.ts"],"names":[],"mappings":";AAsBO,SAAS,OAAO,GAAA,EAAqB;AAC1C,EAAA,MAAM,OAAA,GAAU,GAAA,CAAI,OAAA,CAAQ,gBAAA,EAAkB,GAAG,CAAA;AACjD,EAAA,IAAI,OAAA,KAAY,IAAI,OAAO,QAAA;AAC3B,EAAA,OAAO,SAAS,IAAA,CAAK,OAAO,CAAA,GAAI,CAAA,EAAA,EAAK,OAAO,CAAA,CAAA,GAAK,OAAA;AACnD;AAQO,SAAS,UAAU,GAAA,EAAqB;AAC7C,EAAA,OAAO,GAAA,CAAI,OAAA,CAAQ,YAAA,EAAc,GAAG,EAAE,IAAA,EAAK;AAC7C","file":"chunk-UOC2PL46.js","sourcesContent":["// ═══════════════════════════════════════════════════════════════════════════\n// MERMAID SANITIZING — the two functions every drawing in this package shares.\n//\n// Mermaid state ids must be identifier-safe, and a transition label must not\n// contain the characters that terminate it. Both `machine-viz`'s `toMermaid`\n// (which draws a COMPILED machine) and `chart`'s `chartMermaid` (which draws\n// the CHART) need exactly this, and a state name like `human:cp-approval` —\n// real, from `src/chart/__fixtures__/lane.ts` — breaks the diagram in both.\n//\n// So it lives here, in a module with NO imports: `machine-viz/index.ts` pulls\n// in the whole substrate (`formOf` from `../index`), and `chart/compile.ts`\n// must not acquire that dependency merely to sanitize a string.\n// ═══════════════════════════════════════════════════════════════════════════\n\n/**\n * Sanitize a discriminant string into a Mermaid-safe node identifier.\n *\n * Any run of non-identifier characters becomes `_`; a leading digit is\n * prefixed so the id is always a valid Mermaid token; the empty string gets a\n * stable placeholder. Deterministic and idempotent — `safeId(safeId(x))` is\n * `safeId(x)`, so a diagram may sanitize the same name at several sites.\n */\nexport function safeId(raw: string): string {\n const cleaned = raw.replace(/[^A-Za-z0-9_]/g, \"_\");\n if (cleaned === \"\") return \"_empty\";\n return /^[0-9]/.test(cleaned) ? `s_${cleaned}` : cleaned;\n}\n\n/**\n * Sanitize text used as a Mermaid edge/transition label (the part after `:`).\n *\n * Strips the characters that terminate or confuse a label: newlines, the `:`\n * that separates label from target, and Mermaid's `\"`/`;` delimiters.\n */\nexport function safeLabel(raw: string): string {\n return raw.replace(/[\"\\n\\r;:]/g, \" \").trim();\n}\n"]}
|