@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
JavaScript
;
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;