/** * @constant DEFAULT_OPTIONS_KEYS the default options keys */ var DEFAULT_OPTIONS_KEYS = { isEqual: true, isMatchingKey: true, isPromise: true, maxSize: true, onCacheAdd: true, onCacheChange: true, onCacheHit: true, transformKey: true, }; /** * @function slice * * @description * slice.call() pre-bound */ var slice = Array.prototype.slice; /** * @function cloneArray * * @description * clone the array-like object and return the new array * * @param arrayLike the array-like object to clone * @returns the clone as an array */ function cloneArray(arrayLike) { var length = arrayLike.length; if (!length) { return []; } if (length === 1) { return [arrayLike[0]]; } if (length === 2) { return [arrayLike[0], arrayLike[1]]; } if (length === 3) { return [arrayLike[0], arrayLike[1], arrayLike[2]]; } return slice.call(arrayLike, 0); } /** * @function getCustomOptions * * @description * get the custom options on the object passed * * @param options the memoization options passed * @returns the custom options passed */ function getCustomOptions(options) { var customOptions = {}; /* eslint-disable no-restricted-syntax */ for (var key in options) { if (!DEFAULT_OPTIONS_KEYS[key]) { customOptions[key] = options[key]; } } /* eslint-enable */ return customOptions; } /** * @function isMemoized * * @description * is the function passed already memoized * * @param fn the function to test * @returns is the function already memoized */ function isMemoized(fn) { return typeof fn === 'function' && fn.isMemoized; } /** * @function isSameValueZero * * @description * are the objects equal based on SameValueZero equality * * @param object1 the first object to compare * @param object2 the second object to compare * @returns are the two objects equal */ function isSameValueZero(object1, object2) { // eslint-disable-next-line no-self-compare return object1 === object2 || (object1 !== object1 && object2 !== object2); } /** * @function mergeOptions * * @description * merge the options into the target * * @param existingOptions the options provided * @param newOptions the options to include * @returns the merged options */ function mergeOptions(existingOptions, newOptions) { var target = {}; /* eslint-disable no-restricted-syntax */ for (var key in existingOptions) { target[key] = existingOptions[key]; } for (var key in newOptions) { target[key] = newOptions[key]; } /* eslint-enable */ return target; } // utils var Cache = /** @class */ (function () { function Cache(options) { this.keys = []; this.values = []; this.options = options; var isMatchingKeyFunction = typeof options.isMatchingKey === 'function'; if (isMatchingKeyFunction) { this.getKeyIndex = this._getKeyIndexFromMatchingKey; } else if (options.maxSize > 1) { this.getKeyIndex = this._getKeyIndexForMany; } else { this.getKeyIndex = this._getKeyIndexForSingle; } this.canTransformKey = typeof options.transformKey === 'function'; this.shouldCloneArguments = this.canTransformKey || isMatchingKeyFunction; this.shouldUpdateOnAdd = typeof options.onCacheAdd === 'function'; this.shouldUpdateOnChange = typeof options.onCacheChange === 'function'; this.shouldUpdateOnHit = typeof options.onCacheHit === 'function'; } Object.defineProperty(Cache.prototype, "size", { /** * The number of cached [key,value] results. */ get: function () { return this.keys.length; }, enumerable: false, configurable: true }); Object.defineProperty(Cache.prototype, "snapshot", { /** * A copy of the cache at a moment in time. This is useful * to compare changes over time, since the cache mutates * internally for performance reasons. */ get: function () { return { keys: cloneArray(this.keys), size: this.size, values: cloneArray(this.values), }; }, enumerable: false, configurable: true }); /** * Gets the matching key index when a custom key matcher is used. */ Cache.prototype._getKeyIndexFromMatchingKey = function (keyToMatch) { var _a = this.options, isMatchingKey = _a.isMatchingKey, maxSize = _a.maxSize; var keys = this.keys; var keysLength = keys.length; if (!keysLength) { return -1; } if (isMatchingKey(keys[0], keyToMatch)) { return 0; } if (maxSize > 1) { for (var index = 1; index < keysLength; index++) { if (isMatchingKey(keys[index], keyToMatch)) { return index; } } } return -1; }; /** * Gets the matching key index when multiple keys are used. */ Cache.prototype._getKeyIndexForMany = function (keyToMatch) { var isEqual = this.options.isEqual; var keys = this.keys; var keysLength = keys.length; if (!keysLength) { return -1; } if (keysLength === 1) { return this._getKeyIndexForSingle(keyToMatch); } var keyLength = keyToMatch.length; var existingKey; var argIndex; if (keyLength > 1) { for (var index = 0; index < keysLength; index++) { existingKey = keys[index]; if (existingKey.length === keyLength) { argIndex = 0; for (; argIndex < keyLength; argIndex++) { if (!isEqual(existingKey[argIndex], keyToMatch[argIndex])) { break; } } if (argIndex === keyLength) { return index; } } } } else { for (var index = 0; index < keysLength; index++) { existingKey = keys[index]; if (existingKey.length === keyLength && isEqual(existingKey[0], keyToMatch[0])) { return index; } } } return -1; }; /** * Gets the matching key index when a single key is used. */ Cache.prototype._getKeyIndexForSingle = function (keyToMatch) { var keys = this.keys; if (!keys.length) { return -1; } var existingKey = keys[0]; var length = existingKey.length; if (keyToMatch.length !== length) { return -1; } var isEqual = this.options.isEqual; if (length > 1) { for (var index = 0; index < length; index++) { if (!isEqual(existingKey[index], keyToMatch[index])) { return -1; } } return 0; } return isEqual(existingKey[0], keyToMatch[0]) ? 0 : -1; }; /** * Order the array based on a Least-Recently-Used basis. */ Cache.prototype.orderByLru = function (key, value, startingIndex) { var keys = this.keys; var values = this.values; var currentLength = keys.length; var index = startingIndex; while (index--) { keys[index + 1] = keys[index]; values[index + 1] = values[index]; } keys[0] = key; values[0] = value; var maxSize = this.options.maxSize; if (currentLength === maxSize && startingIndex === currentLength) { keys.pop(); values.pop(); } else if (startingIndex >= maxSize) { // eslint-disable-next-line no-multi-assign keys.length = values.length = maxSize; } }; /** * Update the promise method to auto-remove from cache if rejected, and * if resolved then fire cache hit / changed. */ Cache.prototype.updateAsyncCache = function (memoized) { var _this = this; var _a = this.options, onCacheChange = _a.onCacheChange, onCacheHit = _a.onCacheHit; var firstKey = this.keys[0]; var firstValue = this.values[0]; this.values[0] = firstValue.then(function (value) { if (_this.shouldUpdateOnHit) { onCacheHit(_this, _this.options, memoized); } if (_this.shouldUpdateOnChange) { onCacheChange(_this, _this.options, memoized); } return value; }, function (error) { var keyIndex = _this.getKeyIndex(firstKey); if (keyIndex !== -1) { _this.keys.splice(keyIndex, 1); _this.values.splice(keyIndex, 1); } throw error; }); }; return Cache; }()); function createMemoizedFunction(fn, options) { if (options === void 0) { options = {}; } if (isMemoized(fn)) { return createMemoizedFunction(fn.fn, mergeOptions(fn.options, options)); } if (typeof fn !== 'function') { throw new TypeError('You must pass a function to `memoize`.'); } var _a = options.isEqual, isEqual = _a === void 0 ? isSameValueZero : _a, isMatchingKey = options.isMatchingKey, _b = options.isPromise, isPromise = _b === void 0 ? false : _b, _c = options.maxSize, maxSize = _c === void 0 ? 1 : _c, onCacheAdd = options.onCacheAdd, onCacheChange = options.onCacheChange, onCacheHit = options.onCacheHit, transformKey = options.transformKey; var normalizedOptions = mergeOptions({ isEqual: isEqual, isMatchingKey: isMatchingKey, isPromise: isPromise, maxSize: maxSize, onCacheAdd: onCacheAdd, onCacheChange: onCacheChange, onCacheHit: onCacheHit, transformKey: transformKey, }, getCustomOptions(options)); var cache = new Cache(normalizedOptions); var keys = cache.keys, values = cache.values, canTransformKey = cache.canTransformKey, shouldCloneArguments = cache.shouldCloneArguments, shouldUpdateOnAdd = cache.shouldUpdateOnAdd, shouldUpdateOnChange = cache.shouldUpdateOnChange, shouldUpdateOnHit = cache.shouldUpdateOnHit; var memoized = function () { var key = shouldCloneArguments ? cloneArray(arguments) : arguments; if (canTransformKey) { key = transformKey(key); } var keyIndex = keys.length ? cache.getKeyIndex(key) : -1; if (keyIndex !== -1) { if (shouldUpdateOnHit) { onCacheHit(cache, normalizedOptions, memoized); } if (keyIndex) { cache.orderByLru(keys[keyIndex], values[keyIndex], keyIndex); if (shouldUpdateOnChange) { onCacheChange(cache, normalizedOptions, memoized); } } } else { var newValue = fn.apply(this, arguments); var newKey = shouldCloneArguments ? key : cloneArray(arguments); cache.orderByLru(newKey, newValue, keys.length); if (isPromise) { cache.updateAsyncCache(memoized); } if (shouldUpdateOnAdd) { onCacheAdd(cache, normalizedOptions, memoized); } if (shouldUpdateOnChange) { onCacheChange(cache, normalizedOptions, memoized); } } return values[0]; }; memoized.cache = cache; memoized.fn = fn; memoized.isMemoized = true; memoized.options = normalizedOptions; return memoized; } export { createMemoizedFunction as default }; //# sourceMappingURL=index.mjs.map