@sqb/connect
Version:
Multi-dialect database connection framework written with TypeScript
281 lines (280 loc) • 12.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.SqbConnection = void 0;
const tslib_1 = require("tslib");
const builder_1 = require("@sqb/builder");
const assert_1 = tslib_1.__importDefault(require("assert"));
const debug_1 = tslib_1.__importDefault(require("debug"));
const power_tasks_1 = require("power-tasks");
const putil_varhelpers_1 = require("putil-varhelpers");
const strict_typed_events_1 = require("strict-typed-events");
const entity_metadata_js_1 = require("../orm/model/entity-metadata.js");
const repository_class_js_1 = require("../orm/repository.class.js");
const cursor_js_1 = require("./cursor.js");
const helpers_js_1 = require("./helpers.js");
const debug = (0, debug_1.default)('sqb:connection');
class SqbConnection extends (0, strict_typed_events_1.TypedEventEmitterClass)(strict_typed_events_1.AsyncEventEmitter) {
constructor(client, adapterConnection, options) {
super();
this.client = client;
this._tasks = new power_tasks_1.TaskQueue();
this._inTransaction = false;
this._refCount = 1;
this._intlcon = adapterConnection;
this._options = options || {};
}
/**
* Returns session id
*/
get sessionId() {
return this._intlcon && this._intlcon.sessionId;
}
/**
* Returns reference counter value
*/
get refCount() {
return this._refCount;
}
/**
* Returns true if transaction started
*/
get inTransaction() {
if (this._intlcon && typeof this._intlcon.getInTransaction === 'function') {
this._inTransaction = this._intlcon.getInTransaction();
}
return this._inTransaction;
}
/**
* Increases internal reference counter to keep session alive
*/
retain() {
this._refCount++;
debug('[%s] retain | refCount: %s', this.sessionId, this._refCount);
this.emit('retain', this._refCount);
}
/**
* Decreases the internal reference counter.
* When reference count is 0, connection returns to the pool.
* Returns true if connection released.
*/
release() {
if (!this._intlcon)
return true;
const ref = --this._refCount;
this.emit('release', this._refCount);
debug('[%s] release | refCount: %s', this.sessionId, ref);
if (!ref) {
this.close().catch(() => 0);
return true;
}
return false;
}
/**
* Immediately releases the connection.
*/
async close() {
if (!this._intlcon)
return;
await this.emitAsyncSerial('close');
const intlcon = this._intlcon;
this._intlcon = undefined;
this.client.pool.release(intlcon, (e) => {
if (e)
this.client.emit('error', e);
});
debug('[%s] closed', intlcon.sessionId);
}
async execute(query, options) {
if (!this._intlcon)
throw new Error(`Can't execute query, because connection is released`);
return this._tasks.enqueue(() => this._execute(query, options)).toPromise();
}
getRepository(entity, opts) {
let ctor;
if (typeof entity === 'string') {
ctor = this.client.getEntity(entity);
if (!ctor)
throw new Error(`Repository "${entity}" is not registered`);
}
else
ctor = entity;
const entityDef = entity_metadata_js_1.EntityMetadata.get(ctor);
if (!entityDef)
throw new Error(`You must provide an @Entity annotated constructor`);
return new repository_class_js_1.Repository(entityDef, this, opts?.schema);
}
async getSchema() {
assert_1.default.ok(this._intlcon, `Can't set schema, because connection is released`);
assert_1.default.ok(this._intlcon.getSchema, `${this.client.dialect} adapter does have Schema support`);
return await this._intlcon.getSchema();
}
async setSchema(schema) {
assert_1.default.ok(this._intlcon, `Can't set schema, because connection is released`);
assert_1.default.ok(this._intlcon.setSchema, `${this.client.dialect} adapter does have Schema support`);
await this._intlcon.setSchema(schema);
}
/**
* Executes a query
*/
async _execute(query, options) {
assert_1.default.ok(this._intlcon, `Can't execute query, because connection is released`);
const intlcon = this._intlcon;
this.retain();
try {
const startTime = Date.now();
const request = this._prepareQueryRequest(query, options);
debug('[%s] execute | %o', this.sessionId, request);
this.emit('execute', request);
// Call execute hooks
if (request.executeHooks) {
for (const fn of request.executeHooks) {
await fn(this, request);
}
}
const response = await intlcon.execute(request);
if (!response)
throw new Error('Database adapter returned an empty response');
const result = {
executeTime: Date.now() - startTime,
};
if (request.showSql)
result.query = request;
if (response.rows || response.cursor) {
if (!response.fields)
throw new Error('Adapter did not returned fields info');
if (!response.rowType)
throw new Error('Adapter did not returned rowType');
result.fields = (0, helpers_js_1.wrapAdapterFields)(response.fields, request.fieldNaming);
result.rowType = response.rowType;
if (response.rows) {
result.rows = request.objectRows
? (0, helpers_js_1.normalizeRowsToObjectRows)(result.fields, response.rowType, response.rows, request)
: (0, helpers_js_1.normalizeRowsToArrayRows)(result.fields, response.rowType, response.rows, request);
(0, helpers_js_1.callFetchHooks)(result.rows, request);
}
else if (response.cursor) {
const cursor = (result.cursor = new cursor_js_1.Cursor(this, result.fields, response.cursor, request));
const hook = () => cursor.close();
cursor.once('close', () => this.off('close', hook));
this.on('close', hook);
}
}
if (response.rowsAffected)
result.rowsAffected = response.rowsAffected;
return result;
}
finally {
this.release();
}
}
async startTransaction() {
if (!this._intlcon)
throw new Error('Can not call startTransaction() on a released connection');
await this._intlcon.startTransaction();
this._inTransaction = true;
this.emit('start-transaction');
}
async commit() {
if (!this._intlcon)
throw new Error('Can not call commit() on a released connection');
await this._intlcon.commit();
this._inTransaction = false;
this.emit('commit');
}
async rollback() {
if (!this._intlcon)
throw new Error('Can not call rollback() on a released connection');
await this._intlcon.rollback();
this._inTransaction = false;
this.emit('rollback');
}
async setSavepoint(savepoint) {
if (!this._intlcon)
throw new Error('Can not call setSavepoint() on a released connection');
if (typeof this._intlcon.setSavepoint !== 'function') {
throw new Error(this.client.driver + ' does not support setSavepoint method');
}
if (!this.inTransaction)
await this.startTransaction();
await this._intlcon.setSavepoint(savepoint);
this.emit('set-savepoint');
}
async releaseSavepoint(savepoint) {
if (!this._intlcon)
throw new Error('Can not call releaseSavepoint() on a released connection');
if (typeof this._intlcon.releaseSavepoint !== 'function') {
throw new Error(this.client.driver + ' does not support releaseSavepoint method');
}
await this._intlcon.releaseSavepoint(savepoint);
this.emit('release-savepoint');
}
async rollbackSavepoint(savepoint) {
if (!this._intlcon)
throw new Error('Can not call rollbackSavepoint() on a released connection');
if (typeof this._intlcon.rollbackSavepoint !== 'function') {
throw new Error(this.client.driver + ' does not support rollbackSavepoint method');
}
await this._intlcon.rollbackSavepoint(savepoint);
this.emit('rollback-savepoint');
}
async test() {
if (!this._intlcon)
throw new Error('Can not call test() on a released connection');
await this._intlcon.test();
}
_prepareQueryRequest(query, options = {}) {
if (!this._intlcon)
throw new Error('Session released');
const defaults = this.client.defaults;
const request = {
dialect: this.client.dialect,
sql: '',
autoCommit: this.inTransaction
? false
: (0, putil_varhelpers_1.coerceToBoolean)((0, putil_varhelpers_1.coalesce)(options.autoCommit, this._options?.autoCommit, defaults.autoCommit), true),
cursor: (0, putil_varhelpers_1.coerceToBoolean)((0, putil_varhelpers_1.coalesce)(options.cursor, defaults.cursor), false),
objectRows: (0, putil_varhelpers_1.coerceToBoolean)((0, putil_varhelpers_1.coalesce)(options.objectRows, defaults.objectRows), true),
ignoreNulls: (0, putil_varhelpers_1.coerceToBoolean)((0, putil_varhelpers_1.coalesce)(options.ignoreNulls, defaults.ignoreNulls), false),
fetchRows: (0, putil_varhelpers_1.coerceToInt)((0, putil_varhelpers_1.coalesce)(options.fetchRows, defaults.fetchRows), 100),
fieldNaming: (0, putil_varhelpers_1.coalesce)(options.namingStrategy, defaults.fieldNaming),
transform: (0, putil_varhelpers_1.coalesce)(options.transform, defaults.transform),
showSql: (0, putil_varhelpers_1.coerceToBoolean)((0, putil_varhelpers_1.coalesce)(options.showSql, defaults.showSql), false),
prettyPrint: (0, putil_varhelpers_1.coerceToBoolean)((0, putil_varhelpers_1.coalesce)(options.prettyPrint, defaults.prettyPrint), false),
action: (0, putil_varhelpers_1.coerceToString)(options.action),
fetchAsString: options.fetchAsString,
};
request.ignoreNulls = request.ignoreNulls && request.objectRows;
if (query instanceof builder_1.classes.Query) {
if (this._intlcon.onGenerateQuery)
this._intlcon.onGenerateQuery(request, query);
const q = query.generate({
dialect: request.dialect,
dialectVersion: request.dialectVersion,
params: options.params,
strictParams: true,
prettyPrint: request.prettyPrint,
});
request.sql = q.sql;
request.params = q.params;
request.paramOptions = q.paramOptions;
if (q.returningFields)
request.returningFields = q.returningFields;
if (query.listenerCount('execute'))
request.executeHooks = query.listeners('execute');
if (query.listenerCount('fetch'))
request.fetchHooks = query.listeners('fetch');
}
else {
// noinspection SuspiciousTypeOfGuard
if (typeof query === 'string') {
request.sql = query;
request.params = options.params;
}
}
// @ts-ignore
if (!request.sql)
throw new Error('No sql given');
return request;
}
}
exports.SqbConnection = SqbConnection;