mcp-memory-keeper 0.10.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 (98) hide show
  1. package/CHANGELOG.md +433 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1051 -0
  4. package/bin/mcp-memory-keeper +52 -0
  5. package/dist/__tests__/helpers/database-test-helper.js +160 -0
  6. package/dist/__tests__/helpers/test-server.js +92 -0
  7. package/dist/__tests__/integration/advanced-features.test.js +614 -0
  8. package/dist/__tests__/integration/backward-compatibility.test.js +245 -0
  9. package/dist/__tests__/integration/batchOperationsE2E.test.js +396 -0
  10. package/dist/__tests__/integration/batchOperationsHandler.test.js +1230 -0
  11. package/dist/__tests__/integration/channelManagementHandler.test.js +1291 -0
  12. package/dist/__tests__/integration/channels.test.js +376 -0
  13. package/dist/__tests__/integration/checkpoint.test.js +251 -0
  14. package/dist/__tests__/integration/concurrent-access.test.js +190 -0
  15. package/dist/__tests__/integration/context-operations.test.js +243 -0
  16. package/dist/__tests__/integration/contextDiff.test.js +852 -0
  17. package/dist/__tests__/integration/contextDiffHandler.test.js +976 -0
  18. package/dist/__tests__/integration/contextExportHandler.test.js +510 -0
  19. package/dist/__tests__/integration/contextGetPaginationDefaults.test.js +298 -0
  20. package/dist/__tests__/integration/contextReassignChannelHandler.test.js +908 -0
  21. package/dist/__tests__/integration/contextRelationshipsHandler.test.js +1151 -0
  22. package/dist/__tests__/integration/contextSearch.test.js +938 -0
  23. package/dist/__tests__/integration/contextSearchHandler.test.js +552 -0
  24. package/dist/__tests__/integration/contextWatchActual.test.js +165 -0
  25. package/dist/__tests__/integration/contextWatchHandler.test.js +1500 -0
  26. package/dist/__tests__/integration/cross-session-sharing.test.js +302 -0
  27. package/dist/__tests__/integration/database-initialization.test.js +134 -0
  28. package/dist/__tests__/integration/enhanced-context-operations.test.js +1082 -0
  29. package/dist/__tests__/integration/enhancedContextGetHandler.test.js +915 -0
  30. package/dist/__tests__/integration/enhancedContextTimelineHandler.test.js +716 -0
  31. package/dist/__tests__/integration/error-cases.test.js +407 -0
  32. package/dist/__tests__/integration/export-import.test.js +367 -0
  33. package/dist/__tests__/integration/feature-flags.test.js +542 -0
  34. package/dist/__tests__/integration/file-operations.test.js +264 -0
  35. package/dist/__tests__/integration/git-integration.test.js +237 -0
  36. package/dist/__tests__/integration/index-tools.test.js +496 -0
  37. package/dist/__tests__/integration/issue11-actual-bug-demo.test.js +304 -0
  38. package/dist/__tests__/integration/issue11-search-filters-bug.test.js +561 -0
  39. package/dist/__tests__/integration/issue12-checkpoint-restore-behavior.test.js +621 -0
  40. package/dist/__tests__/integration/issue13-key-validation.test.js +433 -0
  41. package/dist/__tests__/integration/knowledge-graph.test.js +338 -0
  42. package/dist/__tests__/integration/migrations.test.js +528 -0
  43. package/dist/__tests__/integration/multi-agent.test.js +546 -0
  44. package/dist/__tests__/integration/pagination-critical-fix.test.js +296 -0
  45. package/dist/__tests__/integration/paginationDefaultsHandler.test.js +600 -0
  46. package/dist/__tests__/integration/project-directory.test.js +283 -0
  47. package/dist/__tests__/integration/resource-cleanup.test.js +149 -0
  48. package/dist/__tests__/integration/retention.test.js +513 -0
  49. package/dist/__tests__/integration/search.test.js +333 -0
  50. package/dist/__tests__/integration/semantic-search.test.js +266 -0
  51. package/dist/__tests__/integration/server-initialization.test.js +307 -0
  52. package/dist/__tests__/integration/session-management.test.js +219 -0
  53. package/dist/__tests__/integration/simplified-sharing.test.js +346 -0
  54. package/dist/__tests__/integration/smart-compaction.test.js +230 -0
  55. package/dist/__tests__/integration/summarization.test.js +308 -0
  56. package/dist/__tests__/integration/watcher-migration-validation.test.js +544 -0
  57. package/dist/__tests__/security/input-validation.test.js +115 -0
  58. package/dist/__tests__/utils/agents.test.js +473 -0
  59. package/dist/__tests__/utils/database.test.js +177 -0
  60. package/dist/__tests__/utils/git.test.js +122 -0
  61. package/dist/__tests__/utils/knowledge-graph.test.js +297 -0
  62. package/dist/__tests__/utils/migrationHealthCheck.test.js +302 -0
  63. package/dist/__tests__/utils/project-directory-messages.test.js +188 -0
  64. package/dist/__tests__/utils/timezone-safe-dates.js +119 -0
  65. package/dist/__tests__/utils/validation.test.js +200 -0
  66. package/dist/__tests__/utils/vector-store.test.js +231 -0
  67. package/dist/handlers/contextWatchHandlers.js +206 -0
  68. package/dist/index.js +4310 -0
  69. package/dist/index.phase1.backup.js +410 -0
  70. package/dist/index.phase2.backup.js +704 -0
  71. package/dist/migrations/003_add_channels.js +174 -0
  72. package/dist/migrations/004_add_context_watch.js +151 -0
  73. package/dist/migrations/005_add_context_watch.js +98 -0
  74. package/dist/migrations/simplify-sharing.js +117 -0
  75. package/dist/repositories/BaseRepository.js +30 -0
  76. package/dist/repositories/CheckpointRepository.js +140 -0
  77. package/dist/repositories/ContextRepository.js +1873 -0
  78. package/dist/repositories/FileRepository.js +104 -0
  79. package/dist/repositories/RepositoryManager.js +62 -0
  80. package/dist/repositories/SessionRepository.js +66 -0
  81. package/dist/repositories/WatcherRepository.js +252 -0
  82. package/dist/repositories/index.js +15 -0
  83. package/dist/server.js +384 -0
  84. package/dist/test-helpers/database-helper.js +128 -0
  85. package/dist/types/entities.js +3 -0
  86. package/dist/utils/agents.js +791 -0
  87. package/dist/utils/channels.js +150 -0
  88. package/dist/utils/database.js +731 -0
  89. package/dist/utils/feature-flags.js +476 -0
  90. package/dist/utils/git.js +145 -0
  91. package/dist/utils/knowledge-graph.js +264 -0
  92. package/dist/utils/migrationHealthCheck.js +373 -0
  93. package/dist/utils/migrations.js +452 -0
  94. package/dist/utils/retention.js +460 -0
  95. package/dist/utils/timestamps.js +112 -0
  96. package/dist/utils/validation.js +296 -0
  97. package/dist/utils/vector-store.js +247 -0
  98. package/package.json +84 -0
@@ -0,0 +1,600 @@
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
+ // Removed unused import: ValidationError
44
+ (0, globals_1.describe)('Pagination Defaults Handler Integration Tests', () => {
45
+ let dbManager;
46
+ let tempDbPath;
47
+ let db;
48
+ let contextRepo;
49
+ let testSessionId;
50
+ (0, globals_1.beforeEach)(() => {
51
+ tempDbPath = path.join(os.tmpdir(), `test-pagination-defaults-${Date.now()}.db`);
52
+ dbManager = new database_1.DatabaseManager({
53
+ filename: tempDbPath,
54
+ maxSize: 10 * 1024 * 1024,
55
+ walMode: true,
56
+ });
57
+ db = dbManager.getDatabase();
58
+ contextRepo = new ContextRepository_1.ContextRepository(dbManager);
59
+ // Create test session
60
+ testSessionId = (0, uuid_1.v4)();
61
+ db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(testSessionId, 'Test Session');
62
+ });
63
+ (0, globals_1.afterEach)(() => {
64
+ dbManager.close();
65
+ try {
66
+ fs.unlinkSync(tempDbPath);
67
+ fs.unlinkSync(`${tempDbPath}-wal`);
68
+ fs.unlinkSync(`${tempDbPath}-shm`);
69
+ }
70
+ catch (_e) {
71
+ // Ignore
72
+ }
73
+ });
74
+ (0, globals_1.describe)('Default Pagination Parameters', () => {
75
+ (0, globals_1.beforeEach)(() => {
76
+ // Create 150 test items to test pagination limits
77
+ const now = new Date();
78
+ for (let i = 0; i < 150; i++) {
79
+ const createdAt = new Date(now.getTime() - i * 60 * 1000); // 1 minute apart
80
+ db.prepare(`
81
+ INSERT INTO context_items (id, session_id, key, value, created_at, priority, size)
82
+ VALUES (?, ?, ?, ?, ?, ?, ?)
83
+ `).run((0, uuid_1.v4)(), testSessionId, `test.item.${i.toString().padStart(3, '0')}`, `Test value ${i} - ${`This is a longer test value to simulate real content that might contain various information and details that would typically be stored in a context item.`.repeat(3)}`, createdAt.toISOString(), i % 3 === 0 ? 'high' : i % 3 === 1 ? 'normal' : 'low', 500 + i * 10 // Varying sizes
84
+ );
85
+ }
86
+ });
87
+ (0, globals_1.it)('should apply default limit of 100 when not specified', () => {
88
+ const result = contextRepo.queryEnhanced({
89
+ sessionId: testSessionId,
90
+ });
91
+ (0, globals_1.expect)(result.items.length).toBe(100);
92
+ (0, globals_1.expect)(result.totalCount).toBe(150);
93
+ });
94
+ (0, globals_1.it)('should apply default sort of created_desc when not specified', () => {
95
+ const result = contextRepo.queryEnhanced({
96
+ sessionId: testSessionId,
97
+ });
98
+ // Verify items are sorted by created_at descending (newest first)
99
+ for (let i = 1; i < result.items.length; i++) {
100
+ const prevDate = new Date(result.items[i - 1].created_at);
101
+ const currDate = new Date(result.items[i].created_at);
102
+ (0, globals_1.expect)(prevDate.getTime()).toBeGreaterThanOrEqual(currDate.getTime());
103
+ }
104
+ // First item should be the most recent (test.item.000)
105
+ (0, globals_1.expect)(result.items[0].key).toBe('test.item.000');
106
+ });
107
+ (0, globals_1.it)('should respect explicit limit over default', () => {
108
+ const result = contextRepo.queryEnhanced({
109
+ sessionId: testSessionId,
110
+ limit: 50,
111
+ });
112
+ (0, globals_1.expect)(result.items.length).toBe(50);
113
+ (0, globals_1.expect)(result.totalCount).toBe(150);
114
+ });
115
+ (0, globals_1.it)('should respect explicit sort over default', () => {
116
+ const result = contextRepo.queryEnhanced({
117
+ sessionId: testSessionId,
118
+ sort: 'key_asc',
119
+ });
120
+ // Verify items are sorted by key ascending
121
+ for (let i = 1; i < result.items.length; i++) {
122
+ (0, globals_1.expect)(result.items[i - 1].key.localeCompare(result.items[i].key)).toBeLessThanOrEqual(0);
123
+ }
124
+ // First item should be test.item.000 (lexicographically first)
125
+ (0, globals_1.expect)(result.items[0].key).toBe('test.item.000');
126
+ });
127
+ (0, globals_1.it)('should handle limit of 0 as unlimited', () => {
128
+ const result = contextRepo.queryEnhanced({
129
+ sessionId: testSessionId,
130
+ limit: 0,
131
+ });
132
+ (0, globals_1.expect)(result.items.length).toBe(150); // All items
133
+ (0, globals_1.expect)(result.totalCount).toBe(150);
134
+ });
135
+ (0, globals_1.it)('should handle negative limit as default', () => {
136
+ const result = contextRepo.queryEnhanced({
137
+ sessionId: testSessionId,
138
+ limit: -1,
139
+ });
140
+ (0, globals_1.expect)(result.items.length).toBe(100); // Default limit
141
+ (0, globals_1.expect)(result.totalCount).toBe(150);
142
+ });
143
+ });
144
+ (0, globals_1.describe)('Token Limit Safety', () => {
145
+ (0, globals_1.beforeEach)(() => {
146
+ // Create items with varying sizes to test token safety
147
+ const largeValue = 'A'.repeat(5000); // 5KB per item
148
+ for (let i = 0; i < 50; i++) {
149
+ db.prepare(`
150
+ INSERT INTO context_items (id, session_id, key, value, size, created_at)
151
+ VALUES (?, ?, ?, ?, ?, ?)
152
+ `).run((0, uuid_1.v4)(), testSessionId, `large.item.${i}`, largeValue, 5000, new Date(Date.now() - i * 1000).toISOString());
153
+ }
154
+ });
155
+ (0, globals_1.it)('should calculate approximate token usage in response', () => {
156
+ const result = contextRepo.queryEnhanced({
157
+ sessionId: testSessionId,
158
+ includeMetadata: true,
159
+ });
160
+ // Calculate approximate token usage
161
+ let totalSize = 0;
162
+ result.items.forEach(item => {
163
+ totalSize += item.size || Buffer.byteLength(item.value, 'utf8');
164
+ });
165
+ // With metadata, response should include size information
166
+ (0, globals_1.expect)(result.items.every(item => item.size !== undefined)).toBe(true);
167
+ // Verify we don't exceed safe limits
168
+ // Rough estimate: 1 token ≈ 4 characters
169
+ const estimatedTokens = totalSize / 4;
170
+ const safeTokenLimit = 100000; // Conservative limit
171
+ (0, globals_1.expect)(estimatedTokens).toBeLessThan(safeTokenLimit);
172
+ });
173
+ (0, globals_1.it)('should provide size summary when includeMetadata is true', () => {
174
+ const result = contextRepo.queryEnhanced({
175
+ sessionId: testSessionId,
176
+ includeMetadata: true,
177
+ limit: 10,
178
+ });
179
+ // Calculate total size of returned items
180
+ const totalSize = result.items.reduce((sum, item) => sum + (item.size || 0), 0);
181
+ // Handler should include this information
182
+ const handlerResponse = {
183
+ content: [
184
+ {
185
+ type: 'text',
186
+ text: JSON.stringify({
187
+ items: result.items,
188
+ pagination: {
189
+ totalCount: result.totalCount,
190
+ returnedCount: result.items.length,
191
+ totalSize: totalSize,
192
+ averageSize: Math.round(totalSize / result.items.length),
193
+ },
194
+ }, null, 2),
195
+ },
196
+ ],
197
+ };
198
+ const parsed = JSON.parse(handlerResponse.content[0].text);
199
+ (0, globals_1.expect)(parsed.pagination.totalSize).toBe(totalSize);
200
+ (0, globals_1.expect)(parsed.pagination.averageSize).toBeGreaterThan(0);
201
+ });
202
+ (0, globals_1.it)('should warn when approaching token limits', () => {
203
+ // Create a scenario where default pagination would exceed safe limits
204
+ const hugeValue = 'B'.repeat(10000); // 10KB per item
205
+ for (let i = 0; i < 200; i++) {
206
+ db.prepare(`
207
+ INSERT INTO context_items (id, session_id, key, value, size, created_at)
208
+ VALUES (?, ?, ?, ?, ?, ?)
209
+ `).run((0, uuid_1.v4)(), testSessionId, `huge.item.${i}`, hugeValue, 10000, new Date(Date.now() - i * 1000).toISOString());
210
+ }
211
+ const result = contextRepo.queryEnhanced({
212
+ sessionId: testSessionId,
213
+ includeMetadata: true,
214
+ });
215
+ // Should still respect the 100 item limit
216
+ (0, globals_1.expect)(result.items.length).toBe(100);
217
+ // Calculate if this would be safe
218
+ const totalSize = result.items.reduce((sum, item) => sum + (item.size || 0), 0);
219
+ const estimatedTokens = totalSize / 4;
220
+ // Handler could include a warning if needed
221
+ if (estimatedTokens > 50000) {
222
+ const handlerResponse = {
223
+ content: [
224
+ {
225
+ type: 'text',
226
+ text: JSON.stringify({
227
+ items: result.items,
228
+ pagination: {
229
+ totalCount: result.totalCount,
230
+ returnedCount: result.items.length,
231
+ warning: 'Large result set. Consider using smaller limit or more specific filters.',
232
+ },
233
+ }, null, 2),
234
+ },
235
+ ],
236
+ };
237
+ const parsed = JSON.parse(handlerResponse.content[0].text);
238
+ (0, globals_1.expect)(parsed.pagination.warning).toBeTruthy();
239
+ }
240
+ });
241
+ });
242
+ (0, globals_1.describe)('Pagination Metadata in Response', () => {
243
+ (0, globals_1.beforeEach)(() => {
244
+ // Create 25 items for easier pagination testing
245
+ for (let i = 0; i < 25; i++) {
246
+ db.prepare(`
247
+ INSERT INTO context_items (id, session_id, key, value, created_at)
248
+ VALUES (?, ?, ?, ?, ?)
249
+ `).run((0, uuid_1.v4)(), testSessionId, `page.item.${i.toString().padStart(2, '0')}`, `Value ${i}`, new Date(Date.now() - i * 60000).toISOString());
250
+ }
251
+ });
252
+ (0, globals_1.it)('should include complete pagination metadata', () => {
253
+ const limit = 10;
254
+ const offset = 10;
255
+ const result = contextRepo.queryEnhanced({
256
+ sessionId: testSessionId,
257
+ limit: limit,
258
+ offset: offset,
259
+ });
260
+ // Calculate pagination metadata
261
+ const currentPage = Math.floor(offset / limit) + 1;
262
+ const totalPages = Math.ceil(result.totalCount / limit);
263
+ const hasNextPage = currentPage < totalPages;
264
+ const hasPreviousPage = currentPage > 1;
265
+ // Simulate handler response with full pagination metadata
266
+ const handlerResponse = {
267
+ content: [
268
+ {
269
+ type: 'text',
270
+ text: JSON.stringify({
271
+ items: result.items.map(item => ({
272
+ key: item.key,
273
+ value: item.value,
274
+ category: item.category,
275
+ priority: item.priority,
276
+ })),
277
+ pagination: {
278
+ totalCount: result.totalCount,
279
+ page: currentPage,
280
+ pageSize: limit,
281
+ totalPages: totalPages,
282
+ hasNextPage: hasNextPage,
283
+ hasPreviousPage: hasPreviousPage,
284
+ nextOffset: hasNextPage ? offset + limit : null,
285
+ previousOffset: hasPreviousPage ? Math.max(0, offset - limit) : null,
286
+ },
287
+ }, null, 2),
288
+ },
289
+ ],
290
+ };
291
+ const parsed = JSON.parse(handlerResponse.content[0].text);
292
+ (0, globals_1.expect)(parsed.pagination.totalCount).toBe(25);
293
+ (0, globals_1.expect)(parsed.pagination.page).toBe(2);
294
+ (0, globals_1.expect)(parsed.pagination.pageSize).toBe(10);
295
+ (0, globals_1.expect)(parsed.pagination.totalPages).toBe(3);
296
+ (0, globals_1.expect)(parsed.pagination.hasNextPage).toBe(true);
297
+ (0, globals_1.expect)(parsed.pagination.hasPreviousPage).toBe(true);
298
+ (0, globals_1.expect)(parsed.pagination.nextOffset).toBe(20);
299
+ (0, globals_1.expect)(parsed.pagination.previousOffset).toBe(0);
300
+ });
301
+ (0, globals_1.it)('should handle first page correctly', () => {
302
+ const result = contextRepo.queryEnhanced({
303
+ sessionId: testSessionId,
304
+ limit: 10,
305
+ offset: 0,
306
+ });
307
+ const handlerResponse = {
308
+ content: [
309
+ {
310
+ type: 'text',
311
+ text: JSON.stringify({
312
+ items: result.items,
313
+ pagination: {
314
+ totalCount: result.totalCount,
315
+ page: 1,
316
+ pageSize: 10,
317
+ totalPages: 3,
318
+ hasNextPage: true,
319
+ hasPreviousPage: false,
320
+ nextOffset: 10,
321
+ previousOffset: null,
322
+ },
323
+ }, null, 2),
324
+ },
325
+ ],
326
+ };
327
+ const parsed = JSON.parse(handlerResponse.content[0].text);
328
+ (0, globals_1.expect)(parsed.pagination.page).toBe(1);
329
+ (0, globals_1.expect)(parsed.pagination.hasPreviousPage).toBe(false);
330
+ (0, globals_1.expect)(parsed.pagination.hasNextPage).toBe(true);
331
+ });
332
+ (0, globals_1.it)('should handle last page correctly', () => {
333
+ const result = contextRepo.queryEnhanced({
334
+ sessionId: testSessionId,
335
+ limit: 10,
336
+ offset: 20,
337
+ });
338
+ const handlerResponse = {
339
+ content: [
340
+ {
341
+ type: 'text',
342
+ text: JSON.stringify({
343
+ items: result.items,
344
+ pagination: {
345
+ totalCount: result.totalCount,
346
+ page: 3,
347
+ pageSize: 10,
348
+ totalPages: 3,
349
+ hasNextPage: false,
350
+ hasPreviousPage: true,
351
+ nextOffset: null,
352
+ previousOffset: 10,
353
+ },
354
+ }, null, 2),
355
+ },
356
+ ],
357
+ };
358
+ const parsed = JSON.parse(handlerResponse.content[0].text);
359
+ (0, globals_1.expect)(parsed.pagination.page).toBe(3);
360
+ (0, globals_1.expect)(parsed.pagination.hasNextPage).toBe(false);
361
+ (0, globals_1.expect)(parsed.pagination.hasPreviousPage).toBe(true);
362
+ (0, globals_1.expect)(parsed.items.length).toBe(5); // Only 5 items on last page
363
+ });
364
+ (0, globals_1.it)('should handle single page results', () => {
365
+ const result = contextRepo.queryEnhanced({
366
+ sessionId: testSessionId,
367
+ limit: 50, // More than total items
368
+ });
369
+ const handlerResponse = {
370
+ content: [
371
+ {
372
+ type: 'text',
373
+ text: JSON.stringify({
374
+ items: result.items,
375
+ pagination: {
376
+ totalCount: result.totalCount,
377
+ page: 1,
378
+ pageSize: 50,
379
+ totalPages: 1,
380
+ hasNextPage: false,
381
+ hasPreviousPage: false,
382
+ nextOffset: null,
383
+ previousOffset: null,
384
+ },
385
+ }, null, 2),
386
+ },
387
+ ],
388
+ };
389
+ const parsed = JSON.parse(handlerResponse.content[0].text);
390
+ (0, globals_1.expect)(parsed.pagination.totalPages).toBe(1);
391
+ (0, globals_1.expect)(parsed.pagination.hasNextPage).toBe(false);
392
+ (0, globals_1.expect)(parsed.pagination.hasPreviousPage).toBe(false);
393
+ });
394
+ });
395
+ (0, globals_1.describe)('Default Behavior with Filters', () => {
396
+ (0, globals_1.beforeEach)(() => {
397
+ const categories = ['task', 'decision', 'progress', 'note'];
398
+ const priorities = ['high', 'normal', 'low'];
399
+ const channels = ['main', 'feature', 'bugfix'];
400
+ for (let i = 0; i < 120; i++) {
401
+ db.prepare(`
402
+ INSERT INTO context_items (
403
+ id, session_id, key, value, category, priority, channel, created_at
404
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
405
+ `).run((0, uuid_1.v4)(), testSessionId, `filtered.item.${i}`, `Filtered value ${i}`, categories[i % categories.length], priorities[i % priorities.length], channels[i % channels.length], new Date(Date.now() - i * 60000).toISOString());
406
+ }
407
+ });
408
+ (0, globals_1.it)('should apply defaults with category filter', () => {
409
+ const result = contextRepo.queryEnhanced({
410
+ sessionId: testSessionId,
411
+ category: 'task',
412
+ });
413
+ // Should still apply default limit
414
+ (0, globals_1.expect)(result.items.length).toBeLessThanOrEqual(100);
415
+ (0, globals_1.expect)(result.items.every(item => item.category === 'task')).toBe(true);
416
+ // Should still apply default sort
417
+ for (let i = 1; i < result.items.length; i++) {
418
+ const prevDate = new Date(result.items[i - 1].created_at);
419
+ const currDate = new Date(result.items[i].created_at);
420
+ (0, globals_1.expect)(prevDate.getTime()).toBeGreaterThanOrEqual(currDate.getTime());
421
+ }
422
+ });
423
+ (0, globals_1.it)('should apply defaults with multiple filters', () => {
424
+ const result = contextRepo.queryEnhanced({
425
+ sessionId: testSessionId,
426
+ category: 'task', // Only single category is supported
427
+ priorities: ['high'],
428
+ channels: ['main', 'feature'],
429
+ });
430
+ // Should apply all filters
431
+ (0, globals_1.expect)(result.items.every(item => item.category === 'task' &&
432
+ item.priority === 'high' &&
433
+ (item.channel === 'main' || item.channel === 'feature'))).toBe(true);
434
+ // Should still apply default limit (even if fewer items match)
435
+ (0, globals_1.expect)(result.items.length).toBeLessThanOrEqual(100);
436
+ // Should still apply default sort
437
+ for (let i = 1; i < result.items.length; i++) {
438
+ const prevDate = new Date(result.items[i - 1].created_at);
439
+ const currDate = new Date(result.items[i].created_at);
440
+ (0, globals_1.expect)(prevDate.getTime()).toBeGreaterThanOrEqual(currDate.getTime());
441
+ }
442
+ });
443
+ (0, globals_1.it)('should apply defaults with search pattern', () => {
444
+ const result = contextRepo.queryEnhanced({
445
+ sessionId: testSessionId,
446
+ keyPattern: 'filtered.item.1*',
447
+ });
448
+ // Should match items like filtered.item.1, filtered.item.10-19, filtered.item.100-119
449
+ (0, globals_1.expect)(result.items.every(item => item.key.match(/^filtered\.item\.1/))).toBe(true);
450
+ // Should still apply defaults
451
+ (0, globals_1.expect)(result.items.length).toBeLessThanOrEqual(100);
452
+ });
453
+ });
454
+ (0, globals_1.describe)('Handler Response Format with Defaults', () => {
455
+ (0, globals_1.beforeEach)(() => {
456
+ for (let i = 0; i < 10; i++) {
457
+ db.prepare(`
458
+ INSERT INTO context_items (id, session_id, key, value, created_at)
459
+ VALUES (?, ?, ?, ?, ?)
460
+ `).run((0, uuid_1.v4)(), testSessionId, `response.item.${i}`, `Response value ${i}`, new Date(Date.now() - i * 60000).toISOString());
461
+ }
462
+ });
463
+ (0, globals_1.it)('should indicate when defaults were applied', () => {
464
+ const result = contextRepo.queryEnhanced({
465
+ sessionId: testSessionId,
466
+ });
467
+ // Handler could include information about applied defaults
468
+ const handlerResponse = {
469
+ content: [
470
+ {
471
+ type: 'text',
472
+ text: JSON.stringify({
473
+ items: result.items.map(item => ({
474
+ key: item.key,
475
+ value: item.value,
476
+ category: item.category,
477
+ priority: item.priority,
478
+ })),
479
+ pagination: {
480
+ totalCount: result.totalCount,
481
+ limit: 100,
482
+ offset: 0,
483
+ defaultsApplied: {
484
+ limit: true,
485
+ sort: true,
486
+ },
487
+ },
488
+ }, null, 2),
489
+ },
490
+ ],
491
+ };
492
+ const parsed = JSON.parse(handlerResponse.content[0].text);
493
+ (0, globals_1.expect)(parsed.pagination.defaultsApplied.limit).toBe(true);
494
+ (0, globals_1.expect)(parsed.pagination.defaultsApplied.sort).toBe(true);
495
+ });
496
+ (0, globals_1.it)('should not indicate defaults when explicit values provided', () => {
497
+ const result = contextRepo.queryEnhanced({
498
+ sessionId: testSessionId,
499
+ limit: 5,
500
+ sort: 'key_asc',
501
+ });
502
+ const handlerResponse = {
503
+ content: [
504
+ {
505
+ type: 'text',
506
+ text: JSON.stringify({
507
+ items: result.items,
508
+ pagination: {
509
+ totalCount: result.totalCount,
510
+ limit: 5,
511
+ offset: 0,
512
+ defaultsApplied: {
513
+ limit: false,
514
+ sort: false,
515
+ },
516
+ },
517
+ }, null, 2),
518
+ },
519
+ ],
520
+ };
521
+ const parsed = JSON.parse(handlerResponse.content[0].text);
522
+ (0, globals_1.expect)(parsed.pagination.defaultsApplied.limit).toBe(false);
523
+ (0, globals_1.expect)(parsed.pagination.defaultsApplied.sort).toBe(false);
524
+ });
525
+ });
526
+ (0, globals_1.describe)('Error Handling', () => {
527
+ (0, globals_1.it)('should handle invalid pagination parameters gracefully', () => {
528
+ // Test with invalid limit type
529
+ const result = contextRepo.queryEnhanced({
530
+ sessionId: testSessionId,
531
+ limit: 'invalid',
532
+ });
533
+ // Should fall back to default
534
+ (0, globals_1.expect)(result.items.length).toBeLessThanOrEqual(100);
535
+ });
536
+ (0, globals_1.it)('should handle offset beyond available items', () => {
537
+ // Create only 5 items
538
+ for (let i = 0; i < 5; i++) {
539
+ db.prepare(`
540
+ INSERT INTO context_items (id, session_id, key, value)
541
+ VALUES (?, ?, ?, ?)
542
+ `).run((0, uuid_1.v4)(), testSessionId, `limited.${i}`, `Value ${i}`);
543
+ }
544
+ const result = contextRepo.queryEnhanced({
545
+ sessionId: testSessionId,
546
+ offset: 100, // Way beyond available items
547
+ });
548
+ (0, globals_1.expect)(result.items.length).toBe(0);
549
+ (0, globals_1.expect)(result.totalCount).toBe(5);
550
+ // Handler should handle this gracefully
551
+ const handlerResponse = {
552
+ content: [
553
+ {
554
+ type: 'text',
555
+ text: JSON.stringify({
556
+ items: [],
557
+ pagination: {
558
+ totalCount: 5,
559
+ page: 11, // offset 100 / limit 10 + 1
560
+ pageSize: 10,
561
+ totalPages: 1,
562
+ hasNextPage: false,
563
+ hasPreviousPage: true,
564
+ message: 'No items found at this offset',
565
+ },
566
+ }, null, 2),
567
+ },
568
+ ],
569
+ };
570
+ const parsed = JSON.parse(handlerResponse.content[0].text);
571
+ (0, globals_1.expect)(parsed.items).toEqual([]);
572
+ (0, globals_1.expect)(parsed.pagination.message).toBeTruthy();
573
+ });
574
+ });
575
+ (0, globals_1.describe)('Performance with Default Pagination', () => {
576
+ (0, globals_1.it)('should handle large datasets efficiently with defaults', () => {
577
+ // Create 1000 items
578
+ const stmt = db.prepare(`
579
+ INSERT INTO context_items (id, session_id, key, value, created_at)
580
+ VALUES (?, ?, ?, ?, ?)
581
+ `);
582
+ for (let i = 0; i < 1000; i++) {
583
+ stmt.run((0, uuid_1.v4)(), testSessionId, `perf.item.${i.toString().padStart(4, '0')}`, `Performance test value ${i}`, new Date(Date.now() - i * 1000).toISOString());
584
+ }
585
+ const startTime = Date.now();
586
+ const result = contextRepo.queryEnhanced({
587
+ sessionId: testSessionId,
588
+ });
589
+ const endTime = Date.now();
590
+ // Should complete quickly even with 1000 items
591
+ (0, globals_1.expect)(endTime - startTime).toBeLessThan(100);
592
+ // Should return only default limit
593
+ (0, globals_1.expect)(result.items.length).toBe(100);
594
+ (0, globals_1.expect)(result.totalCount).toBe(1000);
595
+ // First 100 items should be the most recent
596
+ (0, globals_1.expect)(result.items[0].key).toBe('perf.item.0000');
597
+ (0, globals_1.expect)(result.items[99].key).toBe('perf.item.0099');
598
+ });
599
+ });
600
+ });