mcp-memory-keeper 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/CHANGELOG.md +433 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1051 -0
  4. package/bin/mcp-memory-keeper +52 -0
  5. package/dist/__tests__/helpers/database-test-helper.js +160 -0
  6. package/dist/__tests__/helpers/test-server.js +92 -0
  7. package/dist/__tests__/integration/advanced-features.test.js +614 -0
  8. package/dist/__tests__/integration/backward-compatibility.test.js +245 -0
  9. package/dist/__tests__/integration/batchOperationsE2E.test.js +396 -0
  10. package/dist/__tests__/integration/batchOperationsHandler.test.js +1230 -0
  11. package/dist/__tests__/integration/channelManagementHandler.test.js +1291 -0
  12. package/dist/__tests__/integration/channels.test.js +376 -0
  13. package/dist/__tests__/integration/checkpoint.test.js +251 -0
  14. package/dist/__tests__/integration/concurrent-access.test.js +190 -0
  15. package/dist/__tests__/integration/context-operations.test.js +243 -0
  16. package/dist/__tests__/integration/contextDiff.test.js +852 -0
  17. package/dist/__tests__/integration/contextDiffHandler.test.js +976 -0
  18. package/dist/__tests__/integration/contextExportHandler.test.js +510 -0
  19. package/dist/__tests__/integration/contextGetPaginationDefaults.test.js +298 -0
  20. package/dist/__tests__/integration/contextReassignChannelHandler.test.js +908 -0
  21. package/dist/__tests__/integration/contextRelationshipsHandler.test.js +1151 -0
  22. package/dist/__tests__/integration/contextSearch.test.js +938 -0
  23. package/dist/__tests__/integration/contextSearchHandler.test.js +552 -0
  24. package/dist/__tests__/integration/contextWatchActual.test.js +165 -0
  25. package/dist/__tests__/integration/contextWatchHandler.test.js +1500 -0
  26. package/dist/__tests__/integration/cross-session-sharing.test.js +302 -0
  27. package/dist/__tests__/integration/database-initialization.test.js +134 -0
  28. package/dist/__tests__/integration/enhanced-context-operations.test.js +1082 -0
  29. package/dist/__tests__/integration/enhancedContextGetHandler.test.js +915 -0
  30. package/dist/__tests__/integration/enhancedContextTimelineHandler.test.js +716 -0
  31. package/dist/__tests__/integration/error-cases.test.js +407 -0
  32. package/dist/__tests__/integration/export-import.test.js +367 -0
  33. package/dist/__tests__/integration/feature-flags.test.js +542 -0
  34. package/dist/__tests__/integration/file-operations.test.js +264 -0
  35. package/dist/__tests__/integration/git-integration.test.js +237 -0
  36. package/dist/__tests__/integration/index-tools.test.js +496 -0
  37. package/dist/__tests__/integration/issue11-actual-bug-demo.test.js +304 -0
  38. package/dist/__tests__/integration/issue11-search-filters-bug.test.js +561 -0
  39. package/dist/__tests__/integration/issue12-checkpoint-restore-behavior.test.js +621 -0
  40. package/dist/__tests__/integration/issue13-key-validation.test.js +433 -0
  41. package/dist/__tests__/integration/knowledge-graph.test.js +338 -0
  42. package/dist/__tests__/integration/migrations.test.js +528 -0
  43. package/dist/__tests__/integration/multi-agent.test.js +546 -0
  44. package/dist/__tests__/integration/pagination-critical-fix.test.js +296 -0
  45. package/dist/__tests__/integration/paginationDefaultsHandler.test.js +600 -0
  46. package/dist/__tests__/integration/project-directory.test.js +283 -0
  47. package/dist/__tests__/integration/resource-cleanup.test.js +149 -0
  48. package/dist/__tests__/integration/retention.test.js +513 -0
  49. package/dist/__tests__/integration/search.test.js +333 -0
  50. package/dist/__tests__/integration/semantic-search.test.js +266 -0
  51. package/dist/__tests__/integration/server-initialization.test.js +307 -0
  52. package/dist/__tests__/integration/session-management.test.js +219 -0
  53. package/dist/__tests__/integration/simplified-sharing.test.js +346 -0
  54. package/dist/__tests__/integration/smart-compaction.test.js +230 -0
  55. package/dist/__tests__/integration/summarization.test.js +308 -0
  56. package/dist/__tests__/integration/watcher-migration-validation.test.js +544 -0
  57. package/dist/__tests__/security/input-validation.test.js +115 -0
  58. package/dist/__tests__/utils/agents.test.js +473 -0
  59. package/dist/__tests__/utils/database.test.js +177 -0
  60. package/dist/__tests__/utils/git.test.js +122 -0
  61. package/dist/__tests__/utils/knowledge-graph.test.js +297 -0
  62. package/dist/__tests__/utils/migrationHealthCheck.test.js +302 -0
  63. package/dist/__tests__/utils/project-directory-messages.test.js +188 -0
  64. package/dist/__tests__/utils/timezone-safe-dates.js +119 -0
  65. package/dist/__tests__/utils/validation.test.js +200 -0
  66. package/dist/__tests__/utils/vector-store.test.js +231 -0
  67. package/dist/handlers/contextWatchHandlers.js +206 -0
  68. package/dist/index.js +4310 -0
  69. package/dist/index.phase1.backup.js +410 -0
  70. package/dist/index.phase2.backup.js +704 -0
  71. package/dist/migrations/003_add_channels.js +174 -0
  72. package/dist/migrations/004_add_context_watch.js +151 -0
  73. package/dist/migrations/005_add_context_watch.js +98 -0
  74. package/dist/migrations/simplify-sharing.js +117 -0
  75. package/dist/repositories/BaseRepository.js +30 -0
  76. package/dist/repositories/CheckpointRepository.js +140 -0
  77. package/dist/repositories/ContextRepository.js +1873 -0
  78. package/dist/repositories/FileRepository.js +104 -0
  79. package/dist/repositories/RepositoryManager.js +62 -0
  80. package/dist/repositories/SessionRepository.js +66 -0
  81. package/dist/repositories/WatcherRepository.js +252 -0
  82. package/dist/repositories/index.js +15 -0
  83. package/dist/server.js +384 -0
  84. package/dist/test-helpers/database-helper.js +128 -0
  85. package/dist/types/entities.js +3 -0
  86. package/dist/utils/agents.js +791 -0
  87. package/dist/utils/channels.js +150 -0
  88. package/dist/utils/database.js +731 -0
  89. package/dist/utils/feature-flags.js +476 -0
  90. package/dist/utils/git.js +145 -0
  91. package/dist/utils/knowledge-graph.js +264 -0
  92. package/dist/utils/migrationHealthCheck.js +373 -0
  93. package/dist/utils/migrations.js +452 -0
  94. package/dist/utils/retention.js +460 -0
  95. package/dist/utils/timestamps.js +112 -0
  96. package/dist/utils/validation.js +296 -0
  97. package/dist/utils/vector-store.js +247 -0
  98. package/package.json +84 -0
@@ -0,0 +1,174 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.addChannelsMigration = void 0;
4
+ exports.addChannelsMigration = {
5
+ version: '3.0.0',
6
+ name: 'Add channels support for context organization',
7
+ description: 'Add channel column to context_items and default_channel to sessions for better context organization.',
8
+ requiresBackup: true,
9
+ dependencies: ['2.0.0'], // Depends on the simplify-sharing migration
10
+ up: `
11
+ -- Add channel column to context_items table with default 'general'
12
+ -- SQLite doesn't support adding columns with constraints directly, so we need to recreate the table
13
+ CREATE TABLE context_items_new (
14
+ id TEXT PRIMARY KEY,
15
+ session_id TEXT NOT NULL,
16
+ key TEXT NOT NULL,
17
+ value TEXT NOT NULL,
18
+ category TEXT,
19
+ priority TEXT DEFAULT 'normal',
20
+ metadata TEXT,
21
+ size INTEGER DEFAULT 0,
22
+ is_private INTEGER DEFAULT 0,
23
+ channel TEXT NOT NULL DEFAULT 'general',
24
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
25
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
26
+ FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE,
27
+ UNIQUE(session_id, key)
28
+ );
29
+
30
+ -- Copy data from old table to new, using 'general' as default channel
31
+ INSERT INTO context_items_new (
32
+ id, session_id, key, value, category, priority, metadata, size, is_private, channel, created_at, updated_at
33
+ )
34
+ SELECT
35
+ id, session_id, key, value, category, priority, metadata, size, is_private, 'general', created_at, updated_at
36
+ FROM context_items;
37
+
38
+ -- Drop old table and rename new one
39
+ DROP TABLE context_items;
40
+ ALTER TABLE context_items_new RENAME TO context_items;
41
+
42
+ -- Recreate all indexes including new channel index
43
+ CREATE INDEX IF NOT EXISTS idx_context_items_session ON context_items(session_id);
44
+ CREATE INDEX IF NOT EXISTS idx_context_items_key ON context_items(key);
45
+ CREATE INDEX IF NOT EXISTS idx_context_items_category ON context_items(category);
46
+ CREATE INDEX IF NOT EXISTS idx_context_items_priority ON context_items(priority);
47
+ CREATE INDEX IF NOT EXISTS idx_context_items_private ON context_items(is_private);
48
+ CREATE INDEX IF NOT EXISTS idx_context_items_created ON context_items(created_at);
49
+ CREATE INDEX IF NOT EXISTS idx_context_items_channel ON context_items(channel);
50
+ CREATE INDEX IF NOT EXISTS idx_context_items_channel_session ON context_items(channel, session_id);
51
+
52
+ -- Recreate the updated_at trigger
53
+ DROP TRIGGER IF EXISTS update_context_items_timestamp;
54
+ CREATE TRIGGER update_context_items_timestamp
55
+ AFTER UPDATE ON context_items
56
+ BEGIN
57
+ UPDATE context_items SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
58
+ END;
59
+
60
+ -- Add default_channel column to sessions table
61
+ -- Again, need to recreate the table
62
+ CREATE TABLE sessions_new (
63
+ id TEXT PRIMARY KEY,
64
+ name TEXT,
65
+ description TEXT,
66
+ branch TEXT,
67
+ working_directory TEXT,
68
+ parent_id TEXT,
69
+ default_channel TEXT NOT NULL DEFAULT 'general',
70
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
71
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
72
+ FOREIGN KEY (parent_id) REFERENCES sessions(id)
73
+ );
74
+
75
+ -- Copy data from old sessions table
76
+ INSERT INTO sessions_new (
77
+ id, name, description, branch, working_directory, parent_id, default_channel, created_at, updated_at
78
+ )
79
+ SELECT
80
+ id, name, description, branch, working_directory, parent_id, 'general', created_at, updated_at
81
+ FROM sessions;
82
+
83
+ -- Drop old table and rename new one
84
+ DROP TABLE sessions;
85
+ ALTER TABLE sessions_new RENAME TO sessions;
86
+
87
+ -- Recreate the updated_at trigger for sessions
88
+ DROP TRIGGER IF EXISTS update_sessions_timestamp;
89
+ CREATE TRIGGER update_sessions_timestamp
90
+ AFTER UPDATE ON sessions
91
+ BEGIN
92
+ UPDATE sessions SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
93
+ END;
94
+ `,
95
+ down: `
96
+ -- Revert context_items to previous schema
97
+ CREATE TABLE context_items_old (
98
+ id TEXT PRIMARY KEY,
99
+ session_id TEXT NOT NULL,
100
+ key TEXT NOT NULL,
101
+ value TEXT NOT NULL,
102
+ category TEXT,
103
+ priority TEXT DEFAULT 'normal',
104
+ metadata TEXT,
105
+ size INTEGER DEFAULT 0,
106
+ is_private INTEGER DEFAULT 0,
107
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
108
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
109
+ FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE,
110
+ UNIQUE(session_id, key)
111
+ );
112
+
113
+ -- Copy data back without channel column
114
+ INSERT INTO context_items_old (
115
+ id, session_id, key, value, category, priority, metadata, size, is_private, created_at, updated_at
116
+ )
117
+ SELECT
118
+ id, session_id, key, value, category, priority, metadata, size, is_private, created_at, updated_at
119
+ FROM context_items;
120
+
121
+ -- Drop new table and rename old one back
122
+ DROP TABLE context_items;
123
+ ALTER TABLE context_items_old RENAME TO context_items;
124
+
125
+ -- Recreate original indexes
126
+ CREATE INDEX IF NOT EXISTS idx_context_items_session ON context_items(session_id);
127
+ CREATE INDEX IF NOT EXISTS idx_context_items_key ON context_items(key);
128
+ CREATE INDEX IF NOT EXISTS idx_context_items_category ON context_items(category);
129
+ CREATE INDEX IF NOT EXISTS idx_context_items_priority ON context_items(priority);
130
+ CREATE INDEX IF NOT EXISTS idx_context_items_private ON context_items(is_private);
131
+ CREATE INDEX IF NOT EXISTS idx_context_items_created ON context_items(created_at);
132
+
133
+ -- Recreate the updated_at trigger
134
+ DROP TRIGGER IF EXISTS update_context_items_timestamp;
135
+ CREATE TRIGGER update_context_items_timestamp
136
+ AFTER UPDATE ON context_items
137
+ BEGIN
138
+ UPDATE context_items SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
139
+ END;
140
+
141
+ -- Revert sessions to previous schema
142
+ CREATE TABLE sessions_old (
143
+ id TEXT PRIMARY KEY,
144
+ name TEXT,
145
+ description TEXT,
146
+ branch TEXT,
147
+ working_directory TEXT,
148
+ parent_id TEXT,
149
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
150
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
151
+ FOREIGN KEY (parent_id) REFERENCES sessions(id)
152
+ );
153
+
154
+ -- Copy data back without default_channel column
155
+ INSERT INTO sessions_old (
156
+ id, name, description, branch, working_directory, parent_id, created_at, updated_at
157
+ )
158
+ SELECT
159
+ id, name, description, branch, working_directory, parent_id, created_at, updated_at
160
+ FROM sessions;
161
+
162
+ -- Drop new table and rename old one back
163
+ DROP TABLE sessions;
164
+ ALTER TABLE sessions_old RENAME TO sessions;
165
+
166
+ -- Recreate the updated_at trigger for sessions
167
+ DROP TRIGGER IF EXISTS update_sessions_timestamp;
168
+ CREATE TRIGGER update_sessions_timestamp
169
+ AFTER UPDATE ON sessions
170
+ BEGIN
171
+ UPDATE sessions SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
172
+ END;
173
+ `,
174
+ };
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.description = exports.version = void 0;
4
+ exports.up = up;
5
+ exports.down = down;
6
+ exports.needsMigration = needsMigration;
7
+ exports.version = '0.4.0';
8
+ exports.description = 'Add context watch functionality with change tracking';
9
+ function up(db) {
10
+ db.transaction(() => {
11
+ // Create change tracking table
12
+ db.exec(`
13
+ CREATE TABLE IF NOT EXISTS context_changes (
14
+ sequence_id INTEGER PRIMARY KEY AUTOINCREMENT,
15
+ session_id TEXT NOT NULL,
16
+ item_id TEXT NOT NULL,
17
+ key TEXT NOT NULL,
18
+ operation TEXT NOT NULL CHECK (operation IN ('CREATE', 'UPDATE', 'DELETE')),
19
+ old_value TEXT,
20
+ new_value TEXT,
21
+ old_metadata TEXT,
22
+ new_metadata TEXT,
23
+ category TEXT,
24
+ priority TEXT,
25
+ channel TEXT,
26
+ size_delta INTEGER DEFAULT 0,
27
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
28
+ created_by TEXT,
29
+ FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
30
+ );
31
+ `);
32
+ // Create watchers registry table
33
+ db.exec(`
34
+ CREATE TABLE IF NOT EXISTS context_watchers (
35
+ id TEXT PRIMARY KEY,
36
+ session_id TEXT,
37
+ filter_keys TEXT,
38
+ filter_categories TEXT,
39
+ filter_channels TEXT,
40
+ filter_priorities TEXT,
41
+ last_sequence INTEGER DEFAULT 0,
42
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
43
+ last_poll_at TIMESTAMP,
44
+ expires_at TIMESTAMP,
45
+ metadata TEXT,
46
+ FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
47
+ );
48
+ `);
49
+ // Create indexes for performance
50
+ db.exec(`
51
+ CREATE INDEX IF NOT EXISTS idx_changes_sequence ON context_changes(sequence_id);
52
+ CREATE INDEX IF NOT EXISTS idx_changes_session_seq ON context_changes(session_id, sequence_id);
53
+ CREATE INDEX IF NOT EXISTS idx_changes_created ON context_changes(created_at);
54
+ CREATE INDEX IF NOT EXISTS idx_watchers_expires ON context_watchers(expires_at);
55
+ CREATE INDEX IF NOT EXISTS idx_watchers_session ON context_watchers(session_id);
56
+ `);
57
+ // Create triggers for change tracking
58
+ db.exec(`
59
+ -- Trigger for INSERT operations on context_items
60
+ CREATE TRIGGER IF NOT EXISTS track_context_insert
61
+ AFTER INSERT ON context_items
62
+ BEGIN
63
+ INSERT INTO context_changes (
64
+ session_id, item_id, key, operation,
65
+ new_value, new_metadata, category, priority, channel,
66
+ size_delta, created_by
67
+ ) VALUES (
68
+ NEW.session_id, NEW.id, NEW.key, 'CREATE',
69
+ NEW.value, NEW.metadata, NEW.category, NEW.priority, NEW.channel,
70
+ NEW.size, 'context_save'
71
+ );
72
+ END;
73
+
74
+ -- Trigger for UPDATE operations on context_items
75
+ CREATE TRIGGER IF NOT EXISTS track_context_update
76
+ AFTER UPDATE ON context_items
77
+ WHEN OLD.value != NEW.value OR
78
+ IFNULL(OLD.metadata, '') != IFNULL(NEW.metadata, '') OR
79
+ IFNULL(OLD.category, '') != IFNULL(NEW.category, '') OR
80
+ IFNULL(OLD.priority, '') != IFNULL(NEW.priority, '') OR
81
+ IFNULL(OLD.channel, '') != IFNULL(NEW.channel, '')
82
+ BEGIN
83
+ INSERT INTO context_changes (
84
+ session_id, item_id, key, operation,
85
+ old_value, new_value, old_metadata, new_metadata,
86
+ category, priority, channel, size_delta, created_by
87
+ ) VALUES (
88
+ NEW.session_id, NEW.id, NEW.key, 'UPDATE',
89
+ OLD.value, NEW.value, OLD.metadata, NEW.metadata,
90
+ NEW.category, NEW.priority, NEW.channel,
91
+ NEW.size - OLD.size, 'context_save'
92
+ );
93
+ END;
94
+
95
+ -- Trigger for DELETE operations on context_items
96
+ CREATE TRIGGER IF NOT EXISTS track_context_delete
97
+ AFTER DELETE ON context_items
98
+ BEGIN
99
+ INSERT INTO context_changes (
100
+ session_id, item_id, key, operation,
101
+ old_value, old_metadata, category, priority, channel,
102
+ size_delta, created_by
103
+ ) VALUES (
104
+ OLD.session_id, OLD.id, OLD.key, 'DELETE',
105
+ OLD.value, OLD.metadata, OLD.category, OLD.priority, OLD.channel,
106
+ -OLD.size, 'context_delete'
107
+ );
108
+ END;
109
+ `);
110
+ // Add migration record
111
+ db.prepare(`
112
+ INSERT INTO migrations (id, version, name, description, up_sql, applied_at)
113
+ VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
114
+ `).run(exports.version, exports.version, '004_add_context_watch', exports.description, 'See migration file for full SQL');
115
+ })();
116
+ }
117
+ function down(db) {
118
+ db.transaction(() => {
119
+ // Drop triggers
120
+ db.exec(`
121
+ DROP TRIGGER IF EXISTS track_context_insert;
122
+ DROP TRIGGER IF EXISTS track_context_update;
123
+ DROP TRIGGER IF EXISTS track_context_delete;
124
+ `);
125
+ // Drop indexes
126
+ db.exec(`
127
+ DROP INDEX IF EXISTS idx_changes_sequence;
128
+ DROP INDEX IF EXISTS idx_changes_session_seq;
129
+ DROP INDEX IF EXISTS idx_changes_created;
130
+ DROP INDEX IF EXISTS idx_watchers_expires;
131
+ DROP INDEX IF EXISTS idx_watchers_session;
132
+ `);
133
+ // Drop tables
134
+ db.exec(`
135
+ DROP TABLE IF EXISTS context_watchers;
136
+ DROP TABLE IF EXISTS context_changes;
137
+ `);
138
+ // Remove migration record
139
+ db.prepare('DELETE FROM migrations WHERE version = ?').run(exports.version);
140
+ })();
141
+ }
142
+ // Helper to check if migration is needed
143
+ function needsMigration(db) {
144
+ const result = db
145
+ .prepare(`
146
+ SELECT COUNT(*) as count FROM sqlite_master
147
+ WHERE type='table' AND name IN ('context_changes', 'context_watchers')
148
+ `)
149
+ .get();
150
+ return result.count < 2;
151
+ }
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.down = exports.up = exports.description = exports.version = void 0;
4
+ exports.version = '0.5.0';
5
+ exports.description = 'Add missing context watch functionality';
6
+ const up = (db) => {
7
+ db.transaction(() => {
8
+ // Add is_active column to context_watchers if not exists
9
+ const watchers_columns = db.prepare('PRAGMA table_info(context_watchers)').all();
10
+ if (!watchers_columns.some((col) => col.name === 'is_active')) {
11
+ db.exec('ALTER TABLE context_watchers ADD COLUMN is_active INTEGER DEFAULT 1');
12
+ db.exec('CREATE INDEX IF NOT EXISTS idx_watchers_active ON context_watchers(is_active)');
13
+ }
14
+ // Add sequence_number column to context_items if not exists
15
+ const columns = db.prepare('PRAGMA table_info(context_items)').all();
16
+ if (!columns.some((col) => col.name === 'sequence_number')) {
17
+ db.exec('ALTER TABLE context_items ADD COLUMN sequence_number INTEGER DEFAULT 0');
18
+ // Update existing rows with sequence numbers
19
+ db.exec(`
20
+ UPDATE context_items
21
+ SET sequence_number = (
22
+ SELECT COUNT(*)
23
+ FROM context_items c2
24
+ WHERE c2.session_id = context_items.session_id
25
+ AND c2.created_at <= context_items.created_at
26
+ )
27
+ WHERE sequence_number = 0
28
+ `);
29
+ // Create trigger to auto-increment sequence numbers for new inserts
30
+ db.exec(`
31
+ CREATE TRIGGER IF NOT EXISTS increment_sequence_insert
32
+ AFTER INSERT ON context_items
33
+ FOR EACH ROW
34
+ WHEN NEW.sequence_number = 0
35
+ BEGIN
36
+ UPDATE context_items
37
+ SET sequence_number = (
38
+ SELECT COALESCE(MAX(sequence_number), 0) + 1
39
+ FROM context_items
40
+ WHERE session_id = NEW.session_id
41
+ )
42
+ WHERE id = NEW.id;
43
+ END
44
+ `);
45
+ // Create trigger to update sequence numbers on updates
46
+ db.exec(`
47
+ CREATE TRIGGER IF NOT EXISTS increment_sequence_update
48
+ AFTER UPDATE OF value, metadata, category, priority, channel ON context_items
49
+ FOR EACH ROW
50
+ WHEN OLD.value != NEW.value OR
51
+ IFNULL(OLD.metadata, '') != IFNULL(NEW.metadata, '') OR
52
+ IFNULL(OLD.category, '') != IFNULL(NEW.category, '') OR
53
+ IFNULL(OLD.priority, '') != IFNULL(NEW.priority, '') OR
54
+ IFNULL(OLD.channel, '') != IFNULL(NEW.channel, '')
55
+ BEGIN
56
+ UPDATE context_items
57
+ SET sequence_number = (
58
+ SELECT COALESCE(MAX(sequence_number), 0) + 1
59
+ FROM context_items
60
+ WHERE session_id = NEW.session_id
61
+ )
62
+ WHERE id = NEW.id;
63
+ END
64
+ `);
65
+ }
66
+ // Create table for tracking deleted items (needed for tests)
67
+ db.exec(`
68
+ CREATE TABLE IF NOT EXISTS deleted_items (
69
+ id TEXT PRIMARY KEY,
70
+ session_id TEXT NOT NULL,
71
+ key TEXT NOT NULL,
72
+ category TEXT,
73
+ channel TEXT,
74
+ sequence_number INTEGER NOT NULL,
75
+ deleted_at TEXT DEFAULT (datetime('now')),
76
+ FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
77
+ )
78
+ `);
79
+ db.exec('CREATE INDEX IF NOT EXISTS idx_deleted_items_session ON deleted_items(session_id)');
80
+ db.exec('CREATE INDEX IF NOT EXISTS idx_deleted_items_key ON deleted_items(key)');
81
+ })();
82
+ };
83
+ exports.up = up;
84
+ const down = (db) => {
85
+ db.transaction(() => {
86
+ // Drop triggers
87
+ db.exec('DROP TRIGGER IF EXISTS increment_sequence_insert');
88
+ db.exec('DROP TRIGGER IF EXISTS increment_sequence_update');
89
+ // Drop indexes
90
+ db.exec('DROP INDEX IF EXISTS idx_watchers_active');
91
+ db.exec('DROP INDEX IF EXISTS idx_deleted_items_session');
92
+ db.exec('DROP INDEX IF EXISTS idx_deleted_items_key');
93
+ // Drop tables
94
+ db.exec('DROP TABLE IF EXISTS deleted_items');
95
+ // Note: We don't remove the sequence_number and is_active columns as it might break existing data
96
+ })();
97
+ };
98
+ exports.down = down;
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.simplifySharingMigration = void 0;
4
+ exports.simplifySharingMigration = {
5
+ version: '2.0.0',
6
+ name: 'Simplify sharing model - remove complex sharing, add is_private flag',
7
+ description: 'Replace broken sharing mechanism with simple is_private flag. Items are shared by default unless marked private.',
8
+ requiresBackup: true,
9
+ dependencies: ['1.2.0'], // Assuming the latest migration from defaults is 1.2.0
10
+ up: `
11
+ -- Add the new is_private column with default false (shared by default)
12
+ ALTER TABLE context_items ADD COLUMN is_private INTEGER DEFAULT 0;
13
+
14
+ -- All existing items become public (is_private = 0)
15
+ -- This makes all previously saved context accessible across sessions
16
+ UPDATE context_items SET is_private = 0;
17
+
18
+ -- Create index for performance on the new column
19
+ CREATE INDEX IF NOT EXISTS idx_context_items_private ON context_items(is_private);
20
+
21
+ -- Drop the old sharing-related columns
22
+ -- SQLite doesn't support DROP COLUMN directly, so we need to recreate the table
23
+ CREATE TABLE context_items_new (
24
+ id TEXT PRIMARY KEY,
25
+ session_id TEXT NOT NULL,
26
+ key TEXT NOT NULL,
27
+ value TEXT NOT NULL,
28
+ category TEXT,
29
+ priority TEXT DEFAULT 'normal',
30
+ metadata TEXT,
31
+ size INTEGER DEFAULT 0,
32
+ is_private INTEGER DEFAULT 0,
33
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
34
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
35
+ FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE,
36
+ UNIQUE(session_id, key)
37
+ );
38
+
39
+ -- Copy data from old table to new
40
+ INSERT INTO context_items_new (
41
+ id, session_id, key, value, category, priority, metadata, size, is_private, created_at, updated_at
42
+ )
43
+ SELECT
44
+ id, session_id, key, value, category, priority, metadata, size, is_private, created_at, updated_at
45
+ FROM context_items;
46
+
47
+ -- Drop old table and rename new one
48
+ DROP TABLE context_items;
49
+ ALTER TABLE context_items_new RENAME TO context_items;
50
+
51
+ -- Recreate indexes
52
+ CREATE INDEX IF NOT EXISTS idx_context_items_session ON context_items(session_id);
53
+ CREATE INDEX IF NOT EXISTS idx_context_items_key ON context_items(key);
54
+ CREATE INDEX IF NOT EXISTS idx_context_items_category ON context_items(category);
55
+ CREATE INDEX IF NOT EXISTS idx_context_items_priority ON context_items(priority);
56
+ CREATE INDEX IF NOT EXISTS idx_context_items_private ON context_items(is_private);
57
+ CREATE INDEX IF NOT EXISTS idx_context_items_created ON context_items(created_at);
58
+
59
+ -- Update the updated_at trigger
60
+ DROP TRIGGER IF EXISTS update_context_items_timestamp;
61
+ CREATE TRIGGER update_context_items_timestamp
62
+ AFTER UPDATE ON context_items
63
+ BEGIN
64
+ UPDATE context_items SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
65
+ END;
66
+ `,
67
+ down: `
68
+ -- Revert to the old schema
69
+ CREATE TABLE context_items_old (
70
+ id TEXT PRIMARY KEY,
71
+ session_id TEXT NOT NULL,
72
+ key TEXT NOT NULL,
73
+ value TEXT NOT NULL,
74
+ category TEXT,
75
+ priority TEXT DEFAULT 'normal',
76
+ metadata TEXT,
77
+ size INTEGER DEFAULT 0,
78
+ shared INTEGER DEFAULT 0,
79
+ shared_with_sessions TEXT,
80
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
81
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
82
+ FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE,
83
+ UNIQUE(session_id, key)
84
+ );
85
+
86
+ -- Copy data back
87
+ -- Since we made everything public in the up migration, we'll set shared = 1 for all
88
+ INSERT INTO context_items_old (
89
+ id, session_id, key, value, category, priority, metadata, size, shared, created_at, updated_at
90
+ )
91
+ SELECT
92
+ id, session_id, key, value, category, priority, metadata, size,
93
+ 1, -- All items were made public, so mark as shared
94
+ created_at, updated_at
95
+ FROM context_items;
96
+
97
+ -- Drop new table and rename old one back
98
+ DROP TABLE context_items;
99
+ ALTER TABLE context_items_old RENAME TO context_items;
100
+
101
+ -- Recreate original indexes
102
+ CREATE INDEX IF NOT EXISTS idx_context_items_session ON context_items(session_id);
103
+ CREATE INDEX IF NOT EXISTS idx_context_items_key ON context_items(key);
104
+ CREATE INDEX IF NOT EXISTS idx_context_items_category ON context_items(category);
105
+ CREATE INDEX IF NOT EXISTS idx_context_items_priority ON context_items(priority);
106
+ CREATE INDEX IF NOT EXISTS idx_context_items_shared ON context_items(shared);
107
+ CREATE INDEX IF NOT EXISTS idx_context_items_created ON context_items(created_at);
108
+
109
+ -- Recreate the updated_at trigger
110
+ DROP TRIGGER IF EXISTS update_context_items_timestamp;
111
+ CREATE TRIGGER update_context_items_timestamp
112
+ AFTER UPDATE ON context_items
113
+ BEGIN
114
+ UPDATE context_items SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
115
+ END;
116
+ `,
117
+ };
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BaseRepository = void 0;
4
+ class BaseRepository {
5
+ db;
6
+ dbManager;
7
+ constructor(dbManager) {
8
+ this.dbManager = dbManager;
9
+ this.db = dbManager.getDatabase();
10
+ }
11
+ calculateSize(value) {
12
+ return Buffer.byteLength(value, 'utf8');
13
+ }
14
+ generateId() {
15
+ // Using crypto.randomUUID() for better performance if available
16
+ if (typeof crypto !== 'undefined' && crypto.randomUUID) {
17
+ return crypto.randomUUID();
18
+ }
19
+ // Fallback to uuid v4 pattern
20
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
21
+ const r = (Math.random() * 16) | 0;
22
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
23
+ return v.toString(16);
24
+ });
25
+ }
26
+ getCurrentTimestamp() {
27
+ return new Date().toISOString();
28
+ }
29
+ }
30
+ exports.BaseRepository = BaseRepository;