@demlik/tea 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -9
- package/dist/agent/index.d.ts +638 -45
- package/dist/agent/index.js +9 -19
- package/dist/{chunk-RH5EFRVJ.js → chunk-22POWGFA.js} +31 -5
- package/dist/chunk-22POWGFA.js.map +1 -0
- package/dist/{chunk-DMYGQB4O.js → chunk-3B5X7SBB.js} +23 -6
- package/dist/chunk-3B5X7SBB.js.map +1 -0
- package/dist/chunk-66PY5WYF.js +1816 -0
- package/dist/chunk-66PY5WYF.js.map +1 -0
- 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-AYS5N6WY.js +505 -0
- package/dist/chunk-AYS5N6WY.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-O4MVQTEJ.js → chunk-L3TND7LA.js} +20 -16
- package/dist/chunk-L3TND7LA.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-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-YUXP55T2.js → chunk-Q5CQTBFA.js} +57 -7
- package/dist/chunk-Q5CQTBFA.js.map +1 -0
- package/dist/{batch-window/index.js → chunk-QPEVCJ5Q.js} +4 -8
- package/dist/chunk-QPEVCJ5Q.js.map +1 -0
- package/dist/chunk-TCLBCERH.js +1480 -0
- package/dist/chunk-TCLBCERH.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-MUUROGIH.js → chunk-V46HSVCK.js} +62 -12
- package/dist/chunk-V46HSVCK.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-ZVJZ72HY.js → chunk-VTQI2NHP.js} +25 -11
- package/dist/chunk-VTQI2NHP.js.map +1 -0
- package/dist/{core-Co0LWkIA.d.ts → core-DnzqWy6u.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 +43 -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-CWzA-o2B.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 +1284 -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/index-Bc40JWVl.d.ts +131 -0
- package/dist/{batch-window/index.d.ts → index-C2DwkjlP.d.ts} +7 -12
- package/dist/index-CA_7I5g9.d.ts +806 -0
- package/dist/index-CSyMYDAB.d.ts +69 -0
- package/dist/{fan-out/index.d.ts → index-CuNO9m8i.d.ts} +7 -9
- package/dist/{debounce/index.d.ts → index-DHSUmQTR.d.ts} +7 -7
- package/dist/{recorder/index.d.ts → index-DrIRb_Ch.d.ts} +4 -7
- package/dist/index-HSYgG2Yt.d.ts +380 -0
- package/dist/{monitored-run/index.d.ts → index-KXqTRJyb.d.ts} +37 -13
- package/dist/{snapshot/index.d.ts → index-ZCMhnVKN.d.ts} +15 -78
- package/dist/index.d.ts +771 -6
- package/dist/index.js +1 -2
- package/dist/jev/index.d.ts +646 -0
- package/dist/jev/index.js +485 -0
- package/dist/jev/index.js.map +1 -0
- package/dist/machine-DGlPWCWI.d.ts +1781 -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 +140 -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/{protocol-B5YmJr0d.d.ts → protocol-D8VbH-VH.d.ts} +10 -1
- 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 +744 -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-CMF13BBh.d.ts +998 -0
- package/dist/testing/index.d.ts +147 -3
- package/dist/testing/index.js +95 -3
- 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-BseNPIhr.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.d.ts +0 -199
- 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/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
package/dist/chunk-WRAA7GVZ.js
DELETED
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
import { typeOf, indexPath, unionKeys, keyPath } from './chunk-EWJP3YYP.js';
|
|
2
|
-
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
|
-
import { useRef, useEffect, useState, useCallback } from 'react';
|
|
4
|
-
|
|
5
|
-
function MsgLog({ history, className }) {
|
|
6
|
-
return /* @__PURE__ */ jsx("div", { className: `tea-dt-log${className ? ` ${className}` : ""}`, children: history.map((r, i) => /* @__PURE__ */ jsxs(
|
|
7
|
-
"div",
|
|
8
|
-
{
|
|
9
|
-
className: `tea-dt-row tea-dt-${r.kind ?? "msg"}`,
|
|
10
|
-
children: [
|
|
11
|
-
/* @__PURE__ */ jsx("span", { className: "tea-dt-t", children: r.ts }),
|
|
12
|
-
/* @__PURE__ */ jsx("span", { className: "tea-dt-m", children: r.text })
|
|
13
|
-
]
|
|
14
|
-
},
|
|
15
|
-
`${r.ts}-${i}`
|
|
16
|
-
)) });
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// src/devtools/state-diff.ts
|
|
20
|
-
function diffState(expected, actual) {
|
|
21
|
-
const out = [];
|
|
22
|
-
walk(expected, actual, "state", out);
|
|
23
|
-
return out;
|
|
24
|
-
}
|
|
25
|
-
function walk(expected, actual, path, out) {
|
|
26
|
-
if (Object.is(expected, actual)) return;
|
|
27
|
-
const te = typeOf(expected);
|
|
28
|
-
const ta = typeOf(actual);
|
|
29
|
-
if (te === "undefined") {
|
|
30
|
-
out.push({ path, kind: "added", expected, actual });
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
if (ta === "undefined") {
|
|
34
|
-
out.push({ path, kind: "removed", expected, actual });
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
if (te !== ta) {
|
|
38
|
-
out.push({ path, kind: "changed", expected, actual });
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
if (te === "array") {
|
|
42
|
-
const ae = expected;
|
|
43
|
-
const aa = actual;
|
|
44
|
-
const len = Math.max(ae.length, aa.length);
|
|
45
|
-
for (let i = 0; i < len; i++) {
|
|
46
|
-
walk(ae[i], aa[i], indexPath(path, i), out);
|
|
47
|
-
}
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
if (te === "object") {
|
|
51
|
-
const oe = expected;
|
|
52
|
-
const oa = actual;
|
|
53
|
-
for (const key of unionKeys(oe, oa)) {
|
|
54
|
-
walk(oe[key], oa[key], keyPath(path, key), out);
|
|
55
|
-
}
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
out.push({ path, kind: "changed", expected, actual });
|
|
59
|
-
}
|
|
60
|
-
function fmtValue(v) {
|
|
61
|
-
if (v === void 0) return "undefined";
|
|
62
|
-
if (typeof v === "number" && Number.isNaN(v)) return "NaN";
|
|
63
|
-
if (typeof v === "bigint") return `${v}n`;
|
|
64
|
-
if (typeof v === "string") return JSON.stringify(v);
|
|
65
|
-
if (typeof v === "function" || typeof v === "symbol") return String(v);
|
|
66
|
-
try {
|
|
67
|
-
return JSON.stringify(v) ?? String(v);
|
|
68
|
-
} catch {
|
|
69
|
-
return String(v);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
function formatDiff(changes) {
|
|
73
|
-
return changes.map(formatChange).join("\n");
|
|
74
|
-
}
|
|
75
|
-
function formatChange(c) {
|
|
76
|
-
switch (c.kind) {
|
|
77
|
-
case "changed":
|
|
78
|
-
return `~ ${c.path}: ${fmtValue(c.expected)} \u2192 ${fmtValue(c.actual)}`;
|
|
79
|
-
case "removed":
|
|
80
|
-
return `- ${c.path}: ${fmtValue(c.expected)}`;
|
|
81
|
-
case "added":
|
|
82
|
-
return `+ ${c.path}: ${fmtValue(c.actual)}`;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
function changeRow(c) {
|
|
86
|
-
switch (c.kind) {
|
|
87
|
-
case "changed":
|
|
88
|
-
return /* @__PURE__ */ jsxs("div", { className: "tea-dt-diff-row tea-dt-diff-changed", children: [
|
|
89
|
-
/* @__PURE__ */ jsx("span", { className: "tea-dt-diff-mark", children: "~" }),
|
|
90
|
-
/* @__PURE__ */ jsx("span", { className: "tea-dt-diff-path", children: c.path }),
|
|
91
|
-
/* @__PURE__ */ jsx("span", { className: "tea-dt-diff-old", children: fmtValue(c.expected) }),
|
|
92
|
-
/* @__PURE__ */ jsx("span", { className: "tea-dt-diff-arrow", children: "\u2192" }),
|
|
93
|
-
/* @__PURE__ */ jsx("span", { className: "tea-dt-diff-new", children: fmtValue(c.actual) })
|
|
94
|
-
] }, c.path);
|
|
95
|
-
case "removed":
|
|
96
|
-
return /* @__PURE__ */ jsxs("div", { className: "tea-dt-diff-row tea-dt-diff-removed", children: [
|
|
97
|
-
/* @__PURE__ */ jsx("span", { className: "tea-dt-diff-mark", children: "-" }),
|
|
98
|
-
/* @__PURE__ */ jsx("span", { className: "tea-dt-diff-path", children: c.path }),
|
|
99
|
-
/* @__PURE__ */ jsx("span", { className: "tea-dt-diff-old", children: fmtValue(c.expected) })
|
|
100
|
-
] }, c.path);
|
|
101
|
-
case "added":
|
|
102
|
-
return /* @__PURE__ */ jsxs("div", { className: "tea-dt-diff-row tea-dt-diff-added", children: [
|
|
103
|
-
/* @__PURE__ */ jsx("span", { className: "tea-dt-diff-mark", children: "+" }),
|
|
104
|
-
/* @__PURE__ */ jsx("span", { className: "tea-dt-diff-path", children: c.path }),
|
|
105
|
-
/* @__PURE__ */ jsx("span", { className: "tea-dt-diff-new", children: fmtValue(c.actual) })
|
|
106
|
-
] }, c.path);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
function StateDiff({ expected, actual, className }) {
|
|
110
|
-
const changes = diffState(expected, actual);
|
|
111
|
-
return /* @__PURE__ */ jsx(
|
|
112
|
-
"div",
|
|
113
|
-
{
|
|
114
|
-
className: `tea-dt-diff${className ? ` ${className}` : ""}`,
|
|
115
|
-
title: changes.length === 0 ? "no changes" : formatDiff(changes),
|
|
116
|
-
children: changes.length === 0 ? /* @__PURE__ */ jsx("div", { className: "tea-dt-diff-empty", children: "no changes" }) : changes.map(changeRow)
|
|
117
|
-
}
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
function escapeHtml(s) {
|
|
121
|
-
return s.replace(
|
|
122
|
-
/[&<>"]/g,
|
|
123
|
-
(c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
function fmtJson(obj, indent = 0) {
|
|
127
|
-
if (obj === null) return '<span class="tea-dt-b">null</span>';
|
|
128
|
-
if (typeof obj === "number") return `<span class="tea-dt-n">${obj}</span>`;
|
|
129
|
-
if (typeof obj === "boolean") return `<span class="tea-dt-b">${obj}</span>`;
|
|
130
|
-
if (typeof obj === "string")
|
|
131
|
-
return `<span class="tea-dt-s">"${escapeHtml(obj)}"</span>`;
|
|
132
|
-
if (Array.isArray(obj)) {
|
|
133
|
-
if (obj.length === 0) return '<span class="tea-dt-br">[]</span>';
|
|
134
|
-
return '<span class="tea-dt-br">[</span>' + obj.map((v) => fmtJson(v, indent + 1)).join('<span class="tea-dt-br">, </span>') + '<span class="tea-dt-br">]</span>';
|
|
135
|
-
}
|
|
136
|
-
if (typeof obj === "object") {
|
|
137
|
-
const keys = Object.keys(obj);
|
|
138
|
-
if (keys.length === 0) return '<span class="tea-dt-br">{}</span>';
|
|
139
|
-
const pad = " ".repeat(indent + 1);
|
|
140
|
-
const lines = keys.map((k, i) => {
|
|
141
|
-
const v = obj[k];
|
|
142
|
-
const comma = i < keys.length - 1 ? '<span class="tea-dt-br">,</span>' : "";
|
|
143
|
-
return `${pad}<span class="tea-dt-k">${escapeHtml(k)}</span><span class="tea-dt-br">:</span> ${fmtJson(v, indent + 1)}${comma}`;
|
|
144
|
-
});
|
|
145
|
-
return [
|
|
146
|
-
'<span class="tea-dt-br">{</span>',
|
|
147
|
-
...lines,
|
|
148
|
-
`${" ".repeat(indent)}<span class="tea-dt-br">}</span>`
|
|
149
|
-
].join("<br/>");
|
|
150
|
-
}
|
|
151
|
-
return escapeHtml(String(obj));
|
|
152
|
-
}
|
|
153
|
-
function StateInspector({
|
|
154
|
-
state,
|
|
155
|
-
flashKey,
|
|
156
|
-
className
|
|
157
|
-
}) {
|
|
158
|
-
const boxRef = useRef(null);
|
|
159
|
-
useEffect(() => {
|
|
160
|
-
if (flashKey === void 0 || !boxRef.current) return;
|
|
161
|
-
boxRef.current.classList.remove("tea-dt-flash");
|
|
162
|
-
void boxRef.current.offsetWidth;
|
|
163
|
-
boxRef.current.classList.add("tea-dt-flash");
|
|
164
|
-
}, [flashKey]);
|
|
165
|
-
return /* @__PURE__ */ jsx(
|
|
166
|
-
"div",
|
|
167
|
-
{
|
|
168
|
-
ref: boxRef,
|
|
169
|
-
className: `tea-dt-state${className ? ` ${className}` : ""}`,
|
|
170
|
-
children: /* @__PURE__ */ jsx(
|
|
171
|
-
"div",
|
|
172
|
-
{
|
|
173
|
-
className: "tea-dt-json",
|
|
174
|
-
dangerouslySetInnerHTML: { __html: fmtJson(state) }
|
|
175
|
-
}
|
|
176
|
-
)
|
|
177
|
-
}
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
function nowTs() {
|
|
181
|
-
return (/* @__PURE__ */ new Date()).toLocaleTimeString("en-GB", { hour12: false }).slice(3);
|
|
182
|
-
}
|
|
183
|
-
function useMsgHistory(dispatch, opts = {}) {
|
|
184
|
-
const max = opts.max ?? 60;
|
|
185
|
-
const now = opts.now ?? nowTs;
|
|
186
|
-
const derive = opts.derive ?? ((msg) => {
|
|
187
|
-
const tag = msg && typeof msg === "object" && "type" in msg ? String(msg.type) : String(msg);
|
|
188
|
-
return { ts: now(), kind: "msg", text: tag };
|
|
189
|
-
});
|
|
190
|
-
const [history, setHistory] = useState([]);
|
|
191
|
-
const dispatchRef = useRef(dispatch);
|
|
192
|
-
dispatchRef.current = dispatch;
|
|
193
|
-
const deriveRef = useRef(derive);
|
|
194
|
-
deriveRef.current = derive;
|
|
195
|
-
const wrapped = useCallback(
|
|
196
|
-
(msg) => {
|
|
197
|
-
const entry = deriveRef.current(msg);
|
|
198
|
-
setHistory((prev) => [entry, ...prev].slice(0, max));
|
|
199
|
-
void dispatchRef.current(msg);
|
|
200
|
-
},
|
|
201
|
-
[max]
|
|
202
|
-
);
|
|
203
|
-
const clear = useCallback(() => setHistory([]), []);
|
|
204
|
-
return [history, wrapped, clear];
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
export { MsgLog, StateDiff, StateInspector, diffState, formatDiff, useMsgHistory };
|
|
208
|
-
//# sourceMappingURL=chunk-WRAA7GVZ.js.map
|
|
209
|
-
//# sourceMappingURL=chunk-WRAA7GVZ.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/devtools/msg-log.tsx","../src/devtools/state-diff.ts","../src/devtools/state-diff-view.tsx","../src/devtools/state-inspector.tsx","../src/devtools/use-msg-history.ts"],"names":["jsxs","jsx","useRef"],"mappings":"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","file":"chunk-WRAA7GVZ.js","sourcesContent":["export type MsgLogEntry = {\n /** Display timestamp (whatever format you produced). */\n ts: string;\n /** Visual category — drives the row icon. Defaults to \"msg\" via CSS. */\n kind?: \"msg\" | \"sub\" | \"cmd\" | \"ok\" | \"fail\";\n /** Row text — usually the Msg tag plus a short detail. */\n text: string;\n};\n\nexport interface MsgLogProps {\n history: readonly MsgLogEntry[];\n className?: string;\n}\n\nexport function MsgLog({ history, className }: MsgLogProps) {\n return (\n <div className={`tea-dt-log${className ? ` ${className}` : \"\"}`}>\n {history.map((r, i) => (\n <div\n key={`${r.ts}-${i}`}\n className={`tea-dt-row tea-dt-${r.kind ?? \"msg\"}`}\n >\n <span className=\"tea-dt-t\">{r.ts}</span>\n <span className=\"tea-dt-m\">{r.text}</span>\n </div>\n ))}\n </div>\n );\n}\n","// ---------------------------------------------------------------------------\n// state-diff — the RICHER cousin of trace-replay's first-divergence walk.\n//\n// `../trace-replay` answers \"did the state drift, and where FIRST?\" — it stops\n// at the first differing cell because a regression check only needs to point at\n// one. This module answers the fuller devtools question: \"show me EVERY cell\n// that differs,\" classified as added / removed / changed, ready to render.\n//\n// Same substrate, same path grammar (root `state`, `foo[2]` for arrays,\n// `foo.bar` for objects, sorted-union key order, `Object.is` leaf equality) so\n// a path printed here is the SAME string trace-replay would print for that\n// cell. The two tools are deliberately consistent: a `divergence.path` from a\n// replay result indexes straight into this module's `StateChange[]`.\n//\n// Pure + zero-dep on purpose — runs in Node, in tests, in a Worker. The React\n// view (`./state-diff.tsx`) is the only React-touching layer.\n// ---------------------------------------------------------------------------\n\nimport {\n indexPath,\n keyPath,\n typeOf,\n unionKeys,\n} from \"../trace-replay/state-walk\";\n\n/**\n * A single differing cell between two states.\n *\n * - `kind: \"added\"` — the path exists in `actual` but not `expected`\n * (`expected` is `undefined`).\n * - `kind: \"removed\"` — the path exists in `expected` but not `actual`\n * (`actual` is `undefined`).\n * - `kind: \"changed\"` — the path exists on both sides with differing leaf\n * values (or differing kinds, e.g. object vs array).\n */\nexport interface StateChange {\n /** Access path from the state root, e.g. `state.items[1].status`. */\n path: string;\n /** How the cell differs between the two states. */\n kind: \"added\" | \"removed\" | \"changed\";\n /** The value on the `expected` side (`undefined` when `added`). */\n expected: unknown;\n /** The value on the `actual` side (`undefined` when `removed`). */\n actual: unknown;\n}\n\n/**\n * Deep-walk two states and return EVERY differing cell, in deterministic\n * pre-order (the node before its children; children in array-index order, then\n * sorted-union key order for objects). The empty array means the two states are\n * deeply equal.\n *\n * Equality + path grammar match `../trace-replay`'s `firstDivergence` exactly:\n * - `Object.is` leaf equality, so `NaN` equals `NaN` and `+0` ≠ `-0`.\n * - arrays index by `[i]`; a length mismatch is NOT reported at the array\n * node — instead each surplus/missing element surfaces as its own\n * added/removed child, which is what a devtools list wants to show.\n * - objects walk the sorted union of both sides' keys; a key on only one\n * side becomes an `added`/`removed` child.\n * - the root path is `\"state\"`.\n *\n * Scope is JSON-shaped state (the same substrate trace-replay targets):\n * primitives, null/undefined, arrays, plain objects. A differing kind at a node\n * (object vs array, number vs string) is reported as a single `changed` at that\n * node — the walk does not descend into mismatched kinds.\n */\nexport function diffState(expected: unknown, actual: unknown): StateChange[] {\n const out: StateChange[] = [];\n walk(expected, actual, \"state\", out);\n return out;\n}\n\nfunction walk(\n expected: unknown,\n actual: unknown,\n path: string,\n out: StateChange[],\n): void {\n // Identity / primitive fast path. Equal cells contribute nothing. `Object.is`\n // so NaN equals NaN and +0 ≠ -0 — same intent as trace-replay's leaf check.\n if (Object.is(expected, actual)) return;\n\n const te = typeOf(expected);\n const ta = typeOf(actual);\n\n // A present/absent pair at this node is an add or a remove — the structural\n // signal a viewer color-codes differently from a value change.\n if (te === \"undefined\") {\n out.push({ path, kind: \"added\", expected, actual });\n return;\n }\n if (ta === \"undefined\") {\n out.push({ path, kind: \"removed\", expected, actual });\n return;\n }\n\n // Different kinds at a present node (object vs array, number vs string,\n // value vs null) — one `changed` at the node; do not descend into mismatched\n // shapes, mirroring trace-replay's \"this node IS the divergence\".\n if (te !== ta) {\n out.push({ path, kind: \"changed\", expected, actual });\n return;\n }\n\n if (te === \"array\") {\n const ae = expected as unknown[];\n const aa = actual as unknown[];\n // Walk the union of indices so surplus elements on EITHER side surface as\n // their own added/removed children (vs trace-replay, which reports a length\n // mismatch at the array node and stops). A viewer wants the per-element\n // detail.\n const len = Math.max(ae.length, aa.length);\n for (let i = 0; i < len; i++) {\n walk(ae[i], aa[i], indexPath(path, i), out);\n }\n return;\n }\n\n if (te === \"object\") {\n const oe = expected as Record<string, unknown>;\n const oa = actual as Record<string, unknown>;\n // Sorted union of keys — deterministic regardless of insertion order, so\n // two states with the same data in different key order produce no changes,\n // and the reported order is reproducible run-to-run.\n for (const key of unionKeys(oe, oa)) {\n walk(oe[key], oa[key], keyPath(path, key), out);\n }\n return;\n }\n\n // Two present primitives (string/number/boolean/bigint/symbol) or two\n // differing nulls that failed the Object.is fast path — a leaf change.\n out.push({ path, kind: \"changed\", expected, actual });\n}\n\n/** Render a single value compactly for a diff cell. Strings get quotes so\n * `\"3\"` is visibly distinct from `3`; everything else is JSON-ish, with the\n * `undefined`/`NaN`/`BigInt` cases JSON.stringify mishandles spelled out.\n * Exported (though not re-exported from `./index`) so the React `<StateDiff>`\n * view renders each cell with this exact formatter rather than a copy. */\nexport function fmtValue(v: unknown): string {\n if (v === undefined) return \"undefined\";\n if (typeof v === \"number\" && Number.isNaN(v)) return \"NaN\";\n if (typeof v === \"bigint\") return `${v}n`;\n if (typeof v === \"string\") return JSON.stringify(v);\n if (typeof v === \"function\" || typeof v === \"symbol\") return String(v);\n try {\n return JSON.stringify(v) ?? String(v);\n } catch {\n // Circular / non-serializable — fall back to a coarse tag.\n return String(v);\n }\n}\n\n/**\n * Pretty multi-line text for a {@link diffState} result. One `±`-prefixed line\n * per change, in the array's (pre-order, deterministic) order:\n *\n * - `~ state.total: 121.5 → 135` (changed: old → new)\n * - `- state.coupon: \"X\"` (removed: only the old value)\n * - `+ state.tax: 8` (added: only the new value)\n *\n * Deterministic given a deterministic `changes` array. An empty array yields\n * the empty string.\n */\nexport function formatDiff(changes: StateChange[]): string {\n return changes.map(formatChange).join(\"\\n\");\n}\n\n/** One `±`-prefixed line for a single change. Split out so the `map` callback\n * is total (Biome's useIterableCallbackReturn) without a dead default branch. */\nfunction formatChange(c: StateChange): string {\n switch (c.kind) {\n case \"changed\":\n return `~ ${c.path}: ${fmtValue(c.expected)} → ${fmtValue(c.actual)}`;\n case \"removed\":\n return `- ${c.path}: ${fmtValue(c.expected)}`;\n case \"added\":\n return `+ ${c.path}: ${fmtValue(c.actual)}`;\n }\n}\n","import {\n diffState,\n fmtValue,\n formatDiff,\n type StateChange,\n} from \"./state-diff\";\n\nexport interface StateDiffProps {\n /**\n * The baseline / \"before\" state — what you expected. In a regression view\n * this is a trace's recorded `finalState`; in a time-travel view it's the\n * previous model.\n */\n expected: unknown;\n /**\n * The \"after\" state to compare against `expected` — the recomputed or\n * current model.\n */\n actual: unknown;\n /** Optional className appended to the container. Use to override layout. */\n className?: string;\n}\n\n/** Per-kind row glyph + value rendering. Lives here (not CSS ::before like the\n * MsgLog rows) because the diff prints the path AND the value(s), so the glyph\n * is part of structured content the screen reader should read in order. */\nfunction changeRow(c: StateChange) {\n // `path` is unique within one diff (each cell appears at most once), so it is\n // a stable React key without needing the array index.\n switch (c.kind) {\n case \"changed\":\n return (\n <div key={c.path} className=\"tea-dt-diff-row tea-dt-diff-changed\">\n <span className=\"tea-dt-diff-mark\">~</span>\n <span className=\"tea-dt-diff-path\">{c.path}</span>\n <span className=\"tea-dt-diff-old\">{fmtValue(c.expected)}</span>\n <span className=\"tea-dt-diff-arrow\">→</span>\n <span className=\"tea-dt-diff-new\">{fmtValue(c.actual)}</span>\n </div>\n );\n case \"removed\":\n return (\n <div key={c.path} className=\"tea-dt-diff-row tea-dt-diff-removed\">\n <span className=\"tea-dt-diff-mark\">-</span>\n <span className=\"tea-dt-diff-path\">{c.path}</span>\n <span className=\"tea-dt-diff-old\">{fmtValue(c.expected)}</span>\n </div>\n );\n case \"added\":\n return (\n <div key={c.path} className=\"tea-dt-diff-row tea-dt-diff-added\">\n <span className=\"tea-dt-diff-mark\">+</span>\n <span className=\"tea-dt-diff-path\">{c.path}</span>\n <span className=\"tea-dt-diff-new\">{fmtValue(c.actual)}</span>\n </div>\n );\n }\n}\n\n/**\n * Presentational diff of two states. Renders {@link diffState}'s output as a\n * color-coded list (added / removed / changed), reusing the devtools `tea-dt-*`\n * class convention so it inherits the same design tokens as `<StateInspector>`\n * and `<MsgLog>` (see ./styles.css). No state, no subscription — pass the two\n * states as props, exactly like the rest of /devtools.\n *\n * When the states are deeply equal the list is empty; a tiny \"no changes\" hint\n * renders so an operator can tell \"equal\" from \"not yet computed\".\n */\nexport function StateDiff({ expected, actual, className }: StateDiffProps) {\n const changes = diffState(expected, actual);\n return (\n <div\n className={`tea-dt-diff${className ? ` ${className}` : \"\"}`}\n // The plain-text diff is the accessible/copyable fallback for the\n // color-coded rows — same content `formatDiff` produces in Node/tests.\n title={changes.length === 0 ? \"no changes\" : formatDiff(changes)}\n >\n {changes.length === 0 ? (\n <div className=\"tea-dt-diff-empty\">no changes</div>\n ) : (\n changes.map(changeRow)\n )}\n </div>\n );\n}\n","import { useEffect, useRef } from \"react\";\n\nfunction escapeHtml(s: string): string {\n return s.replace(\n /[&<>\"]/g,\n (c) =>\n ({ \"&\": \"&\", \"<\": \"<\", \">\": \">\", '\"': \""\" })[c] as string,\n );\n}\n\nfunction fmtJson(obj: unknown, indent = 0): string {\n if (obj === null) return '<span class=\"tea-dt-b\">null</span>';\n if (typeof obj === \"number\") return `<span class=\"tea-dt-n\">${obj}</span>`;\n if (typeof obj === \"boolean\") return `<span class=\"tea-dt-b\">${obj}</span>`;\n if (typeof obj === \"string\")\n return `<span class=\"tea-dt-s\">\"${escapeHtml(obj)}\"</span>`;\n if (Array.isArray(obj)) {\n if (obj.length === 0) return '<span class=\"tea-dt-br\">[]</span>';\n return (\n '<span class=\"tea-dt-br\">[</span>' +\n obj\n .map((v) => fmtJson(v, indent + 1))\n .join('<span class=\"tea-dt-br\">, </span>') +\n '<span class=\"tea-dt-br\">]</span>'\n );\n }\n if (typeof obj === \"object\") {\n const keys = Object.keys(obj as object);\n if (keys.length === 0) return '<span class=\"tea-dt-br\">{}</span>';\n const pad = \" \".repeat(indent + 1);\n const lines = keys.map((k, i) => {\n const v = (obj as Record<string, unknown>)[k];\n const comma =\n i < keys.length - 1 ? '<span class=\"tea-dt-br\">,</span>' : \"\";\n return `${pad}<span class=\"tea-dt-k\">${escapeHtml(k)}</span><span class=\"tea-dt-br\">:</span> ${fmtJson(v, indent + 1)}${comma}`;\n });\n return [\n '<span class=\"tea-dt-br\">{</span>',\n ...lines,\n `${\" \".repeat(indent)}<span class=\"tea-dt-br\">}</span>`,\n ].join(\"<br/>\");\n }\n return escapeHtml(String(obj));\n}\n\nexport interface StateInspectorProps {\n /**\n * The machine state to display. Render-derived shape — pass whatever you\n * want surfaced to the inspector (often a hand-picked subset of the model).\n */\n state: unknown;\n /**\n * When this number changes, the inspector flashes. Pass `model.msgCount` or\n * any other monotonic counter you already have. Omit to disable flashing.\n */\n flashKey?: number;\n /**\n * Optional className appended to the container. Use to override layout.\n */\n className?: string;\n}\n\nexport function StateInspector({\n state,\n flashKey,\n className,\n}: StateInspectorProps) {\n const boxRef = useRef<HTMLDivElement>(null);\n useEffect(() => {\n if (flashKey === undefined || !boxRef.current) return;\n boxRef.current.classList.remove(\"tea-dt-flash\");\n void boxRef.current.offsetWidth;\n boxRef.current.classList.add(\"tea-dt-flash\");\n }, [flashKey]);\n\n return (\n <div\n ref={boxRef}\n className={`tea-dt-state${className ? ` ${className}` : \"\"}`}\n >\n <div\n className=\"tea-dt-json\"\n // biome-ignore lint/security/noDangerouslySetInnerHtml: formatter output\n dangerouslySetInnerHTML={{ __html: fmtJson(state) }}\n />\n </div>\n );\n}\n","import { useCallback, useRef, useState } from \"react\";\nimport type { MsgLogEntry } from \"./msg-log\";\n\nfunction nowTs(): string {\n return new Date().toLocaleTimeString(\"en-GB\", { hour12: false }).slice(3);\n}\n\n/**\n * Wrap a `dispatch` so every Msg it sees lands in a bounded history buffer.\n *\n * `derive` lets you map a Msg to a richer entry (e.g. assign `kind: \"sub\"` to\n * subscription-triggered messages). Default: every msg becomes a `\"msg\"` row\n * tagged with `msg.type` (or `String(msg)` if it has no `type`). The substrate\n * uses `type` as the Msg discriminant — using `_tag` here would render every\n * Msg as `[object Object]` in the inspector.\n *\n * `max` caps the buffer — oldest entries fall off (LIFO, newest first).\n *\n * `now` is the timestamp source for the default `derive`. Defaults to\n * `nowTs` (real wall-clock). Tests inject a deterministic shim so\n * snapshots don't drift; production code never sets it.\n */\nexport function useMsgHistory<M>(\n dispatch: (msg: M) => void | Promise<void>,\n opts: {\n max?: number;\n derive?: (msg: M) => MsgLogEntry;\n now?: () => string;\n } = {},\n): readonly [readonly MsgLogEntry[], (msg: M) => void, () => void] {\n const max = opts.max ?? 60;\n const now = opts.now ?? nowTs;\n const derive =\n opts.derive ??\n ((msg: M): MsgLogEntry => {\n const tag =\n msg && typeof msg === \"object\" && \"type\" in msg\n ? String((msg as { type: unknown }).type)\n : String(msg);\n return { ts: now(), kind: \"msg\", text: tag };\n });\n\n const [history, setHistory] = useState<readonly MsgLogEntry[]>([]);\n // Stable ref to `dispatch` so the wrapper identity stays stable across\n // renders — important when this is passed to keyboard handlers and the like.\n const dispatchRef = useRef(dispatch);\n dispatchRef.current = dispatch;\n const deriveRef = useRef(derive);\n deriveRef.current = derive;\n\n const wrapped = useCallback(\n (msg: M) => {\n const entry = deriveRef.current(msg);\n setHistory((prev) => [entry, ...prev].slice(0, max));\n // Fire-and-forget — `dispatch` may return a Promise; we don't await it\n // here because callers historically treat dispatch as void.\n void dispatchRef.current(msg);\n },\n [max],\n );\n\n const clear = useCallback(() => setHistory([]), []);\n\n return [history, wrapped, clear] as const;\n}\n"]}
|
package/dist/chunk-XGELA764.js
DELETED
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from 'async_hooks';
|
|
2
|
-
|
|
3
|
-
// src/journal/index.ts
|
|
4
|
-
|
|
5
|
-
// src/journal/remote.ts
|
|
6
|
-
function memoryRemoteJournal() {
|
|
7
|
-
const journal = memoryJournal();
|
|
8
|
-
const toRemoteEntry = (entry) => ({
|
|
9
|
-
stream: entry.stream,
|
|
10
|
-
seq: entry.seq,
|
|
11
|
-
author: entry.record.author,
|
|
12
|
-
record: entry.record.record
|
|
13
|
-
});
|
|
14
|
-
return {
|
|
15
|
-
async push({ stream, records }) {
|
|
16
|
-
const seqs = [];
|
|
17
|
-
await journal.withLock(stream, async () => {
|
|
18
|
-
for (const record of records) {
|
|
19
|
-
const { seq } = await journal.append(stream, record);
|
|
20
|
-
seqs.push(seq);
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
|
-
return { seqs };
|
|
24
|
-
},
|
|
25
|
-
async pull({ stream, since }) {
|
|
26
|
-
const entries = (await journal.list(stream)).filter((entry) => entry.seq > since).map(toRemoteEntry);
|
|
27
|
-
return { entries };
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
function memoryCursorStore() {
|
|
32
|
-
const cursors = /* @__PURE__ */ new Map();
|
|
33
|
-
return {
|
|
34
|
-
async get(stream) {
|
|
35
|
-
return cursors.get(stream) ?? 0;
|
|
36
|
-
},
|
|
37
|
-
async set(stream, seq) {
|
|
38
|
-
cursors.set(stream, seq);
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
function makeSyncClient(options) {
|
|
43
|
-
const { remote, author, token } = options;
|
|
44
|
-
const local = options.local ?? memoryJournal();
|
|
45
|
-
const cursors = options.cursors ?? memoryCursorStore();
|
|
46
|
-
const pushed = /* @__PURE__ */ new Map();
|
|
47
|
-
const converged = /* @__PURE__ */ new Map();
|
|
48
|
-
const unpushed = async (stream) => {
|
|
49
|
-
const entries = await local.list(stream);
|
|
50
|
-
return entries.slice(pushed.get(stream) ?? 0);
|
|
51
|
-
};
|
|
52
|
-
return {
|
|
53
|
-
async append(stream, record) {
|
|
54
|
-
await local.append(stream, record);
|
|
55
|
-
},
|
|
56
|
-
async pending(stream) {
|
|
57
|
-
const tail = await unpushed(stream);
|
|
58
|
-
return tail.map((entry) => ({ author, record: entry.record }));
|
|
59
|
-
},
|
|
60
|
-
async sync(stream) {
|
|
61
|
-
const tail = await unpushed(stream);
|
|
62
|
-
if (tail.length > 0) {
|
|
63
|
-
await remote.push({
|
|
64
|
-
token,
|
|
65
|
-
stream,
|
|
66
|
-
records: tail.map((entry) => ({ author, record: entry.record }))
|
|
67
|
-
});
|
|
68
|
-
pushed.set(stream, (pushed.get(stream) ?? 0) + tail.length);
|
|
69
|
-
}
|
|
70
|
-
const since = await cursors.get(stream);
|
|
71
|
-
const { entries } = await remote.pull({ token, stream, since });
|
|
72
|
-
const last = entries[entries.length - 1];
|
|
73
|
-
if (last !== void 0) {
|
|
74
|
-
const log = converged.get(stream) ?? [];
|
|
75
|
-
log.push(...entries);
|
|
76
|
-
converged.set(stream, log);
|
|
77
|
-
await cursors.set(stream, last.seq);
|
|
78
|
-
}
|
|
79
|
-
},
|
|
80
|
-
log(stream) {
|
|
81
|
-
return (converged.get(stream) ?? []).slice();
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// src/journal/index.ts
|
|
87
|
-
var AsyncLock = class {
|
|
88
|
-
tail = Promise.resolve();
|
|
89
|
-
run(fx) {
|
|
90
|
-
const result = this.tail.then(fx, fx);
|
|
91
|
-
this.tail = result.then(
|
|
92
|
-
() => void 0,
|
|
93
|
-
() => void 0
|
|
94
|
-
);
|
|
95
|
-
return result;
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
function makeJournal(backend) {
|
|
99
|
-
const mutexes = /* @__PURE__ */ new Map();
|
|
100
|
-
const heldStreams = new AsyncLocalStorage();
|
|
101
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
102
|
-
const mutexFor = (stream) => {
|
|
103
|
-
let mutex = mutexes.get(stream);
|
|
104
|
-
if (!mutex) {
|
|
105
|
-
mutex = new AsyncLock();
|
|
106
|
-
mutexes.set(stream, mutex);
|
|
107
|
-
}
|
|
108
|
-
return mutex;
|
|
109
|
-
};
|
|
110
|
-
const exclusive = (stream, fx) => {
|
|
111
|
-
const held = heldStreams.getStore();
|
|
112
|
-
if (held?.has(stream)) return fx();
|
|
113
|
-
return mutexFor(stream).run(
|
|
114
|
-
() => backend.lock(stream, () => {
|
|
115
|
-
const nested = new Set(held).add(stream);
|
|
116
|
-
return heldStreams.run(nested, fx);
|
|
117
|
-
})
|
|
118
|
-
);
|
|
119
|
-
};
|
|
120
|
-
return {
|
|
121
|
-
append(stream, record) {
|
|
122
|
-
return exclusive(stream, async () => {
|
|
123
|
-
const entry = await backend.append(stream, record);
|
|
124
|
-
for (const listener of listeners) listener(entry);
|
|
125
|
-
return { seq: entry.seq };
|
|
126
|
-
});
|
|
127
|
-
},
|
|
128
|
-
list(stream) {
|
|
129
|
-
return Promise.resolve(backend.read(stream));
|
|
130
|
-
},
|
|
131
|
-
changes(listener) {
|
|
132
|
-
listeners.add(listener);
|
|
133
|
-
return () => {
|
|
134
|
-
listeners.delete(listener);
|
|
135
|
-
};
|
|
136
|
-
},
|
|
137
|
-
withLock(stream, fx) {
|
|
138
|
-
return exclusive(stream, fx);
|
|
139
|
-
}
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
function memoryJournal() {
|
|
143
|
-
const streams = /* @__PURE__ */ new Map();
|
|
144
|
-
return makeJournal({
|
|
145
|
-
async read(stream) {
|
|
146
|
-
return (streams.get(stream) ?? []).slice();
|
|
147
|
-
},
|
|
148
|
-
async append(stream, record) {
|
|
149
|
-
const log = streams.get(stream) ?? [];
|
|
150
|
-
const entry = { stream, seq: log.length + 1, record };
|
|
151
|
-
log.push(entry);
|
|
152
|
-
streams.set(stream, log);
|
|
153
|
-
return entry;
|
|
154
|
-
},
|
|
155
|
-
async lock(_stream, fx) {
|
|
156
|
-
return fx();
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export { makeJournal, makeSyncClient, memoryCursorStore, memoryJournal, memoryRemoteJournal };
|
|
162
|
-
//# sourceMappingURL=chunk-XGELA764.js.map
|
|
163
|
-
//# sourceMappingURL=chunk-XGELA764.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/journal/remote.ts","../src/journal/index.ts"],"names":[],"mappings":"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","file":"chunk-XGELA764.js","sourcesContent":["/**\n * journal sync — a remote journal and per-remote cursors, so two local journals\n * meet in one order.\n *\n * `Journal<R>` (`./index`) is the LOCAL append-only log a single machine replays\n * from. This is the second half: a **remote** journal that many local journals\n * push their appends into and pull everyone else's out of, with the remote\n * assigning the one `seq` that counts. Records are append-only, so there is\n * nothing to merge — the fold over the remote's order *is* the merge. This is\n * the network-in-the-middle twin of the append-then-fold shape already proven\n * in-repo by `src/do/event-sourced-store.ts` (append each record to a\n * monotonically-keyed log; rebuild state by folding the log via `replay`).\n *\n * The three moving parts:\n * - `RemoteJournal<R>` — the append-only remote. `push` sends the records a\n * client has not yet had acked and gets back the `seq` the remote assigned\n * each; `pull` reads everyone's records after a given `seq`. One monotonic\n * `seq` per stream, assigned by the remote, is the whole ordering contract.\n * - a per-`(client, stream)` **cursor** (`CursorStore`) kept locally — the\n * last remote `seq` a client has pulled — so `push`/`pull` resume\n * incrementally instead of re-sending or re-reading the whole stream.\n * - `SyncClient<R>` — a local `Journal<R>` joined to a `RemoteJournal<R>`\n * through that cursor and a stable author identity. A local `append` is\n * PROVISIONAL (it has a local order but no remote `seq`) until `sync` pushes\n * it and pulls it back under the `seq` the remote assigned.\n *\n * Domain-blind and auth-blind: the remote takes an opaque `token` and an opaque\n * `stream` key and does nothing with the token — who-may-push-to-which-stream is\n * the host's concern, not this primitive's (the same way the local `Journal` is\n * blind to what a record means). Interface shape follows Effect\n * `unstable/eventlog` `EventJournal` (`writeFromRemote`, per-remote cursors, a\n * `changes` stream) as prior art, not a dependency.\n */\n\nimport { type JournalEntry, memoryJournal } from \"./index\";\n\n/**\n * A remote-assigned order key. Like a local `Seq` it starts at 1 and increments\n * by one per record within a stream, but it is assigned by the REMOTE — it is\n * the authoritative order every client folds over, not any one client's local\n * append order.\n */\nexport type RemoteSeq = number;\n\n/**\n * A record paired with the stable identity of whoever authored it. Author\n * identity is the one fact a team-mode record needs that a solo record does not:\n * once records from many clients interleave in the remote's order, the fold\n * still has to know who wrote each. It is opaque — a string the host assigns\n * meaning to (a device id, a user handle) — never interpreted here.\n */\nexport interface AuthoredRecord<R> {\n /** The stable identity of the client that authored `record`. */\n readonly author: string;\n /** The domain record itself; this primitive knows nothing of its shape. */\n readonly record: R;\n}\n\n/**\n * One record as it lives on the remote: the `seq` the remote assigned, the\n * `author` that wrote it, the `stream` it belongs to, and the record. This is\n * the entry every client pulls and folds; two clients that pull the same stream\n * see identical `RemoteEntry`s in identical `seq` order, which is what lets\n * their folds agree.\n */\nexport interface RemoteEntry<R> {\n /** The stream this entry belongs to. */\n readonly stream: string;\n /** The remote-assigned order key — 1-based, gap-free, monotonic per stream. */\n readonly seq: RemoteSeq;\n /** The stable identity of the client that authored the record. */\n readonly author: string;\n /** The domain record. */\n readonly record: R;\n}\n\n/** A `push` — the records a client wants acked, presented under its token. */\nexport interface PushRequest<R> {\n /**\n * An opaque credential the host's remote may check. This primitive does\n * nothing with it: authorization (who may push to which stream) is the host's\n * concern, deliberately out of scope here.\n */\n readonly token: unknown;\n /** The opaque stream key to append to. */\n readonly stream: string;\n /**\n * The records to append, in the client's local order. Normally these are the\n * client's own appends made since its last successful push — the incremental\n * tail, not the whole stream.\n */\n readonly records: ReadonlyArray<AuthoredRecord<R>>;\n}\n\n/** The remote's answer to a `push`: the `seq` it assigned each record, in order. */\nexport interface PushResult {\n /**\n * The remote `seq` assigned to each record of the request, in the same order.\n * `seqs[i]` is the seq of `records[i]`. A client does not need to fold these —\n * it learns the full ordered stream (its own records included) through `pull`,\n * which is the single reader of converged state.\n */\n readonly seqs: ReadonlyArray<RemoteSeq>;\n}\n\n/** A `pull` — read everyone's records after a `seq` the client already has. */\nexport interface PullRequest {\n /** The opaque credential; ignored here, see {@link PushRequest.token}. */\n readonly token: unknown;\n /** The opaque stream key to read. */\n readonly stream: string;\n /**\n * Return only entries with `seq` strictly greater than this. A client passes\n * its cursor (the last `seq` it pulled), so it reads only what is new — never\n * re-reading the whole stream. `0` (the cursor's initial value) reads from the\n * start.\n */\n readonly since: RemoteSeq;\n}\n\n/** The remote's answer to a `pull`: the new entries, in `seq` order. */\nexport interface PullResult<R> {\n /** Every entry with `seq > since`, in ascending `seq` order. */\n readonly entries: ReadonlyArray<RemoteEntry<R>>;\n}\n\n/**\n * An append-only remote journal: many clients push into it, all pull from it,\n * and it assigns the one monotonic `seq` per stream that every client's fold\n * agrees over. It is the network-in-the-middle: a real one is a server; this\n * interface is what a client talks to, host- and transport-blind.\n *\n * The remote never merges and never resolves conflicts — records are\n * append-only, so its only job is to assign an honest total order. That order\n * is enough: two clients that both push and both pull fold the same sequence and\n * reach the same state (the append-then-fold result proven in phoenix spike\n * #6673, reused here rather than reinvented).\n */\nexport interface RemoteJournal<R> {\n /**\n * Append `records` to `stream` durably and return the `seq` assigned to each,\n * in order. Concurrent pushes to one stream are serialised into a single total\n * order — the seq assignment is the remote's whole ordering guarantee.\n */\n push(request: PushRequest<R>): Promise<PushResult>;\n /**\n * Read every entry of `stream` with `seq` strictly greater than\n * `request.since`, in ascending `seq` order. Empty when the client is already\n * caught up.\n */\n pull(request: PullRequest): Promise<PullResult<R>>;\n}\n\n/**\n * An in-memory `RemoteJournal<R>` — the standard test/reference substrate,\n * mirroring `memoryStore` (`@demlik/tea/mem`) and `memoryJournal` (`./index`).\n *\n * It is built ON `memoryJournal`: the remote's authoritative `seq` assignment IS\n * a local `Journal`'s per-stream total order. `push` appends each record under\n * the stream's lock (so concurrent pushes serialise into one order and never\n * race the seq), and `pull` is `list` filtered past the client's cursor. The\n * seq machinery is `makeJournal`'s, reused — this substrate reinvents none of\n * it, exactly as the issue's seam was designed to be consumed.\n *\n * The `token` is accepted and ignored: this substrate enforces no authorization,\n * because who-may-push-to-which-stream is the host's concern, out of scope here.\n */\nexport function memoryRemoteJournal<R>(): RemoteJournal<R> {\n const journal = memoryJournal<AuthoredRecord<R>>();\n\n const toRemoteEntry = (\n entry: JournalEntry<AuthoredRecord<R>>,\n ): RemoteEntry<R> => ({\n stream: entry.stream,\n seq: entry.seq,\n author: entry.record.author,\n record: entry.record.record,\n });\n\n return {\n async push({ stream, records }) {\n const seqs: RemoteSeq[] = [];\n // Hold the stream's lock across the whole batch so a concurrent push\n // cannot interleave its seqs into the middle of this one — a batch lands\n // as a contiguous run, and racing batches serialise as wholes.\n await journal.withLock(stream, async () => {\n for (const record of records) {\n const { seq } = await journal.append(stream, record);\n seqs.push(seq);\n }\n });\n return { seqs };\n },\n async pull({ stream, since }) {\n const entries = (await journal.list(stream))\n .filter((entry) => entry.seq > since)\n .map(toRemoteEntry);\n return { entries };\n },\n };\n}\n\n/**\n * Where a client keeps its per-`(client, stream)` cursor — the last remote `seq`\n * it has pulled. Kept LOCALLY (the client's own disk/memory), never on the\n * remote, so `push`/`pull` resume incrementally after a restart instead of\n * re-reading the whole stream. `get` returns `0` for a stream never pulled.\n */\nexport interface CursorStore {\n /** The last remote `seq` pulled for `stream`, or `0` if none yet. */\n get(stream: string): Promise<RemoteSeq>;\n /** Record `seq` as the last remote `seq` pulled for `stream`. */\n set(stream: string, seq: RemoteSeq): Promise<void>;\n}\n\n/**\n * An in-memory `CursorStore`, mirroring `memoryStore`. A durable client swaps in\n * a file- or DB-backed one so its cursor survives a restart; the semantics are\n * identical.\n */\nexport function memoryCursorStore(): CursorStore {\n const cursors = new Map<string, RemoteSeq>();\n return {\n async get(stream) {\n return cursors.get(stream) ?? 0;\n },\n async set(stream, seq) {\n cursors.set(stream, seq);\n },\n };\n}\n\n/** How to build a {@link SyncClient}. */\nexport interface SyncClientOptions<R> {\n /** The remote every push/pull talks to. */\n readonly remote: RemoteJournal<R>;\n /**\n * This client's stable author identity, stamped on every record it appends —\n * the one fact team mode needs that solo mode does not.\n */\n readonly author: string;\n /**\n * The opaque credential presented on every push/pull. Optional because this\n * primitive never checks it; a host that runs an authorizing remote supplies\n * it here.\n */\n readonly token?: unknown;\n /**\n * The client's LOCAL append-only journal — its own provisional appends live\n * here. Defaults to a `memoryJournal`; a durable client passes a `fileJournal`\n * (or any `Journal<R>`), which is the #30 substrate seam this consumes.\n */\n readonly local?: LocalJournal<R>;\n /**\n * Where the per-stream pull cursor lives. Defaults to a `memoryCursorStore`; a\n * durable client passes one backed by its own storage.\n */\n readonly cursors?: CursorStore;\n}\n\n/**\n * The slice of a local `Journal<R>` a `SyncClient` drives — its own provisional\n * appends and reading them back in local order. This is structurally the #30\n * `Journal<R>` interface narrowed to what sync needs, so a `memoryJournal` or a\n * `fileJournal` satisfies it as-is.\n */\nexport interface LocalJournal<R> {\n append(stream: string, record: R): Promise<{ seq: number }>;\n list(stream: string): Promise<ReadonlyArray<JournalEntry<R>>>;\n}\n\n/**\n * A local `Journal` joined to a `RemoteJournal` through a cursor and an author\n * identity. It is what makes two local journals \"meet in one order\": each\n * appends offline, both `sync`, and both converge on the remote's order.\n */\nexport interface SyncClient<R> {\n /**\n * Append `record` to the client's LOCAL journal. The append is PROVISIONAL: it\n * has a local order immediately, but no remote `seq` — and so no place in the\n * converged order — until a later `sync` pushes it and pulls it back. Resolves\n * once the local append is durable.\n */\n append(stream: string, record: R): Promise<void>;\n /**\n * The client's local appends for `stream` that have not yet been pushed — the\n * provisional tail. Empty right after a successful `sync`.\n */\n pending(stream: string): Promise<ReadonlyArray<AuthoredRecord<R>>>;\n /**\n * Reconcile `stream` with the remote: PUSH every local append not yet pushed,\n * then PULL every remote entry after the cursor and fold it into the converged\n * log, advancing the cursor. One `sync` may not see records a peer pushes\n * concurrently (they may land after this pull); a second `sync` — with nothing\n * new to push — pulls them, which is how two racing clients converge.\n */\n sync(stream: string): Promise<void>;\n /**\n * The converged log for `stream`: every entry the client has pulled, in remote\n * `seq` order. This is the fold's input — reducing over it yields the client's\n * state. After both of two racing clients sync to quiescence, their `log`s are\n * identical, which is the convergence guarantee.\n */\n log(stream: string): ReadonlyArray<RemoteEntry<R>>;\n}\n\n/**\n * Build a {@link SyncClient} over a remote, an author identity, and (optionally)\n * a local journal and cursor store.\n *\n * The client holds two watermarks per stream: how many of its own local appends\n * it has PUSHED (so a re-`sync` sends only the new tail), and — in the\n * `CursorStore` — the last remote `seq` it has PULLED (so it reads only what is\n * new). Its converged `log` is built solely from `pull`, so `pull` is the single\n * source of converged truth; a client learns even its OWN records' remote order\n * by pulling them back.\n */\nexport function makeSyncClient<R>(\n options: SyncClientOptions<R>,\n): SyncClient<R> {\n const { remote, author, token } = options;\n const local = options.local ?? memoryJournal<R>();\n const cursors = options.cursors ?? memoryCursorStore();\n\n // Per stream: how many of this client's local appends have been pushed. Local\n // seqs are 1-based and gap-free, so this count is also the local seq watermark\n // — appends with local seq > pushed are the unpushed tail.\n const pushed = new Map<string, number>();\n // Per stream: the converged log, remote-ordered, built only from `pull`.\n const converged = new Map<string, RemoteEntry<R>[]>();\n\n const unpushed = async (\n stream: string,\n ): Promise<ReadonlyArray<JournalEntry<R>>> => {\n const entries = await local.list(stream);\n return entries.slice(pushed.get(stream) ?? 0);\n };\n\n return {\n async append(stream, record) {\n await local.append(stream, record);\n },\n async pending(stream) {\n const tail = await unpushed(stream);\n return tail.map((entry) => ({ author, record: entry.record }));\n },\n async sync(stream) {\n const tail = await unpushed(stream);\n if (tail.length > 0) {\n await remote.push({\n token,\n stream,\n records: tail.map((entry) => ({ author, record: entry.record })),\n });\n pushed.set(stream, (pushed.get(stream) ?? 0) + tail.length);\n }\n\n const since = await cursors.get(stream);\n const { entries } = await remote.pull({ token, stream, since });\n const last = entries[entries.length - 1];\n if (last !== undefined) {\n const log = converged.get(stream) ?? [];\n log.push(...entries);\n converged.set(stream, log);\n await cursors.set(stream, last.seq);\n }\n },\n log(stream) {\n return (converged.get(stream) ?? []).slice();\n },\n };\n}\n","/**\n * @packageDocumentation\n * @demlik/tea/journal — an append-only, ordered record log for `@demlik/tea`.\n *\n * The log a \"durable, replayable state machine\" replays from. tea already ships\n * the log's neighbours — `fileStore`/`memoryStore` (`Store<S>`: snapshot with\n * whole-load / whole-save), `recorder` (JSONL debug traces), `durable-effects`\n * (`foldLedger` over an owed-effect ledger) — but not the log itself, so every\n * consumer hand-rolled it. This is that log, domain-blind: a `Journal<R>` over\n * an opaque record `R`, with no pipeline/ledger vocabulary of its own.\n *\n * Three operations carry the contract:\n * - `append(stream, record)` — durable before it returns, assigning a\n * per-stream total-order `seq` (1, 2, 3, …). Concurrent appenders to one\n * stream converge on one order; the lock's only job is an honest order key.\n * - `list(stream)` — every entry of a stream in `seq` order.\n * - `changes(listener)` — a subscription over appends made through this\n * instance after subscribing.\n *\n * `withLock(stream, fx)` runs `fx` with exclusive access to a stream, so a\n * read-decide-append sequence is atomic against other writers. It is\n * re-entrant for the same stream: an `append` inside `fx` does not deadlock on\n * the lock `fx` already holds.\n *\n * The substrate is a `JournalBackend<R>` — how a stream is read, how one record\n * is appended durably, and how a stream is locked across processes.\n * `makeJournal` wires the domain-blind orchestration (per-stream serialisation,\n * `seq` assignment, `changes` fan-out) over any backend. `memoryJournal` is the\n * in-process backend that mirrors `memoryStore` for tests; the Node file\n * backend (`fileJournal`) homes in `@demlik/tea/node` beside `fileStore`.\n *\n * Interface shape follows Effect `unstable/eventlog` `EventJournal`\n * (`entries`/`write`/`changes`/`withLock`) as prior art, not a dependency.\n */\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\n\n/** A per-stream total-order key. Starts at 1 and increments by one per append. */\nexport type Seq = number;\n\n/** One appended record, tagged with its stream and its per-stream `seq`. */\nexport interface JournalEntry<R> {\n readonly stream: string;\n readonly seq: Seq;\n readonly record: R;\n}\n\n/**\n * An append-only, ordered record log over an opaque record `R`.\n *\n * Domain-blind: it knows nothing of what a record means. Ordering, durability\n * and the honest order key are its whole contract.\n */\nexport interface Journal<R> {\n /**\n * Append `record` to `stream`, durable before the promise resolves, and\n * resolve with the `seq` it was assigned — the next integer in the stream's\n * total order.\n */\n append(stream: string, record: R): Promise<{ seq: Seq }>;\n /** Every entry of `stream`, in `seq` order. Empty for an unwritten stream. */\n list(stream: string): Promise<ReadonlyArray<JournalEntry<R>>>;\n /**\n * Subscribe to appends made through this instance after this call. Returns an\n * unsubscribe. Cross-process appends are not delivered — a cross-process\n * subscription is a polling concern this log leaves to its consumer.\n */\n changes(listener: (entry: JournalEntry<R>) => void): () => void;\n /**\n * Run `fx` with exclusive access to `stream` — the in-process mutex against\n * other callers here, the backend lock against other processes. Re-entrant\n * for the same stream, so an `append` (or nested `withLock`) inside `fx` runs\n * under the lock this call already holds rather than deadlocking on it.\n */\n withLock<A>(stream: string, fx: () => Promise<A>): Promise<A>;\n}\n\n/**\n * The substrate a `Journal<R>` is built over. Everything host-specific lives\n * here; `makeJournal` owns the host-blind rest.\n *\n * `append` is always invoked under `lock` for the same stream, so a backend\n * assigns `seq` from `read().length + 1` without racing itself.\n */\nexport interface JournalBackend<R> {\n /** Every entry of `stream` in `seq` order; empty for an unwritten stream. */\n read(stream: string): Promise<ReadonlyArray<JournalEntry<R>>>;\n /**\n * Persist `record` as the next entry of `stream`, durable before resolving,\n * and resolve with the entry it wrote. Invoked only while `lock` holds the\n * stream, so `seq` is safely the current length plus one.\n */\n append(stream: string, record: R): Promise<JournalEntry<R>>;\n /** Run `fx` while holding `stream`'s cross-process lock. */\n lock<A>(stream: string, fx: () => Promise<A>): Promise<A>;\n}\n\n/**\n * A promise-chained mutex: each `run` waits for the prior one to settle, so the\n * bodies never interleave. The order key is honest because assignment happens\n * inside a body, never across the `await` that another body could slip through.\n */\nclass AsyncLock {\n private tail: Promise<unknown> = Promise.resolve();\n\n run<A>(fx: () => Promise<A>): Promise<A> {\n const result = this.tail.then(fx, fx);\n this.tail = result.then(\n () => undefined,\n () => undefined,\n );\n return result;\n }\n}\n\n/**\n * Build a `Journal<R>` over `backend`. Owns the host-blind half: per-stream\n * serialisation, re-entrant exclusion, and the `changes` fan-out. `append` and\n * `withLock` both route through `exclusive`, which holds the in-process mutex\n * and the backend lock for one stream and is re-entrant for that stream within\n * a single call chain.\n */\nexport function makeJournal<R>(backend: JournalBackend<R>): Journal<R> {\n const mutexes = new Map<string, AsyncLock>();\n // The streams this async call chain holds. Kept in AsyncLocalStorage, not an\n // instance-wide Set, because re-entrancy is a property of the CALL CHAIN, not\n // of the instance: a genuine re-entrant append (nested inside an `fx` that\n // already holds the stream) and an INDEPENDENT top-level append that merely\n // arrives while another chain holds the lock are indistinguishable to a\n // shared Set — so the shared Set waves the independent call through inline,\n // past the mutex, to run concurrently with the holder. That is a lost record:\n // in `fileJournal` the two both read the same length and compute the same\n // `seq`, and the second `rename` clobbers the first. A per-chain store only\n // ever reports the streams THIS chain entered, so an independent call sees an\n // empty (or unrelated) store and correctly queues on the mutex.\n const heldStreams = new AsyncLocalStorage<ReadonlySet<string>>();\n const listeners = new Set<(entry: JournalEntry<R>) => void>();\n\n const mutexFor = (stream: string): AsyncLock => {\n let mutex = mutexes.get(stream);\n if (!mutex) {\n mutex = new AsyncLock();\n mutexes.set(stream, mutex);\n }\n return mutex;\n };\n\n const exclusive = <A>(stream: string, fx: () => Promise<A>): Promise<A> => {\n const held = heldStreams.getStore();\n // This call chain already holds this stream's lock: run inline, or a\n // re-entrant append/withLock would block forever on a lock it owns.\n if (held?.has(stream)) return fx();\n return mutexFor(stream).run(() =>\n backend.lock(stream, () => {\n // Scope the augmented held-set to `fx` and its async continuations. The\n // store is torn down when `fx` settles — no finally clause to leak it.\n const nested = new Set(held).add(stream);\n return heldStreams.run(nested, fx);\n }),\n );\n };\n\n return {\n append(stream, record) {\n return exclusive(stream, async () => {\n const entry = await backend.append(stream, record);\n for (const listener of listeners) listener(entry);\n return { seq: entry.seq };\n });\n },\n list(stream) {\n return Promise.resolve(backend.read(stream));\n },\n changes(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n withLock(stream, fx) {\n return exclusive(stream, fx);\n },\n };\n}\n\n/**\n * An in-process `Journal<R>` — the standard test substrate, mirroring\n * `memoryStore` from `@demlik/tea/mem`. Reference semantics: a record handed to\n * `append` is stored by reference, exactly as `memoryStore` stores state. No\n * cross-process lock is needed — a single process owns the whole log — so the\n * backend lock is identity and `makeJournal`'s in-process mutex is the only\n * ordering guarantee.\n */\nexport function memoryJournal<R>(): Journal<R> {\n const streams = new Map<string, JournalEntry<R>[]>();\n\n return makeJournal<R>({\n async read(stream) {\n return (streams.get(stream) ?? []).slice();\n },\n async append(stream, record) {\n const log = streams.get(stream) ?? [];\n const entry: JournalEntry<R> = { stream, seq: log.length + 1, record };\n log.push(entry);\n streams.set(stream, log);\n return entry;\n },\n async lock(_stream, fx) {\n return fx();\n },\n });\n}\n\n// The remote-journal + per-remote-cursor half of the journal feature (#31): the\n// remote a client pushes to and pulls from, the local cursor that resumes sync,\n// and the sync client that joins a local `Journal` to a remote so two local\n// journals meet in one order. It builds ON `memoryJournal` above.\nexport * from \"./remote\";\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/paginated-walk/index.ts"],"names":["start","drain","resume"],"mappings":"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","file":"chunk-YUXP55T2.js","sourcesContent":["/**\n * @demlik/tea/paginated-walk — traverse a paginated API / sitemap end to end\n * WITHOUT a fake clock, without 429s, and resumable across a Durable-Object\n * eviction. The L2 composition that bolts `../resilient-call`'s resilience onto\n * `../paginator`'s cursor walk: each page is fetched as ONE keyed resilient\n * call (retry + backoff + rate-limit gate), and the cursor advances only once a\n * page settles `ok`.\n *\n * ## The recipe (the two siblings it composes)\n *\n * - **`../paginator`** owns the walk loop: the opaque `Cursor`, the `seen` /\n * `pages` counters, and the `idle → fetching → … → done` phase machine.\n * `paginated-walk` never re-implements cursor advancement — it folds each\n * settled page through `recordPage`, letting the paginator decide\n * `done` / `fetching(next)` / `paused`. Backpressure is a TWO-way valve:\n * `recordPage` parks the walk in `paused` once `seen >= highWaterMark`, and\n * the `drain` / `resume` verbs (delegating to the paginator's own\n * `drain` / `resume`) lower the gauge and re-open the parked fetch — without\n * them `highWaterMark` would be a one-way valve that strands a paused walk.\n * - **`../resilient-call`** owns the page fetch: the single `resilient_run`\n * effect per page is gated through that knob's cache → circuit → rate-limit\n * → retry pipeline. A transient page failure backs off and re-issues the\n * SAME cursor (idempotent retry); the walk does not advance until the page\n * genuinely settles.\n *\n * Those are the only two siblings this module calls into. The resilience knobs\n * (`retry` / `circuit` / `rateLimit` / `deadline`) are NOT separate bricks\n * `paginated-walk` composes — they belong to `resilient-call`, which owns the\n * rate-limit gate internally. This module merely passes them through to\n * `createResilientCall` and re-uses two config TYPES at the boundary:\n * `RateLimitConfig` (re-exported by `resilient-call`) and `RetryPolicy` (from\n * `../retry-backoff`). Pass a `rateLimit` and `resilient-call`'s token bucket\n * throttles page fetches (\"don't 429 the upstream\"); omit it and the walk runs\n * as fast as the upstream answers.\n *\n * Because the page-fetch resilience IS a `resilient-call` slice, this knob\n * **inherits that knob's contract verbatim**: the slice is a flat record, every\n * verb returns `readonly [State, Cmd[]]`, time is always an `at` parameter, and\n * the jitter RNG is injected once at `createPaginatedWalk(config, rng)` — never\n * read inside a verb (per the spec, `paginated-walk` *extends* `resilient-call`).\n *\n * ## The two non-negotiables (canon)\n *\n * - **Durable** — the slice is plain data: the paginator phase + counters, and\n * the resilient-call brick states (all JSON-serializable). The cursor lives\n * in the Model, not a closure, so a walk survives eviction mid-traversal and\n * resumes from the exact page it was fetching.\n * - **Replayable** — every transition is a verb returning new state + Cmds.\n * `nextCursor` / `onPage` are pure config functions applied at the verb\n * boundary; the Cmds they yield are plain data. `replay` reconstructs the\n * whole walk exactly.\n *\n * ## Where the clock / RNG live\n *\n * Inside the verbs: nowhere. `at` arrives on `start` / `pageOk` / `pageErr` /\n * `onTimer` and is threaded straight into the underlying resilient-call gate (so\n * a rate-limited or retried page measures backoff from that `at`). The jitter\n * RNG is injected at construction. The only clock read is `Date.now()` inside\n * the resilient-call `handlers` port (the effect boundary), inherited unchanged.\n *\n * ## The fixed fetch key\n *\n * A walk fetches one page at a time, so the resilient-call layer tracks exactly\n * one logical call under the constant key `PAGE_KEY`. The cursor being fetched\n * is the resilient call's `input`, so a post-eviction resume re-issues the same\n * page request. (`resilient-call` is keyed to support N concurrent calls; a walk\n * deliberately uses a single key — the paginator is the sequencer.)\n *\n * ## Typical wiring\n *\n * const walk = createPaginatedWalk<PageToken, ApiPage>({\n * firstPage: null, // the API's \"page one\" sentinel\n * nextCursor: (page) => page.nextToken ?? null,\n * onPage: (page) => [{ type: \"index_rows\", rows: page.items }],\n * rateLimit: { capacity: 10, refillPerSec: 5 },\n * retry: defaultRetryPolicy,\n * });\n *\n * // in the machine:\n * init: () => [{ walk: walk.init() }, []],\n * update: {\n * start: (s, m) => lift(s, walk.start(s.walk, m.at)),\n * page_ok: (s, m) => lift(s, walk.pageOk(s.walk, m.result, m.at)),\n * page_err: (s, m) => lift(s, walk.pageErr(s.walk, m.error, m.at)),\n * retry_due:(s, m) => lift(s, walk.onTimer(s.walk, m)),\n * drained: (s, m) => lift(s, walk.drain(s.walk, m.n)),\n * resume: (s, m) => lift(s, walk.resume(s.walk, m.at)),\n * },\n * subscriptions: (s) => walk.subs(s.walk),\n * subscribe: { deadline: subscribeDeadline },\n * interpret: walk.handlers({ run: (cursor) => api.fetchPage(cursor) }),\n */\n\nimport type { Cmd } from \"../index\";\nimport {\n drain as drainWalk,\n initPaginator,\n isDone,\n type PaginatorPolicy,\n type PaginatorState,\n recordPage,\n resume as resumeWalk,\n start as startWalk,\n} from \"../paginator\";\nimport { MsgType } from \"../protocol\";\nimport {\n type CircuitConfig,\n createResilientCall,\n type DeadlineConfig,\n type DeadlineSub,\n deadlineSub,\n type FailMsg,\n type RateLimitConfig,\n type ResilientPorts,\n type ResilientState,\n type ResilientTimerMsg,\n type RunCmd,\n type SucceedMsg,\n subscribeDeadline,\n} from \"../resilient-call\";\nimport type { RetryPolicy } from \"../retry-backoff\";\n\n// ===========================================================================\n// Config — the knob. `firstPage` / `nextCursor` / `onPage` describe the walk;\n// the resilience bricks are inherited from resilient-call (every one optional —\n// omit a brick, omit its gate).\n// ===========================================================================\n\n/**\n * The paginated-walk knob. Generic in `Cursor` (the opaque page handle the\n * upstream API hands back — offset, page token, \"next\" URL, …) and `Page` (the\n * shape your fetch port resolves with).\n *\n * `nextCursor` and `onPage` are PURE config functions applied at the verb\n * boundary, never stored in the slice or in a Cmd (invariant 3 — Cmds are\n * plain data, not closures). `nextCursor(page)` extracts the API's next-page\n * handle (or `null` at the end); `onPage(page)` yields the Cmds the consumer\n * wants emitted for each successfully fetched page (index rows, write to R2,\n * dispatch a downstream Msg — whatever; an empty array is fine).\n *\n * The resilience bricks (`retry` / `circuit` / `rateLimit` / `deadline`) are\n * the SAME optional knobs `resilient-call` exposes — paginated-walk extends it.\n * `rateLimit` is the brick that keeps the walk from 429-ing the upstream.\n */\nexport interface PaginatedWalkConfig<Cursor, Page, EmittedCmd extends Cmd> {\n /**\n * The cursor the walk fetches first. For an offset API this is typically `0`;\n * for a token API, whatever sentinel the API treats as \"page one\" (often\n * `null` or `\"\"`). Threaded into `paginator`'s `firstCursor`.\n */\n readonly firstPage: Cursor;\n /**\n * Extract the next cursor from a just-fetched page. Return a `Cursor` to keep\n * walking, or `null` when the API signalled the end (the walk finishes). PURE\n * + consumer-specific (only the consumer knows the API's pagination shape).\n */\n readonly nextCursor: (page: Page) => Cursor | null;\n /**\n * The Cmds to emit for each successfully fetched page — the consumer's work\n * per page (index, persist, fan out, …). Returns plain-data Cmds; `[]` is a\n * valid \"I process the page synchronously elsewhere\" answer. PURE.\n */\n readonly onPage: (page: Page) => readonly EmittedCmd[];\n /**\n * How many items a page contributes to the paginator's backpressure gauge\n * (`seen`). Defaults to `1` per page (count pages, not items) when omitted; a\n * consumer that wants per-item backpressure returns `page.items.length`. PURE.\n */\n readonly pageSize?: (page: Page) => number;\n /**\n * Backpressure threshold handed to the paginator. `seen >= highWaterMark`\n * parks the walk in `paused` instead of fetching the next page. Non-positive\n * (the default `0`) disables backpressure — the walk runs flat out until the\n * API exhausts. See `../paginator`'s `highWaterMark` docs.\n */\n readonly highWaterMark?: number;\n\n // --- resilience bricks, inherited from resilient-call (all optional) ---\n /** Exponential-backoff retry policy for a transient page-fetch failure. */\n readonly retry?: RetryPolicy;\n /** Circuit breaker around the page fetch (one upstream → one breaker). */\n readonly circuit?: CircuitConfig;\n /** Token-bucket rate limit — the \"don't 429 the upstream\" knob. */\n readonly rateLimit?: RateLimitConfig;\n /** Overall wall-clock deadline per individual page fetch. */\n readonly deadline?: DeadlineConfig;\n}\n\n// ===========================================================================\n// Slice — the Model field this knob owns. The paginator walk state + the\n// resilient-call slice for the page fetch. Both are flat plain data.\n// ===========================================================================\n\n/**\n * The slice. `walk` is the cursor/phase/counters from `../paginator`;\n * `resilience` is the `../resilient-call` slice that gates the page fetch.\n * Every field is JSON-serializable, so the whole walk survives a reload.\n *\n * The two move in lockstep: `walk` decides WHICH cursor to fetch next, while\n * `resilience` decides HOW that fetch is attempted (gated, retried, backed\n * off). `pageOk` advances `walk`; `pageErr` defers to `resilience` and leaves\n * `walk` parked on the same cursor until the page truly settles.\n */\nexport interface PaginatedWalkState<Cursor, Page> {\n readonly walk: PaginatorState<Cursor>;\n readonly resilience: ResilientState<Cursor, Page>;\n}\n\n// ===========================================================================\n// Cmds + Msgs the knob speaks.\n// ===========================================================================\n\n/**\n * The page-fetch effect this knob emits: the inherited `resilient_run` Cmd from\n * resilient-call, whose `input` is the `Cursor` to fetch and whose `key` is the\n * fixed `PAGE_KEY`. The consumer's `handlers(ports)` interprets it.\n */\nexport type FetchPageCmd<Cursor> = RunCmd<Cursor>;\n\n/** Page-settled Msgs the `handlers` port dispatches back (inherited verbatim). */\nexport type PageOkMsg<Page> = SucceedMsg<Page>;\nexport type PageErrMsg = FailMsg;\n\n/** The retry / deadline timer Msg — inherited from resilient-call. */\nexport type PaginatedWalkTimerMsg = ResilientTimerMsg;\n\n/**\n * Ports the consumer supplies to `handlers`. `run(cursor)` fetches ONE page for\n * the given cursor; throwing routes to `pageErr` (and thus the resilience\n * backoff). Same shape as resilient-call's port, specialized to the page fetch.\n */\nexport interface PaginatedWalkPorts<Cursor, Page>\n extends ResilientPorts<Cursor, Page> {}\n\n// ===========================================================================\n// The fixed key. A walk fetches one page at a time → exactly one logical\n// resilient call. The paginator is the sequencer; resilient-call's keying is\n// pinned to this single key.\n// ===========================================================================\n\n/** The single resilient-call key every page fetch runs under. */\nexport const PAGE_KEY = \"page\";\n\n// ===========================================================================\n// The knob factory.\n// ===========================================================================\n\n/**\n * Build a paginated-walk knob from `config`. `rng` is injected for the\n * underlying retry jitter — pass a fixed `() => 0` in tests to pin backoff;\n * defaults to `Math.random` (read at the verb boundary inside resilient-call,\n * never in a `paginated-walk` verb). Inherited straight from resilient-call.\n *\n * Returns the uniform L2 knob contract: `init()`, the verbs `start` / `pageOk`\n * / `pageErr` / `onTimer`, `subs(state)`, and `handlers(ports)`. `Cursor` is\n * the page handle, `Page` the fetch result, `EmittedCmd` the consumer's\n * per-page Cmds plus the inherited `FetchPageCmd`.\n */\nexport function createPaginatedWalk<Cursor, Page, EmittedCmd extends Cmd = Cmd>(\n config: PaginatedWalkConfig<Cursor, Page, EmittedCmd>,\n rng: () => number = Math.random,\n) {\n // The page fetch is a resilient call keyed by Cursor input → Page result.\n // We feed it only the resilience bricks; the walk-loop knobs are ours.\n const rc = createResilientCall<Cursor, Page>(\n {\n retry: config.retry,\n circuit: config.circuit,\n rateLimit: config.rateLimit,\n deadline: config.deadline,\n },\n rng,\n );\n\n // The paginator policy: our firstPage is the paginator's firstCursor; the\n // high-water mark gates backpressure (0 / omitted → unbounded).\n const paginatorPolicy: PaginatorPolicy<Cursor> = {\n firstCursor: config.firstPage,\n highWaterMark: config.highWaterMark ?? 0,\n };\n\n // The combined Cmd type the verbs can emit: the inherited page-fetch effect\n // plus whatever `onPage` yields.\n type OutCmd = FetchPageCmd<Cursor> | EmittedCmd;\n\n /** Replace the resilience slice, leaving `walk` untouched. */\n function withResilience(\n s: PaginatedWalkState<Cursor, Page>,\n resilience: ResilientState<Cursor, Page>,\n ): PaginatedWalkState<Cursor, Page> {\n return { ...s, resilience };\n }\n\n /** The starting slice: an idle paginator + a fresh resilient-call slice. */\n function init(): PaginatedWalkState<Cursor, Page> {\n return {\n walk: initPaginator<Cursor>(),\n resilience: rc.init(),\n };\n }\n\n /**\n * Issue a resilient page fetch for `cursor` at `at`. The cursor is the\n * resilient call's `input` (so a retry / resume re-fetches the same page) and\n * `PAGE_KEY` is the fixed key. Returns the advanced resilience slice + the\n * `resilient_run` Cmd (or none, if the gate cached / fast-failed / backed off).\n * PURE — `at` is the only clock. Shared by `start` and `pageOk`'s advance.\n */\n function fetch(\n s: PaginatedWalkState<Cursor, Page>,\n cursor: Cursor,\n at: number,\n ): readonly [\n PaginatedWalkState<Cursor, Page>,\n readonly FetchPageCmd<Cursor>[],\n ] {\n const [resilience, cmds] = rc.attempt(s.resilience, PAGE_KEY, cursor, at);\n return [withResilience(s, resilience), cmds];\n }\n\n /**\n * Issue the page fetch for a freshly-armed paginator transition, else a pure\n * no-op. `walk` is the result of a paginator verb (`startWalk` / `resumeWalk`)\n * applied to `s.walk`; this states the arm-and-fetch contract ONCE for the two\n * verbs that share it (`start`, `resume`).\n *\n * A paginator verb is a pure no-op on any phase it does not act on — it returns\n * the SAME state object. Reference identity (not phase) is the honest \"did the\n * verb arm a fresh fetch?\" check: a walk already `fetching` would also satisfy\n * `phase === \"fetching\"` yet must NOT re-issue a fetch (page-one skip /\n * double-fetch). So we proceed ONLY when the verb produced a fresh state. The\n * `phase === \"fetching\"` narrow that follows is therefore always true here\n * (both verbs only ever produce a `fetching` arm), and also narrows `.cursor`\n * for the type checker. PURE — `at` threads into the fetch gate.\n */\n function armAndFetch(\n s: PaginatedWalkState<Cursor, Page>,\n walk: PaginatorState<Cursor>,\n at: number,\n ): readonly [PaginatedWalkState<Cursor, Page>, readonly OutCmd[]] {\n if (walk === s.walk || walk.phase !== \"fetching\") {\n return [s, []];\n }\n return fetch({ ...s, walk }, walk.cursor, at);\n }\n\n // === Verb: start =========================================================\n\n /**\n * Begin the walk: arm the paginator's first cursor and issue the first page\n * fetch (gated through resilience). On any non-`idle` paginator phase this is\n * a no-op — re-starting a walk in flight would skip back to page one and\n * double-count, so a stray `start` after launch is absorbed (the paginator's\n * own `start` guard). PURE — `at` threads into the fetch gate.\n */\n function start(\n s: PaginatedWalkState<Cursor, Page>,\n at: number,\n ): readonly [PaginatedWalkState<Cursor, Page>, readonly OutCmd[]] {\n // `startWalk` is a pure no-op on any non-`idle` phase (same reference); a\n // fresh `fetching(firstCursor)` arm → issue the first page fetch.\n return armAndFetch(s, startWalk(s.walk, paginatorPolicy), at);\n }\n\n // === Verb: pageOk ========================================================\n\n /**\n * Record a successfully fetched `page` and decide what comes next. PURE —\n * `at` is the cache / next-fetch clock.\n *\n * 1. Settle the resilient call OK (closes the breaker, fills the cache,\n * resets the page-fetch retry counter) — inherited from resilient-call.\n * 2. Fold the page through the paginator: add `pageSize(page)` to `seen`,\n * bump `pages`, and branch on `nextCursor(page)`:\n * - `null` → the API is exhausted → paginator goes `done`.\n * - a cursor → advance, then either fetch it (backpressure room) or\n * park in `paused` (high-water mark reached).\n * 3. Emit the consumer's `onPage(page)` Cmds either way, PLUS the next\n * page-fetch effect when the paginator stays `fetching`.\n *\n * A `pageOk` that arrives when the paginator is not `fetching` (a late\n * duplicate, a double-dispatch) is absorbed by `recordPage` — the cursor does\n * not advance and `seen` is not double-counted; only the resilient settle +\n * `onPage` Cmds happen. The walk is a coordination aid, not a correctness\n * gate.\n */\n function pageOk(\n s: PaginatedWalkState<Cursor, Page>,\n page: Page,\n at: number,\n ): readonly [PaginatedWalkState<Cursor, Page>, readonly OutCmd[]] {\n // 1) Settle the underlying resilient call as a success.\n const [resilience] = rc.succeed(s.resilience, PAGE_KEY, {\n type: MsgType.ResilientOk,\n key: PAGE_KEY,\n result: page,\n at,\n });\n\n // 2) Advance the paginator with the page's contribution + the next cursor.\n const count = config.pageSize ? config.pageSize(page) : 1;\n const next = config.nextCursor(page);\n const walk = recordPage(s.walk, count, next, paginatorPolicy);\n\n const advanced: PaginatedWalkState<Cursor, Page> = { walk, resilience };\n\n // 3) The consumer's per-page work, always emitted on a successful page.\n const pageCmds: readonly EmittedCmd[] = config.onPage(page);\n\n // If the paginator stayed `fetching`, issue the next page fetch (gated).\n if (walk.phase === \"fetching\") {\n const [withFetch, fetchCmds] = fetch(advanced, walk.cursor, at);\n return [withFetch, [...pageCmds, ...fetchCmds]];\n }\n\n // `done` (exhausted) or `paused` (backpressure) → no further fetch now.\n return [advanced, pageCmds];\n }\n\n // === Verb: pageErr =======================================================\n\n /**\n * Record a failed page fetch and back off via resilience. PURE — `at` stamps\n * the breaker trip + the retry-delay base.\n *\n * Defers to the resilient-call `fail` verb: it trips the breaker and either\n * schedules a retry (paginator stays parked on the SAME cursor — no advance)\n * or settles the resilient call `failed` once retries are exhausted. The\n * paginator is deliberately untouched: a failed fetch must NOT advance the\n * cursor or skip a page. When the retry timer later fires, `onTimer` re-issues\n * the same page fetch.\n *\n * GUARDED by the paginator phase, exactly as `pageOk` is guarded by\n * `recordPage`'s `fetching`-only check. A `pageErr` only makes sense while a\n * page fetch is outstanding (`walk.phase === \"fetching\"`). A stray `pageErr`\n * that arrives after the walk has settled — `done` (exhausted), `paused`\n * (backpressure), or `idle` (not started) — has NO outstanding fetch to fail.\n * Without this guard it would still call `rc.fail`, which trips the SHARED\n * circuit breaker (dinging a healthy upstream) and overwrites the settled\n * `PAGE_KEY` resilient slot with `failed` (clobbering the succeeded result a\n * resume would otherwise observe). So a stray `pageErr` is absorbed as a pure\n * no-op — identity preserved — matching `pageOk`'s \"the walk is a coordination\n * aid, not a correctness gate\" stance.\n */\n function pageErr(\n s: PaginatedWalkState<Cursor, Page>,\n error: unknown,\n at: number,\n ): readonly [PaginatedWalkState<Cursor, Page>, readonly OutCmd[]] {\n // Only a `fetching` paginator has an outstanding page fetch to fail. On any\n // other phase the fail would corrupt shared resilience state for a fetch\n // that is not in flight — absorb it as a pure no-op.\n if (s.walk.phase !== \"fetching\") {\n return [s, []];\n }\n const [resilience, cmds] = rc.fail(s.resilience, PAGE_KEY, {\n type: MsgType.ResilientErr,\n key: PAGE_KEY,\n error,\n at,\n });\n return [withResilience(s, resilience), cmds];\n }\n\n // === Verb: onTimer =======================================================\n\n /**\n * A retry / deadline timer fired. Defers to resilient-call's `onTimer`: a\n * retry timer re-runs the gate for the page-fetch's remembered cursor (re-\n * issuing the SAME page); a deadline timer settles the resilient call failed.\n * The paginator stays parked on its cursor throughout — only `pageOk` advances\n * it. A stale fire is a no-op (inherited). PURE.\n *\n * GUARDED by the paginator phase, for the same reason as `pageErr`. A timer is\n * only meaningful while a page fetch is outstanding (`walk.phase ===\n * \"fetching\"`). A stray timer that fires after the walk has settled (`done` /\n * `paused` / `idle`) would otherwise re-enter `rc.onTimer` — re-issuing a fetch\n * the paginator no longer wants (a deadline timer could even overwrite the\n * settled `PAGE_KEY` slot with a deadline `failed`). On any non-`fetching`\n * phase the timer is absorbed as a pure no-op.\n */\n function onTimer(\n s: PaginatedWalkState<Cursor, Page>,\n msg: PaginatedWalkTimerMsg,\n ): readonly [PaginatedWalkState<Cursor, Page>, readonly OutCmd[]] {\n if (s.walk.phase !== \"fetching\") {\n return [s, []];\n }\n const [resilience, cmds] = rc.onTimer(s.resilience, msg);\n if (resilience === s.resilience) {\n // Inherited no-op identity → keep our slice identity too (pure no-op).\n return [s, []];\n }\n return [withResilience(s, resilience), cmds];\n }\n\n // === Verb: drain =========================================================\n\n /**\n * Acknowledge that the consumer finished processing `n` items, lowering the\n * paginator's backpressure gauge (`seen`). PURE — no clock. Delegates to\n * `paginator.drain`: subtracts `n` (floored at 0), leaves the phase untouched.\n *\n * The OTHER half of the backpressure valve `highWaterMark` opens: `recordPage`\n * parks the walk in `paused` once `seen >= highWaterMark`, and `drain` is how a\n * consumer lowers `seen` back below the mark so `resume` can re-open the fetch.\n * Without it `highWaterMark` would be a one-way valve — a walk could pause but\n * never continue. Emits no Cmds: draining only frees headroom; `resume` is the\n * separate, explicit \"continue now\" decision.\n */\n function drain(\n s: PaginatedWalkState<Cursor, Page>,\n n: number,\n ): readonly [PaginatedWalkState<Cursor, Page>, readonly OutCmd[]] {\n const walk = drainWalk(s.walk, n);\n if (walk === s.walk) {\n // Pure no-op identity (n <= 0, or seen already at floor).\n return [s, []];\n }\n return [{ ...s, walk }, []];\n }\n\n // === Verb: resume ========================================================\n\n /**\n * Re-open the backpressure valve after a `drain`: if the walk is `paused` and\n * `seen` has dropped below `highWaterMark`, re-arm the parked cursor and issue\n * its page fetch (gated through resilience). PURE — `at` threads into the fetch\n * gate. Delegates the phase decision to `paginator.resume`:\n *\n * - `paused` AND `seen < highWaterMark` → `fetching(cursor)` → emit the fetch.\n * - `paused` but still over the mark → no-op (the valve stays shut until a\n * further `drain` frees real headroom).\n * - any other phase → no-op (nothing to resume).\n *\n * Together with `drain` this completes the valve: `recordPage` shuts it\n * (`paused`), `drain` lowers the gauge, `resume` re-opens it and fires the next\n * page fetch — the same `fetch` helper `start` / `pageOk` use, so a resumed\n * fetch is gated identically (cache / circuit / rate-limit / retry).\n */\n function resume(\n s: PaginatedWalkState<Cursor, Page>,\n at: number,\n ): readonly [PaginatedWalkState<Cursor, Page>, readonly OutCmd[]] {\n // `resumeWalk` is a pure no-op (same reference) on any phase but `paused`, or\n // when `seen` is still at/over the mark. Only a fresh `fetching` arm means\n // the valve actually re-opened — issue the page fetch only then.\n return armAndFetch(s, resumeWalk(s.walk, paginatorPolicy), at);\n }\n\n // === Derived predicates ==================================================\n\n /**\n * Whether the walk has finished — the API returned a null next cursor and the\n * paginator is `done`. PURE, read-only. Lets a consumer's reducer fire a\n * \"walk complete\" Cmd (`if (walk.isDone(s.walk)) …`) without re-checking the\n * paginator phase by hand.\n */\n function isComplete(s: PaginatedWalkState<Cursor, Page>): boolean {\n return isDone(s.walk);\n }\n\n /**\n * The terminal page-fetch failure that stranded the walk, or `undefined` if no\n * page has terminally failed. PURE, read-only. Errors are data: a page that\n * exhausts its retries (or trips the deadline) settles the `PAGE_KEY` resilient\n * slot to `failed` while the paginator stays parked on its cursor — the walk is\n * stuck and will never advance on its own. Without a way to read that phase the\n * failure is unobservable: the walk simply stops emitting fetches with no\n * signal. `failure(s)` surfaces the settled error (the same plain-data sentinel\n * resilient-call records) so a consumer can fire a \"walk dead\" Cmd, alert, or\n * re-`start` a fresh walk.\n */\n function failure(s: PaginatedWalkState<Cursor, Page>): unknown {\n const call = s.resilience.calls[PAGE_KEY];\n return call?.phase === \"failed\" ? call.error : undefined;\n }\n\n /**\n * Whether the walk is STUCK: a page fetch has terminally `failed` yet the\n * paginator is still parked on a cursor (not `done`), so no further fetch will\n * ever be issued without intervention. PURE, read-only.\n *\n * This is the dead-walk predicate the consumer polls (`if (walk.isStuck(s)) …`)\n * to distinguish a healthy finish (`isComplete`) from a silent death. A walk is\n * stuck precisely when the resilient slot settled `failed` AND the paginator\n * has NOT reached `done` — a terminal failure on the final page that also\n * finished the walk is `isComplete`, not stuck. Pairs with `failure(s)`, which\n * hands back the error that stuck it.\n */\n function isStuck(s: PaginatedWalkState<Cursor, Page>): boolean {\n return s.resilience.calls[PAGE_KEY]?.phase === \"failed\" && !isDone(s.walk);\n }\n\n // === Subs ================================================================\n\n /**\n * Pre-wired subscriptions: exactly the resilient-call subs for the page\n * fetch — a retry timer while the fetch is `waiting_retry`, and (with the\n * `deadline` brick) a per-fetch deadline timer while it is active. Reconciled\n * by id, so a phase change cancels the matching timer automatically. Wire\n * `subscribe: { deadline: subscribeDeadline }` (re-exported below).\n */\n function subs(s: PaginatedWalkState<Cursor, Page>): readonly DeadlineSub[] {\n return rc.subs(s.resilience);\n }\n\n // === Handlers ============================================================\n\n /**\n * Pre-wired interpret handler for the page-fetch effect. Inherited from\n * resilient-call (`resilient_run`): wraps the consumer's `run(cursor)` port\n * via `tryInterpret` (Railway) — success → `resilient_ok` (handled by\n * `pageOk`), failure → `resilient_err` (handled by `pageErr`), each stamped\n * with `Date.now()` at the effect boundary (the ONE permitted clock read).\n *\n * interpret: walk.handlers({ run: (cursor) => api.fetchPage(cursor) })\n *\n * The consumer maps the inherited Msg types to their `pageOk` / `pageErr`\n * reducer cells (the wire shape is `resilient_ok` / `resilient_err`).\n */\n function handlers(ports: PaginatedWalkPorts<Cursor, Page>) {\n return rc.handlers(ports);\n }\n\n return {\n init,\n start,\n pageOk,\n pageErr,\n onTimer,\n drain,\n resume,\n isComplete,\n failure,\n isStuck,\n subs,\n handlers,\n };\n}\n\n/**\n * Lift a knob result `[slice, cmds]` into a host `[State, cmds]` where the slice\n * lives at `state.walk`. A convenience for the common single-slice host;\n * consumers with a differently-named field spread by hand. Pure — a thin record\n * rebuild, no clock / RNG.\n */\nexport function liftWalk<\n S extends { walk: PaginatedWalkState<Cursor, Page> },\n Cursor,\n Page,\n C extends Cmd,\n>(\n state: S,\n [slice, cmds]: readonly [PaginatedWalkState<Cursor, Page>, readonly C[]],\n): readonly [S, readonly C[]] {\n return [{ ...state, walk: slice }, cmds];\n}\n\n/**\n * Re-export the deadline Sub primitives (inherited from resilient-call) so\n * consumers wire one import: `subscribeDeadline` is the `subscribe` cell,\n * `deadlineSub` builds the Sub literal this knob's `subs` emits.\n */\nexport { subscribeDeadline, deadlineSub };\nexport type { DeadlineSub };\n"]}
|