@fluxgraph/knowledge
Version:
A flexible, database-agnostic knowledge graph implementation for TypeScript
1,338 lines (1,331 loc) • 96.5 kB
JavaScript
var __defProp = Object.defineProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
// src/core/KnowledgeGraph.ts
var KnowledgeGraph = class {
adapter;
initialized = false;
constructor(adapter) {
this.adapter = adapter;
}
/**
* Initialize the knowledge graph
*/
async initialize() {
if (this.initialized) return;
await this.adapter.initialize();
this.initialized = true;
}
/**
* Ensure the graph is initialized
*/
async ensureInitialized() {
if (!this.initialized) {
await this.initialize();
}
}
// ============ Node Operations ============
/**
* Add a new node to the graph
*/
async addNode(options) {
await this.ensureInitialized();
const id = crypto.randomUUID();
const now = /* @__PURE__ */ new Date();
const newNode = {
id,
type: options.type,
label: options.label,
properties: JSON.stringify(options.properties || {}),
confidence: options.confidence || 1,
createdAt: now,
updatedAt: now,
sourceSessionIds: options.sourceSessionId ? JSON.stringify([options.sourceSessionId]) : void 0
};
const node = await this.adapter.insertNode(newNode);
await this.indexNodeForSearch(node);
await this.adapter.insertNodeIndex({
indexKey: `type:${node.type}`,
nodeId: node.id,
createdAt: now
});
return this.normalizeNode(node);
}
/**
* Update an existing node
*/
async updateNode(nodeId, updates, mergeProperties = true) {
await this.ensureInitialized();
const existingNode = await this.adapter.getNode(nodeId);
if (!existingNode) return null;
const existingProperties = existingNode.properties;
const properties = mergeProperties && updates.properties ? { ...existingProperties, ...updates.properties } : updates.properties || existingProperties;
const nodeUpdates = {
type: updates.type,
label: updates.label,
properties: JSON.stringify(properties),
confidence: updates.confidence,
updatedAt: /* @__PURE__ */ new Date()
};
if (updates.sourceSessionId) {
const existingSessionIds = Array.isArray(existingNode.sourceSessionIds) ? existingNode.sourceSessionIds : [];
if (!existingSessionIds.includes(updates.sourceSessionId)) {
existingSessionIds.push(updates.sourceSessionId);
nodeUpdates.sourceSessionIds = JSON.stringify(existingSessionIds);
}
}
const updatedNode = await this.adapter.updateNode(nodeId, nodeUpdates);
if (!updatedNode) return null;
await this.adapter.deleteSearchIndex(nodeId);
await this.indexNodeForSearch(updatedNode);
return this.normalizeNode(updatedNode);
}
/**
* Delete a node and all its edges
*/
async deleteNode(nodeId) {
await this.ensureInitialized();
return await this.adapter.deleteNode(nodeId);
}
/**
* Get a node by ID
*/
async getNode(nodeId) {
await this.ensureInitialized();
const node = await this.adapter.getNode(nodeId);
return node ? this.normalizeNode(node) : null;
}
/**
* Find nodes by label (exact or partial match)
*/
async findNodesByLabel(label, exact = false) {
await this.ensureInitialized();
if (exact) {
const nodes3 = await this.adapter.queryNodes({ label }, 100);
return nodes3.map((n) => this.normalizeNode(n));
}
const searchResults = await this.adapter.searchNodes(label.toLowerCase());
const nodeIds = [...new Set(searchResults.map((r) => r.nodeId))];
if (nodeIds.length === 0) return [];
const nodes2 = await this.adapter.getNodes(nodeIds);
return nodes2.map((n) => this.normalizeNode(n));
}
// ============ Edge Operations ============
/**
* Add an edge between two nodes
*/
async addEdge(options) {
await this.ensureInitialized();
const [fromNode, toNode] = await Promise.all([this.adapter.getNode(options.fromNodeId), this.adapter.getNode(options.toNodeId)]);
if (!fromNode) {
throw new Error(`From node ${options.fromNodeId} does not exist`);
}
if (!toNode) {
throw new Error(`To node ${options.toNodeId} does not exist`);
}
const id = crypto.randomUUID();
const now = /* @__PURE__ */ new Date();
const newEdge = {
id,
type: options.type,
fromNodeId: options.fromNodeId,
toNodeId: options.toNodeId,
properties: JSON.stringify(options.properties || {}),
confidence: options.confidence || 1,
createdAt: now,
sourceSessionIds: options.sourceSessionId ? JSON.stringify([options.sourceSessionId]) : void 0
};
const edge = await this.adapter.insertEdge(newEdge);
await Promise.all([
this.adapter.insertEdgeIndex({
indexKey: `from:${options.fromNodeId}:${options.type}`,
edgeId: id,
createdAt: now
}),
this.adapter.insertEdgeIndex({
indexKey: `to:${options.toNodeId}:${options.type}`,
edgeId: id,
createdAt: now
}),
this.adapter.insertEdgeIndex({
indexKey: `type:${options.type}`,
edgeId: id,
createdAt: now
})
]);
if (options.bidirectional) {
await this.addEdge({
...options,
fromNodeId: options.toNodeId,
toNodeId: options.fromNodeId,
bidirectional: false
// Prevent infinite recursion
});
}
return this.normalizeEdge(edge);
}
/**
* Delete an edge
*/
async deleteEdge(edgeId) {
await this.ensureInitialized();
return await this.adapter.deleteEdge(edgeId);
}
/**
* Get edges between two nodes
*/
async getEdgesBetween(fromNodeId, toNodeId, edgeType) {
await this.ensureInitialized();
const conditions = {
fromNodeId,
toNodeId
};
if (edgeType) {
conditions.type = edgeType;
}
const edges2 = await this.adapter.queryEdges(conditions);
return edges2.map((e) => this.normalizeEdge(e));
}
// ============ Query Operations ============
/**
* Query nodes by type
*/
async queryByType(nodeType, options) {
await this.ensureInitialized();
const nodes2 = await this.adapter.queryNodes({ type: nodeType }, options?.limit || 100, options?.offset || 0);
const edges2 = [];
if (options?.includeEdges && nodes2.length > 0) {
const nodeIds = nodes2.map((n) => n.id);
const edgeResults = await Promise.all([this.adapter.queryEdges({ fromNodeId: nodeIds[0] }), this.adapter.queryEdges({ toNodeId: nodeIds[0] })]);
edges2.push(...edgeResults.flat().map((e) => this.normalizeEdge(e)));
}
return {
nodes: nodes2.map((n) => this.normalizeNode(n)),
edges: edges2,
relevanceScore: 1
};
}
/**
* Query related nodes starting from a given node
*/
async queryRelated(nodeId, options) {
await this.ensureInitialized();
const startNode = await this.adapter.getNode(nodeId);
if (!startNode) {
return { nodes: [], edges: [], relevanceScore: 0 };
}
const visitedNodes = /* @__PURE__ */ new Map();
const visitedEdges = /* @__PURE__ */ new Map();
visitedNodes.set(nodeId, this.normalizeNode(startNode));
await this.traverseGraph(nodeId, options?.depth || 1, options?.direction || "both", options?.edgeTypes, visitedNodes, visitedEdges);
return {
nodes: Array.from(visitedNodes.values()),
edges: Array.from(visitedEdges.values()),
relevanceScore: this.calculateRelevance(visitedNodes.size, visitedEdges.size)
};
}
/**
* Search nodes using text query
*/
async search(options) {
await this.ensureInitialized();
const searchTerms = options.query.toLowerCase().split(/\s+/);
const nodeScores = /* @__PURE__ */ new Map();
for (const term of searchTerms) {
const results = await this.adapter.searchNodes(term, options.limit || 50);
for (const result of results) {
const currentScore = nodeScores.get(result.nodeId) || 0;
nodeScores.set(result.nodeId, currentScore + result.weight);
}
}
const minScore = options.minScore || 0;
const qualifiedNodeIds = Array.from(nodeScores.entries()).filter(([_, score]) => score >= minScore).sort((a, b) => b[1] - a[1]).slice(0, options.limit || 50).map(([id]) => id);
if (qualifiedNodeIds.length === 0) {
return { nodes: [], edges: [], relevanceScore: 0 };
}
const nodes2 = await this.adapter.getNodes(qualifiedNodeIds);
const filteredNodes = options.nodeTypes ? nodes2.filter((n) => options.nodeTypes.includes(n.type)) : nodes2;
return {
nodes: filteredNodes.map((n) => this.normalizeNode(n)),
edges: [],
relevanceScore: qualifiedNodeIds[0] ? nodeScores.get(qualifiedNodeIds[0]) || 0 : 0
};
}
// ============ Graph Traversal ============
/**
* Traverse the graph from a starting node
*/
async traverse(options) {
await this.ensureInitialized();
const visitedNodes = /* @__PURE__ */ new Map();
const visitedEdges = /* @__PURE__ */ new Map();
const queue = [{ nodeId: options.startNodeId, depth: 0 }];
const visited = /* @__PURE__ */ new Set();
while (queue.length > 0) {
const { nodeId, depth } = queue.shift();
if (visited.has(nodeId) && options.visitOnce !== false) continue;
if (depth > (options.maxDepth || Infinity)) continue;
visited.add(nodeId);
const node = await this.adapter.getNode(nodeId);
if (!node) continue;
const normalizedNode = this.normalizeNode(node);
if (!options.nodeFilter || options.nodeFilter(normalizedNode)) {
visitedNodes.set(nodeId, normalizedNode);
}
const edges2 = [];
if (options.direction === "out" || options.direction === "both") {
const outEdges = await this.adapter.queryEdges({ from_node_id: nodeId });
edges2.push(...outEdges.map((e) => this.normalizeEdge(e)));
}
if (options.direction === "in" || options.direction === "both") {
const inEdges = await this.adapter.queryEdges({ to_node_id: nodeId });
edges2.push(...inEdges.map((e) => this.normalizeEdge(e)));
}
for (const edge of edges2) {
if (options.edgeTypes && !options.edgeTypes.includes(edge.type)) continue;
if (options.edgeFilter && !options.edgeFilter(edge)) continue;
visitedEdges.set(edge.id, edge);
const nextNodeId = edge.fromNodeId === nodeId ? edge.toNodeId : edge.fromNodeId;
if (!visited.has(nextNodeId) || options.visitOnce === false) {
queue.push({ nodeId: nextNodeId, depth: depth + 1 });
}
}
}
return {
nodes: Array.from(visitedNodes.values()),
edges: Array.from(visitedEdges.values()),
relevanceScore: 1
};
}
/**
* Find shortest path between two nodes
*/
async findShortestPath(fromNodeId, toNodeId, options) {
await this.ensureInitialized();
const queue = [{ nodeId: fromNodeId, path: [fromNodeId], edges: [] }];
const visited = /* @__PURE__ */ new Set();
while (queue.length > 0) {
const { nodeId, path, edges: edges2 } = queue.shift();
if (nodeId === toNodeId) {
const nodes2 = await this.adapter.getNodes(path);
const edgeObjects = edges2.length > 0 ? await this.adapter.getEdges(edges2) : [];
return {
nodes: nodes2.map((n) => this.normalizeNode(n)),
edges: edgeObjects.map((e) => this.normalizeEdge(e)),
length: path.length - 1
};
}
if (visited.has(nodeId)) continue;
visited.add(nodeId);
const outEdges = await this.adapter.queryEdges({ fromNodeId: nodeId });
for (const edge of outEdges) {
if (options?.edgeTypes && !options.edgeTypes.includes(edge.type)) continue;
const nextNodeId = edge.toNodeId;
if (!visited.has(nextNodeId)) {
queue.push({
nodeId: nextNodeId,
path: [...path, nextNodeId],
edges: [...edges2, edge.id]
});
}
}
}
return null;
}
// ============ Statistics ============
/**
* Get graph statistics
*/
async getStats() {
await this.ensureInitialized();
const dbStats = await this.adapter.getStats();
const nodeTypes = await this.execute("SELECT type, COUNT(*) as count FROM kg_nodes GROUP BY type");
const edgeTypes = await this.execute("SELECT type, COUNT(*) as count FROM kg_edges GROUP BY type");
const nodesByType = {};
for (const { type, count } of nodeTypes) {
nodesByType[type] = count;
}
const edgesByType = {};
for (const { type, count } of edgeTypes) {
edgesByType[type] = count;
}
const averageDegree = dbStats.nodeCount > 0 ? dbStats.edgeCount * 2 / dbStats.nodeCount : 0;
const possibleEdges = dbStats.nodeCount * (dbStats.nodeCount - 1);
const density = possibleEdges > 0 ? dbStats.edgeCount / possibleEdges : 0;
return {
nodeCount: dbStats.nodeCount,
edgeCount: dbStats.edgeCount,
nodesByType,
edgesByType,
averageDegree,
density,
lastUpdated: /* @__PURE__ */ new Date()
};
}
// ============ Batch Operations ============
/**
* Batch insert nodes
*/
async batchAddNodes(nodes2) {
await this.ensureInitialized();
const successful = [];
const errors = [];
for (const nodeOptions of nodes2) {
try {
const node = await this.addNode(nodeOptions);
successful.push(node.id);
} catch (error) {
errors.push({ item: nodeOptions, error });
}
}
return {
successful: successful.length,
failed: errors.length,
errors: errors.length > 0 ? errors : void 0
};
}
/**
* Batch insert edges
*/
async batchAddEdges(edges2) {
await this.ensureInitialized();
const successful = [];
const errors = [];
for (const edgeOptions of edges2) {
try {
const edge = await this.addEdge(edgeOptions);
successful.push(edge.id);
} catch (error) {
errors.push({ item: edgeOptions, error });
}
}
return {
successful: successful.length,
failed: errors.length,
errors: errors.length > 0 ? errors : void 0
};
}
// ============ Maintenance ============
/**
* Vacuum the database to reclaim space
*/
async vacuum() {
await this.ensureInitialized();
await this.adapter.vacuum();
}
/**
* Close the database connection
*/
async close() {
await this.adapter.close();
this.initialized = false;
}
// ============ Helper Methods ============
/**
* Execute raw SQL query (for advanced use cases)
*/
async execute(query, params = []) {
return await this.adapter.execute(query, params);
}
/**
* Normalize a node from database format
*/
normalizeNode(node) {
return {
id: node.id,
type: node.type,
label: node.label,
properties: typeof node.properties === "string" ? JSON.parse(node.properties) : node.properties,
confidence: node.confidence,
createdAt: node.createdAt instanceof Date ? node.createdAt : new Date(node.createdAt),
updatedAt: node.updatedAt instanceof Date ? node.updatedAt : new Date(node.updatedAt),
sourceSessionIds: node.sourceSessionIds ? typeof node.sourceSessionIds === "string" ? JSON.parse(node.sourceSessionIds) : node.sourceSessionIds : void 0
};
}
/**
* Normalize an edge from database format
*/
normalizeEdge(edge) {
return {
id: edge.id,
type: edge.type,
fromNodeId: edge.fromNodeId,
toNodeId: edge.toNodeId,
properties: typeof edge.properties === "string" ? JSON.parse(edge.properties) : edge.properties,
confidence: edge.confidence,
createdAt: edge.createdAt instanceof Date ? edge.createdAt : new Date(edge.createdAt),
sourceSessionIds: edge.sourceSessionIds ? typeof edge.sourceSessionIds === "string" ? JSON.parse(edge.sourceSessionIds) : edge.sourceSessionIds : void 0
};
}
/**
* Index a node for search
*/
async indexNodeForSearch(node) {
const searchTerms = /* @__PURE__ */ new Set();
const labelTerms = node.label.toLowerCase().split(/\s+/);
labelTerms.forEach((term) => searchTerms.add(term));
const properties = typeof node.properties === "string" ? JSON.parse(node.properties) : node.properties;
for (const [, value] of Object.entries(properties)) {
if (typeof value === "string") {
const terms = value.toLowerCase().split(/\s+/);
terms.forEach((term) => searchTerms.add(term));
}
}
for (const term of searchTerms) {
await this.adapter.insertSearchIndex({
term,
nodeId: node.id,
field: "label",
weight: 1
});
}
}
/**
* Traverse graph helper
*/
async traverseGraph(nodeId, depth, direction, edgeTypes, visitedNodes, visitedEdges, currentDepth = 0) {
if (currentDepth >= depth) return;
const edges2 = [];
if (direction === "out" || direction === "both") {
const outEdges = await this.adapter.queryEdges({ fromNodeId: nodeId });
edges2.push(...outEdges);
}
if (direction === "in" || direction === "both") {
const inEdges = await this.adapter.queryEdges({ toNodeId: nodeId });
edges2.push(...inEdges);
}
for (const edge of edges2) {
if (edgeTypes && !edgeTypes.includes(edge.type)) continue;
visitedEdges.set(edge.id, this.normalizeEdge(edge));
const nextNodeId = edge.fromNodeId === nodeId ? edge.toNodeId : edge.fromNodeId;
if (!visitedNodes.has(nextNodeId)) {
const nextNode = await this.adapter.getNode(nextNodeId);
if (nextNode) {
visitedNodes.set(nextNodeId, this.normalizeNode(nextNode));
await this.traverseGraph(nextNodeId, depth, direction, edgeTypes, visitedNodes, visitedEdges, currentDepth + 1);
}
}
}
}
/**
* Calculate relevance score
*/
calculateRelevance(nodeCount, edgeCount) {
return Math.min(1, (nodeCount + edgeCount) / 10);
}
};
// src/types/index.ts
var CommonEdgeType = /* @__PURE__ */ ((CommonEdgeType2) => {
CommonEdgeType2["RELATED_TO"] = "RELATED_TO";
CommonEdgeType2["SIMILAR_TO"] = "SIMILAR_TO";
CommonEdgeType2["OPPOSITE_OF"] = "OPPOSITE_OF";
CommonEdgeType2["PART_OF"] = "PART_OF";
CommonEdgeType2["HAS_PART"] = "HAS_PART";
CommonEdgeType2["KNOWS"] = "KNOWS";
CommonEdgeType2["FRIEND_OF"] = "FRIEND_OF";
CommonEdgeType2["COLLEAGUE_OF"] = "COLLEAGUE_OF";
CommonEdgeType2["REPORTS_TO"] = "REPORTS_TO";
CommonEdgeType2["MANAGES"] = "MANAGES";
CommonEdgeType2["HAS_FAMILY_MEMBER"] = "HAS_FAMILY_MEMBER";
CommonEdgeType2["PARENT_OF"] = "PARENT_OF";
CommonEdgeType2["CHILD_OF"] = "CHILD_OF";
CommonEdgeType2["SPOUSE_OF"] = "SPOUSE_OF";
CommonEdgeType2["SIBLING_OF"] = "SIBLING_OF";
CommonEdgeType2["LIVES_AT"] = "LIVES_AT";
CommonEdgeType2["WORKS_AT"] = "WORKS_AT";
CommonEdgeType2["LOCATED_IN"] = "LOCATED_IN";
CommonEdgeType2["VISITED"] = "VISITED";
CommonEdgeType2["PLANS_TO_VISIT"] = "PLANS_TO_VISIT";
CommonEdgeType2["OWNS"] = "OWNS";
CommonEdgeType2["OWNED_BY"] = "OWNED_BY";
CommonEdgeType2["CREATED_BY"] = "CREATED_BY";
CommonEdgeType2["CREATED"] = "CREATED";
CommonEdgeType2["PAID_TO"] = "PAID_TO";
CommonEdgeType2["RECEIVED_FROM"] = "RECEIVED_FROM";
CommonEdgeType2["SAVED_FOR"] = "SAVED_FOR";
CommonEdgeType2["SPENT_ON"] = "SPENT_ON";
CommonEdgeType2["EARNS_FROM"] = "EARNS_FROM";
CommonEdgeType2["INVESTS_IN"] = "INVESTS_IN";
CommonEdgeType2["EMPLOYED_BY"] = "EMPLOYED_BY";
CommonEdgeType2["EMPLOYS"] = "EMPLOYS";
CommonEdgeType2["HAS_SKILL"] = "HAS_SKILL";
CommonEdgeType2["REQUIRES_SKILL"] = "REQUIRES_SKILL";
CommonEdgeType2["STUDIED_AT"] = "STUDIED_AT";
CommonEdgeType2["GRADUATED_FROM"] = "GRADUATED_FROM";
CommonEdgeType2["HAPPENED_BEFORE"] = "HAPPENED_BEFORE";
CommonEdgeType2["HAPPENED_AFTER"] = "HAPPENED_AFTER";
CommonEdgeType2["HAPPENED_DURING"] = "HAPPENED_DURING";
CommonEdgeType2["CAUSED"] = "CAUSED";
CommonEdgeType2["CAUSED_BY"] = "CAUSED_BY";
CommonEdgeType2["LIKES"] = "LIKES";
CommonEdgeType2["DISLIKES"] = "DISLIKES";
CommonEdgeType2["INTERESTED_IN"] = "INTERESTED_IN";
CommonEdgeType2["PREFERS"] = "PREFERS";
CommonEdgeType2["PARTICIPATED_IN"] = "PARTICIPATED_IN";
CommonEdgeType2["ATTENDED"] = "ATTENDED";
CommonEdgeType2["ORGANIZED"] = "ORGANIZED";
CommonEdgeType2["MENTIONED"] = "MENTIONED";
CommonEdgeType2["REFERENCED"] = "REFERENCED";
CommonEdgeType2["CONTAINS"] = "CONTAINS";
CommonEdgeType2["CONTAINED_IN"] = "CONTAINED_IN";
CommonEdgeType2["DERIVED_FROM"] = "DERIVED_FROM";
CommonEdgeType2["BASED_ON"] = "BASED_ON";
return CommonEdgeType2;
})(CommonEdgeType || {});
// 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
import Database from "better-sqlite3";
import { drizzle } from "drizzle-orm/better-sqlite3";
import { eq, and, inArray, like } from "drizzle-orm";
// src/schema/index.ts
var schema_exports = {};
__export(schema_exports, {
edgeIndices: () => edgeIndices,
edges: () => edges,
graphMetadata: () => graphMetadata,
nodeIndices: () => nodeIndices,
nodes: () => nodes,
searchIndex: () => searchIndex
});
import { sqliteTable, text, integer, index, primaryKey, real } from "drizzle-orm/sqlite-core";
var nodes = sqliteTable("kg_nodes", {
id: text("id").primaryKey(),
type: text("type").notNull(),
label: text("label").notNull(),
properties: text("properties").notNull(),
// JSON string
confidence: real("confidence").notNull().default(1),
createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
sourceSessionIds: text("source_session_ids")
// JSON array of session IDs
}, (table) => ({
typeIdx: index("idx_nodes_type").on(table.type),
labelIdx: index("idx_nodes_label").on(table.label),
createdAtIdx: index("idx_nodes_created_at").on(table.createdAt)
}));
var edges = sqliteTable("kg_edges", {
id: text("id").primaryKey(),
type: text("type").notNull(),
fromNodeId: text("from_node_id").notNull().references(() => nodes.id, { onDelete: "cascade" }),
toNodeId: text("to_node_id").notNull().references(() => nodes.id, { onDelete: "cascade" }),
properties: text("properties").notNull().default("{}"),
// JSON string
confidence: real("confidence").notNull().default(1),
createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
sourceSessionIds: text("source_session_ids")
// JSON array of session IDs
}, (table) => ({
typeIdx: index("idx_edges_type").on(table.type),
fromNodeIdx: index("idx_edges_from_node").on(table.fromNodeId),
toNodeIdx: index("idx_edges_to_node").on(table.toNodeId),
fromTypeIdx: index("idx_edges_from_type").on(table.fromNodeId, table.type),
toTypeIdx: index("idx_edges_to_type").on(table.toNodeId, table.type)
}));
var nodeIndices = sqliteTable("kg_node_indices", {
indexKey: text("index_key").notNull(),
nodeId: text("node_id").notNull().references(() => nodes.id, { onDelete: "cascade" }),
createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
}, (table) => ({
pk: primaryKey({ columns: [table.indexKey, table.nodeId] }),
keyIdx: index("idx_node_indices_key").on(table.indexKey),
nodeIdx: index("idx_node_indices_node").on(table.nodeId)
}));
var edgeIndices = sqliteTable("kg_edge_indices", {
indexKey: text("index_key").notNull(),
edgeId: text("edge_id").notNull().references(() => edges.id, { onDelete: "cascade" }),
createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
}, (table) => ({
pk: primaryKey({ columns: [table.indexKey, table.edgeId] }),
keyIdx: index("idx_edge_indices_key").on(table.indexKey),
edgeIdx: index("idx_edge_indices_edge").on(table.edgeId)
}));
var searchIndex = sqliteTable("kg_search_index", {
term: text("term").notNull(),
nodeId: text("node_id").notNull().references(() => nodes.id, { onDelete: "cascade" }),
field: text("field").notNull(),
// 'label', 'property:key', etc.
weight: real("weight").notNull().default(1)
}, (table) => ({
pk: primaryKey({ columns: [table.term, table.nodeId, table.field] }),
termIdx: index("idx_search_term").on(table.term),
nodeIdx: index("idx_search_node").on(table.nodeId)
}));
var graphMetadata = sqliteTable("kg_graph_metadata", {
key: text("key").primaryKey(),
value: text("value").notNull(),
updatedAt: 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 Database(dbPath);
this.drizzle = 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(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(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(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 eq(column, value);
});
const result = await this.drizzle.select().from(nodes).where(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(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(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(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 eq(column, value);
});
const result = await this.drizzle.select().from(edges).where(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(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(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(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 operat