@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,225 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
Aggregation: () => Aggregation,
|
|
24
|
-
CollectionIndexes: () => CollectionIndexes,
|
|
25
|
-
DEFAULT_JOIN_MAX_ROWS: () => DEFAULT_JOIN_MAX_ROWS,
|
|
26
|
-
DanglingReferenceError: () => DanglingReferenceError,
|
|
27
|
-
GROUPBY_MAX_CARDINALITY: () => GROUPBY_MAX_CARDINALITY,
|
|
28
|
-
GROUPBY_WARN_CARDINALITY: () => GROUPBY_WARN_CARDINALITY,
|
|
29
|
-
GroupCardinalityError: () => GroupCardinalityError,
|
|
30
|
-
GroupedAggregation: () => GroupedAggregation,
|
|
31
|
-
GroupedQuery: () => GroupedQuery,
|
|
32
|
-
GroupedQueryN: () => GroupedQueryN,
|
|
33
|
-
IndexRequiredError: () => IndexRequiredError,
|
|
34
|
-
IndexWriteFailureError: () => IndexWriteFailureError,
|
|
35
|
-
JoinTooLargeError: () => JoinTooLargeError,
|
|
36
|
-
Query: () => Query,
|
|
37
|
-
ScanBuilder: () => ScanBuilder,
|
|
38
|
-
applyJoins: () => applyJoins,
|
|
39
|
-
avg: () => avg,
|
|
40
|
-
buildLiveAggregation: () => buildLiveAggregation,
|
|
41
|
-
buildLiveQuery: () => buildLiveQuery,
|
|
42
|
-
count: () => count,
|
|
43
|
-
evaluateClause: () => evaluateClause,
|
|
44
|
-
evaluateFieldClause: () => evaluateFieldClause,
|
|
45
|
-
executePlan: () => executePlan,
|
|
46
|
-
groupAndReduce: () => groupAndReduce,
|
|
47
|
-
max: () => max,
|
|
48
|
-
min: () => min,
|
|
49
|
-
readPath: () => readPath,
|
|
50
|
-
reduceRecords: () => reduceRecords,
|
|
51
|
-
resetGroupByWarnings: () => resetGroupByWarnings,
|
|
52
|
-
resetJoinWarnings: () => resetJoinWarnings,
|
|
53
|
-
sum: () => sum
|
|
54
|
-
});
|
|
55
|
-
module.exports = __toCommonJS(query_exports);
|
|
56
|
-
|
|
57
|
-
// src/query/predicate.ts
|
|
58
|
-
function readPath(record, path) {
|
|
59
|
-
if (record === null || record === void 0) return void 0;
|
|
60
|
-
if (!path.includes(".")) {
|
|
61
|
-
return record[path];
|
|
62
|
-
}
|
|
63
|
-
const segments = path.split(".");
|
|
64
|
-
let cursor = record;
|
|
65
|
-
for (const segment of segments) {
|
|
66
|
-
if (cursor === null || cursor === void 0) return void 0;
|
|
67
|
-
cursor = cursor[segment];
|
|
68
|
-
}
|
|
69
|
-
return cursor;
|
|
70
|
-
}
|
|
71
|
-
function evaluateFieldClause(record, clause) {
|
|
72
|
-
const actual = readPath(record, clause.field);
|
|
73
|
-
const { op, value } = clause;
|
|
74
|
-
switch (op) {
|
|
75
|
-
case "==":
|
|
76
|
-
return actual === value;
|
|
77
|
-
case "!=":
|
|
78
|
-
return actual !== value;
|
|
79
|
-
case "<":
|
|
80
|
-
return isComparable(actual, value) && actual < value;
|
|
81
|
-
case "<=":
|
|
82
|
-
return isComparable(actual, value) && actual <= value;
|
|
83
|
-
case ">":
|
|
84
|
-
return isComparable(actual, value) && actual > value;
|
|
85
|
-
case ">=":
|
|
86
|
-
return isComparable(actual, value) && actual >= value;
|
|
87
|
-
case "in":
|
|
88
|
-
return Array.isArray(value) && value.includes(actual);
|
|
89
|
-
case "contains":
|
|
90
|
-
if (typeof actual === "string") return typeof value === "string" && actual.includes(value);
|
|
91
|
-
if (Array.isArray(actual)) return actual.includes(value);
|
|
92
|
-
return false;
|
|
93
|
-
case "startsWith":
|
|
94
|
-
return typeof actual === "string" && typeof value === "string" && actual.startsWith(value);
|
|
95
|
-
case "between": {
|
|
96
|
-
if (!Array.isArray(value) || value.length !== 2) return false;
|
|
97
|
-
const [lo, hi] = value;
|
|
98
|
-
if (!isComparable(actual, lo) || !isComparable(actual, hi)) return false;
|
|
99
|
-
return actual >= lo && actual <= hi;
|
|
100
|
-
}
|
|
101
|
-
default: {
|
|
102
|
-
const _exhaustive = op;
|
|
103
|
-
void _exhaustive;
|
|
104
|
-
return false;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
function isComparable(a, b) {
|
|
109
|
-
if (typeof a === "number" && typeof b === "number") return true;
|
|
110
|
-
if (typeof a === "string" && typeof b === "string") return true;
|
|
111
|
-
if (a instanceof Date && b instanceof Date) return true;
|
|
112
|
-
return false;
|
|
113
|
-
}
|
|
114
|
-
function evaluateClause(record, clause) {
|
|
115
|
-
switch (clause.type) {
|
|
116
|
-
case "field":
|
|
117
|
-
return evaluateFieldClause(record, clause);
|
|
118
|
-
case "filter":
|
|
119
|
-
return clause.fn(record);
|
|
120
|
-
case "wherePredicate":
|
|
121
|
-
return clause.fn(record, clause.ctx);
|
|
122
|
-
case "group":
|
|
123
|
-
if (clause.op === "and") {
|
|
124
|
-
for (const child of clause.clauses) {
|
|
125
|
-
if (!evaluateClause(record, child)) return false;
|
|
126
|
-
}
|
|
127
|
-
return true;
|
|
128
|
-
} else {
|
|
129
|
-
for (const child of clause.clauses) {
|
|
130
|
-
if (evaluateClause(record, child)) return true;
|
|
131
|
-
}
|
|
132
|
-
return false;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// src/errors.ts
|
|
138
|
-
var NoydbError = class extends Error {
|
|
139
|
-
/** Machine-readable error code. Stable across library versions. */
|
|
140
|
-
code;
|
|
141
|
-
constructor(code, message) {
|
|
142
|
-
super(message);
|
|
143
|
-
this.name = "NoydbError";
|
|
144
|
-
this.code = code;
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
|
-
var GroupCardinalityError = class extends NoydbError {
|
|
148
|
-
/** The field being grouped on. */
|
|
149
|
-
field;
|
|
150
|
-
/** Observed number of distinct groups at the moment the cap tripped. */
|
|
151
|
-
cardinality;
|
|
152
|
-
/** The cap that was exceeded. */
|
|
153
|
-
maxGroups;
|
|
154
|
-
constructor(field, cardinality, maxGroups) {
|
|
155
|
-
super(
|
|
156
|
-
"GROUP_CARDINALITY",
|
|
157
|
-
`.groupBy("${field}") produced ${cardinality} distinct groups, exceeding the ${maxGroups}-group ceiling. This is almost always a query mistake \u2014 grouping on a high-uniqueness field like "id" or "createdAt" produces one bucket per record. Narrow the query with .where() before grouping, or group on a lower-cardinality field (status, category, clientId). If you genuinely need high-cardinality grouping, file an issue with your use case.`
|
|
158
|
-
);
|
|
159
|
-
this.name = "GroupCardinalityError";
|
|
160
|
-
this.field = field;
|
|
161
|
-
this.cardinality = cardinality;
|
|
162
|
-
this.maxGroups = maxGroups;
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
var IndexRequiredError = class extends NoydbError {
|
|
166
|
-
collection;
|
|
167
|
-
touchedFields;
|
|
168
|
-
missingFields;
|
|
169
|
-
constructor(args) {
|
|
170
|
-
super(
|
|
171
|
-
"INDEX_REQUIRED",
|
|
172
|
-
`Collection "${args.collection}": query references unindexed fields in lazy mode (missing: ${args.missingFields.join(", ")}). Declare an index on each field, or use collection.scan() for non-indexed iteration.`
|
|
173
|
-
);
|
|
174
|
-
this.name = "IndexRequiredError";
|
|
175
|
-
this.collection = args.collection;
|
|
176
|
-
this.touchedFields = [...args.touchedFields];
|
|
177
|
-
this.missingFields = [...args.missingFields];
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
var IndexWriteFailureError = class extends NoydbError {
|
|
181
|
-
recordId;
|
|
182
|
-
field;
|
|
183
|
-
op;
|
|
184
|
-
cause;
|
|
185
|
-
constructor(args) {
|
|
186
|
-
super(
|
|
187
|
-
"INDEX_WRITE_FAILURE",
|
|
188
|
-
`Index side-car ${args.op} failed for field "${args.field}" on record "${args.recordId}"`
|
|
189
|
-
);
|
|
190
|
-
this.name = "IndexWriteFailureError";
|
|
191
|
-
this.recordId = args.recordId;
|
|
192
|
-
this.field = args.field;
|
|
193
|
-
this.op = args.op;
|
|
194
|
-
this.cause = args.cause;
|
|
195
|
-
}
|
|
196
|
-
};
|
|
197
|
-
var JoinTooLargeError = class extends NoydbError {
|
|
198
|
-
leftRows;
|
|
199
|
-
rightRows;
|
|
200
|
-
maxRows;
|
|
201
|
-
side;
|
|
202
|
-
constructor(opts) {
|
|
203
|
-
super("JOIN_TOO_LARGE", opts.message);
|
|
204
|
-
this.name = "JoinTooLargeError";
|
|
205
|
-
this.leftRows = opts.leftRows;
|
|
206
|
-
this.rightRows = opts.rightRows;
|
|
207
|
-
this.maxRows = opts.maxRows;
|
|
208
|
-
this.side = opts.side;
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
var DanglingReferenceError = class extends NoydbError {
|
|
212
|
-
field;
|
|
213
|
-
target;
|
|
214
|
-
refId;
|
|
215
|
-
constructor(opts) {
|
|
216
|
-
super("DANGLING_REFERENCE", opts.message);
|
|
217
|
-
this.name = "DanglingReferenceError";
|
|
218
|
-
this.field = opts.field;
|
|
219
|
-
this.target = opts.target;
|
|
220
|
-
this.refId = opts.refId;
|
|
221
|
-
}
|
|
222
|
-
};
|
|
1
|
+
import {
|
|
2
|
+
wrapMoneyReducers
|
|
3
|
+
} from "./chunk-K6NVSHAH.js";
|
|
4
|
+
import {
|
|
5
|
+
MoneyPrecisionError,
|
|
6
|
+
evaluateClause,
|
|
7
|
+
formatScaledInt,
|
|
8
|
+
hasFnClause,
|
|
9
|
+
moneyFieldClause,
|
|
10
|
+
parseToScaledInt,
|
|
11
|
+
readPath
|
|
12
|
+
} from "./chunk-KJ3K5VZM.js";
|
|
13
|
+
import {
|
|
14
|
+
applyI18nLocale
|
|
15
|
+
} from "./chunk-QXWJQ7UU.js";
|
|
16
|
+
import {
|
|
17
|
+
CrossJoinSourceUnknownError,
|
|
18
|
+
CrossJoinTooLargeError,
|
|
19
|
+
DanglingReferenceError,
|
|
20
|
+
JoinTooLargeError,
|
|
21
|
+
ValidationError
|
|
22
|
+
} from "./chunk-JZ5DS4DP.js";
|
|
223
23
|
|
|
224
24
|
// src/query/join.ts
|
|
225
25
|
var DEFAULT_JOIN_MAX_ROWS = 5e4;
|
|
@@ -249,15 +49,15 @@ function warnCeilingApproaching(target, side, rows, maxRows) {
|
|
|
249
49
|
`[noy-db] .join() ${side} side is at ${pct}% of the ${maxRows}-row ceiling for target "${target}" (${rows} rows). Streaming joins over scan() are not yet supported for collections that need to exceed this.`
|
|
250
50
|
);
|
|
251
51
|
}
|
|
252
|
-
function applyJoins(rows, joins, context) {
|
|
52
|
+
function applyJoins(rows, joins, context, locale) {
|
|
253
53
|
if (joins.length === 0) return [...rows];
|
|
254
54
|
let result = [...rows];
|
|
255
55
|
for (const leg of joins) {
|
|
256
|
-
result = applyOneJoin(result, leg, context);
|
|
56
|
+
result = applyOneJoin(result, leg, context, locale);
|
|
257
57
|
}
|
|
258
58
|
return result;
|
|
259
59
|
}
|
|
260
|
-
function applyOneJoin(leftRows, leg, context) {
|
|
60
|
+
function applyOneJoin(leftRows, leg, context, locale) {
|
|
261
61
|
if (leg.isDictJoin) {
|
|
262
62
|
const dictSource = context.resolveDictSource?.(leg.field);
|
|
263
63
|
if (!dictSource) {
|
|
@@ -312,24 +112,27 @@ function applyOneJoin(leftRows, leg, context) {
|
|
|
312
112
|
if (rightSnapshot.length > maxRows * JOIN_WARN_FRACTION) {
|
|
313
113
|
warnCeilingApproaching(leg.target, "right", rightSnapshot.length, maxRows);
|
|
314
114
|
}
|
|
115
|
+
const effLocale = locale ?? context.defaultLocale;
|
|
116
|
+
const i18nResolve = effLocale !== void 0 && source.i18nFields !== void 0 ? (right) => right !== null && typeof right === "object" ? applyI18nLocale(right, source.i18nFields, effLocale, void 0, "join") : right : void 0;
|
|
315
117
|
const strategy = leg.strategy ?? (source.lookupById ? "nested" : "hash");
|
|
316
118
|
if (strategy === "nested" && source.lookupById) {
|
|
317
119
|
const lookup = (id) => source.lookupById?.(id);
|
|
318
|
-
return nestedLoopJoin(leftRows, leg, lookup);
|
|
120
|
+
return nestedLoopJoin(leftRows, leg, lookup, i18nResolve);
|
|
319
121
|
}
|
|
320
|
-
return hashJoin(leftRows, leg, rightSnapshot);
|
|
122
|
+
return hashJoin(leftRows, leg, rightSnapshot, i18nResolve);
|
|
321
123
|
}
|
|
322
|
-
function nestedLoopJoin(leftRows, leg, lookupById) {
|
|
124
|
+
function nestedLoopJoin(leftRows, leg, lookupById, i18nResolve) {
|
|
323
125
|
const out = [];
|
|
324
126
|
for (const left of leftRows) {
|
|
325
127
|
const rawId = readPath(left, leg.field);
|
|
326
128
|
const key = coerceRefKey(rawId);
|
|
327
|
-
|
|
129
|
+
let right = key === null ? void 0 : lookupById(key);
|
|
130
|
+
if (i18nResolve && right !== void 0) right = i18nResolve(right);
|
|
328
131
|
out.push(attachJoin(left, leg, right, rawId));
|
|
329
132
|
}
|
|
330
133
|
return out;
|
|
331
134
|
}
|
|
332
|
-
function hashJoin(leftRows, leg, rightSnapshot) {
|
|
135
|
+
function hashJoin(leftRows, leg, rightSnapshot, i18nResolve) {
|
|
333
136
|
const rightMap = /* @__PURE__ */ new Map();
|
|
334
137
|
for (const record of rightSnapshot) {
|
|
335
138
|
const rawId = readPath(record, "id");
|
|
@@ -342,7 +145,8 @@ function hashJoin(leftRows, leg, rightSnapshot) {
|
|
|
342
145
|
for (const left of leftRows) {
|
|
343
146
|
const rawId = readPath(left, leg.field);
|
|
344
147
|
const key = coerceRefKey(rawId);
|
|
345
|
-
|
|
148
|
+
let right = key === null ? void 0 : rightMap.get(key);
|
|
149
|
+
if (i18nResolve && right !== void 0) right = i18nResolve(right);
|
|
346
150
|
out.push(attachJoin(left, leg, right, rawId));
|
|
347
151
|
}
|
|
348
152
|
return out;
|
|
@@ -466,6 +270,277 @@ var NO_AGGREGATE = {
|
|
|
466
270
|
}
|
|
467
271
|
};
|
|
468
272
|
|
|
273
|
+
// src/money/paths.ts
|
|
274
|
+
var SEGMENT_RE = /^(\*|[^.[\]*]+)(\[\])?$/;
|
|
275
|
+
var parseCache = /* @__PURE__ */ new Map();
|
|
276
|
+
function parseMoneyPath(path) {
|
|
277
|
+
const cached = parseCache.get(path);
|
|
278
|
+
if (cached) return cached;
|
|
279
|
+
if (typeof path !== "string" || path.length === 0) {
|
|
280
|
+
throw new ValidationError("moneyFields: path must be a non-empty string");
|
|
281
|
+
}
|
|
282
|
+
const segments = [];
|
|
283
|
+
for (const part of path.split(".")) {
|
|
284
|
+
const m = SEGMENT_RE.exec(part);
|
|
285
|
+
if (!m) {
|
|
286
|
+
throw new ValidationError(
|
|
287
|
+
`moneyFields: invalid path "${path}" \u2014 segment "${part}" must be a key, "key[]", "*", or "*[]"`
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
const array = m[2] === "[]";
|
|
291
|
+
segments.push(
|
|
292
|
+
m[1] === "*" ? { kind: "wildcard", array } : { kind: "key", key: m[1], array }
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
parseCache.set(path, segments);
|
|
296
|
+
return segments;
|
|
297
|
+
}
|
|
298
|
+
function isSimpleMoneyPath(path) {
|
|
299
|
+
return !path.includes(".") && !path.includes("[") && !path.includes("*");
|
|
300
|
+
}
|
|
301
|
+
function validateMoneyFieldPaths(moneyFields) {
|
|
302
|
+
for (const path of Object.keys(moneyFields)) parseMoneyPath(path);
|
|
303
|
+
}
|
|
304
|
+
function transformAtMoneyPath(node, path, segments, index, visit, lenient) {
|
|
305
|
+
if (node === null || node === void 0) return node;
|
|
306
|
+
const seg = segments[index];
|
|
307
|
+
const last = index === segments.length - 1;
|
|
308
|
+
if (seg.kind === "key") {
|
|
309
|
+
if (typeof node !== "object" || Array.isArray(node)) {
|
|
310
|
+
if (lenient) return node;
|
|
311
|
+
throw new ValidationError(
|
|
312
|
+
`moneyFields: path "${path}" expected an object at segment "${seg.key}", got ${Array.isArray(node) ? "an array" : typeof node}`
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
const obj2 = node;
|
|
316
|
+
if (!(seg.key in obj2) || obj2[seg.key] === null || obj2[seg.key] === void 0) return node;
|
|
317
|
+
if (seg.array) {
|
|
318
|
+
const arr = obj2[seg.key];
|
|
319
|
+
if (!Array.isArray(arr)) {
|
|
320
|
+
if (lenient) return node;
|
|
321
|
+
throw new ValidationError(
|
|
322
|
+
`moneyFields: path "${path}" declares "${seg.key}[]" but the value is not an array`
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
const cloned = [...arr];
|
|
326
|
+
if (last) {
|
|
327
|
+
for (let i = 0; i < cloned.length; i++) visit(cloned, i);
|
|
328
|
+
} else {
|
|
329
|
+
for (let i = 0; i < cloned.length; i++) {
|
|
330
|
+
cloned[i] = transformAtMoneyPath(cloned[i], path, segments, index + 1, visit, lenient);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return { ...obj2, [seg.key]: cloned };
|
|
334
|
+
}
|
|
335
|
+
const clone2 = { ...obj2 };
|
|
336
|
+
if (last) {
|
|
337
|
+
visit(clone2, seg.key);
|
|
338
|
+
} else {
|
|
339
|
+
clone2[seg.key] = transformAtMoneyPath(clone2[seg.key], path, segments, index + 1, visit, lenient);
|
|
340
|
+
}
|
|
341
|
+
return clone2;
|
|
342
|
+
}
|
|
343
|
+
if (seg.array) {
|
|
344
|
+
if (!Array.isArray(node)) {
|
|
345
|
+
if (lenient) return node;
|
|
346
|
+
throw new ValidationError(`moneyFields: path "${path}" declares "*[]" but the value is not an array`);
|
|
347
|
+
}
|
|
348
|
+
const cloned = [...node];
|
|
349
|
+
if (last) {
|
|
350
|
+
for (let i = 0; i < cloned.length; i++) visit(cloned, i);
|
|
351
|
+
} else {
|
|
352
|
+
for (let i = 0; i < cloned.length; i++) {
|
|
353
|
+
cloned[i] = transformAtMoneyPath(cloned[i], path, segments, index + 1, visit, lenient);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return cloned;
|
|
357
|
+
}
|
|
358
|
+
if (typeof node !== "object" || Array.isArray(node)) {
|
|
359
|
+
if (lenient) return node;
|
|
360
|
+
throw new ValidationError(
|
|
361
|
+
`moneyFields: path "${path}" applies "*" to a non-object (${Array.isArray(node) ? 'array \u2014 use "*[]"' : typeof node})`
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
const obj = node;
|
|
365
|
+
const clone = { ...obj };
|
|
366
|
+
for (const key of Object.keys(obj)) {
|
|
367
|
+
const v = clone[key];
|
|
368
|
+
if (v === null || v === void 0) continue;
|
|
369
|
+
if (last) visit(clone, key);
|
|
370
|
+
else clone[key] = transformAtMoneyPath(v, path, segments, index + 1, visit, lenient);
|
|
371
|
+
}
|
|
372
|
+
return clone;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// src/money/normalize.ts
|
|
376
|
+
function isMoneyValueObject(v) {
|
|
377
|
+
return typeof v === "object" && v !== null && "currency" in v;
|
|
378
|
+
}
|
|
379
|
+
function quantizeAmount(field, input, scale, rounding) {
|
|
380
|
+
const r = parseToScaledInt(input, scale, rounding);
|
|
381
|
+
if (!r.ok) {
|
|
382
|
+
if (r.reason === "precision") throw new MoneyPrecisionError(field, input, scale);
|
|
383
|
+
throw new TypeError(`money: field "${field}" value ${JSON.stringify(input)} is not a finite decimal`);
|
|
384
|
+
}
|
|
385
|
+
return r.value.toString();
|
|
386
|
+
}
|
|
387
|
+
function canonicalizeStoredMoney(record, moneyFields) {
|
|
388
|
+
if (record === null || record === void 0) return record;
|
|
389
|
+
if (!moneyFields || Object.keys(moneyFields).length === 0) return record;
|
|
390
|
+
return decodeMoneyFields(record, moneyFields, "raw");
|
|
391
|
+
}
|
|
392
|
+
function canonicalizeIncomingMoney(record, moneyFields) {
|
|
393
|
+
if (!moneyFields || Object.keys(moneyFields).length === 0) return record;
|
|
394
|
+
try {
|
|
395
|
+
return decodeMoneyFields(
|
|
396
|
+
quantizeMoneyFields(record, moneyFields),
|
|
397
|
+
moneyFields,
|
|
398
|
+
"raw"
|
|
399
|
+
);
|
|
400
|
+
} catch {
|
|
401
|
+
return record;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function quantizeValue(field, raw, desc) {
|
|
405
|
+
if (desc.mode === "fixed") {
|
|
406
|
+
const currency2 = desc.fixedCurrency;
|
|
407
|
+
return quantizeAmount(field, raw, desc.scaleFor(currency2), desc.rounding);
|
|
408
|
+
}
|
|
409
|
+
let amount;
|
|
410
|
+
let currency;
|
|
411
|
+
if (isMoneyValueObject(raw)) {
|
|
412
|
+
currency = String(raw.currency);
|
|
413
|
+
amount = raw.amount;
|
|
414
|
+
} else {
|
|
415
|
+
const sole = desc.soleCurrency();
|
|
416
|
+
if (sole === void 0) {
|
|
417
|
+
throw new TypeError(
|
|
418
|
+
`money: field "${field}" is multi-currency \u2014 write { amount, currency }, not a bare amount`
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
currency = sole;
|
|
422
|
+
amount = raw;
|
|
423
|
+
}
|
|
424
|
+
const scale = desc.scaleFor(currency);
|
|
425
|
+
return { amount: quantizeAmount(field, amount, scale, desc.rounding), currency };
|
|
426
|
+
}
|
|
427
|
+
function quantizeMoneyFields(record, moneyFields) {
|
|
428
|
+
let out = { ...record };
|
|
429
|
+
for (const [path, desc] of Object.entries(moneyFields)) {
|
|
430
|
+
if (isSimpleMoneyPath(path)) {
|
|
431
|
+
const raw = out[path];
|
|
432
|
+
if (raw === null || raw === void 0) continue;
|
|
433
|
+
out[path] = quantizeValue(path, raw, desc);
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
out = transformAtMoneyPath(
|
|
437
|
+
out,
|
|
438
|
+
path,
|
|
439
|
+
parseMoneyPath(path),
|
|
440
|
+
0,
|
|
441
|
+
(container, key) => {
|
|
442
|
+
const raw = container[key];
|
|
443
|
+
if (raw === null || raw === void 0) return;
|
|
444
|
+
container[key] = quantizeValue(path, raw, desc);
|
|
445
|
+
},
|
|
446
|
+
/* lenient */
|
|
447
|
+
false
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
return out;
|
|
451
|
+
}
|
|
452
|
+
function formatCurrency(decimal, currency, scale, locale) {
|
|
453
|
+
const fmt = new Intl.NumberFormat(locale, {
|
|
454
|
+
style: "currency",
|
|
455
|
+
currency,
|
|
456
|
+
minimumFractionDigits: scale,
|
|
457
|
+
maximumFractionDigits: scale
|
|
458
|
+
});
|
|
459
|
+
return fmt.format(decimal);
|
|
460
|
+
}
|
|
461
|
+
function moneyScaledValue(stored, desc) {
|
|
462
|
+
let raw;
|
|
463
|
+
if (desc.mode === "fixed") {
|
|
464
|
+
raw = stored;
|
|
465
|
+
} else {
|
|
466
|
+
if (!isMoneyValueObject(stored)) return null;
|
|
467
|
+
raw = stored.amount;
|
|
468
|
+
}
|
|
469
|
+
if (typeof raw !== "string" && typeof raw !== "number") return null;
|
|
470
|
+
try {
|
|
471
|
+
return BigInt(String(raw));
|
|
472
|
+
} catch {
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
function decodeValue(stored, desc) {
|
|
477
|
+
let currency;
|
|
478
|
+
let scaledIntString;
|
|
479
|
+
if (desc.mode === "fixed") {
|
|
480
|
+
if (typeof stored !== "string" && typeof stored !== "number") return null;
|
|
481
|
+
currency = desc.fixedCurrency;
|
|
482
|
+
scaledIntString = String(stored);
|
|
483
|
+
} else {
|
|
484
|
+
if (!isMoneyValueObject(stored)) return null;
|
|
485
|
+
const amount = stored.amount;
|
|
486
|
+
if (typeof stored.currency !== "string" || typeof amount !== "string" && typeof amount !== "number") return null;
|
|
487
|
+
currency = stored.currency;
|
|
488
|
+
scaledIntString = String(amount);
|
|
489
|
+
}
|
|
490
|
+
const scale = desc.scaleFor(currency);
|
|
491
|
+
let decimal;
|
|
492
|
+
try {
|
|
493
|
+
decimal = formatScaledInt(BigInt(scaledIntString), scale);
|
|
494
|
+
} catch {
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
return {
|
|
498
|
+
decoded: desc.mode === "fixed" ? decimal : { amount: decimal, currency },
|
|
499
|
+
decimal,
|
|
500
|
+
currency,
|
|
501
|
+
scale
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
function decodeMoneyFields(record, moneyFields, locale) {
|
|
505
|
+
let out = { ...record };
|
|
506
|
+
const format = locale !== "raw";
|
|
507
|
+
const fmtLocale = typeof locale === "string" && locale !== "raw" ? locale : "en-US";
|
|
508
|
+
for (const [path, desc] of Object.entries(moneyFields)) {
|
|
509
|
+
if (isSimpleMoneyPath(path)) {
|
|
510
|
+
const stored = out[path];
|
|
511
|
+
if (stored === null || stored === void 0) continue;
|
|
512
|
+
const r = decodeValue(stored, desc);
|
|
513
|
+
if (r === null) continue;
|
|
514
|
+
out[path] = r.decoded;
|
|
515
|
+
if (format) {
|
|
516
|
+
out[`${path}Formatted`] = formatCurrency(r.decimal, r.currency, r.scale, fmtLocale);
|
|
517
|
+
out[`${path}Number`] = Number(r.decimal);
|
|
518
|
+
}
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
out = transformAtMoneyPath(
|
|
522
|
+
out,
|
|
523
|
+
path,
|
|
524
|
+
parseMoneyPath(path),
|
|
525
|
+
0,
|
|
526
|
+
(container, key) => {
|
|
527
|
+
const stored = container[key];
|
|
528
|
+
if (stored === null || stored === void 0) return;
|
|
529
|
+
const r = decodeValue(stored, desc);
|
|
530
|
+
if (r === null) return;
|
|
531
|
+
container[key] = r.decoded;
|
|
532
|
+
if (format && typeof key === "string" && !Array.isArray(container)) {
|
|
533
|
+
container[`${key}Formatted`] = formatCurrency(r.decimal, r.currency, r.scale, fmtLocale);
|
|
534
|
+
container[`${key}Number`] = Number(r.decimal);
|
|
535
|
+
}
|
|
536
|
+
},
|
|
537
|
+
/* lenient */
|
|
538
|
+
true
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
return out;
|
|
542
|
+
}
|
|
543
|
+
|
|
469
544
|
// src/query/builder.ts
|
|
470
545
|
var EMPTY_PLAN = {
|
|
471
546
|
clauses: [],
|
|
@@ -474,6 +549,7 @@ var EMPTY_PLAN = {
|
|
|
474
549
|
offset: 0,
|
|
475
550
|
joins: []
|
|
476
551
|
};
|
|
552
|
+
var DEFAULT_CROSS_JOIN_MAX_ROWS = 5e4;
|
|
477
553
|
var Query = class _Query {
|
|
478
554
|
source;
|
|
479
555
|
plan;
|
|
@@ -506,7 +582,7 @@ var Query = class _Query {
|
|
|
506
582
|
/**
|
|
507
583
|
* @internal — clone this Query with a declared-predicate map
|
|
508
584
|
* attached. Used by the materialized-view registry to enable
|
|
509
|
-
* `.wherePredicate(name, ctx?)` for the MV's query callback
|
|
585
|
+
* `.wherePredicate(name, ctx?)` for the MV's query callback.
|
|
510
586
|
* Consumers don't call this directly.
|
|
511
587
|
*/
|
|
512
588
|
_withPredicates(predicates) {
|
|
@@ -519,7 +595,37 @@ var Query = class _Query {
|
|
|
519
595
|
);
|
|
520
596
|
}
|
|
521
597
|
/**
|
|
522
|
-
*
|
|
598
|
+
* @internal — #308 L3. The ids of records matching this query's plan,
|
|
599
|
+
* recovered by reference identity: `executePlanWithSource` returns the
|
|
600
|
+
* ORIGINAL snapshot record references (money-decode and joins are applied
|
|
601
|
+
* later, in `toArray`), so each matched record is found in the id-paired
|
|
602
|
+
* `snapshotEntries()` map. Used by `collection.retrieve({ within })`.
|
|
603
|
+
* Throws if the source is not collection-backed (no `snapshotEntries`).
|
|
604
|
+
*/
|
|
605
|
+
_idArray() {
|
|
606
|
+
const entries = this.source.snapshotEntries?.();
|
|
607
|
+
if (entries === void 0) {
|
|
608
|
+
throw new Error(
|
|
609
|
+
"Query._idArray(): the query source has no snapshotEntries(); retrieve({ within }) requires a collection-backed query (collection.query())."
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
if (this.plan.clauses.some((c) => c.type === "crossJoin")) {
|
|
613
|
+
throw new Error(
|
|
614
|
+
"Query._idArray(): retrieve({ within }) does not support crossJoin queries (cross-join produces new row objects, breaking id recovery). Use where/filter/and/or, or a projection .join()."
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
const refToId = /* @__PURE__ */ new Map();
|
|
618
|
+
for (const { id, record } of entries) refToId.set(record, id);
|
|
619
|
+
const matched = executePlanWithSource(this.source, this.plan, this.joinContext);
|
|
620
|
+
const ids = [];
|
|
621
|
+
for (const r of matched) {
|
|
622
|
+
const id = refToId.get(r);
|
|
623
|
+
if (id !== void 0) ids.push(id);
|
|
624
|
+
}
|
|
625
|
+
return ids;
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Filter by a registered deterministic predicate. Requires
|
|
523
629
|
* the Query to have been augmented with a predicates map (typically
|
|
524
630
|
* via the materialized-view registry — bare Queries constructed
|
|
525
631
|
* outside an MV throw on `.wherePredicate()`).
|
|
@@ -557,9 +663,18 @@ var Query = class _Query {
|
|
|
557
663
|
this.predicates
|
|
558
664
|
);
|
|
559
665
|
}
|
|
560
|
-
/**
|
|
666
|
+
/**
|
|
667
|
+
* Add a field comparison. Multiple where() calls are AND-combined.
|
|
668
|
+
*
|
|
669
|
+
* A declared money field compares in MAJOR units (#336): the operand
|
|
670
|
+
* (`10000`, `'10000.00'`, or `{ amount, currency }` in multi mode) is
|
|
671
|
+
* quantized into stored scaled-int space at build time and evaluated
|
|
672
|
+
* BigInt-exact per record. A malformed operand or a string operator
|
|
673
|
+
* (`contains`/`startsWith`) throws here, at the call site.
|
|
674
|
+
*/
|
|
561
675
|
where(field, op, value) {
|
|
562
|
-
const
|
|
676
|
+
const desc = this.source.moneyFields?.[field];
|
|
677
|
+
const clause = desc ? moneyFieldClause(field, op, value, desc) : { type: "field", field, op, value };
|
|
563
678
|
return new _Query(
|
|
564
679
|
this.source,
|
|
565
680
|
{ ...this.plan, clauses: [...this.plan.clauses, clause] },
|
|
@@ -625,11 +740,16 @@ var Query = class _Query {
|
|
|
625
740
|
this.predicates
|
|
626
741
|
);
|
|
627
742
|
}
|
|
628
|
-
/**
|
|
629
|
-
|
|
743
|
+
/**
|
|
744
|
+
* Sort by a field. Subsequent calls are tie-breakers. Pass
|
|
745
|
+
* `{ by: 'label' }` to sort a `dictKey`/`staticDict` field by its resolved
|
|
746
|
+
* label at the query locale instead of the stored code (#285).
|
|
747
|
+
*/
|
|
748
|
+
orderBy(field, direction = "asc", opts) {
|
|
749
|
+
const entry = opts?.by === "label" ? { field, direction, by: "label" } : { field, direction };
|
|
630
750
|
return new _Query(
|
|
631
751
|
this.source,
|
|
632
|
-
{ ...this.plan, orderBy: [...this.plan.orderBy,
|
|
752
|
+
{ ...this.plan, orderBy: [...this.plan.orderBy, entry] },
|
|
633
753
|
this.joinContext,
|
|
634
754
|
this.aggregateStrategy,
|
|
635
755
|
this.predicates
|
|
@@ -755,25 +875,119 @@ var Query = class _Query {
|
|
|
755
875
|
this.predicates
|
|
756
876
|
);
|
|
757
877
|
}
|
|
878
|
+
/**
|
|
879
|
+
* Cartesian-product cross-join against `target` collection. Each result row
|
|
880
|
+
* carries the original `T` fields plus `result[as]` populated from every
|
|
881
|
+
* right-side row (or the filtered subset when `on:` is supplied).
|
|
882
|
+
*
|
|
883
|
+
* **Order matters:** `.where().crossJoin()` filters BEFORE expanding (cheaper);
|
|
884
|
+
* `.crossJoin().where('alias.field', ...)` filters AFTER (required when the
|
|
885
|
+
* where clause references the aliased fields).
|
|
886
|
+
*
|
|
887
|
+
* **Cost ceiling:** `CrossJoinTooLargeError` fires before allocation when
|
|
888
|
+
* `leftRows × rightRows` (or the cumulative lateral count) exceeds the limit.
|
|
889
|
+
* Default: 50,000 rows. Override per-clause with `{ maxRows: N }`.
|
|
890
|
+
*
|
|
891
|
+
* **`on:` shapes:**
|
|
892
|
+
* - `on: (left) => TTarget[]` — subset form (most efficient)
|
|
893
|
+
* - `on: (left) => (right) => boolean` — predicate form
|
|
894
|
+
* - `on: { predicate: 'name' }` — MV-safe, hash-tracked form
|
|
895
|
+
* (requires the Query to have been augmented via `_withPredicates`)
|
|
896
|
+
*
|
|
897
|
+
* Requires a JoinContext (constructed via `collection.query()`).
|
|
898
|
+
*/
|
|
899
|
+
crossJoin(target, opts) {
|
|
900
|
+
if (!this.joinContext) {
|
|
901
|
+
throw new Error(
|
|
902
|
+
`Query.crossJoin("${target}"): requires a join context. Use collection.query() to construct a cross-join-capable Query instead of the Query constructor directly.`
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
let onFn;
|
|
906
|
+
let onPredicateName;
|
|
907
|
+
if (opts.on !== void 0) {
|
|
908
|
+
if (typeof opts.on === "function") {
|
|
909
|
+
onFn = opts.on;
|
|
910
|
+
if (this.predicates) {
|
|
911
|
+
console.warn(
|
|
912
|
+
`Query.crossJoin("${target}", { on: callback }): inline on: callback inside a withMaterializedView query() disables queryHash drift detection for this cross-join. Use on: { predicate: '<name>' } to enable it.`
|
|
913
|
+
);
|
|
914
|
+
}
|
|
915
|
+
} else {
|
|
916
|
+
const predName = opts.on.predicate;
|
|
917
|
+
if (!this.predicates) {
|
|
918
|
+
throw new Error(
|
|
919
|
+
`Query.crossJoin("${target}", { on: { predicate: "${predName}" } }): the { predicate } form requires a predicates map. Use this form inside a withMaterializedView query() callback that declares predicates: { ${predName}: { hash, fn } }.`
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
const decl = this.predicates.get(predName);
|
|
923
|
+
if (!decl) {
|
|
924
|
+
throw new Error(
|
|
925
|
+
`Query.crossJoin("${target}"): predicate "${predName}" not registered. Available: ${[...this.predicates.keys()].join(", ") || "(none)"}.`
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
const as = opts.as;
|
|
929
|
+
const predicateFn = decl.fn;
|
|
930
|
+
onFn = (_left) => (right) => predicateFn({ ..._left, [as]: right });
|
|
931
|
+
onPredicateName = predName;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
const clause = {
|
|
935
|
+
type: "crossJoin",
|
|
936
|
+
target,
|
|
937
|
+
as: opts.as,
|
|
938
|
+
...onFn !== void 0 && { on: onFn },
|
|
939
|
+
...onPredicateName !== void 0 && { onPredicateName },
|
|
940
|
+
...opts.maxRows !== void 0 && { maxRows: opts.maxRows }
|
|
941
|
+
};
|
|
942
|
+
return new _Query(
|
|
943
|
+
this.source,
|
|
944
|
+
{ ...this.plan, clauses: [...this.plan.clauses, clause] },
|
|
945
|
+
this.joinContext,
|
|
946
|
+
this.aggregateStrategy,
|
|
947
|
+
this.predicates
|
|
948
|
+
);
|
|
949
|
+
}
|
|
758
950
|
/**
|
|
759
951
|
* Execute the plan and return the matching records. When the plan
|
|
760
952
|
* carries any join legs, they are applied after `where` / `orderBy`
|
|
761
953
|
* / `limit` / `offset` narrow the left set. See the `.join()` doc
|
|
762
954
|
* for the ordering rationale.
|
|
955
|
+
*
|
|
956
|
+
* `opts.locale` (#285 §3) resolves JOINED right-side i18n fields at the
|
|
957
|
+
* `join` layer to that locale; without it, the owning collection's default
|
|
958
|
+
* locale applies, and a locale-less query leaves joined i18n fields raw.
|
|
959
|
+
* (Left/base i18n fields are resolved by `get`/`list`, not here.)
|
|
763
960
|
*/
|
|
764
|
-
toArray() {
|
|
765
|
-
const base = executePlanWithSource(this.source, this.plan);
|
|
961
|
+
toArray(opts) {
|
|
962
|
+
const base = this.decodeMoney(executePlanWithSource(this.source, this.plan, this.joinContext, opts?.locale));
|
|
766
963
|
if (this.plan.joins.length === 0) return base;
|
|
767
964
|
if (!this.joinContext) {
|
|
768
965
|
throw new Error(
|
|
769
966
|
`Query.toArray(): plan carries ${this.plan.joins.length} join leg(s) but no JoinContext is attached. This usually means the Query was constructed via the raw Query constructor with a plan that had joins pre-populated. Use collection.query().join(...) instead.`
|
|
770
967
|
);
|
|
771
968
|
}
|
|
772
|
-
return applyJoins(base, this.plan.joins, this.joinContext);
|
|
969
|
+
return applyJoins(base, this.plan.joins, this.joinContext, opts?.locale);
|
|
773
970
|
}
|
|
774
|
-
/**
|
|
775
|
-
|
|
776
|
-
|
|
971
|
+
/**
|
|
972
|
+
* Decode this source's money fields on read (stored scaled-int → canonical
|
|
973
|
+
* decimal), so `query().toArray()` agrees with `get()`/`sum()` on the value.
|
|
974
|
+
* No-op when the source declares no money fields.
|
|
975
|
+
*
|
|
976
|
+
* The query layer carries no locale context, so we decode with `'raw'` —
|
|
977
|
+
* canonical decimal, WITHOUT fabricating locale-formatted `<field>Formatted`
|
|
978
|
+
* / `<field>Number` virtuals. Producing a guessed-locale string here would
|
|
979
|
+
* just reintroduce #322's "two read paths disagree" failure on the virtual
|
|
980
|
+
* field (e.g. it-IT via `get()` vs en-US here). Consumers who need formatted
|
|
981
|
+
* money read through `get()`/`list()` with a locale.
|
|
982
|
+
*/
|
|
983
|
+
decodeMoney(records) {
|
|
984
|
+
const moneyFields = this.source.moneyFields;
|
|
985
|
+
if (!moneyFields || Object.keys(moneyFields).length === 0) return records;
|
|
986
|
+
return records.map((r) => decodeMoneyFields(r, moneyFields, "raw"));
|
|
987
|
+
}
|
|
988
|
+
/** Return the first matching record, or null. Joins are applied. `opts.locale` resolves joined i18n fields (#285 §3). */
|
|
989
|
+
first(opts) {
|
|
990
|
+
const arr = this.limit(1).toArray(opts);
|
|
777
991
|
return arr[0] ?? null;
|
|
778
992
|
}
|
|
779
993
|
/**
|
|
@@ -786,9 +1000,17 @@ var Query = class _Query {
|
|
|
786
1000
|
* intent is purely to count.
|
|
787
1001
|
*/
|
|
788
1002
|
count() {
|
|
1003
|
+
if (this.plan.clauses.some((c) => c.type === "crossJoin")) {
|
|
1004
|
+
if (!this.joinContext) {
|
|
1005
|
+
throw new Error(
|
|
1006
|
+
`Query.count(): plan contains crossJoin clauses but no JoinContext is attached.`
|
|
1007
|
+
);
|
|
1008
|
+
}
|
|
1009
|
+
return executeClausePipeline(this.source, this.plan.clauses, this.joinContext).length;
|
|
1010
|
+
}
|
|
789
1011
|
const { candidates, remainingClauses } = candidateRecords(this.source, this.plan.clauses);
|
|
790
1012
|
if (remainingClauses.length === 0) return candidates.length;
|
|
791
|
-
return filterRecords(candidates, remainingClauses).length;
|
|
1013
|
+
return filterRecords(candidates, remainingClauses, fnViewDecoder(this.source)).length;
|
|
792
1014
|
}
|
|
793
1015
|
/**
|
|
794
1016
|
* Reduce the matching records through a named set of reducers.
|
|
@@ -831,11 +1053,21 @@ var Query = class _Query {
|
|
|
831
1053
|
* partition boundaries without an API break.
|
|
832
1054
|
*/
|
|
833
1055
|
aggregate(spec) {
|
|
1056
|
+
const moneyFields = this.source.moneyFields;
|
|
1057
|
+
if (moneyFields) {
|
|
1058
|
+
spec = wrapMoneyReducers(spec, moneyFields);
|
|
1059
|
+
}
|
|
834
1060
|
const source = this.source;
|
|
835
1061
|
const clauses = this.plan.clauses;
|
|
1062
|
+
const joinCtx = this.joinContext;
|
|
1063
|
+
const hasCrossJoins = clauses.some((c) => c.type === "crossJoin");
|
|
836
1064
|
const executeRecords = () => {
|
|
1065
|
+
if (hasCrossJoins) {
|
|
1066
|
+
if (!joinCtx) throw new Error("Query.aggregate(): crossJoin requires a join context");
|
|
1067
|
+
return executeClausePipeline(source, clauses, joinCtx);
|
|
1068
|
+
}
|
|
837
1069
|
const { candidates, remainingClauses } = candidateRecords(source, clauses);
|
|
838
|
-
return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses);
|
|
1070
|
+
return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses, fnViewDecoder(source));
|
|
839
1071
|
};
|
|
840
1072
|
const upstreams = [];
|
|
841
1073
|
if (source.subscribe) {
|
|
@@ -850,9 +1082,15 @@ var Query = class _Query {
|
|
|
850
1082
|
}
|
|
851
1083
|
const source = this.source;
|
|
852
1084
|
const clauses = this.plan.clauses;
|
|
1085
|
+
const joinCtx = this.joinContext;
|
|
1086
|
+
const hasCrossJoins = clauses.some((c) => c.type === "crossJoin");
|
|
853
1087
|
const executeRecords = () => {
|
|
1088
|
+
if (hasCrossJoins) {
|
|
1089
|
+
if (!joinCtx) throw new Error("Query.groupBy(): crossJoin requires a join context");
|
|
1090
|
+
return executeClausePipeline(source, clauses, joinCtx);
|
|
1091
|
+
}
|
|
854
1092
|
const { candidates, remainingClauses } = candidateRecords(source, clauses);
|
|
855
|
-
return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses);
|
|
1093
|
+
return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses, fnViewDecoder(source));
|
|
856
1094
|
};
|
|
857
1095
|
const upstreams = [];
|
|
858
1096
|
if (source.subscribe) {
|
|
@@ -866,13 +1104,15 @@ var Query = class _Query {
|
|
|
866
1104
|
executeRecords,
|
|
867
1105
|
field,
|
|
868
1106
|
upstreams,
|
|
869
|
-
dictLabelResolver
|
|
1107
|
+
dictLabelResolver,
|
|
1108
|
+
this.source.moneyFields
|
|
870
1109
|
);
|
|
871
1110
|
}
|
|
872
1111
|
return this.aggregateStrategy.groupByN(
|
|
873
1112
|
executeRecords,
|
|
874
1113
|
fields,
|
|
875
|
-
upstreams
|
|
1114
|
+
upstreams,
|
|
1115
|
+
this.source.moneyFields
|
|
876
1116
|
);
|
|
877
1117
|
}
|
|
878
1118
|
/**
|
|
@@ -964,6 +1204,21 @@ var Query = class _Query {
|
|
|
964
1204
|
}
|
|
965
1205
|
}
|
|
966
1206
|
}
|
|
1207
|
+
if (this.joinContext) {
|
|
1208
|
+
const subscribedCross = /* @__PURE__ */ new Set();
|
|
1209
|
+
for (const clause of this.plan.clauses) {
|
|
1210
|
+
if (clause.type !== "crossJoin") continue;
|
|
1211
|
+
if (subscribedCross.has(clause.target)) continue;
|
|
1212
|
+
subscribedCross.add(clause.target);
|
|
1213
|
+
const rightSource = this.joinContext.resolveSource(clause.target);
|
|
1214
|
+
if (rightSource?.subscribe) {
|
|
1215
|
+
const rightSubscribe = rightSource.subscribe.bind(rightSource);
|
|
1216
|
+
upstreams.push({
|
|
1217
|
+
subscribe: (cb) => rightSubscribe(cb)
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
967
1222
|
return buildLiveQuery(() => this.toArray(), upstreams);
|
|
968
1223
|
}
|
|
969
1224
|
/**
|
|
@@ -975,11 +1230,23 @@ var Query = class _Query {
|
|
|
975
1230
|
return serializePlan(this.plan);
|
|
976
1231
|
}
|
|
977
1232
|
};
|
|
978
|
-
function executePlanWithSource(source, plan) {
|
|
979
|
-
const
|
|
980
|
-
let result
|
|
1233
|
+
function executePlanWithSource(source, plan, joinContext, locale) {
|
|
1234
|
+
const hasCrossJoins = plan.clauses.some((c) => c.type === "crossJoin");
|
|
1235
|
+
let result;
|
|
1236
|
+
if (hasCrossJoins) {
|
|
1237
|
+
if (!joinContext) {
|
|
1238
|
+
throw new Error(
|
|
1239
|
+
`Query.toArray(): plan contains crossJoin clauses but no JoinContext is attached. Use collection.query() instead of new Query() for cross-join support.`
|
|
1240
|
+
);
|
|
1241
|
+
}
|
|
1242
|
+
result = executeClausePipeline(source, plan.clauses, joinContext);
|
|
1243
|
+
} else {
|
|
1244
|
+
const { candidates, remainingClauses } = candidateRecords(source, plan.clauses);
|
|
1245
|
+
result = remainingClauses.length === 0 ? [...candidates] : filterRecords(candidates, remainingClauses, fnViewDecoder(source));
|
|
1246
|
+
}
|
|
981
1247
|
if (plan.orderBy.length > 0) {
|
|
982
|
-
|
|
1248
|
+
const labelMaps = buildOrderLabelMaps(plan.orderBy, joinContext, locale);
|
|
1249
|
+
result = sortRecords(result, plan.orderBy, source.moneyFields, labelMaps);
|
|
983
1250
|
}
|
|
984
1251
|
if (plan.offset > 0) {
|
|
985
1252
|
result = result.slice(plan.offset);
|
|
@@ -999,6 +1266,7 @@ function candidateRecords(source, clauses) {
|
|
|
999
1266
|
const clause = clauses[i];
|
|
1000
1267
|
if (clause.type !== "field") continue;
|
|
1001
1268
|
if (!indexes.has(clause.field)) continue;
|
|
1269
|
+
if (clause.money?.mode === "multi") continue;
|
|
1002
1270
|
let ids = null;
|
|
1003
1271
|
if (clause.op === "==") {
|
|
1004
1272
|
ids = indexes.lookupEqual(clause.field, clause.value);
|
|
@@ -1027,6 +1295,11 @@ function materializeIds(ids, lookupById) {
|
|
|
1027
1295
|
return out;
|
|
1028
1296
|
}
|
|
1029
1297
|
function executePlan(records, plan) {
|
|
1298
|
+
if (plan.clauses.some((c) => c.type === "crossJoin")) {
|
|
1299
|
+
throw new Error(
|
|
1300
|
+
`executePlan(): does not support crossJoin clauses. executePlan is a stateless pure function \u2014 it cannot resolve cross-join right-side collections. Use Query.toArray() (via collection.query()) instead.`
|
|
1301
|
+
);
|
|
1302
|
+
}
|
|
1030
1303
|
let result = filterRecords(records, plan.clauses);
|
|
1031
1304
|
if (plan.orderBy.length > 0) {
|
|
1032
1305
|
result = sortRecords(result, plan.orderBy);
|
|
@@ -1039,13 +1312,20 @@ function executePlan(records, plan) {
|
|
|
1039
1312
|
}
|
|
1040
1313
|
return result;
|
|
1041
1314
|
}
|
|
1042
|
-
function
|
|
1315
|
+
function fnViewDecoder(source) {
|
|
1316
|
+
const mf = source.moneyFields;
|
|
1317
|
+
if (!mf || Object.keys(mf).length === 0) return void 0;
|
|
1318
|
+
return (r) => decodeMoneyFields(r, mf, "raw");
|
|
1319
|
+
}
|
|
1320
|
+
function filterRecords(records, clauses, decodeForFns) {
|
|
1043
1321
|
if (clauses.length === 0) return [...records];
|
|
1322
|
+
const needsFnView = decodeForFns !== void 0 && hasFnClause(clauses);
|
|
1044
1323
|
const out = [];
|
|
1045
1324
|
for (const r of records) {
|
|
1325
|
+
const fnView = needsFnView ? decodeForFns(r) : void 0;
|
|
1046
1326
|
let matches = true;
|
|
1047
1327
|
for (const clause of clauses) {
|
|
1048
|
-
if (!evaluateClause(r, clause)) {
|
|
1328
|
+
if (!evaluateClause(r, clause, fnView)) {
|
|
1049
1329
|
matches = false;
|
|
1050
1330
|
break;
|
|
1051
1331
|
}
|
|
@@ -1054,17 +1334,124 @@ function filterRecords(records, clauses) {
|
|
|
1054
1334
|
}
|
|
1055
1335
|
return out;
|
|
1056
1336
|
}
|
|
1057
|
-
function
|
|
1337
|
+
function executeClausePipeline(source, clauses, joinContext) {
|
|
1338
|
+
let rel = [...source.snapshot()];
|
|
1339
|
+
let filterBatch = [];
|
|
1340
|
+
const decodeForFns = fnViewDecoder(source);
|
|
1341
|
+
for (const clause of clauses) {
|
|
1342
|
+
if (clause.type === "crossJoin") {
|
|
1343
|
+
if (filterBatch.length > 0) {
|
|
1344
|
+
rel = filterRecords(rel, filterBatch, decodeForFns);
|
|
1345
|
+
filterBatch = [];
|
|
1346
|
+
}
|
|
1347
|
+
const rightSource = joinContext.resolveSource(clause.target);
|
|
1348
|
+
if (!rightSource) {
|
|
1349
|
+
throw new CrossJoinSourceUnknownError(clause.target, joinContext.leftCollection);
|
|
1350
|
+
}
|
|
1351
|
+
rel = applyCrossJoin(rel, clause, rightSource);
|
|
1352
|
+
} else {
|
|
1353
|
+
filterBatch.push(clause);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
if (filterBatch.length > 0) {
|
|
1357
|
+
rel = filterRecords(rel, filterBatch, decodeForFns);
|
|
1358
|
+
}
|
|
1359
|
+
return rel;
|
|
1360
|
+
}
|
|
1361
|
+
function applyCrossJoin(leftRel, clause, rightSource) {
|
|
1362
|
+
const rightRows = rightSource.snapshot();
|
|
1363
|
+
const maxRows = clause.maxRows ?? DEFAULT_CROSS_JOIN_MAX_ROWS;
|
|
1364
|
+
const { as } = clause;
|
|
1365
|
+
if (!clause.on) {
|
|
1366
|
+
const product = leftRel.length * rightRows.length;
|
|
1367
|
+
if (product > maxRows) {
|
|
1368
|
+
throw new CrossJoinTooLargeError({ target: clause.target, expected: product, limit: maxRows });
|
|
1369
|
+
}
|
|
1370
|
+
const expanded2 = [];
|
|
1371
|
+
for (const left of leftRel) {
|
|
1372
|
+
const leftObj = left;
|
|
1373
|
+
for (const right of rightRows) {
|
|
1374
|
+
expanded2.push({ ...leftObj, [as]: right });
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
return expanded2;
|
|
1378
|
+
}
|
|
1379
|
+
const expanded = [];
|
|
1380
|
+
let cumulative = 0;
|
|
1381
|
+
for (const left of leftRel) {
|
|
1382
|
+
const callbackResult = clause.on(left);
|
|
1383
|
+
let filteredRight;
|
|
1384
|
+
if (Array.isArray(callbackResult)) {
|
|
1385
|
+
filteredRight = callbackResult;
|
|
1386
|
+
} else {
|
|
1387
|
+
filteredRight = rightRows.filter(
|
|
1388
|
+
callbackResult
|
|
1389
|
+
);
|
|
1390
|
+
}
|
|
1391
|
+
cumulative += filteredRight.length;
|
|
1392
|
+
if (cumulative > maxRows) {
|
|
1393
|
+
throw new CrossJoinTooLargeError({
|
|
1394
|
+
target: clause.target,
|
|
1395
|
+
expected: cumulative,
|
|
1396
|
+
limit: maxRows
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1399
|
+
const leftObj = left;
|
|
1400
|
+
for (const right of filteredRight) {
|
|
1401
|
+
expanded.push({ ...leftObj, [as]: right });
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
return expanded;
|
|
1405
|
+
}
|
|
1406
|
+
function sortRecords(records, orderBy, moneyFields, labelMaps) {
|
|
1058
1407
|
return [...records].sort((a, b) => {
|
|
1059
|
-
for (const { field, direction } of orderBy) {
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
const
|
|
1408
|
+
for (const { field, direction, by } of orderBy) {
|
|
1409
|
+
let av = readField(a, field);
|
|
1410
|
+
let bv = readField(b, field);
|
|
1411
|
+
const labelMap = by === "label" ? labelMaps?.get(field) : void 0;
|
|
1412
|
+
if (labelMap) {
|
|
1413
|
+
av = (typeof av === "string" ? labelMap.get(av) : void 0) ?? av;
|
|
1414
|
+
bv = (typeof bv === "string" ? labelMap.get(bv) : void 0) ?? bv;
|
|
1415
|
+
const cmp2 = compareValues(av, bv);
|
|
1416
|
+
if (cmp2 !== 0) return direction === "asc" ? cmp2 : -cmp2;
|
|
1417
|
+
continue;
|
|
1418
|
+
}
|
|
1419
|
+
const desc = moneyFields?.[field];
|
|
1420
|
+
const cmp = desc ? compareMoney(av, bv, desc) : compareValues(av, bv);
|
|
1063
1421
|
if (cmp !== 0) return direction === "asc" ? cmp : -cmp;
|
|
1064
1422
|
}
|
|
1065
1423
|
return 0;
|
|
1066
1424
|
});
|
|
1067
1425
|
}
|
|
1426
|
+
function buildOrderLabelMaps(orderBy, joinContext, locale) {
|
|
1427
|
+
if (!joinContext?.resolveDictSource) return void 0;
|
|
1428
|
+
const resolveDict = joinContext.resolveDictSource.bind(joinContext);
|
|
1429
|
+
let maps;
|
|
1430
|
+
for (const { field, by } of orderBy) {
|
|
1431
|
+
if (by !== "label") continue;
|
|
1432
|
+
const dictSource = resolveDict(field);
|
|
1433
|
+
if (!dictSource) continue;
|
|
1434
|
+
const loc = locale ?? dictSource.displayLocale;
|
|
1435
|
+
if (loc === void 0) continue;
|
|
1436
|
+
const codeToLabel = /* @__PURE__ */ new Map();
|
|
1437
|
+
for (const entry of dictSource.snapshot()) {
|
|
1438
|
+
const k = entry["key"];
|
|
1439
|
+
const labels = entry["labels"];
|
|
1440
|
+
const label = labels?.[loc];
|
|
1441
|
+
if (typeof k === "string" && typeof label === "string") codeToLabel.set(k, label);
|
|
1442
|
+
}
|
|
1443
|
+
;
|
|
1444
|
+
(maps ??= /* @__PURE__ */ new Map()).set(field, codeToLabel);
|
|
1445
|
+
}
|
|
1446
|
+
return maps;
|
|
1447
|
+
}
|
|
1448
|
+
function compareMoney(a, b, desc) {
|
|
1449
|
+
const av = moneyScaledValue(a, desc);
|
|
1450
|
+
const bv = moneyScaledValue(b, desc);
|
|
1451
|
+
if (av === null) return bv === null ? 0 : 1;
|
|
1452
|
+
if (bv === null) return -1;
|
|
1453
|
+
return av < bv ? -1 : av > bv ? 1 : 0;
|
|
1454
|
+
}
|
|
1068
1455
|
function readField(record, field) {
|
|
1069
1456
|
if (record === null || record === void 0) return void 0;
|
|
1070
1457
|
if (!field.includes(".")) {
|
|
@@ -1116,6 +1503,16 @@ function serializeClause(clause) {
|
|
|
1116
1503
|
clauses: clause.clauses.map(serializeClause)
|
|
1117
1504
|
};
|
|
1118
1505
|
}
|
|
1506
|
+
if (clause.type === "crossJoin") {
|
|
1507
|
+
return {
|
|
1508
|
+
type: "crossJoin",
|
|
1509
|
+
target: clause.target,
|
|
1510
|
+
as: clause.as,
|
|
1511
|
+
on: clause.on ? "[function]" : void 0,
|
|
1512
|
+
onPredicateName: clause.onPredicateName,
|
|
1513
|
+
maxRows: clause.maxRows
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1119
1516
|
return clause;
|
|
1120
1517
|
}
|
|
1121
1518
|
function canonicalCtxHash(ctx) {
|
|
@@ -1141,6 +1538,7 @@ function buildDictLabelResolver(joinCtx, field) {
|
|
|
1141
1538
|
const dictSource = joinCtx.resolveDictSource(field);
|
|
1142
1539
|
if (!dictSource) return void 0;
|
|
1143
1540
|
const snapshot = dictSource.snapshot();
|
|
1541
|
+
const displayLocale = dictSource.displayLocale;
|
|
1144
1542
|
const dictMap = /* @__PURE__ */ new Map();
|
|
1145
1543
|
for (const entry of snapshot) {
|
|
1146
1544
|
const k = entry["key"];
|
|
@@ -1150,9 +1548,11 @@ function buildDictLabelResolver(joinCtx, field) {
|
|
|
1150
1548
|
}
|
|
1151
1549
|
}
|
|
1152
1550
|
return async (key, locale, fallback) => {
|
|
1551
|
+
const effLocale = locale || displayLocale;
|
|
1552
|
+
if (!effLocale) return void 0;
|
|
1153
1553
|
const labels = dictMap.get(key);
|
|
1154
1554
|
if (!labels) return void 0;
|
|
1155
|
-
if (labels[
|
|
1555
|
+
if (labels[effLocale] !== void 0) return labels[effLocale];
|
|
1156
1556
|
const chain = Array.isArray(fallback) ? fallback : fallback ? [fallback] : [];
|
|
1157
1557
|
for (const fb of chain) {
|
|
1158
1558
|
if (fb === "any") {
|
|
@@ -1166,537 +1566,6 @@ function buildDictLabelResolver(joinCtx, field) {
|
|
|
1166
1566
|
};
|
|
1167
1567
|
}
|
|
1168
1568
|
|
|
1169
|
-
// src/indexing/eager-indexes.ts
|
|
1170
|
-
var CollectionIndexes = class {
|
|
1171
|
-
indexes = /* @__PURE__ */ new Map();
|
|
1172
|
-
/**
|
|
1173
|
-
* Declare an index. Subsequent record additions are tracked under it.
|
|
1174
|
-
* Calling this twice for the same field is a no-op (idempotent).
|
|
1175
|
-
*/
|
|
1176
|
-
declare(field) {
|
|
1177
|
-
if (this.indexes.has(field)) return;
|
|
1178
|
-
this.indexes.set(field, { field, buckets: /* @__PURE__ */ new Map() });
|
|
1179
|
-
}
|
|
1180
|
-
/** True if the given field has a declared index. */
|
|
1181
|
-
has(field) {
|
|
1182
|
-
return this.indexes.has(field);
|
|
1183
|
-
}
|
|
1184
|
-
/** All declared field names, in declaration order. */
|
|
1185
|
-
fields() {
|
|
1186
|
-
return [...this.indexes.keys()];
|
|
1187
|
-
}
|
|
1188
|
-
/**
|
|
1189
|
-
* Build all declared indexes from a snapshot of records.
|
|
1190
|
-
* Called once per hydration. O(N × indexes.size).
|
|
1191
|
-
*/
|
|
1192
|
-
build(records) {
|
|
1193
|
-
for (const idx of this.indexes.values()) {
|
|
1194
|
-
idx.buckets.clear();
|
|
1195
|
-
for (const { id, record } of records) {
|
|
1196
|
-
addToIndex(idx, id, record);
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
}
|
|
1200
|
-
/**
|
|
1201
|
-
* Insert or update a single record across all indexes.
|
|
1202
|
-
* Called by `Collection.put()` after the encrypted write succeeds.
|
|
1203
|
-
*
|
|
1204
|
-
* If `previousRecord` is provided, the record is removed from any old
|
|
1205
|
-
* buckets first — this is the update path. Pass `null` for fresh adds.
|
|
1206
|
-
*/
|
|
1207
|
-
upsert(id, newRecord, previousRecord) {
|
|
1208
|
-
if (this.indexes.size === 0) return;
|
|
1209
|
-
if (previousRecord !== null) {
|
|
1210
|
-
this.remove(id, previousRecord);
|
|
1211
|
-
}
|
|
1212
|
-
for (const idx of this.indexes.values()) {
|
|
1213
|
-
addToIndex(idx, id, newRecord);
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
/**
|
|
1217
|
-
* Remove a record from all indexes. Called by `Collection.delete()`
|
|
1218
|
-
* (and as the first half of `upsert` for the update path).
|
|
1219
|
-
*/
|
|
1220
|
-
remove(id, record) {
|
|
1221
|
-
if (this.indexes.size === 0) return;
|
|
1222
|
-
for (const idx of this.indexes.values()) {
|
|
1223
|
-
removeFromIndex(idx, id, record);
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1226
|
-
/** Drop all index data. Called when the collection is invalidated. */
|
|
1227
|
-
clear() {
|
|
1228
|
-
for (const idx of this.indexes.values()) {
|
|
1229
|
-
idx.buckets.clear();
|
|
1230
|
-
}
|
|
1231
|
-
}
|
|
1232
|
-
/**
|
|
1233
|
-
* Equality lookup: return the set of record ids whose `field` matches
|
|
1234
|
-
* the given value. Returns `null` if no index covers the field — the
|
|
1235
|
-
* caller should fall back to a linear scan.
|
|
1236
|
-
*
|
|
1237
|
-
* The returned Set is a reference to the index's internal storage —
|
|
1238
|
-
* callers must NOT mutate it.
|
|
1239
|
-
*/
|
|
1240
|
-
lookupEqual(field, value) {
|
|
1241
|
-
const idx = this.indexes.get(field);
|
|
1242
|
-
if (!idx) return null;
|
|
1243
|
-
const key = stringifyKey(value);
|
|
1244
|
-
return idx.buckets.get(key) ?? EMPTY_SET;
|
|
1245
|
-
}
|
|
1246
|
-
/**
|
|
1247
|
-
* Set lookup: return the union of record ids whose `field` matches any
|
|
1248
|
-
* of the given values. Returns `null` if no index covers the field.
|
|
1249
|
-
*/
|
|
1250
|
-
lookupIn(field, values) {
|
|
1251
|
-
const idx = this.indexes.get(field);
|
|
1252
|
-
if (!idx) return null;
|
|
1253
|
-
const out = /* @__PURE__ */ new Set();
|
|
1254
|
-
for (const value of values) {
|
|
1255
|
-
const key = stringifyKey(value);
|
|
1256
|
-
const bucket = idx.buckets.get(key);
|
|
1257
|
-
if (bucket) {
|
|
1258
|
-
for (const id of bucket) out.add(id);
|
|
1259
|
-
}
|
|
1260
|
-
}
|
|
1261
|
-
return out;
|
|
1262
|
-
}
|
|
1263
|
-
};
|
|
1264
|
-
var EMPTY_SET = /* @__PURE__ */ new Set();
|
|
1265
|
-
function stringifyKey(value) {
|
|
1266
|
-
if (value === null || value === void 0) return "\0NULL\0";
|
|
1267
|
-
if (typeof value === "string") return value;
|
|
1268
|
-
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
1269
|
-
if (value instanceof Date) return value.toISOString();
|
|
1270
|
-
return "\0OBJECT\0";
|
|
1271
|
-
}
|
|
1272
|
-
function addToIndex(idx, id, record) {
|
|
1273
|
-
const value = readPath(record, idx.field);
|
|
1274
|
-
if (value === null || value === void 0) return;
|
|
1275
|
-
const key = stringifyKey(value);
|
|
1276
|
-
let bucket = idx.buckets.get(key);
|
|
1277
|
-
if (!bucket) {
|
|
1278
|
-
bucket = /* @__PURE__ */ new Set();
|
|
1279
|
-
idx.buckets.set(key, bucket);
|
|
1280
|
-
}
|
|
1281
|
-
bucket.add(id);
|
|
1282
|
-
}
|
|
1283
|
-
function removeFromIndex(idx, id, record) {
|
|
1284
|
-
const value = readPath(record, idx.field);
|
|
1285
|
-
if (value === null || value === void 0) return;
|
|
1286
|
-
const key = stringifyKey(value);
|
|
1287
|
-
const bucket = idx.buckets.get(key);
|
|
1288
|
-
if (!bucket) return;
|
|
1289
|
-
bucket.delete(id);
|
|
1290
|
-
if (bucket.size === 0) idx.buckets.delete(key);
|
|
1291
|
-
}
|
|
1292
|
-
|
|
1293
|
-
// src/aggregate/reducers.ts
|
|
1294
|
-
function count(opts) {
|
|
1295
|
-
const _seed = opts?.seed;
|
|
1296
|
-
void _seed;
|
|
1297
|
-
return {
|
|
1298
|
-
init: () => 0,
|
|
1299
|
-
step: (state) => state + 1,
|
|
1300
|
-
remove: (state) => state - 1,
|
|
1301
|
-
finalize: (state) => state
|
|
1302
|
-
};
|
|
1303
|
-
}
|
|
1304
|
-
function sum(field, opts) {
|
|
1305
|
-
const _seed = opts?.seed;
|
|
1306
|
-
void _seed;
|
|
1307
|
-
return {
|
|
1308
|
-
init: () => 0,
|
|
1309
|
-
step: (state, record) => state + readNumber(record, field),
|
|
1310
|
-
remove: (state, record) => state - readNumber(record, field),
|
|
1311
|
-
finalize: (state) => state
|
|
1312
|
-
};
|
|
1313
|
-
}
|
|
1314
|
-
function avg(field, opts) {
|
|
1315
|
-
const _seed = opts?.seed;
|
|
1316
|
-
void _seed;
|
|
1317
|
-
return {
|
|
1318
|
-
init: () => ({ sum: 0, count: 0 }),
|
|
1319
|
-
step: (state, record) => ({
|
|
1320
|
-
sum: state.sum + readNumber(record, field),
|
|
1321
|
-
count: state.count + 1
|
|
1322
|
-
}),
|
|
1323
|
-
remove: (state, record) => ({
|
|
1324
|
-
sum: state.sum - readNumber(record, field),
|
|
1325
|
-
count: state.count - 1
|
|
1326
|
-
}),
|
|
1327
|
-
finalize: (state) => state.count === 0 ? null : state.sum / state.count
|
|
1328
|
-
};
|
|
1329
|
-
}
|
|
1330
|
-
function pushValue(state, value) {
|
|
1331
|
-
return { values: [...state.values, value] };
|
|
1332
|
-
}
|
|
1333
|
-
function removeValue(state, value) {
|
|
1334
|
-
const idx = state.values.indexOf(value);
|
|
1335
|
-
if (idx < 0) return state;
|
|
1336
|
-
const next = state.values.slice();
|
|
1337
|
-
next.splice(idx, 1);
|
|
1338
|
-
return { values: next };
|
|
1339
|
-
}
|
|
1340
|
-
function min(field, opts) {
|
|
1341
|
-
const _seed = opts?.seed;
|
|
1342
|
-
void _seed;
|
|
1343
|
-
return {
|
|
1344
|
-
init: () => ({ values: [] }),
|
|
1345
|
-
step: (state, record) => pushValue(state, readNumber(record, field)),
|
|
1346
|
-
remove: (state, record) => removeValue(state, readNumber(record, field)),
|
|
1347
|
-
finalize: (state) => {
|
|
1348
|
-
if (state.values.length === 0) return null;
|
|
1349
|
-
let out = state.values[0];
|
|
1350
|
-
for (let i = 1; i < state.values.length; i++) {
|
|
1351
|
-
const v = state.values[i];
|
|
1352
|
-
if (v < out) out = v;
|
|
1353
|
-
}
|
|
1354
|
-
return out;
|
|
1355
|
-
}
|
|
1356
|
-
};
|
|
1357
|
-
}
|
|
1358
|
-
function max(field, opts) {
|
|
1359
|
-
const _seed = opts?.seed;
|
|
1360
|
-
void _seed;
|
|
1361
|
-
return {
|
|
1362
|
-
init: () => ({ values: [] }),
|
|
1363
|
-
step: (state, record) => pushValue(state, readNumber(record, field)),
|
|
1364
|
-
remove: (state, record) => removeValue(state, readNumber(record, field)),
|
|
1365
|
-
finalize: (state) => {
|
|
1366
|
-
if (state.values.length === 0) return null;
|
|
1367
|
-
let out = state.values[0];
|
|
1368
|
-
for (let i = 1; i < state.values.length; i++) {
|
|
1369
|
-
const v = state.values[i];
|
|
1370
|
-
if (v > out) out = v;
|
|
1371
|
-
}
|
|
1372
|
-
return out;
|
|
1373
|
-
}
|
|
1374
|
-
};
|
|
1375
|
-
}
|
|
1376
|
-
function readNumber(record, field) {
|
|
1377
|
-
const value = readPath(record, field);
|
|
1378
|
-
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
// src/aggregate/aggregation.ts
|
|
1382
|
-
function reduceRecords(records, spec) {
|
|
1383
|
-
const state = {};
|
|
1384
|
-
for (const key of Object.keys(spec)) {
|
|
1385
|
-
state[key] = spec[key].init();
|
|
1386
|
-
}
|
|
1387
|
-
for (const record of records) {
|
|
1388
|
-
for (const key of Object.keys(spec)) {
|
|
1389
|
-
state[key] = spec[key].step(state[key], record);
|
|
1390
|
-
}
|
|
1391
|
-
}
|
|
1392
|
-
const result = {};
|
|
1393
|
-
for (const key of Object.keys(spec)) {
|
|
1394
|
-
result[key] = spec[key].finalize(state[key]);
|
|
1395
|
-
}
|
|
1396
|
-
return result;
|
|
1397
|
-
}
|
|
1398
|
-
var LiveAggregationImpl = class {
|
|
1399
|
-
constructor(recompute, upstreams) {
|
|
1400
|
-
this.recompute = recompute;
|
|
1401
|
-
try {
|
|
1402
|
-
this.value = recompute();
|
|
1403
|
-
this.error = void 0;
|
|
1404
|
-
} catch (err) {
|
|
1405
|
-
this.value = void 0;
|
|
1406
|
-
this.error = err;
|
|
1407
|
-
}
|
|
1408
|
-
for (const upstream of upstreams) {
|
|
1409
|
-
const unsub = upstream.subscribe(() => this.refresh());
|
|
1410
|
-
this.unsubscribes.push(unsub);
|
|
1411
|
-
}
|
|
1412
|
-
}
|
|
1413
|
-
recompute;
|
|
1414
|
-
value;
|
|
1415
|
-
error;
|
|
1416
|
-
listeners = /* @__PURE__ */ new Set();
|
|
1417
|
-
unsubscribes = [];
|
|
1418
|
-
stopped = false;
|
|
1419
|
-
refresh() {
|
|
1420
|
-
if (this.stopped) return;
|
|
1421
|
-
try {
|
|
1422
|
-
this.value = this.recompute();
|
|
1423
|
-
this.error = void 0;
|
|
1424
|
-
} catch (err) {
|
|
1425
|
-
this.error = err;
|
|
1426
|
-
}
|
|
1427
|
-
for (const listener of this.listeners) {
|
|
1428
|
-
try {
|
|
1429
|
-
listener();
|
|
1430
|
-
} catch (err) {
|
|
1431
|
-
console.warn("[noy-db] LiveAggregation listener threw:", err);
|
|
1432
|
-
}
|
|
1433
|
-
}
|
|
1434
|
-
}
|
|
1435
|
-
subscribe(cb) {
|
|
1436
|
-
if (this.stopped) {
|
|
1437
|
-
return () => {
|
|
1438
|
-
};
|
|
1439
|
-
}
|
|
1440
|
-
this.listeners.add(cb);
|
|
1441
|
-
return () => {
|
|
1442
|
-
this.listeners.delete(cb);
|
|
1443
|
-
};
|
|
1444
|
-
}
|
|
1445
|
-
stop() {
|
|
1446
|
-
if (this.stopped) return;
|
|
1447
|
-
this.stopped = true;
|
|
1448
|
-
for (const unsub of this.unsubscribes) {
|
|
1449
|
-
try {
|
|
1450
|
-
unsub();
|
|
1451
|
-
} catch (err) {
|
|
1452
|
-
console.warn("[noy-db] LiveAggregation upstream unsubscribe threw:", err);
|
|
1453
|
-
}
|
|
1454
|
-
}
|
|
1455
|
-
this.unsubscribes.length = 0;
|
|
1456
|
-
this.listeners.clear();
|
|
1457
|
-
}
|
|
1458
|
-
};
|
|
1459
|
-
var Aggregation = class {
|
|
1460
|
-
constructor(executeRecords, spec, upstreams) {
|
|
1461
|
-
this.executeRecords = executeRecords;
|
|
1462
|
-
this.spec = spec;
|
|
1463
|
-
this.upstreams = upstreams;
|
|
1464
|
-
}
|
|
1465
|
-
executeRecords;
|
|
1466
|
-
spec;
|
|
1467
|
-
upstreams;
|
|
1468
|
-
/**
|
|
1469
|
-
* Execute the query and reduce the results synchronously.
|
|
1470
|
-
* Returns the reduced shape matching the spec — e.g. a spec of
|
|
1471
|
-
* `{ total: sum('amount'), n: count() }` returns
|
|
1472
|
-
* `{ total: number, n: number }`.
|
|
1473
|
-
*/
|
|
1474
|
-
run() {
|
|
1475
|
-
return reduceRecords(this.executeRecords(), this.spec);
|
|
1476
|
-
}
|
|
1477
|
-
/**
|
|
1478
|
-
* Build a reactive `LiveAggregation<R>` that re-runs the reduction
|
|
1479
|
-
* whenever any upstream source notifies of a change. The initial
|
|
1480
|
-
* value is computed eagerly in the constructor, so consumers can
|
|
1481
|
-
* read `live.value` immediately after calling `.live()`.
|
|
1482
|
-
*
|
|
1483
|
-
* Always call `live.stop()` when finished — it tears down the
|
|
1484
|
-
* upstream subscriptions. Vue's `onUnmounted` is the canonical
|
|
1485
|
-
* place.
|
|
1486
|
-
*
|
|
1487
|
-
* **Implementation note:** every upstream change triggers a full
|
|
1488
|
-
* re-reduction. Incremental maintenance (O(1) per delta for
|
|
1489
|
-
* sum/count/avg via the reducer protocol's `remove()` method) is a
|
|
1490
|
-
* planned follow-up optimization — the protocol already supports
|
|
1491
|
-
* it, but the executor doesn't drive it yet. Consumers get
|
|
1492
|
-
* correct, reactive values today; future PRs can switch to
|
|
1493
|
-
* delta-based maintenance without changing this API.
|
|
1494
|
-
*/
|
|
1495
|
-
live() {
|
|
1496
|
-
const recompute = () => reduceRecords(this.executeRecords(), this.spec);
|
|
1497
|
-
return new LiveAggregationImpl(recompute, this.upstreams);
|
|
1498
|
-
}
|
|
1499
|
-
};
|
|
1500
|
-
function buildLiveAggregation(recompute, upstreams) {
|
|
1501
|
-
return new LiveAggregationImpl(recompute, upstreams);
|
|
1502
|
-
}
|
|
1503
|
-
|
|
1504
|
-
// src/aggregate/canonical-key.ts
|
|
1505
|
-
function canonicalGroupKey(fields, row) {
|
|
1506
|
-
const sorted = [...fields].sort();
|
|
1507
|
-
const parts = [];
|
|
1508
|
-
for (const name of sorted) {
|
|
1509
|
-
const v = row[name];
|
|
1510
|
-
const serialised = v === void 0 ? "undefined" : JSON.stringify(v);
|
|
1511
|
-
parts.push(`${name}=${serialised}`);
|
|
1512
|
-
}
|
|
1513
|
-
return parts.join("|");
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
|
-
// src/aggregate/groupby.ts
|
|
1517
|
-
var GROUPBY_WARN_CARDINALITY = 1e4;
|
|
1518
|
-
var GROUPBY_MAX_CARDINALITY = 1e5;
|
|
1519
|
-
var warnedCardinalityFields = /* @__PURE__ */ new Set();
|
|
1520
|
-
function warnCardinalityApproaching(fields, observed) {
|
|
1521
|
-
const key = JSON.stringify([...fields].sort());
|
|
1522
|
-
if (warnedCardinalityFields.has(key)) return;
|
|
1523
|
-
warnedCardinalityFields.add(key);
|
|
1524
|
-
const label = `[${fields.join(", ")}]`;
|
|
1525
|
-
console.warn(
|
|
1526
|
-
`[noy-db] .groupBy(${label}) produced ${observed} distinct groups, ${Math.round(observed / GROUPBY_MAX_CARDINALITY * 100)}% of the ${GROUPBY_MAX_CARDINALITY}-group ceiling. Narrow the query with .where() before grouping, or switch to a lower-cardinality field.`
|
|
1527
|
-
);
|
|
1528
|
-
}
|
|
1529
|
-
function resetGroupByWarnings() {
|
|
1530
|
-
warnedCardinalityFields.clear();
|
|
1531
|
-
}
|
|
1532
|
-
var GroupedQueryBase = class {
|
|
1533
|
-
constructor(executeRecords, fieldOrFields, upstreams, dictLabelResolver) {
|
|
1534
|
-
this.executeRecords = executeRecords;
|
|
1535
|
-
this.upstreams = upstreams;
|
|
1536
|
-
this.dictLabelResolver = dictLabelResolver;
|
|
1537
|
-
this.fields = typeof fieldOrFields === "string" ? [fieldOrFields] : [...fieldOrFields];
|
|
1538
|
-
}
|
|
1539
|
-
executeRecords;
|
|
1540
|
-
upstreams;
|
|
1541
|
-
dictLabelResolver;
|
|
1542
|
-
/**
|
|
1543
|
-
* Field set this grouped query buckets on. Stored in declaration
|
|
1544
|
-
* order — the same order is preserved on every result row by
|
|
1545
|
-
* `groupAndReduce`. For the single-field constructor, this is
|
|
1546
|
-
* `[field]`.
|
|
1547
|
-
*/
|
|
1548
|
-
fields;
|
|
1549
|
-
};
|
|
1550
|
-
var GroupedQuery = class extends GroupedQueryBase {
|
|
1551
|
-
/**
|
|
1552
|
-
* Build a grouped aggregation. Returns a `GroupedAggregation`
|
|
1553
|
-
* with `.run()`, `.runAsync()`, and `.live()` terminals — same shape
|
|
1554
|
-
* as the non-grouped `.aggregate()` wrapper, just with an array
|
|
1555
|
-
* result (one row per bucket) instead of a single reduced object.
|
|
1556
|
-
*/
|
|
1557
|
-
aggregate(spec) {
|
|
1558
|
-
return new GroupedAggregation(
|
|
1559
|
-
this.executeRecords,
|
|
1560
|
-
this.fields,
|
|
1561
|
-
spec,
|
|
1562
|
-
this.upstreams,
|
|
1563
|
-
this.dictLabelResolver
|
|
1564
|
-
);
|
|
1565
|
-
}
|
|
1566
|
-
};
|
|
1567
|
-
var GroupedQueryN = class extends GroupedQueryBase {
|
|
1568
|
-
aggregate(spec) {
|
|
1569
|
-
return new GroupedAggregation(
|
|
1570
|
-
this.executeRecords,
|
|
1571
|
-
this.fields,
|
|
1572
|
-
spec,
|
|
1573
|
-
this.upstreams,
|
|
1574
|
-
this.dictLabelResolver
|
|
1575
|
-
);
|
|
1576
|
-
}
|
|
1577
|
-
};
|
|
1578
|
-
function groupAndReduce(records, fieldOrFields, spec) {
|
|
1579
|
-
const fields = typeof fieldOrFields === "string" ? [fieldOrFields] : fieldOrFields;
|
|
1580
|
-
if (fields.length === 0) {
|
|
1581
|
-
throw new Error(".groupBy() requires at least one field");
|
|
1582
|
-
}
|
|
1583
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
1584
|
-
const fieldLabel = fields.length === 1 ? fields[0] : `[${fields.join(", ")}]`;
|
|
1585
|
-
for (const record of records) {
|
|
1586
|
-
const keyValues = {};
|
|
1587
|
-
for (const f of fields) {
|
|
1588
|
-
keyValues[f] = readPath(record, f);
|
|
1589
|
-
}
|
|
1590
|
-
const dedupKey = canonicalGroupKey(fields, keyValues);
|
|
1591
|
-
let bucket = buckets.get(dedupKey);
|
|
1592
|
-
if (bucket === void 0) {
|
|
1593
|
-
if (buckets.size >= GROUPBY_MAX_CARDINALITY) {
|
|
1594
|
-
throw new GroupCardinalityError(
|
|
1595
|
-
fieldLabel,
|
|
1596
|
-
buckets.size + 1,
|
|
1597
|
-
GROUPBY_MAX_CARDINALITY
|
|
1598
|
-
);
|
|
1599
|
-
}
|
|
1600
|
-
bucket = { keyValues, records: [] };
|
|
1601
|
-
buckets.set(dedupKey, bucket);
|
|
1602
|
-
}
|
|
1603
|
-
bucket.records.push(record);
|
|
1604
|
-
}
|
|
1605
|
-
if (buckets.size >= GROUPBY_WARN_CARDINALITY) {
|
|
1606
|
-
warnCardinalityApproaching(fields, buckets.size);
|
|
1607
|
-
}
|
|
1608
|
-
const reducerKeys = Object.keys(spec);
|
|
1609
|
-
const out = [];
|
|
1610
|
-
for (const bucket of buckets.values()) {
|
|
1611
|
-
const state = {};
|
|
1612
|
-
for (const rk of reducerKeys) {
|
|
1613
|
-
state[rk] = spec[rk].init();
|
|
1614
|
-
}
|
|
1615
|
-
for (const record of bucket.records) {
|
|
1616
|
-
for (const rk of reducerKeys) {
|
|
1617
|
-
state[rk] = spec[rk].step(state[rk], record);
|
|
1618
|
-
}
|
|
1619
|
-
}
|
|
1620
|
-
const row = {};
|
|
1621
|
-
for (const f of fields) {
|
|
1622
|
-
row[f] = bucket.keyValues[f];
|
|
1623
|
-
}
|
|
1624
|
-
for (const rk of reducerKeys) {
|
|
1625
|
-
row[rk] = spec[rk].finalize(state[rk]);
|
|
1626
|
-
}
|
|
1627
|
-
out.push(row);
|
|
1628
|
-
}
|
|
1629
|
-
return out;
|
|
1630
|
-
}
|
|
1631
|
-
var GroupedAggregation = class {
|
|
1632
|
-
constructor(executeRecords, fields, spec, upstreams, dictLabelResolver) {
|
|
1633
|
-
this.executeRecords = executeRecords;
|
|
1634
|
-
this.spec = spec;
|
|
1635
|
-
this.upstreams = upstreams;
|
|
1636
|
-
this.dictLabelResolver = dictLabelResolver;
|
|
1637
|
-
this.fields = typeof fields === "string" ? [fields] : [...fields];
|
|
1638
|
-
}
|
|
1639
|
-
executeRecords;
|
|
1640
|
-
spec;
|
|
1641
|
-
upstreams;
|
|
1642
|
-
dictLabelResolver;
|
|
1643
|
-
fields;
|
|
1644
|
-
/** Execute the query, group, reduce, and return an array of rows. */
|
|
1645
|
-
run() {
|
|
1646
|
-
return groupAndReduce(this.executeRecords(), this.fields, this.spec);
|
|
1647
|
-
}
|
|
1648
|
-
/**
|
|
1649
|
-
* Execute the query, group, reduce, and resolve `<field>Label` for
|
|
1650
|
-
* each result row when the grouping field is a `dictKey` and a
|
|
1651
|
-
* `locale` is provided. Returns `R[]` synchronously when
|
|
1652
|
-
* no locale is specified (identical to `.run()`).
|
|
1653
|
-
*
|
|
1654
|
-
* The `<field>Label` field is appended to each row. Rows whose group
|
|
1655
|
-
* key has no dictionary entry get `<field>Label: undefined`.
|
|
1656
|
-
*
|
|
1657
|
-
* Dict-label resolution is single-field only — multi-key groupings
|
|
1658
|
-
* do not produce a `<field>Label`. The resolver is only attached
|
|
1659
|
-
* by the builder when `fields.length === 1`.
|
|
1660
|
-
*/
|
|
1661
|
-
async runAsync(opts) {
|
|
1662
|
-
const rows = groupAndReduce(this.executeRecords(), this.fields, this.spec);
|
|
1663
|
-
if (!opts?.locale || !this.dictLabelResolver || this.fields.length !== 1) return rows;
|
|
1664
|
-
const resolve = this.dictLabelResolver;
|
|
1665
|
-
const locale = opts.locale;
|
|
1666
|
-
const fallback = opts.fallback;
|
|
1667
|
-
const field = this.fields[0];
|
|
1668
|
-
const labelKey = `${field}Label`;
|
|
1669
|
-
return Promise.all(
|
|
1670
|
-
rows.map(async (row) => {
|
|
1671
|
-
const key = row[field];
|
|
1672
|
-
if (typeof key !== "string") return row;
|
|
1673
|
-
const label = await resolve(key, locale, fallback);
|
|
1674
|
-
return { ...row, [labelKey]: label };
|
|
1675
|
-
})
|
|
1676
|
-
);
|
|
1677
|
-
}
|
|
1678
|
-
/**
|
|
1679
|
-
* Build a reactive `LiveAggregation<R[]>` that re-runs the full
|
|
1680
|
-
* group-and-reduce pipeline whenever any upstream source notifies
|
|
1681
|
-
* of a change. Same error-isolation and idempotent-stop contract
|
|
1682
|
-
* as `Aggregation.live()` — the implementation delegates to the
|
|
1683
|
-
* same `LiveAggregationImpl` class by threading a fresh
|
|
1684
|
-
* recompute closure through the existing constructor.
|
|
1685
|
-
*
|
|
1686
|
-
* uses naive full re-run on every change. Incremental
|
|
1687
|
-
* per-bucket maintenance (apply `step` on inserted records,
|
|
1688
|
-
* `remove` on deleted records, route by bucket key) is a future
|
|
1689
|
-
* optimization — the reducer protocol admits it, but wiring
|
|
1690
|
-
* delta-aware source subscriptions is a separate PR.
|
|
1691
|
-
*
|
|
1692
|
-
* Always call `live.stop()` when finished.
|
|
1693
|
-
*/
|
|
1694
|
-
live() {
|
|
1695
|
-
const recompute = () => groupAndReduce(this.executeRecords(), this.fields, this.spec);
|
|
1696
|
-
return buildLiveAggregation(recompute, this.upstreams);
|
|
1697
|
-
}
|
|
1698
|
-
};
|
|
1699
|
-
|
|
1700
1569
|
// src/query/scan-builder.ts
|
|
1701
1570
|
var DEFAULT_SCAN_PAGE_SIZE = 100;
|
|
1702
1571
|
var ScanBuilder = class _ScanBuilder {
|
|
@@ -1723,12 +1592,29 @@ var ScanBuilder = class _ScanBuilder {
|
|
|
1723
1592
|
* context throws with an actionable error.
|
|
1724
1593
|
*/
|
|
1725
1594
|
joinContext;
|
|
1726
|
-
|
|
1595
|
+
/**
|
|
1596
|
+
* Money field descriptors for the backing collection. When present, yielded
|
|
1597
|
+
* records are decoded (stored scaled-int → canonical decimal) so `scan()`
|
|
1598
|
+
* agrees with `get()`/`list()`/`query().toArray()` — #322. Decoded with
|
|
1599
|
+
* `'raw'` (canonical decimal, no locale-formatted virtuals) since the scan
|
|
1600
|
+
* stream carries no locale context, mirroring `Query.toArray()`.
|
|
1601
|
+
*/
|
|
1602
|
+
moneyFields;
|
|
1603
|
+
constructor(pageProvider, pageSize = DEFAULT_SCAN_PAGE_SIZE, clauses = [], joins = [], joinContext, moneyFields) {
|
|
1727
1604
|
this.pageProvider = pageProvider;
|
|
1728
1605
|
this.pageSize = pageSize;
|
|
1729
1606
|
this.clauses = clauses;
|
|
1730
1607
|
this.joins = joins;
|
|
1731
1608
|
this.joinContext = joinContext;
|
|
1609
|
+
this.moneyFields = moneyFields;
|
|
1610
|
+
}
|
|
1611
|
+
/**
|
|
1612
|
+
* Decode this scan's money fields on a record (stored scaled-int → canonical
|
|
1613
|
+
* decimal). No-op when no money fields are declared. See {@link moneyFields}.
|
|
1614
|
+
*/
|
|
1615
|
+
decodeMoney(record) {
|
|
1616
|
+
if (!this.moneyFields || Object.keys(this.moneyFields).length === 0) return record;
|
|
1617
|
+
return decodeMoneyFields(record, this.moneyFields, "raw");
|
|
1732
1618
|
}
|
|
1733
1619
|
/**
|
|
1734
1620
|
* Add a field comparison. Runs per record as the scan stream
|
|
@@ -1744,13 +1630,15 @@ var ScanBuilder = class _ScanBuilder {
|
|
|
1744
1630
|
* evaluates clauses per record in O(1) per clause.
|
|
1745
1631
|
*/
|
|
1746
1632
|
where(field, op, value) {
|
|
1747
|
-
const
|
|
1633
|
+
const desc = this.moneyFields?.[field];
|
|
1634
|
+
const clause = desc ? moneyFieldClause(field, op, value, desc) : { type: "field", field, op, value };
|
|
1748
1635
|
return new _ScanBuilder(
|
|
1749
1636
|
this.pageProvider,
|
|
1750
1637
|
this.pageSize,
|
|
1751
1638
|
[...this.clauses, clause],
|
|
1752
1639
|
this.joins,
|
|
1753
|
-
this.joinContext
|
|
1640
|
+
this.joinContext,
|
|
1641
|
+
this.moneyFields
|
|
1754
1642
|
);
|
|
1755
1643
|
}
|
|
1756
1644
|
/**
|
|
@@ -1769,7 +1657,8 @@ var ScanBuilder = class _ScanBuilder {
|
|
|
1769
1657
|
this.pageSize,
|
|
1770
1658
|
[...this.clauses, clause],
|
|
1771
1659
|
this.joins,
|
|
1772
|
-
this.joinContext
|
|
1660
|
+
this.joinContext,
|
|
1661
|
+
this.moneyFields
|
|
1773
1662
|
);
|
|
1774
1663
|
}
|
|
1775
1664
|
/**
|
|
@@ -1880,7 +1769,8 @@ var ScanBuilder = class _ScanBuilder {
|
|
|
1880
1769
|
this.pageSize,
|
|
1881
1770
|
this.clauses,
|
|
1882
1771
|
[...this.joins, leg],
|
|
1883
|
-
this.joinContext
|
|
1772
|
+
this.joinContext,
|
|
1773
|
+
this.moneyFields
|
|
1884
1774
|
);
|
|
1885
1775
|
}
|
|
1886
1776
|
/**
|
|
@@ -1897,10 +1787,11 @@ var ScanBuilder = class _ScanBuilder {
|
|
|
1897
1787
|
while (true) {
|
|
1898
1788
|
for (const record of page.items) {
|
|
1899
1789
|
if (!this.recordMatches(record)) continue;
|
|
1790
|
+
const decoded = this.decodeMoney(record);
|
|
1900
1791
|
if (joinResolvers === null) {
|
|
1901
|
-
yield
|
|
1792
|
+
yield decoded;
|
|
1902
1793
|
} else {
|
|
1903
|
-
let attached =
|
|
1794
|
+
let attached = decoded;
|
|
1904
1795
|
for (const resolver of joinResolvers) {
|
|
1905
1796
|
attached = this.applyOneJoinStreaming(attached, resolver);
|
|
1906
1797
|
}
|
|
@@ -2086,8 +1977,9 @@ var ScanBuilder = class _ScanBuilder {
|
|
|
2086
1977
|
*/
|
|
2087
1978
|
recordMatches(record) {
|
|
2088
1979
|
if (this.clauses.length === 0) return true;
|
|
1980
|
+
const fnView = this.moneyFields && Object.keys(this.moneyFields).length > 0 && hasFnClause(this.clauses) ? this.decodeMoney(record) : void 0;
|
|
2089
1981
|
for (const clause of this.clauses) {
|
|
2090
|
-
if (!evaluateClause(record, clause)) return false;
|
|
1982
|
+
if (!evaluateClause(record, clause, fnView)) return false;
|
|
2091
1983
|
}
|
|
2092
1984
|
return true;
|
|
2093
1985
|
}
|
|
@@ -2098,38 +1990,21 @@ function coerceRefKey2(value) {
|
|
|
2098
1990
|
if (typeof value === "number" || typeof value === "bigint") return String(value);
|
|
2099
1991
|
return null;
|
|
2100
1992
|
}
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
1993
|
+
|
|
1994
|
+
export {
|
|
1995
|
+
validateMoneyFieldPaths,
|
|
1996
|
+
canonicalizeStoredMoney,
|
|
1997
|
+
canonicalizeIncomingMoney,
|
|
1998
|
+
quantizeMoneyFields,
|
|
1999
|
+
decodeMoneyFields,
|
|
2105
2000
|
DEFAULT_JOIN_MAX_ROWS,
|
|
2106
|
-
DanglingReferenceError,
|
|
2107
|
-
GROUPBY_MAX_CARDINALITY,
|
|
2108
|
-
GROUPBY_WARN_CARDINALITY,
|
|
2109
|
-
GroupCardinalityError,
|
|
2110
|
-
GroupedAggregation,
|
|
2111
|
-
GroupedQuery,
|
|
2112
|
-
GroupedQueryN,
|
|
2113
|
-
IndexRequiredError,
|
|
2114
|
-
IndexWriteFailureError,
|
|
2115
|
-
JoinTooLargeError,
|
|
2116
|
-
Query,
|
|
2117
|
-
ScanBuilder,
|
|
2118
2001
|
applyJoins,
|
|
2119
|
-
|
|
2120
|
-
buildLiveAggregation,
|
|
2002
|
+
resetJoinWarnings,
|
|
2121
2003
|
buildLiveQuery,
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2004
|
+
NO_AGGREGATE,
|
|
2005
|
+
DEFAULT_CROSS_JOIN_MAX_ROWS,
|
|
2006
|
+
Query,
|
|
2125
2007
|
executePlan,
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
readPath,
|
|
2130
|
-
reduceRecords,
|
|
2131
|
-
resetGroupByWarnings,
|
|
2132
|
-
resetJoinWarnings,
|
|
2133
|
-
sum
|
|
2134
|
-
});
|
|
2135
|
-
//# sourceMappingURL=index.cjs.map
|
|
2008
|
+
ScanBuilder
|
|
2009
|
+
};
|
|
2010
|
+
//# sourceMappingURL=chunk-QGBBQKDS.js.map
|