@danielsimonjr/memory-mcp 0.48.0 → 9.8.0
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/LICENSE +22 -0
- package/README.md +2000 -194
- package/dist/__tests__/file-path.test.js +7 -11
- package/dist/__tests__/knowledge-graph.test.js +3 -8
- package/dist/core/EntityManager.d.ts +266 -0
- package/dist/core/EntityManager.d.ts.map +1 -0
- package/dist/core/EntityManager.js +85 -133
- package/dist/core/GraphEventEmitter.d.ts +202 -0
- package/dist/core/GraphEventEmitter.d.ts.map +1 -0
- package/dist/core/GraphEventEmitter.js +346 -0
- package/dist/core/GraphStorage.d.ts +395 -0
- package/dist/core/GraphStorage.d.ts.map +1 -0
- package/dist/core/GraphStorage.js +643 -31
- package/dist/core/GraphTraversal.d.ts +141 -0
- package/dist/core/GraphTraversal.d.ts.map +1 -0
- package/dist/core/GraphTraversal.js +573 -0
- package/dist/core/HierarchyManager.d.ts +111 -0
- package/dist/core/HierarchyManager.d.ts.map +1 -0
- package/dist/{features → core}/HierarchyManager.js +14 -9
- package/dist/core/ManagerContext.d.ts +72 -0
- package/dist/core/ManagerContext.d.ts.map +1 -0
- package/dist/core/ManagerContext.js +118 -0
- package/dist/core/ObservationManager.d.ts +85 -0
- package/dist/core/ObservationManager.d.ts.map +1 -0
- package/dist/core/ObservationManager.js +51 -57
- package/dist/core/RelationManager.d.ts +131 -0
- package/dist/core/RelationManager.d.ts.map +1 -0
- package/dist/core/RelationManager.js +31 -7
- package/dist/core/SQLiteStorage.d.ts +354 -0
- package/dist/core/SQLiteStorage.d.ts.map +1 -0
- package/dist/core/SQLiteStorage.js +917 -0
- package/dist/core/StorageFactory.d.ts +45 -0
- package/dist/core/StorageFactory.d.ts.map +1 -0
- package/dist/core/StorageFactory.js +64 -0
- package/dist/core/TransactionManager.d.ts +464 -0
- package/dist/core/TransactionManager.d.ts.map +1 -0
- package/dist/core/TransactionManager.js +490 -13
- package/dist/core/index.d.ts +17 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +12 -2
- package/dist/features/AnalyticsManager.d.ts +44 -0
- package/dist/features/AnalyticsManager.d.ts.map +1 -0
- package/dist/features/AnalyticsManager.js +3 -2
- package/dist/features/ArchiveManager.d.ts +133 -0
- package/dist/features/ArchiveManager.d.ts.map +1 -0
- package/dist/features/ArchiveManager.js +221 -14
- package/dist/features/CompressionManager.d.ts +117 -0
- package/dist/features/CompressionManager.d.ts.map +1 -0
- package/dist/features/CompressionManager.js +189 -20
- package/dist/features/IOManager.d.ts +225 -0
- package/dist/features/IOManager.d.ts.map +1 -0
- package/dist/features/IOManager.js +1041 -0
- package/dist/features/StreamingExporter.d.ts +123 -0
- package/dist/features/StreamingExporter.d.ts.map +1 -0
- package/dist/features/StreamingExporter.js +203 -0
- package/dist/features/TagManager.d.ts +147 -0
- package/dist/features/TagManager.d.ts.map +1 -0
- package/dist/features/index.d.ts +12 -0
- package/dist/features/index.d.ts.map +1 -0
- package/dist/features/index.js +5 -6
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -10
- package/dist/memory.jsonl +1 -26
- package/dist/search/BasicSearch.d.ts +51 -0
- package/dist/search/BasicSearch.d.ts.map +1 -0
- package/dist/search/BasicSearch.js +9 -3
- package/dist/search/BooleanSearch.d.ts +98 -0
- package/dist/search/BooleanSearch.d.ts.map +1 -0
- package/dist/search/BooleanSearch.js +156 -9
- package/dist/search/EmbeddingService.d.ts +178 -0
- package/dist/search/EmbeddingService.d.ts.map +1 -0
- package/dist/search/EmbeddingService.js +358 -0
- package/dist/search/FuzzySearch.d.ts +118 -0
- package/dist/search/FuzzySearch.d.ts.map +1 -0
- package/dist/search/FuzzySearch.js +241 -25
- package/dist/search/QueryCostEstimator.d.ts +111 -0
- package/dist/search/QueryCostEstimator.d.ts.map +1 -0
- package/dist/search/QueryCostEstimator.js +355 -0
- package/dist/search/RankedSearch.d.ts +71 -0
- package/dist/search/RankedSearch.d.ts.map +1 -0
- package/dist/search/RankedSearch.js +54 -6
- package/dist/search/SavedSearchManager.d.ts +79 -0
- package/dist/search/SavedSearchManager.d.ts.map +1 -0
- package/dist/search/SearchFilterChain.d.ts +120 -0
- package/dist/search/SearchFilterChain.d.ts.map +1 -0
- package/dist/search/SearchFilterChain.js +2 -4
- package/dist/search/SearchManager.d.ts +326 -0
- package/dist/search/SearchManager.d.ts.map +1 -0
- package/dist/search/SearchManager.js +148 -0
- package/dist/search/SearchSuggestions.d.ts +27 -0
- package/dist/search/SearchSuggestions.d.ts.map +1 -0
- package/dist/search/SearchSuggestions.js +1 -1
- package/dist/search/SemanticSearch.d.ts +149 -0
- package/dist/search/SemanticSearch.d.ts.map +1 -0
- package/dist/search/SemanticSearch.js +323 -0
- package/dist/search/TFIDFEventSync.d.ts +85 -0
- package/dist/search/TFIDFEventSync.d.ts.map +1 -0
- package/dist/search/TFIDFEventSync.js +133 -0
- package/dist/search/TFIDFIndexManager.d.ts +151 -0
- package/dist/search/TFIDFIndexManager.d.ts.map +1 -0
- package/dist/search/TFIDFIndexManager.js +232 -17
- package/dist/search/VectorStore.d.ts +235 -0
- package/dist/search/VectorStore.d.ts.map +1 -0
- package/dist/search/VectorStore.js +311 -0
- package/dist/search/index.d.ts +21 -0
- package/dist/search/index.d.ts.map +1 -0
- package/dist/search/index.js +12 -0
- package/dist/server/MCPServer.d.ts +21 -0
- package/dist/server/MCPServer.d.ts.map +1 -0
- package/dist/server/MCPServer.js +4 -4
- package/dist/server/responseCompressor.d.ts +94 -0
- package/dist/server/responseCompressor.d.ts.map +1 -0
- package/dist/server/responseCompressor.js +127 -0
- package/dist/server/toolDefinitions.d.ts +27 -0
- package/dist/server/toolDefinitions.d.ts.map +1 -0
- package/dist/server/toolDefinitions.js +188 -17
- package/dist/server/toolHandlers.d.ts +41 -0
- package/dist/server/toolHandlers.d.ts.map +1 -0
- package/dist/server/toolHandlers.js +467 -75
- package/dist/types/index.d.ts +13 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +1 -1
- package/dist/types/types.d.ts +1654 -0
- package/dist/types/types.d.ts.map +1 -0
- package/dist/types/types.js +9 -0
- package/dist/utils/compressedCache.d.ts +192 -0
- package/dist/utils/compressedCache.d.ts.map +1 -0
- package/dist/utils/compressedCache.js +309 -0
- package/dist/utils/compressionUtil.d.ts +214 -0
- package/dist/utils/compressionUtil.d.ts.map +1 -0
- package/dist/utils/compressionUtil.js +247 -0
- package/dist/utils/constants.d.ts +245 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/constants.js +124 -0
- package/dist/utils/entityUtils.d.ts +321 -0
- package/dist/utils/entityUtils.d.ts.map +1 -0
- package/dist/utils/entityUtils.js +434 -4
- package/dist/utils/errors.d.ts +95 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +24 -0
- package/dist/utils/formatters.d.ts +145 -0
- package/dist/utils/formatters.d.ts.map +1 -0
- package/dist/utils/{paginationUtils.js → formatters.js} +54 -3
- package/dist/utils/index.d.ts +23 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +69 -31
- package/dist/utils/indexes.d.ts +270 -0
- package/dist/utils/indexes.d.ts.map +1 -0
- package/dist/utils/indexes.js +526 -0
- package/dist/utils/logger.d.ts +24 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/operationUtils.d.ts +124 -0
- package/dist/utils/operationUtils.d.ts.map +1 -0
- package/dist/utils/operationUtils.js +175 -0
- package/dist/utils/parallelUtils.d.ts +72 -0
- package/dist/utils/parallelUtils.d.ts.map +1 -0
- package/dist/utils/parallelUtils.js +169 -0
- package/dist/utils/schemas.d.ts +374 -0
- package/dist/utils/schemas.d.ts.map +1 -0
- package/dist/utils/schemas.js +302 -2
- package/dist/utils/searchAlgorithms.d.ts +99 -0
- package/dist/utils/searchAlgorithms.d.ts.map +1 -0
- package/dist/utils/searchAlgorithms.js +167 -0
- package/dist/utils/searchCache.d.ts +108 -0
- package/dist/utils/searchCache.d.ts.map +1 -0
- package/dist/utils/taskScheduler.d.ts +290 -0
- package/dist/utils/taskScheduler.d.ts.map +1 -0
- package/dist/utils/taskScheduler.js +466 -0
- package/dist/workers/index.d.ts +12 -0
- package/dist/workers/index.d.ts.map +1 -0
- package/dist/workers/index.js +9 -0
- package/dist/workers/levenshteinWorker.d.ts +60 -0
- package/dist/workers/levenshteinWorker.d.ts.map +1 -0
- package/dist/workers/levenshteinWorker.js +98 -0
- package/package.json +17 -4
- package/dist/__tests__/edge-cases/edge-cases.test.js +0 -406
- package/dist/__tests__/integration/workflows.test.js +0 -449
- package/dist/__tests__/performance/benchmarks.test.js +0 -413
- package/dist/__tests__/unit/core/EntityManager.test.js +0 -334
- package/dist/__tests__/unit/core/GraphStorage.test.js +0 -205
- package/dist/__tests__/unit/core/RelationManager.test.js +0 -274
- package/dist/__tests__/unit/features/CompressionManager.test.js +0 -350
- package/dist/__tests__/unit/search/BasicSearch.test.js +0 -311
- package/dist/__tests__/unit/search/BooleanSearch.test.js +0 -432
- package/dist/__tests__/unit/search/FuzzySearch.test.js +0 -448
- package/dist/__tests__/unit/search/RankedSearch.test.js +0 -379
- package/dist/__tests__/unit/utils/levenshtein.test.js +0 -77
- package/dist/core/KnowledgeGraphManager.js +0 -423
- package/dist/features/BackupManager.js +0 -311
- package/dist/features/ExportManager.js +0 -305
- package/dist/features/ImportExportManager.js +0 -50
- package/dist/features/ImportManager.js +0 -328
- package/dist/memory-saved-searches.jsonl +0 -0
- package/dist/memory-tag-aliases.jsonl +0 -0
- package/dist/types/analytics.types.js +0 -6
- package/dist/types/entity.types.js +0 -7
- package/dist/types/import-export.types.js +0 -7
- package/dist/types/search.types.js +0 -7
- package/dist/types/tag.types.js +0 -6
- package/dist/utils/dateUtils.js +0 -89
- package/dist/utils/filterUtils.js +0 -155
- package/dist/utils/levenshtein.js +0 -62
- package/dist/utils/pathUtils.js +0 -115
- package/dist/utils/responseFormatter.js +0 -55
- package/dist/utils/tagUtils.js +0 -107
- package/dist/utils/tfidf.js +0 -90
- package/dist/utils/validationHelper.js +0 -99
- package/dist/utils/validationUtils.js +0 -109
|
@@ -1,448 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FuzzySearch Unit Tests
|
|
3
|
-
*/
|
|
4
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
5
|
-
import { FuzzySearch, DEFAULT_FUZZY_THRESHOLD } from '../../../search/FuzzySearch.js';
|
|
6
|
-
import { EntityManager } from '../../../core/EntityManager.js';
|
|
7
|
-
import { RelationManager } from '../../../core/RelationManager.js';
|
|
8
|
-
import { GraphStorage } from '../../../core/GraphStorage.js';
|
|
9
|
-
import { promises as fs } from 'fs';
|
|
10
|
-
import { join } from 'path';
|
|
11
|
-
import { tmpdir } from 'os';
|
|
12
|
-
describe('FuzzySearch', () => {
|
|
13
|
-
let storage;
|
|
14
|
-
let fuzzySearch;
|
|
15
|
-
let entityManager;
|
|
16
|
-
let relationManager;
|
|
17
|
-
let testDir;
|
|
18
|
-
let testFilePath;
|
|
19
|
-
beforeEach(async () => {
|
|
20
|
-
// Create unique temp directory for each test
|
|
21
|
-
testDir = join(tmpdir(), `fuzzy-search-test-${Date.now()}-${Math.random()}`);
|
|
22
|
-
await fs.mkdir(testDir, { recursive: true });
|
|
23
|
-
testFilePath = join(testDir, 'test-graph.jsonl');
|
|
24
|
-
storage = new GraphStorage(testFilePath);
|
|
25
|
-
fuzzySearch = new FuzzySearch(storage);
|
|
26
|
-
entityManager = new EntityManager(storage);
|
|
27
|
-
relationManager = new RelationManager(storage);
|
|
28
|
-
// Create test data
|
|
29
|
-
await entityManager.createEntities([
|
|
30
|
-
{
|
|
31
|
-
name: 'Alice',
|
|
32
|
-
entityType: 'person',
|
|
33
|
-
observations: ['Software engineer', 'Loves Python', 'Works remotely'],
|
|
34
|
-
tags: ['engineering', 'python'],
|
|
35
|
-
importance: 9,
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
name: 'Alicia',
|
|
39
|
-
entityType: 'person',
|
|
40
|
-
observations: ['Product manager', 'Leads planning'],
|
|
41
|
-
tags: ['product', 'management'],
|
|
42
|
-
importance: 8,
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
name: 'Bob',
|
|
46
|
-
entityType: 'person',
|
|
47
|
-
observations: ['Designer', 'Creates UIs'],
|
|
48
|
-
tags: ['design'],
|
|
49
|
-
importance: 7,
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
name: 'Robert',
|
|
53
|
-
entityType: 'person',
|
|
54
|
-
observations: ['Developer', 'Backend specialist'],
|
|
55
|
-
tags: ['engineering', 'backend'],
|
|
56
|
-
importance: 8,
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
name: 'Project_Alpha',
|
|
60
|
-
entityType: 'project',
|
|
61
|
-
observations: ['Alpha version project'],
|
|
62
|
-
tags: ['project'],
|
|
63
|
-
importance: 10,
|
|
64
|
-
},
|
|
65
|
-
]);
|
|
66
|
-
await relationManager.createRelations([
|
|
67
|
-
{ from: 'Alice', to: 'Project_Alpha', relationType: 'works_on' },
|
|
68
|
-
{ from: 'Bob', to: 'Alicia', relationType: 'reports_to' },
|
|
69
|
-
]);
|
|
70
|
-
});
|
|
71
|
-
afterEach(async () => {
|
|
72
|
-
// Clean up test files
|
|
73
|
-
try {
|
|
74
|
-
await fs.rm(testDir, { recursive: true, force: true });
|
|
75
|
-
}
|
|
76
|
-
catch {
|
|
77
|
-
// Ignore cleanup errors
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
describe('Exact and Substring Matching', () => {
|
|
81
|
-
it('should match exact name', async () => {
|
|
82
|
-
const result = await fuzzySearch.fuzzySearch('Alice');
|
|
83
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(1);
|
|
84
|
-
const names = result.entities.map(e => e.name);
|
|
85
|
-
expect(names).toContain('Alice');
|
|
86
|
-
});
|
|
87
|
-
it('should match partial name (contains)', async () => {
|
|
88
|
-
const result = await fuzzySearch.fuzzySearch('Ali');
|
|
89
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(2);
|
|
90
|
-
const names = result.entities.map(e => e.name);
|
|
91
|
-
expect(names).toContain('Alice');
|
|
92
|
-
expect(names).toContain('Alicia');
|
|
93
|
-
});
|
|
94
|
-
it('should match entity type', async () => {
|
|
95
|
-
const result = await fuzzySearch.fuzzySearch('person', 0.9);
|
|
96
|
-
// With strict threshold, should only match 'person' type (not 'project' which is similar)
|
|
97
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(1);
|
|
98
|
-
result.entities.forEach(e => {
|
|
99
|
-
expect(e.entityType).toBe('person');
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
it('should match observation words', async () => {
|
|
103
|
-
const result = await fuzzySearch.fuzzySearch('engineer');
|
|
104
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(1);
|
|
105
|
-
const names = result.entities.map(e => e.name);
|
|
106
|
-
expect(names).toContain('Alice');
|
|
107
|
-
});
|
|
108
|
-
it('should match full observation text', async () => {
|
|
109
|
-
const result = await fuzzySearch.fuzzySearch('Software engineer');
|
|
110
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(1);
|
|
111
|
-
const names = result.entities.map(e => e.name);
|
|
112
|
-
expect(names).toContain('Alice');
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
describe('Typo Tolerance', () => {
|
|
116
|
-
it('should match name with single character typo', async () => {
|
|
117
|
-
// "Alise" instead of "Alice"
|
|
118
|
-
const result = await fuzzySearch.fuzzySearch('Alise', 0.7);
|
|
119
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(1);
|
|
120
|
-
const names = result.entities.map(e => e.name);
|
|
121
|
-
expect(names).toContain('Alice');
|
|
122
|
-
});
|
|
123
|
-
it('should match name with transposed characters', async () => {
|
|
124
|
-
// "Alcie" instead of "Alice" - 2 char distance, similarity 0.6
|
|
125
|
-
const result = await fuzzySearch.fuzzySearch('Alcie', 0.6);
|
|
126
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(1);
|
|
127
|
-
const names = result.entities.map(e => e.name);
|
|
128
|
-
expect(names).toContain('Alice');
|
|
129
|
-
});
|
|
130
|
-
it('should match name with missing character', async () => {
|
|
131
|
-
// "Alce" instead of "Alice"
|
|
132
|
-
const result = await fuzzySearch.fuzzySearch('Alce', 0.7);
|
|
133
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(1);
|
|
134
|
-
const names = result.entities.map(e => e.name);
|
|
135
|
-
expect(names).toContain('Alice');
|
|
136
|
-
});
|
|
137
|
-
it('should match name with extra character', async () => {
|
|
138
|
-
// "Allice" instead of "Alice"
|
|
139
|
-
const result = await fuzzySearch.fuzzySearch('Allice', 0.7);
|
|
140
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(1);
|
|
141
|
-
const names = result.entities.map(e => e.name);
|
|
142
|
-
expect(names).toContain('Alice');
|
|
143
|
-
});
|
|
144
|
-
it('should not match with too many typos (below threshold)', async () => {
|
|
145
|
-
// "Xyz" is completely different from "Alice"
|
|
146
|
-
const result = await fuzzySearch.fuzzySearch('Xyz', 0.9);
|
|
147
|
-
const names = result.entities.map(e => e.name);
|
|
148
|
-
expect(names).not.toContain('Alice');
|
|
149
|
-
});
|
|
150
|
-
it('should match similar names with high threshold', async () => {
|
|
151
|
-
// "Alice" vs "Alicia" - similar names
|
|
152
|
-
const result = await fuzzySearch.fuzzySearch('Alice', 0.6);
|
|
153
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(2);
|
|
154
|
-
const names = result.entities.map(e => e.name);
|
|
155
|
-
expect(names).toContain('Alice');
|
|
156
|
-
expect(names).toContain('Alicia');
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
describe('Threshold Variations', () => {
|
|
160
|
-
it('should use default threshold (0.7)', async () => {
|
|
161
|
-
const result = await fuzzySearch.fuzzySearch('Alise'); // 1 char typo
|
|
162
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(1);
|
|
163
|
-
});
|
|
164
|
-
it('should accept strict threshold (0.95)', async () => {
|
|
165
|
-
const result = await fuzzySearch.fuzzySearch('Alice', 0.95);
|
|
166
|
-
// Only exact match or very close matches
|
|
167
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(1);
|
|
168
|
-
expect(result.entities[0].name).toBe('Alice');
|
|
169
|
-
});
|
|
170
|
-
it('should accept permissive threshold (0.5)', async () => {
|
|
171
|
-
const result = await fuzzySearch.fuzzySearch('Alic', 0.5);
|
|
172
|
-
// More permissive, should match Alice with 1 char difference
|
|
173
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(1);
|
|
174
|
-
const names = result.entities.map(e => e.name);
|
|
175
|
-
expect(names).toContain('Alice');
|
|
176
|
-
});
|
|
177
|
-
it('should return more results with lower threshold', async () => {
|
|
178
|
-
const strictResult = await fuzzySearch.fuzzySearch('Alice', 0.9);
|
|
179
|
-
const permissiveResult = await fuzzySearch.fuzzySearch('Alice', 0.5);
|
|
180
|
-
expect(permissiveResult.entities.length).toBeGreaterThanOrEqual(strictResult.entities.length);
|
|
181
|
-
});
|
|
182
|
-
it('should handle threshold of 0 (match everything)', async () => {
|
|
183
|
-
const result = await fuzzySearch.fuzzySearch('xyz', 0);
|
|
184
|
-
// With threshold 0, everything matches
|
|
185
|
-
expect(result.entities.length).toBeGreaterThan(0);
|
|
186
|
-
});
|
|
187
|
-
it('should handle threshold of 1 (exact match only)', async () => {
|
|
188
|
-
const result = await fuzzySearch.fuzzySearch('Alice', 1);
|
|
189
|
-
expect(result.entities).toHaveLength(1);
|
|
190
|
-
expect(result.entities[0].name).toBe('Alice');
|
|
191
|
-
});
|
|
192
|
-
});
|
|
193
|
-
describe('Tag Filtering', () => {
|
|
194
|
-
it('should filter by single tag', async () => {
|
|
195
|
-
const result = await fuzzySearch.fuzzySearch('person', 0.7, ['python']);
|
|
196
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(1);
|
|
197
|
-
result.entities.forEach(e => {
|
|
198
|
-
expect(e.tags).toContain('python');
|
|
199
|
-
});
|
|
200
|
-
});
|
|
201
|
-
it('should filter by multiple tags (OR logic)', async () => {
|
|
202
|
-
const result = await fuzzySearch.fuzzySearch('person', 0.7, ['python', 'design']);
|
|
203
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(2);
|
|
204
|
-
result.entities.forEach(e => {
|
|
205
|
-
const hasPython = e.tags?.includes('python');
|
|
206
|
-
const hasDesign = e.tags?.includes('design');
|
|
207
|
-
expect(hasPython || hasDesign).toBe(true);
|
|
208
|
-
});
|
|
209
|
-
});
|
|
210
|
-
it('should combine fuzzy search with tag filter', async () => {
|
|
211
|
-
const result = await fuzzySearch.fuzzySearch('Alise', 0.7, ['python']);
|
|
212
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(1);
|
|
213
|
-
expect(result.entities[0].name).toBe('Alice');
|
|
214
|
-
expect(result.entities[0].tags).toContain('python');
|
|
215
|
-
});
|
|
216
|
-
it('should exclude entities without matching tags', async () => {
|
|
217
|
-
const result = await fuzzySearch.fuzzySearch('person', 0.7, ['nonexistent']);
|
|
218
|
-
expect(result.entities).toHaveLength(0);
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
|
-
describe('Importance Filtering', () => {
|
|
222
|
-
it('should filter by minimum importance', async () => {
|
|
223
|
-
const result = await fuzzySearch.fuzzySearch('person', 0.7, undefined, 9);
|
|
224
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(1);
|
|
225
|
-
result.entities.forEach(e => {
|
|
226
|
-
expect(e.importance).toBeGreaterThanOrEqual(9);
|
|
227
|
-
});
|
|
228
|
-
});
|
|
229
|
-
it('should filter by maximum importance', async () => {
|
|
230
|
-
const result = await fuzzySearch.fuzzySearch('person', 0.7, undefined, undefined, 8);
|
|
231
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(1);
|
|
232
|
-
result.entities.forEach(e => {
|
|
233
|
-
expect(e.importance).toBeLessThanOrEqual(8);
|
|
234
|
-
});
|
|
235
|
-
});
|
|
236
|
-
it('should filter by importance range', async () => {
|
|
237
|
-
const result = await fuzzySearch.fuzzySearch('person', 0.7, undefined, 8, 9);
|
|
238
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(1);
|
|
239
|
-
result.entities.forEach(e => {
|
|
240
|
-
expect(e.importance).toBeGreaterThanOrEqual(8);
|
|
241
|
-
expect(e.importance).toBeLessThanOrEqual(9);
|
|
242
|
-
});
|
|
243
|
-
});
|
|
244
|
-
it('should combine fuzzy search with importance filter', async () => {
|
|
245
|
-
const result = await fuzzySearch.fuzzySearch('Alise', 0.7, undefined, 9);
|
|
246
|
-
expect(result.entities).toHaveLength(1);
|
|
247
|
-
expect(result.entities[0].name).toBe('Alice');
|
|
248
|
-
expect(result.entities[0].importance).toBe(9);
|
|
249
|
-
});
|
|
250
|
-
it('should exclude entities without importance when filtering', async () => {
|
|
251
|
-
await entityManager.createEntities([
|
|
252
|
-
{ name: 'NoImportance', entityType: 'test', observations: ['Test'] },
|
|
253
|
-
]);
|
|
254
|
-
const result = await fuzzySearch.fuzzySearch('test', 0.7, undefined, 5);
|
|
255
|
-
const names = result.entities.map(e => e.name);
|
|
256
|
-
expect(names).not.toContain('NoImportance');
|
|
257
|
-
});
|
|
258
|
-
});
|
|
259
|
-
describe('Relations', () => {
|
|
260
|
-
it('should include relations between matched entities', async () => {
|
|
261
|
-
const result = await fuzzySearch.fuzzySearch('Ali', 0.7);
|
|
262
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(2);
|
|
263
|
-
// Alice and Alicia should be matched
|
|
264
|
-
expect(result.relations.length).toBeGreaterThanOrEqual(0);
|
|
265
|
-
});
|
|
266
|
-
it('should exclude relations to non-matched entities', async () => {
|
|
267
|
-
const result = await fuzzySearch.fuzzySearch('Alice', 0.95);
|
|
268
|
-
expect(result.entities).toHaveLength(1);
|
|
269
|
-
// Alice has relations to Project_Alpha and Bob, but they're not in result with strict threshold
|
|
270
|
-
expect(result.relations).toHaveLength(0);
|
|
271
|
-
});
|
|
272
|
-
it('should include all relations in matched subgraph', async () => {
|
|
273
|
-
const result = await fuzzySearch.fuzzySearch('Ali', 0.5);
|
|
274
|
-
// Should match Alice, Alicia, and Project_Alpha
|
|
275
|
-
const names = result.entities.map(e => e.name);
|
|
276
|
-
if (names.includes('Alice') && names.includes('Project_Alpha')) {
|
|
277
|
-
expect(result.relations.some(r => r.from === 'Alice' && r.to === 'Project_Alpha')).toBe(true);
|
|
278
|
-
}
|
|
279
|
-
});
|
|
280
|
-
});
|
|
281
|
-
describe('Edge Cases', () => {
|
|
282
|
-
it('should handle empty query string', async () => {
|
|
283
|
-
const result = await fuzzySearch.fuzzySearch('');
|
|
284
|
-
// Empty query matches all entities (every string contains empty string)
|
|
285
|
-
expect(result.entities.length).toBe(5);
|
|
286
|
-
expect(result.relations.length).toBeGreaterThanOrEqual(0);
|
|
287
|
-
});
|
|
288
|
-
it('should handle entities with empty observations', async () => {
|
|
289
|
-
await entityManager.createEntities([
|
|
290
|
-
{ name: 'EmptyObs', entityType: 'test', observations: [] },
|
|
291
|
-
]);
|
|
292
|
-
const result = await fuzzySearch.fuzzySearch('test');
|
|
293
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(1);
|
|
294
|
-
const names = result.entities.map(e => e.name);
|
|
295
|
-
expect(names).toContain('EmptyObs');
|
|
296
|
-
});
|
|
297
|
-
it('should handle entities without tags', async () => {
|
|
298
|
-
await entityManager.createEntities([
|
|
299
|
-
{ name: 'NoTags', entityType: 'test', observations: ['Test'] },
|
|
300
|
-
]);
|
|
301
|
-
const result = await fuzzySearch.fuzzySearch('test');
|
|
302
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(1);
|
|
303
|
-
});
|
|
304
|
-
it('should handle entities without importance', async () => {
|
|
305
|
-
await entityManager.createEntities([
|
|
306
|
-
{ name: 'NoImportance', entityType: 'test', observations: ['Test'] },
|
|
307
|
-
]);
|
|
308
|
-
const result = await fuzzySearch.fuzzySearch('test', 0.7, undefined, 5);
|
|
309
|
-
const names = result.entities.map(e => e.name);
|
|
310
|
-
expect(names).not.toContain('NoImportance');
|
|
311
|
-
});
|
|
312
|
-
it('should handle very short queries', async () => {
|
|
313
|
-
const result = await fuzzySearch.fuzzySearch('A', 0.3);
|
|
314
|
-
// Should match names starting with A (Alice, Alicia, Project_Alpha)
|
|
315
|
-
expect(result.entities.length).toBeGreaterThan(0);
|
|
316
|
-
});
|
|
317
|
-
it('should handle very long queries', async () => {
|
|
318
|
-
const longQuery = 'Software engineer who loves Python programming';
|
|
319
|
-
const result = await fuzzySearch.fuzzySearch(longQuery, 0.5);
|
|
320
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(1);
|
|
321
|
-
});
|
|
322
|
-
it('should handle special characters', async () => {
|
|
323
|
-
await entityManager.createEntities([
|
|
324
|
-
{ name: 'Special-Name', entityType: 'test', observations: ['Test!'] },
|
|
325
|
-
]);
|
|
326
|
-
const result = await fuzzySearch.fuzzySearch('Special Name', 0.7);
|
|
327
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(1);
|
|
328
|
-
});
|
|
329
|
-
it('should handle unicode characters', async () => {
|
|
330
|
-
await entityManager.createEntities([
|
|
331
|
-
{ name: 'Café', entityType: 'location', observations: ['French café'] },
|
|
332
|
-
]);
|
|
333
|
-
const result = await fuzzySearch.fuzzySearch('Cafe', 0.7);
|
|
334
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(1);
|
|
335
|
-
const names = result.entities.map(e => e.name);
|
|
336
|
-
expect(names).toContain('Café');
|
|
337
|
-
});
|
|
338
|
-
it('should be case-insensitive', async () => {
|
|
339
|
-
const result = await fuzzySearch.fuzzySearch('ALICE', 0.7);
|
|
340
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(1);
|
|
341
|
-
const names = result.entities.map(e => e.name);
|
|
342
|
-
expect(names).toContain('Alice');
|
|
343
|
-
});
|
|
344
|
-
it('should handle no matches', async () => {
|
|
345
|
-
const result = await fuzzySearch.fuzzySearch('XyzNonExistent', 0.9);
|
|
346
|
-
expect(result.entities).toHaveLength(0);
|
|
347
|
-
expect(result.relations).toHaveLength(0);
|
|
348
|
-
});
|
|
349
|
-
});
|
|
350
|
-
describe('Combined Filters', () => {
|
|
351
|
-
it('should combine fuzzy search with tag and importance filters', async () => {
|
|
352
|
-
const result = await fuzzySearch.fuzzySearch('Alise', 0.7, ['python'], 9);
|
|
353
|
-
expect(result.entities).toHaveLength(1);
|
|
354
|
-
expect(result.entities[0].name).toBe('Alice');
|
|
355
|
-
expect(result.entities[0].tags).toContain('python');
|
|
356
|
-
expect(result.entities[0].importance).toBe(9);
|
|
357
|
-
});
|
|
358
|
-
it('should return empty when filters exclude all matches', async () => {
|
|
359
|
-
const result = await fuzzySearch.fuzzySearch('Alice', 0.7, ['nonexistent']);
|
|
360
|
-
expect(result.entities).toHaveLength(0);
|
|
361
|
-
});
|
|
362
|
-
it('should handle all filters together', async () => {
|
|
363
|
-
const result = await fuzzySearch.fuzzySearch('person', 0.7, ['engineering'], 8, 10);
|
|
364
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(1);
|
|
365
|
-
result.entities.forEach(e => {
|
|
366
|
-
expect(e.entityType).toBe('person');
|
|
367
|
-
expect(e.tags).toContain('engineering');
|
|
368
|
-
expect(e.importance).toBeGreaterThanOrEqual(8);
|
|
369
|
-
expect(e.importance).toBeLessThanOrEqual(10);
|
|
370
|
-
});
|
|
371
|
-
});
|
|
372
|
-
});
|
|
373
|
-
describe('Levenshtein Distance Edge Cases', () => {
|
|
374
|
-
it('should handle identical strings', async () => {
|
|
375
|
-
const result = await fuzzySearch.fuzzySearch('Alice', 1.0);
|
|
376
|
-
expect(result.entities).toHaveLength(1);
|
|
377
|
-
expect(result.entities[0].name).toBe('Alice');
|
|
378
|
-
});
|
|
379
|
-
it('should handle completely different strings with low threshold', async () => {
|
|
380
|
-
const result = await fuzzySearch.fuzzySearch('xyz', 0.1);
|
|
381
|
-
// With very low threshold, might match some entities
|
|
382
|
-
expect(Array.isArray(result.entities)).toBe(true);
|
|
383
|
-
});
|
|
384
|
-
it('should handle empty string comparison', async () => {
|
|
385
|
-
const result = await fuzzySearch.fuzzySearch('', 0.5);
|
|
386
|
-
// Empty string matches all (every string contains empty string)
|
|
387
|
-
expect(result.entities.length).toBe(5);
|
|
388
|
-
expect(result.relations.length).toBeGreaterThanOrEqual(0);
|
|
389
|
-
});
|
|
390
|
-
it('should calculate similarity correctly for similar words', async () => {
|
|
391
|
-
// "Bob" vs "Robert" - different lengths but same person
|
|
392
|
-
const result = await fuzzySearch.fuzzySearch('Bob', 0.5);
|
|
393
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(1);
|
|
394
|
-
const names = result.entities.map(e => e.name);
|
|
395
|
-
expect(names).toContain('Bob');
|
|
396
|
-
// Robert might also match with permissive threshold
|
|
397
|
-
});
|
|
398
|
-
});
|
|
399
|
-
describe('Return Value Structure', () => {
|
|
400
|
-
it('should return KnowledgeGraph with entities and relations', async () => {
|
|
401
|
-
const result = await fuzzySearch.fuzzySearch('Alice');
|
|
402
|
-
expect(result).toHaveProperty('entities');
|
|
403
|
-
expect(result).toHaveProperty('relations');
|
|
404
|
-
expect(Array.isArray(result.entities)).toBe(true);
|
|
405
|
-
expect(Array.isArray(result.relations)).toBe(true);
|
|
406
|
-
});
|
|
407
|
-
it('should return complete entity objects', async () => {
|
|
408
|
-
const result = await fuzzySearch.fuzzySearch('Alice');
|
|
409
|
-
expect(result.entities[0]).toHaveProperty('name');
|
|
410
|
-
expect(result.entities[0]).toHaveProperty('entityType');
|
|
411
|
-
expect(result.entities[0]).toHaveProperty('observations');
|
|
412
|
-
expect(result.entities[0]).toHaveProperty('tags');
|
|
413
|
-
expect(result.entities[0]).toHaveProperty('importance');
|
|
414
|
-
});
|
|
415
|
-
});
|
|
416
|
-
describe('Word-level Matching in Observations', () => {
|
|
417
|
-
it('should match individual words in observations', async () => {
|
|
418
|
-
const result = await fuzzySearch.fuzzySearch('engineer', 0.7);
|
|
419
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(1);
|
|
420
|
-
const names = result.entities.map(e => e.name);
|
|
421
|
-
expect(names).toContain('Alice');
|
|
422
|
-
});
|
|
423
|
-
it('should match observation words with typos', async () => {
|
|
424
|
-
// "enginer" instead of "engineer"
|
|
425
|
-
const result = await fuzzySearch.fuzzySearch('enginer', 0.7);
|
|
426
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(1);
|
|
427
|
-
const names = result.entities.map(e => e.name);
|
|
428
|
-
expect(names).toContain('Alice');
|
|
429
|
-
});
|
|
430
|
-
it('should match full observation text with typos', async () => {
|
|
431
|
-
// "Softwar engineer" instead of "Software engineer"
|
|
432
|
-
const result = await fuzzySearch.fuzzySearch('Softwar engineer', 0.7);
|
|
433
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(1);
|
|
434
|
-
const names = result.entities.map(e => e.name);
|
|
435
|
-
expect(names).toContain('Alice');
|
|
436
|
-
});
|
|
437
|
-
});
|
|
438
|
-
describe('DEFAULT_FUZZY_THRESHOLD Constant', () => {
|
|
439
|
-
it('should export DEFAULT_FUZZY_THRESHOLD', () => {
|
|
440
|
-
expect(DEFAULT_FUZZY_THRESHOLD).toBe(0.7);
|
|
441
|
-
});
|
|
442
|
-
it('should use DEFAULT_FUZZY_THRESHOLD when not specified', async () => {
|
|
443
|
-
// Calling without threshold parameter should use default
|
|
444
|
-
const result = await fuzzySearch.fuzzySearch('Alise');
|
|
445
|
-
expect(result.entities.length).toBeGreaterThanOrEqual(1);
|
|
446
|
-
});
|
|
447
|
-
});
|
|
448
|
-
});
|