const DATABASE_NAME = 'bonsai-webgpu-chat'; const DATABASE_VERSION = 1; const MEMORY_STORE = 'memory'; const CONVERSATION_STORE = 'conversations'; export interface MemoryRecord { key: string; value: string; updatedAt: number; } export interface ConversationRecord { id: string; title: string; messages: TMessage[]; modelId: string; systemPrompt?: string; updatedAt: number; } function requestResult(request: IDBRequest): Promise { return new Promise((resolve, reject) => { request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error ?? new Error('IndexedDB request failed')); }); } function transactionDone(transaction: IDBTransaction): Promise { return new Promise((resolve, reject) => { transaction.oncomplete = () => resolve(); transaction.onerror = () => reject(transaction.error ?? new Error('IndexedDB transaction failed')); transaction.onabort = () => reject(transaction.error ?? new Error('IndexedDB transaction aborted')); }); } async function openDatabase(): Promise { if (!('indexedDB' in globalThis)) throw new Error('IndexedDB is unavailable in this browser'); const request = indexedDB.open(DATABASE_NAME, DATABASE_VERSION); request.onupgradeneeded = () => { const database = request.result; if (!database.objectStoreNames.contains(MEMORY_STORE)) { database.createObjectStore(MEMORY_STORE, { keyPath: 'key' }); } if (!database.objectStoreNames.contains(CONVERSATION_STORE)) { const store = database.createObjectStore(CONVERSATION_STORE, { keyPath: 'id' }); store.createIndex('updatedAt', 'updatedAt'); } }; return requestResult(request); } function validateIdentifier(value: string, label: string): string { const normalized = value.trim(); if (!normalized || normalized.length > 256) throw new Error(`${label} must contain 1–256 characters`); return normalized; } export async function putMemory(key: string, value: string): Promise { const record = { key: validateIdentifier(key, 'memory key'), value: value.slice(0, 64 * 1024), updatedAt: Date.now() }; const database = await openDatabase(); const transaction = database.transaction(MEMORY_STORE, 'readwrite'); transaction.objectStore(MEMORY_STORE).put(record); await transactionDone(transaction); database.close(); return record; } export async function getMemory(key: string): Promise { const database = await openDatabase(); const transaction = database.transaction(MEMORY_STORE, 'readonly'); const result = await requestResult(transaction.objectStore(MEMORY_STORE).get(validateIdentifier(key, 'memory key'))); await transactionDone(transaction); database.close(); return result as MemoryRecord | undefined; } export async function listMemory(): Promise { const database = await openDatabase(); const transaction = database.transaction(MEMORY_STORE, 'readonly'); const result = await requestResult(transaction.objectStore(MEMORY_STORE).getAll()) as MemoryRecord[]; await transactionDone(transaction); database.close(); return result.sort((left, right) => right.updatedAt - left.updatedAt); } export async function deleteMemory(key: string): Promise { const database = await openDatabase(); const transaction = database.transaction(MEMORY_STORE, 'readwrite'); transaction.objectStore(MEMORY_STORE).delete(validateIdentifier(key, 'memory key')); await transactionDone(transaction); database.close(); } export async function saveConversation(record: ConversationRecord): Promise { const id = validateIdentifier(record.id, 'conversation id'); const database = await openDatabase(); const transaction = database.transaction(CONVERSATION_STORE, 'readwrite'); transaction.objectStore(CONVERSATION_STORE).put({ ...record, id, title: record.title.slice(0, 256), updatedAt: Date.now() }); await transactionDone(transaction); database.close(); } export async function loadConversation(id: string): Promise | undefined> { const database = await openDatabase(); const transaction = database.transaction(CONVERSATION_STORE, 'readonly'); const result = await requestResult(transaction.objectStore(CONVERSATION_STORE).get(validateIdentifier(id, 'conversation id'))); await transactionDone(transaction); database.close(); return result as ConversationRecord | undefined; } export async function listConversations(): Promise[]> { const database = await openDatabase(); const transaction = database.transaction(CONVERSATION_STORE, 'readonly'); const result = await requestResult(transaction.objectStore(CONVERSATION_STORE).getAll()) as ConversationRecord[]; await transactionDone(transaction); database.close(); return result.sort((left, right) => right.updatedAt - left.updatedAt); } export async function deleteConversation(id: string): Promise { const database = await openDatabase(); const transaction = database.transaction(CONVERSATION_STORE, 'readwrite'); transaction.objectStore(CONVERSATION_STORE).delete(validateIdentifier(id, 'conversation id')); await transactionDone(transaction); database.close(); }