@noy-db/hub 0.7.0-pre.16 → 0.7.0-pre.17
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 +29 -0
- package/dist/cargo/index.js +1 -1
- package/dist/{chunk-URDWKAQQ.js → chunk-6GOE5PGN.js} +5 -5
- package/dist/{chunk-6LMAVYOY.js → chunk-FFWI7UH7.js} +1 -11
- package/dist/chunk-FFWI7UH7.js.map +1 -0
- package/dist/{chunk-MWAHERVR.js → chunk-HDOU6I2Y.js} +2 -2
- package/dist/{chunk-VNZUVPUO.js → chunk-J6FXAEUG.js} +2 -2
- package/dist/{chunk-LVS44C35.js → chunk-WEQEYLCS.js} +2 -2
- package/dist/derivations/index.js +2 -2
- package/dist/{dispatch-SYKRVTYW.js → dispatch-S73TK3IY.js} +29 -12
- package/dist/dispatch-S73TK3IY.js.map +1 -0
- package/dist/index.js +3 -3
- package/dist/{noydb-RFEFYWE4.js → noydb-DUXZPKSN.js} +2 -2
- package/dist/pod/index.js +2 -2
- package/dist/{registry-K734HBSO.js → registry-YLEWMGAC.js} +3 -3
- package/dist/with-formula/derivations/trigger-match.d.ts +6 -9
- package/dist/with-formula/derivations/types.d.ts +8 -3
- package/package.json +3 -3
- package/dist/chunk-6LMAVYOY.js.map +0 -1
- package/dist/dispatch-SYKRVTYW.js.map +0 -1
- /package/dist/{chunk-URDWKAQQ.js.map → chunk-6GOE5PGN.js.map} +0 -0
- /package/dist/{chunk-MWAHERVR.js.map → chunk-HDOU6I2Y.js.map} +0 -0
- /package/dist/{chunk-VNZUVPUO.js.map → chunk-J6FXAEUG.js.map} +0 -0
- /package/dist/{chunk-LVS44C35.js.map → chunk-WEQEYLCS.js.map} +0 -0
- /package/dist/{noydb-RFEFYWE4.js.map → noydb-DUXZPKSN.js.map} +0 -0
- /package/dist/{registry-K734HBSO.js.map → registry-YLEWMGAC.js.map} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog — hub
|
|
2
2
|
|
|
3
|
+
## 0.7.0-pre.17
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- A DELETE through a `triggerBy` hop now fans out — it silently did not (#1294).
|
|
8
|
+
|
|
9
|
+
Reported by a consumer adopting the hop from `0.7.0-pre.16`: puts through a
|
|
10
|
+
`via` hop re-fired the derivation, deletes did not, while deletes through a
|
|
11
|
+
plain pair did. The delete path built its tuple with the UNHOPPED builder, so a
|
|
12
|
+
mapped pair compared the written record's `from` value against `source[to]` —
|
|
13
|
+
the wrong side of the relationship — and matched nothing. No error, no fan-out.
|
|
14
|
+
|
|
15
|
+
**Two gaps, not one.** Deleting the INTERMEDIATE record was also unhandled, and
|
|
16
|
+
strands every source it addressed for the same reason re-pointing one does:
|
|
17
|
+
nothing is written to the trigger or source collection, so no other path can
|
|
18
|
+
notice. The write path already covered that; the delete path did not, which made
|
|
19
|
+
the hop's correctness argument hold for puts only.
|
|
20
|
+
|
|
21
|
+
**The shipped typings said deletes fan out "for BOTH forms".** That read as
|
|
22
|
+
covering hops and did not — a behaviour gap and a prose-vs-artefact mismatch in
|
|
23
|
+
one. The sentence now enumerates `on`, plain `match`, `via` hops, and deletes of
|
|
24
|
+
the intermediate itself.
|
|
25
|
+
|
|
26
|
+
**The sync tuple builder is deleted rather than kept**, and that is the durable
|
|
27
|
+
half: two builders that had to agree is exactly how this drifted, because the
|
|
28
|
+
delete path called the wrong one. `resolveTuple` with no `via` does what the old
|
|
29
|
+
one did, its unit tests moved across unchanged, and one path cannot disagree
|
|
30
|
+
with itself.
|
|
31
|
+
|
|
3
32
|
## 0.7.0-pre.16
|
|
4
33
|
|
|
5
34
|
### Minor Changes
|
package/dist/cargo/index.js
CHANGED
|
@@ -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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
310
|
+
//# sourceMappingURL=chunk-J6FXAEUG.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createNoydb
|
|
3
|
-
} from "./chunk-
|
|
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-
|
|
289
|
+
//# sourceMappingURL=chunk-WEQEYLCS.js.map
|
|
@@ -2,9 +2,8 @@ import {
|
|
|
2
2
|
hopCollections,
|
|
3
3
|
resolveTuple,
|
|
4
4
|
sameTuple,
|
|
5
|
-
tupleFromIntermediate
|
|
6
|
-
|
|
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
|
-
|
|
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
|
-
|
|
255
|
-
const
|
|
256
|
-
if (
|
|
257
|
-
const
|
|
258
|
-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
90
|
+
//# sourceMappingURL=noydb-DUXZPKSN.js.map
|
package/dist/pod/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DerivationRegistry
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
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-
|
|
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
|
|
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
|
|
230
|
-
* option 2), fanning out on
|
|
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.
|
|
3
|
+
"version": "0.7.0-pre.17",
|
|
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,14 @@
|
|
|
233
233
|
"node": ">=22.0.0"
|
|
234
234
|
},
|
|
235
235
|
"dependencies": {
|
|
236
|
-
"@noy-db/attestation": "0.7.0-pre.
|
|
236
|
+
"@noy-db/attestation": "0.7.0-pre.17"
|
|
237
237
|
},
|
|
238
238
|
"devDependencies": {
|
|
239
239
|
"@types/node": "^22.0.0",
|
|
240
240
|
"esbuild": "^0.25.0",
|
|
241
241
|
"zod": "^4.0.0",
|
|
242
242
|
"zod-to-json-schema": "^3.25.2",
|
|
243
|
-
"@noy-db/on-shamir": "0.7.0-pre.
|
|
243
|
+
"@noy-db/on-shamir": "0.7.0-pre.17"
|
|
244
244
|
},
|
|
245
245
|
"peerDependencies": {
|
|
246
246
|
"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":[]}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|