collective-memory-mcp 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -6
- package/src/storage.js +285 -197
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "collective-memory-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "A persistent, graph-based memory system for AI agents (MCP Server)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/server.js",
|
|
@@ -37,10 +37,6 @@
|
|
|
37
37
|
"win32"
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@modelcontextprotocol/sdk": "^0.6.0"
|
|
41
|
-
"better-sqlite3": "^11.0.0"
|
|
42
|
-
},
|
|
43
|
-
"devDependencies": {
|
|
44
|
-
"@types/better-sqlite3": "^7.6.0"
|
|
40
|
+
"@modelcontextprotocol/sdk": "^0.6.0"
|
|
45
41
|
}
|
|
46
42
|
}
|
package/src/storage.js
CHANGED
|
@@ -1,74 +1,123 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Storage layer for the Collective Memory System using
|
|
2
|
+
* Storage layer for the Collective Memory System using JSON file.
|
|
3
|
+
* Pure JavaScript - no native dependencies required.
|
|
3
4
|
*/
|
|
4
5
|
|
|
5
|
-
import Database from "better-sqlite3";
|
|
6
6
|
import { promises as fs } from "fs";
|
|
7
7
|
import path from "path";
|
|
8
8
|
import os from "os";
|
|
9
|
+
import { createHash } from "crypto";
|
|
9
10
|
import { Entity, Relation } from "./models.js";
|
|
10
11
|
|
|
11
12
|
const DB_DIR = path.join(os.homedir(), ".collective-memory");
|
|
12
|
-
const DB_PATH = path.join(DB_DIR, "memory.
|
|
13
|
+
const DB_PATH = path.join(DB_DIR, "memory.json");
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
|
-
*
|
|
16
|
+
* Lock file to prevent concurrent writes
|
|
17
|
+
*/
|
|
18
|
+
const LOCK_FILE = path.join(DB_DIR, "memory.lock");
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Simple file-based storage with locking
|
|
16
22
|
*/
|
|
17
23
|
export class Storage {
|
|
18
24
|
constructor(dbPath = DB_PATH) {
|
|
19
25
|
this.dbPath = dbPath;
|
|
20
|
-
this.
|
|
26
|
+
this.lockPath = LOCK_FILE;
|
|
27
|
+
this.data = null;
|
|
28
|
+
this.lockRetries = 10;
|
|
29
|
+
this.lockDelay = 50;
|
|
21
30
|
this.init();
|
|
22
31
|
}
|
|
23
32
|
|
|
24
33
|
/**
|
|
25
|
-
* Initialize
|
|
34
|
+
* Initialize storage
|
|
35
|
+
*/
|
|
36
|
+
async init() {
|
|
37
|
+
await this.ensureDir();
|
|
38
|
+
await this.load();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Ensure data directory exists
|
|
43
|
+
*/
|
|
44
|
+
async ensureDir() {
|
|
45
|
+
try {
|
|
46
|
+
await fs.mkdir(path.dirname(this.dbPath), { recursive: true });
|
|
47
|
+
} catch {
|
|
48
|
+
// Ignore if exists
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Load data from file
|
|
54
|
+
*/
|
|
55
|
+
async load() {
|
|
56
|
+
try {
|
|
57
|
+
const content = await fs.readFile(this.dbPath, "utf-8");
|
|
58
|
+
this.data = JSON.parse(content);
|
|
59
|
+
} catch {
|
|
60
|
+
// File doesn't exist or is invalid - create new
|
|
61
|
+
this.data = {
|
|
62
|
+
entities: {},
|
|
63
|
+
relations: [],
|
|
64
|
+
version: "1.0",
|
|
65
|
+
};
|
|
66
|
+
await this.save();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Save data to file
|
|
26
72
|
*/
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
fs.mkdir(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// Create entities table
|
|
35
|
-
this.db.exec(`
|
|
36
|
-
CREATE TABLE IF NOT EXISTS entities (
|
|
37
|
-
name TEXT PRIMARY KEY,
|
|
38
|
-
entity_type TEXT NOT NULL,
|
|
39
|
-
observations TEXT NOT NULL DEFAULT '[]',
|
|
40
|
-
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
41
|
-
metadata TEXT NOT NULL DEFAULT '{}'
|
|
42
|
-
)
|
|
43
|
-
`);
|
|
44
|
-
|
|
45
|
-
// Create relations table
|
|
46
|
-
this.db.exec(`
|
|
47
|
-
CREATE TABLE IF NOT EXISTS relations (
|
|
48
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
49
|
-
from_entity TEXT NOT NULL,
|
|
50
|
-
to_entity TEXT NOT NULL,
|
|
51
|
-
relation_type TEXT NOT NULL,
|
|
52
|
-
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
53
|
-
metadata TEXT NOT NULL DEFAULT '{}',
|
|
54
|
-
UNIQUE(from_entity, to_entity, relation_type)
|
|
55
|
-
)
|
|
56
|
-
`);
|
|
57
|
-
|
|
58
|
-
// Create indexes
|
|
59
|
-
this.db.exec(`
|
|
60
|
-
CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(entity_type);
|
|
61
|
-
CREATE INDEX IF NOT EXISTS idx_relations_from ON relations(from_entity);
|
|
62
|
-
CREATE INDEX IF NOT EXISTS idx_relations_to ON relations(to_entity);
|
|
63
|
-
CREATE INDEX IF NOT EXISTS idx_relations_type ON relations(relation_type);
|
|
64
|
-
`);
|
|
73
|
+
async save() {
|
|
74
|
+
const dir = path.dirname(this.dbPath);
|
|
75
|
+
await fs.mkdir(dir, { recursive: true });
|
|
76
|
+
const tempPath = path.join(dir, ".memory.tmp");
|
|
77
|
+
await fs.writeFile(tempPath, JSON.stringify(this.data, null, 2), "utf-8");
|
|
78
|
+
await fs.rename(tempPath, this.dbPath);
|
|
65
79
|
}
|
|
66
80
|
|
|
67
81
|
/**
|
|
68
|
-
*
|
|
82
|
+
* Acquire lock
|
|
69
83
|
*/
|
|
70
|
-
|
|
71
|
-
|
|
84
|
+
async acquireLock() {
|
|
85
|
+
for (let i = 0; i < this.lockRetries; i++) {
|
|
86
|
+
try {
|
|
87
|
+
await fs.writeFile(
|
|
88
|
+
this.lockPath,
|
|
89
|
+
process.pid.toString(),
|
|
90
|
+
{ flag: "wx" }
|
|
91
|
+
);
|
|
92
|
+
return;
|
|
93
|
+
} catch {
|
|
94
|
+
await new Promise(r => setTimeout(r, this.lockDelay));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// If we can't get lock, just proceed (it's a simple file lock)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Release lock
|
|
102
|
+
*/
|
|
103
|
+
async releaseLock() {
|
|
104
|
+
try {
|
|
105
|
+
await fs.unlink(this.lockPath);
|
|
106
|
+
} catch {
|
|
107
|
+
// Ignore
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Execute operation with lock
|
|
113
|
+
*/
|
|
114
|
+
async withLock(fn) {
|
|
115
|
+
await this.acquireLock();
|
|
116
|
+
try {
|
|
117
|
+
return await fn();
|
|
118
|
+
} finally {
|
|
119
|
+
await this.releaseLock();
|
|
120
|
+
}
|
|
72
121
|
}
|
|
73
122
|
|
|
74
123
|
// ========== Entity Operations ==========
|
|
@@ -76,40 +125,24 @@ export class Storage {
|
|
|
76
125
|
/**
|
|
77
126
|
* Create a new entity. Returns true if created, false if duplicate.
|
|
78
127
|
*/
|
|
79
|
-
createEntity(entity) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
entity.name,
|
|
87
|
-
entity.entityType,
|
|
88
|
-
JSON.stringify(entity.observations),
|
|
89
|
-
entity.createdAt,
|
|
90
|
-
JSON.stringify(entity.metadata)
|
|
91
|
-
);
|
|
128
|
+
async createEntity(entity) {
|
|
129
|
+
return this.withLock(async () => {
|
|
130
|
+
if (this.data.entities[entity.name]) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
this.data.entities[entity.name] = entity.toJSON();
|
|
134
|
+
await this.save();
|
|
92
135
|
return true;
|
|
93
|
-
}
|
|
94
|
-
if (err.code === "SQLITE_CONSTRAINT") return false;
|
|
95
|
-
throw err;
|
|
96
|
-
}
|
|
136
|
+
});
|
|
97
137
|
}
|
|
98
138
|
|
|
99
139
|
/**
|
|
100
140
|
* Get an entity by name
|
|
101
141
|
*/
|
|
102
142
|
getEntity(name) {
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
return new Entity({
|
|
107
|
-
name: row.name,
|
|
108
|
-
entityType: row.entity_type,
|
|
109
|
-
observations: JSON.parse(row.observations),
|
|
110
|
-
createdAt: row.created_at,
|
|
111
|
-
metadata: JSON.parse(row.metadata),
|
|
112
|
-
});
|
|
143
|
+
const data = this.data.entities[name];
|
|
144
|
+
if (data) {
|
|
145
|
+
return new Entity(data);
|
|
113
146
|
}
|
|
114
147
|
return null;
|
|
115
148
|
}
|
|
@@ -118,68 +151,64 @@ export class Storage {
|
|
|
118
151
|
* Get all entities
|
|
119
152
|
*/
|
|
120
153
|
getAllEntities() {
|
|
121
|
-
|
|
122
|
-
const rows = stmt.all();
|
|
123
|
-
return rows.map(row => new Entity({
|
|
124
|
-
name: row.name,
|
|
125
|
-
entityType: row.entity_type,
|
|
126
|
-
observations: JSON.parse(row.observations),
|
|
127
|
-
createdAt: row.created_at,
|
|
128
|
-
metadata: JSON.parse(row.metadata),
|
|
129
|
-
}));
|
|
154
|
+
return Object.values(this.data.entities).map(data => new Entity(data));
|
|
130
155
|
}
|
|
131
156
|
|
|
132
157
|
/**
|
|
133
158
|
* Check if an entity exists
|
|
134
159
|
*/
|
|
135
160
|
entityExists(name) {
|
|
136
|
-
|
|
137
|
-
return stmt.get(name) !== undefined;
|
|
161
|
+
return name in this.data.entities;
|
|
138
162
|
}
|
|
139
163
|
|
|
140
164
|
/**
|
|
141
|
-
* Update entity
|
|
165
|
+
* Update entity
|
|
142
166
|
*/
|
|
143
|
-
updateEntity(name, { observations, metadata } = {}) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
params.push(name);
|
|
159
|
-
const stmt = this.prepare(`UPDATE entities SET ${updates.join(", ")} WHERE name = ?`);
|
|
160
|
-
return stmt.run(...params).changes > 0;
|
|
167
|
+
async updateEntity(name, { observations, metadata } = {}) {
|
|
168
|
+
return this.withLock(async () => {
|
|
169
|
+
const entity = this.data.entities[name];
|
|
170
|
+
if (!entity) return false;
|
|
171
|
+
|
|
172
|
+
if (observations !== undefined) {
|
|
173
|
+
entity.observations = observations;
|
|
174
|
+
}
|
|
175
|
+
if (metadata !== undefined) {
|
|
176
|
+
entity.metadata = metadata;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
await this.save();
|
|
180
|
+
return true;
|
|
181
|
+
});
|
|
161
182
|
}
|
|
162
183
|
|
|
163
184
|
/**
|
|
164
185
|
* Delete an entity and its relations
|
|
165
186
|
*/
|
|
166
|
-
deleteEntity(name) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
187
|
+
async deleteEntity(name) {
|
|
188
|
+
return this.withLock(async () => {
|
|
189
|
+
if (!this.data.entities[name]) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
delete this.data.entities[name];
|
|
171
194
|
|
|
172
|
-
|
|
173
|
-
|
|
195
|
+
// Remove relations involving this entity
|
|
196
|
+
this.data.relations = this.data.relations.filter(
|
|
197
|
+
r => r.from !== name && r.to !== name
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
await this.save();
|
|
201
|
+
return true;
|
|
202
|
+
});
|
|
174
203
|
}
|
|
175
204
|
|
|
176
205
|
/**
|
|
177
206
|
* Delete multiple entities
|
|
178
207
|
*/
|
|
179
|
-
deleteEntities(names) {
|
|
208
|
+
async deleteEntities(names) {
|
|
180
209
|
let count = 0;
|
|
181
210
|
for (const name of names) {
|
|
182
|
-
if (this.deleteEntity(name)) count++;
|
|
211
|
+
if (await this.deleteEntity(name)) count++;
|
|
183
212
|
}
|
|
184
213
|
return count;
|
|
185
214
|
}
|
|
@@ -189,91 +218,86 @@ export class Storage {
|
|
|
189
218
|
/**
|
|
190
219
|
* Create a new relation. Returns true if created, false if duplicate.
|
|
191
220
|
*/
|
|
192
|
-
createRelation(relation) {
|
|
193
|
-
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
relation.relationType,
|
|
202
|
-
relation.createdAt,
|
|
203
|
-
JSON.stringify(relation.metadata)
|
|
204
|
-
);
|
|
221
|
+
async createRelation(relation) {
|
|
222
|
+
return this.withLock(async () => {
|
|
223
|
+
const key = this.relationKey(relation.from, relation.to, relation.relationType);
|
|
224
|
+
if (this.data.relations.some(r => this.relationKey(r.from, r.to, r.relationType) === key)) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
this.data.relations.push(relation.toJSON());
|
|
229
|
+
await this.save();
|
|
205
230
|
return true;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Generate unique key for relation
|
|
236
|
+
*/
|
|
237
|
+
relationKey(from, to, type) {
|
|
238
|
+
return `${from}|${to}|${type}`;
|
|
210
239
|
}
|
|
211
240
|
|
|
212
241
|
/**
|
|
213
242
|
* Get relations with optional filters
|
|
214
243
|
*/
|
|
215
244
|
getRelations({ fromEntity, toEntity, relationType } = {}) {
|
|
216
|
-
let
|
|
217
|
-
const params = [];
|
|
245
|
+
let results = this.data.relations.map(r => new Relation(r));
|
|
218
246
|
|
|
219
247
|
if (fromEntity) {
|
|
220
|
-
|
|
221
|
-
params.push(fromEntity);
|
|
248
|
+
results = results.filter(r => r.from === fromEntity);
|
|
222
249
|
}
|
|
223
250
|
if (toEntity) {
|
|
224
|
-
|
|
225
|
-
params.push(toEntity);
|
|
251
|
+
results = results.filter(r => r.to === toEntity);
|
|
226
252
|
}
|
|
227
253
|
if (relationType) {
|
|
228
|
-
|
|
229
|
-
params.push(relationType);
|
|
254
|
+
results = results.filter(r => r.relationType === relationType);
|
|
230
255
|
}
|
|
231
256
|
|
|
232
|
-
|
|
233
|
-
const rows = stmt.all(...params);
|
|
234
|
-
return rows.map(row => new Relation({
|
|
235
|
-
from: row.from_entity,
|
|
236
|
-
to: row.to_entity,
|
|
237
|
-
relationType: row.relation_type,
|
|
238
|
-
createdAt: row.created_at,
|
|
239
|
-
metadata: JSON.parse(row.metadata),
|
|
240
|
-
}));
|
|
257
|
+
return results;
|
|
241
258
|
}
|
|
242
259
|
|
|
243
260
|
/**
|
|
244
261
|
* Get all relations
|
|
245
262
|
*/
|
|
246
263
|
getAllRelations() {
|
|
247
|
-
return this.
|
|
264
|
+
return this.data.relations.map(r => new Relation(r));
|
|
248
265
|
}
|
|
249
266
|
|
|
250
267
|
/**
|
|
251
268
|
* Check if a relation exists
|
|
252
269
|
*/
|
|
253
270
|
relationExists(fromEntity, toEntity, relationType) {
|
|
254
|
-
|
|
255
|
-
|
|
271
|
+
return this.data.relations.some(
|
|
272
|
+
r => r.from === fromEntity && r.to === toEntity && r.relationType === relationType
|
|
256
273
|
);
|
|
257
|
-
return stmt.get(fromEntity, toEntity, relationType) !== undefined;
|
|
258
274
|
}
|
|
259
275
|
|
|
260
276
|
/**
|
|
261
277
|
* Delete a specific relation
|
|
262
278
|
*/
|
|
263
|
-
deleteRelation(fromEntity, toEntity, relationType) {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
279
|
+
async deleteRelation(fromEntity, toEntity, relationType) {
|
|
280
|
+
return this.withLock(async () => {
|
|
281
|
+
const before = this.data.relations.length;
|
|
282
|
+
this.data.relations = this.data.relations.filter(
|
|
283
|
+
r => !(r.from === fromEntity && r.to === toEntity && r.relationType === relationType)
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
if (this.data.relations.length < before) {
|
|
287
|
+
await this.save();
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
return false;
|
|
291
|
+
});
|
|
268
292
|
}
|
|
269
293
|
|
|
270
294
|
/**
|
|
271
295
|
* Delete multiple relations
|
|
272
296
|
*/
|
|
273
|
-
deleteRelations(relations) {
|
|
297
|
+
async deleteRelations(relations) {
|
|
274
298
|
let count = 0;
|
|
275
299
|
for (const [fromEntity, toEntity, relationType] of relations) {
|
|
276
|
-
if (this.deleteRelation(fromEntity, toEntity, relationType)) count++;
|
|
300
|
+
if (await this.deleteRelation(fromEntity, toEntity, relationType)) count++;
|
|
277
301
|
}
|
|
278
302
|
return count;
|
|
279
303
|
}
|
|
@@ -284,22 +308,13 @@ export class Storage {
|
|
|
284
308
|
* Search entities by name, type, or observations
|
|
285
309
|
*/
|
|
286
310
|
searchEntities(query) {
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
`);
|
|
295
|
-
const rows = stmt.all(pattern, pattern, pattern);
|
|
296
|
-
return rows.map(row => new Entity({
|
|
297
|
-
name: row.name,
|
|
298
|
-
entityType: row.entity_type,
|
|
299
|
-
observations: JSON.parse(row.observations),
|
|
300
|
-
createdAt: row.created_at,
|
|
301
|
-
metadata: JSON.parse(row.metadata),
|
|
302
|
-
}));
|
|
311
|
+
const lowerQuery = query.toLowerCase();
|
|
312
|
+
return this.getAllEntities().filter(e => {
|
|
313
|
+
if (e.name.toLowerCase().includes(lowerQuery)) return true;
|
|
314
|
+
if (e.entityType.toLowerCase().includes(lowerQuery)) return true;
|
|
315
|
+
if (e.observations.some(o => o.toLowerCase().includes(lowerQuery))) return true;
|
|
316
|
+
return false;
|
|
317
|
+
});
|
|
303
318
|
}
|
|
304
319
|
|
|
305
320
|
/**
|
|
@@ -308,47 +323,120 @@ export class Storage {
|
|
|
308
323
|
getRelatedEntities(entityName) {
|
|
309
324
|
const result = { connected: [], incoming: [], outgoing: [] };
|
|
310
325
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
...outgoing.map(r => r.to_entity),
|
|
324
|
-
...incoming.map(r => r.from_entity)
|
|
325
|
-
]);
|
|
326
|
+
const connectedNames = new Set();
|
|
327
|
+
|
|
328
|
+
for (const rel of this.data.relations) {
|
|
329
|
+
if (rel.from === entityName) {
|
|
330
|
+
connectedNames.add(rel.to);
|
|
331
|
+
result.outgoing.push(rel.to);
|
|
332
|
+
}
|
|
333
|
+
if (rel.to === entityName) {
|
|
334
|
+
connectedNames.add(rel.from);
|
|
335
|
+
result.incoming.push(rel.from);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
326
338
|
|
|
327
339
|
for (const name of connectedNames) {
|
|
328
340
|
const entity = this.getEntity(name);
|
|
329
|
-
if (entity)
|
|
341
|
+
if (entity) {
|
|
342
|
+
result.connected.push(entity);
|
|
343
|
+
}
|
|
330
344
|
}
|
|
331
345
|
|
|
332
346
|
return result;
|
|
333
347
|
}
|
|
334
348
|
|
|
335
349
|
/**
|
|
336
|
-
* Close
|
|
350
|
+
* Close storage
|
|
337
351
|
*/
|
|
338
|
-
close() {
|
|
339
|
-
|
|
340
|
-
this.db.close();
|
|
341
|
-
this.db = null;
|
|
342
|
-
}
|
|
352
|
+
async close() {
|
|
353
|
+
await this.save();
|
|
343
354
|
}
|
|
344
355
|
}
|
|
345
356
|
|
|
346
|
-
//
|
|
357
|
+
// Sync wrapper for async operations
|
|
347
358
|
let storageInstance = null;
|
|
348
359
|
|
|
349
360
|
export function getStorage(dbPath) {
|
|
350
361
|
if (!storageInstance) {
|
|
351
362
|
storageInstance = new Storage(dbPath);
|
|
363
|
+
// Initialize asynchronously but don't wait
|
|
364
|
+
storageInstance.init().catch(console.error);
|
|
352
365
|
}
|
|
353
366
|
return storageInstance;
|
|
354
367
|
}
|
|
368
|
+
|
|
369
|
+
// Synchronous methods for compatibility
|
|
370
|
+
export class SyncStorage {
|
|
371
|
+
constructor(dbPath = DB_PATH) {
|
|
372
|
+
this.storage = new Storage(dbPath);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
async init() {
|
|
376
|
+
await this.storage.init();
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
createEntity(entity) {
|
|
380
|
+
return this.storage.createEntity(entity);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
getEntity(name) {
|
|
384
|
+
return this.storage.getEntity(name);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
getAllEntities() {
|
|
388
|
+
return this.storage.getAllEntities();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
entityExists(name) {
|
|
392
|
+
return this.storage.entityExists(name);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
updateEntity(name, data) {
|
|
396
|
+
return this.storage.updateEntity(name, data);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
deleteEntity(name) {
|
|
400
|
+
return this.storage.deleteEntity(name);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
deleteEntities(names) {
|
|
404
|
+
return this.storage.deleteEntities(names);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
createRelation(relation) {
|
|
408
|
+
return this.storage.createRelation(relation);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
getRelations(filters) {
|
|
412
|
+
return this.storage.getRelations(filters);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
getAllRelations() {
|
|
416
|
+
return this.storage.getAllRelations();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
relationExists(from, to, type) {
|
|
420
|
+
return this.storage.relationExists(from, to, type);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
deleteRelation(from, to, type) {
|
|
424
|
+
return this.storage.deleteRelation(from, to, type);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
deleteRelations(relations) {
|
|
428
|
+
return this.storage.deleteRelations(relations);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
searchEntities(query) {
|
|
432
|
+
return this.storage.searchEntities(query);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
getRelatedEntities(name) {
|
|
436
|
+
return this.storage.getRelatedEntities(name);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
close() {
|
|
440
|
+
return this.storage.close();
|
|
441
|
+
}
|
|
442
|
+
}
|