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
JavaScript
/**
* 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