// IndexedDB cache of computed modes files, keyed by a hash of the configuration and options. const DB = "cymplay", STORE = "modes"; function open(): Promise { return new Promise((resolve, reject) => { const req = indexedDB.open(DB, 1); req.onupgradeneeded = () => req.result.createObjectStore(STORE); req.onsuccess = () => resolve(req.result); req.onerror = () => reject(req.error); }); } export async function cacheKey(obj: unknown): Promise { const bytes = new TextEncoder().encode(JSON.stringify(obj)); const digest = await crypto.subtle.digest("SHA-256", bytes); return Array.from(new Uint8Array(digest).slice(0, 16), (b) => b.toString(16).padStart(2, "0")).join(""); } export async function cacheGet(key: string): Promise { try { const db = await open(); return await new Promise((resolve, reject) => { const req = db.transaction(STORE, "readonly").objectStore(STORE).get(key); req.onsuccess = () => resolve((req.result as ArrayBuffer) ?? null); req.onerror = () => reject(req.error); }); } catch { return null; } } export async function cachePut(key: string, buffer: ArrayBuffer): Promise { try { const db = await open(); await new Promise((resolve, reject) => { const tx = db.transaction(STORE, "readwrite"); tx.objectStore(STORE).put(buffer, key); tx.oncomplete = () => resolve(); tx.onerror = () => reject(tx.error); }); } catch { /* caching is best effort */ } } export async function cacheClear(): Promise { try { const db = await open(); await new Promise((resolve, reject) => { const tx = db.transaction(STORE, "readwrite"); tx.objectStore(STORE).clear(); tx.oncomplete = () => resolve(); tx.onerror = () => reject(tx.error); }); } catch { /* ignore */ } }