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.
- package/CHANGELOG.md +433 -0
- package/LICENSE +21 -0
- package/README.md +1051 -0
- package/bin/mcp-memory-keeper +52 -0
- package/dist/__tests__/helpers/database-test-helper.js +160 -0
- package/dist/__tests__/helpers/test-server.js +92 -0
- package/dist/__tests__/integration/advanced-features.test.js +614 -0
- package/dist/__tests__/integration/backward-compatibility.test.js +245 -0
- package/dist/__tests__/integration/batchOperationsE2E.test.js +396 -0
- package/dist/__tests__/integration/batchOperationsHandler.test.js +1230 -0
- package/dist/__tests__/integration/channelManagementHandler.test.js +1291 -0
- package/dist/__tests__/integration/channels.test.js +376 -0
- package/dist/__tests__/integration/checkpoint.test.js +251 -0
- package/dist/__tests__/integration/concurrent-access.test.js +190 -0
- package/dist/__tests__/integration/context-operations.test.js +243 -0
- package/dist/__tests__/integration/contextDiff.test.js +852 -0
- package/dist/__tests__/integration/contextDiffHandler.test.js +976 -0
- package/dist/__tests__/integration/contextExportHandler.test.js +510 -0
- package/dist/__tests__/integration/contextGetPaginationDefaults.test.js +298 -0
- package/dist/__tests__/integration/contextReassignChannelHandler.test.js +908 -0
- package/dist/__tests__/integration/contextRelationshipsHandler.test.js +1151 -0
- package/dist/__tests__/integration/contextSearch.test.js +938 -0
- package/dist/__tests__/integration/contextSearchHandler.test.js +552 -0
- package/dist/__tests__/integration/contextWatchActual.test.js +165 -0
- package/dist/__tests__/integration/contextWatchHandler.test.js +1500 -0
- package/dist/__tests__/integration/cross-session-sharing.test.js +302 -0
- package/dist/__tests__/integration/database-initialization.test.js +134 -0
- package/dist/__tests__/integration/enhanced-context-operations.test.js +1082 -0
- package/dist/__tests__/integration/enhancedContextGetHandler.test.js +915 -0
- package/dist/__tests__/integration/enhancedContextTimelineHandler.test.js +716 -0
- package/dist/__tests__/integration/error-cases.test.js +407 -0
- package/dist/__tests__/integration/export-import.test.js +367 -0
- package/dist/__tests__/integration/feature-flags.test.js +542 -0
- package/dist/__tests__/integration/file-operations.test.js +264 -0
- package/dist/__tests__/integration/git-integration.test.js +237 -0
- package/dist/__tests__/integration/index-tools.test.js +496 -0
- package/dist/__tests__/integration/issue11-actual-bug-demo.test.js +304 -0
- package/dist/__tests__/integration/issue11-search-filters-bug.test.js +561 -0
- package/dist/__tests__/integration/issue12-checkpoint-restore-behavior.test.js +621 -0
- package/dist/__tests__/integration/issue13-key-validation.test.js +433 -0
- package/dist/__tests__/integration/knowledge-graph.test.js +338 -0
- package/dist/__tests__/integration/migrations.test.js +528 -0
- package/dist/__tests__/integration/multi-agent.test.js +546 -0
- package/dist/__tests__/integration/pagination-critical-fix.test.js +296 -0
- package/dist/__tests__/integration/paginationDefaultsHandler.test.js +600 -0
- package/dist/__tests__/integration/project-directory.test.js +283 -0
- package/dist/__tests__/integration/resource-cleanup.test.js +149 -0
- package/dist/__tests__/integration/retention.test.js +513 -0
- package/dist/__tests__/integration/search.test.js +333 -0
- package/dist/__tests__/integration/semantic-search.test.js +266 -0
- package/dist/__tests__/integration/server-initialization.test.js +307 -0
- package/dist/__tests__/integration/session-management.test.js +219 -0
- package/dist/__tests__/integration/simplified-sharing.test.js +346 -0
- package/dist/__tests__/integration/smart-compaction.test.js +230 -0
- package/dist/__tests__/integration/summarization.test.js +308 -0
- package/dist/__tests__/integration/watcher-migration-validation.test.js +544 -0
- package/dist/__tests__/security/input-validation.test.js +115 -0
- package/dist/__tests__/utils/agents.test.js +473 -0
- package/dist/__tests__/utils/database.test.js +177 -0
- package/dist/__tests__/utils/git.test.js +122 -0
- package/dist/__tests__/utils/knowledge-graph.test.js +297 -0
- package/dist/__tests__/utils/migrationHealthCheck.test.js +302 -0
- package/dist/__tests__/utils/project-directory-messages.test.js +188 -0
- package/dist/__tests__/utils/timezone-safe-dates.js +119 -0
- package/dist/__tests__/utils/validation.test.js +200 -0
- package/dist/__tests__/utils/vector-store.test.js +231 -0
- package/dist/handlers/contextWatchHandlers.js +206 -0
- package/dist/index.js +4310 -0
- package/dist/index.phase1.backup.js +410 -0
- package/dist/index.phase2.backup.js +704 -0
- package/dist/migrations/003_add_channels.js +174 -0
- package/dist/migrations/004_add_context_watch.js +151 -0
- package/dist/migrations/005_add_context_watch.js +98 -0
- package/dist/migrations/simplify-sharing.js +117 -0
- package/dist/repositories/BaseRepository.js +30 -0
- package/dist/repositories/CheckpointRepository.js +140 -0
- package/dist/repositories/ContextRepository.js +1873 -0
- package/dist/repositories/FileRepository.js +104 -0
- package/dist/repositories/RepositoryManager.js +62 -0
- package/dist/repositories/SessionRepository.js +66 -0
- package/dist/repositories/WatcherRepository.js +252 -0
- package/dist/repositories/index.js +15 -0
- package/dist/server.js +384 -0
- package/dist/test-helpers/database-helper.js +128 -0
- package/dist/types/entities.js +3 -0
- package/dist/utils/agents.js +791 -0
- package/dist/utils/channels.js +150 -0
- package/dist/utils/database.js +731 -0
- package/dist/utils/feature-flags.js +476 -0
- package/dist/utils/git.js +145 -0
- package/dist/utils/knowledge-graph.js +264 -0
- package/dist/utils/migrationHealthCheck.js +373 -0
- package/dist/utils/migrations.js +452 -0
- package/dist/utils/retention.js +460 -0
- package/dist/utils/timestamps.js +112 -0
- package/dist/utils/validation.js +296 -0
- package/dist/utils/vector-store.js +247 -0
- 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
|
+
});
|