/**
* Simple in-memory cache with TTL support
*
* @author nich
* @website https://x.com/nichxbt
* @github https://github.com/nirholas
* @license Apache-2.0
*/
interface CacheEntry<T> {
value: T;
expiresAt: number;
}
export class Cache<T = unknown> {
private cache = new Map<string, CacheEntry<T>>();
private defaultTtlMs: number;
constructor(defaultTtlMs: number = 60_000) {
this.defaultTtlMs = defaultTtlMs;
}
/**
* Get a value from cache
*/
get(key: string): T | undefined {
const entry = this.cache.get(key);
if (!entry) {
return undefined;
}
if (Date.now() > entry.expiresAt) {
this.cache.delete(key);
return undefined;
}
return entry.value;
}
/**
* Set a value in cache with optional TTL
*/
set(key: string, value: T, ttlMs?: number): void {
const expiresAt = Date.now() + (ttlMs ?? this.defaultTtlMs);
this.cache.set(key, { value, expiresAt });
}
/**
* Check if key exists and is not expired
*/
has(key: string): boolean {
return this.get(key) !== undefined;
}
/**
* Delete a key from cache
*/
delete(key: string): boolean {
return this.cache.delete(key);
}
/**
* Clear all entries from cache
*/
clear(): void {
this.cache.clear();
}
/**
* Get cache size (including expired entries)
*/
get size(): number {
return this.cache.size;
}
/**
* Clean up expired entries
*/
cleanup(): number {
const now = Date.now();
let cleaned = 0;
for (const [key, entry] of this.cache) {
if (now > entry.expiresAt) {
this.cache.delete(key);
cleaned++;
}
}
return cleaned;
}
/**
* Get or set a value using a factory function
*/
async getOrSet(
key: string,
factory: () => Promise<T>,
ttlMs?: number
): Promise<T> {
const existing = this.get(key);
if (existing !== undefined) {
return existing;
}
const value = await factory();
this.set(key, value, ttlMs);
return value;
}
}
// Pre-configured caches for common use cases
export const apiCache = new Cache(5 * 60 * 1000); // 5 minutes for API responses
export const priceCache = new Cache(30 * 1000); // 30 seconds for prices
export const marketDataCache = new Cache(60 * 1000); // 1 minute for market data
/**
* Decorator-like function for caching async function results
*/
export function withCache<T>(
cache: Cache<T>,
keyFn: (...args: unknown[]) => string,
ttlMs?: number
) {
return function (
fn: (...args: unknown[]) => Promise<T>
): (...args: unknown[]) => Promise<T> {
return async (...args: unknown[]): Promise<T> => {
const key = keyFn(...args);
return cache.getOrSet(key, () => fn(...args), ttlMs);
};
};
}