UNPKG

shelving

Version:

Toolkit for using data in JavaScript.

72 lines (71 loc) 3.33 kB
import { ValueError } from "../error/ValueError.js"; import { isArray } from "./array.js"; import { isDate } from "./date.js"; import { isMap } from "./map.js"; import { getProps, getPrototype, isObject, isPlainObject } from "./object.js"; import { isSet } from "./set.js"; import { isString } from "./string.js"; import { mapArray, mapObject } from "./transform.js"; /** Is an unknown value a dehydrated object with a `$type` key. */ function _isDehydrated(value) { return isString(value.$type); } /** * Deeply hydrate a class instance based on a set of `Hydrations` * - Hydration allows a client to receive class instances from a server. * - By its nature hydration is an unsafe operation. * - Deeply iterates into arrays and plain objects to hydrate their items and props too. * - Note: the recursion in this function does not currently protect against infinite loops. */ export function hydrate(value, hydrations) { if (isArray(value)) return mapArray(value, hydrate, hydrations); if (isPlainObject(value)) { if (!_isDehydrated(value)) return mapObject(value, hydrate, hydrations); const { $type, $value } = value; if ($type === "Map") return new Map($value); if ($type === "Set") return new Set($value); if ($type === "Date") return new Date($value); // Complex object, check the hydrations list. const hydration = hydrations[$type]; if (hydration) return { __proto__: hydration.prototype, ...mapObject($value, hydrate, hydrations) }; throw new ValueError(`Cannot hydrate object "${$type}"`, { type: $type, received: $value, caller: hydrate }); } return value; } /** * Deeply dehydrate a class instance based on a set of `Hydrations` * - Dehydration allows you to pass class instances from a server back to a client. * - By its nature dehydration is an unsafe operation. * - Deeply iterates into arrays and plain objects to dehydrate their items and props too. * - Note: the recursion in this function does not currently protect against infinite loops. * * @returns The dehydrated version of the specified value. * @throws `Error` if the value is a class instance that cannot be dehydrated (i.e. is not matched by any constructor in `hydrations`). */ export function dehydrate(value, hydrations) { if (isObject(value)) { if (isArray(value)) return mapArray(value, dehydrate, hydrations); if (isMap(value)) return { $type: "Map", $value: mapArray(value.entries(), dehydrate, hydrations) }; if (isSet(value)) return { $type: "Set", $value: mapArray(value.values(), dehydrate, hydrations) }; if (isDate(value)) return { $type: "Date", $value: value.getTime() }; if (isPlainObject(value)) return mapObject(value, dehydrate, hydrations); // Complex object, check the hydrations list. const proto = getPrototype(value); for (const [$type, hydration] of getProps(hydrations)) if (proto === hydration.prototype) return { $type, $value: mapObject(value, dehydrate, hydrations) }; throw new ValueError("Cannot dehydrate object", { received: value, caller: dehydrate }); } return value; }