Skip to main content
Glama
joelmnz

Article Manager MCP Server

by joelmnz
database.ts7.74 kB
import { Pool, PoolClient, QueryResult, QueryResultRow } from 'pg'; import { handleDatabaseError, DatabaseServiceError, DatabaseErrorType, retryDatabaseOperation, logDatabaseError } from './databaseErrors.js'; export interface DatabaseConfig { host: string; port: number; database: string; user: string; password: string; ssl?: boolean; maxConnections?: number; idleTimeoutMillis?: number; connectionTimeoutMillis?: number; } export interface DatabaseClient { query<T extends QueryResultRow = any>(text: string, params?: any[]): Promise<QueryResult<T>>; release(): void; } class DatabaseService { private pool: Pool | null = null; private config: DatabaseConfig | null = null; /** * Initialize the database connection pool */ async connect(config: DatabaseConfig): Promise<void> { if (this.pool) { await this.disconnect(); } this.config = config; try { this.pool = new Pool({ host: config.host, port: config.port, database: config.database, user: config.user, password: config.password, ssl: config.ssl, max: config.maxConnections || 50, idleTimeoutMillis: config.idleTimeoutMillis || 30000, connectionTimeoutMillis: config.connectionTimeoutMillis || 2000, }); // Set up error handlers for the pool this.pool.on('error', (err) => { const dbError = handleDatabaseError(err); logDatabaseError(dbError, 'Pool Error'); console.error('Unexpected error on idle client', err); }); // Test the connection with retry await retryDatabaseOperation(async () => { const client = await this.pool!.connect(); try { await client.query('SELECT NOW()'); console.log('Database connection established successfully'); } finally { client.release(); } }); } catch (error) { // Clean up on connection failure if (this.pool) { await this.pool.end().catch(() => {}); this.pool = null; } const dbError = handleDatabaseError(error); logDatabaseError(dbError, 'Connection'); throw new DatabaseServiceError( DatabaseErrorType.CONNECTION_ERROR, `Database connection failed: ${dbError.message}`, 'Unable to connect to the database. Please check your database configuration and try again.', dbError.originalError ); } } /** * Disconnect from the database */ async disconnect(): Promise<void> { if (this.pool) { try { await this.pool.end(); console.log('Database connection closed'); } catch (error) { const dbError = handleDatabaseError(error); logDatabaseError(dbError, 'Disconnect'); console.warn('Error during database disconnect:', dbError.message); } finally { this.pool = null; this.config = null; } } } /** * Execute a query with optional parameters */ async query<T extends QueryResultRow = any>(text: string, params?: any[]): Promise<QueryResult<T>> { if (!this.pool) { throw new DatabaseServiceError( DatabaseErrorType.CONNECTION_ERROR, 'Database not connected. Call connect() first.', 'Database connection is not available. Please try again.' ); } try { const result = await this.pool.query<T>(text, params); return result; } catch (error) { const dbError = handleDatabaseError(error); logDatabaseError(dbError, 'Query'); throw dbError; } } /** * Execute a transaction with a callback function */ async transaction<T>(callback: (client: DatabaseClient) => Promise<T>): Promise<T> { if (!this.pool) { throw new DatabaseServiceError( DatabaseErrorType.CONNECTION_ERROR, 'Database not connected. Call connect() first.', 'Database connection is not available. Please try again.' ); } let client: PoolClient | null = null; let transactionStarted = false; try { client = await this.pool.connect(); await client.query('BEGIN'); transactionStarted = true; const wrappedClient: DatabaseClient = { query: <U extends QueryResultRow = any>(text: string, params?: any[]) => { try { return client!.query<U>(text, params); } catch (error) { const dbError = handleDatabaseError(error); logDatabaseError(dbError, 'Transaction Query'); throw dbError; } }, release: () => client!.release() }; const result = await callback(wrappedClient); await client.query('COMMIT'); transactionStarted = false; return result; } catch (error) { // Attempt to rollback if transaction was started if (client && transactionStarted) { try { await client.query('ROLLBACK'); } catch (rollbackError) { const rollbackDbError = handleDatabaseError(rollbackError); logDatabaseError(rollbackDbError, 'Transaction Rollback'); console.error('Failed to rollback transaction:', rollbackDbError.message); } } const dbError = handleDatabaseError(error); logDatabaseError(dbError, 'Transaction'); // Enhance transaction errors with more context if (dbError.type === DatabaseErrorType.UNKNOWN_ERROR) { throw new DatabaseServiceError( DatabaseErrorType.TRANSACTION_ERROR, `Transaction failed: ${dbError.message}`, 'The operation could not be completed due to a conflict. Please try again.', dbError.originalError ); } throw dbError; } finally { if (client) { client.release(); } } } /** * Get a client from the pool for manual transaction management */ async getClient(): Promise<PoolClient> { if (!this.pool) { throw new DatabaseServiceError( DatabaseErrorType.CONNECTION_ERROR, 'Database not connected. Call connect() first.', 'Database connection is not available. Please try again.' ); } try { return await this.pool.connect(); } catch (error) { const dbError = handleDatabaseError(error); logDatabaseError(dbError, 'Get Client'); throw dbError; } } /** * Check if the database is connected */ isConnected(): boolean { return this.pool !== null; } /** * Get connection pool statistics */ getPoolStats() { if (!this.pool) { return null; } return { totalCount: this.pool.totalCount, idleCount: this.pool.idleCount, waitingCount: this.pool.waitingCount, }; } } // Export singleton instance export const database = new DatabaseService(); /** * Get database configuration from environment variables */ export function getDatabaseConfig(): DatabaseConfig { const config: DatabaseConfig = { host: process.env.DB_HOST || 'localhost', port: parseInt(process.env.DB_PORT || '5432', 10), database: process.env.DB_NAME || 'article_manager', user: process.env.DB_USER || 'article_user', password: process.env.DB_PASSWORD || '', ssl: process.env.DB_SSL === 'true', maxConnections: parseInt(process.env.DB_MAX_CONNECTIONS || '20', 10), idleTimeoutMillis: parseInt(process.env.DB_IDLE_TIMEOUT || '30000', 10), connectionTimeoutMillis: parseInt(process.env.DB_CONNECTION_TIMEOUT || '2000', 10), }; // Validate required configuration if (!config.password) { throw new Error('DB_PASSWORD environment variable is required'); } return config; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/joelmnz/mcp-markdown-manager'

If you have feedback or need assistance with the MCP directory API, please join our Discord server