@smallironman/mcp-memory-keeper 0.12.2-fork1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +542 -0
- package/LICENSE +21 -0
- package/README.md +1281 -0
- package/bin/mcp-memory-keeper +54 -0
- package/dist/__tests__/e2e/issue33-reproduce.test.js +234 -0
- package/dist/__tests__/e2e/server-e2e.test.js +341 -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 +1054 -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/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 +411 -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/filterBySessionId.test.js +251 -0
- package/dist/__tests__/integration/git-integration.test.js +241 -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/issue24-final-fix.test.js +241 -0
- package/dist/__tests__/integration/issue24-fix-validation.test.js +158 -0
- package/dist/__tests__/integration/issue24-reproduce.test.js +225 -0
- package/dist/__tests__/integration/issue24-token-limit.test.js +199 -0
- package/dist/__tests__/integration/issue33-array-items-schema.test.js +165 -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 +291 -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 +305 -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/tokenLimitEnforcement.test.js +134 -0
- package/dist/__tests__/integration/tool-profiles-integration.test.js +150 -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 +192 -0
- package/dist/__tests__/utils/timezone-safe-dates.js +119 -0
- package/dist/__tests__/utils/token-limits.test.js +225 -0
- package/dist/__tests__/utils/tool-profiles.test.js +374 -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 +4425 -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 +2017 -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/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 +780 -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/token-limits.js +350 -0
- package/dist/utils/tool-profiles.js +242 -0
- package/dist/utils/validation.js +296 -0
- package/dist/utils/vector-store.js +247 -0
- package/examples/config.json +31 -0
- package/examples/project-directory-setup.md +114 -0
- package/package.json +85 -0
|
@@ -0,0 +1,780 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DatabaseManager = void 0;
|
|
7
|
+
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
8
|
+
const migrationHealthCheck_1 = require("./migrationHealthCheck");
|
|
9
|
+
class DatabaseManager {
|
|
10
|
+
db;
|
|
11
|
+
config;
|
|
12
|
+
constructor(config) {
|
|
13
|
+
this.config = {
|
|
14
|
+
filename: config.filename,
|
|
15
|
+
maxSize: config.maxSize || 100 * 1024 * 1024, // 100MB default
|
|
16
|
+
walMode: config.walMode !== false, // WAL mode enabled by default
|
|
17
|
+
};
|
|
18
|
+
this.db = new better_sqlite3_1.default(this.config.filename);
|
|
19
|
+
this.initialize();
|
|
20
|
+
}
|
|
21
|
+
initialize() {
|
|
22
|
+
// Enable WAL mode for better concurrency
|
|
23
|
+
if (this.config.walMode) {
|
|
24
|
+
this.db.pragma('journal_mode = WAL');
|
|
25
|
+
}
|
|
26
|
+
// Set busy timeout to handle concurrent access
|
|
27
|
+
this.db.pragma('busy_timeout = 5000'); // 5 seconds
|
|
28
|
+
// Enable foreign keys
|
|
29
|
+
this.db.pragma('foreign_keys = ON');
|
|
30
|
+
// First, check if this is an existing database that might need migration
|
|
31
|
+
const tables = this.db
|
|
32
|
+
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'")
|
|
33
|
+
.all();
|
|
34
|
+
if (tables.length > 0) {
|
|
35
|
+
// Existing database - run health check first before creating tables
|
|
36
|
+
const healthCheck = new migrationHealthCheck_1.MigrationHealthCheck(this);
|
|
37
|
+
healthCheck.runAutoFix();
|
|
38
|
+
}
|
|
39
|
+
// Create tables (will use CREATE TABLE IF NOT EXISTS, so safe to run after migrations)
|
|
40
|
+
this.createTables();
|
|
41
|
+
// Apply watcher migrations if needed
|
|
42
|
+
this.applyWatcherMigrations();
|
|
43
|
+
// Apply FTS5 full-text search index (optional — gracefully skipped if unavailable)
|
|
44
|
+
this.applyFts5Migration();
|
|
45
|
+
// Set up maintenance triggers
|
|
46
|
+
this.setupMaintenanceTriggers();
|
|
47
|
+
}
|
|
48
|
+
createTables() {
|
|
49
|
+
this.db.exec(`
|
|
50
|
+
-- Sessions table
|
|
51
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
52
|
+
id TEXT PRIMARY KEY,
|
|
53
|
+
name TEXT,
|
|
54
|
+
description TEXT,
|
|
55
|
+
branch TEXT,
|
|
56
|
+
working_directory TEXT,
|
|
57
|
+
parent_id TEXT,
|
|
58
|
+
default_channel TEXT DEFAULT 'general',
|
|
59
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
60
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
61
|
+
FOREIGN KEY (parent_id) REFERENCES sessions(id)
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
-- Enhanced context_items table with size tracking and simplified sharing
|
|
65
|
+
CREATE TABLE IF NOT EXISTS context_items (
|
|
66
|
+
id TEXT PRIMARY KEY,
|
|
67
|
+
session_id TEXT NOT NULL,
|
|
68
|
+
key TEXT NOT NULL,
|
|
69
|
+
value TEXT NOT NULL,
|
|
70
|
+
category TEXT,
|
|
71
|
+
priority TEXT DEFAULT 'normal',
|
|
72
|
+
metadata TEXT,
|
|
73
|
+
size INTEGER DEFAULT 0,
|
|
74
|
+
is_private INTEGER DEFAULT 0,
|
|
75
|
+
channel TEXT DEFAULT 'general',
|
|
76
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
77
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
78
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE,
|
|
79
|
+
UNIQUE(session_id, key)
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
-- File cache table with size tracking
|
|
83
|
+
CREATE TABLE IF NOT EXISTS file_cache (
|
|
84
|
+
id TEXT PRIMARY KEY,
|
|
85
|
+
session_id TEXT NOT NULL,
|
|
86
|
+
file_path TEXT NOT NULL,
|
|
87
|
+
content TEXT,
|
|
88
|
+
hash TEXT,
|
|
89
|
+
size INTEGER DEFAULT 0,
|
|
90
|
+
last_read TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
91
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
92
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE,
|
|
93
|
+
UNIQUE(session_id, file_path)
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
-- Checkpoints table (Phase 2)
|
|
97
|
+
CREATE TABLE IF NOT EXISTS checkpoints (
|
|
98
|
+
id TEXT PRIMARY KEY,
|
|
99
|
+
session_id TEXT NOT NULL,
|
|
100
|
+
name TEXT NOT NULL,
|
|
101
|
+
description TEXT,
|
|
102
|
+
metadata TEXT,
|
|
103
|
+
git_status TEXT,
|
|
104
|
+
git_branch TEXT,
|
|
105
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
106
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
-- Checkpoint items table (Phase 2)
|
|
110
|
+
CREATE TABLE IF NOT EXISTS checkpoint_items (
|
|
111
|
+
id TEXT PRIMARY KEY,
|
|
112
|
+
checkpoint_id TEXT NOT NULL,
|
|
113
|
+
context_item_id TEXT NOT NULL,
|
|
114
|
+
FOREIGN KEY (checkpoint_id) REFERENCES checkpoints(id) ON DELETE CASCADE,
|
|
115
|
+
FOREIGN KEY (context_item_id) REFERENCES context_items(id) ON DELETE CASCADE
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
-- Checkpoint files table (Phase 2)
|
|
119
|
+
CREATE TABLE IF NOT EXISTS checkpoint_files (
|
|
120
|
+
id TEXT PRIMARY KEY,
|
|
121
|
+
checkpoint_id TEXT NOT NULL,
|
|
122
|
+
file_cache_id TEXT NOT NULL,
|
|
123
|
+
FOREIGN KEY (checkpoint_id) REFERENCES checkpoints(id) ON DELETE CASCADE,
|
|
124
|
+
FOREIGN KEY (file_cache_id) REFERENCES file_cache(id) ON DELETE CASCADE
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
-- Create indexes for better performance
|
|
128
|
+
CREATE INDEX IF NOT EXISTS idx_context_items_session ON context_items(session_id);
|
|
129
|
+
CREATE INDEX IF NOT EXISTS idx_context_items_category ON context_items(category);
|
|
130
|
+
CREATE INDEX IF NOT EXISTS idx_context_items_priority ON context_items(priority);
|
|
131
|
+
CREATE INDEX IF NOT EXISTS idx_context_items_private ON context_items(is_private);
|
|
132
|
+
CREATE INDEX IF NOT EXISTS idx_context_items_channel ON context_items(channel);
|
|
133
|
+
CREATE INDEX IF NOT EXISTS idx_context_items_created ON context_items(created_at);
|
|
134
|
+
CREATE INDEX IF NOT EXISTS idx_context_items_session_created ON context_items(session_id, created_at);
|
|
135
|
+
CREATE INDEX IF NOT EXISTS idx_file_cache_session ON file_cache(session_id);
|
|
136
|
+
CREATE INDEX IF NOT EXISTS idx_checkpoints_session ON checkpoints(session_id);
|
|
137
|
+
|
|
138
|
+
-- Knowledge Graph tables (Phase 4.1)
|
|
139
|
+
CREATE TABLE IF NOT EXISTS entities (
|
|
140
|
+
id TEXT PRIMARY KEY,
|
|
141
|
+
session_id TEXT NOT NULL,
|
|
142
|
+
type TEXT NOT NULL,
|
|
143
|
+
name TEXT NOT NULL,
|
|
144
|
+
attributes TEXT,
|
|
145
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
146
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
CREATE TABLE IF NOT EXISTS relations (
|
|
150
|
+
id TEXT PRIMARY KEY,
|
|
151
|
+
session_id TEXT NOT NULL,
|
|
152
|
+
subject_id TEXT NOT NULL,
|
|
153
|
+
predicate TEXT NOT NULL,
|
|
154
|
+
object_id TEXT NOT NULL,
|
|
155
|
+
confidence REAL DEFAULT 1.0,
|
|
156
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
157
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE,
|
|
158
|
+
FOREIGN KEY (subject_id) REFERENCES entities(id) ON DELETE CASCADE,
|
|
159
|
+
FOREIGN KEY (object_id) REFERENCES entities(id) ON DELETE CASCADE
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
CREATE TABLE IF NOT EXISTS observations (
|
|
163
|
+
id TEXT PRIMARY KEY,
|
|
164
|
+
entity_id TEXT NOT NULL,
|
|
165
|
+
observation TEXT NOT NULL,
|
|
166
|
+
source TEXT,
|
|
167
|
+
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
168
|
+
FOREIGN KEY (entity_id) REFERENCES entities(id) ON DELETE CASCADE
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
-- Vector Storage tables (Phase 4.2)
|
|
172
|
+
CREATE TABLE IF NOT EXISTS embeddings (
|
|
173
|
+
id TEXT PRIMARY KEY,
|
|
174
|
+
content_id TEXT NOT NULL,
|
|
175
|
+
content_type TEXT NOT NULL, -- 'context_item' or 'file_cache'
|
|
176
|
+
embedding BLOB NOT NULL,
|
|
177
|
+
metadata TEXT,
|
|
178
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
179
|
+
UNIQUE(content_id, content_type)
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
CREATE INDEX IF NOT EXISTS idx_embeddings_content ON embeddings(content_id, content_type);
|
|
183
|
+
|
|
184
|
+
-- Multi-Agent System tables (Phase 4.3)
|
|
185
|
+
CREATE TABLE IF NOT EXISTS agent_tasks (
|
|
186
|
+
id TEXT PRIMARY KEY,
|
|
187
|
+
type TEXT NOT NULL,
|
|
188
|
+
input TEXT NOT NULL,
|
|
189
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
CREATE TABLE IF NOT EXISTS agent_results (
|
|
193
|
+
id TEXT PRIMARY KEY,
|
|
194
|
+
task_id TEXT NOT NULL,
|
|
195
|
+
agent_type TEXT NOT NULL,
|
|
196
|
+
output TEXT NOT NULL,
|
|
197
|
+
confidence REAL DEFAULT 0.0,
|
|
198
|
+
reasoning TEXT,
|
|
199
|
+
processing_time INTEGER,
|
|
200
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
201
|
+
FOREIGN KEY (task_id) REFERENCES agent_tasks(id) ON DELETE CASCADE
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
CREATE INDEX IF NOT EXISTS idx_agent_results_task ON agent_results(task_id);
|
|
205
|
+
|
|
206
|
+
-- Journal table (Phase 4.4)
|
|
207
|
+
CREATE TABLE IF NOT EXISTS journal_entries (
|
|
208
|
+
id TEXT PRIMARY KEY,
|
|
209
|
+
session_id TEXT NOT NULL,
|
|
210
|
+
entry TEXT NOT NULL,
|
|
211
|
+
mood TEXT,
|
|
212
|
+
tags TEXT, -- JSON array of tags
|
|
213
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
214
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
CREATE INDEX IF NOT EXISTS idx_journal_session ON journal_entries(session_id);
|
|
218
|
+
CREATE INDEX IF NOT EXISTS idx_journal_created ON journal_entries(created_at);
|
|
219
|
+
|
|
220
|
+
-- Context Relationships table
|
|
221
|
+
CREATE TABLE IF NOT EXISTS context_relationships (
|
|
222
|
+
id TEXT PRIMARY KEY,
|
|
223
|
+
session_id TEXT NOT NULL,
|
|
224
|
+
from_key TEXT NOT NULL,
|
|
225
|
+
to_key TEXT NOT NULL,
|
|
226
|
+
relationship_type TEXT NOT NULL,
|
|
227
|
+
metadata TEXT,
|
|
228
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
229
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE,
|
|
230
|
+
UNIQUE(session_id, from_key, to_key, relationship_type)
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
CREATE INDEX IF NOT EXISTS idx_relationships_from ON context_relationships(session_id, from_key);
|
|
234
|
+
CREATE INDEX IF NOT EXISTS idx_relationships_to ON context_relationships(session_id, to_key);
|
|
235
|
+
CREATE INDEX IF NOT EXISTS idx_relationships_type ON context_relationships(relationship_type);
|
|
236
|
+
|
|
237
|
+
-- Compaction history table (Phase 4.4)
|
|
238
|
+
CREATE TABLE IF NOT EXISTS compaction_history (
|
|
239
|
+
id TEXT PRIMARY KEY,
|
|
240
|
+
session_id TEXT NOT NULL,
|
|
241
|
+
items_before INTEGER NOT NULL,
|
|
242
|
+
items_after INTEGER NOT NULL,
|
|
243
|
+
size_before INTEGER NOT NULL,
|
|
244
|
+
size_after INTEGER NOT NULL,
|
|
245
|
+
summary TEXT,
|
|
246
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
247
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
-- Retention Policies table (Phase 5.1)
|
|
251
|
+
CREATE TABLE IF NOT EXISTS retention_policies (
|
|
252
|
+
id TEXT PRIMARY KEY,
|
|
253
|
+
name TEXT NOT NULL UNIQUE,
|
|
254
|
+
description TEXT,
|
|
255
|
+
enabled BOOLEAN DEFAULT 1,
|
|
256
|
+
policy_config TEXT NOT NULL, -- JSON configuration
|
|
257
|
+
last_run TIMESTAMP,
|
|
258
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
259
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
CREATE TABLE IF NOT EXISTS retention_executions (
|
|
263
|
+
id TEXT PRIMARY KEY,
|
|
264
|
+
policy_id TEXT NOT NULL,
|
|
265
|
+
session_id TEXT,
|
|
266
|
+
dry_run BOOLEAN DEFAULT 1,
|
|
267
|
+
items_affected INTEGER DEFAULT 0,
|
|
268
|
+
size_freed INTEGER DEFAULT 0,
|
|
269
|
+
execution_log TEXT,
|
|
270
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
271
|
+
FOREIGN KEY (policy_id) REFERENCES retention_policies(id) ON DELETE CASCADE,
|
|
272
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE SET NULL
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
CREATE TABLE IF NOT EXISTS retention_logs (
|
|
276
|
+
id TEXT PRIMARY KEY,
|
|
277
|
+
policy_id TEXT NOT NULL,
|
|
278
|
+
result TEXT NOT NULL,
|
|
279
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
280
|
+
FOREIGN KEY (policy_id) REFERENCES retention_policies(id) ON DELETE CASCADE
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
-- Feature Flags table (Phase 5.2)
|
|
284
|
+
CREATE TABLE IF NOT EXISTS feature_flags (
|
|
285
|
+
id TEXT PRIMARY KEY,
|
|
286
|
+
key TEXT NOT NULL UNIQUE,
|
|
287
|
+
name TEXT NOT NULL,
|
|
288
|
+
description TEXT,
|
|
289
|
+
enabled BOOLEAN DEFAULT 0,
|
|
290
|
+
category TEXT,
|
|
291
|
+
environments TEXT, -- JSON array
|
|
292
|
+
users TEXT, -- JSON array
|
|
293
|
+
percentage INTEGER DEFAULT 0,
|
|
294
|
+
enabled_from TEXT,
|
|
295
|
+
enabled_until TEXT,
|
|
296
|
+
tags TEXT, -- JSON array
|
|
297
|
+
created_by TEXT,
|
|
298
|
+
last_modified_by TEXT,
|
|
299
|
+
config TEXT, -- JSON configuration
|
|
300
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
301
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
CREATE TABLE IF NOT EXISTS feature_flag_history (
|
|
305
|
+
id TEXT PRIMARY KEY,
|
|
306
|
+
flag_id TEXT NOT NULL,
|
|
307
|
+
user_id TEXT,
|
|
308
|
+
action TEXT NOT NULL, -- 'created', 'updated', 'evaluated'
|
|
309
|
+
details TEXT,
|
|
310
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
311
|
+
FOREIGN KEY (flag_id) REFERENCES feature_flags(id) ON DELETE CASCADE
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
-- Compressed context table for retention policies
|
|
315
|
+
CREATE TABLE IF NOT EXISTS compressed_context (
|
|
316
|
+
id TEXT PRIMARY KEY,
|
|
317
|
+
session_id TEXT NOT NULL,
|
|
318
|
+
original_count INTEGER NOT NULL,
|
|
319
|
+
compressed_data TEXT NOT NULL,
|
|
320
|
+
compression_ratio REAL NOT NULL,
|
|
321
|
+
date_range_start TIMESTAMP NOT NULL,
|
|
322
|
+
date_range_end TIMESTAMP NOT NULL,
|
|
323
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
324
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
CREATE INDEX IF NOT EXISTS idx_compressed_session ON compressed_context(session_id);
|
|
328
|
+
|
|
329
|
+
-- Database Migrations table (Phase 5.3)
|
|
330
|
+
CREATE TABLE IF NOT EXISTS migrations (
|
|
331
|
+
id TEXT PRIMARY KEY,
|
|
332
|
+
version TEXT NOT NULL UNIQUE,
|
|
333
|
+
name TEXT NOT NULL,
|
|
334
|
+
description TEXT,
|
|
335
|
+
up_sql TEXT NOT NULL,
|
|
336
|
+
down_sql TEXT,
|
|
337
|
+
dependencies TEXT, -- JSON array of version strings
|
|
338
|
+
requires_backup BOOLEAN DEFAULT 0,
|
|
339
|
+
checksum TEXT,
|
|
340
|
+
applied_at TIMESTAMP,
|
|
341
|
+
rolled_back_at TIMESTAMP,
|
|
342
|
+
rollback_at TIMESTAMP,
|
|
343
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
CREATE TABLE IF NOT EXISTS migration_log (
|
|
347
|
+
id TEXT PRIMARY KEY,
|
|
348
|
+
migration_id TEXT NOT NULL,
|
|
349
|
+
version TEXT NOT NULL,
|
|
350
|
+
action TEXT NOT NULL, -- 'apply', 'rollback', 'backup'
|
|
351
|
+
success BOOLEAN NOT NULL,
|
|
352
|
+
errors TEXT, -- JSON array
|
|
353
|
+
warnings TEXT, -- JSON array
|
|
354
|
+
error_message TEXT,
|
|
355
|
+
execution_time INTEGER, -- milliseconds
|
|
356
|
+
rows_affected INTEGER,
|
|
357
|
+
backup_path TEXT,
|
|
358
|
+
duration_ms INTEGER,
|
|
359
|
+
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
360
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
361
|
+
FOREIGN KEY (migration_id) REFERENCES migrations(id) ON DELETE CASCADE
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
CREATE INDEX IF NOT EXISTS idx_migrations_version ON migrations(version);
|
|
365
|
+
CREATE INDEX IF NOT EXISTS idx_migrations_applied ON migrations(applied_at);
|
|
366
|
+
|
|
367
|
+
-- Migration for existing databases - add new columns if they don't exist
|
|
368
|
+
${this.getMigrationSQL()}
|
|
369
|
+
`);
|
|
370
|
+
}
|
|
371
|
+
getMigrationSQL() {
|
|
372
|
+
// The shared columns are already defined in the CREATE TABLE statement above
|
|
373
|
+
// This method is kept for potential future migrations
|
|
374
|
+
return '';
|
|
375
|
+
}
|
|
376
|
+
addColumnIfNotExists(table, column, definition) {
|
|
377
|
+
// SQLite doesn't support IF NOT EXISTS for columns, so we need to check first
|
|
378
|
+
const hasColumn = this.db
|
|
379
|
+
.prepare(`
|
|
380
|
+
SELECT COUNT(*) as count FROM pragma_table_info(?) WHERE name = ?
|
|
381
|
+
`)
|
|
382
|
+
.get(table, column);
|
|
383
|
+
if (hasColumn.count === 0) {
|
|
384
|
+
return `ALTER TABLE ${table} ADD COLUMN ${column} ${definition};`;
|
|
385
|
+
}
|
|
386
|
+
return '';
|
|
387
|
+
}
|
|
388
|
+
setupMaintenanceTriggers() {
|
|
389
|
+
// Update timestamp trigger
|
|
390
|
+
this.db.exec(`
|
|
391
|
+
CREATE TRIGGER IF NOT EXISTS update_context_items_timestamp
|
|
392
|
+
AFTER UPDATE ON context_items
|
|
393
|
+
BEGIN
|
|
394
|
+
UPDATE context_items SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
|
395
|
+
END;
|
|
396
|
+
|
|
397
|
+
CREATE TRIGGER IF NOT EXISTS update_sessions_timestamp
|
|
398
|
+
AFTER UPDATE ON sessions
|
|
399
|
+
BEGIN
|
|
400
|
+
UPDATE sessions SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
|
401
|
+
END;
|
|
402
|
+
|
|
403
|
+
CREATE TRIGGER IF NOT EXISTS update_retention_policies_timestamp
|
|
404
|
+
AFTER UPDATE ON retention_policies
|
|
405
|
+
BEGIN
|
|
406
|
+
UPDATE retention_policies SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
|
407
|
+
END;
|
|
408
|
+
|
|
409
|
+
CREATE TRIGGER IF NOT EXISTS update_feature_flags_timestamp
|
|
410
|
+
AFTER UPDATE ON feature_flags
|
|
411
|
+
BEGIN
|
|
412
|
+
UPDATE feature_flags SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
|
413
|
+
END;
|
|
414
|
+
`);
|
|
415
|
+
}
|
|
416
|
+
getDatabase() {
|
|
417
|
+
return this.db;
|
|
418
|
+
}
|
|
419
|
+
close() {
|
|
420
|
+
this.db.close();
|
|
421
|
+
}
|
|
422
|
+
getDatabaseSize() {
|
|
423
|
+
const result = this.db
|
|
424
|
+
.prepare('SELECT page_count * page_size as size FROM pragma_page_count(), pragma_page_size()')
|
|
425
|
+
.get();
|
|
426
|
+
return result.size;
|
|
427
|
+
}
|
|
428
|
+
getTableSizes() {
|
|
429
|
+
const tables = this.db
|
|
430
|
+
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'")
|
|
431
|
+
.all();
|
|
432
|
+
const sizes = {};
|
|
433
|
+
for (const table of tables) {
|
|
434
|
+
const result = this.db.prepare(`SELECT COUNT(*) as count FROM ${table.name}`).get();
|
|
435
|
+
sizes[table.name] = result.count;
|
|
436
|
+
}
|
|
437
|
+
return sizes;
|
|
438
|
+
}
|
|
439
|
+
vacuum() {
|
|
440
|
+
this.db.exec('VACUUM');
|
|
441
|
+
}
|
|
442
|
+
runInTransaction(fn) {
|
|
443
|
+
const transaction = this.db.transaction(fn);
|
|
444
|
+
return transaction();
|
|
445
|
+
}
|
|
446
|
+
transaction(fn) {
|
|
447
|
+
return this.runInTransaction(fn);
|
|
448
|
+
}
|
|
449
|
+
isDatabaseFull() {
|
|
450
|
+
const currentSize = this.getDatabaseSize();
|
|
451
|
+
return currentSize >= this.config.maxSize;
|
|
452
|
+
}
|
|
453
|
+
getSessionSize(sessionId) {
|
|
454
|
+
const itemsResult = this.db
|
|
455
|
+
.prepare(`
|
|
456
|
+
SELECT COUNT(*) as count, COALESCE(SUM(size), 0) as totalSize
|
|
457
|
+
FROM context_items
|
|
458
|
+
WHERE session_id = ?
|
|
459
|
+
`)
|
|
460
|
+
.get(sessionId);
|
|
461
|
+
const filesResult = this.db
|
|
462
|
+
.prepare(`
|
|
463
|
+
SELECT COUNT(*) as count, COALESCE(SUM(size), 0) as totalSize
|
|
464
|
+
FROM file_cache
|
|
465
|
+
WHERE session_id = ?
|
|
466
|
+
`)
|
|
467
|
+
.get(sessionId);
|
|
468
|
+
return {
|
|
469
|
+
items: itemsResult?.count || 0,
|
|
470
|
+
files: filesResult?.count || 0,
|
|
471
|
+
totalSize: (itemsResult?.totalSize || 0) + (filesResult?.totalSize || 0),
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
cleanupOldSessions(daysToKeep = 30) {
|
|
475
|
+
const cutoffDate = new Date();
|
|
476
|
+
cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
|
|
477
|
+
const oldSessions = this.db
|
|
478
|
+
.prepare(`
|
|
479
|
+
SELECT id FROM sessions
|
|
480
|
+
WHERE updated_at < ?
|
|
481
|
+
ORDER BY updated_at ASC
|
|
482
|
+
`)
|
|
483
|
+
.all(cutoffDate.toISOString());
|
|
484
|
+
let deletedCount = 0;
|
|
485
|
+
for (const session of oldSessions) {
|
|
486
|
+
try {
|
|
487
|
+
this.db.prepare('DELETE FROM sessions WHERE id = ?').run(session.id);
|
|
488
|
+
deletedCount++;
|
|
489
|
+
}
|
|
490
|
+
catch (error) {
|
|
491
|
+
console.error(`Failed to delete session ${session.id}:`, error);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return deletedCount;
|
|
495
|
+
}
|
|
496
|
+
applyWatcherMigrations() {
|
|
497
|
+
try {
|
|
498
|
+
// Check if watcher tables are missing using the needsMigration logic
|
|
499
|
+
const result = this.db
|
|
500
|
+
.prepare(`
|
|
501
|
+
SELECT COUNT(*) as count FROM sqlite_master
|
|
502
|
+
WHERE type='table' AND name IN ('context_changes', 'context_watchers')
|
|
503
|
+
`)
|
|
504
|
+
.get();
|
|
505
|
+
const needsMigration = result.count < 2;
|
|
506
|
+
if (needsMigration) {
|
|
507
|
+
// console.log('Applying watcher migrations...');
|
|
508
|
+
// Apply migration 004 - context watch functionality
|
|
509
|
+
this.db.transaction(() => {
|
|
510
|
+
// Create change tracking table
|
|
511
|
+
this.db.exec(`
|
|
512
|
+
CREATE TABLE IF NOT EXISTS context_changes (
|
|
513
|
+
sequence_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
514
|
+
session_id TEXT NOT NULL,
|
|
515
|
+
item_id TEXT NOT NULL,
|
|
516
|
+
key TEXT NOT NULL,
|
|
517
|
+
operation TEXT NOT NULL CHECK (operation IN ('CREATE', 'UPDATE', 'DELETE')),
|
|
518
|
+
old_value TEXT,
|
|
519
|
+
new_value TEXT,
|
|
520
|
+
old_metadata TEXT,
|
|
521
|
+
new_metadata TEXT,
|
|
522
|
+
category TEXT,
|
|
523
|
+
priority TEXT,
|
|
524
|
+
channel TEXT,
|
|
525
|
+
size_delta INTEGER DEFAULT 0,
|
|
526
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
527
|
+
created_by TEXT,
|
|
528
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
|
|
529
|
+
);
|
|
530
|
+
`);
|
|
531
|
+
// Create watchers registry table
|
|
532
|
+
this.db.exec(`
|
|
533
|
+
CREATE TABLE IF NOT EXISTS context_watchers (
|
|
534
|
+
id TEXT PRIMARY KEY,
|
|
535
|
+
session_id TEXT,
|
|
536
|
+
filter_keys TEXT,
|
|
537
|
+
filter_categories TEXT,
|
|
538
|
+
filter_channels TEXT,
|
|
539
|
+
filter_priorities TEXT,
|
|
540
|
+
last_sequence INTEGER DEFAULT 0,
|
|
541
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
542
|
+
last_poll_at TIMESTAMP,
|
|
543
|
+
expires_at TIMESTAMP,
|
|
544
|
+
metadata TEXT,
|
|
545
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
|
|
546
|
+
);
|
|
547
|
+
`);
|
|
548
|
+
// Create indexes for performance
|
|
549
|
+
this.db.exec(`
|
|
550
|
+
CREATE INDEX IF NOT EXISTS idx_changes_sequence ON context_changes(sequence_id);
|
|
551
|
+
CREATE INDEX IF NOT EXISTS idx_changes_session_seq ON context_changes(session_id, sequence_id);
|
|
552
|
+
CREATE INDEX IF NOT EXISTS idx_changes_created ON context_changes(created_at);
|
|
553
|
+
CREATE INDEX IF NOT EXISTS idx_watchers_expires ON context_watchers(expires_at);
|
|
554
|
+
CREATE INDEX IF NOT EXISTS idx_watchers_session ON context_watchers(session_id);
|
|
555
|
+
`);
|
|
556
|
+
// Create triggers for change tracking
|
|
557
|
+
this.db.exec(`
|
|
558
|
+
-- Trigger for INSERT operations on context_items
|
|
559
|
+
CREATE TRIGGER IF NOT EXISTS track_context_insert
|
|
560
|
+
AFTER INSERT ON context_items
|
|
561
|
+
BEGIN
|
|
562
|
+
INSERT INTO context_changes (
|
|
563
|
+
session_id, item_id, key, operation,
|
|
564
|
+
new_value, new_metadata, category, priority, channel,
|
|
565
|
+
size_delta, created_by
|
|
566
|
+
) VALUES (
|
|
567
|
+
NEW.session_id, NEW.id, NEW.key, 'CREATE',
|
|
568
|
+
NEW.value, NEW.metadata, NEW.category, NEW.priority, NEW.channel,
|
|
569
|
+
NEW.size, 'context_save'
|
|
570
|
+
);
|
|
571
|
+
END;
|
|
572
|
+
|
|
573
|
+
-- Trigger for UPDATE operations on context_items
|
|
574
|
+
CREATE TRIGGER IF NOT EXISTS track_context_update
|
|
575
|
+
AFTER UPDATE ON context_items
|
|
576
|
+
WHEN OLD.value != NEW.value OR
|
|
577
|
+
IFNULL(OLD.metadata, '') != IFNULL(NEW.metadata, '') OR
|
|
578
|
+
IFNULL(OLD.category, '') != IFNULL(NEW.category, '') OR
|
|
579
|
+
IFNULL(OLD.priority, '') != IFNULL(NEW.priority, '') OR
|
|
580
|
+
IFNULL(OLD.channel, '') != IFNULL(NEW.channel, '')
|
|
581
|
+
BEGIN
|
|
582
|
+
INSERT INTO context_changes (
|
|
583
|
+
session_id, item_id, key, operation,
|
|
584
|
+
old_value, new_value, old_metadata, new_metadata,
|
|
585
|
+
category, priority, channel, size_delta, created_by
|
|
586
|
+
) VALUES (
|
|
587
|
+
NEW.session_id, NEW.id, NEW.key, 'UPDATE',
|
|
588
|
+
OLD.value, NEW.value, OLD.metadata, NEW.metadata,
|
|
589
|
+
NEW.category, NEW.priority, NEW.channel,
|
|
590
|
+
NEW.size - OLD.size, 'context_save'
|
|
591
|
+
);
|
|
592
|
+
END;
|
|
593
|
+
|
|
594
|
+
-- Trigger for DELETE operations on context_items
|
|
595
|
+
CREATE TRIGGER IF NOT EXISTS track_context_delete
|
|
596
|
+
AFTER DELETE ON context_items
|
|
597
|
+
BEGIN
|
|
598
|
+
INSERT INTO context_changes (
|
|
599
|
+
session_id, item_id, key, operation,
|
|
600
|
+
old_value, old_metadata, category, priority, channel,
|
|
601
|
+
size_delta, created_by
|
|
602
|
+
) VALUES (
|
|
603
|
+
OLD.session_id, OLD.id, OLD.key, 'DELETE',
|
|
604
|
+
OLD.value, OLD.metadata, OLD.category, OLD.priority, OLD.channel,
|
|
605
|
+
-OLD.size, 'context_delete'
|
|
606
|
+
);
|
|
607
|
+
END;
|
|
608
|
+
`);
|
|
609
|
+
})();
|
|
610
|
+
// Apply migration 005 - additional watcher functionality
|
|
611
|
+
this.db.transaction(() => {
|
|
612
|
+
// Add is_active column to context_watchers if not exists
|
|
613
|
+
const watchers_columns = this.db
|
|
614
|
+
.prepare('PRAGMA table_info(context_watchers)')
|
|
615
|
+
.all();
|
|
616
|
+
if (!watchers_columns.some((col) => col.name === 'is_active')) {
|
|
617
|
+
this.db.exec('ALTER TABLE context_watchers ADD COLUMN is_active INTEGER DEFAULT 1');
|
|
618
|
+
this.db.exec('CREATE INDEX IF NOT EXISTS idx_watchers_active ON context_watchers(is_active)');
|
|
619
|
+
}
|
|
620
|
+
// Add sequence_number column to context_items if not exists
|
|
621
|
+
const columns = this.db.prepare('PRAGMA table_info(context_items)').all();
|
|
622
|
+
if (!columns.some((col) => col.name === 'sequence_number')) {
|
|
623
|
+
this.db.exec('ALTER TABLE context_items ADD COLUMN sequence_number INTEGER DEFAULT 0');
|
|
624
|
+
// Update existing rows with sequence numbers
|
|
625
|
+
this.db.exec(`
|
|
626
|
+
UPDATE context_items
|
|
627
|
+
SET sequence_number = (
|
|
628
|
+
SELECT COUNT(*)
|
|
629
|
+
FROM context_items c2
|
|
630
|
+
WHERE c2.session_id = context_items.session_id
|
|
631
|
+
AND c2.created_at <= context_items.created_at
|
|
632
|
+
)
|
|
633
|
+
WHERE sequence_number = 0
|
|
634
|
+
`);
|
|
635
|
+
// Create trigger to auto-increment sequence numbers for new inserts
|
|
636
|
+
this.db.exec(`
|
|
637
|
+
CREATE TRIGGER IF NOT EXISTS increment_sequence_insert
|
|
638
|
+
AFTER INSERT ON context_items
|
|
639
|
+
FOR EACH ROW
|
|
640
|
+
WHEN NEW.sequence_number = 0
|
|
641
|
+
BEGIN
|
|
642
|
+
UPDATE context_items
|
|
643
|
+
SET sequence_number = (
|
|
644
|
+
SELECT COALESCE(MAX(sequence_number), 0) + 1
|
|
645
|
+
FROM context_items
|
|
646
|
+
WHERE session_id = NEW.session_id
|
|
647
|
+
)
|
|
648
|
+
WHERE id = NEW.id;
|
|
649
|
+
END
|
|
650
|
+
`);
|
|
651
|
+
// Create trigger to update sequence numbers on updates
|
|
652
|
+
this.db.exec(`
|
|
653
|
+
CREATE TRIGGER IF NOT EXISTS increment_sequence_update
|
|
654
|
+
AFTER UPDATE OF value, metadata, category, priority, channel ON context_items
|
|
655
|
+
FOR EACH ROW
|
|
656
|
+
WHEN OLD.value != NEW.value OR
|
|
657
|
+
IFNULL(OLD.metadata, '') != IFNULL(NEW.metadata, '') OR
|
|
658
|
+
IFNULL(OLD.category, '') != IFNULL(NEW.category, '') OR
|
|
659
|
+
IFNULL(OLD.priority, '') != IFNULL(NEW.priority, '') OR
|
|
660
|
+
IFNULL(OLD.channel, '') != IFNULL(NEW.channel, '')
|
|
661
|
+
BEGIN
|
|
662
|
+
UPDATE context_items
|
|
663
|
+
SET sequence_number = (
|
|
664
|
+
SELECT COALESCE(MAX(sequence_number), 0) + 1
|
|
665
|
+
FROM context_items
|
|
666
|
+
WHERE session_id = NEW.session_id
|
|
667
|
+
)
|
|
668
|
+
WHERE id = NEW.id;
|
|
669
|
+
END
|
|
670
|
+
`);
|
|
671
|
+
}
|
|
672
|
+
// Create table for tracking deleted items (needed for tests)
|
|
673
|
+
this.db.exec(`
|
|
674
|
+
CREATE TABLE IF NOT EXISTS deleted_items (
|
|
675
|
+
id TEXT PRIMARY KEY,
|
|
676
|
+
session_id TEXT NOT NULL,
|
|
677
|
+
key TEXT NOT NULL,
|
|
678
|
+
category TEXT,
|
|
679
|
+
channel TEXT,
|
|
680
|
+
sequence_number INTEGER NOT NULL,
|
|
681
|
+
deleted_at TEXT DEFAULT (datetime('now')),
|
|
682
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
|
|
683
|
+
)
|
|
684
|
+
`);
|
|
685
|
+
this.db.exec('CREATE INDEX IF NOT EXISTS idx_deleted_items_session ON deleted_items(session_id)');
|
|
686
|
+
this.db.exec('CREATE INDEX IF NOT EXISTS idx_deleted_items_key ON deleted_items(key)');
|
|
687
|
+
})();
|
|
688
|
+
// Record migrations in the migrations table for tracking
|
|
689
|
+
try {
|
|
690
|
+
// Check if migrations table exists
|
|
691
|
+
const migrationTableExists = this.db
|
|
692
|
+
.prepare(`SELECT COUNT(*) as count FROM sqlite_master WHERE type='table' AND name='migrations'`)
|
|
693
|
+
.get();
|
|
694
|
+
if (migrationTableExists.count > 0) {
|
|
695
|
+
// Record migration 004
|
|
696
|
+
const migration004Exists = this.db
|
|
697
|
+
.prepare('SELECT COUNT(*) as count FROM migrations WHERE version = ?')
|
|
698
|
+
.get('0.4.0');
|
|
699
|
+
if (migration004Exists.count === 0) {
|
|
700
|
+
this.db
|
|
701
|
+
.prepare(`INSERT INTO migrations (id, version, name, description, up_sql, applied_at)
|
|
702
|
+
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`)
|
|
703
|
+
.run('004_add_context_watch', '0.4.0', '004_add_context_watch', 'Add context watch functionality with change tracking', 'See migration file for full SQL');
|
|
704
|
+
}
|
|
705
|
+
// Record migration 005
|
|
706
|
+
const migration005Exists = this.db
|
|
707
|
+
.prepare('SELECT COUNT(*) as count FROM migrations WHERE version = ?')
|
|
708
|
+
.get('0.5.0');
|
|
709
|
+
if (migration005Exists.count === 0) {
|
|
710
|
+
this.db
|
|
711
|
+
.prepare(`INSERT INTO migrations (id, version, name, description, up_sql, applied_at)
|
|
712
|
+
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`)
|
|
713
|
+
.run('005_add_context_watch', '0.5.0', '005_add_context_watch', 'Add missing context watch functionality', 'See migration file for full SQL');
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
catch (error) {
|
|
718
|
+
console.warn('Could not record migrations in tracking table:', error);
|
|
719
|
+
}
|
|
720
|
+
// console.log('Watcher migrations applied successfully');
|
|
721
|
+
}
|
|
722
|
+
else {
|
|
723
|
+
// console.log('Watcher migrations already applied, skipping.');
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
catch (error) {
|
|
727
|
+
console.error('Failed to apply watcher migrations:', error);
|
|
728
|
+
// For production, we should fail here since watcher functionality is critical
|
|
729
|
+
throw new Error(`Critical error: Watcher migrations failed - ${error}`);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
applyFts5Migration() {
|
|
733
|
+
try {
|
|
734
|
+
const exists = this.db
|
|
735
|
+
.prepare("SELECT COUNT(*) as count FROM sqlite_master WHERE type='table' AND name='context_items_fts'")
|
|
736
|
+
.get();
|
|
737
|
+
if (exists.count > 0)
|
|
738
|
+
return;
|
|
739
|
+
this.db.transaction(() => {
|
|
740
|
+
// External-content FTS5 table backed by context_items; trigram tokenizer
|
|
741
|
+
// enables substring matching for both ASCII and CJK (≥3-char terms).
|
|
742
|
+
this.db.exec(`
|
|
743
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS context_items_fts USING fts5(
|
|
744
|
+
key, value,
|
|
745
|
+
content='context_items',
|
|
746
|
+
content_rowid='rowid',
|
|
747
|
+
tokenize='trigram case_sensitive 0'
|
|
748
|
+
);
|
|
749
|
+
|
|
750
|
+
-- Sync triggers (all three required to keep FTS consistent)
|
|
751
|
+
CREATE TRIGGER IF NOT EXISTS fts_ai AFTER INSERT ON context_items BEGIN
|
|
752
|
+
INSERT INTO context_items_fts(rowid, key, value)
|
|
753
|
+
VALUES (new.rowid, new.key, new.value);
|
|
754
|
+
END;
|
|
755
|
+
|
|
756
|
+
CREATE TRIGGER IF NOT EXISTS fts_ad AFTER DELETE ON context_items BEGIN
|
|
757
|
+
INSERT INTO context_items_fts(context_items_fts, rowid, key, value)
|
|
758
|
+
VALUES ('delete', old.rowid, old.key, old.value);
|
|
759
|
+
END;
|
|
760
|
+
|
|
761
|
+
CREATE TRIGGER IF NOT EXISTS fts_au AFTER UPDATE ON context_items BEGIN
|
|
762
|
+
INSERT INTO context_items_fts(context_items_fts, rowid, key, value)
|
|
763
|
+
VALUES ('delete', old.rowid, old.key, old.value);
|
|
764
|
+
INSERT INTO context_items_fts(rowid, key, value)
|
|
765
|
+
VALUES (new.rowid, new.key, new.value);
|
|
766
|
+
END;
|
|
767
|
+
|
|
768
|
+
INSERT INTO context_items_fts(rowid, key, value)
|
|
769
|
+
SELECT rowid, key, value FROM context_items;
|
|
770
|
+
`);
|
|
771
|
+
})();
|
|
772
|
+
}
|
|
773
|
+
catch (e) {
|
|
774
|
+
// Non-fatal: FTS5 trigram is an optional enhancement.
|
|
775
|
+
// LIKE-based search continues to work if this migration is skipped.
|
|
776
|
+
console.warn('FTS5 migration skipped (trigram tokenizer may not be available):', e);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
exports.DatabaseManager = DatabaseManager;
|