@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
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2026 Erik Fortune
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.FragmentSemanticRetriever = exports.FRAGMENT_SEMANTIC_UNWIRED_MESSAGE = void 0;
|
|
8
|
+
const ts_utils_1 = require("@fgv/ts-utils");
|
|
9
|
+
/**
|
|
10
|
+
* The loud-degradation message a {@link FragmentSemanticRetriever} returns when a
|
|
11
|
+
* fragment query is issued but no {@link IFragmentSemanticBackend | backend} is
|
|
12
|
+
* wired — the discovery surface NEVER answers a fragment query with a silent empty.
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
15
|
+
exports.FRAGMENT_SEMANTIC_UNWIRED_MESSAGE = 'fragment recall: no fragment index is wired; wire an IFragmentSemanticBackend to enable sub-document search';
|
|
16
|
+
/**
|
|
17
|
+
* The sub-document semantic-search retriever — the "discovery" half of a
|
|
18
|
+
* search-then-read contract. It embeds a fragment query, queries the
|
|
19
|
+
* {@link IFragmentVectorIndex}, and returns the raw per-fragment
|
|
20
|
+
* {@link IVectorQueryHit | hits} (each carrying a record `target` AND the matched
|
|
21
|
+
* `locator`), NOT resolved records: the consumer re-reads each record and slices it
|
|
22
|
+
* by the locator on its own read side.
|
|
23
|
+
*
|
|
24
|
+
* @remarks
|
|
25
|
+
* Deliberately NOT an {@link IMemoryRetriever}: memory recall is record-granular and
|
|
26
|
+
* returns records; fragment discovery is span-granular and returns locators. Keeping
|
|
27
|
+
* it a distinct surface matches the consumer contract (memory stays record-granular;
|
|
28
|
+
* sub-document knowledge uses a separate fragment index) and avoids overloading the
|
|
29
|
+
* record retriever's return type with a locator that only makes sense here.
|
|
30
|
+
*
|
|
31
|
+
* When no backend is wired, `supportsFragmentRecall` is `false` and any fragment
|
|
32
|
+
* query degrades loudly ({@link FRAGMENT_SEMANTIC_UNWIRED_MESSAGE}) — it NEVER
|
|
33
|
+
* returns a silent empty. A consumer-supplied backend that rejects (throws) is
|
|
34
|
+
* normalized into a `Failure`.
|
|
35
|
+
* @public
|
|
36
|
+
*/
|
|
37
|
+
class FragmentSemanticRetriever {
|
|
38
|
+
constructor(backend) {
|
|
39
|
+
this._backend = backend;
|
|
40
|
+
}
|
|
41
|
+
/** What this retriever can do given its wiring. */
|
|
42
|
+
get capabilities() {
|
|
43
|
+
return { supportsFragmentRecall: this._backend !== undefined };
|
|
44
|
+
}
|
|
45
|
+
/** Family-convention factory. */
|
|
46
|
+
static create(params) {
|
|
47
|
+
return (0, ts_utils_1.succeed)(new FragmentSemanticRetriever(params.backend));
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Embed `query.semantic`, query the fragment index, and return the per-fragment
|
|
51
|
+
* hits in descending score order. Fails loudly when no backend is wired.
|
|
52
|
+
*/
|
|
53
|
+
async retrieve(query) {
|
|
54
|
+
if (this._backend === undefined) {
|
|
55
|
+
return (0, ts_utils_1.fail)(exports.FRAGMENT_SEMANTIC_UNWIRED_MESSAGE);
|
|
56
|
+
}
|
|
57
|
+
const backend = this._backend;
|
|
58
|
+
// Consumer-supplied hooks may throw; normalize both a returned `fail` and a
|
|
59
|
+
// rejection into a single `fragment recall: <label> failed` Failure so
|
|
60
|
+
// `retrieve` always honors its `Promise<Result<...>>` contract.
|
|
61
|
+
const embedded = await FragmentSemanticRetriever._callBackend('query embedding', () => backend.embedQuery(query.semantic));
|
|
62
|
+
if (embedded.isFailure()) {
|
|
63
|
+
return (0, ts_utils_1.fail)(embedded.message);
|
|
64
|
+
}
|
|
65
|
+
return FragmentSemanticRetriever._callBackend('fragment query', () => { var _a; return backend.fragmentIndex.query(embedded.value, (_a = query.topK) !== null && _a !== void 0 ? _a : 10, query.maxPerRecord); });
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Invoke a consumer-supplied backend hook, normalizing both a returned `fail`
|
|
69
|
+
* and a thrown/rejected promise into a single `fragment recall: <label> failed`
|
|
70
|
+
* `Failure`.
|
|
71
|
+
*/
|
|
72
|
+
static async _callBackend(label, op) {
|
|
73
|
+
try {
|
|
74
|
+
return (await op()).withErrorFormat((msg) => `fragment recall: ${label} failed: ${msg}`);
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
return (0, ts_utils_1.fail)(`fragment recall: ${label} failed: ${String(err)}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
exports.FragmentSemanticRetriever = FragmentSemanticRetriever;
|
|
82
|
+
//# 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,4CAAsD;AAItD;;;;;GAKG;AACU,QAAA,iCAAiC,GAC5C,6GAA6G,CAAC;AA0ChH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAa,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,IAAA,kBAAO,EAAC,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,IAAA,eAAI,EAAC,yCAAiC,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,IAAA,eAAI,EAAC,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,IAAA,eAAI,EAAC,oBAAoB,KAAK,YAAY,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAClE,CAAC;IACH,CAAC;CACF;AAvDD,8DAuDC","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"]}
|
|
@@ -4,6 +4,7 @@ export * from './linkTraversalRetriever';
|
|
|
4
4
|
export * from './tagRetriever';
|
|
5
5
|
export * from './structuredFilterRetriever';
|
|
6
6
|
export * from './semanticRetriever';
|
|
7
|
+
export * from './fragmentSemanticRetriever';
|
|
7
8
|
export * from './temporalRetrievers';
|
|
8
9
|
export * from './hybridRetriever';
|
|
9
10
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/packlets/retrieve/index.ts"],"names":[],"mappings":"AAKA,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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/packlets/retrieve/index.ts"],"names":[],"mappings":"AAKA,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"}
|
|
@@ -24,6 +24,7 @@ __exportStar(require("./linkTraversalRetriever"), exports);
|
|
|
24
24
|
__exportStar(require("./tagRetriever"), exports);
|
|
25
25
|
__exportStar(require("./structuredFilterRetriever"), exports);
|
|
26
26
|
__exportStar(require("./semanticRetriever"), exports);
|
|
27
|
+
__exportStar(require("./fragmentSemanticRetriever"), exports);
|
|
27
28
|
__exportStar(require("./temporalRetrievers"), exports);
|
|
28
29
|
__exportStar(require("./hybridRetriever"), exports);
|
|
29
30
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/packlets/retrieve/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;;;;;;;;;;;;;;AAEH,8CAA4B;AAC5B,qDAAmC;AACnC,2DAAyC;AACzC,iDAA+B;AAC/B,8DAA4C;AAC5C,sDAAoC;AACpC,uDAAqC;AACrC,oDAAkC","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,8CAA4B;AAC5B,qDAAmC;AACnC,2DAAyC;AACzC,iDAA+B;AAC/B,8DAA4C;AAC5C,sDAAoC;AACpC,8DAA4C;AAC5C,uDAAqC;AACrC,oDAAkC","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"]}
|
|
@@ -3,7 +3,7 @@ import { FileTree } from '@fgv/ts-json-base';
|
|
|
3
3
|
import { EntityId, IIdentityCodec, IMemoryRecord, IWritePolicy, Kind, MemoryId, MemoryScopeKey, RankProjector, Tag } from '../types';
|
|
4
4
|
import { IBodyConverterRegistry as IRegistry } from '../converters';
|
|
5
5
|
import { IMemoryObserver } from '../observe';
|
|
6
|
-
import { IMemoryRecordSource, IScopedMemoryRecord, IVectorIndex, MemoryEmbedder } from '../vector';
|
|
6
|
+
import { FragmentEmbedder, IFragmentVectorIndex, IMemoryRecordSource, IScopedMemoryRecord, IVectorIndex, MemoryEmbedder } from '../vector';
|
|
7
7
|
/**
|
|
8
8
|
* Filter for {@link IMemoryStore.list}. All present fields are ANDed together.
|
|
9
9
|
* @public
|
|
@@ -22,6 +22,37 @@ export interface IMemoryStoreListFilter {
|
|
|
22
22
|
*/
|
|
23
23
|
readonly asOf?: number;
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Policy for how {@link FileTreeMemoryStore.create}'s initial vault walk reacts
|
|
27
|
+
* to a record that fails to parse or validate.
|
|
28
|
+
*
|
|
29
|
+
* - `'fail'` (the default) — one unreadable record fails the whole open. The
|
|
30
|
+
* walk collapses per-record results with `mapResults`, so any single failure
|
|
31
|
+
* aborts `create()`. This is the historical behavior, preserved byte-for-byte.
|
|
32
|
+
* - `'skip'` — an unreadable record is quarantined (not indexed) rather than
|
|
33
|
+
* failing the open. Every record that DOES parse loads normally; each skip is
|
|
34
|
+
* logged at `warn` and surfaced structurally on
|
|
35
|
+
* {@link FileTreeMemoryStore.skippedRecords}. The offending file is never
|
|
36
|
+
* deleted or mutated, so a later open (after the body converter is fixed)
|
|
37
|
+
* re-indexes it. A vault holds every kind in one store, so a required-field
|
|
38
|
+
* migration on one kind must not make every other record unreadable.
|
|
39
|
+
* @public
|
|
40
|
+
*/
|
|
41
|
+
export type MemoryRecordErrorMode = 'skip' | 'fail';
|
|
42
|
+
/**
|
|
43
|
+
* A record that {@link FileTreeMemoryStore.create} could not load and
|
|
44
|
+
* quarantined (only produced in {@link MemoryRecordErrorMode | `'skip'` mode}).
|
|
45
|
+
* The `path` identifies WHICH record was skipped so a host can repair it.
|
|
46
|
+
* @public
|
|
47
|
+
*/
|
|
48
|
+
export interface ISkippedRecord {
|
|
49
|
+
/** The record file's path within the vault (`<scope>/<filename>.md`). */
|
|
50
|
+
readonly path: string;
|
|
51
|
+
/** The scope the record lives under (its parent directory path). */
|
|
52
|
+
readonly scope: MemoryScopeKey;
|
|
53
|
+
/** The parse/validation failure message (includes the record path). */
|
|
54
|
+
readonly error: string;
|
|
55
|
+
}
|
|
25
56
|
/**
|
|
26
57
|
* The writable, FileTree-backed, content-hash-deduped memory store.
|
|
27
58
|
* @public
|
|
@@ -149,6 +180,41 @@ export interface IFileTreeMemoryStoreCreateParams {
|
|
|
149
180
|
* `rebuild` reconciles, so a vector failure never fails an authoritative write.
|
|
150
181
|
*/
|
|
151
182
|
readonly embed?: MemoryEmbedder;
|
|
183
|
+
/**
|
|
184
|
+
* Optional fragment-granular vector index for sub-document semantic search.
|
|
185
|
+
* Wired together with
|
|
186
|
+
* {@link IFileTreeMemoryStoreCreateParams.fragmentEmbedder | fragmentEmbedder}:
|
|
187
|
+
* when both are present the store chunks + embeds each written record and
|
|
188
|
+
* maintains the fragment index on `put` / `delete` / cap-cull eviction — the
|
|
189
|
+
* "discovery" half of a search-then-read contract, queried through a
|
|
190
|
+
* {@link FragmentSemanticRetriever}. Independent of the record-granular
|
|
191
|
+
* {@link IFileTreeMemoryStoreCreateParams.vectorIndex | vectorIndex} pair: a
|
|
192
|
+
* store may wire record vectors, fragment vectors, both, or neither. Absent (or
|
|
193
|
+
* `fragmentEmbedder` absent) → no fragment work happens and the store behaves
|
|
194
|
+
* byte-identically (the additive, zero-overhead-when-unwired default).
|
|
195
|
+
*/
|
|
196
|
+
readonly fragmentIndex?: IFragmentVectorIndex;
|
|
197
|
+
/**
|
|
198
|
+
* Optional fragment embedder applied to each record on write, wired together
|
|
199
|
+
* with {@link IFileTreeMemoryStoreCreateParams.fragmentIndex | fragmentIndex}.
|
|
200
|
+
* The consumer owns the chunking policy (window size, overlap) and the embedding
|
|
201
|
+
* call; the store stays chunking- and embedder-agnostic. Fragment index
|
|
202
|
+
* maintenance is **best-effort**, exactly like the record-vector path: a failed
|
|
203
|
+
* (or throwing) `fragmentEmbedder` / `addFragments` / `remove` is logged at
|
|
204
|
+
* `warn` and the record operation still succeeds — the fragment index is a
|
|
205
|
+
* derived view a later `rebuild` reconciles.
|
|
206
|
+
*/
|
|
207
|
+
readonly fragmentEmbedder?: FragmentEmbedder;
|
|
208
|
+
/**
|
|
209
|
+
* How the initial vault walk reacts to a record that fails to parse or
|
|
210
|
+
* validate. Defaults to `'fail'` — one bad record fails the whole open, the
|
|
211
|
+
* historical behavior, preserved byte-for-byte. Set `'skip'` to quarantine
|
|
212
|
+
* unreadable records instead: valid records still load, each skip is logged
|
|
213
|
+
* at `warn` and surfaced on {@link FileTreeMemoryStore.skippedRecords}, and
|
|
214
|
+
* the offending file is left untouched for a later (post-fix) re-index. See
|
|
215
|
+
* {@link MemoryRecordErrorMode}.
|
|
216
|
+
*/
|
|
217
|
+
readonly onRecordError?: MemoryRecordErrorMode;
|
|
152
218
|
}
|
|
153
219
|
/**
|
|
154
220
|
* Flat-layout, FileTree-backed {@link IMemoryStore}. The FileTree is the source
|
|
@@ -180,6 +246,13 @@ export declare class FileTreeMemoryStore implements IMemoryStore {
|
|
|
180
246
|
private readonly _logger;
|
|
181
247
|
private readonly _vectorIndex;
|
|
182
248
|
private readonly _embed;
|
|
249
|
+
private readonly _fragmentIndex;
|
|
250
|
+
private readonly _fragmentEmbedder;
|
|
251
|
+
/**
|
|
252
|
+
* Records the initial walk could not load. Populated during `create()` in
|
|
253
|
+
* {@link MemoryRecordErrorMode | `'skip'` mode}; empty otherwise.
|
|
254
|
+
*/
|
|
255
|
+
private readonly _skippedRecords;
|
|
183
256
|
/** Monotonic write counter; incremented inside the write-lock on each put. */
|
|
184
257
|
private _seq;
|
|
185
258
|
/**
|
|
@@ -200,6 +273,15 @@ export declare class FileTreeMemoryStore implements IMemoryStore {
|
|
|
200
273
|
*/
|
|
201
274
|
private static readonly _mutableFieldAccessors;
|
|
202
275
|
private constructor();
|
|
276
|
+
/**
|
|
277
|
+
* Records the initial vault walk could not parse or validate and quarantined
|
|
278
|
+
* (not indexed). Non-empty only when the store was opened with
|
|
279
|
+
* {@link MemoryRecordErrorMode | `onRecordError: 'skip'`} AND at least one
|
|
280
|
+
* record failed to load. Each entry identifies the offending file so a host
|
|
281
|
+
* can repair it; the file itself is never deleted or mutated, so a later open
|
|
282
|
+
* (after the body converter is fixed) re-indexes it.
|
|
283
|
+
*/
|
|
284
|
+
get skippedRecords(): ReadonlyArray<ISkippedRecord>;
|
|
203
285
|
/**
|
|
204
286
|
* Family-convention factory. Builds the derived index and a default LWW
|
|
205
287
|
* policy, then performs an initial FileTree walk so an existing vault is
|
|
@@ -287,6 +369,26 @@ export declare class FileTreeMemoryStore implements IMemoryStore {
|
|
|
287
369
|
* failure).
|
|
288
370
|
*/
|
|
289
371
|
private _embedOnWrite;
|
|
372
|
+
/**
|
|
373
|
+
* Best-effort fragment-embed-on-write. When a fragment index AND a fragment
|
|
374
|
+
* embedder are wired, chunks + embeds the built record and replaces its
|
|
375
|
+
* fragments in the index (`addFragments` is whole-record-replace, so a re-authored
|
|
376
|
+
* document never leaves stale fragments behind — no explicit remove needed). A
|
|
377
|
+
* failure (returned `fail` OR a thrown/rejected hook) is logged and the record is
|
|
378
|
+
* returned unchanged — the put still persists, and the fragment index is a derived
|
|
379
|
+
* view a later `rebuild` reconciles. Unlike {@link FileTreeMemoryStore._embedOnWrite}
|
|
380
|
+
* it stamps nothing on the record (fragments have no per-record `embeddingRef`
|
|
381
|
+
* analog). A pass-through no-op when unwired (byte-identical record).
|
|
382
|
+
*/
|
|
383
|
+
private _embedFragmentsOnWrite;
|
|
384
|
+
/**
|
|
385
|
+
* Best-effort fragment removal. A no-op unless the full fragment lifecycle is
|
|
386
|
+
* wired (both an index AND an embedder), so an unwired store does no fragment
|
|
387
|
+
* work and behaves byte-identically. Failures are logged, never surfaced — a
|
|
388
|
+
* committed delete/eviction must not fail because a derived fragment index could
|
|
389
|
+
* not be pruned.
|
|
390
|
+
*/
|
|
391
|
+
private _removeFragmentsBestEffort;
|
|
290
392
|
/**
|
|
291
393
|
* Evict the records named by a `cull-oldest` decision, best-effort. Runs only
|
|
292
394
|
* after the authoritative `_persist`, so a failed eviction is logged (never
|
|
@@ -482,9 +584,21 @@ export declare class FileTreeMemoryStore implements IMemoryStore {
|
|
|
482
584
|
/**
|
|
483
585
|
* Walk the FileTree once and rebuild the index. Also resumes the `seq`
|
|
484
586
|
* counter past the highest persisted `seq` so new writes stay monotonic.
|
|
587
|
+
*
|
|
588
|
+
* In `'skip'` mode each per-record failure is captured structurally on
|
|
589
|
+
* `this._skippedRecords` (path + scope + path-tagged error) at its failure
|
|
590
|
+
* site and logged at `warn`; the walk keeps every record that loaded.
|
|
485
591
|
*/
|
|
486
592
|
private _initialIndex;
|
|
487
593
|
/** Recursively collect every `.md` record under `dir` (scope = path segments). */
|
|
488
594
|
private _collectEntries;
|
|
595
|
+
/**
|
|
596
|
+
* Load and verify one record file. On failure in `'skip'` mode, records the
|
|
597
|
+
* structured {@link ISkippedRecord} identity (path + scope + path-tagged
|
|
598
|
+
* error) and logs the skip at `warn`; the failure is still returned so the
|
|
599
|
+
* caller's `mapSuccess` drops it from the loaded set. In `'fail'` mode the
|
|
600
|
+
* failure passes through untouched so the historical error is byte-identical.
|
|
601
|
+
*/
|
|
602
|
+
private _loadRecordFile;
|
|
489
603
|
}
|
|
490
604
|
//# sourceMappingURL=fileTreeMemoryStore.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fileTreeMemoryStore.d.ts","sourceRoot":"","sources":["../../../src/packlets/store/fileTreeMemoryStore.ts"],"names":[],"mappings":"AAKA,OAAO,EAAQ,OAAO,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"fileTreeMemoryStore.d.ts","sourceRoot":"","sources":["../../../src/packlets/store/fileTreeMemoryStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -44,10 +44,24 @@ class FileTreeMemoryStore {
|
|
|
44
44
|
this._logger = params.logger;
|
|
45
45
|
this._vectorIndex = params.vectorIndex;
|
|
46
46
|
this._embed = params.embed;
|
|
47
|
+
this._fragmentIndex = params.fragmentIndex;
|
|
48
|
+
this._fragmentEmbedder = params.fragmentEmbedder;
|
|
49
|
+
this._skippedRecords = [];
|
|
47
50
|
this._seq = 0;
|
|
48
51
|
this._observationSeq = 0;
|
|
49
52
|
this._writeTail = Promise.resolve();
|
|
50
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Records the initial vault walk could not parse or validate and quarantined
|
|
56
|
+
* (not indexed). Non-empty only when the store was opened with
|
|
57
|
+
* {@link MemoryRecordErrorMode | `onRecordError: 'skip'`} AND at least one
|
|
58
|
+
* record failed to load. Each entry identifies the offending file so a host
|
|
59
|
+
* can repair it; the file itself is never deleted or mutated, so a later open
|
|
60
|
+
* (after the body converter is fixed) re-indexes it.
|
|
61
|
+
*/
|
|
62
|
+
get skippedRecords() {
|
|
63
|
+
return this._skippedRecords;
|
|
64
|
+
}
|
|
51
65
|
/**
|
|
52
66
|
* Family-convention factory. Builds the derived index and a default LWW
|
|
53
67
|
* policy, then performs an initial FileTree walk so an existing vault is
|
|
@@ -55,7 +69,7 @@ class FileTreeMemoryStore {
|
|
|
55
69
|
*/
|
|
56
70
|
static create(params) {
|
|
57
71
|
return types_1.KnowledgeLwwPolicy.create().onSuccess((defaultPolicy) => index_1.MemoryIndex.create().onSuccess((index) => {
|
|
58
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
72
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
59
73
|
const store = new FileTreeMemoryStore({
|
|
60
74
|
root: params.root,
|
|
61
75
|
registry: params.registry,
|
|
@@ -70,9 +84,11 @@ class FileTreeMemoryStore {
|
|
|
70
84
|
observers: (_f = params.observers) !== null && _f !== void 0 ? _f : [],
|
|
71
85
|
logger: (_g = params.logger) !== null && _g !== void 0 ? _g : new ts_utils_1.Logging.NoOpLogger(),
|
|
72
86
|
vectorIndex: params.vectorIndex,
|
|
73
|
-
embed: params.embed
|
|
87
|
+
embed: params.embed,
|
|
88
|
+
fragmentIndex: params.fragmentIndex,
|
|
89
|
+
fragmentEmbedder: params.fragmentEmbedder
|
|
74
90
|
});
|
|
75
|
-
return store._initialIndex().onSuccess(() => (0, ts_utils_1.succeed)(store));
|
|
91
|
+
return store._initialIndex((_h = params.onRecordError) !== null && _h !== void 0 ? _h : 'fail').onSuccess(() => (0, ts_utils_1.succeed)(store));
|
|
76
92
|
}));
|
|
77
93
|
}
|
|
78
94
|
/** {@inheritDoc IMemoryStore.get} */
|
|
@@ -389,6 +405,7 @@ class FileTreeMemoryStore {
|
|
|
389
405
|
return this._buildRecord(record, body, existing, policy, hash)
|
|
390
406
|
.onSuccess((built) => (0, ts_utils_1.succeed)(this._stampRank(built)))
|
|
391
407
|
.thenOnSuccess((built) => this._embedOnWrite(built, scope))
|
|
408
|
+
.thenOnSuccess((built) => this._embedFragmentsOnWrite(built, scope))
|
|
392
409
|
.onSuccess((embeddedBuilt) => this._persist(embeddedBuilt, scope, idStem))
|
|
393
410
|
.thenOnSuccess(async (persisted) => {
|
|
394
411
|
// Everything after the authoritative `_persist` commit is best-effort and
|
|
@@ -431,6 +448,45 @@ class FileTreeMemoryStore {
|
|
|
431
448
|
}
|
|
432
449
|
return (0, ts_utils_1.succeed)({ envelope: Object.assign(Object.assign({}, built.envelope), { embeddingRef: added.value }), body: built.body });
|
|
433
450
|
}
|
|
451
|
+
/**
|
|
452
|
+
* Best-effort fragment-embed-on-write. When a fragment index AND a fragment
|
|
453
|
+
* embedder are wired, chunks + embeds the built record and replaces its
|
|
454
|
+
* fragments in the index (`addFragments` is whole-record-replace, so a re-authored
|
|
455
|
+
* document never leaves stale fragments behind — no explicit remove needed). A
|
|
456
|
+
* failure (returned `fail` OR a thrown/rejected hook) is logged and the record is
|
|
457
|
+
* returned unchanged — the put still persists, and the fragment index is a derived
|
|
458
|
+
* view a later `rebuild` reconciles. Unlike {@link FileTreeMemoryStore._embedOnWrite}
|
|
459
|
+
* it stamps nothing on the record (fragments have no per-record `embeddingRef`
|
|
460
|
+
* analog). A pass-through no-op when unwired (byte-identical record).
|
|
461
|
+
*/
|
|
462
|
+
async _embedFragmentsOnWrite(built, scope) {
|
|
463
|
+
if (this._fragmentIndex === undefined || this._fragmentEmbedder === undefined) {
|
|
464
|
+
return (0, ts_utils_1.succeed)(built);
|
|
465
|
+
}
|
|
466
|
+
const fragmentIndex = this._fragmentIndex;
|
|
467
|
+
const fragmentEmbedder = this._fragmentEmbedder;
|
|
468
|
+
const target = { scope, id: built.envelope.id };
|
|
469
|
+
const embedded = await this._tryVectorOp(() => fragmentEmbedder(built), `fragment embedding '${built.envelope.id}'`);
|
|
470
|
+
if (embedded.isFailure()) {
|
|
471
|
+
return (0, ts_utils_1.succeed)(built);
|
|
472
|
+
}
|
|
473
|
+
await this._tryVectorOp(() => fragmentIndex.addFragments(target, embedded.value), `fragment add for '${built.envelope.id}'`);
|
|
474
|
+
return (0, ts_utils_1.succeed)(built);
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Best-effort fragment removal. A no-op unless the full fragment lifecycle is
|
|
478
|
+
* wired (both an index AND an embedder), so an unwired store does no fragment
|
|
479
|
+
* work and behaves byte-identically. Failures are logged, never surfaced — a
|
|
480
|
+
* committed delete/eviction must not fail because a derived fragment index could
|
|
481
|
+
* not be pruned.
|
|
482
|
+
*/
|
|
483
|
+
async _removeFragmentsBestEffort(target) {
|
|
484
|
+
if (this._fragmentIndex === undefined || this._fragmentEmbedder === undefined) {
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
const fragmentIndex = this._fragmentIndex;
|
|
488
|
+
await this._tryVectorOp(() => fragmentIndex.remove(target), `fragment removal for '${target.id}'`);
|
|
489
|
+
}
|
|
434
490
|
/**
|
|
435
491
|
* Evict the records named by a `cull-oldest` decision, best-effort. Runs only
|
|
436
492
|
* after the authoritative `_persist`, so a failed eviction is logged (never
|
|
@@ -463,6 +519,7 @@ class FileTreeMemoryStore {
|
|
|
463
519
|
async _removeEvictedVectors(evicted, scope) {
|
|
464
520
|
for (const id of evicted) {
|
|
465
521
|
await this._removeVectorBestEffort({ scope, id });
|
|
522
|
+
await this._removeFragmentsBestEffort({ scope, id });
|
|
466
523
|
}
|
|
467
524
|
}
|
|
468
525
|
/**
|
|
@@ -479,7 +536,7 @@ class FileTreeMemoryStore {
|
|
|
479
536
|
result = (0, ts_utils_1.fail)(`${label} threw: ${String(err)}`);
|
|
480
537
|
}
|
|
481
538
|
if (result.isFailure()) {
|
|
482
|
-
this._warnSwallowed(`memory: ${label} failed (best-effort;
|
|
539
|
+
this._warnSwallowed(`memory: ${label} failed (best-effort; derived index left for rebuild): ${result.message}`);
|
|
483
540
|
}
|
|
484
541
|
return result;
|
|
485
542
|
}
|
|
@@ -566,6 +623,7 @@ class FileTreeMemoryStore {
|
|
|
566
623
|
.onSuccess(() => this._index.patch('delete', { scope, record: existing }))
|
|
567
624
|
.thenOnSuccess(async () => {
|
|
568
625
|
await this._removeVectorBestEffort({ scope, id: existing.envelope.id });
|
|
626
|
+
await this._removeFragmentsBestEffort({ scope, id: existing.envelope.id });
|
|
569
627
|
return (0, ts_utils_1.succeed)(existing.envelope.id);
|
|
570
628
|
});
|
|
571
629
|
});
|
|
@@ -652,6 +710,7 @@ class FileTreeMemoryStore {
|
|
|
652
710
|
.thenOnSuccess((versionStem) => this._buildVersionedRecord(record, body, current, policy, hash, versionStem, validAt, now, seq)
|
|
653
711
|
.onSuccess((built) => (0, ts_utils_1.succeed)(this._stampRank(built)))
|
|
654
712
|
.thenOnSuccess((built) => this._embedOnWrite(built, scope))
|
|
713
|
+
.thenOnSuccess((built) => this._embedFragmentsOnWrite(built, scope))
|
|
655
714
|
.onSuccess((embeddedBuilt) => this._persist(embeddedBuilt, scope, versionStem))
|
|
656
715
|
.onSuccess((persisted) => this._invalidateCurrents(scope, priorCurrents, validAt, now).onSuccess(() => (0, ts_utils_1.succeed)(persisted)))
|
|
657
716
|
.onSuccess((persisted) => (0, ts_utils_1.succeed)({ record: persisted, evicted: [] })));
|
|
@@ -988,9 +1047,13 @@ class FileTreeMemoryStore {
|
|
|
988
1047
|
/**
|
|
989
1048
|
* Walk the FileTree once and rebuild the index. Also resumes the `seq`
|
|
990
1049
|
* counter past the highest persisted `seq` so new writes stay monotonic.
|
|
1050
|
+
*
|
|
1051
|
+
* In `'skip'` mode each per-record failure is captured structurally on
|
|
1052
|
+
* `this._skippedRecords` (path + scope + path-tagged error) at its failure
|
|
1053
|
+
* site and logged at `warn`; the walk keeps every record that loaded.
|
|
991
1054
|
*/
|
|
992
|
-
_initialIndex() {
|
|
993
|
-
return this._collectEntries(this._root, []).onSuccess((entries) => this._index.rebuild(entries).onSuccess(() => {
|
|
1055
|
+
_initialIndex(onRecordError) {
|
|
1056
|
+
return this._collectEntries(this._root, [], onRecordError).onSuccess((entries) => this._index.rebuild(entries).onSuccess(() => {
|
|
994
1057
|
for (const entry of entries) {
|
|
995
1058
|
if (entry.record.envelope.seq > this._seq) {
|
|
996
1059
|
this._seq = entry.record.envelope.seq;
|
|
@@ -1000,11 +1063,11 @@ class FileTreeMemoryStore {
|
|
|
1000
1063
|
}));
|
|
1001
1064
|
}
|
|
1002
1065
|
/** Recursively collect every `.md` record under `dir` (scope = path segments). */
|
|
1003
|
-
_collectEntries(dir, scopeSegments) {
|
|
1066
|
+
_collectEntries(dir, scopeSegments, onRecordError) {
|
|
1004
1067
|
return dir.getChildren().onSuccess((children) => {
|
|
1005
1068
|
const results = children.map((child) => {
|
|
1006
1069
|
if (child.type === 'directory') {
|
|
1007
|
-
return this._collectEntries(child, [...scopeSegments, child.name]);
|
|
1070
|
+
return this._collectEntries(child, [...scopeSegments, child.name], onRecordError);
|
|
1008
1071
|
}
|
|
1009
1072
|
if (!child.name.endsWith(MEMORY_FILE_EXTENSION) || scopeSegments.length === 0) {
|
|
1010
1073
|
// Skip non-record files and any record-shaped file sitting at the root
|
|
@@ -1012,15 +1075,43 @@ class FileTreeMemoryStore {
|
|
|
1012
1075
|
return (0, ts_utils_1.succeed)([]);
|
|
1013
1076
|
}
|
|
1014
1077
|
const scope = scopeSegments.join('/');
|
|
1015
|
-
return child
|
|
1016
|
-
.getRawContents()
|
|
1017
|
-
.onSuccess((raw) => (0, converters_1.parseMemoryFile)(raw, this._registry))
|
|
1018
|
-
.onSuccess((parsedRecord) => this._verifyLoaded(scope, child, parsedRecord))
|
|
1019
|
-
.onSuccess((verified) => (0, ts_utils_1.succeed)([{ scope, record: verified }]));
|
|
1078
|
+
return this._loadRecordFile(scope, child, onRecordError);
|
|
1020
1079
|
});
|
|
1080
|
+
// `'skip'` mode: keep every record that parsed, drop the ones that failed
|
|
1081
|
+
// in a single pass (each failure is captured on `this._skippedRecords` and
|
|
1082
|
+
// warn-logged at its site in `_loadRecordFile`). `.orDefault([])` covers
|
|
1083
|
+
// the all-invalid-subtree edge where `mapSuccess` returns Failure because
|
|
1084
|
+
// no element succeeded. `'fail'` mode: `mapResults` fails the whole open on
|
|
1085
|
+
// any bad record — byte-identical to the historical load path.
|
|
1086
|
+
if (onRecordError === 'skip') {
|
|
1087
|
+
return (0, ts_utils_1.succeed)((0, ts_utils_1.mapSuccess)(results).orDefault([]).flat());
|
|
1088
|
+
}
|
|
1021
1089
|
return (0, ts_utils_1.mapResults)(results).onSuccess((perChild) => (0, ts_utils_1.succeed)(perChild.flat()));
|
|
1022
1090
|
});
|
|
1023
1091
|
}
|
|
1092
|
+
/**
|
|
1093
|
+
* Load and verify one record file. On failure in `'skip'` mode, records the
|
|
1094
|
+
* structured {@link ISkippedRecord} identity (path + scope + path-tagged
|
|
1095
|
+
* error) and logs the skip at `warn`; the failure is still returned so the
|
|
1096
|
+
* caller's `mapSuccess` drops it from the loaded set. In `'fail'` mode the
|
|
1097
|
+
* failure passes through untouched so the historical error is byte-identical.
|
|
1098
|
+
*/
|
|
1099
|
+
_loadRecordFile(scope, child, onRecordError) {
|
|
1100
|
+
return child
|
|
1101
|
+
.getRawContents()
|
|
1102
|
+
.onSuccess((raw) => (0, converters_1.parseMemoryFile)(raw, this._registry))
|
|
1103
|
+
.onSuccess((parsedRecord) => this._verifyLoaded(scope, child, parsedRecord))
|
|
1104
|
+
.onSuccess((verified) => (0, ts_utils_1.succeed)([{ scope, record: verified }]))
|
|
1105
|
+
.onFailure((message) => {
|
|
1106
|
+
if (onRecordError === 'skip') {
|
|
1107
|
+
const path = `${scope}/${child.name}`;
|
|
1108
|
+
const error = `memory record '${path}': ${message}`;
|
|
1109
|
+
this._skippedRecords.push({ path, scope, error });
|
|
1110
|
+
this._warnSwallowed(error);
|
|
1111
|
+
}
|
|
1112
|
+
return (0, ts_utils_1.fail)(message);
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1024
1115
|
}
|
|
1025
1116
|
exports.FileTreeMemoryStore = FileTreeMemoryStore;
|
|
1026
1117
|
/**
|