UNPKG

@codai/cbd

Version:

Codai Better Database - High-Performance Vector Memory System with HPKV-inspired architecture and MCP server

273 lines 11.3 kB
/** * CBD SQLite Storage Adapter * Persistent storage implementation using SQLite */ // @ts-ignore - better-sqlite3 module types import Database from 'better-sqlite3'; export class SQLiteStorageAdapter { db = null; databasePath; constructor(databasePath) { this.databasePath = databasePath; } async connect() { try { this.db = new Database(this.databasePath); // Enable WAL mode for better concurrency this.db.pragma('journal_mode = WAL'); this.db.pragma('synchronous = NORMAL'); this.db.pragma('temp_store = memory'); // Create table if it doesn't exist this.createTables(); console.log(`📁 Connected to SQLite database: ${this.databasePath}`); } catch (error) { throw new Error(`Failed to connect to database: ${error}`); } } async disconnect() { if (this.db) { this.db.close(); this.db = null; console.log('📁 Database connection closed'); } } createTables() { if (!this.db) throw new Error('Database not connected'); const createTableSQL = ` CREATE TABLE IF NOT EXISTS conversation_exchanges ( id INTEGER PRIMARY KEY AUTOINCREMENT, structured_key TEXT UNIQUE NOT NULL, project_name TEXT NOT NULL, session_name TEXT NOT NULL, sequence_number INTEGER NOT NULL, agent_id TEXT NOT NULL, user_request TEXT NOT NULL, assistant_response TEXT NOT NULL, conversation_context TEXT, metadata TEXT DEFAULT '{}', vector_embedding BLOB, confidence_score REAL DEFAULT 0.5, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ) `; const createIndexes = [ 'CREATE INDEX IF NOT EXISTS idx_structured_key ON conversation_exchanges(structured_key)', 'CREATE INDEX IF NOT EXISTS idx_project_session ON conversation_exchanges(project_name, session_name)', 'CREATE INDEX IF NOT EXISTS idx_agent_id ON conversation_exchanges(agent_id)', 'CREATE INDEX IF NOT EXISTS idx_created_at ON conversation_exchanges(created_at)', 'CREATE INDEX IF NOT EXISTS idx_confidence_score ON conversation_exchanges(confidence_score)', 'CREATE INDEX IF NOT EXISTS idx_sequence ON conversation_exchanges(project_name, session_name, sequence_number)' ]; this.db.exec(createTableSQL); createIndexes.forEach(sql => this.db.exec(sql)); } async storeConversation(exchange) { if (!this.db) throw new Error('Database not connected'); const stmt = this.db.prepare(` INSERT OR REPLACE INTO conversation_exchanges ( structured_key, project_name, session_name, sequence_number, agent_id, user_request, assistant_response, conversation_context, metadata, vector_embedding, confidence_score, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `); try { const vectorBlob = exchange.vectorEmbedding ? Buffer.from(exchange.vectorEmbedding.buffer) : null; stmt.run(exchange.structuredKey, exchange.projectName, exchange.sessionName, exchange.sequenceNumber, exchange.agentId, exchange.userRequest, exchange.assistantResponse, exchange.conversationContext, JSON.stringify(exchange.metadata), vectorBlob, exchange.confidenceScore, exchange.createdAt.toISOString(), exchange.updatedAt.toISOString()); return exchange.structuredKey; } catch (error) { throw new Error(`Failed to store conversation: ${error}`); } } async getConversation(structuredKey) { if (!this.db) throw new Error('Database not connected'); const stmt = this.db.prepare(` SELECT * FROM conversation_exchanges WHERE structured_key = ? `); try { const row = stmt.get(structuredKey); if (!row) return null; return this.mapRowToExchange(row); } catch (error) { throw new Error(`Failed to get conversation: ${error}`); } } async searchConversations(query) { if (!this.db) throw new Error('Database not connected'); let sql = 'SELECT * FROM conversation_exchanges WHERE 1=1'; const params = []; // Build dynamic query based on search criteria if (query.projectFilter) { sql += ' AND project_name = ?'; params.push(query.projectFilter); } if (query.sessionFilter) { sql += ' AND session_name = ?'; params.push(query.sessionFilter); } if (query.agentFilter) { sql += ' AND agent_id = ?'; params.push(query.agentFilter); } if (query.confidenceThreshold) { sql += ' AND confidence_score >= ?'; params.push(query.confidenceThreshold); } if (query.timeRange) { sql += ' AND created_at BETWEEN ? AND ?'; params.push(query.timeRange.start.toISOString(), query.timeRange.end.toISOString()); } // Text search in content if (query.query) { sql += ' AND (user_request LIKE ? OR assistant_response LIKE ?)'; const searchTerm = `%${query.query}%`; params.push(searchTerm, searchTerm); } sql += ' ORDER BY created_at DESC'; if (query.limit) { sql += ' LIMIT ?'; params.push(query.limit); } try { const stmt = this.db.prepare(sql); const rows = stmt.all(...params); return rows.map(row => this.mapRowToExchange(row)); } catch (error) { throw new Error(`Failed to search conversations: ${error}`); } } async updateConversation(structuredKey, updates) { if (!this.db) throw new Error('Database not connected'); const updateFields = []; const params = []; // Build dynamic update query Object.entries(updates).forEach(([key, value]) => { switch (key) { case 'userRequest': updateFields.push('user_request = ?'); params.push(value); break; case 'assistantResponse': updateFields.push('assistant_response = ?'); params.push(value); break; case 'conversationContext': updateFields.push('conversation_context = ?'); params.push(value); break; case 'metadata': updateFields.push('metadata = ?'); params.push(JSON.stringify(value)); break; case 'confidenceScore': updateFields.push('confidence_score = ?'); params.push(value); break; case 'vectorEmbedding': updateFields.push('vector_embedding = ?'); params.push(value ? Buffer.from(value.buffer) : null); break; } }); if (updateFields.length === 0) return false; updateFields.push('updated_at = ?'); params.push(new Date().toISOString()); params.push(structuredKey); const sql = `UPDATE conversation_exchanges SET ${updateFields.join(', ')} WHERE structured_key = ?`; try { const stmt = this.db.prepare(sql); const result = stmt.run(...params); return result.changes > 0; } catch (error) { throw new Error(`Failed to update conversation: ${error}`); } } async deleteConversation(structuredKey) { if (!this.db) throw new Error('Database not connected'); const stmt = this.db.prepare('DELETE FROM conversation_exchanges WHERE structured_key = ?'); try { const result = stmt.run(structuredKey); return result.changes > 0; } catch (error) { throw new Error(`Failed to delete conversation: ${error}`); } } async getStats() { if (!this.db) throw new Error('Database not connected'); try { const totalMemories = this.db.prepare('SELECT COUNT(*) as count FROM conversation_exchanges').get(); const uniqueAgents = this.db.prepare('SELECT COUNT(DISTINCT agent_id) as count FROM conversation_exchanges').get(); const uniqueProjects = this.db.prepare('SELECT COUNT(DISTINCT project_name) as count FROM conversation_exchanges').get(); const uniqueSessions = this.db.prepare('SELECT COUNT(DISTINCT session_name) as count FROM conversation_exchanges').get(); const avgConfidence = this.db.prepare('SELECT AVG(confidence_score) as avg FROM conversation_exchanges').get(); return { totalMemories: totalMemories.count, uniqueAgents: uniqueAgents.count, uniqueProjects: uniqueProjects.count, uniqueSessions: uniqueSessions.count, averageConfidence: avgConfidence.avg || 0, databaseSize: 0, // Would need file system access to get actual size lastUpdated: new Date() }; } catch (error) { throw new Error(`Failed to get database stats: ${error}`); } } /** * Get the next sequence number for a project/session combination */ async getNextSequenceNumber(projectName, sessionName) { if (!this.db) throw new Error('Database not connected'); const stmt = this.db.prepare(` SELECT MAX(sequence_number) as max_seq FROM conversation_exchanges WHERE project_name = ? AND session_name = ? `); try { const result = stmt.get(projectName, sessionName); return (result?.max_seq || 0) + 1; } catch (error) { throw new Error(`Failed to get next sequence number: ${error}`); } } mapRowToExchange(row) { const vectorEmbedding = row.vector_embedding ? new Float32Array(row.vector_embedding) : undefined; return { id: row.id?.toString(), structuredKey: row.structured_key, projectName: row.project_name, sessionName: row.session_name, sequenceNumber: row.sequence_number, agentId: row.agent_id, userRequest: row.user_request, assistantResponse: row.assistant_response, conversationContext: row.conversation_context || undefined, metadata: JSON.parse(row.metadata || '{}'), vectorEmbedding, confidenceScore: row.confidence_score, createdAt: new Date(row.created_at), updatedAt: new Date(row.updated_at) }; } } //# sourceMappingURL=SQLiteStorageAdapter.js.map