Problem
Two in-memory Map caches check TTL on read but never evict stale entries:
priceCache (polygon.ts:29)
const priceCache = new Map<string, { price: number; fetchedAt: number }>();
Keys are ${ticker}:${dateString}. With daily syncs across multiple tickers and date lookups, entries accumulate indefinitely. Old entries (past TTL) are never read again but never deleted.
keyCache (webhook.ts:5)
const keyCache = new Map<string, { key: jose.JWK; fetchedAt: number }>();
Plaid rotates verification keys. Old key IDs will never be used again but entries persist forever.
Impact
- Low severity in practice (small data per entry, server restarts clear them)
- But it's unbounded growth in a long-running process — against clean code principles
- Compare with
rate-limit.ts which correctly cleans up stale records on a timer
Fix
Add periodic eviction (like rate-limit's cleanup):
const evictStaleEntries = () => {
const now = Date.now();
for (const [key, entry] of priceCache) {
if (now - entry.fetchedAt >= CACHE_TTL_MS) priceCache.delete(key);
}
};
setInterval(evictStaleEntries, CACHE_TTL_MS);
Or use a simple LRU approach with a max size cap.
Problem
Two in-memory
Mapcaches check TTL on read but never evict stale entries:priceCache (polygon.ts:29)
Keys are
${ticker}:${dateString}. With daily syncs across multiple tickers and date lookups, entries accumulate indefinitely. Old entries (past TTL) are never read again but never deleted.keyCache (webhook.ts:5)
Plaid rotates verification keys. Old key IDs will never be used again but entries persist forever.
Impact
rate-limit.tswhich correctly cleans up stale records on a timerFix
Add periodic eviction (like rate-limit's cleanup):
Or use a simple LRU approach with a max size cap.