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,452 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MigrationManager = void 0;
|
|
4
|
+
const uuid_1 = require("uuid");
|
|
5
|
+
class MigrationManager {
|
|
6
|
+
db;
|
|
7
|
+
constructor(db) {
|
|
8
|
+
this.db = db;
|
|
9
|
+
this.initializeMigrationTables();
|
|
10
|
+
}
|
|
11
|
+
initializeMigrationTables() {
|
|
12
|
+
this.db.getDatabase().exec(`
|
|
13
|
+
CREATE TABLE IF NOT EXISTS migrations (
|
|
14
|
+
id TEXT PRIMARY KEY,
|
|
15
|
+
version TEXT UNIQUE NOT NULL,
|
|
16
|
+
name TEXT NOT NULL,
|
|
17
|
+
description TEXT,
|
|
18
|
+
up_sql TEXT NOT NULL,
|
|
19
|
+
down_sql TEXT,
|
|
20
|
+
dependencies TEXT, -- JSON array
|
|
21
|
+
requires_backup BOOLEAN DEFAULT false,
|
|
22
|
+
checksum TEXT,
|
|
23
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
24
|
+
applied_at TIMESTAMP,
|
|
25
|
+
rollback_at TIMESTAMP
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
CREATE TABLE IF NOT EXISTS migration_log (
|
|
29
|
+
id TEXT PRIMARY KEY,
|
|
30
|
+
migration_id TEXT NOT NULL,
|
|
31
|
+
version TEXT NOT NULL,
|
|
32
|
+
action TEXT NOT NULL, -- 'apply', 'rollback', 'backup'
|
|
33
|
+
success BOOLEAN NOT NULL,
|
|
34
|
+
errors TEXT, -- JSON array
|
|
35
|
+
warnings TEXT, -- JSON array
|
|
36
|
+
execution_time INTEGER, -- milliseconds
|
|
37
|
+
rows_affected INTEGER,
|
|
38
|
+
backup_path TEXT,
|
|
39
|
+
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
40
|
+
FOREIGN KEY (migration_id) REFERENCES migrations(id)
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
CREATE INDEX IF NOT EXISTS idx_migrations_version ON migrations(version);
|
|
44
|
+
CREATE INDEX IF NOT EXISTS idx_migrations_applied ON migrations(applied_at);
|
|
45
|
+
CREATE INDEX IF NOT EXISTS idx_migration_log_version ON migration_log(version);
|
|
46
|
+
CREATE INDEX IF NOT EXISTS idx_migration_log_timestamp ON migration_log(timestamp);
|
|
47
|
+
`);
|
|
48
|
+
}
|
|
49
|
+
createMigration(migration) {
|
|
50
|
+
const id = (0, uuid_1.v4)();
|
|
51
|
+
const now = new Date().toISOString();
|
|
52
|
+
// Calculate checksum
|
|
53
|
+
const checksum = this.calculateChecksum(migration.up + (migration.down || ''));
|
|
54
|
+
const _migrationWithDefaults = {
|
|
55
|
+
id,
|
|
56
|
+
createdAt: now,
|
|
57
|
+
checksum,
|
|
58
|
+
requiresBackup: false,
|
|
59
|
+
...migration,
|
|
60
|
+
};
|
|
61
|
+
this.db
|
|
62
|
+
.getDatabase()
|
|
63
|
+
.prepare(`
|
|
64
|
+
INSERT INTO migrations (
|
|
65
|
+
id, version, name, description, up_sql, down_sql, dependencies,
|
|
66
|
+
requires_backup, checksum, created_at
|
|
67
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
68
|
+
`)
|
|
69
|
+
.run(id, migration.version, migration.name, migration.description, migration.up, migration.down, migration.dependencies ? JSON.stringify(migration.dependencies) : null, migration.requiresBackup ? 1 : 0, checksum, now);
|
|
70
|
+
return id;
|
|
71
|
+
}
|
|
72
|
+
getMigration(version) {
|
|
73
|
+
const row = this.db
|
|
74
|
+
.getDatabase()
|
|
75
|
+
.prepare(`
|
|
76
|
+
SELECT * FROM migrations WHERE version = ?
|
|
77
|
+
`)
|
|
78
|
+
.get(version);
|
|
79
|
+
if (!row)
|
|
80
|
+
return null;
|
|
81
|
+
return this.rowToMigration(row);
|
|
82
|
+
}
|
|
83
|
+
listMigrations(options = {}) {
|
|
84
|
+
let query = 'SELECT * FROM migrations WHERE 1=1';
|
|
85
|
+
const params = [];
|
|
86
|
+
if (options.applied === true) {
|
|
87
|
+
query += ' AND applied_at IS NOT NULL';
|
|
88
|
+
}
|
|
89
|
+
else if (options.applied === false) {
|
|
90
|
+
query += ' AND applied_at IS NULL';
|
|
91
|
+
}
|
|
92
|
+
if (options.pending === true) {
|
|
93
|
+
query += ' AND applied_at IS NULL';
|
|
94
|
+
}
|
|
95
|
+
else if (options.pending === false) {
|
|
96
|
+
query += ' AND applied_at IS NOT NULL';
|
|
97
|
+
}
|
|
98
|
+
query += ' ORDER BY version';
|
|
99
|
+
if (options.limit) {
|
|
100
|
+
query += ' LIMIT ?';
|
|
101
|
+
params.push(options.limit);
|
|
102
|
+
}
|
|
103
|
+
const rows = this.db
|
|
104
|
+
.getDatabase()
|
|
105
|
+
.prepare(query)
|
|
106
|
+
.all(...params);
|
|
107
|
+
return rows.map(row => this.rowToMigration(row));
|
|
108
|
+
}
|
|
109
|
+
getStatus() {
|
|
110
|
+
const allMigrations = this.listMigrations();
|
|
111
|
+
const appliedMigrations = allMigrations.filter(m => m.appliedAt);
|
|
112
|
+
const pendingMigrations = allMigrations.filter(m => !m.appliedAt);
|
|
113
|
+
const lastMigration = appliedMigrations.sort((a, b) => (b.appliedAt || '').localeCompare(a.appliedAt || ''))[0];
|
|
114
|
+
return {
|
|
115
|
+
currentVersion: lastMigration?.version || '0.0.0',
|
|
116
|
+
totalMigrations: allMigrations.length,
|
|
117
|
+
appliedMigrations: appliedMigrations.length,
|
|
118
|
+
pendingMigrations: pendingMigrations.length,
|
|
119
|
+
pending: pendingMigrations.map(m => ({
|
|
120
|
+
version: m.version,
|
|
121
|
+
name: m.name,
|
|
122
|
+
requiresBackup: m.requiresBackup || false,
|
|
123
|
+
})),
|
|
124
|
+
applied: appliedMigrations.map(m => ({
|
|
125
|
+
version: m.version,
|
|
126
|
+
name: m.name,
|
|
127
|
+
appliedAt: m.appliedAt,
|
|
128
|
+
})),
|
|
129
|
+
lastMigration: lastMigration
|
|
130
|
+
? {
|
|
131
|
+
version: lastMigration.version,
|
|
132
|
+
name: lastMigration.name,
|
|
133
|
+
appliedAt: lastMigration.appliedAt,
|
|
134
|
+
}
|
|
135
|
+
: undefined,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
async applyMigration(version, options = {}) {
|
|
139
|
+
const startTime = Date.now();
|
|
140
|
+
const migration = this.getMigration(version);
|
|
141
|
+
if (!migration) {
|
|
142
|
+
throw new Error(`Migration not found: ${version}`);
|
|
143
|
+
}
|
|
144
|
+
if (migration.appliedAt) {
|
|
145
|
+
throw new Error(`Migration ${version} is already applied`);
|
|
146
|
+
}
|
|
147
|
+
const result = {
|
|
148
|
+
migrationId: migration.id,
|
|
149
|
+
version: migration.version,
|
|
150
|
+
name: migration.name,
|
|
151
|
+
success: false,
|
|
152
|
+
errors: [],
|
|
153
|
+
warnings: [],
|
|
154
|
+
executionTime: 0,
|
|
155
|
+
timestamp: new Date().toISOString(),
|
|
156
|
+
};
|
|
157
|
+
try {
|
|
158
|
+
// Check dependencies
|
|
159
|
+
if (migration.dependencies) {
|
|
160
|
+
for (const depVersion of migration.dependencies) {
|
|
161
|
+
const dep = this.getMigration(depVersion);
|
|
162
|
+
if (!dep || !dep.appliedAt) {
|
|
163
|
+
result.errors.push(`Dependency not satisfied: ${depVersion}`);
|
|
164
|
+
result.success = false;
|
|
165
|
+
result.executionTime = Date.now() - startTime;
|
|
166
|
+
this.logMigration(result, 'apply', result.backupCreated);
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Validate migration SQL
|
|
172
|
+
try {
|
|
173
|
+
this.validateSQL(migration.up);
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
result.errors.push(error.message);
|
|
177
|
+
result.success = false;
|
|
178
|
+
result.executionTime = Date.now() - startTime;
|
|
179
|
+
this.logMigration(result, 'apply', result.backupCreated);
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
// Create backup if required or requested
|
|
183
|
+
let backupPath;
|
|
184
|
+
if ((migration.requiresBackup || options.createBackup) && !options.dryRun) {
|
|
185
|
+
backupPath = await this.createBackup(version);
|
|
186
|
+
result.backupCreated = backupPath;
|
|
187
|
+
}
|
|
188
|
+
if (!options.dryRun) {
|
|
189
|
+
// Execute migration
|
|
190
|
+
const db = this.db.getDatabase();
|
|
191
|
+
// Begin transaction
|
|
192
|
+
db.exec('BEGIN TRANSACTION');
|
|
193
|
+
try {
|
|
194
|
+
// Execute the migration SQL
|
|
195
|
+
db.exec(migration.up);
|
|
196
|
+
result.rowsAffected = db.changes || 0;
|
|
197
|
+
// Mark migration as applied
|
|
198
|
+
db.prepare(`
|
|
199
|
+
UPDATE migrations
|
|
200
|
+
SET applied_at = CURRENT_TIMESTAMP
|
|
201
|
+
WHERE id = ?
|
|
202
|
+
`).run(migration.id);
|
|
203
|
+
// Commit transaction
|
|
204
|
+
db.exec('COMMIT');
|
|
205
|
+
result.success = true;
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
// Rollback transaction
|
|
209
|
+
db.exec('ROLLBACK');
|
|
210
|
+
throw error;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
result.success = true;
|
|
215
|
+
result.warnings.push('Dry run - no changes applied');
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
result.errors.push(error.message);
|
|
220
|
+
result.success = false;
|
|
221
|
+
}
|
|
222
|
+
result.executionTime = Date.now() - startTime;
|
|
223
|
+
// Log the result
|
|
224
|
+
this.logMigration(result, 'apply', result.backupCreated);
|
|
225
|
+
return result;
|
|
226
|
+
}
|
|
227
|
+
async rollbackMigration(version, options = {}) {
|
|
228
|
+
const startTime = Date.now();
|
|
229
|
+
const migration = this.getMigration(version);
|
|
230
|
+
if (!migration) {
|
|
231
|
+
throw new Error(`Migration not found: ${version}`);
|
|
232
|
+
}
|
|
233
|
+
if (!migration.appliedAt) {
|
|
234
|
+
throw new Error(`Migration ${version} is not applied`);
|
|
235
|
+
}
|
|
236
|
+
if (!migration.down) {
|
|
237
|
+
throw new Error(`Migration ${version} has no rollback SQL`);
|
|
238
|
+
}
|
|
239
|
+
const result = {
|
|
240
|
+
migrationId: migration.id,
|
|
241
|
+
version: migration.version,
|
|
242
|
+
name: migration.name,
|
|
243
|
+
success: false,
|
|
244
|
+
errors: [],
|
|
245
|
+
warnings: [],
|
|
246
|
+
executionTime: 0,
|
|
247
|
+
timestamp: new Date().toISOString(),
|
|
248
|
+
};
|
|
249
|
+
try {
|
|
250
|
+
// Create backup if requested
|
|
251
|
+
let backupPath;
|
|
252
|
+
if (options.createBackup && !options.dryRun) {
|
|
253
|
+
backupPath = await this.createBackup(version);
|
|
254
|
+
result.backupCreated = backupPath;
|
|
255
|
+
}
|
|
256
|
+
// Validate rollback SQL
|
|
257
|
+
this.validateSQL(migration.down);
|
|
258
|
+
if (!options.dryRun) {
|
|
259
|
+
// Execute rollback
|
|
260
|
+
const db = this.db.getDatabase();
|
|
261
|
+
// Begin transaction
|
|
262
|
+
db.exec('BEGIN TRANSACTION');
|
|
263
|
+
try {
|
|
264
|
+
// Execute the rollback SQL
|
|
265
|
+
db.exec(migration.down);
|
|
266
|
+
result.rowsAffected = db.changes || 0;
|
|
267
|
+
// Mark migration as rolled back
|
|
268
|
+
db.prepare(`
|
|
269
|
+
UPDATE migrations
|
|
270
|
+
SET applied_at = NULL, rollback_at = CURRENT_TIMESTAMP
|
|
271
|
+
WHERE id = ?
|
|
272
|
+
`).run(migration.id);
|
|
273
|
+
// Commit transaction
|
|
274
|
+
db.exec('COMMIT');
|
|
275
|
+
result.success = true;
|
|
276
|
+
}
|
|
277
|
+
catch (error) {
|
|
278
|
+
// Rollback transaction
|
|
279
|
+
db.exec('ROLLBACK');
|
|
280
|
+
throw error;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
result.success = true;
|
|
285
|
+
result.warnings.push('Dry run - no changes applied');
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
result.errors.push(error.message);
|
|
290
|
+
result.success = false;
|
|
291
|
+
}
|
|
292
|
+
result.executionTime = Date.now() - startTime;
|
|
293
|
+
// Log the result
|
|
294
|
+
this.logMigration(result, 'rollback', result.backupCreated);
|
|
295
|
+
return result;
|
|
296
|
+
}
|
|
297
|
+
async applyAllPending(options = {}) {
|
|
298
|
+
const pendingMigrations = this.listMigrations({ pending: true });
|
|
299
|
+
const results = [];
|
|
300
|
+
for (const migration of pendingMigrations) {
|
|
301
|
+
try {
|
|
302
|
+
const result = await this.applyMigration(migration.version, {
|
|
303
|
+
dryRun: options.dryRun,
|
|
304
|
+
createBackup: options.createBackups,
|
|
305
|
+
});
|
|
306
|
+
results.push(result);
|
|
307
|
+
if (!result.success && options.stopOnError) {
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
const errorResult = {
|
|
313
|
+
migrationId: migration.id,
|
|
314
|
+
version: migration.version,
|
|
315
|
+
name: migration.name,
|
|
316
|
+
success: false,
|
|
317
|
+
errors: [error.message],
|
|
318
|
+
warnings: [],
|
|
319
|
+
executionTime: 0,
|
|
320
|
+
timestamp: new Date().toISOString(),
|
|
321
|
+
};
|
|
322
|
+
results.push(errorResult);
|
|
323
|
+
if (options.stopOnError) {
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return results;
|
|
329
|
+
}
|
|
330
|
+
getMigrationLog(version, limit = 50) {
|
|
331
|
+
const query = version
|
|
332
|
+
? 'SELECT * FROM migration_log WHERE version = ? ORDER BY timestamp DESC LIMIT ?'
|
|
333
|
+
: 'SELECT * FROM migration_log ORDER BY timestamp DESC LIMIT ?';
|
|
334
|
+
const params = version ? [version, limit] : [limit];
|
|
335
|
+
return this.db
|
|
336
|
+
.getDatabase()
|
|
337
|
+
.prepare(query)
|
|
338
|
+
.all(...params);
|
|
339
|
+
}
|
|
340
|
+
async createBackup(version) {
|
|
341
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
342
|
+
const backupPath = `context_backup_${version}_${timestamp}.db`;
|
|
343
|
+
// For SQLite, we can create a backup by copying the database file
|
|
344
|
+
// In a more complex implementation, you might use SQLite backup API
|
|
345
|
+
const fs = require('fs');
|
|
346
|
+
fs.copyFileSync(this.db.getDatabase().name, backupPath);
|
|
347
|
+
return backupPath;
|
|
348
|
+
}
|
|
349
|
+
validateSQL(sql) {
|
|
350
|
+
// Basic SQL validation
|
|
351
|
+
if (!sql || sql.trim().length === 0) {
|
|
352
|
+
throw new Error('Empty SQL statement');
|
|
353
|
+
}
|
|
354
|
+
// Check for potentially dangerous operations
|
|
355
|
+
const dangerousPatterns = [
|
|
356
|
+
/DROP\s+DATABASE/i,
|
|
357
|
+
/DELETE\s+FROM\s+\w+\s*;?\s*$/i, // DELETE without WHERE
|
|
358
|
+
/TRUNCATE/i,
|
|
359
|
+
];
|
|
360
|
+
for (const pattern of dangerousPatterns) {
|
|
361
|
+
if (pattern.test(sql)) {
|
|
362
|
+
throw new Error('Potentially dangerous SQL detected');
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
calculateChecksum(content) {
|
|
367
|
+
const crypto = require('crypto');
|
|
368
|
+
return crypto.createHash('sha256').update(content).digest('hex').substring(0, 16);
|
|
369
|
+
}
|
|
370
|
+
rowToMigration(row) {
|
|
371
|
+
return {
|
|
372
|
+
id: row.id,
|
|
373
|
+
version: row.version,
|
|
374
|
+
name: row.name,
|
|
375
|
+
description: row.description,
|
|
376
|
+
up: row.up_sql,
|
|
377
|
+
down: row.down_sql,
|
|
378
|
+
dependencies: row.dependencies ? JSON.parse(row.dependencies) : undefined,
|
|
379
|
+
requiresBackup: Boolean(row.requires_backup),
|
|
380
|
+
checksum: row.checksum,
|
|
381
|
+
createdAt: row.created_at,
|
|
382
|
+
appliedAt: row.applied_at,
|
|
383
|
+
rollbackAt: row.rollback_at,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
logMigration(result, action, backupPath) {
|
|
387
|
+
this.db
|
|
388
|
+
.getDatabase()
|
|
389
|
+
.prepare(`
|
|
390
|
+
INSERT INTO migration_log (
|
|
391
|
+
id, migration_id, version, action, success, errors, warnings,
|
|
392
|
+
execution_time, rows_affected, backup_path
|
|
393
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
394
|
+
`)
|
|
395
|
+
.run((0, uuid_1.v4)(), result.migrationId, result.version, action, result.success ? 1 : 0, result.errors.length > 0 ? JSON.stringify(result.errors) : null, result.warnings.length > 0 ? JSON.stringify(result.warnings) : null, result.executionTime, result.rowsAffected, backupPath);
|
|
396
|
+
}
|
|
397
|
+
// Predefined migrations for common schema updates
|
|
398
|
+
static getDefaultMigrations() {
|
|
399
|
+
return [
|
|
400
|
+
{
|
|
401
|
+
version: '1.0.0',
|
|
402
|
+
name: 'Add size column to context_items',
|
|
403
|
+
description: 'Add size tracking for context items',
|
|
404
|
+
up: `
|
|
405
|
+
ALTER TABLE context_items ADD COLUMN size INTEGER DEFAULT 0;
|
|
406
|
+
UPDATE context_items SET size = LENGTH(value) WHERE size = 0;
|
|
407
|
+
`,
|
|
408
|
+
down: `
|
|
409
|
+
ALTER TABLE context_items DROP COLUMN size;
|
|
410
|
+
`,
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
version: '1.1.0',
|
|
414
|
+
name: 'Create compressed_context table',
|
|
415
|
+
description: 'Support for context compression',
|
|
416
|
+
up: `
|
|
417
|
+
CREATE TABLE IF NOT EXISTS compressed_context (
|
|
418
|
+
id TEXT PRIMARY KEY,
|
|
419
|
+
session_id TEXT NOT NULL,
|
|
420
|
+
original_count INTEGER NOT NULL,
|
|
421
|
+
compressed_data TEXT NOT NULL,
|
|
422
|
+
compression_ratio REAL NOT NULL,
|
|
423
|
+
date_range_start TIMESTAMP NOT NULL,
|
|
424
|
+
date_range_end TIMESTAMP NOT NULL,
|
|
425
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
426
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id)
|
|
427
|
+
);
|
|
428
|
+
CREATE INDEX IF NOT EXISTS idx_compressed_session ON compressed_context(session_id);
|
|
429
|
+
`,
|
|
430
|
+
down: `
|
|
431
|
+
DROP TABLE IF EXISTS compressed_context;
|
|
432
|
+
`,
|
|
433
|
+
dependencies: ['1.0.0'],
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
version: '1.2.0',
|
|
437
|
+
name: 'Add metadata support',
|
|
438
|
+
description: 'Add metadata column for extensibility',
|
|
439
|
+
up: `
|
|
440
|
+
ALTER TABLE context_items ADD COLUMN metadata TEXT;
|
|
441
|
+
ALTER TABLE sessions ADD COLUMN metadata TEXT;
|
|
442
|
+
`,
|
|
443
|
+
down: `
|
|
444
|
+
ALTER TABLE context_items DROP COLUMN metadata;
|
|
445
|
+
ALTER TABLE sessions DROP COLUMN metadata;
|
|
446
|
+
`,
|
|
447
|
+
dependencies: ['1.1.0'],
|
|
448
|
+
},
|
|
449
|
+
];
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
exports.MigrationManager = MigrationManager;
|