snapshotStore.ts3.0 KBView on GitHub import type { FreezeSnapshot, TimelineEntry } from '../types';
export interface StoredSnapshot {
id: string;
ts: number;
reason: 'longtask' | 'worker-heartbeat-miss' | 'raf-gap';
blockedMs: number;
recentTimeline: TimelineEntry[];
storeKeys: string[];
memory?: FreezeSnapshot['memory'];
userAgent: string;
url: string;
viewport: { w: number; h: number };
}
const DB_NAME = 'cedar-deep-debugger';
const STORE_NAME = 'freeze-snapshots';
const DB_VERSION = 1;
const MAX_SNAPSHOTS = 20;
function openDb(): Promise<IDBDatabase> {
return new Promise((resolve, reject) => {
if (typeof indexedDB === 'undefined') {
reject(new Error('IndexedDB unavailable'));
return;
}
const req = indexedDB.open(DB_NAME, DB_VERSION);
req.onupgradeneeded = () => {
const db = req.result;
if (!db.objectStoreNames.contains(STORE_NAME)) {
const store = db.createObjectStore(STORE_NAME, { keyPath: 'id' });
store.createIndex('ts', 'ts');
}
};
req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error);
});
}
export async function saveSnapshot(snap: StoredSnapshot): Promise<void> {
try {
const db = await openDb();
await new Promise<void>((resolve, reject) => {
const tx = db.transaction(STORE_NAME, 'readwrite');
tx.objectStore(STORE_NAME).put(snap);
tx.oncomplete = () => resolve();
tx.onerror = () => reject(tx.error);
});
// Trim to MAX_SNAPSHOTS (oldest first)
const all = await listSnapshots();
if (all.length > MAX_SNAPSHOTS) {
const toRemove = all.slice(0, all.length - MAX_SNAPSHOTS);
await Promise.all(toRemove.map((s) => deleteSnapshot(s.id)));
}
} catch {
// ignore — snapshots are best-effort
}
}
export async function listSnapshots(): Promise<StoredSnapshot[]> {
try {
const db = await openDb();
return await new Promise<StoredSnapshot[]>((resolve, reject) => {
const tx = db.transaction(STORE_NAME, 'readonly');
const req = tx.objectStore(STORE_NAME).getAll();
req.onsuccess = () => {
const items = (req.result as StoredSnapshot[]).sort((a, b) => a.ts - b.ts);
resolve(items);
};
req.onerror = () => reject(req.error);
});
} catch {
return [];
}
}
export async function deleteSnapshot(id: string): Promise<void> {
try {
const db = await openDb();
await new Promise<void>((resolve, reject) => {
const tx = db.transaction(STORE_NAME, 'readwrite');
tx.objectStore(STORE_NAME).delete(id);
tx.oncomplete = () => resolve();
tx.onerror = () => reject(tx.error);
});
} catch {
// ignore
}
}
export async function clearSnapshots(): Promise<void> {
try {
const db = await openDb();
await new Promise<void>((resolve, reject) => {
const tx = db.transaction(STORE_NAME, 'readwrite');
tx.objectStore(STORE_NAME).clear();
tx.oncomplete = () => resolve();
tx.onerror = () => reject(tx.error);
});
} catch {
// ignore
}
}