agent-memory-graph 0.1.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/LICENSE +21 -0
- package/README.md +341 -0
- package/config/default.json +28 -0
- package/config/graph.config.json +28 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +303 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/plugin/entry.d.ts +3 -0
- package/dist/plugin/entry.d.ts.map +1 -0
- package/dist/plugin/entry.js +1652 -0
- package/dist/plugin/entry.js.map +1 -0
- package/dist/src/config/defaults.d.ts +8 -0
- package/dist/src/config/defaults.d.ts.map +1 -0
- package/dist/src/config/defaults.js +31 -0
- package/dist/src/config/defaults.js.map +1 -0
- package/dist/src/config/schema.d.ts +162 -0
- package/dist/src/config/schema.d.ts.map +1 -0
- package/dist/src/config/schema.js +39 -0
- package/dist/src/config/schema.js.map +1 -0
- package/dist/src/extract/dedup.d.ts +14 -0
- package/dist/src/extract/dedup.d.ts.map +1 -0
- package/dist/src/extract/dedup.js +79 -0
- package/dist/src/extract/dedup.js.map +1 -0
- package/dist/src/extract/extractor.d.ts +24 -0
- package/dist/src/extract/extractor.d.ts.map +1 -0
- package/dist/src/extract/extractor.js +162 -0
- package/dist/src/extract/extractor.js.map +1 -0
- package/dist/src/graph/engine.d.ts +90 -0
- package/dist/src/graph/engine.d.ts.map +1 -0
- package/dist/src/graph/engine.js +307 -0
- package/dist/src/graph/engine.js.map +1 -0
- package/dist/src/graph/schema.d.ts +12 -0
- package/dist/src/graph/schema.d.ts.map +1 -0
- package/dist/src/graph/schema.js +115 -0
- package/dist/src/graph/schema.js.map +1 -0
- package/dist/src/index.d.ts +129 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +174 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/search/hybrid.d.ts +22 -0
- package/dist/src/search/hybrid.d.ts.map +1 -0
- package/dist/src/search/hybrid.js +38 -0
- package/dist/src/search/hybrid.js.map +1 -0
- package/dist/src/search/natural-language.d.ts +20 -0
- package/dist/src/search/natural-language.d.ts.map +1 -0
- package/dist/src/search/natural-language.js +429 -0
- package/dist/src/search/natural-language.js.map +1 -0
- package/dist/src/sync/export.d.ts +12 -0
- package/dist/src/sync/export.d.ts.map +1 -0
- package/dist/src/sync/export.js +117 -0
- package/dist/src/sync/export.js.map +1 -0
- package/dist/src/sync/memory-md.d.ts +19 -0
- package/dist/src/sync/memory-md.d.ts.map +1 -0
- package/dist/src/sync/memory-md.js +78 -0
- package/dist/src/sync/memory-md.js.map +1 -0
- package/openclaw.plugin.json +55 -0
- package/package.json +90 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export interface Entity {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
type: string;
|
|
5
|
+
properties: Record<string, unknown>;
|
|
6
|
+
created_at: string;
|
|
7
|
+
updated_at: string;
|
|
8
|
+
source?: string;
|
|
9
|
+
confidence: number;
|
|
10
|
+
}
|
|
11
|
+
export interface Relationship {
|
|
12
|
+
id: string;
|
|
13
|
+
from_id: string;
|
|
14
|
+
to_id: string;
|
|
15
|
+
relation: string;
|
|
16
|
+
properties: Record<string, unknown>;
|
|
17
|
+
created_at: string;
|
|
18
|
+
updated_at: string;
|
|
19
|
+
source?: string;
|
|
20
|
+
confidence: number;
|
|
21
|
+
}
|
|
22
|
+
export interface GraphStats {
|
|
23
|
+
entities: number;
|
|
24
|
+
relationships: number;
|
|
25
|
+
entityTypes: string[];
|
|
26
|
+
relationTypes: string[];
|
|
27
|
+
oldestEntry: string | null;
|
|
28
|
+
newestEntry: string | null;
|
|
29
|
+
}
|
|
30
|
+
export declare class GraphEngine {
|
|
31
|
+
private db;
|
|
32
|
+
constructor(dbPath: string);
|
|
33
|
+
addEntity(name: string, type: string, properties?: Record<string, unknown>, options?: {
|
|
34
|
+
source?: string;
|
|
35
|
+
confidence?: number;
|
|
36
|
+
}): Entity;
|
|
37
|
+
getEntity(id: string): Entity | null;
|
|
38
|
+
findEntityByName(name: string, type?: string): Entity | null;
|
|
39
|
+
updateEntity(id: string, updates: {
|
|
40
|
+
name?: string;
|
|
41
|
+
type?: string;
|
|
42
|
+
properties?: Record<string, unknown>;
|
|
43
|
+
source?: string;
|
|
44
|
+
confidence?: number;
|
|
45
|
+
}): Entity;
|
|
46
|
+
deleteEntity(id: string): boolean;
|
|
47
|
+
listEntities(options?: {
|
|
48
|
+
type?: string;
|
|
49
|
+
limit?: number;
|
|
50
|
+
offset?: number;
|
|
51
|
+
}): Entity[];
|
|
52
|
+
addRelation(fromName: string, relation: string, toName: string, options?: {
|
|
53
|
+
properties?: Record<string, unknown>;
|
|
54
|
+
source?: string;
|
|
55
|
+
confidence?: number;
|
|
56
|
+
fromType?: string;
|
|
57
|
+
toType?: string;
|
|
58
|
+
}): Relationship;
|
|
59
|
+
getRelationsFrom(entityId: string): (Relationship & {
|
|
60
|
+
to_name: string;
|
|
61
|
+
to_type: string;
|
|
62
|
+
})[];
|
|
63
|
+
getRelationsTo(entityId: string): (Relationship & {
|
|
64
|
+
from_name: string;
|
|
65
|
+
from_type: string;
|
|
66
|
+
})[];
|
|
67
|
+
deleteRelation(id: string): boolean;
|
|
68
|
+
searchEntities(query: string, limit?: number): Entity[];
|
|
69
|
+
/**
|
|
70
|
+
* Find path between two entities (BFS, max depth)
|
|
71
|
+
*/
|
|
72
|
+
findPath(fromName: string, toName: string, maxHops?: number): {
|
|
73
|
+
path: string[];
|
|
74
|
+
relations: string[];
|
|
75
|
+
} | null;
|
|
76
|
+
/**
|
|
77
|
+
* Get neighborhood of an entity (all connected within N hops)
|
|
78
|
+
*/
|
|
79
|
+
getNeighborhood(entityName: string, hops?: number): {
|
|
80
|
+
entities: Entity[];
|
|
81
|
+
relationships: Relationship[];
|
|
82
|
+
};
|
|
83
|
+
stats(): GraphStats;
|
|
84
|
+
logExtraction(rawText: string, entities: any[], relations: any[], sessionId?: string): void;
|
|
85
|
+
private rowToEntity;
|
|
86
|
+
private rowToRelationship;
|
|
87
|
+
/** Close database */
|
|
88
|
+
close(): void;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/graph/engine.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,EAAE,CAAoB;gBAElB,MAAM,EAAE,MAAM;IAO1B,SAAS,CACP,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,UAAU,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACxC,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAO,GACrD,MAAM;IA2BT,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAKpC,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAY5D,YAAY,CACV,EAAE,EAAE,MAAM,EACV,OAAO,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,GACpH,MAAM;IA8BT,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAKjC,YAAY,CAAC,OAAO,GAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,MAAM,EAAE;IAgBxF,WAAW,CACT,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAO,GAC/H,YAAY;IAoDf,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,CAAC,YAAY,GAAG;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,EAAE;IAY3F,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,CAAC,YAAY,GAAG;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,EAAE;IAY7F,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAOnC,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,MAAM,EAAE;IAmCnD;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,SAAI,GAAG;QAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAAC,SAAS,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,IAAI;IAuDvG;;OAEG;IACH,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,SAAI,GAAG;QAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;QAAC,aAAa,EAAE,YAAY,EAAE,CAAA;KAAE;IA6CpG,KAAK,IAAI,UAAU;IAwBnB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAU3F,OAAO,CAAC,WAAW;IAanB,OAAO,CAAC,iBAAiB;IAczB,qBAAqB;IACrB,KAAK,IAAI,IAAI;CAGd"}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { nanoid } from 'nanoid';
|
|
2
|
+
import { SchemaManager } from './schema.js';
|
|
3
|
+
export class GraphEngine {
|
|
4
|
+
db;
|
|
5
|
+
constructor(dbPath) {
|
|
6
|
+
const schema = new SchemaManager(dbPath);
|
|
7
|
+
this.db = schema.initialize();
|
|
8
|
+
}
|
|
9
|
+
// ─── Entity CRUD ───────────────────────────────────────────────
|
|
10
|
+
addEntity(name, type, properties = {}, options = {}) {
|
|
11
|
+
const existing = this.findEntityByName(name, type);
|
|
12
|
+
if (existing) {
|
|
13
|
+
// Update existing entity
|
|
14
|
+
return this.updateEntity(existing.id, { properties, ...options });
|
|
15
|
+
}
|
|
16
|
+
const id = `e-${nanoid(12)}`;
|
|
17
|
+
const now = new Date().toISOString();
|
|
18
|
+
this.db.prepare(`
|
|
19
|
+
INSERT INTO entities (id, name, type, properties, created_at, updated_at, source, confidence)
|
|
20
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
21
|
+
`).run(id, name, type, JSON.stringify(properties), now, now, options.source ?? null, options.confidence ?? 1.0);
|
|
22
|
+
return { id, name, type, properties, created_at: now, updated_at: now, source: options.source, confidence: options.confidence ?? 1.0 };
|
|
23
|
+
}
|
|
24
|
+
getEntity(id) {
|
|
25
|
+
const row = this.db.prepare(`SELECT * FROM entities WHERE id = ?`).get(id);
|
|
26
|
+
return row ? this.rowToEntity(row) : null;
|
|
27
|
+
}
|
|
28
|
+
findEntityByName(name, type) {
|
|
29
|
+
const query = type
|
|
30
|
+
? `SELECT * FROM entities WHERE name = ? COLLATE NOCASE AND type = ? COLLATE NOCASE LIMIT 1`
|
|
31
|
+
: `SELECT * FROM entities WHERE name = ? COLLATE NOCASE LIMIT 1`;
|
|
32
|
+
const row = type
|
|
33
|
+
? this.db.prepare(query).get(name, type)
|
|
34
|
+
: this.db.prepare(query).get(name);
|
|
35
|
+
return row ? this.rowToEntity(row) : null;
|
|
36
|
+
}
|
|
37
|
+
updateEntity(id, updates) {
|
|
38
|
+
const existing = this.getEntity(id);
|
|
39
|
+
if (!existing)
|
|
40
|
+
throw new Error(`Entity ${id} not found`);
|
|
41
|
+
const merged = {
|
|
42
|
+
name: updates.name ?? existing.name,
|
|
43
|
+
type: updates.type ?? existing.type,
|
|
44
|
+
properties: updates.properties ? { ...existing.properties, ...updates.properties } : existing.properties,
|
|
45
|
+
source: updates.source ?? existing.source,
|
|
46
|
+
confidence: updates.confidence ?? existing.confidence,
|
|
47
|
+
};
|
|
48
|
+
const now = new Date().toISOString();
|
|
49
|
+
this.db.prepare(`
|
|
50
|
+
UPDATE entities SET name = ?, type = ?, properties = ?, source = ?, confidence = ?, updated_at = ?
|
|
51
|
+
WHERE id = ?
|
|
52
|
+
`).run(merged.name, merged.type, JSON.stringify(merged.properties), merged.source ?? null, merged.confidence, now, id);
|
|
53
|
+
return { ...existing, ...merged, updated_at: now };
|
|
54
|
+
}
|
|
55
|
+
deleteEntity(id) {
|
|
56
|
+
const result = this.db.prepare(`DELETE FROM entities WHERE id = ?`).run(id);
|
|
57
|
+
return result.changes > 0;
|
|
58
|
+
}
|
|
59
|
+
listEntities(options = {}) {
|
|
60
|
+
const { type, limit = 100, offset = 0 } = options;
|
|
61
|
+
const query = type
|
|
62
|
+
? `SELECT * FROM entities WHERE type = ? COLLATE NOCASE ORDER BY updated_at DESC LIMIT ? OFFSET ?`
|
|
63
|
+
: `SELECT * FROM entities ORDER BY updated_at DESC LIMIT ? OFFSET ?`;
|
|
64
|
+
const rows = type
|
|
65
|
+
? this.db.prepare(query).all(type, limit, offset)
|
|
66
|
+
: this.db.prepare(query).all(limit, offset);
|
|
67
|
+
return rows.map(r => this.rowToEntity(r));
|
|
68
|
+
}
|
|
69
|
+
// ─── Relationship CRUD ─────────────────────────────────────────
|
|
70
|
+
addRelation(fromName, relation, toName, options = {}) {
|
|
71
|
+
// Resolve entities by name (create if not exist)
|
|
72
|
+
let fromEntity = this.findEntityByName(fromName);
|
|
73
|
+
if (!fromEntity) {
|
|
74
|
+
fromEntity = this.addEntity(fromName, options.fromType ?? 'Unknown', {}, { source: options.source });
|
|
75
|
+
}
|
|
76
|
+
let toEntity = this.findEntityByName(toName);
|
|
77
|
+
if (!toEntity) {
|
|
78
|
+
toEntity = this.addEntity(toName, options.toType ?? 'Unknown', {}, { source: options.source });
|
|
79
|
+
}
|
|
80
|
+
// Check for existing relationship
|
|
81
|
+
const existing = this.db.prepare(`
|
|
82
|
+
SELECT * FROM relationships WHERE from_id = ? AND to_id = ? AND relation = ? COLLATE NOCASE LIMIT 1
|
|
83
|
+
`).get(fromEntity.id, toEntity.id, relation);
|
|
84
|
+
if (existing) {
|
|
85
|
+
// Update confidence/properties
|
|
86
|
+
const now = new Date().toISOString();
|
|
87
|
+
const mergedProps = { ...JSON.parse(existing.properties || '{}'), ...(options.properties ?? {}) };
|
|
88
|
+
this.db.prepare(`
|
|
89
|
+
UPDATE relationships SET properties = ?, confidence = ?, updated_at = ? WHERE id = ?
|
|
90
|
+
`).run(JSON.stringify(mergedProps), options.confidence ?? existing.confidence, now, existing.id);
|
|
91
|
+
return this.rowToRelationship({ ...existing, properties: JSON.stringify(mergedProps), updated_at: now });
|
|
92
|
+
}
|
|
93
|
+
const id = `r-${nanoid(12)}`;
|
|
94
|
+
const now = new Date().toISOString();
|
|
95
|
+
this.db.prepare(`
|
|
96
|
+
INSERT INTO relationships (id, from_id, to_id, relation, properties, created_at, updated_at, source, confidence)
|
|
97
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
98
|
+
`).run(id, fromEntity.id, toEntity.id, relation, JSON.stringify(options.properties ?? {}), now, now, options.source ?? null, options.confidence ?? 1.0);
|
|
99
|
+
return {
|
|
100
|
+
id, from_id: fromEntity.id, to_id: toEntity.id, relation,
|
|
101
|
+
properties: options.properties ?? {}, created_at: now, updated_at: now,
|
|
102
|
+
source: options.source, confidence: options.confidence ?? 1.0
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
getRelationsFrom(entityId) {
|
|
106
|
+
const rows = this.db.prepare(`
|
|
107
|
+
SELECT r.*, e.name as to_name, e.type as to_type
|
|
108
|
+
FROM relationships r
|
|
109
|
+
JOIN entities e ON r.to_id = e.id
|
|
110
|
+
WHERE r.from_id = ?
|
|
111
|
+
ORDER BY r.updated_at DESC
|
|
112
|
+
`).all(entityId);
|
|
113
|
+
return rows.map(r => ({ ...this.rowToRelationship(r), to_name: r.to_name, to_type: r.to_type }));
|
|
114
|
+
}
|
|
115
|
+
getRelationsTo(entityId) {
|
|
116
|
+
const rows = this.db.prepare(`
|
|
117
|
+
SELECT r.*, e.name as from_name, e.type as from_type
|
|
118
|
+
FROM relationships r
|
|
119
|
+
JOIN entities e ON r.from_id = e.id
|
|
120
|
+
WHERE r.to_id = ?
|
|
121
|
+
ORDER BY r.updated_at DESC
|
|
122
|
+
`).all(entityId);
|
|
123
|
+
return rows.map(r => ({ ...this.rowToRelationship(r), from_name: r.from_name, from_type: r.from_type }));
|
|
124
|
+
}
|
|
125
|
+
deleteRelation(id) {
|
|
126
|
+
const result = this.db.prepare(`DELETE FROM relationships WHERE id = ?`).run(id);
|
|
127
|
+
return result.changes > 0;
|
|
128
|
+
}
|
|
129
|
+
// ─── Search ────────────────────────────────────────────────────
|
|
130
|
+
searchEntities(query, limit = 10) {
|
|
131
|
+
// Sanitize query for FTS5 (remove special characters)
|
|
132
|
+
const sanitized = query.replace(/[?!@#$%^&*(){}\[\]<>:;"'`~|/\\+=]/g, ' ').trim();
|
|
133
|
+
if (sanitized.length > 0) {
|
|
134
|
+
// Try FTS first
|
|
135
|
+
try {
|
|
136
|
+
const ftsRows = this.db.prepare(`
|
|
137
|
+
SELECT e.* FROM entities_fts fts
|
|
138
|
+
JOIN entities e ON e.rowid = fts.rowid
|
|
139
|
+
WHERE entities_fts MATCH ?
|
|
140
|
+
LIMIT ?
|
|
141
|
+
`).all(sanitized, limit);
|
|
142
|
+
if (ftsRows.length > 0) {
|
|
143
|
+
return ftsRows.map(r => this.rowToEntity(r));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// FTS query failed, fall through to LIKE
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Fallback to LIKE search
|
|
151
|
+
const likeQuery = sanitized.length > 0 ? sanitized : query;
|
|
152
|
+
const likeRows = this.db.prepare(`
|
|
153
|
+
SELECT * FROM entities
|
|
154
|
+
WHERE name LIKE ? COLLATE NOCASE OR type LIKE ? COLLATE NOCASE
|
|
155
|
+
LIMIT ?
|
|
156
|
+
`).all(`%${likeQuery}%`, `%${likeQuery}%`, limit);
|
|
157
|
+
return likeRows.map(r => this.rowToEntity(r));
|
|
158
|
+
}
|
|
159
|
+
// ─── Graph Traversal ───────────────────────────────────────────
|
|
160
|
+
/**
|
|
161
|
+
* Find path between two entities (BFS, max depth)
|
|
162
|
+
*/
|
|
163
|
+
findPath(fromName, toName, maxHops = 3) {
|
|
164
|
+
const fromEntity = this.findEntityByName(fromName);
|
|
165
|
+
const toEntity = this.findEntityByName(toName);
|
|
166
|
+
if (!fromEntity || !toEntity)
|
|
167
|
+
return null;
|
|
168
|
+
// BFS
|
|
169
|
+
const queue = [
|
|
170
|
+
{ entityId: fromEntity.id, path: [fromEntity.name], relations: [] }
|
|
171
|
+
];
|
|
172
|
+
const visited = new Set([fromEntity.id]);
|
|
173
|
+
while (queue.length > 0) {
|
|
174
|
+
const current = queue.shift();
|
|
175
|
+
if (current.path.length > maxHops + 1)
|
|
176
|
+
break;
|
|
177
|
+
// Get all neighbors (both directions)
|
|
178
|
+
const outgoing = this.db.prepare(`
|
|
179
|
+
SELECT r.relation, r.to_id as neighbor_id, e.name as neighbor_name
|
|
180
|
+
FROM relationships r JOIN entities e ON r.to_id = e.id
|
|
181
|
+
WHERE r.from_id = ?
|
|
182
|
+
`).all(current.entityId);
|
|
183
|
+
const incoming = this.db.prepare(`
|
|
184
|
+
SELECT r.relation, r.from_id as neighbor_id, e.name as neighbor_name
|
|
185
|
+
FROM relationships r JOIN entities e ON r.from_id = e.id
|
|
186
|
+
WHERE r.to_id = ?
|
|
187
|
+
`).all(current.entityId);
|
|
188
|
+
const neighbors = [
|
|
189
|
+
...outgoing.map((n) => ({ ...n, direction: '->' })),
|
|
190
|
+
...incoming.map((n) => ({ ...n, direction: '<-' })),
|
|
191
|
+
];
|
|
192
|
+
for (const neighbor of neighbors) {
|
|
193
|
+
if (neighbor.neighbor_id === toEntity.id) {
|
|
194
|
+
return {
|
|
195
|
+
path: [...current.path, neighbor.neighbor_name],
|
|
196
|
+
relations: [...current.relations, `${neighbor.direction}[${neighbor.relation}]`],
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
if (!visited.has(neighbor.neighbor_id)) {
|
|
200
|
+
visited.add(neighbor.neighbor_id);
|
|
201
|
+
queue.push({
|
|
202
|
+
entityId: neighbor.neighbor_id,
|
|
203
|
+
path: [...current.path, neighbor.neighbor_name],
|
|
204
|
+
relations: [...current.relations, `${neighbor.direction}[${neighbor.relation}]`],
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Get neighborhood of an entity (all connected within N hops)
|
|
213
|
+
*/
|
|
214
|
+
getNeighborhood(entityName, hops = 1) {
|
|
215
|
+
const entity = this.findEntityByName(entityName);
|
|
216
|
+
if (!entity)
|
|
217
|
+
return { entities: [], relationships: [] };
|
|
218
|
+
const entityIds = new Set([entity.id]);
|
|
219
|
+
const relIds = new Set();
|
|
220
|
+
let frontier = [entity.id];
|
|
221
|
+
for (let i = 0; i < hops; i++) {
|
|
222
|
+
const nextFrontier = [];
|
|
223
|
+
for (const nodeId of frontier) {
|
|
224
|
+
const rels = this.db.prepare(`
|
|
225
|
+
SELECT * FROM relationships WHERE from_id = ? OR to_id = ?
|
|
226
|
+
`).all(nodeId, nodeId);
|
|
227
|
+
for (const rel of rels) {
|
|
228
|
+
relIds.add(rel.id);
|
|
229
|
+
const neighborId = rel.from_id === nodeId ? rel.to_id : rel.from_id;
|
|
230
|
+
if (!entityIds.has(neighborId)) {
|
|
231
|
+
entityIds.add(neighborId);
|
|
232
|
+
nextFrontier.push(neighborId);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
frontier = nextFrontier;
|
|
237
|
+
}
|
|
238
|
+
const entities = [...entityIds]
|
|
239
|
+
.map(id => this.getEntity(id))
|
|
240
|
+
.filter((e) => e !== null);
|
|
241
|
+
const relationships = [...relIds]
|
|
242
|
+
.map(id => {
|
|
243
|
+
const row = this.db.prepare(`SELECT * FROM relationships WHERE id = ?`).get(id);
|
|
244
|
+
return row ? this.rowToRelationship(row) : null;
|
|
245
|
+
})
|
|
246
|
+
.filter((r) => r !== null);
|
|
247
|
+
return { entities, relationships };
|
|
248
|
+
}
|
|
249
|
+
// ─── Stats ─────────────────────────────────────────────────────
|
|
250
|
+
stats() {
|
|
251
|
+
const entityCount = this.db.prepare(`SELECT COUNT(*) as c FROM entities`).get().c;
|
|
252
|
+
const relCount = this.db.prepare(`SELECT COUNT(*) as c FROM relationships`).get().c;
|
|
253
|
+
const entityTypes = this.db.prepare(`SELECT DISTINCT type FROM entities ORDER BY type`).all()
|
|
254
|
+
.map(r => r.type);
|
|
255
|
+
const relationTypes = this.db.prepare(`SELECT DISTINCT relation FROM relationships ORDER BY relation`).all()
|
|
256
|
+
.map(r => r.relation);
|
|
257
|
+
const oldest = this.db.prepare(`SELECT MIN(created_at) as t FROM entities`).get();
|
|
258
|
+
const newest = this.db.prepare(`SELECT MAX(updated_at) as t FROM entities`).get();
|
|
259
|
+
return {
|
|
260
|
+
entities: entityCount,
|
|
261
|
+
relationships: relCount,
|
|
262
|
+
entityTypes,
|
|
263
|
+
relationTypes,
|
|
264
|
+
oldestEntry: oldest?.t ?? null,
|
|
265
|
+
newestEntry: newest?.t ?? null,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
// ─── Memory Log ────────────────────────────────────────────────
|
|
269
|
+
logExtraction(rawText, entities, relations, sessionId) {
|
|
270
|
+
const id = `log-${nanoid(12)}`;
|
|
271
|
+
this.db.prepare(`
|
|
272
|
+
INSERT INTO memory_log (id, raw_text, extracted_entities, extracted_relations, session_id)
|
|
273
|
+
VALUES (?, ?, ?, ?, ?)
|
|
274
|
+
`).run(id, rawText, JSON.stringify(entities), JSON.stringify(relations), sessionId ?? null);
|
|
275
|
+
}
|
|
276
|
+
// ─── Helpers ───────────────────────────────────────────────────
|
|
277
|
+
rowToEntity(row) {
|
|
278
|
+
return {
|
|
279
|
+
id: row.id,
|
|
280
|
+
name: row.name,
|
|
281
|
+
type: row.type,
|
|
282
|
+
properties: JSON.parse(row.properties || '{}'),
|
|
283
|
+
created_at: row.created_at,
|
|
284
|
+
updated_at: row.updated_at,
|
|
285
|
+
source: row.source ?? undefined,
|
|
286
|
+
confidence: row.confidence,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
rowToRelationship(row) {
|
|
290
|
+
return {
|
|
291
|
+
id: row.id,
|
|
292
|
+
from_id: row.from_id,
|
|
293
|
+
to_id: row.to_id,
|
|
294
|
+
relation: row.relation,
|
|
295
|
+
properties: JSON.parse(row.properties || '{}'),
|
|
296
|
+
created_at: row.created_at,
|
|
297
|
+
updated_at: row.updated_at,
|
|
298
|
+
source: row.source ?? undefined,
|
|
299
|
+
confidence: row.confidence,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
/** Close database */
|
|
303
|
+
close() {
|
|
304
|
+
this.db.close();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../../src/graph/engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
export declare class SchemaManager {
|
|
3
|
+
private db;
|
|
4
|
+
constructor(dbPath: string);
|
|
5
|
+
/** Initialize schema (idempotent) */
|
|
6
|
+
initialize(): Database.Database;
|
|
7
|
+
/** Get current schema version */
|
|
8
|
+
getVersion(): number;
|
|
9
|
+
/** Close database connection */
|
|
10
|
+
close(): void;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/graph/schema.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAoFtC,qBAAa,aAAa;IACxB,OAAO,CAAC,EAAE,CAAoB;gBAElB,MAAM,EAAE,MAAM;IAU1B,qCAAqC;IACrC,UAAU,IAAI,QAAQ,CAAC,QAAQ;IAY/B,iCAAiC;IACjC,UAAU,IAAI,MAAM;IAWpB,gCAAgC;IAChC,KAAK,IAAI,IAAI;CAGd"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { mkdirSync } from 'node:fs';
|
|
4
|
+
const SCHEMA_VERSION = 1;
|
|
5
|
+
const SCHEMA_SQL = `
|
|
6
|
+
-- Schema version tracking
|
|
7
|
+
CREATE TABLE IF NOT EXISTS _meta (
|
|
8
|
+
key TEXT PRIMARY KEY,
|
|
9
|
+
value TEXT
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
-- Entities (graph nodes)
|
|
13
|
+
CREATE TABLE IF NOT EXISTS entities (
|
|
14
|
+
id TEXT PRIMARY KEY,
|
|
15
|
+
name TEXT NOT NULL,
|
|
16
|
+
type TEXT NOT NULL,
|
|
17
|
+
properties TEXT DEFAULT '{}',
|
|
18
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
19
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
20
|
+
source TEXT,
|
|
21
|
+
confidence REAL DEFAULT 1.0
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
-- Relationships (graph edges)
|
|
25
|
+
CREATE TABLE IF NOT EXISTS relationships (
|
|
26
|
+
id TEXT PRIMARY KEY,
|
|
27
|
+
from_id TEXT NOT NULL REFERENCES entities(id) ON DELETE CASCADE,
|
|
28
|
+
to_id TEXT NOT NULL REFERENCES entities(id) ON DELETE CASCADE,
|
|
29
|
+
relation TEXT NOT NULL,
|
|
30
|
+
properties TEXT DEFAULT '{}',
|
|
31
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
32
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
33
|
+
source TEXT,
|
|
34
|
+
confidence REAL DEFAULT 1.0
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
-- Memory log (audit trail of extractions)
|
|
38
|
+
CREATE TABLE IF NOT EXISTS memory_log (
|
|
39
|
+
id TEXT PRIMARY KEY,
|
|
40
|
+
raw_text TEXT NOT NULL,
|
|
41
|
+
extracted_entities TEXT DEFAULT '[]',
|
|
42
|
+
extracted_relations TEXT DEFAULT '[]',
|
|
43
|
+
timestamp TEXT NOT NULL DEFAULT (datetime('now')),
|
|
44
|
+
session_id TEXT
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
-- Indexes for fast lookups
|
|
48
|
+
CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
|
|
49
|
+
CREATE INDEX IF NOT EXISTS idx_entities_name ON entities(name COLLATE NOCASE);
|
|
50
|
+
CREATE INDEX IF NOT EXISTS idx_rel_from ON relationships(from_id);
|
|
51
|
+
CREATE INDEX IF NOT EXISTS idx_rel_to ON relationships(to_id);
|
|
52
|
+
CREATE INDEX IF NOT EXISTS idx_rel_relation ON relationships(relation);
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_rel_pair ON relationships(from_id, to_id, relation);
|
|
54
|
+
|
|
55
|
+
-- Full-text search on entities
|
|
56
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
|
|
57
|
+
name,
|
|
58
|
+
type,
|
|
59
|
+
properties,
|
|
60
|
+
content=entities,
|
|
61
|
+
content_rowid=rowid
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
-- FTS triggers to keep in sync
|
|
65
|
+
CREATE TRIGGER IF NOT EXISTS entities_ai AFTER INSERT ON entities BEGIN
|
|
66
|
+
INSERT INTO entities_fts(rowid, name, type, properties)
|
|
67
|
+
VALUES (new.rowid, new.name, new.type, new.properties);
|
|
68
|
+
END;
|
|
69
|
+
|
|
70
|
+
CREATE TRIGGER IF NOT EXISTS entities_ad AFTER DELETE ON entities BEGIN
|
|
71
|
+
INSERT INTO entities_fts(entities_fts, rowid, name, type, properties)
|
|
72
|
+
VALUES ('delete', old.rowid, old.name, old.type, old.properties);
|
|
73
|
+
END;
|
|
74
|
+
|
|
75
|
+
CREATE TRIGGER IF NOT EXISTS entities_au AFTER UPDATE ON entities BEGIN
|
|
76
|
+
INSERT INTO entities_fts(entities_fts, rowid, name, type, properties)
|
|
77
|
+
VALUES ('delete', old.rowid, old.name, old.type, old.properties);
|
|
78
|
+
INSERT INTO entities_fts(rowid, name, type, properties)
|
|
79
|
+
VALUES (new.rowid, new.name, new.type, new.properties);
|
|
80
|
+
END;
|
|
81
|
+
`;
|
|
82
|
+
export class SchemaManager {
|
|
83
|
+
db;
|
|
84
|
+
constructor(dbPath) {
|
|
85
|
+
// Ensure directory exists
|
|
86
|
+
const dir = resolve(dbPath, '..');
|
|
87
|
+
mkdirSync(dir, { recursive: true });
|
|
88
|
+
this.db = new Database(dbPath);
|
|
89
|
+
this.db.pragma('journal_mode = WAL');
|
|
90
|
+
this.db.pragma('foreign_keys = ON');
|
|
91
|
+
}
|
|
92
|
+
/** Initialize schema (idempotent) */
|
|
93
|
+
initialize() {
|
|
94
|
+
this.db.exec(SCHEMA_SQL);
|
|
95
|
+
// Set schema version
|
|
96
|
+
const stmt = this.db.prepare(`INSERT OR REPLACE INTO _meta (key, value) VALUES ('schema_version', ?)`);
|
|
97
|
+
stmt.run(String(SCHEMA_VERSION));
|
|
98
|
+
return this.db;
|
|
99
|
+
}
|
|
100
|
+
/** Get current schema version */
|
|
101
|
+
getVersion() {
|
|
102
|
+
try {
|
|
103
|
+
const row = this.db.prepare(`SELECT value FROM _meta WHERE key = 'schema_version'`).get();
|
|
104
|
+
return row ? parseInt(row.value, 10) : 0;
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return 0;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** Close database connection */
|
|
111
|
+
close() {
|
|
112
|
+
this.db.close();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/graph/schema.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEpC,MAAM,cAAc,GAAG,CAAC,CAAC;AAEzB,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4ElB,CAAC;AAEF,MAAM,OAAO,aAAa;IAChB,EAAE,CAAoB;IAE9B,YAAY,MAAc;QACxB,0BAA0B;QAC1B,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAClC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAEpC,IAAI,CAAC,EAAE,GAAG,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC/B,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;QACrC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;IACtC,CAAC;IAED,qCAAqC;IACrC,UAAU;QACR,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAEzB,qBAAqB;QACrB,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAC1B,wEAAwE,CACzE,CAAC;QACF,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC;QAEjC,OAAO,IAAI,CAAC,EAAE,CAAC;IACjB,CAAC;IAED,iCAAiC;IACjC,UAAU;QACR,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CACzB,sDAAsD,CACvD,CAAC,GAAG,EAAmC,CAAC;YACzC,OAAO,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,CAAC;QACX,CAAC;IACH,CAAC;IAED,gCAAgC;IAChC,KAAK;QACH,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;IAClB,CAAC;CACF"}
|