@smallironman/mcp-memory-keeper 0.12.2-fork1

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 (110) hide show
  1. package/CHANGELOG.md +542 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1281 -0
  4. package/bin/mcp-memory-keeper +54 -0
  5. package/dist/__tests__/e2e/issue33-reproduce.test.js +234 -0
  6. package/dist/__tests__/e2e/server-e2e.test.js +341 -0
  7. package/dist/__tests__/helpers/database-test-helper.js +160 -0
  8. package/dist/__tests__/helpers/test-server.js +92 -0
  9. package/dist/__tests__/integration/advanced-features.test.js +614 -0
  10. package/dist/__tests__/integration/backward-compatibility.test.js +245 -0
  11. package/dist/__tests__/integration/batchOperationsE2E.test.js +396 -0
  12. package/dist/__tests__/integration/batchOperationsHandler.test.js +1230 -0
  13. package/dist/__tests__/integration/channelManagementHandler.test.js +1291 -0
  14. package/dist/__tests__/integration/channels.test.js +376 -0
  15. package/dist/__tests__/integration/checkpoint.test.js +251 -0
  16. package/dist/__tests__/integration/concurrent-access.test.js +190 -0
  17. package/dist/__tests__/integration/context-operations.test.js +243 -0
  18. package/dist/__tests__/integration/contextDiff.test.js +852 -0
  19. package/dist/__tests__/integration/contextDiffHandler.test.js +976 -0
  20. package/dist/__tests__/integration/contextExportHandler.test.js +510 -0
  21. package/dist/__tests__/integration/contextGetPaginationDefaults.test.js +298 -0
  22. package/dist/__tests__/integration/contextReassignChannelHandler.test.js +908 -0
  23. package/dist/__tests__/integration/contextRelationshipsHandler.test.js +1151 -0
  24. package/dist/__tests__/integration/contextSearch.test.js +1054 -0
  25. package/dist/__tests__/integration/contextSearchHandler.test.js +552 -0
  26. package/dist/__tests__/integration/contextWatchActual.test.js +165 -0
  27. package/dist/__tests__/integration/contextWatchHandler.test.js +1500 -0
  28. package/dist/__tests__/integration/database-initialization.test.js +134 -0
  29. package/dist/__tests__/integration/enhanced-context-operations.test.js +1082 -0
  30. package/dist/__tests__/integration/enhancedContextGetHandler.test.js +915 -0
  31. package/dist/__tests__/integration/enhancedContextTimelineHandler.test.js +716 -0
  32. package/dist/__tests__/integration/error-cases.test.js +411 -0
  33. package/dist/__tests__/integration/export-import.test.js +367 -0
  34. package/dist/__tests__/integration/feature-flags.test.js +542 -0
  35. package/dist/__tests__/integration/file-operations.test.js +264 -0
  36. package/dist/__tests__/integration/filterBySessionId.test.js +251 -0
  37. package/dist/__tests__/integration/git-integration.test.js +241 -0
  38. package/dist/__tests__/integration/index-tools.test.js +496 -0
  39. package/dist/__tests__/integration/issue11-actual-bug-demo.test.js +304 -0
  40. package/dist/__tests__/integration/issue11-search-filters-bug.test.js +561 -0
  41. package/dist/__tests__/integration/issue12-checkpoint-restore-behavior.test.js +621 -0
  42. package/dist/__tests__/integration/issue13-key-validation.test.js +433 -0
  43. package/dist/__tests__/integration/issue24-final-fix.test.js +241 -0
  44. package/dist/__tests__/integration/issue24-fix-validation.test.js +158 -0
  45. package/dist/__tests__/integration/issue24-reproduce.test.js +225 -0
  46. package/dist/__tests__/integration/issue24-token-limit.test.js +199 -0
  47. package/dist/__tests__/integration/issue33-array-items-schema.test.js +165 -0
  48. package/dist/__tests__/integration/knowledge-graph.test.js +338 -0
  49. package/dist/__tests__/integration/migrations.test.js +528 -0
  50. package/dist/__tests__/integration/multi-agent.test.js +546 -0
  51. package/dist/__tests__/integration/pagination-critical-fix.test.js +296 -0
  52. package/dist/__tests__/integration/paginationDefaultsHandler.test.js +600 -0
  53. package/dist/__tests__/integration/project-directory.test.js +291 -0
  54. package/dist/__tests__/integration/resource-cleanup.test.js +149 -0
  55. package/dist/__tests__/integration/retention.test.js +513 -0
  56. package/dist/__tests__/integration/search.test.js +333 -0
  57. package/dist/__tests__/integration/semantic-search.test.js +266 -0
  58. package/dist/__tests__/integration/server-initialization.test.js +305 -0
  59. package/dist/__tests__/integration/session-management.test.js +219 -0
  60. package/dist/__tests__/integration/simplified-sharing.test.js +346 -0
  61. package/dist/__tests__/integration/smart-compaction.test.js +230 -0
  62. package/dist/__tests__/integration/summarization.test.js +308 -0
  63. package/dist/__tests__/integration/tokenLimitEnforcement.test.js +134 -0
  64. package/dist/__tests__/integration/tool-profiles-integration.test.js +150 -0
  65. package/dist/__tests__/integration/watcher-migration-validation.test.js +544 -0
  66. package/dist/__tests__/security/input-validation.test.js +115 -0
  67. package/dist/__tests__/utils/agents.test.js +473 -0
  68. package/dist/__tests__/utils/database.test.js +177 -0
  69. package/dist/__tests__/utils/git.test.js +122 -0
  70. package/dist/__tests__/utils/knowledge-graph.test.js +297 -0
  71. package/dist/__tests__/utils/migrationHealthCheck.test.js +302 -0
  72. package/dist/__tests__/utils/project-directory-messages.test.js +192 -0
  73. package/dist/__tests__/utils/timezone-safe-dates.js +119 -0
  74. package/dist/__tests__/utils/token-limits.test.js +225 -0
  75. package/dist/__tests__/utils/tool-profiles.test.js +374 -0
  76. package/dist/__tests__/utils/validation.test.js +200 -0
  77. package/dist/__tests__/utils/vector-store.test.js +231 -0
  78. package/dist/handlers/contextWatchHandlers.js +206 -0
  79. package/dist/index.js +4425 -0
  80. package/dist/migrations/003_add_channels.js +174 -0
  81. package/dist/migrations/004_add_context_watch.js +151 -0
  82. package/dist/migrations/005_add_context_watch.js +98 -0
  83. package/dist/migrations/simplify-sharing.js +117 -0
  84. package/dist/repositories/BaseRepository.js +30 -0
  85. package/dist/repositories/CheckpointRepository.js +140 -0
  86. package/dist/repositories/ContextRepository.js +2017 -0
  87. package/dist/repositories/FileRepository.js +104 -0
  88. package/dist/repositories/RepositoryManager.js +62 -0
  89. package/dist/repositories/SessionRepository.js +66 -0
  90. package/dist/repositories/WatcherRepository.js +252 -0
  91. package/dist/repositories/index.js +15 -0
  92. package/dist/test-helpers/database-helper.js +128 -0
  93. package/dist/types/entities.js +3 -0
  94. package/dist/utils/agents.js +791 -0
  95. package/dist/utils/channels.js +150 -0
  96. package/dist/utils/database.js +780 -0
  97. package/dist/utils/feature-flags.js +476 -0
  98. package/dist/utils/git.js +145 -0
  99. package/dist/utils/knowledge-graph.js +264 -0
  100. package/dist/utils/migrationHealthCheck.js +373 -0
  101. package/dist/utils/migrations.js +452 -0
  102. package/dist/utils/retention.js +460 -0
  103. package/dist/utils/timestamps.js +112 -0
  104. package/dist/utils/token-limits.js +350 -0
  105. package/dist/utils/tool-profiles.js +242 -0
  106. package/dist/utils/validation.js +296 -0
  107. package/dist/utils/vector-store.js +247 -0
  108. package/examples/config.json +31 -0
  109. package/examples/project-directory-setup.md +114 -0
  110. package/package.json +85 -0
@@ -0,0 +1,552 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const globals_1 = require("@jest/globals");
37
+ const database_1 = require("../../utils/database");
38
+ const ContextRepository_1 = require("../../repositories/ContextRepository");
39
+ const os = __importStar(require("os"));
40
+ const path = __importStar(require("path"));
41
+ const fs = __importStar(require("fs"));
42
+ const uuid_1 = require("uuid");
43
+ (0, globals_1.describe)('Context Search Handler Integration Tests', () => {
44
+ let dbManager;
45
+ let tempDbPath;
46
+ let db;
47
+ let contextRepo;
48
+ let testSessionId;
49
+ (0, globals_1.beforeEach)(() => {
50
+ tempDbPath = path.join(os.tmpdir(), `test-context-search-handler-${Date.now()}.db`);
51
+ dbManager = new database_1.DatabaseManager({
52
+ filename: tempDbPath,
53
+ maxSize: 10 * 1024 * 1024,
54
+ walMode: true,
55
+ });
56
+ db = dbManager.getDatabase();
57
+ contextRepo = new ContextRepository_1.ContextRepository(dbManager);
58
+ // Create test session
59
+ testSessionId = (0, uuid_1.v4)();
60
+ db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(testSessionId, 'Test Session');
61
+ });
62
+ (0, globals_1.afterEach)(() => {
63
+ dbManager.close();
64
+ try {
65
+ fs.unlinkSync(tempDbPath);
66
+ fs.unlinkSync(`${tempDbPath}-wal`);
67
+ fs.unlinkSync(`${tempDbPath}-shm`);
68
+ }
69
+ catch (_e) {
70
+ // Ignore
71
+ }
72
+ });
73
+ (0, globals_1.describe)('Enhanced Search Method', () => {
74
+ (0, globals_1.beforeEach)(() => {
75
+ // Create comprehensive test data
76
+ const now = new Date();
77
+ const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);
78
+ const lastWeek = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
79
+ const items = [
80
+ {
81
+ id: (0, uuid_1.v4)(),
82
+ session_id: testSessionId,
83
+ key: 'auth_config',
84
+ value: 'Authentication configuration for the app',
85
+ category: 'config',
86
+ priority: 'high',
87
+ channel: 'main',
88
+ created_at: now.toISOString(),
89
+ metadata: JSON.stringify({ tags: ['auth', 'config'] }),
90
+ size: 40,
91
+ },
92
+ {
93
+ id: (0, uuid_1.v4)(),
94
+ session_id: testSessionId,
95
+ key: 'db_auth_connection',
96
+ value: 'Database connection string with auth params',
97
+ category: 'config',
98
+ priority: 'normal',
99
+ channel: 'feature/auth',
100
+ created_at: yesterday.toISOString(),
101
+ metadata: JSON.stringify({ tags: ['db', 'auth'] }),
102
+ size: 45,
103
+ },
104
+ {
105
+ id: (0, uuid_1.v4)(),
106
+ session_id: testSessionId,
107
+ key: 'user_model',
108
+ value: 'User model with authentication methods',
109
+ category: 'code',
110
+ priority: 'high',
111
+ channel: 'main',
112
+ created_at: lastWeek.toISOString(),
113
+ metadata: null,
114
+ size: 38,
115
+ },
116
+ {
117
+ id: (0, uuid_1.v4)(),
118
+ session_id: testSessionId,
119
+ key: 'api_endpoints',
120
+ value: 'API endpoints documentation',
121
+ category: 'docs',
122
+ priority: 'normal',
123
+ channel: 'main',
124
+ created_at: now.toISOString(),
125
+ is_private: 1,
126
+ },
127
+ ];
128
+ // Insert test data
129
+ const stmt = db.prepare(`
130
+ INSERT INTO context_items (
131
+ id, session_id, key, value, category, priority, channel,
132
+ created_at, metadata, size, is_private
133
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
134
+ `);
135
+ items.forEach(item => {
136
+ stmt.run(item.id, item.session_id, item.key, item.value, item.category || null, item.priority || 'normal', item.channel || null, item.created_at || new Date().toISOString(), item.metadata || null, item.size || Buffer.byteLength(item.value, 'utf8'), item.is_private || 0);
137
+ });
138
+ });
139
+ (0, globals_1.it)('should search with basic query maintaining backward compatibility', () => {
140
+ // Test the existing search method
141
+ const results = contextRepo.search('auth', testSessionId, true);
142
+ (0, globals_1.expect)(results.length).toBeGreaterThanOrEqual(3); // auth_config, db_auth_connection, user_model
143
+ (0, globals_1.expect)(results.every(r => r.key.includes('auth') || r.value.includes('auth') || r.value.includes('Auth'))).toBe(true);
144
+ });
145
+ (0, globals_1.it)('should handle enhanced search with time filtering', () => {
146
+ const twoDaysAgo = new Date();
147
+ twoDaysAgo.setDate(twoDaysAgo.getDate() - 2);
148
+ // Simulate enhanced search parameters
149
+ const searchParams = {
150
+ query: 'auth',
151
+ sessionId: testSessionId,
152
+ createdAfter: twoDaysAgo.toISOString(),
153
+ searchIn: ['key', 'value'],
154
+ };
155
+ // Build query similar to how enhanced search would work
156
+ let sql = `
157
+ SELECT * FROM context_items
158
+ WHERE session_id = ?
159
+ AND (key LIKE ? OR value LIKE ?)
160
+ AND created_at > ?
161
+ ORDER BY priority DESC, created_at DESC
162
+ `;
163
+ const results = db
164
+ .prepare(sql)
165
+ .all(searchParams.sessionId, `%${searchParams.query}%`, `%${searchParams.query}%`, searchParams.createdAfter);
166
+ (0, globals_1.expect)(results.length).toBe(2); // auth_config and db_auth_connection (not user_model which is older)
167
+ });
168
+ (0, globals_1.it)('should handle enhanced search with channel filtering', () => {
169
+ const searchParams = {
170
+ query: 'auth',
171
+ sessionId: testSessionId,
172
+ channel: 'feature/auth',
173
+ };
174
+ let sql = `
175
+ SELECT * FROM context_items
176
+ WHERE session_id = ?
177
+ AND (key LIKE ? OR value LIKE ?)
178
+ AND channel = ?
179
+ ORDER BY priority DESC, created_at DESC
180
+ `;
181
+ const results = db
182
+ .prepare(sql)
183
+ .all(searchParams.sessionId, `%${searchParams.query}%`, `%${searchParams.query}%`, searchParams.channel);
184
+ (0, globals_1.expect)(results.length).toBe(1);
185
+ (0, globals_1.expect)(results[0].key).toBe('db_auth_connection');
186
+ });
187
+ (0, globals_1.it)('should handle enhanced search with multiple channels', () => {
188
+ const searchParams = {
189
+ query: 'auth',
190
+ sessionId: testSessionId,
191
+ channels: ['main', 'feature/auth'],
192
+ };
193
+ const placeholders = searchParams.channels.map(() => '?').join(',');
194
+ let sql = `
195
+ SELECT * FROM context_items
196
+ WHERE session_id = ?
197
+ AND (key LIKE ? OR value LIKE ?)
198
+ AND channel IN (${placeholders})
199
+ ORDER BY priority DESC, created_at DESC
200
+ `;
201
+ const results = db
202
+ .prepare(sql)
203
+ .all(searchParams.sessionId, `%${searchParams.query}%`, `%${searchParams.query}%`, ...searchParams.channels);
204
+ (0, globals_1.expect)(results.length).toBe(3); // All items with 'auth' in main or feature/auth channels
205
+ });
206
+ (0, globals_1.it)('should handle sort parameter correctly', () => {
207
+ const searchParams = {
208
+ query: 'auth',
209
+ sessionId: testSessionId,
210
+ sort: 'key_asc',
211
+ };
212
+ let sql = `
213
+ SELECT * FROM context_items
214
+ WHERE session_id = ?
215
+ AND (key LIKE ? OR value LIKE ?)
216
+ ORDER BY key ASC
217
+ `;
218
+ const results = db
219
+ .prepare(sql)
220
+ .all(searchParams.sessionId, `%${searchParams.query}%`, `%${searchParams.query}%`);
221
+ (0, globals_1.expect)(results[0].key).toBe('auth_config');
222
+ (0, globals_1.expect)(results[1].key).toBe('db_auth_connection');
223
+ });
224
+ (0, globals_1.it)('should include metadata when requested', () => {
225
+ const searchParams = {
226
+ query: 'auth',
227
+ sessionId: testSessionId,
228
+ includeMetadata: true,
229
+ };
230
+ const results = db
231
+ .prepare(`
232
+ SELECT *, size FROM context_items
233
+ WHERE session_id = ?
234
+ AND (key LIKE ? OR value LIKE ?)
235
+ ORDER BY created_at DESC
236
+ `)
237
+ .all(searchParams.sessionId, `%${searchParams.query}%`, `%${searchParams.query}%`);
238
+ results.forEach((item) => {
239
+ if (searchParams.includeMetadata) {
240
+ // Verify metadata structure
241
+ if (item.metadata) {
242
+ const parsed = JSON.parse(item.metadata);
243
+ (0, globals_1.expect)(parsed).toHaveProperty('tags');
244
+ }
245
+ // Verify size is included
246
+ (0, globals_1.expect)(item.size).toBeGreaterThan(0);
247
+ }
248
+ });
249
+ });
250
+ (0, globals_1.it)('should handle pagination correctly', () => {
251
+ const searchParams = {
252
+ query: 'auth',
253
+ sessionId: testSessionId,
254
+ limit: 2,
255
+ offset: 1,
256
+ };
257
+ // Get total count first
258
+ const countResult = db
259
+ .prepare(`
260
+ SELECT COUNT(*) as count FROM context_items
261
+ WHERE session_id = ?
262
+ AND (key LIKE ? OR value LIKE ?)
263
+ `)
264
+ .get(searchParams.sessionId, `%${searchParams.query}%`, `%${searchParams.query}%`);
265
+ const totalCount = countResult.count;
266
+ // Get paginated results
267
+ const results = db
268
+ .prepare(`
269
+ SELECT * FROM context_items
270
+ WHERE session_id = ?
271
+ AND (key LIKE ? OR value LIKE ?)
272
+ ORDER BY created_at DESC
273
+ LIMIT ? OFFSET ?
274
+ `)
275
+ .all(searchParams.sessionId, `%${searchParams.query}%`, `%${searchParams.query}%`, searchParams.limit, searchParams.offset);
276
+ (0, globals_1.expect)(results.length).toBeLessThanOrEqual(searchParams.limit);
277
+ (0, globals_1.expect)(totalCount).toBeGreaterThanOrEqual(3);
278
+ });
279
+ (0, globals_1.it)('should handle keyPattern for regex-like matching', () => {
280
+ const searchParams = {
281
+ query: 'config', // Search in value
282
+ sessionId: testSessionId,
283
+ keyPattern: '*_config', // GLOB pattern for keys ending with _config
284
+ };
285
+ let sql = `
286
+ SELECT * FROM context_items
287
+ WHERE session_id = ?
288
+ AND (key LIKE ? OR value LIKE ?)
289
+ AND key GLOB ?
290
+ ORDER BY created_at DESC
291
+ `;
292
+ const results = db
293
+ .prepare(sql)
294
+ .all(searchParams.sessionId, `%${searchParams.query}%`, `%${searchParams.query}%`, searchParams.keyPattern);
295
+ (0, globals_1.expect)(results.length).toBe(1);
296
+ (0, globals_1.expect)(results[0].key).toBe('auth_config');
297
+ });
298
+ (0, globals_1.it)('should filter by priorities', () => {
299
+ const searchParams = {
300
+ query: 'auth',
301
+ sessionId: testSessionId,
302
+ priorities: ['high'],
303
+ };
304
+ const placeholders = searchParams.priorities.map(() => '?').join(',');
305
+ let sql = `
306
+ SELECT * FROM context_items
307
+ WHERE session_id = ?
308
+ AND (key LIKE ? OR value LIKE ?)
309
+ AND priority IN (${placeholders})
310
+ ORDER BY created_at DESC
311
+ `;
312
+ const results = db
313
+ .prepare(sql)
314
+ .all(searchParams.sessionId, `%${searchParams.query}%`, `%${searchParams.query}%`, ...searchParams.priorities);
315
+ (0, globals_1.expect)(results.length).toBe(2); // auth_config and user_model
316
+ (0, globals_1.expect)(results.every((r) => r.priority === 'high')).toBe(true);
317
+ });
318
+ (0, globals_1.it)('should respect privacy settings', () => {
319
+ // Search without session (should not see private items)
320
+ const publicResults = db
321
+ .prepare(`
322
+ SELECT * FROM context_items
323
+ WHERE is_private = 0
324
+ ORDER BY created_at DESC
325
+ `)
326
+ .all();
327
+ (0, globals_1.expect)(publicResults.some((r) => r.key === 'api_endpoints')).toBe(false);
328
+ // Search with session (should see own private items)
329
+ const sessionResults = db
330
+ .prepare(`
331
+ SELECT * FROM context_items
332
+ WHERE (is_private = 0 OR session_id = ?)
333
+ ORDER BY created_at DESC
334
+ `)
335
+ .all(testSessionId);
336
+ (0, globals_1.expect)(sessionResults.some((r) => r.key === 'api_endpoints')).toBe(true);
337
+ });
338
+ (0, globals_1.it)('should handle relative time parsing', () => {
339
+ // Add a recent item
340
+ const oneHourAgo = new Date();
341
+ oneHourAgo.setHours(oneHourAgo.getHours() - 1);
342
+ db.prepare('INSERT INTO context_items (id, session_id, key, value, created_at) VALUES (?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, 'recent_auth_task', 'Recent authentication task', oneHourAgo.toISOString());
343
+ // Simulate relative time parsing
344
+ const relativeTime = '2 hours ago';
345
+ const match = relativeTime.match(/^(\d+) hours? ago$/);
346
+ (0, globals_1.expect)(match).toBeTruthy();
347
+ const hours = parseInt(match[1]);
348
+ const cutoffTime = new Date();
349
+ cutoffTime.setHours(cutoffTime.getHours() - hours);
350
+ const results = db
351
+ .prepare(`
352
+ SELECT * FROM context_items
353
+ WHERE session_id = ?
354
+ AND (key LIKE ? OR value LIKE ?)
355
+ AND created_at > ?
356
+ ORDER BY created_at DESC
357
+ `)
358
+ .all(testSessionId, '%auth%', '%auth%', cutoffTime.toISOString());
359
+ (0, globals_1.expect)(results.some((r) => r.key === 'recent_auth_task')).toBe(true);
360
+ (0, globals_1.expect)(results.some((r) => r.key === 'auth_config')).toBe(true);
361
+ });
362
+ (0, globals_1.it)('should handle complex combined filters', () => {
363
+ const now = new Date();
364
+ const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000);
365
+ const searchParams = {
366
+ query: 'auth',
367
+ sessionId: testSessionId,
368
+ channels: ['main', 'feature/auth'],
369
+ priorities: ['high', 'normal'],
370
+ createdAfter: twoDaysAgo.toISOString(),
371
+ sort: 'created_at_desc',
372
+ limit: 10,
373
+ includeMetadata: true,
374
+ };
375
+ // Build complex query
376
+ const channelPlaceholders = searchParams.channels.map(() => '?').join(',');
377
+ const priorityPlaceholders = searchParams.priorities.map(() => '?').join(',');
378
+ let sql = `
379
+ SELECT *, size FROM context_items
380
+ WHERE session_id = ?
381
+ AND (key LIKE ? OR value LIKE ?)
382
+ AND channel IN (${channelPlaceholders})
383
+ AND priority IN (${priorityPlaceholders})
384
+ AND created_at > ?
385
+ ORDER BY created_at DESC
386
+ LIMIT ?
387
+ `;
388
+ const results = db
389
+ .prepare(sql)
390
+ .all(searchParams.sessionId, `%${searchParams.query}%`, `%${searchParams.query}%`, ...searchParams.channels, ...searchParams.priorities, searchParams.createdAfter, searchParams.limit);
391
+ // Should get auth_config and db_auth_connection (not user_model which is older)
392
+ (0, globals_1.expect)(results.length).toBe(2);
393
+ (0, globals_1.expect)(results[0].key).toBe('auth_config'); // Most recent
394
+ (0, globals_1.expect)(results[1].key).toBe('db_auth_connection');
395
+ });
396
+ });
397
+ (0, globals_1.describe)('searchIn Parameter Handling', () => {
398
+ (0, globals_1.beforeEach)(() => {
399
+ // Add specific test data
400
+ db.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, 'authentication_service', 'Service for user login');
401
+ db.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, 'user_service', 'Service for authentication');
402
+ });
403
+ (0, globals_1.it)('should search in both key and value when searchIn includes both', () => {
404
+ const searchParams = {
405
+ query: 'authentication',
406
+ sessionId: testSessionId,
407
+ searchIn: ['key', 'value'],
408
+ };
409
+ const results = db
410
+ .prepare(`
411
+ SELECT * FROM context_items
412
+ WHERE session_id = ?
413
+ AND (key LIKE ? OR value LIKE ?)
414
+ ORDER BY created_at DESC
415
+ `)
416
+ .all(searchParams.sessionId, `%${searchParams.query}%`, `%${searchParams.query}%`);
417
+ (0, globals_1.expect)(results.length).toBe(2);
418
+ });
419
+ (0, globals_1.it)('should search only in keys when searchIn is ["key"]', () => {
420
+ const searchParams = {
421
+ query: 'authentication',
422
+ sessionId: testSessionId,
423
+ searchIn: ['key'],
424
+ };
425
+ const results = db
426
+ .prepare(`
427
+ SELECT * FROM context_items
428
+ WHERE session_id = ?
429
+ AND key LIKE ?
430
+ ORDER BY created_at DESC
431
+ `)
432
+ .all(searchParams.sessionId, `%${searchParams.query}%`);
433
+ (0, globals_1.expect)(results.length).toBe(1);
434
+ (0, globals_1.expect)(results[0].key).toBe('authentication_service');
435
+ });
436
+ (0, globals_1.it)('should search only in values when searchIn is ["value"]', () => {
437
+ const searchParams = {
438
+ query: 'authentication',
439
+ sessionId: testSessionId,
440
+ searchIn: ['value'],
441
+ };
442
+ const results = db
443
+ .prepare(`
444
+ SELECT * FROM context_items
445
+ WHERE session_id = ?
446
+ AND value LIKE ?
447
+ ORDER BY created_at DESC
448
+ `)
449
+ .all(searchParams.sessionId, `%${searchParams.query}%`);
450
+ (0, globals_1.expect)(results.length).toBe(1);
451
+ (0, globals_1.expect)(results[0].key).toBe('user_service');
452
+ });
453
+ });
454
+ (0, globals_1.describe)('Response Format', () => {
455
+ (0, globals_1.beforeEach)(() => {
456
+ // Add test data
457
+ for (let i = 0; i < 5; i++) {
458
+ db.prepare('INSERT INTO context_items (id, session_id, key, value, priority, size) VALUES (?, ?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, `test_item_${i}`, `Test value containing auth keyword ${i}`, i % 2 === 0 ? 'high' : 'normal', 50 + i * 10);
459
+ }
460
+ });
461
+ (0, globals_1.it)('should format response without metadata', () => {
462
+ const results = db
463
+ .prepare(`
464
+ SELECT * FROM context_items
465
+ WHERE session_id = ?
466
+ AND value LIKE ?
467
+ ORDER BY created_at DESC
468
+ `)
469
+ .all(testSessionId, '%auth%');
470
+ // Simulate handler response formatting
471
+ const formatted = results.map((r) => ({
472
+ key: r.key,
473
+ value: r.value,
474
+ category: r.category,
475
+ priority: r.priority,
476
+ }));
477
+ (0, globals_1.expect)(formatted.length).toBe(5);
478
+ formatted.forEach((item) => {
479
+ (0, globals_1.expect)(item).toHaveProperty('key');
480
+ (0, globals_1.expect)(item).toHaveProperty('value');
481
+ (0, globals_1.expect)(item).toHaveProperty('priority');
482
+ (0, globals_1.expect)(item).not.toHaveProperty('size');
483
+ (0, globals_1.expect)(item).not.toHaveProperty('created_at');
484
+ });
485
+ });
486
+ (0, globals_1.it)('should format response with metadata when requested', () => {
487
+ const results = db
488
+ .prepare(`
489
+ SELECT *, size FROM context_items
490
+ WHERE session_id = ?
491
+ AND value LIKE ?
492
+ ORDER BY created_at DESC
493
+ LIMIT 3
494
+ `)
495
+ .all(testSessionId, '%auth%');
496
+ // Get total count
497
+ const countResult = db
498
+ .prepare(`
499
+ SELECT COUNT(*) as count FROM context_items
500
+ WHERE session_id = ?
501
+ AND value LIKE ?
502
+ `)
503
+ .get(testSessionId, '%auth%');
504
+ // Simulate handler response with metadata
505
+ const formattedWithMetadata = {
506
+ items: results.map((item) => ({
507
+ key: item.key,
508
+ value: item.value,
509
+ category: item.category,
510
+ priority: item.priority,
511
+ channel: item.channel,
512
+ metadata: item.metadata ? JSON.parse(item.metadata) : null,
513
+ size: item.size,
514
+ created_at: item.created_at,
515
+ updated_at: item.updated_at,
516
+ })),
517
+ totalCount: countResult.count,
518
+ page: 1,
519
+ pageSize: 3,
520
+ };
521
+ (0, globals_1.expect)(formattedWithMetadata.items.length).toBe(3);
522
+ (0, globals_1.expect)(formattedWithMetadata.totalCount).toBe(5);
523
+ (0, globals_1.expect)(formattedWithMetadata.page).toBe(1);
524
+ (0, globals_1.expect)(formattedWithMetadata.pageSize).toBe(3);
525
+ formattedWithMetadata.items.forEach((item) => {
526
+ (0, globals_1.expect)(item).toHaveProperty('size');
527
+ (0, globals_1.expect)(item).toHaveProperty('created_at');
528
+ (0, globals_1.expect)(item).toHaveProperty('updated_at');
529
+ });
530
+ });
531
+ });
532
+ (0, globals_1.describe)('Error Handling', () => {
533
+ (0, globals_1.it)('should handle empty search query gracefully', () => {
534
+ const results = contextRepo.search('', testSessionId, true);
535
+ (0, globals_1.expect)(results.length).toBe(0);
536
+ });
537
+ (0, globals_1.it)('should handle non-existent session gracefully', () => {
538
+ const results = contextRepo.search('test', 'non-existent-session', true);
539
+ (0, globals_1.expect)(results.length).toBe(0);
540
+ });
541
+ (0, globals_1.it)('should handle SQL injection attempts safely', () => {
542
+ const maliciousQuery = "'; DROP TABLE context_items; --";
543
+ // This should not throw and should not damage the database
544
+ const _results = contextRepo.search(maliciousQuery, testSessionId, true);
545
+ // Verify table still exists
546
+ const tableExists = db
547
+ .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='context_items'")
548
+ .get();
549
+ (0, globals_1.expect)(tableExists).toBeTruthy();
550
+ });
551
+ });
552
+ });