@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
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
- init_client_registry();
18105
+ init_coordination();
17864
18106
  SchemaFenceController = class {
17865
- #store;
18107
+ #coordination;
17866
18108
  #vault;
17867
18109
  #onFlush;
17868
- #clientId;
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.#store = opts.store;
18118
+ this.#coordination = opts.coordination;
17877
18119
  this.#vault = opts.vault;
17878
18120
  this.#onFlush = opts.onFlush;
17879
- this.#clientId = opts.clientId ?? "migrator";
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 loadFence(this.#store, this.#vault)).currentSchemaVersion;
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 loadFence(this.#store, this.#vault);
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
- * here). `onPoll` (tests) advances other clients between barrier checks;
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 loadFence(this.#store, this.#vault);
18162
+ const base = await this.#coordination.readFence(this.#vault);
17921
18163
  const generation = base.currentSchemaVersion;
17922
- await this.#setState(generation, "draining");
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
- for (const [collection, transform] of this.#pending) {
17941
- await run(collection, transform);
17942
- migrated++;
17943
- }
17944
- const nextVersion = generation + 1;
17945
- await this.#setState(nextVersion, "complete");
17946
- this.#pending.clear();
17947
- await this.#setState(nextVersion, "normal");
17948
- this.#snapshot = nextVersion;
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 loadFence(this.#store, this.#vault);
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 saveFence(this.#store, this.#vault, { currentSchemaVersion, fenceState });
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
- #store;
18212
+ #coordination;
17973
18213
  #vault;
17974
- #clientId;
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.#store = opts.store;
18223
+ this.#coordination = opts.coordination;
17983
18224
  this.#vault = opts.vault;
17984
- this.#clientId = opts.clientId;
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 writeClientDoc(this.#store, this.#vault, this.#clientId, {
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 loadFence(this.#store, this.#vault);
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
- store: this.adapter,
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
- store: this.adapter,
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 delay2 = backoffMs * Math.pow(2, attempt) * (1 + Math.random() * jitter);
28040
- await new Promise((r) => setTimeout(r, delay2));
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;