@fluxgraph/knowledge
Version:
A flexible, database-agnostic knowledge graph implementation for TypeScript
1,302 lines (1,295 loc) • 55.6 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/adapters/index.ts
var adapters_exports = {};
__export(adapters_exports, {
BaseAdapter: () => BaseAdapter,
D1Adapter: () => D1Adapter,
SQLiteAdapter: () => SQLiteAdapter,
SqlStorageAdapter: () => SqlStorageAdapter,
createAdapter: () => createAdapter
});
module.exports = __toCommonJS(adapters_exports);
// src/adapters/base.ts
var BaseAdapter = class {
config;
tablePrefix;
constructor(config = {}) {
this.config = config;
this.tablePrefix = config.tablePrefix || "";
}
getTableName(table) {
return this.tablePrefix ? `${this.tablePrefix}_${table}` : table;
}
log(_message, ..._args) {
}
error(message, error) {
console.error(`[KnowledgeGraph Error] ${message}`, error);
}
};
// src/adapters/sqlite.ts
var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
var import_better_sqlite32 = require("drizzle-orm/better-sqlite3");
var import_drizzle_orm = require("drizzle-orm");
// src/schema/index.ts
var import_sqlite_core = require("drizzle-orm/sqlite-core");
var nodes = (0, import_sqlite_core.sqliteTable)("kg_nodes", {
id: (0, import_sqlite_core.text)("id").primaryKey(),
type: (0, import_sqlite_core.text)("type").notNull(),
label: (0, import_sqlite_core.text)("label").notNull(),
properties: (0, import_sqlite_core.text)("properties").notNull(),
// JSON string
confidence: (0, import_sqlite_core.real)("confidence").notNull().default(1),
createdAt: (0, import_sqlite_core.integer)("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
updatedAt: (0, import_sqlite_core.integer)("updated_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
sourceSessionIds: (0, import_sqlite_core.text)("source_session_ids")
// JSON array of session IDs
}, (table) => ({
typeIdx: (0, import_sqlite_core.index)("idx_nodes_type").on(table.type),
labelIdx: (0, import_sqlite_core.index)("idx_nodes_label").on(table.label),
createdAtIdx: (0, import_sqlite_core.index)("idx_nodes_created_at").on(table.createdAt)
}));
var edges = (0, import_sqlite_core.sqliteTable)("kg_edges", {
id: (0, import_sqlite_core.text)("id").primaryKey(),
type: (0, import_sqlite_core.text)("type").notNull(),
fromNodeId: (0, import_sqlite_core.text)("from_node_id").notNull().references(() => nodes.id, { onDelete: "cascade" }),
toNodeId: (0, import_sqlite_core.text)("to_node_id").notNull().references(() => nodes.id, { onDelete: "cascade" }),
properties: (0, import_sqlite_core.text)("properties").notNull().default("{}"),
// JSON string
confidence: (0, import_sqlite_core.real)("confidence").notNull().default(1),
createdAt: (0, import_sqlite_core.integer)("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
sourceSessionIds: (0, import_sqlite_core.text)("source_session_ids")
// JSON array of session IDs
}, (table) => ({
typeIdx: (0, import_sqlite_core.index)("idx_edges_type").on(table.type),
fromNodeIdx: (0, import_sqlite_core.index)("idx_edges_from_node").on(table.fromNodeId),
toNodeIdx: (0, import_sqlite_core.index)("idx_edges_to_node").on(table.toNodeId),
fromTypeIdx: (0, import_sqlite_core.index)("idx_edges_from_type").on(table.fromNodeId, table.type),
toTypeIdx: (0, import_sqlite_core.index)("idx_edges_to_type").on(table.toNodeId, table.type)
}));
var nodeIndices = (0, import_sqlite_core.sqliteTable)("kg_node_indices", {
indexKey: (0, import_sqlite_core.text)("index_key").notNull(),
nodeId: (0, import_sqlite_core.text)("node_id").notNull().references(() => nodes.id, { onDelete: "cascade" }),
createdAt: (0, import_sqlite_core.integer)("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
}, (table) => ({
pk: (0, import_sqlite_core.primaryKey)({ columns: [table.indexKey, table.nodeId] }),
keyIdx: (0, import_sqlite_core.index)("idx_node_indices_key").on(table.indexKey),
nodeIdx: (0, import_sqlite_core.index)("idx_node_indices_node").on(table.nodeId)
}));
var edgeIndices = (0, import_sqlite_core.sqliteTable)("kg_edge_indices", {
indexKey: (0, import_sqlite_core.text)("index_key").notNull(),
edgeId: (0, import_sqlite_core.text)("edge_id").notNull().references(() => edges.id, { onDelete: "cascade" }),
createdAt: (0, import_sqlite_core.integer)("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
}, (table) => ({
pk: (0, import_sqlite_core.primaryKey)({ columns: [table.indexKey, table.edgeId] }),
keyIdx: (0, import_sqlite_core.index)("idx_edge_indices_key").on(table.indexKey),
edgeIdx: (0, import_sqlite_core.index)("idx_edge_indices_edge").on(table.edgeId)
}));
var searchIndex = (0, import_sqlite_core.sqliteTable)("kg_search_index", {
term: (0, import_sqlite_core.text)("term").notNull(),
nodeId: (0, import_sqlite_core.text)("node_id").notNull().references(() => nodes.id, { onDelete: "cascade" }),
field: (0, import_sqlite_core.text)("field").notNull(),
// 'label', 'property:key', etc.
weight: (0, import_sqlite_core.real)("weight").notNull().default(1)
}, (table) => ({
pk: (0, import_sqlite_core.primaryKey)({ columns: [table.term, table.nodeId, table.field] }),
termIdx: (0, import_sqlite_core.index)("idx_search_term").on(table.term),
nodeIdx: (0, import_sqlite_core.index)("idx_search_node").on(table.nodeId)
}));
var graphMetadata = (0, import_sqlite_core.sqliteTable)("kg_graph_metadata", {
key: (0, import_sqlite_core.text)("key").primaryKey(),
value: (0, import_sqlite_core.text)("value").notNull(),
updatedAt: (0, import_sqlite_core.integer)("updated_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
});
// src/adapters/sqlite.ts
var SQLiteAdapter = class extends BaseAdapter {
db = null;
drizzle = null;
constructor(config = {}) {
super(config);
}
async initialize() {
try {
const dbPath = this.config.connection || ":memory:";
this.db = new import_better_sqlite3.default(dbPath);
this.drizzle = (0, import_better_sqlite32.drizzle)(this.db);
this.db.exec("PRAGMA foreign_keys = ON");
if (this.config.autoCreate !== false) {
await this.createTables();
}
this.log("SQLite adapter initialized", { path: dbPath });
} catch (error) {
this.error("Failed to initialize SQLite adapter", error);
throw error;
}
}
async createTables() {
if (!this.db) throw new Error("Database not initialized");
this.db.exec(`
CREATE TABLE IF NOT EXISTS kg_nodes (
id TEXT PRIMARY KEY,
type TEXT NOT NULL,
label TEXT NOT NULL,
properties TEXT NOT NULL DEFAULT '{}',
confidence REAL NOT NULL DEFAULT 1.0,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
source_session_ids TEXT
);
CREATE INDEX IF NOT EXISTS idx_nodes_type ON kg_nodes(type);
CREATE INDEX IF NOT EXISTS idx_nodes_label ON kg_nodes(label);
CREATE INDEX IF NOT EXISTS idx_nodes_created_at ON kg_nodes(created_at);
`);
this.db.exec(`
CREATE TABLE IF NOT EXISTS kg_edges (
id TEXT PRIMARY KEY,
type TEXT NOT NULL,
from_node_id TEXT NOT NULL REFERENCES kg_nodes(id) ON DELETE CASCADE,
to_node_id TEXT NOT NULL REFERENCES kg_nodes(id) ON DELETE CASCADE,
properties TEXT NOT NULL DEFAULT '{}',
confidence REAL NOT NULL DEFAULT 1.0,
created_at INTEGER NOT NULL,
source_session_ids TEXT
);
CREATE INDEX IF NOT EXISTS idx_edges_type ON kg_edges(type);
CREATE INDEX IF NOT EXISTS idx_edges_from_node ON kg_edges(from_node_id);
CREATE INDEX IF NOT EXISTS idx_edges_to_node ON kg_edges(to_node_id);
CREATE INDEX IF NOT EXISTS idx_edges_from_type ON kg_edges(from_node_id, type);
CREATE INDEX IF NOT EXISTS idx_edges_to_type ON kg_edges(to_node_id, type);
`);
this.db.exec(`
CREATE TABLE IF NOT EXISTS kg_node_indices (
index_key TEXT NOT NULL,
node_id TEXT NOT NULL REFERENCES kg_nodes(id) ON DELETE CASCADE,
created_at INTEGER NOT NULL,
PRIMARY KEY (index_key, node_id)
);
CREATE INDEX IF NOT EXISTS idx_node_indices_key ON kg_node_indices(index_key);
CREATE INDEX IF NOT EXISTS idx_node_indices_node ON kg_node_indices(node_id);
`);
this.db.exec(`
CREATE TABLE IF NOT EXISTS kg_edge_indices (
index_key TEXT NOT NULL,
edge_id TEXT NOT NULL REFERENCES kg_edges(id) ON DELETE CASCADE,
created_at INTEGER NOT NULL,
PRIMARY KEY (index_key, edge_id)
);
CREATE INDEX IF NOT EXISTS idx_edge_indices_key ON kg_edge_indices(index_key);
CREATE INDEX IF NOT EXISTS idx_edge_indices_edge ON kg_edge_indices(edge_id);
`);
this.db.exec(`
CREATE TABLE IF NOT EXISTS kg_search_index (
term TEXT NOT NULL,
node_id TEXT NOT NULL REFERENCES kg_nodes(id) ON DELETE CASCADE,
field TEXT NOT NULL,
weight REAL NOT NULL DEFAULT 1.0,
PRIMARY KEY (term, node_id, field)
);
CREATE INDEX IF NOT EXISTS idx_search_term ON kg_search_index(term);
CREATE INDEX IF NOT EXISTS idx_search_node ON kg_search_index(node_id);
`);
this.db.exec(`
CREATE TABLE IF NOT EXISTS kg_graph_metadata (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at INTEGER NOT NULL
);
`);
}
async execute(query, params = []) {
if (!this.db) throw new Error("Database not initialized");
try {
const stmt = this.db.prepare(query);
return stmt.all(...params);
} catch (error) {
this.error("Query execution failed", { query, params, error });
throw error;
}
}
async executeUpdate(query, params = []) {
if (!this.db) throw new Error("Database not initialized");
try {
const stmt = this.db.prepare(query);
return stmt.run(...params);
} catch (error) {
this.error("Query execution failed", { query, params, error });
throw error;
}
}
async transaction(fn) {
if (!this.db) throw new Error("Database not initialized");
return new Promise((resolve, reject) => {
try {
if (!this.db) throw new Error("Database not initialized");
const result = this.db.transaction(async () => {
const tx = {
execute: async (query, params = []) => {
return this.execute(query, params);
},
rollback: async () => {
throw new Error("Transaction rollback");
}
};
return await fn(tx);
})();
resolve(result);
} catch (error) {
reject(error);
}
});
}
// Node operations
async insertNode(node) {
if (!this.drizzle) throw new Error("Database not initialized");
const result = await this.drizzle.insert(nodes).values(node).returning();
const insertedNode = result[0];
if (!insertedNode) throw new Error("Failed to create node");
return this.deserializeNode(insertedNode);
}
async updateNode(id, updates) {
if (!this.drizzle) throw new Error("Database not initialized");
const result = await this.drizzle.update(nodes).set({ ...updates, updatedAt: /* @__PURE__ */ new Date() }).where((0, import_drizzle_orm.eq)(nodes.id, id)).returning();
return result[0] ? this.deserializeNode(result[0]) : null;
}
async deleteNode(id) {
const query = `DELETE FROM kg_nodes WHERE id = ?`;
const result = await this.executeUpdate(query, [id]);
return result.changes > 0;
}
async getNode(id) {
if (!this.drizzle) throw new Error("Database not initialized");
const result = await this.drizzle.select().from(nodes).where((0, import_drizzle_orm.eq)(nodes.id, id)).limit(1);
return result[0] ? this.deserializeNode(result[0]) : null;
}
async getNodes(ids) {
if (!this.drizzle || ids.length === 0) return [];
const result = await this.drizzle.select().from(nodes).where((0, import_drizzle_orm.inArray)(nodes.id, ids));
return result.map((n) => this.deserializeNode(n));
}
async queryNodes(conditions, limit = 100, offset = 0) {
if (!this.drizzle) throw new Error("Database not initialized");
const whereConditions = Object.entries(conditions).map(([key, value]) => {
const column = nodes[key];
return (0, import_drizzle_orm.eq)(column, value);
});
const result = await this.drizzle.select().from(nodes).where((0, import_drizzle_orm.and)(...whereConditions)).limit(limit).offset(offset);
return result.map((n) => this.deserializeNode(n));
}
// Edge operations
async insertEdge(edge) {
if (!this.drizzle) throw new Error("Database not initialized");
const result = await this.drizzle.insert(edges).values(edge).returning();
const insertedEdge = result[0];
if (!insertedEdge) throw new Error("Failed to create edge");
return this.deserializeEdge(insertedEdge);
}
async updateEdge(id, updates) {
if (!this.drizzle) throw new Error("Database not initialized");
const result = await this.drizzle.update(edges).set(updates).where((0, import_drizzle_orm.eq)(edges.id, id)).returning();
return result[0] ? this.deserializeEdge(result[0]) : null;
}
async deleteEdge(id) {
const query = `DELETE FROM kg_edges WHERE id = ?`;
await this.execute(query, [id]);
return true;
}
async getEdge(id) {
if (!this.drizzle) throw new Error("Database not initialized");
const result = await this.drizzle.select().from(edges).where((0, import_drizzle_orm.eq)(edges.id, id)).limit(1);
return result[0] ? this.deserializeEdge(result[0]) : null;
}
async getEdges(ids) {
if (!this.drizzle || ids.length === 0) return [];
const result = await this.drizzle.select().from(edges).where((0, import_drizzle_orm.inArray)(edges.id, ids));
return result.map((e) => this.deserializeEdge(e));
}
async queryEdges(conditions, limit = 100, offset = 0) {
if (!this.drizzle) throw new Error("Database not initialized");
const whereConditions = Object.entries(conditions).map(([key, value]) => {
const fieldMap = {
"from_node_id": "fromNodeId",
"to_node_id": "toNodeId",
"created_at": "createdAt",
"updated_at": "updatedAt",
"source_session_ids": "sourceSessionIds"
};
const schemaKey = fieldMap[key] || key;
const column = edges[schemaKey];
return (0, import_drizzle_orm.eq)(column, value);
});
const result = await this.drizzle.select().from(edges).where((0, import_drizzle_orm.and)(...whereConditions)).limit(limit).offset(offset);
return result.map((e) => this.deserializeEdge(e));
}
// Index operations
async insertNodeIndex(index2) {
if (!this.drizzle) throw new Error("Database not initialized");
const result = await this.drizzle.insert(nodeIndices).values(index2).returning();
const insertedIndex = result[0];
if (!insertedIndex) throw new Error("Failed to create node index");
return insertedIndex;
}
async deleteNodeIndex(indexKey, nodeId) {
const query = nodeId ? `DELETE FROM kg_node_indices WHERE index_key = ? AND node_id = ?` : `DELETE FROM kg_node_indices WHERE index_key = ?`;
const params = nodeId ? [indexKey, nodeId] : [indexKey];
if (!this.db) throw new Error("Database not initialized");
const stmt = this.db.prepare(query);
const info = stmt.run(params);
return info.changes;
}
async getNodeIndices(indexKey) {
if (!this.drizzle) throw new Error("Database not initialized");
return await this.drizzle.select().from(nodeIndices).where((0, import_drizzle_orm.eq)(nodeIndices.indexKey, indexKey));
}
async insertEdgeIndex(index2) {
if (!this.drizzle) throw new Error("Database not initialized");
const result = await this.drizzle.insert(edgeIndices).values(index2).returning();
const insertedIndex = result[0];
if (!insertedIndex) throw new Error("Failed to create edge index");
return insertedIndex;
}
async deleteEdgeIndex(indexKey, edgeId) {
const query = edgeId ? `DELETE FROM kg_edge_indices WHERE index_key = ? AND edge_id = ?` : `DELETE FROM kg_edge_indices WHERE index_key = ?`;
const params = edgeId ? [indexKey, edgeId] : [indexKey];
if (!this.db) throw new Error("Database not initialized");
const stmt = this.db.prepare(query);
const info = stmt.run(params);
return info.changes;
}
async getEdgeIndices(indexKey) {
if (!this.drizzle) throw new Error("Database not initialized");
return await this.drizzle.select().from(edgeIndices).where((0, import_drizzle_orm.eq)(edgeIndices.indexKey, indexKey));
}
// Search operations
async insertSearchIndex(index2) {
if (!this.drizzle) throw new Error("Database not initialized");
const result = await this.drizzle.insert(searchIndex).values(index2).returning();
const insertedIndex = result[0];
if (!insertedIndex) throw new Error("Failed to create search index");
return insertedIndex;
}
async deleteSearchIndex(nodeId) {
const query = `DELETE FROM kg_search_index WHERE node_id = ?`;
if (!this.db) throw new Error("Database not initialized");
const stmt = this.db.prepare(query);
const info = stmt.run([nodeId]);
return info.changes;
}
async searchNodes(term, limit = 50) {
if (!this.drizzle) throw new Error("Database not initialized");
return await this.drizzle.select().from(searchIndex).where((0, import_drizzle_orm.like)(searchIndex.term, `%${term}%`)).limit(limit);
}
// Batch operations
async batchInsertNodes(nodes2) {
if (!this.drizzle || nodes2.length === 0) return [];
const result = await this.drizzle.insert(nodes).values(nodes2).returning();
return result.map((n) => this.deserializeNode(n));
}
async batchInsertEdges(edges2) {
if (!this.drizzle || edges2.length === 0) return [];
const result = await this.drizzle.insert(edges).values(edges2).returning();
return result.map((e) => this.deserializeEdge(e));
}
async batchDeleteNodes(ids) {
if (ids.length === 0) return 0;
const placeholders = ids.map(() => "?").join(",");
const query = `DELETE FROM kg_nodes WHERE id IN (${placeholders})`;
if (!this.db) throw new Error("Database not initialized");
const stmt = this.db.prepare(query);
const info = stmt.run(ids);
return info.changes;
}
async batchDeleteEdges(ids) {
if (ids.length === 0) return 0;
const placeholders = ids.map(() => "?").join(",");
const query = `DELETE FROM kg_edges WHERE id IN (${placeholders})`;
if (!this.db) throw new Error("Database not initialized");
const stmt = this.db.prepare(query);
const info = stmt.run(ids);
return info.changes;
}
// Maintenance operations
async vacuum() {
if (!this.db) throw new Error("Database not initialized");
this.db.exec("VACUUM");
this.log("Database vacuumed");
}
async getStats() {
if (!this.db) throw new Error("Database not initialized");
const nodeCount = this.db.prepare("SELECT COUNT(*) as count FROM kg_nodes").get();
const edgeCount = this.db.prepare("SELECT COUNT(*) as count FROM kg_edges").get();
const indexCount = this.db.prepare("SELECT COUNT(*) as count FROM kg_node_indices").get();
return {
nodeCount: nodeCount.count,
edgeCount: edgeCount.count,
indexCount: indexCount.count
};
}
async close() {
if (this.db) {
this.db.close();
this.db = null;
this.drizzle = null;
this.log("Database connection closed");
}
}
// Helper methods
deserializeNode(node) {
return {
...node,
properties: JSON.parse(node.properties),
sourceSessionIds: node.sourceSessionIds ? JSON.parse(node.sourceSessionIds) : void 0
};
}
deserializeEdge(edge) {
return {
...edge,
properties: JSON.parse(edge.properties),
sourceSessionIds: edge.sourceSessionIds ? JSON.parse(edge.sourceSessionIds) : void 0
};
}
};
// src/adapters/d1.ts
var D1Adapter = class extends BaseAdapter {
db = null;
constructor(config) {
super(config);
if (config.database) {
this.db = config.database;
}
}
setDatabase(db) {
this.db = db;
}
async initialize() {
if (!this.db) {
throw new Error("D1 database not provided. Use setDatabase() or pass it in config.");
}
if (this.config.autoCreate !== false) {
await this.createTables();
}
this.log("D1 adapter initialized");
}
async createTables() {
if (!this.db) throw new Error("Database not initialized");
await this.db.exec(`
CREATE TABLE IF NOT EXISTS kg_nodes (
id TEXT PRIMARY KEY,
type TEXT NOT NULL,
label TEXT NOT NULL,
properties TEXT NOT NULL DEFAULT '{}',
confidence REAL NOT NULL DEFAULT 1.0,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
source_session_ids TEXT
);
`);
await this.db.exec(`CREATE INDEX IF NOT EXISTS idx_nodes_type ON kg_nodes(type);`);
await this.db.exec(`CREATE INDEX IF NOT EXISTS idx_nodes_label ON kg_nodes(label);`);
await this.db.exec(`CREATE INDEX IF NOT EXISTS idx_nodes_created_at ON kg_nodes(created_at);`);
await this.db.exec(`
CREATE TABLE IF NOT EXISTS kg_edges (
id TEXT PRIMARY KEY,
type TEXT NOT NULL,
from_node_id TEXT NOT NULL,
to_node_id TEXT NOT NULL,
properties TEXT NOT NULL DEFAULT '{}',
confidence REAL NOT NULL DEFAULT 1.0,
created_at INTEGER NOT NULL,
source_session_ids TEXT,
FOREIGN KEY (from_node_id) REFERENCES kg_nodes(id) ON DELETE CASCADE,
FOREIGN KEY (to_node_id) REFERENCES kg_nodes(id) ON DELETE CASCADE
);
`);
await this.db.exec(`CREATE INDEX IF NOT EXISTS idx_edges_type ON kg_edges(type);`);
await this.db.exec(`CREATE INDEX IF NOT EXISTS idx_edges_from_node ON kg_edges(from_node_id);`);
await this.db.exec(`CREATE INDEX IF NOT EXISTS idx_edges_to_node ON kg_edges(to_node_id);`);
await this.db.exec(`CREATE INDEX IF NOT EXISTS idx_edges_from_type ON kg_edges(from_node_id, type);`);
await this.db.exec(`CREATE INDEX IF NOT EXISTS idx_edges_to_type ON kg_edges(to_node_id, type);`);
await this.db.exec(`
CREATE TABLE IF NOT EXISTS kg_node_indices (
index_key TEXT NOT NULL,
node_id TEXT NOT NULL,
created_at INTEGER NOT NULL,
PRIMARY KEY (index_key, node_id),
FOREIGN KEY (node_id) REFERENCES kg_nodes(id) ON DELETE CASCADE
);
`);
await this.db.exec(`CREATE INDEX IF NOT EXISTS idx_node_indices_key ON kg_node_indices(index_key);`);
await this.db.exec(`CREATE INDEX IF NOT EXISTS idx_node_indices_node ON kg_node_indices(node_id);`);
await this.db.exec(`
CREATE TABLE IF NOT EXISTS kg_edge_indices (
index_key TEXT NOT NULL,
edge_id TEXT NOT NULL,
created_at INTEGER NOT NULL,
PRIMARY KEY (index_key, edge_id),
FOREIGN KEY (edge_id) REFERENCES kg_edges(id) ON DELETE CASCADE
);
`);
await this.db.exec(`CREATE INDEX IF NOT EXISTS idx_edge_indices_key ON kg_edge_indices(index_key);`);
await this.db.exec(`CREATE INDEX IF NOT EXISTS idx_edge_indices_edge ON kg_edge_indices(edge_id);`);
await this.db.exec(`
CREATE TABLE IF NOT EXISTS kg_search_index (
term TEXT NOT NULL,
node_id TEXT NOT NULL,
field TEXT NOT NULL,
weight REAL NOT NULL DEFAULT 1.0,
PRIMARY KEY (term, node_id, field),
FOREIGN KEY (node_id) REFERENCES kg_nodes(id) ON DELETE CASCADE
);
`);
await this.db.exec(`CREATE INDEX IF NOT EXISTS idx_search_term ON kg_search_index(term);`);
await this.db.exec(`CREATE INDEX IF NOT EXISTS idx_search_node ON kg_search_index(node_id);`);
await this.db.exec(`
CREATE TABLE IF NOT EXISTS kg_graph_metadata (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at INTEGER NOT NULL
);
`);
}
async execute(query, params = []) {
if (!this.db) throw new Error("Database not initialized");
try {
const stmt = this.db.prepare(query).bind(...params);
const result = await stmt.all();
return result.results;
} catch (error) {
this.error("Query execution failed", { query, params, error });
throw error;
}
}
async transaction(fn) {
if (!this.db) throw new Error("Database not initialized");
const tx = {
execute: async (query, params = []) => {
return this.execute(query, params);
},
rollback: async () => {
throw new Error("Transaction rollback");
}
};
try {
return await fn(tx);
} catch (error) {
this.error("Transaction failed", error);
throw error;
}
}
// Node operations
async insertNode(node) {
const id = node.id || crypto.randomUUID();
const now = Date.now();
const query = `
INSERT INTO kg_nodes (id, type, label, properties, confidence, created_at, updated_at, source_session_ids)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`;
await this.execute(query, [
id,
node.type,
node.label,
JSON.stringify(node.properties || {}),
node.confidence || 1,
node.createdAt?.getTime() || now,
node.updatedAt?.getTime() || now,
node.sourceSessionIds ? JSON.stringify(node.sourceSessionIds) : null
]);
return this.getNode(id);
}
async updateNode(id, updates) {
const setClauses = [];
const params = [];
if (updates.type !== void 0) {
setClauses.push("type = ?");
params.push(updates.type);
}
if (updates.label !== void 0) {
setClauses.push("label = ?");
params.push(updates.label);
}
if (updates.properties !== void 0) {
setClauses.push("properties = ?");
params.push(JSON.stringify(updates.properties));
}
if (updates.confidence !== void 0) {
setClauses.push("confidence = ?");
params.push(updates.confidence);
}
if (updates.sourceSessionIds !== void 0) {
setClauses.push("source_session_ids = ?");
params.push(JSON.stringify(updates.sourceSessionIds));
}
setClauses.push("updated_at = ?");
params.push(Date.now());
params.push(id);
const query = `UPDATE kg_nodes SET ${setClauses.join(", ")} WHERE id = ?`;
await this.execute(query, params);
return this.getNode(id);
}
async deleteNode(id) {
const query = `DELETE FROM kg_nodes WHERE id = ?`;
await this.execute(query, [id]);
return true;
}
async getNode(id) {
const query = `SELECT * FROM kg_nodes WHERE id = ? LIMIT 1`;
const results = await this.execute(query, [id]);
if (results.length === 0) return null;
return this.deserializeNode(results[0]);
}
async getNodes(ids) {
if (ids.length === 0) return [];
const placeholders = ids.map(() => "?").join(",");
const query = `SELECT * FROM kg_nodes WHERE id IN (${placeholders})`;
const results = await this.execute(query, ids);
return results.map((n) => this.deserializeNode(n));
}
async queryNodes(conditions, limit = 100, offset = 0) {
const whereClauses = [];
const params = [];
for (const [key, value] of Object.entries(conditions)) {
whereClauses.push(`${key} = ?`);
params.push(value);
}
params.push(limit, offset);
const query = `
SELECT * FROM kg_nodes
${whereClauses.length > 0 ? "WHERE " + whereClauses.join(" AND ") : ""}
LIMIT ? OFFSET ?
`;
const results = await this.execute(query, params);
return results.map((n) => this.deserializeNode(n));
}
// Edge operations
async insertEdge(edge) {
const id = edge.id || crypto.randomUUID();
const now = Date.now();
const query = `
INSERT INTO kg_edges (id, type, from_node_id, to_node_id, properties, confidence, created_at, source_session_ids)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`;
await this.execute(query, [
id,
edge.type,
edge.fromNodeId,
edge.toNodeId,
JSON.stringify(edge.properties || {}),
edge.confidence || 1,
edge.createdAt?.getTime() || now,
edge.sourceSessionIds ? JSON.stringify(edge.sourceSessionIds) : null
]);
return this.getEdge(id);
}
async updateEdge(id, updates) {
const setClauses = [];
const params = [];
if (updates.type !== void 0) {
setClauses.push("type = ?");
params.push(updates.type);
}
if (updates.fromNodeId !== void 0) {
setClauses.push("from_node_id = ?");
params.push(updates.fromNodeId);
}
if (updates.toNodeId !== void 0) {
setClauses.push("to_node_id = ?");
params.push(updates.toNodeId);
}
if (updates.properties !== void 0) {
setClauses.push("properties = ?");
params.push(JSON.stringify(updates.properties));
}
if (updates.confidence !== void 0) {
setClauses.push("confidence = ?");
params.push(updates.confidence);
}
if (updates.sourceSessionIds !== void 0) {
setClauses.push("source_session_ids = ?");
params.push(JSON.stringify(updates.sourceSessionIds));
}
params.push(id);
const query = `UPDATE kg_edges SET ${setClauses.join(", ")} WHERE id = ?`;
await this.execute(query, params);
return this.getEdge(id);
}
async deleteEdge(id) {
const query = `DELETE FROM kg_edges WHERE id = ?`;
await this.execute(query, [id]);
return true;
}
async getEdge(id) {
const query = `SELECT * FROM kg_edges WHERE id = ? LIMIT 1`;
const results = await this.execute(query, [id]);
if (results.length === 0) return null;
return this.deserializeEdge(results[0]);
}
async getEdges(ids) {
if (ids.length === 0) return [];
const placeholders = ids.map(() => "?").join(",");
const query = `SELECT * FROM kg_edges WHERE id IN (${placeholders})`;
const results = await this.execute(query, ids);
return results.map((e) => this.deserializeEdge(e));
}
async queryEdges(conditions, limit = 100, offset = 0) {
const whereClauses = [];
const params = [];
const fieldMap = {
"fromNodeId": "from_node_id",
"toNodeId": "to_node_id",
"from_node_id": "from_node_id",
"to_node_id": "to_node_id",
"createdAt": "created_at",
"updatedAt": "updated_at",
"sourceSessionIds": "source_session_ids"
};
for (const [key, value] of Object.entries(conditions)) {
const dbKey = fieldMap[key] || key;
whereClauses.push(`${dbKey} = ?`);
params.push(value);
}
params.push(limit, offset);
const query = `
SELECT * FROM kg_edges
${whereClauses.length > 0 ? "WHERE " + whereClauses.join(" AND ") : ""}
LIMIT ? OFFSET ?
`;
const results = await this.execute(query, params);
return results.map((e) => this.deserializeEdge(e));
}
// Index operations
async insertNodeIndex(index2) {
const query = `
INSERT INTO kg_node_indices (index_key, node_id, created_at)
VALUES (?, ?, ?)
`;
await this.execute(query, [
index2.indexKey,
index2.nodeId,
index2.createdAt?.getTime() || Date.now()
]);
return index2;
}
async deleteNodeIndex(indexKey, nodeId) {
const query = nodeId ? `DELETE FROM kg_node_indices WHERE index_key = ? AND node_id = ?` : `DELETE FROM kg_node_indices WHERE index_key = ?`;
const params = nodeId ? [indexKey, nodeId] : [indexKey];
await this.execute(query, params);
return 1;
}
async getNodeIndices(indexKey) {
const query = `SELECT * FROM kg_node_indices WHERE index_key = ?`;
const results = await this.execute(query, [indexKey]);
return results.map((r) => this.deserializeNodeIndex(r));
}
async insertEdgeIndex(index2) {
const query = `
INSERT INTO kg_edge_indices (index_key, edge_id, created_at)
VALUES (?, ?, ?)
`;
await this.execute(query, [
index2.indexKey,
index2.edgeId,
index2.createdAt?.getTime() || Date.now()
]);
return index2;
}
async deleteEdgeIndex(indexKey, edgeId) {
const query = edgeId ? `DELETE FROM kg_edge_indices WHERE index_key = ? AND edge_id = ?` : `DELETE FROM kg_edge_indices WHERE index_key = ?`;
const params = edgeId ? [indexKey, edgeId] : [indexKey];
await this.execute(query, params);
return 1;
}
async getEdgeIndices(indexKey) {
const query = `SELECT * FROM kg_edge_indices WHERE index_key = ?`;
const results = await this.execute(query, [indexKey]);
return results.map((r) => this.deserializeEdgeIndex(r));
}
// Search operations
async insertSearchIndex(index2) {
const query = `
INSERT OR REPLACE INTO kg_search_index (term, node_id, field, weight)
VALUES (?, ?, ?, ?)
`;
await this.execute(query, [
index2.term,
index2.nodeId,
index2.field,
index2.weight || 1
]);
return index2;
}
async deleteSearchIndex(nodeId) {
const query = `DELETE FROM kg_search_index WHERE node_id = ?`;
await this.execute(query, [nodeId]);
return 1;
}
async searchNodes(term, limit = 50) {
const query = `
SELECT * FROM kg_search_index
WHERE term LIKE ?
ORDER BY weight DESC
LIMIT ?
`;
const results = await this.execute(query, [`%${term}%`, limit]);
return results.map((r) => this.deserializeSearchIndex(r));
}
// Batch operations
async batchInsertNodes(nodes2) {
const insertedNodes = [];
for (const node of nodes2) {
const inserted = await this.insertNode(node);
insertedNodes.push(inserted);
}
return insertedNodes;
}
async batchInsertEdges(edges2) {
const insertedEdges = [];
for (const edge of edges2) {
const inserted = await this.insertEdge(edge);
insertedEdges.push(inserted);
}
return insertedEdges;
}
async batchDeleteNodes(ids) {
if (ids.length === 0) return 0;
const placeholders = ids.map(() => "?").join(",");
const query = `DELETE FROM kg_nodes WHERE id IN (${placeholders})`;
await this.execute(query, ids);
return ids.length;
}
async batchDeleteEdges(ids) {
if (ids.length === 0) return 0;
const placeholders = ids.map(() => "?").join(",");
const query = `DELETE FROM kg_edges WHERE id IN (${placeholders})`;
await this.execute(query, ids);
return ids.length;
}
// Maintenance operations
async vacuum() {
this.log("Vacuum not needed for D1 (handled automatically)");
}
async getStats() {
const nodeCount = await this.execute("SELECT COUNT(*) as count FROM kg_nodes");
const edgeCount = await this.execute("SELECT COUNT(*) as count FROM kg_edges");
const indexCount = await this.execute("SELECT COUNT(*) as count FROM kg_node_indices");
return {
nodeCount: nodeCount[0]?.count || 0,
edgeCount: edgeCount[0]?.count || 0,
indexCount: indexCount[0]?.count || 0
};
}
async close() {
this.db = null;
this.log("D1 adapter closed");
}
// Helper methods
deserializeNode(row) {
return {
id: row.id,
type: row.type,
label: row.label,
properties: typeof row.properties === "string" ? JSON.parse(row.properties) : row.properties || {},
confidence: row.confidence,
createdAt: new Date(row.created_at),
updatedAt: new Date(row.updated_at),
sourceSessionIds: row.source_session_ids ? JSON.parse(row.source_session_ids) : void 0
};
}
deserializeEdge(row) {
return {
id: row.id,
type: row.type,
fromNodeId: row.from_node_id,
toNodeId: row.to_node_id,
properties: typeof row.properties === "string" ? JSON.parse(row.properties) : row.properties || {},
confidence: row.confidence,
createdAt: new Date(row.created_at),
sourceSessionIds: row.source_session_ids ? JSON.parse(row.source_session_ids) : void 0
};
}
deserializeNodeIndex(row) {
return {
indexKey: row.index_key,
nodeId: row.node_id,
createdAt: new Date(row.created_at)
};
}
deserializeEdgeIndex(row) {
return {
indexKey: row.index_key,
edgeId: row.edge_id,
createdAt: new Date(row.created_at)
};
}
deserializeSearchIndex(row) {
return {
term: row.term,
nodeId: row.node_id,
field: row.field,
weight: row.weight
};
}
};
// src/adapters/sql-storage.ts
var SqlStorageAdapter = class extends BaseAdapter {
sql = null;
constructor(config = {}) {
super(config);
}
/**
* Set the SqlStorage instance
* Must be called before using the adapter
*/
setSqlStorage(sql) {
this.sql = sql;
}
async initialize() {
if (!this.sql) {
throw new Error("SqlStorage not set. Call setSqlStorage() first.");
}
try {
if (this.config.autoCreate !== false) {
await this.createTables();
}
this.log("SqlStorage adapter initialized");
} catch (error) {
this.error("Failed to initialize SqlStorage adapter", error);
throw error;
}
}
async createTables() {
if (!this.sql) throw new Error("SqlStorage not initialized");
this.sql.exec(`
CREATE TABLE IF NOT EXISTS kg_nodes (
id TEXT PRIMARY KEY,
type TEXT NOT NULL,
label TEXT NOT NULL,
properties TEXT NOT NULL DEFAULT '{}',
confidence REAL NOT NULL DEFAULT 1.0,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
source_session_ids TEXT
)
`);
this.sql.exec(`CREATE INDEX IF NOT EXISTS idx_nodes_type ON kg_nodes(type)`);
this.sql.exec(`CREATE INDEX IF NOT EXISTS idx_nodes_label ON kg_nodes(label)`);
this.sql.exec(`CREATE INDEX IF NOT EXISTS idx_nodes_created_at ON kg_nodes(created_at)`);
this.sql.exec(`
CREATE TABLE IF NOT EXISTS kg_edges (
id TEXT PRIMARY KEY,
type TEXT NOT NULL,
from_node_id TEXT NOT NULL REFERENCES kg_nodes(id) ON DELETE CASCADE,
to_node_id TEXT NOT NULL REFERENCES kg_nodes(id) ON DELETE CASCADE,
properties TEXT NOT NULL DEFAULT '{}',
confidence REAL NOT NULL DEFAULT 1.0,
created_at INTEGER NOT NULL,
source_session_ids TEXT
)
`);
this.sql.exec(`CREATE INDEX IF NOT EXISTS idx_edges_type ON kg_edges(type)`);
this.sql.exec(`CREATE INDEX IF NOT EXISTS idx_edges_from_node ON kg_edges(from_node_id)`);
this.sql.exec(`CREATE INDEX IF NOT EXISTS idx_edges_to_node ON kg_edges(to_node_id)`);
this.sql.exec(`CREATE INDEX IF NOT EXISTS idx_edges_from_type ON kg_edges(from_node_id, type)`);
this.sql.exec(`CREATE INDEX IF NOT EXISTS idx_edges_to_type ON kg_edges(to_node_id, type)`);
this.sql.exec(`
CREATE TABLE IF NOT EXISTS kg_node_indices (
index_key TEXT NOT NULL,
node_id TEXT NOT NULL REFERENCES kg_nodes(id) ON DELETE CASCADE,
created_at INTEGER NOT NULL,
PRIMARY KEY (index_key, node_id)
)
`);
this.sql.exec(`CREATE INDEX IF NOT EXISTS idx_node_indices_key ON kg_node_indices(index_key)`);
this.sql.exec(`CREATE INDEX IF NOT EXISTS idx_node_indices_node ON kg_node_indices(node_id)`);
this.sql.exec(`
CREATE TABLE IF NOT EXISTS kg_edge_indices (
index_key TEXT NOT NULL,
edge_id TEXT NOT NULL REFERENCES kg_edges(id) ON DELETE CASCADE,
created_at INTEGER NOT NULL,
PRIMARY KEY (index_key, edge_id)
)
`);
this.sql.exec(`CREATE INDEX IF NOT EXISTS idx_edge_indices_key ON kg_edge_indices(index_key)`);
this.sql.exec(`CREATE INDEX IF NOT EXISTS idx_edge_indices_edge ON kg_edge_indices(edge_id)`);
this.sql.exec(`
CREATE TABLE IF NOT EXISTS kg_search_index (
term TEXT NOT NULL,
node_id TEXT NOT NULL REFERENCES kg_nodes(id) ON DELETE CASCADE,
field TEXT NOT NULL,
weight REAL NOT NULL DEFAULT 1.0,
PRIMARY KEY (term, node_id, field)
)
`);
this.sql.exec(`CREATE INDEX IF NOT EXISTS idx_search_term ON kg_search_index(term)`);
this.sql.exec(`CREATE INDEX IF NOT EXISTS idx_search_node ON kg_search_index(node_id)`);
this.sql.exec(`
CREATE TABLE IF NOT EXISTS kg_graph_metadata (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at INTEGER NOT NULL
)
`);
}
async execute(query, params = []) {
if (!this.sql) throw new Error("SqlStorage not initialized");
try {
const result = this.sql.exec(query, ...params);
return result.toArray();
} catch (error) {
this.error("Query execution failed", { query, params, error });
throw error;
}
}
async transaction(fn) {
if (!this.sql) throw new Error("SqlStorage not initialized");
const tx = {
execute: async (query, params = []) => {
return this.execute(query, params);
},
rollback: async () => {
throw new Error("SqlStorage does not support transaction rollback");
}
};
try {
return await fn(tx);
} catch (error) {
this.error("Transaction failed", error);
throw error;
}
}
// Node operations
async insertNode(node) {
const now = Date.now();
const id = node.id || this.generateId();
const properties = JSON.stringify(node.properties || {});
const sourceSessionIds = node.sourceSessionIds ? JSON.stringify(node.sourceSessionIds) : null;
const query = `
INSERT INTO kg_nodes (id, type, label, properties, confidence, created_at, updated_at, source_session_ids)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`;
await this.execute(query, [
id,
node.type,
node.label,
properties,
node.confidence || 1,
node.createdAt?.getTime() || now,
node.updatedAt?.getTime() || now,
sourceSessionIds
]);
const result = await this.getNode(id);
if (!result) throw new Error("Failed to create node");
return result;
}
async updateNode(id, updates) {
const current = await this.getNode(id);
if (!current) return null;
const setClauses = [];
const params = [];
if (updates.type !== void 0) {
setClauses.push("type = ?");
params.push(updates.type);
}
if (updates.label !== void 0) {
setClauses.push("label = ?");
params.push(updates.label);
}
if (updates.properties !== void 0) {
setClauses.push("properties = ?");
params.push(JSON.stringify(updates.properties));
}
if (updates.confidence !== void 0) {
setClauses.push("confidence = ?");
params.push(updates.confidence);
}
if (updates.sourceSessionIds !== void 0) {
setClauses.push("source_session_ids = ?");
params.push(JSON.stringify(updates.sourceSessionIds));
}
setClauses.push("updated_at = ?");
params.push(Date.now());
params.push(id);
const query = `UPDATE kg_nodes SET ${setClauses.join(", ")} WHERE id = ?`;
await this.execute(query, params);
return this.getNode(id);
}
async deleteNode(id) {
const query = `DELETE FROM kg_nodes WHERE id = ?`;
const result = await this.execute(query, [id]);
return Array.isArray(result) || result !== void 0;
}
async getNode(id) {
const query = `SELECT * FROM kg_nodes WHERE id = ? LIMIT 1`;
const result = await this.execute(query, [id]);
if (!result || result.length === 0) return null;
return this.deserializeNode(result[0]);
}
async getNodes(ids) {
if (ids.length === 0) return [];
const placeholders = ids.map(() => "?").join(",");
const query = `SELECT * FROM kg_nodes WHERE id IN (${placeholders})`;
const result = await this.execute(query, ids);
return result.map((n) => this.deserializeNode(n));
}
async queryNodes(conditions, limit = 100, offset = 0) {
const whereClauses = [];
const params = [];
Object.entries(conditions).forEach(([key, value]) => {
whereClauses.push(`${key} = ?`);
params.push(value);
});
params.push(limit, offset);
const query = `
SELECT * FROM kg_nodes
${whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : ""}
LIMIT ? OFFSET ?
`;
const result = await this.execute(query, params);
return result.map((n) => this.deserializeNode(n));
}
// Edge operations
async insertEdge(edge) {
const now = Date.now();
const id = edge.id || this.generateId();
const properties = JSON.stringify(edge.properties || {});
const sourceSessionIds = edge.sourceSessionIds ? JSON.stringify(edge.sourceSessionIds) : null;
const query = `
INSERT INTO kg_edges (id, type, from_node_id, to_node_id, properties, confidence, created_at, source_session_ids)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`;
await this.execute(query, [
id,
edge.type,
edge.fromNodeId,
edge.toNodeId,
properties,
edge.confidence || 1,
edge.createdAt?.getTime() || now,
sourceSessionIds
]);
const result = await this.getEdge(id);
if (!result) throw new Error("Failed to create edge");
return result;
}
async updateEdge(id, updates) {
const current = await this.getEdge(id);
if (!current) return null;
const setClauses = [];
const params = [];
if (updates.type !== void 0) {
setClauses.push("type = ?");
params.push(updates.type);
}
if (updates.fromNodeId !== void 0) {
setClauses.push("from_node_id = ?");
params.push(updates.fromNodeId);
}
if (updates.toNodeId !== void 0) {
setClauses.push("to_node_id = ?");
params.push(updates.toNodeId);
}
if (updates.properties !== void 0) {
setClauses.push("properties = ?");
params.push(JSON.stringify(updates.properties));
}
if (updates.confidence !== void 0) {
setClauses.push("confidence = ?");
params.push(updates.confidence);
}
if (updates.sourceSessionIds !== void 0) {
setClauses.push("source_session_ids = ?");
params.push(JSON.stringify(updates.sourceSessionIds));
}
params.push(id);
const query = `UPDATE kg_edges SET ${setClauses.join(", ")} WHERE id = ?`;
await this.execute(query, params);
return this.getEdge(id);
}
async deleteEdge(id) {
const query = `DELETE FROM kg_edges WHERE id = ?`;
await this.execute(query, [id]);
return true;
}
async getEdge(id) {
const query = `SELECT * FROM kg_edges WHERE id = ? LIMIT 1`;
const result = await this.execute(query, [id]);
if (!result || result.length === 0) return null;
return this.deserializeEdge(result[0]);
}
async getEdges(ids) {
if (ids.length === 0) return [];
const placeholders = ids.map(() => "?").join(",");
const query = `SELECT * FROM kg_edges WHERE id IN (${placeholders})`;
const result = await this.execute(query, ids);
return result.map((e) => this.deserializeEdge(e));
}
async queryEdges(conditions, limit = 100, offset = 0) {
const whereClauses = [];
const params = [];
Object.entries(conditions).forEach(([key, value]) => {
const dbKey = key === "fromNodeId" ? "from_node_id" : key === "toNodeId" ? "to_node_id" : key;
whereClauses.push(`${dbKey} = ?`);
params.push(value);
});
params.push(limit, offset);
const query = `
SELECT * FROM kg_edges
${whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : ""}
LIMIT ? OFFSET ?
`;
const result = await this.execute(query, params);
return result.map((e) => this.deserializeEdge(e));
}
// Index operations
async insertNodeIndex(index2) {
const now = Date.now();
const query = `
INSERT INTO kg_node_indices (index_key, node_id, created_at)
VALUES (?, ?, ?)
`;
await this.execute(query, [index2.indexKey, index2.nodeId, index2.createdAt?.getTime() || now]);
return {
...index2,
createdAt: index2.createdAt || new Date(now)
};
}
async deleteNodeIndex(indexKey, nodeId) {
const query = nodeId ? `DELETE FROM kg_node_indices WHERE index_key = ? AND node_id = ?` : `DELETE FROM kg_node_indices WHERE index_key = ?`;
const params = nodeId ? [indexKey, nodeId] : [indexKey];
const result = await this.execute(query, params);
return Array.isArray(result) ? result.length : 0;
}
async getNodeIndices(indexKey) {
const query = `SELECT * FROM kg_node_indices WHERE index_key = ?`;
const result = await this.execute(query, [indexKey]);
return result.map((idx) => ({
indexKey: idx.index_key,
nodeId: idx.node_id,
createdAt: new Date(idx.created_at)
}));
}
async insertEdgeIndex(i