@noy-db/hub 0.2.0-pre.3 → 0.2.0-pre.31
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 +126 -0
- package/dist/adapter/index.d.ts +9 -0
- package/dist/adapter/index.js +14 -0
- package/dist/aggregate/index.d.ts +3 -2
- package/dist/aggregate/index.js +10 -8
- package/dist/aggregate/index.js.map +1 -1
- package/dist/attestation/index.d.ts +7 -5
- package/dist/attestation/index.js +7 -6
- package/dist/attestation/index.js.map +1 -1
- package/dist/blobs/index.d.ts +9 -7
- package/dist/blobs/index.js +12 -6
- package/dist/blobs/index.js.map +1 -1
- package/dist/bundle/index.d.ts +29 -18
- package/dist/bundle/index.js +53 -192
- package/dist/bundle/index.js.map +1 -1
- package/dist/{chunk-75QDHSE4.js → chunk-2QDWRK3B.js} +5 -5
- package/dist/{chunk-74JEQFMT.js → chunk-2UT7HJJR.js} +5 -5
- package/dist/chunk-2UT7HJJR.js.map +1 -0
- package/dist/{chunk-FCDO7UAO.js → chunk-2ZYIN3BL.js} +2 -2
- package/dist/{chunk-BFI3RS42.js → chunk-33EQCZXE.js} +2 -2
- package/dist/chunk-33EQCZXE.js.map +1 -0
- package/dist/{chunk-WRLHNG6H.js → chunk-4K4QKYK4.js} +4 -4
- package/dist/chunk-4K4QKYK4.js.map +1 -0
- package/dist/{chunk-HGZ7DC5H.js → chunk-4L6N56B4.js} +2 -2
- package/dist/chunk-4L6N56B4.js.map +1 -0
- package/dist/{chunk-YMYK7US4.js → chunk-4YD7VZH4.js} +2 -2
- package/dist/chunk-4YD7VZH4.js.map +1 -0
- package/dist/{chunk-ZUMGGHRB.js → chunk-5PTX7PKD.js} +4 -4
- package/dist/{chunk-QAU5HM6Q.js → chunk-6EKRQ7QF.js} +3 -3
- package/dist/{chunk-ZC2AAE6J.js → chunk-7GPQPLGO.js} +19 -4
- package/dist/chunk-7GPQPLGO.js.map +1 -0
- package/dist/{chunk-EPK6A3WJ.js → chunk-A3JMGXPG.js} +2 -2
- package/dist/chunk-A3JMGXPG.js.map +1 -0
- package/dist/{chunk-FXQYZNOW.js → chunk-A7DNZVAV.js} +1 -1
- package/dist/chunk-A7DNZVAV.js.map +1 -0
- package/dist/{chunk-UMLVJTYV.js → chunk-ADB7GPM3.js} +7 -4
- package/dist/chunk-ADB7GPM3.js.map +1 -0
- package/dist/{chunk-YL2DR3HY.js → chunk-ASIGWYRA.js} +2 -2
- package/dist/chunk-ASIGWYRA.js.map +1 -0
- package/dist/{chunk-2EYC3WDT.js → chunk-AWU4JF7C.js} +93 -93
- package/dist/chunk-AWU4JF7C.js.map +1 -0
- package/dist/chunk-BEZ2EV43.js +129 -0
- package/dist/chunk-BEZ2EV43.js.map +1 -0
- package/dist/{chunk-IS5HWQO7.js → chunk-CIZDUAHI.js} +30 -4
- package/dist/chunk-CIZDUAHI.js.map +1 -0
- package/dist/{chunk-4OQWR46B.js → chunk-CUPTCF4R.js} +5 -5
- package/dist/chunk-CUPTCF4R.js.map +1 -0
- package/dist/{chunk-5ZGZ6HIZ.js → chunk-DEE4CRYN.js} +30 -7
- package/dist/chunk-DEE4CRYN.js.map +1 -0
- package/dist/{chunk-UOF74WQY.js → chunk-E2MDPPOC.js} +37 -2
- package/dist/chunk-E2MDPPOC.js.map +1 -0
- package/dist/{chunk-SAVQ6E2O.js → chunk-FFKQ65YX.js} +10 -3
- package/dist/chunk-FFKQ65YX.js.map +1 -0
- package/dist/{chunk-2XLVPKXG.js → chunk-G7ALNCQH.js} +10 -2
- package/dist/chunk-G7ALNCQH.js.map +1 -0
- package/dist/chunk-GGXN73NM.js +252 -0
- package/dist/chunk-GGXN73NM.js.map +1 -0
- package/dist/{chunk-UVPGJXVO.js → chunk-JOJPBZSZ.js} +5 -5
- package/dist/{chunk-YDLAFP36.js → chunk-JZ5DS4DP.js} +346 -3
- package/dist/chunk-JZ5DS4DP.js.map +1 -0
- package/dist/{chunk-KMI2NBBF.js → chunk-K4JID2ZE.js} +44 -8
- package/dist/chunk-K4JID2ZE.js.map +1 -0
- package/dist/{aggregate/index.cjs → chunk-K6NVSHAH.js} +241 -210
- package/dist/chunk-K6NVSHAH.js.map +1 -0
- package/dist/chunk-KJ3K5VZM.js +524 -0
- package/dist/chunk-KJ3K5VZM.js.map +1 -0
- package/dist/chunk-L5HHBOMS.js +232 -0
- package/dist/chunk-L5HHBOMS.js.map +1 -0
- package/dist/{chunk-YK72A4IT.js → chunk-LBILABKZ.js} +4 -4
- package/dist/chunk-LXKBG52H.js +151 -0
- package/dist/chunk-LXKBG52H.js.map +1 -0
- package/dist/{chunk-NGSPBLLE.js → chunk-MPIZQGFA.js} +50 -20
- package/dist/chunk-MPIZQGFA.js.map +1 -0
- package/dist/{chunk-4X2S7PBF.js → chunk-MRJPSXPE.js} +84 -70
- package/dist/chunk-MRJPSXPE.js.map +1 -0
- package/dist/{chunk-6S3LLAQ5.js → chunk-MSMDDDDJ.js} +3 -3
- package/dist/{chunk-6S3LLAQ5.js.map → chunk-MSMDDDDJ.js.map} +1 -1
- package/dist/{chunk-GAUBWHAF.js → chunk-OCYFFBZS.js} +4 -4
- package/dist/{chunk-NCO2JGKK.js → chunk-PDVP3C2I.js} +1 -1
- package/dist/chunk-PDVP3C2I.js.map +1 -0
- package/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/{chunk-P6256WTJ.js → chunk-QBLPY44S.js} +3 -3
- package/dist/chunk-QBLPY44S.js.map +1 -0
- package/dist/{query/index.cjs → chunk-QGBBQKDS.js} +706 -831
- package/dist/chunk-QGBBQKDS.js.map +1 -0
- package/dist/{chunk-FS7A4XNF.js → chunk-QWJCNXH2.js} +3 -3
- package/dist/chunk-QXWJQ7UU.js +380 -0
- package/dist/chunk-QXWJQ7UU.js.map +1 -0
- package/dist/{chunk-GDTCGIPX.js → chunk-RCB6GWTC.js} +27 -5
- package/dist/chunk-RCB6GWTC.js.map +1 -0
- package/dist/{chunk-K5PVGKE4.js → chunk-SAAJYSVV.js} +2 -2
- package/dist/{chunk-G7PAZ3TD.js → chunk-SHJ3BRTZ.js} +46 -10
- package/dist/chunk-SHJ3BRTZ.js.map +1 -0
- package/dist/chunk-SQXTKZL7.js +125 -0
- package/dist/chunk-SQXTKZL7.js.map +1 -0
- package/dist/chunk-STNPB3UM.js +9 -0
- package/dist/chunk-STNPB3UM.js.map +1 -0
- package/dist/{chunk-NSLTPGEN.js → chunk-TMUMMAWV.js} +12 -2
- package/dist/{chunk-NSLTPGEN.js.map → chunk-TMUMMAWV.js.map} +1 -1
- package/dist/{chunk-LS3JLEIB.js → chunk-TZGWI5AX.js} +4 -4
- package/dist/{chunk-GD3BGKAR.js → chunk-UBF5JEOJ.js} +44 -5
- package/dist/chunk-UBF5JEOJ.js.map +1 -0
- package/dist/{chunk-T6HQMVML.js → chunk-W5NVNJDX.js} +5250 -801
- package/dist/chunk-W5NVNJDX.js.map +1 -0
- package/dist/chunk-XAYDHD2O.js +123 -0
- package/dist/chunk-XAYDHD2O.js.map +1 -0
- package/dist/{chunk-LOL725S4.js → chunk-XYW2CSCI.js} +31 -3
- package/dist/chunk-XYW2CSCI.js.map +1 -0
- package/dist/{chunk-TLFUDXVV.js → chunk-ZCMOS4H2.js} +31 -10
- package/dist/chunk-ZCMOS4H2.js.map +1 -0
- package/dist/{chunk-FBMXWVGP.js → chunk-ZK66B5EY.js} +450 -30
- package/dist/chunk-ZK66B5EY.js.map +1 -0
- package/dist/{chunk-GVXBHCZ2.js → chunk-ZPJSQPQH.js} +66 -7
- package/dist/chunk-ZPJSQPQH.js.map +1 -0
- package/dist/consent/index.d.ts +8 -6
- package/dist/consent/index.js +4 -3
- package/dist/consent/index.js.map +1 -1
- package/dist/crdt/index.js +1 -0
- package/dist/crdt/index.js.map +1 -1
- package/dist/{crypto-H2Y3DDFW.js → crypto-OSIV2IIW.js} +8 -3
- package/dist/{ulid-CiM2OAeM.d.ts → decrypt-partition-CXS5KopB.d.ts} +37 -82
- package/dist/{delegation-QSC7G5QC.js → delegation-BQTRJATI.js} +6 -5
- package/dist/derivations/index.d.ts +11 -9
- package/dist/derivations/index.js +9 -6
- package/dist/{dev-unlock-Cf2B7Kih.d.ts → dev-unlock-CnHTTVea.d.ts} +1 -1
- package/dist/discriminant-BN9REW3o.d.ts +60 -0
- package/dist/errors-BAWO5Z5a.d.ts +1500 -0
- package/dist/executor-KYSKFJ2R.js +9 -0
- package/dist/executor-M7W3NEKR.js +9 -0
- package/dist/executor-VDTDGWO6.js +13 -0
- package/dist/{fanout-sidecar-NRBWSLRK.js → fanout-sidecar-AZALISHB.js} +3 -2
- package/dist/fanout-sidecar-AZALISHB.js.map +1 -0
- package/dist/forget/index.d.ts +1 -0
- package/dist/forget/index.js +15 -0
- package/dist/guards/index.d.ts +16 -8
- package/dist/guards/index.js +12 -5
- package/dist/{hash-gVn_uKhp.d.ts → hash-oc5paYl0.d.ts} +1 -1
- package/dist/history/index.d.ts +9 -7
- package/dist/history/index.js +10 -7
- package/dist/history/index.js.map +1 -1
- package/dist/i18n/index.d.ts +8 -6
- package/dist/i18n/index.js +116 -15
- package/dist/i18n/index.js.map +1 -1
- package/dist/index-BMmajblo.d.ts +362 -0
- package/dist/index-BmhGztUi.d.ts +93 -0
- package/dist/{types-D9eB0Rvh.d.ts → index-DHn9lEP0.d.ts} +4305 -1523
- package/dist/{index-BF1B2HB9.d.ts → index-Ddm8D3Oo.d.ts} +186 -1151
- package/dist/index.d.ts +342 -70
- package/dist/index.js +420 -112
- package/dist/index.js.map +1 -1
- package/dist/indexing/index.d.ts +3 -3
- package/dist/indexing/index.js +5 -4
- package/dist/indexing/index.js.map +1 -1
- package/dist/issue-DPHRF2TI.js +13 -0
- package/dist/kernel/index.d.ts +11 -0
- package/dist/kernel/index.js +50 -0
- package/dist/{lazy-builder-Rpd-V3jP.d.ts → lazy-builder-ChSqcF5t.d.ts} +2 -2
- package/dist/{ledger-WOEJUYTP.js → ledger-CI7GSMLB.js} +7 -6
- package/dist/materialized-views/index.d.ts +23 -20
- package/dist/materialized-views/index.js +9 -7
- package/dist/mime-magic-JeLKHRng.d.ts +103 -0
- package/dist/noydb-NAU2DTFP.js +40 -0
- package/dist/overlay-views/index.d.ts +32 -12
- package/dist/overlay-views/index.js +7 -6
- package/dist/periods/index.d.ts +8 -6
- package/dist/periods/index.js +7 -6
- package/dist/{predicate-Dnu81tsS.d.cts → predicate-BmhBSPCH.d.ts} +87 -5
- package/dist/{public-envelope-OHQ5UZFM.js → public-envelope-MFMBFJLZ.js} +5 -4
- package/dist/query/index.d.ts +4 -3
- package/dist/query/index.js +14 -6
- package/dist/read-only-facade-3OQRZ3VS.js +8 -0
- package/dist/registry-IX5WKK4F.js +8 -0
- package/dist/registry-LMHO5265.js +11 -0
- package/dist/registry-TXCKWH26.js +9 -0
- package/dist/registry-VFEDQSYV.js +9 -0
- package/dist/revoke-5PUOZPJJ.js +18 -0
- package/dist/revoke-5PUOZPJJ.js.map +1 -0
- package/dist/sealed-record/index.d.ts +123 -0
- package/dist/sealed-record/index.js +43 -0
- package/dist/sealed-record/index.js.map +1 -0
- package/dist/session/index.d.ts +9 -7
- package/dist/session/index.js +4 -3
- package/dist/session/index.js.map +1 -1
- package/dist/shadow/index.d.ts +8 -6
- package/dist/shadow/index.js +3 -2
- package/dist/shadow/index.js.map +1 -1
- package/dist/{signer-M4K5HBLD.js → signer-UB5TQOZ6.js} +6 -5
- package/dist/signer-UB5TQOZ6.js.map +1 -0
- package/dist/snapshots/index.d.ts +30 -0
- package/dist/snapshots/index.js +153 -0
- package/dist/snapshots/index.js.map +1 -0
- package/dist/{stale-PAGCS4K5.js → stale-J3TUF5C5.js} +3 -2
- package/dist/stale-J3TUF5C5.js.map +1 -0
- package/dist/store/index.d.ts +15 -6
- package/dist/store/index.js +3 -2
- package/dist/{strategy-DSTrsZ8t.d.ts → strategy-8QGs5KQE.d.ts} +475 -7
- package/dist/sync/index.d.ts +7 -5
- package/dist/sync/index.js +5 -4
- package/dist/sync/index.js.map +1 -1
- package/dist/team/index.d.ts +8 -6
- package/dist/team/index.js +9 -8
- package/dist/transition-guard-Dy3NioQr.d.ts +165 -0
- package/dist/tx/index.d.ts +26 -8
- package/dist/tx/index.js +10 -5
- package/dist/tx/index.js.map +1 -1
- package/dist/ulid-DRH25k3y.d.ts +66 -0
- package/dist/{ulid-COREQ2RQ.js → ulid-KEPZMD2N.js} +2 -1
- package/dist/ulid-KEPZMD2N.js.map +1 -0
- package/dist/util/index.d.ts +2 -0
- package/dist/util/index.js +6 -1
- package/dist/util/index.js.map +1 -1
- package/dist/{with-materialized-view-qtoJ3xKJ.d.ts → with-materialized-view-DIVeez0W.d.ts} +2 -2
- package/dist/{with-overlayed-view-DFaRfgMr.d.ts → with-overlayed-view-DVZrc5Hb.d.ts} +2 -2
- package/dist/with-rollup-CbU-O4wP.d.ts +47 -0
- package/dist/zod-HAJM7LMS.js +14763 -0
- package/dist/zod-HAJM7LMS.js.map +1 -0
- package/package.json +73 -191
- package/dist/aggregate/index.cjs.map +0 -1
- package/dist/aggregate/index.d.cts +0 -38
- package/dist/attestation/index.cjs +0 -305
- package/dist/attestation/index.cjs.map +0 -1
- package/dist/attestation/index.d.cts +0 -52
- package/dist/blobs/index.cjs +0 -1480
- package/dist/blobs/index.cjs.map +0 -1
- package/dist/blobs/index.d.cts +0 -46
- package/dist/bundle/index.cjs +0 -18561
- package/dist/bundle/index.cjs.map +0 -1
- package/dist/bundle/index.d.cts +0 -176
- package/dist/chunk-2EYC3WDT.js.map +0 -1
- package/dist/chunk-2XLVPKXG.js.map +0 -1
- package/dist/chunk-4OQWR46B.js.map +0 -1
- package/dist/chunk-4UBOTYP5.js +0 -1367
- package/dist/chunk-4UBOTYP5.js.map +0 -1
- package/dist/chunk-4X2S7PBF.js.map +0 -1
- package/dist/chunk-5YHWBPOT.js +0 -19
- package/dist/chunk-5YHWBPOT.js.map +0 -1
- package/dist/chunk-5ZGZ6HIZ.js.map +0 -1
- package/dist/chunk-74JEQFMT.js.map +0 -1
- package/dist/chunk-A6SWRXUQ.js +0 -118
- package/dist/chunk-A6SWRXUQ.js.map +0 -1
- package/dist/chunk-BFI3RS42.js.map +0 -1
- package/dist/chunk-EMEX37ZN.js +0 -51
- package/dist/chunk-EMEX37ZN.js.map +0 -1
- package/dist/chunk-EPK6A3WJ.js.map +0 -1
- package/dist/chunk-FBMXWVGP.js.map +0 -1
- package/dist/chunk-FXQYZNOW.js.map +0 -1
- package/dist/chunk-G7PAZ3TD.js.map +0 -1
- package/dist/chunk-GD3BGKAR.js.map +0 -1
- package/dist/chunk-GDTCGIPX.js.map +0 -1
- package/dist/chunk-GVXBHCZ2.js.map +0 -1
- package/dist/chunk-HGZ7DC5H.js.map +0 -1
- package/dist/chunk-IS5HWQO7.js.map +0 -1
- package/dist/chunk-KMI2NBBF.js.map +0 -1
- package/dist/chunk-KYKMKLJ6.js +0 -340
- package/dist/chunk-KYKMKLJ6.js.map +0 -1
- package/dist/chunk-LOL725S4.js.map +0 -1
- package/dist/chunk-MRIBLZL3.js +0 -86
- package/dist/chunk-MRIBLZL3.js.map +0 -1
- package/dist/chunk-NCO2JGKK.js.map +0 -1
- package/dist/chunk-NGSPBLLE.js.map +0 -1
- package/dist/chunk-P6256WTJ.js.map +0 -1
- package/dist/chunk-SAVQ6E2O.js.map +0 -1
- package/dist/chunk-T6HQMVML.js.map +0 -1
- package/dist/chunk-TLFUDXVV.js.map +0 -1
- package/dist/chunk-UMLVJTYV.js.map +0 -1
- package/dist/chunk-UOF74WQY.js.map +0 -1
- package/dist/chunk-WRLHNG6H.js.map +0 -1
- package/dist/chunk-YDLAFP36.js.map +0 -1
- package/dist/chunk-YL2DR3HY.js.map +0 -1
- package/dist/chunk-YMYK7US4.js.map +0 -1
- package/dist/chunk-ZC2AAE6J.js.map +0 -1
- package/dist/consent/index.cjs +0 -204
- package/dist/consent/index.cjs.map +0 -1
- package/dist/consent/index.d.cts +0 -25
- package/dist/crdt/index.cjs +0 -152
- package/dist/crdt/index.cjs.map +0 -1
- package/dist/crdt/index.d.cts +0 -30
- package/dist/derivations/index.cjs +0 -351
- package/dist/derivations/index.cjs.map +0 -1
- package/dist/derivations/index.d.cts +0 -72
- package/dist/dev-unlock-De3mjQWv.d.cts +0 -263
- package/dist/executor-BZKFZVRC.js +0 -8
- package/dist/executor-GFZFDQXV.js +0 -8
- package/dist/executor-KT2IOZVP.js +0 -11
- package/dist/fanout-sidecar-NRBWSLRK.js.map +0 -1
- package/dist/guards/index.cjs +0 -322
- package/dist/guards/index.cjs.map +0 -1
- package/dist/guards/index.d.cts +0 -31
- package/dist/hash-vBCB0-Ps.d.cts +0 -63
- package/dist/history/index.cjs +0 -1222
- package/dist/history/index.cjs.map +0 -1
- package/dist/history/index.d.cts +0 -63
- package/dist/i18n/index.cjs +0 -840
- package/dist/i18n/index.cjs.map +0 -1
- package/dist/i18n/index.d.cts +0 -39
- package/dist/index-DVkvrgpm.d.cts +0 -2290
- package/dist/index.cjs +0 -23590
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -778
- package/dist/indexing/index.cjs +0 -738
- package/dist/indexing/index.cjs.map +0 -1
- package/dist/indexing/index.d.cts +0 -36
- package/dist/issue-BAJ7ZB4S.js +0 -12
- package/dist/lazy-builder-C-rPfWG0.d.cts +0 -304
- package/dist/materialized-views/index.cjs +0 -837
- package/dist/materialized-views/index.cjs.map +0 -1
- package/dist/materialized-views/index.d.cts +0 -184
- package/dist/mime-magic-CBBSOkjm.d.cts +0 -50
- package/dist/mime-magic-CBBSOkjm.d.ts +0 -50
- package/dist/noydb-XNQSKXGO.js +0 -34
- package/dist/overlay-views/index.cjs +0 -359
- package/dist/overlay-views/index.cjs.map +0 -1
- package/dist/overlay-views/index.d.cts +0 -82
- package/dist/periods/index.cjs +0 -1041
- package/dist/periods/index.cjs.map +0 -1
- package/dist/periods/index.d.cts +0 -22
- package/dist/predicate-Dnu81tsS.d.ts +0 -185
- package/dist/query/index.cjs.map +0 -1
- package/dist/query/index.d.cts +0 -3
- package/dist/read-only-facade-ITU6L7BL.js +0 -7
- package/dist/registry-2IEARCGT.js +0 -7
- package/dist/registry-CDHASH73.js +0 -10
- package/dist/registry-EMGLZGR6.js +0 -8
- package/dist/registry-NQALYR77.js +0 -8
- package/dist/revoke-7JOVLZFD.js +0 -17
- package/dist/session/index.cjs +0 -495
- package/dist/session/index.cjs.map +0 -1
- package/dist/session/index.d.cts +0 -46
- package/dist/shadow/index.cjs +0 -133
- package/dist/shadow/index.cjs.map +0 -1
- package/dist/shadow/index.d.cts +0 -17
- package/dist/store/index.cjs +0 -1083
- package/dist/store/index.cjs.map +0 -1
- package/dist/store/index.d.cts +0 -492
- package/dist/strategy-BSxFXGzb.d.cts +0 -110
- package/dist/strategy-DSTrsZ8t.d.cts +0 -601
- package/dist/sync/index.cjs +0 -1062
- package/dist/sync/index.cjs.map +0 -1
- package/dist/sync/index.d.cts +0 -43
- package/dist/team/index.cjs +0 -2798
- package/dist/team/index.cjs.map +0 -1
- package/dist/team/index.d.cts +0 -118
- package/dist/tx/index.cjs +0 -544
- package/dist/tx/index.cjs.map +0 -1
- package/dist/tx/index.d.cts +0 -21
- package/dist/types-CSLcfytP.d.cts +0 -12493
- package/dist/ulid-CG2YvAbg.d.cts +0 -603
- package/dist/util/index.cjs +0 -230
- package/dist/util/index.cjs.map +0 -1
- package/dist/util/index.d.cts +0 -77
- package/dist/with-derivation-Bzpj6UTv.d.ts +0 -13
- package/dist/with-derivation-DWajFh4K.d.cts +0 -13
- package/dist/with-guard-DF_Ul3DT.d.cts +0 -18
- package/dist/with-guard-DR7U-l4v.d.ts +0 -18
- package/dist/with-materialized-view-_piodoIz.d.cts +0 -27
- package/dist/with-overlayed-view-DwzCKxn2.d.cts +0 -13
- /package/dist/{crypto-H2Y3DDFW.js.map → adapter/index.js.map} +0 -0
- /package/dist/{chunk-75QDHSE4.js.map → chunk-2QDWRK3B.js.map} +0 -0
- /package/dist/{chunk-FCDO7UAO.js.map → chunk-2ZYIN3BL.js.map} +0 -0
- /package/dist/{chunk-ZUMGGHRB.js.map → chunk-5PTX7PKD.js.map} +0 -0
- /package/dist/{chunk-QAU5HM6Q.js.map → chunk-6EKRQ7QF.js.map} +0 -0
- /package/dist/{chunk-UVPGJXVO.js.map → chunk-JOJPBZSZ.js.map} +0 -0
- /package/dist/{chunk-YK72A4IT.js.map → chunk-LBILABKZ.js.map} +0 -0
- /package/dist/{chunk-GAUBWHAF.js.map → chunk-OCYFFBZS.js.map} +0 -0
- /package/dist/{delegation-QSC7G5QC.js.map → chunk-PZ5AY32C.js.map} +0 -0
- /package/dist/{chunk-FS7A4XNF.js.map → chunk-QWJCNXH2.js.map} +0 -0
- /package/dist/{chunk-K5PVGKE4.js.map → chunk-SAAJYSVV.js.map} +0 -0
- /package/dist/{chunk-LS3JLEIB.js.map → chunk-TZGWI5AX.js.map} +0 -0
- /package/dist/{executor-BZKFZVRC.js.map → crypto-OSIV2IIW.js.map} +0 -0
- /package/dist/{executor-GFZFDQXV.js.map → delegation-BQTRJATI.js.map} +0 -0
- /package/dist/{executor-KT2IOZVP.js.map → executor-KYSKFJ2R.js.map} +0 -0
- /package/dist/{issue-BAJ7ZB4S.js.map → executor-M7W3NEKR.js.map} +0 -0
- /package/dist/{ledger-WOEJUYTP.js.map → executor-VDTDGWO6.js.map} +0 -0
- /package/dist/{noydb-XNQSKXGO.js.map → forget/index.js.map} +0 -0
- /package/dist/{public-envelope-OHQ5UZFM.js.map → issue-DPHRF2TI.js.map} +0 -0
- /package/dist/{read-only-facade-ITU6L7BL.js.map → kernel/index.js.map} +0 -0
- /package/dist/{registry-2IEARCGT.js.map → ledger-CI7GSMLB.js.map} +0 -0
- /package/dist/{registry-CDHASH73.js.map → noydb-NAU2DTFP.js.map} +0 -0
- /package/dist/{registry-EMGLZGR6.js.map → public-envelope-MFMBFJLZ.js.map} +0 -0
- /package/dist/{registry-NQALYR77.js.map → read-only-facade-3OQRZ3VS.js.map} +0 -0
- /package/dist/{revoke-7JOVLZFD.js.map → registry-IX5WKK4F.js.map} +0 -0
- /package/dist/{signer-M4K5HBLD.js.map → registry-LMHO5265.js.map} +0 -0
- /package/dist/{stale-PAGCS4K5.js.map → registry-TXCKWH26.js.map} +0 -0
- /package/dist/{ulid-COREQ2RQ.js.map → registry-VFEDQSYV.js.map} +0 -0
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
import {
|
|
2
2
|
wrapDbWithPredicates
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-RCB6GWTC.js";
|
|
4
4
|
import {
|
|
5
5
|
canonicalGroupKey,
|
|
6
6
|
groupAndReduce
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-K6NVSHAH.js";
|
|
8
8
|
import {
|
|
9
|
+
applyI18nLocale
|
|
10
|
+
} from "./chunk-QXWJQ7UU.js";
|
|
11
|
+
import {
|
|
12
|
+
LocaleNotSpecifiedError,
|
|
9
13
|
MaterializedViewTooLargeError
|
|
10
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-JZ5DS4DP.js";
|
|
11
15
|
|
|
12
16
|
// src/materialized-views/executor.ts
|
|
13
17
|
var DEFAULT_MAX_ROWS = 1e5;
|
|
14
|
-
async function materializeQueryResult(q, mvName) {
|
|
18
|
+
async function materializeQueryResult(q, mvName, i18nLocale, i18nFields) {
|
|
15
19
|
if (typeof q?.toArray === "function") {
|
|
16
20
|
return await q.toArray();
|
|
17
21
|
}
|
|
18
22
|
if (typeof q?.run === "function") {
|
|
19
|
-
const
|
|
23
|
+
const runOpts = i18nLocale !== void 0 ? { locale: i18nLocale, i18nFields } : void 0;
|
|
24
|
+
const result = await Promise.resolve(q.run(runOpts));
|
|
20
25
|
if (Array.isArray(result)) {
|
|
21
26
|
return result;
|
|
22
27
|
}
|
|
@@ -30,13 +35,44 @@ async function materializeUnionResult(spec, db) {
|
|
|
30
35
|
const unified = [];
|
|
31
36
|
for (const arm of spec.unionSources) {
|
|
32
37
|
const coll = db.collection(arm.collection);
|
|
33
|
-
|
|
38
|
+
let q = coll.query();
|
|
39
|
+
if (arm.join?.length) {
|
|
40
|
+
for (const leg of arm.join) {
|
|
41
|
+
q = q.join(leg.field, { as: leg.as, maxRows: leg.maxRows, strategy: leg.strategy });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const sourceRows = q.toArray();
|
|
34
45
|
for (const r of sourceRows) {
|
|
35
|
-
|
|
46
|
+
const mapped = arm.map(r);
|
|
47
|
+
if (mapped == null) continue;
|
|
48
|
+
unified.push(mapped);
|
|
36
49
|
}
|
|
37
50
|
}
|
|
38
51
|
if (!spec.groupBy) return unified;
|
|
39
52
|
const groupFields = typeof spec.groupBy === "string" ? [spec.groupBy] : spec.groupBy;
|
|
53
|
+
if (spec.i18nLocale !== void 0 && spec.i18nFields !== void 0) {
|
|
54
|
+
const groupI18n = {};
|
|
55
|
+
for (const f of groupFields) {
|
|
56
|
+
const d = spec.i18nFields[f];
|
|
57
|
+
if (d !== void 0) groupI18n[f] = d;
|
|
58
|
+
}
|
|
59
|
+
if (Object.keys(groupI18n).length > 0) {
|
|
60
|
+
for (let i = 0; i < unified.length; i++) {
|
|
61
|
+
unified[i] = applyI18nLocale(unified[i], groupI18n, spec.i18nLocale, void 0, "mv");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
for (const f of groupFields) {
|
|
66
|
+
for (const row of unified) {
|
|
67
|
+
const v = row[f];
|
|
68
|
+
if (v !== null && typeof v === "object") {
|
|
69
|
+
throw new LocaleNotSpecifiedError(
|
|
70
|
+
f,
|
|
71
|
+
`Materialized view "${spec.name}" groups by "${f}", whose value is a raw i18n locale map \u2014 an unstable object group key. Declare { i18nLocale, i18nFields } on the MV to resolve it at the 'mv' layer, or group by a dictKey/staticDict code (the stable key) and resolve the label at read time.`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
40
76
|
if (!spec.aggregate) {
|
|
41
77
|
const seen = /* @__PURE__ */ new Map();
|
|
42
78
|
for (const row of unified) {
|
|
@@ -45,7 +81,7 @@ async function materializeUnionResult(spec, db) {
|
|
|
45
81
|
}
|
|
46
82
|
return [...seen.values()];
|
|
47
83
|
}
|
|
48
|
-
return groupAndReduce(unified, groupFields, spec.aggregate);
|
|
84
|
+
return groupAndReduce(unified, groupFields, spec.aggregate, spec.moneyFields);
|
|
49
85
|
}
|
|
50
86
|
var MaterializedViewExecutor = {
|
|
51
87
|
async refresh(reg, accessor) {
|
|
@@ -61,7 +97,7 @@ var MaterializedViewExecutor = {
|
|
|
61
97
|
rows = await materializeUnionResult(spec, ctxForQuery);
|
|
62
98
|
} else {
|
|
63
99
|
const q = spec.query(ctxForQuery);
|
|
64
|
-
rows = await materializeQueryResult(q, spec.name);
|
|
100
|
+
rows = await materializeQueryResult(q, spec.name, spec.i18nLocale, spec.i18nFields);
|
|
65
101
|
}
|
|
66
102
|
if (rows.length > maxRows) {
|
|
67
103
|
throw new MaterializedViewTooLargeError(spec.name, rows.length, maxRows);
|
|
@@ -142,4 +178,4 @@ async function listOutputIds(outputColl) {
|
|
|
142
178
|
export {
|
|
143
179
|
MaterializedViewExecutor
|
|
144
180
|
};
|
|
145
|
-
//# sourceMappingURL=chunk-
|
|
181
|
+
//# sourceMappingURL=chunk-SHJ3BRTZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/materialized-views/executor.ts"],"sourcesContent":["import type { Collection } from '../collection.js'\nimport type { TxContext } from '../tx/transaction.js'\nimport type { EncryptedEnvelope } from '../types.js'\nimport { MaterializedViewTooLargeError, LocaleNotSpecifiedError } from '../errors.js'\nimport type { MaterializedFromMeta, MVQueryContext, MaterializedViewStrategy } from './types.js'\nimport type { RegisteredMV } from './registry.js'\nimport { wrapDbWithPredicates } from './registry.js'\nimport { groupAndReduce } from '../aggregate/groupby.js'\nimport { canonicalGroupKey } from '../aggregate/canonical-key.js'\nimport { applyI18nLocale, type I18nTextDescriptor } from '../i18n/core.js'\n\n/**\n * Accessor shape passed in from the owning Vault. Mirrors v1's\n * `DerivationStaleAccessor` — provides the per-collection resolver\n * and the active TxContext so refresh writes/tombstones register on\n * `_executed` for rollback symmetry.\n */\nexport interface MVExecutorAccessor {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n getCollection(name: string): Collection<any>\n getActiveTxContext(): TxContext | null\n /**\n * Vault-shaped accessor passed to the MV's `query()` callback at\n * each refresh. Same instance the registry used at registration\n * time; threading through the executor lets the refresh path\n * re-evaluate the closure against the live vault state.\n */\n getQueryContext(): MVQueryContext\n}\n\nexport interface RefreshResult {\n /** Rows newly written / overwritten. */\n written: number\n /** Rows tombstoned via `_internalDelete` (only when `onEmpty: 'delete'`). */\n deleted: number\n /** Failed row writes (non-strict mode). */\n failed: number\n}\n\n/** Default cost ceiling — overridable per-MV via `spec.maxRows`. */\nconst DEFAULT_MAX_ROWS = 100_000\n\n/**\n * Materialize a query terminal that may be a `Query<T>` (call\n * `.toArray()`), an `Aggregation<R>` (call `.run()` returning a\n * single object — wrap as a one-row array), or a `GroupedAggregation<R>`\n * (call `.run()` returning an array of grouped rows). Branches on\n * available terminal at runtime — no type-discrimination at registration.\n */\nasync function materializeQueryResult(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n q: any,\n mvName: string,\n i18nLocale?: string,\n i18nFields?: Record<string, I18nTextDescriptor>,\n): Promise<ReadonlyArray<Record<string, unknown>>> {\n if (typeof q?.toArray === 'function') {\n // Query<T> — non-aggregate path. `.toArray()` returns Promise<T[]>.\n return await q.toArray()\n }\n if (typeof q?.run === 'function') {\n // Aggregation<R> or GroupedAggregation<R>. `.run()` is synchronous\n // and returns either a single object (Aggregation) or an array of\n // rows (GroupedAggregation). Promise.resolve() normalizes both\n // sync and async (future) variants.\n // #285 query-form MV grouping: when the MV declares i18nLocale, pass it +\n // i18nFields so a GroupedAggregation resolves i18n group keys before\n // bucketing (the Aggregation path ignores the extra arg).\n const runOpts = i18nLocale !== undefined ? { locale: i18nLocale, i18nFields } : undefined\n const result: unknown = await Promise.resolve(q.run(runOpts))\n if (Array.isArray(result)) {\n return result as ReadonlyArray<Record<string, unknown>>\n }\n // Single-aggregate result — wrap as one-row array. The consumer's\n // `rowKey()` should return a stable identity (often a literal\n // constant like `'total'`) since there's only one row.\n return [result as Record<string, unknown>]\n }\n throw new Error(\n `MV \"${mvName}\": query() must return a Query<T>, Aggregation, or GroupedAggregation. ` +\n `Got something without a .toArray() or .run() terminal.`,\n )\n}\n\n/**\n * Materialize a UNION-form MV: read every arm's source\n * collection, apply each arm's `map` to project rows into the unified\n * MV row shape, concatenate the mapped streams, then optionally run\n * `groupBy` + `aggregate` over the result.\n *\n * Modes (driven by `spec.groupBy` / `spec.aggregate`):\n *\n * - No `groupBy` → return the concatenated mapped rows unchanged.\n * - `groupBy` without `aggregate` → dedupe by composite group key,\n * keep the first row seen per key (later arms don't overwrite\n * earlier arms — Map insertion order rules).\n * - `groupBy` + `aggregate` → delegate to the shared `groupAndReduce`\n * pipeline used by `Query.groupBy().aggregate()`.\n *\n * Per-arm `map` is the schema-unification boundary; the strategy's\n * `TRow` type parameter enforces that every arm projects into the\n * same shape at compile time.\n *\n * @internal\n */\nasync function materializeUnionResult<TRow extends Record<string, unknown>>(\n spec: MaterializedViewStrategy<TRow>,\n db: MVQueryContext,\n): Promise<ReadonlyArray<Record<string, unknown>>> {\n const unified: TRow[] = []\n for (const arm of spec.unionSources!) {\n const coll = db.collection<Record<string, unknown>>(arm.collection)\n // Optional per-arm FK joins: chain `.join(field, { as, ... })` for\n // each declared leg before terminating. The aliased right-side\n // record lands at `sourceRow[leg.as]`, where the arm's `map` reads\n // it. Cast to `any` because the chained join widens the row type but\n // the executor treats every row as `Record<string, unknown>` — same\n // pattern the query-form join path uses.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let q: any = coll.query()\n if (arm.join?.length) {\n for (const leg of arm.join) {\n q = q.join(leg.field, { as: leg.as, maxRows: leg.maxRows, strategy: leg.strategy })\n }\n }\n const sourceRows = q.toArray() as ReadonlyArray<Record<string, unknown>>\n for (const r of sourceRows) {\n const mapped = arm.map(r)\n // null / undefined means \"omit this source row\" — skip without\n // pushing so groupBy/aggregate never see a null entry (#297).\n if (mapped == null) continue\n unified.push(mapped)\n }\n }\n\n if (!spec.groupBy) return unified\n\n const groupFields: readonly string[] =\n typeof spec.groupBy === 'string' ? [spec.groupBy] : spec.groupBy\n\n // #285 §2/§4 — i18n-aware group keys. An `i18nText` group field carries a raw\n // `{ locale: string }` map, an unstable object key. When `i18nLocale` is\n // declared (with `i18nFields` describing those fields), resolve the declared\n // group-key i18n fields to it at the `mv` layer FIRST — the same unified-rows\n // boundary where money is threaded — so buckets are stable strings and the\n // `mv`-layer `onMissing` policy fires here.\n if (spec.i18nLocale !== undefined && spec.i18nFields !== undefined) {\n const groupI18n: Record<string, I18nTextDescriptor> = {}\n for (const f of groupFields) {\n const d = spec.i18nFields[f]\n if (d !== undefined) groupI18n[f] = d\n }\n if (Object.keys(groupI18n).length > 0) {\n for (let i = 0; i < unified.length; i++) {\n unified[i] = applyI18nLocale(unified[i] as Record<string, unknown>, groupI18n, spec.i18nLocale, undefined, 'mv') as TRow\n }\n }\n }\n // Guard (always): a remaining object-valued group key — an undeclared i18n\n // field or a locale-less MV — would bucket on a map. Refuse, don't bucket wrong.\n for (const f of groupFields) {\n for (const row of unified) {\n const v = (row as Record<string, unknown>)[f]\n if (v !== null && typeof v === 'object') {\n throw new LocaleNotSpecifiedError(\n f,\n `Materialized view \"${spec.name}\" groups by \"${f}\", whose value is a raw i18n locale map — ` +\n `an unstable object group key. Declare { i18nLocale, i18nFields } on the MV to resolve it at ` +\n `the 'mv' layer, or group by a dictKey/staticDict code (the stable key) and resolve the label at read time.`,\n )\n }\n }\n }\n\n // groupBy without aggregate — dedupe by composite key, keep first\n // seen row per key. Useful for cross-arm uniqueness (e.g. unify two\n // sibling collections, keeping one row per natural key).\n if (!spec.aggregate) {\n const seen = new Map<string, TRow>()\n for (const row of unified) {\n const k = canonicalGroupKey(groupFields, row as Record<string, unknown>)\n if (!seen.has(k)) seen.set(k, row)\n }\n return [...seen.values()]\n }\n\n // groupBy + aggregate — delegate to the shared pipeline used by\n // `Query.groupBy().aggregate()`. Result rows carry each grouped\n // field in declaration order followed by the spec's reducer outputs.\n return groupAndReduce<Record<string, unknown>>(unified, groupFields, spec.aggregate, spec.moneyFields)\n}\n\n/**\n * Run an MV's `query()` and write the result rows to the output\n * collection. Same-DEK encryption: routes through the standard\n * `Collection.put` pipeline, so the output collection's DEK is what\n * gets used (matches the v2 spec's \"same DEK as the left-most source\"\n * invariant — `Collection.put` looks up the DEK by collection name,\n * and the output collection IS the MV's owned collection).\n *\n * Stamps `_materializedFrom` onto every emitted row.\n *\n * **Tombstoning:** when `spec.onEmpty: 'delete'` (default), rows\n * that existed in a prior refresh but no longer appear in the new\n * materialized result are deleted via `Collection._internalDelete` —\n * the housekeeping bypass primitive prevents user\n * `onDelete` guards on the output collection from firing on these\n * system-internal deletes. `onEmpty: 'keep'` opts out (rows from\n * prior refreshes linger even when the new result lacks them).\n *\n * **Cost ceiling:** if the materialized row count exceeds\n * `spec.maxRows` (default 100k), throws `MaterializedViewTooLargeError`\n * before any writes hit the store — so strict-mode rollback is\n * clean.\n *\n * **Strict mode:** `spec.strict === true` re-throws on any\n * row-write failure; the active TxContext registration means the\n * source-write rolls back atomically via `revertExecuted`.\n *\n * @internal\n */\nexport const MaterializedViewExecutor = {\n async refresh(\n reg: RegisteredMV,\n accessor: MVExecutorAccessor,\n ): Promise<RefreshResult> {\n const spec = reg.spec\n const outputColl = accessor.getCollection(reg.outputCollection)\n const maxRows = spec.maxRows ?? DEFAULT_MAX_ROWS\n const onEmpty = spec.onEmpty ?? 'delete'\n const strict = spec.strict ?? false\n\n // 1. Materialize the query (branches on terminal shape). If the\n // MV declared predicates, wrap the query context the same way\n // the registry did at registration time so `.wherePredicate()`\n // calls resolve to the registered functions.\n const baseCtx = accessor.getQueryContext()\n const ctxForQuery: MVQueryContext = spec.predicates\n ? wrapDbWithPredicates(baseCtx, spec.predicates)\n : baseCtx\n // UNION-form strategies: read every arm, map to the unified\n // row shape, concatenate, then optionally groupBy + aggregate. The\n // single-source `query()` path is untouched.\n let rows: ReadonlyArray<Record<string, unknown>>\n if (spec.unionSources) {\n rows = await materializeUnionResult(spec, ctxForQuery)\n } else {\n const q = spec.query!(ctxForQuery)\n rows = await materializeQueryResult(q, spec.name, spec.i18nLocale, spec.i18nFields)\n }\n\n // 2. Cost ceiling check BEFORE any writes — keeps the rollback\n // clean if the source-write is wrapped in a transaction.\n if (rows.length > maxRows) {\n throw new MaterializedViewTooLargeError(spec.name, rows.length, maxRows)\n }\n\n const txCtx = accessor.getActiveTxContext()\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const adapter = (outputColl as any).adapter as {\n get(v: string, c: string, i: string): Promise<EncryptedEnvelope | null>\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const vaultName = (outputColl as any).vault as string\n\n // 3. Compute the post-refresh id set so we can diff against the\n // prior-emitted id set for tombstoning (when onEmpty === 'delete').\n const newIds = new Set<string>()\n const enrichedRows: Array<{ id: string; record: Record<string, unknown> }> = []\n for (const row of rows) {\n const id = spec.rowKey(row)\n newIds.add(id)\n const meta: MaterializedFromMeta = {\n mvName: spec.name,\n queryHash: reg.queryHash,\n sourceVersions: {},\n materializedAt: new Date().toISOString(),\n }\n enrichedRows.push({ id, record: { ...row, _materializedFrom: meta } })\n }\n\n // 4. Write the new rows.\n let written = 0\n let failed = 0\n for (const { id, record } of enrichedRows) {\n try {\n if (txCtx !== null) {\n const prior = await adapter.get(vaultName, reg.outputCollection, id)\n txCtx._executed.push({\n op: { type: 'put', vaultName, collectionName: reg.outputCollection, id },\n priorEnvelope: prior,\n })\n }\n await outputColl.put(id, record)\n written++\n } catch (err) {\n failed++\n if (strict) throw err\n \n console.warn(`[mv] \"${spec.name}\" row write failed:`, err)\n }\n }\n\n // 5. Tombstone rows that existed before but don't appear now.\n // `onEmpty: 'keep'` skips this pass entirely. Uses\n // `_internalDelete` so a user-registered `onDelete` on the\n // output collection does NOT fire on housekeeping (composition fix).\n let deleted = 0\n if (onEmpty === 'delete') {\n const priorIds = await listOutputIds(outputColl)\n for (const priorId of priorIds) {\n if (newIds.has(priorId)) continue\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const outAny = outputColl as any\n if (typeof outAny._internalDelete === 'function') {\n await outAny._internalDelete(priorId, txCtx)\n deleted++\n } else {\n // Defensive fallback — should never hit in real flow since\n // every Collection has `_internalDelete`.\n await outputColl.delete(priorId)\n deleted++\n }\n } catch (err) {\n failed++\n if (strict) throw err\n \n console.warn(`[mv] \"${spec.name}\" tombstone failed for id=\"${priorId}\":`, err)\n }\n }\n }\n\n return { written, deleted, failed }\n },\n}\n\n/**\n * List ids currently present in the MV's output collection via the\n * adapter directly (avoids triggering the lazy resolve-on-read path\n * we're INSIDE). Returns an empty array if the collection doesn't\n * exist or the adapter doesn't surface a list method.\n *\n * @internal\n */\nasync function listOutputIds(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n outputColl: Collection<any>,\n): Promise<string[]> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const cAny = outputColl as any\n const adapter = cAny.adapter as { list?: (v: string, c: string) => Promise<readonly string[]> }\n const vault = cAny.vault as string\n const name = cAny.name as string\n if (typeof adapter?.list !== 'function') return []\n try {\n const ids = await adapter.list(vault, name)\n return [...ids]\n } catch {\n return []\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import {
|
|
2
|
+
NOYDB_FORMAT_VERSION
|
|
3
|
+
} from "./chunk-A7DNZVAV.js";
|
|
4
|
+
import {
|
|
5
|
+
decrypt,
|
|
6
|
+
encrypt
|
|
7
|
+
} from "./chunk-E2MDPPOC.js";
|
|
8
|
+
|
|
9
|
+
// src/forget/strategy.ts
|
|
10
|
+
var NO_FORGET = { subjects: {} };
|
|
11
|
+
function withForgetCascade(opts) {
|
|
12
|
+
return { subjects: { ...opts.subjects } };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// src/forget/subject-index.ts
|
|
16
|
+
var SUBJECT_INDEX_COLLECTION = "_subject_index";
|
|
17
|
+
async function sha256HexString(input) {
|
|
18
|
+
const bytes = new TextEncoder().encode(input);
|
|
19
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
|
|
20
|
+
return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
21
|
+
}
|
|
22
|
+
async function subjectKey(subjectId) {
|
|
23
|
+
return sha256HexString(subjectId);
|
|
24
|
+
}
|
|
25
|
+
async function readRefs(adapter, vault, getDEK, encrypted, key) {
|
|
26
|
+
const env = await adapter.get(vault, SUBJECT_INDEX_COLLECTION, key);
|
|
27
|
+
if (!env || !env._data) return [];
|
|
28
|
+
if (!encrypted) return JSON.parse(env._data);
|
|
29
|
+
const dek = await getDEK(SUBJECT_INDEX_COLLECTION);
|
|
30
|
+
const json = await decrypt(env._iv, env._data, dek);
|
|
31
|
+
return JSON.parse(json);
|
|
32
|
+
}
|
|
33
|
+
async function writeRefs(adapter, vault, getDEK, encrypted, key, refs) {
|
|
34
|
+
const json = JSON.stringify(refs);
|
|
35
|
+
let env;
|
|
36
|
+
if (!encrypted) {
|
|
37
|
+
env = { _noydb: NOYDB_FORMAT_VERSION, _v: 1, _ts: (/* @__PURE__ */ new Date()).toISOString(), _iv: "", _data: json };
|
|
38
|
+
} else {
|
|
39
|
+
const dek = await getDEK(SUBJECT_INDEX_COLLECTION);
|
|
40
|
+
const { iv, data } = await encrypt(json, dek);
|
|
41
|
+
env = { _noydb: NOYDB_FORMAT_VERSION, _v: 1, _ts: (/* @__PURE__ */ new Date()).toISOString(), _iv: iv, _data: data };
|
|
42
|
+
}
|
|
43
|
+
await adapter.put(vault, SUBJECT_INDEX_COLLECTION, key, env);
|
|
44
|
+
}
|
|
45
|
+
async function addSubjectRef(adapter, vault, getDEK, encrypted, subjectId, ref) {
|
|
46
|
+
const key = await subjectKey(subjectId);
|
|
47
|
+
const refs = await readRefs(adapter, vault, getDEK, encrypted, key);
|
|
48
|
+
if (refs.some((r) => r.collection === ref.collection && r.id === ref.id)) return;
|
|
49
|
+
refs.push(ref);
|
|
50
|
+
await writeRefs(adapter, vault, getDEK, encrypted, key, refs);
|
|
51
|
+
}
|
|
52
|
+
async function removeSubjectRef(adapter, vault, getDEK, encrypted, subjectId, ref) {
|
|
53
|
+
const key = await subjectKey(subjectId);
|
|
54
|
+
const refs = await readRefs(adapter, vault, getDEK, encrypted, key);
|
|
55
|
+
const next = refs.filter((r) => !(r.collection === ref.collection && r.id === ref.id));
|
|
56
|
+
if (next.length === refs.length) return;
|
|
57
|
+
if (next.length === 0) {
|
|
58
|
+
await adapter.delete(vault, SUBJECT_INDEX_COLLECTION, key);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
await writeRefs(adapter, vault, getDEK, encrypted, key, next);
|
|
62
|
+
}
|
|
63
|
+
async function lookupSubject(adapter, vault, getDEK, encrypted, subjectId) {
|
|
64
|
+
const key = await subjectKey(subjectId);
|
|
65
|
+
return readRefs(adapter, vault, getDEK, encrypted, key);
|
|
66
|
+
}
|
|
67
|
+
async function rebuildSubjectIndex(adapter, vault, getDEK, encrypted, subjects, decodeRecord) {
|
|
68
|
+
const existing = await adapter.list(vault, SUBJECT_INDEX_COLLECTION);
|
|
69
|
+
for (const k of existing) {
|
|
70
|
+
await adapter.delete(vault, SUBJECT_INDEX_COLLECTION, k);
|
|
71
|
+
}
|
|
72
|
+
const bySubject = /* @__PURE__ */ new Map();
|
|
73
|
+
for (const [collection, field] of Object.entries(subjects)) {
|
|
74
|
+
const ids = await adapter.list(vault, collection);
|
|
75
|
+
for (const id of ids) {
|
|
76
|
+
if (id.startsWith("_")) continue;
|
|
77
|
+
const env = await adapter.get(vault, collection, id);
|
|
78
|
+
if (!env || !env._data) continue;
|
|
79
|
+
const record = await decodeRecord(collection, id, env);
|
|
80
|
+
if (record === null) continue;
|
|
81
|
+
const subjectValue = readDottedPath(record, field);
|
|
82
|
+
if (subjectValue === void 0 || subjectValue === null) continue;
|
|
83
|
+
const subjectId = coerceSubjectId(subjectValue);
|
|
84
|
+
const list = bySubject.get(subjectId) ?? [];
|
|
85
|
+
list.push({ collection, id });
|
|
86
|
+
bySubject.set(subjectId, list);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
let entries = 0;
|
|
90
|
+
for (const [subjectId, refs] of bySubject) {
|
|
91
|
+
const key = await subjectKey(subjectId);
|
|
92
|
+
await writeRefs(adapter, vault, getDEK, encrypted, key, refs);
|
|
93
|
+
entries++;
|
|
94
|
+
}
|
|
95
|
+
return entries;
|
|
96
|
+
}
|
|
97
|
+
function coerceSubjectId(value) {
|
|
98
|
+
if (typeof value === "string") return value;
|
|
99
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
100
|
+
return String(value);
|
|
101
|
+
}
|
|
102
|
+
return JSON.stringify(value);
|
|
103
|
+
}
|
|
104
|
+
function readDottedPath(record, field) {
|
|
105
|
+
if (!field.includes(".")) return record[field];
|
|
106
|
+
let cursor = record;
|
|
107
|
+
for (const segment of field.split(".")) {
|
|
108
|
+
if (cursor === null || cursor === void 0) return void 0;
|
|
109
|
+
cursor = cursor[segment];
|
|
110
|
+
}
|
|
111
|
+
return cursor;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export {
|
|
115
|
+
NO_FORGET,
|
|
116
|
+
withForgetCascade,
|
|
117
|
+
SUBJECT_INDEX_COLLECTION,
|
|
118
|
+
addSubjectRef,
|
|
119
|
+
removeSubjectRef,
|
|
120
|
+
lookupSubject,
|
|
121
|
+
rebuildSubjectIndex,
|
|
122
|
+
coerceSubjectId,
|
|
123
|
+
readDottedPath
|
|
124
|
+
};
|
|
125
|
+
//# sourceMappingURL=chunk-SQXTKZL7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/forget/strategy.ts","../src/forget/subject-index.ts"],"sourcesContent":["/**\n * `withForgetCascade` — declaration surface for GDPR right-to-erasure via\n * per-record CEK crypto-shred (#304, step 2 of the CEK security epic).\n *\n * This file holds only the *declaration* shape and the disabled sentinel.\n * The actual erasure machinery lives in:\n * - `subject-index.ts` — the encrypted `_subject_index` reserved collection\n * - `vault.ts` `forget()` — the per-record tombstone + ledger flow\n * - `collection.ts` `_writeTombstone` — the envelope rewrite\n *\n * A `ForgetStrategy` declares which collections carry erasable subject data\n * and the (dotted-path) field on each record that names the data subject.\n * Declaring a collection here ALSO forces `perRecordKeys: true` for it (a\n * shred can only erase a record whose body is keyed off a per-record CEK),\n * so adopters opt into the CEK foundation transitively.\n *\n * @module\n */\nimport type { LedgerEntry } from '../history/ledger/entry.js'\n\n/**\n * User-supplied declaration passed to {@link withForgetCascade}. Maps a\n * collection name to the record field (dotted path supported, e.g.\n * `'billing.buyerId'`) that identifies the data subject for erasure.\n *\n * ```ts\n * withForgetCascade({ subjects: { invoices: 'buyerId', contacts: 'id' } })\n * ```\n */\nexport interface SubjectDeclaration {\n readonly subjects: Record<string, string>\n}\n\n/**\n * Resolved forget strategy threaded through Noydb → every Vault. Carries\n * the same `subjects` map the user declared. `NO_FORGET` (empty map) is the\n * off-by-default sentinel; `vault.forget()` throws\n * `ForgetStrategyNotConfiguredError` when the map is empty.\n */\nexport interface ForgetStrategy {\n /** Collection → subject-field (dotted path). Empty under `NO_FORGET`. */\n readonly subjects: Readonly<Record<string, string>>\n}\n\n/**\n * Disabled sentinel — no collections declare a subject field. `vault.forget()`\n * refuses with `ForgetStrategyNotConfiguredError`; no write hooks register; no\n * collection is forced into `perRecordKeys`. Non-adopters pay nothing.\n */\nexport const NO_FORGET: ForgetStrategy = { subjects: {} }\n\n/**\n * Declare GDPR crypto-shred for one or more collections.\n *\n * Each declared collection is forced to `perRecordKeys: true` (a shred can\n * only guarantee erasure of a record whose body is keyed off a per-record\n * CEK). On write, Noydb extracts `record[subjectField]` and maintains an\n * encrypted `_subject_index` mapping `subject → [{collection, id}]`, so\n * `vault.forget(subjectId)` can find every record for a subject and rewrite\n * each to a tombstone (body + history permanently undecryptable) while the\n * collection DEK and every other record stay intact.\n *\n * @example\n * ```ts\n * createNoydb({\n * secret, user,\n * forgetStrategy: withForgetCascade({ subjects: { invoices: 'buyerId' } }),\n * })\n * const result = await vault.forget('buyer-123')\n * // → { subject, recordsShredded, historyVersionsShredded, collections, … }\n * ```\n */\nexport function withForgetCascade(opts: SubjectDeclaration): ForgetStrategy {\n return { subjects: { ...opts.subjects } }\n}\n\n/**\n * The outcome of a `vault.forget(subjectId)` call.\n *\n * `unmigratedRecords` lists `collection:id` pairs that were tombstoned but\n * whose body had NOT been migrated to a per-record CEK at shred time (legacy\n * body still under the shared collection DEK). Those records are tombstoned\n * (live envelope + history stripped) but their pre-shred ciphertext, if it\n * leaked into a backup before migration, remains decryptable under the\n * collection DEK — so erasure-completeness is NOT guaranteed for them. Run\n * the per-record-CEK migration pass, then re-forget, to close the gap.\n *\n * Blob attachments (#365): a shredded record's **erasable** blobs (on a\n * `perRecordKeys` collection) are crypto-shredded inline — `blobsShredded`\n * counts those taken to refCount 0 (BlobObject deleted → chunks permanently\n * undecryptable), `blobsRetainedShared` counts those still referenced by\n * another record (shared content legitimately persists for its other owner).\n * `blobResidueCollections` now lists only collections with blobs that could\n * NOT be crypto-shredded: **legacy** blobs (no per-blob `_cek`, chunks under\n * the shared `_blob` DEK — migrate them), or a session without the blob\n * subsystem loaded. An all-erasable subject yields an empty residue list.\n */\nexport interface ForgetResult {\n /** The subject id passed to `forget()`. Echoed for caller convenience. */\n readonly subject: string\n /** Count of live records rewritten to a tombstone. */\n readonly recordsShredded: number\n /** Count of `_history` envelopes tombstoned across all shredded records. */\n readonly historyVersionsShredded: number\n /** Distinct collections that had at least one record shredded. */\n readonly collections: readonly string[]\n /** `collection:id` pairs shredded while still un-migrated (see type docs). */\n readonly unmigratedRecords: readonly string[]\n /** Count of erasable blobs crypto-shredded (refCount → 0, BlobObject deleted). */\n readonly blobsShredded: number\n /** Count of erasable blobs retained because still referenced elsewhere (shared). */\n readonly blobsRetainedShared: number\n /** Collections with blobs that could NOT be crypto-shredded — legacy (no `_cek`) or blobs disabled (see type docs). */\n readonly blobResidueCollections: readonly string[]\n /**\n * Count of persisted `_idx/<field>/<recordId>` index side-cars hard-deleted\n * across the shredded records (#401). These live under the retained\n * collection DEK, so crypto-shred alone would leave the indexed field VALUES\n * readable — `forget()` must delete them.\n */\n readonly indexPostingsPurged: number\n /**\n * `collection:id:field` entries whose persisted `_idx` side-car could NOT be\n * deleted (#401) — index residue that still leaks the indexed value under the\n * retained collection DEK. Non-empty means erasure is INCOMPLETE: retry, or\n * purge the side-car out of band.\n */\n readonly indexResidue: readonly string[]\n /** The single `op:'forget'` ledger entry appended for this erasure. */\n readonly ledgerEntry: LedgerEntry\n}\n","/**\n * The encrypted subject index (#304).\n *\n * GDPR crypto-shred needs to answer \"which records belong to data subject\n * X?\" portably (the index must travel with the vault/bundle) WITHOUT leaking\n * subject-equivalence to the store. The rejected alternative — an unencrypted\n * subject tag in envelope metadata — would let anyone with store access see\n * which records share a subject. Instead we keep a reserved `_subject_index`\n * collection, encrypted under its OWN DEK (`getDEK('_subject_index')`):\n *\n * - record id = `sha256Hex(subjectId)` — the raw subject id never appears\n * as a key, so the store can't correlate index entries to a known subject.\n * - record body = AES-GCM(JSON `[{ collection, id }]`) under the index DEK.\n *\n * ## Concurrency (RISK #3 — known v1 limitation)\n *\n * `addSubjectRef` / `removeSubjectRef` are read-modify-write with no CAS. The\n * design assumes a SINGLE WRITER (the noy-db single-process write model). Two\n * concurrent writers racing on the SAME subject can lose an entry (last-write\n * wins on the ref list). This is documented, not fixed in v1:\n * `rebuildSubjectIndex` performs a full scan to recover a correct index from\n * the canonical records, so a lost ref is recoverable. A CAS-backed index is\n * deferred to a later slice.\n *\n * @module\n */\nimport { encrypt, decrypt } from '../crypto.js'\nimport type { NoydbStore, EncryptedEnvelope } from '../types.js'\nimport { NOYDB_FORMAT_VERSION } from '../types.js'\n\n/** Reserved collection holding the encrypted subject → records index. */\nexport const SUBJECT_INDEX_COLLECTION = '_subject_index'\n\n/** A single record reference held in a subject's index entry. */\nexport interface SubjectRef {\n readonly collection: string\n readonly id: string\n}\n\ntype GetDEK = (collectionName: string) => Promise<CryptoKey>\n\n/** SHA-256 hex of a UTF-8 string. The subject-index record key derivation. */\nasync function sha256HexString(input: string): Promise<string> {\n const bytes = new TextEncoder().encode(input)\n const digest = await globalThis.crypto.subtle.digest('SHA-256', bytes)\n return Array.from(new Uint8Array(digest))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('')\n}\n\n/** Stable subject-index record id for a subject id. */\nexport async function subjectKey(subjectId: string): Promise<string> {\n return sha256HexString(subjectId)\n}\n\n/** Read + decrypt the ref list for a subject. Returns `[]` when absent. */\nasync function readRefs(\n adapter: NoydbStore,\n vault: string,\n getDEK: GetDEK,\n encrypted: boolean,\n key: string,\n): Promise<SubjectRef[]> {\n const env = await adapter.get(vault, SUBJECT_INDEX_COLLECTION, key)\n if (!env || !env._data) return []\n if (!encrypted) return JSON.parse(env._data) as SubjectRef[]\n const dek = await getDEK(SUBJECT_INDEX_COLLECTION)\n const json = await decrypt(env._iv, env._data, dek)\n return JSON.parse(json) as SubjectRef[]\n}\n\n/** Encrypt + write a ref list for a subject under its derived key. */\nasync function writeRefs(\n adapter: NoydbStore,\n vault: string,\n getDEK: GetDEK,\n encrypted: boolean,\n key: string,\n refs: SubjectRef[],\n): Promise<void> {\n const json = JSON.stringify(refs)\n let env: EncryptedEnvelope\n if (!encrypted) {\n env = { _noydb: NOYDB_FORMAT_VERSION, _v: 1, _ts: new Date().toISOString(), _iv: '', _data: json }\n } else {\n const dek = await getDEK(SUBJECT_INDEX_COLLECTION)\n const { iv, data } = await encrypt(json, dek)\n env = { _noydb: NOYDB_FORMAT_VERSION, _v: 1, _ts: new Date().toISOString(), _iv: iv, _data: data }\n }\n await adapter.put(vault, SUBJECT_INDEX_COLLECTION, key, env)\n}\n\n/**\n * Add a `{ collection, id }` ref to a subject's index entry (idempotent —\n * a duplicate ref is not appended). Read-modify-write; see the concurrency\n * note in the module docstring.\n */\nexport async function addSubjectRef(\n adapter: NoydbStore,\n vault: string,\n getDEK: GetDEK,\n encrypted: boolean,\n subjectId: string,\n ref: SubjectRef,\n): Promise<void> {\n const key = await subjectKey(subjectId)\n const refs = await readRefs(adapter, vault, getDEK, encrypted, key)\n if (refs.some((r) => r.collection === ref.collection && r.id === ref.id)) return\n refs.push(ref)\n await writeRefs(adapter, vault, getDEK, encrypted, key, refs)\n}\n\n/**\n * Remove a `{ collection, id }` ref from a subject's index entry. When the\n * last ref is removed the (now empty) entry is deleted so the store holds no\n * residual key for an erased subject.\n */\nexport async function removeSubjectRef(\n adapter: NoydbStore,\n vault: string,\n getDEK: GetDEK,\n encrypted: boolean,\n subjectId: string,\n ref: SubjectRef,\n): Promise<void> {\n const key = await subjectKey(subjectId)\n const refs = await readRefs(adapter, vault, getDEK, encrypted, key)\n const next = refs.filter((r) => !(r.collection === ref.collection && r.id === ref.id))\n if (next.length === refs.length) return\n if (next.length === 0) {\n await adapter.delete(vault, SUBJECT_INDEX_COLLECTION, key)\n return\n }\n await writeRefs(adapter, vault, getDEK, encrypted, key, next)\n}\n\n/** Look up every record ref for a subject. Returns `[]` when none exist. */\nexport async function lookupSubject(\n adapter: NoydbStore,\n vault: string,\n getDEK: GetDEK,\n encrypted: boolean,\n subjectId: string,\n): Promise<SubjectRef[]> {\n const key = await subjectKey(subjectId)\n return readRefs(adapter, vault, getDEK, encrypted, key)\n}\n\n/**\n * Rebuild the entire subject index from the canonical records (the recovery\n * path for the documented read-modify-write race). Scans each declared\n * collection, reads `record[subjectField]` (dotted path), and rewrites the\n * index from scratch. Tombstoned (already-shredded) records contribute no\n * ref — their body is gone, so they cannot be re-indexed.\n *\n * `decodeRecord` decrypts an envelope to a plain object (or returns null for\n * a tombstone / unreadable record); supplied by the caller so this module\n * stays free of Collection internals.\n */\nexport async function rebuildSubjectIndex(\n adapter: NoydbStore,\n vault: string,\n getDEK: GetDEK,\n encrypted: boolean,\n subjects: Readonly<Record<string, string>>,\n decodeRecord: (collection: string, id: string, env: EncryptedEnvelope) => Promise<Record<string, unknown> | null>,\n): Promise<number> {\n // Drop every existing index entry first so removed refs don't linger.\n const existing = await adapter.list(vault, SUBJECT_INDEX_COLLECTION)\n for (const k of existing) {\n await adapter.delete(vault, SUBJECT_INDEX_COLLECTION, k)\n }\n\n // subjectId → refs, accumulated across all declared collections.\n const bySubject = new Map<string, SubjectRef[]>()\n for (const [collection, field] of Object.entries(subjects)) {\n const ids = await adapter.list(vault, collection)\n for (const id of ids) {\n if (id.startsWith('_')) continue\n const env = await adapter.get(vault, collection, id)\n if (!env || !env._data) continue // missing or tombstone\n const record = await decodeRecord(collection, id, env)\n if (record === null) continue\n const subjectValue = readDottedPath(record, field)\n if (subjectValue === undefined || subjectValue === null) continue\n const subjectId = coerceSubjectId(subjectValue)\n const list = bySubject.get(subjectId) ?? []\n list.push({ collection, id })\n bySubject.set(subjectId, list)\n }\n }\n\n let entries = 0\n for (const [subjectId, refs] of bySubject) {\n const key = await subjectKey(subjectId)\n await writeRefs(adapter, vault, getDEK, encrypted, key, refs)\n entries++\n }\n return entries\n}\n\n/**\n * Coerce a read subject-field value to a stable string id. Primitives use\n * their natural string form; objects/arrays are JSON-stringified so structural\n * subjects still get a deterministic key (avoids the `[object Object]` trap).\n */\nexport function coerceSubjectId(value: unknown): string {\n if (typeof value === 'string') return value\n if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {\n return String(value)\n }\n return JSON.stringify(value)\n}\n\n/** Read a (possibly dotted) field path from a plain record. */\nexport function readDottedPath(record: Record<string, unknown>, field: string): unknown {\n if (!field.includes('.')) return record[field]\n let cursor: unknown = record\n for (const segment of field.split('.')) {\n if (cursor === null || cursor === undefined) return undefined\n cursor = (cursor as Record<string, unknown>)[segment]\n }\n return cursor\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/util/discriminant.ts"],"sourcesContent":["/**\n * Type-guard helper for narrowing discriminated-union records.\n *\n * `Collection<T>` correctly threads `T` through `get` / `query` / `scan`, but\n * accessing member-specific fields after reading from a `Collection<Union>`\n * still requires a narrowing step in application code. In regular `.ts` files\n * a simple `if (r.kind === 'IV')` block narrows correctly; in vue-tsc /\n * template contexts the inline comparison is sometimes not enough, and the\n * helper below makes the guard portable and avoids `as unknown as …` casts.\n *\n * **Usage with Collection<T>**\n *\n * ```ts\n * import { isDiscriminant } from '@noy-db/hub'\n *\n * type IV = { kind: 'IV'; invoiceNo: string; amount: number }\n * type RE = { kind: 'RE'; receiptNo: string; paidAt: string }\n * type Receipt = IV | RE | ...\n *\n * const receipts = await vault.collection<Receipt>('receipts').query().toArray()\n *\n * // Filter approach — result is IV[], invoiceNo accessible without cast:\n * const ivs = receipts.filter(r => isDiscriminant(r, 'kind', 'IV'))\n * console.log(ivs[0].invoiceNo)\n *\n * // Branch approach:\n * for (const r of receipts) {\n * if (isDiscriminant(r, 'kind', 'IV')) {\n * console.log(r.invoiceNo) // r: IV here\n * }\n * }\n * ```\n *\n * @module\n */\n\n/**\n * Type-guard for narrowing a discriminated-union record by its discriminant.\n *\n * Returns `true` when `record[key] === value`, and narrows `record` to\n * `Extract<T, Record<K, V>>` — the exact union member(s) that carry that\n * discriminant value.\n *\n * The discriminant key is a parameter (not hardcoded to `'kind'`), so this\n * works with any field name: `'kind'`, `'type'`, `'tag'`, etc.\n *\n * @example\n * const ivs = receipts.filter(r => isDiscriminant(r, 'kind', 'IV'))\n * // ivs: IV[] — invoiceNo accessible, no cast needed\n *\n * @typeParam T - The union type of the record (e.g. `Receipt`). Both type-alias\n * unions and interface-extended unions work; use a `type` alias (`type Receipt = IV | RE`) for best\n * results.\n * @typeParam K - The discriminant key (must be a key of `T`).\n * @typeParam V - The discriminant value to match. The `const` modifier ensures\n * TypeScript infers the string literal (e.g. `'IV'`), not the full union\n * `T[K]`, so the predicate resolves to the exact member type.\n */\nexport function isDiscriminant<\n T,\n K extends keyof T,\n const V extends T[K],\n>(record: T, key: K, value: V): record is Extract<T, Record<K, V>> {\n return (record as Record<PropertyKey, unknown>)[key as PropertyKey] === value\n}\n"],"mappings":";AA0DO,SAAS,eAId,QAAW,KAAQ,OAA8C;AACjE,SAAQ,OAAwC,GAAkB,MAAM;AAC1E;","names":[]}
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
OverlayBaseIsVirtualError,
|
|
3
3
|
OverlayCollectionUnavailableError,
|
|
4
4
|
OverlayNameCollisionError
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-JZ5DS4DP.js";
|
|
6
6
|
|
|
7
7
|
// src/overlay-views/registry.ts
|
|
8
8
|
var OverlayedViewRegistry = class {
|
|
@@ -37,6 +37,16 @@ var OverlayedViewRegistry = class {
|
|
|
37
37
|
isOverlay(name) {
|
|
38
38
|
return this._byName.has(name);
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* All registered overlay strategies as a flat array.
|
|
42
|
+
* Each strategy carries `name`, `base`, and `overlay` fields that
|
|
43
|
+
* `describeOverlays()` in the introspection walker reads directly.
|
|
44
|
+
*
|
|
45
|
+
* Used by `dumpSchema()` to populate the `overlayViews` map.
|
|
46
|
+
*/
|
|
47
|
+
all() {
|
|
48
|
+
return [...this._byName.values()];
|
|
49
|
+
}
|
|
40
50
|
/**
|
|
41
51
|
* Resolve the `rowKey` function for an overlay's base MV. Returns
|
|
42
52
|
* `undefined` if the base isn't an MV (raw source collection) or
|
|
@@ -58,4 +68,4 @@ var OverlayedViewRegistry = class {
|
|
|
58
68
|
export {
|
|
59
69
|
OverlayedViewRegistry
|
|
60
70
|
};
|
|
61
|
-
//# sourceMappingURL=chunk-
|
|
71
|
+
//# sourceMappingURL=chunk-TMUMMAWV.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/overlay-views/registry.ts"],"sourcesContent":["import {\n OverlayBaseIsVirtualError,\n OverlayCollectionUnavailableError,\n OverlayNameCollisionError,\n} from '../errors.js'\nimport type { MaterializedViewRegistry } from '../materialized-views/registry.js'\nimport type { OverlayedViewStrategy } from './types.js'\n\n/**\n * Vault-internal registry of overlay strategies. Resolves the base\n * MV's `rowKey` lazily so virtual-collection writes can derive ids\n * from the row.\n *\n * @internal\n */\nexport class OverlayedViewRegistry {\n private readonly _byName = new Map<string, OverlayedViewStrategy>()\n\n /**\n * Register an overlay. Validates name uniqueness, base concreteness,\n * and overlay availability AGAINST the MV registry — overlays\n * declared without the MV registry context skip cross-registry\n * checks but still validate self-consistency.\n */\n register(\n spec: OverlayedViewStrategy,\n options: {\n isOverlayName?: (name: string) => boolean\n isMVOutput?: (name: string) => boolean\n isKnownCollection?: (name: string) => boolean\n },\n ): void {\n const { isOverlayName, isMVOutput, isKnownCollection } = options\n\n // 1. Virtual name must not collide with an MV output or a concrete\n // source collection. Concrete-source detection is best-effort:\n // if `isKnownCollection` is supplied, a hit there + no MV match\n // is treated as a collision.\n if (isMVOutput?.(spec.name) || isOverlayName?.(spec.name)) {\n throw new OverlayNameCollisionError(spec.name)\n }\n // (We don't check isKnownCollection for `name` collision because\n // virtual names are typically NOT pre-existing — they're created\n // by the overlay declaration itself. Future versions may tighten.)\n\n // 2. base must be concrete: NOT another overlay's virtual name.\n if (isOverlayName?.(spec.base)) {\n throw new OverlayBaseIsVirtualError(spec.name, spec.base)\n }\n\n // 3. overlay must be available: a real, vault-known collection\n // that is NOT an MV output (since MVs own their outputs).\n if (isMVOutput?.(spec.overlay)) {\n throw new OverlayCollectionUnavailableError(spec.name, spec.overlay)\n }\n // Best-effort known-collection check — when the vault can answer\n // it. Unknown collections aren't a hard failure (the overlay may\n // be implicitly created on first write), so we only throw on the\n // MV-output case above.\n void isKnownCollection\n\n this._byName.set(spec.name, spec)\n }\n\n byName(name: string): OverlayedViewStrategy | undefined {\n return this._byName.get(name)\n }\n\n /** All overlay virtual names. */\n names(): ReadonlySet<string> {\n return new Set(this._byName.keys())\n }\n\n isOverlay(name: string): boolean {\n return this._byName.has(name)\n }\n\n /**\n * Resolve the `rowKey` function for an overlay's base MV. Returns\n * `undefined` if the base isn't an MV (raw source collection) or\n * if the MV registry isn't supplied. Used by the virtual-collection\n * proxy to derive ids from `put(record)` calls.\n */\n resolveBaseRowKey(\n name: string,\n mvRegistry: MaterializedViewRegistry | null,\n ): ((row: Record<string, unknown>) => string) | undefined {\n const spec = this._byName.get(name)\n if (!spec || !mvRegistry) return undefined\n // The base might be an MV's `output.collection` OR the MV's `name`\n // (when no output.collection is declared). Search by both.\n for (const reg of mvRegistry.all()) {\n if (reg.outputCollection === spec.base || reg.spec.name === spec.base) {\n return (row) => reg.spec.rowKey(row)\n }\n }\n return undefined\n }\n}\n"],"mappings":";;;;;;;AAeO,IAAM,wBAAN,MAA4B;AAAA,EAChB,UAAU,oBAAI,IAAmC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQlE,SACE,MACA,SAKM;AACN,UAAM,EAAE,eAAe,YAAY,kBAAkB,IAAI;AAMzD,QAAI,aAAa,KAAK,IAAI,KAAK,gBAAgB,KAAK,IAAI,GAAG;AACzD,YAAM,IAAI,0BAA0B,KAAK,IAAI;AAAA,IAC/C;AAMA,QAAI,gBAAgB,KAAK,IAAI,GAAG;AAC9B,YAAM,IAAI,0BAA0B,KAAK,MAAM,KAAK,IAAI;AAAA,IAC1D;AAIA,QAAI,aAAa,KAAK,OAAO,GAAG;AAC9B,YAAM,IAAI,kCAAkC,KAAK,MAAM,KAAK,OAAO;AAAA,IACrE;AAKA,SAAK;AAEL,SAAK,QAAQ,IAAI,KAAK,MAAM,IAAI;AAAA,EAClC;AAAA,EAEA,OAAO,MAAiD;AACtD,WAAO,KAAK,QAAQ,IAAI,IAAI;AAAA,EAC9B;AAAA;AAAA,EAGA,QAA6B;AAC3B,WAAO,IAAI,IAAI,KAAK,QAAQ,KAAK,CAAC;AAAA,EACpC;AAAA,EAEA,UAAU,MAAuB;AAC/B,WAAO,KAAK,QAAQ,IAAI,IAAI;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,kBACE,MACA,YACwD;AACxD,UAAM,OAAO,KAAK,QAAQ,IAAI,IAAI;AAClC,QAAI,CAAC,QAAQ,CAAC,WAAY,QAAO;AAGjC,eAAW,OAAO,WAAW,IAAI,GAAG;AAClC,UAAI,IAAI,qBAAqB,KAAK,QAAQ,IAAI,KAAK,SAAS,KAAK,MAAM;AACrE,eAAO,CAAC,QAAQ,IAAI,KAAK,OAAO,GAAG;AAAA,MACrC;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/overlay-views/registry.ts"],"sourcesContent":["import {\n OverlayBaseIsVirtualError,\n OverlayCollectionUnavailableError,\n OverlayNameCollisionError,\n} from '../errors.js'\nimport type { MaterializedViewRegistry } from '../materialized-views/registry.js'\nimport type { OverlayedViewStrategy } from './types.js'\n\n/**\n * Vault-internal registry of overlay strategies. Resolves the base\n * MV's `rowKey` lazily so virtual-collection writes can derive ids\n * from the row.\n *\n * @internal\n */\nexport class OverlayedViewRegistry {\n private readonly _byName = new Map<string, OverlayedViewStrategy>()\n\n /**\n * Register an overlay. Validates name uniqueness, base concreteness,\n * and overlay availability AGAINST the MV registry — overlays\n * declared without the MV registry context skip cross-registry\n * checks but still validate self-consistency.\n */\n register(\n spec: OverlayedViewStrategy,\n options: {\n isOverlayName?: (name: string) => boolean\n isMVOutput?: (name: string) => boolean\n isKnownCollection?: (name: string) => boolean\n },\n ): void {\n const { isOverlayName, isMVOutput, isKnownCollection } = options\n\n // 1. Virtual name must not collide with an MV output or a concrete\n // source collection. Concrete-source detection is best-effort:\n // if `isKnownCollection` is supplied, a hit there + no MV match\n // is treated as a collision.\n if (isMVOutput?.(spec.name) || isOverlayName?.(spec.name)) {\n throw new OverlayNameCollisionError(spec.name)\n }\n // (We don't check isKnownCollection for `name` collision because\n // virtual names are typically NOT pre-existing — they're created\n // by the overlay declaration itself. Future versions may tighten.)\n\n // 2. base must be concrete: NOT another overlay's virtual name.\n if (isOverlayName?.(spec.base)) {\n throw new OverlayBaseIsVirtualError(spec.name, spec.base)\n }\n\n // 3. overlay must be available: a real, vault-known collection\n // that is NOT an MV output (since MVs own their outputs).\n if (isMVOutput?.(spec.overlay)) {\n throw new OverlayCollectionUnavailableError(spec.name, spec.overlay)\n }\n // Best-effort known-collection check — when the vault can answer\n // it. Unknown collections aren't a hard failure (the overlay may\n // be implicitly created on first write), so we only throw on the\n // MV-output case above.\n void isKnownCollection\n\n this._byName.set(spec.name, spec)\n }\n\n byName(name: string): OverlayedViewStrategy | undefined {\n return this._byName.get(name)\n }\n\n /** All overlay virtual names. */\n names(): ReadonlySet<string> {\n return new Set(this._byName.keys())\n }\n\n isOverlay(name: string): boolean {\n return this._byName.has(name)\n }\n\n /**\n * All registered overlay strategies as a flat array.\n * Each strategy carries `name`, `base`, and `overlay` fields that\n * `describeOverlays()` in the introspection walker reads directly.\n *\n * Used by `dumpSchema()` to populate the `overlayViews` map.\n */\n all(): ReadonlyArray<OverlayedViewStrategy> {\n return [...this._byName.values()]\n }\n\n /**\n * Resolve the `rowKey` function for an overlay's base MV. Returns\n * `undefined` if the base isn't an MV (raw source collection) or\n * if the MV registry isn't supplied. Used by the virtual-collection\n * proxy to derive ids from `put(record)` calls.\n */\n resolveBaseRowKey(\n name: string,\n mvRegistry: MaterializedViewRegistry | null,\n ): ((row: Record<string, unknown>) => string) | undefined {\n const spec = this._byName.get(name)\n if (!spec || !mvRegistry) return undefined\n // The base might be an MV's `output.collection` OR the MV's `name`\n // (when no output.collection is declared). Search by both.\n for (const reg of mvRegistry.all()) {\n if (reg.outputCollection === spec.base || reg.spec.name === spec.base) {\n return (row) => reg.spec.rowKey(row)\n }\n }\n return undefined\n }\n}\n"],"mappings":";;;;;;;AAeO,IAAM,wBAAN,MAA4B;AAAA,EAChB,UAAU,oBAAI,IAAmC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQlE,SACE,MACA,SAKM;AACN,UAAM,EAAE,eAAe,YAAY,kBAAkB,IAAI;AAMzD,QAAI,aAAa,KAAK,IAAI,KAAK,gBAAgB,KAAK,IAAI,GAAG;AACzD,YAAM,IAAI,0BAA0B,KAAK,IAAI;AAAA,IAC/C;AAMA,QAAI,gBAAgB,KAAK,IAAI,GAAG;AAC9B,YAAM,IAAI,0BAA0B,KAAK,MAAM,KAAK,IAAI;AAAA,IAC1D;AAIA,QAAI,aAAa,KAAK,OAAO,GAAG;AAC9B,YAAM,IAAI,kCAAkC,KAAK,MAAM,KAAK,OAAO;AAAA,IACrE;AAKA,SAAK;AAEL,SAAK,QAAQ,IAAI,KAAK,MAAM,IAAI;AAAA,EAClC;AAAA,EAEA,OAAO,MAAiD;AACtD,WAAO,KAAK,QAAQ,IAAI,IAAI;AAAA,EAC9B;AAAA;AAAA,EAGA,QAA6B;AAC3B,WAAO,IAAI,IAAI,KAAK,QAAQ,KAAK,CAAC;AAAA,EACpC;AAAA,EAEA,UAAU,MAAuB;AAC/B,WAAO,KAAK,QAAQ,IAAI,IAAI;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAA4C;AAC1C,WAAO,CAAC,GAAG,KAAK,QAAQ,OAAO,CAAC;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,kBACE,MACA,YACwD;AACxD,UAAM,OAAO,KAAK,QAAQ,IAAI,IAAI;AAClC,QAAI,CAAC,QAAQ,CAAC,WAAY,QAAO;AAGjC,eAAW,OAAO,WAAW,IAAI,GAAG;AAClC,UAAI,IAAI,qBAAqB,KAAK,QAAQ,IAAI,KAAK,SAAS,KAAK,MAAM;AACrE,eAAO,CAAC,QAAQ,IAAI,KAAK,OAAO,GAAG;AAAA,MACrC;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACF;","names":[]}
|
|
@@ -3,17 +3,17 @@ import {
|
|
|
3
3
|
} from "./chunk-2QR2PQTT.js";
|
|
4
4
|
import {
|
|
5
5
|
NOYDB_SYNC_VERSION
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-A7DNZVAV.js";
|
|
7
7
|
import {
|
|
8
8
|
bufferToBase64,
|
|
9
9
|
decrypt,
|
|
10
10
|
derivePresenceKey,
|
|
11
11
|
encrypt,
|
|
12
12
|
generateIV
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-E2MDPPOC.js";
|
|
14
14
|
import {
|
|
15
15
|
ConflictError
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-JZ5DS4DP.js";
|
|
17
17
|
|
|
18
18
|
// src/team/presence.ts
|
|
19
19
|
var PresenceHandle = class {
|
|
@@ -719,4 +719,4 @@ export {
|
|
|
719
719
|
SyncEngine,
|
|
720
720
|
SyncTransaction
|
|
721
721
|
};
|
|
722
|
-
//# sourceMappingURL=chunk-
|
|
722
|
+
//# sourceMappingURL=chunk-TZGWI5AX.js.map
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DerivationCycleError
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-JZ5DS4DP.js";
|
|
4
4
|
|
|
5
5
|
// src/derivations/strategy-hash.ts
|
|
6
|
-
async function computeStrategyHash(source, outputKeys, derive) {
|
|
6
|
+
async function computeStrategyHash(source, outputKeys, derive, sources) {
|
|
7
7
|
const canonical = JSON.stringify({
|
|
8
8
|
source,
|
|
9
9
|
outputs: [...outputKeys].sort(),
|
|
10
|
-
derive: derive.toString()
|
|
10
|
+
derive: derive.toString(),
|
|
11
|
+
// Declared sibling sources (#344) — adding/removing a trigger
|
|
12
|
+
// collection invalidates cached derived records. Omitted when empty
|
|
13
|
+
// so strategies without siblings keep their existing hash.
|
|
14
|
+
...sources?.length ? { sources: [...sources].sort() } : {}
|
|
11
15
|
});
|
|
12
16
|
const bytes = new TextEncoder().encode(canonical);
|
|
13
17
|
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
@@ -21,11 +25,26 @@ var DerivationRegistry = class {
|
|
|
21
25
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
26
|
async register(spec) {
|
|
23
27
|
const outputKeys = Object.keys(spec.outputs);
|
|
24
|
-
const strategyHash = await computeStrategyHash(spec.source, outputKeys, spec.derive);
|
|
28
|
+
const strategyHash = await computeStrategyHash(spec.source, outputKeys, spec.derive, spec.sources);
|
|
25
29
|
const reg = { spec, strategyHash };
|
|
26
30
|
const fromSource = this._bySource.get(spec.source);
|
|
27
31
|
if (fromSource) fromSource.push(reg);
|
|
28
32
|
else this._bySource.set(spec.source, [reg]);
|
|
33
|
+
for (const extra of spec.sources ?? []) {
|
|
34
|
+
const fromExtra = this._bySource.get(extra);
|
|
35
|
+
if (fromExtra) fromExtra.push(reg);
|
|
36
|
+
else this._bySource.set(extra, [reg]);
|
|
37
|
+
}
|
|
38
|
+
for (const t of spec.triggerBy ?? []) {
|
|
39
|
+
const fromTrigger = this._bySource.get(t.collection);
|
|
40
|
+
if (fromTrigger) fromTrigger.push(reg);
|
|
41
|
+
else this._bySource.set(t.collection, [reg]);
|
|
42
|
+
}
|
|
43
|
+
if (spec.rollup) {
|
|
44
|
+
const fromRollup = this._bySource.get(spec.rollup.from);
|
|
45
|
+
if (fromRollup) fromRollup.push(reg);
|
|
46
|
+
else this._bySource.set(spec.rollup.from, [reg]);
|
|
47
|
+
}
|
|
29
48
|
for (const key of outputKeys) {
|
|
30
49
|
const output = spec.outputs[key];
|
|
31
50
|
if (!output) continue;
|
|
@@ -41,6 +60,23 @@ var DerivationRegistry = class {
|
|
|
41
60
|
strategiesProducingOutput(collection) {
|
|
42
61
|
return this._byOutput.get(collection) ?? [];
|
|
43
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* All registered strategies as a flat, deduplicated array.
|
|
65
|
+
* Each strategy is indexed once per source (not once per output key),
|
|
66
|
+
* so iterating `_bySource.values()` naturally yields each strategy
|
|
67
|
+
* exactly once per source — deduplication is handled by flattening
|
|
68
|
+
* the per-source arrays and collecting into a Set by identity.
|
|
69
|
+
*
|
|
70
|
+
* Used by `dumpSchema()` / `describeDerivations()` in the introspection
|
|
71
|
+
* walker to populate the derivations map.
|
|
72
|
+
*/
|
|
73
|
+
all() {
|
|
74
|
+
const seen = /* @__PURE__ */ new Set();
|
|
75
|
+
for (const strategies of this._bySource.values()) {
|
|
76
|
+
for (const s of strategies) seen.add(s);
|
|
77
|
+
}
|
|
78
|
+
return [...seen];
|
|
79
|
+
}
|
|
44
80
|
/**
|
|
45
81
|
* Cycle detection over the source → output → … graph. Call after all
|
|
46
82
|
* `register()` calls complete (i.e. at vault open). Throws
|
|
@@ -62,6 +98,9 @@ var DerivationRegistry = class {
|
|
|
62
98
|
for (const key of Object.keys(s.spec.outputs)) {
|
|
63
99
|
const output = s.spec.outputs[key];
|
|
64
100
|
if (!output) continue;
|
|
101
|
+
if (output.shape === "record" && output.collection === s.spec.source && output.denorm !== void 0) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
65
104
|
visit(output.collection);
|
|
66
105
|
}
|
|
67
106
|
}
|
|
@@ -76,4 +115,4 @@ var DerivationRegistry = class {
|
|
|
76
115
|
export {
|
|
77
116
|
DerivationRegistry
|
|
78
117
|
};
|
|
79
|
-
//# sourceMappingURL=chunk-
|
|
118
|
+
//# sourceMappingURL=chunk-UBF5JEOJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/derivations/strategy-hash.ts","../src/derivations/registry.ts"],"sourcesContent":["/**\n * Deterministic hash of a derivation strategy's \"shape\": source\n * collection, declared sibling sources, output keys, derive function\n * source. Used to detect\n * strategy drift: a record whose `_derivedFrom.strategyHash` doesn't\n * match the current strategy is considered stale.\n *\n * Web Crypto SHA-256 — no extra deps.\n */\nexport async function computeStrategyHash(\n source: string,\n outputKeys: readonly string[],\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n derive: (...args: any[]) => any,\n sources?: ReadonlyArray<string>,\n): Promise<string> {\n const canonical = JSON.stringify({\n source,\n outputs: [...outputKeys].sort(),\n derive: derive.toString(),\n // Declared sibling sources (#344) — adding/removing a trigger\n // collection invalidates cached derived records. Omitted when empty\n // so strategies without siblings keep their existing hash.\n ...(sources?.length ? { sources: [...sources].sort() } : {}),\n })\n const bytes = new TextEncoder().encode(canonical)\n const digest = await crypto.subtle.digest('SHA-256', bytes)\n return Array.from(new Uint8Array(digest))\n .map(b => b.toString(16).padStart(2, '0'))\n .join('')\n}\n","import { DerivationCycleError } from '../errors.js'\nimport { computeStrategyHash } from './strategy-hash.js'\nimport type { DerivationStrategy } from './types.js'\n\ninterface RegisteredStrategy {\n // Type-erased to allow the registry to hold heterogeneous strategies.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n spec: DerivationStrategy<any, any>\n strategyHash: string\n}\n\n/**\n * Vault-internal registry of derivation strategies. Owned by `Vault`;\n * not exported.\n *\n * @internal\n */\nexport class DerivationRegistry {\n private readonly _bySource = new Map<string, RegisteredStrategy[]>()\n private readonly _byOutput = new Map<string, RegisteredStrategy[]>()\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async register(spec: DerivationStrategy<any, any>): Promise<void> {\n const outputKeys = Object.keys(spec.outputs)\n const strategyHash = await computeStrategyHash(spec.source, outputKeys, spec.derive, spec.sources)\n const reg: RegisteredStrategy = { spec, strategyHash }\n\n const fromSource = this._bySource.get(spec.source)\n if (fromSource) fromSource.push(reg)\n else this._bySource.set(spec.source, [reg])\n\n // Declared sibling sources (#344) index the SAME `reg` under each\n // extra collection so `strategiesForSource(extra)` returns it and a\n // sibling write re-fires the derivation. Sibling keys also enter\n // `_bySource`, so `validate()`'s cycle DFS walks them automatically.\n for (const extra of spec.sources ?? []) {\n const fromExtra = this._bySource.get(extra)\n if (fromExtra) fromExtra.push(reg)\n else this._bySource.set(extra, [reg])\n }\n\n // FK triggers (#376) index the SAME `reg` under each parent collection so\n // a parent write re-fires the derivation (fanned out to matching source\n // records in `dispatchDerivations`). Like sources[], these keys enter\n // `_bySource` so the cycle DFS walks the trigger→output edge.\n for (const t of spec.triggerBy ?? []) {\n const fromTrigger = this._bySource.get(t.collection)\n if (fromTrigger) fromTrigger.push(reg)\n else this._bySource.set(t.collection, [reg])\n }\n\n // Rollup (#376 slice 2): a write to the child `from` collection recomputes\n // the parent (= spec.source = into) at id child[key]. Index under `from`\n // so a child write fires it; spec.source (into) is already indexed above,\n // so a parent write also recomputes its own aggregate (covers the\n // parent-created-after-children case). The from→into edge enters the cycle\n // DFS automatically.\n if (spec.rollup) {\n const fromRollup = this._bySource.get(spec.rollup.from)\n if (fromRollup) fromRollup.push(reg)\n else this._bySource.set(spec.rollup.from, [reg])\n }\n\n for (const key of outputKeys) {\n const output = spec.outputs[key]\n if (!output) continue\n const outputCollection = output.collection\n const arr = this._byOutput.get(outputCollection)\n if (arr) arr.push(reg)\n else this._byOutput.set(outputCollection, [reg])\n }\n }\n\n strategiesForSource(source: string): ReadonlyArray<RegisteredStrategy> {\n return this._bySource.get(source) ?? []\n }\n\n strategiesProducingOutput(collection: string): ReadonlyArray<RegisteredStrategy> {\n return this._byOutput.get(collection) ?? []\n }\n\n /**\n * All registered strategies as a flat, deduplicated array.\n * Each strategy is indexed once per source (not once per output key),\n * so iterating `_bySource.values()` naturally yields each strategy\n * exactly once per source — deduplication is handled by flattening\n * the per-source arrays and collecting into a Set by identity.\n *\n * Used by `dumpSchema()` / `describeDerivations()` in the introspection\n * walker to populate the derivations map.\n */\n all(): ReadonlyArray<RegisteredStrategy> {\n const seen = new Set<RegisteredStrategy>()\n for (const strategies of this._bySource.values()) {\n for (const s of strategies) seen.add(s)\n }\n return [...seen]\n }\n\n /**\n * Cycle detection over the source → output → … graph. Call after all\n * `register()` calls complete (i.e. at vault open). Throws\n * `DerivationCycleError` on the first cycle found.\n */\n validate(): void {\n const visited = new Set<string>()\n const stack: string[] = []\n\n const visit = (node: string): void => {\n if (stack.includes(node)) {\n const cycle = stack.slice(stack.indexOf(node)).concat(node)\n throw new DerivationCycleError(cycle)\n }\n if (visited.has(node)) return\n stack.push(node)\n const strategies = this._bySource.get(node)\n if (strategies) {\n for (const s of strategies) {\n for (const key of Object.keys(s.spec.outputs)) {\n const output = s.spec.outputs[key]\n if (!output) continue\n // Self-write reverse-denorm (#376): an output back to its own\n // source is intentional, not an infinite cycle — the value-equality\n // guard in dispatch terminates it. Skip this edge so it isn't\n // flagged. (Self-write outputs are required to declare `denorm`.)\n if (\n output.shape === 'record' &&\n output.collection === s.spec.source &&\n output.denorm !== undefined\n ) {\n continue\n }\n visit(output.collection)\n }\n }\n }\n stack.pop()\n visited.add(node)\n }\n\n for (const src of this._bySource.keys()) visit(src)\n }\n}\n"],"mappings":"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","names":[]}
|