UNPKG

@decaf-ts/db-decorators

Version:

Agnostic database decorators and repository

478 lines (477 loc) 22.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BlockOperations = void 0; exports.getHandlersDecorators = getHandlersDecorators; exports.groupDecorators = groupDecorators; exports.sortDecorators = sortDecorators; exports.onCreateUpdate = onCreateUpdate; exports.onUpdate = onUpdate; exports.onCreate = onCreate; exports.onRead = onRead; exports.onDelete = onDelete; exports.onAny = onAny; exports.on = on; exports.afterCreateUpdate = afterCreateUpdate; exports.afterUpdate = afterUpdate; exports.afterCreate = afterCreate; exports.afterRead = afterRead; exports.afterDelete = afterDelete; exports.afterAny = afterAny; exports.after = after; exports.operation = operation; exports.storeHandlerMetadata = storeHandlerMetadata; const constants_1 = require("./constants.cjs"); const Operations_1 = require("./Operations.cjs"); const reflection_1 = require("@decaf-ts/reflection"); const decorator_validation_1 = require("@decaf-ts/decorator-validation"); const errors_1 = require("./../repository/errors.cjs"); const utils_1 = require("./../repository/utils.cjs"); const decoration_1 = require("@decaf-ts/decoration"); const defaultPriority = 50; const DefaultGroupSort = { priority: defaultPriority }; /** * @description Internal function to register operation handlers * @summary Registers an operation handler for a specific operation key on a target property * @param {OperationKeys} op - The operation key to handle * @param {OperationHandler<any, any, any, any, any>} handler - The handler function to register * @return {PropertyDecorator} A decorator that registers the handler * @function handle * @category Property Decorators */ function handle(op, handler) { return (target, propertyKey) => { Operations_1.Operations.register(handler, op, target, propertyKey); }; } /** * @description Retrieves decorator objects for handling database operations * @summary Retrieves a list of decorator objects representing operation handlers for a given model and decorators * @template M - Type for the model, defaults to Model<true | false> * @template R - Type for the repository, defaults to IRepository<M, F, C> * @template V - Type for metadata, defaults to object * @template F - Type for repository flags, defaults to RepositoryFlags * @template C - Type for context, defaults to Context<F> * @param {Model} model - The model for which to retrieve decorator objects * @param {Record<string, DecoratorMetadata[]>} decorators - The decorators associated with the model properties * @param {string} prefix - The operation prefix (e.g., 'on', 'after') * @return {DecoratorObject[]} An array of decorator objects representing operation handlers * @function getHandlersDecorators * @category Function */ function getHandlersDecorators(model, decorators, prefix) { const accum = []; for (const prop in decorators) { const decs = decorators[prop]; for (const dec of decs) { const { key } = dec; const handlers = Operations_1.Operations.get(model, prop, prefix + key); if (!handlers || !handlers.length) throw new errors_1.InternalError(`Could not find registered handler for the operation ${prefix + key} under property ${prop}`); const handlerArgs = (0, utils_1.getHandlerArgs)(dec, prop, model); if (!handlerArgs || Object.values(handlerArgs).length !== handlers.length) throw new errors_1.InternalError("Args and handlers length do not match"); for (let i = 0; i < handlers.length; i++) { const data = handlerArgs[handlers[i].name] .data; accum.push({ handler: handlers[i], data: [data], prop: [prop], }); } } } return accum; } /** * @description Groups decorators based on their group property * @summary Groups decorator objects by their group property, combining data and properties within each group * @param {DecoratorObject[]} decorators - The array of decorator objects to group * @return {DecoratorObject[]} An array of grouped decorator objects * @function groupDecorators * @category Function */ function groupDecorators(decorators) { const grouped = decorators.reduce((acc, dec) => { if (!dec || !dec.data || !dec.prop) throw new errors_1.InternalError("Missing decorator properties or data"); // If decorator have no group if (!dec.data[0].group) { acc.set(Symbol(), dec); return acc; } const groupKey = dec.data[0].group; if (!acc.has(groupKey)) { // first handler is saved in the group acc.set(groupKey, { ...dec }); } else { const existing = acc.get(groupKey); acc.set(groupKey, { handler: existing.handler, data: [...existing.data, ...dec.data], prop: [...existing.prop, ...dec.prop], }); } return acc; }, new Map()); const groups = Array.from(grouped.values()); // Sort inside each group by priority groups.forEach((group) => { const combined = group.data.map((d, i) => ({ data: d, prop: group.prop[i], })); combined.sort((a, b) => (a.data.groupPriority ?? 50) - (b.data.groupPriority ?? 50)); group.data = combined.map((c) => c.data); group.prop = combined.map((c) => c.prop); }); return groups; } /** * @description Sorts decorator objects based on their priority * @summary Sorts an array of decorator objects by the priority of their first data element * @param {DecoratorObject[]} decorators - The array of decorator objects to sort * @return {DecoratorObject[]} The sorted array of decorator objects * @function sortDecorators * @category Function */ function sortDecorators(decorators) { // Sort by groupPriority decorators.sort((a, b) => { const priorityA = a.data[0].priority ?? defaultPriority; const priorityB = b.data[0].priority ?? defaultPriority; return priorityA - priorityB; // lower number = higher priority }); return decorators; } /** * @description Decorator for handling create and update operations * @summary Defines a behavior to execute during both create and update operations * @template V - Type for metadata, defaults to object * @param {GeneralOperationHandler<any, any, V, any, any> | GeneralUpdateOperationHandler<any, any, V, any, any>} handler - The method called upon the operation * @param {V} [data] - Optional metadata to pass to the handler * @return {PropertyDecorator} A decorator that can be applied to class properties * @function onCreateUpdate * @category Property Decorators */ function onCreateUpdate(handler, data, groupsort) { return on(constants_1.DBOperations.CREATE_UPDATE, handler, data, groupsort); } /** * @description Decorator for handling update operations * @summary Defines a behavior to execute during update operations * @template V - Type for metadata, defaults to object * @param {UpdateOperationHandler<any, any, V, any>} handler - The method called upon the operation * @param {V} [data] - Optional metadata to pass to the handler * @return {PropertyDecorator} A decorator that can be applied to class properties * @function onUpdate * @category Property Decorators */ function onUpdate(handler, data, groupsort) { return on(constants_1.DBOperations.UPDATE, handler, data, groupsort); } /** * @description Decorator for handling create operations * @summary Defines a behavior to execute during create operations * @template V - Type for metadata, defaults to object * @param {GeneralOperationHandler<any, any, V, any, any>} handler - The method called upon the operation * @param {V} [data] - Optional metadata to pass to the handler * @return {PropertyDecorator} A decorator that can be applied to class properties * @function onCreate * @category Property Decorators */ function onCreate(handler, data, groupsort) { return on(constants_1.DBOperations.CREATE, handler, data, groupsort); } /** * @description Decorator for handling read operations * @summary Defines a behavior to execute during read operations * @template V - Type for metadata, defaults to object * @param {IdOperationHandler<any, any, V, any, any>} handler - The method called upon the operation * @param {V} [data] - Optional metadata to pass to the handler * @return {PropertyDecorator} A decorator that can be applied to class properties * @function onRead * @category Property Decorators */ function onRead(handler, data, groupsort) { return on(constants_1.DBOperations.READ, handler, data, groupsort); } /** * @description Decorator for handling delete operations * @summary Defines a behavior to execute during delete operations * @template V - Type for metadata, defaults to object * @param {OperationHandler<any, any, V, any, any>} handler - The method called upon the operation * @param {V} [data] - Optional metadata to pass to the handler * @return {PropertyDecorator} A decorator that can be applied to class properties * @function onDelete * @category Property Decorators */ function onDelete(handler, data, groupsort) { return on(constants_1.DBOperations.DELETE, handler, data, groupsort); } /** * @description Decorator for handling all operation types * @summary Defines a behavior to execute during any database operation * @template V - Type for metadata, defaults to object * @param {OperationHandler<any, any, V, any, any>} handler - The method called upon the operation * @param {V} [data] - Optional metadata to pass to the handler * @return {PropertyDecorator} A decorator that can be applied to class properties * @function onAny * @category Property Decorators */ function onAny(handler, data, groupsort) { return on(constants_1.DBOperations.ALL, handler, data, groupsort); } /** * @description Base decorator for handling database operations * @summary Defines a behavior to execute during specified database operations * @template V - Type for metadata, defaults to object * @param {OperationKeys[] | DBOperations} [op=DBOperations.ALL] - One or more operation types to handle * @param {OperationHandler<any, any, V, any, any>} handler - The method called upon the operation * @param {V} [data] - Optional metadata to pass to the handler * @return {PropertyDecorator} A decorator that can be applied to class properties * @function on * @category Property Decorators * @example * // Example usage: * class MyModel { * @on(DBOperations.CREATE, myHandler) * myProperty: string; * } */ function on(op = constants_1.DBOperations.ALL, handler, data, groupsort) { return operation(constants_1.OperationKeys.ON, op, handler, data, groupsort); } /** * @description Decorator for handling post-create and post-update operations * @summary Defines a behavior to execute after both create and update operations * @template V - Type for metadata, defaults to object * @param {StandardOperationHandler<any, any, V, any, any> | UpdateOperationHandler<any, any, V, any, any>} handler - The method called after the operation * @param {V} [data] - Optional metadata to pass to the handler * @return {PropertyDecorator} A decorator that can be applied to class properties * @function afterCreateUpdate * @category Property Decorators */ function afterCreateUpdate(handler, data, groupsort) { return after(constants_1.DBOperations.CREATE_UPDATE, handler, data, groupsort); } /** * @description Decorator for handling post-update operations * @summary Defines a behavior to execute after update operations * @template V - Type for metadata, defaults to object * @param {UpdateOperationHandler<any, any, V, any, any>} handler - The method called after the operation * @param {V} [data] - Optional metadata to pass to the handler * @return {PropertyDecorator} A decorator that can be applied to class properties * @function afterUpdate * @category Property Decorators */ function afterUpdate(handler, data, groupsort) { return after(constants_1.DBOperations.UPDATE, handler, data, groupsort); } /** * @description Decorator for handling post-create operations * @summary Defines a behavior to execute after create operations * @template V - Type for metadata, defaults to object * @param {StandardOperationHandler<any, any, V, any, any>} handler - The method called after the operation * @param {V} [data] - Optional metadata to pass to the handler * @return {PropertyDecorator} A decorator that can be applied to class properties * @function afterCreate * @category Property Decorators */ function afterCreate(handler, data, groupsort) { return after(constants_1.DBOperations.CREATE, handler, data, groupsort); } /** * @description Decorator for handling post-read operations * @summary Defines a behavior to execute after read operations * @template V - Type for metadata, defaults to object * @param {StandardOperationHandler<any, any, V, any, any>} handler - The method called after the operation * @param {V} [data] - Optional metadata to pass to the handler * @return {PropertyDecorator} A decorator that can be applied to class properties * @function afterRead * @category Property Decorators */ function afterRead(handler, data, groupsort) { return after(constants_1.DBOperations.READ, handler, data, groupsort); } /** * @description Decorator for handling post-delete operations * @summary Defines a behavior to execute after delete operations * @template V - Type for metadata, defaults to object * @param {StandardOperationHandler<any, any, V, any, any>} handler - The method called after the operation * @param {V} [data] - Optional metadata to pass to the handler * @return {PropertyDecorator} A decorator that can be applied to class properties * @function afterDelete * @category Property Decorators */ function afterDelete(handler, data, groupsort) { return after(constants_1.DBOperations.DELETE, handler, data, groupsort); } /** * @description Decorator for handling post-operation for all operation types * @summary Defines a behavior to execute after any database operation * @template V - Type for metadata, defaults to object * @param {StandardOperationHandler<any, any, V, any, any>} handler - The method called after the operation * @param {V} [data] - Optional metadata to pass to the handler * @return {PropertyDecorator} A decorator that can be applied to class properties * @function afterAny * @category Property Decorators */ function afterAny(handler, data, groupsort) { return after(constants_1.DBOperations.ALL, handler, data, groupsort); } /** * @description Base decorator for handling post-operation behaviors * @summary Defines a behavior to execute after specified database operations * @template V - Type for metadata, defaults to object * @param {OperationKeys[] | DBOperations} [op=DBOperations.ALL] - One or more operation types to handle * @param {OperationHandler<any, any, V, any, any>} handler - The method called after the operation * @param {V} [data] - Optional metadata to pass to the handler * @return {PropertyDecorator} A decorator that can be applied to class properties * @function after * @category Property Decorators * @example * // Example usage: * class MyModel { * @after(DBOperations.CREATE, myHandler) * myProperty: string; * } */ function after(op = constants_1.DBOperations.ALL, handler, data, groupsort) { return operation(constants_1.OperationKeys.AFTER, op, handler, data, groupsort); } /** * @description Core decorator factory for operation handlers * @summary Creates decorators that register handlers for database operations * @template V - Type for metadata, defaults to object * @param {OperationKeys.ON | OperationKeys.AFTER} baseOp - Whether the handler runs during or after the operation * @param {OperationKeys[]} [operation=DBOperations.ALL] - The specific operations to handle * @param {OperationHandler<any, any, V, any, any>} handler - The handler function to execute * @param {V} [dataToAdd] - Optional metadata to pass to the handler * @return {PropertyDecorator} A decorator that can be applied to class properties * @function operation * @category Property Decorators * @mermaid * sequenceDiagram * participant Client * participant Decorator as @operation * participant Operations as Operations Registry * participant Handler * * Client->>Decorator: Apply to property * Decorator->>Operations: Register handler * Decorator->>Decorator: Store metadata * * Note over Client,Handler: Later, during operation execution * Client->>Operations: Execute operation * Operations->>Handler: Call registered handler * Handler-->>Operations: Return result * Operations-->>Client: Return final result */ function operation(baseOp, operation = constants_1.DBOperations.ALL, handler, dataToAdd, groupsort = DefaultGroupSort) { return (target, propertyKey) => { const name = target.constructor.name; const decorators = operation.reduce((accum, op) => { const compoundKey = baseOp + op; let data = Reflect.getMetadata(Operations_1.Operations.key(compoundKey), target, propertyKey); if (!data) data = { operation: op, handlers: {}, }; const handlerKey = Operations_1.Operations.getHandlerName(handler); let mergeData = groupsort; if (dataToAdd) { if (Object.keys(dataToAdd).filter((key) => key in groupsort).length > 0) throw new errors_1.InternalError(`Unable to merge groupSort into dataToAdd due to overlaping keys`); mergeData = { ...groupsort, ...dataToAdd }; } if (!data.handlers[name] || !data.handlers[name][propertyKey] || !(handlerKey in data.handlers[name][propertyKey])) { data.handlers[name] = data.handlers[name] || {}; data.handlers[name][propertyKey] = data.handlers[name][propertyKey] || {}; data.handlers[name][propertyKey][handlerKey] = { data: mergeData, }; accum.push(handle(compoundKey, handler), (0, decorator_validation_1.propMetadata)(Operations_1.Operations.key(compoundKey), data)); } return accum; }, []); return (0, reflection_1.apply)(...decorators)(target, propertyKey); }; } /** * @description * Creates a higher-order function that attaches a metadata entry containing a handler * and its execution parameters, to be conditionally evaluated later. * * @summary * The `executeIf` function is a decorator factory designed to wrap a handler function * and associate it with a specific metadata key. When invoked, it stores both the * parameters passed and the handler reference inside the metadata system for deferred * or conditional evaluation. This is particularly useful for dynamically applying logic * or decorators only when certain conditions are met. * * @template P - Represents a tuple of any parameter types that the handler function accepts. * * @param {string} key - The metadata key used to store and later retrieve the handler and its parameters. * @param {(...params: P) => boolean} handler - A predicate or handler function that receives the same parameters as the decorator * and determines whether the associated logic should execute. * * @return {(...params: P) => ReturnType<typeof metadata>} * Returns a function that, when invoked with the given parameters, stores a metadata object containing * both the parameters and the handler reference under the provided key. * * @function storeHandlerMetadata * * @mermaid * sequenceDiagram * participant Dev as Developer * participant executeIf as executeIf() * participant ReturnedFn as Returned Function * participant Metadata as metadata() * * Dev->>executeIf: Calls executeIf(key, handler) * executeIf->>ReturnedFn: Returns function(...params) * Dev->>ReturnedFn: Invokes returned function with (...params) * ReturnedFn->>Metadata: Calls metadata(key, { args: params, handler }) * Metadata-->>ReturnedFn: Returns stored metadata reference * ReturnedFn-->>Dev: Returns metadata response * */ function storeHandlerMetadata(key, handler) { return (...params) => { return (0, decoration_1.metadata)(key, { args: params, handler }); }; } /** * @description * Decorator factory that conditionally blocks specific CRUD operations * from being executed on a model or controller. * * @summary * The `BlockOperations` decorator integrates with the `executeIf` mechanism to * associate metadata that defines which CRUD operations should be restricted. * When applied, it registers a conditional handler that evaluates whether a given * operation is included in the list of blocked operations. This enables dynamic, * metadata-driven control over allowed operations in CRUD-based systems. * * @template CrudOperations - Enum or type representing valid CRUD operations. * * @param {CrudOperations[]} operations - An array of CRUD operations that should be blocked. * The handler will later check if the requested operation is part of this list. * * Returns a decorator that stores metadata indicating which operations are blocked. * The metadata can be inspected or enforced later within the application's lifecycle. * * @function BlockOperations * @category decorators */ const BlockOperations = (operations) => storeHandlerMetadata(constants_1.OperationKeys.REFLECT + constants_1.OperationKeys.BLOCK, (operations, operation) => { return operations.includes(operation); })(operations); exports.BlockOperations = BlockOperations; //# sourceMappingURL=decorators.js.map