@iarayan/ch-orm
Version:
A Developer-First ClickHouse ORM with Powerful CLI Tools
214 lines • 8.43 kB
JavaScript
import { Connection } from "./Connection";
/**
* Connection pool for managing multiple ClickHouse connections
* Provides automatic connection management, retry logic, and error handling
*/
export class ConnectionPool {
/**
* Create a new connection pool
* @param connectionOptions - Options for ClickHouse connections
* @param poolOptions - Options for the connection pool
*/
constructor(connectionOptions, poolOptions = {}) {
var _a, _b, _c, _d, _e;
/**
* Pool of available connections
*/
this.availableConnections = [];
/**
* Currently borrowed connections
*/
this.borrowedConnections = new Set();
/**
* Queue of pending requests for connections
*/
this.waitingClients = [];
this.connectionOptions = connectionOptions;
this.minConnections = (_a = poolOptions.minConnections) !== null && _a !== void 0 ? _a : 1;
this.maxConnections = (_b = poolOptions.maxConnections) !== null && _b !== void 0 ? _b : 10;
this.idleTimeoutMillis = (_c = poolOptions.idleTimeoutMillis) !== null && _c !== void 0 ? _c : 60000;
this.acquireTimeoutMillis = (_d = poolOptions.acquireTimeoutMillis) !== null && _d !== void 0 ? _d : 30000;
this.validateOnBorrow = (_e = poolOptions.validateOnBorrow) !== null && _e !== void 0 ? _e : true;
// Initialize the pool with minimum connections
this.initialize();
}
/**
* Initialize the pool with minimum connections
*/
async initialize() {
try {
for (let i = 0; i < this.minConnections; i++) {
const connection = new Connection(this.connectionOptions);
// Test the connection to ensure it's valid
await connection.ping();
this.availableConnections.push({
connection,
lastUsed: Date.now(),
});
}
}
catch (error) {
console.error("Failed to initialize connection pool:", error);
}
}
/**
* Get a connection from the pool
* @returns Promise that resolves to a connection
*/
async getConnection() {
// If there are available connections, use one
if (this.availableConnections.length > 0) {
const { connection } = this.availableConnections.shift();
// Validate the connection if needed
if (this.validateOnBorrow) {
try {
const isValid = await connection.ping();
if (!isValid) {
// Connection is invalid, create a new one
return this.createConnection();
}
}
catch (error) {
// Error during validation, create a new connection
return this.createConnection();
}
}
// Add the connection to borrowed set
this.borrowedConnections.add(connection);
return connection;
}
// If we can create more connections, do so
if (this.borrowedConnections.size < this.maxConnections) {
return this.createConnection();
}
// Otherwise, wait for a connection to become available
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => {
// Remove the client from the waiting queue
this.waitingClients = this.waitingClients.filter((client) => client.timeoutId !== timeoutId);
reject(new Error("Timeout waiting for available connection"));
}, this.acquireTimeoutMillis);
this.waitingClients.push({ resolve, reject, timeoutId });
});
}
/**
* Release a connection back to the pool
* @param connection - Connection to release
*/
releaseConnection(connection) {
// If the connection is borrowed, release it
if (this.borrowedConnections.has(connection)) {
this.borrowedConnections.delete(connection);
// If there are waiting clients, give them the connection
if (this.waitingClients.length > 0) {
const { resolve, timeoutId } = this.waitingClients.shift();
clearTimeout(timeoutId);
this.borrowedConnections.add(connection);
resolve(connection);
return;
}
// Otherwise, add the connection back to the pool
this.availableConnections.push({
connection,
lastUsed: Date.now(),
});
// Check for idle connections whenever a connection is released
this.removeIdleConnections();
}
}
/**
* Create a new connection
* @returns Promise that resolves to a new connection
*/
async createConnection() {
const connection = new Connection(this.connectionOptions);
try {
// Test the connection to ensure it's valid
await connection.ping();
// Add the connection to borrowed set
this.borrowedConnections.add(connection);
return connection;
}
catch (error) {
throw new Error(`Failed to create connection: ${error}`);
}
}
/**
* Remove idle connections from the pool
*/
removeIdleConnections() {
const now = Date.now();
const minIdleCount = this.minConnections - this.borrowedConnections.size;
// Keep at least minConnections total connections
if (minIdleCount > 0) {
// Sort by last used (oldest first)
this.availableConnections.sort((a, b) => a.lastUsed - b.lastUsed);
// Get connections to remove (oldest idle connections beyond minimum)
const connectionsToKeep = this.availableConnections.slice(0, minIdleCount);
const connectionsToCheck = this.availableConnections.slice(minIdleCount);
// Check which connections are idle
const idleConnections = connectionsToCheck.filter(({ lastUsed }) => now - lastUsed > this.idleTimeoutMillis);
// Update the available connections
this.availableConnections = [
...connectionsToKeep,
...connectionsToCheck.filter(({ lastUsed }) => now - lastUsed <= this.idleTimeoutMillis),
];
// Close the idle connections
for (const { connection } of idleConnections) {
try {
// Close the connection properly
connection.close();
}
catch (error) {
console.error("Error closing idle connection:", error);
}
}
}
}
/**
* Execute a function with a connection from the pool
* @param fn - Function to execute with the connection
* @returns Promise that resolves to the function's result
*/
async withConnection(fn) {
const connection = await this.getConnection();
try {
const result = await fn(connection);
this.releaseConnection(connection);
return result;
}
catch (error) {
this.releaseConnection(connection);
throw error;
}
}
/**
* Close all connections in the pool
*/
async close() {
// Clear waiting clients
for (const { reject, timeoutId } of this.waitingClients) {
clearTimeout(timeoutId);
reject(new Error("Connection pool is closing"));
}
this.waitingClients = [];
// Close all connections
const allConnections = [
...this.availableConnections.map(({ connection }) => connection),
...this.borrowedConnections,
];
this.availableConnections = [];
this.borrowedConnections.clear();
// Close each connection
for (const connection of allConnections) {
try {
// Close the connection properly
await connection.close();
}
catch (error) {
console.error("Error closing connection:", error);
}
}
}
}
//# sourceMappingURL=ConnectionPool.js.map