This commit is contained in:
2026-07-02 10:21:27 -05:00
parent 2c435472f5
commit 7f49ca1626

View File

@@ -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<T> { value: T; expires: number; }
interface CacheEntry<T> { value: T; expires: number; refreshing?: boolean; }
const cache = new Map<string, CacheEntry<unknown>>();
export async function cached<T>(key: string, ttlMs: number, fn: () => Promise<T>): Promise<T> {
const hit = cache.get(key) as CacheEntry<T> | 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<T> | 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;
}
// ----------------------------------------------------------------------------