Skip to content

Commit

Permalink
feature: add support for rollbacks
Browse files Browse the repository at this point in the history
  • Loading branch information
jacek-marchwicki committed Nov 6, 2024
1 parent a1ffd97 commit cef824a
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 3 deletions.
14 changes: 11 additions & 3 deletions core/src/jsMain/kotlin/Database.kt
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ public class Database internal constructor(
ensureDatabase().transaction(arrayOf(*store), "readonly", transactionOptions(durability)),
)
val result = transaction.action()
transaction.commit()
transaction.awaitCompletion()
result
}
Expand All @@ -107,9 +108,16 @@ public class Database internal constructor(
.openKeyCursor(autoContinue = false)
.collect { it.close() }
}
val result = transaction.action()
transaction.awaitCompletion()
result
try {
val result = transaction.action()
transaction.commit()
transaction.awaitCompletion()
result
} catch (e: Throwable) {
transaction.abort()
transaction.awaitFailure()
throw e
}
}

public fun close() {
Expand Down
21 changes: 21 additions & 0 deletions core/src/jsMain/kotlin/Transaction.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import org.w3c.dom.events.Event
import kotlin.js.Json

public open class Transaction internal constructor(
internal val transaction: IDBTransaction,
Expand All @@ -23,6 +24,26 @@ public open class Transaction internal constructor(
}
}

internal suspend fun awaitFailure() {
transaction.onNextEvent("complete", "abort", "error") { event ->
when (event.type) {
"abort" -> Unit
"error" -> Unit
else -> Unit
}
}
}
internal fun abort() {
transaction.abort()
}

internal fun commit() {
// Check if function exists before calling it.
if (transaction.unsafeCast<Json>()["commit"] != undefined) {
transaction.commit()
}
}

public fun objectStore(name: String): ObjectStore =
ObjectStore(transaction.objectStore(name))

Expand Down
68 changes: 68 additions & 0 deletions core/src/jsTest/kotlin/TransactionTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.juul.indexeddb

import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith

class TransactionTest {
@Test
fun readWithinTransaction() = runTest {
val database = openDatabase("read-within-transaction", 1) { database, oldVersion, newVersion ->
if (oldVersion < 1) {
database.createObjectStore("users", KeyPath("id"))
}
}
onCleanup {
database.close()
deleteDatabase("read-within-transaction")
}

val user = database.writeTransaction("users") {
objectStore("users").add(
jso {
id = "7740f7c4-f889-498a-bc6d-f88dabdcfb9a"
username = "Username"
},
)
objectStore("users")
.get(Key("7740f7c4-f889-498a-bc6d-f88dabdcfb9a"))
}

assertEquals("Username", user.username)
}

@Test
fun whenExceptionIsThrowWithinTransaction_transactionIsAborted() = runTest {
val database = openDatabase("abort-transaction", 1) { database, oldVersion, newVersion ->
if (oldVersion < 1) {
database.createObjectStore("users", KeyPath("id"))
}
}
onCleanup {
database.close()
deleteDatabase("abort-transaction")
}

assertFailsWith<ExceptionToAbortTransaction> {
database.writeTransaction("users") {
objectStore("users").add(
jso {
id = "7740f7c4-f889-498a-bc6d-f88dabdcfb9a"
username = "Username"
},
)

// Abort transaction
throw ExceptionToAbortTransaction()
}
}

// because transaction is aborted, new values shouldn't be stored
val users = database.transaction("users") {
objectStore("users").getAll().toList()
}

assertEquals(listOf(), users)
}
}
private class ExceptionToAbortTransaction : Exception()

0 comments on commit cef824a

Please sign in to comment.