let db = null; // ── Persistence ────────────────────────────────────────────────────────────── function saveToStorage() { try { const data = db.export(); // Uint8Array let binary = ''; const chunk = 8192; for (let i = 0; i < data.length; i += chunk) { binary += String.fromCharCode(...data.subarray(i, i + chunk)); } localStorage.setItem(STORAGE_KEY, btoa(binary)); } catch (e) { console.warn('catalyst: failed to persist database', e); } } function loadFromStorage() { try { const stored = localStorage.getItem(STORAGE_KEY); if (!stored) return null; const binary = atob(stored); const buf = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) buf[i] = binary.charCodeAt(i); return buf; } catch (e) { console.warn('catalyst: failed to load database from storage', e); return null; } } // ── Init ───────────────────────────────────────────────────────────────────── async function initDB() { const SQL = await initSqlJs({ locateFile: f => SQL_JS_CDN + f }); const saved = loadFromStorage(); if (saved) { db = new SQL.Database(saved); return; } db = new SQL.Database(); db.run(` CREATE TABLE instances ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, state TEXT DEFAULT 'deployed', stack TEXT DEFAULT '', vmid INTEGER UNIQUE NOT NULL, atlas INTEGER DEFAULT 0, argus INTEGER DEFAULT 0, semaphore INTEGER DEFAULT 0, patchmon INTEGER DEFAULT 0, tailscale INTEGER DEFAULT 0, andromeda INTEGER DEFAULT 0, tailscale_ip TEXT DEFAULT '', hardware_acceleration INTEGER DEFAULT 0, createdAt TEXT DEFAULT (datetime('now')), updatedAt TEXT DEFAULT (datetime('now')) ) `); const stmt = db.prepare(` INSERT INTO instances (name, state, stack, vmid, atlas, argus, semaphore, patchmon, tailscale, andromeda, tailscale_ip, hardware_acceleration) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `); SEED.forEach(s => stmt.run([ s.name, s.state, s.stack, s.vmid, +s.atlas, +s.argus, +s.semaphore, +s.patchmon, +s.tailscale, +s.andromeda, s.tailscale_ip, +s.hardware_acceleration, ])); stmt.free(); saveToStorage(); } // ── Queries ────────────────────────────────────────────────────────────────── function getInstances(filters = {}) { let sql = 'SELECT * FROM instances WHERE 1=1'; const params = []; if (filters.search) { sql += ' AND (name LIKE ? OR CAST(vmid AS TEXT) LIKE ? OR stack LIKE ?)'; const s = `%${filters.search}%`; params.push(s, s, s); } if (filters.state) { sql += ' AND state = ?'; params.push(filters.state); } if (filters.stack) { sql += ' AND stack = ?'; params.push(filters.stack); } sql += ' ORDER BY name ASC'; const res = db.exec(sql, params); if (!res.length) return []; const cols = res[0].columns; return res[0].values.map(row => Object.fromEntries(cols.map((c, i) => [c, row[i]]))); } function getInstance(vmid) { const res = db.exec('SELECT * FROM instances WHERE vmid = ?', [vmid]); if (!res.length) return null; const cols = res[0].columns; return Object.fromEntries(cols.map((c, i) => [c, res[0].values[0][i]])); } function getDistinctStacks() { const res = db.exec(`SELECT DISTINCT stack FROM instances WHERE stack != '' ORDER BY stack`); if (!res.length) return []; return res[0].values.map(row => row[0]); } // ── Mutations ───────────────────────────────────────────────────────────────── function createInstance(data) { try { db.run( `INSERT INTO instances (name, state, stack, vmid, atlas, argus, semaphore, patchmon, tailscale, andromeda, tailscale_ip, hardware_acceleration) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [data.name, data.state, data.stack, data.vmid, data.atlas, data.argus, data.semaphore, data.patchmon, data.tailscale, data.andromeda, data.tailscale_ip, data.hardware_acceleration] ); saveToStorage(); return { ok: true }; } catch (e) { return { ok: false, error: e.message }; } } function updateInstance(id, data) { try { db.run( `UPDATE instances SET name=?, state=?, stack=?, vmid=?, atlas=?, argus=?, semaphore=?, patchmon=?, tailscale=?, andromeda=?, tailscale_ip=?, hardware_acceleration=?, updatedAt=datetime('now') WHERE id=?`, [data.name, data.state, data.stack, data.vmid, data.atlas, data.argus, data.semaphore, data.patchmon, data.tailscale, data.andromeda, data.tailscale_ip, data.hardware_acceleration, id] ); saveToStorage(); return { ok: true }; } catch (e) { return { ok: false, error: e.message }; } } function deleteInstance(id) { db.run('DELETE FROM instances WHERE id = ?', [id]); saveToStorage(); }