UNPKG

vibe-coder-mcp

Version:

Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.

204 lines (203 loc) 5.82 kB
import logger from '../../../logger.js'; export class MemoryCache { name; map = new Map(); head = null; tail = null; size = 0; totalSize = 0; hits = 0; misses = 0; evictions = 0; options; static DEFAULT_OPTIONS = { maxEntries: 500, maxAge: 60 * 60 * 1000, sizeCalculator: () => 1, maxSize: 50000 }; constructor(options) { this.name = options.name; this.options = { ...MemoryCache.DEFAULT_OPTIONS, name: options.name, maxEntries: options.maxEntries ?? MemoryCache.DEFAULT_OPTIONS.maxEntries, maxAge: options.maxAge ?? MemoryCache.DEFAULT_OPTIONS.maxAge, sizeCalculator: options.sizeCalculator ?? MemoryCache.DEFAULT_OPTIONS.sizeCalculator, maxSize: options.maxSize ?? MemoryCache.DEFAULT_OPTIONS.maxSize, dispose: options.dispose ?? ((_key, _value) => { }) }; logger.debug(`Created memory cache "${this.name}" with max entries: ${this.options.maxEntries}, max size: ${this.options.maxSize}`); } get(key) { const entry = this.map.get(key); if (!entry) { this.misses++; return undefined; } if (entry.expiry < Date.now()) { this.delete(key); this.misses++; return undefined; } this.moveToFront(entry); this.hits++; return entry.value; } set(key, value, ttl) { const existingEntry = this.map.get(key); if (existingEntry) { this.totalSize -= existingEntry.size; existingEntry.value = value; existingEntry.timestamp = Date.now(); existingEntry.expiry = Date.now() + (ttl ?? this.options.maxAge); existingEntry.size = this.options.sizeCalculator(value); this.totalSize += existingEntry.size; this.moveToFront(existingEntry); } else { const now = Date.now(); const entry = { key, value, timestamp: now, expiry: now + (ttl ?? this.options.maxAge), size: this.options.sizeCalculator(value), prev: null, next: null }; this.map.set(key, entry); this.addToFront(entry); this.size++; this.totalSize += entry.size; } this.pruneSize(); } has(key) { const entry = this.map.get(key); if (!entry) { return false; } if (entry.expiry < Date.now()) { this.delete(key); return false; } return true; } delete(key) { const entry = this.map.get(key); if (!entry) { return false; } this.map.delete(key); this.removeFromList(entry); this.size--; this.totalSize -= entry.size; this.options.dispose(key, entry.value); return true; } clear() { for (const [key, entry] of this.map.entries()) { this.options.dispose(key, entry.value); } this.map.clear(); this.head = null; this.tail = null; this.size = 0; this.totalSize = 0; logger.debug(`Cleared memory cache "${this.name}"`); } pruneSize() { while (this.size > this.options.maxEntries) { this.evictLRU(); } while (this.totalSize > this.options.maxSize && this.size > 0) { this.evictLRU(); } } evictLRU() { if (!this.tail) { return; } const key = this.tail.key; this.delete(key); this.evictions++; logger.debug(`Evicted entry with key ${String(key)} from memory cache "${this.name}"`); } addToFront(entry) { if (!this.head) { this.head = entry; this.tail = entry; } else { entry.next = this.head; this.head.prev = entry; this.head = entry; } } moveToFront(entry) { if (entry === this.head) { return; } this.removeFromList(entry); this.addToFront(entry); } removeFromList(entry) { if (entry === this.head) { this.head = entry.next; } else if (entry.prev) { entry.prev.next = entry.next; } if (entry === this.tail) { this.tail = entry.prev; } else if (entry.next) { entry.next.prev = entry.prev; } entry.prev = null; entry.next = null; } getStats() { return { name: this.name, size: this.size, totalSize: this.totalSize, maxSize: this.options.maxSize, hits: this.hits, misses: this.misses, hitRatio: this.hits / (this.hits + this.misses) || 0, evictions: this.evictions }; } getAll() { const result = new Map(); const now = Date.now(); for (const [key, entry] of this.map.entries()) { if (entry.expiry < now) { this.delete(key); continue; } result.set(key, entry.value); } return result; } getSize() { return this.size; } getTotalSize() { return this.totalSize; } prune() { const now = Date.now(); let prunedCount = 0; for (const [key, entry] of this.map.entries()) { if (entry.expiry >= now) { continue; } this.delete(key); prunedCount++; } return prunedCount; } }