@avanio/expire-cache
Version:
Typescript/Javascript cache with expiration
470 lines (462 loc) • 18.6 kB
text/typescript
import { EventEmitter } from 'events';
import { LogMapInfer, MapLogger, ILoggerLike } from '@avanio/logger-like';
import { CacheEventsMap, ICacheWithEvents } from '@luolapeikko/cache-types';
/**
* Callback type for when entries are expired or cleared
*/
type ICacheOnClearCallback<Payload, Key = string> = (entries: Map<Key, Payload>) => void;
/**
* Synchronous cache interface
* @deprecated Use ```import {type ICache} from '@luolapeikko/cache-types';``` instead.
* @example
* function foo(cache: ICache<string>) {
* const value = cache.get('key');
* cache.set('key', 'value');
* cache.has('key'); // true
* cache.delete('key');
* cache.clear();
* cache.size(); // 0
* }
*/
interface ICache<Payload, Key = string> {
/**
* Gets a value from the cache
* @param key - The key to get the value for
* @returns The cached value or undefined if not found
*/
get(key: Key): Payload | undefined;
/**
* Sets a value in the cache with an optional expiration date
* @param key - The key to set the value for
* @param value - The data to set in the cache
* @param expires - The optional expiration date for the cache entry
*/
set(key: Key, value: Payload, expires?: Date): void;
/**
* Deletes a value from the cache
* @param key - The key to delete the value for
* @returns {boolean} True if the value was deleted, false otherwise
*/
delete(key: Key): boolean;
/**
* Checks if a key exists in the cache
* @param key - The key to check for
* @returns {boolean} True if the key exists in the cache, false otherwise
*/
has(key: Key): boolean;
/**
* Get key expiration Date object or undefined if not found in cache
* @param key - The key to get the expiration for
* @returns {Date | undefined} Date object or undefined if not found in cache
*/
expires(key: Key): Date | undefined;
/**
* Clear all cached values
*/
clear(): void;
/**
* Gets the number of items in the cache
* @returns {number} The number of items in the cache
*/
size(): number;
/**
* Called when a entries are expired, deleted or cleared
*/
onClear(callback: ICacheOnClearCallback<Payload, Key>): void;
/**
* Returns an iterator of key, value pairs for every entry in the cache.
* @example
* for(const [key, value] of cache.entries()) {
* console.log(key, value);
* }
*/
entries(): IterableIterator<[Key, Payload]>;
/**
* Returns an iterator of keys in the cache.
* @example
* for(const key of cache.keys()) {
* console.log(key);
* }
*/
keys(): IterableIterator<Key>;
/**
* Returns an iterator of values in the cache.
* @example
* for(const value of cache.values()) {
* console.log(value);
* }
*/
values(): IterableIterator<Payload>;
}
/**
* Async callback type for when entries are expired or cleared
*/
type IAsyncCacheOnClearCallback<Payload, Key = string> = (entries: Map<Key, Payload>) => Promise<void>;
/**
* Asynchronous cache interface
* @deprecated Use ```import {type IAsyncCache} from '@luolapeikko/cache-types';``` instead.
* @example
* function foo(cache: IAsyncCache<string>) {
* const value = await cache.get('key');
* await cache.set('key', 'value');
* await cache.has('key'); // true
* await cache.delete('key');
* await cache.clear();
* await cache.size(); // 0
* }
*/
interface IAsyncCache<Payload, Key = string> {
/**
* Gets a value from the cache
* @param key - The key to get the value for
* @returns {Promise<Payload | undefined>} Promise of the cached value or undefined if not found
*/
get(key: Key): Promise<Payload | undefined>;
/**
* Sets a value in the cache with an optional expiration date
* @param key - The key to set the value for
* @param value - The data to set in the cache
* @param expires - The optional expiration date for the cache entry
* @returns {Promise<void>} Promise of void
*/
set(key: Key, value: Payload, expires?: Date): Promise<void>;
/**
* Deletes a value from the cache
* @param key - The key to delete the value for
* @returns {Promise<boolean>} Promise of true if the value was deleted, false otherwise
*/
delete(key: Key): Promise<boolean>;
/**
* Checks if a key exists in the cache
* @param key - The key to check for
* @returns {Promise<boolean>} Promise of true if the key exists in the cache, false otherwise
*/
has(key: Key): Promise<boolean>;
/**
* Get key expiration Date object or undefined if not found in cache
* @param key - The key to get the expiration for
* @returns {Promise<Date | undefined>} Promise of Date object or undefined if not found in cache
*/
expires(key: Key): Promise<Date | undefined>;
/**
* Clear all cached values
*/
clear(): Promise<void>;
/**
* Gets the number of items in the cache
* @returns {Promise<number>} Promise of the number of items in the cache
*/
size(): Promise<number>;
/**
* Called when a entries are expired, deleted or cleared
*/
onClear(callback: IAsyncCacheOnClearCallback<Payload, Key>): void;
/**
* Returns an async iterator of key, value pairs for every entry in the cache.
* @example
* for await (const [key, value] of cache.entries()) {
* console.log(key, value);
* }
*/
entries(): AsyncIterableIterator<[Key, Payload]>;
/**
* Async iterator for cache keys
* @example
* for await (const key of cache.keys()) {
* console.log(key);
* }
*/
keys(): AsyncIterableIterator<Key>;
/**
* Async iterator for cache values
* @example
* for await (const value of cache.values()) {
* console.log(value);
* }
*/
values(): AsyncIterableIterator<Payload>;
}
/**
* A type that represents both synchronous and asynchronous cache interfaces.
* This type can be used to create functions that work with both types of cache interfaces.
* @deprecated Use ```import {type IAsyncCache} from '@luolapeikko/cache-types';``` instead.
* @example
* function foo(cache: ICacheOrAsync<string>) {
* const value = await cache.get('key');
* await cache.set('key', 'value');
* await cache.has('key'); // true
* await cache.delete('key');
* await cache.clear();
* await cache.size(); // 0
* }
*/
type ICacheOrAsync<Payload, Key = string> = ICache<Payload, Key> | IAsyncCache<Payload, Key>;
/**
* The default log mapping for the ExpireCache class.
* This maps each method to a log level, allowing you to control the logging output.
* By default, all logs are disabled (None level)
* @example
* const cache = new ExpireCache<string>(console, {
* get: LogLevel.Info,
* set: LogLevel.Debug,
* delete: LogLevel.Warn,
* });
*/
declare const defaultLogMap$2: {
readonly cleanExpired: 0;
readonly clear: 0;
readonly constructor: 0;
readonly delete: 0;
readonly expires: 0;
readonly get: 0;
readonly has: 0;
readonly onExpire: 0;
readonly set: 0;
readonly size: 0;
};
type ExpireCacheLogMapType = LogMapInfer<typeof defaultLogMap$2>;
/**
* ExpireCache class that implements the ICache interface with value expiration and expires on read operations
* @template Payload - The type of the cached data
* @template Key - (optional) The type of the cache key (default is string)
* @since v0.6.5
*/
declare class ExpireCache<Payload, Key = string> extends EventEmitter<CacheEventsMap<Payload, Key>> implements ICacheWithEvents<Payload, Key> {
private readonly cache;
private readonly cacheTtl;
readonly logger: MapLogger<ExpireCacheLogMapType>;
private defaultExpireMs;
/**
* Creates a new instance of the ExpireCache class
* @param {ILoggerLike} logger - The logger to use (optional)
* @param {Partial<ExpireCacheLogMapType>} logMapping - The log mapping to use (optional). Default is all logging disabled
* @param {number} defaultExpireMs - The default expiration time in milliseconds (optional)
*/
constructor(logger?: ILoggerLike, logMapping?: Partial<ExpireCacheLogMapType>, defaultExpireMs?: number);
set(key: Key, data: Payload, expires?: Date): void;
get(key: Key): Payload | undefined;
has(key: Key): boolean;
expires(key: Key): Date | undefined;
delete(key: Key): boolean;
clear(): void;
size(): number;
entries(): IterableIterator<[Key, Payload]>;
keys(): IterableIterator<Key>;
values(): IterableIterator<Payload>;
/**
* Sets the default expiration time in milliseconds
* @param {number} expireMs - The default expiration time in milliseconds
*/
setExpireMs(expireMs: number | undefined): void;
/**
* Cleans expired cache entries
*/
private cleanExpired;
private notifyExpires;
private getExpireDate;
}
/**
* The default log mapping for the ExpireCache class.
* This maps each method to a log level, allowing you to control the logging output.
* By default, all logs are disabled (None level)
* @example
* const cache = new ExpireCache<string>(console, {
* get: LogLevel.Info,
* set: LogLevel.Debug,
* delete: LogLevel.Warn,
* });
*/
declare const defaultLogMap$1: {
cleanExpired: 0;
clear: 0;
constructor: 0;
delete: 0;
expires: 0;
get: 0;
has: 0;
onExpire: 0;
set: 0;
size: 0;
};
type ExpireTimeoutCacheLogMapType = LogMapInfer<typeof defaultLogMap$1>;
/**
* ExpireCache class that implements the ICache interface with value expiration and expires with setTimeout
* @template Payload - The type of the cached data
* @template Key - (optional) The type of the cache key (default is string)
* @since v0.6.5
*/
declare class ExpireTimeoutCache<Payload, Key = string> extends EventEmitter<CacheEventsMap<Payload, Key>> implements ICacheWithEvents<Payload, Key> {
private readonly cache;
private readonly cacheTimeout;
readonly logger: MapLogger<ExpireTimeoutCacheLogMapType>;
private defaultExpireMs;
/**
* Creates a new instance of the ExpireTimeoutCache class
* @param {ILoggerLike} logger - The logger to use (optional)
* @param {Partial<ExpireTimeoutCacheLogMapType>} logMapping - The log mapping to use (optional). Default is all logging disabled
* @param {number} defaultExpireMs - The default expiration time in milliseconds (optional)
*/
constructor(logger?: ILoggerLike, logMapping?: Partial<ExpireTimeoutCacheLogMapType>, defaultExpireMs?: number);
set(key: Key, data: Payload, expires?: Date): void;
get(key: Key): Payload | undefined;
has(key: Key): boolean;
expires(key: Key): Date | undefined;
delete(key: Key): boolean;
clear(): void;
size(): number;
entries(): IterableIterator<[Key, Payload]>;
keys(): IterableIterator<Key>;
values(): IterableIterator<Payload>;
/**
* Set the default expiration time in milliseconds
* @param {number} expireMs - The default expiration time in milliseconds
*/
setExpireMs(expireMs: number | undefined): void;
private clearTimeout;
private notifyExpires;
private getExpireDate;
private handleExpiredCallback;
private handleTimeoutSetup;
}
type TierType<Data, Tier extends string> = {
tier: Tier;
data: Data;
};
/**
* Helper type to build cache tier getter methods
*/
type GetCacheTier<T extends TierType<unknown, string>[], Key> = {
[K in T[number] as `get${Capitalize<K['tier']>}`]: (key: Key, cache: T[number]) => Promise<K['data'] | undefined> | K['data'] | undefined;
};
type TierStatusRecord<T extends TierType<unknown, string>[]> = Record<T[number]['tier'], number>;
type TierStatusInitialRecord<T extends TierType<unknown, string>[]> = Record<T[number]['tier'], 0>;
type TieredCacheStatus<T extends TierType<unknown, string>[]> = {
size: number;
tiers: TierStatusRecord<T>;
};
type MultiTierCacheEvents<T extends TierType<unknown, string>[], Key> = {
update: [status: Readonly<TieredCacheStatus<T>>];
set: [Iterable<Key>];
delete: [Iterable<Key>];
clear: [];
};
declare const defaultLogMap: {
readonly clear: 0;
readonly clearTimeoutKey: 0;
readonly constructor: 0;
readonly delete: 0;
readonly get: 0;
readonly has: 0;
readonly runTimeout: 0;
readonly set: 0;
readonly setTimeout: 0;
readonly size: 0;
};
type TieredCacheLogMapType = LogMapInfer<typeof defaultLogMap>;
/**
* Multi tier cache with timeout support to change tier based on timeout
* @since v0.6.0
*/
declare abstract class TieredCache<Tiers extends TierType<unknown, string>[], TimeoutEnum extends number, Key> extends EventEmitter<MultiTierCacheEvents<Tiers, Key>> {
abstract readonly cacheName: string;
protected readonly cache: Map<Key, Tiers[number]>;
private readonly cacheTimeout;
readonly logger: MapLogger<TieredCacheLogMapType>;
private statusData;
constructor(logger?: ILoggerLike, logMapping?: Partial<ExpireCacheLogMapType>);
/**
* Get cache entry from cache
* @param {Key} key - cache key
* @param {T['tier']} tier - cache tier
* @param {TimeoutEnum} [timeout] - optional update to new timeout for cache entry (if not provided, default timeout for tier will be used)
* @returns - promise that resolves when cache entry is set
*/
get<T extends Tiers[number]>(key: Key, tier: T['tier'], timeout?: TimeoutEnum): Promise<T['data'] | undefined>;
/**
* Set cache entry
* @param {Key} key - cache key
* @param {T['tier']} tier - cache tier
* @param {T['data']} data - cache data
* @param {TimeoutEnum} [timeout] - optional timeout for cache entry. Else timeout will be checked from handleTimeoutValue or default timeout for tier.
*/
set<T extends Tiers[number]>(key: Key, tier: T['tier'], data: T['data'], timeout?: TimeoutEnum): Promise<void>;
/**
* Set multiple cache entries
* @param {T['tier']} tier - cache tier
* @param {Iterable<[Key, T['data']]>} entries - iterable of key, data pairs
* @param {TimeoutEnum} [timeout] - optional timeout for cache entry. Else timeout will be checked from handleTimeoutValue or default timeout for tier.
*/
setEntries<T extends Tiers[number]>(tier: T['tier'], entries: Iterable<[Key, T['data']]>, timeout?: TimeoutEnum): Promise<void>;
/**
* Get tier type for current key
* @param {Key} key - cache key
* @returns {Tiers[number]['tier'] | undefined} The tier type or undefined if not found
*/
getTier(key: Key): Tiers[number]['tier'] | undefined;
/**
* Iterate this.cache values and use handleCacheEntry to get data
* @param {Tiers[number]['tier']} tier - cache tier to get values for
* @returns {AsyncIterable<Tiers[number]['data']>} Async iterable of cache values
*/
tierValues(tier: Tiers[number]['tier']): AsyncIterable<Tiers[number]['data']>;
/**
* Iterate this.cache entries and use handleCacheEntry to get data
* @param {Tiers[number]['tier']} tier - cache tier to get entries for
* @returns {AsyncIterable<[Key, Tiers[number]['data']]>} Async iterable of key-value pairs
*/
tierEntries(tier: Tiers[number]['tier']): AsyncIterable<[Key, Tiers[number]['data']]>;
keys(): Iterable<Key>;
has(key: Key): boolean;
size(): number;
clear(): void;
delete(key: Key): boolean;
deleteKeys(keys: Iterable<Key>): number;
status(): Readonly<TieredCacheStatus<Tiers>>;
protected buildStatus(rebuild: boolean): Readonly<TieredCacheStatus<Tiers>>;
/**
* Internal helper to set cache entry and set timeout
* @param {Key} key - cache entry key
* @param {T['tier']} tier - cache entry tier
* @param {T['data']} data - cache entry data
* @param {TimeoutEnum} [timeout] - timeout value, optional
*/
protected handleSetValue<T extends Tiers[number]>(key: Key, tier: T['tier'], data: T['data'], timeout?: TimeoutEnum): Promise<void>;
/**
* Internal helper to delete cache entry and cancel its timeout
* @param {Key} key - cache entry key
* @returns true if entry was deleted, false if not found
*/
protected handleDeleteValue(key: Key): boolean;
private logCacheName;
private setTimeout;
private clearTimeoutKey;
private clearAllTimeouts;
private runTimeout;
protected abstract handleCacheEntry<T extends Tiers[number]>(key: Key, tier: T['tier'], cache: Tiers[number] | undefined): Promise<T['data'] | undefined> | T['data'] | undefined;
/**
* this return new timeout value for cached entry based on tier (or undefined if tier doesn't have timeout)
* @example
* // if tier not care about data and just want to return default timeouts
* protected handleTimeoutValue<T extends DateCacheTiers[number]>(key: Key, tier: T['type'], _data: T['data']) {
* return this.handleTierDefaultTimeout(tier); // else make logic based on data (i.e. newer data should have longer timeout)
* }
*/
protected abstract handleTimeoutValue<T extends Tiers[number]>(key: Key, tier: T['tier'], data: T['data']): Promise<number | undefined> | number | undefined;
/**
* this handle should change value of cache entry to another shape and return next tier default timeout (can also delete entry)
*/
protected abstract handleTierTimeout(key: Key): Promise<number | undefined> | number | undefined;
/**
* If request didn't specify timeout, this method should return default timeout for tier or undefined if tier doesn't have timeout.
*/
protected abstract handleTierDefaultTimeout(type: Tiers[number]['tier']): TimeoutEnum | undefined;
/**
* Build initial status data for cache
* @example
* protected getInitialStatusData(): Readonly<TierStatusInitialRecord<DateCacheTiers>> {
* return {model: 0, object: 0, stringValue: 0} as const;
* }
*/
protected abstract getInitialStatusData(): Readonly<TierStatusInitialRecord<Tiers>>;
}
export { ExpireCache, type ExpireCacheLogMapType, ExpireTimeoutCache, type ExpireTimeoutCacheLogMapType, type GetCacheTier, type IAsyncCache, type IAsyncCacheOnClearCallback, type ICache, type ICacheOnClearCallback, type ICacheOrAsync, type TierStatusInitialRecord, type TierStatusRecord, type TierType, TieredCache, type TieredCacheLogMapType, type TieredCacheStatus };