@sqb/connect
Version:
Multi-dialect database connection framework written with TypeScript
168 lines (167 loc) • 6.03 kB
JavaScript
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) {
constructor(config) {
super();
this._entities = {};
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 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();
}
}