UNPKG

@darlean/valueobjects

Version:

Library for DDD-like value objects that can be validated, serialized and represented in other programming languages as native objects with native naming.

126 lines (125 loc) 5.25 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.checkLogicalTypes = exports.shouldCacheCanonical = exports.aExtendsB = exports.toValueClass = exports.constructValue = exports.Value = exports.validation = exports.valueobject = exports.optional = exports.required = exports.LOGICAL_TYPES = exports.VALIDATORS = void 0; const valueobject_1 = require("./valueobject"); exports.VALIDATORS = 'validators'; exports.LOGICAL_TYPES = 'logical=types'; //export type ValidatorFunc<T = unknown> = (value: T) => string | boolean | void; function required(clazz) { return { required: true, clazz: clazz }; } exports.required = required; function optional(clazz) { return { required: false, clazz: clazz }; } exports.optional = optional; function valueobject(logicalType) { // eslint-disable-next-line @typescript-eslint/ban-types return function (constructor) { const name = logicalType === undefined ? (0, valueobject_1.deriveTypeName)(constructor.name) : logicalType; let types = Reflect.getOwnMetadata(exports.LOGICAL_TYPES, constructor.prototype); if (!types) { if (name === '') { types = [...(Reflect.getMetadata(exports.LOGICAL_TYPES, constructor.prototype) ?? [])]; } else { types = [...(Reflect.getMetadata(exports.LOGICAL_TYPES, constructor.prototype) ?? []), name]; } Reflect.defineMetadata(exports.LOGICAL_TYPES, types, constructor.prototype); } else { if (name !== '') { types.push(name); } } }; } exports.valueobject = valueobject; function validation(validator, description) { // eslint-disable-next-line @typescript-eslint/ban-types return function (constructor) { const validatorFunc = (value, fail) => { try { const result = validator(value); if (typeof result === 'string') { return fail(result); } if (result === false) { return fail(description ?? ''); } } catch (e) { return fail(e.toString()); } }; let validators = Reflect.getOwnMetadata(exports.VALIDATORS, constructor.prototype); if (!validators) { validators = [ ...(Reflect.getMetadata(exports.VALIDATORS, constructor.prototype) ?? []), validatorFunc ]; Reflect.defineMetadata(exports.VALIDATORS, validators, constructor.prototype); } else { validators.push(validatorFunc); } }; } exports.validation = validation; class Value { // eslint-disable-next-line @typescript-eslint/no-empty-function constructor(_options) { } get _logicalTypes() { throw new Error('Not implemented'); } equals(_other) { throw new Error('Not implemented'); } static get logicalTypes() { return checkLogicalTypes(this.prototype); } } exports.Value = Value; function constructValue(clazz, options) { return Reflect.construct(clazz, [options]); } exports.constructValue = constructValue; function toValueClass(v) { // eslint-disable-next-line @typescript-eslint/ban-types if (v.prototype) { return v; } return v(); } exports.toValueClass = toValueClass; function aExtendsB(a, b) { for (let idx = 0; idx < b.length; idx++) { if (a[idx] !== b[idx]) { return false; } } return true; } exports.aExtendsB = aExtendsB; function shouldCacheCanonical(canonical, expectedTypes, cacheCanonical) { // Caching of canonicals is the mechanism in which a value object stores the canonical it is created from, so that when a canonical is // requested later on, this stored (cached) value can be returned. This has 2 reasons: // 1. Efficiency / performance // 2. To preserve the fields in the canonical that are not part of the value. // Caching should NOT be used when the canonical is incomplete. Like for a flex canonical that does not know the proper logical types // nor the actual physical type. // We detect this situation by using 'aExtendsB', which compares the logical types. When a canonical does not extend the expected type, we // should never cache it. // Note: The "canonical.is" can not be used here because it purposely will return true for such flex canonicals, even though it does not contain a logical type. return cacheCanonical === undefined ? aExtendsB(canonical.logicalTypes, expectedTypes) : cacheCanonical; } exports.shouldCacheCanonical = shouldCacheCanonical; // eslint-disable-next-line @typescript-eslint/ban-types function checkLogicalTypes(proto) { const types = Reflect.getOwnMetadata(exports.LOGICAL_TYPES, proto); if (!types) { throw new Error(`No logical types defined for class '${proto.constructor.name}', possibly due to a missing class decorator.`); } return types; } exports.checkLogicalTypes = checkLogicalTypes;