@noy-db/hub 0.2.0-pre.11 → 0.2.0-pre.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +126 -0
- package/dist/aggregate/index.cjs +289 -12
- package/dist/aggregate/index.cjs.map +1 -1
- package/dist/aggregate/index.d.cts +2 -2
- package/dist/aggregate/index.d.ts +2 -2
- package/dist/aggregate/index.js +7 -7
- package/dist/aggregate/index.js.map +1 -1
- package/dist/attestation/index.cjs.map +1 -1
- package/dist/attestation/index.d.cts +3 -3
- package/dist/attestation/index.d.ts +3 -3
- package/dist/attestation/index.js +6 -6
- package/dist/blobs/index.cjs +28 -0
- package/dist/blobs/index.cjs.map +1 -1
- package/dist/blobs/index.d.cts +4 -4
- package/dist/blobs/index.d.ts +4 -4
- package/dist/blobs/index.js +5 -5
- package/dist/bundle/index.cjs +2024 -69
- package/dist/bundle/index.cjs.map +1 -1
- package/dist/bundle/index.d.cts +5 -5
- package/dist/bundle/index.d.ts +5 -5
- package/dist/bundle/index.js +9 -9
- package/dist/{chunk-LSTBFLL2.js → chunk-3OUCWHV6.js} +2 -2
- package/dist/{chunk-7CEGU63S.js → chunk-3XZRRBFW.js} +2 -2
- package/dist/{chunk-RC6SU5NO.js → chunk-4VCQH32J.js} +2 -2
- package/dist/{chunk-ZROPXHJY.js → chunk-4YDZ7JPZ.js} +2 -2
- package/dist/{chunk-IMYKDWB4.js → chunk-5NISHSBO.js} +2 -2
- package/dist/{chunk-ZBBW7YQN.js → chunk-7TEI2K2A.js} +5 -5
- package/dist/{chunk-6T2UDBKG.js → chunk-AYNF7PVX.js} +2 -2
- package/dist/{chunk-QCXNMCHN.js → chunk-C3WRKABE.js} +4 -4
- package/dist/{chunk-GAUEWM7D.js → chunk-CWFQTAD4.js} +4 -4
- package/dist/{chunk-QSOYKKMD.js → chunk-D5Y3HIC6.js} +2 -2
- package/dist/{chunk-R233SLY3.js → chunk-E3DIBDKA.js} +2 -2
- package/dist/chunk-FNVFT4HZ.js +64 -0
- package/dist/chunk-FNVFT4HZ.js.map +1 -0
- package/dist/{chunk-SLV4LAKX.js → chunk-GC4V7RU7.js} +1 -1
- package/dist/chunk-GC4V7RU7.js.map +1 -0
- package/dist/{chunk-5OEJ6GOT.js → chunk-GL3Z7LH7.js} +2 -2
- package/dist/{chunk-YW5DBAPG.js → chunk-GZJ5JBED.js} +4 -4
- package/dist/{chunk-LJXYPGRH.js → chunk-HHZ77DHM.js} +3 -3
- package/dist/{chunk-6MFH4BMK.js → chunk-HQXOEWLZ.js} +4 -4
- package/dist/{chunk-RYIL3PI2.js → chunk-ILWQGTNH.js} +2 -2
- package/dist/{chunk-3DGHRDCX.js → chunk-J67BP5EP.js} +3 -3
- package/dist/{chunk-PVUUIWHY.js → chunk-JPOQMXGT.js} +10 -3
- package/dist/chunk-JPOQMXGT.js.map +1 -0
- package/dist/{chunk-RRNA5GKT.js → chunk-JWFNOD2T.js} +2 -2
- package/dist/{chunk-BDV7INMP.js → chunk-KHQ3N5AB.js} +4 -4
- package/dist/{chunk-JQ4NEJJ6.js → chunk-KJF7EPUE.js} +3 -3
- package/dist/{chunk-FO3UEG4S.js → chunk-KKB42D3Q.js} +2 -2
- package/dist/{chunk-26NK23DZ.js → chunk-M6KXHRIA.js} +3 -3
- package/dist/{chunk-DAP2XL7Q.js → chunk-NIUXQDWD.js} +2 -2
- package/dist/{chunk-6YLPHBKR.js → chunk-NJMKHRQI.js} +145 -11
- package/dist/chunk-NJMKHRQI.js.map +1 -0
- package/dist/{chunk-O6EJ6WTI.js → chunk-NKGY3C53.js} +87 -2
- package/dist/chunk-NKGY3C53.js.map +1 -0
- package/dist/{chunk-CXJG63MA.js → chunk-NP6EZT44.js} +20 -6
- package/dist/chunk-NP6EZT44.js.map +1 -0
- package/dist/{chunk-Y26YV5R3.js → chunk-O2JW656W.js} +3 -3
- package/dist/{chunk-MPOLUAMI.js → chunk-P5MW7BG2.js} +665 -60
- package/dist/chunk-P5MW7BG2.js.map +1 -0
- package/dist/{chunk-TGALXXLV.js → chunk-PW26DAXS.js} +3 -3
- package/dist/{chunk-V2PZC6AW.js → chunk-QAWCVWCX.js} +5 -5
- package/dist/{chunk-CH22FZHT.js → chunk-QFYVGJLI.js} +2 -2
- package/dist/{chunk-YM7LFCG7.js → chunk-QIVFGU2M.js} +3 -3
- package/dist/{chunk-CXFOITNS.js → chunk-SJ24GHID.js} +2 -2
- package/dist/{chunk-EBVBE7UK.js → chunk-SOU42FGB.js} +5 -5
- package/dist/{chunk-YVZRTCGG.js → chunk-SYSKC237.js} +6 -6
- package/dist/{chunk-77DWLQRY.js → chunk-TDECYU4Y.js} +31 -3
- package/dist/chunk-TDECYU4Y.js.map +1 -0
- package/dist/{chunk-73YLDCNF.js → chunk-TEQGXA7L.js} +5 -5
- package/dist/{chunk-PC6ZEDRL.js → chunk-UNQEWORI.js} +2 -2
- package/dist/{chunk-PC3ZZBTO.js → chunk-VAK6NQAK.js} +5 -5
- package/dist/{chunk-GKI4SDP7.js → chunk-WQKZIQIL.js} +4 -4
- package/dist/chunk-YWBHS25M.js +783 -0
- package/dist/chunk-YWBHS25M.js.map +1 -0
- package/dist/chunk-ZC7J6ZYV.js +7 -0
- package/dist/chunk-ZC7J6ZYV.js.map +1 -0
- package/dist/consent/index.cjs.map +1 -1
- package/dist/consent/index.d.cts +4 -4
- package/dist/consent/index.d.ts +4 -4
- package/dist/consent/index.js +3 -3
- package/dist/{crypto-2CRLG4F4.js → crypto-YXH6SAOW.js} +3 -3
- package/dist/{delegation-ZTRT2PRV.js → delegation-K5ERUH6A.js} +5 -5
- package/dist/derivations/index.cjs.map +1 -1
- package/dist/derivations/index.d.cts +5 -5
- package/dist/derivations/index.d.ts +5 -5
- package/dist/derivations/index.js +4 -4
- package/dist/{dev-unlock-BH6y3Hx0.d.ts → dev-unlock-BW0GNBEV.d.ts} +1 -1
- package/dist/{dev-unlock-H1Xwxc3U.d.cts → dev-unlock-a7SOtaV0.d.cts} +1 -1
- package/dist/executor-AVJ7UEWA.js +8 -0
- package/dist/executor-IQO3KGXQ.js +11 -0
- package/dist/executor-VT7TKGE4.js +8 -0
- package/dist/{fanout-sidecar-F3ZRFU4H.js → fanout-sidecar-N6OJX6QR.js} +2 -2
- package/dist/guards/index.cjs +53 -1
- package/dist/guards/index.cjs.map +1 -1
- package/dist/guards/index.d.cts +12 -6
- package/dist/guards/index.d.ts +12 -6
- package/dist/guards/index.js +5 -3
- package/dist/{hash-_sDFvtmX.d.cts → hash-B0cLQcq_.d.cts} +1 -1
- package/dist/{hash-D89JdDbj.d.ts → hash-uMNIAAW8.d.ts} +1 -1
- package/dist/history/index.cjs.map +1 -1
- package/dist/history/index.d.cts +5 -5
- package/dist/history/index.d.ts +5 -5
- package/dist/history/index.js +5 -5
- package/dist/i18n/index.cjs.map +1 -1
- package/dist/i18n/index.d.cts +4 -4
- package/dist/i18n/index.d.ts +4 -4
- package/dist/i18n/index.js +6 -6
- package/dist/immutable-guard-B0h-ipLz.d.ts +67 -0
- package/dist/immutable-guard-BZIcYhYX.d.cts +67 -0
- package/dist/index-CUVOMtgg.d.cts +1216 -0
- package/dist/index-Cqzp4tt9.d.ts +1216 -0
- package/dist/index.cjs +2200 -106
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +55 -13
- package/dist/index.d.ts +55 -13
- package/dist/index.js +95 -44
- package/dist/index.js.map +1 -1
- package/dist/indexing/index.cjs.map +1 -1
- package/dist/indexing/index.js +2 -2
- package/dist/issue-ZH27C23Y.js +12 -0
- package/dist/{ledger-NYCGJX2D.js → ledger-64TTQMRS.js} +5 -5
- package/dist/materialized-views/index.cjs.map +1 -1
- package/dist/materialized-views/index.d.cts +5 -6
- package/dist/materialized-views/index.d.ts +5 -6
- package/dist/materialized-views/index.js +6 -6
- package/dist/noydb-O76SKBST.js +35 -0
- package/dist/overlay-views/index.cjs.map +1 -1
- package/dist/overlay-views/index.d.cts +5 -5
- package/dist/overlay-views/index.d.ts +5 -5
- package/dist/overlay-views/index.js +4 -4
- package/dist/periods/index.cjs.map +1 -1
- package/dist/periods/index.d.cts +4 -4
- package/dist/periods/index.d.ts +4 -4
- package/dist/periods/index.js +5 -5
- package/dist/{public-envelope-QOXZEHKH.js → public-envelope-MHG6YVXW.js} +4 -4
- package/dist/query/index.cjs +382 -17
- package/dist/query/index.cjs.map +1 -1
- package/dist/query/index.d.cts +2 -2
- package/dist/query/index.d.ts +2 -2
- package/dist/query/index.js +4 -4
- package/dist/registry-2PKBQDCH.js +8 -0
- package/dist/registry-4VXFKCBJ.js +8 -0
- package/dist/{registry-ST2VNFZC.js → registry-PV4G3OPA.js} +3 -3
- package/dist/{revoke-RT7QYB4G.js → revoke-5BOLVJ3N.js} +6 -6
- package/dist/session/index.cjs.map +1 -1
- package/dist/session/index.d.cts +5 -5
- package/dist/session/index.d.ts +5 -5
- package/dist/session/index.js +3 -3
- package/dist/shadow/index.cjs.map +1 -1
- package/dist/shadow/index.d.cts +4 -4
- package/dist/shadow/index.d.ts +4 -4
- package/dist/shadow/index.js +2 -2
- package/dist/{signer-QNU66JF5.js → signer-GRIYBA22.js} +5 -5
- package/dist/snapshots/index.cjs.map +1 -1
- package/dist/snapshots/index.d.cts +4 -4
- package/dist/snapshots/index.d.ts +4 -4
- package/dist/snapshots/index.js +4 -4
- package/dist/{stale-VKXSXJF4.js → stale-LZYMMDDS.js} +2 -2
- package/dist/state-vault-QFJWU23A.js +147 -0
- package/dist/state-vault-QFJWU23A.js.map +1 -0
- package/dist/store/index.cjs.map +1 -1
- package/dist/store/index.d.cts +4 -4
- package/dist/store/index.d.ts +4 -4
- package/dist/store/index.js +2 -2
- package/dist/strategy-CrS7PnbE.d.cts +2048 -0
- package/dist/strategy-CrS7PnbE.d.ts +2048 -0
- package/dist/sync/index.cjs.map +1 -1
- package/dist/sync/index.d.cts +3 -3
- package/dist/sync/index.d.ts +3 -3
- package/dist/sync/index.js +4 -4
- package/dist/team/index.cjs.map +1 -1
- package/dist/team/index.d.cts +4 -4
- package/dist/team/index.d.ts +4 -4
- package/dist/team/index.js +8 -8
- package/dist/tx/index.cjs +8 -1
- package/dist/tx/index.cjs.map +1 -1
- package/dist/tx/index.d.cts +4 -4
- package/dist/tx/index.d.ts +4 -4
- package/dist/tx/index.js +3 -3
- package/dist/{types-DiSXn3a4.d.cts → types-CDwSSXiI.d.cts} +709 -6
- package/dist/{types-CD8mc8zR.d.ts → types-pax34sec.d.ts} +709 -6
- package/dist/{ulid-DQ1hcJvZ.d.cts → ulid-7bCSgIgb.d.cts} +1 -1
- package/dist/{ulid-8p83wbR4.d.ts → ulid-C_t4hL3d.d.ts} +1 -1
- package/dist/util/index.cjs.map +1 -1
- package/dist/util/index.js +1 -1
- package/dist/vault-group-UO4YUZOG.js +493 -0
- package/dist/vault-group-UO4YUZOG.js.map +1 -0
- package/dist/{with-derivation-DWMTpgEH.d.ts → with-derivation-BjdOxUBn.d.ts} +1 -1
- package/dist/{with-derivation-CVT7-dUt.d.cts → with-derivation-D8wFlb6V.d.cts} +1 -1
- package/dist/{with-materialized-view-BTTU3BNK.d.cts → with-materialized-view-5QMF1rS_.d.cts} +1 -1
- package/dist/{with-materialized-view-X0CoL8-L.d.ts → with-materialized-view-DJb-HO65.d.ts} +1 -1
- package/dist/{with-overlayed-view-DQjO_DSG.d.ts → with-overlayed-view-CkqTefbz.d.ts} +1 -1
- package/dist/{with-overlayed-view-DcacRRsS.d.cts → with-overlayed-view-DDNflPvC.d.cts} +1 -1
- package/package.json +3 -3
- package/dist/chunk-2LPPNWF6.js +0 -340
- package/dist/chunk-2LPPNWF6.js.map +0 -1
- package/dist/chunk-6YLPHBKR.js.map +0 -1
- package/dist/chunk-77DWLQRY.js.map +0 -1
- package/dist/chunk-C3WE6UJY.js +0 -19
- package/dist/chunk-C3WE6UJY.js.map +0 -1
- package/dist/chunk-CXJG63MA.js.map +0 -1
- package/dist/chunk-MPOLUAMI.js.map +0 -1
- package/dist/chunk-O6EJ6WTI.js.map +0 -1
- package/dist/chunk-PVUUIWHY.js.map +0 -1
- package/dist/chunk-SLV4LAKX.js.map +0 -1
- package/dist/executor-S76VN45G.js +0 -8
- package/dist/executor-UCXLIGLW.js +0 -11
- package/dist/executor-ZCNZJMGR.js +0 -8
- package/dist/index-B8bjExET.d.cts +0 -2434
- package/dist/index-DfUbNad8.d.ts +0 -2434
- package/dist/issue-IVTVSKWW.js +0 -12
- package/dist/noydb-SH4RLE47.js +0 -34
- package/dist/registry-UFIK7CSR.js +0 -8
- package/dist/registry-ZGYYSM5I.js +0 -8
- package/dist/strategy-CT2LCKAX.d.cts +0 -613
- package/dist/strategy-CT2LCKAX.d.ts +0 -613
- package/dist/with-guard-BRvt53da.d.ts +0 -18
- package/dist/with-guard-Dx2zZnTA.d.cts +0 -18
- /package/dist/{chunk-LSTBFLL2.js.map → chunk-3OUCWHV6.js.map} +0 -0
- /package/dist/{chunk-7CEGU63S.js.map → chunk-3XZRRBFW.js.map} +0 -0
- /package/dist/{chunk-RC6SU5NO.js.map → chunk-4VCQH32J.js.map} +0 -0
- /package/dist/{chunk-ZROPXHJY.js.map → chunk-4YDZ7JPZ.js.map} +0 -0
- /package/dist/{chunk-IMYKDWB4.js.map → chunk-5NISHSBO.js.map} +0 -0
- /package/dist/{chunk-ZBBW7YQN.js.map → chunk-7TEI2K2A.js.map} +0 -0
- /package/dist/{chunk-6T2UDBKG.js.map → chunk-AYNF7PVX.js.map} +0 -0
- /package/dist/{chunk-QCXNMCHN.js.map → chunk-C3WRKABE.js.map} +0 -0
- /package/dist/{chunk-GAUEWM7D.js.map → chunk-CWFQTAD4.js.map} +0 -0
- /package/dist/{chunk-QSOYKKMD.js.map → chunk-D5Y3HIC6.js.map} +0 -0
- /package/dist/{chunk-R233SLY3.js.map → chunk-E3DIBDKA.js.map} +0 -0
- /package/dist/{chunk-5OEJ6GOT.js.map → chunk-GL3Z7LH7.js.map} +0 -0
- /package/dist/{chunk-YW5DBAPG.js.map → chunk-GZJ5JBED.js.map} +0 -0
- /package/dist/{chunk-LJXYPGRH.js.map → chunk-HHZ77DHM.js.map} +0 -0
- /package/dist/{chunk-6MFH4BMK.js.map → chunk-HQXOEWLZ.js.map} +0 -0
- /package/dist/{chunk-RYIL3PI2.js.map → chunk-ILWQGTNH.js.map} +0 -0
- /package/dist/{chunk-3DGHRDCX.js.map → chunk-J67BP5EP.js.map} +0 -0
- /package/dist/{chunk-RRNA5GKT.js.map → chunk-JWFNOD2T.js.map} +0 -0
- /package/dist/{chunk-BDV7INMP.js.map → chunk-KHQ3N5AB.js.map} +0 -0
- /package/dist/{chunk-JQ4NEJJ6.js.map → chunk-KJF7EPUE.js.map} +0 -0
- /package/dist/{chunk-FO3UEG4S.js.map → chunk-KKB42D3Q.js.map} +0 -0
- /package/dist/{chunk-26NK23DZ.js.map → chunk-M6KXHRIA.js.map} +0 -0
- /package/dist/{chunk-DAP2XL7Q.js.map → chunk-NIUXQDWD.js.map} +0 -0
- /package/dist/{chunk-Y26YV5R3.js.map → chunk-O2JW656W.js.map} +0 -0
- /package/dist/{chunk-TGALXXLV.js.map → chunk-PW26DAXS.js.map} +0 -0
- /package/dist/{chunk-V2PZC6AW.js.map → chunk-QAWCVWCX.js.map} +0 -0
- /package/dist/{chunk-CH22FZHT.js.map → chunk-QFYVGJLI.js.map} +0 -0
- /package/dist/{chunk-YM7LFCG7.js.map → chunk-QIVFGU2M.js.map} +0 -0
- /package/dist/{chunk-CXFOITNS.js.map → chunk-SJ24GHID.js.map} +0 -0
- /package/dist/{chunk-EBVBE7UK.js.map → chunk-SOU42FGB.js.map} +0 -0
- /package/dist/{chunk-YVZRTCGG.js.map → chunk-SYSKC237.js.map} +0 -0
- /package/dist/{chunk-73YLDCNF.js.map → chunk-TEQGXA7L.js.map} +0 -0
- /package/dist/{chunk-PC6ZEDRL.js.map → chunk-UNQEWORI.js.map} +0 -0
- /package/dist/{chunk-PC3ZZBTO.js.map → chunk-VAK6NQAK.js.map} +0 -0
- /package/dist/{chunk-GKI4SDP7.js.map → chunk-WQKZIQIL.js.map} +0 -0
- /package/dist/{crypto-2CRLG4F4.js.map → crypto-YXH6SAOW.js.map} +0 -0
- /package/dist/{delegation-ZTRT2PRV.js.map → delegation-K5ERUH6A.js.map} +0 -0
- /package/dist/{executor-S76VN45G.js.map → executor-AVJ7UEWA.js.map} +0 -0
- /package/dist/{executor-UCXLIGLW.js.map → executor-IQO3KGXQ.js.map} +0 -0
- /package/dist/{executor-ZCNZJMGR.js.map → executor-VT7TKGE4.js.map} +0 -0
- /package/dist/{fanout-sidecar-F3ZRFU4H.js.map → fanout-sidecar-N6OJX6QR.js.map} +0 -0
- /package/dist/{issue-IVTVSKWW.js.map → issue-ZH27C23Y.js.map} +0 -0
- /package/dist/{ledger-NYCGJX2D.js.map → ledger-64TTQMRS.js.map} +0 -0
- /package/dist/{noydb-SH4RLE47.js.map → noydb-O76SKBST.js.map} +0 -0
- /package/dist/{public-envelope-QOXZEHKH.js.map → public-envelope-MHG6YVXW.js.map} +0 -0
- /package/dist/{registry-ST2VNFZC.js.map → registry-2PKBQDCH.js.map} +0 -0
- /package/dist/{registry-UFIK7CSR.js.map → registry-4VXFKCBJ.js.map} +0 -0
- /package/dist/{registry-ZGYYSM5I.js.map → registry-PV4G3OPA.js.map} +0 -0
- /package/dist/{revoke-RT7QYB4G.js.map → revoke-5BOLVJ3N.js.map} +0 -0
- /package/dist/{signer-QNU66JF5.js.map → signer-GRIYBA22.js.map} +0 -0
- /package/dist/{stale-VKXSXJF4.js.map → stale-LZYMMDDS.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -46,7 +46,7 @@ var init_types = __esm({
|
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
// src/errors.ts
|
|
49
|
-
var NoydbError, DecryptionError, TamperedError, InvalidKeyError, KeyringCorruptError, NoAccessError, ReadOnlyError, ReadOnlyAtInstantError, ReadOnlyFrameError, PermissionDeniedError, ExportCapabilityError, KeyringExpiredError, ImportCapabilityError, StoreCapabilityError, PrivilegeEscalationError, PeriodClosedError, RecordLockedError, FieldFrozenError, InvariantError, AmendmentForbiddenError, DirectoryDisabledError, TierNotGrantedError, ElevationExpiredError, AlreadyElevatedError, TierDemoteDeniedError, DelegationTargetMissingError, ConflictError, LedgerContentionError, BundleVersionConflictError, NetworkError, NotFoundError, ValidationError, SchemaValidationError, SchemaUpdateError, NonAdditiveSchemaChangeError, SchemaLockedError, SchemaFenceError, MigrationRequiredError, QuiesceTimeoutError, GroupCardinalityError, IndexRequiredError, UniqueConstraintError, UnsupportedIndexOptionError, IndexWriteFailureError, BundleIntegrityError, BundleSealMismatchError, ReservedCollectionNameError, DictKeyMissingError, DictKeyInUseError, MissingTranslationError, LocaleNotSpecifiedError, ScriptViolationError, TranslatorNotConfiguredError, BackupLedgerError, BackupCorruptedError, AttestationError, SessionExpiredError, SessionNotFoundError, SessionPolicyError, JoinTooLargeError, CrossJoinTooLargeError, CrossJoinSourceUnknownError, DanglingReferenceError, FilenameSanitizationError, PathEscapeError, DerivationCycleError, DerivationDepthError, DerivationOutputUnknownError, DerivationOutputShapeError, DerivationCapExceededError, MaterializedViewCycleError, MaterializedViewSourceUnknownError, MaterializedViewTooLargeError, MaterializedViewConfigError, OverlayBaseIsVirtualError, OverlayCollectionUnavailableError, OverlayNameCollisionError, OverlayIdMismatchError, SnapshotNotFoundError;
|
|
49
|
+
var NoydbError, DecryptionError, TamperedError, InvalidKeyError, KeyringCorruptError, NoAccessError, ReadOnlyError, ReadOnlyAtInstantError, ReadOnlyFrameError, PermissionDeniedError, ExportCapabilityError, KeyringExpiredError, ImportCapabilityError, StoreCapabilityError, PrivilegeEscalationError, ReservedVaultNameError, PeriodClosedError, RecordLockedError, FieldFrozenError, InvariantError, AmendmentForbiddenError, DirectoryDisabledError, TierNotGrantedError, ElevationExpiredError, AlreadyElevatedError, TierDemoteDeniedError, DelegationTargetMissingError, ConflictError, LedgerContentionError, SequenceContentionError, SequenceOfflineError, NumberingUncertaintyError, BundleVersionConflictError, NetworkError, NotFoundError, ValidationError, SchemaValidationError, SchemaUpdateError, NonAdditiveSchemaChangeError, SchemaLockedError, SchemaFenceError, MigrationRequiredError, QuiesceTimeoutError, GroupCardinalityError, IndexRequiredError, UniqueConstraintError, UnsupportedIndexOptionError, IndexWriteFailureError, BundleIntegrityError, BundleSealMismatchError, ReservedCollectionNameError, DictKeyMissingError, DictKeyInUseError, MissingTranslationError, LocaleNotSpecifiedError, ScriptViolationError, TranslatorNotConfiguredError, BackupLedgerError, BackupCorruptedError, AttestationError, SessionExpiredError, SessionNotFoundError, SessionPolicyError, JoinTooLargeError, CrossJoinTooLargeError, CrossJoinSourceUnknownError, DanglingReferenceError, FilenameSanitizationError, PathEscapeError, DerivationCycleError, DerivationDepthError, DerivationOutputUnknownError, DerivationOutputShapeError, DerivationCapExceededError, MaterializedViewCycleError, MaterializedViewSourceUnknownError, MaterializedViewTooLargeError, MaterializedViewConfigError, OverlayBaseIsVirtualError, OverlayCollectionUnavailableError, OverlayNameCollisionError, OverlayIdMismatchError, SnapshotNotFoundError, UnknownShardError, ShardProvisioningError, VaultTemplateNotFoundError;
|
|
50
50
|
var init_errors = __esm({
|
|
51
51
|
"src/errors.ts"() {
|
|
52
52
|
"use strict";
|
|
@@ -188,6 +188,18 @@ var init_errors = __esm({
|
|
|
188
188
|
this.offendingCollection = offendingCollection;
|
|
189
189
|
}
|
|
190
190
|
};
|
|
191
|
+
ReservedVaultNameError = class extends NoydbError {
|
|
192
|
+
/** The rejected vault name. */
|
|
193
|
+
vaultName;
|
|
194
|
+
constructor(vaultName) {
|
|
195
|
+
super(
|
|
196
|
+
"RESERVED_VAULT_NAME",
|
|
197
|
+
`"${vaultName}" is a reserved internal vault name and cannot be used as a group name or partition key`
|
|
198
|
+
);
|
|
199
|
+
this.name = "ReservedVaultNameError";
|
|
200
|
+
this.vaultName = vaultName;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
191
203
|
PeriodClosedError = class extends NoydbError {
|
|
192
204
|
periodName;
|
|
193
205
|
endDate;
|
|
@@ -340,6 +352,39 @@ var init_errors = __esm({
|
|
|
340
352
|
this.attempts = attempts;
|
|
341
353
|
}
|
|
342
354
|
};
|
|
355
|
+
SequenceContentionError = class extends NoydbError {
|
|
356
|
+
sequence;
|
|
357
|
+
attempts;
|
|
358
|
+
constructor(sequence, attempts) {
|
|
359
|
+
super(
|
|
360
|
+
"SEQUENCE_CONTENTION",
|
|
361
|
+
`vault.sequence("${sequence}").next(): failed to allocate after ${attempts} optimistic-CAS retries`
|
|
362
|
+
);
|
|
363
|
+
this.name = "SequenceContentionError";
|
|
364
|
+
this.sequence = sequence;
|
|
365
|
+
this.attempts = attempts;
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
SequenceOfflineError = class extends NoydbError {
|
|
369
|
+
constructor() {
|
|
370
|
+
super(
|
|
371
|
+
"SEQUENCE_OFFLINE",
|
|
372
|
+
"vault.sequence().next() requires an online CAS-capable store (capabilities.casAtomic). Gap-free numbering cannot be serialized offline."
|
|
373
|
+
);
|
|
374
|
+
this.name = "SequenceOfflineError";
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
NumberingUncertaintyError = class extends NoydbError {
|
|
378
|
+
series;
|
|
379
|
+
constructor(series) {
|
|
380
|
+
super(
|
|
381
|
+
"NUMBERING_UNCERTAINTY",
|
|
382
|
+
`Deferred numbering for series "${series}" cannot run: the store does not expose getStoreTime() (capabilities.serverWriteTime). Use a CAS sequence or a store with serverWriteTime.`
|
|
383
|
+
);
|
|
384
|
+
this.name = "NumberingUncertaintyError";
|
|
385
|
+
this.series = series;
|
|
386
|
+
}
|
|
387
|
+
};
|
|
343
388
|
BundleVersionConflictError = class extends NoydbError {
|
|
344
389
|
/** The bundle handle of the newer remote version that rejected the push. */
|
|
345
390
|
remoteVersion;
|
|
@@ -451,14 +496,14 @@ var init_errors = __esm({
|
|
|
451
496
|
recordId;
|
|
452
497
|
fields;
|
|
453
498
|
conflictingId;
|
|
454
|
-
constructor(collection,
|
|
499
|
+
constructor(collection, recordId3, fields, conflictingId) {
|
|
455
500
|
super(
|
|
456
501
|
"UNIQUE_CONSTRAINT",
|
|
457
|
-
`Unique constraint on ${collection}.[${fields.join(", ")}] violated: record "${
|
|
502
|
+
`Unique constraint on ${collection}.[${fields.join(", ")}] violated: record "${recordId3}" duplicates a value already held by "${conflictingId}".`
|
|
458
503
|
);
|
|
459
504
|
this.name = "UniqueConstraintError";
|
|
460
505
|
this.collection = collection;
|
|
461
|
-
this.recordId =
|
|
506
|
+
this.recordId = recordId3;
|
|
462
507
|
this.fields = fields;
|
|
463
508
|
this.conflictingId = conflictingId;
|
|
464
509
|
}
|
|
@@ -921,6 +966,39 @@ Resolutions:
|
|
|
921
966
|
this.version = version;
|
|
922
967
|
}
|
|
923
968
|
};
|
|
969
|
+
UnknownShardError = class extends NoydbError {
|
|
970
|
+
partitionKey;
|
|
971
|
+
constructor(partitionKey, groupName) {
|
|
972
|
+
super(
|
|
973
|
+
"SHARD_UNKNOWN",
|
|
974
|
+
`No shard for partition key "${partitionKey}" in vault group "${groupName}" and autoCreate is disabled. Call group.createShard(${JSON.stringify(partitionKey)}) first, or enable sharding.autoCreate.`
|
|
975
|
+
);
|
|
976
|
+
this.name = "UnknownShardError";
|
|
977
|
+
this.partitionKey = partitionKey;
|
|
978
|
+
}
|
|
979
|
+
};
|
|
980
|
+
ShardProvisioningError = class extends NoydbError {
|
|
981
|
+
vaultId;
|
|
982
|
+
constructor(vaultId, partitionKey) {
|
|
983
|
+
super(
|
|
984
|
+
"SHARD_PROVISIONING",
|
|
985
|
+
`Registry has a row for partition "${partitionKey}" (vault "${vaultId}") but that vault is not provisioned in the store. Refusing to recreate it \u2014 the registry and store have diverged. Investigate before retrying.`
|
|
986
|
+
);
|
|
987
|
+
this.name = "ShardProvisioningError";
|
|
988
|
+
this.vaultId = vaultId;
|
|
989
|
+
}
|
|
990
|
+
};
|
|
991
|
+
VaultTemplateNotFoundError = class extends NoydbError {
|
|
992
|
+
templateName;
|
|
993
|
+
constructor(templateName) {
|
|
994
|
+
super(
|
|
995
|
+
"VAULT_TEMPLATE_NOT_FOUND",
|
|
996
|
+
`No vault template registered under "${templateName}". Register it with db.withVaultTemplate(${JSON.stringify(templateName)}, { version, configure }) before opening the vault group.`
|
|
997
|
+
);
|
|
998
|
+
this.name = "VaultTemplateNotFoundError";
|
|
999
|
+
this.templateName = templateName;
|
|
1000
|
+
}
|
|
1001
|
+
};
|
|
924
1002
|
}
|
|
925
1003
|
});
|
|
926
1004
|
|
|
@@ -1200,6 +1278,15 @@ var init_crypto = __esm({
|
|
|
1200
1278
|
}
|
|
1201
1279
|
});
|
|
1202
1280
|
|
|
1281
|
+
// src/federation/constants.ts
|
|
1282
|
+
var STATE_VAULT_NAME;
|
|
1283
|
+
var init_constants = __esm({
|
|
1284
|
+
"src/federation/constants.ts"() {
|
|
1285
|
+
"use strict";
|
|
1286
|
+
STATE_VAULT_NAME = "__noydb_state__";
|
|
1287
|
+
}
|
|
1288
|
+
});
|
|
1289
|
+
|
|
1203
1290
|
// src/meta/public-envelope/schema.ts
|
|
1204
1291
|
function validatePublicEnvelopeInput(input, schema) {
|
|
1205
1292
|
const allowed = new Set(schema.fields);
|
|
@@ -1664,7 +1751,7 @@ var init_patch = __esm({
|
|
|
1664
1751
|
|
|
1665
1752
|
// src/history/ledger/constants.ts
|
|
1666
1753
|
var LEDGER_COLLECTION, LEDGER_DELTAS_COLLECTION;
|
|
1667
|
-
var
|
|
1754
|
+
var init_constants2 = __esm({
|
|
1668
1755
|
"src/history/ledger/constants.ts"() {
|
|
1669
1756
|
"use strict";
|
|
1670
1757
|
LEDGER_COLLECTION = "_ledger";
|
|
@@ -1685,7 +1772,7 @@ var init_hash = __esm({
|
|
|
1685
1772
|
});
|
|
1686
1773
|
|
|
1687
1774
|
// src/history/ledger/store.ts
|
|
1688
|
-
function
|
|
1775
|
+
function sleepBackoff2(attempt) {
|
|
1689
1776
|
const base = 5 * Math.pow(2, attempt);
|
|
1690
1777
|
const jitter = Math.random() * base;
|
|
1691
1778
|
return new Promise((resolve) => setTimeout(resolve, base + jitter));
|
|
@@ -1699,7 +1786,7 @@ var init_store = __esm({
|
|
|
1699
1786
|
init_errors();
|
|
1700
1787
|
init_entry();
|
|
1701
1788
|
init_patch();
|
|
1702
|
-
|
|
1789
|
+
init_constants2();
|
|
1703
1790
|
init_hash();
|
|
1704
1791
|
MAX_APPEND_ATTEMPTS = 8;
|
|
1705
1792
|
LedgerStore = class {
|
|
@@ -1807,7 +1894,7 @@ var init_store = __esm({
|
|
|
1807
1894
|
if (err instanceof ConflictError) {
|
|
1808
1895
|
lastConflict = err;
|
|
1809
1896
|
if (attempt < MAX_APPEND_ATTEMPTS - 1) {
|
|
1810
|
-
await
|
|
1897
|
+
await sleepBackoff2(attempt);
|
|
1811
1898
|
}
|
|
1812
1899
|
continue;
|
|
1813
1900
|
}
|
|
@@ -2221,6 +2308,278 @@ var init_public_envelope = __esm({
|
|
|
2221
2308
|
}
|
|
2222
2309
|
});
|
|
2223
2310
|
|
|
2311
|
+
// src/money/fixed-point.ts
|
|
2312
|
+
function expandExponent(s) {
|
|
2313
|
+
const m = /^([+-]?)(\d+)(?:\.(\d+))?[eE]([+-]?\d+)$/.exec(s);
|
|
2314
|
+
if (!m) return s;
|
|
2315
|
+
const sign = m[1] === "-" ? "-" : "";
|
|
2316
|
+
const intp = m[2];
|
|
2317
|
+
const frac = m[3] ?? "";
|
|
2318
|
+
const exp = Number(m[4]);
|
|
2319
|
+
const digits = intp + frac;
|
|
2320
|
+
const pointPos = intp.length + exp;
|
|
2321
|
+
let body;
|
|
2322
|
+
if (pointPos <= 0) {
|
|
2323
|
+
body = "0." + "0".repeat(-pointPos) + digits;
|
|
2324
|
+
} else if (pointPos >= digits.length) {
|
|
2325
|
+
body = digits + "0".repeat(pointPos - digits.length);
|
|
2326
|
+
} else {
|
|
2327
|
+
body = digits.slice(0, pointPos) + "." + digits.slice(pointPos);
|
|
2328
|
+
}
|
|
2329
|
+
return sign + body;
|
|
2330
|
+
}
|
|
2331
|
+
function toCanonicalDecimalString(input) {
|
|
2332
|
+
let s;
|
|
2333
|
+
if (typeof input === "number") {
|
|
2334
|
+
if (!Number.isFinite(input)) return null;
|
|
2335
|
+
s = String(input);
|
|
2336
|
+
} else {
|
|
2337
|
+
s = input.trim();
|
|
2338
|
+
}
|
|
2339
|
+
s = expandExponent(s);
|
|
2340
|
+
if (s.startsWith("+")) s = s.slice(1);
|
|
2341
|
+
if (!/^-?(\d+(\.\d*)?|\.\d+)$/.test(s)) return null;
|
|
2342
|
+
return s;
|
|
2343
|
+
}
|
|
2344
|
+
function shouldRoundUp(negative, lastKeptDigit, firstDiscarded, hasMoreNonZeroAfterFirst, mode) {
|
|
2345
|
+
switch (mode) {
|
|
2346
|
+
case "up":
|
|
2347
|
+
return true;
|
|
2348
|
+
case "down":
|
|
2349
|
+
return false;
|
|
2350
|
+
case "ceil":
|
|
2351
|
+
return !negative;
|
|
2352
|
+
case "floor":
|
|
2353
|
+
return negative;
|
|
2354
|
+
case "half-up":
|
|
2355
|
+
return firstDiscarded >= 5;
|
|
2356
|
+
case "half-down":
|
|
2357
|
+
return firstDiscarded > 5 || firstDiscarded === 5 && hasMoreNonZeroAfterFirst;
|
|
2358
|
+
case "half-even":
|
|
2359
|
+
if (firstDiscarded > 5) return true;
|
|
2360
|
+
if (firstDiscarded < 5) return false;
|
|
2361
|
+
return hasMoreNonZeroAfterFirst || lastKeptDigit % 2 === 1;
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
function parseToScaledInt(input, scale, rounding) {
|
|
2365
|
+
const canonical2 = toCanonicalDecimalString(input);
|
|
2366
|
+
if (canonical2 === null) return { ok: false, reason: "nonfinite" };
|
|
2367
|
+
const negative = canonical2.startsWith("-");
|
|
2368
|
+
const unsigned = negative ? canonical2.slice(1) : canonical2;
|
|
2369
|
+
const dot = unsigned.indexOf(".");
|
|
2370
|
+
const intPart = dot === -1 ? unsigned : unsigned.slice(0, dot);
|
|
2371
|
+
const fracPart = dot === -1 ? "" : unsigned.slice(dot + 1);
|
|
2372
|
+
const intDigits = intPart === "" ? "0" : intPart;
|
|
2373
|
+
if (fracPart.length <= scale) {
|
|
2374
|
+
const keep2 = fracPart.padEnd(scale, "0");
|
|
2375
|
+
const magnitude2 = BigInt(intDigits + keep2);
|
|
2376
|
+
return { ok: true, value: negative && magnitude2 !== 0n ? -magnitude2 : magnitude2 };
|
|
2377
|
+
}
|
|
2378
|
+
const keep = fracPart.slice(0, scale);
|
|
2379
|
+
const tail = fracPart.slice(scale);
|
|
2380
|
+
const magnitudeDigits = intDigits + keep;
|
|
2381
|
+
let magnitude = BigInt(magnitudeDigits);
|
|
2382
|
+
if (/^0+$/.test(tail)) {
|
|
2383
|
+
return { ok: true, value: negative && magnitude !== 0n ? -magnitude : magnitude };
|
|
2384
|
+
}
|
|
2385
|
+
if (rounding === void 0) return { ok: false, reason: "precision" };
|
|
2386
|
+
const lastKeptDigit = Number(magnitudeDigits[magnitudeDigits.length - 1]);
|
|
2387
|
+
const firstDiscarded = Number(tail[0]);
|
|
2388
|
+
const hasMoreNonZeroAfterFirst = /[1-9]/.test(tail.slice(1));
|
|
2389
|
+
if (shouldRoundUp(negative, lastKeptDigit, firstDiscarded, hasMoreNonZeroAfterFirst, rounding)) {
|
|
2390
|
+
magnitude += 1n;
|
|
2391
|
+
}
|
|
2392
|
+
return { ok: true, value: negative && magnitude !== 0n ? -magnitude : magnitude };
|
|
2393
|
+
}
|
|
2394
|
+
function formatScaledInt(value, scale) {
|
|
2395
|
+
const negative = value < 0n;
|
|
2396
|
+
const abs = (negative ? -value : value).toString();
|
|
2397
|
+
if (scale === 0) return (negative ? "-" : "") + abs;
|
|
2398
|
+
const padded = abs.padStart(scale + 1, "0");
|
|
2399
|
+
const cut = padded.length - scale;
|
|
2400
|
+
const intPart = padded.slice(0, cut);
|
|
2401
|
+
const fracPart = padded.slice(cut);
|
|
2402
|
+
return (negative ? "-" : "") + intPart + "." + fracPart;
|
|
2403
|
+
}
|
|
2404
|
+
var init_fixed_point = __esm({
|
|
2405
|
+
"src/money/fixed-point.ts"() {
|
|
2406
|
+
"use strict";
|
|
2407
|
+
}
|
|
2408
|
+
});
|
|
2409
|
+
|
|
2410
|
+
// src/money/iso4217.ts
|
|
2411
|
+
function scaleForCurrency(code) {
|
|
2412
|
+
const v = MINOR_UNITS[code];
|
|
2413
|
+
return v === void 0 ? null : v;
|
|
2414
|
+
}
|
|
2415
|
+
var MINOR_UNITS;
|
|
2416
|
+
var init_iso4217 = __esm({
|
|
2417
|
+
"src/money/iso4217.ts"() {
|
|
2418
|
+
"use strict";
|
|
2419
|
+
MINOR_UNITS = {
|
|
2420
|
+
// 2-decimal majors
|
|
2421
|
+
EUR: 2,
|
|
2422
|
+
USD: 2,
|
|
2423
|
+
GBP: 2,
|
|
2424
|
+
CHF: 2,
|
|
2425
|
+
CAD: 2,
|
|
2426
|
+
AUD: 2,
|
|
2427
|
+
NZD: 2,
|
|
2428
|
+
SGD: 2,
|
|
2429
|
+
HKD: 2,
|
|
2430
|
+
CNY: 2,
|
|
2431
|
+
INR: 2,
|
|
2432
|
+
BRL: 2,
|
|
2433
|
+
MXN: 2,
|
|
2434
|
+
ZAR: 2,
|
|
2435
|
+
RUB: 2,
|
|
2436
|
+
TRY: 2,
|
|
2437
|
+
PLN: 2,
|
|
2438
|
+
SEK: 2,
|
|
2439
|
+
NOK: 2,
|
|
2440
|
+
DKK: 2,
|
|
2441
|
+
CZK: 2,
|
|
2442
|
+
HUF: 2,
|
|
2443
|
+
RON: 2,
|
|
2444
|
+
ILS: 2,
|
|
2445
|
+
THB: 2,
|
|
2446
|
+
PHP: 2,
|
|
2447
|
+
MYR: 2,
|
|
2448
|
+
IDR: 2,
|
|
2449
|
+
AED: 2,
|
|
2450
|
+
SAR: 2,
|
|
2451
|
+
QAR: 2,
|
|
2452
|
+
EGP: 2,
|
|
2453
|
+
// 0-decimal
|
|
2454
|
+
JPY: 0,
|
|
2455
|
+
KRW: 0,
|
|
2456
|
+
ISK: 0,
|
|
2457
|
+
CLP: 0,
|
|
2458
|
+
VND: 0,
|
|
2459
|
+
XOF: 0,
|
|
2460
|
+
XAF: 0,
|
|
2461
|
+
PYG: 0,
|
|
2462
|
+
// 3-decimal
|
|
2463
|
+
BHD: 3,
|
|
2464
|
+
KWD: 3,
|
|
2465
|
+
OMR: 3,
|
|
2466
|
+
TND: 3,
|
|
2467
|
+
JOD: 3,
|
|
2468
|
+
IQD: 3,
|
|
2469
|
+
LYD: 3
|
|
2470
|
+
};
|
|
2471
|
+
}
|
|
2472
|
+
});
|
|
2473
|
+
|
|
2474
|
+
// src/money/descriptor.ts
|
|
2475
|
+
function isMultiOptions(o) {
|
|
2476
|
+
return "currencies" in o;
|
|
2477
|
+
}
|
|
2478
|
+
function money(options) {
|
|
2479
|
+
const hasFixed = "currency" in options;
|
|
2480
|
+
const hasMulti = "currencies" in options;
|
|
2481
|
+
if (hasFixed && hasMulti) {
|
|
2482
|
+
throw new TypeError("money: `currency` and `currencies` are mutually exclusive");
|
|
2483
|
+
}
|
|
2484
|
+
if (!hasFixed && !hasMulti) {
|
|
2485
|
+
throw new TypeError("money: one of `currency` or `currencies` is required");
|
|
2486
|
+
}
|
|
2487
|
+
const rounding = options.rounding;
|
|
2488
|
+
if (isMultiOptions(options)) {
|
|
2489
|
+
const overrides = options.scaleOverrides ?? {};
|
|
2490
|
+
const allowList = options.currencies;
|
|
2491
|
+
const allows = (c) => allowList === "any" ? true : allowList.includes(c);
|
|
2492
|
+
const scaleFor = (c) => {
|
|
2493
|
+
if (!allows(c)) throw new MoneyCurrencyError(c, "not-allowed");
|
|
2494
|
+
const s = overrides[c] ?? scaleForCurrency(c);
|
|
2495
|
+
if (s === null || s === void 0) throw new MoneyCurrencyError(c, "unknown-scale");
|
|
2496
|
+
return s;
|
|
2497
|
+
};
|
|
2498
|
+
if (allowList !== "any") for (const c of allowList) scaleFor(c);
|
|
2499
|
+
const soleCurrency = () => allowList !== "any" && allowList.length === 1 ? allowList[0] : void 0;
|
|
2500
|
+
return {
|
|
2501
|
+
_noydbMoney: true,
|
|
2502
|
+
mode: "multi",
|
|
2503
|
+
options,
|
|
2504
|
+
rounding,
|
|
2505
|
+
fixedCurrency: void 0,
|
|
2506
|
+
scaleFor,
|
|
2507
|
+
allows,
|
|
2508
|
+
soleCurrency
|
|
2509
|
+
};
|
|
2510
|
+
}
|
|
2511
|
+
const currency = options.currency;
|
|
2512
|
+
const resolvedScale = options.scale ?? scaleForCurrency(currency);
|
|
2513
|
+
if (resolvedScale === null || resolvedScale === void 0) {
|
|
2514
|
+
throw new MoneyCurrencyError(currency, "unknown-scale");
|
|
2515
|
+
}
|
|
2516
|
+
return {
|
|
2517
|
+
_noydbMoney: true,
|
|
2518
|
+
mode: "fixed",
|
|
2519
|
+
options,
|
|
2520
|
+
rounding,
|
|
2521
|
+
fixedCurrency: currency,
|
|
2522
|
+
scaleFor(c) {
|
|
2523
|
+
if (c !== currency) throw new MoneyCurrencyError(c, "not-allowed");
|
|
2524
|
+
return resolvedScale;
|
|
2525
|
+
},
|
|
2526
|
+
allows: (c) => c === currency,
|
|
2527
|
+
soleCurrency: () => currency
|
|
2528
|
+
};
|
|
2529
|
+
}
|
|
2530
|
+
function isMoneyDescriptor(x) {
|
|
2531
|
+
return typeof x === "object" && x !== null && x._noydbMoney === true;
|
|
2532
|
+
}
|
|
2533
|
+
var MoneyPrecisionError, MoneyCurrencyError, MoneyUnsupportedError;
|
|
2534
|
+
var init_descriptor = __esm({
|
|
2535
|
+
"src/money/descriptor.ts"() {
|
|
2536
|
+
"use strict";
|
|
2537
|
+
init_iso4217();
|
|
2538
|
+
init_errors();
|
|
2539
|
+
MoneyPrecisionError = class extends NoydbError {
|
|
2540
|
+
constructor(field, value, scale) {
|
|
2541
|
+
super(
|
|
2542
|
+
"MONEY_PRECISION",
|
|
2543
|
+
`money: value ${JSON.stringify(value)} for field "${field}" exceeds scale ${scale} and no rounding mode is configured`
|
|
2544
|
+
);
|
|
2545
|
+
this.field = field;
|
|
2546
|
+
this.value = value;
|
|
2547
|
+
this.scale = scale;
|
|
2548
|
+
this.name = "MoneyPrecisionError";
|
|
2549
|
+
}
|
|
2550
|
+
field;
|
|
2551
|
+
value;
|
|
2552
|
+
scale;
|
|
2553
|
+
};
|
|
2554
|
+
MoneyCurrencyError = class extends NoydbError {
|
|
2555
|
+
constructor(currency, reason, field) {
|
|
2556
|
+
super(
|
|
2557
|
+
"MONEY_CURRENCY",
|
|
2558
|
+
reason === "not-allowed" ? `money: currency "${currency}" is not allowed${field ? ` for field "${field}"` : ""}` : `money: no scale known for currency "${currency}"${field ? ` (field "${field}")` : ""} \u2014 pass an explicit scale / scaleOverrides`
|
|
2559
|
+
);
|
|
2560
|
+
this.currency = currency;
|
|
2561
|
+
this.reason = reason;
|
|
2562
|
+
this.field = field;
|
|
2563
|
+
this.name = "MoneyCurrencyError";
|
|
2564
|
+
}
|
|
2565
|
+
currency;
|
|
2566
|
+
reason;
|
|
2567
|
+
field;
|
|
2568
|
+
};
|
|
2569
|
+
MoneyUnsupportedError = class extends NoydbError {
|
|
2570
|
+
constructor(field, message) {
|
|
2571
|
+
super(
|
|
2572
|
+
"MONEY_UNSUPPORTED",
|
|
2573
|
+
message ?? `money: operation is not supported on field "${field}" \u2014 use sum() and count() and divide at the boundary`
|
|
2574
|
+
);
|
|
2575
|
+
this.field = field;
|
|
2576
|
+
this.name = "MoneyUnsupportedError";
|
|
2577
|
+
}
|
|
2578
|
+
field;
|
|
2579
|
+
};
|
|
2580
|
+
}
|
|
2581
|
+
});
|
|
2582
|
+
|
|
2224
2583
|
// src/team/tiers.ts
|
|
2225
2584
|
function dekKey(collection, tier) {
|
|
2226
2585
|
if (tier <= 0) return collection;
|
|
@@ -2340,6 +2699,189 @@ var init_predicate = __esm({
|
|
|
2340
2699
|
}
|
|
2341
2700
|
});
|
|
2342
2701
|
|
|
2702
|
+
// src/money/money-reducer.ts
|
|
2703
|
+
function toScaledInt(v) {
|
|
2704
|
+
if (typeof v === "string" || typeof v === "number" || typeof v === "bigint") {
|
|
2705
|
+
try {
|
|
2706
|
+
return BigInt(v);
|
|
2707
|
+
} catch {
|
|
2708
|
+
return null;
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
return null;
|
|
2712
|
+
}
|
|
2713
|
+
function readMoney(record, field, desc) {
|
|
2714
|
+
const raw = readPath(record, field);
|
|
2715
|
+
if (raw === null || raw === void 0) return null;
|
|
2716
|
+
if (desc.mode === "fixed") {
|
|
2717
|
+
const value2 = toScaledInt(raw);
|
|
2718
|
+
return value2 === null ? null : { currency: desc.fixedCurrency, value: value2 };
|
|
2719
|
+
}
|
|
2720
|
+
if (typeof raw !== "object") return null;
|
|
2721
|
+
const o = raw;
|
|
2722
|
+
if (typeof o.currency !== "string") return null;
|
|
2723
|
+
const value = toScaledInt(o.amount);
|
|
2724
|
+
return value === null ? null : { currency: o.currency, value };
|
|
2725
|
+
}
|
|
2726
|
+
function targetScaleFor(desc, currency) {
|
|
2727
|
+
if (desc.allows(currency)) return desc.scaleFor(currency);
|
|
2728
|
+
const s = scaleForCurrency(currency);
|
|
2729
|
+
if (s === null) {
|
|
2730
|
+
throw new Error(`money: cannot determine scale for conversion target "${currency}"`);
|
|
2731
|
+
}
|
|
2732
|
+
return s;
|
|
2733
|
+
}
|
|
2734
|
+
function parseRate(rate) {
|
|
2735
|
+
const s = String(rate).trim();
|
|
2736
|
+
const neg = s.startsWith("-");
|
|
2737
|
+
const body = neg ? s.slice(1) : s;
|
|
2738
|
+
const dot = body.indexOf(".");
|
|
2739
|
+
const intPart = dot === -1 ? body : body.slice(0, dot);
|
|
2740
|
+
const fracPart = dot === -1 ? "" : body.slice(dot + 1);
|
|
2741
|
+
const int = BigInt((intPart === "" ? "0" : intPart) + fracPart);
|
|
2742
|
+
return { int: neg ? -int : int, scale: fracPart.length };
|
|
2743
|
+
}
|
|
2744
|
+
function divRoundHalfEven(n, d) {
|
|
2745
|
+
const q = n / d;
|
|
2746
|
+
const r = n % d;
|
|
2747
|
+
const twiceR = (r < 0n ? -r : r) * 2n;
|
|
2748
|
+
if (twiceR < d) return q;
|
|
2749
|
+
if (twiceR > d) return q + (n < 0n ? -1n : 1n);
|
|
2750
|
+
return q % 2n === 0n ? q : q + (n < 0n ? -1n : 1n);
|
|
2751
|
+
}
|
|
2752
|
+
function convertScaled(value, srcScale, rate, targetScale) {
|
|
2753
|
+
const { int: rateInt, scale: rateScale } = parseRate(rate);
|
|
2754
|
+
const product = value * rateInt;
|
|
2755
|
+
const curScale = srcScale + rateScale;
|
|
2756
|
+
if (curScale === targetScale) return product;
|
|
2757
|
+
if (curScale < targetScale) return product * 10n ** BigInt(targetScale - curScale);
|
|
2758
|
+
return divRoundHalfEven(product, 10n ** BigInt(curScale - targetScale));
|
|
2759
|
+
}
|
|
2760
|
+
function finalizeSum(state, desc, convertTo, fx) {
|
|
2761
|
+
if (convertTo !== void 0) {
|
|
2762
|
+
if (fx === void 0) {
|
|
2763
|
+
throw new Error(`money: sum convertTo "${convertTo}" requires an fx rate map`);
|
|
2764
|
+
}
|
|
2765
|
+
const targetScale = targetScaleFor(desc, convertTo);
|
|
2766
|
+
let total = 0n;
|
|
2767
|
+
for (const [cur, v] of state) {
|
|
2768
|
+
if (cur === convertTo) {
|
|
2769
|
+
total += convertScaled(v, desc.scaleFor(cur), 1, targetScale);
|
|
2770
|
+
continue;
|
|
2771
|
+
}
|
|
2772
|
+
const rate = fx[`${cur}->${convertTo}`];
|
|
2773
|
+
if (rate === void 0) {
|
|
2774
|
+
throw new Error(`money: no fx rate for "${cur}->${convertTo}"`);
|
|
2775
|
+
}
|
|
2776
|
+
total += convertScaled(v, desc.scaleFor(cur), rate, targetScale);
|
|
2777
|
+
}
|
|
2778
|
+
return formatScaledInt(total, targetScale);
|
|
2779
|
+
}
|
|
2780
|
+
if (desc.mode === "fixed") {
|
|
2781
|
+
const cur = desc.fixedCurrency;
|
|
2782
|
+
return formatScaledInt(state.get(cur) ?? 0n, desc.scaleFor(cur));
|
|
2783
|
+
}
|
|
2784
|
+
const out = {};
|
|
2785
|
+
for (const [cur, v] of state) out[cur] = formatScaledInt(v, desc.scaleFor(cur));
|
|
2786
|
+
return out;
|
|
2787
|
+
}
|
|
2788
|
+
function moneySumReducer(field, desc, convertTo, fx) {
|
|
2789
|
+
return {
|
|
2790
|
+
op: "sum",
|
|
2791
|
+
field,
|
|
2792
|
+
init: () => /* @__PURE__ */ new Map(),
|
|
2793
|
+
step: (state, record) => {
|
|
2794
|
+
const m = readMoney(record, field, desc);
|
|
2795
|
+
if (m) state.set(m.currency, (state.get(m.currency) ?? 0n) + m.value);
|
|
2796
|
+
return state;
|
|
2797
|
+
},
|
|
2798
|
+
remove: (state, record) => {
|
|
2799
|
+
const m = readMoney(record, field, desc);
|
|
2800
|
+
if (m) state.set(m.currency, (state.get(m.currency) ?? 0n) - m.value);
|
|
2801
|
+
return state;
|
|
2802
|
+
},
|
|
2803
|
+
finalize: (state) => finalizeSum(state, desc, convertTo, fx)
|
|
2804
|
+
};
|
|
2805
|
+
}
|
|
2806
|
+
function extremum(values, op) {
|
|
2807
|
+
let out = values[0];
|
|
2808
|
+
for (let i = 1; i < values.length; i++) {
|
|
2809
|
+
const v = values[i];
|
|
2810
|
+
if (op === "min" ? v < out : v > out) out = v;
|
|
2811
|
+
}
|
|
2812
|
+
return out;
|
|
2813
|
+
}
|
|
2814
|
+
function moneyMinMaxReducer(op, field, desc) {
|
|
2815
|
+
return {
|
|
2816
|
+
op,
|
|
2817
|
+
field,
|
|
2818
|
+
init: () => /* @__PURE__ */ new Map(),
|
|
2819
|
+
step: (state, record) => {
|
|
2820
|
+
const m = readMoney(record, field, desc);
|
|
2821
|
+
if (m) {
|
|
2822
|
+
const arr = state.get(m.currency);
|
|
2823
|
+
if (arr) arr.push(m.value);
|
|
2824
|
+
else state.set(m.currency, [m.value]);
|
|
2825
|
+
}
|
|
2826
|
+
return state;
|
|
2827
|
+
},
|
|
2828
|
+
remove: (state, record) => {
|
|
2829
|
+
const m = readMoney(record, field, desc);
|
|
2830
|
+
if (m) {
|
|
2831
|
+
const arr = state.get(m.currency);
|
|
2832
|
+
if (arr) {
|
|
2833
|
+
const idx = arr.indexOf(m.value);
|
|
2834
|
+
if (idx >= 0) arr.splice(idx, 1);
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
return state;
|
|
2838
|
+
},
|
|
2839
|
+
finalize: (state) => {
|
|
2840
|
+
if (desc.mode === "fixed") {
|
|
2841
|
+
const cur = desc.fixedCurrency;
|
|
2842
|
+
const arr = state.get(cur);
|
|
2843
|
+
if (!arr || arr.length === 0) return null;
|
|
2844
|
+
return formatScaledInt(extremum(arr, op), desc.scaleFor(cur));
|
|
2845
|
+
}
|
|
2846
|
+
const out = {};
|
|
2847
|
+
for (const [cur, arr] of state) {
|
|
2848
|
+
if (arr.length > 0) out[cur] = formatScaledInt(extremum(arr, op), desc.scaleFor(cur));
|
|
2849
|
+
}
|
|
2850
|
+
return out;
|
|
2851
|
+
}
|
|
2852
|
+
};
|
|
2853
|
+
}
|
|
2854
|
+
function wrapMoneyReducers(spec, moneyFields) {
|
|
2855
|
+
let changed = false;
|
|
2856
|
+
const out = {};
|
|
2857
|
+
for (const [key, reducer] of Object.entries(spec)) {
|
|
2858
|
+
const field = reducer.field;
|
|
2859
|
+
const desc = field ? moneyFields[field] : void 0;
|
|
2860
|
+
if (desc && reducer.op === "avg") {
|
|
2861
|
+
throw new MoneyUnsupportedError(
|
|
2862
|
+
field,
|
|
2863
|
+
`avg() is not supported on money field "${field}" in v1 \u2014 use sum() and count() and divide at the boundary.`
|
|
2864
|
+
);
|
|
2865
|
+
}
|
|
2866
|
+
if (desc && (reducer.op === "sum" || reducer.op === "min" || reducer.op === "max")) {
|
|
2867
|
+
changed = true;
|
|
2868
|
+
out[key] = reducer.op === "sum" ? moneySumReducer(field, desc, reducer.convertTo, reducer.fx) : moneyMinMaxReducer(reducer.op, field, desc);
|
|
2869
|
+
} else {
|
|
2870
|
+
out[key] = reducer;
|
|
2871
|
+
}
|
|
2872
|
+
}
|
|
2873
|
+
return changed ? out : spec;
|
|
2874
|
+
}
|
|
2875
|
+
var init_money_reducer = __esm({
|
|
2876
|
+
"src/money/money-reducer.ts"() {
|
|
2877
|
+
"use strict";
|
|
2878
|
+
init_predicate();
|
|
2879
|
+
init_fixed_point();
|
|
2880
|
+
init_iso4217();
|
|
2881
|
+
init_descriptor();
|
|
2882
|
+
}
|
|
2883
|
+
});
|
|
2884
|
+
|
|
2343
2885
|
// src/aggregate/aggregation.ts
|
|
2344
2886
|
function reduceRecords(records, spec) {
|
|
2345
2887
|
const state = {};
|
|
@@ -2557,19 +3099,22 @@ var init_groupby = __esm({
|
|
|
2557
3099
|
init_aggregation();
|
|
2558
3100
|
init_canonical_key();
|
|
2559
3101
|
init_errors();
|
|
3102
|
+
init_money_reducer();
|
|
2560
3103
|
GROUPBY_WARN_CARDINALITY = 1e4;
|
|
2561
3104
|
GROUPBY_MAX_CARDINALITY = 1e5;
|
|
2562
3105
|
warnedCardinalityFields = /* @__PURE__ */ new Set();
|
|
2563
3106
|
GroupedQueryBase = class {
|
|
2564
|
-
constructor(executeRecords, fieldOrFields, upstreams, dictLabelResolver) {
|
|
3107
|
+
constructor(executeRecords, fieldOrFields, upstreams, dictLabelResolver, moneyFields) {
|
|
2565
3108
|
this.executeRecords = executeRecords;
|
|
2566
3109
|
this.upstreams = upstreams;
|
|
2567
3110
|
this.dictLabelResolver = dictLabelResolver;
|
|
3111
|
+
this.moneyFields = moneyFields;
|
|
2568
3112
|
this.fields = typeof fieldOrFields === "string" ? [fieldOrFields] : [...fieldOrFields];
|
|
2569
3113
|
}
|
|
2570
3114
|
executeRecords;
|
|
2571
3115
|
upstreams;
|
|
2572
3116
|
dictLabelResolver;
|
|
3117
|
+
moneyFields;
|
|
2573
3118
|
/**
|
|
2574
3119
|
* Field set this grouped query buckets on. Stored in declaration
|
|
2575
3120
|
* order — the same order is preserved on every result row by
|
|
@@ -2577,6 +3122,10 @@ var init_groupby = __esm({
|
|
|
2577
3122
|
* `[field]`.
|
|
2578
3123
|
*/
|
|
2579
3124
|
fields;
|
|
3125
|
+
/** Apply money-aware reducer rewriting when money fields are declared. */
|
|
3126
|
+
wrapSpec(spec) {
|
|
3127
|
+
return this.moneyFields ? wrapMoneyReducers(spec, this.moneyFields) : spec;
|
|
3128
|
+
}
|
|
2580
3129
|
};
|
|
2581
3130
|
GroupedQuery = class extends GroupedQueryBase {
|
|
2582
3131
|
/**
|
|
@@ -2589,7 +3138,7 @@ var init_groupby = __esm({
|
|
|
2589
3138
|
return new GroupedAggregation(
|
|
2590
3139
|
this.executeRecords,
|
|
2591
3140
|
this.fields,
|
|
2592
|
-
spec,
|
|
3141
|
+
this.wrapSpec(spec),
|
|
2593
3142
|
this.upstreams,
|
|
2594
3143
|
this.dictLabelResolver
|
|
2595
3144
|
);
|
|
@@ -2600,7 +3149,7 @@ var init_groupby = __esm({
|
|
|
2600
3149
|
return new GroupedAggregation(
|
|
2601
3150
|
this.executeRecords,
|
|
2602
3151
|
this.fields,
|
|
2603
|
-
spec,
|
|
3152
|
+
this.wrapSpec(spec),
|
|
2604
3153
|
this.upstreams,
|
|
2605
3154
|
this.dictLabelResolver
|
|
2606
3155
|
);
|
|
@@ -2716,14 +3265,21 @@ var init_executor = __esm({
|
|
|
2716
3265
|
* Compare existing vs incoming for each `frozenFields.fields` entry
|
|
2717
3266
|
* when `frozenFields.when(existing)` is true. Throws
|
|
2718
3267
|
* `FieldFrozenError` listing every changed frozen field.
|
|
3268
|
+
*
|
|
3269
|
+
* @param skipFields — field names that are schema-owned computed fields.
|
|
3270
|
+
* These are excluded from the comparison because `incoming` carries the
|
|
3271
|
+
* raw user input (computed fields not yet evaluated), so comparing
|
|
3272
|
+
* `existing[field]` vs `incoming[field]` would always look like a
|
|
3273
|
+
* change even when the computed result is unchanged.
|
|
2719
3274
|
*/
|
|
2720
|
-
async checkFrozenFields(guard, id, existing, incoming) {
|
|
3275
|
+
async checkFrozenFields(guard, id, existing, incoming, skipFields) {
|
|
2721
3276
|
const ff = guard.frozenFields;
|
|
2722
3277
|
if (!ff) return;
|
|
2723
3278
|
if (existing === null) return;
|
|
2724
3279
|
if (!ff.when(existing)) return;
|
|
2725
3280
|
const changed = [];
|
|
2726
3281
|
for (const f of ff.fields) {
|
|
3282
|
+
if (skipFields?.has(String(f))) continue;
|
|
2727
3283
|
if (existing[f] !== incoming[f]) {
|
|
2728
3284
|
if (!deepEqual2(existing[f], incoming[f])) changed.push(String(f));
|
|
2729
3285
|
}
|
|
@@ -2942,12 +3498,12 @@ var init_dependency_analyzer = __esm({
|
|
|
2942
3498
|
|
|
2943
3499
|
// src/materialized-views/query-hash.ts
|
|
2944
3500
|
async function computeQueryHash(mvName, dependencies, queryPlanSummary) {
|
|
2945
|
-
const
|
|
3501
|
+
const canonical2 = JSON.stringify({
|
|
2946
3502
|
mvName,
|
|
2947
3503
|
dependencies: [...dependencies].sort(),
|
|
2948
3504
|
queryPlanSummary
|
|
2949
3505
|
});
|
|
2950
|
-
const bytes = new TextEncoder().encode(
|
|
3506
|
+
const bytes = new TextEncoder().encode(canonical2);
|
|
2951
3507
|
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
2952
3508
|
return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2953
3509
|
}
|
|
@@ -3786,12 +4342,12 @@ var init_read_only_facade = __esm({
|
|
|
3786
4342
|
|
|
3787
4343
|
// src/derivations/strategy-hash.ts
|
|
3788
4344
|
async function computeStrategyHash(source, outputKeys, derive) {
|
|
3789
|
-
const
|
|
4345
|
+
const canonical2 = JSON.stringify({
|
|
3790
4346
|
source,
|
|
3791
4347
|
outputs: [...outputKeys].sort(),
|
|
3792
4348
|
derive: derive.toString()
|
|
3793
4349
|
});
|
|
3794
|
-
const bytes = new TextEncoder().encode(
|
|
4350
|
+
const bytes = new TextEncoder().encode(canonical2);
|
|
3795
4351
|
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
3796
4352
|
return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
3797
4353
|
}
|
|
@@ -4052,6 +4608,669 @@ var init_delegation = __esm({
|
|
|
4052
4608
|
}
|
|
4053
4609
|
});
|
|
4054
4610
|
|
|
4611
|
+
// src/federation/classify-skip.ts
|
|
4612
|
+
function classifyShardSkip(err) {
|
|
4613
|
+
return err instanceof NoAccessError ? "no-grant" : "error";
|
|
4614
|
+
}
|
|
4615
|
+
var init_classify_skip = __esm({
|
|
4616
|
+
"src/federation/classify-skip.ts"() {
|
|
4617
|
+
"use strict";
|
|
4618
|
+
init_errors();
|
|
4619
|
+
}
|
|
4620
|
+
});
|
|
4621
|
+
|
|
4622
|
+
// src/federation/cross-vault-live.ts
|
|
4623
|
+
var CrossVaultLive;
|
|
4624
|
+
var init_cross_vault_live = __esm({
|
|
4625
|
+
"src/federation/cross-vault-live.ts"() {
|
|
4626
|
+
"use strict";
|
|
4627
|
+
CrossVaultLive = class {
|
|
4628
|
+
snapshot;
|
|
4629
|
+
error = null;
|
|
4630
|
+
ready;
|
|
4631
|
+
subs = /* @__PURE__ */ new Set();
|
|
4632
|
+
unsubChange;
|
|
4633
|
+
opts;
|
|
4634
|
+
stopped = false;
|
|
4635
|
+
computing = false;
|
|
4636
|
+
dirty = false;
|
|
4637
|
+
scheduled = false;
|
|
4638
|
+
timer = null;
|
|
4639
|
+
resolveReady;
|
|
4640
|
+
settledOnce = false;
|
|
4641
|
+
constructor(opts) {
|
|
4642
|
+
this.opts = opts;
|
|
4643
|
+
this.snapshot = opts.initialSnapshot;
|
|
4644
|
+
this.ready = new Promise((res) => {
|
|
4645
|
+
this.resolveReady = res;
|
|
4646
|
+
});
|
|
4647
|
+
this.unsubChange = opts.subscribeToChanges((e) => {
|
|
4648
|
+
if (this.stopped || !opts.isRelevant(e)) return;
|
|
4649
|
+
this.schedule();
|
|
4650
|
+
});
|
|
4651
|
+
this.schedule();
|
|
4652
|
+
}
|
|
4653
|
+
subscribe(cb) {
|
|
4654
|
+
if (this.stopped) return () => {
|
|
4655
|
+
};
|
|
4656
|
+
this.subs.add(cb);
|
|
4657
|
+
return () => this.subs.delete(cb);
|
|
4658
|
+
}
|
|
4659
|
+
stop() {
|
|
4660
|
+
if (this.stopped) return;
|
|
4661
|
+
this.stopped = true;
|
|
4662
|
+
this.unsubChange();
|
|
4663
|
+
if (this.timer !== null) clearTimeout(this.timer);
|
|
4664
|
+
this.subs.clear();
|
|
4665
|
+
if (!this.settledOnce) this.resolveReady();
|
|
4666
|
+
}
|
|
4667
|
+
schedule() {
|
|
4668
|
+
if (this.stopped) return;
|
|
4669
|
+
if (this.computing) {
|
|
4670
|
+
this.dirty = true;
|
|
4671
|
+
return;
|
|
4672
|
+
}
|
|
4673
|
+
if (this.scheduled) return;
|
|
4674
|
+
this.scheduled = true;
|
|
4675
|
+
const run = () => {
|
|
4676
|
+
this.scheduled = false;
|
|
4677
|
+
void this.runCompute();
|
|
4678
|
+
};
|
|
4679
|
+
const ms = this.opts.debounceMs ?? 0;
|
|
4680
|
+
if (ms > 0) this.timer = setTimeout(run, ms);
|
|
4681
|
+
else queueMicrotask(run);
|
|
4682
|
+
}
|
|
4683
|
+
async runCompute() {
|
|
4684
|
+
if (this.stopped) return;
|
|
4685
|
+
this.computing = true;
|
|
4686
|
+
this.dirty = false;
|
|
4687
|
+
try {
|
|
4688
|
+
const next = await this.opts.compute();
|
|
4689
|
+
if (this.stopped) return;
|
|
4690
|
+
this.snapshot = next;
|
|
4691
|
+
this.error = null;
|
|
4692
|
+
} catch (err) {
|
|
4693
|
+
if (this.stopped) return;
|
|
4694
|
+
this.error = err instanceof Error ? err : new Error(String(err));
|
|
4695
|
+
} finally {
|
|
4696
|
+
this.computing = false;
|
|
4697
|
+
if (!this.stopped) {
|
|
4698
|
+
if (!this.settledOnce) {
|
|
4699
|
+
this.settledOnce = true;
|
|
4700
|
+
this.resolveReady();
|
|
4701
|
+
}
|
|
4702
|
+
for (const cb of this.subs) cb();
|
|
4703
|
+
if (this.dirty) this.schedule();
|
|
4704
|
+
}
|
|
4705
|
+
}
|
|
4706
|
+
}
|
|
4707
|
+
};
|
|
4708
|
+
}
|
|
4709
|
+
});
|
|
4710
|
+
|
|
4711
|
+
// src/federation/aggregate-across.ts
|
|
4712
|
+
var CrossVaultAggregation, CrossVaultGroupedAggregation;
|
|
4713
|
+
var init_aggregate_across = __esm({
|
|
4714
|
+
"src/federation/aggregate-across.ts"() {
|
|
4715
|
+
"use strict";
|
|
4716
|
+
init_aggregation();
|
|
4717
|
+
init_groupby();
|
|
4718
|
+
init_cross_vault_live();
|
|
4719
|
+
CrossVaultAggregation = class {
|
|
4720
|
+
constructor(src, spec, bind) {
|
|
4721
|
+
this.src = src;
|
|
4722
|
+
this.spec = spec;
|
|
4723
|
+
this.bind = bind;
|
|
4724
|
+
}
|
|
4725
|
+
src;
|
|
4726
|
+
spec;
|
|
4727
|
+
bind;
|
|
4728
|
+
async run(options = {}) {
|
|
4729
|
+
const { records, skippedVaults } = await this.src.fanoutRecords(options);
|
|
4730
|
+
return { result: reduceRecords(records, this.spec), skippedVaults };
|
|
4731
|
+
}
|
|
4732
|
+
live(options = {}) {
|
|
4733
|
+
if (!this.bind) throw new Error("CrossVaultAggregation: live() requires a LiveBinding \u2014 use ShardedQuery.aggregate()");
|
|
4734
|
+
const spec = this.spec;
|
|
4735
|
+
const src = this.src;
|
|
4736
|
+
const core = new CrossVaultLive({
|
|
4737
|
+
subscribeToChanges: this.bind.subscribeToChanges,
|
|
4738
|
+
isRelevant: this.bind.isRelevant,
|
|
4739
|
+
compute: async () => {
|
|
4740
|
+
const { records, skippedVaults } = await src.fanoutRecords(options);
|
|
4741
|
+
return { value: reduceRecords(records, spec), skipped: skippedVaults };
|
|
4742
|
+
},
|
|
4743
|
+
initialSnapshot: { value: void 0, skipped: [] },
|
|
4744
|
+
...options.debounceMs !== void 0 ? { debounceMs: options.debounceMs } : {}
|
|
4745
|
+
});
|
|
4746
|
+
return {
|
|
4747
|
+
get value() {
|
|
4748
|
+
return core.snapshot.value;
|
|
4749
|
+
},
|
|
4750
|
+
get skippedVaults() {
|
|
4751
|
+
return core.snapshot.skipped;
|
|
4752
|
+
},
|
|
4753
|
+
get error() {
|
|
4754
|
+
return core.error;
|
|
4755
|
+
},
|
|
4756
|
+
ready: core.ready,
|
|
4757
|
+
subscribe: (cb) => core.subscribe(cb),
|
|
4758
|
+
stop: () => core.stop()
|
|
4759
|
+
};
|
|
4760
|
+
}
|
|
4761
|
+
};
|
|
4762
|
+
CrossVaultGroupedAggregation = class {
|
|
4763
|
+
constructor(src, field, spec, bind) {
|
|
4764
|
+
this.src = src;
|
|
4765
|
+
this.field = field;
|
|
4766
|
+
this.spec = spec;
|
|
4767
|
+
this.bind = bind;
|
|
4768
|
+
}
|
|
4769
|
+
src;
|
|
4770
|
+
field;
|
|
4771
|
+
spec;
|
|
4772
|
+
bind;
|
|
4773
|
+
async run(options = {}) {
|
|
4774
|
+
const { records, skippedVaults } = await this.src.fanoutRecords(options);
|
|
4775
|
+
return {
|
|
4776
|
+
results: groupAndReduce(records, this.field, this.spec),
|
|
4777
|
+
skippedVaults
|
|
4778
|
+
};
|
|
4779
|
+
}
|
|
4780
|
+
live(options = {}) {
|
|
4781
|
+
if (!this.bind) throw new Error("CrossVaultGroupedAggregation: live() requires a LiveBinding \u2014 use ShardedQuery.groupBy().aggregate()");
|
|
4782
|
+
const field = this.field;
|
|
4783
|
+
const spec = this.spec;
|
|
4784
|
+
const src = this.src;
|
|
4785
|
+
const core = new CrossVaultLive({
|
|
4786
|
+
subscribeToChanges: this.bind.subscribeToChanges,
|
|
4787
|
+
isRelevant: this.bind.isRelevant,
|
|
4788
|
+
compute: async () => {
|
|
4789
|
+
const { records, skippedVaults } = await src.fanoutRecords(options);
|
|
4790
|
+
return {
|
|
4791
|
+
records: groupAndReduce(records, field, spec),
|
|
4792
|
+
skipped: skippedVaults
|
|
4793
|
+
};
|
|
4794
|
+
},
|
|
4795
|
+
initialSnapshot: { records: [], skipped: [] },
|
|
4796
|
+
...options.debounceMs !== void 0 ? { debounceMs: options.debounceMs } : {}
|
|
4797
|
+
});
|
|
4798
|
+
return {
|
|
4799
|
+
get value() {
|
|
4800
|
+
return core.snapshot.records;
|
|
4801
|
+
},
|
|
4802
|
+
get skippedVaults() {
|
|
4803
|
+
return core.snapshot.skipped;
|
|
4804
|
+
},
|
|
4805
|
+
get error() {
|
|
4806
|
+
return core.error;
|
|
4807
|
+
},
|
|
4808
|
+
ready: core.ready,
|
|
4809
|
+
subscribe: (cb) => core.subscribe(cb),
|
|
4810
|
+
stop: () => core.stop()
|
|
4811
|
+
};
|
|
4812
|
+
}
|
|
4813
|
+
};
|
|
4814
|
+
}
|
|
4815
|
+
});
|
|
4816
|
+
|
|
4817
|
+
// src/federation/vault-group.ts
|
|
4818
|
+
var vault_group_exports = {};
|
|
4819
|
+
__export(vault_group_exports, {
|
|
4820
|
+
ShardedCollection: () => ShardedCollection,
|
|
4821
|
+
ShardedGroupedQuery: () => ShardedGroupedQuery,
|
|
4822
|
+
ShardedQuery: () => ShardedQuery,
|
|
4823
|
+
VaultGroup: () => VaultGroup
|
|
4824
|
+
});
|
|
4825
|
+
function assertSafePartitionKey(partitionKey) {
|
|
4826
|
+
if (partitionKey.length === 0) {
|
|
4827
|
+
throw new ValidationError("partitionKey must be a non-empty string");
|
|
4828
|
+
}
|
|
4829
|
+
if (partitionKey === STATE_VAULT_NAME) {
|
|
4830
|
+
throw new ReservedVaultNameError(partitionKey);
|
|
4831
|
+
}
|
|
4832
|
+
if (!SAFE_PARTITION_KEY.test(partitionKey)) {
|
|
4833
|
+
throw new ValidationError(
|
|
4834
|
+
`partitionKey "${partitionKey}" contains characters outside [A-Za-z0-9._-]. Map your records to a store-safe key in sharding.keyOf.`
|
|
4835
|
+
);
|
|
4836
|
+
}
|
|
4837
|
+
if (partitionKey.includes(SHARD_SEPARATOR)) {
|
|
4838
|
+
throw new ValidationError(
|
|
4839
|
+
`partitionKey "${partitionKey}" must not contain "--" \u2014 it is reserved as the shard vault-id separator and would risk shard-id collisions.`
|
|
4840
|
+
);
|
|
4841
|
+
}
|
|
4842
|
+
}
|
|
4843
|
+
var SHARD_SEPARATOR, SAFE_PARTITION_KEY, VaultGroup, ShardedCollection, ShardedQuery, ShardedGroupedQuery;
|
|
4844
|
+
var init_vault_group = __esm({
|
|
4845
|
+
"src/federation/vault-group.ts"() {
|
|
4846
|
+
"use strict";
|
|
4847
|
+
init_errors();
|
|
4848
|
+
init_constants();
|
|
4849
|
+
init_classify_skip();
|
|
4850
|
+
init_cross_vault_live();
|
|
4851
|
+
init_aggregate_across();
|
|
4852
|
+
SHARD_SEPARATOR = "--";
|
|
4853
|
+
SAFE_PARTITION_KEY = /^[A-Za-z0-9._-]+$/;
|
|
4854
|
+
VaultGroup = class {
|
|
4855
|
+
constructor(db, name, registry, sharding, template) {
|
|
4856
|
+
this.db = db;
|
|
4857
|
+
this.name = name;
|
|
4858
|
+
this.registry = registry;
|
|
4859
|
+
this.sharding = sharding;
|
|
4860
|
+
this.template = template;
|
|
4861
|
+
if (name.includes(SHARD_SEPARATOR)) {
|
|
4862
|
+
throw new ValidationError(
|
|
4863
|
+
`VaultGroup name "${name}" must not contain "--" (reserved shard vault-id separator).`
|
|
4864
|
+
);
|
|
4865
|
+
}
|
|
4866
|
+
}
|
|
4867
|
+
db;
|
|
4868
|
+
name;
|
|
4869
|
+
registry;
|
|
4870
|
+
sharding;
|
|
4871
|
+
template;
|
|
4872
|
+
/** @internal — set when the group is managed (no explicit registry). */
|
|
4873
|
+
stateVault;
|
|
4874
|
+
/** @internal */
|
|
4875
|
+
_attachStateVault(sv) {
|
|
4876
|
+
this.stateVault = sv;
|
|
4877
|
+
}
|
|
4878
|
+
/** Deterministic vault name for a partition key, namespaced by the group. */
|
|
4879
|
+
shardVaultId(partitionKey) {
|
|
4880
|
+
assertSafePartitionKey(partitionKey);
|
|
4881
|
+
return `${this.name}${SHARD_SEPARATOR}${partitionKey}`;
|
|
4882
|
+
}
|
|
4883
|
+
/**
|
|
4884
|
+
* @internal — group-qualified registry record key (avoids cross-group key
|
|
4885
|
+
* collisions). Identical to the shard vault id by design — the registry row
|
|
4886
|
+
* for a shard is keyed by that shard's vault id — so it delegates to
|
|
4887
|
+
* `shardVaultId`, reusing its partition-key validation.
|
|
4888
|
+
*/
|
|
4889
|
+
registryId(partitionKey) {
|
|
4890
|
+
return this.shardVaultId(partitionKey);
|
|
4891
|
+
}
|
|
4892
|
+
/**
|
|
4893
|
+
* Registry rows for THIS group (hydrates the registry collection first).
|
|
4894
|
+
* The registry may be shared across groups (the auto-wired StateManagement
|
|
4895
|
+
* vault holds one `vaultRegistry` for the whole instance), so rows are
|
|
4896
|
+
* filtered by `group` — without this, a group's fan-out reads would leak
|
|
4897
|
+
* across into other groups' shards. Mirrors the `${group}--` scoping that
|
|
4898
|
+
* `liveBinding().isRelevant` already applies to the reactive path.
|
|
4899
|
+
*/
|
|
4900
|
+
async allRows() {
|
|
4901
|
+
await this.registry.list();
|
|
4902
|
+
const rows = this.registry.query().toArray();
|
|
4903
|
+
return rows.filter((r) => r.group === this.name);
|
|
4904
|
+
}
|
|
4905
|
+
/** Open an existing shard and apply the template. */
|
|
4906
|
+
async openShard(partitionKey) {
|
|
4907
|
+
const vault = await this.db.openVault(this.shardVaultId(partitionKey), { create: false });
|
|
4908
|
+
this.template.configure(vault);
|
|
4909
|
+
return vault;
|
|
4910
|
+
}
|
|
4911
|
+
/**
|
|
4912
|
+
* Idempotently provision a shard for `partitionKey`. Returns the
|
|
4913
|
+
* configured vault handle.
|
|
4914
|
+
*
|
|
4915
|
+
* - row + vault present → no-op, return handle
|
|
4916
|
+
* - row present, vault gone → ShardProvisioningError
|
|
4917
|
+
* - row absent (vault present or not) → open-or-create, configure, write row
|
|
4918
|
+
*/
|
|
4919
|
+
async createShard(partitionKey) {
|
|
4920
|
+
const vaultId = this.shardVaultId(partitionKey);
|
|
4921
|
+
const row = await this.registry.get(this.registryId(partitionKey));
|
|
4922
|
+
const provisioned = await this.db._shardVaultProvisioned(vaultId);
|
|
4923
|
+
if (row && !provisioned) throw new ShardProvisioningError(vaultId, partitionKey);
|
|
4924
|
+
if (row && provisioned) return this.openShard(partitionKey);
|
|
4925
|
+
const vault = await this.db.openVault(vaultId);
|
|
4926
|
+
this.template.configure(vault);
|
|
4927
|
+
await this.registry.put(this.registryId(partitionKey), {
|
|
4928
|
+
vaultId,
|
|
4929
|
+
partitionKey,
|
|
4930
|
+
templateName: this.sharding.vaultTemplate,
|
|
4931
|
+
schemaVersion: this.template.version,
|
|
4932
|
+
createdAt: Date.now(),
|
|
4933
|
+
group: this.name
|
|
4934
|
+
});
|
|
4935
|
+
if (this.stateVault) {
|
|
4936
|
+
try {
|
|
4937
|
+
await this.stateVault.appendEvent({
|
|
4938
|
+
type: "shard-created",
|
|
4939
|
+
group: this.name,
|
|
4940
|
+
vaultId,
|
|
4941
|
+
templateName: this.sharding.vaultTemplate,
|
|
4942
|
+
version: this.template.version
|
|
4943
|
+
});
|
|
4944
|
+
} catch {
|
|
4945
|
+
}
|
|
4946
|
+
}
|
|
4947
|
+
return vault;
|
|
4948
|
+
}
|
|
4949
|
+
/**
|
|
4950
|
+
* Drill down to a single shard's full Collection API. Throws if the shard is unknown.
|
|
4951
|
+
* Also throws ShardProvisioningError if the registry row exists but the vault has been deleted
|
|
4952
|
+
* (registry/store divergence).
|
|
4953
|
+
*/
|
|
4954
|
+
async shard(partitionKey) {
|
|
4955
|
+
const vaultId = this.shardVaultId(partitionKey);
|
|
4956
|
+
const row = await this.registry.get(this.registryId(partitionKey));
|
|
4957
|
+
if (!row) throw new UnknownShardError(partitionKey, this.name);
|
|
4958
|
+
const provisioned = await this.db._shardVaultProvisioned(vaultId);
|
|
4959
|
+
if (!provisioned) throw new ShardProvisioningError(vaultId, partitionKey);
|
|
4960
|
+
return this.openShard(partitionKey);
|
|
4961
|
+
}
|
|
4962
|
+
/** A sharded view over one logical collection across all shards. */
|
|
4963
|
+
collection(collectionName) {
|
|
4964
|
+
return new ShardedCollection(this, collectionName);
|
|
4965
|
+
}
|
|
4966
|
+
/** @internal — eligible (openable-candidate) rows + drift/divergence skips. */
|
|
4967
|
+
async resolveEligible(options = {}) {
|
|
4968
|
+
const rows = await this.allRows();
|
|
4969
|
+
const skipped = [];
|
|
4970
|
+
const versionOk = [];
|
|
4971
|
+
for (const row of rows) {
|
|
4972
|
+
if (options.minVersion !== void 0 && row.schemaVersion < options.minVersion) {
|
|
4973
|
+
skipped.push({ vaultId: row.vaultId, reason: "schema-drift" });
|
|
4974
|
+
} else versionOk.push(row);
|
|
4975
|
+
}
|
|
4976
|
+
const provisioned = await Promise.all(versionOk.map((r) => this.db._shardVaultProvisioned(r.vaultId)));
|
|
4977
|
+
const eligible = [];
|
|
4978
|
+
versionOk.forEach((row, i) => {
|
|
4979
|
+
if (provisioned[i]) eligible.push(row);
|
|
4980
|
+
else skipped.push({ vaultId: row.vaultId, reason: "error", error: new ShardProvisioningError(row.vaultId, row.partitionKey) });
|
|
4981
|
+
});
|
|
4982
|
+
return { eligible, skipped };
|
|
4983
|
+
}
|
|
4984
|
+
};
|
|
4985
|
+
ShardedCollection = class {
|
|
4986
|
+
constructor(group, collectionName) {
|
|
4987
|
+
this.group = group;
|
|
4988
|
+
this.collectionName = collectionName;
|
|
4989
|
+
}
|
|
4990
|
+
group;
|
|
4991
|
+
collectionName;
|
|
4992
|
+
/** Route a write to the shard owning `keyOf(record)`. */
|
|
4993
|
+
async put(id, record) {
|
|
4994
|
+
const key = this.group.sharding.keyOf(record);
|
|
4995
|
+
const row = await this.group.registry.get(this.group.registryId(key));
|
|
4996
|
+
let vault;
|
|
4997
|
+
if (!row) {
|
|
4998
|
+
if (this.group.sharding.autoCreate === false) {
|
|
4999
|
+
throw new UnknownShardError(key, this.group.name);
|
|
5000
|
+
}
|
|
5001
|
+
vault = await this.group.createShard(key);
|
|
5002
|
+
} else {
|
|
5003
|
+
vault = await this.group.openShard(key);
|
|
5004
|
+
}
|
|
5005
|
+
await vault.collection(this.collectionName).put(id, record);
|
|
5006
|
+
}
|
|
5007
|
+
/** Begin a cross-shard fan-out query. */
|
|
5008
|
+
query() {
|
|
5009
|
+
return new ShardedQuery(this.group, this.collectionName, []);
|
|
5010
|
+
}
|
|
5011
|
+
};
|
|
5012
|
+
ShardedQuery = class _ShardedQuery {
|
|
5013
|
+
constructor(group, collectionName, clauses) {
|
|
5014
|
+
this.group = group;
|
|
5015
|
+
this.collectionName = collectionName;
|
|
5016
|
+
this.clauses = clauses;
|
|
5017
|
+
}
|
|
5018
|
+
group;
|
|
5019
|
+
collectionName;
|
|
5020
|
+
clauses;
|
|
5021
|
+
where(field, op, value) {
|
|
5022
|
+
return new _ShardedQuery(this.group, this.collectionName, [
|
|
5023
|
+
...this.clauses,
|
|
5024
|
+
{ field, op, value }
|
|
5025
|
+
]);
|
|
5026
|
+
}
|
|
5027
|
+
/** @internal — fan out the where-filtered records across eligible shards. */
|
|
5028
|
+
async fanoutRecords(options = {}) {
|
|
5029
|
+
const { eligible, skipped } = await this.group.resolveEligible(options);
|
|
5030
|
+
const across = await this.group.db.queryAcross(
|
|
5031
|
+
eligible.map((r) => r.vaultId),
|
|
5032
|
+
async (vault) => {
|
|
5033
|
+
this.group.template.configure(vault);
|
|
5034
|
+
const coll = vault.collection(this.collectionName);
|
|
5035
|
+
await coll.list();
|
|
5036
|
+
let q = coll.query();
|
|
5037
|
+
for (const c of this.clauses) q = q.where(c.field, c.op, c.value);
|
|
5038
|
+
return q.toArray();
|
|
5039
|
+
},
|
|
5040
|
+
{ concurrency: options.concurrency ?? 1, create: false }
|
|
5041
|
+
);
|
|
5042
|
+
const results = [];
|
|
5043
|
+
for (const r of across) {
|
|
5044
|
+
if (r.error) skipped.push({ vaultId: r.vault, reason: classifyShardSkip(r.error), error: r.error });
|
|
5045
|
+
else for (const item of r.result) results.push(item);
|
|
5046
|
+
}
|
|
5047
|
+
return { records: results, skippedVaults: skipped };
|
|
5048
|
+
}
|
|
5049
|
+
/** Fan out across eligible shards and merge results. */
|
|
5050
|
+
async toArray(options = {}) {
|
|
5051
|
+
const { records, skippedVaults } = await this.fanoutRecords(options);
|
|
5052
|
+
return { results: records, skippedVaults };
|
|
5053
|
+
}
|
|
5054
|
+
/** @internal — build the change-subscription + relevance binding for this query's group+collection. */
|
|
5055
|
+
liveBinding() {
|
|
5056
|
+
const group = this.group;
|
|
5057
|
+
const collectionName = this.collectionName;
|
|
5058
|
+
return {
|
|
5059
|
+
subscribeToChanges: (h) => {
|
|
5060
|
+
group.db.on("change", h);
|
|
5061
|
+
return () => group.db.off("change", h);
|
|
5062
|
+
},
|
|
5063
|
+
isRelevant: (e) => e.collection === collectionName && e.vault.startsWith(`${group.name}--`)
|
|
5064
|
+
};
|
|
5065
|
+
}
|
|
5066
|
+
/** Returns a reactive cross-shard live query — a facade over CrossVaultLive. */
|
|
5067
|
+
live(options = {}) {
|
|
5068
|
+
const bind = this.liveBinding();
|
|
5069
|
+
const core = new CrossVaultLive({
|
|
5070
|
+
...bind,
|
|
5071
|
+
compute: async () => {
|
|
5072
|
+
const { records, skippedVaults } = await this.fanoutRecords(options);
|
|
5073
|
+
return { records, skipped: skippedVaults };
|
|
5074
|
+
},
|
|
5075
|
+
initialSnapshot: { records: [], skipped: [] },
|
|
5076
|
+
...options.debounceMs !== void 0 ? { debounceMs: options.debounceMs } : {}
|
|
5077
|
+
});
|
|
5078
|
+
return {
|
|
5079
|
+
get value() {
|
|
5080
|
+
return core.snapshot.records;
|
|
5081
|
+
},
|
|
5082
|
+
get skippedVaults() {
|
|
5083
|
+
return core.snapshot.skipped;
|
|
5084
|
+
},
|
|
5085
|
+
get error() {
|
|
5086
|
+
return core.error;
|
|
5087
|
+
},
|
|
5088
|
+
ready: core.ready,
|
|
5089
|
+
subscribe: (cb) => core.subscribe(cb),
|
|
5090
|
+
stop: () => core.stop()
|
|
5091
|
+
};
|
|
5092
|
+
}
|
|
5093
|
+
/** One-shot distributed aggregate — central reduce over all shard records. */
|
|
5094
|
+
aggregate(spec) {
|
|
5095
|
+
return new CrossVaultAggregation(this, spec, this.liveBinding());
|
|
5096
|
+
}
|
|
5097
|
+
/** Begin a grouped cross-shard aggregate. */
|
|
5098
|
+
groupBy(field) {
|
|
5099
|
+
return new ShardedGroupedQuery(this, field);
|
|
5100
|
+
}
|
|
5101
|
+
};
|
|
5102
|
+
ShardedGroupedQuery = class {
|
|
5103
|
+
constructor(query, field) {
|
|
5104
|
+
this.query = query;
|
|
5105
|
+
this.field = field;
|
|
5106
|
+
}
|
|
5107
|
+
query;
|
|
5108
|
+
field;
|
|
5109
|
+
aggregate(spec) {
|
|
5110
|
+
return new CrossVaultGroupedAggregation(
|
|
5111
|
+
{ fanoutRecords: (o) => this.query.fanoutRecords(o) },
|
|
5112
|
+
this.field,
|
|
5113
|
+
spec,
|
|
5114
|
+
this.query.liveBinding()
|
|
5115
|
+
);
|
|
5116
|
+
}
|
|
5117
|
+
};
|
|
5118
|
+
}
|
|
5119
|
+
});
|
|
5120
|
+
|
|
5121
|
+
// src/federation/schema-manifest.ts
|
|
5122
|
+
function captureBlueprint(configure) {
|
|
5123
|
+
const recorded = [];
|
|
5124
|
+
const collectionStub = new Proxy(
|
|
5125
|
+
{},
|
|
5126
|
+
{
|
|
5127
|
+
get: () => () => collectionStub
|
|
5128
|
+
}
|
|
5129
|
+
);
|
|
5130
|
+
const proxy = new Proxy(
|
|
5131
|
+
{},
|
|
5132
|
+
{
|
|
5133
|
+
get: (_t, prop) => {
|
|
5134
|
+
if (prop === "collection") {
|
|
5135
|
+
return (name, opts) => {
|
|
5136
|
+
recorded.push({
|
|
5137
|
+
name,
|
|
5138
|
+
indexes: opts?.indexes ?? [],
|
|
5139
|
+
persistJsonSchema: !!opts?.persistJsonSchema
|
|
5140
|
+
});
|
|
5141
|
+
return collectionStub;
|
|
5142
|
+
};
|
|
5143
|
+
}
|
|
5144
|
+
return () => proxy;
|
|
5145
|
+
}
|
|
5146
|
+
}
|
|
5147
|
+
);
|
|
5148
|
+
configure(proxy);
|
|
5149
|
+
const sorted = [...recorded].sort((a, b) => a.name.localeCompare(b.name));
|
|
5150
|
+
const indexes = {};
|
|
5151
|
+
const persistJsonSchema = [];
|
|
5152
|
+
for (const c of sorted) {
|
|
5153
|
+
indexes[c.name] = c.indexes;
|
|
5154
|
+
if (c.persistJsonSchema) persistJsonSchema.push(c.name);
|
|
5155
|
+
}
|
|
5156
|
+
return {
|
|
5157
|
+
// `persistJsonSchema` is already name-sorted: it is populated while
|
|
5158
|
+
// iterating `sorted` (collections in name order).
|
|
5159
|
+
collections: sorted.map((c) => c.name),
|
|
5160
|
+
indexes,
|
|
5161
|
+
persistJsonSchema
|
|
5162
|
+
};
|
|
5163
|
+
}
|
|
5164
|
+
function canonical(value) {
|
|
5165
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
5166
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
5167
|
+
const obj = value;
|
|
5168
|
+
const keys = Object.keys(obj).sort();
|
|
5169
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonical(obj[k])}`).join(",")}}`;
|
|
5170
|
+
}
|
|
5171
|
+
async function fingerprintBlueprint(bp) {
|
|
5172
|
+
return sha256Hex(new TextEncoder().encode(canonical(bp)));
|
|
5173
|
+
}
|
|
5174
|
+
var init_schema_manifest = __esm({
|
|
5175
|
+
"src/federation/schema-manifest.ts"() {
|
|
5176
|
+
"use strict";
|
|
5177
|
+
init_crypto();
|
|
5178
|
+
}
|
|
5179
|
+
});
|
|
5180
|
+
|
|
5181
|
+
// src/federation/state-vault.ts
|
|
5182
|
+
var state_vault_exports = {};
|
|
5183
|
+
__export(state_vault_exports, {
|
|
5184
|
+
STATE_VAULT_NAME: () => STATE_VAULT_NAME,
|
|
5185
|
+
StateManagementVault: () => StateManagementVault
|
|
5186
|
+
});
|
|
5187
|
+
var REGISTRY, MANIFEST, EVENTS, StateManagementVault;
|
|
5188
|
+
var init_state_vault = __esm({
|
|
5189
|
+
"src/federation/state-vault.ts"() {
|
|
5190
|
+
"use strict";
|
|
5191
|
+
init_schema_manifest();
|
|
5192
|
+
init_constants();
|
|
5193
|
+
init_ulid();
|
|
5194
|
+
init_constants();
|
|
5195
|
+
REGISTRY = "vaultRegistry";
|
|
5196
|
+
MANIFEST = "schemaManifest";
|
|
5197
|
+
EVENTS = "deploymentEvents";
|
|
5198
|
+
StateManagementVault = class _StateManagementVault {
|
|
5199
|
+
constructor(registry, schemaManifest, events) {
|
|
5200
|
+
this.registry = registry;
|
|
5201
|
+
this.schemaManifest = schemaManifest;
|
|
5202
|
+
this.#events = events;
|
|
5203
|
+
}
|
|
5204
|
+
registry;
|
|
5205
|
+
schemaManifest;
|
|
5206
|
+
/**
|
|
5207
|
+
* The append-only deployment-events log is kept truly private so the raw
|
|
5208
|
+
* mutable Collection is never surfaced — events may only be written via
|
|
5209
|
+
* `appendEvent` and read via `queryEvents`. (`registry` and
|
|
5210
|
+
* `schemaManifest` are deliberately public: consumers read and write them.)
|
|
5211
|
+
*/
|
|
5212
|
+
#events;
|
|
5213
|
+
/** Idempotently open the reserved state vault and bind the three control-plane collections. */
|
|
5214
|
+
static async open(db) {
|
|
5215
|
+
const vault = await db.openVault(STATE_VAULT_NAME);
|
|
5216
|
+
return new _StateManagementVault(
|
|
5217
|
+
vault.collection(REGISTRY),
|
|
5218
|
+
vault.collection(MANIFEST),
|
|
5219
|
+
vault.collection(EVENTS)
|
|
5220
|
+
);
|
|
5221
|
+
}
|
|
5222
|
+
/** Read-only query over the append-only deployment-events log. */
|
|
5223
|
+
queryEvents() {
|
|
5224
|
+
return this.#events.query();
|
|
5225
|
+
}
|
|
5226
|
+
/**
|
|
5227
|
+
* Append a deployment event with a fresh unique (ULID) id. This is the
|
|
5228
|
+
* only write path to the events log; no update/delete is exposed.
|
|
5229
|
+
* Callers should treat failures as non-fatal — this method does not
|
|
5230
|
+
* swallow errors, so wrap the call site in try/catch where appropriate.
|
|
5231
|
+
*/
|
|
5232
|
+
async appendEvent(event) {
|
|
5233
|
+
const ts = event.ts ?? Date.now();
|
|
5234
|
+
const id = generateULID();
|
|
5235
|
+
await this.#events.put(id, { ...event, id, ts });
|
|
5236
|
+
}
|
|
5237
|
+
/**
|
|
5238
|
+
* Ensure a manifest row exists for `(templateName, template.version)`.
|
|
5239
|
+
* Safe to call repeatedly: the `fingerprint` is a deterministic hash of
|
|
5240
|
+
* the template's declared shape (stable across calls), though each call
|
|
5241
|
+
* refreshes `recordedAt`.
|
|
5242
|
+
*/
|
|
5243
|
+
async recordManifest(templateName, template) {
|
|
5244
|
+
const bp = captureBlueprint(template.configure);
|
|
5245
|
+
const fingerprint = await fingerprintBlueprint(bp);
|
|
5246
|
+
await this.schemaManifest.put(`${templateName}:${template.version}`, {
|
|
5247
|
+
templateName,
|
|
5248
|
+
version: template.version,
|
|
5249
|
+
collections: bp.collections,
|
|
5250
|
+
indexes: bp.indexes,
|
|
5251
|
+
persistJsonSchema: bp.persistJsonSchema,
|
|
5252
|
+
fingerprint,
|
|
5253
|
+
recordedAt: Date.now()
|
|
5254
|
+
});
|
|
5255
|
+
return fingerprint;
|
|
5256
|
+
}
|
|
5257
|
+
/**
|
|
5258
|
+
* True when `template`'s current declared shape does not match the recorded
|
|
5259
|
+
* manifest for `(templateName, template.version)`. Because shards carry no
|
|
5260
|
+
* schema state independent of their template, this catches "a template's
|
|
5261
|
+
* shape changed without bumping `version`" — not independent per-shard drift.
|
|
5262
|
+
* A missing manifest is treated as drift (nothing to verify against).
|
|
5263
|
+
*/
|
|
5264
|
+
async detectDrift(templateName, template) {
|
|
5265
|
+
const row = await this.schemaManifest.get(`${templateName}:${template.version}`);
|
|
5266
|
+
if (!row) return true;
|
|
5267
|
+
const current = await fingerprintBlueprint(captureBlueprint(template.configure));
|
|
5268
|
+
return current !== row.fingerprint;
|
|
5269
|
+
}
|
|
5270
|
+
};
|
|
5271
|
+
}
|
|
5272
|
+
});
|
|
5273
|
+
|
|
4055
5274
|
// src/index.ts
|
|
4056
5275
|
var src_exports = {};
|
|
4057
5276
|
__export(src_exports, {
|
|
@@ -4076,6 +5295,7 @@ __export(src_exports, {
|
|
|
4076
5295
|
CollectionFrame: () => CollectionFrame,
|
|
4077
5296
|
CollectionIndexes: () => CollectionIndexes,
|
|
4078
5297
|
CollectionInstant: () => CollectionInstant,
|
|
5298
|
+
ComputedFieldError: () => ComputedFieldError,
|
|
4079
5299
|
ConflictError: () => ConflictError,
|
|
4080
5300
|
CrossJoinSourceUnknownError: () => CrossJoinSourceUnknownError,
|
|
4081
5301
|
CrossJoinTooLargeError: () => CrossJoinTooLargeError,
|
|
@@ -4139,6 +5359,9 @@ __export(src_exports, {
|
|
|
4139
5359
|
MemorySealingKeyProvider: () => MemorySealingKeyProvider,
|
|
4140
5360
|
MigrationRequiredError: () => MigrationRequiredError,
|
|
4141
5361
|
MissingTranslationError: () => MissingTranslationError,
|
|
5362
|
+
MoneyCurrencyError: () => MoneyCurrencyError,
|
|
5363
|
+
MoneyPrecisionError: () => MoneyPrecisionError,
|
|
5364
|
+
MoneyUnsupportedError: () => MoneyUnsupportedError,
|
|
4142
5365
|
NOYDB_BACKUP_VERSION: () => NOYDB_BACKUP_VERSION,
|
|
4143
5366
|
NOYDB_BUNDLE_FORMAT_VERSION: () => NOYDB_BUNDLE_FORMAT_VERSION,
|
|
4144
5367
|
NOYDB_BUNDLE_MAGIC: () => NOYDB_BUNDLE_MAGIC,
|
|
@@ -4152,6 +5375,7 @@ __export(src_exports, {
|
|
|
4152
5375
|
NotFoundError: () => NotFoundError,
|
|
4153
5376
|
Noydb: () => Noydb,
|
|
4154
5377
|
NoydbError: () => NoydbError,
|
|
5378
|
+
NumberingUncertaintyError: () => NumberingUncertaintyError,
|
|
4155
5379
|
OverlayBaseIsVirtualError: () => OverlayBaseIsVirtualError,
|
|
4156
5380
|
OverlayCollectionUnavailableError: () => OverlayCollectionUnavailableError,
|
|
4157
5381
|
OverlayIdMismatchError: () => OverlayIdMismatchError,
|
|
@@ -4181,8 +5405,10 @@ __export(src_exports, {
|
|
|
4181
5405
|
RefRegistry: () => RefRegistry,
|
|
4182
5406
|
RefScopeError: () => RefScopeError,
|
|
4183
5407
|
ReservedCollectionNameError: () => ReservedCollectionNameError,
|
|
5408
|
+
ReservedVaultNameError: () => ReservedVaultNameError,
|
|
4184
5409
|
SCHEMAS_COLLECTION: () => SCHEMAS_COLLECTION,
|
|
4185
5410
|
SEALED_PASSPHRASE_RECORD_ID: () => SEALED_PASSPHRASE_RECORD_ID,
|
|
5411
|
+
STATE_VAULT_NAME: () => STATE_VAULT_NAME,
|
|
4186
5412
|
STRICT_POLICY: () => STRICT_POLICY,
|
|
4187
5413
|
SYNC_CREDENTIALS_COLLECTION: () => SYNC_CREDENTIALS_COLLECTION,
|
|
4188
5414
|
ScanBuilder: () => ScanBuilder,
|
|
@@ -4191,9 +5417,13 @@ __export(src_exports, {
|
|
|
4191
5417
|
SchemaUpdateError: () => SchemaUpdateError,
|
|
4192
5418
|
SchemaValidationError: () => SchemaValidationError,
|
|
4193
5419
|
ScriptViolationError: () => ScriptViolationError,
|
|
5420
|
+
SequenceContentionError: () => SequenceContentionError,
|
|
5421
|
+
SequenceOfflineError: () => SequenceOfflineError,
|
|
5422
|
+
SequenceStore: () => SequenceStore,
|
|
4194
5423
|
SessionExpiredError: () => SessionExpiredError,
|
|
4195
5424
|
SessionNotFoundError: () => SessionNotFoundError,
|
|
4196
5425
|
SessionPolicyError: () => SessionPolicyError,
|
|
5426
|
+
ShardProvisioningError: () => ShardProvisioningError,
|
|
4197
5427
|
SnapshotNotFoundError: () => SnapshotNotFoundError,
|
|
4198
5428
|
StoreCapabilityError: () => StoreCapabilityError,
|
|
4199
5429
|
SyncEngine: () => SyncEngine,
|
|
@@ -4209,6 +5439,7 @@ __export(src_exports, {
|
|
|
4209
5439
|
USER_ENVELOPE_COLLECTION: () => USER_ENVELOPE_COLLECTION,
|
|
4210
5440
|
USER_ENVELOPE_MAX_BYTES: () => USER_ENVELOPE_MAX_BYTES,
|
|
4211
5441
|
UniqueConstraintError: () => UniqueConstraintError,
|
|
5442
|
+
UnknownShardError: () => UnknownShardError,
|
|
4212
5443
|
UnsupportedIndexOptionError: () => UnsupportedIndexOptionError,
|
|
4213
5444
|
UserApi: () => UserApi,
|
|
4214
5445
|
UserEnvelopeOversizedError: () => UserEnvelopeOversizedError,
|
|
@@ -4217,6 +5448,7 @@ __export(src_exports, {
|
|
|
4217
5448
|
Vault: () => Vault,
|
|
4218
5449
|
VaultFrame: () => VaultFrame,
|
|
4219
5450
|
VaultInstant: () => VaultInstant,
|
|
5451
|
+
VaultTemplateNotFoundError: () => VaultTemplateNotFoundError,
|
|
4220
5452
|
WeakPassphraseError: () => WeakPassphraseError,
|
|
4221
5453
|
activeSessionCount: () => activeSessionCount,
|
|
4222
5454
|
additiveOnly: () => additiveOnly,
|
|
@@ -4273,6 +5505,7 @@ __export(src_exports, {
|
|
|
4273
5505
|
envelopePayloadHash: () => envelopePayloadHash,
|
|
4274
5506
|
estimateEntropy: () => estimateEntropy,
|
|
4275
5507
|
estimateRecordBytes: () => estimateRecordBytes,
|
|
5508
|
+
evalComputedFields: () => evalComputedFields,
|
|
4276
5509
|
evaluateClause: () => evaluateClause,
|
|
4277
5510
|
evaluateExportCapability: () => evaluateExportCapability,
|
|
4278
5511
|
evaluateFieldClause: () => evaluateFieldClause,
|
|
@@ -4289,6 +5522,7 @@ __export(src_exports, {
|
|
|
4289
5522
|
hasRecoveryEnrolled: () => hasRecoveryEnrolled,
|
|
4290
5523
|
hashEntry: () => hashEntry,
|
|
4291
5524
|
i18nText: () => i18nText,
|
|
5525
|
+
immutableGuard: () => immutableGuard,
|
|
4292
5526
|
inferScripts: () => inferScripts,
|
|
4293
5527
|
isDevUnlockActive: () => isDevUnlockActive,
|
|
4294
5528
|
isDictCollectionName: () => isDictCollectionName,
|
|
@@ -4296,6 +5530,7 @@ __export(src_exports, {
|
|
|
4296
5530
|
isDiscriminant: () => isDiscriminant,
|
|
4297
5531
|
isI18nTextDescriptor: () => isI18nTextDescriptor,
|
|
4298
5532
|
isMagicLinkGrantExpired: () => isMagicLinkGrantExpired,
|
|
5533
|
+
isMoneyDescriptor: () => isMoneyDescriptor,
|
|
4299
5534
|
isPreCompressed: () => isPreCompressed,
|
|
4300
5535
|
isPublicEnvelope: () => isPublicEnvelope,
|
|
4301
5536
|
isSessionAlive: () => isSessionAlive,
|
|
@@ -4327,6 +5562,7 @@ __export(src_exports, {
|
|
|
4327
5562
|
mintPaperRecoveryEntry: () => mintPaperRecoveryEntry,
|
|
4328
5563
|
mintShamirRecoveryEntry: () => mintShamirRecoveryEntry,
|
|
4329
5564
|
mintWrappedDeksBlob: () => mintWrappedDeksBlob,
|
|
5565
|
+
money: () => money,
|
|
4330
5566
|
paddedIndex: () => paddedIndex,
|
|
4331
5567
|
parseBytes: () => parseBytes,
|
|
4332
5568
|
parseIndex: () => parseIndex,
|
|
@@ -4367,6 +5603,7 @@ __export(src_exports, {
|
|
|
4367
5603
|
saveShamirRecoveryEntries: () => saveShamirRecoveryEntries,
|
|
4368
5604
|
saveUserEnvelope: () => saveUserEnvelope,
|
|
4369
5605
|
saveVaultPolicy: () => saveVaultPolicy,
|
|
5606
|
+
scaleForCurrency: () => scaleForCurrency,
|
|
4370
5607
|
sha256Hex: () => sha256Hex3,
|
|
4371
5608
|
sum: () => sum,
|
|
4372
5609
|
unwrapDeksFromBlob: () => unwrapDeksFromBlob,
|
|
@@ -4380,8 +5617,10 @@ __export(src_exports, {
|
|
|
4380
5617
|
validateSchemaOutput: () => validateSchemaOutput,
|
|
4381
5618
|
validateSessionPolicy: () => validateSessionPolicy,
|
|
4382
5619
|
visibilityRecordId: () => visibilityRecordId,
|
|
5620
|
+
withArchive: () => withArchive,
|
|
4383
5621
|
withCache: () => withCache,
|
|
4384
5622
|
withCircuitBreaker: () => withCircuitBreaker,
|
|
5623
|
+
withDeferredNumbering: () => withDeferredNumbering,
|
|
4385
5624
|
withDerivation: () => withDerivation,
|
|
4386
5625
|
withGuard: () => withGuard,
|
|
4387
5626
|
withHealthCheck: () => withHealthCheck,
|
|
@@ -6338,37 +7577,214 @@ function withHealthCheck(opts = {}) {
|
|
|
6338
7577
|
}
|
|
6339
7578
|
}
|
|
6340
7579
|
}
|
|
6341
|
-
setInterval(() => {
|
|
6342
|
-
void doCheck();
|
|
6343
|
-
}, intervalMs);
|
|
6344
|
-
const wrapped = {
|
|
6345
|
-
...next,
|
|
6346
|
-
name: next.name ? `health(${next.name})` : "health",
|
|
6347
|
-
async get(v, c, id) {
|
|
6348
|
-
return isSuspended ? null : next.get(v, c, id);
|
|
6349
|
-
},
|
|
6350
|
-
async put(v, c, id, env, ev) {
|
|
6351
|
-
if (!isSuspended) await next.put(v, c, id, env, ev);
|
|
6352
|
-
},
|
|
6353
|
-
async delete(v, c, id) {
|
|
6354
|
-
if (!isSuspended) await next.delete(v, c, id);
|
|
6355
|
-
},
|
|
6356
|
-
async list(v, c) {
|
|
6357
|
-
return isSuspended ? [] : next.list(v, c);
|
|
6358
|
-
},
|
|
6359
|
-
async loadAll(v) {
|
|
6360
|
-
return isSuspended ? {} : next.loadAll(v);
|
|
6361
|
-
},
|
|
6362
|
-
async saveAll(v, d) {
|
|
6363
|
-
if (!isSuspended) await next.saveAll(v, d);
|
|
6364
|
-
}
|
|
6365
|
-
};
|
|
6366
|
-
return wrapped;
|
|
7580
|
+
setInterval(() => {
|
|
7581
|
+
void doCheck();
|
|
7582
|
+
}, intervalMs);
|
|
7583
|
+
const wrapped = {
|
|
7584
|
+
...next,
|
|
7585
|
+
name: next.name ? `health(${next.name})` : "health",
|
|
7586
|
+
async get(v, c, id) {
|
|
7587
|
+
return isSuspended ? null : next.get(v, c, id);
|
|
7588
|
+
},
|
|
7589
|
+
async put(v, c, id, env, ev) {
|
|
7590
|
+
if (!isSuspended) await next.put(v, c, id, env, ev);
|
|
7591
|
+
},
|
|
7592
|
+
async delete(v, c, id) {
|
|
7593
|
+
if (!isSuspended) await next.delete(v, c, id);
|
|
7594
|
+
},
|
|
7595
|
+
async list(v, c) {
|
|
7596
|
+
return isSuspended ? [] : next.list(v, c);
|
|
7597
|
+
},
|
|
7598
|
+
async loadAll(v) {
|
|
7599
|
+
return isSuspended ? {} : next.loadAll(v);
|
|
7600
|
+
},
|
|
7601
|
+
async saveAll(v, d) {
|
|
7602
|
+
if (!isSuspended) await next.saveAll(v, d);
|
|
7603
|
+
}
|
|
7604
|
+
};
|
|
7605
|
+
return wrapped;
|
|
7606
|
+
};
|
|
7607
|
+
}
|
|
7608
|
+
|
|
7609
|
+
// src/index.ts
|
|
7610
|
+
init_errors();
|
|
7611
|
+
init_errors();
|
|
7612
|
+
|
|
7613
|
+
// src/archive/engine.ts
|
|
7614
|
+
function isHeld(policy, record) {
|
|
7615
|
+
if (!policy.legalHold) return false;
|
|
7616
|
+
try {
|
|
7617
|
+
return policy.legalHold(record);
|
|
7618
|
+
} catch {
|
|
7619
|
+
return true;
|
|
7620
|
+
}
|
|
7621
|
+
}
|
|
7622
|
+
async function runArchive(ctx, options = {}) {
|
|
7623
|
+
const maxArchives = options.maxArchives ?? Infinity;
|
|
7624
|
+
const dryRun = options.dryRun === true;
|
|
7625
|
+
let archived = 0;
|
|
7626
|
+
let held = 0;
|
|
7627
|
+
let scanned = 0;
|
|
7628
|
+
const byCollection = {};
|
|
7629
|
+
outer: for (const collection of ctx.collectionsWithPolicy()) {
|
|
7630
|
+
const policy = ctx.getPolicy(collection);
|
|
7631
|
+
if (!policy) continue;
|
|
7632
|
+
byCollection[collection] = { archived: 0, held: 0 };
|
|
7633
|
+
for (const id of await ctx.listRecordIds(collection)) {
|
|
7634
|
+
if (archived >= maxArchives) break outer;
|
|
7635
|
+
const record = await ctx.getRecord(collection, id).catch(() => null);
|
|
7636
|
+
if (record === null) continue;
|
|
7637
|
+
scanned += 1;
|
|
7638
|
+
let eligible = false;
|
|
7639
|
+
try {
|
|
7640
|
+
eligible = policy.archiveWhen(record);
|
|
7641
|
+
} catch {
|
|
7642
|
+
eligible = false;
|
|
7643
|
+
}
|
|
7644
|
+
if (!eligible) continue;
|
|
7645
|
+
if (isHeld(policy, record)) {
|
|
7646
|
+
held += 1;
|
|
7647
|
+
byCollection[collection].held += 1;
|
|
7648
|
+
continue;
|
|
7649
|
+
}
|
|
7650
|
+
if (!dryRun) {
|
|
7651
|
+
const env = await ctx.getEnvelope(collection, id);
|
|
7652
|
+
if (!env) continue;
|
|
7653
|
+
await ctx.archiveStore.put(ctx.vaultId, collection, id, env);
|
|
7654
|
+
await ctx.removeFromPrimary(collection, id);
|
|
7655
|
+
}
|
|
7656
|
+
archived += 1;
|
|
7657
|
+
byCollection[collection].archived += 1;
|
|
7658
|
+
}
|
|
7659
|
+
}
|
|
7660
|
+
return { archived, held, scanned, byCollection };
|
|
7661
|
+
}
|
|
7662
|
+
async function runRestore(ctx, collection, id) {
|
|
7663
|
+
const env = await ctx.archiveStore.get(ctx.vaultId, collection, id);
|
|
7664
|
+
if (!env) return false;
|
|
7665
|
+
await ctx.restoreToPrimary(collection, id, env);
|
|
7666
|
+
await ctx.archiveStore.delete(ctx.vaultId, collection, id);
|
|
7667
|
+
return true;
|
|
7668
|
+
}
|
|
7669
|
+
async function runListArchived(ctx, collection) {
|
|
7670
|
+
const collections = collection ? [collection] : ctx.collectionsWithPolicy();
|
|
7671
|
+
const out = [];
|
|
7672
|
+
for (const c of collections) {
|
|
7673
|
+
const ids = await ctx.archiveStore.list(ctx.vaultId, c);
|
|
7674
|
+
for (const id of ids) out.push({ collection: c, id });
|
|
7675
|
+
}
|
|
7676
|
+
return out;
|
|
7677
|
+
}
|
|
7678
|
+
|
|
7679
|
+
// src/archive/index.ts
|
|
7680
|
+
function withArchive(opts) {
|
|
7681
|
+
return { store: opts.store };
|
|
7682
|
+
}
|
|
7683
|
+
|
|
7684
|
+
// src/sequence/index.ts
|
|
7685
|
+
init_types();
|
|
7686
|
+
init_crypto();
|
|
7687
|
+
init_errors();
|
|
7688
|
+
var SEQUENCE_COLLECTION = "_sequences";
|
|
7689
|
+
var MAX_NEXT_ATTEMPTS = 16;
|
|
7690
|
+
async function sleepBackoff(attempt) {
|
|
7691
|
+
const ceil = Math.min(2 ** attempt, 32);
|
|
7692
|
+
const ms = Math.floor(Math.random() * ceil);
|
|
7693
|
+
await new Promise((r) => setTimeout(r, ms));
|
|
7694
|
+
}
|
|
7695
|
+
var SequenceStore = class {
|
|
7696
|
+
adapter;
|
|
7697
|
+
vault;
|
|
7698
|
+
encrypted;
|
|
7699
|
+
getDEK;
|
|
7700
|
+
actor;
|
|
7701
|
+
/**
|
|
7702
|
+
* Memoized DEK promise. The `_sequences` collection DEK is created on
|
|
7703
|
+
* first access; without sharing one promise, a burst of concurrent
|
|
7704
|
+
* `next()` calls would each trigger DEK creation and diverge (one
|
|
7705
|
+
* writer's ciphertext unreadable by another). One shared promise → one
|
|
7706
|
+
* DEK.
|
|
7707
|
+
*/
|
|
7708
|
+
dekPromise = null;
|
|
7709
|
+
constructor(opts) {
|
|
7710
|
+
this.adapter = opts.adapter;
|
|
7711
|
+
this.vault = opts.vault;
|
|
7712
|
+
this.encrypted = opts.encrypted;
|
|
7713
|
+
this.getDEK = opts.getDEK;
|
|
7714
|
+
this.actor = opts.actor;
|
|
7715
|
+
}
|
|
7716
|
+
/** A handle bound to one sequence name. */
|
|
7717
|
+
handle(name) {
|
|
7718
|
+
return {
|
|
7719
|
+
next: () => this.next(name),
|
|
7720
|
+
peek: () => this.peek(name)
|
|
7721
|
+
};
|
|
7722
|
+
}
|
|
7723
|
+
assertOnline() {
|
|
7724
|
+
if (this.adapter.capabilities?.casAtomic !== true) {
|
|
7725
|
+
throw new SequenceOfflineError();
|
|
7726
|
+
}
|
|
7727
|
+
}
|
|
7728
|
+
dek() {
|
|
7729
|
+
if (!this.dekPromise) this.dekPromise = this.getDEK(SEQUENCE_COLLECTION);
|
|
7730
|
+
return this.dekPromise;
|
|
7731
|
+
}
|
|
7732
|
+
async read(name) {
|
|
7733
|
+
const env = await this.adapter.get(this.vault, SEQUENCE_COLLECTION, name);
|
|
7734
|
+
if (!env) return { env: null, value: 0 };
|
|
7735
|
+
const json = this.encrypted ? await decrypt(env._iv, env._data, await this.dek()) : env._data;
|
|
7736
|
+
const state = JSON.parse(json);
|
|
7737
|
+
return { env, value: state.value };
|
|
7738
|
+
}
|
|
7739
|
+
async encryptState(state, version) {
|
|
7740
|
+
const json = JSON.stringify(state);
|
|
7741
|
+
if (!this.encrypted) {
|
|
7742
|
+
return { _noydb: NOYDB_FORMAT_VERSION, _v: version, _ts: (/* @__PURE__ */ new Date()).toISOString(), _iv: "", _data: json, _by: this.actor };
|
|
7743
|
+
}
|
|
7744
|
+
const { iv, data } = await encrypt(json, await this.dek());
|
|
7745
|
+
return { _noydb: NOYDB_FORMAT_VERSION, _v: version, _ts: (/* @__PURE__ */ new Date()).toISOString(), _iv: iv, _data: data, _by: this.actor };
|
|
7746
|
+
}
|
|
7747
|
+
async peek(name) {
|
|
7748
|
+
return (await this.read(name)).value;
|
|
7749
|
+
}
|
|
7750
|
+
async next(name) {
|
|
7751
|
+
this.assertOnline();
|
|
7752
|
+
let lastConflict;
|
|
7753
|
+
for (let attempt = 0; attempt < MAX_NEXT_ATTEMPTS; attempt++) {
|
|
7754
|
+
const { env, value } = await this.read(name);
|
|
7755
|
+
const nextValue = value + 1;
|
|
7756
|
+
const expectedVersion = env?._v ?? 0;
|
|
7757
|
+
const envelope = await this.encryptState({ value: nextValue }, expectedVersion + 1);
|
|
7758
|
+
try {
|
|
7759
|
+
await this.adapter.put(this.vault, SEQUENCE_COLLECTION, name, envelope, expectedVersion);
|
|
7760
|
+
return nextValue;
|
|
7761
|
+
} catch (err) {
|
|
7762
|
+
if (err instanceof ConflictError) {
|
|
7763
|
+
lastConflict = err;
|
|
7764
|
+
if (attempt < MAX_NEXT_ATTEMPTS - 1) await sleepBackoff(attempt);
|
|
7765
|
+
continue;
|
|
7766
|
+
}
|
|
7767
|
+
throw err;
|
|
7768
|
+
}
|
|
7769
|
+
}
|
|
7770
|
+
void lastConflict;
|
|
7771
|
+
throw new SequenceContentionError(name, MAX_NEXT_ATTEMPTS);
|
|
7772
|
+
}
|
|
7773
|
+
};
|
|
7774
|
+
|
|
7775
|
+
// src/numbering/descriptor.ts
|
|
7776
|
+
function withDeferredNumbering(config) {
|
|
7777
|
+
return {
|
|
7778
|
+
series: config.series,
|
|
7779
|
+
collection: config.collection,
|
|
7780
|
+
field: config.field,
|
|
7781
|
+
settleWindowMs: config.settleWindowMs ?? 0
|
|
6367
7782
|
};
|
|
6368
7783
|
}
|
|
6369
7784
|
|
|
6370
7785
|
// src/index.ts
|
|
6371
7786
|
init_errors();
|
|
7787
|
+
init_constants();
|
|
6372
7788
|
init_errors();
|
|
6373
7789
|
|
|
6374
7790
|
// src/bundle/format.ts
|
|
@@ -7176,8 +8592,8 @@ async function derivePersistedSchema(validator) {
|
|
|
7176
8592
|
if (kind === "Zod") {
|
|
7177
8593
|
const convert = await loadZodConverter();
|
|
7178
8594
|
const jsonSchema = convert(validator);
|
|
7179
|
-
const
|
|
7180
|
-
const hash = await sha256Hex(new TextEncoder().encode(
|
|
8595
|
+
const canonical2 = canonicalize(jsonSchema);
|
|
8596
|
+
const hash = await sha256Hex(new TextEncoder().encode(canonical2));
|
|
7181
8597
|
return { _noydb_schema: 1, kind, jsonSchema, hash, derivedAt };
|
|
7182
8598
|
}
|
|
7183
8599
|
return {
|
|
@@ -7251,15 +8667,15 @@ function isEquivalent(a, b) {
|
|
|
7251
8667
|
|
|
7252
8668
|
// src/history/history.ts
|
|
7253
8669
|
var HISTORY_COLLECTION = "_history";
|
|
7254
|
-
function matchesPrefix(id, collection,
|
|
7255
|
-
if (
|
|
7256
|
-
return id.startsWith(`${collection}:${
|
|
8670
|
+
function matchesPrefix(id, collection, recordId3) {
|
|
8671
|
+
if (recordId3) {
|
|
8672
|
+
return id.startsWith(`${collection}:${recordId3}:`);
|
|
7257
8673
|
}
|
|
7258
8674
|
return id.startsWith(`${collection}:`);
|
|
7259
8675
|
}
|
|
7260
|
-
async function getHistory(adapter, vault, collection,
|
|
8676
|
+
async function getHistory(adapter, vault, collection, recordId3, options) {
|
|
7261
8677
|
const allIds = await adapter.list(vault, HISTORY_COLLECTION);
|
|
7262
|
-
const matchingIds = allIds.filter((id) => matchesPrefix(id, collection,
|
|
8678
|
+
const matchingIds = allIds.filter((id) => matchesPrefix(id, collection, recordId3)).sort().reverse();
|
|
7263
8679
|
const entries = [];
|
|
7264
8680
|
for (const id of matchingIds) {
|
|
7265
8681
|
const envelope = await adapter.get(vault, HISTORY_COLLECTION, id);
|
|
@@ -9808,6 +11224,7 @@ init_errors();
|
|
|
9808
11224
|
|
|
9809
11225
|
// src/noydb.ts
|
|
9810
11226
|
init_errors();
|
|
11227
|
+
init_constants();
|
|
9811
11228
|
init_ulid();
|
|
9812
11229
|
init_public_envelope();
|
|
9813
11230
|
|
|
@@ -10034,6 +11451,119 @@ function applyI18nLocale(record, i18nFields, locale, fallback, layer = "read") {
|
|
|
10034
11451
|
return result;
|
|
10035
11452
|
}
|
|
10036
11453
|
|
|
11454
|
+
// src/money/normalize.ts
|
|
11455
|
+
init_fixed_point();
|
|
11456
|
+
init_descriptor();
|
|
11457
|
+
function isMoneyValueObject(v) {
|
|
11458
|
+
return typeof v === "object" && v !== null && "currency" in v;
|
|
11459
|
+
}
|
|
11460
|
+
function quantizeAmount(field, input, scale, rounding) {
|
|
11461
|
+
const r = parseToScaledInt(input, scale, rounding);
|
|
11462
|
+
if (!r.ok) {
|
|
11463
|
+
if (r.reason === "precision") throw new MoneyPrecisionError(field, input, scale);
|
|
11464
|
+
throw new TypeError(`money: field "${field}" value ${JSON.stringify(input)} is not a finite decimal`);
|
|
11465
|
+
}
|
|
11466
|
+
return r.value.toString();
|
|
11467
|
+
}
|
|
11468
|
+
function quantizeMoneyFields(record, moneyFields) {
|
|
11469
|
+
const out = { ...record };
|
|
11470
|
+
for (const [field, desc] of Object.entries(moneyFields)) {
|
|
11471
|
+
const raw = out[field];
|
|
11472
|
+
if (raw === null || raw === void 0) continue;
|
|
11473
|
+
if (desc.mode === "fixed") {
|
|
11474
|
+
const currency2 = desc.fixedCurrency;
|
|
11475
|
+
out[field] = quantizeAmount(field, raw, desc.scaleFor(currency2), desc.rounding);
|
|
11476
|
+
continue;
|
|
11477
|
+
}
|
|
11478
|
+
let amount;
|
|
11479
|
+
let currency;
|
|
11480
|
+
if (isMoneyValueObject(raw)) {
|
|
11481
|
+
currency = String(raw.currency);
|
|
11482
|
+
amount = raw.amount;
|
|
11483
|
+
} else {
|
|
11484
|
+
const sole = desc.soleCurrency();
|
|
11485
|
+
if (sole === void 0) {
|
|
11486
|
+
throw new TypeError(
|
|
11487
|
+
`money: field "${field}" is multi-currency \u2014 write { amount, currency }, not a bare amount`
|
|
11488
|
+
);
|
|
11489
|
+
}
|
|
11490
|
+
currency = sole;
|
|
11491
|
+
amount = raw;
|
|
11492
|
+
}
|
|
11493
|
+
const scale = desc.scaleFor(currency);
|
|
11494
|
+
out[field] = { amount: quantizeAmount(field, amount, scale, desc.rounding), currency };
|
|
11495
|
+
}
|
|
11496
|
+
return out;
|
|
11497
|
+
}
|
|
11498
|
+
function formatCurrency(decimal, currency, scale, locale) {
|
|
11499
|
+
const fmt = new Intl.NumberFormat(locale, {
|
|
11500
|
+
style: "currency",
|
|
11501
|
+
currency,
|
|
11502
|
+
minimumFractionDigits: scale,
|
|
11503
|
+
maximumFractionDigits: scale
|
|
11504
|
+
});
|
|
11505
|
+
return fmt.format(decimal);
|
|
11506
|
+
}
|
|
11507
|
+
function decodeMoneyFields(record, moneyFields, locale) {
|
|
11508
|
+
const out = { ...record };
|
|
11509
|
+
const format = locale !== "raw";
|
|
11510
|
+
const fmtLocale = typeof locale === "string" && locale !== "raw" ? locale : "en-US";
|
|
11511
|
+
for (const [field, desc] of Object.entries(moneyFields)) {
|
|
11512
|
+
const stored = out[field];
|
|
11513
|
+
if (stored === null || stored === void 0) continue;
|
|
11514
|
+
let currency;
|
|
11515
|
+
let scaledIntString;
|
|
11516
|
+
if (desc.mode === "fixed") {
|
|
11517
|
+
if (typeof stored !== "string" && typeof stored !== "number") continue;
|
|
11518
|
+
currency = desc.fixedCurrency;
|
|
11519
|
+
scaledIntString = String(stored);
|
|
11520
|
+
} else {
|
|
11521
|
+
if (!isMoneyValueObject(stored)) continue;
|
|
11522
|
+
const amount = stored.amount;
|
|
11523
|
+
if (typeof stored.currency !== "string" || typeof amount !== "string" && typeof amount !== "number") continue;
|
|
11524
|
+
currency = stored.currency;
|
|
11525
|
+
scaledIntString = String(amount);
|
|
11526
|
+
}
|
|
11527
|
+
const scale = desc.scaleFor(currency);
|
|
11528
|
+
const decimal = formatScaledInt(BigInt(scaledIntString), scale);
|
|
11529
|
+
out[field] = desc.mode === "fixed" ? decimal : { amount: decimal, currency };
|
|
11530
|
+
if (format) {
|
|
11531
|
+
out[`${field}Formatted`] = formatCurrency(decimal, currency, scale, fmtLocale);
|
|
11532
|
+
out[`${field}Number`] = Number(decimal);
|
|
11533
|
+
}
|
|
11534
|
+
}
|
|
11535
|
+
return out;
|
|
11536
|
+
}
|
|
11537
|
+
|
|
11538
|
+
// src/computed/index.ts
|
|
11539
|
+
init_errors();
|
|
11540
|
+
var ComputedFieldError = class extends NoydbError {
|
|
11541
|
+
constructor(field, id, cause) {
|
|
11542
|
+
super(
|
|
11543
|
+
"COMPUTED_FIELD",
|
|
11544
|
+
`computed field "${field}" threw for record "${id}": ` + (cause instanceof Error ? cause.message : String(cause))
|
|
11545
|
+
);
|
|
11546
|
+
this.field = field;
|
|
11547
|
+
this.id = id;
|
|
11548
|
+
this.cause = cause;
|
|
11549
|
+
this.name = "ComputedFieldError";
|
|
11550
|
+
}
|
|
11551
|
+
field;
|
|
11552
|
+
id;
|
|
11553
|
+
cause;
|
|
11554
|
+
};
|
|
11555
|
+
function evalComputedFields(record, computed, id) {
|
|
11556
|
+
const out = { ...record };
|
|
11557
|
+
for (const [field, fn] of Object.entries(computed)) {
|
|
11558
|
+
try {
|
|
11559
|
+
out[field] = fn(out);
|
|
11560
|
+
} catch (cause) {
|
|
11561
|
+
throw new ComputedFieldError(field, id, cause);
|
|
11562
|
+
}
|
|
11563
|
+
}
|
|
11564
|
+
return out;
|
|
11565
|
+
}
|
|
11566
|
+
|
|
10037
11567
|
// src/i18n/strategy.ts
|
|
10038
11568
|
function notEnabled(op) {
|
|
10039
11569
|
return new Error(
|
|
@@ -10352,6 +11882,7 @@ var NO_AGGREGATE = {
|
|
|
10352
11882
|
};
|
|
10353
11883
|
|
|
10354
11884
|
// src/query/builder.ts
|
|
11885
|
+
init_money_reducer();
|
|
10355
11886
|
var EMPTY_PLAN = {
|
|
10356
11887
|
clauses: [],
|
|
10357
11888
|
orderBy: [],
|
|
@@ -10720,7 +12251,7 @@ var Query = class _Query {
|
|
|
10720
12251
|
* for the ordering rationale.
|
|
10721
12252
|
*/
|
|
10722
12253
|
toArray() {
|
|
10723
|
-
const base = executePlanWithSource(this.source, this.plan, this.joinContext);
|
|
12254
|
+
const base = this.decodeMoney(executePlanWithSource(this.source, this.plan, this.joinContext));
|
|
10724
12255
|
if (this.plan.joins.length === 0) return base;
|
|
10725
12256
|
if (!this.joinContext) {
|
|
10726
12257
|
throw new Error(
|
|
@@ -10729,6 +12260,23 @@ var Query = class _Query {
|
|
|
10729
12260
|
}
|
|
10730
12261
|
return applyJoins(base, this.plan.joins, this.joinContext);
|
|
10731
12262
|
}
|
|
12263
|
+
/**
|
|
12264
|
+
* Decode this source's money fields on read (stored scaled-int → canonical
|
|
12265
|
+
* decimal), so `query().toArray()` agrees with `get()`/`sum()` on the value.
|
|
12266
|
+
* No-op when the source declares no money fields.
|
|
12267
|
+
*
|
|
12268
|
+
* The query layer carries no locale context, so we decode with `'raw'` —
|
|
12269
|
+
* canonical decimal, WITHOUT fabricating locale-formatted `<field>Formatted`
|
|
12270
|
+
* / `<field>Number` virtuals. Producing a guessed-locale string here would
|
|
12271
|
+
* just reintroduce #322's "two read paths disagree" failure on the virtual
|
|
12272
|
+
* field (e.g. it-IT via `get()` vs en-US here). Consumers who need formatted
|
|
12273
|
+
* money read through `get()`/`list()` with a locale.
|
|
12274
|
+
*/
|
|
12275
|
+
decodeMoney(records) {
|
|
12276
|
+
const moneyFields = this.source.moneyFields;
|
|
12277
|
+
if (!moneyFields || Object.keys(moneyFields).length === 0) return records;
|
|
12278
|
+
return records.map((r) => decodeMoneyFields(r, moneyFields, "raw"));
|
|
12279
|
+
}
|
|
10732
12280
|
/** Return the first matching record, or null. Joins are applied. */
|
|
10733
12281
|
first() {
|
|
10734
12282
|
const arr = this.limit(1).toArray();
|
|
@@ -10797,6 +12345,10 @@ var Query = class _Query {
|
|
|
10797
12345
|
* partition boundaries without an API break.
|
|
10798
12346
|
*/
|
|
10799
12347
|
aggregate(spec) {
|
|
12348
|
+
const moneyFields = this.source.moneyFields;
|
|
12349
|
+
if (moneyFields) {
|
|
12350
|
+
spec = wrapMoneyReducers(spec, moneyFields);
|
|
12351
|
+
}
|
|
10800
12352
|
const source = this.source;
|
|
10801
12353
|
const clauses = this.plan.clauses;
|
|
10802
12354
|
const joinCtx = this.joinContext;
|
|
@@ -10844,13 +12396,15 @@ var Query = class _Query {
|
|
|
10844
12396
|
executeRecords,
|
|
10845
12397
|
field,
|
|
10846
12398
|
upstreams,
|
|
10847
|
-
dictLabelResolver
|
|
12399
|
+
dictLabelResolver,
|
|
12400
|
+
this.source.moneyFields
|
|
10848
12401
|
);
|
|
10849
12402
|
}
|
|
10850
12403
|
return this.aggregateStrategy.groupByN(
|
|
10851
12404
|
executeRecords,
|
|
10852
12405
|
fields,
|
|
10853
|
-
upstreams
|
|
12406
|
+
upstreams,
|
|
12407
|
+
this.source.moneyFields
|
|
10854
12408
|
);
|
|
10855
12409
|
}
|
|
10856
12410
|
/**
|
|
@@ -11207,7 +12761,7 @@ function serializeClause(clause) {
|
|
|
11207
12761
|
}
|
|
11208
12762
|
function canonicalCtxHash(ctx) {
|
|
11209
12763
|
if (ctx === void 0) return "";
|
|
11210
|
-
const
|
|
12764
|
+
const canonical2 = JSON.stringify(ctx, (_key, value) => {
|
|
11211
12765
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
11212
12766
|
const sorted = {};
|
|
11213
12767
|
for (const k of Object.keys(value).sort()) {
|
|
@@ -11218,8 +12772,8 @@ function canonicalCtxHash(ctx) {
|
|
|
11218
12772
|
return value;
|
|
11219
12773
|
});
|
|
11220
12774
|
let h = 5381;
|
|
11221
|
-
for (let i = 0; i <
|
|
11222
|
-
h = (h << 5) + h ^
|
|
12775
|
+
for (let i = 0; i < canonical2.length; i++) {
|
|
12776
|
+
h = (h << 5) + h ^ canonical2.charCodeAt(i);
|
|
11223
12777
|
}
|
|
11224
12778
|
return (h >>> 0).toString(16).padStart(8, "0");
|
|
11225
12779
|
}
|
|
@@ -11391,7 +12945,8 @@ function count(opts) {
|
|
|
11391
12945
|
init: () => 0,
|
|
11392
12946
|
step: (state) => state + 1,
|
|
11393
12947
|
remove: (state) => state - 1,
|
|
11394
|
-
finalize: (state) => state
|
|
12948
|
+
finalize: (state) => state,
|
|
12949
|
+
merge: (a, b) => a + b
|
|
11395
12950
|
};
|
|
11396
12951
|
}
|
|
11397
12952
|
function sum(field, opts) {
|
|
@@ -11400,10 +12955,15 @@ function sum(field, opts) {
|
|
|
11400
12955
|
return {
|
|
11401
12956
|
op: "sum",
|
|
11402
12957
|
field,
|
|
12958
|
+
// Money-only metadata, read by `wrapMoneyReducers`. No effect on a
|
|
12959
|
+
// generic numeric sum.
|
|
12960
|
+
...opts?.convertTo !== void 0 ? { convertTo: opts.convertTo } : {},
|
|
12961
|
+
...opts?.fx !== void 0 ? { fx: opts.fx } : {},
|
|
11403
12962
|
init: () => 0,
|
|
11404
12963
|
step: (state, record) => state + readNumber(record, field),
|
|
11405
12964
|
remove: (state, record) => state - readNumber(record, field),
|
|
11406
|
-
finalize: (state) => state
|
|
12965
|
+
finalize: (state) => state,
|
|
12966
|
+
merge: (a, b) => a + b
|
|
11407
12967
|
};
|
|
11408
12968
|
}
|
|
11409
12969
|
function avg(field, opts) {
|
|
@@ -11421,7 +12981,8 @@ function avg(field, opts) {
|
|
|
11421
12981
|
sum: state.sum - readNumber(record, field),
|
|
11422
12982
|
count: state.count - 1
|
|
11423
12983
|
}),
|
|
11424
|
-
finalize: (state) => state.count === 0 ? null : state.sum / state.count
|
|
12984
|
+
finalize: (state) => state.count === 0 ? null : state.sum / state.count,
|
|
12985
|
+
merge: (a, b) => ({ sum: a.sum + b.sum, count: a.count + b.count })
|
|
11425
12986
|
};
|
|
11426
12987
|
}
|
|
11427
12988
|
function pushValue(state, value) {
|
|
@@ -11451,7 +13012,8 @@ function min(field, opts) {
|
|
|
11451
13012
|
if (v < out) out = v;
|
|
11452
13013
|
}
|
|
11453
13014
|
return out;
|
|
11454
|
-
}
|
|
13015
|
+
},
|
|
13016
|
+
merge: (a, b) => ({ values: [...a.values, ...b.values] })
|
|
11455
13017
|
};
|
|
11456
13018
|
}
|
|
11457
13019
|
function max(field, opts) {
|
|
@@ -11471,11 +13033,17 @@ function max(field, opts) {
|
|
|
11471
13033
|
if (v > out) out = v;
|
|
11472
13034
|
}
|
|
11473
13035
|
return out;
|
|
11474
|
-
}
|
|
13036
|
+
},
|
|
13037
|
+
merge: (a, b) => ({ values: [...a.values, ...b.values] })
|
|
11475
13038
|
};
|
|
11476
13039
|
}
|
|
11477
13040
|
function readNumber(record, field) {
|
|
11478
13041
|
const value = readPath(record, field);
|
|
13042
|
+
if (typeof value === "object" && value !== null && "amount" in value && "currency" in value) {
|
|
13043
|
+
throw new Error(
|
|
13044
|
+
`aggregate: field "${field}" holds a money value but was not money-aware \u2014 declare it in the collection's moneyFields so sum/min/max stay exact`
|
|
13045
|
+
);
|
|
13046
|
+
}
|
|
11479
13047
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
11480
13048
|
}
|
|
11481
13049
|
|
|
@@ -11511,12 +13079,29 @@ var ScanBuilder = class _ScanBuilder {
|
|
|
11511
13079
|
* context throws with an actionable error.
|
|
11512
13080
|
*/
|
|
11513
13081
|
joinContext;
|
|
11514
|
-
|
|
13082
|
+
/**
|
|
13083
|
+
* Money field descriptors for the backing collection. When present, yielded
|
|
13084
|
+
* records are decoded (stored scaled-int → canonical decimal) so `scan()`
|
|
13085
|
+
* agrees with `get()`/`list()`/`query().toArray()` — #322. Decoded with
|
|
13086
|
+
* `'raw'` (canonical decimal, no locale-formatted virtuals) since the scan
|
|
13087
|
+
* stream carries no locale context, mirroring `Query.toArray()`.
|
|
13088
|
+
*/
|
|
13089
|
+
moneyFields;
|
|
13090
|
+
constructor(pageProvider, pageSize = DEFAULT_SCAN_PAGE_SIZE, clauses = [], joins = [], joinContext, moneyFields) {
|
|
11515
13091
|
this.pageProvider = pageProvider;
|
|
11516
13092
|
this.pageSize = pageSize;
|
|
11517
13093
|
this.clauses = clauses;
|
|
11518
13094
|
this.joins = joins;
|
|
11519
13095
|
this.joinContext = joinContext;
|
|
13096
|
+
this.moneyFields = moneyFields;
|
|
13097
|
+
}
|
|
13098
|
+
/**
|
|
13099
|
+
* Decode this scan's money fields on a record (stored scaled-int → canonical
|
|
13100
|
+
* decimal). No-op when no money fields are declared. See {@link moneyFields}.
|
|
13101
|
+
*/
|
|
13102
|
+
decodeMoney(record) {
|
|
13103
|
+
if (!this.moneyFields || Object.keys(this.moneyFields).length === 0) return record;
|
|
13104
|
+
return decodeMoneyFields(record, this.moneyFields, "raw");
|
|
11520
13105
|
}
|
|
11521
13106
|
/**
|
|
11522
13107
|
* Add a field comparison. Runs per record as the scan stream
|
|
@@ -11538,7 +13123,8 @@ var ScanBuilder = class _ScanBuilder {
|
|
|
11538
13123
|
this.pageSize,
|
|
11539
13124
|
[...this.clauses, clause],
|
|
11540
13125
|
this.joins,
|
|
11541
|
-
this.joinContext
|
|
13126
|
+
this.joinContext,
|
|
13127
|
+
this.moneyFields
|
|
11542
13128
|
);
|
|
11543
13129
|
}
|
|
11544
13130
|
/**
|
|
@@ -11557,7 +13143,8 @@ var ScanBuilder = class _ScanBuilder {
|
|
|
11557
13143
|
this.pageSize,
|
|
11558
13144
|
[...this.clauses, clause],
|
|
11559
13145
|
this.joins,
|
|
11560
|
-
this.joinContext
|
|
13146
|
+
this.joinContext,
|
|
13147
|
+
this.moneyFields
|
|
11561
13148
|
);
|
|
11562
13149
|
}
|
|
11563
13150
|
/**
|
|
@@ -11668,7 +13255,8 @@ var ScanBuilder = class _ScanBuilder {
|
|
|
11668
13255
|
this.pageSize,
|
|
11669
13256
|
this.clauses,
|
|
11670
13257
|
[...this.joins, leg],
|
|
11671
|
-
this.joinContext
|
|
13258
|
+
this.joinContext,
|
|
13259
|
+
this.moneyFields
|
|
11672
13260
|
);
|
|
11673
13261
|
}
|
|
11674
13262
|
/**
|
|
@@ -11685,10 +13273,11 @@ var ScanBuilder = class _ScanBuilder {
|
|
|
11685
13273
|
while (true) {
|
|
11686
13274
|
for (const record of page.items) {
|
|
11687
13275
|
if (!this.recordMatches(record)) continue;
|
|
13276
|
+
const decoded = this.decodeMoney(record);
|
|
11688
13277
|
if (joinResolvers === null) {
|
|
11689
|
-
yield
|
|
13278
|
+
yield decoded;
|
|
11690
13279
|
} else {
|
|
11691
|
-
let attached =
|
|
13280
|
+
let attached = decoded;
|
|
11692
13281
|
for (const resolver of joinResolvers) {
|
|
11693
13282
|
attached = this.applyOneJoinStreaming(attached, resolver);
|
|
11694
13283
|
}
|
|
@@ -11889,8 +13478,8 @@ function coerceRefKey2(value) {
|
|
|
11889
13478
|
|
|
11890
13479
|
// src/indexing/persisted-indexes.ts
|
|
11891
13480
|
var IDX_PREFIX = "_idx/";
|
|
11892
|
-
function encodeIdxId(field,
|
|
11893
|
-
return `${IDX_PREFIX}${field}/${
|
|
13481
|
+
function encodeIdxId(field, recordId3) {
|
|
13482
|
+
return `${IDX_PREFIX}${field}/${recordId3}`;
|
|
11894
13483
|
}
|
|
11895
13484
|
function decodeIdxId(id) {
|
|
11896
13485
|
if (!id.startsWith(IDX_PREFIX)) return null;
|
|
@@ -11898,9 +13487,9 @@ function decodeIdxId(id) {
|
|
|
11898
13487
|
const firstSlash = rest.indexOf("/");
|
|
11899
13488
|
if (firstSlash <= 0) return null;
|
|
11900
13489
|
const field = rest.slice(0, firstSlash);
|
|
11901
|
-
const
|
|
11902
|
-
if (
|
|
11903
|
-
return { field, recordId:
|
|
13490
|
+
const recordId3 = rest.slice(firstSlash + 1);
|
|
13491
|
+
if (recordId3.length === 0) return null;
|
|
13492
|
+
return { field, recordId: recordId3 };
|
|
11904
13493
|
}
|
|
11905
13494
|
|
|
11906
13495
|
// src/indexing/lazy-builder.ts
|
|
@@ -12886,6 +14475,18 @@ var Collection = class {
|
|
|
12886
14475
|
* fields when a locale is requested.
|
|
12887
14476
|
*/
|
|
12888
14477
|
dictKeyFields;
|
|
14478
|
+
/**
|
|
14479
|
+
* Money field descriptors keyed by field path. Declared via the
|
|
14480
|
+
* `moneyFields` collection option: `put()` quantizes to a scaled-int
|
|
14481
|
+
* string, `get()`/`list()` decode back. Mutable so {@link _applyMoneyFields}
|
|
14482
|
+
* can attach descriptors to a collection MV-analysis pre-created.
|
|
14483
|
+
*/
|
|
14484
|
+
moneyFields;
|
|
14485
|
+
/**
|
|
14486
|
+
* Computed scalar fields, evaluated first on every `put()`. Mutable for
|
|
14487
|
+
* the same MV-pre-creation reconcile as {@link moneyFields}.
|
|
14488
|
+
*/
|
|
14489
|
+
computed;
|
|
12889
14490
|
/**
|
|
12890
14491
|
* Async callback provided by the Vault that resolves a dict key
|
|
12891
14492
|
* to its label for a given locale. Used by the locale-read path for
|
|
@@ -13027,6 +14628,8 @@ var Collection = class {
|
|
|
13027
14628
|
this.joinResolver = opts.joinResolver;
|
|
13028
14629
|
this.i18nFields = opts.i18nFields;
|
|
13029
14630
|
this.dictKeyFields = opts.dictKeyFields;
|
|
14631
|
+
this.moneyFields = opts.moneyFields;
|
|
14632
|
+
this.computed = opts.computed;
|
|
13030
14633
|
this.dictLabelResolver = opts.dictLabelResolver;
|
|
13031
14634
|
this.i18nPutValidator = opts.i18nPutValidator;
|
|
13032
14635
|
this.autoTranslateHook = opts.autoTranslateHook;
|
|
@@ -13151,6 +14754,19 @@ var Collection = class {
|
|
|
13151
14754
|
getSchema() {
|
|
13152
14755
|
return this.schema;
|
|
13153
14756
|
}
|
|
14757
|
+
/**
|
|
14758
|
+
* @internal — attach money descriptors post-construction. MV dependency
|
|
14759
|
+
* analysis auto-creates a source collection (without options) during
|
|
14760
|
+
* `openVault`, before the user's `collection(name, { moneyFields })`
|
|
14761
|
+
* declaration; this reconciles that ordering. First-wins. Not public.
|
|
14762
|
+
*/
|
|
14763
|
+
_applyMoneyFields(moneyFields) {
|
|
14764
|
+
if (this.moneyFields === void 0) this.moneyFields = moneyFields;
|
|
14765
|
+
}
|
|
14766
|
+
/** @internal — attach computed fields post-construction. See {@link _applyMoneyFields}. */
|
|
14767
|
+
_applyComputed(computed) {
|
|
14768
|
+
if (this.computed === void 0) this.computed = computed;
|
|
14769
|
+
}
|
|
13154
14770
|
/**
|
|
13155
14771
|
* Get a single record by ID.
|
|
13156
14772
|
*
|
|
@@ -13322,7 +14938,7 @@ var Collection = class {
|
|
|
13322
14938
|
existingRecord = null;
|
|
13323
14939
|
}
|
|
13324
14940
|
}
|
|
13325
|
-
|
|
14941
|
+
const gateEvent = {
|
|
13326
14942
|
op: existingEnv ? "update" : "create",
|
|
13327
14943
|
vault: this.vault,
|
|
13328
14944
|
collection: this.name,
|
|
@@ -13332,12 +14948,20 @@ var Collection = class {
|
|
|
13332
14948
|
existingVersion: existingEnv?._v ?? 0,
|
|
13333
14949
|
existingTs: existingEnv?._ts,
|
|
13334
14950
|
userId: this.keyring.userId,
|
|
13335
|
-
role: this.keyring.role
|
|
13336
|
-
|
|
14951
|
+
role: this.keyring.role,
|
|
14952
|
+
...this.computed !== void 0 ? { computedFieldNames: new Set(Object.keys(this.computed)) } : {}
|
|
14953
|
+
};
|
|
14954
|
+
await this.subsystemBus.dispatchGate("beforePut", gateEvent);
|
|
14955
|
+
}
|
|
14956
|
+
if (this.computed !== void 0) {
|
|
14957
|
+
record = evalComputedFields(record, this.computed, id);
|
|
13337
14958
|
}
|
|
13338
14959
|
if (this.schema !== void 0) {
|
|
13339
14960
|
record = await validateSchemaInput(this.schema, record, `put(${id})`);
|
|
13340
14961
|
}
|
|
14962
|
+
if (this.moneyFields) {
|
|
14963
|
+
record = quantizeMoneyFields(record, this.moneyFields);
|
|
14964
|
+
}
|
|
13341
14965
|
if (this.i18nFields) {
|
|
13342
14966
|
const obj = record;
|
|
13343
14967
|
for (const [field, descriptor] of Object.entries(this.i18nFields)) {
|
|
@@ -13988,9 +15612,17 @@ var Collection = class {
|
|
|
13988
15612
|
}
|
|
13989
15613
|
await this.ensureHydrated();
|
|
13990
15614
|
const records = [...this.cache.values()].map((e) => e.record);
|
|
13991
|
-
if (!
|
|
15615
|
+
if (!this.hasReadTransforms()) return records;
|
|
13992
15616
|
return Promise.all(records.map((r) => this.applyLocaleToRecord(r, locale)));
|
|
13993
15617
|
}
|
|
15618
|
+
/**
|
|
15619
|
+
* @internal — whether any read-side record transform is registered
|
|
15620
|
+
* (money decode, i18nText resolution, dictKey labels). Gates the
|
|
15621
|
+
* no-transform fast path in {@link list}.
|
|
15622
|
+
*/
|
|
15623
|
+
hasReadTransforms() {
|
|
15624
|
+
return this.moneyFields !== void 0 && Object.keys(this.moneyFields).length > 0 || this.i18nFields !== void 0 && Object.keys(this.i18nFields).length > 0 || this.dictKeyFields !== void 0 && Object.keys(this.dictKeyFields).length > 0;
|
|
15625
|
+
}
|
|
13994
15626
|
// ─── Bulk operations ─────────────────────────────────────
|
|
13995
15627
|
/**
|
|
13996
15628
|
* Put many records in one call. Each item is processed sequentially
|
|
@@ -14159,7 +15791,8 @@ var Collection = class {
|
|
|
14159
15791
|
// fields. The Query builder consults these when present and falls
|
|
14160
15792
|
// back to a linear scan otherwise.
|
|
14161
15793
|
getIndexes: () => this.getIndexes(),
|
|
14162
|
-
lookupById: (id) => this.cache.get(id)?.record
|
|
15794
|
+
lookupById: (id) => this.cache.get(id)?.record,
|
|
15795
|
+
...this.moneyFields ? { moneyFields: this.moneyFields } : {}
|
|
14163
15796
|
};
|
|
14164
15797
|
const resolver = this.joinResolver;
|
|
14165
15798
|
const leftCollection = this.name;
|
|
@@ -14495,7 +16128,8 @@ var Collection = class {
|
|
|
14495
16128
|
pageSize,
|
|
14496
16129
|
[],
|
|
14497
16130
|
[],
|
|
14498
|
-
joinContext
|
|
16131
|
+
joinContext,
|
|
16132
|
+
this.moneyFields
|
|
14499
16133
|
);
|
|
14500
16134
|
}
|
|
14501
16135
|
/** Decrypt a page of envelopes returned by `adapter.listPage`. */
|
|
@@ -14661,11 +16295,11 @@ var Collection = class {
|
|
|
14661
16295
|
}
|
|
14662
16296
|
}
|
|
14663
16297
|
persisted.clear();
|
|
14664
|
-
for (const
|
|
14665
|
-
const envelope = await this.adapter.get(this.vault, this.name,
|
|
16298
|
+
for (const recordId3 of canonicalIds) {
|
|
16299
|
+
const envelope = await this.adapter.get(this.vault, this.name, recordId3);
|
|
14666
16300
|
if (!envelope) continue;
|
|
14667
16301
|
const record = await this.decryptRecord(envelope, { skipValidation: true });
|
|
14668
|
-
await this.maintainPersistedIndexesOnPut(
|
|
16302
|
+
await this.maintainPersistedIndexesOnPut(recordId3, record, null, envelope._v);
|
|
14669
16303
|
}
|
|
14670
16304
|
this.persistedIndexesLoaded = true;
|
|
14671
16305
|
}
|
|
@@ -14841,10 +16475,14 @@ var Collection = class {
|
|
|
14841
16475
|
async applyLocaleToRecord(record, localeOpts) {
|
|
14842
16476
|
const hasI18n = this.i18nFields && Object.keys(this.i18nFields).length > 0;
|
|
14843
16477
|
const hasDict = this.dictKeyFields && Object.keys(this.dictKeyFields).length > 0;
|
|
14844
|
-
|
|
16478
|
+
const hasMoney = this.moneyFields && Object.keys(this.moneyFields).length > 0;
|
|
16479
|
+
if (!hasI18n && !hasDict && !hasMoney) return record;
|
|
14845
16480
|
const locale = localeOpts?.locale ?? this.defaultLocale;
|
|
14846
|
-
if (!locale) return record;
|
|
14847
16481
|
let result = record;
|
|
16482
|
+
if (hasMoney && this.moneyFields) {
|
|
16483
|
+
result = decodeMoneyFields(result, this.moneyFields, typeof locale === "string" ? locale : void 0);
|
|
16484
|
+
}
|
|
16485
|
+
if (!locale) return result;
|
|
14848
16486
|
if (hasI18n && this.i18nFields) {
|
|
14849
16487
|
result = this.i18nStrategy.applyI18nLocale(result, this.i18nFields, locale, localeOpts?.fallback);
|
|
14850
16488
|
}
|
|
@@ -15715,7 +17353,165 @@ var OverlayedCollection = class {
|
|
|
15715
17353
|
// src/vault.ts
|
|
15716
17354
|
init_errors();
|
|
15717
17355
|
init_errors();
|
|
15718
|
-
|
|
17356
|
+
|
|
17357
|
+
// src/numbering/index.ts
|
|
17358
|
+
init_types();
|
|
17359
|
+
init_crypto();
|
|
17360
|
+
init_errors();
|
|
17361
|
+
var NUMBERING_HEAD_COLLECTION = "_numbering_head";
|
|
17362
|
+
var NUMBERING_PENDING_COLLECTION = "_numbering_pending";
|
|
17363
|
+
var DeferredNumberingStore = class {
|
|
17364
|
+
adapter;
|
|
17365
|
+
vault;
|
|
17366
|
+
encrypted;
|
|
17367
|
+
getDEK;
|
|
17368
|
+
actor;
|
|
17369
|
+
configs;
|
|
17370
|
+
/**
|
|
17371
|
+
* Stamp a serial onto a USER record THROUGH the Collection layer (so the
|
|
17372
|
+
* cache, indexes, and MVs stay coherent — the engine must NOT write user
|
|
17373
|
+
* collections at the raw adapter level). Returns false if the record is
|
|
17374
|
+
* gone (the engine then skips it without burning a serial). Provided by the
|
|
17375
|
+
* vault; unit tests pass a Map-backed double.
|
|
17376
|
+
*/
|
|
17377
|
+
stamp;
|
|
17378
|
+
/** In-process registry: `${series}::${recordId}` → resolver for the live next() Promise. */
|
|
17379
|
+
waiters = /* @__PURE__ */ new Map();
|
|
17380
|
+
dekCache = /* @__PURE__ */ new Map();
|
|
17381
|
+
constructor(opts) {
|
|
17382
|
+
this.adapter = opts.adapter;
|
|
17383
|
+
this.vault = opts.vault;
|
|
17384
|
+
this.encrypted = opts.encrypted;
|
|
17385
|
+
this.getDEK = opts.getDEK;
|
|
17386
|
+
this.actor = opts.actor;
|
|
17387
|
+
this.configs = opts.configs;
|
|
17388
|
+
this.stamp = opts.stamp;
|
|
17389
|
+
}
|
|
17390
|
+
has(series) {
|
|
17391
|
+
return this.configs.has(series);
|
|
17392
|
+
}
|
|
17393
|
+
dek(collection) {
|
|
17394
|
+
let p = this.dekCache.get(collection);
|
|
17395
|
+
if (!p) {
|
|
17396
|
+
p = this.getDEK(collection);
|
|
17397
|
+
this.dekCache.set(collection, p);
|
|
17398
|
+
}
|
|
17399
|
+
return p;
|
|
17400
|
+
}
|
|
17401
|
+
async readJson(collection, id) {
|
|
17402
|
+
const env = await this.adapter.get(this.vault, collection, id);
|
|
17403
|
+
if (!env) return { env: null, value: null };
|
|
17404
|
+
const json = this.encrypted ? await decrypt(env._iv, env._data, await this.dek(collection)) : env._data;
|
|
17405
|
+
return { env, value: JSON.parse(json) };
|
|
17406
|
+
}
|
|
17407
|
+
async writeJson(collection, id, value, expectedVersion) {
|
|
17408
|
+
const json = JSON.stringify(value);
|
|
17409
|
+
let env;
|
|
17410
|
+
if (!this.encrypted) {
|
|
17411
|
+
env = { _noydb: NOYDB_FORMAT_VERSION, _v: expectedVersion + 1, _ts: (/* @__PURE__ */ new Date()).toISOString(), _iv: "", _data: json, _by: this.actor };
|
|
17412
|
+
} else {
|
|
17413
|
+
const { iv, data } = await encrypt(json, await this.dek(collection));
|
|
17414
|
+
env = { _noydb: NOYDB_FORMAT_VERSION, _v: expectedVersion + 1, _ts: (/* @__PURE__ */ new Date()).toISOString(), _iv: iv, _data: data, _by: this.actor };
|
|
17415
|
+
}
|
|
17416
|
+
await this.adapter.put(this.vault, collection, id, env, expectedVersion);
|
|
17417
|
+
}
|
|
17418
|
+
pendingId(series, recordId3) {
|
|
17419
|
+
return `${series}::${recordId3}`;
|
|
17420
|
+
}
|
|
17421
|
+
/** Current last-assigned serial for a series (0 if none). */
|
|
17422
|
+
async peek(series) {
|
|
17423
|
+
const { value } = await this.readJson(NUMBERING_HEAD_COLLECTION, series);
|
|
17424
|
+
return value?.lastSerial ?? 0;
|
|
17425
|
+
}
|
|
17426
|
+
/**
|
|
17427
|
+
* Enqueue a record for numbering: stamp it with the current store clock and
|
|
17428
|
+
* durably write a pending entry. The returned Promise resolves once the
|
|
17429
|
+
* record is durably enqueued; its `assigned` field resolves with the serial
|
|
17430
|
+
* at the next pass (the record's `field` is the durable source of truth —
|
|
17431
|
+
* `assigned` is an in-process convenience that a crash may drop).
|
|
17432
|
+
*/
|
|
17433
|
+
async enqueue(series, recordId3) {
|
|
17434
|
+
const cfg = this.configs.get(series);
|
|
17435
|
+
if (!cfg) throw new NumberingUncertaintyError(series);
|
|
17436
|
+
if (typeof this.adapter.getStoreTime !== "function") throw new NumberingUncertaintyError(series);
|
|
17437
|
+
const st = await this.adapter.getStoreTime();
|
|
17438
|
+
const id = this.pendingId(series, recordId3);
|
|
17439
|
+
const { env } = await this.readJson(NUMBERING_PENDING_COLLECTION, id);
|
|
17440
|
+
const entry = {
|
|
17441
|
+
series,
|
|
17442
|
+
recordId: recordId3,
|
|
17443
|
+
collection: cfg.collection,
|
|
17444
|
+
field: cfg.field,
|
|
17445
|
+
storeEarliest: st.earliest,
|
|
17446
|
+
storeLatest: st.latest,
|
|
17447
|
+
enqueuedAt: Date.now()
|
|
17448
|
+
};
|
|
17449
|
+
await this.writeJson(NUMBERING_PENDING_COLLECTION, id, entry, env?._v ?? 0);
|
|
17450
|
+
const assigned = new Promise((resolve, reject) => {
|
|
17451
|
+
this.waiters.set(id, { resolve, reject });
|
|
17452
|
+
});
|
|
17453
|
+
return { assigned };
|
|
17454
|
+
}
|
|
17455
|
+
async listPending(series) {
|
|
17456
|
+
const ids = await this.adapter.list(this.vault, NUMBERING_PENDING_COLLECTION);
|
|
17457
|
+
const prefix = `${series}::`;
|
|
17458
|
+
const out = [];
|
|
17459
|
+
for (const id of ids) {
|
|
17460
|
+
if (!id.startsWith(prefix)) continue;
|
|
17461
|
+
const { value } = await this.readJson(NUMBERING_PENDING_COLLECTION, id);
|
|
17462
|
+
if (value) out.push({ id, entry: value });
|
|
17463
|
+
}
|
|
17464
|
+
return out;
|
|
17465
|
+
}
|
|
17466
|
+
/**
|
|
17467
|
+
* Run a numbering pass for `series`: select entries provably settled
|
|
17468
|
+
* (`storeLatest ≤ now.earliest` — commit-wait), order by
|
|
17469
|
+
* `(storeEarliest, recordId)`, assign serials after the head, stamp each
|
|
17470
|
+
* record's field, advance the head with one CAS, and consume the entries.
|
|
17471
|
+
* Idempotent/convergent: a losing concurrent pass returns `[]` and the next
|
|
17472
|
+
* pass reconciles. Resolves any in-process enqueue() `assigned` Promises.
|
|
17473
|
+
*/
|
|
17474
|
+
async runPass(series) {
|
|
17475
|
+
const cfg = this.configs.get(series);
|
|
17476
|
+
if (!cfg) throw new NumberingUncertaintyError(series);
|
|
17477
|
+
if (typeof this.adapter.getStoreTime !== "function") throw new NumberingUncertaintyError(series);
|
|
17478
|
+
const now = await this.adapter.getStoreTime();
|
|
17479
|
+
const settled = (await this.listPending(series)).filter((p) => p.entry.storeLatest <= now.earliest).sort(
|
|
17480
|
+
(a, b) => a.entry.storeEarliest - b.entry.storeEarliest || (a.entry.recordId < b.entry.recordId ? -1 : a.entry.recordId > b.entry.recordId ? 1 : 0)
|
|
17481
|
+
);
|
|
17482
|
+
if (settled.length === 0) return [];
|
|
17483
|
+
const { env: headEnv, value: head } = await this.readJson(NUMBERING_HEAD_COLLECTION, series);
|
|
17484
|
+
let serial = head?.lastSerial ?? 0;
|
|
17485
|
+
const assignments = [];
|
|
17486
|
+
for (const { entry } of settled) {
|
|
17487
|
+
serial += 1;
|
|
17488
|
+
const ok = await this.stamp(entry.collection, entry.recordId, entry.field, serial);
|
|
17489
|
+
if (!ok) {
|
|
17490
|
+
serial -= 1;
|
|
17491
|
+
continue;
|
|
17492
|
+
}
|
|
17493
|
+
assignments.push({ recordId: entry.recordId, serial });
|
|
17494
|
+
}
|
|
17495
|
+
try {
|
|
17496
|
+
await this.writeJson(NUMBERING_HEAD_COLLECTION, series, { series, lastSerial: serial, watermark: now.earliest }, headEnv?._v ?? 0);
|
|
17497
|
+
} catch (err) {
|
|
17498
|
+
if (err instanceof ConflictError) return [];
|
|
17499
|
+
throw err;
|
|
17500
|
+
}
|
|
17501
|
+
for (const { id, entry } of settled) {
|
|
17502
|
+
await this.adapter.delete(this.vault, NUMBERING_PENDING_COLLECTION, id);
|
|
17503
|
+
const a = assignments.find((x) => x.recordId === entry.recordId);
|
|
17504
|
+
if (a) {
|
|
17505
|
+
this.waiters.get(id)?.resolve(a.serial);
|
|
17506
|
+
this.waiters.delete(id);
|
|
17507
|
+
}
|
|
17508
|
+
}
|
|
17509
|
+
return assignments;
|
|
17510
|
+
}
|
|
17511
|
+
};
|
|
17512
|
+
|
|
17513
|
+
// src/vault.ts
|
|
17514
|
+
init_constants2();
|
|
15719
17515
|
init_entry();
|
|
15720
17516
|
|
|
15721
17517
|
// src/shadow/strategy.ts
|
|
@@ -16243,6 +18039,7 @@ async function runCompaction(ctx, options = {}) {
|
|
|
16243
18039
|
let evicted = 0;
|
|
16244
18040
|
let records = 0;
|
|
16245
18041
|
let auditEntries = 0;
|
|
18042
|
+
let held = 0;
|
|
16246
18043
|
let collectionsWithPolicy = 0;
|
|
16247
18044
|
outer: for (const collectionName of allCollections) {
|
|
16248
18045
|
if (collectionName.startsWith("_")) continue;
|
|
@@ -16253,25 +18050,29 @@ async function runCompaction(ctx, options = {}) {
|
|
|
16253
18050
|
collectionsWithPolicy += 1;
|
|
16254
18051
|
byCollection[collectionName] = { records: 0, evicted: 0 };
|
|
16255
18052
|
const ids = await ctx.listRecords(collectionName);
|
|
16256
|
-
for (const
|
|
18053
|
+
for (const recordId3 of ids) {
|
|
16257
18054
|
if (evicted >= maxEvictions) break outer;
|
|
16258
|
-
const record = await ctx.getRecord(collectionName,
|
|
18055
|
+
const record = await ctx.getRecord(collectionName, recordId3).catch(() => null);
|
|
16259
18056
|
if (record === null) continue;
|
|
16260
18057
|
records += 1;
|
|
16261
18058
|
byCollection[collectionName].records += 1;
|
|
16262
|
-
const slots = await ctx.listSlots(collectionName,
|
|
18059
|
+
const slots = await ctx.listSlots(collectionName, recordId3).catch(() => []);
|
|
16263
18060
|
for (const slot of slots) {
|
|
16264
18061
|
if (evicted >= maxEvictions) break outer;
|
|
16265
18062
|
const policy = config[slot.name];
|
|
16266
18063
|
if (!policy) continue;
|
|
16267
18064
|
const reason = evaluatePolicy(policy, record, slot, now);
|
|
16268
18065
|
if (!reason) continue;
|
|
18066
|
+
if (isHeld2(policy, record, now)) {
|
|
18067
|
+
held += 1;
|
|
18068
|
+
continue;
|
|
18069
|
+
}
|
|
16269
18070
|
if (!dryRun) {
|
|
16270
|
-
await ctx.deleteSlot(collectionName,
|
|
18071
|
+
await ctx.deleteSlot(collectionName, recordId3, slot.name);
|
|
16271
18072
|
await writeAuditEntry(ctx, {
|
|
16272
|
-
id: generateEvictionId(collectionName,
|
|
18073
|
+
id: generateEvictionId(collectionName, recordId3, slot.name),
|
|
16273
18074
|
collection: collectionName,
|
|
16274
|
-
recordId:
|
|
18075
|
+
recordId: recordId3,
|
|
16275
18076
|
slotName: slot.name,
|
|
16276
18077
|
blobHash: slot.eTag,
|
|
16277
18078
|
reason,
|
|
@@ -16290,9 +18091,32 @@ async function runCompaction(ctx, options = {}) {
|
|
|
16290
18091
|
records,
|
|
16291
18092
|
collections: collectionsWithPolicy,
|
|
16292
18093
|
auditEntries,
|
|
18094
|
+
held,
|
|
16293
18095
|
byCollection
|
|
16294
18096
|
};
|
|
16295
18097
|
}
|
|
18098
|
+
function isHeld2(policy, record, now) {
|
|
18099
|
+
if (policy.legalHold) {
|
|
18100
|
+
try {
|
|
18101
|
+
if (policy.legalHold(record)) return true;
|
|
18102
|
+
} catch {
|
|
18103
|
+
return true;
|
|
18104
|
+
}
|
|
18105
|
+
}
|
|
18106
|
+
if (policy.retainUntil) {
|
|
18107
|
+
try {
|
|
18108
|
+
const until = policy.retainUntil(record);
|
|
18109
|
+
if (until !== null && until !== void 0) {
|
|
18110
|
+
const t = until instanceof Date ? until.getTime() : typeof until === "number" ? until : Date.parse(String(until));
|
|
18111
|
+
if (!Number.isFinite(t)) return true;
|
|
18112
|
+
if (t > now.getTime()) return true;
|
|
18113
|
+
}
|
|
18114
|
+
} catch {
|
|
18115
|
+
return true;
|
|
18116
|
+
}
|
|
18117
|
+
}
|
|
18118
|
+
return false;
|
|
18119
|
+
}
|
|
16296
18120
|
function evaluatePolicy(policy, record, slot, now) {
|
|
16297
18121
|
let ttlTriggered = false;
|
|
16298
18122
|
let predicateTriggered = false;
|
|
@@ -16315,11 +18139,11 @@ function evaluatePolicy(policy, record, slot, now) {
|
|
|
16315
18139
|
if (predicateTriggered) return "predicate";
|
|
16316
18140
|
return null;
|
|
16317
18141
|
}
|
|
16318
|
-
function generateEvictionId(collection,
|
|
18142
|
+
function generateEvictionId(collection, recordId3, slotName) {
|
|
16319
18143
|
const rand = globalThis.crypto.getRandomValues(new Uint8Array(8));
|
|
16320
18144
|
let suffix = "";
|
|
16321
18145
|
for (const b of rand) suffix += b.toString(16).padStart(2, "0");
|
|
16322
|
-
return `${collection}__${
|
|
18146
|
+
return `${collection}__${recordId3}__${slotName}__${suffix}`;
|
|
16323
18147
|
}
|
|
16324
18148
|
async function writeAuditEntry(ctx, entry) {
|
|
16325
18149
|
const json = JSON.stringify(entry);
|
|
@@ -16370,7 +18194,7 @@ async function deriveMagicLinkContentKey(serverSecret, token, vault) {
|
|
|
16370
18194
|
["encrypt", "decrypt"]
|
|
16371
18195
|
);
|
|
16372
18196
|
}
|
|
16373
|
-
async function writeMagicLinkGrant(store, vault, grantor, contentKey, grantKek,
|
|
18197
|
+
async function writeMagicLinkGrant(store, vault, grantor, contentKey, grantKek, recordId3, opts) {
|
|
16374
18198
|
const collectionName = opts.collection ?? null;
|
|
16375
18199
|
const sourceKey = collectionName ? dekKey(collectionName, opts.tier) : `__any#${opts.tier}`;
|
|
16376
18200
|
const sourceDek = grantor.deks.get(sourceKey);
|
|
@@ -16383,7 +18207,7 @@ async function writeMagicLinkGrant(store, vault, grantor, contentKey, grantKek,
|
|
|
16383
18207
|
const until = typeof opts.until === "string" ? opts.until : opts.until.toISOString();
|
|
16384
18208
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
16385
18209
|
const payload = {
|
|
16386
|
-
id:
|
|
18210
|
+
id: recordId3,
|
|
16387
18211
|
toUser: opts.toUser,
|
|
16388
18212
|
fromUser: grantor.userId,
|
|
16389
18213
|
tier: opts.tier,
|
|
@@ -16403,11 +18227,11 @@ async function writeMagicLinkGrant(store, vault, grantor, contentKey, grantKek,
|
|
|
16403
18227
|
_data: data,
|
|
16404
18228
|
_by: grantor.userId
|
|
16405
18229
|
};
|
|
16406
|
-
await store.put(vault, MAGIC_LINK_GRANTS_COLLECTION,
|
|
16407
|
-
return { recordId:
|
|
18230
|
+
await store.put(vault, MAGIC_LINK_GRANTS_COLLECTION, recordId3, envelope);
|
|
18231
|
+
return { recordId: recordId3, payload };
|
|
16408
18232
|
}
|
|
16409
|
-
async function readMagicLinkGrantRecord(store, vault, contentKey,
|
|
16410
|
-
const env = await store.get(vault, MAGIC_LINK_GRANTS_COLLECTION,
|
|
18233
|
+
async function readMagicLinkGrantRecord(store, vault, contentKey, recordId3) {
|
|
18234
|
+
const env = await store.get(vault, MAGIC_LINK_GRANTS_COLLECTION, recordId3);
|
|
16411
18235
|
if (!env) return null;
|
|
16412
18236
|
try {
|
|
16413
18237
|
const json = await decrypt(env._iv, env._data, contentKey);
|
|
@@ -17009,6 +18833,10 @@ var Vault = class {
|
|
|
17009
18833
|
* call throws with a pointer at `@noy-db/hub/blobs`.
|
|
17010
18834
|
*/
|
|
17011
18835
|
blobStrategy;
|
|
18836
|
+
/** Cold-storage archival strategy (the archive target store). */
|
|
18837
|
+
archiveStrategy;
|
|
18838
|
+
/** Per-collection record archival policies. Indexed by collection name. */
|
|
18839
|
+
archiveRegistry = /* @__PURE__ */ new Map();
|
|
17012
18840
|
indexStrategy;
|
|
17013
18841
|
aggregateStrategy;
|
|
17014
18842
|
crdtStrategy;
|
|
@@ -17112,6 +18940,12 @@ var Vault = class {
|
|
|
17112
18940
|
* docstring.
|
|
17113
18941
|
*/
|
|
17114
18942
|
ledgerStore = null;
|
|
18943
|
+
/** Lazily-built atomic-sequence store. See {@link sequence}. */
|
|
18944
|
+
sequenceStore = null;
|
|
18945
|
+
/** Lazily-built deferred-numbering engine. See {@link runNumberingPass}. */
|
|
18946
|
+
deferredNumbering = null;
|
|
18947
|
+
/** Registered deferred-numbering series, keyed by series name. */
|
|
18948
|
+
numberingConfigs;
|
|
17115
18949
|
/**
|
|
17116
18950
|
* Background writes for persisted-schema envelopes (#schema-dump v0
|
|
17117
18951
|
* slice 1). One promise per `collection({ persistJsonSchema: true })`
|
|
@@ -17198,6 +19032,7 @@ var Vault = class {
|
|
|
17198
19032
|
constructor(opts) {
|
|
17199
19033
|
this.adapter = opts.adapter;
|
|
17200
19034
|
this.name = opts.name;
|
|
19035
|
+
this.numberingConfigs = new Map((opts.numberingConfigs ?? []).map((c) => [c.series, c]));
|
|
17201
19036
|
this.noydb = opts.noydb;
|
|
17202
19037
|
this.keyring = opts.keyring;
|
|
17203
19038
|
this.encrypted = opts.encrypted;
|
|
@@ -17213,6 +19048,7 @@ var Vault = class {
|
|
|
17213
19048
|
this.onRegisterConflictResolver = opts.onRegisterConflictResolver;
|
|
17214
19049
|
this.syncAdapter = opts.syncAdapter;
|
|
17215
19050
|
this.blobStrategy = opts.blobStrategy;
|
|
19051
|
+
this.archiveStrategy = opts.archiveStrategy;
|
|
17216
19052
|
this.indexStrategy = opts.indexStrategy;
|
|
17217
19053
|
this.aggregateStrategy = opts.aggregateStrategy;
|
|
17218
19054
|
this.crdtStrategy = opts.crdtStrategy;
|
|
@@ -17275,8 +19111,9 @@ var Vault = class {
|
|
|
17275
19111
|
*. `put()` validates keys against the declared set; reads
|
|
17276
19112
|
* with `{ locale }` add `<field>Label` virtual fields.
|
|
17277
19113
|
*
|
|
17278
|
-
* Throws `ReservedCollectionNameError` for names starting with `_dict_
|
|
17279
|
-
* Use `vault.dictionary(name)`
|
|
19114
|
+
* Throws `ReservedCollectionNameError` for names starting with `_dict_` or
|
|
19115
|
+
* equal to `_sequences`. Use `vault.dictionary(name)` for dict collections
|
|
19116
|
+
* and `vault.sequence(name)` for sequence counters.
|
|
17280
19117
|
*
|
|
17281
19118
|
* Lazy mode + indexes is rejected at construction time — see the
|
|
17282
19119
|
* Collection constructor for the rationale.
|
|
@@ -17295,7 +19132,16 @@ var Vault = class {
|
|
|
17295
19132
|
if (isDictCollectionName(collectionName)) {
|
|
17296
19133
|
throw new ReservedCollectionNameError(collectionName);
|
|
17297
19134
|
}
|
|
19135
|
+
if (collectionName === SEQUENCE_COLLECTION) {
|
|
19136
|
+
throw new ReservedCollectionNameError(collectionName);
|
|
19137
|
+
}
|
|
17298
19138
|
let coll = this.collectionCache.get(collectionName);
|
|
19139
|
+
if (coll && options?.moneyFields) {
|
|
19140
|
+
coll._applyMoneyFields(options.moneyFields);
|
|
19141
|
+
}
|
|
19142
|
+
if (coll && options?.computed) {
|
|
19143
|
+
coll._applyComputed(options.computed);
|
|
19144
|
+
}
|
|
17299
19145
|
if (!coll) {
|
|
17300
19146
|
if (options?.refs) {
|
|
17301
19147
|
this.refRegistry.register(collectionName, options.refs);
|
|
@@ -17306,6 +19152,9 @@ var Vault = class {
|
|
|
17306
19152
|
if (options?.blobFields) {
|
|
17307
19153
|
this.blobFieldsRegistry.set(collectionName, options.blobFields);
|
|
17308
19154
|
}
|
|
19155
|
+
if (options?.archive) {
|
|
19156
|
+
this.archiveRegistry.set(collectionName, options.archive);
|
|
19157
|
+
}
|
|
17309
19158
|
if (options?.attestation !== void 0) {
|
|
17310
19159
|
this.attestationRegistry.set(collectionName, options.attestation);
|
|
17311
19160
|
}
|
|
@@ -17421,6 +19270,8 @@ var Vault = class {
|
|
|
17421
19270
|
collOpts.onCrossTierAccess = (event) => this.emitCrossTier(event);
|
|
17422
19271
|
if (this.syncAdapter !== void 0) collOpts.syncAdapter = this.syncAdapter;
|
|
17423
19272
|
if (options?.i18nFields !== void 0) collOpts.i18nFields = options.i18nFields;
|
|
19273
|
+
if (options?.moneyFields !== void 0) collOpts.moneyFields = options.moneyFields;
|
|
19274
|
+
if (options?.computed !== void 0) collOpts.computed = options.computed;
|
|
17424
19275
|
if (options?.dictKeyFields !== void 0) {
|
|
17425
19276
|
collOpts.dictLabelResolver = async (dictName, key, locale, fallback) => {
|
|
17426
19277
|
const handle = this.dictionary(dictName);
|
|
@@ -17840,6 +19691,77 @@ var Vault = class {
|
|
|
17840
19691
|
* await vault.compact({ maxEvictions: 1000 }) // cap batch
|
|
17841
19692
|
* ```
|
|
17842
19693
|
*/
|
|
19694
|
+
/**
|
|
19695
|
+
* Atomic, gap-free numbering. `vault.sequence('invoice-2026').next()`
|
|
19696
|
+
* returns 1, 2, 3, … with no gaps or duplicates under concurrency, via
|
|
19697
|
+
* an optimistic-CAS counter at `_sequences/<name>`. Each name is an
|
|
19698
|
+
* independent sequence.
|
|
19699
|
+
*
|
|
19700
|
+
* **Online-only:** `next()` throws `SequenceOfflineError` unless the
|
|
19701
|
+
* store advertises `capabilities.casAtomic` — gap-free numbering cannot
|
|
19702
|
+
* be serialized by an offline / non-CAS writer.
|
|
19703
|
+
*
|
|
19704
|
+
* ```ts
|
|
19705
|
+
* const n = await vault.sequence('invoice-2026').next() // 1, then 2, …
|
|
19706
|
+
* const cur = await vault.sequence('invoice-2026').peek() // current value, no allocation
|
|
19707
|
+
* ```
|
|
19708
|
+
*/
|
|
19709
|
+
sequence(name) {
|
|
19710
|
+
if (this.numberingConfigs.has(name)) {
|
|
19711
|
+
const eng = this.deferred();
|
|
19712
|
+
return {
|
|
19713
|
+
next: async (opts) => {
|
|
19714
|
+
if (!opts?.for) {
|
|
19715
|
+
throw new ValidationError(`sequence("${name}") is a deferred-numbering series; call next({ for: recordId }).`);
|
|
19716
|
+
}
|
|
19717
|
+
return (await eng.enqueue(name, opts.for)).assigned;
|
|
19718
|
+
},
|
|
19719
|
+
peek: () => eng.peek(name)
|
|
19720
|
+
};
|
|
19721
|
+
}
|
|
19722
|
+
if (!this.sequenceStore) {
|
|
19723
|
+
this.sequenceStore = new SequenceStore({
|
|
19724
|
+
adapter: this.adapter,
|
|
19725
|
+
vault: this.name,
|
|
19726
|
+
encrypted: this.encrypted,
|
|
19727
|
+
getDEK: this.getDEK,
|
|
19728
|
+
actor: this.keyring.userId
|
|
19729
|
+
});
|
|
19730
|
+
}
|
|
19731
|
+
return this.sequenceStore.handle(name);
|
|
19732
|
+
}
|
|
19733
|
+
/** @internal — lazily build the deferred-numbering engine with a cache-coherent stamp. */
|
|
19734
|
+
deferred() {
|
|
19735
|
+
if (!this.deferredNumbering) {
|
|
19736
|
+
this.deferredNumbering = new DeferredNumberingStore({
|
|
19737
|
+
adapter: this.adapter,
|
|
19738
|
+
vault: this.name,
|
|
19739
|
+
encrypted: this.encrypted,
|
|
19740
|
+
getDEK: this.getDEK,
|
|
19741
|
+
actor: this.keyring.userId,
|
|
19742
|
+
configs: this.numberingConfigs,
|
|
19743
|
+
// Stamp THROUGH the Collection layer so cache/indexes/MVs stay coherent —
|
|
19744
|
+
// `this.collection(name)` returns the shared cached instance, so a
|
|
19745
|
+
// subsequent user `collection.get(id)` sees the assigned serial.
|
|
19746
|
+
stamp: async (collection, recordId3, field, serial) => {
|
|
19747
|
+
const coll = this.collection(collection);
|
|
19748
|
+
const rec = await coll.get(recordId3);
|
|
19749
|
+
if (!rec) return false;
|
|
19750
|
+
await coll.put(recordId3, { ...rec, [field]: serial });
|
|
19751
|
+
return true;
|
|
19752
|
+
}
|
|
19753
|
+
});
|
|
19754
|
+
}
|
|
19755
|
+
return this.deferredNumbering;
|
|
19756
|
+
}
|
|
19757
|
+
/**
|
|
19758
|
+
* Run a deferred-numbering pass for `series`: assign gap-free serials to all
|
|
19759
|
+
* records whose store-commit-time interval has settled, in store-time order.
|
|
19760
|
+
* Returns the assignments made. See {@link sequence} / `withDeferredNumbering`.
|
|
19761
|
+
*/
|
|
19762
|
+
async runNumberingPass(series) {
|
|
19763
|
+
return this.deferred().runPass(series);
|
|
19764
|
+
}
|
|
17843
19765
|
async compact(options = {}) {
|
|
17844
19766
|
return runCompaction({
|
|
17845
19767
|
adapter: this.adapter,
|
|
@@ -17864,6 +19786,48 @@ var Vault = class {
|
|
|
17864
19786
|
}
|
|
17865
19787
|
}, options);
|
|
17866
19788
|
}
|
|
19789
|
+
/**
|
|
19790
|
+
* Sweep records eligible by their collection's `archive` policy into the
|
|
19791
|
+
* cold archive store. Relocation is envelope-level (no re-encryption) and
|
|
19792
|
+
* bypasses guards + materialized-view dispatch, so issued/immutable
|
|
19793
|
+
* records over a sealed period can be archived without recomputing
|
|
19794
|
+
* finalized aggregates. A `legalHold` predicate blocks archival.
|
|
19795
|
+
* Requires `archiveStrategy: withArchive({ store })` in `createNoydb`.
|
|
19796
|
+
*/
|
|
19797
|
+
async archive(options = {}) {
|
|
19798
|
+
return runArchive(this._archiveContext(), options);
|
|
19799
|
+
}
|
|
19800
|
+
/** Relocate one archived record back to the primary store. Returns false if it was not archived. */
|
|
19801
|
+
async restore(collection, id) {
|
|
19802
|
+
return runRestore(this._archiveContext(), collection, id);
|
|
19803
|
+
}
|
|
19804
|
+
/** List archived record ids for a collection (or all collections with an archive policy). */
|
|
19805
|
+
async listArchived(collection) {
|
|
19806
|
+
return runListArchived(this._archiveContext(), collection);
|
|
19807
|
+
}
|
|
19808
|
+
_archiveContext() {
|
|
19809
|
+
const strategy = this.archiveStrategy;
|
|
19810
|
+
if (!strategy) {
|
|
19811
|
+
throw new Error(
|
|
19812
|
+
"vault.archive/restore/listArchived require `archiveStrategy: withArchive({ store })` in createNoydb"
|
|
19813
|
+
);
|
|
19814
|
+
}
|
|
19815
|
+
const archiveStore = strategy.store;
|
|
19816
|
+
return {
|
|
19817
|
+
vaultId: this.name,
|
|
19818
|
+
archiveStore,
|
|
19819
|
+
collectionsWithPolicy: () => [...this.archiveRegistry.keys()],
|
|
19820
|
+
getPolicy: (c) => this.archiveRegistry.get(c) ?? null,
|
|
19821
|
+
listRecordIds: (c) => this.adapter.list(this.name, c),
|
|
19822
|
+
getRecord: async (c, id) => await this.collection(c).get(id, { locale: "raw" }),
|
|
19823
|
+
getEnvelope: (c, id) => this.adapter.get(this.name, c, id),
|
|
19824
|
+
removeFromPrimary: (c, id) => this.collection(c)._internalDelete(id),
|
|
19825
|
+
restoreToPrimary: async (c, id, env) => {
|
|
19826
|
+
await this.adapter.put(this.name, c, id, env);
|
|
19827
|
+
await this.collection(c)._invalidateCacheEntry(id);
|
|
19828
|
+
}
|
|
19829
|
+
};
|
|
19830
|
+
}
|
|
17867
19831
|
exportBlobs(options = {}) {
|
|
17868
19832
|
this.assertCanExport("plaintext", "blob");
|
|
17869
19833
|
return createExportBlobsHandle(
|
|
@@ -18562,7 +20526,7 @@ var Vault = class {
|
|
|
18562
20526
|
*
|
|
18563
20527
|
* @internal
|
|
18564
20528
|
*/
|
|
18565
|
-
async _logConsent(op, collection,
|
|
20529
|
+
async _logConsent(op, collection, recordId3) {
|
|
18566
20530
|
const ctx = this.consentContext;
|
|
18567
20531
|
if (!ctx) return;
|
|
18568
20532
|
await this.consentStrategy.write(
|
|
@@ -18575,7 +20539,7 @@ var Vault = class {
|
|
|
18575
20539
|
consentHash: ctx.consentHash,
|
|
18576
20540
|
op,
|
|
18577
20541
|
collection,
|
|
18578
|
-
recordId:
|
|
20542
|
+
recordId: recordId3
|
|
18579
20543
|
},
|
|
18580
20544
|
this.getDEK
|
|
18581
20545
|
);
|
|
@@ -18758,14 +20722,14 @@ var Vault = class {
|
|
|
18758
20722
|
* the HKDF derivation, record-id composition, and batch logic so the
|
|
18759
20723
|
* grantor doesn't touch this method directly.
|
|
18760
20724
|
*/
|
|
18761
|
-
async writeMagicLinkGrant(contentKey, grantKek,
|
|
20725
|
+
async writeMagicLinkGrant(contentKey, grantKek, recordId3, opts) {
|
|
18762
20726
|
return writeMagicLinkGrant(
|
|
18763
20727
|
this.adapter,
|
|
18764
20728
|
this.name,
|
|
18765
20729
|
this.keyring,
|
|
18766
20730
|
contentKey,
|
|
18767
20731
|
grantKek,
|
|
18768
|
-
|
|
20732
|
+
recordId3,
|
|
18769
20733
|
opts
|
|
18770
20734
|
);
|
|
18771
20735
|
}
|
|
@@ -19125,7 +21089,7 @@ var Vault = class {
|
|
|
19125
21089
|
}
|
|
19126
21090
|
}
|
|
19127
21091
|
const internalSnapshot = {};
|
|
19128
|
-
for (const internalName of [LEDGER_COLLECTION, LEDGER_DELTAS_COLLECTION, SCHEMAS_COLLECTION]) {
|
|
21092
|
+
for (const internalName of [LEDGER_COLLECTION, LEDGER_DELTAS_COLLECTION, SCHEMAS_COLLECTION, SEQUENCE_COLLECTION]) {
|
|
19129
21093
|
const ids = await this.adapter.list(this.name, internalName);
|
|
19130
21094
|
if (ids.length === 0) continue;
|
|
19131
21095
|
const records = {};
|
|
@@ -20601,6 +22565,7 @@ var Noydb = class {
|
|
|
20601
22565
|
writeRelay;
|
|
20602
22566
|
/** Per-vault policy enforcers. */
|
|
20603
22567
|
policyEnforcers = /* @__PURE__ */ new Map();
|
|
22568
|
+
vaultTemplates = /* @__PURE__ */ new Map();
|
|
20604
22569
|
txStrategy;
|
|
20605
22570
|
sessionStrategy;
|
|
20606
22571
|
syncStrategy;
|
|
@@ -20670,7 +22635,7 @@ var Noydb = class {
|
|
|
20670
22635
|
await registry.runChecks(e.collection, incoming, ctx);
|
|
20671
22636
|
const { GuardExecutor: GuardExecutor2 } = await Promise.resolve().then(() => (init_executor(), executor_exports));
|
|
20672
22637
|
for (const g of guards) {
|
|
20673
|
-
await GuardExecutor2.checkFrozenFields(g, e.docId, existing, incoming);
|
|
22638
|
+
await GuardExecutor2.checkFrozenFields(g, e.docId, existing, incoming, e.computedFieldNames);
|
|
20674
22639
|
}
|
|
20675
22640
|
});
|
|
20676
22641
|
this.subsystemBus.registerGate("beforeDelete", async (e) => {
|
|
@@ -20849,6 +22814,7 @@ var Noydb = class {
|
|
|
20849
22814
|
syncAdapter: targets.length > 0 ? targets[0].store : void 0,
|
|
20850
22815
|
historyConfig: this.options.history,
|
|
20851
22816
|
...this.options.blobStrategy !== void 0 ? { blobStrategy: this.options.blobStrategy } : {},
|
|
22817
|
+
...this.options.archiveStrategy !== void 0 ? { archiveStrategy: this.options.archiveStrategy } : {},
|
|
20852
22818
|
...this.options.indexStrategy !== void 0 ? { indexStrategy: this.options.indexStrategy } : {},
|
|
20853
22819
|
...this.options.aggregateStrategy !== void 0 ? { aggregateStrategy: this.options.aggregateStrategy } : {},
|
|
20854
22820
|
...this.options.crdtStrategy !== void 0 ? { crdtStrategy: this.options.crdtStrategy } : {},
|
|
@@ -20859,6 +22825,7 @@ var Noydb = class {
|
|
|
20859
22825
|
...this.options.i18nStrategy !== void 0 ? { i18nStrategy: this.options.i18nStrategy } : {},
|
|
20860
22826
|
...this.options.syncStrategy !== void 0 ? { syncStrategy: this.options.syncStrategy } : {},
|
|
20861
22827
|
...this.options.guardStrategies !== void 0 ? { guardStrategies: this.options.guardStrategies } : {},
|
|
22828
|
+
...this.options.numbering !== void 0 ? { numberingConfigs: this.options.numbering } : {},
|
|
20862
22829
|
locale: opts?.locale,
|
|
20863
22830
|
// Thread the translator hook so Collection.put() can invoke it
|
|
20864
22831
|
plaintextTranslator: this.options.plaintextTranslator ? (text, from, to, field, collection) => this.invokeTranslator(text, from, to, field, collection) : void 0,
|
|
@@ -20902,6 +22869,7 @@ var Noydb = class {
|
|
|
20902
22869
|
emitter: this.emitter,
|
|
20903
22870
|
historyConfig: this.options.history,
|
|
20904
22871
|
...this.options.blobStrategy !== void 0 ? { blobStrategy: this.options.blobStrategy } : {},
|
|
22872
|
+
...this.options.archiveStrategy !== void 0 ? { archiveStrategy: this.options.archiveStrategy } : {},
|
|
20905
22873
|
...this.options.indexStrategy !== void 0 ? { indexStrategy: this.options.indexStrategy } : {},
|
|
20906
22874
|
...this.options.aggregateStrategy !== void 0 ? { aggregateStrategy: this.options.aggregateStrategy } : {},
|
|
20907
22875
|
...this.options.crdtStrategy !== void 0 ? { crdtStrategy: this.options.crdtStrategy } : {},
|
|
@@ -20911,7 +22879,8 @@ var Noydb = class {
|
|
|
20911
22879
|
...this.options.historyStrategy !== void 0 ? { historyStrategy: this.options.historyStrategy } : {},
|
|
20912
22880
|
...this.options.i18nStrategy !== void 0 ? { i18nStrategy: this.options.i18nStrategy } : {},
|
|
20913
22881
|
...this.options.syncStrategy !== void 0 ? { syncStrategy: this.options.syncStrategy } : {},
|
|
20914
|
-
...this.options.guardStrategies !== void 0 ? { guardStrategies: this.options.guardStrategies } : {}
|
|
22882
|
+
...this.options.guardStrategies !== void 0 ? { guardStrategies: this.options.guardStrategies } : {},
|
|
22883
|
+
...this.options.numbering !== void 0 ? { numberingConfigs: this.options.numbering } : {}
|
|
20915
22884
|
});
|
|
20916
22885
|
this.vaultCache.set(name, comp2);
|
|
20917
22886
|
return comp2;
|
|
@@ -20930,6 +22899,7 @@ var Noydb = class {
|
|
|
20930
22899
|
encrypted: true,
|
|
20931
22900
|
historyConfig: this.options.history,
|
|
20932
22901
|
...this.options.blobStrategy !== void 0 ? { blobStrategy: this.options.blobStrategy } : {},
|
|
22902
|
+
...this.options.archiveStrategy !== void 0 ? { archiveStrategy: this.options.archiveStrategy } : {},
|
|
20933
22903
|
...this.options.indexStrategy !== void 0 ? { indexStrategy: this.options.indexStrategy } : {},
|
|
20934
22904
|
...this.options.aggregateStrategy !== void 0 ? { aggregateStrategy: this.options.aggregateStrategy } : {},
|
|
20935
22905
|
...this.options.crdtStrategy !== void 0 ? { crdtStrategy: this.options.crdtStrategy } : {},
|
|
@@ -20940,6 +22910,7 @@ var Noydb = class {
|
|
|
20940
22910
|
...this.options.i18nStrategy !== void 0 ? { i18nStrategy: this.options.i18nStrategy } : {},
|
|
20941
22911
|
...this.options.syncStrategy !== void 0 ? { syncStrategy: this.options.syncStrategy } : {},
|
|
20942
22912
|
...this.options.guardStrategies !== void 0 ? { guardStrategies: this.options.guardStrategies } : {},
|
|
22913
|
+
...this.options.numbering !== void 0 ? { numberingConfigs: this.options.numbering } : {},
|
|
20943
22914
|
emitter: this.emitter
|
|
20944
22915
|
});
|
|
20945
22916
|
this.vaultCache.set(name, comp);
|
|
@@ -21244,6 +23215,61 @@ var Noydb = class {
|
|
|
21244
23215
|
}
|
|
21245
23216
|
return results;
|
|
21246
23217
|
}
|
|
23218
|
+
/**
|
|
23219
|
+
* Register a shard schema blueprint. `createShard` / `openVaultGroup`
|
|
23220
|
+
* stamp shards from the named template. See the MVF design spec.
|
|
23221
|
+
*/
|
|
23222
|
+
withVaultTemplate(name, template) {
|
|
23223
|
+
this.vaultTemplates.set(name, template);
|
|
23224
|
+
}
|
|
23225
|
+
/**
|
|
23226
|
+
* Open a VaultGroup — transparent routing over per-partition shard
|
|
23227
|
+
* vaults, with shard discovery backed by the supplied `vault-registry`
|
|
23228
|
+
* collection.
|
|
23229
|
+
*/
|
|
23230
|
+
async openVaultGroup(name, opts) {
|
|
23231
|
+
if (this.closed) throw new ValidationError("Instance is closed");
|
|
23232
|
+
if (name === STATE_VAULT_NAME) throw new ReservedVaultNameError(name);
|
|
23233
|
+
const template = this.vaultTemplates.get(opts.sharding.vaultTemplate);
|
|
23234
|
+
if (!template) throw new VaultTemplateNotFoundError(opts.sharding.vaultTemplate);
|
|
23235
|
+
const { VaultGroup: VaultGroup2 } = await Promise.resolve().then(() => (init_vault_group(), vault_group_exports));
|
|
23236
|
+
const { StateManagementVault: StateManagementVault2 } = await Promise.resolve().then(() => (init_state_vault(), state_vault_exports));
|
|
23237
|
+
const stateVault = opts.registry ? void 0 : await StateManagementVault2.open(this);
|
|
23238
|
+
const registry = opts.registry ?? stateVault.registry;
|
|
23239
|
+
const group = new VaultGroup2(this, name, registry, opts.sharding, template);
|
|
23240
|
+
if (stateVault) {
|
|
23241
|
+
group._attachStateVault(stateVault);
|
|
23242
|
+
await stateVault.recordManifest(opts.sharding.vaultTemplate, template);
|
|
23243
|
+
try {
|
|
23244
|
+
await stateVault.appendEvent({
|
|
23245
|
+
type: "manifest-recorded",
|
|
23246
|
+
group: name,
|
|
23247
|
+
templateName: opts.sharding.vaultTemplate,
|
|
23248
|
+
version: template.version
|
|
23249
|
+
});
|
|
23250
|
+
await stateVault.appendEvent({ type: "group-opened", group: name });
|
|
23251
|
+
} catch {
|
|
23252
|
+
}
|
|
23253
|
+
}
|
|
23254
|
+
return group;
|
|
23255
|
+
}
|
|
23256
|
+
/**
|
|
23257
|
+
* Open the reserved StateManagement control-plane vault (registry +
|
|
23258
|
+
* schema-manifest + deployment-events). Lazy-loaded so the federation
|
|
23259
|
+
* chunk stays out of the core graph until used.
|
|
23260
|
+
*/
|
|
23261
|
+
async openStateManagementVault() {
|
|
23262
|
+
if (this.closed) throw new ValidationError("Instance is closed");
|
|
23263
|
+
const { StateManagementVault: StateManagementVault2 } = await Promise.resolve().then(() => (init_state_vault(), state_vault_exports));
|
|
23264
|
+
return StateManagementVault2.open(this);
|
|
23265
|
+
}
|
|
23266
|
+
/**
|
|
23267
|
+
* @internal — true when an encrypted shard vault is provisioned
|
|
23268
|
+
* (its keyring exists in the store).
|
|
23269
|
+
*/
|
|
23270
|
+
async _shardVaultProvisioned(vaultId) {
|
|
23271
|
+
return (await this.options.store.list(vaultId, "_keyring")).length > 0;
|
|
23272
|
+
}
|
|
21247
23273
|
/**
|
|
21248
23274
|
* Change the current user's passphrase for a vault.
|
|
21249
23275
|
*
|
|
@@ -23506,6 +25532,50 @@ function withGuard(strategy) {
|
|
|
23506
25532
|
};
|
|
23507
25533
|
}
|
|
23508
25534
|
|
|
25535
|
+
// src/guards/immutable-guard.ts
|
|
25536
|
+
init_errors();
|
|
25537
|
+
function recordId2(record) {
|
|
25538
|
+
const id = record?.id;
|
|
25539
|
+
return typeof id === "string" ? id : "";
|
|
25540
|
+
}
|
|
25541
|
+
function immutableGuard(config) {
|
|
25542
|
+
const { collection, after, appendOnly, amendmentRoles } = config;
|
|
25543
|
+
if (appendOnly && after !== void 0) {
|
|
25544
|
+
throw new ValidationError("immutableGuard: `after` and `appendOnly` are mutually exclusive");
|
|
25545
|
+
}
|
|
25546
|
+
if (!appendOnly && after === void 0) {
|
|
25547
|
+
throw new ValidationError("immutableGuard: provide `after` or `appendOnly: true`");
|
|
25548
|
+
}
|
|
25549
|
+
const isImmutable = appendOnly ? () => true : after;
|
|
25550
|
+
const reason = appendOnly ? "append-only collection" : "record is immutable after issue";
|
|
25551
|
+
const spec = {
|
|
25552
|
+
collection,
|
|
25553
|
+
// Block updates to an already-immutable record. Inserts (existing
|
|
25554
|
+
// null) and the transition write that first makes the record
|
|
25555
|
+
// immutable are allowed — `after` reads the prior state.
|
|
25556
|
+
check: (incoming, ctx) => {
|
|
25557
|
+
if (ctx.existing !== null && isImmutable(ctx.existing)) {
|
|
25558
|
+
throw new RecordLockedError(collection, recordId2(incoming), reason);
|
|
25559
|
+
}
|
|
25560
|
+
},
|
|
25561
|
+
// Block deletes of an immutable record.
|
|
25562
|
+
onDelete: (existing) => {
|
|
25563
|
+
if (isImmutable(existing)) {
|
|
25564
|
+
throw new RecordLockedError(collection, recordId2(existing), reason);
|
|
25565
|
+
}
|
|
25566
|
+
},
|
|
25567
|
+
// The authorized override: inside an amendment transaction the
|
|
25568
|
+
// check/onDelete are skipped and the change is ledgered. No extra
|
|
25569
|
+
// invariant — the amendment itself is the sanctioned exception.
|
|
25570
|
+
amendment: {
|
|
25571
|
+
roles: amendmentRoles ?? ["admin", "owner"],
|
|
25572
|
+
invariant: () => {
|
|
25573
|
+
}
|
|
25574
|
+
}
|
|
25575
|
+
};
|
|
25576
|
+
return withGuard(spec);
|
|
25577
|
+
}
|
|
25578
|
+
|
|
23509
25579
|
// src/derivations/with-derivation.ts
|
|
23510
25580
|
init_errors();
|
|
23511
25581
|
function withDerivation(spec) {
|
|
@@ -23663,6 +25733,10 @@ function withOverlayedView(spec) {
|
|
|
23663
25733
|
init_errors();
|
|
23664
25734
|
init_errors();
|
|
23665
25735
|
|
|
25736
|
+
// src/money/index.ts
|
|
25737
|
+
init_descriptor();
|
|
25738
|
+
init_iso4217();
|
|
25739
|
+
|
|
23666
25740
|
// src/i18n/script.ts
|
|
23667
25741
|
init_errors();
|
|
23668
25742
|
var LATIN_BASE = /* @__PURE__ */ new Set([
|
|
@@ -24448,6 +26522,7 @@ function shortJSON(value) {
|
|
|
24448
26522
|
CollectionFrame,
|
|
24449
26523
|
CollectionIndexes,
|
|
24450
26524
|
CollectionInstant,
|
|
26525
|
+
ComputedFieldError,
|
|
24451
26526
|
ConflictError,
|
|
24452
26527
|
CrossJoinSourceUnknownError,
|
|
24453
26528
|
CrossJoinTooLargeError,
|
|
@@ -24511,6 +26586,9 @@ function shortJSON(value) {
|
|
|
24511
26586
|
MemorySealingKeyProvider,
|
|
24512
26587
|
MigrationRequiredError,
|
|
24513
26588
|
MissingTranslationError,
|
|
26589
|
+
MoneyCurrencyError,
|
|
26590
|
+
MoneyPrecisionError,
|
|
26591
|
+
MoneyUnsupportedError,
|
|
24514
26592
|
NOYDB_BACKUP_VERSION,
|
|
24515
26593
|
NOYDB_BUNDLE_FORMAT_VERSION,
|
|
24516
26594
|
NOYDB_BUNDLE_MAGIC,
|
|
@@ -24524,6 +26602,7 @@ function shortJSON(value) {
|
|
|
24524
26602
|
NotFoundError,
|
|
24525
26603
|
Noydb,
|
|
24526
26604
|
NoydbError,
|
|
26605
|
+
NumberingUncertaintyError,
|
|
24527
26606
|
OverlayBaseIsVirtualError,
|
|
24528
26607
|
OverlayCollectionUnavailableError,
|
|
24529
26608
|
OverlayIdMismatchError,
|
|
@@ -24553,8 +26632,10 @@ function shortJSON(value) {
|
|
|
24553
26632
|
RefRegistry,
|
|
24554
26633
|
RefScopeError,
|
|
24555
26634
|
ReservedCollectionNameError,
|
|
26635
|
+
ReservedVaultNameError,
|
|
24556
26636
|
SCHEMAS_COLLECTION,
|
|
24557
26637
|
SEALED_PASSPHRASE_RECORD_ID,
|
|
26638
|
+
STATE_VAULT_NAME,
|
|
24558
26639
|
STRICT_POLICY,
|
|
24559
26640
|
SYNC_CREDENTIALS_COLLECTION,
|
|
24560
26641
|
ScanBuilder,
|
|
@@ -24563,9 +26644,13 @@ function shortJSON(value) {
|
|
|
24563
26644
|
SchemaUpdateError,
|
|
24564
26645
|
SchemaValidationError,
|
|
24565
26646
|
ScriptViolationError,
|
|
26647
|
+
SequenceContentionError,
|
|
26648
|
+
SequenceOfflineError,
|
|
26649
|
+
SequenceStore,
|
|
24566
26650
|
SessionExpiredError,
|
|
24567
26651
|
SessionNotFoundError,
|
|
24568
26652
|
SessionPolicyError,
|
|
26653
|
+
ShardProvisioningError,
|
|
24569
26654
|
SnapshotNotFoundError,
|
|
24570
26655
|
StoreCapabilityError,
|
|
24571
26656
|
SyncEngine,
|
|
@@ -24581,6 +26666,7 @@ function shortJSON(value) {
|
|
|
24581
26666
|
USER_ENVELOPE_COLLECTION,
|
|
24582
26667
|
USER_ENVELOPE_MAX_BYTES,
|
|
24583
26668
|
UniqueConstraintError,
|
|
26669
|
+
UnknownShardError,
|
|
24584
26670
|
UnsupportedIndexOptionError,
|
|
24585
26671
|
UserApi,
|
|
24586
26672
|
UserEnvelopeOversizedError,
|
|
@@ -24589,6 +26675,7 @@ function shortJSON(value) {
|
|
|
24589
26675
|
Vault,
|
|
24590
26676
|
VaultFrame,
|
|
24591
26677
|
VaultInstant,
|
|
26678
|
+
VaultTemplateNotFoundError,
|
|
24592
26679
|
WeakPassphraseError,
|
|
24593
26680
|
activeSessionCount,
|
|
24594
26681
|
additiveOnly,
|
|
@@ -24645,6 +26732,7 @@ function shortJSON(value) {
|
|
|
24645
26732
|
envelopePayloadHash,
|
|
24646
26733
|
estimateEntropy,
|
|
24647
26734
|
estimateRecordBytes,
|
|
26735
|
+
evalComputedFields,
|
|
24648
26736
|
evaluateClause,
|
|
24649
26737
|
evaluateExportCapability,
|
|
24650
26738
|
evaluateFieldClause,
|
|
@@ -24661,6 +26749,7 @@ function shortJSON(value) {
|
|
|
24661
26749
|
hasRecoveryEnrolled,
|
|
24662
26750
|
hashEntry,
|
|
24663
26751
|
i18nText,
|
|
26752
|
+
immutableGuard,
|
|
24664
26753
|
inferScripts,
|
|
24665
26754
|
isDevUnlockActive,
|
|
24666
26755
|
isDictCollectionName,
|
|
@@ -24668,6 +26757,7 @@ function shortJSON(value) {
|
|
|
24668
26757
|
isDiscriminant,
|
|
24669
26758
|
isI18nTextDescriptor,
|
|
24670
26759
|
isMagicLinkGrantExpired,
|
|
26760
|
+
isMoneyDescriptor,
|
|
24671
26761
|
isPreCompressed,
|
|
24672
26762
|
isPublicEnvelope,
|
|
24673
26763
|
isSessionAlive,
|
|
@@ -24699,6 +26789,7 @@ function shortJSON(value) {
|
|
|
24699
26789
|
mintPaperRecoveryEntry,
|
|
24700
26790
|
mintShamirRecoveryEntry,
|
|
24701
26791
|
mintWrappedDeksBlob,
|
|
26792
|
+
money,
|
|
24702
26793
|
paddedIndex,
|
|
24703
26794
|
parseBytes,
|
|
24704
26795
|
parseIndex,
|
|
@@ -24739,6 +26830,7 @@ function shortJSON(value) {
|
|
|
24739
26830
|
saveShamirRecoveryEntries,
|
|
24740
26831
|
saveUserEnvelope,
|
|
24741
26832
|
saveVaultPolicy,
|
|
26833
|
+
scaleForCurrency,
|
|
24742
26834
|
sha256Hex,
|
|
24743
26835
|
sum,
|
|
24744
26836
|
unwrapDeksFromBlob,
|
|
@@ -24752,8 +26844,10 @@ function shortJSON(value) {
|
|
|
24752
26844
|
validateSchemaOutput,
|
|
24753
26845
|
validateSessionPolicy,
|
|
24754
26846
|
visibilityRecordId,
|
|
26847
|
+
withArchive,
|
|
24755
26848
|
withCache,
|
|
24756
26849
|
withCircuitBreaker,
|
|
26850
|
+
withDeferredNumbering,
|
|
24757
26851
|
withDerivation,
|
|
24758
26852
|
withGuard,
|
|
24759
26853
|
withHealthCheck,
|