UNPKG

devflow-ai

Version:

Enterprise-grade AI agent orchestration with swarm management UI dashboard

305 lines (250 loc) 6.03 kB
/** * TTL Map Implementation * Map with time-to-live for automatic entry expiration */ interface TTLItem<T> { value: T; expiry: number; createdAt: number; accessCount: number; lastAccessedAt: number; } export interface TTLMapOptions { defaultTTL?: number; cleanupInterval?: number; maxSize?: number; onExpire?: <K, V>(key: K, value: V) => void; } export class TTLMap<K, V> { private items = new Map<K, TTLItem<V>>(); private cleanupTimer?: NodeJS.Timeout; private defaultTTL: number; private cleanupInterval: number; private maxSize?: number; private onExpire?: <K, V>(key: K, value: V) => void; private stats = { hits: 0, misses: 0, evictions: 0, expirations: 0, }; constructor(options: TTLMapOptions = {}) { this.defaultTTL = options.defaultTTL || 3600000; // 1 hour default this.cleanupInterval = options.cleanupInterval || 60000; // 1 minute default this.maxSize = options.maxSize; this.onExpire = options.onExpire; this.startCleanup(); } set(key: K, value: V, ttl?: number): void { const now = Date.now(); const expiry = now + (ttl || this.defaultTTL); // Check if we need to evict items due to size limit if (this.maxSize && this.items.size >= this.maxSize && !this.items.has(key)) { this.evictLRU(); } this.items.set(key, { value, expiry, createdAt: now, accessCount: 0, lastAccessedAt: now, }); } get(key: K): V | undefined { const item = this.items.get(key); if (!item) { this.stats.misses++; return undefined; } const now = Date.now(); if (now > item.expiry) { this.items.delete(key); this.stats.expirations++; this.stats.misses++; if (this.onExpire) { this.onExpire(key, item.value); } return undefined; } // Update access stats item.accessCount++; item.lastAccessedAt = now; this.stats.hits++; return item.value; } has(key: K): boolean { const item = this.items.get(key); if (!item) { return false; } if (Date.now() > item.expiry) { this.items.delete(key); this.stats.expirations++; if (this.onExpire) { this.onExpire(key, item.value); } return false; } return true; } delete(key: K): boolean { return this.items.delete(key); } clear(): void { this.items.clear(); } /** * Update TTL for an existing key */ touch(key: K, ttl?: number): boolean { const item = this.items.get(key); if (!item || Date.now() > item.expiry) { return false; } item.expiry = Date.now() + (ttl || this.defaultTTL); item.lastAccessedAt = Date.now(); return true; } /** * Get remaining TTL for a key */ getTTL(key: K): number { const item = this.items.get(key); if (!item) { return -1; } const remaining = item.expiry - Date.now(); return remaining > 0 ? remaining : -1; } /** * Get all keys (excluding expired ones) */ keys(): K[] { const now = Date.now(); const validKeys: K[] = []; for (const [key, item] of this.items) { if (now <= item.expiry) { validKeys.push(key); } } return validKeys; } /** * Get all values (excluding expired ones) */ values(): V[] { const now = Date.now(); const validValues: V[] = []; for (const item of this.items.values()) { if (now <= item.expiry) { validValues.push(item.value); } } return validValues; } /** * Get all entries (excluding expired ones) */ entries(): Array<[K, V]> { const now = Date.now(); const validEntries: Array<[K, V]> = []; for (const [key, item] of this.items) { if (now <= item.expiry) { validEntries.push([key, item.value]); } } return validEntries; } /** * Get size (excluding expired items) */ get size(): number { this.cleanup(); // Clean up expired items first return this.items.size; } private startCleanup(): void { this.cleanupTimer = setInterval(() => { this.cleanup(); }, this.cleanupInterval); } private cleanup(): void { const now = Date.now(); let cleaned = 0; for (const [key, item] of this.items) { if (now > item.expiry) { this.items.delete(key); cleaned++; this.stats.expirations++; if (this.onExpire) { this.onExpire(key, item.value); } } } if (cleaned > 0) { // Optional: Log cleanup stats } } private evictLRU(): void { let lruKey: K | undefined; let lruTime = Infinity; // Find least recently used item for (const [key, item] of this.items) { if (item.lastAccessedAt < lruTime) { lruTime = item.lastAccessedAt; lruKey = key; } } if (lruKey !== undefined) { this.items.delete(lruKey); this.stats.evictions++; } } /** * Stop the cleanup timer */ destroy(): void { if (this.cleanupTimer) { clearInterval(this.cleanupTimer); this.cleanupTimer = undefined; } this.items.clear(); } /** * Get statistics about the map */ getStats() { return { ...this.stats, size: this.items.size, hitRate: this.stats.hits / (this.stats.hits + this.stats.misses) || 0, }; } /** * Get detailed information about all items */ inspect(): Map< K, { value: V; ttl: number; age: number; accessCount: number; lastAccessed: number; } > { const now = Date.now(); const result = new Map(); for (const [key, item] of this.items) { if (now <= item.expiry) { result.set(key, { value: item.value, ttl: item.expiry - now, age: now - item.createdAt, accessCount: item.accessCount, lastAccessed: now - item.lastAccessedAt, }); } } return result; } }