UNPKG

codecrucible-synth

Version:

Production-Ready AI Development Platform with Multi-Voice Synthesis, Smithery MCP Integration, Enterprise Security, and Zero-Timeout Reliability

558 lines 18.5 kB
/** * Production Database Manager with PostgreSQL, Connection Pooling, and Migrations * Replaces the SQLite-based DatabaseManager with enterprise-grade database support */ import { Pool } from 'pg'; import knex from 'knex'; import Redis from 'redis'; import { logger } from '../core/logger.js'; import { performance } from 'perf_hooks'; export class ProductionDatabaseManager { config; masterPool; replicaPools = []; knexInstance; redisClient; queryMetrics = new Map(); healthCheckInterval; constructor(config) { this.config = { pool: { min: 2, max: 20, idleTimeoutMillis: 60000, createTimeoutMillis: 30000, destroyTimeoutMillis: 5000, reapIntervalMillis: 1000, createRetryIntervalMillis: 200, }, migrations: { directory: './migrations', tableName: 'knex_migrations', }, ...config, }; } /** * Initialize database connections */ async initialize() { try { await this.initializePrimaryConnection(); await this.initializeReadReplicas(); await this.initializeRedisCache(); await this.runMigrations(); this.startHealthChecks(); logger.info('Production database manager initialized', { type: this.config.type, replicas: this.replicaPools.length, poolMax: this.config.pool?.max, redis: !!this.redisClient, }); } catch (error) { logger.error('Failed to initialize database:', error); throw error; } } /** * Initialize primary database connection */ async initializePrimaryConnection() { if (this.config.type === 'postgresql') { this.masterPool = new Pool({ host: this.config.host || 'localhost', port: this.config.port || 5432, database: this.config.database, user: this.config.username, password: this.config.password, ssl: this.config.ssl, min: this.config.pool?.min || 2, max: this.config.pool?.max || 20, idleTimeoutMillis: this.config.pool?.idleTimeoutMillis || 60000, }); // Test connection const client = await this.masterPool.connect(); const result = await client.query('SELECT NOW()'); client.release(); logger.info('PostgreSQL primary connection established', { serverTime: result.rows[0].now, }); } // Initialize Knex for query building and migrations this.knexInstance = knex({ client: this.config.type === 'postgresql' ? 'pg' : 'mysql2', connection: { host: this.config.host, port: this.config.port, database: this.config.database, user: this.config.username, password: this.config.password, ssl: this.config.ssl, }, pool: this.config.pool, migrations: this.config.migrations, acquireConnectionTimeout: this.config.pool?.idleTimeoutMillis || 60000, }); } /** * Initialize read replica connections */ async initializeReadReplicas() { if (!this.config.readReplicas) return; for (const replica of this.config.readReplicas) { try { const replicaPool = new Pool({ host: replica.host, port: replica.port || this.config.port || 5432, database: replica.database, user: replica.username || this.config.username, password: replica.password || this.config.password, ssl: this.config.ssl, ...this.config.pool, }); // Test replica connection const client = await replicaPool.connect(); await client.query('SELECT 1'); client.release(); this.replicaPools.push(replicaPool); logger.info(`Read replica connected: ${replica.host}`); } catch (error) { logger.error(`Failed to connect to read replica ${replica.host}:`, error); } } } /** * Initialize Redis cache */ async initializeRedisCache() { if (!this.config.redis) return; try { this.redisClient = Redis.createClient({ socket: { host: this.config.redis.host, port: this.config.redis.port, }, password: this.config.redis.password, database: this.config.redis.db, }); await this.redisClient.connect(); await this.redisClient.ping(); logger.info('Redis cache connected'); } catch (error) { logger.error('Redis connection failed:', error); this.redisClient = undefined; } } /** * Run database migrations */ async runMigrations() { if (!this.knexInstance) return; try { const [batchNo, migrations] = await this.knexInstance.migrate.latest(); if (migrations.length > 0) { logger.info(`Ran ${migrations.length} migrations in batch ${batchNo}`, { migrations: migrations, }); } else { logger.info('Database is up to date'); } } catch (error) { logger.error('Migration failed:', error); throw error; } } /** * Execute query with performance tracking and caching */ async query(sql, params = [], options = {}) { const startTime = performance.now(); const queryId = this.generateQueryId(sql); try { // Check cache first if (options.cache && this.redisClient) { const cached = await this.getCachedResult(queryId, params); if (cached) { this.trackQueryMetrics(queryId, performance.now() - startTime, true); return cached; } } // Choose connection pool const pool = options.readReplica && this.replicaPools.length > 0 ? this.getRandomReplica() : this.masterPool; if (!pool) { throw new Error('No database connection available'); } // Execute query const client = await pool.connect(); try { if (options.timeout) { // Set statement timeout await client.query(`SET statement_timeout = ${options.timeout}`); } const result = await client.query(sql, params); // Cache result if caching is enabled if (options.cache && this.redisClient) { await this.setCachedResult(queryId, params, result, options.cacheTTL || 300); } this.trackQueryMetrics(queryId, performance.now() - startTime, false); return result; } finally { client.release(); } } catch (error) { this.trackQueryMetrics(queryId, performance.now() - startTime, false, true); logger.error('Query execution failed:', { sql: sql.substring(0, 100), error: error.message, duration: performance.now() - startTime, }); throw error; } } /** * Begin database transaction */ async beginTransaction() { if (!this.masterPool) { throw new Error('Master pool not initialized'); } const client = await this.masterPool.connect(); await client.query('BEGIN'); return { query: async (sql, params = []) => { return client.query(sql, params); }, commit: async () => { try { await client.query('COMMIT'); } finally { client.release(); } }, rollback: async () => { try { await client.query('ROLLBACK'); } finally { client.release(); } }, }; } /** * Execute multiple queries in a transaction */ async transaction(callback) { const trx = await this.beginTransaction(); try { const result = await callback(trx); await trx.commit(); return result; } catch (error) { await trx.rollback(); throw error; } } /** * Knex query builder access */ get knex() { if (!this.knexInstance) { throw new Error('Knex not initialized'); } return this.knexInstance; } /** * Store voice interaction with optimized query */ async storeVoiceInteraction(interaction) { const query = ` INSERT INTO voice_interactions (session_id, voice_name, prompt, response, confidence, tokens_used, created_at) VALUES ($1, $2, $3, $4, $5, $6, NOW()) RETURNING id `; const result = await this.query(query, [ interaction.sessionId, interaction.voiceName, interaction.prompt, interaction.response, interaction.confidence, interaction.tokensUsed, ]); return result.rows[0].id; } /** * Store code analysis with JSONB support */ async storeCodeAnalysis(analysis) { const query = ` INSERT INTO code_analysis (project_id, file_path, analysis_type, results, quality_score, created_at) VALUES ($1, $2, $3, $4, $5, NOW()) RETURNING id `; const result = await this.query(query, [ analysis.projectId, analysis.filePath, analysis.analysisType, JSON.stringify(analysis.results), analysis.qualityScore || null, ]); return result.rows[0].id; } /** * Get session history with pagination */ async getSessionHistory(sessionId, limit = 50, offset = 0) { const query = ` SELECT * FROM voice_interactions WHERE session_id = $1 ORDER BY created_at DESC LIMIT $2 OFFSET $3 `; const result = await this.query(query, [sessionId, limit, offset], { cache: true, cacheTTL: 60, // 1 minute cache readReplica: true, }); return result.rows; } /** * Get aggregated statistics with caching */ async getStats() { const query = ` WITH stats AS ( SELECT (SELECT COUNT(*) FROM voice_interactions) as total_interactions, (SELECT COUNT(*) FROM projects) as total_projects, (SELECT COUNT(*) FROM code_analysis) as total_analysis, (SELECT AVG(confidence) FROM voice_interactions) as avg_confidence, (SELECT COUNT(*) FROM voice_interactions WHERE created_at >= NOW() - INTERVAL '24 hours') as interactions_24h ) SELECT * FROM stats `; const result = await this.query(query, [], { cache: true, cacheTTL: 300, // 5 minute cache readReplica: true, }); return result.rows[0]; } /** * Bulk insert with batch processing */ async bulkInsert(table, data, batchSize = 1000) { if (!data.length) return; const columns = Object.keys(data[0]); const batches = this.createBatches(data, batchSize); for (const batch of batches) { const placeholders = batch .map((_, i) => { const start = i * columns.length + 1; const end = start + columns.length - 1; return `(${Array.from({ length: columns.length }, (_, j) => `$${start + j}`).join(', ')})`; }) .join(', '); const query = ` INSERT INTO ${table} (${columns.join(', ')}) VALUES ${placeholders} `; const params = batch.flatMap(row => columns.map(col => row[col])); await this.query(query, params); } } /** * Health check for all connections */ async healthCheck() { const health = { master: false, replicas: [], redis: false, metrics: this.getQueryMetrics(), }; // Check master connection try { if (this.masterPool) { const client = await this.masterPool.connect(); await client.query('SELECT 1'); client.release(); health.master = true; } } catch (error) { logger.error('Master database health check failed:', error); } // Check replica connections for (const pool of this.replicaPools) { try { const client = await pool.connect(); await client.query('SELECT 1'); client.release(); health.replicas.push(true); } catch (error) { health.replicas.push(false); logger.error('Replica health check failed:', error); } } // Check Redis connection try { if (this.redisClient) { await this.redisClient.ping(); health.redis = true; } } catch (error) { logger.error('Redis health check failed:', error); } return health; } /** * Get connection pool status */ getPoolStatus() { return { master: this.masterPool ? { totalCount: this.masterPool.totalCount, idleCount: this.masterPool.idleCount, waitingCount: this.masterPool.waitingCount, } : null, replicas: this.replicaPools.map(pool => ({ totalCount: pool.totalCount, idleCount: pool.idleCount, waitingCount: pool.waitingCount, })), }; } /** * Private helper methods */ getRandomReplica() { const randomIndex = Math.floor(Math.random() * this.replicaPools.length); return this.replicaPools[randomIndex]; } generateQueryId(sql) { return sql.replace(/\s+/g, ' ').trim().substring(0, 100); } async getCachedResult(queryId, params) { if (!this.redisClient) return null; try { const key = `query:${queryId}:${JSON.stringify(params)}`; const cached = await this.redisClient.get(key); return cached ? JSON.parse(cached) : null; } catch (error) { logger.warn('Cache get failed:', error); return null; } } async setCachedResult(queryId, params, result, ttl) { if (!this.redisClient) return; try { const key = `query:${queryId}:${JSON.stringify(params)}`; await this.redisClient.setEx(key, ttl, JSON.stringify(result)); } catch (error) { logger.warn('Cache set failed:', error); } } trackQueryMetrics(queryId, duration, fromCache = false, error = false) { if (!this.queryMetrics.has(queryId)) { this.queryMetrics.set(queryId, { count: 0, totalTime: 0 }); } const metrics = this.queryMetrics.get(queryId); metrics.count++; if (!fromCache && !error) { metrics.totalTime += duration; } } getQueryMetrics() { const metrics = {}; for (const [queryId, stats] of this.queryMetrics.entries()) { metrics[queryId] = { count: stats.count, averageTime: stats.totalTime / stats.count, totalTime: stats.totalTime, }; } return metrics; } createBatches(data, batchSize) { const batches = []; for (let i = 0; i < data.length; i += batchSize) { batches.push(data.slice(i, i + batchSize)); } return batches; } startHealthChecks() { this.healthCheckInterval = setInterval(async () => { const health = await this.healthCheck(); if (!health.master || health.replicas.includes(false)) { logger.warn('Database health check detected issues', health); } }, 60000); // Check every minute } /** * Close all connections */ async close() { if (this.healthCheckInterval) { clearInterval(this.healthCheckInterval); } await Promise.all([ this.masterPool?.end(), ...this.replicaPools.map(async (pool) => pool.end()), this.knexInstance?.destroy(), this.redisClient?.disconnect(), ]); logger.info('All database connections closed'); } /** * Check if database manager is initialized */ isInitialized() { return this.masterPool !== null; } /** * Get raw database instance for migrations * Returns knex instance for raw SQL operations */ getRawDb() { if (!this.knexInstance) { throw new Error('Database not initialized'); } return this.knexInstance; } /** * Backup database (stub for backup manager) */ async backup(options) { // This is a stub - implement actual backup logic logger.info('Database backup requested'); return 'backup-placeholder-path'; } } export default ProductionDatabaseManager; //# sourceMappingURL=production-database-manager.js.map