@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/bundle/index.cjs
CHANGED
|
@@ -4343,6 +4343,21 @@ function formatCurrency(decimal, currency, scale, locale) {
|
|
|
4343
4343
|
});
|
|
4344
4344
|
return fmt.format(decimal);
|
|
4345
4345
|
}
|
|
4346
|
+
function moneyScaledValue(stored, desc) {
|
|
4347
|
+
let raw;
|
|
4348
|
+
if (desc.mode === "fixed") {
|
|
4349
|
+
raw = stored;
|
|
4350
|
+
} else {
|
|
4351
|
+
if (!isMoneyValueObject(stored)) return null;
|
|
4352
|
+
raw = stored.amount;
|
|
4353
|
+
}
|
|
4354
|
+
if (typeof raw !== "string" && typeof raw !== "number") return null;
|
|
4355
|
+
try {
|
|
4356
|
+
return BigInt(String(raw));
|
|
4357
|
+
} catch {
|
|
4358
|
+
return null;
|
|
4359
|
+
}
|
|
4360
|
+
}
|
|
4346
4361
|
function decodeValue(stored, desc) {
|
|
4347
4362
|
let currency;
|
|
4348
4363
|
let scaledIntString;
|
|
@@ -5300,7 +5315,7 @@ function executePlanWithSource(source, plan, joinContext) {
|
|
|
5300
5315
|
result = remainingClauses.length === 0 ? [...candidates] : filterRecords(candidates, remainingClauses, fnViewDecoder(source));
|
|
5301
5316
|
}
|
|
5302
5317
|
if (plan.orderBy.length > 0) {
|
|
5303
|
-
result = sortRecords(result, plan.orderBy);
|
|
5318
|
+
result = sortRecords(result, plan.orderBy, source.moneyFields);
|
|
5304
5319
|
}
|
|
5305
5320
|
if (plan.offset > 0) {
|
|
5306
5321
|
result = result.slice(plan.offset);
|
|
@@ -5439,17 +5454,25 @@ function applyCrossJoin(leftRel, clause, rightSource) {
|
|
|
5439
5454
|
}
|
|
5440
5455
|
return expanded;
|
|
5441
5456
|
}
|
|
5442
|
-
function sortRecords(records, orderBy) {
|
|
5457
|
+
function sortRecords(records, orderBy, moneyFields) {
|
|
5443
5458
|
return [...records].sort((a, b) => {
|
|
5444
5459
|
for (const { field, direction } of orderBy) {
|
|
5445
5460
|
const av = readField(a, field);
|
|
5446
5461
|
const bv = readField(b, field);
|
|
5447
|
-
const
|
|
5462
|
+
const desc = moneyFields?.[field];
|
|
5463
|
+
const cmp = desc ? compareMoney(av, bv, desc) : compareValues(av, bv);
|
|
5448
5464
|
if (cmp !== 0) return direction === "asc" ? cmp : -cmp;
|
|
5449
5465
|
}
|
|
5450
5466
|
return 0;
|
|
5451
5467
|
});
|
|
5452
5468
|
}
|
|
5469
|
+
function compareMoney(a, b, desc) {
|
|
5470
|
+
const av = moneyScaledValue(a, desc);
|
|
5471
|
+
const bv = moneyScaledValue(b, desc);
|
|
5472
|
+
if (av === null) return bv === null ? 0 : 1;
|
|
5473
|
+
if (bv === null) return -1;
|
|
5474
|
+
return av < bv ? -1 : av > bv ? 1 : 0;
|
|
5475
|
+
}
|
|
5453
5476
|
function readField(record, field) {
|
|
5454
5477
|
if (record === null || record === void 0) return void 0;
|
|
5455
5478
|
if (!field.includes(".")) {
|
|
@@ -8272,6 +8295,15 @@ var init_fanout_sidecar = __esm({
|
|
|
8272
8295
|
});
|
|
8273
8296
|
|
|
8274
8297
|
// src/collection.ts
|
|
8298
|
+
function selfWriteFieldEqual(a, b) {
|
|
8299
|
+
if (a === b) return true;
|
|
8300
|
+
if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
|
|
8301
|
+
try {
|
|
8302
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
8303
|
+
} catch {
|
|
8304
|
+
return false;
|
|
8305
|
+
}
|
|
8306
|
+
}
|
|
8275
8307
|
function warnOnceFallback(adapterName) {
|
|
8276
8308
|
if (fallbackWarned.has(adapterName)) return;
|
|
8277
8309
|
fallbackWarned.add(adapterName);
|
|
@@ -9276,6 +9308,111 @@ var init_collection = __esm({
|
|
|
9276
9308
|
* output (carries `_derivedFrom`) — defensive guard against missed
|
|
9277
9309
|
* cycle detection.
|
|
9278
9310
|
*/
|
|
9311
|
+
/**
|
|
9312
|
+
* @internal #376 — the RAW stored record (canonical-money form, i18n maps
|
|
9313
|
+
* intact), WITHOUT the locale resolution `get()` applies. Used as the
|
|
9314
|
+
* patch base for self-write reverse-denorm so writing back never clobbers
|
|
9315
|
+
* an i18n map or re-quantizes money incorrectly. Returns null for
|
|
9316
|
+
* missing / tombstoned records.
|
|
9317
|
+
*/
|
|
9318
|
+
async _getStoredRecord(id) {
|
|
9319
|
+
let raw;
|
|
9320
|
+
if (this.lazy && this.lru) {
|
|
9321
|
+
const cached = this.lru.get(id);
|
|
9322
|
+
if (cached) raw = cached.record;
|
|
9323
|
+
else {
|
|
9324
|
+
const env = await this.adapter.get(this.vault, this.name, id);
|
|
9325
|
+
if (!env || isTombstone(env, this.encrypted)) return null;
|
|
9326
|
+
raw = await this.decryptRecord(env, { id });
|
|
9327
|
+
if (raw === null) return null;
|
|
9328
|
+
this.lru.set(id, { record: raw, version: env._v }, estimateRecordBytes(raw));
|
|
9329
|
+
}
|
|
9330
|
+
} else {
|
|
9331
|
+
await this.ensureHydrated();
|
|
9332
|
+
raw = this.cache.get(id)?.record ?? null;
|
|
9333
|
+
}
|
|
9334
|
+
if (raw === null) return null;
|
|
9335
|
+
return canonicalizeStoredMoney(raw, this.moneyFields);
|
|
9336
|
+
}
|
|
9337
|
+
/**
|
|
9338
|
+
* @internal #376 — ids of records whose top-level `field` equals `value`.
|
|
9339
|
+
* Uses the FK index when the field is indexed (O(matches)); otherwise a
|
|
9340
|
+
* linear scan (O(N) — fine for small child sets; index the FK to scale).
|
|
9341
|
+
*/
|
|
9342
|
+
async _findMatchingIds(field, value) {
|
|
9343
|
+
const hit = this.getIndexes()?.lookupEqual(field, value);
|
|
9344
|
+
if (hit) return [...hit];
|
|
9345
|
+
const target = String(value);
|
|
9346
|
+
const matches = (rec) => {
|
|
9347
|
+
const fv = rec[field];
|
|
9348
|
+
return (typeof fv === "string" || typeof fv === "number") && String(fv) === target;
|
|
9349
|
+
};
|
|
9350
|
+
if (!this.lazy) {
|
|
9351
|
+
await this.ensureHydrated();
|
|
9352
|
+
const out2 = [];
|
|
9353
|
+
for (const [rid, e] of this.cache) {
|
|
9354
|
+
if (matches(e.record)) out2.push(rid);
|
|
9355
|
+
}
|
|
9356
|
+
return out2;
|
|
9357
|
+
}
|
|
9358
|
+
const ids = await this.adapter.list(this.vault, this.name);
|
|
9359
|
+
const out = [];
|
|
9360
|
+
for (const rid of ids) {
|
|
9361
|
+
const raw = await this._getStoredRecord(rid);
|
|
9362
|
+
if (raw !== null && matches(raw)) out.push(rid);
|
|
9363
|
+
}
|
|
9364
|
+
return out;
|
|
9365
|
+
}
|
|
9366
|
+
/**
|
|
9367
|
+
* @internal #376 slice 2 — recompute a rollup aggregate onto the parent.
|
|
9368
|
+
* Gathers every child of `parentId`, runs `compute`, and patches only the
|
|
9369
|
+
* rollup `field` onto the parent's raw stored record (value-equality
|
|
9370
|
+
* guarded). No-op when the parent record does not exist.
|
|
9371
|
+
*/
|
|
9372
|
+
async recomputeRollup(spec, parentId) {
|
|
9373
|
+
if (this.derivationSource === void 0 || spec.rollup === void 0) return;
|
|
9374
|
+
const { from, key, field, compute } = spec.rollup;
|
|
9375
|
+
const into = spec.source;
|
|
9376
|
+
const intoColl = this.derivationSource.getCollection(into);
|
|
9377
|
+
const base = await intoColl._getStoredRecord(parentId);
|
|
9378
|
+
if (base === null) return;
|
|
9379
|
+
const fromColl = this.derivationSource.getCollection(from);
|
|
9380
|
+
const childIds = await fromColl._findMatchingIds(key, parentId);
|
|
9381
|
+
const children = [];
|
|
9382
|
+
for (const cid of childIds) {
|
|
9383
|
+
const c = await fromColl.get(cid);
|
|
9384
|
+
if (c !== null && c !== void 0) children.push(c);
|
|
9385
|
+
}
|
|
9386
|
+
const newValue = compute(children);
|
|
9387
|
+
if (selfWriteFieldEqual(base[field], newValue)) return;
|
|
9388
|
+
const patched = { ...base, [field]: newValue };
|
|
9389
|
+
const txCtx = this.derivationSource.getActiveTxContext();
|
|
9390
|
+
if (txCtx !== null) {
|
|
9391
|
+
const prior = await this.adapter.get(this.vault, into, parentId);
|
|
9392
|
+
txCtx._executed.push({
|
|
9393
|
+
op: { type: "put", vaultName: this.vault, collectionName: into, id: parentId },
|
|
9394
|
+
priorEnvelope: prior
|
|
9395
|
+
});
|
|
9396
|
+
}
|
|
9397
|
+
await intoColl.put(parentId, patched);
|
|
9398
|
+
}
|
|
9399
|
+
/**
|
|
9400
|
+
* @internal #376 slice 2 — fire any rollups for which THIS collection is the
|
|
9401
|
+
* child `from`, recomputing the affected parent after a child delete. Called
|
|
9402
|
+
* from the delete path with the just-removed record's key value. Other
|
|
9403
|
+
* derivation kinds do not react to deletes (unchanged).
|
|
9404
|
+
*/
|
|
9405
|
+
async dispatchRollupsOnDelete(deleted) {
|
|
9406
|
+
if (this.derivationSource === void 0) return;
|
|
9407
|
+
const registry = this.derivationSource.registry();
|
|
9408
|
+
const rec = deleted;
|
|
9409
|
+
for (const { spec } of registry.strategiesForSource(this.name)) {
|
|
9410
|
+
if (!spec.rollup || spec.rollup.from !== this.name) continue;
|
|
9411
|
+
const kv = rec[spec.rollup.key];
|
|
9412
|
+
if (typeof kv !== "string" && typeof kv !== "number") continue;
|
|
9413
|
+
await this.recomputeRollup(spec, String(kv));
|
|
9414
|
+
}
|
|
9415
|
+
}
|
|
9279
9416
|
async dispatchDerivations(id, record, version) {
|
|
9280
9417
|
if (this.derivationSource === void 0) return;
|
|
9281
9418
|
const incoming = canonicalizeStoredMoney(record, this.moneyFields);
|
|
@@ -9286,29 +9423,60 @@ var init_collection = __esm({
|
|
|
9286
9423
|
let DerivationExecutor2 = null;
|
|
9287
9424
|
for (const { spec, strategyHash } of strategies) {
|
|
9288
9425
|
const mode = typeof spec.lifecycle === "string" ? spec.lifecycle : spec.lifecycle.mode;
|
|
9289
|
-
if (
|
|
9290
|
-
if (
|
|
9291
|
-
|
|
9292
|
-
|
|
9293
|
-
|
|
9294
|
-
|
|
9295
|
-
if (spec.source === this.name) {
|
|
9296
|
-
sourceWithId = { ...incoming, id };
|
|
9426
|
+
if (spec.rollup) {
|
|
9427
|
+
if (mode !== "eager") continue;
|
|
9428
|
+
let parentId;
|
|
9429
|
+
if (this.name === spec.rollup.from) {
|
|
9430
|
+
const kv = incoming[spec.rollup.key];
|
|
9431
|
+
parentId = typeof kv === "string" || typeof kv === "number" ? String(kv) : null;
|
|
9297
9432
|
} else {
|
|
9298
|
-
|
|
9299
|
-
|
|
9300
|
-
|
|
9301
|
-
|
|
9433
|
+
parentId = id;
|
|
9434
|
+
}
|
|
9435
|
+
if (parentId !== null) await this.recomputeRollup(spec, parentId);
|
|
9436
|
+
continue;
|
|
9437
|
+
}
|
|
9438
|
+
const isSource = spec.source === this.name;
|
|
9439
|
+
const isSibling = !isSource && (spec.sources?.includes(this.name) ?? false);
|
|
9440
|
+
const trigger = !isSource && !isSibling ? spec.triggerBy?.find((t) => t.collection === this.name) : void 0;
|
|
9441
|
+
const runs = [];
|
|
9442
|
+
if (isSource) {
|
|
9443
|
+
runs.push({ input: { ...incoming, id }, base: incoming, runId: id, version });
|
|
9444
|
+
} else if (isSibling) {
|
|
9445
|
+
const p = await this.derivationSource.getCollection(spec.source).get(id);
|
|
9446
|
+
if (p !== null && p !== void 0) {
|
|
9447
|
+
const raw = await this.derivationSource.getCollection(spec.source)._getStoredRecord(id);
|
|
9448
|
+
runs.push({ input: { ...p, id }, base: raw ?? p, runId: id, version: 0 });
|
|
9302
9449
|
}
|
|
9450
|
+
} else if (trigger) {
|
|
9451
|
+
const srcColl = this.derivationSource.getCollection(spec.source);
|
|
9452
|
+
const ids = await srcColl._findMatchingIds(trigger.on, id);
|
|
9453
|
+
if (trigger.maxFanout !== void 0 && ids.length > trigger.maxFanout) {
|
|
9454
|
+
throw new DerivationCapExceededError(`triggerBy ${this.name}\u2192${spec.source}`, ids.length, trigger.maxFanout);
|
|
9455
|
+
}
|
|
9456
|
+
for (const sid of ids) {
|
|
9457
|
+
const raw = await srcColl._getStoredRecord(sid);
|
|
9458
|
+
if (raw === null) continue;
|
|
9459
|
+
runs.push({ input: { ...raw, id: sid }, base: raw, runId: sid, version: 0 });
|
|
9460
|
+
}
|
|
9461
|
+
}
|
|
9462
|
+
if (runs.length === 0) continue;
|
|
9463
|
+
if (mode !== "eager") {
|
|
9464
|
+
for (const run of runs) await markStale(registry, spec, run.runId);
|
|
9465
|
+
continue;
|
|
9466
|
+
}
|
|
9467
|
+
if (DerivationExecutor2 === null) {
|
|
9468
|
+
({ DerivationExecutor: DerivationExecutor2 } = await Promise.resolve().then(() => (init_executor(), executor_exports)));
|
|
9469
|
+
}
|
|
9470
|
+
for (const run of runs) {
|
|
9303
9471
|
const ctx = { vault: this.derivationSource.getReadOnlyFacade() };
|
|
9304
|
-
const result = await DerivationExecutor2.run(spec,
|
|
9472
|
+
const result = await DerivationExecutor2.run(spec, run.input, run.version, strategyHash, ctx);
|
|
9305
9473
|
for (const key of Object.keys(spec.outputs)) {
|
|
9306
9474
|
const out = result.outputs[key];
|
|
9307
9475
|
if (!out) continue;
|
|
9308
9476
|
if (out.kind === "failed") {
|
|
9309
9477
|
const err = out.error;
|
|
9310
9478
|
if (spec.strict) throw err;
|
|
9311
|
-
console.warn(`[derivation] output "${key}" for source "${spec.source}" id="${
|
|
9479
|
+
console.warn(`[derivation] output "${key}" for source "${spec.source}" id="${run.runId}" failed:`, err);
|
|
9312
9480
|
continue;
|
|
9313
9481
|
}
|
|
9314
9482
|
const outSpec = spec.outputs[key];
|
|
@@ -9321,7 +9489,7 @@ var init_collection = __esm({
|
|
|
9321
9489
|
this.adapter,
|
|
9322
9490
|
this.vault,
|
|
9323
9491
|
spec.source,
|
|
9324
|
-
|
|
9492
|
+
run.runId,
|
|
9325
9493
|
key
|
|
9326
9494
|
);
|
|
9327
9495
|
const prevKeys = new Set(prior?.keys ?? []);
|
|
@@ -9348,7 +9516,7 @@ var init_collection = __esm({
|
|
|
9348
9516
|
}
|
|
9349
9517
|
await saveFanoutSidecar2(this.adapter, this.vault, {
|
|
9350
9518
|
source: spec.source,
|
|
9351
|
-
sourceId:
|
|
9519
|
+
sourceId: run.runId,
|
|
9352
9520
|
outputKey: key,
|
|
9353
9521
|
outputCollection: outSpec.collection,
|
|
9354
9522
|
keys: newKeysList
|
|
@@ -9356,25 +9524,44 @@ var init_collection = __esm({
|
|
|
9356
9524
|
continue;
|
|
9357
9525
|
}
|
|
9358
9526
|
if (out.skipped === true) {
|
|
9359
|
-
await outputCollection._internalDelete(
|
|
9527
|
+
await outputCollection._internalDelete(run.runId, txCtx);
|
|
9528
|
+
continue;
|
|
9529
|
+
}
|
|
9530
|
+
if (outSpec.shape === "record" && outSpec.denorm !== void 0 && outSpec.collection === spec.source) {
|
|
9531
|
+
const value = out.value;
|
|
9532
|
+
const patched = { ...run.base };
|
|
9533
|
+
let changed = false;
|
|
9534
|
+
for (const f of outSpec.denorm) {
|
|
9535
|
+
if (!selfWriteFieldEqual(run.base[f], value[f])) {
|
|
9536
|
+
patched[f] = value[f];
|
|
9537
|
+
changed = true;
|
|
9538
|
+
}
|
|
9539
|
+
}
|
|
9540
|
+
if (!changed) continue;
|
|
9541
|
+
if (txCtx !== null) {
|
|
9542
|
+
const prior = await this.adapter.get(this.vault, outSpec.collection, run.runId);
|
|
9543
|
+
txCtx._executed.push({
|
|
9544
|
+
op: { type: "put", vaultName: this.vault, collectionName: outSpec.collection, id: run.runId },
|
|
9545
|
+
priorEnvelope: prior
|
|
9546
|
+
});
|
|
9547
|
+
}
|
|
9548
|
+
await outputCollection.put(run.runId, patched);
|
|
9360
9549
|
continue;
|
|
9361
9550
|
}
|
|
9362
9551
|
if (txCtx !== null) {
|
|
9363
|
-
const prior = await this.adapter.get(this.vault, outSpec.collection,
|
|
9552
|
+
const prior = await this.adapter.get(this.vault, outSpec.collection, run.runId);
|
|
9364
9553
|
txCtx._executed.push({
|
|
9365
9554
|
op: {
|
|
9366
9555
|
type: "put",
|
|
9367
9556
|
vaultName: this.vault,
|
|
9368
9557
|
collectionName: outSpec.collection,
|
|
9369
|
-
id
|
|
9558
|
+
id: run.runId
|
|
9370
9559
|
},
|
|
9371
9560
|
priorEnvelope: prior
|
|
9372
9561
|
});
|
|
9373
9562
|
}
|
|
9374
|
-
await outputCollection.put(
|
|
9563
|
+
await outputCollection.put(run.runId, out.value);
|
|
9375
9564
|
}
|
|
9376
|
-
} else {
|
|
9377
|
-
await markStale(registry, spec, id);
|
|
9378
9565
|
}
|
|
9379
9566
|
}
|
|
9380
9567
|
}
|
|
@@ -9591,6 +9778,7 @@ var init_collection = __esm({
|
|
|
9591
9778
|
if (!internal) {
|
|
9592
9779
|
await this.dispatchMaterializedViewsOnDelete(id);
|
|
9593
9780
|
await this.dispatchArrayDerivationsOnDelete(id);
|
|
9781
|
+
if (existing) await this.dispatchRollupsOnDelete(existing.record);
|
|
9594
9782
|
}
|
|
9595
9783
|
}
|
|
9596
9784
|
/**
|
|
@@ -10645,14 +10833,15 @@ var init_collection = __esm({
|
|
|
10645
10833
|
(d) => isStaticDictDescriptor(d) && d.displayLocale !== void 0
|
|
10646
10834
|
);
|
|
10647
10835
|
if (!locale && !hasStaticDisplay) return result;
|
|
10836
|
+
const layer = localeOpts?._layer ?? "read";
|
|
10648
10837
|
if (locale && hasI18n && this.i18nFields) {
|
|
10649
|
-
result = this.i18nStrategy.applyI18nLocale(result, this.i18nFields, locale, localeOpts?.fallback);
|
|
10838
|
+
result = this.i18nStrategy.applyI18nLocale(result, this.i18nFields, locale, localeOpts?.fallback, layer);
|
|
10650
10839
|
}
|
|
10651
10840
|
if (hasDict && this.dictKeyFields && this.dictLabelResolver && locale !== "raw") {
|
|
10652
10841
|
const withLabels = { ...result };
|
|
10653
10842
|
const resolver = this.dictLabelResolver;
|
|
10654
10843
|
for (const [field, desc] of Object.entries(this.dictKeyFields)) {
|
|
10655
|
-
const policy = desc.onMissing ? resolvePolicy(desc.onMissing,
|
|
10844
|
+
const policy = desc.onMissing ? resolvePolicy(desc.onMissing, layer) : "null";
|
|
10656
10845
|
const fallback = policy === "substitute" ? localeOpts?.fallback ?? desc.substitute : localeOpts?.fallback;
|
|
10657
10846
|
const effLocale = locale ?? (isStaticDictDescriptor(desc) ? desc.displayLocale : void 0);
|
|
10658
10847
|
const resolveKey = async (key) => {
|
|
@@ -11672,6 +11861,35 @@ var init_archive = __esm({
|
|
|
11672
11861
|
});
|
|
11673
11862
|
|
|
11674
11863
|
// src/sequence/index.ts
|
|
11864
|
+
function compileSequenceFormat(format, series, partition) {
|
|
11865
|
+
const parts = partition ?? [];
|
|
11866
|
+
for (const m of format.matchAll(SEQ_FORMAT_TOKEN)) {
|
|
11867
|
+
const token = m[1] ?? "";
|
|
11868
|
+
if (token === "seq") continue;
|
|
11869
|
+
if (SEQ_PAD_TOKEN.test(token)) continue;
|
|
11870
|
+
const partMatch = SEQ_PARTITION_TOKEN.exec(token);
|
|
11871
|
+
if (partMatch) {
|
|
11872
|
+
const idx = Number(partMatch[1]);
|
|
11873
|
+
if (idx >= parts.length) {
|
|
11874
|
+
throw new ValidationError(
|
|
11875
|
+
`sequence("${series}"): format token "{${token}}" references partition index ${idx}, but only ${parts.length} partition component(s) were supplied.`
|
|
11876
|
+
);
|
|
11877
|
+
}
|
|
11878
|
+
continue;
|
|
11879
|
+
}
|
|
11880
|
+
throw new ValidationError(
|
|
11881
|
+
`sequence("${series}"): format contains unknown token "{${token}}". Accepted tokens: {seq}, {seq:0N}, {partition.i}.`
|
|
11882
|
+
);
|
|
11883
|
+
}
|
|
11884
|
+
return (serial) => format.replace(SEQ_FORMAT_TOKEN, (full, token) => {
|
|
11885
|
+
if (token === "seq") return String(serial);
|
|
11886
|
+
const padMatch = SEQ_PAD_TOKEN.exec(token);
|
|
11887
|
+
if (padMatch) return String(serial).padStart(Number(padMatch[1]), "0");
|
|
11888
|
+
const partMatch = SEQ_PARTITION_TOKEN.exec(token);
|
|
11889
|
+
if (partMatch) return String(parts[Number(partMatch[1])]);
|
|
11890
|
+
return full;
|
|
11891
|
+
});
|
|
11892
|
+
}
|
|
11675
11893
|
function resolveSequenceKey(series, opts) {
|
|
11676
11894
|
const partition = opts?.partition;
|
|
11677
11895
|
if (!partition || partition.length === 0) return series;
|
|
@@ -11692,7 +11910,7 @@ async function sleepBackoff2(attempt) {
|
|
|
11692
11910
|
const ms = Math.floor(Math.random() * ceil);
|
|
11693
11911
|
await new Promise((r) => setTimeout(r, ms));
|
|
11694
11912
|
}
|
|
11695
|
-
var SEQUENCE_COLLECTION, MAX_NEXT_ATTEMPTS, SequenceStore;
|
|
11913
|
+
var SEQUENCE_COLLECTION, MAX_NEXT_ATTEMPTS, SEQ_FORMAT_TOKEN, SEQ_PAD_TOKEN, SEQ_PARTITION_TOKEN, SequenceStore;
|
|
11696
11914
|
var init_sequence = __esm({
|
|
11697
11915
|
"src/sequence/index.ts"() {
|
|
11698
11916
|
"use strict";
|
|
@@ -11701,6 +11919,9 @@ var init_sequence = __esm({
|
|
|
11701
11919
|
init_errors();
|
|
11702
11920
|
SEQUENCE_COLLECTION = "_sequences";
|
|
11703
11921
|
MAX_NEXT_ATTEMPTS = 16;
|
|
11922
|
+
SEQ_FORMAT_TOKEN = /\{([^{}]*)\}/g;
|
|
11923
|
+
SEQ_PAD_TOKEN = /^seq:0(\d+)$/;
|
|
11924
|
+
SEQ_PARTITION_TOKEN = /^partition\.(\d+)$/;
|
|
11704
11925
|
SequenceStore = class {
|
|
11705
11926
|
adapter;
|
|
11706
11927
|
vault;
|
|
@@ -12145,6 +12366,9 @@ var init_strategy10 = __esm({
|
|
|
12145
12366
|
});
|
|
12146
12367
|
|
|
12147
12368
|
// src/refs.ts
|
|
12369
|
+
function isRefArray(desc) {
|
|
12370
|
+
return desc.isArray === true;
|
|
12371
|
+
}
|
|
12148
12372
|
var RefIntegrityError, RefRegistry;
|
|
12149
12373
|
var init_refs = __esm({
|
|
12150
12374
|
"src/refs.ts"() {
|
|
@@ -12190,7 +12414,7 @@ var init_refs = __esm({
|
|
|
12190
12414
|
for (const k of existingKeys) {
|
|
12191
12415
|
const a = existing[k];
|
|
12192
12416
|
const b = refs[k];
|
|
12193
|
-
if (!a || !b || a.target !== b.target || a.mode !== b.mode) {
|
|
12417
|
+
if (!a || !b || a.target !== b.target || a.mode !== b.mode || a.isArray !== b.isArray) {
|
|
12194
12418
|
throw new Error(
|
|
12195
12419
|
`RefRegistry: conflicting ref declarations for collection "${collection}" field "${k}"`
|
|
12196
12420
|
);
|
|
@@ -12201,7 +12425,7 @@ var init_refs = __esm({
|
|
|
12201
12425
|
this.outbound.set(collection, { ...refs });
|
|
12202
12426
|
for (const [field, desc] of Object.entries(refs)) {
|
|
12203
12427
|
const list = this.inbound.get(desc.target) ?? [];
|
|
12204
|
-
list.push({ collection, field, mode: desc.mode });
|
|
12428
|
+
list.push({ collection, field, mode: desc.mode, ...desc.isArray ? { isArray: true } : {} });
|
|
12205
12429
|
this.inbound.set(desc.target, list);
|
|
12206
12430
|
}
|
|
12207
12431
|
}
|
|
@@ -12231,6 +12455,150 @@ var init_refs = __esm({
|
|
|
12231
12455
|
}
|
|
12232
12456
|
});
|
|
12233
12457
|
|
|
12458
|
+
// src/links/link-set.ts
|
|
12459
|
+
function linkCollectionName(name) {
|
|
12460
|
+
return `${LINK_COLLECTION_PREFIX}${name}`;
|
|
12461
|
+
}
|
|
12462
|
+
function isLinkCollectionName(name) {
|
|
12463
|
+
return name.startsWith(LINK_COLLECTION_PREFIX);
|
|
12464
|
+
}
|
|
12465
|
+
function linkRowKey(aId, bId) {
|
|
12466
|
+
return `${encodeURIComponent(aId)}|${encodeURIComponent(bId)}`;
|
|
12467
|
+
}
|
|
12468
|
+
var LINK_COLLECTION_PREFIX, LinkSet, LinkEndpointError, LinkIntegrityError;
|
|
12469
|
+
var init_link_set = __esm({
|
|
12470
|
+
"src/links/link-set.ts"() {
|
|
12471
|
+
"use strict";
|
|
12472
|
+
init_types();
|
|
12473
|
+
init_crypto();
|
|
12474
|
+
init_errors();
|
|
12475
|
+
LINK_COLLECTION_PREFIX = "_links_";
|
|
12476
|
+
LinkSet = class {
|
|
12477
|
+
constructor(adapter, vault, name, spec, encrypted, getDEK, actor, emitter, endpointExists) {
|
|
12478
|
+
this.adapter = adapter;
|
|
12479
|
+
this.vault = vault;
|
|
12480
|
+
this.name = name;
|
|
12481
|
+
this.spec = spec;
|
|
12482
|
+
this.encrypted = encrypted;
|
|
12483
|
+
this.getDEK = getDEK;
|
|
12484
|
+
this.actor = actor;
|
|
12485
|
+
this.emitter = emitter;
|
|
12486
|
+
this.endpointExists = endpointExists;
|
|
12487
|
+
this.collName = linkCollectionName(name);
|
|
12488
|
+
}
|
|
12489
|
+
adapter;
|
|
12490
|
+
vault;
|
|
12491
|
+
name;
|
|
12492
|
+
spec;
|
|
12493
|
+
encrypted;
|
|
12494
|
+
getDEK;
|
|
12495
|
+
actor;
|
|
12496
|
+
emitter;
|
|
12497
|
+
endpointExists;
|
|
12498
|
+
collName;
|
|
12499
|
+
dekPromise = null;
|
|
12500
|
+
dek() {
|
|
12501
|
+
if (!this.dekPromise) this.dekPromise = this.getDEK(this.collName);
|
|
12502
|
+
return this.dekPromise;
|
|
12503
|
+
}
|
|
12504
|
+
async encryptEntry(entry, version) {
|
|
12505
|
+
const json = JSON.stringify(entry);
|
|
12506
|
+
const base = { _noydb: NOYDB_FORMAT_VERSION, _v: version, _ts: (/* @__PURE__ */ new Date()).toISOString(), _by: this.actor };
|
|
12507
|
+
if (!this.encrypted) return { ...base, _iv: "", _data: json };
|
|
12508
|
+
const { iv, data } = await encrypt(json, await this.dek());
|
|
12509
|
+
return { ...base, _iv: iv, _data: data };
|
|
12510
|
+
}
|
|
12511
|
+
async decryptEntry(env) {
|
|
12512
|
+
const json = this.encrypted ? await decrypt(env._iv, env._data, await this.dek()) : env._data;
|
|
12513
|
+
return JSON.parse(json);
|
|
12514
|
+
}
|
|
12515
|
+
async connect(aId, bId, meta) {
|
|
12516
|
+
if (!await this.endpointExists(this.spec.a, aId)) {
|
|
12517
|
+
throw new LinkEndpointError(this.name, this.spec.a, aId);
|
|
12518
|
+
}
|
|
12519
|
+
if (!await this.endpointExists(this.spec.b, bId)) {
|
|
12520
|
+
throw new LinkEndpointError(this.name, this.spec.b, bId);
|
|
12521
|
+
}
|
|
12522
|
+
const key = linkRowKey(aId, bId);
|
|
12523
|
+
const entry = meta !== void 0 ? { a: aId, b: bId, meta } : { a: aId, b: bId };
|
|
12524
|
+
const existing = await this.adapter.get(this.vault, this.collName, key);
|
|
12525
|
+
const env = await this.encryptEntry(entry, (existing?._v ?? 0) + 1);
|
|
12526
|
+
await this.adapter.put(this.vault, this.collName, key, env, existing?._v);
|
|
12527
|
+
this.emitter.emit("change", { vault: this.vault, collection: this.collName, id: key, action: "put" });
|
|
12528
|
+
}
|
|
12529
|
+
async disconnect(aId, bId) {
|
|
12530
|
+
const key = linkRowKey(aId, bId);
|
|
12531
|
+
const existing = await this.adapter.get(this.vault, this.collName, key);
|
|
12532
|
+
if (!existing) return;
|
|
12533
|
+
await this.adapter.delete(this.vault, this.collName, key);
|
|
12534
|
+
this.emitter.emit("change", { vault: this.vault, collection: this.collName, id: key, action: "delete" });
|
|
12535
|
+
}
|
|
12536
|
+
async has(aId, bId) {
|
|
12537
|
+
return await this.adapter.get(this.vault, this.collName, linkRowKey(aId, bId)) !== null;
|
|
12538
|
+
}
|
|
12539
|
+
async of(id) {
|
|
12540
|
+
const rows = await this.list();
|
|
12541
|
+
return rows.filter((r) => r.a === id || r.b === id);
|
|
12542
|
+
}
|
|
12543
|
+
async list() {
|
|
12544
|
+
const keys = await this.adapter.list(this.vault, this.collName);
|
|
12545
|
+
const out = [];
|
|
12546
|
+
for (const key of keys) {
|
|
12547
|
+
const env = await this.adapter.get(this.vault, this.collName, key);
|
|
12548
|
+
if (!env) continue;
|
|
12549
|
+
const e = await this.decryptEntry(env);
|
|
12550
|
+
out.push(e.meta !== void 0 ? { a: e.a, b: e.b, meta: e.meta } : { a: e.a, b: e.b });
|
|
12551
|
+
}
|
|
12552
|
+
return out;
|
|
12553
|
+
}
|
|
12554
|
+
// ── Vault-internal cascade helpers ──────────────────────────────────
|
|
12555
|
+
/** @internal — rows where the deleted endpoint id matches the relevant slot. */
|
|
12556
|
+
async _rowsTouchingEndpoint(collection, id) {
|
|
12557
|
+
const rows = await this.list();
|
|
12558
|
+
return rows.filter(
|
|
12559
|
+
(r) => this.spec.a === collection && r.a === id || this.spec.b === collection && r.b === id
|
|
12560
|
+
);
|
|
12561
|
+
}
|
|
12562
|
+
/** @internal — the storage collection name (for tx pre-image capture). */
|
|
12563
|
+
get _collectionName() {
|
|
12564
|
+
return this.collName;
|
|
12565
|
+
}
|
|
12566
|
+
};
|
|
12567
|
+
LinkEndpointError = class extends NoydbError {
|
|
12568
|
+
link;
|
|
12569
|
+
endpoint;
|
|
12570
|
+
missingId;
|
|
12571
|
+
constructor(link, endpoint, missingId) {
|
|
12572
|
+
super(
|
|
12573
|
+
"LINK_ENDPOINT",
|
|
12574
|
+
`link("${link}").connect: endpoint "${endpoint}" has no record "${missingId}".`
|
|
12575
|
+
);
|
|
12576
|
+
this.name = "LinkEndpointError";
|
|
12577
|
+
this.link = link;
|
|
12578
|
+
this.endpoint = endpoint;
|
|
12579
|
+
this.missingId = missingId;
|
|
12580
|
+
}
|
|
12581
|
+
};
|
|
12582
|
+
LinkIntegrityError = class extends NoydbError {
|
|
12583
|
+
link;
|
|
12584
|
+
endpoint;
|
|
12585
|
+
id;
|
|
12586
|
+
count;
|
|
12587
|
+
constructor(link, endpoint, id, count) {
|
|
12588
|
+
super(
|
|
12589
|
+
"LINK_INTEGRITY",
|
|
12590
|
+
`Cannot delete "${endpoint}"/"${id}": ${count} link(s) in "${link}" still reference it (onDelete: 'strict').`
|
|
12591
|
+
);
|
|
12592
|
+
this.name = "LinkIntegrityError";
|
|
12593
|
+
this.link = link;
|
|
12594
|
+
this.endpoint = endpoint;
|
|
12595
|
+
this.id = id;
|
|
12596
|
+
this.count = count;
|
|
12597
|
+
}
|
|
12598
|
+
};
|
|
12599
|
+
}
|
|
12600
|
+
});
|
|
12601
|
+
|
|
12234
12602
|
// src/periods/periods.ts
|
|
12235
12603
|
var PERIODS_COLLECTION;
|
|
12236
12604
|
var init_periods = __esm({
|
|
@@ -13906,14 +14274,17 @@ var init_read_only_facade = __esm({
|
|
|
13906
14274
|
"use strict";
|
|
13907
14275
|
ReadOnlyVaultFacade = class {
|
|
13908
14276
|
_vault;
|
|
13909
|
-
|
|
14277
|
+
_layer;
|
|
14278
|
+
constructor(vault, layer = "read") {
|
|
13910
14279
|
this._vault = vault;
|
|
14280
|
+
this._layer = layer;
|
|
13911
14281
|
}
|
|
13912
14282
|
collection(name) {
|
|
13913
14283
|
const c = this._vault.collection(name);
|
|
14284
|
+
const layer = this._layer;
|
|
13914
14285
|
return {
|
|
13915
|
-
get: (id) => c.get(id),
|
|
13916
|
-
list: () => c.list(),
|
|
14286
|
+
get: (id) => c.get(id, { _layer: layer }),
|
|
14287
|
+
list: () => c.list({ _layer: layer }),
|
|
13917
14288
|
query: () => c.query()
|
|
13918
14289
|
};
|
|
13919
14290
|
}
|
|
@@ -13969,6 +14340,16 @@ var init_registry3 = __esm({
|
|
|
13969
14340
|
if (fromExtra) fromExtra.push(reg);
|
|
13970
14341
|
else this._bySource.set(extra, [reg]);
|
|
13971
14342
|
}
|
|
14343
|
+
for (const t of spec.triggerBy ?? []) {
|
|
14344
|
+
const fromTrigger = this._bySource.get(t.collection);
|
|
14345
|
+
if (fromTrigger) fromTrigger.push(reg);
|
|
14346
|
+
else this._bySource.set(t.collection, [reg]);
|
|
14347
|
+
}
|
|
14348
|
+
if (spec.rollup) {
|
|
14349
|
+
const fromRollup = this._bySource.get(spec.rollup.from);
|
|
14350
|
+
if (fromRollup) fromRollup.push(reg);
|
|
14351
|
+
else this._bySource.set(spec.rollup.from, [reg]);
|
|
14352
|
+
}
|
|
13972
14353
|
for (const key of outputKeys) {
|
|
13973
14354
|
const output = spec.outputs[key];
|
|
13974
14355
|
if (!output) continue;
|
|
@@ -14022,6 +14403,9 @@ var init_registry3 = __esm({
|
|
|
14022
14403
|
for (const key of Object.keys(s.spec.outputs)) {
|
|
14023
14404
|
const output = s.spec.outputs[key];
|
|
14024
14405
|
if (!output) continue;
|
|
14406
|
+
if (output.shape === "record" && output.collection === s.spec.source && output.denorm !== void 0) {
|
|
14407
|
+
continue;
|
|
14408
|
+
}
|
|
14025
14409
|
visit(output.collection);
|
|
14026
14410
|
}
|
|
14027
14411
|
}
|
|
@@ -14237,6 +14621,7 @@ var init_vault = __esm({
|
|
|
14237
14621
|
init_strategy10();
|
|
14238
14622
|
init_refs();
|
|
14239
14623
|
init_dictionary();
|
|
14624
|
+
init_link_set();
|
|
14240
14625
|
init_core();
|
|
14241
14626
|
init_strategy2();
|
|
14242
14627
|
init_sync_strategy();
|
|
@@ -14329,13 +14714,17 @@ var init_vault = __esm({
|
|
|
14329
14714
|
*/
|
|
14330
14715
|
overlayedViewRegistry = null;
|
|
14331
14716
|
/**
|
|
14332
|
-
* Cached read-only
|
|
14333
|
-
* and to derivation callbacks via `derive(source, ctx)`.
|
|
14334
|
-
*
|
|
14717
|
+
* Cached read-only facades handed to guard callbacks via `ctx.vault`
|
|
14718
|
+
* and to derivation callbacks via `derive(source, ctx)`. Split by
|
|
14719
|
+
* resolution layer (#285): the guard facade reads at `layer:'guard'`,
|
|
14720
|
+
* the derivation facade at `layer:'derivation'`, so i18nText / dictKey
|
|
14721
|
+
* fields resolve under that layer's `onMissing` policy. Allocated
|
|
14722
|
+
* eagerly inside `_initGuards()` / `_initDerivations()` so read
|
|
14335
14723
|
* accessors stay synchronous (callers in `tx/transaction.ts` rely on
|
|
14336
|
-
* that).
|
|
14724
|
+
* that). Each stays `null` for vaults without that subsystem.
|
|
14337
14725
|
*/
|
|
14338
|
-
|
|
14726
|
+
guardFacade = null;
|
|
14727
|
+
derivationFacade = null;
|
|
14339
14728
|
getDEK;
|
|
14340
14729
|
/**
|
|
14341
14730
|
* Per-principal user envelope API.
|
|
@@ -14497,6 +14886,10 @@ var init_vault = __esm({
|
|
|
14497
14886
|
i18nFieldRegistry = /* @__PURE__ */ new Map();
|
|
14498
14887
|
/** Cache of DictionaryHandle instances, one per dictionary name. */
|
|
14499
14888
|
dictionaryCache = /* @__PURE__ */ new Map();
|
|
14889
|
+
/** Registered link specs (#377-B), keyed by link name; set by `vault.link()`. */
|
|
14890
|
+
linkRegistry = /* @__PURE__ */ new Map();
|
|
14891
|
+
/** Cache of LinkSet handles, one per link name. */
|
|
14892
|
+
linkSetCache = /* @__PURE__ */ new Map();
|
|
14500
14893
|
/** — subscribers for cross-tier access events. */
|
|
14501
14894
|
crossTierSubs = /* @__PURE__ */ new Set();
|
|
14502
14895
|
/** — currently-active elevation, or null. One per vault. */
|
|
@@ -14613,6 +15006,9 @@ var init_vault = __esm({
|
|
|
14613
15006
|
if (collectionName === SEQUENCE_COLLECTION) {
|
|
14614
15007
|
throw new ReservedCollectionNameError(collectionName);
|
|
14615
15008
|
}
|
|
15009
|
+
if (isLinkCollectionName(collectionName)) {
|
|
15010
|
+
throw new ReservedCollectionNameError(collectionName);
|
|
15011
|
+
}
|
|
14616
15012
|
let coll = this.collectionCache.get(collectionName);
|
|
14617
15013
|
if (coll && options?.moneyFields) {
|
|
14618
15014
|
coll._applyMoneyFields(options.moneyFields);
|
|
@@ -14684,6 +15080,7 @@ var init_vault = __esm({
|
|
|
14684
15080
|
}));
|
|
14685
15081
|
schemaUpdateGate = new SchemaUpdateGate(work);
|
|
14686
15082
|
}
|
|
15083
|
+
const effectiveHistoryConfig = options?.historyConfig ?? this.historyConfig;
|
|
14687
15084
|
const collOpts = {
|
|
14688
15085
|
adapter: this.adapter,
|
|
14689
15086
|
vault: this.name,
|
|
@@ -14699,7 +15096,7 @@ var init_vault = __esm({
|
|
|
14699
15096
|
schemaFence: this.schemaFence,
|
|
14700
15097
|
getDEK: this.getDEK,
|
|
14701
15098
|
onDirty: this.onDirty,
|
|
14702
|
-
historyConfig:
|
|
15099
|
+
historyConfig: effectiveHistoryConfig,
|
|
14703
15100
|
// thread the vault-wide blob strategy into every
|
|
14704
15101
|
// collection. `undefined` is intentionally preserved so the
|
|
14705
15102
|
// Collection constructor uses its NO_BLOBS default.
|
|
@@ -14710,7 +15107,11 @@ var init_vault = __esm({
|
|
|
14710
15107
|
historyStrategy: this.historyStrategy,
|
|
14711
15108
|
i18nStrategy: this.i18nStrategy,
|
|
14712
15109
|
syncStrategy: this.syncStrategy,
|
|
14713
|
-
ledger
|
|
15110
|
+
// Per-collection ledger opt-out (#361): when this collection sets
|
|
15111
|
+
// `historyConfig.ledger: false`, withhold the ledger reference so all
|
|
15112
|
+
// four `if (this.ledger)` append sites in Collection no-op. The chain
|
|
15113
|
+
// stays valid — it simply never receives this collection's entries.
|
|
15114
|
+
ledger: effectiveHistoryConfig.ledger === false ? void 0 : this.getLedgerOrNull() ?? void 0,
|
|
14714
15115
|
refEnforcer: this,
|
|
14715
15116
|
joinResolver: this,
|
|
14716
15117
|
defaultLocale: this.locale,
|
|
@@ -15071,6 +15472,68 @@ var init_vault = __esm({
|
|
|
15071
15472
|
}
|
|
15072
15473
|
return handle;
|
|
15073
15474
|
}
|
|
15475
|
+
/**
|
|
15476
|
+
* Declare a managed many-to-many link set (#377-B). Registers a
|
|
15477
|
+
* `_links_<name>` junction between two endpoint collections; access its
|
|
15478
|
+
* rows via `vault.links(name)`. Idempotent for an identical re-declaration;
|
|
15479
|
+
* a conflicting one throws. See {@link links}.
|
|
15480
|
+
*
|
|
15481
|
+
* ```ts
|
|
15482
|
+
* vault.link('saleLineLinks', { a: ref('saleLines'), b: ref('purchaseLines'), onDelete: 'cascade' })
|
|
15483
|
+
* ```
|
|
15484
|
+
*
|
|
15485
|
+
* `a` / `b` accept either a collection name or a `ref(target)` descriptor
|
|
15486
|
+
* (only its `target` is used — links manage their own integrity). `onDelete`
|
|
15487
|
+
* governs what happens to link rows when an endpoint record is deleted
|
|
15488
|
+
* (`'cascade'` default, `'strict'`, `'warn'`).
|
|
15489
|
+
*/
|
|
15490
|
+
link(name, spec) {
|
|
15491
|
+
const a = typeof spec.a === "string" ? spec.a : spec.a.target;
|
|
15492
|
+
const b = typeof spec.b === "string" ? spec.b : spec.b.target;
|
|
15493
|
+
for (const [slot, target] of [["a", a], ["b", b]]) {
|
|
15494
|
+
if (!target || target.startsWith("_") || target.includes("/")) {
|
|
15495
|
+
throw new ValidationError(
|
|
15496
|
+
`vault.link("${name}"): endpoint "${slot}" must be a simple collection name, got "${target}".`
|
|
15497
|
+
);
|
|
15498
|
+
}
|
|
15499
|
+
}
|
|
15500
|
+
const resolved = { a, b, ...spec.onDelete ? { onDelete: spec.onDelete } : {} };
|
|
15501
|
+
const existing = this.linkRegistry.get(name);
|
|
15502
|
+
if (existing) {
|
|
15503
|
+
if (existing.a !== resolved.a || existing.b !== resolved.b || (existing.onDelete ?? "cascade") !== (resolved.onDelete ?? "cascade")) {
|
|
15504
|
+
throw new ValidationError(`vault.link("${name}"): conflicting re-declaration.`);
|
|
15505
|
+
}
|
|
15506
|
+
return;
|
|
15507
|
+
}
|
|
15508
|
+
this.linkRegistry.set(name, resolved);
|
|
15509
|
+
}
|
|
15510
|
+
/**
|
|
15511
|
+
* Access a declared link set (#377-B). Throws if `name` was not first
|
|
15512
|
+
* declared via {@link link}. Returns a cached {@link LinkSetHandle}:
|
|
15513
|
+
* `connect(a, b, meta?)`, `disconnect(a, b)`, `has(a, b)`, `of(id)`, `list()`.
|
|
15514
|
+
*/
|
|
15515
|
+
links(name) {
|
|
15516
|
+
let handle = this.linkSetCache.get(name);
|
|
15517
|
+
if (!handle) {
|
|
15518
|
+
const spec = this.linkRegistry.get(name);
|
|
15519
|
+
if (!spec) {
|
|
15520
|
+
throw new ValidationError(`vault.links("${name}"): not declared. Call vault.link("${name}", { a, b }) first.`);
|
|
15521
|
+
}
|
|
15522
|
+
handle = new LinkSet(
|
|
15523
|
+
this.adapter,
|
|
15524
|
+
this.name,
|
|
15525
|
+
name,
|
|
15526
|
+
spec,
|
|
15527
|
+
this.encrypted,
|
|
15528
|
+
this.getDEK,
|
|
15529
|
+
this.keyring.userId,
|
|
15530
|
+
this.emitter,
|
|
15531
|
+
async (collection, id) => await this.collection(collection).get(id) !== null
|
|
15532
|
+
);
|
|
15533
|
+
this.linkSetCache.set(name, handle);
|
|
15534
|
+
}
|
|
15535
|
+
return handle;
|
|
15536
|
+
}
|
|
15074
15537
|
/**
|
|
15075
15538
|
* Build a `JoinableSource` for a dictKey field, for use in dict joins
|
|
15076
15539
|
*. Returns a source whose snapshot contains `{ key, ...labels }`
|
|
@@ -15230,65 +15693,16 @@ var init_vault = __esm({
|
|
|
15230
15693
|
});
|
|
15231
15694
|
}
|
|
15232
15695
|
}
|
|
15233
|
-
/**
|
|
15234
|
-
* Bulk blob extraction primitive.
|
|
15235
|
-
*
|
|
15236
|
-
* Returns an async-iterable handle over every blob attached to
|
|
15237
|
-
* records in the vault. Single capability check (`plaintext/blob`)
|
|
15238
|
-
* at handle creation; single audit entry to `_export_audit` before
|
|
15239
|
-
* the first yield. Per-blob decryption happens lazily as the
|
|
15240
|
-
* consumer pulls tuples.
|
|
15241
|
-
*
|
|
15242
|
-
* ```ts
|
|
15243
|
-
* const handle = vault.exportBlobs({
|
|
15244
|
-
* collections: ['invoiceScans'],
|
|
15245
|
-
* where: (rec) => (rec as { clientId?: string }).clientId === 'c-123',
|
|
15246
|
-
* })
|
|
15247
|
-
* for await (const { bytes, meta, recordRef } of handle) {
|
|
15248
|
-
* await uploadToColdStorage(bytes, recordRef)
|
|
15249
|
-
* }
|
|
15250
|
-
* ```
|
|
15251
|
-
*
|
|
15252
|
-
* @see `@noy-db/hub/store/export-blobs` for the full option surface.
|
|
15253
|
-
*/
|
|
15254
|
-
/**
|
|
15255
|
-
* Evict blob slots per the per-collection `blobFields` retention
|
|
15256
|
-
* policy.
|
|
15257
|
-
*
|
|
15258
|
-
* Iterates every collection declared with `{ blobFields: {...} }`.
|
|
15259
|
-
* For each record, checks every configured slot against its
|
|
15260
|
-
* policy — `retainDays` (age-based TTL) and/or `evictWhen(record)`
|
|
15261
|
-
* (predicate) — and evicts matching slots. Every eviction writes
|
|
15262
|
-
* one entry to `_blob_eviction_audit` (actor + eTag + reason +
|
|
15263
|
-
* timestamp, no plaintext). Consumer-scheduled; noy-db never runs
|
|
15264
|
-
* this on its own.
|
|
15265
|
-
*
|
|
15266
|
-
* ```ts
|
|
15267
|
-
* await vault.compact() // run full pass
|
|
15268
|
-
* await vault.compact({ dryRun: true }) // preview counts
|
|
15269
|
-
* await vault.compact({ maxEvictions: 1000 }) // cap batch
|
|
15270
|
-
* ```
|
|
15271
|
-
*/
|
|
15272
|
-
/**
|
|
15273
|
-
* Atomic, gap-free numbering. `vault.sequence('invoice-2026').next()`
|
|
15274
|
-
* returns 1, 2, 3, … with no gaps or duplicates under concurrency, via
|
|
15275
|
-
* an optimistic-CAS counter at `_sequences/<name>`. Each name is an
|
|
15276
|
-
* independent sequence.
|
|
15277
|
-
*
|
|
15278
|
-
* **Online-only:** `next()` throws `SequenceOfflineError` unless the
|
|
15279
|
-
* store advertises `capabilities.casAtomic` — gap-free numbering cannot
|
|
15280
|
-
* be serialized by an offline / non-CAS writer.
|
|
15281
|
-
*
|
|
15282
|
-
* ```ts
|
|
15283
|
-
* const n = await vault.sequence('invoice-2026').next() // 1, then 2, …
|
|
15284
|
-
* const cur = await vault.sequence('invoice-2026').peek() // current value, no allocation
|
|
15285
|
-
* ```
|
|
15286
|
-
*/
|
|
15287
15696
|
sequence(series, opts) {
|
|
15288
15697
|
if (series.includes("\0")) {
|
|
15289
15698
|
throw new ValidationError(`sequence("${series}"): series name must not contain a null byte (\\x00).`);
|
|
15290
15699
|
}
|
|
15291
15700
|
if (this.numberingConfigs.has(series)) {
|
|
15701
|
+
if (opts?.format !== void 0) {
|
|
15702
|
+
throw new ValidationError(
|
|
15703
|
+
`sequence("${series}") is a deferred-numbering series; the format option applies to CAS sequences only.`
|
|
15704
|
+
);
|
|
15705
|
+
}
|
|
15292
15706
|
const eng = this.deferred();
|
|
15293
15707
|
return {
|
|
15294
15708
|
next: async (nextOpts) => {
|
|
@@ -15312,7 +15726,17 @@ var init_vault = __esm({
|
|
|
15312
15726
|
actor: this.keyring.userId
|
|
15313
15727
|
});
|
|
15314
15728
|
}
|
|
15315
|
-
|
|
15729
|
+
const handle = this.sequenceStore.handle(resolveSequenceKey(series, opts));
|
|
15730
|
+
if (opts?.format === void 0) return handle;
|
|
15731
|
+
const render = compileSequenceFormat(opts.format, series, opts.partition);
|
|
15732
|
+
return {
|
|
15733
|
+
next: async (nextOpts) => {
|
|
15734
|
+
const serial = await handle.next(nextOpts);
|
|
15735
|
+
return { serial, formatted: render(serial) };
|
|
15736
|
+
},
|
|
15737
|
+
peek: () => handle.peek(),
|
|
15738
|
+
seedTo: (n) => handle.seedTo(n)
|
|
15739
|
+
};
|
|
15316
15740
|
}
|
|
15317
15741
|
/** @internal — lazily build the deferred-numbering engine with a cache-coherent stamp. */
|
|
15318
15742
|
deferred() {
|
|
@@ -15539,6 +15963,43 @@ var init_vault = __esm({
|
|
|
15539
15963
|
if (descriptor.mode !== "strict") continue;
|
|
15540
15964
|
const rawId = obj[field];
|
|
15541
15965
|
if (rawId === null || rawId === void 0) continue;
|
|
15966
|
+
if (isRefArray(descriptor)) {
|
|
15967
|
+
if (!Array.isArray(rawId)) {
|
|
15968
|
+
throw new RefIntegrityError({
|
|
15969
|
+
collection: collectionName,
|
|
15970
|
+
id: obj["id"] ?? "<unknown>",
|
|
15971
|
+
field,
|
|
15972
|
+
refTo: descriptor.target,
|
|
15973
|
+
refId: null,
|
|
15974
|
+
message: `Array ref field "${collectionName}.${field}" must be an array, got ${typeof rawId}.`
|
|
15975
|
+
});
|
|
15976
|
+
}
|
|
15977
|
+
const arrTarget = this.collection(descriptor.target);
|
|
15978
|
+
for (const el of rawId) {
|
|
15979
|
+
if (typeof el !== "string" && typeof el !== "number") {
|
|
15980
|
+
throw new RefIntegrityError({
|
|
15981
|
+
collection: collectionName,
|
|
15982
|
+
id: obj["id"] ?? "<unknown>",
|
|
15983
|
+
field,
|
|
15984
|
+
refTo: descriptor.target,
|
|
15985
|
+
refId: null,
|
|
15986
|
+
message: `Array ref "${collectionName}.${field}" elements must be strings or numbers, got ${typeof el}.`
|
|
15987
|
+
});
|
|
15988
|
+
}
|
|
15989
|
+
const elId = String(el);
|
|
15990
|
+
if (!await arrTarget.get(elId)) {
|
|
15991
|
+
throw new RefIntegrityError({
|
|
15992
|
+
collection: collectionName,
|
|
15993
|
+
id: obj["id"] ?? "<unknown>",
|
|
15994
|
+
field,
|
|
15995
|
+
refTo: descriptor.target,
|
|
15996
|
+
refId: elId,
|
|
15997
|
+
message: `Strict array ref "${collectionName}.${field}" \u2192 "${descriptor.target}" cannot be satisfied: element id "${elId}" not found in "${descriptor.target}".`
|
|
15998
|
+
});
|
|
15999
|
+
}
|
|
16000
|
+
}
|
|
16001
|
+
continue;
|
|
16002
|
+
}
|
|
15542
16003
|
if (typeof rawId !== "string" && typeof rawId !== "number") {
|
|
15543
16004
|
throw new RefIntegrityError({
|
|
15544
16005
|
collection: collectionName,
|
|
@@ -15588,6 +16049,11 @@ var init_vault = __esm({
|
|
|
15588
16049
|
const allRecords = await fromCollection.list();
|
|
15589
16050
|
const matches = allRecords.filter((rec) => {
|
|
15590
16051
|
const raw = rec[rule.field];
|
|
16052
|
+
if (rule.isArray) {
|
|
16053
|
+
return Array.isArray(raw) && raw.some(
|
|
16054
|
+
(el) => (typeof el === "string" || typeof el === "number") && String(el) === id
|
|
16055
|
+
);
|
|
16056
|
+
}
|
|
15591
16057
|
if (typeof raw !== "string" && typeof raw !== "number") return false;
|
|
15592
16058
|
return String(raw) === id;
|
|
15593
16059
|
});
|
|
@@ -15626,10 +16092,45 @@ var init_vault = __esm({
|
|
|
15626
16092
|
}
|
|
15627
16093
|
}
|
|
15628
16094
|
}
|
|
16095
|
+
await this.enforceLinksOnDelete(collectionName, id);
|
|
15629
16096
|
} finally {
|
|
15630
16097
|
this.cascadeInProgress.delete(key);
|
|
15631
16098
|
}
|
|
15632
16099
|
}
|
|
16100
|
+
/**
|
|
16101
|
+
* @internal — apply link `onDelete` policy when an endpoint record is
|
|
16102
|
+
* deleted (#377-B). `'strict'` throws (blocks the delete), `'cascade'`
|
|
16103
|
+
* removes the touching link rows (tx-atomic when a transaction is active),
|
|
16104
|
+
* `'warn'` leaves orphans for `checkIntegrity()`.
|
|
16105
|
+
*/
|
|
16106
|
+
async enforceLinksOnDelete(collectionName, id) {
|
|
16107
|
+
for (const [name, spec] of this.linkRegistry) {
|
|
16108
|
+
if (spec.a !== collectionName && spec.b !== collectionName) continue;
|
|
16109
|
+
const handle = this.links(name);
|
|
16110
|
+
const touching = await handle._rowsTouchingEndpoint(collectionName, id);
|
|
16111
|
+
if (touching.length === 0) continue;
|
|
16112
|
+
const mode = spec.onDelete ?? "cascade";
|
|
16113
|
+
if (mode === "warn") continue;
|
|
16114
|
+
if (mode === "strict") {
|
|
16115
|
+
throw new LinkIntegrityError(name, collectionName, id, touching.length);
|
|
16116
|
+
}
|
|
16117
|
+
const linkColl = handle._collectionName;
|
|
16118
|
+
const txCtx = this.noydb._activeTxContextOrNull;
|
|
16119
|
+
for (const row of touching) {
|
|
16120
|
+
const rowKey = linkRowKey(row.a, row.b);
|
|
16121
|
+
if (txCtx !== null) {
|
|
16122
|
+
const prior = await this.adapter.get(this.name, linkColl, rowKey);
|
|
16123
|
+
if (prior !== null) {
|
|
16124
|
+
txCtx._executed.push({
|
|
16125
|
+
op: { type: "delete", vaultName: this.name, collectionName: linkColl, id: rowKey },
|
|
16126
|
+
priorEnvelope: prior
|
|
16127
|
+
});
|
|
16128
|
+
}
|
|
16129
|
+
}
|
|
16130
|
+
await handle.disconnect(row.a, row.b);
|
|
16131
|
+
}
|
|
16132
|
+
}
|
|
16133
|
+
}
|
|
15633
16134
|
// ─── Join resolver) ────────────────────
|
|
15634
16135
|
/**
|
|
15635
16136
|
* Look up the `RefDescriptor` the left collection declared for a
|
|
@@ -15690,6 +16191,23 @@ var init_vault = __esm({
|
|
|
15690
16191
|
for (const [field, descriptor] of Object.entries(refs)) {
|
|
15691
16192
|
const rawId = record[field];
|
|
15692
16193
|
if (rawId === null || rawId === void 0) continue;
|
|
16194
|
+
const target = this.collection(descriptor.target);
|
|
16195
|
+
if (isRefArray(descriptor)) {
|
|
16196
|
+
if (!Array.isArray(rawId)) {
|
|
16197
|
+
violations.push({ collection: collectionName, id: recId, field, refTo: descriptor.target, refId: rawId, mode: descriptor.mode });
|
|
16198
|
+
continue;
|
|
16199
|
+
}
|
|
16200
|
+
for (const el of rawId) {
|
|
16201
|
+
if (typeof el !== "string" && typeof el !== "number") {
|
|
16202
|
+
violations.push({ collection: collectionName, id: recId, field, refTo: descriptor.target, refId: el, mode: descriptor.mode });
|
|
16203
|
+
continue;
|
|
16204
|
+
}
|
|
16205
|
+
if (!await target.get(String(el))) {
|
|
16206
|
+
violations.push({ collection: collectionName, id: recId, field, refTo: descriptor.target, refId: el, mode: descriptor.mode });
|
|
16207
|
+
}
|
|
16208
|
+
}
|
|
16209
|
+
continue;
|
|
16210
|
+
}
|
|
15693
16211
|
if (typeof rawId !== "string" && typeof rawId !== "number") {
|
|
15694
16212
|
violations.push({
|
|
15695
16213
|
collection: collectionName,
|
|
@@ -15702,7 +16220,6 @@ var init_vault = __esm({
|
|
|
15702
16220
|
continue;
|
|
15703
16221
|
}
|
|
15704
16222
|
const refId = String(rawId);
|
|
15705
|
-
const target = this.collection(descriptor.target);
|
|
15706
16223
|
const exists = await target.get(refId);
|
|
15707
16224
|
if (!exists) {
|
|
15708
16225
|
violations.push({
|
|
@@ -15717,6 +16234,19 @@ var init_vault = __esm({
|
|
|
15717
16234
|
}
|
|
15718
16235
|
}
|
|
15719
16236
|
}
|
|
16237
|
+
for (const [name, spec] of this.linkRegistry) {
|
|
16238
|
+
const linkColl = linkCollectionName(name);
|
|
16239
|
+
const rows = await this.links(name).list();
|
|
16240
|
+
for (const row of rows) {
|
|
16241
|
+
const rowKey = linkRowKey(row.a, row.b);
|
|
16242
|
+
if (await this.collection(spec.a).get(row.a) === null) {
|
|
16243
|
+
violations.push({ collection: linkColl, id: rowKey, field: "a", refTo: spec.a, refId: row.a, mode: spec.onDelete ?? "cascade" });
|
|
16244
|
+
}
|
|
16245
|
+
if (await this.collection(spec.b).get(row.b) === null) {
|
|
16246
|
+
violations.push({ collection: linkColl, id: rowKey, field: "b", refTo: spec.b, refId: row.b, mode: spec.onDelete ?? "cascade" });
|
|
16247
|
+
}
|
|
16248
|
+
}
|
|
16249
|
+
}
|
|
15720
16250
|
return { violations };
|
|
15721
16251
|
}
|
|
15722
16252
|
/**
|
|
@@ -15992,7 +16522,7 @@ var init_vault = __esm({
|
|
|
15992
16522
|
const registry = new GuardRegistry2();
|
|
15993
16523
|
for (const h of handles) registry.register(h.spec);
|
|
15994
16524
|
this.guardRegistry = registry;
|
|
15995
|
-
this.
|
|
16525
|
+
this.guardFacade = new ReadOnlyVaultFacade2(this, "guard");
|
|
15996
16526
|
}
|
|
15997
16527
|
/**
|
|
15998
16528
|
* @internal — The gate handler in Noydb.#registerGuardGate calls into
|
|
@@ -16023,8 +16553,8 @@ var init_vault = __esm({
|
|
|
16023
16553
|
}
|
|
16024
16554
|
registry.validate();
|
|
16025
16555
|
this.derivationRegistry = registry;
|
|
16026
|
-
if (this.
|
|
16027
|
-
this.
|
|
16556
|
+
if (this.derivationFacade === null) {
|
|
16557
|
+
this.derivationFacade = new ReadOnlyVaultFacade2(this, "derivation");
|
|
16028
16558
|
}
|
|
16029
16559
|
}
|
|
16030
16560
|
/**
|
|
@@ -16141,7 +16671,7 @@ var init_vault = __esm({
|
|
|
16141
16671
|
const { DerivationExecutor: DerivationExecutor2 } = await Promise.resolve().then(() => (init_executor(), executor_exports));
|
|
16142
16672
|
const sourceColl = this.collection(sourceCollection);
|
|
16143
16673
|
const records = await sourceColl.list();
|
|
16144
|
-
const ctx = { vault: this.
|
|
16674
|
+
const ctx = { vault: this.derivationFacade ?? new (await Promise.resolve().then(() => (init_read_only_facade(), read_only_facade_exports))).ReadOnlyVaultFacade(this, "derivation") };
|
|
16145
16675
|
let derived = 0;
|
|
16146
16676
|
let failed = 0;
|
|
16147
16677
|
for (const record of records) {
|
|
@@ -16206,17 +16736,18 @@ var init_vault = __esm({
|
|
|
16206
16736
|
* never see null).
|
|
16207
16737
|
*/
|
|
16208
16738
|
_getReadOnlyFacade() {
|
|
16209
|
-
return this.
|
|
16739
|
+
return this.guardFacade;
|
|
16210
16740
|
}
|
|
16211
16741
|
/**
|
|
16212
|
-
* Internal lazy-allocator for the read-only facade
|
|
16213
|
-
* defensive fallback; in practice
|
|
16214
|
-
* instantiates this, so the lazy path is
|
|
16742
|
+
* Internal lazy-allocator for the derivation read-only facade
|
|
16743
|
+
* (`layer:'derivation'`). Used as a defensive fallback; in practice
|
|
16744
|
+
* `_initDerivations()` eagerly instantiates this, so the lazy path is
|
|
16745
|
+
* a no-op.
|
|
16215
16746
|
*/
|
|
16216
16747
|
_ensureReadOnlyFacade() {
|
|
16217
|
-
if (this.
|
|
16748
|
+
if (this.derivationFacade !== null) return this.derivationFacade;
|
|
16218
16749
|
throw new Error(
|
|
16219
|
-
"Vault:
|
|
16750
|
+
"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."
|
|
16220
16751
|
);
|
|
16221
16752
|
}
|
|
16222
16753
|
/**
|
|
@@ -18615,17 +19146,188 @@ var init_executor3 = __esm({
|
|
|
18615
19146
|
}
|
|
18616
19147
|
});
|
|
18617
19148
|
|
|
18618
|
-
// src/federation/
|
|
18619
|
-
function
|
|
18620
|
-
|
|
18621
|
-
|
|
18622
|
-
|
|
18623
|
-
|
|
18624
|
-
|
|
18625
|
-
|
|
18626
|
-
|
|
18627
|
-
|
|
18628
|
-
|
|
19149
|
+
// src/federation/schema-manifest.ts
|
|
19150
|
+
function captureBlueprint(configure) {
|
|
19151
|
+
const recorded = [];
|
|
19152
|
+
const collectionStub = new Proxy(
|
|
19153
|
+
{},
|
|
19154
|
+
{
|
|
19155
|
+
get: () => () => collectionStub
|
|
19156
|
+
}
|
|
19157
|
+
);
|
|
19158
|
+
const proxy = new Proxy(
|
|
19159
|
+
{},
|
|
19160
|
+
{
|
|
19161
|
+
get: (_t, prop) => {
|
|
19162
|
+
if (prop === "collection") {
|
|
19163
|
+
return (name, opts) => {
|
|
19164
|
+
recorded.push({
|
|
19165
|
+
name,
|
|
19166
|
+
indexes: opts?.indexes ?? [],
|
|
19167
|
+
persistJsonSchema: !!opts?.persistJsonSchema
|
|
19168
|
+
});
|
|
19169
|
+
return collectionStub;
|
|
19170
|
+
};
|
|
19171
|
+
}
|
|
19172
|
+
return () => proxy;
|
|
19173
|
+
}
|
|
19174
|
+
}
|
|
19175
|
+
);
|
|
19176
|
+
configure(proxy);
|
|
19177
|
+
const sorted = [...recorded].sort((a, b) => a.name.localeCompare(b.name));
|
|
19178
|
+
const indexes = {};
|
|
19179
|
+
const persistJsonSchema = [];
|
|
19180
|
+
for (const c of sorted) {
|
|
19181
|
+
indexes[c.name] = c.indexes;
|
|
19182
|
+
if (c.persistJsonSchema) persistJsonSchema.push(c.name);
|
|
19183
|
+
}
|
|
19184
|
+
return {
|
|
19185
|
+
// `persistJsonSchema` is already name-sorted: it is populated while
|
|
19186
|
+
// iterating `sorted` (collections in name order).
|
|
19187
|
+
collections: sorted.map((c) => c.name),
|
|
19188
|
+
indexes,
|
|
19189
|
+
persistJsonSchema
|
|
19190
|
+
};
|
|
19191
|
+
}
|
|
19192
|
+
function canonical(value) {
|
|
19193
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
19194
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
19195
|
+
const obj = value;
|
|
19196
|
+
const keys = Object.keys(obj).sort();
|
|
19197
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonical(obj[k])}`).join(",")}}`;
|
|
19198
|
+
}
|
|
19199
|
+
async function fingerprintBlueprint(bp) {
|
|
19200
|
+
return sha256Hex2(new TextEncoder().encode(canonical(bp)));
|
|
19201
|
+
}
|
|
19202
|
+
var init_schema_manifest = __esm({
|
|
19203
|
+
"src/federation/schema-manifest.ts"() {
|
|
19204
|
+
"use strict";
|
|
19205
|
+
init_crypto();
|
|
19206
|
+
}
|
|
19207
|
+
});
|
|
19208
|
+
|
|
19209
|
+
// src/federation/state-vault.ts
|
|
19210
|
+
var state_vault_exports = {};
|
|
19211
|
+
__export(state_vault_exports, {
|
|
19212
|
+
STATE_VAULT_NAME: () => STATE_VAULT_NAME,
|
|
19213
|
+
StateManagementVault: () => StateManagementVault
|
|
19214
|
+
});
|
|
19215
|
+
var REGISTRY, MANIFEST, EVENTS, MIGRATION_STATUS, StateManagementVault;
|
|
19216
|
+
var init_state_vault = __esm({
|
|
19217
|
+
"src/federation/state-vault.ts"() {
|
|
19218
|
+
"use strict";
|
|
19219
|
+
init_schema_manifest();
|
|
19220
|
+
init_constants2();
|
|
19221
|
+
init_ulid();
|
|
19222
|
+
init_constants2();
|
|
19223
|
+
REGISTRY = "vaultRegistry";
|
|
19224
|
+
MANIFEST = "schemaManifest";
|
|
19225
|
+
EVENTS = "deploymentEvents";
|
|
19226
|
+
MIGRATION_STATUS = "migrationStatus";
|
|
19227
|
+
StateManagementVault = class _StateManagementVault {
|
|
19228
|
+
constructor(registry, schemaManifest, events, migrationStatus) {
|
|
19229
|
+
this.registry = registry;
|
|
19230
|
+
this.schemaManifest = schemaManifest;
|
|
19231
|
+
this.#events = events;
|
|
19232
|
+
this.#migrationStatus = migrationStatus;
|
|
19233
|
+
}
|
|
19234
|
+
registry;
|
|
19235
|
+
schemaManifest;
|
|
19236
|
+
/**
|
|
19237
|
+
* The append-only deployment-events log is kept truly private so the raw
|
|
19238
|
+
* mutable Collection is never surfaced — events may only be written via
|
|
19239
|
+
* `appendEvent` and read via `queryEvents`. (`registry` and
|
|
19240
|
+
* `schemaManifest` are deliberately public: consumers read and write them.)
|
|
19241
|
+
*/
|
|
19242
|
+
#events;
|
|
19243
|
+
/** Per-shard fleet-migration progress (#271). Surfaced via typed methods only. */
|
|
19244
|
+
#migrationStatus;
|
|
19245
|
+
/** Idempotently open the reserved state vault and bind the control-plane collections. */
|
|
19246
|
+
static async open(db) {
|
|
19247
|
+
const vault = await db.openVault(STATE_VAULT_NAME);
|
|
19248
|
+
return new _StateManagementVault(
|
|
19249
|
+
vault.collection(REGISTRY),
|
|
19250
|
+
vault.collection(MANIFEST),
|
|
19251
|
+
vault.collection(EVENTS),
|
|
19252
|
+
vault.collection(MIGRATION_STATUS)
|
|
19253
|
+
);
|
|
19254
|
+
}
|
|
19255
|
+
/** Read one shard's migration status (or null). */
|
|
19256
|
+
async getMigrationStatus(vaultId) {
|
|
19257
|
+
return this.#migrationStatus.get(vaultId);
|
|
19258
|
+
}
|
|
19259
|
+
/** All migration-status rows (hydrates first). */
|
|
19260
|
+
async listMigrationStatus() {
|
|
19261
|
+
await this.#migrationStatus.list();
|
|
19262
|
+
return this.#migrationStatus.query().toArray();
|
|
19263
|
+
}
|
|
19264
|
+
/** Upsert one shard's migration status (keyed by vaultId). */
|
|
19265
|
+
async upsertMigrationStatus(row) {
|
|
19266
|
+
await this.#migrationStatus.put(row.vaultId, row);
|
|
19267
|
+
}
|
|
19268
|
+
/** Read-only query over the append-only deployment-events log. */
|
|
19269
|
+
queryEvents() {
|
|
19270
|
+
return this.#events.query();
|
|
19271
|
+
}
|
|
19272
|
+
/**
|
|
19273
|
+
* Append a deployment event with a fresh unique (ULID) id. This is the
|
|
19274
|
+
* only write path to the events log; no update/delete is exposed.
|
|
19275
|
+
* Callers should treat failures as non-fatal — this method does not
|
|
19276
|
+
* swallow errors, so wrap the call site in try/catch where appropriate.
|
|
19277
|
+
*/
|
|
19278
|
+
async appendEvent(event) {
|
|
19279
|
+
const ts = event.ts ?? Date.now();
|
|
19280
|
+
const id = generateULID();
|
|
19281
|
+
await this.#events.put(id, { ...event, id, ts });
|
|
19282
|
+
}
|
|
19283
|
+
/**
|
|
19284
|
+
* Ensure a manifest row exists for `(templateName, template.version)`.
|
|
19285
|
+
* Safe to call repeatedly: the `fingerprint` is a deterministic hash of
|
|
19286
|
+
* the template's declared shape (stable across calls), though each call
|
|
19287
|
+
* refreshes `recordedAt`.
|
|
19288
|
+
*/
|
|
19289
|
+
async recordManifest(templateName, template) {
|
|
19290
|
+
const bp = captureBlueprint(template.configure);
|
|
19291
|
+
const fingerprint = await fingerprintBlueprint(bp);
|
|
19292
|
+
await this.schemaManifest.put(`${templateName}:${template.version}`, {
|
|
19293
|
+
templateName,
|
|
19294
|
+
version: template.version,
|
|
19295
|
+
collections: bp.collections,
|
|
19296
|
+
indexes: bp.indexes,
|
|
19297
|
+
persistJsonSchema: bp.persistJsonSchema,
|
|
19298
|
+
fingerprint,
|
|
19299
|
+
recordedAt: Date.now()
|
|
19300
|
+
});
|
|
19301
|
+
return fingerprint;
|
|
19302
|
+
}
|
|
19303
|
+
/**
|
|
19304
|
+
* True when `template`'s current declared shape does not match the recorded
|
|
19305
|
+
* manifest for `(templateName, template.version)`. Because shards carry no
|
|
19306
|
+
* schema state independent of their template, this catches "a template's
|
|
19307
|
+
* shape changed without bumping `version`" — not independent per-shard drift.
|
|
19308
|
+
* A missing manifest is treated as drift (nothing to verify against).
|
|
19309
|
+
*/
|
|
19310
|
+
async detectDrift(templateName, template) {
|
|
19311
|
+
const row = await this.schemaManifest.get(`${templateName}:${template.version}`);
|
|
19312
|
+
if (!row) return true;
|
|
19313
|
+
const current = await fingerprintBlueprint(captureBlueprint(template.configure));
|
|
19314
|
+
return current !== row.fingerprint;
|
|
19315
|
+
}
|
|
19316
|
+
};
|
|
19317
|
+
}
|
|
19318
|
+
});
|
|
19319
|
+
|
|
19320
|
+
// src/federation/classify-skip.ts
|
|
19321
|
+
function classifyShardSkip(err) {
|
|
19322
|
+
return err instanceof NoAccessError ? "no-grant" : "error";
|
|
19323
|
+
}
|
|
19324
|
+
var init_classify_skip = __esm({
|
|
19325
|
+
"src/federation/classify-skip.ts"() {
|
|
19326
|
+
"use strict";
|
|
19327
|
+
init_errors();
|
|
19328
|
+
}
|
|
19329
|
+
});
|
|
19330
|
+
|
|
18629
19331
|
// src/federation/cross-shard-join.ts
|
|
18630
19332
|
function coerceKey(value) {
|
|
18631
19333
|
if (value === null || value === void 0) return null;
|
|
@@ -18899,6 +19601,7 @@ var SHARD_SEPARATOR, SAFE_PARTITION_KEY, VaultGroup, ShardedCollection, ShardedQ
|
|
|
18899
19601
|
var init_vault_group = __esm({
|
|
18900
19602
|
"src/federation/vault-group.ts"() {
|
|
18901
19603
|
"use strict";
|
|
19604
|
+
init_state_vault();
|
|
18902
19605
|
init_errors();
|
|
18903
19606
|
init_constants2();
|
|
18904
19607
|
init_classify_skip();
|
|
@@ -18908,12 +19611,13 @@ var init_vault_group = __esm({
|
|
|
18908
19611
|
SHARD_SEPARATOR = "--";
|
|
18909
19612
|
SAFE_PARTITION_KEY = /^[A-Za-z0-9._-]+$/;
|
|
18910
19613
|
VaultGroup = class {
|
|
18911
|
-
constructor(db, name, registry, sharding, template) {
|
|
19614
|
+
constructor(db, name, registry, sharding, template, migrateOnOpen = false) {
|
|
18912
19615
|
this.db = db;
|
|
18913
19616
|
this.name = name;
|
|
18914
19617
|
this.registry = registry;
|
|
18915
19618
|
this.sharding = sharding;
|
|
18916
19619
|
this.template = template;
|
|
19620
|
+
this.migrateOnOpen = migrateOnOpen;
|
|
18917
19621
|
if (name.includes(SHARD_SEPARATOR)) {
|
|
18918
19622
|
throw new ValidationError(
|
|
18919
19623
|
`VaultGroup name "${name}" must not contain "--" (reserved shard vault-id separator).`
|
|
@@ -18925,6 +19629,7 @@ var init_vault_group = __esm({
|
|
|
18925
19629
|
registry;
|
|
18926
19630
|
sharding;
|
|
18927
19631
|
template;
|
|
19632
|
+
migrateOnOpen;
|
|
18928
19633
|
/** @internal — set when the group is managed (no explicit registry). */
|
|
18929
19634
|
stateVault;
|
|
18930
19635
|
/** @internal */
|
|
@@ -18958,8 +19663,22 @@ var init_vault_group = __esm({
|
|
|
18958
19663
|
const rows = this.registry.query().toArray();
|
|
18959
19664
|
return rows.filter((r) => r.group === this.name);
|
|
18960
19665
|
}
|
|
18961
|
-
/**
|
|
19666
|
+
/**
|
|
19667
|
+
* Open an existing shard and apply the template. When `migrateOnOpen` is set
|
|
19668
|
+
* (#271) and the shard's registry version is behind the template, its cutover
|
|
19669
|
+
* runs inline first — so a behind shard never surfaces a stale handle.
|
|
19670
|
+
*/
|
|
18962
19671
|
async openShard(partitionKey) {
|
|
19672
|
+
if (this.migrateOnOpen) {
|
|
19673
|
+
const row = await this.registry.get(this.registryId(partitionKey));
|
|
19674
|
+
if (row && row.schemaVersion < this.template.version) {
|
|
19675
|
+
await this.migrateShard(partitionKey);
|
|
19676
|
+
}
|
|
19677
|
+
}
|
|
19678
|
+
return this._openShardRaw(partitionKey);
|
|
19679
|
+
}
|
|
19680
|
+
/** @internal — open + configure with no migrate-on-open hook (used by the migration path itself to avoid recursion). */
|
|
19681
|
+
async _openShardRaw(partitionKey) {
|
|
18963
19682
|
const vault = await this.db.openVault(this.shardVaultId(partitionKey), { create: false });
|
|
18964
19683
|
this.template.configure(vault);
|
|
18965
19684
|
return vault;
|
|
@@ -19037,6 +19756,161 @@ var init_vault_group = __esm({
|
|
|
19037
19756
|
});
|
|
19038
19757
|
return { eligible, skipped };
|
|
19039
19758
|
}
|
|
19759
|
+
/** @internal — registered push-model cross-vault derivations (#271 Insight Vault). */
|
|
19760
|
+
crossVaultDerivations = [];
|
|
19761
|
+
/**
|
|
19762
|
+
* Register a push-model cross-vault derivation — the Insight Vault pattern
|
|
19763
|
+
* (#271, Layer 4). Drive it with {@link refreshInsights}.
|
|
19764
|
+
*
|
|
19765
|
+
* For each shard, `derive(records, ctx)` runs on that shard's `source`
|
|
19766
|
+
* records and its return value is written into the analytics
|
|
19767
|
+
* (`target.vault` / `target.collection`) vault, keyed by partition key —
|
|
19768
|
+
* one summary row per shard. The derivation runs in-process under THIS
|
|
19769
|
+
* group's `Noydb` (which already holds both the shard and Insight Vault
|
|
19770
|
+
* keyrings); the shard's decrypted records are reduced to a summary that is
|
|
19771
|
+
* re-encrypted under the Insight Vault's own DEK, so no shard ciphertext
|
|
19772
|
+
* crosses a DEK boundary.
|
|
19773
|
+
*
|
|
19774
|
+
* **Zero-knowledge note:** the Insight Vault backend sees aggregated
|
|
19775
|
+
* structure (totals, counts, timestamps) drawn from many shards — a weaker
|
|
19776
|
+
* ZK profile than the per-shard vaults. Opt-in; keep summaries to aggregate
|
|
19777
|
+
* scalars (no embeddings / no raw records).
|
|
19778
|
+
*
|
|
19779
|
+
* v1 is explicit-refresh (no write-path push); call `refreshInsights()`
|
|
19780
|
+
* after a batch of writes, or on a schedule.
|
|
19781
|
+
*/
|
|
19782
|
+
withCrossVaultDerivation(spec) {
|
|
19783
|
+
this.crossVaultDerivations.push(spec);
|
|
19784
|
+
}
|
|
19785
|
+
/**
|
|
19786
|
+
* Run every registered {@link withCrossVaultDerivation}: read each eligible
|
|
19787
|
+
* shard's source records, derive a per-shard summary, and write it into the
|
|
19788
|
+
* Insight Vault keyed by partition key. Shards behind `minVersion`,
|
|
19789
|
+
* unprovisioned, or whose read errors are reported in `skippedVaults` and
|
|
19790
|
+
* are not written (a stale summary is never left behind for a failed shard).
|
|
19791
|
+
*/
|
|
19792
|
+
async refreshInsights(options = {}) {
|
|
19793
|
+
if (this.crossVaultDerivations.length === 0) return { written: 0, skippedVaults: [] };
|
|
19794
|
+
const { eligible, skipped } = await this.resolveEligible(
|
|
19795
|
+
options.minVersion !== void 0 ? { minVersion: options.minVersion } : {}
|
|
19796
|
+
);
|
|
19797
|
+
let written = 0;
|
|
19798
|
+
for (const spec of this.crossVaultDerivations) {
|
|
19799
|
+
const results = await this.db.queryAcross(
|
|
19800
|
+
eligible.map((r) => r.vaultId),
|
|
19801
|
+
async (vault) => {
|
|
19802
|
+
this.template.configure(vault);
|
|
19803
|
+
return vault.collection(spec.source).list();
|
|
19804
|
+
},
|
|
19805
|
+
{ create: false, ...options.concurrency !== void 0 ? { concurrency: options.concurrency } : {} }
|
|
19806
|
+
);
|
|
19807
|
+
const insight = await this.db.openVault(spec.target.vault);
|
|
19808
|
+
const out = insight.collection(spec.target.collection);
|
|
19809
|
+
for (let i = 0; i < eligible.length; i++) {
|
|
19810
|
+
const row = eligible[i];
|
|
19811
|
+
const res = results[i];
|
|
19812
|
+
if (!res || res.result === void 0) {
|
|
19813
|
+
skipped.push({ vaultId: row.vaultId, reason: "error", ...res?.error ? { error: res.error } : {} });
|
|
19814
|
+
continue;
|
|
19815
|
+
}
|
|
19816
|
+
const ctx = {
|
|
19817
|
+
vaultId: row.vaultId,
|
|
19818
|
+
partitionKey: row.partitionKey,
|
|
19819
|
+
schemaVersion: row.schemaVersion
|
|
19820
|
+
};
|
|
19821
|
+
const summary = spec.derive(res.result, ctx);
|
|
19822
|
+
await out.put(row.partitionKey, summary);
|
|
19823
|
+
written++;
|
|
19824
|
+
}
|
|
19825
|
+
}
|
|
19826
|
+
return { written, skippedVaults: skipped };
|
|
19827
|
+
}
|
|
19828
|
+
/** @internal — the control-plane vault for migration status; lazily opened. */
|
|
19829
|
+
async ensureStateVault() {
|
|
19830
|
+
if (!this.stateVault) this.stateVault = await StateManagementVault.open(this.db);
|
|
19831
|
+
return this.stateVault;
|
|
19832
|
+
}
|
|
19833
|
+
/**
|
|
19834
|
+
* Migrate ONE shard to the template's current version (#271 fleet runner,
|
|
19835
|
+
* per-shard step). Opens the shard (applying the template, which arms the
|
|
19836
|
+
* M12 cutover), drains schema-write detection, runs `vault.runSchemaCutover()`
|
|
19837
|
+
* (the per-vault drain-barrier-transform protocol), then advances the
|
|
19838
|
+
* registry row's `schemaVersion` and records `migration-status`. A shard
|
|
19839
|
+
* already at the template version is a no-op (`status: 'done'`, migrated 0).
|
|
19840
|
+
* Never throws on a cutover failure — it records `status: 'failed'` and
|
|
19841
|
+
* returns the row, so a fleet run continues past a bad shard.
|
|
19842
|
+
*/
|
|
19843
|
+
async migrateShard(partitionKey) {
|
|
19844
|
+
const vaultId = this.shardVaultId(partitionKey);
|
|
19845
|
+
const row = await this.registry.get(this.registryId(partitionKey));
|
|
19846
|
+
if (!row) throw new UnknownShardError(partitionKey, this.name);
|
|
19847
|
+
const target = this.template.version;
|
|
19848
|
+
const sv = await this.ensureStateVault();
|
|
19849
|
+
const base = { vaultId, group: this.name, currentVersion: row.schemaVersion, targetVersion: target };
|
|
19850
|
+
if (row.schemaVersion >= target) {
|
|
19851
|
+
const done = { ...base, status: "done", migrated: 0, finishedAt: Date.now() };
|
|
19852
|
+
await sv.upsertMigrationStatus(done);
|
|
19853
|
+
return done;
|
|
19854
|
+
}
|
|
19855
|
+
await sv.upsertMigrationStatus({ ...base, status: "running", startedAt: Date.now() });
|
|
19856
|
+
try {
|
|
19857
|
+
await sv.appendEvent({ type: "migration-started", group: this.name, vaultId, version: target });
|
|
19858
|
+
} catch {
|
|
19859
|
+
}
|
|
19860
|
+
try {
|
|
19861
|
+
const vault = await this._openShardRaw(partitionKey);
|
|
19862
|
+
await vault._drainPendingSchemaWrites();
|
|
19863
|
+
const { migrated } = await vault.runSchemaCutover();
|
|
19864
|
+
await this.registry.put(this.registryId(partitionKey), { ...row, schemaVersion: target });
|
|
19865
|
+
const done = { ...base, currentVersion: target, status: "done", migrated, finishedAt: Date.now() };
|
|
19866
|
+
await sv.upsertMigrationStatus(done);
|
|
19867
|
+
try {
|
|
19868
|
+
await sv.appendEvent({ type: "migration-completed", group: this.name, vaultId, version: target });
|
|
19869
|
+
} catch {
|
|
19870
|
+
}
|
|
19871
|
+
return done;
|
|
19872
|
+
} catch (err) {
|
|
19873
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
19874
|
+
const failed = { ...base, status: "failed", error, finishedAt: Date.now() };
|
|
19875
|
+
await sv.upsertMigrationStatus(failed);
|
|
19876
|
+
try {
|
|
19877
|
+
await sv.appendEvent({ type: "migration-failed", group: this.name, vaultId, version: target, detail: error });
|
|
19878
|
+
} catch {
|
|
19879
|
+
}
|
|
19880
|
+
return failed;
|
|
19881
|
+
}
|
|
19882
|
+
}
|
|
19883
|
+
/**
|
|
19884
|
+
* Active batch runner (#271): migrate every shard behind the template version
|
|
19885
|
+
* to it, in controlled batches. **Resumable + crash-safe** — shards already at
|
|
19886
|
+
* the target are skipped (the registry version is the source of truth), so a
|
|
19887
|
+
* re-run after a crash only picks up the unfinished + previously-failed shards.
|
|
19888
|
+
*
|
|
19889
|
+
* - `cohort` — restrict to these partition keys (the staged / canary rollout:
|
|
19890
|
+
* migrate a small cohort, verify the Insight Vault, then run the rest).
|
|
19891
|
+
* - `batchSize` — max shards migrated concurrently per batch (back-pressure).
|
|
19892
|
+
* Default 4. Batches run sequentially; shards within a batch run in parallel.
|
|
19893
|
+
*/
|
|
19894
|
+
async migrateFleet(options = {}) {
|
|
19895
|
+
const target = this.template.version;
|
|
19896
|
+
const rows = await this.allRows();
|
|
19897
|
+
const cohort = options.cohort;
|
|
19898
|
+
const todo = rows.filter(
|
|
19899
|
+
(r) => r.schemaVersion < target && (cohort === void 0 || cohort.includes(r.partitionKey))
|
|
19900
|
+
);
|
|
19901
|
+
const batchSize = Math.max(1, options.batchSize ?? 4);
|
|
19902
|
+
const migrated = [];
|
|
19903
|
+
const failed = [];
|
|
19904
|
+
for (let i = 0; i < todo.length; i += batchSize) {
|
|
19905
|
+
const batch = todo.slice(i, i + batchSize);
|
|
19906
|
+
const settled = await Promise.all(batch.map((r) => this.migrateShard(r.partitionKey)));
|
|
19907
|
+
for (const res of settled) {
|
|
19908
|
+
if (res.status === "done") migrated.push(res.vaultId);
|
|
19909
|
+
else failed.push({ vaultId: res.vaultId, error: res.error ?? "unknown" });
|
|
19910
|
+
}
|
|
19911
|
+
}
|
|
19912
|
+
return { target, migrated, failed };
|
|
19913
|
+
}
|
|
19040
19914
|
};
|
|
19041
19915
|
ShardedCollection = class {
|
|
19042
19916
|
constructor(group, collectionName) {
|
|
@@ -19244,159 +20118,6 @@ var init_vault_group = __esm({
|
|
|
19244
20118
|
}
|
|
19245
20119
|
});
|
|
19246
20120
|
|
|
19247
|
-
// src/federation/schema-manifest.ts
|
|
19248
|
-
function captureBlueprint(configure) {
|
|
19249
|
-
const recorded = [];
|
|
19250
|
-
const collectionStub = new Proxy(
|
|
19251
|
-
{},
|
|
19252
|
-
{
|
|
19253
|
-
get: () => () => collectionStub
|
|
19254
|
-
}
|
|
19255
|
-
);
|
|
19256
|
-
const proxy = new Proxy(
|
|
19257
|
-
{},
|
|
19258
|
-
{
|
|
19259
|
-
get: (_t, prop) => {
|
|
19260
|
-
if (prop === "collection") {
|
|
19261
|
-
return (name, opts) => {
|
|
19262
|
-
recorded.push({
|
|
19263
|
-
name,
|
|
19264
|
-
indexes: opts?.indexes ?? [],
|
|
19265
|
-
persistJsonSchema: !!opts?.persistJsonSchema
|
|
19266
|
-
});
|
|
19267
|
-
return collectionStub;
|
|
19268
|
-
};
|
|
19269
|
-
}
|
|
19270
|
-
return () => proxy;
|
|
19271
|
-
}
|
|
19272
|
-
}
|
|
19273
|
-
);
|
|
19274
|
-
configure(proxy);
|
|
19275
|
-
const sorted = [...recorded].sort((a, b) => a.name.localeCompare(b.name));
|
|
19276
|
-
const indexes = {};
|
|
19277
|
-
const persistJsonSchema = [];
|
|
19278
|
-
for (const c of sorted) {
|
|
19279
|
-
indexes[c.name] = c.indexes;
|
|
19280
|
-
if (c.persistJsonSchema) persistJsonSchema.push(c.name);
|
|
19281
|
-
}
|
|
19282
|
-
return {
|
|
19283
|
-
// `persistJsonSchema` is already name-sorted: it is populated while
|
|
19284
|
-
// iterating `sorted` (collections in name order).
|
|
19285
|
-
collections: sorted.map((c) => c.name),
|
|
19286
|
-
indexes,
|
|
19287
|
-
persistJsonSchema
|
|
19288
|
-
};
|
|
19289
|
-
}
|
|
19290
|
-
function canonical(value) {
|
|
19291
|
-
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
19292
|
-
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
19293
|
-
const obj = value;
|
|
19294
|
-
const keys = Object.keys(obj).sort();
|
|
19295
|
-
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonical(obj[k])}`).join(",")}}`;
|
|
19296
|
-
}
|
|
19297
|
-
async function fingerprintBlueprint(bp) {
|
|
19298
|
-
return sha256Hex2(new TextEncoder().encode(canonical(bp)));
|
|
19299
|
-
}
|
|
19300
|
-
var init_schema_manifest = __esm({
|
|
19301
|
-
"src/federation/schema-manifest.ts"() {
|
|
19302
|
-
"use strict";
|
|
19303
|
-
init_crypto();
|
|
19304
|
-
}
|
|
19305
|
-
});
|
|
19306
|
-
|
|
19307
|
-
// src/federation/state-vault.ts
|
|
19308
|
-
var state_vault_exports = {};
|
|
19309
|
-
__export(state_vault_exports, {
|
|
19310
|
-
STATE_VAULT_NAME: () => STATE_VAULT_NAME,
|
|
19311
|
-
StateManagementVault: () => StateManagementVault
|
|
19312
|
-
});
|
|
19313
|
-
var REGISTRY, MANIFEST, EVENTS, StateManagementVault;
|
|
19314
|
-
var init_state_vault = __esm({
|
|
19315
|
-
"src/federation/state-vault.ts"() {
|
|
19316
|
-
"use strict";
|
|
19317
|
-
init_schema_manifest();
|
|
19318
|
-
init_constants2();
|
|
19319
|
-
init_ulid();
|
|
19320
|
-
init_constants2();
|
|
19321
|
-
REGISTRY = "vaultRegistry";
|
|
19322
|
-
MANIFEST = "schemaManifest";
|
|
19323
|
-
EVENTS = "deploymentEvents";
|
|
19324
|
-
StateManagementVault = class _StateManagementVault {
|
|
19325
|
-
constructor(registry, schemaManifest, events) {
|
|
19326
|
-
this.registry = registry;
|
|
19327
|
-
this.schemaManifest = schemaManifest;
|
|
19328
|
-
this.#events = events;
|
|
19329
|
-
}
|
|
19330
|
-
registry;
|
|
19331
|
-
schemaManifest;
|
|
19332
|
-
/**
|
|
19333
|
-
* The append-only deployment-events log is kept truly private so the raw
|
|
19334
|
-
* mutable Collection is never surfaced — events may only be written via
|
|
19335
|
-
* `appendEvent` and read via `queryEvents`. (`registry` and
|
|
19336
|
-
* `schemaManifest` are deliberately public: consumers read and write them.)
|
|
19337
|
-
*/
|
|
19338
|
-
#events;
|
|
19339
|
-
/** Idempotently open the reserved state vault and bind the three control-plane collections. */
|
|
19340
|
-
static async open(db) {
|
|
19341
|
-
const vault = await db.openVault(STATE_VAULT_NAME);
|
|
19342
|
-
return new _StateManagementVault(
|
|
19343
|
-
vault.collection(REGISTRY),
|
|
19344
|
-
vault.collection(MANIFEST),
|
|
19345
|
-
vault.collection(EVENTS)
|
|
19346
|
-
);
|
|
19347
|
-
}
|
|
19348
|
-
/** Read-only query over the append-only deployment-events log. */
|
|
19349
|
-
queryEvents() {
|
|
19350
|
-
return this.#events.query();
|
|
19351
|
-
}
|
|
19352
|
-
/**
|
|
19353
|
-
* Append a deployment event with a fresh unique (ULID) id. This is the
|
|
19354
|
-
* only write path to the events log; no update/delete is exposed.
|
|
19355
|
-
* Callers should treat failures as non-fatal — this method does not
|
|
19356
|
-
* swallow errors, so wrap the call site in try/catch where appropriate.
|
|
19357
|
-
*/
|
|
19358
|
-
async appendEvent(event) {
|
|
19359
|
-
const ts = event.ts ?? Date.now();
|
|
19360
|
-
const id = generateULID();
|
|
19361
|
-
await this.#events.put(id, { ...event, id, ts });
|
|
19362
|
-
}
|
|
19363
|
-
/**
|
|
19364
|
-
* Ensure a manifest row exists for `(templateName, template.version)`.
|
|
19365
|
-
* Safe to call repeatedly: the `fingerprint` is a deterministic hash of
|
|
19366
|
-
* the template's declared shape (stable across calls), though each call
|
|
19367
|
-
* refreshes `recordedAt`.
|
|
19368
|
-
*/
|
|
19369
|
-
async recordManifest(templateName, template) {
|
|
19370
|
-
const bp = captureBlueprint(template.configure);
|
|
19371
|
-
const fingerprint = await fingerprintBlueprint(bp);
|
|
19372
|
-
await this.schemaManifest.put(`${templateName}:${template.version}`, {
|
|
19373
|
-
templateName,
|
|
19374
|
-
version: template.version,
|
|
19375
|
-
collections: bp.collections,
|
|
19376
|
-
indexes: bp.indexes,
|
|
19377
|
-
persistJsonSchema: bp.persistJsonSchema,
|
|
19378
|
-
fingerprint,
|
|
19379
|
-
recordedAt: Date.now()
|
|
19380
|
-
});
|
|
19381
|
-
return fingerprint;
|
|
19382
|
-
}
|
|
19383
|
-
/**
|
|
19384
|
-
* True when `template`'s current declared shape does not match the recorded
|
|
19385
|
-
* manifest for `(templateName, template.version)`. Because shards carry no
|
|
19386
|
-
* schema state independent of their template, this catches "a template's
|
|
19387
|
-
* shape changed without bumping `version`" — not independent per-shard drift.
|
|
19388
|
-
* A missing manifest is treated as drift (nothing to verify against).
|
|
19389
|
-
*/
|
|
19390
|
-
async detectDrift(templateName, template) {
|
|
19391
|
-
const row = await this.schemaManifest.get(`${templateName}:${template.version}`);
|
|
19392
|
-
if (!row) return true;
|
|
19393
|
-
const current = await fingerprintBlueprint(captureBlueprint(template.configure));
|
|
19394
|
-
return current !== row.fingerprint;
|
|
19395
|
-
}
|
|
19396
|
-
};
|
|
19397
|
-
}
|
|
19398
|
-
});
|
|
19399
|
-
|
|
19400
20121
|
// src/noydb.ts
|
|
19401
20122
|
var noydb_exports = {};
|
|
19402
20123
|
__export(noydb_exports, {
|
|
@@ -20266,7 +20987,7 @@ var init_noydb = __esm({
|
|
|
20266
20987
|
const { StateManagementVault: StateManagementVault2 } = await Promise.resolve().then(() => (init_state_vault(), state_vault_exports));
|
|
20267
20988
|
const stateVault = opts.registry ? void 0 : await StateManagementVault2.open(this);
|
|
20268
20989
|
const registry = opts.registry ?? stateVault.registry;
|
|
20269
|
-
const group = new VaultGroup2(this, name, registry, opts.sharding, template);
|
|
20990
|
+
const group = new VaultGroup2(this, name, registry, opts.sharding, template, opts.migrateOnOpen ?? false);
|
|
20270
20991
|
if (stateVault) {
|
|
20271
20992
|
group._attachStateVault(stateVault);
|
|
20272
20993
|
await stateVault.recordManifest(opts.sharding.vaultTemplate, template);
|