UNPKG

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
"use strict"; 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