@noy-db/hub 0.4.0-pre.2 → 0.4.0-pre.4

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 (72) hide show
  1. package/dist/aggregate/index.js +1 -1
  2. package/dist/blobs/index.js.map +1 -1
  3. package/dist/broker/index.js +3 -3
  4. package/dist/broker/index.js.map +1 -1
  5. package/dist/cargo/index.js +5 -5
  6. package/dist/{chunk-OGY4ELVE.js → chunk-3XVOLB75.js} +2 -2
  7. package/dist/{chunk-OTU5ZI3U.js → chunk-4X6DJZHY.js} +3 -3
  8. package/dist/{chunk-IU22SQGU.js → chunk-5KI3REO3.js} +2 -2
  9. package/dist/{chunk-IU22SQGU.js.map → chunk-5KI3REO3.js.map} +1 -1
  10. package/dist/{chunk-PWZKD4HW.js → chunk-BLB7P7LS.js} +3 -3
  11. package/dist/{chunk-Y5E6CH6S.js → chunk-EW4HFWGJ.js} +2 -2
  12. package/dist/{chunk-S3OYXPSE.js → chunk-HKR25MGX.js} +2 -2
  13. package/dist/{chunk-RI3OKC3Y.js → chunk-JIMEB4C6.js} +1 -1
  14. package/dist/chunk-JIMEB4C6.js.map +1 -0
  15. package/dist/{chunk-J6U4BOKP.js → chunk-JW3XCZA2.js} +9 -9
  16. package/dist/{chunk-4JYYIFHW.js → chunk-QUIG6BZT.js} +16 -16
  17. package/dist/{chunk-4JYYIFHW.js.map → chunk-QUIG6BZT.js.map} +1 -1
  18. package/dist/{chunk-TYBXP6WE.js → chunk-QYJIOWKN.js} +2 -2
  19. package/dist/{chunk-A64XXIIB.js → chunk-U73LHVPF.js} +17 -17
  20. package/dist/{chunk-A64XXIIB.js.map → chunk-U73LHVPF.js.map} +1 -1
  21. package/dist/{chunk-VZELV7ZJ.js → chunk-W7TCXZWU.js} +455 -554
  22. package/dist/chunk-W7TCXZWU.js.map +1 -0
  23. package/dist/{chunk-RGL53NXZ.js → chunk-WEC3JGBA.js} +2 -2
  24. package/dist/{chunk-HVZT5T22.js → chunk-WJDYZP3U.js} +1 -1
  25. package/dist/chunk-WJDYZP3U.js.map +1 -0
  26. package/dist/{collection-facade-ENLTJZBT.js → collection-facade-CMZGQ6IS.js} +2 -2
  27. package/dist/{executor-UF7LTX6O.js → executor-YORMIBWV.js} +3 -3
  28. package/dist/index.d.ts +5 -0
  29. package/dist/index.js +14 -14
  30. package/dist/kernel/collection-config.d.ts +11 -69
  31. package/dist/kernel/collection.d.ts +10 -21
  32. package/dist/kernel/enclave/index.d.ts +1 -1
  33. package/dist/kernel/noydb.d.ts +24 -13
  34. package/dist/kernel/vault.d.ts +21 -68
  35. package/dist/{liberate-W5MDWYOV.js → liberate-FK7JI2QX.js} +3 -3
  36. package/dist/materialized-views/index.js +3 -3
  37. package/dist/{noydb-U63S3Z7F.js → noydb-N5IW6OFZ.js} +10 -10
  38. package/dist/port/with/archive-strategy.d.ts +31 -0
  39. package/dist/port/with/strategies.d.ts +134 -0
  40. package/dist/query/index.js +2 -2
  41. package/dist/{seed-6GTDHL4E.js → seed-EKF4DIBB.js} +2 -2
  42. package/dist/{stale-NB5BGRVU.js → stale-4UNPHWOU.js} +2 -2
  43. package/dist/team/index.js +3 -3
  44. package/dist/tiers/index.js +1 -1
  45. package/dist/via/blob/active.d.ts +2 -1
  46. package/dist/with/index.js +2 -2
  47. package/dist/with-audit/forget/index.d.ts +2 -1
  48. package/dist/with-audit/tiers/index.d.ts +1 -0
  49. package/dist/with-cargo/index.d.ts +2 -0
  50. package/dist/with-formula/derivations/index.d.ts +2 -0
  51. package/dist/with-formula/materialized-views/index.d.ts +1 -0
  52. package/dist/with-lookup/aggregate/reducers.d.ts +1 -2
  53. package/dist/with-party/broker/index.d.ts +1 -0
  54. package/dist/with-party/team/keyring.d.ts +2 -2
  55. package/package.json +5 -4
  56. package/dist/chunk-HVZT5T22.js.map +0 -1
  57. package/dist/chunk-RI3OKC3Y.js.map +0 -1
  58. package/dist/chunk-VZELV7ZJ.js.map +0 -1
  59. /package/dist/{chunk-OGY4ELVE.js.map → chunk-3XVOLB75.js.map} +0 -0
  60. /package/dist/{chunk-OTU5ZI3U.js.map → chunk-4X6DJZHY.js.map} +0 -0
  61. /package/dist/{chunk-PWZKD4HW.js.map → chunk-BLB7P7LS.js.map} +0 -0
  62. /package/dist/{chunk-Y5E6CH6S.js.map → chunk-EW4HFWGJ.js.map} +0 -0
  63. /package/dist/{chunk-S3OYXPSE.js.map → chunk-HKR25MGX.js.map} +0 -0
  64. /package/dist/{chunk-J6U4BOKP.js.map → chunk-JW3XCZA2.js.map} +0 -0
  65. /package/dist/{chunk-TYBXP6WE.js.map → chunk-QYJIOWKN.js.map} +0 -0
  66. /package/dist/{chunk-RGL53NXZ.js.map → chunk-WEC3JGBA.js.map} +0 -0
  67. /package/dist/{collection-facade-ENLTJZBT.js.map → collection-facade-CMZGQ6IS.js.map} +0 -0
  68. /package/dist/{executor-UF7LTX6O.js.map → executor-YORMIBWV.js.map} +0 -0
  69. /package/dist/{liberate-W5MDWYOV.js.map → liberate-FK7JI2QX.js.map} +0 -0
  70. /package/dist/{noydb-U63S3Z7F.js.map → noydb-N5IW6OFZ.js.map} +0 -0
  71. /package/dist/{seed-6GTDHL4E.js.map → seed-EKF4DIBB.js.map} +0 -0
  72. /package/dist/{stale-NB5BGRVU.js.map → stale-4UNPHWOU.js.map} +0 -0
@@ -31,7 +31,7 @@ import {
31
31
  buildPersistedIndexCallbacks,
32
32
  encodeVecId,
33
33
  syncTierSearch
34
- } from "./chunk-J6U4BOKP.js";
34
+ } from "./chunk-JW3XCZA2.js";
35
35
  import {
36
36
  derivePersistedSchema,
37
37
  isZod4Schema
@@ -44,7 +44,7 @@ import {
44
44
  CustodyApi,
45
45
  NO_CARGO,
46
46
  NO_CUSTODY
47
- } from "./chunk-A64XXIIB.js";
47
+ } from "./chunk-U73LHVPF.js";
48
48
  import {
49
49
  resolveManagedSecret,
50
50
  saveSealedPassphrase
@@ -56,7 +56,7 @@ import {
56
56
  assertCanImport,
57
57
  canExport,
58
58
  canImport
59
- } from "./chunk-RGL53NXZ.js";
59
+ } from "./chunk-WEC3JGBA.js";
60
60
  import {
61
61
  runDrainBarrier
62
62
  } from "./chunk-KMONEHDJ.js";
@@ -72,7 +72,7 @@ import {
72
72
  NO_TIERS,
73
73
  classifySealedShred,
74
74
  syncDerivedOutputs
75
- } from "./chunk-IU22SQGU.js";
75
+ } from "./chunk-5KI3REO3.js";
76
76
  import {
77
77
  NO_PORTABILITY
78
78
  } from "./chunk-GYB2QK5H.js";
@@ -115,7 +115,7 @@ import {
115
115
  NO_AGGREGATE,
116
116
  Query,
117
117
  ScanBuilder
118
- } from "./chunk-OTU5ZI3U.js";
118
+ } from "./chunk-4X6DJZHY.js";
119
119
  import {
120
120
  EXPORT_AUDIT_COLLECTION,
121
121
  createExportBlobsHandle,
@@ -133,7 +133,7 @@ import {
133
133
  } from "./chunk-NO272T7Q.js";
134
134
  import {
135
135
  canonicalGroupKey
136
- } from "./chunk-RI3OKC3Y.js";
136
+ } from "./chunk-JIMEB4C6.js";
137
137
  import {
138
138
  readPath
139
139
  } from "./chunk-IGJCYUT5.js";
@@ -163,7 +163,7 @@ import {
163
163
  rotatePassphrase,
164
164
  updateAuthenticator,
165
165
  writeMagicLinkGrant
166
- } from "./chunk-4JYYIFHW.js";
166
+ } from "./chunk-QUIG6BZT.js";
167
167
  import {
168
168
  assertKeyringOpenAllowed,
169
169
  buildRecipientKeyringFile,
@@ -176,7 +176,7 @@ import {
176
176
  listUsers,
177
177
  loadKeyring,
178
178
  updateKeyringIdentity
179
- } from "./chunk-HVZT5T22.js";
179
+ } from "./chunk-WJDYZP3U.js";
180
180
  import {
181
181
  persistDirectoryConfig,
182
182
  readDirectoryConfig
@@ -293,6 +293,284 @@ import {
293
293
  ValidationError
294
294
  } from "./chunk-IGM563N5.js";
295
295
 
296
+ // src/via/blob/binding.ts
297
+ async function eraseBlobs(ctx, cfg) {
298
+ if (cfg.purgeBlobsForRecord) return cfg.purgeBlobsForRecord(ctx.id);
299
+ return { shredded: 0, residue: [] };
300
+ }
301
+ function buildBlobDescribeFragment(fields) {
302
+ return {
303
+ blobFields: Object.fromEntries(
304
+ Object.entries(fields).map(([field, p]) => [field, {
305
+ ...p.retainDays !== void 0 ? { retainDays: p.retainDays } : {},
306
+ ...p.evictWhen !== void 0 ? { evictWhen: true } : {},
307
+ ...p.legalHold !== void 0 ? { legalHold: true } : {},
308
+ ...p.retainUntil !== void 0 ? { retainUntil: true } : {},
309
+ ...p.external === true ? { external: true } : {},
310
+ ...p.public === true ? { public: true } : {},
311
+ ...p.backlink !== void 0 ? { backlink: p.backlink } : {}
312
+ }])
313
+ )
314
+ };
315
+ }
316
+ function blobBinding(cfg) {
317
+ return {
318
+ brand: "blob",
319
+ // encryptedAtRest: 'envelope' — blob chunks are AEAD-encrypted envelopes
320
+ // in `_blob_chunks` (service-side), not sealed record fields; queryable:
321
+ // 'none' — nothing indexes blob content; exportable — vault.exportBlobs()
322
+ // is a first-class door; forgettable — shredAllForRecord participates in
323
+ // vault.forget().
324
+ posture: { encryptedAtRest: "envelope", queryable: "none", exportable: true, forgettable: true },
325
+ covers: (field) => field in cfg.fields,
326
+ erase: (ctx) => eraseBlobs(ctx, cfg),
327
+ describeFragment: () => buildBlobDescribeFragment(cfg.fields)
328
+ };
329
+ }
330
+ function linkBlobVia() {
331
+ installViaBinder("blob", (c) => blobBinding(c));
332
+ }
333
+
334
+ // src/port/with/blob-strategy.ts
335
+ linkBlobVia();
336
+ var NO_BLOBS = {
337
+ openSlot() {
338
+ throw new Error(
339
+ 'Blob storage is not enabled on this Noydb instance. Import `{ withBlobs }` from "@noy-db/hub/blobs" and pass `withBlobs()` to `createNoydb({ blobStrategy: withBlobs() })`.'
340
+ );
341
+ }
342
+ };
343
+
344
+ // src/with-audit/consent/strategy.ts
345
+ var NO_CONSENT = {
346
+ async write() {
347
+ },
348
+ async read() {
349
+ return [];
350
+ }
351
+ };
352
+
353
+ // src/with-commit/crdt/strategy.ts
354
+ var NOT_ENABLED = new Error(
355
+ 'CRDT mode requires the CRDT strategy. Import `{ withCrdt }` from "@noy-db/hub/crdt" and pass it to `createNoydb({ crdtStrategy: withCrdt() })`.'
356
+ );
357
+ var NO_CRDT = {
358
+ buildLwwMapState() {
359
+ throw NOT_ENABLED;
360
+ },
361
+ buildRgaState() {
362
+ throw NOT_ENABLED;
363
+ },
364
+ mergeCrdtStates() {
365
+ throw NOT_ENABLED;
366
+ },
367
+ resolveCrdtSnapshot() {
368
+ throw NOT_ENABLED;
369
+ }
370
+ };
371
+
372
+ // src/with-commit/history/strategy.ts
373
+ function notEnabled(op) {
374
+ return new Error(
375
+ `${op} requires the history strategy. Import \`{ withHistory }\` from "@noy-db/hub/history" and pass it to \`createNoydb({ historyStrategy: withHistory() })\`.`
376
+ );
377
+ }
378
+ var NO_HISTORY = {
379
+ async saveHistory() {
380
+ },
381
+ async getHistoryEntries() {
382
+ throw notEnabled("collection.history()");
383
+ },
384
+ async getVersionEnvelope() {
385
+ throw notEnabled("collection.getVersion()");
386
+ },
387
+ async pruneHistory() {
388
+ return 0;
389
+ },
390
+ async clearHistory() {
391
+ return 0;
392
+ },
393
+ async tombstoneHistory() {
394
+ return 0;
395
+ },
396
+ async rewrapHistory() {
397
+ },
398
+ async envelopePayloadHash() {
399
+ return "";
400
+ },
401
+ computePatch() {
402
+ return [];
403
+ },
404
+ diff() {
405
+ throw notEnabled("collection.diff()");
406
+ },
407
+ buildLedger() {
408
+ return null;
409
+ },
410
+ buildVaultInstant() {
411
+ throw notEnabled("vault.at() / vault.timeMachine()");
412
+ }
413
+ };
414
+
415
+ // src/with-lookup/indexing/strategy.ts
416
+ var NO_INDEXING = {
417
+ createState() {
418
+ return DISABLED_STATE;
419
+ }
420
+ };
421
+ var DISABLED_STATE = {
422
+ isEnabled: false,
423
+ getEagerIndexes: () => null,
424
+ getPersistedIndexes: () => null
425
+ };
426
+
427
+ // src/with-audit/periods/strategy.ts
428
+ var NOT_ENABLED2 = new Error(
429
+ 'Accounting periods require the periods strategy. Import `{ withPeriods }` from "@noy-db/hub/periods" and pass it to `createNoydb({ periodsStrategy: withPeriods() })`.'
430
+ );
431
+ var NO_PERIODS = {
432
+ async loadPeriods() {
433
+ return [];
434
+ },
435
+ async chainAnchor() {
436
+ return { priorPeriodHash: "" };
437
+ },
438
+ assertTsWritable() {
439
+ },
440
+ validatePeriodName() {
441
+ throw NOT_ENABLED2;
442
+ },
443
+ async appendPeriodLedgerEntry() {
444
+ throw NOT_ENABLED2;
445
+ }
446
+ };
447
+
448
+ // src/with-party/session/strategy.ts
449
+ function notEnabled2(op) {
450
+ return new Error(
451
+ `${op} requires the session strategy. Import \`{ withSession }\` from "@noy-db/hub/session" and pass it to \`createNoydb({ sessionStrategy: withSession() })\`.`
452
+ );
453
+ }
454
+ var NO_SESSION = {
455
+ validateSessionPolicy() {
456
+ throw notEnabled2("sessionPolicy");
457
+ },
458
+ createEnforcer() {
459
+ throw notEnabled2("session policy enforcement");
460
+ },
461
+ revokeAllSessions() {
462
+ }
463
+ };
464
+
465
+ // src/with-fork/shadow/strategy.ts
466
+ var NOT_ENABLED3 = new Error(
467
+ 'VaultFrame requires the shadow strategy. Import `{ withShadow }` from "@noy-db/hub/shadow" and pass it to `createNoydb({ shadowStrategy: withShadow() })`.'
468
+ );
469
+ var NO_SHADOW = {
470
+ buildFrame() {
471
+ throw NOT_ENABLED3;
472
+ }
473
+ };
474
+
475
+ // src/with-fork/snapshots/strategy.ts
476
+ var NOT_ENABLED4 = new Error(
477
+ 'Snapshots require the snapshot strategy. Import `{ withSnapshots }` from "@noy-db/hub/snapshots" and pass it to `createNoydb({ snapshotStrategy: withSnapshots({ store }) })`.'
478
+ );
479
+ var NO_SNAPSHOTS = {
480
+ async snapshot() {
481
+ throw NOT_ENABLED4;
482
+ },
483
+ async listSnapshots() {
484
+ throw NOT_ENABLED4;
485
+ },
486
+ async restoreSnapshot() {
487
+ throw NOT_ENABLED4;
488
+ },
489
+ async autoSnapshot() {
490
+ throw NOT_ENABLED4;
491
+ }
492
+ };
493
+
494
+ // src/with-party/team/sync-strategy.ts
495
+ function notEnabled3(op) {
496
+ return new Error(
497
+ `${op} requires the sync strategy. Import \`{ withSync }\` from "@noy-db/hub/sync" and pass it to \`createNoydb({ syncStrategy: withSync() })\`.`
498
+ );
499
+ }
500
+ var NO_SYNC = {
501
+ buildSyncEngine() {
502
+ throw notEnabled3("SyncEngine");
503
+ },
504
+ buildSyncTransaction() {
505
+ throw notEnabled3("SyncTransaction");
506
+ },
507
+ buildPresence() {
508
+ throw notEnabled3("collection.presence()");
509
+ }
510
+ };
511
+
512
+ // src/with-commit/tx/strategy.ts
513
+ var NOT_ENABLED5 = new Error(
514
+ 'Multi-record transactions require the tx strategy. Import `{ withTransactions }` from "@noy-db/hub/tx" and pass it to `createNoydb({ txStrategy: withTransactions() })`.'
515
+ );
516
+ var NO_TX = {
517
+ async runTransaction(_db, _fn, _options, _txInvariants) {
518
+ throw NOT_ENABLED5;
519
+ },
520
+ async runDryRun() {
521
+ throw NOT_ENABLED5;
522
+ }
523
+ };
524
+
525
+ // src/port/with/archive-strategy.ts
526
+ var ARCHIVE_NOT_ENABLED = "vault.archive/restore/listArchived require `archiveStrategy: withArchive({ store })` in createNoydb";
527
+ var NO_ARCHIVE = {
528
+ get store() {
529
+ throw new Error(ARCHIVE_NOT_ENABLED);
530
+ }
531
+ };
532
+
533
+ // src/port/with/strategies.ts
534
+ var STRATEGY_DEFAULTS = {
535
+ aggregate: NO_AGGREGATE,
536
+ archive: NO_ARCHIVE,
537
+ attestation: NO_ATTESTATION,
538
+ blob: NO_BLOBS,
539
+ broker: NO_BROKER,
540
+ cargo: NO_CARGO,
541
+ classified: NO_CLASSIFIED,
542
+ consent: NO_CONSENT,
543
+ crdt: NO_CRDT,
544
+ custody: NO_CUSTODY,
545
+ forget: NO_FORGET,
546
+ history: NO_HISTORY,
547
+ i18n: NO_I18N,
548
+ index: NO_INDEXING,
549
+ lazy: IMPLICIT_LAZY,
550
+ periods: NO_PERIODS,
551
+ portability: NO_PORTABILITY,
552
+ sealedRecord: NO_SEALED_RECORD,
553
+ search: NO_SEARCH,
554
+ sequence: NO_SEQUENCE,
555
+ session: NO_SESSION,
556
+ shadow: NO_SHADOW,
557
+ snapshot: NO_SNAPSHOTS,
558
+ sync: NO_SYNC,
559
+ team: NO_TEAM,
560
+ tiers: NO_TIERS,
561
+ tx: NO_TX
562
+ };
563
+ var STRATEGY_KEYS = Object.keys(STRATEGY_DEFAULTS);
564
+ function resolveStrategies(options) {
565
+ const provided = options;
566
+ const resolved = { ...STRATEGY_DEFAULTS };
567
+ for (const key of STRATEGY_KEYS) {
568
+ const value = provided[`${key}Strategy`];
569
+ if (value !== void 0) resolved[key] = value;
570
+ }
571
+ return resolved;
572
+ }
573
+
296
574
  // src/port/by/default-provider.ts
297
575
  async function createDefaultCoordinationProvider(store) {
298
576
  const { StoreCoordinationProvider } = await import("./store-coordination-provider-3AELCZ4Y.js");
@@ -964,61 +1242,6 @@ async function resolveTierSyncPrior(ctx, id, priorEnvelope) {
964
1242
  return await ctx.codec.decryptRecord(priorEnvelope, { skipValidation: true, id });
965
1243
  }
966
1244
 
967
- // src/with-commit/history/strategy.ts
968
- function notEnabled(op) {
969
- return new Error(
970
- `${op} requires the history strategy. Import \`{ withHistory }\` from "@noy-db/hub/history" and pass it to \`createNoydb({ historyStrategy: withHistory() })\`.`
971
- );
972
- }
973
- var NO_HISTORY = {
974
- async saveHistory() {
975
- },
976
- async getHistoryEntries() {
977
- throw notEnabled("collection.history()");
978
- },
979
- async getVersionEnvelope() {
980
- throw notEnabled("collection.getVersion()");
981
- },
982
- async pruneHistory() {
983
- return 0;
984
- },
985
- async clearHistory() {
986
- return 0;
987
- },
988
- async tombstoneHistory() {
989
- return 0;
990
- },
991
- async rewrapHistory() {
992
- },
993
- async envelopePayloadHash() {
994
- return "";
995
- },
996
- computePatch() {
997
- return [];
998
- },
999
- diff() {
1000
- throw notEnabled("collection.diff()");
1001
- },
1002
- buildLedger() {
1003
- return null;
1004
- },
1005
- buildVaultInstant() {
1006
- throw notEnabled("vault.at() / vault.timeMachine()");
1007
- }
1008
- };
1009
-
1010
- // src/with-lookup/indexing/strategy.ts
1011
- var NO_INDEXING = {
1012
- createState() {
1013
- return DISABLED_STATE;
1014
- }
1015
- };
1016
- var DISABLED_STATE = {
1017
- isEnabled: false,
1018
- getEagerIndexes: () => null,
1019
- getPersistedIndexes: () => null
1020
- };
1021
-
1022
1245
  // src/with-lookup/search/serialize.ts
1023
1246
  function serializeIndex(idx) {
1024
1247
  return JSON.stringify(idx.toSnapshot());
@@ -1621,54 +1844,6 @@ function buildDescription(input) {
1621
1844
  };
1622
1845
  }
1623
1846
 
1624
- // src/via/blob/binding.ts
1625
- async function eraseBlobs(ctx, cfg) {
1626
- if (cfg.purgeBlobsForRecord) return cfg.purgeBlobsForRecord(ctx.id);
1627
- return { shredded: 0, residue: [] };
1628
- }
1629
- function buildBlobDescribeFragment(fields) {
1630
- return {
1631
- blobFields: Object.fromEntries(
1632
- Object.entries(fields).map(([field, p]) => [field, {
1633
- ...p.retainDays !== void 0 ? { retainDays: p.retainDays } : {},
1634
- ...p.evictWhen !== void 0 ? { evictWhen: true } : {},
1635
- ...p.legalHold !== void 0 ? { legalHold: true } : {},
1636
- ...p.retainUntil !== void 0 ? { retainUntil: true } : {},
1637
- ...p.external === true ? { external: true } : {},
1638
- ...p.public === true ? { public: true } : {},
1639
- ...p.backlink !== void 0 ? { backlink: p.backlink } : {}
1640
- }])
1641
- )
1642
- };
1643
- }
1644
- function blobBinding(cfg) {
1645
- return {
1646
- brand: "blob",
1647
- // encryptedAtRest: 'envelope' — blob chunks are AEAD-encrypted envelopes
1648
- // in `_blob_chunks` (service-side), not sealed record fields; queryable:
1649
- // 'none' — nothing indexes blob content; exportable — vault.exportBlobs()
1650
- // is a first-class door; forgettable — shredAllForRecord participates in
1651
- // vault.forget().
1652
- posture: { encryptedAtRest: "envelope", queryable: "none", exportable: true, forgettable: true },
1653
- covers: (field) => field in cfg.fields,
1654
- erase: (ctx) => eraseBlobs(ctx, cfg),
1655
- describeFragment: () => buildBlobDescribeFragment(cfg.fields)
1656
- };
1657
- }
1658
- function linkBlobVia() {
1659
- installViaBinder("blob", (c) => blobBinding(c));
1660
- }
1661
-
1662
- // src/port/with/blob-strategy.ts
1663
- linkBlobVia();
1664
- var NO_BLOBS = {
1665
- openSlot() {
1666
- throw new Error(
1667
- 'Blob storage is not enabled on this Noydb instance. Import `{ withBlobs }` from "@noy-db/hub/blobs" and pass `withBlobs()` to `createNoydb({ blobStrategy: withBlobs() })`.'
1668
- );
1669
- }
1670
- };
1671
-
1672
1847
  // src/with-formula/derivations/stale.ts
1673
1848
  var _staleByRegistry = /* @__PURE__ */ new WeakMap();
1674
1849
  var keyFor = (source, sourceId) => `${source}/${sourceId}`;
@@ -1742,43 +1917,6 @@ async function resolveStaleOnRead(accessor, outputCollection, id) {
1742
1917
  }
1743
1918
  }
1744
1919
 
1745
- // src/with-commit/crdt/strategy.ts
1746
- var NOT_ENABLED = new Error(
1747
- 'CRDT mode requires the CRDT strategy. Import `{ withCrdt }` from "@noy-db/hub/crdt" and pass it to `createNoydb({ crdtStrategy: withCrdt() })`.'
1748
- );
1749
- var NO_CRDT = {
1750
- buildLwwMapState() {
1751
- throw NOT_ENABLED;
1752
- },
1753
- buildRgaState() {
1754
- throw NOT_ENABLED;
1755
- },
1756
- mergeCrdtStates() {
1757
- throw NOT_ENABLED;
1758
- },
1759
- resolveCrdtSnapshot() {
1760
- throw NOT_ENABLED;
1761
- }
1762
- };
1763
-
1764
- // src/with-party/team/sync-strategy.ts
1765
- function notEnabled2(op) {
1766
- return new Error(
1767
- `${op} requires the sync strategy. Import \`{ withSync }\` from "@noy-db/hub/sync" and pass it to \`createNoydb({ syncStrategy: withSync() })\`.`
1768
- );
1769
- }
1770
- var NO_SYNC = {
1771
- buildSyncEngine() {
1772
- throw notEnabled2("SyncEngine");
1773
- },
1774
- buildSyncTransaction() {
1775
- throw notEnabled2("SyncTransaction");
1776
- },
1777
- buildPresence() {
1778
- throw notEnabled2("collection.presence()");
1779
- }
1780
- };
1781
-
1782
1920
  // src/kernel/via/compose.ts
1783
1921
  function via(...descriptors) {
1784
1922
  if (descriptors.length === 0) {
@@ -1942,7 +2080,7 @@ function compileViaBindings(opts, classifiedGuardCtx, eraseCfgOut) {
1942
2080
  ...i18nFields !== void 0 ? { i18nFields } : {},
1943
2081
  ...dictKeyFields !== void 0 ? { dictKeyFields } : {},
1944
2082
  ...i18nDensifyFields !== void 0 ? { i18nDensifyFields } : {},
1945
- strategy: opts.i18nStrategy ?? NO_I18N,
2083
+ strategy: opts.strategies.i18n,
1946
2084
  ...opts.defaultLocale !== void 0 ? { defaultLocale: opts.defaultLocale } : {},
1947
2085
  ...opts.autoTranslateHook !== void 0 ? { autoTranslateHook: opts.autoTranslateHook } : {},
1948
2086
  ...opts.dictLabelResolver !== void 0 ? { dictLabelResolver: opts.dictLabelResolver } : {},
@@ -2056,6 +2194,11 @@ function resolveCollectionConfig(opts) {
2056
2194
  `Collection "${opts.name}": embeddings are not supported on CRDT collections (L2). Use a non-CRDT collection for semantic search.`
2057
2195
  );
2058
2196
  }
2197
+ if (opts.sensitive && opts.sensitive.length > 0 && opts.crdt) {
2198
+ throw new Error(
2199
+ `Collection "${opts.name}": sensitive (structural group-encryption) is not supported on CRDT collections \u2014 the CRDT write path bypasses per-field sealing, so the fields would be stored in the ordinary encrypted body with no \`_sealed\` slot and no per-field key. Use a non-CRDT collection for sealed fields.`
2200
+ );
2201
+ }
2059
2202
  if (opts.tiers && opts.tiers.length > 0 && opts.blobFields !== void 0 && Object.keys(opts.blobFields).length > 0 && opts.perRecordKeys !== true) {
2060
2203
  throw new UnsupportedTierCompositionError(
2061
2204
  "blobs",
@@ -2172,14 +2315,9 @@ function resolveCollectionConfig(opts) {
2172
2315
  writeHooks: opts.writeHooks,
2173
2316
  subsystemBus: opts.subsystemBus,
2174
2317
  activeTxId: opts.activeTxId,
2175
- blobStrategy: opts.blobStrategy ?? NO_BLOBS,
2318
+ strategies: opts.strategies,
2176
2319
  objectStore: opts.objectStore,
2177
2320
  blobFields: opts.blobFields,
2178
- aggregateStrategy: opts.aggregateStrategy ?? NO_AGGREGATE,
2179
- crdtStrategy: opts.crdtStrategy ?? NO_CRDT,
2180
- historyStrategy: opts.historyStrategy ?? NO_HISTORY,
2181
- i18nStrategy: opts.i18nStrategy ?? NO_I18N,
2182
- syncStrategy: opts.syncStrategy ?? NO_SYNC,
2183
2321
  reconcileOnOpen: opts.reconcileOnOpen ?? "off",
2184
2322
  getDEK: opts.getDEK,
2185
2323
  onDirty: opts.onDirty,
@@ -2208,7 +2346,6 @@ function resolveCollectionConfig(opts) {
2208
2346
  moneyFields: effectiveViaFields.moneyFields,
2209
2347
  classified: resolvedClassified,
2210
2348
  classifiedGuardCtx,
2211
- classifiedStrategy: opts.classifiedStrategy ?? NO_CLASSIFIED,
2212
2349
  computed: mergedComputed,
2213
2350
  computedEdges,
2214
2351
  viaDepsEdges,
@@ -2230,8 +2367,6 @@ function resolveCollectionConfig(opts) {
2230
2367
  materializedViewSource: opts.materializedViewSource,
2231
2368
  graphDispatch: opts.graphDispatch,
2232
2369
  tiers: opts.tiers && opts.tiers.length > 0 ? new Set(opts.tiers) : null,
2233
- tiersStrategy: opts.tiersStrategy ?? NO_TIERS,
2234
- searchStrategy: opts.searchStrategy ?? NO_SEARCH,
2235
2370
  tierMode: opts.tierMode ?? "invisibility",
2236
2371
  blobTierPolicy: opts.blobTierPolicy ?? "isolate",
2237
2372
  onCrossTierAccess: opts.onCrossTierAccess,
@@ -2293,25 +2428,20 @@ var Collection = class {
2293
2428
  /** True when the caller explicitly provided a `historyConfig` option (vs. inheriting the vault default). */
2294
2429
  historyConfigExplicit;
2295
2430
  /**
2296
- * tree-shake seam the strategy that backs `collection.blob(id)`.
2297
- * Defaults to `NO_BLOBS`, a ~10-line stub that throws with an actionable
2298
- * message. Consumers opt into real blob storage by importing
2299
- * `{ blobs }` from `@noy-db/hub/blobs` and passing the returned
2300
- * strategy to `createNoydb({ blobStrategy: blobs() })`. With the
2301
- * default stub, none of the BlobSet / chunk / MIME-magic machinery
2302
- * reaches the bundle.
2431
+ * Every opt-in service, resolved once by `createNoydb` (#838) and shared by
2432
+ * reference with the owning `Vault` and `Noydb`.
2433
+ *
2434
+ * Each entry is a tree-shake seam. An un-opted-in service resolves to its
2435
+ * `NO_*` stub a tiny module that throws with an actionable message —
2436
+ * so, for example, none of the BlobSet / chunk / MIME-magic machinery
2437
+ * reaches the bundle unless the consumer imports `@noy-db/hub/blobs` and
2438
+ * passes `blobStrategy: blobs()` to `createNoydb`.
2303
2439
  */
2304
- blobStrategy;
2440
+ strategies;
2305
2441
  objectStore;
2306
2442
  blobFields;
2307
2443
  blobTierPolicy;
2308
2444
  // #724 T2/T3
2309
- aggregateStrategy;
2310
- crdtStrategy;
2311
- tiersStrategy;
2312
- searchStrategy;
2313
- historyStrategy;
2314
- syncStrategy;
2315
2445
  // In-memory cache of decrypted records (eager mode only). Lazy mode
2316
2446
  // uses `lru` instead. Both fields exist so a single Collection instance
2317
2447
  // doesn't need a runtime branch on every cache access.
@@ -2481,12 +2611,6 @@ var Collection = class {
2481
2611
  /** C-A/R10 memoization: marker declared-set lookup (undefined=unresolved) + classified-handle persist-once flag. */
2482
2612
  _markerDigestOnlyCache = void 0;
2483
2613
  _markerPersisted = false;
2484
- /**
2485
- * Tree-shake seam for `reveal()` — defaults to `NO_CLASSIFIED`, which
2486
- * throws `ClassifiedNotEnabledError`. Set via the `classifiedStrategy`
2487
- * `createNoydb()` option (opt in with `withClassified()`).
2488
- */
2489
- classifiedStrategy;
2490
2614
  /** Async callback provided by the Vault to open a dynamic dictionary handle (for label-map pre-computation in the search index). Only used in `resolveDictLabelMaps()`; static dicts bypass this entirely. Mutable, assign-once — see {@link _reconcileReadState} (#671 item 1). */
2491
2615
  getDictionary;
2492
2616
  /**
@@ -2635,16 +2759,10 @@ var Collection = class {
2635
2759
  this.writeHooks = cfg.writeHooks;
2636
2760
  this.subsystemBus = cfg.subsystemBus;
2637
2761
  this.activeTxId = cfg.activeTxId;
2638
- this.blobStrategy = cfg.blobStrategy;
2762
+ this.strategies = cfg.strategies;
2639
2763
  this.objectStore = cfg.objectStore;
2640
2764
  this.blobFields = cfg.blobFields;
2641
2765
  this.blobTierPolicy = cfg.blobTierPolicy ?? "isolate";
2642
- this.aggregateStrategy = cfg.aggregateStrategy;
2643
- this.crdtStrategy = cfg.crdtStrategy;
2644
- this.tiersStrategy = cfg.tiersStrategy;
2645
- this.searchStrategy = cfg.searchStrategy;
2646
- this.historyStrategy = cfg.historyStrategy;
2647
- this.syncStrategy = cfg.syncStrategy;
2648
2766
  this.reconcileOnOpen = cfg.reconcileOnOpen;
2649
2767
  this.getDEK = cfg.getDEK;
2650
2768
  this.onDirty = cfg.onDirty;
@@ -2671,7 +2789,6 @@ var Collection = class {
2671
2789
  this.classified = cfg.classified;
2672
2790
  this.classifiedGuardCtx = cfg.classifiedGuardCtx;
2673
2791
  this.vdigFields = cfg.vdigFields;
2674
- this.classifiedStrategy = cfg.classifiedStrategy;
2675
2792
  this.computed = cfg.computed;
2676
2793
  this.getDictionary = cfg.getDictionary;
2677
2794
  this.defaultLocale = cfg.defaultLocale;
@@ -2719,7 +2836,7 @@ var Collection = class {
2719
2836
  deterministicFields: this.deterministicFields,
2720
2837
  vdigFields: this.vdigFields,
2721
2838
  crdtMode: this.crdtMode,
2722
- crdtStrategy: this.crdtStrategy,
2839
+ crdtStrategy: this.strategies.crdt,
2723
2840
  schema: this.schema,
2724
2841
  getDEK: (collection) => this.getDEK(collection ?? this.name),
2725
2842
  cekCache: this.cekCache,
@@ -2737,7 +2854,7 @@ var Collection = class {
2737
2854
  const remoteJson = await this.codec.decryptJsonString(remote, id);
2738
2855
  if (localJson === null) return local;
2739
2856
  if (remoteJson === null) return remote;
2740
- const merged = this.crdtStrategy.mergeCrdtStates(JSON.parse(localJson), JSON.parse(remoteJson));
2857
+ const merged = this.strategies.crdt.mergeCrdtStates(JSON.parse(localJson), JSON.parse(remoteJson));
2741
2858
  const mergedVersion = Math.max(local._v, remote._v) + 1;
2742
2859
  const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
2743
2860
  return this.codec.encryptJsonString(JSON.stringify(merged), mergedVersion, cek);
@@ -2795,12 +2912,12 @@ var Collection = class {
2795
2912
  }
2796
2913
  this.lazy = opts.prefetch === false;
2797
2914
  if (this.lazy) {
2798
- this.lru = (opts.lazyStrategy ?? IMPLICIT_LAZY).createCache(this.name, opts.cache);
2915
+ this.lru = opts.strategies.lazy.createCache(this.name, opts.cache);
2799
2916
  this.hydrated = true;
2800
2917
  } else {
2801
2918
  this.lru = null;
2802
2919
  }
2803
- const strategy = opts.indexStrategy ?? NO_INDEXING;
2920
+ const strategy = opts.strategies.index;
2804
2921
  this.indexState = strategy.createState({
2805
2922
  defs: opts.indexes ?? [],
2806
2923
  lazy: this.lazy
@@ -2955,7 +3072,7 @@ var Collection = class {
2955
3072
  if (spec.storage === "digest-only") {
2956
3073
  throw new ClassifiedRevealError(this.name, field, `storage:'digest-only' \u2014 verify-only; nothing recoverable to reveal`);
2957
3074
  }
2958
- return this.classifiedStrategy.reveal({
3075
+ return this.strategies.classified.reveal({
2959
3076
  collection: this.name,
2960
3077
  spec,
2961
3078
  encrypted: this.storeCiphertext,
@@ -2975,7 +3092,7 @@ var Collection = class {
2975
3092
  throw new ClassifiedVerifyError(this.name, field, `storage:'never' \u2014 nothing is stored to verify against`);
2976
3093
  }
2977
3094
  const ctx = this._classifiedVerifyCtx(spec);
2978
- return spec.storage === "digest-only" ? this.classifiedStrategy.verify(ctx, id, field, candidate) : this.classifiedStrategy.verifyText(ctx, id, field, candidate);
3095
+ return spec.storage === "digest-only" ? this.strategies.classified.verify(ctx, id, field, candidate) : this.strategies.classified.verifyText(ctx, id, field, candidate);
2979
3096
  }
2980
3097
  /** k-of-n challenge over the collection's secretAnswer members. Requires withClassified(). */
2981
3098
  async verifyGroup(id, answers, opts) {
@@ -2984,7 +3101,7 @@ var Collection = class {
2984
3101
  throw new ClassifiedVerifyError(this.name, "*", "no groupable digest-only (secretAnswer) fields declared");
2985
3102
  }
2986
3103
  const ctx = { ...this._classifiedVerifyCtx(members[0].spec), groupMembers: members };
2987
- return this.classifiedStrategy.matchGroup(ctx, id, answers, opts);
3104
+ return this.strategies.classified.matchGroup(ctx, id, answers, opts);
2988
3105
  }
2989
3106
  _classifiedVerifyCtx(spec) {
2990
3107
  return {
@@ -3039,7 +3156,7 @@ var Collection = class {
3039
3156
  if (typeof candidate !== "string") {
3040
3157
  throw new ClassifiedVerifyError(this.name, "*", "candidate must be a string");
3041
3158
  }
3042
- const target = await this.classifiedStrategy.computeTarget(this._classifiedVerifyCtx(spec), field, candidate);
3159
+ const target = await this.strategies.classified.computeTarget(this._classifiedVerifyCtx(spec), field, candidate);
3043
3160
  const ids = await this.adapter.list(this.vault, this.name);
3044
3161
  const hits = [];
3045
3162
  const hitMap = /* @__PURE__ */ new Map();
@@ -3126,7 +3243,7 @@ var Collection = class {
3126
3243
  id,
3127
3244
  version: scrubbed._v,
3128
3245
  actor: this.keyring.userId,
3129
- payloadHash: await this.historyStrategy.envelopePayloadHash(scrubbed),
3246
+ payloadHash: await this.strategies.history.envelopePayloadHash(scrubbed),
3130
3247
  reason: "classified:scrub-equatable-tags"
3131
3248
  });
3132
3249
  }
@@ -3258,7 +3375,7 @@ var Collection = class {
3258
3375
  }
3259
3376
  }
3260
3377
  if (this.materializedViewSource !== void 0) {
3261
- const { resolveStaleMVOnRead } = await import("./stale-NB5BGRVU.js");
3378
+ const { resolveStaleMVOnRead } = await import("./stale-4UNPHWOU.js");
3262
3379
  await resolveStaleMVOnRead(this.materializedViewSource, this.name, this.#dispatchCtx({ collection: this.name, id: "resolve-on-read" }));
3263
3380
  }
3264
3381
  let record;
@@ -3352,7 +3469,7 @@ var Collection = class {
3352
3469
  if (this.syncAdapter !== void 0) presenceOpts.syncAdapter = this.syncAdapter;
3353
3470
  if (opts?.staleMs !== void 0) presenceOpts.staleMs = opts.staleMs;
3354
3471
  if (opts?.pollIntervalMs !== void 0) presenceOpts.pollIntervalMs = opts.pollIntervalMs;
3355
- return this.syncStrategy.buildPresence(presenceOpts);
3472
+ return this.strategies.sync.buildPresence(presenceOpts);
3356
3473
  }
3357
3474
  /**
3358
3475
  * Create or update a record. Runs inside the hub's write-queue tracker
@@ -3572,7 +3689,7 @@ var Collection = class {
3572
3689
  }
3573
3690
  }
3574
3691
  }
3575
- crdtState = this.crdtStrategy.buildLwwMapState(record, existingState, now);
3692
+ crdtState = this.strategies.crdt.buildLwwMapState(record, existingState, now);
3576
3693
  } else if (this.crdtMode === "rga") {
3577
3694
  let existingState;
3578
3695
  if (existingEnvelope) {
@@ -3584,7 +3701,7 @@ var Collection = class {
3584
3701
  }
3585
3702
  }
3586
3703
  }
3587
- crdtState = this.crdtStrategy.buildRgaState(Array.isArray(record) ? record : [record], existingState, generateULID);
3704
+ crdtState = this.strategies.crdt.buildRgaState(Array.isArray(record) ? record : [record], existingState, generateULID);
3588
3705
  } else {
3589
3706
  crdtState = { _crdt: "yjs", update: record };
3590
3707
  }
@@ -3592,14 +3709,14 @@ var Collection = class {
3592
3709
  const cek2 = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
3593
3710
  const envelope2 = await this.codec.encryptJsonString(JSON.stringify(crdtState), version2, cek2, options?.source, options?.sourceTs);
3594
3711
  await this.adapter.put(this.vault, this.name, id, envelope2);
3595
- const resolvedRecord = this.crdtStrategy.resolveCrdtSnapshot(crdtState);
3712
+ const resolvedRecord = this.strategies.crdt.resolveCrdtSnapshot(crdtState);
3596
3713
  const existingResolvedRecord = existingEnvelope ? await this.codec.decryptRecord(existingEnvelope, { skipValidation: true }) : null;
3597
3714
  const existingResolved = existingResolvedRecord !== null ? { record: existingResolvedRecord, version: existingVersion } : void 0;
3598
3715
  if (existingResolved && this.historyConfig.enabled !== false) {
3599
- await this.historyStrategy.saveHistory(this.adapter, this.vault, this.name, id, await this.codec.encryptRecord(existingResolved.record, existingResolved.version, cek2, void 0, void 0, this.vdigFields !== null ? { id, prev: existingEnvelope } : void 0, id));
3716
+ await this.strategies.history.saveHistory(this.adapter, this.vault, this.name, id, await this.codec.encryptRecord(existingResolved.record, existingResolved.version, cek2, void 0, void 0, this.vdigFields !== null ? { id, prev: existingEnvelope } : void 0, id));
3600
3717
  this.emitter.emit("history:save", { vault: this.vault, collection: this.name, id, version: existingResolved.version });
3601
3718
  if (this.historyConfig.maxVersions) {
3602
- await this.historyStrategy.pruneHistory(this.adapter, this.vault, this.name, id, { keepVersions: this.historyConfig.maxVersions });
3719
+ await this.strategies.history.pruneHistory(this.adapter, this.vault, this.name, id, { keepVersions: this.historyConfig.maxVersions });
3603
3720
  }
3604
3721
  }
3605
3722
  if (this.ledger) {
@@ -3609,9 +3726,9 @@ var Collection = class {
3609
3726
  id,
3610
3727
  version: version2,
3611
3728
  actor: this.keyring.userId,
3612
- payloadHash: await this.historyStrategy.envelopePayloadHash(envelope2)
3729
+ payloadHash: await this.strategies.history.envelopePayloadHash(envelope2)
3613
3730
  };
3614
- if (existingResolved) appendInput.delta = this.historyStrategy.computePatch(resolvedRecord, existingResolved.record);
3731
+ if (existingResolved) appendInput.delta = this.strategies.history.computePatch(resolvedRecord, existingResolved.record);
3615
3732
  if (options?.reason !== void 0) appendInput.reason = options.reason;
3616
3733
  await this.ledger.append(appendInput);
3617
3734
  }
@@ -3658,21 +3775,21 @@ var Collection = class {
3658
3775
  const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
3659
3776
  const vdigCtx = this.vdigFields !== null ? { id, prev: await this.adapter.get(this.vault, this.name, id) } : void 0;
3660
3777
  if (existing && this.historyConfig.enabled !== false) {
3661
- await this.historyStrategy.saveHistory(this.adapter, this.vault, this.name, id, await this.codec.encryptRecord(existing.record, existing.version, cek, void 0, void 0, vdigCtx, id));
3778
+ await this.strategies.history.saveHistory(this.adapter, this.vault, this.name, id, await this.codec.encryptRecord(existing.record, existing.version, cek, void 0, void 0, vdigCtx, id));
3662
3779
  this.emitter.emit("history:save", {
3663
3780
  vault: this.vault,
3664
3781
  collection: this.name,
3665
3782
  id,
3666
3783
  version: existing.version
3667
3784
  });
3668
- if (this.historyConfig.maxVersions) await this.historyStrategy.pruneHistory(this.adapter, this.vault, this.name, id, { keepVersions: this.historyConfig.maxVersions });
3785
+ if (this.historyConfig.maxVersions) await this.strategies.history.pruneHistory(this.adapter, this.vault, this.name, id, { keepVersions: this.historyConfig.maxVersions });
3669
3786
  }
3670
3787
  const envelope = await this.codec.encryptRecord(record, version, cek, options?.source, options?.sourceTs, vdigCtx, id);
3671
3788
  await this.adapter.put(this.vault, this.name, id, envelope);
3672
3789
  this.markerIds.delete(id);
3673
3790
  await this._ensureClassifiedMarker();
3674
3791
  if (this.embeddings) {
3675
- await this.searchStrategy.embedOnWrite(this.searchContext(), id, record, version);
3792
+ await this.strategies.search.embedOnWrite(this.searchContext(), id, record, version);
3676
3793
  }
3677
3794
  if (this.ledger) {
3678
3795
  const appendInput = {
@@ -3681,10 +3798,10 @@ var Collection = class {
3681
3798
  id,
3682
3799
  version,
3683
3800
  actor: this.keyring.userId,
3684
- payloadHash: await this.historyStrategy.envelopePayloadHash(envelope)
3801
+ payloadHash: await this.strategies.history.envelopePayloadHash(envelope)
3685
3802
  };
3686
3803
  if (existing) {
3687
- appendInput.delta = this.historyStrategy.computePatch(record, existing.record);
3804
+ appendInput.delta = this.strategies.history.computePatch(record, existing.record);
3688
3805
  }
3689
3806
  if (options?.reason !== void 0) appendInput.reason = options.reason;
3690
3807
  await this.ledger.append(appendInput);
@@ -3727,7 +3844,7 @@ var Collection = class {
3727
3844
  if (wave?.seen(`mv\0${reg.spec.name}`)) continue;
3728
3845
  if (executor === null) {
3729
3846
  ;
3730
- ({ MaterializedViewExecutor: executor } = await import("./executor-UF7LTX6O.js"));
3847
+ ({ MaterializedViewExecutor: executor } = await import("./executor-YORMIBWV.js"));
3731
3848
  }
3732
3849
  await executor.refresh(reg, {
3733
3850
  getCollection: (name) => this.materializedViewSource.getCollection(name),
@@ -3737,7 +3854,7 @@ var Collection = class {
3737
3854
  });
3738
3855
  } else if (mode === "lazy") {
3739
3856
  if (staleHelpers === null) {
3740
- staleHelpers = await import("./stale-NB5BGRVU.js");
3857
+ staleHelpers = await import("./stale-4UNPHWOU.js");
3741
3858
  }
3742
3859
  staleHelpers.markMVStale(registry, reg.spec.name);
3743
3860
  }
@@ -4203,10 +4320,10 @@ var Collection = class {
4203
4320
  if (existing && this.historyConfig.enabled !== false) {
4204
4321
  const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
4205
4322
  const prevForVdig = this.vdigFields !== null ? await this.adapter.get(this.vault, this.name, id) : null;
4206
- await this.historyStrategy.saveHistory(this.adapter, this.vault, this.name, id, await this.codec.encryptRecord(existing.record, existing.version, cek, void 0, void 0, this.vdigFields !== null ? { id, prev: prevForVdig } : void 0, id));
4323
+ await this.strategies.history.saveHistory(this.adapter, this.vault, this.name, id, await this.codec.encryptRecord(existing.record, existing.version, cek, void 0, void 0, this.vdigFields !== null ? { id, prev: prevForVdig } : void 0, id));
4207
4324
  }
4208
4325
  const previousEnvelope = await this.adapter.get(this.vault, this.name, id);
4209
- const previousPayloadHash = await this.historyStrategy.envelopePayloadHash(previousEnvelope);
4326
+ const previousPayloadHash = await this.strategies.history.envelopePayloadHash(previousEnvelope);
4210
4327
  let markerVersion;
4211
4328
  if (this.onDirty) {
4212
4329
  const live = previousEnvelope;
@@ -4356,7 +4473,7 @@ var Collection = class {
4356
4473
  if (mode === "eager") {
4357
4474
  if (executor === null) {
4358
4475
  ;
4359
- ({ MaterializedViewExecutor: executor } = await import("./executor-UF7LTX6O.js"));
4476
+ ({ MaterializedViewExecutor: executor } = await import("./executor-YORMIBWV.js"));
4360
4477
  }
4361
4478
  const rr = await executor.refresh(reg, {
4362
4479
  getCollection: (name) => this.materializedViewSource.getCollection(name),
@@ -4369,7 +4486,7 @@ var Collection = class {
4369
4486
  residueDeclined.push(...rr.residueDeclined);
4370
4487
  } else {
4371
4488
  if (staleHelpers === null) {
4372
- staleHelpers = await import("./stale-NB5BGRVU.js");
4489
+ staleHelpers = await import("./stale-4UNPHWOU.js");
4373
4490
  }
4374
4491
  const inv = await staleHelpers.invalidateMVAtRest(this.materializedViewSource, reg, mode);
4375
4492
  deleted += inv.deleted;
@@ -4396,7 +4513,7 @@ var Collection = class {
4396
4513
  );
4397
4514
  }
4398
4515
  if (this.materializedViewSource !== void 0) {
4399
- const { resolveStaleMVOnRead } = await import("./stale-NB5BGRVU.js");
4516
+ const { resolveStaleMVOnRead } = await import("./stale-4UNPHWOU.js");
4400
4517
  await resolveStaleMVOnRead(this.materializedViewSource, this.name, this.#dispatchCtx({ collection: this.name, id: "resolve-on-read" }));
4401
4518
  }
4402
4519
  await this.ensureHydrated();
@@ -4417,28 +4534,28 @@ var Collection = class {
4417
4534
  * see `src/search/tokenize.ts` for the Thai/CJK caveat.
4418
4535
  */
4419
4536
  search(field, query, opts = {}) {
4420
- return this.searchStrategy.search(this.searchContext(), field, query, opts);
4537
+ return this.strategies.search.search(this.searchContext(), field, query, opts);
4421
4538
  }
4422
4539
  /** Force-persist the lexical index now — gated behind `searchStrategy: withSearch()`. */
4423
4540
  flushIndex() {
4424
- return this.searchStrategy.flushIndex(this.searchContext());
4541
+ return this.strategies.search.flushIndex(this.searchContext());
4425
4542
  }
4426
4543
  /** Pre-build the lexical index — gated behind `searchStrategy: withSearch()`. */
4427
4544
  warmIndex() {
4428
- return this.searchStrategy.warmIndex(this.searchContext());
4545
+ return this.strategies.search.warmIndex(this.searchContext());
4429
4546
  }
4430
4547
  /** Retrieval (lexical | semantic | hybrid) — gated behind `searchStrategy: withSearch()`. */
4431
4548
  retrieve(query, opts = {}) {
4432
- return this.searchStrategy.retrieve(this.searchContext(), query, opts);
4549
+ return this.strategies.search.retrieve(this.searchContext(), query, opts);
4433
4550
  }
4434
4551
  /** Raw-vector kNN — gated behind `searchStrategy: withSearch()`. */
4435
4552
  similarTo(vector, opts = {}) {
4436
- return this.searchStrategy.similarTo(this.searchContext(), vector, opts);
4553
+ return this.strategies.search.similarTo(this.searchContext(), vector, opts);
4437
4554
  }
4438
4555
  /** Opt-in bulk `_vec` re-derive (#788) — a plain collection has nothing to rebuild; gated behind `searchStrategy: withSearch()` otherwise. */
4439
4556
  rebuildEmbeddings() {
4440
4557
  if (!this.embeddings) return Promise.resolve({ rebuilt: 0, skipped: 0 });
4441
- return this.searchStrategy.rebuildEmbeddings(this.searchContext());
4558
+ return this.strategies.search.rebuildEmbeddings(this.searchContext());
4442
4559
  }
4443
4560
  /**
4444
4561
  * Bind the {@link SearchContext} the search/retrieval surface needs. The
@@ -4648,7 +4765,7 @@ var Collection = class {
4648
4765
  ...this.defaultLocale !== void 0 ? { defaultLocale: this.defaultLocale } : {},
4649
4766
  ...resolver.resolveDictSource ? { resolveDictSource: (field) => resolver.resolveDictSource(leftCollection, field) } : {}
4650
4767
  } : void 0;
4651
- return new Query(source, void 0, joinContext, this.aggregateStrategy);
4768
+ return new Query(source, void 0, joinContext, this.strategies.aggregate);
4652
4769
  }
4653
4770
  /**
4654
4771
  * Subscribe to every put/delete on this collection. Returns an
@@ -4762,7 +4879,7 @@ var Collection = class {
4762
4879
  // ─── History Methods ────────────────────────────────────────────
4763
4880
  /** Get version history for a record, newest first. */
4764
4881
  async history(id, options) {
4765
- const envelopes = await this.historyStrategy.getHistoryEntries(
4882
+ const envelopes = await this.strategies.history.getHistoryEntries(
4766
4883
  this.adapter,
4767
4884
  this.vault,
4768
4885
  this.name,
@@ -4794,7 +4911,7 @@ var Collection = class {
4794
4911
  * and re-put the records through the normal `put()` path.
4795
4912
  */
4796
4913
  async getVersion(id, version) {
4797
- const envelope = await this.historyStrategy.getVersionEnvelope(
4914
+ const envelope = await this.strategies.history.getVersionEnvelope(
4798
4915
  this.adapter,
4799
4916
  this.vault,
4800
4917
  this.name,
@@ -4819,7 +4936,7 @@ var Collection = class {
4819
4936
  */
4820
4937
  async diff(id, versionA, versionB) {
4821
4938
  const recordA = versionA === 0 ? null : await this.resolveVersion(id, versionA);
4822
- return this.historyStrategy.diff(recordA, versionB === void 0 || versionB === 0 ? versionB === 0 ? null : await this.resolveCurrentOrVersion(id) : await this.resolveVersion(id, versionB));
4939
+ return this.strategies.history.diff(recordA, versionB === void 0 || versionB === 0 ? versionB === 0 ? null : await this.resolveCurrentOrVersion(id) : await this.resolveVersion(id, versionB));
4823
4940
  }
4824
4941
  /** Resolve a version: try history first, then check if it's the current version. */
4825
4942
  async resolveVersion(id, version) {
@@ -4836,7 +4953,7 @@ var Collection = class {
4836
4953
  }
4837
4954
  /** Prune history entries for a record (or all records if id is undefined). */
4838
4955
  async pruneRecordHistory(id, options) {
4839
- const pruned = await this.historyStrategy.pruneHistory(
4956
+ const pruned = await this.strategies.history.pruneHistory(
4840
4957
  this.adapter,
4841
4958
  this.vault,
4842
4959
  this.name,
@@ -4855,7 +4972,7 @@ var Collection = class {
4855
4972
  }
4856
4973
  /** Clear all history for this collection (or a specific record). */
4857
4974
  async clearHistory(id) {
4858
- return this.historyStrategy.clearHistory(this.adapter, this.vault, this.name, id);
4975
+ return this.strategies.history.clearHistory(this.adapter, this.vault, this.name, id);
4859
4976
  }
4860
4977
  // ─── Core Methods ─────────────────────────────────────────────
4861
4978
  /**
@@ -5277,7 +5394,7 @@ var Collection = class {
5277
5394
  * vault-shared `_blob` DEK (enabling cross-collection deduplication).
5278
5395
  */
5279
5396
  blob(id) {
5280
- return this.blobStrategy.openSlot({
5397
+ return this.strategies.blob.openSlot({
5281
5398
  store: this.adapter,
5282
5399
  vault: this.vault,
5283
5400
  collection: this.name,
@@ -5598,23 +5715,23 @@ var Collection = class {
5598
5715
  // ─── Hierarchical Access ──────────────────────────
5599
5716
  /** tier-aware put — gated behind `tiersStrategy: withTiers()`. `searchResidue: true` = stuck search compensation (#774, mirroring #764's elevate/demote posture); the write itself always completes. */
5600
5717
  putAtTier(id, record, tier, opts) {
5601
- return this.tiersStrategy.putAtTier(this.tiersContext(), id, record, tier, opts);
5718
+ return this.strategies.tiers.putAtTier(this.tiersContext(), id, record, tier, opts);
5602
5719
  }
5603
5720
  /** tier-aware get — gated behind `tiersStrategy: withTiers()`. */
5604
5721
  getAtTier(id) {
5605
- return this.tiersStrategy.getAtTier(this.tiersContext(), id);
5722
+ return this.strategies.tiers.getAtTier(this.tiersContext(), id);
5606
5723
  }
5607
5724
  /** list ids grouped by the caller's readability — gated behind `withTiers()`. */
5608
5725
  listAtTier() {
5609
- return this.tiersStrategy.listAtTier(this.tiersContext());
5726
+ return this.strategies.tiers.listAtTier(this.tiersContext());
5610
5727
  }
5611
5728
  /** elevate a record to a higher tier — gated behind `withTiers()`. `searchResidue: true` = stuck search compensation (#764); the move itself always completes. */
5612
5729
  elevate(id, toTier) {
5613
- return this.tiersStrategy.elevate(this.tiersContext(), id, toTier);
5730
+ return this.strategies.tiers.elevate(this.tiersContext(), id, toTier);
5614
5731
  }
5615
5732
  /** demote a record to a lower tier — gated behind `withTiers()`. Same `searchResidue` posture as {@link elevate} (#764). */
5616
5733
  demote(id, toTier) {
5617
- return this.tiersStrategy.demote(this.tiersContext(), id, toTier);
5734
+ return this.strategies.tiers.demote(this.tiersContext(), id, toTier);
5618
5735
  }
5619
5736
  /**
5620
5737
  * Emit a cross-tier access event. The subscriber sink stays collection-resident (it captures `onCrossTierAccess`); the tiers module reaches it via the {@link TiersContext.emitCrossTierEvent} callback.
@@ -5675,16 +5792,16 @@ var Collection = class {
5675
5792
  },
5676
5793
  syncIndexes: (id, rec, version, priorEnvelope) => syncTierIndexes(this.indexingContext(), id, rec, version, priorEnvelope),
5677
5794
  syncSearch: (id, rec, version) => syncTierSearch(this.searchContext(), id, rec, version),
5678
- syncHistory: async (id, fromDek, toDek) => this.historyStrategy.rewrapHistory(this.adapter, this.vault, this.name, id, fromDek, toDek, await this.getDEK(this.name)),
5795
+ syncHistory: async (id, fromDek, toDek) => this.strategies.history.rewrapHistory(this.adapter, this.vault, this.name, id, fromDek, toDek, await this.getDEK(this.name)),
5679
5796
  saveHistorySnapshot: async (id, envelope) => {
5680
5797
  if (this.historyConfig.enabled === false) return;
5681
- await this.historyStrategy.saveHistory(this.adapter, this.vault, this.name, id, envelope);
5798
+ await this.strategies.history.saveHistory(this.adapter, this.vault, this.name, id, envelope);
5682
5799
  this.emitter.emit("history:save", { vault: this.vault, collection: this.name, id, version: envelope._v });
5683
- if (this.historyConfig.maxVersions) await this.historyStrategy.pruneHistory(this.adapter, this.vault, this.name, id, { keepVersions: this.historyConfig.maxVersions });
5800
+ if (this.historyConfig.maxVersions) await this.strategies.history.pruneHistory(this.adapter, this.vault, this.name, id, { keepVersions: this.historyConfig.maxVersions });
5684
5801
  },
5685
- historyEnabled: this.historyConfig.enabled !== false && this.historyStrategy !== NO_HISTORY,
5802
+ historyEnabled: this.historyConfig.enabled !== false && this.strategies.history !== NO_HISTORY,
5686
5803
  // #728/#737: lets putAtTier skip decrypting `existing` when no real history strategy is wired
5687
- syncBlobs: (id, fromTier, toTier) => this.blobStrategy !== NO_BLOBS ? this.blob(id).syncTierMove(fromTier, toTier, this.blobTierPolicy) : Promise.resolve(),
5804
+ syncBlobs: (id, fromTier, toTier) => this.strategies.blob !== NO_BLOBS ? this.blob(id).syncTierMove(fromTier, toTier, this.blobTierPolicy) : Promise.resolve(),
5688
5805
  // #724 I1: gate on blob storage enabled (not on declared blobFields) so undeclared-field blobs still rehome; #746: syncTierMove mints/resumes the rehome journal marker; self-no-ops on an empty slot map
5689
5806
  syncLedger: async (id) => {
5690
5807
  await this.ledger?.purgeRecordDeltas(this.name, id);
@@ -6012,46 +6129,6 @@ var DeferredNumberingStore = class {
6012
6129
  }
6013
6130
  };
6014
6131
 
6015
- // src/with-fork/shadow/strategy.ts
6016
- var NOT_ENABLED2 = new Error(
6017
- 'VaultFrame requires the shadow strategy. Import `{ withShadow }` from "@noy-db/hub/shadow" and pass it to `createNoydb({ shadowStrategy: withShadow() })`.'
6018
- );
6019
- var NO_SHADOW = {
6020
- buildFrame() {
6021
- throw NOT_ENABLED2;
6022
- }
6023
- };
6024
-
6025
- // src/with-audit/consent/strategy.ts
6026
- var NO_CONSENT = {
6027
- async write() {
6028
- },
6029
- async read() {
6030
- return [];
6031
- }
6032
- };
6033
-
6034
- // src/with-audit/periods/strategy.ts
6035
- var NOT_ENABLED3 = new Error(
6036
- 'Accounting periods require the periods strategy. Import `{ withPeriods }` from "@noy-db/hub/periods" and pass it to `createNoydb({ periodsStrategy: withPeriods() })`.'
6037
- );
6038
- var NO_PERIODS = {
6039
- async loadPeriods() {
6040
- return [];
6041
- },
6042
- async chainAnchor() {
6043
- return { priorPeriodHash: "" };
6044
- },
6045
- assertTsWritable() {
6046
- },
6047
- validatePeriodName() {
6048
- throw NOT_ENABLED3;
6049
- },
6050
- async appendPeriodLedgerEntry() {
6051
- throw NOT_ENABLED3;
6052
- }
6053
- };
6054
-
6055
6132
  // src/with-audit/periods/vault-facade.ts
6056
6133
  var VaultPeriods = class {
6057
6134
  constructor(deps) {
@@ -8053,36 +8130,22 @@ var Vault = class {
8053
8130
  syncAdapter;
8054
8131
  getPurgeableTargets;
8055
8132
  historyConfig;
8056
- /** tree-shake seam for the optional blob service; `undefined` means "blobs are off for this
8057
- * vault" every `collection.blob(id)` call throws with a pointer at `@noy-db/hub/blobs`. */
8058
- blobStrategy;
8133
+ /**
8134
+ * Every opt-in service, resolved once by `createNoydb` (#838). Shared by
8135
+ * reference with the owning `Noydb` and with every `Collection` this vault
8136
+ * opens, so all three always agree about which services are enabled.
8137
+ */
8138
+ strategies;
8059
8139
  objectStore;
8060
- /** Cold-storage archival strategy (the archive target store). */
8061
- archiveStrategy;
8062
8140
  /** Per-collection record archival policies. Indexed by collection name. */
8063
8141
  archiveRegistry = /* @__PURE__ */ new Map();
8064
- indexStrategy;
8065
- lazyStrategy;
8066
- aggregateStrategy;
8067
- crdtStrategy;
8068
- tiersStrategy;
8069
- searchStrategy;
8070
- /** Cargo (partition extraction) strategy — `NO_CARGO` (throwing) unless `withCargo()` was passed.
8071
- * Public so the `extractPartition` free function (which takes a `Vault`) routes through it. */
8072
- cargoStrategy;
8073
- brokerStrategy;
8074
- sealedRecordStrategy;
8075
- portabilityStrategy;
8076
- sequenceStrategy;
8077
- consentStrategy;
8078
8142
  periods;
8079
8143
  linksEnforcer;
8080
- shadowStrategy;
8081
- historyStrategy;
8082
- forgetStrategy;
8083
- i18nStrategy;
8084
- syncStrategy;
8085
- classifiedStrategy;
8144
+ /** Cargo (partition extraction) strategy — `NO_CARGO` (throwing) unless `withCargo()` was passed.
8145
+ * Public so the `extractPartition` free function (which takes a `Vault`) routes through it. */
8146
+ get cargoStrategy() {
8147
+ return this.strategies.cargo;
8148
+ }
8086
8149
  /** Per-vault guard registry; `null` until `_initGuards()` runs (or for vaults that never register
8087
8150
  * one) — the runtime class is dynamic-imported on demand to keep it out of the floor bundle. */
8088
8151
  guardRegistry = null;
@@ -8264,23 +8327,10 @@ var Vault = class {
8264
8327
  this.onRegisterConflictResolver = opts.onRegisterConflictResolver;
8265
8328
  this.syncAdapter = opts.syncAdapter;
8266
8329
  this.getPurgeableTargets = opts.getPurgeableTargets ?? (() => []);
8267
- this.blobStrategy = opts.blobStrategy;
8330
+ this.strategies = opts.strategies;
8268
8331
  this.objectStore = opts.objectStore;
8269
- this.archiveStrategy = opts.archiveStrategy;
8270
- this.indexStrategy = opts.indexStrategy;
8271
- this.lazyStrategy = opts.lazyStrategy;
8272
- this.aggregateStrategy = opts.aggregateStrategy;
8273
- this.crdtStrategy = opts.crdtStrategy;
8274
- this.tiersStrategy = opts.tiersStrategy;
8275
- this.searchStrategy = opts.searchStrategy;
8276
- this.cargoStrategy = opts.cargoStrategy ?? NO_CARGO;
8277
- this.brokerStrategy = opts.brokerStrategy ?? NO_BROKER;
8278
- this.sealedRecordStrategy = opts.sealedRecordStrategy ?? NO_SEALED_RECORD;
8279
- this.portabilityStrategy = opts.portabilityStrategy ?? NO_PORTABILITY;
8280
- this.sequenceStrategy = opts.sequenceStrategy ?? NO_SEQUENCE;
8281
- this.consentStrategy = opts.consentStrategy ?? NO_CONSENT;
8282
8332
  this.periods = new VaultPeriods({
8283
- strategy: opts.periodsStrategy ?? NO_PERIODS,
8333
+ strategy: this.strategies.periods,
8284
8334
  adapter: this.adapter,
8285
8335
  vault: this.name,
8286
8336
  encrypted: this.encrypted,
@@ -8305,12 +8355,6 @@ var Vault = class {
8305
8355
  getActiveTxContext: () => this.noydb._activeTxContextOrNull,
8306
8356
  emitter: this.emitter
8307
8357
  });
8308
- this.shadowStrategy = opts.shadowStrategy ?? NO_SHADOW;
8309
- this.historyStrategy = opts.historyStrategy ?? NO_HISTORY;
8310
- this.forgetStrategy = opts.forgetStrategy ?? NO_FORGET;
8311
- this.i18nStrategy = opts.i18nStrategy ?? NO_I18N;
8312
- this.syncStrategy = opts.syncStrategy ?? NO_SYNC;
8313
- this.classifiedStrategy = opts.classifiedStrategy ?? NO_CLASSIFIED;
8314
8358
  this.graph = new ViaGraph();
8315
8359
  void opts.guardStrategies;
8316
8360
  this.historyConfig = opts.historyConfig ?? { enabled: true };
@@ -8325,19 +8369,19 @@ var Vault = class {
8325
8369
  getDEK: (collection) => this.getDEK(collection),
8326
8370
  role: () => this.keyring.role,
8327
8371
  getRawRecord: async (collection, recId) => await this.collection(collection).get(recId, { locale: "raw" })
8328
- }, opts.attestationStrategy ?? NO_ATTESTATION);
8372
+ }, this.strategies.attestation);
8329
8373
  this.user = this.noydb.userApiFactory({
8330
8374
  adapter: this.adapter,
8331
8375
  vaultName: this.name,
8332
8376
  writerKeyringId: this.keyring.userId,
8333
8377
  getDek: () => this.getDEK(USER_ENVELOPE_COLLECTION),
8334
8378
  checkGate: (gate, presented) => this.noydb.checkGate(this.name, gate, presented),
8335
- exportAccessible: (opts2) => this.portabilityStrategy.exportAccessibleData(this, opts2),
8336
- unilateralWithdraw: (opts2) => this.portabilityStrategy.withdrawAccessibleData(this, opts2),
8337
- requestWithdraw: (opts2) => this.portabilityStrategy.requestWithdrawal(this, opts2),
8338
- listWithdrawals: (opts2) => this.portabilityStrategy.listWithdrawalRequests(this, opts2),
8339
- approveWithdraw: (requestId, opts2) => this.portabilityStrategy.approveWithdrawal(this, requestId, opts2),
8340
- rejectWithdraw: (requestId, opts2) => this.portabilityStrategy.rejectWithdrawal(this, requestId, opts2)
8379
+ exportAccessible: (opts2) => this.strategies.portability.exportAccessibleData(this, opts2),
8380
+ unilateralWithdraw: (opts2) => this.strategies.portability.withdrawAccessibleData(this, opts2),
8381
+ requestWithdraw: (opts2) => this.strategies.portability.requestWithdrawal(this, opts2),
8382
+ listWithdrawals: (opts2) => this.strategies.portability.listWithdrawalRequests(this, opts2),
8383
+ approveWithdraw: (requestId, opts2) => this.strategies.portability.approveWithdrawal(this, requestId, opts2),
8384
+ rejectWithdraw: (requestId, opts2) => this.strategies.portability.rejectWithdrawal(this, requestId, opts2)
8341
8385
  });
8342
8386
  this.custody = new CustodyApi(
8343
8387
  (options, factors) => this.noydb.grantCustodian(this.name, options, factors),
@@ -8395,7 +8439,7 @@ var Vault = class {
8395
8439
  * dispatch reads/writes — from this vault's OWN privates, rather than passing `this` itself. */
8396
8440
  _viaReconcileCtx() {
8397
8441
  return {
8398
- i18nStrategy: this.i18nStrategy,
8442
+ i18nStrategy: this.strategies.i18n,
8399
8443
  locale: this.locale,
8400
8444
  translateText: this.translateText,
8401
8445
  i18nFieldRegistry: this.i18nFieldRegistry,
@@ -8436,7 +8480,7 @@ var Vault = class {
8436
8480
  this.satelliteRegistry = declareSatellite({
8437
8481
  adapter: this.adapter,
8438
8482
  vaultName: this.name,
8439
- forgetSubjects: this.forgetStrategy.subjects,
8483
+ forgetSubjects: this.strategies.forget.subjects,
8440
8484
  getDEK: this.getDEK,
8441
8485
  getBaseSchema: (base) => this.collectionCache.get(base)?.getSchema(),
8442
8486
  getBaseCrdt: (base) => this.collectionCache.get(base)?.getConfig()?.crdt,
@@ -8556,22 +8600,9 @@ var Vault = class {
8556
8600
  // #693: re-create gate fallback
8557
8601
  historyConfig: effectiveHistoryConfig,
8558
8602
  historyConfigExplicit: options?.historyConfig !== void 0,
8559
- // thread the vault-wide blob strategy into every
8560
- // collection. `undefined` is intentionally preserved so the
8561
- // Collection constructor uses its NO_BLOBS default.
8562
- ...this.blobStrategy !== void 0 ? { blobStrategy: this.blobStrategy } : {},
8563
8603
  ...this.objectStore !== void 0 ? { objectStore: this.objectStore } : {},
8564
8604
  ...options?.blobFields !== void 0 ? { blobFields: options.blobFields } : {},
8565
- ...this.indexStrategy !== void 0 ? { indexStrategy: this.indexStrategy } : {},
8566
- ...this.lazyStrategy !== void 0 ? { lazyStrategy: this.lazyStrategy } : {},
8567
- ...this.aggregateStrategy !== void 0 ? { aggregateStrategy: this.aggregateStrategy } : {},
8568
- ...this.crdtStrategy !== void 0 ? { crdtStrategy: this.crdtStrategy } : {},
8569
- ...this.tiersStrategy !== void 0 ? { tiersStrategy: this.tiersStrategy } : {},
8570
- ...this.searchStrategy !== void 0 ? { searchStrategy: this.searchStrategy } : {},
8571
- historyStrategy: this.historyStrategy,
8572
- i18nStrategy: this.i18nStrategy,
8573
- syncStrategy: this.syncStrategy,
8574
- classifiedStrategy: this.classifiedStrategy,
8605
+ strategies: this.strategies,
8575
8606
  // Per-collection ledger opt-out: when this collection sets
8576
8607
  // `historyConfig.ledger: false`, withhold the ledger reference so all
8577
8608
  // four `if (this.ledger)` append sites in Collection no-op. The chain
@@ -8629,7 +8660,7 @@ var Vault = class {
8629
8660
  collOpts.sensitive = options.sensitive;
8630
8661
  }
8631
8662
  if (options?.perRecordKeys !== void 0) collOpts.perRecordKeys = options.perRecordKeys;
8632
- const subjectKey = this.forgetStrategy.subjects[collectionName];
8663
+ const subjectKey = this.strategies.forget.subjects[collectionName];
8633
8664
  if (subjectKey !== void 0) {
8634
8665
  if (options?.perRecordKeys === false) {
8635
8666
  console.warn(
@@ -9010,7 +9041,7 @@ var Vault = class {
9010
9041
  const values = getAtPath(obj, field);
9011
9042
  for (const value of values) {
9012
9043
  if (value === void 0 || value === null) continue;
9013
- this.i18nStrategy.validateI18nTextValue(value, field, descriptor);
9044
+ this.strategies.i18n.validateI18nTextValue(value, field, descriptor);
9014
9045
  }
9015
9046
  }
9016
9047
  }
@@ -9051,7 +9082,7 @@ var Vault = class {
9051
9082
  this.reservedLookupCollections.set(dictCollectionName(name), name);
9052
9083
  let handle = this.dictionaryCache.get(name);
9053
9084
  if (!handle) {
9054
- handle = this.i18nStrategy.buildDictionaryHandle({
9085
+ handle = this.strategies.i18n.buildDictionaryHandle({
9055
9086
  adapter: this.adapter,
9056
9087
  compartmentName: this.name,
9057
9088
  dictionaryName: name,
@@ -9264,7 +9295,7 @@ var Vault = class {
9264
9295
  };
9265
9296
  }
9266
9297
  if (!this.sequenceStore) {
9267
- this.sequenceStore = this.sequenceStrategy.createStore({
9298
+ this.sequenceStore = this.strategies.sequence.createStore({
9268
9299
  adapter: this.adapter,
9269
9300
  vault: this.name,
9270
9301
  encrypted: this.encrypted,
@@ -9362,7 +9393,7 @@ var Vault = class {
9362
9393
  return runListArchived(this._archiveContext(), collection);
9363
9394
  }
9364
9395
  _archiveContext() {
9365
- const strategy = this.archiveStrategy;
9396
+ const strategy = this.strategies.archive;
9366
9397
  if (!strategy) {
9367
9398
  throw new Error(
9368
9399
  "vault.archive/restore/listArchived require `archiveStrategy: withArchive({ store })` in createNoydb"
@@ -9529,7 +9560,7 @@ var Vault = class {
9529
9560
  */
9530
9561
  getLedgerOrNull() {
9531
9562
  if (!this.ledgerStore) {
9532
- this.ledgerStore = this.historyStrategy.buildLedger({
9563
+ this.ledgerStore = this.strategies.history.buildLedger({
9533
9564
  adapter: this.adapter,
9534
9565
  vault: this.name,
9535
9566
  encrypted: this.encrypted,
@@ -9554,7 +9585,7 @@ var Vault = class {
9554
9585
  * number of distinct subjects re-indexed.
9555
9586
  */
9556
9587
  async rebuildSubjectIndex() {
9557
- if (Object.keys(this.forgetStrategy.subjects).length === 0) {
9588
+ if (Object.keys(this.strategies.forget.subjects).length === 0) {
9558
9589
  throw new ForgetStrategyNotConfiguredError();
9559
9590
  }
9560
9591
  return rebuildSubjectIndex(
@@ -9562,7 +9593,7 @@ var Vault = class {
9562
9593
  this.name,
9563
9594
  this.getDEK,
9564
9595
  this.encrypted,
9565
- this.forgetStrategy.subjects,
9596
+ this.strategies.forget.subjects,
9566
9597
  async (collectionName, id, env) => {
9567
9598
  const coll = this.collection(collectionName);
9568
9599
  return coll._decodeEnvelope(env, id);
@@ -9587,7 +9618,7 @@ var Vault = class {
9587
9618
  * @throws ForgetStrategyNotConfiguredError when no `withForgetCascade` was set.
9588
9619
  */
9589
9620
  async forget(subjectId) {
9590
- if (Object.keys(this.forgetStrategy.subjects).length === 0) {
9621
+ if (Object.keys(this.strategies.forget.subjects).length === 0) {
9591
9622
  throw new ForgetStrategyNotConfiguredError();
9592
9623
  }
9593
9624
  const ledger = this.getLedgerOrNull();
@@ -9609,17 +9640,17 @@ var Vault = class {
9609
9640
  const sealedResidue = [];
9610
9641
  const indexResidue = [];
9611
9642
  const ledgerDeltaResidue = [];
9612
- const blobsEnabled = this.blobStrategy !== void 0;
9643
+ const blobsEnabled = this.strategies.blob !== NO_BLOBS;
9613
9644
  const actor = this.keyring.userId;
9614
9645
  const fanoutStats = { recordsErased: 0, aggregatesRecomputed: 0, residueFrozen: [], lookupReferencesCascaded: 0, lookupReferencesNullified: 0, lookupReferencesResidue: [], derivedResidueUndecodable: [], derivedResidueDeclined: [] };
9615
9646
  const { partitionSealedCekKeys, shouldSkipBlobScan, bumpResidueCount, residueNoticesFromMap } = await import("./purge-scope-FGNNLWHD.js");
9616
- const scopedPurge = this.forgetStrategy.scopedPurge === true;
9647
+ const scopedPurge = this.strategies.forget.scopedPurge === true;
9617
9648
  const sealedCekScopedSkipped = /* @__PURE__ */ new Map();
9618
9649
  const blobScopedSkipped = /* @__PURE__ */ new Map();
9619
9650
  for (const ref2 of allRefs) {
9620
9651
  const coll = this.collection(ref2.collection);
9621
9652
  const satelliteOf = ref2.satelliteOf;
9622
- const perRecordKeys = this.forgetStrategy.subjects[satelliteOf ?? ref2.collection] !== void 0;
9653
+ const perRecordKeys = this.strategies.forget.subjects[satelliteOf ?? ref2.collection] !== void 0;
9623
9654
  const lookupCompareKeys = await this.linksEnforcer.checkLookupRefsRestrict(this.graph, ref2.collection, ref2.collection, ref2.id);
9624
9655
  const live = await this.adapter.get(this.name, ref2.collection, ref2.id);
9625
9656
  if (perRecordKeys && live && live._data && !hasPerRecordKey(live)) {
@@ -9679,7 +9710,7 @@ var Vault = class {
9679
9710
  collections.add(ref2.collection);
9680
9711
  await forgetDerivedFanout(this, { collection: ref2.collection, id: ref2.id }, live, fanoutStats, lookupCompareKeys);
9681
9712
  }
9682
- historyVersionsShredded += await this.historyStrategy.tombstoneHistory(
9713
+ historyVersionsShredded += await this.strategies.history.tombstoneHistory(
9683
9714
  this.adapter,
9684
9715
  this.name,
9685
9716
  ref2.collection,
@@ -9809,7 +9840,7 @@ var Vault = class {
9809
9840
  * `<collection>/<id>/<pid>` key the delivery envelope was written under.
9810
9841
  */
9811
9842
  async sealRecordToHost(collection, id, hostSealer, opts) {
9812
- return this.sealedRecordStrategy.sealRecordToHost(this._sealingContext(), collection, id, hostSealer, opts);
9843
+ return this.strategies.sealedRecord.sealRecordToHost(this._sealingContext(), collection, id, hostSealer, opts);
9813
9844
  }
9814
9845
  /**
9815
9846
  * Revoke a sealed-CEK grant. **Default is SOFT**: deletes the `pid` delivery
@@ -9819,7 +9850,7 @@ var Vault = class {
9819
9850
  * longer open the record. See `revokeSealedRecord` in `record-keys/sealing.ts`.
9820
9851
  */
9821
9852
  async revokeSealedRecord(collection, id, pid, opts) {
9822
- return this.sealedRecordStrategy.revokeSealedRecord(this._sealingContext(), collection, id, pid, opts);
9853
+ return this.strategies.sealedRecord.revokeSealedRecord(this._sealingContext(), collection, id, pid, opts);
9823
9854
  }
9824
9855
  /**
9825
9856
  * HARD-rotate a record's CEK: decrypt the live body under the old CEK,
@@ -9839,7 +9870,7 @@ var Vault = class {
9839
9870
  * @throws {@link RecordCekNotFoundError} if the record is missing or has no `_cek`.
9840
9871
  */
9841
9872
  async rotateRecordCek(collection, id) {
9842
- return this.sealedRecordStrategy.rotateRecordCek(this._sealingContext(), collection, id);
9873
+ return this.strategies.sealedRecord.rotateRecordCek(this._sealingContext(), collection, id);
9843
9874
  }
9844
9875
  /**
9845
9876
  * Build the {@link SealingContext} the record-keys grantor functions need:
@@ -10005,14 +10036,14 @@ var Vault = class {
10005
10036
  if (!reg) {
10006
10037
  throw new Error(`refreshView: no MV registered with name "${name}"`);
10007
10038
  }
10008
- const { MaterializedViewExecutor } = await import("./executor-UF7LTX6O.js");
10039
+ const { MaterializedViewExecutor } = await import("./executor-YORMIBWV.js");
10009
10040
  const result = await MaterializedViewExecutor.refresh(reg, {
10010
10041
  getCollection: (n) => this.collection(n),
10011
10042
  getActiveTxContext: () => this.noydb._activeTxContextOrNull,
10012
10043
  getQueryContext: () => this,
10013
10044
  dispatchCtx: this._dispatchCtx({ collection: name, id: "refreshView" })
10014
10045
  });
10015
- const { clearMVStaleFully } = await import("./stale-NB5BGRVU.js");
10046
+ const { clearMVStaleFully } = await import("./stale-4UNPHWOU.js");
10016
10047
  await clearMVStaleFully(this.adapter, this.name, registry, name);
10017
10048
  return result;
10018
10049
  }
@@ -10146,7 +10177,7 @@ var Vault = class {
10146
10177
  */
10147
10178
  at(timestamp) {
10148
10179
  const iso = timestamp instanceof Date ? timestamp.toISOString() : timestamp;
10149
- return this.historyStrategy.buildVaultInstant(
10180
+ return this.strategies.history.buildVaultInstant(
10150
10181
  {
10151
10182
  adapter: this.adapter,
10152
10183
  name: this.name,
@@ -10178,11 +10209,11 @@ var Vault = class {
10178
10209
  *.
10179
10210
  */
10180
10211
  frame() {
10181
- return this.shadowStrategy.buildFrame(this);
10212
+ return this.strategies.shadow.buildFrame(this);
10182
10213
  }
10183
10214
  /** Credential-broker handle (#479); `NO_BROKER` throws unless `brokerStrategy: withBroker(config)` was passed to createNoydb(). */
10184
10215
  broker() {
10185
- return buildCredentialBrokerHandle(this.brokerStrategy, this.adapter, this.name, () => this.keyring);
10216
+ return buildCredentialBrokerHandle(this.strategies.broker, this.adapter, this.name, () => this.keyring);
10186
10217
  }
10187
10218
  /**
10188
10219
  * Run `fn` under a consent scope. Every `get` / `put` / `delete`
@@ -10224,7 +10255,7 @@ var Vault = class {
10224
10255
  *.
10225
10256
  */
10226
10257
  async consentAudit(filter = {}) {
10227
- return this.consentStrategy.read(this.adapter, this.name, this.encrypted, this.getDEK, filter);
10258
+ return this.strategies.consent.read(this.adapter, this.name, this.encrypted, this.getDEK, filter);
10228
10259
  }
10229
10260
  /**
10230
10261
  * Called by Collection after every access when a consent scope is
@@ -10235,7 +10266,7 @@ var Vault = class {
10235
10266
  async _logConsent(op, collection, recordId) {
10236
10267
  const ctx = this.consentContext;
10237
10268
  if (!ctx) return;
10238
- await this.consentStrategy.write(
10269
+ await this.strategies.consent.write(
10239
10270
  this.adapter,
10240
10271
  this.name,
10241
10272
  this.encrypted,
@@ -10714,7 +10745,7 @@ var Vault = class {
10714
10745
  vault: this.name,
10715
10746
  userId: () => this.keyring.userId,
10716
10747
  getLedgerOrNull: () => this.getLedgerOrNull(),
10717
- envelopePayloadHash: (envelope) => this.historyStrategy.envelopePayloadHash(envelope),
10748
+ envelopePayloadHash: (envelope) => this.strategies.history.envelopePayloadHash(envelope),
10718
10749
  reloadKeyringAndRebuildDEK: async () => {
10719
10750
  if (this.reloadKeyring) {
10720
10751
  this.keyring = await this.reloadKeyring();
@@ -11399,25 +11430,6 @@ var QuickUnlockStore = class {
11399
11430
  }
11400
11431
  };
11401
11432
 
11402
- // src/with-fork/snapshots/strategy.ts
11403
- var NOT_ENABLED4 = new Error(
11404
- 'Snapshots require the snapshot strategy. Import `{ withSnapshots }` from "@noy-db/hub/snapshots" and pass it to `createNoydb({ snapshotStrategy: withSnapshots({ store }) })`.'
11405
- );
11406
- var NO_SNAPSHOTS = {
11407
- async snapshot() {
11408
- throw NOT_ENABLED4;
11409
- },
11410
- async listSnapshots() {
11411
- throw NOT_ENABLED4;
11412
- },
11413
- async restoreSnapshot() {
11414
- throw NOT_ENABLED4;
11415
- },
11416
- async autoSnapshot() {
11417
- throw NOT_ENABLED4;
11418
- }
11419
- };
11420
-
11421
11433
  // src/with-fork/snapshots/scheduler.ts
11422
11434
  var SnapshotScheduler = class {
11423
11435
  policy;
@@ -11618,19 +11630,6 @@ var NoydbSnapshots = class {
11618
11630
  }
11619
11631
  };
11620
11632
 
11621
- // src/with-commit/tx/strategy.ts
11622
- var NOT_ENABLED5 = new Error(
11623
- 'Multi-record transactions require the tx strategy. Import `{ withTransactions }` from "@noy-db/hub/tx" and pass it to `createNoydb({ txStrategy: withTransactions() })`.'
11624
- );
11625
- var NO_TX = {
11626
- async runTransaction(_db, _fn, _options, _txInvariants) {
11627
- throw NOT_ENABLED5;
11628
- },
11629
- async runDryRun() {
11630
- throw NOT_ENABLED5;
11631
- }
11632
- };
11633
-
11634
11633
  // src/kernel/memory-store.ts
11635
11634
  function memoryStore() {
11636
11635
  const store = /* @__PURE__ */ new Map();
@@ -11714,23 +11713,6 @@ function memoryStore() {
11714
11713
  };
11715
11714
  }
11716
11715
 
11717
- // src/with-party/session/strategy.ts
11718
- function notEnabled3(op) {
11719
- return new Error(
11720
- `${op} requires the session strategy. Import \`{ withSession }\` from "@noy-db/hub/session" and pass it to \`createNoydb({ sessionStrategy: withSession() })\`.`
11721
- );
11722
- }
11723
- var NO_SESSION = {
11724
- validateSessionPolicy() {
11725
- throw notEnabled3("sessionPolicy");
11726
- },
11727
- createEnforcer() {
11728
- throw notEnabled3("session policy enforcement");
11729
- },
11730
- revokeAllSessions() {
11731
- }
11732
- };
11733
-
11734
11716
  // src/with-party/auth-introspection/index.ts
11735
11717
  async function describeAuthConfig(store, vault) {
11736
11718
  const policy = await loadVaultPolicy(store, vault) ?? defaultPolicySnapshot();
@@ -12684,21 +12666,22 @@ var Noydb = class {
12684
12666
  writeRelay;
12685
12667
  /** Per-vault policy enforcers. */
12686
12668
  policyEnforcers = /* @__PURE__ */ new Map();
12687
- txStrategy;
12688
- forgetStrategy;
12689
12669
  /**
12690
- * Opt-in sovereign-custody (FR-6) strategy `NO_CUSTODY` (throwing) unless
12691
- * `withCustody()` was passed. Public so `Vault` routes `vault.custody.liberate`
12692
- * through it; grant/revoke route through it from this class. @internal
12670
+ * Every opt-in service, resolved once (#838). Shared by reference with each
12671
+ * `Vault` this instance opens and, through them, every `Collection` so
12672
+ * there is exactly one resolution and no construction path can disagree
12673
+ * about which services are enabled (the #834 bug class).
12693
12674
  */
12694
- custodyStrategy;
12675
+ strategies;
12695
12676
  /**
12696
- * Opt-in multi-user team strategy (#267 keyring-grant → team split)
12697
- * `NO_TEAM` (throwing) unless `withTeam()` was passed; the keyring
12698
- * engines are linked only by the active strategy, not by this file. */
12699
- teamStrategy;
12700
- sessionStrategy;
12701
- syncStrategy;
12677
+ * Opt-in sovereign-custody (FR-6) strategy `NO_CUSTODY` (throwing) unless
12678
+ * `withCustody()` was passed. Kept as a public accessor because `Vault`
12679
+ * routes `vault.custody.liberate` through `this.noydb.custodyStrategy`.
12680
+ * @internal
12681
+ */
12682
+ get custodyStrategy() {
12683
+ return this.strategies.custody;
12684
+ }
12702
12685
  snapshots;
12703
12686
  policyManager;
12704
12687
  /** Pre-resolved policy-gate engine function (mirrors `coordinationProvider`/`userApiFactory` above). */
@@ -12742,14 +12725,9 @@ var Noydb = class {
12742
12725
  this.userApiFactory = options.userApiFactory;
12743
12726
  if (!options.policyFactory || !options.policyCheckGateFn) throw new ValidationError("Noydb must be constructed via createNoydb(), which resolves the default policy service.");
12744
12727
  this.policyCheckGate = options.policyCheckGateFn;
12745
- this.txStrategy = options.txStrategy ?? NO_TX;
12746
- this.forgetStrategy = options.forgetStrategy ?? NO_FORGET;
12747
- this.custodyStrategy = options.custodyStrategy ?? NO_CUSTODY;
12748
- this.teamStrategy = options.teamStrategy ?? NO_TEAM;
12749
- this.sessionStrategy = options.sessionStrategy ?? NO_SESSION;
12750
- this.syncStrategy = options.syncStrategy ?? NO_SYNC;
12728
+ this.strategies = resolveStrategies(options);
12751
12729
  this.snapshots = new NoydbSnapshots({
12752
- strategy: options.snapshotStrategy ?? NO_SNAPSHOTS,
12730
+ strategy: this.strategies.snapshot,
12753
12731
  user: options.user,
12754
12732
  isClosed: () => this.closed,
12755
12733
  getVault: (name) => this.vaultCache.get(name),
@@ -12786,7 +12764,7 @@ var Noydb = class {
12786
12764
  }
12787
12765
  });
12788
12766
  if (options.sessionPolicy) {
12789
- this.sessionStrategy.validateSessionPolicy(options.sessionPolicy);
12767
+ this.strategies.session.validateSessionPolicy(options.sessionPolicy);
12790
12768
  }
12791
12769
  this.#registerGuardGate();
12792
12770
  this.#registerPeriodGate();
@@ -12795,7 +12773,7 @@ var Noydb = class {
12795
12773
  }
12796
12774
  /** @internal — resolved forget strategy (NO_FORGET when not configured). */
12797
12775
  get _forgetStrategy() {
12798
- return this.forgetStrategy;
12776
+ return this.strategies.forget;
12799
12777
  }
12800
12778
  // GDPR subject-index maintenance. When `withForgetCascade` declares
12801
12779
  // any subject fields, keep the encrypted `_subject_index` in lock-step with
@@ -12808,7 +12786,7 @@ var Noydb = class {
12808
12786
  // does NOT) — drop the ref so a deleted record never lingers in the
12809
12787
  // index (RISK #2). Without it, forget() would try to shred a ghost.
12810
12788
  #registerForgetHooks() {
12811
- const subjects = this.forgetStrategy.subjects;
12789
+ const subjects = this.strategies.forget.subjects;
12812
12790
  if (Object.keys(subjects).length === 0) return;
12813
12791
  const subjectFieldFor = (collection) => subjects[collection];
12814
12792
  this.writeHooks.onAfterWrite(async (event) => {
@@ -12997,7 +12975,7 @@ var Noydb = class {
12997
12975
  if (targets.length > 0) {
12998
12976
  const primary = targets.find((t) => t.role === "sync-peer") ?? targets[0];
12999
12977
  const effectivePolicy = this.options.syncPolicy ?? primary.policy ?? INDEXED_STORE_POLICY;
13000
- syncEngine = this.syncStrategy.buildSyncEngine({
12978
+ syncEngine = this.strategies.sync.buildSyncEngine({
13001
12979
  local: this.options.store,
13002
12980
  remote: primary.store,
13003
12981
  vault: name,
@@ -13011,7 +12989,7 @@ var Noydb = class {
13011
12989
  for (const target of targets) {
13012
12990
  if (target === primary) continue;
13013
12991
  const targetPolicy = target.policy ?? this.options.syncPolicy ?? INDEXED_STORE_POLICY;
13014
- const engine = this.syncStrategy.buildSyncEngine({
12992
+ const engine = this.strategies.sync.buildSyncEngine({
13015
12993
  local: this.options.store,
13016
12994
  remote: target.store,
13017
12995
  vault: name,
@@ -13044,31 +13022,14 @@ var Noydb = class {
13044
13022
  syncAdapter: targets.length > 0 ? targets[0].store : void 0,
13045
13023
  getPurgeableTargets: () => targets.filter((t) => t.role === "backup" || t.role === "archive").map((t) => ({ store: t.store, role: t.role, ...t.label !== void 0 ? { label: t.label } : {} })),
13046
13024
  historyConfig: this.options.history,
13047
- ...this.options.blobStrategy !== void 0 ? { blobStrategy: this.options.blobStrategy } : {},
13025
+ // #838 the 21 conditional strategy spreads that used to live here are
13026
+ // one shared reference. This block is where #834's drift happened: a
13027
+ // copy of it silently omitted six strategies, and there was nothing to
13028
+ // catch that. There is no longer a per-service line to omit.
13029
+ strategies: this.strategies,
13048
13030
  ...this.options.objectStore !== void 0 ? { objectStore: this.options.objectStore } : {},
13049
- ...this.options.archiveStrategy !== void 0 ? { archiveStrategy: this.options.archiveStrategy } : {},
13050
- ...this.options.indexStrategy !== void 0 ? { indexStrategy: this.options.indexStrategy } : {},
13051
- ...this.options.lazyStrategy !== void 0 ? { lazyStrategy: this.options.lazyStrategy } : {},
13052
- ...this.options.aggregateStrategy !== void 0 ? { aggregateStrategy: this.options.aggregateStrategy } : {},
13053
- ...this.options.crdtStrategy !== void 0 ? { crdtStrategy: this.options.crdtStrategy } : {},
13054
- ...this.options.tiersStrategy !== void 0 ? { tiersStrategy: this.options.tiersStrategy } : {},
13055
- ...this.options.searchStrategy !== void 0 ? { searchStrategy: this.options.searchStrategy } : {},
13056
- ...this.options.cargoStrategy !== void 0 ? { cargoStrategy: this.options.cargoStrategy } : {},
13057
- ...this.options.brokerStrategy !== void 0 ? { brokerStrategy: this.options.brokerStrategy } : {},
13058
- ...this.options.consentStrategy !== void 0 ? { consentStrategy: this.options.consentStrategy } : {},
13059
- ...this.options.periodsStrategy !== void 0 ? { periodsStrategy: this.options.periodsStrategy } : {},
13060
- ...this.options.shadowStrategy !== void 0 ? { shadowStrategy: this.options.shadowStrategy } : {},
13061
- ...this.options.historyStrategy !== void 0 ? { historyStrategy: this.options.historyStrategy } : {},
13062
- ...this.options.i18nStrategy !== void 0 ? { i18nStrategy: this.options.i18nStrategy } : {},
13063
- ...this.options.syncStrategy !== void 0 ? { syncStrategy: this.options.syncStrategy } : {},
13064
13031
  ...this.options.guardStrategies !== void 0 ? { guardStrategies: this.options.guardStrategies } : {},
13065
13032
  ...this.options.numbering !== void 0 ? { numberingConfigs: this.options.numbering } : {},
13066
- forgetStrategy: this.forgetStrategy,
13067
- ...this.options.attestationStrategy !== void 0 ? { attestationStrategy: this.options.attestationStrategy } : {},
13068
- ...this.options.classifiedStrategy !== void 0 ? { classifiedStrategy: this.options.classifiedStrategy } : {},
13069
- ...this.options.sealedRecordStrategy !== void 0 ? { sealedRecordStrategy: this.options.sealedRecordStrategy } : {},
13070
- ...this.options.portabilityStrategy !== void 0 ? { portabilityStrategy: this.options.portabilityStrategy } : {},
13071
- ...this.options.sequenceStrategy !== void 0 ? { sequenceStrategy: this.options.sequenceStrategy } : {},
13072
13033
  locale: opts?.locale,
13073
13034
  ...opts?.meta !== void 0 ? { meta: opts.meta } : {},
13074
13035
  // Thread the translator hook so Collection.put() can invoke it
@@ -13105,85 +13066,25 @@ var Noydb = class {
13105
13066
  this.vaultCache.set(name, comp);
13106
13067
  return comp;
13107
13068
  }
13108
- /** Synchronous vault access (must call openVault first, or auto-opens). */
13069
+ /**
13070
+ * Synchronous access to an ALREADY-OPEN vault.
13071
+ *
13072
+ * Returns the instance `openVault()` produced, or throws — it never
13073
+ * constructs. It used to fall back to building a Vault itself (once for
13074
+ * plaintext, once for encrypted-with-cached-keyring), and those copies
13075
+ * drifted from the real open path: the encrypted one silently omitted six
13076
+ * strategies, so a vault reached this way threw `*NotEnabledError` for
13077
+ * services the caller had configured (#834). Both fallbacks also skipped
13078
+ * the async registry init and schema-fence snapshot that `openVault`
13079
+ * performs, which a synchronous method cannot await. One construction
13080
+ * path is the only way that divergence stays fixed.
13081
+ */
13109
13082
  vault(name) {
13110
13083
  const cached = this.vaultCache.get(name);
13111
13084
  if (cached) return cached;
13112
- if (this.options.encrypt === false) {
13113
- const keyring2 = createPlaintextKeyring(this.options.user, this.options.debugPlaintext === true);
13114
- const comp2 = new Vault({
13115
- adapter: this.options.store,
13116
- name,
13117
- noydb: this,
13118
- keyring: keyring2,
13119
- encrypted: false,
13120
- emitter: this.emitter,
13121
- historyConfig: this.options.history,
13122
- ...this.options.blobStrategy !== void 0 ? { blobStrategy: this.options.blobStrategy } : {},
13123
- ...this.options.objectStore !== void 0 ? { objectStore: this.options.objectStore } : {},
13124
- ...this.options.archiveStrategy !== void 0 ? { archiveStrategy: this.options.archiveStrategy } : {},
13125
- ...this.options.indexStrategy !== void 0 ? { indexStrategy: this.options.indexStrategy } : {},
13126
- ...this.options.lazyStrategy !== void 0 ? { lazyStrategy: this.options.lazyStrategy } : {},
13127
- ...this.options.aggregateStrategy !== void 0 ? { aggregateStrategy: this.options.aggregateStrategy } : {},
13128
- ...this.options.crdtStrategy !== void 0 ? { crdtStrategy: this.options.crdtStrategy } : {},
13129
- ...this.options.tiersStrategy !== void 0 ? { tiersStrategy: this.options.tiersStrategy } : {},
13130
- ...this.options.searchStrategy !== void 0 ? { searchStrategy: this.options.searchStrategy } : {},
13131
- ...this.options.cargoStrategy !== void 0 ? { cargoStrategy: this.options.cargoStrategy } : {},
13132
- ...this.options.brokerStrategy !== void 0 ? { brokerStrategy: this.options.brokerStrategy } : {},
13133
- ...this.options.consentStrategy !== void 0 ? { consentStrategy: this.options.consentStrategy } : {},
13134
- ...this.options.periodsStrategy !== void 0 ? { periodsStrategy: this.options.periodsStrategy } : {},
13135
- ...this.options.shadowStrategy !== void 0 ? { shadowStrategy: this.options.shadowStrategy } : {},
13136
- ...this.options.historyStrategy !== void 0 ? { historyStrategy: this.options.historyStrategy } : {},
13137
- ...this.options.i18nStrategy !== void 0 ? { i18nStrategy: this.options.i18nStrategy } : {},
13138
- ...this.options.syncStrategy !== void 0 ? { syncStrategy: this.options.syncStrategy } : {},
13139
- ...this.options.guardStrategies !== void 0 ? { guardStrategies: this.options.guardStrategies } : {},
13140
- ...this.options.numbering !== void 0 ? { numberingConfigs: this.options.numbering } : {},
13141
- forgetStrategy: this.forgetStrategy,
13142
- ...this.options.attestationStrategy !== void 0 ? { attestationStrategy: this.options.attestationStrategy } : {},
13143
- ...this.options.classifiedStrategy !== void 0 ? { classifiedStrategy: this.options.classifiedStrategy } : {},
13144
- ...this.options.sealedRecordStrategy !== void 0 ? { sealedRecordStrategy: this.options.sealedRecordStrategy } : {},
13145
- ...this.options.portabilityStrategy !== void 0 ? { portabilityStrategy: this.options.portabilityStrategy } : {},
13146
- ...this.options.sequenceStrategy !== void 0 ? { sequenceStrategy: this.options.sequenceStrategy } : {}
13147
- });
13148
- this.vaultCache.set(name, comp2);
13149
- return comp2;
13150
- }
13151
- const keyring = this.keyringCache.get(name);
13152
- if (!keyring) {
13153
- throw new ValidationError(
13154
- `Vault "${name}" not opened. Use await db.openVault("${name}") first.`
13155
- );
13156
- }
13157
- const comp = new Vault({
13158
- adapter: this.options.store,
13159
- name,
13160
- noydb: this,
13161
- keyring,
13162
- encrypted: true,
13163
- historyConfig: this.options.history,
13164
- ...this.options.blobStrategy !== void 0 ? { blobStrategy: this.options.blobStrategy } : {},
13165
- ...this.options.objectStore !== void 0 ? { objectStore: this.options.objectStore } : {},
13166
- ...this.options.archiveStrategy !== void 0 ? { archiveStrategy: this.options.archiveStrategy } : {},
13167
- ...this.options.indexStrategy !== void 0 ? { indexStrategy: this.options.indexStrategy } : {},
13168
- ...this.options.lazyStrategy !== void 0 ? { lazyStrategy: this.options.lazyStrategy } : {},
13169
- ...this.options.aggregateStrategy !== void 0 ? { aggregateStrategy: this.options.aggregateStrategy } : {},
13170
- ...this.options.crdtStrategy !== void 0 ? { crdtStrategy: this.options.crdtStrategy } : {},
13171
- ...this.options.tiersStrategy !== void 0 ? { tiersStrategy: this.options.tiersStrategy } : {},
13172
- ...this.options.searchStrategy !== void 0 ? { searchStrategy: this.options.searchStrategy } : {},
13173
- ...this.options.cargoStrategy !== void 0 ? { cargoStrategy: this.options.cargoStrategy } : {},
13174
- ...this.options.brokerStrategy !== void 0 ? { brokerStrategy: this.options.brokerStrategy } : {},
13175
- ...this.options.consentStrategy !== void 0 ? { consentStrategy: this.options.consentStrategy } : {},
13176
- ...this.options.periodsStrategy !== void 0 ? { periodsStrategy: this.options.periodsStrategy } : {},
13177
- ...this.options.shadowStrategy !== void 0 ? { shadowStrategy: this.options.shadowStrategy } : {},
13178
- ...this.options.historyStrategy !== void 0 ? { historyStrategy: this.options.historyStrategy } : {},
13179
- ...this.options.i18nStrategy !== void 0 ? { i18nStrategy: this.options.i18nStrategy } : {},
13180
- ...this.options.syncStrategy !== void 0 ? { syncStrategy: this.options.syncStrategy } : {},
13181
- ...this.options.guardStrategies !== void 0 ? { guardStrategies: this.options.guardStrategies } : {},
13182
- ...this.options.numbering !== void 0 ? { numberingConfigs: this.options.numbering } : {},
13183
- emitter: this.emitter
13184
- });
13185
- this.vaultCache.set(name, comp);
13186
- return comp;
13085
+ throw new ValidationError(
13086
+ `Vault "${name}" is not open. Call \`await db.openVault("${name}")\` first \u2014 vault() only returns an already-open vault, because opening involves async work (registry init, schema fence) that a sync accessor cannot do.`
13087
+ );
13187
13088
  }
13188
13089
  /**
13189
13090
  * Grant access to a user for a vault.
@@ -13197,7 +13098,7 @@ var Noydb = class {
13197
13098
  * Opt-in (#267): throws {@link TeamNotEnabledError} without `withTeam()`.
13198
13099
  */
13199
13100
  async grant(vault, options, factors) {
13200
- return this.teamStrategy.grant(this.team, vault, options, factors);
13101
+ return this.strategies.team.grant(this.team, vault, options, factors);
13201
13102
  }
13202
13103
  /**
13203
13104
  * Revoke a user's access to a vault.
@@ -13210,7 +13111,7 @@ var Noydb = class {
13210
13111
  * Opt-in (#267): throws {@link TeamNotEnabledError} without `withTeam()`.
13211
13112
  */
13212
13113
  async revoke(vault, options, factors) {
13213
- return this.teamStrategy.revoke(this.team, vault, options, factors);
13114
+ return this.strategies.team.revoke(this.team, vault, options, factors);
13214
13115
  }
13215
13116
  /**
13216
13117
  * Grant the FR-6 `custodian` role to a user (owner-only custody API).
@@ -13223,7 +13124,7 @@ var Noydb = class {
13223
13124
  * owner-only invariant even if a host mis-configures the gate.
13224
13125
  */
13225
13126
  async grantCustodian(vault, options, factors) {
13226
- return this.custodyStrategy.grantCustodian(this, vault, options, factors);
13127
+ return this.strategies.custody.grantCustodian(this, vault, options, factors);
13227
13128
  }
13228
13129
  /** @internal — grant-custodian engine, reached only via withCustody(); the
13229
13130
  * keyring engine arrives as an argument so the floor never carries it (#267). */
@@ -13242,7 +13143,7 @@ var Noydb = class {
13242
13143
  * 'owner'` check, so an admin cannot unwind a custodianship.
13243
13144
  */
13244
13145
  async revokeCustodian(vault, options, factors) {
13245
- return this.custodyStrategy.revokeCustodian(this, vault, options, factors);
13146
+ return this.strategies.custody.revokeCustodian(this, vault, options, factors);
13246
13147
  }
13247
13148
  /** @internal — revoke-custodian engine, reached only via withCustody().
13248
13149
  * Mirrors `_grantCustodianImpl` (#267: engine passed in). */
@@ -13321,7 +13222,7 @@ var Noydb = class {
13321
13222
  * Opt-in (#267): throws {@link TeamNotEnabledError} without `withTeam()`.
13322
13223
  */
13323
13224
  async rotate(vault, collections) {
13324
- return this.teamStrategy.rotate(this.team, vault, collections);
13225
+ return this.strategies.team.rotate(this.team, vault, collections);
13325
13226
  }
13326
13227
  /** List all users with access to a vault. */
13327
13228
  async listUsers(vault) {
@@ -13611,7 +13512,7 @@ var Noydb = class {
13611
13512
  }
13612
13513
  transaction(arg, maybeFn) {
13613
13514
  if (typeof arg === "function") {
13614
- return this.txStrategy.runTransaction(this, arg);
13515
+ return this.strategies.tx.runTransaction(this, arg);
13615
13516
  }
13616
13517
  if (typeof arg === "object" && arg !== null && arg.dryRun === true) {
13617
13518
  if (typeof maybeFn !== "function") {
@@ -13619,7 +13520,7 @@ var Noydb = class {
13619
13520
  "db.transaction({ dryRun: true }, fn) requires the callback as the second argument."
13620
13521
  );
13621
13522
  }
13622
- return this.txStrategy.runDryRun(this, maybeFn);
13523
+ return this.strategies.tx.runDryRun(this, maybeFn);
13623
13524
  }
13624
13525
  if (typeof arg === "object" && arg !== null && arg.amendment === true) {
13625
13526
  if (typeof maybeFn !== "function") {
@@ -13627,7 +13528,7 @@ var Noydb = class {
13627
13528
  "db.transaction({ amendment: true }, fn) requires the callback as the second argument."
13628
13529
  );
13629
13530
  }
13630
- return this.txStrategy.runTransaction(this, maybeFn, arg);
13531
+ return this.strategies.tx.runTransaction(this, maybeFn, arg);
13631
13532
  }
13632
13533
  const vault = arg;
13633
13534
  const comp = this.vaultCache.get(vault);
@@ -13637,7 +13538,7 @@ var Noydb = class {
13637
13538
  );
13638
13539
  }
13639
13540
  const engine = this.getSyncEngine(vault);
13640
- return this.syncStrategy.buildSyncTransaction(comp, engine);
13541
+ return this.strategies.sync.buildSyncTransaction(comp, engine);
13641
13542
  }
13642
13543
  /**
13643
13544
  * Internal accessor for the primary store — used by the tx
@@ -13918,7 +13819,7 @@ var Noydb = class {
13918
13819
  enforcer.destroy();
13919
13820
  }
13920
13821
  this.policyEnforcers.clear();
13921
- this.sessionStrategy.revokeAllSessions();
13822
+ this.strategies.session.revokeAllSessions();
13922
13823
  for (const engine of this.syncEngines.values()) {
13923
13824
  engine.stopAutoSync();
13924
13825
  }
@@ -14419,4 +14320,4 @@ export {
14419
14320
  Noydb,
14420
14321
  createNoydb
14421
14322
  };
14422
- //# sourceMappingURL=chunk-VZELV7ZJ.js.map
14323
+ //# sourceMappingURL=chunk-W7TCXZWU.js.map