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,190 @@
|
|
|
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 fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const os = __importStar(require("os"));
|
|
40
|
+
const database_helper_js_1 = require("../../test-helpers/database-helper.js");
|
|
41
|
+
(0, globals_1.describe)('Concurrent Access Tests', () => {
|
|
42
|
+
let tempDir;
|
|
43
|
+
let tempDbPath;
|
|
44
|
+
let dbManager;
|
|
45
|
+
(0, globals_1.beforeEach)(() => {
|
|
46
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mcp-test-'));
|
|
47
|
+
tempDbPath = path.join(tempDir, 'test.db');
|
|
48
|
+
dbManager = database_helper_js_1.TestDatabaseHelper.createTestDatabase();
|
|
49
|
+
});
|
|
50
|
+
(0, globals_1.afterEach)(async () => {
|
|
51
|
+
// Clean up all tracked databases
|
|
52
|
+
await database_helper_js_1.TestDatabaseHelper.cleanupAll();
|
|
53
|
+
// Clean up temp directory
|
|
54
|
+
try {
|
|
55
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
56
|
+
}
|
|
57
|
+
catch (_error) {
|
|
58
|
+
console.warn('Error cleaning up temp directory:', _error);
|
|
59
|
+
}
|
|
60
|
+
// Clean up any WAL/SHM files
|
|
61
|
+
database_helper_js_1.TestDatabaseHelper.cleanupDbFiles(tempDbPath);
|
|
62
|
+
});
|
|
63
|
+
(0, globals_1.it)('should handle concurrent session writes without corruption', async () => {
|
|
64
|
+
const db = dbManager.getDatabase();
|
|
65
|
+
const sessionId = 'test-session';
|
|
66
|
+
// Create session
|
|
67
|
+
db.prepare(`
|
|
68
|
+
INSERT INTO sessions (id, name, description, branch, working_directory)
|
|
69
|
+
VALUES (?, ?, ?, ?, ?)
|
|
70
|
+
`).run(sessionId, 'Test Session', 'Test', null, null);
|
|
71
|
+
// Simulate concurrent writes to same session
|
|
72
|
+
const promises = [];
|
|
73
|
+
for (let i = 0; i < 10; i++) {
|
|
74
|
+
promises.push(new Promise(resolve => {
|
|
75
|
+
setTimeout(() => {
|
|
76
|
+
try {
|
|
77
|
+
db.prepare(`
|
|
78
|
+
INSERT OR REPLACE INTO context_items (id, session_id, key, value, category, priority)
|
|
79
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
80
|
+
`).run(`item-${i}`, sessionId, `key-${i}`, `value-${i}`, 'test', 'normal');
|
|
81
|
+
resolve();
|
|
82
|
+
}
|
|
83
|
+
catch (_error) {
|
|
84
|
+
resolve(); // Still resolve to continue test
|
|
85
|
+
}
|
|
86
|
+
}, Math.random() * 10);
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
await Promise.all(promises);
|
|
90
|
+
// Verify all items were written
|
|
91
|
+
const items = db
|
|
92
|
+
.prepare('SELECT COUNT(*) as count FROM context_items WHERE session_id = ?')
|
|
93
|
+
.get(sessionId);
|
|
94
|
+
(0, globals_1.expect)(items.count).toBe(10);
|
|
95
|
+
});
|
|
96
|
+
(0, globals_1.it)('should handle concurrent updates to same key', async () => {
|
|
97
|
+
const db = dbManager.getDatabase();
|
|
98
|
+
const sessionId = 'test-session';
|
|
99
|
+
const key = 'shared-key';
|
|
100
|
+
// Create session
|
|
101
|
+
db.prepare(`
|
|
102
|
+
INSERT INTO sessions (id, name, description, branch, working_directory)
|
|
103
|
+
VALUES (?, ?, ?, ?, ?)
|
|
104
|
+
`).run(sessionId, 'Test Session', 'Test', null, null);
|
|
105
|
+
// Initial value
|
|
106
|
+
db.prepare(`
|
|
107
|
+
INSERT INTO context_items (id, session_id, key, value, category, priority)
|
|
108
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
109
|
+
`).run('initial', sessionId, key, 'initial-value', 'test', 'normal');
|
|
110
|
+
// Concurrent updates
|
|
111
|
+
const updates = [];
|
|
112
|
+
for (let i = 0; i < 20; i++) {
|
|
113
|
+
updates.push(new Promise(resolve => {
|
|
114
|
+
setTimeout(() => {
|
|
115
|
+
try {
|
|
116
|
+
db.prepare(`
|
|
117
|
+
UPDATE context_items
|
|
118
|
+
SET value = ?, updated_at = CURRENT_TIMESTAMP
|
|
119
|
+
WHERE session_id = ? AND key = ?
|
|
120
|
+
`).run(`value-${i}`, sessionId, key);
|
|
121
|
+
resolve();
|
|
122
|
+
}
|
|
123
|
+
catch (_error) {
|
|
124
|
+
resolve();
|
|
125
|
+
}
|
|
126
|
+
}, Math.random() * 10);
|
|
127
|
+
}));
|
|
128
|
+
}
|
|
129
|
+
await Promise.all(updates);
|
|
130
|
+
// Should have exactly one item with the key
|
|
131
|
+
const items = db
|
|
132
|
+
.prepare('SELECT COUNT(*) as count FROM context_items WHERE session_id = ? AND key = ?')
|
|
133
|
+
.get(sessionId, key);
|
|
134
|
+
(0, globals_1.expect)(items.count).toBe(1);
|
|
135
|
+
});
|
|
136
|
+
(0, globals_1.it)('should handle database locks gracefully', () => {
|
|
137
|
+
const db = dbManager.getDatabase();
|
|
138
|
+
// In WAL mode, readers don't block writers and vice versa
|
|
139
|
+
// So we need to test a different scenario - two writers
|
|
140
|
+
// Start a write transaction
|
|
141
|
+
db.prepare('BEGIN IMMEDIATE').run();
|
|
142
|
+
// Try to start another write transaction (should fail)
|
|
143
|
+
let errorThrown = false;
|
|
144
|
+
try {
|
|
145
|
+
// This should fail because we already have a write transaction
|
|
146
|
+
db.prepare('BEGIN IMMEDIATE').run();
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
errorThrown = true;
|
|
150
|
+
// The exact error code might vary
|
|
151
|
+
(0, globals_1.expect)(error.message).toMatch(/database is locked|cannot start a transaction/i);
|
|
152
|
+
}
|
|
153
|
+
db.prepare('ROLLBACK').run();
|
|
154
|
+
// If no error was thrown, it might be because SQLite is configured differently
|
|
155
|
+
// In that case, just verify we can at least do transactions
|
|
156
|
+
if (!errorThrown) {
|
|
157
|
+
// Verify basic transaction works
|
|
158
|
+
db.prepare('BEGIN').run();
|
|
159
|
+
db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run('test', 'Test');
|
|
160
|
+
db.prepare('COMMIT').run();
|
|
161
|
+
const result = db.prepare('SELECT * FROM sessions WHERE id = ?').get('test');
|
|
162
|
+
(0, globals_1.expect)(result.name).toBe('Test');
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
(0, globals_1.it)('should handle WAL mode concurrent reads during write', () => {
|
|
166
|
+
const db = dbManager.getDatabase();
|
|
167
|
+
// Verify WAL mode is enabled
|
|
168
|
+
const journalMode = db.pragma('journal_mode');
|
|
169
|
+
(0, globals_1.expect)(journalMode[0].journal_mode).toBe('wal');
|
|
170
|
+
// Create a session
|
|
171
|
+
const sessionId = 'wal-test';
|
|
172
|
+
db.prepare(`
|
|
173
|
+
INSERT INTO sessions (id, name, description, branch, working_directory)
|
|
174
|
+
VALUES (?, ?, ?, ?, ?)
|
|
175
|
+
`).run(sessionId, 'WAL Test', 'Test', null, null);
|
|
176
|
+
// Start a write transaction
|
|
177
|
+
const writeStmt = db.prepare('BEGIN IMMEDIATE');
|
|
178
|
+
writeStmt.run();
|
|
179
|
+
// Insert item in transaction
|
|
180
|
+
db.prepare(`
|
|
181
|
+
INSERT INTO context_items (id, session_id, key, value, category, priority)
|
|
182
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
183
|
+
`).run('item-1', sessionId, 'key-1', 'value-1', 'test', 'normal');
|
|
184
|
+
// Read should work even with ongoing write transaction (WAL mode benefit)
|
|
185
|
+
const readResult = db.prepare('SELECT COUNT(*) as count FROM sessions').get();
|
|
186
|
+
(0, globals_1.expect)(readResult.count).toBeGreaterThan(0);
|
|
187
|
+
// Commit the transaction
|
|
188
|
+
db.prepare('COMMIT').run();
|
|
189
|
+
});
|
|
190
|
+
});
|
|
@@ -0,0 +1,243 @@
|
|
|
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 database_1 = require("../../utils/database");
|
|
37
|
+
const os = __importStar(require("os"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const uuid_1 = require("uuid");
|
|
41
|
+
describe('Context Operations Integration Tests', () => {
|
|
42
|
+
let dbManager;
|
|
43
|
+
let tempDbPath;
|
|
44
|
+
let db;
|
|
45
|
+
let testSessionId;
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
tempDbPath = path.join(os.tmpdir(), `test-context-${Date.now()}.db`);
|
|
48
|
+
dbManager = new database_1.DatabaseManager({
|
|
49
|
+
filename: tempDbPath,
|
|
50
|
+
maxSize: 10 * 1024 * 1024,
|
|
51
|
+
walMode: true,
|
|
52
|
+
});
|
|
53
|
+
db = dbManager.getDatabase();
|
|
54
|
+
// Create test session
|
|
55
|
+
testSessionId = (0, uuid_1.v4)();
|
|
56
|
+
db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(testSessionId, 'Test Session');
|
|
57
|
+
});
|
|
58
|
+
afterEach(() => {
|
|
59
|
+
dbManager.close();
|
|
60
|
+
try {
|
|
61
|
+
fs.unlinkSync(tempDbPath);
|
|
62
|
+
fs.unlinkSync(`${tempDbPath}-wal`);
|
|
63
|
+
fs.unlinkSync(`${tempDbPath}-shm`);
|
|
64
|
+
}
|
|
65
|
+
catch (_e) {
|
|
66
|
+
// Ignore
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
describe('context_save', () => {
|
|
70
|
+
it('should save a basic context item', () => {
|
|
71
|
+
const result = db
|
|
72
|
+
.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)')
|
|
73
|
+
.run((0, uuid_1.v4)(), testSessionId, 'test_key', 'test_value');
|
|
74
|
+
expect(result.changes).toBe(1);
|
|
75
|
+
const saved = db
|
|
76
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ? AND key = ?')
|
|
77
|
+
.get(testSessionId, 'test_key');
|
|
78
|
+
expect(saved).toBeDefined();
|
|
79
|
+
expect(saved.key).toBe('test_key');
|
|
80
|
+
expect(saved.value).toBe('test_value');
|
|
81
|
+
});
|
|
82
|
+
it('should save context with category and priority', () => {
|
|
83
|
+
const id = (0, uuid_1.v4)();
|
|
84
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value, category, priority) VALUES (?, ?, ?, ?, ?, ?)').run(id, testSessionId, 'task_item', 'Fix bug in auth', 'task', 'high');
|
|
85
|
+
const saved = db.prepare('SELECT * FROM context_items WHERE id = ?').get(id);
|
|
86
|
+
expect(saved.category).toBe('task');
|
|
87
|
+
expect(saved.priority).toBe('high');
|
|
88
|
+
});
|
|
89
|
+
it('should update existing key in same session', () => {
|
|
90
|
+
// Insert first
|
|
91
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, 'update_key', 'original_value');
|
|
92
|
+
// Update using INSERT OR REPLACE
|
|
93
|
+
db.prepare('INSERT OR REPLACE INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, 'update_key', 'updated_value');
|
|
94
|
+
const items = db
|
|
95
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ? AND key = ?')
|
|
96
|
+
.all(testSessionId, 'update_key');
|
|
97
|
+
expect(items).toHaveLength(1);
|
|
98
|
+
expect(items[0].value).toBe('updated_value');
|
|
99
|
+
});
|
|
100
|
+
it('should handle large values', () => {
|
|
101
|
+
const largeValue = 'x'.repeat(10000); // 10KB of data
|
|
102
|
+
const result = db
|
|
103
|
+
.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)')
|
|
104
|
+
.run((0, uuid_1.v4)(), testSessionId, 'large_key', largeValue);
|
|
105
|
+
expect(result.changes).toBe(1);
|
|
106
|
+
const saved = db
|
|
107
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ? AND key = ?')
|
|
108
|
+
.get(testSessionId, 'large_key');
|
|
109
|
+
expect(saved.value).toBe(largeValue);
|
|
110
|
+
});
|
|
111
|
+
it('should handle special characters in values', () => {
|
|
112
|
+
const specialValue = `Line 1\nLine 2\t"quoted"\n'single'\n\`backtick\``;
|
|
113
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, 'special_key', specialValue);
|
|
114
|
+
const saved = db
|
|
115
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ? AND key = ?')
|
|
116
|
+
.get(testSessionId, 'special_key');
|
|
117
|
+
expect(saved.value).toBe(specialValue);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
describe('context_get', () => {
|
|
121
|
+
beforeEach(() => {
|
|
122
|
+
// Add test data
|
|
123
|
+
const items = [
|
|
124
|
+
{ key: 'item1', value: 'value1', category: 'task', priority: 'high' },
|
|
125
|
+
{ key: 'item2', value: 'value2', category: 'task', priority: 'normal' },
|
|
126
|
+
{ key: 'item3', value: 'value3', category: 'decision', priority: 'high' },
|
|
127
|
+
{ key: 'item4', value: 'value4', category: 'note', priority: 'low' },
|
|
128
|
+
];
|
|
129
|
+
items.forEach(item => {
|
|
130
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value, category, priority) VALUES (?, ?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, item.key, item.value, item.category, item.priority);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
it('should get item by key', () => {
|
|
134
|
+
const item = db
|
|
135
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ? AND key = ?')
|
|
136
|
+
.get(testSessionId, 'item1');
|
|
137
|
+
expect(item).toBeDefined();
|
|
138
|
+
expect(item.value).toBe('value1');
|
|
139
|
+
expect(item.category).toBe('task');
|
|
140
|
+
});
|
|
141
|
+
it('should get all items for session', () => {
|
|
142
|
+
const items = db
|
|
143
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ? ORDER BY key')
|
|
144
|
+
.all(testSessionId);
|
|
145
|
+
expect(items).toHaveLength(4);
|
|
146
|
+
expect(items[0].key).toBe('item1');
|
|
147
|
+
expect(items[3].key).toBe('item4');
|
|
148
|
+
});
|
|
149
|
+
it('should filter by category', () => {
|
|
150
|
+
const tasks = db
|
|
151
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ? AND category = ?')
|
|
152
|
+
.all(testSessionId, 'task');
|
|
153
|
+
expect(tasks).toHaveLength(2);
|
|
154
|
+
expect(tasks.every((t) => t.category === 'task')).toBe(true);
|
|
155
|
+
});
|
|
156
|
+
it('should filter by priority', () => {
|
|
157
|
+
const highPriority = db
|
|
158
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ? AND priority = ?')
|
|
159
|
+
.all(testSessionId, 'high');
|
|
160
|
+
expect(highPriority).toHaveLength(2);
|
|
161
|
+
expect(highPriority.every((t) => t.priority === 'high')).toBe(true);
|
|
162
|
+
});
|
|
163
|
+
it('should filter by category and priority', () => {
|
|
164
|
+
const highTasks = db
|
|
165
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ? AND category = ? AND priority = ?')
|
|
166
|
+
.all(testSessionId, 'task', 'high');
|
|
167
|
+
expect(highTasks).toHaveLength(1);
|
|
168
|
+
expect(highTasks[0].key).toBe('item1');
|
|
169
|
+
});
|
|
170
|
+
it('should return empty array when no matches', () => {
|
|
171
|
+
const items = db
|
|
172
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ? AND key = ?')
|
|
173
|
+
.all(testSessionId, 'nonexistent');
|
|
174
|
+
expect(items).toHaveLength(0);
|
|
175
|
+
});
|
|
176
|
+
it('should get items from specific session', () => {
|
|
177
|
+
// Create another session
|
|
178
|
+
const otherSessionId = (0, uuid_1.v4)();
|
|
179
|
+
db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(otherSessionId, 'Other Session');
|
|
180
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)').run((0, uuid_1.v4)(), otherSessionId, 'other_item', 'other_value');
|
|
181
|
+
// Get from specific session
|
|
182
|
+
const items = db
|
|
183
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ?')
|
|
184
|
+
.all(otherSessionId);
|
|
185
|
+
expect(items).toHaveLength(1);
|
|
186
|
+
expect(items[0].key).toBe('other_item');
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
describe('context_status', () => {
|
|
190
|
+
it('should show status with no items', () => {
|
|
191
|
+
const session = db.prepare('SELECT * FROM sessions WHERE id = ?').get(testSessionId);
|
|
192
|
+
const itemCount = db
|
|
193
|
+
.prepare('SELECT COUNT(*) as count FROM context_items WHERE session_id = ?')
|
|
194
|
+
.get(testSessionId);
|
|
195
|
+
const fileCount = db
|
|
196
|
+
.prepare('SELECT COUNT(*) as count FROM file_cache WHERE session_id = ?')
|
|
197
|
+
.get(testSessionId);
|
|
198
|
+
expect(session).toBeDefined();
|
|
199
|
+
expect(itemCount.count).toBe(0);
|
|
200
|
+
expect(fileCount.count).toBe(0);
|
|
201
|
+
});
|
|
202
|
+
it('should show complete status information', () => {
|
|
203
|
+
// Add some data
|
|
204
|
+
for (let i = 0; i < 5; i++) {
|
|
205
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value, category) VALUES (?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, `key${i}`, `value${i}`, i % 2 === 0 ? 'task' : 'note');
|
|
206
|
+
}
|
|
207
|
+
for (let i = 0; i < 3; i++) {
|
|
208
|
+
db.prepare('INSERT INTO file_cache (id, session_id, file_path, content, hash) VALUES (?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, `/file${i}.txt`, `content${i}`, `hash${i}`);
|
|
209
|
+
}
|
|
210
|
+
// Get status
|
|
211
|
+
const itemCount = db
|
|
212
|
+
.prepare('SELECT COUNT(*) as count FROM context_items WHERE session_id = ?')
|
|
213
|
+
.get(testSessionId);
|
|
214
|
+
const fileCount = db
|
|
215
|
+
.prepare('SELECT COUNT(*) as count FROM file_cache WHERE session_id = ?')
|
|
216
|
+
.get(testSessionId);
|
|
217
|
+
const categoryBreakdown = db
|
|
218
|
+
.prepare(`SELECT category, COUNT(*) as count
|
|
219
|
+
FROM context_items
|
|
220
|
+
WHERE session_id = ?
|
|
221
|
+
GROUP BY category`)
|
|
222
|
+
.all(testSessionId);
|
|
223
|
+
expect(itemCount.count).toBe(5);
|
|
224
|
+
expect(fileCount.count).toBe(3);
|
|
225
|
+
expect(categoryBreakdown).toHaveLength(2);
|
|
226
|
+
expect(categoryBreakdown.find((c) => c.category === 'task')?.count).toBe(3);
|
|
227
|
+
expect(categoryBreakdown.find((c) => c.category === 'note')?.count).toBe(2);
|
|
228
|
+
});
|
|
229
|
+
it('should calculate database size', () => {
|
|
230
|
+
// Add substantial data
|
|
231
|
+
const largeContent = 'x'.repeat(1000);
|
|
232
|
+
for (let i = 0; i < 10; i++) {
|
|
233
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, `large${i}`, largeContent);
|
|
234
|
+
}
|
|
235
|
+
// Calculate approximate size
|
|
236
|
+
const items = db
|
|
237
|
+
.prepare('SELECT LENGTH(value) as size FROM context_items WHERE session_id = ?')
|
|
238
|
+
.all(testSessionId);
|
|
239
|
+
const totalSize = items.reduce((sum, item) => sum + item.size, 0);
|
|
240
|
+
expect(totalSize).toBe(10000); // 10 items × 1000 chars
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
});
|