From 7f49ca1626c922d91a932345475794b96b29a2b7 Mon Sep 17 00:00:00 2001 From: Andreas Knuth Date: Thu, 2 Jul 2026 10:21:27 -0500 Subject: [PATCH] cache --- lib/feeds.ts | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/lib/feeds.ts b/lib/feeds.ts index a597eb7..9a29371 100644 --- a/lib/feeds.ts +++ b/lib/feeds.ts @@ -5,24 +5,36 @@ import { computeGroupTables, computeThirdPlaceTable } from "./standings"; import { FIFA_GROUP_MAP, FIFA_STAGE_MAP } from "./fifa-constants"; // ---------------------------------------------------------------------------- -// Caching: einfacher In-Memory-Cache mit TTL. +// Caching: In-Memory-Cache mit TTL + stale-while-revalidate. +// Bei abgelaufenem Cache wird der alte Wert sofort zurückgegeben und die +// Erneuerung im Hintergrund angestoßen (keine Wartezeit für den Nutzer). // ---------------------------------------------------------------------------- -interface CacheEntry { value: T; expires: number; } +interface CacheEntry { value: T; expires: number; refreshing?: boolean; } const cache = new Map>(); export async function cached(key: string, ttlMs: number, fn: () => Promise): Promise { const hit = cache.get(key) as CacheEntry | undefined; const now = Date.now(); + + // Frischer Cache → direkt zurück if (hit && hit.expires > now) return hit.value; - try { - const value = await fn(); - cache.set(key, { value, expires: now + ttlMs }); - return value; - } catch (err) { - // Bei Upstream-Fehler abgelaufenen Cache weiterverwenden, statt hart zu failen. - if (hit) return hit.value; - throw err; + + // Abgelaufen, aber vorhanden → sofort alten Wert liefern, im Hintergrund erneuern + if (hit) { + if (!hit.refreshing) { + hit.refreshing = true; + fn() + .then((value) => { cache.set(key, { value, expires: Date.now() + ttlMs }); }) + .catch((err) => { console.error(`[cache] Hintergrund-Refresh fehlgeschlagen (${key}):`, err); }) + .finally(() => { const e = cache.get(key) as CacheEntry | undefined; if (e) e.refreshing = false; }); + } + return hit.value; } + + // Kaltstart: kein Cache → synchron laden + const value = await fn(); + cache.set(key, { value, expires: now + ttlMs }); + return value; } // ----------------------------------------------------------------------------