@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
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ValidationError
|
|
3
|
+
} from "./chunk-HMFC6M2G.js";
|
|
4
|
+
|
|
5
|
+
// src/derivations/with-derivation.ts
|
|
6
|
+
function withDerivation(spec) {
|
|
7
|
+
if (!spec.source || spec.source.length === 0) {
|
|
8
|
+
throw new ValidationError("withDerivation: source collection name is required");
|
|
9
|
+
}
|
|
10
|
+
if (!spec.outputs || Object.keys(spec.outputs).length === 0) {
|
|
11
|
+
throw new ValidationError("withDerivation: outputs map must declare at least one output");
|
|
12
|
+
}
|
|
13
|
+
if (spec.deterministic !== true) {
|
|
14
|
+
throw new ValidationError("withDerivation: v1 only supports deterministic derivations");
|
|
15
|
+
}
|
|
16
|
+
if (typeof spec.derive !== "function") {
|
|
17
|
+
throw new ValidationError("withDerivation: derive must be a function");
|
|
18
|
+
}
|
|
19
|
+
if (spec.sources !== void 0) {
|
|
20
|
+
for (const extra of spec.sources) {
|
|
21
|
+
if (typeof extra !== "string" || extra.length === 0) {
|
|
22
|
+
throw new ValidationError("withDerivation: each entry in sources[] must be a non-empty string");
|
|
23
|
+
}
|
|
24
|
+
if (extra === spec.source) {
|
|
25
|
+
throw new ValidationError(
|
|
26
|
+
`withDerivation: sources[] must not contain the primary source "${spec.source}"`
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (spec.triggerBy !== void 0) {
|
|
32
|
+
for (const t of spec.triggerBy) {
|
|
33
|
+
if (typeof t?.collection !== "string" || t.collection.length === 0) {
|
|
34
|
+
throw new ValidationError("withDerivation: each triggerBy entry needs a non-empty `collection`");
|
|
35
|
+
}
|
|
36
|
+
if (t.collection === spec.source) {
|
|
37
|
+
throw new ValidationError(
|
|
38
|
+
`withDerivation: triggerBy.collection must not equal the source "${spec.source}" (use sources[] for same-id triggers)`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
if (typeof t.on !== "string" || t.on.length === 0) {
|
|
42
|
+
throw new ValidationError(
|
|
43
|
+
`withDerivation: triggerBy on "${t.collection}" needs a non-empty \`on\` (the FK field on the source)`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
if (t.maxFanout !== void 0 && (!Number.isInteger(t.maxFanout) || t.maxFanout < 1)) {
|
|
47
|
+
throw new ValidationError(
|
|
48
|
+
`withDerivation: triggerBy maxFanout on "${t.collection}" must be a positive integer (got ${String(t.maxFanout)}).`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const lifecycleMode = typeof spec.lifecycle === "string" ? spec.lifecycle : spec.lifecycle.mode;
|
|
54
|
+
for (const [outputKey, outputSpec] of Object.entries(spec.outputs)) {
|
|
55
|
+
if (outputSpec.shape === "record" && outputSpec.collection === spec.source) {
|
|
56
|
+
if (!outputSpec.denorm || outputSpec.denorm.length === 0) {
|
|
57
|
+
throw new ValidationError(
|
|
58
|
+
`withDerivation: self-write output "${outputKey}" (collection === source "${spec.source}") must declare \`denorm: [...]\` naming the fields it maintains.`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (outputSpec.shape === "array") {
|
|
63
|
+
if (lifecycleMode !== "eager") {
|
|
64
|
+
throw new ValidationError(
|
|
65
|
+
`withDerivation: shape 'array' supports lifecycle 'eager' only in this release Output "${outputKey}" declared lifecycle '${lifecycleMode}'. Switch to \`lifecycle: "eager"\` or use shape: "record".`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
if (typeof outputSpec.key !== "function") {
|
|
69
|
+
throw new ValidationError(
|
|
70
|
+
`withDerivation: shape 'array' output "${outputKey}" requires \`key: (out) => string\`.`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
if (outputSpec.maxFanout !== void 0) {
|
|
74
|
+
if (!Number.isInteger(outputSpec.maxFanout) || outputSpec.maxFanout < 1) {
|
|
75
|
+
throw new ValidationError(
|
|
76
|
+
`withDerivation: maxFanout for output "${outputKey}" must be a positive integer (got ${String(outputSpec.maxFanout)}).`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
__noydb_strategy: "derivation",
|
|
84
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
85
|
+
spec
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/derivations/with-rollup.ts
|
|
90
|
+
function withRollup(config) {
|
|
91
|
+
const { from, key, into, field, compute } = config;
|
|
92
|
+
if (!from || from.length === 0) {
|
|
93
|
+
throw new ValidationError("withRollup: `from` (child collection) is required");
|
|
94
|
+
}
|
|
95
|
+
if (!into || into.length === 0) {
|
|
96
|
+
throw new ValidationError("withRollup: `into` (parent collection) is required");
|
|
97
|
+
}
|
|
98
|
+
if (from === into) {
|
|
99
|
+
throw new ValidationError("withRollup: `from` and `into` must be different collections");
|
|
100
|
+
}
|
|
101
|
+
if (!key || key.length === 0) {
|
|
102
|
+
throw new ValidationError("withRollup: `key` (FK field on the child) is required");
|
|
103
|
+
}
|
|
104
|
+
if (!field || field.length === 0) {
|
|
105
|
+
throw new ValidationError("withRollup: `field` (target field on the parent) is required");
|
|
106
|
+
}
|
|
107
|
+
if (typeof compute !== "function") {
|
|
108
|
+
throw new ValidationError("withRollup: `compute` must be a function");
|
|
109
|
+
}
|
|
110
|
+
const spec = {
|
|
111
|
+
source: into,
|
|
112
|
+
// the parent record is what carries the rolled-up field
|
|
113
|
+
deterministic: true,
|
|
114
|
+
rollup: { from, key, field, compute },
|
|
115
|
+
// Synthetic self-write output for registry / cycle bookkeeping. Dispatch
|
|
116
|
+
// patches `field` directly (value-equality guarded); the executor is not run.
|
|
117
|
+
outputs: { value: { shape: "record", collection: into, denorm: [field] } },
|
|
118
|
+
derive: () => ({ value: {} }),
|
|
119
|
+
// never invoked for a rollup strategy
|
|
120
|
+
lifecycle: "eager"
|
|
121
|
+
};
|
|
122
|
+
return { __noydb_strategy: "derivation", spec };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export {
|
|
126
|
+
withDerivation,
|
|
127
|
+
withRollup
|
|
128
|
+
};
|
|
129
|
+
//# sourceMappingURL=chunk-YWYW2YNO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/derivations/with-derivation.ts","../src/derivations/with-rollup.ts"],"sourcesContent":["import { ValidationError } from '../errors.js'\nimport type { DerivationStrategy, DerivationStrategyHandle } from './types.js'\n\n/**\n * Register a deterministic derivation: one source collection → one or\n * more typed outputs, computed by the user's `derive` function on\n * plaintext after DEK unwrap. Outputs are encrypted with the same DEK\n * as the source and written via the standard `Collection.put` path.\n *\n * See docs/superpowers/specs/2026-05-01-dim14-derivation-v1-design.md.\n */\nexport function withDerivation<\n TSource extends Record<string, unknown>,\n TOutputs extends Record<string, Record<string, unknown>>,\n>(spec: DerivationStrategy<TSource, TOutputs>): DerivationStrategyHandle {\n if (!spec.source || spec.source.length === 0) {\n throw new ValidationError('withDerivation: source collection name is required')\n }\n if (!spec.outputs || Object.keys(spec.outputs).length === 0) {\n throw new ValidationError('withDerivation: outputs map must declare at least one output')\n }\n if (spec.deterministic !== true) {\n throw new ValidationError('withDerivation: v1 only supports deterministic derivations')\n }\n if (typeof spec.derive !== 'function') {\n throw new ValidationError('withDerivation: derive must be a function')\n }\n\n // Validate declared sibling sources (#344). Each must be a non-empty\n // string and must differ from the primary source — a self-reference\n // would double-register the strategy under the same `_bySource` key.\n if (spec.sources !== undefined) {\n for (const extra of spec.sources) {\n if (typeof extra !== 'string' || extra.length === 0) {\n throw new ValidationError('withDerivation: each entry in sources[] must be a non-empty string')\n }\n if (extra === spec.source) {\n throw new ValidationError(\n `withDerivation: sources[] must not contain the primary source \"${spec.source}\"`,\n )\n }\n }\n }\n\n // Validate FK triggers (#376). Each `collection` must be a non-empty\n // string differing from the primary source, and `on` a non-empty field.\n if (spec.triggerBy !== undefined) {\n for (const t of spec.triggerBy) {\n if (typeof t?.collection !== 'string' || t.collection.length === 0) {\n throw new ValidationError('withDerivation: each triggerBy entry needs a non-empty `collection`')\n }\n if (t.collection === spec.source) {\n throw new ValidationError(\n `withDerivation: triggerBy.collection must not equal the source \"${spec.source}\" (use sources[] for same-id triggers)`,\n )\n }\n if (typeof t.on !== 'string' || t.on.length === 0) {\n throw new ValidationError(\n `withDerivation: triggerBy on \"${t.collection}\" needs a non-empty \\`on\\` (the FK field on the source)`,\n )\n }\n if (t.maxFanout !== undefined && (!Number.isInteger(t.maxFanout) || t.maxFanout < 1)) {\n throw new ValidationError(\n `withDerivation: triggerBy maxFanout on \"${t.collection}\" must be a positive integer (got ${String(t.maxFanout)}).`,\n )\n }\n }\n }\n\n // Validate array-shape outputs.\n const lifecycleMode = typeof spec.lifecycle === 'string' ? spec.lifecycle : spec.lifecycle.mode\n for (const [outputKey, outputSpec] of Object.entries(spec.outputs)) {\n // Self-write output (collection === source): reverse-denorm must declare\n // `denorm` (the fields it owns) — field-level provenance, #376.\n if (outputSpec.shape === 'record' && outputSpec.collection === spec.source) {\n if (!outputSpec.denorm || outputSpec.denorm.length === 0) {\n throw new ValidationError(\n `withDerivation: self-write output \"${outputKey}\" (collection === source \"${spec.source}\") `\n + 'must declare `denorm: [...]` naming the fields it maintains.',\n )\n }\n }\n if (outputSpec.shape === 'array') {\n if (lifecycleMode !== 'eager') {\n throw new ValidationError(\n `withDerivation: shape 'array' supports lifecycle 'eager' only in this release `\n + `Output \"${outputKey}\" declared lifecycle '${lifecycleMode}'. `\n + 'Switch to `lifecycle: \"eager\"` or use shape: \"record\".',\n )\n }\n if (typeof outputSpec.key !== 'function') {\n throw new ValidationError(\n `withDerivation: shape 'array' output \"${outputKey}\" requires \\`key: (out) => string\\`.`,\n )\n }\n if (outputSpec.maxFanout !== undefined) {\n if (!Number.isInteger(outputSpec.maxFanout) || outputSpec.maxFanout < 1) {\n throw new ValidationError(\n `withDerivation: maxFanout for output \"${outputKey}\" must be a positive integer `\n + `(got ${String(outputSpec.maxFanout)}).`,\n )\n }\n }\n }\n }\n\n return {\n __noydb_strategy: 'derivation',\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n spec: spec as DerivationStrategy<any, any>,\n }\n}\n","import { ValidationError } from '../errors.js'\nimport type { DerivationStrategy, DerivationStrategyHandle } from './types.js'\n\n/**\n * `withRollup` — aggregate many child records onto a single field of their\n * parent (#376 slice 2). The reverse of a join: instead of reading children\n * on demand, the parent carries a maintained summary.\n *\n * ```ts\n * withRollup<Sale, Buyer>({\n * from: 'sales', // child collection (the trigger)\n * key: 'buyerId', // FK on the child → parent id\n * into: 'buyers', // parent collection\n * field: 'revenueByYear', // field on the parent to maintain\n * compute: (sales) => groupSumByYear(sales, 'total'),\n * })\n * ```\n *\n * On every write OR delete of a `from` record, the parent at id `child[key]`\n * is recomputed: `compute(allChildren where child[key] === parentId)` is\n * patched onto `parent[field]`. A parent write also recomputes its own\n * aggregate (so a parent created after its children still fills in). Only the\n * `field` is touched — the rest of the parent record is never clobbered — and\n * a value-equality guard suppresses no-op writes. The aggregate is gap-free\n * with respect to child inserts, updates, and deletes.\n *\n * Desugars to a `withDerivation` strategy carrying a `rollup` marker; dispatch\n * handles it without invoking the executor. Eager-only in this slice.\n */\nexport function withRollup<\n TChild extends Record<string, unknown> = Record<string, unknown>,\n TParent extends Record<string, unknown> = Record<string, unknown>,\n>(config: {\n from: string\n key: keyof TChild & string\n into: string\n field: keyof TParent & string\n compute: (children: TChild[]) => unknown\n}): DerivationStrategyHandle {\n const { from, key, into, field, compute } = config\n if (!from || from.length === 0) {\n throw new ValidationError('withRollup: `from` (child collection) is required')\n }\n if (!into || into.length === 0) {\n throw new ValidationError('withRollup: `into` (parent collection) is required')\n }\n if (from === into) {\n throw new ValidationError('withRollup: `from` and `into` must be different collections')\n }\n if (!key || key.length === 0) {\n throw new ValidationError('withRollup: `key` (FK field on the child) is required')\n }\n if (!field || field.length === 0) {\n throw new ValidationError('withRollup: `field` (target field on the parent) is required')\n }\n if (typeof compute !== 'function') {\n throw new ValidationError('withRollup: `compute` must be a function')\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const spec: DerivationStrategy<any, any> = {\n source: into, // the parent record is what carries the rolled-up field\n deterministic: true,\n rollup: { from, key, field, compute: compute as (children: unknown[]) => unknown },\n // Synthetic self-write output for registry / cycle bookkeeping. Dispatch\n // patches `field` directly (value-equality guarded); the executor is not run.\n outputs: { value: { shape: 'record', collection: into, denorm: [field] } },\n derive: () => ({ value: {} }), // never invoked for a rollup strategy\n lifecycle: 'eager',\n }\n\n return { __noydb_strategy: 'derivation', spec }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
readPath
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-RZWQNMMP.js";
|
|
4
4
|
|
|
5
5
|
// src/aggregate/reducers.ts
|
|
6
6
|
function count(opts) {
|
|
@@ -120,4 +120,4 @@ export {
|
|
|
120
120
|
min,
|
|
121
121
|
max
|
|
122
122
|
};
|
|
123
|
-
//# sourceMappingURL=chunk-
|
|
123
|
+
//# sourceMappingURL=chunk-Z3BE5BRK.js.map
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
+
IllegalTransitionError,
|
|
2
3
|
RecordLockedError,
|
|
3
4
|
ValidationError
|
|
4
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-HMFC6M2G.js";
|
|
5
6
|
|
|
6
7
|
// src/guards/with-guard.ts
|
|
7
8
|
function withGuard(strategy) {
|
|
@@ -61,8 +62,62 @@ function immutableGuard(config) {
|
|
|
61
62
|
return withGuard(spec);
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
// src/guards/transition-guard.ts
|
|
66
|
+
function recordId2(record) {
|
|
67
|
+
const id = record?.id;
|
|
68
|
+
return typeof id === "string" ? id : "";
|
|
69
|
+
}
|
|
70
|
+
function stateOf(record, field) {
|
|
71
|
+
const v = record[field];
|
|
72
|
+
return typeof v === "string" ? v : String(v);
|
|
73
|
+
}
|
|
74
|
+
function transitionGuard(config) {
|
|
75
|
+
const { collection, field, transitions, initial, amendmentRoles, amendmentInvariant } = config;
|
|
76
|
+
const allowIdempotent = config.allowIdempotent ?? true;
|
|
77
|
+
if (!field) {
|
|
78
|
+
throw new ValidationError("transitionGuard: `field` is required");
|
|
79
|
+
}
|
|
80
|
+
if (transitions === void 0 || typeof transitions !== "object") {
|
|
81
|
+
throw new ValidationError("transitionGuard: `transitions` must be a state\u2192states map");
|
|
82
|
+
}
|
|
83
|
+
const spec = {
|
|
84
|
+
collection,
|
|
85
|
+
check: (incoming, ctx) => {
|
|
86
|
+
const rec = incoming;
|
|
87
|
+
const to = stateOf(rec, field);
|
|
88
|
+
if (ctx.existing === null) {
|
|
89
|
+
if (initial !== void 0 && !initial.includes(to)) {
|
|
90
|
+
throw new IllegalTransitionError(collection, recordId2(rec), "(none)", to);
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const from = stateOf(ctx.existing, field);
|
|
95
|
+
if (from === to) {
|
|
96
|
+
if (allowIdempotent) return;
|
|
97
|
+
throw new IllegalTransitionError(collection, recordId2(rec), from, to);
|
|
98
|
+
}
|
|
99
|
+
const allowed = transitions[from] ?? [];
|
|
100
|
+
if (!allowed.includes(to)) {
|
|
101
|
+
throw new IllegalTransitionError(collection, recordId2(rec), from, to);
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
// The authorized override: inside an amendment transaction the check
|
|
105
|
+
// is skipped and the change is ledgered. By default no extra invariant
|
|
106
|
+
// — the amendment itself is the sanctioned exception. Callers may
|
|
107
|
+
// supply `amendmentInvariant` to keep a constraint inviolable even
|
|
108
|
+
// under amendment; a throw reverts the amendment as `InvariantError`.
|
|
109
|
+
amendment: {
|
|
110
|
+
roles: amendmentRoles ?? ["admin", "owner"],
|
|
111
|
+
invariant: amendmentInvariant ?? (() => {
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
return withGuard(spec);
|
|
116
|
+
}
|
|
117
|
+
|
|
64
118
|
export {
|
|
65
119
|
withGuard,
|
|
66
|
-
immutableGuard
|
|
120
|
+
immutableGuard,
|
|
121
|
+
transitionGuard
|
|
67
122
|
};
|
|
68
|
-
//# sourceMappingURL=chunk-
|
|
123
|
+
//# sourceMappingURL=chunk-Z3I2WNGF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/guards/with-guard.ts","../src/guards/immutable-guard.ts","../src/guards/transition-guard.ts"],"sourcesContent":["import { ValidationError } from '../errors.js'\nimport type { GuardStrategy, GuardStrategyHandle } from './types.js'\n\n/**\n * Register a guard for a collection. Guards run on every `put()` /\n * `delete()` for the named collection (after permissions, before\n * encryption) and may:\n *\n * - `check` — block writes by throwing (typically `RecordLockedError`)\n * - `frozenFields` — freeze specific fields once a condition is true\n * - `amendment` — declare an authorized-override path with invariant\n *\n * Pass the returned handle to `createNoydb({ strategies: [...] })`.\n *\n * @see docs/superpowers/specs/2026-05-18-guards-design.md\n */\nexport function withGuard<T extends Record<string, unknown>>(\n strategy: GuardStrategy<T>,\n): GuardStrategyHandle<T> {\n if (!strategy.collection || strategy.collection.length === 0) {\n throw new ValidationError('withGuard: collection name is required')\n }\n return {\n __noydb_strategy: 'guard',\n spec: strategy,\n }\n}\n","/**\n * `immutableGuard` — declarative WORM / append-only sugar over the guard\n * subsystem.\n *\n * Issued fiscal documents (invoices, DDTs) must be immutable after issue.\n * That is expressible today with a hand-rolled `withGuard` (block on\n * `check`/`onDelete`, allow an admin `amendment`), but the boilerplate is\n * repetitive and easy to get subtly wrong. `immutableGuard` generates\n * exactly that guard from a declarative config, reusing the guard\n * machinery wholesale — `check`/`onDelete` rejection, the ledgered\n * `amendment` override, and composition with `periods`/`history`.\n *\n * ```ts\n * createNoydb({ guardStrategies: [\n * immutableGuard({\n * collection: 'invoices',\n * after: (r) => r.status === 'issued', // immutable once issued\n * }),\n * ] })\n * ```\n *\n * A record is mutable until `after(record)` holds; from then on, updates\n * and deletes throw `RecordLockedError` unless performed inside an\n * `amendment` transaction by an authorized role (the override is\n * ledgered by the guard amendment mechanism). `appendOnly: true` is\n * shorthand for `after: () => true` — immutable from creation.\n */\n\nimport { withGuard } from './with-guard.js'\nimport type { GuardStrategy, GuardStrategyHandle, GuardContext, GuardChange } from './types.js'\nimport { RecordLockedError, ValidationError } from '../errors.js'\n\nexport interface ImmutableGuardConfig<T extends Record<string, unknown>> {\n /** The collection to make WORM. */\n collection: string\n /**\n * A record becomes immutable once this predicate holds. Evaluated on\n * the *existing* (already-persisted) record, so the write that first\n * makes it true is still allowed; subsequent writes are blocked.\n * Mutually exclusive with `appendOnly`.\n */\n after?: (record: T) => boolean\n /** Shorthand for `after: () => true` — immutable from creation. */\n appendOnly?: boolean\n /** Roles permitted to override via an amendment transaction. Default `['admin', 'owner']`. */\n amendmentRoles?: ReadonlyArray<'admin' | 'owner'>\n /**\n * Optional set-level invariant run over the amendment change-set after\n * the writes execute. Signature matches `GuardStrategy.amendment.invariant`\n * exactly: it receives every {before, after} pair touching this\n * collection in the amendment plus the guard context; throwing reverts\n * the whole amendment and surfaces as `InvariantError`.\n *\n * Use this to keep a constraint inviolable EVEN under amendment — e.g.\n * forbid deleting an issued document by re-throwing on any\n * `before !== null && after === null` change, or assert a cross-record\n * sum is preserved. When omitted the amendment is unconditionally\n * allowed (the amendment itself is the sanctioned, ledgered override) —\n * this is the backward-compatible default.\n */\n amendmentInvariant?: (\n changes: ReadonlyArray<GuardChange<T>>,\n ctx: GuardContext<T>,\n ) => Promise<void> | void\n}\n\nfunction recordId(record: Record<string, unknown> | null): string {\n const id = record?.id\n return typeof id === 'string' ? id : ''\n}\n\n/**\n * Build an immutability guard. Pass the returned handle to\n * `createNoydb({ guardStrategies: [...] })`.\n */\nexport function immutableGuard<T extends Record<string, unknown>>(\n config: ImmutableGuardConfig<T>,\n): GuardStrategyHandle<T> {\n const { collection, after, appendOnly, amendmentRoles, amendmentInvariant } = config\n if (appendOnly && after !== undefined) {\n throw new ValidationError('immutableGuard: `after` and `appendOnly` are mutually exclusive')\n }\n if (!appendOnly && after === undefined) {\n throw new ValidationError('immutableGuard: provide `after` or `appendOnly: true`')\n }\n\n const isImmutable: (record: T) => boolean = appendOnly ? () => true : after!\n const reason = appendOnly ? 'append-only collection' : 'record is immutable after issue'\n\n const spec: GuardStrategy<T> = {\n collection,\n // Block updates to an already-immutable record. Inserts (existing\n // null) and the transition write that first makes the record\n // immutable are allowed — `after` reads the prior state.\n check: (incoming: T, ctx: GuardContext<T>) => {\n if (ctx.existing !== null && isImmutable(ctx.existing)) {\n throw new RecordLockedError(collection, recordId(incoming as Record<string, unknown>), reason)\n }\n },\n // Block deletes of an immutable record.\n onDelete: (existing: T) => {\n if (isImmutable(existing)) {\n throw new RecordLockedError(collection, recordId(existing as Record<string, unknown>), reason)\n }\n },\n // The authorized override: inside an amendment transaction the\n // check/onDelete are skipped and the change is ledgered. By default\n // there is no extra invariant — the amendment itself is the\n // sanctioned exception. Callers may supply `amendmentInvariant` to\n // keep a constraint inviolable even under amendment (e.g. forbid\n // deletes, or preserve a cross-record sum); a throw reverts the\n // amendment and surfaces as `InvariantError`.\n amendment: {\n roles: amendmentRoles ?? ['admin', 'owner'],\n invariant: amendmentInvariant ?? (() => {\n /* allow — the amendment is the override, and is ledgered */\n }),\n },\n }\n\n return withGuard<T>(spec)\n}\n","/**\n * `transitionGuard` — declarative state-machine sugar over the guard\n * subsystem.\n *\n * Any record with a lifecycle field (invoice `status`, order state,\n * ticket workflow, subscription phase) needs transition validation: a\n * write may only move the field along a declared arc. That is expressible\n * with a hand-rolled `withGuard({ check })`, but every consumer\n * re-implements the same graph lookup + error. `transitionGuard`\n * generates exactly that guard from a state graph, reusing the guard\n * machinery wholesale — `check` rejection, the ledgered `amendment`\n * override, and composition with `periods`/`history`.\n *\n * It generalizes {@link immutableGuard}: WORM is the special case \"every\n * state has no outgoing arcs\", i.e. `transitions` mapping each state to `[]`.\n *\n * ```ts\n * createNoydb({ guardStrategies: [\n * transitionGuard<Sale>({\n * collection: 'sales', field: 'status',\n * transitions: { // absence of an arc = forbidden\n * draft: ['to_verify', 'cancelled'],\n * to_verify: ['proforma', 'draft', 'cancelled'],\n * proforma: ['invoiced', 'cancelled'],\n * invoiced: ['paid'], paid: [], cancelled: [],\n * },\n * initial: ['draft', 'to_verify'], // allowed status on insert\n * }),\n * ] })\n * ```\n *\n * Semantics:\n * - **Insert** (`ctx.existing === null`): `incoming[field]` must be in\n * `initial`. When `initial` is omitted, any value is allowed on insert.\n * - **Update**: the arc `(existing[field] → incoming[field])` must be\n * listed in `transitions[from]`, else `IllegalTransitionError`. A\n * same-value write (`from === to`) is allowed when `allowIdempotent`\n * (default `true`) — so writes that touch other fields without moving\n * state pass.\n * - **Override**: inside an `amendment` transaction by an authorized role\n * the check is skipped and the change is ledgered (mirrors every guard).\n *\n * The status graph is caller-supplied data — no UI, no domain logic.\n */\n\nimport { withGuard } from './with-guard.js'\nimport type { GuardStrategy, GuardStrategyHandle, GuardContext, GuardChange } from './types.js'\nimport { IllegalTransitionError, ValidationError } from '../errors.js'\n\nexport interface TransitionGuardConfig<T extends Record<string, unknown>> {\n /** The collection whose state field is governed. */\n collection: string\n /** The state field on the record (e.g. `'status'`). */\n field: keyof T & string\n /**\n * The transition graph: each state maps to the states it may move to.\n * A state absent from the map (or mapped to `[]`) is terminal — no\n * outgoing arc, so any non-idempotent write from it is rejected.\n */\n transitions: Readonly<Record<string, readonly string[]>>\n /**\n * States allowed as the initial value on insert (`existing === null`).\n * Omit to allow any value on insert.\n */\n initial?: readonly string[]\n /**\n * Allow a same-value write (`from === to`) on update. Default `true` —\n * lets a put that changes other fields, but not the state, through.\n */\n allowIdempotent?: boolean\n /** Roles permitted to override via an amendment transaction. Default `['admin', 'owner']`. */\n amendmentRoles?: ReadonlyArray<'admin' | 'owner'>\n /**\n * Optional set-level invariant run over the amendment change-set after\n * the writes execute. Signature matches `GuardStrategy.amendment.invariant`\n * exactly. When omitted the amendment is unconditionally allowed (the\n * amendment itself is the sanctioned, ledgered override) — the\n * backward-compatible default. Mirrors {@link immutableGuard}.\n */\n amendmentInvariant?: (\n changes: ReadonlyArray<GuardChange<T>>,\n ctx: GuardContext<T>,\n ) => Promise<void> | void\n}\n\nfunction recordId(record: Record<string, unknown> | null): string {\n const id = record?.id\n return typeof id === 'string' ? id : ''\n}\n\nfunction stateOf(record: Record<string, unknown>, field: string): string {\n const v = record[field]\n return typeof v === 'string' ? v : String(v)\n}\n\n/**\n * Build a state-machine transition guard. Pass the returned handle to\n * `createNoydb({ guardStrategies: [...] })`.\n */\nexport function transitionGuard<T extends Record<string, unknown>>(\n config: TransitionGuardConfig<T>,\n): GuardStrategyHandle<T> {\n const { collection, field, transitions, initial, amendmentRoles, amendmentInvariant } = config\n const allowIdempotent = config.allowIdempotent ?? true\n\n if (!field) {\n throw new ValidationError('transitionGuard: `field` is required')\n }\n if (transitions === undefined || typeof transitions !== 'object') {\n throw new ValidationError('transitionGuard: `transitions` must be a state→states map')\n }\n\n const spec: GuardStrategy<T> = {\n collection,\n check: (incoming: T, ctx: GuardContext<T>) => {\n const rec = incoming as Record<string, unknown>\n const to = stateOf(rec, field)\n\n // Insert — gate on the allowed initial set (any value if unset).\n if (ctx.existing === null) {\n if (initial !== undefined && !initial.includes(to)) {\n throw new IllegalTransitionError(collection, recordId(rec), '(none)', to)\n }\n return\n }\n\n // Update — the arc (from → to) must be a declared edge.\n const from = stateOf(ctx.existing as Record<string, unknown>, field)\n if (from === to) {\n if (allowIdempotent) return\n throw new IllegalTransitionError(collection, recordId(rec), from, to)\n }\n const allowed = transitions[from] ?? []\n if (!allowed.includes(to)) {\n throw new IllegalTransitionError(collection, recordId(rec), from, to)\n }\n },\n // The authorized override: inside an amendment transaction the check\n // is skipped and the change is ledgered. By default no extra invariant\n // — the amendment itself is the sanctioned exception. Callers may\n // supply `amendmentInvariant` to keep a constraint inviolable even\n // under amendment; a throw reverts the amendment as `InvariantError`.\n amendment: {\n roles: amendmentRoles ?? ['admin', 'owner'],\n invariant: amendmentInvariant ?? (() => {\n /* allow — the amendment is the override, and is ledgered */\n }),\n },\n }\n\n return withGuard<T>(spec)\n}\n"],"mappings":"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","names":["recordId"]}
|
|
@@ -6,8 +6,7 @@ import {
|
|
|
6
6
|
} from "./chunk-FZU343FL.js";
|
|
7
7
|
import {
|
|
8
8
|
sha256Hex
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-ZEGSDPB7.js";
|
|
9
|
+
} from "./chunk-QJKZ5WUP.js";
|
|
11
10
|
|
|
12
11
|
// src/federation/schema-manifest.ts
|
|
13
12
|
function captureBlueprint(configure) {
|
|
@@ -67,11 +66,13 @@ async function fingerprintBlueprint(bp) {
|
|
|
67
66
|
var REGISTRY = "vaultRegistry";
|
|
68
67
|
var MANIFEST = "schemaManifest";
|
|
69
68
|
var EVENTS = "deploymentEvents";
|
|
69
|
+
var MIGRATION_STATUS = "migrationStatus";
|
|
70
70
|
var StateManagementVault = class _StateManagementVault {
|
|
71
|
-
constructor(registry, schemaManifest, events) {
|
|
71
|
+
constructor(registry, schemaManifest, events, migrationStatus) {
|
|
72
72
|
this.registry = registry;
|
|
73
73
|
this.schemaManifest = schemaManifest;
|
|
74
74
|
this.#events = events;
|
|
75
|
+
this.#migrationStatus = migrationStatus;
|
|
75
76
|
}
|
|
76
77
|
registry;
|
|
77
78
|
schemaManifest;
|
|
@@ -82,15 +83,31 @@ var StateManagementVault = class _StateManagementVault {
|
|
|
82
83
|
* `schemaManifest` are deliberately public: consumers read and write them.)
|
|
83
84
|
*/
|
|
84
85
|
#events;
|
|
85
|
-
/**
|
|
86
|
+
/** Per-shard fleet-migration progress (#271). Surfaced via typed methods only. */
|
|
87
|
+
#migrationStatus;
|
|
88
|
+
/** Idempotently open the reserved state vault and bind the control-plane collections. */
|
|
86
89
|
static async open(db) {
|
|
87
90
|
const vault = await db.openVault(STATE_VAULT_NAME);
|
|
88
91
|
return new _StateManagementVault(
|
|
89
92
|
vault.collection(REGISTRY),
|
|
90
93
|
vault.collection(MANIFEST),
|
|
91
|
-
vault.collection(EVENTS)
|
|
94
|
+
vault.collection(EVENTS),
|
|
95
|
+
vault.collection(MIGRATION_STATUS)
|
|
92
96
|
);
|
|
93
97
|
}
|
|
98
|
+
/** Read one shard's migration status (or null). */
|
|
99
|
+
async getMigrationStatus(vaultId) {
|
|
100
|
+
return this.#migrationStatus.get(vaultId);
|
|
101
|
+
}
|
|
102
|
+
/** All migration-status rows (hydrates first). */
|
|
103
|
+
async listMigrationStatus() {
|
|
104
|
+
await this.#migrationStatus.list();
|
|
105
|
+
return this.#migrationStatus.query().toArray();
|
|
106
|
+
}
|
|
107
|
+
/** Upsert one shard's migration status (keyed by vaultId). */
|
|
108
|
+
async upsertMigrationStatus(row) {
|
|
109
|
+
await this.#migrationStatus.put(row.vaultId, row);
|
|
110
|
+
}
|
|
94
111
|
/** Read-only query over the append-only deployment-events log. */
|
|
95
112
|
queryEvents() {
|
|
96
113
|
return this.#events.query();
|
|
@@ -140,8 +157,8 @@ var StateManagementVault = class _StateManagementVault {
|
|
|
140
157
|
return current !== row.fingerprint;
|
|
141
158
|
}
|
|
142
159
|
};
|
|
160
|
+
|
|
143
161
|
export {
|
|
144
|
-
STATE_VAULT_NAME,
|
|
145
162
|
StateManagementVault
|
|
146
163
|
};
|
|
147
|
-
//# sourceMappingURL=
|
|
164
|
+
//# sourceMappingURL=chunk-ZJ67TB4S.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/federation/schema-manifest.ts","../src/federation/state-vault.ts"],"sourcesContent":["/**\n * @category capability\n * StateManagement Vault — schema blueprint capture + deterministic\n * fingerprint. See\n * docs/superpowers/specs/2026-06-08-statemanagement-vault-design.md.\n */\nimport type { Vault } from '../vault.js'\nimport type { IndexDef } from '../indexing/eager-indexes.js'\nimport { sha256Hex } from '../crypto.js'\nimport type { CapturedBlueprint } from './types.js'\n\ninterface RecordedCollection {\n name: string\n indexes: IndexDef[]\n persistJsonSchema: boolean\n}\n\n/**\n * Run `configure` against a recording proxy that intercepts\n * `collection(name, opts)` calls and captures the declared blueprint.\n * The proxy delegates every other access to a no-op stub so unrelated\n * `configure` calls (guards, blob setup) do not throw — only the\n * declared collections/indexes feed the fingerprint.\n */\nexport function captureBlueprint(configure: (vault: Vault) => void): CapturedBlueprint {\n const recorded: RecordedCollection[] = []\n // Minimal chainable stub returned by intercepted collection() — supports\n // the fluent calls a template might make without affecting the blueprint.\n const collectionStub = new Proxy(\n {},\n {\n get: () => () => collectionStub,\n },\n )\n const proxy = new Proxy(\n {},\n {\n get: (_t, prop) => {\n if (prop === 'collection') {\n return (name: string, opts?: { indexes?: IndexDef[]; persistJsonSchema?: boolean }) => {\n recorded.push({\n name,\n indexes: opts?.indexes ?? [],\n persistJsonSchema: !!opts?.persistJsonSchema,\n })\n return collectionStub\n }\n }\n // Any other vault method/property: a no-op callable that returns the proxy.\n return () => proxy\n },\n },\n ) as unknown as Vault\n\n configure(proxy)\n\n const sorted = [...recorded].sort((a, b) => a.name.localeCompare(b.name))\n const indexes: Record<string, IndexDef[]> = {}\n const persistJsonSchema: string[] = []\n for (const c of sorted) {\n indexes[c.name] = c.indexes\n if (c.persistJsonSchema) persistJsonSchema.push(c.name)\n }\n return {\n // `persistJsonSchema` is already name-sorted: it is populated while\n // iterating `sorted` (collections in name order).\n collections: sorted.map((c) => c.name),\n indexes,\n persistJsonSchema,\n }\n}\n\n/** Canonical JSON: object keys sorted recursively so the bytes are stable. */\nfunction canonical(value: unknown): string {\n if (value === null || typeof value !== 'object') return JSON.stringify(value)\n if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`\n const obj = value as Record<string, unknown>\n const keys = Object.keys(obj).sort()\n return `{${keys.map((k) => `${JSON.stringify(k)}:${canonical(obj[k])}`).join(',')}}`\n}\n\n/** sha256 (hex) over the canonicalized serializable blueprint. Uses the shared hub helper. */\nexport async function fingerprintBlueprint(bp: CapturedBlueprint): Promise<string> {\n return sha256Hex(new TextEncoder().encode(canonical(bp)))\n}\n","/**\n * @category capability\n * StateManagement Vault — federation control plane (registry +\n * schema-manifest + append-only deployment-events). See\n * docs/superpowers/specs/2026-06-08-statemanagement-vault-design.md.\n */\nimport type { Noydb } from '../noydb.js'\nimport type { Collection } from '../collection.js'\nimport type { Query } from '../query/builder.js'\nimport type { VaultRegistryRow, SchemaManifestRow, DeploymentEvent, MigrationStatusRow, VaultTemplate } from './types.js'\nimport { captureBlueprint, fingerprintBlueprint } from './schema-manifest.js'\nimport { STATE_VAULT_NAME } from './constants.js'\nimport { generateULID } from '../bundle/ulid.js'\n\n// Re-export so consumers can `import { STATE_VAULT_NAME } from '@noy-db/hub'`.\nexport { STATE_VAULT_NAME } from './constants.js'\n\n// Physical collection names — single-token (camelCase) to stay clear of any\n// collection-name charset restrictions; the existing suite uses single-word names.\nconst REGISTRY = 'vaultRegistry'\nconst MANIFEST = 'schemaManifest'\nconst EVENTS = 'deploymentEvents'\nconst MIGRATION_STATUS = 'migrationStatus'\n\nexport class StateManagementVault {\n /**\n * The append-only deployment-events log is kept truly private so the raw\n * mutable Collection is never surfaced — events may only be written via\n * `appendEvent` and read via `queryEvents`. (`registry` and\n * `schemaManifest` are deliberately public: consumers read and write them.)\n */\n readonly #events: Collection<DeploymentEvent>\n /** Per-shard fleet-migration progress (#271). Surfaced via typed methods only. */\n readonly #migrationStatus: Collection<MigrationStatusRow>\n\n private constructor(\n readonly registry: Collection<VaultRegistryRow>,\n readonly schemaManifest: Collection<SchemaManifestRow>,\n events: Collection<DeploymentEvent>,\n migrationStatus: Collection<MigrationStatusRow>,\n ) {\n this.#events = events\n this.#migrationStatus = migrationStatus\n }\n\n /** Idempotently open the reserved state vault and bind the control-plane collections. */\n static async open(db: Noydb): Promise<StateManagementVault> {\n const vault = await db.openVault(STATE_VAULT_NAME)\n return new StateManagementVault(\n vault.collection<VaultRegistryRow>(REGISTRY),\n vault.collection<SchemaManifestRow>(MANIFEST),\n vault.collection<DeploymentEvent>(EVENTS),\n vault.collection<MigrationStatusRow>(MIGRATION_STATUS),\n )\n }\n\n /** Read one shard's migration status (or null). */\n async getMigrationStatus(vaultId: string): Promise<MigrationStatusRow | null> {\n return this.#migrationStatus.get(vaultId)\n }\n\n /** All migration-status rows (hydrates first). */\n async listMigrationStatus(): Promise<MigrationStatusRow[]> {\n await this.#migrationStatus.list()\n return this.#migrationStatus.query().toArray()\n }\n\n /** Upsert one shard's migration status (keyed by vaultId). */\n async upsertMigrationStatus(row: MigrationStatusRow): Promise<void> {\n await this.#migrationStatus.put(row.vaultId, row)\n }\n\n /** Read-only query over the append-only deployment-events log. */\n queryEvents(): Query<DeploymentEvent> {\n return this.#events.query()\n }\n\n /**\n * Append a deployment event with a fresh unique (ULID) id. This is the\n * only write path to the events log; no update/delete is exposed.\n * Callers should treat failures as non-fatal — this method does not\n * swallow errors, so wrap the call site in try/catch where appropriate.\n */\n async appendEvent(event: Omit<DeploymentEvent, 'id' | 'ts'> & { ts?: number }): Promise<void> {\n const ts = event.ts ?? Date.now()\n const id = generateULID()\n await this.#events.put(id, { ...event, id, ts })\n }\n\n /**\n * Ensure a manifest row exists for `(templateName, template.version)`.\n * Safe to call repeatedly: the `fingerprint` is a deterministic hash of\n * the template's declared shape (stable across calls), though each call\n * refreshes `recordedAt`.\n */\n async recordManifest(templateName: string, template: VaultTemplate): Promise<string> {\n const bp = captureBlueprint(template.configure)\n const fingerprint = await fingerprintBlueprint(bp)\n await this.schemaManifest.put(`${templateName}:${template.version}`, {\n templateName,\n version: template.version,\n collections: bp.collections,\n indexes: bp.indexes,\n persistJsonSchema: bp.persistJsonSchema,\n fingerprint,\n recordedAt: Date.now(),\n })\n return fingerprint\n }\n\n /**\n * True when `template`'s current declared shape does not match the recorded\n * manifest for `(templateName, template.version)`. Because shards carry no\n * schema state independent of their template, this catches \"a template's\n * shape changed without bumping `version`\" — not independent per-shard drift.\n * A missing manifest is treated as drift (nothing to verify against).\n */\n async detectDrift(templateName: string, template: VaultTemplate): Promise<boolean> {\n const row = await this.schemaManifest.get(`${templateName}:${template.version}`)\n if (!row) return true\n const current = await fingerprintBlueprint(captureBlueprint(template.configure))\n return current !== row.fingerprint\n }\n}\n"],"mappings":"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","names":[]}
|