// Contenido con más de estos meses de antigüedad se considera "estable" // y se cachea por más tiempo. Lo reciente no se cachea para evitar // servir links rotos o resultados incompletos de providers que aún indexan. const STABLE_CONTENT_MONTHS = 3 const SHORT_TTL_MS = 20_000 // contenido reciente: 20s (solo dedup de vuelo) const LONG_TTL_MS = 30 * 60_000 // contenido estable: 30 minutos const MAX_ENTRIES = 500 const resultsCache = new Map() const inflight = new Map() function pruneExpired() { const now = Date.now() for (const [key, entry] of resultsCache.entries()) { if (entry.expires <= now) { resultsCache.delete(key) } } while (resultsCache.size > MAX_ENTRIES) { const oldestKey = resultsCache.keys().next().value if (oldestKey !== undefined) resultsCache.delete(oldestKey) } } /** * Determina si el contenido es suficientemente antiguo para cachearlo largo. * @param {string|null} releaseDate - Fecha ISO "YYYY-MM-DD" o null */ function isStableContent(releaseDate) { if (!releaseDate) return false const release = new Date(releaseDate) if (isNaN(release.getTime())) return false const threshold = new Date() threshold.setMonth(threshold.getMonth() - STABLE_CONTENT_MONTHS) return release < threshold } /** * @param {string} key * @param {() => Promise} factory * @param {{ releaseDate?: string|null, cacheErrors?: boolean }} [opts] */ async function dedupeRequest(key, factory, { releaseDate = null, cacheErrors = false } = {}) { if (!key || typeof key !== 'string') { return factory() } pruneExpired() const cached = resultsCache.get(key) if (cached && cached.expires > Date.now()) { return cached.value } if (inflight.has(key)) { return inflight.get(key) } const promise = (async () => factory())() inflight.set(key, promise) try { const value = await promise // Solo cachear largo si el contenido es estable Y hay resultados reales const hasResults = Array.isArray(value) && value.length > 0 const stable = isStableContent(releaseDate) const ttl = (stable && hasResults) ? LONG_TTL_MS : SHORT_TTL_MS resultsCache.set(key, { value, expires: Date.now() + ttl, }) pruneExpired() return value } catch (error) { if (cacheErrors) { resultsCache.set(key, { value: { error: error.message || String(error) }, expires: Date.now() + Math.min(SHORT_TTL_MS, 5_000), }) } throw error } finally { inflight.delete(key) } } function clearRequestDedupCache() { resultsCache.clear() inflight.clear() } module.exports = { dedupeRequest, clearRequestDedupCache, isStableContent, STABLE_CONTENT_MONTHS, }