@noy-db/hub 0.7.0-pre.11 → 0.7.0-pre.13

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 (98) hide show
  1. package/CHANGELOG.md +142 -0
  2. package/dist/as/index.js +3 -2
  3. package/dist/as/index.js.map +1 -1
  4. package/dist/{backup-VASRVLDZ.js → backup-R6A4VZRX.js} +4 -4
  5. package/dist/cargo/index.js +2 -2
  6. package/dist/chunk-66TQOPFM.js +37 -0
  7. package/dist/chunk-66TQOPFM.js.map +1 -0
  8. package/dist/{chunk-OEA5NVSU.js → chunk-AJQY44CE.js} +86 -6
  9. package/dist/chunk-AJQY44CE.js.map +1 -0
  10. package/dist/{chunk-S5AMPTNE.js → chunk-BMELYFHU.js} +2 -2
  11. package/dist/{chunk-T6PSWLAR.js → chunk-BUE2D7PX.js} +25 -2
  12. package/dist/chunk-BUE2D7PX.js.map +1 -0
  13. package/dist/chunk-BWXGSSFX.js +1589 -0
  14. package/dist/chunk-BWXGSSFX.js.map +1 -0
  15. package/dist/{chunk-HN4FNA4X.js → chunk-COTPDJR5.js} +8 -8
  16. package/dist/{chunk-E3EBMC4G.js → chunk-E2MYJGSG.js} +2 -2
  17. package/dist/{chunk-JJR2JIN4.js → chunk-FFBYRWDJ.js} +2 -2
  18. package/dist/{chunk-WQF3QABE.js → chunk-IHFXR2VQ.js} +9 -79
  19. package/dist/chunk-IHFXR2VQ.js.map +1 -0
  20. package/dist/chunk-MGOXJIBV.js +176 -0
  21. package/dist/chunk-MGOXJIBV.js.map +1 -0
  22. package/dist/chunk-PDD75NUZ.js +87 -0
  23. package/dist/chunk-PDD75NUZ.js.map +1 -0
  24. package/dist/chunk-RTBJ5IOO.js +87 -0
  25. package/dist/chunk-RTBJ5IOO.js.map +1 -0
  26. package/dist/chunk-RXLYKVTF.js +17 -0
  27. package/dist/chunk-RXLYKVTF.js.map +1 -0
  28. package/dist/{chunk-ESVREVWN.js → chunk-SXCYYMVS.js} +2 -2
  29. package/dist/chunk-TFMM5DBB.js +20 -0
  30. package/dist/chunk-TFMM5DBB.js.map +1 -0
  31. package/dist/chunk-TPHAGLUH.js +30 -0
  32. package/dist/chunk-TPHAGLUH.js.map +1 -0
  33. package/dist/{chunk-THNJ3IKU.js → chunk-ULUMAZ5S.js} +2 -2
  34. package/dist/{chunk-NO272T7Q.js → chunk-V4PXLYWM.js} +2 -31
  35. package/dist/chunk-V4PXLYWM.js.map +1 -0
  36. package/dist/{chunk-A5WZEPXJ.js → chunk-VGICZPVE.js} +5 -3
  37. package/dist/{chunk-A5WZEPXJ.js.map → chunk-VGICZPVE.js.map} +1 -1
  38. package/dist/{chunk-KHKKD62N.js → chunk-VYYM3MEN.js} +2 -16
  39. package/dist/chunk-VYYM3MEN.js.map +1 -0
  40. package/dist/{chunk-SWVNZQVR.js → chunk-YPXNAP72.js} +2 -169
  41. package/dist/chunk-YPXNAP72.js.map +1 -0
  42. package/dist/{chunk-I6DGKPTY.js → chunk-ZVCC36XQ.js} +101 -1273
  43. package/dist/chunk-ZVCC36XQ.js.map +1 -0
  44. package/dist/{collection-facade-MGGHQ3HU.js → collection-facade-4ACKIDDY.js} +4 -3
  45. package/dist/derivations/index.js +21 -6
  46. package/dist/dispatch-MSTR5VBI.js +255 -0
  47. package/dist/dispatch-MSTR5VBI.js.map +1 -0
  48. package/dist/{dispatch-44J4TL72.js → dispatch-PDP5TA73.js} +5 -5
  49. package/dist/{executor-KSPGNVDO.js → executor-YDDC5XGY.js} +4 -3
  50. package/dist/index.d.ts +2 -2
  51. package/dist/index.js +39 -35
  52. package/dist/index.js.map +1 -1
  53. package/dist/introspection/index.js +3 -2
  54. package/dist/kernel/collection.d.ts +11 -6
  55. package/dist/kernel/match-pairs.d.ts +30 -0
  56. package/dist/lazy/index.js +3 -2
  57. package/dist/materialized-views/index.js +4 -3
  58. package/dist/{noydb-QJFZAH34.js → noydb-2C6QQJ5S.js} +17 -12
  59. package/dist/pod/index.js +17 -12
  60. package/dist/registry-D2YXKRYC.js +33 -0
  61. package/dist/search/index.js +1 -1
  62. package/dist/{stale-KSPG6LFX.js → stale-2CHKZTMK.js} +2 -2
  63. package/dist/{walk-G3CZIL4L.js → walk-P3E6QSNC.js} +4 -3
  64. package/dist/with-formula/derivations/dispatch.d.ts +16 -1
  65. package/dist/with-formula/derivations/registry.d.ts +20 -6
  66. package/dist/with-formula/derivations/trigger-match.d.ts +52 -0
  67. package/dist/with-formula/derivations/types.d.ts +23 -0
  68. package/dist/with-shape/introspection/behaviors.d.ts +7 -0
  69. package/dist/with-shape/introspection/describe.d.ts +30 -0
  70. package/package.json +3 -3
  71. package/dist/chunk-I6DGKPTY.js.map +0 -1
  72. package/dist/chunk-KHKKD62N.js.map +0 -1
  73. package/dist/chunk-NO272T7Q.js.map +0 -1
  74. package/dist/chunk-OEA5NVSU.js.map +0 -1
  75. package/dist/chunk-SWVNZQVR.js.map +0 -1
  76. package/dist/chunk-T6PSWLAR.js.map +0 -1
  77. package/dist/chunk-UB5CRTFF.js +0 -344
  78. package/dist/chunk-UB5CRTFF.js.map +0 -1
  79. package/dist/chunk-WQF3QABE.js.map +0 -1
  80. package/dist/chunk-YPMW26SL.js +0 -81
  81. package/dist/chunk-YPMW26SL.js.map +0 -1
  82. package/dist/dispatch-IM52HDXT.js +0 -194
  83. package/dist/dispatch-IM52HDXT.js.map +0 -1
  84. package/dist/registry-FZXPQRAB.js +0 -18
  85. /package/dist/{backup-VASRVLDZ.js.map → backup-R6A4VZRX.js.map} +0 -0
  86. /package/dist/{chunk-S5AMPTNE.js.map → chunk-BMELYFHU.js.map} +0 -0
  87. /package/dist/{chunk-HN4FNA4X.js.map → chunk-COTPDJR5.js.map} +0 -0
  88. /package/dist/{chunk-E3EBMC4G.js.map → chunk-E2MYJGSG.js.map} +0 -0
  89. /package/dist/{chunk-JJR2JIN4.js.map → chunk-FFBYRWDJ.js.map} +0 -0
  90. /package/dist/{chunk-ESVREVWN.js.map → chunk-SXCYYMVS.js.map} +0 -0
  91. /package/dist/{chunk-THNJ3IKU.js.map → chunk-ULUMAZ5S.js.map} +0 -0
  92. /package/dist/{collection-facade-MGGHQ3HU.js.map → collection-facade-4ACKIDDY.js.map} +0 -0
  93. /package/dist/{dispatch-44J4TL72.js.map → dispatch-PDP5TA73.js.map} +0 -0
  94. /package/dist/{executor-KSPGNVDO.js.map → executor-YDDC5XGY.js.map} +0 -0
  95. /package/dist/{noydb-QJFZAH34.js.map → noydb-2C6QQJ5S.js.map} +0 -0
  96. /package/dist/{registry-FZXPQRAB.js.map → registry-D2YXKRYC.js.map} +0 -0
  97. /package/dist/{stale-KSPG6LFX.js.map → stale-2CHKZTMK.js.map} +0 -0
  98. /package/dist/{walk-G3CZIL4L.js.map → walk-P3E6QSNC.js.map} +0 -0
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  resolveStaleOnRead
3
- } from "./chunk-YPMW26SL.js";
3
+ } from "./chunk-PDD75NUZ.js";
4
4
  import {
5
5
  isManifestReservedCollection
6
6
  } from "./chunk-EPFF7LOX.js";
@@ -19,11 +19,9 @@ import {
19
19
  loadFence
20
20
  } from "./chunk-GM4IM7TC.js";
21
21
  import {
22
- VectorSet,
23
22
  buildPersistedIndexCallbacks,
24
- encodeVecId,
25
23
  syncTierSearch
26
- } from "./chunk-WQF3QABE.js";
24
+ } from "./chunk-IHFXR2VQ.js";
27
25
  import {
28
26
  isBaseLive,
29
27
  liveBaseIdSet
@@ -34,11 +32,6 @@ import {
34
32
  import {
35
33
  NO_ATTESTATION
36
34
  } from "./chunk-Q7HZX3IF.js";
37
- import {
38
- NO_CLASSIFIED,
39
- guardClassifiedCompat,
40
- resolveClassifiedFields
41
- } from "./chunk-KCUIMITG.js";
42
35
  import {
43
36
  NO_TIERS,
44
37
  classifySealedShred,
@@ -57,16 +50,8 @@ import {
57
50
  runDrainBarrier
58
51
  } from "./chunk-VTFVLCUH.js";
59
52
  import {
60
- fallbackDerivationName,
61
- jsonSchemaToFields
62
- } from "./chunk-KHKKD62N.js";
63
- import {
64
- derivePersistedSchema,
65
- isZod4Schema
66
- } from "./chunk-URHRIAV6.js";
67
- import {
68
- loadPersistedSchema
69
- } from "./chunk-WWNYSV7S.js";
53
+ fallbackDerivationName
54
+ } from "./chunk-RXLYKVTF.js";
70
55
  import {
71
56
  NO_SEQUENCE,
72
57
  SEQUENCE_COLLECTION,
@@ -79,8 +64,45 @@ import {
79
64
  } from "./chunk-Y4MJQ2UG.js";
80
65
  import {
81
66
  DEFAULT_POSTURE,
82
- ViaGraph
83
- } from "./chunk-UB5CRTFF.js";
67
+ EXPORT_REDACTION_MARKER,
68
+ ViaGraph,
69
+ ViaPipeline,
70
+ buildDescription,
71
+ collectKnownFieldNames,
72
+ computedEntryParts,
73
+ deriveZodFields,
74
+ exportRedact,
75
+ guardReconcileCollisions,
76
+ mergeViaFields,
77
+ resolveCollectionConfig,
78
+ resolveComputedEdges,
79
+ resolveDescribeFieldIds,
80
+ resolveVirtualMoneyFields
81
+ } from "./chunk-BWXGSSFX.js";
82
+ import {
83
+ findMatchingIdsByPairs
84
+ } from "./chunk-TFMM5DBB.js";
85
+ import {
86
+ encodeVecId
87
+ } from "./chunk-RTBJ5IOO.js";
88
+ import {
89
+ NO_CLASSIFIED,
90
+ guardClassifiedCompat,
91
+ resolveClassifiedFields
92
+ } from "./chunk-KCUIMITG.js";
93
+ import {
94
+ derivePersistedSchema
95
+ } from "./chunk-URHRIAV6.js";
96
+ import {
97
+ findRollupSpecForIntent,
98
+ forgetDerivedFanout,
99
+ ledgerAuditHook,
100
+ putDerivedOutput,
101
+ resolveRollupDeleteIntents,
102
+ runGraphDispatchWave,
103
+ selfWriteFieldEqual,
104
+ touchFor
105
+ } from "./chunk-MGOXJIBV.js";
84
106
  import {
85
107
  buildLookupAltIndex,
86
108
  buildLookupSnapshotRows,
@@ -89,22 +111,13 @@ import {
89
111
  coerceLookupKey,
90
112
  collectLookupDictCompat,
91
113
  enforceStaticDictOnPut,
92
- findRollupSpecForIntent,
93
- forgetDerivedFanout,
94
- isLookupDescriptor,
95
- ledgerAuditHook,
96
114
  matchesReferencingValue,
97
- putDerivedOutput,
98
115
  registerLookupRefEdges,
99
116
  reservedDictDepsOf,
100
117
  resolveDictSource,
101
118
  resolveLabelFromMap,
102
- resolveRollupDeleteIntents,
103
- runGraphDispatchWave,
104
- selfWriteFieldEqual,
105
- touchFor,
106
119
  updateReferencingRecords
107
- } from "./chunk-SWVNZQVR.js";
120
+ } from "./chunk-YPXNAP72.js";
108
121
  import {
109
122
  OverlayedCollection
110
123
  } from "./chunk-SKGMN35F.js";
@@ -148,7 +161,7 @@ import {
148
161
  InvertedIndex,
149
162
  MemoryIndexStore,
150
163
  NO_SEARCH
151
- } from "./chunk-HN4FNA4X.js";
164
+ } from "./chunk-COTPDJR5.js";
152
165
  import {
153
166
  NO_SEALED_RECORD
154
167
  } from "./chunk-6YXCPL4T.js";
@@ -172,10 +185,11 @@ import {
172
185
  normalizeIndexDefs
173
186
  } from "./chunk-I3ISCYT7.js";
174
187
  import {
175
- IMPLICIT_LAZY,
176
- Lru,
188
+ IMPLICIT_LAZY
189
+ } from "./chunk-66TQOPFM.js";
190
+ import {
177
191
  estimateRecordBytes
178
- } from "./chunk-NO272T7Q.js";
192
+ } from "./chunk-V4PXLYWM.js";
179
193
  import {
180
194
  canonicalGroupKey
181
195
  } from "./chunk-XZHEOSQ2.js";
@@ -195,8 +209,6 @@ import {
195
209
  NO_I18N,
196
210
  getAtPath,
197
211
  isDictCollectionName,
198
- isDictKeyDescriptor,
199
- isI18nTextDescriptor,
200
212
  isStaticDictDescriptor
201
213
  } from "./chunk-TRGTKX5E.js";
202
214
  import {
@@ -320,7 +332,6 @@ import {
320
332
  EchoCeremonyRequiredError,
321
333
  ElevationExpiredError,
322
334
  ExportCapabilityError,
323
- FieldNotQueryableError,
324
335
  ForgetStrategyNotConfiguredError,
325
336
  ImportCapabilityError,
326
337
  IndexWriteFailureError,
@@ -347,7 +358,6 @@ import {
347
358
  USER_ENVELOPE_COLLECTION,
348
359
  UniqueConstraintError,
349
360
  UnsupportedIndexOptionError,
350
- UnsupportedTierCompositionError,
351
361
  ValidationError,
352
362
  isConflictError
353
363
  } from "./chunk-7O3Y4PPD.js";
@@ -603,370 +613,6 @@ function populateCollectionRegistries(r, collectionName, options, effectiveViaFi
603
613
  }
604
614
  }
605
615
 
606
- // src/kernel/via/pipeline.ts
607
- var ViaPipeline = class _ViaPipeline {
608
- constructor(bindings, taint) {
609
- this.bindings = bindings;
610
- this.taint = taint;
611
- const moneyVias = bindings.filter((b) => b.brand === "money");
612
- const computedVias = bindings.filter((b) => b.brand === "computed");
613
- const restBindings = bindings.filter((b) => b.brand !== "money" && b.brand !== "computed");
614
- this._presentOrder = [...moneyVias, ...computedVias, ...restBindings];
615
- this._presentLateBoundary = moneyVias.length + computedVias.length;
616
- }
617
- bindings;
618
- taint;
619
- /**
620
- * Present-phase-ONLY order (#665, corrected under #665 review) — a stable
621
- * THREE-WAY partition of `bindings` computed once here at construction:
622
- * every `'money'`-brand binding first (existing relative order), then
623
- * every `'computed'`-brand binding (existing relative order), then
624
- * everything else (existing relative order). `present()` folds over this
625
- * instead of `bindings` so a `mode: 'virtual'` computed field's value
626
- * exists before i18n/lookup's dressing `present()` hooks run on it
627
- * (today's `compileVias` order is money→i18n→lookup→classified→
628
- * blob→computed, `collection-config.ts:554`, so dressing unconditionally
629
- * ran BEFORE the value it dresses existed). This reorder is binding-level,
630
- * not conditioned on any particular field composition — it applies to
631
- * EVERY collection that compiles a `'computed'` binding at all, not only
632
- * ones stacking a dresser on the SAME field as a computed one. EVERY other
633
- * phase (`ingest`/`encodeWrite`/`encodeAtRest`/`enforceWrite`/etc.) keeps
634
- * folding over `bindings` unchanged — those write/query phases need
635
- * money-first (`_applyMoneyFields` PREPENDS money, `_applyClassifiedFields`
636
- * APPENDS classified to preserve it, `kernel/collection.ts:1275-1284,1364-
637
- * 1368`).
638
- *
639
- * Money is deliberately carved OUT of the generic "everything else" slice
640
- * and kept FIRST — the original two-way `[computed..., rest...]` partition
641
- * regressed money's composed-field behavior from a benign missing-dressing
642
- * gap into VALUE CORRUPTION: money's `present()` DECODES its input as a
643
- * STORED SCALED-INT (and derives `<field>Formatted` from it) — it is not a
644
- * pure dressing CONSUMER like i18n/lookup, which only ADD a `<field>Label`
645
- * key and never rewrite the field's own value. Running money AFTER
646
- * computed on a field composing `computed(virtual)` + `money(...)` on
647
- * ITSELF means money's decode sees the virtual field's raw MAJOR-unit
648
- * output (e.g. `21`) and misreads it as a scaled-int, producing a
649
- * corrupted value (`'0.21'`) instead of the pre-#665 result (the raw `21`
650
- * surviving untouched, with no `Formatted` key added — a benign
651
- * missing-dressing gap, not a wrong value). Money-first restores money's
652
- * exact pre-#665 present position — byte-identical money behavior; the
653
- * materialized composition pins from the #631 work and the money suites
654
- * all ran under money-first. i18n/lookup are genuine dressing consumers
655
- * (they only ADD, never rewrite), so they stay after computed, which is
656
- * the fix #665 intended. A `'taint'` binding (appended LAST by
657
- * `via/graph-wiring.ts#applyTaintOverlay`) stays last here too (it's
658
- * neither money nor computed, so it lands at the tail of the "everything
659
- * else" slice), preserving its "runs after computed, redacts tainted
660
- * virtual output" contract (`via/taint-binding.ts`'s `taintBinding` doc
661
- * comment).
662
- *
663
- * Money-dressing a virtual computed field's OWN output (the composed
664
- * `computed(virtual) + money` case) is NOT resolved by this partition
665
- * alone — #669 resolves it with a separate mid-fold hook instead:
666
- * `NoydbVia.presentLate` runs after every binding's `present()` in this
667
- * money+computed segment, before the "everything else" segment below, so
668
- * money can quantize/reinterpret a virtual field's fresh MAJOR-UNITS
669
- * output as if it were a stored value, without perturbing this ordering
670
- * or the #665 invariant above it (`_presentLateBoundary` below marks
671
- * exactly where that mid-fold point sits within `_presentOrder`).
672
- */
673
- _presentOrder;
674
- /** #669 — index into `_presentOrder` where the money+computed segment ends and the
675
- * "everything else" segment begins (`moneyVias.length + computedVias.length`);
676
- * `present()` runs every binding's `presentLate` at exactly this boundary. */
677
- _presentLateBoundary;
678
- /** undefined when there is nothing to enforce — the zero-via fast path is
679
- * `this.via === undefined` (#553: keeps an all-plain collection sync). A
680
- * `'*'`-defaulted output collection (#642) counts as "something to enforce"
681
- * even with zero field-specific postures and zero bindings. */
682
- static build(bindings, taint) {
683
- if (bindings.length === 0 && (!taint || taint.postures.size === 0 && taint.defaultPosture === void 0)) return void 0;
684
- return new _ViaPipeline(bindings, taint);
685
- }
686
- /** Refuse a write before crypto runs: awaits each binding's `enforceWrite` in order — first throw wins. */
687
- async enforceWrite(record, ctx) {
688
- for (const b of this.bindings) if (b.enforceWrite) await b.enforceWrite(record, ctx);
689
- }
690
- ingest(record) {
691
- let r = record;
692
- for (const b of this.bindings) if (b.ingest) r = b.ingest(r);
693
- return r;
694
- }
695
- canonicalizeStored(record) {
696
- let r = record;
697
- for (const b of this.bindings) if (b.canonicalizeStored) r = b.canonicalizeStored(r);
698
- return r;
699
- }
700
- async encodeWrite(record, ctx) {
701
- let r = record;
702
- for (const b of this.bindings) if (b.encodeWrite) r = await b.encodeWrite(r, ctx);
703
- return r;
704
- }
705
- /**
706
- * Final write-pipeline stage: folds each binding's `encodeAtRest` in
707
- * order, threading the record and accumulating the sealed-slot map. A
708
- * field belongs to exactly one via feature — two bindings sealing the
709
- * same field is a brand-keyed collision and throws.
710
- */
711
- async encodeAtRest(record, crypto) {
712
- let r = record;
713
- let sealed;
714
- const sealedBy = /* @__PURE__ */ new Map();
715
- for (const b of this.bindings) {
716
- if (!b.encodeAtRest) continue;
717
- const result = await b.encodeAtRest(r, crypto);
718
- r = result.record;
719
- if (!result.sealed) continue;
720
- for (const [field, ref2] of Object.entries(result.sealed)) {
721
- const priorBrand = sealedBy.get(field);
722
- if (priorBrand !== void 0) {
723
- throw new ValidationError(
724
- `via encodeAtRest: field "${field}" sealed by binding "${priorBrand}" is also sealed by binding "${b.brand}"`
725
- );
726
- }
727
- sealedBy.set(field, b.brand);
728
- sealed ??= {};
729
- sealed[field] = ref2;
730
- }
731
- }
732
- return sealed ? { record: r, sealed } : { record: r };
733
- }
734
- /** First read-pipeline stage: folds each binding's `decodeAtRest` in order. */
735
- async decodeAtRest(record, sealed, crypto, opts) {
736
- let r = record;
737
- for (const b of this.bindings) if (b.decodeAtRest) r = await b.decodeAtRest(r, sealed, crypto, opts);
738
- return r;
739
- }
740
- async present(record, ctx) {
741
- let r = record;
742
- for (let i = 0; i < this._presentLateBoundary; i++) {
743
- const b = this._presentOrder[i];
744
- if (b.present) r = await b.present(r, ctx);
745
- }
746
- for (const b of this.bindings) if (b.presentLate) r = await b.presentLate(r, ctx);
747
- for (let i = this._presentLateBoundary; i < this._presentOrder.length; i++) {
748
- const b = this._presentOrder[i];
749
- if (b.present) r = await b.present(r, ctx);
750
- }
751
- return r;
752
- }
753
- buildClause(field, op, value) {
754
- for (const b of this.bindings) {
755
- const payload = b.buildClause?.(field, op, value);
756
- if (payload !== void 0) return { brand: b.brand, payload };
757
- }
758
- return void 0;
759
- }
760
- evaluateClause(clause, actual, op) {
761
- const b = this.bindings.find((x) => x.brand === clause.brand);
762
- return b?.evaluateClause ? b.evaluateClause(actual, op, clause.payload) : false;
763
- }
764
- /**
765
- * Resolve a `buildClause` payload to an index-probe operand (#625) —
766
- * mirrors `evaluateClause`'s brand dispatch. `undefined` (a binding
767
- * with no `indexProbe`, or one that declines to probe this op/payload)
768
- * means the caller (`candidateRecords()`) must fall back to a scan.
769
- */
770
- // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
771
- indexProbe(clause, op) {
772
- const b = this.bindings.find((x) => x.brand === clause.brand);
773
- return b?.indexProbe ? b.indexProbe(op, clause.payload) : void 0;
774
- }
775
- /**
776
- * Resolve a raw STORED field value to its canonical eager-index bucket
777
- * key (#672) — folds every binding's `canonicalizeIndexKey`, first
778
- * non-undefined wins (same fold discipline as `buildClause`). `undefined`
779
- * means no binding claims `field` (or the value can't be canonicalized)
780
- * — the caller buckets the raw stringified value, unchanged from before
781
- * this hook existed.
782
- */
783
- canonicalizeIndexKey(field, rawValue) {
784
- for (const b of this.bindings) {
785
- const key = b.canonicalizeIndexKey?.(field, rawValue);
786
- if (key !== void 0) return key;
787
- }
788
- return void 0;
789
- }
790
- decodeResults(record) {
791
- let r = record;
792
- for (const b of this.bindings) if (b.decodeResults) r = b.decodeResults(r);
793
- return r;
794
- }
795
- /** undefined = no binding covers the field → caller falls back to generic compare. */
796
- compareForOrder(field, a, b) {
797
- for (const bind of this.bindings) {
798
- const c = bind.compareForOrder?.(field, a, b);
799
- if (c !== void 0) return c;
800
- }
801
- return void 0;
802
- }
803
- /** undefined = no binding resolves an order-sort label for `key` at `field`/`locale` (#650 Task 7 —
804
- * the `orderBy(..., {by:'label'})` per-call-locale channel `compareForOrder` above can't serve). */
805
- resolveOrderLabel(field, key, locale) {
806
- for (const bind of this.bindings) {
807
- const label = bind.resolveOrderLabel?.(field, key, locale);
808
- if (label !== void 0) return label;
809
- }
810
- return void 0;
811
- }
812
- /**
813
- * Which posture governs `field`, if any binding covers it — `undefined`
814
- * means no binding declares `field` (the generic, non-via query path
815
- * applies). #629 Task 8's posture consumer: `.where()`/`.orderBy()`
816
- * consult this before building/evaluating a clause, refusing fields whose
817
- * posture is `queryable: 'none'` (e.g. blob); every other posture
818
- * ('det-exact'/'ordered'/'full') is left to the existing per-binding
819
- * buildClause/evaluateClause/compareForOrder machinery, unchanged.
820
- *
821
- * #638 Task 3: the graph-computed taint overlay is consulted FIRST — a
822
- * derived field's assigned (most-restrictive-of-sources) posture wins over
823
- * whatever a binding would otherwise report for that field name (no
824
- * binding covers a computed/derived field today, so this never actually
825
- * shadows one — see `via/taint-binding.ts`'s `taintBinding.covers`, which
826
- * only claims the sealed subset for `encodeAtRest`/`decodeAtRest`, not for
827
- * this lookup).
828
- */
829
- postureFor(field) {
830
- const t = this.taint?.postures.get(field);
831
- if (t) return t;
832
- for (const b of this.bindings) {
833
- if (b.covers?.(field)) return b.posture;
834
- }
835
- return this.taint?.defaultPosture;
836
- }
837
- /**
838
- * Refuse any field-based reducer over a `queryable: 'none'` field (blob) —
839
- * metadata-only: walks each reducer's `.field` and checks posture, no
840
- * rewriting. Reducers with no `.field` (e.g. `count()`) are skipped.
841
- *
842
- * Shared by `wrapReducers` below (which ALSO applies each binding's
843
- * rewrite, e.g. money's exact-BigInt reducer swap) and by
844
- * `ScanBuilder.aggregate()` (#629 Task 8 review fix wave 1), which must
845
- * call THIS, not `wrapReducers` — wiring full `wrapReducers` into
846
- * `ScanBuilder.aggregate()` would newly activate money/i18n reducer
847
- * wrapping on a path that has never run it, a parity-breaking behavior
848
- * change for existing brands.
849
- */
850
- refuseUnqueryableReducers(spec) {
851
- for (const reducer of Object.values(spec)) {
852
- const field = reducer?.field;
853
- if (field !== void 0 && this.postureFor(field)?.queryable === "none") {
854
- throw new FieldNotQueryableError(field);
855
- }
856
- }
857
- }
858
- /**
859
- * Rewrite an aggregate spec (money exact reducers), then refuse any
860
- * field-based reducer over a `queryable: 'none'` field (blob) — the same
861
- * posture gate `.where()`/`.orderBy()` apply, extended to `.aggregate()`
862
- * (both the bare-spec and builder forms funnel through here).
863
- */
864
- wrapReducers(spec) {
865
- this.refuseUnqueryableReducers(spec);
866
- let s = spec;
867
- for (const b of this.bindings) if (b.wrapReducers) s = b.wrapReducers(s);
868
- return s;
869
- }
870
- /**
871
- * Deliberate export-layer redaction (#629 Task 9's posture consumer):
872
- * for every OWN field the decoded record carries, if the covering
873
- * binding's posture declares `exportable: false` (classified today;
874
- * money/i18n/blob are all `true`), replace the value with
875
- * {@link EXPORT_REDACTION_MARKER} — the same `'[sealed]'` string
876
- * `SealedHandle.toJSON()` (`kernel/types.ts`) already emits, so this is
877
- * byte-parity with today's accidental redaction, now independent of the
878
- * field's runtime shape. `SealedHandle.toJSON()` stays untouched as
879
- * defense-in-depth: a record read via `collection.get()` and
880
- * JSON.stringify'd outside `exportStream()` still gets the accident's
881
- * redaction on its own. Never mutates the input; returns a new object
882
- * only when a field was actually redacted.
883
- */
884
- redactForExport(record) {
885
- let out;
886
- for (const field of Object.keys(record)) {
887
- if (this.postureFor(field)?.exportable === false) {
888
- out ??= { ...record };
889
- out[field] = EXPORT_REDACTION_MARKER;
890
- }
891
- }
892
- return out ?? record;
893
- }
894
- /**
895
- * Shared fold over a subset of `this.bindings`: runs each binding's
896
- * `erase`, concatenating shredded/retainedShared counts and residue.
897
- * Consults `posture.forgettable` (#629 Task 10) — a binding declaring
898
- * `forgettable: false` is skipped even if it defines `erase` (none does
899
- * today; a future non-forgettable binding is supported without a brand check).
900
- */
901
- async foldErase(ctx, bindings) {
902
- let shredded = 0;
903
- let retainedShared = 0;
904
- const residue = [];
905
- for (const b of bindings) {
906
- if (!b.erase || b.posture.forgettable === false) continue;
907
- const report = await b.erase(ctx);
908
- shredded += report.shredded;
909
- retainedShared += report.retainedShared ?? 0;
910
- residue.push(...report.residue);
911
- }
912
- return retainedShared > 0 ? { shredded, residue, retainedShared } : { shredded, residue };
913
- }
914
- /** `forget()`'s per-ref erasure door: folds every binding's `erase`. */
915
- async erase(ctx) {
916
- return this.foldErase(ctx, this.bindings);
917
- }
918
- /**
919
- * `forget()`'s SEALED-posture-only erase fold (#629 Task 10, `Collection._onViaErase`'s
920
- * one caller) — classified today; metadata-filtered on
921
- * `posture.encryptedAtRest`, not brand-checked. `undefined` when no
922
- * sealed-posture binding is compiled in — `vault.ts` then falls back to
923
- * its own bare-`sensitive` classification, which no via binding covers.
924
- */
925
- async eraseSealed(ctx) {
926
- const sealed = this.bindings.filter((b) => b.posture.encryptedAtRest === "sealed");
927
- return sealed.length === 0 ? void 0 : this.foldErase(ctx, sealed);
928
- }
929
- /** True iff any binding implements decodeResults (query fast-path gating). */
930
- get hasResultDecode() {
931
- return this.bindings.some((b) => b.decodeResults !== void 0);
932
- }
933
- /**
934
- * True iff any binding declares an at-rest hook (`encodeAtRest`/
935
- * `decodeAtRest`) — the codec boundary's async-stack gate (#629 Task 3
936
- * consults this to choose between the hook path and today's inline
937
- * sealed-slot path; a stack with only sync hooks, e.g. money-only, stays
938
- * `false`).
939
- */
940
- get hasAtRestHooks() {
941
- return this.bindings.some((b) => b.encodeAtRest !== void 0 || b.decodeAtRest !== void 0);
942
- }
943
- /**
944
- * Fold every binding's `describeFragment()` into one `brand -> fragment`
945
- * map (#650 Task 7 — the first-ever consumer; `describeFragment` was
946
- * declared at `via/index.ts:136` since #623 with zero callers until this task).
947
- * `undefined` when no compiled binding implements it. Consumed by
948
- * `Collection.describe()`/`describeAsync()`, threaded to `buildDescription`
949
- * as `BuildDescriptionInput.viaFragments`.
950
- */
951
- describeFragments() {
952
- let out;
953
- for (const b of this.bindings) {
954
- const f = b.describeFragment?.();
955
- if (f !== void 0) {
956
- out ??= {};
957
- out[b.brand] = f;
958
- }
959
- }
960
- return out;
961
- }
962
- };
963
- var EXPORT_REDACTION_MARKER = "[sealed]";
964
- function exportRedact(coll, record) {
965
- if (record === null || typeof record !== "object") return record;
966
- const via2 = coll._via;
967
- return via2 ? via2.redactForExport(record) : record;
968
- }
969
-
970
616
  // src/kernel/lazy-count.ts
971
617
  var COUNT_PAGE_SIZE = 1e3;
972
618
  function isLive(env, storeCiphertext) {
@@ -1575,356 +1221,6 @@ function buildUniqueConstraintSet(collectionName, indexes, mode) {
1575
1221
  return new UniqueConstraintSet(collectionName, uniqueDefs);
1576
1222
  }
1577
1223
 
1578
- // src/with-shape/introspection/field-meta.ts
1579
- var FieldMetaUnknownFieldError = class extends Error {
1580
- constructor(collection, key) {
1581
- super(`fieldMeta for collection "${collection}" references unknown field "${key}". Declare it in the schema/config or remove the fieldMeta entry.`);
1582
- this.collection = collection;
1583
- this.key = key;
1584
- this.name = "FieldMetaUnknownFieldError";
1585
- }
1586
- collection;
1587
- key;
1588
- };
1589
- function validateFieldMetaKeys(collection, fieldMeta, knownFields) {
1590
- for (const key of Object.keys(fieldMeta)) {
1591
- if (!knownFields.has(key)) throw new FieldMetaUnknownFieldError(collection, key);
1592
- }
1593
- }
1594
- function humanizeFieldKey(key) {
1595
- return key.replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").trim().split(/\s+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
1596
- }
1597
- function resolveFieldMeta(key, inputs) {
1598
- const { channel, zodMeta, inferred } = inputs;
1599
- const pick = (k) => channel?.[k] ?? zodMeta?.[k] ?? inferred?.[k];
1600
- const description = pick("description");
1601
- const semanticType = pick("semanticType");
1602
- const unit = pick("unit");
1603
- const sensitivity = pick("sensitivity");
1604
- const aggregate = pick("aggregate");
1605
- const aliases = pick("aliases");
1606
- const displayFor = pick("displayFor");
1607
- const widget = pick("widget");
1608
- const group = pick("group");
1609
- const order = pick("order");
1610
- return {
1611
- label: pick("label") ?? humanizeFieldKey(key),
1612
- ...description !== void 0 ? { description } : {},
1613
- ...semanticType !== void 0 ? { semanticType } : {},
1614
- ...unit !== void 0 ? { unit } : {},
1615
- ...sensitivity !== void 0 ? { sensitivity } : {},
1616
- ...aggregate !== void 0 ? { aggregate } : {},
1617
- ...aliases !== void 0 ? { aliases } : {},
1618
- ...displayFor !== void 0 ? { displayFor } : {},
1619
- ...widget !== void 0 ? { widget } : {},
1620
- ...group !== void 0 ? { group } : {},
1621
- ...order !== void 0 ? { order } : {}
1622
- };
1623
- }
1624
-
1625
- // src/with-shape/introspection/describe.ts
1626
- var ZOD_META_KEYS = /* @__PURE__ */ new Set([
1627
- "label",
1628
- "description",
1629
- "unit",
1630
- "semanticType",
1631
- "sensitivity",
1632
- "aggregate",
1633
- "aliases",
1634
- "displayFor",
1635
- "group",
1636
- "order"
1637
- ]);
1638
- function schemaFieldKeys(schema) {
1639
- if (schema === null || typeof schema !== "object") return void 0;
1640
- const shape = schema.shape;
1641
- if (shape === null || typeof shape !== "object") return void 0;
1642
- const keys = Object.keys(shape);
1643
- return keys.length > 0 ? keys : void 0;
1644
- }
1645
- async function deriveZodFields(schema) {
1646
- const envelope = await derivePersistedSchema(schema);
1647
- if (!envelope.jsonSchema) return {};
1648
- const isZod4 = isZod4Schema(schema);
1649
- const jsonSchema = envelope.jsonSchema;
1650
- const fieldDescriptors = jsonSchemaToFields(jsonSchema, "live-validator");
1651
- const propertiesRaw = jsonSchema["properties"];
1652
- const result = {};
1653
- for (const [key, descriptor] of Object.entries(fieldDescriptors)) {
1654
- let meta;
1655
- if (isZod4 && propertiesRaw) {
1656
- const prop = propertiesRaw[key];
1657
- if (prop) {
1658
- const extracted = {};
1659
- let hasAny = false;
1660
- for (const mk of ZOD_META_KEYS) {
1661
- if (mk in prop) {
1662
- extracted[mk] = prop[mk];
1663
- hasAny = true;
1664
- }
1665
- }
1666
- if (hasAny) meta = extracted;
1667
- }
1668
- }
1669
- result[key] = {
1670
- type: descriptor.type,
1671
- optional: descriptor.optional === true,
1672
- ...descriptor.constraints !== void 0 ? { constraints: descriptor.constraints } : {},
1673
- ...meta !== void 0 ? { meta } : {}
1674
- };
1675
- }
1676
- return result;
1677
- }
1678
- async function resolveDescribeFieldIds(store, vaultName, collectionName, getDEK) {
1679
- let dek;
1680
- try {
1681
- dek = await getDEK(collectionName);
1682
- } catch {
1683
- return {};
1684
- }
1685
- const persisted = await loadPersistedSchema(store, vaultName, collectionName, dek);
1686
- return persisted?.fieldIds !== void 0 ? { fieldIds: persisted.fieldIds } : {};
1687
- }
1688
- function deriveWidget(opts) {
1689
- if (opts.resolvedWidget !== void 0) return opts.resolvedWidget;
1690
- switch (opts.semanticType) {
1691
- case "date":
1692
- case "datetime":
1693
- return "date";
1694
- case "currency":
1695
- return "money";
1696
- case "entity":
1697
- return "ref-select";
1698
- case "url":
1699
- return "url";
1700
- case "email":
1701
- return "email";
1702
- case "percent":
1703
- return "number";
1704
- }
1705
- if (opts.dict !== void 0 || opts.lookup !== void 0) return "select";
1706
- if (opts.blob !== void 0) return "file";
1707
- if (opts.type === "boolean") return "checkbox";
1708
- if (opts.type === "number") return "number";
1709
- return "text";
1710
- }
1711
- function buildDescription(input) {
1712
- const { collection, fieldMeta, moneyFields, dictKeyFields, lookupFields, computed, refs, zodFields, schema, dictLabels, meta, i18nFields, classified, taint, fieldIds } = input;
1713
- const lookupFragments = input.viaFragments?.["lookup"]?.lookupFields;
1714
- const blobFragments = input.viaFragments?.["blob"]?.blobFields;
1715
- const validatorKeys = zodFields !== void 0 && Object.keys(zodFields).length > 0 ? Object.keys(zodFields) : schemaFieldKeys(schema);
1716
- const canEnumerateFields = validatorKeys !== void 0;
1717
- if (canEnumerateFields && fieldMeta !== void 0) {
1718
- const knownFields = /* @__PURE__ */ new Set([
1719
- ...Object.keys(moneyFields ?? {}),
1720
- ...Object.keys(dictKeyFields ?? {}),
1721
- ...Object.keys(refs),
1722
- ...Object.keys(computed ?? {}),
1723
- ...validatorKeys ?? [],
1724
- ...Object.keys(i18nFields ?? {}),
1725
- ...Object.keys(lookupFields ?? {}),
1726
- ...Object.keys(blobFragments ?? {}),
1727
- ...Object.keys(classified ?? {})
1728
- ]);
1729
- validateFieldMetaKeys(collection, fieldMeta, knownFields);
1730
- }
1731
- const allKeys = /* @__PURE__ */ new Set([
1732
- ...Object.keys(moneyFields ?? {}),
1733
- ...Object.keys(dictKeyFields ?? {}),
1734
- ...Object.keys(lookupFields ?? {}),
1735
- ...Object.keys(refs),
1736
- ...Object.keys(computed ?? {}),
1737
- ...Object.keys(fieldMeta ?? {}),
1738
- ...Object.keys(zodFields ?? {}),
1739
- ...Object.keys(i18nFields ?? {}),
1740
- ...Object.keys(classified ?? {}),
1741
- ...Object.keys(blobFragments ?? {})
1742
- ]);
1743
- const fields = [];
1744
- for (const key of [...allKeys].sort()) {
1745
- const zod = zodFields?.[key];
1746
- const money = moneyFields?.[key];
1747
- const dict = dictKeyFields?.[key];
1748
- const lookupDesc = lookupFields?.[key];
1749
- const refDesc = refs[key];
1750
- const isComputed = computed !== void 0 && key in computed;
1751
- const i18nDesc = i18nFields?.[key];
1752
- const cls = classified?.[key];
1753
- const blobFragmentEntry = blobFragments?.[key];
1754
- const taintPosture = taint?.postures.get(key);
1755
- let type = zod?.type ?? "unknown";
1756
- const inferred = {};
1757
- let moneyBlock;
1758
- let dictBlock;
1759
- let refBlock;
1760
- let lookupBlock;
1761
- let blobBlock;
1762
- if (money) {
1763
- type = "number";
1764
- inferred.semanticType = "currency";
1765
- inferred.aggregate = "sum";
1766
- const currency = money.soleCurrency();
1767
- const scale = currency !== void 0 ? money.scaleFor(currency) : void 0;
1768
- moneyBlock = {
1769
- mode: money.mode,
1770
- ...currency !== void 0 ? { currency } : {},
1771
- ...scale !== void 0 ? { scale } : {},
1772
- ...money.rounding !== void 0 ? { rounding: money.rounding } : {}
1773
- };
1774
- } else if (refDesc) {
1775
- type = refDesc.isArray ? "array" : "string";
1776
- inferred.semanticType = "entity";
1777
- refBlock = {
1778
- target: refDesc.target,
1779
- mode: refDesc.mode,
1780
- ...refDesc.isArray ? { isArray: true } : {}
1781
- };
1782
- } else if (dict) {
1783
- type = "enum";
1784
- if (isStaticDictDescriptor(dict)) {
1785
- const displayLocale = dict.displayLocale;
1786
- const values = dict.keys.map((k) => {
1787
- const localeMap = dict.table[k];
1788
- const label = displayLocale !== void 0 ? localeMap?.[displayLocale] ?? void 0 : void 0;
1789
- return label !== void 0 ? { value: k, label } : { value: k };
1790
- });
1791
- dictBlock = { name: dict.name, static: true, values };
1792
- } else {
1793
- const labelMap = dictLabels?.[dict.name];
1794
- const labelMapHasEntries = labelMap !== void 0 && Object.keys(labelMap).length > 0;
1795
- if (labelMapHasEntries) {
1796
- const values = Object.entries(labelMap).map(([value, label]) => ({ value, label }));
1797
- dictBlock = { name: dict.name, static: false, values };
1798
- } else if (dict.keys !== void 0) {
1799
- const values = dict.keys.map((k) => {
1800
- const label = dict.labels?.[k];
1801
- return label !== void 0 ? { value: k, label } : { value: k };
1802
- });
1803
- dictBlock = { name: dict.name, static: false, values };
1804
- } else {
1805
- dictBlock = { name: dict.name, static: false };
1806
- }
1807
- }
1808
- } else if (lookupDesc) {
1809
- type = "enum";
1810
- const lookupLabelMap = dictLabels?.[lookupDesc.dimension];
1811
- const lookupLabelMapHasEntries = lookupLabelMap !== void 0 && Object.keys(lookupLabelMap).length > 0;
1812
- if (lookupDesc.table !== void 0) {
1813
- const displayLocale = lookupDesc.displayLocale;
1814
- const table = lookupDesc.table;
1815
- const values = (lookupDesc.keys ?? Object.keys(table)).map((k) => {
1816
- const localeMap = table[k];
1817
- const label = displayLocale !== void 0 ? localeMap?.[displayLocale] ?? void 0 : void 0;
1818
- return label !== void 0 ? { value: k, label } : { value: k };
1819
- });
1820
- dictBlock = { name: lookupDesc.dimension, static: true, values };
1821
- } else if (lookupLabelMapHasEntries) {
1822
- const values = Object.entries(lookupLabelMap).map(([value, label]) => ({ value, label }));
1823
- dictBlock = { name: lookupDesc.dimension, static: false, values };
1824
- } else if (lookupDesc.keys !== void 0) {
1825
- const values = lookupDesc.keys.map((k) => {
1826
- const label = lookupDesc.labels?.[k];
1827
- return label !== void 0 ? { value: k, label } : { value: k };
1828
- });
1829
- dictBlock = { name: lookupDesc.dimension, static: false, values };
1830
- } else {
1831
- dictBlock = { name: lookupDesc.dimension, static: false };
1832
- }
1833
- } else if (i18nDesc) {
1834
- type = "string";
1835
- } else if (isComputed) {
1836
- } else if (blobFragmentEntry !== void 0) {
1837
- type = "blob";
1838
- blobBlock = { ...blobFragmentEntry, queryable: "none" };
1839
- }
1840
- const lookupFragmentEntry = lookupFragments?.[key];
1841
- if (lookupFragmentEntry !== void 0) {
1842
- lookupBlock = {
1843
- ...lookupFragmentEntry.dimension !== void 0 ? { dimension: lookupFragmentEntry.dimension } : {},
1844
- backing: lookupFragmentEntry.backing,
1845
- vocabulary: lookupFragmentEntry.vocabulary,
1846
- key: lookupFragmentEntry.key,
1847
- onDelete: lookupFragmentEntry.onDelete,
1848
- ...lookupFragmentEntry.altKeys !== void 0 ? { altKeys: lookupFragmentEntry.altKeys } : {},
1849
- ...lookupFragmentEntry.present !== void 0 ? { present: lookupFragmentEntry.present } : {},
1850
- ...lookupFragmentEntry.sortBy !== void 0 ? { sortBy: lookupFragmentEntry.sortBy } : {},
1851
- ...lookupFragmentEntry.keys !== void 0 ? { keys: lookupFragmentEntry.keys } : {}
1852
- };
1853
- }
1854
- if (cls !== void 0) inferred.sensitivity = cls.sensitivity;
1855
- const channelEntry = fieldMeta?.[key];
1856
- const zodMeta = zod?.meta;
1857
- const resolved = resolveFieldMeta(key, {
1858
- ...channelEntry !== void 0 ? { channel: channelEntry } : {},
1859
- ...zodMeta !== void 0 ? { zodMeta } : {},
1860
- inferred
1861
- });
1862
- let i18nBlock;
1863
- if (i18nDesc !== void 0) {
1864
- const locales = i18nDesc.options.languages;
1865
- const densify = i18nDesc.options.densifyOnWrite;
1866
- i18nBlock = {
1867
- ...locales !== void 0 ? { locales } : {},
1868
- ...densify !== void 0 ? { densify } : {}
1869
- };
1870
- }
1871
- const widget = deriveWidget({
1872
- type,
1873
- ...resolved.semanticType !== void 0 ? { semanticType: resolved.semanticType } : {},
1874
- ...dictBlock !== void 0 ? { dict: dictBlock } : {},
1875
- ...lookupBlock !== void 0 ? { lookup: lookupBlock } : {},
1876
- ...blobBlock !== void 0 ? { blob: blobBlock } : {},
1877
- ...resolved.widget !== void 0 ? { resolvedWidget: resolved.widget } : {}
1878
- });
1879
- const editable = !isComputed && key !== "id" && blobBlock === void 0;
1880
- const field = {
1881
- key,
1882
- ...fieldIds?.[key] !== void 0 ? { id: fieldIds[key] } : {},
1883
- type,
1884
- optional: zod?.optional ?? false,
1885
- ...zod?.constraints !== void 0 ? { constraints: zod.constraints } : {},
1886
- label: resolved.label,
1887
- ...resolved.description !== void 0 ? { description: resolved.description } : {},
1888
- ...resolved.group !== void 0 ? { group: resolved.group } : {},
1889
- ...resolved.order !== void 0 ? { order: resolved.order } : {},
1890
- ...resolved.semanticType !== void 0 ? { semanticType: resolved.semanticType } : {},
1891
- ...resolved.unit !== void 0 ? { unit: resolved.unit } : {},
1892
- ...resolved.sensitivity !== void 0 ? { sensitivity: resolved.sensitivity } : {},
1893
- ...resolved.aggregate !== void 0 ? { aggregate: resolved.aggregate } : {},
1894
- ...resolved.aliases !== void 0 ? { aliases: resolved.aliases } : {},
1895
- ...resolved.displayFor !== void 0 ? { displayFor: resolved.displayFor } : {},
1896
- ...refBlock !== void 0 ? { ref: refBlock } : {},
1897
- ...moneyBlock !== void 0 ? { money: moneyBlock } : {},
1898
- ...dictBlock !== void 0 ? { dict: dictBlock } : {},
1899
- ...lookupBlock !== void 0 ? { lookup: lookupBlock } : {},
1900
- ...blobBlock !== void 0 ? { blob: blobBlock } : {},
1901
- ...isComputed ? { computed: true } : {},
1902
- ...i18nBlock !== void 0 ? { i18n: i18nBlock } : {},
1903
- widget,
1904
- editable,
1905
- ...cls !== void 0 ? {
1906
- classified: {
1907
- preset: cls.preset,
1908
- storage: cls.storage,
1909
- list: cls.list.kind === "omit" ? "omit" : cls.list.kind === "mask" ? { mask: cls.list.pattern } : { rider: cls.list.rider },
1910
- ...cls.equatable === true ? { equatable: true } : {}
1911
- }
1912
- } : {},
1913
- ...taintPosture !== void 0 ? { taint: { posture: taintPosture, forcedBy: taint?.provenance?.get(key) ?? [] } } : {}
1914
- };
1915
- fields.push(field);
1916
- }
1917
- const collectionMeta = {
1918
- ...meta,
1919
- label: meta?.label ?? humanizeFieldKey(collection)
1920
- };
1921
- return {
1922
- collection,
1923
- fields,
1924
- meta: collectionMeta
1925
- };
1926
- }
1927
-
1928
1224
  // src/kernel/put-many-atomic.ts
1929
1225
  var toRevertLeg = ({ op, priorEnvelope }) => ({
1930
1226
  vaultName: op.vaultName,
@@ -2028,469 +1324,6 @@ async function commitSequentially(ctx, entries, priors) {
2028
1324
  }
2029
1325
  }
2030
1326
 
2031
- // src/kernel/via/compose.ts
2032
- function via(...descriptors) {
2033
- if (descriptors.length === 0) {
2034
- throw new ValidationError("via(): at least one descriptor is required, e.g. via(money({ currency: 'EUR' }))");
2035
- }
2036
- return { _noydbVia: true, descriptors };
2037
- }
2038
- function isViaFieldSpec(x) {
2039
- return typeof x === "object" && x !== null && x._noydbVia === true;
2040
- }
2041
- function mergeViaFields(sources) {
2042
- for (const field of Object.keys(sources.dictKeyFields ?? {})) {
2043
- if (sources.lookupFields && field in sources.lookupFields) {
2044
- throw new ValidationError(
2045
- `mergeViaFields(): field "${field}" is declared in both \`dictKeyFields\` and \`lookupFields\` \u2014 declare it in one place only.`
2046
- );
2047
- }
2048
- }
2049
- if (!sources.viaFields || Object.keys(sources.viaFields).length === 0) {
2050
- return {
2051
- moneyFields: sources.moneyFields,
2052
- i18nFields: sources.i18nFields,
2053
- dictKeyFields: sources.dictKeyFields,
2054
- computedFields: void 0,
2055
- lookupFields: sources.lookupFields
2056
- };
2057
- }
2058
- const sugarFieldNames = /* @__PURE__ */ new Set([
2059
- ...Object.keys(sources.moneyFields ?? {}),
2060
- ...Object.keys(sources.i18nFields ?? {}),
2061
- ...Object.keys(sources.dictKeyFields ?? {}),
2062
- ...Object.keys(sources.lookupFields ?? {})
2063
- ]);
2064
- const viaMoney = {};
2065
- const viaI18nText = {};
2066
- const viaDictKey = {};
2067
- const viaComputed = {};
2068
- const viaLookup = {};
2069
- for (const [field, spec] of Object.entries(sources.viaFields)) {
2070
- if (sugarFieldNames.has(field)) {
2071
- throw new ValidationError(
2072
- `via(): field "${field}" is declared via both a sugar key and \`viaFields\` \u2014 declare it in one place only.`
2073
- );
2074
- }
2075
- for (const descriptor of spec.descriptors) {
2076
- if (descriptor._viaBrand === "money") {
2077
- viaMoney[field] = descriptor;
2078
- } else if (descriptor._viaBrand === "i18n") {
2079
- if (isI18nTextDescriptor(descriptor)) viaI18nText[field] = descriptor;
2080
- else if (isDictKeyDescriptor(descriptor) || isStaticDictDescriptor(descriptor)) viaDictKey[field] = descriptor;
2081
- } else if (descriptor._viaBrand === "computed") {
2082
- viaComputed[field] = descriptor;
2083
- } else if (descriptor._viaBrand === "lookup" && isLookupDescriptor(descriptor)) {
2084
- viaLookup[field] = descriptor;
2085
- } else {
2086
- throw new ValidationError(`via(): field "${field}" has a descriptor with unrecognized _viaBrand "${descriptor._viaBrand}" \u2014 via() only supports money/i18n/computed/lookup descriptors today.`);
2087
- }
2088
- }
2089
- }
2090
- return {
2091
- moneyFields: Object.keys(viaMoney).length > 0 ? { ...sources.moneyFields ?? {}, ...viaMoney } : sources.moneyFields,
2092
- i18nFields: Object.keys(viaI18nText).length > 0 ? { ...sources.i18nFields ?? {}, ...viaI18nText } : sources.i18nFields,
2093
- dictKeyFields: Object.keys(viaDictKey).length > 0 ? { ...sources.dictKeyFields ?? {}, ...viaDictKey } : sources.dictKeyFields,
2094
- computedFields: Object.keys(viaComputed).length > 0 ? viaComputed : void 0,
2095
- lookupFields: Object.keys(viaLookup).length > 0 ? { ...sources.lookupFields ?? {}, ...viaLookup } : sources.lookupFields
2096
- };
2097
- }
2098
- var VIA_FIELD_MAP_FAMILY = {
2099
- moneyFields: "money",
2100
- i18nFields: "i18n",
2101
- dictKeyFields: "i18n",
2102
- lookupFields: "lookup",
2103
- classifiedFields: "classified",
2104
- blobFields: "blob",
2105
- computed: "computed"
2106
- };
2107
- function guardCrossBindingFieldCollisions(fieldMaps, callerPrefix = "compileVias()") {
2108
- const claimantsByField = /* @__PURE__ */ new Map();
2109
- for (const [sourceKey, map] of Object.entries(fieldMaps)) {
2110
- for (const field of Object.keys(map ?? {})) {
2111
- const claimants = claimantsByField.get(field) ?? /* @__PURE__ */ new Set();
2112
- claimants.add(sourceKey);
2113
- claimantsByField.set(field, claimants);
2114
- }
2115
- }
2116
- for (const [field, sourceKeys] of claimantsByField) {
2117
- const families = new Set([...sourceKeys].map((key) => VIA_FIELD_MAP_FAMILY[key]));
2118
- if (families.size < 2) continue;
2119
- if (sourceKeys.size === 2 && families.has("computed")) {
2120
- const other = [...families].find((f) => f !== "computed");
2121
- if (other === "money" || other === "i18n" || other === "lookup") continue;
2122
- }
2123
- const keys = [...sourceKeys].sort().map((k) => `\`${k}\``);
2124
- const joined = keys.length === 2 ? keys.join(" and ") : `${keys.slice(0, -1).join(", ")}, and ${keys[keys.length - 1]}`;
2125
- throw new ValidationError(
2126
- `${callerPrefix}: field "${field}" is declared in both ${joined} \u2014 a field cannot be claimed by two different via-binding families. Declare it in one place only.`
2127
- );
2128
- }
2129
- }
2130
- function guardReconcileCollisions(existingVia, incomingFieldMaps) {
2131
- guardCrossBindingFieldCollisions(incomingFieldMaps, "guardReconcileCollisions()");
2132
- if (!existingVia) return;
2133
- const existingBindings = existingVia.bindings.filter((b) => b.brand !== "taint");
2134
- for (const [sourceKey, map] of Object.entries(incomingFieldMaps)) {
2135
- const incomingFamily = VIA_FIELD_MAP_FAMILY[sourceKey];
2136
- if (incomingFamily === void 0) continue;
2137
- for (const field of Object.keys(map ?? {})) {
2138
- const existingFamilies = new Set(
2139
- existingBindings.filter((b) => b.covers?.(field)).map((b) => b.brand)
2140
- );
2141
- for (const existingFamily of existingFamilies) {
2142
- if (existingFamily === incomingFamily) continue;
2143
- const isExempt = existingFamily === "computed" && (incomingFamily === "money" || incomingFamily === "i18n" || incomingFamily === "lookup") || incomingFamily === "computed" && (existingFamily === "money" || existingFamily === "i18n" || existingFamily === "lookup");
2144
- if (isExempt) continue;
2145
- throw new ValidationError(
2146
- `vault.collection(): field "${field}" is already bound by the "${existingFamily}" via family on this collection \u2014 an incoming \`${sourceKey}\` declaration cannot claim it for a different family ("${incomingFamily}"). Declare it in one place only.`
2147
- );
2148
- }
2149
- }
2150
- }
2151
- }
2152
-
2153
- // src/kernel/collection-config.ts
2154
- function computedEntryParts(entry) {
2155
- if (typeof entry === "function") return { fn: entry, mode: "materialized" };
2156
- return { fn: entry.fn, ...entry.deps !== void 0 ? { deps: entry.deps } : {}, mode: entry.mode ?? "materialized" };
2157
- }
2158
- function unifyComputedFields(opts, viaComputedFields) {
2159
- return { ...opts.computed ?? {}, ...viaComputedFields ?? {} };
2160
- }
2161
- function compileVias(opts, classifiedGuardCtx, eraseCfgOut) {
2162
- const { moneyFields, i18nFields, dictKeyFields, computedFields, lookupFields } = mergeViaFields(opts);
2163
- const allComputedFields = unifyComputedFields(opts, computedFields);
2164
- guardCrossBindingFieldCollisions({
2165
- moneyFields,
2166
- i18nFields,
2167
- dictKeyFields,
2168
- lookupFields,
2169
- classifiedFields: opts.classifiedFields !== void 0 ? resolveClassifiedFields(opts.name, opts.classifiedFields).byField : void 0,
2170
- blobFields: opts.blobFields,
2171
- computed: allComputedFields
2172
- });
2173
- const bindings = [];
2174
- const virtualFields = /* @__PURE__ */ new Map();
2175
- for (const [field, entry] of Object.entries(allComputedFields)) {
2176
- const parts = computedEntryParts(entry);
2177
- if (parts.mode === "virtual") {
2178
- virtualFields.set(field, { _viaBrand: "computed", fn: parts.fn, mode: "virtual", ...parts.deps !== void 0 ? { deps: parts.deps } : {} });
2179
- }
2180
- }
2181
- if (moneyFields) {
2182
- const virtualMoney = resolveVirtualMoneyFields(Object.keys(moneyFields), (f) => virtualFields.has(f));
2183
- bindings.push(viaBinder("money")({ moneyFields, ...virtualMoney.size > 0 ? { virtualMoneyFields: virtualMoney } : {} }));
2184
- }
2185
- if (i18nFields || dictKeyFields) {
2186
- const densify = i18nFields ? Object.fromEntries(
2187
- Object.entries(i18nFields).filter(([, d]) => d.options.densifyOnWrite === true)
2188
- ) : {};
2189
- const i18nDensifyFields = Object.keys(densify).length > 0 ? densify : void 0;
2190
- bindings.push(viaBinder("i18n")({
2191
- ...i18nFields !== void 0 ? { i18nFields } : {},
2192
- ...dictKeyFields !== void 0 ? { dictKeyFields } : {},
2193
- ...i18nDensifyFields !== void 0 ? { i18nDensifyFields } : {},
2194
- strategy: opts.strategies.i18n,
2195
- ...opts.defaultLocale !== void 0 ? { defaultLocale: opts.defaultLocale } : {},
2196
- ...opts.autoTranslateHook !== void 0 ? { autoTranslateHook: opts.autoTranslateHook } : {},
2197
- ...opts.dictLabelResolver !== void 0 ? { dictLabelResolver: opts.dictLabelResolver } : {},
2198
- ...opts.i18nPutValidator !== void 0 ? { i18nPutValidator: opts.i18nPutValidator } : {},
2199
- collectionName: opts.name
2200
- }));
2201
- }
2202
- if (lookupFields !== void 0) {
2203
- bindings.push(viaBinder("lookup")({
2204
- lookupFields,
2205
- ...opts.lookupLabelResolver !== void 0 ? { lookupLabelResolver: opts.lookupLabelResolver } : {},
2206
- ...opts.getLookupBacking !== void 0 ? { getLookupBacking: opts.getLookupBacking } : {},
2207
- ...opts.membership !== void 0 ? { membership: opts.membership } : {},
2208
- ...opts.getAltIndex !== void 0 ? { getAltIndex: opts.getAltIndex } : {},
2209
- ...opts.snapshotFor !== void 0 ? { snapshotFor: opts.snapshotFor } : {},
2210
- collectionName: opts.name
2211
- }));
2212
- }
2213
- if (opts.classifiedFields !== void 0) {
2214
- const classifiedCfg = {
2215
- entries: opts.classifiedFields,
2216
- collectionName: opts.name,
2217
- guardCtx: classifiedGuardCtx
2218
- };
2219
- if (eraseCfgOut) eraseCfgOut.classified = classifiedCfg;
2220
- bindings.push(viaBinder("classified")(classifiedCfg));
2221
- }
2222
- if (opts.blobFields !== void 0) {
2223
- bindings.push(viaBinder("blob")({
2224
- fields: opts.blobFields,
2225
- collectionName: opts.name
2226
- }));
2227
- }
2228
- if (virtualFields.size > 0) {
2229
- bindings.push(viaBinder("computed")({ virtualFields }));
2230
- }
2231
- return bindings;
2232
- }
2233
- function resolveVirtualMoneyFields(moneyFieldNames, isVirtual) {
2234
- const out = /* @__PURE__ */ new Set();
2235
- for (const field of moneyFieldNames) if (isVirtual(field)) out.add(field);
2236
- return out;
2237
- }
2238
- function collectKnownFieldNames(parts) {
2239
- return /* @__PURE__ */ new Set([
2240
- ...Object.keys(parts.moneyFields ?? {}),
2241
- ...Object.keys(parts.i18nFields ?? {}),
2242
- ...Object.keys(parts.dictKeyFields ?? {}),
2243
- ...Object.keys(parts.classifiedFields ?? {}),
2244
- ...Object.keys(parts.computed ?? {}),
2245
- ...Object.keys(parts.lookupFields ?? {})
2246
- ]);
2247
- }
2248
- var EMPTY_KNOWN_FIELDS = /* @__PURE__ */ new Set();
2249
- function resolveComputedEdges(collectionName, computed, hasClassifiedFields, knownFields = EMPTY_KNOWN_FIELDS) {
2250
- if (!computed) return [];
2251
- const edges = [];
2252
- for (const [field, entry] of Object.entries(computed)) {
2253
- const parts = computedEntryParts(entry);
2254
- const deps = parts.deps;
2255
- if (deps === void 0) {
2256
- if (hasClassifiedFields) {
2257
- throw new ValidationError(
2258
- `Collection "${collectionName}": computed field "${field}" has no declared \`deps\` and the collection declares classified fields \u2014 an opaque computed function could silently copy a classified field's plaintext into an ordinary, unredacted field. Declare \`deps\` naming the source fields it reads, e.g. computed: { ${field}: { fn, deps: [...] } } or via(computed(fn, { deps: [...] })).`
2259
- );
2260
- }
2261
- continue;
2262
- }
2263
- if (deps.length === 0) {
2264
- throw new ValidationError(`Collection "${collectionName}": computed field "${field}"'s \`deps\` must be non-empty.`);
2265
- }
2266
- for (const dep of deps) {
2267
- if (typeof dep !== "string" || dep.length === 0) {
2268
- throw new ValidationError(`Collection "${collectionName}": computed field "${field}"'s \`deps\` entries must be non-empty strings.`);
2269
- }
2270
- if (hasClassifiedFields && !knownFields.has(dep)) {
2271
- throw new ValidationError(
2272
- `Collection "${collectionName}": computed field "${field}"'s \`deps\` entry "${dep}" does not name a declared field, and the collection declares classified fields \u2014 an opaque computed function could silently copy a classified field's plaintext into an ordinary, unredacted field via a mistyped/unknown dep name. Declare \`deps\` naming only known fields (money/i18n/dictKey/classified/computed) \u2014 check "${dep}" for a typo.`
2273
- );
2274
- }
2275
- }
2276
- edges.push({
2277
- target: { collection: collectionName, field },
2278
- sources: deps.map((d) => ({ collection: collectionName, field: d })),
2279
- grain: parts.mode === "virtual" ? "virtual" : "record"
2280
- });
2281
- }
2282
- return edges;
2283
- }
2284
- function resolveViaDepsEdges(collectionName, bindings, knownFields) {
2285
- const edges = [];
2286
- for (const binding of bindings) {
2287
- if (!binding.deps || binding.deps.length === 0) continue;
2288
- for (const dep of binding.deps) {
2289
- if (!knownFields.has(dep)) {
2290
- throw new ValidationError(
2291
- `Collection "${collectionName}": via binding "${binding.brand}" deps references undeclared field "${dep}".`
2292
- );
2293
- }
2294
- }
2295
- const sources = binding.deps.map((d) => ({ collection: collectionName, field: d }));
2296
- for (const field of knownFields) {
2297
- if (binding.covers?.(field)) edges.push({ target: { collection: collectionName, field }, sources });
2298
- }
2299
- }
2300
- return edges;
2301
- }
2302
- function resolveCollectionConfig(opts) {
2303
- if (opts.embeddings && opts.crdt) {
2304
- throw new Error(
2305
- `Collection "${opts.name}": embeddings are not supported on CRDT collections (L2). Use a non-CRDT collection for semantic search.`
2306
- );
2307
- }
2308
- if (opts.sensitive && opts.sensitive.length > 0 && opts.crdt) {
2309
- throw new Error(
2310
- `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.`
2311
- );
2312
- }
2313
- if (opts.tiers && opts.tiers.length > 0 && opts.blobFields !== void 0 && Object.keys(opts.blobFields).length > 0 && opts.perRecordKeys !== true) {
2314
- throw new UnsupportedTierCompositionError(
2315
- "blobs",
2316
- `Collection "${opts.name}": tiered collections that declare blobFields require \`perRecordKeys: true\` \u2014 legacy (non-perRecordKeys) blobs have no per-blob content CEK and cannot be tier-isolated on elevate/demote.`
2317
- );
2318
- }
2319
- if (opts.tiers && opts.tiers.length > 0 && opts.blobFields !== void 0) {
2320
- for (const [field, policy] of Object.entries(opts.blobFields)) {
2321
- if (policy?.external && policy.public === true) {
2322
- throw new UnsupportedTierCompositionError(
2323
- "blobs",
2324
- `Collection "${opts.name}": blob field "${field}" cannot be both \`external: true, public: true\` and declared on a tiered collection \u2014 the object key is deterministic and its bytes are world-readable by design, so tier-invisibility can never be enforced for it once elevated. Use a private/presigned external field (\`public\` omitted or \`false\`) or move this field to a non-tiered collection.`
2325
- );
2326
- }
2327
- }
2328
- }
2329
- if (opts.tiers && opts.tiers.length > 0 && opts.encrypted === false) {
2330
- throw new UnsupportedTierCompositionError(
2331
- "plaintext",
2332
- `Collection "${opts.name}": tiers are per-tier ENCRYPTION keys \u2014 a plaintext collection (\`encrypted: false\`) has nothing to re-key, so \`putAtTier\`/\`elevate\` would encrypt/decrypt incoherently. Use an encrypted collection.`
2333
- );
2334
- }
2335
- if (opts.tiers && opts.tiers.length > 0 && opts.crdt !== void 0 && ((opts.derivationSource?.registry().strategiesForSource(opts.name).length ?? 0) > 0 || (opts.materializedViewSource?.registry().mvsForSource(opts.name).length ?? 0) > 0)) {
2336
- throw new UnsupportedTierCompositionError(
2337
- "crdt-derivation",
2338
- `Collection "${opts.name}": tiers + crdt cannot be combined with this collection acting as a derivation/rollup source (#739) \u2014 the tier-aware pre-move decode \`syncDerived\` uses on elevate()/demote() does not resolve CRDT state, so a registered derivation/rollup reads raw CrdtState instead of the record and silently no-ops. Use a non-CRDT collection for a tiered derivation/rollup source, or move the derivation off this collection.`
2339
- );
2340
- }
2341
- const effectiveViaFields = mergeViaFields(opts);
2342
- const resolvedClassified = opts.classifiedFields !== void 0 ? resolveClassifiedFields(opts.name, opts.classifiedFields) : void 0;
2343
- const allComputed = unifyComputedFields(opts, effectiveViaFields.computedFields);
2344
- const materializedComputed = {};
2345
- for (const [field, entry] of Object.entries(allComputed)) {
2346
- if (computedEntryParts(entry).mode !== "virtual") materializedComputed[field] = entry;
2347
- }
2348
- let mergedComputed = Object.keys(materializedComputed).length > 0 ? materializedComputed : void 0;
2349
- if (resolvedClassified !== void 0) {
2350
- for (const key of Object.keys(allComputed)) {
2351
- if (resolvedClassified.riderComputed[key] !== void 0) {
2352
- throw new ClassifiedConfigError(opts.name, `computed field "${key}" collides with a rider companion`);
2353
- }
2354
- }
2355
- mergedComputed = { ...resolvedClassified.riderComputed, ...materializedComputed };
2356
- }
2357
- let deterministicFields;
2358
- if (opts.deterministicFields && opts.deterministicFields.length > 0) {
2359
- if (opts.acknowledgeDeterministicRisk !== true) {
2360
- throw new Error(
2361
- `Collection "${opts.name}": deterministicFields requires \`acknowledgeDeterministicRisk: true\`. Deterministic encryption leaks equality between records \u2014 two records with the same field value produce identical ciphertexts visible to anyone with store access. If that trade-off is acceptable for your threat model, set \`acknowledgeDeterministicRisk: true\` to enable.`
2362
- );
2363
- }
2364
- deterministicFields = Object.freeze(new Set(opts.deterministicFields));
2365
- } else {
2366
- deterministicFields = null;
2367
- }
2368
- const guardIndexedFields = /* @__PURE__ */ new Set();
2369
- for (const ix of opts.indexes ?? []) {
2370
- if (typeof ix === "string") guardIndexedFields.add(ix);
2371
- else if (Array.isArray(ix)) for (const f of ix) guardIndexedFields.add(f);
2372
- else for (const f of ix.fields) guardIndexedFields.add(f);
2373
- }
2374
- const embeddingSources = opts.embeddings === void 0 ? [] : typeof opts.embeddings.source === "string" ? [opts.embeddings.source] : [...opts.embeddings.source];
2375
- const classifiedGuardCtx = {
2376
- perRecordKeys: opts.perRecordKeys === true,
2377
- crdt: opts.crdt !== void 0,
2378
- hasConflictPolicy: opts.conflictPolicy !== void 0,
2379
- storeCiphertext: opts.encrypted,
2380
- deterministicFields,
2381
- indexedFields: guardIndexedFields,
2382
- textIndexFields: new Set(opts.textIndexes ?? []),
2383
- vectorSourceFields: new Set(embeddingSources),
2384
- subjectKeyField: opts.subjectKeyField,
2385
- bareSensitiveFields: new Set(opts.sensitive ?? []),
2386
- acknowledgeEquatableRisk: opts.acknowledgeEquatableRisk === true
2387
- };
2388
- if (resolvedClassified !== void 0) {
2389
- guardClassifiedCompat(opts.name, resolvedClassified.byField, classifiedGuardCtx);
2390
- }
2391
- const classifiedSensitive = resolvedClassified === void 0 ? [] : Object.entries(resolvedClassified.byField).filter(([, s]) => s.storage === "recoverable").map(([f]) => f);
2392
- const sensitiveList = [...opts.sensitive ?? [], ...classifiedSensitive];
2393
- const sensitiveFields = sensitiveList.length > 0 ? Object.freeze(new Set(sensitiveList)) : Object.freeze(/* @__PURE__ */ new Set());
2394
- const vdigEntries = resolvedClassified === void 0 ? [] : Object.entries(resolvedClassified.byField).filter(([, s]) => s.storage === "digest-only").map(([f, s]) => [f, {
2395
- normalize: s.verifyNormalize ?? "password",
2396
- notLastN: s.notLastN ?? 0,
2397
- equatable: s.equatable === true,
2398
- ...s.rotateDays !== void 0 ? { rotateDays: s.rotateDays } : {}
2399
- }]);
2400
- const vdigFields = vdigEntries.length > 0 ? new Map(vdigEntries) : null;
2401
- const perRecordCek = opts.perRecordKeys === true;
2402
- const cekCache = perRecordCek ? new Lru({ maxRecords: 4096 }) : null;
2403
- const viaEraseCfgOut = {};
2404
- const via2 = ViaPipeline.build(compileVias(opts, classifiedGuardCtx, viaEraseCfgOut));
2405
- const knownFields = collectKnownFieldNames({
2406
- moneyFields: effectiveViaFields.moneyFields,
2407
- i18nFields: effectiveViaFields.i18nFields,
2408
- dictKeyFields: effectiveViaFields.dictKeyFields,
2409
- classifiedFields: resolvedClassified?.byField,
2410
- computed: allComputed,
2411
- lookupFields: effectiveViaFields.lookupFields
2412
- });
2413
- const computedEdges = resolveComputedEdges(opts.name, allComputed, resolvedClassified !== void 0, knownFields);
2414
- const viaDepsEdges = resolveViaDepsEdges(opts.name, via2?.bindings ?? [], knownFields);
2415
- return {
2416
- adapter: opts.adapter,
2417
- vault: opts.vault,
2418
- name: opts.name,
2419
- keyring: opts.keyring,
2420
- storeCiphertext: opts.encrypted,
2421
- ramCiphertext: opts.ramCiphertext ?? false,
2422
- emitter: opts.emitter,
2423
- writeQueue: opts.writeQueue,
2424
- schemaUpdateGate: opts.schemaUpdateGate,
2425
- schemaFence: opts.schemaFence,
2426
- writeHooks: opts.writeHooks,
2427
- subsystemBus: opts.subsystemBus,
2428
- activeTxId: opts.activeTxId,
2429
- strategies: opts.strategies,
2430
- objectStore: opts.objectStore,
2431
- blobFields: opts.blobFields,
2432
- reconcileOnOpen: opts.reconcileOnOpen ?? "off",
2433
- getDEK: opts.getDEK,
2434
- onDirty: opts.onDirty,
2435
- tabCoordinated: opts.tabCoordinated,
2436
- historyConfig: opts.historyConfig ?? { enabled: true },
2437
- historyConfigExplicit: opts.historyConfigExplicit ?? false,
2438
- schema: opts.schema,
2439
- ledger: opts.ledger,
2440
- refEnforcer: opts.refEnforcer,
2441
- joinResolver: opts.joinResolver,
2442
- i18nFields: effectiveViaFields.i18nFields,
2443
- textIndexes: opts.textIndexes,
2444
- embeddings: opts.embeddings,
2445
- vectorSet: opts.embeddings ? new VectorSet() : void 0,
2446
- dictKeyFields: effectiveViaFields.dictKeyFields,
2447
- lookupFields: effectiveViaFields.lookupFields,
2448
- // #650 Task 6 — the sync join-dressing hook (#626 retirement, spec §5);
2449
- // `undefined` when this collection declares neither i18nText nor lookup
2450
- // fields, matching today's `i18nFields`-absent JoinableSource shape.
2451
- presentForJoin: buildPresentForJoin(effectiveViaFields.i18nFields, effectiveViaFields.lookupFields, opts.snapshotFor),
2452
- fieldMeta: opts.fieldMeta,
2453
- meta: opts.meta,
2454
- _refs: opts.declaredRefs ?? {},
2455
- via: via2,
2456
- classifiedEraseCfg: viaEraseCfgOut.classified,
2457
- moneyFields: effectiveViaFields.moneyFields,
2458
- classified: resolvedClassified,
2459
- classifiedGuardCtx,
2460
- computed: mergedComputed,
2461
- computedEdges,
2462
- viaDepsEdges,
2463
- // #638 Task 7 — every declared computed field name (both modes, both surfaces); the
2464
- // reconcile path's cross-call depsless-computed leak-guard memory
2465
- // (`via/graph-wiring.ts#registerCollectionGraphSources`) reads this instead of
2466
- // `Object.keys(opts.computed ?? {})` alone, so a via(computed(...))-declared field
2467
- // participates in that guard identically to a sugar-declared one.
2468
- computedFieldNames: Object.keys(allComputed),
2469
- dictLabelResolver: opts.dictLabelResolver,
2470
- getDictionary: opts.getDictionary,
2471
- i18nPutValidator: opts.i18nPutValidator,
2472
- autoTranslateHook: opts.autoTranslateHook,
2473
- defaultLocale: opts.defaultLocale,
2474
- crdtMode: opts.crdt,
2475
- syncAdapter: opts.syncAdapter,
2476
- onAccess: opts.onAccess,
2477
- derivationSource: opts.derivationSource,
2478
- materializedViewSource: opts.materializedViewSource,
2479
- graphDispatch: opts.graphDispatch,
2480
- tiers: opts.tiers && opts.tiers.length > 0 ? new Set(opts.tiers) : null,
2481
- tierMode: opts.tierMode ?? "invisibility",
2482
- blobTierPolicy: opts.blobTierPolicy ?? "isolate",
2483
- onCrossTierAccess: opts.onCrossTierAccess,
2484
- addSubjectRef: opts.addSubjectRef,
2485
- deterministicFields,
2486
- sensitiveFields,
2487
- vdigFields,
2488
- perRecordCek,
2489
- cekCache,
2490
- provenance: opts.provenance === true
2491
- };
2492
- }
2493
-
2494
1327
  // src/with-formula/computed/lazy.ts
2495
1328
  var loadComputed = lazy(() => import("./computed-LSEZYRJS.js"));
2496
1329
  async function loadEvalComputedFields() {
@@ -3489,7 +2322,7 @@ var Collection = class {
3489
2322
  }
3490
2323
  }
3491
2324
  if (this.materializedViewSource !== void 0) {
3492
- const { resolveStaleMVOnRead } = await import("./stale-KSPG6LFX.js");
2325
+ const { resolveStaleMVOnRead } = await import("./stale-2CHKZTMK.js");
3493
2326
  await resolveStaleMVOnRead(this.materializedViewSource, this.name, this.#dispatchCtx({ collection: this.name, id: "resolve-on-read" }));
3494
2327
  }
3495
2328
  let record;
@@ -3841,7 +2674,8 @@ var Collection = class {
3841
2674
  this.indexes?.upsert(id, indexed, prior ? prior.record : null);
3842
2675
  this.uniqueConstraints?.upsert(id, indexed, prior?.record);
3843
2676
  }
3844
- await this._onRecordMutated(id, "put", "local-write", { record: event, version });
2677
+ const priorForTrigger = prior ? prior.record : null;
2678
+ await this._onRecordMutated(id, "put", "local-write", { record: event, version, prior: priorForTrigger });
3845
2679
  }
3846
2680
  /**
3847
2681
  * The pre-envelope stages both write paths share (#904): permission + tier
@@ -4020,7 +2854,7 @@ var Collection = class {
4020
2854
  */
4021
2855
  async dispatchMaterializedViews(id, record, wave) {
4022
2856
  if (this.materializedViewSource === void 0) return;
4023
- const { dispatchMaterializedViews } = await import("./dispatch-44J4TL72.js");
2857
+ const { dispatchMaterializedViews } = await import("./dispatch-PDP5TA73.js");
4024
2858
  return dispatchMaterializedViews(this.#mvDispatchCtx(this.materializedViewSource), id, record, wave);
4025
2859
  }
4026
2860
  /**
@@ -4079,34 +2913,23 @@ var Collection = class {
4079
2913
  const record = await this.codec.decryptRecord({ collection: this.name, id }, env, {});
4080
2914
  return record === null ? null : { record, version: env._v };
4081
2915
  }
4082
- /**
4083
- * @internal Ids of records whose top-level `field` equals `value`.
4084
- * Uses the FK index when the field is indexed (O(matches)); otherwise a
4085
- * linear scan (O(N) — fine for small child sets; index the FK to scale).
4086
- */
2916
+ /** @internal Ids of records whose top-level `field` equals `value` — delegates to the composite scan. */
4087
2917
  async _findMatchingIds(field, value) {
4088
- const hit = this.getIndexes()?.lookupEqual(field, value);
4089
- if (hit) return [...hit];
4090
- const target = String(value);
4091
- const matches = (rec) => {
4092
- const fv = rec[field];
4093
- return (typeof fv === "string" || typeof fv === "number") && String(fv) === target;
4094
- };
4095
- if (!this.lazy) {
4096
- await this.ensureHydrated();
4097
- const out2 = [];
4098
- for (const [rid, e] of this.cache) {
4099
- if (matches(e.record)) out2.push(rid);
4100
- }
4101
- return out2;
4102
- }
4103
- const ids = await this.adapter.list(this.vault, this.name);
4104
- const out = [];
4105
- for (const rid of ids) {
4106
- const raw = await this._getStoredRecord(rid);
4107
- if (raw !== null && matches(raw)) out.push(rid);
4108
- }
4109
- return out;
2918
+ if (typeof value !== "string" && typeof value !== "number") return [];
2919
+ return this._findMatchingCompositeIds([{ field, value: String(value) }]);
2920
+ }
2921
+ /** @internal conjunction fan-out for composite triggerBy (#1249). First indexed pair narrows,
2922
+ * filtered by the OTHER pairs only; zero reads when the index alone decides membership. */
2923
+ async _findMatchingCompositeIds(pairs) {
2924
+ if (!this.lazy) await this.ensureHydrated();
2925
+ const i = pairs.findIndex((p) => this.getIndexes()?.lookupEqual(p.field, p.value));
2926
+ const hit = i < 0 ? null : this.getIndexes().lookupEqual(pairs[i].field, pairs[i].value);
2927
+ const residual = i < 0 ? pairs : pairs.filter((_, j) => j !== i);
2928
+ return findMatchingIdsByPairs(residual, {
2929
+ indexCandidates: hit ? [...hit] : null,
2930
+ listIds: async () => this.lazy ? this.adapter.list(this.vault, this.name) : [...this.cache.keys()],
2931
+ getRecord: async (id) => this.lazy ? await this._getStoredRecord(id) : this.cache.get(id)?.record ?? null
2932
+ });
4110
2933
  }
4111
2934
  /** @internal — ctx for `putDerivedOutput`'s frozen-period skip+audit (#638 Task 5). */
4112
2935
  /** What `with-formula/materialized-views/dispatch.ts` needs from this collection (#842). */
@@ -4189,18 +3012,22 @@ var Collection = class {
4189
3012
  if (spec) await this.recomputeRollup(spec, intent.parentId, { collection: this.name, id: "<sync-delete>" }, wave);
4190
3013
  }
4191
3014
  }
3015
+ /** Extras added onto {@link #derivationDeleteCtx} — shared by `dispatchDerivations` and `dispatchTriggerDerivationsOnDelete` (#1249). */
3016
+ #derivationDispatchCtx() {
3017
+ return { ...this.#derivationDeleteCtx(this.derivationSource), via: this.via, recomputeRollup: this.recomputeRollup.bind(this), dispatchCtx: this.#dispatchCtx.bind(this), trackPut: this.#trackPut.bind(this) };
3018
+ }
4192
3019
  /** @internal `wave` (#638 Task 4) — threaded to `recomputeRollup` for the sync/cutover/restore
4193
3020
  * dispatch wave's per-target dedup; `undefined` on the local-write path (byte-identical). */
4194
- async dispatchDerivations(id, record, version, wave) {
3021
+ async dispatchDerivations(id, record, version, wave, prior) {
4195
3022
  if (this.derivationSource === void 0) return;
4196
- const { dispatchDerivations } = await import("./dispatch-IM52HDXT.js");
4197
- return dispatchDerivations({
4198
- ...this.#derivationDeleteCtx(this.derivationSource),
4199
- via: this.via,
4200
- recomputeRollup: (spec, parentId, source, w) => this.recomputeRollup(spec, parentId, source, w),
4201
- dispatchCtx: (source) => this.#dispatchCtx(source),
4202
- trackPut: (txCtx, collectionName, rid, prior) => this.#trackPut(txCtx, collectionName, rid, prior)
4203
- }, id, record, version, wave);
3023
+ const { dispatchDerivations } = await import("./dispatch-MSTR5VBI.js");
3024
+ return dispatchDerivations(this.#derivationDispatchCtx(), id, record, version, wave, prior);
3025
+ }
3026
+ /** @internal — trigger fan-out for a deleted parent (#1249); see dispatch.ts. */
3027
+ async dispatchTriggerDerivationsOnDelete(id, deleted) {
3028
+ if (this.derivationSource === void 0) return;
3029
+ const { dispatchTriggerDerivationsOnDelete } = await import("./dispatch-MSTR5VBI.js");
3030
+ return dispatchTriggerDerivationsOnDelete(this.#derivationDispatchCtx(), id, deleted);
4204
3031
  }
4205
3032
  /**
4206
3033
  * Delete a record by ID. Runs inside the hub's write-queue tracker
@@ -4438,7 +3265,10 @@ var Collection = class {
4438
3265
  if (!internal) {
4439
3266
  await this.dispatchMaterializedViewsOnDelete(id);
4440
3267
  await this.dispatchArrayDerivationsOnDelete(id);
4441
- if (existing) await this.dispatchRollupsOnDelete(id, existing.record);
3268
+ if (existing) {
3269
+ await this.dispatchRollupsOnDelete(id, existing.record);
3270
+ await this.dispatchTriggerDerivationsOnDelete(id, existing.record);
3271
+ }
4442
3272
  }
4443
3273
  return true;
4444
3274
  }
@@ -4508,7 +3338,7 @@ var Collection = class {
4508
3338
  */
4509
3339
  async dispatchArrayDerivationsOnDelete(id, eraseRecordShapeToo = false) {
4510
3340
  if (this.derivationSource === void 0) return 0;
4511
- const { dispatchArrayDerivationsOnDelete } = await import("./dispatch-IM52HDXT.js");
3341
+ const { dispatchArrayDerivationsOnDelete } = await import("./dispatch-MSTR5VBI.js");
4512
3342
  return dispatchArrayDerivationsOnDelete(this.#derivationDeleteCtx(this.derivationSource), id, eraseRecordShapeToo);
4513
3343
  }
4514
3344
  /**
@@ -4522,7 +3352,7 @@ var Collection = class {
4522
3352
  */
4523
3353
  async dispatchMaterializedViewsOnDelete(id) {
4524
3354
  if (this.materializedViewSource === void 0) return { deleted: 0, residueUndecodable: [], residueDeclined: [] };
4525
- const { dispatchMaterializedViewsOnDelete } = await import("./dispatch-44J4TL72.js");
3355
+ const { dispatchMaterializedViewsOnDelete } = await import("./dispatch-PDP5TA73.js");
4526
3356
  return dispatchMaterializedViewsOnDelete(this.#mvDispatchCtx(this.materializedViewSource), id);
4527
3357
  }
4528
3358
  /**
@@ -4542,7 +3372,7 @@ var Collection = class {
4542
3372
  );
4543
3373
  }
4544
3374
  if (this.materializedViewSource !== void 0) {
4545
- const { resolveStaleMVOnRead } = await import("./stale-KSPG6LFX.js");
3375
+ const { resolveStaleMVOnRead } = await import("./stale-2CHKZTMK.js");
4546
3376
  await resolveStaleMVOnRead(this.materializedViewSource, this.name, this.#dispatchCtx({ collection: this.name, id: "resolve-on-read" }));
4547
3377
  }
4548
3378
  await this.ensureHydrated();
@@ -5206,7 +4036,7 @@ var Collection = class {
5206
4036
  this.emitter.emit("change", { vault: this.vault, collection: this.name, id, action: "put" });
5207
4037
  this.searchIndexStore?.markDirty();
5208
4038
  await this.onAccess?.("put", id);
5209
- await this.dispatchDerivations(id, record, version);
4039
+ await this.dispatchDerivations(id, record, version, void 0, ctx.prior);
5210
4040
  await this.dispatchMaterializedViews(id, record);
5211
4041
  return;
5212
4042
  }
@@ -8973,7 +7803,7 @@ var Vault = class {
8973
7803
  // Vaults without derivations skip this so `Collection.put`'s
8974
7804
  // `if (this.derivationSource)` branch no-ops without touching the
8975
7805
  // derivation service code.
8976
- ...this.derivationRegistry !== null ? {
7806
+ ...this.derivationRegistry !== null ? (this.derivationRegistry.validateFieldsFor(collectionName, options?.schema, Object.keys({ ...options?.fieldMeta, ...options?.moneyFields, ...options?.dictKeyFields, ...options?.refs, ...options?.computed, ...options?.i18nFields, ...options?.lookupFields, ...options?.blobFields, ...options?.classifiedFields }), options), {
8977
7807
  derivationSource: {
8978
7808
  registry: () => this.derivationRegistry,
8979
7809
  getCollection: (name) => this.collection(name),
@@ -8983,7 +7813,7 @@ var Vault = class {
8983
7813
  setActiveTxContext: (ctx) => this.noydb._setActiveTxContext(ctx),
8984
7814
  clearActiveTxContext: (ctx) => this.noydb._clearActiveTxContext(ctx)
8985
7815
  }
8986
- } : {},
7816
+ }) : {},
8987
7817
  ...this.materializedViewRegistry !== null ? {
8988
7818
  materializedViewSource: {
8989
7819
  registry: () => this.materializedViewRegistry,
@@ -10256,7 +9086,7 @@ var Vault = class {
10256
9086
  async _initDerivations(handles) {
10257
9087
  if (handles.length === 0) return;
10258
9088
  const [{ DerivationRegistry }, { ReadOnlyVaultFacade }] = await Promise.all([
10259
- import("./registry-FZXPQRAB.js"),
9089
+ import("./registry-D2YXKRYC.js"),
10260
9090
  import("./read-only-facade-CHCKSEPD.js")
10261
9091
  ]);
10262
9092
  const registry = new DerivationRegistry();
@@ -10376,14 +9206,14 @@ var Vault = class {
10376
9206
  if (!reg) {
10377
9207
  throw new Error(`refreshView: no MV registered with name "${name}"`);
10378
9208
  }
10379
- const { MaterializedViewExecutor } = await import("./executor-KSPGNVDO.js");
9209
+ const { MaterializedViewExecutor } = await import("./executor-YDDC5XGY.js");
10380
9210
  const result = await MaterializedViewExecutor.refresh(reg, {
10381
9211
  getCollection: (n) => this.collection(n),
10382
9212
  getActiveTxContext: () => this.noydb._activeTxContextOrNull,
10383
9213
  getQueryContext: () => this,
10384
9214
  dispatchCtx: this._dispatchCtx({ collection: name, id: "refreshView" })
10385
9215
  });
10386
- const { clearMVStaleFully } = await import("./stale-KSPG6LFX.js");
9216
+ const { clearMVStaleFully } = await import("./stale-2CHKZTMK.js");
10387
9217
  await clearMVStaleFully(this.adapter, this.name, registry, name);
10388
9218
  return result;
10389
9219
  }
@@ -10925,7 +9755,7 @@ var Vault = class {
10925
9755
  * @see design-history/2026-05-22-schema-dump-design.md
10926
9756
  */
10927
9757
  async dumpSchema(opts = {}) {
10928
- const { dumpVaultSchema } = await import("./walk-G3CZIL4L.js");
9758
+ const { dumpVaultSchema } = await import("./walk-P3E6QSNC.js");
10929
9759
  return dumpVaultSchema(this, opts);
10930
9760
  }
10931
9761
  /**
@@ -11053,7 +9883,7 @@ var Vault = class {
11053
9883
  * both modes round-trip cleanly.
11054
9884
  */
11055
9885
  async dump() {
11056
- const { dumpVault } = await import("./backup-VASRVLDZ.js");
9886
+ const { dumpVault } = await import("./backup-R6A4VZRX.js");
11057
9887
  return dumpVault(this.backupContext());
11058
9888
  }
11059
9889
  /**
@@ -11105,7 +9935,7 @@ var Vault = class {
11105
9935
  * — there's no chain to verify against.
11106
9936
  */
11107
9937
  async load(backupJson) {
11108
- const { loadVault } = await import("./backup-VASRVLDZ.js");
9938
+ const { loadVault } = await import("./backup-R6A4VZRX.js");
11109
9939
  return loadVault(this.backupContext(), backupJson);
11110
9940
  }
11111
9941
  /**
@@ -11137,7 +9967,7 @@ var Vault = class {
11137
9967
  * way to detect tampering of an in-place vault.
11138
9968
  */
11139
9969
  async verifyBackupIntegrity() {
11140
- const { verifyBackupIntegrity } = await import("./backup-VASRVLDZ.js");
9970
+ const { verifyBackupIntegrity } = await import("./backup-R6A4VZRX.js");
11141
9971
  return verifyBackupIntegrity(this.backupContext());
11142
9972
  }
11143
9973
  /**
@@ -11367,7 +10197,7 @@ var Vault = class {
11367
10197
  * is the live validator object, not a serialization of it).
11368
10198
  */
11369
10199
  async exportJSON(opts = {}) {
11370
- const { exportVaultJSON } = await import("./backup-VASRVLDZ.js");
10200
+ const { exportVaultJSON } = await import("./backup-R6A4VZRX.js");
11371
10201
  return exportVaultJSON(this.backupContext(), opts);
11372
10202
  }
11373
10203
  };
@@ -14686,8 +13516,6 @@ function normalizeSyncTargets(sync) {
14686
13516
  export {
14687
13517
  memoryStore,
14688
13518
  withArchive,
14689
- via,
14690
- isViaFieldSpec,
14691
13519
  Collection,
14692
13520
  ELEVATION_AUDIT_COLLECTION,
14693
13521
  ElevatedHandle,
@@ -14702,4 +13530,4 @@ export {
14702
13530
  Noydb,
14703
13531
  createNoydb
14704
13532
  };
14705
- //# sourceMappingURL=chunk-I6DGKPTY.js.map
13533
+ //# sourceMappingURL=chunk-ZVCC36XQ.js.map