1import type { SongMeta, SongRecord } from '../types.ts';
3// Song library in IndexedDB. One object store keyed by song id; the whole
4// song is small JSON, so records are read and written whole.
6const DB_NAME = 'midiboot';
7const STORE = 'songs';
9let dbPromise: Promise<IDBDatabase> | null = null;
11function openDb(): Promise<IDBDatabase> {
12 if (dbPromise) return dbPromise;
13 dbPromise = new Promise((resolve, reject) => {
14 const req = indexedDB.open(DB_NAME, 1);
15 req.onupgradeneeded = () => {
16 const db = req.result;
17 if (!db.objectStoreNames.contains(STORE)) db.createObjectStore(STORE, { keyPath: 'id' });
18 };
19 req.onsuccess = () => resolve(req.result);
20 req.onerror = () => reject(req.error);
21 });
22 return dbPromise;
23}
25function txDone(tx: IDBTransaction): Promise<void> {
26 return new Promise((resolve, reject) => {
27 tx.oncomplete = () => resolve();
28 tx.onerror = () => reject(tx.error);
29 tx.onabort = () => reject(tx.error);
30 });
31}
33export async function putSong(rec: SongRecord): Promise<void> {
34 const db = await openDb();
35 const tx = db.transaction(STORE, 'readwrite');
36 tx.objectStore(STORE).put(rec);
37 await txDone(tx);
38}
40export async function getSong(id: string): Promise<SongRecord | null> {
41 const db = await openDb();
42 return new Promise((resolve, reject) => {
43 const req = db.transaction(STORE, 'readonly').objectStore(STORE).get(id);
44 req.onsuccess = () => resolve((req.result as SongRecord) ?? null);
45 req.onerror = () => reject(req.error);
46 });
47}
49export async function deleteSong(id: string): Promise<void> {
50 const db = await openDb();
51 const tx = db.transaction(STORE, 'readwrite');
52 tx.objectStore(STORE).delete(id);
53 await txDone(tx);
54}
56export async function listSongs(): Promise<SongMeta[]> {
57 const db = await openDb();
58 return new Promise((resolve, reject) => {
59 const req = db.transaction(STORE, 'readonly').objectStore(STORE).getAll();
60 req.onsuccess = () => {
61 const recs = (req.result as SongRecord[]) ?? [];
62 const metas = recs.map<SongMeta>((r) => ({
63 id: r.id,
64 name: r.name,
65 updatedAt: r.updatedAt,
66 trackCount: r.song.tracks.length,
67 noteCount: r.song.tracks.reduce((s, t) => s + t.notes.length, 0),
68 }));
69 metas.sort((a, b) => b.updatedAt - a.updatedAt);
70 resolve(metas);
71 };
72 req.onerror = () => reject(req.error);
73 });
74}