UNPKG

@fluxgraph/knowledge

Version:

A flexible, database-agnostic knowledge graph implementation for TypeScript

1,274 lines (1,266 loc) 100 kB
"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/index.ts var src_exports = {}; __export(src_exports, { BaseAdapter: () => BaseAdapter, ColorUtils: () => ColorUtils, CommonEdgeType: () => CommonEdgeType, D1Adapter: () => D1Adapter, GraphVisualizationManager: () => MermaidGraphVisualizer, KnowledgeExtractor: () => KnowledgeExtractor, KnowledgeGraph: () => KnowledgeGraph, LayoutUtils: () => LayoutUtils, MermaidGraphVisualizer: () => MermaidGraphVisualizer, MermaidUtils: () => MermaidUtils, PerformanceUtils: () => PerformanceUtils, SQLiteAdapter: () => SQLiteAdapter, SqlStorageAdapter: () => SqlStorageAdapter, createAdapter: () => createAdapter, extractFromConversation: () => extractFromConversation, extractFromText: () => extractFromText, processExtractedKnowledge: () => processExtractedKnowledge, schema: () => schema_exports }); module.exports = __toCommonJS(src_exports); // 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 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 schema_exports = {}; __export(schema_exports, { edgeIndices: () => edgeIndices, edges: () => edges, graphMetadata: () => graphMetadata, nodeIndices: () => nodeIndices, nodes: () => nodes, searchIndex: () => searchIndex }); 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