@danielsimonjr/memory-mcp 0.7.2 → 0.41.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.
Files changed (61) hide show
  1. package/dist/__tests__/edge-cases/edge-cases.test.js +406 -0
  2. package/dist/__tests__/file-path.test.js +5 -5
  3. package/dist/__tests__/integration/workflows.test.js +449 -0
  4. package/dist/__tests__/knowledge-graph.test.js +8 -3
  5. package/dist/__tests__/performance/benchmarks.test.js +411 -0
  6. package/dist/__tests__/unit/core/EntityManager.test.js +334 -0
  7. package/dist/__tests__/unit/core/GraphStorage.test.js +205 -0
  8. package/dist/__tests__/unit/core/RelationManager.test.js +274 -0
  9. package/dist/__tests__/unit/features/CompressionManager.test.js +350 -0
  10. package/dist/__tests__/unit/search/BasicSearch.test.js +311 -0
  11. package/dist/__tests__/unit/search/BooleanSearch.test.js +432 -0
  12. package/dist/__tests__/unit/search/FuzzySearch.test.js +448 -0
  13. package/dist/__tests__/unit/search/RankedSearch.test.js +379 -0
  14. package/dist/__tests__/unit/utils/levenshtein.test.js +77 -0
  15. package/dist/core/EntityManager.js +554 -0
  16. package/dist/core/GraphStorage.js +172 -0
  17. package/dist/core/KnowledgeGraphManager.js +400 -0
  18. package/dist/core/ObservationManager.js +129 -0
  19. package/dist/core/RelationManager.js +186 -0
  20. package/dist/core/TransactionManager.js +389 -0
  21. package/dist/core/index.js +9 -0
  22. package/dist/features/AnalyticsManager.js +222 -0
  23. package/dist/features/ArchiveManager.js +74 -0
  24. package/dist/features/BackupManager.js +311 -0
  25. package/dist/features/CompressionManager.js +310 -0
  26. package/dist/features/ExportManager.js +305 -0
  27. package/dist/features/HierarchyManager.js +219 -0
  28. package/dist/features/ImportExportManager.js +50 -0
  29. package/dist/features/ImportManager.js +328 -0
  30. package/dist/features/TagManager.js +210 -0
  31. package/dist/features/index.js +12 -0
  32. package/dist/index.js +13 -996
  33. package/dist/memory.jsonl +225 -0
  34. package/dist/search/BasicSearch.js +161 -0
  35. package/dist/search/BooleanSearch.js +304 -0
  36. package/dist/search/FuzzySearch.js +115 -0
  37. package/dist/search/RankedSearch.js +206 -0
  38. package/dist/search/SavedSearchManager.js +145 -0
  39. package/dist/search/SearchManager.js +305 -0
  40. package/dist/search/SearchSuggestions.js +57 -0
  41. package/dist/search/TFIDFIndexManager.js +217 -0
  42. package/dist/search/index.js +10 -0
  43. package/dist/server/MCPServer.js +889 -0
  44. package/dist/types/analytics.types.js +6 -0
  45. package/dist/types/entity.types.js +7 -0
  46. package/dist/types/import-export.types.js +7 -0
  47. package/dist/types/index.js +12 -0
  48. package/dist/types/search.types.js +7 -0
  49. package/dist/types/tag.types.js +6 -0
  50. package/dist/utils/constants.js +127 -0
  51. package/dist/utils/dateUtils.js +89 -0
  52. package/dist/utils/errors.js +121 -0
  53. package/dist/utils/index.js +13 -0
  54. package/dist/utils/levenshtein.js +62 -0
  55. package/dist/utils/logger.js +33 -0
  56. package/dist/utils/pathUtils.js +115 -0
  57. package/dist/utils/schemas.js +184 -0
  58. package/dist/utils/searchCache.js +209 -0
  59. package/dist/utils/tfidf.js +90 -0
  60. package/dist/utils/validationUtils.js +109 -0
  61. package/package.json +50 -48
@@ -0,0 +1,411 @@
1
+ /**
2
+ * Performance Benchmarks
3
+ *
4
+ * Tests for performance budgets and benchmarks across all operations.
5
+ * Validates that operations complete within acceptable time limits.
6
+ */
7
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
8
+ import { GraphStorage } from '../../core/GraphStorage.js';
9
+ import { EntityManager } from '../../core/EntityManager.js';
10
+ import { RelationManager } from '../../core/RelationManager.js';
11
+ import { CompressionManager } from '../../features/CompressionManager.js';
12
+ import { BasicSearch } from '../../search/BasicSearch.js';
13
+ import { RankedSearch } from '../../search/RankedSearch.js';
14
+ import { BooleanSearch } from '../../search/BooleanSearch.js';
15
+ import { FuzzySearch } from '../../search/FuzzySearch.js';
16
+ import { promises as fs } from 'fs';
17
+ import { join } from 'path';
18
+ import { tmpdir } from 'os';
19
+ describe('Performance Benchmarks', () => {
20
+ let storage;
21
+ let entityManager;
22
+ let relationManager;
23
+ let compressionManager;
24
+ let basicSearch;
25
+ let rankedSearch;
26
+ let booleanSearch;
27
+ let fuzzySearch;
28
+ let testDir;
29
+ let testFilePath;
30
+ beforeEach(async () => {
31
+ testDir = join(tmpdir(), `perf-test-${Date.now()}-${Math.random()}`);
32
+ await fs.mkdir(testDir, { recursive: true });
33
+ testFilePath = join(testDir, 'test-graph.jsonl');
34
+ storage = new GraphStorage(testFilePath);
35
+ entityManager = new EntityManager(storage);
36
+ relationManager = new RelationManager(storage);
37
+ compressionManager = new CompressionManager(storage);
38
+ basicSearch = new BasicSearch(storage);
39
+ rankedSearch = new RankedSearch(storage);
40
+ booleanSearch = new BooleanSearch(storage);
41
+ fuzzySearch = new FuzzySearch(storage);
42
+ });
43
+ afterEach(async () => {
44
+ try {
45
+ await fs.rm(testDir, { recursive: true, force: true });
46
+ }
47
+ catch {
48
+ // Ignore cleanup errors
49
+ }
50
+ });
51
+ describe('Entity Creation Performance', () => {
52
+ it('should create 1 entity in < 50ms', async () => {
53
+ const startTime = Date.now();
54
+ await entityManager.createEntities([
55
+ { name: 'Entity1', entityType: 'test', observations: ['Test observation'] },
56
+ ]);
57
+ const duration = Date.now() - startTime;
58
+ expect(duration).toBeLessThan(50);
59
+ });
60
+ it('should create 100 entities in < 200ms', async () => {
61
+ const entities = Array.from({ length: 100 }, (_, i) => ({
62
+ name: `Entity${i}`,
63
+ entityType: 'test',
64
+ observations: [`Observation ${i}`],
65
+ importance: (i % 10) + 1,
66
+ }));
67
+ const startTime = Date.now();
68
+ await entityManager.createEntities(entities);
69
+ const duration = Date.now() - startTime;
70
+ expect(duration).toBeLessThan(200);
71
+ });
72
+ it('should create 1000 entities in < 1500ms', async () => {
73
+ const entities = Array.from({ length: 1000 }, (_, i) => ({
74
+ name: `Entity${i}`,
75
+ entityType: 'test',
76
+ observations: [`Observation ${i}`],
77
+ }));
78
+ const startTime = Date.now();
79
+ await entityManager.createEntities(entities);
80
+ const duration = Date.now() - startTime;
81
+ expect(duration).toBeLessThan(1500);
82
+ });
83
+ it('should batch update 100 entities in < 200ms', async () => {
84
+ // Create entities first
85
+ const entities = Array.from({ length: 100 }, (_, i) => ({
86
+ name: `Entity${i}`,
87
+ entityType: 'test',
88
+ observations: [`Observation ${i}`],
89
+ }));
90
+ await entityManager.createEntities(entities);
91
+ // Batch update
92
+ const updates = Array.from({ length: 100 }, (_, i) => ({
93
+ name: `Entity${i}`,
94
+ updates: { importance: 5 },
95
+ }));
96
+ const startTime = Date.now();
97
+ await entityManager.batchUpdate(updates);
98
+ const duration = Date.now() - startTime;
99
+ expect(duration).toBeLessThan(200);
100
+ });
101
+ });
102
+ describe('Relation Creation Performance', () => {
103
+ it('should create 100 relations in < 200ms', async () => {
104
+ // Create entities first
105
+ const entities = Array.from({ length: 50 }, (_, i) => ({
106
+ name: `Entity${i}`,
107
+ entityType: 'test',
108
+ observations: ['Test'],
109
+ }));
110
+ await entityManager.createEntities(entities);
111
+ // Create relations
112
+ const relations = Array.from({ length: 100 }, (_, i) => ({
113
+ from: `Entity${i % 50}`,
114
+ to: `Entity${(i + 1) % 50}`,
115
+ relationType: 'connects',
116
+ }));
117
+ const startTime = Date.now();
118
+ await relationManager.createRelations(relations);
119
+ const duration = Date.now() - startTime;
120
+ expect(duration).toBeLessThan(200);
121
+ });
122
+ it('should create 1000 relations in < 1500ms', async () => {
123
+ // Create entities first
124
+ const entities = Array.from({ length: 100 }, (_, i) => ({
125
+ name: `Entity${i}`,
126
+ entityType: 'test',
127
+ observations: ['Test'],
128
+ }));
129
+ await entityManager.createEntities(entities);
130
+ // Create relations
131
+ const relations = Array.from({ length: 1000 }, (_, i) => ({
132
+ from: `Entity${i % 100}`,
133
+ to: `Entity${(i + 1) % 100}`,
134
+ relationType: 'connects',
135
+ }));
136
+ const startTime = Date.now();
137
+ await relationManager.createRelations(relations);
138
+ const duration = Date.now() - startTime;
139
+ expect(duration).toBeLessThan(1500);
140
+ });
141
+ });
142
+ describe('Search Performance', () => {
143
+ beforeEach(async () => {
144
+ // Create a moderate-sized graph
145
+ const entities = Array.from({ length: 500 }, (_, i) => ({
146
+ name: `Entity${i}`,
147
+ entityType: i % 5 === 0 ? 'person' : 'project',
148
+ observations: [`This is observation ${i} with some searchable text`],
149
+ tags: i % 3 === 0 ? ['tagged', 'test'] : undefined,
150
+ importance: (i % 10) + 1,
151
+ }));
152
+ await entityManager.createEntities(entities);
153
+ });
154
+ it('should perform basic search in < 100ms', async () => {
155
+ const startTime = Date.now();
156
+ await basicSearch.searchNodes('Entity');
157
+ const duration = Date.now() - startTime;
158
+ expect(duration).toBeLessThan(100);
159
+ });
160
+ it('should perform ranked search in < 600ms', async () => {
161
+ const startTime = Date.now();
162
+ await rankedSearch.searchNodesRanked('searchable text');
163
+ const duration = Date.now() - startTime;
164
+ expect(duration).toBeLessThan(600);
165
+ });
166
+ it('should perform boolean search in < 150ms', async () => {
167
+ const startTime = Date.now();
168
+ await booleanSearch.booleanSearch('person AND observation');
169
+ const duration = Date.now() - startTime;
170
+ expect(duration).toBeLessThan(150);
171
+ });
172
+ it('should perform fuzzy search in < 200ms', async () => {
173
+ const startTime = Date.now();
174
+ await fuzzySearch.fuzzySearch('Entty', 0.7);
175
+ const duration = Date.now() - startTime;
176
+ expect(duration).toBeLessThan(200);
177
+ });
178
+ it('should search with filters in < 150ms', async () => {
179
+ const startTime = Date.now();
180
+ await basicSearch.searchNodes('Entity', ['tagged'], 5, 8);
181
+ const duration = Date.now() - startTime;
182
+ expect(duration).toBeLessThan(150);
183
+ });
184
+ it('should open 50 nodes in < 100ms', async () => {
185
+ const nodeNames = Array.from({ length: 50 }, (_, i) => `Entity${i}`);
186
+ const startTime = Date.now();
187
+ await basicSearch.openNodes(nodeNames);
188
+ const duration = Date.now() - startTime;
189
+ expect(duration).toBeLessThan(100);
190
+ });
191
+ });
192
+ describe('Compression Performance', () => {
193
+ it('should detect duplicates in 100 entities in < 300ms', async () => {
194
+ // Create entities with some duplicates
195
+ const entities = Array.from({ length: 100 }, (_, i) => ({
196
+ name: `Entity${i}`,
197
+ entityType: 'person',
198
+ observations: [i % 10 === 0 ? 'Duplicate observation' : `Unique observation ${i}`],
199
+ }));
200
+ await entityManager.createEntities(entities);
201
+ const startTime = Date.now();
202
+ await compressionManager.findDuplicates(0.8);
203
+ const duration = Date.now() - startTime;
204
+ expect(duration).toBeLessThan(300);
205
+ });
206
+ it('should detect duplicates in 500 entities in < 1500ms', async () => {
207
+ // Create entities with some duplicates
208
+ const entities = Array.from({ length: 500 }, (_, i) => ({
209
+ name: `Entity${i}`,
210
+ entityType: 'person',
211
+ observations: [i % 20 === 0 ? 'Duplicate observation' : `Unique observation ${i}`],
212
+ }));
213
+ await entityManager.createEntities(entities);
214
+ const startTime = Date.now();
215
+ await compressionManager.findDuplicates(0.8);
216
+ const duration = Date.now() - startTime;
217
+ expect(duration).toBeLessThan(1500);
218
+ });
219
+ it('should compress duplicates in 100 entities in < 400ms', async () => {
220
+ // Create similar entities
221
+ const entities = Array.from({ length: 100 }, (_, i) => ({
222
+ name: `Entity${i}`,
223
+ entityType: 'person',
224
+ observations: [i % 10 === 0 ? 'Similar observation' : `Observation ${i}`],
225
+ }));
226
+ await entityManager.createEntities(entities);
227
+ const startTime = Date.now();
228
+ await compressionManager.compressGraph(0.8, false);
229
+ const duration = Date.now() - startTime;
230
+ expect(duration).toBeLessThan(400);
231
+ });
232
+ });
233
+ describe('Graph Loading/Saving Performance', () => {
234
+ it('should load graph with 100 entities in < 100ms', async () => {
235
+ // Create entities
236
+ const entities = Array.from({ length: 100 }, (_, i) => ({
237
+ name: `Entity${i}`,
238
+ entityType: 'test',
239
+ observations: [`Observation ${i}`],
240
+ }));
241
+ await entityManager.createEntities(entities);
242
+ const startTime = Date.now();
243
+ await storage.loadGraph();
244
+ const duration = Date.now() - startTime;
245
+ expect(duration).toBeLessThan(100);
246
+ });
247
+ it('should load graph with 1000 entities in < 500ms', async () => {
248
+ // Create entities
249
+ const entities = Array.from({ length: 1000 }, (_, i) => ({
250
+ name: `Entity${i}`,
251
+ entityType: 'test',
252
+ observations: [`Observation ${i}`],
253
+ }));
254
+ await entityManager.createEntities(entities);
255
+ const startTime = Date.now();
256
+ await storage.loadGraph();
257
+ const duration = Date.now() - startTime;
258
+ expect(duration).toBeLessThan(500);
259
+ });
260
+ it('should save graph with 100 entities in < 150ms', async () => {
261
+ // Create entities
262
+ const entities = Array.from({ length: 100 }, (_, i) => ({
263
+ name: `Entity${i}`,
264
+ entityType: 'test',
265
+ observations: [`Observation ${i}`],
266
+ }));
267
+ const graph = await storage.loadGraph();
268
+ graph.entities = entities.map(e => ({
269
+ ...e,
270
+ createdAt: new Date().toISOString(),
271
+ lastModified: new Date().toISOString(),
272
+ }));
273
+ const startTime = Date.now();
274
+ await storage.saveGraph(graph);
275
+ const duration = Date.now() - startTime;
276
+ expect(duration).toBeLessThan(150);
277
+ });
278
+ it('should save graph with 1000 entities in < 800ms', async () => {
279
+ // Create entities
280
+ const entities = Array.from({ length: 1000 }, (_, i) => ({
281
+ name: `Entity${i}`,
282
+ entityType: 'test',
283
+ observations: [`Observation ${i}`],
284
+ }));
285
+ const graph = await storage.loadGraph();
286
+ graph.entities = entities.map(e => ({
287
+ ...e,
288
+ createdAt: new Date().toISOString(),
289
+ lastModified: new Date().toISOString(),
290
+ }));
291
+ const startTime = Date.now();
292
+ await storage.saveGraph(graph);
293
+ const duration = Date.now() - startTime;
294
+ expect(duration).toBeLessThan(800);
295
+ });
296
+ });
297
+ describe('Complex Workflow Performance', () => {
298
+ it('should complete full CRUD workflow in < 300ms', async () => {
299
+ const startTime = Date.now();
300
+ // Create
301
+ await entityManager.createEntities([
302
+ { name: 'Entity1', entityType: 'test', observations: ['Test 1'] },
303
+ { name: 'Entity2', entityType: 'test', observations: ['Test 2'] },
304
+ ]);
305
+ // Read
306
+ await entityManager.getEntity('Entity1');
307
+ // Update
308
+ await entityManager.updateEntity('Entity1', { importance: 5 });
309
+ // Search
310
+ await basicSearch.searchNodes('Entity');
311
+ // Delete
312
+ await entityManager.deleteEntities(['Entity2']);
313
+ const duration = Date.now() - startTime;
314
+ expect(duration).toBeLessThan(300);
315
+ });
316
+ it('should handle bulk workflow (create, relate, search) in < 500ms', async () => {
317
+ const startTime = Date.now();
318
+ // Bulk create
319
+ const entities = Array.from({ length: 50 }, (_, i) => ({
320
+ name: `Entity${i}`,
321
+ entityType: 'test',
322
+ observations: [`Observation ${i}`],
323
+ }));
324
+ await entityManager.createEntities(entities);
325
+ // Bulk relate
326
+ const relations = Array.from({ length: 50 }, (_, i) => ({
327
+ from: `Entity${i}`,
328
+ to: `Entity${(i + 1) % 50}`,
329
+ relationType: 'connects',
330
+ }));
331
+ await relationManager.createRelations(relations);
332
+ // Search
333
+ await basicSearch.searchNodes('Entity');
334
+ const duration = Date.now() - startTime;
335
+ expect(duration).toBeLessThan(500);
336
+ });
337
+ it('should handle complex query workflow in < 400ms', async () => {
338
+ // Setup
339
+ const entities = Array.from({ length: 100 }, (_, i) => ({
340
+ name: `Entity${i}`,
341
+ entityType: i % 2 === 0 ? 'person' : 'project',
342
+ observations: [`Observation ${i}`],
343
+ tags: i % 3 === 0 ? ['important'] : undefined,
344
+ importance: (i % 10) + 1,
345
+ }));
346
+ await entityManager.createEntities(entities);
347
+ const startTime = Date.now();
348
+ // Multiple complex queries
349
+ await rankedSearch.searchNodesRanked('Observation', ['important'], 5);
350
+ await booleanSearch.booleanSearch('person AND (important OR project)');
351
+ await fuzzySearch.fuzzySearch('Observatn', 0.7);
352
+ const duration = Date.now() - startTime;
353
+ expect(duration).toBeLessThan(400);
354
+ });
355
+ });
356
+ describe('Memory Efficiency', () => {
357
+ it('should handle 2000 entities without excessive memory', async () => {
358
+ // Create in batches due to 1000 entity limit
359
+ const batch1 = Array.from({ length: 1000 }, (_, i) => ({
360
+ name: `Entity${i}`,
361
+ entityType: 'test',
362
+ observations: [`Observation ${i}`],
363
+ }));
364
+ const batch2 = Array.from({ length: 1000 }, (_, i) => ({
365
+ name: `Entity${i + 1000}`,
366
+ entityType: 'test',
367
+ observations: [`Observation ${i + 1000}`],
368
+ }));
369
+ await entityManager.createEntities(batch1);
370
+ await entityManager.createEntities(batch2);
371
+ const graph = await storage.loadGraph();
372
+ expect(graph.entities).toHaveLength(2000);
373
+ });
374
+ it('should handle graph with 5000 total elements (entities + relations)', async () => {
375
+ // Create 1000 entities
376
+ const entities = Array.from({ length: 1000 }, (_, i) => ({
377
+ name: `Entity${i}`,
378
+ entityType: 'test',
379
+ observations: [`Observation ${i}`],
380
+ }));
381
+ await entityManager.createEntities(entities);
382
+ // Create 4000 relations in batches due to 1000 relation limit
383
+ const batch1 = Array.from({ length: 1000 }, (_, i) => ({
384
+ from: `Entity${i % 1000}`,
385
+ to: `Entity${(i + 1) % 1000}`,
386
+ relationType: i % 2 === 0 ? 'connects' : 'relates',
387
+ }));
388
+ const batch2 = Array.from({ length: 1000 }, (_, i) => ({
389
+ from: `Entity${(i + 1) % 1000}`,
390
+ to: `Entity${(i + 2) % 1000}`,
391
+ relationType: i % 2 === 0 ? 'links' : 'relates_to',
392
+ }));
393
+ const batch3 = Array.from({ length: 1000 }, (_, i) => ({
394
+ from: `Entity${(i + 2) % 1000}`,
395
+ to: `Entity${(i + 3) % 1000}`,
396
+ relationType: i % 2 === 0 ? 'connects_to' : 'associates',
397
+ }));
398
+ const batch4 = Array.from({ length: 1000 }, (_, i) => ({
399
+ from: `Entity${(i + 3) % 1000}`,
400
+ to: `Entity${(i + 4) % 1000}`,
401
+ relationType: i % 2 === 0 ? 'joins' : 'interacts',
402
+ }));
403
+ await relationManager.createRelations(batch1);
404
+ await relationManager.createRelations(batch2);
405
+ await relationManager.createRelations(batch3);
406
+ await relationManager.createRelations(batch4);
407
+ const graph = await storage.loadGraph();
408
+ expect(graph.entities.length + graph.relations.length).toBe(5000);
409
+ });
410
+ });
411
+ });