UNPKG

@avanio/expire-cache

Version:

Typescript/Javascript cache with expiration

462 lines (454 loc) 17.8 kB
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 data - 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 data - 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 logger - The logger to use (optional) * @param logMapping - The log mapping to use (optional). Default is all logging disabled * @param 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 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 logger - The logger to use (optional) * @param logMapping - The log mapping to use (optional). Default is all logging disabled * @param 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 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 - cache key * @param tier - cache tier * @param 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<Promise<T['data'] | undefined> | T['data'] | undefined>; /** * Set cache entry * @param key - cache key * @param tier - cache tier * @param data - cache data * @param 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 tier - cache tier * @param entries - iterable of key, data pairs * @param 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 */ getTier(key: Key): Tiers[number]['tier'] | undefined; /** * Iterate this.cache values and use handleCacheEntry to get data * @param tier */ tierValues(tier: Tiers[number]['tier']): AsyncIterable<Tiers[number]['data']>; 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 - cache entry key * @param tier - cache entry tier * @param data - cache entry data * @param 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 - 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 };