UNPKG

@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
"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