/* MIT License http://www.opensource.org/licenses/mit-license.php */ "use strict"; const memoize = require("../util/memoize"); const LAZY_TARGET = Symbol("lazy serialization target"); const LAZY_SERIALIZED_VALUE = Symbol("lazy serialization data"); /** @typedef {SerializerMiddleware>} LazyTarget */ /** @typedef {Record} LazyOptions */ /** * @template InputValue * @template OutputValue * @template {LazyTarget} InternalLazyTarget * @template {LazyOptions | undefined} InternalLazyOptions * @typedef {(() => InputValue | Promise) & Partial<{ [LAZY_TARGET]: InternalLazyTarget, options: InternalLazyOptions, [LAZY_SERIALIZED_VALUE]?: OutputValue | LazyFunction | undefined }>} LazyFunction */ /** * @template DeserializedType * @template SerializedType * @template Context */ class SerializerMiddleware { /* istanbul ignore next */ /** * @abstract * @param {DeserializedType} data data * @param {Context} context context object * @returns {SerializedType | Promise | null} serialized data */ serialize(data, context) { const AbstractMethodError = require("../AbstractMethodError"); throw new AbstractMethodError(); } /* istanbul ignore next */ /** * @abstract * @param {SerializedType} data data * @param {Context} context context object * @returns {DeserializedType | Promise} deserialized data */ deserialize(data, context) { const AbstractMethodError = require("../AbstractMethodError"); throw new AbstractMethodError(); } /** * @template TLazyInputValue * @template TLazyOutputValue * @template {LazyTarget} TLazyTarget * @template {LazyOptions | undefined} TLazyOptions * @param {TLazyInputValue | (() => TLazyInputValue)} value contained value or function to value * @param {TLazyTarget} target target middleware * @param {TLazyOptions=} options lazy options * @param {TLazyOutputValue=} serializedValue serialized value * @returns {LazyFunction} lazy function */ static createLazy( value, target, options = /** @type {TLazyOptions} */ ({}), serializedValue = undefined ) { if (SerializerMiddleware.isLazy(value, target)) return value; const fn = /** @type {LazyFunction} */ (typeof value === "function" ? value : () => value); fn[LAZY_TARGET] = target; fn.options = options; fn[LAZY_SERIALIZED_VALUE] = serializedValue; return fn; } /** * @template {LazyTarget} TLazyTarget * @param {EXPECTED_ANY} fn lazy function * @param {TLazyTarget=} target target middleware * @returns {fn is LazyFunction} true, when fn is a lazy function (optionally of that target) */ static isLazy(fn, target) { if (typeof fn !== "function") return false; const t = fn[LAZY_TARGET]; return target ? t === target : Boolean(t); } /** * @template TLazyInputValue * @template TLazyOutputValue * @template {LazyTarget} TLazyTarget * @template {Record} TLazyOptions * @template TLazySerializedValue * @param {LazyFunction} fn lazy function * @returns {LazyOptions | undefined} options */ static getLazyOptions(fn) { if (typeof fn !== "function") return; return fn.options; } /** * @template TLazyInputValue * @template TLazyOutputValue * @template {LazyTarget} TLazyTarget * @template {LazyOptions} TLazyOptions * @param {LazyFunction | EXPECTED_ANY} fn lazy function * @returns {TLazyOutputValue | undefined} serialized value */ static getLazySerializedValue(fn) { if (typeof fn !== "function") return; return fn[LAZY_SERIALIZED_VALUE]; } /** * @template TLazyInputValue * @template TLazyOutputValue * @template {LazyTarget} TLazyTarget * @template {LazyOptions} TLazyOptions * @param {LazyFunction} fn lazy function * @param {TLazyOutputValue} value serialized value * @returns {void} */ static setLazySerializedValue(fn, value) { fn[LAZY_SERIALIZED_VALUE] = value; } /** * @template TLazyInputValue DeserializedValue * @template TLazyOutputValue SerializedValue * @template {LazyTarget} TLazyTarget * @template {LazyOptions | undefined} TLazyOptions * @param {LazyFunction} lazy lazy function * @param {(value: TLazyInputValue) => TLazyOutputValue} serialize serialize function * @returns {LazyFunction} new lazy */ static serializeLazy(lazy, serialize) { const fn = /** @type {LazyFunction} */ ( memoize(() => { const r = lazy(); if ( r && typeof (/** @type {Promise} */ (r).then) === "function" ) { return ( /** @type {Promise} */ (r).then(data => data && serialize(data)) ); } return serialize(/** @type {TLazyInputValue} */ (r)); }) ); fn[LAZY_TARGET] = lazy[LAZY_TARGET]; fn.options = lazy.options; lazy[LAZY_SERIALIZED_VALUE] = fn; return fn; } /** * @template TLazyInputValue SerializedValue * @template TLazyOutputValue DeserializedValue * @template SerializedValue * @template {LazyTarget} TLazyTarget * @template {LazyOptions | undefined} TLazyOptions * @param {LazyFunction} lazy lazy function * @param {(data: TLazyInputValue) => TLazyOutputValue} deserialize deserialize function * @returns {LazyFunction} new lazy */ static deserializeLazy(lazy, deserialize) { const fn = /** @type {LazyFunction} */ ( memoize(() => { const r = lazy(); if ( r && typeof (/** @type {Promise} */ (r).then) === "function" ) { return ( /** @type {Promise} */ (r).then(data => deserialize(data)) ); } return deserialize(/** @type {TLazyInputValue} */ (r)); }) ); fn[LAZY_TARGET] = lazy[LAZY_TARGET]; fn.options = lazy.options; fn[LAZY_SERIALIZED_VALUE] = lazy; return fn; } /** * @template TLazyInputValue * @template TLazyOutputValue * @template {LazyTarget} TLazyTarget * @template {LazyOptions} TLazyOptions * @param {LazyFunction | undefined} lazy lazy function * @returns {LazyFunction | undefined} new lazy */ static unMemoizeLazy(lazy) { if (!SerializerMiddleware.isLazy(lazy)) return lazy; /** @type {LazyFunction} */ const fn = () => { throw new Error( "A lazy value that has been unmemorized can't be called again" ); }; fn[LAZY_SERIALIZED_VALUE] = SerializerMiddleware.unMemoizeLazy( /** @type {LazyFunction} */ (lazy[LAZY_SERIALIZED_VALUE]) ); fn[LAZY_TARGET] = lazy[LAZY_TARGET]; fn.options = lazy.options; return fn; } } module.exports = SerializerMiddleware;