@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 @@
|
|
|
1
|
+
{"version":3,"file":"envelopeConverter.test.js","sourceRoot":"","sources":["../../../../src/test/unit/converters/envelopeConverter.test.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport '@fgv/ts-utils-jest';\nimport { Converters } from '@fgv/ts-utils';\nimport {\n BodyConverterRegistry,\n edgeConverter,\n envelopeConverter,\n envelopeYamlConverter,\n joinFrontmatter,\n parseMemoryFile,\n provenanceConverter,\n serializeMemoryFile,\n splitFrontmatter,\n temporalConverter\n} from '../../../packlets/converters';\nimport { IMemoryEnvelope, Kind } from '../../../packlets/types';\n\nconst validEnvelopeObject: Record<string, unknown> = {\n id: 'intro-to-rust',\n entityId: 'intro-to-rust',\n kind: 'knowledge',\n tags: ['rust', 'systems'],\n links: [{ type: 'related', target: 'ownership' }],\n created: 1000,\n updated: 2000,\n seq: 5,\n contentHash: 'crc-abc',\n provenance: { source: 'agent', by: 'curator' }\n};\n\ndescribe('provenanceConverter', () => {\n test('validates the known fields', () => {\n expect(\n provenanceConverter.convert({ source: 'human', by: 'erik', model: 'gpt', confidence: 0.9 })\n ).toSucceedWith({ source: 'human', by: 'erik', model: 'gpt', confidence: 0.9 });\n });\n\n test('preserves opaque extension keys verbatim', () => {\n expect(\n provenanceConverter.convert({ source: 'agent', sentiment: { score: 0.5 }, epistemic: 'belief' })\n ).toSucceedAndSatisfy((p) => {\n expect(p.source).toBe('agent');\n expect(p.sentiment).toEqual({ score: 0.5 });\n expect(p.epistemic).toBe('belief');\n });\n });\n\n test('accepts an arbitrary source string (open vocabulary)', () => {\n expect(provenanceConverter.convert({ source: 'host-ingest' })).toSucceedWith({ source: 'host-ingest' });\n });\n\n test('fails when source is missing', () => {\n expect(provenanceConverter.convert({ by: 'erik' })).toFail();\n });\n\n test('fails on a non-object', () => {\n expect(provenanceConverter.convert('nope')).toFail();\n });\n});\n\ndescribe('edgeConverter', () => {\n test('validates a full attributed edge', () => {\n expect(\n edgeConverter.convert({\n type: 'mtm-ref',\n target: 'turn-3',\n confidence: 0.8,\n provenance: { source: 'agent' },\n valid_at: 100,\n invalid_at: 200\n })\n ).toSucceedAndSatisfy((edge) => {\n expect(edge.type).toBe('mtm-ref');\n expect(edge.target).toBe('turn-3');\n expect(edge.confidence).toBe(0.8);\n expect(edge.provenance).toEqual({ source: 'agent' });\n expect(edge.valid_at).toBe(100);\n expect(edge.invalid_at).toBe(200);\n });\n });\n\n test('validates a minimal edge', () => {\n expect(edgeConverter.convert({ type: 'related', target: 'ownership' })).toSucceedAndSatisfy((edge) => {\n expect(edge.type).toBe('related');\n expect(edge.target).toBe('ownership');\n expect(edge.confidence).toBeUndefined();\n });\n });\n\n test('accepts a null invalid_at (still valid)', () => {\n expect(edgeConverter.convert({ type: 'related', target: 'x', invalid_at: null })).toSucceedAndSatisfy(\n (edge) => {\n expect(edge.invalid_at).toBeNull();\n }\n );\n });\n\n test('fails when invalid_at is neither a number nor null', () => {\n expect(edgeConverter.convert({ type: 'related', target: 'x', invalid_at: 'soon' })).toFail();\n });\n\n test('fails when target is missing', () => {\n expect(edgeConverter.convert({ type: 'related' })).toFail();\n });\n});\n\ndescribe('temporalConverter', () => {\n test('validates a temporal block', () => {\n expect(temporalConverter.convert({ valid_at: 1, invalid_at: null })).toSucceedWith({\n valid_at: 1,\n invalid_at: null\n });\n });\n\n test('validates an empty temporal block', () => {\n expect(temporalConverter.convert({})).toSucceedWith({});\n });\n});\n\ndescribe('envelopeConverter', () => {\n test('validates a full envelope without optional blocks', () => {\n expect(envelopeConverter.convert(validEnvelopeObject)).toSucceedAndSatisfy((envelope) => {\n expect(envelope.id).toBe('intro-to-rust');\n expect(envelope.tags).toEqual(['rust', 'systems']);\n expect(envelope.links).toHaveLength(1);\n expect(envelope.temporal).toBeUndefined();\n expect(envelope.embeddingRef).toBeUndefined();\n });\n });\n\n test('validates a temporal envelope with a string embeddingRef', () => {\n expect(\n envelopeConverter.convert({\n ...validEnvelopeObject,\n temporal: { valid_at: 10 },\n embeddingRef: 'vec-1'\n })\n ).toSucceedAndSatisfy((envelope) => {\n expect(envelope.temporal).toEqual({ valid_at: 10 });\n expect(envelope.embeddingRef).toBe('vec-1');\n });\n });\n\n test('accepts a null embeddingRef (not embedded)', () => {\n expect(envelopeConverter.convert({ ...validEnvelopeObject, embeddingRef: null })).toSucceedAndSatisfy(\n (envelope) => {\n expect(envelope.embeddingRef).toBeNull();\n }\n );\n });\n\n test('fails when embeddingRef is neither a string nor null', () => {\n expect(envelopeConverter.convert({ ...validEnvelopeObject, embeddingRef: 42 })).toFail();\n });\n\n test('fails when a required field is missing', () => {\n const missing: Record<string, unknown> = { ...validEnvelopeObject };\n delete missing.contentHash;\n expect(envelopeConverter.convert(missing)).toFail();\n });\n});\n\ndescribe('envelopeYamlConverter', () => {\n test('parses an envelope from a YAML string', () => {\n const yaml = serializeMemoryFile(\n envelopeConverter.convert(validEnvelopeObject).orThrow(),\n 'body text'\n ).orThrow();\n const frontmatter = splitFrontmatter(yaml).orThrow().frontmatter;\n expect(envelopeYamlConverter.convert(frontmatter)).toSucceedAndSatisfy((envelope) => {\n expect(envelope.id).toBe('intro-to-rust');\n });\n });\n\n test('fails on a YAML string that is not an object', () => {\n expect(envelopeYamlConverter.convert('just a scalar')).toFail();\n });\n});\n\ndescribe('splitFrontmatter / joinFrontmatter', () => {\n test('splits a well-formed memory file', () => {\n const raw = '---\\nid: x\\nkind: knowledge\\n---\\nThe body.\\nSecond line.';\n expect(splitFrontmatter(raw)).toSucceedWith({\n frontmatter: 'id: x\\nkind: knowledge',\n body: 'The body.\\nSecond line.'\n });\n });\n\n test('strips trailing CR so CRLF-authored files parse identically to LF', () => {\n const raw = '---\\r\\nid: x\\r\\nkind: knowledge\\r\\n---\\r\\nThe body.\\r\\nSecond line.';\n expect(splitFrontmatter(raw)).toSucceedWith({\n frontmatter: 'id: x\\nkind: knowledge',\n body: 'The body.\\nSecond line.'\n });\n });\n\n test('fails when the opening delimiter is missing', () => {\n expect(splitFrontmatter('no frontmatter here')).toFailWith(/missing opening frontmatter delimiter/i);\n });\n\n test('fails when the closing delimiter is missing', () => {\n expect(splitFrontmatter('---\\nid: x\\nstill going')).toFailWith(/missing closing frontmatter delimiter/i);\n });\n\n test('join adds a trailing newline to the frontmatter when absent', () => {\n expect(joinFrontmatter('id: x', 'body')).toBe('---\\nid: x\\n---\\nbody');\n });\n\n test('join does not double the trailing newline when present', () => {\n expect(joinFrontmatter('id: x\\n', 'body')).toBe('---\\nid: x\\n---\\nbody');\n });\n\n test('split and join round-trip', () => {\n const raw = '---\\nid: x\\n---\\nbody content';\n const parts = splitFrontmatter(raw).orThrow();\n expect(joinFrontmatter(parts.frontmatter, parts.body)).toBe(raw);\n });\n});\n\ndescribe('parseMemoryFile / serializeMemoryFile', () => {\n let registry: BodyConverterRegistry;\n const knowledgeKind: Kind = 'knowledge' as Kind;\n\n beforeEach(() => {\n registry = BodyConverterRegistry.create().orThrow();\n registry.register(knowledgeKind, Converters.string);\n });\n\n test('serializes then parses a record round-trip', () => {\n const envelope = envelopeConverter.convert(validEnvelopeObject).orThrow();\n const file = serializeMemoryFile(envelope, 'The knowledge body.').orThrow();\n expect(parseMemoryFile(file, registry)).toSucceedAndSatisfy((record) => {\n expect(record.envelope.id).toBe('intro-to-rust');\n expect(record.body).toBe('The knowledge body.');\n });\n });\n\n test('fails when the frontmatter cannot be split', () => {\n expect(parseMemoryFile('no delimiters', registry)).toFailWith(/missing opening frontmatter delimiter/i);\n });\n\n test('fails with envelope context when the envelope is invalid', () => {\n const file = '---\\nid: x\\n---\\nbody';\n expect(parseMemoryFile(file, registry)).toFailWith(/invalid envelope/i);\n });\n\n test('fails with body context when the body fails the registered converter', () => {\n const envelope = envelopeConverter.convert({ ...validEnvelopeObject, kind: 'unregistered' }).orThrow();\n const file = serializeMemoryFile(envelope, 'body').orThrow();\n expect(parseMemoryFile(file, registry)).toFailWith(/invalid body.*no converter registered/i);\n });\n\n test('serialize fails (with id context) when the envelope cannot be dumped to YAML', () => {\n const unserializable = { ...validEnvelopeObject, weird: BigInt(10) } as unknown as IMemoryEnvelope;\n expect(serializeMemoryFile(unserializable, 'body')).toFailWith(/failed to serialize envelope/i);\n });\n});\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memoryIndex.test.d.ts","sourceRoot":"","sources":["../../../../src/test/unit/index/memoryIndex.test.ts"],"names":[],"mappings":"AAKA,OAAO,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2026 Erik Fortune
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
require("@fgv/ts-utils-jest");
|
|
8
|
+
const index_1 = require("../../../index");
|
|
9
|
+
function makeEntry(spec) {
|
|
10
|
+
var _a, _b, _c, _d, _e, _f;
|
|
11
|
+
const record = {
|
|
12
|
+
envelope: index_1.envelopeConverter
|
|
13
|
+
.convert({
|
|
14
|
+
id: spec.id,
|
|
15
|
+
entityId: spec.id,
|
|
16
|
+
kind: (_a = spec.kind) !== null && _a !== void 0 ? _a : 'knowledge',
|
|
17
|
+
tags: (_b = spec.tags) !== null && _b !== void 0 ? _b : [],
|
|
18
|
+
links: ((_c = spec.links) !== null && _c !== void 0 ? _c : []).map((target) => ({ type: 'rel', target })),
|
|
19
|
+
created: 0,
|
|
20
|
+
updated: (_d = spec.updated) !== null && _d !== void 0 ? _d : 0,
|
|
21
|
+
seq: (_e = spec.seq) !== null && _e !== void 0 ? _e : 0,
|
|
22
|
+
contentHash: 'h',
|
|
23
|
+
provenance: { source: 'agent' }
|
|
24
|
+
})
|
|
25
|
+
.orThrow(),
|
|
26
|
+
body: `body-${spec.id}`
|
|
27
|
+
};
|
|
28
|
+
return { scope: ((_f = spec.scope) !== null && _f !== void 0 ? _f : 'knowledge'), record };
|
|
29
|
+
}
|
|
30
|
+
function ids(records) {
|
|
31
|
+
return records.map((r) => r.envelope.id);
|
|
32
|
+
}
|
|
33
|
+
describe('MemoryIndex', () => {
|
|
34
|
+
let index;
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
index = index_1.MemoryIndex.create().orThrow();
|
|
37
|
+
});
|
|
38
|
+
describe('create', () => {
|
|
39
|
+
test('starts empty', () => {
|
|
40
|
+
expect(index.entries()).toHaveLength(0);
|
|
41
|
+
expect(index.byKind('knowledge')).toHaveLength(0);
|
|
42
|
+
expect(index.byTag('any')).toHaveLength(0);
|
|
43
|
+
expect(index.byRecency()).toHaveLength(0);
|
|
44
|
+
expect(index.backlinks('none')).toHaveLength(0);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
describe('rebuild', () => {
|
|
48
|
+
test('indexes every entry and reports the count', () => {
|
|
49
|
+
const entries = [
|
|
50
|
+
makeEntry({ id: 'a', tags: ['x'], links: ['b'] }),
|
|
51
|
+
makeEntry({ id: 'b', tags: ['x', 'y'] })
|
|
52
|
+
];
|
|
53
|
+
expect(index.rebuild(entries)).toSucceedWith(2);
|
|
54
|
+
expect(index.entries()).toHaveLength(2);
|
|
55
|
+
expect([...ids(index.byKind('knowledge'))].sort()).toEqual(['a', 'b']);
|
|
56
|
+
expect([...ids(index.byTag('x'))].sort()).toEqual(['a', 'b']);
|
|
57
|
+
expect(ids(index.byTag('y'))).toEqual(['b']);
|
|
58
|
+
expect(index.backlinks('b')).toEqual(['a']);
|
|
59
|
+
});
|
|
60
|
+
test('clears prior state', () => {
|
|
61
|
+
index.rebuild([makeEntry({ id: 'a', tags: ['x'] })]).orThrow();
|
|
62
|
+
expect(index.rebuild([makeEntry({ id: 'c', tags: ['z'] })])).toSucceedWith(1);
|
|
63
|
+
expect(ids(index.entries().map((e) => e.record))).toEqual(['c']);
|
|
64
|
+
expect(index.byTag('x')).toHaveLength(0);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
describe('patch', () => {
|
|
68
|
+
test('put inserts a new entry and returns it', () => {
|
|
69
|
+
const entry = makeEntry({ id: 'a', tags: ['x'], links: ['b'] });
|
|
70
|
+
expect(index.patch('put', entry)).toSucceedWith(entry);
|
|
71
|
+
expect(ids(index.byKind('knowledge'))).toEqual(['a']);
|
|
72
|
+
expect(index.backlinks('b')).toEqual(['a']);
|
|
73
|
+
});
|
|
74
|
+
test('put on an existing key replaces stale associations', () => {
|
|
75
|
+
index.patch('put', makeEntry({ id: 'a', kind: 'knowledge', tags: ['old'], links: ['b'] })).orThrow();
|
|
76
|
+
index.patch('put', makeEntry({ id: 'a', kind: 'note', tags: ['new'], links: ['c'] })).orThrow();
|
|
77
|
+
expect(index.entries()).toHaveLength(1);
|
|
78
|
+
expect(index.byTag('old')).toHaveLength(0);
|
|
79
|
+
expect(ids(index.byTag('new'))).toEqual(['a']);
|
|
80
|
+
expect(index.byKind('knowledge')).toHaveLength(0);
|
|
81
|
+
expect(ids(index.byKind('note'))).toEqual(['a']);
|
|
82
|
+
expect(index.backlinks('b')).toHaveLength(0);
|
|
83
|
+
expect(index.backlinks('c')).toEqual(['a']);
|
|
84
|
+
});
|
|
85
|
+
test('delete removes the entry and its associations', () => {
|
|
86
|
+
const entry = makeEntry({ id: 'a', tags: ['x'], links: ['b'] });
|
|
87
|
+
index.patch('put', entry).orThrow();
|
|
88
|
+
expect(index.patch('delete', entry)).toSucceedWith(entry);
|
|
89
|
+
expect(index.entries()).toHaveLength(0);
|
|
90
|
+
expect(index.byTag('x')).toHaveLength(0);
|
|
91
|
+
expect(index.backlinks('b')).toHaveLength(0);
|
|
92
|
+
});
|
|
93
|
+
test('delete of an absent key is a no-op that still succeeds', () => {
|
|
94
|
+
const entry = makeEntry({ id: 'ghost' });
|
|
95
|
+
expect(index.patch('delete', entry)).toSucceedWith(entry);
|
|
96
|
+
expect(index.entries()).toHaveLength(0);
|
|
97
|
+
});
|
|
98
|
+
test('distinguishes entries with the same id across scopes', () => {
|
|
99
|
+
index.patch('put', makeEntry({ id: 'turn-0', scope: 'conversations/c1' })).orThrow();
|
|
100
|
+
index.patch('put', makeEntry({ id: 'turn-0', scope: 'conversations/c2' })).orThrow();
|
|
101
|
+
expect(index.entries()).toHaveLength(2);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
describe('byRecency', () => {
|
|
105
|
+
test('orders most-recently-updated first, breaking ties by seq', () => {
|
|
106
|
+
index
|
|
107
|
+
.rebuild([
|
|
108
|
+
makeEntry({ id: 'old', updated: 100, seq: 1 }),
|
|
109
|
+
makeEntry({ id: 'newest', updated: 300, seq: 3 }),
|
|
110
|
+
makeEntry({ id: 'tie-lo', updated: 200, seq: 4 }),
|
|
111
|
+
makeEntry({ id: 'tie-hi', updated: 200, seq: 9 })
|
|
112
|
+
])
|
|
113
|
+
.orThrow();
|
|
114
|
+
expect(ids(index.byRecency())).toEqual(['newest', 'tie-hi', 'tie-lo', 'old']);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
describe('backlinks', () => {
|
|
118
|
+
test('aggregates multiple sources pointing at one target', () => {
|
|
119
|
+
index
|
|
120
|
+
.rebuild([makeEntry({ id: 'a', links: ['target'] }), makeEntry({ id: 'b', links: ['target'] })])
|
|
121
|
+
.orThrow();
|
|
122
|
+
expect([...index.backlinks('target')].sort()).toEqual(['a', 'b']);
|
|
123
|
+
});
|
|
124
|
+
test('drops the target set once the last source is removed', () => {
|
|
125
|
+
const a = makeEntry({ id: 'a', links: ['target'] });
|
|
126
|
+
index.patch('put', a).orThrow();
|
|
127
|
+
index.patch('delete', a).orThrow();
|
|
128
|
+
expect(index.backlinks('target')).toHaveLength(0);
|
|
129
|
+
});
|
|
130
|
+
test('tracks same-id sources across scopes independently', () => {
|
|
131
|
+
// Two distinct records that share the id 'a' under different scopes, both
|
|
132
|
+
// linking 'target'. Removing one must NOT drop the other's inbound edge.
|
|
133
|
+
const a1 = makeEntry({ id: 'a', scope: 'conversations/c1', links: ['target'] });
|
|
134
|
+
const a2 = makeEntry({ id: 'a', scope: 'conversations/c2', links: ['target'] });
|
|
135
|
+
index.patch('put', a1).orThrow();
|
|
136
|
+
index.patch('put', a2).orThrow();
|
|
137
|
+
expect(index.backlinks('target')).toEqual(['a', 'a']);
|
|
138
|
+
index.patch('delete', a1).orThrow();
|
|
139
|
+
expect(index.backlinks('target')).toEqual(['a']);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
describe('duplicate associations', () => {
|
|
143
|
+
test('a record with duplicate tags and links is cleaned up exactly once on delete', () => {
|
|
144
|
+
const entry = makeEntry({ id: 'a', tags: ['dup', 'dup'], links: ['t', 't'] });
|
|
145
|
+
index.patch('put', entry).orThrow();
|
|
146
|
+
expect(ids(index.byTag('dup'))).toEqual(['a']);
|
|
147
|
+
expect(index.backlinks('t')).toEqual(['a']);
|
|
148
|
+
index.patch('delete', entry).orThrow();
|
|
149
|
+
expect(index.byTag('dup')).toHaveLength(0);
|
|
150
|
+
expect(index.backlinks('t')).toHaveLength(0);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
//# sourceMappingURL=memoryIndex.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memoryIndex.test.js","sourceRoot":"","sources":["../../../../src/test/unit/index/memoryIndex.test.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport '@fgv/ts-utils-jest';\nimport {\n IIndexedMemoryRecord,\n IMemoryRecord,\n Kind,\n MemoryId,\n MemoryIndex,\n MemoryScopeKey,\n Tag,\n envelopeConverter\n} from '../../../index';\n\ninterface IRecordSpec {\n readonly id: string;\n readonly scope?: string;\n readonly kind?: string;\n readonly tags?: ReadonlyArray<string>;\n readonly links?: ReadonlyArray<string>;\n readonly updated?: number;\n readonly seq?: number;\n}\n\nfunction makeEntry(spec: IRecordSpec): IIndexedMemoryRecord {\n const record: IMemoryRecord<unknown> = {\n envelope: envelopeConverter\n .convert({\n id: spec.id,\n entityId: spec.id,\n kind: spec.kind ?? 'knowledge',\n tags: spec.tags ?? [],\n links: (spec.links ?? []).map((target) => ({ type: 'rel', target })),\n created: 0,\n updated: spec.updated ?? 0,\n seq: spec.seq ?? 0,\n contentHash: 'h',\n provenance: { source: 'agent' }\n })\n .orThrow(),\n body: `body-${spec.id}`\n };\n return { scope: (spec.scope ?? 'knowledge') as MemoryScopeKey, record };\n}\n\nfunction ids(records: ReadonlyArray<IMemoryRecord<unknown>>): ReadonlyArray<string> {\n return records.map((r) => r.envelope.id as string);\n}\n\ndescribe('MemoryIndex', () => {\n let index: MemoryIndex;\n\n beforeEach(() => {\n index = MemoryIndex.create().orThrow();\n });\n\n describe('create', () => {\n test('starts empty', () => {\n expect(index.entries()).toHaveLength(0);\n expect(index.byKind('knowledge' as Kind)).toHaveLength(0);\n expect(index.byTag('any' as Tag)).toHaveLength(0);\n expect(index.byRecency()).toHaveLength(0);\n expect(index.backlinks('none' as MemoryId)).toHaveLength(0);\n });\n });\n\n describe('rebuild', () => {\n test('indexes every entry and reports the count', () => {\n const entries = [\n makeEntry({ id: 'a', tags: ['x'], links: ['b'] }),\n makeEntry({ id: 'b', tags: ['x', 'y'] })\n ];\n expect(index.rebuild(entries)).toSucceedWith(2);\n expect(index.entries()).toHaveLength(2);\n expect([...ids(index.byKind('knowledge' as Kind))].sort()).toEqual(['a', 'b']);\n expect([...ids(index.byTag('x' as Tag))].sort()).toEqual(['a', 'b']);\n expect(ids(index.byTag('y' as Tag))).toEqual(['b']);\n expect(index.backlinks('b' as MemoryId)).toEqual(['a']);\n });\n\n test('clears prior state', () => {\n index.rebuild([makeEntry({ id: 'a', tags: ['x'] })]).orThrow();\n expect(index.rebuild([makeEntry({ id: 'c', tags: ['z'] })])).toSucceedWith(1);\n expect(ids(index.entries().map((e) => e.record))).toEqual(['c']);\n expect(index.byTag('x' as Tag)).toHaveLength(0);\n });\n });\n\n describe('patch', () => {\n test('put inserts a new entry and returns it', () => {\n const entry = makeEntry({ id: 'a', tags: ['x'], links: ['b'] });\n expect(index.patch('put', entry)).toSucceedWith(entry);\n expect(ids(index.byKind('knowledge' as Kind))).toEqual(['a']);\n expect(index.backlinks('b' as MemoryId)).toEqual(['a']);\n });\n\n test('put on an existing key replaces stale associations', () => {\n index.patch('put', makeEntry({ id: 'a', kind: 'knowledge', tags: ['old'], links: ['b'] })).orThrow();\n index.patch('put', makeEntry({ id: 'a', kind: 'note', tags: ['new'], links: ['c'] })).orThrow();\n expect(index.entries()).toHaveLength(1);\n expect(index.byTag('old' as Tag)).toHaveLength(0);\n expect(ids(index.byTag('new' as Tag))).toEqual(['a']);\n expect(index.byKind('knowledge' as Kind)).toHaveLength(0);\n expect(ids(index.byKind('note' as Kind))).toEqual(['a']);\n expect(index.backlinks('b' as MemoryId)).toHaveLength(0);\n expect(index.backlinks('c' as MemoryId)).toEqual(['a']);\n });\n\n test('delete removes the entry and its associations', () => {\n const entry = makeEntry({ id: 'a', tags: ['x'], links: ['b'] });\n index.patch('put', entry).orThrow();\n expect(index.patch('delete', entry)).toSucceedWith(entry);\n expect(index.entries()).toHaveLength(0);\n expect(index.byTag('x' as Tag)).toHaveLength(0);\n expect(index.backlinks('b' as MemoryId)).toHaveLength(0);\n });\n\n test('delete of an absent key is a no-op that still succeeds', () => {\n const entry = makeEntry({ id: 'ghost' });\n expect(index.patch('delete', entry)).toSucceedWith(entry);\n expect(index.entries()).toHaveLength(0);\n });\n\n test('distinguishes entries with the same id across scopes', () => {\n index.patch('put', makeEntry({ id: 'turn-0', scope: 'conversations/c1' })).orThrow();\n index.patch('put', makeEntry({ id: 'turn-0', scope: 'conversations/c2' })).orThrow();\n expect(index.entries()).toHaveLength(2);\n });\n });\n\n describe('byRecency', () => {\n test('orders most-recently-updated first, breaking ties by seq', () => {\n index\n .rebuild([\n makeEntry({ id: 'old', updated: 100, seq: 1 }),\n makeEntry({ id: 'newest', updated: 300, seq: 3 }),\n makeEntry({ id: 'tie-lo', updated: 200, seq: 4 }),\n makeEntry({ id: 'tie-hi', updated: 200, seq: 9 })\n ])\n .orThrow();\n expect(ids(index.byRecency())).toEqual(['newest', 'tie-hi', 'tie-lo', 'old']);\n });\n });\n\n describe('backlinks', () => {\n test('aggregates multiple sources pointing at one target', () => {\n index\n .rebuild([makeEntry({ id: 'a', links: ['target'] }), makeEntry({ id: 'b', links: ['target'] })])\n .orThrow();\n expect([...index.backlinks('target' as MemoryId)].sort()).toEqual(['a', 'b']);\n });\n\n test('drops the target set once the last source is removed', () => {\n const a = makeEntry({ id: 'a', links: ['target'] });\n index.patch('put', a).orThrow();\n index.patch('delete', a).orThrow();\n expect(index.backlinks('target' as MemoryId)).toHaveLength(0);\n });\n\n test('tracks same-id sources across scopes independently', () => {\n // Two distinct records that share the id 'a' under different scopes, both\n // linking 'target'. Removing one must NOT drop the other's inbound edge.\n const a1 = makeEntry({ id: 'a', scope: 'conversations/c1', links: ['target'] });\n const a2 = makeEntry({ id: 'a', scope: 'conversations/c2', links: ['target'] });\n index.patch('put', a1).orThrow();\n index.patch('put', a2).orThrow();\n expect(index.backlinks('target' as MemoryId)).toEqual(['a', 'a']);\n index.patch('delete', a1).orThrow();\n expect(index.backlinks('target' as MemoryId)).toEqual(['a']);\n });\n });\n\n describe('duplicate associations', () => {\n test('a record with duplicate tags and links is cleaned up exactly once on delete', () => {\n const entry = makeEntry({ id: 'a', tags: ['dup', 'dup'], links: ['t', 't'] });\n index.patch('put', entry).orThrow();\n expect(ids(index.byTag('dup' as Tag))).toEqual(['a']);\n expect(index.backlinks('t' as MemoryId)).toEqual(['a']);\n index.patch('delete', entry).orThrow();\n expect(index.byTag('dup' as Tag)).toHaveLength(0);\n expect(index.backlinks('t' as MemoryId)).toHaveLength(0);\n });\n });\n});\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memoryObservationStore.test.d.ts","sourceRoot":"","sources":["../../../../src/test/unit/observe/memoryObservationStore.test.ts"],"names":[],"mappings":"AAKA,OAAO,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2026 Erik Fortune
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
require("@fgv/ts-utils-jest");
|
|
8
|
+
const index_1 = require("../../../index");
|
|
9
|
+
function obs(spec) {
|
|
10
|
+
var _a, _b, _c;
|
|
11
|
+
return {
|
|
12
|
+
seq: spec.seq,
|
|
13
|
+
timestamp: (_a = spec.timestamp) !== null && _a !== void 0 ? _a : spec.seq,
|
|
14
|
+
phase: (_b = spec.phase) !== null && _b !== void 0 ? _b : 'write',
|
|
15
|
+
scope: spec.scope === undefined ? undefined : spec.scope,
|
|
16
|
+
kind: spec.kind === undefined ? undefined : spec.kind,
|
|
17
|
+
id: spec.id === undefined ? undefined : spec.id,
|
|
18
|
+
outcome: (_c = spec.outcome) !== null && _c !== void 0 ? _c : 'success'
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function seqs(records) {
|
|
22
|
+
return records.map((r) => r.seq);
|
|
23
|
+
}
|
|
24
|
+
describe('MemoryObservationStore', () => {
|
|
25
|
+
describe('create', () => {
|
|
26
|
+
test('builds with defaults', () => {
|
|
27
|
+
expect(index_1.MemoryObservationStore.create()).toSucceedAndSatisfy((store) => {
|
|
28
|
+
expect(store.size).toBe(0);
|
|
29
|
+
expect(store.lastSeq).toBe(0);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
test('rejects a non-positive-integer maxRecords', () => {
|
|
33
|
+
expect(index_1.MemoryObservationStore.create({ maxRecords: 0 })).toFailWith(/positive integer/i);
|
|
34
|
+
expect(index_1.MemoryObservationStore.create({ maxRecords: 2.5 })).toFailWith(/positive integer/i);
|
|
35
|
+
expect(index_1.MemoryObservationStore.create({ maxRecords: -1 })).toFailWith(/positive integer/i);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
describe('observe', () => {
|
|
39
|
+
test('retains records and tracks size + lastSeq', async () => {
|
|
40
|
+
const store = index_1.MemoryObservationStore.create().orThrow();
|
|
41
|
+
expect(await store.observe(obs({ seq: 1 }))).toSucceed();
|
|
42
|
+
expect(await store.observe(obs({ seq: 2 }))).toSucceed();
|
|
43
|
+
expect(store.size).toBe(2);
|
|
44
|
+
expect(store.lastSeq).toBe(2);
|
|
45
|
+
});
|
|
46
|
+
test('evicts the oldest beyond maxRecords, preserving lastSeq', async () => {
|
|
47
|
+
const store = index_1.MemoryObservationStore.create({ maxRecords: 2 }).orThrow();
|
|
48
|
+
await store.observe(obs({ seq: 1 }));
|
|
49
|
+
await store.observe(obs({ seq: 2 }));
|
|
50
|
+
await store.observe(obs({ seq: 3 }));
|
|
51
|
+
expect(store.size).toBe(2);
|
|
52
|
+
expect(store.lastSeq).toBe(3);
|
|
53
|
+
expect(seqs(store.query())).toEqual([2, 3]);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
describe('query', () => {
|
|
57
|
+
let store;
|
|
58
|
+
beforeEach(async () => {
|
|
59
|
+
store = index_1.MemoryObservationStore.create().orThrow();
|
|
60
|
+
await store.observe(obs({
|
|
61
|
+
seq: 1,
|
|
62
|
+
timestamp: 100,
|
|
63
|
+
phase: 'write',
|
|
64
|
+
scope: 'knowledge',
|
|
65
|
+
kind: 'knowledge',
|
|
66
|
+
outcome: 'success'
|
|
67
|
+
}));
|
|
68
|
+
await store.observe(obs({
|
|
69
|
+
seq: 2,
|
|
70
|
+
timestamp: 200,
|
|
71
|
+
phase: 'read',
|
|
72
|
+
scope: 'knowledge',
|
|
73
|
+
kind: 'knowledge',
|
|
74
|
+
outcome: 'failure'
|
|
75
|
+
}));
|
|
76
|
+
await store.observe(obs({ seq: 3, timestamp: 300, phase: 'delete', scope: 'other', kind: 'note', outcome: 'success' }));
|
|
77
|
+
});
|
|
78
|
+
test('returns all records oldest-first with no criteria', () => {
|
|
79
|
+
expect(seqs(store.query())).toEqual([1, 2, 3]);
|
|
80
|
+
});
|
|
81
|
+
test('pages by sinceSeq', () => {
|
|
82
|
+
expect(seqs(store.query({ sinceSeq: 1 }))).toEqual([2, 3]);
|
|
83
|
+
});
|
|
84
|
+
test('limits to the most-recent N', () => {
|
|
85
|
+
expect(seqs(store.query({ limit: 2 }))).toEqual([2, 3]);
|
|
86
|
+
});
|
|
87
|
+
test('filters by timestamp window', () => {
|
|
88
|
+
expect(seqs(store.query({ since: 200 }))).toEqual([2, 3]);
|
|
89
|
+
expect(seqs(store.query({ until: 200 }))).toEqual([1, 2]);
|
|
90
|
+
expect(seqs(store.query({ since: 150, until: 250 }))).toEqual([2]);
|
|
91
|
+
});
|
|
92
|
+
test('filters by scope', () => {
|
|
93
|
+
expect(seqs(store.query({ scope: 'knowledge' }))).toEqual([1, 2]);
|
|
94
|
+
});
|
|
95
|
+
test('filters by kind', () => {
|
|
96
|
+
expect(seqs(store.query({ kind: 'note' }))).toEqual([3]);
|
|
97
|
+
});
|
|
98
|
+
test('filters by phase', () => {
|
|
99
|
+
expect(seqs(store.query({ phase: 'read' }))).toEqual([2]);
|
|
100
|
+
});
|
|
101
|
+
test('filters by outcome', () => {
|
|
102
|
+
expect(seqs(store.query({ outcome: 'failure' }))).toEqual([2]);
|
|
103
|
+
});
|
|
104
|
+
test('combines criteria with AND', () => {
|
|
105
|
+
expect(seqs(store.query({ scope: 'knowledge', outcome: 'success' }))).toEqual([1]);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
describe('clear', () => {
|
|
109
|
+
test('drops records but preserves lastSeq as a cursor floor', async () => {
|
|
110
|
+
const store = index_1.MemoryObservationStore.create().orThrow();
|
|
111
|
+
await store.observe(obs({ seq: 1 }));
|
|
112
|
+
await store.observe(obs({ seq: 2 }));
|
|
113
|
+
store.clear();
|
|
114
|
+
expect(store.size).toBe(0);
|
|
115
|
+
expect(store.lastSeq).toBe(2);
|
|
116
|
+
expect(store.query()).toEqual([]);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
//# sourceMappingURL=memoryObservationStore.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memoryObservationStore.test.js","sourceRoot":"","sources":["../../../../src/test/unit/observe/memoryObservationStore.test.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport '@fgv/ts-utils-jest';\nimport {\n IMemoryObservationRecord,\n Kind,\n MemoryId,\n MemoryObservationPhase,\n MemoryObservationStore,\n MemoryScopeKey\n} from '../../../index';\n\ninterface IObsSpec {\n readonly seq: number;\n readonly timestamp?: number;\n readonly phase?: MemoryObservationPhase;\n readonly scope?: string;\n readonly kind?: string;\n readonly id?: string;\n readonly outcome?: 'success' | 'failure';\n}\n\nfunction obs(spec: IObsSpec): IMemoryObservationRecord {\n return {\n seq: spec.seq,\n timestamp: spec.timestamp ?? spec.seq,\n phase: spec.phase ?? 'write',\n scope: spec.scope === undefined ? undefined : (spec.scope as MemoryScopeKey),\n kind: spec.kind === undefined ? undefined : (spec.kind as Kind),\n id: spec.id === undefined ? undefined : (spec.id as MemoryId),\n outcome: spec.outcome ?? 'success'\n };\n}\n\nfunction seqs(records: ReadonlyArray<IMemoryObservationRecord>): ReadonlyArray<number> {\n return records.map((r) => r.seq);\n}\n\ndescribe('MemoryObservationStore', () => {\n describe('create', () => {\n test('builds with defaults', () => {\n expect(MemoryObservationStore.create()).toSucceedAndSatisfy((store: MemoryObservationStore) => {\n expect(store.size).toBe(0);\n expect(store.lastSeq).toBe(0);\n });\n });\n\n test('rejects a non-positive-integer maxRecords', () => {\n expect(MemoryObservationStore.create({ maxRecords: 0 })).toFailWith(/positive integer/i);\n expect(MemoryObservationStore.create({ maxRecords: 2.5 })).toFailWith(/positive integer/i);\n expect(MemoryObservationStore.create({ maxRecords: -1 })).toFailWith(/positive integer/i);\n });\n });\n\n describe('observe', () => {\n test('retains records and tracks size + lastSeq', async () => {\n const store = MemoryObservationStore.create().orThrow();\n expect(await store.observe(obs({ seq: 1 }))).toSucceed();\n expect(await store.observe(obs({ seq: 2 }))).toSucceed();\n expect(store.size).toBe(2);\n expect(store.lastSeq).toBe(2);\n });\n\n test('evicts the oldest beyond maxRecords, preserving lastSeq', async () => {\n const store = MemoryObservationStore.create({ maxRecords: 2 }).orThrow();\n await store.observe(obs({ seq: 1 }));\n await store.observe(obs({ seq: 2 }));\n await store.observe(obs({ seq: 3 }));\n expect(store.size).toBe(2);\n expect(store.lastSeq).toBe(3);\n expect(seqs(store.query())).toEqual([2, 3]);\n });\n });\n\n describe('query', () => {\n let store: MemoryObservationStore;\n\n beforeEach(async () => {\n store = MemoryObservationStore.create().orThrow();\n await store.observe(\n obs({\n seq: 1,\n timestamp: 100,\n phase: 'write',\n scope: 'knowledge',\n kind: 'knowledge',\n outcome: 'success'\n })\n );\n await store.observe(\n obs({\n seq: 2,\n timestamp: 200,\n phase: 'read',\n scope: 'knowledge',\n kind: 'knowledge',\n outcome: 'failure'\n })\n );\n await store.observe(\n obs({ seq: 3, timestamp: 300, phase: 'delete', scope: 'other', kind: 'note', outcome: 'success' })\n );\n });\n\n test('returns all records oldest-first with no criteria', () => {\n expect(seqs(store.query())).toEqual([1, 2, 3]);\n });\n\n test('pages by sinceSeq', () => {\n expect(seqs(store.query({ sinceSeq: 1 }))).toEqual([2, 3]);\n });\n\n test('limits to the most-recent N', () => {\n expect(seqs(store.query({ limit: 2 }))).toEqual([2, 3]);\n });\n\n test('filters by timestamp window', () => {\n expect(seqs(store.query({ since: 200 }))).toEqual([2, 3]);\n expect(seqs(store.query({ until: 200 }))).toEqual([1, 2]);\n expect(seqs(store.query({ since: 150, until: 250 }))).toEqual([2]);\n });\n\n test('filters by scope', () => {\n expect(seqs(store.query({ scope: 'knowledge' as MemoryScopeKey }))).toEqual([1, 2]);\n });\n\n test('filters by kind', () => {\n expect(seqs(store.query({ kind: 'note' as Kind }))).toEqual([3]);\n });\n\n test('filters by phase', () => {\n expect(seqs(store.query({ phase: 'read' }))).toEqual([2]);\n });\n\n test('filters by outcome', () => {\n expect(seqs(store.query({ outcome: 'failure' }))).toEqual([2]);\n });\n\n test('combines criteria with AND', () => {\n expect(seqs(store.query({ scope: 'knowledge' as MemoryScopeKey, outcome: 'success' }))).toEqual([1]);\n });\n });\n\n describe('clear', () => {\n test('drops records but preserves lastSeq as a cursor floor', async () => {\n const store = MemoryObservationStore.create().orThrow();\n await store.observe(obs({ seq: 1 }));\n await store.observe(obs({ seq: 2 }));\n store.clear();\n expect(store.size).toBe(0);\n expect(store.lastSeq).toBe(2);\n expect(store.query()).toEqual([]);\n });\n });\n});\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linkTraversalRetriever.test.d.ts","sourceRoot":"","sources":["../../../../src/test/unit/retrieve/linkTraversalRetriever.test.ts"],"names":[],"mappings":"AAKA,OAAO,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2026 Erik Fortune
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
require("@fgv/ts-utils-jest");
|
|
8
|
+
const index_1 = require("../../../index");
|
|
9
|
+
function makeEntry(spec) {
|
|
10
|
+
var _a, _b, _c, _d, _e, _f;
|
|
11
|
+
const record = {
|
|
12
|
+
envelope: index_1.envelopeConverter
|
|
13
|
+
.convert({
|
|
14
|
+
id: spec.id,
|
|
15
|
+
entityId: spec.id,
|
|
16
|
+
kind: (_a = spec.kind) !== null && _a !== void 0 ? _a : 'experience',
|
|
17
|
+
tags: (_b = spec.tags) !== null && _b !== void 0 ? _b : [],
|
|
18
|
+
links: ((_c = spec.links) !== null && _c !== void 0 ? _c : []).map((target) => ({ type: 'rel', target })),
|
|
19
|
+
created: 0,
|
|
20
|
+
updated: (_d = spec.updated) !== null && _d !== void 0 ? _d : 0,
|
|
21
|
+
seq: (_e = spec.seq) !== null && _e !== void 0 ? _e : 0,
|
|
22
|
+
contentHash: 'h',
|
|
23
|
+
provenance: { source: 'agent' }
|
|
24
|
+
})
|
|
25
|
+
.orThrow(),
|
|
26
|
+
body: `body-${spec.id}`
|
|
27
|
+
};
|
|
28
|
+
return { scope: ((_f = spec.scope) !== null && _f !== void 0 ? _f : 'experience'), record };
|
|
29
|
+
}
|
|
30
|
+
function buildIndex(specs) {
|
|
31
|
+
const index = index_1.MemoryIndex.create().orThrow();
|
|
32
|
+
index.rebuild(specs.map(makeEntry)).orThrow();
|
|
33
|
+
return index;
|
|
34
|
+
}
|
|
35
|
+
function ids(records) {
|
|
36
|
+
return records.map((r) => r.envelope.id);
|
|
37
|
+
}
|
|
38
|
+
describe('LinkTraversalRetriever', () => {
|
|
39
|
+
test('reports the link-traversal capability', () => {
|
|
40
|
+
const index = buildIndex([]);
|
|
41
|
+
const retriever = index_1.LinkTraversalRetriever.create(index).orThrow();
|
|
42
|
+
expect(retriever.capabilities).toEqual({
|
|
43
|
+
supportsSemanticRecall: false,
|
|
44
|
+
supportsTemporalQuery: false,
|
|
45
|
+
supportsLinkTraversal: true
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
describe('outbound traversal (linkedFrom)', () => {
|
|
49
|
+
test('returns the direct outbound neighbors at the default single hop', async () => {
|
|
50
|
+
const index = buildIndex([
|
|
51
|
+
{ id: 'a', links: ['b', 'c'] },
|
|
52
|
+
{ id: 'b', updated: 20 },
|
|
53
|
+
{ id: 'c', updated: 10 }
|
|
54
|
+
]);
|
|
55
|
+
const retriever = index_1.LinkTraversalRetriever.create(index).orThrow();
|
|
56
|
+
expect(await retriever.retrieve({ linkedFrom: 'a' })).toSucceedAndSatisfy((records) => {
|
|
57
|
+
// Recency-ordered: b (updated 20) before c (updated 10). 'a' (the seed) excluded.
|
|
58
|
+
expect(ids(records)).toEqual(['b', 'c']);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
test('stops at the default single hop (does not reach 2-hop neighbors)', async () => {
|
|
62
|
+
const index = buildIndex([{ id: 'a', links: ['b'] }, { id: 'b', links: ['c'] }, { id: 'c' }]);
|
|
63
|
+
const retriever = index_1.LinkTraversalRetriever.create(index).orThrow();
|
|
64
|
+
expect(await retriever.retrieve({ linkedFrom: 'a' })).toSucceedAndSatisfy((records) => {
|
|
65
|
+
expect(ids(records)).toEqual(['b']);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
test('follows multiple hops when hops is raised', async () => {
|
|
69
|
+
const index = buildIndex([{ id: 'a', links: ['b'] }, { id: 'b', links: ['c'] }, { id: 'c' }]);
|
|
70
|
+
const retriever = index_1.LinkTraversalRetriever.create(index).orThrow();
|
|
71
|
+
expect(await retriever.retrieve({ linkedFrom: 'a', hops: 2 })).toSucceedAndSatisfy((records) => {
|
|
72
|
+
expect(ids(records).sort()).toEqual(['b', 'c']);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
describe('inbound traversal (linkedTo)', () => {
|
|
77
|
+
test('returns the records whose edges point at the seed (backlinks)', async () => {
|
|
78
|
+
const index = buildIndex([
|
|
79
|
+
{ id: 'a', links: ['target'], updated: 30 },
|
|
80
|
+
{ id: 'b', links: ['target'], updated: 10 },
|
|
81
|
+
{ id: 'target' }
|
|
82
|
+
]);
|
|
83
|
+
const retriever = index_1.LinkTraversalRetriever.create(index).orThrow();
|
|
84
|
+
expect(await retriever.retrieve({ linkedTo: 'target' })).toSucceedAndSatisfy((records) => {
|
|
85
|
+
expect(ids(records)).toEqual(['a', 'b']);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
describe('cycle safety', () => {
|
|
90
|
+
test('terminates on a self-loop and excludes the seed', async () => {
|
|
91
|
+
const index = buildIndex([{ id: 'a', links: ['a'] }]);
|
|
92
|
+
const retriever = index_1.LinkTraversalRetriever.create(index).orThrow();
|
|
93
|
+
expect(await retriever.retrieve({ linkedFrom: 'a', hops: 5 })).toSucceedAndSatisfy((records) => {
|
|
94
|
+
expect(records).toHaveLength(0);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
test('terminates on a multi-hop cycle without revisiting', async () => {
|
|
98
|
+
const index = buildIndex([
|
|
99
|
+
{ id: 'a', links: ['b'] },
|
|
100
|
+
{ id: 'b', links: ['c'] },
|
|
101
|
+
{ id: 'c', links: ['a'] }
|
|
102
|
+
]);
|
|
103
|
+
const retriever = index_1.LinkTraversalRetriever.create(index).orThrow();
|
|
104
|
+
expect(await retriever.retrieve({ linkedFrom: 'a', hops: 10 })).toSucceedAndSatisfy((records) => {
|
|
105
|
+
// a→b→c→a: reaches b and c, then the edge back to the seed is pruned.
|
|
106
|
+
expect(ids(records).sort()).toEqual(['b', 'c']);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
test('traverses a cross-kind edge (experience derivedFrom a knowledge doc)', async () => {
|
|
111
|
+
const index = buildIndex([
|
|
112
|
+
{ id: 'turn-5', scope: 'conversations/conv-1', kind: 'summarized-turn', links: ['doc-1'] },
|
|
113
|
+
{ id: 'doc-1', scope: 'knowledge', kind: 'knowledge' }
|
|
114
|
+
]);
|
|
115
|
+
const retriever = index_1.LinkTraversalRetriever.create(index).orThrow();
|
|
116
|
+
expect(await retriever.retrieve({ linkedFrom: 'turn-5' })).toSucceedAndSatisfy((records) => {
|
|
117
|
+
expect(ids(records)).toEqual(['doc-1']);
|
|
118
|
+
expect(records[0].envelope.kind).toBe('knowledge');
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
describe('post-filtering and bounds', () => {
|
|
122
|
+
test('applies the kind pre-filter to the reached records', async () => {
|
|
123
|
+
const index = buildIndex([
|
|
124
|
+
{ id: 'a', links: ['b', 'c'] },
|
|
125
|
+
{ id: 'b', kind: 'keep' },
|
|
126
|
+
{ id: 'c', kind: 'drop' }
|
|
127
|
+
]);
|
|
128
|
+
const retriever = index_1.LinkTraversalRetriever.create(index).orThrow();
|
|
129
|
+
expect(await retriever.retrieve({ linkedFrom: 'a', kind: 'keep' })).toSucceedAndSatisfy((records) => {
|
|
130
|
+
expect(ids(records)).toEqual(['b']);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
test('applies the limit after ordering', async () => {
|
|
134
|
+
const index = buildIndex([
|
|
135
|
+
{ id: 'a', links: ['b', 'c', 'd'] },
|
|
136
|
+
{ id: 'b', updated: 30 },
|
|
137
|
+
{ id: 'c', updated: 20 },
|
|
138
|
+
{ id: 'd', updated: 10 }
|
|
139
|
+
]);
|
|
140
|
+
const retriever = index_1.LinkTraversalRetriever.create(index).orThrow();
|
|
141
|
+
expect(await retriever.retrieve({ linkedFrom: 'a', limit: 2 })).toSucceedAndSatisfy((records) => {
|
|
142
|
+
expect(ids(records)).toEqual(['b', 'c']);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
test('returns empty when the seed is not in the index', async () => {
|
|
146
|
+
const index = buildIndex([{ id: 'a', links: ['b'] }, { id: 'b' }]);
|
|
147
|
+
const retriever = index_1.LinkTraversalRetriever.create(index).orThrow();
|
|
148
|
+
expect(await retriever.retrieve({ linkedFrom: 'missing' })).toSucceedWith([]);
|
|
149
|
+
});
|
|
150
|
+
test('resolves every record sharing a reached id (cross-scope id reuse)', async () => {
|
|
151
|
+
const index = buildIndex([
|
|
152
|
+
{ id: 'seed', scope: 'a', links: ['dup'] },
|
|
153
|
+
{ id: 'dup', scope: 'a' },
|
|
154
|
+
{ id: 'dup', scope: 'b' }
|
|
155
|
+
]);
|
|
156
|
+
const retriever = index_1.LinkTraversalRetriever.create(index).orThrow();
|
|
157
|
+
expect(await retriever.retrieve({ linkedFrom: 'seed' })).toSucceedAndSatisfy((records) => {
|
|
158
|
+
expect(records).toHaveLength(2);
|
|
159
|
+
expect(ids(records)).toEqual(['dup', 'dup']);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
describe('loud degradation', () => {
|
|
164
|
+
test('fails when no seed (linkedFrom / linkedTo) is supplied', async () => {
|
|
165
|
+
const index = buildIndex([{ id: 'a' }]);
|
|
166
|
+
const retriever = index_1.LinkTraversalRetriever.create(index).orThrow();
|
|
167
|
+
expect(await retriever.retrieve({ hops: 2 })).toFailWith(index_1.LINK_TRAVERSAL_NO_SEED_MESSAGE);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
describe('LINK_TRAVERSAL_UNWIRED_MESSAGE constant', () => {
|
|
172
|
+
test('has the stable public message non-link retrievers use to degrade loudly', () => {
|
|
173
|
+
// The link-traversal retriever supports the axis, so it never emits this
|
|
174
|
+
// message; the constant is the shared one the non-link v1 retrievers raise
|
|
175
|
+
// (asserted here to keep the public string stable).
|
|
176
|
+
expect(index_1.LINK_TRAVERSAL_UNWIRED_MESSAGE).toMatch(/link traversal requires a backlink index/i);
|
|
177
|
+
});
|
|
178
|
+
test('a link query against the link-capable retriever still succeeds', async () => {
|
|
179
|
+
const index = buildIndex([{ id: 'a', links: ['b'] }, { id: 'b' }]);
|
|
180
|
+
const retriever = index_1.LinkTraversalRetriever.create(index).orThrow();
|
|
181
|
+
expect(await retriever.retrieve({ linkedFrom: 'a' })).toSucceed();
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
//# sourceMappingURL=linkTraversalRetriever.test.js.map
|