@decaf-ts/db-decorators
Version:
Agnostic database decorators and repository
494 lines • 25.6 kB
JavaScript
"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