@hiki9/rich-domain
Version:
Rich Domain is a library that provides a set of tools to help you build complex business logic in NodeJS using Domain Driven Design principles.
215 lines • 8.08 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Entity = void 0;
const lodash_1 = __importDefault(require("lodash"));
const deep_freeze_1 = require("../../utils/deep-freeze");
const lodash_compare_1 = require("../../utils/lodash-compare");
const errors_1 = require("../errors");
const auto_mapper_entity_1 = require("./auto-mapper-entity");
const entity_keys_to_exclude_on_compare_1 = require("./entity-keys-to-exclude-on-compare");
const history_1 = require("./history");
const ids_1 = require("./ids");
const proxy_1 = require("./proxy");
const revalidate_error_1 = require("./revalidate-error");
class Entity {
constructor(input, options = {}) {
this.isEntity = true;
this._id = null;
this._createdAt = null;
this._updatedAt = null;
this.metaHistory = null;
this.rulesIsLocked = false;
const instance = this.constructor;
if (!input || typeof input !== 'object') {
throw new errors_1.DomainError('Entity input must be an object reference to "' + instance?.name + 'Props"');
}
if (input instanceof Entity) {
throw new errors_1.DomainError('Entity instance cannot be passed as argument');
}
this.props = this.transformBeforeCreate(input);
this.generateOrAssignId(this.props);
this.assignAndRemoveTimestampSignatureFromProps(this.props);
this.revalidate();
this.ensureBusinessRules();
if (!options?.isAggregate) {
this.onEntityCreate();
}
if (!options?.preventHistoryTracker) {
this.props = this.generateProxyProps();
const self = this;
const onAddedSnapshot = (snapshot) => {
const field = snapshot.trace.update;
self.revalidate(field);
if (!self.rulesIsLocked) {
self.ensureBusinessRules();
}
if (typeof instance?.hooks?.onChange === 'function') {
instance.hooks.onChange(self, snapshot);
if (options.isAggregate)
self.history.deepWatch(self, instance.hooks.onChange);
}
};
this.metaHistory = new history_1.EntityMetaHistory(this.props, { onAddedSnapshot });
if (options.isAggregate && typeof instance?.hooks?.onChange === 'function') {
this.history.deepWatch(this, instance.hooks.onChange);
}
}
}
subscribe(props) {
if (!this.history) {
throw new errors_1.DomainError('History is not enabled for this entity');
}
return this.history.subscribe(this, props);
}
// Dispatch Entity Hook Validation
revalidate(fieldToRevalidate) {
const instance = this.constructor;
const typeValidation = instance.hooks?.typeValidation;
if (!typeValidation)
return;
if (fieldToRevalidate) {
const value = this.props[fieldToRevalidate];
const validation = typeValidation[fieldToRevalidate];
const errorMessage = validation?.(value);
if (errorMessage) {
const fieldMessage = `. field=${fieldToRevalidate?.toString()} instance=${instance?.name}`;
const expected = typeValidation[fieldToRevalidate]?.name;
const field = fieldToRevalidate.toString();
throw (0, revalidate_error_1.RevalidateError)(errorMessage + fieldMessage, value, expected, field);
}
}
else {
Object.entries(typeValidation)
.forEach(([field, validation]) => {
const value = this.props[field];
const errorMessage = validation(value);
if (errorMessage) {
const fieldMessage = `. field=${field?.toString()} instance=${instance?.name}`;
throw (0, revalidate_error_1.RevalidateError)(errorMessage + fieldMessage, value, validation?.name, field);
}
});
}
}
/**
@deprecated
This method will throw an error if called.
*/
getRawProps() {
throw new errors_1.DomainError('Method .getRawProps() is not allowed.');
}
ensureBusinessRules() {
const instance = this.constructor;
instance?.hooks?.rules?.(this);
}
get history() {
if (!this.metaHistory) {
throw new errors_1.DomainError('History is not enabled for this entity');
}
return this.metaHistory;
}
get createdAt() {
return this._createdAt;
}
get updatedAt() {
return this._updatedAt;
}
get id() {
if (!this._id) {
throw new errors_1.DomainError('Entity id is not defined');
}
return this._id;
}
clone() {
const instance = Reflect.getPrototypeOf(this);
const args = [this.props];
const entity = Reflect.construct(instance.constructor, args);
return entity;
}
isNew() {
return this.id.isNew();
}
toPrimitives() {
const result = Entity.autoMapper.entityToObj(this);
const frozen = (0, deep_freeze_1.deepFreeze)(result);
return frozen;
}
toJSON() {
return this.toPrimitives();
}
hashCode() {
const name = Reflect.getPrototypeOf(this);
return new ids_1.Id(`entity@${name?.constructor?.name}:${this.id.value}`);
}
isEqual(other) {
if (!other)
return false;
if (!(other instanceof Entity))
return false;
if (this === other)
return true;
const thisProps = this['props'];
const otherProps = other['props'];
const currentProps = lodash_1.default.cloneDeep(thisProps);
const providedProps = lodash_1.default.cloneDeep(otherProps);
const equalId = this.id.isEqual(other.id);
return equalId && lodash_1.default.isEqualWith(currentProps, providedProps, entity_keys_to_exclude_on_compare_1.isEqualWithoutCompareSpecifiedKeys);
}
compare(other) {
return (0, lodash_compare_1.lodashCompare)(this.props, other?.props);
}
transformBeforeCreate(props) {
const instance = this.constructor;
if (!instance?.hooks?.defaultValues) {
return props;
}
if (instance?.hooks?.defaultValues) {
Object.entries(instance.hooks.defaultValues).forEach(([key, defaultValue]) => {
if (props[key] === undefined) {
if (typeof defaultValue === 'function')
props[key] = defaultValue?.(props[key], props);
else
props[key] = defaultValue;
}
});
}
return props;
}
onEntityCreate() {
const instance = this.constructor;
instance?.hooks?.onCreate?.(this);
}
generateProxyProps() {
return new Proxy(this.props, (0, proxy_1.proxyHandler)(this));
}
generateOrAssignId(props) {
if (props.id instanceof ids_1.Id) {
this._id = props.id;
}
else {
const id = new ids_1.Id(typeof props.id === 'string' ? props.id : undefined);
this._id = id;
props.id = id;
}
}
assignAndRemoveTimestampSignatureFromProps(props) {
if (props?.createdAt) {
this._createdAt = props.createdAt;
}
else {
if (props.id.isNew()) {
this._createdAt = new Date();
}
}
if (props?.updatedAt) {
this._updatedAt = props.updatedAt;
}
delete props?.createdAt;
delete props?.updatedAt;
}
}
exports.Entity = Entity;
Entity.autoMapper = new auto_mapper_entity_1.AutoMapperEntity();
//# sourceMappingURL=entity.js.map