@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/throttle/index.ts"],"names":[],"mappings":";AAkIO,SAAS,QAAA,CACd,EAAA,EACA,EAAA,EACA,IAAA,EACc;AACd,EAAA,MAAM,OAAA,GAAU,MAAM,OAAA,IAAW,IAAA;AACjC,EAAA,MAAM,QAAA,GAAW,MAAM,QAAA,IAAY,IAAA;AAMnC,EAAA,IAAI,MAAA,GAA+C,IAAA;AAOnD,EAAA,IAAI,WAAA,GAAwB,IAAA;AAE5B,EAAA,SAAS,WAAA,GAAoB;AAC3B,IAAA,IAAI,WAAW,IAAA,EAAM;AACnB,MAAA,YAAA,CAAa,MAAM,CAAA;AACnB,MAAA,MAAA,GAAS,IAAA;AAAA,IACX;AAAA,EACF;AAOA,EAAA,SAAS,eAAA,GAAwB;AAC/B,IAAA,IAAI,QAAA,IAAY,gBAAgB,IAAA,EAAM;AACpC,MAAA,MAAM,IAAA,GAAO,WAAA;AACb,MAAA,WAAA,GAAc,IAAA;AAId,MAAA,MAAA,GAAS,UAAA,CAAW,iBAAiB,EAAE,CAAA;AACvC,MAAA,EAAA,CAAG,GAAG,IAAI,CAAA;AAAA,IACZ,CAAA,MAAO;AAEL,MAAA,MAAA,GAAS,IAAA;AAAA,IACX;AAAA,EACF;AAEA,EAAA,MAAM,SAAA,GAAY,IAAI,IAAA,KAAkB;AACtC,IAAA,IAAI,WAAW,IAAA,EAAM;AAEnB,MAAA,IAAI,OAAA,EAAS;AACX,QAAA,EAAA,CAAG,GAAG,IAAI,CAAA;AAAA,MACZ,WAAW,QAAA,EAAU;AAInB,QAAA,WAAA,GAAc,IAAA;AAAA,MAChB;AACA,MAAA,MAAA,GAAS,UAAA,CAAW,iBAAiB,EAAE,CAAA;AAAA,IACzC,WAAW,QAAA,EAAU;AAGnB,MAAA,WAAA,GAAc,IAAA;AAAA,IAChB;AAAA,EAEF,CAAA;AAEA,EAAA,SAAA,CAAU,SAAS,MAAY;AAC7B,IAAA,WAAA,EAAY;AACZ,IAAA,WAAA,GAAc,IAAA;AAAA,EAChB,CAAA;AAEA,EAAA,SAAA,CAAU,QAAQ,MAAY;AAI5B,IAAA,IAAI,gBAAgB,IAAA,EAAM;AACxB,MAAA,MAAM,IAAA,GAAO,WAAA;AACb,MAAA,WAAA,GAAc,IAAA;AACd,MAAA,WAAA,EAAY;AACZ,MAAA,EAAA,CAAG,GAAG,IAAI,CAAA;AAAA,IACZ,CAAA,MAAO;AAGL,MAAA,WAAA,EAAY;AAAA,IACd;AAAA,EACF,CAAA;AAEA,EAAA,OAAO,SAAA;AACT","file":"chunk-HCEVHFJ7.js","sourcesContent":["/**\n * @demlik/tea/throttle — a timer-based call transformer that caps invocation\n * to at most once per `ms` window.\n *\n * Sibling of `@demlik/tea/debounce` — the two share a SYMMETRIC surface\n * (`Throttled<A>` / `Debounced<A>`, both callable + `.cancel()` + `.flush()`,\n * both `{ leading?, trailing? }`) so they read as a pair. They are kept in\n * separate files with NO cross-import: each is a self-contained timer, and the\n * coalesce-vs-drop policies differ enough that sharing code would hide the\n * distinction. The contrast IS the lesson:\n *\n * - **debounce** waits for quiet — fires once AFTER a burst settles.\n * - **throttle** enforces a rate — fires AT MOST once per `ms` while the\n * burst is ongoing, dropping the calls in between.\n *\n * Layering with `@demlik/tea/throttled-input`: `throttle` and `debounce` are\n * the two host-boundary PRIMITIVES — two distinct algorithms\n * (at-most-once-per-window vs collapse-a-burst) behind one signature shape.\n * `throttled-input` is NOT a third primitive: it is a TEA machine composed\n * OVER them (the same semantics, reimplemented as pure, durable Model state)\n * that re-exports both. Wrapping a raw host event → these primitives; durable, replayable\n * gating inside a machine → `throttled-input` (see its module doc for the full\n * layering map).\n *\n * WHAT THIS IS NOT — and why it lives outside `update`:\n *\n * - NOT a reducer / Transitions cell. Reducers are PURE (invariant 2 — same\n * inputs → same outputs, no `Date.now`, no timers). A throttle holds a live\n * `setTimeout` handle and a window cursor; folding it into `update` would\n * smuggle a timer into the pure core.\n * - NOT a Sub. A Sub is a continuous SOURCE of Msgs the substrate reconciles\n * by id every transition (invariant 4). A throttle is a SINK transformer:\n * it wraps a `void`-returning handler (the canonical one is\n * `Runtime.dispatch`) so a high-frequency HOST event is rate-limited BEFORE\n * it ever becomes a Msg.\n *\n * It is a higher-order function at the host boundary (pattern 14 — TEA owns\n * state, JS owns timing). The host attaches the wrapped function to a\n * high-frequency event source (`mousemove`, `scroll`, `pointermove`, a game\n * loop) and the substrate downstream only ever sees the rate-limited Msg:\n *\n * const onMoved = throttle(\n * (e: PointerEvent) =>\n * runtime.dispatch({ type: \"UserMovedCursor\", x: e.clientX, y: e.clientY }),\n * 16, // ~60fps cap\n * );\n * window.addEventListener(\"pointermove\", onMoved);\n * // cleanup: onMoved.cancel(); window.removeEventListener(\"pointermove\", onMoved);\n *\n * NOT exported from the package root — reached via the `@demlik/tea/throttle`\n * subpath, same one-shape-per-package rule as `rate-limit`, `deadline`, and\n * `work-queue`.\n *\n * Uses the universal `setTimeout` / `clearTimeout` timer idiom (same discipline\n * as `subs/from-timeout.ts`): exactly one live handle at a time, the window\n * timer clears itself on expiry, `cancel` / `flush` clear and null it so a\n * stale callback can never run.\n */\n\n/**\n * A throttled wrapper around `fn`. Callable with the SAME argument tuple as\n * `fn` — `(...args: A) => void` — plus two control methods that share names and\n * shapes with `Debounced<A>` so the two modules read as a symmetric pair.\n */\nexport interface Throttled<A extends readonly unknown[]> {\n /** Invoke `fn` if the window is open, else record args for the trailing fire. */\n (...args: A): void;\n /**\n * Drop any pending trailing fire WITHOUT invoking `fn`, and close the active\n * window. Clears the timer, forgets the captured trailing args, and resets\n * the cursor so the next call fires immediately on the leading edge (if\n * `leading` is enabled). Idempotent.\n *\n * The host-cleanup partner of `removeEventListener`: cancel the pending fire\n * when the component unmounts / the listener detaches, so a queued\n * `dispatch` can't land after teardown.\n */\n cancel(): void;\n /**\n * Fire any pending trailing call IMMEDIATELY with its latest captured args,\n * then close the window. No-op when nothing is pending. Use to force the last\n * dropped call out early — e.g. flush the final cursor position when a drag\n * ends, so the rest state isn't a stale mid-drag sample.\n */\n flush(): void;\n}\n\n/**\n * Wrap `fn` so it runs AT MOST once per `ms` window, no matter how often it is\n * called.\n *\n * Leading + trailing (the default): the first call fires `fn` IMMEDIATELY (the\n * leading edge) and opens a `ms` window. Calls during the window are dropped\n * from immediate firing but their args are remembered; when the window closes,\n * `fn` fires ONCE more with the LATEST dropped args (the trailing edge) and a\n * new window opens. A call after the window has already closed leads a fresh\n * window immediately. Spacing every call at least `ms` apart therefore fires\n * each one on its own leading edge.\n *\n * @param fn The handler to rate-limit. Typically `(...) => runtime.dispatch(...)`,\n * but any `(...args: A) => void` works. The return value is\n * ignored — throttle is a fire transformer, not a value pipe.\n * @param ms Window length in milliseconds. `fn` fires at most once per `ms`.\n * @param opts.leading Fire on the FIRST call of a window (the leading edge),\n * immediately. Default `true`.\n * @param opts.trailing Fire once at window END with the latest args dropped\n * during the window. Default `true`.\n *\n * Edge combinations (matching lodash's settled semantics, the de-facto\n * standard the ecosystem rediscovered):\n *\n * - `{ leading: true, trailing: true }` (default): immediate fire + a\n * trailing fire per window while calls keep arriving. The trailing fire is\n * SUPPRESSED when no extra call arrived during the window (a lone call\n * already fired on the leading edge — no stale trailing double-fire).\n * - `{ leading: true, trailing: false }`: only the immediate fire; everything\n * in the window is dropped with no catch-up at the end.\n * - `{ leading: false, trailing: true }`: no immediate fire; the FIRST call\n * opens a window and `fn` fires at the window's end with the latest args.\n * Steady \"sample every `ms`\" behavior with no instant response.\n * - `{ leading: false, trailing: false }`: never fires. Degenerate but legal;\n * we don't throw — the caller asked for a no-op transformer.\n *\n * WHY a default of leading + trailing both `true`: a throttle's job is \"respond\n * now, then keep responding at a steady rate.\" Leading gives the instant\n * response (the first `mousemove` updates immediately); trailing guarantees the\n * FINAL sample of a window isn't lost (the cursor ends where the user actually\n * left it, not one window-width behind). Dropping either loses one of those two\n * guarantees, so both default on.\n */\nexport function throttle<A extends readonly unknown[]>(\n fn: (...args: A) => void,\n ms: number,\n opts?: { leading?: boolean; trailing?: boolean },\n): Throttled<A> {\n const leading = opts?.leading ?? true;\n const trailing = opts?.trailing ?? true;\n\n // Exactly one live handle at a time (the from-timeout discipline). `null`\n // when no window is open — the single source of truth for \"is a window\n // active?\". The window timer arms on a fire and clears itself on expiry; the\n // call path never stacks overlapping timers.\n let handle: ReturnType<typeof setTimeout> | null = null;\n // The latest args seen DURING an open window, held for the trailing fire.\n // Overwritten by every dropped call (the trailing edge fires with the LAST\n // args), cleared the instant we fire / cancel. `null` (not `undefined`) is\n // the empty sentinel because `A` may itself contain `undefined` elements — a\n // present-but-empty tuple is still a pending call, distinct from \"nothing\n // pending\".\n let pendingArgs: A | null = null;\n\n function clearHandle(): void {\n if (handle !== null) {\n clearTimeout(handle);\n handle = null;\n }\n }\n\n // Window expiry: the `ms` window just closed. If a call arrived during the\n // window (`pendingArgs !== null`) and trailing is enabled, fire it now and\n // open a FRESH window so the rate cap continues across a sustained burst.\n // If nothing was dropped, let the window close (handle → null) so the next\n // call leads a new window immediately.\n function onWindowExpired(): void {\n if (trailing && pendingArgs !== null) {\n const args = pendingArgs;\n pendingArgs = null;\n // Re-arm BEFORE firing so a re-entrant call from inside `fn` sees an open\n // window (and is correctly dropped/queued) rather than leading a second\n // window — keeps the \"at most once per `ms`\" invariant under re-entry.\n handle = setTimeout(onWindowExpired, ms);\n fn(...args);\n } else {\n // No trailing fire owed — close the window.\n handle = null;\n }\n }\n\n const throttled = (...args: A): void => {\n if (handle === null) {\n // Window is closed → this call leads a new window.\n if (leading) {\n fn(...args);\n } else if (trailing) {\n // No leading fire, but trailing wants the first call's args so the\n // window's end has something to fire. Without this, a leading:false\n // throttle would swallow a lone call entirely.\n pendingArgs = args;\n }\n handle = setTimeout(onWindowExpired, ms);\n } else if (trailing) {\n // Window is open → drop this call from immediate firing, but remember its\n // args as the latest candidate for the trailing edge.\n pendingArgs = args;\n }\n // Window open + trailing disabled → pure drop, nothing to remember.\n };\n\n throttled.cancel = (): void => {\n clearHandle();\n pendingArgs = null;\n };\n\n throttled.flush = (): void => {\n // Force the pending trailing fire out now and close the window. Mirrors\n // `onWindowExpired`'s fire branch but is caller-driven, so it tears the\n // timer down rather than re-arming.\n if (pendingArgs !== null) {\n const args = pendingArgs;\n pendingArgs = null;\n clearHandle();\n fn(...args);\n } else {\n // Nothing pending — still close a lingering window so a post-flush call\n // leads cleanly.\n clearHandle();\n }\n };\n\n return throttled;\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/debounce/index.ts"],"names":[],"mappings":";AAoHO,SAAS,QAAA,CACd,EAAA,EACA,EAAA,EACA,IAAA,EACc;AACd,EAAA,MAAM,OAAA,GAAU,MAAM,OAAA,IAAW,KAAA;AACjC,EAAA,MAAM,QAAA,GAAW,MAAM,QAAA,IAAY,IAAA;AAMnC,EAAA,IAAI,MAAA,GAA+C,IAAA;AAOnD,EAAA,IAAI,WAAA,GAAwB,IAAA;AAK5B,EAAA,IAAI,YAAA,GAAe,KAAA;AAEnB,EAAA,SAAS,WAAA,GAAoB;AAC3B,IAAA,IAAI,WAAW,IAAA,EAAM;AACnB,MAAA,YAAA,CAAa,MAAM,CAAA;AACnB,MAAA,MAAA,GAAS,IAAA;AAAA,IACX;AAAA,EACF;AAKA,EAAA,SAAS,cAAA,GAAuB;AAC9B,IAAA,MAAA,GAAS,IAAA;AAQT,IAAA,IAAI,QAAA,IAAY,gBAAgB,IAAA,EAAM;AACpC,MAAA,MAAM,IAAA,GAAO,WAAA;AACb,MAAA,WAAA,GAAc,IAAA;AACd,MAAA,EAAA,CAAG,GAAG,IAAI,CAAA;AAAA,IACZ;AAEA,IAAA,YAAA,GAAe,KAAA;AAAA,EACjB;AAEA,EAAA,MAAM,SAAA,GAAY,IAAI,IAAA,KAAkB;AAItC,IAAA,MAAM,eAAe,MAAA,KAAW,IAAA;AAChC,IAAA,IAAI,OAAA,IAAW,YAAA,IAAgB,CAAC,YAAA,EAAc;AAC5C,MAAA,YAAA,GAAe,IAAA;AACf,MAAA,EAAA,CAAG,GAAG,IAAI,CAAA;AAAA,IACZ,WAAW,QAAA,EAAU;AAGnB,MAAA,WAAA,GAAc,IAAA;AAAA,IAChB;AAKA,IAAA,WAAA,EAAY;AACZ,IAAA,MAAA,GAAS,UAAA,CAAW,gBAAgB,EAAE,CAAA;AAAA,EACxC,CAAA;AAEA,EAAA,SAAA,CAAU,SAAS,MAAY;AAC7B,IAAA,WAAA,EAAY;AACZ,IAAA,WAAA,GAAc,IAAA;AACd,IAAA,YAAA,GAAe,KAAA;AAAA,EACjB,CAAA;AAEA,EAAA,SAAA,CAAU,QAAQ,MAAY;AAI5B,IAAA,IAAI,gBAAgB,IAAA,EAAM;AACxB,MAAA,MAAM,IAAA,GAAO,WAAA;AACb,MAAA,WAAA,GAAc,IAAA;AACd,MAAA,WAAA,EAAY;AAEZ,MAAA,YAAA,GAAe,KAAA;AACf,MAAA,EAAA,CAAG,GAAG,IAAI,CAAA;AAAA,IACZ,CAAA,MAAO;AAGL,MAAA,WAAA,EAAY;AACZ,MAAA,YAAA,GAAe,KAAA;AAAA,IACjB;AAAA,EACF,CAAA;AAEA,EAAA,OAAO,SAAA;AACT","file":"chunk-J6527VRQ.js","sourcesContent":["/**\n * @demlik/tea/debounce — a timer-based call transformer that coalesces a burst\n * of calls into a single fire.\n *\n * Sibling of `@demlik/tea/throttle` — the two share a SYMMETRIC surface\n * (`Debounced<A>` / `Throttled<A>`, both callable + `.cancel()` + `.flush()`,\n * both `{ leading?, trailing? }`) so they read as a pair. They are kept in\n * separate files with NO cross-import: each is a self-contained timer, and the\n * coalesce-vs-drop policies are different enough that sharing code would hide\n * the distinction. Read one to understand the other; do not couple them.\n *\n * Layering with `@demlik/tea/throttled-input`: `debounce` and `throttle` are\n * the two host-boundary PRIMITIVES — two distinct algorithms (collapse-a-burst\n * vs at-most-once-per-window) behind one signature shape. `throttled-input` is\n * NOT a third primitive: it is a TEA machine composed OVER them that\n * re-exports both (see its module doc for the full layering map).\n *\n * WHAT THIS IS NOT — and why it lives outside `update`:\n *\n * - NOT a reducer / Transitions cell. Reducers are PURE (invariant 2 — same\n * inputs → same outputs, no `Date.now`, no timers). A debounce holds a live\n * `setTimeout` handle and mutable pending-args state; folding it into\n * `update` would smuggle a timer into the pure core.\n * - NOT a Sub. A Sub is a continuous SOURCE of Msgs the substrate reconciles\n * by id every transition (invariant 4). A debounce is a SINK transformer:\n * it wraps a `void`-returning handler (the canonical one is\n * `Runtime.dispatch`) so bursty HOST events coalesce BEFORE they ever\n * become a Msg.\n *\n * It is a higher-order function at the host boundary (pattern 14 — TEA owns\n * state, JS owns timing). The host attaches the wrapped function to a noisy\n * event source (`scroll`, `resize`, `input`, a ResizeObserver) and the\n * substrate downstream only ever sees the coalesced Msg:\n *\n * const onResized = debounce(\n * () => runtime.dispatch({ type: \"BrowserResizedWindow\" }),\n * 150,\n * );\n * window.addEventListener(\"resize\", onResized);\n * // cleanup: onResized.cancel(); window.removeEventListener(\"resize\", onResized);\n *\n * NOT exported from the package root — reached via the `@demlik/tea/debounce`\n * subpath, same one-shape-per-package rule as `rate-limit`, `deadline`, and\n * `work-queue`.\n *\n * Uses the universal `setTimeout` / `clearTimeout` timer idiom (same discipline\n * as `subs/from-timeout.ts`): exactly one live handle at a time, every arm\n * clears the previous, every fire / cancel clears the handle and nulls it so a\n * stale callback can never run.\n */\n\n/**\n * A debounced wrapper around `fn`. Callable with the SAME argument tuple as\n * `fn` — `(...args: A) => void` — plus two control methods that share names and\n * shapes with `Throttled<A>` so the two modules read as a symmetric pair.\n */\nexport interface Debounced<A extends readonly unknown[]> {\n /** Schedule (or re-schedule) a fire of the wrapped `fn`. See `debounce`. */\n (...args: A): void;\n /**\n * Drop any pending trailing fire WITHOUT invoking `fn`. Clears the timer and\n * forgets the captured args. Idempotent — calling it with nothing pending is\n * a no-op. The leading-edge latch (if `leading` is enabled) also resets, so\n * the next call after `cancel` is treated as a fresh burst.\n *\n * The host-cleanup partner of `removeEventListener`: cancel the pending fire\n * when the component unmounts / the listener detaches, so a queued\n * `dispatch` can't land after teardown.\n */\n cancel(): void;\n /**\n * Fire any pending trailing call IMMEDIATELY with its captured args, then\n * clear the timer. No-op when nothing is pending. Use to force the last\n * coalesced call out early — e.g. flush a debounced save on `beforeunload`,\n * or flush a debounced search when the user presses Enter.\n */\n flush(): void;\n}\n\n/**\n * Wrap `fn` so a BURST of calls collapses to a single invocation.\n *\n * Trailing edge (the default): each call (re)starts a `ms` timer; `fn` fires\n * once with the LAST args after `ms` of quiet. Every call within `ms` of the\n * previous resets the timer — the classic \"wait until the user stops typing\"\n * behavior.\n *\n * @param fn The handler to coalesce. Typically `(...) => runtime.dispatch(...)`,\n * but any `(...args: A) => void` works. The return value is\n * ignored — debounce is a fire transformer, not a value pipe.\n * @param ms Quiet window in milliseconds. Calls closer together than `ms`\n * coalesce; a gap of at least `ms` lets the trailing fire through.\n * @param opts.leading Fire on the FIRST call of a burst (the leading edge),\n * with that first call's args. Default `false`.\n * @param opts.trailing Fire on the trailing edge with the LAST call's args.\n * Default `true`.\n *\n * Edge combinations (matching lodash's settled semantics, the de-facto\n * standard the ecosystem rediscovered):\n *\n * - `{ trailing: true }` (default): one fire, after the burst, last args.\n * - `{ leading: true, trailing: false }`: one fire, at the burst START,\n * first args. Subsequent calls within the window are swallowed; the timer\n * only re-opens the \"can lead again\" latch after `ms` of quiet.\n * - `{ leading: true, trailing: true }`: fires at the start AND end of a\n * burst — BUT the trailing fire is SUPPRESSED for a burst of exactly ONE\n * call (the leading fire already covered it, so a lone call doesn't\n * double-fire). This matches lodash and is the behavior tests pin.\n * - `{ leading: false, trailing: false }`: never fires. Degenerate but legal;\n * we don't throw — the caller asked for a no-op transformer.\n *\n * WHY a default of trailing-only: a debounce's whole job is \"act after the\n * activity settles.\" The trailing edge IS that semantic — fire with the final\n * state of the burst (the last keystroke, the final scroll position). Leading\n * is the opt-in for \"respond instantly, then go quiet.\"\n */\nexport function debounce<A extends readonly unknown[]>(\n fn: (...args: A) => void,\n ms: number,\n opts?: { leading?: boolean; trailing?: boolean },\n): Debounced<A> {\n const leading = opts?.leading ?? false;\n const trailing = opts?.trailing ?? true;\n\n // Exactly one live handle at a time (the from-timeout discipline). `null`\n // when no fire is pending — the single source of truth for \"is a timer\n // armed?\". Every arm clears the previous handle before opening a new one, so\n // we never leak overlapping timers across a burst.\n let handle: ReturnType<typeof setTimeout> | null = null;\n // The most recent args, held only while a trailing fire is pending. Captured\n // on every call (the trailing edge fires with the LAST args), cleared the\n // instant we fire / cancel so a stale tuple can never leak into a later\n // burst. `null` (not `undefined`) is the empty sentinel because `A` may\n // itself contain `undefined` elements — a present-but-empty tuple is still a\n // pending call, distinct from \"nothing pending\".\n let pendingArgs: A | null = null;\n // Leading-edge latch: true once a leading fire has happened for the current\n // burst, reset to false after `ms` of quiet (the timer's expiry) or on\n // `cancel`. Gates the \"fire on first call of a burst, then stay quiet\" rule\n // so mid-burst calls don't re-trigger the leading fire.\n let leadingFired = false;\n\n function clearHandle(): void {\n if (handle !== null) {\n clearTimeout(handle);\n handle = null;\n }\n }\n\n // Timer expiry: the burst has gone quiet for `ms`. Fire the trailing edge if\n // one is owed, then reset all burst state so the NEXT call starts a fresh\n // burst (re-arming the leading latch, clearing pending args).\n function onTimerExpired(): void {\n handle = null;\n // Suppress the trailing fire for a lone leading-only-eligible call:\n // when `leadingFired` is true AND trailing is enabled, a burst of exactly\n // one call already fired on the leading edge — `pendingArgs` was cleared\n // at that point, so there is nothing to fire here. A burst of 2+ calls\n // re-captured `pendingArgs` after the leading fire, so the trailing fire\n // lands with the last args. The `pendingArgs !== null` guard encodes\n // exactly that distinction; no separate call-count needed.\n if (trailing && pendingArgs !== null) {\n const args = pendingArgs;\n pendingArgs = null;\n fn(...args);\n }\n // Burst over — re-open the leading latch for the next burst.\n leadingFired = false;\n }\n\n const debounced = (...args: A): void => {\n // Leading edge: the FIRST call of a burst (no timer armed yet, latch open).\n // Fire immediately with this call's args; do NOT stash them as pending so a\n // lone call won't also fire on the trailing edge.\n const isBurstStart = handle === null;\n if (leading && isBurstStart && !leadingFired) {\n leadingFired = true;\n fn(...args);\n } else if (trailing) {\n // Capture the latest args for the trailing fire. Overwrites any prior\n // pending tuple — the trailing edge fires with the LAST args, by design.\n pendingArgs = args;\n }\n\n // (Re)arm the quiet-window timer. Clearing the previous handle is what\n // makes the window SLIDE: every call within `ms` pushes the trailing fire\n // out to `ms` after THIS call.\n clearHandle();\n handle = setTimeout(onTimerExpired, ms);\n };\n\n debounced.cancel = (): void => {\n clearHandle();\n pendingArgs = null;\n leadingFired = false;\n };\n\n debounced.flush = (): void => {\n // Force the pending trailing fire out now. Mirrors `onTimerExpired`'s fire\n // branch but is caller-driven rather than timer-driven, so it also tears\n // down the armed timer (a timer-driven expiry already nulled `handle`).\n if (pendingArgs !== null) {\n const args = pendingArgs;\n pendingArgs = null;\n clearHandle();\n // A flushed trailing fire ends the burst, same as a natural expiry.\n leadingFired = false;\n fn(...args);\n } else {\n // Nothing pending — still tear down a lingering leading-only timer so a\n // post-flush call leads cleanly.\n clearHandle();\n leadingFired = false;\n }\n };\n\n return debounced;\n}\n"]}
|
package/dist/chunk-JELSZN25.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
// src/protocol.ts
|
|
2
|
-
var MsgType = {
|
|
3
|
-
// --- resilient-call: the run Cmd + its two settle Msgs -------------------
|
|
4
|
-
/** The "run the port for `key`" effect Cmd resilient-call emits. */
|
|
5
|
-
ResilientRun: "resilient_run",
|
|
6
|
-
/** PRIVATE success settle Msg (read only by `agentEvents`, #47). */
|
|
7
|
-
ResilientOk: "resilient_ok",
|
|
8
|
-
/** Failure settle Msg — routed to the `fail` verb (back off via retry). */
|
|
9
|
-
ResilientErr: "resilient_err",
|
|
10
|
-
// --- agent: the wired machine's own entry-point Msgs ---------------------
|
|
11
|
-
/** Begin a run. */
|
|
12
|
-
AgentStart: "agent_start",
|
|
13
|
-
/** PRIVATE tool-fan-out success settle Msg (read only by `agentEvents`, #47). */
|
|
14
|
-
AgentToolOk: "agent_tool_ok",
|
|
15
|
-
/** Tool-fan-out failure settle Msg. */
|
|
16
|
-
AgentToolErr: "agent_tool_err",
|
|
17
|
-
/**
|
|
18
|
-
* Re-fire the one outstanding effect on rehydrate (the do↔agent seam). Fired
|
|
19
|
-
* by `do/host`'s `autoBoot` via the typed `agentBootMsg` constructor, matched
|
|
20
|
-
* by the agent reducer — see `AgentBootPort` in `../agent`.
|
|
21
|
-
*/
|
|
22
|
-
AgentBoot: "agent_boot",
|
|
23
|
-
// --- agent: the context-compaction round-trip (#85) ----------------------
|
|
24
|
-
/**
|
|
25
|
-
* The "summarize the oldest N turns" effect Cmd the agent emits when its
|
|
26
|
-
* `CompactionPolicy` asks to compact before a brain call. A DEDICATED
|
|
27
|
-
* round-trip (design B1), distinct from the brain call's `resilient_run`, so
|
|
28
|
-
* compaction is its own honest `Awaiting` state — see `../agent`.
|
|
29
|
-
*/
|
|
30
|
-
CompactRun: "compact_run",
|
|
31
|
-
/** Compaction round-trip resolved — carries the summary text the fold-back folds. */
|
|
32
|
-
CompactOk: "compact_ok",
|
|
33
|
-
/** Compaction round-trip failed (exhausted retry) — surfaced as data, not a stall. */
|
|
34
|
-
CompactErr: "compact_err",
|
|
35
|
-
// --- token-refresh: the two refresh-result Msgs --------------------------
|
|
36
|
-
/** Refresh port resolved — carries the fresh `Token`. */
|
|
37
|
-
TokenRefreshed: "token_refreshed",
|
|
38
|
-
/** Refresh port rejected — carries the rejection (errors are data). */
|
|
39
|
-
TokenRefreshFailed: "token_refresh_failed",
|
|
40
|
-
// --- authed-call: the 401 trigger ----------------------------------------
|
|
41
|
-
/** The 401 the consumer dispatches when a guarded call comes back unauthorized. */
|
|
42
|
-
Unauthorized: "unauthorized"
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
export { MsgType };
|
|
46
|
-
//# sourceMappingURL=chunk-JELSZN25.js.map
|
|
47
|
-
//# sourceMappingURL=chunk-JELSZN25.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/protocol.ts"],"names":[],"mappings":";AAmCO,IAAM,OAAA,GAAU;AAAA;AAAA;AAAA,EAGrB,YAAA,EAAc,eAAA;AAAA;AAAA,EAEd,WAAA,EAAa,cAAA;AAAA;AAAA,EAEb,YAAA,EAAc,eAAA;AAAA;AAAA;AAAA,EAId,UAAA,EAAY,aAAA;AAAA;AAAA,EAEZ,WAAA,EAAa,eAAA;AAAA;AAAA,EAEb,YAAA,EAAc,gBAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMd,SAAA,EAAW,YAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASX,UAAA,EAAY,aAAA;AAAA;AAAA,EAEZ,SAAA,EAAW,YAAA;AAAA;AAAA,EAEX,UAAA,EAAY,aAAA;AAAA;AAAA;AAAA,EAIZ,cAAA,EAAgB,iBAAA;AAAA;AAAA,EAEhB,kBAAA,EAAoB,sBAAA;AAAA;AAAA;AAAA,EAIpB,YAAA,EAAc;AAChB","file":"chunk-JELSZN25.js","sourcesContent":["// ===========================================================================\n// Protocol — the ONE typed source of truth for the cross-module Msg/Cmd\n// discriminant strings (issue #60).\n// ===========================================================================\n//\n// The resilient/agent/auth plumbing speaks a small fixed vocabulary of\n// discriminant strings on `{ type: ... }` Msgs and Cmds. Before this module\n// those literals were retyped inline across ~8 modules — `resilient_ok` alone\n// appeared as a hand-written `\"resilient_ok\"` in resilient-call, with-resilience,\n// llm-call, agent, do/host, paginated-walk, reconciler. Adding a variant or\n// renaming one was shotgun surgery with no compiler to catch a missed site.\n//\n// `MsgType` is that vocabulary, declared ONCE `as const`. Each entry is BOTH:\n// - a value — `MsgType.ResilientOk` is the string `\"resilient_ok\"`, used at\n// construction sites (`{ type: MsgType.ResilientOk, ... }`), reducer keys\n// (`[MsgType.ResilientOk]: ...`), and switch labels (`case MsgType.ResilientOk:`).\n// - a type — `typeof MsgType.ResilientOk` is the literal type `\"resilient_ok\"`,\n// used in the `readonly type:` field of every Msg/Cmd interface.\n//\n// Because the value and the type both derive from the single `as const`\n// declaration, a new variant or a rename is a ONE-LINE change here that\n// propagates with full exhaustiveness — not an 8-file edit.\n//\n// This module is BEHAVIOR-PRESERVING: every string is byte-identical to the\n// literal it replaces. It is internal plumbing vocabulary, NOT a public event\n// surface — `resilient_ok` / `agent_tool_ok` stay PRIVATE (the agent's\n// `agentEvents` projector is the ONE place they are read; #47). Centralizing\n// the literal here does NOT re-expose them to consumers.\n\n/**\n * The closed set of cross-module Msg/Cmd discriminant strings. Declared `as\n * const` so each entry is usable as both a value (construction / reducer keys /\n * switch labels) and, via `typeof MsgType.X`, a literal type (`readonly type:`\n * fields). The single source of truth for issue #60.\n */\nexport const MsgType = {\n // --- resilient-call: the run Cmd + its two settle Msgs -------------------\n /** The \"run the port for `key`\" effect Cmd resilient-call emits. */\n ResilientRun: \"resilient_run\",\n /** PRIVATE success settle Msg (read only by `agentEvents`, #47). */\n ResilientOk: \"resilient_ok\",\n /** Failure settle Msg — routed to the `fail` verb (back off via retry). */\n ResilientErr: \"resilient_err\",\n\n // --- agent: the wired machine's own entry-point Msgs ---------------------\n /** Begin a run. */\n AgentStart: \"agent_start\",\n /** PRIVATE tool-fan-out success settle Msg (read only by `agentEvents`, #47). */\n AgentToolOk: \"agent_tool_ok\",\n /** Tool-fan-out failure settle Msg. */\n AgentToolErr: \"agent_tool_err\",\n /**\n * Re-fire the one outstanding effect on rehydrate (the do↔agent seam). Fired\n * by `do/host`'s `autoBoot` via the typed `agentBootMsg` constructor, matched\n * by the agent reducer — see `AgentBootPort` in `../agent`.\n */\n AgentBoot: \"agent_boot\",\n\n // --- agent: the context-compaction round-trip (#85) ----------------------\n /**\n * The \"summarize the oldest N turns\" effect Cmd the agent emits when its\n * `CompactionPolicy` asks to compact before a brain call. A DEDICATED\n * round-trip (design B1), distinct from the brain call's `resilient_run`, so\n * compaction is its own honest `Awaiting` state — see `../agent`.\n */\n CompactRun: \"compact_run\",\n /** Compaction round-trip resolved — carries the summary text the fold-back folds. */\n CompactOk: \"compact_ok\",\n /** Compaction round-trip failed (exhausted retry) — surfaced as data, not a stall. */\n CompactErr: \"compact_err\",\n\n // --- token-refresh: the two refresh-result Msgs --------------------------\n /** Refresh port resolved — carries the fresh `Token`. */\n TokenRefreshed: \"token_refreshed\",\n /** Refresh port rejected — carries the rejection (errors are data). */\n TokenRefreshFailed: \"token_refresh_failed\",\n\n // --- authed-call: the 401 trigger ----------------------------------------\n /** The 401 the consumer dispatches when a guarded call comes back unauthorized. */\n Unauthorized: \"unauthorized\",\n} as const;\n\n/** The union of every discriminant string in the protocol. */\nexport type MsgTypeValue = (typeof MsgType)[keyof typeof MsgType];\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/idempotency/index.ts"],"names":[],"mappings":";AAuFO,SAAS,UAAa,IAAA,EAGL;AACtB,EAAA,OAAO;AAAA,IACL,SAAS,EAAC;AAAA,IACV,OAAO,EAAC;AAAA,IACR,UAAU,IAAA,EAAM,QAAA;AAAA,IAChB,OAAO,IAAA,EAAM;AAAA,GACf;AACF;AAUA,SAAS,SAAA,CACP,KAAA,EACA,KAAA,EACA,KAAA,EACS;AACT,EAAA,IAAI,KAAA,KAAU,QAAW,OAAO,KAAA;AAChC,EAAA,OAAO,KAAA,GAAQ,MAAM,IAAA,IAAQ,KAAA;AAC/B;AAQO,SAAS,IAAA,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,KAAA,EAAO,MAAM,KAAK,CAAA;AAC7C;AASO,SAAS,MAAA,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,KAAA,EAAO,MAAM,KAAK,CAAA;AAC5D,IAAA,OAAO,MAAA;AACT,EAAA,OAAO,KAAA,CAAM,KAAA;AACf;AAQO,SAAS,YAAA,CACd,OACA,KAAA,EACqB;AACrB,EAAA,IAAI,KAAA,CAAM,KAAA,KAAU,MAAA,EAAW,OAAO,KAAA;AACtC,EAAA,MAAM,cAAmD,EAAC;AAC1D,EAAA,MAAM,YAAsB,EAAC;AAG7B,EAAA,KAAA,MAAW,GAAA,IAAO,MAAM,KAAA,EAAO;AAC7B,IAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA;AAC/B,IAAA,IAAI,UAAU,MAAA,EAAW;AACzB,IAAA,IAAI,SAAA,CAAU,KAAA,EAAO,KAAA,EAAO,KAAA,CAAM,KAAK,CAAA,EAAG;AAC1C,IAAA,WAAA,CAAY,GAAG,CAAA,GAAI,KAAA;AACnB,IAAA,SAAA,CAAU,KAAK,GAAG,CAAA;AAAA,EACpB;AACA,EAAA,OAAO,EAAE,GAAG,KAAA,EAAO,OAAA,EAAS,WAAA,EAAa,OAAO,SAAA,EAAU;AAC5D;AAoBO,SAAS,QAAA,CACd,KAAA,EACA,GAAA,EACA,KAAA,EACA,KAAA,EACqB;AAErB,EAAA,MAAM,MAAA,GAAS,YAAA,CAAa,KAAA,EAAO,KAAK,CAAA;AAKxC,EAAA,MAAM,WAAA,GAAmD;AAAA,IACvD,GAAG,MAAA,CAAO;AAAA,GACZ;AACA,EAAA,WAAA,CAAY,GAAG,CAAA,GAAI,EAAE,KAAA,EAAO,MAAM,KAAA,EAAM;AACxC,EAAA,MAAM,YAAY,MAAA,CAAO,KAAA,CAAM,OAAO,CAAC,CAAA,KAAM,MAAM,GAAG,CAAA;AACtD,EAAA,SAAA,CAAU,KAAK,GAAG,CAAA;AAKlB,EAAA,IAAI,KAAA,CAAM,aAAa,MAAA,EAAW;AAChC,IAAA,OAAO,SAAA,CAAU,MAAA,GAAS,KAAA,CAAM,QAAA,EAAU;AACxC,MAAA,MAAM,MAAA,GAAS,UAAU,KAAA,EAAM;AAC/B,MAAA,IAAI,WAAW,MAAA,EAAW;AAC1B,MAAA,OAAO,YAAY,MAAM,CAAA;AAAA,IAC3B;AAAA,EACF;AAEA,EAAA,OAAO,EAAE,GAAG,KAAA,EAAO,OAAA,EAAS,WAAA,EAAa,OAAO,SAAA,EAAU;AAC5D","file":"chunk-K575PT4S.js","sourcesContent":["/**\n * @demlik/tea/idempotency — dedupe-by-key + last-result cache as pure state + ops.\n *\n * Same shape as `@demlik/tea/work-queue`'s `ops.ts` and `@demlik/tea/rate-limit`:\n * a state type plus pure transition functions. Host-agnostic by construction —\n * no `Date.now()`, no timers, no I/O. The caller injects the clock (`nowMs`)\n * and wires the result into its own reducer / interpret. This keeps the store\n * inside TEA's invariant 2 (transitions are pure: same inputs → same outputs,\n * no `Date.now`) so a consumer can fold it through `update` and exercise it\n * with `replay` / the pbt fold without a fake clock.\n *\n * Every op returns a NEW store value and never mutates its input (invariant 1 —\n * state is a value, not a place). `seen` / `recall` are read-shaped predicates;\n * `remember` / `evictExpired` return a new store.\n *\n * The use case: a webhook receiver, a retried queue message, or a repeated user\n * action arrives carrying an idempotency key. `seen(store, key, now)` answers\n * \"did I already process this?\"; `recall(store, key, now)` returns the cached\n * result so the duplicate request gets the SAME response the original produced\n * rather than re-running the side effect. `remember(store, key, value, now)`\n * records the key + its result after the first run.\n *\n * Two eviction policies keep the store bounded, both injected at `initStore`:\n *\n * - **`ttlMs`** — an entry expires `ttlMs` after the `atMs` it was remembered\n * at. `seen` / `recall` treat an expired entry as absent; `evictExpired`\n * (and every `remember`) physically drops it. Without a TTL, entries live\n * until evicted by capacity. The cutoff is half-open: an entry exactly\n * `ttlMs` old is expired (`age >= ttlMs` drops it), matching `rate-limit`'s\n * window edge — a key remembered at `t` no longer counts at `t + ttlMs`.\n * - **`capacity`** — at most `capacity` entries are retained. When a\n * `remember` would push the count past `capacity` (after expiry eviction),\n * the OLDEST entries by insertion `order` are dropped until the count fits.\n * Without a capacity, the store grows unbounded (TTL alone bounds it).\n *\n * `order` is the insertion-order index that capacity eviction reads — the keys\n * oldest-first. It is the store's own LRU-by-insertion ledger, kept in lockstep\n * with `entries` so `entries` never needs to be sorted to find the oldest.\n *\n * **Overwrite semantics** (decided + pinned by tests): re-`remember`ing an\n * existing key is an UPDATE, not a no-op. It (1) overwrites `value`, (2)\n * refreshes `atMs` to the new `nowMs` — so the TTL clock restarts and a\n * frequently-seen key never expires — and (3) moves the key to the NEWEST\n * position in `order` — so capacity eviction treats a re-remembered key as\n * fresh, not as a stale candidate for eviction. This makes `remember` behave\n * like \"touch\": a key actively being re-seen is kept alive on both the TTL and\n * capacity axes.\n *\n * NOT exported from the package root — reached via the `@demlik/tea/idempotency`\n * subpath, same one-shape-per-package rule as `work-queue`, `rate-limit`, and\n * `do`.\n */\n\n/**\n * A cached entry: the result `value` the original request produced and the\n * clock reading `atMs` it was remembered at. `atMs` is the TTL anchor — expiry\n * is measured from here, and a re-`remember` refreshes it. Both fields are\n * plain data so the whole store is trivially JSON-serializable into a\n * consumer's `Store<S>`.\n */\nexport interface IdempotencyEntry<V> {\n readonly value: V;\n readonly atMs: number;\n}\n\n/**\n * The dedupe store: a map of `key → entry`, an insertion-`order` ledger\n * (oldest-first) that capacity eviction reads, and the two optional bounds.\n *\n * `entries` and `order` are kept in lockstep — every key in `order` has an\n * entry and vice versa. `capacity` / `ttlMs` are carried on the store (not\n * passed per-op) so a single value fully describes the policy; a consumer\n * persists the store and rehydrates the same bounds without re-deriving them.\n */\nexport interface IdempotencyStore<V> {\n readonly entries: Readonly<Record<string, IdempotencyEntry<V>>>;\n readonly order: readonly string[];\n readonly capacity?: number;\n readonly ttlMs?: number;\n}\n\n/**\n * Create an empty store. `capacity` and `ttlMs` are both optional — omit both\n * for an unbounded cache, supply either or both to bound by count, age, or\n * both. Stored on the returned value so every later op reads the policy off\n * the store rather than from a re-passed argument.\n */\nexport function initStore<V>(opts?: {\n capacity?: number;\n ttlMs?: number;\n}): IdempotencyStore<V> {\n return {\n entries: {},\n order: [],\n capacity: opts?.capacity,\n ttlMs: opts?.ttlMs,\n };\n}\n\n/**\n * Whether `entry` (remembered at `entry.atMs`) is expired at `nowMs` under\n * `ttlMs`. Half-open: an entry exactly `ttlMs` old is expired (`age >= ttlMs`),\n * matching `rate-limit`'s window-edge cutoff. A `nowMs` before `atMs` (clock\n * skewed backwards) yields a negative age — never expired, the safe direction\n * (a duplicate is still deduped rather than re-run). When `ttlMs` is undefined\n * the entry never expires by age.\n */\nfunction isExpired<V>(\n entry: IdempotencyEntry<V>,\n nowMs: number,\n ttlMs: number | undefined,\n): boolean {\n if (ttlMs === undefined) return false;\n return nowMs - entry.atMs >= ttlMs;\n}\n\n/**\n * True iff `key` is present AND not expired at `nowMs`. This is the dedupe\n * predicate: a webhook / retried message / repeated action whose key returns\n * `true` has already been processed and should be short-circuited (paired with\n * `recall` to return the original result). PURE — reads only, never mutates.\n */\nexport function seen<V>(\n store: IdempotencyStore<V>,\n key: string,\n nowMs: number,\n): boolean {\n const entry = store.entries[key];\n if (entry === undefined) return false;\n return !isExpired(entry, nowMs, store.ttlMs);\n}\n\n/**\n * The cached `value` for `key` if present and unexpired at `nowMs`, else\n * `undefined`. Use after `seen` returns `true` to replay the original result to\n * a duplicate request. An expired entry reads as absent (returns `undefined`)\n * even though it is physically still in `entries` until the next `remember` /\n * `evictExpired` drops it. PURE — reads only, never mutates.\n */\nexport function recall<V>(\n store: IdempotencyStore<V>,\n key: string,\n nowMs: number,\n): V | undefined {\n const entry = store.entries[key];\n if (entry === undefined || isExpired(entry, nowMs, store.ttlMs))\n return undefined;\n return entry.value;\n}\n\n/**\n * Drop every entry expired at `nowMs` (older than `ttlMs`). Returns a NEW store\n * with the survivors; `order` is rebuilt to drop the same keys, preserving the\n * relative insertion order of the survivors. A no-TTL store returns an\n * equivalent store (nothing ages out). PURE — the input store is never mutated.\n */\nexport function evictExpired<V>(\n store: IdempotencyStore<V>,\n nowMs: number,\n): IdempotencyStore<V> {\n if (store.ttlMs === undefined) return store;\n const nextEntries: Record<string, IdempotencyEntry<V>> = {};\n const nextOrder: string[] = [];\n // Walk `order` (not `Object.keys`) so the surviving order ledger keeps the\n // exact relative insertion order capacity eviction depends on.\n for (const key of store.order) {\n const entry = store.entries[key];\n if (entry === undefined) continue; // defensive: order/entries kept in lockstep\n if (isExpired(entry, nowMs, store.ttlMs)) continue;\n nextEntries[key] = entry;\n nextOrder.push(key);\n }\n return { ...store, entries: nextEntries, order: nextOrder };\n}\n\n/**\n * Record `key → value` at `nowMs`, then enforce both bounds. PURE — returns a\n * new store; the input is untouched.\n *\n * Order of operations (each step matters):\n * 1. **Expiry eviction** — drop everything older than `ttlMs` first, so an\n * expired key never occupies a capacity slot the new entry could take, and\n * a stale entry under the same `key` is cleared before the overwrite.\n * 2. **Insert / overwrite** — write the new entry. If `key` already exists,\n * this is an UPDATE: `value` is overwritten, `atMs` is refreshed to\n * `nowMs` (restarting the TTL clock), and the key is moved to the NEWEST\n * position in `order` (removed from wherever it sat, appended at the end).\n * See the module-level \"Overwrite semantics\" note.\n * 3. **Capacity eviction** — if the entry count now exceeds `capacity`, drop\n * the OLDEST keys by `order` until the count fits. Because step 2 moves a\n * re-remembered key to the newest position, an actively re-seen key is\n * never the eviction victim.\n */\nexport function remember<V>(\n store: IdempotencyStore<V>,\n key: string,\n value: V,\n nowMs: number,\n): IdempotencyStore<V> {\n // Step 1 — expire first so stale entries free capacity slots before insert.\n const pruned = evictExpired(store, nowMs);\n\n // Step 2 — insert/overwrite. Drop any prior position of `key` from order,\n // then append it as newest. `delete` on a fresh shallow copy keeps the input\n // store untouched (invariant 1).\n const nextEntries: Record<string, IdempotencyEntry<V>> = {\n ...pruned.entries,\n };\n nextEntries[key] = { value, atMs: nowMs };\n const nextOrder = pruned.order.filter((k) => k !== key);\n nextOrder.push(key);\n\n // Step 3 — capacity eviction. Drop oldest (front of `order`) until the count\n // fits. `capacity <= 0` evicts everything just inserted down to the bound;\n // `undefined` capacity skips eviction entirely.\n if (store.capacity !== undefined) {\n while (nextOrder.length > store.capacity) {\n const oldest = nextOrder.shift();\n if (oldest === undefined) break; // order emptied — nothing left to evict\n delete nextEntries[oldest];\n }\n }\n\n return { ...store, entries: nextEntries, order: nextOrder };\n}\n"]}
|
package/dist/chunk-L57ZOT5K.js
DELETED
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import { describeError } from './chunk-G5E74IJB.js';
|
|
2
|
-
import { historyTracker } from './chunk-ZSUUOEPL.js';
|
|
3
|
-
|
|
4
|
-
// src/extension/bridge.ts
|
|
5
|
-
function channelTypes(channel) {
|
|
6
|
-
return {
|
|
7
|
-
dispatchType: `${channel}:dispatch`,
|
|
8
|
-
hydrateType: `${channel}:hydrate`
|
|
9
|
-
};
|
|
10
|
-
}
|
|
11
|
-
function bridgeRuntime(runtime, { channel, parseMsg, serialize, historySize }) {
|
|
12
|
-
const { dispatchType, hydrateType } = channelTypes(channel);
|
|
13
|
-
const project = serialize ?? ((state) => state);
|
|
14
|
-
const tracker = historySize !== void 0 && historySize > 0 ? historyTracker(runtime, historySize) : null;
|
|
15
|
-
const sendBroadcast = (msg, state) => {
|
|
16
|
-
const envelope = {
|
|
17
|
-
type: channel,
|
|
18
|
-
msg,
|
|
19
|
-
state: project(state)
|
|
20
|
-
};
|
|
21
|
-
chrome.runtime.sendMessage(envelope).catch(() => {
|
|
22
|
-
});
|
|
23
|
-
};
|
|
24
|
-
const unobserve = runtime.observe((msg, state) => {
|
|
25
|
-
sendBroadcast(msg, state);
|
|
26
|
-
});
|
|
27
|
-
const unboot = runtime.onBoot((state) => {
|
|
28
|
-
sendBroadcast(null, state);
|
|
29
|
-
});
|
|
30
|
-
const onMessage = (message, _sender, sendResponse) => {
|
|
31
|
-
if (!message || typeof message !== "object") return void 0;
|
|
32
|
-
const obj = message;
|
|
33
|
-
if (obj.type === hydrateType) {
|
|
34
|
-
let state;
|
|
35
|
-
try {
|
|
36
|
-
state = project(runtime.getState());
|
|
37
|
-
} catch {
|
|
38
|
-
state = null;
|
|
39
|
-
}
|
|
40
|
-
const backlog = tracker ? tracker.snapshot().map((entry) => ({ msg: entry.msg, state: project(entry.state) })) : [];
|
|
41
|
-
const response = { state, backlog };
|
|
42
|
-
sendResponse(response);
|
|
43
|
-
return false;
|
|
44
|
-
}
|
|
45
|
-
if (obj.type === dispatchType) {
|
|
46
|
-
const msg = parseMsg(obj.msg);
|
|
47
|
-
if (msg === null) {
|
|
48
|
-
sendResponse({ ok: false, error: "dispatch msg failed parse" });
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
runtime.dispatch(msg).then(() => {
|
|
52
|
-
sendResponse({ ok: true });
|
|
53
|
-
}).catch((err) => {
|
|
54
|
-
sendResponse({
|
|
55
|
-
ok: false,
|
|
56
|
-
error: describeError(err)
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
return true;
|
|
60
|
-
}
|
|
61
|
-
return void 0;
|
|
62
|
-
};
|
|
63
|
-
chrome.runtime.onMessage.addListener(onMessage);
|
|
64
|
-
return () => {
|
|
65
|
-
unobserve();
|
|
66
|
-
unboot();
|
|
67
|
-
chrome.runtime.onMessage.removeListener(onMessage);
|
|
68
|
-
tracker?.stop();
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
function passThroughMsg(raw) {
|
|
72
|
-
return raw;
|
|
73
|
-
}
|
|
74
|
-
function parseEnvelope(obj, parseState, parseMsg) {
|
|
75
|
-
const state = parseState(obj.state);
|
|
76
|
-
if (state === null) return null;
|
|
77
|
-
let msg;
|
|
78
|
-
if (obj.msg === null || obj.msg === void 0) {
|
|
79
|
-
msg = null;
|
|
80
|
-
} else {
|
|
81
|
-
msg = parseMsg(obj.msg);
|
|
82
|
-
if (msg === null) return null;
|
|
83
|
-
}
|
|
84
|
-
return { msg, state };
|
|
85
|
-
}
|
|
86
|
-
function replayBacklog(raw, parseState, parseMsg, listeners, commitState) {
|
|
87
|
-
if (!Array.isArray(raw)) return;
|
|
88
|
-
for (const entryRaw of raw) {
|
|
89
|
-
if (!entryRaw || typeof entryRaw !== "object") continue;
|
|
90
|
-
const entry = entryRaw;
|
|
91
|
-
const parsed = parseEnvelope(entry, parseState, parseMsg);
|
|
92
|
-
if (parsed === null) continue;
|
|
93
|
-
commitState(parsed.state);
|
|
94
|
-
for (const l of listeners) l(parsed.msg, parsed.state);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
function makeBridgeClient({
|
|
98
|
-
channel,
|
|
99
|
-
parseState,
|
|
100
|
-
parseMsg,
|
|
101
|
-
send,
|
|
102
|
-
senderMatches = () => true
|
|
103
|
-
}) {
|
|
104
|
-
const { dispatchType, hydrateType } = channelTypes(channel);
|
|
105
|
-
let latestState = null;
|
|
106
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
107
|
-
const client = {
|
|
108
|
-
async hydrate() {
|
|
109
|
-
const req = { type: hydrateType };
|
|
110
|
-
const res = await send(req);
|
|
111
|
-
replayBacklog(res?.backlog, parseState, parseMsg, listeners, (next) => {
|
|
112
|
-
latestState = next;
|
|
113
|
-
});
|
|
114
|
-
const raw = res?.state;
|
|
115
|
-
if (raw === void 0 || raw === null) return null;
|
|
116
|
-
const parsed = parseState(raw);
|
|
117
|
-
if (parsed === null) return null;
|
|
118
|
-
latestState = parsed;
|
|
119
|
-
for (const l of listeners) l(null, parsed);
|
|
120
|
-
return parsed;
|
|
121
|
-
},
|
|
122
|
-
async dispatch(msg) {
|
|
123
|
-
const env = { type: dispatchType, msg };
|
|
124
|
-
await send(env);
|
|
125
|
-
},
|
|
126
|
-
subscribe(listener) {
|
|
127
|
-
listeners.add(listener);
|
|
128
|
-
const onMessage = (message, sender) => {
|
|
129
|
-
if (!senderMatches(sender)) return;
|
|
130
|
-
if (!message || typeof message !== "object") return;
|
|
131
|
-
const obj = message;
|
|
132
|
-
if (obj.type !== channel) return;
|
|
133
|
-
const parsed = parseEnvelope(obj, parseState, parseMsg);
|
|
134
|
-
if (parsed === null) return;
|
|
135
|
-
latestState = parsed.state;
|
|
136
|
-
listener(parsed.msg, parsed.state);
|
|
137
|
-
};
|
|
138
|
-
chrome.runtime.onMessage.addListener(onMessage);
|
|
139
|
-
return () => {
|
|
140
|
-
chrome.runtime.onMessage.removeListener(onMessage);
|
|
141
|
-
listeners.delete(listener);
|
|
142
|
-
};
|
|
143
|
-
},
|
|
144
|
-
getSnapshot() {
|
|
145
|
-
return latestState;
|
|
146
|
-
}
|
|
147
|
-
};
|
|
148
|
-
return client;
|
|
149
|
-
}
|
|
150
|
-
function bridgeClient({
|
|
151
|
-
channel,
|
|
152
|
-
parseState,
|
|
153
|
-
parseMsg
|
|
154
|
-
}) {
|
|
155
|
-
return makeBridgeClient({
|
|
156
|
-
channel,
|
|
157
|
-
parseState,
|
|
158
|
-
parseMsg,
|
|
159
|
-
send: (message) => chrome.runtime.sendMessage(message)
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
function bridgeTabClient({
|
|
163
|
-
tabId,
|
|
164
|
-
channel,
|
|
165
|
-
parseState,
|
|
166
|
-
parseMsg
|
|
167
|
-
}) {
|
|
168
|
-
return makeBridgeClient({
|
|
169
|
-
channel,
|
|
170
|
-
parseState,
|
|
171
|
-
parseMsg,
|
|
172
|
-
// SW → content transport: `chrome.runtime.sendMessage` does NOT reach a
|
|
173
|
-
// tab's content script; `chrome.tabs.sendMessage(tabId, …)` does.
|
|
174
|
-
send: (message) => chrome.tabs.sendMessage(tabId, message),
|
|
175
|
-
// Every tab's content script broadcasts through the same SW
|
|
176
|
-
// `chrome.runtime.onMessage`; filter to this tab so a client subscribed
|
|
177
|
-
// to one tab never sees another's broadcasts.
|
|
178
|
-
senderMatches: (sender) => sender.tab?.id === tabId
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
export { bridgeClient, bridgeRuntime, bridgeTabClient, passThroughMsg };
|
|
183
|
-
//# sourceMappingURL=chunk-L57ZOT5K.js.map
|
|
184
|
-
//# sourceMappingURL=chunk-L57ZOT5K.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/extension/bridge.ts"],"names":[],"mappings":"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","file":"chunk-L57ZOT5K.js","sourcesContent":["/**\n * Runtime bridge — expose a `@demlik/tea` runtime running in one chrome context\n * (typically the background service worker) to consumers in other contexts\n * (sidepanel, popup, devtools panel) over `chrome.runtime.sendMessage` /\n * `chrome.runtime.onMessage`.\n *\n * Two halves, one channel name string.\n *\n * ┌──── background (host) ────┐ ┌──── react surface ────┐\n * │ bridgeRuntime(rt, {ch}) │ chrome.* │ useBackgroundRuntime │\n * │ ├ observe → broadcast │ ──────────▶ │ ├ hydrate (initial) │\n * │ └ onMessage(\":dispatch\") │ ◀────────── │ └ dispatch (msgs) │\n * │ └ onMessage(\":hydrate\") │ │ │\n * └────────────────────────────┘ └───────────────────────┘\n *\n * Wire-shape (single `channel` string, three messages):\n *\n * { type: channel, msg, state } // background → surfaces (broadcast)\n * { type: `${channel}:hydrate` } // surface → background (request)\n * { type: `${channel}:dispatch`, msg } // surface → background (input)\n *\n * The `:hydrate` reply is the current `runtime.getState()` snapshot —\n * lets a newly-opened surface render the right view BEFORE the next\n * transition broadcast arrives.\n *\n * The bridge is intentionally agnostic to `chrome.runtime.sendMessage`\n * failure modes (no receiver, sender gone, etc.) — every cross-context send\n * is fire-and-forget with `.catch(() => {})`. Surfaces that need\n * delivery guarantees should layer their own retry, not bake it here.\n */\n\nimport { describeError } from \"../describe-error\";\nimport { type HistoryTracker, historyTracker, type Runtime } from \"../index\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Wire types — internal. Consumers don't import these directly; they pick\n// the channel string and the bridge handles serialization.\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface BroadcastEnvelope<S, M> {\n type: string; // === `channel`\n msg: M | null;\n state: S;\n}\n\ninterface DispatchEnvelope<M> {\n type: string; // === `${channel}:dispatch`\n msg: M;\n}\n\ninterface HydrateRequest {\n type: string; // === `${channel}:hydrate`\n}\n\n/**\n * Wire shape of one entry in the hydrate `backlog`. Mirrors a single\n * broadcast envelope, but without the `type` discriminator (the parent\n * `HydrateResponse` already identified the channel).\n */\ninterface BacklogEntry<V, M> {\n msg: M | null;\n state: V;\n}\n\n/**\n * Single source for the two derived wire-type strings. The `:dispatch` /\n * `:hydrate` suffix convention lives here and nowhere else — `bridgeRuntime`\n * and both clients derive their type constants from this so the convention\n * can't drift across the three functions (or the wire-shape comments above).\n */\nfunction channelTypes(channel: string): {\n dispatchType: string;\n hydrateType: string;\n} {\n return {\n dispatchType: `${channel}:dispatch`,\n hydrateType: `${channel}:hydrate`,\n };\n}\n\ninterface HydrateResponse<S, M> {\n state: S;\n /**\n * The runtime's history (most recent transitions, oldest first) projected\n * through `serialize` if provided. Present when the host runtime was\n * started with `historySize > 0`; an empty array otherwise. The surface\n * replays each entry through registered listeners before firing the\n * current-state marker, so a late subscriber sees the recent msg log AND\n * the current state on connect — not just the current state.\n */\n backlog: BacklogEntry<S, M>[];\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Background side — bridgeRuntime(runtime, { channel, serialize? })\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface BridgeRuntimeOpts<S, M, V = S> {\n channel: string;\n /**\n * REQUIRED parse for inbound `msg` payloads on `:dispatch` envelopes.\n * Chrome runtime messages are an `unknown` boundary (Inv 8) — the same\n * JSON/chrome serialization seam the symmetric client side parses via\n * `BridgeClientOpts.parseMsg`. An untrusted surface can drive the host\n * reducer with an arbitrary `M`, so the host parses too — never casts. A\n * `msg` that parses to `null` is dropped: the dispatch never reaches the\n * reducer and the surface's `dispatch()` rejects with `{ ok: false }`.\n *\n * Hosts that trust the dispatch channel and treat the msg as opaque opt\n * out EXPLICITLY by passing the {@link passThroughMsg} helper — the unsound\n * cast is then a visible call-site decision, not a silent default.\n */\n parseMsg: (raw: unknown) => M | null;\n /**\n * Optional projection from the runtime's live state `S` to a JSON-safe\n * view `V` that goes over the wire (broadcast + hydrate). Required when\n * `S` carries non-serializable values (DOM refs, Map, Date, …).\n *\n * Identity passthrough by default — existing callers with POJO state pass\n * `serialize` unset and `V = S`.\n *\n * Wire shape with `serialize`:\n * { type: channel, msg, state: serialize(currentState) } // broadcast\n * { type: hydrate } → { state: serialize(currentState) } // hydrate\n *\n * Dispatch is NOT affected — it carries `M` end-to-end. Surfaces parse the\n * `V` they receive (they should pick a type parameter matching the\n * projection).\n */\n serialize?: (state: S) => V;\n /**\n * If > 0, the bridge composes a `historyTracker(runtime, size)` over the\n * runtime and returns the captured `(msg, state)` backlog in every\n * `:hydrate` reply. Late subscribers receive recent traffic on connect —\n * the inspector then sees prior tool calls / phase transitions instead\n * of just the current state snapshot.\n *\n * Defaults to 0 (no tracker, empty backlog). Composition stays opt-in:\n * the substrate is untouched, the dispatch loop is untouched, and the\n * bridge attaches the tracker only when this opt is set. Cleanup is\n * the bridge's responsibility — the disposer returned by `bridgeRuntime`\n * stops the tracker along with the observer.\n *\n * Memory cost: ~size × (msg + state) bytes, in-memory only, dies with\n * the bridge. Privacy: backlog entries flow to every `:hydrate` reply —\n * any surface that can hydrate sees them. Do not enable on bridges\n * carrying user secrets.\n */\n historySize?: number;\n}\n\n/**\n * Install the bridge for a runtime. Returns a cleanup function that removes\n * the observer and the chrome listener.\n *\n * - **Broadcast:** every (msg, state) transition emits\n * `{ type: channel, msg, state }` via `chrome.runtime.sendMessage`. The\n * send is fire-and-forget — when no surface is listening, chrome.runtime\n * rejects with \"Could not establish connection. Receiving end does not\n * exist.\" which we swallow. If `serialize` is provided, `state` carries\n * `serialize(state)` instead of the raw state.\n * - **Hydrate:** `{ type: \":hydrate\" }` is answered via `sendResponse` with\n * the current (optionally projected) `runtime.getState()`. If the runtime\n * is mid-boot (store load not yet resolved), `getState()` throws — we\n * swallow and reply with `state: null`; surfaces should treat null as\n * \"not ready yet\" and wait for the first broadcast.\n * - **Dispatch:** `{ type: \":dispatch\", msg }` is parsed through `parseMsg`\n * (Inv 8 — inbound msgs are an `unknown` boundary) and, if non-null, routes\n * to `runtime.dispatch`. Returns `true` from the listener so chrome keeps the\n * message channel open until dispatch's promise settles (we ack with\n * `{ ok: true }`). A msg that fails the parse is dropped — the reducer never\n * sees it and we reply `{ ok: false }`.\n */\nexport function bridgeRuntime<S, M extends { type: string }, V = S>(\n runtime: Runtime<S, M>,\n { channel, parseMsg, serialize, historySize }: BridgeRuntimeOpts<S, M, V>,\n): () => void {\n const { dispatchType, hydrateType } = channelTypes(channel);\n // Default to identity passthrough when no projection is provided. The\n // type parameter defaults `V = S` so callers with no projection see no\n // change in the wire-shape — the broadcast still carries `S`.\n const project: (state: S) => V =\n serialize ?? ((state: S): V => state as unknown as V);\n // Compose a history tracker only when the caller opted in. Substrate\n // (`@demlik/tea`'s `run()`) knows nothing about history — the tracker is a\n // pure composition over `runtime.observe(...)`, owned by the bridge.\n // `null` when unset means \"no backlog in :hydrate replies\"; the wire\n // shape stays uniform (backlog is always an array, possibly empty).\n const tracker: HistoryTracker<S, M> | null =\n historySize !== undefined && historySize > 0\n ? historyTracker(runtime, historySize)\n : null;\n\n // The broadcast wire still carries `msg: M | null` — surfaces depend on the\n // boot frame (`msg: null`) to hydrate from the bridge. Applied transitions now\n // arrive via `observe` with a total `msg`; the boot frame arrives via `onBoot`\n // (#47). One helper builds the envelope for both.\n const sendBroadcast = (msg: M | null, state: S): void => {\n const envelope: BroadcastEnvelope<V, M | null> = {\n type: channel,\n msg,\n state: project(state),\n };\n chrome.runtime.sendMessage(envelope).catch(() => {\n // No surface listening — drop silently. Costs are negligible at\n // typical audit-event rates; surfaces poll on hydrate on open.\n });\n };\n const unobserve = runtime.observe((msg, state) => {\n sendBroadcast(msg, state);\n });\n const unboot = runtime.onBoot((state) => {\n sendBroadcast(null, state);\n });\n\n const onMessage = (\n message: unknown,\n _sender: chrome.runtime.MessageSender,\n sendResponse: (response: unknown) => void,\n ): boolean | undefined => {\n if (!message || typeof message !== \"object\") return undefined;\n const obj = message as Record<string, unknown>;\n\n if (obj.type === hydrateType) {\n let state: V | null;\n try {\n state = project(runtime.getState());\n } catch {\n // Runtime still booting (store.load pending). Surfaces fall back to\n // waiting for the first broadcast.\n state = null;\n }\n // Read the tracker's snapshot and project each entry's state through\n // `serialize`. When `historySize` was unset (tracker === null) we\n // send `[]` — uniform wire shape means surfaces never branch on\n // \"did the bridge enable history\" — they always iterate.\n const backlog: BacklogEntry<V, M>[] = tracker\n ? tracker\n .snapshot()\n .map((entry) => ({ msg: entry.msg, state: project(entry.state) }))\n : [];\n const response: HydrateResponse<V | null, M> = { state, backlog };\n sendResponse(response);\n return false;\n }\n\n if (obj.type === dispatchType) {\n // Inv 8: the inbound msg crosses the same JSON/chrome boundary the\n // client side parses — parse here too, never cast. A null parse drops\n // the dispatch (the reducer never sees an arbitrary/malformed `M`) and\n // the surface's dispatch() rejects on the `{ ok: false }` reply.\n const msg = parseMsg(obj.msg);\n if (msg === null) {\n sendResponse({ ok: false, error: \"dispatch msg failed parse\" });\n return false;\n }\n runtime\n .dispatch(msg)\n .then(() => {\n sendResponse({ ok: true });\n })\n .catch((err: unknown) => {\n sendResponse({\n ok: false,\n error: describeError(err),\n });\n });\n return true; // keep channel open for async sendResponse\n }\n\n return undefined;\n };\n\n chrome.runtime.onMessage.addListener(onMessage);\n\n return () => {\n unobserve();\n unboot();\n chrome.runtime.onMessage.removeListener(onMessage);\n // Detach the history observer too — leaving it attached after the\n // bridge tears down would silently retain memory and keep observing\n // a runtime whose backlog can no longer reach any consumer.\n tracker?.stop();\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Surface side — the bridge half that lives in a React surface. Exported as\n// a pure helper so the React adapter (or a test) wires the listeners and the\n// hydrate round-trip with the same wire format the host emits.\n//\n// Note: this file deliberately has NO React import. The React hook lives in\n// `react.ts` and consumes these helpers. Keeps the bridge testable in plain\n// vitest without happy-dom + React renderers.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The explicit opt-out of inbound msg parsing: an identity pass-through that\n * casts the raw `unknown` to `M`. Surfaces that gate only on state and treat\n * the broadcast msg as advisory pass this as `parseMsg` to acknowledge — at\n * the call site, in the types — that they are NOT parsing the msg boundary.\n *\n * This is the same behavior the old optional-`parseMsg` default had, but the\n * unsound cast now lives at one named, greppable, opt-in seam instead of being\n * the silent default for everyone. (Inv 8: the boundary parses; this helper is\n * the documented exception, chosen explicitly.)\n */\nexport function passThroughMsg<M>(raw: unknown): M | null {\n return raw as M;\n}\n\nexport interface BridgeClient<S, M> {\n /** Send the hydrate request; resolves with the initial snapshot (or null\n * if the host is still booting OR the response failed to parse). Also\n * primes `getSnapshot()` so React's `useSyncExternalStore` sees the\n * hydrated value on the next render. */\n hydrate(): Promise<S | null>;\n /** Send a dispatch envelope; resolves once the host's dispatch settles. */\n dispatch(msg: M): Promise<void>;\n /** Subscribe to broadcasts. Returns a cleanup function. Broadcasts whose\n * payload fails `parseState` or `parseMsg` are dropped silently — the\n * listener is not called. Each successful broadcast updates\n * `getSnapshot()`. */\n subscribe(listener: (msg: M | null, state: S) => void): () => void;\n /** Synchronous accessor for the latest known state, or `null` until the\n * first successful hydrate or broadcast lands. Required reference stability\n * for `useSyncExternalStore`: returns the SAME object reference when the\n * underlying state hasn't changed since the prior call. */\n getSnapshot(): S | null;\n}\n\nexport interface BridgeClientOpts<S, M> {\n channel: string;\n /**\n * REQUIRED parse for inbound `state` payloads (from `:hydrate` reply AND\n * every broadcast). Chrome runtime messages are an `unknown` boundary\n * (Inv 8) — the surface is the only party that knows the domain type `S`,\n * so it owns the boundary parse. Returning `null` drops the payload\n * silently (subscriber not called; hydrate resolves to `null`). The\n * parser MUST NOT throw — model rejection as `null`.\n */\n parseState: (raw: unknown) => S | null;\n /**\n * REQUIRED parse for inbound `msg` payloads on broadcasts. Chrome runtime\n * messages are an `unknown` boundary (Inv 8) just like the `state` payload,\n * so the msg crosses the same JSON serialization seam and must be parsed —\n * never cast. Returning `null` drops the broadcast silently.\n *\n * Surfaces that treat the msg union as advisory and only gate on state opt\n * out EXPLICITLY by passing the {@link passThroughMsg} helper — the opt-out\n * is now a visible call-site decision, not a hidden default that lets an\n * unparsed `unknown` reach listeners typed as `M`.\n */\n parseMsg: (raw: unknown) => M | null;\n}\n\n/**\n * The state+msg boundary parse (Inv 8) for one inbound envelope, in ONE\n * place. Chrome runtime messages are an `unknown` boundary: a failed\n * `parseState` drops the envelope (null state is indistinguishable from\n * \"host still booting\" and is not a useful signal), and a non-null `msg`\n * that fails `parseMsg` drops it too. `msg: null | undefined` on the wire is\n * the boot/current-state marker and passes through as `null`.\n *\n * Returns `null` to signal \"drop\"; callers translate that to `return`\n * (live `subscribe`) or `continue` (backlog replay). Living here keeps the\n * live-broadcast and backlog-replay paths from drifting apart.\n */\nfunction parseEnvelope<S, M extends { type: string }>(\n obj: Record<string, unknown>,\n parseState: (raw: unknown) => S | null,\n parseMsg: (raw: unknown) => M | null,\n): { msg: M | null; state: S } | null {\n const state = parseState(obj.state);\n if (state === null) return null;\n let msg: M | null;\n if (obj.msg === null || obj.msg === undefined) {\n msg = null;\n } else {\n msg = parseMsg(obj.msg);\n if (msg === null) return null;\n }\n return { msg, state };\n}\n\n/**\n * Shared backlog-replay helper used by both bridge clients. Iterates raw\n * entries from the wire, runs each through the same boundary-parse rules a\n * live broadcast would (via {@link parseEnvelope}), and fires the listeners\n * with the parsed `(msg, state)` pair. Updates the closure-held `latestState`\n * via `commitState` so post-replay `getSnapshot()` returns the most recent\n * backlog state if the current-state marker is absent or unparseable.\n *\n * Extracted so the two clients can't drift in how they parse backlog\n * payloads — both must stay symmetric with their live `subscribe` parse.\n */\nfunction replayBacklog<S, M extends { type: string }>(\n raw: unknown,\n parseState: (raw: unknown) => S | null,\n parseMsg: (raw: unknown) => M | null,\n listeners: Set<(msg: M | null, state: S) => void>,\n commitState: (next: S) => void,\n): void {\n if (!Array.isArray(raw)) return;\n for (const entryRaw of raw) {\n if (!entryRaw || typeof entryRaw !== \"object\") continue;\n const entry = entryRaw as Record<string, unknown>;\n const parsed = parseEnvelope(entry, parseState, parseMsg);\n if (parsed === null) continue;\n commitState(parsed.state);\n for (const l of listeners) l(parsed.msg, parsed.state);\n }\n}\n\n/**\n * Options for the shared client factory. On top of the public\n * `BridgeClient` contract (channel + the two boundary parsers), the factory\n * takes the two axes on which the runtime and tab clients actually differ:\n *\n * - `send` abstracts the request transport — `chrome.runtime.sendMessage`\n * for the runtime client, `chrome.tabs.sendMessage(tabId, …)` for the tab\n * client — used by both `hydrate()` and `dispatch()`.\n * - `senderMatches` filters inbound broadcasts by their `sender`. Defaults\n * to accept-all (runtime client); the tab client passes\n * `sender => sender.tab?.id === tabId` so a per-tab client only sees its\n * own tab's broadcasts.\n */\ninterface MakeBridgeClientOpts<S, M> {\n channel: string;\n parseState: (raw: unknown) => S | null;\n parseMsg: (raw: unknown) => M | null;\n send: (message: unknown) => Promise<unknown>;\n senderMatches?: (sender: chrome.runtime.MessageSender) => boolean;\n}\n\n/**\n * The single client implementation behind {@link bridgeClient} and\n * {@link bridgeTabClient}. Both public constructors are thin wrappers that\n * pick the transport (`send`) and, for the tab client, the sender filter.\n * Everything else — the `latestState`/`listeners` closures, the hydrate\n * round-trip + backlog replay, dispatch, the live-broadcast parse, and\n * `getSnapshot` reference stability — lives here so the two clients cannot\n * drift.\n */\nfunction makeBridgeClient<S, M extends { type: string }>({\n channel,\n parseState,\n parseMsg,\n send,\n senderMatches = () => true,\n}: MakeBridgeClientOpts<S, M>): BridgeClient<S, M> {\n const { dispatchType, hydrateType } = channelTypes(channel);\n // Closure-held latest snapshot. `useSyncExternalStore` reads this via\n // `getSnapshot`; the reference is stable across calls until a hydrate\n // reply or broadcast overwrites it. Identity matters: returning a new\n // object for the same logical state would loop React. `null` is the\n // pre-hydrate sentinel.\n let latestState: S | null = null;\n // Each active subscribe() registers a listener. Hydrate replies notify\n // every subscriber with `msg: null` (no host transition occurred — the\n // change source is the hydrate round-trip) so `useSyncExternalStore`\n // sees the snapshot update on first mount.\n const listeners = new Set<(msg: M | null, state: S) => void>();\n\n const client: BridgeClient<S, M> = {\n async hydrate(): Promise<S | null> {\n const req: HydrateRequest = { type: hydrateType };\n const res = (await send(req)) as\n | { state: unknown; backlog?: unknown }\n | undefined;\n // Replay the backlog FIRST — listeners observing for a msg log see\n // historical (msg, state) pairs in oldest-first order before the\n // current-state marker. Backlog entries that fail boundary parse are\n // dropped silently (same posture as live broadcasts). When the host\n // didn't enable history, `backlog` is `[]` and this loop is a no-op.\n replayBacklog(res?.backlog, parseState, parseMsg, listeners, (next) => {\n latestState = next;\n });\n const raw = res?.state;\n if (raw === undefined || raw === null) return null;\n const parsed = parseState(raw);\n if (parsed === null) return null;\n latestState = parsed;\n // Fire the current-state marker (msg = null) — distinct from any\n // backlog entry. Surfaces that show \"X just connected\" key off this.\n for (const l of listeners) l(null, parsed);\n return parsed;\n },\n\n async dispatch(msg: M): Promise<void> {\n const env: DispatchEnvelope<M> = { type: dispatchType, msg };\n await send(env);\n },\n\n subscribe(listener: (msg: M | null, state: S) => void): () => void {\n listeners.add(listener);\n const onMessage = (\n message: unknown,\n sender: chrome.runtime.MessageSender,\n ): void => {\n if (!senderMatches(sender)) return;\n if (!message || typeof message !== \"object\") return;\n const obj = message as Record<string, unknown>;\n if (obj.type !== channel) return;\n const parsed = parseEnvelope(obj, parseState, parseMsg);\n if (parsed === null) return;\n latestState = parsed.state;\n listener(parsed.msg, parsed.state);\n };\n chrome.runtime.onMessage.addListener(onMessage);\n return () => {\n chrome.runtime.onMessage.removeListener(onMessage);\n listeners.delete(listener);\n };\n },\n\n getSnapshot(): S | null {\n return latestState;\n },\n };\n\n return client;\n}\n\nexport function bridgeClient<S, M extends { type: string }>({\n channel,\n parseState,\n parseMsg,\n}: BridgeClientOpts<S, M>): BridgeClient<S, M> {\n return makeBridgeClient({\n channel,\n parseState,\n parseMsg,\n send: (message) => chrome.runtime.sendMessage(message),\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// SW-side client of a content-script-hosted runtime.\n//\n// `bridgeClient` above sends via `chrome.runtime.sendMessage`, which lands on\n// the SW's `chrome.runtime.onMessage`. That's the surface-to-SW direction.\n//\n// For the SW-to-content direction we need:\n// - SENDS via `chrome.tabs.sendMessage(tabId, …)` — `chrome.runtime.sendMessage`\n// does NOT reach content scripts running in a tab.\n// - BROADCAST subscribers must filter on `sender.tab?.id === tabId`, since\n// every tab's content script broadcasts through the same SW\n// `chrome.runtime.onMessage`. Without the filter, a SW client subscribed\n// to \"tab 17\" would also see broadcasts from tabs 22, 31, ….\n//\n// Shape mirrors `BridgeClient<S, M>` so consumers can pick the constructor by\n// context and use the rest of the surface identically.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface BridgeTabClientOpts<S, M> {\n tabId: number;\n channel: string;\n /**\n * REQUIRED parse for inbound `state` payloads. See `BridgeClientOpts.parseState`\n * — same contract, same rationale. Chrome runtime messages from a tab are\n * an `unknown` boundary; the SW client owns the parse.\n */\n parseState: (raw: unknown) => S | null;\n /**\n * REQUIRED parse for inbound `msg` payloads. See `BridgeClientOpts.parseMsg`\n * — same contract, same rationale. Opt out of msg parsing with\n * {@link passThroughMsg}.\n */\n parseMsg: (raw: unknown) => M | null;\n}\n\n/**\n * SW-side client of a `bridgeRuntime` hosted in a content script.\n *\n * Identical contract to `bridgeClient` (same `BridgeClient<S, M>` shape).\n * Differences are confined to transport:\n *\n * - `hydrate()` / `dispatch(msg)` send via `chrome.tabs.sendMessage(tabId, …)`.\n * If the tab is gone, `chrome.tabs.sendMessage` rejects (same shape as\n * `chrome.runtime.sendMessage` with no receiver) — the promise propagates\n * the rejection. Callers decide whether to retry, drop, or surface; the\n * bridge has no opinion. Fire-and-forget posture matches `bridgeRuntime`.\n * - `subscribe(listener)` registers a `chrome.runtime.onMessage` listener\n * filtered by both `envelope.type === channel` AND `sender.tab?.id === tabId`,\n * so broadcasts from other tabs are ignored (no cross-contamination across\n * per-tab content-script runtimes).\n */\nexport function bridgeTabClient<S, M extends { type: string }>({\n tabId,\n channel,\n parseState,\n parseMsg,\n}: BridgeTabClientOpts<S, M>): BridgeClient<S, M> {\n return makeBridgeClient({\n channel,\n parseState,\n parseMsg,\n // SW → content transport: `chrome.runtime.sendMessage` does NOT reach a\n // tab's content script; `chrome.tabs.sendMessage(tabId, …)` does.\n send: (message) => chrome.tabs.sendMessage(tabId, message),\n // Every tab's content script broadcasts through the same SW\n // `chrome.runtime.onMessage`; filter to this tab so a client subscribed\n // to one tab never sees another's broadcasts.\n senderMatches: (sender) => sender.tab?.id === tabId,\n });\n}\n"]}
|
package/dist/chunk-MU6MCIAC.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { patchItemOp, markDoneOp, claimNextOp, enqueueOp } from './chunk-FV3Z5MBE.js';
|
|
2
|
-
|
|
3
|
-
// src/work-queue/adapter.ts
|
|
4
|
-
function queueAdapter() {
|
|
5
|
-
return {
|
|
6
|
-
enqueue: (queue, input, now, id) => enqueueOp(queue, input, now, id),
|
|
7
|
-
claim: (queue, now) => claimNextOp(queue, now),
|
|
8
|
-
markDone: (queue, id) => markDoneOp(queue, id),
|
|
9
|
-
patch: (queue, id, patch) => patchItemOp(queue, id, patch)
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export { queueAdapter };
|
|
14
|
-
//# sourceMappingURL=chunk-MU6MCIAC.js.map
|
|
15
|
-
//# sourceMappingURL=chunk-MU6MCIAC.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/work-queue/adapter.ts"],"names":[],"mappings":";;;AAqFO,SAAS,YAAA,GAAmC;AACjD,EAAA,OAAO;AAAA,IACL,OAAA,EAAS,CAAC,KAAA,EAAO,KAAA,EAAO,GAAA,EAAK,OAAO,SAAA,CAAa,KAAA,EAAO,KAAA,EAAO,GAAA,EAAK,EAAE,CAAA;AAAA,IACtE,OAAO,CAAC,KAAA,EAAO,GAAA,KAAQ,WAAA,CAAe,OAAO,GAAG,CAAA;AAAA,IAChD,UAAU,CAAC,KAAA,EAAO,EAAA,KAAO,UAAA,CAAc,OAAO,EAAE,CAAA;AAAA,IAChD,KAAA,EAAO,CAAC,KAAA,EAAO,EAAA,EAAI,UAAU,WAAA,CAAe,KAAA,EAAO,IAAI,KAAK;AAAA,GAC9D;AACF","file":"chunk-MU6MCIAC.js","sourcesContent":["/**\n * @demlik/tea/work-queue/adapter — the verb seam over the pure queue ops.\n *\n * The pure ops in `./ops` are the BLESSED transitions, but their names and\n * argument tuples (`enqueueOp(queue, input, now, id)`, `claimNextOp(queue,\n * now)`, …) and their `{ next, item }` / `{ next, changed }` result shapes are\n * the STORE's vocabulary, not the consumer's. An L2 composition that owns a\n * `QueueItem<I>[]` slice (`monitored-run`, `saga`, `idempotent-intake`,\n * `fan-out`) used to import those ops by name and call them directly, so a\n * change to the op surface — or to `QueueItem`'s fields — cascaded to every\n * call site.\n *\n * `QueueAdapter<I>` is the insulating interface: four VERBS (`enqueue` /\n * `claim` / `markDone` / `patch`) that consumers depend on instead of the raw\n * ops. The verbs keep the ops' pure `(queue, …) => { next, … }` contract — they\n * are pure array transforms, no I/O, no `Date.now` (the clock is injected as\n * `now`, exactly as the ops require it) — so a consumer folding the adapter\n * through its reducer stays inside invariant 2 (pure transitions).\n *\n * `queueAdapter<I>()` is the canonical implementation, a thin binding of the\n * blessed ops. Because the verbs are the only surface consumers touch, a\n * `QueueItem` shape change (a new field, a renamed status, a different op\n * signature) is now absorbed HERE — re-bind the verb once — rather than at the\n * 4 consumer call sites. The `patch` verb deliberately takes the same\n * `(item) => item` closure `patchItemOp` does, so the one place a `QueueItem`\n * literal is spread stays the consumer's patch closure, never a re-rolled\n * record.\n *\n * This is an INTERNAL seam (no public-API port): consumers import the adapter\n * from this subpath the same way they imported the ops. The ops remain\n * exported for the in-process `createQueue` adapter (`./index`) and for any\n * call site that genuinely wants the op directly.\n */\n\nimport type { QueueItem } from \"./index\";\nimport { claimNextOp, enqueueOp, markDoneOp, patchItemOp } from \"./ops\";\n\n/**\n * The verb interface over a `QueueItem<I>[]` slice. Each verb mirrors a blessed\n * op's pure contract but speaks the consumer's lifecycle vocabulary, so the\n * consumer never names a `*Op` function or reaches into a `QueueItem` field it\n * did not put there.\n */\nexport interface QueueAdapter<I> {\n /** Append a new `pending` item stamped `now` under `id`. */\n enqueue(\n queue: readonly QueueItem<I>[],\n input: I,\n now: number,\n id: string,\n ): { next: QueueItem<I>[]; item: QueueItem<I> };\n\n /**\n * Claim the first `pending` item — flip it `running`, stamp `startedAt` to\n * `now` — or `null` if nothing is pending.\n */\n claim(\n queue: readonly QueueItem<I>[],\n now: number,\n ): { next: QueueItem<I>[]; claimed: QueueItem<I> } | null;\n\n /** Drop the item `id` from the queue (completed runs leave the queue). */\n markDone(\n queue: readonly QueueItem<I>[],\n id: string,\n ): { next: QueueItem<I>[]; changed: boolean };\n\n /**\n * Single-item transition: replace the item `id` with `patch(item)`. The\n * closure is the only place a `QueueItem` literal is constructed, so the\n * field shape stays out of the consumer's hands except through this verb.\n */\n patch(\n queue: readonly QueueItem<I>[],\n id: string,\n patch: (item: QueueItem<I>) => QueueItem<I>,\n ): { next: QueueItem<I>[]; changed: boolean };\n}\n\n/**\n * The canonical `QueueAdapter<I>`, binding each verb to its blessed op. Pure\n * and stateless — call once at module scope in a consumer and fold its verbs\n * through the reducer. A `QueueItem` shape change is absorbed by re-binding a\n * verb here, not at the consumers.\n */\nexport function queueAdapter<I>(): QueueAdapter<I> {\n return {\n enqueue: (queue, input, now, id) => enqueueOp<I>(queue, input, now, id),\n claim: (queue, now) => claimNextOp<I>(queue, now),\n markDone: (queue, id) => markDoneOp<I>(queue, id),\n patch: (queue, id, patch) => patchItemOp<I>(queue, id, patch),\n };\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/recorder/index.ts"],"names":[],"mappings":"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","file":"chunk-MUUROGIH.js","sourcesContent":["// ---------------------------------------------------------------------------\n// recorder — capture a Runtime's Msg sequence for later replay.\n//\n// `historyTracker` (in ../index) is the exemplar: a BOUNDED, in-memory ring\n// buffer over `runtime.observe`. The recorder is its UNBOUNDED, SERIALIZABLE\n// cousin. Where historyTracker exists to answer \"what were the last N\n// transitions?\", the recorder exists to answer \"reproduce this entire run\n// locally against the current reducer.\"\n//\n// The killer feature this enables: record a prod Msg sequence, dump it as\n// JSONL, ship the file home, and feed it to `../trace-replay`'s `replayTrace`\n// — which folds the SAME msgs through `replay(machine, ...)` and diffs the\n// resulting state against the recorded final state. If they diverge, the\n// reducer changed behavior for that input. Regression-as-a-trace.\n//\n// What `replay` needs to reproduce a run (see `replay` in ../index):\n// - `loaded` — the value `init` was booted with. Captured at the boot\n// observe, where `msg === null` and `state` is the post-init\n// initial state. (We capture the post-init state, not the raw\n// storage value, because replay re-runs `init(loaded, ctx)`\n// itself — and for a faithful re-run we feed it that initial\n// state as `loaded` so `init` returns `[loaded, []]`. This is\n// the rehydrate path, which the substrate guarantees is a pure\n// passthrough; see Invariant 2.)\n// - `msgs` — every non-null msg, in dispatch order.\n// - `ctx` — NOT captured here. Ctx is host wiring (sibling runtimes,\n// clients), supplied fresh at replay time. The trace is the\n// INPUT (loaded + msgs); ctx is the ENVIRONMENT.\n//\n// This module OWNS the `Trace` type. `../trace-replay` imports it.\n// ---------------------------------------------------------------------------\n\nimport type { BootingRuntime } from \"../index\";\n\n/**\n * A recorded run, sufficient to reproduce it via `../trace-replay`.\n *\n * The recorder OWNS this type; `../trace-replay` imports it. It is the wire\n * contract between the two halves of the record/replay feature.\n *\n * @typeParam S - the machine's State type.\n * @typeParam M - the machine's Msg union.\n */\nexport interface Trace<S, M> {\n /**\n * The initial state captured at the boot observe (the transition where\n * `msg === null`). Fed back to replay as `loaded` so `init(loaded, ctx)`\n * reproduces the exact starting point. `null` only if the recorder attached\n * AFTER boot (it missed the boot observe) — replay then re-runs fresh `init`.\n */\n readonly loaded: S | null;\n /** The ordered, non-null msgs as observed. The replay input sequence. */\n readonly msgs: readonly M[];\n /**\n * The last observed state. The oracle: `replayTrace` deep-compares its\n * recomputed final state against this. Equal ⇒ the reducer still produces\n * the same result for this input.\n */\n readonly finalState: S;\n /**\n * Per-transition `(msg, post-state)` pairs, in dispatch order, for human\n * inspection (e.g. \"show me the state after step 3\"). When present, there\n * is exactly one entry per recorded msg and `state` is the POST-transition\n * state for that msg (so `steps[i].msg === msgs[i]` and `steps.at(-1).state`\n * deep-equals `finalState`). NOT required for replay — replay only needs\n * `loaded` + `msgs` + `finalState`.\n *\n * Present iff the recorder was created with `captureSteps: true`, OR the\n * Trace was re-hydrated from JSONL via {@link parseJSONL} (the JSONL format\n * carries per-step state, so the parser populates `steps` whenever a step\n * line had a non-null state). A trace dumped with `captureSteps: false` has\n * `steps === undefined`.\n */\n readonly steps?: readonly { readonly msg: M; readonly state: S }[];\n}\n\n/** Options for {@link recorder}. */\nexport interface RecorderOptions {\n /**\n * Fraction of msgs to record, in `(0, 1]`. Default `1` (record everything).\n *\n * IMPORTANT — sampling breaks replay fidelity. A sampled trace DROPS msgs,\n * so its `msgs` array is no longer the complete input sequence and\n * `replayTrace` will NOT reproduce `finalState`. Sampling exists for\n * VOLUME STATISTICS (how much traffic, msg-type distribution), not for\n * faithful replay. Use the default `1` for any trace you intend to replay.\n *\n * The boot observe (`msg === null`) is ALWAYS recorded regardless of\n * `sampleRate` — `loaded` and `finalState` are anchors, not samples.\n */\n readonly sampleRate?: number;\n /**\n * When `true`, retain a per-transition `(msg, post-state)` pair in\n * {@link Trace.steps} for EVERY recorded transition. Default `false`.\n *\n * Size vs. fidelity — the precise trade-off:\n *\n * - `captureSteps: false` (default) — the MINIMAL replay-faithful trace.\n * The recorder keeps `loaded` (boot state), the ordered `msgs`, and\n * `finalState`. That is exactly what `../trace-replay` needs to refold\n * the run and diff the result. Memory is O(msgs) — one msg ref each, no\n * per-step state snapshot. `dump().steps` is `undefined`; in JSONL,\n * interior step lines carry `state: null` (the post-states were never\n * retained) and only the LAST step line carries `finalState`. A trace\n * dumped this way REPLAYS correctly but cannot answer \"what was the\n * state after step 3?\".\n *\n * - `captureSteps: true` — the FULL-fidelity trace. The recorder also\n * snapshots the post-transition state for every msg, so `dump().steps`\n * holds `{ msg, state }` for every transition (post-state) and\n * `toJSONL()` emits a non-null `state` on every step line. Memory is\n * O(msgs × |state|) — a state ref per transition (refs, not deep\n * copies; TEA states are conventionally immutable, so the runtime's\n * post-transition state object is retained by reference). This is the\n * mode for per-step inspection and for a JSONL file that round-trips\n * `steps` exactly via {@link parseJSONL}.\n *\n * Replay fidelity is identical in both modes — `../trace-replay` reads\n * `loaded` + `msgs` + `finalState` only and never touches `steps`. The\n * choice is purely \"do I also want to inspect intermediate states?\".\n */\n readonly captureSteps?: boolean;\n}\n\n/**\n * A live recorder attached to a Runtime. Mirrors `historyTracker`'s\n * stop/snapshot semantics: `dump()` works at any time (including after\n * `stop()`), `stop()` is idempotent and detaches the observer.\n *\n * @typeParam S - the machine's State type.\n * @typeParam M - the machine's Msg union.\n */\nexport interface Recorder<S, M> {\n /**\n * Snapshot the recording so far as a {@link Trace}. Returns a fresh object\n * with copied arrays on every call (callers may slice/replay without\n * affecting the recorder's buffers). Entry values are references to the\n * originals (TEA states/msgs are conventionally immutable).\n *\n * Throws only if called before the boot observe has fired AND no msgs have\n * been recorded — i.e. there is no `finalState` yet. In practice callers\n * `await runtime.ready` before `dump()`, after which boot has fired.\n */\n dump(): Trace<S, M>;\n /**\n * Serialize the recording as JSONL (one JSON object per line) — a streamable\n * format that appends cleanly and survives truncation (a partial tail line\n * is the only loss). The first line is the boot header\n * `{ kind: \"boot\", state }`; each subsequent line is\n * `{ kind: \"step\", msg, state }` for one recorded transition.\n *\n * Re-hydrate with {@link parseJSONL}.\n *\n * Round-trip fidelity depends on `captureSteps`:\n * - `captureSteps: true` — every step line carries its non-null\n * post-state, so `parseJSONL(rec.toJSONL())` reconstructs `steps`\n * EXACTLY and deep-equals `rec.dump()` (which also has `steps`). Full\n * per-step fidelity round-trips.\n * - `captureSteps: false` (default) — interior step lines carry\n * `state: null` (those post-states were never retained); only the last\n * step line carries `finalState`. `parseJSONL` then populates `steps`\n * with just the entries that had a non-null state (the final one), so\n * the re-hydrated Trace is NOT deep-equal to `rec.dump()` (which has no\n * `steps`). It is still fully replay-faithful: `loaded`, `msgs`, and\n * `finalState` are intact. Record with `captureSteps: true` if you need\n * interior states to survive the round-trip.\n */\n toJSONL(): string;\n /**\n * Detach the underlying observer. After `stop()`:\n * - no new transitions are recorded;\n * - `dump()` / `toJSONL()` keep working against the buffered contents;\n * - the recorder holds no observer reference into the runtime.\n *\n * Idempotent — subsequent calls are no-ops.\n */\n stop(): void;\n}\n\n/**\n * Attach a recorder to a Runtime. Subscribes via `runtime.observe`, capturing\n * the boot state as `loaded` and every subsequent non-null msg in order.\n *\n * One-line creation, mirroring `historyTracker`:\n *\n * const rec = recorder(runtime);\n * // ... drive the runtime ...\n * await runtime.ready; // boot observe has fired\n * const trace = rec.dump(); // { loaded, msgs, finalState }\n *\n * @param runtime - any `BootingRuntime<S, M>` (a full `Runtime` satisfies it).\n * The recorder uses only `observe`, which is total before boot\n * — so you attach it to the synchronous `run()` handle to\n * capture the boot transition as `loaded`.\n * @param opts - see {@link RecorderOptions}. Default records everything,\n * no step retention.\n */\nexport function recorder<S, M extends { type: string }>(\n runtime: BootingRuntime<S, M>,\n opts?: RecorderOptions,\n): Recorder<S, M> {\n // Clamp sampleRate into (0, 1]. A non-positive or NaN rate would record\n // nothing (and break replay anchors); default it to 1 (record all). Rates\n // above 1 are meaningless — clamp to 1.\n const rawRate = opts?.sampleRate;\n const sampleRate =\n rawRate === undefined || Number.isNaN(rawRate) || rawRate <= 0\n ? 1\n : Math.min(rawRate, 1);\n const captureSteps = opts?.captureSteps ?? false;\n\n // `loaded` is the post-init state captured at the boot observe. `undefined`\n // sentinel = boot not yet seen; resolved to `S | null` in dump() (null when\n // the recorder attached after boot and never observed msg === null).\n let loaded: S | undefined;\n let sawBoot = false;\n // The ordered, recorded msgs. Unbounded (the recorder's whole point vs the\n // bounded historyTracker).\n const msgs: M[] = [];\n // Per-step (msg, state) pairs — only populated when captureSteps is on.\n const steps: { msg: M; state: S }[] = [];\n // The last observed state — boot state until the first step, then advances\n // with every recorded OR dropped transition (finalState must reflect the\n // true latest state even under sampling, so it stays a valid replay oracle\n // for the unsampled case and an honest \"where the run ended\" marker).\n let finalState: S | undefined;\n let stopped = false;\n\n // The boot transition — the post-init initial state the old `observe(msg ===\n // null)` arm captured as `loaded` — now arrives via `onBoot` (#47). Always\n // captured (boot is the replay anchor). It is also the first `finalState`\n // until the first applied transition advances it.\n const unboot = runtime.onBoot((state) => {\n finalState = state;\n loaded = state;\n sawBoot = true;\n });\n\n const unobserve = runtime.observe((msg, state) => {\n // finalState tracks the latest state regardless of sampling — it is the\n // run's endpoint, an anchor, not a sample.\n finalState = state;\n\n // Sampling decision. sampleRate === 1 short-circuits to always-record so\n // the common (faithful) path pays no RNG cost. Math.random() < rate keeps\n // a `rate` fraction in expectation.\n if (sampleRate < 1 && Math.random() >= sampleRate) return;\n\n msgs.push(msg);\n if (captureSteps) steps.push({ msg, state });\n });\n\n return {\n dump(): Trace<S, M> {\n if (finalState === undefined) {\n throw new Error(\n \"@demlik/tea/recorder: dump() called before any transition was observed. \" +\n \"Await `runtime.ready` (boot fires the first observe) before dumping.\",\n );\n }\n return {\n // sawBoot ⇒ `loaded` is the captured boot state; otherwise the\n // recorder attached post-boot and replay must re-run fresh init.\n loaded: sawBoot ? (loaded as S) : null,\n msgs: msgs.slice(),\n finalState,\n // Only attach `steps` when captureSteps was requested — keeps the\n // dumped Trace shape honest about what was retained.\n ...(captureSteps ? { steps: steps.slice() } : {}),\n };\n },\n toJSONL(): string {\n if (finalState === undefined) {\n throw new Error(\n \"@demlik/tea/recorder: toJSONL() called before any transition was observed. \" +\n \"Await `runtime.ready` (boot fires the first observe) before serializing.\",\n );\n }\n // Serialize the live snapshot via the shared Trace→JSONL writer so the\n // recorder and the standalone `traceAttachment` path emit byte-identical\n // wire format. `dump()` resolves the captureSteps logic (steps present\n // iff captureSteps was on); `traceToJSONL` then emits a non-null state\n // on every step line when steps exist, or `null` interior + finalState\n // on the last line when they don't. The replay path never reads step\n // states, so the captureSteps:false null-interiors never affect replay.\n return traceToJSONL(this.dump());\n },\n stop(): void {\n if (stopped) return;\n stopped = true;\n unobserve();\n unboot();\n },\n };\n}\n\n/** One line of the JSONL format produced by {@link Recorder.toJSONL}. */\ntype JsonlLine<S, M> =\n | { readonly kind: \"boot\"; readonly state: S | null }\n | { readonly kind: \"step\"; readonly msg: M; readonly state: S | null };\n\n/**\n * Re-hydrate a {@link Trace} from the JSONL produced by\n * {@link Recorder.toJSONL}. The inverse of `toJSONL`, so\n * `parseJSONL(rec.toJSONL())` deep-equals `rec.dump()`.\n *\n * Parsing rules:\n * - The first non-blank line MUST be the boot header `{ kind: \"boot\", state }`.\n * Its `state` becomes `Trace.loaded`.\n * - Each `{ kind: \"step\", msg, state }` line contributes `msg` to\n * `Trace.msgs` and `(msg, state)` to `Trace.steps`.\n * - `Trace.finalState` is the `state` of the LAST step line, or the boot\n * `state` when there are no steps (a boot-only trace).\n *\n * Blank lines are skipped (tolerant of trailing newlines / hand-edited files).\n * A malformed line, a missing/duplicate boot header, or a step with `null`\n * final state throws — the trace is unusable for replay and silent corruption\n * is worse than a loud parse error (errors are data; surface them).\n *\n * @typeParam S - the machine's State type.\n * @typeParam M - the machine's Msg union.\n */\nexport function parseJSONL<S, M>(jsonl: string): Trace<S, M> {\n const rawLines = jsonl.split(\"\\n\");\n let loaded: S | null = null;\n let sawBoot = false;\n const msgs: M[] = [];\n const steps: { msg: M; state: S }[] = [];\n let finalState: S | null = null;\n let finalKnown = false;\n\n for (const [i, rawLine] of rawLines.entries()) {\n const line = rawLine.trim();\n if (line === \"\") continue; // tolerate blank / trailing-newline lines\n\n let parsed: JsonlLine<S, M>;\n try {\n parsed = JSON.parse(line) as JsonlLine<S, M>;\n } catch (err) {\n throw new Error(\n `@demlik/tea/recorder: parseJSONL failed on line ${i + 1}: not valid JSON. ${String(err)}`,\n );\n }\n\n if (parsed.kind === \"boot\") {\n if (sawBoot) {\n throw new Error(\n `@demlik/tea/recorder: parseJSONL saw a second boot header on line ${i + 1}. ` +\n \"A trace has exactly one boot line.\",\n );\n }\n sawBoot = true;\n loaded = parsed.state;\n // The boot state is the finalState until a step supersedes it (boot-only\n // trace case).\n finalState = parsed.state;\n finalKnown = parsed.state !== null;\n } else if (parsed.kind === \"step\") {\n if (!sawBoot) {\n throw new Error(\n `@demlik/tea/recorder: parseJSONL saw a step line before the boot header on line ${i + 1}.`,\n );\n }\n msgs.push(parsed.msg);\n if (parsed.state !== null) {\n steps.push({ msg: parsed.msg, state: parsed.state });\n finalState = parsed.state;\n finalKnown = true;\n }\n } else {\n throw new Error(\n `@demlik/tea/recorder: parseJSONL saw an unknown line kind on line ${i + 1}: ` +\n JSON.stringify((parsed as { kind?: unknown }).kind),\n );\n }\n }\n\n if (!sawBoot) {\n throw new Error(\n \"@demlik/tea/recorder: parseJSONL found no boot header. A trace must start with \" +\n '{ kind: \"boot\", state }.',\n );\n }\n if (!finalKnown || finalState === null) {\n throw new Error(\n \"@demlik/tea/recorder: parseJSONL could not determine finalState — the boot state \" +\n \"was null and no step carried a non-null state. The trace is unusable for replay.\",\n );\n }\n\n return { loaded, msgs, finalState, steps };\n}\n\n// ---------------------------------------------------------------------------\n// Sentry crash→trace adapters — pure, zero-dependency, Sentry-SHAPED.\n//\n// The killer DevX flow: a machine crashes in prod → its recorded Trace rides\n// along on the Sentry event → one click later you `parseJSONL` it and replay\n// the exact run locally against the (possibly fixed) reducer. These two\n// adapters are the bridge from a `Trace` to the two Sentry surfaces that carry\n// it: an Attachment (the full replayable trace) and Breadcrumbs (a human-\n// readable timeline of the msgs that led to the crash).\n//\n// They take NO dependency on `@sentry/*`. They emit PLAIN OBJECTS whose shapes\n// match Sentry's public `Attachment` and `Breadcrumb` types, and the consumer\n// wires them at their own Sentry boundary (browser or node). This keeps the\n// recorder runnable anywhere a machine runs (a Worker, a Node script, a test)\n// and lets the consumer pin whatever Sentry SDK version they already ship.\n// ---------------------------------------------------------------------------\n\n/**\n * A Sentry-shaped attachment carrying the full {@link Trace} as JSONL. The\n * shape matches Sentry's `Attachment` type so the consumer can hand it\n * straight to `addAttachment(...)` without any adaptation.\n *\n * - `filename` — `\"tea-trace.jsonl\"`. The name Sentry shows in the UI and\n * uses for download; `.jsonl` signals the streamable format.\n * - `data` — the trace serialized via {@link Recorder.toJSONL}'s format\n * (re-hydrate downstream with {@link parseJSONL}).\n * - `contentType` — `\"application/x-ndjson\"`, the standard MIME for\n * newline-delimited JSON.\n */\nexport interface TraceAttachment {\n readonly filename: string;\n readonly data: string;\n readonly contentType: string;\n}\n\n/**\n * Serialize a {@link Trace} into a Sentry-shaped attachment so a crash event\n * carries the full, replayable run. Pure — no Sentry import, no I/O. The\n * returned object matches Sentry's `Attachment` shape exactly.\n *\n * The `data` field is JSONL in the same format {@link Recorder.toJSONL}\n * produces, so the home-side flow is `parseJSONL(attachment.data)` →\n * `replayTrace(machine, trace, ctx)`.\n *\n * Consumer wiring (no Sentry import here — the consumer owns the boundary):\n *\n * ```ts\n * import * as Sentry from \"@sentry/browser\"; // or \"@sentry/node\"\n * import { traceAttachment } from \"@demlik/tea/recorder\";\n * Sentry.getCurrentScope().addAttachment(traceAttachment(rec.dump()));\n * ```\n *\n * @typeParam S - the machine's State type.\n * @typeParam M - the machine's Msg union.\n */\nexport function traceAttachment<S, M extends { type: string }>(\n trace: Trace<S, M>,\n): TraceAttachment {\n return {\n filename: \"tea-trace.jsonl\",\n data: traceToJSONL(trace),\n contentType: \"application/x-ndjson\",\n };\n}\n\n/** A Sentry-shaped breadcrumb. Matches the fields of Sentry's `Breadcrumb`. */\nexport interface TraceBreadcrumb {\n readonly category: string;\n readonly type: string;\n readonly level: string;\n readonly message: string;\n readonly data?: Record<string, unknown>;\n}\n\n/** Options for {@link breadcrumbsFromTrace}. */\nexport interface BreadcrumbsOptions<M> {\n /**\n * Max breadcrumbs to emit. Default `100` (Sentry's own default breadcrumb\n * cap). When the trace has more msgs than `limit`, the OLDEST are dropped —\n * the returned list is the newest-last tail, matching how a Sentry\n * breadcrumb ring buffer behaves (the crash context is the recent past).\n */\n readonly limit?: number;\n /**\n * Customize each breadcrumb's `message`. Default uses `msg.type`. Provide a\n * serializer to surface a richer one-liner (e.g. include an id). The msg is\n * ALSO always attached verbatim under `data.msg` regardless of this option,\n * so the structured payload is never lost to the string rendering.\n */\n readonly serialize?: (msg: M) => string;\n}\n\n/**\n * Project a {@link Trace}'s recorded msgs into Sentry-shaped breadcrumbs — a\n * human-readable timeline of \"what happened before the crash\". Pure — no\n * Sentry import, no I/O. Each returned object matches Sentry's `Breadcrumb`\n * shape, so the consumer can loop and call `addBreadcrumb(...)`.\n *\n * One breadcrumb per recorded msg, in dispatch order (NEWEST LAST — Sentry\n * renders breadcrumbs oldest-first and the crash is the most recent event).\n * Each breadcrumb:\n * - `category` — `\"tea\"` (groups them in the Sentry breadcrumb panel).\n * - `type` — `\"default\"` (the generic Sentry breadcrumb type).\n * - `level` — `\"info\"` (msgs are normal flow; the crash is the event).\n * - `message` — `msg.type`, or `serialize(msg)` when provided.\n * - `data.msg` — the full msg object (structured detail kept verbatim).\n *\n * Capped at `opts.limit` (default 100). When the trace has more msgs than the\n * cap, the OLDEST are dropped and the newest `limit` are kept — the recent\n * lead-up to the crash is the useful context.\n *\n * Consumer wiring (no Sentry import here — the consumer owns the boundary):\n *\n * ```ts\n * import * as Sentry from \"@sentry/browser\"; // or \"@sentry/node\"\n * import { breadcrumbsFromTrace } from \"@demlik/tea/recorder\";\n * for (const b of breadcrumbsFromTrace(rec.dump())) Sentry.addBreadcrumb(b);\n * ```\n *\n * @typeParam S - the machine's State type.\n * @typeParam M - the machine's Msg union.\n */\nexport function breadcrumbsFromTrace<S, M extends { type: string }>(\n trace: Trace<S, M>,\n opts?: BreadcrumbsOptions<M>,\n): TraceBreadcrumb[] {\n const rawLimit = opts?.limit;\n // Clamp limit to a non-negative integer. `undefined`/NaN → 100 (Sentry's\n // default). A negative or zero limit yields an empty list (the caller asked\n // for no breadcrumbs); fractional values floor (you can't emit half a crumb).\n const limit =\n rawLimit === undefined || Number.isNaN(rawLimit)\n ? 100\n : Math.max(0, Math.floor(rawLimit));\n const serialize = opts?.serialize;\n\n // Keep the NEWEST `limit` msgs (the recent lead-up to the crash). Slicing\n // from `length - limit` (clamped to 0) preserves dispatch order within the\n // kept window. Note we do NOT use `slice(-limit)`: `slice(-0)` is `slice(0)`\n // (JS treats -0 as 0), which would return the WHOLE array for `limit: 0`.\n const start = Math.max(0, trace.msgs.length - limit);\n const kept = start === 0 ? trace.msgs : trace.msgs.slice(start);\n\n const crumbs: TraceBreadcrumb[] = [];\n for (const msg of kept) {\n crumbs.push({\n category: \"tea\",\n type: \"default\",\n level: \"info\",\n message: serialize ? serialize(msg) : msg.type,\n // Full msg kept verbatim so the structured payload survives even when\n // `message` is a terse one-liner. Cast: a Msg is a `{ type: string }`\n // record, which is a `Record<string, unknown>` for Sentry's `data`.\n data: { msg: msg as unknown as Record<string, unknown> },\n });\n }\n return crumbs;\n}\n\n// ---------------------------------------------------------------------------\n// traceToJSONL — serialize a STANDALONE Trace to JSONL (the dump→file path).\n//\n// `Recorder.toJSONL()` serializes the recorder's live buffers. `traceAttachment`\n// works over a plain `Trace` value (a `dump()` snapshot or a `parseJSONL`\n// result), which has no recorder behind it — so it needs a Trace→JSONL\n// function. This is the same wire format `Recorder.toJSONL` emits and\n// `parseJSONL` reads, so `parseJSONL(traceToJSONL(trace))` round-trips.\n//\n// When `trace.steps` is present (captureSteps was on, or the trace came from\n// parseJSONL), every step line carries its post-state. When absent, interior\n// step states are unknown — emitted as `null`, with `finalState` on the LAST\n// step line (identical to the captureSteps:false path in `Recorder.toJSONL`).\n// ---------------------------------------------------------------------------\nfunction traceToJSONL<S, M>(trace: Trace<S, M>): string {\n const lines: string[] = [];\n lines.push(JSON.stringify({ kind: \"boot\", state: trace.loaded }));\n\n const steps = trace.steps;\n for (let i = 0; i < trace.msgs.length; i++) {\n const msg = trace.msgs[i];\n const stepState = steps\n ? steps[i]?.state\n : i === trace.msgs.length - 1\n ? trace.finalState\n : null;\n lines.push(JSON.stringify({ kind: \"step\", msg, state: stepState }));\n }\n return lines.join(\"\\n\");\n}\n"]}
|