UNPKG

@iarayan/ch-orm

Version:

A Developer-First ClickHouse ORM with Powerful CLI Tools

214 lines 8.43 kB
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