interface CacheEntry<T> {
value: T;
timestamp: number;
}
export interface LRUCacheOptions {
maxSize?: number;
ttl?: number; // Time to live in milliseconds
}
export class LRUCache<T> {
private cache: Map<string, CacheEntry<T>>;
private maxSize: number;
private ttl: number;
constructor(options: LRUCacheOptions = {}) {
this.cache = new Map();
this.maxSize = options.maxSize ?? 100;
this.ttl = options.ttl ?? 300000; // Default 5 minutes
}
get(key: string): T | null {
const entry = this.cache.get(key);
if (!entry) {
return null;
}
// Check if entry has expired
if (this.isExpired(entry)) {
this.cache.delete(key);
return null;
}
// Move to end (most recently used)
this.cache.delete(key);
this.cache.set(key, entry);
return entry.value;
}
set(key: string, value: T): void {
// Remove if already exists to update position
if (this.cache.has(key)) {
this.cache.delete(key);
}
// Check size limit
if (this.cache.size >= this.maxSize) {
// Remove least recently used (first item)
const firstKey = this.cache.keys().next().value;
if (firstKey) {
this.cache.delete(firstKey);
}
}
this.cache.set(key, {
value,
timestamp: Date.now(),
});
}
has(key: string): boolean {
const entry = this.cache.get(key);
if (!entry) {
return false;
}
if (this.isExpired(entry)) {
this.cache.delete(key);
return false;
}
return true;
}
delete(key: string): boolean {
return this.cache.delete(key);
}
clear(): void {
this.cache.clear();
}
size(): number {
return this.cache.size;
}
keys(): IterableIterator<string> {
return this.cache.keys();
}
private isExpired(entry: CacheEntry<T>): boolean {
return Date.now() - entry.timestamp > this.ttl;
}
// Clean up expired entries
evictExpired(): void {
for (const [key, entry] of this.cache.entries()) {
if (this.isExpired(entry)) {
this.cache.delete(key);
}
}
}
}