From 9102f1434083baade89b8e6e5b016021878489fa Mon Sep 17 00:00:00 2001 From: Janne Mareike Koschinski <147375862+janne-koschinski@users.noreply.github.com> Date: Wed, 31 Jan 2024 18:23:52 +0100 Subject: [PATCH] Fix deleteDatabase blocking due to open connections (#144) - Automatically close database connections on delete - Handle "blocked" event in deleteDatabase --- core/src/jsMain/kotlin/Database.kt | 23 +++++++++++++++++------ core/src/jsMain/kotlin/Transaction.kt | 8 ++++---- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/core/src/jsMain/kotlin/Database.kt b/core/src/jsMain/kotlin/Database.kt index 4c6c9bd..9efd695 100644 --- a/core/src/jsMain/kotlin/Database.kt +++ b/core/src/jsMain/kotlin/Database.kt @@ -46,15 +46,25 @@ public suspend fun openDatabase( public suspend fun deleteDatabase(name: String) { val factory = checkNotNull(window.indexedDB) { "Your browser doesn't support IndexedDB." } val request = factory.deleteDatabase(name) - request.onNextEvent("success", "error") { event -> + request.onNextEvent("success", "error", "blocked") { event -> when (event.type) { - "error" -> throw ErrorEventException(event) + "error", "blocked" -> throw ErrorEventException(event) else -> null } } } -public class Database internal constructor(internal val database: IDBDatabase) { +public class Database internal constructor(database: IDBDatabase) { + private var database: IDBDatabase? = database + + init { + // listen for database structure changes (e.g., upgradeneeded while DB is open or deleteDatabase) + database.addEventListener("versionchange", { close() }) + // listen for force close, e.g., browser profile on a USB drive that's ejected or db deleted through dev tools + database.addEventListener("close", { close() }) + } + + internal fun ensureDatabase(): IDBDatabase = checkNotNull(database) { "database is closed" } /** * Inside the [action] block, you must not call any `suspend` functions except for: @@ -68,7 +78,7 @@ public class Database internal constructor(internal val database: IDBDatabase) { action: suspend Transaction.() -> T, ): T = withContext(Dispatchers.Unconfined) { val transaction = Transaction( - database.transaction(arrayOf(*store), "readonly", transactionOptions(durability)), + ensureDatabase().transaction(arrayOf(*store), "readonly", transactionOptions(durability)), ) val result = transaction.action() transaction.awaitCompletion() @@ -87,7 +97,7 @@ public class Database internal constructor(internal val database: IDBDatabase) { action: suspend WriteTransaction.() -> T, ): T = withContext(Dispatchers.Unconfined) { val transaction = WriteTransaction( - database.transaction(arrayOf(*store), "readwrite", transactionOptions(durability)), + ensureDatabase().transaction(arrayOf(*store), "readwrite", transactionOptions(durability)), ) with(transaction) { // Force overlapping transactions to not call `action` until prior transactions complete. @@ -101,7 +111,8 @@ public class Database internal constructor(internal val database: IDBDatabase) { } public fun close() { - database.close() + database?.close() + database = null } } diff --git a/core/src/jsMain/kotlin/Transaction.kt b/core/src/jsMain/kotlin/Transaction.kt index 58ee691..8aee08a 100644 --- a/core/src/jsMain/kotlin/Transaction.kt +++ b/core/src/jsMain/kotlin/Transaction.kt @@ -300,18 +300,18 @@ public class VersionChangeTransaction internal constructor( /** Creates an object-store that uses explicit out-of-line keys. */ public fun Database.createObjectStore(name: String): ObjectStore = - ObjectStore(database.createObjectStore(name)) + ObjectStore(ensureDatabase().createObjectStore(name)) /** Creates an object-store that uses in-line keys. */ public fun Database.createObjectStore(name: String, keyPath: KeyPath): ObjectStore = - ObjectStore(database.createObjectStore(name, keyPath.toWrappedJs())) + ObjectStore(ensureDatabase().createObjectStore(name, keyPath.toWrappedJs())) /** Creates an object-store that uses out-of-line keys with a key-generator. */ public fun Database.createObjectStore(name: String, autoIncrement: AutoIncrement): ObjectStore = - ObjectStore(database.createObjectStore(name, autoIncrement.toJs())) + ObjectStore(ensureDatabase().createObjectStore(name, autoIncrement.toJs())) public fun Database.deleteObjectStore(name: String) { - database.deleteObjectStore(name) + ensureDatabase().deleteObjectStore(name) } public fun ObjectStore.createIndex(name: String, keyPath: KeyPath, unique: Boolean): Index =