rich-domain
Version:
This package provide utils file and interfaces to assistant build a complex application with domain driving design
150 lines • 6.85 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ValueObject = void 0;
const deep_freeze_util_1 = require("../utils/deep-freeze.util");
const auto_mapper_1 = require("./auto-mapper");
const base_getters_and_setters_1 = require("./base-getters-and-setters");
const result_1 = require("./result");
/**
* @description A `ValueObject` represents a domain object characterized by its properties rather than a unique identifier.
* Commonly used in domain-driven design, value objects are immutable and should be structurally equal
* (two value objects with the same properties are considered equal).
* This class provides functionalities to:
* - Validate properties
* - Compare equality between value objects
* - Convert the value object into a plain object representation
* - Clone the value object
*/
class ValueObject extends base_getters_and_setters_1.default {
autoMapper;
/**
* @description Initializes a new ValueObject instance.
* @param props Properties that define the ValueObject.
* @param config Optional configuration settings for getter/setter behavior.
*/
constructor(props, config) {
super(props, config);
this.autoMapper = new auto_mapper_1.default();
}
/**
* @description Determines if the current value object is equal to another value object of the same type.
* Equality is defined by comparing properties, excluding `createdAt` and `updatedAt`.
* Primitive values (strings, numbers, booleans), dates, arrays, and IDs are compared by value.
* Complex object structures are compared by their JSON-serialized form (excluding `createdAt` and `updatedAt`).
*
* @param other The value object to compare against.
* @returns `true` if all considered properties are equal; `false` otherwise.
*/
isEqual(other) {
const props = this.props;
const otherProps = other?.props;
const stringifyAndOmit = (obj) => {
if (!obj)
return '';
const { createdAt, updatedAt, ...cleanedProps } = obj;
return JSON.stringify(cleanedProps);
};
if (this.validator.isString(props)) {
return this.validator.string(props).isEqual(otherProps);
}
if (this.validator.isDate(props)) {
return props.getTime() === otherProps?.getTime();
}
if (this.validator.isArray(props) || this.validator.isFunction(props)) {
return JSON.stringify(props) === JSON.stringify(otherProps);
}
if (this.validator.isBoolean(props)) {
return props === otherProps;
}
if (this.validator.isID(props)) {
return props.value() === otherProps?.value();
}
if (this.validator.isNumber(props) || typeof props === 'bigint') {
return this.validator.number(props).isEqualTo(otherProps);
}
if (this.validator.isUndefined(props) || this.validator.isNull(props)) {
return props === otherProps;
}
if (this.validator.isSymbol(props)) {
return props.description === otherProps?.description;
}
return stringifyAndOmit(props) === stringifyAndOmit(otherProps);
}
/**
* @description Creates a new instance of the value object, optionally overriding properties.
* Deep cloning is performed for object-based props, ensuring immutability of value objects.
*
* @param props Optional partial properties to override in the cloned instance.
* @returns A new ValueObject instance with updated properties.
*/
clone(props) {
const instance = Reflect.getPrototypeOf(this);
if (typeof this.props === 'object' && !(this.props instanceof Date) && !Array.isArray(this.props)) {
const _props = props ? { ...this.props, ...props } : { ...this.props };
const args = [_props, this.config];
return Reflect.construct(instance.constructor, args);
}
const args = [this.props, this.config];
return Reflect.construct(instance.constructor, args);
}
/**
* @description Converts the value object into a plain object or a format defined by the given adapter.
* If no adapter is provided, the object is serialized using an `AutoMapper` and frozen to ensure immutability.
*
* @param adapter Optional adapter to transform the value object into a custom format.
* @returns A deeply frozen, plain object representation of the value object properties.
*/
toObject(adapter) {
if (adapter && typeof adapter?.adaptOne === 'function') {
return adapter.adaptOne(this);
}
if (adapter && typeof adapter?.build === 'function') {
return adapter.build(this).value();
}
const serializedObject = this.autoMapper.valueObjectToObj(this);
const frozenObject = (0, deep_freeze_util_1.deepFreeze)(serializedObject);
return frozenObject;
}
/**
* @description Checks if a given value is considered valid for creating a ValueObject instance.
* Subclasses can override this to apply additional validation logic.
* @param value The value to validate.
* @returns `true` if valid; `false` otherwise.
*/
static isValid(value) {
return this.isValidProps(value);
}
/**
* @description Validates the provided properties before creating a new value object instance.
* @param props The properties to validate.
* @returns `true` if the properties are valid; `false` otherwise.
*/
static isValidProps(props) {
return !this.validator.isUndefined(props) && !this.validator.isNull(props);
}
/**
* @description Intended to initialize a new value object instance with a given value.
* This method should be implemented by subclasses as needed.
* @param value The initial value or properties.
* @throws An error indicating the method is not implemented.
*/
static init(value) {
throw new Error('method not implemented: init', {
cause: value
});
}
/**
* @description Creates a new ValueObject instance wrapped inside a `Result`.
* Returns a failure `Result` if the provided properties are invalid.
* @param props The properties needed to create the value object.
* @returns A `Result` containing the new ValueObject on success, or a failure `Result` on invalid properties.
*/
static create(props) {
if (!this.isValidProps(props))
return result_1.default.fail('Invalid props to create an instance of ' + this.name);
return result_1.default.Ok(new this(props));
}
}
exports.ValueObject = ValueObject;
exports.default = ValueObject;
//# sourceMappingURL=value-object.js.map