@decaf-ts/core
Version:
Core persistence module for the decaf framework
493 lines • 20 kB
JavaScript
;
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