Files
Epicnabbo-Catalogus-Updated…/src/lib/cache.ts
T
openhands 6a67fb6e83 refactor: remove additional dead exports and unused actions
Remove buildFontUrl, measureText, uncached, invalidateCache, fetchJsonWithFlareSolver, upsertPermission, deletePermission, bulkImportPermissions, clearAllPermissions, bulkDeletePermissions, bulkDeletePhotos, userReplyTicket, closeTicketByUser. Update staff-smoke-contract test for bulkDeletePhotos removal.
2026-08-07 19:19:05 +02:00

50 lines
1.2 KiB
TypeScript

import "server-only";
import { redis } from "@/lib/redis";
type CacheEntry<T> = { data: T; expiresAt: number };
const memory = new Map<string, CacheEntry<unknown>>();
/**
* Redis-first cached query with an in-memory fallback.
* Use for read-heavy endpoints polled by the browser (online count, etc.).
*/
export async function cached<T>(
key: string,
ttlMs: number,
fn: () => Promise<T>,
): Promise<T> {
const ttlSec = Math.ceil(ttlMs / 1000);
// Redis path (shared across instances).
if (redis && redis.status !== "end") {
try {
const cached = await redis.get(key);
if (cached !== null && cached !== undefined) {
return JSON.parse(cached) as T;
}
} catch {
/* fall through to DB / memory */
}
}
// In-memory fallback (single-instance fast path).
const existing = memory.get(key);
if (existing && existing.expiresAt > Date.now()) {
return existing.data as T;
}
const data = await fn();
if (redis && redis.status !== "end") {
try {
await redis.setex(key, ttlSec, JSON.stringify(data));
} catch {
/* non-critical: memory cache still works */
}
}
memory.set(key, { data, expiresAt: Date.now() + ttlMs });
return data;
}