@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.
Files changed (182) hide show
  1. package/dist/aggregate/index.cjs +7 -0
  2. package/dist/aggregate/index.cjs.map +1 -1
  3. package/dist/aggregate/index.d.cts +2 -2
  4. package/dist/aggregate/index.d.ts +2 -2
  5. package/dist/aggregate/index.js +2 -2
  6. package/dist/attestation/index.cjs.map +1 -1
  7. package/dist/attestation/index.d.cts +3 -3
  8. package/dist/attestation/index.d.ts +3 -3
  9. package/dist/attestation/index.js +4 -4
  10. package/dist/blobs/index.cjs.map +1 -1
  11. package/dist/blobs/index.d.cts +5 -5
  12. package/dist/blobs/index.d.ts +5 -5
  13. package/dist/blobs/index.js +4 -4
  14. package/dist/bundle/index.cjs +403 -149
  15. package/dist/bundle/index.cjs.map +1 -1
  16. package/dist/bundle/index.d.cts +5 -5
  17. package/dist/bundle/index.d.ts +5 -5
  18. package/dist/bundle/index.js +8 -8
  19. package/dist/{chunk-PS6PSEZL.js → chunk-2M62POB5.js} +3 -3
  20. package/dist/{chunk-C7UIT5XY.js → chunk-345PYZD6.js} +2 -2
  21. package/dist/{chunk-56ENKU46.js → chunk-3LHTFCU2.js} +186 -161
  22. package/dist/chunk-3LHTFCU2.js.map +1 -0
  23. package/dist/{chunk-ANLOD6IS.js → chunk-42EGY2FQ.js} +3 -3
  24. package/dist/{chunk-KJ37E3R5.js → chunk-4FHKQDHN.js} +2 -2
  25. package/dist/{chunk-KD253AI5.js → chunk-6LD37AMK.js} +22 -3
  26. package/dist/chunk-6LD37AMK.js.map +1 -0
  27. package/dist/{chunk-EYZJULEN.js → chunk-7GKDVWMD.js} +2 -2
  28. package/dist/{chunk-N4EXCKWP.js → chunk-7TBCLM52.js} +2 -2
  29. package/dist/{chunk-VJNV2GRF.js → chunk-AHDQYG7P.js} +3 -3
  30. package/dist/{chunk-ZCBJIDT4.js → chunk-DCYWBKQ2.js} +2 -2
  31. package/dist/{chunk-Y5CTT6K5.js → chunk-DMWUOV7X.js} +2 -2
  32. package/dist/{chunk-RZOGD7IF.js → chunk-DQ6XF2K2.js} +6 -6
  33. package/dist/{chunk-OCRDV3NU.js → chunk-ENC4C6XW.js} +3 -3
  34. package/dist/{chunk-FCIZXX56.js → chunk-GVEUI7VR.js} +2 -2
  35. package/dist/{chunk-WVYL6HM7.js → chunk-HI7FY7QZ.js} +2 -2
  36. package/dist/{chunk-JJKXJAH2.js → chunk-HQ242WNG.js} +3 -3
  37. package/dist/{chunk-GHXOVGTX.js → chunk-IMY4FXYE.js} +3 -3
  38. package/dist/{chunk-YP2AYE5W.js → chunk-JEQB2KVI.js} +2 -2
  39. package/dist/chunk-M2VZQ24Q.js +220 -0
  40. package/dist/chunk-M2VZQ24Q.js.map +1 -0
  41. package/dist/{chunk-HUXDQIVU.js → chunk-PPF7Z2YQ.js} +2 -2
  42. package/dist/{chunk-2RHBFCWQ.js → chunk-QBC2TZHC.js} +3 -3
  43. package/dist/{chunk-LR7CODVN.js → chunk-QSLIT4JZ.js} +1 -1
  44. package/dist/chunk-QSLIT4JZ.js.map +1 -0
  45. package/dist/{chunk-TSUICI5N.js → chunk-RG5KLMEU.js} +2 -2
  46. package/dist/{chunk-GPZHHTJU.js → chunk-SWGVCSIY.js} +2 -2
  47. package/dist/{chunk-UNBX2HMA.js → chunk-VSL3W2MO.js} +2 -2
  48. package/dist/{chunk-VGAN5RLD.js → chunk-WH7G2YEE.js} +2 -2
  49. package/dist/{chunk-QYQRAOEF.js → chunk-YYLFXX2K.js} +2 -2
  50. package/dist/consent/index.d.cts +4 -4
  51. package/dist/consent/index.d.ts +4 -4
  52. package/dist/{decrypt-partition-CyyJUWLR.d.ts → decrypt-partition-BtGtE-19.d.ts} +1 -1
  53. package/dist/{decrypt-partition-C71vhnND.d.cts → decrypt-partition-Cqi5gcOl.d.cts} +1 -1
  54. package/dist/derivations/index.d.cts +5 -5
  55. package/dist/derivations/index.d.ts +5 -5
  56. package/dist/{dev-unlock-BdrE0kbS.d.cts → dev-unlock-Cts_iiVv.d.cts} +1 -1
  57. package/dist/{dev-unlock-ByBkl99-.d.ts → dev-unlock-D0p9cQzN.d.ts} +1 -1
  58. package/dist/{executor-BIW4FT5R.js → executor-PJHMRZWJ.js} +4 -4
  59. package/dist/{fanout-sidecar-ZQT4Y7PF.js → fanout-sidecar-DSBVAR2P.js} +2 -2
  60. package/dist/forget/index.js +2 -2
  61. package/dist/guards/index.d.cts +5 -5
  62. package/dist/guards/index.d.ts +5 -5
  63. package/dist/{hash-CZxVv8RH.d.ts → hash-BtDtwguU.d.ts} +1 -1
  64. package/dist/{hash-BUkDp_8Q.d.cts → hash-DA75XwW2.d.cts} +1 -1
  65. package/dist/history/index.cjs.map +1 -1
  66. package/dist/history/index.d.cts +5 -5
  67. package/dist/history/index.d.ts +5 -5
  68. package/dist/history/index.js +3 -3
  69. package/dist/i18n/index.cjs +98 -1
  70. package/dist/i18n/index.cjs.map +1 -1
  71. package/dist/i18n/index.d.cts +4 -4
  72. package/dist/i18n/index.d.ts +4 -4
  73. package/dist/i18n/index.js +83 -4
  74. package/dist/i18n/index.js.map +1 -1
  75. package/dist/{index-DFhKV-6A.d.ts → index-BidHvmWf.d.ts} +3 -3
  76. package/dist/{index-CBUhOmrM.d.cts → index-CP24aYCp.d.cts} +3 -3
  77. package/dist/{index-DoxKSsMj.d.cts → index-CVnt2Qmq.d.cts} +1 -1
  78. package/dist/{index-LaexBi3v.d.ts → index-DxBNV54L.d.ts} +1 -1
  79. package/dist/index.cjs +411 -146
  80. package/dist/index.cjs.map +1 -1
  81. package/dist/index.d.cts +14 -14
  82. package/dist/index.d.ts +14 -14
  83. package/dist/index.js +24 -23
  84. package/dist/index.js.map +1 -1
  85. package/dist/{issue-LEBPVF3Y.js → issue-JSGGSVY4.js} +4 -4
  86. package/dist/kernel/index.cjs +61 -0
  87. package/dist/kernel/index.cjs.map +1 -1
  88. package/dist/kernel/index.d.cts +4 -4
  89. package/dist/kernel/index.d.ts +4 -4
  90. package/dist/kernel/index.js +9 -2
  91. package/dist/{ledger-FLRTSOYH.js → ledger-PLMSH7LD.js} +3 -3
  92. package/dist/materialized-views/index.cjs +7 -0
  93. package/dist/materialized-views/index.cjs.map +1 -1
  94. package/dist/materialized-views/index.d.cts +5 -5
  95. package/dist/materialized-views/index.d.ts +5 -5
  96. package/dist/materialized-views/index.js +4 -4
  97. package/dist/{mime-magic-C1UbcBxP.d.ts → mime-magic--NcogI82.d.ts} +1 -1
  98. package/dist/{mime-magic-BAhLjkHw.d.cts → mime-magic-i2VSlkPM.d.cts} +1 -1
  99. package/dist/noydb-PHXA5E6I.js +39 -0
  100. package/dist/overlay-views/index.d.cts +5 -5
  101. package/dist/overlay-views/index.d.ts +5 -5
  102. package/dist/periods/index.cjs.map +1 -1
  103. package/dist/periods/index.d.cts +4 -4
  104. package/dist/periods/index.d.ts +4 -4
  105. package/dist/periods/index.js +3 -3
  106. package/dist/{public-envelope-DBKJEBBF.js → public-envelope-7MTH2PVE.js} +3 -3
  107. package/dist/query/index.cjs +7 -0
  108. package/dist/query/index.cjs.map +1 -1
  109. package/dist/query/index.d.cts +2 -2
  110. package/dist/query/index.d.ts +2 -2
  111. package/dist/query/index.js +3 -3
  112. package/dist/{revoke-P5D3UTRX.js → revoke-DBAGKIDA.js} +4 -4
  113. package/dist/session/index.d.cts +5 -5
  114. package/dist/session/index.d.ts +5 -5
  115. package/dist/shadow/index.d.cts +4 -4
  116. package/dist/shadow/index.d.ts +4 -4
  117. package/dist/{signer-NEQPCHMW.js → signer-ADFJNS5W.js} +3 -3
  118. package/dist/snapshots/index.d.cts +4 -4
  119. package/dist/snapshots/index.d.ts +4 -4
  120. package/dist/snapshots/index.js +3 -3
  121. package/dist/{stale-KKCHF2VB.js → stale-BCIE3SMC.js} +2 -2
  122. package/dist/store/index.d.cts +4 -4
  123. package/dist/store/index.d.ts +4 -4
  124. package/dist/{strategy-YQ1qJWyq.d.ts → strategy-BoITAb2H.d.ts} +20 -1
  125. package/dist/{strategy-D1zjEV3n.d.cts → strategy-DDNvt_UD.d.cts} +20 -1
  126. package/dist/sync/index.cjs.map +1 -1
  127. package/dist/sync/index.d.cts +3 -3
  128. package/dist/sync/index.d.ts +3 -3
  129. package/dist/sync/index.js +2 -2
  130. package/dist/team/index.cjs.map +1 -1
  131. package/dist/team/index.d.cts +4 -4
  132. package/dist/team/index.d.ts +4 -4
  133. package/dist/team/index.js +5 -5
  134. package/dist/{transition-guard-BSLdikC_.d.ts → transition-guard-BZhOlrFT.d.ts} +1 -1
  135. package/dist/{transition-guard-DPs6al8h.d.cts → transition-guard-Bekgaxux.d.cts} +1 -1
  136. package/dist/tx/index.d.cts +4 -4
  137. package/dist/tx/index.d.ts +4 -4
  138. package/dist/{types-CCq0WHh9.d.ts → types-84nsWSDF.d.ts} +182 -11
  139. package/dist/{types-BCYvhKzr.d.cts → types-B_eCkuEI.d.cts} +182 -11
  140. package/dist/{with-materialized-view-DiD41wQp.d.ts → with-materialized-view-CAMGQroY.d.ts} +1 -1
  141. package/dist/{with-materialized-view-CTHe6uh9.d.cts → with-materialized-view-DKMaZmkJ.d.cts} +1 -1
  142. package/dist/{with-overlayed-view-Dlz5hcM8.d.cts → with-overlayed-view--lWXImbV.d.cts} +1 -1
  143. package/dist/{with-overlayed-view-DlbsJMhF.d.ts → with-overlayed-view-FnOf1v6H.d.ts} +1 -1
  144. package/dist/{with-rollup-BBWdrCvu.d.cts → with-rollup-C5875Ad5.d.cts} +1 -1
  145. package/dist/{with-rollup-mT4_CWaU.d.ts → with-rollup-xi-mkY6Y.d.ts} +1 -1
  146. package/package.json +3 -3
  147. package/dist/chunk-56ENKU46.js.map +0 -1
  148. package/dist/chunk-KD253AI5.js.map +0 -1
  149. package/dist/chunk-LR7CODVN.js.map +0 -1
  150. package/dist/noydb-6FA46A4M.js +0 -38
  151. /package/dist/{chunk-PS6PSEZL.js.map → chunk-2M62POB5.js.map} +0 -0
  152. /package/dist/{chunk-C7UIT5XY.js.map → chunk-345PYZD6.js.map} +0 -0
  153. /package/dist/{chunk-ANLOD6IS.js.map → chunk-42EGY2FQ.js.map} +0 -0
  154. /package/dist/{chunk-KJ37E3R5.js.map → chunk-4FHKQDHN.js.map} +0 -0
  155. /package/dist/{chunk-EYZJULEN.js.map → chunk-7GKDVWMD.js.map} +0 -0
  156. /package/dist/{chunk-N4EXCKWP.js.map → chunk-7TBCLM52.js.map} +0 -0
  157. /package/dist/{chunk-VJNV2GRF.js.map → chunk-AHDQYG7P.js.map} +0 -0
  158. /package/dist/{chunk-ZCBJIDT4.js.map → chunk-DCYWBKQ2.js.map} +0 -0
  159. /package/dist/{chunk-Y5CTT6K5.js.map → chunk-DMWUOV7X.js.map} +0 -0
  160. /package/dist/{chunk-RZOGD7IF.js.map → chunk-DQ6XF2K2.js.map} +0 -0
  161. /package/dist/{chunk-OCRDV3NU.js.map → chunk-ENC4C6XW.js.map} +0 -0
  162. /package/dist/{chunk-FCIZXX56.js.map → chunk-GVEUI7VR.js.map} +0 -0
  163. /package/dist/{chunk-WVYL6HM7.js.map → chunk-HI7FY7QZ.js.map} +0 -0
  164. /package/dist/{chunk-JJKXJAH2.js.map → chunk-HQ242WNG.js.map} +0 -0
  165. /package/dist/{chunk-GHXOVGTX.js.map → chunk-IMY4FXYE.js.map} +0 -0
  166. /package/dist/{chunk-YP2AYE5W.js.map → chunk-JEQB2KVI.js.map} +0 -0
  167. /package/dist/{chunk-HUXDQIVU.js.map → chunk-PPF7Z2YQ.js.map} +0 -0
  168. /package/dist/{chunk-2RHBFCWQ.js.map → chunk-QBC2TZHC.js.map} +0 -0
  169. /package/dist/{chunk-TSUICI5N.js.map → chunk-RG5KLMEU.js.map} +0 -0
  170. /package/dist/{chunk-GPZHHTJU.js.map → chunk-SWGVCSIY.js.map} +0 -0
  171. /package/dist/{chunk-UNBX2HMA.js.map → chunk-VSL3W2MO.js.map} +0 -0
  172. /package/dist/{chunk-VGAN5RLD.js.map → chunk-WH7G2YEE.js.map} +0 -0
  173. /package/dist/{chunk-QYQRAOEF.js.map → chunk-YYLFXX2K.js.map} +0 -0
  174. /package/dist/{executor-BIW4FT5R.js.map → executor-PJHMRZWJ.js.map} +0 -0
  175. /package/dist/{fanout-sidecar-ZQT4Y7PF.js.map → fanout-sidecar-DSBVAR2P.js.map} +0 -0
  176. /package/dist/{issue-LEBPVF3Y.js.map → issue-JSGGSVY4.js.map} +0 -0
  177. /package/dist/{ledger-FLRTSOYH.js.map → ledger-PLMSH7LD.js.map} +0 -0
  178. /package/dist/{noydb-6FA46A4M.js.map → noydb-PHXA5E6I.js.map} +0 -0
  179. /package/dist/{public-envelope-DBKJEBBF.js.map → public-envelope-7MTH2PVE.js.map} +0 -0
  180. /package/dist/{revoke-P5D3UTRX.js.map → revoke-DBAGKIDA.js.map} +0 -0
  181. /package/dist/{signer-NEQPCHMW.js.map → signer-ADFJNS5W.js.map} +0 -0
  182. /package/dist/{stale-KKCHF2VB.js.map → stale-BCIE3SMC.js.map} +0 -0
@@ -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, META_COLLECTION2, POLICY_RECORD_ID);
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, META_COLLECTION2, POLICY_RECORD_ID, envelope);
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 META_COLLECTION2, POLICY_RECORD_ID;
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
- META_COLLECTION2 = "_meta";
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 init_types3 = __esm({
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
- init_types3();
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
- init_client_registry();
15464
+ init_coordination();
15234
15465
  SchemaFenceController = class {
15235
- #store;
15466
+ #coordination;
15236
15467
  #vault;
15237
15468
  #onFlush;
15238
- #clientId;
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.#store = opts.store;
15477
+ this.#coordination = opts.coordination;
15247
15478
  this.#vault = opts.vault;
15248
15479
  this.#onFlush = opts.onFlush;
15249
- this.#clientId = opts.clientId ?? "migrator";
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 loadFence(this.#store, this.#vault)).currentSchemaVersion;
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 loadFence(this.#store, this.#vault);
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
- * here). `onPoll` (tests) advances other clients between barrier checks;
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 loadFence(this.#store, this.#vault);
15521
+ const base = await this.#coordination.readFence(this.#vault);
15291
15522
  const generation = base.currentSchemaVersion;
15292
- await this.#setState(generation, "draining");
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
- for (const [collection, transform] of this.#pending) {
15311
- await run(collection, transform);
15312
- migrated++;
15313
- }
15314
- const nextVersion = generation + 1;
15315
- await this.#setState(nextVersion, "complete");
15316
- this.#pending.clear();
15317
- await this.#setState(nextVersion, "normal");
15318
- this.#snapshot = nextVersion;
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 loadFence(this.#store, this.#vault);
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 saveFence(this.#store, this.#vault, { currentSchemaVersion, fenceState });
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
- #store;
15571
+ #coordination;
15343
15572
  #vault;
15344
- #clientId;
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.#store = opts.store;
15582
+ this.#coordination = opts.coordination;
15353
15583
  this.#vault = opts.vault;
15354
- this.#clientId = opts.clientId;
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 writeClientDoc(this.#store, this.#vault, this.#clientId, {
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 loadFence(this.#store, this.#vault);
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
- store: this.adapter,
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
- store: this.adapter,
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 —