export interface CachedPolicy { defaultLightThemeUri: string | null; defaultDarkThemeUri: string | null; allowUserChoice: boolean; availableThemes: Array<{ uri: string; /** * Present for pinned refs (locked to an exact version). * Absent for live refs (e.g. canonical atbb.space presets) — those * resolve to the current record at the URI without CID verification. */ cid?: string; }>; } export interface CachedTheme { cid: string; /** All token values are CSS strings at the cache boundary. */ tokens: Record; cssOverrides: string | null; fontUrls: string[] | null; } interface CacheEntry { data: T; expiresAt: number; } /** * In-memory TTL cache for resolved theme data on the web server. * * Themes change rarely. A single instance is created per server startup * (inside createThemeMiddleware) and shared across all requests. * * Policy: single entry, keyed to the forum. * Themes: map keyed by `${at-uri}:${colorScheme}` — colorScheme in the key * ensures light and dark cached entries are never confused, even if the same * AT-URI is used for both defaults. */ export class ThemeCache { private policy: CacheEntry | null = null; private themes = new Map>(); constructor(readonly ttlMs: number) {} getPolicy(): Readonly | null { if (!this.policy || Date.now() > this.policy.expiresAt) { this.policy = null; return null; } return this.policy.data; } setPolicy(data: CachedPolicy): void { this.policy = { data, expiresAt: Date.now() + this.ttlMs }; } getTheme(uri: string, colorScheme: "light" | "dark"): Readonly | null { const key = `${uri}:${colorScheme}`; const entry = this.themes.get(key); if (!entry || Date.now() > entry.expiresAt) { this.themes.delete(key); return null; } return entry.data; } setTheme(uri: string, colorScheme: "light" | "dark", data: CachedTheme): void { const key = `${uri}:${colorScheme}`; this.themes.set(key, { data, expiresAt: Date.now() + this.ttlMs }); } /** Evict a stale theme entry so the next request fetches fresh data. */ deleteTheme(uri: string, colorScheme: "light" | "dark"): void { this.themes.delete(`${uri}:${colorScheme}`); } }