Files
client/greyscale/src/cache.js
T
2026-04-17 16:52:51 -04:00

63 lines
1.7 KiB
JavaScript

export class APICache {
constructor({ maxSize = 250, ttl = 1000 * 60 * 20 } = {}) {
this.maxSize = maxSize;
this.ttl = ttl;
this.store = new Map();
}
makeKey(namespace, key) {
return `${namespace}:${String(key)}`;
}
isExpired(entry) {
return !entry || Date.now() - entry.ts > entry.ttl;
}
pruneToSize() {
if (this.store.size <= this.maxSize) return;
const entries = [...this.store.entries()].sort((a, b) => a[1].ts - b[1].ts);
const overflow = this.store.size - this.maxSize;
for (let i = 0; i < overflow; i += 1) {
this.store.delete(entries[i][0]);
}
}
async get(namespace, key) {
const cacheKey = this.makeKey(namespace, key);
const entry = this.store.get(cacheKey);
if (!entry) return null;
if (this.isExpired(entry)) {
this.store.delete(cacheKey);
return null;
}
return entry.value;
}
async set(namespace, key, value, ttl = this.ttl) {
const cacheKey = this.makeKey(namespace, key);
this.store.set(cacheKey, { value, ts: Date.now(), ttl });
this.pruneToSize();
}
clearExpired() {
for (const [key, entry] of this.store.entries()) {
if (this.isExpired(entry)) this.store.delete(key);
}
}
async clear() {
this.store.clear();
}
getCacheStats() {
const namespaces = new Set();
for (const key of this.store.keys()) namespaces.add(key.split(':', 1)[0]);
return {
size: this.store.size,
namespaces: [...namespaces],
maxSize: this.maxSize,
ttlMs: this.ttl,
};
}
}