refactor: migrate snapshot storage from localStorage to IndexedDB

To improve data consistency and reliability, snapshot storage has been migrated from localStorage to IndexedDB. This change includes adding a new 'snapshots' object store, updating database version to 2, and modifying related functions to use IndexedDB for snapshot operations. The migration ensures better handling of snapshots alongside chat data and removes dependency on localStorage preventing UI lag.
This commit is contained in:
KevIsDev
2025-04-23 12:17:06 +01:00
parent 5c44cb4e00
commit fe37f5ceea
2 changed files with 144 additions and 32 deletions

View File

@@ -1,6 +1,7 @@
import type { Message } from 'ai';
import { createScopedLogger } from '~/utils/logger';
import type { ChatHistoryItem } from './useChatHistory';
import type { Snapshot } from './types'; // Import Snapshot type
export interface IChatMetadata {
gitUrl: string;
@@ -18,15 +19,24 @@ export async function openDatabase(): Promise<IDBDatabase | undefined> {
}
return new Promise((resolve) => {
const request = indexedDB.open('boltHistory', 1);
const request = indexedDB.open('boltHistory', 2);
request.onupgradeneeded = (event: IDBVersionChangeEvent) => {
const db = (event.target as IDBOpenDBRequest).result;
const oldVersion = event.oldVersion;
if (!db.objectStoreNames.contains('chats')) {
const store = db.createObjectStore('chats', { keyPath: 'id' });
store.createIndex('id', 'id', { unique: true });
store.createIndex('urlId', 'urlId', { unique: true });
if (oldVersion < 1) {
if (!db.objectStoreNames.contains('chats')) {
const store = db.createObjectStore('chats', { keyPath: 'id' });
store.createIndex('id', 'id', { unique: true });
store.createIndex('urlId', 'urlId', { unique: true });
}
}
if (oldVersion < 2) {
if (!db.objectStoreNames.contains('snapshots')) {
db.createObjectStore('snapshots', { keyPath: 'chatId' });
}
}
};
@@ -113,12 +123,46 @@ export async function getMessagesById(db: IDBDatabase, id: string): Promise<Chat
export async function deleteById(db: IDBDatabase, id: string): Promise<void> {
return new Promise((resolve, reject) => {
const transaction = db.transaction('chats', 'readwrite');
const store = transaction.objectStore('chats');
const request = store.delete(id);
const transaction = db.transaction(['chats', 'snapshots'], 'readwrite'); // Add snapshots store to transaction
const chatStore = transaction.objectStore('chats');
const snapshotStore = transaction.objectStore('snapshots');
request.onsuccess = () => resolve(undefined);
request.onerror = () => reject(request.error);
const deleteChatRequest = chatStore.delete(id);
const deleteSnapshotRequest = snapshotStore.delete(id); // Also delete snapshot
let chatDeleted = false;
let snapshotDeleted = false;
const checkCompletion = () => {
if (chatDeleted && snapshotDeleted) {
resolve(undefined);
}
};
deleteChatRequest.onsuccess = () => {
chatDeleted = true;
checkCompletion();
};
deleteChatRequest.onerror = () => reject(deleteChatRequest.error);
deleteSnapshotRequest.onsuccess = () => {
snapshotDeleted = true;
checkCompletion();
};
deleteSnapshotRequest.onerror = (event) => {
if ((event.target as IDBRequest).error?.name === 'NotFoundError') {
snapshotDeleted = true;
checkCompletion();
} else {
reject(deleteSnapshotRequest.error);
}
};
transaction.oncomplete = () => {
// This might resolve before checkCompletion if one operation finishes much faster
};
transaction.onerror = () => reject(transaction.error);
});
}
@@ -257,3 +301,43 @@ export async function updateChatMetadata(
await setMessages(db, id, chat.messages, chat.urlId, chat.description, chat.timestamp, metadata);
}
export async function getSnapshot(db: IDBDatabase, chatId: string): Promise<Snapshot | undefined> {
return new Promise((resolve, reject) => {
const transaction = db.transaction('snapshots', 'readonly');
const store = transaction.objectStore('snapshots');
const request = store.get(chatId);
request.onsuccess = () => resolve(request.result?.snapshot as Snapshot | undefined);
request.onerror = () => reject(request.error);
});
}
export async function setSnapshot(db: IDBDatabase, chatId: string, snapshot: Snapshot): Promise<void> {
return new Promise((resolve, reject) => {
const transaction = db.transaction('snapshots', 'readwrite');
const store = transaction.objectStore('snapshots');
const request = store.put({ chatId, snapshot });
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
export async function deleteSnapshot(db: IDBDatabase, chatId: string): Promise<void> {
return new Promise((resolve, reject) => {
const transaction = db.transaction('snapshots', 'readwrite');
const store = transaction.objectStore('snapshots');
const request = store.delete(chatId);
request.onsuccess = () => resolve();
request.onerror = (event) => {
if ((event.target as IDBRequest).error?.name === 'NotFoundError') {
resolve();
} else {
reject(request.error);
}
};
});
}