domain-objects
Version:
A simple, convenient way to represent domain objects, leverage domain knowledge, and add runtime validation in your code base.
101 lines (98 loc) • 7.14 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.hydrateNestedDomainObjects = exports.NestedDomainObjectHydrationError = void 0;
/* eslint-disable no-underscore-dangle */
const DomainObject_1 = require("../DomainObject");
const isArray = (val) => Array.isArray(val);
class NestedDomainObjectHydrationError extends Error {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
constructor(message, metadata) {
super(`
${message}
${JSON.stringify(metadata, null, 2)}
`.trim());
}
}
exports.NestedDomainObjectHydrationError = NestedDomainObjectHydrationError;
const hydrateNestedDomainObjects = ({ props, nested, domainObjectName, }) => {
// create a new object, so as to not mutate original props
const hydratedProps = Object.assign({}, props);
// drop the "_dobj" prop, if one was defined due to serialization
delete hydratedProps._dobj; // eslint-disable-line no-underscore-dangle
// for each key that we were told is a nested domain object
Object.keys(nested).forEach((key) => {
// check that the value of "nested" was defined as a DomainObject or an array of DomainObject choices
const declaredNestedDomainObjectValue = nested[key];
const DeclaredNestedDomainObjectClassOptions = isArray(declaredNestedDomainObjectValue)
? declaredNestedDomainObjectValue
: [declaredNestedDomainObjectValue];
const eachIsDomainObjectBased = DeclaredNestedDomainObjectClassOptions.every((NestedDomainObject) => NestedDomainObject.prototype instanceof DomainObject_1.DomainObject);
if (!eachIsDomainObjectBased)
throw new NestedDomainObjectHydrationError(`each value of each ${domainObjectName}.nested key must be a single typeof DomainObject or an array of options of typeof DomainObject`, { DeclaredNestedDomainObjectClassOptions });
// check that the implemented value for this nested prop is an object - otherwise, this field must be nullable
const nestedProp = props[key];
if (typeof nestedProp !== 'object' || nestedProp === null)
return; // skip hydration if its not an object or is null - since no domain object would validate that
// define how to instantiate a prop into the nested DomainObject out of the specified options
const declaredNestedClassNameOptionsForProp = DeclaredNestedDomainObjectClassOptions.map((ClassOption) => ClassOption.name);
const instantiateThisPropIfNeeded = (prop) => {
// if it's already an instance of a domain object, then just check that it's one of the options specified and return it if so
const instantiatedClassName = prop instanceof DomainObject_1.DomainObject ? prop.constructor.name : null;
if (instantiatedClassName) {
// if the name is included in the valid nested class options, then return it
if (declaredNestedClassNameOptionsForProp.includes(instantiatedClassName))
return prop; // it's already instantiated as a valid domain object. good to go
}
// try and instantiate it by leveraging the fact that there's only one option, if possible
if (DeclaredNestedDomainObjectClassOptions.length === 1)
return new DeclaredNestedDomainObjectClassOptions[0](prop); // this case is easy, since there's only one option
// otherwise, we must rely on the `_dobj` prop having been set by the `serialize` function or manually
const declaredClassNameOfProp = prop._dobj;
if (!declaredClassNameOfProp)
throw new NestedDomainObjectHydrationError(`
DomainObject property ${domainObjectName}.${key} was declared as a nested domain object with ${DeclaredNestedDomainObjectClassOptions.length} options but was not already instantiated as one of the declared nested domain objects nor did it have a ._dobj key specified from serialization. Can not determine which option to use without one of those two specified.
Please try one of the following
${[
// recommend adding the class the prop was instantiated with to the list of declared nested class options, if it was instantiated as a domain object already
instantiatedClassName
? `- include the class ${instantiatedClassName}, which ${domainObjectName}.${key} was instantiated with, as one of the declared nested class options for this key`
: undefined,
// recommend instantiating the prop w/ the domain object manually, in case they are manually creating domain objects
`- instantiate ${domainObjectName}.${key} as a nested DomainObject when instantiating ${domainObjectName}`,
// recommend using the serialize method, in case they were trying to deserialize into domain objects
`- make sure that you are using the 'serialize' method of this library if deserializing`,
]
.filter((str) => !!str)
.join('\n')}
}
`.trim(), {
key,
prop,
instantiatedClassName,
declaredNestedClassNameOptionsForProp,
});
const CorrectNestedDomainObject = DeclaredNestedDomainObjectClassOptions.find((ClassOption) => ClassOption.name === declaredClassNameOfProp);
if (!CorrectNestedDomainObject)
throw new NestedDomainObjectHydrationError(`
DomainObject property ${domainObjectName}.${key} was declared as a nested domain object with ${DeclaredNestedDomainObjectClassOptions.length} options and had a ._dobj key specified on the input. However, the specified ._dobj was not found as one of the declared nested domain object options. In otherwords, the correct class constructor was not present on the DomainObject.nested definition.
Please check the declared nested domain object options for ${domainObjectName}.${key} and update it to include ${declaredClassNameOfProp} or instantiate ${domainObjectName}.${key} with one of the declared options before serialization.
`.trim(), {
key,
prop,
_dobj: declaredClassNameOfProp,
declaredNestedClassNameOptionsForProp,
});
return new CorrectNestedDomainObject(prop);
};
// instantiate the prop into the nested DomainObject specified, if not already instantiated
const hydratedProp = isArray(nestedProp)
? nestedProp.map(instantiateThisPropIfNeeded) // if array, then hydrate each
: instantiateThisPropIfNeeded(nestedProp); // else, hydrate the root
// and overwrite the prior prop value w/ the hydrated value, in the hydratedProp object
hydratedProps[key] = hydratedProp;
});
// now return the hydrated props
return hydratedProps;
};
exports.hydrateNestedDomainObjects = hydrateNestedDomainObjects;
//# sourceMappingURL=hydrateNestedDomainObjects.js.map