Skip to content

Commit 936706c

Browse files
authored
Logging Support (#171)
1 parent 22f4bed commit 936706c

File tree

16 files changed

+339
-100
lines changed

16 files changed

+339
-100
lines changed

core/src/jsMain/kotlin/Database.kt

Lines changed: 71 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@ import com.juul.indexeddb.external.IDBDatabase
44
import com.juul.indexeddb.external.IDBFactory
55
import com.juul.indexeddb.external.IDBVersionChangeEvent
66
import com.juul.indexeddb.external.indexedDB
7+
import com.juul.indexeddb.logs.Logger
8+
import com.juul.indexeddb.logs.NoOpLogger
9+
import com.juul.indexeddb.logs.Type
710
import kotlinx.browser.window
811
import kotlinx.coroutines.Dispatchers
912
import kotlinx.coroutines.withContext
13+
import org.w3c.dom.events.Event
1014

1115
/**
1216
* Inside the [initialize] block, you must not call any `suspend` functions except for:
@@ -17,6 +21,7 @@ import kotlinx.coroutines.withContext
1721
public suspend fun openDatabase(
1822
name: String,
1923
version: Int,
24+
logger: Logger = NoOpLogger,
2025
initialize: suspend VersionChangeTransaction.(
2126
database: Database,
2227
oldVersion: Int,
@@ -25,6 +30,7 @@ public suspend fun openDatabase(
2530
): Database = withContext(Dispatchers.Unconfined) {
2631
val indexedDB: IDBFactory? = js("self.indexedDB || self.webkitIndexedDB") as? IDBFactory
2732
val factory = checkNotNull(indexedDB) { "Your browser doesn't support IndexedDB." }
33+
logger.log(Type.Database) { "Opening database `$name` at version `$version`" }
2834
val request = factory.open(name, version)
2935
val versionChangeEvent = request.onNextEvent("success", "upgradeneeded", "error", "blocked") { event ->
3036
when (event.type) {
@@ -34,36 +40,59 @@ public suspend fun openDatabase(
3440
else -> null
3541
}
3642
}
37-
Database(request.result).also { database ->
43+
Database(request.result, logger).also { database ->
3844
if (versionChangeEvent != null) {
39-
val transaction = VersionChangeTransaction(checkNotNull(request.transaction))
45+
logger.log(Type.Database, versionChangeEvent) {
46+
"Upgrading database `$name` from version `${versionChangeEvent.oldVersion}` to `${versionChangeEvent.newVersion}`"
47+
}
48+
val id = database.transactionId++
49+
logger.log(Type.Transaction) { "Opening versionchange transaction $id on database `$name`" }
50+
val transaction = VersionChangeTransaction(checkNotNull(request.transaction), logger, id)
4051
transaction.initialize(database, versionChangeEvent.oldVersion, versionChangeEvent.newVersion)
41-
transaction.awaitCompletion()
52+
transaction.awaitCompletion { event ->
53+
logger.log(Type.Transaction, event) { "Closed versionchange transaction $id on database `$name`" }
54+
}
4255
}
56+
logger.log(Type.Database) { "Opened database `$name`" }
4357
}
4458
}
4559

46-
public suspend fun deleteDatabase(name: String) {
60+
public suspend fun deleteDatabase(
61+
name: String,
62+
logger: Logger = NoOpLogger,
63+
) {
64+
logger.log(Type.Database) { "Deleting database `$name`" }
4765
val factory = checkNotNull(window.indexedDB) { "Your browser doesn't support IndexedDB." }
4866
val request = factory.deleteDatabase(name)
4967
request.onNextEvent("success", "error", "blocked") { event ->
5068
when (event.type) {
51-
"error", "blocked" -> throw ErrorEventException(event)
52-
else -> null
69+
"error", "blocked" -> {
70+
logger.log(Type.Database, event) { "Delete failed for database `$name`" }
71+
throw ErrorEventException(event)
72+
}
73+
74+
else -> logger.log(Type.Database, event) { "Deleted database `$name`" }
5375
}
5476
}
5577
}
5678

5779
public class Database internal constructor(
5880
database: IDBDatabase,
81+
private val logger: Logger,
5982
) {
83+
private val name = database.name
6084
private var database: IDBDatabase? = database
85+
internal var transactionId = 0L
6186

6287
init {
88+
val callback = { event: Event ->
89+
logger.log(Type.Database, event) { "Closing database `$name` due to event" }
90+
tryClose()
91+
}
6392
// listen for database structure changes (e.g., upgradeneeded while DB is open or deleteDatabase)
64-
database.addEventListener("versionchange", { close() })
93+
database.addEventListener("versionchange", callback)
6594
// listen for force close, e.g., browser profile on a USB drive that's ejected or db deleted through dev tools
66-
database.addEventListener("close", { close() })
95+
database.addEventListener("close", callback)
6796
}
6897

6998
internal fun ensureDatabase(): IDBDatabase = checkNotNull(database) { "database is closed" }
@@ -79,12 +108,21 @@ public class Database internal constructor(
79108
durability: Durability = Durability.Default,
80109
action: suspend Transaction.() -> T,
81110
): T = withContext(Dispatchers.Unconfined) {
111+
val id = transactionId++
112+
logger.log(Type.Transaction) {
113+
"Opened readonly transaction $id using stores ${store.joinToString { "`$it`" }} on database `$name`"
114+
}
115+
82116
val transaction = Transaction(
83117
ensureDatabase().transaction(arrayOf(*store), "readonly", transactionOptions(durability)),
118+
logger,
119+
id,
84120
)
85121
val result = transaction.action()
86122
transaction.commit()
87-
transaction.awaitCompletion()
123+
transaction.awaitCompletion { event ->
124+
logger.log(Type.Transaction, event) { "Closed readonly transaction $id on database `$name`" }
125+
}
88126
result
89127
}
90128

@@ -99,19 +137,26 @@ public class Database internal constructor(
99137
durability: Durability = Durability.Default,
100138
action: suspend WriteTransaction.() -> T,
101139
): T = withContext(Dispatchers.Unconfined) {
140+
val id = transactionId++
141+
logger.log(Type.Transaction) {
142+
"Opening readwrite transaction $id using stores ${store.joinToString { "`$it`" }} on database `$name`"
143+
}
144+
102145
val transaction = WriteTransaction(
103146
ensureDatabase().transaction(arrayOf(*store), "readwrite", transactionOptions(durability)),
147+
logger,
148+
id,
104149
)
105150
with(transaction) {
106151
// Force overlapping transactions to not call `action` until prior transactions complete.
107-
objectStore(store.first())
108-
.openKeyCursor(autoContinue = false)
109-
.collect { it.close() }
152+
objectStore(store.first()).awaitTransaction()
110153
}
111154
try {
112155
val result = transaction.action()
113156
transaction.commit()
114-
transaction.awaitCompletion()
157+
transaction.awaitCompletion { event ->
158+
logger.log(Type.Transaction, event) { "Closed readwrite transaction $id on database `$name`" }
159+
}
115160
result
116161
} catch (e: Throwable) {
117162
transaction.abort()
@@ -121,8 +166,19 @@ public class Database internal constructor(
121166
}
122167

123168
public fun close() {
124-
database?.close()
125-
database = null
169+
logger.log(Type.Database) { "Closing database `$name` due to explicit `close()`" }
170+
tryClose()
171+
}
172+
173+
private fun tryClose() {
174+
val db = database
175+
if (db != null) {
176+
db.close()
177+
database = null
178+
logger.log(Type.Database) { "Closed database `$name`" }
179+
} else {
180+
logger.log(Type.Database) { "Close skipped, database `$name` already closed" }
181+
}
126182
}
127183
}
128184

core/src/jsMain/kotlin/Index.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ import com.juul.indexeddb.external.IDBIndex
77
public class Index internal constructor(
88
internal val index: IDBIndex,
99
) : Queryable() {
10+
11+
override val type: String
12+
get() = "index"
13+
14+
override val name: String
15+
get() = index.name
16+
1017
override fun requestGet(key: Key): Request<dynamic> =
1118
Request(index.get(key.toJs()))
1219

core/src/jsMain/kotlin/ObjectStore.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ import com.juul.indexeddb.external.IDBObjectStore
77
public class ObjectStore internal constructor(
88
internal val objectStore: IDBObjectStore,
99
) : Queryable() {
10+
11+
override val type: String
12+
get() = "object store"
13+
14+
override val name: String
15+
get() = objectStore.name
16+
1017
override fun requestGet(key: Key): Request<dynamic> =
1118
Request(objectStore.get(key.toJs()))
1219

core/src/jsMain/kotlin/Queryable.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import com.juul.indexeddb.external.IDBCursor
44
import com.juul.indexeddb.external.IDBCursorWithValue
55

66
public sealed class Queryable {
7+
internal abstract val type: String
8+
internal abstract val name: String
79
internal abstract fun requestGet(key: Key): Request<dynamic>
810
internal abstract fun requestGetAll(query: Key?): Request<Array<dynamic>>
911
internal abstract fun requestOpenCursor(query: Key?, direction: Cursor.Direction): Request<IDBCursorWithValue?>

0 commit comments

Comments
 (0)