UNPKG

@decaf-ts/core

Version:

Core persistence module for the decaf framework

493 lines 20 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.table = table; exports.column = column; exports.index = index; exports.uniqueOnCreateUpdate = uniqueOnCreateUpdate; exports.unique = unique; exports.createdByOnCreateUpdate = createdByOnCreateUpdate; exports.createdBy = createdBy; exports.updatedBy = updatedBy; exports.createdAt = createdAt; exports.updatedAt = updatedAt; exports.oneToOne = oneToOne; exports.oneToMany = oneToMany; exports.manyToOne = manyToOne; exports.manyToMany = manyToMany; exports.generated = generated; exports.noValidateOn = noValidateOn; exports.noValidateOnCreate = noValidateOnCreate; exports.noValidateOnUpdate = noValidateOnUpdate; exports.noValidateOnCreateUpdate = noValidateOnCreateUpdate; exports.relation = relation; const db_decorators_1 = require("@decaf-ts/db-decorators"); const decoration_1 = require("@decaf-ts/decoration"); const constants_1 = require("./../persistence/constants.cjs"); const constants_2 = require("./../repository/constants.cjs"); const decorator_validation_1 = require("@decaf-ts/decorator-validation"); const Condition_1 = require("./../query/Condition.cjs"); const construction_1 = require("./construction.cjs"); const errors_1 = require("./../utils/errors.cjs"); /** * @description Specifies the database table name for a model * @summary Decorator that sets the table name for a model class in the database * @param {string} opts - The name of the table in the database * @return {Function} A decorator function that can be applied to a class * @function table * @category Class Decorators */ function table(opts) { return decoration_1.Decoration.for(constants_1.PersistenceKeys.TABLE) .define({ decorator: function table(opts) { return function table(target) { return (0, decoration_1.metadata)(constants_1.PersistenceKeys.TABLE, opts || target.name.toLowerCase())(target); }; }, args: [opts], }) .apply(); } /** * @description Specifies the database column name for a model property * @summary Decorator that maps a model property to a specific column name in the database * @param {string} columnName - The name of the column in the database * @return {Function} A decorator function that can be applied to a class property * @function column * @category Property Decorators */ function column(columnName) { return decoration_1.Decoration.for(constants_1.PersistenceKeys.COLUMN) .define({ decorator: function column(c) { return function column(obj, attr) { return (0, decoration_1.propMetadata)(decoration_1.Metadata.key(constants_1.PersistenceKeys.COLUMN, attr), c || attr)(obj, attr); }; }, args: [columnName], }) .apply(); } function index(directions, compositions, name) { function index(directions, compositions, name) { return function index(obj, attr) { if (typeof directions === "string") { name = directions; directions = undefined; compositions = undefined; } if (typeof compositions === "string") { name = compositions; compositions = undefined; } if (!compositions && directions) { if (directions.find((d) => ![constants_2.OrderDirection.ASC, constants_2.OrderDirection.DSC].includes(d))) { compositions = directions; directions = undefined; } } return (0, decoration_1.propMetadata)(decoration_1.Metadata.key(`${constants_1.PersistenceKeys.INDEX}${compositions && compositions?.length ? `.${compositions.join(".")}` : ""}`, attr), { directions: directions, compositions: compositions, name: name, })(obj, attr); }; } return decoration_1.Decoration.for(constants_1.PersistenceKeys.INDEX) .define({ decorator: index, args: [directions, compositions, name], }) .apply(); } /** * @description Enforces uniqueness constraint during model creation and update * @summary Internal function used by the unique decorator to check if a property value already exists in the database * @template M - The model type extending Model * @template R - The repository type extending Repo<M, F, C> * @template V - The metadata type * @template F - The repository flags type * @template C - The context type extending Context<F> * @param {R} this - The repository instance * @param {Context<F>} context - The context for the operation * @param {V} data - The metadata for the property * @param key - The property key to check for uniqueness * @param {M} model - The model instance being created or updated * @return {Promise<void>} A promise that resolves when the check is complete or rejects with a ConflictError * @function uniqueOnCreateUpdate * @memberOf module:core */ async function uniqueOnCreateUpdate(context, data, key, model) { if (!model[key]) return; const existing = await this.select() .where(Condition_1.Condition.attribute(key).eq(model[key])) .execute(); if (existing.length) throw new db_decorators_1.ConflictError(`model already exists with property ${key} equal to ${JSON.stringify(model[key], undefined, 2)}`); } /** * @description Tags a property as unique * @summary Decorator that ensures a property value is unique across all instances of a model in the database * @return {Function} A decorator function that can be applied to a class property * @function unique * @category Property Decorators * @example * ```typescript * class User extends BaseModel { * @unique() * @required() * username!: string; * } * ``` */ function unique() { const key = constants_1.PersistenceKeys.UNIQUE; return decoration_1.Decoration.for(key) .define((0, decorator_validation_1.async)(), (0, db_decorators_1.onCreateUpdate)(uniqueOnCreateUpdate), (0, decoration_1.propMetadata)(key, {})) .apply(); } /** * @description Handles user identification for ownership tracking * @summary Internal function used by the createdBy and updatedBy decorators to set ownership information * @template M - The model type extending Model * @template R - The repository type extending Repo<M, F, C> * @template V - The relations metadata type extending RelationsMetadata * @template F - The repository flags type * @template C - The context type extending Context<F> * @param {R} this - The repository instance * @param {Context<F>} context - The context for the operation * @param {V} data - The metadata for the property * @param key - The property key to store the user identifier * @param {M} model - The model instance being created or updated * @return {Promise<void>} A promise that rejects with an AuthorizationError if user identification is not supported * @function createdByOnCreateUpdate * @memberOf module:core */ async function createdByOnCreateUpdate( // eslint-disable-next-line @typescript-eslint/no-unused-vars context, // eslint-disable-next-line @typescript-eslint/no-unused-vars data, // eslint-disable-next-line @typescript-eslint/no-unused-vars key, // eslint-disable-next-line @typescript-eslint/no-unused-vars model) { throw new errors_1.AuthorizationError("This adapter does not support user identification"); } /** * @description Tracks the creator of a model instance * @summary Decorator that marks a property to store the identifier of the user who created the model instance * @return {Function} A decorator function that can be applied to a class property * @function createdBy * @category Property Decorators * @example * ```typescript * class Document extends BaseModel { * @createdBy() * creator!: string; * } * ``` */ function createdBy() { function createdBy() { return function createdBy(target, prop) { return (0, decoration_1.apply)((0, db_decorators_1.onCreate)(createdByOnCreateUpdate), (0, decoration_1.propMetadata)(constants_1.PersistenceKeys.CREATED_BY, prop), generated())(target, prop); }; } return decoration_1.Decoration.for(constants_1.PersistenceKeys.CREATED_BY) .define({ decorator: createdBy, args: [], }) .apply(); } /** * @description Tracks the last updater of a model instance * @summary Decorator that marks a property to store the identifier of the user who last updated the model instance * @return {Function} A decorator function that can be applied to a class property * @function updatedBy * @category Property Decorators * @example * ```typescript * class Document extends BaseModel { * @updatedBy() * lastEditor!: string; * } * ``` */ function updatedBy() { function updatedBy() { return function updatedBy(target, prop) { return (0, decoration_1.apply)((0, db_decorators_1.onUpdate)(createdByOnCreateUpdate), (0, decoration_1.propMetadata)(constants_1.PersistenceKeys.UPDATED_BY, prop), generated())(target, prop); }; } return decoration_1.Decoration.for(constants_1.PersistenceKeys.UPDATED_BY) .define({ decorator: updatedBy, args: [], }) .apply(); } function createdAt() { return (0, db_decorators_1.timestamp)([db_decorators_1.OperationKeys.CREATE]); } function updatedAt() { return (0, db_decorators_1.timestamp)(); } /** * @description Defines a one-to-one relationship between models * @summary Decorator that establishes a one-to-one relationship between the current model and another model * @template M - The related model type extending Model * @param {Constructor<M>} clazz - The constructor of the related model class * @param {CascadeMetadata} [cascadeOptions=DefaultCascade] - Options for cascading operations (create, update, delete) * @param {boolean} [populate=true] - If true, automatically populates the relationship when the model is retrieved * @return {Function} A decorator function that can be applied to a class property * @function oneToOne * @category Property Decorators * @example * ```typescript * class User extends BaseModel { * @oneToOne(Profile) * profile!: string | Profile; * } * * class Profile extends BaseModel { * @required() * bio!: string; * } * ``` * @see oneToMany * @see manyToOne */ function oneToOne(clazz, cascadeOptions = constants_2.DefaultCascade, populate = true, joinColumnOpts, fk) { const key = constants_1.PersistenceKeys.ONE_TO_ONE; function oneToOneDec(clazz, cascade, populate, joinColumnOpts, fk) { const meta = { class: clazz, cascade: cascade, populate: populate, }; if (joinColumnOpts) meta.joinTable = joinColumnOpts; if (fk) meta.name = fk; return (0, decoration_1.apply)((0, decoration_1.prop)(), relation(key, meta), (0, decorator_validation_1.type)([clazz, String, Number, BigInt]), (0, db_decorators_1.onCreate)(construction_1.oneToOneOnCreate, meta), (0, db_decorators_1.onUpdate)(construction_1.oneToOneOnUpdate, meta), (0, db_decorators_1.onDelete)(construction_1.oneToOneOnDelete, meta), (0, db_decorators_1.afterAny)(construction_1.populate, meta)); } return decoration_1.Decoration.for(key) .define({ decorator: oneToOneDec, args: [clazz, cascadeOptions, populate, joinColumnOpts, fk], }) .apply(); } /** * @description Defines a one-to-many relationship between models * @summary Decorator that establishes a one-to-many relationship between the current model and multiple instances of another model * @template M - The related model type extending Model * @param {Constructor<M>} clazz - The constructor of the related model class * @param {CascadeMetadata} [cascadeOptions=DefaultCascade] - Options for cascading operations (create, update, delete) * @param {boolean} [populate=true] - If true, automatically populates the relationship when the model is retrieved * @return {Function} A decorator function that can be applied to a class property * @function oneToMany * @category Property Decorators * @example * ```typescript * class Author extends BaseModel { * @required() * name!: string; * * @oneToMany(Book) * books!: string[] | Book[]; * } * * class Book extends BaseModel { * @required() * title!: string; * } * ``` * @see oneToOne * @see manyToOne */ function oneToMany(clazz, cascadeOptions = constants_2.DefaultCascade, populate = true, joinTableOpts, fk) { const key = constants_1.PersistenceKeys.ONE_TO_MANY; function oneToManyDec(clazz, cascade, populate, joinTableOpts, fk) { const metadata = { class: clazz, cascade: cascade, populate: populate, }; if (joinTableOpts) metadata.joinTable = joinTableOpts; if (fk) metadata.name = fk; return (0, decoration_1.apply)((0, decoration_1.prop)(), relation(key, metadata), (0, decorator_validation_1.list)([ clazz, String, Number, // @ts-expect-error Bigint is not a constructor BigInt, ]), (0, db_decorators_1.onCreate)(construction_1.oneToManyOnCreate, metadata), (0, db_decorators_1.onUpdate)(construction_1.oneToManyOnUpdate, metadata), (0, db_decorators_1.onDelete)(construction_1.oneToManyOnDelete, metadata), (0, db_decorators_1.afterAny)(construction_1.populate, metadata)); } return decoration_1.Decoration.for(key) .define({ decorator: oneToManyDec, args: [clazz, cascadeOptions, populate, joinTableOpts, fk], }) .apply(); } /** * @description Defines a many-to-one relationship between models * @summary Decorator that establishes a many-to-one relationship between multiple instances of the current model and another model * @template M - The related model type extending Model * @param {Constructor<M>} clazz - The constructor of the related model class * @param {CascadeMetadata} [cascadeOptions=DefaultCascade] - Options for cascading operations (create, update, delete) * @param {boolean} [populate=true] - If true, automatically populates the relationship when the model is retrieved * @return {Function} A decorator function that can be applied to a class property * @function manyToOne * @category Property Decorators * @example * ```typescript * class Book extends BaseModel { * @required() * title!: string; * * @manyToOne(Author) * author!: string | Author; * } * * class Author extends BaseModel { * @required() * name!: string; * } * ``` * @see oneToMany * @see oneToOne */ function manyToOne(clazz, cascadeOptions = constants_2.DefaultCascade, populate = true, joinTableOpts, fk) { // Model.register(clazz as Constructor<M>); const key = constants_1.PersistenceKeys.MANY_TO_ONE; function manyToOneDec(clazz, cascade, populate, joinTableOpts, fk) { const metadata = { class: clazz, cascade: cascade, populate: populate, }; if (joinTableOpts) metadata.joinTable = joinTableOpts; if (fk) metadata.name = fk; return (0, decoration_1.apply)((0, decoration_1.prop)(), relation(key, metadata), (0, decorator_validation_1.type)([clazz, String, Number, BigInt]) // onCreate(oneToManyOnCreate, metadata), // onUpdate(oneToManyOnUpdate, metadata), // onDelete(oneToManyOnDelete, metadata), // afterAny(pop, metadata), ); } return decoration_1.Decoration.for(key) .define({ decorator: manyToOneDec, args: [clazz, cascadeOptions, populate, joinTableOpts, fk], }) .apply(); } /** * @description Defines a many-to-one relationship between models * @summary Decorator that establishes a many-to-one relationship between multiple instances of the current model and another model * @template M - The related model type extending Model * @param {Constructor<M>} clazz - The constructor of the related model class * @param {CascadeMetadata} [cascadeOptions=DefaultCascade] - Options for cascading operations (create, update, delete) * @param {boolean} [populate=true] - If true, automatically populates the relationship when the model is retrieved * @return {Function} A decorator function that can be applied to a class property * @function manyToOne * @category Property Decorators * @example * ```typescript * class Book extends BaseModel { * @required() * title!: string; * * @manyToOne(Author) * author!: string | Author; * } * * class Author extends BaseModel { * @required() * name!: string; * } * ``` * @see oneToMany * @see oneToOne */ function manyToMany(clazz, cascadeOptions = constants_2.DefaultCascade, populate = true, joinTableOpts, fk) { // Model.register(clazz as Constructor<M>); const key = constants_1.PersistenceKeys.MANY_TO_MANY; function manyToManyDec(clazz, cascade, populate, joinTableOpts, fk) { const metadata = { class: clazz, cascade: cascade, populate: populate, }; if (joinTableOpts) metadata.joinTable = joinTableOpts; if (fk) metadata.name = fk; return (0, decoration_1.apply)((0, decoration_1.prop)(), relation(key, metadata), (0, decorator_validation_1.list)([clazz, String, Number, BigInt]) // onCreate(oneToManyOnCreate, metadata), // onUpdate(oneToManyOnUpdate, metadata), // onDelete(oneToManyOnDelete, metadata), // afterAll(populate, metadata), ); } return decoration_1.Decoration.for(key) .define({ decorator: manyToManyDec, args: [clazz, cascadeOptions, populate, joinTableOpts, fk], }) .apply(); } function generated() { return function generated(target, prop) { return (0, decoration_1.propMetadata)(decoration_1.Metadata.key(constants_1.PersistenceKeys.GENERATED, prop), true)(target, prop); }; } function noValidateOn(...ops) { return function noValidateOn(target, propertyKey) { const currentMeta = decoration_1.Metadata.get(target, decoration_1.Metadata.key(constants_1.PersistenceKeys.NO_VALIDATE, propertyKey)) || []; const newMeta = [...new Set([...currentMeta, ...ops])]; return (0, decoration_1.apply)((0, decoration_1.metadata)(decoration_1.Metadata.key(constants_1.PersistenceKeys.NO_VALIDATE, propertyKey), newMeta))(target, propertyKey); }; } function noValidateOnCreate() { return noValidateOn(db_decorators_1.OperationKeys.CREATE); } function noValidateOnUpdate() { return noValidateOn(db_decorators_1.OperationKeys.UPDATE); } function noValidateOnCreateUpdate() { return noValidateOn(db_decorators_1.OperationKeys.UPDATE, db_decorators_1.OperationKeys.CREATE); } /** * @description Specifies the model property as a relation * @summary Decorator that specifies the model property as a relation in the database * @return {Function} A decorator function that can be applied to a class property * @function relation * @category Property Decorators */ function relation(relationKey, meta) { function relation(relationKey, meta) { return function relation(obj, attr) { (0, decoration_1.propMetadata)(relationKey, meta)(obj, attr); return (0, decoration_1.propMetadata)(decoration_1.Metadata.key(constants_1.PersistenceKeys.RELATIONS, attr), Object.assign({}, meta, { key: relationKey, }))(obj, attr); }; } return decoration_1.Decoration.for(constants_1.PersistenceKeys.RELATIONS) .define({ decorator: relation, args: [relationKey, meta], }) .apply(); } //# sourceMappingURL=decorators.js.map