interface StoredHead { blob: Blob timestamp: number } export class HeadStorage { private dbName = 'head-storage' private version = 1 private db: IDBDatabase | null = null async init(): Promise { return new Promise((resolve, reject) => { const request = indexedDB.open(this.dbName, this.version) request.onerror = () => reject(request.error) request.onsuccess = () => { this.db = request.result resolve() } request.onupgradeneeded = () => { const db = request.result if (!db.objectStoreNames.contains('heads')) { db.createObjectStore('heads') } } }) } async store(key: string, blob: Blob): Promise { if (!this.db) await this.init() const transaction = this.db!.transaction(['heads'], 'readwrite') const store = transaction.objectStore('heads') const storedHead: StoredHead = { blob, timestamp: Date.now(), } return new Promise((resolve, reject) => { const request = store.put(storedHead, key) request.onsuccess = () => resolve() request.onerror = () => reject(request.error) }) } async retrieve(key: string): Promise { if (!this.db) await this.init() const transaction = this.db!.transaction(['heads'], 'readonly') const store = transaction.objectStore('heads') return new Promise((resolve, reject) => { const request = store.get(key) request.onsuccess = () => { const result = request.result as StoredHead | undefined if (!result) { resolve(null) return } const url = URL.createObjectURL(result.blob) resolve(url) } request.onerror = () => reject(request.error) }) } async batchRetrieve(keys: string[]): Promise> { if (!this.db) await this.init() const transaction = this.db!.transaction(['heads'], 'readonly') const store = transaction.objectStore('heads') const results: Record = {} return new Promise((resolve, _reject) => { let completedRequests = 0 if (keys.length === 0) { resolve(results) return } for (const key of keys) { const request = store.get(key) request.onsuccess = () => { const result = request.result as StoredHead | undefined if (result) { results[key] = result.blob } else { results[key] = null } completedRequests++ if (completedRequests === keys.length) { resolve(results) } } request.onerror = () => { results[key] = null completedRequests++ if (completedRequests === keys.length) { resolve(results) } } } }) } async cleanupInvalidKeys(validKeys: Set): Promise { if (!this.db) await this.init() const transaction = this.db!.transaction(['heads'], 'readwrite') const store = transaction.objectStore('heads') let deletedCount = 0 return new Promise((resolve, reject) => { const request = store.openCursor() request.onsuccess = (event) => { const cursor = (event.target as IDBRequest).result if (cursor) { const key = cursor.primaryKey as string if (!validKeys.has(key)) { const deleteRequest = cursor.delete() deleteRequest.onsuccess = () => { deletedCount++ } deleteRequest.onerror = () => { console.warn('Failed to delete invalid head entry:', key) } } cursor.continue() } else { resolve(deletedCount) } } request.onerror = () => reject(request.error) }) } async debugCalculateStorage(): Promise { if (!this.db) await this.init() const transaction = this.db!.transaction(['heads'], 'readonly') const store = transaction.objectStore('heads') let totalSize = 0 let count = 0 const entries: Array<{ key: string; size: number }> = [] return new Promise((resolve, reject) => { const request = store.openCursor() request.onsuccess = (event) => { const cursor = (event.target as IDBRequest).result if (cursor) { const key = cursor.primaryKey as string const value = cursor.value as StoredHead const entrySize = value.blob.size totalSize += entrySize count++ entries.push({ key, size: entrySize, }) cursor.continue() } else { console.group('🗄️ Head Storage Debug Info') console.log(`Total entries: ${count}`) console.log(`Total size: ${(totalSize / 1024 / 1024).toFixed(2)} MB`) console.log( `Average size per entry: ${count > 0 ? (totalSize / count / 1024).toFixed(2) : 0} KB`, ) if (entries.length > 0) { const sortedEntries = entries.sort((a, b) => b.size - a.size) console.log( 'Largest entry:', sortedEntries[0].key, '(' + (sortedEntries[0].size / 1024).toFixed(2) + ' KB)', ) console.log( 'Smallest entry:', sortedEntries[sortedEntries.length - 1].key, '(' + (sortedEntries[sortedEntries.length - 1].size / 1024).toFixed(2) + ' KB)', ) } console.groupEnd() resolve() } } request.onerror = () => reject(request.error) }) } async clearAll(): Promise { if (!this.db) await this.init() const transaction = this.db!.transaction(['heads'], 'readwrite') const store = transaction.objectStore('heads') return new Promise((resolve, reject) => { const request = store.clear() request.onsuccess = () => resolve() request.onerror = () => reject(request.error) }) } } export const headStorage = new HeadStorage()