@demlik/tea 0.2.0 → 0.4.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 +49 -0
- package/dist/agent/index.d.ts +16 -770
- package/dist/agent/index.js +18 -16
- package/dist/authed-call/index.d.ts +3 -3
- package/dist/authed-call/index.js +22 -36
- package/dist/authed-call/index.js.map +1 -1
- package/dist/await-terminal/index.d.ts +2 -2
- package/dist/await-terminal/index.js +2 -2
- package/dist/batch-window/index.d.ts +3 -3
- package/dist/batch-window/index.js +3 -3
- package/dist/cache/index.d.ts +2 -2
- package/dist/cache/index.js +5 -2
- package/dist/{chunk-3545BOYL.js → chunk-25LBDCBC.js} +6 -5
- package/dist/chunk-25LBDCBC.js.map +1 -0
- package/dist/{chunk-IK4S5DJN.js → chunk-2AHAR7CX.js} +7 -14
- package/dist/chunk-2AHAR7CX.js.map +1 -0
- package/dist/{chunk-IN33Q4VJ.js → chunk-2KNLKQVN.js} +3 -3
- package/dist/{chunk-IN33Q4VJ.js.map → chunk-2KNLKQVN.js.map} +1 -1
- package/dist/{chunk-VFGKTGGC.js → chunk-5W7CNAHB.js} +191 -178
- package/dist/chunk-5W7CNAHB.js.map +1 -0
- package/dist/{chunk-6GIGQT6C.js → chunk-B5RAZVXW.js} +12 -11
- package/dist/chunk-B5RAZVXW.js.map +1 -0
- package/dist/{chunk-6VDCTR7N.js → chunk-D4NJBQAH.js} +258 -151
- package/dist/chunk-D4NJBQAH.js.map +1 -0
- package/dist/{chunk-UV6UWGEO.js → chunk-D736QUGF.js} +6 -5
- package/dist/chunk-D736QUGF.js.map +1 -0
- package/dist/{chunk-EGWTT5XJ.js → chunk-ET6QQNSQ.js} +76 -95
- package/dist/chunk-ET6QQNSQ.js.map +1 -0
- package/dist/chunk-EWJP3YYP.js +21 -0
- package/dist/chunk-EWJP3YYP.js.map +1 -0
- package/dist/{chunk-M6UZT7HS.js → chunk-FCKP6N7Y.js} +6 -5
- package/dist/chunk-FCKP6N7Y.js.map +1 -0
- package/dist/{chunk-TGG26XMK.js → chunk-FV3Z5MBE.js} +10 -5
- package/dist/chunk-FV3Z5MBE.js.map +1 -0
- package/dist/chunk-G5E74IJB.js +8 -0
- package/dist/chunk-G5E74IJB.js.map +1 -0
- package/dist/{chunk-G2U7DJHL.js → chunk-JFFVBEDI.js} +3 -3
- package/dist/{chunk-G2U7DJHL.js.map → chunk-JFFVBEDI.js.map} +1 -1
- package/dist/{chunk-FOGL3TC7.js → chunk-JXYYRKD6.js} +8 -10
- package/dist/chunk-JXYYRKD6.js.map +1 -0
- package/dist/{chunk-ZDRN3YCE.js → chunk-LJJV6ZD5.js} +17 -24
- package/dist/chunk-LJJV6ZD5.js.map +1 -0
- package/dist/chunk-LYLDCIPH.js +365 -0
- package/dist/chunk-LYLDCIPH.js.map +1 -0
- package/dist/{chunk-HJO6VNQI.js → chunk-MU6MCIAC.js} +3 -3
- package/dist/{chunk-HJO6VNQI.js.map → chunk-MU6MCIAC.js.map} +1 -1
- package/dist/{chunk-UYWUVEP6.js → chunk-MUUROGIH.js} +2 -2
- package/dist/chunk-MUUROGIH.js.map +1 -0
- package/dist/{chunk-DYRKDCJI.js → chunk-PBDYW2TU.js} +41 -7
- package/dist/chunk-PBDYW2TU.js.map +1 -0
- package/dist/{chunk-5VNWZADP.js → chunk-Q2UM7BXU.js} +18 -7
- package/dist/chunk-Q2UM7BXU.js.map +1 -0
- package/dist/chunk-RMX4EOQK.js +26 -0
- package/dist/chunk-RMX4EOQK.js.map +1 -0
- package/dist/{chunk-2YOE5PIS.js → chunk-S2EGRPCM.js} +8 -6
- package/dist/chunk-S2EGRPCM.js.map +1 -0
- package/dist/{chunk-5KVHID6A.js → chunk-SPWVPIFQ.js} +4 -4
- package/dist/{chunk-5KVHID6A.js.map → chunk-SPWVPIFQ.js.map} +1 -1
- package/dist/chunk-SSHDBOXF.js +71 -0
- package/dist/chunk-SSHDBOXF.js.map +1 -0
- package/dist/{chunk-6OUDPSSG.js → chunk-TWDHNIY6.js} +3 -3
- package/dist/{chunk-6OUDPSSG.js.map → chunk-TWDHNIY6.js.map} +1 -1
- package/dist/{chunk-SC3FAFZN.js → chunk-UF4MZSLT.js} +2 -2
- package/dist/chunk-UF4MZSLT.js.map +1 -0
- package/dist/chunk-ZJ5AOYBH.js +8 -0
- package/dist/chunk-ZJ5AOYBH.js.map +1 -0
- package/dist/{chunk-LEC4U6CX.js → chunk-ZVJZ72HY.js} +3 -3
- package/dist/{chunk-LEC4U6CX.js.map → chunk-ZVJZ72HY.js.map} +1 -1
- package/dist/circuit-breaker/index.d.ts +1 -0
- package/dist/circuit-breaker/index.js +1 -1
- package/dist/{core-BmlJOvxM.d.ts → core-42kapW0n.d.ts} +96 -6
- package/dist/deadline/index.d.ts +77 -11
- package/dist/deadline/index.js +3 -3
- package/dist/devtools/index.js +8 -28
- package/dist/devtools/index.js.map +1 -1
- package/dist/do/index.d.ts +52 -24
- package/dist/do/index.js +94 -92
- package/dist/do/index.js.map +1 -1
- package/dist/{durable-effects-9LxFpmh3.d.ts → durable-effects-CFRngTNA.d.ts} +3 -3
- package/dist/extension/index.d.ts +25 -7
- package/dist/extension/index.js +4 -3
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/react.js +4 -3
- package/dist/extension/react.js.map +1 -1
- package/dist/extension/subs/index.d.ts +2 -2
- package/dist/extension/subs/index.js +7 -10
- package/dist/extension/subs/index.js.map +1 -1
- package/dist/fan-out/index.d.ts +10 -10
- package/dist/fan-out/index.js +3 -3
- package/dist/idempotent-intake/index.d.ts +2 -2
- package/dist/idempotent-intake/index.js +3 -3
- package/dist/index.d.ts +50 -489
- package/dist/index.js +2 -2
- package/dist/llm-call/index.d.ts +3 -2
- package/dist/llm-call/index.js +12 -10
- package/dist/machine-B_BF4jij.d.ts +804 -0
- package/dist/machine-viz/index.d.ts +1 -1
- package/dist/machine-viz/index.js +2 -2
- package/dist/mem/index.d.ts +3 -2
- package/dist/mem/index.js.map +1 -1
- package/dist/monitored-run/index.d.ts +54 -30
- package/dist/monitored-run/index.js +7 -7
- package/dist/node/index.d.ts +3 -2
- package/dist/node/index.js +2 -3
- package/dist/node/index.js.map +1 -1
- package/dist/paginated-walk/index.d.ts +2 -2
- package/dist/paginated-walk/index.js +11 -10
- package/dist/parity/index.d.ts +6 -5
- package/dist/parity/index.js +5 -4
- package/dist/parity/index.js.map +1 -1
- package/dist/pbt/arbitraries/index.d.ts +1 -1
- package/dist/pbt/index.d.ts +1 -1
- package/dist/pbt/index.js +3 -3
- package/dist/pbt/index.js.map +1 -1
- package/dist/pbt/runners/index.d.ts +1 -1
- package/dist/pbt/runners/index.js +3 -3
- package/dist/poller/index.d.ts +59 -17
- package/dist/poller/index.js +56 -20
- package/dist/poller/index.js.map +1 -1
- package/dist/prediction/index.d.ts +1 -1
- package/dist/prediction/index.js +2 -2
- package/dist/pure/index.d.ts +1 -1
- package/dist/pure/index.js +2 -2
- package/dist/rate-limit/index.d.ts +2 -2
- package/dist/rate-limit/index.js +1 -1
- package/dist/react/index.d.ts +10 -3
- package/dist/react/index.js +2 -2
- package/dist/react/index.js.map +1 -1
- package/dist/reconciler/index.d.ts +2 -2
- package/dist/reconciler/index.js +13 -15
- package/dist/reconciler/index.js.map +1 -1
- package/dist/recorder/index.d.ts +20 -20
- package/dist/recorder/index.js +1 -1
- package/dist/resilient-call/index.d.ts +31 -6
- package/dist/resilient-call/index.js +10 -9
- package/dist/retry-backoff/index.d.ts +191 -14
- package/dist/retry-backoff/index.js +1 -1
- package/dist/retry-to-success/index.js +1 -1
- package/dist/runtime-types-CukV8kMX.d.ts +406 -0
- package/dist/saga/index.d.ts +3 -2
- package/dist/saga/index.js +2 -2
- package/dist/saga/index.js.map +1 -1
- package/dist/snapshot/index.d.ts +1 -1
- package/dist/snapshot/index.js +3 -3
- package/dist/subs/index.d.ts +372 -4
- package/dist/subs/index.js +4 -1
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +2 -2
- package/dist/throttled-input/index.d.ts +2 -2
- package/dist/throttled-input/index.js +14 -13
- package/dist/throttled-input/index.js.map +1 -1
- package/dist/token-refresh/index.d.ts +1 -1
- package/dist/token-refresh/index.js +3 -3
- package/dist/trace-replay/index.d.ts +12 -7
- package/dist/trace-replay/index.js +4 -3
- package/dist/{types-BoUWgwSm.d.ts → types-KbL3W1mj.d.ts} +1 -1
- package/dist/with-deadline/index.d.ts +1 -1
- package/dist/with-deadline/index.js +2 -2
- package/dist/with-resilience/index.d.ts +21 -2
- package/dist/with-resilience/index.js +12 -23
- package/dist/with-resilience/index.js.map +1 -1
- package/dist/with-telemetry/index.d.ts +1 -1
- package/dist/with-telemetry/index.js +2 -2
- package/dist/work-queue/adapter.d.ts +2 -2
- package/dist/work-queue/adapter.js +2 -2
- package/dist/work-queue/index.d.ts +3 -2
- package/dist/work-queue/index.js +1 -1
- package/dist/work-queue/index.js.map +1 -1
- package/dist/work-queue/ops.d.ts +2 -2
- package/dist/work-queue/ops.js +1 -1
- package/dist/workflow/index.d.ts +51 -19
- package/dist/workflow/index.js +71 -52
- package/dist/workflow/index.js.map +1 -1
- package/package.json +4 -1
- package/dist/chunk-2YOE5PIS.js.map +0 -1
- package/dist/chunk-3545BOYL.js.map +0 -1
- package/dist/chunk-5VNWZADP.js.map +0 -1
- package/dist/chunk-6GIGQT6C.js.map +0 -1
- package/dist/chunk-6VDCTR7N.js.map +0 -1
- package/dist/chunk-DYRKDCJI.js.map +0 -1
- package/dist/chunk-EGWTT5XJ.js.map +0 -1
- package/dist/chunk-EMJTPHI7.js +0 -91
- package/dist/chunk-EMJTPHI7.js.map +0 -1
- package/dist/chunk-FOGL3TC7.js.map +0 -1
- package/dist/chunk-IK4S5DJN.js.map +0 -1
- package/dist/chunk-M6UZT7HS.js.map +0 -1
- package/dist/chunk-NSDFA6M6.js +0 -198
- package/dist/chunk-NSDFA6M6.js.map +0 -1
- package/dist/chunk-SC3FAFZN.js.map +0 -1
- package/dist/chunk-SKAMZKTE.js +0 -20
- package/dist/chunk-SKAMZKTE.js.map +0 -1
- package/dist/chunk-TGG26XMK.js.map +0 -1
- package/dist/chunk-UV6UWGEO.js.map +0 -1
- package/dist/chunk-UYWUVEP6.js.map +0 -1
- package/dist/chunk-VFGKTGGC.js.map +0 -1
- package/dist/chunk-ZDRN3YCE.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/reconciler/index.ts"],"names":["w","planned","applied"],"mappings":";;;;;;;;;;;;;;;AA6TO,SAAS,gBAAA,CAQd,MAAA,EACA,GAAA,GAAoB,IAAA,CAAK,MAAA,EACzB;AAaA,EAAA,MAAM,IAAA,GAAO,mBAAA;AAAA,IACX;AAAA,MACE,WAAW,MAAA,CAAO,SAAA;AAAA,MAClB,YAAY,MAAA,CAAO,UAAA;AAAA;AAAA;AAAA;AAAA,MAInB,MAAA,EAAQ,MAAM,EAAC;AAAA,MACf,OAAO,MAAA,CAAO,KAAA;AAAA,MACd,SAAS,MAAA,CAAO,OAAA;AAAA,MAChB,WAAW,MAAA,CAAO,SAAA;AAAA,MAClB,UAAU,MAAA,CAAO;AAAA,KACnB;AAAA,IACA;AAAA,GACF;AAUA,EAAA,SAAS,SAAS,MAAA,EAAwB;AACxC,IAAA,OAAO,MAAA,CAAO,OAAO,MAAA,CAAO,IAAA,CAAK,MAAM,CAAA,GAAI,IAAA,CAAK,UAAU,MAAM,CAAA;AAAA,EAClE;AAGA,EAAA,SAAS,QAAA,CAAS,GAAU,CAAA,EAAyB;AACnD,IAAA,OAAO,EAAE,GAAG,CAAA,EAAG,IAAA,EAAM,CAAA,EAAE;AAAA,EACzB;AAGA,EAAA,SAAS,IAAA,GAAc;AACrB,IAAA,OAAO;AAAA,MACL,KAAA,EAAO,MAAA;AAAA,MACP,IAAA,EAAM,KAAK,IAAA,EAAK;AAAA,MAChB,QAAQ,EAAC;AAAA,MACT,MAAM,EAAC;AAAA,MACP,aAAA,EAAe,CAAA;AAAA,MACf,SAAS,SAAA;AAAkB,KAC7B;AAAA,EACF;AAWA,EAAA,SAAS,IAAA,CAAK,GAAU,EAAA,EAAiD;AACvE,IAAA,IAAI,EAAE,KAAA,KAAU,MAAA,SAAe,CAAC,CAAA,EAAG,EAAE,CAAA;AACrC,IAAA,MAAM,CAAC,GAAG,IAAI,CAAA,GAAI,KAAK,KAAA,CAAM,CAAA,CAAE,MAAM,EAAE,CAAA;AACvC,IAAA,MAAM,OAAc,EAAE,GAAG,GAAG,KAAA,EAAO,UAAA,EAAY,MAAM,CAAA,EAAE;AACvD,IAAA,OAAO,CAAC,MAAM,IAAI,CAAA;AAAA,EACpB;AAcA,EAAA,SAAS,MAAA,CACP,CAAA,EACA,IAAA,EACA,EAAA,EACqC;AAErC,IAAA,IAAI,CAAA,CAAE,UAAU,UAAA,EAAY;AAC1B,MAAA,MAAM,CAACA,EAAC,CAAA,GAAI,IAAA,CAAK,OAAO,CAAA,CAAE,IAAA,EAAM,MAAM,EAAE,CAAA;AACxC,MAAA,OAAO,CAAC,QAAA,CAAS,CAAA,EAAGA,EAAC,CAAA,EAAG,EAAE,CAAA;AAAA,IAC5B;AAEA,IAAA,MAAM,CAAC,GAAG,QAAQ,CAAA,GAAI,KAAK,MAAA,CAAO,CAAA,CAAE,IAAA,EAAM,IAAA,EAAM,EAAE,CAAA;AAClD,IAAA,MAAM,MAAA,GAAS,CAAC,GAAG,CAAA,CAAE,QAAQ,GAAG,MAAA,CAAO,OAAA,CAAQ,IAAI,CAAC,CAAA;AACpD,IAAA,MAAM,UAAiB,EAAE,GAAG,CAAA,EAAG,IAAA,EAAM,GAAG,MAAA,EAAO;AAI/C,IAAA,MAAM,IAAA,GAA0B,QAAA;AAIhC,IAAA,IAAI,IAAA,CAAK,UAAA,CAAW,OAAA,CAAQ,IAAI,CAAA,EAAG;AACjC,MAAA,MAAM,CAACC,QAAAA,EAAS,SAAS,CAAA,GAAI,SAAA,CAAU,SAAS,EAAE,CAAA;AAClD,MAAA,OAAO,CAACA,QAAAA,EAAS,CAAC,GAAG,IAAA,EAAM,GAAG,SAAS,CAAC,CAAA;AAAA,IAC1C;AAEA,IAAA,OAAO,CAAC,SAAS,IAAI,CAAA;AAAA,EACvB;AAWA,EAAA,SAAS,OAAA,CACP,CAAA,EACA,KAAA,EACA,EAAA,EACqC;AACrC,IAAA,MAAM,CAAC,GAAG,IAAI,CAAA,GAAI,KAAK,OAAA,CAAQ,CAAA,CAAE,IAAA,EAAM,KAAA,EAAO,EAAE,CAAA;AAChD,IAAA,IAAI,IAAA,GAAO,QAAA,CAAS,CAAA,EAAG,CAAC,CAAA;AAGxB,IAAA,IACE,CAAA,CAAE,UAAU,UAAA,IACZ,CAAA,CAAE,WAAW,KAAA,CAAM,QAAQ,CAAA,EAAG,KAAA,KAAU,QAAA,EACxC;AACA,MAAA,IAAA,GAAO,EAAE,GAAG,IAAA,EAAM,KAAA,EAAO,QAAA,EAAS;AAAA,IACpC;AACA,IAAA,OAAO,CAAC,MAAM,IAAI,CAAA;AAAA,EACpB;AAiBA,EAAA,SAAS,OAAA,CACP,CAAA,EACA,EAAA,EACA,OAAA,EACqC;AACrC,IAAA,MAAM,OAAO,OAAA,IAAW,MAAA,CAAO,KAAK,MAAA,CAAO,OAAA,EAAS,EAAE,MAAM,CAAA;AAC5D,IAAA,MAAM,QAAA,GAAkB;AAAA,MACtB,GAAG,CAAA;AAAA,MACH,KAAA,EAAO,UAAA;AAAA,MACP,IAAA;AAAA,MACA,aAAA,EAAe;AAAA,KACjB;AACA,IAAA,OAAO,SAAA,CAAU,UAAU,EAAE,CAAA;AAAA,EAC/B;AAGA,EAAA,SAAS,SAAA,CACP,GACA,EAAA,EACqC;AACrC,IAAA,OAAO,OAAA,CAAQ,GAAG,EAAE,CAAA;AAAA,EACtB;AAgBA,EAAA,SAAS,SAAA,CACP,GACA,EAAA,EACqC;AACrC,IAAA,IAAI,EAAE,KAAA,KAAU,UAAA,SAAmB,CAAC,CAAA,EAAG,EAAE,CAAA;AAEzC,IAAA,IAAI,SAAS,CAAA,CAAE,aAAA;AAGf,IAAA,OAAO,MAAA,GAAS,CAAA,CAAE,IAAA,CAAK,MAAA,EAAQ;AAC7B,MAAA,MAAM,MAAA,GAAS,CAAA,CAAE,IAAA,CAAK,MAAM,CAAA;AAI5B,MAAA,IAAI,WAAW,MAAA,EAAW;AACxB,QAAA,MAAA,EAAA;AACA,QAAA;AAAA,MACF;AACA,MAAA,MAAM,EAAA,GAAK,SAAS,MAAM,CAAA;AAC1B,MAAA,IAAI,IAAS,CAAA,CAAE,OAAA,EAAS,EAAA,EAAI,EAAE,MAAM,MAAA,EAAW;AAG7C,QAAA,MAAM,QAAA,GAAkB,EAAE,GAAG,CAAA,EAAG,eAAe,MAAA,EAAO;AACtD,QAAA,OAAO,CAAC,QAAA,EAAU,CAAC,OAAO,KAAA,CAAM,MAAM,CAAC,CAAC,CAAA;AAAA,MAC1C;AACA,MAAA,MAAA,EAAA;AAAA,IACF;AAGA,IAAA,MAAM,OAAc,EAAE,GAAG,GAAG,KAAA,EAAO,MAAA,EAAQ,eAAe,MAAA,EAAO;AACjE,IAAA,OAAO,CAAC,IAAA,EAAM,EAAE,CAAA;AAAA,EAClB;AAgBA,EAAA,SAAS,OAAA,CACP,CAAA,EACA,MAAA,EACA,EAAA,EACqC;AACrC,IAAA,IAAI,EAAE,KAAA,KAAU,UAAA,SAAmB,CAAC,CAAA,EAAG,EAAE,CAAA;AACzC,IAAA,MAAM,EAAA,GAAK,SAAS,MAAM,CAAA;AAI1B,IAAA,MAAMC,QAAAA,GAAU,GAAA;AAAA,MACd,CAAA,CAAE,OAAA;AAAA,MACF,EAAA;AAAA,MACA,MAAA;AAAA,MACA,EAAA;AAAA,MACA,MAAA,CAAO;AAAA,KACT;AACA,IAAA,MAAM,QAAA,GAAkB;AAAA,MACtB,GAAG,CAAA;AAAA,MACH,OAAA,EAAAA,QAAAA;AAAA,MACA,aAAA,EAAe,EAAE,aAAA,GAAgB;AAAA,KACnC;AACA,IAAA,OAAO,SAAA,CAAU,UAAU,EAAE,CAAA;AAAA,EAC/B;AAWA,EAAA,SAAS,OAAA,CACP,GACA,GAAA,EACqC;AACrC,IAAA,MAAM,CAAC,GAAG,IAAI,CAAA,GAAI,KAAK,OAAA,CAAQ,CAAA,CAAE,MAAM,GAAG,CAAA;AAC1C,IAAA,IAAI,CAAA,KAAM,EAAE,IAAA,EAAM;AAEhB,MAAA,OAAO,CAAC,CAAA,EAAG,EAAE,CAAA;AAAA,IACf;AACA,IAAA,IAAI,IAAA,GAAO,QAAA,CAAS,CAAA,EAAG,CAAC,CAAA;AACxB,IAAA,IACE,CAAA,CAAE,UAAU,UAAA,IACZ,CAAA,CAAE,WAAW,KAAA,CAAM,QAAQ,CAAA,EAAG,KAAA,KAAU,QAAA,EACxC;AACA,MAAA,IAAA,GAAO,EAAE,GAAG,IAAA,EAAM,KAAA,EAAO,QAAA,EAAS;AAAA,IACpC;AACA,IAAA,OAAO,CAAC,MAAM,IAAI,CAAA;AAAA,EACpB;AASA,EAAA,SAAS,WAAW,CAAA,EAAmB;AACrC,IAAA,OAAO,EAAE,KAAA,KAAU,MAAA;AAAA,EACrB;AAYA,EAAA,SAAS,KAAK,CAAA,EAAkC;AAC9C,IAAA,OAAO,IAAA,CAAK,IAAA,CAAK,CAAA,CAAE,IAAI,CAAA;AAAA,EACzB;AAiBA,EAAA,SAAS,SAAS,KAAA,EAAsC;AACtD,IAAA,OAAO,IAAA,CAAK,SAAS,KAAK,CAAA;AAAA,EAC5B;AAEA,EAAA,OAAO;AAAA,IACL,IAAA;AAAA,IACA,IAAA;AAAA,IACA,MAAA;AAAA,IACA,OAAA;AAAA,IACA,OAAA;AAAA,IACA,SAAA;AAAA,IACA,OAAA;AAAA,IACA,OAAA;AAAA,IACA,UAAA;AAAA,IACA,IAAA;AAAA,IACA;AAAA,GACF;AACF;AAQO,SAAS,cAAA,CAQd,KAAA,EACA,CAAC,KAAA,EAAO,IAAI,CAAA,EAIgB;AAC5B,EAAA,OAAO,CAAC,EAAE,GAAG,OAAO,GAAA,EAAK,KAAA,IAAS,IAAI,CAAA;AACxC","file":"index.js","sourcesContent":["/**\n * @demlik/tea/reconciler — the desired-vs-actual sync loop (the \"fleet-sync /\n * coverage-gap\" shape): walk the ACTUAL world end to end, diff it against the\n * DESIRED spec, then apply each resulting `Change` one at a time until the two\n * agree. The control-plane reconcile pattern (Kubernetes controllers, fleet\n * config rollout, accessibility coverage-gap remediation) lifted to the uniform\n * L2 knob contract.\n *\n * ## The recipe (which bricks it composes)\n *\n * - **`../paginated-walk`** owns the SCAN. The actual world is rarely a single\n * fetch — it is a paginated list (every node in a fleet, every page of an\n * audit, every row of a table). `reconciler` does not re-implement that walk:\n * it embeds a `paginated-walk` slice and lets it sequence the actual-state\n * fetch (gated, retried, rate-limited, resumable across eviction). The walk's\n * own `onPage` hook stays empty (`() => []`) — `reconciler` accumulates each\n * fetched page's items into the running `actual` snapshot inside its OWN\n * `pageOk` verb, where it holds its slice and so can extend the accumulator\n * the walk cannot see; when the walk reports `isComplete`, the full actual\n * snapshot is in hand.\n * - **`../cache`** owns the APPLIED ledger. Every `Change` that settles is\n * written into a TTL cache keyed by the change's id, so a re-`applyNext`\n * after an eviction skips changes already applied (idempotent re-apply — the\n * reconcile is safe to resume from any point). The cache is the durable\n * \"what have I already done?\" memory the apply loop reads before re-issuing.\n *\n * Because the scan IS a `paginated-walk` slice (which itself IS a\n * `resilient-call` slice), this knob **inherits that contract verbatim**: the\n * slice is flat plain data, every verb returns `readonly [State, Cmd[]]`, time\n * is always an `at` parameter, and the jitter RNG is injected once at\n * `createReconciler(config, rng)` — never read inside a verb (the spec:\n * `reconciler` *extends* `paginated-walk`).\n *\n * ## Why the run lifecycle is INLINE (run-tracker not extracted)\n *\n * The reconcile phase union (`idle` / `scanning` / `applying` / `done` /\n * `failed`) and its bookkeeping (`plan`, `appliedCursor`, the `actual`\n * accumulator) live directly on this slice rather than behind a `run-tracker`\n * sub-knob — exactly as `monitored-run` keeps its lifecycle inline. Extraction\n * earns its keep when two independent axes vary; here the lifecycle has one\n * anchoring axis (this reconcile pass). The genuinely independent axes — the\n * paginated SCAN and the applied LEDGER — ARE extracted (`../paginated-walk`,\n * `../cache`). The lifecycle is the glue that sequences scan → plan → apply.\n *\n * ## The two non-negotiables (canon)\n *\n * - **Durable** — the slice is plain data: the paginated-walk slice, the\n * accumulated `actual` items, the planned `Change[]`, the applied cursor, and\n * the applied-ledger cache (all JSON-serializable). A reconcile survives DO\n * eviction mid-scan or mid-apply and resumes from the exact position.\n * - **Replayable** — every transition is a verb returning new state + Cmds.\n * `diff` / `apply` are pure config functions applied at the verb boundary;\n * the Cmds they yield are plain data. `replay` reconstructs the whole pass\n * exactly. Nothing here closes over time or RNG.\n *\n * ## Where the clock / RNG live\n *\n * Inside the verbs: nowhere. `at` arrives on `scan` / `pageOk` / `pageErr` /\n * `applied` / `onTimer` and threads into the underlying paginated-walk gate (so a\n * retried / rate-limited page measures backoff from that `at`) and into the\n * applied-ledger cache writes. The jitter RNG is injected at construction. The\n * only clock read is `Date.now()` inside the inherited paginated-walk `handlers`\n * port (the effect boundary).\n *\n * ## Typical wiring\n *\n * const rec = createReconciler<FleetNode, NodeSpec, Page, SyncChange, ApplyCmd>({\n * desired: targetSpecs, // the spec to converge on\n * firstPage: null, // actual-list page-one sentinel\n * nextCursor: (page) => page.next ?? null,\n * itemsOf: (page) => page.nodes, // page → actual items\n * diff: (desired, actual) => planChanges(desired, actual),\n * apply: (change) => ({ type: \"apply_change\", change }),\n * idOf: (change) => change.nodeId, // applied-ledger key\n * rateLimit: { capacity: 10, refillPerSec: 5 },\n * retry: defaultRetryPolicy,\n * });\n *\n * // in the machine:\n * init: () => [{ rec: rec.init() }, []],\n * update: {\n * reconcile: (s, m) => lift(s, rec.scan(s.rec, m.at)),\n * page_ok: (s, m) => lift(s, rec.pageOk(s.rec, m.result, m.at)),\n * page_err: (s, m) => lift(s, rec.pageErr(s.rec, m.error, m.at)),\n * change_done: (s, m) => lift(s, rec.applied(s.rec, m.change, m.at)),\n * retry_due: (s, m) => lift(s, rec.onTimer(s.rec, m)),\n * },\n * subscriptions: (s) => rec.subs(s.rec),\n * subscribe: { deadline: subscribeDeadline },\n * interpret: rec.handlers({ run: (cursor) => api.listActual(cursor) }),\n */\n\nimport {\n get as cacheGet,\n set as cacheSet,\n initCache,\n type TtlCache,\n} from \"../cache\";\nimport type { Cmd } from \"../index\";\nimport {\n createPaginatedWalk,\n type DeadlineSub,\n deadlineSub,\n type FetchPageCmd,\n PAGE_KEY,\n type PageErrMsg,\n type PageOkMsg,\n type PaginatedWalkState,\n type PaginatedWalkTimerMsg,\n subscribeDeadline,\n} from \"../paginated-walk\";\nimport type {\n CircuitConfig,\n DeadlineConfig,\n RateLimitConfig,\n ResilientPorts,\n} from \"../resilient-call\";\nimport type { RetryPolicy } from \"../retry-backoff\";\n\n// ===========================================================================\n// Config — the knob. `desired` / `diff` / `apply` / `idOf` describe the\n// reconcile; the SCAN knobs (firstPage / nextCursor / itemsOf) describe how the\n// actual world is walked; the resilience bricks are inherited from\n// paginated-walk (every one optional — omit a brick, omit its gate).\n// ===========================================================================\n\n/**\n * The reconciler knob. Generic in:\n * - `Actual` — one item of the ACTUAL world (a fleet node, an audited page, a\n * table row). The scan accumulates these.\n * - `Desired` — the DESIRED spec value the diff converges toward. Opaque to\n * this module; only `diff` reads it.\n * - `Page` — the shape one actual-list page fetch resolves with.\n * - `Change` — one unit of remediation the diff produces and `apply` realizes.\n * - `ApplyCmd`— the Cmd `apply(change)` yields (the effect that mutates the\n * actual world toward desired).\n *\n * `diff` / `apply` / `idOf` / `nextCursor` / `itemsOf` are PURE config functions\n * applied at the verb boundary, never stored in the slice or in a Cmd (Cmds are\n * plain data, not closures). The resilience bricks (`retry` / `circuit` /\n * `rateLimit` / `deadline`) are the SAME optional knobs `paginated-walk` /\n * `resilient-call` expose — reconciler extends them; `rateLimit` keeps the scan\n * from 429-ing the upstream listing API.\n */\nexport interface ReconcilerConfig<\n Actual,\n Desired,\n Page,\n Change,\n ApplyCmd extends Cmd,\n Cursor = number,\n> {\n /**\n * The DESIRED spec to converge on. Held verbatim and handed to `diff` once the\n * scan completes. Any JSON-serializable value — a list of specs, a target map,\n * a single target object — `diff` alone interprets it.\n */\n readonly desired: Desired;\n /**\n * Compute the remediation plan: the ordered `Change[]` that moves `actual` to\n * `desired`. Called once when the scan finishes (the full actual snapshot is in\n * hand). An empty plan means \"already in sync — nothing to apply\". PURE +\n * consumer-specific (only the consumer knows the domain's reconcile rules).\n */\n readonly diff: (\n desired: Desired,\n actual: readonly Actual[],\n ) => readonly Change[];\n /**\n * Realize one `Change` as the Cmd that mutates the actual world. Returns plain\n * data (a `{ type, ... }` Cmd the consumer's interpret handler performs).\n * PURE — the side effect happens when the runtime performs the emitted Cmd,\n * never here.\n */\n readonly apply: (change: Change) => ApplyCmd;\n /**\n * Stable identity for a `Change` — the applied-ledger cache key. A re-`applyNext`\n * after eviction (or after a re-plan) skips a change whose id is already in the\n * ledger (idempotent re-apply). MUST be a property of the change itself, NOT its\n * position in the plan: the ledger has to survive re-planning. When omitted the\n * default keys by the change's serialized CONTENT (`JSON.stringify(change)`) —\n * a stable per-change identity that is unchanged by where the change sits in the\n * plan. Supply a domain id (the target node id, the resource key) when one\n * exists; it is cheaper than serializing and dedupes two changes that differ\n * only in fields irrelevant to identity. PURE.\n *\n * Why NOT positional: keying by index silently drops changes on the documented\n * re-plan path. After applying the index-0 change, its `\"0\"` ledger entry would\n * match WHATEVER different, never-applied change lands at index 0 in the next\n * plan — the apply loop skips it as \"already done\". Identity must be intrinsic\n * to the change so the ledger means \"this change settled\", not \"slot N settled\".\n */\n readonly idOf?: (change: Change) => string;\n\n // --- scan knobs (the actual-world walk), inherited from paginated-walk ---\n /**\n * The cursor the actual-list scan fetches first. For an offset API typically\n * `0`; for a token API whatever sentinel the API treats as page one.\n */\n readonly firstPage: Cursor;\n /**\n * Extract the next scan cursor from a fetched page — a `Cursor` to keep\n * scanning, or `null` when the actual listing is exhausted (scan finishes,\n * plan computed). PURE + consumer-specific.\n */\n readonly nextCursor: (page: Page) => Cursor | null;\n /**\n * Extract the actual items from one fetched page. Each page's items are\n * appended to the running `actual` accumulator. PURE.\n */\n readonly itemsOf: (page: Page) => readonly Actual[];\n\n // --- resilience bricks, inherited from paginated-walk (all optional) ---\n /** Exponential-backoff retry policy for a transient scan-page failure. */\n readonly retry?: RetryPolicy;\n /** Circuit breaker around the scan fetch (one upstream → one breaker). */\n readonly circuit?: CircuitConfig;\n /** Token-bucket rate limit on the scan — the \"don't 429 the listing API\" knob. */\n readonly rateLimit?: RateLimitConfig;\n /** Overall wall-clock deadline per individual scan-page fetch. */\n readonly deadline?: DeadlineConfig;\n}\n\n// ===========================================================================\n// Slice — the Model field this knob owns. The paginated-walk scan slice + the\n// accumulated actual snapshot + the inline reconcile lifecycle (plan, applied\n// cursor, ledger). All flat plain data.\n// ===========================================================================\n\n/**\n * Reconcile lifecycle phase. Discriminated on `phase`:\n *\n * - `idle` — created, never scanned. `scan` starts the actual-world walk.\n * - `scanning` — the paginated-walk is traversing the actual listing; pages\n * fold into `actual` as they arrive. The scan owns the `walk`\n * sub-slice; this phase ends when the walk finishes.\n * - `applying` — the scan finished, `diff` produced the `plan`, and changes are\n * being applied one at a time. `appliedCursor` is the index of\n * the next change to apply.\n * - `done` — every planned change applied (or an empty plan: already in\n * sync). Terminal.\n * - `failed` — the scan failed terminally (the page-fetch retries were\n * exhausted / a deadline fired). `error` carries why. Terminal.\n */\nexport type ReconcilePhase =\n | \"idle\"\n | \"scanning\"\n | \"applying\"\n | \"done\"\n | \"failed\";\n\n/**\n * The slice. Plain data end to end:\n * - `phase` — the lifecycle discriminant.\n * - `walk` — the `../paginated-walk` scan slice (cursor + resilience).\n * Drives the actual-world traversal; POSITION survives\n * eviction.\n * - `actual` — the accumulated actual items, appended page by page during\n * the scan. The full snapshot `diff` is computed against.\n * - `plan` — the `Change[]` `diff` produced. Empty until the scan\n * finishes; the apply loop walks it by `appliedCursor`.\n * - `appliedCursor` — index of the NEXT change to apply (0 ≤ cursor ≤ plan.length).\n * `cursor === plan.length` means every change applied.\n * - `applied` — the applied-ledger cache: `idOf(change) → change`, written\n * as each change settles. The idempotency memory the apply\n * loop reads to skip already-applied changes after eviction.\n */\nexport interface ReconcilerState<Actual, Change, Page, Cursor = number> {\n readonly phase: ReconcilePhase;\n readonly walk: PaginatedWalkState<Cursor, Page>;\n readonly actual: readonly Actual[];\n readonly plan: readonly Change[];\n readonly appliedCursor: number;\n readonly applied: TtlCache<Change>;\n}\n\n// ===========================================================================\n// Cmds + Msgs the knob speaks. The scan emits the inherited page-fetch effect;\n// the apply loop emits the consumer's `apply(change)` Cmds.\n// ===========================================================================\n\n/**\n * The actual-list page-fetch effect: the inherited `resilient_run` Cmd from\n * paginated-walk, 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 ScanPageCmd<Cursor> = FetchPageCmd<Cursor>;\n\n/** Page-settled Msgs the scan `handlers` port dispatches back (inherited verbatim). */\nexport type ScanPageOkMsg<Page> = PageOkMsg<Page>;\nexport type ScanPageErrMsg = PageErrMsg;\n\n/** The scan retry / deadline timer Msg — inherited from paginated-walk. */\nexport type ReconcilerTimerMsg = PaginatedWalkTimerMsg;\n\n/**\n * Ports the consumer supplies to `handlers`. `run(cursor)` fetches ONE page of\n * the ACTUAL listing for the given cursor; throwing routes to `pageErr` (and\n * thus the scan backoff). Same shape as paginated-walk's port.\n */\nexport interface ReconcilerPorts<Cursor, Page>\n extends ResilientPorts<Cursor, Page> {}\n\n// ===========================================================================\n// The knob factory.\n// ===========================================================================\n\n/**\n * Build a reconciler knob from `config`. `rng` is injected for the underlying\n * scan-retry jitter — pass a fixed `() => 0` in tests to pin backoff; defaults\n * to `Math.random` (read at the verb boundary inside resilient-call, never in a\n * reconciler verb). Inherited straight from paginated-walk.\n *\n * Returns the uniform L2 knob contract: `init()`, the verbs `scan` / `pageOk` /\n * `pageErr` / `planned` / `applyNext` / `applied` / `onTimer`, the derived\n * `isComplete`, `subs(state)`, and `handlers(ports)`.\n */\nexport function createReconciler<\n Actual,\n Desired,\n Page,\n Change,\n ApplyCmd extends Cmd = Cmd,\n Cursor = number,\n>(\n config: ReconcilerConfig<Actual, Desired, Page, Change, ApplyCmd, Cursor>,\n rng: () => number = Math.random,\n) {\n type State = ReconcilerState<Actual, Change, Page, Cursor>;\n // The combined Cmd type the verbs can emit: the inherited scan page-fetch\n // effect plus whatever `apply(change)` yields.\n type OutCmd = ScanPageCmd<Cursor> | ApplyCmd;\n\n // The scan is a paginated-walk over the actual listing. We feed it the scan\n // cursor knobs + the resilience bricks; `onPage` is OURS — it is a pure marker\n // (we accumulate items in `pageOk` instead, where we hold the reconciler\n // slice). The walk's `Page` payload is the consumer's `Page`; the reconciler\n // slice stores the walk as `PaginatedWalkState<Cursor, unknown>` so its own\n // type does not have to thread `Page` (the page never lands in the slice — only\n // the extracted `actual` items do).\n const walk = createPaginatedWalk<Cursor, Page, never>(\n {\n firstPage: config.firstPage,\n nextCursor: config.nextCursor,\n // The walk's per-page hook emits no Cmds — the reconciler accumulates the\n // page's items itself in `pageOk` (where it holds its own slice), so the\n // walk's onPage stays empty.\n onPage: () => [],\n retry: config.retry,\n circuit: config.circuit,\n rateLimit: config.rateLimit,\n deadline: config.deadline,\n },\n rng,\n );\n\n /**\n * The applied-ledger key for a change. Custom `idOf` when supplied; otherwise\n * the change's serialized CONTENT — a stable per-change identity. NEVER the\n * positional index: an index-keyed ledger drops a never-applied change that\n * happens to land at a slot a different, already-applied change once occupied\n * (the re-plan defect). Identity is intrinsic to the change, so the ledger\n * means \"this change settled\", surviving a re-plan that reorders the slots.\n */\n function changeId(change: Change): string {\n return config.idOf ? config.idOf(change) : JSON.stringify(change);\n }\n\n /** Replace the walk sub-slice, leaving the rest of the reconciler untouched. */\n function withWalk(s: State, w: State[\"walk\"]): State {\n return { ...s, walk: w };\n }\n\n /** The starting slice: idle, fresh walk + empty accumulator / plan / ledger. */\n function init(): State {\n return {\n phase: \"idle\",\n walk: walk.init(),\n actual: [],\n plan: [],\n appliedCursor: 0,\n applied: initCache<Change>(),\n };\n }\n\n // === Verb: scan ==========================================================\n\n /**\n * Begin the reconcile: start the paginated walk over the ACTUAL world and\n * enter `scanning`. Issues the first scan-page fetch (gated through the\n * inherited resilience). A no-op on any non-`idle` phase — re-scanning a\n * reconcile already in flight would restart the actual walk and double-count,\n * so a stray `scan` is absorbed. PURE — `at` threads into the fetch gate.\n */\n function scan(s: State, at: number): readonly [State, readonly OutCmd[]] {\n if (s.phase !== \"idle\") return [s, []];\n const [w, cmds] = walk.start(s.walk, at);\n const next: State = { ...s, phase: \"scanning\", walk: w };\n return [next, cmds];\n }\n\n // === Verb: pageOk ========================================================\n\n /**\n * Record a successfully fetched actual-list `page`: append its items to the\n * `actual` accumulator and advance the walk. When the walk finishes (the\n * listing is exhausted), the full actual snapshot is in hand → compute the\n * plan (`planned`) and start applying. PURE — `at` is the scan clock.\n *\n * A stray `pageOk` while not `scanning` (a late duplicate after the scan\n * completed) is absorbed by the walk (no cursor advance) and contributes no\n * items — the reconcile never re-accumulates after the plan is computed.\n */\n function pageOk(\n s: State,\n page: Page,\n at: number,\n ): readonly [State, readonly OutCmd[]] {\n // Once we are past scanning, the actual snapshot is frozen — absorb stray pages.\n if (s.phase !== \"scanning\") {\n const [w] = walk.pageOk(s.walk, page, at);\n return [withWalk(s, w), []];\n }\n\n const [w, walkCmds] = walk.pageOk(s.walk, page, at);\n const actual = [...s.actual, ...config.itemsOf(page)];\n const scanned: State = { ...s, walk: w, actual };\n\n // The walk emits the next scan-page fetch while it stays fetching. When it\n // finishes (done / paused with no fetch), `walkCmds` is empty.\n const cmds: readonly OutCmd[] = walkCmds;\n\n // Scan exhausted → plan + begin applying. `walk.isComplete` is the honest\n // \"the API returned a null next cursor\" signal.\n if (walk.isComplete(scanned.walk)) {\n const [planned, applyCmds] = beginPlan(scanned, at);\n return [planned, [...cmds, ...applyCmds]];\n }\n\n return [scanned, cmds];\n }\n\n // === Verb: pageErr =======================================================\n\n /**\n * Record a failed scan-page fetch and back off via the inherited resilience:\n * schedule a retry (the scan cursor stays parked — no advance) or, once the\n * page-fetch retries are exhausted, the underlying call settles `failed`. When\n * the scan call is terminally failed, the whole reconcile enters `failed`.\n * PURE — `at` stamps the breaker trip + the retry-delay base.\n */\n function pageErr(\n s: State,\n error: unknown,\n at: number,\n ): readonly [State, readonly OutCmd[]] {\n const [w, cmds] = walk.pageErr(s.walk, error, at);\n let next = withWalk(s, w);\n // The page-fetch call settling `failed` (retries exhausted) is a terminal\n // scan failure → the reconcile cannot trust an incomplete actual snapshot.\n if (\n s.phase === \"scanning\" &&\n w.resilience.calls[PAGE_KEY]?.phase === \"failed\"\n ) {\n next = { ...next, phase: \"failed\" };\n }\n return [next, cmds];\n }\n\n // === Verb: planned =======================================================\n\n /**\n * Compute the remediation plan from the accumulated `actual` snapshot and the\n * configured `desired`, store it, and enter `applying`. Normally driven\n * internally by `pageOk` when the scan finishes (`beginPlan`), but exposed as a\n * verb so a consumer can force planning from an externally-supplied actual\n * snapshot (e.g. a single-shot actual fetch outside the paginated scan), or\n * re-plan after a desired change. An empty plan settles straight to `done`\n * (already in sync). PURE — `at` only stamps the (empty) apply-loop clock.\n *\n * `changes` is optional: omit it to diff the slice's own accumulated `actual`\n * against `config.desired`; pass an explicit plan to install it directly\n * (skipping the diff — the consumer computed it elsewhere).\n */\n function planned(\n s: State,\n at: number,\n changes?: readonly Change[],\n ): readonly [State, readonly OutCmd[]] {\n const plan = changes ?? config.diff(config.desired, s.actual);\n const planning: State = {\n ...s,\n phase: \"applying\",\n plan,\n appliedCursor: 0,\n };\n return applyNext(planning, at);\n }\n\n /** Internal: compute + install the plan and kick off the apply loop. */\n function beginPlan(\n s: State,\n at: number,\n ): readonly [State, readonly OutCmd[]] {\n return planned(s, at);\n }\n\n // === Verb: applyNext =====================================================\n\n /**\n * Emit the apply Cmd for the next not-yet-applied change in the plan, skipping\n * any whose id is already in the applied ledger (idempotent re-apply after\n * eviction). Advances `appliedCursor` past skipped + the emitted change. When\n * the cursor reaches the end of the plan, settles `done`. A no-op unless\n * `applying`. PURE — `at` is read only for the ledger lookup clock; `idOf`\n * keys the skip check.\n *\n * Emits at most ONE apply Cmd per call (the loop is sequential: each `applied`\n * drives the next `applyNext`). This keeps the actual-world mutation\n * one-at-a-time, matching a controller's serial reconcile.\n */\n function applyNext(\n s: State,\n at: number,\n ): readonly [State, readonly OutCmd[]] {\n if (s.phase !== \"applying\") return [s, []];\n\n let cursor = s.appliedCursor;\n // Skip changes already in the ledger (a resumed reconcile re-issues from the\n // cursor but must not re-apply what already settled).\n while (cursor < s.plan.length) {\n const change = s.plan[cursor];\n // `cursor < plan.length` guarantees an element under normal indexing; the\n // explicit `undefined` narrow (vs a non-null assertion) keeps the access\n // typed without a suppression and tolerates a sparse hole by skipping it.\n if (change === undefined) {\n cursor++;\n continue;\n }\n const id = changeId(change);\n if (cacheGet(s.applied, id, at) === undefined) {\n // This change is not yet applied — emit its Cmd and park the cursor here\n // (it advances to cursor+1 only once `applied` confirms the change).\n const advanced: State = { ...s, appliedCursor: cursor };\n return [advanced, [config.apply(change)]];\n }\n cursor++;\n }\n\n // Every change applied → reconcile complete.\n const done: State = { ...s, phase: \"done\", appliedCursor: cursor };\n return [done, []];\n }\n\n // === Verb: applied =======================================================\n\n /**\n * Record that `change` finished applying: write it into the applied ledger,\n * advance the cursor past it, and drive the next `applyNext`. PURE — `at` is\n * the ledger-write clock (the entry never expires in practice; a large TTL\n * keeps the idempotency window open for the reconcile's lifetime).\n *\n * `change` is the SAME change the consumer's apply handler settled (echoed back\n * on the settle Msg) — its id is computed from the change's own identity (NOT a\n * cursor position), so the ledger key matches the skip check in `applyNext`\n * regardless of where the change sits in the plan. A no-op unless `applying` (a\n * late settle after `done` is ignored). PURE.\n */\n function applied(\n s: State,\n change: Change,\n at: number,\n ): readonly [State, readonly OutCmd[]] {\n if (s.phase !== \"applying\") return [s, []];\n const id = changeId(change);\n // A very large TTL: the ledger entry must outlive the whole reconcile pass.\n // `get(..., at)` treats it as fresh for `at < expiresAtMs`, which holds for\n // any realistic reconcile (the cache exists to dedupe within one pass).\n const applied = cacheSet(\n s.applied,\n id,\n change,\n at,\n Number.MAX_SAFE_INTEGER,\n );\n const advanced: State = {\n ...s,\n applied,\n appliedCursor: s.appliedCursor + 1,\n };\n return applyNext(advanced, at);\n }\n\n // === Verb: onTimer =======================================================\n\n /**\n * A scan retry / deadline timer fired. Defers to paginated-walk's `onTimer`: a\n * retry timer re-issues the SAME scan-page fetch (re-fetching the parked\n * cursor); a deadline timer settles the scan call failed. A deadline-driven\n * terminal scan failure escalates the whole reconcile to `failed`. A stale fire\n * is a pure no-op (inherited identity). PURE.\n */\n function onTimer(\n s: State,\n msg: ReconcilerTimerMsg,\n ): readonly [State, readonly OutCmd[]] {\n const [w, cmds] = walk.onTimer(s.walk, msg);\n if (w === s.walk) {\n // Inherited no-op identity → keep our slice identity too.\n return [s, []];\n }\n let next = withWalk(s, w);\n if (\n s.phase === \"scanning\" &&\n w.resilience.calls[PAGE_KEY]?.phase === \"failed\"\n ) {\n next = { ...next, phase: \"failed\" };\n }\n return [next, cmds];\n }\n\n // === Derived predicate ===================================================\n\n /**\n * Whether the reconcile has fully settled — every planned change applied (or an\n * empty plan: already in sync). PURE, read-only. Lets a consumer's reducer fire\n * a \"reconcile complete\" Cmd without re-checking the phase by hand.\n */\n function isComplete(s: State): boolean {\n return s.phase === \"done\";\n }\n\n // === Subs ================================================================\n\n /**\n * Pre-wired subscriptions: exactly the scan's paginated-walk subs — a retry\n * timer while a scan page is `waiting_retry`, and (with the `deadline` brick) a\n * per-page deadline timer while a scan fetch is active. The apply loop emits no\n * timers (each apply settles via the consumer's own Msg), so once the scan\n * finishes the desired sub set empties. Reconciled by id; wire `subscribe: {\n * deadline: subscribeDeadline }` (re-exported below).\n */\n function subs(s: State): readonly DeadlineSub[] {\n return walk.subs(s.walk);\n }\n\n // === Handlers ============================================================\n\n /**\n * Pre-wired interpret handler for the scan page-fetch effect. Inherited from\n * paginated-walk (`resilient_run`): wraps the consumer's `run(cursor)` actual-\n * list port 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: rec.handlers({ run: (cursor) => api.listActual(cursor) })\n *\n * The apply-Cmd side is the CONSUMER's interpret handler (it knows how to\n * realize `apply(change)`); the consumer routes its settle Msg back through\n * `applied`. This knob only owns the scan's interpret cell.\n */\n function handlers(ports: ReconcilerPorts<Cursor, Page>) {\n return walk.handlers(ports);\n }\n\n return {\n init,\n scan,\n pageOk,\n pageErr,\n planned,\n applyNext,\n applied,\n onTimer,\n isComplete,\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.rec`. Convenience for the common single-slice host; hosts with\n * a differently-named field spread by hand. PURE — a thin record rebuild, no\n * clock / RNG.\n */\nexport function liftReconciler<\n S extends { rec: ReconcilerState<Actual, Change, Page, Cursor> },\n Actual,\n Change,\n Page,\n Cursor,\n C extends Cmd,\n>(\n state: S,\n [slice, cmds]: readonly [\n ReconcilerState<Actual, Change, Page, Cursor>,\n readonly C[],\n ],\n): readonly [S, readonly C[]] {\n return [{ ...state, rec: slice }, cmds];\n}\n\n/**\n * Re-export the deadline Sub primitives (inherited from paginated-walk) so\n * consumers wire one import: `subscribeDeadline` is the `subscribe` cell,\n * `deadlineSub` builds the Sub literal this knob's `subs` emits.\n */\nexport { subscribeDeadline, deadlineSub };\nexport type { DeadlineSub };\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/reconciler/index.ts"],"names":["w","applied"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @demlik/tea/reconciler — the desired-vs-actual sync loop (the \"fleet-sync /\n * coverage-gap\" shape): walk the ACTUAL world end to end, diff it against the\n * DESIRED spec, then apply each resulting `Change` one at a time until the two\n * agree. The control-plane reconcile pattern (Kubernetes controllers, fleet\n * config rollout, accessibility coverage-gap remediation) lifted to the uniform\n * L2 knob contract.\n *\n * ## The recipe (which bricks it composes)\n *\n * - **`../paginated-walk`** owns the SCAN. The actual world is rarely a single\n * fetch — it is a paginated list (every node in a fleet, every page of an\n * audit, every row of a table). `reconciler` does not re-implement that walk:\n * it embeds a `paginated-walk` slice and lets it sequence the actual-state\n * fetch (gated, retried, rate-limited, resumable across eviction). The walk's\n * own `onPage` hook stays empty (`() => []`) — `reconciler` accumulates each\n * fetched page's items into the running `actual` snapshot inside its OWN\n * `pageOk` verb, where it holds its slice and so can extend the accumulator\n * the walk cannot see; when the walk reports `isComplete`, the full actual\n * snapshot is in hand.\n * - **`../cache`** owns the APPLIED ledger. Every `Change` that settles is\n * written into a TTL cache keyed by the change's id, so a re-`applyNext`\n * after an eviction skips changes already applied (idempotent re-apply — the\n * reconcile is safe to resume from any point). The cache is the durable\n * \"what have I already done?\" memory the apply loop reads before re-issuing.\n *\n * Because the scan IS a `paginated-walk` slice (which itself IS a\n * `resilient-call` slice), this knob **inherits that contract verbatim**: the\n * slice is flat plain data, every verb returns `readonly [State, Cmd[]]`, time\n * is always an `at` parameter, and the jitter RNG is injected once at\n * `createReconciler(config, rng)` — never read inside a verb (the spec:\n * `reconciler` *extends* `paginated-walk`).\n *\n * ## Why the run lifecycle is INLINE (run-tracker not extracted)\n *\n * The reconcile phase union (`idle` / `scanning` / `applying` / `done` /\n * `failed`) and its bookkeeping (`plan`, `appliedCursor`, the `actual`\n * accumulator) live directly on this slice rather than behind a `run-tracker`\n * sub-knob — exactly as `monitored-run` keeps its lifecycle inline. Extraction\n * earns its keep when two independent axes vary; here the lifecycle has one\n * anchoring axis (this reconcile pass). The genuinely independent axes — the\n * paginated SCAN and the applied LEDGER — ARE extracted (`../paginated-walk`,\n * `../cache`). The lifecycle is the glue that sequences scan → plan → apply.\n *\n * ## The two non-negotiables (canon)\n *\n * - **Durable** — the slice is plain data: the paginated-walk slice, the\n * accumulated `actual` items, the planned `Change[]`, the applied cursor, and\n * the applied-ledger cache (all JSON-serializable). A reconcile survives DO\n * eviction mid-scan or mid-apply and resumes from the exact position.\n * - **Replayable** — every transition is a verb returning new state + Cmds.\n * `diff` / `apply` are pure config functions applied at the verb boundary;\n * the Cmds they yield are plain data. `replay` reconstructs the whole pass\n * exactly. Nothing here closes over time or RNG.\n *\n * ## Where the clock / RNG live\n *\n * Inside the verbs: nowhere. `at` arrives on `scan` / `pageOk` / `pageErr` /\n * `applied` / `onTimer` and threads into the underlying paginated-walk gate (so a\n * retried / rate-limited page measures backoff from that `at`) and into the\n * applied-ledger cache writes. The jitter RNG is injected at construction. The\n * only clock read is `Date.now()` inside the inherited paginated-walk `handlers`\n * port (the effect boundary).\n *\n * ## Typical wiring\n *\n * const rec = createReconciler<FleetNode, NodeSpec, Page, SyncChange, ApplyCmd>({\n * desired: targetSpecs, // the spec to converge on\n * firstPage: null, // actual-list page-one sentinel\n * nextCursor: (page) => page.next ?? null,\n * itemsOf: (page) => page.nodes, // page → actual items\n * diff: (desired, actual) => planChanges(desired, actual),\n * apply: (change) => ({ type: \"apply_change\", change }),\n * idOf: (change) => change.nodeId, // applied-ledger key\n * rateLimit: { capacity: 10, refillPerSec: 5 },\n * retry: defaultRetryPolicy,\n * });\n *\n * // in the machine:\n * init: () => [{ rec: rec.init() }, []],\n * update: {\n * reconcile: (s, m) => lift(s, rec.scan(s.rec, m.at)),\n * page_ok: (s, m) => lift(s, rec.pageOk(s.rec, m.result, m.at)),\n * page_err: (s, m) => lift(s, rec.pageErr(s.rec, m.error, m.at)),\n * change_done: (s, m) => lift(s, rec.applied(s.rec, m.change, m.at)),\n * retry_due: (s, m) => lift(s, rec.onTimer(s.rec, m)),\n * },\n * subscriptions: (s) => rec.subs(s.rec),\n * subscribe: { deadline: subscribeDeadline },\n * interpret: rec.handlers({ run: (cursor) => api.listActual(cursor) }),\n */\n\nimport {\n get as cacheGet,\n set as cacheSet,\n initCache,\n type TtlCache,\n} from \"../cache\";\nimport type { Cmd } from \"../index\";\nimport {\n createPaginatedWalk,\n type DeadlineSub,\n deadlineSub,\n type FetchPageCmd,\n PAGE_KEY,\n type PageErrMsg,\n type PageOkMsg,\n type PaginatedWalkState,\n type PaginatedWalkTimerMsg,\n subscribeDeadline,\n} from \"../paginated-walk\";\nimport type {\n CircuitConfig,\n DeadlineConfig,\n RateLimitConfig,\n ResilientPorts,\n} from \"../resilient-call\";\nimport type { RetryPolicy } from \"../retry-backoff\";\n\n// ===========================================================================\n// Config — the knob. `desired` / `diff` / `apply` / `idOf` describe the\n// reconcile; the SCAN knobs (firstPage / nextCursor / itemsOf) describe how the\n// actual world is walked; the resilience bricks are inherited from\n// paginated-walk (every one optional — omit a brick, omit its gate).\n// ===========================================================================\n\n/**\n * The reconciler knob. Generic in:\n * - `Actual` — one item of the ACTUAL world (a fleet node, an audited page, a\n * table row). The scan accumulates these.\n * - `Desired` — the DESIRED spec value the diff converges toward. Opaque to\n * this module; only `diff` reads it.\n * - `Page` — the shape one actual-list page fetch resolves with.\n * - `Change` — one unit of remediation the diff produces and `apply` realizes.\n * - `ApplyCmd`— the Cmd `apply(change)` yields (the effect that mutates the\n * actual world toward desired).\n *\n * `diff` / `apply` / `idOf` / `nextCursor` / `itemsOf` are PURE config functions\n * applied at the verb boundary, never stored in the slice or in a Cmd (Cmds are\n * plain data, not closures). The resilience bricks (`retry` / `circuit` /\n * `rateLimit` / `deadline`) are the SAME optional knobs `paginated-walk` /\n * `resilient-call` expose — reconciler extends them; `rateLimit` keeps the scan\n * from 429-ing the upstream listing API.\n */\nexport interface ReconcilerConfig<\n Actual,\n Desired,\n Page,\n Change,\n ApplyCmd extends Cmd,\n Cursor = number,\n> {\n /**\n * The DESIRED spec to converge on. Held verbatim and handed to `diff` once the\n * scan completes. Any JSON-serializable value — a list of specs, a target map,\n * a single target object — `diff` alone interprets it.\n */\n readonly desired: Desired;\n /**\n * Compute the remediation plan: the ordered `Change[]` that moves `actual` to\n * `desired`. Called once when the scan finishes (the full actual snapshot is in\n * hand). An empty plan means \"already in sync — nothing to apply\". PURE +\n * consumer-specific (only the consumer knows the domain's reconcile rules).\n */\n readonly diff: (\n desired: Desired,\n actual: readonly Actual[],\n ) => readonly Change[];\n /**\n * Realize one `Change` as the Cmd that mutates the actual world. Returns plain\n * data (a `{ type, ... }` Cmd the consumer's interpret handler performs).\n * PURE — the side effect happens when the runtime performs the emitted Cmd,\n * never here.\n */\n readonly apply: (change: Change) => ApplyCmd;\n /**\n * Stable identity for a `Change` — the applied-ledger cache key. A re-`applyNext`\n * after eviction (or after a re-plan) skips a change whose id is already in the\n * ledger (idempotent re-apply). MUST be a property of the change itself, NOT its\n * position in the plan: the ledger has to survive re-planning. When omitted the\n * default keys by the change's serialized CONTENT (`JSON.stringify(change)`) —\n * a stable per-change identity that is unchanged by where the change sits in the\n * plan. Supply a domain id (the target node id, the resource key) when one\n * exists; it is cheaper than serializing and dedupes two changes that differ\n * only in fields irrelevant to identity. PURE.\n *\n * Why NOT positional: keying by index silently drops changes on the documented\n * re-plan path. After applying the index-0 change, its `\"0\"` ledger entry would\n * match WHATEVER different, never-applied change lands at index 0 in the next\n * plan — the apply loop skips it as \"already done\". Identity must be intrinsic\n * to the change so the ledger means \"this change settled\", not \"slot N settled\".\n */\n readonly idOf?: (change: Change) => string;\n\n // --- scan knobs (the actual-world walk), inherited from paginated-walk ---\n /**\n * The cursor the actual-list scan fetches first. For an offset API typically\n * `0`; for a token API whatever sentinel the API treats as page one.\n */\n readonly firstPage: Cursor;\n /**\n * Extract the next scan cursor from a fetched page — a `Cursor` to keep\n * scanning, or `null` when the actual listing is exhausted (scan finishes,\n * plan computed). PURE + consumer-specific.\n */\n readonly nextCursor: (page: Page) => Cursor | null;\n /**\n * Extract the actual items from one fetched page. Each page's items are\n * appended to the running `actual` accumulator. PURE.\n */\n readonly itemsOf: (page: Page) => readonly Actual[];\n\n // --- resilience bricks, inherited from paginated-walk (all optional) ---\n /** Exponential-backoff retry policy for a transient scan-page failure. */\n readonly retry?: RetryPolicy;\n /** Circuit breaker around the scan fetch (one upstream → one breaker). */\n readonly circuit?: CircuitConfig;\n /** Token-bucket rate limit on the scan — the \"don't 429 the listing API\" knob. */\n readonly rateLimit?: RateLimitConfig;\n /** Overall wall-clock deadline per individual scan-page fetch. */\n readonly deadline?: DeadlineConfig;\n}\n\n// ===========================================================================\n// Slice — the Model field this knob owns. The paginated-walk scan slice + the\n// accumulated actual snapshot + the inline reconcile lifecycle (plan, applied\n// cursor, ledger). All flat plain data.\n// ===========================================================================\n\n/**\n * Reconcile lifecycle phase. Discriminated on `phase`:\n *\n * - `idle` — created, never scanned. `scan` starts the actual-world walk.\n * - `scanning` — the paginated-walk is traversing the actual listing; pages\n * fold into `actual` as they arrive. The scan owns the `walk`\n * sub-slice; this phase ends when the walk finishes.\n * - `applying` — the scan finished, `diff` produced the `plan`, and changes are\n * being applied one at a time. `appliedCursor` is the index of\n * the next change to apply.\n * - `done` — every planned change applied (or an empty plan: already in\n * sync). Terminal.\n * - `failed` — the scan failed terminally (the page-fetch retries were\n * exhausted / a deadline fired). `error` carries why. Terminal.\n */\nexport type ReconcilePhase =\n | \"idle\"\n | \"scanning\"\n | \"applying\"\n | \"done\"\n | \"failed\";\n\n/**\n * The slice. Plain data end to end:\n * - `phase` — the lifecycle discriminant.\n * - `walk` — the `../paginated-walk` scan slice (cursor + resilience).\n * Drives the actual-world traversal; POSITION survives\n * eviction.\n * - `actual` — the accumulated actual items, appended page by page during\n * the scan. The full snapshot `diff` is computed against.\n * - `plan` — the `Change[]` `diff` produced. Empty until the scan\n * finishes; the apply loop walks it by `appliedCursor`.\n * - `appliedCursor` — index of the NEXT change to apply (0 ≤ cursor ≤ plan.length).\n * `cursor === plan.length` means every change applied.\n * - `applied` — the applied-ledger cache: `idOf(change) → change`, written\n * as each change settles. The idempotency memory the apply\n * loop reads to skip already-applied changes after eviction.\n */\nexport interface ReconcilerState<Actual, Change, Page, Cursor = number> {\n readonly phase: ReconcilePhase;\n readonly walk: PaginatedWalkState<Cursor, Page>;\n readonly actual: readonly Actual[];\n readonly plan: readonly Change[];\n readonly appliedCursor: number;\n readonly applied: TtlCache<Change>;\n}\n\n// ===========================================================================\n// Cmds + Msgs the knob speaks. The scan emits the inherited page-fetch effect;\n// the apply loop emits the consumer's `apply(change)` Cmds.\n// ===========================================================================\n\n/**\n * The actual-list page-fetch effect: the inherited `resilient_run` Cmd from\n * paginated-walk, 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 ScanPageCmd<Cursor> = FetchPageCmd<Cursor>;\n\n/** Page-settled Msgs the scan `handlers` port dispatches back (inherited verbatim). */\nexport type ScanPageOkMsg<Page> = PageOkMsg<Page>;\nexport type ScanPageErrMsg = PageErrMsg;\n\n/** The scan retry / deadline timer Msg — inherited from paginated-walk. */\nexport type ReconcilerTimerMsg = PaginatedWalkTimerMsg;\n\n/**\n * Ports the consumer supplies to `handlers`. `run(cursor)` fetches ONE page of\n * the ACTUAL listing for the given cursor; throwing routes to `pageErr` (and\n * thus the scan backoff). Same shape as paginated-walk's port.\n */\nexport interface ReconcilerPorts<Cursor, Page>\n extends ResilientPorts<Cursor, Page> {}\n\n// ===========================================================================\n// The knob factory.\n// ===========================================================================\n\n/**\n * Build a reconciler knob from `config`. `rng` is injected for the underlying\n * scan-retry jitter — pass a fixed `() => 0` in tests to pin backoff; defaults\n * to `Math.random` (read at the verb boundary inside resilient-call, never in a\n * reconciler verb). Inherited straight from paginated-walk.\n *\n * Returns the uniform L2 knob contract: `init()`, the verbs `scan` / `pageOk` /\n * `pageErr` / `planned` / `applyNext` / `applied` / `onTimer`, the derived\n * `isComplete`, `subs(state)`, and `handlers(ports)`.\n */\nexport function createReconciler<\n Actual,\n Desired,\n Page,\n Change,\n ApplyCmd extends Cmd = Cmd,\n Cursor = number,\n>(\n config: ReconcilerConfig<Actual, Desired, Page, Change, ApplyCmd, Cursor>,\n rng: () => number = Math.random,\n) {\n type State = ReconcilerState<Actual, Change, Page, Cursor>;\n // The combined Cmd type the verbs can emit: the inherited scan page-fetch\n // effect plus whatever `apply(change)` yields.\n type OutCmd = ScanPageCmd<Cursor> | ApplyCmd;\n\n // The scan is a paginated-walk over the actual listing. We feed it the scan\n // cursor knobs + the resilience bricks; `onPage` is OURS — it is a pure marker\n // (we accumulate items in `pageOk` instead, where we hold the reconciler\n // slice). The walk's `Page` payload is the consumer's `Page`; the reconciler\n // slice stores the walk as `PaginatedWalkState<Cursor, unknown>` so its own\n // type does not have to thread `Page` (the page never lands in the slice — only\n // the extracted `actual` items do).\n const walk = createPaginatedWalk<Cursor, Page, never>(\n {\n firstPage: config.firstPage,\n nextCursor: config.nextCursor,\n // The walk's per-page hook emits no Cmds — the reconciler accumulates the\n // page's items itself in `pageOk` (where it holds its own slice), so the\n // walk's onPage stays empty.\n onPage: () => [],\n retry: config.retry,\n circuit: config.circuit,\n rateLimit: config.rateLimit,\n deadline: config.deadline,\n },\n rng,\n );\n\n /**\n * The applied-ledger key for a change. Custom `idOf` when supplied; otherwise\n * the change's serialized CONTENT — a stable per-change identity. NEVER the\n * positional index: an index-keyed ledger drops a never-applied change that\n * happens to land at a slot a different, already-applied change once occupied\n * (the re-plan defect). Identity is intrinsic to the change, so the ledger\n * means \"this change settled\", surviving a re-plan that reorders the slots.\n */\n function changeId(change: Change): string {\n return config.idOf ? config.idOf(change) : JSON.stringify(change);\n }\n\n /** Replace the walk sub-slice, leaving the rest of the reconciler untouched. */\n function withWalk(s: State, w: State[\"walk\"]): State {\n return { ...s, walk: w };\n }\n\n /** The starting slice: idle, fresh walk + empty accumulator / plan / ledger. */\n function init(): State {\n return {\n phase: \"idle\",\n walk: walk.init(),\n actual: [],\n plan: [],\n appliedCursor: 0,\n applied: initCache<Change>(),\n };\n }\n\n // === Verb: scan ==========================================================\n\n /**\n * Begin the reconcile: start the paginated walk over the ACTUAL world and\n * enter `scanning`. Issues the first scan-page fetch (gated through the\n * inherited resilience). A no-op on any non-`idle` phase — re-scanning a\n * reconcile already in flight would restart the actual walk and double-count,\n * so a stray `scan` is absorbed. PURE — `at` threads into the fetch gate.\n */\n function scan(s: State, at: number): readonly [State, readonly OutCmd[]] {\n if (s.phase !== \"idle\") return [s, []];\n const [w, cmds] = walk.start(s.walk, at);\n const next: State = { ...s, phase: \"scanning\", walk: w };\n return [next, cmds];\n }\n\n // === Verb: pageOk ========================================================\n\n /**\n * Record a successfully fetched actual-list `page`: append its items to the\n * `actual` accumulator and advance the walk. When the walk finishes (the\n * listing is exhausted), the full actual snapshot is in hand → compute the\n * plan (`planned`) and start applying. PURE — `at` is the scan clock.\n *\n * A stray `pageOk` while not `scanning` (a late duplicate after the scan\n * completed) is absorbed by the walk (no cursor advance) and contributes no\n * items — the reconcile never re-accumulates after the plan is computed.\n */\n function pageOk(\n s: State,\n page: Page,\n at: number,\n ): readonly [State, readonly OutCmd[]] {\n // Once we are past scanning, the actual snapshot is frozen — absorb stray pages.\n if (s.phase !== \"scanning\") {\n const [w] = walk.pageOk(s.walk, page, at);\n return [withWalk(s, w), []];\n }\n\n const [w, walkCmds] = walk.pageOk(s.walk, page, at);\n const actual = [...s.actual, ...config.itemsOf(page)];\n const scanned: State = { ...s, walk: w, actual };\n\n // The walk emits the next scan-page fetch while it stays fetching. When it\n // finishes (done / paused with no fetch), `walkCmds` is empty.\n const cmds: readonly OutCmd[] = walkCmds;\n\n // Scan exhausted → plan + begin applying. `walk.isComplete` is the honest\n // \"the API returned a null next cursor\" signal.\n if (walk.isComplete(scanned.walk)) {\n const [plannedState, applyCmds] = planned(scanned, at);\n return [plannedState, [...cmds, ...applyCmds]];\n }\n\n return [scanned, cmds];\n }\n\n // === Verb: pageErr =======================================================\n\n /**\n * Record a failed scan-page fetch and back off via the inherited resilience:\n * schedule a retry (the scan cursor stays parked — no advance) or, once the\n * page-fetch retries are exhausted, the underlying call settles `failed`. When\n * the scan call is terminally failed, the whole reconcile enters `failed`.\n * PURE — `at` stamps the breaker trip + the retry-delay base.\n */\n function pageErr(\n s: State,\n error: unknown,\n at: number,\n ): readonly [State, readonly OutCmd[]] {\n const [w, cmds] = walk.pageErr(s.walk, error, at);\n let next = withWalk(s, w);\n // The page-fetch call settling `failed` (retries exhausted) is a terminal\n // scan failure → the reconcile cannot trust an incomplete actual snapshot.\n if (\n s.phase === \"scanning\" &&\n w.resilience.calls[PAGE_KEY]?.phase === \"failed\"\n ) {\n next = { ...next, phase: \"failed\" };\n }\n return [next, cmds];\n }\n\n // === Verb: planned =======================================================\n\n /**\n * Compute the remediation plan from the accumulated `actual` snapshot and the\n * configured `desired`, store it, and enter `applying`. Normally driven\n * internally by `pageOk` when the scan finishes, but exposed as a\n * verb so a consumer can force planning from an externally-supplied actual\n * snapshot (e.g. a single-shot actual fetch outside the paginated scan), or\n * re-plan after a desired change. An empty plan settles straight to `done`\n * (already in sync). PURE — `at` only stamps the (empty) apply-loop clock.\n *\n * `changes` is optional: omit it to diff the slice's own accumulated `actual`\n * against `config.desired`; pass an explicit plan to install it directly\n * (skipping the diff — the consumer computed it elsewhere).\n */\n function planned(\n s: State,\n at: number,\n changes?: readonly Change[],\n ): readonly [State, readonly OutCmd[]] {\n const plan = changes ?? config.diff(config.desired, s.actual);\n const planning: State = {\n ...s,\n phase: \"applying\",\n plan,\n appliedCursor: 0,\n };\n return applyNext(planning, at);\n }\n\n // === Verb: applyNext =====================================================\n\n /**\n * Emit the apply Cmd for the next not-yet-applied change in the plan, skipping\n * any whose id is already in the applied ledger (idempotent re-apply after\n * eviction). Advances `appliedCursor` past skipped + the emitted change. When\n * the cursor reaches the end of the plan, settles `done`. A no-op unless\n * `applying`. PURE — `at` is read only for the ledger lookup clock; `idOf`\n * keys the skip check.\n *\n * Emits at most ONE apply Cmd per call (the loop is sequential: each `applied`\n * drives the next `applyNext`). This keeps the actual-world mutation\n * one-at-a-time, matching a controller's serial reconcile.\n */\n function applyNext(\n s: State,\n at: number,\n ): readonly [State, readonly OutCmd[]] {\n if (s.phase !== \"applying\") return [s, []];\n\n let cursor = s.appliedCursor;\n // Skip changes already in the ledger (a resumed reconcile re-issues from the\n // cursor but must not re-apply what already settled).\n while (cursor < s.plan.length) {\n const change = s.plan[cursor];\n // `cursor < plan.length` guarantees an element under normal indexing; the\n // explicit `undefined` narrow (vs a non-null assertion) keeps the access\n // typed without a suppression and tolerates a sparse hole by skipping it.\n if (change === undefined) {\n cursor++;\n continue;\n }\n const id = changeId(change);\n if (cacheGet(s.applied, id, at) === undefined) {\n // This change is not yet applied — emit its Cmd and park the cursor here\n // (it advances to cursor+1 only once `applied` confirms the change).\n const advanced: State = { ...s, appliedCursor: cursor };\n return [advanced, [config.apply(change)]];\n }\n cursor++;\n }\n\n // Every change applied → reconcile complete.\n const done: State = { ...s, phase: \"done\", appliedCursor: cursor };\n return [done, []];\n }\n\n // === Verb: applied =======================================================\n\n /**\n * Record that `change` finished applying: write it into the applied ledger,\n * advance the cursor past it, and drive the next `applyNext`. PURE — `at` is\n * the ledger-write clock (the entry never expires in practice; a large TTL\n * keeps the idempotency window open for the reconcile's lifetime).\n *\n * `change` is the SAME change the consumer's apply handler settled (echoed back\n * on the settle Msg) — its id is computed from the change's own identity (NOT a\n * cursor position), so the ledger key matches the skip check in `applyNext`\n * regardless of where the change sits in the plan. A no-op unless `applying` (a\n * late settle after `done` is ignored). PURE.\n */\n function applied(\n s: State,\n change: Change,\n at: number,\n ): readonly [State, readonly OutCmd[]] {\n if (s.phase !== \"applying\") return [s, []];\n const id = changeId(change);\n // A very large TTL: the ledger entry must outlive the whole reconcile pass.\n // `get(..., at)` treats it as fresh for `at < expiresAtMs`, which holds for\n // any realistic reconcile (the cache exists to dedupe within one pass).\n const applied = cacheSet(\n s.applied,\n id,\n change,\n at,\n Number.MAX_SAFE_INTEGER,\n );\n const advanced: State = {\n ...s,\n applied,\n appliedCursor: s.appliedCursor + 1,\n };\n return applyNext(advanced, at);\n }\n\n // === Verb: onTimer =======================================================\n\n /**\n * A scan retry / deadline timer fired. Defers to paginated-walk's `onTimer`: a\n * retry timer re-issues the SAME scan-page fetch (re-fetching the parked\n * cursor); a deadline timer settles the scan call failed. A deadline-driven\n * terminal scan failure escalates the whole reconcile to `failed`. A stale fire\n * is a pure no-op (inherited identity). PURE.\n */\n function onTimer(\n s: State,\n msg: ReconcilerTimerMsg,\n ): readonly [State, readonly OutCmd[]] {\n const [w, cmds] = walk.onTimer(s.walk, msg);\n if (w === s.walk) {\n // Inherited no-op identity → keep our slice identity too.\n return [s, []];\n }\n let next = withWalk(s, w);\n if (\n s.phase === \"scanning\" &&\n w.resilience.calls[PAGE_KEY]?.phase === \"failed\"\n ) {\n next = { ...next, phase: \"failed\" };\n }\n return [next, cmds];\n }\n\n // === Derived predicate ===================================================\n\n /**\n * Whether the reconcile has fully settled — every planned change applied (or an\n * empty plan: already in sync). PURE, read-only. Lets a consumer's reducer fire\n * a \"reconcile complete\" Cmd without re-checking the phase by hand.\n */\n function isComplete(s: State): boolean {\n return s.phase === \"done\";\n }\n\n // === Subs ================================================================\n\n /**\n * Pre-wired subscriptions: exactly the scan's paginated-walk subs — a retry\n * timer while a scan page is `waiting_retry`, and (with the `deadline` brick) a\n * per-page deadline timer while a scan fetch is active. The apply loop emits no\n * timers (each apply settles via the consumer's own Msg), so once the scan\n * finishes the desired sub set empties. Reconciled by id; wire `subscribe: {\n * deadline: subscribeDeadline }` (re-exported below).\n */\n function subs(s: State): readonly DeadlineSub[] {\n return walk.subs(s.walk);\n }\n\n // === Handlers ============================================================\n\n /**\n * Pre-wired interpret handler for the scan page-fetch effect. Inherited from\n * paginated-walk (`resilient_run`): wraps the consumer's `run(cursor)` actual-\n * list port 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: rec.handlers({ run: (cursor) => api.listActual(cursor) })\n *\n * The apply-Cmd side is the CONSUMER's interpret handler (it knows how to\n * realize `apply(change)`); the consumer routes its settle Msg back through\n * `applied`. This knob only owns the scan's interpret cell.\n */\n function handlers(ports: ReconcilerPorts<Cursor, Page>) {\n return walk.handlers(ports);\n }\n\n return {\n init,\n scan,\n pageOk,\n pageErr,\n planned,\n applyNext,\n applied,\n onTimer,\n isComplete,\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.rec`. Convenience for the common single-slice host; hosts with\n * a differently-named field spread by hand. PURE — a thin record rebuild, no\n * clock / RNG.\n */\nexport function liftReconciler<\n S extends { rec: ReconcilerState<Actual, Change, Page, Cursor> },\n Actual,\n Change,\n Page,\n Cursor,\n C extends Cmd,\n>(\n state: S,\n [slice, cmds]: readonly [\n ReconcilerState<Actual, Change, Page, Cursor>,\n readonly C[],\n ],\n): readonly [S, readonly C[]] {\n return [{ ...state, rec: slice }, cmds];\n}\n\n/**\n * Re-export the deadline Sub primitives (inherited from paginated-walk) 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"]}
|
package/dist/recorder/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { BootingRuntime } from '../
|
|
2
|
-
import '../core-
|
|
1
|
+
import { B as BootingRuntime } from '../runtime-types-CukV8kMX.js';
|
|
2
|
+
import '../core-42kapW0n.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* A recorded run, sufficient to reproduce it via `../trace-replay`.
|
|
@@ -17,15 +17,15 @@ interface Trace<S, M> {
|
|
|
17
17
|
* reproduces the exact starting point. `null` only if the recorder attached
|
|
18
18
|
* AFTER boot (it missed the boot observe) — replay then re-runs fresh `init`.
|
|
19
19
|
*/
|
|
20
|
-
loaded: S | null;
|
|
20
|
+
readonly loaded: S | null;
|
|
21
21
|
/** The ordered, non-null msgs as observed. The replay input sequence. */
|
|
22
|
-
msgs: readonly M[];
|
|
22
|
+
readonly msgs: readonly M[];
|
|
23
23
|
/**
|
|
24
24
|
* The last observed state. The oracle: `replayTrace` deep-compares its
|
|
25
25
|
* recomputed final state against this. Equal ⇒ the reducer still produces
|
|
26
26
|
* the same result for this input.
|
|
27
27
|
*/
|
|
28
|
-
finalState: S;
|
|
28
|
+
readonly finalState: S;
|
|
29
29
|
/**
|
|
30
30
|
* Per-transition `(msg, post-state)` pairs, in dispatch order, for human
|
|
31
31
|
* inspection (e.g. "show me the state after step 3"). When present, there
|
|
@@ -40,9 +40,9 @@ interface Trace<S, M> {
|
|
|
40
40
|
* line had a non-null state). A trace dumped with `captureSteps: false` has
|
|
41
41
|
* `steps === undefined`.
|
|
42
42
|
*/
|
|
43
|
-
steps?: readonly {
|
|
44
|
-
msg: M;
|
|
45
|
-
state: S;
|
|
43
|
+
readonly steps?: readonly {
|
|
44
|
+
readonly msg: M;
|
|
45
|
+
readonly state: S;
|
|
46
46
|
}[];
|
|
47
47
|
}
|
|
48
48
|
/** Options for {@link recorder}. */
|
|
@@ -59,7 +59,7 @@ interface RecorderOptions {
|
|
|
59
59
|
* The boot observe (`msg === null`) is ALWAYS recorded regardless of
|
|
60
60
|
* `sampleRate` — `loaded` and `finalState` are anchors, not samples.
|
|
61
61
|
*/
|
|
62
|
-
sampleRate?: number;
|
|
62
|
+
readonly sampleRate?: number;
|
|
63
63
|
/**
|
|
64
64
|
* When `true`, retain a per-transition `(msg, post-state)` pair in
|
|
65
65
|
* {@link Trace.steps} for EVERY recorded transition. Default `false`.
|
|
@@ -90,7 +90,7 @@ interface RecorderOptions {
|
|
|
90
90
|
* `loaded` + `msgs` + `finalState` only and never touches `steps`. The
|
|
91
91
|
* choice is purely "do I also want to inspect intermediate states?".
|
|
92
92
|
*/
|
|
93
|
-
captureSteps?: boolean;
|
|
93
|
+
readonly captureSteps?: boolean;
|
|
94
94
|
}
|
|
95
95
|
/**
|
|
96
96
|
* A live recorder attached to a Runtime. Mirrors `historyTracker`'s
|
|
@@ -202,9 +202,9 @@ declare function parseJSONL<S, M>(jsonl: string): Trace<S, M>;
|
|
|
202
202
|
* newline-delimited JSON.
|
|
203
203
|
*/
|
|
204
204
|
interface TraceAttachment {
|
|
205
|
-
filename: string;
|
|
206
|
-
data: string;
|
|
207
|
-
contentType: string;
|
|
205
|
+
readonly filename: string;
|
|
206
|
+
readonly data: string;
|
|
207
|
+
readonly contentType: string;
|
|
208
208
|
}
|
|
209
209
|
/**
|
|
210
210
|
* Serialize a {@link Trace} into a Sentry-shaped attachment so a crash event
|
|
@@ -231,11 +231,11 @@ declare function traceAttachment<S, M extends {
|
|
|
231
231
|
}>(trace: Trace<S, M>): TraceAttachment;
|
|
232
232
|
/** A Sentry-shaped breadcrumb. Matches the fields of Sentry's `Breadcrumb`. */
|
|
233
233
|
interface TraceBreadcrumb {
|
|
234
|
-
category: string;
|
|
235
|
-
type: string;
|
|
236
|
-
level: string;
|
|
237
|
-
message: string;
|
|
238
|
-
data?: Record<string, unknown>;
|
|
234
|
+
readonly category: string;
|
|
235
|
+
readonly type: string;
|
|
236
|
+
readonly level: string;
|
|
237
|
+
readonly message: string;
|
|
238
|
+
readonly data?: Record<string, unknown>;
|
|
239
239
|
}
|
|
240
240
|
/** Options for {@link breadcrumbsFromTrace}. */
|
|
241
241
|
interface BreadcrumbsOptions<M> {
|
|
@@ -245,14 +245,14 @@ interface BreadcrumbsOptions<M> {
|
|
|
245
245
|
* the returned list is the newest-last tail, matching how a Sentry
|
|
246
246
|
* breadcrumb ring buffer behaves (the crash context is the recent past).
|
|
247
247
|
*/
|
|
248
|
-
limit?: number;
|
|
248
|
+
readonly limit?: number;
|
|
249
249
|
/**
|
|
250
250
|
* Customize each breadcrumb's `message`. Default uses `msg.type`. Provide a
|
|
251
251
|
* serializer to surface a richer one-liner (e.g. include an id). The msg is
|
|
252
252
|
* ALSO always attached verbatim under `data.msg` regardless of this option,
|
|
253
253
|
* so the structured payload is never lost to the string rendering.
|
|
254
254
|
*/
|
|
255
|
-
serialize?: (msg: M) => string;
|
|
255
|
+
readonly serialize?: (msg: M) => string;
|
|
256
256
|
}
|
|
257
257
|
/**
|
|
258
258
|
* Project a {@link Trace}'s recorded msgs into Sentry-shaped breadcrumbs — a
|
package/dist/recorder/index.js
CHANGED
|
@@ -2,13 +2,15 @@ import { TtlCache } from '../cache/index.js';
|
|
|
2
2
|
import { CircuitState } from '../circuit-breaker/index.js';
|
|
3
3
|
import { DeadlineExceeded, DeadlineSub } from '../deadline/index.js';
|
|
4
4
|
export { deadlineSub, subscribeDeadline } from '../deadline/index.js';
|
|
5
|
-
import { C as Cmd } from '../core-
|
|
5
|
+
import { C as Cmd } from '../core-42kapW0n.js';
|
|
6
6
|
import { M as MsgType } from '../protocol-B5YmJr0d.js';
|
|
7
7
|
import { TokenBucket } from '../rate-limit/index.js';
|
|
8
|
-
import { RetryState,
|
|
9
|
-
import '../types-
|
|
8
|
+
import { RetryState, AnyRetryPolicy } from '../retry-backoff/index.js';
|
|
9
|
+
import '../types-KbL3W1mj.js';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
+
* @packageDocumentation
|
|
13
|
+
*
|
|
12
14
|
* @deprecated `@demlik/tea/resilient-call` is deprecated in favor of
|
|
13
15
|
* `@demlik/tea/with-resilience` — the higher-order `withResilience(base,
|
|
14
16
|
* config)` wrapper (TEA-idiomatic: it wraps any existing machine instead of
|
|
@@ -43,8 +45,7 @@ import '../types-BoUWgwSm.js';
|
|
|
43
45
|
* `llm-call`, `authed-call`, `reconciler`, `paginated-walk`) intentionally keep
|
|
44
46
|
* importing `../resilient-call` — the deprecation targets the PUBLISHED export,
|
|
45
47
|
* not the module's internal role.
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
+
*
|
|
48
49
|
* @demlik/tea/resilient-call — the ROOT composition: a single resilience knob
|
|
49
50
|
* that bundles cache → circuit-breaker → rate-limit → exponential-backoff retry
|
|
50
51
|
* (with a deadline-driven retry timer) around any fallible `(input) => Promise`
|
|
@@ -91,6 +92,12 @@ import '../types-BoUWgwSm.js';
|
|
|
91
92
|
* by default. The ONLY clock read is `Date.now()` inside the `handlers` port
|
|
92
93
|
* (the effect boundary), exactly as `resilient-fetch.ts` stamps its result Msgs.
|
|
93
94
|
*
|
|
95
|
+
* That same `at` is what makes a DURATION-bounded retry budget reachable here:
|
|
96
|
+
* `recordFailure` / `shouldRetry` are fed the failure's observation instant,
|
|
97
|
+
* so `config.retry` accepts any `AnyRetryPolicy` — a count, a wall-clock outage
|
|
98
|
+
* budget (`maxElapsedMs`), or explicit `unbounded: true` — with no new argument
|
|
99
|
+
* on any verb and no clock read inside one.
|
|
100
|
+
*
|
|
94
101
|
* ## Typical wiring
|
|
95
102
|
*
|
|
96
103
|
* const rc = createResilientCall<string, Result>({
|
|
@@ -151,7 +158,18 @@ interface DeadlineConfig {
|
|
|
151
158
|
* config `{}` is a valid pass-through — a bare effect with no resilience at all.
|
|
152
159
|
*/
|
|
153
160
|
interface ResilientConfig {
|
|
154
|
-
|
|
161
|
+
/**
|
|
162
|
+
* The backoff brick. Omit and a failure is terminal (no backoff, no timer).
|
|
163
|
+
*
|
|
164
|
+
* Any bound the `../retry-backoff` union admits: a count (`RetryPolicy`), a
|
|
165
|
+
* wall-clock outage budget (`DurationRetryPolicy`), or explicit
|
|
166
|
+
* `unbounded: true`. A duration bound needs no extra wiring: every path that
|
|
167
|
+
* records a failure already holds the instant it was observed as DATA —
|
|
168
|
+
* `fail`'s `msg.at` (stamped at the interpret boundary) and `gate`'s `at`
|
|
169
|
+
* (the caller's `attempt` / the retry timer's `atMs`) — so the streak clock
|
|
170
|
+
* is fed from a Msg, never from a `Date.now()` inside a verb (invariant 2).
|
|
171
|
+
*/
|
|
172
|
+
readonly retry?: AnyRetryPolicy;
|
|
155
173
|
readonly circuit?: CircuitConfig;
|
|
156
174
|
readonly rateLimit?: RateLimitConfig;
|
|
157
175
|
readonly cache?: CacheConfig;
|
|
@@ -207,6 +225,13 @@ type CallPhase<I, R> = {
|
|
|
207
225
|
interface ResilientState<I, R> {
|
|
208
226
|
readonly circuit: CircuitState;
|
|
209
227
|
readonly bucket: TokenBucket;
|
|
228
|
+
/**
|
|
229
|
+
* Per-key backoff counter. Entries minted by `backoff` are `TimedRetryState`s
|
|
230
|
+
* (they carry the streak's `firstFailureAtMs`, since every failure path holds
|
|
231
|
+
* the observation instant); the field is typed at the `RetryState` supertype
|
|
232
|
+
* because a key that has never failed has no streak, and a slice persisted
|
|
233
|
+
* before the origin existed rehydrates without one. Still plain data.
|
|
234
|
+
*/
|
|
210
235
|
readonly retry: Readonly<Record<string, RetryState>>;
|
|
211
236
|
readonly cache: TtlCache<R>;
|
|
212
237
|
readonly calls: Readonly<Record<string, CallPhase<I, R>>>;
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
export { createResilientCall, liftResilience } from '../chunk-
|
|
1
|
+
export { createResilientCall, liftResilience } from '../chunk-B5RAZVXW.js';
|
|
2
2
|
import '../chunk-JELSZN25.js';
|
|
3
|
-
export { deadlineSub, subscribeDeadline } from '../chunk-
|
|
4
|
-
import '../chunk-
|
|
5
|
-
import '../chunk-
|
|
6
|
-
import '../chunk-
|
|
7
|
-
import '../chunk-
|
|
8
|
-
import '../chunk-
|
|
3
|
+
export { deadlineSub, subscribeDeadline } from '../chunk-RMX4EOQK.js';
|
|
4
|
+
import '../chunk-Q2UM7BXU.js';
|
|
5
|
+
import '../chunk-S2EGRPCM.js';
|
|
6
|
+
import '../chunk-UF4MZSLT.js';
|
|
7
|
+
import '../chunk-2KNLKQVN.js';
|
|
8
|
+
import '../chunk-LYLDCIPH.js';
|
|
9
9
|
import '../chunk-2YFLOCWX.js';
|
|
10
|
-
import '../chunk-
|
|
11
|
-
import '../chunk-
|
|
10
|
+
import '../chunk-ZJ5AOYBH.js';
|
|
11
|
+
import '../chunk-D4NJBQAH.js';
|
|
12
|
+
import '../chunk-PBDYW2TU.js';
|
|
12
13
|
//# sourceMappingURL=index.js.map
|
|
13
14
|
//# sourceMappingURL=index.js.map
|