@cogitator-ai/memory 0.9.1 → 0.11.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 (41) hide show
  1. package/README.md +207 -90
  2. package/dist/adapters/postgres.d.ts +1 -0
  3. package/dist/adapters/postgres.d.ts.map +1 -1
  4. package/dist/adapters/postgres.js +78 -38
  5. package/dist/adapters/postgres.js.map +1 -1
  6. package/dist/adapters/qdrant.d.ts.map +1 -1
  7. package/dist/adapters/qdrant.js +16 -2
  8. package/dist/adapters/qdrant.js.map +1 -1
  9. package/dist/adapters/sqlite.d.ts +1 -0
  10. package/dist/adapters/sqlite.d.ts.map +1 -1
  11. package/dist/adapters/sqlite.js +17 -16
  12. package/dist/adapters/sqlite.js.map +1 -1
  13. package/dist/compaction.d.ts +14 -1
  14. package/dist/compaction.d.ts.map +1 -1
  15. package/dist/compaction.js +4 -1
  16. package/dist/compaction.js.map +1 -1
  17. package/dist/index.d.ts +2 -1
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +2 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/knowledge-graph/entity-extractor.d.ts +24 -11
  22. package/dist/knowledge-graph/entity-extractor.d.ts.map +1 -1
  23. package/dist/knowledge-graph/entity-extractor.js +15 -3
  24. package/dist/knowledge-graph/entity-extractor.js.map +1 -1
  25. package/dist/knowledge-graph/graph-adapter.d.ts +1 -0
  26. package/dist/knowledge-graph/graph-adapter.d.ts.map +1 -1
  27. package/dist/knowledge-graph/graph-adapter.js +470 -448
  28. package/dist/knowledge-graph/graph-adapter.js.map +1 -1
  29. package/dist/knowledge-graph/sqlite-graph-adapter.d.ts +1 -0
  30. package/dist/knowledge-graph/sqlite-graph-adapter.d.ts.map +1 -1
  31. package/dist/knowledge-graph/sqlite-graph-adapter.js +459 -435
  32. package/dist/knowledge-graph/sqlite-graph-adapter.js.map +1 -1
  33. package/dist/result.d.ts +11 -0
  34. package/dist/result.d.ts.map +1 -0
  35. package/dist/result.js +14 -0
  36. package/dist/result.js.map +1 -0
  37. package/dist/schema.d.ts +4 -0
  38. package/dist/schema.d.ts.map +1 -1
  39. package/dist/schema.js +2 -0
  40. package/dist/schema.js.map +1 -1
  41. package/package.json +4 -4
@@ -1,4 +1,5 @@
1
1
  import { nanoid } from 'nanoid';
2
+ import { unwrap } from '../result.js';
2
3
  export class PostgresGraphAdapter {
3
4
  pool;
4
5
  schema;
@@ -109,308 +110,326 @@ export class PostgresGraphAdapter {
109
110
  failure(error) {
110
111
  return { success: false, error };
111
112
  }
113
+ async run(operation) {
114
+ try {
115
+ await this.initialize();
116
+ return await operation();
117
+ }
118
+ catch (err) {
119
+ return this.failure(err instanceof Error ? err.message : String(err));
120
+ }
121
+ }
112
122
  generateId(prefix) {
113
123
  return `${prefix}_${nanoid(12)}`;
114
124
  }
115
125
  async addNode(node) {
116
- await this.initialize();
117
- const id = this.generateId('node');
118
- const now = new Date();
119
- const embeddingStr = node.embedding ? `[${node.embedding.join(',')}]` : null;
120
- await this.pool.query(`INSERT INTO ${this.schema}.graph_nodes
121
- (id, agent_id, type, name, aliases, description, properties, embedding, confidence, source, metadata, created_at, updated_at, last_accessed_at, access_count)
122
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $12, $12, 0)`, [
123
- id,
124
- node.agentId,
125
- node.type,
126
- node.name,
127
- node.aliases,
128
- node.description ?? null,
129
- node.properties,
130
- embeddingStr,
131
- node.confidence,
132
- node.source,
133
- node.metadata ?? {},
134
- now,
135
- ]);
136
- return this.success({
137
- ...node,
138
- id,
139
- createdAt: now,
140
- updatedAt: now,
141
- lastAccessedAt: now,
142
- accessCount: 0,
126
+ return this.run(async () => {
127
+ const id = this.generateId('node');
128
+ const now = new Date();
129
+ const embeddingStr = node.embedding ? `[${node.embedding.join(',')}]` : null;
130
+ await this.pool.query(`INSERT INTO ${this.schema}.graph_nodes
131
+ (id, agent_id, type, name, aliases, description, properties, embedding, confidence, source, metadata, created_at, updated_at, last_accessed_at, access_count)
132
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $12, $12, 0)`, [
133
+ id,
134
+ node.agentId,
135
+ node.type,
136
+ node.name,
137
+ node.aliases,
138
+ node.description ?? null,
139
+ node.properties,
140
+ embeddingStr,
141
+ node.confidence,
142
+ node.source,
143
+ node.metadata ?? {},
144
+ now,
145
+ ]);
146
+ return this.success({
147
+ ...node,
148
+ id,
149
+ createdAt: now,
150
+ updatedAt: now,
151
+ lastAccessedAt: now,
152
+ accessCount: 0,
153
+ });
143
154
  });
144
155
  }
145
156
  async getNode(nodeId) {
146
- await this.initialize();
147
- await this.pool.query(`UPDATE ${this.schema}.graph_nodes
148
- SET last_accessed_at = NOW(), access_count = access_count + 1
149
- WHERE id = $1`, [nodeId]);
150
- const result = await this.pool.query(`SELECT * FROM ${this.schema}.graph_nodes WHERE id = $1`, [
151
- nodeId,
152
- ]);
153
- if (result.rows.length === 0)
154
- return this.success(null);
155
- return this.success(this.rowToNode(result.rows[0]));
157
+ return this.run(async () => {
158
+ await this.pool.query(`UPDATE ${this.schema}.graph_nodes
159
+ SET last_accessed_at = NOW(), access_count = access_count + 1
160
+ WHERE id = $1`, [nodeId]);
161
+ const result = await this.pool.query(`SELECT * FROM ${this.schema}.graph_nodes WHERE id = $1`, [nodeId]);
162
+ if (result.rows.length === 0)
163
+ return this.success(null);
164
+ return this.success(this.rowToNode(result.rows[0]));
165
+ });
156
166
  }
157
167
  async getNodeByName(agentId, name) {
158
- await this.initialize();
159
- const result = await this.pool.query(`SELECT * FROM ${this.schema}.graph_nodes
160
- WHERE agent_id = $1 AND (name = $2 OR $2 = ANY(aliases))`, [agentId, name]);
161
- if (result.rows.length === 0)
162
- return this.success(null);
163
- return this.success(this.rowToNode(result.rows[0]));
168
+ return this.run(async () => {
169
+ const result = await this.pool.query(`SELECT * FROM ${this.schema}.graph_nodes
170
+ WHERE agent_id = $1 AND (name = $2 OR $2 = ANY(aliases))`, [agentId, name]);
171
+ if (result.rows.length === 0)
172
+ return this.success(null);
173
+ return this.success(this.rowToNode(result.rows[0]));
174
+ });
164
175
  }
165
176
  async updateNode(nodeId, updates) {
166
- await this.initialize();
167
- const setClauses = ['updated_at = NOW()'];
168
- const params = [];
169
- let paramIndex = 1;
170
- if (updates.name !== undefined) {
171
- setClauses.push(`name = $${paramIndex++}`);
172
- params.push(updates.name);
173
- }
174
- if (updates.aliases !== undefined) {
175
- setClauses.push(`aliases = $${paramIndex++}`);
176
- params.push(updates.aliases);
177
- }
178
- if (updates.description !== undefined) {
179
- setClauses.push(`description = $${paramIndex++}`);
180
- params.push(updates.description);
181
- }
182
- if (updates.properties !== undefined) {
183
- setClauses.push(`properties = $${paramIndex++}`);
184
- params.push(updates.properties);
185
- }
186
- if (updates.confidence !== undefined) {
187
- setClauses.push(`confidence = $${paramIndex++}`);
188
- params.push(updates.confidence);
189
- }
190
- if (updates.metadata !== undefined) {
191
- setClauses.push(`metadata = $${paramIndex++}`);
192
- params.push(updates.metadata);
193
- }
194
- if (updates.embedding !== undefined) {
195
- setClauses.push(`embedding = $${paramIndex++}`);
196
- params.push(`[${updates.embedding.join(',')}]`);
197
- }
198
- params.push(nodeId);
199
- const result = await this.pool.query(`UPDATE ${this.schema}.graph_nodes SET ${setClauses.join(', ')} WHERE id = $${paramIndex} RETURNING *`, params);
200
- if (result.rows.length === 0) {
201
- return this.failure(`Node not found: ${nodeId}`);
202
- }
203
- return this.success(this.rowToNode(result.rows[0]));
177
+ return this.run(async () => {
178
+ const setClauses = ['updated_at = NOW()'];
179
+ const params = [];
180
+ let paramIndex = 1;
181
+ if (updates.name !== undefined) {
182
+ setClauses.push(`name = $${paramIndex++}`);
183
+ params.push(updates.name);
184
+ }
185
+ if (updates.aliases !== undefined) {
186
+ setClauses.push(`aliases = $${paramIndex++}`);
187
+ params.push(updates.aliases);
188
+ }
189
+ if (updates.description !== undefined) {
190
+ setClauses.push(`description = $${paramIndex++}`);
191
+ params.push(updates.description);
192
+ }
193
+ if (updates.properties !== undefined) {
194
+ setClauses.push(`properties = $${paramIndex++}`);
195
+ params.push(updates.properties);
196
+ }
197
+ if (updates.confidence !== undefined) {
198
+ setClauses.push(`confidence = $${paramIndex++}`);
199
+ params.push(updates.confidence);
200
+ }
201
+ if (updates.metadata !== undefined) {
202
+ setClauses.push(`metadata = $${paramIndex++}`);
203
+ params.push(updates.metadata);
204
+ }
205
+ if (updates.embedding !== undefined) {
206
+ setClauses.push(`embedding = $${paramIndex++}`);
207
+ params.push(`[${updates.embedding.join(',')}]`);
208
+ }
209
+ params.push(nodeId);
210
+ const result = await this.pool.query(`UPDATE ${this.schema}.graph_nodes SET ${setClauses.join(', ')} WHERE id = $${paramIndex} RETURNING *`, params);
211
+ if (result.rows.length === 0) {
212
+ return this.failure(`Node not found: ${nodeId}`);
213
+ }
214
+ return this.success(this.rowToNode(result.rows[0]));
215
+ });
204
216
  }
205
217
  async deleteNode(nodeId) {
206
- await this.initialize();
207
- await this.pool.query(`DELETE FROM ${this.schema}.graph_nodes WHERE id = $1`, [nodeId]);
208
- return this.success(undefined);
218
+ return this.run(async () => {
219
+ await this.pool.query(`DELETE FROM ${this.schema}.graph_nodes WHERE id = $1`, [nodeId]);
220
+ return this.success(undefined);
221
+ });
209
222
  }
210
223
  async queryNodes(query) {
211
- await this.initialize();
212
- let sql = `SELECT * FROM ${this.schema}.graph_nodes WHERE agent_id = $1`;
213
- const params = [query.agentId];
214
- let paramIndex = 2;
215
- if (query.types && query.types.length > 0) {
216
- sql += ` AND type = ANY($${paramIndex++})`;
217
- params.push(query.types);
218
- }
219
- if (query.namePattern) {
220
- sql += ` AND name ILIKE $${paramIndex++}`;
221
- params.push(`%${query.namePattern.replace(/[%_\\]/g, '\\$&')}%`);
222
- }
223
- if (query.minConfidence !== undefined) {
224
- sql += ` AND confidence >= $${paramIndex++}`;
225
- params.push(query.minConfidence);
226
- }
227
- sql += ' ORDER BY access_count DESC, updated_at DESC';
228
- if (query.limit) {
229
- sql += ` LIMIT $${paramIndex}`;
230
- params.push(query.limit);
231
- }
232
- const result = await this.pool.query(sql, params);
233
- return this.success(result.rows.map((row) => this.rowToNode(row, query.includeEmbedding)));
224
+ return this.run(async () => {
225
+ let sql = `SELECT * FROM ${this.schema}.graph_nodes WHERE agent_id = $1`;
226
+ const params = [query.agentId];
227
+ let paramIndex = 2;
228
+ if (query.types && query.types.length > 0) {
229
+ sql += ` AND type = ANY($${paramIndex++})`;
230
+ params.push(query.types);
231
+ }
232
+ if (query.namePattern) {
233
+ sql += ` AND name ILIKE $${paramIndex++}`;
234
+ params.push(`%${query.namePattern.replace(/[%_\\]/g, '\\$&')}%`);
235
+ }
236
+ if (query.minConfidence !== undefined) {
237
+ sql += ` AND confidence >= $${paramIndex++}`;
238
+ params.push(query.minConfidence);
239
+ }
240
+ sql += ' ORDER BY access_count DESC, updated_at DESC';
241
+ if (query.limit) {
242
+ sql += ` LIMIT $${paramIndex}`;
243
+ params.push(query.limit);
244
+ }
245
+ const result = await this.pool.query(sql, params);
246
+ return this.success(result.rows.map((row) => this.rowToNode(row, query.includeEmbedding)));
247
+ });
234
248
  }
235
249
  async searchNodesSemantic(options) {
236
- await this.initialize();
237
- if (!options.vector) {
238
- return this.failure('searchNodesSemantic requires vector');
239
- }
240
- const vectorStr = `[${options.vector.join(',')}]`;
241
- const limit = options.limit ?? 10;
242
- const threshold = options.threshold ?? 0.7;
243
- let sql = `
244
- SELECT *, 1 - (embedding <=> $1) as score
245
- FROM ${this.schema}.graph_nodes
246
- WHERE agent_id = $2 AND embedding IS NOT NULL AND 1 - (embedding <=> $1) >= $3
247
- `;
248
- const params = [vectorStr, options.agentId, threshold];
249
- let paramIndex = 4;
250
- if (options.entityTypes && options.entityTypes.length > 0) {
251
- sql += ` AND type = ANY($${paramIndex++})`;
252
- params.push(options.entityTypes);
253
- }
254
- sql += ` ORDER BY embedding <=> $1 LIMIT $${paramIndex}`;
255
- params.push(limit);
256
- const result = await this.pool.query(sql, params);
257
- return this.success(result.rows.map((row) => ({
258
- ...this.rowToNode(row),
259
- score: row.score,
260
- })));
250
+ return this.run(async () => {
251
+ if (!options.vector) {
252
+ return this.failure('searchNodesSemantic requires vector');
253
+ }
254
+ const vectorStr = `[${options.vector.join(',')}]`;
255
+ const limit = options.limit ?? 10;
256
+ const threshold = options.threshold ?? 0.7;
257
+ let sql = `
258
+ SELECT *, 1 - (embedding <=> $1) as score
259
+ FROM ${this.schema}.graph_nodes
260
+ WHERE agent_id = $2 AND embedding IS NOT NULL AND 1 - (embedding <=> $1) >= $3
261
+ `;
262
+ const params = [vectorStr, options.agentId, threshold];
263
+ let paramIndex = 4;
264
+ if (options.entityTypes && options.entityTypes.length > 0) {
265
+ sql += ` AND type = ANY($${paramIndex++})`;
266
+ params.push(options.entityTypes);
267
+ }
268
+ sql += ` ORDER BY embedding <=> $1 LIMIT $${paramIndex}`;
269
+ params.push(limit);
270
+ const result = await this.pool.query(sql, params);
271
+ return this.success(result.rows.map((row) => ({
272
+ ...this.rowToNode(row),
273
+ score: row.score,
274
+ })));
275
+ });
261
276
  }
262
277
  async addEdge(edge) {
263
- await this.initialize();
264
- const id = this.generateId('edge');
265
- const now = new Date();
266
- await this.pool.query(`INSERT INTO ${this.schema}.graph_edges
267
- (id, agent_id, source_node_id, target_node_id, type, label, weight, bidirectional, properties, confidence, source, valid_from, valid_until, metadata, created_at, updated_at)
268
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $15)`, [
269
- id,
270
- edge.agentId,
271
- edge.sourceNodeId,
272
- edge.targetNodeId,
273
- edge.type,
274
- edge.label ?? null,
275
- edge.weight,
276
- edge.bidirectional,
277
- edge.properties,
278
- edge.confidence,
279
- edge.source,
280
- edge.validFrom ?? null,
281
- edge.validUntil ?? null,
282
- edge.metadata ?? {},
283
- now,
284
- ]);
285
- return this.success({
286
- ...edge,
287
- id,
288
- createdAt: now,
289
- updatedAt: now,
278
+ return this.run(async () => {
279
+ const id = this.generateId('edge');
280
+ const now = new Date();
281
+ await this.pool.query(`INSERT INTO ${this.schema}.graph_edges
282
+ (id, agent_id, source_node_id, target_node_id, type, label, weight, bidirectional, properties, confidence, source, valid_from, valid_until, metadata, created_at, updated_at)
283
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $15)`, [
284
+ id,
285
+ edge.agentId,
286
+ edge.sourceNodeId,
287
+ edge.targetNodeId,
288
+ edge.type,
289
+ edge.label ?? null,
290
+ edge.weight,
291
+ edge.bidirectional,
292
+ edge.properties,
293
+ edge.confidence,
294
+ edge.source,
295
+ edge.validFrom ?? null,
296
+ edge.validUntil ?? null,
297
+ edge.metadata ?? {},
298
+ now,
299
+ ]);
300
+ return this.success({
301
+ ...edge,
302
+ id,
303
+ createdAt: now,
304
+ updatedAt: now,
305
+ });
290
306
  });
291
307
  }
292
308
  async getEdge(edgeId) {
293
- await this.initialize();
294
- const result = await this.pool.query(`SELECT * FROM ${this.schema}.graph_edges WHERE id = $1`, [
295
- edgeId,
296
- ]);
297
- if (result.rows.length === 0)
298
- return this.success(null);
299
- return this.success(this.rowToEdge(result.rows[0]));
309
+ return this.run(async () => {
310
+ const result = await this.pool.query(`SELECT * FROM ${this.schema}.graph_edges WHERE id = $1`, [edgeId]);
311
+ if (result.rows.length === 0)
312
+ return this.success(null);
313
+ return this.success(this.rowToEdge(result.rows[0]));
314
+ });
300
315
  }
301
316
  async getEdgesBetween(sourceNodeId, targetNodeId) {
302
- await this.initialize();
303
- const result = await this.pool.query(`SELECT * FROM ${this.schema}.graph_edges
304
- WHERE (source_node_id = $1 AND target_node_id = $2)
305
- OR (bidirectional = TRUE AND source_node_id = $2 AND target_node_id = $1)`, [sourceNodeId, targetNodeId]);
306
- return this.success(result.rows.map((row) => this.rowToEdge(row)));
317
+ return this.run(async () => {
318
+ const result = await this.pool.query(`SELECT * FROM ${this.schema}.graph_edges
319
+ WHERE (source_node_id = $1 AND target_node_id = $2)
320
+ OR (bidirectional = TRUE AND source_node_id = $2 AND target_node_id = $1)`, [sourceNodeId, targetNodeId]);
321
+ return this.success(result.rows.map((row) => this.rowToEdge(row)));
322
+ });
307
323
  }
308
324
  async updateEdge(edgeId, updates) {
309
- await this.initialize();
310
- const setClauses = ['updated_at = NOW()'];
311
- const params = [];
312
- let paramIndex = 1;
313
- if (updates.weight !== undefined) {
314
- setClauses.push(`weight = $${paramIndex++}`);
315
- params.push(updates.weight);
316
- }
317
- if (updates.label !== undefined) {
318
- setClauses.push(`label = $${paramIndex++}`);
319
- params.push(updates.label);
320
- }
321
- if (updates.properties !== undefined) {
322
- setClauses.push(`properties = $${paramIndex++}`);
323
- params.push(updates.properties);
324
- }
325
- if (updates.confidence !== undefined) {
326
- setClauses.push(`confidence = $${paramIndex++}`);
327
- params.push(updates.confidence);
328
- }
329
- if (updates.validFrom !== undefined) {
330
- setClauses.push(`valid_from = $${paramIndex++}`);
331
- params.push(updates.validFrom);
332
- }
333
- if (updates.validUntil !== undefined) {
334
- setClauses.push(`valid_until = $${paramIndex++}`);
335
- params.push(updates.validUntil);
336
- }
337
- if (updates.metadata !== undefined) {
338
- setClauses.push(`metadata = $${paramIndex++}`);
339
- params.push(updates.metadata);
340
- }
341
- params.push(edgeId);
342
- const result = await this.pool.query(`UPDATE ${this.schema}.graph_edges SET ${setClauses.join(', ')} WHERE id = $${paramIndex} RETURNING *`, params);
343
- if (result.rows.length === 0) {
344
- return this.failure(`Edge not found: ${edgeId}`);
345
- }
346
- return this.success(this.rowToEdge(result.rows[0]));
325
+ return this.run(async () => {
326
+ const setClauses = ['updated_at = NOW()'];
327
+ const params = [];
328
+ let paramIndex = 1;
329
+ if (updates.weight !== undefined) {
330
+ setClauses.push(`weight = $${paramIndex++}`);
331
+ params.push(updates.weight);
332
+ }
333
+ if (updates.label !== undefined) {
334
+ setClauses.push(`label = $${paramIndex++}`);
335
+ params.push(updates.label);
336
+ }
337
+ if (updates.properties !== undefined) {
338
+ setClauses.push(`properties = $${paramIndex++}`);
339
+ params.push(updates.properties);
340
+ }
341
+ if (updates.confidence !== undefined) {
342
+ setClauses.push(`confidence = $${paramIndex++}`);
343
+ params.push(updates.confidence);
344
+ }
345
+ if (updates.validFrom !== undefined) {
346
+ setClauses.push(`valid_from = $${paramIndex++}`);
347
+ params.push(updates.validFrom);
348
+ }
349
+ if (updates.validUntil !== undefined) {
350
+ setClauses.push(`valid_until = $${paramIndex++}`);
351
+ params.push(updates.validUntil);
352
+ }
353
+ if (updates.metadata !== undefined) {
354
+ setClauses.push(`metadata = $${paramIndex++}`);
355
+ params.push(updates.metadata);
356
+ }
357
+ params.push(edgeId);
358
+ const result = await this.pool.query(`UPDATE ${this.schema}.graph_edges SET ${setClauses.join(', ')} WHERE id = $${paramIndex} RETURNING *`, params);
359
+ if (result.rows.length === 0) {
360
+ return this.failure(`Edge not found: ${edgeId}`);
361
+ }
362
+ return this.success(this.rowToEdge(result.rows[0]));
363
+ });
347
364
  }
348
365
  async deleteEdge(edgeId) {
349
- await this.initialize();
350
- await this.pool.query(`DELETE FROM ${this.schema}.graph_edges WHERE id = $1`, [edgeId]);
351
- return this.success(undefined);
366
+ return this.run(async () => {
367
+ await this.pool.query(`DELETE FROM ${this.schema}.graph_edges WHERE id = $1`, [edgeId]);
368
+ return this.success(undefined);
369
+ });
352
370
  }
353
371
  async queryEdges(query) {
354
- await this.initialize();
355
- let sql = `SELECT * FROM ${this.schema}.graph_edges WHERE agent_id = $1`;
356
- const params = [query.agentId];
357
- let paramIndex = 2;
358
- if (query.sourceNodeId) {
359
- sql += ` AND source_node_id = $${paramIndex++}`;
360
- params.push(query.sourceNodeId);
361
- }
362
- if (query.targetNodeId) {
363
- sql += ` AND target_node_id = $${paramIndex++}`;
364
- params.push(query.targetNodeId);
365
- }
366
- if (query.types && query.types.length > 0) {
367
- sql += ` AND type = ANY($${paramIndex++})`;
368
- params.push(query.types);
369
- }
370
- if (query.minWeight !== undefined) {
371
- sql += ` AND weight >= $${paramIndex++}`;
372
- params.push(query.minWeight);
373
- }
374
- if (query.minConfidence !== undefined) {
375
- sql += ` AND confidence >= $${paramIndex++}`;
376
- params.push(query.minConfidence);
377
- }
378
- if (query.bidirectionalOnly) {
379
- sql += ' AND bidirectional = TRUE';
380
- }
381
- sql += ' ORDER BY weight DESC, confidence DESC';
382
- if (query.limit) {
383
- sql += ` LIMIT $${paramIndex}`;
384
- params.push(query.limit);
385
- }
386
- const result = await this.pool.query(sql, params);
387
- return this.success(result.rows.map((row) => this.rowToEdge(row)));
372
+ return this.run(async () => {
373
+ let sql = `SELECT * FROM ${this.schema}.graph_edges WHERE agent_id = $1`;
374
+ const params = [query.agentId];
375
+ let paramIndex = 2;
376
+ if (query.sourceNodeId) {
377
+ sql += ` AND source_node_id = $${paramIndex++}`;
378
+ params.push(query.sourceNodeId);
379
+ }
380
+ if (query.targetNodeId) {
381
+ sql += ` AND target_node_id = $${paramIndex++}`;
382
+ params.push(query.targetNodeId);
383
+ }
384
+ if (query.types && query.types.length > 0) {
385
+ sql += ` AND type = ANY($${paramIndex++})`;
386
+ params.push(query.types);
387
+ }
388
+ if (query.minWeight !== undefined) {
389
+ sql += ` AND weight >= $${paramIndex++}`;
390
+ params.push(query.minWeight);
391
+ }
392
+ if (query.minConfidence !== undefined) {
393
+ sql += ` AND confidence >= $${paramIndex++}`;
394
+ params.push(query.minConfidence);
395
+ }
396
+ if (query.bidirectionalOnly) {
397
+ sql += ' AND bidirectional = TRUE';
398
+ }
399
+ sql += ' ORDER BY weight DESC, confidence DESC';
400
+ if (query.limit) {
401
+ sql += ` LIMIT $${paramIndex}`;
402
+ params.push(query.limit);
403
+ }
404
+ const result = await this.pool.query(sql, params);
405
+ return this.success(result.rows.map((row) => this.rowToEdge(row)));
406
+ });
388
407
  }
389
408
  async traverse(options) {
390
- await this.initialize();
391
- const visited = new Set();
392
- const visitedEdges = new Set();
393
- const paths = [];
394
- const allNodes = [];
395
- const allEdges = [];
396
- const startNodeResult = await this.getNode(options.startNodeId);
397
- if (!startNodeResult.success || !startNodeResult.data) {
398
- return this.failure(`Start node not found: ${options.startNodeId}`);
399
- }
400
- const startNode = startNodeResult.data;
401
- visited.add(startNode.id);
402
- allNodes.push(startNode);
403
- await this.traverseRecursive(startNode, [], [], 0, options, visited, visitedEdges, paths, allNodes, allEdges);
404
- let actualMaxDepth = 0;
405
- for (const p of paths) {
406
- if (p.length > actualMaxDepth)
407
- actualMaxDepth = p.length;
408
- }
409
- return this.success({
410
- paths,
411
- visitedNodes: allNodes,
412
- visitedEdges: allEdges,
413
- depth: actualMaxDepth,
409
+ return this.run(async () => {
410
+ const visited = new Set();
411
+ const visitedEdges = new Set();
412
+ const paths = [];
413
+ const allNodes = [];
414
+ const allEdges = [];
415
+ const startNode = unwrap(await this.getNode(options.startNodeId));
416
+ if (!startNode) {
417
+ return this.failure(`Start node not found: ${options.startNodeId}`);
418
+ }
419
+ visited.add(startNode.id);
420
+ allNodes.push(startNode);
421
+ await this.traverseRecursive(startNode, [], [], 0, options, visited, visitedEdges, paths, allNodes, allEdges);
422
+ let actualMaxDepth = 0;
423
+ for (const p of paths) {
424
+ if (p.length > actualMaxDepth)
425
+ actualMaxDepth = p.length;
426
+ }
427
+ return this.success({
428
+ paths,
429
+ visitedNodes: allNodes,
430
+ visitedEdges: allEdges,
431
+ depth: actualMaxDepth,
432
+ });
414
433
  });
415
434
  }
416
435
  async traverseRecursive(currentNode, pathNodes, pathEdges, currentDepth, options, visited, visitedEdges, paths, allNodes, allEdges) {
@@ -427,11 +446,9 @@ export class PostgresGraphAdapter {
427
446
  }
428
447
  if (options.limit && paths.length >= options.limit)
429
448
  return;
430
- const neighborsResult = await this.getNeighbors(currentNode.id, options.direction);
431
- if (!neighborsResult.success)
432
- return;
449
+ const neighbors = unwrap(await this.getNeighbors(currentNode.id, options.direction));
433
450
  let hasUnvisitedNeighbors = false;
434
- for (const { node, edge } of neighborsResult.data) {
451
+ for (const { node, edge } of neighbors) {
435
452
  if (options.edgeTypes && !options.edgeTypes.includes(edge.type))
436
453
  continue;
437
454
  if (options.minEdgeWeight !== undefined && edge.weight < options.minEdgeWeight)
@@ -459,196 +476,201 @@ export class PostgresGraphAdapter {
459
476
  }
460
477
  }
461
478
  async findShortestPath(agentId, startNodeId, endNodeId, maxDepth = 5) {
462
- await this.initialize();
463
- if (startNodeId === endNodeId) {
464
- const nodeResult = await this.getNode(startNodeId);
465
- if (!nodeResult.success || !nodeResult.data) {
466
- return this.failure(`Node not found: ${startNodeId}`);
479
+ return this.run(async () => {
480
+ if (startNodeId === endNodeId) {
481
+ const node = unwrap(await this.getNode(startNodeId));
482
+ if (!node) {
483
+ return this.failure(`Node not found: ${startNodeId}`);
484
+ }
485
+ return this.success({
486
+ nodes: [node],
487
+ edges: [],
488
+ totalWeight: 0,
489
+ length: 0,
490
+ });
467
491
  }
468
- return this.success({
469
- nodes: [nodeResult.data],
470
- edges: [],
471
- totalWeight: 0,
472
- length: 0,
473
- });
474
- }
475
- const result = await this.pool.query(`
476
- WITH RECURSIVE path_search AS (
477
- SELECT
478
- source_node_id as start_node,
479
- CASE
480
- WHEN source_node_id = $2 THEN target_node_id
481
- ELSE source_node_id
482
- END as end_node,
483
- ARRAY[$2, CASE WHEN source_node_id = $2 THEN target_node_id ELSE source_node_id END] as path,
484
- ARRAY[id] as edge_ids,
485
- weight as total_weight,
486
- 1 as depth
487
- FROM ${this.schema}.graph_edges
488
- WHERE agent_id = $1
489
- AND (source_node_id = $2 OR (target_node_id = $2 AND bidirectional = TRUE))
492
+ const result = await this.pool.query(`
493
+ WITH RECURSIVE path_search AS (
494
+ SELECT
495
+ source_node_id as start_node,
496
+ CASE
497
+ WHEN source_node_id = $2 THEN target_node_id
498
+ ELSE source_node_id
499
+ END as end_node,
500
+ ARRAY[$2, CASE WHEN source_node_id = $2 THEN target_node_id ELSE source_node_id END] as path,
501
+ ARRAY[id] as edge_ids,
502
+ weight as total_weight,
503
+ 1 as depth
504
+ FROM ${this.schema}.graph_edges
505
+ WHERE agent_id = $1
506
+ AND (source_node_id = $2 OR (target_node_id = $2 AND bidirectional = TRUE))
490
507
 
491
- UNION ALL
508
+ UNION ALL
492
509
 
493
- SELECT
494
- ps.start_node,
495
- CASE
496
- WHEN e.source_node_id = ps.end_node THEN e.target_node_id
497
- ELSE e.source_node_id
498
- END,
499
- ps.path || CASE WHEN e.source_node_id = ps.end_node THEN e.target_node_id ELSE e.source_node_id END,
500
- ps.edge_ids || e.id,
501
- ps.total_weight + e.weight,
502
- ps.depth + 1
503
- FROM path_search ps
504
- JOIN ${this.schema}.graph_edges e ON (
505
- e.source_node_id = ps.end_node
506
- OR (e.target_node_id = ps.end_node AND e.bidirectional = TRUE)
510
+ SELECT
511
+ ps.start_node,
512
+ CASE
513
+ WHEN e.source_node_id = ps.end_node THEN e.target_node_id
514
+ ELSE e.source_node_id
515
+ END,
516
+ ps.path || CASE WHEN e.source_node_id = ps.end_node THEN e.target_node_id ELSE e.source_node_id END,
517
+ ps.edge_ids || e.id,
518
+ ps.total_weight + e.weight,
519
+ ps.depth + 1
520
+ FROM path_search ps
521
+ JOIN ${this.schema}.graph_edges e ON (
522
+ e.source_node_id = ps.end_node
523
+ OR (e.target_node_id = ps.end_node AND e.bidirectional = TRUE)
524
+ )
525
+ WHERE e.agent_id = $1
526
+ AND NOT (CASE WHEN e.source_node_id = ps.end_node THEN e.target_node_id ELSE e.source_node_id END) = ANY(ps.path)
527
+ AND ps.depth < $4
507
528
  )
508
- WHERE e.agent_id = $1
509
- AND NOT (CASE WHEN e.source_node_id = ps.end_node THEN e.target_node_id ELSE e.source_node_id END) = ANY(ps.path)
510
- AND ps.depth < $4
511
- )
512
- SELECT path, edge_ids, total_weight
513
- FROM path_search
514
- WHERE end_node = $3
515
- ORDER BY depth, total_weight
516
- LIMIT 1
517
- `, [agentId, startNodeId, endNodeId, maxDepth]);
518
- if (result.rows.length === 0)
519
- return this.success(null);
520
- const row = result.rows[0];
521
- const nodeIds = row.path;
522
- const edgeIds = row.edge_ids;
523
- const nodes = [];
524
- const edges = [];
525
- for (const nodeId of nodeIds) {
526
- const nodeResult = await this.getNode(nodeId);
527
- if (nodeResult.success && nodeResult.data) {
528
- nodes.push(nodeResult.data);
529
+ SELECT path, edge_ids, total_weight
530
+ FROM path_search
531
+ WHERE end_node = $3
532
+ ORDER BY depth, total_weight
533
+ LIMIT 1
534
+ `, [agentId, startNodeId, endNodeId, maxDepth]);
535
+ if (result.rows.length === 0)
536
+ return this.success(null);
537
+ const row = result.rows[0];
538
+ const nodeIds = row.path;
539
+ const edgeIds = row.edge_ids;
540
+ const nodes = [];
541
+ const edges = [];
542
+ for (const nodeId of nodeIds) {
543
+ const node = unwrap(await this.getNode(nodeId));
544
+ if (node)
545
+ nodes.push(node);
529
546
  }
530
- }
531
- for (const edgeId of edgeIds) {
532
- const edgeResult = await this.getEdge(edgeId);
533
- if (edgeResult.success && edgeResult.data) {
534
- edges.push(edgeResult.data);
547
+ for (const edgeId of edgeIds) {
548
+ const edge = unwrap(await this.getEdge(edgeId));
549
+ if (edge)
550
+ edges.push(edge);
535
551
  }
536
- }
537
- return this.success({
538
- nodes,
539
- edges,
540
- totalWeight: row.total_weight,
541
- length: edges.length,
552
+ return this.success({
553
+ nodes,
554
+ edges,
555
+ totalWeight: row.total_weight,
556
+ length: edges.length,
557
+ });
542
558
  });
543
559
  }
544
560
  async getNeighbors(nodeId, direction = 'both') {
545
- await this.initialize();
546
- let sql;
547
- const params = [nodeId];
548
- const selectCols = `
549
- n.id, n.agent_id, n.type, n.name, n.aliases, n.description,
550
- n.properties, n.confidence, n.source, n.metadata,
551
- n.created_at, n.updated_at, n.last_accessed_at, n.access_count,
552
- e.id as edge_id, e.agent_id as edge_agent_id, e.type as edge_type,
553
- e.source_node_id, e.target_node_id, e.label, e.weight, e.bidirectional,
554
- e.properties as edge_properties, e.confidence as edge_confidence,
555
- e.source as edge_source, e.valid_from, e.valid_until,
556
- e.metadata as edge_metadata, e.created_at as edge_created_at, e.updated_at as edge_updated_at
557
- `;
558
- if (direction === 'outgoing') {
559
- sql = `
560
- SELECT ${selectCols}
561
- FROM ${this.schema}.graph_edges e
562
- JOIN ${this.schema}.graph_nodes n ON n.id = e.target_node_id
563
- WHERE e.source_node_id = $1
564
- UNION ALL
565
- SELECT ${selectCols}
566
- FROM ${this.schema}.graph_edges e
567
- JOIN ${this.schema}.graph_nodes n ON n.id = e.source_node_id
568
- WHERE e.target_node_id = $1 AND e.bidirectional = TRUE
561
+ return this.run(async () => {
562
+ let sql;
563
+ const params = [nodeId];
564
+ const selectCols = `
565
+ n.id, n.agent_id, n.type, n.name, n.aliases, n.description,
566
+ n.properties, n.confidence, n.source, n.metadata,
567
+ n.created_at, n.updated_at, n.last_accessed_at, n.access_count,
568
+ e.id as edge_id, e.agent_id as edge_agent_id, e.type as edge_type,
569
+ e.source_node_id, e.target_node_id, e.label, e.weight, e.bidirectional,
570
+ e.properties as edge_properties, e.confidence as edge_confidence,
571
+ e.source as edge_source, e.valid_from, e.valid_until,
572
+ e.metadata as edge_metadata, e.created_at as edge_created_at, e.updated_at as edge_updated_at
569
573
  `;
570
- }
571
- else if (direction === 'incoming') {
572
- sql = `
573
- SELECT ${selectCols}
574
- FROM ${this.schema}.graph_edges e
575
- JOIN ${this.schema}.graph_nodes n ON n.id = e.source_node_id
576
- WHERE e.target_node_id = $1
577
- UNION ALL
578
- SELECT ${selectCols}
579
- FROM ${this.schema}.graph_edges e
580
- JOIN ${this.schema}.graph_nodes n ON n.id = e.target_node_id
581
- WHERE e.source_node_id = $1 AND e.bidirectional = TRUE
582
- `;
583
- }
584
- else {
585
- sql = `
586
- SELECT ${selectCols}
587
- FROM ${this.schema}.graph_edges e
588
- JOIN ${this.schema}.graph_nodes n ON (
589
- (e.source_node_id = $1 AND n.id = e.target_node_id) OR
590
- (e.target_node_id = $1 AND n.id = e.source_node_id)
591
- )
592
- WHERE e.source_node_id = $1 OR e.target_node_id = $1
593
- `;
594
- }
595
- const result = await this.pool.query(sql, params);
596
- return this.success(result.rows.map((row) => ({
597
- node: this.rowToNode(row),
598
- edge: this.rowToEdgeFromJoin(row),
599
- })));
574
+ if (direction === 'outgoing') {
575
+ sql = `
576
+ SELECT ${selectCols}
577
+ FROM ${this.schema}.graph_edges e
578
+ JOIN ${this.schema}.graph_nodes n ON n.id = e.target_node_id
579
+ WHERE e.source_node_id = $1
580
+ UNION ALL
581
+ SELECT ${selectCols}
582
+ FROM ${this.schema}.graph_edges e
583
+ JOIN ${this.schema}.graph_nodes n ON n.id = e.source_node_id
584
+ WHERE e.target_node_id = $1 AND e.bidirectional = TRUE
585
+ `;
586
+ }
587
+ else if (direction === 'incoming') {
588
+ sql = `
589
+ SELECT ${selectCols}
590
+ FROM ${this.schema}.graph_edges e
591
+ JOIN ${this.schema}.graph_nodes n ON n.id = e.source_node_id
592
+ WHERE e.target_node_id = $1
593
+ UNION ALL
594
+ SELECT ${selectCols}
595
+ FROM ${this.schema}.graph_edges e
596
+ JOIN ${this.schema}.graph_nodes n ON n.id = e.target_node_id
597
+ WHERE e.source_node_id = $1 AND e.bidirectional = TRUE
598
+ `;
599
+ }
600
+ else {
601
+ sql = `
602
+ SELECT ${selectCols}
603
+ FROM ${this.schema}.graph_edges e
604
+ JOIN ${this.schema}.graph_nodes n ON (
605
+ (e.source_node_id = $1 AND n.id = e.target_node_id) OR
606
+ (e.target_node_id = $1 AND n.id = e.source_node_id)
607
+ )
608
+ WHERE e.source_node_id = $1 OR e.target_node_id = $1
609
+ `;
610
+ }
611
+ const result = await this.pool.query(sql, params);
612
+ return this.success(result.rows.map((row) => ({
613
+ node: this.rowToNode(row),
614
+ edge: this.rowToEdgeFromJoin(row),
615
+ })));
616
+ });
600
617
  }
601
618
  async mergeNodes(targetNodeId, sourceNodeIds) {
602
- await this.initialize();
603
- for (const sourceId of sourceNodeIds) {
604
- const sourceNode = await this.getNode(sourceId);
605
- await this.pool.query(`UPDATE ${this.schema}.graph_edges
606
- SET source_node_id = $1 WHERE source_node_id = $2`, [targetNodeId, sourceId]);
607
- await this.pool.query(`UPDATE ${this.schema}.graph_edges
608
- SET target_node_id = $1 WHERE target_node_id = $2`, [targetNodeId, sourceId]);
609
- await this.pool.query(`DELETE FROM ${this.schema}.graph_edges
610
- WHERE source_node_id = $1 AND target_node_id = $1`, [targetNodeId]);
611
- if (sourceNode.success && sourceNode.data) {
612
- await this.pool.query(`UPDATE ${this.schema}.graph_nodes
613
- SET aliases = array_cat(aliases, $1::TEXT[])
614
- WHERE id = $2`, [[sourceNode.data.name, ...sourceNode.data.aliases], targetNodeId]);
615
- }
616
- await this.deleteNode(sourceId);
617
- }
618
- const mergedNode = await this.getNode(targetNodeId);
619
- if (!mergedNode.success || !mergedNode.data) {
620
- return this.failure(`Target node not found: ${targetNodeId}`);
621
- }
622
- return this.success(mergedNode.data);
619
+ return this.run(async () => {
620
+ for (const sourceId of sourceNodeIds) {
621
+ const sourceNode = unwrap(await this.getNode(sourceId));
622
+ await this.pool.query(`UPDATE ${this.schema}.graph_edges
623
+ SET source_node_id = $1 WHERE source_node_id = $2`, [targetNodeId, sourceId]);
624
+ await this.pool.query(`UPDATE ${this.schema}.graph_edges
625
+ SET target_node_id = $1 WHERE target_node_id = $2`, [targetNodeId, sourceId]);
626
+ await this.pool.query(`DELETE FROM ${this.schema}.graph_edges
627
+ WHERE source_node_id = $1 AND target_node_id = $1`, [targetNodeId]);
628
+ if (sourceNode) {
629
+ await this.pool.query(`UPDATE ${this.schema}.graph_nodes
630
+ SET aliases = array_cat(aliases, $1::TEXT[])
631
+ WHERE id = $2`, [[sourceNode.name, ...sourceNode.aliases], targetNodeId]);
632
+ }
633
+ unwrap(await this.deleteNode(sourceId));
634
+ }
635
+ const mergedNode = unwrap(await this.getNode(targetNodeId));
636
+ if (!mergedNode) {
637
+ return this.failure(`Target node not found: ${targetNodeId}`);
638
+ }
639
+ return this.success(mergedNode);
640
+ });
623
641
  }
624
642
  async clearGraph(agentId) {
625
- await this.initialize();
626
- await this.pool.query(`DELETE FROM ${this.schema}.graph_nodes WHERE agent_id = $1`, [agentId]);
627
- return this.success(undefined);
643
+ return this.run(async () => {
644
+ await this.pool.query(`DELETE FROM ${this.schema}.graph_nodes WHERE agent_id = $1`, [
645
+ agentId,
646
+ ]);
647
+ return this.success(undefined);
648
+ });
628
649
  }
629
650
  async getGraphStats(agentId) {
630
- await this.initialize();
631
- const nodeCountResult = await this.pool.query(`SELECT COUNT(*) as count FROM ${this.schema}.graph_nodes WHERE agent_id = $1`, [agentId]);
632
- const edgeCountResult = await this.pool.query(`SELECT COUNT(*) as count FROM ${this.schema}.graph_edges WHERE agent_id = $1`, [agentId]);
633
- const nodesByTypeResult = await this.pool.query(`SELECT type, COUNT(*) as count FROM ${this.schema}.graph_nodes WHERE agent_id = $1 GROUP BY type`, [agentId]);
634
- const edgesByTypeResult = await this.pool.query(`SELECT type, COUNT(*) as count FROM ${this.schema}.graph_edges WHERE agent_id = $1 GROUP BY type`, [agentId]);
635
- const nodeCount = parseInt(nodeCountResult.rows[0]?.count ?? '0', 10);
636
- const edgeCount = parseInt(edgeCountResult.rows[0]?.count ?? '0', 10);
637
- const nodesByType = {};
638
- for (const row of nodesByTypeResult.rows) {
639
- nodesByType[row.type] = parseInt(row.count, 10);
640
- }
641
- const edgesByType = {};
642
- for (const row of edgesByTypeResult.rows) {
643
- edgesByType[row.type] = parseInt(row.count, 10);
644
- }
645
- return this.success({
646
- nodeCount,
647
- edgeCount,
648
- nodesByType,
649
- edgesByType,
650
- averageEdgesPerNode: nodeCount > 0 ? edgeCount / nodeCount : 0,
651
- maxDepth: 0,
651
+ return this.run(async () => {
652
+ const nodeCountResult = await this.pool.query(`SELECT COUNT(*) as count FROM ${this.schema}.graph_nodes WHERE agent_id = $1`, [agentId]);
653
+ const edgeCountResult = await this.pool.query(`SELECT COUNT(*) as count FROM ${this.schema}.graph_edges WHERE agent_id = $1`, [agentId]);
654
+ const nodesByTypeResult = await this.pool.query(`SELECT type, COUNT(*) as count FROM ${this.schema}.graph_nodes WHERE agent_id = $1 GROUP BY type`, [agentId]);
655
+ const edgesByTypeResult = await this.pool.query(`SELECT type, COUNT(*) as count FROM ${this.schema}.graph_edges WHERE agent_id = $1 GROUP BY type`, [agentId]);
656
+ const nodeCount = parseInt(nodeCountResult.rows[0]?.count ?? '0', 10);
657
+ const edgeCount = parseInt(edgeCountResult.rows[0]?.count ?? '0', 10);
658
+ const nodesByType = {};
659
+ for (const row of nodesByTypeResult.rows) {
660
+ nodesByType[row.type] = parseInt(row.count, 10);
661
+ }
662
+ const edgesByType = {};
663
+ for (const row of edgesByTypeResult.rows) {
664
+ edgesByType[row.type] = parseInt(row.count, 10);
665
+ }
666
+ return this.success({
667
+ nodeCount,
668
+ edgeCount,
669
+ nodesByType,
670
+ edgesByType,
671
+ averageEdgesPerNode: nodeCount > 0 ? edgeCount / nodeCount : 0,
672
+ maxDepth: 0,
673
+ });
652
674
  });
653
675
  }
654
676
  rowToNode(row, includeEmbedding = false) {