@sqb/connect
Version:
Multi-dialect database connection framework written with TypeScript
287 lines (286 loc) • 11 kB
JavaScript
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,
});
}
}