UNPKG

@sqb/connect

Version:

Multi-dialect database connection framework written with TypeScript

171 lines (170 loc) 6.06 kB
import _debug from 'debug'; import { createPool, PoolState, } from 'lightning-pool'; import { coerceToBoolean, coerceToInt } from 'putil-varhelpers'; import { AsyncEventEmitter, TypedEventEmitterClass } from 'strict-typed-events'; import { EntityMetadata } from '../orm/model/entity-metadata.js'; import { Repository } from '../orm/repository.class.js'; import { AdapterRegistry } from './extensions.js'; import { SqbConnection } from './sqb-connection.js'; const debug = _debug('sqb:client'); const inspect = Symbol.for('nodejs.util.inspect.custom'); export class SqbClient extends TypedEventEmitterClass(AsyncEventEmitter) { _adapter; _pool; _defaults; _entities = {}; constructor(config) { super(); if (!(config && typeof config === 'object')) throw new TypeError('Configuration object required'); let adapter; if (config.driver) { adapter = AdapterRegistry.findDriver(config.driver); if (!adapter) throw new Error(`No database adapter registered for "${config.driver}" driver`); } else if (config.dialect) { adapter = AdapterRegistry.findDialect(config.dialect); if (!adapter) throw new Error(`No database adapter registered for "${config.dialect}" dialect`); } if (!adapter) throw new Error(`You must provide one of "driver" or "dialect" properties`); this._adapter = adapter; this._defaults = config.defaults || {}; const poolOptions = {}; const popts = config.pool || {}; poolOptions.acquireMaxRetries = coerceToInt(popts.acquireMaxRetries, 0); poolOptions.acquireRetryWait = coerceToInt(popts.acquireRetryWait, 2000); poolOptions.acquireTimeoutMillis = coerceToInt(popts.acquireTimeoutMillis, 0); poolOptions.idleTimeoutMillis = coerceToInt(popts.idleTimeoutMillis, 30000); poolOptions.max = coerceToInt(popts.max, 10); poolOptions.maxQueue = coerceToInt(popts.maxQueue, 1000); poolOptions.max = coerceToInt(popts.max, 10); poolOptions.min = coerceToInt(popts.min, 0); poolOptions.minIdle = coerceToInt(popts.minIdle, 0); poolOptions.validation = coerceToBoolean(popts.validation, false); const cfg = { ...config }; const poolFactory = { create: () => adapter.connect(cfg), destroy: instance => instance.close(), reset: async (instance) => instance.reset(), validate: instance => instance.test(), }; this._pool = createPool(poolFactory, poolOptions); this._pool.on('closing', () => this.emit('closing')); this._pool.on('close', () => this.emit('close')); this._pool.on('terminate', () => this.emit('terminate')); // @ts-ignore this._pool.on('error', (...args) => this.emit('error', ...args)); } get defaults() { return this._defaults; } /** * Returns dialect */ get dialect() { return this._adapter.dialect; } /** * Returns database driver name */ get driver() { return this._adapter.driver; } /** * Returns true if pool is closed */ get isClosed() { return this._pool.state === PoolState.CLOSED; } get pool() { return this._pool; } async acquire(arg0, arg1) { debug('acquire'); if (typeof arg0 === 'function') { const connection = await this.acquire(arg1); try { return await arg0(connection); } finally { connection.release(); } } const options = arg1; const adapterConnection = await this._pool.acquire(); const opts = { autoCommit: this.defaults.autoCommit, ...options }; const connection = new SqbConnection(this, adapterConnection, opts); await this.emitAsyncSerial('acquire', connection); connection.on('execute', (request) => this.emit('execute', request)); connection.on('error', (error) => this.emit('error', error)); connection.on('close', () => this.emitAsyncSerial('connection-return', connection)); return connection; } /** * Shuts down the pool and destroys all resources. */ async close(terminateWait) { const ms = terminateWait == null ? Infinity : 0; return this._pool.close(ms); } /** * Executes a query or callback with a new acquired connection. */ async execute(query, options) { debug('execute'); const connection = await this.acquire(); try { const qr = await connection.execute(query, options); if (qr && qr.cursor) { connection.retain(); qr.cursor.once('close', () => connection.release()); } return qr; } finally { connection.release(); } } /** * Tests the pool */ async test() { const connection = await this.acquire(); try { await connection.test(); } finally { connection.release(); } } getRepository(entity, opts) { let ctor; if (typeof entity === 'string') { ctor = this.getEntity(entity); if (!ctor) throw new Error(`Repository "${entity}" is not registered`); } else ctor = entity; const entityDef = EntityMetadata.get(ctor); if (!entityDef) throw new Error(`You must provide an @Entity annotated constructor`); return new Repository(entityDef, this, opts?.schema); } getEntity(name) { return this._entities[name]; } toString() { return ('[object ' + Object.getPrototypeOf(this).constructor.name + '(' + this.dialect + ')]'); } [inspect]() { return this.toString(); } }