domain-objects
Version:
A simple, convenient way to represent domain objects, leverage domain knowledge, and add runtime validation in your code base.
85 lines • 4.06 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.hasReadonly = void 0;
const helpful_errors_1 = require("helpful-errors");
const type_fns_1 = require("type-fns");
const isOfDomainEntity_1 = require("../instantiation/inherit/isOfDomainEntity");
const isOfDomainObject_1 = require("../instantiation/inherit/isOfDomainObject");
const DomainObjectMetadataMustBeDefinedError_1 = require("./DomainObjectMetadataMustBeDefinedError");
/**
* checks whether a (possibly nested, dot-path) readonly key resolves to a defined value
*
* - flat key (no dot) => the value on this object must be defined
* - dot-path key => descend each segment; the terminal value must be defined
* - array along the path => every element must satisfy the rest of the path
*/
const isReadonlyPathDefined = (value, path) => {
// array along the path => every element must satisfy the rest of the path
if (Array.isArray(value))
return value.every((item) => isReadonlyPathDefined(item, path));
const dotIndex = path.indexOf('.');
const head = dotIndex === -1 ? path : path.slice(0, dotIndex);
const tail = dotIndex === -1 ? null : path.slice(dotIndex + 1);
const next = value?.[head];
// terminal segment => the value must be defined
if (tail === null)
return next !== undefined;
// non-terminal segment => must be able to descend, then check the rest of the path
if (next === undefined || next === null)
return false;
return isReadonlyPathDefined(next, tail);
};
/**
* runtime type guard that asserts that all readonly keys (metadata + explicit readonly)
* of a domain object have defined values
*
* note: this function requires that `metadata` is explicitly declared on the class.
* if metadata is not explicitly declared, this function will throw an error to
* prevent ambiguity about which keys are being checked.
*
* this is useful for narrowing types after persistence operations where readonly
* attributes are expected to be populated
*
* for example:
* ```ts
* const cluster = new DeclaredAwsRdsCluster({ name: 'testdb' });
* // cluster.arn is string | undefined
* // cluster.port is number | undefined
*
* const persisted = await persist(cluster);
* if (hasReadonly({ of: DeclaredAwsRdsCluster })(persisted)) {
* // persisted.arn is string (required)
* // persisted.port is number (required)
* }
*
* // or use .assure to throw if readonly keys are missing
* const assured = hasReadonly({ of: DeclaredAwsRdsCluster }).assure(persisted);
* // assured.arn is string (required)
* // assured.port is number (required)
* ```
*/
const hasReadonly = ({ of: dobj, }) => {
const assess = (obj) => {
// ensure it's a domain object
if (!(0, isOfDomainObject_1.isOfDomainObject)(obj))
throw new helpful_errors_1.UnexpectedCodePathError('hasReadonly called on object that is not an instance of a DomainObject. Are you sure you instantiated the object? (Related: see `DomainObject.nested`)', { obj });
// fail fast if metadata is not explicitly declared
const metadataKeys = dobj.metadata;
if (!metadataKeys)
throw new DomainObjectMetadataMustBeDefinedError_1.DomainObjectMetadataMustBeDefinedError({
domainObjectName: dobj.name,
nameOfFunctionNeededFor: 'hasReadonly',
});
// get explicit readonly keys (only applicable to DomainEntity)
const readonlyKeys = (0, isOfDomainEntity_1.isOfDomainEntity)(obj)
? (dobj.readonly ?? [])
: [];
// combine metadata and readonly keys
const allReadonlyKeys = [...new Set([...metadataKeys, ...readonlyKeys])];
// check that all readonly keys (flat or nested dot-path) have defined values
return allReadonlyKeys.every((key) => isReadonlyPathDefined(obj, key));
};
return (0, type_fns_1.withAssure)(assess, { name: `hasReadonly({ of: ${dobj.name} })` });
};
exports.hasReadonly = hasReadonly;
//# sourceMappingURL=hasReadonly.js.map