@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
|
@@ -4,34 +4,34 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
TxContext,
|
|
6
6
|
revertExecuted
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-GVXBHCZ2.js";
|
|
8
8
|
import {
|
|
9
9
|
OverlayedCollection
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-NGSPBLLE.js";
|
|
11
11
|
import {
|
|
12
12
|
LazyQuery,
|
|
13
13
|
decodeIdxId,
|
|
14
14
|
encodeIdxId
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-WRLHNG6H.js";
|
|
16
16
|
import {
|
|
17
17
|
SCHEMAS_COLLECTION,
|
|
18
18
|
loadPersistedSchema,
|
|
19
19
|
resolveManagedSecret,
|
|
20
20
|
savePersistedSchema,
|
|
21
21
|
saveSealedPassphrase
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-4X2S7PBF.js";
|
|
23
23
|
import {
|
|
24
24
|
loadPublicEnvelope,
|
|
25
25
|
readPublicEnvelope,
|
|
26
26
|
savePublicEnvelope,
|
|
27
27
|
validatePublicEnvelopeInput
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-FS7A4XNF.js";
|
|
29
29
|
import {
|
|
30
30
|
PERIODS_COLLECTION
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-GAUBWHAF.js";
|
|
32
32
|
import {
|
|
33
33
|
isDictCollectionName
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-KMI2NBBF.js";
|
|
35
35
|
import {
|
|
36
36
|
ManagedRecoveryNotEnrolledError,
|
|
37
37
|
PolicyDeniedError,
|
|
@@ -53,11 +53,11 @@ import {
|
|
|
53
53
|
saveShamirRecoveryEntries,
|
|
54
54
|
updateAuthenticator,
|
|
55
55
|
writeMagicLinkGrant
|
|
56
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-2EYC3WDT.js";
|
|
57
57
|
import {
|
|
58
58
|
assertTierAccess,
|
|
59
59
|
dekKey
|
|
60
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-6S3LLAQ5.js";
|
|
61
61
|
import {
|
|
62
62
|
USER_ENVELOPE_COLLECTION,
|
|
63
63
|
buildRecipientKeyringFile,
|
|
@@ -81,7 +81,7 @@ import {
|
|
|
81
81
|
rotateKeys,
|
|
82
82
|
saveUserEnvelope,
|
|
83
83
|
updateKeyringIdentity
|
|
84
|
-
} from "./chunk-
|
|
84
|
+
} from "./chunk-TLFUDXVV.js";
|
|
85
85
|
import {
|
|
86
86
|
INDEXED_STORE_POLICY
|
|
87
87
|
} from "./chunk-2QR2PQTT.js";
|
|
@@ -91,30 +91,30 @@ import {
|
|
|
91
91
|
import {
|
|
92
92
|
LEDGER_COLLECTION,
|
|
93
93
|
LEDGER_DELTAS_COLLECTION
|
|
94
|
-
} from "./chunk-
|
|
94
|
+
} from "./chunk-74JEQFMT.js";
|
|
95
95
|
import {
|
|
96
96
|
sha256Hex as sha256Hex2
|
|
97
|
-
} from "./chunk-
|
|
97
|
+
} from "./chunk-NCO2JGKK.js";
|
|
98
98
|
import {
|
|
99
99
|
NO_AGGREGATE,
|
|
100
100
|
Query,
|
|
101
101
|
ScanBuilder
|
|
102
|
-
} from "./chunk-
|
|
102
|
+
} from "./chunk-4UBOTYP5.js";
|
|
103
103
|
import {
|
|
104
104
|
EXPORT_AUDIT_COLLECTION,
|
|
105
105
|
createExportBlobsHandle,
|
|
106
106
|
runCompaction
|
|
107
|
-
} from "./chunk-
|
|
107
|
+
} from "./chunk-LOL725S4.js";
|
|
108
108
|
import {
|
|
109
109
|
NOYDB_BACKUP_VERSION,
|
|
110
110
|
NOYDB_FORMAT_VERSION
|
|
111
|
-
} from "./chunk-
|
|
111
|
+
} from "./chunk-FXQYZNOW.js";
|
|
112
112
|
import {
|
|
113
113
|
decrypt,
|
|
114
114
|
encrypt,
|
|
115
115
|
encryptDeterministic,
|
|
116
116
|
sha256Hex
|
|
117
|
-
} from "./chunk-
|
|
117
|
+
} from "./chunk-UOF74WQY.js";
|
|
118
118
|
import {
|
|
119
119
|
AlreadyElevatedError,
|
|
120
120
|
AttestationError,
|
|
@@ -127,18 +127,21 @@ import {
|
|
|
127
127
|
IndexWriteFailureError,
|
|
128
128
|
InvalidKeyError,
|
|
129
129
|
KeyringCorruptError,
|
|
130
|
+
MigrationRequiredError,
|
|
130
131
|
NoAccessError,
|
|
131
132
|
NoydbError,
|
|
132
133
|
PermissionDeniedError,
|
|
134
|
+
QuiesceTimeoutError,
|
|
133
135
|
ReadOnlyError,
|
|
134
136
|
ReservedCollectionNameError,
|
|
137
|
+
SchemaFenceError,
|
|
135
138
|
SchemaValidationError,
|
|
136
139
|
StoreCapabilityError,
|
|
137
140
|
TierDemoteDeniedError,
|
|
138
141
|
TierNotGrantedError,
|
|
139
142
|
TranslatorNotConfiguredError,
|
|
140
143
|
ValidationError
|
|
141
|
-
} from "./chunk-
|
|
144
|
+
} from "./chunk-YDLAFP36.js";
|
|
142
145
|
|
|
143
146
|
// src/policy/storage.ts
|
|
144
147
|
var META_COLLECTION = "_meta";
|
|
@@ -641,7 +644,7 @@ async function resolveStaleOnRead(accessor, outputCollection, id) {
|
|
|
641
644
|
}
|
|
642
645
|
const sourceWithId = { ...source, id };
|
|
643
646
|
if (DerivationExecutor === null) {
|
|
644
|
-
({ DerivationExecutor } = await import("./executor-
|
|
647
|
+
({ DerivationExecutor } = await import("./executor-GFZFDQXV.js"));
|
|
645
648
|
}
|
|
646
649
|
const ctx = { vault: accessor.getReadOnlyFacade() };
|
|
647
650
|
const result = await DerivationExecutor.run(spec, sourceWithId, 0, strategyHash, ctx);
|
|
@@ -695,6 +698,11 @@ var Collection = class {
|
|
|
695
698
|
keyring;
|
|
696
699
|
encrypted;
|
|
697
700
|
emitter;
|
|
701
|
+
writeQueue;
|
|
702
|
+
schemaUpdateGate;
|
|
703
|
+
schemaFence;
|
|
704
|
+
writeHooks;
|
|
705
|
+
activeTxId;
|
|
698
706
|
getDEK;
|
|
699
707
|
onDirty;
|
|
700
708
|
historyConfig;
|
|
@@ -969,6 +977,11 @@ var Collection = class {
|
|
|
969
977
|
this.keyring = opts.keyring;
|
|
970
978
|
this.encrypted = opts.encrypted;
|
|
971
979
|
this.emitter = opts.emitter;
|
|
980
|
+
this.writeQueue = opts.writeQueue;
|
|
981
|
+
this.schemaUpdateGate = opts.schemaUpdateGate;
|
|
982
|
+
this.schemaFence = opts.schemaFence;
|
|
983
|
+
this.writeHooks = opts.writeHooks;
|
|
984
|
+
this.activeTxId = opts.activeTxId;
|
|
972
985
|
this.blobStrategy = opts.blobStrategy ?? NO_BLOBS;
|
|
973
986
|
this.aggregateStrategy = opts.aggregateStrategy ?? NO_AGGREGATE;
|
|
974
987
|
this.crdtStrategy = opts.crdtStrategy ?? NO_CRDT;
|
|
@@ -1127,7 +1140,7 @@ var Collection = class {
|
|
|
1127
1140
|
}
|
|
1128
1141
|
}
|
|
1129
1142
|
if (this.materializedViewSource !== void 0) {
|
|
1130
|
-
const { resolveStaleMVOnRead } = await import("./stale-
|
|
1143
|
+
const { resolveStaleMVOnRead } = await import("./stale-PAGCS4K5.js");
|
|
1131
1144
|
await resolveStaleMVOnRead(this.materializedViewSource, this.name);
|
|
1132
1145
|
}
|
|
1133
1146
|
let record;
|
|
@@ -1194,7 +1207,8 @@ var Collection = class {
|
|
|
1194
1207
|
return this.syncStrategy.buildPresence(presenceOpts);
|
|
1195
1208
|
}
|
|
1196
1209
|
/**
|
|
1197
|
-
* Create or update a record.
|
|
1210
|
+
* Create or update a record. Runs inside the hub's write-queue tracker
|
|
1211
|
+
* (#227) so `hub.writeQueue.pending` reflects this write.
|
|
1198
1212
|
*
|
|
1199
1213
|
* @param id Record identifier.
|
|
1200
1214
|
* @param record The record body (validated by the collection's schema
|
|
@@ -1205,6 +1219,59 @@ var Collection = class {
|
|
|
1205
1219
|
* `entries.filter(e => e.reason?.startsWith('import:'))`.
|
|
1206
1220
|
*/
|
|
1207
1221
|
async put(id, record, options) {
|
|
1222
|
+
await this.schemaUpdateGate?.assertWritable();
|
|
1223
|
+
await this.schemaFence?.assertWritable(this.name);
|
|
1224
|
+
let event;
|
|
1225
|
+
if (this.#hooksActive()) {
|
|
1226
|
+
const prior = await this.#priorForHook(id);
|
|
1227
|
+
event = {
|
|
1228
|
+
op: prior.record === null ? "create" : "update",
|
|
1229
|
+
vault: this.vault,
|
|
1230
|
+
collection: this.name,
|
|
1231
|
+
docId: id,
|
|
1232
|
+
before: prior.record,
|
|
1233
|
+
after: record,
|
|
1234
|
+
userId: this.keyring.userId,
|
|
1235
|
+
timestamp: Date.now(),
|
|
1236
|
+
txId: this.#txIdForHook(),
|
|
1237
|
+
baseVersion: prior.version,
|
|
1238
|
+
version: prior.version + 1
|
|
1239
|
+
};
|
|
1240
|
+
await this.writeHooks.runBefore(event);
|
|
1241
|
+
}
|
|
1242
|
+
if (this.writeQueue) await this.writeQueue.track(() => this.putInternal(id, record, options));
|
|
1243
|
+
else await this.putInternal(id, record, options);
|
|
1244
|
+
if (event) await this.writeHooks.runAfter(event);
|
|
1245
|
+
}
|
|
1246
|
+
/** @internal #230 — true when hooks should fire for this write (handlers exist, not re-entrant). */
|
|
1247
|
+
#hooksActive() {
|
|
1248
|
+
return this.writeHooks !== void 0 && this.writeHooks.hasHandlers && !this.writeHooks.suppressed;
|
|
1249
|
+
}
|
|
1250
|
+
/**
|
|
1251
|
+
* @internal #230/#228c — resolve the prior record for a hook's `before` and
|
|
1252
|
+
* its version. Critically, this uses the SAME basis `putInternal` writes from
|
|
1253
|
+
* (the in-memory cache in eager mode; lru-then-adapter in lazy) — NOT a fresh
|
|
1254
|
+
* store read — so `baseVersion`/`version` match the version actually written.
|
|
1255
|
+
* A separate store read would diverge once another tab has advanced the shared
|
|
1256
|
+
* store past this tab's cache, breaking #228c conflict detection.
|
|
1257
|
+
*/
|
|
1258
|
+
async #priorForHook(id) {
|
|
1259
|
+
if (this.lazy && this.lru) {
|
|
1260
|
+
const cached2 = this.lru.get(id);
|
|
1261
|
+
if (cached2) return { record: cached2.record, version: cached2.version };
|
|
1262
|
+
const env = await this.adapter.get(this.vault, this.name, id);
|
|
1263
|
+
if (!env) return { record: null, version: 0 };
|
|
1264
|
+
return { record: await this.decryptRecord(env, { skipValidation: true }), version: env._v };
|
|
1265
|
+
}
|
|
1266
|
+
await this.ensureHydrated();
|
|
1267
|
+
const cached = this.cache.get(id);
|
|
1268
|
+
return cached ? { record: cached.record, version: cached.version } : { record: null, version: 0 };
|
|
1269
|
+
}
|
|
1270
|
+
#txIdForHook() {
|
|
1271
|
+
return this.activeTxId?.() ?? generateULID();
|
|
1272
|
+
}
|
|
1273
|
+
/** @internal Untracked put body — call {@link put}, not this. */
|
|
1274
|
+
async putInternal(id, record, options) {
|
|
1208
1275
|
if (!hasWritePermission(this.keyring, this.name)) {
|
|
1209
1276
|
throw new ReadOnlyError();
|
|
1210
1277
|
}
|
|
@@ -1233,7 +1300,7 @@ var Collection = class {
|
|
|
1233
1300
|
registry.collectChange(this.name, id, existingRecord, incomingRecord, vBefore, vBefore + 1);
|
|
1234
1301
|
} else {
|
|
1235
1302
|
await registry.runChecks(this.name, incomingRecord, ctx);
|
|
1236
|
-
const { GuardExecutor } = await import("./executor-
|
|
1303
|
+
const { GuardExecutor } = await import("./executor-BZKFZVRC.js");
|
|
1237
1304
|
for (const g of guards) {
|
|
1238
1305
|
await GuardExecutor.checkFrozenFields(g, id, existingRecord, incomingRecord);
|
|
1239
1306
|
}
|
|
@@ -1462,7 +1529,7 @@ var Collection = class {
|
|
|
1462
1529
|
if (mode === "eager") {
|
|
1463
1530
|
if (executor === null) {
|
|
1464
1531
|
;
|
|
1465
|
-
({ MaterializedViewExecutor: executor } = await import("./executor-
|
|
1532
|
+
({ MaterializedViewExecutor: executor } = await import("./executor-KT2IOZVP.js"));
|
|
1466
1533
|
}
|
|
1467
1534
|
await executor.refresh(reg, {
|
|
1468
1535
|
getCollection: (name) => this.materializedViewSource.getCollection(name),
|
|
@@ -1471,7 +1538,7 @@ var Collection = class {
|
|
|
1471
1538
|
});
|
|
1472
1539
|
} else if (mode === "lazy") {
|
|
1473
1540
|
if (staleHelpers === null) {
|
|
1474
|
-
staleHelpers = await import("./stale-
|
|
1541
|
+
staleHelpers = await import("./stale-PAGCS4K5.js");
|
|
1475
1542
|
}
|
|
1476
1543
|
staleHelpers.markMVStale(registry, reg.spec.name);
|
|
1477
1544
|
}
|
|
@@ -1500,7 +1567,7 @@ var Collection = class {
|
|
|
1500
1567
|
const mode = typeof spec.lifecycle === "string" ? spec.lifecycle : spec.lifecycle.mode;
|
|
1501
1568
|
if (mode === "eager") {
|
|
1502
1569
|
if (DerivationExecutor === null) {
|
|
1503
|
-
({ DerivationExecutor } = await import("./executor-
|
|
1570
|
+
({ DerivationExecutor } = await import("./executor-GFZFDQXV.js"));
|
|
1504
1571
|
}
|
|
1505
1572
|
const sourceWithId = { ...incoming, id };
|
|
1506
1573
|
const ctx = { vault: this.derivationSource.getReadOnlyFacade() };
|
|
@@ -1519,7 +1586,7 @@ var Collection = class {
|
|
|
1519
1586
|
const outputCollection = this.derivationSource.getCollection(outSpec.collection);
|
|
1520
1587
|
const txCtx = this.derivationSource.getActiveTxContext();
|
|
1521
1588
|
if (out.kind === "array") {
|
|
1522
|
-
const { loadFanoutSidecar, saveFanoutSidecar } = await import("./fanout-sidecar-
|
|
1589
|
+
const { loadFanoutSidecar, saveFanoutSidecar } = await import("./fanout-sidecar-NRBWSLRK.js");
|
|
1523
1590
|
const prior = await loadFanoutSidecar(
|
|
1524
1591
|
this.adapter,
|
|
1525
1592
|
this.vault,
|
|
@@ -1581,8 +1648,71 @@ var Collection = class {
|
|
|
1581
1648
|
}
|
|
1582
1649
|
}
|
|
1583
1650
|
}
|
|
1584
|
-
/**
|
|
1651
|
+
/**
|
|
1652
|
+
* Delete a record by ID. Runs inside the hub's write-queue tracker
|
|
1653
|
+
* (#227) so `hub.writeQueue.pending` reflects this write.
|
|
1654
|
+
*/
|
|
1585
1655
|
async delete(id) {
|
|
1656
|
+
await this.schemaUpdateGate?.assertWritable();
|
|
1657
|
+
await this.schemaFence?.assertWritable(this.name);
|
|
1658
|
+
let event;
|
|
1659
|
+
if (this.#hooksActive()) {
|
|
1660
|
+
const prior = await this.#priorForHook(id);
|
|
1661
|
+
event = {
|
|
1662
|
+
op: "delete",
|
|
1663
|
+
vault: this.vault,
|
|
1664
|
+
collection: this.name,
|
|
1665
|
+
docId: id,
|
|
1666
|
+
before: prior.record,
|
|
1667
|
+
after: null,
|
|
1668
|
+
userId: this.keyring.userId,
|
|
1669
|
+
timestamp: Date.now(),
|
|
1670
|
+
txId: this.#txIdForHook(),
|
|
1671
|
+
baseVersion: prior.version,
|
|
1672
|
+
version: prior.version + 1
|
|
1673
|
+
};
|
|
1674
|
+
await this.writeHooks.runBefore(event);
|
|
1675
|
+
}
|
|
1676
|
+
if (this.writeQueue) await this.writeQueue.track(() => this.deleteInternal(id));
|
|
1677
|
+
else await this.deleteInternal(id);
|
|
1678
|
+
if (event) await this.writeHooks.runAfter(event);
|
|
1679
|
+
}
|
|
1680
|
+
/**
|
|
1681
|
+
* @internal #232 — bulk-rewrite every record through a cutover transform.
|
|
1682
|
+
* Raw adapter path (bypasses the write gate + guards — the transform is
|
|
1683
|
+
* trusted and runs only during the `migrating` phase). Bumps each
|
|
1684
|
+
* record's `_v` and appends a ledger `op:'migration'` entry.
|
|
1685
|
+
*/
|
|
1686
|
+
async _applyCutoverTransform(transform) {
|
|
1687
|
+
const ids = await this.adapter.list(this.vault, this.name);
|
|
1688
|
+
let count = 0;
|
|
1689
|
+
for (const id of ids) {
|
|
1690
|
+
const env = await this.adapter.get(this.vault, this.name, id);
|
|
1691
|
+
if (!env) continue;
|
|
1692
|
+
const record = await this.decryptRecord(env, { skipValidation: true });
|
|
1693
|
+
const next = transform(record);
|
|
1694
|
+
const nextVersion = (env._v ?? 0) + 1;
|
|
1695
|
+
const newEnv = await this.encryptRecord(next, nextVersion);
|
|
1696
|
+
await this.adapter.put(this.vault, this.name, id, newEnv);
|
|
1697
|
+
await this._invalidateCacheEntry(id);
|
|
1698
|
+
if (this.ledger) {
|
|
1699
|
+
await this.ledger.append({
|
|
1700
|
+
op: "migration",
|
|
1701
|
+
collection: this.name,
|
|
1702
|
+
id,
|
|
1703
|
+
version: nextVersion,
|
|
1704
|
+
actor: this.keyring.userId,
|
|
1705
|
+
payloadHash: "",
|
|
1706
|
+
reason: "schema:coordinated-cutover"
|
|
1707
|
+
}).catch(() => {
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1710
|
+
count++;
|
|
1711
|
+
}
|
|
1712
|
+
return count;
|
|
1713
|
+
}
|
|
1714
|
+
/** @internal Untracked delete body — call {@link delete}, not this. */
|
|
1715
|
+
async deleteInternal(id) {
|
|
1586
1716
|
await this._doDelete(id, false);
|
|
1587
1717
|
}
|
|
1588
1718
|
/**
|
|
@@ -1775,7 +1905,7 @@ var Collection = class {
|
|
|
1775
1905
|
for (const [outputKey, outSpec] of Object.entries(spec.outputs)) {
|
|
1776
1906
|
if (outSpec.shape !== "array") continue;
|
|
1777
1907
|
if (helpers === null) {
|
|
1778
|
-
helpers = await import("./fanout-sidecar-
|
|
1908
|
+
helpers = await import("./fanout-sidecar-NRBWSLRK.js");
|
|
1779
1909
|
}
|
|
1780
1910
|
const sidecar = await helpers.loadFanoutSidecar(
|
|
1781
1911
|
this.adapter,
|
|
@@ -1815,7 +1945,7 @@ var Collection = class {
|
|
|
1815
1945
|
if (mode === "eager") {
|
|
1816
1946
|
if (executor === null) {
|
|
1817
1947
|
;
|
|
1818
|
-
({ MaterializedViewExecutor: executor } = await import("./executor-
|
|
1948
|
+
({ MaterializedViewExecutor: executor } = await import("./executor-KT2IOZVP.js"));
|
|
1819
1949
|
}
|
|
1820
1950
|
await executor.refresh(reg, {
|
|
1821
1951
|
getCollection: (name) => this.materializedViewSource.getCollection(name),
|
|
@@ -1824,7 +1954,7 @@ var Collection = class {
|
|
|
1824
1954
|
});
|
|
1825
1955
|
} else if (mode === "lazy") {
|
|
1826
1956
|
if (staleHelpers === null) {
|
|
1827
|
-
staleHelpers = await import("./stale-
|
|
1957
|
+
staleHelpers = await import("./stale-PAGCS4K5.js");
|
|
1828
1958
|
}
|
|
1829
1959
|
staleHelpers.markMVStale(registry, reg.spec.name);
|
|
1830
1960
|
}
|
|
@@ -1847,7 +1977,7 @@ var Collection = class {
|
|
|
1847
1977
|
);
|
|
1848
1978
|
}
|
|
1849
1979
|
if (this.materializedViewSource !== void 0) {
|
|
1850
|
-
const { resolveStaleMVOnRead } = await import("./stale-
|
|
1980
|
+
const { resolveStaleMVOnRead } = await import("./stale-PAGCS4K5.js");
|
|
1851
1981
|
await resolveStaleMVOnRead(this.materializedViewSource, this.name);
|
|
1852
1982
|
}
|
|
1853
1983
|
await this.ensureHydrated();
|
|
@@ -2405,6 +2535,21 @@ var Collection = class {
|
|
|
2405
2535
|
this.cache.set(id, { record, version: envelope._v });
|
|
2406
2536
|
this.indexes?.upsert(id, record, previous ? previous.record : null);
|
|
2407
2537
|
}
|
|
2538
|
+
/**
|
|
2539
|
+
* #228b — apply a peer tab's committed write to THIS tab's in-memory view:
|
|
2540
|
+
* re-read the (already-persisted) envelope from the shared store + refresh
|
|
2541
|
+
* cache/indexes, then emit a `change` event so reactive consumers re-render.
|
|
2542
|
+
* Never writes to the store and never fires write hooks, so it cannot loop.
|
|
2543
|
+
*/
|
|
2544
|
+
async _applyRemoteChange(id, action) {
|
|
2545
|
+
await this._invalidateCacheEntry(id);
|
|
2546
|
+
this.emitter.emit("change", { vault: this.vault, collection: this.name, id, action });
|
|
2547
|
+
}
|
|
2548
|
+
/** @internal #228c — the current in-memory record without a store read (for conflict capture). */
|
|
2549
|
+
_peekCached(id) {
|
|
2550
|
+
const entry = this.lazy && this.lru ? this.lru.get(id) : this.cache.get(id);
|
|
2551
|
+
return entry ? entry.record : null;
|
|
2552
|
+
}
|
|
2408
2553
|
async ensureHydrated() {
|
|
2409
2554
|
if (this.hydrated) return;
|
|
2410
2555
|
const ids = await this.adapter.list(this.vault, this.name);
|
|
@@ -3802,15 +3947,68 @@ async function derivePersistedSchema(validator) {
|
|
|
3802
3947
|
};
|
|
3803
3948
|
}
|
|
3804
3949
|
|
|
3950
|
+
// src/schema-update/delta.ts
|
|
3951
|
+
function computeSchemaDelta(stored, fresh, collection) {
|
|
3952
|
+
const a = stored;
|
|
3953
|
+
const b = fresh;
|
|
3954
|
+
const aProps = a.properties ?? {};
|
|
3955
|
+
const bProps = b.properties ?? {};
|
|
3956
|
+
const aReq = new Set(a.required ?? []);
|
|
3957
|
+
const bReq = new Set(b.required ?? []);
|
|
3958
|
+
const aKeys = Object.keys(aProps);
|
|
3959
|
+
const bKeys = Object.keys(bProps);
|
|
3960
|
+
const added = bKeys.filter((k) => !(k in aProps));
|
|
3961
|
+
const removed = aKeys.filter((k) => !(k in bProps));
|
|
3962
|
+
const changed = [];
|
|
3963
|
+
for (const k of bKeys) {
|
|
3964
|
+
if (!(k in aProps)) continue;
|
|
3965
|
+
const shapeChanged = canonicalize(aProps[k]) !== canonicalize(bProps[k]);
|
|
3966
|
+
const requiredChanged = aReq.has(k) !== bReq.has(k);
|
|
3967
|
+
if (shapeChanged || requiredChanged) {
|
|
3968
|
+
changed.push({ field: k, requiredChanged, shapeChanged });
|
|
3969
|
+
}
|
|
3970
|
+
}
|
|
3971
|
+
let kind;
|
|
3972
|
+
if (added.length === 0 && removed.length === 0 && changed.length === 0) {
|
|
3973
|
+
kind = "none";
|
|
3974
|
+
} else if (removed.length === 0 && changed.length === 0 && added.every((k) => !bReq.has(k))) {
|
|
3975
|
+
kind = "additive";
|
|
3976
|
+
} else {
|
|
3977
|
+
kind = "non-additive";
|
|
3978
|
+
}
|
|
3979
|
+
return { collection, kind, added, removed, changed };
|
|
3980
|
+
}
|
|
3981
|
+
|
|
3982
|
+
// src/schema-update/dispatch.ts
|
|
3983
|
+
async function evaluateStrategies(delta, strategies, ctx) {
|
|
3984
|
+
for (const strategy of strategies) {
|
|
3985
|
+
const decision = await strategy.onSchemaDelta(delta, ctx);
|
|
3986
|
+
if (decision.action !== "allow") return decision;
|
|
3987
|
+
}
|
|
3988
|
+
return { action: "allow" };
|
|
3989
|
+
}
|
|
3990
|
+
|
|
3805
3991
|
// src/persisted-schemas/register.ts
|
|
3806
3992
|
async function persistSchemaIfNeeded(opts) {
|
|
3807
3993
|
const fresh = await derivePersistedSchema(opts.validator);
|
|
3808
3994
|
const stored = await loadPersistedSchema(opts.store, opts.vault, opts.collectionName, opts.dek);
|
|
3809
3995
|
if (stored && isEquivalent(stored, fresh)) {
|
|
3810
|
-
return { written: false, skipped: true, envelope: stored };
|
|
3996
|
+
return { written: false, skipped: true, envelope: stored, decision: { action: "allow" } };
|
|
3997
|
+
}
|
|
3998
|
+
let decision = { action: "allow" };
|
|
3999
|
+
const strategies = opts.strategies ?? [];
|
|
4000
|
+
if (stored && strategies.length > 0 && stored.kind === fresh.kind && isPlainObject2(stored.jsonSchema) && isPlainObject2(fresh.jsonSchema)) {
|
|
4001
|
+
const delta = computeSchemaDelta(stored.jsonSchema, fresh.jsonSchema, opts.collectionName);
|
|
4002
|
+
decision = await evaluateStrategies(delta, strategies, { collection: opts.collectionName });
|
|
4003
|
+
}
|
|
4004
|
+
if (decision.action !== "allow") {
|
|
4005
|
+
return { written: false, skipped: false, envelope: stored ?? fresh, decision };
|
|
3811
4006
|
}
|
|
3812
4007
|
await savePersistedSchema(opts.store, opts.vault, opts.collectionName, opts.dek, fresh);
|
|
3813
|
-
return { written: true, skipped: false, envelope: fresh };
|
|
4008
|
+
return { written: true, skipped: false, envelope: fresh, decision };
|
|
4009
|
+
}
|
|
4010
|
+
function isPlainObject2(v) {
|
|
4011
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3814
4012
|
}
|
|
3815
4013
|
function isEquivalent(a, b) {
|
|
3816
4014
|
if (a.kind !== b.kind) return false;
|
|
@@ -3819,6 +4017,254 @@ function isEquivalent(a, b) {
|
|
|
3819
4017
|
return false;
|
|
3820
4018
|
}
|
|
3821
4019
|
|
|
4020
|
+
// src/schema-update/gate.ts
|
|
4021
|
+
var SchemaUpdateGate = class {
|
|
4022
|
+
#decision;
|
|
4023
|
+
constructor(decision) {
|
|
4024
|
+
this.#decision = decision.catch(() => null);
|
|
4025
|
+
}
|
|
4026
|
+
async assertWritable() {
|
|
4027
|
+
const decision = await this.#decision;
|
|
4028
|
+
if (decision && decision.action === "reject") {
|
|
4029
|
+
throw decision.error;
|
|
4030
|
+
}
|
|
4031
|
+
}
|
|
4032
|
+
};
|
|
4033
|
+
|
|
4034
|
+
// src/schema-update/fence.ts
|
|
4035
|
+
var FENCE_RECORD_ID = "schema-fence";
|
|
4036
|
+
var META_COLLECTION2 = "_meta";
|
|
4037
|
+
var DEFAULT_FENCE = { currentSchemaVersion: 0, fenceState: "normal" };
|
|
4038
|
+
async function loadFence(store, vault) {
|
|
4039
|
+
const envelope = await store.get(vault, META_COLLECTION2, FENCE_RECORD_ID);
|
|
4040
|
+
if (!envelope) return DEFAULT_FENCE;
|
|
4041
|
+
try {
|
|
4042
|
+
const parsed = JSON.parse(envelope._data);
|
|
4043
|
+
if (!isFenceDoc(parsed)) return DEFAULT_FENCE;
|
|
4044
|
+
return parsed;
|
|
4045
|
+
} catch {
|
|
4046
|
+
return DEFAULT_FENCE;
|
|
4047
|
+
}
|
|
4048
|
+
}
|
|
4049
|
+
async function saveFence(store, vault, fence) {
|
|
4050
|
+
const envelope = {
|
|
4051
|
+
_noydb: NOYDB_FORMAT_VERSION,
|
|
4052
|
+
_v: 1,
|
|
4053
|
+
_ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4054
|
+
_iv: "",
|
|
4055
|
+
_data: JSON.stringify(fence)
|
|
4056
|
+
};
|
|
4057
|
+
await store.put(vault, META_COLLECTION2, FENCE_RECORD_ID, envelope);
|
|
4058
|
+
}
|
|
4059
|
+
function isFenceDoc(x) {
|
|
4060
|
+
if (x === null || typeof x !== "object") return false;
|
|
4061
|
+
const o = x;
|
|
4062
|
+
return typeof o["currentSchemaVersion"] === "number" && (o["fenceState"] === "normal" || o["fenceState"] === "draining" || o["fenceState"] === "migrating" || o["fenceState"] === "complete");
|
|
4063
|
+
}
|
|
4064
|
+
|
|
4065
|
+
// src/schema-update/client-registry.ts
|
|
4066
|
+
var META_COLLECTION3 = "_meta";
|
|
4067
|
+
var CLIENT_PREFIX = "schema-fence:client:";
|
|
4068
|
+
async function writeClientDoc(store, vault, clientId, doc) {
|
|
4069
|
+
const envelope = {
|
|
4070
|
+
_noydb: NOYDB_FORMAT_VERSION,
|
|
4071
|
+
_v: 1,
|
|
4072
|
+
_ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4073
|
+
_iv: "",
|
|
4074
|
+
_data: JSON.stringify({ clientId, ...doc })
|
|
4075
|
+
};
|
|
4076
|
+
await store.put(vault, META_COLLECTION3, `${CLIENT_PREFIX}${clientId}`, envelope);
|
|
4077
|
+
}
|
|
4078
|
+
async function listClientDocs(store, vault) {
|
|
4079
|
+
const ids = await store.list(vault, META_COLLECTION3);
|
|
4080
|
+
const out = [];
|
|
4081
|
+
for (const id of ids) {
|
|
4082
|
+
if (!id.startsWith(CLIENT_PREFIX)) continue;
|
|
4083
|
+
const env = await store.get(vault, META_COLLECTION3, id);
|
|
4084
|
+
if (!env) continue;
|
|
4085
|
+
try {
|
|
4086
|
+
const parsed = JSON.parse(env._data);
|
|
4087
|
+
if (isClientDoc(parsed)) out.push(parsed);
|
|
4088
|
+
} catch {
|
|
4089
|
+
}
|
|
4090
|
+
}
|
|
4091
|
+
return out;
|
|
4092
|
+
}
|
|
4093
|
+
async function activeQuiesced(store, vault, opts) {
|
|
4094
|
+
const docs = await listClientDocs(store, vault);
|
|
4095
|
+
const active = docs.filter(
|
|
4096
|
+
(d) => d.lastSeen >= opts.now - opts.staleMs && d.clientId !== opts.excludeClientId
|
|
4097
|
+
);
|
|
4098
|
+
return active.every((d) => d.quiescedAtVersion === opts.generation);
|
|
4099
|
+
}
|
|
4100
|
+
function isClientDoc(x) {
|
|
4101
|
+
if (x === null || typeof x !== "object") return false;
|
|
4102
|
+
const o = x;
|
|
4103
|
+
return typeof o["clientId"] === "string" && typeof o["lastSeen"] === "number" && (o["quiescedAtVersion"] === null || typeof o["quiescedAtVersion"] === "number");
|
|
4104
|
+
}
|
|
4105
|
+
|
|
4106
|
+
// src/schema-update/fence-controller.ts
|
|
4107
|
+
var SchemaFenceController = class {
|
|
4108
|
+
#store;
|
|
4109
|
+
#vault;
|
|
4110
|
+
#onFlush;
|
|
4111
|
+
#clientId;
|
|
4112
|
+
#now;
|
|
4113
|
+
#staleMs;
|
|
4114
|
+
#quiesceTimeoutMs;
|
|
4115
|
+
#emit;
|
|
4116
|
+
#snapshot = 0;
|
|
4117
|
+
#pending = /* @__PURE__ */ new Map();
|
|
4118
|
+
constructor(opts) {
|
|
4119
|
+
this.#store = opts.store;
|
|
4120
|
+
this.#vault = opts.vault;
|
|
4121
|
+
this.#onFlush = opts.onFlush;
|
|
4122
|
+
this.#clientId = opts.clientId ?? "migrator";
|
|
4123
|
+
this.#now = opts.now ?? (() => Date.now());
|
|
4124
|
+
this.#staleMs = opts.staleMs ?? 3e4;
|
|
4125
|
+
this.#quiesceTimeoutMs = opts.quiesceTimeoutMs ?? 6e4;
|
|
4126
|
+
this.#emit = opts.emit ?? (() => {
|
|
4127
|
+
});
|
|
4128
|
+
}
|
|
4129
|
+
/** Capture the generation snapshot at vault-open. */
|
|
4130
|
+
async init() {
|
|
4131
|
+
this.#snapshot = (await loadFence(this.#store, this.#vault)).currentSchemaVersion;
|
|
4132
|
+
}
|
|
4133
|
+
/** Record a per-collection pending cutover (from a registration `cutover` decision). */
|
|
4134
|
+
registerPendingCutover(collection, transform) {
|
|
4135
|
+
this.#pending.set(collection, transform);
|
|
4136
|
+
}
|
|
4137
|
+
/** Write-path gate. Throws when behind, fenced, or this collection is cutover-pending. */
|
|
4138
|
+
async assertWritable(collection) {
|
|
4139
|
+
const fence = await loadFence(this.#store, this.#vault);
|
|
4140
|
+
if (fence.currentSchemaVersion > this.#snapshot) {
|
|
4141
|
+
throw new MigrationRequiredError(
|
|
4142
|
+
`Vault "${this.#vault}" advanced to schema generation ${fence.currentSchemaVersion} (this client opened at ${this.#snapshot}). Reload to continue.`
|
|
4143
|
+
);
|
|
4144
|
+
}
|
|
4145
|
+
if (fence.fenceState === "draining" || fence.fenceState === "migrating") {
|
|
4146
|
+
throw new SchemaFenceError(`Vault "${this.#vault}" is mid-cutover (${fence.fenceState}); writes are paused.`);
|
|
4147
|
+
}
|
|
4148
|
+
if (this.#pending.has(collection)) {
|
|
4149
|
+
throw new SchemaFenceError(
|
|
4150
|
+
`Collection "${collection}" has a pending schema cutover; run vault.runSchemaCutover() before writing.`
|
|
4151
|
+
);
|
|
4152
|
+
}
|
|
4153
|
+
}
|
|
4154
|
+
/**
|
|
4155
|
+
* Admin trigger. Drain → wait for the active set to quiesce (or time out)
|
|
4156
|
+
* → migrate each pending transform → bump → complete → normal. The
|
|
4157
|
+
* migrator excludes itself from the barrier (it drained synchronously
|
|
4158
|
+
* here). `onPoll` (tests) advances other clients between barrier checks;
|
|
4159
|
+
* production falls back to a short real delay.
|
|
4160
|
+
*/
|
|
4161
|
+
async runCutover(run, opts) {
|
|
4162
|
+
if (this.#pending.size === 0) return { migrated: 0 };
|
|
4163
|
+
const base = await loadFence(this.#store, this.#vault);
|
|
4164
|
+
const generation = base.currentSchemaVersion;
|
|
4165
|
+
await this.#setState(generation, "draining");
|
|
4166
|
+
await this.#onFlush();
|
|
4167
|
+
const deadline = this.#now() + this.#quiesceTimeoutMs;
|
|
4168
|
+
while (!await activeQuiesced(this.#store, this.#vault, {
|
|
4169
|
+
generation,
|
|
4170
|
+
now: this.#now(),
|
|
4171
|
+
staleMs: this.#staleMs,
|
|
4172
|
+
excludeClientId: this.#clientId
|
|
4173
|
+
})) {
|
|
4174
|
+
if (this.#now() >= deadline) {
|
|
4175
|
+
throw new QuiesceTimeoutError(
|
|
4176
|
+
`Cutover on "${this.#vault}" timed out waiting for clients to quiesce at generation ${generation}.`
|
|
4177
|
+
);
|
|
4178
|
+
}
|
|
4179
|
+
await (opts?.onPoll ? opts.onPoll() : delay(50));
|
|
4180
|
+
}
|
|
4181
|
+
await this.#setState(generation, "migrating");
|
|
4182
|
+
let migrated = 0;
|
|
4183
|
+
for (const [collection, transform] of this.#pending) {
|
|
4184
|
+
await run(collection, transform);
|
|
4185
|
+
migrated++;
|
|
4186
|
+
}
|
|
4187
|
+
const nextVersion = generation + 1;
|
|
4188
|
+
await this.#setState(nextVersion, "complete");
|
|
4189
|
+
this.#pending.clear();
|
|
4190
|
+
await this.#setState(nextVersion, "normal");
|
|
4191
|
+
this.#snapshot = nextVersion;
|
|
4192
|
+
return { migrated };
|
|
4193
|
+
}
|
|
4194
|
+
/** Recover a stuck drain: reset fenceState to normal at the current version (no bump). */
|
|
4195
|
+
async abort() {
|
|
4196
|
+
const fence = await loadFence(this.#store, this.#vault);
|
|
4197
|
+
await this.#setState(fence.currentSchemaVersion, "normal");
|
|
4198
|
+
}
|
|
4199
|
+
async #setState(currentSchemaVersion, fenceState) {
|
|
4200
|
+
await saveFence(this.#store, this.#vault, { currentSchemaVersion, fenceState });
|
|
4201
|
+
this.#emit({ currentSchemaVersion, fenceState });
|
|
4202
|
+
}
|
|
4203
|
+
};
|
|
4204
|
+
function delay(ms) {
|
|
4205
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
4206
|
+
}
|
|
4207
|
+
|
|
4208
|
+
// src/schema-update/fence-watcher.ts
|
|
4209
|
+
var FenceWatcher = class {
|
|
4210
|
+
#store;
|
|
4211
|
+
#vault;
|
|
4212
|
+
#clientId;
|
|
4213
|
+
#onFlush;
|
|
4214
|
+
#now;
|
|
4215
|
+
#emit;
|
|
4216
|
+
#lastState = null;
|
|
4217
|
+
#quiescedAtVersion = null;
|
|
4218
|
+
#timer;
|
|
4219
|
+
constructor(opts) {
|
|
4220
|
+
this.#store = opts.store;
|
|
4221
|
+
this.#vault = opts.vault;
|
|
4222
|
+
this.#clientId = opts.clientId;
|
|
4223
|
+
this.#onFlush = opts.onFlush;
|
|
4224
|
+
this.#now = opts.now ?? (() => Date.now());
|
|
4225
|
+
this.#emit = opts.emit ?? (() => {
|
|
4226
|
+
});
|
|
4227
|
+
}
|
|
4228
|
+
/** Publish liveness (and the current ack) without changing quiesce state. */
|
|
4229
|
+
async beat() {
|
|
4230
|
+
await writeClientDoc(this.#store, this.#vault, this.#clientId, {
|
|
4231
|
+
lastSeen: this.#now(),
|
|
4232
|
+
quiescedAtVersion: this.#quiescedAtVersion
|
|
4233
|
+
});
|
|
4234
|
+
}
|
|
4235
|
+
/** Poll the fence; quiesce on draining; emit on transitions. */
|
|
4236
|
+
async check() {
|
|
4237
|
+
const fence = await loadFence(this.#store, this.#vault);
|
|
4238
|
+
if (fence.fenceState !== this.#lastState) {
|
|
4239
|
+
this.#lastState = fence.fenceState;
|
|
4240
|
+
this.#emit({ currentSchemaVersion: fence.currentSchemaVersion, fenceState: fence.fenceState });
|
|
4241
|
+
}
|
|
4242
|
+
if (fence.fenceState === "draining" && this.#quiescedAtVersion !== fence.currentSchemaVersion) {
|
|
4243
|
+
await this.#onFlush();
|
|
4244
|
+
this.#quiescedAtVersion = fence.currentSchemaVersion;
|
|
4245
|
+
await this.beat();
|
|
4246
|
+
}
|
|
4247
|
+
if (fence.fenceState === "normal") {
|
|
4248
|
+
this.#quiescedAtVersion = null;
|
|
4249
|
+
}
|
|
4250
|
+
}
|
|
4251
|
+
start(intervalMs) {
|
|
4252
|
+
if (this.#timer) return;
|
|
4253
|
+
this.#timer = setInterval(() => {
|
|
4254
|
+
void this.beat();
|
|
4255
|
+
void this.check();
|
|
4256
|
+
}, intervalMs);
|
|
4257
|
+
const timer = this.#timer;
|
|
4258
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
4259
|
+
}
|
|
4260
|
+
stop() {
|
|
4261
|
+
if (this.#timer) {
|
|
4262
|
+
clearInterval(this.#timer);
|
|
4263
|
+
this.#timer = void 0;
|
|
4264
|
+
}
|
|
4265
|
+
}
|
|
4266
|
+
};
|
|
4267
|
+
|
|
3822
4268
|
// src/introspection/fields.ts
|
|
3823
4269
|
function jsonSchemaType(node) {
|
|
3824
4270
|
if (Array.isArray(node.type)) {
|
|
@@ -4165,6 +4611,13 @@ var Vault = class {
|
|
|
4165
4611
|
*/
|
|
4166
4612
|
reloadKeyring;
|
|
4167
4613
|
collectionCache = /* @__PURE__ */ new Map();
|
|
4614
|
+
/** #232 — vault-level schema cutover fence/controller. */
|
|
4615
|
+
schemaFence;
|
|
4616
|
+
/** #232 — per-client heartbeat/watcher; started lazily on cutover registration. */
|
|
4617
|
+
#fenceWatcher;
|
|
4618
|
+
#fenceCoordinationStarted = false;
|
|
4619
|
+
/** #229 — per-collection registered schema-update strategy names. */
|
|
4620
|
+
#schemaUpdateNames = /* @__PURE__ */ new Map();
|
|
4168
4621
|
/**
|
|
4169
4622
|
* per-collection `blobFields` retention/TTL config.
|
|
4170
4623
|
* Populated on `collection({ blobFields })` and read by
|
|
@@ -4280,6 +4733,13 @@ var Vault = class {
|
|
|
4280
4733
|
this.noydb = opts.noydb;
|
|
4281
4734
|
this.keyring = opts.keyring;
|
|
4282
4735
|
this.encrypted = opts.encrypted;
|
|
4736
|
+
this.schemaFence = new SchemaFenceController({
|
|
4737
|
+
store: this.adapter,
|
|
4738
|
+
vault: this.name,
|
|
4739
|
+
onFlush: () => this.noydb._writeQueueTracker.onFlush(),
|
|
4740
|
+
clientId: this.noydb._clientId,
|
|
4741
|
+
emit: (e) => this.emitter.emit("schema:fence-changed", { vault: this.name, ...e })
|
|
4742
|
+
});
|
|
4283
4743
|
this.emitter = opts.emitter;
|
|
4284
4744
|
this.onDirty = opts.onDirty;
|
|
4285
4745
|
this.onRegisterConflictResolver = opts.onRegisterConflictResolver;
|
|
@@ -4388,6 +4848,35 @@ var Vault = class {
|
|
|
4388
4848
|
}
|
|
4389
4849
|
this.dictKeyFieldRegistry.set(collectionName, dictFieldMap);
|
|
4390
4850
|
}
|
|
4851
|
+
if ((options?.schemaUpdate?.length ?? 0) > 0) {
|
|
4852
|
+
this.#schemaUpdateNames.set(collectionName, (options.schemaUpdate ?? []).map((s) => s.name));
|
|
4853
|
+
}
|
|
4854
|
+
let schemaUpdateGate;
|
|
4855
|
+
if (options?.persistJsonSchema === true && options.schema !== void 0 && (options.schemaUpdate?.length ?? 0) > 0) {
|
|
4856
|
+
const validator = options.schema;
|
|
4857
|
+
const strategies = options.schemaUpdate ?? [];
|
|
4858
|
+
const work = (async () => {
|
|
4859
|
+
const dek = await this.getDEK(collectionName);
|
|
4860
|
+
const result = await persistSchemaIfNeeded({
|
|
4861
|
+
store: this.adapter,
|
|
4862
|
+
vault: this.name,
|
|
4863
|
+
collectionName,
|
|
4864
|
+
validator,
|
|
4865
|
+
dek,
|
|
4866
|
+
strategies
|
|
4867
|
+
});
|
|
4868
|
+
const decision = result.decision ?? { action: "allow" };
|
|
4869
|
+
if (decision.action === "cutover") {
|
|
4870
|
+
this.schemaFence.registerPendingCutover(collectionName, decision.transform);
|
|
4871
|
+
this._ensureFenceCoordination();
|
|
4872
|
+
}
|
|
4873
|
+
return decision;
|
|
4874
|
+
})();
|
|
4875
|
+
this._pendingSchemaWrites.push(work.then(() => {
|
|
4876
|
+
}, () => {
|
|
4877
|
+
}));
|
|
4878
|
+
schemaUpdateGate = new SchemaUpdateGate(work);
|
|
4879
|
+
}
|
|
4391
4880
|
const collOpts = {
|
|
4392
4881
|
adapter: this.adapter,
|
|
4393
4882
|
vault: this.name,
|
|
@@ -4395,6 +4884,11 @@ var Vault = class {
|
|
|
4395
4884
|
keyring: this.keyring,
|
|
4396
4885
|
encrypted: this.encrypted,
|
|
4397
4886
|
emitter: this.emitter,
|
|
4887
|
+
writeQueue: this.noydb._writeQueueTracker,
|
|
4888
|
+
writeHooks: this.noydb._writeHooks,
|
|
4889
|
+
activeTxId: () => this.noydb._activeTxContextOrNull?.txId ?? null,
|
|
4890
|
+
schemaUpdateGate,
|
|
4891
|
+
schemaFence: this.schemaFence,
|
|
4398
4892
|
getDEK: this.getDEK,
|
|
4399
4893
|
onDirty: this.onDirty,
|
|
4400
4894
|
historyConfig: this.historyConfig,
|
|
@@ -4481,7 +4975,7 @@ var Vault = class {
|
|
|
4481
4975
|
}
|
|
4482
4976
|
coll = new Collection(collOpts);
|
|
4483
4977
|
this.collectionCache.set(collectionName, coll);
|
|
4484
|
-
if (options?.persistJsonSchema === true && options.schema !== void 0) {
|
|
4978
|
+
if (options?.persistJsonSchema === true && options.schema !== void 0 && (options.schemaUpdate?.length ?? 0) === 0) {
|
|
4485
4979
|
const validator = options.schema;
|
|
4486
4980
|
const work = (async () => {
|
|
4487
4981
|
try {
|
|
@@ -4514,6 +5008,87 @@ var Vault = class {
|
|
|
4514
5008
|
this._pendingSchemaWrites = [];
|
|
4515
5009
|
await Promise.allSettled(pending);
|
|
4516
5010
|
}
|
|
5011
|
+
/**
|
|
5012
|
+
* Run a coordinated schema cutover (#232). Drains pending writes, waits
|
|
5013
|
+
* for the active client set to quiesce (the ack-barrier), applies every
|
|
5014
|
+
* pending collection transform in bulk, bumps the vault schema generation,
|
|
5015
|
+
* and clears the fence. Returns the count of collections migrated.
|
|
5016
|
+
* `opts.onPoll` (tests) advances other clients between barrier checks.
|
|
5017
|
+
*/
|
|
5018
|
+
async runSchemaCutover(opts) {
|
|
5019
|
+
return this.schemaFence.runCutover(
|
|
5020
|
+
(collectionName, transform) => this.#runCutoverTransform(collectionName, transform),
|
|
5021
|
+
opts
|
|
5022
|
+
);
|
|
5023
|
+
}
|
|
5024
|
+
async #runCutoverTransform(collectionName, transform) {
|
|
5025
|
+
const coll = this.collectionCache.get(collectionName);
|
|
5026
|
+
if (!coll) return;
|
|
5027
|
+
await coll._applyCutoverTransform(transform);
|
|
5028
|
+
}
|
|
5029
|
+
/**
|
|
5030
|
+
* #228b — refresh a loaded collection's view of one document from a peer
|
|
5031
|
+
* tab's broadcast. No-op when the collection isn't loaded in this tab
|
|
5032
|
+
* (it will read fresh on next open). Mirrors #runCutoverTransform's guard.
|
|
5033
|
+
*/
|
|
5034
|
+
async _applyRemoteWrite(collectionName, docId, action) {
|
|
5035
|
+
const coll = this.collectionCache.get(collectionName);
|
|
5036
|
+
if (!coll) return;
|
|
5037
|
+
await coll._applyRemoteChange(docId, action);
|
|
5038
|
+
}
|
|
5039
|
+
/**
|
|
5040
|
+
* #228c — for a detected conflict: capture this tab's clobbered record,
|
|
5041
|
+
* read the common ancestor from history, converge the cache to the store's
|
|
5042
|
+
* authoritative value (the (b) re-read), and return all three for the
|
|
5043
|
+
* WriteConflict payload. Returns null when the collection isn't loaded.
|
|
5044
|
+
*/
|
|
5045
|
+
async _captureAndConverge(collectionName, docId, action, baseV) {
|
|
5046
|
+
const coll = this.collectionCache.get(collectionName);
|
|
5047
|
+
if (!coll) return null;
|
|
5048
|
+
const local = coll._peekCached(docId);
|
|
5049
|
+
let base = null;
|
|
5050
|
+
try {
|
|
5051
|
+
base = await coll.getVersion(docId, baseV);
|
|
5052
|
+
} catch {
|
|
5053
|
+
base = null;
|
|
5054
|
+
}
|
|
5055
|
+
await coll._applyRemoteChange(docId, action);
|
|
5056
|
+
const remote = await coll.get(docId);
|
|
5057
|
+
return { local, remote, base };
|
|
5058
|
+
}
|
|
5059
|
+
/** Recover a stuck cutover fence (#232) — reset to normal without bumping. */
|
|
5060
|
+
async abortSchemaCutover() {
|
|
5061
|
+
await this.schemaFence.abort();
|
|
5062
|
+
}
|
|
5063
|
+
/** Current schema-cutover fence state for this vault (#232/#233). Thin live read. */
|
|
5064
|
+
async schemaFenceState() {
|
|
5065
|
+
return loadFence(this.adapter, this.name);
|
|
5066
|
+
}
|
|
5067
|
+
/** @internal Start the per-client heartbeat + fence watcher once a cutover is registered (#232). */
|
|
5068
|
+
_ensureFenceCoordination() {
|
|
5069
|
+
if (this.#fenceCoordinationStarted) return;
|
|
5070
|
+
this.#fenceCoordinationStarted = true;
|
|
5071
|
+
this.#fenceWatcher = new FenceWatcher({
|
|
5072
|
+
store: this.adapter,
|
|
5073
|
+
vault: this.name,
|
|
5074
|
+
clientId: this.noydb._clientId,
|
|
5075
|
+
onFlush: () => this.noydb._writeQueueTracker.onFlush(),
|
|
5076
|
+
emit: (e) => this.emitter.emit("schema:fence-changed", { vault: this.name, ...e })
|
|
5077
|
+
});
|
|
5078
|
+
this.#fenceWatcher.start(2e3);
|
|
5079
|
+
}
|
|
5080
|
+
/** @internal Stop the heartbeat/watcher (vault lock/close). */
|
|
5081
|
+
_stopFenceCoordination() {
|
|
5082
|
+
this.#fenceWatcher?.stop();
|
|
5083
|
+
this.#fenceWatcher = void 0;
|
|
5084
|
+
this.#fenceCoordinationStarted = false;
|
|
5085
|
+
}
|
|
5086
|
+
/** @internal Drive one heartbeat + watch cycle deterministically (tests). */
|
|
5087
|
+
async _fenceTick() {
|
|
5088
|
+
this._ensureFenceCoordination();
|
|
5089
|
+
await this.#fenceWatcher.beat();
|
|
5090
|
+
await this.#fenceWatcher.check();
|
|
5091
|
+
}
|
|
4517
5092
|
/**
|
|
4518
5093
|
* Validate i18nText fields on a `put()`. Called by Collection just
|
|
4519
5094
|
* before the adapter write, after schema validation. Throws
|
|
@@ -4839,12 +5414,12 @@ var Vault = class {
|
|
|
4839
5414
|
if (!fieldSchema) {
|
|
4840
5415
|
throw new AttestationError(`issueAttestation: collection '${collectionName}' has no attestation field-schema. Declare it via vault.collection('${collectionName}', { attestation: { fields: [...] } }).`);
|
|
4841
5416
|
}
|
|
4842
|
-
const { issueAttestationCore } = await import("./issue-
|
|
5417
|
+
const { issueAttestationCore } = await import("./issue-BAJ7ZB4S.js");
|
|
4843
5418
|
const out = await issueAttestationCore(this.makeIssueContext(), { collection: collectionName, id, fieldSchema });
|
|
4844
5419
|
return { docId: out.docId, qr: out.qr, keyId: out.keyId, publicKeyB64: out.publicKeyB64 };
|
|
4845
5420
|
}
|
|
4846
5421
|
async getDocumentSigningPublicKey() {
|
|
4847
|
-
const { loadSigner, loadOrCreateSigner } = await import("./signer-
|
|
5422
|
+
const { loadSigner, loadOrCreateSigner } = await import("./signer-M4K5HBLD.js");
|
|
4848
5423
|
const existing = await loadSigner(this.adapter, this.name, this.getDEK);
|
|
4849
5424
|
if (existing) return { keyId: existing.keyId, publicKeyB64: existing.publicKeyB64 };
|
|
4850
5425
|
if (this.keyring.role !== "owner") {
|
|
@@ -4870,19 +5445,19 @@ var Vault = class {
|
|
|
4870
5445
|
};
|
|
4871
5446
|
}
|
|
4872
5447
|
async revokeAttestation(docId) {
|
|
4873
|
-
const { revokeDocCore } = await import("./revoke-
|
|
5448
|
+
const { revokeDocCore } = await import("./revoke-7JOVLZFD.js");
|
|
4874
5449
|
await revokeDocCore(this.makeRevokeContext(), docId);
|
|
4875
5450
|
}
|
|
4876
5451
|
async unrevokeAttestation(docId) {
|
|
4877
|
-
const { unrevokeDocCore } = await import("./revoke-
|
|
5452
|
+
const { unrevokeDocCore } = await import("./revoke-7JOVLZFD.js");
|
|
4878
5453
|
await unrevokeDocCore(this.makeRevokeContext(), docId);
|
|
4879
5454
|
}
|
|
4880
5455
|
async getRevokedDocIds() {
|
|
4881
|
-
const { getRevokedDocIdsCore } = await import("./revoke-
|
|
5456
|
+
const { getRevokedDocIdsCore } = await import("./revoke-7JOVLZFD.js");
|
|
4882
5457
|
return getRevokedDocIdsCore(this.makeRevokeContext());
|
|
4883
5458
|
}
|
|
4884
5459
|
async publishRevocationList() {
|
|
4885
|
-
const { publishRevocationListCore } = await import("./revoke-
|
|
5460
|
+
const { publishRevocationListCore } = await import("./revoke-7JOVLZFD.js");
|
|
4886
5461
|
return publishRevocationListCore(this.makeRevokeContext());
|
|
4887
5462
|
}
|
|
4888
5463
|
makeRevokeContext() {
|
|
@@ -5171,7 +5746,7 @@ var Vault = class {
|
|
|
5171
5746
|
async _initGuards(handles) {
|
|
5172
5747
|
if (handles.length === 0) return;
|
|
5173
5748
|
const [{ GuardRegistry }, { ReadOnlyVaultFacade }] = await Promise.all([
|
|
5174
|
-
import("./registry-
|
|
5749
|
+
import("./registry-2IEARCGT.js"),
|
|
5175
5750
|
import("./read-only-facade-ITU6L7BL.js")
|
|
5176
5751
|
]);
|
|
5177
5752
|
const registry = new GuardRegistry();
|
|
@@ -5200,7 +5775,7 @@ var Vault = class {
|
|
|
5200
5775
|
async _initDerivations(handles) {
|
|
5201
5776
|
if (handles.length === 0) return;
|
|
5202
5777
|
const [{ DerivationRegistry }, { ReadOnlyVaultFacade }] = await Promise.all([
|
|
5203
|
-
import("./registry-
|
|
5778
|
+
import("./registry-EMGLZGR6.js"),
|
|
5204
5779
|
import("./read-only-facade-ITU6L7BL.js")
|
|
5205
5780
|
]);
|
|
5206
5781
|
const registry = new DerivationRegistry();
|
|
@@ -5231,7 +5806,7 @@ var Vault = class {
|
|
|
5231
5806
|
*/
|
|
5232
5807
|
async _initMaterializedViews(handles) {
|
|
5233
5808
|
if (handles.length === 0) return;
|
|
5234
|
-
const { MaterializedViewRegistry } = await import("./registry-
|
|
5809
|
+
const { MaterializedViewRegistry } = await import("./registry-CDHASH73.js");
|
|
5235
5810
|
const registry = new MaterializedViewRegistry();
|
|
5236
5811
|
this.materializedViewRegistry = registry;
|
|
5237
5812
|
const db = this;
|
|
@@ -5255,7 +5830,7 @@ var Vault = class {
|
|
|
5255
5830
|
*/
|
|
5256
5831
|
async _initOverlayedViews(handles) {
|
|
5257
5832
|
if (handles.length === 0) return;
|
|
5258
|
-
const { OverlayedViewRegistry } = await import("./registry-
|
|
5833
|
+
const { OverlayedViewRegistry } = await import("./registry-NQALYR77.js");
|
|
5259
5834
|
const registry = new OverlayedViewRegistry();
|
|
5260
5835
|
const mvRegistry = this.materializedViewRegistry;
|
|
5261
5836
|
const overlayNames = /* @__PURE__ */ new Set();
|
|
@@ -5302,13 +5877,13 @@ var Vault = class {
|
|
|
5302
5877
|
if (!reg) {
|
|
5303
5878
|
throw new Error(`refreshView: no MV registered with name "${name}"`);
|
|
5304
5879
|
}
|
|
5305
|
-
const { MaterializedViewExecutor } = await import("./executor-
|
|
5880
|
+
const { MaterializedViewExecutor } = await import("./executor-KT2IOZVP.js");
|
|
5306
5881
|
const result = await MaterializedViewExecutor.refresh(reg, {
|
|
5307
5882
|
getCollection: (n) => this.collection(n),
|
|
5308
5883
|
getActiveTxContext: () => this.noydb._activeTxContextOrNull,
|
|
5309
5884
|
getQueryContext: () => this
|
|
5310
5885
|
});
|
|
5311
|
-
const { clearMVStale } = await import("./stale-
|
|
5886
|
+
const { clearMVStale } = await import("./stale-PAGCS4K5.js");
|
|
5312
5887
|
clearMVStale(registry, name);
|
|
5313
5888
|
return result;
|
|
5314
5889
|
}
|
|
@@ -5324,7 +5899,7 @@ var Vault = class {
|
|
|
5324
5899
|
if (registry === null) return { derived: 0, failed: 0 };
|
|
5325
5900
|
const strategies = registry.strategiesForSource(sourceCollection);
|
|
5326
5901
|
if (strategies.length === 0) return { derived: 0, failed: 0 };
|
|
5327
|
-
const { DerivationExecutor } = await import("./executor-
|
|
5902
|
+
const { DerivationExecutor } = await import("./executor-GFZFDQXV.js");
|
|
5328
5903
|
const sourceColl = this.collection(sourceCollection);
|
|
5329
5904
|
const records = await sourceColl.list();
|
|
5330
5905
|
const ctx = { vault: this.readOnlyFacade ?? new (await import("./read-only-facade-ITU6L7BL.js")).ReadOnlyVaultFacade(this) };
|
|
@@ -5349,7 +5924,7 @@ var Vault = class {
|
|
|
5349
5924
|
if (!outSpec) continue;
|
|
5350
5925
|
const outputColl = this.collection(outSpec.collection);
|
|
5351
5926
|
if (out.kind === "array") {
|
|
5352
|
-
const { loadFanoutSidecar, saveFanoutSidecar } = await import("./fanout-sidecar-
|
|
5927
|
+
const { loadFanoutSidecar, saveFanoutSidecar } = await import("./fanout-sidecar-NRBWSLRK.js");
|
|
5353
5928
|
const prior = await loadFanoutSidecar(this.adapter, this.name, spec.source, id, key);
|
|
5354
5929
|
const prevKeys = new Set(prior?.keys ?? []);
|
|
5355
5930
|
const newKeysList = out.entries.map((e) => e.key);
|
|
@@ -5573,7 +6148,7 @@ var Vault = class {
|
|
|
5573
6148
|
* collection.
|
|
5574
6149
|
*/
|
|
5575
6150
|
async delegate(opts) {
|
|
5576
|
-
const { issueDelegation, DELEGATIONS_COLLECTION } = await import("./delegation-
|
|
6151
|
+
const { issueDelegation, DELEGATIONS_COLLECTION } = await import("./delegation-QSC7G5QC.js");
|
|
5577
6152
|
if (!this.keyring.kek) {
|
|
5578
6153
|
throw new ValidationError(
|
|
5579
6154
|
"issueDelegation: keyring.kek is null \u2014 issuing a delegation requires a tier-1 unlock. Re-authenticate at tier 1 (passphrase) first."
|
|
@@ -5595,7 +6170,7 @@ var Vault = class {
|
|
|
5595
6170
|
* if the id does not exist.
|
|
5596
6171
|
*/
|
|
5597
6172
|
async revokeDelegation(id) {
|
|
5598
|
-
const { revokeDelegation, DELEGATIONS_COLLECTION } = await import("./delegation-
|
|
6173
|
+
const { revokeDelegation, DELEGATIONS_COLLECTION } = await import("./delegation-QSC7G5QC.js");
|
|
5599
6174
|
await revokeDelegation(this.adapter, this.name, id);
|
|
5600
6175
|
void DELEGATIONS_COLLECTION;
|
|
5601
6176
|
}
|
|
@@ -5942,6 +6517,27 @@ var Vault = class {
|
|
|
5942
6517
|
async dumpSchema(opts = {}) {
|
|
5943
6518
|
return dumpVaultSchema(this, opts);
|
|
5944
6519
|
}
|
|
6520
|
+
/**
|
|
6521
|
+
* Lightweight read of the vault's registered schema (#229): collections
|
|
6522
|
+
* (+ doc counts), guards, materialized views, schema-update strategies,
|
|
6523
|
+
* and the unlocked user's grants. Cheap — one `adapter.list` per
|
|
6524
|
+
* collection, no decryption. For a full snapshot + stats use dumpSchema().
|
|
6525
|
+
* Post-unlock by construction (a Vault only exists with an unlocked keyring).
|
|
6526
|
+
*/
|
|
6527
|
+
async introspect() {
|
|
6528
|
+
const byCol = (a, b) => a.collection.localeCompare(b.collection);
|
|
6529
|
+
const names = [.../* @__PURE__ */ new Set([...this.collectionCache.keys(), ...await this.collections()])].filter((n) => !n.startsWith("_")).sort((a, b) => a.localeCompare(b));
|
|
6530
|
+
const collections = [];
|
|
6531
|
+
for (const name of names) {
|
|
6532
|
+
const ids = await this.adapter.list(this.name, name);
|
|
6533
|
+
collections.push({ name, docCount: ids.length });
|
|
6534
|
+
}
|
|
6535
|
+
const guards = (this._getGuardRegistry()?.summary() ?? []).slice().sort(byCol);
|
|
6536
|
+
const materializedViews = (this._getMaterializedViewRegistry()?.all() ?? []).map((mv) => ({ name: mv.spec.name, sourceCollections: [...mv.dependencies].sort() })).sort((a, b) => a.name.localeCompare(b.name));
|
|
6537
|
+
const schemaUpdate = [...this.#schemaUpdateNames.entries()].map(([collection, strategies]) => ({ collection, strategies })).sort(byCol);
|
|
6538
|
+
const grants = [...this.keyring.deks.keys()].filter((collection) => !collection.startsWith("_")).map((collection) => ({ collection, permission: this.keyring.permissions[collection] ?? "rw" })).sort(byCol);
|
|
6539
|
+
return { collections, guards, materializedViews, schemaUpdate, grants };
|
|
6540
|
+
}
|
|
5945
6541
|
/**
|
|
5946
6542
|
* Internal accessor for {@link dumpVaultSchema}. Exposes the structural
|
|
5947
6543
|
* state the walker needs (collection cache, registries, ref registry,
|
|
@@ -6043,7 +6639,7 @@ var Vault = class {
|
|
|
6043
6639
|
* @see docs/subsystems/public-envelope.md
|
|
6044
6640
|
*/
|
|
6045
6641
|
async getPublicEnvelope(opts = {}) {
|
|
6046
|
-
const { readPublicEnvelope: readPublicEnvelope2 } = await import("./public-envelope-
|
|
6642
|
+
const { readPublicEnvelope: readPublicEnvelope2 } = await import("./public-envelope-OHQ5UZFM.js");
|
|
6047
6643
|
return readPublicEnvelope2(this.adapter, this.name, opts);
|
|
6048
6644
|
}
|
|
6049
6645
|
/**
|
|
@@ -6581,6 +7177,387 @@ var NoydbEventEmitter = class {
|
|
|
6581
7177
|
}
|
|
6582
7178
|
};
|
|
6583
7179
|
|
|
7180
|
+
// src/write-queue.ts
|
|
7181
|
+
var WriteQueueTracker = class {
|
|
7182
|
+
#depth = 0;
|
|
7183
|
+
#error = null;
|
|
7184
|
+
#changeHandlers = /* @__PURE__ */ new Set();
|
|
7185
|
+
#flushWaiters = [];
|
|
7186
|
+
get pending() {
|
|
7187
|
+
return this.#depth > 0;
|
|
7188
|
+
}
|
|
7189
|
+
get depth() {
|
|
7190
|
+
return this.#depth;
|
|
7191
|
+
}
|
|
7192
|
+
/** Mark one write as started. */
|
|
7193
|
+
begin() {
|
|
7194
|
+
this.#depth++;
|
|
7195
|
+
this.#emitChange();
|
|
7196
|
+
}
|
|
7197
|
+
/** Mark one write as finished. Pass the error if it failed. */
|
|
7198
|
+
settle(error) {
|
|
7199
|
+
this.#depth = Math.max(0, this.#depth - 1);
|
|
7200
|
+
if (error) this.#error = error;
|
|
7201
|
+
this.#emitChange();
|
|
7202
|
+
if (this.#depth === 0) this.#drainFlush();
|
|
7203
|
+
}
|
|
7204
|
+
onChange(handler) {
|
|
7205
|
+
this.#changeHandlers.add(handler);
|
|
7206
|
+
return () => {
|
|
7207
|
+
this.#changeHandlers.delete(handler);
|
|
7208
|
+
};
|
|
7209
|
+
}
|
|
7210
|
+
onFlush() {
|
|
7211
|
+
if (this.#depth === 0) {
|
|
7212
|
+
const error = this.#error;
|
|
7213
|
+
this.#error = null;
|
|
7214
|
+
return error ? Promise.reject(error) : Promise.resolve();
|
|
7215
|
+
}
|
|
7216
|
+
return new Promise((resolve, reject) => {
|
|
7217
|
+
this.#flushWaiters.push({ resolve, reject });
|
|
7218
|
+
});
|
|
7219
|
+
}
|
|
7220
|
+
/**
|
|
7221
|
+
* Run `fn` as a tracked write: depth++ on entry, depth-- on settle
|
|
7222
|
+
* (success or failure). The fn's resolved value is returned; a thrown
|
|
7223
|
+
* error is re-thrown after the queue is decremented.
|
|
7224
|
+
*/
|
|
7225
|
+
async track(fn) {
|
|
7226
|
+
this.begin();
|
|
7227
|
+
try {
|
|
7228
|
+
const value = await fn();
|
|
7229
|
+
this.settle();
|
|
7230
|
+
return value;
|
|
7231
|
+
} catch (error) {
|
|
7232
|
+
this.settle(error);
|
|
7233
|
+
throw error;
|
|
7234
|
+
}
|
|
7235
|
+
}
|
|
7236
|
+
#emitChange() {
|
|
7237
|
+
for (const handler of this.#changeHandlers) handler();
|
|
7238
|
+
}
|
|
7239
|
+
#drainFlush() {
|
|
7240
|
+
const waiters = this.#flushWaiters;
|
|
7241
|
+
this.#flushWaiters = [];
|
|
7242
|
+
const error = this.#error;
|
|
7243
|
+
this.#error = null;
|
|
7244
|
+
for (const waiter of waiters) {
|
|
7245
|
+
if (error) waiter.reject(error);
|
|
7246
|
+
else waiter.resolve();
|
|
7247
|
+
}
|
|
7248
|
+
}
|
|
7249
|
+
};
|
|
7250
|
+
|
|
7251
|
+
// src/write-hooks.ts
|
|
7252
|
+
var WriteHookRegistry = class {
|
|
7253
|
+
#before = [];
|
|
7254
|
+
#after = [];
|
|
7255
|
+
#suppressed = false;
|
|
7256
|
+
/** True while handlers are running — used by the write path to skip nested firing. */
|
|
7257
|
+
get suppressed() {
|
|
7258
|
+
return this.#suppressed;
|
|
7259
|
+
}
|
|
7260
|
+
/** True when any hook is registered (cheap gate for the write path). */
|
|
7261
|
+
get hasHandlers() {
|
|
7262
|
+
return this.#before.length > 0 || this.#after.length > 0;
|
|
7263
|
+
}
|
|
7264
|
+
onBeforeWrite(handler) {
|
|
7265
|
+
this.#before.push(handler);
|
|
7266
|
+
return () => {
|
|
7267
|
+
const i = this.#before.indexOf(handler);
|
|
7268
|
+
if (i >= 0) this.#before.splice(i, 1);
|
|
7269
|
+
};
|
|
7270
|
+
}
|
|
7271
|
+
onAfterWrite(handler) {
|
|
7272
|
+
this.#after.push(handler);
|
|
7273
|
+
return () => {
|
|
7274
|
+
const i = this.#after.indexOf(handler);
|
|
7275
|
+
if (i >= 0) this.#after.splice(i, 1);
|
|
7276
|
+
};
|
|
7277
|
+
}
|
|
7278
|
+
/** Run before-hooks (awaited, in order). A throw propagates and aborts the write. */
|
|
7279
|
+
async runBefore(event) {
|
|
7280
|
+
if (this.#before.length === 0) return;
|
|
7281
|
+
this.#suppressed = true;
|
|
7282
|
+
try {
|
|
7283
|
+
for (const h of this.#before.slice()) await h(event);
|
|
7284
|
+
} finally {
|
|
7285
|
+
this.#suppressed = false;
|
|
7286
|
+
}
|
|
7287
|
+
}
|
|
7288
|
+
/** Run after-hooks (awaited, in order). Per-handler errors are warned, not thrown. */
|
|
7289
|
+
async runAfter(event) {
|
|
7290
|
+
if (this.#after.length === 0) return;
|
|
7291
|
+
this.#suppressed = true;
|
|
7292
|
+
try {
|
|
7293
|
+
for (const h of this.#after.slice()) {
|
|
7294
|
+
try {
|
|
7295
|
+
await h(event);
|
|
7296
|
+
} catch (err) {
|
|
7297
|
+
console.warn(
|
|
7298
|
+
`[noy-db] onAfterWrite handler failed for ${event.collection}/${event.docId}: ` + (err instanceof Error ? err.message : String(err))
|
|
7299
|
+
);
|
|
7300
|
+
}
|
|
7301
|
+
}
|
|
7302
|
+
} finally {
|
|
7303
|
+
this.#suppressed = false;
|
|
7304
|
+
}
|
|
7305
|
+
}
|
|
7306
|
+
};
|
|
7307
|
+
|
|
7308
|
+
// src/tab-coordination.ts
|
|
7309
|
+
var TabCoordinator = class {
|
|
7310
|
+
tabId;
|
|
7311
|
+
role = "unknown";
|
|
7312
|
+
#lockManager;
|
|
7313
|
+
#channel;
|
|
7314
|
+
#lockName;
|
|
7315
|
+
#heartbeatMs;
|
|
7316
|
+
#staleMs;
|
|
7317
|
+
#now;
|
|
7318
|
+
#peers = /* @__PURE__ */ new Map();
|
|
7319
|
+
#roleHandlers = /* @__PURE__ */ new Set();
|
|
7320
|
+
#tabsHandlers = /* @__PURE__ */ new Set();
|
|
7321
|
+
#ac;
|
|
7322
|
+
#releaseLock;
|
|
7323
|
+
#unsub;
|
|
7324
|
+
#closeUnsub;
|
|
7325
|
+
#timer;
|
|
7326
|
+
#ownsChannel;
|
|
7327
|
+
#started = false;
|
|
7328
|
+
#disposed = false;
|
|
7329
|
+
#lastTabsSig = "";
|
|
7330
|
+
constructor(opts = {}) {
|
|
7331
|
+
this.tabId = opts.tabId ?? `tab-${Math.trunc((opts.now ?? (() => 0))())}-${cheapRand()}`;
|
|
7332
|
+
this.#lockManager = opts.lockManager;
|
|
7333
|
+
this.#channel = opts.channel;
|
|
7334
|
+
this.#lockName = opts.lockName ?? "noydb:tab-primary";
|
|
7335
|
+
this.#heartbeatMs = opts.heartbeatMs ?? 2e3;
|
|
7336
|
+
this.#staleMs = opts.staleMs ?? 6e3;
|
|
7337
|
+
this.#now = opts.now ?? (() => Date.now());
|
|
7338
|
+
this.#ownsChannel = opts.closeChannelOnDispose ?? false;
|
|
7339
|
+
}
|
|
7340
|
+
start() {
|
|
7341
|
+
if (this.#disposed || this.#started) return;
|
|
7342
|
+
this.#started = true;
|
|
7343
|
+
if (this.#channel) {
|
|
7344
|
+
this.#unsub = this.#channel.on("message", (p) => this.#onMessage(p));
|
|
7345
|
+
this.#closeUnsub = this.#channel.on("close", () => this.#onChannelClose());
|
|
7346
|
+
this.#beat();
|
|
7347
|
+
this.#timer = setInterval(() => this.#tick(), this.#heartbeatMs);
|
|
7348
|
+
const t = this.#timer;
|
|
7349
|
+
if (typeof t.unref === "function") t.unref();
|
|
7350
|
+
}
|
|
7351
|
+
if (this.#lockManager) {
|
|
7352
|
+
this.#ac = new AbortController();
|
|
7353
|
+
this.#setRole("secondary");
|
|
7354
|
+
void this.#lockManager.request(this.#lockName, { mode: "exclusive", signal: this.#ac.signal }, () => {
|
|
7355
|
+
this.#setRole("primary");
|
|
7356
|
+
return new Promise((resolve) => {
|
|
7357
|
+
this.#releaseLock = resolve;
|
|
7358
|
+
});
|
|
7359
|
+
}).catch(() => {
|
|
7360
|
+
});
|
|
7361
|
+
}
|
|
7362
|
+
}
|
|
7363
|
+
activeTabs() {
|
|
7364
|
+
if (!this.#channel) return [];
|
|
7365
|
+
const cutoff = this.#now() - this.#staleMs;
|
|
7366
|
+
const self = { tabId: this.tabId, lastSeen: this.#now(), role: this.role };
|
|
7367
|
+
const out = [self, ...[...this.#peers.values()].filter((p) => p.lastSeen >= cutoff)];
|
|
7368
|
+
return out.sort((a, b) => a.tabId.localeCompare(b.tabId));
|
|
7369
|
+
}
|
|
7370
|
+
onTabRoleChange(fn) {
|
|
7371
|
+
this.#roleHandlers.add(fn);
|
|
7372
|
+
return () => this.#roleHandlers.delete(fn);
|
|
7373
|
+
}
|
|
7374
|
+
onActiveTabsChange(fn) {
|
|
7375
|
+
this.#tabsHandlers.add(fn);
|
|
7376
|
+
return () => this.#tabsHandlers.delete(fn);
|
|
7377
|
+
}
|
|
7378
|
+
dispose() {
|
|
7379
|
+
if (this.#disposed) return;
|
|
7380
|
+
this.#disposed = true;
|
|
7381
|
+
this.#releaseLock?.();
|
|
7382
|
+
this.#ac?.abort();
|
|
7383
|
+
if (this.#timer) {
|
|
7384
|
+
clearInterval(this.#timer);
|
|
7385
|
+
this.#timer = void 0;
|
|
7386
|
+
}
|
|
7387
|
+
this.#unsub?.();
|
|
7388
|
+
this.#closeUnsub?.();
|
|
7389
|
+
if (this.#ownsChannel) this.#channel?.close();
|
|
7390
|
+
this.#setRole("unknown");
|
|
7391
|
+
}
|
|
7392
|
+
/** @internal test seam — broadcast one heartbeat now. */
|
|
7393
|
+
_beat() {
|
|
7394
|
+
this.#beat();
|
|
7395
|
+
}
|
|
7396
|
+
#tick() {
|
|
7397
|
+
this.#prune();
|
|
7398
|
+
this.#emitTabs();
|
|
7399
|
+
this.#beat();
|
|
7400
|
+
}
|
|
7401
|
+
#beat() {
|
|
7402
|
+
if (this.#disposed) return;
|
|
7403
|
+
if (!this.#channel || !this.#channel.isOpen) return;
|
|
7404
|
+
const msg = { kind: "tab-presence", tabId: this.tabId, lastSeen: this.#now(), role: this.role };
|
|
7405
|
+
this.#channel.send(JSON.stringify(msg));
|
|
7406
|
+
}
|
|
7407
|
+
#onChannelClose() {
|
|
7408
|
+
if (this.#timer) {
|
|
7409
|
+
clearInterval(this.#timer);
|
|
7410
|
+
this.#timer = void 0;
|
|
7411
|
+
}
|
|
7412
|
+
this.#setRole("unknown");
|
|
7413
|
+
}
|
|
7414
|
+
#onMessage(payload) {
|
|
7415
|
+
let msg;
|
|
7416
|
+
try {
|
|
7417
|
+
msg = JSON.parse(payload);
|
|
7418
|
+
} catch {
|
|
7419
|
+
return;
|
|
7420
|
+
}
|
|
7421
|
+
if (!isPresenceMsg(msg) || msg.tabId === this.tabId) return;
|
|
7422
|
+
this.#peers.set(msg.tabId, { tabId: msg.tabId, lastSeen: msg.lastSeen, role: msg.role });
|
|
7423
|
+
this.#prune();
|
|
7424
|
+
this.#emitTabs();
|
|
7425
|
+
}
|
|
7426
|
+
#prune() {
|
|
7427
|
+
const cutoff = this.#now() - this.#staleMs;
|
|
7428
|
+
for (const [id, p] of this.#peers) if (p.lastSeen < cutoff) this.#peers.delete(id);
|
|
7429
|
+
}
|
|
7430
|
+
#setRole(role) {
|
|
7431
|
+
if (this.role === role) return;
|
|
7432
|
+
this.role = role;
|
|
7433
|
+
for (const h of this.#roleHandlers) h(role);
|
|
7434
|
+
this.#beat();
|
|
7435
|
+
this.#emitTabs();
|
|
7436
|
+
}
|
|
7437
|
+
#emitTabs() {
|
|
7438
|
+
const tabs = this.activeTabs();
|
|
7439
|
+
const sig = tabs.map((t) => `${t.tabId}:${t.role}`).join("|");
|
|
7440
|
+
if (sig === this.#lastTabsSig) return;
|
|
7441
|
+
this.#lastTabsSig = sig;
|
|
7442
|
+
for (const h of this.#tabsHandlers) h(tabs);
|
|
7443
|
+
}
|
|
7444
|
+
};
|
|
7445
|
+
function isPresenceMsg(x) {
|
|
7446
|
+
if (x === null || typeof x !== "object") return false;
|
|
7447
|
+
const o = x;
|
|
7448
|
+
return o["kind"] === "tab-presence" && typeof o["tabId"] === "string" && typeof o["lastSeen"] === "number" && (o["role"] === "primary" || o["role"] === "secondary" || o["role"] === "unknown");
|
|
7449
|
+
}
|
|
7450
|
+
function cheapRand() {
|
|
7451
|
+
const g = globalThis;
|
|
7452
|
+
return g.crypto?.randomUUID ? g.crypto.randomUUID().slice(0, 8) : "anon";
|
|
7453
|
+
}
|
|
7454
|
+
function defaultLockManager() {
|
|
7455
|
+
const nav = globalThis.navigator;
|
|
7456
|
+
return nav?.locks;
|
|
7457
|
+
}
|
|
7458
|
+
function defaultChannel(name = "noydb:tabs") {
|
|
7459
|
+
if (typeof globalThis.window === "undefined") return void 0;
|
|
7460
|
+
const Bc = globalThis.BroadcastChannel;
|
|
7461
|
+
if (!Bc) return void 0;
|
|
7462
|
+
const bc = new Bc(name);
|
|
7463
|
+
const msgListeners = /* @__PURE__ */ new Set();
|
|
7464
|
+
bc.onmessage = (e) => {
|
|
7465
|
+
for (const l of msgListeners) l(String(e.data));
|
|
7466
|
+
};
|
|
7467
|
+
return {
|
|
7468
|
+
isOpen: true,
|
|
7469
|
+
send(payload) {
|
|
7470
|
+
bc.postMessage(payload);
|
|
7471
|
+
},
|
|
7472
|
+
on(event, listener) {
|
|
7473
|
+
if (event === "message") {
|
|
7474
|
+
const l = listener;
|
|
7475
|
+
msgListeners.add(l);
|
|
7476
|
+
return () => msgListeners.delete(l);
|
|
7477
|
+
}
|
|
7478
|
+
return () => {
|
|
7479
|
+
};
|
|
7480
|
+
},
|
|
7481
|
+
close() {
|
|
7482
|
+
msgListeners.clear();
|
|
7483
|
+
bc.close();
|
|
7484
|
+
}
|
|
7485
|
+
};
|
|
7486
|
+
}
|
|
7487
|
+
|
|
7488
|
+
// src/tab-write-relay.ts
|
|
7489
|
+
var CrossTabWriteRelay = class {
|
|
7490
|
+
#channel;
|
|
7491
|
+
#writerId;
|
|
7492
|
+
#subscribeAfterWrite;
|
|
7493
|
+
#applyRemoteWrite;
|
|
7494
|
+
#reportConflict;
|
|
7495
|
+
#ledger = /* @__PURE__ */ new Map();
|
|
7496
|
+
#ownsChannel;
|
|
7497
|
+
#unsubMsg;
|
|
7498
|
+
#unsubWrite;
|
|
7499
|
+
#started = false;
|
|
7500
|
+
#disposed = false;
|
|
7501
|
+
constructor(opts) {
|
|
7502
|
+
this.#channel = opts.channel;
|
|
7503
|
+
this.#writerId = opts.writerId;
|
|
7504
|
+
this.#subscribeAfterWrite = opts.subscribeAfterWrite;
|
|
7505
|
+
this.#applyRemoteWrite = opts.applyRemoteWrite;
|
|
7506
|
+
this.#reportConflict = opts.reportConflict;
|
|
7507
|
+
this.#ownsChannel = opts.closeChannelOnDispose ?? false;
|
|
7508
|
+
}
|
|
7509
|
+
start() {
|
|
7510
|
+
if (this.#started || this.#disposed) return;
|
|
7511
|
+
this.#started = true;
|
|
7512
|
+
this.#unsubMsg = this.#channel.on("message", (p) => this.#onMessage(p));
|
|
7513
|
+
this.#unsubWrite = this.#subscribeAfterWrite((e) => this.#onLocalWrite(e));
|
|
7514
|
+
}
|
|
7515
|
+
dispose() {
|
|
7516
|
+
if (this.#disposed) return;
|
|
7517
|
+
this.#disposed = true;
|
|
7518
|
+
this.#unsubWrite?.();
|
|
7519
|
+
this.#unsubMsg?.();
|
|
7520
|
+
if (this.#ownsChannel) this.#channel.close();
|
|
7521
|
+
}
|
|
7522
|
+
#onLocalWrite(e) {
|
|
7523
|
+
if (this.#disposed || !this.#channel.isOpen) return;
|
|
7524
|
+
this.#ledger.set(ledgerKey(e.vault, e.collection, e.docId), e.version);
|
|
7525
|
+
const action = e.op === "delete" ? "delete" : "put";
|
|
7526
|
+
const msg = { kind: "tab-write", writerId: this.#writerId, vault: e.vault, collection: e.collection, docId: e.docId, action, baseV: e.baseVersion, v: e.version };
|
|
7527
|
+
this.#channel.send(JSON.stringify(msg));
|
|
7528
|
+
}
|
|
7529
|
+
#onMessage(payload) {
|
|
7530
|
+
if (this.#disposed) return;
|
|
7531
|
+
let msg;
|
|
7532
|
+
try {
|
|
7533
|
+
msg = JSON.parse(payload);
|
|
7534
|
+
} catch {
|
|
7535
|
+
return;
|
|
7536
|
+
}
|
|
7537
|
+
if (!isTabWriteMsg(msg) || msg.writerId === this.#writerId) return;
|
|
7538
|
+
const key = ledgerKey(msg.vault, msg.collection, msg.docId);
|
|
7539
|
+
const ownV = this.#ledger.get(key);
|
|
7540
|
+
if (ownV !== void 0 && msg.baseV < ownV && this.#reportConflict) {
|
|
7541
|
+
void Promise.resolve(this.#reportConflict(msg.vault, msg.collection, msg.docId, msg.action, msg.baseV, msg.v, ownV)).catch((err) => {
|
|
7542
|
+
console.warn(`[noy-db] cross-tab conflict report failed for ${msg.collection}/${msg.docId}: ` + (err instanceof Error ? err.message : String(err)));
|
|
7543
|
+
});
|
|
7544
|
+
return;
|
|
7545
|
+
}
|
|
7546
|
+
if (ownV !== void 0 && msg.baseV >= ownV) this.#ledger.set(key, msg.v);
|
|
7547
|
+
void Promise.resolve(this.#applyRemoteWrite(msg.vault, msg.collection, msg.docId, msg.action)).catch((err) => {
|
|
7548
|
+
console.warn(`[noy-db] cross-tab apply failed for ${msg.collection}/${msg.docId}: ` + (err instanceof Error ? err.message : String(err)));
|
|
7549
|
+
});
|
|
7550
|
+
}
|
|
7551
|
+
};
|
|
7552
|
+
function ledgerKey(vault, collection, docId) {
|
|
7553
|
+
return `${vault}\0${collection}\0${docId}`;
|
|
7554
|
+
}
|
|
7555
|
+
function isTabWriteMsg(x) {
|
|
7556
|
+
if (x === null || typeof x !== "object") return false;
|
|
7557
|
+
const o = x;
|
|
7558
|
+
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";
|
|
7559
|
+
}
|
|
7560
|
+
|
|
6584
7561
|
// src/session/unlock-state.ts
|
|
6585
7562
|
var QuickUnlockStore = class {
|
|
6586
7563
|
states = /* @__PURE__ */ new Map();
|
|
@@ -6629,6 +7606,9 @@ var NOT_ENABLED4 = new Error(
|
|
|
6629
7606
|
var NO_TX = {
|
|
6630
7607
|
async runTransaction() {
|
|
6631
7608
|
throw NOT_ENABLED4;
|
|
7609
|
+
},
|
|
7610
|
+
async runDryRun() {
|
|
7611
|
+
throw NOT_ENABLED4;
|
|
6632
7612
|
}
|
|
6633
7613
|
};
|
|
6634
7614
|
|
|
@@ -6926,6 +7906,9 @@ function createPlaintextKeyring(userId) {
|
|
|
6926
7906
|
var Noydb = class {
|
|
6927
7907
|
options;
|
|
6928
7908
|
emitter = new NoydbEventEmitter();
|
|
7909
|
+
writeQueueTracker = new WriteQueueTracker();
|
|
7910
|
+
writeHooks = new WriteHookRegistry();
|
|
7911
|
+
clientId = generateULID();
|
|
6929
7912
|
vaultCache = /* @__PURE__ */ new Map();
|
|
6930
7913
|
keyringCache = /* @__PURE__ */ new Map();
|
|
6931
7914
|
syncEngines = /* @__PURE__ */ new Map();
|
|
@@ -6958,6 +7941,10 @@ var Noydb = class {
|
|
|
6958
7941
|
publicEnvelopeSchema;
|
|
6959
7942
|
closed = false;
|
|
6960
7943
|
sessionTimer = null;
|
|
7944
|
+
/** Same-device multi-tab coordinator (#228); created on `enableTabCoordination()`. */
|
|
7945
|
+
tabCoordinator;
|
|
7946
|
+
/** Cross-tab write relay (#228b); created on `enableTabCoordination()`. */
|
|
7947
|
+
writeRelay;
|
|
6961
7948
|
/** Per-vault policy enforcers. */
|
|
6962
7949
|
policyEnforcers = /* @__PURE__ */ new Map();
|
|
6963
7950
|
txStrategy;
|
|
@@ -7150,6 +8137,7 @@ var Noydb = class {
|
|
|
7150
8137
|
await comp._initDerivations(this.options.derivationStrategies ?? []);
|
|
7151
8138
|
await comp._initMaterializedViews(this.options.materializedViewStrategies ?? []);
|
|
7152
8139
|
await comp._initOverlayedViews(this.options.overlayedViewStrategies ?? []);
|
|
8140
|
+
await comp.schemaFence.init();
|
|
7153
8141
|
this.vaultCache.set(name, comp);
|
|
7154
8142
|
return comp;
|
|
7155
8143
|
}
|
|
@@ -7577,6 +8565,14 @@ var Noydb = class {
|
|
|
7577
8565
|
if (typeof arg === "function") {
|
|
7578
8566
|
return this.txStrategy.runTransaction(this, arg);
|
|
7579
8567
|
}
|
|
8568
|
+
if (typeof arg === "object" && arg !== null && arg.dryRun === true) {
|
|
8569
|
+
if (typeof maybeFn !== "function") {
|
|
8570
|
+
throw new ValidationError(
|
|
8571
|
+
"db.transaction({ dryRun: true }, fn) requires the callback as the second argument."
|
|
8572
|
+
);
|
|
8573
|
+
}
|
|
8574
|
+
return this.txStrategy.runDryRun(this, maybeFn);
|
|
8575
|
+
}
|
|
7580
8576
|
if (typeof arg === "object" && arg !== null && arg.amendment === true) {
|
|
7581
8577
|
if (typeof maybeFn !== "function") {
|
|
7582
8578
|
throw new ValidationError(
|
|
@@ -7689,6 +8685,133 @@ var Noydb = class {
|
|
|
7689
8685
|
off(event, handler) {
|
|
7690
8686
|
this.emitter.off(event, handler);
|
|
7691
8687
|
}
|
|
8688
|
+
/**
|
|
8689
|
+
* Observable write-queue for this hub instance. Reflects outstanding
|
|
8690
|
+
* in-flight writes across all collections. See {@link WriteQueue}.
|
|
8691
|
+
*
|
|
8692
|
+
* @example
|
|
8693
|
+
* window.addEventListener('beforeunload', (e) => {
|
|
8694
|
+
* if (db.writeQueue.pending) { e.preventDefault(); e.returnValue = '' }
|
|
8695
|
+
* })
|
|
8696
|
+
*/
|
|
8697
|
+
get writeQueue() {
|
|
8698
|
+
return this.writeQueueTracker;
|
|
8699
|
+
}
|
|
8700
|
+
/**
|
|
8701
|
+
* @internal Mutable tracker behind {@link writeQueue}. Threaded into
|
|
8702
|
+
* each Collection (via Vault) so `put`/`delete` can `track()` writes.
|
|
8703
|
+
* Not part of the public surface — consumers use `writeQueue`.
|
|
8704
|
+
*/
|
|
8705
|
+
get _writeQueueTracker() {
|
|
8706
|
+
return this.writeQueueTracker;
|
|
8707
|
+
}
|
|
8708
|
+
/**
|
|
8709
|
+
* Register a hook that runs before each write (#230). Awaited; a throw
|
|
8710
|
+
* aborts the write. Returns an unsubscribe function.
|
|
8711
|
+
*/
|
|
8712
|
+
onBeforeWrite(handler) {
|
|
8713
|
+
return this.writeHooks.onBeforeWrite(handler);
|
|
8714
|
+
}
|
|
8715
|
+
/**
|
|
8716
|
+
* Register a hook that runs after each committed write (#230). Awaited;
|
|
8717
|
+
* a handler error is warned, never rolled back. Returns an unsubscribe fn.
|
|
8718
|
+
*/
|
|
8719
|
+
onAfterWrite(handler) {
|
|
8720
|
+
return this.writeHooks.onAfterWrite(handler);
|
|
8721
|
+
}
|
|
8722
|
+
/** Subscribe to cross-tab write conflicts (#228c). Returns an unsubscribe. */
|
|
8723
|
+
onWriteConflict(fn) {
|
|
8724
|
+
this.on("write:conflict", fn);
|
|
8725
|
+
return () => this.off("write:conflict", fn);
|
|
8726
|
+
}
|
|
8727
|
+
/**
|
|
8728
|
+
* Enable same-device multi-tab coordination (#228): primary/secondary
|
|
8729
|
+
* election + presence. Browser-only — a graceful no-op (role 'unknown')
|
|
8730
|
+
* when Web Locks / BroadcastChannel are unavailable and nothing is
|
|
8731
|
+
* injected. Idempotent; returns a disposer.
|
|
8732
|
+
*/
|
|
8733
|
+
enableTabCoordination(opts = {}) {
|
|
8734
|
+
if (this.tabCoordinator) return { dispose: () => this.disableTabCoordination() };
|
|
8735
|
+
const lockManager = opts.lockManager ?? defaultLockManager();
|
|
8736
|
+
const channel = opts.channel ?? defaultChannel();
|
|
8737
|
+
const c = new TabCoordinator({
|
|
8738
|
+
...opts,
|
|
8739
|
+
...lockManager ? { lockManager } : {},
|
|
8740
|
+
...channel ? { channel } : {},
|
|
8741
|
+
// We own the channel only when we created the default; never close a caller-injected one.
|
|
8742
|
+
closeChannelOnDispose: opts.channel === void 0 && channel !== void 0
|
|
8743
|
+
});
|
|
8744
|
+
this.tabCoordinator = c;
|
|
8745
|
+
c.start();
|
|
8746
|
+
if (opts.propagateWrites !== false) {
|
|
8747
|
+
const writeChannel = opts.writeChannel ?? defaultChannel("noydb:tab-writes");
|
|
8748
|
+
if (writeChannel) {
|
|
8749
|
+
const relay = new CrossTabWriteRelay({
|
|
8750
|
+
channel: writeChannel,
|
|
8751
|
+
writerId: c.tabId,
|
|
8752
|
+
subscribeAfterWrite: (h) => this.onAfterWrite(h),
|
|
8753
|
+
applyRemoteWrite: (vault, collection, docId, action) => this.#applyRemoteWrite(vault, collection, docId, action),
|
|
8754
|
+
reportConflict: (vault, collection, docId, action, baseV, v, ownV) => this.#reportWriteConflict(vault, collection, docId, action, baseV, v, ownV),
|
|
8755
|
+
// Own the channel only when we created the default (mirrors the presence channel).
|
|
8756
|
+
closeChannelOnDispose: opts.writeChannel === void 0 && writeChannel !== void 0
|
|
8757
|
+
});
|
|
8758
|
+
this.writeRelay = relay;
|
|
8759
|
+
relay.start();
|
|
8760
|
+
}
|
|
8761
|
+
}
|
|
8762
|
+
return { dispose: () => this.disableTabCoordination() };
|
|
8763
|
+
}
|
|
8764
|
+
#applyRemoteWrite(vaultName, collectionName, docId, action) {
|
|
8765
|
+
const v = this.vaultCache.get(vaultName);
|
|
8766
|
+
if (!v) return Promise.resolve();
|
|
8767
|
+
return v._applyRemoteWrite(collectionName, docId, action);
|
|
8768
|
+
}
|
|
8769
|
+
async #reportWriteConflict(vaultName, collectionName, docId, action, baseV, v, ownV) {
|
|
8770
|
+
const vault = this.vaultCache.get(vaultName);
|
|
8771
|
+
if (!vault) return;
|
|
8772
|
+
const cap = await vault._captureAndConverge(collectionName, docId, action, baseV);
|
|
8773
|
+
if (!cap) return;
|
|
8774
|
+
const conflict = {
|
|
8775
|
+
vault: vaultName,
|
|
8776
|
+
collection: collectionName,
|
|
8777
|
+
docId,
|
|
8778
|
+
local: cap.local,
|
|
8779
|
+
remote: cap.remote,
|
|
8780
|
+
base: cap.base,
|
|
8781
|
+
localVersion: ownV,
|
|
8782
|
+
remoteVersion: v,
|
|
8783
|
+
baseVersion: baseV
|
|
8784
|
+
};
|
|
8785
|
+
this.emitter.emit("write:conflict", conflict);
|
|
8786
|
+
}
|
|
8787
|
+
disableTabCoordination() {
|
|
8788
|
+
this.tabCoordinator?.dispose();
|
|
8789
|
+
this.tabCoordinator = void 0;
|
|
8790
|
+
this.writeRelay?.dispose();
|
|
8791
|
+
this.writeRelay = void 0;
|
|
8792
|
+
}
|
|
8793
|
+
get tabRole() {
|
|
8794
|
+
return this.tabCoordinator?.role ?? "unknown";
|
|
8795
|
+
}
|
|
8796
|
+
activeTabs() {
|
|
8797
|
+
return this.tabCoordinator?.activeTabs() ?? [];
|
|
8798
|
+
}
|
|
8799
|
+
onTabRoleChange(fn) {
|
|
8800
|
+
return this.tabCoordinator?.onTabRoleChange(fn) ?? (() => {
|
|
8801
|
+
});
|
|
8802
|
+
}
|
|
8803
|
+
onActiveTabsChange(fn) {
|
|
8804
|
+
return this.tabCoordinator?.onActiveTabsChange(fn) ?? (() => {
|
|
8805
|
+
});
|
|
8806
|
+
}
|
|
8807
|
+
/** @internal The write-hook registry, threaded into each Collection. */
|
|
8808
|
+
get _writeHooks() {
|
|
8809
|
+
return this.writeHooks;
|
|
8810
|
+
}
|
|
8811
|
+
/** @internal Stable per-instance id for schema-cutover coordination (#232). */
|
|
8812
|
+
get _clientId() {
|
|
8813
|
+
return this.clientId;
|
|
8814
|
+
}
|
|
7692
8815
|
/**
|
|
7693
8816
|
* Soft-lock a single vault: clear its in-memory keyring, DEKs, vault
|
|
7694
8817
|
* instance, sync engine, policy enforcer, and active-tier entry —
|
|
@@ -7715,6 +8838,7 @@ var Noydb = class {
|
|
|
7715
8838
|
this.syncEngines.delete(vault);
|
|
7716
8839
|
this.policyEnforcers.get(vault)?.destroy();
|
|
7717
8840
|
this.policyEnforcers.delete(vault);
|
|
8841
|
+
this.vaultCache.get(vault)?._stopFenceCoordination();
|
|
7718
8842
|
this.keyringCache.delete(vault);
|
|
7719
8843
|
this.vaultCache.delete(vault);
|
|
7720
8844
|
this.activeTier.delete(vault);
|
|
@@ -7734,6 +8858,8 @@ var Noydb = class {
|
|
|
7734
8858
|
engine.stopAutoSync();
|
|
7735
8859
|
}
|
|
7736
8860
|
this.syncEngines.clear();
|
|
8861
|
+
for (const v of this.vaultCache.values()) v._stopFenceCoordination();
|
|
8862
|
+
this.disableTabCoordination();
|
|
7737
8863
|
this.keyringCache.clear();
|
|
7738
8864
|
this.vaultCache.clear();
|
|
7739
8865
|
this.activeTier.clear();
|
|
@@ -8831,4 +9957,4 @@ export {
|
|
|
8831
9957
|
Noydb,
|
|
8832
9958
|
createNoydb
|
|
8833
9959
|
};
|
|
8834
|
-
//# sourceMappingURL=chunk-
|
|
9960
|
+
//# sourceMappingURL=chunk-T6HQMVML.js.map
|