type CacheKey = string | number | symbol; const isKey = (key: unknown): key is CacheKey => { return ( typeof key === "string" || typeof key === "number" || typeof key === "symbol" ); }; type Props = { limit?: number; ttl?: number }; type CacheIndex = { [key: string]: V; [key: number]: V; [key: symbol]: V; }; type CacheValue = { value: V; expiresAt?: number }; class AlliorCacheCore { private readonly _map: Map>; private readonly _limit?: number; private readonly _ttl?: number; constructor(props?: Props) { this._map = new Map>(); this._limit = props?.limit; this._ttl = props?.ttl; return new Proxy(this, { set: ( target: AlliorCacheCore, key: string | symbol, value: V, ): boolean => { if (isKey(key)) { return target.set(key as K, value); } return false; }, get: ( target: AlliorCacheCore, key: string | symbol, receiver: unknown, ): unknown => { if (isKey(key)) { if (key in target) { return Reflect.get(target, key, receiver); } return target.get(key as K); } return undefined; }, has: (target: AlliorCacheCore, key: string | symbol): boolean => { if (isKey(key)) { return key in target || target.has(key as K); } return false; }, } as ProxyHandler>) as AlliorCacheCore & CacheIndex & Record; } public keys() { return this._map.keys(); } public *values(): IterableIterator { const now = Date.now(); for (const [key, entry] of this._map.entries()) { if (entry.expiresAt && entry.expiresAt < now) { this._map.delete(key); continue; } yield entry.value; } } public valuesWithMeta() { return this._map.values(); } public entries() { return this._map.entries(); } public clear(): void { this._map.clear(); } public has(key: K): boolean { const entry = this._map.get(key); if (!entry) { return false; } if (entry.expiresAt && entry.expiresAt < Date.now()) { this._map.delete(key); return false; } return true; } public set(key: K, value: V, ttl?: number): true { const effectiveTtl = ttl ?? this._ttl; const entry: CacheValue = { value }; if (effectiveTtl) { entry.expiresAt = Date.now() + effectiveTtl; } this._map.set(key, entry); this._handleLimit(); return true; } public get(key: K): V | undefined { const entry = this._map.get(key); if (!entry) { return undefined; } if (entry.expiresAt && entry.expiresAt < Date.now()) { this._map.delete(key); return undefined; } return entry.value; } public delete(key: K): boolean { return this._map.delete(key); } private _handleLimit(): void { if (!this._limit || this._limit === 0) { return; } if (this._map.size > this._limit) { const firstKey = this._map.keys().next().value; if (firstKey !== undefined) { this._map.delete(firstKey); } } } public json(): Record { const now = Date.now(); const result: Record = {}; for (const [key, entry] of this._map.entries()) { if (entry.expiresAt && entry.expiresAt < now) { this._map.delete(key); continue; } result[key.toString()] = entry.value; } return result; } } export type AlliorCache< K extends CacheKey = CacheKey, V = unknown, > = AlliorCacheCore & CacheIndex & Record; export const AlliorCache = AlliorCacheCore as unknown as { new ( props?: Props, ): AlliorCache; readonly prototype: AlliorCacheCore; };