@loomwork/plugin-content 0.0.0-stage → 0.1.1
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/LICENSE +202 -0
- package/NOTICE +8 -0
- package/README.md +92 -2
- package/dist/CachingContentStore.d.ts +54 -0
- package/dist/CachingContentStore.d.ts.map +1 -0
- package/dist/CachingContentStore.js +171 -0
- package/dist/CachingContentStore.js.map +1 -0
- package/dist/ContentResolvingEventStore.d.ts +70 -0
- package/dist/ContentResolvingEventStore.d.ts.map +1 -0
- package/dist/ContentResolvingEventStore.js +245 -0
- package/dist/ContentResolvingEventStore.js.map +1 -0
- package/dist/ContentStore.d.ts +186 -0
- package/dist/ContentStore.d.ts.map +1 -0
- package/dist/ContentStore.js +177 -0
- package/dist/ContentStore.js.map +1 -0
- package/dist/ContentTouchingSnapshotStore.d.ts +56 -0
- package/dist/ContentTouchingSnapshotStore.d.ts.map +1 -0
- package/dist/ContentTouchingSnapshotStore.js +85 -0
- package/dist/ContentTouchingSnapshotStore.js.map +1 -0
- package/dist/citations.d.ts +40 -0
- package/dist/citations.d.ts.map +1 -0
- package/dist/citations.js +118 -0
- package/dist/citations.js.map +1 -0
- package/dist/content.bench.d.ts +2 -0
- package/dist/content.bench.d.ts.map +1 -0
- package/dist/content.bench.js +75 -0
- package/dist/content.bench.js.map +1 -0
- package/dist/content.d.ts +40 -0
- package/dist/content.d.ts.map +1 -0
- package/dist/content.js +177 -0
- package/dist/content.js.map +1 -0
- package/dist/derive.d.ts +9 -0
- package/dist/derive.d.ts.map +1 -0
- package/dist/derive.js +40 -0
- package/dist/derive.js.map +1 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +20 -0
- package/dist/errors.js.map +1 -0
- package/dist/executionEnd.d.ts +22 -0
- package/dist/executionEnd.d.ts.map +1 -0
- package/dist/executionEnd.js +66 -0
- package/dist/executionEnd.js.map +1 -0
- package/dist/gc.d.ts +470 -0
- package/dist/gc.d.ts.map +1 -0
- package/dist/gc.js +819 -0
- package/dist/gc.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/touchAround.d.ts +46 -0
- package/dist/touchAround.d.ts.map +1 -0
- package/dist/touchAround.js +98 -0
- package/dist/touchAround.js.map +1 -0
- package/package.json +58 -3
- package/src/CachingContentStore.ts +184 -0
- package/src/ContentResolvingEventStore.ts +325 -0
- package/src/ContentStore.ts +317 -0
- package/src/ContentTouchingSnapshotStore.ts +109 -0
- package/src/citations.ts +117 -0
- package/src/content.bench.ts +84 -0
- package/src/content.ts +191 -0
- package/src/derive.ts +40 -0
- package/src/errors.ts +19 -0
- package/src/executionEnd.ts +64 -0
- package/src/gc.ts +1213 -0
- package/src/index.ts +54 -0
- package/src/touchAround.ts +131 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Snapshot, SnapshotStore } from "@loomwork/snapshot-runtime";
|
|
2
|
+
import type { ContentStore } from "./ContentStore";
|
|
3
|
+
import { type TouchErrorHook } from "./touchAround";
|
|
4
|
+
/**
|
|
5
|
+
* The brand a {@link ContentTouchingSnapshotStore} carries (a registered symbol, so a second copy of
|
|
6
|
+
* this package still recognises it). Read it through {@link isContentTouchingSnapshotStore}.
|
|
7
|
+
*/
|
|
8
|
+
export declare const CONTENT_TOUCHING_SNAPSHOT_STORE: unique symbol;
|
|
9
|
+
/**
|
|
10
|
+
* True when `store` is a {@link ContentTouchingSnapshotStore}. Evidence about THIS object only: a
|
|
11
|
+
* `false` does not prove the runtime wrote unwrapped (a sweep or the CLI reads a raw store over the
|
|
12
|
+
* same tables a wrapped one wrote), and a `true` does not prove every OTHER store the runtime saves
|
|
13
|
+
* to is wrapped. `sweepUnpinnedContent` therefore uses it only to warn, never to relax or refuse.
|
|
14
|
+
*/
|
|
15
|
+
export declare function isContentTouchingSnapshotStore(store: unknown): boolean;
|
|
16
|
+
export interface ContentTouchingSnapshotStoreOptions {
|
|
17
|
+
/** See {@link TouchErrorHook}: the touch after a durable save, and a cited blob found missing. */
|
|
18
|
+
onTouchError?: TouchErrorHook;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* ADR 0055 D6 amendment 2 — the SECOND citation path. `copyPrefix` copies only the post-gap events
|
|
22
|
+
* of a compacted parent; the refs its truncated prefix cited live in the child's SEED SNAPSHOT
|
|
23
|
+
* (the runtime's `fork` saves it), where an R5 media source or a claim-check envelope stays
|
|
24
|
+
* verbatim and nothing `put`s it again. `ContentResolvingEventStore` cannot see it, so this
|
|
25
|
+
* decorator touches the citations of every snapshot it SAVES, before and after, exactly as that
|
|
26
|
+
* store does for events (same walker, same guarantee: D5's margin, see {@link touchAround}).
|
|
27
|
+
*
|
|
28
|
+
* Chosen over a hook in the runtime because the runtime holds no content store (and must not
|
|
29
|
+
* depend on the content plugin), and over touching at fork start because a decorator also covers
|
|
30
|
+
* compaction checkpoints and every other save with no extra seam. Cost: one iterative walk of the
|
|
31
|
+
* saved state and a round trip only when it cites a blob; with a content store that has no `touch`
|
|
32
|
+
* nothing at all.
|
|
33
|
+
*
|
|
34
|
+
* Wrap it OUTSIDE an `EncryptingSnapshotStore` (it must see the plaintext state, or refs under a
|
|
35
|
+
* sensitive path are hidden from the walk), and pass the same content store the event log uses.
|
|
36
|
+
* The seed is saved after the prefix copy, so a fork racing the DELETION of its own parent is
|
|
37
|
+
* outside the guarantee (the fork's read of the parent fails then, too).
|
|
38
|
+
*/
|
|
39
|
+
export declare class ContentTouchingSnapshotStore implements SnapshotStore {
|
|
40
|
+
private readonly inner;
|
|
41
|
+
private readonly content;
|
|
42
|
+
readonly deleteExecution?: NonNullable<SnapshotStore["deleteExecution"]>;
|
|
43
|
+
readonly loadLatestSealed?: NonNullable<SnapshotStore["loadLatestSealed"]>;
|
|
44
|
+
readonly pruneSnapshots?: NonNullable<SnapshotStore["pruneSnapshots"]>;
|
|
45
|
+
readonly listSequences?: NonNullable<SnapshotStore["listSequences"]>;
|
|
46
|
+
/** Brand: see {@link isContentTouchingSnapshotStore}. */
|
|
47
|
+
readonly [CONTENT_TOUCHING_SNAPSHOT_STORE] = true;
|
|
48
|
+
private readonly onTouchError;
|
|
49
|
+
constructor(inner: SnapshotStore, content: Pick<ContentStore, "touch">, options?: ContentTouchingSnapshotStoreOptions);
|
|
50
|
+
save(snapshot: Snapshot): Promise<void>;
|
|
51
|
+
loadLatest(executionId: string): Promise<Snapshot | null>;
|
|
52
|
+
loadAt(executionId: string, sequence: number): Promise<Snapshot | null>;
|
|
53
|
+
loadLatestBefore(executionId: string, sequence: number): Promise<Snapshot | null>;
|
|
54
|
+
close(): Promise<void>;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=ContentTouchingSnapshotStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContentTouchingSnapshotStore.d.ts","sourceRoot":"","sources":["../src/ContentTouchingSnapshotStore.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,QAAQ,EACR,aAAa,EACd,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,KAAK,cAAc,EAAe,MAAM,eAAe,CAAC;AAEjE;;;GAGG;AACH,eAAO,MAAM,+BAA+B,EAAE,OAAO,MAEpD,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAMtE;AAED,MAAM,WAAW,mCAAmC;IAClD,kGAAkG;IAClG,YAAY,CAAC,EAAE,cAAc,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,4BAA6B,YAAW,aAAa;IAU9D,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAV1B,QAAQ,CAAC,eAAe,CAAC,EAAE,WAAW,CAAC,aAAa,CAAC,iBAAiB,CAAC,CAAC,CAAC;IACzE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,WAAW,CAAC,aAAa,CAAC,kBAAkB,CAAC,CAAC,CAAC;IAC3E,QAAQ,CAAC,cAAc,CAAC,EAAE,WAAW,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC,CAAC;IACvE,QAAQ,CAAC,aAAa,CAAC,EAAE,WAAW,CAAC,aAAa,CAAC,eAAe,CAAC,CAAC,CAAC;IACrE,yDAAyD;IACzD,QAAQ,CAAC,CAAC,+BAA+B,CAAC,QAAQ;IAClD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA6B;gBAGvC,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,EACrD,OAAO,GAAE,mCAAwC;IAgBnD,IAAI,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAUvC,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAIzD,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAIvE,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAIjF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAGvB"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ContentTouchingSnapshotStore = exports.CONTENT_TOUCHING_SNAPSHOT_STORE = void 0;
|
|
4
|
+
exports.isContentTouchingSnapshotStore = isContentTouchingSnapshotStore;
|
|
5
|
+
const event_runtime_1 = require("@loomwork/event-runtime");
|
|
6
|
+
const touchAround_1 = require("./touchAround");
|
|
7
|
+
/**
|
|
8
|
+
* The brand a {@link ContentTouchingSnapshotStore} carries (a registered symbol, so a second copy of
|
|
9
|
+
* this package still recognises it). Read it through {@link isContentTouchingSnapshotStore}.
|
|
10
|
+
*/
|
|
11
|
+
exports.CONTENT_TOUCHING_SNAPSHOT_STORE = Symbol.for("loom.plugin-content.ContentTouchingSnapshotStore");
|
|
12
|
+
/**
|
|
13
|
+
* True when `store` is a {@link ContentTouchingSnapshotStore}. Evidence about THIS object only: a
|
|
14
|
+
* `false` does not prove the runtime wrote unwrapped (a sweep or the CLI reads a raw store over the
|
|
15
|
+
* same tables a wrapped one wrote), and a `true` does not prove every OTHER store the runtime saves
|
|
16
|
+
* to is wrapped. `sweepUnpinnedContent` therefore uses it only to warn, never to relax or refuse.
|
|
17
|
+
*/
|
|
18
|
+
function isContentTouchingSnapshotStore(store) {
|
|
19
|
+
return (typeof store === "object" &&
|
|
20
|
+
store !== null &&
|
|
21
|
+
store[exports.CONTENT_TOUCHING_SNAPSHOT_STORE] === true);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* ADR 0055 D6 amendment 2 — the SECOND citation path. `copyPrefix` copies only the post-gap events
|
|
25
|
+
* of a compacted parent; the refs its truncated prefix cited live in the child's SEED SNAPSHOT
|
|
26
|
+
* (the runtime's `fork` saves it), where an R5 media source or a claim-check envelope stays
|
|
27
|
+
* verbatim and nothing `put`s it again. `ContentResolvingEventStore` cannot see it, so this
|
|
28
|
+
* decorator touches the citations of every snapshot it SAVES, before and after, exactly as that
|
|
29
|
+
* store does for events (same walker, same guarantee: D5's margin, see {@link touchAround}).
|
|
30
|
+
*
|
|
31
|
+
* Chosen over a hook in the runtime because the runtime holds no content store (and must not
|
|
32
|
+
* depend on the content plugin), and over touching at fork start because a decorator also covers
|
|
33
|
+
* compaction checkpoints and every other save with no extra seam. Cost: one iterative walk of the
|
|
34
|
+
* saved state and a round trip only when it cites a blob; with a content store that has no `touch`
|
|
35
|
+
* nothing at all.
|
|
36
|
+
*
|
|
37
|
+
* Wrap it OUTSIDE an `EncryptingSnapshotStore` (it must see the plaintext state, or refs under a
|
|
38
|
+
* sensitive path are hidden from the walk), and pass the same content store the event log uses.
|
|
39
|
+
* The seed is saved after the prefix copy, so a fork racing the DELETION of its own parent is
|
|
40
|
+
* outside the guarantee (the fork's read of the parent fails then, too).
|
|
41
|
+
*/
|
|
42
|
+
class ContentTouchingSnapshotStore {
|
|
43
|
+
inner;
|
|
44
|
+
content;
|
|
45
|
+
deleteExecution;
|
|
46
|
+
loadLatestSealed;
|
|
47
|
+
pruneSnapshots;
|
|
48
|
+
listSequences;
|
|
49
|
+
/** Brand: see {@link isContentTouchingSnapshotStore}. */
|
|
50
|
+
[exports.CONTENT_TOUCHING_SNAPSHOT_STORE] = true;
|
|
51
|
+
onTouchError;
|
|
52
|
+
constructor(inner, content, options = {}) {
|
|
53
|
+
this.inner = inner;
|
|
54
|
+
this.content = content;
|
|
55
|
+
this.onTouchError = options.onTouchError;
|
|
56
|
+
const plan = {
|
|
57
|
+
deleteExecution: "forward",
|
|
58
|
+
loadLatestSealed: "forward",
|
|
59
|
+
// ADR 0134 D6 — a delete cites nothing, so there is nothing to touch. A blob only a pruned
|
|
60
|
+
// snapshot cited was never pinned by the sweep (it pins the latest and the checkpoint,
|
|
61
|
+
// both of which pruning keeps), so no sweep decision changes.
|
|
62
|
+
pruneSnapshots: "forward",
|
|
63
|
+
// ADR 0134 amendment - a read of sequences cites no content.
|
|
64
|
+
listSequences: "forward",
|
|
65
|
+
};
|
|
66
|
+
(0, event_runtime_1.installOptional)(this, inner, plan);
|
|
67
|
+
}
|
|
68
|
+
save(snapshot) {
|
|
69
|
+
return (0, touchAround_1.touchAround)(this.content, [snapshot.state], { operation: "snapshotSave", executionId: snapshot.executionId }, this.onTouchError, () => this.inner.save(snapshot));
|
|
70
|
+
}
|
|
71
|
+
loadLatest(executionId) {
|
|
72
|
+
return this.inner.loadLatest(executionId);
|
|
73
|
+
}
|
|
74
|
+
loadAt(executionId, sequence) {
|
|
75
|
+
return this.inner.loadAt(executionId, sequence);
|
|
76
|
+
}
|
|
77
|
+
loadLatestBefore(executionId, sequence) {
|
|
78
|
+
return this.inner.loadLatestBefore(executionId, sequence);
|
|
79
|
+
}
|
|
80
|
+
close() {
|
|
81
|
+
return this.inner.close();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
exports.ContentTouchingSnapshotStore = ContentTouchingSnapshotStore;
|
|
85
|
+
//# sourceMappingURL=ContentTouchingSnapshotStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContentTouchingSnapshotStore.js","sourceRoot":"","sources":["../src/ContentTouchingSnapshotStore.ts"],"names":[],"mappings":";;;AAuBA,wEAMC;AA7BD,2DAA8E;AAO9E,+CAAiE;AAEjE;;;GAGG;AACU,QAAA,+BAA+B,GAAkB,MAAM,CAAC,GAAG,CACtE,kDAAkD,CACnD,CAAC;AAEF;;;;;GAKG;AACH,SAAgB,8BAA8B,CAAC,KAAc;IAC3D,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACb,KAAiC,CAAC,uCAA+B,CAAC,KAAK,IAAI,CAC7E,CAAC;AACJ,CAAC;AAOD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAa,4BAA4B;IAUpB;IACA;IAVV,eAAe,CAAiD;IAChE,gBAAgB,CAAkD;IAClE,cAAc,CAAgD;IAC9D,aAAa,CAA+C;IACrE,yDAAyD;IAChD,CAAC,uCAA+B,CAAC,GAAG,IAAI,CAAC;IACjC,YAAY,CAA6B;IAE1D,YACmB,KAAoB,EACpB,OAAoC,EACrD,UAA+C,EAAE;QAFhC,UAAK,GAAL,KAAK,CAAe;QACpB,YAAO,GAAP,OAAO,CAA6B;QAGrD,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;QACzC,MAAM,IAAI,GAAG;YACX,eAAe,EAAE,SAAS;YAC1B,gBAAgB,EAAE,SAAS;YAC3B,2FAA2F;YAC3F,uFAAuF;YACvF,8DAA8D;YAC9D,cAAc,EAAE,SAAS;YACzB,6DAA6D;YAC7D,aAAa,EAAE,SAAS;SAC2C,CAAC;QACtE,IAAA,+BAAe,EAA6C,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IACjF,CAAC;IAED,IAAI,CAAC,QAAkB;QACrB,OAAO,IAAA,yBAAW,EAChB,IAAI,CAAC,OAAO,EACZ,CAAC,QAAQ,CAAC,KAAK,CAAC,EAChB,EAAE,SAAS,EAAE,cAAc,EAAE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE,EAChE,IAAI,CAAC,YAAY,EACjB,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAChC,CAAC;IACJ,CAAC;IAED,UAAU,CAAC,WAAmB;QAC5B,OAAO,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;IAC5C,CAAC;IAED,MAAM,CAAC,WAAmB,EAAE,QAAgB;QAC1C,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IAClD,CAAC;IAED,gBAAgB,CAAC,WAAmB,EAAE,QAAgB;QACpD,OAAO,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IAC5D,CAAC;IAED,KAAK;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IAC5B,CAAC;CACF;AArDD,oEAqDC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Visit every content address an event payload (or snapshot state) cites. Any object carrying a
|
|
3
|
+
* string `__loomContent` is a ref leaf — this covers bare ADR 0046
|
|
4
|
+
* `ContentRef`s (e.g. a finding's `evidence` list, an externalized field) AND
|
|
5
|
+
* the `ref` of an ADR 0054 claim-check envelope. `inEnvelope` is true ONLY for
|
|
6
|
+
* a RECLAIMABLE envelope's own `ref` — the offloaded value, the one citation whose
|
|
7
|
+
* erasure is replay-invisible (ADR 0055 D4). An envelope marked `retain: true` (ADR 0140:
|
|
8
|
+
* the opt-in agent claim-check, whose offloaded bytes are the ONLY copy of a tool output the
|
|
9
|
+
* log once held) reports its ref as load-bearing (`inEnvelope` false): kept while any live
|
|
10
|
+
* log, archive segment or snapshot cites it, freed only when that history is deleted. Every other envelope field (`digest` above
|
|
11
|
+
* all: orchestration branches on it) is recorded, replay-visible data and is
|
|
12
|
+
* walked in the envelope's own context like any other payload.
|
|
13
|
+
*
|
|
14
|
+
* Because the enveloped context never descends (the `ref` is a leaf), one
|
|
15
|
+
* WeakSet cycle guard suffices: an object aliased at a bare site and as an
|
|
16
|
+
* envelope's `ref` is still visited bare, since the envelope reports its ref
|
|
17
|
+
* directly rather than through the guard.
|
|
18
|
+
*
|
|
19
|
+
* Iterative with cycle detection: the sweep is a retention job over
|
|
20
|
+
* app-controlled payloads, so a pathologically deep (or, from an in-memory
|
|
21
|
+
* store, cyclic) value must not stack-overflow the pass (audit W8).
|
|
22
|
+
*/
|
|
23
|
+
export declare function visitCitedAddresses(value: unknown, visit: (address: string, inEnvelope: boolean) => void,
|
|
24
|
+
/**
|
|
25
|
+
* ADR 0140 D11 - when given, a `retain: true` envelope is honoured as load-bearing ONLY if it is in
|
|
26
|
+
* this set (see {@link retainRootsOf}); any other (a nested, tool-authored lookalike) stays reclaimable.
|
|
27
|
+
* Absent: every retained envelope is honoured (snapshots and archives, which pin everything anyway).
|
|
28
|
+
*/
|
|
29
|
+
trustedRetain?: ReadonlySet<object>): void;
|
|
30
|
+
/**
|
|
31
|
+
* ADR 0140 D11 - the objects in an event payload where a retained envelope is TRUSTED: the top-level
|
|
32
|
+
* `output` of a `STEP_COMPLETED` (through a taint wrapper), exactly where the opt-in preset records its
|
|
33
|
+
* envelope. A `retain: true` lookalike nested inside a tool's result is tool-authored data: it cannot
|
|
34
|
+
* pin a blob.
|
|
35
|
+
*/
|
|
36
|
+
export declare function retainRootsOf(event: {
|
|
37
|
+
eventType: string;
|
|
38
|
+
payload: unknown;
|
|
39
|
+
}): Set<object>;
|
|
40
|
+
//# sourceMappingURL=citations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"citations.d.ts","sourceRoot":"","sources":["../src/citations.ts"],"names":[],"mappings":"AAuBA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,KAAK,IAAI;AACrD;;;;GAIG;AACH,aAAa,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GAClC,IAAI,CAyCN;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GAAG,GAAG,CAAC,MAAM,CAAC,CAazF"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// The ONE walker for "which blobs does this value cite" — shared by the mark phase of
|
|
3
|
+
// `sweepUnpinnedContent` and by `ContentResolvingEventStore`, which touches what it carries
|
|
4
|
+
// into a log without a `put` (ADR 0055 D6 amendment 2). Iterative + cycle-safe: neither a
|
|
5
|
+
// retention job nor an append may stack-overflow on an app-controlled payload.
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.visitCitedAddresses = visitCitedAddresses;
|
|
8
|
+
exports.retainRootsOf = retainRootsOf;
|
|
9
|
+
/**
|
|
10
|
+
* The structural claim-check test, identical to `isClaimCheck` in `@loomwork/middleware`
|
|
11
|
+
* (kept structural so this plugin takes no middleware dependency): the flag AND a string
|
|
12
|
+
* `ref.__loomContent`. An app object that merely carries the flag is app data.
|
|
13
|
+
*/
|
|
14
|
+
function envelopeRef(value) {
|
|
15
|
+
if (value.__loomClaimCheck !== true)
|
|
16
|
+
return undefined;
|
|
17
|
+
const address = value.ref?.__loomContent;
|
|
18
|
+
return typeof address === "string" ? address : undefined;
|
|
19
|
+
}
|
|
20
|
+
/** The address of an R5 media source `{ kind: "ref", ref: { address: string } }`, if `value` is one. */
|
|
21
|
+
function mediaSourceAddress(value) {
|
|
22
|
+
if (value.kind !== "ref")
|
|
23
|
+
return undefined;
|
|
24
|
+
const address = value.ref?.address;
|
|
25
|
+
return typeof address === "string" ? address : undefined;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Visit every content address an event payload (or snapshot state) cites. Any object carrying a
|
|
29
|
+
* string `__loomContent` is a ref leaf — this covers bare ADR 0046
|
|
30
|
+
* `ContentRef`s (e.g. a finding's `evidence` list, an externalized field) AND
|
|
31
|
+
* the `ref` of an ADR 0054 claim-check envelope. `inEnvelope` is true ONLY for
|
|
32
|
+
* a RECLAIMABLE envelope's own `ref` — the offloaded value, the one citation whose
|
|
33
|
+
* erasure is replay-invisible (ADR 0055 D4). An envelope marked `retain: true` (ADR 0140:
|
|
34
|
+
* the opt-in agent claim-check, whose offloaded bytes are the ONLY copy of a tool output the
|
|
35
|
+
* log once held) reports its ref as load-bearing (`inEnvelope` false): kept while any live
|
|
36
|
+
* log, archive segment or snapshot cites it, freed only when that history is deleted. Every other envelope field (`digest` above
|
|
37
|
+
* all: orchestration branches on it) is recorded, replay-visible data and is
|
|
38
|
+
* walked in the envelope's own context like any other payload.
|
|
39
|
+
*
|
|
40
|
+
* Because the enveloped context never descends (the `ref` is a leaf), one
|
|
41
|
+
* WeakSet cycle guard suffices: an object aliased at a bare site and as an
|
|
42
|
+
* envelope's `ref` is still visited bare, since the envelope reports its ref
|
|
43
|
+
* directly rather than through the guard.
|
|
44
|
+
*
|
|
45
|
+
* Iterative with cycle detection: the sweep is a retention job over
|
|
46
|
+
* app-controlled payloads, so a pathologically deep (or, from an in-memory
|
|
47
|
+
* store, cyclic) value must not stack-overflow the pass (audit W8).
|
|
48
|
+
*/
|
|
49
|
+
function visitCitedAddresses(value, visit,
|
|
50
|
+
/**
|
|
51
|
+
* ADR 0140 D11 - when given, a `retain: true` envelope is honoured as load-bearing ONLY if it is in
|
|
52
|
+
* this set (see {@link retainRootsOf}); any other (a nested, tool-authored lookalike) stays reclaimable.
|
|
53
|
+
* Absent: every retained envelope is honoured (snapshots and archives, which pin everything anyway).
|
|
54
|
+
*/
|
|
55
|
+
trustedRetain) {
|
|
56
|
+
const stack = [value];
|
|
57
|
+
const seen = new WeakSet();
|
|
58
|
+
while (stack.length > 0) {
|
|
59
|
+
const current = stack.pop();
|
|
60
|
+
if (typeof current !== "object" || current === null)
|
|
61
|
+
continue;
|
|
62
|
+
if (seen.has(current))
|
|
63
|
+
continue;
|
|
64
|
+
seen.add(current);
|
|
65
|
+
const address = current.__loomContent;
|
|
66
|
+
if (typeof address === "string") {
|
|
67
|
+
visit(address, false);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (Array.isArray(current)) {
|
|
71
|
+
for (const item of current)
|
|
72
|
+
stack.push(item);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
// R5 (ADR 0120) — an LLM media source `{ kind: "ref", ref: { address } }` cites a blob by its
|
|
76
|
+
// address WITHOUT the `__loomContent` marker (deliberately: the resolving store would
|
|
77
|
+
// JSON-decode that marker). It is as load-bearing as a bare ref — a live model call resolves
|
|
78
|
+
// it, and erasing it makes the next call fail — so the mark phase must see it, or the sweep
|
|
79
|
+
// would reclaim media the transcript still cites.
|
|
80
|
+
const mediaAddress = mediaSourceAddress(current);
|
|
81
|
+
if (mediaAddress !== undefined) {
|
|
82
|
+
visit(mediaAddress, false);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const offloaded = envelopeRef(current);
|
|
86
|
+
// ADR 0140 - a RETAINED envelope's blob is audit evidence, not telemetry: load-bearing.
|
|
87
|
+
if (offloaded !== undefined) {
|
|
88
|
+
visit(offloaded, current.retain !== true ||
|
|
89
|
+
(trustedRetain !== undefined && !trustedRetain.has(current)));
|
|
90
|
+
}
|
|
91
|
+
for (const [key, item] of Object.entries(current)) {
|
|
92
|
+
if (offloaded !== undefined && key === "ref")
|
|
93
|
+
continue;
|
|
94
|
+
stack.push(item);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* ADR 0140 D11 - the objects in an event payload where a retained envelope is TRUSTED: the top-level
|
|
100
|
+
* `output` of a `STEP_COMPLETED` (through a taint wrapper), exactly where the opt-in preset records its
|
|
101
|
+
* envelope. A `retain: true` lookalike nested inside a tool's result is tool-authored data: it cannot
|
|
102
|
+
* pin a blob.
|
|
103
|
+
*/
|
|
104
|
+
function retainRootsOf(event) {
|
|
105
|
+
const roots = new Set();
|
|
106
|
+
if (event.eventType !== "STEP_COMPLETED")
|
|
107
|
+
return roots;
|
|
108
|
+
let out = event.payload?.output;
|
|
109
|
+
if (typeof out === "object" &&
|
|
110
|
+
out !== null &&
|
|
111
|
+
out.__loomTainted !== undefined) {
|
|
112
|
+
out = out.value;
|
|
113
|
+
}
|
|
114
|
+
if (typeof out === "object" && out !== null)
|
|
115
|
+
roots.add(out);
|
|
116
|
+
return roots;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=citations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"citations.js","sourceRoot":"","sources":["../src/citations.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.bench.d.ts","sourceRoot":"","sources":["../src/content.bench.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const event_runtime_1 = require("@loomwork/event-runtime");
|
|
4
|
+
const vitest_1 = require("vitest");
|
|
5
|
+
const ContentResolvingEventStore_1 = require("./ContentResolvingEventStore");
|
|
6
|
+
const ContentStore_1 = require("./ContentStore");
|
|
7
|
+
// ── ADR 0062 guardrail bench — frozen-snapshot serving (TS-10) ────────────────
|
|
8
|
+
//
|
|
9
|
+
// 50 externalized refs (one dashboard's widgets) read through a backend with
|
|
10
|
+
// ~2ms simulated round-trip latency. Compare: batched getMany (one round-trip)
|
|
11
|
+
// vs the no-getMany fallback (coalesced parallel gets) vs the pre-ADR-0062
|
|
12
|
+
// baseline shape (serialized per-ref awaits). Budget: p95 ≤ 150ms for ≤ 50 refs
|
|
13
|
+
// — the batched path should sit near the single round-trip floor.
|
|
14
|
+
const LATENCY_MS = 2;
|
|
15
|
+
const WIDGETS = 50;
|
|
16
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
17
|
+
function withLatency(inner, supportsGetMany) {
|
|
18
|
+
const store = {
|
|
19
|
+
put: (bytes) => inner.put(bytes),
|
|
20
|
+
get: async (ref) => {
|
|
21
|
+
await sleep(LATENCY_MS);
|
|
22
|
+
return inner.get(ref);
|
|
23
|
+
},
|
|
24
|
+
delete: (ref) => inner.delete(ref),
|
|
25
|
+
};
|
|
26
|
+
if (supportsGetMany) {
|
|
27
|
+
store.getMany = async (refs) => {
|
|
28
|
+
await sleep(LATENCY_MS);
|
|
29
|
+
return inner.getMany(refs);
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return store;
|
|
33
|
+
}
|
|
34
|
+
function envelope(sequence, payload) {
|
|
35
|
+
return {
|
|
36
|
+
id: `e-${sequence}`,
|
|
37
|
+
executionId: "x-1",
|
|
38
|
+
workflowId: "w-1",
|
|
39
|
+
eventType: "STEP_COMPLETED",
|
|
40
|
+
sequence,
|
|
41
|
+
timestamp: 1_000 + sequence,
|
|
42
|
+
payload,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async function seeded(supportsGetMany) {
|
|
46
|
+
const memory = new ContentStore_1.InMemoryContentStore();
|
|
47
|
+
const content = withLatency(memory, supportsGetMany);
|
|
48
|
+
const store = new ContentResolvingEventStore_1.ContentResolvingEventStore(new event_runtime_1.InMemoryAsyncEventStore(), content, {
|
|
49
|
+
externalize: { STEP_COMPLETED: ["output.doc"] },
|
|
50
|
+
});
|
|
51
|
+
for (let i = 0; i < WIDGETS; i++) {
|
|
52
|
+
await store.append(envelope(i, { stepId: `hydrate#${i}`, output: { doc: { widget: i } } }));
|
|
53
|
+
}
|
|
54
|
+
return store;
|
|
55
|
+
}
|
|
56
|
+
let batched;
|
|
57
|
+
let fallback;
|
|
58
|
+
(0, vitest_1.describe)(`resolve ${WIDGETS} refs @ ${LATENCY_MS}ms backend latency`, () => {
|
|
59
|
+
(0, vitest_1.bench)("getMany (one batched round-trip)", async () => {
|
|
60
|
+
batched ??= await seeded(true);
|
|
61
|
+
await batched.read("x-1");
|
|
62
|
+
});
|
|
63
|
+
(0, vitest_1.bench)("no getMany (coalesced parallel gets)", async () => {
|
|
64
|
+
fallback ??= await seeded(false);
|
|
65
|
+
await fallback.read("x-1");
|
|
66
|
+
});
|
|
67
|
+
(0, vitest_1.bench)("pre-ADR-0062 shape (serialized per-ref awaits — baseline)", async () => {
|
|
68
|
+
// The old resolver awaited object fields sequentially; emulate its
|
|
69
|
+
// round-trip profile directly against the latency store.
|
|
70
|
+
for (let i = 0; i < WIDGETS; i++) {
|
|
71
|
+
await sleep(LATENCY_MS);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
//# sourceMappingURL=content.bench.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.bench.js","sourceRoot":"","sources":["../src/content.bench.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** A content-addressed reference standing in for an externalized value in the log. */
|
|
2
|
+
export interface ContentRef {
|
|
3
|
+
/** The content address, e.g. `"sha256:<hex>"`. Uniquely and deterministically names the bytes. */
|
|
4
|
+
readonly __loomContent: string;
|
|
5
|
+
/** Byte length of the stored content (informational). */
|
|
6
|
+
readonly size?: number;
|
|
7
|
+
/** How the bytes decode back to a value (only `"json"` today). */
|
|
8
|
+
readonly codec?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function isContentRef(value: unknown): value is ContentRef;
|
|
11
|
+
/** The content address of some bytes: `sha256:<hex>`. Pure — the same bytes always map here. */
|
|
12
|
+
export declare function contentAddress(bytes: Uint8Array): string;
|
|
13
|
+
/** Encode a value to bytes for the content store (JSON — the only codec today). */
|
|
14
|
+
export declare function encodeValue(value: unknown): Uint8Array;
|
|
15
|
+
/** Decode content-store bytes back to the original value. */
|
|
16
|
+
export declare function decodeValue(bytes: Uint8Array): unknown;
|
|
17
|
+
/** Whether a payload carries any {@link ContentRef} (the read-side fast path). */
|
|
18
|
+
export declare function hasContentRef(value: unknown): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* ADR 0062 — phase 1 of batched resolution: collect every {@link ContentRef} in
|
|
21
|
+
* a payload into `into` (keyed by content address — duplicates collapse), and
|
|
22
|
+
* report whether any was found. One walk replaces the old `hasContentRef`
|
|
23
|
+
* pre-walk + per-ref fetch recursion.
|
|
24
|
+
*/
|
|
25
|
+
export declare function collectContentRefs(value: unknown, into: Map<string, ContentRef>): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* ADR 0062 — phase 2 of batched resolution: pure rebuild substituting each
|
|
28
|
+
* {@link ContentRef} from the fetched bytes. Decoding is deliberately
|
|
29
|
+
* per-OCCURRENCE (the fetch dedupes by address — the expensive part — but two
|
|
30
|
+
* payload sites never alias one decoded object). Ref-free subtrees are returned
|
|
31
|
+
* as-is (structural sharing).
|
|
32
|
+
*/
|
|
33
|
+
export declare function substituteContentRefs(value: unknown, bytesByAddress: ReadonlyMap<string, Uint8Array>): unknown;
|
|
34
|
+
/**
|
|
35
|
+
* Async structural path-mapper — like the crypto plugin's `mapPaths`, but the transform is
|
|
36
|
+
* awaited (a content `put` is async). Copies only the spine it touches (structural sharing
|
|
37
|
+
* elsewhere), never mutates `root`, and skips absent paths.
|
|
38
|
+
*/
|
|
39
|
+
export declare function mapPathsAsync(root: unknown, paths: readonly string[], transform: (value: unknown) => Promise<unknown>): Promise<unknown>;
|
|
40
|
+
//# sourceMappingURL=content.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../src/content.ts"],"names":[],"mappings":"AAWA,sFAAsF;AACtF,MAAM,WAAW,UAAU;IACzB,kGAAkG;IAClG,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,yDAAyD;IACzD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,kEAAkE;IAClE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAMhE;AAED,gGAAgG;AAChG,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAExD;AAOD,mFAAmF;AACnF,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,CAEtD;AAED,6DAA6D;AAC7D,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAEtD;AAuBD,kFAAkF;AAClF,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAQrD;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,OAAO,CAmBzF;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,OAAO,EACd,cAAc,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,GAC9C,OAAO,CA6BT;AAQD;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,OAAO,EACb,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,SAAS,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,GAC9C,OAAO,CAAC,OAAO,CAAC,CA0BlB"}
|
package/dist/content.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isContentRef = isContentRef;
|
|
4
|
+
exports.contentAddress = contentAddress;
|
|
5
|
+
exports.encodeValue = encodeValue;
|
|
6
|
+
exports.decodeValue = decodeValue;
|
|
7
|
+
exports.hasContentRef = hasContentRef;
|
|
8
|
+
exports.collectContentRefs = collectContentRefs;
|
|
9
|
+
exports.substituteContentRefs = substituteContentRefs;
|
|
10
|
+
exports.mapPathsAsync = mapPathsAsync;
|
|
11
|
+
const node_crypto_1 = require("node:crypto");
|
|
12
|
+
const classify_1 = require("@loomwork/classify");
|
|
13
|
+
function isContentRef(value) {
|
|
14
|
+
return (typeof value === "object" &&
|
|
15
|
+
value !== null &&
|
|
16
|
+
typeof value.__loomContent === "string");
|
|
17
|
+
}
|
|
18
|
+
/** The content address of some bytes: `sha256:<hex>`. Pure — the same bytes always map here. */
|
|
19
|
+
function contentAddress(bytes) {
|
|
20
|
+
return `sha256:${(0, node_crypto_1.createHash)("sha256").update(bytes).digest("hex")}`;
|
|
21
|
+
}
|
|
22
|
+
// Stateless for whole-input encode/decode — hoisted (audit nit: decodeValue runs
|
|
23
|
+
// per ref occurrence during substitution, incl. the live tail).
|
|
24
|
+
const UTF8_ENCODER = new TextEncoder();
|
|
25
|
+
const UTF8_DECODER = new TextDecoder();
|
|
26
|
+
/** Encode a value to bytes for the content store (JSON — the only codec today). */
|
|
27
|
+
function encodeValue(value) {
|
|
28
|
+
return UTF8_ENCODER.encode(JSON.stringify(value));
|
|
29
|
+
}
|
|
30
|
+
/** Decode content-store bytes back to the original value. */
|
|
31
|
+
function decodeValue(bytes) {
|
|
32
|
+
return JSON.parse(UTF8_DECODER.decode(bytes));
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* An ADR 0054 claim-check envelope, recognised structurally with the same test as
|
|
36
|
+
* `@loomwork/middleware`'s `isClaimCheck` (`__loomClaimCheck === true` AND a string
|
|
37
|
+
* `ref.__loomContent`), so this plugin takes no dependency for it (ADR 0055 D3).
|
|
38
|
+
*
|
|
39
|
+
* The read-side walkers below treat one as a LEAF. An envelope is resolved only by the
|
|
40
|
+
* claim-check plugin (`resolveClaimCheck`): its `ref` is a TTL-reclaimable citation whose
|
|
41
|
+
* erasure the fold never sees, and its bytes may not be JSON (`codec: "bytes"`). Descending
|
|
42
|
+
* into it replaced `ref` with the decoded value, threw `ContentMissingError` once the sweep
|
|
43
|
+
* had reclaimed the blob, and failed outright on a binary one (ADR 0055 D4's known gap).
|
|
44
|
+
*/
|
|
45
|
+
function isClaimCheckEnvelope(value) {
|
|
46
|
+
const v = value;
|
|
47
|
+
return (v.__loomClaimCheck === true &&
|
|
48
|
+
typeof v.ref === "object" &&
|
|
49
|
+
v.ref !== null &&
|
|
50
|
+
typeof v.ref.__loomContent === "string");
|
|
51
|
+
}
|
|
52
|
+
/** Whether a payload carries any {@link ContentRef} (the read-side fast path). */
|
|
53
|
+
function hasContentRef(value) {
|
|
54
|
+
if (isContentRef(value))
|
|
55
|
+
return true;
|
|
56
|
+
if (Array.isArray(value))
|
|
57
|
+
return value.some(hasContentRef);
|
|
58
|
+
if (typeof value === "object" && value !== null) {
|
|
59
|
+
if (isClaimCheckEnvelope(value))
|
|
60
|
+
return false; // the claim-check plugin's to resolve
|
|
61
|
+
return Object.values(value).some(hasContentRef);
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* ADR 0062 — phase 1 of batched resolution: collect every {@link ContentRef} in
|
|
67
|
+
* a payload into `into` (keyed by content address — duplicates collapse), and
|
|
68
|
+
* report whether any was found. One walk replaces the old `hasContentRef`
|
|
69
|
+
* pre-walk + per-ref fetch recursion.
|
|
70
|
+
*/
|
|
71
|
+
function collectContentRefs(value, into) {
|
|
72
|
+
if (isContentRef(value)) {
|
|
73
|
+
if (!into.has(value.__loomContent))
|
|
74
|
+
into.set(value.__loomContent, value);
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
if (Array.isArray(value)) {
|
|
78
|
+
let found = false;
|
|
79
|
+
for (const v of value)
|
|
80
|
+
if (collectContentRefs(v, into))
|
|
81
|
+
found = true;
|
|
82
|
+
return found;
|
|
83
|
+
}
|
|
84
|
+
if (typeof value === "object" && value !== null) {
|
|
85
|
+
if (isClaimCheckEnvelope(value))
|
|
86
|
+
return false; // a leaf: see isClaimCheckEnvelope
|
|
87
|
+
let found = false;
|
|
88
|
+
for (const v of Object.values(value)) {
|
|
89
|
+
if (collectContentRefs(v, into))
|
|
90
|
+
found = true;
|
|
91
|
+
}
|
|
92
|
+
return found;
|
|
93
|
+
}
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* ADR 0062 — phase 2 of batched resolution: pure rebuild substituting each
|
|
98
|
+
* {@link ContentRef} from the fetched bytes. Decoding is deliberately
|
|
99
|
+
* per-OCCURRENCE (the fetch dedupes by address — the expensive part — but two
|
|
100
|
+
* payload sites never alias one decoded object). Ref-free subtrees are returned
|
|
101
|
+
* as-is (structural sharing).
|
|
102
|
+
*/
|
|
103
|
+
function substituteContentRefs(value, bytesByAddress) {
|
|
104
|
+
if (isContentRef(value)) {
|
|
105
|
+
const bytes = bytesByAddress.get(value.__loomContent);
|
|
106
|
+
// Callers guarantee every collected address was fetched; a miss here is a
|
|
107
|
+
// programming error, surfaced loudly.
|
|
108
|
+
if (bytes === undefined)
|
|
109
|
+
throw new Error(`unfetched content ref: ${value.__loomContent}`);
|
|
110
|
+
return decodeValue(bytes);
|
|
111
|
+
}
|
|
112
|
+
if (Array.isArray(value)) {
|
|
113
|
+
let changed = false;
|
|
114
|
+
const out = value.map((v) => {
|
|
115
|
+
const r = substituteContentRefs(v, bytesByAddress);
|
|
116
|
+
if (r !== v)
|
|
117
|
+
changed = true;
|
|
118
|
+
return r;
|
|
119
|
+
});
|
|
120
|
+
return changed ? out : value;
|
|
121
|
+
}
|
|
122
|
+
if (typeof value === "object" && value !== null) {
|
|
123
|
+
if (isClaimCheckEnvelope(value))
|
|
124
|
+
return value; // a leaf: see isClaimCheckEnvelope
|
|
125
|
+
let changed = false;
|
|
126
|
+
const out = {};
|
|
127
|
+
for (const [k, v] of Object.entries(value)) {
|
|
128
|
+
const r = substituteContentRefs(v, bytesByAddress);
|
|
129
|
+
out[k] = r;
|
|
130
|
+
if (r !== v)
|
|
131
|
+
changed = true;
|
|
132
|
+
}
|
|
133
|
+
return changed ? out : value;
|
|
134
|
+
}
|
|
135
|
+
return value;
|
|
136
|
+
}
|
|
137
|
+
function shallowCopy(value) {
|
|
138
|
+
return Array.isArray(value)
|
|
139
|
+
? [...value]
|
|
140
|
+
: { ...value };
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Async structural path-mapper — like the crypto plugin's `mapPaths`, but the transform is
|
|
144
|
+
* awaited (a content `put` is async). Copies only the spine it touches (structural sharing
|
|
145
|
+
* elsewhere), never mutates `root`, and skips absent paths.
|
|
146
|
+
*/
|
|
147
|
+
async function mapPathsAsync(root, paths, transform) {
|
|
148
|
+
if (paths.length === 0 || typeof root !== "object" || root === null)
|
|
149
|
+
return root;
|
|
150
|
+
// `[]` segments are resolved against the concrete value first, so a mark inside an array
|
|
151
|
+
// element reaches every element (#260). Paths without one pass through untouched.
|
|
152
|
+
const targets = (0, classify_1.hasWildcard)(paths) ? (0, classify_1.expandPaths)(root, paths) : paths;
|
|
153
|
+
const out = shallowCopy(root);
|
|
154
|
+
for (const path of targets) {
|
|
155
|
+
const segs = path.split(".");
|
|
156
|
+
let src = root;
|
|
157
|
+
let dst = out;
|
|
158
|
+
let reachable = true;
|
|
159
|
+
for (let i = 0; i < segs.length - 1; i++) {
|
|
160
|
+
const seg = segs[i];
|
|
161
|
+
const child = src[seg];
|
|
162
|
+
if (typeof child !== "object" || child === null) {
|
|
163
|
+
reachable = false;
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
if (dst[seg] === child)
|
|
167
|
+
dst[seg] = shallowCopy(child);
|
|
168
|
+
src = child;
|
|
169
|
+
dst = dst[seg];
|
|
170
|
+
}
|
|
171
|
+
const leaf = segs[segs.length - 1];
|
|
172
|
+
if (reachable && Object.hasOwn(src, leaf))
|
|
173
|
+
dst[leaf] = await transform(src[leaf]);
|
|
174
|
+
}
|
|
175
|
+
return out;
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=content.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.js","sourceRoot":"","sources":["../src/content.ts"],"names":[],"mappings":"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"}
|