import { createSharedStore, type SharedStore } from "@hivegrid/shared-store"; /** * Singleton SharedStore for the web process. Tenant-scoped caches, * cross-replica rate limits, and MFA challenge state migrated as part * of task #38 all use this instance so a single Redis connection is * reused across the web lifetime. */ let cachedStore: SharedStore | null = null; let crossAppCacheStore: SharedStore | null = null; let factory: () => SharedStore = () => createSharedStore({ namespace: "web", redisUrl: process.env.REDIS_URL }); export function getSharedStore(): SharedStore { if (cachedStore === null) { cachedStore = factory(); } return cachedStore; } /** Shared cache-invalidation namespace used by both the API and web apps. */ export function getCrossAppCacheStore(): SharedStore { if (crossAppCacheStore === null) { crossAppCacheStore = createSharedStore({ namespace: "cross-app-cache", adapter: getSharedStore().adapter }); } return crossAppCacheStore; } /** Close the singleton adapter for short-lived processes such as DB seeding. */ export async function closeSharedStores(): Promise { const stores = [cachedStore, crossAppCacheStore].filter((store): store is SharedStore => store !== null); cachedStore = null; crossAppCacheStore = null; const seenAdapters = new Set(); await Promise.all( stores.map(async (store) => { if (seenAdapters.has(store.adapter)) return; seenAdapters.add(store.adapter); await store.close(); }), ); } export function __setSharedStoreFactoryForTests(next: () => SharedStore): void { factory = next; cachedStore = null; crossAppCacheStore = null; } export function __resetSharedStoreForTests(): void { factory = () => createSharedStore({ namespace: "web", redisUrl: process.env.REDIS_URL }); cachedStore = null; crossAppCacheStore = null; }