@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/bundle/index.cjs
CHANGED
|
@@ -4353,9 +4353,252 @@ var init_peer_recover = __esm({
|
|
|
4353
4353
|
}
|
|
4354
4354
|
});
|
|
4355
4355
|
|
|
4356
|
+
// src/coordination/types.ts
|
|
4357
|
+
function isQuorum(writers, generation, excludeWriterId) {
|
|
4358
|
+
return writers.filter((w) => w.writerId !== excludeWriterId).every((w) => w.quiescedAtVersion === generation);
|
|
4359
|
+
}
|
|
4360
|
+
async function runDrainBarrier(provider, o, run) {
|
|
4361
|
+
await provider.setFence(o.vault, { currentSchemaVersion: o.generation, fenceState: "draining" });
|
|
4362
|
+
await o.onFlush();
|
|
4363
|
+
const deadline = o.now() + o.quiesceTimeoutMs;
|
|
4364
|
+
const seeded = await provider.reachableWriters(o.vault, { staleMs: o.staleMs, now: o.now() });
|
|
4365
|
+
if (!isQuorum(seeded, o.generation, o.writerId)) {
|
|
4366
|
+
await new Promise((resolve, reject) => {
|
|
4367
|
+
let settled = false;
|
|
4368
|
+
const finish = (fn) => {
|
|
4369
|
+
if (!settled) {
|
|
4370
|
+
settled = true;
|
|
4371
|
+
unsub();
|
|
4372
|
+
fn();
|
|
4373
|
+
}
|
|
4374
|
+
};
|
|
4375
|
+
const unsub = provider.observePresence(o.vault, (writers) => {
|
|
4376
|
+
if (isQuorum(writers, o.generation, o.writerId)) finish(resolve);
|
|
4377
|
+
});
|
|
4378
|
+
const tick = async () => {
|
|
4379
|
+
if (settled) return;
|
|
4380
|
+
if (o.now() >= deadline) {
|
|
4381
|
+
finish(
|
|
4382
|
+
() => reject(
|
|
4383
|
+
new QuiesceTimeoutError(
|
|
4384
|
+
`Cutover of vault "${o.vault}" to generation ${o.generation} timed out after ${o.quiesceTimeoutMs}ms waiting for active writers to quiesce.`
|
|
4385
|
+
)
|
|
4386
|
+
)
|
|
4387
|
+
);
|
|
4388
|
+
return;
|
|
4389
|
+
}
|
|
4390
|
+
if (o.onPoll) await o.onPoll();
|
|
4391
|
+
const w = await provider.reachableWriters(o.vault, { staleMs: o.staleMs, now: o.now() });
|
|
4392
|
+
if (isQuorum(w, o.generation, o.writerId)) finish(resolve);
|
|
4393
|
+
else setTimeout(() => void tick(), 25);
|
|
4394
|
+
};
|
|
4395
|
+
void tick();
|
|
4396
|
+
});
|
|
4397
|
+
}
|
|
4398
|
+
await run();
|
|
4399
|
+
}
|
|
4400
|
+
var init_types3 = __esm({
|
|
4401
|
+
"src/coordination/types.ts"() {
|
|
4402
|
+
"use strict";
|
|
4403
|
+
init_errors();
|
|
4404
|
+
}
|
|
4405
|
+
});
|
|
4406
|
+
|
|
4407
|
+
// src/schema-update/fence.ts
|
|
4408
|
+
async function loadFence(store, vault) {
|
|
4409
|
+
const envelope = await store.get(vault, META_COLLECTION2, FENCE_RECORD_ID);
|
|
4410
|
+
if (!envelope) return DEFAULT_FENCE;
|
|
4411
|
+
try {
|
|
4412
|
+
const parsed = JSON.parse(envelope._data);
|
|
4413
|
+
if (!isFenceDoc(parsed)) return DEFAULT_FENCE;
|
|
4414
|
+
return parsed;
|
|
4415
|
+
} catch {
|
|
4416
|
+
return DEFAULT_FENCE;
|
|
4417
|
+
}
|
|
4418
|
+
}
|
|
4419
|
+
async function saveFence(store, vault, fence) {
|
|
4420
|
+
const envelope = {
|
|
4421
|
+
_noydb: NOYDB_FORMAT_VERSION,
|
|
4422
|
+
_v: 1,
|
|
4423
|
+
_ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4424
|
+
_iv: "",
|
|
4425
|
+
_data: JSON.stringify(fence)
|
|
4426
|
+
};
|
|
4427
|
+
await store.put(vault, META_COLLECTION2, FENCE_RECORD_ID, envelope);
|
|
4428
|
+
}
|
|
4429
|
+
function isFenceDoc(x) {
|
|
4430
|
+
if (x === null || typeof x !== "object") return false;
|
|
4431
|
+
const o = x;
|
|
4432
|
+
return typeof o["currentSchemaVersion"] === "number" && (o["fenceState"] === "normal" || o["fenceState"] === "draining" || o["fenceState"] === "migrating" || o["fenceState"] === "complete");
|
|
4433
|
+
}
|
|
4434
|
+
var FENCE_RECORD_ID, META_COLLECTION2, DEFAULT_FENCE;
|
|
4435
|
+
var init_fence = __esm({
|
|
4436
|
+
"src/schema-update/fence.ts"() {
|
|
4437
|
+
"use strict";
|
|
4438
|
+
init_types();
|
|
4439
|
+
FENCE_RECORD_ID = "schema-fence";
|
|
4440
|
+
META_COLLECTION2 = "_meta";
|
|
4441
|
+
DEFAULT_FENCE = { currentSchemaVersion: 0, fenceState: "normal" };
|
|
4442
|
+
}
|
|
4443
|
+
});
|
|
4444
|
+
|
|
4445
|
+
// src/schema-update/client-registry.ts
|
|
4446
|
+
async function writeClientDoc(store, vault, clientId, doc) {
|
|
4447
|
+
const envelope = {
|
|
4448
|
+
_noydb: NOYDB_FORMAT_VERSION,
|
|
4449
|
+
_v: 1,
|
|
4450
|
+
_ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4451
|
+
_iv: "",
|
|
4452
|
+
_data: JSON.stringify({ clientId, ...doc })
|
|
4453
|
+
};
|
|
4454
|
+
await store.put(vault, META_COLLECTION3, `${CLIENT_PREFIX}${clientId}`, envelope);
|
|
4455
|
+
}
|
|
4456
|
+
async function listClientDocs(store, vault) {
|
|
4457
|
+
const ids = await store.list(vault, META_COLLECTION3);
|
|
4458
|
+
const out = [];
|
|
4459
|
+
for (const id of ids) {
|
|
4460
|
+
if (!id.startsWith(CLIENT_PREFIX)) continue;
|
|
4461
|
+
const env = await store.get(vault, META_COLLECTION3, id);
|
|
4462
|
+
if (!env) continue;
|
|
4463
|
+
try {
|
|
4464
|
+
const parsed = JSON.parse(env._data);
|
|
4465
|
+
if (isClientDoc(parsed)) out.push(parsed);
|
|
4466
|
+
} catch {
|
|
4467
|
+
}
|
|
4468
|
+
}
|
|
4469
|
+
return out;
|
|
4470
|
+
}
|
|
4471
|
+
function isClientDoc(x) {
|
|
4472
|
+
if (x === null || typeof x !== "object") return false;
|
|
4473
|
+
const o = x;
|
|
4474
|
+
return typeof o["clientId"] === "string" && typeof o["lastSeen"] === "number" && (o["quiescedAtVersion"] === null || typeof o["quiescedAtVersion"] === "number") && (o["sessionId"] === void 0 || typeof o["sessionId"] === "string");
|
|
4475
|
+
}
|
|
4476
|
+
var META_COLLECTION3, CLIENT_PREFIX;
|
|
4477
|
+
var init_client_registry = __esm({
|
|
4478
|
+
"src/schema-update/client-registry.ts"() {
|
|
4479
|
+
"use strict";
|
|
4480
|
+
init_types();
|
|
4481
|
+
META_COLLECTION3 = "_meta";
|
|
4482
|
+
CLIENT_PREFIX = "schema-fence:client:";
|
|
4483
|
+
}
|
|
4484
|
+
});
|
|
4485
|
+
|
|
4486
|
+
// src/coordination/store-provider.ts
|
|
4487
|
+
function toPresence(doc) {
|
|
4488
|
+
return {
|
|
4489
|
+
writerId: doc.clientId,
|
|
4490
|
+
// Pre-#469 docs (and explicit empty) have no session — fall back to the
|
|
4491
|
+
// writerId so every presence is session-addressable.
|
|
4492
|
+
sessionId: doc.sessionId && doc.sessionId.length > 0 ? doc.sessionId : doc.clientId,
|
|
4493
|
+
lastSeen: doc.lastSeen,
|
|
4494
|
+
quiescedAtVersion: doc.quiescedAtVersion
|
|
4495
|
+
};
|
|
4496
|
+
}
|
|
4497
|
+
function fenceEqual(a, b) {
|
|
4498
|
+
return a.currentSchemaVersion === b.currentSchemaVersion && a.fenceState === b.fenceState;
|
|
4499
|
+
}
|
|
4500
|
+
function presenceListEqual(a, b) {
|
|
4501
|
+
if (a.length !== b.length) return false;
|
|
4502
|
+
const key = (w) => `${w.writerId}\0${w.sessionId}\0${w.lastSeen}\0${String(w.quiescedAtVersion)}`;
|
|
4503
|
+
const bk = new Set(b.map(key));
|
|
4504
|
+
return a.every((w) => bk.has(key(w)));
|
|
4505
|
+
}
|
|
4506
|
+
function unref(timer) {
|
|
4507
|
+
const t = timer;
|
|
4508
|
+
if (typeof t.unref === "function") t.unref();
|
|
4509
|
+
}
|
|
4510
|
+
var DEFAULT_POLL_INTERVAL_MS, StoreCoordinationProvider;
|
|
4511
|
+
var init_store_provider = __esm({
|
|
4512
|
+
"src/coordination/store-provider.ts"() {
|
|
4513
|
+
"use strict";
|
|
4514
|
+
init_fence();
|
|
4515
|
+
init_client_registry();
|
|
4516
|
+
DEFAULT_POLL_INTERVAL_MS = 50;
|
|
4517
|
+
StoreCoordinationProvider = class {
|
|
4518
|
+
#store;
|
|
4519
|
+
#pollIntervalMs;
|
|
4520
|
+
constructor(store, opts) {
|
|
4521
|
+
this.#store = store;
|
|
4522
|
+
this.#pollIntervalMs = opts?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
4523
|
+
}
|
|
4524
|
+
async setFence(vault, fence) {
|
|
4525
|
+
await saveFence(this.#store, vault, fence);
|
|
4526
|
+
}
|
|
4527
|
+
async readFence(vault) {
|
|
4528
|
+
return loadFence(this.#store, vault);
|
|
4529
|
+
}
|
|
4530
|
+
observeFence(vault, onChange) {
|
|
4531
|
+
let last = null;
|
|
4532
|
+
let busy = false;
|
|
4533
|
+
const poll = async () => {
|
|
4534
|
+
if (busy) return;
|
|
4535
|
+
busy = true;
|
|
4536
|
+
try {
|
|
4537
|
+
const fence = await loadFence(this.#store, vault);
|
|
4538
|
+
if (last === null || !fenceEqual(last, fence)) {
|
|
4539
|
+
last = fence;
|
|
4540
|
+
onChange(fence);
|
|
4541
|
+
}
|
|
4542
|
+
} catch {
|
|
4543
|
+
} finally {
|
|
4544
|
+
busy = false;
|
|
4545
|
+
}
|
|
4546
|
+
};
|
|
4547
|
+
void poll();
|
|
4548
|
+
const timer = setInterval(() => void poll(), this.#pollIntervalMs);
|
|
4549
|
+
unref(timer);
|
|
4550
|
+
return () => clearInterval(timer);
|
|
4551
|
+
}
|
|
4552
|
+
async reportPresence(vault, p) {
|
|
4553
|
+
await writeClientDoc(this.#store, vault, p.writerId, {
|
|
4554
|
+
lastSeen: p.lastSeen,
|
|
4555
|
+
quiescedAtVersion: p.quiescedAtVersion,
|
|
4556
|
+
sessionId: p.sessionId
|
|
4557
|
+
});
|
|
4558
|
+
}
|
|
4559
|
+
observePresence(vault, onChange) {
|
|
4560
|
+
let last = null;
|
|
4561
|
+
let busy = false;
|
|
4562
|
+
const poll = async () => {
|
|
4563
|
+
if (busy) return;
|
|
4564
|
+
busy = true;
|
|
4565
|
+
try {
|
|
4566
|
+
const docs = await listClientDocs(this.#store, vault);
|
|
4567
|
+
const writers = docs.map(toPresence);
|
|
4568
|
+
if (last === null || !presenceListEqual(last, writers)) {
|
|
4569
|
+
last = writers;
|
|
4570
|
+
onChange(writers);
|
|
4571
|
+
}
|
|
4572
|
+
} catch {
|
|
4573
|
+
} finally {
|
|
4574
|
+
busy = false;
|
|
4575
|
+
}
|
|
4576
|
+
};
|
|
4577
|
+
void poll();
|
|
4578
|
+
const timer = setInterval(() => void poll(), this.#pollIntervalMs);
|
|
4579
|
+
unref(timer);
|
|
4580
|
+
return () => clearInterval(timer);
|
|
4581
|
+
}
|
|
4582
|
+
async reachableWriters(vault, o) {
|
|
4583
|
+
const docs = await listClientDocs(this.#store, vault);
|
|
4584
|
+
return docs.map(toPresence).filter((w) => o.now - w.lastSeen <= o.staleMs);
|
|
4585
|
+
}
|
|
4586
|
+
};
|
|
4587
|
+
}
|
|
4588
|
+
});
|
|
4589
|
+
|
|
4590
|
+
// src/coordination/index.ts
|
|
4591
|
+
var init_coordination = __esm({
|
|
4592
|
+
"src/coordination/index.ts"() {
|
|
4593
|
+
"use strict";
|
|
4594
|
+
init_types3();
|
|
4595
|
+
init_store_provider();
|
|
4596
|
+
}
|
|
4597
|
+
});
|
|
4598
|
+
|
|
4356
4599
|
// src/policy/storage.ts
|
|
4357
4600
|
async function loadVaultPolicy(store, vault) {
|
|
4358
|
-
const envelope = await store.get(vault,
|
|
4601
|
+
const envelope = await store.get(vault, META_COLLECTION4, POLICY_RECORD_ID);
|
|
4359
4602
|
if (!envelope) return void 0;
|
|
4360
4603
|
try {
|
|
4361
4604
|
const parsed = JSON.parse(envelope._data);
|
|
@@ -4373,7 +4616,7 @@ async function saveVaultPolicy(store, vault, policy) {
|
|
|
4373
4616
|
_iv: "",
|
|
4374
4617
|
_data: JSON.stringify(policy)
|
|
4375
4618
|
};
|
|
4376
|
-
await store.put(vault,
|
|
4619
|
+
await store.put(vault, META_COLLECTION4, POLICY_RECORD_ID, envelope);
|
|
4377
4620
|
}
|
|
4378
4621
|
function isVaultPolicy(x) {
|
|
4379
4622
|
if (x === null || typeof x !== "object") return false;
|
|
@@ -4381,12 +4624,12 @@ function isVaultPolicy(x) {
|
|
|
4381
4624
|
const gates = x.gates;
|
|
4382
4625
|
return gates !== null && typeof gates === "object";
|
|
4383
4626
|
}
|
|
4384
|
-
var
|
|
4627
|
+
var META_COLLECTION4, POLICY_RECORD_ID;
|
|
4385
4628
|
var init_storage5 = __esm({
|
|
4386
4629
|
"src/policy/storage.ts"() {
|
|
4387
4630
|
"use strict";
|
|
4388
4631
|
init_types();
|
|
4389
|
-
|
|
4632
|
+
META_COLLECTION4 = "_meta";
|
|
4390
4633
|
POLICY_RECORD_ID = "policy";
|
|
4391
4634
|
}
|
|
4392
4635
|
});
|
|
@@ -4541,7 +4784,7 @@ function resolveSchema(schema) {
|
|
|
4541
4784
|
};
|
|
4542
4785
|
}
|
|
4543
4786
|
var PUBLIC_ENVELOPE_FIELDS, DEFAULT_PUBLIC_ENVELOPE_SCHEMA;
|
|
4544
|
-
var
|
|
4787
|
+
var init_types4 = __esm({
|
|
4545
4788
|
"src/meta/public-envelope/types.ts"() {
|
|
4546
4789
|
"use strict";
|
|
4547
4790
|
PUBLIC_ENVELOPE_FIELDS = [
|
|
@@ -4579,7 +4822,7 @@ __export(public_envelope_exports, {
|
|
|
4579
4822
|
var init_public_envelope = __esm({
|
|
4580
4823
|
"src/meta/public-envelope/index.ts"() {
|
|
4581
4824
|
"use strict";
|
|
4582
|
-
|
|
4825
|
+
init_types4();
|
|
4583
4826
|
init_schema();
|
|
4584
4827
|
init_storage();
|
|
4585
4828
|
}
|
|
@@ -4845,8 +5088,15 @@ function applyI18nLocale(record, i18nFields, locale, fallback, layer = "read") {
|
|
|
4845
5088
|
};
|
|
4846
5089
|
result = applyAtPath(result, field, locale, fallback, opts);
|
|
4847
5090
|
}
|
|
5091
|
+
result = stripI18nFilled(result);
|
|
4848
5092
|
return result;
|
|
4849
5093
|
}
|
|
5094
|
+
function stripI18nFilled(record) {
|
|
5095
|
+
if (!Object.prototype.hasOwnProperty.call(record, "_i18nFilled")) return record;
|
|
5096
|
+
const rest = { ...record };
|
|
5097
|
+
delete rest._i18nFilled;
|
|
5098
|
+
return rest;
|
|
5099
|
+
}
|
|
4850
5100
|
var init_core = __esm({
|
|
4851
5101
|
"src/i18n/core.ts"() {
|
|
4852
5102
|
"use strict";
|
|
@@ -5410,6 +5660,11 @@ var init_strategy2 = __esm({
|
|
|
5410
5660
|
enforceScript(value) {
|
|
5411
5661
|
return { value, warnings: [] };
|
|
5412
5662
|
},
|
|
5663
|
+
computeExemptFills() {
|
|
5664
|
+
return /* @__PURE__ */ new Map();
|
|
5665
|
+
},
|
|
5666
|
+
densify() {
|
|
5667
|
+
},
|
|
5413
5668
|
buildDictionaryHandle() {
|
|
5414
5669
|
throw notEnabled("vault.dictionary()");
|
|
5415
5670
|
}
|
|
@@ -9584,6 +9839,12 @@ var init_collection = __esm({
|
|
|
9584
9839
|
* Declared via the `i18nFields` collection option.
|
|
9585
9840
|
*/
|
|
9586
9841
|
i18nFields;
|
|
9842
|
+
/**
|
|
9843
|
+
* #435 — the densify-enabled subset of {@link i18nFields} (fields whose
|
|
9844
|
+
* descriptor opts in via `densifyOnWrite: true`). `undefined` when none opt
|
|
9845
|
+
* in, so the write path skips all densify work for ordinary collections.
|
|
9846
|
+
*/
|
|
9847
|
+
i18nDensifyFields;
|
|
9587
9848
|
/**
|
|
9588
9849
|
* Map of field name → `DictKeyDescriptor` for fields declared with
|
|
9589
9850
|
* `dictKey()`. Used by `get()`/`list()` to add `<field>Label` virtual
|
|
@@ -9770,6 +10031,10 @@ var init_collection = __esm({
|
|
|
9770
10031
|
this.refEnforcer = opts.refEnforcer;
|
|
9771
10032
|
this.joinResolver = opts.joinResolver;
|
|
9772
10033
|
this.i18nFields = opts.i18nFields;
|
|
10034
|
+
const densifyFields = opts.i18nFields ? Object.fromEntries(
|
|
10035
|
+
Object.entries(opts.i18nFields).filter(([, d]) => d.options.densifyOnWrite === true)
|
|
10036
|
+
) : {};
|
|
10037
|
+
this.i18nDensifyFields = Object.keys(densifyFields).length > 0 ? densifyFields : void 0;
|
|
9773
10038
|
this.dictKeyFields = opts.dictKeyFields;
|
|
9774
10039
|
if (opts.moneyFields) validateMoneyFieldPaths(opts.moneyFields);
|
|
9775
10040
|
this.moneyFields = opts.moneyFields;
|
|
@@ -10089,6 +10354,32 @@ var init_collection = __esm({
|
|
|
10089
10354
|
if (busAfterPut) await this.subsystemBus.dispatch("afterPut", event);
|
|
10090
10355
|
}
|
|
10091
10356
|
}
|
|
10357
|
+
/**
|
|
10358
|
+
* #435 — resolve the prior stored record (with its `_i18nFilled` marker) for
|
|
10359
|
+
* densify. Eager: in-memory cache; lazy: LRU then adapter. undefined if absent.
|
|
10360
|
+
*/
|
|
10361
|
+
async resolveDensifyPrior(id) {
|
|
10362
|
+
if (this.lazy && this.lru) {
|
|
10363
|
+
const cached = this.lru.get(id);
|
|
10364
|
+
if (cached) return cached.record;
|
|
10365
|
+
const env = await this.adapter.get(this.vault, this.name, id);
|
|
10366
|
+
if (!env) return void 0;
|
|
10367
|
+
const rec = await this.decryptRecord(env);
|
|
10368
|
+
return rec === null ? void 0 : rec;
|
|
10369
|
+
}
|
|
10370
|
+
await this.ensureHydrated();
|
|
10371
|
+
return this.cache.get(id)?.record;
|
|
10372
|
+
}
|
|
10373
|
+
/**
|
|
10374
|
+
* #435 — densify provenance for a record: which i18n slots were auto-filled,
|
|
10375
|
+
* e.g. `{ name: ['en'] }`. undefined when nothing was filled. The marker is
|
|
10376
|
+
* stripped from ordinary reads; this is the sanctioned audit accessor.
|
|
10377
|
+
*/
|
|
10378
|
+
async i18nProvenance(id) {
|
|
10379
|
+
const prior = await this.resolveDensifyPrior(id);
|
|
10380
|
+
const marker = prior?.["_i18nFilled"];
|
|
10381
|
+
return marker && Object.keys(marker).length > 0 ? marker : void 0;
|
|
10382
|
+
}
|
|
10092
10383
|
/**
|
|
10093
10384
|
* Validate a record against this collection's schema WITHOUT writing it.
|
|
10094
10385
|
* Returns the (possibly coerced) record on success; throws
|
|
@@ -10203,6 +10494,16 @@ var init_collection = __esm({
|
|
|
10203
10494
|
setAtPathInPlace(obj, field, translated);
|
|
10204
10495
|
}
|
|
10205
10496
|
}
|
|
10497
|
+
let densifyPrior;
|
|
10498
|
+
let exemptFills;
|
|
10499
|
+
if (this.i18nDensifyFields) {
|
|
10500
|
+
densifyPrior = await this.resolveDensifyPrior(id);
|
|
10501
|
+
exemptFills = this.i18nStrategy.computeExemptFills(
|
|
10502
|
+
densifyPrior,
|
|
10503
|
+
record,
|
|
10504
|
+
this.i18nDensifyFields
|
|
10505
|
+
);
|
|
10506
|
+
}
|
|
10206
10507
|
if (this.i18nFields) {
|
|
10207
10508
|
const obj = record;
|
|
10208
10509
|
for (const [field, descriptor] of Object.entries(this.i18nFields)) {
|
|
@@ -10210,18 +10511,38 @@ var init_collection = __esm({
|
|
|
10210
10511
|
for (const leaf of getAtPath(obj, field)) {
|
|
10211
10512
|
if (!leaf || typeof leaf !== "object" || Array.isArray(leaf)) continue;
|
|
10212
10513
|
const leafMap = leaf;
|
|
10213
|
-
const { value: cleaned } = this.i18nStrategy.enforceScript(
|
|
10514
|
+
const { value: cleaned, warnings } = this.i18nStrategy.enforceScript(
|
|
10214
10515
|
leafMap,
|
|
10215
10516
|
field,
|
|
10216
|
-
descriptor
|
|
10517
|
+
descriptor,
|
|
10518
|
+
exemptFills?.get(field)
|
|
10217
10519
|
);
|
|
10218
10520
|
if (cleaned !== leafMap) Object.assign(leafMap, cleaned);
|
|
10521
|
+
const mode = descriptor.options.onScriptViolation;
|
|
10522
|
+
if (mode === "warn" || mode === "filter") {
|
|
10523
|
+
for (const w of warnings) {
|
|
10524
|
+
this.emitter.emit("i18n:script-violation", {
|
|
10525
|
+
vault: this.vault,
|
|
10526
|
+
collection: this.name,
|
|
10527
|
+
id,
|
|
10528
|
+
mode,
|
|
10529
|
+
warning: w
|
|
10530
|
+
});
|
|
10531
|
+
}
|
|
10532
|
+
}
|
|
10219
10533
|
}
|
|
10220
10534
|
}
|
|
10221
10535
|
}
|
|
10222
10536
|
if (this.i18nPutValidator !== void 0) {
|
|
10223
10537
|
this.i18nPutValidator(record);
|
|
10224
10538
|
}
|
|
10539
|
+
if (this.i18nDensifyFields) {
|
|
10540
|
+
this.i18nStrategy.densify(
|
|
10541
|
+
record,
|
|
10542
|
+
densifyPrior,
|
|
10543
|
+
this.i18nDensifyFields
|
|
10544
|
+
);
|
|
10545
|
+
}
|
|
10225
10546
|
if (this.refEnforcer !== void 0) {
|
|
10226
10547
|
await this.refEnforcer.enforceRefsOnPut(this.name, record);
|
|
10227
10548
|
}
|
|
@@ -11075,7 +11396,7 @@ var init_collection = __esm({
|
|
|
11075
11396
|
}
|
|
11076
11397
|
await this.ensureHydrated();
|
|
11077
11398
|
const entries = [];
|
|
11078
|
-
for (const [id, e] of this.cache) entries.push({ id, record: e.record });
|
|
11399
|
+
for (const [id, e] of this.cache) entries.push({ id, record: stripI18nFilled(e.record) });
|
|
11079
11400
|
return searchScan(entries, field, query, opts);
|
|
11080
11401
|
}
|
|
11081
11402
|
// ─── Bulk operations ─────────────────────────────────────
|
|
@@ -11989,7 +12310,7 @@ var init_collection = __esm({
|
|
|
11989
12310
|
const hasStaticDisplay = hasDict && this.dictKeyFields !== void 0 && Object.values(this.dictKeyFields).some(
|
|
11990
12311
|
(d) => isStaticDictDescriptor(d) && d.displayLocale !== void 0
|
|
11991
12312
|
);
|
|
11992
|
-
if (!locale && !hasStaticDisplay) return result;
|
|
12313
|
+
if (!locale && !hasStaticDisplay) return stripI18nFilled(result);
|
|
11993
12314
|
const layer = localeOpts?._layer ?? "read";
|
|
11994
12315
|
if (locale && hasI18n && this.i18nFields) {
|
|
11995
12316
|
result = this.i18nStrategy.applyI18nLocale(result, this.i18nFields, locale, localeOpts?.fallback, layer);
|
|
@@ -12056,7 +12377,7 @@ var init_collection = __esm({
|
|
|
12056
12377
|
}
|
|
12057
12378
|
result = withLabels;
|
|
12058
12379
|
}
|
|
12059
|
-
return result;
|
|
12380
|
+
return stripI18nFilled(result);
|
|
12060
12381
|
}
|
|
12061
12382
|
/**
|
|
12062
12383
|
* Low-level: encrypt a pre-serialised JSON string into an EncryptedEnvelope.
|
|
@@ -15134,108 +15455,18 @@ var init_gate = __esm({
|
|
|
15134
15455
|
}
|
|
15135
15456
|
});
|
|
15136
15457
|
|
|
15137
|
-
// src/schema-update/fence.ts
|
|
15138
|
-
async function loadFence(store, vault) {
|
|
15139
|
-
const envelope = await store.get(vault, META_COLLECTION3, FENCE_RECORD_ID);
|
|
15140
|
-
if (!envelope) return DEFAULT_FENCE;
|
|
15141
|
-
try {
|
|
15142
|
-
const parsed = JSON.parse(envelope._data);
|
|
15143
|
-
if (!isFenceDoc(parsed)) return DEFAULT_FENCE;
|
|
15144
|
-
return parsed;
|
|
15145
|
-
} catch {
|
|
15146
|
-
return DEFAULT_FENCE;
|
|
15147
|
-
}
|
|
15148
|
-
}
|
|
15149
|
-
async function saveFence(store, vault, fence) {
|
|
15150
|
-
const envelope = {
|
|
15151
|
-
_noydb: NOYDB_FORMAT_VERSION,
|
|
15152
|
-
_v: 1,
|
|
15153
|
-
_ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15154
|
-
_iv: "",
|
|
15155
|
-
_data: JSON.stringify(fence)
|
|
15156
|
-
};
|
|
15157
|
-
await store.put(vault, META_COLLECTION3, FENCE_RECORD_ID, envelope);
|
|
15158
|
-
}
|
|
15159
|
-
function isFenceDoc(x) {
|
|
15160
|
-
if (x === null || typeof x !== "object") return false;
|
|
15161
|
-
const o = x;
|
|
15162
|
-
return typeof o["currentSchemaVersion"] === "number" && (o["fenceState"] === "normal" || o["fenceState"] === "draining" || o["fenceState"] === "migrating" || o["fenceState"] === "complete");
|
|
15163
|
-
}
|
|
15164
|
-
var FENCE_RECORD_ID, META_COLLECTION3, DEFAULT_FENCE;
|
|
15165
|
-
var init_fence = __esm({
|
|
15166
|
-
"src/schema-update/fence.ts"() {
|
|
15167
|
-
"use strict";
|
|
15168
|
-
init_types();
|
|
15169
|
-
FENCE_RECORD_ID = "schema-fence";
|
|
15170
|
-
META_COLLECTION3 = "_meta";
|
|
15171
|
-
DEFAULT_FENCE = { currentSchemaVersion: 0, fenceState: "normal" };
|
|
15172
|
-
}
|
|
15173
|
-
});
|
|
15174
|
-
|
|
15175
|
-
// src/schema-update/client-registry.ts
|
|
15176
|
-
async function writeClientDoc(store, vault, clientId, doc) {
|
|
15177
|
-
const envelope = {
|
|
15178
|
-
_noydb: NOYDB_FORMAT_VERSION,
|
|
15179
|
-
_v: 1,
|
|
15180
|
-
_ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15181
|
-
_iv: "",
|
|
15182
|
-
_data: JSON.stringify({ clientId, ...doc })
|
|
15183
|
-
};
|
|
15184
|
-
await store.put(vault, META_COLLECTION4, `${CLIENT_PREFIX}${clientId}`, envelope);
|
|
15185
|
-
}
|
|
15186
|
-
async function listClientDocs(store, vault) {
|
|
15187
|
-
const ids = await store.list(vault, META_COLLECTION4);
|
|
15188
|
-
const out = [];
|
|
15189
|
-
for (const id of ids) {
|
|
15190
|
-
if (!id.startsWith(CLIENT_PREFIX)) continue;
|
|
15191
|
-
const env = await store.get(vault, META_COLLECTION4, id);
|
|
15192
|
-
if (!env) continue;
|
|
15193
|
-
try {
|
|
15194
|
-
const parsed = JSON.parse(env._data);
|
|
15195
|
-
if (isClientDoc(parsed)) out.push(parsed);
|
|
15196
|
-
} catch {
|
|
15197
|
-
}
|
|
15198
|
-
}
|
|
15199
|
-
return out;
|
|
15200
|
-
}
|
|
15201
|
-
async function activeQuiesced(store, vault, opts) {
|
|
15202
|
-
const docs = await listClientDocs(store, vault);
|
|
15203
|
-
const active = docs.filter(
|
|
15204
|
-
(d) => d.lastSeen >= opts.now - opts.staleMs && d.clientId !== opts.excludeClientId
|
|
15205
|
-
);
|
|
15206
|
-
return active.every((d) => d.quiescedAtVersion === opts.generation);
|
|
15207
|
-
}
|
|
15208
|
-
function isClientDoc(x) {
|
|
15209
|
-
if (x === null || typeof x !== "object") return false;
|
|
15210
|
-
const o = x;
|
|
15211
|
-
return typeof o["clientId"] === "string" && typeof o["lastSeen"] === "number" && (o["quiescedAtVersion"] === null || typeof o["quiescedAtVersion"] === "number");
|
|
15212
|
-
}
|
|
15213
|
-
var META_COLLECTION4, CLIENT_PREFIX;
|
|
15214
|
-
var init_client_registry = __esm({
|
|
15215
|
-
"src/schema-update/client-registry.ts"() {
|
|
15216
|
-
"use strict";
|
|
15217
|
-
init_types();
|
|
15218
|
-
META_COLLECTION4 = "_meta";
|
|
15219
|
-
CLIENT_PREFIX = "schema-fence:client:";
|
|
15220
|
-
}
|
|
15221
|
-
});
|
|
15222
|
-
|
|
15223
15458
|
// src/schema-update/fence-controller.ts
|
|
15224
|
-
function delay(ms) {
|
|
15225
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
15226
|
-
}
|
|
15227
15459
|
var SchemaFenceController;
|
|
15228
15460
|
var init_fence_controller = __esm({
|
|
15229
15461
|
"src/schema-update/fence-controller.ts"() {
|
|
15230
15462
|
"use strict";
|
|
15231
|
-
init_fence();
|
|
15232
15463
|
init_errors();
|
|
15233
|
-
|
|
15464
|
+
init_coordination();
|
|
15234
15465
|
SchemaFenceController = class {
|
|
15235
|
-
#
|
|
15466
|
+
#coordination;
|
|
15236
15467
|
#vault;
|
|
15237
15468
|
#onFlush;
|
|
15238
|
-
#
|
|
15469
|
+
#writerId;
|
|
15239
15470
|
#now;
|
|
15240
15471
|
#staleMs;
|
|
15241
15472
|
#quiesceTimeoutMs;
|
|
@@ -15243,10 +15474,10 @@ var init_fence_controller = __esm({
|
|
|
15243
15474
|
#snapshot = 0;
|
|
15244
15475
|
#pending = /* @__PURE__ */ new Map();
|
|
15245
15476
|
constructor(opts) {
|
|
15246
|
-
this.#
|
|
15477
|
+
this.#coordination = opts.coordination;
|
|
15247
15478
|
this.#vault = opts.vault;
|
|
15248
15479
|
this.#onFlush = opts.onFlush;
|
|
15249
|
-
this.#
|
|
15480
|
+
this.#writerId = opts.clientId ?? "migrator";
|
|
15250
15481
|
this.#now = opts.now ?? (() => Date.now());
|
|
15251
15482
|
this.#staleMs = opts.staleMs ?? 3e4;
|
|
15252
15483
|
this.#quiesceTimeoutMs = opts.quiesceTimeoutMs ?? 6e4;
|
|
@@ -15255,7 +15486,7 @@ var init_fence_controller = __esm({
|
|
|
15255
15486
|
}
|
|
15256
15487
|
/** Capture the generation snapshot at vault-open. */
|
|
15257
15488
|
async init() {
|
|
15258
|
-
this.#snapshot = (await
|
|
15489
|
+
this.#snapshot = (await this.#coordination.readFence(this.#vault)).currentSchemaVersion;
|
|
15259
15490
|
}
|
|
15260
15491
|
/** Record a per-collection pending cutover (from a registration `cutover` decision). */
|
|
15261
15492
|
registerPendingCutover(collection, transform) {
|
|
@@ -15263,7 +15494,7 @@ var init_fence_controller = __esm({
|
|
|
15263
15494
|
}
|
|
15264
15495
|
/** Write-path gate. Throws when behind, fenced, or this collection is cutover-pending. */
|
|
15265
15496
|
async assertWritable(collection) {
|
|
15266
|
-
const fence = await
|
|
15497
|
+
const fence = await this.#coordination.readFence(this.#vault);
|
|
15267
15498
|
if (fence.currentSchemaVersion > this.#snapshot) {
|
|
15268
15499
|
throw new MigrationRequiredError(
|
|
15269
15500
|
`Vault "${this.#vault}" advanced to schema generation ${fence.currentSchemaVersion} (this client opened at ${this.#snapshot}). Reload to continue.`
|
|
@@ -15282,49 +15513,49 @@ var init_fence_controller = __esm({
|
|
|
15282
15513
|
* Admin trigger. Drain → wait for the active set to quiesce (or time out)
|
|
15283
15514
|
* → migrate each pending transform → bump → complete → normal. The
|
|
15284
15515
|
* migrator excludes itself from the barrier (it drained synchronously
|
|
15285
|
-
*
|
|
15286
|
-
* production falls back to a short real delay.
|
|
15516
|
+
* inside {@link runDrainBarrier}). `onPoll` (tests) advances other clients
|
|
15517
|
+
* between barrier checks; production falls back to a short real delay.
|
|
15287
15518
|
*/
|
|
15288
15519
|
async runCutover(run, opts) {
|
|
15289
15520
|
if (this.#pending.size === 0) return { migrated: 0 };
|
|
15290
|
-
const base = await
|
|
15521
|
+
const base = await this.#coordination.readFence(this.#vault);
|
|
15291
15522
|
const generation = base.currentSchemaVersion;
|
|
15292
|
-
|
|
15293
|
-
await this.#onFlush();
|
|
15294
|
-
const deadline = this.#now() + this.#quiesceTimeoutMs;
|
|
15295
|
-
while (!await activeQuiesced(this.#store, this.#vault, {
|
|
15296
|
-
generation,
|
|
15297
|
-
now: this.#now(),
|
|
15298
|
-
staleMs: this.#staleMs,
|
|
15299
|
-
excludeClientId: this.#clientId
|
|
15300
|
-
})) {
|
|
15301
|
-
if (this.#now() >= deadline) {
|
|
15302
|
-
throw new QuiesceTimeoutError(
|
|
15303
|
-
`Cutover on "${this.#vault}" timed out waiting for clients to quiesce at generation ${generation}.`
|
|
15304
|
-
);
|
|
15305
|
-
}
|
|
15306
|
-
await (opts?.onPoll ? opts.onPoll() : delay(50));
|
|
15307
|
-
}
|
|
15308
|
-
await this.#setState(generation, "migrating");
|
|
15523
|
+
this.#emit({ currentSchemaVersion: generation, fenceState: "draining" });
|
|
15309
15524
|
let migrated = 0;
|
|
15310
|
-
|
|
15311
|
-
|
|
15312
|
-
|
|
15313
|
-
|
|
15314
|
-
|
|
15315
|
-
|
|
15316
|
-
|
|
15317
|
-
|
|
15318
|
-
|
|
15525
|
+
await runDrainBarrier(
|
|
15526
|
+
this.#coordination,
|
|
15527
|
+
{
|
|
15528
|
+
vault: this.#vault,
|
|
15529
|
+
generation,
|
|
15530
|
+
writerId: this.#writerId,
|
|
15531
|
+
onFlush: this.#onFlush,
|
|
15532
|
+
staleMs: this.#staleMs,
|
|
15533
|
+
quiesceTimeoutMs: this.#quiesceTimeoutMs,
|
|
15534
|
+
now: this.#now,
|
|
15535
|
+
...opts?.onPoll ? { onPoll: opts.onPoll } : {}
|
|
15536
|
+
},
|
|
15537
|
+
async () => {
|
|
15538
|
+
await this.#setState(generation, "migrating");
|
|
15539
|
+
for (const [collection, transform] of this.#pending) {
|
|
15540
|
+
await run(collection, transform);
|
|
15541
|
+
migrated++;
|
|
15542
|
+
}
|
|
15543
|
+
const nextVersion = generation + 1;
|
|
15544
|
+
await this.#setState(nextVersion, "complete");
|
|
15545
|
+
this.#pending.clear();
|
|
15546
|
+
await this.#setState(nextVersion, "normal");
|
|
15547
|
+
this.#snapshot = nextVersion;
|
|
15548
|
+
}
|
|
15549
|
+
);
|
|
15319
15550
|
return { migrated };
|
|
15320
15551
|
}
|
|
15321
15552
|
/** Recover a stuck drain: reset fenceState to normal at the current version (no bump). */
|
|
15322
15553
|
async abort() {
|
|
15323
|
-
const fence = await
|
|
15554
|
+
const fence = await this.#coordination.readFence(this.#vault);
|
|
15324
15555
|
await this.#setState(fence.currentSchemaVersion, "normal");
|
|
15325
15556
|
}
|
|
15326
15557
|
async #setState(currentSchemaVersion, fenceState) {
|
|
15327
|
-
await
|
|
15558
|
+
await this.#coordination.setFence(this.#vault, { currentSchemaVersion, fenceState });
|
|
15328
15559
|
this.#emit({ currentSchemaVersion, fenceState });
|
|
15329
15560
|
}
|
|
15330
15561
|
};
|
|
@@ -15336,12 +15567,11 @@ var FenceWatcher;
|
|
|
15336
15567
|
var init_fence_watcher = __esm({
|
|
15337
15568
|
"src/schema-update/fence-watcher.ts"() {
|
|
15338
15569
|
"use strict";
|
|
15339
|
-
init_fence();
|
|
15340
|
-
init_client_registry();
|
|
15341
15570
|
FenceWatcher = class {
|
|
15342
|
-
#
|
|
15571
|
+
#coordination;
|
|
15343
15572
|
#vault;
|
|
15344
|
-
#
|
|
15573
|
+
#writerId;
|
|
15574
|
+
#sessionId;
|
|
15345
15575
|
#onFlush;
|
|
15346
15576
|
#now;
|
|
15347
15577
|
#emit;
|
|
@@ -15349,9 +15579,10 @@ var init_fence_watcher = __esm({
|
|
|
15349
15579
|
#quiescedAtVersion = null;
|
|
15350
15580
|
#timer;
|
|
15351
15581
|
constructor(opts) {
|
|
15352
|
-
this.#
|
|
15582
|
+
this.#coordination = opts.coordination;
|
|
15353
15583
|
this.#vault = opts.vault;
|
|
15354
|
-
this.#
|
|
15584
|
+
this.#writerId = opts.clientId;
|
|
15585
|
+
this.#sessionId = opts.sessionId ?? opts.clientId;
|
|
15355
15586
|
this.#onFlush = opts.onFlush;
|
|
15356
15587
|
this.#now = opts.now ?? (() => Date.now());
|
|
15357
15588
|
this.#emit = opts.emit ?? (() => {
|
|
@@ -15359,14 +15590,16 @@ var init_fence_watcher = __esm({
|
|
|
15359
15590
|
}
|
|
15360
15591
|
/** Publish liveness (and the current ack) without changing quiesce state. */
|
|
15361
15592
|
async beat() {
|
|
15362
|
-
await
|
|
15593
|
+
await this.#coordination.reportPresence(this.#vault, {
|
|
15594
|
+
writerId: this.#writerId,
|
|
15595
|
+
sessionId: this.#sessionId,
|
|
15363
15596
|
lastSeen: this.#now(),
|
|
15364
15597
|
quiescedAtVersion: this.#quiescedAtVersion
|
|
15365
15598
|
});
|
|
15366
15599
|
}
|
|
15367
15600
|
/** Poll the fence; quiesce on draining; emit on transitions. */
|
|
15368
15601
|
async check() {
|
|
15369
|
-
const fence = await
|
|
15602
|
+
const fence = await this.#coordination.readFence(this.#vault);
|
|
15370
15603
|
if (fence.fenceState !== this.#lastState) {
|
|
15371
15604
|
this.#lastState = fence.fenceState;
|
|
15372
15605
|
this.#emit({ currentSchemaVersion: fence.currentSchemaVersion, fenceState: fence.fenceState });
|
|
@@ -16678,10 +16911,11 @@ var init_vault = __esm({
|
|
|
16678
16911
|
this.keyring = opts.keyring;
|
|
16679
16912
|
this.encrypted = opts.encrypted;
|
|
16680
16913
|
this.schemaFence = new SchemaFenceController({
|
|
16681
|
-
|
|
16914
|
+
coordination: this.noydb.coordination,
|
|
16682
16915
|
vault: this.name,
|
|
16683
16916
|
onFlush: () => this.noydb._writeQueueTracker.onFlush(),
|
|
16684
16917
|
clientId: this.noydb._clientId,
|
|
16918
|
+
sessionId: this.noydb._sessionId,
|
|
16685
16919
|
emit: (e) => this.emitter.emit("schema:fence-changed", { vault: this.name, ...e })
|
|
16686
16920
|
});
|
|
16687
16921
|
this.emitter = opts.emitter;
|
|
@@ -17077,9 +17311,10 @@ var init_vault = __esm({
|
|
|
17077
17311
|
if (this.#fenceCoordinationStarted) return;
|
|
17078
17312
|
this.#fenceCoordinationStarted = true;
|
|
17079
17313
|
this.#fenceWatcher = new FenceWatcher({
|
|
17080
|
-
|
|
17314
|
+
coordination: this.noydb.coordination,
|
|
17081
17315
|
vault: this.name,
|
|
17082
17316
|
clientId: this.noydb._clientId,
|
|
17317
|
+
sessionId: this.noydb._sessionId,
|
|
17083
17318
|
onFlush: () => this.noydb._writeQueueTracker.onFlush(),
|
|
17084
17319
|
emit: (e) => this.emitter.emit("schema:fence-changed", { vault: this.name, ...e })
|
|
17085
17320
|
});
|
|
@@ -21043,6 +21278,7 @@ var init_noydb = __esm({
|
|
|
21043
21278
|
init_recovery();
|
|
21044
21279
|
init_managed_passphrase();
|
|
21045
21280
|
init_ulid();
|
|
21281
|
+
init_coordination();
|
|
21046
21282
|
init_errors2();
|
|
21047
21283
|
init_auth_introspection();
|
|
21048
21284
|
init_public_envelope();
|
|
@@ -21086,6 +21322,10 @@ var init_noydb = __esm({
|
|
|
21086
21322
|
writeHooks = new WriteHookRegistry();
|
|
21087
21323
|
subsystemBus = new SubsystemBus();
|
|
21088
21324
|
clientId = generateULID();
|
|
21325
|
+
/** Session that owns this instance's writers (one user's writers across vaults). */
|
|
21326
|
+
sessionId;
|
|
21327
|
+
/** Drain-barrier coordination transport for the schema fence (#469). */
|
|
21328
|
+
coordinationProvider;
|
|
21089
21329
|
vaultCache = /* @__PURE__ */ new Map();
|
|
21090
21330
|
keyringCache = /* @__PURE__ */ new Map();
|
|
21091
21331
|
syncEngines = /* @__PURE__ */ new Map();
|
|
@@ -21160,6 +21400,8 @@ var init_noydb = __esm({
|
|
|
21160
21400
|
"[noydb] debugPlaintext is ON \u2014 records are stored UNENCRYPTED and laid out for native store inspection. NEVER use this for production or client data."
|
|
21161
21401
|
);
|
|
21162
21402
|
}
|
|
21403
|
+
this.sessionId = options.sessionId ?? generateULID();
|
|
21404
|
+
this.coordinationProvider = options.coordinationStrategy ?? new StoreCoordinationProvider(options.store);
|
|
21163
21405
|
this.txStrategy = options.txStrategy ?? NO_TX;
|
|
21164
21406
|
this.forgetStrategy = options.forgetStrategy ?? NO_FORGET;
|
|
21165
21407
|
this.sessionStrategy = options.sessionStrategy ?? NO_SESSION;
|
|
@@ -22213,6 +22455,18 @@ var init_noydb = __esm({
|
|
|
22213
22455
|
get _clientId() {
|
|
22214
22456
|
return this.clientId;
|
|
22215
22457
|
}
|
|
22458
|
+
/** @internal Session that owns this instance's writers (#469). */
|
|
22459
|
+
get _sessionId() {
|
|
22460
|
+
return this.sessionId;
|
|
22461
|
+
}
|
|
22462
|
+
/**
|
|
22463
|
+
* @internal Drain-barrier coordination transport for the schema fence (#469).
|
|
22464
|
+
* The default store-backed provider reproduces today's fence behavior; a
|
|
22465
|
+
* `by-*` real-time transport is injected via `coordinationStrategy`.
|
|
22466
|
+
*/
|
|
22467
|
+
get coordination() {
|
|
22468
|
+
return this.coordinationProvider;
|
|
22469
|
+
}
|
|
22216
22470
|
/**
|
|
22217
22471
|
* Soft-lock a single vault: clear its in-memory keyring, DEKs, vault
|
|
22218
22472
|
* instance, sync engine, policy enforcer, and active-tier entry —
|