From 7e692f9862d31cf455ea6a9ba545df2f52cf74f1 Mon Sep 17 00:00:00 2001 From: Aryan Keluskar Date: Thu, 6 Nov 2025 22:20:57 -0800 Subject: Migrate Chat Persistence from localStorage to IndexedDB to Fix QuotaExceededError (#560) --- apps/web/package.json | 1 + apps/web/stores/chat.ts | 4 +++- apps/web/stores/indexeddb-storage.ts | 24 ++++++++++++++++++++++++ bun.lock | 3 +++ 4 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 apps/web/stores/indexeddb-storage.ts diff --git a/apps/web/package.json b/apps/web/package.json index e2247046..46bfa8fb 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -59,6 +59,7 @@ "embla-carousel-autoplay": "^8.6.0", "embla-carousel-react": "^8.6.0", "framer-motion": "^12.23.12", + "idb-keyval": "^6.2.2", "is-hotkey": "^0.2.0", "lucide-react": "^0.525.0", "masonic": "^4.1.0", diff --git a/apps/web/stores/chat.ts b/apps/web/stores/chat.ts index ac204442..f1701139 100644 --- a/apps/web/stores/chat.ts +++ b/apps/web/stores/chat.ts @@ -1,7 +1,8 @@ import type { UIMessage } from "@ai-sdk/react" import { create } from "zustand" -import { persist } from "zustand/middleware" +import { persist, createJSONStorage } from "zustand/middleware" import { useCallback } from "react" +import { indexedDBStorage } from "./indexeddb-storage" /** * Deep equality check for UIMessage arrays to prevent unnecessary state updates @@ -175,6 +176,7 @@ export const usePersistentChatStore = create()( }), { name: "supermemory-chats", + storage: createJSONStorage(() => indexedDBStorage), }, ), ) diff --git a/apps/web/stores/indexeddb-storage.ts b/apps/web/stores/indexeddb-storage.ts new file mode 100644 index 00000000..c2a1db91 --- /dev/null +++ b/apps/web/stores/indexeddb-storage.ts @@ -0,0 +1,24 @@ +import { get, set, del } from 'idb-keyval'; + +export const indexedDBStorage = { + getItem: async (name: string) => { + let value = await get(name); + if (value !== undefined) { + return value; + } + // Migrate from localStorage if exists + value = localStorage.getItem(name); + if (value !== null) { + await set(name, value); + localStorage.removeItem(name); + return value; + } + return null; + }, + setItem: async (name: string, value: string) => { + await set(name, value); + }, + removeItem: async (name: string) => { + await del(name); + }, +}; diff --git a/bun.lock b/bun.lock index cad35279..2c82cc12 100644 --- a/bun.lock +++ b/bun.lock @@ -140,6 +140,7 @@ "embla-carousel-autoplay": "^8.6.0", "embla-carousel-react": "^8.6.0", "framer-motion": "^12.23.12", + "idb-keyval": "^6.2.2", "is-hotkey": "^0.2.0", "lucide-react": "^0.525.0", "masonic": "^4.1.0", @@ -2884,6 +2885,8 @@ "iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], + "idb-keyval": ["idb-keyval@6.2.2", "", {}, "sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg=="], + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], -- cgit v1.2.3