@untangle-code/storage-local 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 (65) hide show
  1. package/LICENSE +661 -0
  2. package/NOTICE +90 -0
  3. package/README.md +226 -2
  4. package/THIRD_PARTY_LICENSES.md +24 -0
  5. package/dist/abort.d.ts +18 -0
  6. package/dist/abort.d.ts.map +1 -0
  7. package/dist/abort.js +14 -0
  8. package/dist/abort.js.map +1 -0
  9. package/dist/analysis-cache.d.ts +81 -0
  10. package/dist/analysis-cache.d.ts.map +1 -0
  11. package/dist/analysis-cache.js +322 -0
  12. package/dist/analysis-cache.js.map +1 -0
  13. package/dist/checkpoint-store.d.ts +22 -0
  14. package/dist/checkpoint-store.d.ts.map +1 -0
  15. package/dist/checkpoint-store.js +123 -0
  16. package/dist/checkpoint-store.js.map +1 -0
  17. package/dist/embedding-cache.d.ts +38 -0
  18. package/dist/embedding-cache.d.ts.map +1 -0
  19. package/dist/embedding-cache.js +229 -0
  20. package/dist/embedding-cache.js.map +1 -0
  21. package/dist/fs-errors.d.ts +4 -0
  22. package/dist/fs-errors.d.ts.map +1 -0
  23. package/dist/fs-errors.js +14 -0
  24. package/dist/fs-errors.js.map +1 -0
  25. package/dist/index.d.ts +15 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +23 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/layout.d.ts +50 -0
  30. package/dist/layout.d.ts.map +1 -0
  31. package/dist/layout.js +99 -0
  32. package/dist/layout.js.map +1 -0
  33. package/dist/local-file-system.d.ts +37 -0
  34. package/dist/local-file-system.d.ts.map +1 -0
  35. package/dist/local-file-system.js +193 -0
  36. package/dist/local-file-system.js.map +1 -0
  37. package/dist/lock-provider.d.ts +59 -0
  38. package/dist/lock-provider.d.ts.map +1 -0
  39. package/dist/lock-provider.js +318 -0
  40. package/dist/lock-provider.js.map +1 -0
  41. package/dist/real-time.d.ts +16 -0
  42. package/dist/real-time.d.ts.map +1 -0
  43. package/dist/real-time.js +19 -0
  44. package/dist/real-time.js.map +1 -0
  45. package/dist/search-journey-store.d.ts +34 -0
  46. package/dist/search-journey-store.d.ts.map +1 -0
  47. package/dist/search-journey-store.js +530 -0
  48. package/dist/search-journey-store.js.map +1 -0
  49. package/dist/storage.d.ts +42 -0
  50. package/dist/storage.d.ts.map +1 -0
  51. package/dist/storage.js +34 -0
  52. package/dist/storage.js.map +1 -0
  53. package/package.json +60 -3
  54. package/src/abort.ts +26 -0
  55. package/src/analysis-cache.ts +404 -0
  56. package/src/checkpoint-store.ts +155 -0
  57. package/src/embedding-cache.ts +282 -0
  58. package/src/fs-errors.ts +19 -0
  59. package/src/index.ts +30 -0
  60. package/src/layout.ts +149 -0
  61. package/src/local-file-system.ts +232 -0
  62. package/src/lock-provider.ts +381 -0
  63. package/src/real-time.ts +18 -0
  64. package/src/search-journey-store.ts +638 -0
  65. package/src/storage.ts +98 -0
@@ -0,0 +1,322 @@
1
+ /**
2
+ * Inhaltsadressierte Caches fuer Parse, Chunk und Komplexitaet (WP-100,
3
+ * docs/INDEX-SHARING.md §5) — unter `.untangle/cache/analysis/`.
4
+ *
5
+ * WARUM ES SIE GIBT: Parsen, Chunken und Messen sind reine Funktionen des
6
+ * Dateiinhalts. Ohne Cache rechnet jeder Lauf sie neu, obwohl sich zwischen
7
+ * zwei Laeufen (und zwischen zwei Worktrees desselben Repositoriums)
8
+ * typischerweise nur wenige Prozent der Dateien unterscheiden. Der
9
+ * Einbettungs-Cache (spec.md §9.4) ist bereits inhaltsadressiert; diese drei
10
+ * Caches schliessen die Luecke davor.
11
+ *
12
+ * DER SCHLUESSEL IST DIE GANZE SICHERHEIT. Ein Cache, der ein falsches
13
+ * Ergebnis liefert, ist schlimmer als kein Cache: das falsche Ergebnis ist
14
+ * nicht sichtbar. Der Schluesselbau steht deshalb in `@untangle-code/domain`
15
+ * (analysis-cache-key.ts) und traegt jede Eingabe, von der der gespeicherte
16
+ * Wert abhaengt.
17
+ *
18
+ * ABLAGE: ein JSON je Eintrag, zwei Hexzeichen tief geshardet
19
+ * (`<hash[0:2]>/<hash>.json`), plus ein `index.json` je Cache mit Groesse und
20
+ * letztem Zugriff — die Buchfuehrung fuer die Verdraengung nach Groesse (LRU).
21
+ *
22
+ * UNTERSCHIED ZUM EINBETTUNGS-CACHE: dessen Manifest wird bei JEDER Mutation
23
+ * geschrieben. Das waere hier falsch: drei Caches mal zwei Zugriffe je Datei
24
+ * ergaeben sechs Manifest-Schreibvorgaenge je Datei und fraessen genau die
25
+ * Zeit auf, die der Cache spart. Das Manifest wird deshalb im Speicher
26
+ * gefuehrt und alle {@link MANIFEST_FLUSH_INTERVAL} Mutationen sowie bei
27
+ * {@link LocalContentCache.flush} geschrieben. PREIS: bricht der Lauf hart ab,
28
+ * koennen geschriebene Eintraege im Manifest fehlen. Das kostet Treffer und
29
+ * laesst Dateien ungezaehlt liegen — es liefert NIE ein falsches Ergebnis,
30
+ * weil ein Wert ausschliesslich ueber das Manifest gefunden wird.
31
+ *
32
+ * Lesen degradiert kontrolliert (architecture.md §12: lokale Caches sind
33
+ * loeschbar und rekonstruierbar): fehlende Datei, kaputtes JSON, kaputtes
34
+ * Manifest — alles ist ein Fehlschlag des Nachschlagens, kein geworfener
35
+ * Fehler.
36
+ */
37
+ import * as nodePath from "node:path";
38
+ import { sha256Hex } from "@untangle-code/domain";
39
+ import { assertNotAborted } from "./abort.js";
40
+ import { isNotFoundError } from "./fs-errors.js";
41
+ // Der Schluesselbau liegt in `@untangle-code/domain`, weil ihn BEIDE Seiten
42
+ // brauchen: dieses Paket legt ab, `@untangle-code/core` schlaegt nach. Zwei
43
+ // Kopien desselben Schluessels waeren die sicherste Art, den Cache eines Tages
44
+ // an der falschen Stelle treffen zu lassen. Hier nur weitergereicht, damit ein
45
+ // Aufrufer beides aus einem Paket bekommt.
46
+ export { ANALYSIS_CACHE_KINDS, buildChunkCacheKey, buildMetricsCacheKey, buildParseCacheKey, } from "@untangle-code/domain";
47
+ /** 256 MiB je Cache — eine Untangle-Vorgabe, keine Spec-Zahl. */
48
+ export const DEFAULT_ANALYSIS_CACHE_MAX_BYTES = 256 * 1024 * 1024;
49
+ /** Nach so vielen Mutationen wird das Manifest geschrieben. */
50
+ export const MANIFEST_FLUSH_INTERVAL = 64;
51
+ const MANIFEST_FILE_NAME = "index.json";
52
+ function encode(text) {
53
+ return new TextEncoder().encode(text);
54
+ }
55
+ function signalOptions(options) {
56
+ return options?.signal ? { signal: options.signal } : undefined;
57
+ }
58
+ function totalBytesOf(manifest) {
59
+ let total = 0;
60
+ for (const entry of Object.values(manifest.entries)) {
61
+ total += entry.sizeBytes;
62
+ }
63
+ return total;
64
+ }
65
+ function pickEvictionCandidate(manifest, protectedKey) {
66
+ const keys = Object.keys(manifest.entries);
67
+ let best = null;
68
+ let bestAccess = Number.POSITIVE_INFINITY;
69
+ for (const key of keys) {
70
+ if (key === protectedKey && keys.length === 1) {
71
+ // Der eben geschriebene Eintrag ist der letzte — ihn zu verdraengen
72
+ // hiesse, `set()` stillschweigend wirkungslos zu machen.
73
+ continue;
74
+ }
75
+ const entry = manifest.entries[key];
76
+ if (entry !== undefined && entry.lastAccessEpochMs < bestAccess) {
77
+ bestAccess = entry.lastAccessEpochMs;
78
+ best = key;
79
+ }
80
+ }
81
+ return best;
82
+ }
83
+ /**
84
+ * Ein inhaltsadressierter JSON-Cache mit Groessengrenze, Verdraengung und
85
+ * Trefferquote. Der Schluessel ist eine undurchsichtige Zeichenkette; wer ihn
86
+ * baut, verantwortet seine Vollstaendigkeit (siehe Kopf dieser Datei).
87
+ */
88
+ export class LocalContentCache {
89
+ fs;
90
+ dir;
91
+ clock;
92
+ random;
93
+ maxBytes;
94
+ manifest = null;
95
+ dirty = 0;
96
+ hits = 0;
97
+ misses = 0;
98
+ evictions = 0;
99
+ constructor(fs, dir, clock, random, options) {
100
+ this.fs = fs;
101
+ this.dir = dir;
102
+ this.clock = clock;
103
+ this.random = random;
104
+ this.maxBytes = options?.maxBytes ?? DEFAULT_ANALYSIS_CACHE_MAX_BYTES;
105
+ }
106
+ manifestPath() {
107
+ return nodePath.join(this.dir, MANIFEST_FILE_NAME);
108
+ }
109
+ entryFileName(logicalKey) {
110
+ const hash = sha256Hex(logicalKey);
111
+ return nodePath.join(hash.slice(0, 2), `${hash}.json`);
112
+ }
113
+ entryPath(fileName) {
114
+ return nodePath.join(this.dir, fileName);
115
+ }
116
+ async loadManifest(options) {
117
+ if (this.manifest !== null) {
118
+ return this.manifest;
119
+ }
120
+ let text;
121
+ try {
122
+ text = await this.fs.readTextFile(this.manifestPath(), options);
123
+ }
124
+ catch (error) {
125
+ if (isNotFoundError(error)) {
126
+ this.manifest = { version: 1, entries: {} };
127
+ return this.manifest;
128
+ }
129
+ throw error;
130
+ }
131
+ try {
132
+ const parsed = JSON.parse(text);
133
+ this.manifest =
134
+ parsed.entries === undefined
135
+ ? { version: 1, entries: {} }
136
+ : { version: 1, entries: parsed.entries };
137
+ }
138
+ catch {
139
+ // Kaputtes Manifest — leerer Cache statt geworfener Fehler.
140
+ this.manifest = { version: 1, entries: {} };
141
+ }
142
+ return this.manifest;
143
+ }
144
+ async persistManifest(options) {
145
+ const manifest = this.manifest;
146
+ if (manifest === null) {
147
+ return;
148
+ }
149
+ await this.fs.mkdir(this.dir, { recursive: true, ...signalOptions(options) });
150
+ const tempPath = `${this.manifestPath()}.${this.random.uuid()}.tmp`;
151
+ await this.fs.writeFile(tempPath, encode(JSON.stringify(manifest)), options);
152
+ await this.fs.rename(tempPath, this.manifestPath(), options);
153
+ this.dirty = 0;
154
+ }
155
+ async touched(options) {
156
+ this.dirty++;
157
+ if (this.dirty >= MANIFEST_FLUSH_INTERVAL) {
158
+ await this.persistManifest(options);
159
+ }
160
+ }
161
+ /** Schreibt das Manifest, falls Mutationen ausstehen. Am Ende eines Laufs aufrufen. */
162
+ async flush(options) {
163
+ if (this.dirty > 0) {
164
+ await this.persistManifest(options);
165
+ }
166
+ }
167
+ /** Der Wert, oder `null` fuer "nicht im Cache". Wirft nicht fuer kaputte Eintraege. */
168
+ async get(logicalKey, options) {
169
+ assertNotAborted(options, "analysis_cache_get");
170
+ const manifest = await this.loadManifest(options);
171
+ const entry = manifest.entries[logicalKey];
172
+ if (entry === undefined) {
173
+ this.misses++;
174
+ return null;
175
+ }
176
+ let text;
177
+ try {
178
+ text = await this.fs.readTextFile(this.entryPath(entry.fileName), options);
179
+ }
180
+ catch (error) {
181
+ if (!isNotFoundError(error)) {
182
+ throw error;
183
+ }
184
+ delete manifest.entries[logicalKey];
185
+ await this.touched(options);
186
+ this.misses++;
187
+ return null;
188
+ }
189
+ let value;
190
+ try {
191
+ value = JSON.parse(text);
192
+ }
193
+ catch {
194
+ delete manifest.entries[logicalKey];
195
+ await this.touched(options);
196
+ this.misses++;
197
+ return null;
198
+ }
199
+ entry.lastAccessEpochMs = this.clock.nowEpochMs();
200
+ this.dirty++;
201
+ this.hits++;
202
+ return value;
203
+ }
204
+ async set(logicalKey, value, options) {
205
+ assertNotAborted(options, "analysis_cache_set");
206
+ const fileName = this.entryFileName(logicalKey);
207
+ const entryPath = this.entryPath(fileName);
208
+ const bytes = encode(JSON.stringify(value));
209
+ await this.fs.mkdir(nodePath.dirname(entryPath), {
210
+ recursive: true,
211
+ ...signalOptions(options),
212
+ });
213
+ const tempPath = `${entryPath}.${this.random.uuid()}.tmp`;
214
+ await this.fs.writeFile(tempPath, bytes, options);
215
+ await this.fs.rename(tempPath, entryPath, options);
216
+ const manifest = await this.loadManifest(options);
217
+ manifest.entries[logicalKey] = {
218
+ fileName,
219
+ sizeBytes: bytes.length,
220
+ lastAccessEpochMs: this.clock.nowEpochMs(),
221
+ };
222
+ await this.evictUntilWithinLimit(manifest, logicalKey, options);
223
+ await this.touched(options);
224
+ }
225
+ async evictUntilWithinLimit(manifest, justWrittenKey, options) {
226
+ let total = totalBytesOf(manifest);
227
+ while (total > this.maxBytes) {
228
+ const victim = pickEvictionCandidate(manifest, justWrittenKey);
229
+ if (victim === null) {
230
+ break;
231
+ }
232
+ const entry = manifest.entries[victim];
233
+ delete manifest.entries[victim];
234
+ if (entry !== undefined) {
235
+ total -= entry.sizeBytes;
236
+ this.evictions++;
237
+ try {
238
+ await this.fs.remove(this.entryPath(entry.fileName), options);
239
+ }
240
+ catch {
241
+ // Der Manifest-Eintrag ist so oder so weg; ein spaeterer `get`
242
+ // trifft nicht mehr.
243
+ }
244
+ }
245
+ }
246
+ }
247
+ async stats(options) {
248
+ assertNotAborted(options, "analysis_cache_stats");
249
+ const manifest = await this.loadManifest(options);
250
+ const lookups = this.hits + this.misses;
251
+ return {
252
+ entryCount: Object.keys(manifest.entries).length,
253
+ totalBytes: totalBytesOf(manifest),
254
+ hits: this.hits,
255
+ misses: this.misses,
256
+ hitRate: lookups === 0 ? 0 : this.hits / lookups,
257
+ evictions: this.evictions,
258
+ };
259
+ }
260
+ /** Loescht ausschliesslich das eigene Verzeichnis dieses Caches. */
261
+ async clear(options) {
262
+ assertNotAborted(options, "analysis_cache_clear");
263
+ await this.fs.remove(this.dir, { recursive: true, ...signalOptions(options) });
264
+ this.manifest = { version: 1, entries: {} };
265
+ this.dirty = 0;
266
+ }
267
+ }
268
+ /**
269
+ * Die drei Caches als ein Gegenstand — genau die Form, die
270
+ * `@untangle-code/core` als `AnalysisCacheLike` erwartet.
271
+ */
272
+ export class LocalAnalysisCache {
273
+ parse;
274
+ chunk;
275
+ metrics;
276
+ constructor(parse, chunk, metrics) {
277
+ this.parse = parse;
278
+ this.chunk = chunk;
279
+ this.metrics = metrics;
280
+ }
281
+ of(kind) {
282
+ if (kind === "parse")
283
+ return this.parse;
284
+ if (kind === "chunk")
285
+ return this.chunk;
286
+ return this.metrics;
287
+ }
288
+ async get(kind, key, options) {
289
+ return this.of(kind).get(key, options);
290
+ }
291
+ async set(kind, key, value, options) {
292
+ await this.of(kind).set(key, value, options);
293
+ }
294
+ /** Schreibt die Manifeste aller drei Caches. */
295
+ async flush(options) {
296
+ await this.parse.flush(options);
297
+ await this.chunk.flush(options);
298
+ await this.metrics.flush(options);
299
+ }
300
+ async stats(options) {
301
+ const parse = await this.parse.stats(options);
302
+ const chunk = await this.chunk.stats(options);
303
+ const metrics = await this.metrics.stats(options);
304
+ const totalHits = parse.hits + chunk.hits + metrics.hits;
305
+ const totalMisses = parse.misses + chunk.misses + metrics.misses;
306
+ const lookups = totalHits + totalMisses;
307
+ return {
308
+ parse,
309
+ chunk,
310
+ metrics,
311
+ totalHits,
312
+ totalMisses,
313
+ hitRate: lookups === 0 ? 0 : totalHits / lookups,
314
+ };
315
+ }
316
+ async clear(options) {
317
+ await this.parse.clear(options);
318
+ await this.chunk.clear(options);
319
+ await this.metrics.clear(options);
320
+ }
321
+ }
322
+ //# sourceMappingURL=analysis-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analysis-cache.js","sourceRoot":"","sources":["../src/analysis-cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { CheckpointStorePort, FileSystemPort, IndexCheckpointV1, PortCallOptions, Random } from "@untangle-code/domain";
2
+ /** Local, atomically-mirrored `CheckpointStorePort` under `jobs/`. */
3
+ export declare class LocalCheckpointStore implements CheckpointStorePort {
4
+ private readonly fs;
5
+ private readonly jobsDir;
6
+ private readonly random;
7
+ constructor(fs: FileSystemPort, jobsDir: string, random: Random);
8
+ private pathForJob;
9
+ /**
10
+ * Both components are variable-length, so a plain concatenation would be
11
+ * ambiguous (e.g. `repoId="ab"` + `refId="c"` vs. `repoId="a"` +
12
+ * `refId="bc"`) — joined with the same separator domain's own identity
13
+ * model uses for exactly this reason (`identity.ts`'s doc comment).
14
+ */
15
+ private pointerPathFor;
16
+ private writeAtomic;
17
+ save(checkpoint: IndexCheckpointV1, options?: PortCallOptions): Promise<void>;
18
+ load(jobId: string, options?: PortCallOptions): Promise<IndexCheckpointV1 | null>;
19
+ loadLatest(repoId: string, refId: string, options?: PortCallOptions): Promise<IndexCheckpointV1 | null>;
20
+ delete(jobId: string, options?: PortCallOptions): Promise<void>;
21
+ }
22
+ //# sourceMappingURL=checkpoint-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkpoint-store.d.ts","sourceRoot":"","sources":["../src/checkpoint-store.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EACV,mBAAmB,EACnB,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,MAAM,EACP,MAAM,uBAAuB,CAAC;AAoB/B,sEAAsE;AACtE,qBAAa,oBAAqB,YAAW,mBAAmB;IAE5D,OAAO,CAAC,QAAQ,CAAC,EAAE;IACnB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAFN,EAAE,EAAE,cAAc,EAClB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM;IAGjC,OAAO,CAAC,UAAU;IAIlB;;;;;OAKG;IACH,OAAO,CAAC,cAAc;YAKR,WAAW;IAYZ,IAAI,CAAC,UAAU,EAAE,iBAAiB,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAW7E,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IA0BjF,UAAU,CACrB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAsBvB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;CAI7E"}
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Local checkpoint mirror (architecture.md §12, §13.3; spec.md §11.2:
3
+ * "Nach jedem konfigurierten Dateibatch MUSS ein Checkpoint gespeichert
4
+ * werden" / "Bei Neustart MUSS der Job ab dem letzten bestätigten
5
+ * Checkpoint fortsetzbar sein").
6
+ *
7
+ * Every write is temp-file-then-rename: the checkpoint at `jobs/<jobId>.json`
8
+ * is only ever created or replaced by a single atomic `rename` onto that
9
+ * exact path, so a process crash mid-write leaves, at worst, an orphaned
10
+ * `*.tmp` file next to it — the real path itself is always either the
11
+ * previous good checkpoint or the new one, never a half-written mix of both
12
+ * (see README "Entscheidungen" for the crash-simulation test).
13
+ *
14
+ * `jobs/latest/<hash>.json` is a small secondary index — "which job is the
15
+ * latest checkpoint for this repo+ref" — so `loadLatest` does not have to
16
+ * list and parse every checkpoint file. It is written the same atomic way,
17
+ * AFTER the checkpoint itself, so a crash between the two writes leaves the
18
+ * pointer stale (pointing at the previous job) rather than pointing at a
19
+ * checkpoint that does not exist — `loadLatest` degrades to `null` if the
20
+ * pointer cannot be trusted at all (spec.md §25.2: reads degrade optional
21
+ * data in a controlled way), while `load(jobId)` on an actually corrupted
22
+ * checkpoint body throws instead, because THAT is the resume-critical record
23
+ * itself, not an accelerator index over it.
24
+ */
25
+ import * as nodePath from "node:path";
26
+ import { ID_COMPONENT_SEPARATOR, sha256Hex, UntangleError } from "@untangle-code/domain";
27
+ import { assertNotAborted } from "./abort.js";
28
+ import { isNotFoundError } from "./fs-errors.js";
29
+ const POINTER_DIR_NAME = "latest";
30
+ function encode(text) {
31
+ return new TextEncoder().encode(text);
32
+ }
33
+ function signalOptions(options) {
34
+ return options?.signal ? { signal: options.signal } : undefined;
35
+ }
36
+ /** Local, atomically-mirrored `CheckpointStorePort` under `jobs/`. */
37
+ export class LocalCheckpointStore {
38
+ fs;
39
+ jobsDir;
40
+ random;
41
+ constructor(fs, jobsDir, random) {
42
+ this.fs = fs;
43
+ this.jobsDir = jobsDir;
44
+ this.random = random;
45
+ }
46
+ pathForJob(jobId) {
47
+ return nodePath.join(this.jobsDir, `${jobId}.json`);
48
+ }
49
+ /**
50
+ * Both components are variable-length, so a plain concatenation would be
51
+ * ambiguous (e.g. `repoId="ab"` + `refId="c"` vs. `repoId="a"` +
52
+ * `refId="bc"`) — joined with the same separator domain's own identity
53
+ * model uses for exactly this reason (`identity.ts`'s doc comment).
54
+ */
55
+ pointerPathFor(repoId, refId) {
56
+ const hash = sha256Hex(`${repoId}${ID_COMPONENT_SEPARATOR}${refId}`);
57
+ return nodePath.join(this.jobsDir, POINTER_DIR_NAME, `${hash}.json`);
58
+ }
59
+ async writeAtomic(finalPath, content, options) {
60
+ const dir = nodePath.dirname(finalPath);
61
+ await this.fs.mkdir(dir, { recursive: true, ...signalOptions(options) });
62
+ const tempPath = `${finalPath}.${this.random.uuid()}.tmp`;
63
+ await this.fs.writeFile(tempPath, encode(content), options);
64
+ await this.fs.rename(tempPath, finalPath, options);
65
+ }
66
+ async save(checkpoint, options) {
67
+ assertNotAborted(options, "checkpoint_save");
68
+ await this.writeAtomic(this.pathForJob(checkpoint.jobId), JSON.stringify(checkpoint), options);
69
+ const pointer = { jobId: checkpoint.jobId };
70
+ await this.writeAtomic(this.pointerPathFor(checkpoint.repoId, checkpoint.refId), JSON.stringify(pointer), options);
71
+ }
72
+ async load(jobId, options) {
73
+ assertNotAborted(options, "checkpoint_load");
74
+ let text;
75
+ try {
76
+ text = await this.fs.readTextFile(this.pathForJob(jobId), options);
77
+ }
78
+ catch (error) {
79
+ if (isNotFoundError(error)) {
80
+ return null;
81
+ }
82
+ throw error;
83
+ }
84
+ try {
85
+ return JSON.parse(text);
86
+ }
87
+ catch (parseError) {
88
+ // Should never happen given atomic temp+rename writes — this is a
89
+ // defensive, loud failure (fail-closed, spec.md §25.2) for the one
90
+ // record a resume actually depends on, not a "missing optional data"
91
+ // case that could silently degrade to "start over".
92
+ throw new UntangleError("INDEX_INCOMPLETE", `checkpoint "${jobId}" is corrupted and cannot be safely resumed from`, { operation: "checkpoint_load", context: { jobId }, cause: parseError });
93
+ }
94
+ }
95
+ async loadLatest(repoId, refId, options) {
96
+ assertNotAborted(options, "checkpoint_load_latest");
97
+ let text;
98
+ try {
99
+ text = await this.fs.readTextFile(this.pointerPathFor(repoId, refId), options);
100
+ }
101
+ catch (error) {
102
+ if (isNotFoundError(error)) {
103
+ return null;
104
+ }
105
+ throw error;
106
+ }
107
+ let pointer;
108
+ try {
109
+ pointer = JSON.parse(text);
110
+ }
111
+ catch {
112
+ // The pointer is an accelerator index, not the resume record itself —
113
+ // degrade to "no known latest" rather than throwing (spec.md §25.2).
114
+ return null;
115
+ }
116
+ return this.load(pointer.jobId, options);
117
+ }
118
+ async delete(jobId, options) {
119
+ assertNotAborted(options, "checkpoint_delete");
120
+ await this.fs.remove(this.pathForJob(jobId), options);
121
+ }
122
+ }
123
+ //# sourceMappingURL=checkpoint-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkpoint-store.js","sourceRoot":"","sources":["../src/checkpoint-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import type { Clock, FileSystemPort, PortCallOptions, Random } from "@untangle-code/domain";
2
+ /** 512 MiB — an Untangle default, not spec-mandated (spec.md §9.4 leaves the
3
+ * limit open); callers pass their own `maxBytes` where config specifies one. */
4
+ export declare const DEFAULT_EMBEDDING_CACHE_MAX_BYTES: number;
5
+ export interface EmbeddingCacheStatsV1 {
6
+ entryCount: number;
7
+ totalBytes: number;
8
+ }
9
+ /**
10
+ * Cache key exactly per spec.md §9.4: `embedding_profile_id + SHA256(prepared_text)`.
11
+ * Exported so callers/tests can reason about identity without depending on
12
+ * this class's internal file sharding.
13
+ */
14
+ export declare function buildEmbeddingCacheKey(embeddingProfileId: string, preparedText: string): string;
15
+ export declare class LocalEmbeddingCache {
16
+ private readonly fs;
17
+ private readonly dir;
18
+ private readonly clock;
19
+ private readonly random;
20
+ private readonly maxBytes;
21
+ private manifest;
22
+ constructor(fs: FileSystemPort, dir: string, clock: Clock, random: Random, options?: {
23
+ maxBytes?: number;
24
+ });
25
+ private manifestPath;
26
+ private entryFileName;
27
+ private entryPath;
28
+ private loadManifest;
29
+ private persistManifest;
30
+ get(embeddingProfileId: string, preparedText: string, options?: PortCallOptions): Promise<number[] | null>;
31
+ set(embeddingProfileId: string, preparedText: string, vector: readonly number[], options?: PortCallOptions): Promise<void>;
32
+ private evictUntilWithinLimit;
33
+ stats(options?: PortCallOptions): Promise<EmbeddingCacheStatsV1>;
34
+ /** Explicit, whole-cache removal (the one "clear everything" operation this
35
+ * class exposes — always naming its exact target: this cache's own directory). */
36
+ clear(options?: PortCallOptions): Promise<void>;
37
+ }
38
+ //# sourceMappingURL=embedding-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embedding-cache.d.ts","sourceRoot":"","sources":["../src/embedding-cache.ts"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAM5F;gFACgF;AAChF,eAAO,MAAM,iCAAiC,QAAoB,CAAC;AAInE,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAuDD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,kBAAkB,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAE/F;AAED,qBAAa,mBAAmB;IAK5B,OAAO,CAAC,QAAQ,CAAC,EAAE;IACnB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAPzB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAA2B;gBAGxB,EAAE,EAAE,cAAc,EAClB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,MAAM,EAC/B,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE;IAKjC,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,SAAS;YAIH,YAAY;YA2BZ,eAAe;IAWhB,GAAG,CACd,kBAAkB,EAAE,MAAM,EAC1B,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC;IAiCd,GAAG,CACd,kBAAkB,EAAE,MAAM,EAC1B,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,IAAI,CAAC;YA0BF,qBAAqB;IAyBtB,KAAK,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAM7E;sFACkF;IACrE,KAAK,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;CAK7D"}