@fgv/ts-agent-memory 5.1.0-36
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/chunked-rush-logs/ts-agent-memory.build.chunks.jsonl +9 -0
- package/.rush/temp/f6a88bfdd66517ccb98c2c7ae1be6e6fe9e15d38.tar.log +282 -0
- package/.rush/temp/operation/build/all.log +9 -0
- package/.rush/temp/operation/build/log-chunks.jsonl +9 -0
- package/.rush/temp/operation/build/state.json +3 -0
- package/.rush/temp/shrinkwrap-deps.json +688 -0
- package/LICENSE +21 -0
- package/README.md +45 -0
- package/config/api-extractor.json +343 -0
- package/config/jest.config.json +14 -0
- package/config/rig.json +4 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/packlets/converters/bodyConverterRegistry.js +51 -0
- package/dist/packlets/converters/bodyConverterRegistry.js.map +1 -0
- package/dist/packlets/converters/envelopeConverter.js +159 -0
- package/dist/packlets/converters/envelopeConverter.js.map +1 -0
- package/dist/packlets/converters/index.js +7 -0
- package/dist/packlets/converters/index.js.map +1 -0
- package/dist/packlets/index/index.js +6 -0
- package/dist/packlets/index/index.js.map +1 -0
- package/dist/packlets/index/memoryIndex.js +170 -0
- package/dist/packlets/index/memoryIndex.js.map +1 -0
- package/dist/packlets/observe/index.js +7 -0
- package/dist/packlets/observe/index.js.map +1 -0
- package/dist/packlets/observe/memoryObservationStore.js +119 -0
- package/dist/packlets/observe/memoryObservationStore.js.map +1 -0
- package/dist/packlets/observe/observer.js +6 -0
- package/dist/packlets/observe/observer.js.map +1 -0
- package/dist/packlets/retrieve/hybridRetriever.js +135 -0
- package/dist/packlets/retrieve/hybridRetriever.js.map +1 -0
- package/dist/packlets/retrieve/index.js +12 -0
- package/dist/packlets/retrieve/index.js.map +1 -0
- package/dist/packlets/retrieve/linkTraversalRetriever.js +143 -0
- package/dist/packlets/retrieve/linkTraversalRetriever.js.map +1 -0
- package/dist/packlets/retrieve/recencyRetriever.js +33 -0
- package/dist/packlets/retrieve/recencyRetriever.js.map +1 -0
- package/dist/packlets/retrieve/retriever.js +110 -0
- package/dist/packlets/retrieve/retriever.js.map +1 -0
- package/dist/packlets/retrieve/semanticRetriever.js +86 -0
- package/dist/packlets/retrieve/semanticRetriever.js.map +1 -0
- package/dist/packlets/retrieve/structuredFilterRetriever.js +37 -0
- package/dist/packlets/retrieve/structuredFilterRetriever.js.map +1 -0
- package/dist/packlets/retrieve/tagRetriever.js +37 -0
- package/dist/packlets/retrieve/tagRetriever.js.map +1 -0
- package/dist/packlets/store/fileTreeMemoryStore.js +698 -0
- package/dist/packlets/store/fileTreeMemoryStore.js.map +1 -0
- package/dist/packlets/store/index.js +7 -0
- package/dist/packlets/store/index.js.map +1 -0
- package/dist/packlets/store/scopeEncoding.js +31 -0
- package/dist/packlets/store/scopeEncoding.js.map +1 -0
- package/dist/packlets/types/envelope.js +6 -0
- package/dist/packlets/types/envelope.js.map +1 -0
- package/dist/packlets/types/filenameSafety.js +52 -0
- package/dist/packlets/types/filenameSafety.js.map +1 -0
- package/dist/packlets/types/identityCodec.js +184 -0
- package/dist/packlets/types/identityCodec.js.map +1 -0
- package/dist/packlets/types/ids.js +67 -0
- package/dist/packlets/types/ids.js.map +1 -0
- package/dist/packlets/types/index.js +10 -0
- package/dist/packlets/types/index.js.map +1 -0
- package/dist/packlets/types/writePolicy.js +263 -0
- package/dist/packlets/types/writePolicy.js.map +1 -0
- package/dist/packlets/vector/inMemoryCosineIndex.js +150 -0
- package/dist/packlets/vector/inMemoryCosineIndex.js.map +1 -0
- package/dist/packlets/vector/index.js +7 -0
- package/dist/packlets/vector/index.js.map +1 -0
- package/dist/packlets/vector/vectorIndex.js +6 -0
- package/dist/packlets/vector/vectorIndex.js.map +1 -0
- package/dist/test/unit/converters/bodyConverterRegistry.test.js +72 -0
- package/dist/test/unit/converters/bodyConverterRegistry.test.js.map +1 -0
- package/dist/test/unit/converters/envelopeConverter.test.js +196 -0
- package/dist/test/unit/converters/envelopeConverter.test.js.map +1 -0
- package/dist/test/unit/index/memoryIndex.test.js +152 -0
- package/dist/test/unit/index/memoryIndex.test.js.map +1 -0
- package/dist/test/unit/observe/memoryObservationStore.test.js +118 -0
- package/dist/test/unit/observe/memoryObservationStore.test.js.map +1 -0
- package/dist/test/unit/retrieve/linkTraversalRetriever.test.js +182 -0
- package/dist/test/unit/retrieve/linkTraversalRetriever.test.js.map +1 -0
- package/dist/test/unit/retrieve/retrievers.test.js +506 -0
- package/dist/test/unit/retrieve/retrievers.test.js.map +1 -0
- package/dist/test/unit/store/embedOnWrite.test.js +260 -0
- package/dist/test/unit/store/embedOnWrite.test.js.map +1 -0
- package/dist/test/unit/store/fileTreeMemoryStore.test.js +647 -0
- package/dist/test/unit/store/fileTreeMemoryStore.test.js.map +1 -0
- package/dist/test/unit/store/observations.test.js +239 -0
- package/dist/test/unit/store/observations.test.js.map +1 -0
- package/dist/test/unit/store/scopeEncoding.test.js +24 -0
- package/dist/test/unit/store/scopeEncoding.test.js.map +1 -0
- package/dist/test/unit/types/identityCodec.test.js +187 -0
- package/dist/test/unit/types/identityCodec.test.js.map +1 -0
- package/dist/test/unit/types/ids.test.js +84 -0
- package/dist/test/unit/types/ids.test.js.map +1 -0
- package/dist/test/unit/types/writePolicy.test.js +241 -0
- package/dist/test/unit/types/writePolicy.test.js.map +1 -0
- package/dist/test/unit/vector/inMemoryCosineIndex.test.js +192 -0
- package/dist/test/unit/vector/inMemoryCosineIndex.test.js.map +1 -0
- package/dist/test/unit/vector/vectorIndex.test.js +42 -0
- package/dist/test/unit/vector/vectorIndex.test.js.map +1 -0
- package/dist/ts-agent-memory.d.ts +1901 -0
- package/dist/tsdoc-metadata.json +11 -0
- package/eslint.config.js +15 -0
- package/etc/ts-agent-memory.api.md +525 -0
- package/lib/index.d.ts +8 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +28 -0
- package/lib/index.js.map +1 -0
- package/lib/packlets/converters/bodyConverterRegistry.d.ts +64 -0
- package/lib/packlets/converters/bodyConverterRegistry.d.ts.map +1 -0
- package/lib/packlets/converters/bodyConverterRegistry.js +55 -0
- package/lib/packlets/converters/bodyConverterRegistry.js.map +1 -0
- package/lib/packlets/converters/envelopeConverter.d.ts +70 -0
- package/lib/packlets/converters/envelopeConverter.d.ts.map +1 -0
- package/lib/packlets/converters/envelopeConverter.js +166 -0
- package/lib/packlets/converters/envelopeConverter.js.map +1 -0
- package/lib/packlets/converters/index.d.ts +3 -0
- package/lib/packlets/converters/index.d.ts.map +1 -0
- package/lib/packlets/converters/index.js +23 -0
- package/lib/packlets/converters/index.js.map +1 -0
- package/lib/packlets/index/index.d.ts +2 -0
- package/lib/packlets/index/index.d.ts.map +1 -0
- package/lib/packlets/index/index.js +22 -0
- package/lib/packlets/index/index.js.map +1 -0
- package/lib/packlets/index/memoryIndex.d.ts +127 -0
- package/lib/packlets/index/memoryIndex.d.ts.map +1 -0
- package/lib/packlets/index/memoryIndex.js +174 -0
- package/lib/packlets/index/memoryIndex.js.map +1 -0
- package/lib/packlets/observe/index.d.ts +3 -0
- package/lib/packlets/observe/index.d.ts.map +1 -0
- package/lib/packlets/observe/index.js +23 -0
- package/lib/packlets/observe/index.js.map +1 -0
- package/lib/packlets/observe/memoryObservationStore.d.ts +91 -0
- package/lib/packlets/observe/memoryObservationStore.d.ts.map +1 -0
- package/lib/packlets/observe/memoryObservationStore.js +123 -0
- package/lib/packlets/observe/memoryObservationStore.js.map +1 -0
- package/lib/packlets/observe/observer.d.ts +110 -0
- package/lib/packlets/observe/observer.d.ts.map +1 -0
- package/lib/packlets/observe/observer.js +7 -0
- package/lib/packlets/observe/observer.js.map +1 -0
- package/lib/packlets/retrieve/hybridRetriever.d.ts +79 -0
- package/lib/packlets/retrieve/hybridRetriever.d.ts.map +1 -0
- package/lib/packlets/retrieve/hybridRetriever.js +140 -0
- package/lib/packlets/retrieve/hybridRetriever.js.map +1 -0
- package/lib/packlets/retrieve/index.d.ts +8 -0
- package/lib/packlets/retrieve/index.d.ts.map +1 -0
- package/lib/packlets/retrieve/index.js +28 -0
- package/lib/packlets/retrieve/index.js.map +1 -0
- package/lib/packlets/retrieve/linkTraversalRetriever.d.ts +61 -0
- package/lib/packlets/retrieve/linkTraversalRetriever.d.ts.map +1 -0
- package/lib/packlets/retrieve/linkTraversalRetriever.js +147 -0
- package/lib/packlets/retrieve/linkTraversalRetriever.js.map +1 -0
- package/lib/packlets/retrieve/recencyRetriever.d.ts +21 -0
- package/lib/packlets/retrieve/recencyRetriever.d.ts.map +1 -0
- package/lib/packlets/retrieve/recencyRetriever.js +37 -0
- package/lib/packlets/retrieve/recencyRetriever.js.map +1 -0
- package/lib/packlets/retrieve/retriever.d.ts +133 -0
- package/lib/packlets/retrieve/retriever.d.ts.map +1 -0
- package/lib/packlets/retrieve/retriever.js +119 -0
- package/lib/packlets/retrieve/retriever.js.map +1 -0
- package/lib/packlets/retrieve/semanticRetriever.d.ts +69 -0
- package/lib/packlets/retrieve/semanticRetriever.d.ts.map +1 -0
- package/lib/packlets/retrieve/semanticRetriever.js +90 -0
- package/lib/packlets/retrieve/semanticRetriever.js.map +1 -0
- package/lib/packlets/retrieve/structuredFilterRetriever.d.ts +22 -0
- package/lib/packlets/retrieve/structuredFilterRetriever.d.ts.map +1 -0
- package/lib/packlets/retrieve/structuredFilterRetriever.js +41 -0
- package/lib/packlets/retrieve/structuredFilterRetriever.js.map +1 -0
- package/lib/packlets/retrieve/tagRetriever.d.ts +22 -0
- package/lib/packlets/retrieve/tagRetriever.d.ts.map +1 -0
- package/lib/packlets/retrieve/tagRetriever.js +41 -0
- package/lib/packlets/retrieve/tagRetriever.js.map +1 -0
- package/lib/packlets/store/fileTreeMemoryStore.d.ts +327 -0
- package/lib/packlets/store/fileTreeMemoryStore.d.ts.map +1 -0
- package/lib/packlets/store/fileTreeMemoryStore.js +702 -0
- package/lib/packlets/store/fileTreeMemoryStore.js.map +1 -0
- package/lib/packlets/store/index.d.ts +3 -0
- package/lib/packlets/store/index.d.ts.map +1 -0
- package/lib/packlets/store/index.js +23 -0
- package/lib/packlets/store/index.js.map +1 -0
- package/lib/packlets/store/scopeEncoding.d.ts +19 -0
- package/lib/packlets/store/scopeEncoding.d.ts.map +1 -0
- package/lib/packlets/store/scopeEncoding.js +34 -0
- package/lib/packlets/store/scopeEncoding.js.map +1 -0
- package/lib/packlets/types/envelope.d.ts +119 -0
- package/lib/packlets/types/envelope.d.ts.map +1 -0
- package/lib/packlets/types/envelope.js +7 -0
- package/lib/packlets/types/envelope.js.map +1 -0
- package/lib/packlets/types/filenameSafety.d.ts +16 -0
- package/lib/packlets/types/filenameSafety.d.ts.map +1 -0
- package/lib/packlets/types/filenameSafety.js +55 -0
- package/lib/packlets/types/filenameSafety.js.map +1 -0
- package/lib/packlets/types/identityCodec.d.ts +136 -0
- package/lib/packlets/types/identityCodec.d.ts.map +1 -0
- package/lib/packlets/types/identityCodec.js +190 -0
- package/lib/packlets/types/identityCodec.js.map +1 -0
- package/lib/packlets/types/ids.d.ts +55 -0
- package/lib/packlets/types/ids.d.ts.map +1 -0
- package/lib/packlets/types/ids.js +70 -0
- package/lib/packlets/types/ids.js.map +1 -0
- package/lib/packlets/types/index.d.ts +6 -0
- package/lib/packlets/types/index.d.ts.map +1 -0
- package/lib/packlets/types/index.js +26 -0
- package/lib/packlets/types/index.js.map +1 -0
- package/lib/packlets/types/writePolicy.d.ts +213 -0
- package/lib/packlets/types/writePolicy.d.ts.map +1 -0
- package/lib/packlets/types/writePolicy.js +268 -0
- package/lib/packlets/types/writePolicy.js.map +1 -0
- package/lib/packlets/vector/inMemoryCosineIndex.d.ts +69 -0
- package/lib/packlets/vector/inMemoryCosineIndex.d.ts.map +1 -0
- package/lib/packlets/vector/inMemoryCosineIndex.js +154 -0
- package/lib/packlets/vector/inMemoryCosineIndex.js.map +1 -0
- package/lib/packlets/vector/index.d.ts +3 -0
- package/lib/packlets/vector/index.d.ts.map +1 -0
- package/lib/packlets/vector/index.js +23 -0
- package/lib/packlets/vector/index.js.map +1 -0
- package/lib/packlets/vector/vectorIndex.d.ts +68 -0
- package/lib/packlets/vector/vectorIndex.d.ts.map +1 -0
- package/lib/packlets/vector/vectorIndex.js +7 -0
- package/lib/packlets/vector/vectorIndex.js.map +1 -0
- package/lib/test/unit/converters/bodyConverterRegistry.test.d.ts +2 -0
- package/lib/test/unit/converters/bodyConverterRegistry.test.d.ts.map +1 -0
- package/lib/test/unit/converters/bodyConverterRegistry.test.js +74 -0
- package/lib/test/unit/converters/bodyConverterRegistry.test.js.map +1 -0
- package/lib/test/unit/converters/envelopeConverter.test.d.ts +2 -0
- package/lib/test/unit/converters/envelopeConverter.test.d.ts.map +1 -0
- package/lib/test/unit/converters/envelopeConverter.test.js +198 -0
- package/lib/test/unit/converters/envelopeConverter.test.js.map +1 -0
- package/lib/test/unit/index/memoryIndex.test.d.ts +2 -0
- package/lib/test/unit/index/memoryIndex.test.d.ts.map +1 -0
- package/lib/test/unit/index/memoryIndex.test.js +154 -0
- package/lib/test/unit/index/memoryIndex.test.js.map +1 -0
- package/lib/test/unit/observe/memoryObservationStore.test.d.ts +2 -0
- package/lib/test/unit/observe/memoryObservationStore.test.d.ts.map +1 -0
- package/lib/test/unit/observe/memoryObservationStore.test.js +120 -0
- package/lib/test/unit/observe/memoryObservationStore.test.js.map +1 -0
- package/lib/test/unit/retrieve/linkTraversalRetriever.test.d.ts +2 -0
- package/lib/test/unit/retrieve/linkTraversalRetriever.test.d.ts.map +1 -0
- package/lib/test/unit/retrieve/linkTraversalRetriever.test.js +184 -0
- package/lib/test/unit/retrieve/linkTraversalRetriever.test.js.map +1 -0
- package/lib/test/unit/retrieve/retrievers.test.d.ts +2 -0
- package/lib/test/unit/retrieve/retrievers.test.d.ts.map +1 -0
- package/lib/test/unit/retrieve/retrievers.test.js +508 -0
- package/lib/test/unit/retrieve/retrievers.test.js.map +1 -0
- package/lib/test/unit/store/embedOnWrite.test.d.ts +2 -0
- package/lib/test/unit/store/embedOnWrite.test.d.ts.map +1 -0
- package/lib/test/unit/store/embedOnWrite.test.js +262 -0
- package/lib/test/unit/store/embedOnWrite.test.js.map +1 -0
- package/lib/test/unit/store/fileTreeMemoryStore.test.d.ts +2 -0
- package/lib/test/unit/store/fileTreeMemoryStore.test.d.ts.map +1 -0
- package/lib/test/unit/store/fileTreeMemoryStore.test.js +649 -0
- package/lib/test/unit/store/fileTreeMemoryStore.test.js.map +1 -0
- package/lib/test/unit/store/observations.test.d.ts +2 -0
- package/lib/test/unit/store/observations.test.d.ts.map +1 -0
- package/lib/test/unit/store/observations.test.js +241 -0
- package/lib/test/unit/store/observations.test.js.map +1 -0
- package/lib/test/unit/store/scopeEncoding.test.d.ts +2 -0
- package/lib/test/unit/store/scopeEncoding.test.d.ts.map +1 -0
- package/lib/test/unit/store/scopeEncoding.test.js +26 -0
- package/lib/test/unit/store/scopeEncoding.test.js.map +1 -0
- package/lib/test/unit/types/identityCodec.test.d.ts +2 -0
- package/lib/test/unit/types/identityCodec.test.d.ts.map +1 -0
- package/lib/test/unit/types/identityCodec.test.js +189 -0
- package/lib/test/unit/types/identityCodec.test.js.map +1 -0
- package/lib/test/unit/types/ids.test.d.ts +2 -0
- package/lib/test/unit/types/ids.test.d.ts.map +1 -0
- package/lib/test/unit/types/ids.test.js +86 -0
- package/lib/test/unit/types/ids.test.js.map +1 -0
- package/lib/test/unit/types/writePolicy.test.d.ts +2 -0
- package/lib/test/unit/types/writePolicy.test.d.ts.map +1 -0
- package/lib/test/unit/types/writePolicy.test.js +243 -0
- package/lib/test/unit/types/writePolicy.test.js.map +1 -0
- package/lib/test/unit/vector/inMemoryCosineIndex.test.d.ts +2 -0
- package/lib/test/unit/vector/inMemoryCosineIndex.test.d.ts.map +1 -0
- package/lib/test/unit/vector/inMemoryCosineIndex.test.js +194 -0
- package/lib/test/unit/vector/inMemoryCosineIndex.test.js.map +1 -0
- package/lib/test/unit/vector/vectorIndex.test.d.ts +2 -0
- package/lib/test/unit/vector/vectorIndex.test.d.ts.map +1 -0
- package/lib/test/unit/vector/vectorIndex.test.js +44 -0
- package/lib/test/unit/vector/vectorIndex.test.js.map +1 -0
- package/package.json +81 -0
- package/rush-logs/ts-agent-memory.build.cache.log +3 -0
- package/rush-logs/ts-agent-memory.build.log +9 -0
- package/src/index.ts +12 -0
- package/src/packlets/converters/bodyConverterRegistry.ts +105 -0
- package/src/packlets/converters/envelopeConverter.ts +210 -0
- package/src/packlets/converters/index.ts +7 -0
- package/src/packlets/index/index.ts +6 -0
- package/src/packlets/index/memoryIndex.ts +268 -0
- package/src/packlets/observe/index.ts +7 -0
- package/src/packlets/observe/memoryObservationStore.ts +153 -0
- package/src/packlets/observe/observer.ts +119 -0
- package/src/packlets/retrieve/hybridRetriever.ts +181 -0
- package/src/packlets/retrieve/index.ts +12 -0
- package/src/packlets/retrieve/linkTraversalRetriever.ts +169 -0
- package/src/packlets/retrieve/recencyRetriever.ts +54 -0
- package/src/packlets/retrieve/retriever.ts +207 -0
- package/src/packlets/retrieve/semanticRetriever.ts +147 -0
- package/src/packlets/retrieve/structuredFilterRetriever.ts +58 -0
- package/src/packlets/retrieve/tagRetriever.ts +58 -0
- package/src/packlets/store/fileTreeMemoryStore.ts +1073 -0
- package/src/packlets/store/index.ts +7 -0
- package/src/packlets/store/scopeEncoding.ts +36 -0
- package/src/packlets/types/envelope.ts +138 -0
- package/src/packlets/types/filenameSafety.ts +57 -0
- package/src/packlets/types/identityCodec.ts +263 -0
- package/src/packlets/types/ids.ts +124 -0
- package/src/packlets/types/index.ts +10 -0
- package/src/packlets/types/writePolicy.ts +447 -0
- package/src/packlets/vector/inMemoryCosineIndex.ts +173 -0
- package/src/packlets/vector/index.ts +7 -0
- package/src/packlets/vector/vectorIndex.ts +78 -0
- package/src/test/unit/converters/bodyConverterRegistry.test.ts +89 -0
- package/src/test/unit/converters/envelopeConverter.test.ts +261 -0
- package/src/test/unit/index/memoryIndex.test.ts +187 -0
- package/src/test/unit/observe/memoryObservationStore.test.ts +158 -0
- package/src/test/unit/retrieve/linkTraversalRetriever.test.ts +230 -0
- package/src/test/unit/retrieve/retrievers.test.ts +662 -0
- package/src/test/unit/store/embedOnWrite.test.ts +346 -0
- package/src/test/unit/store/fileTreeMemoryStore.test.ts +875 -0
- package/src/test/unit/store/observations.test.ts +290 -0
- package/src/test/unit/store/scopeEncoding.test.ts +37 -0
- package/src/test/unit/types/identityCodec.test.ts +266 -0
- package/src/test/unit/types/ids.test.ts +94 -0
- package/src/test/unit/types/writePolicy.test.ts +325 -0
- package/src/test/unit/vector/inMemoryCosineIndex.test.ts +242 -0
- package/src/test/unit/vector/vectorIndex.test.ts +48 -0
- package/temp/build/lint/_eslint-5eVG3S6w.json +182 -0
- package/temp/build/typescript/ts_8nwakTlr.json +1 -0
- package/temp/ts-agent-memory.api.json +12438 -0
- package/temp/ts-agent-memory.api.md +525 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Result } from '@fgv/ts-utils';
|
|
2
|
+
import { IMemoryRecord } from '../types';
|
|
3
|
+
import { IMemoryQuery, IMemoryRetriever, IMemoryRetrieverCapabilities } from './retriever';
|
|
4
|
+
/**
|
|
5
|
+
* Strategy for combining the result sets of the retrievers a
|
|
6
|
+
* {@link HybridRetriever} composes. Injectable so a consumer can weight,
|
|
7
|
+
* re-rank, or intersect instead of the default union.
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
export interface IMergeStrategy {
|
|
11
|
+
/**
|
|
12
|
+
* Merge the per-retriever result sets into a single ordered result.
|
|
13
|
+
* @param resultSets - One entry per composed retriever, in composition order.
|
|
14
|
+
*/
|
|
15
|
+
merge(resultSets: ReadonlyArray<ReadonlyArray<IMemoryRecord<unknown>>>): Result<ReadonlyArray<IMemoryRecord<unknown>>>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The reference {@link IMergeStrategy}: a score-union. Every record is scored by
|
|
19
|
+
* the number of composed result sets it appears in (deduplicated by
|
|
20
|
+
* {@link IMemoryEnvelope.id | id}); the merged result is ordered by descending
|
|
21
|
+
* score, then by recency. Records surfaced by more retrievers rank higher.
|
|
22
|
+
*
|
|
23
|
+
* @remarks
|
|
24
|
+
* Dedup is by `id` alone, which is unambiguous for the flat (one-file-per-entity)
|
|
25
|
+
* kinds B2 ships. Phase-C versioned kinds that reuse a stem across scopes will
|
|
26
|
+
* extend the dedup key — additive, no API change.
|
|
27
|
+
* @public
|
|
28
|
+
*/
|
|
29
|
+
export declare class ScoreUnionMergeStrategy implements IMergeStrategy {
|
|
30
|
+
private constructor();
|
|
31
|
+
/** Family-convention factory. */
|
|
32
|
+
static create(): Result<ScoreUnionMergeStrategy>;
|
|
33
|
+
/** {@inheritDoc IMergeStrategy.merge} */
|
|
34
|
+
merge(resultSets: ReadonlyArray<ReadonlyArray<IMemoryRecord<unknown>>>): Result<ReadonlyArray<IMemoryRecord<unknown>>>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Composes several retrievers, dispatches a query to each, and merges their
|
|
38
|
+
* results via an injectable {@link IMergeStrategy}. Its capabilities are the
|
|
39
|
+
* union of the composed retrievers' capabilities.
|
|
40
|
+
*
|
|
41
|
+
* @remarks
|
|
42
|
+
* The hybrid enforces the loud-degradation contract against its OWN (union)
|
|
43
|
+
* capabilities, then projects the query for each child: a child that does not
|
|
44
|
+
* support the `semantic` (or `asOf`) axis is handed a query with that axis
|
|
45
|
+
* stripped, so it returns its normal results instead of loud-failing on a field
|
|
46
|
+
* a sibling handles. A child that genuinely fails (e.g. a wired semantic backend
|
|
47
|
+
* erroring) propagates — the hybrid never silently drops a failure.
|
|
48
|
+
* @public
|
|
49
|
+
*/
|
|
50
|
+
export declare class HybridRetriever implements IMemoryRetriever {
|
|
51
|
+
private readonly _retrievers;
|
|
52
|
+
private readonly _mergeStrategy;
|
|
53
|
+
private readonly _capabilities;
|
|
54
|
+
private constructor();
|
|
55
|
+
/** {@inheritDoc IMemoryRetriever.capabilities} */
|
|
56
|
+
get capabilities(): IMemoryRetrieverCapabilities;
|
|
57
|
+
/**
|
|
58
|
+
* Family-convention factory.
|
|
59
|
+
* @param retrievers - The retrievers to compose (at least one).
|
|
60
|
+
* @param mergeStrategy - How to combine their results.
|
|
61
|
+
*/
|
|
62
|
+
static create(retrievers: ReadonlyArray<IMemoryRetriever>, mergeStrategy: IMergeStrategy): Result<HybridRetriever>;
|
|
63
|
+
/** {@inheritDoc IMemoryRetriever.retrieve} */
|
|
64
|
+
retrieve(query: IMemoryQuery): Promise<Result<ReadonlyArray<IMemoryRecord<unknown>>>>;
|
|
65
|
+
/**
|
|
66
|
+
* Project the query for one child retriever. Two adjustments:
|
|
67
|
+
*
|
|
68
|
+
* - Strip axes the child does not support (`semantic` / `topK` for a
|
|
69
|
+
* non-semantic child, `asOf` for a non-temporal child, the link axes for a
|
|
70
|
+
* non-link child) so it returns its normal results rather than loud-failing
|
|
71
|
+
* on a field a sibling handles.
|
|
72
|
+
* - Strip `limit` unconditionally: limit is a post-merge concern. A child that
|
|
73
|
+
* pre-truncated its result set would starve the merge strategy of candidates
|
|
74
|
+
* it needs to score correctly (a record both children would surface must
|
|
75
|
+
* reach the merge to score 2). The hybrid applies `limit` once, after merge.
|
|
76
|
+
*/
|
|
77
|
+
private _projectQuery;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=hybridRetriever.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hybridRetriever.d.ts","sourceRoot":"","sources":["../../../src/packlets/retrieve/hybridRetriever.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,MAAM,EAA6B,MAAM,eAAe,CAAC;AAClE,OAAO,EAAE,aAAa,EAAY,MAAM,UAAU,CAAC;AACnD,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,4BAA4B,EAI7B,MAAM,aAAa,CAAC;AAErB;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,KAAK,CACH,UAAU,EAAE,aAAa,CAAC,aAAa,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,GAC/D,MAAM,CAAC,aAAa,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;CAClD;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,uBAAwB,YAAW,cAAc;IAC5D,OAAO;IAEP,iCAAiC;WACnB,MAAM,IAAI,MAAM,CAAC,uBAAuB,CAAC;IAIvD,yCAAyC;IAClC,KAAK,CACV,UAAU,EAAE,aAAa,CAAC,aAAa,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,GAC/D,MAAM,CAAC,aAAa,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC;CAyBjD;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,eAAgB,YAAW,gBAAgB;IACtD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAkC;IAC9D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiB;IAChD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA+B;IAE7D,OAAO;IAUP,kDAAkD;IAClD,IAAW,YAAY,IAAI,4BAA4B,CAEtD;IAED;;;;OAIG;WACW,MAAM,CAClB,UAAU,EAAE,aAAa,CAAC,gBAAgB,CAAC,EAC3C,aAAa,EAAE,cAAc,GAC5B,MAAM,CAAC,eAAe,CAAC;IAe1B,8CAA8C;IACjC,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAWlG;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,aAAa;CAiBtB"}
|
|
@@ -0,0 +1,140 @@
|
|
|
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.HybridRetriever = exports.ScoreUnionMergeStrategy = void 0;
|
|
8
|
+
const ts_utils_1 = require("@fgv/ts-utils");
|
|
9
|
+
const retriever_1 = require("./retriever");
|
|
10
|
+
/**
|
|
11
|
+
* The reference {@link IMergeStrategy}: a score-union. Every record is scored by
|
|
12
|
+
* the number of composed result sets it appears in (deduplicated by
|
|
13
|
+
* {@link IMemoryEnvelope.id | id}); the merged result is ordered by descending
|
|
14
|
+
* score, then by recency. Records surfaced by more retrievers rank higher.
|
|
15
|
+
*
|
|
16
|
+
* @remarks
|
|
17
|
+
* Dedup is by `id` alone, which is unambiguous for the flat (one-file-per-entity)
|
|
18
|
+
* kinds B2 ships. Phase-C versioned kinds that reuse a stem across scopes will
|
|
19
|
+
* extend the dedup key — additive, no API change.
|
|
20
|
+
* @public
|
|
21
|
+
*/
|
|
22
|
+
class ScoreUnionMergeStrategy {
|
|
23
|
+
constructor() { }
|
|
24
|
+
/** Family-convention factory. */
|
|
25
|
+
static create() {
|
|
26
|
+
return (0, ts_utils_1.succeed)(new ScoreUnionMergeStrategy());
|
|
27
|
+
}
|
|
28
|
+
/** {@inheritDoc IMergeStrategy.merge} */
|
|
29
|
+
merge(resultSets) {
|
|
30
|
+
const scored = new Map();
|
|
31
|
+
for (const set of resultSets) {
|
|
32
|
+
// Count each id at most once per result set, so the score is "how many
|
|
33
|
+
// retrievers surfaced this record" rather than "how many copies total".
|
|
34
|
+
const seenInSet = new Set();
|
|
35
|
+
for (const record of set) {
|
|
36
|
+
const id = record.envelope.id;
|
|
37
|
+
if (seenInSet.has(id)) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
seenInSet.add(id);
|
|
41
|
+
const existing = scored.get(id);
|
|
42
|
+
if (existing === undefined) {
|
|
43
|
+
scored.set(id, { record, score: 1 });
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
existing.score += 1;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const merged = Array.from(scored.values())
|
|
51
|
+
.sort((a, b) => (b.score !== a.score ? b.score - a.score : (0, retriever_1.recencyCompare)(a.record, b.record)))
|
|
52
|
+
.map((entry) => entry.record);
|
|
53
|
+
return (0, ts_utils_1.succeed)(merged);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
exports.ScoreUnionMergeStrategy = ScoreUnionMergeStrategy;
|
|
57
|
+
/**
|
|
58
|
+
* Composes several retrievers, dispatches a query to each, and merges their
|
|
59
|
+
* results via an injectable {@link IMergeStrategy}. Its capabilities are the
|
|
60
|
+
* union of the composed retrievers' capabilities.
|
|
61
|
+
*
|
|
62
|
+
* @remarks
|
|
63
|
+
* The hybrid enforces the loud-degradation contract against its OWN (union)
|
|
64
|
+
* capabilities, then projects the query for each child: a child that does not
|
|
65
|
+
* support the `semantic` (or `asOf`) axis is handed a query with that axis
|
|
66
|
+
* stripped, so it returns its normal results instead of loud-failing on a field
|
|
67
|
+
* a sibling handles. A child that genuinely fails (e.g. a wired semantic backend
|
|
68
|
+
* erroring) propagates — the hybrid never silently drops a failure.
|
|
69
|
+
* @public
|
|
70
|
+
*/
|
|
71
|
+
class HybridRetriever {
|
|
72
|
+
constructor(retrievers, mergeStrategy, capabilities) {
|
|
73
|
+
this._retrievers = retrievers;
|
|
74
|
+
this._mergeStrategy = mergeStrategy;
|
|
75
|
+
this._capabilities = capabilities;
|
|
76
|
+
}
|
|
77
|
+
/** {@inheritDoc IMemoryRetriever.capabilities} */
|
|
78
|
+
get capabilities() {
|
|
79
|
+
return this._capabilities;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Family-convention factory.
|
|
83
|
+
* @param retrievers - The retrievers to compose (at least one).
|
|
84
|
+
* @param mergeStrategy - How to combine their results.
|
|
85
|
+
*/
|
|
86
|
+
static create(retrievers, mergeStrategy) {
|
|
87
|
+
if (retrievers.length === 0) {
|
|
88
|
+
return (0, ts_utils_1.fail)('HybridRetriever: at least one retriever is required');
|
|
89
|
+
}
|
|
90
|
+
// Snapshot the caller's array so a later mutation cannot make `retrieve()`
|
|
91
|
+
// and the cached `capabilities` disagree.
|
|
92
|
+
const stableRetrievers = [...retrievers];
|
|
93
|
+
const capabilities = {
|
|
94
|
+
supportsSemanticRecall: stableRetrievers.some((r) => r.capabilities.supportsSemanticRecall),
|
|
95
|
+
supportsTemporalQuery: stableRetrievers.some((r) => r.capabilities.supportsTemporalQuery),
|
|
96
|
+
supportsLinkTraversal: stableRetrievers.some((r) => r.capabilities.supportsLinkTraversal)
|
|
97
|
+
};
|
|
98
|
+
return (0, ts_utils_1.succeed)(new HybridRetriever(stableRetrievers, mergeStrategy, capabilities));
|
|
99
|
+
}
|
|
100
|
+
/** {@inheritDoc IMemoryRetriever.retrieve} */
|
|
101
|
+
async retrieve(query) {
|
|
102
|
+
return (0, retriever_1.guardRetrieverCapabilities)(query, this._capabilities).thenOnSuccess(async () => {
|
|
103
|
+
const perRetriever = await Promise.all(this._retrievers.map((retriever) => retriever.retrieve(this._projectQuery(query, retriever))));
|
|
104
|
+
return (0, ts_utils_1.mapResults)(perRetriever)
|
|
105
|
+
.onSuccess((resultSets) => this._mergeStrategy.merge(resultSets))
|
|
106
|
+
.onSuccess((merged) => (0, ts_utils_1.succeed)((0, retriever_1.limitRecords)(merged, query.limit)));
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Project the query for one child retriever. Two adjustments:
|
|
111
|
+
*
|
|
112
|
+
* - Strip axes the child does not support (`semantic` / `topK` for a
|
|
113
|
+
* non-semantic child, `asOf` for a non-temporal child, the link axes for a
|
|
114
|
+
* non-link child) so it returns its normal results rather than loud-failing
|
|
115
|
+
* on a field a sibling handles.
|
|
116
|
+
* - Strip `limit` unconditionally: limit is a post-merge concern. A child that
|
|
117
|
+
* pre-truncated its result set would starve the merge strategy of candidates
|
|
118
|
+
* it needs to score correctly (a record both children would surface must
|
|
119
|
+
* reach the merge to score 2). The hybrid applies `limit` once, after merge.
|
|
120
|
+
*/
|
|
121
|
+
_projectQuery(query, retriever) {
|
|
122
|
+
const projected = Object.assign({}, query);
|
|
123
|
+
delete projected.limit;
|
|
124
|
+
if (!retriever.capabilities.supportsSemanticRecall) {
|
|
125
|
+
delete projected.semantic;
|
|
126
|
+
delete projected.topK;
|
|
127
|
+
}
|
|
128
|
+
if (!retriever.capabilities.supportsTemporalQuery) {
|
|
129
|
+
delete projected.asOf;
|
|
130
|
+
}
|
|
131
|
+
if (!retriever.capabilities.supportsLinkTraversal) {
|
|
132
|
+
delete projected.linkedFrom;
|
|
133
|
+
delete projected.linkedTo;
|
|
134
|
+
delete projected.hops;
|
|
135
|
+
}
|
|
136
|
+
return projected;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
exports.HybridRetriever = HybridRetriever;
|
|
140
|
+
//# sourceMappingURL=hybridRetriever.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hybridRetriever.js","sourceRoot":"","sources":["../../../src/packlets/retrieve/hybridRetriever.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Result, fail, mapResults, succeed } from '@fgv/ts-utils';\nimport { IMemoryRecord, MemoryId } from '../types';\nimport {\n IMemoryQuery,\n IMemoryRetriever,\n IMemoryRetrieverCapabilities,\n guardRetrieverCapabilities,\n limitRecords,\n recencyCompare\n} from './retriever';\n\n/**\n * Strategy for combining the result sets of the retrievers a\n * {@link HybridRetriever} composes. Injectable so a consumer can weight,\n * re-rank, or intersect instead of the default union.\n * @public\n */\nexport interface IMergeStrategy {\n /**\n * Merge the per-retriever result sets into a single ordered result.\n * @param resultSets - One entry per composed retriever, in composition order.\n */\n merge(\n resultSets: ReadonlyArray<ReadonlyArray<IMemoryRecord<unknown>>>\n ): Result<ReadonlyArray<IMemoryRecord<unknown>>>;\n}\n\n/**\n * The reference {@link IMergeStrategy}: a score-union. Every record is scored by\n * the number of composed result sets it appears in (deduplicated by\n * {@link IMemoryEnvelope.id | id}); the merged result is ordered by descending\n * score, then by recency. Records surfaced by more retrievers rank higher.\n *\n * @remarks\n * Dedup is by `id` alone, which is unambiguous for the flat (one-file-per-entity)\n * kinds B2 ships. Phase-C versioned kinds that reuse a stem across scopes will\n * extend the dedup key — additive, no API change.\n * @public\n */\nexport class ScoreUnionMergeStrategy implements IMergeStrategy {\n private constructor() {}\n\n /** Family-convention factory. */\n public static create(): Result<ScoreUnionMergeStrategy> {\n return succeed(new ScoreUnionMergeStrategy());\n }\n\n /** {@inheritDoc IMergeStrategy.merge} */\n public merge(\n resultSets: ReadonlyArray<ReadonlyArray<IMemoryRecord<unknown>>>\n ): Result<ReadonlyArray<IMemoryRecord<unknown>>> {\n const scored: Map<MemoryId, { readonly record: IMemoryRecord<unknown>; score: number }> = new Map();\n for (const set of resultSets) {\n // Count each id at most once per result set, so the score is \"how many\n // retrievers surfaced this record\" rather than \"how many copies total\".\n const seenInSet: Set<MemoryId> = new Set<MemoryId>();\n for (const record of set) {\n const id: MemoryId = record.envelope.id;\n if (seenInSet.has(id)) {\n continue;\n }\n seenInSet.add(id);\n const existing = scored.get(id);\n if (existing === undefined) {\n scored.set(id, { record, score: 1 });\n } else {\n existing.score += 1;\n }\n }\n }\n const merged: IMemoryRecord<unknown>[] = Array.from(scored.values())\n .sort((a, b) => (b.score !== a.score ? b.score - a.score : recencyCompare(a.record, b.record)))\n .map((entry) => entry.record);\n return succeed(merged);\n }\n}\n\n/**\n * Composes several retrievers, dispatches a query to each, and merges their\n * results via an injectable {@link IMergeStrategy}. Its capabilities are the\n * union of the composed retrievers' capabilities.\n *\n * @remarks\n * The hybrid enforces the loud-degradation contract against its OWN (union)\n * capabilities, then projects the query for each child: a child that does not\n * support the `semantic` (or `asOf`) axis is handed a query with that axis\n * stripped, so it returns its normal results instead of loud-failing on a field\n * a sibling handles. A child that genuinely fails (e.g. a wired semantic backend\n * erroring) propagates — the hybrid never silently drops a failure.\n * @public\n */\nexport class HybridRetriever implements IMemoryRetriever {\n private readonly _retrievers: ReadonlyArray<IMemoryRetriever>;\n private readonly _mergeStrategy: IMergeStrategy;\n private readonly _capabilities: IMemoryRetrieverCapabilities;\n\n private constructor(\n retrievers: ReadonlyArray<IMemoryRetriever>,\n mergeStrategy: IMergeStrategy,\n capabilities: IMemoryRetrieverCapabilities\n ) {\n this._retrievers = retrievers;\n this._mergeStrategy = mergeStrategy;\n this._capabilities = capabilities;\n }\n\n /** {@inheritDoc IMemoryRetriever.capabilities} */\n public get capabilities(): IMemoryRetrieverCapabilities {\n return this._capabilities;\n }\n\n /**\n * Family-convention factory.\n * @param retrievers - The retrievers to compose (at least one).\n * @param mergeStrategy - How to combine their results.\n */\n public static create(\n retrievers: ReadonlyArray<IMemoryRetriever>,\n mergeStrategy: IMergeStrategy\n ): Result<HybridRetriever> {\n if (retrievers.length === 0) {\n return fail('HybridRetriever: at least one retriever is required');\n }\n // Snapshot the caller's array so a later mutation cannot make `retrieve()`\n // and the cached `capabilities` disagree.\n const stableRetrievers: ReadonlyArray<IMemoryRetriever> = [...retrievers];\n const capabilities: IMemoryRetrieverCapabilities = {\n supportsSemanticRecall: stableRetrievers.some((r) => r.capabilities.supportsSemanticRecall),\n supportsTemporalQuery: stableRetrievers.some((r) => r.capabilities.supportsTemporalQuery),\n supportsLinkTraversal: stableRetrievers.some((r) => r.capabilities.supportsLinkTraversal)\n };\n return succeed(new HybridRetriever(stableRetrievers, mergeStrategy, capabilities));\n }\n\n /** {@inheritDoc IMemoryRetriever.retrieve} */\n public async retrieve(query: IMemoryQuery): Promise<Result<ReadonlyArray<IMemoryRecord<unknown>>>> {\n return guardRetrieverCapabilities(query, this._capabilities).thenOnSuccess(async () => {\n const perRetriever: Result<ReadonlyArray<IMemoryRecord<unknown>>>[] = await Promise.all(\n this._retrievers.map((retriever) => retriever.retrieve(this._projectQuery(query, retriever)))\n );\n return mapResults(perRetriever)\n .onSuccess((resultSets) => this._mergeStrategy.merge(resultSets))\n .onSuccess((merged) => succeed(limitRecords(merged, query.limit)));\n });\n }\n\n /**\n * Project the query for one child retriever. Two adjustments:\n *\n * - Strip axes the child does not support (`semantic` / `topK` for a\n * non-semantic child, `asOf` for a non-temporal child, the link axes for a\n * non-link child) so it returns its normal results rather than loud-failing\n * on a field a sibling handles.\n * - Strip `limit` unconditionally: limit is a post-merge concern. A child that\n * pre-truncated its result set would starve the merge strategy of candidates\n * it needs to score correctly (a record both children would surface must\n * reach the merge to score 2). The hybrid applies `limit` once, after merge.\n */\n private _projectQuery(query: IMemoryQuery, retriever: IMemoryRetriever): IMemoryQuery {\n const projected: { -readonly [K in keyof IMemoryQuery]: IMemoryQuery[K] } = { ...query };\n delete projected.limit;\n if (!retriever.capabilities.supportsSemanticRecall) {\n delete projected.semantic;\n delete projected.topK;\n }\n if (!retriever.capabilities.supportsTemporalQuery) {\n delete projected.asOf;\n }\n if (!retriever.capabilities.supportsLinkTraversal) {\n delete projected.linkedFrom;\n delete projected.linkedTo;\n delete projected.hops;\n }\n return projected;\n }\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './retriever';
|
|
2
|
+
export * from './recencyRetriever';
|
|
3
|
+
export * from './linkTraversalRetriever';
|
|
4
|
+
export * from './tagRetriever';
|
|
5
|
+
export * from './structuredFilterRetriever';
|
|
6
|
+
export * from './semanticRetriever';
|
|
7
|
+
export * from './hybridRetriever';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +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,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2026 Erik Fortune
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
18
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
19
|
+
};
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
__exportStar(require("./retriever"), exports);
|
|
22
|
+
__exportStar(require("./recencyRetriever"), exports);
|
|
23
|
+
__exportStar(require("./linkTraversalRetriever"), exports);
|
|
24
|
+
__exportStar(require("./tagRetriever"), exports);
|
|
25
|
+
__exportStar(require("./structuredFilterRetriever"), exports);
|
|
26
|
+
__exportStar(require("./semanticRetriever"), exports);
|
|
27
|
+
__exportStar(require("./hybridRetriever"), exports);
|
|
28
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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,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 './hybridRetriever';\n"]}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Result } from '@fgv/ts-utils';
|
|
2
|
+
import { IMemoryRecord } from '../types';
|
|
3
|
+
import { IMemoryIndex } from '../index';
|
|
4
|
+
import { IMemoryQuery, IMemoryRetriever, IMemoryRetrieverCapabilities } from './retriever';
|
|
5
|
+
/**
|
|
6
|
+
* The loud-degradation message returned when a link-traversal query supplies no
|
|
7
|
+
* seed (`linkedFrom` / `linkedTo`).
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
export declare const LINK_TRAVERSAL_NO_SEED_MESSAGE: string;
|
|
11
|
+
/**
|
|
12
|
+
* Breadth-first link-traversal retriever. From a seed {@link MemoryId} it walks
|
|
13
|
+
* the link graph up to `query.hops` levels and returns the records reached
|
|
14
|
+
* (excluding the seed), recency-ordered and limited.
|
|
15
|
+
*
|
|
16
|
+
* @remarks
|
|
17
|
+
* - **Direction.** `linkedFrom` walks OUTBOUND edges (each record's
|
|
18
|
+
* `envelope.links[].target`); `linkedTo` walks INBOUND edges (the index's
|
|
19
|
+
* `backlinks`). Exactly one is the seed; `linkedFrom` wins if both are set.
|
|
20
|
+
* - **Bound + cycle safety.** Traversal is bounded by `hops` (default `1` — a
|
|
21
|
+
* single hop) and a visited-set guard. The graph is keyed by bare
|
|
22
|
+
* string {@link MemoryId}s, so a `Set<string>` visited-set is the exact,
|
|
23
|
+
* collision-free cycle key — no structural hashing (e.g. `Crc32Normalizer`) is
|
|
24
|
+
* needed. A self-loop or any multi-hop cycle terminates because a revisited id
|
|
25
|
+
* is never re-expanded.
|
|
26
|
+
* - **Post-filter.** The scope / kind / tag / predicate axes of the query are
|
|
27
|
+
* applied to the reached records (the link axes are the traversal itself).
|
|
28
|
+
* @public
|
|
29
|
+
*/
|
|
30
|
+
export declare class LinkTraversalRetriever implements IMemoryRetriever {
|
|
31
|
+
private readonly _index;
|
|
32
|
+
private constructor();
|
|
33
|
+
/** Family-convention factory. */
|
|
34
|
+
static create(index: IMemoryIndex): Result<LinkTraversalRetriever>;
|
|
35
|
+
/** {@inheritDoc IMemoryRetriever.capabilities} */
|
|
36
|
+
get capabilities(): IMemoryRetrieverCapabilities;
|
|
37
|
+
/** {@inheritDoc IMemoryRetriever.retrieve} */
|
|
38
|
+
retrieve(query: IMemoryQuery): Promise<Result<ReadonlyArray<IMemoryRecord<unknown>>>>;
|
|
39
|
+
/** Run the bounded, cycle-safe BFS and post-filter the reached records. */
|
|
40
|
+
private _traverse;
|
|
41
|
+
/**
|
|
42
|
+
* Group the index's entries by bare {@link MemoryId}. An id can map to more
|
|
43
|
+
* than one entry when distinct scopes reuse a filename stem (e.g. `turn-0` in
|
|
44
|
+
* two conversations), so the value is an array.
|
|
45
|
+
*
|
|
46
|
+
* @remarks
|
|
47
|
+
* **Design note (links are globally-scoped identifiers in this phase).** An
|
|
48
|
+
* {@link IEdge.target} is a bare `MemoryId`, not a `(scope, id)` pair, so
|
|
49
|
+
* traversal resolves a target across ALL scopes that hold that id. When two
|
|
50
|
+
* scopes reuse a stem, following an edge to it reaches every match. This
|
|
51
|
+
* mirrors the `backlinks` index, which is also keyed by bare id. Scope-
|
|
52
|
+
* qualified link resolution is intentionally out of scope for Phase C and
|
|
53
|
+
* would be an additive change here (and to {@link IEdge} / the index).
|
|
54
|
+
*/
|
|
55
|
+
private _indexById;
|
|
56
|
+
/** Outbound neighbors: the targets of every edge on the records with this id. */
|
|
57
|
+
private _outbound;
|
|
58
|
+
/** Inbound neighbors: the ids whose edges point AT this id (the backlinks). */
|
|
59
|
+
private _inbound;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=linkTraversalRetriever.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linkTraversalRetriever.d.ts","sourceRoot":"","sources":["../../../src/packlets/retrieve/linkTraversalRetriever.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,MAAM,EAAiB,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,aAAa,EAAY,MAAM,UAAU,CAAC;AACnD,OAAO,EAAwB,YAAY,EAAE,MAAM,UAAU,CAAC;AAC9D,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,4BAA4B,EAK7B,MAAM,aAAa,CAAC;AAYrB;;;;GAIG;AACH,eAAO,MAAM,8BAA8B,EAAE,MACiB,CAAC;AAE/D;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,sBAAuB,YAAW,gBAAgB;IAC7D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IAEtC,OAAO;IAIP,iCAAiC;WACnB,MAAM,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAAC,sBAAsB,CAAC;IAIzE,kDAAkD;IAClD,IAAW,YAAY,IAAI,4BAA4B,CAEtD;IAED,8CAA8C;IACvC,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAM5F,2EAA2E;IAC3E,OAAO,CAAC,SAAS;IA0CjB;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,UAAU;IAclB,iFAAiF;IACjF,OAAO,CAAC,SAAS;IAajB,+EAA+E;IAC/E,OAAO,CAAC,QAAQ;CAGjB"}
|
|
@@ -0,0 +1,147 @@
|
|
|
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.LinkTraversalRetriever = exports.LINK_TRAVERSAL_NO_SEED_MESSAGE = void 0;
|
|
8
|
+
const ts_utils_1 = require("@fgv/ts-utils");
|
|
9
|
+
const retriever_1 = require("./retriever");
|
|
10
|
+
/** The capabilities a link-traversal retriever exposes (link traversal only). */
|
|
11
|
+
const LINK_TRAVERSAL_CAPABILITIES = {
|
|
12
|
+
supportsSemanticRecall: false,
|
|
13
|
+
supportsTemporalQuery: false,
|
|
14
|
+
supportsLinkTraversal: true
|
|
15
|
+
};
|
|
16
|
+
/** Default BFS hop count when `query.hops` is not supplied. */
|
|
17
|
+
const DEFAULT_HOPS = 1;
|
|
18
|
+
/**
|
|
19
|
+
* The loud-degradation message returned when a link-traversal query supplies no
|
|
20
|
+
* seed (`linkedFrom` / `linkedTo`).
|
|
21
|
+
* @public
|
|
22
|
+
*/
|
|
23
|
+
exports.LINK_TRAVERSAL_NO_SEED_MESSAGE = 'link traversal requires a seed id (linkedFrom or linkedTo)';
|
|
24
|
+
/**
|
|
25
|
+
* Breadth-first link-traversal retriever. From a seed {@link MemoryId} it walks
|
|
26
|
+
* the link graph up to `query.hops` levels and returns the records reached
|
|
27
|
+
* (excluding the seed), recency-ordered and limited.
|
|
28
|
+
*
|
|
29
|
+
* @remarks
|
|
30
|
+
* - **Direction.** `linkedFrom` walks OUTBOUND edges (each record's
|
|
31
|
+
* `envelope.links[].target`); `linkedTo` walks INBOUND edges (the index's
|
|
32
|
+
* `backlinks`). Exactly one is the seed; `linkedFrom` wins if both are set.
|
|
33
|
+
* - **Bound + cycle safety.** Traversal is bounded by `hops` (default `1` — a
|
|
34
|
+
* single hop) and a visited-set guard. The graph is keyed by bare
|
|
35
|
+
* string {@link MemoryId}s, so a `Set<string>` visited-set is the exact,
|
|
36
|
+
* collision-free cycle key — no structural hashing (e.g. `Crc32Normalizer`) is
|
|
37
|
+
* needed. A self-loop or any multi-hop cycle terminates because a revisited id
|
|
38
|
+
* is never re-expanded.
|
|
39
|
+
* - **Post-filter.** The scope / kind / tag / predicate axes of the query are
|
|
40
|
+
* applied to the reached records (the link axes are the traversal itself).
|
|
41
|
+
* @public
|
|
42
|
+
*/
|
|
43
|
+
class LinkTraversalRetriever {
|
|
44
|
+
constructor(index) {
|
|
45
|
+
this._index = index;
|
|
46
|
+
}
|
|
47
|
+
/** Family-convention factory. */
|
|
48
|
+
static create(index) {
|
|
49
|
+
return (0, ts_utils_1.succeed)(new LinkTraversalRetriever(index));
|
|
50
|
+
}
|
|
51
|
+
/** {@inheritDoc IMemoryRetriever.capabilities} */
|
|
52
|
+
get capabilities() {
|
|
53
|
+
return LINK_TRAVERSAL_CAPABILITIES;
|
|
54
|
+
}
|
|
55
|
+
/** {@inheritDoc IMemoryRetriever.retrieve} */
|
|
56
|
+
retrieve(query) {
|
|
57
|
+
return Promise.resolve((0, retriever_1.guardRetrieverCapabilities)(query, this.capabilities).onSuccess(() => this._traverse(query)));
|
|
58
|
+
}
|
|
59
|
+
/** Run the bounded, cycle-safe BFS and post-filter the reached records. */
|
|
60
|
+
_traverse(query) {
|
|
61
|
+
var _a, _b;
|
|
62
|
+
const outbound = query.linkedFrom !== undefined;
|
|
63
|
+
const seed = (_a = query.linkedFrom) !== null && _a !== void 0 ? _a : query.linkedTo;
|
|
64
|
+
if (seed === undefined) {
|
|
65
|
+
return (0, ts_utils_1.fail)(exports.LINK_TRAVERSAL_NO_SEED_MESSAGE);
|
|
66
|
+
}
|
|
67
|
+
const hops = (_b = query.hops) !== null && _b !== void 0 ? _b : DEFAULT_HOPS;
|
|
68
|
+
const byId = this._indexById();
|
|
69
|
+
// The visited-set IS the cycle guard: ids are strings, so set membership is
|
|
70
|
+
// an exact identity check. The seed is pre-marked so it is never re-added.
|
|
71
|
+
const visited = new Set([seed]);
|
|
72
|
+
const reached = [];
|
|
73
|
+
let frontier = [seed];
|
|
74
|
+
for (let hop = 0; hop < hops && frontier.length > 0; hop++) {
|
|
75
|
+
const next = [];
|
|
76
|
+
for (const id of frontier) {
|
|
77
|
+
for (const neighbor of outbound ? this._outbound(id, byId) : this._inbound(id)) {
|
|
78
|
+
if (!visited.has(neighbor)) {
|
|
79
|
+
visited.add(neighbor);
|
|
80
|
+
reached.push(neighbor);
|
|
81
|
+
next.push(neighbor);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
frontier = next;
|
|
86
|
+
}
|
|
87
|
+
const entries = [];
|
|
88
|
+
for (const id of reached) {
|
|
89
|
+
const matches = byId.get(id);
|
|
90
|
+
if (matches !== undefined) {
|
|
91
|
+
entries.push(...matches);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const ordered = entries
|
|
95
|
+
.filter((entry) => (0, retriever_1.indexedRecordMatchesQuery)(entry, query))
|
|
96
|
+
.map((entry) => entry.record)
|
|
97
|
+
.sort(retriever_1.recencyCompare);
|
|
98
|
+
return (0, ts_utils_1.succeed)((0, retriever_1.limitRecords)(ordered, query.limit));
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Group the index's entries by bare {@link MemoryId}. An id can map to more
|
|
102
|
+
* than one entry when distinct scopes reuse a filename stem (e.g. `turn-0` in
|
|
103
|
+
* two conversations), so the value is an array.
|
|
104
|
+
*
|
|
105
|
+
* @remarks
|
|
106
|
+
* **Design note (links are globally-scoped identifiers in this phase).** An
|
|
107
|
+
* {@link IEdge.target} is a bare `MemoryId`, not a `(scope, id)` pair, so
|
|
108
|
+
* traversal resolves a target across ALL scopes that hold that id. When two
|
|
109
|
+
* scopes reuse a stem, following an edge to it reaches every match. This
|
|
110
|
+
* mirrors the `backlinks` index, which is also keyed by bare id. Scope-
|
|
111
|
+
* qualified link resolution is intentionally out of scope for Phase C and
|
|
112
|
+
* would be an additive change here (and to {@link IEdge} / the index).
|
|
113
|
+
*/
|
|
114
|
+
_indexById() {
|
|
115
|
+
const byId = new Map();
|
|
116
|
+
for (const entry of this._index.entries()) {
|
|
117
|
+
const id = entry.record.envelope.id;
|
|
118
|
+
const existing = byId.get(id);
|
|
119
|
+
if (existing === undefined) {
|
|
120
|
+
byId.set(id, [entry]);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
existing.push(entry);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return byId;
|
|
127
|
+
}
|
|
128
|
+
/** Outbound neighbors: the targets of every edge on the records with this id. */
|
|
129
|
+
_outbound(id, byId) {
|
|
130
|
+
const targets = [];
|
|
131
|
+
const matches = byId.get(id);
|
|
132
|
+
if (matches !== undefined) {
|
|
133
|
+
for (const entry of matches) {
|
|
134
|
+
for (const edge of entry.record.envelope.links) {
|
|
135
|
+
targets.push(edge.target);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return targets;
|
|
140
|
+
}
|
|
141
|
+
/** Inbound neighbors: the ids whose edges point AT this id (the backlinks). */
|
|
142
|
+
_inbound(id) {
|
|
143
|
+
return this._index.backlinks(id);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
exports.LinkTraversalRetriever = LinkTraversalRetriever;
|
|
147
|
+
//# sourceMappingURL=linkTraversalRetriever.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linkTraversalRetriever.js","sourceRoot":"","sources":["../../../src/packlets/retrieve/linkTraversalRetriever.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Result, fail, succeed } from '@fgv/ts-utils';\nimport { IMemoryRecord, MemoryId } from '../types';\nimport { IIndexedMemoryRecord, IMemoryIndex } from '../index';\nimport {\n IMemoryQuery,\n IMemoryRetriever,\n IMemoryRetrieverCapabilities,\n guardRetrieverCapabilities,\n indexedRecordMatchesQuery,\n limitRecords,\n recencyCompare\n} from './retriever';\n\n/** The capabilities a link-traversal retriever exposes (link traversal only). */\nconst LINK_TRAVERSAL_CAPABILITIES: IMemoryRetrieverCapabilities = {\n supportsSemanticRecall: false,\n supportsTemporalQuery: false,\n supportsLinkTraversal: true\n};\n\n/** Default BFS hop count when `query.hops` is not supplied. */\nconst DEFAULT_HOPS: number = 1;\n\n/**\n * The loud-degradation message returned when a link-traversal query supplies no\n * seed (`linkedFrom` / `linkedTo`).\n * @public\n */\nexport const LINK_TRAVERSAL_NO_SEED_MESSAGE: string =\n 'link traversal requires a seed id (linkedFrom or linkedTo)';\n\n/**\n * Breadth-first link-traversal retriever. From a seed {@link MemoryId} it walks\n * the link graph up to `query.hops` levels and returns the records reached\n * (excluding the seed), recency-ordered and limited.\n *\n * @remarks\n * - **Direction.** `linkedFrom` walks OUTBOUND edges (each record's\n * `envelope.links[].target`); `linkedTo` walks INBOUND edges (the index's\n * `backlinks`). Exactly one is the seed; `linkedFrom` wins if both are set.\n * - **Bound + cycle safety.** Traversal is bounded by `hops` (default `1` — a\n * single hop) and a visited-set guard. The graph is keyed by bare\n * string {@link MemoryId}s, so a `Set<string>` visited-set is the exact,\n * collision-free cycle key — no structural hashing (e.g. `Crc32Normalizer`) is\n * needed. A self-loop or any multi-hop cycle terminates because a revisited id\n * is never re-expanded.\n * - **Post-filter.** The scope / kind / tag / predicate axes of the query are\n * applied to the reached records (the link axes are the traversal itself).\n * @public\n */\nexport class LinkTraversalRetriever implements IMemoryRetriever {\n private readonly _index: IMemoryIndex;\n\n private constructor(index: IMemoryIndex) {\n this._index = index;\n }\n\n /** Family-convention factory. */\n public static create(index: IMemoryIndex): Result<LinkTraversalRetriever> {\n return succeed(new LinkTraversalRetriever(index));\n }\n\n /** {@inheritDoc IMemoryRetriever.capabilities} */\n public get capabilities(): IMemoryRetrieverCapabilities {\n return LINK_TRAVERSAL_CAPABILITIES;\n }\n\n /** {@inheritDoc IMemoryRetriever.retrieve} */\n public retrieve(query: IMemoryQuery): Promise<Result<ReadonlyArray<IMemoryRecord<unknown>>>> {\n return Promise.resolve(\n guardRetrieverCapabilities(query, this.capabilities).onSuccess(() => this._traverse(query))\n );\n }\n\n /** Run the bounded, cycle-safe BFS and post-filter the reached records. */\n private _traverse(query: IMemoryQuery): Result<ReadonlyArray<IMemoryRecord<unknown>>> {\n const outbound: boolean = query.linkedFrom !== undefined;\n const seed: MemoryId | undefined = query.linkedFrom ?? query.linkedTo;\n if (seed === undefined) {\n return fail(LINK_TRAVERSAL_NO_SEED_MESSAGE);\n }\n const hops: number = query.hops ?? DEFAULT_HOPS;\n const byId: ReadonlyMap<MemoryId, IIndexedMemoryRecord[]> = this._indexById();\n\n // The visited-set IS the cycle guard: ids are strings, so set membership is\n // an exact identity check. The seed is pre-marked so it is never re-added.\n const visited: Set<string> = new Set<string>([seed]);\n const reached: MemoryId[] = [];\n let frontier: MemoryId[] = [seed];\n for (let hop = 0; hop < hops && frontier.length > 0; hop++) {\n const next: MemoryId[] = [];\n for (const id of frontier) {\n for (const neighbor of outbound ? this._outbound(id, byId) : this._inbound(id)) {\n if (!visited.has(neighbor)) {\n visited.add(neighbor);\n reached.push(neighbor);\n next.push(neighbor);\n }\n }\n }\n frontier = next;\n }\n\n const entries: IIndexedMemoryRecord[] = [];\n for (const id of reached) {\n const matches: IIndexedMemoryRecord[] | undefined = byId.get(id);\n if (matches !== undefined) {\n entries.push(...matches);\n }\n }\n const ordered: IMemoryRecord<unknown>[] = entries\n .filter((entry) => indexedRecordMatchesQuery(entry, query))\n .map((entry) => entry.record)\n .sort(recencyCompare);\n return succeed(limitRecords(ordered, query.limit));\n }\n\n /**\n * Group the index's entries by bare {@link MemoryId}. An id can map to more\n * than one entry when distinct scopes reuse a filename stem (e.g. `turn-0` in\n * two conversations), so the value is an array.\n *\n * @remarks\n * **Design note (links are globally-scoped identifiers in this phase).** An\n * {@link IEdge.target} is a bare `MemoryId`, not a `(scope, id)` pair, so\n * traversal resolves a target across ALL scopes that hold that id. When two\n * scopes reuse a stem, following an edge to it reaches every match. This\n * mirrors the `backlinks` index, which is also keyed by bare id. Scope-\n * qualified link resolution is intentionally out of scope for Phase C and\n * would be an additive change here (and to {@link IEdge} / the index).\n */\n private _indexById(): ReadonlyMap<MemoryId, IIndexedMemoryRecord[]> {\n const byId: Map<MemoryId, IIndexedMemoryRecord[]> = new Map<MemoryId, IIndexedMemoryRecord[]>();\n for (const entry of this._index.entries()) {\n const id: MemoryId = entry.record.envelope.id;\n const existing: IIndexedMemoryRecord[] | undefined = byId.get(id);\n if (existing === undefined) {\n byId.set(id, [entry]);\n } else {\n existing.push(entry);\n }\n }\n return byId;\n }\n\n /** Outbound neighbors: the targets of every edge on the records with this id. */\n private _outbound(id: MemoryId, byId: ReadonlyMap<MemoryId, IIndexedMemoryRecord[]>): MemoryId[] {\n const targets: MemoryId[] = [];\n const matches: IIndexedMemoryRecord[] | undefined = byId.get(id);\n if (matches !== undefined) {\n for (const entry of matches) {\n for (const edge of entry.record.envelope.links) {\n targets.push(edge.target);\n }\n }\n }\n return targets;\n }\n\n /** Inbound neighbors: the ids whose edges point AT this id (the backlinks). */\n private _inbound(id: MemoryId): ReadonlyArray<MemoryId> {\n return this._index.backlinks(id);\n }\n}\n"]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Result } from '@fgv/ts-utils';
|
|
2
|
+
import { IMemoryRecord } from '../types';
|
|
3
|
+
import { IMemoryIndex } from '../index';
|
|
4
|
+
import { IMemoryQuery, IMemoryRetriever, IMemoryRetrieverCapabilities } from './retriever';
|
|
5
|
+
/**
|
|
6
|
+
* Returns records matching the query's scope / kind / tag / predicate filters,
|
|
7
|
+
* ordered most-recently-updated first. The universal v1 retriever — an empty
|
|
8
|
+
* query returns the whole vault in recency order.
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
export declare class RecencyRetriever implements IMemoryRetriever {
|
|
12
|
+
private readonly _index;
|
|
13
|
+
private constructor();
|
|
14
|
+
/** {@inheritDoc IMemoryRetriever.capabilities} */
|
|
15
|
+
get capabilities(): IMemoryRetrieverCapabilities;
|
|
16
|
+
/** Family-convention factory. */
|
|
17
|
+
static create(index: IMemoryIndex): Result<RecencyRetriever>;
|
|
18
|
+
/** {@inheritDoc IMemoryRetriever.retrieve} */
|
|
19
|
+
retrieve(query: IMemoryQuery): Promise<Result<ReadonlyArray<IMemoryRecord<unknown>>>>;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=recencyRetriever.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recencyRetriever.d.ts","sourceRoot":"","sources":["../../../src/packlets/retrieve/recencyRetriever.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,MAAM,EAAW,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,4BAA4B,EAM7B,MAAM,aAAa,CAAC;AAErB;;;;;GAKG;AACH,qBAAa,gBAAiB,YAAW,gBAAgB;IACvD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IAEtC,OAAO;IAIP,kDAAkD;IAClD,IAAW,YAAY,IAAI,4BAA4B,CAEtD;IAED,iCAAiC;WACnB,MAAM,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAAC,gBAAgB,CAAC;IAInE,8CAA8C;IACvC,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;CAU7F"}
|
|
@@ -0,0 +1,37 @@
|
|
|
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.RecencyRetriever = void 0;
|
|
8
|
+
const ts_utils_1 = require("@fgv/ts-utils");
|
|
9
|
+
const retriever_1 = require("./retriever");
|
|
10
|
+
/**
|
|
11
|
+
* Returns records matching the query's scope / kind / tag / predicate filters,
|
|
12
|
+
* ordered most-recently-updated first. The universal v1 retriever — an empty
|
|
13
|
+
* query returns the whole vault in recency order.
|
|
14
|
+
* @public
|
|
15
|
+
*/
|
|
16
|
+
class RecencyRetriever {
|
|
17
|
+
constructor(index) {
|
|
18
|
+
this._index = index;
|
|
19
|
+
}
|
|
20
|
+
/** {@inheritDoc IMemoryRetriever.capabilities} */
|
|
21
|
+
get capabilities() {
|
|
22
|
+
return retriever_1.NON_SEMANTIC_CAPABILITIES;
|
|
23
|
+
}
|
|
24
|
+
/** Family-convention factory. */
|
|
25
|
+
static create(index) {
|
|
26
|
+
return (0, ts_utils_1.succeed)(new RecencyRetriever(index));
|
|
27
|
+
}
|
|
28
|
+
/** {@inheritDoc IMemoryRetriever.retrieve} */
|
|
29
|
+
retrieve(query) {
|
|
30
|
+
return Promise.resolve((0, retriever_1.guardRetrieverCapabilities)(query, this.capabilities).onSuccess(() => {
|
|
31
|
+
const ordered = (0, retriever_1.selectByQuery)(this._index.entries(), query).sort(retriever_1.recencyCompare);
|
|
32
|
+
return (0, ts_utils_1.succeed)((0, retriever_1.limitRecords)(ordered, query.limit));
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.RecencyRetriever = RecencyRetriever;
|
|
37
|
+
//# sourceMappingURL=recencyRetriever.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recencyRetriever.js","sourceRoot":"","sources":["../../../src/packlets/retrieve/recencyRetriever.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,4CAAgD;AAGhD,2CASqB;AAErB;;;;;GAKG;AACH,MAAa,gBAAgB;IAG3B,YAAoB,KAAmB;QACrC,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;IACtB,CAAC;IAED,kDAAkD;IAClD,IAAW,YAAY;QACrB,OAAO,qCAAyB,CAAC;IACnC,CAAC;IAED,iCAAiC;IAC1B,MAAM,CAAC,MAAM,CAAC,KAAmB;QACtC,OAAO,IAAA,kBAAO,EAAC,IAAI,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC;IAC9C,CAAC;IAED,8CAA8C;IACvC,QAAQ,CAAC,KAAmB;QACjC,OAAO,OAAO,CAAC,OAAO,CACpB,IAAA,sCAA0B,EAAC,KAAK,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE;YAClE,MAAM,OAAO,GAA6B,IAAA,yBAAa,EAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,KAAK,CAAC,CAAC,IAAI,CACxF,0BAAc,CACf,CAAC;YACF,OAAO,IAAA,kBAAO,EAAC,IAAA,wBAAY,EAAC,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QACrD,CAAC,CAAC,CACH,CAAC;IACJ,CAAC;CACF;AA5BD,4CA4BC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Result, succeed } from '@fgv/ts-utils';\nimport { IMemoryRecord } from '../types';\nimport { IMemoryIndex } from '../index';\nimport {\n IMemoryQuery,\n IMemoryRetriever,\n IMemoryRetrieverCapabilities,\n NON_SEMANTIC_CAPABILITIES,\n guardRetrieverCapabilities,\n limitRecords,\n recencyCompare,\n selectByQuery\n} from './retriever';\n\n/**\n * Returns records matching the query's scope / kind / tag / predicate filters,\n * ordered most-recently-updated first. The universal v1 retriever — an empty\n * query returns the whole vault in recency order.\n * @public\n */\nexport class RecencyRetriever implements IMemoryRetriever {\n private readonly _index: IMemoryIndex;\n\n private constructor(index: IMemoryIndex) {\n this._index = index;\n }\n\n /** {@inheritDoc IMemoryRetriever.capabilities} */\n public get capabilities(): IMemoryRetrieverCapabilities {\n return NON_SEMANTIC_CAPABILITIES;\n }\n\n /** Family-convention factory. */\n public static create(index: IMemoryIndex): Result<RecencyRetriever> {\n return succeed(new RecencyRetriever(index));\n }\n\n /** {@inheritDoc IMemoryRetriever.retrieve} */\n public retrieve(query: IMemoryQuery): Promise<Result<ReadonlyArray<IMemoryRecord<unknown>>>> {\n return Promise.resolve(\n guardRetrieverCapabilities(query, this.capabilities).onSuccess(() => {\n const ordered: IMemoryRecord<unknown>[] = selectByQuery(this._index.entries(), query).sort(\n recencyCompare\n );\n return succeed(limitRecords(ordered, query.limit));\n })\n );\n }\n}\n"]}
|