claude-flow-novice
Version:
Claude Flow Novice - Advanced orchestration platform for multi-agent AI workflows with CFN Loop architecture Includes Local RuVector Accelerator and all CFN skills for complete functionality.
315 lines (279 loc) • 7.95 kB
text/typescript
import sqlite3 from 'sqlite3';
import pg from 'pg';
import { StandardError, ErrorCode } from '../../../src/lib/errors';
interface DatabaseConfig {
sqlite?: {
path: string;
};
postgres?: {
host: string;
port?: number;
database: string;
user?: string;
password?: string;
poolSize?: number;
idleTimeout?: number;
};
}
interface QueryResult<T> {
rows: T[];
rowCount: number;
}
export class DatabaseService {
private sqliteDb?: sqlite3.Database;
private pgPool?: pg.Pool;
private config: DatabaseConfig;
constructor(config: DatabaseConfig) {
this.config = config;
}
/**
* Initialize database connections
*/
async initialize(): Promise<void> {
if (this.config.sqlite) {
this.sqliteDb = new sqlite3.Database(
this.config.sqlite.path,
(err) => {
if (err) {
throw new StandardError(
'Failed to connect to SQLite database',
ErrorCode.DATABASE_CONNECTION_FAILED,
{ path: this.config.sqlite?.path },
err
);
}
}
);
}
if (this.config.postgres) {
this.pgPool = new pg.Pool({
host: this.config.postgres.host,
port: this.config.postgres.port || 5432,
database: this.config.postgres.database,
user: this.config.postgres.user || 'postgres',
password: this.config.postgres.password,
max: this.config.postgres.poolSize || 20,
idleTimeoutMillis: this.config.postgres.idleTimeout || 30000,
});
// Test connection
try {
const client = await this.pgPool.connect();
client.release();
} catch (err) {
throw new StandardError(
'Failed to connect to PostgreSQL database',
ErrorCode.DATABASE_CONNECTION_FAILED,
{
host: this.config.postgres.host,
database: this.config.postgres.database
},
err
);
}
}
}
/**
* Execute a query on the specified database
*/
async query<T = any>(
database: 'sqlite' | 'postgres',
sql: string,
params: any[] = []
): Promise<T[]> {
const start = Date.now();
try {
if (database === 'sqlite') {
return await this.querySQLite<T>(sql, params);
} else {
return await this.queryPostgres<T>(sql, params);
}
} catch (error) {
const duration = Date.now() - start;
throw new StandardError(
'Database query failed',
ErrorCode.DATABASE_QUERY_FAILED,
{
database,
sql: sql.substring(0, 100), // Truncate for logging
paramCount: params.length,
duration
},
error
);
}
}
/**
* Execute operations within a transaction
*/
async transaction<T>(
database: 'sqlite' | 'postgres',
callback: (db: DatabaseService) => Promise<T>
): Promise<T> {
if (database === 'postgres') {
return await this.transactionPostgres(callback);
} else {
return await this.transactionSQLite(callback);
}
}
/**
* Check database health
*/
async healthCheck(): Promise<{
sqlite: boolean;
postgres: boolean;
}> {
const health = {
sqlite: false,
postgres: false
};
if (this.sqliteDb) {
try {
await this.querySQLite('SELECT 1');
health.sqlite = true;
} catch (error) {
health.sqlite = false;
}
}
if (this.pgPool) {
try {
await this.queryPostgres('SELECT 1');
health.postgres = true;
} catch (error) {
health.postgres = false;
}
}
return health;
}
/**
* Close all database connections
*/
async close(): Promise<void> {
const closeOperations: Promise<void>[] = [];
if (this.sqliteDb) {
closeOperations.push(
new Promise((resolve, reject) => {
this.sqliteDb!.close((err) => {
if (err) reject(err);
else resolve();
});
})
);
}
if (this.pgPool) {
closeOperations.push(this.pgPool.end());
}
try {
await Promise.all(closeOperations);
} catch (error) {
throw new StandardError(
'Failed to close database connections',
ErrorCode.DATABASE_ERROR,
{},
error
);
}
}
// Private methods
private async querySQLite<T>(sql: string, params: any[]): Promise<T[]> {
return new Promise((resolve, reject) => {
if (!this.sqliteDb) {
reject(new Error('SQLite database not initialized'));
return;
}
this.sqliteDb.all(sql, params, (err, rows) => {
if (err) {
reject(err);
} else {
resolve(rows as T[]);
}
});
});
}
private async queryPostgres<T>(sql: string, params: any[]): Promise<T[]> {
if (!this.pgPool) {
throw new Error('PostgreSQL pool not initialized');
}
const result = await this.pgPool.query(sql, params);
return result.rows as T[];
}
private async transactionPostgres<T>(
callback: (db: DatabaseService) => Promise<T>
): Promise<T> {
if (!this.pgPool) {
throw new Error('PostgreSQL pool not initialized');
}
const client = await this.pgPool.connect();
try {
await client.query('BEGIN');
// Create a temporary DatabaseService that uses this client
const txDb = new DatabaseService(this.config);
txDb.pgPool = { query: client.query.bind(client) } as any;
const result = await callback(txDb);
await client.query('COMMIT');
return result;
} catch (error) {
await client.query('ROLLBACK');
throw new StandardError(
'Transaction failed and was rolled back',
ErrorCode.DATABASE_TRANSACTION_FAILED,
{ database: 'postgres' },
error
);
} finally {
client.release();
}
}
private async transactionSQLite<T>(
callback: (db: DatabaseService) => Promise<T>
): Promise<T> {
if (!this.sqliteDb) {
throw new Error('SQLite database not initialized');
}
return new Promise(async (resolve, reject) => {
this.sqliteDb!.run('BEGIN TRANSACTION', async (err) => {
if (err) {
reject(new StandardError(
'Failed to begin transaction',
ErrorCode.DATABASE_TRANSACTION_FAILED,
{ database: 'sqlite' },
err
));
return;
}
try {
const result = await callback(this);
this.sqliteDb!.run('COMMIT', (commitErr) => {
if (commitErr) {
reject(new StandardError(
'Failed to commit transaction',
ErrorCode.DATABASE_TRANSACTION_FAILED,
{ database: 'sqlite' },
commitErr
));
} else {
resolve(result);
}
});
} catch (error) {
this.sqliteDb!.run('ROLLBACK', (rollbackErr) => {
if (rollbackErr) {
reject(new StandardError(
'Transaction failed and rollback also failed',
ErrorCode.DATABASE_TRANSACTION_FAILED,
{ database: 'sqlite' },
rollbackErr
));
} else {
reject(new StandardError(
'Transaction failed and was rolled back',
ErrorCode.DATABASE_TRANSACTION_FAILED,
{ database: 'sqlite' },
error
));
}
});
}
});
});
}
}