@decaf-ts/db-decorators
Version:
Agnostic database decorators and repository
153 lines • 6.73 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 { InternalError, 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;
};
Metadata.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(Metadata);
Model.pk = function pk(model, keyValue = false) {
return Metadata.pk(model, keyValue);
}.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 extractData = (model) => Object.entries(model).reduce((accum, [key, val]) => {
if (typeof val !== "undefined" && val !== null)
accum[key] = val;
return accum;
}, {});
const data = Object.assign({}, extractData(oldModel), extractData(newModel));
return new constructor(data);
}.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);
Model.versionProp = function versionProp(model) {
const meta = Metadata.get(model.constructor);
if (!meta || !meta[DBKeys.VERSION])
throw new InternalError(`No version found for ${model.constructor.name}`);
return Object.keys(meta)[0];
}.bind(Model);
Model.versionOf = function versionOf(model) {
const version = model[Model.versionProp(model)];
if (typeof version !== "number" || version < 1)
throw new InternalError(`Invalid version number: ${version}`);
return version;
}.bind(Model);
//# sourceMappingURL=overrides.js.map