UNPKG

domain-objects

Version:

A simple, convenient way to represent domain objects, leverage domain knowledge, and add runtime validation in your code base.

81 lines (78 loc) 4.76 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.assertDomainObjectIsSafeToManipulate = exports.DomainObjectNotSafeToManipulateError = void 0; const helpful_errors_1 = require("helpful-errors"); const isOfDomainObject_1 = require("../instantiation/inherit/isOfDomainObject"); // .why = an undeclared nested domain object is a developer misconfiguration of the class (the fix is // to add `static nested`), not a caller's bad input — so it is a MalfunctionError (exit 1, http 500) class DomainObjectNotSafeToManipulateError extends helpful_errors_1.MalfunctionError { constructor({ unsafeKeys, className, }) { const message = ` DomainObject '${className}' is not safe to manipulate. Specifically, the keys: ${JSON.stringify(unsafeKeys)}. Please make sure all nested objects are DomainObjects and are explicitly defined on the class definition, using the 'nested' static property. For example: \`\`\`ts class ${className} ... { public static nested = { ${unsafeKeys[0]}: ... }; } \`\`\` `.trim(); super(message); } } exports.DomainObjectNotSafeToManipulateError = DomainObjectNotSafeToManipulateError; /** * For internal use only. DomainObjects must have all nested domain objects defined in their `DomainObject.nested` definition. Otherwise, we could be dealing with a domain object that is not instantiated, and thus we will not appropriately serialize it. * * Therefore, if any DomainObject has nested objects that are not instantiated as DomainObjects, there is a risk that the nested object is really a nested, uninstantiated DomainObject - which would cause bugs if uncaught. * * @param obj - The domain object to validate * @param options - Optional configuration * @param options.onKeys - When provided, only validates the specified keys; otherwise validates all keys */ const assertDomainObjectIsSafeToManipulate = (obj, options) => { // determine which keys to check based on options const keysToCheck = options?.onKeys ?? Object.keys(obj); // grab the keys that have objects defined for their values const nestedObjectKeys = keysToCheck.filter((key) => typeof obj[key] === 'object' && obj[key] !== null); // grab all the "nested object keys" who's values are defined as "should be domain object" const nestedDomainObjectKeysDefined = Object.keys(obj.constructor.nested ?? {}); const nestedNonDomainObjectObjectKeys = nestedObjectKeys.filter((key) => !nestedDomainObjectKeysDefined.includes(key)); // not explicitly defined as a nested domain object key // now apply some filters to those concerning keys let concerningKeys = nestedNonDomainObjectObjectKeys; // now filter out the keys which correspond to well known objects that we known are serializable and are not potentially DomainObjects (e.g., dates) concerningKeys = concerningKeys.filter((concerningKey) => { const value = obj[concerningKey]; if ((0, isOfDomainObject_1.isOfDomainObject)(value)) return false; // if value is already as a domain object, not concerning if (value instanceof Date) return false; // if its a date, not concerning if ('Buffer' in globalThis && value instanceof Buffer) return false; // if its a buffer, not concerning return true; // if it wasn't filtered out by now, its still concerning }); // now filter out the keys which correspond to arrays of objects which are safe to manipulate; // TODO: consider how we can move this check to not be dependent on runtime contents of the domain object instance concerningKeys = concerningKeys.filter((concerningKey) => { const value = obj[concerningKey]; if (!Array.isArray(value)) return true; // if its not an array, its still concerning try { (0, exports.assertDomainObjectIsSafeToManipulate)(value); return false; // if we reached here, that means that the contents of the array _are_ safe, so not concerning } catch (error) { if (error instanceof DomainObjectNotSafeToManipulateError) return true; // if it was this error, that means that this key is definitely still concerning throw error; // otherwise its an error we couldn't handle, so we should pass it up } }); // if we have any concerningKeys still, then its not safe! if (concerningKeys.length) throw new DomainObjectNotSafeToManipulateError({ unsafeKeys: concerningKeys, className: obj.constructor.name, }); }; exports.assertDomainObjectIsSafeToManipulate = assertDomainObjectIsSafeToManipulate; //# sourceMappingURL=assertDomainObjectIsSafeToManipulate.js.map