@httpx/lru
Version:
LruCache implementations with O(1) complexity
595 lines • 21.3 kB
text/typescript
//#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.cts.map