UNPKG

@httpx/lru

Version:

LruCache implementations with O(1) complexity

595 lines 21.3 kB
//#region src/types.d.ts type BaseCacheKeyTypes = string | number; type Milliseconds = number; interface LruCacheHasOptions { /** * If true, the item will be marked as recently used. * @default option touchOnHas in the constructor */ touch?: boolean; } type BaseCacheValueTypes = string | number | bigint | boolean | null | unknown[] | (object & { then?: void; }) | Record<string | number | symbol, unknown>; type SupportedCacheValues = Readonly<BaseCacheValueTypes> | BaseCacheValueTypes; //#endregion //#region src/lru-cache.interface.d.ts interface ILruCache<TValue extends SupportedCacheValues = SupportedCacheValues, TKey extends BaseCacheKeyTypes = string> { /** * Return the params */ readonly params: { maxSize: number; }; /** * Return the current number of items in the cache */ readonly size: number; /** * Clear all entries from the cache and return the number of deleted items */ clear: () => number; /** * Checks whether an entry exist. * * The item will be marked as recently used only if either * * - the global cache `touchOnHas` option is true (default: false) * - or the `touch` option is true (default false) * * @example * ```typescript * import { LruCache } from '@httpx/lru'; * * const lru = new LruCache({ * maxSize: 1, * // 👇 Optional, default to false * touchOnHas: false, * }); * * lru.set('key0', 'value0'); * // 👇 Will evict key0 as maxSize is 1 * lru.set('key1', 'value1'); * * lru.has('key0'); // 👈 false * lru.has('key1'); // 👈 true (item is present) * lru.has('key1', { * // 👇 Optional, default to global touchOnHas * touch: false * }); // 👈 true (item is present) * ``` */ has: (key: TKey, options?: LruCacheHasOptions) => boolean; /** * Get an item from the cache or return undefined if it doesn't exist. * Item will be marked as most recently used. * * ```typescript * import { LruCache } from '@httpx/lru'; * * const lru = new LruCache({ maxSize: 1 }); * * lru.set('key0', 'value0'); * lru.get('key0'); // 👈 'value0' * lru.get('key1'); // 👈 undefined * ``` */ get: (key: TKey) => TValue | undefined; /** * Add a new entry to the cache and overwrite value if the key was already * present.It will move the item as the most recently used. * * Note that eviction will happen if maximum capacity is reached.. * * ```typescript * import { LruCache } from '@httpx/lru'; * * const lru = new LruCache({ * maxSize: 1, * onEviction: () => { console.log('evicted') } * }); * * lru.set('key0', 'value0'); // 👈 true (new key, size increase) * lru.set('key0', 'valuex'); // 👈 false (existing key, no size increase) * * // 👇 Will evict key0 as maxSize is 1 and trigger onEviction * lru.set('key2', 'value2'); // 👈 true (existing key, no size increase) * ``` */ set: (key: TKey, value: TValue) => boolean; /** * Get an item from the cache, if the item doesn't exist or has expired * it will create a new entry with the provided value or function and returns it. * * In case of a new entry (key either doesn't exist or has expired): * - the provided value or the result of the function will be used as value. * - it will be marked as most recently used. * - an eviction will be triggered if the maximum capacity is reached * * In case the item exists and hasn't expired: * - the existing value will be returned. * - it will be marked as most recently used. * * @example * ```typescript * const lru = new LruCache({ maxSize: 2 }); * * // The key exists * lru.set('key1', 'value1'); * lru.getOrSet('key1', () => 'value2'); // 👈 returns 'value1' (entry exists) * * // The key doesn't exist, a new entry will be created from the function return value * lru.getOrSet('key2', () => 'value2'); // 👈 returns 'value2' * lru.has('key2'); // 👈 true (it was added) * lru.get('key1'); // 👈 'value1' * * // Will trigger an eviction as maxSize capacity (2) is reached. * lru.getOrSet('key3', () => 'value3'); // 👈 returns 'value3' * * lru.get('key1'); // 👈 undefined (first entry was evicted) * ``` */ getOrSet: <T extends TValue>(key: TKey, valueOrFn: T | (() => T)) => T; /** * Get an item without marking it as recently used or undefined if item doesn't exist. */ peek: (key: TKey) => TValue | undefined; /** * Delete an item from the cache and return a boolean indicating * if the item was actually deleted in case it exist. */ delete: (key: TKey) => boolean; /** * Iterate over the cache from the least recently used to the most recently used. * * @example * ```typescript * import { LruCache } from '@httpx/lru'; * * const lru = new LruCache({ maxSize: 2 }); * * // 👇 Fill the cache with 3 entries * lru.set('key1', 'value1'); * lru.set('key2', 'value2'); * lru.set('key3', 'value3'); // 👈 Will evict key1 as maxSize is 2 * * lru.get('key2'); // 👈 Trigger a get to move key2 to the head * * const results = []; * * // 🖖 Iterate over the cache entries * for (const [key, value] of lru) { * results.push([key, value]); * } * * expect(results).toStrictEqual([ * ['key3', 'value3'], // 👈 Least recently used first * ['key2', 'value2'], // 👈 Most recently used last * ]); * ``` */ [Symbol.iterator]: () => IterableIterator<[TKey, TValue]>; } //#endregion //#region src/lru-cache.d.ts type LruCacheParams<TValue extends SupportedCacheValues = SupportedCacheValues, TKey extends BaseCacheKeyTypes = string> = { /** * The maximum number of items that the cache can hold. */ maxSize: number; /** * If true, the item will be marked as recently used when calling has. * @default false */ touchOnHas?: boolean; /** * Callback that will be called before an item is evicted from the cache. * Useful for side effects or for items like object URLs that need explicit cleanup (revokeObjectURL). */ onEviction?: (key: TKey, value: TValue) => void; }; /** * Double linked list based lru cache that supports get in O(1) */ declare class LruCache<TValue extends SupportedCacheValues = SupportedCacheValues, TKey extends BaseCacheKeyTypes = string> implements ILruCache<TValue, TKey> { #private; /** * Create a new LruCache instance. * * 👉 As an alternative to constructor, consider using the helper * `getOrCreateLruCache` to ensure only one instance is created. * * @example * ```typescript * import { LruCache } from '@httpx/lru'; * * const lru = new LruCache({ maxSize: 1000 }); * * lru.set('🦄', ['cool', 'stuff']); * if (lru.has('🦄')) {; * console.log(lru.get('🦄')); * // ['cool', 'stuff'] * } * console.log(lru.size); // 1 * lru.delete('🦄'); * console.log(lru.size); // 0 * lru.clear(); * ``` */ constructor(params: LruCacheParams<TValue, TKey>); get params(): ILruCache['params']; /** * Return the current number of items in the cache */ get size(): number; clear(): number; has(key: TKey, options?: LruCacheHasOptions): boolean; set(key: TKey, value: TValue): boolean; get(key: TKey): TValue | undefined; getOrSet<T extends TValue>(key: TKey, valueOrFn: T | (() => T)): T; peek(key: TKey): TValue | undefined; delete(key: TKey): boolean; [Symbol.iterator](): IterableIterator<[TKey, TValue]>; } //#endregion //#region src/helpers/get-or-create-lru-cache.d.ts type LruCacheSingleInstanceName = string; declare global { var __httpx_lru_cache_instances: // eslint-disable-next-line @typescript-eslint/no-explicit-any Map<LruCacheSingleInstanceName, LruCache<any, any>> | undefined; } type GetOrCreateLruCacheOptions = { onCreate?: <TValue extends SupportedCacheValues = SupportedCacheValues, TKey extends BaseCacheKeyTypes = string>(name: LruCacheSingleInstanceName, params: LruCacheParams<TValue, TKey>) => void; }; /** * Creates or retrieves a singleton LruCache instance by name * ensuring that only one instance exists for each unique name. * * This helper function relies on globalThis to store and retrieve * the instance. * * @example * ```typescript * import { getOrCreateLruCache } from '@httpx/lru'; * * const lru = getOrCreateLruCache('main-cache', { maxSize: 500 }); * ``` * * @warning The same name must always be used with consistent TValue and TKey types. * Calling this function with different type parameters for the same name will cause * type safety violations and unexpected behavior. */ declare const getOrCreateLruCache: <TValue extends SupportedCacheValues = SupportedCacheValues, TKey extends BaseCacheKeyTypes = string>(name: LruCacheSingleInstanceName, lruCacheParams: LruCacheParams<TValue, TKey>, options?: GetOrCreateLruCacheOptions) => LruCache<TValue, TKey>; //#endregion //#region src/time-lru-cache.interface.d.ts interface ITimeLruCache<TValue extends SupportedCacheValues = SupportedCacheValues, TKey extends BaseCacheKeyTypes = string> extends ILruCache<TValue, TKey> { /** * Return the params */ readonly params: { maxSize: number; defaultTTL: Milliseconds; }; /** * Checks whether an entry exist and hasn't expired. * * If the entry exists but has expired, it will be removed automatically * and trigger the `onEviction` callback if present. * * The item will be marked as recently used only if either * * - the global cache `touchOnHas` option is true (default: false) * - or the `touch` option is true (default false) * * @example * ```typescript * import { TimeLruCache } from '@httpx/lru'; * * const oneSecondInMillis = 1000; * * const lru = new TimeLruCache({ * maxSize: 1, * defaultTTL: oneSecondInMillis, * // 👇 Optional, default to noop * onEviction: () => { console.log('evicted') } * // 👇 Optional, default to false * touchOnHas: false, * }); * * lru.set('key0', 'value0', 2 * oneSecondInMillis); * * // 👇 Will evict key0 as maxSize is 1 and trigger onEviction * lru.set('key1', 'value1', 2 * oneSecondInMillis); * * lru.has('key0'); // 👈 false (item does not exist) * lru.has('key1'); // 👈 true (item is present and is not expired) * * lru.has('key1', { * // 👇 Optional, default to global touchOnHas * touch: false * }); // 👈 true (item is present) * * const value = lru.get('key1'); // 👈 'value1' (item is present and is not expired) * * // 🕛 wait 3 seconds, time for the item to expire * * lru.has('key1'); // 👈 false (item is present but expired - 👋 onEviction will be called) * * ``` */ has: (key: TKey, options?: LruCacheHasOptions) => boolean; /** * Get an item from the cache or return undefined if it doesn't exist or * has expired. * * Item will be marked as most recently used. * * ```typescript * import { TimeLruCache } from '@httpx/lru'; * * const lru = new TimeLruCache({ * maxSize: 1, * defaultTTL: 30_000, // 30 seconds * }); * * lru.set('key0', 'value0'); * lru.get('key0'); // 👈 'value0' * lru.get('key1'); // 👈 undefined * ``` */ get: (key: TKey) => TValue | undefined; /** * Add a new entry to the cache and overwrite value if the key was already * present. It will move the item as the most recently used. * * If maximum capacity is reached and eviction will be done and the * onEviction callback will be triggered. * * ```typescript * import { TimeLruCache } from '@httpx/lru'; * * const lru = new TimeLruCache({ * maxSize: 1, * defaultTTL: 30_000, // 30 seconds in millis * onEviction: () => { console.log('evicted') } * }); * * lru.set('key0', 'value0', 1000); // 👈 true (new key, size increase) * lru.set('key0', 'valuex', 1000); // 👈 false (existing key, no size increase) * lru.get('key0'); // 👈 'valuex' * * // 👇 Will evict key0 as maximum capacity is reached * lru.set('key1', 'value1', 1000); * ``` */ set: (key: TKey, value: TValue, ttl?: Milliseconds) => boolean; /** * Get an item from the cache, if the item doesn't exist or has expired * it will create a new entry with the provided value or function and returns it. * * In case of a new entry (key either doesn't exist or has expired): * - the provided value or the result of the function will be used as value. * - it will be marked as most recently used. * - an eviction will be triggered if the maximum capacity is reached * * In case the item exists and hasn't expired: * - the existing value will be returned. * - it will be marked as most recently used. * * @example * ```typescript * const lru = new TimeLruCache({ maxSize: 2, defaultTTL: 30_000 }); * * // The key exists and hasn't expired * lru.set('key1', 'value1'); * lru.getOrSet('key1', () => 'value2'); // 👈 returns 'value1' (entry exists) * * // The key doesn't exist, a new entry will be created from the function return value * lru.getOrSet('key2', () => 'value2', 2_000); // 👈 returns 'value2' * lru.has('key2'); // 👈 true (it was added) * lru.get('key1'); // 👈 'value1' * * // Will trigger an eviction as maxSize capacity (2) is reached. * lru.getOrSet('key3', () => 'value3'); // 👈 returns 'value3' * * lru.get('key1'); // 👈 undefined (first entry was evicted) * ``` */ getOrSet: <T extends TValue>(key: TKey, /** * Value or function that will return the value to set in case the * key doesn't exist or has expired. */ valueOrFn: T | (() => T), /** * Optional time to live for this specific item in milliseconds. * If not provided, the cache defaultTTL will be used. */ ttl?: Milliseconds) => T; /** * Get an item without marking it as recently used. Will return undefined if * the item doesn't exist or has expired (ttl). * * Note that peek doesn't evict items that have expired, but will * return undefined if they have. */ peek: (key: TKey) => TValue | undefined; /** * Delete an item from the cache and return a boolean indicating * if the item was actually deleted in case it exist. */ delete: (key: TKey) => boolean; /** * Iterate over the cache from the least recently used to the most recently used. * * @example * import { TimeLruCache } from '@httpx/lru'; * * const lru = new TimeLruCache({ maxSize: 2 }); * * // 👇 Fill the cache with 3 entries * lru.set('key1', 'value1'); * lru.set('key2', 'value2'); * lru.set('key3', 'value3'); // 👈 Will evict key1 as maxSize is 2 * * lru.get('key2'); // 👈 Trigger a get to move key2 to the head * * const results = []; * * // 🖖 Iterate over the cache entries * for (const [key, value] of lru) { * results.push([key, value]); * } * * expect(results).toStrictEqual([ * ['key3', 'value3'], // 👈 Least recently used first * ['key2', 'value2'], // 👈 Most recently used last * ]); * ``` */ [Symbol.iterator]: () => IterableIterator<[TKey, TValue]>; } //#endregion //#region src/time-lru-cache.d.ts type TimeLruCacheParams<TValue extends SupportedCacheValues = SupportedCacheValues, TKey extends BaseCacheKeyTypes = string> = LruCacheParams<TValue, TKey> & { /** * Default time to live for each entry in milliseconds */ defaultTTL: Milliseconds; }; /** * Double linked list based lru cache that supports get in O(1) and time to live for each entry */ declare class TimeLruCache<TValue extends SupportedCacheValues = SupportedCacheValues, TKey extends BaseCacheKeyTypes = string> implements ITimeLruCache<TValue, TKey> { #private; /** * Create a new LruCache instance * * 👉 As an alternative to constructor, consider using the helper * `getOrCreateTimeLruCache` to ensure only one instance is created. * * @example * ```typescript * import { TimeLruCache } from '@httpx/lru'; * * const THIRTY_SECONDS_IN_MILLIS = 30_000 * * const lru = new TimeLruCache({ maxSize: 1000, defaultTTL: THIRTY_SECONDS_IN_MILLIS}); * lru.set('🦄', ['cool', 'stuff'], THIRTY_SECONDS_IN_MILLIS); * if (lru.has('🦄')) {; * console.log(lru.get('🦄')); * // ['cool', 'stuff'] * } * console.log(lru.size); // 1 * lru.delete('🦄'); * console.log(lru.size); // 0 * lru.clear(); * ``` */ constructor(params: TimeLruCacheParams<TValue, TKey>); /** * Return the current size of the cache */ get params(): ITimeLruCache['params']; /** * Return the current number of entries in the cache */ get size(): number; clear(): number; has(key: TKey, options?: LruCacheHasOptions): boolean; set(key: TKey, value: TValue, ttl?: Milliseconds): boolean; get(key: TKey): TValue | undefined; getOrSet<T extends TValue>(key: TKey, valueOrFn: T | (() => T), ttl?: Milliseconds): T; peek(key: TKey): TValue | undefined; delete(key: TKey): boolean; [Symbol.iterator](): IterableIterator<[TKey, TValue]>; } //#endregion //#region src/helpers/get-or-create-time-lru-cache.d.ts type TimeLruCacheSingleInstanceName = string; declare global { var __httpx_time_lru_cache_instances: // eslint-disable-next-line @typescript-eslint/no-explicit-any Map<TimeLruCacheSingleInstanceName, TimeLruCache<any, any>> | undefined; } type GetOrCreateTimeLruCacheOptions = { onCreate?: <TValue extends SupportedCacheValues = SupportedCacheValues, TKey extends BaseCacheKeyTypes = string>(name: TimeLruCacheSingleInstanceName, params: TimeLruCacheParams<TValue, TKey>) => void; }; /** * Creates or retrieves a singleton TimeLruCache instance by name * ensuring that only one instance exists for each unique name. * * This helper function relies on globalThis to store and retrieve * the instance. * * @example * ```typescript * import { getOrCreateTimeLruCache } from '@httpx/lru'; * * const ttlLru = getOrCreateTimeLruCache('main-cache', { maxSize: 500, defaultTTL: 60000 }); * ``` * * @warning The same name must always be used with consistent TValue and TKey types. * Calling this function with different type parameters for the same name will cause * type safety violations and unexpected behavior. */ declare const getOrCreateTimeLruCache: <TValue extends SupportedCacheValues = SupportedCacheValues, TKey extends BaseCacheKeyTypes = string>(name: TimeLruCacheSingleInstanceName, lruCacheParams: TimeLruCacheParams<TValue, TKey>, options?: GetOrCreateTimeLruCacheOptions) => TimeLruCache<TValue, TKey>; //#endregion //#region src/null-lru-cache.d.ts declare class NullLruCache<TValue extends SupportedCacheValues = SupportedCacheValues, TKey extends BaseCacheKeyTypes = string> implements ILruCache<TValue, TKey> { /** * Create a new NullLruCache (does cache nothing) * * @example * ```typescript * import { NullLruCache } from '@httpx/lru'; * * const lru = new NullLruCache({ maxSize: 1000 }); * ``` */ constructor(_params: LruCacheParams<TValue, TKey>); readonly size = 0; readonly params: { maxSize: number; }; clear(): number; has(_key: TKey, _options?: LruCacheHasOptions): boolean; set(_key: TKey, _value: TValue): boolean; get(_key: TKey): undefined; getOrSet<T extends TValue>(_key: TKey, valueOrFn: T | (() => T)): T; peek(_key: TKey): undefined; delete(_key: TKey): boolean; [Symbol.iterator](): IterableIterator<[TKey, TValue]>; } //#endregion //#region src/null-time-lru-cache.d.ts declare class NullTimeLruCache<TValue extends SupportedCacheValues = SupportedCacheValues, TKey extends BaseCacheKeyTypes = string> implements ITimeLruCache<TValue, TKey> { /** * Create a new NullTimeLruCache (does cache nothing) * * @example * ```typescript * import { NullTimeLruCache } from '@httpx/lru'; * * const lru = new NullTimeLruCache({ maxSize: 1000 }); * ``` */ constructor(_params: TimeLruCacheParams<TValue, TKey>); readonly size = 0; readonly params: { maxSize: number; defaultTTL: number; }; clear(): number; has(_key: TKey, _options?: LruCacheHasOptions): boolean; set(_key: TKey, _value: TValue, _ttl?: Milliseconds): boolean; get(_key: TKey): undefined; getOrSet<T = TValue>(_key: TKey, valueOrFn: T | (() => T), _ttl?: Milliseconds): T; peek(_key: TKey): undefined; delete(_key: TKey): boolean; [Symbol.iterator](): IterableIterator<[TKey, TValue]>; } //#endregion export { type BaseCacheKeyTypes, type GetOrCreateLruCacheOptions, type GetOrCreateTimeLruCacheOptions, type ILruCache, type ITimeLruCache, LruCache, type LruCacheHasOptions, type LruCacheParams, NullLruCache, NullTimeLruCache, type SupportedCacheValues, TimeLruCache, type TimeLruCacheParams, getOrCreateLruCache, getOrCreateTimeLruCache }; //# sourceMappingURL=index.d.ts.map