From 31e25522af266ae70b10764d674722a8a1333f7c Mon Sep 17 00:00:00 2001 From: Oskar Eichler <62393985+OskarEichler@users.noreply.github.com> Date: Thu, 27 Aug 2026 20:34:16 +0300 Subject: [PATCH 1/4] fix(web): track transaction completion and connection lifetime --- .changeset/review-indexeddb.md | 6 +++ .../web-module/IndexedDBConnectionRegistry.ts | 40 +++++++---------- .../src/web-module/IndexedDBStorage.ts | 43 ++++++++++--------- 3 files changed, 43 insertions(+), 46 deletions(-) create mode 100644 .changeset/review-indexeddb.md diff --git a/.changeset/review-indexeddb.md b/.changeset/review-indexeddb.md new file mode 100644 index 00000000..6f2afa90 --- /dev/null +++ b/.changeset/review-indexeddb.md @@ -0,0 +1,6 @@ +--- +"@react-native-async-storage/async-storage": patch +--- + +Propagate open/transaction failures through caller promises, wait for deletion commit, close blocking connections and discard terminated connections. + diff --git a/packages/async-storage/src/web-module/IndexedDBConnectionRegistry.ts b/packages/async-storage/src/web-module/IndexedDBConnectionRegistry.ts index 529d75b6..4303232b 100644 --- a/packages/async-storage/src/web-module/IndexedDBConnectionRegistry.ts +++ b/packages/async-storage/src/web-module/IndexedDBConnectionRegistry.ts @@ -1,6 +1,5 @@ import type { DBSchema, IDBPDatabase } from "idb"; import { openDB } from "idb"; -import { AsyncStorageError } from "../AsyncStorageError"; const WebStorageTableName = "entries" as const; @@ -30,36 +29,27 @@ class IndexedDBConnectionRegistry { db.createObjectStore(WebStorageTableName); } }, - blocked: ( - currentVersion: number, - blockedVersion: number | null, - _: IDBVersionChangeEvent - ) => { - throw AsyncStorageError.jsError( - `New version (${blockedVersion}) is blocked by current one (${currentVersion})`, - AsyncStorageError.Type.WebStorageError - ); + blocking: () => { + void db.then((connection) => connection.close()); + if (this.registry.get(dbName) === db) { + this.registry.delete(dbName); + } }, - blocking: ( - currentVersion: number, - blockedVersion: number | null, - _: IDBVersionChangeEvent - ) => { - throw AsyncStorageError.jsError( - `Current db version (${currentVersion}) is blocking upgrade to next version (${blockedVersion})`, - AsyncStorageError.Type.WebStorageError - ); + terminated: () => { + if (this.registry.get(dbName) === db) { + this.registry.delete(dbName); + } }, + }).catch((err) => { + // Return the rejection to callers while allowing a later open to retry. + if (this.registry.get(dbName) === db) { + this.registry.delete(dbName); + } + throw err; }); this.registry.set(dbName, db); - // in case of error while opening, clear the storage to retry - db.catch((err) => { - this.registry.delete(dbName); - throw err; - }); - return db; } } diff --git a/packages/async-storage/src/web-module/IndexedDBStorage.ts b/packages/async-storage/src/web-module/IndexedDBStorage.ts index 1bd16bbc..b00e2ee4 100644 --- a/packages/async-storage/src/web-module/IndexedDBStorage.ts +++ b/packages/async-storage/src/web-module/IndexedDBStorage.ts @@ -12,14 +12,15 @@ class IndexedDBStorage { const tx = db.transaction(registry.TableName, "readonly"); const store = tx.objectStore(registry.TableName); - const result = await Promise.all( - keys.map(async (key) => { - const entry = await store.get(key); - return { key, value: entry ?? null }; - }) - ); - - await tx.done; + const [result] = await Promise.all([ + Promise.all( + keys.map(async (key) => { + const entry = await store.get(key); + return { key, value: entry ?? null }; + }) + ), + tx.done, + ]); return result; } catch (e: any) { throw this.createError(e); @@ -34,14 +35,15 @@ class IndexedDBStorage { const tx = db.transaction(registry.TableName, "readwrite"); const store = tx.objectStore(registry.TableName); - const result = await Promise.all( - values.map(async (entry) => { - await store.put(entry.value, entry.key); - return { key: entry.key, value: entry.value }; - }) - ); - - await tx.done; + const [result] = await Promise.all([ + Promise.all( + values.map(async (entry) => { + await store.put(entry.value, entry.key); + return { key: entry.key, value: entry.value }; + }) + ), + tx.done, + ]); return result; } catch (e: any) { throw this.createError(e); @@ -54,11 +56,10 @@ class IndexedDBStorage { const tx = db.transaction(registry.TableName, "readwrite"); const store = tx.objectStore(registry.TableName); - await Promise.all( - keys.map(async (key) => { - await store.delete(key); - }) - ); + await Promise.all([ + tx.done, + ...keys.map(async (key) => await store.delete(key)), + ]); } catch (e: any) { throw this.createError(e); } From 46989c114f116dbca5d7a2a2eaac6f5ab230deb0 Mon Sep 17 00:00:00 2001 From: Oskar Eichler <62393985+OskarEichler@users.noreply.github.com> Date: Fri, 28 Aug 2026 14:30:15 +0300 Subject: [PATCH 2/4] Avoid redundant async wrappers for IndexedDB deletes --- packages/async-storage/src/web-module/IndexedDBStorage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/async-storage/src/web-module/IndexedDBStorage.ts b/packages/async-storage/src/web-module/IndexedDBStorage.ts index b00e2ee4..a937d2aa 100644 --- a/packages/async-storage/src/web-module/IndexedDBStorage.ts +++ b/packages/async-storage/src/web-module/IndexedDBStorage.ts @@ -58,7 +58,7 @@ class IndexedDBStorage { await Promise.all([ tx.done, - ...keys.map(async (key) => await store.delete(key)), + ...keys.map((key) => store.delete(key)), ]); } catch (e: any) { throw this.createError(e); From 75f2b3901f2d37517468ca9b87d8d49044e94778 Mon Sep 17 00:00:00 2001 From: Oskar Eichler Date: Mon, 31 Aug 2026 17:08:56 +0700 Subject: [PATCH 3/4] Preserve blocked upgrade errors --- .../src/web-module/IndexedDBConnectionRegistry.ts | 7 +++++++ packages/async-storage/src/web-module/IndexedDBStorage.ts | 5 +---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/async-storage/src/web-module/IndexedDBConnectionRegistry.ts b/packages/async-storage/src/web-module/IndexedDBConnectionRegistry.ts index 4303232b..34616306 100644 --- a/packages/async-storage/src/web-module/IndexedDBConnectionRegistry.ts +++ b/packages/async-storage/src/web-module/IndexedDBConnectionRegistry.ts @@ -1,5 +1,6 @@ import type { DBSchema, IDBPDatabase } from "idb"; import { openDB } from "idb"; +import { AsyncStorageError } from "../AsyncStorageError"; const WebStorageTableName = "entries" as const; @@ -29,6 +30,12 @@ class IndexedDBConnectionRegistry { db.createObjectStore(WebStorageTableName); } }, + blocked: (currentVersion: number, blockedVersion: number | null) => { + throw AsyncStorageError.jsError( + `New version (${blockedVersion}) is blocked by current one (${currentVersion})`, + AsyncStorageError.Type.WebStorageError + ); + }, blocking: () => { void db.then((connection) => connection.close()); if (this.registry.get(dbName) === db) { diff --git a/packages/async-storage/src/web-module/IndexedDBStorage.ts b/packages/async-storage/src/web-module/IndexedDBStorage.ts index a937d2aa..d90d7df2 100644 --- a/packages/async-storage/src/web-module/IndexedDBStorage.ts +++ b/packages/async-storage/src/web-module/IndexedDBStorage.ts @@ -56,10 +56,7 @@ class IndexedDBStorage { const tx = db.transaction(registry.TableName, "readwrite"); const store = tx.objectStore(registry.TableName); - await Promise.all([ - tx.done, - ...keys.map((key) => store.delete(key)), - ]); + await Promise.all([tx.done, ...keys.map((key) => store.delete(key))]); } catch (e: any) { throw this.createError(e); } From 3565f9847ee2196651ae2d8a62c7aca5e5759ab2 Mon Sep 17 00:00:00 2001 From: Oskar Eichler <62393985+OskarEichler@users.noreply.github.com> Date: Tue, 1 Sep 2026 12:01:17 +0300 Subject: [PATCH 4/4] Preserve blocking upgrade errors --- .../src/web-module/IndexedDBConnectionRegistry.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/async-storage/src/web-module/IndexedDBConnectionRegistry.ts b/packages/async-storage/src/web-module/IndexedDBConnectionRegistry.ts index 34616306..8016496c 100644 --- a/packages/async-storage/src/web-module/IndexedDBConnectionRegistry.ts +++ b/packages/async-storage/src/web-module/IndexedDBConnectionRegistry.ts @@ -36,11 +36,15 @@ class IndexedDBConnectionRegistry { AsyncStorageError.Type.WebStorageError ); }, - blocking: () => { + blocking: (currentVersion: number, blockedVersion: number | null) => { void db.then((connection) => connection.close()); if (this.registry.get(dbName) === db) { this.registry.delete(dbName); } + throw AsyncStorageError.jsError( + `Current db version (${currentVersion}) is blocking upgrade to next version (${blockedVersion})`, + AsyncStorageError.Type.WebStorageError + ); }, terminated: () => { if (this.registry.get(dbName) === db) {