@fgv/ts-agent-memory 5.1.0-41 → 5.1.0-43
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/.rush/temp/{cbbdbe09515171b4eba8f2592be2dace1e4e8142.tar.log → 285f03271c27ef724d49e730c0db58d9e4ac1a44.tar.log} +38 -2
- package/.rush/temp/chunked-rush-logs/ts-agent-memory.build.chunks.jsonl +2 -2
- package/.rush/temp/operation/build/all.log +2 -2
- package/.rush/temp/operation/build/log-chunks.jsonl +2 -2
- package/.rush/temp/operation/build/state.json +1 -1
- package/dist/packlets/retrieve/fragmentSemanticRetriever.js +78 -0
- package/dist/packlets/retrieve/fragmentSemanticRetriever.js.map +1 -0
- package/dist/packlets/retrieve/index.js +1 -0
- package/dist/packlets/retrieve/index.js.map +1 -1
- package/dist/packlets/store/fileTreeMemoryStore.js +105 -14
- package/dist/packlets/store/fileTreeMemoryStore.js.map +1 -1
- package/dist/packlets/vector/inMemoryFragmentCosineIndex.js +200 -0
- package/dist/packlets/vector/inMemoryFragmentCosineIndex.js.map +1 -0
- package/dist/packlets/vector/index.js +1 -0
- package/dist/packlets/vector/index.js.map +1 -1
- package/dist/packlets/vector/vectorIndex.js.map +1 -1
- package/dist/test/unit/retrieve/fragmentSemanticRetriever.test.js +116 -0
- package/dist/test/unit/retrieve/fragmentSemanticRetriever.test.js.map +1 -0
- package/dist/test/unit/store/fragmentEmbedOnWrite.test.js +255 -0
- package/dist/test/unit/store/fragmentEmbedOnWrite.test.js.map +1 -0
- package/dist/test/unit/store/lenientOpen.test.js +248 -0
- package/dist/test/unit/store/lenientOpen.test.js.map +1 -0
- package/dist/test/unit/vector/inMemoryFragmentCosineIndex.test.js +297 -0
- package/dist/test/unit/vector/inMemoryFragmentCosineIndex.test.js.map +1 -0
- package/dist/ts-agent-memory.d.ts +367 -4
- package/etc/ts-agent-memory.api.md +78 -0
- package/lib/packlets/retrieve/fragmentSemanticRetriever.d.ts +90 -0
- package/lib/packlets/retrieve/fragmentSemanticRetriever.d.ts.map +1 -0
- package/lib/packlets/retrieve/fragmentSemanticRetriever.js +82 -0
- package/lib/packlets/retrieve/fragmentSemanticRetriever.js.map +1 -0
- package/lib/packlets/retrieve/index.d.ts +1 -0
- package/lib/packlets/retrieve/index.d.ts.map +1 -1
- package/lib/packlets/retrieve/index.js +1 -0
- package/lib/packlets/retrieve/index.js.map +1 -1
- package/lib/packlets/store/fileTreeMemoryStore.d.ts +115 -1
- package/lib/packlets/store/fileTreeMemoryStore.d.ts.map +1 -1
- package/lib/packlets/store/fileTreeMemoryStore.js +104 -13
- package/lib/packlets/store/fileTreeMemoryStore.js.map +1 -1
- package/lib/packlets/vector/inMemoryFragmentCosineIndex.d.ts +74 -0
- package/lib/packlets/vector/inMemoryFragmentCosineIndex.d.ts.map +1 -0
- package/lib/packlets/vector/inMemoryFragmentCosineIndex.js +204 -0
- package/lib/packlets/vector/inMemoryFragmentCosineIndex.js.map +1 -0
- package/lib/packlets/vector/index.d.ts +1 -0
- package/lib/packlets/vector/index.d.ts.map +1 -1
- package/lib/packlets/vector/index.js +1 -0
- package/lib/packlets/vector/index.js.map +1 -1
- package/lib/packlets/vector/vectorIndex.d.ts +85 -4
- package/lib/packlets/vector/vectorIndex.d.ts.map +1 -1
- package/lib/packlets/vector/vectorIndex.js.map +1 -1
- package/lib/test/unit/retrieve/fragmentSemanticRetriever.test.d.ts +2 -0
- package/lib/test/unit/retrieve/fragmentSemanticRetriever.test.d.ts.map +1 -0
- package/lib/test/unit/retrieve/fragmentSemanticRetriever.test.js +118 -0
- package/lib/test/unit/retrieve/fragmentSemanticRetriever.test.js.map +1 -0
- package/lib/test/unit/store/fragmentEmbedOnWrite.test.d.ts +2 -0
- package/lib/test/unit/store/fragmentEmbedOnWrite.test.d.ts.map +1 -0
- package/lib/test/unit/store/fragmentEmbedOnWrite.test.js +257 -0
- package/lib/test/unit/store/fragmentEmbedOnWrite.test.js.map +1 -0
- package/lib/test/unit/store/lenientOpen.test.d.ts +2 -0
- package/lib/test/unit/store/lenientOpen.test.d.ts.map +1 -0
- package/lib/test/unit/store/lenientOpen.test.js +250 -0
- package/lib/test/unit/store/lenientOpen.test.js.map +1 -0
- package/lib/test/unit/vector/inMemoryFragmentCosineIndex.test.d.ts +2 -0
- package/lib/test/unit/vector/inMemoryFragmentCosineIndex.test.d.ts.map +1 -0
- package/lib/test/unit/vector/inMemoryFragmentCosineIndex.test.js +299 -0
- package/lib/test/unit/vector/inMemoryFragmentCosineIndex.test.js.map +1 -0
- package/package.json +7 -7
- package/rush-logs/ts-agent-memory.build.cache.log +1 -1
- package/rush-logs/ts-agent-memory.build.log +2 -2
- package/src/packlets/retrieve/fragmentSemanticRetriever.ts +135 -0
- package/src/packlets/retrieve/index.ts +1 -0
- package/src/packlets/store/fileTreeMemoryStore.ts +208 -16
- package/src/packlets/vector/inMemoryFragmentCosineIndex.ts +262 -0
- package/src/packlets/vector/index.ts +1 -0
- package/src/packlets/vector/vectorIndex.ts +97 -4
- package/src/test/unit/retrieve/fragmentSemanticRetriever.test.ts +163 -0
- package/src/test/unit/store/fragmentEmbedOnWrite.test.ts +349 -0
- package/src/test/unit/store/lenientOpen.test.ts +292 -0
- package/src/test/unit/vector/inMemoryFragmentCosineIndex.test.ts +389 -0
- package/temp/build/lint/_eslint-5eVG3S6w.json +29 -5
- package/temp/build/typescript/ts_8nwakTlr.json +1 -1
- package/temp/ts-agent-memory.api.json +4584 -2859
- package/temp/ts-agent-memory.api.md +78 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
Start time:
|
|
2
|
-
Invoking "/usr/bin/tar -c -f /home/runner/work/fgv/fgv/common/temp/build-cache/
|
|
1
|
+
Start time: Mon Jul 20 2026 00:41:10 GMT+0000 (Coordinated Universal Time)
|
|
2
|
+
Invoking "/usr/bin/tar -c -f /home/runner/work/fgv/fgv/common/temp/build-cache/285f03271c27ef724d49e730c0db58d9e4ac1a44-b5b9975b621910d8.temp -z --files-from=-"
|
|
3
3
|
|
|
4
4
|
======= BEGIN PROCESS INPUT ======
|
|
5
5
|
.rush/temp/operation/build/all.log
|
|
@@ -33,6 +33,8 @@ dist/packlets/observe/memoryObservationStore.js
|
|
|
33
33
|
dist/packlets/observe/memoryObservationStore.js.map
|
|
34
34
|
dist/packlets/observe/observer.js
|
|
35
35
|
dist/packlets/observe/observer.js.map
|
|
36
|
+
dist/packlets/retrieve/fragmentSemanticRetriever.js
|
|
37
|
+
dist/packlets/retrieve/fragmentSemanticRetriever.js.map
|
|
36
38
|
dist/packlets/retrieve/hybridRetriever.js
|
|
37
39
|
dist/packlets/retrieve/hybridRetriever.js.map
|
|
38
40
|
dist/packlets/retrieve/index.js
|
|
@@ -77,6 +79,8 @@ dist/packlets/types/writePolicy.js
|
|
|
77
79
|
dist/packlets/types/writePolicy.js.map
|
|
78
80
|
dist/packlets/vector/inMemoryCosineIndex.js
|
|
79
81
|
dist/packlets/vector/inMemoryCosineIndex.js.map
|
|
82
|
+
dist/packlets/vector/inMemoryFragmentCosineIndex.js
|
|
83
|
+
dist/packlets/vector/inMemoryFragmentCosineIndex.js.map
|
|
80
84
|
dist/packlets/vector/index.js
|
|
81
85
|
dist/packlets/vector/index.js.map
|
|
82
86
|
dist/packlets/vector/vectorIndex.js
|
|
@@ -97,6 +101,8 @@ dist/test/unit/ingest/orchestrator.test.js
|
|
|
97
101
|
dist/test/unit/ingest/orchestrator.test.js.map
|
|
98
102
|
dist/test/unit/observe/memoryObservationStore.test.js
|
|
99
103
|
dist/test/unit/observe/memoryObservationStore.test.js.map
|
|
104
|
+
dist/test/unit/retrieve/fragmentSemanticRetriever.test.js
|
|
105
|
+
dist/test/unit/retrieve/fragmentSemanticRetriever.test.js.map
|
|
100
106
|
dist/test/unit/retrieve/linkTraversalRetriever.test.js
|
|
101
107
|
dist/test/unit/retrieve/linkTraversalRetriever.test.js.map
|
|
102
108
|
dist/test/unit/retrieve/retrievers.test.js
|
|
@@ -109,6 +115,10 @@ dist/test/unit/store/embedOnWrite.test.js
|
|
|
109
115
|
dist/test/unit/store/embedOnWrite.test.js.map
|
|
110
116
|
dist/test/unit/store/fileTreeMemoryStore.test.js
|
|
111
117
|
dist/test/unit/store/fileTreeMemoryStore.test.js.map
|
|
118
|
+
dist/test/unit/store/fragmentEmbedOnWrite.test.js
|
|
119
|
+
dist/test/unit/store/fragmentEmbedOnWrite.test.js.map
|
|
120
|
+
dist/test/unit/store/lenientOpen.test.js
|
|
121
|
+
dist/test/unit/store/lenientOpen.test.js.map
|
|
112
122
|
dist/test/unit/store/listScoped.test.js
|
|
113
123
|
dist/test/unit/store/listScoped.test.js.map
|
|
114
124
|
dist/test/unit/store/observations.test.js
|
|
@@ -133,6 +143,8 @@ dist/test/unit/types/writePolicy.test.js
|
|
|
133
143
|
dist/test/unit/types/writePolicy.test.js.map
|
|
134
144
|
dist/test/unit/vector/inMemoryCosineIndex.test.js
|
|
135
145
|
dist/test/unit/vector/inMemoryCosineIndex.test.js.map
|
|
146
|
+
dist/test/unit/vector/inMemoryFragmentCosineIndex.test.js
|
|
147
|
+
dist/test/unit/vector/inMemoryFragmentCosineIndex.test.js.map
|
|
136
148
|
dist/test/unit/vector/vectorIndex.test.js
|
|
137
149
|
dist/test/unit/vector/vectorIndex.test.js.map
|
|
138
150
|
dist/ts-agent-memory.d.ts
|
|
@@ -193,6 +205,10 @@ lib/packlets/observe/observer.d.ts
|
|
|
193
205
|
lib/packlets/observe/observer.d.ts.map
|
|
194
206
|
lib/packlets/observe/observer.js
|
|
195
207
|
lib/packlets/observe/observer.js.map
|
|
208
|
+
lib/packlets/retrieve/fragmentSemanticRetriever.d.ts
|
|
209
|
+
lib/packlets/retrieve/fragmentSemanticRetriever.d.ts.map
|
|
210
|
+
lib/packlets/retrieve/fragmentSemanticRetriever.js
|
|
211
|
+
lib/packlets/retrieve/fragmentSemanticRetriever.js.map
|
|
196
212
|
lib/packlets/retrieve/hybridRetriever.d.ts
|
|
197
213
|
lib/packlets/retrieve/hybridRetriever.d.ts.map
|
|
198
214
|
lib/packlets/retrieve/hybridRetriever.js
|
|
@@ -281,6 +297,10 @@ lib/packlets/vector/inMemoryCosineIndex.d.ts
|
|
|
281
297
|
lib/packlets/vector/inMemoryCosineIndex.d.ts.map
|
|
282
298
|
lib/packlets/vector/inMemoryCosineIndex.js
|
|
283
299
|
lib/packlets/vector/inMemoryCosineIndex.js.map
|
|
300
|
+
lib/packlets/vector/inMemoryFragmentCosineIndex.d.ts
|
|
301
|
+
lib/packlets/vector/inMemoryFragmentCosineIndex.d.ts.map
|
|
302
|
+
lib/packlets/vector/inMemoryFragmentCosineIndex.js
|
|
303
|
+
lib/packlets/vector/inMemoryFragmentCosineIndex.js.map
|
|
284
304
|
lib/packlets/vector/index.d.ts
|
|
285
305
|
lib/packlets/vector/index.d.ts.map
|
|
286
306
|
lib/packlets/vector/index.js
|
|
@@ -321,6 +341,10 @@ lib/test/unit/observe/memoryObservationStore.test.d.ts
|
|
|
321
341
|
lib/test/unit/observe/memoryObservationStore.test.d.ts.map
|
|
322
342
|
lib/test/unit/observe/memoryObservationStore.test.js
|
|
323
343
|
lib/test/unit/observe/memoryObservationStore.test.js.map
|
|
344
|
+
lib/test/unit/retrieve/fragmentSemanticRetriever.test.d.ts
|
|
345
|
+
lib/test/unit/retrieve/fragmentSemanticRetriever.test.d.ts.map
|
|
346
|
+
lib/test/unit/retrieve/fragmentSemanticRetriever.test.js
|
|
347
|
+
lib/test/unit/retrieve/fragmentSemanticRetriever.test.js.map
|
|
324
348
|
lib/test/unit/retrieve/linkTraversalRetriever.test.d.ts
|
|
325
349
|
lib/test/unit/retrieve/linkTraversalRetriever.test.d.ts.map
|
|
326
350
|
lib/test/unit/retrieve/linkTraversalRetriever.test.js
|
|
@@ -345,6 +369,14 @@ lib/test/unit/store/fileTreeMemoryStore.test.d.ts
|
|
|
345
369
|
lib/test/unit/store/fileTreeMemoryStore.test.d.ts.map
|
|
346
370
|
lib/test/unit/store/fileTreeMemoryStore.test.js
|
|
347
371
|
lib/test/unit/store/fileTreeMemoryStore.test.js.map
|
|
372
|
+
lib/test/unit/store/fragmentEmbedOnWrite.test.d.ts
|
|
373
|
+
lib/test/unit/store/fragmentEmbedOnWrite.test.d.ts.map
|
|
374
|
+
lib/test/unit/store/fragmentEmbedOnWrite.test.js
|
|
375
|
+
lib/test/unit/store/fragmentEmbedOnWrite.test.js.map
|
|
376
|
+
lib/test/unit/store/lenientOpen.test.d.ts
|
|
377
|
+
lib/test/unit/store/lenientOpen.test.d.ts.map
|
|
378
|
+
lib/test/unit/store/lenientOpen.test.js
|
|
379
|
+
lib/test/unit/store/lenientOpen.test.js.map
|
|
348
380
|
lib/test/unit/store/listScoped.test.d.ts
|
|
349
381
|
lib/test/unit/store/listScoped.test.d.ts.map
|
|
350
382
|
lib/test/unit/store/listScoped.test.js
|
|
@@ -393,6 +425,10 @@ lib/test/unit/vector/inMemoryCosineIndex.test.d.ts
|
|
|
393
425
|
lib/test/unit/vector/inMemoryCosineIndex.test.d.ts.map
|
|
394
426
|
lib/test/unit/vector/inMemoryCosineIndex.test.js
|
|
395
427
|
lib/test/unit/vector/inMemoryCosineIndex.test.js.map
|
|
428
|
+
lib/test/unit/vector/inMemoryFragmentCosineIndex.test.d.ts
|
|
429
|
+
lib/test/unit/vector/inMemoryFragmentCosineIndex.test.d.ts.map
|
|
430
|
+
lib/test/unit/vector/inMemoryFragmentCosineIndex.test.js
|
|
431
|
+
lib/test/unit/vector/inMemoryFragmentCosineIndex.test.js.map
|
|
396
432
|
lib/test/unit/vector/vectorIndex.test.d.ts
|
|
397
433
|
lib/test/unit/vector/vectorIndex.test.d.ts.map
|
|
398
434
|
lib/test/unit/vector/vectorIndex.test.js
|
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
{"kind":"O","text":"[build:lint] Using ESLint version 9.39.5\n"}
|
|
6
6
|
{"kind":"O","text":"[build:api-extractor] Using API Extractor version 7.58.9\n"}
|
|
7
7
|
{"kind":"O","text":"[build:api-extractor] Analysis will use the bundled TypeScript version 5.9.3\n"}
|
|
8
|
-
{"kind":"O","text":" ---- build finished (26.
|
|
9
|
-
{"kind":"O","text":"-------------------- Finished (26.
|
|
8
|
+
{"kind":"O","text":" ---- build finished (26.514s) ---- \n"}
|
|
9
|
+
{"kind":"O","text":"-------------------- Finished (26.523s) --------------------\n"}
|
|
@@ -5,5 +5,5 @@ Invoking: heft build --clean
|
|
|
5
5
|
[build:lint] Using ESLint version 9.39.5
|
|
6
6
|
[build:api-extractor] Using API Extractor version 7.58.9
|
|
7
7
|
[build:api-extractor] Analysis will use the bundled TypeScript version 5.9.3
|
|
8
|
-
---- build finished (26.
|
|
9
|
-
-------------------- Finished (26.
|
|
8
|
+
---- build finished (26.514s) ----
|
|
9
|
+
-------------------- Finished (26.523s) --------------------
|
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
{"kind":"O","text":"[build:lint] Using ESLint version 9.39.5\n"}
|
|
6
6
|
{"kind":"O","text":"[build:api-extractor] Using API Extractor version 7.58.9\n"}
|
|
7
7
|
{"kind":"O","text":"[build:api-extractor] Analysis will use the bundled TypeScript version 5.9.3\n"}
|
|
8
|
-
{"kind":"O","text":" ---- build finished (26.
|
|
9
|
-
{"kind":"O","text":"-------------------- Finished (26.
|
|
8
|
+
{"kind":"O","text":" ---- build finished (26.514s) ---- \n"}
|
|
9
|
+
{"kind":"O","text":"-------------------- Finished (26.523s) --------------------\n"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026 Erik Fortune
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
import { fail, succeed } from '@fgv/ts-utils';
|
|
6
|
+
/**
|
|
7
|
+
* The loud-degradation message a {@link FragmentSemanticRetriever} returns when a
|
|
8
|
+
* fragment query is issued but no {@link IFragmentSemanticBackend | backend} is
|
|
9
|
+
* wired — the discovery surface NEVER answers a fragment query with a silent empty.
|
|
10
|
+
* @public
|
|
11
|
+
*/
|
|
12
|
+
export const FRAGMENT_SEMANTIC_UNWIRED_MESSAGE = 'fragment recall: no fragment index is wired; wire an IFragmentSemanticBackend to enable sub-document search';
|
|
13
|
+
/**
|
|
14
|
+
* The sub-document semantic-search retriever — the "discovery" half of a
|
|
15
|
+
* search-then-read contract. It embeds a fragment query, queries the
|
|
16
|
+
* {@link IFragmentVectorIndex}, and returns the raw per-fragment
|
|
17
|
+
* {@link IVectorQueryHit | hits} (each carrying a record `target` AND the matched
|
|
18
|
+
* `locator`), NOT resolved records: the consumer re-reads each record and slices it
|
|
19
|
+
* by the locator on its own read side.
|
|
20
|
+
*
|
|
21
|
+
* @remarks
|
|
22
|
+
* Deliberately NOT an {@link IMemoryRetriever}: memory recall is record-granular and
|
|
23
|
+
* returns records; fragment discovery is span-granular and returns locators. Keeping
|
|
24
|
+
* it a distinct surface matches the consumer contract (memory stays record-granular;
|
|
25
|
+
* sub-document knowledge uses a separate fragment index) and avoids overloading the
|
|
26
|
+
* record retriever's return type with a locator that only makes sense here.
|
|
27
|
+
*
|
|
28
|
+
* When no backend is wired, `supportsFragmentRecall` is `false` and any fragment
|
|
29
|
+
* query degrades loudly ({@link FRAGMENT_SEMANTIC_UNWIRED_MESSAGE}) — it NEVER
|
|
30
|
+
* returns a silent empty. A consumer-supplied backend that rejects (throws) is
|
|
31
|
+
* normalized into a `Failure`.
|
|
32
|
+
* @public
|
|
33
|
+
*/
|
|
34
|
+
export class FragmentSemanticRetriever {
|
|
35
|
+
constructor(backend) {
|
|
36
|
+
this._backend = backend;
|
|
37
|
+
}
|
|
38
|
+
/** What this retriever can do given its wiring. */
|
|
39
|
+
get capabilities() {
|
|
40
|
+
return { supportsFragmentRecall: this._backend !== undefined };
|
|
41
|
+
}
|
|
42
|
+
/** Family-convention factory. */
|
|
43
|
+
static create(params) {
|
|
44
|
+
return succeed(new FragmentSemanticRetriever(params.backend));
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Embed `query.semantic`, query the fragment index, and return the per-fragment
|
|
48
|
+
* hits in descending score order. Fails loudly when no backend is wired.
|
|
49
|
+
*/
|
|
50
|
+
async retrieve(query) {
|
|
51
|
+
if (this._backend === undefined) {
|
|
52
|
+
return fail(FRAGMENT_SEMANTIC_UNWIRED_MESSAGE);
|
|
53
|
+
}
|
|
54
|
+
const backend = this._backend;
|
|
55
|
+
// Consumer-supplied hooks may throw; normalize both a returned `fail` and a
|
|
56
|
+
// rejection into a single `fragment recall: <label> failed` Failure so
|
|
57
|
+
// `retrieve` always honors its `Promise<Result<...>>` contract.
|
|
58
|
+
const embedded = await FragmentSemanticRetriever._callBackend('query embedding', () => backend.embedQuery(query.semantic));
|
|
59
|
+
if (embedded.isFailure()) {
|
|
60
|
+
return fail(embedded.message);
|
|
61
|
+
}
|
|
62
|
+
return FragmentSemanticRetriever._callBackend('fragment query', () => { var _a; return backend.fragmentIndex.query(embedded.value, (_a = query.topK) !== null && _a !== void 0 ? _a : 10, query.maxPerRecord); });
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Invoke a consumer-supplied backend hook, normalizing both a returned `fail`
|
|
66
|
+
* and a thrown/rejected promise into a single `fragment recall: <label> failed`
|
|
67
|
+
* `Failure`.
|
|
68
|
+
*/
|
|
69
|
+
static async _callBackend(label, op) {
|
|
70
|
+
try {
|
|
71
|
+
return (await op()).withErrorFormat((msg) => `fragment recall: ${label} failed: ${msg}`);
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
return fail(`fragment recall: ${label} failed: ${String(err)}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=fragmentSemanticRetriever.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fragmentSemanticRetriever.js","sourceRoot":"","sources":["../../../src/packlets/retrieve/fragmentSemanticRetriever.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAU,IAAI,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAItD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iCAAiC,GAC5C,6GAA6G,CAAC;AA0ChH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,OAAO,yBAAyB;IAGpC,YAAoB,OAA6C;QAC/D,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;IAC1B,CAAC;IAED,mDAAmD;IACnD,IAAW,YAAY;QACrB,OAAO,EAAE,sBAAsB,EAAE,IAAI,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;IACjE,CAAC;IAED,iCAAiC;IAC1B,MAAM,CAAC,MAAM,CAAC,MAEpB;QACC,OAAO,OAAO,CAAC,IAAI,yBAAyB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;IAChE,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,QAAQ,CAAC,KAAqB;QACzC,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;YAChC,OAAO,IAAI,CAAC,iCAAiC,CAAC,CAAC;QACjD,CAAC;QACD,MAAM,OAAO,GAA6B,IAAI,CAAC,QAAQ,CAAC;QACxD,4EAA4E;QAC5E,uEAAuE;QACvE,gEAAgE;QAChE,MAAM,QAAQ,GAAyB,MAAM,yBAAyB,CAAC,YAAY,CACjF,iBAAiB,EACjB,GAAG,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,CACzC,CAAC;QACF,IAAI,QAAQ,CAAC,SAAS,EAAE,EAAE,CAAC;YACzB,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAChC,CAAC;QACD,OAAO,yBAAyB,CAAC,YAAY,CAAC,gBAAgB,EAAE,GAAG,EAAE,WACnE,OAAA,OAAO,CAAC,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAA,KAAK,CAAC,IAAI,mCAAI,EAAE,EAAE,KAAK,CAAC,YAAY,CAAC,CAAA,EAAA,CAClF,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACK,MAAM,CAAC,KAAK,CAAC,YAAY,CAAI,KAAa,EAAE,EAA4B;QAC9E,IAAI,CAAC;YACH,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,eAAe,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,oBAAoB,KAAK,YAAY,GAAG,EAAE,CAAC,CAAC;QAC3F,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,IAAI,CAAC,oBAAoB,KAAK,YAAY,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAClE,CAAC;IACH,CAAC;CACF","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Result, fail, succeed } from '@fgv/ts-utils';\nimport { IFragmentVectorIndex, IVectorQueryHit } from '../vector';\nimport { QueryEmbedder } from './semanticRetriever';\n\n/**\n * The loud-degradation message a {@link FragmentSemanticRetriever} returns when a\n * fragment query is issued but no {@link IFragmentSemanticBackend | backend} is\n * wired — the discovery surface NEVER answers a fragment query with a silent empty.\n * @public\n */\nexport const FRAGMENT_SEMANTIC_UNWIRED_MESSAGE: string =\n 'fragment recall: no fragment index is wired; wire an IFragmentSemanticBackend to enable sub-document search';\n\n/**\n * The fragment backend wired into a {@link FragmentSemanticRetriever}: the fragment\n * index to query and the embedder that turns the query text into a vector. Both are\n * required together — a fragment index is useless without a way to embed the query.\n * @public\n */\nexport interface IFragmentSemanticBackend {\n /** The fragment-granular vector index to query. */\n readonly fragmentIndex: IFragmentVectorIndex;\n /** Turns the query text into a vector. */\n readonly embedQuery: QueryEmbedder;\n}\n\n/**\n * A sub-document semantic-search request: the natural-language `semantic` text to\n * match, an optional `topK` result cap (default 10), and an optional\n * `maxPerRecord` cap that keeps one long document from monopolizing the result.\n * @public\n */\nexport interface IFragmentQuery {\n /** The natural-language text to embed and match against stored fragments. */\n readonly semantic: string;\n /** Maximum number of fragment hits to return. Defaults to 10. */\n readonly topK?: number;\n /**\n * Maximum number of fragments any single record may contribute to the result.\n * Applied during selection (before the `topK` cut). Omit for uncapped.\n */\n readonly maxPerRecord?: number;\n}\n\n/**\n * What a {@link FragmentSemanticRetriever} can do given its wiring.\n * @public\n */\nexport interface IFragmentRetrieverCapabilities {\n /** `true` when a fragment backend is wired and fragment recall is operational. */\n readonly supportsFragmentRecall: boolean;\n}\n\n/**\n * The sub-document semantic-search retriever — the \"discovery\" half of a\n * search-then-read contract. It embeds a fragment query, queries the\n * {@link IFragmentVectorIndex}, and returns the raw per-fragment\n * {@link IVectorQueryHit | hits} (each carrying a record `target` AND the matched\n * `locator`), NOT resolved records: the consumer re-reads each record and slices it\n * by the locator on its own read side.\n *\n * @remarks\n * Deliberately NOT an {@link IMemoryRetriever}: memory recall is record-granular and\n * returns records; fragment discovery is span-granular and returns locators. Keeping\n * it a distinct surface matches the consumer contract (memory stays record-granular;\n * sub-document knowledge uses a separate fragment index) and avoids overloading the\n * record retriever's return type with a locator that only makes sense here.\n *\n * When no backend is wired, `supportsFragmentRecall` is `false` and any fragment\n * query degrades loudly ({@link FRAGMENT_SEMANTIC_UNWIRED_MESSAGE}) — it NEVER\n * returns a silent empty. A consumer-supplied backend that rejects (throws) is\n * normalized into a `Failure`.\n * @public\n */\nexport class FragmentSemanticRetriever {\n private readonly _backend: IFragmentSemanticBackend | undefined;\n\n private constructor(backend: IFragmentSemanticBackend | undefined) {\n this._backend = backend;\n }\n\n /** What this retriever can do given its wiring. */\n public get capabilities(): IFragmentRetrieverCapabilities {\n return { supportsFragmentRecall: this._backend !== undefined };\n }\n\n /** Family-convention factory. */\n public static create(params: {\n readonly backend?: IFragmentSemanticBackend;\n }): Result<FragmentSemanticRetriever> {\n return succeed(new FragmentSemanticRetriever(params.backend));\n }\n\n /**\n * Embed `query.semantic`, query the fragment index, and return the per-fragment\n * hits in descending score order. Fails loudly when no backend is wired.\n */\n public async retrieve(query: IFragmentQuery): Promise<Result<ReadonlyArray<IVectorQueryHit>>> {\n if (this._backend === undefined) {\n return fail(FRAGMENT_SEMANTIC_UNWIRED_MESSAGE);\n }\n const backend: IFragmentSemanticBackend = this._backend;\n // Consumer-supplied hooks may throw; normalize both a returned `fail` and a\n // rejection into a single `fragment recall: <label> failed` Failure so\n // `retrieve` always honors its `Promise<Result<...>>` contract.\n const embedded: Result<Float32Array> = await FragmentSemanticRetriever._callBackend(\n 'query embedding',\n () => backend.embedQuery(query.semantic)\n );\n if (embedded.isFailure()) {\n return fail(embedded.message);\n }\n return FragmentSemanticRetriever._callBackend('fragment query', () =>\n backend.fragmentIndex.query(embedded.value, query.topK ?? 10, query.maxPerRecord)\n );\n }\n\n /**\n * Invoke a consumer-supplied backend hook, normalizing both a returned `fail`\n * and a thrown/rejected promise into a single `fragment recall: <label> failed`\n * `Failure`.\n */\n private static async _callBackend<T>(label: string, op: () => Promise<Result<T>>): Promise<Result<T>> {\n try {\n return (await op()).withErrorFormat((msg) => `fragment recall: ${label} failed: ${msg}`);\n } catch (err) {\n return fail(`fragment recall: ${label} failed: ${String(err)}`);\n }\n }\n}\n"]}
|
|
@@ -8,6 +8,7 @@ export * from './linkTraversalRetriever';
|
|
|
8
8
|
export * from './tagRetriever';
|
|
9
9
|
export * from './structuredFilterRetriever';
|
|
10
10
|
export * from './semanticRetriever';
|
|
11
|
+
export * from './fragmentSemanticRetriever';
|
|
11
12
|
export * from './temporalRetrievers';
|
|
12
13
|
export * from './hybridRetriever';
|
|
13
14
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/packlets/retrieve/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nexport * from './retriever';\nexport * from './recencyRetriever';\nexport * from './linkTraversalRetriever';\nexport * from './tagRetriever';\nexport * from './structuredFilterRetriever';\nexport * from './semanticRetriever';\nexport * from './temporalRetrievers';\nexport * from './hybridRetriever';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/packlets/retrieve/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nexport * from './retriever';\nexport * from './recencyRetriever';\nexport * from './linkTraversalRetriever';\nexport * from './tagRetriever';\nexport * from './structuredFilterRetriever';\nexport * from './semanticRetriever';\nexport * from './fragmentSemanticRetriever';\nexport * from './temporalRetrievers';\nexport * from './hybridRetriever';\n"]}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Copyright (c) 2026 Erik Fortune
|
|
3
3
|
* SPDX-License-Identifier: MIT
|
|
4
4
|
*/
|
|
5
|
-
import { Hash, Logging, fail, mapResults, succeed } from '@fgv/ts-utils';
|
|
5
|
+
import { Hash, Logging, fail, mapResults, mapSuccess, succeed } from '@fgv/ts-utils';
|
|
6
6
|
import { FileTree } from '@fgv/ts-json-base';
|
|
7
7
|
import { DEFAULT_DEDUP_SCOPE, KnowledgeLwwPolicy, isTemporalIdentityCodec, isTemporalRecord, isVersionCurrent, selectCurrentVersion, selectVersionAsOf } from '../types';
|
|
8
8
|
import { parseMemoryFile, serializeMemoryFile } from '../converters';
|
|
@@ -41,10 +41,24 @@ export class FileTreeMemoryStore {
|
|
|
41
41
|
this._logger = params.logger;
|
|
42
42
|
this._vectorIndex = params.vectorIndex;
|
|
43
43
|
this._embed = params.embed;
|
|
44
|
+
this._fragmentIndex = params.fragmentIndex;
|
|
45
|
+
this._fragmentEmbedder = params.fragmentEmbedder;
|
|
46
|
+
this._skippedRecords = [];
|
|
44
47
|
this._seq = 0;
|
|
45
48
|
this._observationSeq = 0;
|
|
46
49
|
this._writeTail = Promise.resolve();
|
|
47
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Records the initial vault walk could not parse or validate and quarantined
|
|
53
|
+
* (not indexed). Non-empty only when the store was opened with
|
|
54
|
+
* {@link MemoryRecordErrorMode | `onRecordError: 'skip'`} AND at least one
|
|
55
|
+
* record failed to load. Each entry identifies the offending file so a host
|
|
56
|
+
* can repair it; the file itself is never deleted or mutated, so a later open
|
|
57
|
+
* (after the body converter is fixed) re-indexes it.
|
|
58
|
+
*/
|
|
59
|
+
get skippedRecords() {
|
|
60
|
+
return this._skippedRecords;
|
|
61
|
+
}
|
|
48
62
|
/**
|
|
49
63
|
* Family-convention factory. Builds the derived index and a default LWW
|
|
50
64
|
* policy, then performs an initial FileTree walk so an existing vault is
|
|
@@ -52,7 +66,7 @@ export class FileTreeMemoryStore {
|
|
|
52
66
|
*/
|
|
53
67
|
static create(params) {
|
|
54
68
|
return KnowledgeLwwPolicy.create().onSuccess((defaultPolicy) => MemoryIndex.create().onSuccess((index) => {
|
|
55
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
69
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
56
70
|
const store = new FileTreeMemoryStore({
|
|
57
71
|
root: params.root,
|
|
58
72
|
registry: params.registry,
|
|
@@ -67,9 +81,11 @@ export class FileTreeMemoryStore {
|
|
|
67
81
|
observers: (_f = params.observers) !== null && _f !== void 0 ? _f : [],
|
|
68
82
|
logger: (_g = params.logger) !== null && _g !== void 0 ? _g : new Logging.NoOpLogger(),
|
|
69
83
|
vectorIndex: params.vectorIndex,
|
|
70
|
-
embed: params.embed
|
|
84
|
+
embed: params.embed,
|
|
85
|
+
fragmentIndex: params.fragmentIndex,
|
|
86
|
+
fragmentEmbedder: params.fragmentEmbedder
|
|
71
87
|
});
|
|
72
|
-
return store._initialIndex().onSuccess(() => succeed(store));
|
|
88
|
+
return store._initialIndex((_h = params.onRecordError) !== null && _h !== void 0 ? _h : 'fail').onSuccess(() => succeed(store));
|
|
73
89
|
}));
|
|
74
90
|
}
|
|
75
91
|
/** {@inheritDoc IMemoryStore.get} */
|
|
@@ -386,6 +402,7 @@ export class FileTreeMemoryStore {
|
|
|
386
402
|
return this._buildRecord(record, body, existing, policy, hash)
|
|
387
403
|
.onSuccess((built) => succeed(this._stampRank(built)))
|
|
388
404
|
.thenOnSuccess((built) => this._embedOnWrite(built, scope))
|
|
405
|
+
.thenOnSuccess((built) => this._embedFragmentsOnWrite(built, scope))
|
|
389
406
|
.onSuccess((embeddedBuilt) => this._persist(embeddedBuilt, scope, idStem))
|
|
390
407
|
.thenOnSuccess(async (persisted) => {
|
|
391
408
|
// Everything after the authoritative `_persist` commit is best-effort and
|
|
@@ -428,6 +445,45 @@ export class FileTreeMemoryStore {
|
|
|
428
445
|
}
|
|
429
446
|
return succeed({ envelope: Object.assign(Object.assign({}, built.envelope), { embeddingRef: added.value }), body: built.body });
|
|
430
447
|
}
|
|
448
|
+
/**
|
|
449
|
+
* Best-effort fragment-embed-on-write. When a fragment index AND a fragment
|
|
450
|
+
* embedder are wired, chunks + embeds the built record and replaces its
|
|
451
|
+
* fragments in the index (`addFragments` is whole-record-replace, so a re-authored
|
|
452
|
+
* document never leaves stale fragments behind — no explicit remove needed). A
|
|
453
|
+
* failure (returned `fail` OR a thrown/rejected hook) is logged and the record is
|
|
454
|
+
* returned unchanged — the put still persists, and the fragment index is a derived
|
|
455
|
+
* view a later `rebuild` reconciles. Unlike {@link FileTreeMemoryStore._embedOnWrite}
|
|
456
|
+
* it stamps nothing on the record (fragments have no per-record `embeddingRef`
|
|
457
|
+
* analog). A pass-through no-op when unwired (byte-identical record).
|
|
458
|
+
*/
|
|
459
|
+
async _embedFragmentsOnWrite(built, scope) {
|
|
460
|
+
if (this._fragmentIndex === undefined || this._fragmentEmbedder === undefined) {
|
|
461
|
+
return succeed(built);
|
|
462
|
+
}
|
|
463
|
+
const fragmentIndex = this._fragmentIndex;
|
|
464
|
+
const fragmentEmbedder = this._fragmentEmbedder;
|
|
465
|
+
const target = { scope, id: built.envelope.id };
|
|
466
|
+
const embedded = await this._tryVectorOp(() => fragmentEmbedder(built), `fragment embedding '${built.envelope.id}'`);
|
|
467
|
+
if (embedded.isFailure()) {
|
|
468
|
+
return succeed(built);
|
|
469
|
+
}
|
|
470
|
+
await this._tryVectorOp(() => fragmentIndex.addFragments(target, embedded.value), `fragment add for '${built.envelope.id}'`);
|
|
471
|
+
return succeed(built);
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Best-effort fragment removal. A no-op unless the full fragment lifecycle is
|
|
475
|
+
* wired (both an index AND an embedder), so an unwired store does no fragment
|
|
476
|
+
* work and behaves byte-identically. Failures are logged, never surfaced — a
|
|
477
|
+
* committed delete/eviction must not fail because a derived fragment index could
|
|
478
|
+
* not be pruned.
|
|
479
|
+
*/
|
|
480
|
+
async _removeFragmentsBestEffort(target) {
|
|
481
|
+
if (this._fragmentIndex === undefined || this._fragmentEmbedder === undefined) {
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
const fragmentIndex = this._fragmentIndex;
|
|
485
|
+
await this._tryVectorOp(() => fragmentIndex.remove(target), `fragment removal for '${target.id}'`);
|
|
486
|
+
}
|
|
431
487
|
/**
|
|
432
488
|
* Evict the records named by a `cull-oldest` decision, best-effort. Runs only
|
|
433
489
|
* after the authoritative `_persist`, so a failed eviction is logged (never
|
|
@@ -460,6 +516,7 @@ export class FileTreeMemoryStore {
|
|
|
460
516
|
async _removeEvictedVectors(evicted, scope) {
|
|
461
517
|
for (const id of evicted) {
|
|
462
518
|
await this._removeVectorBestEffort({ scope, id });
|
|
519
|
+
await this._removeFragmentsBestEffort({ scope, id });
|
|
463
520
|
}
|
|
464
521
|
}
|
|
465
522
|
/**
|
|
@@ -476,7 +533,7 @@ export class FileTreeMemoryStore {
|
|
|
476
533
|
result = fail(`${label} threw: ${String(err)}`);
|
|
477
534
|
}
|
|
478
535
|
if (result.isFailure()) {
|
|
479
|
-
this._warnSwallowed(`memory: ${label} failed (best-effort;
|
|
536
|
+
this._warnSwallowed(`memory: ${label} failed (best-effort; derived index left for rebuild): ${result.message}`);
|
|
480
537
|
}
|
|
481
538
|
return result;
|
|
482
539
|
}
|
|
@@ -563,6 +620,7 @@ export class FileTreeMemoryStore {
|
|
|
563
620
|
.onSuccess(() => this._index.patch('delete', { scope, record: existing }))
|
|
564
621
|
.thenOnSuccess(async () => {
|
|
565
622
|
await this._removeVectorBestEffort({ scope, id: existing.envelope.id });
|
|
623
|
+
await this._removeFragmentsBestEffort({ scope, id: existing.envelope.id });
|
|
566
624
|
return succeed(existing.envelope.id);
|
|
567
625
|
});
|
|
568
626
|
});
|
|
@@ -649,6 +707,7 @@ export class FileTreeMemoryStore {
|
|
|
649
707
|
.thenOnSuccess((versionStem) => this._buildVersionedRecord(record, body, current, policy, hash, versionStem, validAt, now, seq)
|
|
650
708
|
.onSuccess((built) => succeed(this._stampRank(built)))
|
|
651
709
|
.thenOnSuccess((built) => this._embedOnWrite(built, scope))
|
|
710
|
+
.thenOnSuccess((built) => this._embedFragmentsOnWrite(built, scope))
|
|
652
711
|
.onSuccess((embeddedBuilt) => this._persist(embeddedBuilt, scope, versionStem))
|
|
653
712
|
.onSuccess((persisted) => this._invalidateCurrents(scope, priorCurrents, validAt, now).onSuccess(() => succeed(persisted)))
|
|
654
713
|
.onSuccess((persisted) => succeed({ record: persisted, evicted: [] })));
|
|
@@ -985,9 +1044,13 @@ export class FileTreeMemoryStore {
|
|
|
985
1044
|
/**
|
|
986
1045
|
* Walk the FileTree once and rebuild the index. Also resumes the `seq`
|
|
987
1046
|
* counter past the highest persisted `seq` so new writes stay monotonic.
|
|
1047
|
+
*
|
|
1048
|
+
* In `'skip'` mode each per-record failure is captured structurally on
|
|
1049
|
+
* `this._skippedRecords` (path + scope + path-tagged error) at its failure
|
|
1050
|
+
* site and logged at `warn`; the walk keeps every record that loaded.
|
|
988
1051
|
*/
|
|
989
|
-
_initialIndex() {
|
|
990
|
-
return this._collectEntries(this._root, []).onSuccess((entries) => this._index.rebuild(entries).onSuccess(() => {
|
|
1052
|
+
_initialIndex(onRecordError) {
|
|
1053
|
+
return this._collectEntries(this._root, [], onRecordError).onSuccess((entries) => this._index.rebuild(entries).onSuccess(() => {
|
|
991
1054
|
for (const entry of entries) {
|
|
992
1055
|
if (entry.record.envelope.seq > this._seq) {
|
|
993
1056
|
this._seq = entry.record.envelope.seq;
|
|
@@ -997,11 +1060,11 @@ export class FileTreeMemoryStore {
|
|
|
997
1060
|
}));
|
|
998
1061
|
}
|
|
999
1062
|
/** Recursively collect every `.md` record under `dir` (scope = path segments). */
|
|
1000
|
-
_collectEntries(dir, scopeSegments) {
|
|
1063
|
+
_collectEntries(dir, scopeSegments, onRecordError) {
|
|
1001
1064
|
return dir.getChildren().onSuccess((children) => {
|
|
1002
1065
|
const results = children.map((child) => {
|
|
1003
1066
|
if (child.type === 'directory') {
|
|
1004
|
-
return this._collectEntries(child, [...scopeSegments, child.name]);
|
|
1067
|
+
return this._collectEntries(child, [...scopeSegments, child.name], onRecordError);
|
|
1005
1068
|
}
|
|
1006
1069
|
if (!child.name.endsWith(MEMORY_FILE_EXTENSION) || scopeSegments.length === 0) {
|
|
1007
1070
|
// Skip non-record files and any record-shaped file sitting at the root
|
|
@@ -1009,15 +1072,43 @@ export class FileTreeMemoryStore {
|
|
|
1009
1072
|
return succeed([]);
|
|
1010
1073
|
}
|
|
1011
1074
|
const scope = scopeSegments.join('/');
|
|
1012
|
-
return child
|
|
1013
|
-
.getRawContents()
|
|
1014
|
-
.onSuccess((raw) => parseMemoryFile(raw, this._registry))
|
|
1015
|
-
.onSuccess((parsedRecord) => this._verifyLoaded(scope, child, parsedRecord))
|
|
1016
|
-
.onSuccess((verified) => succeed([{ scope, record: verified }]));
|
|
1075
|
+
return this._loadRecordFile(scope, child, onRecordError);
|
|
1017
1076
|
});
|
|
1077
|
+
// `'skip'` mode: keep every record that parsed, drop the ones that failed
|
|
1078
|
+
// in a single pass (each failure is captured on `this._skippedRecords` and
|
|
1079
|
+
// warn-logged at its site in `_loadRecordFile`). `.orDefault([])` covers
|
|
1080
|
+
// the all-invalid-subtree edge where `mapSuccess` returns Failure because
|
|
1081
|
+
// no element succeeded. `'fail'` mode: `mapResults` fails the whole open on
|
|
1082
|
+
// any bad record — byte-identical to the historical load path.
|
|
1083
|
+
if (onRecordError === 'skip') {
|
|
1084
|
+
return succeed(mapSuccess(results).orDefault([]).flat());
|
|
1085
|
+
}
|
|
1018
1086
|
return mapResults(results).onSuccess((perChild) => succeed(perChild.flat()));
|
|
1019
1087
|
});
|
|
1020
1088
|
}
|
|
1089
|
+
/**
|
|
1090
|
+
* Load and verify one record file. On failure in `'skip'` mode, records the
|
|
1091
|
+
* structured {@link ISkippedRecord} identity (path + scope + path-tagged
|
|
1092
|
+
* error) and logs the skip at `warn`; the failure is still returned so the
|
|
1093
|
+
* caller's `mapSuccess` drops it from the loaded set. In `'fail'` mode the
|
|
1094
|
+
* failure passes through untouched so the historical error is byte-identical.
|
|
1095
|
+
*/
|
|
1096
|
+
_loadRecordFile(scope, child, onRecordError) {
|
|
1097
|
+
return child
|
|
1098
|
+
.getRawContents()
|
|
1099
|
+
.onSuccess((raw) => parseMemoryFile(raw, this._registry))
|
|
1100
|
+
.onSuccess((parsedRecord) => this._verifyLoaded(scope, child, parsedRecord))
|
|
1101
|
+
.onSuccess((verified) => succeed([{ scope, record: verified }]))
|
|
1102
|
+
.onFailure((message) => {
|
|
1103
|
+
if (onRecordError === 'skip') {
|
|
1104
|
+
const path = `${scope}/${child.name}`;
|
|
1105
|
+
const error = `memory record '${path}': ${message}`;
|
|
1106
|
+
this._skippedRecords.push({ path, scope, error });
|
|
1107
|
+
this._warnSwallowed(error);
|
|
1108
|
+
}
|
|
1109
|
+
return fail(message);
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1021
1112
|
}
|
|
1022
1113
|
/**
|
|
1023
1114
|
* The record-level mutable-field vocabulary: maps a declared mutable field
|