@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.
Files changed (69) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +8 -0
  3. package/README.md +92 -2
  4. package/dist/CachingContentStore.d.ts +54 -0
  5. package/dist/CachingContentStore.d.ts.map +1 -0
  6. package/dist/CachingContentStore.js +171 -0
  7. package/dist/CachingContentStore.js.map +1 -0
  8. package/dist/ContentResolvingEventStore.d.ts +70 -0
  9. package/dist/ContentResolvingEventStore.d.ts.map +1 -0
  10. package/dist/ContentResolvingEventStore.js +245 -0
  11. package/dist/ContentResolvingEventStore.js.map +1 -0
  12. package/dist/ContentStore.d.ts +186 -0
  13. package/dist/ContentStore.d.ts.map +1 -0
  14. package/dist/ContentStore.js +177 -0
  15. package/dist/ContentStore.js.map +1 -0
  16. package/dist/ContentTouchingSnapshotStore.d.ts +56 -0
  17. package/dist/ContentTouchingSnapshotStore.d.ts.map +1 -0
  18. package/dist/ContentTouchingSnapshotStore.js +85 -0
  19. package/dist/ContentTouchingSnapshotStore.js.map +1 -0
  20. package/dist/citations.d.ts +40 -0
  21. package/dist/citations.d.ts.map +1 -0
  22. package/dist/citations.js +118 -0
  23. package/dist/citations.js.map +1 -0
  24. package/dist/content.bench.d.ts +2 -0
  25. package/dist/content.bench.d.ts.map +1 -0
  26. package/dist/content.bench.js +75 -0
  27. package/dist/content.bench.js.map +1 -0
  28. package/dist/content.d.ts +40 -0
  29. package/dist/content.d.ts.map +1 -0
  30. package/dist/content.js +177 -0
  31. package/dist/content.js.map +1 -0
  32. package/dist/derive.d.ts +9 -0
  33. package/dist/derive.d.ts.map +1 -0
  34. package/dist/derive.js +40 -0
  35. package/dist/derive.js.map +1 -0
  36. package/dist/errors.d.ts +6 -0
  37. package/dist/errors.d.ts.map +1 -0
  38. package/dist/errors.js +20 -0
  39. package/dist/errors.js.map +1 -0
  40. package/dist/executionEnd.d.ts +22 -0
  41. package/dist/executionEnd.d.ts.map +1 -0
  42. package/dist/executionEnd.js +66 -0
  43. package/dist/executionEnd.js.map +1 -0
  44. package/dist/gc.d.ts +470 -0
  45. package/dist/gc.d.ts.map +1 -0
  46. package/dist/gc.js +819 -0
  47. package/dist/gc.js.map +1 -0
  48. package/dist/index.d.ts +10 -0
  49. package/dist/index.d.ts.map +1 -0
  50. package/dist/index.js +42 -0
  51. package/dist/index.js.map +1 -0
  52. package/dist/touchAround.d.ts +46 -0
  53. package/dist/touchAround.d.ts.map +1 -0
  54. package/dist/touchAround.js +98 -0
  55. package/dist/touchAround.js.map +1 -0
  56. package/package.json +58 -3
  57. package/src/CachingContentStore.ts +184 -0
  58. package/src/ContentResolvingEventStore.ts +325 -0
  59. package/src/ContentStore.ts +317 -0
  60. package/src/ContentTouchingSnapshotStore.ts +109 -0
  61. package/src/citations.ts +117 -0
  62. package/src/content.bench.ts +84 -0
  63. package/src/content.ts +191 -0
  64. package/src/derive.ts +40 -0
  65. package/src/errors.ts +19 -0
  66. package/src/executionEnd.ts +64 -0
  67. package/src/gc.ts +1213 -0
  68. package/src/index.ts +54 -0
  69. package/src/touchAround.ts +131 -0
@@ -0,0 +1,245 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ContentResolvingEventStore = void 0;
4
+ const event_runtime_1 = require("@loomwork/event-runtime");
5
+ const content_1 = require("./content");
6
+ const errors_1 = require("./errors");
7
+ const touchAround_1 = require("./touchAround");
8
+ /**
9
+ * ADR 0046 — wraps any {@link EventStore} so marked payload fields are stored in a
10
+ * {@link ContentStore} (as content-addressed refs) instead of inline in the log. Reads resolve
11
+ * refs back transparently, so the fold, resume, and every consumer above the store layer see
12
+ * plain values — the authoritative log holds only references + structure.
13
+ *
14
+ * Mirrors `EncryptingEventStore`: a store decorator, optional capabilities forwarded so it
15
+ * composes with fork/fencing. `copyPrefix` copies refs verbatim — because refs are
16
+ * content-addressed, a fork *shares* the parent's blobs for free (natural dedup), with no
17
+ * re-wrap needed. A read of an execution whose externalized bytes were erased throws
18
+ * {@link ContentMissingError} — replay fails loudly, never silently diverges (G1).
19
+ *
20
+ * **Composing with `@loomwork/plugin-audit-chain`:** wrap this store INSIDE the hash chain —
21
+ * `hashChainedEventStore(new ContentResolvingEventStore(...))`. Chained the other way round
22
+ * the chain hashes only the ref string, so a rewritten blob verifies clean. See
23
+ * `hashChainedEventStore`'s note and `contentOrdering.test.ts`.
24
+ *
25
+ * **Claim-check envelopes are left alone.** An ADR 0054 envelope (`__loomClaimCheck`) is read
26
+ * back verbatim — its `ref` is resolved only by the claim-check plugin, it may be reclaimed on a
27
+ * TTL (ADR 0055 D4), and its bytes need not be JSON. A bare ref beside it still resolves.
28
+ *
29
+ * **Reclamation:** `deleteExecution`/`truncatePrefix` remove the log's events but do NOT
30
+ * delete the referenced blobs — forks share content, so a blob outlives any one log. Blobs are
31
+ * reclaimed by the separate mark-and-sweep `sweepUnpinnedContent` (ADR 0055, which closed the
32
+ * ADR 0046 "refcounted blob GC" follow-up); erase one explicitly via `contentStore.delete(ref)`.
33
+ */
34
+ /**
35
+ * Own-key lookup for a caller-controlled event type. Plain indexing would resolve
36
+ * `"__proto__"`/`"constructor"` up the prototype chain to a truthy non-config value, which then
37
+ * flows into code expecting an array (or a function). An unrecognised event type must be a
38
+ * no-op whatever it is named.
39
+ */
40
+ function pathsFor(map, eventType) {
41
+ return Object.hasOwn(map, eventType) ? map[eventType] : undefined;
42
+ }
43
+ class ContentResolvingEventStore {
44
+ inner;
45
+ content;
46
+ externalize;
47
+ onTouchError;
48
+ appendBatch;
49
+ head;
50
+ readRange;
51
+ acquireFence;
52
+ acquireFenceIf;
53
+ appendIfUnchanged;
54
+ storeNow;
55
+ sealingCapability;
56
+ readAtRest;
57
+ copyPrefix;
58
+ deleteExecution;
59
+ deleteThrough;
60
+ truncatePrefix;
61
+ truncatePrefixKeeping;
62
+ listExecutions;
63
+ listExecutionsPage;
64
+ constructor(inner, content, options) {
65
+ this.inner = inner;
66
+ this.content = content;
67
+ // ADR 0143: a sealing store needs the owning execution on every put/get, and ADR 0046 externalization
68
+ // resolves a read BATCH across executions without carrying it. Refuse rather than seal under a guess
69
+ // (the claim-check path, which has the execution, is the one that works over a sealing store).
70
+ if (content.sealsContent === true) {
71
+ throw new TypeError("ContentResolvingEventStore: this content store seals per subject (sealsContent: true), which ADR 0046 externalization cannot drive (it does not carry the owning execution into put/get). Use a plain store here, or offload through execution.claimCheck.");
72
+ }
73
+ this.externalize = options.externalize;
74
+ this.onTouchError = options.onTouchError;
75
+ // Every optional method decided over the whole union (ADR 0074 Amendment), each
76
+ // installed only when the inner store has it.
77
+ const plan = {
78
+ // Externalize every envelope concurrently, then ONE round-trip. Puts are
79
+ // content-addressed and idempotent, so a blob orphaned by a failed batch is exactly
80
+ // what a failed `append` orphans today (and the sweep reclaims it). `Promise.all`
81
+ // keeps array order, the SPI's per-execution order.
82
+ appendBatch: async (envelopes) => this.touching(envelopes.map((e) => e.payload), { operation: "appendBatch", executionId: envelopes[0]?.executionId }, async () => inner.appendBatch(await Promise.all(envelopes.map((e) => this.externalizeEnvelope(e))))),
83
+ // A tip is a sequence number — no payload to resolve, so it forwards unchanged.
84
+ head: "forward",
85
+ readRange: (executionId, from, to) => inner.readRange(executionId, from, to).then((evs) => this.resolveAll(executionId, evs)),
86
+ acquireFence: "forward",
87
+ acquireFenceIf: "forward",
88
+ // The blob is written BEFORE the store can say no, and a content-addressed blob may be
89
+ // shared with other events (so deleting it on a refusal could break them). So the
90
+ // externalisation happens only after a lock-free observation says the condition still
91
+ // holds: a late write that already lost (the common case) stores no blob at all. What is
92
+ // left is the window between the observation and the write, whose orphan is reclaimed by
93
+ // `sweepUnpinnedContent` exactly like a plain `append` that failed after its `put`.
94
+ appendIfUnchanged: async (executionId, event, expect) => {
95
+ if (event === null)
96
+ return inner.appendIfUnchanged(executionId, null, expect);
97
+ const paths = pathsFor(this.externalize, event.eventType);
98
+ if (paths !== undefined && paths.length > 0 && expect !== undefined) {
99
+ const now = await inner.appendIfUnchanged(executionId, null);
100
+ if (!now.appended &&
101
+ (now.reason === "gone" ||
102
+ now.head !== expect.head ||
103
+ now.fence !== expect.fence ||
104
+ now.anchor !== expect.anchor ||
105
+ event.sequence !== expect.head + 1)) {
106
+ return now.reason === "gone"
107
+ ? now
108
+ : {
109
+ appended: false,
110
+ reason: "moved",
111
+ head: now.head,
112
+ fence: now.fence,
113
+ anchor: now.anchor,
114
+ };
115
+ }
116
+ }
117
+ return this.touching([event.payload], { operation: "appendIfUnchanged", executionId }, async () => inner.appendIfUnchanged(executionId, await this.externalizeEnvelope(event), expect));
118
+ },
119
+ storeNow: "forward", // a clock reading: no envelope to externalise
120
+ sealingCapability: "forward", // externalising refs seals nothing
121
+ readAtRest: "forward",
122
+ // Refs are content-addressed: copying them verbatim makes the fork share the parent's
123
+ // blobs (dedup for free) — no decrypt/re-wrap step (contrast the crypto plugin's keys).
124
+ // But a verbatim copy is a citation with NO `put`, invisible to the orphan sweep's
125
+ // conditional delete, so over a store with SPI 1.4.0 `touch` the copied range's refs are
126
+ // touched around the copy (ADR 0055 D6 amendment 2). The range is read raw (unresolved).
127
+ copyPrefix: content.touch === undefined
128
+ ? "forward"
129
+ : async (fromExecutionId, toExecutionId, throughSequence, metadata) => {
130
+ const copied = await (0, event_runtime_1.readRangeOf)(inner, fromExecutionId, 0, throughSequence);
131
+ return this.touching(copied.map((e) => e.payload), { operation: "copyPrefix", executionId: toExecutionId }, () => inner.copyPrefix(fromExecutionId, toExecutionId, throughSequence, metadata));
132
+ },
133
+ // Forwarded so a content-wrapped store keeps its retention/compaction capabilities.
134
+ // These reclaim EVENTS only; blob reclamation is separate (see the class note).
135
+ deleteExecution: "forward",
136
+ deleteThrough: "forward",
137
+ truncatePrefix: "forward",
138
+ truncatePrefixKeeping: "forward",
139
+ // Enumeration is over executionIds only — no payload, so no resolution needed.
140
+ listExecutions: "forward",
141
+ listExecutionsPage: "forward",
142
+ };
143
+ (0, event_runtime_1.installOptional)(this, inner, plan);
144
+ }
145
+ async externalizeEnvelope(envelope) {
146
+ const paths = pathsFor(this.externalize, envelope.eventType);
147
+ if (paths === undefined || paths.length === 0)
148
+ return envelope; // unmarked → verbatim (G2)
149
+ const payload = await (0, content_1.mapPathsAsync)(envelope.payload, paths, (v) => this.externalizeValue(v));
150
+ return { ...envelope, payload };
151
+ }
152
+ async append(envelope) {
153
+ return this.touching([envelope.payload], { operation: "append", executionId: envelope.executionId }, async () => this.inner.append(await this.externalizeEnvelope(envelope)));
154
+ }
155
+ /**
156
+ * SPI 1.4.0 (ADR 0055 D6 amendment 2) — a ref that reaches a log WITHOUT a `put` (one an
157
+ * incoming payload already carries: a recalled memory value, a `scriptRef`, a ref handed
158
+ * between steps) is a citation the orphan sweep's conditional delete cannot see. So, when the
159
+ * content store can `touch`, the payloads' cited addresses are touched BEFORE the write (a
160
+ * failure aborts it, nothing landed) and again AFTER it (best-effort and reported through
161
+ * `onTouchError`: the write is durable, so its outcome must not be turned into a failure).
162
+ * The guarantee is D5's: the BEFORE touch protects a write whose touch-to-commit window is
163
+ * shorter than the sweep's margin (`touchClock() - cutoff`, which an erasing orphan pass
164
+ * requires to be at least 60 s); the AFTER touch only extends that for a slower write. See
165
+ * {@link touchAround}. Refs externalized by THIS write are not in the INPUT payloads (their
166
+ * `put` is the touch). Cost: with no `touch` on the store, nothing (the closure is the
167
+ * write); otherwise one iterative cycle-safe walk of the input payload (no recursion; the same
168
+ * walker the sweep's mark phase uses), and a round trip only when a ref was found.
169
+ */
170
+ touching(payloads, ctx, write) {
171
+ return (0, touchAround_1.touchAround)(this.content, payloads, ctx, this.onTouchError, write);
172
+ }
173
+ async externalizeValue(value) {
174
+ const ref = await this.content.put((0, content_1.encodeValue)(value));
175
+ return { ...ref, codec: "json" };
176
+ }
177
+ async read(executionId) {
178
+ return this.resolveAll(executionId, await this.inner.read(executionId));
179
+ }
180
+ async readFrom(executionId, fromSequence) {
181
+ return this.resolveAll(executionId, await this.inner.readFrom(executionId, fromSequence));
182
+ }
183
+ // ADR 0062 — batch-scoped two-phase resolution. Phase 1 collects DISTINCT refs
184
+ // across the WHOLE read batch (one walk, dedupe by content address); the fetch
185
+ // is ONE `getMany` round-trip when the store supports it (coalesced parallel
186
+ // `get`s otherwise — still strictly better than the old per-ref sequential
187
+ // awaits); phase 2 is a pure synchronous substitute pass. O(payload nodes) +
188
+ // one round-trip replaces O(refs) serialized round-trips. Fast path: no event
189
+ // carries a ref → verbatim, no allocation.
190
+ async resolveAll(executionId, events) {
191
+ const distinct = new Map();
192
+ const refBearing = events.map((e) => (0, content_1.collectContentRefs)(e.payload, distinct));
193
+ if (distinct.size === 0)
194
+ return events;
195
+ const bytesByAddress = await this.fetchBytes(distinct, executionId);
196
+ return events.map((e, i) => refBearing[i] ? { ...e, payload: (0, content_1.substituteContentRefs)(e.payload, bytesByAddress) } : e);
197
+ }
198
+ /** Fetch all distinct refs; loudness contract unchanged (missing ⇒ ContentMissingError, G1). */
199
+ async fetchBytes(distinct, executionId) {
200
+ // Single pass over the map (audit nit): addresses and refs stay index-aligned.
201
+ const addresses = new Array(distinct.size);
202
+ const refs = new Array(distinct.size);
203
+ let n = 0;
204
+ for (const [address, ref] of distinct) {
205
+ addresses[n] = address;
206
+ refs[n] = ref;
207
+ n++;
208
+ }
209
+ const fetched = this.content.getMany !== undefined
210
+ ? await this.content.getMany(refs)
211
+ : await Promise.all(refs.map((r) => this.content.get(r)));
212
+ const out = new Map();
213
+ for (let i = 0; i < addresses.length; i++) {
214
+ const bytes = fetched[i];
215
+ if (bytes === null || bytes === undefined) {
216
+ throw new errors_1.ContentMissingError(addresses[i], executionId);
217
+ }
218
+ out.set(addresses[i], bytes);
219
+ }
220
+ return out;
221
+ }
222
+ async subscribe(executionId, handler) {
223
+ // Live subscribers get resolved events too (best-effort: if resolution fails — e.g. a race
224
+ // with erasure — the raw ref-bearing event is delivered rather than dropping it).
225
+ // Audit PERF-A1 — the ref-free common case (heartbeats, STEP_STARTED, decisions)
226
+ // pays the zero-allocation `hasContentRef` walk only; the Map is allocated
227
+ // exclusively for ref-bearing events.
228
+ return this.inner.subscribe(executionId, (event) => {
229
+ if (!(0, content_1.hasContentRef)(event.payload)) {
230
+ handler(event);
231
+ return;
232
+ }
233
+ const distinct = new Map();
234
+ (0, content_1.collectContentRefs)(event.payload, distinct);
235
+ void this.fetchBytes(distinct, executionId)
236
+ .then((bytes) => handler({ ...event, payload: (0, content_1.substituteContentRefs)(event.payload, bytes) }))
237
+ .catch(() => handler(event));
238
+ });
239
+ }
240
+ async close() {
241
+ return this.inner.close();
242
+ }
243
+ }
244
+ exports.ContentResolvingEventStore = ContentResolvingEventStore;
245
+ //# sourceMappingURL=ContentResolvingEventStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ContentResolvingEventStore.js","sourceRoot":"","sources":["../src/ContentResolvingEventStore.ts"],"names":[],"mappings":";;;AACA,2DAMiC;AAEjC,uCAOmB;AACnB,qCAA+C;AAC/C,+CAAiE;AAmBjE;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH;;;;;GAKG;AACH,SAAS,QAAQ,CACf,GAAsC,EACtC,SAAiB;IAEjB,OAAO,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACpE,CAAC;AAED,MAAa,0BAA0B;IAqBlB;IACA;IArBF,WAAW,CAAoC;IAC/C,YAAY,CAA6B;IACjD,WAAW,CAA0C;IACrD,IAAI,CAAmC;IACvC,SAAS,CAAwC;IACjD,YAAY,CAA2C;IACvD,cAAc,CAA6C;IAC3D,iBAAiB,CAAgD;IACjE,QAAQ,CAAuC;IAC/C,iBAAiB,CAAgD;IACjE,UAAU,CAAyC;IACnD,UAAU,CAAyC;IACnD,eAAe,CAA8C;IAC7D,aAAa,CAA4C;IACzD,cAAc,CAA6C;IAC3D,qBAAqB,CAAoD;IACzE,cAAc,CAA6C;IAC3D,kBAAkB,CAAiD;IAE5E,YACmB,KAAiB,EACjB,OAAqB,EACtC,OAA0C;QAFzB,UAAK,GAAL,KAAK,CAAY;QACjB,YAAO,GAAP,OAAO,CAAc;QAGtC,sGAAsG;QACtG,qGAAqG;QACrG,+FAA+F;QAC/F,IAAI,OAAO,CAAC,YAAY,KAAK,IAAI,EAAE,CAAC;YAClC,MAAM,IAAI,SAAS,CACjB,4PAA4P,CAC7P,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;QACvC,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;QACzC,gFAAgF;QAChF,8CAA8C;QAC9C,MAAM,IAAI,GAAG;YACX,yEAAyE;YACzE,oFAAoF;YACpF,kFAAkF;YAClF,oDAAoD;YACpD,WAAW,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,CAC/B,IAAI,CAAC,QAAQ,CACX,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAC/B,EAAE,SAAS,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,EACpE,KAAK,IAAI,EAAE,CACT,KAAK,CAAC,WAAY,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAC3F;YACH,gFAAgF;YAChF,IAAI,EAAE,SAAS;YACf,SAAS,EAAE,CAAC,WAAW,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,CACnC,KAAK,CAAC,SAAU,CAAC,WAAW,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;YAC1F,YAAY,EAAE,SAAS;YACvB,cAAc,EAAE,SAAS;YACzB,uFAAuF;YACvF,kFAAkF;YAClF,sFAAsF;YACtF,yFAAyF;YACzF,yFAAyF;YACzF,oFAAoF;YACpF,iBAAiB,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;gBACtD,IAAI,KAAK,KAAK,IAAI;oBAAE,OAAO,KAAK,CAAC,iBAAkB,CAAC,WAAW,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;gBAC/E,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;gBAC1D,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;oBACpE,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,iBAAkB,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;oBAC9D,IACE,CAAC,GAAG,CAAC,QAAQ;wBACb,CAAC,GAAG,CAAC,MAAM,KAAK,MAAM;4BACpB,GAAG,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI;4BACxB,GAAG,CAAC,KAAK,KAAK,MAAM,CAAC,KAAK;4BAC1B,GAAG,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM;4BAC5B,KAAK,CAAC,QAAQ,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC,EACrC,CAAC;wBACD,OAAO,GAAG,CAAC,MAAM,KAAK,MAAM;4BAC1B,CAAC,CAAC,GAAG;4BACL,CAAC,CAAC;gCACE,QAAQ,EAAE,KAAK;gCACf,MAAM,EAAE,OAAO;gCACf,IAAI,EAAE,GAAG,CAAC,IAAI;gCACd,KAAK,EAAE,GAAG,CAAC,KAAK;gCAChB,MAAM,EAAE,GAAG,CAAC,MAAM;6BACnB,CAAC;oBACR,CAAC;gBACH,CAAC;gBACD,OAAO,IAAI,CAAC,QAAQ,CAClB,CAAC,KAAK,CAAC,OAAO,CAAC,EACf,EAAE,SAAS,EAAE,mBAAmB,EAAE,WAAW,EAAE,EAC/C,KAAK,IAAI,EAAE,CACT,KAAK,CAAC,iBAAkB,CAAC,WAAW,EAAE,MAAM,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,CACvF,CAAC;YACJ,CAAC;YACD,QAAQ,EAAE,SAAS,EAAE,8CAA8C;YACnE,iBAAiB,EAAE,SAAS,EAAE,mCAAmC;YACjE,UAAU,EAAE,SAAS;YACrB,sFAAsF;YACtF,wFAAwF;YACxF,mFAAmF;YACnF,yFAAyF;YACzF,yFAAyF;YACzF,UAAU,EACR,OAAO,CAAC,KAAK,KAAK,SAAS;gBACzB,CAAC,CAAC,SAAS;gBACX,CAAC,CAAC,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,eAAe,EAAE,QAAQ,EAAE,EAAE;oBAClE,MAAM,MAAM,GAAG,MAAM,IAAA,2BAAW,EAAC,KAAK,EAAE,eAAe,EAAE,CAAC,EAAE,eAAe,CAAC,CAAC;oBAC7E,OAAO,IAAI,CAAC,QAAQ,CAClB,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAC5B,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,EACvD,GAAG,EAAE,CAAC,KAAK,CAAC,UAAW,CAAC,eAAe,EAAE,aAAa,EAAE,eAAe,EAAE,QAAQ,CAAC,CACnF,CAAC;gBACJ,CAAC;YACP,oFAAoF;YACpF,gFAAgF;YAChF,eAAe,EAAE,SAAS;YAC1B,aAAa,EAAE,SAAS;YACxB,cAAc,EAAE,SAAS;YACzB,qBAAqB,EAAE,SAAS;YAChC,+EAA+E;YAC/E,cAAc,EAAE,SAAS;YACzB,kBAAkB,EAAE,SAAS;SACgC,CAAC;QAChE,IAAA,+BAAe,EAAuC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IAC3E,CAAC;IAEO,KAAK,CAAC,mBAAmB,CAAC,QAAuB;QACvD,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;QAC7D,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,QAAQ,CAAC,CAAC,2BAA2B;QAC3F,MAAM,OAAO,GAAG,MAAM,IAAA,uBAAa,EAAC,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9F,OAAO,EAAE,GAAG,QAAQ,EAAE,OAAO,EAAE,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,QAAuB;QAClC,OAAO,IAAI,CAAC,QAAQ,CAClB,CAAC,QAAQ,CAAC,OAAO,CAAC,EAClB,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE,EAC1D,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAC,CACxE,CAAC;IACJ,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACK,QAAQ,CACd,QAA4B,EAC5B,GAAsC,EACtC,KAAuB;QAEvB,OAAO,IAAA,yBAAW,EAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,IAAI,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;IAC5E,CAAC;IAEO,KAAK,CAAC,gBAAgB,CAAC,KAAc;QAC3C,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAA,qBAAW,EAAC,KAAK,CAAC,CAAC,CAAC;QACvD,OAAO,EAAE,GAAG,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,WAAmB;QAC5B,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,EAAE,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;IAC1E,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,WAAmB,EAAE,YAAoB;QACtD,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,EAAE,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC,CAAC;IAC5F,CAAC;IAED,+EAA+E;IAC/E,+EAA+E;IAC/E,6EAA6E;IAC7E,2EAA2E;IAC3E,6EAA6E;IAC7E,8EAA8E;IAC9E,2CAA2C;IACnC,KAAK,CAAC,UAAU,CAAC,WAAmB,EAAE,MAAuB;QACnE,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAsB,CAAC;QAC/C,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAA,4BAAkB,EAAC,CAAC,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;QAC9E,IAAI,QAAQ,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO,MAAM,CAAC;QACvC,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QACpE,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACzB,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,IAAA,+BAAqB,EAAC,CAAC,CAAC,OAAO,EAAE,cAAc,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CACxF,CAAC;IACJ,CAAC;IAED,gGAAgG;IACxF,KAAK,CAAC,UAAU,CACtB,QAAyC,EACzC,WAAmB;QAEnB,+EAA+E;QAC/E,MAAM,SAAS,GAAG,IAAI,KAAK,CAAS,QAAQ,CAAC,IAAI,CAAC,CAAC;QACnD,MAAM,IAAI,GAAG,IAAI,KAAK,CAAa,QAAQ,CAAC,IAAI,CAAC,CAAC;QAClD,IAAI,CAAC,GAAG,CAAC,CAAC;QACV,KAAK,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,QAAQ,EAAE,CAAC;YACtC,SAAS,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC;YACvB,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC;YACd,CAAC,EAAE,CAAC;QACN,CAAC;QACD,MAAM,OAAO,GACX,IAAI,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS;YAChC,CAAC,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC;YAClC,CAAC,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9D,MAAM,GAAG,GAAG,IAAI,GAAG,EAAsB,CAAC;QAC1C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC1C,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;YACzB,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBAC1C,MAAM,IAAI,4BAAmB,CAAC,SAAS,CAAC,CAAC,CAAW,EAAE,WAAW,CAAC,CAAC;YACrE,CAAC;YACD,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAW,EAAE,KAAK,CAAC,CAAC;QACzC,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,KAAK,CAAC,SAAS,CACb,WAAmB,EACnB,OAAuC;QAEvC,2FAA2F;QAC3F,kFAAkF;QAClF,iFAAiF;QACjF,2EAA2E;QAC3E,sCAAsC;QACtC,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,KAAoB,EAAE,EAAE;YAChE,IAAI,CAAC,IAAA,uBAAa,EAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;gBAClC,OAAO,CAAC,KAAK,CAAC,CAAC;gBACf,OAAO;YACT,CAAC;YACD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAsB,CAAC;YAC/C,IAAA,4BAAkB,EAAC,KAAK,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;YAC5C,KAAK,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,WAAW,CAAC;iBACxC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CACd,OAAO,CAAC,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,IAAA,+BAAqB,EAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC,CAC5E;iBACA,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;QACjC,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,KAAK;QACT,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IAC5B,CAAC;CACF;AAxPD,gEAwPC"}
@@ -0,0 +1,186 @@
1
+ import { type ContentRef } from "./content";
2
+ /**
3
+ * ContentStore SPI version (introduced retroactively by ADR 0062 — the SPI had
4
+ * shipped two revisions without a version anchor for conformance to pin).
5
+ * - 1.0.0: `put`/`get`/`delete` (ADR 0046).
6
+ * - 1.1.0: optional `putStream` (ST-1, ADR 0059).
7
+ * - 1.2.0: optional `getMany` (TS-10, ADR 0062 — batched interactive reads).
8
+ * - 1.3.0: optional `deleteIfUntouchedSince` + `listPage` + `touchClock`, implemented all
9
+ * three or none, and a per-blob touch time that every `put`/`putStream` bumps,
10
+ * monotonically (ADR 0055 D5/D6 — the race-safe, orphan-reclaiming sweep).
11
+ * - 1.4.0: optional `touch` — bump touch times WITHOUT a put, so a citation that enters a log
12
+ * without one (a fork's `copyPrefix`, a ref an `append` carries) is visible to the
13
+ * conditional delete; `reclaimOrphans` requires it (ADR 0055 D6 amendment 2).
14
+ * - 1.5.0: an optional {@link ContentAccess} argument on `put`/`putStream`/`get`/`getMany` (the execution a
15
+ * blob belongs to) and the optional `sealsContent` marker, so a store can seal each blob under its
16
+ * owner's subject key (ADR 0143). A plain store ignores the argument; nothing else changes.
17
+ */
18
+ export declare const CONTENT_STORE_SPI_VERSION = "1.5.0";
19
+ /**
20
+ * ADR 0143 - WHO a content operation is for: the execution that offloads (`put`) or reads (`get`) the blob.
21
+ * It is the same pair every event-store `subjectOf` takes, so a sealing store maps it to the erasure subject
22
+ * with the mapping the log already uses. A plain store ignores it. A store that seals
23
+ * ({@link ContentStore.sealsContent}) REFUSES a call without it (fail closed).
24
+ */
25
+ export interface ContentAccess {
26
+ readonly executionId: string;
27
+ readonly workflowId: string;
28
+ }
29
+ /** One blob as {@link ContentStore.listPage} reports it (SPI 1.3.0). */
30
+ export interface ContentPageEntry {
31
+ address: string;
32
+ /** Epoch ms of the blob's last `put`/`putStream`, by the STORE's clock. */
33
+ touchedAt: number;
34
+ }
35
+ /** Options for {@link ContentStore.listPage} (SPI 1.3.0). */
36
+ export interface ContentListPageOptions {
37
+ /** Resume after this address: the previous page's `nextCursor`. Absent = from the start. */
38
+ cursor?: string;
39
+ /**
40
+ * Page size. MUST be a positive integer (anything else rejects with a `RangeError`, never
41
+ * "no limit"); a store MAY return fewer. Default {@link CONTENT_LIST_PAGE_DEFAULT_LIMIT},
42
+ * clamped to {@link CONTENT_LIST_PAGE_MAX_LIMIT}.
43
+ */
44
+ limit?: number;
45
+ /** Only blobs whose touch time is strictly before this (epoch ms). */
46
+ touchedBefore?: number;
47
+ }
48
+ /** One page of {@link ContentStore.listPage}. `nextCursor` absent = the scan is complete. */
49
+ export interface ContentListPage {
50
+ entries: ContentPageEntry[];
51
+ nextCursor?: string;
52
+ }
53
+ export declare const CONTENT_LIST_PAGE_DEFAULT_LIMIT = 500;
54
+ export declare const CONTENT_LIST_PAGE_MAX_LIMIT = 10000;
55
+ /**
56
+ * Validate and clamp a `listPage` limit (shared by the reference stores so a 3P backend has
57
+ * one rule to copy). Rejects rather than defaulting: SQLite reads `LIMIT -1` as NO limit.
58
+ */
59
+ export declare function contentListPageLimit(limit: number | undefined): number;
60
+ export interface ContentStore {
61
+ /**
62
+ * ADR 0143 - `true` for a store that SEALS each blob under its owning subject's key (so a crypto-shred of
63
+ * the subject makes every blob it holds unreadable). `defineConfig` accepts `execution.claimCheck` with
64
+ * `security.crypto` only for such a store, and erasure treats the shred as erasing its blobs. Absent/false:
65
+ * a plain store whose bytes sit in the clear.
66
+ */
67
+ readonly sealsContent?: boolean;
68
+ /**
69
+ * OPTIONAL, for a store that {@link sealsContent} (SPI 1.5.0, ADR 0143 D5) - the OWNERSHIP PROOF: does the
70
+ * blob at `ref` authenticate under `access`'s subject key (decrypt and discard)? A citation in a log is not
71
+ * ownership; erasure and export ask before they delete or open a cited blob. `false` for an absent blob or
72
+ * one that does not open under this subject; REJECTS when it cannot decide (the key is destroyed or not on
73
+ * this node). A store that seals and lacks it makes erasure REFUSE a subject that cites blobs.
74
+ */
75
+ ownedBy?(ref: ContentRef, access: ContentAccess): Promise<boolean>;
76
+ /**
77
+ * Store bytes, returning their content-addressed ref. Idempotent by hash (stored once).
78
+ * `access` (SPI 1.5.0, ADR 0143) names the owning execution; a plain store ignores it.
79
+ */
80
+ put(bytes: Uint8Array, access?: ContentAccess): Promise<ContentRef>;
81
+ /**
82
+ * OPTIONAL (ST-1, ADR 0059) — store a byte STREAM without materializing it in
83
+ * the caller's memory. Contract: the returned ref MUST be the content address
84
+ * of the full streamed sequence, idempotent by that address, exactly as if
85
+ * `put(concat(chunks))` had been called. HOW the backend achieves it is its
86
+ * business — the reference approach hashes while spooling; an object-store
87
+ * backend that cannot address before completion may hash-while-upload and
88
+ * verify (or re-key) on finalize (resolves requirements §6.3). Node
89
+ * `Readable` streams satisfy `AsyncIterable<Uint8Array>` directly.
90
+ */
91
+ putStream?(stream: AsyncIterable<Uint8Array>, access?: ContentAccess): Promise<ContentRef>;
92
+ /** Resolve a ref to its bytes, or `null` if erased/absent — MUST NOT throw on absence. */
93
+ get(ref: ContentRef, access?: ContentAccess): Promise<Uint8Array | null>;
94
+ /**
95
+ * OPTIONAL (TS-10, ADR 0062) — batched get for interactive read paths (e.g.
96
+ * frozen-snapshot serving: many refs, one human waiting). Result is
97
+ * positionally aligned with `refs`; `null` per absent entry; MUST NOT throw
98
+ * on absence. Duplicate refs are legal and resolve identically. Semantics are
99
+ * exactly element-wise `get` — only the round-trip count may differ.
100
+ */
101
+ getMany?(refs: readonly ContentRef[], access?: ContentAccess): Promise<(Uint8Array | null)[]>;
102
+ /** Physically erase the bytes. Idempotent; referential integrity is the caller's concern. */
103
+ delete(ref: ContentRef): Promise<void>;
104
+ /**
105
+ * OPTIONAL (SPI 1.3.0, ADR 0055 D5) — erase the blob only if its touch time is strictly
106
+ * before `cutoffMs`, as ONE atomic operation (a single conditional `DELETE`, never a read
107
+ * followed by a delete). Resolves `true` iff it deleted; `false` for a touched or absent
108
+ * blob, never a throw.
109
+ *
110
+ * A store that implements this MUST bump the blob's touch time on EVERY `put`/`putStream`,
111
+ * including a put of bytes it already holds (`ON CONFLICT … DO UPDATE`, not
112
+ * `INSERT OR IGNORE`). A dedup'd put is a re-citation the log has not recorded yet; the
113
+ * touch is how the sweep learns of it.
114
+ */
115
+ deleteIfUntouchedSince?(ref: ContentRef, cutoffMs: number): Promise<boolean>;
116
+ /**
117
+ * OPTIONAL (SPI 1.3.0, ADR 0055 D6) — enumerate blobs, keyset-paged ascending by address,
118
+ * so a scan uses bounded memory and deleting entries mid-scan never skips a survivor.
119
+ * Every blob present for the whole scan is returned exactly once. Implement together
120
+ * with {@link deleteIfUntouchedSince}: the orphan pass requires both.
121
+ */
122
+ listPage?(opts: ContentListPageOptions): Promise<ContentListPage>;
123
+ /**
124
+ * OPTIONAL (SPI 1.3.0, ADR 0055 D5) — the current time on the clock this store stamps touch
125
+ * times with (a database server's `now()`, the process clock of an embedded store). The
126
+ * sweep derives its guard cutoff from it, so the guard never compares a log-time instant
127
+ * with a store-time touch.
128
+ *
129
+ * The three 1.3.0 methods come as a set: a store implements all of `deleteIfUntouchedSince`,
130
+ * `listPage` and `touchClock`, or none of them (the conformance kit fails a partial set).
131
+ * The touch time never moves backwards: a put under a clock that stepped back keeps the
132
+ * later time. `0` means UNKNOWN (a pre-1.3.0 writer's column default) and is never
133
+ * orphan-eligible.
134
+ */
135
+ touchClock?(): Promise<number>;
136
+ /**
137
+ * OPTIONAL (SPI 1.4.0, ADR 0055 D6 amendment 2) — bump the touch time of every listed blob
138
+ * to the store clock, as one batch, WITHOUT a `put`. It is how a citation that enters a log
139
+ * with no `put` (a fork copying refs verbatim, an `append` carrying a ref already in its
140
+ * payload) tells the conditional delete "this was re-cited now".
141
+ *
142
+ * Same semantics as a `put` of bytes the store holds: the time never moves backwards (max).
143
+ * An address the store does not hold is a NO-OP, never a throw and never an insert; the
144
+ * promise resolves with those missing addresses (empty when all were present) so a caller
145
+ * can notice a dangling ref early. Duplicates are legal. An empty list does nothing.
146
+ *
147
+ * Implement it with the 1.3.0 set; `sweepUnpinnedContent({ reclaimOrphans })` refuses to
148
+ * erase through a store that lacks it (the race it closes would otherwise be open).
149
+ */
150
+ touch?(addresses: readonly string[]): Promise<string[]>;
151
+ }
152
+ /** Options for {@link InMemoryContentStore}. */
153
+ export interface InMemoryContentStoreOptions {
154
+ /** Clock for touch times (epoch ms). Default `Date.now`. */
155
+ now?: () => number;
156
+ }
157
+ /**
158
+ * Reference in-memory {@link ContentStore} (tests / ephemeral apps). Content-addressed, so a
159
+ * repeated `put` of identical bytes stores one blob — dedup is visible via {@link size} — and
160
+ * bumps that blob's touch time (SPI 1.3.0).
161
+ */
162
+ export declare class InMemoryContentStore implements ContentStore {
163
+ private readonly blobs;
164
+ /**
165
+ * Addresses in ascending order for `listPage`'s keyset scan, rebuilt lazily: only a NEW
166
+ * address invalidates it (O(1) per put), a delete leaves a stale entry the scan skips, so a
167
+ * sweep deleting while it pages sorts once — O(n log n) per scan, O(log n + limit) per page.
168
+ */
169
+ private sorted;
170
+ private sortedStale;
171
+ private readonly now;
172
+ constructor(options?: InMemoryContentStoreOptions);
173
+ put(bytes: Uint8Array): Promise<ContentRef>;
174
+ putStream(stream: AsyncIterable<Uint8Array>): Promise<ContentRef>;
175
+ get(ref: ContentRef): Promise<Uint8Array | null>;
176
+ getMany(refs: readonly ContentRef[]): Promise<(Uint8Array | null)[]>;
177
+ delete(ref: ContentRef): Promise<void>;
178
+ deleteIfUntouchedSince(ref: ContentRef, cutoffMs: number): Promise<boolean>;
179
+ touchClock(): Promise<number>;
180
+ touch(addresses: readonly string[]): Promise<string[]>;
181
+ listPage(opts?: ContentListPageOptions): Promise<ContentListPage>;
182
+ /** Number of distinct blobs stored — makes content-addressed dedup observable in tests. */
183
+ get size(): number;
184
+ private store;
185
+ }
186
+ //# sourceMappingURL=ContentStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ContentStore.d.ts","sourceRoot":"","sources":["../src/ContentStore.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InMemoryContentStore = exports.CONTENT_LIST_PAGE_MAX_LIMIT = exports.CONTENT_LIST_PAGE_DEFAULT_LIMIT = exports.CONTENT_STORE_SPI_VERSION = void 0;
4
+ exports.contentListPageLimit = contentListPageLimit;
5
+ const node_crypto_1 = require("node:crypto");
6
+ const content_1 = require("./content");
7
+ // ── ADR 0046 — the ContentStore SPI ───────────────────────────────────────────
8
+ //
9
+ // A key→blob store for externalized payload bytes, addressed by content hash. Back it with
10
+ // object storage / a filesystem / an encrypted blob store in production (as separate
11
+ // `@loomwork/plugin-content-*` backends); the reference implementation here is in-memory.
12
+ /**
13
+ * ContentStore SPI version (introduced retroactively by ADR 0062 — the SPI had
14
+ * shipped two revisions without a version anchor for conformance to pin).
15
+ * - 1.0.0: `put`/`get`/`delete` (ADR 0046).
16
+ * - 1.1.0: optional `putStream` (ST-1, ADR 0059).
17
+ * - 1.2.0: optional `getMany` (TS-10, ADR 0062 — batched interactive reads).
18
+ * - 1.3.0: optional `deleteIfUntouchedSince` + `listPage` + `touchClock`, implemented all
19
+ * three or none, and a per-blob touch time that every `put`/`putStream` bumps,
20
+ * monotonically (ADR 0055 D5/D6 — the race-safe, orphan-reclaiming sweep).
21
+ * - 1.4.0: optional `touch` — bump touch times WITHOUT a put, so a citation that enters a log
22
+ * without one (a fork's `copyPrefix`, a ref an `append` carries) is visible to the
23
+ * conditional delete; `reclaimOrphans` requires it (ADR 0055 D6 amendment 2).
24
+ * - 1.5.0: an optional {@link ContentAccess} argument on `put`/`putStream`/`get`/`getMany` (the execution a
25
+ * blob belongs to) and the optional `sealsContent` marker, so a store can seal each blob under its
26
+ * owner's subject key (ADR 0143). A plain store ignores the argument; nothing else changes.
27
+ */
28
+ exports.CONTENT_STORE_SPI_VERSION = "1.5.0";
29
+ exports.CONTENT_LIST_PAGE_DEFAULT_LIMIT = 500;
30
+ exports.CONTENT_LIST_PAGE_MAX_LIMIT = 10_000;
31
+ /**
32
+ * Validate and clamp a `listPage` limit (shared by the reference stores so a 3P backend has
33
+ * one rule to copy). Rejects rather than defaulting: SQLite reads `LIMIT -1` as NO limit.
34
+ */
35
+ function contentListPageLimit(limit) {
36
+ if (limit === undefined)
37
+ return exports.CONTENT_LIST_PAGE_DEFAULT_LIMIT;
38
+ if (!Number.isInteger(limit) || limit < 1) {
39
+ throw new RangeError(`listPage: limit must be a positive integer, got ${limit}`);
40
+ }
41
+ return Math.min(limit, exports.CONTENT_LIST_PAGE_MAX_LIMIT);
42
+ }
43
+ /**
44
+ * Reference in-memory {@link ContentStore} (tests / ephemeral apps). Content-addressed, so a
45
+ * repeated `put` of identical bytes stores one blob — dedup is visible via {@link size} — and
46
+ * bumps that blob's touch time (SPI 1.3.0).
47
+ */
48
+ class InMemoryContentStore {
49
+ blobs = new Map();
50
+ /**
51
+ * Addresses in ascending order for `listPage`'s keyset scan, rebuilt lazily: only a NEW
52
+ * address invalidates it (O(1) per put), a delete leaves a stale entry the scan skips, so a
53
+ * sweep deleting while it pages sorts once — O(n log n) per scan, O(log n + limit) per page.
54
+ */
55
+ sorted = [];
56
+ sortedStale = false;
57
+ now;
58
+ constructor(options = {}) {
59
+ this.now = options.now ?? Date.now;
60
+ }
61
+ async put(bytes) {
62
+ const address = (0, content_1.contentAddress)(bytes);
63
+ this.store(address, bytes);
64
+ return { __loomContent: address, size: bytes.length };
65
+ }
66
+ async putStream(stream) {
67
+ // Hash incrementally while spooling (an in-memory store holds the bytes
68
+ // anyway — durable backends spool to their medium instead of an array).
69
+ const hash = (0, node_crypto_1.createHash)("sha256");
70
+ const chunks = [];
71
+ let size = 0;
72
+ for await (const chunk of stream) {
73
+ hash.update(chunk);
74
+ chunks.push(chunk);
75
+ size += chunk.byteLength;
76
+ }
77
+ const address = `sha256:${hash.digest("hex")}`;
78
+ const existing = this.blobs.get(address);
79
+ if (existing !== undefined) {
80
+ existing.touchedAt = Math.max(existing.touchedAt, this.now()); // monotonic
81
+ }
82
+ else {
83
+ const bytes = new Uint8Array(size);
84
+ let offset = 0;
85
+ for (const chunk of chunks) {
86
+ bytes.set(chunk, offset);
87
+ offset += chunk.byteLength;
88
+ }
89
+ this.store(address, bytes);
90
+ }
91
+ return { __loomContent: address, size };
92
+ }
93
+ async get(ref) {
94
+ return this.blobs.get(ref.__loomContent)?.bytes ?? null;
95
+ }
96
+ async getMany(refs) {
97
+ return refs.map((ref) => this.blobs.get(ref.__loomContent)?.bytes ?? null);
98
+ }
99
+ async delete(ref) {
100
+ this.blobs.delete(ref.__loomContent);
101
+ }
102
+ async deleteIfUntouchedSince(ref, cutoffMs) {
103
+ // No await between the check and the delete: atomic on the event loop.
104
+ const blob = this.blobs.get(ref.__loomContent);
105
+ if (blob === undefined || !(blob.touchedAt < cutoffMs))
106
+ return false;
107
+ return this.blobs.delete(ref.__loomContent);
108
+ }
109
+ async touchClock() {
110
+ return this.now();
111
+ }
112
+ async touch(addresses) {
113
+ const touchedAt = this.now();
114
+ const missing = [];
115
+ for (const address of addresses) {
116
+ const blob = this.blobs.get(address);
117
+ if (blob === undefined)
118
+ missing.push(address);
119
+ else
120
+ blob.touchedAt = Math.max(blob.touchedAt, touchedAt); // monotonic, like put
121
+ }
122
+ return missing;
123
+ }
124
+ async listPage(opts = {}) {
125
+ const limit = contentListPageLimit(opts.limit);
126
+ if (this.sortedStale || this.sorted.length > 2 * this.blobs.size + 64) {
127
+ this.sorted = [...this.blobs.keys()].sort();
128
+ this.sortedStale = false;
129
+ }
130
+ let i = opts.cursor === undefined ? 0 : upperBound(this.sorted, opts.cursor);
131
+ const entries = [];
132
+ let last;
133
+ for (; i < this.sorted.length && entries.length < limit; i++) {
134
+ const address = this.sorted[i];
135
+ last = address;
136
+ const blob = this.blobs.get(address);
137
+ if (blob === undefined)
138
+ continue; // deleted since the last rebuild
139
+ if (opts.touchedBefore !== undefined && !(blob.touchedAt < opts.touchedBefore))
140
+ continue;
141
+ entries.push({ address, touchedAt: blob.touchedAt });
142
+ }
143
+ return i < this.sorted.length && last !== undefined
144
+ ? { entries, nextCursor: last }
145
+ : { entries };
146
+ }
147
+ /** Number of distinct blobs stored — makes content-addressed dedup observable in tests. */
148
+ get size() {
149
+ return this.blobs.size;
150
+ }
151
+ store(address, bytes) {
152
+ const touchedAt = this.now();
153
+ const existing = this.blobs.get(address);
154
+ if (existing !== undefined) {
155
+ // A re-citation: bump (never backwards), keep the stored bytes.
156
+ existing.touchedAt = Math.max(existing.touchedAt, touchedAt);
157
+ return;
158
+ }
159
+ this.blobs.set(address, { bytes, touchedAt });
160
+ this.sortedStale = true;
161
+ }
162
+ }
163
+ exports.InMemoryContentStore = InMemoryContentStore;
164
+ /** First index whose element is strictly greater than `key` (binary search). */
165
+ function upperBound(sorted, key) {
166
+ let lo = 0;
167
+ let hi = sorted.length;
168
+ while (lo < hi) {
169
+ const mid = (lo + hi) >>> 1;
170
+ if (sorted[mid] <= key)
171
+ lo = mid + 1;
172
+ else
173
+ hi = mid;
174
+ }
175
+ return lo;
176
+ }
177
+ //# sourceMappingURL=ContentStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ContentStore.js","sourceRoot":"","sources":["../src/ContentStore.ts"],"names":[],"mappings":"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"}