UNPKG

@codai/memorai

Version:

Universal Database & Storage Service for CODAI Ecosystem - CBD Backend

282 lines 10 kB
/** * Database Service - Production Implementation */ import { EventEmitter } from 'events'; export class DatabaseService extends EventEmitter { constructor(config) { super(); this.config = config; this.prisma = null; this.isConnected = false; } async initialize() { try { // TODO: Initialize database client (Prisma/Better-SQLite3 pending schema setup) // this.prisma = new PrismaClient({ // datasources: { // db: { // url: this.config.url || process.env.DATABASE_URL // } // }, // log: this.config.logging ? ['query', 'info', 'warn', 'error'] : [] // }) // Test connection - temporarily disabled // await this.prisma.$connect() // Run migrations if needed in development if (process.env.NODE_ENV === 'development') { try { // await this.prisma.$queryRaw`SELECT 1` console.log('Database connection check - temporarily disabled'); } catch (error) { console.warn('Database might need migrations:', error); } } this.isConnected = true; this.emit('connected'); console.log('🗃️ Database Service initialized'); } catch (error) { console.error('Failed to initialize database:', error); this.emit('error', error); throw error; } } async shutdown() { if (this.prisma && this.isConnected) { try { // await this.prisma.$disconnect() this.isConnected = false; this.emit('disconnected'); console.log('🗃️ Database Service shutdown'); } catch (error) { console.error('Error shutting down database:', error); throw error; } } } async execute(query) { if (!this.prisma) { throw new Error('Database not initialized'); } try { const startTime = Date.now(); let result = []; switch (query.operation) { case 'select': result = await this.executeSelect(query); break; case 'insert': result = await this.executeInsert(query); break; case 'update': result = await this.executeUpdate(query); break; case 'delete': result = await this.executeDelete(query); break; default: throw new Error(`Unsupported operation: ${query.operation}`); } const executionTime = Date.now() - startTime; this.emit('query:executed', { query, executionTime, resultCount: result.length }); return result; } catch (error) { console.error('Database query error:', error); this.emit('query:error', { query, error }); throw error; } } async executeSelect(query) { if (!this.prisma) throw new Error('Database not initialized'); // Build dynamic query based on table const model = this.getModelForTable(query.table); if (!model) { throw new Error(`Unknown table: ${query.table}`); } // Build where conditions const where = this.buildWhereConditions(query.conditions); // Build query options const queryOptions = { where, select: query.fields ? this.buildSelectFields(query.fields) : undefined, orderBy: query.orderBy ? this.buildOrderBy(query.orderBy) : undefined, take: query.limit, skip: query.offset }; // Remove undefined options Object.keys(queryOptions).forEach(key => { if (queryOptions[key] === undefined) { delete queryOptions[key]; } }); return await model.findMany(queryOptions); } async executeInsert(query) { if (!this.prisma) throw new Error('Database not initialized'); const model = this.getModelForTable(query.table); if (!model) { throw new Error(`Unknown table: ${query.table}`); } // Handle single or batch insert if (Array.isArray(query.data)) { // Batch insert const results = []; for (const item of query.data) { const result = await model.create({ data: item }); results.push(result); } return results; } else { // Single insert const result = await model.create({ data: query.data }); return [result]; } } async executeUpdate(query) { if (!this.prisma) throw new Error('Database not initialized'); const model = this.getModelForTable(query.table); if (!model) { throw new Error(`Unknown table: ${query.table}`); } const where = this.buildWhereConditions(query.conditions); if (Object.keys(where).length === 0) { throw new Error('Update operations require WHERE conditions'); } // Use updateMany for multiple records const updateResult = await model.updateMany({ where, data: query.data }); // Return updated records return await model.findMany({ where }); } async executeDelete(query) { if (!this.prisma) throw new Error('Database not initialized'); const model = this.getModelForTable(query.table); if (!model) { throw new Error(`Unknown table: ${query.table}`); } const where = this.buildWhereConditions(query.conditions); if (Object.keys(where).length === 0) { throw new Error('Delete operations require WHERE conditions'); } // Get records before deletion const recordsToDelete = await model.findMany({ where }); // Perform deletion await model.deleteMany({ where }); return recordsToDelete; } getModelForTable(tableName) { if (!this.prisma) return null; // Map table names to Prisma models const tableModelMap = { 'agents': 'agent', 'memories': 'memory', 'memory_sessions': 'memorySession', 'context_windows': 'contextWindow', 'memory_embeddings': 'memoryEmbedding', 'memory_associations': 'memoryAssociation', 'memory_retrievals': 'memoryRetrieval', 'storage_files': 'storageFile' // Custom table for file metadata }; const modelName = tableModelMap[tableName]; if (!modelName) return null; return this.prisma[modelName]; } buildWhereConditions(conditions) { if (!conditions || conditions.length === 0) return {}; const where = {}; for (const condition of conditions) { const { field, operator, value } = condition; switch (operator) { case '=': where[field] = value; break; case '!=': where[field] = { not: value }; break; case '>': where[field] = { gt: value }; break; case '>=': where[field] = { gte: value }; break; case '<': where[field] = { lt: value }; break; case '<=': where[field] = { lte: value }; break; case 'LIKE': where[field] = { contains: value }; break; case 'IN': where[field] = { in: Array.isArray(value) ? value : [value] }; break; case 'NOT IN': where[field] = { notIn: Array.isArray(value) ? value : [value] }; break; case 'IS NULL': where[field] = null; break; case 'IS NOT NULL': where[field] = { not: null }; break; default: throw new Error(`Unsupported operator: ${operator}`); } } return where; } buildSelectFields(fields) { const select = {}; for (const field of fields) { select[field] = true; } return select; } buildOrderBy(orderBy) { if (!orderBy || orderBy.length === 0) return undefined; return orderBy.map(order => ({ [order.field]: order.direction?.toLowerCase() || 'asc' })); } async getHealth() { if (!this.prisma || !this.isConnected) { return { status: 'unhealthy', details: { connected: false } }; } try { // Simple health check query await this.prisma.$queryRaw `SELECT 1`; return { status: 'healthy', details: { connected: true, database: this.config.type || 'sqlite' } }; } catch (error) { return { status: 'unhealthy', details: { connected: false, error: error instanceof Error ? error.message : 'Unknown error' } }; } } } //# sourceMappingURL=DatabaseService.js.map