UNPKG

claude-flow-tbowman01

Version:

Enterprise-grade AI agent orchestration with ruv-swarm integration (Alpha Release)

291 lines 9.75 kB
/** * SQLite backend implementation for memory storage */ import { promises as fs } from 'fs'; import path from 'path'; import { MemoryBackendError } from '../../utils/errors.js'; // Dynamic imports for SQLite let createDatabase; let isSQLiteAvailable; /** * SQLite-based memory backend */ export class SQLiteBackend { dbPath; logger; db; sqliteLoaded = false; constructor(dbPath, logger) { this.dbPath = dbPath; this.logger = logger; } async initialize() { this.logger.info('Initializing SQLite backend', { dbPath: this.dbPath }); try { // Load SQLite wrapper if not loaded if (!this.sqliteLoaded) { const module = await import('../sqlite-wrapper.js'); createDatabase = module.createDatabase; isSQLiteAvailable = module.isSQLiteAvailable; this.sqliteLoaded = true; } // Check if SQLite is available const sqliteAvailable = await isSQLiteAvailable(); if (!sqliteAvailable) { throw new Error('SQLite module not available'); } // Ensure directory exists const dir = path.dirname(this.dbPath); await fs.mkdir(dir, { recursive: true }); // Open SQLite connection this.db = await createDatabase(this.dbPath); // Enable WAL mode for better performance this.db.pragma('journal_mode = WAL'); this.db.pragma('synchronous = NORMAL'); this.db.pragma('cache_size = 1000'); this.db.pragma('temp_store = memory'); // Create tables this.createTables(); // Create indexes this.createIndexes(); this.logger.info('SQLite backend initialized'); } catch (error) { throw new MemoryBackendError('Failed to initialize SQLite backend', { error }); } } async shutdown() { this.logger.info('Shutting down SQLite backend'); if (this.db) { this.db.close(); delete this.db; } } async store(entry) { if (!this.db) { throw new MemoryBackendError('Database not initialized'); } const sql = ` INSERT OR REPLACE INTO memory_entries ( id, agent_id, session_id, type, content, context, timestamp, tags, version, parent_id, metadata ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `; const params = [ entry.id, entry.agentId, entry.sessionId, entry.type, entry.content, JSON.stringify(entry.context), entry.timestamp.toISOString(), JSON.stringify(entry.tags), entry.version, entry.parentId || null, entry.metadata ? JSON.stringify(entry.metadata) : null, ]; try { const stmt = this.db.prepare(sql); stmt.run(...params); } catch (error) { throw new MemoryBackendError('Failed to store entry', { error }); } } async retrieve(id) { if (!this.db) { throw new MemoryBackendError('Database not initialized'); } const sql = 'SELECT * FROM memory_entries WHERE id = ?'; try { const stmt = this.db.prepare(sql); const row = stmt.get(id); if (!row) { return undefined; } return this.rowToEntry(row); } catch (error) { throw new MemoryBackendError('Failed to retrieve entry', { error }); } } async update(id, entry) { // SQLite INSERT OR REPLACE handles updates await this.store(entry); } async delete(id) { if (!this.db) { throw new MemoryBackendError('Database not initialized'); } const sql = 'DELETE FROM memory_entries WHERE id = ?'; try { const stmt = this.db.prepare(sql); stmt.run(id); } catch (error) { throw new MemoryBackendError('Failed to delete entry', { error }); } } async query(query) { if (!this.db) { throw new MemoryBackendError('Database not initialized'); } const conditions = []; const params = []; if (query.agentId) { conditions.push('agent_id = ?'); params.push(query.agentId); } if (query.sessionId) { conditions.push('session_id = ?'); params.push(query.sessionId); } if (query.type) { conditions.push('type = ?'); params.push(query.type); } if (query.startTime) { conditions.push('timestamp >= ?'); params.push(query.startTime.toISOString()); } if (query.endTime) { conditions.push('timestamp <= ?'); params.push(query.endTime.toISOString()); } if (query.search) { conditions.push('(content LIKE ? OR tags LIKE ?)'); params.push(`%${query.search}%`, `%${query.search}%`); } if (query.tags && query.tags.length > 0) { const tagConditions = query.tags.map(() => 'tags LIKE ?'); conditions.push(`(${tagConditions.join(' OR ')})`); query.tags.forEach((tag) => params.push(`%"${tag}"%`)); } let sql = 'SELECT * FROM memory_entries'; if (conditions.length > 0) { sql += ' WHERE ' + conditions.join(' AND '); } sql += ' ORDER BY timestamp DESC'; if (query.limit) { sql += ' LIMIT ?'; params.push(query.limit); } if (query.offset) { // SQLite requires LIMIT when using OFFSET if (!query.limit) { sql += ' LIMIT -1'; // -1 means no limit in SQLite } sql += ' OFFSET ?'; params.push(query.offset); } try { const stmt = this.db.prepare(sql); const rows = stmt.all(...params); return rows.map((row) => this.rowToEntry(row)); } catch (error) { throw new MemoryBackendError('Failed to query entries', { error }); } } async getAllEntries() { if (!this.db) { throw new MemoryBackendError('Database not initialized'); } const sql = 'SELECT * FROM memory_entries ORDER BY timestamp DESC'; try { const stmt = this.db.prepare(sql); const rows = stmt.all(); return rows.map((row) => this.rowToEntry(row)); } catch (error) { throw new MemoryBackendError('Failed to get all entries', { error }); } } async getHealthStatus() { if (!this.db) { return { healthy: false, error: 'Database not initialized', }; } try { // Check database connectivity this.db.prepare('SELECT 1').get(); // Get metrics const countResult = this.db .prepare('SELECT COUNT(*) as count FROM memory_entries') .get(); const entryCount = countResult.count; const sizeResult = this.db .prepare('SELECT page_count * page_size as size FROM pragma_page_count(), pragma_page_size()') .get(); const dbSize = sizeResult.size; return { healthy: true, metrics: { entryCount, dbSizeBytes: dbSize, }, }; } catch (error) { return { healthy: false, error: error instanceof Error ? error.message : 'Unknown error', }; } } createTables() { const sql = ` CREATE TABLE IF NOT EXISTS memory_entries ( id TEXT PRIMARY KEY, agent_id TEXT NOT NULL, session_id TEXT NOT NULL, type TEXT NOT NULL, content TEXT NOT NULL, context TEXT NOT NULL, timestamp TEXT NOT NULL, tags TEXT NOT NULL, version INTEGER NOT NULL, parent_id TEXT, metadata TEXT, created_at TEXT DEFAULT CURRENT_TIMESTAMP, updated_at TEXT DEFAULT CURRENT_TIMESTAMP ) `; this.db.exec(sql); } createIndexes() { const indexes = [ 'CREATE INDEX IF NOT EXISTS idx_agent_id ON memory_entries(agent_id)', 'CREATE INDEX IF NOT EXISTS idx_session_id ON memory_entries(session_id)', 'CREATE INDEX IF NOT EXISTS idx_type ON memory_entries(type)', 'CREATE INDEX IF NOT EXISTS idx_timestamp ON memory_entries(timestamp)', 'CREATE INDEX IF NOT EXISTS idx_parent_id ON memory_entries(parent_id)', ]; for (const sql of indexes) { this.db.exec(sql); } } rowToEntry(row) { const entry = { id: row.id, agentId: row.agent_id, sessionId: row.session_id, type: row.type, content: row.content, context: JSON.parse(row.context), timestamp: new Date(row.timestamp), tags: JSON.parse(row.tags), version: row.version, }; if (row.parent_id) { entry.parentId = row.parent_id; } if (row.metadata) { entry.metadata = JSON.parse(row.metadata); } return entry; } } //# sourceMappingURL=sqlite.js.map