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
JavaScript
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;
}
}