concept-collection / mri-scanner
mri-scanner / src / storage / seqStore.ts
77 lines · 2.4 KBBlameHistoryRaw
1// Persist uploaded .seq files in the browser (IndexedDB). Small, promise-based
2// wrapper — no dependency. Each record keeps the raw .seq text plus a
3// user-editable name.
4export interface StoredSequence {
5 id: string
6 name: string
7 text: string
8 size: number
9 addedAt: number
12const DB_NAME = 'mri-scanner'
13const STORE = 'sequences'
14const VERSION = 1
16let dbPromise: Promise<IDBDatabase> | null = null
18function openDb(): Promise<IDBDatabase> {
19 if (dbPromise) return dbPromise
20 dbPromise = new Promise((resolve, reject) => {
21 const req = indexedDB.open(DB_NAME, VERSION)
22 req.onupgradeneeded = () => {
23 const db = req.result
24 if (!db.objectStoreNames.contains(STORE)) {
25 db.createObjectStore(STORE, { keyPath: 'id' })
26 }
27 }
28 req.onsuccess = () => resolve(req.result)
29 req.onerror = () => reject(req.error)
30 })
31 return dbPromise
34function tx<T>(mode: IDBTransactionMode, fn: (store: IDBObjectStore) => IDBRequest<T>): Promise<T> {
35 return openDb().then(
36 (db) =>
37 new Promise<T>((resolve, reject) => {
38 const t = db.transaction(STORE, mode)
39 const req = fn(t.objectStore(STORE))
40 req.onsuccess = () => resolve(req.result)
41 req.onerror = () => reject(req.error)
42 }),
43 )
46export async function listSequences(): Promise<StoredSequence[]> {
47 const all = await tx<StoredSequence[]>('readonly', (s) => s.getAll() as IDBRequest<StoredSequence[]>)
48 return all.sort((a, b) => b.addedAt - a.addedAt)
51function uid(): string {
52 if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) return crypto.randomUUID()
53 return 'seq_' + Math.floor(performance.now() * 1000).toString(36) + Math.floor(performance.now()).toString(36)
56export async function addSequence(name: string, text: string): Promise<StoredSequence> {
57 const record: StoredSequence = {
58 id: uid(),
59 name,
60 text,
61 size: text.length,
62 addedAt: Date.now(),
63 }
64 await tx('readwrite', (s) => s.put(record))
65 return record
68export async function renameSequence(id: string, name: string): Promise<void> {
69 const rec = await tx<StoredSequence | undefined>('readonly', (s) => s.get(id) as IDBRequest<StoredSequence | undefined>)
70 if (!rec) return
71 rec.name = name
72 await tx('readwrite', (s) => s.put(rec))
75export async function deleteSequence(id: string): Promise<void> {
76 await tx('readwrite', (s) => s.delete(id) as unknown as IDBRequest<undefined>)