Files
mcp-outline-postgresql/src/pg-client.ts
Emanuel Almeida 5f49cb63e8 feat: v1.3.1 - Multi-transport + Production deployment
- Add HTTP transport (StreamableHTTPServerTransport)
- Add shared server module (src/server/)
- Configure production for hub.descomplicar.pt
- Add SSH tunnel script (start-tunnel.sh)
- Fix connection leak in pg-client.ts
- Fix atomicity bug in comments deletion
- Update docs with test plan for 164 tools

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 17:06:30 +00:00

169 lines
4.3 KiB
TypeScript

/**
* MCP Outline PostgreSQL - PostgreSQL Client
* @author Descomplicar® | @link descomplicar.pt | @copyright 2026
*/
import { Pool, PoolConfig, QueryResult, QueryResultRow } from 'pg';
import { DatabaseConfig } from './config/database.js';
import { logger } from './utils/logger.js';
export class PgClient {
private pool: Pool;
private isConnected: boolean = false;
constructor(config: DatabaseConfig) {
const poolConfig: PoolConfig = config.connectionString
? {
connectionString: config.connectionString,
max: config.max,
idleTimeoutMillis: config.idleTimeoutMillis,
connectionTimeoutMillis: config.connectionTimeoutMillis
}
: {
host: config.host,
port: config.port,
user: config.user,
password: config.password,
database: config.database,
ssl: config.ssl ? { rejectUnauthorized: false } : false,
max: config.max,
idleTimeoutMillis: config.idleTimeoutMillis,
connectionTimeoutMillis: config.connectionTimeoutMillis
};
this.pool = new Pool(poolConfig);
// Handle pool errors
this.pool.on('error', (err) => {
logger.error('Unexpected PostgreSQL pool error', { error: err.message });
});
}
/**
* Get the underlying pool for direct access
*/
getPool(): Pool {
return this.pool;
}
/**
* Test database connection
*/
async testConnection(): Promise<boolean> {
let client = null;
try {
client = await this.pool.connect();
await client.query('SELECT 1');
this.isConnected = true;
logger.info('PostgreSQL connection successful');
return true;
} catch (error) {
logger.error('PostgreSQL connection failed', {
error: error instanceof Error ? error.message : String(error)
});
this.isConnected = false;
return false;
} finally {
if (client) {
client.release();
}
}
}
/**
* Execute a query with parameters
*/
async query<T extends QueryResultRow = any>(sql: string, params?: any[]): Promise<T[]> {
const start = Date.now();
try {
const result = await this.pool.query<T>(sql, params);
const duration = Date.now() - start;
logger.debug('Query executed', {
sql: sql.substring(0, 100),
duration,
rowCount: result.rowCount
});
return result.rows;
} catch (error) {
const duration = Date.now() - start;
logger.error('Query failed', {
sql: sql.substring(0, 100),
duration,
error: error instanceof Error ? error.message : String(error)
});
throw error;
}
}
/**
* Execute a query and return the full result
*/
async queryRaw<T extends QueryResultRow = any>(sql: string, params?: any[]): Promise<QueryResult<T>> {
return this.pool.query<T>(sql, params);
}
/**
* Execute a query and return a single row
*/
async queryOne<T extends QueryResultRow = any>(sql: string, params?: any[]): Promise<T | null> {
const rows = await this.query<T>(sql, params);
return rows.length > 0 ? rows[0] : null;
}
/**
* Execute multiple queries in a transaction
*/
async transaction<T>(callback: (client: any) => Promise<T>): Promise<T> {
const client = await this.pool.connect();
try {
await client.query('BEGIN');
const result = await callback(client);
await client.query('COMMIT');
return result;
} catch (error) {
try {
await client.query('ROLLBACK');
} catch (rollbackError) {
logger.error('Rollback failed', {
error: rollbackError instanceof Error ? rollbackError.message : String(rollbackError),
});
}
throw error;
} finally {
client.release();
}
}
/**
* Close the pool
*/
async close(): Promise<void> {
await this.pool.end();
this.isConnected = false;
logger.info('PostgreSQL pool closed');
}
/**
* Check if connected
*/
isPoolConnected(): boolean {
return this.isConnected;
}
}
// Export a singleton factory
let instance: PgClient | null = null;
export function createPgClient(config: DatabaseConfig): PgClient {
if (!instance) {
instance = new PgClient(config);
}
return instance;
}
export function getPgClient(): PgClient | null {
return instance;
}