@noy-db/hub 0.2.0-pre.2 → 0.2.0-pre.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aggregate/index.cjs.map +1 -1
- package/dist/aggregate/index.js +2 -2
- package/dist/attestation/index.cjs.map +1 -1
- package/dist/attestation/index.d.cts +2 -2
- package/dist/attestation/index.d.ts +2 -2
- package/dist/attestation/index.js +6 -6
- package/dist/blobs/index.cjs.map +1 -1
- package/dist/blobs/index.d.cts +3 -3
- package/dist/blobs/index.d.ts +3 -3
- package/dist/blobs/index.js +5 -5
- package/dist/bundle/index.cjs +1245 -6
- package/dist/bundle/index.cjs.map +1 -1
- package/dist/bundle/index.d.cts +4 -4
- package/dist/bundle/index.d.ts +4 -4
- package/dist/bundle/index.js +10 -10
- package/dist/{chunk-EUYOGYGV.js → chunk-2EYC3WDT.js} +6 -6
- package/dist/{chunk-MUWOSVEP.js → chunk-2XLVPKXG.js} +2 -2
- package/dist/{chunk-NWZ3I6R6.js → chunk-4OQWR46B.js} +5 -5
- package/dist/{chunk-J4KLMEUL.js → chunk-4UBOTYP5.js} +2 -2
- package/dist/{chunk-UND4XIB6.js → chunk-4X2S7PBF.js} +3 -3
- package/dist/{chunk-VE6YVP32.js → chunk-5YHWBPOT.js} +2 -2
- package/dist/{chunk-7BUTTVMR.js → chunk-6S3LLAQ5.js} +2 -2
- package/dist/{chunk-7Z23ZFLV.js → chunk-74JEQFMT.js} +5 -5
- package/dist/{chunk-AHPFONIL.js → chunk-75QDHSE4.js} +5 -5
- package/dist/{chunk-3XHOCQK4.js → chunk-A6SWRXUQ.js} +2 -2
- package/dist/{chunk-PLI5TV7N.js → chunk-BFI3RS42.js} +2 -2
- package/dist/{chunk-CXSCDO5T.js → chunk-EMEX37ZN.js} +2 -2
- package/dist/{chunk-PEULZC6M.js → chunk-EPK6A3WJ.js} +8 -1
- package/dist/chunk-EPK6A3WJ.js.map +1 -0
- package/dist/{chunk-JYQTXEIO.js → chunk-FBMXWVGP.js} +5 -5
- package/dist/{chunk-QXQRKXCU.js → chunk-FCDO7UAO.js} +2 -2
- package/dist/{chunk-243PNUA6.js → chunk-FS7A4XNF.js} +3 -3
- package/dist/{chunk-YS3POABP.js → chunk-FXQYZNOW.js} +1 -1
- package/dist/chunk-FXQYZNOW.js.map +1 -0
- package/dist/{chunk-Y2RKOPNC.js → chunk-G7PAZ3TD.js} +4 -4
- package/dist/{chunk-QPEXPHJR.js → chunk-GAUBWHAF.js} +4 -4
- package/dist/{chunk-GIV6DWBG.js → chunk-GD3BGKAR.js} +2 -2
- package/dist/{chunk-TBKOGSYR.js → chunk-GDTCGIPX.js} +2 -2
- package/dist/{chunk-3Z2TPHC4.js → chunk-GVXBHCZ2.js} +8 -3
- package/dist/chunk-GVXBHCZ2.js.map +1 -0
- package/dist/{chunk-OVZDFEOR.js → chunk-HGZ7DC5H.js} +2 -2
- package/dist/{chunk-VPSUZLOJ.js → chunk-IS5HWQO7.js} +4 -4
- package/dist/{chunk-VPSUZLOJ.js.map → chunk-IS5HWQO7.js.map} +1 -1
- package/dist/{chunk-VK5EER6C.js → chunk-K5PVGKE4.js} +2 -2
- package/dist/{chunk-LRAZDV5X.js → chunk-KMI2NBBF.js} +6 -6
- package/dist/{chunk-VRBCTEKQ.js → chunk-KYKMKLJ6.js} +2 -2
- package/dist/{chunk-PFSNOPBQ.js → chunk-LOL725S4.js} +3 -3
- package/dist/{chunk-3Y53S2SA.js → chunk-LS3JLEIB.js} +4 -4
- package/dist/{chunk-XG3PTSCD.js → chunk-NCO2JGKK.js} +1 -1
- package/dist/chunk-NCO2JGKK.js.map +1 -0
- package/dist/{chunk-YTXSFG3C.js → chunk-NGSPBLLE.js} +2 -2
- package/dist/{chunk-FAQVNJD4.js → chunk-NSLTPGEN.js} +2 -2
- package/dist/{chunk-VCGTOS2A.js → chunk-P6256WTJ.js} +3 -3
- package/dist/{chunk-7Q5PLD5C.js → chunk-QAU5HM6Q.js} +3 -3
- package/dist/{chunk-HXJXPZRE.js → chunk-SAVQ6E2O.js} +2 -2
- package/dist/{chunk-E535SAN4.js → chunk-T6HQMVML.js} +1177 -51
- package/dist/chunk-T6HQMVML.js.map +1 -0
- package/dist/{chunk-Q6W2CMEJ.js → chunk-TLFUDXVV.js} +4 -4
- package/dist/{chunk-2PAQNPE3.js → chunk-UOF74WQY.js} +2 -2
- package/dist/{chunk-3QAKZ37R.js → chunk-UVPGJXVO.js} +5 -5
- package/dist/{chunk-7BRE6EUA.js → chunk-WRLHNG6H.js} +2 -2
- package/dist/{chunk-W3XXT26A.js → chunk-YDLAFP36.js} +43 -1
- package/dist/chunk-YDLAFP36.js.map +1 -0
- package/dist/{chunk-G6FRSBKK.js → chunk-YK72A4IT.js} +4 -4
- package/dist/{chunk-3S4BJX25.js → chunk-YL2DR3HY.js} +2 -2
- package/dist/{chunk-RTZVQAJ7.js → chunk-ZC2AAE6J.js} +2 -2
- package/dist/{chunk-4HIL6AHQ.js → chunk-ZUMGGHRB.js} +4 -4
- package/dist/consent/index.cjs.map +1 -1
- package/dist/consent/index.d.cts +3 -3
- package/dist/consent/index.d.ts +3 -3
- package/dist/consent/index.js +3 -3
- package/dist/{crypto-5ZDIY3NG.js → crypto-H2Y3DDFW.js} +3 -3
- package/dist/{delegation-QYXZW25W.js → delegation-QSC7G5QC.js} +5 -5
- package/dist/derivations/index.cjs.map +1 -1
- package/dist/derivations/index.d.cts +4 -4
- package/dist/derivations/index.d.ts +4 -4
- package/dist/derivations/index.js +4 -4
- package/dist/{dev-unlock-utkybTKb.d.ts → dev-unlock-Cf2B7Kih.d.ts} +1 -1
- package/dist/{dev-unlock-DQCNDfFp.d.cts → dev-unlock-De3mjQWv.d.cts} +1 -1
- package/dist/executor-BZKFZVRC.js +8 -0
- package/dist/executor-GFZFDQXV.js +8 -0
- package/dist/executor-KT2IOZVP.js +11 -0
- package/dist/{fanout-sidecar-VJ52RIEY.js → fanout-sidecar-NRBWSLRK.js} +2 -2
- package/dist/guards/index.cjs +7 -0
- package/dist/guards/index.cjs.map +1 -1
- package/dist/guards/index.d.cts +4 -4
- package/dist/guards/index.d.ts +4 -4
- package/dist/guards/index.js +4 -4
- package/dist/{hash-DcoYWfJ_.d.ts → hash-gVn_uKhp.d.ts} +1 -1
- package/dist/{hash-jDowCrK2.d.cts → hash-vBCB0-Ps.d.cts} +1 -1
- package/dist/history/index.cjs +1 -1
- package/dist/history/index.cjs.map +1 -1
- package/dist/history/index.d.cts +4 -4
- package/dist/history/index.d.ts +4 -4
- package/dist/history/index.js +6 -6
- package/dist/i18n/index.cjs.map +1 -1
- package/dist/i18n/index.d.cts +3 -3
- package/dist/i18n/index.d.ts +3 -3
- package/dist/i18n/index.js +7 -7
- package/dist/{index-BCKdioeh.d.ts → index-BF1B2HB9.d.ts} +25 -1
- package/dist/{index-BMjrzNZr.d.cts → index-DVkvrgpm.d.cts} +25 -1
- package/dist/index.cjs +1273 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +33 -12
- package/dist/index.d.ts +33 -12
- package/dist/index.js +109 -42
- 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-BAJ7ZB4S.js +12 -0
- package/dist/{ledger-3IU5GMXA.js → ledger-WOEJUYTP.js} +6 -6
- package/dist/materialized-views/index.cjs.map +1 -1
- package/dist/materialized-views/index.d.cts +5 -5
- package/dist/materialized-views/index.d.ts +5 -5
- package/dist/materialized-views/index.js +6 -6
- package/dist/noydb-XNQSKXGO.js +34 -0
- package/dist/overlay-views/index.cjs.map +1 -1
- package/dist/overlay-views/index.d.cts +4 -4
- package/dist/overlay-views/index.d.ts +4 -4
- package/dist/overlay-views/index.js +4 -4
- package/dist/periods/index.cjs.map +1 -1
- package/dist/periods/index.d.cts +3 -3
- package/dist/periods/index.d.ts +3 -3
- package/dist/periods/index.js +6 -6
- package/dist/{public-envelope-U3CMEOMV.js → public-envelope-OHQ5UZFM.js} +4 -4
- package/dist/query/index.cjs.map +1 -1
- package/dist/query/index.d.cts +1 -1
- package/dist/query/index.d.ts +1 -1
- package/dist/query/index.js +3 -3
- package/dist/registry-2IEARCGT.js +7 -0
- package/dist/{registry-3ALP62P6.js → registry-CDHASH73.js} +3 -3
- package/dist/registry-EMGLZGR6.js +8 -0
- package/dist/registry-NQALYR77.js +8 -0
- package/dist/{revoke-KY2GB4KP.js → revoke-7JOVLZFD.js} +6 -6
- package/dist/session/index.cjs.map +1 -1
- package/dist/session/index.d.cts +4 -4
- package/dist/session/index.d.ts +4 -4
- package/dist/session/index.js +3 -3
- package/dist/shadow/index.cjs.map +1 -1
- package/dist/shadow/index.d.cts +3 -3
- package/dist/shadow/index.d.ts +3 -3
- package/dist/shadow/index.js +2 -2
- package/dist/{signer-GRI5TZKH.js → signer-M4K5HBLD.js} +5 -5
- package/dist/{stale-OTOF3FH7.js → stale-PAGCS4K5.js} +2 -2
- package/dist/store/index.cjs.map +1 -1
- package/dist/store/index.d.cts +3 -3
- package/dist/store/index.d.ts +3 -3
- package/dist/store/index.js +2 -2
- package/dist/sync/index.cjs.map +1 -1
- package/dist/sync/index.d.cts +2 -2
- package/dist/sync/index.d.ts +2 -2
- package/dist/sync/index.js +4 -4
- package/dist/team/index.cjs.map +1 -1
- package/dist/team/index.d.cts +3 -3
- package/dist/team/index.d.ts +3 -3
- package/dist/team/index.js +8 -8
- package/dist/tx/index.cjs +81 -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 +56 -3
- package/dist/tx/index.js.map +1 -1
- package/dist/{types-DJG8HG6F.d.cts → types-CSLcfytP.d.cts} +528 -5
- package/dist/{types-BoFFiskX.d.ts → types-D9eB0Rvh.d.ts} +528 -5
- package/dist/{ulid-C7ms9oli.d.cts → ulid-CG2YvAbg.d.cts} +1 -1
- package/dist/{ulid-BmBgooGm.d.ts → ulid-CiM2OAeM.d.ts} +1 -1
- package/dist/util/index.cjs.map +1 -1
- package/dist/util/index.js +1 -1
- package/dist/{with-derivation-BKXXa8Vt.d.ts → with-derivation-Bzpj6UTv.d.ts} +1 -1
- package/dist/{with-derivation-BjQ7q4NE.d.cts → with-derivation-DWajFh4K.d.cts} +1 -1
- package/dist/{with-guard-DQme5DKE.d.cts → with-guard-DF_Ul3DT.d.cts} +1 -1
- package/dist/{with-guard-C25yNjzd.d.ts → with-guard-DR7U-l4v.d.ts} +1 -1
- package/dist/{with-materialized-view-BbEPFIIJ.d.cts → with-materialized-view-_piodoIz.d.cts} +1 -1
- package/dist/{with-materialized-view-CqnRwI2S.d.ts → with-materialized-view-qtoJ3xKJ.d.ts} +1 -1
- package/dist/{with-overlayed-view-Ct1fSJt-.d.ts → with-overlayed-view-DFaRfgMr.d.ts} +1 -1
- package/dist/{with-overlayed-view-bwlmmFjx.d.cts → with-overlayed-view-DwzCKxn2.d.cts} +1 -1
- package/package.json +3 -3
- package/dist/chunk-3Z2TPHC4.js.map +0 -1
- package/dist/chunk-E535SAN4.js.map +0 -1
- package/dist/chunk-PEULZC6M.js.map +0 -1
- package/dist/chunk-W3XXT26A.js.map +0 -1
- package/dist/chunk-XG3PTSCD.js.map +0 -1
- package/dist/chunk-YS3POABP.js.map +0 -1
- package/dist/executor-AS2IDHKZ.js +0 -11
- package/dist/executor-HLXFXNFM.js +0 -8
- package/dist/executor-HN6YBHZ5.js +0 -8
- package/dist/issue-ORP37MVW.js +0 -12
- package/dist/noydb-5H3C24GG.js +0 -34
- package/dist/registry-7HE6VJGC.js +0 -8
- package/dist/registry-PSIPG2QR.js +0 -8
- package/dist/registry-RFGGMVNJ.js +0 -7
- /package/dist/{chunk-EUYOGYGV.js.map → chunk-2EYC3WDT.js.map} +0 -0
- /package/dist/{chunk-MUWOSVEP.js.map → chunk-2XLVPKXG.js.map} +0 -0
- /package/dist/{chunk-NWZ3I6R6.js.map → chunk-4OQWR46B.js.map} +0 -0
- /package/dist/{chunk-J4KLMEUL.js.map → chunk-4UBOTYP5.js.map} +0 -0
- /package/dist/{chunk-UND4XIB6.js.map → chunk-4X2S7PBF.js.map} +0 -0
- /package/dist/{chunk-VE6YVP32.js.map → chunk-5YHWBPOT.js.map} +0 -0
- /package/dist/{chunk-7BUTTVMR.js.map → chunk-6S3LLAQ5.js.map} +0 -0
- /package/dist/{chunk-7Z23ZFLV.js.map → chunk-74JEQFMT.js.map} +0 -0
- /package/dist/{chunk-AHPFONIL.js.map → chunk-75QDHSE4.js.map} +0 -0
- /package/dist/{chunk-3XHOCQK4.js.map → chunk-A6SWRXUQ.js.map} +0 -0
- /package/dist/{chunk-PLI5TV7N.js.map → chunk-BFI3RS42.js.map} +0 -0
- /package/dist/{chunk-CXSCDO5T.js.map → chunk-EMEX37ZN.js.map} +0 -0
- /package/dist/{chunk-JYQTXEIO.js.map → chunk-FBMXWVGP.js.map} +0 -0
- /package/dist/{chunk-QXQRKXCU.js.map → chunk-FCDO7UAO.js.map} +0 -0
- /package/dist/{chunk-243PNUA6.js.map → chunk-FS7A4XNF.js.map} +0 -0
- /package/dist/{chunk-Y2RKOPNC.js.map → chunk-G7PAZ3TD.js.map} +0 -0
- /package/dist/{chunk-QPEXPHJR.js.map → chunk-GAUBWHAF.js.map} +0 -0
- /package/dist/{chunk-GIV6DWBG.js.map → chunk-GD3BGKAR.js.map} +0 -0
- /package/dist/{chunk-TBKOGSYR.js.map → chunk-GDTCGIPX.js.map} +0 -0
- /package/dist/{chunk-OVZDFEOR.js.map → chunk-HGZ7DC5H.js.map} +0 -0
- /package/dist/{chunk-VK5EER6C.js.map → chunk-K5PVGKE4.js.map} +0 -0
- /package/dist/{chunk-LRAZDV5X.js.map → chunk-KMI2NBBF.js.map} +0 -0
- /package/dist/{chunk-VRBCTEKQ.js.map → chunk-KYKMKLJ6.js.map} +0 -0
- /package/dist/{chunk-PFSNOPBQ.js.map → chunk-LOL725S4.js.map} +0 -0
- /package/dist/{chunk-3Y53S2SA.js.map → chunk-LS3JLEIB.js.map} +0 -0
- /package/dist/{chunk-YTXSFG3C.js.map → chunk-NGSPBLLE.js.map} +0 -0
- /package/dist/{chunk-FAQVNJD4.js.map → chunk-NSLTPGEN.js.map} +0 -0
- /package/dist/{chunk-VCGTOS2A.js.map → chunk-P6256WTJ.js.map} +0 -0
- /package/dist/{chunk-7Q5PLD5C.js.map → chunk-QAU5HM6Q.js.map} +0 -0
- /package/dist/{chunk-HXJXPZRE.js.map → chunk-SAVQ6E2O.js.map} +0 -0
- /package/dist/{chunk-Q6W2CMEJ.js.map → chunk-TLFUDXVV.js.map} +0 -0
- /package/dist/{chunk-2PAQNPE3.js.map → chunk-UOF74WQY.js.map} +0 -0
- /package/dist/{chunk-3QAKZ37R.js.map → chunk-UVPGJXVO.js.map} +0 -0
- /package/dist/{chunk-7BRE6EUA.js.map → chunk-WRLHNG6H.js.map} +0 -0
- /package/dist/{chunk-G6FRSBKK.js.map → chunk-YK72A4IT.js.map} +0 -0
- /package/dist/{chunk-3S4BJX25.js.map → chunk-YL2DR3HY.js.map} +0 -0
- /package/dist/{chunk-RTZVQAJ7.js.map → chunk-ZC2AAE6J.js.map} +0 -0
- /package/dist/{chunk-4HIL6AHQ.js.map → chunk-ZUMGGHRB.js.map} +0 -0
- /package/dist/{crypto-5ZDIY3NG.js.map → crypto-H2Y3DDFW.js.map} +0 -0
- /package/dist/{delegation-QYXZW25W.js.map → delegation-QSC7G5QC.js.map} +0 -0
- /package/dist/{executor-AS2IDHKZ.js.map → executor-BZKFZVRC.js.map} +0 -0
- /package/dist/{executor-HLXFXNFM.js.map → executor-GFZFDQXV.js.map} +0 -0
- /package/dist/{executor-HN6YBHZ5.js.map → executor-KT2IOZVP.js.map} +0 -0
- /package/dist/{fanout-sidecar-VJ52RIEY.js.map → fanout-sidecar-NRBWSLRK.js.map} +0 -0
- /package/dist/{issue-ORP37MVW.js.map → issue-BAJ7ZB4S.js.map} +0 -0
- /package/dist/{ledger-3IU5GMXA.js.map → ledger-WOEJUYTP.js.map} +0 -0
- /package/dist/{noydb-5H3C24GG.js.map → noydb-XNQSKXGO.js.map} +0 -0
- /package/dist/{public-envelope-U3CMEOMV.js.map → public-envelope-OHQ5UZFM.js.map} +0 -0
- /package/dist/{registry-3ALP62P6.js.map → registry-2IEARCGT.js.map} +0 -0
- /package/dist/{registry-7HE6VJGC.js.map → registry-CDHASH73.js.map} +0 -0
- /package/dist/{registry-PSIPG2QR.js.map → registry-EMGLZGR6.js.map} +0 -0
- /package/dist/{registry-RFGGMVNJ.js.map → registry-NQALYR77.js.map} +0 -0
- /package/dist/{revoke-KY2GB4KP.js.map → revoke-7JOVLZFD.js.map} +0 -0
- /package/dist/{signer-GRI5TZKH.js.map → signer-M4K5HBLD.js.map} +0 -0
- /package/dist/{stale-OTOF3FH7.js.map → stale-PAGCS4K5.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, GroupCardinalityError, IndexRequiredError, IndexWriteFailureError, BundleIntegrityError, BundleSealMismatchError, ReservedCollectionNameError, DictKeyMissingError, DictKeyInUseError, MissingTranslationError, LocaleNotSpecifiedError, TranslatorNotConfiguredError, BackupLedgerError, BackupCorruptedError, AttestationError, SessionExpiredError, SessionNotFoundError, SessionPolicyError, JoinTooLargeError, DanglingReferenceError, FilenameSanitizationError, PathEscapeError, DerivationCycleError, DerivationDepthError, DerivationOutputUnknownError, DerivationOutputShapeError, DerivationCapExceededError, MaterializedViewCycleError, MaterializedViewSourceUnknownError, MaterializedViewTooLargeError, MaterializedViewConfigError, OverlayBaseIsVirtualError, OverlayCollectionUnavailableError, OverlayNameCollisionError, OverlayIdMismatchError;
|
|
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, IndexWriteFailureError, BundleIntegrityError, BundleSealMismatchError, ReservedCollectionNameError, DictKeyMissingError, DictKeyInUseError, MissingTranslationError, LocaleNotSpecifiedError, TranslatorNotConfiguredError, BackupLedgerError, BackupCorruptedError, AttestationError, SessionExpiredError, SessionNotFoundError, SessionPolicyError, JoinTooLargeError, DanglingReferenceError, FilenameSanitizationError, PathEscapeError, DerivationCycleError, DerivationDepthError, DerivationOutputUnknownError, DerivationOutputShapeError, DerivationCapExceededError, MaterializedViewCycleError, MaterializedViewSourceUnknownError, MaterializedViewTooLargeError, MaterializedViewConfigError, OverlayBaseIsVirtualError, OverlayCollectionUnavailableError, OverlayNameCollisionError, OverlayIdMismatchError;
|
|
50
50
|
var init_errors = __esm({
|
|
51
51
|
"src/errors.ts"() {
|
|
52
52
|
"use strict";
|
|
@@ -377,6 +377,42 @@ var init_errors = __esm({
|
|
|
377
377
|
this.direction = direction;
|
|
378
378
|
}
|
|
379
379
|
};
|
|
380
|
+
SchemaUpdateError = class extends NoydbError {
|
|
381
|
+
constructor(code, message) {
|
|
382
|
+
super(code, message);
|
|
383
|
+
this.name = "SchemaUpdateError";
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
NonAdditiveSchemaChangeError = class extends SchemaUpdateError {
|
|
387
|
+
constructor(message) {
|
|
388
|
+
super("NON_ADDITIVE_SCHEMA_CHANGE", message);
|
|
389
|
+
this.name = "NonAdditiveSchemaChangeError";
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
SchemaLockedError = class extends SchemaUpdateError {
|
|
393
|
+
constructor(message) {
|
|
394
|
+
super("SCHEMA_LOCKED", message);
|
|
395
|
+
this.name = "SchemaLockedError";
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
SchemaFenceError = class extends SchemaUpdateError {
|
|
399
|
+
constructor(message) {
|
|
400
|
+
super("SCHEMA_FENCE", message);
|
|
401
|
+
this.name = "SchemaFenceError";
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
MigrationRequiredError = class extends SchemaUpdateError {
|
|
405
|
+
constructor(message) {
|
|
406
|
+
super("MIGRATION_REQUIRED", message);
|
|
407
|
+
this.name = "MigrationRequiredError";
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
QuiesceTimeoutError = class extends SchemaUpdateError {
|
|
411
|
+
constructor(message) {
|
|
412
|
+
super("QUIESCE_TIMEOUT", message);
|
|
413
|
+
this.name = "QuiesceTimeoutError";
|
|
414
|
+
}
|
|
415
|
+
};
|
|
380
416
|
GroupCardinalityError = class extends NoydbError {
|
|
381
417
|
/** The field being grouped on. */
|
|
382
418
|
field;
|
|
@@ -3506,6 +3542,13 @@ var init_registry2 = __esm({
|
|
|
3506
3542
|
guardsFor(collection) {
|
|
3507
3543
|
return this._byCollection.get(collection) ?? [];
|
|
3508
3544
|
}
|
|
3545
|
+
/** Per-collection guard counts, for introspection (#229). */
|
|
3546
|
+
summary() {
|
|
3547
|
+
return [...this._byCollection.entries()].map(([collection, guards]) => ({
|
|
3548
|
+
collection,
|
|
3549
|
+
count: guards.length
|
|
3550
|
+
}));
|
|
3551
|
+
}
|
|
3509
3552
|
/**
|
|
3510
3553
|
* Run every guard's `check` for this collection. First throw wins —
|
|
3511
3554
|
* remaining guards are not invoked. Guards without a `check` skip.
|
|
@@ -3956,6 +3999,7 @@ __export(src_exports, {
|
|
|
3956
3999
|
MaterializedViewTooLargeError: () => MaterializedViewTooLargeError,
|
|
3957
4000
|
MemoryRecipientSealer: () => MemoryRecipientSealer,
|
|
3958
4001
|
MemorySealingKeyProvider: () => MemorySealingKeyProvider,
|
|
4002
|
+
MigrationRequiredError: () => MigrationRequiredError,
|
|
3959
4003
|
MissingTranslationError: () => MissingTranslationError,
|
|
3960
4004
|
NOYDB_BACKUP_VERSION: () => NOYDB_BACKUP_VERSION,
|
|
3961
4005
|
NOYDB_BUNDLE_FORMAT_VERSION: () => NOYDB_BUNDLE_FORMAT_VERSION,
|
|
@@ -3966,6 +4010,7 @@ __export(src_exports, {
|
|
|
3966
4010
|
NOYDB_SYNC_VERSION: () => NOYDB_SYNC_VERSION,
|
|
3967
4011
|
NetworkError: () => NetworkError,
|
|
3968
4012
|
NoAccessError: () => NoAccessError,
|
|
4013
|
+
NonAdditiveSchemaChangeError: () => NonAdditiveSchemaChangeError,
|
|
3969
4014
|
NotFoundError: () => NotFoundError,
|
|
3970
4015
|
Noydb: () => Noydb,
|
|
3971
4016
|
NoydbError: () => NoydbError,
|
|
@@ -3987,6 +4032,7 @@ __export(src_exports, {
|
|
|
3987
4032
|
PrivilegeEscalationError: () => PrivilegeEscalationError,
|
|
3988
4033
|
Query: () => Query,
|
|
3989
4034
|
QuickUnlockStore: () => QuickUnlockStore,
|
|
4035
|
+
QuiesceTimeoutError: () => QuiesceTimeoutError,
|
|
3990
4036
|
ReadOnlyAtInstantError: () => ReadOnlyAtInstantError,
|
|
3991
4037
|
ReadOnlyError: () => ReadOnlyError,
|
|
3992
4038
|
ReadOnlyFrameError: () => ReadOnlyFrameError,
|
|
@@ -4002,6 +4048,9 @@ __export(src_exports, {
|
|
|
4002
4048
|
STRICT_POLICY: () => STRICT_POLICY,
|
|
4003
4049
|
SYNC_CREDENTIALS_COLLECTION: () => SYNC_CREDENTIALS_COLLECTION,
|
|
4004
4050
|
ScanBuilder: () => ScanBuilder,
|
|
4051
|
+
SchemaFenceError: () => SchemaFenceError,
|
|
4052
|
+
SchemaLockedError: () => SchemaLockedError,
|
|
4053
|
+
SchemaUpdateError: () => SchemaUpdateError,
|
|
4005
4054
|
SchemaValidationError: () => SchemaValidationError,
|
|
4006
4055
|
SessionExpiredError: () => SessionExpiredError,
|
|
4007
4056
|
SessionNotFoundError: () => SessionNotFoundError,
|
|
@@ -4028,6 +4077,7 @@ __export(src_exports, {
|
|
|
4028
4077
|
VaultInstant: () => VaultInstant,
|
|
4029
4078
|
WeakPassphraseError: () => WeakPassphraseError,
|
|
4030
4079
|
activeSessionCount: () => activeSessionCount,
|
|
4080
|
+
additiveOnly: () => additiveOnly,
|
|
4031
4081
|
applyI18nLocale: () => applyI18nLocale,
|
|
4032
4082
|
applyJoins: () => applyJoins,
|
|
4033
4083
|
applyPatch: () => applyPatch,
|
|
@@ -4035,6 +4085,7 @@ __export(src_exports, {
|
|
|
4035
4085
|
assertTierAccess: () => assertTierAccess,
|
|
4036
4086
|
avg: () => avg,
|
|
4037
4087
|
base64ToBuffer: () => base64ToBuffer,
|
|
4088
|
+
blindUpdate: () => blindUpdate,
|
|
4038
4089
|
bufferToBase64: () => bufferToBase64,
|
|
4039
4090
|
buildLiveQuery: () => buildLiveQuery,
|
|
4040
4091
|
buildRecipientKeyringFile: () => buildRecipientKeyringFile,
|
|
@@ -4043,6 +4094,7 @@ __export(src_exports, {
|
|
|
4043
4094
|
checkGate: () => checkGate,
|
|
4044
4095
|
clearDevUnlock: () => clearDevUnlock,
|
|
4045
4096
|
computePatch: () => computePatch,
|
|
4097
|
+
coordinatedCutover: () => coordinatedCutover,
|
|
4046
4098
|
count: () => count,
|
|
4047
4099
|
createBundleStore: () => createBundleStore,
|
|
4048
4100
|
createEnforcer: () => createEnforcer,
|
|
@@ -4121,6 +4173,7 @@ __export(src_exports, {
|
|
|
4121
4173
|
loadShamirRecoveryEntries: () => loadShamirRecoveryEntries,
|
|
4122
4174
|
loadUserEnvelope: () => loadUserEnvelope,
|
|
4123
4175
|
loadVaultPolicy: () => loadVaultPolicy,
|
|
4176
|
+
lockSchema: () => lockSchema,
|
|
4124
4177
|
magicLinkGrantRecordId: () => magicLinkGrantRecordId,
|
|
4125
4178
|
max: () => max,
|
|
4126
4179
|
mergeCrdtStates: () => mergeCrdtStates,
|
|
@@ -5202,6 +5255,127 @@ function createBundleStore(factory) {
|
|
|
5202
5255
|
return factory;
|
|
5203
5256
|
}
|
|
5204
5257
|
|
|
5258
|
+
// src/persisted-schemas/canonicalize.ts
|
|
5259
|
+
function canonicalize(value) {
|
|
5260
|
+
if (value === null || typeof value !== "object") {
|
|
5261
|
+
return JSON.stringify(value);
|
|
5262
|
+
}
|
|
5263
|
+
if (Array.isArray(value)) {
|
|
5264
|
+
return "[" + value.map(canonicalize).join(",") + "]";
|
|
5265
|
+
}
|
|
5266
|
+
const obj = value;
|
|
5267
|
+
const keys = Object.keys(obj).sort();
|
|
5268
|
+
const parts = keys.map((k) => JSON.stringify(k) + ":" + canonicalize(obj[k]));
|
|
5269
|
+
return "{" + parts.join(",") + "}";
|
|
5270
|
+
}
|
|
5271
|
+
|
|
5272
|
+
// src/schema-update/delta.ts
|
|
5273
|
+
function computeSchemaDelta(stored, fresh, collection) {
|
|
5274
|
+
const a = stored;
|
|
5275
|
+
const b = fresh;
|
|
5276
|
+
const aProps = a.properties ?? {};
|
|
5277
|
+
const bProps = b.properties ?? {};
|
|
5278
|
+
const aReq = new Set(a.required ?? []);
|
|
5279
|
+
const bReq = new Set(b.required ?? []);
|
|
5280
|
+
const aKeys = Object.keys(aProps);
|
|
5281
|
+
const bKeys = Object.keys(bProps);
|
|
5282
|
+
const added = bKeys.filter((k) => !(k in aProps));
|
|
5283
|
+
const removed = aKeys.filter((k) => !(k in bProps));
|
|
5284
|
+
const changed = [];
|
|
5285
|
+
for (const k of bKeys) {
|
|
5286
|
+
if (!(k in aProps)) continue;
|
|
5287
|
+
const shapeChanged = canonicalize(aProps[k]) !== canonicalize(bProps[k]);
|
|
5288
|
+
const requiredChanged = aReq.has(k) !== bReq.has(k);
|
|
5289
|
+
if (shapeChanged || requiredChanged) {
|
|
5290
|
+
changed.push({ field: k, requiredChanged, shapeChanged });
|
|
5291
|
+
}
|
|
5292
|
+
}
|
|
5293
|
+
let kind;
|
|
5294
|
+
if (added.length === 0 && removed.length === 0 && changed.length === 0) {
|
|
5295
|
+
kind = "none";
|
|
5296
|
+
} else if (removed.length === 0 && changed.length === 0 && added.every((k) => !bReq.has(k))) {
|
|
5297
|
+
kind = "additive";
|
|
5298
|
+
} else {
|
|
5299
|
+
kind = "non-additive";
|
|
5300
|
+
}
|
|
5301
|
+
return { collection, kind, added, removed, changed };
|
|
5302
|
+
}
|
|
5303
|
+
|
|
5304
|
+
// src/schema-update/dispatch.ts
|
|
5305
|
+
async function evaluateStrategies(delta, strategies, ctx) {
|
|
5306
|
+
for (const strategy of strategies) {
|
|
5307
|
+
const decision = await strategy.onSchemaDelta(delta, ctx);
|
|
5308
|
+
if (decision.action !== "allow") return decision;
|
|
5309
|
+
}
|
|
5310
|
+
return { action: "allow" };
|
|
5311
|
+
}
|
|
5312
|
+
|
|
5313
|
+
// src/schema-update/strategies.ts
|
|
5314
|
+
init_errors();
|
|
5315
|
+
function blindUpdate() {
|
|
5316
|
+
return { name: "blindUpdate", onSchemaDelta: () => ({ action: "allow" }) };
|
|
5317
|
+
}
|
|
5318
|
+
function additiveOnly() {
|
|
5319
|
+
return {
|
|
5320
|
+
name: "additiveOnly",
|
|
5321
|
+
onSchemaDelta(delta) {
|
|
5322
|
+
if (delta.kind === "non-additive") {
|
|
5323
|
+
return {
|
|
5324
|
+
action: "reject",
|
|
5325
|
+
error: new NonAdditiveSchemaChangeError(
|
|
5326
|
+
`Non-additive schema change to "${delta.collection}" (added: [${delta.added.join(", ")}], removed: [${delta.removed.join(", ")}], changed: [${delta.changed.map((c) => c.field).join(", ")}]). Register a coordinatedCutover() strategy to migrate, or revert the change.`
|
|
5327
|
+
)
|
|
5328
|
+
};
|
|
5329
|
+
}
|
|
5330
|
+
return { action: "allow" };
|
|
5331
|
+
}
|
|
5332
|
+
};
|
|
5333
|
+
}
|
|
5334
|
+
function lockSchema(opts) {
|
|
5335
|
+
const fields = opts?.fields;
|
|
5336
|
+
return {
|
|
5337
|
+
name: "lockSchema",
|
|
5338
|
+
onSchemaDelta(delta) {
|
|
5339
|
+
if (delta.kind === "none") return { action: "allow" };
|
|
5340
|
+
const touched = fields ? [...delta.added, ...delta.removed, ...delta.changed.map((c) => c.field)].filter((f) => fields.includes(f)) : ["<any>"];
|
|
5341
|
+
if (touched.length === 0) return { action: "allow" };
|
|
5342
|
+
return {
|
|
5343
|
+
action: "reject",
|
|
5344
|
+
error: new SchemaLockedError(
|
|
5345
|
+
`Schema for "${delta.collection}" is locked` + (fields ? ` on fields [${fields.join(", ")}] (touched: [${touched.join(", ")}])` : "") + `; the change was refused.`
|
|
5346
|
+
)
|
|
5347
|
+
};
|
|
5348
|
+
}
|
|
5349
|
+
};
|
|
5350
|
+
}
|
|
5351
|
+
|
|
5352
|
+
// src/schema-update/cutover.ts
|
|
5353
|
+
function coordinatedCutover(opts) {
|
|
5354
|
+
return {
|
|
5355
|
+
name: "coordinatedCutover",
|
|
5356
|
+
onSchemaDelta(delta) {
|
|
5357
|
+
if (delta.kind === "non-additive") {
|
|
5358
|
+
return { action: "cutover", transform: opts.transform };
|
|
5359
|
+
}
|
|
5360
|
+
return { action: "allow" };
|
|
5361
|
+
}
|
|
5362
|
+
};
|
|
5363
|
+
}
|
|
5364
|
+
|
|
5365
|
+
// src/schema-update/gate.ts
|
|
5366
|
+
var SchemaUpdateGate = class {
|
|
5367
|
+
#decision;
|
|
5368
|
+
constructor(decision) {
|
|
5369
|
+
this.#decision = decision.catch(() => null);
|
|
5370
|
+
}
|
|
5371
|
+
async assertWritable() {
|
|
5372
|
+
const decision = await this.#decision;
|
|
5373
|
+
if (decision && decision.action === "reject") {
|
|
5374
|
+
throw decision.error;
|
|
5375
|
+
}
|
|
5376
|
+
}
|
|
5377
|
+
};
|
|
5378
|
+
|
|
5205
5379
|
// src/store/sync-policy.ts
|
|
5206
5380
|
var INDEXED_STORE_POLICY = {
|
|
5207
5381
|
push: { mode: "on-change", minIntervalMs: 0, onUnload: true },
|
|
@@ -5776,8 +5950,8 @@ function withRetry(opts = {}) {
|
|
|
5776
5950
|
} catch (err) {
|
|
5777
5951
|
lastError = err;
|
|
5778
5952
|
if (attempt >= maxRetries || !shouldRetry(err)) throw err;
|
|
5779
|
-
const
|
|
5780
|
-
await new Promise((r) => setTimeout(r,
|
|
5953
|
+
const delay2 = backoffMs * Math.pow(2, attempt) * (1 + Math.random() * jitter);
|
|
5954
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
5781
5955
|
}
|
|
5782
5956
|
}
|
|
5783
5957
|
throw lastError;
|
|
@@ -6824,20 +6998,6 @@ function formatPath(path) {
|
|
|
6824
6998
|
).join(".");
|
|
6825
6999
|
}
|
|
6826
7000
|
|
|
6827
|
-
// src/persisted-schemas/canonicalize.ts
|
|
6828
|
-
function canonicalize(value) {
|
|
6829
|
-
if (value === null || typeof value !== "object") {
|
|
6830
|
-
return JSON.stringify(value);
|
|
6831
|
-
}
|
|
6832
|
-
if (Array.isArray(value)) {
|
|
6833
|
-
return "[" + value.map(canonicalize).join(",") + "]";
|
|
6834
|
-
}
|
|
6835
|
-
const obj = value;
|
|
6836
|
-
const keys = Object.keys(obj).sort();
|
|
6837
|
-
const parts = keys.map((k) => JSON.stringify(k) + ":" + canonicalize(obj[k]));
|
|
6838
|
-
return "{" + parts.join(",") + "}";
|
|
6839
|
-
}
|
|
6840
|
-
|
|
6841
7001
|
// src/persisted-schemas/derive.ts
|
|
6842
7002
|
init_crypto();
|
|
6843
7003
|
function isZodSchema(value) {
|
|
@@ -6918,10 +7078,22 @@ async function persistSchemaIfNeeded(opts) {
|
|
|
6918
7078
|
const fresh = await derivePersistedSchema(opts.validator);
|
|
6919
7079
|
const stored = await loadPersistedSchema(opts.store, opts.vault, opts.collectionName, opts.dek);
|
|
6920
7080
|
if (stored && isEquivalent(stored, fresh)) {
|
|
6921
|
-
return { written: false, skipped: true, envelope: stored };
|
|
7081
|
+
return { written: false, skipped: true, envelope: stored, decision: { action: "allow" } };
|
|
7082
|
+
}
|
|
7083
|
+
let decision = { action: "allow" };
|
|
7084
|
+
const strategies = opts.strategies ?? [];
|
|
7085
|
+
if (stored && strategies.length > 0 && stored.kind === fresh.kind && isPlainObject(stored.jsonSchema) && isPlainObject(fresh.jsonSchema)) {
|
|
7086
|
+
const delta = computeSchemaDelta(stored.jsonSchema, fresh.jsonSchema, opts.collectionName);
|
|
7087
|
+
decision = await evaluateStrategies(delta, strategies, { collection: opts.collectionName });
|
|
7088
|
+
}
|
|
7089
|
+
if (decision.action !== "allow") {
|
|
7090
|
+
return { written: false, skipped: false, envelope: stored ?? fresh, decision };
|
|
6922
7091
|
}
|
|
6923
7092
|
await savePersistedSchema(opts.store, opts.vault, opts.collectionName, opts.dek, fresh);
|
|
6924
|
-
return { written: true, skipped: false, envelope: fresh };
|
|
7093
|
+
return { written: true, skipped: false, envelope: fresh, decision };
|
|
7094
|
+
}
|
|
7095
|
+
function isPlainObject(v) {
|
|
7096
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
6925
7097
|
}
|
|
6926
7098
|
function isEquivalent(a, b) {
|
|
6927
7099
|
if (a.kind !== b.kind) return false;
|
|
@@ -7063,7 +7235,7 @@ var CollectionInstant = class {
|
|
|
7063
7235
|
if (e.collection !== this.name || e.id !== id) continue;
|
|
7064
7236
|
if (e.ts > this.targetTs) break;
|
|
7065
7237
|
if (e.op === "amendment" || e.op === "lifecycle") continue;
|
|
7066
|
-
latest = { op: e.op, version: e.version };
|
|
7238
|
+
latest = { op: e.op === "migration" ? "put" : e.op, version: e.version };
|
|
7067
7239
|
}
|
|
7068
7240
|
if (!latest) return null;
|
|
7069
7241
|
if (latest.op === "delete") return null;
|
|
@@ -9016,7 +9188,7 @@ var UserApi = class {
|
|
|
9016
9188
|
}
|
|
9017
9189
|
};
|
|
9018
9190
|
function deepMerge(source, patch) {
|
|
9019
|
-
if (!
|
|
9191
|
+
if (!isPlainObject2(source) || !isPlainObject2(patch)) {
|
|
9020
9192
|
return patch;
|
|
9021
9193
|
}
|
|
9022
9194
|
const out = { ...source };
|
|
@@ -9029,8 +9201,8 @@ function deepMerge(source, patch) {
|
|
|
9029
9201
|
continue;
|
|
9030
9202
|
}
|
|
9031
9203
|
const sourceVal = source[key];
|
|
9032
|
-
if (
|
|
9033
|
-
const recurseSource =
|
|
9204
|
+
if (isPlainObject2(patchVal)) {
|
|
9205
|
+
const recurseSource = isPlainObject2(sourceVal) ? sourceVal : {};
|
|
9034
9206
|
out[key] = deepMerge(recurseSource, patchVal);
|
|
9035
9207
|
} else {
|
|
9036
9208
|
out[key] = patchVal;
|
|
@@ -9038,7 +9210,7 @@ function deepMerge(source, patch) {
|
|
|
9038
9210
|
}
|
|
9039
9211
|
return out;
|
|
9040
9212
|
}
|
|
9041
|
-
function
|
|
9213
|
+
function isPlainObject2(x) {
|
|
9042
9214
|
if (x === null || typeof x !== "object") return false;
|
|
9043
9215
|
if (Array.isArray(x)) return false;
|
|
9044
9216
|
const proto = Object.getPrototypeOf(x);
|
|
@@ -11521,7 +11693,10 @@ var NO_BLOBS = {
|
|
|
11521
11693
|
|
|
11522
11694
|
// src/tx/transaction.ts
|
|
11523
11695
|
init_errors();
|
|
11696
|
+
init_ulid();
|
|
11524
11697
|
var TxContext = class {
|
|
11698
|
+
/** Stable id for this transaction; shared by all writes it performs (#230). */
|
|
11699
|
+
txId = generateULID();
|
|
11525
11700
|
/** @internal */
|
|
11526
11701
|
_ops = [];
|
|
11527
11702
|
/**
|
|
@@ -11886,6 +12061,11 @@ var Collection = class {
|
|
|
11886
12061
|
keyring;
|
|
11887
12062
|
encrypted;
|
|
11888
12063
|
emitter;
|
|
12064
|
+
writeQueue;
|
|
12065
|
+
schemaUpdateGate;
|
|
12066
|
+
schemaFence;
|
|
12067
|
+
writeHooks;
|
|
12068
|
+
activeTxId;
|
|
11889
12069
|
getDEK;
|
|
11890
12070
|
onDirty;
|
|
11891
12071
|
historyConfig;
|
|
@@ -12160,6 +12340,11 @@ var Collection = class {
|
|
|
12160
12340
|
this.keyring = opts.keyring;
|
|
12161
12341
|
this.encrypted = opts.encrypted;
|
|
12162
12342
|
this.emitter = opts.emitter;
|
|
12343
|
+
this.writeQueue = opts.writeQueue;
|
|
12344
|
+
this.schemaUpdateGate = opts.schemaUpdateGate;
|
|
12345
|
+
this.schemaFence = opts.schemaFence;
|
|
12346
|
+
this.writeHooks = opts.writeHooks;
|
|
12347
|
+
this.activeTxId = opts.activeTxId;
|
|
12163
12348
|
this.blobStrategy = opts.blobStrategy ?? NO_BLOBS;
|
|
12164
12349
|
this.aggregateStrategy = opts.aggregateStrategy ?? NO_AGGREGATE;
|
|
12165
12350
|
this.crdtStrategy = opts.crdtStrategy ?? NO_CRDT;
|
|
@@ -12385,7 +12570,8 @@ var Collection = class {
|
|
|
12385
12570
|
return this.syncStrategy.buildPresence(presenceOpts);
|
|
12386
12571
|
}
|
|
12387
12572
|
/**
|
|
12388
|
-
* Create or update a record.
|
|
12573
|
+
* Create or update a record. Runs inside the hub's write-queue tracker
|
|
12574
|
+
* (#227) so `hub.writeQueue.pending` reflects this write.
|
|
12389
12575
|
*
|
|
12390
12576
|
* @param id Record identifier.
|
|
12391
12577
|
* @param record The record body (validated by the collection's schema
|
|
@@ -12396,6 +12582,59 @@ var Collection = class {
|
|
|
12396
12582
|
* `entries.filter(e => e.reason?.startsWith('import:'))`.
|
|
12397
12583
|
*/
|
|
12398
12584
|
async put(id, record, options) {
|
|
12585
|
+
await this.schemaUpdateGate?.assertWritable();
|
|
12586
|
+
await this.schemaFence?.assertWritable(this.name);
|
|
12587
|
+
let event;
|
|
12588
|
+
if (this.#hooksActive()) {
|
|
12589
|
+
const prior = await this.#priorForHook(id);
|
|
12590
|
+
event = {
|
|
12591
|
+
op: prior.record === null ? "create" : "update",
|
|
12592
|
+
vault: this.vault,
|
|
12593
|
+
collection: this.name,
|
|
12594
|
+
docId: id,
|
|
12595
|
+
before: prior.record,
|
|
12596
|
+
after: record,
|
|
12597
|
+
userId: this.keyring.userId,
|
|
12598
|
+
timestamp: Date.now(),
|
|
12599
|
+
txId: this.#txIdForHook(),
|
|
12600
|
+
baseVersion: prior.version,
|
|
12601
|
+
version: prior.version + 1
|
|
12602
|
+
};
|
|
12603
|
+
await this.writeHooks.runBefore(event);
|
|
12604
|
+
}
|
|
12605
|
+
if (this.writeQueue) await this.writeQueue.track(() => this.putInternal(id, record, options));
|
|
12606
|
+
else await this.putInternal(id, record, options);
|
|
12607
|
+
if (event) await this.writeHooks.runAfter(event);
|
|
12608
|
+
}
|
|
12609
|
+
/** @internal #230 — true when hooks should fire for this write (handlers exist, not re-entrant). */
|
|
12610
|
+
#hooksActive() {
|
|
12611
|
+
return this.writeHooks !== void 0 && this.writeHooks.hasHandlers && !this.writeHooks.suppressed;
|
|
12612
|
+
}
|
|
12613
|
+
/**
|
|
12614
|
+
* @internal #230/#228c — resolve the prior record for a hook's `before` and
|
|
12615
|
+
* its version. Critically, this uses the SAME basis `putInternal` writes from
|
|
12616
|
+
* (the in-memory cache in eager mode; lru-then-adapter in lazy) — NOT a fresh
|
|
12617
|
+
* store read — so `baseVersion`/`version` match the version actually written.
|
|
12618
|
+
* A separate store read would diverge once another tab has advanced the shared
|
|
12619
|
+
* store past this tab's cache, breaking #228c conflict detection.
|
|
12620
|
+
*/
|
|
12621
|
+
async #priorForHook(id) {
|
|
12622
|
+
if (this.lazy && this.lru) {
|
|
12623
|
+
const cached2 = this.lru.get(id);
|
|
12624
|
+
if (cached2) return { record: cached2.record, version: cached2.version };
|
|
12625
|
+
const env = await this.adapter.get(this.vault, this.name, id);
|
|
12626
|
+
if (!env) return { record: null, version: 0 };
|
|
12627
|
+
return { record: await this.decryptRecord(env, { skipValidation: true }), version: env._v };
|
|
12628
|
+
}
|
|
12629
|
+
await this.ensureHydrated();
|
|
12630
|
+
const cached = this.cache.get(id);
|
|
12631
|
+
return cached ? { record: cached.record, version: cached.version } : { record: null, version: 0 };
|
|
12632
|
+
}
|
|
12633
|
+
#txIdForHook() {
|
|
12634
|
+
return this.activeTxId?.() ?? generateULID();
|
|
12635
|
+
}
|
|
12636
|
+
/** @internal Untracked put body — call {@link put}, not this. */
|
|
12637
|
+
async putInternal(id, record, options) {
|
|
12399
12638
|
if (!hasWritePermission(this.keyring, this.name)) {
|
|
12400
12639
|
throw new ReadOnlyError();
|
|
12401
12640
|
}
|
|
@@ -12772,8 +13011,71 @@ var Collection = class {
|
|
|
12772
13011
|
}
|
|
12773
13012
|
}
|
|
12774
13013
|
}
|
|
12775
|
-
/**
|
|
13014
|
+
/**
|
|
13015
|
+
* Delete a record by ID. Runs inside the hub's write-queue tracker
|
|
13016
|
+
* (#227) so `hub.writeQueue.pending` reflects this write.
|
|
13017
|
+
*/
|
|
12776
13018
|
async delete(id) {
|
|
13019
|
+
await this.schemaUpdateGate?.assertWritable();
|
|
13020
|
+
await this.schemaFence?.assertWritable(this.name);
|
|
13021
|
+
let event;
|
|
13022
|
+
if (this.#hooksActive()) {
|
|
13023
|
+
const prior = await this.#priorForHook(id);
|
|
13024
|
+
event = {
|
|
13025
|
+
op: "delete",
|
|
13026
|
+
vault: this.vault,
|
|
13027
|
+
collection: this.name,
|
|
13028
|
+
docId: id,
|
|
13029
|
+
before: prior.record,
|
|
13030
|
+
after: null,
|
|
13031
|
+
userId: this.keyring.userId,
|
|
13032
|
+
timestamp: Date.now(),
|
|
13033
|
+
txId: this.#txIdForHook(),
|
|
13034
|
+
baseVersion: prior.version,
|
|
13035
|
+
version: prior.version + 1
|
|
13036
|
+
};
|
|
13037
|
+
await this.writeHooks.runBefore(event);
|
|
13038
|
+
}
|
|
13039
|
+
if (this.writeQueue) await this.writeQueue.track(() => this.deleteInternal(id));
|
|
13040
|
+
else await this.deleteInternal(id);
|
|
13041
|
+
if (event) await this.writeHooks.runAfter(event);
|
|
13042
|
+
}
|
|
13043
|
+
/**
|
|
13044
|
+
* @internal #232 — bulk-rewrite every record through a cutover transform.
|
|
13045
|
+
* Raw adapter path (bypasses the write gate + guards — the transform is
|
|
13046
|
+
* trusted and runs only during the `migrating` phase). Bumps each
|
|
13047
|
+
* record's `_v` and appends a ledger `op:'migration'` entry.
|
|
13048
|
+
*/
|
|
13049
|
+
async _applyCutoverTransform(transform) {
|
|
13050
|
+
const ids = await this.adapter.list(this.vault, this.name);
|
|
13051
|
+
let count2 = 0;
|
|
13052
|
+
for (const id of ids) {
|
|
13053
|
+
const env = await this.adapter.get(this.vault, this.name, id);
|
|
13054
|
+
if (!env) continue;
|
|
13055
|
+
const record = await this.decryptRecord(env, { skipValidation: true });
|
|
13056
|
+
const next = transform(record);
|
|
13057
|
+
const nextVersion = (env._v ?? 0) + 1;
|
|
13058
|
+
const newEnv = await this.encryptRecord(next, nextVersion);
|
|
13059
|
+
await this.adapter.put(this.vault, this.name, id, newEnv);
|
|
13060
|
+
await this._invalidateCacheEntry(id);
|
|
13061
|
+
if (this.ledger) {
|
|
13062
|
+
await this.ledger.append({
|
|
13063
|
+
op: "migration",
|
|
13064
|
+
collection: this.name,
|
|
13065
|
+
id,
|
|
13066
|
+
version: nextVersion,
|
|
13067
|
+
actor: this.keyring.userId,
|
|
13068
|
+
payloadHash: "",
|
|
13069
|
+
reason: "schema:coordinated-cutover"
|
|
13070
|
+
}).catch(() => {
|
|
13071
|
+
});
|
|
13072
|
+
}
|
|
13073
|
+
count2++;
|
|
13074
|
+
}
|
|
13075
|
+
return count2;
|
|
13076
|
+
}
|
|
13077
|
+
/** @internal Untracked delete body — call {@link delete}, not this. */
|
|
13078
|
+
async deleteInternal(id) {
|
|
12777
13079
|
await this._doDelete(id, false);
|
|
12778
13080
|
}
|
|
12779
13081
|
/**
|
|
@@ -13596,6 +13898,21 @@ var Collection = class {
|
|
|
13596
13898
|
this.cache.set(id, { record, version: envelope._v });
|
|
13597
13899
|
this.indexes?.upsert(id, record, previous ? previous.record : null);
|
|
13598
13900
|
}
|
|
13901
|
+
/**
|
|
13902
|
+
* #228b — apply a peer tab's committed write to THIS tab's in-memory view:
|
|
13903
|
+
* re-read the (already-persisted) envelope from the shared store + refresh
|
|
13904
|
+
* cache/indexes, then emit a `change` event so reactive consumers re-render.
|
|
13905
|
+
* Never writes to the store and never fires write hooks, so it cannot loop.
|
|
13906
|
+
*/
|
|
13907
|
+
async _applyRemoteChange(id, action) {
|
|
13908
|
+
await this._invalidateCacheEntry(id);
|
|
13909
|
+
this.emitter.emit("change", { vault: this.vault, collection: this.name, id, action });
|
|
13910
|
+
}
|
|
13911
|
+
/** @internal #228c — the current in-memory record without a store read (for conflict capture). */
|
|
13912
|
+
_peekCached(id) {
|
|
13913
|
+
const entry = this.lazy && this.lru ? this.lru.get(id) : this.cache.get(id);
|
|
13914
|
+
return entry ? entry.record : null;
|
|
13915
|
+
}
|
|
13599
13916
|
async ensureHydrated() {
|
|
13600
13917
|
if (this.hydrated) return;
|
|
13601
13918
|
const ids = await this.adapter.list(this.vault, this.name);
|
|
@@ -15425,6 +15742,245 @@ function isMagicLinkGrantExpired(payload, now = /* @__PURE__ */ new Date()) {
|
|
|
15425
15742
|
return payload.until <= now.toISOString();
|
|
15426
15743
|
}
|
|
15427
15744
|
|
|
15745
|
+
// src/schema-update/fence.ts
|
|
15746
|
+
init_types();
|
|
15747
|
+
var FENCE_RECORD_ID = "schema-fence";
|
|
15748
|
+
var META_COLLECTION3 = "_meta";
|
|
15749
|
+
var DEFAULT_FENCE = { currentSchemaVersion: 0, fenceState: "normal" };
|
|
15750
|
+
async function loadFence(store, vault) {
|
|
15751
|
+
const envelope = await store.get(vault, META_COLLECTION3, FENCE_RECORD_ID);
|
|
15752
|
+
if (!envelope) return DEFAULT_FENCE;
|
|
15753
|
+
try {
|
|
15754
|
+
const parsed = JSON.parse(envelope._data);
|
|
15755
|
+
if (!isFenceDoc(parsed)) return DEFAULT_FENCE;
|
|
15756
|
+
return parsed;
|
|
15757
|
+
} catch {
|
|
15758
|
+
return DEFAULT_FENCE;
|
|
15759
|
+
}
|
|
15760
|
+
}
|
|
15761
|
+
async function saveFence(store, vault, fence) {
|
|
15762
|
+
const envelope = {
|
|
15763
|
+
_noydb: NOYDB_FORMAT_VERSION,
|
|
15764
|
+
_v: 1,
|
|
15765
|
+
_ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15766
|
+
_iv: "",
|
|
15767
|
+
_data: JSON.stringify(fence)
|
|
15768
|
+
};
|
|
15769
|
+
await store.put(vault, META_COLLECTION3, FENCE_RECORD_ID, envelope);
|
|
15770
|
+
}
|
|
15771
|
+
function isFenceDoc(x) {
|
|
15772
|
+
if (x === null || typeof x !== "object") return false;
|
|
15773
|
+
const o = x;
|
|
15774
|
+
return typeof o["currentSchemaVersion"] === "number" && (o["fenceState"] === "normal" || o["fenceState"] === "draining" || o["fenceState"] === "migrating" || o["fenceState"] === "complete");
|
|
15775
|
+
}
|
|
15776
|
+
|
|
15777
|
+
// src/schema-update/fence-controller.ts
|
|
15778
|
+
init_errors();
|
|
15779
|
+
|
|
15780
|
+
// src/schema-update/client-registry.ts
|
|
15781
|
+
init_types();
|
|
15782
|
+
var META_COLLECTION4 = "_meta";
|
|
15783
|
+
var CLIENT_PREFIX = "schema-fence:client:";
|
|
15784
|
+
async function writeClientDoc(store, vault, clientId, doc) {
|
|
15785
|
+
const envelope = {
|
|
15786
|
+
_noydb: NOYDB_FORMAT_VERSION,
|
|
15787
|
+
_v: 1,
|
|
15788
|
+
_ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15789
|
+
_iv: "",
|
|
15790
|
+
_data: JSON.stringify({ clientId, ...doc })
|
|
15791
|
+
};
|
|
15792
|
+
await store.put(vault, META_COLLECTION4, `${CLIENT_PREFIX}${clientId}`, envelope);
|
|
15793
|
+
}
|
|
15794
|
+
async function listClientDocs(store, vault) {
|
|
15795
|
+
const ids = await store.list(vault, META_COLLECTION4);
|
|
15796
|
+
const out = [];
|
|
15797
|
+
for (const id of ids) {
|
|
15798
|
+
if (!id.startsWith(CLIENT_PREFIX)) continue;
|
|
15799
|
+
const env = await store.get(vault, META_COLLECTION4, id);
|
|
15800
|
+
if (!env) continue;
|
|
15801
|
+
try {
|
|
15802
|
+
const parsed = JSON.parse(env._data);
|
|
15803
|
+
if (isClientDoc(parsed)) out.push(parsed);
|
|
15804
|
+
} catch {
|
|
15805
|
+
}
|
|
15806
|
+
}
|
|
15807
|
+
return out;
|
|
15808
|
+
}
|
|
15809
|
+
async function activeQuiesced(store, vault, opts) {
|
|
15810
|
+
const docs = await listClientDocs(store, vault);
|
|
15811
|
+
const active = docs.filter(
|
|
15812
|
+
(d) => d.lastSeen >= opts.now - opts.staleMs && d.clientId !== opts.excludeClientId
|
|
15813
|
+
);
|
|
15814
|
+
return active.every((d) => d.quiescedAtVersion === opts.generation);
|
|
15815
|
+
}
|
|
15816
|
+
function isClientDoc(x) {
|
|
15817
|
+
if (x === null || typeof x !== "object") return false;
|
|
15818
|
+
const o = x;
|
|
15819
|
+
return typeof o["clientId"] === "string" && typeof o["lastSeen"] === "number" && (o["quiescedAtVersion"] === null || typeof o["quiescedAtVersion"] === "number");
|
|
15820
|
+
}
|
|
15821
|
+
|
|
15822
|
+
// src/schema-update/fence-controller.ts
|
|
15823
|
+
var SchemaFenceController = class {
|
|
15824
|
+
#store;
|
|
15825
|
+
#vault;
|
|
15826
|
+
#onFlush;
|
|
15827
|
+
#clientId;
|
|
15828
|
+
#now;
|
|
15829
|
+
#staleMs;
|
|
15830
|
+
#quiesceTimeoutMs;
|
|
15831
|
+
#emit;
|
|
15832
|
+
#snapshot = 0;
|
|
15833
|
+
#pending = /* @__PURE__ */ new Map();
|
|
15834
|
+
constructor(opts) {
|
|
15835
|
+
this.#store = opts.store;
|
|
15836
|
+
this.#vault = opts.vault;
|
|
15837
|
+
this.#onFlush = opts.onFlush;
|
|
15838
|
+
this.#clientId = opts.clientId ?? "migrator";
|
|
15839
|
+
this.#now = opts.now ?? (() => Date.now());
|
|
15840
|
+
this.#staleMs = opts.staleMs ?? 3e4;
|
|
15841
|
+
this.#quiesceTimeoutMs = opts.quiesceTimeoutMs ?? 6e4;
|
|
15842
|
+
this.#emit = opts.emit ?? (() => {
|
|
15843
|
+
});
|
|
15844
|
+
}
|
|
15845
|
+
/** Capture the generation snapshot at vault-open. */
|
|
15846
|
+
async init() {
|
|
15847
|
+
this.#snapshot = (await loadFence(this.#store, this.#vault)).currentSchemaVersion;
|
|
15848
|
+
}
|
|
15849
|
+
/** Record a per-collection pending cutover (from a registration `cutover` decision). */
|
|
15850
|
+
registerPendingCutover(collection, transform) {
|
|
15851
|
+
this.#pending.set(collection, transform);
|
|
15852
|
+
}
|
|
15853
|
+
/** Write-path gate. Throws when behind, fenced, or this collection is cutover-pending. */
|
|
15854
|
+
async assertWritable(collection) {
|
|
15855
|
+
const fence = await loadFence(this.#store, this.#vault);
|
|
15856
|
+
if (fence.currentSchemaVersion > this.#snapshot) {
|
|
15857
|
+
throw new MigrationRequiredError(
|
|
15858
|
+
`Vault "${this.#vault}" advanced to schema generation ${fence.currentSchemaVersion} (this client opened at ${this.#snapshot}). Reload to continue.`
|
|
15859
|
+
);
|
|
15860
|
+
}
|
|
15861
|
+
if (fence.fenceState === "draining" || fence.fenceState === "migrating") {
|
|
15862
|
+
throw new SchemaFenceError(`Vault "${this.#vault}" is mid-cutover (${fence.fenceState}); writes are paused.`);
|
|
15863
|
+
}
|
|
15864
|
+
if (this.#pending.has(collection)) {
|
|
15865
|
+
throw new SchemaFenceError(
|
|
15866
|
+
`Collection "${collection}" has a pending schema cutover; run vault.runSchemaCutover() before writing.`
|
|
15867
|
+
);
|
|
15868
|
+
}
|
|
15869
|
+
}
|
|
15870
|
+
/**
|
|
15871
|
+
* Admin trigger. Drain → wait for the active set to quiesce (or time out)
|
|
15872
|
+
* → migrate each pending transform → bump → complete → normal. The
|
|
15873
|
+
* migrator excludes itself from the barrier (it drained synchronously
|
|
15874
|
+
* here). `onPoll` (tests) advances other clients between barrier checks;
|
|
15875
|
+
* production falls back to a short real delay.
|
|
15876
|
+
*/
|
|
15877
|
+
async runCutover(run, opts) {
|
|
15878
|
+
if (this.#pending.size === 0) return { migrated: 0 };
|
|
15879
|
+
const base = await loadFence(this.#store, this.#vault);
|
|
15880
|
+
const generation = base.currentSchemaVersion;
|
|
15881
|
+
await this.#setState(generation, "draining");
|
|
15882
|
+
await this.#onFlush();
|
|
15883
|
+
const deadline = this.#now() + this.#quiesceTimeoutMs;
|
|
15884
|
+
while (!await activeQuiesced(this.#store, this.#vault, {
|
|
15885
|
+
generation,
|
|
15886
|
+
now: this.#now(),
|
|
15887
|
+
staleMs: this.#staleMs,
|
|
15888
|
+
excludeClientId: this.#clientId
|
|
15889
|
+
})) {
|
|
15890
|
+
if (this.#now() >= deadline) {
|
|
15891
|
+
throw new QuiesceTimeoutError(
|
|
15892
|
+
`Cutover on "${this.#vault}" timed out waiting for clients to quiesce at generation ${generation}.`
|
|
15893
|
+
);
|
|
15894
|
+
}
|
|
15895
|
+
await (opts?.onPoll ? opts.onPoll() : delay(50));
|
|
15896
|
+
}
|
|
15897
|
+
await this.#setState(generation, "migrating");
|
|
15898
|
+
let migrated = 0;
|
|
15899
|
+
for (const [collection, transform] of this.#pending) {
|
|
15900
|
+
await run(collection, transform);
|
|
15901
|
+
migrated++;
|
|
15902
|
+
}
|
|
15903
|
+
const nextVersion = generation + 1;
|
|
15904
|
+
await this.#setState(nextVersion, "complete");
|
|
15905
|
+
this.#pending.clear();
|
|
15906
|
+
await this.#setState(nextVersion, "normal");
|
|
15907
|
+
this.#snapshot = nextVersion;
|
|
15908
|
+
return { migrated };
|
|
15909
|
+
}
|
|
15910
|
+
/** Recover a stuck drain: reset fenceState to normal at the current version (no bump). */
|
|
15911
|
+
async abort() {
|
|
15912
|
+
const fence = await loadFence(this.#store, this.#vault);
|
|
15913
|
+
await this.#setState(fence.currentSchemaVersion, "normal");
|
|
15914
|
+
}
|
|
15915
|
+
async #setState(currentSchemaVersion, fenceState) {
|
|
15916
|
+
await saveFence(this.#store, this.#vault, { currentSchemaVersion, fenceState });
|
|
15917
|
+
this.#emit({ currentSchemaVersion, fenceState });
|
|
15918
|
+
}
|
|
15919
|
+
};
|
|
15920
|
+
function delay(ms) {
|
|
15921
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
15922
|
+
}
|
|
15923
|
+
|
|
15924
|
+
// src/schema-update/fence-watcher.ts
|
|
15925
|
+
var FenceWatcher = class {
|
|
15926
|
+
#store;
|
|
15927
|
+
#vault;
|
|
15928
|
+
#clientId;
|
|
15929
|
+
#onFlush;
|
|
15930
|
+
#now;
|
|
15931
|
+
#emit;
|
|
15932
|
+
#lastState = null;
|
|
15933
|
+
#quiescedAtVersion = null;
|
|
15934
|
+
#timer;
|
|
15935
|
+
constructor(opts) {
|
|
15936
|
+
this.#store = opts.store;
|
|
15937
|
+
this.#vault = opts.vault;
|
|
15938
|
+
this.#clientId = opts.clientId;
|
|
15939
|
+
this.#onFlush = opts.onFlush;
|
|
15940
|
+
this.#now = opts.now ?? (() => Date.now());
|
|
15941
|
+
this.#emit = opts.emit ?? (() => {
|
|
15942
|
+
});
|
|
15943
|
+
}
|
|
15944
|
+
/** Publish liveness (and the current ack) without changing quiesce state. */
|
|
15945
|
+
async beat() {
|
|
15946
|
+
await writeClientDoc(this.#store, this.#vault, this.#clientId, {
|
|
15947
|
+
lastSeen: this.#now(),
|
|
15948
|
+
quiescedAtVersion: this.#quiescedAtVersion
|
|
15949
|
+
});
|
|
15950
|
+
}
|
|
15951
|
+
/** Poll the fence; quiesce on draining; emit on transitions. */
|
|
15952
|
+
async check() {
|
|
15953
|
+
const fence = await loadFence(this.#store, this.#vault);
|
|
15954
|
+
if (fence.fenceState !== this.#lastState) {
|
|
15955
|
+
this.#lastState = fence.fenceState;
|
|
15956
|
+
this.#emit({ currentSchemaVersion: fence.currentSchemaVersion, fenceState: fence.fenceState });
|
|
15957
|
+
}
|
|
15958
|
+
if (fence.fenceState === "draining" && this.#quiescedAtVersion !== fence.currentSchemaVersion) {
|
|
15959
|
+
await this.#onFlush();
|
|
15960
|
+
this.#quiescedAtVersion = fence.currentSchemaVersion;
|
|
15961
|
+
await this.beat();
|
|
15962
|
+
}
|
|
15963
|
+
if (fence.fenceState === "normal") {
|
|
15964
|
+
this.#quiescedAtVersion = null;
|
|
15965
|
+
}
|
|
15966
|
+
}
|
|
15967
|
+
start(intervalMs) {
|
|
15968
|
+
if (this.#timer) return;
|
|
15969
|
+
this.#timer = setInterval(() => {
|
|
15970
|
+
void this.beat();
|
|
15971
|
+
void this.check();
|
|
15972
|
+
}, intervalMs);
|
|
15973
|
+
const timer = this.#timer;
|
|
15974
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
15975
|
+
}
|
|
15976
|
+
stop() {
|
|
15977
|
+
if (this.#timer) {
|
|
15978
|
+
clearInterval(this.#timer);
|
|
15979
|
+
this.#timer = void 0;
|
|
15980
|
+
}
|
|
15981
|
+
}
|
|
15982
|
+
};
|
|
15983
|
+
|
|
15428
15984
|
// src/introspection/fields.ts
|
|
15429
15985
|
function jsonSchemaType(node) {
|
|
15430
15986
|
if (Array.isArray(node.type)) {
|
|
@@ -15771,6 +16327,13 @@ var Vault = class {
|
|
|
15771
16327
|
*/
|
|
15772
16328
|
reloadKeyring;
|
|
15773
16329
|
collectionCache = /* @__PURE__ */ new Map();
|
|
16330
|
+
/** #232 — vault-level schema cutover fence/controller. */
|
|
16331
|
+
schemaFence;
|
|
16332
|
+
/** #232 — per-client heartbeat/watcher; started lazily on cutover registration. */
|
|
16333
|
+
#fenceWatcher;
|
|
16334
|
+
#fenceCoordinationStarted = false;
|
|
16335
|
+
/** #229 — per-collection registered schema-update strategy names. */
|
|
16336
|
+
#schemaUpdateNames = /* @__PURE__ */ new Map();
|
|
15774
16337
|
/**
|
|
15775
16338
|
* per-collection `blobFields` retention/TTL config.
|
|
15776
16339
|
* Populated on `collection({ blobFields })` and read by
|
|
@@ -15886,6 +16449,13 @@ var Vault = class {
|
|
|
15886
16449
|
this.noydb = opts.noydb;
|
|
15887
16450
|
this.keyring = opts.keyring;
|
|
15888
16451
|
this.encrypted = opts.encrypted;
|
|
16452
|
+
this.schemaFence = new SchemaFenceController({
|
|
16453
|
+
store: this.adapter,
|
|
16454
|
+
vault: this.name,
|
|
16455
|
+
onFlush: () => this.noydb._writeQueueTracker.onFlush(),
|
|
16456
|
+
clientId: this.noydb._clientId,
|
|
16457
|
+
emit: (e) => this.emitter.emit("schema:fence-changed", { vault: this.name, ...e })
|
|
16458
|
+
});
|
|
15889
16459
|
this.emitter = opts.emitter;
|
|
15890
16460
|
this.onDirty = opts.onDirty;
|
|
15891
16461
|
this.onRegisterConflictResolver = opts.onRegisterConflictResolver;
|
|
@@ -15994,6 +16564,35 @@ var Vault = class {
|
|
|
15994
16564
|
}
|
|
15995
16565
|
this.dictKeyFieldRegistry.set(collectionName, dictFieldMap);
|
|
15996
16566
|
}
|
|
16567
|
+
if ((options?.schemaUpdate?.length ?? 0) > 0) {
|
|
16568
|
+
this.#schemaUpdateNames.set(collectionName, (options.schemaUpdate ?? []).map((s) => s.name));
|
|
16569
|
+
}
|
|
16570
|
+
let schemaUpdateGate;
|
|
16571
|
+
if (options?.persistJsonSchema === true && options.schema !== void 0 && (options.schemaUpdate?.length ?? 0) > 0) {
|
|
16572
|
+
const validator = options.schema;
|
|
16573
|
+
const strategies = options.schemaUpdate ?? [];
|
|
16574
|
+
const work = (async () => {
|
|
16575
|
+
const dek = await this.getDEK(collectionName);
|
|
16576
|
+
const result = await persistSchemaIfNeeded({
|
|
16577
|
+
store: this.adapter,
|
|
16578
|
+
vault: this.name,
|
|
16579
|
+
collectionName,
|
|
16580
|
+
validator,
|
|
16581
|
+
dek,
|
|
16582
|
+
strategies
|
|
16583
|
+
});
|
|
16584
|
+
const decision = result.decision ?? { action: "allow" };
|
|
16585
|
+
if (decision.action === "cutover") {
|
|
16586
|
+
this.schemaFence.registerPendingCutover(collectionName, decision.transform);
|
|
16587
|
+
this._ensureFenceCoordination();
|
|
16588
|
+
}
|
|
16589
|
+
return decision;
|
|
16590
|
+
})();
|
|
16591
|
+
this._pendingSchemaWrites.push(work.then(() => {
|
|
16592
|
+
}, () => {
|
|
16593
|
+
}));
|
|
16594
|
+
schemaUpdateGate = new SchemaUpdateGate(work);
|
|
16595
|
+
}
|
|
15997
16596
|
const collOpts = {
|
|
15998
16597
|
adapter: this.adapter,
|
|
15999
16598
|
vault: this.name,
|
|
@@ -16001,6 +16600,11 @@ var Vault = class {
|
|
|
16001
16600
|
keyring: this.keyring,
|
|
16002
16601
|
encrypted: this.encrypted,
|
|
16003
16602
|
emitter: this.emitter,
|
|
16603
|
+
writeQueue: this.noydb._writeQueueTracker,
|
|
16604
|
+
writeHooks: this.noydb._writeHooks,
|
|
16605
|
+
activeTxId: () => this.noydb._activeTxContextOrNull?.txId ?? null,
|
|
16606
|
+
schemaUpdateGate,
|
|
16607
|
+
schemaFence: this.schemaFence,
|
|
16004
16608
|
getDEK: this.getDEK,
|
|
16005
16609
|
onDirty: this.onDirty,
|
|
16006
16610
|
historyConfig: this.historyConfig,
|
|
@@ -16087,7 +16691,7 @@ var Vault = class {
|
|
|
16087
16691
|
}
|
|
16088
16692
|
coll = new Collection(collOpts);
|
|
16089
16693
|
this.collectionCache.set(collectionName, coll);
|
|
16090
|
-
if (options?.persistJsonSchema === true && options.schema !== void 0) {
|
|
16694
|
+
if (options?.persistJsonSchema === true && options.schema !== void 0 && (options.schemaUpdate?.length ?? 0) === 0) {
|
|
16091
16695
|
const validator = options.schema;
|
|
16092
16696
|
const work = (async () => {
|
|
16093
16697
|
try {
|
|
@@ -16120,6 +16724,87 @@ var Vault = class {
|
|
|
16120
16724
|
this._pendingSchemaWrites = [];
|
|
16121
16725
|
await Promise.allSettled(pending);
|
|
16122
16726
|
}
|
|
16727
|
+
/**
|
|
16728
|
+
* Run a coordinated schema cutover (#232). Drains pending writes, waits
|
|
16729
|
+
* for the active client set to quiesce (the ack-barrier), applies every
|
|
16730
|
+
* pending collection transform in bulk, bumps the vault schema generation,
|
|
16731
|
+
* and clears the fence. Returns the count of collections migrated.
|
|
16732
|
+
* `opts.onPoll` (tests) advances other clients between barrier checks.
|
|
16733
|
+
*/
|
|
16734
|
+
async runSchemaCutover(opts) {
|
|
16735
|
+
return this.schemaFence.runCutover(
|
|
16736
|
+
(collectionName, transform) => this.#runCutoverTransform(collectionName, transform),
|
|
16737
|
+
opts
|
|
16738
|
+
);
|
|
16739
|
+
}
|
|
16740
|
+
async #runCutoverTransform(collectionName, transform) {
|
|
16741
|
+
const coll = this.collectionCache.get(collectionName);
|
|
16742
|
+
if (!coll) return;
|
|
16743
|
+
await coll._applyCutoverTransform(transform);
|
|
16744
|
+
}
|
|
16745
|
+
/**
|
|
16746
|
+
* #228b — refresh a loaded collection's view of one document from a peer
|
|
16747
|
+
* tab's broadcast. No-op when the collection isn't loaded in this tab
|
|
16748
|
+
* (it will read fresh on next open). Mirrors #runCutoverTransform's guard.
|
|
16749
|
+
*/
|
|
16750
|
+
async _applyRemoteWrite(collectionName, docId, action) {
|
|
16751
|
+
const coll = this.collectionCache.get(collectionName);
|
|
16752
|
+
if (!coll) return;
|
|
16753
|
+
await coll._applyRemoteChange(docId, action);
|
|
16754
|
+
}
|
|
16755
|
+
/**
|
|
16756
|
+
* #228c — for a detected conflict: capture this tab's clobbered record,
|
|
16757
|
+
* read the common ancestor from history, converge the cache to the store's
|
|
16758
|
+
* authoritative value (the (b) re-read), and return all three for the
|
|
16759
|
+
* WriteConflict payload. Returns null when the collection isn't loaded.
|
|
16760
|
+
*/
|
|
16761
|
+
async _captureAndConverge(collectionName, docId, action, baseV) {
|
|
16762
|
+
const coll = this.collectionCache.get(collectionName);
|
|
16763
|
+
if (!coll) return null;
|
|
16764
|
+
const local = coll._peekCached(docId);
|
|
16765
|
+
let base = null;
|
|
16766
|
+
try {
|
|
16767
|
+
base = await coll.getVersion(docId, baseV);
|
|
16768
|
+
} catch {
|
|
16769
|
+
base = null;
|
|
16770
|
+
}
|
|
16771
|
+
await coll._applyRemoteChange(docId, action);
|
|
16772
|
+
const remote = await coll.get(docId);
|
|
16773
|
+
return { local, remote, base };
|
|
16774
|
+
}
|
|
16775
|
+
/** Recover a stuck cutover fence (#232) — reset to normal without bumping. */
|
|
16776
|
+
async abortSchemaCutover() {
|
|
16777
|
+
await this.schemaFence.abort();
|
|
16778
|
+
}
|
|
16779
|
+
/** Current schema-cutover fence state for this vault (#232/#233). Thin live read. */
|
|
16780
|
+
async schemaFenceState() {
|
|
16781
|
+
return loadFence(this.adapter, this.name);
|
|
16782
|
+
}
|
|
16783
|
+
/** @internal Start the per-client heartbeat + fence watcher once a cutover is registered (#232). */
|
|
16784
|
+
_ensureFenceCoordination() {
|
|
16785
|
+
if (this.#fenceCoordinationStarted) return;
|
|
16786
|
+
this.#fenceCoordinationStarted = true;
|
|
16787
|
+
this.#fenceWatcher = new FenceWatcher({
|
|
16788
|
+
store: this.adapter,
|
|
16789
|
+
vault: this.name,
|
|
16790
|
+
clientId: this.noydb._clientId,
|
|
16791
|
+
onFlush: () => this.noydb._writeQueueTracker.onFlush(),
|
|
16792
|
+
emit: (e) => this.emitter.emit("schema:fence-changed", { vault: this.name, ...e })
|
|
16793
|
+
});
|
|
16794
|
+
this.#fenceWatcher.start(2e3);
|
|
16795
|
+
}
|
|
16796
|
+
/** @internal Stop the heartbeat/watcher (vault lock/close). */
|
|
16797
|
+
_stopFenceCoordination() {
|
|
16798
|
+
this.#fenceWatcher?.stop();
|
|
16799
|
+
this.#fenceWatcher = void 0;
|
|
16800
|
+
this.#fenceCoordinationStarted = false;
|
|
16801
|
+
}
|
|
16802
|
+
/** @internal Drive one heartbeat + watch cycle deterministically (tests). */
|
|
16803
|
+
async _fenceTick() {
|
|
16804
|
+
this._ensureFenceCoordination();
|
|
16805
|
+
await this.#fenceWatcher.beat();
|
|
16806
|
+
await this.#fenceWatcher.check();
|
|
16807
|
+
}
|
|
16123
16808
|
/**
|
|
16124
16809
|
* Validate i18nText fields on a `put()`. Called by Collection just
|
|
16125
16810
|
* before the adapter write, after schema validation. Throws
|
|
@@ -17548,6 +18233,27 @@ var Vault = class {
|
|
|
17548
18233
|
async dumpSchema(opts = {}) {
|
|
17549
18234
|
return dumpVaultSchema(this, opts);
|
|
17550
18235
|
}
|
|
18236
|
+
/**
|
|
18237
|
+
* Lightweight read of the vault's registered schema (#229): collections
|
|
18238
|
+
* (+ doc counts), guards, materialized views, schema-update strategies,
|
|
18239
|
+
* and the unlocked user's grants. Cheap — one `adapter.list` per
|
|
18240
|
+
* collection, no decryption. For a full snapshot + stats use dumpSchema().
|
|
18241
|
+
* Post-unlock by construction (a Vault only exists with an unlocked keyring).
|
|
18242
|
+
*/
|
|
18243
|
+
async introspect() {
|
|
18244
|
+
const byCol = (a, b) => a.collection.localeCompare(b.collection);
|
|
18245
|
+
const names = [.../* @__PURE__ */ new Set([...this.collectionCache.keys(), ...await this.collections()])].filter((n) => !n.startsWith("_")).sort((a, b) => a.localeCompare(b));
|
|
18246
|
+
const collections = [];
|
|
18247
|
+
for (const name of names) {
|
|
18248
|
+
const ids = await this.adapter.list(this.name, name);
|
|
18249
|
+
collections.push({ name, docCount: ids.length });
|
|
18250
|
+
}
|
|
18251
|
+
const guards = (this._getGuardRegistry()?.summary() ?? []).slice().sort(byCol);
|
|
18252
|
+
const materializedViews = (this._getMaterializedViewRegistry()?.all() ?? []).map((mv) => ({ name: mv.spec.name, sourceCollections: [...mv.dependencies].sort() })).sort((a, b) => a.name.localeCompare(b.name));
|
|
18253
|
+
const schemaUpdate = [...this.#schemaUpdateNames.entries()].map(([collection, strategies]) => ({ collection, strategies })).sort(byCol);
|
|
18254
|
+
const grants = [...this.keyring.deks.keys()].filter((collection) => !collection.startsWith("_")).map((collection) => ({ collection, permission: this.keyring.permissions[collection] ?? "rw" })).sort(byCol);
|
|
18255
|
+
return { collections, guards, materializedViews, schemaUpdate, grants };
|
|
18256
|
+
}
|
|
17551
18257
|
/**
|
|
17552
18258
|
* Internal accessor for {@link dumpVaultSchema}. Exposes the structural
|
|
17553
18259
|
* state the walker needs (collection cache, registries, ref registry,
|
|
@@ -18187,6 +18893,387 @@ var NoydbEventEmitter = class {
|
|
|
18187
18893
|
}
|
|
18188
18894
|
};
|
|
18189
18895
|
|
|
18896
|
+
// src/write-queue.ts
|
|
18897
|
+
var WriteQueueTracker = class {
|
|
18898
|
+
#depth = 0;
|
|
18899
|
+
#error = null;
|
|
18900
|
+
#changeHandlers = /* @__PURE__ */ new Set();
|
|
18901
|
+
#flushWaiters = [];
|
|
18902
|
+
get pending() {
|
|
18903
|
+
return this.#depth > 0;
|
|
18904
|
+
}
|
|
18905
|
+
get depth() {
|
|
18906
|
+
return this.#depth;
|
|
18907
|
+
}
|
|
18908
|
+
/** Mark one write as started. */
|
|
18909
|
+
begin() {
|
|
18910
|
+
this.#depth++;
|
|
18911
|
+
this.#emitChange();
|
|
18912
|
+
}
|
|
18913
|
+
/** Mark one write as finished. Pass the error if it failed. */
|
|
18914
|
+
settle(error) {
|
|
18915
|
+
this.#depth = Math.max(0, this.#depth - 1);
|
|
18916
|
+
if (error) this.#error = error;
|
|
18917
|
+
this.#emitChange();
|
|
18918
|
+
if (this.#depth === 0) this.#drainFlush();
|
|
18919
|
+
}
|
|
18920
|
+
onChange(handler) {
|
|
18921
|
+
this.#changeHandlers.add(handler);
|
|
18922
|
+
return () => {
|
|
18923
|
+
this.#changeHandlers.delete(handler);
|
|
18924
|
+
};
|
|
18925
|
+
}
|
|
18926
|
+
onFlush() {
|
|
18927
|
+
if (this.#depth === 0) {
|
|
18928
|
+
const error = this.#error;
|
|
18929
|
+
this.#error = null;
|
|
18930
|
+
return error ? Promise.reject(error) : Promise.resolve();
|
|
18931
|
+
}
|
|
18932
|
+
return new Promise((resolve, reject) => {
|
|
18933
|
+
this.#flushWaiters.push({ resolve, reject });
|
|
18934
|
+
});
|
|
18935
|
+
}
|
|
18936
|
+
/**
|
|
18937
|
+
* Run `fn` as a tracked write: depth++ on entry, depth-- on settle
|
|
18938
|
+
* (success or failure). The fn's resolved value is returned; a thrown
|
|
18939
|
+
* error is re-thrown after the queue is decremented.
|
|
18940
|
+
*/
|
|
18941
|
+
async track(fn) {
|
|
18942
|
+
this.begin();
|
|
18943
|
+
try {
|
|
18944
|
+
const value = await fn();
|
|
18945
|
+
this.settle();
|
|
18946
|
+
return value;
|
|
18947
|
+
} catch (error) {
|
|
18948
|
+
this.settle(error);
|
|
18949
|
+
throw error;
|
|
18950
|
+
}
|
|
18951
|
+
}
|
|
18952
|
+
#emitChange() {
|
|
18953
|
+
for (const handler of this.#changeHandlers) handler();
|
|
18954
|
+
}
|
|
18955
|
+
#drainFlush() {
|
|
18956
|
+
const waiters = this.#flushWaiters;
|
|
18957
|
+
this.#flushWaiters = [];
|
|
18958
|
+
const error = this.#error;
|
|
18959
|
+
this.#error = null;
|
|
18960
|
+
for (const waiter of waiters) {
|
|
18961
|
+
if (error) waiter.reject(error);
|
|
18962
|
+
else waiter.resolve();
|
|
18963
|
+
}
|
|
18964
|
+
}
|
|
18965
|
+
};
|
|
18966
|
+
|
|
18967
|
+
// src/write-hooks.ts
|
|
18968
|
+
var WriteHookRegistry = class {
|
|
18969
|
+
#before = [];
|
|
18970
|
+
#after = [];
|
|
18971
|
+
#suppressed = false;
|
|
18972
|
+
/** True while handlers are running — used by the write path to skip nested firing. */
|
|
18973
|
+
get suppressed() {
|
|
18974
|
+
return this.#suppressed;
|
|
18975
|
+
}
|
|
18976
|
+
/** True when any hook is registered (cheap gate for the write path). */
|
|
18977
|
+
get hasHandlers() {
|
|
18978
|
+
return this.#before.length > 0 || this.#after.length > 0;
|
|
18979
|
+
}
|
|
18980
|
+
onBeforeWrite(handler) {
|
|
18981
|
+
this.#before.push(handler);
|
|
18982
|
+
return () => {
|
|
18983
|
+
const i = this.#before.indexOf(handler);
|
|
18984
|
+
if (i >= 0) this.#before.splice(i, 1);
|
|
18985
|
+
};
|
|
18986
|
+
}
|
|
18987
|
+
onAfterWrite(handler) {
|
|
18988
|
+
this.#after.push(handler);
|
|
18989
|
+
return () => {
|
|
18990
|
+
const i = this.#after.indexOf(handler);
|
|
18991
|
+
if (i >= 0) this.#after.splice(i, 1);
|
|
18992
|
+
};
|
|
18993
|
+
}
|
|
18994
|
+
/** Run before-hooks (awaited, in order). A throw propagates and aborts the write. */
|
|
18995
|
+
async runBefore(event) {
|
|
18996
|
+
if (this.#before.length === 0) return;
|
|
18997
|
+
this.#suppressed = true;
|
|
18998
|
+
try {
|
|
18999
|
+
for (const h of this.#before.slice()) await h(event);
|
|
19000
|
+
} finally {
|
|
19001
|
+
this.#suppressed = false;
|
|
19002
|
+
}
|
|
19003
|
+
}
|
|
19004
|
+
/** Run after-hooks (awaited, in order). Per-handler errors are warned, not thrown. */
|
|
19005
|
+
async runAfter(event) {
|
|
19006
|
+
if (this.#after.length === 0) return;
|
|
19007
|
+
this.#suppressed = true;
|
|
19008
|
+
try {
|
|
19009
|
+
for (const h of this.#after.slice()) {
|
|
19010
|
+
try {
|
|
19011
|
+
await h(event);
|
|
19012
|
+
} catch (err) {
|
|
19013
|
+
console.warn(
|
|
19014
|
+
`[noy-db] onAfterWrite handler failed for ${event.collection}/${event.docId}: ` + (err instanceof Error ? err.message : String(err))
|
|
19015
|
+
);
|
|
19016
|
+
}
|
|
19017
|
+
}
|
|
19018
|
+
} finally {
|
|
19019
|
+
this.#suppressed = false;
|
|
19020
|
+
}
|
|
19021
|
+
}
|
|
19022
|
+
};
|
|
19023
|
+
|
|
19024
|
+
// src/tab-coordination.ts
|
|
19025
|
+
var TabCoordinator = class {
|
|
19026
|
+
tabId;
|
|
19027
|
+
role = "unknown";
|
|
19028
|
+
#lockManager;
|
|
19029
|
+
#channel;
|
|
19030
|
+
#lockName;
|
|
19031
|
+
#heartbeatMs;
|
|
19032
|
+
#staleMs;
|
|
19033
|
+
#now;
|
|
19034
|
+
#peers = /* @__PURE__ */ new Map();
|
|
19035
|
+
#roleHandlers = /* @__PURE__ */ new Set();
|
|
19036
|
+
#tabsHandlers = /* @__PURE__ */ new Set();
|
|
19037
|
+
#ac;
|
|
19038
|
+
#releaseLock;
|
|
19039
|
+
#unsub;
|
|
19040
|
+
#closeUnsub;
|
|
19041
|
+
#timer;
|
|
19042
|
+
#ownsChannel;
|
|
19043
|
+
#started = false;
|
|
19044
|
+
#disposed = false;
|
|
19045
|
+
#lastTabsSig = "";
|
|
19046
|
+
constructor(opts = {}) {
|
|
19047
|
+
this.tabId = opts.tabId ?? `tab-${Math.trunc((opts.now ?? (() => 0))())}-${cheapRand()}`;
|
|
19048
|
+
this.#lockManager = opts.lockManager;
|
|
19049
|
+
this.#channel = opts.channel;
|
|
19050
|
+
this.#lockName = opts.lockName ?? "noydb:tab-primary";
|
|
19051
|
+
this.#heartbeatMs = opts.heartbeatMs ?? 2e3;
|
|
19052
|
+
this.#staleMs = opts.staleMs ?? 6e3;
|
|
19053
|
+
this.#now = opts.now ?? (() => Date.now());
|
|
19054
|
+
this.#ownsChannel = opts.closeChannelOnDispose ?? false;
|
|
19055
|
+
}
|
|
19056
|
+
start() {
|
|
19057
|
+
if (this.#disposed || this.#started) return;
|
|
19058
|
+
this.#started = true;
|
|
19059
|
+
if (this.#channel) {
|
|
19060
|
+
this.#unsub = this.#channel.on("message", (p) => this.#onMessage(p));
|
|
19061
|
+
this.#closeUnsub = this.#channel.on("close", () => this.#onChannelClose());
|
|
19062
|
+
this.#beat();
|
|
19063
|
+
this.#timer = setInterval(() => this.#tick(), this.#heartbeatMs);
|
|
19064
|
+
const t = this.#timer;
|
|
19065
|
+
if (typeof t.unref === "function") t.unref();
|
|
19066
|
+
}
|
|
19067
|
+
if (this.#lockManager) {
|
|
19068
|
+
this.#ac = new AbortController();
|
|
19069
|
+
this.#setRole("secondary");
|
|
19070
|
+
void this.#lockManager.request(this.#lockName, { mode: "exclusive", signal: this.#ac.signal }, () => {
|
|
19071
|
+
this.#setRole("primary");
|
|
19072
|
+
return new Promise((resolve) => {
|
|
19073
|
+
this.#releaseLock = resolve;
|
|
19074
|
+
});
|
|
19075
|
+
}).catch(() => {
|
|
19076
|
+
});
|
|
19077
|
+
}
|
|
19078
|
+
}
|
|
19079
|
+
activeTabs() {
|
|
19080
|
+
if (!this.#channel) return [];
|
|
19081
|
+
const cutoff = this.#now() - this.#staleMs;
|
|
19082
|
+
const self = { tabId: this.tabId, lastSeen: this.#now(), role: this.role };
|
|
19083
|
+
const out = [self, ...[...this.#peers.values()].filter((p) => p.lastSeen >= cutoff)];
|
|
19084
|
+
return out.sort((a, b) => a.tabId.localeCompare(b.tabId));
|
|
19085
|
+
}
|
|
19086
|
+
onTabRoleChange(fn) {
|
|
19087
|
+
this.#roleHandlers.add(fn);
|
|
19088
|
+
return () => this.#roleHandlers.delete(fn);
|
|
19089
|
+
}
|
|
19090
|
+
onActiveTabsChange(fn) {
|
|
19091
|
+
this.#tabsHandlers.add(fn);
|
|
19092
|
+
return () => this.#tabsHandlers.delete(fn);
|
|
19093
|
+
}
|
|
19094
|
+
dispose() {
|
|
19095
|
+
if (this.#disposed) return;
|
|
19096
|
+
this.#disposed = true;
|
|
19097
|
+
this.#releaseLock?.();
|
|
19098
|
+
this.#ac?.abort();
|
|
19099
|
+
if (this.#timer) {
|
|
19100
|
+
clearInterval(this.#timer);
|
|
19101
|
+
this.#timer = void 0;
|
|
19102
|
+
}
|
|
19103
|
+
this.#unsub?.();
|
|
19104
|
+
this.#closeUnsub?.();
|
|
19105
|
+
if (this.#ownsChannel) this.#channel?.close();
|
|
19106
|
+
this.#setRole("unknown");
|
|
19107
|
+
}
|
|
19108
|
+
/** @internal test seam — broadcast one heartbeat now. */
|
|
19109
|
+
_beat() {
|
|
19110
|
+
this.#beat();
|
|
19111
|
+
}
|
|
19112
|
+
#tick() {
|
|
19113
|
+
this.#prune();
|
|
19114
|
+
this.#emitTabs();
|
|
19115
|
+
this.#beat();
|
|
19116
|
+
}
|
|
19117
|
+
#beat() {
|
|
19118
|
+
if (this.#disposed) return;
|
|
19119
|
+
if (!this.#channel || !this.#channel.isOpen) return;
|
|
19120
|
+
const msg = { kind: "tab-presence", tabId: this.tabId, lastSeen: this.#now(), role: this.role };
|
|
19121
|
+
this.#channel.send(JSON.stringify(msg));
|
|
19122
|
+
}
|
|
19123
|
+
#onChannelClose() {
|
|
19124
|
+
if (this.#timer) {
|
|
19125
|
+
clearInterval(this.#timer);
|
|
19126
|
+
this.#timer = void 0;
|
|
19127
|
+
}
|
|
19128
|
+
this.#setRole("unknown");
|
|
19129
|
+
}
|
|
19130
|
+
#onMessage(payload) {
|
|
19131
|
+
let msg;
|
|
19132
|
+
try {
|
|
19133
|
+
msg = JSON.parse(payload);
|
|
19134
|
+
} catch {
|
|
19135
|
+
return;
|
|
19136
|
+
}
|
|
19137
|
+
if (!isPresenceMsg(msg) || msg.tabId === this.tabId) return;
|
|
19138
|
+
this.#peers.set(msg.tabId, { tabId: msg.tabId, lastSeen: msg.lastSeen, role: msg.role });
|
|
19139
|
+
this.#prune();
|
|
19140
|
+
this.#emitTabs();
|
|
19141
|
+
}
|
|
19142
|
+
#prune() {
|
|
19143
|
+
const cutoff = this.#now() - this.#staleMs;
|
|
19144
|
+
for (const [id, p] of this.#peers) if (p.lastSeen < cutoff) this.#peers.delete(id);
|
|
19145
|
+
}
|
|
19146
|
+
#setRole(role) {
|
|
19147
|
+
if (this.role === role) return;
|
|
19148
|
+
this.role = role;
|
|
19149
|
+
for (const h of this.#roleHandlers) h(role);
|
|
19150
|
+
this.#beat();
|
|
19151
|
+
this.#emitTabs();
|
|
19152
|
+
}
|
|
19153
|
+
#emitTabs() {
|
|
19154
|
+
const tabs = this.activeTabs();
|
|
19155
|
+
const sig = tabs.map((t) => `${t.tabId}:${t.role}`).join("|");
|
|
19156
|
+
if (sig === this.#lastTabsSig) return;
|
|
19157
|
+
this.#lastTabsSig = sig;
|
|
19158
|
+
for (const h of this.#tabsHandlers) h(tabs);
|
|
19159
|
+
}
|
|
19160
|
+
};
|
|
19161
|
+
function isPresenceMsg(x) {
|
|
19162
|
+
if (x === null || typeof x !== "object") return false;
|
|
19163
|
+
const o = x;
|
|
19164
|
+
return o["kind"] === "tab-presence" && typeof o["tabId"] === "string" && typeof o["lastSeen"] === "number" && (o["role"] === "primary" || o["role"] === "secondary" || o["role"] === "unknown");
|
|
19165
|
+
}
|
|
19166
|
+
function cheapRand() {
|
|
19167
|
+
const g = globalThis;
|
|
19168
|
+
return g.crypto?.randomUUID ? g.crypto.randomUUID().slice(0, 8) : "anon";
|
|
19169
|
+
}
|
|
19170
|
+
function defaultLockManager() {
|
|
19171
|
+
const nav = globalThis.navigator;
|
|
19172
|
+
return nav?.locks;
|
|
19173
|
+
}
|
|
19174
|
+
function defaultChannel(name = "noydb:tabs") {
|
|
19175
|
+
if (typeof globalThis.window === "undefined") return void 0;
|
|
19176
|
+
const Bc = globalThis.BroadcastChannel;
|
|
19177
|
+
if (!Bc) return void 0;
|
|
19178
|
+
const bc = new Bc(name);
|
|
19179
|
+
const msgListeners = /* @__PURE__ */ new Set();
|
|
19180
|
+
bc.onmessage = (e) => {
|
|
19181
|
+
for (const l of msgListeners) l(String(e.data));
|
|
19182
|
+
};
|
|
19183
|
+
return {
|
|
19184
|
+
isOpen: true,
|
|
19185
|
+
send(payload) {
|
|
19186
|
+
bc.postMessage(payload);
|
|
19187
|
+
},
|
|
19188
|
+
on(event, listener) {
|
|
19189
|
+
if (event === "message") {
|
|
19190
|
+
const l = listener;
|
|
19191
|
+
msgListeners.add(l);
|
|
19192
|
+
return () => msgListeners.delete(l);
|
|
19193
|
+
}
|
|
19194
|
+
return () => {
|
|
19195
|
+
};
|
|
19196
|
+
},
|
|
19197
|
+
close() {
|
|
19198
|
+
msgListeners.clear();
|
|
19199
|
+
bc.close();
|
|
19200
|
+
}
|
|
19201
|
+
};
|
|
19202
|
+
}
|
|
19203
|
+
|
|
19204
|
+
// src/tab-write-relay.ts
|
|
19205
|
+
var CrossTabWriteRelay = class {
|
|
19206
|
+
#channel;
|
|
19207
|
+
#writerId;
|
|
19208
|
+
#subscribeAfterWrite;
|
|
19209
|
+
#applyRemoteWrite;
|
|
19210
|
+
#reportConflict;
|
|
19211
|
+
#ledger = /* @__PURE__ */ new Map();
|
|
19212
|
+
#ownsChannel;
|
|
19213
|
+
#unsubMsg;
|
|
19214
|
+
#unsubWrite;
|
|
19215
|
+
#started = false;
|
|
19216
|
+
#disposed = false;
|
|
19217
|
+
constructor(opts) {
|
|
19218
|
+
this.#channel = opts.channel;
|
|
19219
|
+
this.#writerId = opts.writerId;
|
|
19220
|
+
this.#subscribeAfterWrite = opts.subscribeAfterWrite;
|
|
19221
|
+
this.#applyRemoteWrite = opts.applyRemoteWrite;
|
|
19222
|
+
this.#reportConflict = opts.reportConflict;
|
|
19223
|
+
this.#ownsChannel = opts.closeChannelOnDispose ?? false;
|
|
19224
|
+
}
|
|
19225
|
+
start() {
|
|
19226
|
+
if (this.#started || this.#disposed) return;
|
|
19227
|
+
this.#started = true;
|
|
19228
|
+
this.#unsubMsg = this.#channel.on("message", (p) => this.#onMessage(p));
|
|
19229
|
+
this.#unsubWrite = this.#subscribeAfterWrite((e) => this.#onLocalWrite(e));
|
|
19230
|
+
}
|
|
19231
|
+
dispose() {
|
|
19232
|
+
if (this.#disposed) return;
|
|
19233
|
+
this.#disposed = true;
|
|
19234
|
+
this.#unsubWrite?.();
|
|
19235
|
+
this.#unsubMsg?.();
|
|
19236
|
+
if (this.#ownsChannel) this.#channel.close();
|
|
19237
|
+
}
|
|
19238
|
+
#onLocalWrite(e) {
|
|
19239
|
+
if (this.#disposed || !this.#channel.isOpen) return;
|
|
19240
|
+
this.#ledger.set(ledgerKey(e.vault, e.collection, e.docId), e.version);
|
|
19241
|
+
const action = e.op === "delete" ? "delete" : "put";
|
|
19242
|
+
const msg = { kind: "tab-write", writerId: this.#writerId, vault: e.vault, collection: e.collection, docId: e.docId, action, baseV: e.baseVersion, v: e.version };
|
|
19243
|
+
this.#channel.send(JSON.stringify(msg));
|
|
19244
|
+
}
|
|
19245
|
+
#onMessage(payload) {
|
|
19246
|
+
if (this.#disposed) return;
|
|
19247
|
+
let msg;
|
|
19248
|
+
try {
|
|
19249
|
+
msg = JSON.parse(payload);
|
|
19250
|
+
} catch {
|
|
19251
|
+
return;
|
|
19252
|
+
}
|
|
19253
|
+
if (!isTabWriteMsg(msg) || msg.writerId === this.#writerId) return;
|
|
19254
|
+
const key = ledgerKey(msg.vault, msg.collection, msg.docId);
|
|
19255
|
+
const ownV = this.#ledger.get(key);
|
|
19256
|
+
if (ownV !== void 0 && msg.baseV < ownV && this.#reportConflict) {
|
|
19257
|
+
void Promise.resolve(this.#reportConflict(msg.vault, msg.collection, msg.docId, msg.action, msg.baseV, msg.v, ownV)).catch((err) => {
|
|
19258
|
+
console.warn(`[noy-db] cross-tab conflict report failed for ${msg.collection}/${msg.docId}: ` + (err instanceof Error ? err.message : String(err)));
|
|
19259
|
+
});
|
|
19260
|
+
return;
|
|
19261
|
+
}
|
|
19262
|
+
if (ownV !== void 0 && msg.baseV >= ownV) this.#ledger.set(key, msg.v);
|
|
19263
|
+
void Promise.resolve(this.#applyRemoteWrite(msg.vault, msg.collection, msg.docId, msg.action)).catch((err) => {
|
|
19264
|
+
console.warn(`[noy-db] cross-tab apply failed for ${msg.collection}/${msg.docId}: ` + (err instanceof Error ? err.message : String(err)));
|
|
19265
|
+
});
|
|
19266
|
+
}
|
|
19267
|
+
};
|
|
19268
|
+
function ledgerKey(vault, collection, docId) {
|
|
19269
|
+
return `${vault}\0${collection}\0${docId}`;
|
|
19270
|
+
}
|
|
19271
|
+
function isTabWriteMsg(x) {
|
|
19272
|
+
if (x === null || typeof x !== "object") return false;
|
|
19273
|
+
const o = x;
|
|
19274
|
+
return o["kind"] === "tab-write" && typeof o["writerId"] === "string" && typeof o["vault"] === "string" && typeof o["collection"] === "string" && typeof o["docId"] === "string" && (o["action"] === "put" || o["action"] === "delete") && typeof o["baseV"] === "number" && typeof o["v"] === "number";
|
|
19275
|
+
}
|
|
19276
|
+
|
|
18190
19277
|
// src/tx/strategy.ts
|
|
18191
19278
|
var NOT_ENABLED5 = new Error(
|
|
18192
19279
|
'Multi-record transactions require the tx strategy. Import `{ withTransactions }` from "@noy-db/hub/tx" and pass it to `createNoydb({ txStrategy: withTransactions() })`.'
|
|
@@ -18194,6 +19281,9 @@ var NOT_ENABLED5 = new Error(
|
|
|
18194
19281
|
var NO_TX = {
|
|
18195
19282
|
async runTransaction() {
|
|
18196
19283
|
throw NOT_ENABLED5;
|
|
19284
|
+
},
|
|
19285
|
+
async runDryRun() {
|
|
19286
|
+
throw NOT_ENABLED5;
|
|
18197
19287
|
}
|
|
18198
19288
|
};
|
|
18199
19289
|
|
|
@@ -18491,6 +19581,9 @@ function createPlaintextKeyring(userId) {
|
|
|
18491
19581
|
var Noydb = class {
|
|
18492
19582
|
options;
|
|
18493
19583
|
emitter = new NoydbEventEmitter();
|
|
19584
|
+
writeQueueTracker = new WriteQueueTracker();
|
|
19585
|
+
writeHooks = new WriteHookRegistry();
|
|
19586
|
+
clientId = generateULID();
|
|
18494
19587
|
vaultCache = /* @__PURE__ */ new Map();
|
|
18495
19588
|
keyringCache = /* @__PURE__ */ new Map();
|
|
18496
19589
|
syncEngines = /* @__PURE__ */ new Map();
|
|
@@ -18523,6 +19616,10 @@ var Noydb = class {
|
|
|
18523
19616
|
publicEnvelopeSchema;
|
|
18524
19617
|
closed = false;
|
|
18525
19618
|
sessionTimer = null;
|
|
19619
|
+
/** Same-device multi-tab coordinator (#228); created on `enableTabCoordination()`. */
|
|
19620
|
+
tabCoordinator;
|
|
19621
|
+
/** Cross-tab write relay (#228b); created on `enableTabCoordination()`. */
|
|
19622
|
+
writeRelay;
|
|
18526
19623
|
/** Per-vault policy enforcers. */
|
|
18527
19624
|
policyEnforcers = /* @__PURE__ */ new Map();
|
|
18528
19625
|
txStrategy;
|
|
@@ -18715,6 +19812,7 @@ var Noydb = class {
|
|
|
18715
19812
|
await comp._initDerivations(this.options.derivationStrategies ?? []);
|
|
18716
19813
|
await comp._initMaterializedViews(this.options.materializedViewStrategies ?? []);
|
|
18717
19814
|
await comp._initOverlayedViews(this.options.overlayedViewStrategies ?? []);
|
|
19815
|
+
await comp.schemaFence.init();
|
|
18718
19816
|
this.vaultCache.set(name, comp);
|
|
18719
19817
|
return comp;
|
|
18720
19818
|
}
|
|
@@ -19142,6 +20240,14 @@ var Noydb = class {
|
|
|
19142
20240
|
if (typeof arg === "function") {
|
|
19143
20241
|
return this.txStrategy.runTransaction(this, arg);
|
|
19144
20242
|
}
|
|
20243
|
+
if (typeof arg === "object" && arg !== null && arg.dryRun === true) {
|
|
20244
|
+
if (typeof maybeFn !== "function") {
|
|
20245
|
+
throw new ValidationError(
|
|
20246
|
+
"db.transaction({ dryRun: true }, fn) requires the callback as the second argument."
|
|
20247
|
+
);
|
|
20248
|
+
}
|
|
20249
|
+
return this.txStrategy.runDryRun(this, maybeFn);
|
|
20250
|
+
}
|
|
19145
20251
|
if (typeof arg === "object" && arg !== null && arg.amendment === true) {
|
|
19146
20252
|
if (typeof maybeFn !== "function") {
|
|
19147
20253
|
throw new ValidationError(
|
|
@@ -19254,6 +20360,133 @@ var Noydb = class {
|
|
|
19254
20360
|
off(event, handler) {
|
|
19255
20361
|
this.emitter.off(event, handler);
|
|
19256
20362
|
}
|
|
20363
|
+
/**
|
|
20364
|
+
* Observable write-queue for this hub instance. Reflects outstanding
|
|
20365
|
+
* in-flight writes across all collections. See {@link WriteQueue}.
|
|
20366
|
+
*
|
|
20367
|
+
* @example
|
|
20368
|
+
* window.addEventListener('beforeunload', (e) => {
|
|
20369
|
+
* if (db.writeQueue.pending) { e.preventDefault(); e.returnValue = '' }
|
|
20370
|
+
* })
|
|
20371
|
+
*/
|
|
20372
|
+
get writeQueue() {
|
|
20373
|
+
return this.writeQueueTracker;
|
|
20374
|
+
}
|
|
20375
|
+
/**
|
|
20376
|
+
* @internal Mutable tracker behind {@link writeQueue}. Threaded into
|
|
20377
|
+
* each Collection (via Vault) so `put`/`delete` can `track()` writes.
|
|
20378
|
+
* Not part of the public surface — consumers use `writeQueue`.
|
|
20379
|
+
*/
|
|
20380
|
+
get _writeQueueTracker() {
|
|
20381
|
+
return this.writeQueueTracker;
|
|
20382
|
+
}
|
|
20383
|
+
/**
|
|
20384
|
+
* Register a hook that runs before each write (#230). Awaited; a throw
|
|
20385
|
+
* aborts the write. Returns an unsubscribe function.
|
|
20386
|
+
*/
|
|
20387
|
+
onBeforeWrite(handler) {
|
|
20388
|
+
return this.writeHooks.onBeforeWrite(handler);
|
|
20389
|
+
}
|
|
20390
|
+
/**
|
|
20391
|
+
* Register a hook that runs after each committed write (#230). Awaited;
|
|
20392
|
+
* a handler error is warned, never rolled back. Returns an unsubscribe fn.
|
|
20393
|
+
*/
|
|
20394
|
+
onAfterWrite(handler) {
|
|
20395
|
+
return this.writeHooks.onAfterWrite(handler);
|
|
20396
|
+
}
|
|
20397
|
+
/** Subscribe to cross-tab write conflicts (#228c). Returns an unsubscribe. */
|
|
20398
|
+
onWriteConflict(fn) {
|
|
20399
|
+
this.on("write:conflict", fn);
|
|
20400
|
+
return () => this.off("write:conflict", fn);
|
|
20401
|
+
}
|
|
20402
|
+
/**
|
|
20403
|
+
* Enable same-device multi-tab coordination (#228): primary/secondary
|
|
20404
|
+
* election + presence. Browser-only — a graceful no-op (role 'unknown')
|
|
20405
|
+
* when Web Locks / BroadcastChannel are unavailable and nothing is
|
|
20406
|
+
* injected. Idempotent; returns a disposer.
|
|
20407
|
+
*/
|
|
20408
|
+
enableTabCoordination(opts = {}) {
|
|
20409
|
+
if (this.tabCoordinator) return { dispose: () => this.disableTabCoordination() };
|
|
20410
|
+
const lockManager = opts.lockManager ?? defaultLockManager();
|
|
20411
|
+
const channel = opts.channel ?? defaultChannel();
|
|
20412
|
+
const c = new TabCoordinator({
|
|
20413
|
+
...opts,
|
|
20414
|
+
...lockManager ? { lockManager } : {},
|
|
20415
|
+
...channel ? { channel } : {},
|
|
20416
|
+
// We own the channel only when we created the default; never close a caller-injected one.
|
|
20417
|
+
closeChannelOnDispose: opts.channel === void 0 && channel !== void 0
|
|
20418
|
+
});
|
|
20419
|
+
this.tabCoordinator = c;
|
|
20420
|
+
c.start();
|
|
20421
|
+
if (opts.propagateWrites !== false) {
|
|
20422
|
+
const writeChannel = opts.writeChannel ?? defaultChannel("noydb:tab-writes");
|
|
20423
|
+
if (writeChannel) {
|
|
20424
|
+
const relay = new CrossTabWriteRelay({
|
|
20425
|
+
channel: writeChannel,
|
|
20426
|
+
writerId: c.tabId,
|
|
20427
|
+
subscribeAfterWrite: (h) => this.onAfterWrite(h),
|
|
20428
|
+
applyRemoteWrite: (vault, collection, docId, action) => this.#applyRemoteWrite(vault, collection, docId, action),
|
|
20429
|
+
reportConflict: (vault, collection, docId, action, baseV, v, ownV) => this.#reportWriteConflict(vault, collection, docId, action, baseV, v, ownV),
|
|
20430
|
+
// Own the channel only when we created the default (mirrors the presence channel).
|
|
20431
|
+
closeChannelOnDispose: opts.writeChannel === void 0 && writeChannel !== void 0
|
|
20432
|
+
});
|
|
20433
|
+
this.writeRelay = relay;
|
|
20434
|
+
relay.start();
|
|
20435
|
+
}
|
|
20436
|
+
}
|
|
20437
|
+
return { dispose: () => this.disableTabCoordination() };
|
|
20438
|
+
}
|
|
20439
|
+
#applyRemoteWrite(vaultName, collectionName, docId, action) {
|
|
20440
|
+
const v = this.vaultCache.get(vaultName);
|
|
20441
|
+
if (!v) return Promise.resolve();
|
|
20442
|
+
return v._applyRemoteWrite(collectionName, docId, action);
|
|
20443
|
+
}
|
|
20444
|
+
async #reportWriteConflict(vaultName, collectionName, docId, action, baseV, v, ownV) {
|
|
20445
|
+
const vault = this.vaultCache.get(vaultName);
|
|
20446
|
+
if (!vault) return;
|
|
20447
|
+
const cap = await vault._captureAndConverge(collectionName, docId, action, baseV);
|
|
20448
|
+
if (!cap) return;
|
|
20449
|
+
const conflict = {
|
|
20450
|
+
vault: vaultName,
|
|
20451
|
+
collection: collectionName,
|
|
20452
|
+
docId,
|
|
20453
|
+
local: cap.local,
|
|
20454
|
+
remote: cap.remote,
|
|
20455
|
+
base: cap.base,
|
|
20456
|
+
localVersion: ownV,
|
|
20457
|
+
remoteVersion: v,
|
|
20458
|
+
baseVersion: baseV
|
|
20459
|
+
};
|
|
20460
|
+
this.emitter.emit("write:conflict", conflict);
|
|
20461
|
+
}
|
|
20462
|
+
disableTabCoordination() {
|
|
20463
|
+
this.tabCoordinator?.dispose();
|
|
20464
|
+
this.tabCoordinator = void 0;
|
|
20465
|
+
this.writeRelay?.dispose();
|
|
20466
|
+
this.writeRelay = void 0;
|
|
20467
|
+
}
|
|
20468
|
+
get tabRole() {
|
|
20469
|
+
return this.tabCoordinator?.role ?? "unknown";
|
|
20470
|
+
}
|
|
20471
|
+
activeTabs() {
|
|
20472
|
+
return this.tabCoordinator?.activeTabs() ?? [];
|
|
20473
|
+
}
|
|
20474
|
+
onTabRoleChange(fn) {
|
|
20475
|
+
return this.tabCoordinator?.onTabRoleChange(fn) ?? (() => {
|
|
20476
|
+
});
|
|
20477
|
+
}
|
|
20478
|
+
onActiveTabsChange(fn) {
|
|
20479
|
+
return this.tabCoordinator?.onActiveTabsChange(fn) ?? (() => {
|
|
20480
|
+
});
|
|
20481
|
+
}
|
|
20482
|
+
/** @internal The write-hook registry, threaded into each Collection. */
|
|
20483
|
+
get _writeHooks() {
|
|
20484
|
+
return this.writeHooks;
|
|
20485
|
+
}
|
|
20486
|
+
/** @internal Stable per-instance id for schema-cutover coordination (#232). */
|
|
20487
|
+
get _clientId() {
|
|
20488
|
+
return this.clientId;
|
|
20489
|
+
}
|
|
19257
20490
|
/**
|
|
19258
20491
|
* Soft-lock a single vault: clear its in-memory keyring, DEKs, vault
|
|
19259
20492
|
* instance, sync engine, policy enforcer, and active-tier entry —
|
|
@@ -19280,6 +20513,7 @@ var Noydb = class {
|
|
|
19280
20513
|
this.syncEngines.delete(vault);
|
|
19281
20514
|
this.policyEnforcers.get(vault)?.destroy();
|
|
19282
20515
|
this.policyEnforcers.delete(vault);
|
|
20516
|
+
this.vaultCache.get(vault)?._stopFenceCoordination();
|
|
19283
20517
|
this.keyringCache.delete(vault);
|
|
19284
20518
|
this.vaultCache.delete(vault);
|
|
19285
20519
|
this.activeTier.delete(vault);
|
|
@@ -19299,6 +20533,8 @@ var Noydb = class {
|
|
|
19299
20533
|
engine.stopAutoSync();
|
|
19300
20534
|
}
|
|
19301
20535
|
this.syncEngines.clear();
|
|
20536
|
+
for (const v of this.vaultCache.values()) v._stopFenceCoordination();
|
|
20537
|
+
this.disableTabCoordination();
|
|
19302
20538
|
this.keyringCache.clear();
|
|
19303
20539
|
this.vaultCache.clear();
|
|
19304
20540
|
this.activeTier.clear();
|
|
@@ -22101,6 +23337,7 @@ function shortJSON(value) {
|
|
|
22101
23337
|
MaterializedViewTooLargeError,
|
|
22102
23338
|
MemoryRecipientSealer,
|
|
22103
23339
|
MemorySealingKeyProvider,
|
|
23340
|
+
MigrationRequiredError,
|
|
22104
23341
|
MissingTranslationError,
|
|
22105
23342
|
NOYDB_BACKUP_VERSION,
|
|
22106
23343
|
NOYDB_BUNDLE_FORMAT_VERSION,
|
|
@@ -22111,6 +23348,7 @@ function shortJSON(value) {
|
|
|
22111
23348
|
NOYDB_SYNC_VERSION,
|
|
22112
23349
|
NetworkError,
|
|
22113
23350
|
NoAccessError,
|
|
23351
|
+
NonAdditiveSchemaChangeError,
|
|
22114
23352
|
NotFoundError,
|
|
22115
23353
|
Noydb,
|
|
22116
23354
|
NoydbError,
|
|
@@ -22132,6 +23370,7 @@ function shortJSON(value) {
|
|
|
22132
23370
|
PrivilegeEscalationError,
|
|
22133
23371
|
Query,
|
|
22134
23372
|
QuickUnlockStore,
|
|
23373
|
+
QuiesceTimeoutError,
|
|
22135
23374
|
ReadOnlyAtInstantError,
|
|
22136
23375
|
ReadOnlyError,
|
|
22137
23376
|
ReadOnlyFrameError,
|
|
@@ -22147,6 +23386,9 @@ function shortJSON(value) {
|
|
|
22147
23386
|
STRICT_POLICY,
|
|
22148
23387
|
SYNC_CREDENTIALS_COLLECTION,
|
|
22149
23388
|
ScanBuilder,
|
|
23389
|
+
SchemaFenceError,
|
|
23390
|
+
SchemaLockedError,
|
|
23391
|
+
SchemaUpdateError,
|
|
22150
23392
|
SchemaValidationError,
|
|
22151
23393
|
SessionExpiredError,
|
|
22152
23394
|
SessionNotFoundError,
|
|
@@ -22173,6 +23415,7 @@ function shortJSON(value) {
|
|
|
22173
23415
|
VaultInstant,
|
|
22174
23416
|
WeakPassphraseError,
|
|
22175
23417
|
activeSessionCount,
|
|
23418
|
+
additiveOnly,
|
|
22176
23419
|
applyI18nLocale,
|
|
22177
23420
|
applyJoins,
|
|
22178
23421
|
applyPatch,
|
|
@@ -22180,6 +23423,7 @@ function shortJSON(value) {
|
|
|
22180
23423
|
assertTierAccess,
|
|
22181
23424
|
avg,
|
|
22182
23425
|
base64ToBuffer,
|
|
23426
|
+
blindUpdate,
|
|
22183
23427
|
bufferToBase64,
|
|
22184
23428
|
buildLiveQuery,
|
|
22185
23429
|
buildRecipientKeyringFile,
|
|
@@ -22188,6 +23432,7 @@ function shortJSON(value) {
|
|
|
22188
23432
|
checkGate,
|
|
22189
23433
|
clearDevUnlock,
|
|
22190
23434
|
computePatch,
|
|
23435
|
+
coordinatedCutover,
|
|
22191
23436
|
count,
|
|
22192
23437
|
createBundleStore,
|
|
22193
23438
|
createEnforcer,
|
|
@@ -22266,6 +23511,7 @@ function shortJSON(value) {
|
|
|
22266
23511
|
loadShamirRecoveryEntries,
|
|
22267
23512
|
loadUserEnvelope,
|
|
22268
23513
|
loadVaultPolicy,
|
|
23514
|
+
lockSchema,
|
|
22269
23515
|
magicLinkGrantRecordId,
|
|
22270
23516
|
max,
|
|
22271
23517
|
mergeCrdtStates,
|