@noy-db/hub 0.2.0-pre.17 → 0.2.0-pre.19
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/dist/aggregate/index.cjs +227 -3
- package/dist/aggregate/index.cjs.map +1 -1
- package/dist/aggregate/index.d.cts +3 -3
- package/dist/aggregate/index.d.ts +3 -3
- package/dist/aggregate/index.js +5 -4
- package/dist/aggregate/index.js.map +1 -1
- package/dist/attestation/index.cjs.map +1 -1
- package/dist/attestation/index.d.cts +5 -5
- package/dist/attestation/index.d.ts +5 -5
- package/dist/attestation/index.js +6 -6
- package/dist/blobs/index.cjs +4 -10
- package/dist/blobs/index.cjs.map +1 -1
- package/dist/blobs/index.d.cts +6 -6
- package/dist/blobs/index.d.ts +6 -6
- package/dist/blobs/index.js +6 -6
- package/dist/bundle/index.cjs +1587 -392
- package/dist/bundle/index.cjs.map +1 -1
- package/dist/bundle/index.d.cts +7 -7
- package/dist/bundle/index.d.ts +7 -7
- package/dist/bundle/index.js +10 -10
- package/dist/{chunk-NBBMMJ2H.js → chunk-3FSMVWBN.js} +4 -4
- package/dist/{chunk-HGVSHKZW.js → chunk-3Q2AOPLT.js} +100 -29
- package/dist/chunk-3Q2AOPLT.js.map +1 -0
- package/dist/{chunk-SHX5QBCI.js → chunk-4ULLGYPA.js} +3 -3
- package/dist/{chunk-CD2AVTEM.js → chunk-5IGWRMEC.js} +5 -5
- package/dist/{chunk-QO6RGLLD.js → chunk-6KESZO5D.js} +35 -7
- package/dist/chunk-6KESZO5D.js.map +1 -0
- package/dist/{chunk-GP3SDSH2.js → chunk-6OSOE6BY.js} +15 -2
- package/dist/chunk-6OSOE6BY.js.map +1 -0
- package/dist/{chunk-F4G63NTZ.js → chunk-7C6VFNIY.js} +2 -2
- package/dist/{chunk-XJV6OB4D.js → chunk-7HD67R6U.js} +2 -2
- package/dist/{chunk-UMLVJTYV.js → chunk-ADB7GPM3.js} +7 -4
- package/dist/chunk-ADB7GPM3.js.map +1 -0
- package/dist/{chunk-NYSYPFXJ.js → chunk-B6E5IRPJ.js} +3 -3
- package/dist/chunk-CYNTFU2D.js +129 -0
- package/dist/chunk-CYNTFU2D.js.map +1 -0
- package/dist/{chunk-ZEGSDPB7.js → chunk-DJF3FXW5.js} +35 -1
- package/dist/chunk-DJF3FXW5.js.map +1 -0
- package/dist/{chunk-3G3W65EQ.js → chunk-DY3EOJEN.js} +2 -2
- package/dist/{chunk-YYVZYTWW.js → chunk-E66DSTJP.js} +3 -3
- package/dist/{chunk-5LIROIDM.js → chunk-FBLAWK6A.js} +2 -2
- package/dist/{chunk-E77UKJYL.js → chunk-FPHRTW2Z.js} +5 -5
- package/dist/{state-vault-W2OEABNO.js → chunk-G4PYA575.js} +24 -7
- package/dist/chunk-G4PYA575.js.map +1 -0
- package/dist/{chunk-U5QCMH3W.js → chunk-GKQAU52M.js} +4 -4
- package/dist/{chunk-2FU2FTXD.js → chunk-GYAWXHFO.js} +2 -2
- package/dist/{chunk-ROPJVUG3.js → chunk-H42KZXNV.js} +5 -210
- package/dist/chunk-H42KZXNV.js.map +1 -0
- package/dist/{chunk-XPIHJ34I.js → chunk-IBVTH4JR.js} +4 -4
- package/dist/{chunk-C3HYQPV4.js → chunk-IVP5IVON.js} +2 -2
- package/dist/{chunk-BL5GYANC.js → chunk-KEDJDWWQ.js} +3 -3
- package/dist/{chunk-I5IUYN7B.js → chunk-KNKNOJFS.js} +3 -3
- package/dist/chunk-KNKNOJFS.js.map +1 -0
- package/dist/{chunk-D77ZQSQQ.js → chunk-KYGGXXT6.js} +829 -170
- package/dist/chunk-KYGGXXT6.js.map +1 -0
- package/dist/{chunk-J7RWBXFY.js → chunk-LSIIPKYT.js} +2 -2
- package/dist/{chunk-BSZOCSDZ.js → chunk-M3FPNTO2.js} +4 -4
- package/dist/{chunk-XMVHEWF6.js → chunk-MI36HL5G.js} +4 -4
- package/dist/{chunk-ROVO6NPJ.js → chunk-NN6IISZO.js} +58 -3
- package/dist/chunk-NN6IISZO.js.map +1 -0
- package/dist/{chunk-7H2GEJ3O.js → chunk-OBMYMKGO.js} +29 -6
- package/dist/{chunk-7H2GEJ3O.js.map → chunk-OBMYMKGO.js.map} +1 -1
- package/dist/{chunk-UNTGHX5A.js → chunk-OKOKPYWH.js} +2 -2
- package/dist/{chunk-WV7WV6JO.js → chunk-OY7RX2VL.js} +9 -15
- package/dist/chunk-OY7RX2VL.js.map +1 -0
- package/dist/{chunk-H2MRGONI.js → chunk-PTGQPWMV.js} +2 -2
- package/dist/{chunk-BJSLBUJ7.js → chunk-PWFTQHYX.js} +2 -2
- package/dist/{chunk-5AXTH4QZ.js → chunk-Q5MCHUXZ.js} +2 -2
- package/dist/{chunk-QHM6XEAH.js → chunk-S22UOMHM.js} +6 -6
- package/dist/{chunk-WIAOUFFB.js → chunk-S3XA7G35.js} +2 -2
- package/dist/{chunk-SISBMAPO.js → chunk-SHIUFIPW.js} +1 -1
- package/dist/chunk-SHIUFIPW.js.map +1 -0
- package/dist/{chunk-KCEHMDZF.js → chunk-U7JNBSS3.js} +3 -3
- package/dist/{chunk-ZNGPEV5J.js → chunk-V3VIRTTE.js} +3 -3
- package/dist/{chunk-TIDXB5DF.js → chunk-V5FZWQNN.js} +4 -4
- package/dist/chunk-VEIVAYJ7.js +361 -0
- package/dist/chunk-VEIVAYJ7.js.map +1 -0
- package/dist/{chunk-AEIKD3PP.js → chunk-VNUE6FHP.js} +3 -3
- package/dist/{chunk-DYYYUW5D.js → chunk-WFK2EVYU.js} +10 -2
- package/dist/chunk-WFK2EVYU.js.map +1 -0
- package/dist/{chunk-XMHUK5PN.js → chunk-X7FJMKT3.js} +2 -2
- package/dist/{chunk-FEJDVE3Z.js → chunk-XPH3FWME.js} +7 -2
- package/dist/{chunk-FEJDVE3Z.js.map → chunk-XPH3FWME.js.map} +1 -1
- package/dist/{chunk-SNMJ7SB3.js → chunk-Y5J63SMF.js} +5 -5
- package/dist/{chunk-M476FOQ7.js → chunk-YLRRU72W.js} +2 -2
- package/dist/{chunk-DWEBTE2W.js → chunk-YX333DPS.js} +4 -4
- package/dist/{chunk-BH3X5L6A.js → chunk-YZE6C3TQ.js} +3 -3
- package/dist/consent/index.cjs.map +1 -1
- package/dist/consent/index.d.cts +6 -6
- package/dist/consent/index.d.ts +6 -6
- package/dist/consent/index.js +3 -3
- package/dist/{crypto-7BN2HDWG.js → crypto-B46VNH6X.js} +3 -3
- package/dist/{delegation-MGH5SODX.js → delegation-5HON72PV.js} +5 -5
- package/dist/derivations/index.cjs +82 -2
- package/dist/derivations/index.cjs.map +1 -1
- package/dist/derivations/index.d.cts +7 -7
- package/dist/derivations/index.d.ts +7 -7
- package/dist/derivations/index.js +8 -6
- package/dist/{dev-unlock-iXbYFAWl.d.cts → dev-unlock-BR1rMOS-.d.cts} +1 -1
- package/dist/{dev-unlock-CI1ijTML.d.ts → dev-unlock-whL49sxV.d.ts} +1 -1
- package/dist/{errors-Dz64FA65.d.cts → errors-DL-zTrrF.d.cts} +29 -1
- package/dist/{errors-Dz64FA65.d.ts → errors-DL-zTrrF.d.ts} +29 -1
- package/dist/executor-44R5CUS2.js +12 -0
- package/dist/executor-AOACUK7Z.js +8 -0
- package/dist/executor-OKFLQCDW.js +8 -0
- package/dist/{fanout-sidecar-FIJJ46YG.js → fanout-sidecar-DCQWJQ6S.js} +2 -2
- package/dist/forget/index.cjs.map +1 -1
- package/dist/forget/index.d.cts +1 -1
- package/dist/forget/index.d.ts +1 -1
- package/dist/forget/index.js +4 -4
- package/dist/guards/index.cjs +80 -3
- package/dist/guards/index.cjs.map +1 -1
- package/dist/guards/index.d.cts +7 -7
- package/dist/guards/index.d.ts +7 -7
- package/dist/guards/index.js +8 -4
- package/dist/{hash-tEcM5fnv.d.cts → hash-BEUBmmI4.d.cts} +1 -1
- package/dist/{hash-blk7Bkes.d.ts → hash-Dtb7FwWd.d.ts} +1 -1
- package/dist/history/index.cjs.map +1 -1
- package/dist/history/index.d.cts +7 -7
- package/dist/history/index.d.ts +7 -7
- package/dist/history/index.js +5 -5
- package/dist/i18n/index.cjs +149 -132
- package/dist/i18n/index.cjs.map +1 -1
- package/dist/i18n/index.d.cts +6 -6
- package/dist/i18n/index.d.ts +6 -6
- package/dist/i18n/index.js +14 -14
- package/dist/{index-u-kWzSrL.d.cts → index-BM7O48Ur.d.cts} +85 -9
- package/dist/{index-C-SSRIxP.d.cts → index-BMmajblo.d.cts} +14 -0
- package/dist/{index-C-SSRIxP.d.ts → index-BMmajblo.d.ts} +14 -0
- package/dist/{index-DpU6KWof.d.ts → index-BelbyUwz.d.ts} +85 -9
- package/dist/index.cjs +2206 -992
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -16
- package/dist/index.d.ts +29 -16
- package/dist/index.js +76 -54
- package/dist/index.js.map +1 -1
- package/dist/indexing/index.cjs.map +1 -1
- package/dist/indexing/index.js +4 -4
- package/dist/issue-EPA2PSWP.js +12 -0
- package/dist/{ledger-LFVLHE5H.js → ledger-LS6GXCBP.js} +5 -5
- package/dist/materialized-views/index.cjs +257 -4
- package/dist/materialized-views/index.cjs.map +1 -1
- package/dist/materialized-views/index.d.cts +7 -7
- package/dist/materialized-views/index.d.ts +7 -7
- package/dist/materialized-views/index.js +8 -7
- package/dist/noydb-BVKFP74P.js +38 -0
- package/dist/overlay-views/index.cjs.map +1 -1
- package/dist/overlay-views/index.d.cts +7 -7
- package/dist/overlay-views/index.d.ts +7 -7
- package/dist/overlay-views/index.js +4 -4
- package/dist/periods/index.cjs.map +1 -1
- package/dist/periods/index.d.cts +6 -6
- package/dist/periods/index.d.ts +6 -6
- package/dist/periods/index.js +5 -5
- package/dist/{public-envelope-RXZNP3V6.js → public-envelope-AGU6SS4Z.js} +4 -4
- package/dist/query/index.cjs +320 -28
- package/dist/query/index.cjs.map +1 -1
- package/dist/query/index.d.cts +3 -3
- package/dist/query/index.d.ts +3 -3
- package/dist/query/index.js +7 -6
- package/dist/read-only-facade-EX6WZZBP.js +7 -0
- package/dist/registry-ERNAMRDE.js +8 -0
- package/dist/registry-EXTHSXQW.js +8 -0
- package/dist/{registry-SECUWSGY.js → registry-RDPTFXQ7.js} +3 -3
- package/dist/{revoke-B54H2S2W.js → revoke-IFLXEZA5.js} +6 -6
- package/dist/sealed-record/index.cjs.map +1 -1
- package/dist/sealed-record/index.d.cts +1 -1
- package/dist/sealed-record/index.d.ts +1 -1
- package/dist/sealed-record/index.js +2 -2
- package/dist/session/index.cjs.map +1 -1
- package/dist/session/index.d.cts +7 -7
- package/dist/session/index.d.ts +7 -7
- package/dist/session/index.js +3 -3
- package/dist/shadow/index.cjs.map +1 -1
- package/dist/shadow/index.d.cts +6 -6
- package/dist/shadow/index.d.ts +6 -6
- package/dist/shadow/index.js +2 -2
- package/dist/{signer-YSXZT574.js → signer-UNWOUJAK.js} +5 -5
- package/dist/snapshots/index.cjs.map +1 -1
- package/dist/snapshots/index.d.cts +6 -6
- package/dist/snapshots/index.d.ts +6 -6
- package/dist/snapshots/index.js +4 -4
- package/dist/{stale-TOA36SRK.js → stale-NTEV5SLX.js} +2 -2
- package/dist/state-vault-TUTFRTOA.js +14 -0
- package/dist/state-vault-TUTFRTOA.js.map +1 -0
- package/dist/store/index.cjs +8 -0
- package/dist/store/index.cjs.map +1 -1
- package/dist/store/index.d.cts +13 -6
- package/dist/store/index.d.ts +13 -6
- package/dist/store/index.js +2 -2
- package/dist/{strategy-4M9jo172.d.ts → strategy-BDxQnnTX.d.ts} +315 -4
- package/dist/{strategy-CLC1j79g.d.cts → strategy-C5ol6NdV.d.cts} +315 -4
- package/dist/sync/index.cjs.map +1 -1
- package/dist/sync/index.d.cts +5 -5
- package/dist/sync/index.d.ts +5 -5
- package/dist/sync/index.js +4 -4
- package/dist/team/index.cjs.map +1 -1
- package/dist/team/index.d.cts +6 -6
- package/dist/team/index.d.ts +6 -6
- package/dist/team/index.js +8 -8
- package/dist/transition-guard-B1N82hMf.d.cts +165 -0
- package/dist/transition-guard-C__YeF3_.d.ts +165 -0
- package/dist/tx/index.cjs.map +1 -1
- package/dist/tx/index.d.cts +6 -6
- package/dist/tx/index.d.ts +6 -6
- package/dist/tx/index.js +3 -3
- package/dist/{types-CljIHm_J.d.ts → types-CraiZOyO.d.ts} +609 -305
- package/dist/{types-CrSpRDuG.d.cts → types-D-gr5t0G.d.cts} +609 -305
- package/dist/{ulid-CrI7PPbA.d.cts → ulid-DQnSAP5W.d.cts} +1 -1
- package/dist/{ulid-CWfL2Vfv.d.ts → ulid-FFRRHkVf.d.ts} +1 -1
- package/dist/util/index.cjs.map +1 -1
- package/dist/util/index.js +1 -1
- package/dist/{vault-group-DHAHFX2A.js → vault-group-27EV7KB4.js} +205 -8
- package/dist/vault-group-27EV7KB4.js.map +1 -0
- package/dist/{with-materialized-view-NzF71cG_.d.cts → with-materialized-view-BboqxyV3.d.cts} +1 -1
- package/dist/{with-materialized-view-B892zYZV.d.ts → with-materialized-view-CguCeVcT.d.ts} +1 -1
- package/dist/{with-overlayed-view-CR6m7CHe.d.ts → with-overlayed-view-DO08u_tx.d.ts} +1 -1
- package/dist/{with-overlayed-view-UI8qSGL4.d.cts → with-overlayed-view-mmsg5Of3.d.cts} +1 -1
- package/dist/with-rollup-_TyBzz3T.d.ts +47 -0
- package/dist/with-rollup-aaxOZcIb.d.cts +47 -0
- package/package.json +3 -3
- package/dist/chunk-D77ZQSQQ.js.map +0 -1
- package/dist/chunk-DYYYUW5D.js.map +0 -1
- package/dist/chunk-GP3SDSH2.js.map +0 -1
- package/dist/chunk-HGVSHKZW.js.map +0 -1
- package/dist/chunk-I5IUYN7B.js.map +0 -1
- package/dist/chunk-JDWE6JMX.js +0 -139
- package/dist/chunk-JDWE6JMX.js.map +0 -1
- package/dist/chunk-PDULVIBY.js +0 -63
- package/dist/chunk-PDULVIBY.js.map +0 -1
- package/dist/chunk-QO6RGLLD.js.map +0 -1
- package/dist/chunk-ROPJVUG3.js.map +0 -1
- package/dist/chunk-ROVO6NPJ.js.map +0 -1
- package/dist/chunk-SISBMAPO.js.map +0 -1
- package/dist/chunk-UMLVJTYV.js.map +0 -1
- package/dist/chunk-WV7WV6JO.js.map +0 -1
- package/dist/chunk-ZEGSDPB7.js.map +0 -1
- package/dist/executor-3W63Y44O.js +0 -11
- package/dist/executor-CFFWPWBJ.js +0 -8
- package/dist/executor-VDQQOR4F.js +0 -8
- package/dist/immutable-guard-B5M95nbq.d.ts +0 -82
- package/dist/immutable-guard-qN3zF8o1.d.cts +0 -82
- package/dist/issue-TTMGHQ2J.js +0 -12
- package/dist/noydb-36S6GQNC.js +0 -37
- package/dist/read-only-facade-ITU6L7BL.js +0 -7
- package/dist/registry-3YFLZ7WD.js +0 -8
- package/dist/registry-TGZISEWC.js +0 -8
- package/dist/state-vault-W2OEABNO.js.map +0 -1
- package/dist/vault-group-DHAHFX2A.js.map +0 -1
- package/dist/with-derivation-BZ2y4bzF.d.ts +0 -13
- package/dist/with-derivation-Bozs8DmD.d.cts +0 -13
- /package/dist/{chunk-NBBMMJ2H.js.map → chunk-3FSMVWBN.js.map} +0 -0
- /package/dist/{chunk-SHX5QBCI.js.map → chunk-4ULLGYPA.js.map} +0 -0
- /package/dist/{chunk-CD2AVTEM.js.map → chunk-5IGWRMEC.js.map} +0 -0
- /package/dist/{chunk-F4G63NTZ.js.map → chunk-7C6VFNIY.js.map} +0 -0
- /package/dist/{chunk-XJV6OB4D.js.map → chunk-7HD67R6U.js.map} +0 -0
- /package/dist/{chunk-NYSYPFXJ.js.map → chunk-B6E5IRPJ.js.map} +0 -0
- /package/dist/{chunk-3G3W65EQ.js.map → chunk-DY3EOJEN.js.map} +0 -0
- /package/dist/{chunk-YYVZYTWW.js.map → chunk-E66DSTJP.js.map} +0 -0
- /package/dist/{chunk-5LIROIDM.js.map → chunk-FBLAWK6A.js.map} +0 -0
- /package/dist/{chunk-E77UKJYL.js.map → chunk-FPHRTW2Z.js.map} +0 -0
- /package/dist/{chunk-U5QCMH3W.js.map → chunk-GKQAU52M.js.map} +0 -0
- /package/dist/{chunk-2FU2FTXD.js.map → chunk-GYAWXHFO.js.map} +0 -0
- /package/dist/{chunk-XPIHJ34I.js.map → chunk-IBVTH4JR.js.map} +0 -0
- /package/dist/{chunk-C3HYQPV4.js.map → chunk-IVP5IVON.js.map} +0 -0
- /package/dist/{chunk-BL5GYANC.js.map → chunk-KEDJDWWQ.js.map} +0 -0
- /package/dist/{chunk-J7RWBXFY.js.map → chunk-LSIIPKYT.js.map} +0 -0
- /package/dist/{chunk-BSZOCSDZ.js.map → chunk-M3FPNTO2.js.map} +0 -0
- /package/dist/{chunk-XMVHEWF6.js.map → chunk-MI36HL5G.js.map} +0 -0
- /package/dist/{chunk-UNTGHX5A.js.map → chunk-OKOKPYWH.js.map} +0 -0
- /package/dist/{chunk-H2MRGONI.js.map → chunk-PTGQPWMV.js.map} +0 -0
- /package/dist/{chunk-BJSLBUJ7.js.map → chunk-PWFTQHYX.js.map} +0 -0
- /package/dist/{chunk-5AXTH4QZ.js.map → chunk-Q5MCHUXZ.js.map} +0 -0
- /package/dist/{chunk-QHM6XEAH.js.map → chunk-S22UOMHM.js.map} +0 -0
- /package/dist/{chunk-WIAOUFFB.js.map → chunk-S3XA7G35.js.map} +0 -0
- /package/dist/{chunk-KCEHMDZF.js.map → chunk-U7JNBSS3.js.map} +0 -0
- /package/dist/{chunk-ZNGPEV5J.js.map → chunk-V3VIRTTE.js.map} +0 -0
- /package/dist/{chunk-TIDXB5DF.js.map → chunk-V5FZWQNN.js.map} +0 -0
- /package/dist/{chunk-AEIKD3PP.js.map → chunk-VNUE6FHP.js.map} +0 -0
- /package/dist/{chunk-XMHUK5PN.js.map → chunk-X7FJMKT3.js.map} +0 -0
- /package/dist/{chunk-SNMJ7SB3.js.map → chunk-Y5J63SMF.js.map} +0 -0
- /package/dist/{chunk-M476FOQ7.js.map → chunk-YLRRU72W.js.map} +0 -0
- /package/dist/{chunk-DWEBTE2W.js.map → chunk-YX333DPS.js.map} +0 -0
- /package/dist/{chunk-BH3X5L6A.js.map → chunk-YZE6C3TQ.js.map} +0 -0
- /package/dist/{crypto-7BN2HDWG.js.map → crypto-B46VNH6X.js.map} +0 -0
- /package/dist/{delegation-MGH5SODX.js.map → delegation-5HON72PV.js.map} +0 -0
- /package/dist/{executor-3W63Y44O.js.map → executor-44R5CUS2.js.map} +0 -0
- /package/dist/{executor-CFFWPWBJ.js.map → executor-AOACUK7Z.js.map} +0 -0
- /package/dist/{executor-VDQQOR4F.js.map → executor-OKFLQCDW.js.map} +0 -0
- /package/dist/{fanout-sidecar-FIJJ46YG.js.map → fanout-sidecar-DCQWJQ6S.js.map} +0 -0
- /package/dist/{issue-TTMGHQ2J.js.map → issue-EPA2PSWP.js.map} +0 -0
- /package/dist/{ledger-LFVLHE5H.js.map → ledger-LS6GXCBP.js.map} +0 -0
- /package/dist/{noydb-36S6GQNC.js.map → noydb-BVKFP74P.js.map} +0 -0
- /package/dist/{public-envelope-RXZNP3V6.js.map → public-envelope-AGU6SS4Z.js.map} +0 -0
- /package/dist/{read-only-facade-ITU6L7BL.js.map → read-only-facade-EX6WZZBP.js.map} +0 -0
- /package/dist/{registry-3YFLZ7WD.js.map → registry-ERNAMRDE.js.map} +0 -0
- /package/dist/{registry-SECUWSGY.js.map → registry-EXTHSXQW.js.map} +0 -0
- /package/dist/{registry-TGZISEWC.js.map → registry-RDPTFXQ7.js.map} +0 -0
- /package/dist/{revoke-B54H2S2W.js.map → revoke-IFLXEZA5.js.map} +0 -0
- /package/dist/{signer-YSXZT574.js.map → signer-UNWOUJAK.js.map} +0 -0
- /package/dist/{stale-TOA36SRK.js.map → stale-NTEV5SLX.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -46,7 +46,7 @@ var init_types = __esm({
|
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
// src/errors.ts
|
|
49
|
-
var NoydbError, DecryptionError, TamperedError, InvalidKeyError, KeyringCorruptError, NoAccessError, ReadOnlyError, ReadOnlyAtInstantError, ReadOnlyFrameError, PermissionDeniedError, ExportCapabilityError, KeyringExpiredError, ImportCapabilityError, StoreCapabilityError, PrivilegeEscalationError, ReservedVaultNameError, PeriodClosedError, RecordLockedError, FieldFrozenError, InvariantError, AmendmentForbiddenError, DirectoryDisabledError, TierNotGrantedError, ElevationExpiredError, AlreadyElevatedError, TierDemoteDeniedError, DelegationTargetMissingError, ConflictError, LedgerContentionError, SequenceContentionError, SequenceOfflineError, NumberingUncertaintyError, BundleVersionConflictError, NetworkError, NotFoundError, ValidationError, SchemaValidationError, SchemaUpdateError, NonAdditiveSchemaChangeError, SchemaLockedError, SchemaFenceError, MigrationRequiredError, QuiesceTimeoutError, GroupCardinalityError, IndexRequiredError, UniqueConstraintError, UnsupportedIndexOptionError, IndexWriteFailureError, BundleIntegrityError, BundleSealMismatchError, ReservedCollectionNameError, DictKeyMissingError, DictKeyInUseError, MissingTranslationError, LocaleNotSpecifiedError, ScriptViolationError, StaticDictReadonlyError, UnknownDictCodeError, TranslatorNotConfiguredError, BackupLedgerError, BackupCorruptedError, AttestationError, SessionExpiredError, SessionNotFoundError, SessionPolicyError, JoinTooLargeError, CrossJoinTooLargeError, CrossJoinSourceUnknownError, DanglingReferenceError, FilenameSanitizationError, PathEscapeError, DerivationCycleError, DerivationDepthError, DerivationOutputUnknownError, DerivationOutputShapeError, DerivationCapExceededError, MaterializedViewCycleError, MaterializedViewSourceUnknownError, MaterializedViewTooLargeError, MaterializedViewConfigError, OverlayBaseIsVirtualError, OverlayCollectionUnavailableError, OverlayNameCollisionError, OverlayIdMismatchError, SnapshotNotFoundError, UnknownShardError, ShardProvisioningError, CrossShardJoinError, VaultTemplateNotFoundError, ForgetStrategyNotConfiguredError, SealedRecordExpiredError, SealedRecordMismatchError, RecordCekNotFoundError;
|
|
49
|
+
var NoydbError, DecryptionError, TamperedError, InvalidKeyError, KeyringCorruptError, NoAccessError, ReadOnlyError, ReadOnlyAtInstantError, ReadOnlyFrameError, PermissionDeniedError, ExportCapabilityError, KeyringExpiredError, ImportCapabilityError, StoreCapabilityError, PrivilegeEscalationError, ReservedVaultNameError, PeriodClosedError, RecordLockedError, FieldFrozenError, IllegalTransitionError, InvariantError, AmendmentForbiddenError, DirectoryDisabledError, TierNotGrantedError, ElevationExpiredError, AlreadyElevatedError, TierDemoteDeniedError, DelegationTargetMissingError, ConflictError, LedgerContentionError, SequenceContentionError, SequenceOfflineError, NumberingUncertaintyError, BundleVersionConflictError, NetworkError, NotFoundError, ValidationError, SchemaValidationError, SchemaUpdateError, NonAdditiveSchemaChangeError, SchemaLockedError, SchemaFenceError, MigrationRequiredError, QuiesceTimeoutError, GroupCardinalityError, IndexRequiredError, UniqueConstraintError, UnsupportedIndexOptionError, IndexWriteFailureError, BundleIntegrityError, BundleSealMismatchError, ReservedCollectionNameError, DictKeyMissingError, DictKeyInUseError, MissingTranslationError, LocaleNotSpecifiedError, ScriptViolationError, StaticDictReadonlyError, UnknownDictCodeError, TranslatorNotConfiguredError, BackupLedgerError, BackupCorruptedError, AttestationError, SessionExpiredError, SessionNotFoundError, SessionPolicyError, JoinTooLargeError, CrossJoinTooLargeError, CrossJoinSourceUnknownError, DanglingReferenceError, FilenameSanitizationError, PathEscapeError, DerivationCycleError, DerivationDepthError, DerivationOutputUnknownError, DerivationOutputShapeError, DerivationCapExceededError, MaterializedViewCycleError, MaterializedViewSourceUnknownError, MaterializedViewTooLargeError, MaterializedViewConfigError, OverlayBaseIsVirtualError, OverlayCollectionUnavailableError, OverlayNameCollisionError, OverlayIdMismatchError, SnapshotNotFoundError, UnknownShardError, ShardProvisioningError, DataResidencyError, CrossShardJoinError, VaultTemplateNotFoundError, ForgetStrategyNotConfiguredError, SealedRecordExpiredError, SealedRecordMismatchError, RecordCekNotFoundError;
|
|
50
50
|
var init_errors = __esm({
|
|
51
51
|
"src/errors.ts"() {
|
|
52
52
|
"use strict";
|
|
@@ -245,6 +245,23 @@ var init_errors = __esm({
|
|
|
245
245
|
this.fields = fields;
|
|
246
246
|
}
|
|
247
247
|
};
|
|
248
|
+
IllegalTransitionError = class extends NoydbError {
|
|
249
|
+
collection;
|
|
250
|
+
id;
|
|
251
|
+
from;
|
|
252
|
+
to;
|
|
253
|
+
constructor(collection, id, from, to) {
|
|
254
|
+
super(
|
|
255
|
+
"ILLEGAL_TRANSITION",
|
|
256
|
+
`Cannot transition ${collection}/${id} from "${from}" to "${to}" \u2014 not a declared arc. Use withTransactions({ amendment: true, reason }) with admin/owner role to override.`
|
|
257
|
+
);
|
|
258
|
+
this.name = "IllegalTransitionError";
|
|
259
|
+
this.collection = collection;
|
|
260
|
+
this.id = id;
|
|
261
|
+
this.from = from;
|
|
262
|
+
this.to = to;
|
|
263
|
+
}
|
|
264
|
+
};
|
|
248
265
|
InvariantError = class extends NoydbError {
|
|
249
266
|
constructor(message) {
|
|
250
267
|
super("INVARIANT_VIOLATED", message);
|
|
@@ -496,14 +513,14 @@ var init_errors = __esm({
|
|
|
496
513
|
recordId;
|
|
497
514
|
fields;
|
|
498
515
|
conflictingId;
|
|
499
|
-
constructor(collection,
|
|
516
|
+
constructor(collection, recordId4, fields, conflictingId) {
|
|
500
517
|
super(
|
|
501
518
|
"UNIQUE_CONSTRAINT",
|
|
502
|
-
`Unique constraint on ${collection}.[${fields.join(", ")}] violated: record "${
|
|
519
|
+
`Unique constraint on ${collection}.[${fields.join(", ")}] violated: record "${recordId4}" duplicates a value already held by "${conflictingId}".`
|
|
503
520
|
);
|
|
504
521
|
this.name = "UniqueConstraintError";
|
|
505
522
|
this.collection = collection;
|
|
506
|
-
this.recordId =
|
|
523
|
+
this.recordId = recordId4;
|
|
507
524
|
this.fields = fields;
|
|
508
525
|
this.conflictingId = conflictingId;
|
|
509
526
|
}
|
|
@@ -1018,6 +1035,21 @@ Resolutions:
|
|
|
1018
1035
|
this.vaultId = vaultId;
|
|
1019
1036
|
}
|
|
1020
1037
|
};
|
|
1038
|
+
DataResidencyError = class extends NoydbError {
|
|
1039
|
+
vaultId;
|
|
1040
|
+
requiredRegion;
|
|
1041
|
+
backendRegion;
|
|
1042
|
+
constructor(vaultId, requiredRegion, backendRegion) {
|
|
1043
|
+
super(
|
|
1044
|
+
"DATA_RESIDENCY",
|
|
1045
|
+
`Shard "${vaultId}" requires region "${requiredRegion}" but its placement backend declares region ${backendRegion === void 0 ? "(none)" : `"${backendRegion}"`}. Refusing to provision \u2014 route this shard to a region-correct backend via routeStore({ vaultRoutes }) (e.g. a region-encoded partition key) before retrying.`
|
|
1046
|
+
);
|
|
1047
|
+
this.name = "DataResidencyError";
|
|
1048
|
+
this.vaultId = vaultId;
|
|
1049
|
+
this.requiredRegion = requiredRegion;
|
|
1050
|
+
this.backendRegion = backendRegion;
|
|
1051
|
+
}
|
|
1052
|
+
};
|
|
1021
1053
|
CrossShardJoinError = class extends NoydbError {
|
|
1022
1054
|
constructor(message) {
|
|
1023
1055
|
super("CROSS_SHARD_JOIN", message);
|
|
@@ -2422,196 +2454,558 @@ var init_public_envelope = __esm({
|
|
|
2422
2454
|
}
|
|
2423
2455
|
});
|
|
2424
2456
|
|
|
2425
|
-
// src/
|
|
2426
|
-
function
|
|
2427
|
-
const
|
|
2428
|
-
|
|
2429
|
-
const
|
|
2430
|
-
|
|
2431
|
-
const frac = m[3] ?? "";
|
|
2432
|
-
const exp = Number(m[4]);
|
|
2433
|
-
const digits = intp + frac;
|
|
2434
|
-
const pointPos = intp.length + exp;
|
|
2435
|
-
let body;
|
|
2436
|
-
if (pointPos <= 0) {
|
|
2437
|
-
body = "0." + "0".repeat(-pointPos) + digits;
|
|
2438
|
-
} else if (pointPos >= digits.length) {
|
|
2439
|
-
body = digits + "0".repeat(pointPos - digits.length);
|
|
2440
|
-
} else {
|
|
2441
|
-
body = digits.slice(0, pointPos) + "." + digits.slice(pointPos);
|
|
2442
|
-
}
|
|
2443
|
-
return sign + body;
|
|
2457
|
+
// src/i18n/policy.ts
|
|
2458
|
+
function resolvePolicy(onMissing, layer) {
|
|
2459
|
+
const explicit = onMissing && typeof onMissing === "object" ? onMissing[layer] : void 0;
|
|
2460
|
+
const scalar = typeof onMissing === "string" ? onMissing : void 0;
|
|
2461
|
+
const layerDefault = layer === "guard" ? "substitute" : void 0;
|
|
2462
|
+
return explicit ?? layerDefault ?? scalar ?? "throw";
|
|
2444
2463
|
}
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
if (!Number.isFinite(input)) return null;
|
|
2449
|
-
s = String(input);
|
|
2450
|
-
} else {
|
|
2451
|
-
s = input.trim();
|
|
2464
|
+
var init_policy = __esm({
|
|
2465
|
+
"src/i18n/policy.ts"() {
|
|
2466
|
+
"use strict";
|
|
2452
2467
|
}
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2468
|
+
});
|
|
2469
|
+
|
|
2470
|
+
// src/i18n/script.ts
|
|
2471
|
+
function inferScripts(locale) {
|
|
2472
|
+
const parts = locale.split("-");
|
|
2473
|
+
const subtag = parts.find((t) => /^[A-Z][a-z]{3}$/.test(t));
|
|
2474
|
+
if (subtag && SUBTAG_SCRIPTS[subtag]) return SUBTAG_SCRIPTS[subtag];
|
|
2475
|
+
const base = (parts[0] ?? "").toLowerCase();
|
|
2476
|
+
if (LATIN_BASE.has(base)) return ["Latin"];
|
|
2477
|
+
const primary = SCRIPT_TABLE[base];
|
|
2478
|
+
if (primary) return [...primary, "Latin"];
|
|
2479
|
+
return ["Latin"];
|
|
2457
2480
|
}
|
|
2458
|
-
function
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
return false;
|
|
2464
|
-
case "ceil":
|
|
2465
|
-
return !negative;
|
|
2466
|
-
case "floor":
|
|
2467
|
-
return negative;
|
|
2468
|
-
case "half-up":
|
|
2469
|
-
return firstDiscarded >= 5;
|
|
2470
|
-
case "half-down":
|
|
2471
|
-
return firstDiscarded > 5 || firstDiscarded === 5 && hasMoreNonZeroAfterFirst;
|
|
2472
|
-
case "half-even":
|
|
2473
|
-
if (firstDiscarded > 5) return true;
|
|
2474
|
-
if (firstDiscarded < 5) return false;
|
|
2475
|
-
return hasMoreNonZeroAfterFirst || lastKeptDigit % 2 === 1;
|
|
2481
|
+
function allowedFor(descriptor, locale) {
|
|
2482
|
+
const script = descriptor.options.script;
|
|
2483
|
+
if (script && script !== "auto") {
|
|
2484
|
+
const explicit = script[locale];
|
|
2485
|
+
if (explicit) return explicit;
|
|
2476
2486
|
}
|
|
2487
|
+
return inferScripts(locale);
|
|
2477
2488
|
}
|
|
2478
|
-
function
|
|
2479
|
-
const
|
|
2480
|
-
|
|
2481
|
-
const negative = canonical2.startsWith("-");
|
|
2482
|
-
const unsigned = negative ? canonical2.slice(1) : canonical2;
|
|
2483
|
-
const dot = unsigned.indexOf(".");
|
|
2484
|
-
const intPart = dot === -1 ? unsigned : unsigned.slice(0, dot);
|
|
2485
|
-
const fracPart = dot === -1 ? "" : unsigned.slice(dot + 1);
|
|
2486
|
-
const intDigits = intPart === "" ? "0" : intPart;
|
|
2487
|
-
if (fracPart.length <= scale) {
|
|
2488
|
-
const keep2 = fracPart.padEnd(scale, "0");
|
|
2489
|
-
const magnitude2 = BigInt(intDigits + keep2);
|
|
2490
|
-
return { ok: true, value: negative && magnitude2 !== 0n ? -magnitude2 : magnitude2 };
|
|
2491
|
-
}
|
|
2492
|
-
const keep = fracPart.slice(0, scale);
|
|
2493
|
-
const tail = fracPart.slice(scale);
|
|
2494
|
-
const magnitudeDigits = intDigits + keep;
|
|
2495
|
-
let magnitude = BigInt(magnitudeDigits);
|
|
2496
|
-
if (/^0+$/.test(tail)) {
|
|
2497
|
-
return { ok: true, value: negative && magnitude !== 0n ? -magnitude : magnitude };
|
|
2498
|
-
}
|
|
2499
|
-
if (rounding === void 0) return { ok: false, reason: "precision" };
|
|
2500
|
-
const lastKeptDigit = Number(magnitudeDigits[magnitudeDigits.length - 1]);
|
|
2501
|
-
const firstDiscarded = Number(tail[0]);
|
|
2502
|
-
const hasMoreNonZeroAfterFirst = /[1-9]/.test(tail.slice(1));
|
|
2503
|
-
if (shouldRoundUp(negative, lastKeptDigit, firstDiscarded, hasMoreNonZeroAfterFirst, rounding)) {
|
|
2504
|
-
magnitude += 1n;
|
|
2505
|
-
}
|
|
2506
|
-
return { ok: true, value: negative && magnitude !== 0n ? -magnitude : magnitude };
|
|
2489
|
+
function fullMatcher(scripts) {
|
|
2490
|
+
const cls = scripts.map((s) => `\\p{Script=${s}}`).join("");
|
|
2491
|
+
return new RegExp(`^[${BASELINE}${cls}]*$`, "u");
|
|
2507
2492
|
}
|
|
2508
|
-
function
|
|
2509
|
-
const
|
|
2510
|
-
|
|
2511
|
-
const dot = s.indexOf(".");
|
|
2512
|
-
return dot === -1 ? 0 : s.length - dot - 1;
|
|
2493
|
+
function charMatcher(scripts) {
|
|
2494
|
+
const cls = scripts.map((s) => `\\p{Script=${s}}`).join("");
|
|
2495
|
+
return new RegExp(`[${BASELINE}${cls}]`, "u");
|
|
2513
2496
|
}
|
|
2514
|
-
function
|
|
2515
|
-
|
|
2516
|
-
const
|
|
2517
|
-
const
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
const tail = absStr.slice(absStr.length - drop);
|
|
2521
|
-
let magnitude = BigInt(keptStr);
|
|
2522
|
-
if (!/^0+$/.test(tail)) {
|
|
2523
|
-
const lastKeptDigit = Number(keptStr[keptStr.length - 1]);
|
|
2524
|
-
const firstDiscarded = Number(tail[0]);
|
|
2525
|
-
const hasMoreNonZeroAfterFirst = /[1-9]/.test(tail.slice(1));
|
|
2526
|
-
if (shouldRoundUp(negative, lastKeptDigit, firstDiscarded, hasMoreNonZeroAfterFirst, rounding)) {
|
|
2527
|
-
magnitude += 1n;
|
|
2528
|
-
}
|
|
2497
|
+
function offendingSample(str, scripts) {
|
|
2498
|
+
const ok = charMatcher(scripts);
|
|
2499
|
+
const bad = [];
|
|
2500
|
+
for (const ch of str) {
|
|
2501
|
+
if (!ok.test(ch)) bad.push(ch);
|
|
2502
|
+
if (bad.length >= 8) break;
|
|
2529
2503
|
}
|
|
2530
|
-
return
|
|
2504
|
+
return bad.join("");
|
|
2531
2505
|
}
|
|
2532
|
-
function
|
|
2533
|
-
const
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
const cut = padded.length - scale;
|
|
2538
|
-
const intPart = padded.slice(0, cut);
|
|
2539
|
-
const fracPart = padded.slice(cut);
|
|
2540
|
-
return (negative ? "-" : "") + intPart + "." + fracPart;
|
|
2506
|
+
function stripDisallowed(str, scripts) {
|
|
2507
|
+
const ok = charMatcher(scripts);
|
|
2508
|
+
let out = "";
|
|
2509
|
+
for (const ch of str) if (ok.test(ch)) out += ch;
|
|
2510
|
+
return out;
|
|
2541
2511
|
}
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2512
|
+
function enforceScript(value, field, descriptor) {
|
|
2513
|
+
const opt = descriptor.options;
|
|
2514
|
+
if (!opt.script) return { value, warnings: [] };
|
|
2515
|
+
const mode = opt.onScriptViolation ?? "reject";
|
|
2516
|
+
const warnings = [];
|
|
2517
|
+
let out = value;
|
|
2518
|
+
for (const [locale, raw] of Object.entries(value)) {
|
|
2519
|
+
if (typeof raw !== "string") continue;
|
|
2520
|
+
const allowed = allowedFor(descriptor, locale);
|
|
2521
|
+
if (fullMatcher(allowed).test(raw)) continue;
|
|
2522
|
+
const sample = offendingSample(raw, allowed);
|
|
2523
|
+
if (mode === "reject") {
|
|
2524
|
+
throw new ScriptViolationError(field, locale, allowed, sample);
|
|
2525
|
+
}
|
|
2526
|
+
warnings.push({ field, locale, expected: allowed, sample });
|
|
2527
|
+
if (mode === "filter") {
|
|
2528
|
+
if (out === value) out = { ...value };
|
|
2529
|
+
out[locale] = stripDisallowed(raw, allowed);
|
|
2530
|
+
}
|
|
2545
2531
|
}
|
|
2546
|
-
}
|
|
2547
|
-
|
|
2548
|
-
// src/money/iso4217.ts
|
|
2549
|
-
function scaleForCurrency(code) {
|
|
2550
|
-
const v = MINOR_UNITS[code];
|
|
2551
|
-
return v === void 0 ? null : v;
|
|
2532
|
+
return { value: out, warnings };
|
|
2552
2533
|
}
|
|
2553
|
-
var
|
|
2554
|
-
var
|
|
2555
|
-
"src/
|
|
2534
|
+
var LATIN_BASE, SCRIPT_TABLE, SUBTAG_SCRIPTS, BASELINE;
|
|
2535
|
+
var init_script = __esm({
|
|
2536
|
+
"src/i18n/script.ts"() {
|
|
2556
2537
|
"use strict";
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2538
|
+
init_errors();
|
|
2539
|
+
LATIN_BASE = /* @__PURE__ */ new Set([
|
|
2540
|
+
"en",
|
|
2541
|
+
"fr",
|
|
2542
|
+
"de",
|
|
2543
|
+
"es",
|
|
2544
|
+
"it",
|
|
2545
|
+
"pt",
|
|
2546
|
+
"nl",
|
|
2547
|
+
"sv",
|
|
2548
|
+
"no",
|
|
2549
|
+
"da",
|
|
2550
|
+
"fi",
|
|
2551
|
+
"is",
|
|
2552
|
+
"pl",
|
|
2553
|
+
"cs",
|
|
2554
|
+
"sk",
|
|
2555
|
+
"hu",
|
|
2556
|
+
"ro",
|
|
2557
|
+
"hr",
|
|
2558
|
+
"sl",
|
|
2559
|
+
"et",
|
|
2560
|
+
"lv",
|
|
2561
|
+
"lt",
|
|
2562
|
+
"tr",
|
|
2563
|
+
"vi",
|
|
2564
|
+
"id",
|
|
2565
|
+
"ms",
|
|
2566
|
+
"tl",
|
|
2567
|
+
"sw",
|
|
2568
|
+
"af",
|
|
2569
|
+
"ca",
|
|
2570
|
+
"gl",
|
|
2571
|
+
"eu",
|
|
2572
|
+
"cy",
|
|
2573
|
+
"ga"
|
|
2574
|
+
]);
|
|
2575
|
+
SCRIPT_TABLE = {
|
|
2576
|
+
th: ["Thai"],
|
|
2577
|
+
ko: ["Hangul", "Han"],
|
|
2578
|
+
ja: ["Han", "Hiragana", "Katakana"],
|
|
2579
|
+
zh: ["Han"],
|
|
2580
|
+
ar: ["Arabic"],
|
|
2581
|
+
fa: ["Arabic"],
|
|
2582
|
+
ur: ["Arabic"],
|
|
2583
|
+
ru: ["Cyrillic"],
|
|
2584
|
+
uk: ["Cyrillic"],
|
|
2585
|
+
bg: ["Cyrillic"],
|
|
2586
|
+
sr: ["Cyrillic"],
|
|
2587
|
+
he: ["Hebrew"],
|
|
2588
|
+
el: ["Greek"],
|
|
2589
|
+
hi: ["Devanagari"],
|
|
2590
|
+
ta: ["Tamil"],
|
|
2591
|
+
km: ["Khmer"],
|
|
2592
|
+
lo: ["Lao"],
|
|
2593
|
+
my: ["Myanmar"]
|
|
2594
|
+
};
|
|
2595
|
+
SUBTAG_SCRIPTS = {
|
|
2596
|
+
Latn: ["Latin"],
|
|
2597
|
+
Cyrl: ["Cyrillic", "Latin"],
|
|
2598
|
+
Hans: ["Han", "Latin"],
|
|
2599
|
+
Hant: ["Han", "Latin"],
|
|
2600
|
+
Thai: ["Thai", "Latin"],
|
|
2601
|
+
Arab: ["Arabic", "Latin"]
|
|
2608
2602
|
};
|
|
2603
|
+
BASELINE = String.raw`\p{White_Space}\p{Script=Common}\p{Script=Inherited}\p{Mark}`;
|
|
2609
2604
|
}
|
|
2610
2605
|
});
|
|
2611
2606
|
|
|
2612
|
-
// src/
|
|
2613
|
-
function
|
|
2614
|
-
return
|
|
2607
|
+
// src/i18n/core.ts
|
|
2608
|
+
function i18nText(options) {
|
|
2609
|
+
return { _noydbI18nText: true, options };
|
|
2610
|
+
}
|
|
2611
|
+
function isI18nTextDescriptor(x) {
|
|
2612
|
+
return typeof x === "object" && x !== null && x._noydbI18nText === true;
|
|
2613
|
+
}
|
|
2614
|
+
function validateI18nTextValue(value, field, descriptor) {
|
|
2615
|
+
const { options } = descriptor;
|
|
2616
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
2617
|
+
throw new MissingTranslationError(
|
|
2618
|
+
field,
|
|
2619
|
+
options.languages,
|
|
2620
|
+
`Field "${field}" must be a { [locale]: string } map, got ${typeof value}.`
|
|
2621
|
+
);
|
|
2622
|
+
}
|
|
2623
|
+
const map = value;
|
|
2624
|
+
for (const [locale, v] of Object.entries(map)) {
|
|
2625
|
+
if (typeof v !== "string") {
|
|
2626
|
+
throw new MissingTranslationError(
|
|
2627
|
+
field,
|
|
2628
|
+
[locale],
|
|
2629
|
+
`Field "${field}": locale "${locale}" must be a string, got ${typeof v}.`
|
|
2630
|
+
);
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
const { required } = options;
|
|
2634
|
+
if (required === "all") {
|
|
2635
|
+
const missing = options.languages.filter(
|
|
2636
|
+
(lang) => !(lang in map) || map[lang] === ""
|
|
2637
|
+
);
|
|
2638
|
+
if (missing.length > 0) {
|
|
2639
|
+
throw new MissingTranslationError(
|
|
2640
|
+
field,
|
|
2641
|
+
missing,
|
|
2642
|
+
`Field "${field}" requires all declared languages. Missing: ${missing.join(", ")}.`
|
|
2643
|
+
);
|
|
2644
|
+
}
|
|
2645
|
+
} else if (required === "any") {
|
|
2646
|
+
const present = options.languages.some(
|
|
2647
|
+
(lang) => lang in map && map[lang] !== ""
|
|
2648
|
+
);
|
|
2649
|
+
if (!present) {
|
|
2650
|
+
throw new MissingTranslationError(
|
|
2651
|
+
field,
|
|
2652
|
+
options.languages,
|
|
2653
|
+
`Field "${field}" requires at least one declared language. None present.`
|
|
2654
|
+
);
|
|
2655
|
+
}
|
|
2656
|
+
} else {
|
|
2657
|
+
const requiredList = required;
|
|
2658
|
+
const missing = requiredList.filter(
|
|
2659
|
+
(lang) => !(lang in map) || map[lang] === ""
|
|
2660
|
+
);
|
|
2661
|
+
if (missing.length > 0) {
|
|
2662
|
+
throw new MissingTranslationError(
|
|
2663
|
+
field,
|
|
2664
|
+
missing,
|
|
2665
|
+
`Field "${field}" requires: ${requiredList.join(", ")}. Missing: ${missing.join(", ")}.`
|
|
2666
|
+
);
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
function toChain(fallback) {
|
|
2671
|
+
return Array.isArray(fallback) ? fallback : fallback ? [fallback] : [];
|
|
2672
|
+
}
|
|
2673
|
+
function pickFromChain(value, chain) {
|
|
2674
|
+
for (const fb of chain) {
|
|
2675
|
+
if (fb === "any") {
|
|
2676
|
+
const any = Object.values(value).find((v) => v !== "");
|
|
2677
|
+
if (any !== void 0) return any;
|
|
2678
|
+
} else if (value[fb] !== void 0 && value[fb] !== "") {
|
|
2679
|
+
return value[fb];
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
return void 0;
|
|
2683
|
+
}
|
|
2684
|
+
function resolveI18nText(value, locale, fallback, field, opts) {
|
|
2685
|
+
if (locale === "raw") {
|
|
2686
|
+
return value;
|
|
2687
|
+
}
|
|
2688
|
+
if (!locale) {
|
|
2689
|
+
throw new LocaleNotSpecifiedError(field ?? "<unknown>");
|
|
2690
|
+
}
|
|
2691
|
+
if (value[locale] !== void 0 && value[locale] !== "") {
|
|
2692
|
+
return value[locale];
|
|
2693
|
+
}
|
|
2694
|
+
const policy = opts?.policy ?? "throw";
|
|
2695
|
+
const callerChain = toChain(fallback);
|
|
2696
|
+
const callerHit = pickFromChain(value, callerChain);
|
|
2697
|
+
if (callerHit !== void 0) return callerHit;
|
|
2698
|
+
if (policy === "substitute") {
|
|
2699
|
+
const subHit = pickFromChain(value, toChain(opts?.substitute));
|
|
2700
|
+
if (subHit !== void 0) return subHit;
|
|
2701
|
+
if (opts?.smartSubstitute) {
|
|
2702
|
+
const smartHit = pickNearestScript(value, locale);
|
|
2703
|
+
if (smartHit !== void 0) return smartHit;
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
if (policy === "throw") {
|
|
2707
|
+
throw new LocaleNotSpecifiedError(
|
|
2708
|
+
field ?? "<unknown>",
|
|
2709
|
+
`No translation available for locale "${locale}"` + (callerChain.length > 0 ? ` or fallback chain [${callerChain.join(", ")}]` : "") + "."
|
|
2710
|
+
);
|
|
2711
|
+
}
|
|
2712
|
+
return null;
|
|
2713
|
+
}
|
|
2714
|
+
function pickNearestScript(value, target) {
|
|
2715
|
+
const targetScript = inferScripts(target)[0] ?? "Latin";
|
|
2716
|
+
let best;
|
|
2717
|
+
for (const [loc, v] of Object.entries(value)) {
|
|
2718
|
+
if (typeof v !== "string" || v === "") continue;
|
|
2719
|
+
const s = inferScripts(loc)[0] ?? "Latin";
|
|
2720
|
+
const score = s === targetScript ? 0 : s === "Latin" ? 1 : 2;
|
|
2721
|
+
if (best === void 0 || score < best.score) best = { score, v };
|
|
2722
|
+
if (score === 0) break;
|
|
2723
|
+
}
|
|
2724
|
+
return best?.v;
|
|
2725
|
+
}
|
|
2726
|
+
function getAtPath(obj, path) {
|
|
2727
|
+
const arrayIdx = path.indexOf("[].");
|
|
2728
|
+
if (arrayIdx !== -1) {
|
|
2729
|
+
const arrayKey = path.slice(0, arrayIdx);
|
|
2730
|
+
const restPath = path.slice(arrayIdx + 3);
|
|
2731
|
+
const arr = obj[arrayKey];
|
|
2732
|
+
if (!Array.isArray(arr)) return [];
|
|
2733
|
+
return arr.flatMap((item) => {
|
|
2734
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return [];
|
|
2735
|
+
return getAtPath(item, restPath);
|
|
2736
|
+
});
|
|
2737
|
+
}
|
|
2738
|
+
const dotIdx = path.indexOf(".");
|
|
2739
|
+
if (dotIdx !== -1) {
|
|
2740
|
+
const head = path.slice(0, dotIdx);
|
|
2741
|
+
const rest = path.slice(dotIdx + 1);
|
|
2742
|
+
const nested = obj[head];
|
|
2743
|
+
if (!nested || typeof nested !== "object" || Array.isArray(nested)) return [];
|
|
2744
|
+
return getAtPath(nested, rest);
|
|
2745
|
+
}
|
|
2746
|
+
const val = obj[path];
|
|
2747
|
+
return val !== void 0 ? [val] : [];
|
|
2748
|
+
}
|
|
2749
|
+
function setAtPathInPlace(obj, path, value) {
|
|
2750
|
+
const dotIdx = path.indexOf(".");
|
|
2751
|
+
if (dotIdx !== -1) {
|
|
2752
|
+
const head = path.slice(0, dotIdx);
|
|
2753
|
+
const rest = path.slice(dotIdx + 1);
|
|
2754
|
+
const nested = obj[head];
|
|
2755
|
+
if (!nested || typeof nested !== "object" || Array.isArray(nested)) return;
|
|
2756
|
+
setAtPathInPlace(nested, rest, value);
|
|
2757
|
+
return;
|
|
2758
|
+
}
|
|
2759
|
+
obj[path] = value;
|
|
2760
|
+
}
|
|
2761
|
+
function applyAtPath(obj, path, locale, fallback, opts) {
|
|
2762
|
+
const arrayIdx = path.indexOf("[].");
|
|
2763
|
+
if (arrayIdx !== -1) {
|
|
2764
|
+
const arrayKey = path.slice(0, arrayIdx);
|
|
2765
|
+
const restPath = path.slice(arrayIdx + 3);
|
|
2766
|
+
const arr = obj[arrayKey];
|
|
2767
|
+
if (!Array.isArray(arr)) return obj;
|
|
2768
|
+
return {
|
|
2769
|
+
...obj,
|
|
2770
|
+
[arrayKey]: arr.map((item) => {
|
|
2771
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return item;
|
|
2772
|
+
return applyAtPath(item, restPath, locale, fallback, opts);
|
|
2773
|
+
})
|
|
2774
|
+
};
|
|
2775
|
+
}
|
|
2776
|
+
const dotIdx = path.indexOf(".");
|
|
2777
|
+
if (dotIdx !== -1) {
|
|
2778
|
+
const head = path.slice(0, dotIdx);
|
|
2779
|
+
const rest = path.slice(dotIdx + 1);
|
|
2780
|
+
const nested = obj[head];
|
|
2781
|
+
if (!nested || typeof nested !== "object" || Array.isArray(nested)) return obj;
|
|
2782
|
+
return {
|
|
2783
|
+
...obj,
|
|
2784
|
+
[head]: applyAtPath(nested, rest, locale, fallback, opts)
|
|
2785
|
+
};
|
|
2786
|
+
}
|
|
2787
|
+
const raw = obj[path];
|
|
2788
|
+
if (raw === void 0 || raw === null) return obj;
|
|
2789
|
+
if (typeof raw !== "object" || Array.isArray(raw)) return obj;
|
|
2790
|
+
return {
|
|
2791
|
+
...obj,
|
|
2792
|
+
[path]: resolveI18nText(raw, locale, fallback, path, opts)
|
|
2793
|
+
};
|
|
2794
|
+
}
|
|
2795
|
+
function applyI18nLocale(record, i18nFields, locale, fallback, layer = "read") {
|
|
2796
|
+
const fieldNames = Object.keys(i18nFields);
|
|
2797
|
+
if (fieldNames.length === 0) return record;
|
|
2798
|
+
let result = record;
|
|
2799
|
+
for (const [field, descriptor] of Object.entries(i18nFields)) {
|
|
2800
|
+
const { onMissing, substitute, smartSubstitute } = descriptor.options;
|
|
2801
|
+
const opts = {
|
|
2802
|
+
policy: resolvePolicy(onMissing, layer),
|
|
2803
|
+
...substitute !== void 0 ? { substitute } : {},
|
|
2804
|
+
...smartSubstitute ? { smartSubstitute } : {}
|
|
2805
|
+
};
|
|
2806
|
+
result = applyAtPath(result, field, locale, fallback, opts);
|
|
2807
|
+
}
|
|
2808
|
+
return result;
|
|
2809
|
+
}
|
|
2810
|
+
var init_core = __esm({
|
|
2811
|
+
"src/i18n/core.ts"() {
|
|
2812
|
+
"use strict";
|
|
2813
|
+
init_errors();
|
|
2814
|
+
init_policy();
|
|
2815
|
+
init_script();
|
|
2816
|
+
}
|
|
2817
|
+
});
|
|
2818
|
+
|
|
2819
|
+
// src/money/fixed-point.ts
|
|
2820
|
+
function expandExponent(s) {
|
|
2821
|
+
const m = /^([+-]?)(\d+)(?:\.(\d+))?[eE]([+-]?\d+)$/.exec(s);
|
|
2822
|
+
if (!m) return s;
|
|
2823
|
+
const sign = m[1] === "-" ? "-" : "";
|
|
2824
|
+
const intp = m[2];
|
|
2825
|
+
const frac = m[3] ?? "";
|
|
2826
|
+
const exp = Number(m[4]);
|
|
2827
|
+
const digits = intp + frac;
|
|
2828
|
+
const pointPos = intp.length + exp;
|
|
2829
|
+
let body;
|
|
2830
|
+
if (pointPos <= 0) {
|
|
2831
|
+
body = "0." + "0".repeat(-pointPos) + digits;
|
|
2832
|
+
} else if (pointPos >= digits.length) {
|
|
2833
|
+
body = digits + "0".repeat(pointPos - digits.length);
|
|
2834
|
+
} else {
|
|
2835
|
+
body = digits.slice(0, pointPos) + "." + digits.slice(pointPos);
|
|
2836
|
+
}
|
|
2837
|
+
return sign + body;
|
|
2838
|
+
}
|
|
2839
|
+
function toCanonicalDecimalString(input) {
|
|
2840
|
+
let s;
|
|
2841
|
+
if (typeof input === "number") {
|
|
2842
|
+
if (!Number.isFinite(input)) return null;
|
|
2843
|
+
s = String(input);
|
|
2844
|
+
} else {
|
|
2845
|
+
s = input.trim();
|
|
2846
|
+
}
|
|
2847
|
+
s = expandExponent(s);
|
|
2848
|
+
if (s.startsWith("+")) s = s.slice(1);
|
|
2849
|
+
if (!/^-?(\d+(\.\d*)?|\.\d+)$/.test(s)) return null;
|
|
2850
|
+
return s;
|
|
2851
|
+
}
|
|
2852
|
+
function shouldRoundUp(negative, lastKeptDigit, firstDiscarded, hasMoreNonZeroAfterFirst, mode) {
|
|
2853
|
+
switch (mode) {
|
|
2854
|
+
case "up":
|
|
2855
|
+
return true;
|
|
2856
|
+
case "down":
|
|
2857
|
+
return false;
|
|
2858
|
+
case "ceil":
|
|
2859
|
+
return !negative;
|
|
2860
|
+
case "floor":
|
|
2861
|
+
return negative;
|
|
2862
|
+
case "half-up":
|
|
2863
|
+
return firstDiscarded >= 5;
|
|
2864
|
+
case "half-down":
|
|
2865
|
+
return firstDiscarded > 5 || firstDiscarded === 5 && hasMoreNonZeroAfterFirst;
|
|
2866
|
+
case "half-even":
|
|
2867
|
+
if (firstDiscarded > 5) return true;
|
|
2868
|
+
if (firstDiscarded < 5) return false;
|
|
2869
|
+
return hasMoreNonZeroAfterFirst || lastKeptDigit % 2 === 1;
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
function parseToScaledInt(input, scale, rounding) {
|
|
2873
|
+
const canonical2 = toCanonicalDecimalString(input);
|
|
2874
|
+
if (canonical2 === null) return { ok: false, reason: "nonfinite" };
|
|
2875
|
+
const negative = canonical2.startsWith("-");
|
|
2876
|
+
const unsigned = negative ? canonical2.slice(1) : canonical2;
|
|
2877
|
+
const dot = unsigned.indexOf(".");
|
|
2878
|
+
const intPart = dot === -1 ? unsigned : unsigned.slice(0, dot);
|
|
2879
|
+
const fracPart = dot === -1 ? "" : unsigned.slice(dot + 1);
|
|
2880
|
+
const intDigits = intPart === "" ? "0" : intPart;
|
|
2881
|
+
if (fracPart.length <= scale) {
|
|
2882
|
+
const keep2 = fracPart.padEnd(scale, "0");
|
|
2883
|
+
const magnitude2 = BigInt(intDigits + keep2);
|
|
2884
|
+
return { ok: true, value: negative && magnitude2 !== 0n ? -magnitude2 : magnitude2 };
|
|
2885
|
+
}
|
|
2886
|
+
const keep = fracPart.slice(0, scale);
|
|
2887
|
+
const tail = fracPart.slice(scale);
|
|
2888
|
+
const magnitudeDigits = intDigits + keep;
|
|
2889
|
+
let magnitude = BigInt(magnitudeDigits);
|
|
2890
|
+
if (/^0+$/.test(tail)) {
|
|
2891
|
+
return { ok: true, value: negative && magnitude !== 0n ? -magnitude : magnitude };
|
|
2892
|
+
}
|
|
2893
|
+
if (rounding === void 0) return { ok: false, reason: "precision" };
|
|
2894
|
+
const lastKeptDigit = Number(magnitudeDigits[magnitudeDigits.length - 1]);
|
|
2895
|
+
const firstDiscarded = Number(tail[0]);
|
|
2896
|
+
const hasMoreNonZeroAfterFirst = /[1-9]/.test(tail.slice(1));
|
|
2897
|
+
if (shouldRoundUp(negative, lastKeptDigit, firstDiscarded, hasMoreNonZeroAfterFirst, rounding)) {
|
|
2898
|
+
magnitude += 1n;
|
|
2899
|
+
}
|
|
2900
|
+
return { ok: true, value: negative && magnitude !== 0n ? -magnitude : magnitude };
|
|
2901
|
+
}
|
|
2902
|
+
function decimalScaleOf(input) {
|
|
2903
|
+
const s = toCanonicalDecimalString(input);
|
|
2904
|
+
if (s === null) return null;
|
|
2905
|
+
const dot = s.indexOf(".");
|
|
2906
|
+
return dot === -1 ? 0 : s.length - dot - 1;
|
|
2907
|
+
}
|
|
2908
|
+
function rescaleScaledInt(value, fromScale, toScale, rounding = "half-up") {
|
|
2909
|
+
if (toScale >= fromScale) return value * 10n ** BigInt(toScale - fromScale);
|
|
2910
|
+
const drop = fromScale - toScale;
|
|
2911
|
+
const negative = value < 0n;
|
|
2912
|
+
const absStr = (negative ? -value : value).toString().padStart(drop + 1, "0");
|
|
2913
|
+
const keptStr = absStr.slice(0, absStr.length - drop);
|
|
2914
|
+
const tail = absStr.slice(absStr.length - drop);
|
|
2915
|
+
let magnitude = BigInt(keptStr);
|
|
2916
|
+
if (!/^0+$/.test(tail)) {
|
|
2917
|
+
const lastKeptDigit = Number(keptStr[keptStr.length - 1]);
|
|
2918
|
+
const firstDiscarded = Number(tail[0]);
|
|
2919
|
+
const hasMoreNonZeroAfterFirst = /[1-9]/.test(tail.slice(1));
|
|
2920
|
+
if (shouldRoundUp(negative, lastKeptDigit, firstDiscarded, hasMoreNonZeroAfterFirst, rounding)) {
|
|
2921
|
+
magnitude += 1n;
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
return negative && magnitude !== 0n ? -magnitude : magnitude;
|
|
2925
|
+
}
|
|
2926
|
+
function formatScaledInt(value, scale) {
|
|
2927
|
+
const negative = value < 0n;
|
|
2928
|
+
const abs = (negative ? -value : value).toString();
|
|
2929
|
+
if (scale === 0) return (negative ? "-" : "") + abs;
|
|
2930
|
+
const padded = abs.padStart(scale + 1, "0");
|
|
2931
|
+
const cut = padded.length - scale;
|
|
2932
|
+
const intPart = padded.slice(0, cut);
|
|
2933
|
+
const fracPart = padded.slice(cut);
|
|
2934
|
+
return (negative ? "-" : "") + intPart + "." + fracPart;
|
|
2935
|
+
}
|
|
2936
|
+
var init_fixed_point = __esm({
|
|
2937
|
+
"src/money/fixed-point.ts"() {
|
|
2938
|
+
"use strict";
|
|
2939
|
+
}
|
|
2940
|
+
});
|
|
2941
|
+
|
|
2942
|
+
// src/money/iso4217.ts
|
|
2943
|
+
function scaleForCurrency(code) {
|
|
2944
|
+
const v = MINOR_UNITS[code];
|
|
2945
|
+
return v === void 0 ? null : v;
|
|
2946
|
+
}
|
|
2947
|
+
var MINOR_UNITS;
|
|
2948
|
+
var init_iso4217 = __esm({
|
|
2949
|
+
"src/money/iso4217.ts"() {
|
|
2950
|
+
"use strict";
|
|
2951
|
+
MINOR_UNITS = {
|
|
2952
|
+
// 2-decimal majors
|
|
2953
|
+
EUR: 2,
|
|
2954
|
+
USD: 2,
|
|
2955
|
+
GBP: 2,
|
|
2956
|
+
CHF: 2,
|
|
2957
|
+
CAD: 2,
|
|
2958
|
+
AUD: 2,
|
|
2959
|
+
NZD: 2,
|
|
2960
|
+
SGD: 2,
|
|
2961
|
+
HKD: 2,
|
|
2962
|
+
CNY: 2,
|
|
2963
|
+
INR: 2,
|
|
2964
|
+
BRL: 2,
|
|
2965
|
+
MXN: 2,
|
|
2966
|
+
ZAR: 2,
|
|
2967
|
+
RUB: 2,
|
|
2968
|
+
TRY: 2,
|
|
2969
|
+
PLN: 2,
|
|
2970
|
+
SEK: 2,
|
|
2971
|
+
NOK: 2,
|
|
2972
|
+
DKK: 2,
|
|
2973
|
+
CZK: 2,
|
|
2974
|
+
HUF: 2,
|
|
2975
|
+
RON: 2,
|
|
2976
|
+
ILS: 2,
|
|
2977
|
+
THB: 2,
|
|
2978
|
+
PHP: 2,
|
|
2979
|
+
MYR: 2,
|
|
2980
|
+
IDR: 2,
|
|
2981
|
+
AED: 2,
|
|
2982
|
+
SAR: 2,
|
|
2983
|
+
QAR: 2,
|
|
2984
|
+
EGP: 2,
|
|
2985
|
+
// 0-decimal
|
|
2986
|
+
JPY: 0,
|
|
2987
|
+
KRW: 0,
|
|
2988
|
+
ISK: 0,
|
|
2989
|
+
CLP: 0,
|
|
2990
|
+
VND: 0,
|
|
2991
|
+
XOF: 0,
|
|
2992
|
+
XAF: 0,
|
|
2993
|
+
PYG: 0,
|
|
2994
|
+
// 3-decimal
|
|
2995
|
+
BHD: 3,
|
|
2996
|
+
KWD: 3,
|
|
2997
|
+
OMR: 3,
|
|
2998
|
+
TND: 3,
|
|
2999
|
+
JOD: 3,
|
|
3000
|
+
IQD: 3,
|
|
3001
|
+
LYD: 3
|
|
3002
|
+
};
|
|
3003
|
+
}
|
|
3004
|
+
});
|
|
3005
|
+
|
|
3006
|
+
// src/money/descriptor.ts
|
|
3007
|
+
function isMultiOptions(o) {
|
|
3008
|
+
return "currencies" in o;
|
|
2615
3009
|
}
|
|
2616
3010
|
function money(options) {
|
|
2617
3011
|
const hasFixed = "currency" in options;
|
|
@@ -3401,6 +3795,7 @@ var init_groupby = __esm({
|
|
|
3401
3795
|
init_canonical_key();
|
|
3402
3796
|
init_errors();
|
|
3403
3797
|
init_money_reducer();
|
|
3798
|
+
init_core();
|
|
3404
3799
|
GROUPBY_WARN_CARDINALITY = 1e4;
|
|
3405
3800
|
GROUPBY_MAX_CARDINALITY = 1e5;
|
|
3406
3801
|
warnedCardinalityFields = /* @__PURE__ */ new Set();
|
|
@@ -3469,9 +3864,29 @@ var init_groupby = __esm({
|
|
|
3469
3864
|
upstreams;
|
|
3470
3865
|
dictLabelResolver;
|
|
3471
3866
|
fields;
|
|
3472
|
-
/**
|
|
3473
|
-
|
|
3474
|
-
|
|
3867
|
+
/**
|
|
3868
|
+
* Execute the query, group, reduce, and return an array of rows.
|
|
3869
|
+
*
|
|
3870
|
+
* `opts` (#285 query-form MV grouping): when a `locale` + `i18nFields` are
|
|
3871
|
+
* given, the declared group-key `i18nText` fields are resolved to that locale
|
|
3872
|
+
* at the `mv` layer BEFORE bucketing — so an i18n group key is a stable string
|
|
3873
|
+
* instead of a raw `{locale}` map. The MV executor passes the MV's
|
|
3874
|
+
* `i18nLocale`/`i18nFields`; ordinary `.run()` callers pass nothing and are
|
|
3875
|
+
* unaffected.
|
|
3876
|
+
*/
|
|
3877
|
+
run(opts) {
|
|
3878
|
+
let records = this.executeRecords();
|
|
3879
|
+
if (opts?.locale !== void 0 && opts.i18nFields !== void 0) {
|
|
3880
|
+
const groupI18n = {};
|
|
3881
|
+
for (const f of this.fields) {
|
|
3882
|
+
const d = opts.i18nFields[f];
|
|
3883
|
+
if (d !== void 0) groupI18n[f] = d;
|
|
3884
|
+
}
|
|
3885
|
+
if (Object.keys(groupI18n).length > 0) {
|
|
3886
|
+
records = records.map((r) => applyI18nLocale(r, groupI18n, opts.locale, void 0, "mv"));
|
|
3887
|
+
}
|
|
3888
|
+
}
|
|
3889
|
+
return groupAndReduce(records, this.fields, this.spec);
|
|
3475
3890
|
}
|
|
3476
3891
|
/**
|
|
3477
3892
|
* Execute the query, group, reduce, and resolve `<field>Label` for
|
|
@@ -4056,12 +4471,13 @@ var executor_exports3 = {};
|
|
|
4056
4471
|
__export(executor_exports3, {
|
|
4057
4472
|
MaterializedViewExecutor: () => MaterializedViewExecutor
|
|
4058
4473
|
});
|
|
4059
|
-
async function materializeQueryResult(q, mvName) {
|
|
4474
|
+
async function materializeQueryResult(q, mvName, i18nLocale, i18nFields) {
|
|
4060
4475
|
if (typeof q?.toArray === "function") {
|
|
4061
4476
|
return await q.toArray();
|
|
4062
4477
|
}
|
|
4063
4478
|
if (typeof q?.run === "function") {
|
|
4064
|
-
const
|
|
4479
|
+
const runOpts = i18nLocale !== void 0 ? { locale: i18nLocale, i18nFields } : void 0;
|
|
4480
|
+
const result = await Promise.resolve(q.run(runOpts));
|
|
4065
4481
|
if (Array.isArray(result)) {
|
|
4066
4482
|
return result;
|
|
4067
4483
|
}
|
|
@@ -4090,6 +4506,29 @@ async function materializeUnionResult(spec, db) {
|
|
|
4090
4506
|
}
|
|
4091
4507
|
if (!spec.groupBy) return unified;
|
|
4092
4508
|
const groupFields = typeof spec.groupBy === "string" ? [spec.groupBy] : spec.groupBy;
|
|
4509
|
+
if (spec.i18nLocale !== void 0 && spec.i18nFields !== void 0) {
|
|
4510
|
+
const groupI18n = {};
|
|
4511
|
+
for (const f of groupFields) {
|
|
4512
|
+
const d = spec.i18nFields[f];
|
|
4513
|
+
if (d !== void 0) groupI18n[f] = d;
|
|
4514
|
+
}
|
|
4515
|
+
if (Object.keys(groupI18n).length > 0) {
|
|
4516
|
+
for (let i = 0; i < unified.length; i++) {
|
|
4517
|
+
unified[i] = applyI18nLocale(unified[i], groupI18n, spec.i18nLocale, void 0, "mv");
|
|
4518
|
+
}
|
|
4519
|
+
}
|
|
4520
|
+
}
|
|
4521
|
+
for (const f of groupFields) {
|
|
4522
|
+
for (const row of unified) {
|
|
4523
|
+
const v = row[f];
|
|
4524
|
+
if (v !== null && typeof v === "object") {
|
|
4525
|
+
throw new LocaleNotSpecifiedError(
|
|
4526
|
+
f,
|
|
4527
|
+
`Materialized view "${spec.name}" groups by "${f}", whose value is a raw i18n locale map \u2014 an unstable object group key. Declare { i18nLocale, i18nFields } on the MV to resolve it at the 'mv' layer, or group by a dictKey/staticDict code (the stable key) and resolve the label at read time.`
|
|
4528
|
+
);
|
|
4529
|
+
}
|
|
4530
|
+
}
|
|
4531
|
+
}
|
|
4093
4532
|
if (!spec.aggregate) {
|
|
4094
4533
|
const seen = /* @__PURE__ */ new Map();
|
|
4095
4534
|
for (const row of unified) {
|
|
@@ -4121,6 +4560,7 @@ var init_executor3 = __esm({
|
|
|
4121
4560
|
init_registry();
|
|
4122
4561
|
init_groupby();
|
|
4123
4562
|
init_canonical_key();
|
|
4563
|
+
init_core();
|
|
4124
4564
|
DEFAULT_MAX_ROWS = 1e5;
|
|
4125
4565
|
MaterializedViewExecutor = {
|
|
4126
4566
|
async refresh(reg, accessor) {
|
|
@@ -4136,7 +4576,7 @@ var init_executor3 = __esm({
|
|
|
4136
4576
|
rows = await materializeUnionResult(spec, ctxForQuery);
|
|
4137
4577
|
} else {
|
|
4138
4578
|
const q = spec.query(ctxForQuery);
|
|
4139
|
-
rows = await materializeQueryResult(q, spec.name);
|
|
4579
|
+
rows = await materializeQueryResult(q, spec.name, spec.i18nLocale, spec.i18nFields);
|
|
4140
4580
|
}
|
|
4141
4581
|
if (rows.length > maxRows) {
|
|
4142
4582
|
throw new MaterializedViewTooLargeError(spec.name, rows.length, maxRows);
|
|
@@ -4637,14 +5077,17 @@ var init_read_only_facade = __esm({
|
|
|
4637
5077
|
"use strict";
|
|
4638
5078
|
ReadOnlyVaultFacade = class {
|
|
4639
5079
|
_vault;
|
|
4640
|
-
|
|
5080
|
+
_layer;
|
|
5081
|
+
constructor(vault, layer = "read") {
|
|
4641
5082
|
this._vault = vault;
|
|
5083
|
+
this._layer = layer;
|
|
4642
5084
|
}
|
|
4643
5085
|
collection(name) {
|
|
4644
5086
|
const c = this._vault.collection(name);
|
|
5087
|
+
const layer = this._layer;
|
|
4645
5088
|
return {
|
|
4646
|
-
get: (id) => c.get(id),
|
|
4647
|
-
list: () => c.list(),
|
|
5089
|
+
get: (id) => c.get(id, { _layer: layer }),
|
|
5090
|
+
list: () => c.list({ _layer: layer }),
|
|
4648
5091
|
query: () => c.query()
|
|
4649
5092
|
};
|
|
4650
5093
|
}
|
|
@@ -4700,6 +5143,16 @@ var init_registry3 = __esm({
|
|
|
4700
5143
|
if (fromExtra) fromExtra.push(reg);
|
|
4701
5144
|
else this._bySource.set(extra, [reg]);
|
|
4702
5145
|
}
|
|
5146
|
+
for (const t of spec.triggerBy ?? []) {
|
|
5147
|
+
const fromTrigger = this._bySource.get(t.collection);
|
|
5148
|
+
if (fromTrigger) fromTrigger.push(reg);
|
|
5149
|
+
else this._bySource.set(t.collection, [reg]);
|
|
5150
|
+
}
|
|
5151
|
+
if (spec.rollup) {
|
|
5152
|
+
const fromRollup = this._bySource.get(spec.rollup.from);
|
|
5153
|
+
if (fromRollup) fromRollup.push(reg);
|
|
5154
|
+
else this._bySource.set(spec.rollup.from, [reg]);
|
|
5155
|
+
}
|
|
4703
5156
|
for (const key of outputKeys) {
|
|
4704
5157
|
const output = spec.outputs[key];
|
|
4705
5158
|
if (!output) continue;
|
|
@@ -4753,6 +5206,9 @@ var init_registry3 = __esm({
|
|
|
4753
5206
|
for (const key of Object.keys(s.spec.outputs)) {
|
|
4754
5207
|
const output = s.spec.outputs[key];
|
|
4755
5208
|
if (!output) continue;
|
|
5209
|
+
if (output.shape === "record" && output.collection === s.spec.source && output.denorm !== void 0) {
|
|
5210
|
+
continue;
|
|
5211
|
+
}
|
|
4756
5212
|
visit(output.collection);
|
|
4757
5213
|
}
|
|
4758
5214
|
}
|
|
@@ -4929,6 +5385,177 @@ var init_delegation = __esm({
|
|
|
4929
5385
|
}
|
|
4930
5386
|
});
|
|
4931
5387
|
|
|
5388
|
+
// src/federation/schema-manifest.ts
|
|
5389
|
+
function captureBlueprint(configure) {
|
|
5390
|
+
const recorded = [];
|
|
5391
|
+
const collectionStub = new Proxy(
|
|
5392
|
+
{},
|
|
5393
|
+
{
|
|
5394
|
+
get: () => () => collectionStub
|
|
5395
|
+
}
|
|
5396
|
+
);
|
|
5397
|
+
const proxy = new Proxy(
|
|
5398
|
+
{},
|
|
5399
|
+
{
|
|
5400
|
+
get: (_t, prop) => {
|
|
5401
|
+
if (prop === "collection") {
|
|
5402
|
+
return (name, opts) => {
|
|
5403
|
+
recorded.push({
|
|
5404
|
+
name,
|
|
5405
|
+
indexes: opts?.indexes ?? [],
|
|
5406
|
+
persistJsonSchema: !!opts?.persistJsonSchema
|
|
5407
|
+
});
|
|
5408
|
+
return collectionStub;
|
|
5409
|
+
};
|
|
5410
|
+
}
|
|
5411
|
+
return () => proxy;
|
|
5412
|
+
}
|
|
5413
|
+
}
|
|
5414
|
+
);
|
|
5415
|
+
configure(proxy);
|
|
5416
|
+
const sorted = [...recorded].sort((a, b) => a.name.localeCompare(b.name));
|
|
5417
|
+
const indexes = {};
|
|
5418
|
+
const persistJsonSchema = [];
|
|
5419
|
+
for (const c of sorted) {
|
|
5420
|
+
indexes[c.name] = c.indexes;
|
|
5421
|
+
if (c.persistJsonSchema) persistJsonSchema.push(c.name);
|
|
5422
|
+
}
|
|
5423
|
+
return {
|
|
5424
|
+
// `persistJsonSchema` is already name-sorted: it is populated while
|
|
5425
|
+
// iterating `sorted` (collections in name order).
|
|
5426
|
+
collections: sorted.map((c) => c.name),
|
|
5427
|
+
indexes,
|
|
5428
|
+
persistJsonSchema
|
|
5429
|
+
};
|
|
5430
|
+
}
|
|
5431
|
+
function canonical(value) {
|
|
5432
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
5433
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
5434
|
+
const obj = value;
|
|
5435
|
+
const keys = Object.keys(obj).sort();
|
|
5436
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonical(obj[k])}`).join(",")}}`;
|
|
5437
|
+
}
|
|
5438
|
+
async function fingerprintBlueprint(bp) {
|
|
5439
|
+
return sha256Hex(new TextEncoder().encode(canonical(bp)));
|
|
5440
|
+
}
|
|
5441
|
+
var init_schema_manifest = __esm({
|
|
5442
|
+
"src/federation/schema-manifest.ts"() {
|
|
5443
|
+
"use strict";
|
|
5444
|
+
init_crypto();
|
|
5445
|
+
}
|
|
5446
|
+
});
|
|
5447
|
+
|
|
5448
|
+
// src/federation/state-vault.ts
|
|
5449
|
+
var state_vault_exports = {};
|
|
5450
|
+
__export(state_vault_exports, {
|
|
5451
|
+
STATE_VAULT_NAME: () => STATE_VAULT_NAME,
|
|
5452
|
+
StateManagementVault: () => StateManagementVault
|
|
5453
|
+
});
|
|
5454
|
+
var REGISTRY, MANIFEST, EVENTS, MIGRATION_STATUS, StateManagementVault;
|
|
5455
|
+
var init_state_vault = __esm({
|
|
5456
|
+
"src/federation/state-vault.ts"() {
|
|
5457
|
+
"use strict";
|
|
5458
|
+
init_schema_manifest();
|
|
5459
|
+
init_constants();
|
|
5460
|
+
init_ulid();
|
|
5461
|
+
init_constants();
|
|
5462
|
+
REGISTRY = "vaultRegistry";
|
|
5463
|
+
MANIFEST = "schemaManifest";
|
|
5464
|
+
EVENTS = "deploymentEvents";
|
|
5465
|
+
MIGRATION_STATUS = "migrationStatus";
|
|
5466
|
+
StateManagementVault = class _StateManagementVault {
|
|
5467
|
+
constructor(registry, schemaManifest, events, migrationStatus) {
|
|
5468
|
+
this.registry = registry;
|
|
5469
|
+
this.schemaManifest = schemaManifest;
|
|
5470
|
+
this.#events = events;
|
|
5471
|
+
this.#migrationStatus = migrationStatus;
|
|
5472
|
+
}
|
|
5473
|
+
registry;
|
|
5474
|
+
schemaManifest;
|
|
5475
|
+
/**
|
|
5476
|
+
* The append-only deployment-events log is kept truly private so the raw
|
|
5477
|
+
* mutable Collection is never surfaced — events may only be written via
|
|
5478
|
+
* `appendEvent` and read via `queryEvents`. (`registry` and
|
|
5479
|
+
* `schemaManifest` are deliberately public: consumers read and write them.)
|
|
5480
|
+
*/
|
|
5481
|
+
#events;
|
|
5482
|
+
/** Per-shard fleet-migration progress (#271). Surfaced via typed methods only. */
|
|
5483
|
+
#migrationStatus;
|
|
5484
|
+
/** Idempotently open the reserved state vault and bind the control-plane collections. */
|
|
5485
|
+
static async open(db) {
|
|
5486
|
+
const vault = await db.openVault(STATE_VAULT_NAME);
|
|
5487
|
+
return new _StateManagementVault(
|
|
5488
|
+
vault.collection(REGISTRY),
|
|
5489
|
+
vault.collection(MANIFEST),
|
|
5490
|
+
vault.collection(EVENTS),
|
|
5491
|
+
vault.collection(MIGRATION_STATUS)
|
|
5492
|
+
);
|
|
5493
|
+
}
|
|
5494
|
+
/** Read one shard's migration status (or null). */
|
|
5495
|
+
async getMigrationStatus(vaultId) {
|
|
5496
|
+
return this.#migrationStatus.get(vaultId);
|
|
5497
|
+
}
|
|
5498
|
+
/** All migration-status rows (hydrates first). */
|
|
5499
|
+
async listMigrationStatus() {
|
|
5500
|
+
await this.#migrationStatus.list();
|
|
5501
|
+
return this.#migrationStatus.query().toArray();
|
|
5502
|
+
}
|
|
5503
|
+
/** Upsert one shard's migration status (keyed by vaultId). */
|
|
5504
|
+
async upsertMigrationStatus(row) {
|
|
5505
|
+
await this.#migrationStatus.put(row.vaultId, row);
|
|
5506
|
+
}
|
|
5507
|
+
/** Read-only query over the append-only deployment-events log. */
|
|
5508
|
+
queryEvents() {
|
|
5509
|
+
return this.#events.query();
|
|
5510
|
+
}
|
|
5511
|
+
/**
|
|
5512
|
+
* Append a deployment event with a fresh unique (ULID) id. This is the
|
|
5513
|
+
* only write path to the events log; no update/delete is exposed.
|
|
5514
|
+
* Callers should treat failures as non-fatal — this method does not
|
|
5515
|
+
* swallow errors, so wrap the call site in try/catch where appropriate.
|
|
5516
|
+
*/
|
|
5517
|
+
async appendEvent(event) {
|
|
5518
|
+
const ts = event.ts ?? Date.now();
|
|
5519
|
+
const id = generateULID();
|
|
5520
|
+
await this.#events.put(id, { ...event, id, ts });
|
|
5521
|
+
}
|
|
5522
|
+
/**
|
|
5523
|
+
* Ensure a manifest row exists for `(templateName, template.version)`.
|
|
5524
|
+
* Safe to call repeatedly: the `fingerprint` is a deterministic hash of
|
|
5525
|
+
* the template's declared shape (stable across calls), though each call
|
|
5526
|
+
* refreshes `recordedAt`.
|
|
5527
|
+
*/
|
|
5528
|
+
async recordManifest(templateName, template) {
|
|
5529
|
+
const bp = captureBlueprint(template.configure);
|
|
5530
|
+
const fingerprint = await fingerprintBlueprint(bp);
|
|
5531
|
+
await this.schemaManifest.put(`${templateName}:${template.version}`, {
|
|
5532
|
+
templateName,
|
|
5533
|
+
version: template.version,
|
|
5534
|
+
collections: bp.collections,
|
|
5535
|
+
indexes: bp.indexes,
|
|
5536
|
+
persistJsonSchema: bp.persistJsonSchema,
|
|
5537
|
+
fingerprint,
|
|
5538
|
+
recordedAt: Date.now()
|
|
5539
|
+
});
|
|
5540
|
+
return fingerprint;
|
|
5541
|
+
}
|
|
5542
|
+
/**
|
|
5543
|
+
* True when `template`'s current declared shape does not match the recorded
|
|
5544
|
+
* manifest for `(templateName, template.version)`. Because shards carry no
|
|
5545
|
+
* schema state independent of their template, this catches "a template's
|
|
5546
|
+
* shape changed without bumping `version`" — not independent per-shard drift.
|
|
5547
|
+
* A missing manifest is treated as drift (nothing to verify against).
|
|
5548
|
+
*/
|
|
5549
|
+
async detectDrift(templateName, template) {
|
|
5550
|
+
const row = await this.schemaManifest.get(`${templateName}:${template.version}`);
|
|
5551
|
+
if (!row) return true;
|
|
5552
|
+
const current = await fingerprintBlueprint(captureBlueprint(template.configure));
|
|
5553
|
+
return current !== row.fingerprint;
|
|
5554
|
+
}
|
|
5555
|
+
};
|
|
5556
|
+
}
|
|
5557
|
+
});
|
|
5558
|
+
|
|
4932
5559
|
// src/federation/classify-skip.ts
|
|
4933
5560
|
function classifyShardSkip(err) {
|
|
4934
5561
|
return err instanceof NoAccessError ? "no-grant" : "error";
|
|
@@ -5213,6 +5840,7 @@ var SHARD_SEPARATOR, SAFE_PARTITION_KEY, VaultGroup, ShardedCollection, ShardedQ
|
|
|
5213
5840
|
var init_vault_group = __esm({
|
|
5214
5841
|
"src/federation/vault-group.ts"() {
|
|
5215
5842
|
"use strict";
|
|
5843
|
+
init_state_vault();
|
|
5216
5844
|
init_errors();
|
|
5217
5845
|
init_constants();
|
|
5218
5846
|
init_classify_skip();
|
|
@@ -5222,12 +5850,13 @@ var init_vault_group = __esm({
|
|
|
5222
5850
|
SHARD_SEPARATOR = "--";
|
|
5223
5851
|
SAFE_PARTITION_KEY = /^[A-Za-z0-9._-]+$/;
|
|
5224
5852
|
VaultGroup = class {
|
|
5225
|
-
constructor(db, name, registry, sharding, template) {
|
|
5853
|
+
constructor(db, name, registry, sharding, template, migrateOnOpen = false) {
|
|
5226
5854
|
this.db = db;
|
|
5227
5855
|
this.name = name;
|
|
5228
5856
|
this.registry = registry;
|
|
5229
5857
|
this.sharding = sharding;
|
|
5230
5858
|
this.template = template;
|
|
5859
|
+
this.migrateOnOpen = migrateOnOpen;
|
|
5231
5860
|
if (name.includes(SHARD_SEPARATOR)) {
|
|
5232
5861
|
throw new ValidationError(
|
|
5233
5862
|
`VaultGroup name "${name}" must not contain "--" (reserved shard vault-id separator).`
|
|
@@ -5239,6 +5868,7 @@ var init_vault_group = __esm({
|
|
|
5239
5868
|
registry;
|
|
5240
5869
|
sharding;
|
|
5241
5870
|
template;
|
|
5871
|
+
migrateOnOpen;
|
|
5242
5872
|
/** @internal — set when the group is managed (no explicit registry). */
|
|
5243
5873
|
stateVault;
|
|
5244
5874
|
/** @internal */
|
|
@@ -5272,8 +5902,22 @@ var init_vault_group = __esm({
|
|
|
5272
5902
|
const rows = this.registry.query().toArray();
|
|
5273
5903
|
return rows.filter((r) => r.group === this.name);
|
|
5274
5904
|
}
|
|
5275
|
-
/**
|
|
5905
|
+
/**
|
|
5906
|
+
* Open an existing shard and apply the template. When `migrateOnOpen` is set
|
|
5907
|
+
* (#271) and the shard's registry version is behind the template, its cutover
|
|
5908
|
+
* runs inline first — so a behind shard never surfaces a stale handle.
|
|
5909
|
+
*/
|
|
5276
5910
|
async openShard(partitionKey) {
|
|
5911
|
+
if (this.migrateOnOpen) {
|
|
5912
|
+
const row = await this.registry.get(this.registryId(partitionKey));
|
|
5913
|
+
if (row && row.schemaVersion < this.template.version) {
|
|
5914
|
+
await this.migrateShard(partitionKey);
|
|
5915
|
+
}
|
|
5916
|
+
}
|
|
5917
|
+
return this._openShardRaw(partitionKey);
|
|
5918
|
+
}
|
|
5919
|
+
/** @internal — open + configure with no migrate-on-open hook (used by the migration path itself to avoid recursion). */
|
|
5920
|
+
async _openShardRaw(partitionKey) {
|
|
5277
5921
|
const vault = await this.db.openVault(this.shardVaultId(partitionKey), { create: false });
|
|
5278
5922
|
this.template.configure(vault);
|
|
5279
5923
|
return vault;
|
|
@@ -5285,13 +5929,21 @@ var init_vault_group = __esm({
|
|
|
5285
5929
|
* - row + vault present → no-op, return handle
|
|
5286
5930
|
* - row present, vault gone → ShardProvisioningError
|
|
5287
5931
|
* - row absent (vault present or not) → open-or-create, configure, write row
|
|
5932
|
+
*
|
|
5933
|
+
* When `region` is given (the routing `put` passes `sharding.regionOf(record)`),
|
|
5934
|
+
* the candidate backend's `capabilities.region` must match or this throws
|
|
5935
|
+
* `DataResidencyError` BEFORE provisioning (#271 data-residency guard).
|
|
5288
5936
|
*/
|
|
5289
|
-
async createShard(partitionKey) {
|
|
5937
|
+
async createShard(partitionKey, region) {
|
|
5290
5938
|
const vaultId = this.shardVaultId(partitionKey);
|
|
5291
5939
|
const row = await this.registry.get(this.registryId(partitionKey));
|
|
5292
5940
|
const provisioned = await this.db._shardVaultProvisioned(vaultId);
|
|
5293
5941
|
if (row && !provisioned) throw new ShardProvisioningError(vaultId, partitionKey);
|
|
5294
5942
|
if (row && provisioned) return this.openShard(partitionKey);
|
|
5943
|
+
if (region !== void 0) {
|
|
5944
|
+
const backendRegion = this.db._resolveBackend(vaultId).capabilities?.region;
|
|
5945
|
+
if (backendRegion !== region) throw new DataResidencyError(vaultId, region, backendRegion);
|
|
5946
|
+
}
|
|
5295
5947
|
const vault = await this.db.openVault(vaultId);
|
|
5296
5948
|
this.template.configure(vault);
|
|
5297
5949
|
await this.registry.put(this.registryId(partitionKey), {
|
|
@@ -5336,20 +5988,186 @@ var init_vault_group = __esm({
|
|
|
5336
5988
|
/** @internal — eligible (openable-candidate) rows + drift/divergence skips. */
|
|
5337
5989
|
async resolveEligible(options = {}) {
|
|
5338
5990
|
const rows = await this.allRows();
|
|
5339
|
-
const skipped = [];
|
|
5340
|
-
const versionOk = [];
|
|
5341
|
-
for (const row of rows) {
|
|
5342
|
-
if (options.minVersion !== void 0 && row.schemaVersion < options.minVersion) {
|
|
5343
|
-
skipped.push({ vaultId: row.vaultId, reason: "schema-drift" });
|
|
5344
|
-
} else versionOk.push(row);
|
|
5991
|
+
const skipped = [];
|
|
5992
|
+
const versionOk = [];
|
|
5993
|
+
for (const row of rows) {
|
|
5994
|
+
if (options.minVersion !== void 0 && row.schemaVersion < options.minVersion) {
|
|
5995
|
+
skipped.push({ vaultId: row.vaultId, reason: "schema-drift" });
|
|
5996
|
+
} else versionOk.push(row);
|
|
5997
|
+
}
|
|
5998
|
+
const provisioned = await Promise.all(versionOk.map((r) => this.db._shardVaultProvisioned(r.vaultId)));
|
|
5999
|
+
const eligible = [];
|
|
6000
|
+
versionOk.forEach((row, i) => {
|
|
6001
|
+
if (provisioned[i]) eligible.push(row);
|
|
6002
|
+
else skipped.push({ vaultId: row.vaultId, reason: "error", error: new ShardProvisioningError(row.vaultId, row.partitionKey) });
|
|
6003
|
+
});
|
|
6004
|
+
return { eligible, skipped };
|
|
6005
|
+
}
|
|
6006
|
+
/** @internal — registered push-model cross-vault derivations (#271 Insight Vault). */
|
|
6007
|
+
crossVaultDerivations = [];
|
|
6008
|
+
/**
|
|
6009
|
+
* Register a push-model cross-vault derivation — the Insight Vault pattern
|
|
6010
|
+
* (#271, Layer 4). Drive it with {@link refreshInsights}.
|
|
6011
|
+
*
|
|
6012
|
+
* For each shard, `derive(records, ctx)` runs on that shard's `source`
|
|
6013
|
+
* records and its return value is written into the analytics
|
|
6014
|
+
* (`target.vault` / `target.collection`) vault, keyed by partition key —
|
|
6015
|
+
* one summary row per shard. The derivation runs in-process under THIS
|
|
6016
|
+
* group's `Noydb` (which already holds both the shard and Insight Vault
|
|
6017
|
+
* keyrings); the shard's decrypted records are reduced to a summary that is
|
|
6018
|
+
* re-encrypted under the Insight Vault's own DEK, so no shard ciphertext
|
|
6019
|
+
* crosses a DEK boundary.
|
|
6020
|
+
*
|
|
6021
|
+
* **Zero-knowledge note:** the Insight Vault backend sees aggregated
|
|
6022
|
+
* structure (totals, counts, timestamps) drawn from many shards — a weaker
|
|
6023
|
+
* ZK profile than the per-shard vaults. Opt-in; keep summaries to aggregate
|
|
6024
|
+
* scalars (no embeddings / no raw records).
|
|
6025
|
+
*
|
|
6026
|
+
* v1 is explicit-refresh (no write-path push); call `refreshInsights()`
|
|
6027
|
+
* after a batch of writes, or on a schedule.
|
|
6028
|
+
*
|
|
6029
|
+
* The `target.vault` must NOT be the group itself or one of its shards —
|
|
6030
|
+
* a summary writing back into client-shard data would breach the Insight
|
|
6031
|
+
* Vault's separate-DEK-boundary contract. Such a target throws a
|
|
6032
|
+
* `ValidationError` at registration (#271 Insight-write isolation).
|
|
6033
|
+
*/
|
|
6034
|
+
withCrossVaultDerivation(spec) {
|
|
6035
|
+
const target = spec.target.vault;
|
|
6036
|
+
if (target === this.name || target.startsWith(`${this.name}${SHARD_SEPARATOR}`)) {
|
|
6037
|
+
throw new ValidationError(
|
|
6038
|
+
`withCrossVaultDerivation: target.vault "${target}" is the "${this.name}" group itself or one of its shards \u2014 an Insight summary must target a SEPARATE analytics vault, never write back into client-shard data (it would breach the per-shard DEK boundary). Use a distinct vault name.`
|
|
6039
|
+
);
|
|
6040
|
+
}
|
|
6041
|
+
this.crossVaultDerivations.push(spec);
|
|
6042
|
+
}
|
|
6043
|
+
/**
|
|
6044
|
+
* Run every registered {@link withCrossVaultDerivation}: read each eligible
|
|
6045
|
+
* shard's source records, derive a per-shard summary, and write it into the
|
|
6046
|
+
* Insight Vault keyed by partition key. Shards behind `minVersion`,
|
|
6047
|
+
* unprovisioned, or whose read errors are reported in `skippedVaults` and
|
|
6048
|
+
* are not written (a stale summary is never left behind for a failed shard).
|
|
6049
|
+
*/
|
|
6050
|
+
async refreshInsights(options = {}) {
|
|
6051
|
+
if (this.crossVaultDerivations.length === 0) return { written: 0, skippedVaults: [] };
|
|
6052
|
+
const { eligible, skipped } = await this.resolveEligible(
|
|
6053
|
+
options.minVersion !== void 0 ? { minVersion: options.minVersion } : {}
|
|
6054
|
+
);
|
|
6055
|
+
let written = 0;
|
|
6056
|
+
for (const spec of this.crossVaultDerivations) {
|
|
6057
|
+
const results = await this.db.queryAcross(
|
|
6058
|
+
eligible.map((r) => r.vaultId),
|
|
6059
|
+
async (vault) => {
|
|
6060
|
+
this.template.configure(vault);
|
|
6061
|
+
return vault.collection(spec.source).list();
|
|
6062
|
+
},
|
|
6063
|
+
{ create: false, ...options.concurrency !== void 0 ? { concurrency: options.concurrency } : {} }
|
|
6064
|
+
);
|
|
6065
|
+
const insight = await this.db.openVault(spec.target.vault);
|
|
6066
|
+
const out = insight.collection(spec.target.collection);
|
|
6067
|
+
for (let i = 0; i < eligible.length; i++) {
|
|
6068
|
+
const row = eligible[i];
|
|
6069
|
+
const res = results[i];
|
|
6070
|
+
if (!res || res.result === void 0) {
|
|
6071
|
+
skipped.push({ vaultId: row.vaultId, reason: "error", ...res?.error ? { error: res.error } : {} });
|
|
6072
|
+
continue;
|
|
6073
|
+
}
|
|
6074
|
+
const ctx = {
|
|
6075
|
+
vaultId: row.vaultId,
|
|
6076
|
+
partitionKey: row.partitionKey,
|
|
6077
|
+
schemaVersion: row.schemaVersion
|
|
6078
|
+
};
|
|
6079
|
+
const summary = spec.derive(res.result, ctx);
|
|
6080
|
+
await out.put(row.partitionKey, summary);
|
|
6081
|
+
written++;
|
|
6082
|
+
}
|
|
6083
|
+
}
|
|
6084
|
+
return { written, skippedVaults: skipped };
|
|
6085
|
+
}
|
|
6086
|
+
/** @internal — the control-plane vault for migration status; lazily opened. */
|
|
6087
|
+
async ensureStateVault() {
|
|
6088
|
+
if (!this.stateVault) this.stateVault = await StateManagementVault.open(this.db);
|
|
6089
|
+
return this.stateVault;
|
|
6090
|
+
}
|
|
6091
|
+
/**
|
|
6092
|
+
* Migrate ONE shard to the template's current version (#271 fleet runner,
|
|
6093
|
+
* per-shard step). Opens the shard (applying the template, which arms the
|
|
6094
|
+
* M12 cutover), drains schema-write detection, runs `vault.runSchemaCutover()`
|
|
6095
|
+
* (the per-vault drain-barrier-transform protocol), then advances the
|
|
6096
|
+
* registry row's `schemaVersion` and records `migration-status`. A shard
|
|
6097
|
+
* already at the template version is a no-op (`status: 'done'`, migrated 0).
|
|
6098
|
+
* Never throws on a cutover failure — it records `status: 'failed'` and
|
|
6099
|
+
* returns the row, so a fleet run continues past a bad shard.
|
|
6100
|
+
*/
|
|
6101
|
+
async migrateShard(partitionKey) {
|
|
6102
|
+
const vaultId = this.shardVaultId(partitionKey);
|
|
6103
|
+
const row = await this.registry.get(this.registryId(partitionKey));
|
|
6104
|
+
if (!row) throw new UnknownShardError(partitionKey, this.name);
|
|
6105
|
+
const target = this.template.version;
|
|
6106
|
+
const sv = await this.ensureStateVault();
|
|
6107
|
+
const base = { vaultId, group: this.name, currentVersion: row.schemaVersion, targetVersion: target };
|
|
6108
|
+
if (row.schemaVersion >= target) {
|
|
6109
|
+
const done = { ...base, status: "done", migrated: 0, finishedAt: Date.now() };
|
|
6110
|
+
await sv.upsertMigrationStatus(done);
|
|
6111
|
+
return done;
|
|
6112
|
+
}
|
|
6113
|
+
await sv.upsertMigrationStatus({ ...base, status: "running", startedAt: Date.now() });
|
|
6114
|
+
try {
|
|
6115
|
+
await sv.appendEvent({ type: "migration-started", group: this.name, vaultId, version: target });
|
|
6116
|
+
} catch {
|
|
6117
|
+
}
|
|
6118
|
+
try {
|
|
6119
|
+
const vault = await this._openShardRaw(partitionKey);
|
|
6120
|
+
await vault._drainPendingSchemaWrites();
|
|
6121
|
+
const { migrated } = await vault.runSchemaCutover();
|
|
6122
|
+
await this.registry.put(this.registryId(partitionKey), { ...row, schemaVersion: target });
|
|
6123
|
+
const done = { ...base, currentVersion: target, status: "done", migrated, finishedAt: Date.now() };
|
|
6124
|
+
await sv.upsertMigrationStatus(done);
|
|
6125
|
+
try {
|
|
6126
|
+
await sv.appendEvent({ type: "migration-completed", group: this.name, vaultId, version: target });
|
|
6127
|
+
} catch {
|
|
6128
|
+
}
|
|
6129
|
+
return done;
|
|
6130
|
+
} catch (err) {
|
|
6131
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
6132
|
+
const failed = { ...base, status: "failed", error, finishedAt: Date.now() };
|
|
6133
|
+
await sv.upsertMigrationStatus(failed);
|
|
6134
|
+
try {
|
|
6135
|
+
await sv.appendEvent({ type: "migration-failed", group: this.name, vaultId, version: target, detail: error });
|
|
6136
|
+
} catch {
|
|
6137
|
+
}
|
|
6138
|
+
return failed;
|
|
6139
|
+
}
|
|
6140
|
+
}
|
|
6141
|
+
/**
|
|
6142
|
+
* Active batch runner (#271): migrate every shard behind the template version
|
|
6143
|
+
* to it, in controlled batches. **Resumable + crash-safe** — shards already at
|
|
6144
|
+
* the target are skipped (the registry version is the source of truth), so a
|
|
6145
|
+
* re-run after a crash only picks up the unfinished + previously-failed shards.
|
|
6146
|
+
*
|
|
6147
|
+
* - `cohort` — restrict to these partition keys (the staged / canary rollout:
|
|
6148
|
+
* migrate a small cohort, verify the Insight Vault, then run the rest).
|
|
6149
|
+
* - `batchSize` — max shards migrated concurrently per batch (back-pressure).
|
|
6150
|
+
* Default 4. Batches run sequentially; shards within a batch run in parallel.
|
|
6151
|
+
*/
|
|
6152
|
+
async migrateFleet(options = {}) {
|
|
6153
|
+
const target = this.template.version;
|
|
6154
|
+
const rows = await this.allRows();
|
|
6155
|
+
const cohort = options.cohort;
|
|
6156
|
+
const todo = rows.filter(
|
|
6157
|
+
(r) => r.schemaVersion < target && (cohort === void 0 || cohort.includes(r.partitionKey))
|
|
6158
|
+
);
|
|
6159
|
+
const batchSize = Math.max(1, options.batchSize ?? 4);
|
|
6160
|
+
const migrated = [];
|
|
6161
|
+
const failed = [];
|
|
6162
|
+
for (let i = 0; i < todo.length; i += batchSize) {
|
|
6163
|
+
const batch = todo.slice(i, i + batchSize);
|
|
6164
|
+
const settled = await Promise.all(batch.map((r) => this.migrateShard(r.partitionKey)));
|
|
6165
|
+
for (const res of settled) {
|
|
6166
|
+
if (res.status === "done") migrated.push(res.vaultId);
|
|
6167
|
+
else failed.push({ vaultId: res.vaultId, error: res.error ?? "unknown" });
|
|
6168
|
+
}
|
|
5345
6169
|
}
|
|
5346
|
-
|
|
5347
|
-
const eligible = [];
|
|
5348
|
-
versionOk.forEach((row, i) => {
|
|
5349
|
-
if (provisioned[i]) eligible.push(row);
|
|
5350
|
-
else skipped.push({ vaultId: row.vaultId, reason: "error", error: new ShardProvisioningError(row.vaultId, row.partitionKey) });
|
|
5351
|
-
});
|
|
5352
|
-
return { eligible, skipped };
|
|
6170
|
+
return { target, migrated, failed };
|
|
5353
6171
|
}
|
|
5354
6172
|
};
|
|
5355
6173
|
ShardedCollection = class {
|
|
@@ -5368,7 +6186,7 @@ var init_vault_group = __esm({
|
|
|
5368
6186
|
if (this.group.sharding.autoCreate === false) {
|
|
5369
6187
|
throw new UnknownShardError(key, this.group.name);
|
|
5370
6188
|
}
|
|
5371
|
-
vault = await this.group.createShard(key);
|
|
6189
|
+
vault = await this.group.createShard(key, this.group.sharding.regionOf?.(record));
|
|
5372
6190
|
} else {
|
|
5373
6191
|
vault = await this.group.openShard(key);
|
|
5374
6192
|
}
|
|
@@ -5558,159 +6376,6 @@ var init_vault_group = __esm({
|
|
|
5558
6376
|
}
|
|
5559
6377
|
});
|
|
5560
6378
|
|
|
5561
|
-
// src/federation/schema-manifest.ts
|
|
5562
|
-
function captureBlueprint(configure) {
|
|
5563
|
-
const recorded = [];
|
|
5564
|
-
const collectionStub = new Proxy(
|
|
5565
|
-
{},
|
|
5566
|
-
{
|
|
5567
|
-
get: () => () => collectionStub
|
|
5568
|
-
}
|
|
5569
|
-
);
|
|
5570
|
-
const proxy = new Proxy(
|
|
5571
|
-
{},
|
|
5572
|
-
{
|
|
5573
|
-
get: (_t, prop) => {
|
|
5574
|
-
if (prop === "collection") {
|
|
5575
|
-
return (name, opts) => {
|
|
5576
|
-
recorded.push({
|
|
5577
|
-
name,
|
|
5578
|
-
indexes: opts?.indexes ?? [],
|
|
5579
|
-
persistJsonSchema: !!opts?.persistJsonSchema
|
|
5580
|
-
});
|
|
5581
|
-
return collectionStub;
|
|
5582
|
-
};
|
|
5583
|
-
}
|
|
5584
|
-
return () => proxy;
|
|
5585
|
-
}
|
|
5586
|
-
}
|
|
5587
|
-
);
|
|
5588
|
-
configure(proxy);
|
|
5589
|
-
const sorted = [...recorded].sort((a, b) => a.name.localeCompare(b.name));
|
|
5590
|
-
const indexes = {};
|
|
5591
|
-
const persistJsonSchema = [];
|
|
5592
|
-
for (const c of sorted) {
|
|
5593
|
-
indexes[c.name] = c.indexes;
|
|
5594
|
-
if (c.persistJsonSchema) persistJsonSchema.push(c.name);
|
|
5595
|
-
}
|
|
5596
|
-
return {
|
|
5597
|
-
// `persistJsonSchema` is already name-sorted: it is populated while
|
|
5598
|
-
// iterating `sorted` (collections in name order).
|
|
5599
|
-
collections: sorted.map((c) => c.name),
|
|
5600
|
-
indexes,
|
|
5601
|
-
persistJsonSchema
|
|
5602
|
-
};
|
|
5603
|
-
}
|
|
5604
|
-
function canonical(value) {
|
|
5605
|
-
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
5606
|
-
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
5607
|
-
const obj = value;
|
|
5608
|
-
const keys = Object.keys(obj).sort();
|
|
5609
|
-
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonical(obj[k])}`).join(",")}}`;
|
|
5610
|
-
}
|
|
5611
|
-
async function fingerprintBlueprint(bp) {
|
|
5612
|
-
return sha256Hex(new TextEncoder().encode(canonical(bp)));
|
|
5613
|
-
}
|
|
5614
|
-
var init_schema_manifest = __esm({
|
|
5615
|
-
"src/federation/schema-manifest.ts"() {
|
|
5616
|
-
"use strict";
|
|
5617
|
-
init_crypto();
|
|
5618
|
-
}
|
|
5619
|
-
});
|
|
5620
|
-
|
|
5621
|
-
// src/federation/state-vault.ts
|
|
5622
|
-
var state_vault_exports = {};
|
|
5623
|
-
__export(state_vault_exports, {
|
|
5624
|
-
STATE_VAULT_NAME: () => STATE_VAULT_NAME,
|
|
5625
|
-
StateManagementVault: () => StateManagementVault
|
|
5626
|
-
});
|
|
5627
|
-
var REGISTRY, MANIFEST, EVENTS, StateManagementVault;
|
|
5628
|
-
var init_state_vault = __esm({
|
|
5629
|
-
"src/federation/state-vault.ts"() {
|
|
5630
|
-
"use strict";
|
|
5631
|
-
init_schema_manifest();
|
|
5632
|
-
init_constants();
|
|
5633
|
-
init_ulid();
|
|
5634
|
-
init_constants();
|
|
5635
|
-
REGISTRY = "vaultRegistry";
|
|
5636
|
-
MANIFEST = "schemaManifest";
|
|
5637
|
-
EVENTS = "deploymentEvents";
|
|
5638
|
-
StateManagementVault = class _StateManagementVault {
|
|
5639
|
-
constructor(registry, schemaManifest, events) {
|
|
5640
|
-
this.registry = registry;
|
|
5641
|
-
this.schemaManifest = schemaManifest;
|
|
5642
|
-
this.#events = events;
|
|
5643
|
-
}
|
|
5644
|
-
registry;
|
|
5645
|
-
schemaManifest;
|
|
5646
|
-
/**
|
|
5647
|
-
* The append-only deployment-events log is kept truly private so the raw
|
|
5648
|
-
* mutable Collection is never surfaced — events may only be written via
|
|
5649
|
-
* `appendEvent` and read via `queryEvents`. (`registry` and
|
|
5650
|
-
* `schemaManifest` are deliberately public: consumers read and write them.)
|
|
5651
|
-
*/
|
|
5652
|
-
#events;
|
|
5653
|
-
/** Idempotently open the reserved state vault and bind the three control-plane collections. */
|
|
5654
|
-
static async open(db) {
|
|
5655
|
-
const vault = await db.openVault(STATE_VAULT_NAME);
|
|
5656
|
-
return new _StateManagementVault(
|
|
5657
|
-
vault.collection(REGISTRY),
|
|
5658
|
-
vault.collection(MANIFEST),
|
|
5659
|
-
vault.collection(EVENTS)
|
|
5660
|
-
);
|
|
5661
|
-
}
|
|
5662
|
-
/** Read-only query over the append-only deployment-events log. */
|
|
5663
|
-
queryEvents() {
|
|
5664
|
-
return this.#events.query();
|
|
5665
|
-
}
|
|
5666
|
-
/**
|
|
5667
|
-
* Append a deployment event with a fresh unique (ULID) id. This is the
|
|
5668
|
-
* only write path to the events log; no update/delete is exposed.
|
|
5669
|
-
* Callers should treat failures as non-fatal — this method does not
|
|
5670
|
-
* swallow errors, so wrap the call site in try/catch where appropriate.
|
|
5671
|
-
*/
|
|
5672
|
-
async appendEvent(event) {
|
|
5673
|
-
const ts = event.ts ?? Date.now();
|
|
5674
|
-
const id = generateULID();
|
|
5675
|
-
await this.#events.put(id, { ...event, id, ts });
|
|
5676
|
-
}
|
|
5677
|
-
/**
|
|
5678
|
-
* Ensure a manifest row exists for `(templateName, template.version)`.
|
|
5679
|
-
* Safe to call repeatedly: the `fingerprint` is a deterministic hash of
|
|
5680
|
-
* the template's declared shape (stable across calls), though each call
|
|
5681
|
-
* refreshes `recordedAt`.
|
|
5682
|
-
*/
|
|
5683
|
-
async recordManifest(templateName, template) {
|
|
5684
|
-
const bp = captureBlueprint(template.configure);
|
|
5685
|
-
const fingerprint = await fingerprintBlueprint(bp);
|
|
5686
|
-
await this.schemaManifest.put(`${templateName}:${template.version}`, {
|
|
5687
|
-
templateName,
|
|
5688
|
-
version: template.version,
|
|
5689
|
-
collections: bp.collections,
|
|
5690
|
-
indexes: bp.indexes,
|
|
5691
|
-
persistJsonSchema: bp.persistJsonSchema,
|
|
5692
|
-
fingerprint,
|
|
5693
|
-
recordedAt: Date.now()
|
|
5694
|
-
});
|
|
5695
|
-
return fingerprint;
|
|
5696
|
-
}
|
|
5697
|
-
/**
|
|
5698
|
-
* True when `template`'s current declared shape does not match the recorded
|
|
5699
|
-
* manifest for `(templateName, template.version)`. Because shards carry no
|
|
5700
|
-
* schema state independent of their template, this catches "a template's
|
|
5701
|
-
* shape changed without bumping `version`" — not independent per-shard drift.
|
|
5702
|
-
* A missing manifest is treated as drift (nothing to verify against).
|
|
5703
|
-
*/
|
|
5704
|
-
async detectDrift(templateName, template) {
|
|
5705
|
-
const row = await this.schemaManifest.get(`${templateName}:${template.version}`);
|
|
5706
|
-
if (!row) return true;
|
|
5707
|
-
const current = await fingerprintBlueprint(captureBlueprint(template.configure));
|
|
5708
|
-
return current !== row.fingerprint;
|
|
5709
|
-
}
|
|
5710
|
-
};
|
|
5711
|
-
}
|
|
5712
|
-
});
|
|
5713
|
-
|
|
5714
6379
|
// src/index.ts
|
|
5715
6380
|
var src_exports = {};
|
|
5716
6381
|
__export(src_exports, {
|
|
@@ -5748,6 +6413,7 @@ __export(src_exports, {
|
|
|
5748
6413
|
DICT_COLLECTION_PREFIX: () => DICT_COLLECTION_PREFIX,
|
|
5749
6414
|
DIRECTORY_RECORD_ID: () => DIRECTORY_RECORD_ID,
|
|
5750
6415
|
DanglingReferenceError: () => DanglingReferenceError,
|
|
6416
|
+
DataResidencyError: () => DataResidencyError,
|
|
5751
6417
|
DecryptionError: () => DecryptionError,
|
|
5752
6418
|
DelegationTargetMissingError: () => DelegationTargetMissingError,
|
|
5753
6419
|
DerivationCapExceededError: () => DerivationCapExceededError,
|
|
@@ -5773,6 +6439,7 @@ __export(src_exports, {
|
|
|
5773
6439
|
GroupedQuery: () => GroupedQuery,
|
|
5774
6440
|
GroupedQueryN: () => GroupedQueryN,
|
|
5775
6441
|
INDEXED_STORE_POLICY: () => INDEXED_STORE_POLICY,
|
|
6442
|
+
IllegalTransitionError: () => IllegalTransitionError,
|
|
5776
6443
|
ImportCapabilityError: () => ImportCapabilityError,
|
|
5777
6444
|
IndexRequiredError: () => IndexRequiredError,
|
|
5778
6445
|
IndexWriteFailureError: () => IndexWriteFailureError,
|
|
@@ -5785,6 +6452,8 @@ __export(src_exports, {
|
|
|
5785
6452
|
LEDGER_DELTAS_COLLECTION: () => LEDGER_DELTAS_COLLECTION,
|
|
5786
6453
|
LedgerContentionError: () => LedgerContentionError,
|
|
5787
6454
|
LedgerStore: () => LedgerStore,
|
|
6455
|
+
LinkEndpointError: () => LinkEndpointError,
|
|
6456
|
+
LinkIntegrityError: () => LinkIntegrityError,
|
|
5788
6457
|
LocaleNotSpecifiedError: () => LocaleNotSpecifiedError,
|
|
5789
6458
|
Lru: () => Lru,
|
|
5790
6459
|
MAGIC_LINK_CONTENT_INFO_PREFIX: () => MAGIC_LINK_CONTENT_INFO_PREFIX,
|
|
@@ -5916,6 +6585,7 @@ __export(src_exports, {
|
|
|
5916
6585
|
canonicalJson: () => canonicalJson,
|
|
5917
6586
|
checkGate: () => checkGate,
|
|
5918
6587
|
clearDevUnlock: () => clearDevUnlock,
|
|
6588
|
+
compileSequenceFormat: () => compileSequenceFormat,
|
|
5919
6589
|
computePatch: () => computePatch,
|
|
5920
6590
|
coordinatedCutover: () => coordinatedCutover,
|
|
5921
6591
|
count: () => count,
|
|
@@ -5978,11 +6648,13 @@ __export(src_exports, {
|
|
|
5978
6648
|
isDictKeyDescriptor: () => isDictKeyDescriptor,
|
|
5979
6649
|
isDiscriminant: () => isDiscriminant,
|
|
5980
6650
|
isI18nTextDescriptor: () => isI18nTextDescriptor,
|
|
6651
|
+
isLinkCollectionName: () => isLinkCollectionName,
|
|
5981
6652
|
isMagicLinkGrantExpired: () => isMagicLinkGrantExpired,
|
|
5982
6653
|
isMoneyDescriptor: () => isMoneyDescriptor,
|
|
5983
6654
|
isMoneyString: () => isMoneyString,
|
|
5984
6655
|
isPreCompressed: () => isPreCompressed,
|
|
5985
6656
|
isPublicEnvelope: () => isPublicEnvelope,
|
|
6657
|
+
isRefArray: () => isRefArray,
|
|
5986
6658
|
isSessionAlive: () => isSessionAlive,
|
|
5987
6659
|
isStaticDictDescriptor: () => isStaticDictDescriptor,
|
|
5988
6660
|
isULID: () => isULID,
|
|
@@ -6036,6 +6708,7 @@ __export(src_exports, {
|
|
|
6036
6708
|
recoverUser: () => recoverUser,
|
|
6037
6709
|
reduceRecords: () => reduceRecords,
|
|
6038
6710
|
ref: () => ref,
|
|
6711
|
+
refArray: () => refArray,
|
|
6039
6712
|
removeAuthenticator: () => removeAuthenticator,
|
|
6040
6713
|
resetBrotliSupportCache: () => resetBrotliSupportCache,
|
|
6041
6714
|
resetJoinWarnings: () => resetJoinWarnings,
|
|
@@ -6063,6 +6736,8 @@ __export(src_exports, {
|
|
|
6063
6736
|
sha256Hex: () => sha256Hex3,
|
|
6064
6737
|
staticDict: () => staticDict,
|
|
6065
6738
|
sum: () => sum,
|
|
6739
|
+
tokenize: () => tokenize,
|
|
6740
|
+
transitionGuard: () => transitionGuard,
|
|
6066
6741
|
unwrapDeksFromBlob: () => unwrapDeksFromBlob,
|
|
6067
6742
|
unwrapDeksFromPaperEntry: () => unwrapDeksFromPaperEntry,
|
|
6068
6743
|
unwrapDeksFromShamirEntry: () => unwrapDeksFromShamirEntry,
|
|
@@ -6086,6 +6761,7 @@ __export(src_exports, {
|
|
|
6086
6761
|
withMetrics: () => withMetrics,
|
|
6087
6762
|
withOverlayedView: () => withOverlayedView,
|
|
6088
6763
|
withRetry: () => withRetry,
|
|
6764
|
+
withRollup: () => withRollup,
|
|
6089
6765
|
wrapBundleStore: () => wrapBundleStore,
|
|
6090
6766
|
wrapStore: () => wrapStore,
|
|
6091
6767
|
writeMagicLinkGrant: () => writeMagicLinkGrant,
|
|
@@ -6459,11 +7135,8 @@ async function compressBytes(data) {
|
|
|
6459
7135
|
if (typeof CompressionStream === "undefined") {
|
|
6460
7136
|
return { bytes: data, algorithm: "none" };
|
|
6461
7137
|
}
|
|
6462
|
-
const
|
|
6463
|
-
const
|
|
6464
|
-
await writer.write(data);
|
|
6465
|
-
await writer.close();
|
|
6466
|
-
const buf = await new Response(cs.readable).arrayBuffer();
|
|
7138
|
+
const piped = new Response(data).body.pipeThrough(new CompressionStream("gzip"));
|
|
7139
|
+
const buf = await new Response(piped).arrayBuffer();
|
|
6467
7140
|
return { bytes: new Uint8Array(buf), algorithm: "gzip" };
|
|
6468
7141
|
}
|
|
6469
7142
|
async function decompressBytes(data) {
|
|
@@ -6472,11 +7145,8 @@ async function decompressBytes(data) {
|
|
|
6472
7145
|
"[noy-db] DecompressionStream not available \u2014 cannot decompress blob chunk"
|
|
6473
7146
|
);
|
|
6474
7147
|
}
|
|
6475
|
-
const
|
|
6476
|
-
const
|
|
6477
|
-
await writer.write(data);
|
|
6478
|
-
await writer.close();
|
|
6479
|
-
const buf = await new Response(ds.readable).arrayBuffer();
|
|
7148
|
+
const piped = new Response(data).body.pipeThrough(new DecompressionStream("gzip"));
|
|
7149
|
+
const buf = await new Response(piped).arrayBuffer();
|
|
6480
7150
|
return new Uint8Array(buf);
|
|
6481
7151
|
}
|
|
6482
7152
|
function concatChunks(chunks) {
|
|
@@ -8008,6 +8678,14 @@ function routeStore(opts) {
|
|
|
8008
8678
|
const q = {};
|
|
8009
8679
|
for (const [k, v] of writeQueues) q[k] = v.writes.length;
|
|
8010
8680
|
return { overrides: ov, suspended: [...suspended], queued: q };
|
|
8681
|
+
},
|
|
8682
|
+
resolveBackend(vaultId) {
|
|
8683
|
+
if (opts.vaultRoutes) {
|
|
8684
|
+
for (const [prefix, s] of Object.entries(opts.vaultRoutes)) {
|
|
8685
|
+
if (vaultId.startsWith(prefix)) return s;
|
|
8686
|
+
}
|
|
8687
|
+
}
|
|
8688
|
+
return primary;
|
|
8011
8689
|
}
|
|
8012
8690
|
};
|
|
8013
8691
|
if (anyHas("listVaults")) {
|
|
@@ -8454,6 +9132,38 @@ init_crypto();
|
|
|
8454
9132
|
init_errors();
|
|
8455
9133
|
var SEQUENCE_COLLECTION = "_sequences";
|
|
8456
9134
|
var MAX_NEXT_ATTEMPTS = 16;
|
|
9135
|
+
var SEQ_FORMAT_TOKEN = /\{([^{}]*)\}/g;
|
|
9136
|
+
var SEQ_PAD_TOKEN = /^seq:0(\d+)$/;
|
|
9137
|
+
var SEQ_PARTITION_TOKEN = /^partition\.(\d+)$/;
|
|
9138
|
+
function compileSequenceFormat(format, series, partition) {
|
|
9139
|
+
const parts = partition ?? [];
|
|
9140
|
+
for (const m of format.matchAll(SEQ_FORMAT_TOKEN)) {
|
|
9141
|
+
const token = m[1] ?? "";
|
|
9142
|
+
if (token === "seq") continue;
|
|
9143
|
+
if (SEQ_PAD_TOKEN.test(token)) continue;
|
|
9144
|
+
const partMatch = SEQ_PARTITION_TOKEN.exec(token);
|
|
9145
|
+
if (partMatch) {
|
|
9146
|
+
const idx = Number(partMatch[1]);
|
|
9147
|
+
if (idx >= parts.length) {
|
|
9148
|
+
throw new ValidationError(
|
|
9149
|
+
`sequence("${series}"): format token "{${token}}" references partition index ${idx}, but only ${parts.length} partition component(s) were supplied.`
|
|
9150
|
+
);
|
|
9151
|
+
}
|
|
9152
|
+
continue;
|
|
9153
|
+
}
|
|
9154
|
+
throw new ValidationError(
|
|
9155
|
+
`sequence("${series}"): format contains unknown token "{${token}}". Accepted tokens: {seq}, {seq:0N}, {partition.i}.`
|
|
9156
|
+
);
|
|
9157
|
+
}
|
|
9158
|
+
return (serial) => format.replace(SEQ_FORMAT_TOKEN, (full, token) => {
|
|
9159
|
+
if (token === "seq") return String(serial);
|
|
9160
|
+
const padMatch = SEQ_PAD_TOKEN.exec(token);
|
|
9161
|
+
if (padMatch) return String(serial).padStart(Number(padMatch[1]), "0");
|
|
9162
|
+
const partMatch = SEQ_PARTITION_TOKEN.exec(token);
|
|
9163
|
+
if (partMatch) return String(parts[Number(partMatch[1])]);
|
|
9164
|
+
return full;
|
|
9165
|
+
});
|
|
9166
|
+
}
|
|
8457
9167
|
function resolveSequenceKey(series, opts) {
|
|
8458
9168
|
const partition = opts?.partition;
|
|
8459
9169
|
if (!partition || partition.length === 0) return series;
|
|
@@ -9476,15 +10186,15 @@ function isEquivalent(a, b) {
|
|
|
9476
10186
|
|
|
9477
10187
|
// src/history/history.ts
|
|
9478
10188
|
var HISTORY_COLLECTION = "_history";
|
|
9479
|
-
function matchesPrefix(id, collection,
|
|
9480
|
-
if (
|
|
9481
|
-
return id.startsWith(`${collection}:${
|
|
10189
|
+
function matchesPrefix(id, collection, recordId4) {
|
|
10190
|
+
if (recordId4) {
|
|
10191
|
+
return id.startsWith(`${collection}:${recordId4}:`);
|
|
9482
10192
|
}
|
|
9483
10193
|
return id.startsWith(`${collection}:`);
|
|
9484
10194
|
}
|
|
9485
|
-
async function getHistory(adapter, vault, collection,
|
|
10195
|
+
async function getHistory(adapter, vault, collection, recordId4, options) {
|
|
9486
10196
|
const allIds = await adapter.list(vault, HISTORY_COLLECTION);
|
|
9487
|
-
const matchingIds = allIds.filter((id) => matchesPrefix(id, collection,
|
|
10197
|
+
const matchingIds = allIds.filter((id) => matchesPrefix(id, collection, recordId4)).sort().reverse();
|
|
9488
10198
|
const entries = [];
|
|
9489
10199
|
for (const id of matchingIds) {
|
|
9490
10200
|
const envelope = await adapter.get(vault, HISTORY_COLLECTION, id);
|
|
@@ -9737,6 +10447,9 @@ init_ledger();
|
|
|
9737
10447
|
|
|
9738
10448
|
// src/refs.ts
|
|
9739
10449
|
init_errors();
|
|
10450
|
+
function isRefArray(desc) {
|
|
10451
|
+
return desc.isArray === true;
|
|
10452
|
+
}
|
|
9740
10453
|
var RefIntegrityError = class extends NoydbError {
|
|
9741
10454
|
collection;
|
|
9742
10455
|
id;
|
|
@@ -9773,6 +10486,17 @@ function ref(target, mode = "strict") {
|
|
|
9773
10486
|
}
|
|
9774
10487
|
return { target, mode };
|
|
9775
10488
|
}
|
|
10489
|
+
function refArray(target, mode = "strict") {
|
|
10490
|
+
if (target.includes("/")) {
|
|
10491
|
+
throw new RefScopeError(target);
|
|
10492
|
+
}
|
|
10493
|
+
if (!target || target.startsWith("_")) {
|
|
10494
|
+
throw new Error(
|
|
10495
|
+
`refArray(): target collection name must be non-empty and cannot start with '_' (reserved for internal collections). Got "${target}".`
|
|
10496
|
+
);
|
|
10497
|
+
}
|
|
10498
|
+
return { target, mode, isArray: true };
|
|
10499
|
+
}
|
|
9776
10500
|
var RefRegistry = class {
|
|
9777
10501
|
outbound = /* @__PURE__ */ new Map();
|
|
9778
10502
|
inbound = /* @__PURE__ */ new Map();
|
|
@@ -9797,7 +10521,7 @@ var RefRegistry = class {
|
|
|
9797
10521
|
for (const k of existingKeys) {
|
|
9798
10522
|
const a = existing[k];
|
|
9799
10523
|
const b = refs[k];
|
|
9800
|
-
if (!a || !b || a.target !== b.target || a.mode !== b.mode) {
|
|
10524
|
+
if (!a || !b || a.target !== b.target || a.mode !== b.mode || a.isArray !== b.isArray) {
|
|
9801
10525
|
throw new Error(
|
|
9802
10526
|
`RefRegistry: conflicting ref declarations for collection "${collection}" field "${k}"`
|
|
9803
10527
|
);
|
|
@@ -9805,34 +10529,172 @@ var RefRegistry = class {
|
|
|
9805
10529
|
}
|
|
9806
10530
|
return;
|
|
9807
10531
|
}
|
|
9808
|
-
this.outbound.set(collection, { ...refs });
|
|
9809
|
-
for (const [field, desc] of Object.entries(refs)) {
|
|
9810
|
-
const list = this.inbound.get(desc.target) ?? [];
|
|
9811
|
-
list.push({ collection, field, mode: desc.mode });
|
|
9812
|
-
this.inbound.set(desc.target, list);
|
|
10532
|
+
this.outbound.set(collection, { ...refs });
|
|
10533
|
+
for (const [field, desc] of Object.entries(refs)) {
|
|
10534
|
+
const list = this.inbound.get(desc.target) ?? [];
|
|
10535
|
+
list.push({ collection, field, mode: desc.mode, ...desc.isArray ? { isArray: true } : {} });
|
|
10536
|
+
this.inbound.set(desc.target, list);
|
|
10537
|
+
}
|
|
10538
|
+
}
|
|
10539
|
+
/** Get the outbound refs declared by a collection (or `{}` if none). */
|
|
10540
|
+
getOutbound(collection) {
|
|
10541
|
+
return this.outbound.get(collection) ?? {};
|
|
10542
|
+
}
|
|
10543
|
+
/** Get the inbound refs that target a given collection (or `[]`). */
|
|
10544
|
+
getInbound(target) {
|
|
10545
|
+
return this.inbound.get(target) ?? [];
|
|
10546
|
+
}
|
|
10547
|
+
/**
|
|
10548
|
+
* Iterate every (collection → refs) pair that has at least one
|
|
10549
|
+
* declared reference. Used by `checkIntegrity` to walk the full
|
|
10550
|
+
* universe of outbound refs without needing to track collection
|
|
10551
|
+
* names elsewhere.
|
|
10552
|
+
*/
|
|
10553
|
+
entries() {
|
|
10554
|
+
return [...this.outbound.entries()];
|
|
10555
|
+
}
|
|
10556
|
+
/** Clear the registry. Test-only escape hatch; never called from production code. */
|
|
10557
|
+
clear() {
|
|
10558
|
+
this.outbound.clear();
|
|
10559
|
+
this.inbound.clear();
|
|
10560
|
+
}
|
|
10561
|
+
};
|
|
10562
|
+
|
|
10563
|
+
// src/links/link-set.ts
|
|
10564
|
+
init_types();
|
|
10565
|
+
init_crypto();
|
|
10566
|
+
init_errors();
|
|
10567
|
+
var LINK_COLLECTION_PREFIX = "_links_";
|
|
10568
|
+
function linkCollectionName(name) {
|
|
10569
|
+
return `${LINK_COLLECTION_PREFIX}${name}`;
|
|
10570
|
+
}
|
|
10571
|
+
function isLinkCollectionName(name) {
|
|
10572
|
+
return name.startsWith(LINK_COLLECTION_PREFIX);
|
|
10573
|
+
}
|
|
10574
|
+
function linkRowKey(aId, bId) {
|
|
10575
|
+
return `${encodeURIComponent(aId)}|${encodeURIComponent(bId)}`;
|
|
10576
|
+
}
|
|
10577
|
+
var LinkSet = class {
|
|
10578
|
+
constructor(adapter, vault, name, spec, encrypted, getDEK, actor, emitter, endpointExists) {
|
|
10579
|
+
this.adapter = adapter;
|
|
10580
|
+
this.vault = vault;
|
|
10581
|
+
this.name = name;
|
|
10582
|
+
this.spec = spec;
|
|
10583
|
+
this.encrypted = encrypted;
|
|
10584
|
+
this.getDEK = getDEK;
|
|
10585
|
+
this.actor = actor;
|
|
10586
|
+
this.emitter = emitter;
|
|
10587
|
+
this.endpointExists = endpointExists;
|
|
10588
|
+
this.collName = linkCollectionName(name);
|
|
10589
|
+
}
|
|
10590
|
+
adapter;
|
|
10591
|
+
vault;
|
|
10592
|
+
name;
|
|
10593
|
+
spec;
|
|
10594
|
+
encrypted;
|
|
10595
|
+
getDEK;
|
|
10596
|
+
actor;
|
|
10597
|
+
emitter;
|
|
10598
|
+
endpointExists;
|
|
10599
|
+
collName;
|
|
10600
|
+
dekPromise = null;
|
|
10601
|
+
dek() {
|
|
10602
|
+
if (!this.dekPromise) this.dekPromise = this.getDEK(this.collName);
|
|
10603
|
+
return this.dekPromise;
|
|
10604
|
+
}
|
|
10605
|
+
async encryptEntry(entry, version) {
|
|
10606
|
+
const json = JSON.stringify(entry);
|
|
10607
|
+
const base = { _noydb: NOYDB_FORMAT_VERSION, _v: version, _ts: (/* @__PURE__ */ new Date()).toISOString(), _by: this.actor };
|
|
10608
|
+
if (!this.encrypted) return { ...base, _iv: "", _data: json };
|
|
10609
|
+
const { iv, data } = await encrypt(json, await this.dek());
|
|
10610
|
+
return { ...base, _iv: iv, _data: data };
|
|
10611
|
+
}
|
|
10612
|
+
async decryptEntry(env) {
|
|
10613
|
+
const json = this.encrypted ? await decrypt(env._iv, env._data, await this.dek()) : env._data;
|
|
10614
|
+
return JSON.parse(json);
|
|
10615
|
+
}
|
|
10616
|
+
async connect(aId, bId, meta) {
|
|
10617
|
+
if (!await this.endpointExists(this.spec.a, aId)) {
|
|
10618
|
+
throw new LinkEndpointError(this.name, this.spec.a, aId);
|
|
10619
|
+
}
|
|
10620
|
+
if (!await this.endpointExists(this.spec.b, bId)) {
|
|
10621
|
+
throw new LinkEndpointError(this.name, this.spec.b, bId);
|
|
10622
|
+
}
|
|
10623
|
+
const key = linkRowKey(aId, bId);
|
|
10624
|
+
const entry = meta !== void 0 ? { a: aId, b: bId, meta } : { a: aId, b: bId };
|
|
10625
|
+
const existing = await this.adapter.get(this.vault, this.collName, key);
|
|
10626
|
+
const env = await this.encryptEntry(entry, (existing?._v ?? 0) + 1);
|
|
10627
|
+
await this.adapter.put(this.vault, this.collName, key, env, existing?._v);
|
|
10628
|
+
this.emitter.emit("change", { vault: this.vault, collection: this.collName, id: key, action: "put" });
|
|
10629
|
+
}
|
|
10630
|
+
async disconnect(aId, bId) {
|
|
10631
|
+
const key = linkRowKey(aId, bId);
|
|
10632
|
+
const existing = await this.adapter.get(this.vault, this.collName, key);
|
|
10633
|
+
if (!existing) return;
|
|
10634
|
+
await this.adapter.delete(this.vault, this.collName, key);
|
|
10635
|
+
this.emitter.emit("change", { vault: this.vault, collection: this.collName, id: key, action: "delete" });
|
|
10636
|
+
}
|
|
10637
|
+
async has(aId, bId) {
|
|
10638
|
+
return await this.adapter.get(this.vault, this.collName, linkRowKey(aId, bId)) !== null;
|
|
10639
|
+
}
|
|
10640
|
+
async of(id) {
|
|
10641
|
+
const rows = await this.list();
|
|
10642
|
+
return rows.filter((r) => r.a === id || r.b === id);
|
|
10643
|
+
}
|
|
10644
|
+
async list() {
|
|
10645
|
+
const keys = await this.adapter.list(this.vault, this.collName);
|
|
10646
|
+
const out = [];
|
|
10647
|
+
for (const key of keys) {
|
|
10648
|
+
const env = await this.adapter.get(this.vault, this.collName, key);
|
|
10649
|
+
if (!env) continue;
|
|
10650
|
+
const e = await this.decryptEntry(env);
|
|
10651
|
+
out.push(e.meta !== void 0 ? { a: e.a, b: e.b, meta: e.meta } : { a: e.a, b: e.b });
|
|
9813
10652
|
}
|
|
10653
|
+
return out;
|
|
9814
10654
|
}
|
|
9815
|
-
|
|
9816
|
-
|
|
9817
|
-
|
|
10655
|
+
// ── Vault-internal cascade helpers ──────────────────────────────────
|
|
10656
|
+
/** @internal — rows where the deleted endpoint id matches the relevant slot. */
|
|
10657
|
+
async _rowsTouchingEndpoint(collection, id) {
|
|
10658
|
+
const rows = await this.list();
|
|
10659
|
+
return rows.filter(
|
|
10660
|
+
(r) => this.spec.a === collection && r.a === id || this.spec.b === collection && r.b === id
|
|
10661
|
+
);
|
|
9818
10662
|
}
|
|
9819
|
-
/**
|
|
9820
|
-
|
|
9821
|
-
return this.
|
|
10663
|
+
/** @internal — the storage collection name (for tx pre-image capture). */
|
|
10664
|
+
get _collectionName() {
|
|
10665
|
+
return this.collName;
|
|
9822
10666
|
}
|
|
9823
|
-
|
|
9824
|
-
|
|
9825
|
-
|
|
9826
|
-
|
|
9827
|
-
|
|
9828
|
-
|
|
9829
|
-
|
|
9830
|
-
|
|
10667
|
+
};
|
|
10668
|
+
var LinkEndpointError = class extends NoydbError {
|
|
10669
|
+
link;
|
|
10670
|
+
endpoint;
|
|
10671
|
+
missingId;
|
|
10672
|
+
constructor(link, endpoint, missingId) {
|
|
10673
|
+
super(
|
|
10674
|
+
"LINK_ENDPOINT",
|
|
10675
|
+
`link("${link}").connect: endpoint "${endpoint}" has no record "${missingId}".`
|
|
10676
|
+
);
|
|
10677
|
+
this.name = "LinkEndpointError";
|
|
10678
|
+
this.link = link;
|
|
10679
|
+
this.endpoint = endpoint;
|
|
10680
|
+
this.missingId = missingId;
|
|
9831
10681
|
}
|
|
9832
|
-
|
|
9833
|
-
|
|
9834
|
-
|
|
9835
|
-
|
|
10682
|
+
};
|
|
10683
|
+
var LinkIntegrityError = class extends NoydbError {
|
|
10684
|
+
link;
|
|
10685
|
+
endpoint;
|
|
10686
|
+
id;
|
|
10687
|
+
count;
|
|
10688
|
+
constructor(link, endpoint, id, count2) {
|
|
10689
|
+
super(
|
|
10690
|
+
"LINK_INTEGRITY",
|
|
10691
|
+
`Cannot delete "${endpoint}"/"${id}": ${count2} link(s) in "${link}" still reference it (onDelete: 'strict').`
|
|
10692
|
+
);
|
|
10693
|
+
this.name = "LinkIntegrityError";
|
|
10694
|
+
this.link = link;
|
|
10695
|
+
this.endpoint = endpoint;
|
|
10696
|
+
this.id = id;
|
|
10697
|
+
this.count = count2;
|
|
9836
10698
|
}
|
|
9837
10699
|
};
|
|
9838
10700
|
|
|
@@ -11967,309 +12829,114 @@ async function resolveManagedSecret(store, vault, provider) {
|
|
|
11967
12829
|
globalThis.crypto.getRandomValues(random);
|
|
11968
12830
|
const sealed = await provider.seal(random);
|
|
11969
12831
|
await saveSealedPassphrase(store, vault, { providerId: provider.id, sealed });
|
|
11970
|
-
return bytesToBase644(random);
|
|
11971
|
-
}
|
|
11972
|
-
|
|
11973
|
-
// src/team/peer-recover.ts
|
|
11974
|
-
init_types();
|
|
11975
|
-
init_crypto();
|
|
11976
|
-
init_errors();
|
|
11977
|
-
var ADMIN_RECOVERABLE_TARGETS = ["operator", "viewer", "client", "admin"];
|
|
11978
|
-
function canRecover(callerRole, targetRole) {
|
|
11979
|
-
if (callerRole === "owner") return true;
|
|
11980
|
-
if (callerRole === "admin") return ADMIN_RECOVERABLE_TARGETS.includes(targetRole);
|
|
11981
|
-
return false;
|
|
11982
|
-
}
|
|
11983
|
-
async function recoverUser(store, vault, callerKeyring, options) {
|
|
11984
|
-
const env = await store.get(vault, "_keyring", options.userId);
|
|
11985
|
-
if (!env) {
|
|
11986
|
-
throw new NoAccessError(
|
|
11987
|
-
`recoverUser: user "${options.userId}" has no keyring in vault "${vault}".`
|
|
11988
|
-
);
|
|
11989
|
-
}
|
|
11990
|
-
const target = JSON.parse(env._data);
|
|
11991
|
-
const targetRole = options.role ?? target.role;
|
|
11992
|
-
if (!canRecover(callerKeyring.role, targetRole)) {
|
|
11993
|
-
throw new PermissionDeniedError(
|
|
11994
|
-
`Role "${callerKeyring.role}" cannot recover role "${targetRole}"`
|
|
11995
|
-
);
|
|
11996
|
-
}
|
|
11997
|
-
if (!canRecover(callerKeyring.role, target.role)) {
|
|
11998
|
-
throw new PermissionDeniedError(
|
|
11999
|
-
`Role "${callerKeyring.role}" cannot recover role "${target.role}"`
|
|
12000
|
-
);
|
|
12001
|
-
}
|
|
12002
|
-
for (const coll of Object.keys(target.deks)) {
|
|
12003
|
-
if (!callerKeyring.deks.has(coll)) {
|
|
12004
|
-
throw new PrivilegeEscalationError(coll);
|
|
12005
|
-
}
|
|
12006
|
-
}
|
|
12007
|
-
if (options.validatePassphrase && !options.allowWeakPassphrase) {
|
|
12008
|
-
assertStrongPassphrase(options.passphrase, options.passphrasePolicy);
|
|
12009
|
-
}
|
|
12010
|
-
const newSalt = generateSalt();
|
|
12011
|
-
const newKek = await deriveKey(options.passphrase, newSalt);
|
|
12012
|
-
const wrappedDeks = {};
|
|
12013
|
-
for (const coll of Object.keys(target.deks)) {
|
|
12014
|
-
const callerDek = callerKeyring.deks.get(coll);
|
|
12015
|
-
if (!callerDek) {
|
|
12016
|
-
throw new PrivilegeEscalationError(coll);
|
|
12017
|
-
}
|
|
12018
|
-
wrappedDeks[coll] = await wrapKey(callerDek, newKek);
|
|
12019
|
-
}
|
|
12020
|
-
const canary = await mintKeyringCanary(newKek);
|
|
12021
|
-
const next = {
|
|
12022
|
-
...target,
|
|
12023
|
-
_noydb_keyring: NOYDB_KEYRING_VERSION,
|
|
12024
|
-
role: targetRole,
|
|
12025
|
-
display_name: options.displayName ?? target.display_name,
|
|
12026
|
-
deks: wrappedDeks,
|
|
12027
|
-
salt: bufferToBase64(newSalt),
|
|
12028
|
-
granted_by: callerKeyring.userId,
|
|
12029
|
-
authenticators: [],
|
|
12030
|
-
canary
|
|
12031
|
-
};
|
|
12032
|
-
const envelope = {
|
|
12033
|
-
_noydb: 1,
|
|
12034
|
-
_v: 1,
|
|
12035
|
-
_ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12036
|
-
_iv: "",
|
|
12037
|
-
_data: JSON.stringify(next)
|
|
12038
|
-
};
|
|
12039
|
-
await store.put(vault, "_keyring", options.userId, envelope);
|
|
12040
|
-
}
|
|
12041
|
-
|
|
12042
|
-
// src/index.ts
|
|
12043
|
-
init_errors();
|
|
12044
|
-
|
|
12045
|
-
// src/noydb.ts
|
|
12046
|
-
init_errors();
|
|
12047
|
-
init_constants();
|
|
12048
|
-
init_ulid();
|
|
12049
|
-
init_public_envelope();
|
|
12050
|
-
|
|
12051
|
-
// src/vault.ts
|
|
12052
|
-
init_types();
|
|
12053
|
-
|
|
12054
|
-
// src/collection.ts
|
|
12055
|
-
init_types();
|
|
12056
|
-
|
|
12057
|
-
// src/crdt/strategy.ts
|
|
12058
|
-
var NOT_ENABLED = new Error(
|
|
12059
|
-
'CRDT mode requires the CRDT strategy. Import `{ withCrdt }` from "@noy-db/hub/crdt" and pass it to `createNoydb({ crdtStrategy: withCrdt() })`.'
|
|
12060
|
-
);
|
|
12061
|
-
var NO_CRDT = {
|
|
12062
|
-
buildLwwMapState() {
|
|
12063
|
-
throw NOT_ENABLED;
|
|
12064
|
-
},
|
|
12065
|
-
buildRgaState() {
|
|
12066
|
-
throw NOT_ENABLED;
|
|
12067
|
-
},
|
|
12068
|
-
mergeCrdtStates() {
|
|
12069
|
-
throw NOT_ENABLED;
|
|
12070
|
-
},
|
|
12071
|
-
resolveCrdtSnapshot() {
|
|
12072
|
-
throw NOT_ENABLED;
|
|
12073
|
-
}
|
|
12074
|
-
};
|
|
12075
|
-
|
|
12076
|
-
// src/i18n/core.ts
|
|
12077
|
-
init_errors();
|
|
12078
|
-
|
|
12079
|
-
// src/i18n/policy.ts
|
|
12080
|
-
function resolvePolicy(onMissing, layer) {
|
|
12081
|
-
const explicit = onMissing && typeof onMissing === "object" ? onMissing[layer] : void 0;
|
|
12082
|
-
const scalar = typeof onMissing === "string" ? onMissing : void 0;
|
|
12083
|
-
const layerDefault = layer === "guard" ? "substitute" : void 0;
|
|
12084
|
-
return explicit ?? layerDefault ?? scalar ?? "throw";
|
|
12085
|
-
}
|
|
12086
|
-
|
|
12087
|
-
// src/i18n/core.ts
|
|
12088
|
-
function i18nText(options) {
|
|
12089
|
-
return { _noydbI18nText: true, options };
|
|
12090
|
-
}
|
|
12091
|
-
function isI18nTextDescriptor(x) {
|
|
12092
|
-
return typeof x === "object" && x !== null && x._noydbI18nText === true;
|
|
12093
|
-
}
|
|
12094
|
-
function validateI18nTextValue(value, field, descriptor) {
|
|
12095
|
-
const { options } = descriptor;
|
|
12096
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
12097
|
-
throw new MissingTranslationError(
|
|
12098
|
-
field,
|
|
12099
|
-
options.languages,
|
|
12100
|
-
`Field "${field}" must be a { [locale]: string } map, got ${typeof value}.`
|
|
12101
|
-
);
|
|
12102
|
-
}
|
|
12103
|
-
const map = value;
|
|
12104
|
-
for (const [locale, v] of Object.entries(map)) {
|
|
12105
|
-
if (typeof v !== "string") {
|
|
12106
|
-
throw new MissingTranslationError(
|
|
12107
|
-
field,
|
|
12108
|
-
[locale],
|
|
12109
|
-
`Field "${field}": locale "${locale}" must be a string, got ${typeof v}.`
|
|
12110
|
-
);
|
|
12111
|
-
}
|
|
12112
|
-
}
|
|
12113
|
-
const { required } = options;
|
|
12114
|
-
if (required === "all") {
|
|
12115
|
-
const missing = options.languages.filter(
|
|
12116
|
-
(lang) => !(lang in map) || map[lang] === ""
|
|
12117
|
-
);
|
|
12118
|
-
if (missing.length > 0) {
|
|
12119
|
-
throw new MissingTranslationError(
|
|
12120
|
-
field,
|
|
12121
|
-
missing,
|
|
12122
|
-
`Field "${field}" requires all declared languages. Missing: ${missing.join(", ")}.`
|
|
12123
|
-
);
|
|
12124
|
-
}
|
|
12125
|
-
} else if (required === "any") {
|
|
12126
|
-
const present = options.languages.some(
|
|
12127
|
-
(lang) => lang in map && map[lang] !== ""
|
|
12128
|
-
);
|
|
12129
|
-
if (!present) {
|
|
12130
|
-
throw new MissingTranslationError(
|
|
12131
|
-
field,
|
|
12132
|
-
options.languages,
|
|
12133
|
-
`Field "${field}" requires at least one declared language. None present.`
|
|
12134
|
-
);
|
|
12135
|
-
}
|
|
12136
|
-
} else {
|
|
12137
|
-
const requiredList = required;
|
|
12138
|
-
const missing = requiredList.filter(
|
|
12139
|
-
(lang) => !(lang in map) || map[lang] === ""
|
|
12140
|
-
);
|
|
12141
|
-
if (missing.length > 0) {
|
|
12142
|
-
throw new MissingTranslationError(
|
|
12143
|
-
field,
|
|
12144
|
-
missing,
|
|
12145
|
-
`Field "${field}" requires: ${requiredList.join(", ")}. Missing: ${missing.join(", ")}.`
|
|
12146
|
-
);
|
|
12147
|
-
}
|
|
12148
|
-
}
|
|
12149
|
-
}
|
|
12150
|
-
function toChain(fallback) {
|
|
12151
|
-
return Array.isArray(fallback) ? fallback : fallback ? [fallback] : [];
|
|
12152
|
-
}
|
|
12153
|
-
function pickFromChain(value, chain) {
|
|
12154
|
-
for (const fb of chain) {
|
|
12155
|
-
if (fb === "any") {
|
|
12156
|
-
const any = Object.values(value).find((v) => v !== "");
|
|
12157
|
-
if (any !== void 0) return any;
|
|
12158
|
-
} else if (value[fb] !== void 0 && value[fb] !== "") {
|
|
12159
|
-
return value[fb];
|
|
12160
|
-
}
|
|
12161
|
-
}
|
|
12162
|
-
return void 0;
|
|
12163
|
-
}
|
|
12164
|
-
function resolveI18nText(value, locale, fallback, field, opts) {
|
|
12165
|
-
if (locale === "raw") {
|
|
12166
|
-
return value;
|
|
12167
|
-
}
|
|
12168
|
-
if (!locale) {
|
|
12169
|
-
throw new LocaleNotSpecifiedError(field ?? "<unknown>");
|
|
12170
|
-
}
|
|
12171
|
-
if (value[locale] !== void 0 && value[locale] !== "") {
|
|
12172
|
-
return value[locale];
|
|
12173
|
-
}
|
|
12174
|
-
const policy = opts?.policy ?? "throw";
|
|
12175
|
-
const callerChain = toChain(fallback);
|
|
12176
|
-
const callerHit = pickFromChain(value, callerChain);
|
|
12177
|
-
if (callerHit !== void 0) return callerHit;
|
|
12178
|
-
if (policy === "substitute") {
|
|
12179
|
-
const subHit = pickFromChain(value, toChain(opts?.substitute));
|
|
12180
|
-
if (subHit !== void 0) return subHit;
|
|
12181
|
-
}
|
|
12182
|
-
if (policy === "throw") {
|
|
12183
|
-
throw new LocaleNotSpecifiedError(
|
|
12184
|
-
field ?? "<unknown>",
|
|
12185
|
-
`No translation available for locale "${locale}"` + (callerChain.length > 0 ? ` or fallback chain [${callerChain.join(", ")}]` : "") + "."
|
|
12832
|
+
return bytesToBase644(random);
|
|
12833
|
+
}
|
|
12834
|
+
|
|
12835
|
+
// src/team/peer-recover.ts
|
|
12836
|
+
init_types();
|
|
12837
|
+
init_crypto();
|
|
12838
|
+
init_errors();
|
|
12839
|
+
var ADMIN_RECOVERABLE_TARGETS = ["operator", "viewer", "client", "admin"];
|
|
12840
|
+
function canRecover(callerRole, targetRole) {
|
|
12841
|
+
if (callerRole === "owner") return true;
|
|
12842
|
+
if (callerRole === "admin") return ADMIN_RECOVERABLE_TARGETS.includes(targetRole);
|
|
12843
|
+
return false;
|
|
12844
|
+
}
|
|
12845
|
+
async function recoverUser(store, vault, callerKeyring, options) {
|
|
12846
|
+
const env = await store.get(vault, "_keyring", options.userId);
|
|
12847
|
+
if (!env) {
|
|
12848
|
+
throw new NoAccessError(
|
|
12849
|
+
`recoverUser: user "${options.userId}" has no keyring in vault "${vault}".`
|
|
12186
12850
|
);
|
|
12187
12851
|
}
|
|
12188
|
-
|
|
12189
|
-
|
|
12190
|
-
|
|
12191
|
-
|
|
12192
|
-
|
|
12193
|
-
|
|
12194
|
-
const restPath = path.slice(arrayIdx + 3);
|
|
12195
|
-
const arr = obj[arrayKey];
|
|
12196
|
-
if (!Array.isArray(arr)) return [];
|
|
12197
|
-
return arr.flatMap((item) => {
|
|
12198
|
-
if (!item || typeof item !== "object" || Array.isArray(item)) return [];
|
|
12199
|
-
return getAtPath(item, restPath);
|
|
12200
|
-
});
|
|
12852
|
+
const target = JSON.parse(env._data);
|
|
12853
|
+
const targetRole = options.role ?? target.role;
|
|
12854
|
+
if (!canRecover(callerKeyring.role, targetRole)) {
|
|
12855
|
+
throw new PermissionDeniedError(
|
|
12856
|
+
`Role "${callerKeyring.role}" cannot recover role "${targetRole}"`
|
|
12857
|
+
);
|
|
12201
12858
|
}
|
|
12202
|
-
|
|
12203
|
-
|
|
12204
|
-
|
|
12205
|
-
|
|
12206
|
-
const nested = obj[head];
|
|
12207
|
-
if (!nested || typeof nested !== "object" || Array.isArray(nested)) return [];
|
|
12208
|
-
return getAtPath(nested, rest);
|
|
12859
|
+
if (!canRecover(callerKeyring.role, target.role)) {
|
|
12860
|
+
throw new PermissionDeniedError(
|
|
12861
|
+
`Role "${callerKeyring.role}" cannot recover role "${target.role}"`
|
|
12862
|
+
);
|
|
12209
12863
|
}
|
|
12210
|
-
const
|
|
12211
|
-
|
|
12212
|
-
|
|
12213
|
-
|
|
12214
|
-
const dotIdx = path.indexOf(".");
|
|
12215
|
-
if (dotIdx !== -1) {
|
|
12216
|
-
const head = path.slice(0, dotIdx);
|
|
12217
|
-
const rest = path.slice(dotIdx + 1);
|
|
12218
|
-
const nested = obj[head];
|
|
12219
|
-
if (!nested || typeof nested !== "object" || Array.isArray(nested)) return;
|
|
12220
|
-
setAtPathInPlace(nested, rest, value);
|
|
12221
|
-
return;
|
|
12864
|
+
for (const coll of Object.keys(target.deks)) {
|
|
12865
|
+
if (!callerKeyring.deks.has(coll)) {
|
|
12866
|
+
throw new PrivilegeEscalationError(coll);
|
|
12867
|
+
}
|
|
12222
12868
|
}
|
|
12223
|
-
|
|
12224
|
-
|
|
12225
|
-
function applyAtPath(obj, path, locale, fallback, opts) {
|
|
12226
|
-
const arrayIdx = path.indexOf("[].");
|
|
12227
|
-
if (arrayIdx !== -1) {
|
|
12228
|
-
const arrayKey = path.slice(0, arrayIdx);
|
|
12229
|
-
const restPath = path.slice(arrayIdx + 3);
|
|
12230
|
-
const arr = obj[arrayKey];
|
|
12231
|
-
if (!Array.isArray(arr)) return obj;
|
|
12232
|
-
return {
|
|
12233
|
-
...obj,
|
|
12234
|
-
[arrayKey]: arr.map((item) => {
|
|
12235
|
-
if (!item || typeof item !== "object" || Array.isArray(item)) return item;
|
|
12236
|
-
return applyAtPath(item, restPath, locale, fallback, opts);
|
|
12237
|
-
})
|
|
12238
|
-
};
|
|
12869
|
+
if (options.validatePassphrase && !options.allowWeakPassphrase) {
|
|
12870
|
+
assertStrongPassphrase(options.passphrase, options.passphrasePolicy);
|
|
12239
12871
|
}
|
|
12240
|
-
const
|
|
12241
|
-
|
|
12242
|
-
|
|
12243
|
-
|
|
12244
|
-
const
|
|
12245
|
-
if (!
|
|
12246
|
-
|
|
12247
|
-
|
|
12248
|
-
|
|
12249
|
-
};
|
|
12872
|
+
const newSalt = generateSalt();
|
|
12873
|
+
const newKek = await deriveKey(options.passphrase, newSalt);
|
|
12874
|
+
const wrappedDeks = {};
|
|
12875
|
+
for (const coll of Object.keys(target.deks)) {
|
|
12876
|
+
const callerDek = callerKeyring.deks.get(coll);
|
|
12877
|
+
if (!callerDek) {
|
|
12878
|
+
throw new PrivilegeEscalationError(coll);
|
|
12879
|
+
}
|
|
12880
|
+
wrappedDeks[coll] = await wrapKey(callerDek, newKek);
|
|
12250
12881
|
}
|
|
12251
|
-
const
|
|
12252
|
-
|
|
12253
|
-
|
|
12254
|
-
|
|
12255
|
-
|
|
12256
|
-
|
|
12882
|
+
const canary = await mintKeyringCanary(newKek);
|
|
12883
|
+
const next = {
|
|
12884
|
+
...target,
|
|
12885
|
+
_noydb_keyring: NOYDB_KEYRING_VERSION,
|
|
12886
|
+
role: targetRole,
|
|
12887
|
+
display_name: options.displayName ?? target.display_name,
|
|
12888
|
+
deks: wrappedDeks,
|
|
12889
|
+
salt: bufferToBase64(newSalt),
|
|
12890
|
+
granted_by: callerKeyring.userId,
|
|
12891
|
+
authenticators: [],
|
|
12892
|
+
canary
|
|
12893
|
+
};
|
|
12894
|
+
const envelope = {
|
|
12895
|
+
_noydb: 1,
|
|
12896
|
+
_v: 1,
|
|
12897
|
+
_ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12898
|
+
_iv: "",
|
|
12899
|
+
_data: JSON.stringify(next)
|
|
12257
12900
|
};
|
|
12901
|
+
await store.put(vault, "_keyring", options.userId, envelope);
|
|
12258
12902
|
}
|
|
12259
|
-
|
|
12260
|
-
|
|
12261
|
-
|
|
12262
|
-
|
|
12263
|
-
|
|
12264
|
-
|
|
12265
|
-
|
|
12266
|
-
|
|
12267
|
-
|
|
12268
|
-
|
|
12269
|
-
|
|
12903
|
+
|
|
12904
|
+
// src/index.ts
|
|
12905
|
+
init_errors();
|
|
12906
|
+
|
|
12907
|
+
// src/noydb.ts
|
|
12908
|
+
init_errors();
|
|
12909
|
+
init_constants();
|
|
12910
|
+
init_ulid();
|
|
12911
|
+
init_public_envelope();
|
|
12912
|
+
|
|
12913
|
+
// src/vault.ts
|
|
12914
|
+
init_types();
|
|
12915
|
+
|
|
12916
|
+
// src/collection.ts
|
|
12917
|
+
init_types();
|
|
12918
|
+
|
|
12919
|
+
// src/crdt/strategy.ts
|
|
12920
|
+
var NOT_ENABLED = new Error(
|
|
12921
|
+
'CRDT mode requires the CRDT strategy. Import `{ withCrdt }` from "@noy-db/hub/crdt" and pass it to `createNoydb({ crdtStrategy: withCrdt() })`.'
|
|
12922
|
+
);
|
|
12923
|
+
var NO_CRDT = {
|
|
12924
|
+
buildLwwMapState() {
|
|
12925
|
+
throw NOT_ENABLED;
|
|
12926
|
+
},
|
|
12927
|
+
buildRgaState() {
|
|
12928
|
+
throw NOT_ENABLED;
|
|
12929
|
+
},
|
|
12930
|
+
mergeCrdtStates() {
|
|
12931
|
+
throw NOT_ENABLED;
|
|
12932
|
+
},
|
|
12933
|
+
resolveCrdtSnapshot() {
|
|
12934
|
+
throw NOT_ENABLED;
|
|
12270
12935
|
}
|
|
12271
|
-
|
|
12272
|
-
|
|
12936
|
+
};
|
|
12937
|
+
|
|
12938
|
+
// src/collection.ts
|
|
12939
|
+
init_core();
|
|
12273
12940
|
|
|
12274
12941
|
// src/i18n/dictionary.ts
|
|
12275
12942
|
init_types();
|
|
@@ -12840,6 +13507,21 @@ function formatCurrency(decimal, currency, scale, locale) {
|
|
|
12840
13507
|
});
|
|
12841
13508
|
return fmt.format(decimal);
|
|
12842
13509
|
}
|
|
13510
|
+
function moneyScaledValue(stored, desc) {
|
|
13511
|
+
let raw;
|
|
13512
|
+
if (desc.mode === "fixed") {
|
|
13513
|
+
raw = stored;
|
|
13514
|
+
} else {
|
|
13515
|
+
if (!isMoneyValueObject(stored)) return null;
|
|
13516
|
+
raw = stored.amount;
|
|
13517
|
+
}
|
|
13518
|
+
if (typeof raw !== "string" && typeof raw !== "number") return null;
|
|
13519
|
+
try {
|
|
13520
|
+
return BigInt(String(raw));
|
|
13521
|
+
} catch {
|
|
13522
|
+
return null;
|
|
13523
|
+
}
|
|
13524
|
+
}
|
|
12843
13525
|
function decodeValue(stored, desc) {
|
|
12844
13526
|
let currency;
|
|
12845
13527
|
let scaledIntString;
|
|
@@ -12959,6 +13641,7 @@ var NO_I18N = {
|
|
|
12959
13641
|
};
|
|
12960
13642
|
|
|
12961
13643
|
// src/collection.ts
|
|
13644
|
+
init_policy();
|
|
12962
13645
|
init_crypto();
|
|
12963
13646
|
init_errors();
|
|
12964
13647
|
init_tiers();
|
|
@@ -13010,6 +13693,7 @@ init_predicate();
|
|
|
13010
13693
|
// src/query/join.ts
|
|
13011
13694
|
init_predicate();
|
|
13012
13695
|
init_errors();
|
|
13696
|
+
init_core();
|
|
13013
13697
|
var DEFAULT_JOIN_MAX_ROWS = 5e4;
|
|
13014
13698
|
var JOIN_WARN_FRACTION = 0.8;
|
|
13015
13699
|
function coerceRefKey(value) {
|
|
@@ -13037,15 +13721,15 @@ function warnCeilingApproaching(target, side, rows, maxRows) {
|
|
|
13037
13721
|
`[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.`
|
|
13038
13722
|
);
|
|
13039
13723
|
}
|
|
13040
|
-
function applyJoins(rows, joins, context) {
|
|
13724
|
+
function applyJoins(rows, joins, context, locale) {
|
|
13041
13725
|
if (joins.length === 0) return [...rows];
|
|
13042
13726
|
let result = [...rows];
|
|
13043
13727
|
for (const leg of joins) {
|
|
13044
|
-
result = applyOneJoin(result, leg, context);
|
|
13728
|
+
result = applyOneJoin(result, leg, context, locale);
|
|
13045
13729
|
}
|
|
13046
13730
|
return result;
|
|
13047
13731
|
}
|
|
13048
|
-
function applyOneJoin(leftRows, leg, context) {
|
|
13732
|
+
function applyOneJoin(leftRows, leg, context, locale) {
|
|
13049
13733
|
if (leg.isDictJoin) {
|
|
13050
13734
|
const dictSource = context.resolveDictSource?.(leg.field);
|
|
13051
13735
|
if (!dictSource) {
|
|
@@ -13100,24 +13784,27 @@ function applyOneJoin(leftRows, leg, context) {
|
|
|
13100
13784
|
if (rightSnapshot.length > maxRows * JOIN_WARN_FRACTION) {
|
|
13101
13785
|
warnCeilingApproaching(leg.target, "right", rightSnapshot.length, maxRows);
|
|
13102
13786
|
}
|
|
13787
|
+
const effLocale = locale ?? context.defaultLocale;
|
|
13788
|
+
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;
|
|
13103
13789
|
const strategy = leg.strategy ?? (source.lookupById ? "nested" : "hash");
|
|
13104
13790
|
if (strategy === "nested" && source.lookupById) {
|
|
13105
13791
|
const lookup = (id) => source.lookupById?.(id);
|
|
13106
|
-
return nestedLoopJoin(leftRows, leg, lookup);
|
|
13792
|
+
return nestedLoopJoin(leftRows, leg, lookup, i18nResolve);
|
|
13107
13793
|
}
|
|
13108
|
-
return hashJoin(leftRows, leg, rightSnapshot);
|
|
13794
|
+
return hashJoin(leftRows, leg, rightSnapshot, i18nResolve);
|
|
13109
13795
|
}
|
|
13110
|
-
function nestedLoopJoin(leftRows, leg, lookupById) {
|
|
13796
|
+
function nestedLoopJoin(leftRows, leg, lookupById, i18nResolve) {
|
|
13111
13797
|
const out = [];
|
|
13112
13798
|
for (const left of leftRows) {
|
|
13113
13799
|
const rawId = readPath(left, leg.field);
|
|
13114
13800
|
const key = coerceRefKey(rawId);
|
|
13115
|
-
|
|
13801
|
+
let right = key === null ? void 0 : lookupById(key);
|
|
13802
|
+
if (i18nResolve && right !== void 0) right = i18nResolve(right);
|
|
13116
13803
|
out.push(attachJoin(left, leg, right, rawId));
|
|
13117
13804
|
}
|
|
13118
13805
|
return out;
|
|
13119
13806
|
}
|
|
13120
|
-
function hashJoin(leftRows, leg, rightSnapshot) {
|
|
13807
|
+
function hashJoin(leftRows, leg, rightSnapshot, i18nResolve) {
|
|
13121
13808
|
const rightMap = /* @__PURE__ */ new Map();
|
|
13122
13809
|
for (const record of rightSnapshot) {
|
|
13123
13810
|
const rawId = readPath(record, "id");
|
|
@@ -13130,7 +13817,8 @@ function hashJoin(leftRows, leg, rightSnapshot) {
|
|
|
13130
13817
|
for (const left of leftRows) {
|
|
13131
13818
|
const rawId = readPath(left, leg.field);
|
|
13132
13819
|
const key = coerceRefKey(rawId);
|
|
13133
|
-
|
|
13820
|
+
let right = key === null ? void 0 : rightMap.get(key);
|
|
13821
|
+
if (i18nResolve && right !== void 0) right = i18nResolve(right);
|
|
13134
13822
|
out.push(attachJoin(left, leg, right, rawId));
|
|
13135
13823
|
}
|
|
13136
13824
|
return out;
|
|
@@ -13428,11 +14116,16 @@ var Query = class _Query {
|
|
|
13428
14116
|
this.predicates
|
|
13429
14117
|
);
|
|
13430
14118
|
}
|
|
13431
|
-
/**
|
|
13432
|
-
|
|
14119
|
+
/**
|
|
14120
|
+
* Sort by a field. Subsequent calls are tie-breakers. Pass
|
|
14121
|
+
* `{ by: 'label' }` to sort a `dictKey`/`staticDict` field by its resolved
|
|
14122
|
+
* label at the query locale instead of the stored code (#285).
|
|
14123
|
+
*/
|
|
14124
|
+
orderBy(field, direction = "asc", opts) {
|
|
14125
|
+
const entry = opts?.by === "label" ? { field, direction, by: "label" } : { field, direction };
|
|
13433
14126
|
return new _Query(
|
|
13434
14127
|
this.source,
|
|
13435
|
-
{ ...this.plan, orderBy: [...this.plan.orderBy,
|
|
14128
|
+
{ ...this.plan, orderBy: [...this.plan.orderBy, entry] },
|
|
13436
14129
|
this.joinContext,
|
|
13437
14130
|
this.aggregateStrategy,
|
|
13438
14131
|
this.predicates
|
|
@@ -13635,16 +14328,21 @@ var Query = class _Query {
|
|
|
13635
14328
|
* carries any join legs, they are applied after `where` / `orderBy`
|
|
13636
14329
|
* / `limit` / `offset` narrow the left set. See the `.join()` doc
|
|
13637
14330
|
* for the ordering rationale.
|
|
14331
|
+
*
|
|
14332
|
+
* `opts.locale` (#285 §3) resolves JOINED right-side i18n fields at the
|
|
14333
|
+
* `join` layer to that locale; without it, the owning collection's default
|
|
14334
|
+
* locale applies, and a locale-less query leaves joined i18n fields raw.
|
|
14335
|
+
* (Left/base i18n fields are resolved by `get`/`list`, not here.)
|
|
13638
14336
|
*/
|
|
13639
|
-
toArray() {
|
|
13640
|
-
const base = this.decodeMoney(executePlanWithSource(this.source, this.plan, this.joinContext));
|
|
14337
|
+
toArray(opts) {
|
|
14338
|
+
const base = this.decodeMoney(executePlanWithSource(this.source, this.plan, this.joinContext, opts?.locale));
|
|
13641
14339
|
if (this.plan.joins.length === 0) return base;
|
|
13642
14340
|
if (!this.joinContext) {
|
|
13643
14341
|
throw new Error(
|
|
13644
14342
|
`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.`
|
|
13645
14343
|
);
|
|
13646
14344
|
}
|
|
13647
|
-
return applyJoins(base, this.plan.joins, this.joinContext);
|
|
14345
|
+
return applyJoins(base, this.plan.joins, this.joinContext, opts?.locale);
|
|
13648
14346
|
}
|
|
13649
14347
|
/**
|
|
13650
14348
|
* Decode this source's money fields on read (stored scaled-int → canonical
|
|
@@ -13663,9 +14361,9 @@ var Query = class _Query {
|
|
|
13663
14361
|
if (!moneyFields || Object.keys(moneyFields).length === 0) return records;
|
|
13664
14362
|
return records.map((r) => decodeMoneyFields(r, moneyFields, "raw"));
|
|
13665
14363
|
}
|
|
13666
|
-
/** Return the first matching record, or null. Joins are applied. */
|
|
13667
|
-
first() {
|
|
13668
|
-
const arr = this.limit(1).toArray();
|
|
14364
|
+
/** Return the first matching record, or null. Joins are applied. `opts.locale` resolves joined i18n fields (#285 §3). */
|
|
14365
|
+
first(opts) {
|
|
14366
|
+
const arr = this.limit(1).toArray(opts);
|
|
13669
14367
|
return arr[0] ?? null;
|
|
13670
14368
|
}
|
|
13671
14369
|
/**
|
|
@@ -13908,7 +14606,7 @@ var Query = class _Query {
|
|
|
13908
14606
|
return serializePlan(this.plan);
|
|
13909
14607
|
}
|
|
13910
14608
|
};
|
|
13911
|
-
function executePlanWithSource(source, plan, joinContext) {
|
|
14609
|
+
function executePlanWithSource(source, plan, joinContext, locale) {
|
|
13912
14610
|
const hasCrossJoins = plan.clauses.some((c) => c.type === "crossJoin");
|
|
13913
14611
|
let result;
|
|
13914
14612
|
if (hasCrossJoins) {
|
|
@@ -13923,7 +14621,8 @@ function executePlanWithSource(source, plan, joinContext) {
|
|
|
13923
14621
|
result = remainingClauses.length === 0 ? [...candidates] : filterRecords(candidates, remainingClauses, fnViewDecoder(source));
|
|
13924
14622
|
}
|
|
13925
14623
|
if (plan.orderBy.length > 0) {
|
|
13926
|
-
|
|
14624
|
+
const labelMaps = buildOrderLabelMaps(plan.orderBy, joinContext, locale);
|
|
14625
|
+
result = sortRecords(result, plan.orderBy, source.moneyFields, labelMaps);
|
|
13927
14626
|
}
|
|
13928
14627
|
if (plan.offset > 0) {
|
|
13929
14628
|
result = result.slice(plan.offset);
|
|
@@ -14080,17 +14779,55 @@ function applyCrossJoin(leftRel, clause, rightSource) {
|
|
|
14080
14779
|
}
|
|
14081
14780
|
return expanded;
|
|
14082
14781
|
}
|
|
14083
|
-
function sortRecords(records, orderBy) {
|
|
14782
|
+
function sortRecords(records, orderBy, moneyFields, labelMaps) {
|
|
14084
14783
|
return [...records].sort((a, b) => {
|
|
14085
|
-
for (const { field, direction } of orderBy) {
|
|
14086
|
-
|
|
14087
|
-
|
|
14088
|
-
const
|
|
14784
|
+
for (const { field, direction, by } of orderBy) {
|
|
14785
|
+
let av = readField(a, field);
|
|
14786
|
+
let bv = readField(b, field);
|
|
14787
|
+
const labelMap = by === "label" ? labelMaps?.get(field) : void 0;
|
|
14788
|
+
if (labelMap) {
|
|
14789
|
+
av = (typeof av === "string" ? labelMap.get(av) : void 0) ?? av;
|
|
14790
|
+
bv = (typeof bv === "string" ? labelMap.get(bv) : void 0) ?? bv;
|
|
14791
|
+
const cmp2 = compareValues(av, bv);
|
|
14792
|
+
if (cmp2 !== 0) return direction === "asc" ? cmp2 : -cmp2;
|
|
14793
|
+
continue;
|
|
14794
|
+
}
|
|
14795
|
+
const desc = moneyFields?.[field];
|
|
14796
|
+
const cmp = desc ? compareMoney(av, bv, desc) : compareValues(av, bv);
|
|
14089
14797
|
if (cmp !== 0) return direction === "asc" ? cmp : -cmp;
|
|
14090
14798
|
}
|
|
14091
14799
|
return 0;
|
|
14092
14800
|
});
|
|
14093
14801
|
}
|
|
14802
|
+
function buildOrderLabelMaps(orderBy, joinContext, locale) {
|
|
14803
|
+
if (!joinContext?.resolveDictSource) return void 0;
|
|
14804
|
+
const resolveDict = joinContext.resolveDictSource.bind(joinContext);
|
|
14805
|
+
let maps;
|
|
14806
|
+
for (const { field, by } of orderBy) {
|
|
14807
|
+
if (by !== "label") continue;
|
|
14808
|
+
const dictSource = resolveDict(field);
|
|
14809
|
+
if (!dictSource) continue;
|
|
14810
|
+
const loc = locale ?? dictSource.displayLocale;
|
|
14811
|
+
if (loc === void 0) continue;
|
|
14812
|
+
const codeToLabel = /* @__PURE__ */ new Map();
|
|
14813
|
+
for (const entry of dictSource.snapshot()) {
|
|
14814
|
+
const k = entry["key"];
|
|
14815
|
+
const labels = entry["labels"];
|
|
14816
|
+
const label = labels?.[loc];
|
|
14817
|
+
if (typeof k === "string" && typeof label === "string") codeToLabel.set(k, label);
|
|
14818
|
+
}
|
|
14819
|
+
;
|
|
14820
|
+
(maps ??= /* @__PURE__ */ new Map()).set(field, codeToLabel);
|
|
14821
|
+
}
|
|
14822
|
+
return maps;
|
|
14823
|
+
}
|
|
14824
|
+
function compareMoney(a, b, desc) {
|
|
14825
|
+
const av = moneyScaledValue(a, desc);
|
|
14826
|
+
const bv = moneyScaledValue(b, desc);
|
|
14827
|
+
if (av === null) return bv === null ? 0 : 1;
|
|
14828
|
+
if (bv === null) return -1;
|
|
14829
|
+
return av < bv ? -1 : av > bv ? 1 : 0;
|
|
14830
|
+
}
|
|
14094
14831
|
function readField(record, field) {
|
|
14095
14832
|
if (record === null || record === void 0) return void 0;
|
|
14096
14833
|
if (!field.includes(".")) {
|
|
@@ -14879,8 +15616,8 @@ function coerceRefKey2(value) {
|
|
|
14879
15616
|
|
|
14880
15617
|
// src/indexing/persisted-indexes.ts
|
|
14881
15618
|
var IDX_PREFIX = "_idx/";
|
|
14882
|
-
function encodeIdxId(field,
|
|
14883
|
-
return `${IDX_PREFIX}${field}/${
|
|
15619
|
+
function encodeIdxId(field, recordId4) {
|
|
15620
|
+
return `${IDX_PREFIX}${field}/${recordId4}`;
|
|
14884
15621
|
}
|
|
14885
15622
|
function decodeIdxId(id) {
|
|
14886
15623
|
if (!id.startsWith(IDX_PREFIX)) return null;
|
|
@@ -14888,9 +15625,9 @@ function decodeIdxId(id) {
|
|
|
14888
15625
|
const firstSlash = rest.indexOf("/");
|
|
14889
15626
|
if (firstSlash <= 0) return null;
|
|
14890
15627
|
const field = rest.slice(0, firstSlash);
|
|
14891
|
-
const
|
|
14892
|
-
if (
|
|
14893
|
-
return { field, recordId:
|
|
15628
|
+
const recordId4 = rest.slice(firstSlash + 1);
|
|
15629
|
+
if (recordId4.length === 0) return null;
|
|
15630
|
+
return { field, recordId: recordId4 };
|
|
14894
15631
|
}
|
|
14895
15632
|
|
|
14896
15633
|
// src/indexing/lazy-builder.ts
|
|
@@ -15063,6 +15800,74 @@ var DISABLED_STATE = {
|
|
|
15063
15800
|
getPersistedIndexes: () => null
|
|
15064
15801
|
};
|
|
15065
15802
|
|
|
15803
|
+
// src/search/tokenize.ts
|
|
15804
|
+
var WORD = /[\p{L}\p{N}]+/gu;
|
|
15805
|
+
var tokenize = (text) => {
|
|
15806
|
+
if (!text) return [];
|
|
15807
|
+
return text.normalize("NFKC").toLowerCase().match(WORD) ?? [];
|
|
15808
|
+
};
|
|
15809
|
+
|
|
15810
|
+
// src/search/scan.ts
|
|
15811
|
+
var K1 = 1.2;
|
|
15812
|
+
var B = 0.75;
|
|
15813
|
+
function fieldText(record, field) {
|
|
15814
|
+
const v = record[field];
|
|
15815
|
+
if (typeof v === "string") return v;
|
|
15816
|
+
if (v === null || v === void 0) return "";
|
|
15817
|
+
if (typeof v === "number" || typeof v === "boolean") return String(v);
|
|
15818
|
+
return "";
|
|
15819
|
+
}
|
|
15820
|
+
function searchScan(entries, field, query, opts = {}, tokenizer = tokenize) {
|
|
15821
|
+
const queryTerms = tokenizer(query);
|
|
15822
|
+
if (queryTerms.length === 0) return [];
|
|
15823
|
+
const match = opts.match ?? "any";
|
|
15824
|
+
const usePrefix = opts.prefix ?? false;
|
|
15825
|
+
const exactTerms = usePrefix ? queryTerms.slice(0, -1) : queryTerms;
|
|
15826
|
+
const prefixTerm = usePrefix ? queryTerms[queryTerms.length - 1] : void 0;
|
|
15827
|
+
const docs = entries.map((e) => ({ id: e.id, record: e.record, terms: tokenizer(fieldText(e.record, field)) }));
|
|
15828
|
+
const N = docs.length || 1;
|
|
15829
|
+
const df = /* @__PURE__ */ new Map();
|
|
15830
|
+
let totalLen = 0;
|
|
15831
|
+
for (const d of docs) {
|
|
15832
|
+
totalLen += d.terms.length;
|
|
15833
|
+
for (const t of new Set(d.terms)) df.set(t, (df.get(t) ?? 0) + 1);
|
|
15834
|
+
}
|
|
15835
|
+
const avgdl = totalLen / N || 1;
|
|
15836
|
+
let prefixDf = 0;
|
|
15837
|
+
if (prefixTerm !== void 0) {
|
|
15838
|
+
for (const d of docs) {
|
|
15839
|
+
if (d.terms.some((t) => t.startsWith(prefixTerm))) prefixDf++;
|
|
15840
|
+
}
|
|
15841
|
+
}
|
|
15842
|
+
const requiredCount = exactTerms.length + (prefixTerm !== void 0 ? 1 : 0);
|
|
15843
|
+
const results = [];
|
|
15844
|
+
for (const d of docs) {
|
|
15845
|
+
const tf = /* @__PURE__ */ new Map();
|
|
15846
|
+
for (const t of d.terms) tf.set(t, (tf.get(t) ?? 0) + 1);
|
|
15847
|
+
const matched = [];
|
|
15848
|
+
for (const qt of exactTerms) {
|
|
15849
|
+
const c = tf.get(qt) ?? 0;
|
|
15850
|
+
if (c > 0) matched.push({ tf: c, df: df.get(qt) ?? 0 });
|
|
15851
|
+
}
|
|
15852
|
+
if (prefixTerm !== void 0) {
|
|
15853
|
+
let ptf = 0;
|
|
15854
|
+
for (const [t, c] of tf) if (t.startsWith(prefixTerm)) ptf += c;
|
|
15855
|
+
if (ptf > 0) matched.push({ tf: ptf, df: prefixDf });
|
|
15856
|
+
}
|
|
15857
|
+
if (matched.length === 0) continue;
|
|
15858
|
+
if (match === "all" && matched.length < requiredCount) continue;
|
|
15859
|
+
let score = 0;
|
|
15860
|
+
for (const m of matched) {
|
|
15861
|
+
const idf = Math.log(1 + (N - m.df + 0.5) / (m.df + 0.5));
|
|
15862
|
+
const denom = m.tf + K1 * (1 - B + B * (d.terms.length / avgdl));
|
|
15863
|
+
score += idf * (m.tf * (K1 + 1) / (denom || 1));
|
|
15864
|
+
}
|
|
15865
|
+
results.push({ id: d.id, score, record: d.record });
|
|
15866
|
+
}
|
|
15867
|
+
results.sort((a, b) => b.score - a.score);
|
|
15868
|
+
return opts.limit !== void 0 ? results.slice(0, opts.limit) : results;
|
|
15869
|
+
}
|
|
15870
|
+
|
|
15066
15871
|
// src/collection.ts
|
|
15067
15872
|
init_errors();
|
|
15068
15873
|
|
|
@@ -15780,6 +16585,15 @@ async function resolveStaleOnRead(accessor, outputCollection, id) {
|
|
|
15780
16585
|
}
|
|
15781
16586
|
|
|
15782
16587
|
// src/collection.ts
|
|
16588
|
+
function selfWriteFieldEqual(a, b) {
|
|
16589
|
+
if (a === b) return true;
|
|
16590
|
+
if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
|
|
16591
|
+
try {
|
|
16592
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
16593
|
+
} catch {
|
|
16594
|
+
return false;
|
|
16595
|
+
}
|
|
16596
|
+
}
|
|
15783
16597
|
var fallbackWarned = /* @__PURE__ */ new Set();
|
|
15784
16598
|
function warnOnceFallback(adapterName) {
|
|
15785
16599
|
if (fallbackWarned.has(adapterName)) return;
|
|
@@ -16714,6 +17528,111 @@ var Collection = class {
|
|
|
16714
17528
|
* output (carries `_derivedFrom`) — defensive guard against missed
|
|
16715
17529
|
* cycle detection.
|
|
16716
17530
|
*/
|
|
17531
|
+
/**
|
|
17532
|
+
* @internal #376 — the RAW stored record (canonical-money form, i18n maps
|
|
17533
|
+
* intact), WITHOUT the locale resolution `get()` applies. Used as the
|
|
17534
|
+
* patch base for self-write reverse-denorm so writing back never clobbers
|
|
17535
|
+
* an i18n map or re-quantizes money incorrectly. Returns null for
|
|
17536
|
+
* missing / tombstoned records.
|
|
17537
|
+
*/
|
|
17538
|
+
async _getStoredRecord(id) {
|
|
17539
|
+
let raw;
|
|
17540
|
+
if (this.lazy && this.lru) {
|
|
17541
|
+
const cached = this.lru.get(id);
|
|
17542
|
+
if (cached) raw = cached.record;
|
|
17543
|
+
else {
|
|
17544
|
+
const env = await this.adapter.get(this.vault, this.name, id);
|
|
17545
|
+
if (!env || isTombstone(env, this.encrypted)) return null;
|
|
17546
|
+
raw = await this.decryptRecord(env, { id });
|
|
17547
|
+
if (raw === null) return null;
|
|
17548
|
+
this.lru.set(id, { record: raw, version: env._v }, estimateRecordBytes(raw));
|
|
17549
|
+
}
|
|
17550
|
+
} else {
|
|
17551
|
+
await this.ensureHydrated();
|
|
17552
|
+
raw = this.cache.get(id)?.record ?? null;
|
|
17553
|
+
}
|
|
17554
|
+
if (raw === null) return null;
|
|
17555
|
+
return canonicalizeStoredMoney(raw, this.moneyFields);
|
|
17556
|
+
}
|
|
17557
|
+
/**
|
|
17558
|
+
* @internal #376 — ids of records whose top-level `field` equals `value`.
|
|
17559
|
+
* Uses the FK index when the field is indexed (O(matches)); otherwise a
|
|
17560
|
+
* linear scan (O(N) — fine for small child sets; index the FK to scale).
|
|
17561
|
+
*/
|
|
17562
|
+
async _findMatchingIds(field, value) {
|
|
17563
|
+
const hit = this.getIndexes()?.lookupEqual(field, value);
|
|
17564
|
+
if (hit) return [...hit];
|
|
17565
|
+
const target = String(value);
|
|
17566
|
+
const matches = (rec) => {
|
|
17567
|
+
const fv = rec[field];
|
|
17568
|
+
return (typeof fv === "string" || typeof fv === "number") && String(fv) === target;
|
|
17569
|
+
};
|
|
17570
|
+
if (!this.lazy) {
|
|
17571
|
+
await this.ensureHydrated();
|
|
17572
|
+
const out2 = [];
|
|
17573
|
+
for (const [rid, e] of this.cache) {
|
|
17574
|
+
if (matches(e.record)) out2.push(rid);
|
|
17575
|
+
}
|
|
17576
|
+
return out2;
|
|
17577
|
+
}
|
|
17578
|
+
const ids = await this.adapter.list(this.vault, this.name);
|
|
17579
|
+
const out = [];
|
|
17580
|
+
for (const rid of ids) {
|
|
17581
|
+
const raw = await this._getStoredRecord(rid);
|
|
17582
|
+
if (raw !== null && matches(raw)) out.push(rid);
|
|
17583
|
+
}
|
|
17584
|
+
return out;
|
|
17585
|
+
}
|
|
17586
|
+
/**
|
|
17587
|
+
* @internal #376 slice 2 — recompute a rollup aggregate onto the parent.
|
|
17588
|
+
* Gathers every child of `parentId`, runs `compute`, and patches only the
|
|
17589
|
+
* rollup `field` onto the parent's raw stored record (value-equality
|
|
17590
|
+
* guarded). No-op when the parent record does not exist.
|
|
17591
|
+
*/
|
|
17592
|
+
async recomputeRollup(spec, parentId) {
|
|
17593
|
+
if (this.derivationSource === void 0 || spec.rollup === void 0) return;
|
|
17594
|
+
const { from, key, field, compute } = spec.rollup;
|
|
17595
|
+
const into = spec.source;
|
|
17596
|
+
const intoColl = this.derivationSource.getCollection(into);
|
|
17597
|
+
const base = await intoColl._getStoredRecord(parentId);
|
|
17598
|
+
if (base === null) return;
|
|
17599
|
+
const fromColl = this.derivationSource.getCollection(from);
|
|
17600
|
+
const childIds = await fromColl._findMatchingIds(key, parentId);
|
|
17601
|
+
const children = [];
|
|
17602
|
+
for (const cid of childIds) {
|
|
17603
|
+
const c = await fromColl.get(cid);
|
|
17604
|
+
if (c !== null && c !== void 0) children.push(c);
|
|
17605
|
+
}
|
|
17606
|
+
const newValue = compute(children);
|
|
17607
|
+
if (selfWriteFieldEqual(base[field], newValue)) return;
|
|
17608
|
+
const patched = { ...base, [field]: newValue };
|
|
17609
|
+
const txCtx = this.derivationSource.getActiveTxContext();
|
|
17610
|
+
if (txCtx !== null) {
|
|
17611
|
+
const prior = await this.adapter.get(this.vault, into, parentId);
|
|
17612
|
+
txCtx._executed.push({
|
|
17613
|
+
op: { type: "put", vaultName: this.vault, collectionName: into, id: parentId },
|
|
17614
|
+
priorEnvelope: prior
|
|
17615
|
+
});
|
|
17616
|
+
}
|
|
17617
|
+
await intoColl.put(parentId, patched);
|
|
17618
|
+
}
|
|
17619
|
+
/**
|
|
17620
|
+
* @internal #376 slice 2 — fire any rollups for which THIS collection is the
|
|
17621
|
+
* child `from`, recomputing the affected parent after a child delete. Called
|
|
17622
|
+
* from the delete path with the just-removed record's key value. Other
|
|
17623
|
+
* derivation kinds do not react to deletes (unchanged).
|
|
17624
|
+
*/
|
|
17625
|
+
async dispatchRollupsOnDelete(deleted) {
|
|
17626
|
+
if (this.derivationSource === void 0) return;
|
|
17627
|
+
const registry = this.derivationSource.registry();
|
|
17628
|
+
const rec = deleted;
|
|
17629
|
+
for (const { spec } of registry.strategiesForSource(this.name)) {
|
|
17630
|
+
if (!spec.rollup || spec.rollup.from !== this.name) continue;
|
|
17631
|
+
const kv = rec[spec.rollup.key];
|
|
17632
|
+
if (typeof kv !== "string" && typeof kv !== "number") continue;
|
|
17633
|
+
await this.recomputeRollup(spec, String(kv));
|
|
17634
|
+
}
|
|
17635
|
+
}
|
|
16717
17636
|
async dispatchDerivations(id, record, version) {
|
|
16718
17637
|
if (this.derivationSource === void 0) return;
|
|
16719
17638
|
const incoming = canonicalizeStoredMoney(record, this.moneyFields);
|
|
@@ -16724,29 +17643,60 @@ var Collection = class {
|
|
|
16724
17643
|
let DerivationExecutor2 = null;
|
|
16725
17644
|
for (const { spec, strategyHash } of strategies) {
|
|
16726
17645
|
const mode = typeof spec.lifecycle === "string" ? spec.lifecycle : spec.lifecycle.mode;
|
|
16727
|
-
if (
|
|
16728
|
-
if (
|
|
16729
|
-
|
|
16730
|
-
|
|
16731
|
-
|
|
16732
|
-
|
|
16733
|
-
if (spec.source === this.name) {
|
|
16734
|
-
sourceWithId = { ...incoming, id };
|
|
17646
|
+
if (spec.rollup) {
|
|
17647
|
+
if (mode !== "eager") continue;
|
|
17648
|
+
let parentId;
|
|
17649
|
+
if (this.name === spec.rollup.from) {
|
|
17650
|
+
const kv = incoming[spec.rollup.key];
|
|
17651
|
+
parentId = typeof kv === "string" || typeof kv === "number" ? String(kv) : null;
|
|
16735
17652
|
} else {
|
|
16736
|
-
|
|
16737
|
-
|
|
16738
|
-
|
|
16739
|
-
|
|
17653
|
+
parentId = id;
|
|
17654
|
+
}
|
|
17655
|
+
if (parentId !== null) await this.recomputeRollup(spec, parentId);
|
|
17656
|
+
continue;
|
|
17657
|
+
}
|
|
17658
|
+
const isSource = spec.source === this.name;
|
|
17659
|
+
const isSibling = !isSource && (spec.sources?.includes(this.name) ?? false);
|
|
17660
|
+
const trigger = !isSource && !isSibling ? spec.triggerBy?.find((t) => t.collection === this.name) : void 0;
|
|
17661
|
+
const runs = [];
|
|
17662
|
+
if (isSource) {
|
|
17663
|
+
runs.push({ input: { ...incoming, id }, base: incoming, runId: id, version });
|
|
17664
|
+
} else if (isSibling) {
|
|
17665
|
+
const p = await this.derivationSource.getCollection(spec.source).get(id);
|
|
17666
|
+
if (p !== null && p !== void 0) {
|
|
17667
|
+
const raw = await this.derivationSource.getCollection(spec.source)._getStoredRecord(id);
|
|
17668
|
+
runs.push({ input: { ...p, id }, base: raw ?? p, runId: id, version: 0 });
|
|
17669
|
+
}
|
|
17670
|
+
} else if (trigger) {
|
|
17671
|
+
const srcColl = this.derivationSource.getCollection(spec.source);
|
|
17672
|
+
const ids = await srcColl._findMatchingIds(trigger.on, id);
|
|
17673
|
+
if (trigger.maxFanout !== void 0 && ids.length > trigger.maxFanout) {
|
|
17674
|
+
throw new DerivationCapExceededError(`triggerBy ${this.name}\u2192${spec.source}`, ids.length, trigger.maxFanout);
|
|
16740
17675
|
}
|
|
17676
|
+
for (const sid of ids) {
|
|
17677
|
+
const raw = await srcColl._getStoredRecord(sid);
|
|
17678
|
+
if (raw === null) continue;
|
|
17679
|
+
runs.push({ input: { ...raw, id: sid }, base: raw, runId: sid, version: 0 });
|
|
17680
|
+
}
|
|
17681
|
+
}
|
|
17682
|
+
if (runs.length === 0) continue;
|
|
17683
|
+
if (mode !== "eager") {
|
|
17684
|
+
for (const run of runs) await markStale(registry, spec, run.runId);
|
|
17685
|
+
continue;
|
|
17686
|
+
}
|
|
17687
|
+
if (DerivationExecutor2 === null) {
|
|
17688
|
+
({ DerivationExecutor: DerivationExecutor2 } = await Promise.resolve().then(() => (init_executor2(), executor_exports2)));
|
|
17689
|
+
}
|
|
17690
|
+
for (const run of runs) {
|
|
16741
17691
|
const ctx = { vault: this.derivationSource.getReadOnlyFacade() };
|
|
16742
|
-
const result = await DerivationExecutor2.run(spec,
|
|
17692
|
+
const result = await DerivationExecutor2.run(spec, run.input, run.version, strategyHash, ctx);
|
|
16743
17693
|
for (const key of Object.keys(spec.outputs)) {
|
|
16744
17694
|
const out = result.outputs[key];
|
|
16745
17695
|
if (!out) continue;
|
|
16746
17696
|
if (out.kind === "failed") {
|
|
16747
17697
|
const err = out.error;
|
|
16748
17698
|
if (spec.strict) throw err;
|
|
16749
|
-
console.warn(`[derivation] output "${key}" for source "${spec.source}" id="${
|
|
17699
|
+
console.warn(`[derivation] output "${key}" for source "${spec.source}" id="${run.runId}" failed:`, err);
|
|
16750
17700
|
continue;
|
|
16751
17701
|
}
|
|
16752
17702
|
const outSpec = spec.outputs[key];
|
|
@@ -16759,7 +17709,7 @@ var Collection = class {
|
|
|
16759
17709
|
this.adapter,
|
|
16760
17710
|
this.vault,
|
|
16761
17711
|
spec.source,
|
|
16762
|
-
|
|
17712
|
+
run.runId,
|
|
16763
17713
|
key
|
|
16764
17714
|
);
|
|
16765
17715
|
const prevKeys = new Set(prior?.keys ?? []);
|
|
@@ -16786,7 +17736,7 @@ var Collection = class {
|
|
|
16786
17736
|
}
|
|
16787
17737
|
await saveFanoutSidecar2(this.adapter, this.vault, {
|
|
16788
17738
|
source: spec.source,
|
|
16789
|
-
sourceId:
|
|
17739
|
+
sourceId: run.runId,
|
|
16790
17740
|
outputKey: key,
|
|
16791
17741
|
outputCollection: outSpec.collection,
|
|
16792
17742
|
keys: newKeysList
|
|
@@ -16794,25 +17744,44 @@ var Collection = class {
|
|
|
16794
17744
|
continue;
|
|
16795
17745
|
}
|
|
16796
17746
|
if (out.skipped === true) {
|
|
16797
|
-
await outputCollection._internalDelete(
|
|
17747
|
+
await outputCollection._internalDelete(run.runId, txCtx);
|
|
17748
|
+
continue;
|
|
17749
|
+
}
|
|
17750
|
+
if (outSpec.shape === "record" && outSpec.denorm !== void 0 && outSpec.collection === spec.source) {
|
|
17751
|
+
const value = out.value;
|
|
17752
|
+
const patched = { ...run.base };
|
|
17753
|
+
let changed = false;
|
|
17754
|
+
for (const f of outSpec.denorm) {
|
|
17755
|
+
if (!selfWriteFieldEqual(run.base[f], value[f])) {
|
|
17756
|
+
patched[f] = value[f];
|
|
17757
|
+
changed = true;
|
|
17758
|
+
}
|
|
17759
|
+
}
|
|
17760
|
+
if (!changed) continue;
|
|
17761
|
+
if (txCtx !== null) {
|
|
17762
|
+
const prior = await this.adapter.get(this.vault, outSpec.collection, run.runId);
|
|
17763
|
+
txCtx._executed.push({
|
|
17764
|
+
op: { type: "put", vaultName: this.vault, collectionName: outSpec.collection, id: run.runId },
|
|
17765
|
+
priorEnvelope: prior
|
|
17766
|
+
});
|
|
17767
|
+
}
|
|
17768
|
+
await outputCollection.put(run.runId, patched);
|
|
16798
17769
|
continue;
|
|
16799
17770
|
}
|
|
16800
17771
|
if (txCtx !== null) {
|
|
16801
|
-
const prior = await this.adapter.get(this.vault, outSpec.collection,
|
|
17772
|
+
const prior = await this.adapter.get(this.vault, outSpec.collection, run.runId);
|
|
16802
17773
|
txCtx._executed.push({
|
|
16803
17774
|
op: {
|
|
16804
17775
|
type: "put",
|
|
16805
17776
|
vaultName: this.vault,
|
|
16806
17777
|
collectionName: outSpec.collection,
|
|
16807
|
-
id
|
|
17778
|
+
id: run.runId
|
|
16808
17779
|
},
|
|
16809
17780
|
priorEnvelope: prior
|
|
16810
17781
|
});
|
|
16811
17782
|
}
|
|
16812
|
-
await outputCollection.put(
|
|
17783
|
+
await outputCollection.put(run.runId, out.value);
|
|
16813
17784
|
}
|
|
16814
|
-
} else {
|
|
16815
|
-
await markStale(registry, spec, id);
|
|
16816
17785
|
}
|
|
16817
17786
|
}
|
|
16818
17787
|
}
|
|
@@ -17029,6 +17998,7 @@ var Collection = class {
|
|
|
17029
17998
|
if (!internal) {
|
|
17030
17999
|
await this.dispatchMaterializedViewsOnDelete(id);
|
|
17031
18000
|
await this.dispatchArrayDerivationsOnDelete(id);
|
|
18001
|
+
if (existing) await this.dispatchRollupsOnDelete(existing.record);
|
|
17032
18002
|
}
|
|
17033
18003
|
}
|
|
17034
18004
|
/**
|
|
@@ -17185,6 +18155,29 @@ var Collection = class {
|
|
|
17185
18155
|
hasReadTransforms() {
|
|
17186
18156
|
return this.moneyFields !== void 0 && Object.keys(this.moneyFields).length > 0 || this.i18nFields !== void 0 && Object.keys(this.i18nFields).length > 0 || this.dictKeyFields !== void 0 && Object.keys(this.dictKeyFields).length > 0;
|
|
17187
18157
|
}
|
|
18158
|
+
/**
|
|
18159
|
+
* Scan-mode full-text search over a plain-text `field` (#308). Decrypts the
|
|
18160
|
+
* collection in memory and ranks records by BM25 against the tokenized query.
|
|
18161
|
+
* **Zero added store leakage** — pure client-side scan; nothing searchable is
|
|
18162
|
+
* written to the store. (A store-usable blind index for at-scale search is a
|
|
18163
|
+
* separate, gated opt-in — see the #308 design note.) Eager mode only.
|
|
18164
|
+
*
|
|
18165
|
+
* `opts.match` (`'any'` default | `'all'`), `opts.prefix` (last query term as
|
|
18166
|
+
* a prefix → typeahead), `opts.limit` (top-N). Returns `{ id, score, record }`
|
|
18167
|
+
* ranked by descending score. The default tokenizer is word-boundary based —
|
|
18168
|
+
* see `src/search/tokenize.ts` for the Thai/CJK caveat.
|
|
18169
|
+
*/
|
|
18170
|
+
async search(field, query, opts = {}) {
|
|
18171
|
+
if (this.lazy) {
|
|
18172
|
+
throw new Error(
|
|
18173
|
+
`Collection "${this.name}": search() (scan mode) requires eager mode (prefetch: true). A store-usable blind index for lazy / at-scale search is a separate gated opt-in (#308).`
|
|
18174
|
+
);
|
|
18175
|
+
}
|
|
18176
|
+
await this.ensureHydrated();
|
|
18177
|
+
const entries = [];
|
|
18178
|
+
for (const [id, e] of this.cache) entries.push({ id, record: e.record });
|
|
18179
|
+
return searchScan(entries, field, query, opts);
|
|
18180
|
+
}
|
|
17188
18181
|
// ─── Bulk operations ─────────────────────────────────────
|
|
17189
18182
|
/**
|
|
17190
18183
|
* Put many records in one call. Each item is processed sequentially
|
|
@@ -17362,6 +18355,10 @@ var Collection = class {
|
|
|
17362
18355
|
leftCollection,
|
|
17363
18356
|
resolveRef: (field) => resolver.resolveRef(leftCollection, field),
|
|
17364
18357
|
resolveSource: (collectionName) => resolver.resolveSource(collectionName),
|
|
18358
|
+
// #285 §3 — flow the vault/collection default locale to joins so a
|
|
18359
|
+
// joined i18n field resolves like get()/list() when no per-call
|
|
18360
|
+
// locale is given; toArray({ locale }) overrides it.
|
|
18361
|
+
...this.defaultLocale !== void 0 ? { defaultLocale: this.defaultLocale } : {},
|
|
17365
18362
|
...resolver.resolveDictSource ? { resolveDictSource: (field) => resolver.resolveDictSource(leftCollection, field) } : {}
|
|
17366
18363
|
} : void 0;
|
|
17367
18364
|
return new Query(source, void 0, joinContext, this.aggregateStrategy);
|
|
@@ -17450,7 +18447,10 @@ var Collection = class {
|
|
|
17450
18447
|
};
|
|
17451
18448
|
this.emitter.on("change", handler);
|
|
17452
18449
|
return () => this.emitter.off("change", handler);
|
|
17453
|
-
}
|
|
18450
|
+
},
|
|
18451
|
+
// #285 §3 — expose this (right-side) collection's i18nText descriptors so
|
|
18452
|
+
// the join executor can resolve joined i18n fields at the `join` layer.
|
|
18453
|
+
...this.i18nFields !== void 0 ? { i18nFields: this.i18nFields } : {}
|
|
17454
18454
|
};
|
|
17455
18455
|
}
|
|
17456
18456
|
/**
|
|
@@ -17683,6 +18683,10 @@ var Collection = class {
|
|
|
17683
18683
|
leftCollection,
|
|
17684
18684
|
resolveRef: (field) => resolver.resolveRef(leftCollection, field),
|
|
17685
18685
|
resolveSource: (collectionName) => resolver.resolveSource(collectionName),
|
|
18686
|
+
// #285 §3 — flow the vault/collection default locale to joins so a
|
|
18687
|
+
// joined i18n field resolves like get()/list() when no per-call
|
|
18688
|
+
// locale is given; toArray({ locale }) overrides it.
|
|
18689
|
+
...this.defaultLocale !== void 0 ? { defaultLocale: this.defaultLocale } : {},
|
|
17686
18690
|
...resolver.resolveDictSource ? { resolveDictSource: (field) => resolver.resolveDictSource(leftCollection, field) } : {}
|
|
17687
18691
|
} : void 0;
|
|
17688
18692
|
return new ScanBuilder(
|
|
@@ -17883,12 +18887,12 @@ var Collection = class {
|
|
|
17883
18887
|
}
|
|
17884
18888
|
}
|
|
17885
18889
|
persisted.clear();
|
|
17886
|
-
for (const
|
|
17887
|
-
const envelope = await this.adapter.get(this.vault, this.name,
|
|
18890
|
+
for (const recordId4 of canonicalIds) {
|
|
18891
|
+
const envelope = await this.adapter.get(this.vault, this.name, recordId4);
|
|
17888
18892
|
if (!envelope) continue;
|
|
17889
18893
|
const record = await this.decryptRecord(envelope, { skipValidation: true });
|
|
17890
18894
|
if (record === null) continue;
|
|
17891
|
-
await this.maintainPersistedIndexesOnPut(
|
|
18895
|
+
await this.maintainPersistedIndexesOnPut(recordId4, record, null, envelope._v);
|
|
17892
18896
|
}
|
|
17893
18897
|
this.persistedIndexesLoaded = true;
|
|
17894
18898
|
}
|
|
@@ -18083,14 +19087,15 @@ var Collection = class {
|
|
|
18083
19087
|
(d) => isStaticDictDescriptor(d) && d.displayLocale !== void 0
|
|
18084
19088
|
);
|
|
18085
19089
|
if (!locale && !hasStaticDisplay) return result;
|
|
19090
|
+
const layer = localeOpts?._layer ?? "read";
|
|
18086
19091
|
if (locale && hasI18n && this.i18nFields) {
|
|
18087
|
-
result = this.i18nStrategy.applyI18nLocale(result, this.i18nFields, locale, localeOpts?.fallback);
|
|
19092
|
+
result = this.i18nStrategy.applyI18nLocale(result, this.i18nFields, locale, localeOpts?.fallback, layer);
|
|
18088
19093
|
}
|
|
18089
19094
|
if (hasDict && this.dictKeyFields && this.dictLabelResolver && locale !== "raw") {
|
|
18090
19095
|
const withLabels = { ...result };
|
|
18091
19096
|
const resolver = this.dictLabelResolver;
|
|
18092
19097
|
for (const [field, desc] of Object.entries(this.dictKeyFields)) {
|
|
18093
|
-
const policy = desc.onMissing ? resolvePolicy(desc.onMissing,
|
|
19098
|
+
const policy = desc.onMissing ? resolvePolicy(desc.onMissing, layer) : "null";
|
|
18094
19099
|
const fallback = policy === "substitute" ? localeOpts?.fallback ?? desc.substitute : localeOpts?.fallback;
|
|
18095
19100
|
const effLocale = locale ?? (isStaticDictDescriptor(desc) ? desc.displayLocale : void 0);
|
|
18096
19101
|
const resolveKey = async (key) => {
|
|
@@ -18239,6 +19244,34 @@ var Collection = class {
|
|
|
18239
19244
|
}
|
|
18240
19245
|
}
|
|
18241
19246
|
}
|
|
19247
|
+
/**
|
|
19248
|
+
* @internal — hard-delete this record's persisted `_idx/<field>/<recordId>`
|
|
19249
|
+
* side-cars for the erasure path (#401). `forget()` crypto-shreds the body but
|
|
19250
|
+
* keeps the collection DEK, under which these side-cars are encrypted — so
|
|
19251
|
+
* without this they leave the indexed field VALUES readable after a "forget".
|
|
19252
|
+
*
|
|
19253
|
+
* Content-free: the side-car id is `encodeIdxId(def.key, id)`, so it needs no
|
|
19254
|
+
* body decode (the body is being shredded). Eager mode has no durable side-car
|
|
19255
|
+
* → no-op. The in-memory mirror is left as-is: it is ephemeral (rebuilt from
|
|
19256
|
+
* the now-deleted side-cars on reopen) and live reads skip the tombstone, so a
|
|
19257
|
+
* stale mirror hit cannot surface the erased record. Returns the count deleted
|
|
19258
|
+
* + the `def.key`s whose delete FAILED (residue that still leaks the value).
|
|
19259
|
+
*/
|
|
19260
|
+
async _purgePersistedIndexes(id) {
|
|
19261
|
+
const persisted = this.persistedIndexes;
|
|
19262
|
+
if (!persisted) return { purged: 0, residue: [] };
|
|
19263
|
+
let purged = 0;
|
|
19264
|
+
const residue = [];
|
|
19265
|
+
for (const def of persisted.definitions()) {
|
|
19266
|
+
try {
|
|
19267
|
+
await this.adapter.delete(this.vault, this.name, encodeIdxId(def.key, id));
|
|
19268
|
+
purged++;
|
|
19269
|
+
} catch {
|
|
19270
|
+
residue.push(def.key);
|
|
19271
|
+
}
|
|
19272
|
+
}
|
|
19273
|
+
return { purged, residue };
|
|
19274
|
+
}
|
|
18242
19275
|
/**
|
|
18243
19276
|
* Bulk-load the persisted-index mirror from `_idx/<field>/*` side-cars
|
|
18244
19277
|
* on first lazy-mode query. Idempotent — subsequent calls short-circuit
|
|
@@ -19124,8 +20157,8 @@ var DeferredNumberingStore = class {
|
|
|
19124
20157
|
}
|
|
19125
20158
|
await this.adapter.put(this.vault, collection, id, env, expectedVersion);
|
|
19126
20159
|
}
|
|
19127
|
-
pendingId(series,
|
|
19128
|
-
return `${series}::${
|
|
20160
|
+
pendingId(series, recordId4) {
|
|
20161
|
+
return `${series}::${recordId4}`;
|
|
19129
20162
|
}
|
|
19130
20163
|
/** Current last-assigned serial for a series (0 if none). */
|
|
19131
20164
|
async peek(series) {
|
|
@@ -19139,16 +20172,16 @@ var DeferredNumberingStore = class {
|
|
|
19139
20172
|
* at the next pass (the record's `field` is the durable source of truth —
|
|
19140
20173
|
* `assigned` is an in-process convenience that a crash may drop).
|
|
19141
20174
|
*/
|
|
19142
|
-
async enqueue(series,
|
|
20175
|
+
async enqueue(series, recordId4) {
|
|
19143
20176
|
const cfg = this.configs.get(series);
|
|
19144
20177
|
if (!cfg) throw new NumberingUncertaintyError(series);
|
|
19145
20178
|
if (typeof this.adapter.getStoreTime !== "function") throw new NumberingUncertaintyError(series);
|
|
19146
20179
|
const st = await this.adapter.getStoreTime();
|
|
19147
|
-
const id = this.pendingId(series,
|
|
20180
|
+
const id = this.pendingId(series, recordId4);
|
|
19148
20181
|
const { env } = await this.readJson(NUMBERING_PENDING_COLLECTION, id);
|
|
19149
20182
|
const entry = {
|
|
19150
20183
|
series,
|
|
19151
|
-
recordId:
|
|
20184
|
+
recordId: recordId4,
|
|
19152
20185
|
collection: cfg.collection,
|
|
19153
20186
|
field: cfg.field,
|
|
19154
20187
|
storeEarliest: st.earliest,
|
|
@@ -19371,6 +20404,7 @@ var NO_PERIODS = {
|
|
|
19371
20404
|
};
|
|
19372
20405
|
|
|
19373
20406
|
// src/vault.ts
|
|
20407
|
+
init_core();
|
|
19374
20408
|
init_errors();
|
|
19375
20409
|
|
|
19376
20410
|
// src/periods/periods.ts
|
|
@@ -19505,13 +20539,13 @@ async function runCompaction(ctx, options = {}) {
|
|
|
19505
20539
|
collectionsWithPolicy += 1;
|
|
19506
20540
|
byCollection[collectionName] = { records: 0, evicted: 0 };
|
|
19507
20541
|
const ids = await ctx.listRecords(collectionName);
|
|
19508
|
-
for (const
|
|
20542
|
+
for (const recordId4 of ids) {
|
|
19509
20543
|
if (evicted >= maxEvictions) break outer;
|
|
19510
|
-
const record = await ctx.getRecord(collectionName,
|
|
20544
|
+
const record = await ctx.getRecord(collectionName, recordId4).catch(() => null);
|
|
19511
20545
|
if (record === null) continue;
|
|
19512
20546
|
records += 1;
|
|
19513
20547
|
byCollection[collectionName].records += 1;
|
|
19514
|
-
const slots = await ctx.listSlots(collectionName,
|
|
20548
|
+
const slots = await ctx.listSlots(collectionName, recordId4).catch(() => []);
|
|
19515
20549
|
for (const slot of slots) {
|
|
19516
20550
|
if (evicted >= maxEvictions) break outer;
|
|
19517
20551
|
const policy = config[slot.name];
|
|
@@ -19523,11 +20557,11 @@ async function runCompaction(ctx, options = {}) {
|
|
|
19523
20557
|
continue;
|
|
19524
20558
|
}
|
|
19525
20559
|
if (!dryRun) {
|
|
19526
|
-
await ctx.deleteSlot(collectionName,
|
|
20560
|
+
await ctx.deleteSlot(collectionName, recordId4, slot.name);
|
|
19527
20561
|
await writeAuditEntry(ctx, {
|
|
19528
|
-
id: generateEvictionId(collectionName,
|
|
20562
|
+
id: generateEvictionId(collectionName, recordId4, slot.name),
|
|
19529
20563
|
collection: collectionName,
|
|
19530
|
-
recordId:
|
|
20564
|
+
recordId: recordId4,
|
|
19531
20565
|
slotName: slot.name,
|
|
19532
20566
|
blobHash: slot.eTag,
|
|
19533
20567
|
reason,
|
|
@@ -19594,11 +20628,11 @@ function evaluatePolicy(policy, record, slot, now) {
|
|
|
19594
20628
|
if (predicateTriggered) return "predicate";
|
|
19595
20629
|
return null;
|
|
19596
20630
|
}
|
|
19597
|
-
function generateEvictionId(collection,
|
|
20631
|
+
function generateEvictionId(collection, recordId4, slotName) {
|
|
19598
20632
|
const rand = globalThis.crypto.getRandomValues(new Uint8Array(8));
|
|
19599
20633
|
let suffix = "";
|
|
19600
20634
|
for (const b of rand) suffix += b.toString(16).padStart(2, "0");
|
|
19601
|
-
return `${collection}__${
|
|
20635
|
+
return `${collection}__${recordId4}__${slotName}__${suffix}`;
|
|
19602
20636
|
}
|
|
19603
20637
|
async function writeAuditEntry(ctx, entry) {
|
|
19604
20638
|
const json = JSON.stringify(entry);
|
|
@@ -19649,7 +20683,7 @@ async function deriveMagicLinkContentKey(serverSecret, token, vault) {
|
|
|
19649
20683
|
["encrypt", "decrypt"]
|
|
19650
20684
|
);
|
|
19651
20685
|
}
|
|
19652
|
-
async function writeMagicLinkGrant(store, vault, grantor, contentKey, grantKek,
|
|
20686
|
+
async function writeMagicLinkGrant(store, vault, grantor, contentKey, grantKek, recordId4, opts) {
|
|
19653
20687
|
const collectionName = opts.collection ?? null;
|
|
19654
20688
|
const sourceKey = collectionName ? dekKey(collectionName, opts.tier) : `__any#${opts.tier}`;
|
|
19655
20689
|
const sourceDek = grantor.deks.get(sourceKey);
|
|
@@ -19662,7 +20696,7 @@ async function writeMagicLinkGrant(store, vault, grantor, contentKey, grantKek,
|
|
|
19662
20696
|
const until = typeof opts.until === "string" ? opts.until : opts.until.toISOString();
|
|
19663
20697
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
19664
20698
|
const payload = {
|
|
19665
|
-
id:
|
|
20699
|
+
id: recordId4,
|
|
19666
20700
|
toUser: opts.toUser,
|
|
19667
20701
|
fromUser: grantor.userId,
|
|
19668
20702
|
tier: opts.tier,
|
|
@@ -19682,11 +20716,11 @@ async function writeMagicLinkGrant(store, vault, grantor, contentKey, grantKek,
|
|
|
19682
20716
|
_data: data,
|
|
19683
20717
|
_by: grantor.userId
|
|
19684
20718
|
};
|
|
19685
|
-
await store.put(vault, MAGIC_LINK_GRANTS_COLLECTION,
|
|
19686
|
-
return { recordId:
|
|
20719
|
+
await store.put(vault, MAGIC_LINK_GRANTS_COLLECTION, recordId4, envelope);
|
|
20720
|
+
return { recordId: recordId4, payload };
|
|
19687
20721
|
}
|
|
19688
|
-
async function readMagicLinkGrantRecord(store, vault, contentKey,
|
|
19689
|
-
const env = await store.get(vault, MAGIC_LINK_GRANTS_COLLECTION,
|
|
20722
|
+
async function readMagicLinkGrantRecord(store, vault, contentKey, recordId4) {
|
|
20723
|
+
const env = await store.get(vault, MAGIC_LINK_GRANTS_COLLECTION, recordId4);
|
|
19690
20724
|
if (!env) return null;
|
|
19691
20725
|
try {
|
|
19692
20726
|
const json = await decrypt(env._iv, env._data, contentKey);
|
|
@@ -20342,13 +21376,17 @@ var Vault = class {
|
|
|
20342
21376
|
*/
|
|
20343
21377
|
overlayedViewRegistry = null;
|
|
20344
21378
|
/**
|
|
20345
|
-
* Cached read-only
|
|
20346
|
-
* and to derivation callbacks via `derive(source, ctx)`.
|
|
20347
|
-
*
|
|
21379
|
+
* Cached read-only facades handed to guard callbacks via `ctx.vault`
|
|
21380
|
+
* and to derivation callbacks via `derive(source, ctx)`. Split by
|
|
21381
|
+
* resolution layer (#285): the guard facade reads at `layer:'guard'`,
|
|
21382
|
+
* the derivation facade at `layer:'derivation'`, so i18nText / dictKey
|
|
21383
|
+
* fields resolve under that layer's `onMissing` policy. Allocated
|
|
21384
|
+
* eagerly inside `_initGuards()` / `_initDerivations()` so read
|
|
20348
21385
|
* accessors stay synchronous (callers in `tx/transaction.ts` rely on
|
|
20349
|
-
* that).
|
|
21386
|
+
* that). Each stays `null` for vaults without that subsystem.
|
|
20350
21387
|
*/
|
|
20351
|
-
|
|
21388
|
+
guardFacade = null;
|
|
21389
|
+
derivationFacade = null;
|
|
20352
21390
|
getDEK;
|
|
20353
21391
|
/**
|
|
20354
21392
|
* Per-principal user envelope API.
|
|
@@ -20510,6 +21548,10 @@ var Vault = class {
|
|
|
20510
21548
|
i18nFieldRegistry = /* @__PURE__ */ new Map();
|
|
20511
21549
|
/** Cache of DictionaryHandle instances, one per dictionary name. */
|
|
20512
21550
|
dictionaryCache = /* @__PURE__ */ new Map();
|
|
21551
|
+
/** Registered link specs (#377-B), keyed by link name; set by `vault.link()`. */
|
|
21552
|
+
linkRegistry = /* @__PURE__ */ new Map();
|
|
21553
|
+
/** Cache of LinkSet handles, one per link name. */
|
|
21554
|
+
linkSetCache = /* @__PURE__ */ new Map();
|
|
20513
21555
|
/** — subscribers for cross-tier access events. */
|
|
20514
21556
|
crossTierSubs = /* @__PURE__ */ new Set();
|
|
20515
21557
|
/** — currently-active elevation, or null. One per vault. */
|
|
@@ -20626,6 +21668,9 @@ var Vault = class {
|
|
|
20626
21668
|
if (collectionName === SEQUENCE_COLLECTION) {
|
|
20627
21669
|
throw new ReservedCollectionNameError(collectionName);
|
|
20628
21670
|
}
|
|
21671
|
+
if (isLinkCollectionName(collectionName)) {
|
|
21672
|
+
throw new ReservedCollectionNameError(collectionName);
|
|
21673
|
+
}
|
|
20629
21674
|
let coll = this.collectionCache.get(collectionName);
|
|
20630
21675
|
if (coll && options?.moneyFields) {
|
|
20631
21676
|
coll._applyMoneyFields(options.moneyFields);
|
|
@@ -20697,6 +21742,7 @@ var Vault = class {
|
|
|
20697
21742
|
}));
|
|
20698
21743
|
schemaUpdateGate = new SchemaUpdateGate(work);
|
|
20699
21744
|
}
|
|
21745
|
+
const effectiveHistoryConfig = options?.historyConfig ?? this.historyConfig;
|
|
20700
21746
|
const collOpts = {
|
|
20701
21747
|
adapter: this.adapter,
|
|
20702
21748
|
vault: this.name,
|
|
@@ -20712,7 +21758,7 @@ var Vault = class {
|
|
|
20712
21758
|
schemaFence: this.schemaFence,
|
|
20713
21759
|
getDEK: this.getDEK,
|
|
20714
21760
|
onDirty: this.onDirty,
|
|
20715
|
-
historyConfig:
|
|
21761
|
+
historyConfig: effectiveHistoryConfig,
|
|
20716
21762
|
// thread the vault-wide blob strategy into every
|
|
20717
21763
|
// collection. `undefined` is intentionally preserved so the
|
|
20718
21764
|
// Collection constructor uses its NO_BLOBS default.
|
|
@@ -20723,7 +21769,11 @@ var Vault = class {
|
|
|
20723
21769
|
historyStrategy: this.historyStrategy,
|
|
20724
21770
|
i18nStrategy: this.i18nStrategy,
|
|
20725
21771
|
syncStrategy: this.syncStrategy,
|
|
20726
|
-
ledger
|
|
21772
|
+
// Per-collection ledger opt-out (#361): when this collection sets
|
|
21773
|
+
// `historyConfig.ledger: false`, withhold the ledger reference so all
|
|
21774
|
+
// four `if (this.ledger)` append sites in Collection no-op. The chain
|
|
21775
|
+
// stays valid — it simply never receives this collection's entries.
|
|
21776
|
+
ledger: effectiveHistoryConfig.ledger === false ? void 0 : this.getLedgerOrNull() ?? void 0,
|
|
20727
21777
|
refEnforcer: this,
|
|
20728
21778
|
joinResolver: this,
|
|
20729
21779
|
defaultLocale: this.locale,
|
|
@@ -21084,6 +22134,68 @@ var Vault = class {
|
|
|
21084
22134
|
}
|
|
21085
22135
|
return handle;
|
|
21086
22136
|
}
|
|
22137
|
+
/**
|
|
22138
|
+
* Declare a managed many-to-many link set (#377-B). Registers a
|
|
22139
|
+
* `_links_<name>` junction between two endpoint collections; access its
|
|
22140
|
+
* rows via `vault.links(name)`. Idempotent for an identical re-declaration;
|
|
22141
|
+
* a conflicting one throws. See {@link links}.
|
|
22142
|
+
*
|
|
22143
|
+
* ```ts
|
|
22144
|
+
* vault.link('saleLineLinks', { a: ref('saleLines'), b: ref('purchaseLines'), onDelete: 'cascade' })
|
|
22145
|
+
* ```
|
|
22146
|
+
*
|
|
22147
|
+
* `a` / `b` accept either a collection name or a `ref(target)` descriptor
|
|
22148
|
+
* (only its `target` is used — links manage their own integrity). `onDelete`
|
|
22149
|
+
* governs what happens to link rows when an endpoint record is deleted
|
|
22150
|
+
* (`'cascade'` default, `'strict'`, `'warn'`).
|
|
22151
|
+
*/
|
|
22152
|
+
link(name, spec) {
|
|
22153
|
+
const a = typeof spec.a === "string" ? spec.a : spec.a.target;
|
|
22154
|
+
const b = typeof spec.b === "string" ? spec.b : spec.b.target;
|
|
22155
|
+
for (const [slot, target] of [["a", a], ["b", b]]) {
|
|
22156
|
+
if (!target || target.startsWith("_") || target.includes("/")) {
|
|
22157
|
+
throw new ValidationError(
|
|
22158
|
+
`vault.link("${name}"): endpoint "${slot}" must be a simple collection name, got "${target}".`
|
|
22159
|
+
);
|
|
22160
|
+
}
|
|
22161
|
+
}
|
|
22162
|
+
const resolved = { a, b, ...spec.onDelete ? { onDelete: spec.onDelete } : {} };
|
|
22163
|
+
const existing = this.linkRegistry.get(name);
|
|
22164
|
+
if (existing) {
|
|
22165
|
+
if (existing.a !== resolved.a || existing.b !== resolved.b || (existing.onDelete ?? "cascade") !== (resolved.onDelete ?? "cascade")) {
|
|
22166
|
+
throw new ValidationError(`vault.link("${name}"): conflicting re-declaration.`);
|
|
22167
|
+
}
|
|
22168
|
+
return;
|
|
22169
|
+
}
|
|
22170
|
+
this.linkRegistry.set(name, resolved);
|
|
22171
|
+
}
|
|
22172
|
+
/**
|
|
22173
|
+
* Access a declared link set (#377-B). Throws if `name` was not first
|
|
22174
|
+
* declared via {@link link}. Returns a cached {@link LinkSetHandle}:
|
|
22175
|
+
* `connect(a, b, meta?)`, `disconnect(a, b)`, `has(a, b)`, `of(id)`, `list()`.
|
|
22176
|
+
*/
|
|
22177
|
+
links(name) {
|
|
22178
|
+
let handle = this.linkSetCache.get(name);
|
|
22179
|
+
if (!handle) {
|
|
22180
|
+
const spec = this.linkRegistry.get(name);
|
|
22181
|
+
if (!spec) {
|
|
22182
|
+
throw new ValidationError(`vault.links("${name}"): not declared. Call vault.link("${name}", { a, b }) first.`);
|
|
22183
|
+
}
|
|
22184
|
+
handle = new LinkSet(
|
|
22185
|
+
this.adapter,
|
|
22186
|
+
this.name,
|
|
22187
|
+
name,
|
|
22188
|
+
spec,
|
|
22189
|
+
this.encrypted,
|
|
22190
|
+
this.getDEK,
|
|
22191
|
+
this.keyring.userId,
|
|
22192
|
+
this.emitter,
|
|
22193
|
+
async (collection, id) => await this.collection(collection).get(id) !== null
|
|
22194
|
+
);
|
|
22195
|
+
this.linkSetCache.set(name, handle);
|
|
22196
|
+
}
|
|
22197
|
+
return handle;
|
|
22198
|
+
}
|
|
21087
22199
|
/**
|
|
21088
22200
|
* Build a `JoinableSource` for a dictKey field, for use in dict joins
|
|
21089
22201
|
*. Returns a source whose snapshot contains `{ key, ...labels }`
|
|
@@ -21243,65 +22355,16 @@ var Vault = class {
|
|
|
21243
22355
|
});
|
|
21244
22356
|
}
|
|
21245
22357
|
}
|
|
21246
|
-
/**
|
|
21247
|
-
* Bulk blob extraction primitive.
|
|
21248
|
-
*
|
|
21249
|
-
* Returns an async-iterable handle over every blob attached to
|
|
21250
|
-
* records in the vault. Single capability check (`plaintext/blob`)
|
|
21251
|
-
* at handle creation; single audit entry to `_export_audit` before
|
|
21252
|
-
* the first yield. Per-blob decryption happens lazily as the
|
|
21253
|
-
* consumer pulls tuples.
|
|
21254
|
-
*
|
|
21255
|
-
* ```ts
|
|
21256
|
-
* const handle = vault.exportBlobs({
|
|
21257
|
-
* collections: ['invoiceScans'],
|
|
21258
|
-
* where: (rec) => (rec as { clientId?: string }).clientId === 'c-123',
|
|
21259
|
-
* })
|
|
21260
|
-
* for await (const { bytes, meta, recordRef } of handle) {
|
|
21261
|
-
* await uploadToColdStorage(bytes, recordRef)
|
|
21262
|
-
* }
|
|
21263
|
-
* ```
|
|
21264
|
-
*
|
|
21265
|
-
* @see `@noy-db/hub/store/export-blobs` for the full option surface.
|
|
21266
|
-
*/
|
|
21267
|
-
/**
|
|
21268
|
-
* Evict blob slots per the per-collection `blobFields` retention
|
|
21269
|
-
* policy.
|
|
21270
|
-
*
|
|
21271
|
-
* Iterates every collection declared with `{ blobFields: {...} }`.
|
|
21272
|
-
* For each record, checks every configured slot against its
|
|
21273
|
-
* policy — `retainDays` (age-based TTL) and/or `evictWhen(record)`
|
|
21274
|
-
* (predicate) — and evicts matching slots. Every eviction writes
|
|
21275
|
-
* one entry to `_blob_eviction_audit` (actor + eTag + reason +
|
|
21276
|
-
* timestamp, no plaintext). Consumer-scheduled; noy-db never runs
|
|
21277
|
-
* this on its own.
|
|
21278
|
-
*
|
|
21279
|
-
* ```ts
|
|
21280
|
-
* await vault.compact() // run full pass
|
|
21281
|
-
* await vault.compact({ dryRun: true }) // preview counts
|
|
21282
|
-
* await vault.compact({ maxEvictions: 1000 }) // cap batch
|
|
21283
|
-
* ```
|
|
21284
|
-
*/
|
|
21285
|
-
/**
|
|
21286
|
-
* Atomic, gap-free numbering. `vault.sequence('invoice-2026').next()`
|
|
21287
|
-
* returns 1, 2, 3, … with no gaps or duplicates under concurrency, via
|
|
21288
|
-
* an optimistic-CAS counter at `_sequences/<name>`. Each name is an
|
|
21289
|
-
* independent sequence.
|
|
21290
|
-
*
|
|
21291
|
-
* **Online-only:** `next()` throws `SequenceOfflineError` unless the
|
|
21292
|
-
* store advertises `capabilities.casAtomic` — gap-free numbering cannot
|
|
21293
|
-
* be serialized by an offline / non-CAS writer.
|
|
21294
|
-
*
|
|
21295
|
-
* ```ts
|
|
21296
|
-
* const n = await vault.sequence('invoice-2026').next() // 1, then 2, …
|
|
21297
|
-
* const cur = await vault.sequence('invoice-2026').peek() // current value, no allocation
|
|
21298
|
-
* ```
|
|
21299
|
-
*/
|
|
21300
22358
|
sequence(series, opts) {
|
|
21301
22359
|
if (series.includes("\0")) {
|
|
21302
22360
|
throw new ValidationError(`sequence("${series}"): series name must not contain a null byte (\\x00).`);
|
|
21303
22361
|
}
|
|
21304
22362
|
if (this.numberingConfigs.has(series)) {
|
|
22363
|
+
if (opts?.format !== void 0) {
|
|
22364
|
+
throw new ValidationError(
|
|
22365
|
+
`sequence("${series}") is a deferred-numbering series; the format option applies to CAS sequences only.`
|
|
22366
|
+
);
|
|
22367
|
+
}
|
|
21305
22368
|
const eng = this.deferred();
|
|
21306
22369
|
return {
|
|
21307
22370
|
next: async (nextOpts) => {
|
|
@@ -21325,7 +22388,17 @@ var Vault = class {
|
|
|
21325
22388
|
actor: this.keyring.userId
|
|
21326
22389
|
});
|
|
21327
22390
|
}
|
|
21328
|
-
|
|
22391
|
+
const handle = this.sequenceStore.handle(resolveSequenceKey(series, opts));
|
|
22392
|
+
if (opts?.format === void 0) return handle;
|
|
22393
|
+
const render = compileSequenceFormat(opts.format, series, opts.partition);
|
|
22394
|
+
return {
|
|
22395
|
+
next: async (nextOpts) => {
|
|
22396
|
+
const serial = await handle.next(nextOpts);
|
|
22397
|
+
return { serial, formatted: render(serial) };
|
|
22398
|
+
},
|
|
22399
|
+
peek: () => handle.peek(),
|
|
22400
|
+
seedTo: (n) => handle.seedTo(n)
|
|
22401
|
+
};
|
|
21329
22402
|
}
|
|
21330
22403
|
/** @internal — lazily build the deferred-numbering engine with a cache-coherent stamp. */
|
|
21331
22404
|
deferred() {
|
|
@@ -21340,11 +22413,11 @@ var Vault = class {
|
|
|
21340
22413
|
// Stamp THROUGH the Collection layer so cache/indexes/MVs stay coherent —
|
|
21341
22414
|
// `this.collection(name)` returns the shared cached instance, so a
|
|
21342
22415
|
// subsequent user `collection.get(id)` sees the assigned serial.
|
|
21343
|
-
stamp: async (collection,
|
|
22416
|
+
stamp: async (collection, recordId4, field, serial) => {
|
|
21344
22417
|
const coll = this.collection(collection);
|
|
21345
|
-
const rec = await coll.get(
|
|
22418
|
+
const rec = await coll.get(recordId4);
|
|
21346
22419
|
if (!rec) return false;
|
|
21347
|
-
await coll.put(
|
|
22420
|
+
await coll.put(recordId4, { ...rec, [field]: serial });
|
|
21348
22421
|
return true;
|
|
21349
22422
|
}
|
|
21350
22423
|
});
|
|
@@ -21552,6 +22625,43 @@ var Vault = class {
|
|
|
21552
22625
|
if (descriptor.mode !== "strict") continue;
|
|
21553
22626
|
const rawId = obj[field];
|
|
21554
22627
|
if (rawId === null || rawId === void 0) continue;
|
|
22628
|
+
if (isRefArray(descriptor)) {
|
|
22629
|
+
if (!Array.isArray(rawId)) {
|
|
22630
|
+
throw new RefIntegrityError({
|
|
22631
|
+
collection: collectionName,
|
|
22632
|
+
id: obj["id"] ?? "<unknown>",
|
|
22633
|
+
field,
|
|
22634
|
+
refTo: descriptor.target,
|
|
22635
|
+
refId: null,
|
|
22636
|
+
message: `Array ref field "${collectionName}.${field}" must be an array, got ${typeof rawId}.`
|
|
22637
|
+
});
|
|
22638
|
+
}
|
|
22639
|
+
const arrTarget = this.collection(descriptor.target);
|
|
22640
|
+
for (const el of rawId) {
|
|
22641
|
+
if (typeof el !== "string" && typeof el !== "number") {
|
|
22642
|
+
throw new RefIntegrityError({
|
|
22643
|
+
collection: collectionName,
|
|
22644
|
+
id: obj["id"] ?? "<unknown>",
|
|
22645
|
+
field,
|
|
22646
|
+
refTo: descriptor.target,
|
|
22647
|
+
refId: null,
|
|
22648
|
+
message: `Array ref "${collectionName}.${field}" elements must be strings or numbers, got ${typeof el}.`
|
|
22649
|
+
});
|
|
22650
|
+
}
|
|
22651
|
+
const elId = String(el);
|
|
22652
|
+
if (!await arrTarget.get(elId)) {
|
|
22653
|
+
throw new RefIntegrityError({
|
|
22654
|
+
collection: collectionName,
|
|
22655
|
+
id: obj["id"] ?? "<unknown>",
|
|
22656
|
+
field,
|
|
22657
|
+
refTo: descriptor.target,
|
|
22658
|
+
refId: elId,
|
|
22659
|
+
message: `Strict array ref "${collectionName}.${field}" \u2192 "${descriptor.target}" cannot be satisfied: element id "${elId}" not found in "${descriptor.target}".`
|
|
22660
|
+
});
|
|
22661
|
+
}
|
|
22662
|
+
}
|
|
22663
|
+
continue;
|
|
22664
|
+
}
|
|
21555
22665
|
if (typeof rawId !== "string" && typeof rawId !== "number") {
|
|
21556
22666
|
throw new RefIntegrityError({
|
|
21557
22667
|
collection: collectionName,
|
|
@@ -21601,6 +22711,11 @@ var Vault = class {
|
|
|
21601
22711
|
const allRecords = await fromCollection.list();
|
|
21602
22712
|
const matches = allRecords.filter((rec) => {
|
|
21603
22713
|
const raw = rec[rule.field];
|
|
22714
|
+
if (rule.isArray) {
|
|
22715
|
+
return Array.isArray(raw) && raw.some(
|
|
22716
|
+
(el) => (typeof el === "string" || typeof el === "number") && String(el) === id
|
|
22717
|
+
);
|
|
22718
|
+
}
|
|
21604
22719
|
if (typeof raw !== "string" && typeof raw !== "number") return false;
|
|
21605
22720
|
return String(raw) === id;
|
|
21606
22721
|
});
|
|
@@ -21639,10 +22754,45 @@ var Vault = class {
|
|
|
21639
22754
|
}
|
|
21640
22755
|
}
|
|
21641
22756
|
}
|
|
22757
|
+
await this.enforceLinksOnDelete(collectionName, id);
|
|
21642
22758
|
} finally {
|
|
21643
22759
|
this.cascadeInProgress.delete(key);
|
|
21644
22760
|
}
|
|
21645
22761
|
}
|
|
22762
|
+
/**
|
|
22763
|
+
* @internal — apply link `onDelete` policy when an endpoint record is
|
|
22764
|
+
* deleted (#377-B). `'strict'` throws (blocks the delete), `'cascade'`
|
|
22765
|
+
* removes the touching link rows (tx-atomic when a transaction is active),
|
|
22766
|
+
* `'warn'` leaves orphans for `checkIntegrity()`.
|
|
22767
|
+
*/
|
|
22768
|
+
async enforceLinksOnDelete(collectionName, id) {
|
|
22769
|
+
for (const [name, spec] of this.linkRegistry) {
|
|
22770
|
+
if (spec.a !== collectionName && spec.b !== collectionName) continue;
|
|
22771
|
+
const handle = this.links(name);
|
|
22772
|
+
const touching = await handle._rowsTouchingEndpoint(collectionName, id);
|
|
22773
|
+
if (touching.length === 0) continue;
|
|
22774
|
+
const mode = spec.onDelete ?? "cascade";
|
|
22775
|
+
if (mode === "warn") continue;
|
|
22776
|
+
if (mode === "strict") {
|
|
22777
|
+
throw new LinkIntegrityError(name, collectionName, id, touching.length);
|
|
22778
|
+
}
|
|
22779
|
+
const linkColl = handle._collectionName;
|
|
22780
|
+
const txCtx = this.noydb._activeTxContextOrNull;
|
|
22781
|
+
for (const row of touching) {
|
|
22782
|
+
const rowKey = linkRowKey(row.a, row.b);
|
|
22783
|
+
if (txCtx !== null) {
|
|
22784
|
+
const prior = await this.adapter.get(this.name, linkColl, rowKey);
|
|
22785
|
+
if (prior !== null) {
|
|
22786
|
+
txCtx._executed.push({
|
|
22787
|
+
op: { type: "delete", vaultName: this.name, collectionName: linkColl, id: rowKey },
|
|
22788
|
+
priorEnvelope: prior
|
|
22789
|
+
});
|
|
22790
|
+
}
|
|
22791
|
+
}
|
|
22792
|
+
await handle.disconnect(row.a, row.b);
|
|
22793
|
+
}
|
|
22794
|
+
}
|
|
22795
|
+
}
|
|
21646
22796
|
// ─── Join resolver) ────────────────────
|
|
21647
22797
|
/**
|
|
21648
22798
|
* Look up the `RefDescriptor` the left collection declared for a
|
|
@@ -21703,6 +22853,23 @@ var Vault = class {
|
|
|
21703
22853
|
for (const [field, descriptor] of Object.entries(refs)) {
|
|
21704
22854
|
const rawId = record[field];
|
|
21705
22855
|
if (rawId === null || rawId === void 0) continue;
|
|
22856
|
+
const target = this.collection(descriptor.target);
|
|
22857
|
+
if (isRefArray(descriptor)) {
|
|
22858
|
+
if (!Array.isArray(rawId)) {
|
|
22859
|
+
violations.push({ collection: collectionName, id: recId, field, refTo: descriptor.target, refId: rawId, mode: descriptor.mode });
|
|
22860
|
+
continue;
|
|
22861
|
+
}
|
|
22862
|
+
for (const el of rawId) {
|
|
22863
|
+
if (typeof el !== "string" && typeof el !== "number") {
|
|
22864
|
+
violations.push({ collection: collectionName, id: recId, field, refTo: descriptor.target, refId: el, mode: descriptor.mode });
|
|
22865
|
+
continue;
|
|
22866
|
+
}
|
|
22867
|
+
if (!await target.get(String(el))) {
|
|
22868
|
+
violations.push({ collection: collectionName, id: recId, field, refTo: descriptor.target, refId: el, mode: descriptor.mode });
|
|
22869
|
+
}
|
|
22870
|
+
}
|
|
22871
|
+
continue;
|
|
22872
|
+
}
|
|
21706
22873
|
if (typeof rawId !== "string" && typeof rawId !== "number") {
|
|
21707
22874
|
violations.push({
|
|
21708
22875
|
collection: collectionName,
|
|
@@ -21715,7 +22882,6 @@ var Vault = class {
|
|
|
21715
22882
|
continue;
|
|
21716
22883
|
}
|
|
21717
22884
|
const refId = String(rawId);
|
|
21718
|
-
const target = this.collection(descriptor.target);
|
|
21719
22885
|
const exists = await target.get(refId);
|
|
21720
22886
|
if (!exists) {
|
|
21721
22887
|
violations.push({
|
|
@@ -21730,6 +22896,19 @@ var Vault = class {
|
|
|
21730
22896
|
}
|
|
21731
22897
|
}
|
|
21732
22898
|
}
|
|
22899
|
+
for (const [name, spec] of this.linkRegistry) {
|
|
22900
|
+
const linkColl = linkCollectionName(name);
|
|
22901
|
+
const rows = await this.links(name).list();
|
|
22902
|
+
for (const row of rows) {
|
|
22903
|
+
const rowKey = linkRowKey(row.a, row.b);
|
|
22904
|
+
if (await this.collection(spec.a).get(row.a) === null) {
|
|
22905
|
+
violations.push({ collection: linkColl, id: rowKey, field: "a", refTo: spec.a, refId: row.a, mode: spec.onDelete ?? "cascade" });
|
|
22906
|
+
}
|
|
22907
|
+
if (await this.collection(spec.b).get(row.b) === null) {
|
|
22908
|
+
violations.push({ collection: linkColl, id: rowKey, field: "b", refTo: spec.b, refId: row.b, mode: spec.onDelete ?? "cascade" });
|
|
22909
|
+
}
|
|
22910
|
+
}
|
|
22911
|
+
}
|
|
21733
22912
|
return { violations };
|
|
21734
22913
|
}
|
|
21735
22914
|
/**
|
|
@@ -21836,6 +23015,8 @@ var Vault = class {
|
|
|
21836
23015
|
const blobResidueCollections = /* @__PURE__ */ new Set();
|
|
21837
23016
|
let blobsShredded = 0;
|
|
21838
23017
|
let blobsRetainedShared = 0;
|
|
23018
|
+
let indexPostingsPurged = 0;
|
|
23019
|
+
const indexResidue = [];
|
|
21839
23020
|
const blobsEnabled = this.blobStrategy !== void 0;
|
|
21840
23021
|
const actor = this.keyring.userId;
|
|
21841
23022
|
for (const ref2 of refs) {
|
|
@@ -21857,6 +23038,9 @@ var Vault = class {
|
|
|
21857
23038
|
ref2.id,
|
|
21858
23039
|
actor
|
|
21859
23040
|
);
|
|
23041
|
+
const idxPurge = await coll._purgePersistedIndexes(ref2.id);
|
|
23042
|
+
indexPostingsPurged += idxPurge.purged;
|
|
23043
|
+
for (const field of idxPurge.residue) indexResidue.push(`${ref2.collection}:${ref2.id}:${field}`);
|
|
21860
23044
|
if (blobsEnabled) {
|
|
21861
23045
|
const r = await this.collection(ref2.collection).blob(ref2.id).shredAllForRecord();
|
|
21862
23046
|
blobsShredded += r.shredded.length;
|
|
@@ -21892,7 +23076,9 @@ var Vault = class {
|
|
|
21892
23076
|
unmigratedCount: unmigratedRecords.length,
|
|
21893
23077
|
blobsShredded,
|
|
21894
23078
|
blobsRetainedShared,
|
|
21895
|
-
blobResidueCollections: [...blobResidueCollections]
|
|
23079
|
+
blobResidueCollections: [...blobResidueCollections],
|
|
23080
|
+
indexPostingsPurged,
|
|
23081
|
+
indexResidueCount: indexResidue.length
|
|
21896
23082
|
})
|
|
21897
23083
|
});
|
|
21898
23084
|
return {
|
|
@@ -21904,6 +23090,8 @@ var Vault = class {
|
|
|
21904
23090
|
blobsShredded,
|
|
21905
23091
|
blobsRetainedShared,
|
|
21906
23092
|
blobResidueCollections: [...blobResidueCollections],
|
|
23093
|
+
indexPostingsPurged,
|
|
23094
|
+
indexResidue,
|
|
21907
23095
|
ledgerEntry
|
|
21908
23096
|
};
|
|
21909
23097
|
}
|
|
@@ -22005,7 +23193,7 @@ var Vault = class {
|
|
|
22005
23193
|
const registry = new GuardRegistry2();
|
|
22006
23194
|
for (const h of handles) registry.register(h.spec);
|
|
22007
23195
|
this.guardRegistry = registry;
|
|
22008
|
-
this.
|
|
23196
|
+
this.guardFacade = new ReadOnlyVaultFacade2(this, "guard");
|
|
22009
23197
|
}
|
|
22010
23198
|
/**
|
|
22011
23199
|
* @internal — The gate handler in Noydb.#registerGuardGate calls into
|
|
@@ -22036,8 +23224,8 @@ var Vault = class {
|
|
|
22036
23224
|
}
|
|
22037
23225
|
registry.validate();
|
|
22038
23226
|
this.derivationRegistry = registry;
|
|
22039
|
-
if (this.
|
|
22040
|
-
this.
|
|
23227
|
+
if (this.derivationFacade === null) {
|
|
23228
|
+
this.derivationFacade = new ReadOnlyVaultFacade2(this, "derivation");
|
|
22041
23229
|
}
|
|
22042
23230
|
}
|
|
22043
23231
|
/**
|
|
@@ -22154,7 +23342,7 @@ var Vault = class {
|
|
|
22154
23342
|
const { DerivationExecutor: DerivationExecutor2 } = await Promise.resolve().then(() => (init_executor2(), executor_exports2));
|
|
22155
23343
|
const sourceColl = this.collection(sourceCollection);
|
|
22156
23344
|
const records = await sourceColl.list();
|
|
22157
|
-
const ctx = { vault: this.
|
|
23345
|
+
const ctx = { vault: this.derivationFacade ?? new (await Promise.resolve().then(() => (init_read_only_facade(), read_only_facade_exports))).ReadOnlyVaultFacade(this, "derivation") };
|
|
22158
23346
|
let derived = 0;
|
|
22159
23347
|
let failed = 0;
|
|
22160
23348
|
for (const record of records) {
|
|
@@ -22219,17 +23407,18 @@ var Vault = class {
|
|
|
22219
23407
|
* never see null).
|
|
22220
23408
|
*/
|
|
22221
23409
|
_getReadOnlyFacade() {
|
|
22222
|
-
return this.
|
|
23410
|
+
return this.guardFacade;
|
|
22223
23411
|
}
|
|
22224
23412
|
/**
|
|
22225
|
-
* Internal lazy-allocator for the read-only facade
|
|
22226
|
-
* defensive fallback; in practice
|
|
22227
|
-
* instantiates this, so the lazy path is
|
|
23413
|
+
* Internal lazy-allocator for the derivation read-only facade
|
|
23414
|
+
* (`layer:'derivation'`). Used as a defensive fallback; in practice
|
|
23415
|
+
* `_initDerivations()` eagerly instantiates this, so the lazy path is
|
|
23416
|
+
* a no-op.
|
|
22228
23417
|
*/
|
|
22229
23418
|
_ensureReadOnlyFacade() {
|
|
22230
|
-
if (this.
|
|
23419
|
+
if (this.derivationFacade !== null) return this.derivationFacade;
|
|
22231
23420
|
throw new Error(
|
|
22232
|
-
"Vault:
|
|
23421
|
+
"Vault: derivation hook fired before _initDerivations() completed. This typically means the vault was opened via the sync fallback path (Noydb.vault(name)) without first calling await db.openVault(name). See issue #132."
|
|
22233
23422
|
);
|
|
22234
23423
|
}
|
|
22235
23424
|
/**
|
|
@@ -22350,7 +23539,7 @@ var Vault = class {
|
|
|
22350
23539
|
*
|
|
22351
23540
|
* @internal
|
|
22352
23541
|
*/
|
|
22353
|
-
async _logConsent(op, collection,
|
|
23542
|
+
async _logConsent(op, collection, recordId4) {
|
|
22354
23543
|
const ctx = this.consentContext;
|
|
22355
23544
|
if (!ctx) return;
|
|
22356
23545
|
await this.consentStrategy.write(
|
|
@@ -22363,7 +23552,7 @@ var Vault = class {
|
|
|
22363
23552
|
consentHash: ctx.consentHash,
|
|
22364
23553
|
op,
|
|
22365
23554
|
collection,
|
|
22366
|
-
recordId:
|
|
23555
|
+
recordId: recordId4
|
|
22367
23556
|
},
|
|
22368
23557
|
this.getDEK
|
|
22369
23558
|
);
|
|
@@ -22546,14 +23735,14 @@ var Vault = class {
|
|
|
22546
23735
|
* the HKDF derivation, record-id composition, and batch logic so the
|
|
22547
23736
|
* grantor doesn't touch this method directly.
|
|
22548
23737
|
*/
|
|
22549
|
-
async writeMagicLinkGrant(contentKey, grantKek,
|
|
23738
|
+
async writeMagicLinkGrant(contentKey, grantKek, recordId4, opts) {
|
|
22550
23739
|
return writeMagicLinkGrant(
|
|
22551
23740
|
this.adapter,
|
|
22552
23741
|
this.name,
|
|
22553
23742
|
this.keyring,
|
|
22554
23743
|
contentKey,
|
|
22555
23744
|
grantKek,
|
|
22556
|
-
|
|
23745
|
+
recordId4,
|
|
22557
23746
|
opts
|
|
22558
23747
|
);
|
|
22559
23748
|
}
|
|
@@ -23165,6 +24354,8 @@ var Vault = class {
|
|
|
23165
24354
|
*/
|
|
23166
24355
|
async *exportStream(opts = {}) {
|
|
23167
24356
|
const granularity = opts.granularity ?? "collection";
|
|
24357
|
+
const exportLocale = opts.resolveLabels;
|
|
24358
|
+
const localeOpts = exportLocale !== void 0 ? { locale: exportLocale, _layer: "export" } : void 0;
|
|
23168
24359
|
const snapshot = await this.adapter.loadAll(this.name);
|
|
23169
24360
|
const collectionNames = Object.keys(snapshot).sort();
|
|
23170
24361
|
const ledgerHead = opts.withLedgerHead ? await (async () => {
|
|
@@ -23174,19 +24365,21 @@ var Vault = class {
|
|
|
23174
24365
|
return head ? { hash: head.hash, index: head.entry.index, ts: head.entry.ts } : void 0;
|
|
23175
24366
|
})() : void 0;
|
|
23176
24367
|
const dictSnapshotCache = /* @__PURE__ */ new Map();
|
|
23177
|
-
|
|
23178
|
-
const
|
|
23179
|
-
|
|
23180
|
-
|
|
23181
|
-
|
|
23182
|
-
const
|
|
23183
|
-
|
|
23184
|
-
|
|
23185
|
-
|
|
24368
|
+
if (exportLocale === void 0) {
|
|
24369
|
+
for (const collectionName of collectionNames) {
|
|
24370
|
+
const dictFields = this.dictKeyFieldRegistry.get(collectionName);
|
|
24371
|
+
if (dictFields && Object.keys(dictFields).length > 0) {
|
|
24372
|
+
const snap = {};
|
|
24373
|
+
for (const [fieldName, dictName] of Object.entries(dictFields)) {
|
|
24374
|
+
const entries = await this.dictionary(dictName).list();
|
|
24375
|
+
const keyMap = {};
|
|
24376
|
+
for (const entry of entries) {
|
|
24377
|
+
keyMap[entry.key] = entry.labels;
|
|
24378
|
+
}
|
|
24379
|
+
snap[fieldName] = keyMap;
|
|
23186
24380
|
}
|
|
23187
|
-
snap
|
|
24381
|
+
dictSnapshotCache.set(collectionName, snap);
|
|
23188
24382
|
}
|
|
23189
|
-
dictSnapshotCache.set(collectionName, snap);
|
|
23190
24383
|
}
|
|
23191
24384
|
}
|
|
23192
24385
|
for (const collectionName of collectionNames) {
|
|
@@ -23199,7 +24392,7 @@ var Vault = class {
|
|
|
23199
24392
|
if (granularity === "collection") {
|
|
23200
24393
|
const records = [];
|
|
23201
24394
|
for (const id of ids) {
|
|
23202
|
-
const record = await coll.get(id);
|
|
24395
|
+
const record = await coll.get(id, localeOpts);
|
|
23203
24396
|
if (record !== null) records.push(record);
|
|
23204
24397
|
}
|
|
23205
24398
|
const chunk = {
|
|
@@ -23213,7 +24406,7 @@ var Vault = class {
|
|
|
23213
24406
|
yield chunk;
|
|
23214
24407
|
} else {
|
|
23215
24408
|
for (const id of ids) {
|
|
23216
|
-
const record = await coll.get(id);
|
|
24409
|
+
const record = await coll.get(id, localeOpts);
|
|
23217
24410
|
if (record === null) continue;
|
|
23218
24411
|
const chunk = {
|
|
23219
24412
|
collection: collectionName,
|
|
@@ -23317,7 +24510,10 @@ var Vault = class {
|
|
|
23317
24510
|
const allDictionaries = {};
|
|
23318
24511
|
for await (const chunk of this.exportStream({
|
|
23319
24512
|
granularity: "collection",
|
|
23320
|
-
withLedgerHead: opts.withLedgerHead === true
|
|
24513
|
+
withLedgerHead: opts.withLedgerHead === true,
|
|
24514
|
+
// #285 export layer: thread the export locale so records are read at the
|
|
24515
|
+
// `export` layer (i18nText collapsed + dictKey/staticDict labels resolved).
|
|
24516
|
+
...opts.resolveLabels !== void 0 ? { resolveLabels: opts.resolveLabels } : {}
|
|
23321
24517
|
})) {
|
|
23322
24518
|
collections[chunk.collection] = {
|
|
23323
24519
|
schema: null,
|
|
@@ -25117,7 +26313,7 @@ var Noydb = class {
|
|
|
25117
26313
|
const { StateManagementVault: StateManagementVault2 } = await Promise.resolve().then(() => (init_state_vault(), state_vault_exports));
|
|
25118
26314
|
const stateVault = opts.registry ? void 0 : await StateManagementVault2.open(this);
|
|
25119
26315
|
const registry = opts.registry ?? stateVault.registry;
|
|
25120
|
-
const group = new VaultGroup2(this, name, registry, opts.sharding, template);
|
|
26316
|
+
const group = new VaultGroup2(this, name, registry, opts.sharding, template, opts.migrateOnOpen ?? false);
|
|
25121
26317
|
if (stateVault) {
|
|
25122
26318
|
group._attachStateVault(stateVault);
|
|
25123
26319
|
await stateVault.recordManifest(opts.sharding.vaultTemplate, template);
|
|
@@ -25151,6 +26347,16 @@ var Noydb = class {
|
|
|
25151
26347
|
async _shardVaultProvisioned(vaultId) {
|
|
25152
26348
|
return (await this.options.store.list(vaultId, "_keyring")).length > 0;
|
|
25153
26349
|
}
|
|
26350
|
+
/**
|
|
26351
|
+
* @internal — the physical backend store a vault id maps to. A
|
|
26352
|
+
* `routeStore` resolves the vault-prefix route via its `resolveBackend`;
|
|
26353
|
+
* a plain store is its own backend. Used by the federation data-residency
|
|
26354
|
+
* guard to read the placement backend's `capabilities.region` (#271).
|
|
26355
|
+
*/
|
|
26356
|
+
_resolveBackend(vaultId) {
|
|
26357
|
+
const store = this.options.store;
|
|
26358
|
+
return store.resolveBackend ? store.resolveBackend(vaultId) : this.options.store;
|
|
26359
|
+
}
|
|
25154
26360
|
/**
|
|
25155
26361
|
* Change the current user's passphrase for a vault.
|
|
25156
26362
|
*
|
|
@@ -27461,6 +28667,60 @@ function immutableGuard(config) {
|
|
|
27461
28667
|
return withGuard(spec);
|
|
27462
28668
|
}
|
|
27463
28669
|
|
|
28670
|
+
// src/guards/transition-guard.ts
|
|
28671
|
+
init_errors();
|
|
28672
|
+
function recordId3(record) {
|
|
28673
|
+
const id = record?.id;
|
|
28674
|
+
return typeof id === "string" ? id : "";
|
|
28675
|
+
}
|
|
28676
|
+
function stateOf(record, field) {
|
|
28677
|
+
const v = record[field];
|
|
28678
|
+
return typeof v === "string" ? v : String(v);
|
|
28679
|
+
}
|
|
28680
|
+
function transitionGuard(config) {
|
|
28681
|
+
const { collection, field, transitions, initial, amendmentRoles, amendmentInvariant } = config;
|
|
28682
|
+
const allowIdempotent = config.allowIdempotent ?? true;
|
|
28683
|
+
if (!field) {
|
|
28684
|
+
throw new ValidationError("transitionGuard: `field` is required");
|
|
28685
|
+
}
|
|
28686
|
+
if (transitions === void 0 || typeof transitions !== "object") {
|
|
28687
|
+
throw new ValidationError("transitionGuard: `transitions` must be a state\u2192states map");
|
|
28688
|
+
}
|
|
28689
|
+
const spec = {
|
|
28690
|
+
collection,
|
|
28691
|
+
check: (incoming, ctx) => {
|
|
28692
|
+
const rec = incoming;
|
|
28693
|
+
const to = stateOf(rec, field);
|
|
28694
|
+
if (ctx.existing === null) {
|
|
28695
|
+
if (initial !== void 0 && !initial.includes(to)) {
|
|
28696
|
+
throw new IllegalTransitionError(collection, recordId3(rec), "(none)", to);
|
|
28697
|
+
}
|
|
28698
|
+
return;
|
|
28699
|
+
}
|
|
28700
|
+
const from = stateOf(ctx.existing, field);
|
|
28701
|
+
if (from === to) {
|
|
28702
|
+
if (allowIdempotent) return;
|
|
28703
|
+
throw new IllegalTransitionError(collection, recordId3(rec), from, to);
|
|
28704
|
+
}
|
|
28705
|
+
const allowed = transitions[from] ?? [];
|
|
28706
|
+
if (!allowed.includes(to)) {
|
|
28707
|
+
throw new IllegalTransitionError(collection, recordId3(rec), from, to);
|
|
28708
|
+
}
|
|
28709
|
+
},
|
|
28710
|
+
// The authorized override: inside an amendment transaction the check
|
|
28711
|
+
// is skipped and the change is ledgered. By default no extra invariant
|
|
28712
|
+
// — the amendment itself is the sanctioned exception. Callers may
|
|
28713
|
+
// supply `amendmentInvariant` to keep a constraint inviolable even
|
|
28714
|
+
// under amendment; a throw reverts the amendment as `InvariantError`.
|
|
28715
|
+
amendment: {
|
|
28716
|
+
roles: amendmentRoles ?? ["admin", "owner"],
|
|
28717
|
+
invariant: amendmentInvariant ?? (() => {
|
|
28718
|
+
})
|
|
28719
|
+
}
|
|
28720
|
+
};
|
|
28721
|
+
return withGuard(spec);
|
|
28722
|
+
}
|
|
28723
|
+
|
|
27464
28724
|
// src/derivations/with-derivation.ts
|
|
27465
28725
|
init_errors();
|
|
27466
28726
|
function withDerivation(spec) {
|
|
@@ -27488,8 +28748,37 @@ function withDerivation(spec) {
|
|
|
27488
28748
|
}
|
|
27489
28749
|
}
|
|
27490
28750
|
}
|
|
28751
|
+
if (spec.triggerBy !== void 0) {
|
|
28752
|
+
for (const t of spec.triggerBy) {
|
|
28753
|
+
if (typeof t?.collection !== "string" || t.collection.length === 0) {
|
|
28754
|
+
throw new ValidationError("withDerivation: each triggerBy entry needs a non-empty `collection`");
|
|
28755
|
+
}
|
|
28756
|
+
if (t.collection === spec.source) {
|
|
28757
|
+
throw new ValidationError(
|
|
28758
|
+
`withDerivation: triggerBy.collection must not equal the source "${spec.source}" (use sources[] for same-id triggers)`
|
|
28759
|
+
);
|
|
28760
|
+
}
|
|
28761
|
+
if (typeof t.on !== "string" || t.on.length === 0) {
|
|
28762
|
+
throw new ValidationError(
|
|
28763
|
+
`withDerivation: triggerBy on "${t.collection}" needs a non-empty \`on\` (the FK field on the source)`
|
|
28764
|
+
);
|
|
28765
|
+
}
|
|
28766
|
+
if (t.maxFanout !== void 0 && (!Number.isInteger(t.maxFanout) || t.maxFanout < 1)) {
|
|
28767
|
+
throw new ValidationError(
|
|
28768
|
+
`withDerivation: triggerBy maxFanout on "${t.collection}" must be a positive integer (got ${String(t.maxFanout)}).`
|
|
28769
|
+
);
|
|
28770
|
+
}
|
|
28771
|
+
}
|
|
28772
|
+
}
|
|
27491
28773
|
const lifecycleMode = typeof spec.lifecycle === "string" ? spec.lifecycle : spec.lifecycle.mode;
|
|
27492
28774
|
for (const [outputKey, outputSpec] of Object.entries(spec.outputs)) {
|
|
28775
|
+
if (outputSpec.shape === "record" && outputSpec.collection === spec.source) {
|
|
28776
|
+
if (!outputSpec.denorm || outputSpec.denorm.length === 0) {
|
|
28777
|
+
throw new ValidationError(
|
|
28778
|
+
`withDerivation: self-write output "${outputKey}" (collection === source "${spec.source}") must declare \`denorm: [...]\` naming the fields it maintains.`
|
|
28779
|
+
);
|
|
28780
|
+
}
|
|
28781
|
+
}
|
|
27493
28782
|
if (outputSpec.shape === "array") {
|
|
27494
28783
|
if (lifecycleMode !== "eager") {
|
|
27495
28784
|
throw new ValidationError(
|
|
@@ -27517,6 +28806,43 @@ function withDerivation(spec) {
|
|
|
27517
28806
|
};
|
|
27518
28807
|
}
|
|
27519
28808
|
|
|
28809
|
+
// src/derivations/with-rollup.ts
|
|
28810
|
+
init_errors();
|
|
28811
|
+
function withRollup(config) {
|
|
28812
|
+
const { from, key, into, field, compute } = config;
|
|
28813
|
+
if (!from || from.length === 0) {
|
|
28814
|
+
throw new ValidationError("withRollup: `from` (child collection) is required");
|
|
28815
|
+
}
|
|
28816
|
+
if (!into || into.length === 0) {
|
|
28817
|
+
throw new ValidationError("withRollup: `into` (parent collection) is required");
|
|
28818
|
+
}
|
|
28819
|
+
if (from === into) {
|
|
28820
|
+
throw new ValidationError("withRollup: `from` and `into` must be different collections");
|
|
28821
|
+
}
|
|
28822
|
+
if (!key || key.length === 0) {
|
|
28823
|
+
throw new ValidationError("withRollup: `key` (FK field on the child) is required");
|
|
28824
|
+
}
|
|
28825
|
+
if (!field || field.length === 0) {
|
|
28826
|
+
throw new ValidationError("withRollup: `field` (target field on the parent) is required");
|
|
28827
|
+
}
|
|
28828
|
+
if (typeof compute !== "function") {
|
|
28829
|
+
throw new ValidationError("withRollup: `compute` must be a function");
|
|
28830
|
+
}
|
|
28831
|
+
const spec = {
|
|
28832
|
+
source: into,
|
|
28833
|
+
// the parent record is what carries the rolled-up field
|
|
28834
|
+
deterministic: true,
|
|
28835
|
+
rollup: { from, key, field, compute },
|
|
28836
|
+
// Synthetic self-write output for registry / cycle bookkeeping. Dispatch
|
|
28837
|
+
// patches `field` directly (value-equality guarded); the executor is not run.
|
|
28838
|
+
outputs: { value: { shape: "record", collection: into, denorm: [field] } },
|
|
28839
|
+
derive: () => ({ value: {} }),
|
|
28840
|
+
// never invoked for a rollup strategy
|
|
28841
|
+
lifecycle: "eager"
|
|
28842
|
+
};
|
|
28843
|
+
return { __noydb_strategy: "derivation", spec };
|
|
28844
|
+
}
|
|
28845
|
+
|
|
27520
28846
|
// src/index.ts
|
|
27521
28847
|
init_errors();
|
|
27522
28848
|
|
|
@@ -27590,6 +28916,11 @@ function withMaterializedView(spec) {
|
|
|
27590
28916
|
);
|
|
27591
28917
|
}
|
|
27592
28918
|
}
|
|
28919
|
+
if (spec.i18nLocale !== void 0 && spec.i18nFields === void 0) {
|
|
28920
|
+
throw new MaterializedViewConfigError(
|
|
28921
|
+
`withMaterializedView "${spec.name}": i18nLocale requires i18nFields \u2014 declare the i18nText descriptors of the group-key fields so they can be resolved at the mv layer before bucketing.`
|
|
28922
|
+
);
|
|
28923
|
+
}
|
|
27593
28924
|
if (typeof spec.rowKey !== "function") {
|
|
27594
28925
|
throw new ValidationError("withMaterializedView: rowKey is required (no default; see spec \xA7 Type surface)");
|
|
27595
28926
|
}
|
|
@@ -27639,6 +28970,7 @@ function withOverlayedView(spec) {
|
|
|
27639
28970
|
// src/index.ts
|
|
27640
28971
|
init_errors();
|
|
27641
28972
|
init_errors();
|
|
28973
|
+
init_core();
|
|
27642
28974
|
|
|
27643
28975
|
// src/money/index.ts
|
|
27644
28976
|
init_descriptor();
|
|
@@ -27755,138 +29087,9 @@ function isMoneyLike(value) {
|
|
|
27755
29087
|
return decimalScaleOf(value) !== null;
|
|
27756
29088
|
}
|
|
27757
29089
|
|
|
27758
|
-
// src/i18n/script.ts
|
|
27759
|
-
init_errors();
|
|
27760
|
-
var LATIN_BASE = /* @__PURE__ */ new Set([
|
|
27761
|
-
"en",
|
|
27762
|
-
"fr",
|
|
27763
|
-
"de",
|
|
27764
|
-
"es",
|
|
27765
|
-
"it",
|
|
27766
|
-
"pt",
|
|
27767
|
-
"nl",
|
|
27768
|
-
"sv",
|
|
27769
|
-
"no",
|
|
27770
|
-
"da",
|
|
27771
|
-
"fi",
|
|
27772
|
-
"is",
|
|
27773
|
-
"pl",
|
|
27774
|
-
"cs",
|
|
27775
|
-
"sk",
|
|
27776
|
-
"hu",
|
|
27777
|
-
"ro",
|
|
27778
|
-
"hr",
|
|
27779
|
-
"sl",
|
|
27780
|
-
"et",
|
|
27781
|
-
"lv",
|
|
27782
|
-
"lt",
|
|
27783
|
-
"tr",
|
|
27784
|
-
"vi",
|
|
27785
|
-
"id",
|
|
27786
|
-
"ms",
|
|
27787
|
-
"tl",
|
|
27788
|
-
"sw",
|
|
27789
|
-
"af",
|
|
27790
|
-
"ca",
|
|
27791
|
-
"gl",
|
|
27792
|
-
"eu",
|
|
27793
|
-
"cy",
|
|
27794
|
-
"ga"
|
|
27795
|
-
]);
|
|
27796
|
-
var SCRIPT_TABLE = {
|
|
27797
|
-
th: ["Thai"],
|
|
27798
|
-
ko: ["Hangul", "Han"],
|
|
27799
|
-
ja: ["Han", "Hiragana", "Katakana"],
|
|
27800
|
-
zh: ["Han"],
|
|
27801
|
-
ar: ["Arabic"],
|
|
27802
|
-
fa: ["Arabic"],
|
|
27803
|
-
ur: ["Arabic"],
|
|
27804
|
-
ru: ["Cyrillic"],
|
|
27805
|
-
uk: ["Cyrillic"],
|
|
27806
|
-
bg: ["Cyrillic"],
|
|
27807
|
-
sr: ["Cyrillic"],
|
|
27808
|
-
he: ["Hebrew"],
|
|
27809
|
-
el: ["Greek"],
|
|
27810
|
-
hi: ["Devanagari"],
|
|
27811
|
-
ta: ["Tamil"],
|
|
27812
|
-
km: ["Khmer"],
|
|
27813
|
-
lo: ["Lao"],
|
|
27814
|
-
my: ["Myanmar"]
|
|
27815
|
-
};
|
|
27816
|
-
var SUBTAG_SCRIPTS = {
|
|
27817
|
-
Latn: ["Latin"],
|
|
27818
|
-
Cyrl: ["Cyrillic", "Latin"],
|
|
27819
|
-
Hans: ["Han", "Latin"],
|
|
27820
|
-
Hant: ["Han", "Latin"],
|
|
27821
|
-
Thai: ["Thai", "Latin"],
|
|
27822
|
-
Arab: ["Arabic", "Latin"]
|
|
27823
|
-
};
|
|
27824
|
-
function inferScripts(locale) {
|
|
27825
|
-
const parts = locale.split("-");
|
|
27826
|
-
const subtag = parts.find((t) => /^[A-Z][a-z]{3}$/.test(t));
|
|
27827
|
-
if (subtag && SUBTAG_SCRIPTS[subtag]) return SUBTAG_SCRIPTS[subtag];
|
|
27828
|
-
const base = (parts[0] ?? "").toLowerCase();
|
|
27829
|
-
if (LATIN_BASE.has(base)) return ["Latin"];
|
|
27830
|
-
const primary = SCRIPT_TABLE[base];
|
|
27831
|
-
if (primary) return [...primary, "Latin"];
|
|
27832
|
-
return ["Latin"];
|
|
27833
|
-
}
|
|
27834
|
-
function allowedFor(descriptor, locale) {
|
|
27835
|
-
const script = descriptor.options.script;
|
|
27836
|
-
if (script && script !== "auto") {
|
|
27837
|
-
const explicit = script[locale];
|
|
27838
|
-
if (explicit) return explicit;
|
|
27839
|
-
}
|
|
27840
|
-
return inferScripts(locale);
|
|
27841
|
-
}
|
|
27842
|
-
var BASELINE = String.raw`\p{White_Space}\p{Script=Common}\p{Script=Inherited}\p{Mark}`;
|
|
27843
|
-
function fullMatcher(scripts) {
|
|
27844
|
-
const cls = scripts.map((s) => `\\p{Script=${s}}`).join("");
|
|
27845
|
-
return new RegExp(`^[${BASELINE}${cls}]*$`, "u");
|
|
27846
|
-
}
|
|
27847
|
-
function charMatcher(scripts) {
|
|
27848
|
-
const cls = scripts.map((s) => `\\p{Script=${s}}`).join("");
|
|
27849
|
-
return new RegExp(`[${BASELINE}${cls}]`, "u");
|
|
27850
|
-
}
|
|
27851
|
-
function offendingSample(str, scripts) {
|
|
27852
|
-
const ok = charMatcher(scripts);
|
|
27853
|
-
const bad = [];
|
|
27854
|
-
for (const ch of str) {
|
|
27855
|
-
if (!ok.test(ch)) bad.push(ch);
|
|
27856
|
-
if (bad.length >= 8) break;
|
|
27857
|
-
}
|
|
27858
|
-
return bad.join("");
|
|
27859
|
-
}
|
|
27860
|
-
function stripDisallowed(str, scripts) {
|
|
27861
|
-
const ok = charMatcher(scripts);
|
|
27862
|
-
let out = "";
|
|
27863
|
-
for (const ch of str) if (ok.test(ch)) out += ch;
|
|
27864
|
-
return out;
|
|
27865
|
-
}
|
|
27866
|
-
function enforceScript(value, field, descriptor) {
|
|
27867
|
-
const opt = descriptor.options;
|
|
27868
|
-
if (!opt.script) return { value, warnings: [] };
|
|
27869
|
-
const mode = opt.onScriptViolation ?? "reject";
|
|
27870
|
-
const warnings = [];
|
|
27871
|
-
let out = value;
|
|
27872
|
-
for (const [locale, raw] of Object.entries(value)) {
|
|
27873
|
-
if (typeof raw !== "string") continue;
|
|
27874
|
-
const allowed = allowedFor(descriptor, locale);
|
|
27875
|
-
if (fullMatcher(allowed).test(raw)) continue;
|
|
27876
|
-
const sample = offendingSample(raw, allowed);
|
|
27877
|
-
if (mode === "reject") {
|
|
27878
|
-
throw new ScriptViolationError(field, locale, allowed, sample);
|
|
27879
|
-
}
|
|
27880
|
-
warnings.push({ field, locale, expected: allowed, sample });
|
|
27881
|
-
if (mode === "filter") {
|
|
27882
|
-
if (out === value) out = { ...value };
|
|
27883
|
-
out[locale] = stripDisallowed(raw, allowed);
|
|
27884
|
-
}
|
|
27885
|
-
}
|
|
27886
|
-
return { value: out, warnings };
|
|
27887
|
-
}
|
|
27888
|
-
|
|
27889
29090
|
// src/index.ts
|
|
29091
|
+
init_policy();
|
|
29092
|
+
init_script();
|
|
27890
29093
|
init_errors();
|
|
27891
29094
|
|
|
27892
29095
|
// src/team/sync-credentials.ts
|
|
@@ -28553,6 +29756,7 @@ function shortJSON(value) {
|
|
|
28553
29756
|
DICT_COLLECTION_PREFIX,
|
|
28554
29757
|
DIRECTORY_RECORD_ID,
|
|
28555
29758
|
DanglingReferenceError,
|
|
29759
|
+
DataResidencyError,
|
|
28556
29760
|
DecryptionError,
|
|
28557
29761
|
DelegationTargetMissingError,
|
|
28558
29762
|
DerivationCapExceededError,
|
|
@@ -28578,6 +29782,7 @@ function shortJSON(value) {
|
|
|
28578
29782
|
GroupedQuery,
|
|
28579
29783
|
GroupedQueryN,
|
|
28580
29784
|
INDEXED_STORE_POLICY,
|
|
29785
|
+
IllegalTransitionError,
|
|
28581
29786
|
ImportCapabilityError,
|
|
28582
29787
|
IndexRequiredError,
|
|
28583
29788
|
IndexWriteFailureError,
|
|
@@ -28590,6 +29795,8 @@ function shortJSON(value) {
|
|
|
28590
29795
|
LEDGER_DELTAS_COLLECTION,
|
|
28591
29796
|
LedgerContentionError,
|
|
28592
29797
|
LedgerStore,
|
|
29798
|
+
LinkEndpointError,
|
|
29799
|
+
LinkIntegrityError,
|
|
28593
29800
|
LocaleNotSpecifiedError,
|
|
28594
29801
|
Lru,
|
|
28595
29802
|
MAGIC_LINK_CONTENT_INFO_PREFIX,
|
|
@@ -28721,6 +29928,7 @@ function shortJSON(value) {
|
|
|
28721
29928
|
canonicalJson,
|
|
28722
29929
|
checkGate,
|
|
28723
29930
|
clearDevUnlock,
|
|
29931
|
+
compileSequenceFormat,
|
|
28724
29932
|
computePatch,
|
|
28725
29933
|
coordinatedCutover,
|
|
28726
29934
|
count,
|
|
@@ -28783,11 +29991,13 @@ function shortJSON(value) {
|
|
|
28783
29991
|
isDictKeyDescriptor,
|
|
28784
29992
|
isDiscriminant,
|
|
28785
29993
|
isI18nTextDescriptor,
|
|
29994
|
+
isLinkCollectionName,
|
|
28786
29995
|
isMagicLinkGrantExpired,
|
|
28787
29996
|
isMoneyDescriptor,
|
|
28788
29997
|
isMoneyString,
|
|
28789
29998
|
isPreCompressed,
|
|
28790
29999
|
isPublicEnvelope,
|
|
30000
|
+
isRefArray,
|
|
28791
30001
|
isSessionAlive,
|
|
28792
30002
|
isStaticDictDescriptor,
|
|
28793
30003
|
isULID,
|
|
@@ -28841,6 +30051,7 @@ function shortJSON(value) {
|
|
|
28841
30051
|
recoverUser,
|
|
28842
30052
|
reduceRecords,
|
|
28843
30053
|
ref,
|
|
30054
|
+
refArray,
|
|
28844
30055
|
removeAuthenticator,
|
|
28845
30056
|
resetBrotliSupportCache,
|
|
28846
30057
|
resetJoinWarnings,
|
|
@@ -28868,6 +30079,8 @@ function shortJSON(value) {
|
|
|
28868
30079
|
sha256Hex,
|
|
28869
30080
|
staticDict,
|
|
28870
30081
|
sum,
|
|
30082
|
+
tokenize,
|
|
30083
|
+
transitionGuard,
|
|
28871
30084
|
unwrapDeksFromBlob,
|
|
28872
30085
|
unwrapDeksFromPaperEntry,
|
|
28873
30086
|
unwrapDeksFromShamirEntry,
|
|
@@ -28891,6 +30104,7 @@ function shortJSON(value) {
|
|
|
28891
30104
|
withMetrics,
|
|
28892
30105
|
withOverlayedView,
|
|
28893
30106
|
withRetry,
|
|
30107
|
+
withRollup,
|
|
28894
30108
|
wrapBundleStore,
|
|
28895
30109
|
wrapStore,
|
|
28896
30110
|
writeMagicLinkGrant,
|