UNPKG

@decaf-ts/db-decorators

Version:

Agnostic database decorators and repository

494 lines 25.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Repository = void 0; require("./../overrides/index.cjs"); const decorator_validation_1 = require("@decaf-ts/decorator-validation"); const utils_1 = require("./utils.cjs"); const constants_1 = require("./../operations/constants.cjs"); const errors_1 = require("./errors.cjs"); const wrappers_1 = require("./wrappers.cjs"); const Context_1 = require("./Context.cjs"); /** * @description Base repository implementation providing CRUD operations for models. * @summary The BaseRepository class serves as a foundation for repository implementations, providing * abstract and concrete methods for creating, reading, updating, and deleting model instances. * It handles operation lifecycles including prefix and suffix operations, and enforces decorators. * @template M - The model type extending Model * @template F - The repository flags type, defaults to RepositoryFlags * @template C - The context type, defaults to Context<F> * @param {Constructor<M>} clazz - The constructor for the model class * @class Repository * @example * class UserModel extends Model { * @id() * id: string; * * @required() * name: string; * } * * class UserRepository extends BaseRepository<UserModel> { * constructor() { * super(UserModel); * } * * async create(model: UserModel): Promise<UserModel> { * // Implementation * return model; * } * * async read(key: string): Promise<UserModel> { * // Implementation * return new UserModel({ id: key, name: 'User' }); * } * * async update(model: UserModel): Promise<UserModel> { * // Implementation * return model; * } * * async delete(key: string): Promise<UserModel> { * // Implementation * const model = await this.read(key); * return model; * } * } * * @mermaid * sequenceDiagram * participant C as Client * participant R as Repository * participant P as Prefix Methods * participant D as Database * participant S as Suffix Methods * participant V as Validators/Decorators * * Note over C,V: Create Operation * C->>R: create(model) * R->>P: createPrefix(model) * P->>V: enforceDBDecorators(ON) * P->>D: Database operation * D->>S: createSuffix(model) * S->>V: enforceDBDecorators(AFTER) * S->>C: Return model * * Note over C,V: Read Operation * C->>R: read(key) * R->>P: readPrefix(key) * P->>V: enforceDBDecorators(ON) * P->>D: Database operation * D->>S: readSuffix(model) * S->>V: enforceDBDecorators(AFTER) * S->>C: Return model * * Note over C,V: Update Operation * C->>R: update(model) * R->>P: updatePrefix(model) * P->>V: enforceDBDecorators(ON) * P->>D: Database operation * D->>S: updateSuffix(model) * S->>V: enforceDBDecorators(AFTER) * S->>C: Return model * * Note over C,V: Delete Operation * C->>R: delete(key) * R->>P: deletePrefix(key) * P->>V: enforceDBDecorators(ON) * P->>D: Database operation * D->>S: deleteSuffix(model) * S->>V: enforceDBDecorators(AFTER) * S->>C: Return model */ class Repository { /** * @description Gets the model class constructor. * @summary Retrieves the constructor for the model class associated with this repository. * Throws an error if no class definition is found. * @return {Constructor<M>} The constructor for the model class */ get class() { if (!this._class) throw new errors_1.InternalError(`No class definition found for this repository`); return this._class; } /** * @description Gets the primary key property name of the model. * @summary Retrieves the name of the property that serves as the primary key for the model. * If not already determined, it finds the primary key using the model's decorators. * @return The name of the primary key property */ get pk() { return decorator_validation_1.Model.pk(this.class); } /** * @description Gets the primary key properties. * @summary Retrieves the properties associated with the primary key of the model. * If not already determined, it triggers the pk getter to find the primary key properties. * @return {any} The properties of the primary key */ get pkProps() { return decorator_validation_1.Model.pkProps(this.class); } constructor(clazz) { if (clazz) this._class = clazz; // eslint-disable-next-line @typescript-eslint/no-this-alias const self = this; [this.create, this.read, this.delete].forEach((m) => { const name = m.name; (0, wrappers_1.wrapMethodWithContext)(self, self[name + "Prefix"], m, self[name + "Suffix"]); }); (0, wrappers_1.wrapMethodWithContextForUpdate)(self, self[this.update.name + "Prefix"], this.update, self[this.update.name + "Suffix"]); } /** * @description Creates multiple model instances in the repository. * @summary Persists multiple model instances to the underlying data store by calling * the create method for each model in the array. * @param {M[]} models - The array of model instances to create * @param {any[]} args - Additional arguments for the create operation * @return {Promise<M[]>} A promise that resolves to an array of created model instances */ async createAll(models, ...args) { return Promise.all(models.map((m) => this.create(m, ...args))); } /** * @description Prepares a model for creation and executes pre-creation operations. * @summary Processes a model before it is created in the data store. This includes * creating a context, instantiating a new model instance, and enforcing any decorators * that should be applied before creation. * @param {M} model - The model instance to prepare for creation * @param {any[]} args - Additional arguments for the create operation * @return A promise that resolves to an array containing the prepared model and context arguments */ async createPrefix(model, ...args) { const contextArgs = await Context_1.Context.args(constants_1.OperationKeys.CREATE, this.class, args); model = new this.class(model); if (!contextArgs.context.get("ignoreHandlers")) await (0, utils_1.enforceDBDecorators)(this, contextArgs.context, model, constants_1.OperationKeys.CREATE, constants_1.OperationKeys.ON); if (!contextArgs.context.get("ignoreValidation")) { const errors = await Promise.resolve(model.hasErrors()); if (errors) throw new errors_1.ValidationError(errors.toString()); } return [model, ...contextArgs.args]; } /** * @description Processes a model after creation and executes post-creation operations. * @summary Finalizes a model after it has been created in the data store. This includes * enforcing any decorators that should be applied after creation. * @param {M} model - The model instance that was created * @param {C} context - The context for the operation * @return {Promise<M>} A promise that resolves to the processed model instance */ async createSuffix(model, context) { if (!context.get("ignoreHandlers")) await (0, utils_1.enforceDBDecorators)(this, context, model, constants_1.OperationKeys.CREATE, constants_1.OperationKeys.AFTER); return model; } /** * @description Prepares multiple models for creation and executes pre-creation operations. * @summary Processes multiple models before they are created in the data store. This includes * creating a context, instantiating new model instances, and enforcing any decorators * that should be applied before creation for each model. * @param {M[]} models - The array of model instances to prepare for creation * @param {any[]} args - Additional arguments for the create operation * @return A promise that resolves to an array containing the prepared models and context arguments */ async createAllPrefix(models, ...args) { const contextArgs = await Context_1.Context.args(constants_1.OperationKeys.CREATE, this.class, args); const ignoreHandlers = contextArgs.context.get("ignoreHandlers"); const ignoreValidate = contextArgs.context.get("ignoreValidation"); models = await Promise.all(models.map(async (m) => { const model = new this.class(m); if (!ignoreHandlers) await (0, utils_1.enforceDBDecorators)(this, contextArgs.context, model, constants_1.OperationKeys.CREATE, constants_1.OperationKeys.ON); return model; })); if (!ignoreValidate) { const modelsValidation = await Promise.all(models.map((m) => Promise.resolve(m.hasErrors()))); const errors = (0, utils_1.reduceErrorsToPrint)(modelsValidation); if (errors) throw new errors_1.ValidationError(errors); } return [models, ...contextArgs.args]; } /** * @description Processes multiple models after creation and executes post-creation operations. * @summary Finalizes multiple models after they have been created in the data store. This includes * enforcing any decorators that should be applied after creation for each model. * @param {M[]} models - The array of model instances that were created * @param {C} context - The context for the operation * @return {Promise<M[]>} A promise that resolves to the array of processed model instances */ async createAllSuffix(models, context) { if (!context.get("ignoreHandlers")) await Promise.all(models.map((m) => (0, utils_1.enforceDBDecorators)(this, context, m, constants_1.OperationKeys.CREATE, constants_1.OperationKeys.AFTER))); return models; } /** * @description Retrieves multiple model instances from the repository by their primary keys. * @summary Fetches multiple model instances from the underlying data store using their primary keys * by calling the read method for each key in the array. * @param {string[] | number[]} keys - The array of primary keys of the models to retrieve * @param {any[]} args - Additional arguments for the read operation * @return {Promise<M[]>} A promise that resolves to an array of retrieved model instances */ async readAll(keys, ...args) { return await Promise.all(keys.map((id) => this.read(id, ...args))); } /** * @description Processes a model after retrieval and executes post-read operations. * @summary Finalizes a model after it has been retrieved from the data store. This includes * enforcing any decorators that should be applied after reading. * @param {M} model - The model instance that was retrieved * @param {C} context - The context for the operation * @return {Promise<M>} A promise that resolves to the processed model instance */ async readSuffix(model, context) { if (!context.get("ignoreHandlers")) await (0, utils_1.enforceDBDecorators)(this, context, model, constants_1.OperationKeys.READ, constants_1.OperationKeys.AFTER); return model; } /** * @description Prepares for reading a model and executes pre-read operations. * @summary Processes a key before a model is read from the data store. This includes * creating a context, instantiating a new model instance with the key, and enforcing any decorators * that should be applied before reading. * @param {string} key - The primary key of the model to read * @param {any[]} args - Additional arguments for the read operation * @return A promise that resolves to an array containing the key and context arguments */ async readPrefix(key, ...args) { const contextArgs = await Context_1.Context.args(constants_1.OperationKeys.READ, this.class, args); const model = new this.class(); model[this.pk] = key; await (0, utils_1.enforceDBDecorators)(this, contextArgs.context, model, constants_1.OperationKeys.READ, constants_1.OperationKeys.ON); return [key, ...contextArgs.args]; } /** * @description Prepares for reading multiple models and executes pre-read operations. * @summary Processes multiple keys before models are read from the data store. This includes * creating a context, instantiating new model instances with the keys, and enforcing any decorators * that should be applied before reading for each key. * @param {string[] | number[]} keys - The array of primary keys of the models to read * @param {any[]} args - Additional arguments for the read operation * @return A promise that resolves to an array containing the keys and context arguments */ async readAllPrefix(keys, ...args) { const contextArgs = await Context_1.Context.args(constants_1.OperationKeys.READ, this.class, args); await Promise.all(keys.map(async (k) => { const m = new this.class(); m[this.pk] = k; return (0, utils_1.enforceDBDecorators)(this, contextArgs.context, m, constants_1.OperationKeys.READ, constants_1.OperationKeys.ON); })); return [keys, ...contextArgs.args]; } /** * @description Processes multiple models after retrieval and executes post-read operations. * @summary Finalizes multiple models after they have been retrieved from the data store. This includes * enforcing any decorators that should be applied after reading for each model. * @param {M[]} models - The array of model instances that were retrieved * @param {C} context - The context for the operation * @return {Promise<M[]>} A promise that resolves to the array of processed model instances */ async readAllSuffix(models, context) { if (!context.get("ignoreHandlers")) await Promise.all(models.map((m) => (0, utils_1.enforceDBDecorators)(this, context, m, constants_1.OperationKeys.READ, constants_1.OperationKeys.AFTER))); return models; } /** * @description Updates multiple model instances in the repository. * @summary Updates multiple model instances in the underlying data store by calling * the update method for each model in the array. * @param {M[]} models - The array of model instances to update * @param {any[]} args - Additional arguments for the update operation * @return {Promise<M[]>} A promise that resolves to an array of updated model instances */ async updateAll(models, ...args) { return Promise.all(models.map((m) => this.update(m, ...args))); } /** * @description Processes a model after update and executes post-update operations. * @summary Finalizes a model after it has been updated in the data store. This includes * enforcing any decorators that should be applied after updating. * @param {M} model - The model instance that was updated * @param {C} context - The context for the operation * @return {Promise<M>} A promise that resolves to the processed model instance */ async updateSuffix(model, oldModel, context) { if (!context.get("ignoreHandlers")) await (0, utils_1.enforceDBDecorators)(this, context, model, constants_1.OperationKeys.UPDATE, constants_1.OperationKeys.AFTER, oldModel); return model; } /** * @description Prepares a model for update and executes pre-update operations. * @summary Processes a model before it is updated in the data store. This includes * creating a context, validating the primary key, retrieving the existing model, * and enforcing any decorators that should be applied before updating. * @param {M} model - The model instance to prepare for update * @param {any[]} args - Additional arguments for the update operation * @return A promise that resolves to an array containing the prepared model and context arguments */ async updatePrefix(model, ...args) { const contextArgs = await Context_1.Context.args(constants_1.OperationKeys.UPDATE, this.class, args); const context = contextArgs.context; const ignoreHandlers = contextArgs.context.get("ignoreHandlers"); const ignoreValidate = contextArgs.context.get("ignoreValidation"); const pk = model[this.pk]; if (!pk) throw new errors_1.InternalError(`No value for the Id is defined under the property ${this.pk}`); let oldModel; if (context.get("applyUpdateValidation")) { oldModel = await this.read(pk); if (context.get("mergeForUpdate")) model = decorator_validation_1.Model.merge(oldModel, model, this.class); } if (!ignoreHandlers) await (0, utils_1.enforceDBDecorators)(this, contextArgs.context, model, constants_1.OperationKeys.UPDATE, constants_1.OperationKeys.ON, oldModel); if (!ignoreValidate) { const errors = await Promise.resolve(model.hasErrors(oldModel)); if (errors) throw new errors_1.ValidationError(errors.toString()); } return [model, ...contextArgs.args, oldModel]; } /** * @description Prepares multiple models for update and executes pre-update operations. * @summary Processes multiple models before they are updated in the data store. This includes * creating a context, instantiating new model instances, and enforcing any decorators * that should be applied before updating for each model. * @param {M[]} models - The array of model instances to prepare for update * @param {any[]} args - Additional arguments for the update operation * @return A promise that resolves to an array containing the prepared models and context arguments */ async updateAllPrefix(models, ...args) { const contextArgs = await Context_1.Context.args(constants_1.OperationKeys.UPDATE, this.class, args); const context = contextArgs.context; const ignoreHandlers = context.get("ignoreHandlers"); const ignoreValidate = context.get("ignoreValidation"); const ids = models.map((m) => { const id = m[this.pk]; if (typeof id === "undefined") throw new errors_1.InternalError(`No value for the Id is defined under the property ${this.pk}`); return id; }); let oldModels; if (context.get("applyUpdateValidation")) { oldModels = await this.readAll(ids, context); if (context.get("mergeForUpdate")) models = models.map((m, i) => decorator_validation_1.Model.merge(oldModels[i], m, this.class)); } if (!ignoreHandlers) await Promise.all(models.map((m, i) => (0, utils_1.enforceDBDecorators)(this, contextArgs.context, m, constants_1.OperationKeys.UPDATE, constants_1.OperationKeys.ON, oldModels ? oldModels[i] : undefined))); if (!ignoreValidate) { let modelsValidation; if (!context.get("applyUpdateValidation")) { modelsValidation = await Promise.resolve(models.map((m) => m.hasErrors())); } else { modelsValidation = await Promise.all(models.map((m, i) => Promise.resolve(m.hasErrors(oldModels[i])))); } const errors = (0, utils_1.reduceErrorsToPrint)(modelsValidation); if (errors) throw new errors_1.ValidationError(errors); } return [models, ...contextArgs.args, oldModels]; } /** * @description Processes multiple models after update and executes post-update operations. * @summary Finalizes multiple models after they have been updated in the data store. This includes * enforcing any decorators that should be applied after updating for each model. * @param {M[]} models - The array of model instances that were updated * @param {C} context - The context for the operation * @return {Promise<M[]>} A promise that resolves to the array of processed model instances */ async updateAllSuffix(models, oldModels, context) { if (context.get("applyUpdateValidation") && !context.get("ignoreDevSafeGuards")) { if (!oldModels) throw new errors_1.InternalError("No previous versions of models provided"); } if (!context.get("ignoreHandlers")) await Promise.all(models.map((m, i) => (0, utils_1.enforceDBDecorators)(this, context, m, constants_1.OperationKeys.UPDATE, constants_1.OperationKeys.AFTER, oldModels ? oldModels[i] : undefined))); return models; } /** * @description Deletes multiple model instances from the repository by their primary keys. * @summary Removes multiple model instances from the underlying data store using their primary keys * by calling the delete method for each key in the array. * @param {string[] | number[]} keys - The array of primary keys of the models to delete * @param {any[]} args - Additional arguments for the delete operation * @return {Promise<M[]>} A promise that resolves to an array of deleted model instances */ async deleteAll(keys, ...args) { return Promise.all(keys.map((k) => this.delete(k, ...args))); } /** * @description Processes a model after deletion and executes post-delete operations. * @summary Finalizes a model after it has been deleted from the data store. This includes * enforcing any decorators that should be applied after deletion. * @param {M} model - The model instance that was deleted * @param {C} context - The context for the operation * @return {Promise<M>} A promise that resolves to the processed model instance */ async deleteSuffix(model, context) { if (!context.get("ignoreHandlers")) await (0, utils_1.enforceDBDecorators)(this, context, model, constants_1.OperationKeys.DELETE, constants_1.OperationKeys.AFTER); return model; } /** * @description Prepares for deleting a model and executes pre-delete operations. * @summary Processes a key before a model is deleted from the data store. This includes * creating a context, retrieving the model to be deleted, and enforcing any decorators * that should be applied before deletion. * @param {any} key - The primary key of the model to delete * @param {any[]} args - Additional arguments for the delete operation * @return A promise that resolves to an array containing the key and context arguments */ async deletePrefix(key, ...args) { const contextArgs = await Context_1.Context.args(constants_1.OperationKeys.DELETE, this.class, args); const model = await this.read(key, ...contextArgs.args); await (0, utils_1.enforceDBDecorators)(this, contextArgs.context, model, constants_1.OperationKeys.DELETE, constants_1.OperationKeys.ON); return [key, ...contextArgs.args]; } /** * @description Prepares for deleting multiple models and executes pre-delete operations. * @summary Processes multiple keys before models are deleted from the data store. This includes * creating a context, retrieving the models to be deleted, and enforcing any decorators * that should be applied before deletion for each model. * @param {string[] | number[]} keys - The array of primary keys of the models to delete * @param {any[]} args - Additional arguments for the delete operation * @return A promise that resolves to an array containing the keys and context arguments */ async deleteAllPrefix(keys, ...args) { const contextArgs = await Context_1.Context.args(constants_1.OperationKeys.DELETE, this.class, args); const models = await this.readAll(keys, ...contextArgs.args); await Promise.all(models.map(async (m) => { return (0, utils_1.enforceDBDecorators)(this, contextArgs.context, m, constants_1.OperationKeys.DELETE, constants_1.OperationKeys.ON); })); return [keys, ...contextArgs.args]; } /** * @description Processes multiple models after deletion and executes post-delete operations. * @summary Finalizes multiple models after they have been deleted from the data store. This includes * enforcing any decorators that should be applied after deletion for each model. * @param {M[]} models - The array of model instances that were deleted * @param {C} context - The context for the operation * @return {Promise<M[]>} A promise that resolves to the array of processed model instances */ async deleteAllSuffix(models, context) { if (!context.get("ignoreHandlers")) await Promise.all(models.map((m) => (0, utils_1.enforceDBDecorators)(this, context, m, constants_1.OperationKeys.DELETE, constants_1.OperationKeys.AFTER))); return models; } /** * @description Returns a string representation of the repository. * @summary Creates a string that identifies this repository by the name of its model class. * @return {string} A string representation of the repository */ toString() { return `${this.class.name} Repository`; } } exports.Repository = Repository; //# sourceMappingURL=Repository.js.map