sequelae-mcp
Version:
Let Claude, Cursor, and other AI agents run real SQL queries on live Postgres databases. No more copy-pasting SQL, stale schema docs, or hallucinated DB adapters — just raw, real-time access. Now with MCP support!
131 lines • 4.23 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.PoolManager = void 0;
const pg_1 = require("pg");
const logger_1 = require("../utils/logger");
/**
* Manages a singleton connection pool for the application
*/
class PoolManager {
constructor() {
this.pool = null;
this.config = null;
}
static getInstance() {
if (!PoolManager.instance) {
PoolManager.instance = new PoolManager();
}
return PoolManager.instance;
}
/**
* Initialize the pool with configuration
*/
initialize(config) {
// If pool exists with different config, close it
if (this.pool && this.config?.connectionString !== config.connectionString) {
this.close();
}
// If pool already exists with same config, reuse it
if (this.pool && this.config?.connectionString === config.connectionString) {
return;
}
const poolConfig = {
connectionString: config.connectionString,
ssl: config.ssl,
max: config.maxConnections || parseInt(process.env.POSTGRES_MAX_CONNECTIONS || '10'),
idleTimeoutMillis: config.idleTimeoutMillis || parseInt(process.env.POSTGRES_IDLE_TIMEOUT || '10000'),
connectionTimeoutMillis: config.connectionTimeoutMillis ||
parseInt(process.env.POSTGRES_CONNECTION_TIMEOUT || '30000'),
statement_timeout: config.statementTimeout || parseInt(process.env.POSTGRES_STATEMENT_TIMEOUT || '120000'),
};
this.pool = new pg_1.Pool(poolConfig);
this.config = config;
// Handle pool errors
this.pool.on('error', err => {
logger_1.logger.error('Unexpected error on idle client', { error: err });
});
}
/**
* Get the current pool instance
*/
getPool() {
if (!this.pool) {
throw new Error('Pool not initialized. Call initialize() first.');
}
return this.pool;
}
/**
* Get a client with retry logic
*/
async getClient(maxRetries = 3, initialDelay = 1000) {
const pool = this.getPool();
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await pool.connect();
}
catch (error) {
logger_1.logger.warn(`Failed to get client (attempt ${attempt + 1}/${maxRetries})`, { error });
if (attempt === maxRetries - 1) {
throw error;
}
// Simple exponential backoff: 1s, 2s, 4s
const delay = initialDelay * Math.pow(2, attempt);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw new Error('Failed to get client after all retries');
}
/**
* Check if pool is initialized
*/
isInitialized() {
return this.pool !== null;
}
/**
* Get pool statistics
*/
getStats() {
if (!this.pool) {
return { total: 0, idle: 0, waiting: 0 };
}
return {
total: this.pool.totalCount,
idle: this.pool.idleCount,
waiting: this.pool.waitingCount,
};
}
/**
* Get pool status information
*/
getStatus() {
const stats = this.getStats();
return {
initialized: this.isInitialized(),
...stats,
maxConnections: this.config?.maxConnections || 10,
idleTimeout: this.config?.idleTimeoutMillis || 10000,
connectionTimeout: this.config?.connectionTimeoutMillis || 0,
};
}
/**
* Close the pool and cleanup
*/
async close() {
if (this.pool) {
await this.pool.end();
this.pool = null;
this.config = null;
}
}
/**
* Reset the singleton instance (mainly for testing)
*/
static reset() {
if (PoolManager.instance) {
PoolManager.instance.close();
PoolManager.instance = new PoolManager();
}
}
}
exports.PoolManager = PoolManager;
//# sourceMappingURL=pool-manager.js.map