simple-memory-mcp 1.1.3

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 (247) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +742 -0
  3. package/dist/cli/arg-parser.d.ts +9 -0
  4. package/dist/cli/arg-parser.d.ts.map +1 -0
  5. package/dist/cli/arg-parser.js +68 -0
  6. package/dist/cli/arg-parser.js.map +1 -0
  7. package/dist/cli/help-generator.d.ts +8 -0
  8. package/dist/cli/help-generator.d.ts.map +1 -0
  9. package/dist/cli/help-generator.js +89 -0
  10. package/dist/cli/help-generator.js.map +1 -0
  11. package/dist/cli/index.d.ts +27 -0
  12. package/dist/cli/index.d.ts.map +1 -0
  13. package/dist/cli/index.js +56 -0
  14. package/dist/cli/index.js.map +1 -0
  15. package/dist/cli/query-builder.d.ts +8 -0
  16. package/dist/cli/query-builder.d.ts.map +1 -0
  17. package/dist/cli/query-builder.js +63 -0
  18. package/dist/cli/query-builder.js.map +1 -0
  19. package/dist/cli/shortcuts-config.d.ts +26 -0
  20. package/dist/cli/shortcuts-config.d.ts.map +1 -0
  21. package/dist/cli/shortcuts-config.js +94 -0
  22. package/dist/cli/shortcuts-config.js.map +1 -0
  23. package/dist/graphql/resolvers.d.ts +101 -0
  24. package/dist/graphql/resolvers.d.ts.map +1 -0
  25. package/dist/graphql/resolvers.js +98 -0
  26. package/dist/graphql/resolvers.js.map +1 -0
  27. package/dist/graphql/schema.d.ts +13 -0
  28. package/dist/graphql/schema.d.ts.map +1 -0
  29. package/dist/graphql/schema.js +314 -0
  30. package/dist/graphql/schema.js.map +1 -0
  31. package/dist/index.d.ts +3 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +573 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/services/backup-service.d.ts +57 -0
  36. package/dist/services/backup-service.d.ts.map +1 -0
  37. package/dist/services/backup-service.js +191 -0
  38. package/dist/services/backup-service.js.map +1 -0
  39. package/dist/services/database-optimizer.d.ts +15 -0
  40. package/dist/services/database-optimizer.d.ts.map +1 -0
  41. package/dist/services/database-optimizer.js +45 -0
  42. package/dist/services/database-optimizer.js.map +1 -0
  43. package/dist/services/memory-service.d.ts +126 -0
  44. package/dist/services/memory-service.d.ts.map +1 -0
  45. package/dist/services/memory-service.js +862 -0
  46. package/dist/services/memory-service.js.map +1 -0
  47. package/dist/services/migrations.d.ts +17 -0
  48. package/dist/services/migrations.d.ts.map +1 -0
  49. package/dist/services/migrations.js +273 -0
  50. package/dist/services/migrations.js.map +1 -0
  51. package/dist/tests/backup-export-test.d.ts +2 -0
  52. package/dist/tests/backup-export-test.d.ts.map +1 -0
  53. package/dist/tests/backup-export-test.js +162 -0
  54. package/dist/tests/backup-export-test.js.map +1 -0
  55. package/dist/tests/backup-test.d.ts +14 -0
  56. package/dist/tests/backup-test.d.ts.map +1 -0
  57. package/dist/tests/backup-test.js +209 -0
  58. package/dist/tests/backup-test.js.map +1 -0
  59. package/dist/tests/export-import-test.d.ts +15 -0
  60. package/dist/tests/export-import-test.d.ts.map +1 -0
  61. package/dist/tests/export-import-test.js +227 -0
  62. package/dist/tests/export-import-test.js.map +1 -0
  63. package/dist/tests/graphql-comprehensive-test.d.ts +6 -0
  64. package/dist/tests/graphql-comprehensive-test.d.ts.map +1 -0
  65. package/dist/tests/graphql-comprehensive-test.js +342 -0
  66. package/dist/tests/graphql-comprehensive-test.js.map +1 -0
  67. package/dist/tests/graphql-performance-test.d.ts +6 -0
  68. package/dist/tests/graphql-performance-test.d.ts.map +1 -0
  69. package/dist/tests/graphql-performance-test.js +141 -0
  70. package/dist/tests/graphql-performance-test.js.map +1 -0
  71. package/dist/tests/graphql-test.d.ts +5 -0
  72. package/dist/tests/graphql-test.d.ts.map +1 -0
  73. package/dist/tests/graphql-test.js +47 -0
  74. package/dist/tests/graphql-test.js.map +1 -0
  75. package/dist/tests/memory-server-tests.d.ts +7 -0
  76. package/dist/tests/memory-server-tests.d.ts.map +1 -0
  77. package/dist/tests/memory-server-tests.js +466 -0
  78. package/dist/tests/memory-server-tests.js.map +1 -0
  79. package/dist/tests/migration-test.d.ts +2 -0
  80. package/dist/tests/migration-test.d.ts.map +1 -0
  81. package/dist/tests/migration-test.js +270 -0
  82. package/dist/tests/migration-test.js.map +1 -0
  83. package/dist/tests/performance-benchmark.d.ts +7 -0
  84. package/dist/tests/performance-benchmark.d.ts.map +1 -0
  85. package/dist/tests/performance-benchmark.js +234 -0
  86. package/dist/tests/performance-benchmark.js.map +1 -0
  87. package/dist/tests/performance-test.d.ts +3 -0
  88. package/dist/tests/performance-test.d.ts.map +1 -0
  89. package/dist/tests/performance-test.js +61 -0
  90. package/dist/tests/performance-test.js.map +1 -0
  91. package/dist/tests/time-range-test.d.ts +7 -0
  92. package/dist/tests/time-range-test.d.ts.map +1 -0
  93. package/dist/tests/time-range-test.js +169 -0
  94. package/dist/tests/time-range-test.js.map +1 -0
  95. package/dist/tools/delete-memory/cli-parser.d.ts +2 -0
  96. package/dist/tools/delete-memory/cli-parser.d.ts.map +1 -0
  97. package/dist/tools/delete-memory/cli-parser.js +13 -0
  98. package/dist/tools/delete-memory/cli-parser.js.map +1 -0
  99. package/dist/tools/delete-memory/executor.d.ts +13 -0
  100. package/dist/tools/delete-memory/executor.d.ts.map +1 -0
  101. package/dist/tools/delete-memory/executor.js +40 -0
  102. package/dist/tools/delete-memory/executor.js.map +1 -0
  103. package/dist/tools/delete-memory/index.d.ts +3 -0
  104. package/dist/tools/delete-memory/index.d.ts.map +1 -0
  105. package/dist/tools/delete-memory/index.js +24 -0
  106. package/dist/tools/delete-memory/index.js.map +1 -0
  107. package/dist/tools/export-memory/cli-parser.d.ts +2 -0
  108. package/dist/tools/export-memory/cli-parser.d.ts.map +1 -0
  109. package/dist/tools/export-memory/cli-parser.js +34 -0
  110. package/dist/tools/export-memory/cli-parser.js.map +1 -0
  111. package/dist/tools/export-memory/executor.d.ts +10 -0
  112. package/dist/tools/export-memory/executor.d.ts.map +1 -0
  113. package/dist/tools/export-memory/executor.js +41 -0
  114. package/dist/tools/export-memory/executor.js.map +1 -0
  115. package/dist/tools/export-memory/index.d.ts +4 -0
  116. package/dist/tools/export-memory/index.d.ts.map +1 -0
  117. package/dist/tools/export-memory/index.js +99 -0
  118. package/dist/tools/export-memory/index.js.map +1 -0
  119. package/dist/tools/import-memory/cli-parser.d.ts +2 -0
  120. package/dist/tools/import-memory/cli-parser.d.ts.map +1 -0
  121. package/dist/tools/import-memory/cli-parser.js +25 -0
  122. package/dist/tools/import-memory/cli-parser.js.map +1 -0
  123. package/dist/tools/import-memory/executor.d.ts +8 -0
  124. package/dist/tools/import-memory/executor.d.ts.map +1 -0
  125. package/dist/tools/import-memory/executor.js +31 -0
  126. package/dist/tools/import-memory/executor.js.map +1 -0
  127. package/dist/tools/import-memory/index.d.ts +4 -0
  128. package/dist/tools/import-memory/index.d.ts.map +1 -0
  129. package/dist/tools/import-memory/index.js +70 -0
  130. package/dist/tools/import-memory/index.js.map +1 -0
  131. package/dist/tools/index.d.ts +14 -0
  132. package/dist/tools/index.d.ts.map +1 -0
  133. package/dist/tools/index.js +48 -0
  134. package/dist/tools/index.js.map +1 -0
  135. package/dist/tools/memory-graphql/cli-parser.d.ts +6 -0
  136. package/dist/tools/memory-graphql/cli-parser.d.ts.map +1 -0
  137. package/dist/tools/memory-graphql/cli-parser.js +24 -0
  138. package/dist/tools/memory-graphql/cli-parser.js.map +1 -0
  139. package/dist/tools/memory-graphql/executor.d.ts +18 -0
  140. package/dist/tools/memory-graphql/executor.d.ts.map +1 -0
  141. package/dist/tools/memory-graphql/executor.js +53 -0
  142. package/dist/tools/memory-graphql/executor.js.map +1 -0
  143. package/dist/tools/memory-graphql/index.d.ts +3 -0
  144. package/dist/tools/memory-graphql/index.d.ts.map +1 -0
  145. package/dist/tools/memory-graphql/index.js +73 -0
  146. package/dist/tools/memory-graphql/index.js.map +1 -0
  147. package/dist/tools/memory-stats/cli-parser.d.ts +2 -0
  148. package/dist/tools/memory-stats/cli-parser.d.ts.map +1 -0
  149. package/dist/tools/memory-stats/cli-parser.js +8 -0
  150. package/dist/tools/memory-stats/cli-parser.js.map +1 -0
  151. package/dist/tools/memory-stats/executor.d.ts +4 -0
  152. package/dist/tools/memory-stats/executor.d.ts.map +1 -0
  153. package/dist/tools/memory-stats/executor.js +4 -0
  154. package/dist/tools/memory-stats/executor.js.map +1 -0
  155. package/dist/tools/memory-stats/index.d.ts +3 -0
  156. package/dist/tools/memory-stats/index.d.ts.map +1 -0
  157. package/dist/tools/memory-stats/index.js +15 -0
  158. package/dist/tools/memory-stats/index.js.map +1 -0
  159. package/dist/tools/search-memory/cli-parser.d.ts +2 -0
  160. package/dist/tools/search-memory/cli-parser.d.ts.map +1 -0
  161. package/dist/tools/search-memory/cli-parser.js +56 -0
  162. package/dist/tools/search-memory/cli-parser.js.map +1 -0
  163. package/dist/tools/search-memory/executor.d.ts +36 -0
  164. package/dist/tools/search-memory/executor.d.ts.map +1 -0
  165. package/dist/tools/search-memory/executor.js +83 -0
  166. package/dist/tools/search-memory/executor.js.map +1 -0
  167. package/dist/tools/search-memory/index.d.ts +3 -0
  168. package/dist/tools/search-memory/index.d.ts.map +1 -0
  169. package/dist/tools/search-memory/index.js +89 -0
  170. package/dist/tools/search-memory/index.js.map +1 -0
  171. package/dist/tools/store-memory/cli-parser.d.ts +2 -0
  172. package/dist/tools/store-memory/cli-parser.d.ts.map +1 -0
  173. package/dist/tools/store-memory/cli-parser.js +21 -0
  174. package/dist/tools/store-memory/cli-parser.js.map +1 -0
  175. package/dist/tools/store-memory/executor.d.ts +16 -0
  176. package/dist/tools/store-memory/executor.d.ts.map +1 -0
  177. package/dist/tools/store-memory/executor.js +100 -0
  178. package/dist/tools/store-memory/executor.js.map +1 -0
  179. package/dist/tools/store-memory/index.d.ts +3 -0
  180. package/dist/tools/store-memory/index.d.ts.map +1 -0
  181. package/dist/tools/store-memory/index.js +68 -0
  182. package/dist/tools/store-memory/index.js.map +1 -0
  183. package/dist/tools/sync-memory/cli-parser.d.ts +1 -0
  184. package/dist/tools/sync-memory/cli-parser.d.ts.map +1 -0
  185. package/dist/tools/sync-memory/cli-parser.js +2 -0
  186. package/dist/tools/sync-memory/cli-parser.js.map +1 -0
  187. package/dist/tools/sync-memory/executor.d.ts +1 -0
  188. package/dist/tools/sync-memory/executor.d.ts.map +1 -0
  189. package/dist/tools/sync-memory/executor.js +2 -0
  190. package/dist/tools/sync-memory/executor.js.map +1 -0
  191. package/dist/tools/sync-memory/index.d.ts +1 -0
  192. package/dist/tools/sync-memory/index.d.ts.map +1 -0
  193. package/dist/tools/sync-memory/index.js +2 -0
  194. package/dist/tools/sync-memory/index.js.map +1 -0
  195. package/dist/tools/update-memory/cli-parser.d.ts +2 -0
  196. package/dist/tools/update-memory/cli-parser.d.ts.map +1 -0
  197. package/dist/tools/update-memory/cli-parser.js +17 -0
  198. package/dist/tools/update-memory/cli-parser.js.map +1 -0
  199. package/dist/tools/update-memory/executor.d.ts +16 -0
  200. package/dist/tools/update-memory/executor.d.ts.map +1 -0
  201. package/dist/tools/update-memory/executor.js +59 -0
  202. package/dist/tools/update-memory/executor.js.map +1 -0
  203. package/dist/tools/update-memory/index.d.ts +3 -0
  204. package/dist/tools/update-memory/index.d.ts.map +1 -0
  205. package/dist/tools/update-memory/index.js +30 -0
  206. package/dist/tools/update-memory/index.js.map +1 -0
  207. package/dist/transports/streamable-http.d.ts +38 -0
  208. package/dist/transports/streamable-http.d.ts.map +1 -0
  209. package/dist/transports/streamable-http.js +209 -0
  210. package/dist/transports/streamable-http.js.map +1 -0
  211. package/dist/types/tools.d.ts +79 -0
  212. package/dist/types/tools.d.ts.map +1 -0
  213. package/dist/types/tools.js +3 -0
  214. package/dist/types/tools.js.map +1 -0
  215. package/dist/utils/cli-parser.d.ts +12 -0
  216. package/dist/utils/cli-parser.d.ts.map +1 -0
  217. package/dist/utils/cli-parser.js +43 -0
  218. package/dist/utils/cli-parser.js.map +1 -0
  219. package/dist/utils/config.d.ts +95 -0
  220. package/dist/utils/config.d.ts.map +1 -0
  221. package/dist/utils/config.js +176 -0
  222. package/dist/utils/config.js.map +1 -0
  223. package/dist/utils/db-integrity-check.d.ts +22 -0
  224. package/dist/utils/db-integrity-check.d.ts.map +1 -0
  225. package/dist/utils/db-integrity-check.js +69 -0
  226. package/dist/utils/db-integrity-check.js.map +1 -0
  227. package/dist/utils/debug.d.ts +25 -0
  228. package/dist/utils/debug.d.ts.map +1 -0
  229. package/dist/utils/debug.js +67 -0
  230. package/dist/utils/debug.js.map +1 -0
  231. package/dist/utils/help-generator.d.ts +18 -0
  232. package/dist/utils/help-generator.d.ts.map +1 -0
  233. package/dist/utils/help-generator.js +81 -0
  234. package/dist/utils/help-generator.js.map +1 -0
  235. package/dist/utils/json-parser.d.ts +12 -0
  236. package/dist/utils/json-parser.d.ts.map +1 -0
  237. package/dist/utils/json-parser.js +52 -0
  238. package/dist/utils/json-parser.js.map +1 -0
  239. package/dist/utils/mcp-config.d.ts +12 -0
  240. package/dist/utils/mcp-config.d.ts.map +1 -0
  241. package/dist/utils/mcp-config.js +64 -0
  242. package/dist/utils/mcp-config.js.map +1 -0
  243. package/dist/web-server.d.ts +3 -0
  244. package/dist/web-server.d.ts.map +1 -0
  245. package/dist/web-server.js +265 -0
  246. package/dist/web-server.js.map +1 -0
  247. package/package.json +75 -0
@@ -0,0 +1,862 @@
1
+ import Database from 'better-sqlite3';
2
+ import { createHash } from 'crypto';
3
+ import { hostname } from 'os';
4
+ import { resolve } from 'path';
5
+ import { readFileSync } from 'fs';
6
+ import { fileURLToPath } from 'url';
7
+ import { dirname, join } from 'path';
8
+ import { debugLog, debugLogHash } from '../utils/debug.js';
9
+ import { runMigrations } from './migrations.js';
10
+ import { DatabaseOptimizer } from './database-optimizer.js';
11
+ import { BackupService } from './backup-service.js';
12
+ import { getMCPConfigPaths } from '../utils/mcp-config.js';
13
+ import { getBackupConfig, getConfigPath } from '../utils/config.js';
14
+ // Get package version for export metadata
15
+ function getPackageVersion() {
16
+ try {
17
+ const __filename = fileURLToPath(import.meta.url);
18
+ const __dirname = dirname(__filename);
19
+ const packagePath = join(__dirname, '..', '..', 'package.json');
20
+ const packageJson = JSON.parse(readFileSync(packagePath, 'utf-8'));
21
+ return packageJson.version || '1.0.0';
22
+ }
23
+ catch {
24
+ return '1.0.0'; // Fallback version
25
+ }
26
+ }
27
+ /**
28
+ * Memory Service for persistent storage and retrieval of memories with tagging and relationships.
29
+ * Based on SQLite with FTS (Full Text Search) for efficient querying.
30
+ */
31
+ export class MemoryService {
32
+ db = null;
33
+ dbPath;
34
+ resolvedDbPath;
35
+ stmts;
36
+ maxContentSize = 1024 * 1024; // 1MB default
37
+ backup;
38
+ constructor(dbPath = 'memory.db', maxContentSize) {
39
+ this.dbPath = dbPath;
40
+ if (maxContentSize)
41
+ this.maxContentSize = maxContentSize;
42
+ // Cache resolved path once
43
+ this.resolvedDbPath = resolve(dbPath);
44
+ }
45
+ initialize() {
46
+ try {
47
+ this.db = new Database(this.dbPath);
48
+ this.initDb();
49
+ // Configure backup service after db is ready
50
+ const backupConfig = getBackupConfig();
51
+ if (backupConfig.path) {
52
+ this.backup = new BackupService({
53
+ backupPath: backupConfig.path,
54
+ autoBackupInterval: backupConfig.interval,
55
+ maxBackups: backupConfig.keep,
56
+ source: backupConfig.source,
57
+ getBackupData: () => this.exportMemories()
58
+ });
59
+ this.backup.initialize();
60
+ }
61
+ debugLog('MemoryService initialized with database:', this.dbPath);
62
+ }
63
+ catch (error) {
64
+ throw new Error(`Failed to initialize database: ${error.message}`);
65
+ }
66
+ }
67
+ initDb() {
68
+ if (!this.db) {
69
+ throw new Error('Database not initialized');
70
+ }
71
+ // Apply SQLite optimizations first
72
+ DatabaseOptimizer.applyOptimizations(this.db);
73
+ // Create base tables (if they don't exist)
74
+ this.db.exec(`
75
+ CREATE TABLE IF NOT EXISTS memories (
76
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
77
+ content TEXT NOT NULL,
78
+ created_at TEXT,
79
+ hash TEXT UNIQUE
80
+ )
81
+ `);
82
+ // Create relationships table for linking memories
83
+ this.db.exec(`
84
+ CREATE TABLE IF NOT EXISTS relationships (
85
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
86
+ from_memory_id INTEGER,
87
+ to_memory_id INTEGER,
88
+ relationship_type TEXT DEFAULT 'related',
89
+ created_at TEXT,
90
+ FOREIGN KEY (from_memory_id) REFERENCES memories (id) ON DELETE CASCADE,
91
+ FOREIGN KEY (to_memory_id) REFERENCES memories (id) ON DELETE CASCADE,
92
+ UNIQUE(from_memory_id, to_memory_id, relationship_type)
93
+ )
94
+ `);
95
+ // Create normalized tags table for efficient tag queries
96
+ this.db.exec(`
97
+ CREATE TABLE IF NOT EXISTS tags (
98
+ memory_id INTEGER NOT NULL,
99
+ tag TEXT NOT NULL,
100
+ FOREIGN KEY (memory_id) REFERENCES memories (id) ON DELETE CASCADE,
101
+ PRIMARY KEY (memory_id, tag)
102
+ )
103
+ `);
104
+ // Create indexes for performance
105
+ this.db.exec(`
106
+ CREATE INDEX IF NOT EXISTS idx_tags_tag ON tags(tag);
107
+ CREATE INDEX IF NOT EXISTS idx_tags_memory_id ON tags(memory_id);
108
+ CREATE INDEX IF NOT EXISTS idx_memories_created_at ON memories(created_at DESC);
109
+ CREATE INDEX IF NOT EXISTS idx_memories_hash ON memories(hash);
110
+ CREATE INDEX IF NOT EXISTS idx_relationships_from ON relationships(from_memory_id);
111
+ CREATE INDEX IF NOT EXISTS idx_relationships_to ON relationships(to_memory_id);
112
+ CREATE INDEX IF NOT EXISTS idx_relationships_composite ON relationships(from_memory_id, to_memory_id);
113
+ `);
114
+ // Create FTS table for fast text search (content only, tags in separate table)
115
+ this.db.exec(`
116
+ CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts
117
+ USING fts5(content, content='memories', content_rowid='id')
118
+ `);
119
+ // Create trigger to automatically update FTS when memories are inserted
120
+ this.db.exec(`
121
+ CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
122
+ INSERT INTO memories_fts (rowid, content)
123
+ VALUES (new.id, new.content);
124
+ END;
125
+ `);
126
+ // Create trigger to automatically update FTS when memories are updated
127
+ // Note: External content FTS5 tables don't support UPDATE, must DELETE+INSERT
128
+ this.db.exec(`
129
+ CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
130
+ DELETE FROM memories_fts WHERE rowid = old.id;
131
+ INSERT INTO memories_fts (rowid, content) VALUES (new.id, new.content);
132
+ END;
133
+ `);
134
+ // Create trigger to automatically delete from FTS when memories are deleted
135
+ this.db.exec(`
136
+ CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
137
+ DELETE FROM memories_fts WHERE rowid = old.id;
138
+ END;
139
+ `);
140
+ // Run migrations (creates tags table, indexes, etc.)
141
+ // This is where all the magic happens - automatic, tracked, safe
142
+ runMigrations(this.db, this.dbPath);
143
+ // Optimize FTS after migrations
144
+ DatabaseOptimizer.optimizeFTS(this.db);
145
+ // Prepare statements for better performance
146
+ this.prepareStatements();
147
+ debugLog('MemoryService: Database initialized successfully');
148
+ }
149
+ prepareStatements() {
150
+ this.stmts = {
151
+ // Memory operations
152
+ insert: this.db.prepare(`
153
+ INSERT INTO memories (content, created_at, hash)
154
+ VALUES (?, ?, ?)
155
+ `),
156
+ getMemoryById: this.db.prepare(`
157
+ SELECT * FROM memories WHERE id = ?
158
+ `),
159
+ getMemoryByHash: this.db.prepare(`
160
+ SELECT * FROM memories WHERE hash = ?
161
+ `),
162
+ getRecent: this.db.prepare(`
163
+ SELECT * FROM memories
164
+ ORDER BY created_at DESC
165
+ LIMIT ?
166
+ `),
167
+ deleteByHash: this.db.prepare(`
168
+ DELETE FROM memories WHERE hash = ?
169
+ `),
170
+ updateMemory: this.db.prepare(`
171
+ UPDATE memories
172
+ SET content = ?, hash = ?
173
+ WHERE id = ?
174
+ `),
175
+ // Tag operations (NEW)
176
+ insertTag: this.db.prepare(`
177
+ INSERT OR IGNORE INTO tags (memory_id, tag) VALUES (?, ?)
178
+ `),
179
+ getTagsForMemory: this.db.prepare(`
180
+ SELECT tag FROM tags WHERE memory_id = ? ORDER BY tag
181
+ `),
182
+ deleteTagsForMemory: this.db.prepare(`
183
+ DELETE FROM tags WHERE memory_id = ?
184
+ `),
185
+ searchByTag: this.db.prepare(`
186
+ SELECT DISTINCT m.*
187
+ FROM memories m
188
+ INNER JOIN tags t ON m.id = t.memory_id
189
+ WHERE t.tag = ?
190
+ ORDER BY m.created_at DESC
191
+ LIMIT ?
192
+ `),
193
+ deleteByTag: this.db.prepare(`
194
+ DELETE FROM memories
195
+ WHERE id IN (SELECT memory_id FROM tags WHERE tag = ?)
196
+ `),
197
+ // FTS search with BM25 ranking for relevance scoring
198
+ searchText: this.db.prepare(`
199
+ SELECT m.*, bm25(memories_fts) as rank
200
+ FROM memories m
201
+ JOIN memories_fts fts ON m.id = fts.rowid
202
+ WHERE memories_fts MATCH ?
203
+ ORDER BY rank, m.created_at DESC
204
+ LIMIT ?
205
+ `),
206
+ // Legacy tag search (no longer used after migration 2)
207
+ searchTagsLegacy: this.db.prepare(`
208
+ SELECT * FROM memories
209
+ WHERE 1=0
210
+ ORDER BY created_at DESC
211
+ LIMIT ?
212
+ `),
213
+ // Relationship operations
214
+ insertRelationship: this.db.prepare(`
215
+ INSERT INTO relationships (from_memory_id, to_memory_id, relationship_type, created_at)
216
+ VALUES (?, ?, ?, ?)
217
+ `),
218
+ getRelated: this.db.prepare(`
219
+ SELECT m.*, r.relationship_type
220
+ FROM memories m
221
+ JOIN relationships r ON (m.id = r.to_memory_id OR m.id = r.from_memory_id)
222
+ WHERE (r.from_memory_id = ? OR r.to_memory_id = ?) AND m.id != ?
223
+ ORDER BY r.created_at DESC
224
+ LIMIT ?
225
+ `),
226
+ // Stats
227
+ getStats: this.db.prepare(`
228
+ SELECT COUNT(*) as count FROM memories
229
+ `),
230
+ getRelationshipStats: this.db.prepare(`
231
+ SELECT COUNT(*) as count FROM relationships
232
+ `)
233
+ };
234
+ }
235
+ /**
236
+ * Store a memory with optional tags
237
+ */
238
+ store(content, tags = []) {
239
+ // Validate content size
240
+ if (content.length > this.maxContentSize) {
241
+ throw new Error(`Content exceeds maximum size of ${this.maxContentSize} characters`);
242
+ }
243
+ const hash = createHash('md5').update(content).digest('hex');
244
+ const createdAt = new Date().toISOString();
245
+ try {
246
+ if (!this.db) {
247
+ throw new Error('Database not initialized');
248
+ }
249
+ // Use transaction for atomicity and performance
250
+ const insertMemory = this.db.transaction(() => {
251
+ // Insert memory (tags stored in normalized 'tags' table since v2.0)
252
+ const result = this.stmts.insert.run(content, createdAt, hash);
253
+ const memoryId = result.lastInsertRowid;
254
+ // Insert tags into normalized tags table
255
+ for (const tag of tags) {
256
+ const normalizedTag = tag.trim().toLowerCase();
257
+ if (normalizedTag) {
258
+ this.stmts.insertTag.run(memoryId, normalizedTag);
259
+ }
260
+ }
261
+ return hash;
262
+ });
263
+ const resultHash = insertMemory();
264
+ debugLogHash('MemoryService: Stored memory with hash:', hash);
265
+ // Backup if needed (lazy, throttled)
266
+ this.backup?.backupIfNeeded();
267
+ return resultHash;
268
+ }
269
+ catch (error) {
270
+ if (error.code === 'SQLITE_CONSTRAINT_UNIQUE') {
271
+ debugLogHash('MemoryService: Memory already exists with hash:', hash);
272
+ return hash; // Already exists
273
+ }
274
+ debugLog('MemoryService: Error storing memory:', error);
275
+ throw error;
276
+ }
277
+ }
278
+ /**
279
+ * Search memories by content or tags
280
+ */
281
+ search(query, tags, limit = 10, daysAgo, startDate, endDate, minRelevance) {
282
+ let results;
283
+ // Calculate date boundaries for filtering
284
+ let minDate;
285
+ let maxDate;
286
+ if (daysAgo !== undefined && daysAgo >= 0) {
287
+ // Convert daysAgo to a start date (N days ago from now)
288
+ // Use UTC to match how SQLite stores timestamps
289
+ const now = new Date();
290
+ minDate = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() - daysAgo, 0, 0, 0, 0 // Start of day in UTC
291
+ ));
292
+ }
293
+ if (startDate) {
294
+ // Parse start date (supports both YYYY-MM-DD and full ISO strings)
295
+ const parsed = new Date(startDate);
296
+ if (!isNaN(parsed.getTime())) {
297
+ minDate = parsed;
298
+ }
299
+ }
300
+ if (endDate) {
301
+ // Parse end date (supports both YYYY-MM-DD and full ISO strings)
302
+ const parsed = new Date(endDate);
303
+ if (!isNaN(parsed.getTime())) {
304
+ maxDate = parsed;
305
+ // Set to end of day if only date provided (no time component)
306
+ if (endDate.length === 10) { // YYYY-MM-DD format
307
+ maxDate.setHours(23, 59, 59, 999);
308
+ }
309
+ }
310
+ }
311
+ if (query) {
312
+ // Use FTS for text search
313
+ let ftsResults;
314
+ // Tokenize query into words and join with OR for flexible matching
315
+ // This allows: "git reset" to match memories with either "git" OR "reset"
316
+ const words = query
317
+ .split(/\s+/)
318
+ .map(word => word.trim())
319
+ .filter(word => word.length > 0)
320
+ .map(word => `"${word.replace(/"/g, '""')}"`); // Quote each word for exact word matching
321
+ const escapedQuery = words.length > 0 ? words.join(' OR ') : query.replace(/"/g, '""');
322
+ // Fetch more results initially if we need to filter by tags or relevance
323
+ const fetchLimit = (tags && tags.length > 0) || minRelevance !== undefined ? limit * 5 : limit * 2;
324
+ ftsResults = this.stmts.searchText.all(escapedQuery, fetchLimit);
325
+ // Filter by relevance score if threshold provided
326
+ if (minRelevance !== undefined && ftsResults.length > 0) {
327
+ // BM25 returns negative scores (more negative = better match)
328
+ // Normalize to 0-1 range where 1 is best match, 0 is worst
329
+ const scores = ftsResults.map(r => Math.abs(r.rank));
330
+ const maxScore = Math.max(...scores);
331
+ const minScore = Math.min(...scores);
332
+ const range = maxScore - minScore || 1;
333
+ ftsResults = ftsResults.filter((row) => {
334
+ const normalizedScore = 1 - ((Math.abs(row.rank) - minScore) / range);
335
+ row.relevance = normalizedScore; // Store for debugging/display
336
+ return normalizedScore >= minRelevance;
337
+ });
338
+ }
339
+ // Hydrate with tags from tags table
340
+ results = ftsResults.map((row) => {
341
+ const tagRows = this.stmts.getTagsForMemory.all(row.id);
342
+ return {
343
+ ...row,
344
+ tags: tagRows.map(t => t.tag)
345
+ };
346
+ });
347
+ }
348
+ else if (tags && tags.length > 0) {
349
+ // Fast indexed tag search using normalized tags table
350
+ const normalizedTag = tags[0].trim().toLowerCase();
351
+ const tagResults = this.stmts.searchByTag.all(normalizedTag, limit * 2); // Fetch more to allow for filtering
352
+ // Hydrate with all tags for each memory
353
+ results = tagResults.map((row) => {
354
+ const tagRows = this.stmts.getTagsForMemory.all(row.id);
355
+ return {
356
+ ...row,
357
+ tags: tagRows.map(t => t.tag)
358
+ };
359
+ });
360
+ }
361
+ else {
362
+ // Get recent memories
363
+ const recentResults = this.stmts.getRecent.all(limit * 2); // Fetch more to allow for filtering
364
+ // Hydrate with tags
365
+ results = recentResults.map((row) => {
366
+ const tagRows = this.stmts.getTagsForMemory.all(row.id);
367
+ return {
368
+ ...row,
369
+ tags: tagRows.map(t => t.tag)
370
+ };
371
+ });
372
+ }
373
+ // Apply date filtering if needed
374
+ if (minDate || maxDate) {
375
+ results = results.filter((row) => {
376
+ const createdAt = new Date(row.created_at);
377
+ if (minDate && createdAt < minDate)
378
+ return false;
379
+ if (maxDate && createdAt > maxDate)
380
+ return false;
381
+ return true;
382
+ });
383
+ }
384
+ // Apply limit after filtering
385
+ results = results.slice(0, limit);
386
+ // Convert to MemoryEntry format
387
+ const memories = results.map(row => ({
388
+ id: row.id,
389
+ content: row.content,
390
+ tags: row.tags || [],
391
+ createdAt: row.created_at,
392
+ hash: row.hash,
393
+ ...(row.relevance !== undefined && { relevance: row.relevance })
394
+ }));
395
+ debugLog('MemoryService: Search returned', memories.length, 'results');
396
+ return memories;
397
+ }
398
+ /**
399
+ * Delete a memory by hash
400
+ */
401
+ delete(hash) {
402
+ const result = this.stmts.deleteByHash.run(hash);
403
+ let deleted = result.changes > 0;
404
+ // Fallback: If hash lookup failed, force full table scan (bypasses corrupted index)
405
+ // The + prefix tells SQLite to not use the index on hash column
406
+ if (!deleted && this.db) {
407
+ debugLogHash('MemoryService: Hash lookup failed, trying fallback full table scan for:', hash);
408
+ const orphaned = this.db.prepare('SELECT id FROM memories WHERE +hash = ?').get(hash);
409
+ if (orphaned) {
410
+ // DIAGNOSTIC: This indicates hash index corruption - log details for investigation
411
+ console.error('⚠️ HASH INDEX CORRUPTION DETECTED ⚠️');
412
+ console.error('Hash:', hash);
413
+ console.error('Memory ID:', orphaned.id);
414
+ console.error('This suggests index corruption occurred during a previous operation.');
415
+ console.error('Please report this with the hash and operation that preceded it.');
416
+ debugLog('MemoryService: Found orphaned memory with corrupted hash index, deleting by ID:', orphaned.id);
417
+ const fallbackResult = this.db.prepare('DELETE FROM memories WHERE id = ?').run(orphaned.id);
418
+ deleted = fallbackResult.changes > 0;
419
+ }
420
+ }
421
+ debugLogHash('MemoryService: Delete by hash', hash, deleted ? 'success' : 'not found');
422
+ // Backup if needed (lazy, throttled)
423
+ if (deleted)
424
+ this.backup?.backupIfNeeded();
425
+ return deleted;
426
+ }
427
+ /**
428
+ * Delete memories by tag
429
+ */
430
+ deleteByTag(tag) {
431
+ if (!this.db) {
432
+ throw new Error('Database not initialized');
433
+ }
434
+ const normalizedTag = tag.trim().toLowerCase();
435
+ const result = this.stmts.deleteByTag.run(normalizedTag);
436
+ debugLog('MemoryService: Deleted', result.changes, 'memories with tag:', normalizedTag);
437
+ // Backup if needed (lazy, throttled)
438
+ if (result.changes > 0)
439
+ this.backup?.backupIfNeeded();
440
+ return result.changes;
441
+ }
442
+ /**
443
+ * Bulk link memories in a single transaction for performance
444
+ * Returns the number of relationships successfully created
445
+ */
446
+ linkMemoriesBulk(relationships) {
447
+ if (!this.db) {
448
+ throw new Error('Database not initialized');
449
+ }
450
+ if (relationships.length === 0) {
451
+ return 0;
452
+ }
453
+ const insertBulk = this.db.transaction(() => {
454
+ let count = 0;
455
+ const createdAt = new Date().toISOString();
456
+ for (const rel of relationships) {
457
+ const fromMemory = this.stmts.getMemoryByHash.get(rel.fromHash);
458
+ const toMemory = this.stmts.getMemoryByHash.get(rel.toHash);
459
+ if (!fromMemory || !toMemory)
460
+ continue;
461
+ try {
462
+ this.stmts.insertRelationship.run(fromMemory.id, toMemory.id, rel.relationshipType || 'related', createdAt);
463
+ count++;
464
+ }
465
+ catch (error) {
466
+ if (error.code === 'SQLITE_CONSTRAINT_UNIQUE') {
467
+ // Skip duplicates silently
468
+ continue;
469
+ }
470
+ throw error;
471
+ }
472
+ }
473
+ return count;
474
+ });
475
+ const created = insertBulk();
476
+ debugLog('MemoryService: Bulk linked', created, 'relationships');
477
+ return created;
478
+ }
479
+ /**
480
+ * Link two memories with a relationship
481
+ */
482
+ linkMemories(fromHash, toHash, relationshipType = 'related') {
483
+ const fromMemory = this.stmts.getMemoryByHash.get(fromHash);
484
+ const toMemory = this.stmts.getMemoryByHash.get(toHash);
485
+ if (!fromMemory || !toMemory) {
486
+ throw new Error('One or both memories not found');
487
+ }
488
+ const createdAt = new Date().toISOString();
489
+ try {
490
+ this.stmts.insertRelationship.run(fromMemory.id, toMemory.id, relationshipType, createdAt);
491
+ debugLogHash('MemoryService: Linked memories:', fromHash, 'to', toHash);
492
+ return true;
493
+ }
494
+ catch (error) {
495
+ if (error.code === 'SQLITE_CONSTRAINT_UNIQUE') {
496
+ debugLog('MemoryService: Relationship already exists');
497
+ return false; // Relationship already exists
498
+ }
499
+ throw error;
500
+ }
501
+ }
502
+ /**
503
+ * Get memories related to a specific memory
504
+ */
505
+ getRelated(hash, limit = 10) {
506
+ const memory = this.stmts.getMemoryByHash.get(hash);
507
+ if (!memory) {
508
+ debugLogHash('MemoryService: Memory not found for getRelated:', hash);
509
+ return [];
510
+ }
511
+ const results = this.stmts.getRelated.all(memory.id, memory.id, memory.id, limit);
512
+ const related = results.map((row) => {
513
+ // Hydrate tags from tags table
514
+ const tagRows = this.stmts.getTagsForMemory.all(row.id);
515
+ return {
516
+ id: row.id,
517
+ content: row.content,
518
+ tags: tagRows.map(t => t.tag),
519
+ createdAt: row.created_at,
520
+ hash: row.hash,
521
+ relationshipType: row.relationship_type
522
+ };
523
+ });
524
+ debugLog('MemoryService: Found', related.length, 'related memories');
525
+ return related;
526
+ }
527
+ /**
528
+ * Get statistics about the memory database
529
+ */
530
+ stats() {
531
+ if (!this.db) {
532
+ throw new Error('Database not initialized');
533
+ }
534
+ const memoryCount = this.stmts.getStats.get();
535
+ const relationshipCount = this.stmts.getRelationshipStats.get();
536
+ // Get current schema version from migrations table
537
+ const versionResult = this.db.prepare('SELECT MAX(version) as version FROM schema_migrations').get();
538
+ const schemaVersion = versionResult?.version || 0;
539
+ // Get backup config from unified config system
540
+ const backupConfig = getBackupConfig();
541
+ const stats = {
542
+ version: getPackageVersion(),
543
+ totalMemories: memoryCount.count,
544
+ totalRelationships: relationshipCount.count,
545
+ dbSize: this.db.pragma('page_size', { simple: true }) *
546
+ this.db.pragma('page_count', { simple: true }),
547
+ dbPath: this.dbPath,
548
+ resolvedPath: this.resolvedDbPath.replace(/\\/g, '/'), // Normalize to forward slashes
549
+ schemaVersion,
550
+ configPath: getConfigPath().replace(/\\/g, '/'), // Path to config.json
551
+ };
552
+ // Add backup information if backup service is configured
553
+ if (this.backup) {
554
+ const lastBackupAge = this.backup.getTimeSinceLastBackup();
555
+ const backupInterval = backupConfig.interval;
556
+ stats.backupEnabled = true;
557
+ stats.backupPath = backupConfig.path;
558
+ stats.backupCount = this.backup.getBackupCount();
559
+ stats.lastBackupAge = lastBackupAge >= 0 ? lastBackupAge : undefined;
560
+ // Calculate next backup time
561
+ if (backupInterval > 0 && lastBackupAge >= 0) {
562
+ const nextBackup = backupInterval - lastBackupAge;
563
+ stats.nextBackupIn = nextBackup > 0 ? nextBackup : -1; // -1 means will backup on next write
564
+ }
565
+ else if (backupInterval === 0) {
566
+ stats.nextBackupIn = -1; // Will backup on every write
567
+ }
568
+ }
569
+ // Add MCP configuration file paths (only existing ones)
570
+ stats.mcpConfigPaths = getMCPConfigPaths().filter(p => p.exists);
571
+ debugLog('MemoryService: Stats:', stats);
572
+ return stats;
573
+ }
574
+ /**
575
+ * Update an existing memory by hash
576
+ * @param hash The hash of the memory to update
577
+ * @param newContent The new content for the memory
578
+ * @param newTags Optional new tags (if provided, replaces all existing tags)
579
+ * @returns The new hash if successful, null if memory not found
580
+ */
581
+ update(hash, newContent, newTags) {
582
+ // Validate content size
583
+ if (newContent.length > this.maxContentSize) {
584
+ throw new Error(`Content exceeds maximum size of ${this.maxContentSize} characters`);
585
+ }
586
+ if (!this.db) {
587
+ throw new Error('Database not initialized');
588
+ }
589
+ // Find the existing memory
590
+ let existing = this.stmts.getMemoryByHash.get(hash);
591
+ // Fallback: If hash lookup failed, force full table scan (bypasses corrupted index)
592
+ // The + prefix tells SQLite to not use the index on hash column
593
+ if (!existing) {
594
+ debugLogHash('MemoryService: Hash lookup failed, trying fallback full table scan for:', hash);
595
+ existing = this.db.prepare('SELECT * FROM memories WHERE +hash = ?').get(hash);
596
+ if (existing) {
597
+ // DIAGNOSTIC: This indicates hash index corruption - log details for investigation
598
+ console.error('⚠️ HASH INDEX CORRUPTION DETECTED ⚠️');
599
+ console.error('Hash:', hash);
600
+ console.error('Memory ID:', existing.id);
601
+ console.error('Operation: UPDATE');
602
+ console.error('This suggests index corruption occurred during a previous operation.');
603
+ console.error('Please report this with the hash and operation that preceded it.');
604
+ debugLog('MemoryService: Found orphaned memory with corrupted hash index, ID:', existing.id);
605
+ }
606
+ }
607
+ if (!existing) {
608
+ debugLogHash('MemoryService: Memory not found for update:', hash);
609
+ return null;
610
+ }
611
+ // Calculate new hash
612
+ const newHash = createHash('md5').update(newContent).digest('hex');
613
+ try {
614
+ // Use transaction for atomicity
615
+ const updateMemory = this.db.transaction(() => {
616
+ // Update memory content (FTS will be updated automatically by trigger)
617
+ this.stmts.updateMemory.run(newContent, newHash, existing.id);
618
+ // Update tags if provided
619
+ if (newTags !== undefined) {
620
+ // Delete old tags
621
+ this.stmts.deleteTagsForMemory.run(existing.id);
622
+ // Insert new tags
623
+ for (const tag of newTags) {
624
+ const normalizedTag = tag.trim().toLowerCase();
625
+ if (normalizedTag) {
626
+ this.stmts.insertTag.run(existing.id, normalizedTag);
627
+ }
628
+ }
629
+ }
630
+ return newHash;
631
+ });
632
+ const resultHash = updateMemory();
633
+ debugLogHash('MemoryService: Updated memory from hash:', hash, 'to new hash:', resultHash);
634
+ // Backup if needed (lazy, throttled)
635
+ this.backup?.backupIfNeeded();
636
+ return resultHash;
637
+ }
638
+ catch (error) {
639
+ if (error.code === 'SQLITE_CONSTRAINT_UNIQUE') {
640
+ throw new Error(`Cannot update: a memory with the new content already exists (hash: ${newHash})`);
641
+ }
642
+ debugLog('MemoryService: Error updating memory:', error);
643
+ throw error;
644
+ }
645
+ }
646
+ /**
647
+ * Get memory by hash
648
+ */
649
+ getByHash(hash) {
650
+ const result = this.stmts.getMemoryByHash.get(hash);
651
+ if (!result) {
652
+ return null;
653
+ }
654
+ // Hydrate with tags from tags table
655
+ const tagRows = this.stmts.getTagsForMemory.all(result.id);
656
+ return {
657
+ id: result.id,
658
+ content: result.content,
659
+ tags: tagRows.map(t => t.tag),
660
+ createdAt: result.created_at,
661
+ hash: result.hash
662
+ };
663
+ }
664
+ /**
665
+ * Close database connection
666
+ */
667
+ close() {
668
+ if (this.db) {
669
+ this.db.close();
670
+ this.db = null;
671
+ debugLog('MemoryService: Database connection closed');
672
+ }
673
+ }
674
+ /**
675
+ * Create a manual backup
676
+ */
677
+ createBackup(label = 'manual') {
678
+ return this.backup?.backup(label) || null;
679
+ }
680
+ /**
681
+ * Export memories to JSON format with optional filtering
682
+ */
683
+ exportMemories(filters) {
684
+ if (!this.db) {
685
+ throw new Error('Database not initialized');
686
+ }
687
+ // Use existing search method to get memories
688
+ // Pass undefined for query to use tag search (if tags provided) or recent search (if no filters)
689
+ const memories = this.search(undefined, // query - let search decide based on tags
690
+ filters?.tags, filters?.limit || 1000, // default high limit for export
691
+ undefined, // daysAgo
692
+ filters?.startDate?.toISOString(), filters?.endDate?.toISOString());
693
+ // Get relationships for each memory
694
+ const exportedMemories = memories.map((memory) => {
695
+ const relationships = this.getMemoryRelationships(memory.id);
696
+ return {
697
+ id: memory.id,
698
+ content: memory.content,
699
+ tags: memory.tags,
700
+ createdAt: memory.createdAt,
701
+ hash: memory.hash,
702
+ relationships: relationships.length > 0 ? relationships.map(rel => ({
703
+ relatedMemoryHash: rel.relatedMemoryHash,
704
+ relatedMemoryId: rel.relatedMemoryId,
705
+ relationshipType: rel.relationshipType
706
+ })) : undefined
707
+ };
708
+ });
709
+ return {
710
+ exportedAt: new Date().toISOString(),
711
+ exportVersion: getPackageVersion(),
712
+ source: hostname(),
713
+ totalMemories: exportedMemories.length,
714
+ memories: exportedMemories
715
+ };
716
+ }
717
+ /**
718
+ * Import memories from JSON format
719
+ */
720
+ importMemories(jsonData, options) {
721
+ if (!this.db) {
722
+ throw new Error('Database not initialized');
723
+ }
724
+ const result = {
725
+ imported: 0,
726
+ skipped: 0,
727
+ errors: []
728
+ };
729
+ let exportData;
730
+ try {
731
+ exportData = JSON.parse(jsonData);
732
+ }
733
+ catch (error) {
734
+ throw new Error(`Invalid JSON format: ${error.message}`);
735
+ }
736
+ // Validate export format
737
+ if (!exportData.memories || !Array.isArray(exportData.memories)) {
738
+ throw new Error('Invalid export format: missing memories array');
739
+ }
740
+ debugLog(`Importing ${exportData.totalMemories} memories from export version ${exportData.exportVersion}`);
741
+ for (const memory of exportData.memories) {
742
+ try {
743
+ // Check for duplicates by hash
744
+ if (options?.skipDuplicates) {
745
+ const existing = this.getMemoryByHash(memory.hash);
746
+ if (existing) {
747
+ result.skipped++;
748
+ debugLog(`Skipped duplicate memory: ${memory.hash}`);
749
+ continue;
750
+ }
751
+ }
752
+ // Store memory (without relationships for now)
753
+ const stored = this.store(memory.content, memory.tags);
754
+ if (stored) {
755
+ result.imported++;
756
+ debugLog(`Imported memory: ${stored}`);
757
+ }
758
+ }
759
+ catch (error) {
760
+ result.errors.push({
761
+ memory: memory,
762
+ error: error.message
763
+ });
764
+ debugLog(`Error importing memory ${memory.hash}: ${error.message}`);
765
+ }
766
+ }
767
+ // Second pass: restore relationships
768
+ if (result.imported > 0) {
769
+ this.restoreRelationships(exportData.memories);
770
+ }
771
+ debugLog(`Import complete: ${result.imported} imported, ${result.skipped} skipped, ${result.errors.length} errors`);
772
+ // Trigger backup after import if enabled
773
+ if (this.backup && result.imported > 0) {
774
+ this.backup.backupIfNeeded();
775
+ }
776
+ return result;
777
+ }
778
+ /**
779
+ * Get relationships for a memory
780
+ */
781
+ getMemoryRelationships(memoryId) {
782
+ if (!this.db)
783
+ return [];
784
+ const stmt = this.db.prepare(`
785
+ SELECT
786
+ r.to_memory_id as relatedMemoryId,
787
+ r.relationship_type as relationshipType,
788
+ m.hash as relatedMemoryHash
789
+ FROM relationships r
790
+ JOIN memories m ON m.id = r.to_memory_id
791
+ WHERE r.from_memory_id = ?
792
+ `);
793
+ const relationships = stmt.all(memoryId);
794
+ return relationships.map(rel => ({
795
+ relatedMemoryHash: rel.relatedMemoryHash,
796
+ relatedMemoryId: rel.relatedMemoryId,
797
+ relationshipType: rel.relationshipType
798
+ }));
799
+ }
800
+ /**
801
+ * Restore relationships after importing memories
802
+ */
803
+ restoreRelationships(memories) {
804
+ if (!this.db)
805
+ return;
806
+ let restoredCount = 0;
807
+ for (const memory of memories) {
808
+ if (!memory.relationships || memory.relationships.length === 0)
809
+ continue;
810
+ // Find the imported memory by hash
811
+ const fromMemory = this.getMemoryByHash(memory.hash);
812
+ if (!fromMemory)
813
+ continue;
814
+ for (const rel of memory.relationships) {
815
+ // Find the related memory by hash
816
+ const toMemory = this.getMemoryByHash(rel.relatedMemoryHash);
817
+ if (!toMemory)
818
+ continue;
819
+ try {
820
+ // Create relationship
821
+ const stmt = this.db.prepare(`
822
+ INSERT OR IGNORE INTO relationships (from_memory_id, to_memory_id, relationship_type, created_at)
823
+ VALUES (?, ?, ?, ?)
824
+ `);
825
+ const info = stmt.run(fromMemory.id, toMemory.id, rel.relationshipType, new Date().toISOString());
826
+ if (info.changes > 0) {
827
+ restoredCount++;
828
+ }
829
+ }
830
+ catch (error) {
831
+ debugLog(`Warning: Could not restore relationship: ${error.message}`);
832
+ }
833
+ }
834
+ }
835
+ if (restoredCount > 0) {
836
+ debugLog(`Restored ${restoredCount} relationships`);
837
+ }
838
+ }
839
+ /**
840
+ * Get a memory by its hash
841
+ */
842
+ getMemoryByHash(hash) {
843
+ if (!this.db)
844
+ return null;
845
+ const stmt = this.db.prepare(`
846
+ SELECT * FROM memories WHERE hash = ?
847
+ `);
848
+ const result = stmt.get(hash);
849
+ if (!result)
850
+ return null;
851
+ // Hydrate with tags from tags table
852
+ const tagRows = this.stmts.getTagsForMemory.all(result.id);
853
+ return {
854
+ id: result.id,
855
+ content: result.content,
856
+ tags: tagRows.map(t => t.tag),
857
+ createdAt: result.created_at,
858
+ hash: result.hash
859
+ };
860
+ }
861
+ }
862
+ //# sourceMappingURL=memory-service.js.map