@noy-db/hub 0.7.0-pre.16 → 0.7.0-pre.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,107 @@
1
1
  # Changelog — hub
2
2
 
3
+ ## 0.7.0-pre.18
4
+
5
+ ### Patch Changes
6
+
7
+ - Two dependency-declaration defects, both invisible to every gate because both
8
+ were satisfied by something that never promised anything.
9
+
10
+ **`happy-dom` was used by 22 packages and declared by 4.** The `as-*`, `on-*`
11
+ and `by-*` families named it in their vitest `environment:` and never declared
12
+ it; it resolved only because `in-react`, `in-pinia`, `in-nuxt` and
13
+ `to-browser-idb` happened to declare it — at **three different majors**
14
+ (`^15.11.7`, `^17.4.4`, `^18.0.0`). So those suites ran against whichever
15
+ version won hoisting, pinned by nothing, and a devDependency bump in an
16
+ unrelated package could have silently changed the DOM implementation under
17
+ them. Every user now declares it, all at one range.
18
+
19
+ ⚠️ It is named by no `import` anywhere — only by a vitest config string, or (in
20
+ hub's case) by an `@vitest-environment` DOCBLOCK PRAGMA, which is invisible to
21
+ an import scan and a config grep alike. Found by extracting a family and
22
+ watching it fail to stand alone.
23
+
24
+ ⛔ **Declaring it does NOT make the mistake self-detecting, and a sweep alone
25
+ would have implied otherwise.** pnpm's virtual store still satisfies an
26
+ undeclared package from a sibling that declares it — deleting a declaration and
27
+ reinstalling leaves the suite green, including the test that needs the
28
+ environment. So correct manifests today prevent nothing tomorrow. A static
29
+ check (`pnpm check:test-env-deps`, wired into CI) is the part that enforces it;
30
+ the manifests are merely honest. (`require.resolve` reports the opposite —
31
+ Node's algorithm is not pnpm's store, so the probe has to read manifests rather
32
+ than resolve.)
33
+
34
+ **Sibling `peerDependencies` published as EXACT versions**, completing the fix
35
+ started in #1228. That issue converted the `hub` peer to `workspace:^` (which
36
+ publishes as a caret) and left sibling peers at `workspace:*` (which publishes
37
+ as an exact pin), so two satellites from different cuts were mutually
38
+ uninstallable by name. Install-proven with a discriminating control:
39
+
40
+ ```
41
+ as-xlsx@pre.17 + as-zip@pre.16 -> exit 1 the defect
42
+ as-xlsx@pre.17 + as-zip@pre.17 -> exit 0 control: it is the SKEW, not the pair
43
+ as-csv@pre.17 + in-vue@pre.16 -> exit 0 caret peers DO cross cuts
44
+ ```
45
+
46
+ Nine sibling edges across seven packages move `workspace:*` → `workspace:^`.
47
+ That changes the FORM without changing the KIND: they stay peers, so a consumer
48
+ still controls instance identity, and no per-pair judgement about whether
49
+ deduplication matters was required.
50
+
51
+ Already-published versions stay broken — an exact peer is frozen in a published
52
+ manifest. What this buys is that the next cut is not broken too.
53
+
54
+ - `check:test-env-deps` now catches OVER-declaration too, and one live instance is
55
+ removed.
56
+
57
+ The guard shipped with the previous fix caught only the missing direction — a
58
+ package running tests in an environment it does not declare. The mirror case
59
+ turns out to be the mechanism the whole class rests on: **a package that
60
+ declares an environment it never runs in is what silently satisfies the packages
61
+ that use it and do not declare it.** The over-declaration is what makes the
62
+ under-declaration invisible.
63
+
64
+ `to-browser-idb` carried exactly that — `happy-dom ^18.0.0` while running
65
+ `environment: 'node'` with a fake-indexeddb polyfill. It was plausibly the
66
+ declarer supplying 18.x to the nine `as-*` suites that named the environment and
67
+ declared nothing. Removed; every genuine user now declares its own.
68
+
69
+ Added after the same mistake was made twice in one day: a substring scan gave
70
+ `by-peer` a declaration for a mention in a COMMENT, and this predated all of it.
71
+
72
+ Mutation-checked in both directions — a package that genuinely uses the
73
+ environment keeps its declaration without firing (no false positive on
74
+ legitimate declarers), and restoring the phantom fails the check.
75
+
76
+ ## 0.7.0-pre.17
77
+
78
+ ### Patch Changes
79
+
80
+ - A DELETE through a `triggerBy` hop now fans out — it silently did not (#1294).
81
+
82
+ Reported by a consumer adopting the hop from `0.7.0-pre.16`: puts through a
83
+ `via` hop re-fired the derivation, deletes did not, while deletes through a
84
+ plain pair did. The delete path built its tuple with the UNHOPPED builder, so a
85
+ mapped pair compared the written record's `from` value against `source[to]` —
86
+ the wrong side of the relationship — and matched nothing. No error, no fan-out.
87
+
88
+ **Two gaps, not one.** Deleting the INTERMEDIATE record was also unhandled, and
89
+ strands every source it addressed for the same reason re-pointing one does:
90
+ nothing is written to the trigger or source collection, so no other path can
91
+ notice. The write path already covered that; the delete path did not, which made
92
+ the hop's correctness argument hold for puts only.
93
+
94
+ **The shipped typings said deletes fan out "for BOTH forms".** That read as
95
+ covering hops and did not — a behaviour gap and a prose-vs-artefact mismatch in
96
+ one. The sentence now enumerates `on`, plain `match`, `via` hops, and deletes of
97
+ the intermediate itself.
98
+
99
+ **The sync tuple builder is deleted rather than kept**, and that is the durable
100
+ half: two builders that had to agree is exactly how this drifted, because the
101
+ delete path called the wrong one. `resolveTuple` with no `via` does what the old
102
+ one did, its unit tests moved across unchanged, and one path cannot disagree
103
+ with itself.
104
+
3
105
  ## 0.7.0-pre.16
4
106
 
5
107
  ### Minor Changes
@@ -5,7 +5,7 @@ import {
5
5
  describeExtraction,
6
6
  unsealDeks,
7
7
  withCargo
8
- } from "../chunk-VNZUVPUO.js";
8
+ } from "../chunk-J6FXAEUG.js";
9
9
  import {
10
10
  diffVault
11
11
  } from "../chunk-LFUID7OW.js";
@@ -3020,13 +3020,13 @@ var Collection = class {
3020
3020
  * dispatch wave's per-target dedup; `undefined` on the local-write path (byte-identical). */
3021
3021
  async dispatchDerivations(id, record, version, wave, prior) {
3022
3022
  if (this.derivationSource === void 0) return;
3023
- const { dispatchDerivations } = await import("./dispatch-SYKRVTYW.js");
3023
+ const { dispatchDerivations } = await import("./dispatch-S73TK3IY.js");
3024
3024
  return dispatchDerivations(this.#derivationDispatchCtx(), id, record, version, wave, prior);
3025
3025
  }
3026
3026
  /** @internal — trigger fan-out for a deleted parent (#1249); see dispatch.ts. */
3027
3027
  async dispatchTriggerDerivationsOnDelete(id, deleted) {
3028
3028
  if (this.derivationSource === void 0) return;
3029
- const { dispatchTriggerDerivationsOnDelete } = await import("./dispatch-SYKRVTYW.js");
3029
+ const { dispatchTriggerDerivationsOnDelete } = await import("./dispatch-S73TK3IY.js");
3030
3030
  return dispatchTriggerDerivationsOnDelete(this.#derivationDispatchCtx(), id, deleted);
3031
3031
  }
3032
3032
  /**
@@ -3338,7 +3338,7 @@ var Collection = class {
3338
3338
  */
3339
3339
  async dispatchArrayDerivationsOnDelete(id, eraseRecordShapeToo = false) {
3340
3340
  if (this.derivationSource === void 0) return 0;
3341
- const { dispatchArrayDerivationsOnDelete } = await import("./dispatch-SYKRVTYW.js");
3341
+ const { dispatchArrayDerivationsOnDelete } = await import("./dispatch-S73TK3IY.js");
3342
3342
  return dispatchArrayDerivationsOnDelete(this.#derivationDeleteCtx(this.derivationSource), id, eraseRecordShapeToo);
3343
3343
  }
3344
3344
  /**
@@ -9086,7 +9086,7 @@ var Vault = class {
9086
9086
  async _initDerivations(handles) {
9087
9087
  if (handles.length === 0) return;
9088
9088
  const [{ DerivationRegistry }, { ReadOnlyVaultFacade }] = await Promise.all([
9089
- import("./registry-K734HBSO.js"),
9089
+ import("./registry-YLEWMGAC.js"),
9090
9090
  import("./read-only-facade-BIGO37U3.js")
9091
9091
  ]);
9092
9092
  const registry = new DerivationRegistry();
@@ -13530,4 +13530,4 @@ export {
13530
13530
  Noydb,
13531
13531
  createNoydb
13532
13532
  };
13533
- //# sourceMappingURL=chunk-URDWKAQQ.js.map
13533
+ //# sourceMappingURL=chunk-6GOE5PGN.js.map
@@ -11,15 +11,6 @@ function normalizeTriggerBy(triggerBy) {
11
11
  }));
12
12
  }
13
13
  var scalar = (v) => typeof v === "string" || typeof v === "number" ? String(v) : null;
14
- function tupleFromWritten(match, writtenId, record) {
15
- const out = [];
16
- for (const pair of match) {
17
- const v = pair.from === "id" ? writtenId : scalar(record?.[pair.from]);
18
- if (v === null) return null;
19
- out.push({ field: pair.to, value: v });
20
- }
21
- return out;
22
- }
23
14
  async function resolveTuple(match, writtenId, record, lookup) {
24
15
  const out = [];
25
16
  for (const pair of match) {
@@ -55,9 +46,8 @@ function sameTuple(a, b) {
55
46
  export {
56
47
  hopCollections,
57
48
  normalizeTriggerBy,
58
- tupleFromWritten,
59
49
  resolveTuple,
60
50
  tupleFromIntermediate,
61
51
  sameTuple
62
52
  };
63
- //# sourceMappingURL=chunk-6LMAVYOY.js.map
53
+ //# sourceMappingURL=chunk-FFWI7UH7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/with-formula/derivations/trigger-match.ts"],"sourcesContent":["/**\n * Pure helpers behind composite `triggerBy` (#1249).\n *\n * A trigger entry is a CONJUNCTION of equality pairs: a source record\n * matches when EVERY pair satisfies String(source[to]) === String(written[from]).\n * `from: 'id'` reads the written record's id (winning over any stored field\n * named `id`, matching dispatch's `{ ...incoming, id }` convention). The\n * legacy `on` form normalizes to `[{ from: 'id', to: on }]` so everything\n * downstream has ONE shape.\n *\n * Scalar coercion is verbatim from `_findMatchingIds`: both sides must be\n * string | number; anything else fails the pair (never throws).\n * Spec: docs/superpowers/specs/2026-08-29-composite-triggerby-design.md §4-§5.\n * @module\n */\n\n/** One declared hop through an intermediate collection (#1277). */\nexport interface MatchVia {\n /** The intermediate collection to resolve through. */\n readonly collection: string\n /** Field on the intermediate whose value equals `written[from]`. */\n readonly take: string\n /** Field on the intermediate whose value is compared against `source[to]`. */\n readonly on: string\n}\n\nexport interface MatchPair {\n readonly from: string\n readonly to: string\n readonly via?: MatchVia\n}\n\n/** True when any pair in this trigger resolves through an intermediate. */\nexport function hasHop(match: ReadonlyArray<MatchPair>): boolean {\n return match.some((p) => p.via !== undefined)\n}\n\n/** Every intermediate collection named by a trigger's pairs, deduplicated. */\nexport function hopCollections(match: ReadonlyArray<MatchPair>): string[] {\n return [...new Set(match.flatMap((p) => (p.via ? [p.via.collection] : [])))]\n}\n\nexport interface NormalizedTrigger {\n readonly collection: string\n readonly match: ReadonlyArray<MatchPair>\n readonly maxFanout?: number\n}\n\ninterface RawTrigger {\n readonly collection: string\n readonly on?: string\n readonly match?: ReadonlyArray<MatchPair>\n readonly maxFanout?: number\n}\n\nexport function normalizeTriggerBy(triggerBy: ReadonlyArray<RawTrigger> | undefined): NormalizedTrigger[] {\n if (triggerBy === undefined) return []\n return triggerBy.map((t) => ({\n collection: t.collection,\n match: t.match ?? [{ from: 'id', to: t.on! }],\n ...(t.maxFanout !== undefined ? { maxFanout: t.maxFanout } : {}),\n }))\n}\n\nconst scalar = (v: unknown): string | null =>\n (typeof v === 'string' || typeof v === 'number') ? String(v) : null\n\n/**\n * Resolve one trigger's pairs into the value tuple a written record presents,\n * following any declared hop (#1277).\n *\n * Non-hop pairs behave exactly as {@link tupleFromWritten}. A hop pair reads\n * `written[from]`, finds the intermediate record whose `via.take` equals it,\n * and substitutes that record's `via.on` value — so everything downstream\n * still compares a flat `{ field, value }` tuple against source records and\n * the existing index-backed matcher is unchanged.\n *\n * Returns `null` when ANY pair cannot produce a value — a missing field, a\n * non-scalar, or an intermediate that does not resolve. That is a legitimate\n * no-match, not an error: a dangling reference addresses nothing, and refusing\n * here would turn a data condition into a thrown exception on every write.\n *\n * Supersedes the sync `tupleFromWritten`, which was DELETED rather than kept\n * (#1294): having two tuple builders that had to agree is precisely how the\n * delete path drifted — it called the unhopped one, so a mapped pair compared\n * the wrong side of the relationship and matched nothing, silently. One path\n * cannot disagree with itself.\n *\n * ONE lookup per hop per written record. `take: 'id'` is a direct `get`; any\n * other `take` is a field match on the intermediate, which uses its index when\n * one is declared.\n */\nexport async function resolveTuple(\n match: ReadonlyArray<MatchPair>,\n writtenId: string,\n record: Record<string, unknown> | null,\n lookup: (collection: string, field: string, value: string) => Promise<Record<string, unknown> | null>,\n): Promise<Array<{ field: string; value: string }> | null> {\n const out: Array<{ field: string; value: string }> = []\n for (const pair of match) {\n const raw = pair.from === 'id' ? writtenId : scalar(record?.[pair.from])\n if (raw === null) return null\n if (pair.via === undefined) {\n out.push({ field: pair.to, value: raw })\n continue\n }\n const intermediate = await lookup(pair.via.collection, pair.via.take, raw)\n if (intermediate === null) return null\n const hopped = scalar(intermediate[pair.via.on])\n if (hopped === null) return null\n out.push({ field: pair.to, value: hopped })\n }\n return out\n}\n\n/**\n * The tuple a written INTERMEDIATE record presents (#1277 option 2).\n *\n * When a record of `via.collection` is written, the sources it addresses are\n * those whose `to` equals the intermediate's own `via.on` value — no lookup is\n * needed, because the intermediate IS the written record. Pairs that do not\n * hop through this collection cannot be constrained by such a write and are\n * skipped, which makes the fan-out deliberately WIDER than a trigger write:\n * refreshing too much is recoverable, refreshing too little is the silent\n * staleness this feature exists to remove.\n *\n * Returns `null` when no pair hops through `collection`, or when the\n * intermediate carries no scalar at `via.on`.\n */\nexport function tupleFromIntermediate(\n match: ReadonlyArray<MatchPair>,\n collection: string,\n record: Record<string, unknown> | null,\n): Array<{ field: string; value: string }> | null {\n const out: Array<{ field: string; value: string }> = []\n for (const pair of match) {\n if (pair.via?.collection !== collection) continue\n const v = scalar(record?.[pair.via.on])\n if (v === null) return null\n out.push({ field: pair.to, value: v })\n }\n return out.length > 0 ? out : null\n}\n\n/** Any single component differing means NOT the same tuple (spec §7). */\nexport function sameTuple(\n a: Array<{ field: string; value: string }> | null,\n b: Array<{ field: string; value: string }> | null,\n): boolean {\n if (a === null || b === null) return a === b\n return a.length === b.length && a.every((p, i) => p.field === b[i]!.field && p.value === b[i]!.value)\n}\n\n/** Moved to the kernel (#1249) so `collection.ts` can import statically without\n * dragging derivations code into the floor bundle; re-exported here for\n * compatibility with existing consumers of this module. */\nexport { recordMatchesPairs, findMatchingIdsByPairs } from '../../kernel/match-pairs.js'\n"],"mappings":";AAsCO,SAAS,eAAe,OAA2C;AACxE,SAAO,CAAC,GAAG,IAAI,IAAI,MAAM,QAAQ,CAAC,MAAO,EAAE,MAAM,CAAC,EAAE,IAAI,UAAU,IAAI,CAAC,CAAE,CAAC,CAAC;AAC7E;AAeO,SAAS,mBAAmB,WAAuE;AACxG,MAAI,cAAc,OAAW,QAAO,CAAC;AACrC,SAAO,UAAU,IAAI,CAAC,OAAO;AAAA,IAC3B,YAAY,EAAE;AAAA,IACd,OAAO,EAAE,SAAS,CAAC,EAAE,MAAM,MAAM,IAAI,EAAE,GAAI,CAAC;AAAA,IAC5C,GAAI,EAAE,cAAc,SAAY,EAAE,WAAW,EAAE,UAAU,IAAI,CAAC;AAAA,EAChE,EAAE;AACJ;AAEA,IAAM,SAAS,CAAC,MACb,OAAO,MAAM,YAAY,OAAO,MAAM,WAAY,OAAO,CAAC,IAAI;AA2BjE,eAAsB,aACpB,OACA,WACA,QACA,QACyD;AACzD,QAAM,MAA+C,CAAC;AACtD,aAAW,QAAQ,OAAO;AACxB,UAAM,MAAM,KAAK,SAAS,OAAO,YAAY,OAAO,SAAS,KAAK,IAAI,CAAC;AACvE,QAAI,QAAQ,KAAM,QAAO;AACzB,QAAI,KAAK,QAAQ,QAAW;AAC1B,UAAI,KAAK,EAAE,OAAO,KAAK,IAAI,OAAO,IAAI,CAAC;AACvC;AAAA,IACF;AACA,UAAM,eAAe,MAAM,OAAO,KAAK,IAAI,YAAY,KAAK,IAAI,MAAM,GAAG;AACzE,QAAI,iBAAiB,KAAM,QAAO;AAClC,UAAM,SAAS,OAAO,aAAa,KAAK,IAAI,EAAE,CAAC;AAC/C,QAAI,WAAW,KAAM,QAAO;AAC5B,QAAI,KAAK,EAAE,OAAO,KAAK,IAAI,OAAO,OAAO,CAAC;AAAA,EAC5C;AACA,SAAO;AACT;AAgBO,SAAS,sBACd,OACA,YACA,QACgD;AAChD,QAAM,MAA+C,CAAC;AACtD,aAAW,QAAQ,OAAO;AACxB,QAAI,KAAK,KAAK,eAAe,WAAY;AACzC,UAAM,IAAI,OAAO,SAAS,KAAK,IAAI,EAAE,CAAC;AACtC,QAAI,MAAM,KAAM,QAAO;AACvB,QAAI,KAAK,EAAE,OAAO,KAAK,IAAI,OAAO,EAAE,CAAC;AAAA,EACvC;AACA,SAAO,IAAI,SAAS,IAAI,MAAM;AAChC;AAGO,SAAS,UACd,GACA,GACS;AACT,MAAI,MAAM,QAAQ,MAAM,KAAM,QAAO,MAAM;AAC3C,SAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,CAAC,EAAG,SAAS,EAAE,UAAU,EAAE,CAAC,EAAG,KAAK;AACtG;","names":[]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  hopCollections,
3
3
  normalizeTriggerBy
4
- } from "./chunk-6LMAVYOY.js";
4
+ } from "./chunk-FFWI7UH7.js";
5
5
  import {
6
6
  ViaGraph,
7
7
  matchTargetFieldNames,
@@ -244,4 +244,4 @@ var DerivationRegistry = class {
244
244
  export {
245
245
  DerivationRegistry
246
246
  };
247
- //# sourceMappingURL=chunk-MWAHERVR.js.map
247
+ //# sourceMappingURL=chunk-HDOU6I2Y.js.map
@@ -252,7 +252,7 @@ async function createOwnerOnAdoptedPartition(store, vaultName, opts) {
252
252
  }
253
253
  }
254
254
  if (isManaged(opts)) {
255
- const { createNoydb } = await import("./noydb-RFEFYWE4.js");
255
+ const { createNoydb } = await import("./noydb-DUXZPKSN.js");
256
256
  const db = await createNoydb({
257
257
  store,
258
258
  user: userId,
@@ -307,4 +307,4 @@ export {
307
307
  withCargo,
308
308
  describeExtraction
309
309
  };
310
- //# sourceMappingURL=chunk-VNZUVPUO.js.map
310
+ //# sourceMappingURL=chunk-J6FXAEUG.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createNoydb
3
- } from "./chunk-URDWKAQQ.js";
3
+ } from "./chunk-6GOE5PGN.js";
4
4
  import {
5
5
  deriveSchemaManifest
6
6
  } from "./chunk-VXGSWL6L.js";
@@ -286,4 +286,4 @@ export {
286
286
  followRedirects,
287
287
  open
288
288
  };
289
- //# sourceMappingURL=chunk-LVS44C35.js.map
289
+ //# sourceMappingURL=chunk-WEQEYLCS.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  DerivationRegistry
3
- } from "../chunk-MWAHERVR.js";
4
- import "../chunk-6LMAVYOY.js";
3
+ } from "../chunk-HDOU6I2Y.js";
4
+ import "../chunk-FFWI7UH7.js";
5
5
  import {
6
6
  DerivationExecutor
7
7
  } from "../chunk-TFR7DSOV.js";
@@ -2,9 +2,8 @@ import {
2
2
  hopCollections,
3
3
  resolveTuple,
4
4
  sameTuple,
5
- tupleFromIntermediate,
6
- tupleFromWritten
7
- } from "./chunk-6LMAVYOY.js";
5
+ tupleFromIntermediate
6
+ } from "./chunk-FFWI7UH7.js";
8
7
  import {
9
8
  markStale
10
9
  } from "./chunk-SMH6SRX7.js";
@@ -247,21 +246,39 @@ async function dispatchTriggerDerivationsOnDelete(ctx, id, deleted) {
247
246
  if (spec.source === collectionName) continue;
248
247
  if (spec.sources?.includes(collectionName)) continue;
249
248
  const entries = triggers.filter((t) => t.collection === collectionName);
250
- if (entries.length === 0) continue;
249
+ const hopEntries = triggers.filter((t) => hopCollections(t.match).includes(collectionName));
250
+ if (entries.length === 0 && hopEntries.length === 0) continue;
251
251
  const mode = typeof spec.lifecycle === "string" ? spec.lifecycle : spec.lifecycle.mode;
252
252
  const srcColl = derivationSource.getCollection(spec.source);
253
253
  const matched = /* @__PURE__ */ new Set();
254
- for (const trigger of entries) {
255
- const tuple = tupleFromWritten(trigger.match, id, deleted);
256
- if (tuple === null) continue;
257
- const ids = await srcColl._findMatchingCompositeIds(tuple);
258
- if (trigger.maxFanout !== void 0 && ids.length > trigger.maxFanout) {
254
+ const lookup = async (coll, field, value) => {
255
+ const c = derivationSource.getCollection(coll);
256
+ if (field === "id") return c._getStoredRecord(value);
257
+ const found = await c._findMatchingCompositeIds([{ field, value }]);
258
+ const first = found[0];
259
+ return first === void 0 ? null : c._getStoredRecord(first);
260
+ };
261
+ const cap = (trigger, n, label) => {
262
+ if (trigger.maxFanout !== void 0 && n > trigger.maxFanout) {
259
263
  throw new DerivationCapExceededError(
260
- `triggerBy ${collectionName}\u2192${spec.source} [${trigger.match.map((p) => p.to).join(",")}] (delete)`,
261
- ids.length,
264
+ `triggerBy ${label} ${collectionName}\u2192${spec.source} [${trigger.match.map((p) => p.to).join(",")}] (delete)`,
265
+ n,
262
266
  trigger.maxFanout
263
267
  );
264
268
  }
269
+ };
270
+ for (const trigger of entries) {
271
+ const tuple = await resolveTuple(trigger.match, id, deleted, lookup);
272
+ if (tuple === null) continue;
273
+ const ids = await srcColl._findMatchingCompositeIds(tuple);
274
+ cap(trigger, ids.length, "");
275
+ for (const sid of ids) matched.add(sid);
276
+ }
277
+ for (const trigger of hopEntries) {
278
+ const tuple = tupleFromIntermediate(trigger.match, collectionName, deleted);
279
+ if (tuple === null) continue;
280
+ const ids = await srcColl._findMatchingCompositeIds(tuple);
281
+ cap(trigger, ids.length, "hop");
265
282
  for (const sid of ids) matched.add(sid);
266
283
  }
267
284
  if (matched.size === 0) continue;
@@ -282,4 +299,4 @@ export {
282
299
  dispatchDerivations,
283
300
  dispatchTriggerDerivationsOnDelete
284
301
  };
285
- //# sourceMappingURL=dispatch-SYKRVTYW.js.map
302
+ //# sourceMappingURL=dispatch-S73TK3IY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/with-formula/derivations/dispatch.ts"],"sourcesContent":["/**\n * Derivation dispatch, lifted out of the kernel spine (#842 part b).\n *\n * `Collection` carried ~380 lines of dispatch logic that belongs to the\n * derivation service, not to the always-on kernel. These are free functions\n * taking an explicit context so the spine keeps only a thin delegator.\n *\n * ## How the spine reaches this module\n *\n * By dynamic `import()`, never a static one. `check-architecture.mjs`'s\n * `port-layering` check forbids the spine from statically importing a\n * `with-*` service; `collection.ts` is grandfathered for ten\n * `with-formula/*` specifiers, but that list is frozen per file and a new\n * one is a violation. A dynamic import is the sanctioned S4 gate recipe, and\n * it keeps this chunk out of the floor bundle for consumers who never fire a\n * derivation.\n *\n * The `Collection` type reference below points back at the spine. That is\n * inward (service → kernel) and so allowed, and it is TYPE-ONLY — erased at\n * build, so no runtime cycle exists.\n *\n * @internal\n */\n\nimport type { NoydbStore } from '../../kernel/types.js'\nimport type { TxContext } from '../../with-commit/tx/transaction.js'\nimport type { DerivationRegistry } from './registry.js'\nimport type { ReadOnlyVaultFacade } from '../../with-audit/guards/types.js'\nimport type { Collection } from '../../kernel/collection.js'\nimport type { EnclaveKey } from '../../kernel/enclave/index.js'\nimport type { EncryptedEnvelope } from '../../kernel/types.js'\nimport type { ledgerAuditHook, WaveContext } from '../../kernel/via/dispatch.js'\nimport { putDerivedOutput, selfWriteFieldEqual } from '../../kernel/via/dispatch.js'\nimport { DerivationCapExceededError } from '../../kernel/errors.js'\nimport { sameTuple, resolveTuple, tupleFromIntermediate, hopCollections } from './trigger-match.js'\nimport { markStale } from './stale.js'\nimport type { DerivationExecutor } from './executor.js'\n\n/** The per-write dispatch context threaded into `putDerivedOutput`. */\ninterface DispatchCtx {\n emit: (e: string, p: unknown) => void\n source: { readonly collection: string; readonly id: string }\n audit: ReturnType<typeof ledgerAuditHook>\n}\n\n/**\n * What the dispatchers need from the collection that owns them. Passing this\n * explicitly is what lets the logic live outside the class.\n */\nexport interface DerivationDeleteCtx {\n readonly derivationSource: {\n registry(): DerivationRegistry\n getCollection(name: string): Collection<Record<string, unknown>>\n getActiveTxContext(): TxContext | null\n getReadOnlyFacade(): ReadOnlyVaultFacade\n }\n /** The collection the delete happened in. */\n readonly collectionName: string\n readonly adapter: NoydbStore\n readonly vault: string\n readonly getDEK: (collectionName: string) => Promise<EnclaveKey>\n readonly storeCiphertext: boolean\n}\n\n/**\n * Erase the derived rows a deleted source record fanned out to, and drop the\n * fan-out sidecars that recorded them.\n *\n * @param eraseRecordShapeToo also erase same-id `shape: 'record'` outputs.\n * Restricted to a source-triggered strategy writing into a DIFFERENT\n * collection — the self-denorm case would re-delete the record just\n * tombstoned, and triggerBy/sibling strategies derive under a different id.\n * @returns rows ACTUALLY deleted, not edges visited (#622).\n */\nexport async function dispatchArrayDerivationsOnDelete(\n ctx: DerivationDeleteCtx,\n id: string,\n eraseRecordShapeToo = false,\n): Promise<number> {\n const { derivationSource, collectionName, adapter, vault, getDEK, storeCiphertext } = ctx\n\n const strategies = derivationSource.registry().strategiesForSource(collectionName)\n if (strategies.length === 0) return 0\n\n const { loadFanoutSidecar, deleteFanoutSidecar } = await import('./fanout-sidecar.js')\n const txCtx = derivationSource.getActiveTxContext()\n let erased = 0\n\n for (const { spec } of strategies) {\n for (const [outputKey, outSpec] of Object.entries(spec.outputs)) {\n if (outSpec.shape === 'record') {\n if (eraseRecordShapeToo && spec.source === collectionName && outSpec.collection !== collectionName) {\n if (await derivationSource.getCollection(outSpec.collection)._internalDelete(id, txCtx)) erased += 1\n }\n continue\n }\n\n const sidecar = await loadFanoutSidecar(adapter, vault, spec.source, id, outputKey, getDEK, storeCiphertext)\n if (!sidecar) continue\n\n const outputCollection = derivationSource.getCollection(outSpec.collection)\n for (const derivedId of sidecar.keys) {\n if (await outputCollection._internalDelete(derivedId, txCtx)) erased += 1\n }\n await deleteFanoutSidecar(adapter, vault, spec.source, id, outputKey)\n }\n }\n\n return erased\n}\n\n/** Adds what the on-write derivation dispatch needs beyond the delete path. */\nexport interface DerivationDispatchCtx extends DerivationDeleteCtx {\n /** Via pipeline, for decoding the stored form back to canonical money shape. */\n readonly via: { canonicalizeStored(r: Record<string, unknown>): Record<string, unknown> } | undefined\n /** Recompute a rollup aggregate onto a parent. Stays on the spine — it walks collections. */\n readonly recomputeRollup: (\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n spec: any,\n parentId: string,\n source: { readonly collection: string; readonly id: string },\n wave?: WaveContext,\n ) => Promise<unknown>\n /** Builds the per-write dispatch context threaded into `putDerivedOutput`. */\n readonly dispatchCtx: (source: { readonly collection: string; readonly id: string }) => DispatchCtx\n /** Registers a write on the active transaction for rollback. */\n readonly trackPut: (txCtx: TxContext, collectionName: string, id: string, priorEnvelope: EncryptedEnvelope | null) => void\n}\n\n/**\n * Fire registered derivation strategies for this source collection.\n *\n * Eager mode runs `derive` inline and writes each output; lazy mode records a\n * stale mark for cold-session recompute. Rollups are handled here rather than\n * by the executor — a write to the child recomputes the parent aggregate, and\n * a write to the parent recomputes its own.\n */\nexport async function dispatchDerivations(\n ctx: DerivationDispatchCtx,\n id: string,\n record: Record<string, unknown>,\n version: number,\n wave?: WaveContext,\n /** The pre-write record at `id` in THIS collection (undefined = not\n * captured — no field-match trigger, or a sync-applied wave; null =\n * captured and absent, i.e. a create). Only a `Record` re-fires the OLD\n * matched set alongside the new one (spec §7). */\n prior?: Record<string, unknown> | null,\n): Promise<void> {\n const { derivationSource, collectionName, via, recomputeRollup } = ctx\n\n // `record` is the stored form here (post-quantize) — decode so\n // derive(source, ctx) sees the canonical money shape.\n const incoming = (via ? via.canonicalizeStored(record) : record)\n if (incoming && typeof incoming === 'object' && '_derivedFrom' in incoming) return\n // `prior` is the raw stored pre-write record — canonicalize it the same\n // way as `incoming` before it feeds `resolveTuple` below, or a\n // via-shaped match field (e.g. a money field) compares canonical against\n // raw and never matches the old tuple (Min 5).\n const canonicalPrior = prior != null ? (via ? via.canonicalizeStored(prior) : prior) : prior\n const registry = derivationSource.registry()\n const strategies = registry.strategiesForSource(collectionName)\n if (strategies.length === 0) return\n // Dynamic-import the executor only on the first eager-mode\n // dispatch. Lazy-mode dispatches use `markStale` (a pure helper)\n // which doesn't reach into the executor at all. Keeps the\n // derivation executor chunk out of the floor bundle for any\n // consumer that doesn't fire an eager derivation.\n let executorClass: typeof DerivationExecutor | null = null\n for (const { spec, strategyHash, triggers } of strategies) {\n const mode = typeof spec.lifecycle === 'string' ? spec.lifecycle : spec.lifecycle.mode\n\n // Rollup: a write to the child `from` recomputes the\n // parent at id child[key]; a write to the parent (source = into)\n // recomputes its own aggregate. Handled here (the executor is not run).\n if (spec.rollup) {\n if (mode !== 'eager') continue\n const asParentId = (v: unknown): string | null =>\n (typeof v === 'string' || typeof v === 'number') ? String(v) : null\n let parentId: string | null\n // #1257 — a child that RE-PARENTS strands the old parent's aggregate\n // unless the old key is recomputed too. Reads the prior stored record\n // (canonicalized alongside `incoming`, so a via-shaped key compares like\n // for like). `undefined` prior — a sync-applied wave write or a tiers\n // restore, which do not thread it — degrades to new-parent-only, the\n // pre-#1257 behaviour, rather than guessing.\n let oldParentId: string | null = null\n if (collectionName === spec.rollup.from) {\n parentId = asParentId(incoming[spec.rollup.key])\n const priorKey = canonicalPrior != null ? asParentId(canonicalPrior[spec.rollup.key]) : null\n if (priorKey !== null && priorKey !== parentId) oldParentId = priorKey\n } else {\n parentId = id // a write to the parent recomputes its own aggregate\n }\n if (parentId !== null) await recomputeRollup(spec, parentId, { collection: collectionName, id }, wave)\n // The old parent is recomputed AFTER the new one so a `maxFanout`-style\n // failure on the new parent surfaces before extra work is spent.\n if (oldParentId !== null) await recomputeRollup(spec, oldParentId, { collection: collectionName, id }, wave)\n continue\n }\n\n // Determine how `collectionName` triggers this strategy, and build the list\n // of source records to (re-)derive:\n // • source — re-derive the written record itself (same-id).\n // • sources[] — re-derive the PRIMARY source at the same id.\n // • triggerBy — FK fan-out: re-derive every source record\n // whose `on` field equals the written parent's id.\n // `input` is passed to derive(); `base` is the raw stored source record\n // used as the patch base for a self-write reverse-denorm output.\n const isSource = spec.source === collectionName\n const isSibling = !isSource && (spec.sources?.includes(collectionName) ?? false)\n const triggerEntries = !isSource && !isSibling\n ? triggers.filter((t) => t.collection === collectionName)\n : []\n // #1277 option 2 — a write to an INTERMEDIATE collection fires the trigger\n // too. Without this, re-pointing the intermediate strands every source it\n // used to address: nothing is written to the trigger or source collection,\n // so no other path can notice. Kept separate from `triggerEntries` because\n // the tuple is derived differently (the intermediate IS the written record,\n // so no lookup is needed).\n const hopEntries = !isSource && !isSibling\n ? triggers.filter((t) => hopCollections(t.match).includes(collectionName))\n : []\n\n const runs: Array<{\n input: Record<string, unknown> & { id: string }\n base: Record<string, unknown>\n runId: string\n version: number\n }> = []\n\n if (isSource) {\n runs.push({ input: { ...incoming, id }, base: incoming, runId: id, version })\n } else if (isSibling) {\n const p = await derivationSource.getCollection(spec.source).get(id)\n if (p !== null && p !== undefined) {\n // Raw base for a (rare) sibling self-write; falls back to the\n // resolved primary if the raw read misses.\n const raw = await derivationSource.getCollection(spec.source)._getStoredRecord(id)\n runs.push({ input: { ...p, id }, base: raw ?? p, runId: id, version: 0 })\n }\n } else if (triggerEntries.length > 0 || hopEntries.length > 0) {\n const srcColl = derivationSource.getCollection(spec.source)\n const matched = new Set<string>()\n // One lookup per hop per written record — `take: 'id'` is a direct get,\n // anything else is a single field match on the intermediate. Never per\n // candidate row (#1266's bound).\n const lookup = async (coll: string, field: string, value: string): Promise<Record<string, unknown> | null> => {\n const c = derivationSource.getCollection(coll)\n if (field === 'id') return c._getStoredRecord(value)\n const ids = await c._findMatchingCompositeIds([{ field, value }])\n const first = ids[0]\n return first === undefined ? null : c._getStoredRecord(first)\n }\n // A write to an intermediate collection: fan out on its old ∪ new value.\n // The prior record is the intermediate's own, so #1249's capture already\n // supplies it — the same mechanism, pointed at a third collection.\n for (const trigger of hopEntries) {\n const tuples = [tupleFromIntermediate(trigger.match, collectionName, incoming)]\n if (canonicalPrior != null) {\n const old = tupleFromIntermediate(trigger.match, collectionName, canonicalPrior)\n if (!sameTuple(old, tuples[0]!)) tuples.push(old)\n }\n const ids = new Set<string>()\n for (const tuple of tuples) {\n if (tuple === null) continue\n for (const sid of await srcColl._findMatchingCompositeIds(tuple)) ids.add(sid)\n }\n if (trigger.maxFanout !== undefined && ids.size > trigger.maxFanout) {\n throw new DerivationCapExceededError(\n `triggerBy hop ${collectionName}→${spec.source} [${trigger.match.map(p => p.to).join(',')}]`,\n ids.size, trigger.maxFanout)\n }\n for (const sid of ids) matched.add(sid)\n }\n for (const trigger of triggerEntries) {\n // Union fan-out (spec §7): an update changing any matched component\n // must re-fire BOTH the old and the new matched set, or the old\n // set's derived output goes stale (never re-derived once it no\n // longer matches). `prior` is only ever a `Record` here — a wave\n // dispatch and a create both skip the old tuple outright.\n const tuples = [await resolveTuple(trigger.match, id, incoming, lookup)]\n if (canonicalPrior != null && trigger.match.some((p) => p.from !== 'id')) {\n const old = await resolveTuple(trigger.match, id, canonicalPrior, lookup)\n if (!sameTuple(old, tuples[0]!)) tuples.push(old)\n }\n const ids = new Set<string>()\n for (const tuple of tuples) {\n if (tuple === null) continue // a from-value is absent/non-scalar: matches nothing\n for (const sid of await srcColl._findMatchingCompositeIds(tuple)) ids.add(sid)\n }\n if (trigger.maxFanout !== undefined && ids.size > trigger.maxFanout) {\n throw new DerivationCapExceededError(\n `triggerBy ${collectionName}→${spec.source} [${trigger.match.map(p => p.to).join(',')}]`,\n ids.size, trigger.maxFanout)\n }\n for (const sid of ids) matched.add(sid)\n }\n for (const sid of matched) {\n const raw = await srcColl._getStoredRecord(sid)\n if (raw === null) continue\n runs.push({ input: { ...raw, id: sid }, base: raw, runId: sid, version: 0 })\n }\n }\n\n if (runs.length === 0) continue\n\n if (mode !== 'eager') {\n for (const run of runs) await markStale(registry, spec, run.runId)\n continue\n }\n\n if (executorClass === null) {\n ;({ DerivationExecutor: executorClass } = await import('./executor.js'))\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n for (const run of runs) await runOne(ctx, spec as any, strategyHash, executorClass, run)\n }\n}\n\n/**\n * Run the executor for one (spec, run) pair and write its outputs. Shared by\n * `dispatchDerivations`' eager loop and `dispatchTriggerDerivationsOnDelete`\n * (#1249) — the write-out logic (array diff, record self-write reverse-denorm,\n * normal record output) is identical for a live write and a delete-triggered\n * re-derive; only how `runs` gets built differs.\n */\nasync function runOne(\n ctx: DerivationDispatchCtx,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n spec: any,\n strategyHash: string,\n executorClass: typeof DerivationExecutor,\n run: { input: Record<string, unknown> & { id: string }; base: Record<string, unknown>; runId: string; version: number },\n): Promise<void> {\n const { derivationSource, adapter, vault, getDEK, storeCiphertext, dispatchCtx, trackPut } = ctx\n const execCtx = { vault: derivationSource.getReadOnlyFacade() }\n const outCtx = dispatchCtx({ collection: spec.source, id: run.runId })\n const result = await executorClass.run(spec, run.input, run.version, strategyHash, execCtx)\n for (const key of Object.keys(spec.outputs)) {\n const out = result.outputs[key]\n if (!out) continue\n if (out.kind === 'failed') {\n const err = out.error\n if (spec.strict) throw err\n console.warn(`[derivation] output \"${key}\" for source \"${spec.source}\" id=\"${run.runId}\" failed:`, err)\n continue\n }\n const outSpec = spec.outputs[key]\n if (!outSpec) continue\n const outputCollection = derivationSource.getCollection(outSpec.collection)\n // If we're inside a multi-record transaction, register\n // derived writes as side-effect ops on the active ctx\n // BEFORE they fire. `revertExecuted` walks `_executed` in\n // reverse on rollback, so capturing the pre-write envelope\n // here lets a later mid-batch failure restore this output's\n // prior state alongside the source op. Outside a transaction\n // the context is null and tracking is skipped.\n const txCtx = derivationSource.getActiveTxContext()\n\n // ── Array-shape branch ─────────────────────────────────\n if (out.kind === 'array') {\n // Load the prior key set from the fanout sidecar.\n const { loadFanoutSidecar, saveFanoutSidecar } = await import('./fanout-sidecar.js')\n const prior = await loadFanoutSidecar(adapter, vault, spec.source, run.runId, key, getDEK, storeCiphertext)\n const prevKeys = new Set<string>(prior?.keys ?? [])\n const newKeysList = out.entries.map((e: { key: string }) => e.key)\n const newKeysSet = new Set<string>(newKeysList)\n\n // Diff — delete keys that were in prev but not in new.\n for (const k of prevKeys) {\n if (newKeysSet.has(k)) continue\n await outputCollection._internalDelete(k, txCtx)\n }\n\n // Upsert every entry in the new set. (Slice 1: no\n // identity-skip optimisation; write every row, idempotent\n // at the (collection, id) level.)\n for (const entry of out.entries) {\n if (txCtx !== null) {\n trackPut(txCtx, outSpec.collection, entry.key, await adapter.get(vault, outSpec.collection, entry.key))\n }\n await putDerivedOutput(outputCollection, entry.key, entry.value, outCtx, { source: 'derived' })\n }\n\n // Persist the new key set last, for failure-mode symmetry.\n await saveFanoutSidecar(adapter, vault, {\n source: spec.source,\n sourceId: run.runId,\n outputKey: key,\n outputCollection: outSpec.collection,\n keys: newKeysList,\n }, getDEK, storeCiphertext)\n continue\n }\n\n // ── Record-shape branch ────────────────────────────────\n if (out.skipped === true) {\n // Optional output returned null. Delete the\n // previously-emitted output at this id, if any. Routed\n // through `_internalDelete` so a user-registered\n // `onDelete` on the output collection does NOT\n // fire — this is a system-internal tombstone, not a\n // user-initiated delete. The txCtx hookup captures the\n // prior envelope inside `_internalDelete` for rollback\n // symmetry; delete-of-absent is a silent no-op.\n await outputCollection._internalDelete(run.runId, txCtx)\n continue\n }\n\n // ── Self-write reverse-denorm ───────────────────────────\n // An output back to its own source: patch ONLY the declared\n // `denorm` fields onto the raw stored record, never the whole\n // value (which would clobber user fields / i18n maps and carries\n // the executor's `_derivedFrom` tag). If the patch changes\n // nothing, skip the write — that value-equality is the cycle\n // guard: the self-write re-fires the source-path derivation,\n // which recomputes identical fields and terminates here.\n if (outSpec.shape === 'record' && outSpec.denorm !== undefined && outSpec.collection === spec.source) {\n const value = out.value\n const patched: Record<string, unknown> = { ...run.base }\n let changed = false\n for (const f of outSpec.denorm) {\n if (!selfWriteFieldEqual(run.base[f], value[f])) {\n patched[f] = value[f]\n changed = true\n }\n }\n if (!changed) continue // cycle guard — nothing to write\n if (txCtx !== null) {\n trackPut(txCtx, outSpec.collection, run.runId, await adapter.get(vault, outSpec.collection, run.runId))\n }\n await putDerivedOutput(outputCollection, run.runId, patched, outCtx, { source: 'derived' })\n continue\n }\n\n // ── Normal record output (separate output collection) ──\n if (txCtx !== null) {\n trackPut(txCtx, outSpec.collection, run.runId, await adapter.get(vault, outSpec.collection, run.runId))\n }\n await putDerivedOutput(outputCollection, run.runId, out.value, outCtx, { source: 'derived' })\n }\n}\n\n/**\n * Trigger fan-out for a DELETED parent record (#1249, spec §8). Distinct from\n * the \"record-shape derivations not dispatched on delete\" rule — that is\n * about deleting a SOURCE record; this fires when a TRIGGER collection's\n * record is deleted, re-deriving source records that still exist. Pairs\n * evaluate against the tombstoned record's values; matched sources re-derive\n * through the normal executor (their derive() reads the now-absent parent\n * and decides what that means — the engine never cascades deletes).\n */\nexport async function dispatchTriggerDerivationsOnDelete(\n ctx: DerivationDispatchCtx, id: string, deleted: Record<string, unknown>,\n): Promise<void> {\n const { derivationSource, collectionName } = ctx\n const registry = derivationSource.registry()\n const strategies = registry.strategiesForSource(collectionName)\n if (strategies.length === 0) return\n let executorClass: typeof DerivationExecutor | null = null\n for (const { spec, strategyHash, triggers } of strategies) {\n if (spec.rollup) continue // rollup-on-delete already exists\n if (spec.source === collectionName) continue // source delete: existing rule, untouched\n if (spec.sources?.includes(collectionName)) continue // declared sibling source: same exclusion as the write path's isSibling check\n const entries = triggers.filter((t) => t.collection === collectionName)\n // #1294 — deleting an INTERMEDIATE strands every source it addressed, for\n // the same reason re-pointing one does: nothing is written to the trigger\n // or source collection. The write path handles this; the delete path did\n // not, which made the hop's correctness argument hold for puts only.\n const hopEntries = triggers.filter((t) => hopCollections(t.match).includes(collectionName))\n if (entries.length === 0 && hopEntries.length === 0) continue\n const mode = typeof spec.lifecycle === 'string' ? spec.lifecycle : spec.lifecycle.mode\n const srcColl = derivationSource.getCollection(spec.source)\n const matched = new Set<string>()\n const lookup = async (coll: string, field: string, value: string): Promise<Record<string, unknown> | null> => {\n const c = derivationSource.getCollection(coll)\n if (field === 'id') return c._getStoredRecord(value)\n const found = await c._findMatchingCompositeIds([{ field, value }])\n const first = found[0]\n return first === undefined ? null : c._getStoredRecord(first)\n }\n const cap = (trigger: { maxFanout?: number; match: ReadonlyArray<{ to: string }> }, n: number, label: string): void => {\n if (trigger.maxFanout !== undefined && n > trigger.maxFanout) {\n throw new DerivationCapExceededError(\n `triggerBy ${label} ${collectionName}→${spec.source} [${trigger.match.map(p => p.to).join(',')}] (delete)`,\n n, trigger.maxFanout)\n }\n }\n for (const trigger of entries) {\n // #1294 — resolve the hop here too. The old sync builder compared the raw\n // `from` value against `source[to]`, which for a mapped pair is the\n // wrong side of the relationship: it matched nothing, silently, so a\n // delete through a hop fanned out to no one while a delete through a\n // plain pair worked.\n const tuple = await resolveTuple(trigger.match, id, deleted, lookup)\n if (tuple === null) continue\n const ids = await srcColl._findMatchingCompositeIds(tuple)\n cap(trigger, ids.length, '')\n for (const sid of ids) matched.add(sid)\n }\n for (const trigger of hopEntries) {\n const tuple = tupleFromIntermediate(trigger.match, collectionName, deleted)\n if (tuple === null) continue\n const ids = await srcColl._findMatchingCompositeIds(tuple)\n cap(trigger, ids.length, 'hop')\n for (const sid of ids) matched.add(sid)\n }\n if (matched.size === 0) continue\n if (mode !== 'eager') { for (const sid of matched) await markStale(registry, spec, sid); continue }\n if (executorClass === null) ({ DerivationExecutor: executorClass } = await import('./executor.js'))\n for (const sid of matched) {\n const raw = await srcColl._getStoredRecord(sid)\n if (raw === null) continue\n await runOne(ctx, spec, strategyHash, executorClass, { input: { ...raw, id: sid }, base: raw, runId: sid, version: 0 })\n }\n }\n}\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -63,7 +63,7 @@ import {
63
63
  import {
64
64
  decryptExtractedPartition,
65
65
  withCargo
66
- } from "./chunk-VNZUVPUO.js";
66
+ } from "./chunk-J6FXAEUG.js";
67
67
  import {
68
68
  diffVault
69
69
  } from "./chunk-LFUID7OW.js";
@@ -121,7 +121,7 @@ import {
121
121
  signRedirect,
122
122
  verifyRedirect,
123
123
  wrapPodStore
124
- } from "./chunk-LVS44C35.js";
124
+ } from "./chunk-WEQEYLCS.js";
125
125
  import {
126
126
  Collection,
127
127
  ELEVATION_AUDIT_COLLECTION,
@@ -138,7 +138,7 @@ import {
138
138
  ref,
139
139
  refArray,
140
140
  withArchive
141
- } from "./chunk-URDWKAQQ.js";
141
+ } from "./chunk-6GOE5PGN.js";
142
142
  import {
143
143
  deriveSchemaManifest,
144
144
  loadSchemaManifestEntry
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Noydb,
3
3
  createNoydb
4
- } from "./chunk-URDWKAQQ.js";
4
+ } from "./chunk-6GOE5PGN.js";
5
5
  import "./chunk-EPFF7LOX.js";
6
6
  import "./chunk-QIZPZMUI.js";
7
7
  import "./chunk-TUFM4UZP.js";
@@ -87,4 +87,4 @@ export {
87
87
  Noydb,
88
88
  createNoydb
89
89
  };
90
- //# sourceMappingURL=noydb-RFEFYWE4.js.map
90
+ //# sourceMappingURL=noydb-DUXZPKSN.js.map
package/dist/pod/index.js CHANGED
@@ -5,8 +5,8 @@ import {
5
5
  signRedirect,
6
6
  verifyRedirect,
7
7
  wrapPodStore
8
- } from "../chunk-LVS44C35.js";
9
- import "../chunk-URDWKAQQ.js";
8
+ } from "../chunk-WEQEYLCS.js";
9
+ import "../chunk-6GOE5PGN.js";
10
10
  import {
11
11
  deriveSchemaManifest,
12
12
  loadSchemaManifestEntry
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  DerivationRegistry
3
- } from "./chunk-MWAHERVR.js";
4
- import "./chunk-6LMAVYOY.js";
3
+ } from "./chunk-HDOU6I2Y.js";
4
+ import "./chunk-FFWI7UH7.js";
5
5
  import "./chunk-2C3AQB46.js";
6
6
  import "./chunk-TFMM5DBB.js";
7
7
  import "./chunk-RTBJ5IOO.js";
@@ -29,4 +29,4 @@ import "./chunk-7O3Y4PPD.js";
29
29
  export {
30
30
  DerivationRegistry
31
31
  };
32
- //# sourceMappingURL=registry-K734HBSO.js.map
32
+ //# sourceMappingURL=registry-YLEWMGAC.js.map
@@ -43,15 +43,6 @@ interface RawTrigger {
43
43
  readonly maxFanout?: number;
44
44
  }
45
45
  export declare function normalizeTriggerBy(triggerBy: ReadonlyArray<RawTrigger> | undefined): NormalizedTrigger[];
46
- /**
47
- * The value tuple a written record presents to one trigger entry.
48
- * `null` means "this record cannot address any source" (a from-field is
49
- * absent or non-scalar) — a legitimate no-match, not an error.
50
- */
51
- export declare function tupleFromWritten(match: ReadonlyArray<MatchPair>, writtenId: string, record: Record<string, unknown> | null): Array<{
52
- field: string;
53
- value: string;
54
- }> | null;
55
46
  /**
56
47
  * Resolve one trigger's pairs into the value tuple a written record presents,
57
48
  * following any declared hop (#1277).
@@ -67,6 +58,12 @@ export declare function tupleFromWritten(match: ReadonlyArray<MatchPair>, writte
67
58
  * no-match, not an error: a dangling reference addresses nothing, and refusing
68
59
  * here would turn a data condition into a thrown exception on every write.
69
60
  *
61
+ * Supersedes the sync `tupleFromWritten`, which was DELETED rather than kept
62
+ * (#1294): having two tuple builders that had to agree is precisely how the
63
+ * delete path drifted — it called the unhopped one, so a mapped pair compared
64
+ * the wrong side of the relationship and matched nothing, silently. One path
65
+ * cannot disagree with itself.
66
+ *
70
67
  * ONE lookup per hop per written record. `take: 'id'` is a direct `get`; any
71
68
  * other `take` is a field match on the intermediate, which uses its index when
72
69
  * one is declared.
@@ -194,7 +194,11 @@ export interface DerivationSpec<TSource extends Record<string, unknown>, TOutput
194
194
  * component changing counts) — the prior record is threaded only
195
195
  * from the local write path, so a sync-applied wave write or a
196
196
  * tiers restore fans out on the new tuple only. Parent DELETES fan
197
- * out using the tombstoned record's values — for BOTH forms. A
197
+ * out using the tombstoned record's values — for the `on` form, the
198
+ * plain `match` form, AND a `via` hop, including a delete of the
199
+ * INTERMEDIATE record itself (#1294; before that fix "both forms"
200
+ * meant `on` | `match` and silently excluded hops, which is a
201
+ * sentence a consumer reasonably read the other way). A
198
202
  * missing/non-scalar `from` value matches nothing. Match fields
199
203
  * are validated against the collections' enumerable field sets
200
204
  * where possible (silent for TS-generic collections — see the
@@ -226,8 +230,9 @@ export interface DerivationSpec<TSource extends Record<string, unknown>, TOutput
226
230
  * `take` is `'id'`), not one per candidate row — the same bound that
227
231
  * made #1266 refuse virtual computed fields as match targets.
228
232
  *
229
- * ⚠️ A write to `via.collection` ALSO fires this trigger (#1277
230
- * option 2), fanning out on the intermediate's old ∪ new value. That
233
+ * ⚠️ A write OR DELETE of a `via.collection` record ALSO fires this
234
+ * trigger (#1277 option 2, deletes added in #1294), fanning out on
235
+ * the intermediate's old ∪ new value. That
231
236
  * is what keeps a re-pointed intermediate from silently stranding
232
237
  * every source it used to address; the alternative failed silently on
233
238
  * exactly the rare edit nobody repeats.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noy-db/hub",
3
- "version": "0.7.0-pre.16",
3
+ "version": "0.7.0-pre.18",
4
4
  "description": "Zero-knowledge, offline-first, encrypted document store — core library with AES-256-GCM, PBKDF2, multi-user keyring, and sync engine",
5
5
  "license": "MIT",
6
6
  "author": "vLannaAi <vicio@lanna.ai>",
@@ -233,14 +233,15 @@
233
233
  "node": ">=22.0.0"
234
234
  },
235
235
  "dependencies": {
236
- "@noy-db/attestation": "0.7.0-pre.16"
236
+ "@noy-db/attestation": "0.7.0-pre.18"
237
237
  },
238
238
  "devDependencies": {
239
239
  "@types/node": "^22.0.0",
240
240
  "esbuild": "^0.25.0",
241
+ "happy-dom": "^18.0.0",
241
242
  "zod": "^4.0.0",
242
243
  "zod-to-json-schema": "^3.25.2",
243
- "@noy-db/on-shamir": "0.7.0-pre.16"
244
+ "@noy-db/on-shamir": "0.7.0-pre.18"
244
245
  },
245
246
  "peerDependencies": {
246
247
  "zod": "^3.0.0 || ^4.0.0",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/with-formula/derivations/trigger-match.ts"],"sourcesContent":["/**\n * Pure helpers behind composite `triggerBy` (#1249).\n *\n * A trigger entry is a CONJUNCTION of equality pairs: a source record\n * matches when EVERY pair satisfies String(source[to]) === String(written[from]).\n * `from: 'id'` reads the written record's id (winning over any stored field\n * named `id`, matching dispatch's `{ ...incoming, id }` convention). The\n * legacy `on` form normalizes to `[{ from: 'id', to: on }]` so everything\n * downstream has ONE shape.\n *\n * Scalar coercion is verbatim from `_findMatchingIds`: both sides must be\n * string | number; anything else fails the pair (never throws).\n * Spec: docs/superpowers/specs/2026-08-29-composite-triggerby-design.md §4-§5.\n * @module\n */\n\n/** One declared hop through an intermediate collection (#1277). */\nexport interface MatchVia {\n /** The intermediate collection to resolve through. */\n readonly collection: string\n /** Field on the intermediate whose value equals `written[from]`. */\n readonly take: string\n /** Field on the intermediate whose value is compared against `source[to]`. */\n readonly on: string\n}\n\nexport interface MatchPair {\n readonly from: string\n readonly to: string\n readonly via?: MatchVia\n}\n\n/** True when any pair in this trigger resolves through an intermediate. */\nexport function hasHop(match: ReadonlyArray<MatchPair>): boolean {\n return match.some((p) => p.via !== undefined)\n}\n\n/** Every intermediate collection named by a trigger's pairs, deduplicated. */\nexport function hopCollections(match: ReadonlyArray<MatchPair>): string[] {\n return [...new Set(match.flatMap((p) => (p.via ? [p.via.collection] : [])))]\n}\n\nexport interface NormalizedTrigger {\n readonly collection: string\n readonly match: ReadonlyArray<MatchPair>\n readonly maxFanout?: number\n}\n\ninterface RawTrigger {\n readonly collection: string\n readonly on?: string\n readonly match?: ReadonlyArray<MatchPair>\n readonly maxFanout?: number\n}\n\nexport function normalizeTriggerBy(triggerBy: ReadonlyArray<RawTrigger> | undefined): NormalizedTrigger[] {\n if (triggerBy === undefined) return []\n return triggerBy.map((t) => ({\n collection: t.collection,\n match: t.match ?? [{ from: 'id', to: t.on! }],\n ...(t.maxFanout !== undefined ? { maxFanout: t.maxFanout } : {}),\n }))\n}\n\nconst scalar = (v: unknown): string | null =>\n (typeof v === 'string' || typeof v === 'number') ? String(v) : null\n\n/**\n * The value tuple a written record presents to one trigger entry.\n * `null` means \"this record cannot address any source\" (a from-field is\n * absent or non-scalar) — a legitimate no-match, not an error.\n */\nexport function tupleFromWritten(\n match: ReadonlyArray<MatchPair>,\n writtenId: string,\n record: Record<string, unknown> | null,\n): Array<{ field: string; value: string }> | null {\n const out: Array<{ field: string; value: string }> = []\n for (const pair of match) {\n const v = pair.from === 'id' ? writtenId : scalar(record?.[pair.from])\n if (v === null) return null\n out.push({ field: pair.to, value: v })\n }\n return out\n}\n\n/**\n * Resolve one trigger's pairs into the value tuple a written record presents,\n * following any declared hop (#1277).\n *\n * Non-hop pairs behave exactly as {@link tupleFromWritten}. A hop pair reads\n * `written[from]`, finds the intermediate record whose `via.take` equals it,\n * and substitutes that record's `via.on` value — so everything downstream\n * still compares a flat `{ field, value }` tuple against source records and\n * the existing index-backed matcher is unchanged.\n *\n * Returns `null` when ANY pair cannot produce a value — a missing field, a\n * non-scalar, or an intermediate that does not resolve. That is a legitimate\n * no-match, not an error: a dangling reference addresses nothing, and refusing\n * here would turn a data condition into a thrown exception on every write.\n *\n * ONE lookup per hop per written record. `take: 'id'` is a direct `get`; any\n * other `take` is a field match on the intermediate, which uses its index when\n * one is declared.\n */\nexport async function resolveTuple(\n match: ReadonlyArray<MatchPair>,\n writtenId: string,\n record: Record<string, unknown> | null,\n lookup: (collection: string, field: string, value: string) => Promise<Record<string, unknown> | null>,\n): Promise<Array<{ field: string; value: string }> | null> {\n const out: Array<{ field: string; value: string }> = []\n for (const pair of match) {\n const raw = pair.from === 'id' ? writtenId : scalar(record?.[pair.from])\n if (raw === null) return null\n if (pair.via === undefined) {\n out.push({ field: pair.to, value: raw })\n continue\n }\n const intermediate = await lookup(pair.via.collection, pair.via.take, raw)\n if (intermediate === null) return null\n const hopped = scalar(intermediate[pair.via.on])\n if (hopped === null) return null\n out.push({ field: pair.to, value: hopped })\n }\n return out\n}\n\n/**\n * The tuple a written INTERMEDIATE record presents (#1277 option 2).\n *\n * When a record of `via.collection` is written, the sources it addresses are\n * those whose `to` equals the intermediate's own `via.on` value — no lookup is\n * needed, because the intermediate IS the written record. Pairs that do not\n * hop through this collection cannot be constrained by such a write and are\n * skipped, which makes the fan-out deliberately WIDER than a trigger write:\n * refreshing too much is recoverable, refreshing too little is the silent\n * staleness this feature exists to remove.\n *\n * Returns `null` when no pair hops through `collection`, or when the\n * intermediate carries no scalar at `via.on`.\n */\nexport function tupleFromIntermediate(\n match: ReadonlyArray<MatchPair>,\n collection: string,\n record: Record<string, unknown> | null,\n): Array<{ field: string; value: string }> | null {\n const out: Array<{ field: string; value: string }> = []\n for (const pair of match) {\n if (pair.via?.collection !== collection) continue\n const v = scalar(record?.[pair.via.on])\n if (v === null) return null\n out.push({ field: pair.to, value: v })\n }\n return out.length > 0 ? out : null\n}\n\n/** Any single component differing means NOT the same tuple (spec §7). */\nexport function sameTuple(\n a: Array<{ field: string; value: string }> | null,\n b: Array<{ field: string; value: string }> | null,\n): boolean {\n if (a === null || b === null) return a === b\n return a.length === b.length && a.every((p, i) => p.field === b[i]!.field && p.value === b[i]!.value)\n}\n\n/** Moved to the kernel (#1249) so `collection.ts` can import statically without\n * dragging derivations code into the floor bundle; re-exported here for\n * compatibility with existing consumers of this module. */\nexport { recordMatchesPairs, findMatchingIdsByPairs } from '../../kernel/match-pairs.js'\n"],"mappings":";AAsCO,SAAS,eAAe,OAA2C;AACxE,SAAO,CAAC,GAAG,IAAI,IAAI,MAAM,QAAQ,CAAC,MAAO,EAAE,MAAM,CAAC,EAAE,IAAI,UAAU,IAAI,CAAC,CAAE,CAAC,CAAC;AAC7E;AAeO,SAAS,mBAAmB,WAAuE;AACxG,MAAI,cAAc,OAAW,QAAO,CAAC;AACrC,SAAO,UAAU,IAAI,CAAC,OAAO;AAAA,IAC3B,YAAY,EAAE;AAAA,IACd,OAAO,EAAE,SAAS,CAAC,EAAE,MAAM,MAAM,IAAI,EAAE,GAAI,CAAC;AAAA,IAC5C,GAAI,EAAE,cAAc,SAAY,EAAE,WAAW,EAAE,UAAU,IAAI,CAAC;AAAA,EAChE,EAAE;AACJ;AAEA,IAAM,SAAS,CAAC,MACb,OAAO,MAAM,YAAY,OAAO,MAAM,WAAY,OAAO,CAAC,IAAI;AAO1D,SAAS,iBACd,OACA,WACA,QACgD;AAChD,QAAM,MAA+C,CAAC;AACtD,aAAW,QAAQ,OAAO;AACxB,UAAM,IAAI,KAAK,SAAS,OAAO,YAAY,OAAO,SAAS,KAAK,IAAI,CAAC;AACrE,QAAI,MAAM,KAAM,QAAO;AACvB,QAAI,KAAK,EAAE,OAAO,KAAK,IAAI,OAAO,EAAE,CAAC;AAAA,EACvC;AACA,SAAO;AACT;AAqBA,eAAsB,aACpB,OACA,WACA,QACA,QACyD;AACzD,QAAM,MAA+C,CAAC;AACtD,aAAW,QAAQ,OAAO;AACxB,UAAM,MAAM,KAAK,SAAS,OAAO,YAAY,OAAO,SAAS,KAAK,IAAI,CAAC;AACvE,QAAI,QAAQ,KAAM,QAAO;AACzB,QAAI,KAAK,QAAQ,QAAW;AAC1B,UAAI,KAAK,EAAE,OAAO,KAAK,IAAI,OAAO,IAAI,CAAC;AACvC;AAAA,IACF;AACA,UAAM,eAAe,MAAM,OAAO,KAAK,IAAI,YAAY,KAAK,IAAI,MAAM,GAAG;AACzE,QAAI,iBAAiB,KAAM,QAAO;AAClC,UAAM,SAAS,OAAO,aAAa,KAAK,IAAI,EAAE,CAAC;AAC/C,QAAI,WAAW,KAAM,QAAO;AAC5B,QAAI,KAAK,EAAE,OAAO,KAAK,IAAI,OAAO,OAAO,CAAC;AAAA,EAC5C;AACA,SAAO;AACT;AAgBO,SAAS,sBACd,OACA,YACA,QACgD;AAChD,QAAM,MAA+C,CAAC;AACtD,aAAW,QAAQ,OAAO;AACxB,QAAI,KAAK,KAAK,eAAe,WAAY;AACzC,UAAM,IAAI,OAAO,SAAS,KAAK,IAAI,EAAE,CAAC;AACtC,QAAI,MAAM,KAAM,QAAO;AACvB,QAAI,KAAK,EAAE,OAAO,KAAK,IAAI,OAAO,EAAE,CAAC;AAAA,EACvC;AACA,SAAO,IAAI,SAAS,IAAI,MAAM;AAChC;AAGO,SAAS,UACd,GACA,GACS;AACT,MAAI,MAAM,QAAQ,MAAM,KAAM,QAAO,MAAM;AAC3C,SAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,CAAC,EAAG,SAAS,EAAE,UAAU,EAAE,CAAC,EAAG,KAAK;AACtG;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/with-formula/derivations/dispatch.ts"],"sourcesContent":["/**\n * Derivation dispatch, lifted out of the kernel spine (#842 part b).\n *\n * `Collection` carried ~380 lines of dispatch logic that belongs to the\n * derivation service, not to the always-on kernel. These are free functions\n * taking an explicit context so the spine keeps only a thin delegator.\n *\n * ## How the spine reaches this module\n *\n * By dynamic `import()`, never a static one. `check-architecture.mjs`'s\n * `port-layering` check forbids the spine from statically importing a\n * `with-*` service; `collection.ts` is grandfathered for ten\n * `with-formula/*` specifiers, but that list is frozen per file and a new\n * one is a violation. A dynamic import is the sanctioned S4 gate recipe, and\n * it keeps this chunk out of the floor bundle for consumers who never fire a\n * derivation.\n *\n * The `Collection` type reference below points back at the spine. That is\n * inward (service → kernel) and so allowed, and it is TYPE-ONLY — erased at\n * build, so no runtime cycle exists.\n *\n * @internal\n */\n\nimport type { NoydbStore } from '../../kernel/types.js'\nimport type { TxContext } from '../../with-commit/tx/transaction.js'\nimport type { DerivationRegistry } from './registry.js'\nimport type { ReadOnlyVaultFacade } from '../../with-audit/guards/types.js'\nimport type { Collection } from '../../kernel/collection.js'\nimport type { EnclaveKey } from '../../kernel/enclave/index.js'\nimport type { EncryptedEnvelope } from '../../kernel/types.js'\nimport type { ledgerAuditHook, WaveContext } from '../../kernel/via/dispatch.js'\nimport { putDerivedOutput, selfWriteFieldEqual } from '../../kernel/via/dispatch.js'\nimport { DerivationCapExceededError } from '../../kernel/errors.js'\nimport { tupleFromWritten, sameTuple, resolveTuple, tupleFromIntermediate, hopCollections } from './trigger-match.js'\nimport { markStale } from './stale.js'\nimport type { DerivationExecutor } from './executor.js'\n\n/** The per-write dispatch context threaded into `putDerivedOutput`. */\ninterface DispatchCtx {\n emit: (e: string, p: unknown) => void\n source: { readonly collection: string; readonly id: string }\n audit: ReturnType<typeof ledgerAuditHook>\n}\n\n/**\n * What the dispatchers need from the collection that owns them. Passing this\n * explicitly is what lets the logic live outside the class.\n */\nexport interface DerivationDeleteCtx {\n readonly derivationSource: {\n registry(): DerivationRegistry\n getCollection(name: string): Collection<Record<string, unknown>>\n getActiveTxContext(): TxContext | null\n getReadOnlyFacade(): ReadOnlyVaultFacade\n }\n /** The collection the delete happened in. */\n readonly collectionName: string\n readonly adapter: NoydbStore\n readonly vault: string\n readonly getDEK: (collectionName: string) => Promise<EnclaveKey>\n readonly storeCiphertext: boolean\n}\n\n/**\n * Erase the derived rows a deleted source record fanned out to, and drop the\n * fan-out sidecars that recorded them.\n *\n * @param eraseRecordShapeToo also erase same-id `shape: 'record'` outputs.\n * Restricted to a source-triggered strategy writing into a DIFFERENT\n * collection — the self-denorm case would re-delete the record just\n * tombstoned, and triggerBy/sibling strategies derive under a different id.\n * @returns rows ACTUALLY deleted, not edges visited (#622).\n */\nexport async function dispatchArrayDerivationsOnDelete(\n ctx: DerivationDeleteCtx,\n id: string,\n eraseRecordShapeToo = false,\n): Promise<number> {\n const { derivationSource, collectionName, adapter, vault, getDEK, storeCiphertext } = ctx\n\n const strategies = derivationSource.registry().strategiesForSource(collectionName)\n if (strategies.length === 0) return 0\n\n const { loadFanoutSidecar, deleteFanoutSidecar } = await import('./fanout-sidecar.js')\n const txCtx = derivationSource.getActiveTxContext()\n let erased = 0\n\n for (const { spec } of strategies) {\n for (const [outputKey, outSpec] of Object.entries(spec.outputs)) {\n if (outSpec.shape === 'record') {\n if (eraseRecordShapeToo && spec.source === collectionName && outSpec.collection !== collectionName) {\n if (await derivationSource.getCollection(outSpec.collection)._internalDelete(id, txCtx)) erased += 1\n }\n continue\n }\n\n const sidecar = await loadFanoutSidecar(adapter, vault, spec.source, id, outputKey, getDEK, storeCiphertext)\n if (!sidecar) continue\n\n const outputCollection = derivationSource.getCollection(outSpec.collection)\n for (const derivedId of sidecar.keys) {\n if (await outputCollection._internalDelete(derivedId, txCtx)) erased += 1\n }\n await deleteFanoutSidecar(adapter, vault, spec.source, id, outputKey)\n }\n }\n\n return erased\n}\n\n/** Adds what the on-write derivation dispatch needs beyond the delete path. */\nexport interface DerivationDispatchCtx extends DerivationDeleteCtx {\n /** Via pipeline, for decoding the stored form back to canonical money shape. */\n readonly via: { canonicalizeStored(r: Record<string, unknown>): Record<string, unknown> } | undefined\n /** Recompute a rollup aggregate onto a parent. Stays on the spine — it walks collections. */\n readonly recomputeRollup: (\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n spec: any,\n parentId: string,\n source: { readonly collection: string; readonly id: string },\n wave?: WaveContext,\n ) => Promise<unknown>\n /** Builds the per-write dispatch context threaded into `putDerivedOutput`. */\n readonly dispatchCtx: (source: { readonly collection: string; readonly id: string }) => DispatchCtx\n /** Registers a write on the active transaction for rollback. */\n readonly trackPut: (txCtx: TxContext, collectionName: string, id: string, priorEnvelope: EncryptedEnvelope | null) => void\n}\n\n/**\n * Fire registered derivation strategies for this source collection.\n *\n * Eager mode runs `derive` inline and writes each output; lazy mode records a\n * stale mark for cold-session recompute. Rollups are handled here rather than\n * by the executor — a write to the child recomputes the parent aggregate, and\n * a write to the parent recomputes its own.\n */\nexport async function dispatchDerivations(\n ctx: DerivationDispatchCtx,\n id: string,\n record: Record<string, unknown>,\n version: number,\n wave?: WaveContext,\n /** The pre-write record at `id` in THIS collection (undefined = not\n * captured — no field-match trigger, or a sync-applied wave; null =\n * captured and absent, i.e. a create). Only a `Record` re-fires the OLD\n * matched set alongside the new one (spec §7). */\n prior?: Record<string, unknown> | null,\n): Promise<void> {\n const { derivationSource, collectionName, via, recomputeRollup } = ctx\n\n // `record` is the stored form here (post-quantize) — decode so\n // derive(source, ctx) sees the canonical money shape.\n const incoming = (via ? via.canonicalizeStored(record) : record)\n if (incoming && typeof incoming === 'object' && '_derivedFrom' in incoming) return\n // `prior` is the raw stored pre-write record — canonicalize it the same\n // way as `incoming` before it feeds `tupleFromWritten` below, or a\n // via-shaped match field (e.g. a money field) compares canonical against\n // raw and never matches the old tuple (Min 5).\n const canonicalPrior = prior != null ? (via ? via.canonicalizeStored(prior) : prior) : prior\n const registry = derivationSource.registry()\n const strategies = registry.strategiesForSource(collectionName)\n if (strategies.length === 0) return\n // Dynamic-import the executor only on the first eager-mode\n // dispatch. Lazy-mode dispatches use `markStale` (a pure helper)\n // which doesn't reach into the executor at all. Keeps the\n // derivation executor chunk out of the floor bundle for any\n // consumer that doesn't fire an eager derivation.\n let executorClass: typeof DerivationExecutor | null = null\n for (const { spec, strategyHash, triggers } of strategies) {\n const mode = typeof spec.lifecycle === 'string' ? spec.lifecycle : spec.lifecycle.mode\n\n // Rollup: a write to the child `from` recomputes the\n // parent at id child[key]; a write to the parent (source = into)\n // recomputes its own aggregate. Handled here (the executor is not run).\n if (spec.rollup) {\n if (mode !== 'eager') continue\n const asParentId = (v: unknown): string | null =>\n (typeof v === 'string' || typeof v === 'number') ? String(v) : null\n let parentId: string | null\n // #1257 — a child that RE-PARENTS strands the old parent's aggregate\n // unless the old key is recomputed too. Reads the prior stored record\n // (canonicalized alongside `incoming`, so a via-shaped key compares like\n // for like). `undefined` prior — a sync-applied wave write or a tiers\n // restore, which do not thread it — degrades to new-parent-only, the\n // pre-#1257 behaviour, rather than guessing.\n let oldParentId: string | null = null\n if (collectionName === spec.rollup.from) {\n parentId = asParentId(incoming[spec.rollup.key])\n const priorKey = canonicalPrior != null ? asParentId(canonicalPrior[spec.rollup.key]) : null\n if (priorKey !== null && priorKey !== parentId) oldParentId = priorKey\n } else {\n parentId = id // a write to the parent recomputes its own aggregate\n }\n if (parentId !== null) await recomputeRollup(spec, parentId, { collection: collectionName, id }, wave)\n // The old parent is recomputed AFTER the new one so a `maxFanout`-style\n // failure on the new parent surfaces before extra work is spent.\n if (oldParentId !== null) await recomputeRollup(spec, oldParentId, { collection: collectionName, id }, wave)\n continue\n }\n\n // Determine how `collectionName` triggers this strategy, and build the list\n // of source records to (re-)derive:\n // • source — re-derive the written record itself (same-id).\n // • sources[] — re-derive the PRIMARY source at the same id.\n // • triggerBy — FK fan-out: re-derive every source record\n // whose `on` field equals the written parent's id.\n // `input` is passed to derive(); `base` is the raw stored source record\n // used as the patch base for a self-write reverse-denorm output.\n const isSource = spec.source === collectionName\n const isSibling = !isSource && (spec.sources?.includes(collectionName) ?? false)\n const triggerEntries = !isSource && !isSibling\n ? triggers.filter((t) => t.collection === collectionName)\n : []\n // #1277 option 2 — a write to an INTERMEDIATE collection fires the trigger\n // too. Without this, re-pointing the intermediate strands every source it\n // used to address: nothing is written to the trigger or source collection,\n // so no other path can notice. Kept separate from `triggerEntries` because\n // the tuple is derived differently (the intermediate IS the written record,\n // so no lookup is needed).\n const hopEntries = !isSource && !isSibling\n ? triggers.filter((t) => hopCollections(t.match).includes(collectionName))\n : []\n\n const runs: Array<{\n input: Record<string, unknown> & { id: string }\n base: Record<string, unknown>\n runId: string\n version: number\n }> = []\n\n if (isSource) {\n runs.push({ input: { ...incoming, id }, base: incoming, runId: id, version })\n } else if (isSibling) {\n const p = await derivationSource.getCollection(spec.source).get(id)\n if (p !== null && p !== undefined) {\n // Raw base for a (rare) sibling self-write; falls back to the\n // resolved primary if the raw read misses.\n const raw = await derivationSource.getCollection(spec.source)._getStoredRecord(id)\n runs.push({ input: { ...p, id }, base: raw ?? p, runId: id, version: 0 })\n }\n } else if (triggerEntries.length > 0 || hopEntries.length > 0) {\n const srcColl = derivationSource.getCollection(spec.source)\n const matched = new Set<string>()\n // One lookup per hop per written record — `take: 'id'` is a direct get,\n // anything else is a single field match on the intermediate. Never per\n // candidate row (#1266's bound).\n const lookup = async (coll: string, field: string, value: string): Promise<Record<string, unknown> | null> => {\n const c = derivationSource.getCollection(coll)\n if (field === 'id') return c._getStoredRecord(value)\n const ids = await c._findMatchingCompositeIds([{ field, value }])\n const first = ids[0]\n return first === undefined ? null : c._getStoredRecord(first)\n }\n // A write to an intermediate collection: fan out on its old ∪ new value.\n // The prior record is the intermediate's own, so #1249's capture already\n // supplies it — the same mechanism, pointed at a third collection.\n for (const trigger of hopEntries) {\n const tuples = [tupleFromIntermediate(trigger.match, collectionName, incoming)]\n if (canonicalPrior != null) {\n const old = tupleFromIntermediate(trigger.match, collectionName, canonicalPrior)\n if (!sameTuple(old, tuples[0]!)) tuples.push(old)\n }\n const ids = new Set<string>()\n for (const tuple of tuples) {\n if (tuple === null) continue\n for (const sid of await srcColl._findMatchingCompositeIds(tuple)) ids.add(sid)\n }\n if (trigger.maxFanout !== undefined && ids.size > trigger.maxFanout) {\n throw new DerivationCapExceededError(\n `triggerBy hop ${collectionName}→${spec.source} [${trigger.match.map(p => p.to).join(',')}]`,\n ids.size, trigger.maxFanout)\n }\n for (const sid of ids) matched.add(sid)\n }\n for (const trigger of triggerEntries) {\n // Union fan-out (spec §7): an update changing any matched component\n // must re-fire BOTH the old and the new matched set, or the old\n // set's derived output goes stale (never re-derived once it no\n // longer matches). `prior` is only ever a `Record` here — a wave\n // dispatch and a create both skip the old tuple outright.\n const tuples = [await resolveTuple(trigger.match, id, incoming, lookup)]\n if (canonicalPrior != null && trigger.match.some((p) => p.from !== 'id')) {\n const old = await resolveTuple(trigger.match, id, canonicalPrior, lookup)\n if (!sameTuple(old, tuples[0]!)) tuples.push(old)\n }\n const ids = new Set<string>()\n for (const tuple of tuples) {\n if (tuple === null) continue // a from-value is absent/non-scalar: matches nothing\n for (const sid of await srcColl._findMatchingCompositeIds(tuple)) ids.add(sid)\n }\n if (trigger.maxFanout !== undefined && ids.size > trigger.maxFanout) {\n throw new DerivationCapExceededError(\n `triggerBy ${collectionName}→${spec.source} [${trigger.match.map(p => p.to).join(',')}]`,\n ids.size, trigger.maxFanout)\n }\n for (const sid of ids) matched.add(sid)\n }\n for (const sid of matched) {\n const raw = await srcColl._getStoredRecord(sid)\n if (raw === null) continue\n runs.push({ input: { ...raw, id: sid }, base: raw, runId: sid, version: 0 })\n }\n }\n\n if (runs.length === 0) continue\n\n if (mode !== 'eager') {\n for (const run of runs) await markStale(registry, spec, run.runId)\n continue\n }\n\n if (executorClass === null) {\n ;({ DerivationExecutor: executorClass } = await import('./executor.js'))\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n for (const run of runs) await runOne(ctx, spec as any, strategyHash, executorClass, run)\n }\n}\n\n/**\n * Run the executor for one (spec, run) pair and write its outputs. Shared by\n * `dispatchDerivations`' eager loop and `dispatchTriggerDerivationsOnDelete`\n * (#1249) — the write-out logic (array diff, record self-write reverse-denorm,\n * normal record output) is identical for a live write and a delete-triggered\n * re-derive; only how `runs` gets built differs.\n */\nasync function runOne(\n ctx: DerivationDispatchCtx,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n spec: any,\n strategyHash: string,\n executorClass: typeof DerivationExecutor,\n run: { input: Record<string, unknown> & { id: string }; base: Record<string, unknown>; runId: string; version: number },\n): Promise<void> {\n const { derivationSource, adapter, vault, getDEK, storeCiphertext, dispatchCtx, trackPut } = ctx\n const execCtx = { vault: derivationSource.getReadOnlyFacade() }\n const outCtx = dispatchCtx({ collection: spec.source, id: run.runId })\n const result = await executorClass.run(spec, run.input, run.version, strategyHash, execCtx)\n for (const key of Object.keys(spec.outputs)) {\n const out = result.outputs[key]\n if (!out) continue\n if (out.kind === 'failed') {\n const err = out.error\n if (spec.strict) throw err\n console.warn(`[derivation] output \"${key}\" for source \"${spec.source}\" id=\"${run.runId}\" failed:`, err)\n continue\n }\n const outSpec = spec.outputs[key]\n if (!outSpec) continue\n const outputCollection = derivationSource.getCollection(outSpec.collection)\n // If we're inside a multi-record transaction, register\n // derived writes as side-effect ops on the active ctx\n // BEFORE they fire. `revertExecuted` walks `_executed` in\n // reverse on rollback, so capturing the pre-write envelope\n // here lets a later mid-batch failure restore this output's\n // prior state alongside the source op. Outside a transaction\n // the context is null and tracking is skipped.\n const txCtx = derivationSource.getActiveTxContext()\n\n // ── Array-shape branch ─────────────────────────────────\n if (out.kind === 'array') {\n // Load the prior key set from the fanout sidecar.\n const { loadFanoutSidecar, saveFanoutSidecar } = await import('./fanout-sidecar.js')\n const prior = await loadFanoutSidecar(adapter, vault, spec.source, run.runId, key, getDEK, storeCiphertext)\n const prevKeys = new Set<string>(prior?.keys ?? [])\n const newKeysList = out.entries.map((e: { key: string }) => e.key)\n const newKeysSet = new Set<string>(newKeysList)\n\n // Diff — delete keys that were in prev but not in new.\n for (const k of prevKeys) {\n if (newKeysSet.has(k)) continue\n await outputCollection._internalDelete(k, txCtx)\n }\n\n // Upsert every entry in the new set. (Slice 1: no\n // identity-skip optimisation; write every row, idempotent\n // at the (collection, id) level.)\n for (const entry of out.entries) {\n if (txCtx !== null) {\n trackPut(txCtx, outSpec.collection, entry.key, await adapter.get(vault, outSpec.collection, entry.key))\n }\n await putDerivedOutput(outputCollection, entry.key, entry.value, outCtx, { source: 'derived' })\n }\n\n // Persist the new key set last, for failure-mode symmetry.\n await saveFanoutSidecar(adapter, vault, {\n source: spec.source,\n sourceId: run.runId,\n outputKey: key,\n outputCollection: outSpec.collection,\n keys: newKeysList,\n }, getDEK, storeCiphertext)\n continue\n }\n\n // ── Record-shape branch ────────────────────────────────\n if (out.skipped === true) {\n // Optional output returned null. Delete the\n // previously-emitted output at this id, if any. Routed\n // through `_internalDelete` so a user-registered\n // `onDelete` on the output collection does NOT\n // fire — this is a system-internal tombstone, not a\n // user-initiated delete. The txCtx hookup captures the\n // prior envelope inside `_internalDelete` for rollback\n // symmetry; delete-of-absent is a silent no-op.\n await outputCollection._internalDelete(run.runId, txCtx)\n continue\n }\n\n // ── Self-write reverse-denorm ───────────────────────────\n // An output back to its own source: patch ONLY the declared\n // `denorm` fields onto the raw stored record, never the whole\n // value (which would clobber user fields / i18n maps and carries\n // the executor's `_derivedFrom` tag). If the patch changes\n // nothing, skip the write — that value-equality is the cycle\n // guard: the self-write re-fires the source-path derivation,\n // which recomputes identical fields and terminates here.\n if (outSpec.shape === 'record' && outSpec.denorm !== undefined && outSpec.collection === spec.source) {\n const value = out.value\n const patched: Record<string, unknown> = { ...run.base }\n let changed = false\n for (const f of outSpec.denorm) {\n if (!selfWriteFieldEqual(run.base[f], value[f])) {\n patched[f] = value[f]\n changed = true\n }\n }\n if (!changed) continue // cycle guard — nothing to write\n if (txCtx !== null) {\n trackPut(txCtx, outSpec.collection, run.runId, await adapter.get(vault, outSpec.collection, run.runId))\n }\n await putDerivedOutput(outputCollection, run.runId, patched, outCtx, { source: 'derived' })\n continue\n }\n\n // ── Normal record output (separate output collection) ──\n if (txCtx !== null) {\n trackPut(txCtx, outSpec.collection, run.runId, await adapter.get(vault, outSpec.collection, run.runId))\n }\n await putDerivedOutput(outputCollection, run.runId, out.value, outCtx, { source: 'derived' })\n }\n}\n\n/**\n * Trigger fan-out for a DELETED parent record (#1249, spec §8). Distinct from\n * the \"record-shape derivations not dispatched on delete\" rule — that is\n * about deleting a SOURCE record; this fires when a TRIGGER collection's\n * record is deleted, re-deriving source records that still exist. Pairs\n * evaluate against the tombstoned record's values; matched sources re-derive\n * through the normal executor (their derive() reads the now-absent parent\n * and decides what that means — the engine never cascades deletes).\n */\nexport async function dispatchTriggerDerivationsOnDelete(\n ctx: DerivationDispatchCtx, id: string, deleted: Record<string, unknown>,\n): Promise<void> {\n const { derivationSource, collectionName } = ctx\n const registry = derivationSource.registry()\n const strategies = registry.strategiesForSource(collectionName)\n if (strategies.length === 0) return\n let executorClass: typeof DerivationExecutor | null = null\n for (const { spec, strategyHash, triggers } of strategies) {\n if (spec.rollup) continue // rollup-on-delete already exists\n if (spec.source === collectionName) continue // source delete: existing rule, untouched\n if (spec.sources?.includes(collectionName)) continue // declared sibling source: same exclusion as the write path's isSibling check\n const entries = triggers.filter((t) => t.collection === collectionName)\n if (entries.length === 0) continue\n const mode = typeof spec.lifecycle === 'string' ? spec.lifecycle : spec.lifecycle.mode\n const srcColl = derivationSource.getCollection(spec.source)\n const matched = new Set<string>()\n for (const trigger of entries) {\n const tuple = tupleFromWritten(trigger.match, id, deleted)\n if (tuple === null) continue\n const ids = await srcColl._findMatchingCompositeIds(tuple)\n if (trigger.maxFanout !== undefined && ids.length > trigger.maxFanout) {\n throw new DerivationCapExceededError(\n `triggerBy ${collectionName}→${spec.source} [${trigger.match.map(p => p.to).join(',')}] (delete)`,\n ids.length, trigger.maxFanout)\n }\n for (const sid of ids) matched.add(sid)\n }\n if (matched.size === 0) continue\n if (mode !== 'eager') { for (const sid of matched) await markStale(registry, spec, sid); continue }\n if (executorClass === null) ({ DerivationExecutor: executorClass } = await import('./executor.js'))\n for (const sid of matched) {\n const raw = await srcColl._getStoredRecord(sid)\n if (raw === null) continue\n await runOne(ctx, spec, strategyHash, executorClass, { input: { ...raw, id: sid }, base: raw, runId: sid, version: 0 })\n }\n }\n}\n"],"mappings":"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","names":[]}