diff --git a/src/index.ts b/src/index.ts index 432cda7..f86e6b0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -460,6 +460,96 @@ export namespace LRUCache { context?: undefined } + export interface MemoOptions + extends Pick< + OptionsBase, + | 'allowStale' + | 'updateAgeOnGet' + | 'noDeleteOnStaleGet' + | 'sizeCalculation' + | 'ttl' + | 'noDisposeOnSet' + | 'noUpdateTTL' + | 'noDeleteOnFetchRejection' + | 'allowStaleOnFetchRejection' + | 'ignoreFetchAbort' + | 'allowStaleOnFetchAbort' + > { + /** + * Set to true to force a re-load of the existing data, even if it + * is not yet stale. + */ + forceRefresh?: boolean + /** + * Context provided to the {@link OptionsBase.memoMethod} as + * the {@link MemoizerOptions.context} param. + * + * If the FC type is specified as unknown (the default), + * undefined or void, then this is optional. Otherwise, it will + * be required. + */ + context?: FC + status?: Status + } + /** + * Options provided to {@link LRUCache#memo} when the FC type is something + * other than `unknown`, `undefined`, or `void` + */ + export interface MemoOptionsWithContext + extends MemoOptions { + context: FC + } + /** + * Options provided to {@link LRUCache#memo} when the FC type is + * `undefined` or `void` + */ + export interface MemoOptionsNoContext + extends MemoOptions { + context?: undefined + } + + /** + * Options provided to the + * {@link OptionsBase.memoMethod} function. + */ + export interface MemoizerOptions { + options: MemoizerMemoOptions + /** + * Object provided in the {@link MemoOptions.context} option to + * {@link LRUCache#memo} + */ + context: FC + } + + /** + * options which override the options set in the LRUCache constructor + * when calling {@link LRUCache#memo}. + * + * This is the union of {@link GetOptions} and {@link SetOptions}, plus + * {@link MemoOptions.forceRefresh}, and + * {@link MemoerOptions.context} + * + * Any of these may be modified in the {@link OptionsBase.memoMethod} + * function, but the {@link GetOptions} fields will of course have no + * effect, as the {@link LRUCache#get} call already happened by the time + * the memoMethod is called. + */ + export interface MemoizerMemoOptions + extends Pick< + OptionsBase, + | 'allowStale' + | 'updateAgeOnGet' + | 'noDeleteOnStaleGet' + | 'sizeCalculation' + | 'ttl' + | 'noDisposeOnSet' + | 'noUpdateTTL' + > { + status?: Status + size?: Size + start?: Milliseconds + } + /** * Options that may be passed to the {@link LRUCache#has} method. */ @@ -520,6 +610,15 @@ export namespace LRUCache { options: FetcherOptions ) => Promise | V | undefined | void + /** + * the type signature for the {@link OptionsBase.memoMethod} option. + */ + export type Memoizer = ( + key: K, + staleValue: V | undefined, + options: MemoizerOptions + ) => V + /** * Options which may be passed to the {@link LRUCache} constructor. * @@ -698,6 +797,11 @@ export namespace LRUCache { */ fetchMethod?: Fetcher + /** + * Method that provides the implementation for {@link LRUCache#memo} + */ + memoMethod?: Memoizer + /** * Set to true to suppress the deletion of stale data when a * {@link OptionsBase.fetchMethod} returns a rejected promise. @@ -839,6 +943,7 @@ export class LRUCache readonly #dispose?: LRUCache.Disposer readonly #disposeAfter?: LRUCache.Disposer readonly #fetchMethod?: LRUCache.Fetcher + readonly #memoMethod?: LRUCache.Memoizer /** * {@link LRUCache.OptionsBase.ttl} @@ -1011,6 +1116,9 @@ export class LRUCache get fetchMethod(): LRUCache.Fetcher | undefined { return this.#fetchMethod } + get memoMethod(): LRUCache.Memoizer | undefined { + return this.#memoMethod + } /** * {@link LRUCache.OptionsBase.dispose} (read-only) */ @@ -1043,6 +1151,7 @@ export class LRUCache maxEntrySize = 0, sizeCalculation, fetchMethod, + memoMethod, noDeleteOnFetchRejection, noDeleteOnStaleGet, allowStaleOnFetchRejection, @@ -1074,6 +1183,14 @@ export class LRUCache } } + if ( + memoMethod !== undefined && + typeof memoMethod !== 'function' + ) { + throw new TypeError('memoMethod must be a function if defined') + } + this.#memoMethod = memoMethod + if ( fetchMethod !== undefined && typeof fetchMethod !== 'function' @@ -2248,6 +2365,49 @@ export class LRUCache return v } + /** + * If the key is found in the cache, then this is equivalent to + * {@link LRUCache#get}. If not, in the cache, then + * calculate the value using the {@link LRUCache.OptionsBase.memoMethod}, + * and add it to the cache. + */ + memo( + k: K, + memoOptions: unknown extends FC + ? LRUCache.MemoOptions + : FC extends undefined | void + ? LRUCache.MemoOptionsNoContext + : LRUCache.MemoOptionsWithContext + ): V + // this overload not allowed if context is required + memo( + k: unknown extends FC + ? K + : FC extends undefined | void + ? K + : never, + memoOptions?: unknown extends FC + ? LRUCache.MemoOptions + : FC extends undefined | void + ? LRUCache.MemoOptionsNoContext + : never + ): V + memo(k: K, memoOptions: LRUCache.MemoOptions = {}) { + const memoMethod = this.#memoMethod + if (!memoMethod) { + throw new Error('no memoMethod provided to constructor') + } + const { context, forceRefresh, ...options } = memoOptions + const v = this.get(k, options) + if (!forceRefresh && v !== undefined) return v + const vv = memoMethod(k, v, { + options, + context, + } as LRUCache.MemoizerOptions) + this.set(k, vv, options) + return vv + } + /** * Return a value from the cache. Will update the recency of the cache * entry found.