UNPKG

@adonisjs/lucid

Version:

SQL ORM built on top of Active Record pattern

210 lines (209 loc) 7.1 kB
/* * @adonisjs/lucid * * (c) Harminder Virk <virk@adonisjs.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ import { Connection } from './index.js'; import * as errors from '../errors.js'; /** * Connection manager job is to manage multiple named connections. You can add any number * or connections by registering their config only once and then make use of `connect` * and `close` methods to create and destroy db connections. */ export class ConnectionManager { logger; emitter; /** * List of managed connections */ connections = new Map(); /** * Connections for which the config was patched. They must get removed * overtime, unless application is behaving unstable. */ orphanConnections = new Set(); constructor(logger, emitter) { this.logger = logger; this.emitter = emitter; } /** * Handles disconnection of a connection */ handleDisconnect(connection) { /** * We received the close event on the orphan connection and not the connection * that is in use */ if (this.orphanConnections.has(connection)) { this.orphanConnections.delete(connection); this.emitter.emit('db:connection:disconnect', connection); this.logger.trace({ connection: connection.name }, 'disconnecting connection inside manager'); return; } const internalConnection = this.get(connection.name); /** * This will be false, when connection was released at the * time of closing */ if (!internalConnection) { return; } this.emitter.emit('db:connection:disconnect', connection); this.logger.trace({ connection: connection.name }, 'disconnecting connection inside manager'); delete internalConnection.connection; internalConnection.state = 'closed'; } /** * Handles event when a new connection is added */ handleConnect(connection) { const internalConnection = this.get(connection.name); if (!internalConnection) { return; } this.emitter.emit('db:connection:connect', connection); internalConnection.state = 'open'; } /** * Monitors a given connection by listening for lifecycle events */ monitorConnection(connection) { connection.on('disconnect', ($connection) => this.handleDisconnect($connection)); connection.on('connect', ($connection) => this.handleConnect($connection)); connection.on('error', (error, $connection) => { this.emitter.emit('db:connection:error', [error, $connection]); }); } /** * Add a named connection with it's configuration. Make sure to call `connect` * before using the connection to make database queries. */ add(connectionName, config) { /** * Noop when connection already exists. If one wants to change the config, they * must release the old connection and add a new one */ if (this.has(connectionName)) { return; } this.logger.trace({ connection: connectionName }, 'adding new connection to the manager'); this.connections.set(connectionName, { name: connectionName, config: config, state: 'registered', }); } /** * Connect to the database using config for a given named connection */ connect(connectionName) { const connection = this.connections.get(connectionName); if (!connection) { throw new errors.E_UNMANAGED_DB_CONNECTION([connectionName]); } /** * Ignore when the there is already a connection. */ if (this.isConnected(connection.name)) { return; } /** * Create a new connection and monitor it's state */ connection.connection = new Connection(connection.name, connection.config, this.logger); this.monitorConnection(connection.connection); connection.connection.connect(); } /** * Patching the config */ patch(connectionName, config) { const connection = this.get(connectionName); /** * If connection is missing, then simply add it */ if (!connection) { return this.add(connectionName, config); } /** * Move the current connection to the orphan connections. We need * to keep a separate track of old connections to make sure * they cleanup after some time */ if (connection.connection) { this.orphanConnections.add(connection.connection); connection.connection.disconnect(); } /** * Updating config and state. Next call to connect will use the * new config */ connection.state = 'migrating'; connection.config = config; /** * Removing the connection right away, so that the next call to `connect` * creates a new one with new config */ delete connection.connection; } /** * Returns the connection node for a given named connection */ get(connectionName) { return this.connections.get(connectionName); } /** * Returns a boolean telling if we have connection details for * a given named connection. This method doesn't tell if * connection is connected or not. */ has(connectionName) { return this.connections.has(connectionName); } /** * Returns a boolean telling if connection has been established * with the database or not */ isConnected(connectionName) { if (!this.has(connectionName)) { return false; } const connection = this.get(connectionName); return !!connection.connection && connection.state === 'open'; } /** * Closes a given connection and can optionally release it from the * tracking list */ async close(connectionName, release = false) { if (this.isConnected(connectionName)) { const connection = this.get(connectionName); await connection.connection.disconnect(); connection.state = 'closing'; } if (release) { await this.release(connectionName); } } /** * Close all tracked connections */ async closeAll(release = false) { await Promise.all(Array.from(this.connections.keys()).map((name) => this.close(name, release))); } /** * Release a connection. This will disconnect the connection * and will delete it from internal list */ async release(connectionName) { if (this.isConnected(connectionName)) { await this.close(connectionName, true); } else { this.connections.delete(connectionName); } } }