import { Program } from '../../engine';
import { IStorageService, StoredProgramSummary } from './storage-interface';
const DB_NAME = 'lego_ladder_db';
const STORE_NAME = 'ladder_programs';
const DB_VERSION = 1;
interface StoredProgram extends Program {
id: string;
lastModified: number;
name: string;
}
export class IndexedDBStorageService implements IStorageService {
private db: IDBDatabase | null = null;
private async getDB(): Promise<IDBDatabase> {
if (this.db) return this.db;
return new Promise((resolve, reject) => {
const request = indexedDB.open(DB_NAME, DB_VERSION);
request.onupgradeneeded = (event) => {
const db = (event.target as IDBOpenDBRequest).result as IDBDatabase;
if (!db.objectStoreNames.contains(STORE_NAME)) {
db.createObjectStore(STORE_NAME, { keyPath: 'id' });
}
};
request.onsuccess = (event) => {
this.db = (event.target as IDBOpenDBRequest).result as IDBDatabase;
resolve(this.db);
};
request.onerror = (event) => {
reject(new Error(`IndexedDB error: ${(event.target as IDBOpenDBRequest).error}`));
};
});
}
async saveProgram(program: Program, id?: string): Promise<string> {
const db = await this.getDB();
const programId = id || crypto.randomUUID();
const storedProgram: StoredProgram = {
...program,
id: programId,
lastModified: Date.now(),
};
return new Promise((resolve, reject) => {
const transaction = db.transaction(STORE_NAME, 'readwrite');
const store = transaction.objectStore(STORE_NAME);
const request = store.put(storedProgram);
request.onsuccess = () => resolve(programId);
request.onerror = () => reject(request.error);
});
}
async loadProgram(id: string): Promise<Program> {
const db = await this.getDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(STORE_NAME, 'readonly');
const store = transaction.objectStore(STORE_NAME);
const request = store.get(id);
request.onsuccess = () => {
const result = request.result as StoredProgram;
if (result) {
// Remove id and lastModified before returning to match Program interface
const { id: _, lastModified: __, ...program } = result;
resolve(program);
} else {
reject(new Error(`Program with id ${id} not found`));
}
};
request.onerror = () => reject(request.error);
});
}
async listPrograms(): Promise<StoredProgramSummary[]> {
const db = await this.getDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(STORE_NAME, 'readonly');
const store = transaction.objectStore(STORE_NAME);
const request = store.getAll();
request.onsuccess = () => {
const results = (request.result as StoredProgram[]).map((p) => ({
id: p.id,
name: p.name,
lastModified: p.lastModified,
}));
resolve(results);
};
request.onerror = () => reject(request.error);
});
}
async deleteProgram(id: string): Promise<void> {
const db = await this.getDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(STORE_NAME, 'readwrite');
const store = transaction.objectStore(STORE_NAME);
const request = store.delete(id);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
async close(): Promise<void> {
if (this.db) {
this.db.close();
this.db = null;
}
}
async renameProgram(id: string, newName: string): Promise<void> {
const db = await this.getDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(STORE_NAME, 'readwrite');
const store = transaction.objectStore(STORE_NAME);
const getRequest = store.get(id);
getRequest.onsuccess = () => {
const program = getRequest.result as StoredProgram;
if (program) {
program.name = newName;
program.lastModified = Date.now();
store.put(program);
transaction.oncomplete = () => resolve();
} else {
reject(new Error(`Program with id ${id} not found`));
}
};
getRequest.onerror = () => reject(getRequest.error);
transaction.onerror = () => reject(transaction.error);
});
}
}