// 자동 백업: versioned INP (+ asset attributes) copies of the model kept in a // separate IndexedDB, so a bad edit hours ago can still be undone after the // autosave (which keeps only the latest state) has overwritten it. import type { AssetStore } from './assets'; const DB_NAME = 'spipe-backups'; const STORE = 'backups'; export const MAX_BACKUPS = 30; export interface BackupRecord { id: string; savedAt: string; fileName: string; reason: 'auto' | 'manual'; inp: string; assets?: AssetStore; nodes: number; links: number; } function open(): Promise { return new Promise((resolve, reject) => { const req = indexedDB.open(DB_NAME, 1); req.onupgradeneeded = () => { if (!req.result.objectStoreNames.contains(STORE)) req.result.createObjectStore(STORE, { keyPath: 'id' }); }; req.onsuccess = () => resolve(req.result); req.onerror = () => reject(req.error ?? new Error('백업 저장소를 열 수 없습니다.')); }); } async function withStore(mode: IDBTransactionMode, fn: (store: IDBObjectStore) => IDBRequest | void): Promise { const db = await open(); try { return await new Promise((resolve, reject) => { const tx = db.transaction(STORE, mode); const req = fn(tx.objectStore(STORE)); tx.oncomplete = () => resolve(req ? req.result : undefined); tx.onerror = () => reject(tx.error ?? new Error('백업 저장소 오류')); tx.onabort = () => reject(tx.error ?? new Error('백업 저장 중단')); }); } finally { db.close(); } } export async function listBackups(): Promise { const all = ((await withStore('readonly', (s) => s.getAll())) ?? []) as BackupRecord[]; return all.sort((a, b) => (a.savedAt < b.savedAt ? 1 : -1)); } /** Save a backup unless it is identical to the newest one; trims to MAX_BACKUPS. */ export async function saveBackup(rec: Omit): Promise { const existing = await listBackups(); const last = existing[0]; if (last && last.inp === rec.inp && JSON.stringify(last.assets ?? null) === JSON.stringify(rec.assets ?? null)) return false; const savedAt = new Date().toISOString(); await withStore('readwrite', (s) => { s.put({ ...rec, id: `${savedAt}_${Math.random().toString(36).slice(2, 7)}`, savedAt }); for (const old of existing.slice(MAX_BACKUPS - 1)) s.delete(old.id); }); return true; } export async function deleteBackup(id: string): Promise { await withStore('readwrite', (s) => { s.delete(id); }); }