@noy-db/hub 0.2.0-pre.17 → 0.2.0-pre.18
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.map +1 -1
- package/dist/aggregate/index.d.cts +3 -3
- package/dist/aggregate/index.d.ts +3 -3
- package/dist/aggregate/index.js +4 -4
- package/dist/attestation/index.cjs.map +1 -1
- package/dist/attestation/index.d.cts +4 -4
- package/dist/attestation/index.d.ts +4 -4
- package/dist/attestation/index.js +6 -6
- package/dist/blobs/index.cjs.map +1 -1
- package/dist/blobs/index.d.cts +5 -5
- package/dist/blobs/index.d.ts +5 -5
- package/dist/blobs/index.js +6 -6
- package/dist/bundle/index.cjs +994 -273
- package/dist/bundle/index.cjs.map +1 -1
- package/dist/bundle/index.d.cts +6 -6
- package/dist/bundle/index.d.ts +6 -6
- package/dist/bundle/index.js +10 -10
- package/dist/{chunk-SHX5QBCI.js → chunk-2U226RDC.js} +3 -3
- package/dist/{chunk-7H2GEJ3O.js → chunk-32XVU2LT.js} +3 -3
- package/dist/{chunk-XJV6OB4D.js → chunk-33DAO2XG.js} +2 -2
- package/dist/{chunk-U5QCMH3W.js → chunk-45643PAU.js} +4 -4
- package/dist/{chunk-BH3X5L6A.js → chunk-4UI5T3K7.js} +3 -3
- package/dist/{chunk-2FU2FTXD.js → chunk-5KKNBDCT.js} +2 -2
- package/dist/{chunk-HGVSHKZW.js → chunk-647TFNYL.js} +30 -7
- package/dist/chunk-647TFNYL.js.map +1 -0
- package/dist/{chunk-CD2AVTEM.js → chunk-6FHCU3QO.js} +5 -5
- package/dist/{chunk-NBBMMJ2H.js → chunk-6Q5XRLKG.js} +4 -4
- package/dist/{chunk-XPIHJ34I.js → chunk-6XEGHIBA.js} +4 -4
- package/dist/{chunk-5LIROIDM.js → chunk-6YEC7LLO.js} +2 -2
- package/dist/{chunk-C3HYQPV4.js → chunk-AB7JF2KF.js} +2 -2
- package/dist/{chunk-UMLVJTYV.js → chunk-ADB7GPM3.js} +7 -4
- package/dist/chunk-ADB7GPM3.js.map +1 -0
- package/dist/{chunk-KCEHMDZF.js → chunk-BUBJYIZ7.js} +3 -3
- package/dist/{chunk-I5IUYN7B.js → chunk-C2OYWD5S.js} +3 -3
- package/dist/{chunk-WV7WV6JO.js → chunk-CMISAJAE.js} +5 -5
- package/dist/{chunk-SNMJ7SB3.js → chunk-DKMPR76W.js} +5 -5
- package/dist/{chunk-XMVHEWF6.js → chunk-DR5I7Q6N.js} +4 -4
- package/dist/{chunk-D77ZQSQQ.js → chunk-F2IJ2HGD.js} +652 -152
- package/dist/chunk-F2IJ2HGD.js.map +1 -0
- package/dist/{chunk-BSZOCSDZ.js → chunk-FQRAYDS4.js} +4 -4
- package/dist/{chunk-ZEGSDPB7.js → chunk-HMFC6M2G.js} +19 -1
- package/dist/chunk-HMFC6M2G.js.map +1 -0
- package/dist/{chunk-M476FOQ7.js → chunk-HOO5I3VG.js} +2 -2
- package/dist/{chunk-F4G63NTZ.js → chunk-HWK75CYX.js} +2 -2
- package/dist/{chunk-FEJDVE3Z.js → chunk-HZOEBM67.js} +2 -2
- package/dist/{chunk-QHM6XEAH.js → chunk-IQ4GMEYZ.js} +6 -6
- package/dist/{chunk-5AXTH4QZ.js → chunk-K3NYRK7U.js} +2 -2
- package/dist/{chunk-ROPJVUG3.js → chunk-KOURQXIU.js} +5 -5
- package/dist/chunk-KOURQXIU.js.map +1 -0
- package/dist/{chunk-GP3SDSH2.js → chunk-KQ523X3A.js} +15 -2
- package/dist/chunk-KQ523X3A.js.map +1 -0
- package/dist/{chunk-3G3W65EQ.js → chunk-KTZ2MHQK.js} +2 -2
- package/dist/{chunk-SISBMAPO.js → chunk-LGPSCKWZ.js} +1 -1
- package/dist/chunk-LGPSCKWZ.js.map +1 -0
- package/dist/{chunk-E77UKJYL.js → chunk-LQ3GD5LL.js} +5 -5
- package/dist/{chunk-JDWE6JMX.js → chunk-M3H7VSRV.js} +2 -2
- package/dist/{chunk-DWEBTE2W.js → chunk-MGB67HKX.js} +4 -4
- package/dist/{chunk-AEIKD3PP.js → chunk-P57D4KBG.js} +3 -3
- package/dist/{chunk-YYVZYTWW.js → chunk-PGVEL5IZ.js} +3 -3
- package/dist/{chunk-UNTGHX5A.js → chunk-QJKZ5WUP.js} +2 -2
- package/dist/{chunk-BJSLBUJ7.js → chunk-QPJ7Z4L3.js} +2 -2
- package/dist/{chunk-NYSYPFXJ.js → chunk-RQFG2YSV.js} +3 -3
- package/dist/{chunk-J7RWBXFY.js → chunk-RZWQNMMP.js} +2 -2
- package/dist/{chunk-BL5GYANC.js → chunk-T4T5I5L6.js} +3 -3
- package/dist/{chunk-ZNGPEV5J.js → chunk-TFAN3NFD.js} +3 -3
- package/dist/{chunk-DYYYUW5D.js → chunk-TPOHMOGX.js} +2 -2
- package/dist/{chunk-XMHUK5PN.js → chunk-TTS3RWL5.js} +2 -2
- package/dist/{chunk-TIDXB5DF.js → chunk-VVDSDOVV.js} +4 -4
- package/dist/{chunk-WIAOUFFB.js → chunk-WZCG3EZ6.js} +2 -2
- package/dist/{chunk-QO6RGLLD.js → chunk-Y5XVB75E.js} +4 -4
- package/dist/chunk-YWYW2YNO.js +129 -0
- package/dist/chunk-YWYW2YNO.js.map +1 -0
- package/dist/{chunk-H2MRGONI.js → chunk-Z3BE5BRK.js} +2 -2
- package/dist/{chunk-ROVO6NPJ.js → chunk-Z3I2WNGF.js} +58 -3
- package/dist/chunk-Z3I2WNGF.js.map +1 -0
- package/dist/{state-vault-W2OEABNO.js → chunk-ZJ67TB4S.js} +24 -7
- package/dist/chunk-ZJ67TB4S.js.map +1 -0
- package/dist/consent/index.cjs.map +1 -1
- package/dist/consent/index.d.cts +5 -5
- package/dist/consent/index.d.ts +5 -5
- package/dist/consent/index.js +3 -3
- package/dist/{crypto-7BN2HDWG.js → crypto-FNK3XPCS.js} +3 -3
- package/dist/{delegation-MGH5SODX.js → delegation-FMXNUWE6.js} +5 -5
- package/dist/derivations/index.cjs +82 -2
- package/dist/derivations/index.cjs.map +1 -1
- package/dist/derivations/index.d.cts +6 -6
- package/dist/derivations/index.d.ts +6 -6
- package/dist/derivations/index.js +8 -6
- package/dist/{dev-unlock-iXbYFAWl.d.cts → dev-unlock-3_2b_vo6.d.cts} +1 -1
- package/dist/{dev-unlock-CI1ijTML.d.ts → dev-unlock-BMvwPr_E.d.ts} +1 -1
- package/dist/{errors-Dz64FA65.d.ts → errors-DUTlAt3Y.d.cts} +16 -1
- package/dist/{errors-Dz64FA65.d.cts → errors-DUTlAt3Y.d.ts} +16 -1
- package/dist/executor-IZ2NVXCY.js +11 -0
- package/dist/executor-THSEYEJG.js +8 -0
- package/dist/executor-WLFDUTOM.js +8 -0
- package/dist/{fanout-sidecar-FIJJ46YG.js → fanout-sidecar-JGHXAJO5.js} +2 -2
- 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 +6 -6
- package/dist/guards/index.d.ts +6 -6
- package/dist/guards/index.js +8 -4
- package/dist/{hash-blk7Bkes.d.ts → hash-BThBJFO1.d.ts} +1 -1
- package/dist/{hash-tEcM5fnv.d.cts → hash-BnWnL9bQ.d.cts} +1 -1
- package/dist/history/index.cjs.map +1 -1
- package/dist/history/index.d.cts +6 -6
- package/dist/history/index.d.ts +6 -6
- package/dist/history/index.js +5 -5
- package/dist/i18n/index.cjs.map +1 -1
- package/dist/i18n/index.d.cts +5 -5
- package/dist/i18n/index.d.ts +5 -5
- package/dist/i18n/index.js +6 -6
- package/dist/{index-u-kWzSrL.d.cts → index-C6lgoUhK.d.cts} +40 -3
- package/dist/{index-DpU6KWof.d.ts → index-DP1JTWHZ.d.ts} +40 -3
- package/dist/index.cjs +1202 -322
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -14
- package/dist/index.d.ts +14 -14
- package/dist/index.js +65 -47
- 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-R2MWQO6K.js +12 -0
- package/dist/{ledger-LFVLHE5H.js → ledger-GXC2YA3A.js} +5 -5
- package/dist/materialized-views/index.cjs.map +1 -1
- package/dist/materialized-views/index.d.cts +6 -6
- package/dist/materialized-views/index.d.ts +6 -6
- package/dist/materialized-views/index.js +7 -7
- package/dist/noydb-RJL6FQ4B.js +37 -0
- package/dist/overlay-views/index.cjs.map +1 -1
- package/dist/overlay-views/index.d.cts +6 -6
- package/dist/overlay-views/index.d.ts +6 -6
- package/dist/overlay-views/index.js +4 -4
- package/dist/periods/index.cjs.map +1 -1
- package/dist/periods/index.d.cts +5 -5
- package/dist/periods/index.d.ts +5 -5
- package/dist/periods/index.js +5 -5
- package/dist/{public-envelope-RXZNP3V6.js → public-envelope-HXOFHY4N.js} +4 -4
- package/dist/query/index.cjs +26 -3
- 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 +6 -6
- package/dist/read-only-facade-EX6WZZBP.js +7 -0
- package/dist/registry-3T2RZC5A.js +8 -0
- package/dist/registry-DMS7OKBM.js +8 -0
- package/dist/{registry-SECUWSGY.js → registry-WVXO6NH5.js} +3 -3
- package/dist/{revoke-B54H2S2W.js → revoke-7LCWE2AH.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 +6 -6
- package/dist/session/index.d.ts +6 -6
- package/dist/session/index.js +3 -3
- package/dist/shadow/index.cjs.map +1 -1
- package/dist/shadow/index.d.cts +5 -5
- package/dist/shadow/index.d.ts +5 -5
- package/dist/shadow/index.js +2 -2
- package/dist/{signer-YSXZT574.js → signer-HAVDLGOK.js} +5 -5
- package/dist/snapshots/index.cjs.map +1 -1
- package/dist/snapshots/index.d.cts +5 -5
- package/dist/snapshots/index.d.ts +5 -5
- package/dist/snapshots/index.js +4 -4
- package/dist/{stale-TOA36SRK.js → stale-PGTEGJDI.js} +2 -2
- package/dist/state-vault-QKQKN3H3.js +14 -0
- package/dist/state-vault-QKQKN3H3.js.map +1 -0
- package/dist/store/index.cjs.map +1 -1
- package/dist/store/index.d.cts +5 -5
- package/dist/store/index.d.ts +5 -5
- package/dist/store/index.js +2 -2
- package/dist/{strategy-4M9jo172.d.ts → strategy-Diwh5lzS.d.ts} +1 -1
- package/dist/{strategy-CLC1j79g.d.cts → strategy-nuyN8K5N.d.cts} +1 -1
- package/dist/sync/index.cjs.map +1 -1
- package/dist/sync/index.d.cts +4 -4
- package/dist/sync/index.d.ts +4 -4
- package/dist/sync/index.js +4 -4
- package/dist/team/index.cjs.map +1 -1
- package/dist/team/index.d.cts +5 -5
- package/dist/team/index.d.ts +5 -5
- package/dist/team/index.js +8 -8
- package/dist/transition-guard--t3exQHF.d.cts +165 -0
- package/dist/transition-guard-BlI9Oy5K.d.ts +165 -0
- package/dist/tx/index.cjs.map +1 -1
- package/dist/tx/index.d.cts +5 -5
- package/dist/tx/index.d.ts +5 -5
- package/dist/tx/index.js +3 -3
- package/dist/{types-CrSpRDuG.d.cts → types-BpLPqyaO.d.cts} +487 -25
- package/dist/{types-CljIHm_J.d.ts → types-Diqc2caK.d.ts} +487 -25
- package/dist/{ulid-CWfL2Vfv.d.ts → ulid-B1zNV8r9.d.ts} +1 -1
- package/dist/{ulid-CrI7PPbA.d.cts → ulid-DNiRB4Mx.d.cts} +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-DPZVFRI5.js} +182 -6
- package/dist/vault-group-DPZVFRI5.js.map +1 -0
- package/dist/{with-materialized-view-NzF71cG_.d.cts → with-materialized-view-BdH_A_r6.d.cts} +1 -1
- package/dist/{with-materialized-view-B892zYZV.d.ts → with-materialized-view-CzAgp_HJ.d.ts} +1 -1
- package/dist/{with-overlayed-view-CR6m7CHe.d.ts → with-overlayed-view-BJbqQnsR.d.ts} +1 -1
- package/dist/{with-overlayed-view-UI8qSGL4.d.cts → with-overlayed-view-C40rDPlu.d.cts} +1 -1
- package/dist/with-rollup-Bopu5UDZ.d.cts +47 -0
- package/dist/with-rollup-DrlGkxiE.d.ts +47 -0
- package/package.json +3 -3
- package/dist/chunk-D77ZQSQQ.js.map +0 -1
- package/dist/chunk-GP3SDSH2.js.map +0 -1
- package/dist/chunk-HGVSHKZW.js.map +0 -1
- package/dist/chunk-PDULVIBY.js +0 -63
- package/dist/chunk-PDULVIBY.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-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-SHX5QBCI.js.map → chunk-2U226RDC.js.map} +0 -0
- /package/dist/{chunk-7H2GEJ3O.js.map → chunk-32XVU2LT.js.map} +0 -0
- /package/dist/{chunk-XJV6OB4D.js.map → chunk-33DAO2XG.js.map} +0 -0
- /package/dist/{chunk-U5QCMH3W.js.map → chunk-45643PAU.js.map} +0 -0
- /package/dist/{chunk-BH3X5L6A.js.map → chunk-4UI5T3K7.js.map} +0 -0
- /package/dist/{chunk-2FU2FTXD.js.map → chunk-5KKNBDCT.js.map} +0 -0
- /package/dist/{chunk-CD2AVTEM.js.map → chunk-6FHCU3QO.js.map} +0 -0
- /package/dist/{chunk-NBBMMJ2H.js.map → chunk-6Q5XRLKG.js.map} +0 -0
- /package/dist/{chunk-XPIHJ34I.js.map → chunk-6XEGHIBA.js.map} +0 -0
- /package/dist/{chunk-5LIROIDM.js.map → chunk-6YEC7LLO.js.map} +0 -0
- /package/dist/{chunk-C3HYQPV4.js.map → chunk-AB7JF2KF.js.map} +0 -0
- /package/dist/{chunk-KCEHMDZF.js.map → chunk-BUBJYIZ7.js.map} +0 -0
- /package/dist/{chunk-I5IUYN7B.js.map → chunk-C2OYWD5S.js.map} +0 -0
- /package/dist/{chunk-WV7WV6JO.js.map → chunk-CMISAJAE.js.map} +0 -0
- /package/dist/{chunk-SNMJ7SB3.js.map → chunk-DKMPR76W.js.map} +0 -0
- /package/dist/{chunk-XMVHEWF6.js.map → chunk-DR5I7Q6N.js.map} +0 -0
- /package/dist/{chunk-BSZOCSDZ.js.map → chunk-FQRAYDS4.js.map} +0 -0
- /package/dist/{chunk-M476FOQ7.js.map → chunk-HOO5I3VG.js.map} +0 -0
- /package/dist/{chunk-F4G63NTZ.js.map → chunk-HWK75CYX.js.map} +0 -0
- /package/dist/{chunk-FEJDVE3Z.js.map → chunk-HZOEBM67.js.map} +0 -0
- /package/dist/{chunk-QHM6XEAH.js.map → chunk-IQ4GMEYZ.js.map} +0 -0
- /package/dist/{chunk-5AXTH4QZ.js.map → chunk-K3NYRK7U.js.map} +0 -0
- /package/dist/{chunk-3G3W65EQ.js.map → chunk-KTZ2MHQK.js.map} +0 -0
- /package/dist/{chunk-E77UKJYL.js.map → chunk-LQ3GD5LL.js.map} +0 -0
- /package/dist/{chunk-JDWE6JMX.js.map → chunk-M3H7VSRV.js.map} +0 -0
- /package/dist/{chunk-DWEBTE2W.js.map → chunk-MGB67HKX.js.map} +0 -0
- /package/dist/{chunk-AEIKD3PP.js.map → chunk-P57D4KBG.js.map} +0 -0
- /package/dist/{chunk-YYVZYTWW.js.map → chunk-PGVEL5IZ.js.map} +0 -0
- /package/dist/{chunk-UNTGHX5A.js.map → chunk-QJKZ5WUP.js.map} +0 -0
- /package/dist/{chunk-BJSLBUJ7.js.map → chunk-QPJ7Z4L3.js.map} +0 -0
- /package/dist/{chunk-NYSYPFXJ.js.map → chunk-RQFG2YSV.js.map} +0 -0
- /package/dist/{chunk-J7RWBXFY.js.map → chunk-RZWQNMMP.js.map} +0 -0
- /package/dist/{chunk-BL5GYANC.js.map → chunk-T4T5I5L6.js.map} +0 -0
- /package/dist/{chunk-ZNGPEV5J.js.map → chunk-TFAN3NFD.js.map} +0 -0
- /package/dist/{chunk-DYYYUW5D.js.map → chunk-TPOHMOGX.js.map} +0 -0
- /package/dist/{chunk-XMHUK5PN.js.map → chunk-TTS3RWL5.js.map} +0 -0
- /package/dist/{chunk-TIDXB5DF.js.map → chunk-VVDSDOVV.js.map} +0 -0
- /package/dist/{chunk-WIAOUFFB.js.map → chunk-WZCG3EZ6.js.map} +0 -0
- /package/dist/{chunk-QO6RGLLD.js.map → chunk-Y5XVB75E.js.map} +0 -0
- /package/dist/{chunk-H2MRGONI.js.map → chunk-Z3BE5BRK.js.map} +0 -0
- /package/dist/{crypto-7BN2HDWG.js.map → crypto-FNK3XPCS.js.map} +0 -0
- /package/dist/{delegation-MGH5SODX.js.map → delegation-FMXNUWE6.js.map} +0 -0
- /package/dist/{executor-3W63Y44O.js.map → executor-IZ2NVXCY.js.map} +0 -0
- /package/dist/{executor-CFFWPWBJ.js.map → executor-THSEYEJG.js.map} +0 -0
- /package/dist/{executor-VDQQOR4F.js.map → executor-WLFDUTOM.js.map} +0 -0
- /package/dist/{fanout-sidecar-FIJJ46YG.js.map → fanout-sidecar-JGHXAJO5.js.map} +0 -0
- /package/dist/{issue-TTMGHQ2J.js.map → issue-R2MWQO6K.js.map} +0 -0
- /package/dist/{ledger-LFVLHE5H.js.map → ledger-GXC2YA3A.js.map} +0 -0
- /package/dist/{noydb-36S6GQNC.js.map → noydb-RJL6FQ4B.js.map} +0 -0
- /package/dist/{public-envelope-RXZNP3V6.js.map → public-envelope-HXOFHY4N.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-3T2RZC5A.js.map} +0 -0
- /package/dist/{registry-SECUWSGY.js.map → registry-DMS7OKBM.js.map} +0 -0
- /package/dist/{registry-TGZISEWC.js.map → registry-WVXO6NH5.js.map} +0 -0
- /package/dist/{revoke-B54H2S2W.js.map → revoke-7LCWE2AH.js.map} +0 -0
- /package/dist/{signer-YSXZT574.js.map → signer-HAVDLGOK.js.map} +0 -0
- /package/dist/{stale-TOA36SRK.js.map → stale-PGTEGJDI.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, 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
|
}
|
|
@@ -4637,14 +4654,17 @@ var init_read_only_facade = __esm({
|
|
|
4637
4654
|
"use strict";
|
|
4638
4655
|
ReadOnlyVaultFacade = class {
|
|
4639
4656
|
_vault;
|
|
4640
|
-
|
|
4657
|
+
_layer;
|
|
4658
|
+
constructor(vault, layer = "read") {
|
|
4641
4659
|
this._vault = vault;
|
|
4660
|
+
this._layer = layer;
|
|
4642
4661
|
}
|
|
4643
4662
|
collection(name) {
|
|
4644
4663
|
const c = this._vault.collection(name);
|
|
4664
|
+
const layer = this._layer;
|
|
4645
4665
|
return {
|
|
4646
|
-
get: (id) => c.get(id),
|
|
4647
|
-
list: () => c.list(),
|
|
4666
|
+
get: (id) => c.get(id, { _layer: layer }),
|
|
4667
|
+
list: () => c.list({ _layer: layer }),
|
|
4648
4668
|
query: () => c.query()
|
|
4649
4669
|
};
|
|
4650
4670
|
}
|
|
@@ -4700,6 +4720,16 @@ var init_registry3 = __esm({
|
|
|
4700
4720
|
if (fromExtra) fromExtra.push(reg);
|
|
4701
4721
|
else this._bySource.set(extra, [reg]);
|
|
4702
4722
|
}
|
|
4723
|
+
for (const t of spec.triggerBy ?? []) {
|
|
4724
|
+
const fromTrigger = this._bySource.get(t.collection);
|
|
4725
|
+
if (fromTrigger) fromTrigger.push(reg);
|
|
4726
|
+
else this._bySource.set(t.collection, [reg]);
|
|
4727
|
+
}
|
|
4728
|
+
if (spec.rollup) {
|
|
4729
|
+
const fromRollup = this._bySource.get(spec.rollup.from);
|
|
4730
|
+
if (fromRollup) fromRollup.push(reg);
|
|
4731
|
+
else this._bySource.set(spec.rollup.from, [reg]);
|
|
4732
|
+
}
|
|
4703
4733
|
for (const key of outputKeys) {
|
|
4704
4734
|
const output = spec.outputs[key];
|
|
4705
4735
|
if (!output) continue;
|
|
@@ -4753,6 +4783,9 @@ var init_registry3 = __esm({
|
|
|
4753
4783
|
for (const key of Object.keys(s.spec.outputs)) {
|
|
4754
4784
|
const output = s.spec.outputs[key];
|
|
4755
4785
|
if (!output) continue;
|
|
4786
|
+
if (output.shape === "record" && output.collection === s.spec.source && output.denorm !== void 0) {
|
|
4787
|
+
continue;
|
|
4788
|
+
}
|
|
4756
4789
|
visit(output.collection);
|
|
4757
4790
|
}
|
|
4758
4791
|
}
|
|
@@ -4929,6 +4962,177 @@ var init_delegation = __esm({
|
|
|
4929
4962
|
}
|
|
4930
4963
|
});
|
|
4931
4964
|
|
|
4965
|
+
// src/federation/schema-manifest.ts
|
|
4966
|
+
function captureBlueprint(configure) {
|
|
4967
|
+
const recorded = [];
|
|
4968
|
+
const collectionStub = new Proxy(
|
|
4969
|
+
{},
|
|
4970
|
+
{
|
|
4971
|
+
get: () => () => collectionStub
|
|
4972
|
+
}
|
|
4973
|
+
);
|
|
4974
|
+
const proxy = new Proxy(
|
|
4975
|
+
{},
|
|
4976
|
+
{
|
|
4977
|
+
get: (_t, prop) => {
|
|
4978
|
+
if (prop === "collection") {
|
|
4979
|
+
return (name, opts) => {
|
|
4980
|
+
recorded.push({
|
|
4981
|
+
name,
|
|
4982
|
+
indexes: opts?.indexes ?? [],
|
|
4983
|
+
persistJsonSchema: !!opts?.persistJsonSchema
|
|
4984
|
+
});
|
|
4985
|
+
return collectionStub;
|
|
4986
|
+
};
|
|
4987
|
+
}
|
|
4988
|
+
return () => proxy;
|
|
4989
|
+
}
|
|
4990
|
+
}
|
|
4991
|
+
);
|
|
4992
|
+
configure(proxy);
|
|
4993
|
+
const sorted = [...recorded].sort((a, b) => a.name.localeCompare(b.name));
|
|
4994
|
+
const indexes = {};
|
|
4995
|
+
const persistJsonSchema = [];
|
|
4996
|
+
for (const c of sorted) {
|
|
4997
|
+
indexes[c.name] = c.indexes;
|
|
4998
|
+
if (c.persistJsonSchema) persistJsonSchema.push(c.name);
|
|
4999
|
+
}
|
|
5000
|
+
return {
|
|
5001
|
+
// `persistJsonSchema` is already name-sorted: it is populated while
|
|
5002
|
+
// iterating `sorted` (collections in name order).
|
|
5003
|
+
collections: sorted.map((c) => c.name),
|
|
5004
|
+
indexes,
|
|
5005
|
+
persistJsonSchema
|
|
5006
|
+
};
|
|
5007
|
+
}
|
|
5008
|
+
function canonical(value) {
|
|
5009
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
5010
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
5011
|
+
const obj = value;
|
|
5012
|
+
const keys = Object.keys(obj).sort();
|
|
5013
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonical(obj[k])}`).join(",")}}`;
|
|
5014
|
+
}
|
|
5015
|
+
async function fingerprintBlueprint(bp) {
|
|
5016
|
+
return sha256Hex(new TextEncoder().encode(canonical(bp)));
|
|
5017
|
+
}
|
|
5018
|
+
var init_schema_manifest = __esm({
|
|
5019
|
+
"src/federation/schema-manifest.ts"() {
|
|
5020
|
+
"use strict";
|
|
5021
|
+
init_crypto();
|
|
5022
|
+
}
|
|
5023
|
+
});
|
|
5024
|
+
|
|
5025
|
+
// src/federation/state-vault.ts
|
|
5026
|
+
var state_vault_exports = {};
|
|
5027
|
+
__export(state_vault_exports, {
|
|
5028
|
+
STATE_VAULT_NAME: () => STATE_VAULT_NAME,
|
|
5029
|
+
StateManagementVault: () => StateManagementVault
|
|
5030
|
+
});
|
|
5031
|
+
var REGISTRY, MANIFEST, EVENTS, MIGRATION_STATUS, StateManagementVault;
|
|
5032
|
+
var init_state_vault = __esm({
|
|
5033
|
+
"src/federation/state-vault.ts"() {
|
|
5034
|
+
"use strict";
|
|
5035
|
+
init_schema_manifest();
|
|
5036
|
+
init_constants();
|
|
5037
|
+
init_ulid();
|
|
5038
|
+
init_constants();
|
|
5039
|
+
REGISTRY = "vaultRegistry";
|
|
5040
|
+
MANIFEST = "schemaManifest";
|
|
5041
|
+
EVENTS = "deploymentEvents";
|
|
5042
|
+
MIGRATION_STATUS = "migrationStatus";
|
|
5043
|
+
StateManagementVault = class _StateManagementVault {
|
|
5044
|
+
constructor(registry, schemaManifest, events, migrationStatus) {
|
|
5045
|
+
this.registry = registry;
|
|
5046
|
+
this.schemaManifest = schemaManifest;
|
|
5047
|
+
this.#events = events;
|
|
5048
|
+
this.#migrationStatus = migrationStatus;
|
|
5049
|
+
}
|
|
5050
|
+
registry;
|
|
5051
|
+
schemaManifest;
|
|
5052
|
+
/**
|
|
5053
|
+
* The append-only deployment-events log is kept truly private so the raw
|
|
5054
|
+
* mutable Collection is never surfaced — events may only be written via
|
|
5055
|
+
* `appendEvent` and read via `queryEvents`. (`registry` and
|
|
5056
|
+
* `schemaManifest` are deliberately public: consumers read and write them.)
|
|
5057
|
+
*/
|
|
5058
|
+
#events;
|
|
5059
|
+
/** Per-shard fleet-migration progress (#271). Surfaced via typed methods only. */
|
|
5060
|
+
#migrationStatus;
|
|
5061
|
+
/** Idempotently open the reserved state vault and bind the control-plane collections. */
|
|
5062
|
+
static async open(db) {
|
|
5063
|
+
const vault = await db.openVault(STATE_VAULT_NAME);
|
|
5064
|
+
return new _StateManagementVault(
|
|
5065
|
+
vault.collection(REGISTRY),
|
|
5066
|
+
vault.collection(MANIFEST),
|
|
5067
|
+
vault.collection(EVENTS),
|
|
5068
|
+
vault.collection(MIGRATION_STATUS)
|
|
5069
|
+
);
|
|
5070
|
+
}
|
|
5071
|
+
/** Read one shard's migration status (or null). */
|
|
5072
|
+
async getMigrationStatus(vaultId) {
|
|
5073
|
+
return this.#migrationStatus.get(vaultId);
|
|
5074
|
+
}
|
|
5075
|
+
/** All migration-status rows (hydrates first). */
|
|
5076
|
+
async listMigrationStatus() {
|
|
5077
|
+
await this.#migrationStatus.list();
|
|
5078
|
+
return this.#migrationStatus.query().toArray();
|
|
5079
|
+
}
|
|
5080
|
+
/** Upsert one shard's migration status (keyed by vaultId). */
|
|
5081
|
+
async upsertMigrationStatus(row) {
|
|
5082
|
+
await this.#migrationStatus.put(row.vaultId, row);
|
|
5083
|
+
}
|
|
5084
|
+
/** Read-only query over the append-only deployment-events log. */
|
|
5085
|
+
queryEvents() {
|
|
5086
|
+
return this.#events.query();
|
|
5087
|
+
}
|
|
5088
|
+
/**
|
|
5089
|
+
* Append a deployment event with a fresh unique (ULID) id. This is the
|
|
5090
|
+
* only write path to the events log; no update/delete is exposed.
|
|
5091
|
+
* Callers should treat failures as non-fatal — this method does not
|
|
5092
|
+
* swallow errors, so wrap the call site in try/catch where appropriate.
|
|
5093
|
+
*/
|
|
5094
|
+
async appendEvent(event) {
|
|
5095
|
+
const ts = event.ts ?? Date.now();
|
|
5096
|
+
const id = generateULID();
|
|
5097
|
+
await this.#events.put(id, { ...event, id, ts });
|
|
5098
|
+
}
|
|
5099
|
+
/**
|
|
5100
|
+
* Ensure a manifest row exists for `(templateName, template.version)`.
|
|
5101
|
+
* Safe to call repeatedly: the `fingerprint` is a deterministic hash of
|
|
5102
|
+
* the template's declared shape (stable across calls), though each call
|
|
5103
|
+
* refreshes `recordedAt`.
|
|
5104
|
+
*/
|
|
5105
|
+
async recordManifest(templateName, template) {
|
|
5106
|
+
const bp = captureBlueprint(template.configure);
|
|
5107
|
+
const fingerprint = await fingerprintBlueprint(bp);
|
|
5108
|
+
await this.schemaManifest.put(`${templateName}:${template.version}`, {
|
|
5109
|
+
templateName,
|
|
5110
|
+
version: template.version,
|
|
5111
|
+
collections: bp.collections,
|
|
5112
|
+
indexes: bp.indexes,
|
|
5113
|
+
persistJsonSchema: bp.persistJsonSchema,
|
|
5114
|
+
fingerprint,
|
|
5115
|
+
recordedAt: Date.now()
|
|
5116
|
+
});
|
|
5117
|
+
return fingerprint;
|
|
5118
|
+
}
|
|
5119
|
+
/**
|
|
5120
|
+
* True when `template`'s current declared shape does not match the recorded
|
|
5121
|
+
* manifest for `(templateName, template.version)`. Because shards carry no
|
|
5122
|
+
* schema state independent of their template, this catches "a template's
|
|
5123
|
+
* shape changed without bumping `version`" — not independent per-shard drift.
|
|
5124
|
+
* A missing manifest is treated as drift (nothing to verify against).
|
|
5125
|
+
*/
|
|
5126
|
+
async detectDrift(templateName, template) {
|
|
5127
|
+
const row = await this.schemaManifest.get(`${templateName}:${template.version}`);
|
|
5128
|
+
if (!row) return true;
|
|
5129
|
+
const current = await fingerprintBlueprint(captureBlueprint(template.configure));
|
|
5130
|
+
return current !== row.fingerprint;
|
|
5131
|
+
}
|
|
5132
|
+
};
|
|
5133
|
+
}
|
|
5134
|
+
});
|
|
5135
|
+
|
|
4932
5136
|
// src/federation/classify-skip.ts
|
|
4933
5137
|
function classifyShardSkip(err) {
|
|
4934
5138
|
return err instanceof NoAccessError ? "no-grant" : "error";
|
|
@@ -5213,6 +5417,7 @@ var SHARD_SEPARATOR, SAFE_PARTITION_KEY, VaultGroup, ShardedCollection, ShardedQ
|
|
|
5213
5417
|
var init_vault_group = __esm({
|
|
5214
5418
|
"src/federation/vault-group.ts"() {
|
|
5215
5419
|
"use strict";
|
|
5420
|
+
init_state_vault();
|
|
5216
5421
|
init_errors();
|
|
5217
5422
|
init_constants();
|
|
5218
5423
|
init_classify_skip();
|
|
@@ -5222,12 +5427,13 @@ var init_vault_group = __esm({
|
|
|
5222
5427
|
SHARD_SEPARATOR = "--";
|
|
5223
5428
|
SAFE_PARTITION_KEY = /^[A-Za-z0-9._-]+$/;
|
|
5224
5429
|
VaultGroup = class {
|
|
5225
|
-
constructor(db, name, registry, sharding, template) {
|
|
5430
|
+
constructor(db, name, registry, sharding, template, migrateOnOpen = false) {
|
|
5226
5431
|
this.db = db;
|
|
5227
5432
|
this.name = name;
|
|
5228
5433
|
this.registry = registry;
|
|
5229
5434
|
this.sharding = sharding;
|
|
5230
5435
|
this.template = template;
|
|
5436
|
+
this.migrateOnOpen = migrateOnOpen;
|
|
5231
5437
|
if (name.includes(SHARD_SEPARATOR)) {
|
|
5232
5438
|
throw new ValidationError(
|
|
5233
5439
|
`VaultGroup name "${name}" must not contain "--" (reserved shard vault-id separator).`
|
|
@@ -5239,6 +5445,7 @@ var init_vault_group = __esm({
|
|
|
5239
5445
|
registry;
|
|
5240
5446
|
sharding;
|
|
5241
5447
|
template;
|
|
5448
|
+
migrateOnOpen;
|
|
5242
5449
|
/** @internal — set when the group is managed (no explicit registry). */
|
|
5243
5450
|
stateVault;
|
|
5244
5451
|
/** @internal */
|
|
@@ -5272,8 +5479,22 @@ var init_vault_group = __esm({
|
|
|
5272
5479
|
const rows = this.registry.query().toArray();
|
|
5273
5480
|
return rows.filter((r) => r.group === this.name);
|
|
5274
5481
|
}
|
|
5275
|
-
/**
|
|
5482
|
+
/**
|
|
5483
|
+
* Open an existing shard and apply the template. When `migrateOnOpen` is set
|
|
5484
|
+
* (#271) and the shard's registry version is behind the template, its cutover
|
|
5485
|
+
* runs inline first — so a behind shard never surfaces a stale handle.
|
|
5486
|
+
*/
|
|
5276
5487
|
async openShard(partitionKey) {
|
|
5488
|
+
if (this.migrateOnOpen) {
|
|
5489
|
+
const row = await this.registry.get(this.registryId(partitionKey));
|
|
5490
|
+
if (row && row.schemaVersion < this.template.version) {
|
|
5491
|
+
await this.migrateShard(partitionKey);
|
|
5492
|
+
}
|
|
5493
|
+
}
|
|
5494
|
+
return this._openShardRaw(partitionKey);
|
|
5495
|
+
}
|
|
5496
|
+
/** @internal — open + configure with no migrate-on-open hook (used by the migration path itself to avoid recursion). */
|
|
5497
|
+
async _openShardRaw(partitionKey) {
|
|
5277
5498
|
const vault = await this.db.openVault(this.shardVaultId(partitionKey), { create: false });
|
|
5278
5499
|
this.template.configure(vault);
|
|
5279
5500
|
return vault;
|
|
@@ -5351,6 +5572,161 @@ var init_vault_group = __esm({
|
|
|
5351
5572
|
});
|
|
5352
5573
|
return { eligible, skipped };
|
|
5353
5574
|
}
|
|
5575
|
+
/** @internal — registered push-model cross-vault derivations (#271 Insight Vault). */
|
|
5576
|
+
crossVaultDerivations = [];
|
|
5577
|
+
/**
|
|
5578
|
+
* Register a push-model cross-vault derivation — the Insight Vault pattern
|
|
5579
|
+
* (#271, Layer 4). Drive it with {@link refreshInsights}.
|
|
5580
|
+
*
|
|
5581
|
+
* For each shard, `derive(records, ctx)` runs on that shard's `source`
|
|
5582
|
+
* records and its return value is written into the analytics
|
|
5583
|
+
* (`target.vault` / `target.collection`) vault, keyed by partition key —
|
|
5584
|
+
* one summary row per shard. The derivation runs in-process under THIS
|
|
5585
|
+
* group's `Noydb` (which already holds both the shard and Insight Vault
|
|
5586
|
+
* keyrings); the shard's decrypted records are reduced to a summary that is
|
|
5587
|
+
* re-encrypted under the Insight Vault's own DEK, so no shard ciphertext
|
|
5588
|
+
* crosses a DEK boundary.
|
|
5589
|
+
*
|
|
5590
|
+
* **Zero-knowledge note:** the Insight Vault backend sees aggregated
|
|
5591
|
+
* structure (totals, counts, timestamps) drawn from many shards — a weaker
|
|
5592
|
+
* ZK profile than the per-shard vaults. Opt-in; keep summaries to aggregate
|
|
5593
|
+
* scalars (no embeddings / no raw records).
|
|
5594
|
+
*
|
|
5595
|
+
* v1 is explicit-refresh (no write-path push); call `refreshInsights()`
|
|
5596
|
+
* after a batch of writes, or on a schedule.
|
|
5597
|
+
*/
|
|
5598
|
+
withCrossVaultDerivation(spec) {
|
|
5599
|
+
this.crossVaultDerivations.push(spec);
|
|
5600
|
+
}
|
|
5601
|
+
/**
|
|
5602
|
+
* Run every registered {@link withCrossVaultDerivation}: read each eligible
|
|
5603
|
+
* shard's source records, derive a per-shard summary, and write it into the
|
|
5604
|
+
* Insight Vault keyed by partition key. Shards behind `minVersion`,
|
|
5605
|
+
* unprovisioned, or whose read errors are reported in `skippedVaults` and
|
|
5606
|
+
* are not written (a stale summary is never left behind for a failed shard).
|
|
5607
|
+
*/
|
|
5608
|
+
async refreshInsights(options = {}) {
|
|
5609
|
+
if (this.crossVaultDerivations.length === 0) return { written: 0, skippedVaults: [] };
|
|
5610
|
+
const { eligible, skipped } = await this.resolveEligible(
|
|
5611
|
+
options.minVersion !== void 0 ? { minVersion: options.minVersion } : {}
|
|
5612
|
+
);
|
|
5613
|
+
let written = 0;
|
|
5614
|
+
for (const spec of this.crossVaultDerivations) {
|
|
5615
|
+
const results = await this.db.queryAcross(
|
|
5616
|
+
eligible.map((r) => r.vaultId),
|
|
5617
|
+
async (vault) => {
|
|
5618
|
+
this.template.configure(vault);
|
|
5619
|
+
return vault.collection(spec.source).list();
|
|
5620
|
+
},
|
|
5621
|
+
{ create: false, ...options.concurrency !== void 0 ? { concurrency: options.concurrency } : {} }
|
|
5622
|
+
);
|
|
5623
|
+
const insight = await this.db.openVault(spec.target.vault);
|
|
5624
|
+
const out = insight.collection(spec.target.collection);
|
|
5625
|
+
for (let i = 0; i < eligible.length; i++) {
|
|
5626
|
+
const row = eligible[i];
|
|
5627
|
+
const res = results[i];
|
|
5628
|
+
if (!res || res.result === void 0) {
|
|
5629
|
+
skipped.push({ vaultId: row.vaultId, reason: "error", ...res?.error ? { error: res.error } : {} });
|
|
5630
|
+
continue;
|
|
5631
|
+
}
|
|
5632
|
+
const ctx = {
|
|
5633
|
+
vaultId: row.vaultId,
|
|
5634
|
+
partitionKey: row.partitionKey,
|
|
5635
|
+
schemaVersion: row.schemaVersion
|
|
5636
|
+
};
|
|
5637
|
+
const summary = spec.derive(res.result, ctx);
|
|
5638
|
+
await out.put(row.partitionKey, summary);
|
|
5639
|
+
written++;
|
|
5640
|
+
}
|
|
5641
|
+
}
|
|
5642
|
+
return { written, skippedVaults: skipped };
|
|
5643
|
+
}
|
|
5644
|
+
/** @internal — the control-plane vault for migration status; lazily opened. */
|
|
5645
|
+
async ensureStateVault() {
|
|
5646
|
+
if (!this.stateVault) this.stateVault = await StateManagementVault.open(this.db);
|
|
5647
|
+
return this.stateVault;
|
|
5648
|
+
}
|
|
5649
|
+
/**
|
|
5650
|
+
* Migrate ONE shard to the template's current version (#271 fleet runner,
|
|
5651
|
+
* per-shard step). Opens the shard (applying the template, which arms the
|
|
5652
|
+
* M12 cutover), drains schema-write detection, runs `vault.runSchemaCutover()`
|
|
5653
|
+
* (the per-vault drain-barrier-transform protocol), then advances the
|
|
5654
|
+
* registry row's `schemaVersion` and records `migration-status`. A shard
|
|
5655
|
+
* already at the template version is a no-op (`status: 'done'`, migrated 0).
|
|
5656
|
+
* Never throws on a cutover failure — it records `status: 'failed'` and
|
|
5657
|
+
* returns the row, so a fleet run continues past a bad shard.
|
|
5658
|
+
*/
|
|
5659
|
+
async migrateShard(partitionKey) {
|
|
5660
|
+
const vaultId = this.shardVaultId(partitionKey);
|
|
5661
|
+
const row = await this.registry.get(this.registryId(partitionKey));
|
|
5662
|
+
if (!row) throw new UnknownShardError(partitionKey, this.name);
|
|
5663
|
+
const target = this.template.version;
|
|
5664
|
+
const sv = await this.ensureStateVault();
|
|
5665
|
+
const base = { vaultId, group: this.name, currentVersion: row.schemaVersion, targetVersion: target };
|
|
5666
|
+
if (row.schemaVersion >= target) {
|
|
5667
|
+
const done = { ...base, status: "done", migrated: 0, finishedAt: Date.now() };
|
|
5668
|
+
await sv.upsertMigrationStatus(done);
|
|
5669
|
+
return done;
|
|
5670
|
+
}
|
|
5671
|
+
await sv.upsertMigrationStatus({ ...base, status: "running", startedAt: Date.now() });
|
|
5672
|
+
try {
|
|
5673
|
+
await sv.appendEvent({ type: "migration-started", group: this.name, vaultId, version: target });
|
|
5674
|
+
} catch {
|
|
5675
|
+
}
|
|
5676
|
+
try {
|
|
5677
|
+
const vault = await this._openShardRaw(partitionKey);
|
|
5678
|
+
await vault._drainPendingSchemaWrites();
|
|
5679
|
+
const { migrated } = await vault.runSchemaCutover();
|
|
5680
|
+
await this.registry.put(this.registryId(partitionKey), { ...row, schemaVersion: target });
|
|
5681
|
+
const done = { ...base, currentVersion: target, status: "done", migrated, finishedAt: Date.now() };
|
|
5682
|
+
await sv.upsertMigrationStatus(done);
|
|
5683
|
+
try {
|
|
5684
|
+
await sv.appendEvent({ type: "migration-completed", group: this.name, vaultId, version: target });
|
|
5685
|
+
} catch {
|
|
5686
|
+
}
|
|
5687
|
+
return done;
|
|
5688
|
+
} catch (err) {
|
|
5689
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
5690
|
+
const failed = { ...base, status: "failed", error, finishedAt: Date.now() };
|
|
5691
|
+
await sv.upsertMigrationStatus(failed);
|
|
5692
|
+
try {
|
|
5693
|
+
await sv.appendEvent({ type: "migration-failed", group: this.name, vaultId, version: target, detail: error });
|
|
5694
|
+
} catch {
|
|
5695
|
+
}
|
|
5696
|
+
return failed;
|
|
5697
|
+
}
|
|
5698
|
+
}
|
|
5699
|
+
/**
|
|
5700
|
+
* Active batch runner (#271): migrate every shard behind the template version
|
|
5701
|
+
* to it, in controlled batches. **Resumable + crash-safe** — shards already at
|
|
5702
|
+
* the target are skipped (the registry version is the source of truth), so a
|
|
5703
|
+
* re-run after a crash only picks up the unfinished + previously-failed shards.
|
|
5704
|
+
*
|
|
5705
|
+
* - `cohort` — restrict to these partition keys (the staged / canary rollout:
|
|
5706
|
+
* migrate a small cohort, verify the Insight Vault, then run the rest).
|
|
5707
|
+
* - `batchSize` — max shards migrated concurrently per batch (back-pressure).
|
|
5708
|
+
* Default 4. Batches run sequentially; shards within a batch run in parallel.
|
|
5709
|
+
*/
|
|
5710
|
+
async migrateFleet(options = {}) {
|
|
5711
|
+
const target = this.template.version;
|
|
5712
|
+
const rows = await this.allRows();
|
|
5713
|
+
const cohort = options.cohort;
|
|
5714
|
+
const todo = rows.filter(
|
|
5715
|
+
(r) => r.schemaVersion < target && (cohort === void 0 || cohort.includes(r.partitionKey))
|
|
5716
|
+
);
|
|
5717
|
+
const batchSize = Math.max(1, options.batchSize ?? 4);
|
|
5718
|
+
const migrated = [];
|
|
5719
|
+
const failed = [];
|
|
5720
|
+
for (let i = 0; i < todo.length; i += batchSize) {
|
|
5721
|
+
const batch = todo.slice(i, i + batchSize);
|
|
5722
|
+
const settled = await Promise.all(batch.map((r) => this.migrateShard(r.partitionKey)));
|
|
5723
|
+
for (const res of settled) {
|
|
5724
|
+
if (res.status === "done") migrated.push(res.vaultId);
|
|
5725
|
+
else failed.push({ vaultId: res.vaultId, error: res.error ?? "unknown" });
|
|
5726
|
+
}
|
|
5727
|
+
}
|
|
5728
|
+
return { target, migrated, failed };
|
|
5729
|
+
}
|
|
5354
5730
|
};
|
|
5355
5731
|
ShardedCollection = class {
|
|
5356
5732
|
constructor(group, collectionName) {
|
|
@@ -5558,159 +5934,6 @@ var init_vault_group = __esm({
|
|
|
5558
5934
|
}
|
|
5559
5935
|
});
|
|
5560
5936
|
|
|
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
5937
|
// src/index.ts
|
|
5715
5938
|
var src_exports = {};
|
|
5716
5939
|
__export(src_exports, {
|
|
@@ -5773,6 +5996,7 @@ __export(src_exports, {
|
|
|
5773
5996
|
GroupedQuery: () => GroupedQuery,
|
|
5774
5997
|
GroupedQueryN: () => GroupedQueryN,
|
|
5775
5998
|
INDEXED_STORE_POLICY: () => INDEXED_STORE_POLICY,
|
|
5999
|
+
IllegalTransitionError: () => IllegalTransitionError,
|
|
5776
6000
|
ImportCapabilityError: () => ImportCapabilityError,
|
|
5777
6001
|
IndexRequiredError: () => IndexRequiredError,
|
|
5778
6002
|
IndexWriteFailureError: () => IndexWriteFailureError,
|
|
@@ -5785,6 +6009,8 @@ __export(src_exports, {
|
|
|
5785
6009
|
LEDGER_DELTAS_COLLECTION: () => LEDGER_DELTAS_COLLECTION,
|
|
5786
6010
|
LedgerContentionError: () => LedgerContentionError,
|
|
5787
6011
|
LedgerStore: () => LedgerStore,
|
|
6012
|
+
LinkEndpointError: () => LinkEndpointError,
|
|
6013
|
+
LinkIntegrityError: () => LinkIntegrityError,
|
|
5788
6014
|
LocaleNotSpecifiedError: () => LocaleNotSpecifiedError,
|
|
5789
6015
|
Lru: () => Lru,
|
|
5790
6016
|
MAGIC_LINK_CONTENT_INFO_PREFIX: () => MAGIC_LINK_CONTENT_INFO_PREFIX,
|
|
@@ -5916,6 +6142,7 @@ __export(src_exports, {
|
|
|
5916
6142
|
canonicalJson: () => canonicalJson,
|
|
5917
6143
|
checkGate: () => checkGate,
|
|
5918
6144
|
clearDevUnlock: () => clearDevUnlock,
|
|
6145
|
+
compileSequenceFormat: () => compileSequenceFormat,
|
|
5919
6146
|
computePatch: () => computePatch,
|
|
5920
6147
|
coordinatedCutover: () => coordinatedCutover,
|
|
5921
6148
|
count: () => count,
|
|
@@ -5978,11 +6205,13 @@ __export(src_exports, {
|
|
|
5978
6205
|
isDictKeyDescriptor: () => isDictKeyDescriptor,
|
|
5979
6206
|
isDiscriminant: () => isDiscriminant,
|
|
5980
6207
|
isI18nTextDescriptor: () => isI18nTextDescriptor,
|
|
6208
|
+
isLinkCollectionName: () => isLinkCollectionName,
|
|
5981
6209
|
isMagicLinkGrantExpired: () => isMagicLinkGrantExpired,
|
|
5982
6210
|
isMoneyDescriptor: () => isMoneyDescriptor,
|
|
5983
6211
|
isMoneyString: () => isMoneyString,
|
|
5984
6212
|
isPreCompressed: () => isPreCompressed,
|
|
5985
6213
|
isPublicEnvelope: () => isPublicEnvelope,
|
|
6214
|
+
isRefArray: () => isRefArray,
|
|
5986
6215
|
isSessionAlive: () => isSessionAlive,
|
|
5987
6216
|
isStaticDictDescriptor: () => isStaticDictDescriptor,
|
|
5988
6217
|
isULID: () => isULID,
|
|
@@ -6036,6 +6265,7 @@ __export(src_exports, {
|
|
|
6036
6265
|
recoverUser: () => recoverUser,
|
|
6037
6266
|
reduceRecords: () => reduceRecords,
|
|
6038
6267
|
ref: () => ref,
|
|
6268
|
+
refArray: () => refArray,
|
|
6039
6269
|
removeAuthenticator: () => removeAuthenticator,
|
|
6040
6270
|
resetBrotliSupportCache: () => resetBrotliSupportCache,
|
|
6041
6271
|
resetJoinWarnings: () => resetJoinWarnings,
|
|
@@ -6063,6 +6293,7 @@ __export(src_exports, {
|
|
|
6063
6293
|
sha256Hex: () => sha256Hex3,
|
|
6064
6294
|
staticDict: () => staticDict,
|
|
6065
6295
|
sum: () => sum,
|
|
6296
|
+
transitionGuard: () => transitionGuard,
|
|
6066
6297
|
unwrapDeksFromBlob: () => unwrapDeksFromBlob,
|
|
6067
6298
|
unwrapDeksFromPaperEntry: () => unwrapDeksFromPaperEntry,
|
|
6068
6299
|
unwrapDeksFromShamirEntry: () => unwrapDeksFromShamirEntry,
|
|
@@ -6086,6 +6317,7 @@ __export(src_exports, {
|
|
|
6086
6317
|
withMetrics: () => withMetrics,
|
|
6087
6318
|
withOverlayedView: () => withOverlayedView,
|
|
6088
6319
|
withRetry: () => withRetry,
|
|
6320
|
+
withRollup: () => withRollup,
|
|
6089
6321
|
wrapBundleStore: () => wrapBundleStore,
|
|
6090
6322
|
wrapStore: () => wrapStore,
|
|
6091
6323
|
writeMagicLinkGrant: () => writeMagicLinkGrant,
|
|
@@ -8454,6 +8686,38 @@ init_crypto();
|
|
|
8454
8686
|
init_errors();
|
|
8455
8687
|
var SEQUENCE_COLLECTION = "_sequences";
|
|
8456
8688
|
var MAX_NEXT_ATTEMPTS = 16;
|
|
8689
|
+
var SEQ_FORMAT_TOKEN = /\{([^{}]*)\}/g;
|
|
8690
|
+
var SEQ_PAD_TOKEN = /^seq:0(\d+)$/;
|
|
8691
|
+
var SEQ_PARTITION_TOKEN = /^partition\.(\d+)$/;
|
|
8692
|
+
function compileSequenceFormat(format, series, partition) {
|
|
8693
|
+
const parts = partition ?? [];
|
|
8694
|
+
for (const m of format.matchAll(SEQ_FORMAT_TOKEN)) {
|
|
8695
|
+
const token = m[1] ?? "";
|
|
8696
|
+
if (token === "seq") continue;
|
|
8697
|
+
if (SEQ_PAD_TOKEN.test(token)) continue;
|
|
8698
|
+
const partMatch = SEQ_PARTITION_TOKEN.exec(token);
|
|
8699
|
+
if (partMatch) {
|
|
8700
|
+
const idx = Number(partMatch[1]);
|
|
8701
|
+
if (idx >= parts.length) {
|
|
8702
|
+
throw new ValidationError(
|
|
8703
|
+
`sequence("${series}"): format token "{${token}}" references partition index ${idx}, but only ${parts.length} partition component(s) were supplied.`
|
|
8704
|
+
);
|
|
8705
|
+
}
|
|
8706
|
+
continue;
|
|
8707
|
+
}
|
|
8708
|
+
throw new ValidationError(
|
|
8709
|
+
`sequence("${series}"): format contains unknown token "{${token}}". Accepted tokens: {seq}, {seq:0N}, {partition.i}.`
|
|
8710
|
+
);
|
|
8711
|
+
}
|
|
8712
|
+
return (serial) => format.replace(SEQ_FORMAT_TOKEN, (full, token) => {
|
|
8713
|
+
if (token === "seq") return String(serial);
|
|
8714
|
+
const padMatch = SEQ_PAD_TOKEN.exec(token);
|
|
8715
|
+
if (padMatch) return String(serial).padStart(Number(padMatch[1]), "0");
|
|
8716
|
+
const partMatch = SEQ_PARTITION_TOKEN.exec(token);
|
|
8717
|
+
if (partMatch) return String(parts[Number(partMatch[1])]);
|
|
8718
|
+
return full;
|
|
8719
|
+
});
|
|
8720
|
+
}
|
|
8457
8721
|
function resolveSequenceKey(series, opts) {
|
|
8458
8722
|
const partition = opts?.partition;
|
|
8459
8723
|
if (!partition || partition.length === 0) return series;
|
|
@@ -9476,15 +9740,15 @@ function isEquivalent(a, b) {
|
|
|
9476
9740
|
|
|
9477
9741
|
// src/history/history.ts
|
|
9478
9742
|
var HISTORY_COLLECTION = "_history";
|
|
9479
|
-
function matchesPrefix(id, collection,
|
|
9480
|
-
if (
|
|
9481
|
-
return id.startsWith(`${collection}:${
|
|
9743
|
+
function matchesPrefix(id, collection, recordId4) {
|
|
9744
|
+
if (recordId4) {
|
|
9745
|
+
return id.startsWith(`${collection}:${recordId4}:`);
|
|
9482
9746
|
}
|
|
9483
9747
|
return id.startsWith(`${collection}:`);
|
|
9484
9748
|
}
|
|
9485
|
-
async function getHistory(adapter, vault, collection,
|
|
9749
|
+
async function getHistory(adapter, vault, collection, recordId4, options) {
|
|
9486
9750
|
const allIds = await adapter.list(vault, HISTORY_COLLECTION);
|
|
9487
|
-
const matchingIds = allIds.filter((id) => matchesPrefix(id, collection,
|
|
9751
|
+
const matchingIds = allIds.filter((id) => matchesPrefix(id, collection, recordId4)).sort().reverse();
|
|
9488
9752
|
const entries = [];
|
|
9489
9753
|
for (const id of matchingIds) {
|
|
9490
9754
|
const envelope = await adapter.get(vault, HISTORY_COLLECTION, id);
|
|
@@ -9737,6 +10001,9 @@ init_ledger();
|
|
|
9737
10001
|
|
|
9738
10002
|
// src/refs.ts
|
|
9739
10003
|
init_errors();
|
|
10004
|
+
function isRefArray(desc) {
|
|
10005
|
+
return desc.isArray === true;
|
|
10006
|
+
}
|
|
9740
10007
|
var RefIntegrityError = class extends NoydbError {
|
|
9741
10008
|
collection;
|
|
9742
10009
|
id;
|
|
@@ -9773,6 +10040,17 @@ function ref(target, mode = "strict") {
|
|
|
9773
10040
|
}
|
|
9774
10041
|
return { target, mode };
|
|
9775
10042
|
}
|
|
10043
|
+
function refArray(target, mode = "strict") {
|
|
10044
|
+
if (target.includes("/")) {
|
|
10045
|
+
throw new RefScopeError(target);
|
|
10046
|
+
}
|
|
10047
|
+
if (!target || target.startsWith("_")) {
|
|
10048
|
+
throw new Error(
|
|
10049
|
+
`refArray(): target collection name must be non-empty and cannot start with '_' (reserved for internal collections). Got "${target}".`
|
|
10050
|
+
);
|
|
10051
|
+
}
|
|
10052
|
+
return { target, mode, isArray: true };
|
|
10053
|
+
}
|
|
9776
10054
|
var RefRegistry = class {
|
|
9777
10055
|
outbound = /* @__PURE__ */ new Map();
|
|
9778
10056
|
inbound = /* @__PURE__ */ new Map();
|
|
@@ -9797,7 +10075,7 @@ var RefRegistry = class {
|
|
|
9797
10075
|
for (const k of existingKeys) {
|
|
9798
10076
|
const a = existing[k];
|
|
9799
10077
|
const b = refs[k];
|
|
9800
|
-
if (!a || !b || a.target !== b.target || a.mode !== b.mode) {
|
|
10078
|
+
if (!a || !b || a.target !== b.target || a.mode !== b.mode || a.isArray !== b.isArray) {
|
|
9801
10079
|
throw new Error(
|
|
9802
10080
|
`RefRegistry: conflicting ref declarations for collection "${collection}" field "${k}"`
|
|
9803
10081
|
);
|
|
@@ -9808,31 +10086,169 @@ var RefRegistry = class {
|
|
|
9808
10086
|
this.outbound.set(collection, { ...refs });
|
|
9809
10087
|
for (const [field, desc] of Object.entries(refs)) {
|
|
9810
10088
|
const list = this.inbound.get(desc.target) ?? [];
|
|
9811
|
-
list.push({ collection, field, mode: desc.mode });
|
|
10089
|
+
list.push({ collection, field, mode: desc.mode, ...desc.isArray ? { isArray: true } : {} });
|
|
9812
10090
|
this.inbound.set(desc.target, list);
|
|
9813
10091
|
}
|
|
9814
10092
|
}
|
|
9815
|
-
/** Get the outbound refs declared by a collection (or `{}` if none). */
|
|
9816
|
-
getOutbound(collection) {
|
|
9817
|
-
return this.outbound.get(collection) ?? {};
|
|
10093
|
+
/** Get the outbound refs declared by a collection (or `{}` if none). */
|
|
10094
|
+
getOutbound(collection) {
|
|
10095
|
+
return this.outbound.get(collection) ?? {};
|
|
10096
|
+
}
|
|
10097
|
+
/** Get the inbound refs that target a given collection (or `[]`). */
|
|
10098
|
+
getInbound(target) {
|
|
10099
|
+
return this.inbound.get(target) ?? [];
|
|
10100
|
+
}
|
|
10101
|
+
/**
|
|
10102
|
+
* Iterate every (collection → refs) pair that has at least one
|
|
10103
|
+
* declared reference. Used by `checkIntegrity` to walk the full
|
|
10104
|
+
* universe of outbound refs without needing to track collection
|
|
10105
|
+
* names elsewhere.
|
|
10106
|
+
*/
|
|
10107
|
+
entries() {
|
|
10108
|
+
return [...this.outbound.entries()];
|
|
10109
|
+
}
|
|
10110
|
+
/** Clear the registry. Test-only escape hatch; never called from production code. */
|
|
10111
|
+
clear() {
|
|
10112
|
+
this.outbound.clear();
|
|
10113
|
+
this.inbound.clear();
|
|
10114
|
+
}
|
|
10115
|
+
};
|
|
10116
|
+
|
|
10117
|
+
// src/links/link-set.ts
|
|
10118
|
+
init_types();
|
|
10119
|
+
init_crypto();
|
|
10120
|
+
init_errors();
|
|
10121
|
+
var LINK_COLLECTION_PREFIX = "_links_";
|
|
10122
|
+
function linkCollectionName(name) {
|
|
10123
|
+
return `${LINK_COLLECTION_PREFIX}${name}`;
|
|
10124
|
+
}
|
|
10125
|
+
function isLinkCollectionName(name) {
|
|
10126
|
+
return name.startsWith(LINK_COLLECTION_PREFIX);
|
|
10127
|
+
}
|
|
10128
|
+
function linkRowKey(aId, bId) {
|
|
10129
|
+
return `${encodeURIComponent(aId)}|${encodeURIComponent(bId)}`;
|
|
10130
|
+
}
|
|
10131
|
+
var LinkSet = class {
|
|
10132
|
+
constructor(adapter, vault, name, spec, encrypted, getDEK, actor, emitter, endpointExists) {
|
|
10133
|
+
this.adapter = adapter;
|
|
10134
|
+
this.vault = vault;
|
|
10135
|
+
this.name = name;
|
|
10136
|
+
this.spec = spec;
|
|
10137
|
+
this.encrypted = encrypted;
|
|
10138
|
+
this.getDEK = getDEK;
|
|
10139
|
+
this.actor = actor;
|
|
10140
|
+
this.emitter = emitter;
|
|
10141
|
+
this.endpointExists = endpointExists;
|
|
10142
|
+
this.collName = linkCollectionName(name);
|
|
10143
|
+
}
|
|
10144
|
+
adapter;
|
|
10145
|
+
vault;
|
|
10146
|
+
name;
|
|
10147
|
+
spec;
|
|
10148
|
+
encrypted;
|
|
10149
|
+
getDEK;
|
|
10150
|
+
actor;
|
|
10151
|
+
emitter;
|
|
10152
|
+
endpointExists;
|
|
10153
|
+
collName;
|
|
10154
|
+
dekPromise = null;
|
|
10155
|
+
dek() {
|
|
10156
|
+
if (!this.dekPromise) this.dekPromise = this.getDEK(this.collName);
|
|
10157
|
+
return this.dekPromise;
|
|
10158
|
+
}
|
|
10159
|
+
async encryptEntry(entry, version) {
|
|
10160
|
+
const json = JSON.stringify(entry);
|
|
10161
|
+
const base = { _noydb: NOYDB_FORMAT_VERSION, _v: version, _ts: (/* @__PURE__ */ new Date()).toISOString(), _by: this.actor };
|
|
10162
|
+
if (!this.encrypted) return { ...base, _iv: "", _data: json };
|
|
10163
|
+
const { iv, data } = await encrypt(json, await this.dek());
|
|
10164
|
+
return { ...base, _iv: iv, _data: data };
|
|
10165
|
+
}
|
|
10166
|
+
async decryptEntry(env) {
|
|
10167
|
+
const json = this.encrypted ? await decrypt(env._iv, env._data, await this.dek()) : env._data;
|
|
10168
|
+
return JSON.parse(json);
|
|
10169
|
+
}
|
|
10170
|
+
async connect(aId, bId, meta) {
|
|
10171
|
+
if (!await this.endpointExists(this.spec.a, aId)) {
|
|
10172
|
+
throw new LinkEndpointError(this.name, this.spec.a, aId);
|
|
10173
|
+
}
|
|
10174
|
+
if (!await this.endpointExists(this.spec.b, bId)) {
|
|
10175
|
+
throw new LinkEndpointError(this.name, this.spec.b, bId);
|
|
10176
|
+
}
|
|
10177
|
+
const key = linkRowKey(aId, bId);
|
|
10178
|
+
const entry = meta !== void 0 ? { a: aId, b: bId, meta } : { a: aId, b: bId };
|
|
10179
|
+
const existing = await this.adapter.get(this.vault, this.collName, key);
|
|
10180
|
+
const env = await this.encryptEntry(entry, (existing?._v ?? 0) + 1);
|
|
10181
|
+
await this.adapter.put(this.vault, this.collName, key, env, existing?._v);
|
|
10182
|
+
this.emitter.emit("change", { vault: this.vault, collection: this.collName, id: key, action: "put" });
|
|
10183
|
+
}
|
|
10184
|
+
async disconnect(aId, bId) {
|
|
10185
|
+
const key = linkRowKey(aId, bId);
|
|
10186
|
+
const existing = await this.adapter.get(this.vault, this.collName, key);
|
|
10187
|
+
if (!existing) return;
|
|
10188
|
+
await this.adapter.delete(this.vault, this.collName, key);
|
|
10189
|
+
this.emitter.emit("change", { vault: this.vault, collection: this.collName, id: key, action: "delete" });
|
|
10190
|
+
}
|
|
10191
|
+
async has(aId, bId) {
|
|
10192
|
+
return await this.adapter.get(this.vault, this.collName, linkRowKey(aId, bId)) !== null;
|
|
10193
|
+
}
|
|
10194
|
+
async of(id) {
|
|
10195
|
+
const rows = await this.list();
|
|
10196
|
+
return rows.filter((r) => r.a === id || r.b === id);
|
|
10197
|
+
}
|
|
10198
|
+
async list() {
|
|
10199
|
+
const keys = await this.adapter.list(this.vault, this.collName);
|
|
10200
|
+
const out = [];
|
|
10201
|
+
for (const key of keys) {
|
|
10202
|
+
const env = await this.adapter.get(this.vault, this.collName, key);
|
|
10203
|
+
if (!env) continue;
|
|
10204
|
+
const e = await this.decryptEntry(env);
|
|
10205
|
+
out.push(e.meta !== void 0 ? { a: e.a, b: e.b, meta: e.meta } : { a: e.a, b: e.b });
|
|
10206
|
+
}
|
|
10207
|
+
return out;
|
|
10208
|
+
}
|
|
10209
|
+
// ── Vault-internal cascade helpers ──────────────────────────────────
|
|
10210
|
+
/** @internal — rows where the deleted endpoint id matches the relevant slot. */
|
|
10211
|
+
async _rowsTouchingEndpoint(collection, id) {
|
|
10212
|
+
const rows = await this.list();
|
|
10213
|
+
return rows.filter(
|
|
10214
|
+
(r) => this.spec.a === collection && r.a === id || this.spec.b === collection && r.b === id
|
|
10215
|
+
);
|
|
9818
10216
|
}
|
|
9819
|
-
/**
|
|
9820
|
-
|
|
9821
|
-
return this.
|
|
10217
|
+
/** @internal — the storage collection name (for tx pre-image capture). */
|
|
10218
|
+
get _collectionName() {
|
|
10219
|
+
return this.collName;
|
|
9822
10220
|
}
|
|
9823
|
-
|
|
9824
|
-
|
|
9825
|
-
|
|
9826
|
-
|
|
9827
|
-
|
|
9828
|
-
|
|
9829
|
-
|
|
9830
|
-
|
|
10221
|
+
};
|
|
10222
|
+
var LinkEndpointError = class extends NoydbError {
|
|
10223
|
+
link;
|
|
10224
|
+
endpoint;
|
|
10225
|
+
missingId;
|
|
10226
|
+
constructor(link, endpoint, missingId) {
|
|
10227
|
+
super(
|
|
10228
|
+
"LINK_ENDPOINT",
|
|
10229
|
+
`link("${link}").connect: endpoint "${endpoint}" has no record "${missingId}".`
|
|
10230
|
+
);
|
|
10231
|
+
this.name = "LinkEndpointError";
|
|
10232
|
+
this.link = link;
|
|
10233
|
+
this.endpoint = endpoint;
|
|
10234
|
+
this.missingId = missingId;
|
|
9831
10235
|
}
|
|
9832
|
-
|
|
9833
|
-
|
|
9834
|
-
|
|
9835
|
-
|
|
10236
|
+
};
|
|
10237
|
+
var LinkIntegrityError = class extends NoydbError {
|
|
10238
|
+
link;
|
|
10239
|
+
endpoint;
|
|
10240
|
+
id;
|
|
10241
|
+
count;
|
|
10242
|
+
constructor(link, endpoint, id, count2) {
|
|
10243
|
+
super(
|
|
10244
|
+
"LINK_INTEGRITY",
|
|
10245
|
+
`Cannot delete "${endpoint}"/"${id}": ${count2} link(s) in "${link}" still reference it (onDelete: 'strict').`
|
|
10246
|
+
);
|
|
10247
|
+
this.name = "LinkIntegrityError";
|
|
10248
|
+
this.link = link;
|
|
10249
|
+
this.endpoint = endpoint;
|
|
10250
|
+
this.id = id;
|
|
10251
|
+
this.count = count2;
|
|
9836
10252
|
}
|
|
9837
10253
|
};
|
|
9838
10254
|
|
|
@@ -12840,6 +13256,21 @@ function formatCurrency(decimal, currency, scale, locale) {
|
|
|
12840
13256
|
});
|
|
12841
13257
|
return fmt.format(decimal);
|
|
12842
13258
|
}
|
|
13259
|
+
function moneyScaledValue(stored, desc) {
|
|
13260
|
+
let raw;
|
|
13261
|
+
if (desc.mode === "fixed") {
|
|
13262
|
+
raw = stored;
|
|
13263
|
+
} else {
|
|
13264
|
+
if (!isMoneyValueObject(stored)) return null;
|
|
13265
|
+
raw = stored.amount;
|
|
13266
|
+
}
|
|
13267
|
+
if (typeof raw !== "string" && typeof raw !== "number") return null;
|
|
13268
|
+
try {
|
|
13269
|
+
return BigInt(String(raw));
|
|
13270
|
+
} catch {
|
|
13271
|
+
return null;
|
|
13272
|
+
}
|
|
13273
|
+
}
|
|
12843
13274
|
function decodeValue(stored, desc) {
|
|
12844
13275
|
let currency;
|
|
12845
13276
|
let scaledIntString;
|
|
@@ -13923,7 +14354,7 @@ function executePlanWithSource(source, plan, joinContext) {
|
|
|
13923
14354
|
result = remainingClauses.length === 0 ? [...candidates] : filterRecords(candidates, remainingClauses, fnViewDecoder(source));
|
|
13924
14355
|
}
|
|
13925
14356
|
if (plan.orderBy.length > 0) {
|
|
13926
|
-
result = sortRecords(result, plan.orderBy);
|
|
14357
|
+
result = sortRecords(result, plan.orderBy, source.moneyFields);
|
|
13927
14358
|
}
|
|
13928
14359
|
if (plan.offset > 0) {
|
|
13929
14360
|
result = result.slice(plan.offset);
|
|
@@ -14080,17 +14511,25 @@ function applyCrossJoin(leftRel, clause, rightSource) {
|
|
|
14080
14511
|
}
|
|
14081
14512
|
return expanded;
|
|
14082
14513
|
}
|
|
14083
|
-
function sortRecords(records, orderBy) {
|
|
14514
|
+
function sortRecords(records, orderBy, moneyFields) {
|
|
14084
14515
|
return [...records].sort((a, b) => {
|
|
14085
14516
|
for (const { field, direction } of orderBy) {
|
|
14086
14517
|
const av = readField(a, field);
|
|
14087
14518
|
const bv = readField(b, field);
|
|
14088
|
-
const
|
|
14519
|
+
const desc = moneyFields?.[field];
|
|
14520
|
+
const cmp = desc ? compareMoney(av, bv, desc) : compareValues(av, bv);
|
|
14089
14521
|
if (cmp !== 0) return direction === "asc" ? cmp : -cmp;
|
|
14090
14522
|
}
|
|
14091
14523
|
return 0;
|
|
14092
14524
|
});
|
|
14093
14525
|
}
|
|
14526
|
+
function compareMoney(a, b, desc) {
|
|
14527
|
+
const av = moneyScaledValue(a, desc);
|
|
14528
|
+
const bv = moneyScaledValue(b, desc);
|
|
14529
|
+
if (av === null) return bv === null ? 0 : 1;
|
|
14530
|
+
if (bv === null) return -1;
|
|
14531
|
+
return av < bv ? -1 : av > bv ? 1 : 0;
|
|
14532
|
+
}
|
|
14094
14533
|
function readField(record, field) {
|
|
14095
14534
|
if (record === null || record === void 0) return void 0;
|
|
14096
14535
|
if (!field.includes(".")) {
|
|
@@ -14879,8 +15318,8 @@ function coerceRefKey2(value) {
|
|
|
14879
15318
|
|
|
14880
15319
|
// src/indexing/persisted-indexes.ts
|
|
14881
15320
|
var IDX_PREFIX = "_idx/";
|
|
14882
|
-
function encodeIdxId(field,
|
|
14883
|
-
return `${IDX_PREFIX}${field}/${
|
|
15321
|
+
function encodeIdxId(field, recordId4) {
|
|
15322
|
+
return `${IDX_PREFIX}${field}/${recordId4}`;
|
|
14884
15323
|
}
|
|
14885
15324
|
function decodeIdxId(id) {
|
|
14886
15325
|
if (!id.startsWith(IDX_PREFIX)) return null;
|
|
@@ -14888,9 +15327,9 @@ function decodeIdxId(id) {
|
|
|
14888
15327
|
const firstSlash = rest.indexOf("/");
|
|
14889
15328
|
if (firstSlash <= 0) return null;
|
|
14890
15329
|
const field = rest.slice(0, firstSlash);
|
|
14891
|
-
const
|
|
14892
|
-
if (
|
|
14893
|
-
return { field, recordId:
|
|
15330
|
+
const recordId4 = rest.slice(firstSlash + 1);
|
|
15331
|
+
if (recordId4.length === 0) return null;
|
|
15332
|
+
return { field, recordId: recordId4 };
|
|
14894
15333
|
}
|
|
14895
15334
|
|
|
14896
15335
|
// src/indexing/lazy-builder.ts
|
|
@@ -15780,6 +16219,15 @@ async function resolveStaleOnRead(accessor, outputCollection, id) {
|
|
|
15780
16219
|
}
|
|
15781
16220
|
|
|
15782
16221
|
// src/collection.ts
|
|
16222
|
+
function selfWriteFieldEqual(a, b) {
|
|
16223
|
+
if (a === b) return true;
|
|
16224
|
+
if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
|
|
16225
|
+
try {
|
|
16226
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
16227
|
+
} catch {
|
|
16228
|
+
return false;
|
|
16229
|
+
}
|
|
16230
|
+
}
|
|
15783
16231
|
var fallbackWarned = /* @__PURE__ */ new Set();
|
|
15784
16232
|
function warnOnceFallback(adapterName) {
|
|
15785
16233
|
if (fallbackWarned.has(adapterName)) return;
|
|
@@ -16714,6 +17162,111 @@ var Collection = class {
|
|
|
16714
17162
|
* output (carries `_derivedFrom`) — defensive guard against missed
|
|
16715
17163
|
* cycle detection.
|
|
16716
17164
|
*/
|
|
17165
|
+
/**
|
|
17166
|
+
* @internal #376 — the RAW stored record (canonical-money form, i18n maps
|
|
17167
|
+
* intact), WITHOUT the locale resolution `get()` applies. Used as the
|
|
17168
|
+
* patch base for self-write reverse-denorm so writing back never clobbers
|
|
17169
|
+
* an i18n map or re-quantizes money incorrectly. Returns null for
|
|
17170
|
+
* missing / tombstoned records.
|
|
17171
|
+
*/
|
|
17172
|
+
async _getStoredRecord(id) {
|
|
17173
|
+
let raw;
|
|
17174
|
+
if (this.lazy && this.lru) {
|
|
17175
|
+
const cached = this.lru.get(id);
|
|
17176
|
+
if (cached) raw = cached.record;
|
|
17177
|
+
else {
|
|
17178
|
+
const env = await this.adapter.get(this.vault, this.name, id);
|
|
17179
|
+
if (!env || isTombstone(env, this.encrypted)) return null;
|
|
17180
|
+
raw = await this.decryptRecord(env, { id });
|
|
17181
|
+
if (raw === null) return null;
|
|
17182
|
+
this.lru.set(id, { record: raw, version: env._v }, estimateRecordBytes(raw));
|
|
17183
|
+
}
|
|
17184
|
+
} else {
|
|
17185
|
+
await this.ensureHydrated();
|
|
17186
|
+
raw = this.cache.get(id)?.record ?? null;
|
|
17187
|
+
}
|
|
17188
|
+
if (raw === null) return null;
|
|
17189
|
+
return canonicalizeStoredMoney(raw, this.moneyFields);
|
|
17190
|
+
}
|
|
17191
|
+
/**
|
|
17192
|
+
* @internal #376 — ids of records whose top-level `field` equals `value`.
|
|
17193
|
+
* Uses the FK index when the field is indexed (O(matches)); otherwise a
|
|
17194
|
+
* linear scan (O(N) — fine for small child sets; index the FK to scale).
|
|
17195
|
+
*/
|
|
17196
|
+
async _findMatchingIds(field, value) {
|
|
17197
|
+
const hit = this.getIndexes()?.lookupEqual(field, value);
|
|
17198
|
+
if (hit) return [...hit];
|
|
17199
|
+
const target = String(value);
|
|
17200
|
+
const matches = (rec) => {
|
|
17201
|
+
const fv = rec[field];
|
|
17202
|
+
return (typeof fv === "string" || typeof fv === "number") && String(fv) === target;
|
|
17203
|
+
};
|
|
17204
|
+
if (!this.lazy) {
|
|
17205
|
+
await this.ensureHydrated();
|
|
17206
|
+
const out2 = [];
|
|
17207
|
+
for (const [rid, e] of this.cache) {
|
|
17208
|
+
if (matches(e.record)) out2.push(rid);
|
|
17209
|
+
}
|
|
17210
|
+
return out2;
|
|
17211
|
+
}
|
|
17212
|
+
const ids = await this.adapter.list(this.vault, this.name);
|
|
17213
|
+
const out = [];
|
|
17214
|
+
for (const rid of ids) {
|
|
17215
|
+
const raw = await this._getStoredRecord(rid);
|
|
17216
|
+
if (raw !== null && matches(raw)) out.push(rid);
|
|
17217
|
+
}
|
|
17218
|
+
return out;
|
|
17219
|
+
}
|
|
17220
|
+
/**
|
|
17221
|
+
* @internal #376 slice 2 — recompute a rollup aggregate onto the parent.
|
|
17222
|
+
* Gathers every child of `parentId`, runs `compute`, and patches only the
|
|
17223
|
+
* rollup `field` onto the parent's raw stored record (value-equality
|
|
17224
|
+
* guarded). No-op when the parent record does not exist.
|
|
17225
|
+
*/
|
|
17226
|
+
async recomputeRollup(spec, parentId) {
|
|
17227
|
+
if (this.derivationSource === void 0 || spec.rollup === void 0) return;
|
|
17228
|
+
const { from, key, field, compute } = spec.rollup;
|
|
17229
|
+
const into = spec.source;
|
|
17230
|
+
const intoColl = this.derivationSource.getCollection(into);
|
|
17231
|
+
const base = await intoColl._getStoredRecord(parentId);
|
|
17232
|
+
if (base === null) return;
|
|
17233
|
+
const fromColl = this.derivationSource.getCollection(from);
|
|
17234
|
+
const childIds = await fromColl._findMatchingIds(key, parentId);
|
|
17235
|
+
const children = [];
|
|
17236
|
+
for (const cid of childIds) {
|
|
17237
|
+
const c = await fromColl.get(cid);
|
|
17238
|
+
if (c !== null && c !== void 0) children.push(c);
|
|
17239
|
+
}
|
|
17240
|
+
const newValue = compute(children);
|
|
17241
|
+
if (selfWriteFieldEqual(base[field], newValue)) return;
|
|
17242
|
+
const patched = { ...base, [field]: newValue };
|
|
17243
|
+
const txCtx = this.derivationSource.getActiveTxContext();
|
|
17244
|
+
if (txCtx !== null) {
|
|
17245
|
+
const prior = await this.adapter.get(this.vault, into, parentId);
|
|
17246
|
+
txCtx._executed.push({
|
|
17247
|
+
op: { type: "put", vaultName: this.vault, collectionName: into, id: parentId },
|
|
17248
|
+
priorEnvelope: prior
|
|
17249
|
+
});
|
|
17250
|
+
}
|
|
17251
|
+
await intoColl.put(parentId, patched);
|
|
17252
|
+
}
|
|
17253
|
+
/**
|
|
17254
|
+
* @internal #376 slice 2 — fire any rollups for which THIS collection is the
|
|
17255
|
+
* child `from`, recomputing the affected parent after a child delete. Called
|
|
17256
|
+
* from the delete path with the just-removed record's key value. Other
|
|
17257
|
+
* derivation kinds do not react to deletes (unchanged).
|
|
17258
|
+
*/
|
|
17259
|
+
async dispatchRollupsOnDelete(deleted) {
|
|
17260
|
+
if (this.derivationSource === void 0) return;
|
|
17261
|
+
const registry = this.derivationSource.registry();
|
|
17262
|
+
const rec = deleted;
|
|
17263
|
+
for (const { spec } of registry.strategiesForSource(this.name)) {
|
|
17264
|
+
if (!spec.rollup || spec.rollup.from !== this.name) continue;
|
|
17265
|
+
const kv = rec[spec.rollup.key];
|
|
17266
|
+
if (typeof kv !== "string" && typeof kv !== "number") continue;
|
|
17267
|
+
await this.recomputeRollup(spec, String(kv));
|
|
17268
|
+
}
|
|
17269
|
+
}
|
|
16717
17270
|
async dispatchDerivations(id, record, version) {
|
|
16718
17271
|
if (this.derivationSource === void 0) return;
|
|
16719
17272
|
const incoming = canonicalizeStoredMoney(record, this.moneyFields);
|
|
@@ -16724,29 +17277,60 @@ var Collection = class {
|
|
|
16724
17277
|
let DerivationExecutor2 = null;
|
|
16725
17278
|
for (const { spec, strategyHash } of strategies) {
|
|
16726
17279
|
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 };
|
|
17280
|
+
if (spec.rollup) {
|
|
17281
|
+
if (mode !== "eager") continue;
|
|
17282
|
+
let parentId;
|
|
17283
|
+
if (this.name === spec.rollup.from) {
|
|
17284
|
+
const kv = incoming[spec.rollup.key];
|
|
17285
|
+
parentId = typeof kv === "string" || typeof kv === "number" ? String(kv) : null;
|
|
16735
17286
|
} else {
|
|
16736
|
-
|
|
16737
|
-
|
|
16738
|
-
|
|
16739
|
-
|
|
17287
|
+
parentId = id;
|
|
17288
|
+
}
|
|
17289
|
+
if (parentId !== null) await this.recomputeRollup(spec, parentId);
|
|
17290
|
+
continue;
|
|
17291
|
+
}
|
|
17292
|
+
const isSource = spec.source === this.name;
|
|
17293
|
+
const isSibling = !isSource && (spec.sources?.includes(this.name) ?? false);
|
|
17294
|
+
const trigger = !isSource && !isSibling ? spec.triggerBy?.find((t) => t.collection === this.name) : void 0;
|
|
17295
|
+
const runs = [];
|
|
17296
|
+
if (isSource) {
|
|
17297
|
+
runs.push({ input: { ...incoming, id }, base: incoming, runId: id, version });
|
|
17298
|
+
} else if (isSibling) {
|
|
17299
|
+
const p = await this.derivationSource.getCollection(spec.source).get(id);
|
|
17300
|
+
if (p !== null && p !== void 0) {
|
|
17301
|
+
const raw = await this.derivationSource.getCollection(spec.source)._getStoredRecord(id);
|
|
17302
|
+
runs.push({ input: { ...p, id }, base: raw ?? p, runId: id, version: 0 });
|
|
17303
|
+
}
|
|
17304
|
+
} else if (trigger) {
|
|
17305
|
+
const srcColl = this.derivationSource.getCollection(spec.source);
|
|
17306
|
+
const ids = await srcColl._findMatchingIds(trigger.on, id);
|
|
17307
|
+
if (trigger.maxFanout !== void 0 && ids.length > trigger.maxFanout) {
|
|
17308
|
+
throw new DerivationCapExceededError(`triggerBy ${this.name}\u2192${spec.source}`, ids.length, trigger.maxFanout);
|
|
16740
17309
|
}
|
|
17310
|
+
for (const sid of ids) {
|
|
17311
|
+
const raw = await srcColl._getStoredRecord(sid);
|
|
17312
|
+
if (raw === null) continue;
|
|
17313
|
+
runs.push({ input: { ...raw, id: sid }, base: raw, runId: sid, version: 0 });
|
|
17314
|
+
}
|
|
17315
|
+
}
|
|
17316
|
+
if (runs.length === 0) continue;
|
|
17317
|
+
if (mode !== "eager") {
|
|
17318
|
+
for (const run of runs) await markStale(registry, spec, run.runId);
|
|
17319
|
+
continue;
|
|
17320
|
+
}
|
|
17321
|
+
if (DerivationExecutor2 === null) {
|
|
17322
|
+
({ DerivationExecutor: DerivationExecutor2 } = await Promise.resolve().then(() => (init_executor2(), executor_exports2)));
|
|
17323
|
+
}
|
|
17324
|
+
for (const run of runs) {
|
|
16741
17325
|
const ctx = { vault: this.derivationSource.getReadOnlyFacade() };
|
|
16742
|
-
const result = await DerivationExecutor2.run(spec,
|
|
17326
|
+
const result = await DerivationExecutor2.run(spec, run.input, run.version, strategyHash, ctx);
|
|
16743
17327
|
for (const key of Object.keys(spec.outputs)) {
|
|
16744
17328
|
const out = result.outputs[key];
|
|
16745
17329
|
if (!out) continue;
|
|
16746
17330
|
if (out.kind === "failed") {
|
|
16747
17331
|
const err = out.error;
|
|
16748
17332
|
if (spec.strict) throw err;
|
|
16749
|
-
console.warn(`[derivation] output "${key}" for source "${spec.source}" id="${
|
|
17333
|
+
console.warn(`[derivation] output "${key}" for source "${spec.source}" id="${run.runId}" failed:`, err);
|
|
16750
17334
|
continue;
|
|
16751
17335
|
}
|
|
16752
17336
|
const outSpec = spec.outputs[key];
|
|
@@ -16759,7 +17343,7 @@ var Collection = class {
|
|
|
16759
17343
|
this.adapter,
|
|
16760
17344
|
this.vault,
|
|
16761
17345
|
spec.source,
|
|
16762
|
-
|
|
17346
|
+
run.runId,
|
|
16763
17347
|
key
|
|
16764
17348
|
);
|
|
16765
17349
|
const prevKeys = new Set(prior?.keys ?? []);
|
|
@@ -16786,7 +17370,7 @@ var Collection = class {
|
|
|
16786
17370
|
}
|
|
16787
17371
|
await saveFanoutSidecar2(this.adapter, this.vault, {
|
|
16788
17372
|
source: spec.source,
|
|
16789
|
-
sourceId:
|
|
17373
|
+
sourceId: run.runId,
|
|
16790
17374
|
outputKey: key,
|
|
16791
17375
|
outputCollection: outSpec.collection,
|
|
16792
17376
|
keys: newKeysList
|
|
@@ -16794,25 +17378,44 @@ var Collection = class {
|
|
|
16794
17378
|
continue;
|
|
16795
17379
|
}
|
|
16796
17380
|
if (out.skipped === true) {
|
|
16797
|
-
await outputCollection._internalDelete(
|
|
17381
|
+
await outputCollection._internalDelete(run.runId, txCtx);
|
|
17382
|
+
continue;
|
|
17383
|
+
}
|
|
17384
|
+
if (outSpec.shape === "record" && outSpec.denorm !== void 0 && outSpec.collection === spec.source) {
|
|
17385
|
+
const value = out.value;
|
|
17386
|
+
const patched = { ...run.base };
|
|
17387
|
+
let changed = false;
|
|
17388
|
+
for (const f of outSpec.denorm) {
|
|
17389
|
+
if (!selfWriteFieldEqual(run.base[f], value[f])) {
|
|
17390
|
+
patched[f] = value[f];
|
|
17391
|
+
changed = true;
|
|
17392
|
+
}
|
|
17393
|
+
}
|
|
17394
|
+
if (!changed) continue;
|
|
17395
|
+
if (txCtx !== null) {
|
|
17396
|
+
const prior = await this.adapter.get(this.vault, outSpec.collection, run.runId);
|
|
17397
|
+
txCtx._executed.push({
|
|
17398
|
+
op: { type: "put", vaultName: this.vault, collectionName: outSpec.collection, id: run.runId },
|
|
17399
|
+
priorEnvelope: prior
|
|
17400
|
+
});
|
|
17401
|
+
}
|
|
17402
|
+
await outputCollection.put(run.runId, patched);
|
|
16798
17403
|
continue;
|
|
16799
17404
|
}
|
|
16800
17405
|
if (txCtx !== null) {
|
|
16801
|
-
const prior = await this.adapter.get(this.vault, outSpec.collection,
|
|
17406
|
+
const prior = await this.adapter.get(this.vault, outSpec.collection, run.runId);
|
|
16802
17407
|
txCtx._executed.push({
|
|
16803
17408
|
op: {
|
|
16804
17409
|
type: "put",
|
|
16805
17410
|
vaultName: this.vault,
|
|
16806
17411
|
collectionName: outSpec.collection,
|
|
16807
|
-
id
|
|
17412
|
+
id: run.runId
|
|
16808
17413
|
},
|
|
16809
17414
|
priorEnvelope: prior
|
|
16810
17415
|
});
|
|
16811
17416
|
}
|
|
16812
|
-
await outputCollection.put(
|
|
17417
|
+
await outputCollection.put(run.runId, out.value);
|
|
16813
17418
|
}
|
|
16814
|
-
} else {
|
|
16815
|
-
await markStale(registry, spec, id);
|
|
16816
17419
|
}
|
|
16817
17420
|
}
|
|
16818
17421
|
}
|
|
@@ -17029,6 +17632,7 @@ var Collection = class {
|
|
|
17029
17632
|
if (!internal) {
|
|
17030
17633
|
await this.dispatchMaterializedViewsOnDelete(id);
|
|
17031
17634
|
await this.dispatchArrayDerivationsOnDelete(id);
|
|
17635
|
+
if (existing) await this.dispatchRollupsOnDelete(existing.record);
|
|
17032
17636
|
}
|
|
17033
17637
|
}
|
|
17034
17638
|
/**
|
|
@@ -17883,12 +18487,12 @@ var Collection = class {
|
|
|
17883
18487
|
}
|
|
17884
18488
|
}
|
|
17885
18489
|
persisted.clear();
|
|
17886
|
-
for (const
|
|
17887
|
-
const envelope = await this.adapter.get(this.vault, this.name,
|
|
18490
|
+
for (const recordId4 of canonicalIds) {
|
|
18491
|
+
const envelope = await this.adapter.get(this.vault, this.name, recordId4);
|
|
17888
18492
|
if (!envelope) continue;
|
|
17889
18493
|
const record = await this.decryptRecord(envelope, { skipValidation: true });
|
|
17890
18494
|
if (record === null) continue;
|
|
17891
|
-
await this.maintainPersistedIndexesOnPut(
|
|
18495
|
+
await this.maintainPersistedIndexesOnPut(recordId4, record, null, envelope._v);
|
|
17892
18496
|
}
|
|
17893
18497
|
this.persistedIndexesLoaded = true;
|
|
17894
18498
|
}
|
|
@@ -18083,14 +18687,15 @@ var Collection = class {
|
|
|
18083
18687
|
(d) => isStaticDictDescriptor(d) && d.displayLocale !== void 0
|
|
18084
18688
|
);
|
|
18085
18689
|
if (!locale && !hasStaticDisplay) return result;
|
|
18690
|
+
const layer = localeOpts?._layer ?? "read";
|
|
18086
18691
|
if (locale && hasI18n && this.i18nFields) {
|
|
18087
|
-
result = this.i18nStrategy.applyI18nLocale(result, this.i18nFields, locale, localeOpts?.fallback);
|
|
18692
|
+
result = this.i18nStrategy.applyI18nLocale(result, this.i18nFields, locale, localeOpts?.fallback, layer);
|
|
18088
18693
|
}
|
|
18089
18694
|
if (hasDict && this.dictKeyFields && this.dictLabelResolver && locale !== "raw") {
|
|
18090
18695
|
const withLabels = { ...result };
|
|
18091
18696
|
const resolver = this.dictLabelResolver;
|
|
18092
18697
|
for (const [field, desc] of Object.entries(this.dictKeyFields)) {
|
|
18093
|
-
const policy = desc.onMissing ? resolvePolicy(desc.onMissing,
|
|
18698
|
+
const policy = desc.onMissing ? resolvePolicy(desc.onMissing, layer) : "null";
|
|
18094
18699
|
const fallback = policy === "substitute" ? localeOpts?.fallback ?? desc.substitute : localeOpts?.fallback;
|
|
18095
18700
|
const effLocale = locale ?? (isStaticDictDescriptor(desc) ? desc.displayLocale : void 0);
|
|
18096
18701
|
const resolveKey = async (key) => {
|
|
@@ -19124,8 +19729,8 @@ var DeferredNumberingStore = class {
|
|
|
19124
19729
|
}
|
|
19125
19730
|
await this.adapter.put(this.vault, collection, id, env, expectedVersion);
|
|
19126
19731
|
}
|
|
19127
|
-
pendingId(series,
|
|
19128
|
-
return `${series}::${
|
|
19732
|
+
pendingId(series, recordId4) {
|
|
19733
|
+
return `${series}::${recordId4}`;
|
|
19129
19734
|
}
|
|
19130
19735
|
/** Current last-assigned serial for a series (0 if none). */
|
|
19131
19736
|
async peek(series) {
|
|
@@ -19139,16 +19744,16 @@ var DeferredNumberingStore = class {
|
|
|
19139
19744
|
* at the next pass (the record's `field` is the durable source of truth —
|
|
19140
19745
|
* `assigned` is an in-process convenience that a crash may drop).
|
|
19141
19746
|
*/
|
|
19142
|
-
async enqueue(series,
|
|
19747
|
+
async enqueue(series, recordId4) {
|
|
19143
19748
|
const cfg = this.configs.get(series);
|
|
19144
19749
|
if (!cfg) throw new NumberingUncertaintyError(series);
|
|
19145
19750
|
if (typeof this.adapter.getStoreTime !== "function") throw new NumberingUncertaintyError(series);
|
|
19146
19751
|
const st = await this.adapter.getStoreTime();
|
|
19147
|
-
const id = this.pendingId(series,
|
|
19752
|
+
const id = this.pendingId(series, recordId4);
|
|
19148
19753
|
const { env } = await this.readJson(NUMBERING_PENDING_COLLECTION, id);
|
|
19149
19754
|
const entry = {
|
|
19150
19755
|
series,
|
|
19151
|
-
recordId:
|
|
19756
|
+
recordId: recordId4,
|
|
19152
19757
|
collection: cfg.collection,
|
|
19153
19758
|
field: cfg.field,
|
|
19154
19759
|
storeEarliest: st.earliest,
|
|
@@ -19505,13 +20110,13 @@ async function runCompaction(ctx, options = {}) {
|
|
|
19505
20110
|
collectionsWithPolicy += 1;
|
|
19506
20111
|
byCollection[collectionName] = { records: 0, evicted: 0 };
|
|
19507
20112
|
const ids = await ctx.listRecords(collectionName);
|
|
19508
|
-
for (const
|
|
20113
|
+
for (const recordId4 of ids) {
|
|
19509
20114
|
if (evicted >= maxEvictions) break outer;
|
|
19510
|
-
const record = await ctx.getRecord(collectionName,
|
|
20115
|
+
const record = await ctx.getRecord(collectionName, recordId4).catch(() => null);
|
|
19511
20116
|
if (record === null) continue;
|
|
19512
20117
|
records += 1;
|
|
19513
20118
|
byCollection[collectionName].records += 1;
|
|
19514
|
-
const slots = await ctx.listSlots(collectionName,
|
|
20119
|
+
const slots = await ctx.listSlots(collectionName, recordId4).catch(() => []);
|
|
19515
20120
|
for (const slot of slots) {
|
|
19516
20121
|
if (evicted >= maxEvictions) break outer;
|
|
19517
20122
|
const policy = config[slot.name];
|
|
@@ -19523,11 +20128,11 @@ async function runCompaction(ctx, options = {}) {
|
|
|
19523
20128
|
continue;
|
|
19524
20129
|
}
|
|
19525
20130
|
if (!dryRun) {
|
|
19526
|
-
await ctx.deleteSlot(collectionName,
|
|
20131
|
+
await ctx.deleteSlot(collectionName, recordId4, slot.name);
|
|
19527
20132
|
await writeAuditEntry(ctx, {
|
|
19528
|
-
id: generateEvictionId(collectionName,
|
|
20133
|
+
id: generateEvictionId(collectionName, recordId4, slot.name),
|
|
19529
20134
|
collection: collectionName,
|
|
19530
|
-
recordId:
|
|
20135
|
+
recordId: recordId4,
|
|
19531
20136
|
slotName: slot.name,
|
|
19532
20137
|
blobHash: slot.eTag,
|
|
19533
20138
|
reason,
|
|
@@ -19594,11 +20199,11 @@ function evaluatePolicy(policy, record, slot, now) {
|
|
|
19594
20199
|
if (predicateTriggered) return "predicate";
|
|
19595
20200
|
return null;
|
|
19596
20201
|
}
|
|
19597
|
-
function generateEvictionId(collection,
|
|
20202
|
+
function generateEvictionId(collection, recordId4, slotName) {
|
|
19598
20203
|
const rand = globalThis.crypto.getRandomValues(new Uint8Array(8));
|
|
19599
20204
|
let suffix = "";
|
|
19600
20205
|
for (const b of rand) suffix += b.toString(16).padStart(2, "0");
|
|
19601
|
-
return `${collection}__${
|
|
20206
|
+
return `${collection}__${recordId4}__${slotName}__${suffix}`;
|
|
19602
20207
|
}
|
|
19603
20208
|
async function writeAuditEntry(ctx, entry) {
|
|
19604
20209
|
const json = JSON.stringify(entry);
|
|
@@ -19649,7 +20254,7 @@ async function deriveMagicLinkContentKey(serverSecret, token, vault) {
|
|
|
19649
20254
|
["encrypt", "decrypt"]
|
|
19650
20255
|
);
|
|
19651
20256
|
}
|
|
19652
|
-
async function writeMagicLinkGrant(store, vault, grantor, contentKey, grantKek,
|
|
20257
|
+
async function writeMagicLinkGrant(store, vault, grantor, contentKey, grantKek, recordId4, opts) {
|
|
19653
20258
|
const collectionName = opts.collection ?? null;
|
|
19654
20259
|
const sourceKey = collectionName ? dekKey(collectionName, opts.tier) : `__any#${opts.tier}`;
|
|
19655
20260
|
const sourceDek = grantor.deks.get(sourceKey);
|
|
@@ -19662,7 +20267,7 @@ async function writeMagicLinkGrant(store, vault, grantor, contentKey, grantKek,
|
|
|
19662
20267
|
const until = typeof opts.until === "string" ? opts.until : opts.until.toISOString();
|
|
19663
20268
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
19664
20269
|
const payload = {
|
|
19665
|
-
id:
|
|
20270
|
+
id: recordId4,
|
|
19666
20271
|
toUser: opts.toUser,
|
|
19667
20272
|
fromUser: grantor.userId,
|
|
19668
20273
|
tier: opts.tier,
|
|
@@ -19682,11 +20287,11 @@ async function writeMagicLinkGrant(store, vault, grantor, contentKey, grantKek,
|
|
|
19682
20287
|
_data: data,
|
|
19683
20288
|
_by: grantor.userId
|
|
19684
20289
|
};
|
|
19685
|
-
await store.put(vault, MAGIC_LINK_GRANTS_COLLECTION,
|
|
19686
|
-
return { recordId:
|
|
20290
|
+
await store.put(vault, MAGIC_LINK_GRANTS_COLLECTION, recordId4, envelope);
|
|
20291
|
+
return { recordId: recordId4, payload };
|
|
19687
20292
|
}
|
|
19688
|
-
async function readMagicLinkGrantRecord(store, vault, contentKey,
|
|
19689
|
-
const env = await store.get(vault, MAGIC_LINK_GRANTS_COLLECTION,
|
|
20293
|
+
async function readMagicLinkGrantRecord(store, vault, contentKey, recordId4) {
|
|
20294
|
+
const env = await store.get(vault, MAGIC_LINK_GRANTS_COLLECTION, recordId4);
|
|
19690
20295
|
if (!env) return null;
|
|
19691
20296
|
try {
|
|
19692
20297
|
const json = await decrypt(env._iv, env._data, contentKey);
|
|
@@ -20342,13 +20947,17 @@ var Vault = class {
|
|
|
20342
20947
|
*/
|
|
20343
20948
|
overlayedViewRegistry = null;
|
|
20344
20949
|
/**
|
|
20345
|
-
* Cached read-only
|
|
20346
|
-
* and to derivation callbacks via `derive(source, ctx)`.
|
|
20347
|
-
*
|
|
20950
|
+
* Cached read-only facades handed to guard callbacks via `ctx.vault`
|
|
20951
|
+
* and to derivation callbacks via `derive(source, ctx)`. Split by
|
|
20952
|
+
* resolution layer (#285): the guard facade reads at `layer:'guard'`,
|
|
20953
|
+
* the derivation facade at `layer:'derivation'`, so i18nText / dictKey
|
|
20954
|
+
* fields resolve under that layer's `onMissing` policy. Allocated
|
|
20955
|
+
* eagerly inside `_initGuards()` / `_initDerivations()` so read
|
|
20348
20956
|
* accessors stay synchronous (callers in `tx/transaction.ts` rely on
|
|
20349
|
-
* that).
|
|
20957
|
+
* that). Each stays `null` for vaults without that subsystem.
|
|
20350
20958
|
*/
|
|
20351
|
-
|
|
20959
|
+
guardFacade = null;
|
|
20960
|
+
derivationFacade = null;
|
|
20352
20961
|
getDEK;
|
|
20353
20962
|
/**
|
|
20354
20963
|
* Per-principal user envelope API.
|
|
@@ -20510,6 +21119,10 @@ var Vault = class {
|
|
|
20510
21119
|
i18nFieldRegistry = /* @__PURE__ */ new Map();
|
|
20511
21120
|
/** Cache of DictionaryHandle instances, one per dictionary name. */
|
|
20512
21121
|
dictionaryCache = /* @__PURE__ */ new Map();
|
|
21122
|
+
/** Registered link specs (#377-B), keyed by link name; set by `vault.link()`. */
|
|
21123
|
+
linkRegistry = /* @__PURE__ */ new Map();
|
|
21124
|
+
/** Cache of LinkSet handles, one per link name. */
|
|
21125
|
+
linkSetCache = /* @__PURE__ */ new Map();
|
|
20513
21126
|
/** — subscribers for cross-tier access events. */
|
|
20514
21127
|
crossTierSubs = /* @__PURE__ */ new Set();
|
|
20515
21128
|
/** — currently-active elevation, or null. One per vault. */
|
|
@@ -20626,6 +21239,9 @@ var Vault = class {
|
|
|
20626
21239
|
if (collectionName === SEQUENCE_COLLECTION) {
|
|
20627
21240
|
throw new ReservedCollectionNameError(collectionName);
|
|
20628
21241
|
}
|
|
21242
|
+
if (isLinkCollectionName(collectionName)) {
|
|
21243
|
+
throw new ReservedCollectionNameError(collectionName);
|
|
21244
|
+
}
|
|
20629
21245
|
let coll = this.collectionCache.get(collectionName);
|
|
20630
21246
|
if (coll && options?.moneyFields) {
|
|
20631
21247
|
coll._applyMoneyFields(options.moneyFields);
|
|
@@ -20697,6 +21313,7 @@ var Vault = class {
|
|
|
20697
21313
|
}));
|
|
20698
21314
|
schemaUpdateGate = new SchemaUpdateGate(work);
|
|
20699
21315
|
}
|
|
21316
|
+
const effectiveHistoryConfig = options?.historyConfig ?? this.historyConfig;
|
|
20700
21317
|
const collOpts = {
|
|
20701
21318
|
adapter: this.adapter,
|
|
20702
21319
|
vault: this.name,
|
|
@@ -20712,7 +21329,7 @@ var Vault = class {
|
|
|
20712
21329
|
schemaFence: this.schemaFence,
|
|
20713
21330
|
getDEK: this.getDEK,
|
|
20714
21331
|
onDirty: this.onDirty,
|
|
20715
|
-
historyConfig:
|
|
21332
|
+
historyConfig: effectiveHistoryConfig,
|
|
20716
21333
|
// thread the vault-wide blob strategy into every
|
|
20717
21334
|
// collection. `undefined` is intentionally preserved so the
|
|
20718
21335
|
// Collection constructor uses its NO_BLOBS default.
|
|
@@ -20723,7 +21340,11 @@ var Vault = class {
|
|
|
20723
21340
|
historyStrategy: this.historyStrategy,
|
|
20724
21341
|
i18nStrategy: this.i18nStrategy,
|
|
20725
21342
|
syncStrategy: this.syncStrategy,
|
|
20726
|
-
ledger
|
|
21343
|
+
// Per-collection ledger opt-out (#361): when this collection sets
|
|
21344
|
+
// `historyConfig.ledger: false`, withhold the ledger reference so all
|
|
21345
|
+
// four `if (this.ledger)` append sites in Collection no-op. The chain
|
|
21346
|
+
// stays valid — it simply never receives this collection's entries.
|
|
21347
|
+
ledger: effectiveHistoryConfig.ledger === false ? void 0 : this.getLedgerOrNull() ?? void 0,
|
|
20727
21348
|
refEnforcer: this,
|
|
20728
21349
|
joinResolver: this,
|
|
20729
21350
|
defaultLocale: this.locale,
|
|
@@ -21084,6 +21705,68 @@ var Vault = class {
|
|
|
21084
21705
|
}
|
|
21085
21706
|
return handle;
|
|
21086
21707
|
}
|
|
21708
|
+
/**
|
|
21709
|
+
* Declare a managed many-to-many link set (#377-B). Registers a
|
|
21710
|
+
* `_links_<name>` junction between two endpoint collections; access its
|
|
21711
|
+
* rows via `vault.links(name)`. Idempotent for an identical re-declaration;
|
|
21712
|
+
* a conflicting one throws. See {@link links}.
|
|
21713
|
+
*
|
|
21714
|
+
* ```ts
|
|
21715
|
+
* vault.link('saleLineLinks', { a: ref('saleLines'), b: ref('purchaseLines'), onDelete: 'cascade' })
|
|
21716
|
+
* ```
|
|
21717
|
+
*
|
|
21718
|
+
* `a` / `b` accept either a collection name or a `ref(target)` descriptor
|
|
21719
|
+
* (only its `target` is used — links manage their own integrity). `onDelete`
|
|
21720
|
+
* governs what happens to link rows when an endpoint record is deleted
|
|
21721
|
+
* (`'cascade'` default, `'strict'`, `'warn'`).
|
|
21722
|
+
*/
|
|
21723
|
+
link(name, spec) {
|
|
21724
|
+
const a = typeof spec.a === "string" ? spec.a : spec.a.target;
|
|
21725
|
+
const b = typeof spec.b === "string" ? spec.b : spec.b.target;
|
|
21726
|
+
for (const [slot, target] of [["a", a], ["b", b]]) {
|
|
21727
|
+
if (!target || target.startsWith("_") || target.includes("/")) {
|
|
21728
|
+
throw new ValidationError(
|
|
21729
|
+
`vault.link("${name}"): endpoint "${slot}" must be a simple collection name, got "${target}".`
|
|
21730
|
+
);
|
|
21731
|
+
}
|
|
21732
|
+
}
|
|
21733
|
+
const resolved = { a, b, ...spec.onDelete ? { onDelete: spec.onDelete } : {} };
|
|
21734
|
+
const existing = this.linkRegistry.get(name);
|
|
21735
|
+
if (existing) {
|
|
21736
|
+
if (existing.a !== resolved.a || existing.b !== resolved.b || (existing.onDelete ?? "cascade") !== (resolved.onDelete ?? "cascade")) {
|
|
21737
|
+
throw new ValidationError(`vault.link("${name}"): conflicting re-declaration.`);
|
|
21738
|
+
}
|
|
21739
|
+
return;
|
|
21740
|
+
}
|
|
21741
|
+
this.linkRegistry.set(name, resolved);
|
|
21742
|
+
}
|
|
21743
|
+
/**
|
|
21744
|
+
* Access a declared link set (#377-B). Throws if `name` was not first
|
|
21745
|
+
* declared via {@link link}. Returns a cached {@link LinkSetHandle}:
|
|
21746
|
+
* `connect(a, b, meta?)`, `disconnect(a, b)`, `has(a, b)`, `of(id)`, `list()`.
|
|
21747
|
+
*/
|
|
21748
|
+
links(name) {
|
|
21749
|
+
let handle = this.linkSetCache.get(name);
|
|
21750
|
+
if (!handle) {
|
|
21751
|
+
const spec = this.linkRegistry.get(name);
|
|
21752
|
+
if (!spec) {
|
|
21753
|
+
throw new ValidationError(`vault.links("${name}"): not declared. Call vault.link("${name}", { a, b }) first.`);
|
|
21754
|
+
}
|
|
21755
|
+
handle = new LinkSet(
|
|
21756
|
+
this.adapter,
|
|
21757
|
+
this.name,
|
|
21758
|
+
name,
|
|
21759
|
+
spec,
|
|
21760
|
+
this.encrypted,
|
|
21761
|
+
this.getDEK,
|
|
21762
|
+
this.keyring.userId,
|
|
21763
|
+
this.emitter,
|
|
21764
|
+
async (collection, id) => await this.collection(collection).get(id) !== null
|
|
21765
|
+
);
|
|
21766
|
+
this.linkSetCache.set(name, handle);
|
|
21767
|
+
}
|
|
21768
|
+
return handle;
|
|
21769
|
+
}
|
|
21087
21770
|
/**
|
|
21088
21771
|
* Build a `JoinableSource` for a dictKey field, for use in dict joins
|
|
21089
21772
|
*. Returns a source whose snapshot contains `{ key, ...labels }`
|
|
@@ -21243,65 +21926,16 @@ var Vault = class {
|
|
|
21243
21926
|
});
|
|
21244
21927
|
}
|
|
21245
21928
|
}
|
|
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
21929
|
sequence(series, opts) {
|
|
21301
21930
|
if (series.includes("\0")) {
|
|
21302
21931
|
throw new ValidationError(`sequence("${series}"): series name must not contain a null byte (\\x00).`);
|
|
21303
21932
|
}
|
|
21304
21933
|
if (this.numberingConfigs.has(series)) {
|
|
21934
|
+
if (opts?.format !== void 0) {
|
|
21935
|
+
throw new ValidationError(
|
|
21936
|
+
`sequence("${series}") is a deferred-numbering series; the format option applies to CAS sequences only.`
|
|
21937
|
+
);
|
|
21938
|
+
}
|
|
21305
21939
|
const eng = this.deferred();
|
|
21306
21940
|
return {
|
|
21307
21941
|
next: async (nextOpts) => {
|
|
@@ -21325,7 +21959,17 @@ var Vault = class {
|
|
|
21325
21959
|
actor: this.keyring.userId
|
|
21326
21960
|
});
|
|
21327
21961
|
}
|
|
21328
|
-
|
|
21962
|
+
const handle = this.sequenceStore.handle(resolveSequenceKey(series, opts));
|
|
21963
|
+
if (opts?.format === void 0) return handle;
|
|
21964
|
+
const render = compileSequenceFormat(opts.format, series, opts.partition);
|
|
21965
|
+
return {
|
|
21966
|
+
next: async (nextOpts) => {
|
|
21967
|
+
const serial = await handle.next(nextOpts);
|
|
21968
|
+
return { serial, formatted: render(serial) };
|
|
21969
|
+
},
|
|
21970
|
+
peek: () => handle.peek(),
|
|
21971
|
+
seedTo: (n) => handle.seedTo(n)
|
|
21972
|
+
};
|
|
21329
21973
|
}
|
|
21330
21974
|
/** @internal — lazily build the deferred-numbering engine with a cache-coherent stamp. */
|
|
21331
21975
|
deferred() {
|
|
@@ -21340,11 +21984,11 @@ var Vault = class {
|
|
|
21340
21984
|
// Stamp THROUGH the Collection layer so cache/indexes/MVs stay coherent —
|
|
21341
21985
|
// `this.collection(name)` returns the shared cached instance, so a
|
|
21342
21986
|
// subsequent user `collection.get(id)` sees the assigned serial.
|
|
21343
|
-
stamp: async (collection,
|
|
21987
|
+
stamp: async (collection, recordId4, field, serial) => {
|
|
21344
21988
|
const coll = this.collection(collection);
|
|
21345
|
-
const rec = await coll.get(
|
|
21989
|
+
const rec = await coll.get(recordId4);
|
|
21346
21990
|
if (!rec) return false;
|
|
21347
|
-
await coll.put(
|
|
21991
|
+
await coll.put(recordId4, { ...rec, [field]: serial });
|
|
21348
21992
|
return true;
|
|
21349
21993
|
}
|
|
21350
21994
|
});
|
|
@@ -21552,6 +22196,43 @@ var Vault = class {
|
|
|
21552
22196
|
if (descriptor.mode !== "strict") continue;
|
|
21553
22197
|
const rawId = obj[field];
|
|
21554
22198
|
if (rawId === null || rawId === void 0) continue;
|
|
22199
|
+
if (isRefArray(descriptor)) {
|
|
22200
|
+
if (!Array.isArray(rawId)) {
|
|
22201
|
+
throw new RefIntegrityError({
|
|
22202
|
+
collection: collectionName,
|
|
22203
|
+
id: obj["id"] ?? "<unknown>",
|
|
22204
|
+
field,
|
|
22205
|
+
refTo: descriptor.target,
|
|
22206
|
+
refId: null,
|
|
22207
|
+
message: `Array ref field "${collectionName}.${field}" must be an array, got ${typeof rawId}.`
|
|
22208
|
+
});
|
|
22209
|
+
}
|
|
22210
|
+
const arrTarget = this.collection(descriptor.target);
|
|
22211
|
+
for (const el of rawId) {
|
|
22212
|
+
if (typeof el !== "string" && typeof el !== "number") {
|
|
22213
|
+
throw new RefIntegrityError({
|
|
22214
|
+
collection: collectionName,
|
|
22215
|
+
id: obj["id"] ?? "<unknown>",
|
|
22216
|
+
field,
|
|
22217
|
+
refTo: descriptor.target,
|
|
22218
|
+
refId: null,
|
|
22219
|
+
message: `Array ref "${collectionName}.${field}" elements must be strings or numbers, got ${typeof el}.`
|
|
22220
|
+
});
|
|
22221
|
+
}
|
|
22222
|
+
const elId = String(el);
|
|
22223
|
+
if (!await arrTarget.get(elId)) {
|
|
22224
|
+
throw new RefIntegrityError({
|
|
22225
|
+
collection: collectionName,
|
|
22226
|
+
id: obj["id"] ?? "<unknown>",
|
|
22227
|
+
field,
|
|
22228
|
+
refTo: descriptor.target,
|
|
22229
|
+
refId: elId,
|
|
22230
|
+
message: `Strict array ref "${collectionName}.${field}" \u2192 "${descriptor.target}" cannot be satisfied: element id "${elId}" not found in "${descriptor.target}".`
|
|
22231
|
+
});
|
|
22232
|
+
}
|
|
22233
|
+
}
|
|
22234
|
+
continue;
|
|
22235
|
+
}
|
|
21555
22236
|
if (typeof rawId !== "string" && typeof rawId !== "number") {
|
|
21556
22237
|
throw new RefIntegrityError({
|
|
21557
22238
|
collection: collectionName,
|
|
@@ -21601,6 +22282,11 @@ var Vault = class {
|
|
|
21601
22282
|
const allRecords = await fromCollection.list();
|
|
21602
22283
|
const matches = allRecords.filter((rec) => {
|
|
21603
22284
|
const raw = rec[rule.field];
|
|
22285
|
+
if (rule.isArray) {
|
|
22286
|
+
return Array.isArray(raw) && raw.some(
|
|
22287
|
+
(el) => (typeof el === "string" || typeof el === "number") && String(el) === id
|
|
22288
|
+
);
|
|
22289
|
+
}
|
|
21604
22290
|
if (typeof raw !== "string" && typeof raw !== "number") return false;
|
|
21605
22291
|
return String(raw) === id;
|
|
21606
22292
|
});
|
|
@@ -21639,10 +22325,45 @@ var Vault = class {
|
|
|
21639
22325
|
}
|
|
21640
22326
|
}
|
|
21641
22327
|
}
|
|
22328
|
+
await this.enforceLinksOnDelete(collectionName, id);
|
|
21642
22329
|
} finally {
|
|
21643
22330
|
this.cascadeInProgress.delete(key);
|
|
21644
22331
|
}
|
|
21645
22332
|
}
|
|
22333
|
+
/**
|
|
22334
|
+
* @internal — apply link `onDelete` policy when an endpoint record is
|
|
22335
|
+
* deleted (#377-B). `'strict'` throws (blocks the delete), `'cascade'`
|
|
22336
|
+
* removes the touching link rows (tx-atomic when a transaction is active),
|
|
22337
|
+
* `'warn'` leaves orphans for `checkIntegrity()`.
|
|
22338
|
+
*/
|
|
22339
|
+
async enforceLinksOnDelete(collectionName, id) {
|
|
22340
|
+
for (const [name, spec] of this.linkRegistry) {
|
|
22341
|
+
if (spec.a !== collectionName && spec.b !== collectionName) continue;
|
|
22342
|
+
const handle = this.links(name);
|
|
22343
|
+
const touching = await handle._rowsTouchingEndpoint(collectionName, id);
|
|
22344
|
+
if (touching.length === 0) continue;
|
|
22345
|
+
const mode = spec.onDelete ?? "cascade";
|
|
22346
|
+
if (mode === "warn") continue;
|
|
22347
|
+
if (mode === "strict") {
|
|
22348
|
+
throw new LinkIntegrityError(name, collectionName, id, touching.length);
|
|
22349
|
+
}
|
|
22350
|
+
const linkColl = handle._collectionName;
|
|
22351
|
+
const txCtx = this.noydb._activeTxContextOrNull;
|
|
22352
|
+
for (const row of touching) {
|
|
22353
|
+
const rowKey = linkRowKey(row.a, row.b);
|
|
22354
|
+
if (txCtx !== null) {
|
|
22355
|
+
const prior = await this.adapter.get(this.name, linkColl, rowKey);
|
|
22356
|
+
if (prior !== null) {
|
|
22357
|
+
txCtx._executed.push({
|
|
22358
|
+
op: { type: "delete", vaultName: this.name, collectionName: linkColl, id: rowKey },
|
|
22359
|
+
priorEnvelope: prior
|
|
22360
|
+
});
|
|
22361
|
+
}
|
|
22362
|
+
}
|
|
22363
|
+
await handle.disconnect(row.a, row.b);
|
|
22364
|
+
}
|
|
22365
|
+
}
|
|
22366
|
+
}
|
|
21646
22367
|
// ─── Join resolver) ────────────────────
|
|
21647
22368
|
/**
|
|
21648
22369
|
* Look up the `RefDescriptor` the left collection declared for a
|
|
@@ -21703,6 +22424,23 @@ var Vault = class {
|
|
|
21703
22424
|
for (const [field, descriptor] of Object.entries(refs)) {
|
|
21704
22425
|
const rawId = record[field];
|
|
21705
22426
|
if (rawId === null || rawId === void 0) continue;
|
|
22427
|
+
const target = this.collection(descriptor.target);
|
|
22428
|
+
if (isRefArray(descriptor)) {
|
|
22429
|
+
if (!Array.isArray(rawId)) {
|
|
22430
|
+
violations.push({ collection: collectionName, id: recId, field, refTo: descriptor.target, refId: rawId, mode: descriptor.mode });
|
|
22431
|
+
continue;
|
|
22432
|
+
}
|
|
22433
|
+
for (const el of rawId) {
|
|
22434
|
+
if (typeof el !== "string" && typeof el !== "number") {
|
|
22435
|
+
violations.push({ collection: collectionName, id: recId, field, refTo: descriptor.target, refId: el, mode: descriptor.mode });
|
|
22436
|
+
continue;
|
|
22437
|
+
}
|
|
22438
|
+
if (!await target.get(String(el))) {
|
|
22439
|
+
violations.push({ collection: collectionName, id: recId, field, refTo: descriptor.target, refId: el, mode: descriptor.mode });
|
|
22440
|
+
}
|
|
22441
|
+
}
|
|
22442
|
+
continue;
|
|
22443
|
+
}
|
|
21706
22444
|
if (typeof rawId !== "string" && typeof rawId !== "number") {
|
|
21707
22445
|
violations.push({
|
|
21708
22446
|
collection: collectionName,
|
|
@@ -21715,7 +22453,6 @@ var Vault = class {
|
|
|
21715
22453
|
continue;
|
|
21716
22454
|
}
|
|
21717
22455
|
const refId = String(rawId);
|
|
21718
|
-
const target = this.collection(descriptor.target);
|
|
21719
22456
|
const exists = await target.get(refId);
|
|
21720
22457
|
if (!exists) {
|
|
21721
22458
|
violations.push({
|
|
@@ -21730,6 +22467,19 @@ var Vault = class {
|
|
|
21730
22467
|
}
|
|
21731
22468
|
}
|
|
21732
22469
|
}
|
|
22470
|
+
for (const [name, spec] of this.linkRegistry) {
|
|
22471
|
+
const linkColl = linkCollectionName(name);
|
|
22472
|
+
const rows = await this.links(name).list();
|
|
22473
|
+
for (const row of rows) {
|
|
22474
|
+
const rowKey = linkRowKey(row.a, row.b);
|
|
22475
|
+
if (await this.collection(spec.a).get(row.a) === null) {
|
|
22476
|
+
violations.push({ collection: linkColl, id: rowKey, field: "a", refTo: spec.a, refId: row.a, mode: spec.onDelete ?? "cascade" });
|
|
22477
|
+
}
|
|
22478
|
+
if (await this.collection(spec.b).get(row.b) === null) {
|
|
22479
|
+
violations.push({ collection: linkColl, id: rowKey, field: "b", refTo: spec.b, refId: row.b, mode: spec.onDelete ?? "cascade" });
|
|
22480
|
+
}
|
|
22481
|
+
}
|
|
22482
|
+
}
|
|
21733
22483
|
return { violations };
|
|
21734
22484
|
}
|
|
21735
22485
|
/**
|
|
@@ -22005,7 +22755,7 @@ var Vault = class {
|
|
|
22005
22755
|
const registry = new GuardRegistry2();
|
|
22006
22756
|
for (const h of handles) registry.register(h.spec);
|
|
22007
22757
|
this.guardRegistry = registry;
|
|
22008
|
-
this.
|
|
22758
|
+
this.guardFacade = new ReadOnlyVaultFacade2(this, "guard");
|
|
22009
22759
|
}
|
|
22010
22760
|
/**
|
|
22011
22761
|
* @internal — The gate handler in Noydb.#registerGuardGate calls into
|
|
@@ -22036,8 +22786,8 @@ var Vault = class {
|
|
|
22036
22786
|
}
|
|
22037
22787
|
registry.validate();
|
|
22038
22788
|
this.derivationRegistry = registry;
|
|
22039
|
-
if (this.
|
|
22040
|
-
this.
|
|
22789
|
+
if (this.derivationFacade === null) {
|
|
22790
|
+
this.derivationFacade = new ReadOnlyVaultFacade2(this, "derivation");
|
|
22041
22791
|
}
|
|
22042
22792
|
}
|
|
22043
22793
|
/**
|
|
@@ -22154,7 +22904,7 @@ var Vault = class {
|
|
|
22154
22904
|
const { DerivationExecutor: DerivationExecutor2 } = await Promise.resolve().then(() => (init_executor2(), executor_exports2));
|
|
22155
22905
|
const sourceColl = this.collection(sourceCollection);
|
|
22156
22906
|
const records = await sourceColl.list();
|
|
22157
|
-
const ctx = { vault: this.
|
|
22907
|
+
const ctx = { vault: this.derivationFacade ?? new (await Promise.resolve().then(() => (init_read_only_facade(), read_only_facade_exports))).ReadOnlyVaultFacade(this, "derivation") };
|
|
22158
22908
|
let derived = 0;
|
|
22159
22909
|
let failed = 0;
|
|
22160
22910
|
for (const record of records) {
|
|
@@ -22219,17 +22969,18 @@ var Vault = class {
|
|
|
22219
22969
|
* never see null).
|
|
22220
22970
|
*/
|
|
22221
22971
|
_getReadOnlyFacade() {
|
|
22222
|
-
return this.
|
|
22972
|
+
return this.guardFacade;
|
|
22223
22973
|
}
|
|
22224
22974
|
/**
|
|
22225
|
-
* Internal lazy-allocator for the read-only facade
|
|
22226
|
-
* defensive fallback; in practice
|
|
22227
|
-
* instantiates this, so the lazy path is
|
|
22975
|
+
* Internal lazy-allocator for the derivation read-only facade
|
|
22976
|
+
* (`layer:'derivation'`). Used as a defensive fallback; in practice
|
|
22977
|
+
* `_initDerivations()` eagerly instantiates this, so the lazy path is
|
|
22978
|
+
* a no-op.
|
|
22228
22979
|
*/
|
|
22229
22980
|
_ensureReadOnlyFacade() {
|
|
22230
|
-
if (this.
|
|
22981
|
+
if (this.derivationFacade !== null) return this.derivationFacade;
|
|
22231
22982
|
throw new Error(
|
|
22232
|
-
"Vault:
|
|
22983
|
+
"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
22984
|
);
|
|
22234
22985
|
}
|
|
22235
22986
|
/**
|
|
@@ -22350,7 +23101,7 @@ var Vault = class {
|
|
|
22350
23101
|
*
|
|
22351
23102
|
* @internal
|
|
22352
23103
|
*/
|
|
22353
|
-
async _logConsent(op, collection,
|
|
23104
|
+
async _logConsent(op, collection, recordId4) {
|
|
22354
23105
|
const ctx = this.consentContext;
|
|
22355
23106
|
if (!ctx) return;
|
|
22356
23107
|
await this.consentStrategy.write(
|
|
@@ -22363,7 +23114,7 @@ var Vault = class {
|
|
|
22363
23114
|
consentHash: ctx.consentHash,
|
|
22364
23115
|
op,
|
|
22365
23116
|
collection,
|
|
22366
|
-
recordId:
|
|
23117
|
+
recordId: recordId4
|
|
22367
23118
|
},
|
|
22368
23119
|
this.getDEK
|
|
22369
23120
|
);
|
|
@@ -22546,14 +23297,14 @@ var Vault = class {
|
|
|
22546
23297
|
* the HKDF derivation, record-id composition, and batch logic so the
|
|
22547
23298
|
* grantor doesn't touch this method directly.
|
|
22548
23299
|
*/
|
|
22549
|
-
async writeMagicLinkGrant(contentKey, grantKek,
|
|
23300
|
+
async writeMagicLinkGrant(contentKey, grantKek, recordId4, opts) {
|
|
22550
23301
|
return writeMagicLinkGrant(
|
|
22551
23302
|
this.adapter,
|
|
22552
23303
|
this.name,
|
|
22553
23304
|
this.keyring,
|
|
22554
23305
|
contentKey,
|
|
22555
23306
|
grantKek,
|
|
22556
|
-
|
|
23307
|
+
recordId4,
|
|
22557
23308
|
opts
|
|
22558
23309
|
);
|
|
22559
23310
|
}
|
|
@@ -25117,7 +25868,7 @@ var Noydb = class {
|
|
|
25117
25868
|
const { StateManagementVault: StateManagementVault2 } = await Promise.resolve().then(() => (init_state_vault(), state_vault_exports));
|
|
25118
25869
|
const stateVault = opts.registry ? void 0 : await StateManagementVault2.open(this);
|
|
25119
25870
|
const registry = opts.registry ?? stateVault.registry;
|
|
25120
|
-
const group = new VaultGroup2(this, name, registry, opts.sharding, template);
|
|
25871
|
+
const group = new VaultGroup2(this, name, registry, opts.sharding, template, opts.migrateOnOpen ?? false);
|
|
25121
25872
|
if (stateVault) {
|
|
25122
25873
|
group._attachStateVault(stateVault);
|
|
25123
25874
|
await stateVault.recordManifest(opts.sharding.vaultTemplate, template);
|
|
@@ -27461,6 +28212,60 @@ function immutableGuard(config) {
|
|
|
27461
28212
|
return withGuard(spec);
|
|
27462
28213
|
}
|
|
27463
28214
|
|
|
28215
|
+
// src/guards/transition-guard.ts
|
|
28216
|
+
init_errors();
|
|
28217
|
+
function recordId3(record) {
|
|
28218
|
+
const id = record?.id;
|
|
28219
|
+
return typeof id === "string" ? id : "";
|
|
28220
|
+
}
|
|
28221
|
+
function stateOf(record, field) {
|
|
28222
|
+
const v = record[field];
|
|
28223
|
+
return typeof v === "string" ? v : String(v);
|
|
28224
|
+
}
|
|
28225
|
+
function transitionGuard(config) {
|
|
28226
|
+
const { collection, field, transitions, initial, amendmentRoles, amendmentInvariant } = config;
|
|
28227
|
+
const allowIdempotent = config.allowIdempotent ?? true;
|
|
28228
|
+
if (!field) {
|
|
28229
|
+
throw new ValidationError("transitionGuard: `field` is required");
|
|
28230
|
+
}
|
|
28231
|
+
if (transitions === void 0 || typeof transitions !== "object") {
|
|
28232
|
+
throw new ValidationError("transitionGuard: `transitions` must be a state\u2192states map");
|
|
28233
|
+
}
|
|
28234
|
+
const spec = {
|
|
28235
|
+
collection,
|
|
28236
|
+
check: (incoming, ctx) => {
|
|
28237
|
+
const rec = incoming;
|
|
28238
|
+
const to = stateOf(rec, field);
|
|
28239
|
+
if (ctx.existing === null) {
|
|
28240
|
+
if (initial !== void 0 && !initial.includes(to)) {
|
|
28241
|
+
throw new IllegalTransitionError(collection, recordId3(rec), "(none)", to);
|
|
28242
|
+
}
|
|
28243
|
+
return;
|
|
28244
|
+
}
|
|
28245
|
+
const from = stateOf(ctx.existing, field);
|
|
28246
|
+
if (from === to) {
|
|
28247
|
+
if (allowIdempotent) return;
|
|
28248
|
+
throw new IllegalTransitionError(collection, recordId3(rec), from, to);
|
|
28249
|
+
}
|
|
28250
|
+
const allowed = transitions[from] ?? [];
|
|
28251
|
+
if (!allowed.includes(to)) {
|
|
28252
|
+
throw new IllegalTransitionError(collection, recordId3(rec), from, to);
|
|
28253
|
+
}
|
|
28254
|
+
},
|
|
28255
|
+
// The authorized override: inside an amendment transaction the check
|
|
28256
|
+
// is skipped and the change is ledgered. By default no extra invariant
|
|
28257
|
+
// — the amendment itself is the sanctioned exception. Callers may
|
|
28258
|
+
// supply `amendmentInvariant` to keep a constraint inviolable even
|
|
28259
|
+
// under amendment; a throw reverts the amendment as `InvariantError`.
|
|
28260
|
+
amendment: {
|
|
28261
|
+
roles: amendmentRoles ?? ["admin", "owner"],
|
|
28262
|
+
invariant: amendmentInvariant ?? (() => {
|
|
28263
|
+
})
|
|
28264
|
+
}
|
|
28265
|
+
};
|
|
28266
|
+
return withGuard(spec);
|
|
28267
|
+
}
|
|
28268
|
+
|
|
27464
28269
|
// src/derivations/with-derivation.ts
|
|
27465
28270
|
init_errors();
|
|
27466
28271
|
function withDerivation(spec) {
|
|
@@ -27488,8 +28293,37 @@ function withDerivation(spec) {
|
|
|
27488
28293
|
}
|
|
27489
28294
|
}
|
|
27490
28295
|
}
|
|
28296
|
+
if (spec.triggerBy !== void 0) {
|
|
28297
|
+
for (const t of spec.triggerBy) {
|
|
28298
|
+
if (typeof t?.collection !== "string" || t.collection.length === 0) {
|
|
28299
|
+
throw new ValidationError("withDerivation: each triggerBy entry needs a non-empty `collection`");
|
|
28300
|
+
}
|
|
28301
|
+
if (t.collection === spec.source) {
|
|
28302
|
+
throw new ValidationError(
|
|
28303
|
+
`withDerivation: triggerBy.collection must not equal the source "${spec.source}" (use sources[] for same-id triggers)`
|
|
28304
|
+
);
|
|
28305
|
+
}
|
|
28306
|
+
if (typeof t.on !== "string" || t.on.length === 0) {
|
|
28307
|
+
throw new ValidationError(
|
|
28308
|
+
`withDerivation: triggerBy on "${t.collection}" needs a non-empty \`on\` (the FK field on the source)`
|
|
28309
|
+
);
|
|
28310
|
+
}
|
|
28311
|
+
if (t.maxFanout !== void 0 && (!Number.isInteger(t.maxFanout) || t.maxFanout < 1)) {
|
|
28312
|
+
throw new ValidationError(
|
|
28313
|
+
`withDerivation: triggerBy maxFanout on "${t.collection}" must be a positive integer (got ${String(t.maxFanout)}).`
|
|
28314
|
+
);
|
|
28315
|
+
}
|
|
28316
|
+
}
|
|
28317
|
+
}
|
|
27491
28318
|
const lifecycleMode = typeof spec.lifecycle === "string" ? spec.lifecycle : spec.lifecycle.mode;
|
|
27492
28319
|
for (const [outputKey, outputSpec] of Object.entries(spec.outputs)) {
|
|
28320
|
+
if (outputSpec.shape === "record" && outputSpec.collection === spec.source) {
|
|
28321
|
+
if (!outputSpec.denorm || outputSpec.denorm.length === 0) {
|
|
28322
|
+
throw new ValidationError(
|
|
28323
|
+
`withDerivation: self-write output "${outputKey}" (collection === source "${spec.source}") must declare \`denorm: [...]\` naming the fields it maintains.`
|
|
28324
|
+
);
|
|
28325
|
+
}
|
|
28326
|
+
}
|
|
27493
28327
|
if (outputSpec.shape === "array") {
|
|
27494
28328
|
if (lifecycleMode !== "eager") {
|
|
27495
28329
|
throw new ValidationError(
|
|
@@ -27517,6 +28351,43 @@ function withDerivation(spec) {
|
|
|
27517
28351
|
};
|
|
27518
28352
|
}
|
|
27519
28353
|
|
|
28354
|
+
// src/derivations/with-rollup.ts
|
|
28355
|
+
init_errors();
|
|
28356
|
+
function withRollup(config) {
|
|
28357
|
+
const { from, key, into, field, compute } = config;
|
|
28358
|
+
if (!from || from.length === 0) {
|
|
28359
|
+
throw new ValidationError("withRollup: `from` (child collection) is required");
|
|
28360
|
+
}
|
|
28361
|
+
if (!into || into.length === 0) {
|
|
28362
|
+
throw new ValidationError("withRollup: `into` (parent collection) is required");
|
|
28363
|
+
}
|
|
28364
|
+
if (from === into) {
|
|
28365
|
+
throw new ValidationError("withRollup: `from` and `into` must be different collections");
|
|
28366
|
+
}
|
|
28367
|
+
if (!key || key.length === 0) {
|
|
28368
|
+
throw new ValidationError("withRollup: `key` (FK field on the child) is required");
|
|
28369
|
+
}
|
|
28370
|
+
if (!field || field.length === 0) {
|
|
28371
|
+
throw new ValidationError("withRollup: `field` (target field on the parent) is required");
|
|
28372
|
+
}
|
|
28373
|
+
if (typeof compute !== "function") {
|
|
28374
|
+
throw new ValidationError("withRollup: `compute` must be a function");
|
|
28375
|
+
}
|
|
28376
|
+
const spec = {
|
|
28377
|
+
source: into,
|
|
28378
|
+
// the parent record is what carries the rolled-up field
|
|
28379
|
+
deterministic: true,
|
|
28380
|
+
rollup: { from, key, field, compute },
|
|
28381
|
+
// Synthetic self-write output for registry / cycle bookkeeping. Dispatch
|
|
28382
|
+
// patches `field` directly (value-equality guarded); the executor is not run.
|
|
28383
|
+
outputs: { value: { shape: "record", collection: into, denorm: [field] } },
|
|
28384
|
+
derive: () => ({ value: {} }),
|
|
28385
|
+
// never invoked for a rollup strategy
|
|
28386
|
+
lifecycle: "eager"
|
|
28387
|
+
};
|
|
28388
|
+
return { __noydb_strategy: "derivation", spec };
|
|
28389
|
+
}
|
|
28390
|
+
|
|
27520
28391
|
// src/index.ts
|
|
27521
28392
|
init_errors();
|
|
27522
28393
|
|
|
@@ -28578,6 +29449,7 @@ function shortJSON(value) {
|
|
|
28578
29449
|
GroupedQuery,
|
|
28579
29450
|
GroupedQueryN,
|
|
28580
29451
|
INDEXED_STORE_POLICY,
|
|
29452
|
+
IllegalTransitionError,
|
|
28581
29453
|
ImportCapabilityError,
|
|
28582
29454
|
IndexRequiredError,
|
|
28583
29455
|
IndexWriteFailureError,
|
|
@@ -28590,6 +29462,8 @@ function shortJSON(value) {
|
|
|
28590
29462
|
LEDGER_DELTAS_COLLECTION,
|
|
28591
29463
|
LedgerContentionError,
|
|
28592
29464
|
LedgerStore,
|
|
29465
|
+
LinkEndpointError,
|
|
29466
|
+
LinkIntegrityError,
|
|
28593
29467
|
LocaleNotSpecifiedError,
|
|
28594
29468
|
Lru,
|
|
28595
29469
|
MAGIC_LINK_CONTENT_INFO_PREFIX,
|
|
@@ -28721,6 +29595,7 @@ function shortJSON(value) {
|
|
|
28721
29595
|
canonicalJson,
|
|
28722
29596
|
checkGate,
|
|
28723
29597
|
clearDevUnlock,
|
|
29598
|
+
compileSequenceFormat,
|
|
28724
29599
|
computePatch,
|
|
28725
29600
|
coordinatedCutover,
|
|
28726
29601
|
count,
|
|
@@ -28783,11 +29658,13 @@ function shortJSON(value) {
|
|
|
28783
29658
|
isDictKeyDescriptor,
|
|
28784
29659
|
isDiscriminant,
|
|
28785
29660
|
isI18nTextDescriptor,
|
|
29661
|
+
isLinkCollectionName,
|
|
28786
29662
|
isMagicLinkGrantExpired,
|
|
28787
29663
|
isMoneyDescriptor,
|
|
28788
29664
|
isMoneyString,
|
|
28789
29665
|
isPreCompressed,
|
|
28790
29666
|
isPublicEnvelope,
|
|
29667
|
+
isRefArray,
|
|
28791
29668
|
isSessionAlive,
|
|
28792
29669
|
isStaticDictDescriptor,
|
|
28793
29670
|
isULID,
|
|
@@ -28841,6 +29718,7 @@ function shortJSON(value) {
|
|
|
28841
29718
|
recoverUser,
|
|
28842
29719
|
reduceRecords,
|
|
28843
29720
|
ref,
|
|
29721
|
+
refArray,
|
|
28844
29722
|
removeAuthenticator,
|
|
28845
29723
|
resetBrotliSupportCache,
|
|
28846
29724
|
resetJoinWarnings,
|
|
@@ -28868,6 +29746,7 @@ function shortJSON(value) {
|
|
|
28868
29746
|
sha256Hex,
|
|
28869
29747
|
staticDict,
|
|
28870
29748
|
sum,
|
|
29749
|
+
transitionGuard,
|
|
28871
29750
|
unwrapDeksFromBlob,
|
|
28872
29751
|
unwrapDeksFromPaperEntry,
|
|
28873
29752
|
unwrapDeksFromShamirEntry,
|
|
@@ -28891,6 +29770,7 @@ function shortJSON(value) {
|
|
|
28891
29770
|
withMetrics,
|
|
28892
29771
|
withOverlayedView,
|
|
28893
29772
|
withRetry,
|
|
29773
|
+
withRollup,
|
|
28894
29774
|
wrapBundleStore,
|
|
28895
29775
|
wrapStore,
|
|
28896
29776
|
writeMagicLinkGrant,
|