UNPKG

domain-objects

Version:

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

99 lines 5.38 kB
"use strict"; /* eslint-disable @typescript-eslint/no-use-before-define */ Object.defineProperty(exports, "__esModule", { value: true }); exports.serialize = void 0; /* eslint-disable no-underscore-dangle */ const assertDomainObjectIsSafeToManipulate_1 = require("../../constraints/assertDomainObjectIsSafeToManipulate"); const isOfDomainEntity_1 = require("../../instantiation/inherit/isOfDomainEntity"); const isOfDomainLiteral_1 = require("../../instantiation/inherit/isOfDomainLiteral"); const isOfDomainObject_1 = require("../../instantiation/inherit/isOfDomainObject"); const getUniqueIdentifier_1 = require("../../manipulation/getUniqueIdentifier"); /** * Returns a deterministic, human readable, and de-serializable string representation of a Domain Object. * * Deterministic: * - orders object keys * - sorts elements in arrays * - when lossless=false, replaces nested domain objects with their minimal and static representation (i.e., calls `getUniqueIdentifier` on any nested domain objects) * * Use Cases: * - change detection * - e.g., determine whether there has been a change to an entity or not, by serializing the previous and current versions, and comparing equality * - i.e., `const areEqual = serialize(omitMetadata(userBefore)) === serialize(omitMetadata(userNow));` * * - identity comparison * - e.g., determine whether two domain objects are the same, by getting their unique identities, serializing each and comparing equality * - i.e., `const areEqual = serialize(getUniqueIdentity(contactMethod1)) === serialize(getUniqueIdentity(contactMethod2))` * * - persistance * - e.g., serialize domain objects into a persistant store in string format (to be later revived with the `deserialize` method) */ const serialize = (value, options = { lossless: false, orderless: false }) => { // convert the value to a deterministically serializable object const serializableValue = toSerializable(value, options, true); // json stringify it last, so that we dont have a recursively growing set of nested quotes return JSON.stringify(serializableValue); }; exports.serialize = serialize; /** * helper method for serialize * * converts any value to a deterministically serializable representation (i.e., sorts arrays + object keys, casts .toString(), handles domain objects, etc) */ const toSerializable = (value, options, root) => { // if this value is not an array and is not an object, then it's a literal, so no more preparation required, return it itself if (!Array.isArray(value) && typeof value !== 'object') return value; // if its null, return it too (null is typeof 'object' in js :shrug:) if (value === null) return null; // if its an array, then `toSerializable` each element, and sort the result if needed if (Array.isArray(value)) return value .map((el) => toSerializable(el, options)) .sort((a, b) => { // if order was said to not matter, then sort in ascending value of their stringified form, to allow for deterministic comparisons if (options.orderless) return JSON.stringify(a) < JSON.stringify(b) ? -1 : 1; // otherwise, retain original order return 0; }); // if it has a `.toString()` method, use the `.toString()` method; (e.g., new Date().toString()) if (value.toString()) { const toStringResult = value.toString(); if (toStringResult !== '[object Object]') return toStringResult; // use the result, unless its the "generic object -> ['object Object']" result, which isn't helpful } // otherwise, prepare to make the object stringifiable return toSerializableObject(value, options, root); }; /** * helper method for `toSerializable` * * converts all objects to deterministically serializable objects (e.g., sort keys, handle domain objects, etc) */ const toSerializableObject = (obj, options, root) => { const stringifiableObj = {}; // if this object is a DomainObject, add its name to the serial parts as metadata (i.e., underscore suffix) if ((0, isOfDomainObject_1.isOfDomainObject)(obj)) stringifiableObj._dobj = obj.constructor.name; // if this object is a domain object, make sure that it is safe to manipulate if ((0, isOfDomainObject_1.isOfDomainObject)(obj)) (0, assertDomainObjectIsSafeToManipulate_1.assertDomainObjectIsSafeToManipulate)(obj); // if this object is a persistable DomainObject AND its not the root object we're serializing && lossless is off, then only consider its unique properties for serialization; (i.e., only consider which domain objects the root object references, not the current state of the domain objects the root object references) let objToMakeSerializable = obj; if (!root && !options.lossless && ((0, isOfDomainEntity_1.isOfDomainEntity)(obj) || (0, isOfDomainLiteral_1.isOfDomainLiteral)(obj))) { objToMakeSerializable = (0, getUniqueIdentifier_1.getUniqueIdentifier)(obj); } // for each key, in sorted order, get the serial representation Object.keys(objToMakeSerializable) .sort() .forEach((key) => { stringifiableObj[key] = toSerializable(obj[key], options); }); // return it return stringifiableObj; }; //# sourceMappingURL=serialize.js.map