import "server-only"; import { logger } from "@/lib/logger"; import { redis } from "@/lib/redis"; const DEFAULT_CACHE_TTL = 60; function isRedisAvailable(): boolean { return !!redis && redis.status !== "end"; } export async function cachedQuery( cacheKey: string, queryFn: () => Promise, ttl: number = DEFAULT_CACHE_TTL, ): Promise { if (!isRedisAvailable()) { return queryFn(); } try { const cached = await redis?.get(cacheKey); if (cached !== null && cached !== undefined) { return JSON.parse(cached) as T; } } catch (err) { logger.warn("[cache] read failed, falling back to DB", { key: cacheKey, error: String(err), }); } const result = await queryFn(); try { await redis?.setex(cacheKey, ttl, JSON.stringify(result)); } catch (err) { logger.warn("[cache] write failed, continuing without cache", { key: cacheKey, error: String(err), }); } return result; } /** Invalidate cache keys matching a glob pattern. */ export async function invalidateCache(pattern: string): Promise { if (!isRedisAvailable()) return 0; const r = redis; if (!r) return 0; const keys = await r.keys(pattern); return keys.length > 0 ? r.del(...keys) : 0; } /** Invalidate a single cache key immediately. */ export async function invalidateKey(key: string): Promise { if (!isRedisAvailable()) return 0; if (!redis) return 0; return redis.del(key); }