@httpx/lru
Version:
LruCache implementations with O(1) complexity
482 lines (474 loc) • 16.9 kB
TypeScript
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;
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 it will
* create a new entry with the provided value and returns it.
*
* In case of a new entry:
* - it will be marked as most recently used.
* - an eviction will be triggered if the maximum capacity is reached
*
* @example
* ```typescript
* const lru = new LruCache({ maxSize: 2 });
* lru.set('key1', 'value1');
* lru.getOrSet('key1', () => 'value2'); // 👈 'value1' (entry exists)
* lru.getOrSet('key2', () => 'value2'); // 👈 'value2' (new entry)
* lru.has('key2'); // 👈 true (it was added)
* lru.get('key1'); // 👈 'value1'
*
* // Will trigger an eviction as capacity (2) is reached.
* lru.getOrSet('key3', () => '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.
*
* ```typescript
* const lru = new LruCache({ maxSize: 2 });
* lru.set('key1', 'value1');
* lru.set('key2', 'value2');
* lru.set('key3', 'value3');
* // trigger a get to move key2 to the head
* lru.get('key2');
* 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
* ['key2', 'value2'], // Most recently used
* ]);
* ```
*/
[Symbol.iterator]: () => IterableIterator<[TKey, TValue]>;
}
type LruCacheParams<TValue = unknown, 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
*
* @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]>;
}
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]>;
}
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 and returns it.
*
* In case of a new entry:
* - it will be marked as most recently used.
* - an eviction will be triggered if the maximum capacity is reached
* or the item has expired.
*
* @example
* ```typescript
* const lru = new TimeLruCache({ maxSize: 2 });
* lru.set('key1', 'value1');
* lru.getOrSet('key1', () => 'value2'); // 👈 'value1' (entry exists)
* lru.getOrSet('key2', () => 'value2'); // 👈 'value2' (new entry)
* lru.has('key2'); // 👈 true (it was added)
* lru.get('key1'); // 👈 'value1'
*
* // Will trigger an eviction as capacity (2) is reached.
* lru.getOrSet('key3', () => 'value3');
*
* lru.get('key1'); // 👈 undefined (first entry was evicted)
* ```
*/
getOrSet: <T extends TValue>(key: TKey, valueOrFn: T | (() => T), 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.
*/
[Symbol.iterator]: () => IterableIterator<[TKey, TValue]>;
}
type TimeLruCacheParams<TValue = unknown, 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
*
* @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;
/**
* Iterate over the cache from the least recently used to the most recently used.
*
* ```typescript
* const lru = new LruCache({ maxSize: 2 });
* lru.set('key1', 'value1');
* lru.set('key2', 'value2');
* lru.set('key3', 'value3');
* // trigger a get to move key2 to the head
* lru.get('key2');
* 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
* ['key2', 'value2'], // Most recently used
* ]);
* ```
*/
[Symbol.iterator](): IterableIterator<[TKey, TValue]>;
}
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]>;
}
export { type BaseCacheKeyTypes, type ILruCache, type ITimeLruCache, LruCache, type LruCacheHasOptions, type LruCacheParams, NullLruCache, NullTimeLruCache, type SupportedCacheValues, TimeLruCache, type TimeLruCacheParams };