@noy-db/hub 0.2.0-pre.26 → 0.2.0-pre.28
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 +7 -0
- package/dist/aggregate/index.cjs.map +1 -1
- package/dist/aggregate/index.d.cts +2 -2
- package/dist/aggregate/index.d.ts +2 -2
- package/dist/aggregate/index.js +2 -2
- package/dist/attestation/index.cjs.map +1 -1
- package/dist/attestation/index.d.cts +3 -3
- package/dist/attestation/index.d.ts +3 -3
- package/dist/attestation/index.js +4 -4
- package/dist/blobs/index.cjs.map +1 -1
- package/dist/blobs/index.d.cts +5 -5
- package/dist/blobs/index.d.ts +5 -5
- package/dist/blobs/index.js +4 -4
- package/dist/bundle/index.cjs +403 -149
- package/dist/bundle/index.cjs.map +1 -1
- package/dist/bundle/index.d.cts +5 -5
- package/dist/bundle/index.d.ts +5 -5
- package/dist/bundle/index.js +8 -8
- package/dist/{chunk-PS6PSEZL.js → chunk-2M62POB5.js} +3 -3
- package/dist/{chunk-C7UIT5XY.js → chunk-345PYZD6.js} +2 -2
- package/dist/{chunk-56ENKU46.js → chunk-3LHTFCU2.js} +186 -161
- package/dist/chunk-3LHTFCU2.js.map +1 -0
- package/dist/{chunk-ANLOD6IS.js → chunk-42EGY2FQ.js} +3 -3
- package/dist/{chunk-KJ37E3R5.js → chunk-4FHKQDHN.js} +2 -2
- package/dist/{chunk-KD253AI5.js → chunk-6LD37AMK.js} +22 -3
- package/dist/chunk-6LD37AMK.js.map +1 -0
- package/dist/{chunk-EYZJULEN.js → chunk-7GKDVWMD.js} +2 -2
- package/dist/{chunk-N4EXCKWP.js → chunk-7TBCLM52.js} +2 -2
- package/dist/{chunk-VJNV2GRF.js → chunk-AHDQYG7P.js} +3 -3
- package/dist/{chunk-ZCBJIDT4.js → chunk-DCYWBKQ2.js} +2 -2
- package/dist/{chunk-Y5CTT6K5.js → chunk-DMWUOV7X.js} +2 -2
- package/dist/{chunk-RZOGD7IF.js → chunk-DQ6XF2K2.js} +6 -6
- package/dist/{chunk-OCRDV3NU.js → chunk-ENC4C6XW.js} +3 -3
- package/dist/{chunk-FCIZXX56.js → chunk-GVEUI7VR.js} +2 -2
- package/dist/{chunk-WVYL6HM7.js → chunk-HI7FY7QZ.js} +2 -2
- package/dist/{chunk-JJKXJAH2.js → chunk-HQ242WNG.js} +3 -3
- package/dist/{chunk-GHXOVGTX.js → chunk-IMY4FXYE.js} +3 -3
- package/dist/{chunk-YP2AYE5W.js → chunk-JEQB2KVI.js} +2 -2
- package/dist/chunk-M2VZQ24Q.js +220 -0
- package/dist/chunk-M2VZQ24Q.js.map +1 -0
- package/dist/{chunk-HUXDQIVU.js → chunk-PPF7Z2YQ.js} +2 -2
- package/dist/{chunk-2RHBFCWQ.js → chunk-QBC2TZHC.js} +3 -3
- package/dist/{chunk-LR7CODVN.js → chunk-QSLIT4JZ.js} +1 -1
- package/dist/chunk-QSLIT4JZ.js.map +1 -0
- package/dist/{chunk-TSUICI5N.js → chunk-RG5KLMEU.js} +2 -2
- package/dist/{chunk-GPZHHTJU.js → chunk-SWGVCSIY.js} +2 -2
- package/dist/{chunk-UNBX2HMA.js → chunk-VSL3W2MO.js} +2 -2
- package/dist/{chunk-VGAN5RLD.js → chunk-WH7G2YEE.js} +2 -2
- package/dist/{chunk-QYQRAOEF.js → chunk-YYLFXX2K.js} +2 -2
- package/dist/consent/index.d.cts +4 -4
- package/dist/consent/index.d.ts +4 -4
- package/dist/{decrypt-partition-CyyJUWLR.d.ts → decrypt-partition-BtGtE-19.d.ts} +1 -1
- package/dist/{decrypt-partition-C71vhnND.d.cts → decrypt-partition-Cqi5gcOl.d.cts} +1 -1
- package/dist/derivations/index.d.cts +5 -5
- package/dist/derivations/index.d.ts +5 -5
- package/dist/{dev-unlock-BdrE0kbS.d.cts → dev-unlock-Cts_iiVv.d.cts} +1 -1
- package/dist/{dev-unlock-ByBkl99-.d.ts → dev-unlock-D0p9cQzN.d.ts} +1 -1
- package/dist/{executor-BIW4FT5R.js → executor-PJHMRZWJ.js} +4 -4
- package/dist/{fanout-sidecar-ZQT4Y7PF.js → fanout-sidecar-DSBVAR2P.js} +2 -2
- package/dist/forget/index.js +2 -2
- package/dist/guards/index.d.cts +5 -5
- package/dist/guards/index.d.ts +5 -5
- package/dist/{hash-CZxVv8RH.d.ts → hash-BtDtwguU.d.ts} +1 -1
- package/dist/{hash-BUkDp_8Q.d.cts → hash-DA75XwW2.d.cts} +1 -1
- package/dist/history/index.cjs.map +1 -1
- package/dist/history/index.d.cts +5 -5
- package/dist/history/index.d.ts +5 -5
- package/dist/history/index.js +3 -3
- package/dist/i18n/index.cjs +98 -1
- package/dist/i18n/index.cjs.map +1 -1
- package/dist/i18n/index.d.cts +4 -4
- package/dist/i18n/index.d.ts +4 -4
- package/dist/i18n/index.js +83 -4
- package/dist/i18n/index.js.map +1 -1
- package/dist/{index-DFhKV-6A.d.ts → index-BidHvmWf.d.ts} +3 -3
- package/dist/{index-CBUhOmrM.d.cts → index-CP24aYCp.d.cts} +3 -3
- package/dist/{index-DoxKSsMj.d.cts → index-CVnt2Qmq.d.cts} +1 -1
- package/dist/{index-LaexBi3v.d.ts → index-DxBNV54L.d.ts} +1 -1
- package/dist/index.cjs +411 -146
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -14
- package/dist/index.d.ts +14 -14
- package/dist/index.js +24 -23
- package/dist/index.js.map +1 -1
- package/dist/{issue-LEBPVF3Y.js → issue-JSGGSVY4.js} +4 -4
- package/dist/kernel/index.cjs +61 -0
- package/dist/kernel/index.cjs.map +1 -1
- package/dist/kernel/index.d.cts +4 -4
- package/dist/kernel/index.d.ts +4 -4
- package/dist/kernel/index.js +9 -2
- package/dist/{ledger-FLRTSOYH.js → ledger-PLMSH7LD.js} +3 -3
- package/dist/materialized-views/index.cjs +7 -0
- 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 +4 -4
- package/dist/{mime-magic-C1UbcBxP.d.ts → mime-magic--NcogI82.d.ts} +1 -1
- package/dist/{mime-magic-BAhLjkHw.d.cts → mime-magic-i2VSlkPM.d.cts} +1 -1
- package/dist/noydb-PHXA5E6I.js +39 -0
- package/dist/overlay-views/index.d.cts +5 -5
- package/dist/overlay-views/index.d.ts +5 -5
- package/dist/periods/index.cjs.map +1 -1
- package/dist/periods/index.d.cts +4 -4
- package/dist/periods/index.d.ts +4 -4
- package/dist/periods/index.js +3 -3
- package/dist/{public-envelope-DBKJEBBF.js → public-envelope-7MTH2PVE.js} +3 -3
- package/dist/query/index.cjs +7 -0
- package/dist/query/index.cjs.map +1 -1
- package/dist/query/index.d.cts +2 -2
- package/dist/query/index.d.ts +2 -2
- package/dist/query/index.js +3 -3
- package/dist/{revoke-P5D3UTRX.js → revoke-DBAGKIDA.js} +4 -4
- package/dist/session/index.d.cts +5 -5
- package/dist/session/index.d.ts +5 -5
- package/dist/shadow/index.d.cts +4 -4
- package/dist/shadow/index.d.ts +4 -4
- package/dist/{signer-NEQPCHMW.js → signer-ADFJNS5W.js} +3 -3
- package/dist/snapshots/index.d.cts +4 -4
- package/dist/snapshots/index.d.ts +4 -4
- package/dist/snapshots/index.js +3 -3
- package/dist/{stale-KKCHF2VB.js → stale-BCIE3SMC.js} +2 -2
- package/dist/store/index.d.cts +4 -4
- package/dist/store/index.d.ts +4 -4
- package/dist/{strategy-YQ1qJWyq.d.ts → strategy-BoITAb2H.d.ts} +20 -1
- package/dist/{strategy-D1zjEV3n.d.cts → strategy-DDNvt_UD.d.cts} +20 -1
- package/dist/sync/index.cjs.map +1 -1
- package/dist/sync/index.d.cts +3 -3
- package/dist/sync/index.d.ts +3 -3
- package/dist/sync/index.js +2 -2
- package/dist/team/index.cjs.map +1 -1
- package/dist/team/index.d.cts +4 -4
- package/dist/team/index.d.ts +4 -4
- package/dist/team/index.js +5 -5
- package/dist/{transition-guard-BSLdikC_.d.ts → transition-guard-BZhOlrFT.d.ts} +1 -1
- package/dist/{transition-guard-DPs6al8h.d.cts → transition-guard-Bekgaxux.d.cts} +1 -1
- package/dist/tx/index.d.cts +4 -4
- package/dist/tx/index.d.ts +4 -4
- package/dist/{types-CCq0WHh9.d.ts → types-84nsWSDF.d.ts} +182 -11
- package/dist/{types-BCYvhKzr.d.cts → types-B_eCkuEI.d.cts} +182 -11
- package/dist/{with-materialized-view-DiD41wQp.d.ts → with-materialized-view-CAMGQroY.d.ts} +1 -1
- package/dist/{with-materialized-view-CTHe6uh9.d.cts → with-materialized-view-DKMaZmkJ.d.cts} +1 -1
- package/dist/{with-overlayed-view-Dlz5hcM8.d.cts → with-overlayed-view--lWXImbV.d.cts} +1 -1
- package/dist/{with-overlayed-view-DlbsJMhF.d.ts → with-overlayed-view-FnOf1v6H.d.ts} +1 -1
- package/dist/{with-rollup-BBWdrCvu.d.cts → with-rollup-C5875Ad5.d.cts} +1 -1
- package/dist/{with-rollup-mT4_CWaU.d.ts → with-rollup-xi-mkY6Y.d.ts} +1 -1
- package/package.json +3 -3
- package/dist/chunk-56ENKU46.js.map +0 -1
- package/dist/chunk-KD253AI5.js.map +0 -1
- package/dist/chunk-LR7CODVN.js.map +0 -1
- package/dist/noydb-6FA46A4M.js +0 -38
- /package/dist/{chunk-PS6PSEZL.js.map → chunk-2M62POB5.js.map} +0 -0
- /package/dist/{chunk-C7UIT5XY.js.map → chunk-345PYZD6.js.map} +0 -0
- /package/dist/{chunk-ANLOD6IS.js.map → chunk-42EGY2FQ.js.map} +0 -0
- /package/dist/{chunk-KJ37E3R5.js.map → chunk-4FHKQDHN.js.map} +0 -0
- /package/dist/{chunk-EYZJULEN.js.map → chunk-7GKDVWMD.js.map} +0 -0
- /package/dist/{chunk-N4EXCKWP.js.map → chunk-7TBCLM52.js.map} +0 -0
- /package/dist/{chunk-VJNV2GRF.js.map → chunk-AHDQYG7P.js.map} +0 -0
- /package/dist/{chunk-ZCBJIDT4.js.map → chunk-DCYWBKQ2.js.map} +0 -0
- /package/dist/{chunk-Y5CTT6K5.js.map → chunk-DMWUOV7X.js.map} +0 -0
- /package/dist/{chunk-RZOGD7IF.js.map → chunk-DQ6XF2K2.js.map} +0 -0
- /package/dist/{chunk-OCRDV3NU.js.map → chunk-ENC4C6XW.js.map} +0 -0
- /package/dist/{chunk-FCIZXX56.js.map → chunk-GVEUI7VR.js.map} +0 -0
- /package/dist/{chunk-WVYL6HM7.js.map → chunk-HI7FY7QZ.js.map} +0 -0
- /package/dist/{chunk-JJKXJAH2.js.map → chunk-HQ242WNG.js.map} +0 -0
- /package/dist/{chunk-GHXOVGTX.js.map → chunk-IMY4FXYE.js.map} +0 -0
- /package/dist/{chunk-YP2AYE5W.js.map → chunk-JEQB2KVI.js.map} +0 -0
- /package/dist/{chunk-HUXDQIVU.js.map → chunk-PPF7Z2YQ.js.map} +0 -0
- /package/dist/{chunk-2RHBFCWQ.js.map → chunk-QBC2TZHC.js.map} +0 -0
- /package/dist/{chunk-TSUICI5N.js.map → chunk-RG5KLMEU.js.map} +0 -0
- /package/dist/{chunk-GPZHHTJU.js.map → chunk-SWGVCSIY.js.map} +0 -0
- /package/dist/{chunk-UNBX2HMA.js.map → chunk-VSL3W2MO.js.map} +0 -0
- /package/dist/{chunk-VGAN5RLD.js.map → chunk-WH7G2YEE.js.map} +0 -0
- /package/dist/{chunk-QYQRAOEF.js.map → chunk-YYLFXX2K.js.map} +0 -0
- /package/dist/{executor-BIW4FT5R.js.map → executor-PJHMRZWJ.js.map} +0 -0
- /package/dist/{fanout-sidecar-ZQT4Y7PF.js.map → fanout-sidecar-DSBVAR2P.js.map} +0 -0
- /package/dist/{issue-LEBPVF3Y.js.map → issue-JSGGSVY4.js.map} +0 -0
- /package/dist/{ledger-FLRTSOYH.js.map → ledger-PLMSH7LD.js.map} +0 -0
- /package/dist/{noydb-6FA46A4M.js.map → noydb-PHXA5E6I.js.map} +0 -0
- /package/dist/{public-envelope-DBKJEBBF.js.map → public-envelope-7MTH2PVE.js.map} +0 -0
- /package/dist/{revoke-P5D3UTRX.js.map → revoke-DBAGKIDA.js.map} +0 -0
- /package/dist/{signer-NEQPCHMW.js.map → signer-ADFJNS5W.js.map} +0 -0
- /package/dist/{stale-KKCHF2VB.js.map → stale-BCIE3SMC.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -7255,6 +7255,249 @@ var init_peer_recover = __esm({
|
|
|
7255
7255
|
}
|
|
7256
7256
|
});
|
|
7257
7257
|
|
|
7258
|
+
// src/coordination/types.ts
|
|
7259
|
+
function isQuorum(writers, generation, excludeWriterId) {
|
|
7260
|
+
return writers.filter((w) => w.writerId !== excludeWriterId).every((w) => w.quiescedAtVersion === generation);
|
|
7261
|
+
}
|
|
7262
|
+
async function runDrainBarrier(provider, o, run) {
|
|
7263
|
+
await provider.setFence(o.vault, { currentSchemaVersion: o.generation, fenceState: "draining" });
|
|
7264
|
+
await o.onFlush();
|
|
7265
|
+
const deadline = o.now() + o.quiesceTimeoutMs;
|
|
7266
|
+
const seeded = await provider.reachableWriters(o.vault, { staleMs: o.staleMs, now: o.now() });
|
|
7267
|
+
if (!isQuorum(seeded, o.generation, o.writerId)) {
|
|
7268
|
+
await new Promise((resolve, reject) => {
|
|
7269
|
+
let settled = false;
|
|
7270
|
+
const finish = (fn) => {
|
|
7271
|
+
if (!settled) {
|
|
7272
|
+
settled = true;
|
|
7273
|
+
unsub();
|
|
7274
|
+
fn();
|
|
7275
|
+
}
|
|
7276
|
+
};
|
|
7277
|
+
const unsub = provider.observePresence(o.vault, (writers) => {
|
|
7278
|
+
if (isQuorum(writers, o.generation, o.writerId)) finish(resolve);
|
|
7279
|
+
});
|
|
7280
|
+
const tick = async () => {
|
|
7281
|
+
if (settled) return;
|
|
7282
|
+
if (o.now() >= deadline) {
|
|
7283
|
+
finish(
|
|
7284
|
+
() => reject(
|
|
7285
|
+
new QuiesceTimeoutError(
|
|
7286
|
+
`Cutover of vault "${o.vault}" to generation ${o.generation} timed out after ${o.quiesceTimeoutMs}ms waiting for active writers to quiesce.`
|
|
7287
|
+
)
|
|
7288
|
+
)
|
|
7289
|
+
);
|
|
7290
|
+
return;
|
|
7291
|
+
}
|
|
7292
|
+
if (o.onPoll) await o.onPoll();
|
|
7293
|
+
const w = await provider.reachableWriters(o.vault, { staleMs: o.staleMs, now: o.now() });
|
|
7294
|
+
if (isQuorum(w, o.generation, o.writerId)) finish(resolve);
|
|
7295
|
+
else setTimeout(() => void tick(), 25);
|
|
7296
|
+
};
|
|
7297
|
+
void tick();
|
|
7298
|
+
});
|
|
7299
|
+
}
|
|
7300
|
+
await run();
|
|
7301
|
+
}
|
|
7302
|
+
var init_types4 = __esm({
|
|
7303
|
+
"src/coordination/types.ts"() {
|
|
7304
|
+
"use strict";
|
|
7305
|
+
init_errors();
|
|
7306
|
+
}
|
|
7307
|
+
});
|
|
7308
|
+
|
|
7309
|
+
// src/schema-update/fence.ts
|
|
7310
|
+
async function loadFence(store, vault) {
|
|
7311
|
+
const envelope = await store.get(vault, META_COLLECTION3, FENCE_RECORD_ID);
|
|
7312
|
+
if (!envelope) return DEFAULT_FENCE;
|
|
7313
|
+
try {
|
|
7314
|
+
const parsed = JSON.parse(envelope._data);
|
|
7315
|
+
if (!isFenceDoc(parsed)) return DEFAULT_FENCE;
|
|
7316
|
+
return parsed;
|
|
7317
|
+
} catch {
|
|
7318
|
+
return DEFAULT_FENCE;
|
|
7319
|
+
}
|
|
7320
|
+
}
|
|
7321
|
+
async function saveFence(store, vault, fence) {
|
|
7322
|
+
const envelope = {
|
|
7323
|
+
_noydb: NOYDB_FORMAT_VERSION,
|
|
7324
|
+
_v: 1,
|
|
7325
|
+
_ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7326
|
+
_iv: "",
|
|
7327
|
+
_data: JSON.stringify(fence)
|
|
7328
|
+
};
|
|
7329
|
+
await store.put(vault, META_COLLECTION3, FENCE_RECORD_ID, envelope);
|
|
7330
|
+
}
|
|
7331
|
+
function isFenceDoc(x) {
|
|
7332
|
+
if (x === null || typeof x !== "object") return false;
|
|
7333
|
+
const o = x;
|
|
7334
|
+
return typeof o["currentSchemaVersion"] === "number" && (o["fenceState"] === "normal" || o["fenceState"] === "draining" || o["fenceState"] === "migrating" || o["fenceState"] === "complete");
|
|
7335
|
+
}
|
|
7336
|
+
var FENCE_RECORD_ID, META_COLLECTION3, DEFAULT_FENCE;
|
|
7337
|
+
var init_fence = __esm({
|
|
7338
|
+
"src/schema-update/fence.ts"() {
|
|
7339
|
+
"use strict";
|
|
7340
|
+
init_types();
|
|
7341
|
+
FENCE_RECORD_ID = "schema-fence";
|
|
7342
|
+
META_COLLECTION3 = "_meta";
|
|
7343
|
+
DEFAULT_FENCE = { currentSchemaVersion: 0, fenceState: "normal" };
|
|
7344
|
+
}
|
|
7345
|
+
});
|
|
7346
|
+
|
|
7347
|
+
// src/schema-update/client-registry.ts
|
|
7348
|
+
async function writeClientDoc(store, vault, clientId, doc) {
|
|
7349
|
+
const envelope = {
|
|
7350
|
+
_noydb: NOYDB_FORMAT_VERSION,
|
|
7351
|
+
_v: 1,
|
|
7352
|
+
_ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7353
|
+
_iv: "",
|
|
7354
|
+
_data: JSON.stringify({ clientId, ...doc })
|
|
7355
|
+
};
|
|
7356
|
+
await store.put(vault, META_COLLECTION4, `${CLIENT_PREFIX}${clientId}`, envelope);
|
|
7357
|
+
}
|
|
7358
|
+
async function listClientDocs(store, vault) {
|
|
7359
|
+
const ids = await store.list(vault, META_COLLECTION4);
|
|
7360
|
+
const out = [];
|
|
7361
|
+
for (const id of ids) {
|
|
7362
|
+
if (!id.startsWith(CLIENT_PREFIX)) continue;
|
|
7363
|
+
const env = await store.get(vault, META_COLLECTION4, id);
|
|
7364
|
+
if (!env) continue;
|
|
7365
|
+
try {
|
|
7366
|
+
const parsed = JSON.parse(env._data);
|
|
7367
|
+
if (isClientDoc(parsed)) out.push(parsed);
|
|
7368
|
+
} catch {
|
|
7369
|
+
}
|
|
7370
|
+
}
|
|
7371
|
+
return out;
|
|
7372
|
+
}
|
|
7373
|
+
function isClientDoc(x) {
|
|
7374
|
+
if (x === null || typeof x !== "object") return false;
|
|
7375
|
+
const o = x;
|
|
7376
|
+
return typeof o["clientId"] === "string" && typeof o["lastSeen"] === "number" && (o["quiescedAtVersion"] === null || typeof o["quiescedAtVersion"] === "number") && (o["sessionId"] === void 0 || typeof o["sessionId"] === "string");
|
|
7377
|
+
}
|
|
7378
|
+
var META_COLLECTION4, CLIENT_PREFIX;
|
|
7379
|
+
var init_client_registry = __esm({
|
|
7380
|
+
"src/schema-update/client-registry.ts"() {
|
|
7381
|
+
"use strict";
|
|
7382
|
+
init_types();
|
|
7383
|
+
META_COLLECTION4 = "_meta";
|
|
7384
|
+
CLIENT_PREFIX = "schema-fence:client:";
|
|
7385
|
+
}
|
|
7386
|
+
});
|
|
7387
|
+
|
|
7388
|
+
// src/coordination/store-provider.ts
|
|
7389
|
+
function toPresence(doc) {
|
|
7390
|
+
return {
|
|
7391
|
+
writerId: doc.clientId,
|
|
7392
|
+
// Pre-#469 docs (and explicit empty) have no session — fall back to the
|
|
7393
|
+
// writerId so every presence is session-addressable.
|
|
7394
|
+
sessionId: doc.sessionId && doc.sessionId.length > 0 ? doc.sessionId : doc.clientId,
|
|
7395
|
+
lastSeen: doc.lastSeen,
|
|
7396
|
+
quiescedAtVersion: doc.quiescedAtVersion
|
|
7397
|
+
};
|
|
7398
|
+
}
|
|
7399
|
+
function fenceEqual(a, b) {
|
|
7400
|
+
return a.currentSchemaVersion === b.currentSchemaVersion && a.fenceState === b.fenceState;
|
|
7401
|
+
}
|
|
7402
|
+
function presenceListEqual(a, b) {
|
|
7403
|
+
if (a.length !== b.length) return false;
|
|
7404
|
+
const key = (w) => `${w.writerId}\0${w.sessionId}\0${w.lastSeen}\0${String(w.quiescedAtVersion)}`;
|
|
7405
|
+
const bk = new Set(b.map(key));
|
|
7406
|
+
return a.every((w) => bk.has(key(w)));
|
|
7407
|
+
}
|
|
7408
|
+
function unref(timer) {
|
|
7409
|
+
const t = timer;
|
|
7410
|
+
if (typeof t.unref === "function") t.unref();
|
|
7411
|
+
}
|
|
7412
|
+
var DEFAULT_POLL_INTERVAL_MS, StoreCoordinationProvider;
|
|
7413
|
+
var init_store_provider = __esm({
|
|
7414
|
+
"src/coordination/store-provider.ts"() {
|
|
7415
|
+
"use strict";
|
|
7416
|
+
init_fence();
|
|
7417
|
+
init_client_registry();
|
|
7418
|
+
DEFAULT_POLL_INTERVAL_MS = 50;
|
|
7419
|
+
StoreCoordinationProvider = class {
|
|
7420
|
+
#store;
|
|
7421
|
+
#pollIntervalMs;
|
|
7422
|
+
constructor(store, opts) {
|
|
7423
|
+
this.#store = store;
|
|
7424
|
+
this.#pollIntervalMs = opts?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
7425
|
+
}
|
|
7426
|
+
async setFence(vault, fence) {
|
|
7427
|
+
await saveFence(this.#store, vault, fence);
|
|
7428
|
+
}
|
|
7429
|
+
async readFence(vault) {
|
|
7430
|
+
return loadFence(this.#store, vault);
|
|
7431
|
+
}
|
|
7432
|
+
observeFence(vault, onChange) {
|
|
7433
|
+
let last = null;
|
|
7434
|
+
let busy = false;
|
|
7435
|
+
const poll = async () => {
|
|
7436
|
+
if (busy) return;
|
|
7437
|
+
busy = true;
|
|
7438
|
+
try {
|
|
7439
|
+
const fence = await loadFence(this.#store, vault);
|
|
7440
|
+
if (last === null || !fenceEqual(last, fence)) {
|
|
7441
|
+
last = fence;
|
|
7442
|
+
onChange(fence);
|
|
7443
|
+
}
|
|
7444
|
+
} catch {
|
|
7445
|
+
} finally {
|
|
7446
|
+
busy = false;
|
|
7447
|
+
}
|
|
7448
|
+
};
|
|
7449
|
+
void poll();
|
|
7450
|
+
const timer = setInterval(() => void poll(), this.#pollIntervalMs);
|
|
7451
|
+
unref(timer);
|
|
7452
|
+
return () => clearInterval(timer);
|
|
7453
|
+
}
|
|
7454
|
+
async reportPresence(vault, p) {
|
|
7455
|
+
await writeClientDoc(this.#store, vault, p.writerId, {
|
|
7456
|
+
lastSeen: p.lastSeen,
|
|
7457
|
+
quiescedAtVersion: p.quiescedAtVersion,
|
|
7458
|
+
sessionId: p.sessionId
|
|
7459
|
+
});
|
|
7460
|
+
}
|
|
7461
|
+
observePresence(vault, onChange) {
|
|
7462
|
+
let last = null;
|
|
7463
|
+
let busy = false;
|
|
7464
|
+
const poll = async () => {
|
|
7465
|
+
if (busy) return;
|
|
7466
|
+
busy = true;
|
|
7467
|
+
try {
|
|
7468
|
+
const docs = await listClientDocs(this.#store, vault);
|
|
7469
|
+
const writers = docs.map(toPresence);
|
|
7470
|
+
if (last === null || !presenceListEqual(last, writers)) {
|
|
7471
|
+
last = writers;
|
|
7472
|
+
onChange(writers);
|
|
7473
|
+
}
|
|
7474
|
+
} catch {
|
|
7475
|
+
} finally {
|
|
7476
|
+
busy = false;
|
|
7477
|
+
}
|
|
7478
|
+
};
|
|
7479
|
+
void poll();
|
|
7480
|
+
const timer = setInterval(() => void poll(), this.#pollIntervalMs);
|
|
7481
|
+
unref(timer);
|
|
7482
|
+
return () => clearInterval(timer);
|
|
7483
|
+
}
|
|
7484
|
+
async reachableWriters(vault, o) {
|
|
7485
|
+
const docs = await listClientDocs(this.#store, vault);
|
|
7486
|
+
return docs.map(toPresence).filter((w) => o.now - w.lastSeen <= o.staleMs);
|
|
7487
|
+
}
|
|
7488
|
+
};
|
|
7489
|
+
}
|
|
7490
|
+
});
|
|
7491
|
+
|
|
7492
|
+
// src/coordination/index.ts
|
|
7493
|
+
var init_coordination = __esm({
|
|
7494
|
+
"src/coordination/index.ts"() {
|
|
7495
|
+
"use strict";
|
|
7496
|
+
init_types4();
|
|
7497
|
+
init_store_provider();
|
|
7498
|
+
}
|
|
7499
|
+
});
|
|
7500
|
+
|
|
7258
7501
|
// src/crdt/strategy.ts
|
|
7259
7502
|
var NOT_ENABLED, NO_CRDT;
|
|
7260
7503
|
var init_strategy = __esm({
|
|
@@ -7335,13 +7578,14 @@ function stripDisallowed(str, scripts) {
|
|
|
7335
7578
|
for (const ch of str) if (ok.test(ch)) out += ch;
|
|
7336
7579
|
return out;
|
|
7337
7580
|
}
|
|
7338
|
-
function enforceScript(value, field, descriptor) {
|
|
7581
|
+
function enforceScript(value, field, descriptor, exempt) {
|
|
7339
7582
|
const opt = descriptor.options;
|
|
7340
7583
|
if (!opt.script) return { value, warnings: [] };
|
|
7341
7584
|
const mode = opt.onScriptViolation ?? "reject";
|
|
7342
7585
|
const warnings = [];
|
|
7343
7586
|
let out = value;
|
|
7344
7587
|
for (const [locale, raw] of Object.entries(value)) {
|
|
7588
|
+
if (exempt?.has(locale)) continue;
|
|
7345
7589
|
if (typeof raw !== "string") continue;
|
|
7346
7590
|
const allowed = allowedFor(descriptor, locale);
|
|
7347
7591
|
if (fullMatcher(allowed).test(raw)) continue;
|
|
@@ -7432,8 +7676,18 @@ var init_script = __esm({
|
|
|
7432
7676
|
|
|
7433
7677
|
// src/i18n/core.ts
|
|
7434
7678
|
function i18nText(options) {
|
|
7679
|
+
if (options.densifyOnWrite === true && hasThrowPolicy(options.onMissing)) {
|
|
7680
|
+
throw new Error(
|
|
7681
|
+
`i18nText: densifyOnWrite cannot be combined with an explicit onMissing 'throw' policy \u2014 densify fills every empty slot, so a 'throw' would be unreachable. Remove the 'throw' policy or disable densifyOnWrite.`
|
|
7682
|
+
);
|
|
7683
|
+
}
|
|
7435
7684
|
return { _noydbI18nText: true, options };
|
|
7436
7685
|
}
|
|
7686
|
+
function hasThrowPolicy(onMissing) {
|
|
7687
|
+
if (onMissing === void 0) return false;
|
|
7688
|
+
if (typeof onMissing === "string") return onMissing === "throw";
|
|
7689
|
+
return Object.values(onMissing).includes("throw");
|
|
7690
|
+
}
|
|
7437
7691
|
function isI18nTextDescriptor(x) {
|
|
7438
7692
|
return typeof x === "object" && x !== null && x._noydbI18nText === true;
|
|
7439
7693
|
}
|
|
@@ -7631,8 +7885,15 @@ function applyI18nLocale(record, i18nFields, locale, fallback, layer = "read") {
|
|
|
7631
7885
|
};
|
|
7632
7886
|
result = applyAtPath(result, field, locale, fallback, opts);
|
|
7633
7887
|
}
|
|
7888
|
+
result = stripI18nFilled(result);
|
|
7634
7889
|
return result;
|
|
7635
7890
|
}
|
|
7891
|
+
function stripI18nFilled(record) {
|
|
7892
|
+
if (!Object.prototype.hasOwnProperty.call(record, "_i18nFilled")) return record;
|
|
7893
|
+
const rest = { ...record };
|
|
7894
|
+
delete rest._i18nFilled;
|
|
7895
|
+
return rest;
|
|
7896
|
+
}
|
|
7636
7897
|
var init_core = __esm({
|
|
7637
7898
|
"src/i18n/core.ts"() {
|
|
7638
7899
|
"use strict";
|
|
@@ -8662,6 +8923,11 @@ var init_strategy2 = __esm({
|
|
|
8662
8923
|
enforceScript(value) {
|
|
8663
8924
|
return { value, warnings: [] };
|
|
8664
8925
|
},
|
|
8926
|
+
computeExemptFills() {
|
|
8927
|
+
return /* @__PURE__ */ new Map();
|
|
8928
|
+
},
|
|
8929
|
+
densify() {
|
|
8930
|
+
},
|
|
8665
8931
|
buildDictionaryHandle() {
|
|
8666
8932
|
throw notEnabled("vault.dictionary()");
|
|
8667
8933
|
}
|
|
@@ -13625,6 +13891,12 @@ var init_collection = __esm({
|
|
|
13625
13891
|
* Declared via the `i18nFields` collection option.
|
|
13626
13892
|
*/
|
|
13627
13893
|
i18nFields;
|
|
13894
|
+
/**
|
|
13895
|
+
* #435 — the densify-enabled subset of {@link i18nFields} (fields whose
|
|
13896
|
+
* descriptor opts in via `densifyOnWrite: true`). `undefined` when none opt
|
|
13897
|
+
* in, so the write path skips all densify work for ordinary collections.
|
|
13898
|
+
*/
|
|
13899
|
+
i18nDensifyFields;
|
|
13628
13900
|
/**
|
|
13629
13901
|
* Map of field name → `DictKeyDescriptor` for fields declared with
|
|
13630
13902
|
* `dictKey()`. Used by `get()`/`list()` to add `<field>Label` virtual
|
|
@@ -13811,6 +14083,10 @@ var init_collection = __esm({
|
|
|
13811
14083
|
this.refEnforcer = opts.refEnforcer;
|
|
13812
14084
|
this.joinResolver = opts.joinResolver;
|
|
13813
14085
|
this.i18nFields = opts.i18nFields;
|
|
14086
|
+
const densifyFields = opts.i18nFields ? Object.fromEntries(
|
|
14087
|
+
Object.entries(opts.i18nFields).filter(([, d]) => d.options.densifyOnWrite === true)
|
|
14088
|
+
) : {};
|
|
14089
|
+
this.i18nDensifyFields = Object.keys(densifyFields).length > 0 ? densifyFields : void 0;
|
|
13814
14090
|
this.dictKeyFields = opts.dictKeyFields;
|
|
13815
14091
|
if (opts.moneyFields) validateMoneyFieldPaths(opts.moneyFields);
|
|
13816
14092
|
this.moneyFields = opts.moneyFields;
|
|
@@ -14130,6 +14406,32 @@ var init_collection = __esm({
|
|
|
14130
14406
|
if (busAfterPut) await this.subsystemBus.dispatch("afterPut", event);
|
|
14131
14407
|
}
|
|
14132
14408
|
}
|
|
14409
|
+
/**
|
|
14410
|
+
* #435 — resolve the prior stored record (with its `_i18nFilled` marker) for
|
|
14411
|
+
* densify. Eager: in-memory cache; lazy: LRU then adapter. undefined if absent.
|
|
14412
|
+
*/
|
|
14413
|
+
async resolveDensifyPrior(id) {
|
|
14414
|
+
if (this.lazy && this.lru) {
|
|
14415
|
+
const cached = this.lru.get(id);
|
|
14416
|
+
if (cached) return cached.record;
|
|
14417
|
+
const env = await this.adapter.get(this.vault, this.name, id);
|
|
14418
|
+
if (!env) return void 0;
|
|
14419
|
+
const rec = await this.decryptRecord(env);
|
|
14420
|
+
return rec === null ? void 0 : rec;
|
|
14421
|
+
}
|
|
14422
|
+
await this.ensureHydrated();
|
|
14423
|
+
return this.cache.get(id)?.record;
|
|
14424
|
+
}
|
|
14425
|
+
/**
|
|
14426
|
+
* #435 — densify provenance for a record: which i18n slots were auto-filled,
|
|
14427
|
+
* e.g. `{ name: ['en'] }`. undefined when nothing was filled. The marker is
|
|
14428
|
+
* stripped from ordinary reads; this is the sanctioned audit accessor.
|
|
14429
|
+
*/
|
|
14430
|
+
async i18nProvenance(id) {
|
|
14431
|
+
const prior = await this.resolveDensifyPrior(id);
|
|
14432
|
+
const marker = prior?.["_i18nFilled"];
|
|
14433
|
+
return marker && Object.keys(marker).length > 0 ? marker : void 0;
|
|
14434
|
+
}
|
|
14133
14435
|
/**
|
|
14134
14436
|
* Validate a record against this collection's schema WITHOUT writing it.
|
|
14135
14437
|
* Returns the (possibly coerced) record on success; throws
|
|
@@ -14244,6 +14546,16 @@ var init_collection = __esm({
|
|
|
14244
14546
|
setAtPathInPlace(obj, field, translated);
|
|
14245
14547
|
}
|
|
14246
14548
|
}
|
|
14549
|
+
let densifyPrior;
|
|
14550
|
+
let exemptFills;
|
|
14551
|
+
if (this.i18nDensifyFields) {
|
|
14552
|
+
densifyPrior = await this.resolveDensifyPrior(id);
|
|
14553
|
+
exemptFills = this.i18nStrategy.computeExemptFills(
|
|
14554
|
+
densifyPrior,
|
|
14555
|
+
record,
|
|
14556
|
+
this.i18nDensifyFields
|
|
14557
|
+
);
|
|
14558
|
+
}
|
|
14247
14559
|
if (this.i18nFields) {
|
|
14248
14560
|
const obj = record;
|
|
14249
14561
|
for (const [field, descriptor] of Object.entries(this.i18nFields)) {
|
|
@@ -14251,18 +14563,38 @@ var init_collection = __esm({
|
|
|
14251
14563
|
for (const leaf of getAtPath(obj, field)) {
|
|
14252
14564
|
if (!leaf || typeof leaf !== "object" || Array.isArray(leaf)) continue;
|
|
14253
14565
|
const leafMap = leaf;
|
|
14254
|
-
const { value: cleaned } = this.i18nStrategy.enforceScript(
|
|
14566
|
+
const { value: cleaned, warnings } = this.i18nStrategy.enforceScript(
|
|
14255
14567
|
leafMap,
|
|
14256
14568
|
field,
|
|
14257
|
-
descriptor
|
|
14569
|
+
descriptor,
|
|
14570
|
+
exemptFills?.get(field)
|
|
14258
14571
|
);
|
|
14259
14572
|
if (cleaned !== leafMap) Object.assign(leafMap, cleaned);
|
|
14573
|
+
const mode = descriptor.options.onScriptViolation;
|
|
14574
|
+
if (mode === "warn" || mode === "filter") {
|
|
14575
|
+
for (const w of warnings) {
|
|
14576
|
+
this.emitter.emit("i18n:script-violation", {
|
|
14577
|
+
vault: this.vault,
|
|
14578
|
+
collection: this.name,
|
|
14579
|
+
id,
|
|
14580
|
+
mode,
|
|
14581
|
+
warning: w
|
|
14582
|
+
});
|
|
14583
|
+
}
|
|
14584
|
+
}
|
|
14260
14585
|
}
|
|
14261
14586
|
}
|
|
14262
14587
|
}
|
|
14263
14588
|
if (this.i18nPutValidator !== void 0) {
|
|
14264
14589
|
this.i18nPutValidator(record);
|
|
14265
14590
|
}
|
|
14591
|
+
if (this.i18nDensifyFields) {
|
|
14592
|
+
this.i18nStrategy.densify(
|
|
14593
|
+
record,
|
|
14594
|
+
densifyPrior,
|
|
14595
|
+
this.i18nDensifyFields
|
|
14596
|
+
);
|
|
14597
|
+
}
|
|
14266
14598
|
if (this.refEnforcer !== void 0) {
|
|
14267
14599
|
await this.refEnforcer.enforceRefsOnPut(this.name, record);
|
|
14268
14600
|
}
|
|
@@ -15116,7 +15448,7 @@ var init_collection = __esm({
|
|
|
15116
15448
|
}
|
|
15117
15449
|
await this.ensureHydrated();
|
|
15118
15450
|
const entries = [];
|
|
15119
|
-
for (const [id, e] of this.cache) entries.push({ id, record: e.record });
|
|
15451
|
+
for (const [id, e] of this.cache) entries.push({ id, record: stripI18nFilled(e.record) });
|
|
15120
15452
|
return searchScan(entries, field, query, opts);
|
|
15121
15453
|
}
|
|
15122
15454
|
// ─── Bulk operations ─────────────────────────────────────
|
|
@@ -16030,7 +16362,7 @@ var init_collection = __esm({
|
|
|
16030
16362
|
const hasStaticDisplay = hasDict && this.dictKeyFields !== void 0 && Object.values(this.dictKeyFields).some(
|
|
16031
16363
|
(d) => isStaticDictDescriptor(d) && d.displayLocale !== void 0
|
|
16032
16364
|
);
|
|
16033
|
-
if (!locale && !hasStaticDisplay) return result;
|
|
16365
|
+
if (!locale && !hasStaticDisplay) return stripI18nFilled(result);
|
|
16034
16366
|
const layer = localeOpts?._layer ?? "read";
|
|
16035
16367
|
if (locale && hasI18n && this.i18nFields) {
|
|
16036
16368
|
result = this.i18nStrategy.applyI18nLocale(result, this.i18nFields, locale, localeOpts?.fallback, layer);
|
|
@@ -16097,7 +16429,7 @@ var init_collection = __esm({
|
|
|
16097
16429
|
}
|
|
16098
16430
|
result = withLabels;
|
|
16099
16431
|
}
|
|
16100
|
-
return result;
|
|
16432
|
+
return stripI18nFilled(result);
|
|
16101
16433
|
}
|
|
16102
16434
|
/**
|
|
16103
16435
|
* Low-level: encrypt a pre-serialised JSON string into an EncryptedEnvelope.
|
|
@@ -17764,108 +18096,18 @@ var init_magic_link_grant = __esm({
|
|
|
17764
18096
|
}
|
|
17765
18097
|
});
|
|
17766
18098
|
|
|
17767
|
-
// src/schema-update/fence.ts
|
|
17768
|
-
async function loadFence(store, vault) {
|
|
17769
|
-
const envelope = await store.get(vault, META_COLLECTION3, FENCE_RECORD_ID);
|
|
17770
|
-
if (!envelope) return DEFAULT_FENCE;
|
|
17771
|
-
try {
|
|
17772
|
-
const parsed = JSON.parse(envelope._data);
|
|
17773
|
-
if (!isFenceDoc(parsed)) return DEFAULT_FENCE;
|
|
17774
|
-
return parsed;
|
|
17775
|
-
} catch {
|
|
17776
|
-
return DEFAULT_FENCE;
|
|
17777
|
-
}
|
|
17778
|
-
}
|
|
17779
|
-
async function saveFence(store, vault, fence) {
|
|
17780
|
-
const envelope = {
|
|
17781
|
-
_noydb: NOYDB_FORMAT_VERSION,
|
|
17782
|
-
_v: 1,
|
|
17783
|
-
_ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
17784
|
-
_iv: "",
|
|
17785
|
-
_data: JSON.stringify(fence)
|
|
17786
|
-
};
|
|
17787
|
-
await store.put(vault, META_COLLECTION3, FENCE_RECORD_ID, envelope);
|
|
17788
|
-
}
|
|
17789
|
-
function isFenceDoc(x) {
|
|
17790
|
-
if (x === null || typeof x !== "object") return false;
|
|
17791
|
-
const o = x;
|
|
17792
|
-
return typeof o["currentSchemaVersion"] === "number" && (o["fenceState"] === "normal" || o["fenceState"] === "draining" || o["fenceState"] === "migrating" || o["fenceState"] === "complete");
|
|
17793
|
-
}
|
|
17794
|
-
var FENCE_RECORD_ID, META_COLLECTION3, DEFAULT_FENCE;
|
|
17795
|
-
var init_fence = __esm({
|
|
17796
|
-
"src/schema-update/fence.ts"() {
|
|
17797
|
-
"use strict";
|
|
17798
|
-
init_types();
|
|
17799
|
-
FENCE_RECORD_ID = "schema-fence";
|
|
17800
|
-
META_COLLECTION3 = "_meta";
|
|
17801
|
-
DEFAULT_FENCE = { currentSchemaVersion: 0, fenceState: "normal" };
|
|
17802
|
-
}
|
|
17803
|
-
});
|
|
17804
|
-
|
|
17805
|
-
// src/schema-update/client-registry.ts
|
|
17806
|
-
async function writeClientDoc(store, vault, clientId, doc) {
|
|
17807
|
-
const envelope = {
|
|
17808
|
-
_noydb: NOYDB_FORMAT_VERSION,
|
|
17809
|
-
_v: 1,
|
|
17810
|
-
_ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
17811
|
-
_iv: "",
|
|
17812
|
-
_data: JSON.stringify({ clientId, ...doc })
|
|
17813
|
-
};
|
|
17814
|
-
await store.put(vault, META_COLLECTION4, `${CLIENT_PREFIX}${clientId}`, envelope);
|
|
17815
|
-
}
|
|
17816
|
-
async function listClientDocs(store, vault) {
|
|
17817
|
-
const ids = await store.list(vault, META_COLLECTION4);
|
|
17818
|
-
const out = [];
|
|
17819
|
-
for (const id of ids) {
|
|
17820
|
-
if (!id.startsWith(CLIENT_PREFIX)) continue;
|
|
17821
|
-
const env = await store.get(vault, META_COLLECTION4, id);
|
|
17822
|
-
if (!env) continue;
|
|
17823
|
-
try {
|
|
17824
|
-
const parsed = JSON.parse(env._data);
|
|
17825
|
-
if (isClientDoc(parsed)) out.push(parsed);
|
|
17826
|
-
} catch {
|
|
17827
|
-
}
|
|
17828
|
-
}
|
|
17829
|
-
return out;
|
|
17830
|
-
}
|
|
17831
|
-
async function activeQuiesced(store, vault, opts) {
|
|
17832
|
-
const docs = await listClientDocs(store, vault);
|
|
17833
|
-
const active = docs.filter(
|
|
17834
|
-
(d) => d.lastSeen >= opts.now - opts.staleMs && d.clientId !== opts.excludeClientId
|
|
17835
|
-
);
|
|
17836
|
-
return active.every((d) => d.quiescedAtVersion === opts.generation);
|
|
17837
|
-
}
|
|
17838
|
-
function isClientDoc(x) {
|
|
17839
|
-
if (x === null || typeof x !== "object") return false;
|
|
17840
|
-
const o = x;
|
|
17841
|
-
return typeof o["clientId"] === "string" && typeof o["lastSeen"] === "number" && (o["quiescedAtVersion"] === null || typeof o["quiescedAtVersion"] === "number");
|
|
17842
|
-
}
|
|
17843
|
-
var META_COLLECTION4, CLIENT_PREFIX;
|
|
17844
|
-
var init_client_registry = __esm({
|
|
17845
|
-
"src/schema-update/client-registry.ts"() {
|
|
17846
|
-
"use strict";
|
|
17847
|
-
init_types();
|
|
17848
|
-
META_COLLECTION4 = "_meta";
|
|
17849
|
-
CLIENT_PREFIX = "schema-fence:client:";
|
|
17850
|
-
}
|
|
17851
|
-
});
|
|
17852
|
-
|
|
17853
18099
|
// src/schema-update/fence-controller.ts
|
|
17854
|
-
function delay(ms) {
|
|
17855
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
17856
|
-
}
|
|
17857
18100
|
var SchemaFenceController;
|
|
17858
18101
|
var init_fence_controller = __esm({
|
|
17859
18102
|
"src/schema-update/fence-controller.ts"() {
|
|
17860
18103
|
"use strict";
|
|
17861
|
-
init_fence();
|
|
17862
18104
|
init_errors();
|
|
17863
|
-
|
|
18105
|
+
init_coordination();
|
|
17864
18106
|
SchemaFenceController = class {
|
|
17865
|
-
#
|
|
18107
|
+
#coordination;
|
|
17866
18108
|
#vault;
|
|
17867
18109
|
#onFlush;
|
|
17868
|
-
#
|
|
18110
|
+
#writerId;
|
|
17869
18111
|
#now;
|
|
17870
18112
|
#staleMs;
|
|
17871
18113
|
#quiesceTimeoutMs;
|
|
@@ -17873,10 +18115,10 @@ var init_fence_controller = __esm({
|
|
|
17873
18115
|
#snapshot = 0;
|
|
17874
18116
|
#pending = /* @__PURE__ */ new Map();
|
|
17875
18117
|
constructor(opts) {
|
|
17876
|
-
this.#
|
|
18118
|
+
this.#coordination = opts.coordination;
|
|
17877
18119
|
this.#vault = opts.vault;
|
|
17878
18120
|
this.#onFlush = opts.onFlush;
|
|
17879
|
-
this.#
|
|
18121
|
+
this.#writerId = opts.clientId ?? "migrator";
|
|
17880
18122
|
this.#now = opts.now ?? (() => Date.now());
|
|
17881
18123
|
this.#staleMs = opts.staleMs ?? 3e4;
|
|
17882
18124
|
this.#quiesceTimeoutMs = opts.quiesceTimeoutMs ?? 6e4;
|
|
@@ -17885,7 +18127,7 @@ var init_fence_controller = __esm({
|
|
|
17885
18127
|
}
|
|
17886
18128
|
/** Capture the generation snapshot at vault-open. */
|
|
17887
18129
|
async init() {
|
|
17888
|
-
this.#snapshot = (await
|
|
18130
|
+
this.#snapshot = (await this.#coordination.readFence(this.#vault)).currentSchemaVersion;
|
|
17889
18131
|
}
|
|
17890
18132
|
/** Record a per-collection pending cutover (from a registration `cutover` decision). */
|
|
17891
18133
|
registerPendingCutover(collection, transform) {
|
|
@@ -17893,7 +18135,7 @@ var init_fence_controller = __esm({
|
|
|
17893
18135
|
}
|
|
17894
18136
|
/** Write-path gate. Throws when behind, fenced, or this collection is cutover-pending. */
|
|
17895
18137
|
async assertWritable(collection) {
|
|
17896
|
-
const fence = await
|
|
18138
|
+
const fence = await this.#coordination.readFence(this.#vault);
|
|
17897
18139
|
if (fence.currentSchemaVersion > this.#snapshot) {
|
|
17898
18140
|
throw new MigrationRequiredError(
|
|
17899
18141
|
`Vault "${this.#vault}" advanced to schema generation ${fence.currentSchemaVersion} (this client opened at ${this.#snapshot}). Reload to continue.`
|
|
@@ -17912,49 +18154,49 @@ var init_fence_controller = __esm({
|
|
|
17912
18154
|
* Admin trigger. Drain → wait for the active set to quiesce (or time out)
|
|
17913
18155
|
* → migrate each pending transform → bump → complete → normal. The
|
|
17914
18156
|
* migrator excludes itself from the barrier (it drained synchronously
|
|
17915
|
-
*
|
|
17916
|
-
* production falls back to a short real delay.
|
|
18157
|
+
* inside {@link runDrainBarrier}). `onPoll` (tests) advances other clients
|
|
18158
|
+
* between barrier checks; production falls back to a short real delay.
|
|
17917
18159
|
*/
|
|
17918
18160
|
async runCutover(run, opts) {
|
|
17919
18161
|
if (this.#pending.size === 0) return { migrated: 0 };
|
|
17920
|
-
const base = await
|
|
18162
|
+
const base = await this.#coordination.readFence(this.#vault);
|
|
17921
18163
|
const generation = base.currentSchemaVersion;
|
|
17922
|
-
|
|
17923
|
-
await this.#onFlush();
|
|
17924
|
-
const deadline = this.#now() + this.#quiesceTimeoutMs;
|
|
17925
|
-
while (!await activeQuiesced(this.#store, this.#vault, {
|
|
17926
|
-
generation,
|
|
17927
|
-
now: this.#now(),
|
|
17928
|
-
staleMs: this.#staleMs,
|
|
17929
|
-
excludeClientId: this.#clientId
|
|
17930
|
-
})) {
|
|
17931
|
-
if (this.#now() >= deadline) {
|
|
17932
|
-
throw new QuiesceTimeoutError(
|
|
17933
|
-
`Cutover on "${this.#vault}" timed out waiting for clients to quiesce at generation ${generation}.`
|
|
17934
|
-
);
|
|
17935
|
-
}
|
|
17936
|
-
await (opts?.onPoll ? opts.onPoll() : delay(50));
|
|
17937
|
-
}
|
|
17938
|
-
await this.#setState(generation, "migrating");
|
|
18164
|
+
this.#emit({ currentSchemaVersion: generation, fenceState: "draining" });
|
|
17939
18165
|
let migrated = 0;
|
|
17940
|
-
|
|
17941
|
-
|
|
17942
|
-
|
|
17943
|
-
|
|
17944
|
-
|
|
17945
|
-
|
|
17946
|
-
|
|
17947
|
-
|
|
17948
|
-
|
|
18166
|
+
await runDrainBarrier(
|
|
18167
|
+
this.#coordination,
|
|
18168
|
+
{
|
|
18169
|
+
vault: this.#vault,
|
|
18170
|
+
generation,
|
|
18171
|
+
writerId: this.#writerId,
|
|
18172
|
+
onFlush: this.#onFlush,
|
|
18173
|
+
staleMs: this.#staleMs,
|
|
18174
|
+
quiesceTimeoutMs: this.#quiesceTimeoutMs,
|
|
18175
|
+
now: this.#now,
|
|
18176
|
+
...opts?.onPoll ? { onPoll: opts.onPoll } : {}
|
|
18177
|
+
},
|
|
18178
|
+
async () => {
|
|
18179
|
+
await this.#setState(generation, "migrating");
|
|
18180
|
+
for (const [collection, transform] of this.#pending) {
|
|
18181
|
+
await run(collection, transform);
|
|
18182
|
+
migrated++;
|
|
18183
|
+
}
|
|
18184
|
+
const nextVersion = generation + 1;
|
|
18185
|
+
await this.#setState(nextVersion, "complete");
|
|
18186
|
+
this.#pending.clear();
|
|
18187
|
+
await this.#setState(nextVersion, "normal");
|
|
18188
|
+
this.#snapshot = nextVersion;
|
|
18189
|
+
}
|
|
18190
|
+
);
|
|
17949
18191
|
return { migrated };
|
|
17950
18192
|
}
|
|
17951
18193
|
/** Recover a stuck drain: reset fenceState to normal at the current version (no bump). */
|
|
17952
18194
|
async abort() {
|
|
17953
|
-
const fence = await
|
|
18195
|
+
const fence = await this.#coordination.readFence(this.#vault);
|
|
17954
18196
|
await this.#setState(fence.currentSchemaVersion, "normal");
|
|
17955
18197
|
}
|
|
17956
18198
|
async #setState(currentSchemaVersion, fenceState) {
|
|
17957
|
-
await
|
|
18199
|
+
await this.#coordination.setFence(this.#vault, { currentSchemaVersion, fenceState });
|
|
17958
18200
|
this.#emit({ currentSchemaVersion, fenceState });
|
|
17959
18201
|
}
|
|
17960
18202
|
};
|
|
@@ -17966,12 +18208,11 @@ var FenceWatcher;
|
|
|
17966
18208
|
var init_fence_watcher = __esm({
|
|
17967
18209
|
"src/schema-update/fence-watcher.ts"() {
|
|
17968
18210
|
"use strict";
|
|
17969
|
-
init_fence();
|
|
17970
|
-
init_client_registry();
|
|
17971
18211
|
FenceWatcher = class {
|
|
17972
|
-
#
|
|
18212
|
+
#coordination;
|
|
17973
18213
|
#vault;
|
|
17974
|
-
#
|
|
18214
|
+
#writerId;
|
|
18215
|
+
#sessionId;
|
|
17975
18216
|
#onFlush;
|
|
17976
18217
|
#now;
|
|
17977
18218
|
#emit;
|
|
@@ -17979,9 +18220,10 @@ var init_fence_watcher = __esm({
|
|
|
17979
18220
|
#quiescedAtVersion = null;
|
|
17980
18221
|
#timer;
|
|
17981
18222
|
constructor(opts) {
|
|
17982
|
-
this.#
|
|
18223
|
+
this.#coordination = opts.coordination;
|
|
17983
18224
|
this.#vault = opts.vault;
|
|
17984
|
-
this.#
|
|
18225
|
+
this.#writerId = opts.clientId;
|
|
18226
|
+
this.#sessionId = opts.sessionId ?? opts.clientId;
|
|
17985
18227
|
this.#onFlush = opts.onFlush;
|
|
17986
18228
|
this.#now = opts.now ?? (() => Date.now());
|
|
17987
18229
|
this.#emit = opts.emit ?? (() => {
|
|
@@ -17989,14 +18231,16 @@ var init_fence_watcher = __esm({
|
|
|
17989
18231
|
}
|
|
17990
18232
|
/** Publish liveness (and the current ack) without changing quiesce state. */
|
|
17991
18233
|
async beat() {
|
|
17992
|
-
await
|
|
18234
|
+
await this.#coordination.reportPresence(this.#vault, {
|
|
18235
|
+
writerId: this.#writerId,
|
|
18236
|
+
sessionId: this.#sessionId,
|
|
17993
18237
|
lastSeen: this.#now(),
|
|
17994
18238
|
quiescedAtVersion: this.#quiescedAtVersion
|
|
17995
18239
|
});
|
|
17996
18240
|
}
|
|
17997
18241
|
/** Poll the fence; quiesce on draining; emit on transitions. */
|
|
17998
18242
|
async check() {
|
|
17999
|
-
const fence = await
|
|
18243
|
+
const fence = await this.#coordination.readFence(this.#vault);
|
|
18000
18244
|
if (fence.fenceState !== this.#lastState) {
|
|
18001
18245
|
this.#lastState = fence.fenceState;
|
|
18002
18246
|
this.#emit({ currentSchemaVersion: fence.currentSchemaVersion, fenceState: fence.fenceState });
|
|
@@ -19308,10 +19552,11 @@ var init_vault = __esm({
|
|
|
19308
19552
|
this.keyring = opts.keyring;
|
|
19309
19553
|
this.encrypted = opts.encrypted;
|
|
19310
19554
|
this.schemaFence = new SchemaFenceController({
|
|
19311
|
-
|
|
19555
|
+
coordination: this.noydb.coordination,
|
|
19312
19556
|
vault: this.name,
|
|
19313
19557
|
onFlush: () => this.noydb._writeQueueTracker.onFlush(),
|
|
19314
19558
|
clientId: this.noydb._clientId,
|
|
19559
|
+
sessionId: this.noydb._sessionId,
|
|
19315
19560
|
emit: (e) => this.emitter.emit("schema:fence-changed", { vault: this.name, ...e })
|
|
19316
19561
|
});
|
|
19317
19562
|
this.emitter = opts.emitter;
|
|
@@ -19707,9 +19952,10 @@ var init_vault = __esm({
|
|
|
19707
19952
|
if (this.#fenceCoordinationStarted) return;
|
|
19708
19953
|
this.#fenceCoordinationStarted = true;
|
|
19709
19954
|
this.#fenceWatcher = new FenceWatcher({
|
|
19710
|
-
|
|
19955
|
+
coordination: this.noydb.coordination,
|
|
19711
19956
|
vault: this.name,
|
|
19712
19957
|
clientId: this.noydb._clientId,
|
|
19958
|
+
sessionId: this.noydb._sessionId,
|
|
19713
19959
|
onFlush: () => this.noydb._writeQueueTracker.onFlush(),
|
|
19714
19960
|
emit: (e) => this.emitter.emit("schema:fence-changed", { vault: this.name, ...e })
|
|
19715
19961
|
});
|
|
@@ -23450,6 +23696,7 @@ var init_noydb = __esm({
|
|
|
23450
23696
|
init_recovery();
|
|
23451
23697
|
init_managed_passphrase();
|
|
23452
23698
|
init_ulid();
|
|
23699
|
+
init_coordination();
|
|
23453
23700
|
init_errors2();
|
|
23454
23701
|
init_auth_introspection();
|
|
23455
23702
|
init_public_envelope();
|
|
@@ -23493,6 +23740,10 @@ var init_noydb = __esm({
|
|
|
23493
23740
|
writeHooks = new WriteHookRegistry();
|
|
23494
23741
|
subsystemBus = new SubsystemBus();
|
|
23495
23742
|
clientId = generateULID();
|
|
23743
|
+
/** Session that owns this instance's writers (one user's writers across vaults). */
|
|
23744
|
+
sessionId;
|
|
23745
|
+
/** Drain-barrier coordination transport for the schema fence (#469). */
|
|
23746
|
+
coordinationProvider;
|
|
23496
23747
|
vaultCache = /* @__PURE__ */ new Map();
|
|
23497
23748
|
keyringCache = /* @__PURE__ */ new Map();
|
|
23498
23749
|
syncEngines = /* @__PURE__ */ new Map();
|
|
@@ -23567,6 +23818,8 @@ var init_noydb = __esm({
|
|
|
23567
23818
|
"[noydb] debugPlaintext is ON \u2014 records are stored UNENCRYPTED and laid out for native store inspection. NEVER use this for production or client data."
|
|
23568
23819
|
);
|
|
23569
23820
|
}
|
|
23821
|
+
this.sessionId = options.sessionId ?? generateULID();
|
|
23822
|
+
this.coordinationProvider = options.coordinationStrategy ?? new StoreCoordinationProvider(options.store);
|
|
23570
23823
|
this.txStrategy = options.txStrategy ?? NO_TX;
|
|
23571
23824
|
this.forgetStrategy = options.forgetStrategy ?? NO_FORGET;
|
|
23572
23825
|
this.sessionStrategy = options.sessionStrategy ?? NO_SESSION;
|
|
@@ -24620,6 +24873,18 @@ var init_noydb = __esm({
|
|
|
24620
24873
|
get _clientId() {
|
|
24621
24874
|
return this.clientId;
|
|
24622
24875
|
}
|
|
24876
|
+
/** @internal Session that owns this instance's writers (#469). */
|
|
24877
|
+
get _sessionId() {
|
|
24878
|
+
return this.sessionId;
|
|
24879
|
+
}
|
|
24880
|
+
/**
|
|
24881
|
+
* @internal Drain-barrier coordination transport for the schema fence (#469).
|
|
24882
|
+
* The default store-backed provider reproduces today's fence behavior; a
|
|
24883
|
+
* `by-*` real-time transport is injected via `coordinationStrategy`.
|
|
24884
|
+
*/
|
|
24885
|
+
get coordination() {
|
|
24886
|
+
return this.coordinationProvider;
|
|
24887
|
+
}
|
|
24623
24888
|
/**
|
|
24624
24889
|
* Soft-lock a single vault: clear its in-memory keyring, DEKs, vault
|
|
24625
24890
|
* instance, sync engine, policy enforcer, and active-tier entry —
|
|
@@ -28036,8 +28301,8 @@ function withRetry(opts = {}) {
|
|
|
28036
28301
|
} catch (err) {
|
|
28037
28302
|
lastError = err;
|
|
28038
28303
|
if (attempt >= maxRetries || !shouldRetry(err)) throw err;
|
|
28039
|
-
const
|
|
28040
|
-
await new Promise((r) => setTimeout(r,
|
|
28304
|
+
const delay = backoffMs * Math.pow(2, attempt) * (1 + Math.random() * jitter);
|
|
28305
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
28041
28306
|
}
|
|
28042
28307
|
}
|
|
28043
28308
|
throw lastError;
|