import type { Creature, CreatureId } from "@initiative/domain"; import { type IDBPDatabase, openDB } from "idb"; const DB_NAME = "initiative-bestiary"; const STORE_NAME = "sources"; const DB_VERSION = 1; export interface CachedSourceInfo { readonly sourceCode: string; readonly displayName: string; readonly creatureCount: number; readonly cachedAt: number; } interface CachedSourceRecord { sourceCode: string; displayName: string; creatures: Creature[]; cachedAt: number; creatureCount: number; } let db: IDBPDatabase | null = null; let dbFailed = false; // In-memory fallback when IndexedDB is unavailable const memoryStore = new Map(); async function getDb(): Promise { if (db) return db; if (dbFailed) return null; try { db = await openDB(DB_NAME, DB_VERSION, { upgrade(database) { if (!database.objectStoreNames.contains(STORE_NAME)) { database.createObjectStore(STORE_NAME, { keyPath: "sourceCode", }); } }, }); return db; } catch { dbFailed = true; console.warn( "IndexedDB unavailable — bestiary cache will not persist across sessions.", ); return null; } } export async function cacheSource( sourceCode: string, displayName: string, creatures: Creature[], ): Promise { const record: CachedSourceRecord = { sourceCode, displayName, creatures, cachedAt: Date.now(), creatureCount: creatures.length, }; const database = await getDb(); if (database) { await database.put(STORE_NAME, record); } else { memoryStore.set(sourceCode, record); } } export async function isSourceCached(sourceCode: string): Promise { const database = await getDb(); if (database) { const record = await database.get(STORE_NAME, sourceCode); return record !== undefined; } return memoryStore.has(sourceCode); } export async function getCachedSources(): Promise { const database = await getDb(); if (database) { const all: CachedSourceRecord[] = await database.getAll(STORE_NAME); return all.map((r) => ({ sourceCode: r.sourceCode, displayName: r.displayName, creatureCount: r.creatureCount, cachedAt: r.cachedAt, })); } return [...memoryStore.values()].map((r) => ({ sourceCode: r.sourceCode, displayName: r.displayName, creatureCount: r.creatureCount, cachedAt: r.cachedAt, })); } export async function clearSource(sourceCode: string): Promise { const database = await getDb(); if (database) { await database.delete(STORE_NAME, sourceCode); } else { memoryStore.delete(sourceCode); } } export async function clearAll(): Promise { const database = await getDb(); if (database) { await database.clear(STORE_NAME); } else { memoryStore.clear(); } } export async function loadAllCachedCreatures(): Promise< Map > { const map = new Map(); const database = await getDb(); let records: CachedSourceRecord[]; if (database) { records = await database.getAll(STORE_NAME); } else { records = [...memoryStore.values()]; } for (const record of records) { for (const creature of record.creatures) { map.set(creature.id, creature); } } return map; }