@decaf-ts/db-decorators
Version:
Agnostic database decorators and repository
137 lines • 6.02 kB
JavaScript
import { Model, validate, } from "@decaf-ts/decorator-validation";
import { validateCompare } from "./../model/validation.js";
import { Metadata } from "@decaf-ts/decoration";
import { DBKeys } from "./../model/constants.js";
import { ModelOperations } from "./../operations/constants.js";
import { SerializationError } from "./../repository/errors.js";
Model.prototype.isTransient = function () {
return Metadata.isTransient(this);
};
/**
* @description Validates the model and checks for errors
* @summary Validates the current model state and optionally compares with a previous version
* @template M - Type extending Model
* @param {M|any} [previousVersion] - Optional previous version of the model for comparison
* @param {...any[]} exclusions - Properties to exclude from validation
* @return {ModelErrorDefinition|undefined} Error definition if validation fails, undefined otherwise
* @function hasErrors
* @memberOf module:db-decorators
*/
Model.prototype.hasErrors = function (previousVersion, ...exclusions) {
if (previousVersion && !(previousVersion instanceof Model)) {
exclusions.unshift(previousVersion);
previousVersion = undefined;
}
const async = this.isAsync();
const errs = validate(this, async, ...exclusions);
if (async) {
return Promise.resolve(errs).then((resolvedErrs) => {
if (resolvedErrs || !previousVersion) {
return resolvedErrs;
}
return validateCompare(previousVersion, this, async, ...exclusions);
});
}
if (errs || !previousVersion)
return errs;
// @ts-expect-error Overriding Model prototype method with dynamic conditional return type.
return validateCompare(previousVersion, this, async, ...exclusions);
};
Model.prototype.segregate = function segregate() {
return Model.segregate(this);
};
Model.segregate = function segregate(model) {
if (!Metadata.isTransient(model))
return { model: model };
const decoratedProperties = Metadata.validatableProperties(model.constructor);
const transientProps = Metadata.get(model.constructor, DBKeys.TRANSIENT);
const result = {
model: {},
transient: {},
};
for (const key of decoratedProperties) {
const isTransient = Object.keys(transientProps).includes(key);
if (isTransient) {
result.transient = result.transient || {};
try {
result.transient[key] = model[key];
}
catch (e) {
throw new SerializationError(`Failed to serialize transient property ${key}: ${e}`);
}
}
else {
result.model = result.model || {};
result.model[key] = model[key];
}
}
result.model = Model.build(result.model, model.constructor.name);
return result;
};
Model.pk = function pk(model, keyValue = false) {
if (!model)
throw new Error("No model was provided");
const constr = model instanceof Model ? model.constructor : model;
const idProp = Metadata.get(constr, DBKeys.ID);
if (!idProp) {
throw new Error(`No Id property defined for model ${constr?.name || "Unknown Model"}`);
}
const key = Object.keys(idProp)[0];
if (!keyValue)
return key;
if (model instanceof Model)
return model[key];
throw new Error("Cannot get the value of the pk from the constructor");
}.bind(Model);
Model.pkProps = function pkProps(model) {
return Metadata.get(model, Metadata.key(DBKeys.ID, Model.pk(model)));
}.bind(Model);
Model.isTransient = function isTransient(model) {
return !!Metadata.get(typeof model !== "function" ? model.constructor : model, DBKeys.TRANSIENT);
}.bind(Model);
Model.composed = function composed(model, prop) {
const constr = model instanceof Model ? model.constructor : model;
if (prop)
return Metadata.get(constr, Metadata.key(DBKeys.COMPOSED, prop));
return !!Metadata.get(constr, DBKeys.COMPOSED);
}.bind(Model);
/**
* @description Merges two model instances into a new instance.
* @summary Creates a new model instance by combining properties from an old model and a new model.
* Properties from the new model override properties from the old model if they are defined.
* @template {M} - Type extending Model
* @param {M} oldModel - The original model instance
* @param {M} model - The new model instance with updated properties
* @return {M} A new model instance with merged properties
*/
Model.merge = function merge(oldModel, newModel, constructor) {
constructor = constructor || oldModel.constructor;
const extract = (model) => Object.entries(model).reduce((accum, [key, val]) => {
if (typeof val !== "undefined")
accum[key] = val;
return accum;
}, {});
return new constructor(Object.assign({}, extract(oldModel), extract(newModel)));
}.bind(Model);
Metadata.saveOperation = function saveOperation(model, propertyKey, operation, metadata) {
if (!propertyKey)
return;
Metadata.set(model, Metadata.key(ModelOperations.OPERATIONS, propertyKey, operation), metadata);
}.bind(Metadata);
Metadata.readOperation = function readOperation(model, propertyKey, operation) {
if (!propertyKey || !operation)
return;
return Metadata.get(model, Metadata.key(ModelOperations.OPERATIONS, propertyKey, operation));
}.bind(Metadata);
Metadata.isTransient = function isTransient(model) {
return !!Metadata.get(typeof model !== "function" ? model.constructor : model, DBKeys.TRANSIENT);
}.bind(Metadata);
Model.generated = function generated(model, prop) {
return !!Metadata.get(typeof model !== "function" ? model.constructor : model, Metadata.key(DBKeys.GENERATED, prop));
}.bind(Model);
Model.shouldGenerate = function shouldGenerate(model, prop, ctx) {
if (ctx.get("allowGenerationOverride") && typeof model[prop] !== "undefined")
return false;
return true;
}.bind(Model);
//# sourceMappingURL=overrides.js.map