UNPKG

@sqb/connect

Version:

Multi-dialect database connection framework written with TypeScript

287 lines (286 loc) 11 kB
import { AsyncEventEmitter, TypedEventEmitterClass } from 'strict-typed-events'; import { SqbConnection } from '../client/sqb-connection.js'; import { CountCommand } from './commands/count.command.js'; import { CreateCommand } from './commands/create.command.js'; import { DeleteCommand } from './commands/delete.command.js'; import { FindCommand } from './commands/find.command.js'; import { UpdateCommand } from './commands/update.command.js'; import { EntityMetadata } from './model/entity-metadata.js'; import { extractKeyValues } from './util/extract-keyvalues.js'; /** * @class Repository * @template T - The data type class type of the record */ export class Repository extends TypedEventEmitterClass(AsyncEventEmitter) { constructor(entityDef, executor, schema) { super(); this._executor = executor; this._entity = entityDef; this._schema = schema; } get entity() { return this._entity; } get type() { return this._entity.ctor; } create(input, options) { return this._execute(async (connection) => { const keyValue = await this._create(input, { ...options, connection, returning: true, }); const result = keyValue && (await this._find(keyValue, { ...options, connection })); if (!result) throw new Error('Unable to insert new row'); return result; }, options); } /** * Creates a new resource but returns nothing * * @param {PartialDTO<T>} input - The input data * @param {Repository.CreateOptions} [options] - The options object * @returns {Promise<any>} A promise that resolves key value(s) of the created record * @throws {Error} if an unknown error occurs while creating the resource */ createOnly(input, options) { return this._execute(async (connection) => { const r = await this._create(input, { ...options, connection, returning: true, }); const primaryIndex = EntityMetadata.getPrimaryIndex(this.entity); if (!primaryIndex || primaryIndex.columns.length > 1) return r; return r[primaryIndex.columns[0]]; }, options); } /** * Returns the count of records based on the provided options * * @param {Repository.CountOptions} options - The options for the count operation. * @return {Promise<number>} - A promise that resolves to the count of records */ count(options) { return this._execute(async (connection) => this._count({ ...options, connection }), options); } /** * Deletes a record from the collection. * * @param {any} keyValue - The ID of the resource to delete. * @param {Repository.DeleteOptions} [options] - Optional delete options. * @return {Promise<boolean>} - A Promise that resolves true or false. True when resource deleted. */ delete(keyValue, options) { return this._execute(async (connection) => this._delete(keyValue, { ...options, connection }), options); } /** * Deletes multiple documents from the collection that meet the specified filter criteria. * * @param {Repository.DeleteManyOptions} options - The options for the delete operation. * @return {Promise<number>} - A promise that resolves to the number of resources deleted. */ deleteMany(options) { return this._execute(async (connection) => this._deleteMany({ ...options, connection }), options); } /** * Checks if a record with the given id exists. * * @param {any} keyValue - The id of the object to check. * @param {Repository.ExistsOptions} [options] - The options for the query (optional). * @return {Promise<boolean>} - A Promise that resolves to a boolean indicating whether the record exists or not. */ exists(keyValue, options) { return this._execute(async (connection) => this._exists(keyValue, { ...options, connection }), options); } /** * Checks if a record with the given arguments exists. * * @param {Repository.ExistsOptions} [options] - The options for the query (optional). * @return {Promise<boolean>} - A Promise that resolves to a boolean indicating whether the record exists or not. */ existsOne(options) { return this._execute(async (connection) => this._existsOne({ ...options, connection }), options); } findById(keyValue, options) { return this._execute(async (connection) => this._find(keyValue, { ...options, connection }), options); } findOne(options) { return this._execute(async (connection) => await this._findOne({ ...options, connection, }), options); } findMany(options) { return this._execute(async (connection) => this._findMany({ ...options, connection }), options); } update(keyValue, input, options) { return this._execute(async (connection) => { const opts = { ...options, connection }; const keyValues = await this._update(keyValue, input, opts); if (keyValues) return this._find(keyValues, opts); }, options); } /** * Updates a record in the collection with the specified ID and returns updated record count * * @param {any} keyValue - The ID of the document to update. * @param {PatchDTO<T>} input - The partial input data to update the document with. * @param {Repository.UpdateOptions} options - The options for updating the document. * @returns {Promise<number>} - A Promise that resolves true or false. True when resource updated. */ updateOnly(keyValue, input, options) { return this._execute(async (connection) => !!(await this._update(keyValue, input, { ...options, connection })), options); } /** * Updates multiple records in the collection based on the specified input and options. * * @param {PatchDTO<T>} input - The partial input to update the documents with. * @param {Repository.UpdateManyOptions} options - The options for updating the documents. * @return {Promise<number>} - A promise that resolves to the number of documents matched and modified. */ updateMany(input, options) { return this._execute(async (connection) => this._updateMany(input, { ...options, connection })); } async _execute(fn, opts) { let connection = opts?.connection; if (!connection && this._executor instanceof SqbConnection) connection = this._executor; if (connection) { if (this._schema) await connection.setSchema(this._schema); return fn(connection); } return this._executor.acquire(async (conn) => { if (this._schema) await conn.setSchema(this._schema); await this.emitAsyncSerial('acquire', conn); return fn(conn); }); } async _create(values, options) { if (!values) throw new TypeError('You must provide values'); const keyValues = await CreateCommand.execute({ ...options, entity: this._entity, values, }); if (options.returning && !keyValues) throw new Error('Unable to insert new row'); return keyValues; } async _exists(keyValue, options) { const filter = [extractKeyValues(this._entity, keyValue, true)]; if (options && options.filter) { if (Array.isArray(options.filter)) filter.push(...options.filter); else filter.push(options.filter); } return this._existsOne({ ...options, filter, }); } async _existsOne(options) { const filter = []; if (options && options.filter) { if (Array.isArray(options.filter)) filter.push(...options.filter); else filter.push(options.filter); } const resp = await FindCommand.execute({ ...options, filter, entity: this._entity, }); return resp.length > 0; } async _count(options) { return CountCommand.execute({ ...options, entity: this._entity, }); } async _findMany(options) { return await FindCommand.execute({ ...options, entity: this._entity, }); } async _findOne(options) { const rows = await this._findMany({ ...options, limit: 1, }); return rows && rows[0]; } async _find(keyValue, options) { const filter = [extractKeyValues(this._entity, keyValue, true)]; if (options && options.filter) { if (Array.isArray(options.filter)) filter.push(...options.filter); else filter.push(options.filter); } return await this._findOne({ ...options, filter, offset: 0 }); } async _delete(keyValue, options) { const filter = [extractKeyValues(this._entity, keyValue, true)]; if (options && options.filter) { if (Array.isArray(options.filter)) filter.push(...options.filter); else filter.push(options.filter); } return !!(await DeleteCommand.execute({ ...options, filter, entity: this._entity, })); } async _deleteMany(options) { return DeleteCommand.execute({ ...options, entity: this._entity, filter: options?.filter, params: options?.params, }); } async _update(keyValue, values, options) { if (!values) throw new TypeError('You must provide values'); const keyValues = extractKeyValues(this._entity, keyValue, true); const filter = [keyValues]; if (options.filter) { if (Array.isArray(options.filter)) filter.push(...options.filter); else filter.push(options.filter); } const updateValues = { ...values }; Object.keys(keyValues).forEach(k => delete updateValues[k]); const rowsAffected = await UpdateCommand.execute({ ...options, entity: this._entity, values: updateValues, filter, }); return rowsAffected ? keyValues : undefined; } async _updateMany(values, options) { if (!values) throw new TypeError('You must provide values'); return await UpdateCommand.execute({ ...options, entity: this._entity, values, }); } }