From bbd0d59ca98f2c6c7b342705a68140136669d637 Mon Sep 17 00:00:00 2001 From: Eliezer Graber Date: Mon, 2 Sep 2024 15:40:52 -0400 Subject: [PATCH] Commonize --- README.md | 2 +- core/build.gradle.kts | 21 +- .../kotlin/Cursor.kt | 16 +- .../kotlin/CursorStart.kt | 13 +- .../kotlin/Database.kt | 71 ++-- .../kotlin/Exceptions.kt | 4 +- core/src/commonMain/kotlin/Index.kt | 31 ++ core/src/commonMain/kotlin/JsArray.kt | 30 ++ core/src/commonMain/kotlin/Jso.kt | 9 + core/src/commonMain/kotlin/Key.kt | 39 +++ core/src/commonMain/kotlin/ObjectStore.kt | 25 ++ core/src/commonMain/kotlin/OnNextEvent.kt | 29 ++ core/src/commonMain/kotlin/Platform.kt | 16 + core/src/commonMain/kotlin/Queryable.kt | 13 + core/src/commonMain/kotlin/Request.kt | 6 + .../kotlin/Transaction.kt | 60 ++-- .../kotlin/AutoIncrementKeyObjectStore.kt | 22 +- .../kotlin/InLineKeyObjectStore.kt | 26 +- core/src/commonTest/kotlin/KeyTests.kt | 40 +++ .../kotlin/OutOfLineKeyObjectStore.kt | 24 +- .../{jsTest => commonTest}/kotlin/Samples.kt | 28 +- core/src/jsMain/kotlin/Cursor.kt | 53 --- core/src/jsMain/kotlin/CursorStart.kt | 33 -- core/src/jsMain/kotlin/Database.kt | 123 ------- core/src/jsMain/kotlin/Durability.kt | 19 -- core/src/jsMain/kotlin/Exceptions.kt | 25 -- core/src/jsMain/kotlin/Index.kt | 24 -- core/src/jsMain/kotlin/Jso.kt | 7 +- core/src/jsMain/kotlin/Key.kt | 63 ---- core/src/jsMain/kotlin/ObjectStore.kt | 24 -- core/src/jsMain/kotlin/OnNextEvent.kt | 27 -- core/src/jsMain/kotlin/Platform.kt | 16 + core/src/jsMain/kotlin/Queryable.kt | 12 - core/src/jsMain/kotlin/Request.kt | 7 - core/src/jsMain/kotlin/Transaction.js.kt | 14 + core/src/jsMain/kotlin/Transaction.kt | 323 ------------------ .../kotlin/AutoIncrementKeyObjectStore.kt | 30 -- core/src/jsTest/kotlin/Common.kt | 40 --- .../src/jsTest/kotlin/InLineKeyObjectStore.kt | 35 -- core/src/jsTest/kotlin/KeyTests.kt | 59 ---- .../jsTest/kotlin/OutOfLineKeyObjectStore.kt | 30 -- core/src/wasmJsMain/kotlin/Index.kt | 25 -- core/src/wasmJsMain/kotlin/JsArray.kt | 24 -- core/src/wasmJsMain/kotlin/Jso.kt | 4 +- core/src/wasmJsMain/kotlin/Key.kt | 68 ---- core/src/wasmJsMain/kotlin/ObjectStore.kt | 25 -- core/src/wasmJsMain/kotlin/OnNextEvent.kt | 27 -- core/src/wasmJsMain/kotlin/Platform.kt | 11 + core/src/wasmJsMain/kotlin/Queryable.kt | 13 - core/src/wasmJsMain/kotlin/Request.kt | 7 - core/src/wasmJsTest/kotlin/Common.kt | 40 --- core/src/wasmJsTest/kotlin/KeyTests.kt | 70 ---- core/src/wasmJsTest/kotlin/Samples.kt | 123 ------- external/build.gradle.kts | 7 + external/src/commonMain/kotlin/IDBCursor.kt | 22 ++ external/src/commonMain/kotlin/IDBDatabase.kt | 24 ++ external/src/commonMain/kotlin/IDBFactory.kt | 7 + external/src/commonMain/kotlin/IDBIndex.kt | 7 + .../src/commonMain/kotlin/IDBIndexOptions.kt | 8 + external/src/commonMain/kotlin/IDBKey.kt | 68 ++++ external/src/commonMain/kotlin/IDBKeyRange.kt | 57 ++++ .../src/commonMain/kotlin/IDBObjectStore.kt | 21 ++ .../kotlin/IDBObjectStoreOptions.kt | 15 + .../src/commonMain/kotlin/IDBQueryable.kt | 43 +++ external/src/commonMain/kotlin/IDBRequest.kt | 16 + .../src/commonMain/kotlin/IDBTransaction.kt | 14 + .../kotlin/IDBTransactionOptions.kt | 16 + .../kotlin/IDBVersionChangeEvent.kt | 7 + external/src/commonMain/kotlin/Interop.kt | 73 ++++ external/src/jsMain/kotlin/IDBCursor.kt | 23 +- external/src/jsMain/kotlin/IDBDatabase.kt | 30 +- external/src/jsMain/kotlin/IDBFactory.kt | 13 +- external/src/jsMain/kotlin/IDBIndex.kt | 7 +- external/src/jsMain/kotlin/IDBIndexOptions.kt | 10 + external/src/jsMain/kotlin/IDBKey.kt | 3 + external/src/jsMain/kotlin/IDBKeyRange.kt | 15 +- external/src/jsMain/kotlin/IDBObjectStore.kt | 24 +- .../jsMain/kotlin/IDBObjectStoreOptions.kt | 14 + external/src/jsMain/kotlin/IDBQueryable.kt | 51 ++- external/src/jsMain/kotlin/IDBRequest.kt | 23 +- external/src/jsMain/kotlin/IDBTransaction.kt | 24 +- .../jsMain/kotlin/IDBTransactionOptions.kt | 11 + .../jsMain/kotlin/IDBVersionChangeEvent.kt | 9 +- external/src/jsMain/kotlin/Interop.kt | 106 ++++++ external/src/jsMain/kotlin/Jso.kt | 7 + external/src/wasmJsMain/kotlin/IDBCursor.kt | 23 +- external/src/wasmJsMain/kotlin/IDBDatabase.kt | 29 +- external/src/wasmJsMain/kotlin/IDBFactory.kt | 14 +- external/src/wasmJsMain/kotlin/IDBIndex.kt | 7 +- .../src/wasmJsMain/kotlin/IDBIndexOptions.kt | 10 +- external/src/wasmJsMain/kotlin/IDBKey.kt | 2 +- external/src/wasmJsMain/kotlin/IDBKeyRange.kt | 23 +- .../src/wasmJsMain/kotlin/IDBObjectStore.kt | 28 +- .../kotlin/IDBObjectStoreOptions.kt | 17 +- .../src/wasmJsMain/kotlin/IDBQueryable.kt | 91 +++-- external/src/wasmJsMain/kotlin/IDBRequest.kt | 23 +- .../src/wasmJsMain/kotlin/IDBTransaction.kt | 24 +- .../kotlin/IDBTransactionOptions.kt | 16 +- .../kotlin/IDBVersionChangeEvent.kt | 9 +- external/src/wasmJsMain/kotlin/Interop.kt | 103 ++++++ external/src/wasmJsMain/kotlin/Jso.kt | 7 + .../src/wasmJsMain/kotlin/ReadonlyArray.kt | 3 - gradle/libs.versions.toml | 1 + 103 files changed, 1345 insertions(+), 1786 deletions(-) rename core/src/{wasmJsMain => commonMain}/kotlin/Cursor.kt (74%) rename core/src/{wasmJsMain => commonMain}/kotlin/CursorStart.kt (72%) rename core/src/{wasmJsMain => commonMain}/kotlin/Database.kt (77%) rename core/src/{wasmJsMain => commonMain}/kotlin/Exceptions.kt (92%) create mode 100644 core/src/commonMain/kotlin/Index.kt create mode 100644 core/src/commonMain/kotlin/JsArray.kt create mode 100644 core/src/commonMain/kotlin/Jso.kt create mode 100644 core/src/commonMain/kotlin/Key.kt create mode 100644 core/src/commonMain/kotlin/ObjectStore.kt create mode 100644 core/src/commonMain/kotlin/OnNextEvent.kt create mode 100644 core/src/commonMain/kotlin/Platform.kt create mode 100644 core/src/commonMain/kotlin/Queryable.kt create mode 100644 core/src/commonMain/kotlin/Request.kt rename core/src/{wasmJsMain => commonMain}/kotlin/Transaction.kt (87%) rename core/src/{wasmJsTest => commonTest}/kotlin/AutoIncrementKeyObjectStore.kt (64%) rename core/src/{wasmJsTest => commonTest}/kotlin/InLineKeyObjectStore.kt (58%) create mode 100644 core/src/commonTest/kotlin/KeyTests.kt rename core/src/{wasmJsTest => commonTest}/kotlin/OutOfLineKeyObjectStore.kt (61%) rename core/src/{jsTest => commonTest}/kotlin/Samples.kt (82%) delete mode 100644 core/src/jsMain/kotlin/Cursor.kt delete mode 100644 core/src/jsMain/kotlin/CursorStart.kt delete mode 100644 core/src/jsMain/kotlin/Database.kt delete mode 100644 core/src/jsMain/kotlin/Durability.kt delete mode 100644 core/src/jsMain/kotlin/Exceptions.kt delete mode 100644 core/src/jsMain/kotlin/Index.kt delete mode 100644 core/src/jsMain/kotlin/Key.kt delete mode 100644 core/src/jsMain/kotlin/ObjectStore.kt delete mode 100644 core/src/jsMain/kotlin/OnNextEvent.kt create mode 100644 core/src/jsMain/kotlin/Platform.kt delete mode 100644 core/src/jsMain/kotlin/Queryable.kt delete mode 100644 core/src/jsMain/kotlin/Request.kt create mode 100644 core/src/jsMain/kotlin/Transaction.js.kt delete mode 100644 core/src/jsMain/kotlin/Transaction.kt delete mode 100644 core/src/jsTest/kotlin/AutoIncrementKeyObjectStore.kt delete mode 100644 core/src/jsTest/kotlin/Common.kt delete mode 100644 core/src/jsTest/kotlin/InLineKeyObjectStore.kt delete mode 100644 core/src/jsTest/kotlin/KeyTests.kt delete mode 100644 core/src/jsTest/kotlin/OutOfLineKeyObjectStore.kt delete mode 100644 core/src/wasmJsMain/kotlin/Index.kt delete mode 100644 core/src/wasmJsMain/kotlin/JsArray.kt delete mode 100644 core/src/wasmJsMain/kotlin/Key.kt delete mode 100644 core/src/wasmJsMain/kotlin/ObjectStore.kt delete mode 100644 core/src/wasmJsMain/kotlin/OnNextEvent.kt create mode 100644 core/src/wasmJsMain/kotlin/Platform.kt delete mode 100644 core/src/wasmJsMain/kotlin/Queryable.kt delete mode 100644 core/src/wasmJsMain/kotlin/Request.kt delete mode 100644 core/src/wasmJsTest/kotlin/Common.kt delete mode 100644 core/src/wasmJsTest/kotlin/KeyTests.kt delete mode 100644 core/src/wasmJsTest/kotlin/Samples.kt create mode 100644 external/src/commonMain/kotlin/IDBCursor.kt create mode 100644 external/src/commonMain/kotlin/IDBDatabase.kt create mode 100644 external/src/commonMain/kotlin/IDBFactory.kt create mode 100644 external/src/commonMain/kotlin/IDBIndex.kt create mode 100644 external/src/commonMain/kotlin/IDBIndexOptions.kt create mode 100644 external/src/commonMain/kotlin/IDBKey.kt create mode 100644 external/src/commonMain/kotlin/IDBKeyRange.kt create mode 100644 external/src/commonMain/kotlin/IDBObjectStore.kt create mode 100644 external/src/commonMain/kotlin/IDBObjectStoreOptions.kt create mode 100644 external/src/commonMain/kotlin/IDBQueryable.kt create mode 100644 external/src/commonMain/kotlin/IDBRequest.kt create mode 100644 external/src/commonMain/kotlin/IDBTransaction.kt create mode 100644 external/src/commonMain/kotlin/IDBTransactionOptions.kt create mode 100644 external/src/commonMain/kotlin/IDBVersionChangeEvent.kt create mode 100644 external/src/commonMain/kotlin/Interop.kt create mode 100644 external/src/jsMain/kotlin/IDBIndexOptions.kt create mode 100644 external/src/jsMain/kotlin/IDBKey.kt create mode 100644 external/src/jsMain/kotlin/IDBObjectStoreOptions.kt create mode 100644 external/src/jsMain/kotlin/IDBTransactionOptions.kt create mode 100644 external/src/jsMain/kotlin/Interop.kt create mode 100644 external/src/jsMain/kotlin/Jso.kt create mode 100644 external/src/wasmJsMain/kotlin/Interop.kt create mode 100644 external/src/wasmJsMain/kotlin/Jso.kt delete mode 100644 external/src/wasmJsMain/kotlin/ReadonlyArray.kt diff --git a/README.md b/README.md index 88d3774..c434fdd 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ a [`WriteTransaction`] sharing that store. ```kotlin val bill = database.transaction("customers") { - objectStore("customers").get(Key("444-44-4444")) as Customer + objectStore("customers").get(IDBKey("444-44-4444")) as Customer } assertEquals("Bill", bill.name) ``` diff --git a/core/build.gradle.kts b/core/build.gradle.kts index ba858ad..2c1ae33 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -1,4 +1,5 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl +import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask plugins { kotlin("multiplatform") @@ -23,28 +24,28 @@ kotlin { sourceSets { commonMain.dependencies { + api(project(":external")) + api(libs.coroutines.core) } commonTest.dependencies { - implementation(kotlin("test-common")) - implementation(kotlin("test-annotations-common")) - } - - jsMain.dependencies { - implementation(project(":external")) + implementation(kotlin("test")) + implementation(libs.coroutines.test) } jsTest.dependencies { implementation(kotlin("test-js")) } - wasmJsMain.dependencies { - implementation(project(":external")) - } - wasmJsTest.dependencies { implementation(kotlin("test-wasm-js")) } } } + +tasks.withType>().configureEach { + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + } +} diff --git a/core/src/wasmJsMain/kotlin/Cursor.kt b/core/src/commonMain/kotlin/Cursor.kt similarity index 74% rename from core/src/wasmJsMain/kotlin/Cursor.kt rename to core/src/commonMain/kotlin/Cursor.kt index 2250ba0..be6127f 100644 --- a/core/src/wasmJsMain/kotlin/Cursor.kt +++ b/core/src/commonMain/kotlin/Cursor.kt @@ -1,17 +1,17 @@ -package com.juul.indexeddb - import com.juul.indexeddb.external.IDBCursor import com.juul.indexeddb.external.IDBCursorWithValue +import com.juul.indexeddb.external.IDBKey +import com.juul.indexeddb.external.JsAny import kotlinx.coroutines.channels.SendChannel public open class Cursor internal constructor( internal open val cursor: IDBCursor, private val channel: SendChannel<*>, ) { - public val key: JsAny + public val key: IDBKey get() = cursor.key - public val primaryKey: JsAny + public val primaryKey: IDBKey get() = cursor.primaryKey public fun close() { @@ -26,12 +26,12 @@ public open class Cursor internal constructor( cursor.advance(count) } - public fun `continue`(key: Key) { - cursor.`continue`(key.toJs()) + public fun `continue`(key: IDBKey) { + cursor.`continue`(key) } - public fun continuePrimaryKey(key: Key, primaryKey: Key) { - cursor.continuePrimaryKey(key.toJs(), primaryKey.toJs()) + public fun continuePrimaryKey(key: IDBKey, primaryKey: IDBKey) { + cursor.continuePrimaryKey(key, primaryKey) } public enum class Direction( diff --git a/core/src/wasmJsMain/kotlin/CursorStart.kt b/core/src/commonMain/kotlin/CursorStart.kt similarity index 72% rename from core/src/wasmJsMain/kotlin/CursorStart.kt rename to core/src/commonMain/kotlin/CursorStart.kt index 8370720..70f8851 100644 --- a/core/src/wasmJsMain/kotlin/CursorStart.kt +++ b/core/src/commonMain/kotlin/CursorStart.kt @@ -1,6 +1,5 @@ -package com.juul.indexeddb - import com.juul.indexeddb.external.IDBCursor +import com.juul.indexeddb.external.IDBKey public sealed class CursorStart { @@ -15,19 +14,19 @@ public sealed class CursorStart { } public data class Continue( - val key: Key, + val key: IDBKey, ) : CursorStart() { override fun apply(cursor: IDBCursor) { - cursor.`continue`(key.toJs()) + cursor.`continue`(key) } } public data class ContinuePrimaryKey( - val key: Key, - val primaryKey: Key, + val key: IDBKey, + val primaryKey: IDBKey, ) : CursorStart() { override fun apply(cursor: IDBCursor) { - cursor.continuePrimaryKey(key.toJs(), primaryKey.toJs()) + cursor.continuePrimaryKey(key, primaryKey) } } } diff --git a/core/src/wasmJsMain/kotlin/Database.kt b/core/src/commonMain/kotlin/Database.kt similarity index 77% rename from core/src/wasmJsMain/kotlin/Database.kt rename to core/src/commonMain/kotlin/Database.kt index 6351417..70b0a9a 100644 --- a/core/src/wasmJsMain/kotlin/Database.kt +++ b/core/src/commonMain/kotlin/Database.kt @@ -1,12 +1,11 @@ -package com.juul.indexeddb - import com.juul.indexeddb.external.IDBDatabase import com.juul.indexeddb.external.IDBFactory import com.juul.indexeddb.external.IDBTransactionDurability import com.juul.indexeddb.external.IDBTransactionOptions import com.juul.indexeddb.external.IDBVersionChangeEvent import com.juul.indexeddb.external.indexedDB -import kotlinx.browser.window +import com.juul.indexeddb.external.window +import com.juul.indexeddb.selfIndexedDB import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -81,23 +80,28 @@ public class Database internal constructor( * - `suspend` functions composed entirely of other legal functions */ public suspend fun transaction( - vararg store: String, + store: String, + vararg moreStores: String, durability: IDBTransactionDurability = IDBTransactionDurability.Default, action: suspend Transaction.() -> T, ): T = withContext(Dispatchers.Unconfined) { - check(store.isNotEmpty()) { - "At least one store needs to be passed to transaction" - } + val transaction = when { + moreStores.isEmpty() -> Transaction( + ensureDatabase().transaction( + storeName = store, + mode = "readonly", + options = IDBTransactionOptions(durability), + ), + ) - val transaction = Transaction( - ensureDatabase().transaction( - storeNames = ReadonlyArray( - *store.map { it.toJsString() }.toTypedArray(), + else -> Transaction( + ensureDatabase().transaction( + storeNames = toReadonlyArray(store, *moreStores), + mode = "readonly", + options = IDBTransactionOptions(durability), ), - mode = "readonly", - options = IDBTransactionOptions(durability), - ), - ) + ) + } val result = transaction.action() transaction.awaitCompletion() result @@ -110,28 +114,34 @@ public class Database internal constructor( * - `suspend` functions composed entirely of other legal functions */ public suspend fun writeTransaction( - vararg store: String, + store: String, + vararg moreStores: String, durability: IDBTransactionDurability = IDBTransactionDurability.Default, action: suspend WriteTransaction.() -> T, ): T = withContext(Dispatchers.Unconfined) { - check(store.isNotEmpty()) { - "At least one store needs to be passed to writeTransaction" - } + val transaction = when { + moreStores.isEmpty() -> WriteTransaction( + ensureDatabase() + .transaction( + storeName = store, + mode = "readwrite", + options = IDBTransactionOptions(durability), + ), + ) - val transaction = WriteTransaction( - ensureDatabase() - .transaction( - storeNames = ReadonlyArray( - *store.map { it.toJsString() }.toTypedArray(), + else -> WriteTransaction( + ensureDatabase() + .transaction( + storeNames = toReadonlyArray(store, *moreStores), + mode = "readwrite", + options = IDBTransactionOptions(durability), ), - mode = "readwrite", - options = IDBTransactionOptions(durability), - ), - ) + ) + } with(transaction) { // Force overlapping transactions to not call `action` until prior transactions complete. - objectStore(store.first()) + objectStore(store) .openKeyCursor(autoContinue = false) .collect { it.close() } } @@ -145,6 +155,3 @@ public class Database internal constructor( database = null } } - -@Suppress("RedundantNullableReturnType") -private val selfIndexedDB: IDBFactory? = js("self.indexedDB || self.webkitIndexedDB") diff --git a/core/src/wasmJsMain/kotlin/Exceptions.kt b/core/src/commonMain/kotlin/Exceptions.kt similarity index 92% rename from core/src/wasmJsMain/kotlin/Exceptions.kt rename to core/src/commonMain/kotlin/Exceptions.kt index 2699062..6d9718c 100644 --- a/core/src/wasmJsMain/kotlin/Exceptions.kt +++ b/core/src/commonMain/kotlin/Exceptions.kt @@ -1,6 +1,4 @@ -package com.juul.indexeddb - -import org.w3c.dom.events.Event +import com.juul.indexeddb.external.Event public abstract class EventException( message: String?, diff --git a/core/src/commonMain/kotlin/Index.kt b/core/src/commonMain/kotlin/Index.kt new file mode 100644 index 0000000..a8331df --- /dev/null +++ b/core/src/commonMain/kotlin/Index.kt @@ -0,0 +1,31 @@ +import com.juul.indexeddb.external.IDBCursor +import com.juul.indexeddb.external.IDBCursorWithValue +import com.juul.indexeddb.external.IDBIndex +import com.juul.indexeddb.external.IDBKey +import com.juul.indexeddb.external.JsNumber +import com.juul.indexeddb.external.ReadonlyArray + +public class Index internal constructor( + internal val index: IDBIndex, +) : Queryable() { + override fun requestGet(key: IDBKey): Request<*> = + Request(index.get(key)) + + override fun requestGetAll(query: IDBKey?): Request> = + Request(index.getAll(query)) + + override fun requestOpenCursor( + query: IDBKey?, + direction: Cursor.Direction, + ): Request = + Request(index.openCursor(query, direction.constant)) + + override fun requestOpenKeyCursor( + query: IDBKey?, + direction: Cursor.Direction, + ): Request = + Request(index.openKeyCursor(query, direction.constant)) + + override fun requestCount(query: IDBKey?): Request = + Request(index.count(query)) +} diff --git a/core/src/commonMain/kotlin/JsArray.kt b/core/src/commonMain/kotlin/JsArray.kt new file mode 100644 index 0000000..b943c96 --- /dev/null +++ b/core/src/commonMain/kotlin/JsArray.kt @@ -0,0 +1,30 @@ +import com.juul.indexeddb.external.JsAny +import com.juul.indexeddb.external.JsArray +import com.juul.indexeddb.external.JsString +import com.juul.indexeddb.external.ReadonlyArray +import com.juul.indexeddb.external.set +import com.juul.indexeddb.external.toJsString +import com.juul.indexeddb.toReadonlyArray + +internal fun toReadonlyArray(value: String, vararg moreValues: String): ReadonlyArray = + JsArray() + .apply { + set(0, value.toJsString()) + moreValues.forEachIndexed { index, s -> + set(index + 1, s.toJsString()) + } + }.toReadonlyArray() + +internal fun Iterable.toJsArray(): JsArray = + JsArray().apply { + forEachIndexed { index, s -> + set(index, s?.toJsString()) + } + } + +internal fun JsArray(vararg values: T): JsArray = + JsArray().apply { + for (i in values.indices) { + set(i, values[i]) + } + } diff --git a/core/src/commonMain/kotlin/Jso.kt b/core/src/commonMain/kotlin/Jso.kt new file mode 100644 index 0000000..e7a44ad --- /dev/null +++ b/core/src/commonMain/kotlin/Jso.kt @@ -0,0 +1,9 @@ +package com.juul.indexeddb + +import com.juul.indexeddb.external.JsAny + +// Copied from: +// https://github.com/JetBrains/kotlin-wrappers/blob/91b2c1568ec6f779af5ec10d89b5e2cbdfe785ff/kotlin-extensions/src/main/kotlin/kotlinx/js/jso.kt + +internal expect fun jso(): T +internal fun jso(block: T.() -> Unit): T = jso().apply(block) diff --git a/core/src/commonMain/kotlin/Key.kt b/core/src/commonMain/kotlin/Key.kt new file mode 100644 index 0000000..0d11c86 --- /dev/null +++ b/core/src/commonMain/kotlin/Key.kt @@ -0,0 +1,39 @@ +import com.juul.indexeddb.external.IDBKey +import com.juul.indexeddb.external.IDBKeyRange +import com.juul.indexeddb.external.JsAny +import com.juul.indexeddb.external.toJsString + +public object AutoIncrement + +public class KeyPath private constructor( + private val paths: List, +) { + init { + require(paths.isNotEmpty()) { "A key path must have at least one member." } + } + + public constructor(path: String?, vararg morePaths: String?) : this(listOf(path, *morePaths)) + + internal fun toJs(): JsAny? = if (paths.size == 1) paths[0]?.toJsString() else paths.toJsArray() +} + +public fun lowerBound( + x: JsAny?, + open: Boolean = false, +): IDBKey = IDBKey(IDBKeyRange.lowerBound(x, open)) + +public fun upperBound( + y: JsAny?, + open: Boolean = false, +): IDBKey = IDBKey(IDBKeyRange.upperBound(y, open)) + +public fun bound( + x: JsAny?, + y: JsAny?, + lowerOpen: Boolean = false, + upperOpen: Boolean = false, +): IDBKey = IDBKey(IDBKeyRange.bound(x, y, lowerOpen, upperOpen)) + +public fun only( + z: JsAny?, +): IDBKey = IDBKey(IDBKeyRange.only(z)) diff --git a/core/src/commonMain/kotlin/ObjectStore.kt b/core/src/commonMain/kotlin/ObjectStore.kt new file mode 100644 index 0000000..5e74714 --- /dev/null +++ b/core/src/commonMain/kotlin/ObjectStore.kt @@ -0,0 +1,25 @@ +import com.juul.indexeddb.external.IDBCursor +import com.juul.indexeddb.external.IDBCursorWithValue +import com.juul.indexeddb.external.IDBKey +import com.juul.indexeddb.external.IDBObjectStore +import com.juul.indexeddb.external.JsNumber +import com.juul.indexeddb.external.ReadonlyArray + +public class ObjectStore internal constructor( + internal val objectStore: IDBObjectStore, +) : Queryable() { + override fun requestGet(key: IDBKey): Request<*> = + Request(objectStore.get(key)) + + override fun requestGetAll(query: IDBKey?): Request> = + Request(objectStore.getAll(query)) + + override fun requestOpenCursor(query: IDBKey?, direction: Cursor.Direction): Request = + Request(objectStore.openCursor(query, direction.constant)) + + override fun requestOpenKeyCursor(query: IDBKey?, direction: Cursor.Direction): Request = + Request(objectStore.openKeyCursor(query, direction.constant)) + + override fun requestCount(query: IDBKey?): Request = + Request(objectStore.count(query)) +} diff --git a/core/src/commonMain/kotlin/OnNextEvent.kt b/core/src/commonMain/kotlin/OnNextEvent.kt new file mode 100644 index 0000000..c6b0672 --- /dev/null +++ b/core/src/commonMain/kotlin/OnNextEvent.kt @@ -0,0 +1,29 @@ +import com.juul.indexeddb.external.Event +import com.juul.indexeddb.external.EventTarget +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException + +/** Subscribes to events matching [type] and [moreTypes], unsubscribing immediately before [action] is called. */ +internal suspend fun EventTarget.onNextEvent( + type: String, + vararg moreTypes: String, + action: (Event) -> T, +): T = suspendCancellableCoroutine { continuation -> + lateinit var callback: (Event) -> Unit + callback = { event -> + removeEventListener(type, callback) + moreTypes.forEach { type -> removeEventListener(type, callback) } + try { + continuation.resume(action.invoke(event)) + } catch (t: Throwable) { + continuation.resumeWithException(EventHandlerException(t, event)) + } + } + addEventListener(type, callback) + moreTypes.forEach { type -> addEventListener(type, callback) } + continuation.invokeOnCancellation { + removeEventListener(type, callback) + moreTypes.forEach { type -> removeEventListener(type, callback) } + } +} diff --git a/core/src/commonMain/kotlin/Platform.kt b/core/src/commonMain/kotlin/Platform.kt new file mode 100644 index 0000000..65cb941 --- /dev/null +++ b/core/src/commonMain/kotlin/Platform.kt @@ -0,0 +1,16 @@ +package com.juul.indexeddb + +import com.juul.indexeddb.external.IDBFactory +import com.juul.indexeddb.external.JsAny +import com.juul.indexeddb.external.JsArray +import com.juul.indexeddb.external.JsByteArray +import com.juul.indexeddb.external.ReadonlyArray +import com.juul.indexeddb.external.toJsByteArray + +internal expect val selfIndexedDB: IDBFactory? + +internal expect fun JsArray.toReadonlyArray(): ReadonlyArray + +internal expect fun JsAny.unsafeCast(): T + +internal fun ByteArray.toJsByteArray(): JsByteArray = toTypedArray().toJsByteArray() diff --git a/core/src/commonMain/kotlin/Queryable.kt b/core/src/commonMain/kotlin/Queryable.kt new file mode 100644 index 0000000..2996018 --- /dev/null +++ b/core/src/commonMain/kotlin/Queryable.kt @@ -0,0 +1,13 @@ +import com.juul.indexeddb.external.IDBCursor +import com.juul.indexeddb.external.IDBCursorWithValue +import com.juul.indexeddb.external.IDBKey +import com.juul.indexeddb.external.JsNumber +import com.juul.indexeddb.external.ReadonlyArray + +public sealed class Queryable { + internal abstract fun requestGet(key: IDBKey): Request<*> + internal abstract fun requestGetAll(query: IDBKey?): Request> + internal abstract fun requestOpenCursor(query: IDBKey?, direction: Cursor.Direction): Request + internal abstract fun requestOpenKeyCursor(query: IDBKey?, direction: Cursor.Direction): Request + internal abstract fun requestCount(query: IDBKey?): Request +} diff --git a/core/src/commonMain/kotlin/Request.kt b/core/src/commonMain/kotlin/Request.kt new file mode 100644 index 0000000..4ac43e4 --- /dev/null +++ b/core/src/commonMain/kotlin/Request.kt @@ -0,0 +1,6 @@ +import com.juul.indexeddb.external.IDBRequest +import com.juul.indexeddb.external.JsAny + +internal class Request internal constructor( + internal val request: IDBRequest, +) diff --git a/core/src/wasmJsMain/kotlin/Transaction.kt b/core/src/commonMain/kotlin/Transaction.kt similarity index 87% rename from core/src/wasmJsMain/kotlin/Transaction.kt rename to core/src/commonMain/kotlin/Transaction.kt index fea880a..edb35ae 100644 --- a/core/src/wasmJsMain/kotlin/Transaction.kt +++ b/core/src/commonMain/kotlin/Transaction.kt @@ -1,15 +1,19 @@ -package com.juul.indexeddb - +import com.juul.indexeddb.external.Event import com.juul.indexeddb.external.IDBCursor +import com.juul.indexeddb.external.IDBIndexOptions +import com.juul.indexeddb.external.IDBKey import com.juul.indexeddb.external.IDBObjectStoreOptions import com.juul.indexeddb.external.IDBRequest import com.juul.indexeddb.external.IDBTransaction +import com.juul.indexeddb.external.JsAny import com.juul.indexeddb.external.ReadonlyArray +import com.juul.indexeddb.external.UnsafeJsAny +import com.juul.indexeddb.external.toInt +import com.juul.indexeddb.unsafeCast import kotlinx.coroutines.channels.SendChannel import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow -import org.w3c.dom.events.Event public open class Transaction internal constructor( internal val transaction: IDBTransaction, @@ -28,7 +32,7 @@ public open class Transaction internal constructor( public fun objectStore(name: String): ObjectStore = ObjectStore(transaction.objectStore(name)) - public suspend fun Queryable.get(key: Key): JsAny? { + public suspend fun Queryable.get(key: IDBKey): JsAny? { val request = requestGet(key).request return request.onNextEvent("success", "error") { event -> when (event.type) { @@ -38,7 +42,7 @@ public open class Transaction internal constructor( } } - public suspend fun Queryable.getAll(query: Key? = null): ReadonlyArray<*> { + public suspend fun Queryable.getAll(query: IDBKey? = null): ReadonlyArray<*> { val request = requestGetAll(query).request return request.onNextEvent("success", "error") { event -> when (event.type) { @@ -53,7 +57,7 @@ public open class Transaction internal constructor( ReplaceWith("openCursor(query, direction, cursorStart, autoContinue = true)"), ) public suspend fun Queryable.openCursor( - query: Key? = null, + query: IDBKey? = null, direction: Cursor.Direction = Cursor.Direction.Next, cursorStart: CursorStart? = null, ): Flow = openCursor( @@ -73,7 +77,7 @@ public open class Transaction internal constructor( * will result in the flow stalling. */ public suspend fun Queryable.openCursor( - query: Key? = null, + query: IDBKey? = null, direction: Cursor.Direction = Cursor.Direction.Next, cursorStart: CursorStart? = null, autoContinue: Boolean, @@ -91,7 +95,7 @@ public open class Transaction internal constructor( ReplaceWith("openKeyCursor(query, direction, cursorStart, autoContinue = true)"), ) public suspend fun Queryable.openKeyCursor( - query: Key? = null, + query: IDBKey? = null, direction: Cursor.Direction = Cursor.Direction.Next, cursorStart: CursorStart? = null, ): Flow = openKeyCursor( @@ -111,7 +115,7 @@ public open class Transaction internal constructor( * will result in the flow stalling. */ public suspend fun Queryable.openKeyCursor( - query: Key? = null, + query: IDBKey? = null, direction: Cursor.Direction = Cursor.Direction.Next, cursorStart: CursorStart? = null, autoContinue: Boolean, @@ -125,10 +129,10 @@ public open class Transaction internal constructor( ) private suspend fun openCursorImpl( - query: Key?, + query: IDBKey?, direction: Cursor.Direction, cursorStart: CursorStart?, - open: (Key?, Cursor.Direction) -> Request, + open: (IDBKey?, Cursor.Direction) -> Request, wrap: (U, SendChannel<*>) -> T, autoContinue: Boolean, ): Flow = callbackFlow { @@ -160,12 +164,12 @@ public open class Transaction internal constructor( } } - public suspend fun Queryable.count(query: Key? = null): JsNumber { + public suspend fun Queryable.count(query: IDBKey? = null): Int { val request = requestCount(query).request return request.onNextEvent("success", "error") { event -> when (event.type) { "error" -> throw ErrorEventException(event) - else -> request.result + else -> request.result.toInt() } } } @@ -187,12 +191,12 @@ public open class WriteTransaction internal constructor( * Generally, you'll want to create an explicit `external interface` and pass that in, to guarantee that Kotlin * doesn't mangle, prefix, or otherwise mess with your field names. */ - public suspend fun ObjectStore.add(item: JsAny?): JsAny { + public suspend fun ObjectStore.add(item: JsAny?): UnsafeJsAny { val request = objectStore.add(item) return request.onNextEvent("success", "error") { event -> when (event.type) { "error" -> throw ErrorEventException(event) - else -> request.result + else -> request.result.unsafeCast() } } } @@ -206,12 +210,12 @@ public open class WriteTransaction internal constructor( * Generally, you'll want to create an explicit `external interface` and pass that in, to guarantee that Kotlin * doesn't mangle, prefix, or otherwise mess with your field names. */ - public suspend fun ObjectStore.add(item: JsAny?, key: Key): JsAny { - val request = objectStore.add(item, key.toJs()) + public suspend fun ObjectStore.add(item: JsAny?, key: IDBKey): UnsafeJsAny { + val request = objectStore.add(item, key) return request.onNextEvent("success", "error") { event -> when (event.type) { "error" -> throw ErrorEventException(event) - else -> request.result + else -> request.result.unsafeCast() } } } @@ -245,8 +249,8 @@ public open class WriteTransaction internal constructor( * Generally, you'll want to create an explicit `external interface` and pass that in, to guarantee that Kotlin * doesn't mangle, prefix, or otherwise mess with your field names. */ - public suspend fun ObjectStore.put(item: JsAny?, key: Key): JsAny { - val request = objectStore.put(item, key.toJs()) + public suspend fun ObjectStore.put(item: JsAny?, key: IDBKey): JsAny { + val request = objectStore.put(item, key) return request.onNextEvent("success", "error") { event -> when (event.type) { "error" -> throw ErrorEventException(event) @@ -255,8 +259,8 @@ public open class WriteTransaction internal constructor( } } - public suspend fun ObjectStore.delete(key: Key) { - val request = objectStore.delete(key.toJs()) + public suspend fun ObjectStore.delete(key: IDBKey) { + val request = objectStore.delete(key) request.onNextEvent("success", "error") { event -> when (event.type) { "error" -> throw ErrorEventException(event) @@ -310,15 +314,15 @@ public class VersionChangeTransaction internal constructor( ensureDatabase() .createObjectStore( name = name, - options = IDBObjectStoreOptions( - autoIncrement = false, - keyPath = keyPath.toJs(), - ), + options = IDBObjectStoreOptions(keyPath = keyPath.toJs()), ), ) /** Creates an object-store that uses out-of-line keys with a key-generator. */ - public fun Database.createObjectStore(name: String, autoIncrement: AutoIncrement): ObjectStore = + public fun Database.createObjectStore( + name: String, + @Suppress("UNUSED_PARAMETER") autoIncrement: AutoIncrement, + ): ObjectStore = ObjectStore( ensureDatabase() .createObjectStore( @@ -332,7 +336,7 @@ public class VersionChangeTransaction internal constructor( } public fun ObjectStore.createIndex(name: String, keyPath: KeyPath, unique: Boolean): Index = - Index(objectStore.createIndex(name, keyPath.toJs(), jso { this.unique = unique })) + Index(objectStore.createIndex(name, keyPath.toJs(), IDBIndexOptions { this.unique = unique })) public fun ObjectStore.deleteIndex(name: String) { objectStore.deleteIndex(name) diff --git a/core/src/wasmJsTest/kotlin/AutoIncrementKeyObjectStore.kt b/core/src/commonTest/kotlin/AutoIncrementKeyObjectStore.kt similarity index 64% rename from core/src/wasmJsTest/kotlin/AutoIncrementKeyObjectStore.kt rename to core/src/commonTest/kotlin/AutoIncrementKeyObjectStore.kt index e5308ef..8ed9a61 100644 --- a/core/src/wasmJsTest/kotlin/AutoIncrementKeyObjectStore.kt +++ b/core/src/commonTest/kotlin/AutoIncrementKeyObjectStore.kt @@ -1,10 +1,13 @@ -package com.juul.indexeddb - +import com.juul.indexeddb.external.IDBKey +import com.juul.indexeddb.external.JsAny +import com.juul.indexeddb.jso +import com.juul.indexeddb.unsafeCast +import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals private external interface User : JsAny { - var username: JsString + var username: String } class AutoIncrementKeyObjectStore { @@ -15,20 +18,19 @@ class AutoIncrementKeyObjectStore { database.createObjectStore("users", AutoIncrement) } } - onCleanup { - database.close() - deleteDatabase("auto-increment-keys") - } val id = database.writeTransaction("users") { - objectStore("users").add(jso { username = "Username".toJsString() }) + objectStore("users").add(jso { username = "Username" }) } val user = database.transaction("users") { objectStore("users") - .get(Key(id)) + .get(IDBKey(id)) ?.unsafeCast() } - assertEquals("Username", user?.username?.toString()) + assertEquals("Username", user?.username) + + database.close() + deleteDatabase("auto-increment-keys") } } diff --git a/core/src/wasmJsTest/kotlin/InLineKeyObjectStore.kt b/core/src/commonTest/kotlin/InLineKeyObjectStore.kt similarity index 58% rename from core/src/wasmJsTest/kotlin/InLineKeyObjectStore.kt rename to core/src/commonTest/kotlin/InLineKeyObjectStore.kt index eb239ee..85efefe 100644 --- a/core/src/wasmJsTest/kotlin/InLineKeyObjectStore.kt +++ b/core/src/commonTest/kotlin/InLineKeyObjectStore.kt @@ -1,11 +1,14 @@ -package com.juul.indexeddb - +import com.juul.indexeddb.external.IDBKey +import com.juul.indexeddb.external.JsAny +import com.juul.indexeddb.jso +import com.juul.indexeddb.unsafeCast +import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals private external interface InLineKeyUser : JsAny { - var id: JsString - var username: JsString + var id: String + var username: String } class InLineKeyObjectStore { @@ -17,25 +20,24 @@ class InLineKeyObjectStore { database.createObjectStore("users", KeyPath("id")) } } - onCleanup { - database.close() - deleteDatabase("in-line-keys") - } database.writeTransaction("users") { objectStore("users").add( jso { - id = "7740f7c4-f889-498a-bc6d-f88dabdcfb9a".toJsString() - username = "Username".toJsString() + id = "7740f7c4-f889-498a-bc6d-f88dabdcfb9a" + username = "Username" }, ) } val user = database.transaction("users") { objectStore("users") - .get(Key("7740f7c4-f889-498a-bc6d-f88dabdcfb9a".toJsString())) + .get(IDBKey("7740f7c4-f889-498a-bc6d-f88dabdcfb9a")) ?.unsafeCast() } - assertEquals("Username", user?.username?.toString()) + assertEquals("Username", user?.username) + + database.close() + deleteDatabase("in-line-keys") } } diff --git a/core/src/commonTest/kotlin/KeyTests.kt b/core/src/commonTest/kotlin/KeyTests.kt new file mode 100644 index 0000000..224bb8b --- /dev/null +++ b/core/src/commonTest/kotlin/KeyTests.kt @@ -0,0 +1,40 @@ +import com.juul.indexeddb.external.IDBKey +import com.juul.indexeddb.external.IDBKeyRange +import com.juul.indexeddb.external.JsDate +import com.juul.indexeddb.external.toJsString +import com.juul.indexeddb.external.upperBound +import com.juul.indexeddb.toJsByteArray +import kotlin.test.Test + +public class KeyTests { + + @Test + public fun constructor_withString_completes() { + IDBKey("string") + } + + @Test + public fun constructor_withDate_completes() { + IDBKey(JsDate("2021-11-11T12:00:00".toJsString())) + } + + @Test + public fun constructor_withNiceNumbers_completes() { + IDBKey(IDBKey(1), IDBKey(3.0)) + } + + @Test + public fun constructor_withByteArray_completes() { + IDBKey(byteArrayOf(1, 2, 3, 4, 5, 6).toJsByteArray()) + } + + @Test + public fun constructor_withArrayOfString_completes() { + IDBKey(JsArray(IDBKey(JsArray(IDBKey("foo"), IDBKey("bar"))))) + } + + @Test + public fun constructor_withRange_completes() { + IDBKey(IDBKeyRange.upperBound("foobar", false)) + } +} diff --git a/core/src/wasmJsTest/kotlin/OutOfLineKeyObjectStore.kt b/core/src/commonTest/kotlin/OutOfLineKeyObjectStore.kt similarity index 61% rename from core/src/wasmJsTest/kotlin/OutOfLineKeyObjectStore.kt rename to core/src/commonTest/kotlin/OutOfLineKeyObjectStore.kt index b4143d2..4a2c3df 100644 --- a/core/src/wasmJsTest/kotlin/OutOfLineKeyObjectStore.kt +++ b/core/src/commonTest/kotlin/OutOfLineKeyObjectStore.kt @@ -1,10 +1,13 @@ -package com.juul.indexeddb - +import com.juul.indexeddb.external.IDBKey +import com.juul.indexeddb.external.JsAny +import com.juul.indexeddb.jso +import com.juul.indexeddb.unsafeCast +import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals private external interface OutOfLineKeyUser : JsAny { - var username: JsString + var username: String } class OutOfLineKeyObjectStore { @@ -16,24 +19,23 @@ class OutOfLineKeyObjectStore { database.createObjectStore("users") } } - onCleanup { - database.close() - deleteDatabase("out-of-line-keys") - } database.writeTransaction("users") { objectStore("users") .add( - jso { username = "Username".toJsString() }, - Key("7740f7c4-f889-498a-bc6d-f88dabdcfb9a".toJsString()), + jso { username = "Username" }, + IDBKey("7740f7c4-f889-498a-bc6d-f88dabdcfb9a"), ) } val user = database.transaction("users") { objectStore("users") - .get(Key("7740f7c4-f889-498a-bc6d-f88dabdcfb9a".toJsString())) + .get(IDBKey("7740f7c4-f889-498a-bc6d-f88dabdcfb9a")) ?.unsafeCast() } - assertEquals("Username", user?.username?.toString()) + assertEquals("Username", user?.username) + + database.close() + deleteDatabase("out-of-line-keys") } } diff --git a/core/src/jsTest/kotlin/Samples.kt b/core/src/commonTest/kotlin/Samples.kt similarity index 82% rename from core/src/jsTest/kotlin/Samples.kt rename to core/src/commonTest/kotlin/Samples.kt index fc8f619..f5d75e8 100644 --- a/core/src/jsTest/kotlin/Samples.kt +++ b/core/src/commonTest/kotlin/Samples.kt @@ -1,14 +1,17 @@ -package com.juul.indexeddb - +import com.juul.indexeddb.external.IDBKey +import com.juul.indexeddb.external.JsAny +import com.juul.indexeddb.external.toJsNumber +import com.juul.indexeddb.jso import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals // This file is not a good unit test. Instead, it serves as proof of the README's usage sample. -external interface Customer { +external interface Customer : JsAny { var ssn: String var name: String var age: Int @@ -30,10 +33,6 @@ class Samples { store.deleteIndex("unnecessary_index") } } - onCleanup { - database.close() - deleteDatabase("your-database-name") - } database.writeTransaction("customers") { val store = objectStore("customers") @@ -72,12 +71,12 @@ class Samples { } val bill = database.transaction("customers") { - objectStore("customers").get(Key("444-44-4444")) as Customer + objectStore("customers").get(IDBKey("444-44-4444")) as Customer } assertEquals("Bill", bill.name) val donna = database.transaction("customers") { - objectStore("customers").index("age").get(bound(30, 32)) as Customer + objectStore("customers").index("age").get(bound(30.toJsNumber(), 32.toJsNumber())) as Customer } assertEquals("Donna", donna.name) @@ -96,7 +95,7 @@ class Samples { assertEquals(4, count) val countBelowThirtyTwo = database.transaction("customers") { - objectStore("customers").index("age").count(upperBound(32)) + objectStore("customers").index("age").count(upperBound(32.toJsNumber())) } assertEquals(2, countBelowThirtyTwo) @@ -108,16 +107,19 @@ class Samples { .map { it.name } .toList() } - assertEquals(listOf("Alice", "Bill"), skipTwoYoungest) + assertEquals(listOf("Alice", "Bill"), skipTwoYoungest.map { it }) val skipUntil33 = database.transaction("customers") { objectStore("customers") .index("age") - .openCursor(cursorStart = CursorStart.Continue(Key(33)), autoContinue = true) + .openCursor(cursorStart = CursorStart.Continue(IDBKey(33)), autoContinue = true) .map { it.value as Customer } .map { it.name } .toList() } - assertEquals(listOf("Alice", "Bill"), skipUntil33) + assertEquals(listOf("Alice", "Bill"), skipUntil33.map { it }) + + database.close() + deleteDatabase("your-database-name") } } diff --git a/core/src/jsMain/kotlin/Cursor.kt b/core/src/jsMain/kotlin/Cursor.kt deleted file mode 100644 index 6f2cf54..0000000 --- a/core/src/jsMain/kotlin/Cursor.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.juul.indexeddb - -import com.juul.indexeddb.external.IDBCursor -import com.juul.indexeddb.external.IDBCursorWithValue -import kotlinx.coroutines.channels.SendChannel - -public open class Cursor internal constructor( - internal open val cursor: IDBCursor, - private val channel: SendChannel<*>, -) { - public val key: dynamic - get() = cursor.key - - public val primaryKey: dynamic - get() = cursor.primaryKey - - public fun close() { - channel.close() - } - - public fun `continue`() { - cursor.`continue`() - } - - public fun advance(count: Int) { - cursor.advance(count) - } - - public fun `continue`(key: Key) { - cursor.`continue`(key.toJs()) - } - - public fun continuePrimaryKey(key: Key, primaryKey: Key) { - cursor.continuePrimaryKey(key.toJs(), primaryKey.toJs()) - } - - public enum class Direction( - internal val constant: String, - ) { - Next("next"), - NextUnique("nextunique"), - Previous("prev"), - PreviousUnique("prevunique"), - } -} - -public class CursorWithValue internal constructor( - override val cursor: IDBCursorWithValue, - channel: SendChannel<*>, -) : Cursor(cursor, channel) { - public val value: dynamic - get() = cursor.value -} diff --git a/core/src/jsMain/kotlin/CursorStart.kt b/core/src/jsMain/kotlin/CursorStart.kt deleted file mode 100644 index 8370720..0000000 --- a/core/src/jsMain/kotlin/CursorStart.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.juul.indexeddb - -import com.juul.indexeddb.external.IDBCursor - -public sealed class CursorStart { - - internal abstract fun apply(cursor: IDBCursor) - - public data class Advance( - val count: Int, - ) : CursorStart() { - override fun apply(cursor: IDBCursor) { - cursor.advance(count) - } - } - - public data class Continue( - val key: Key, - ) : CursorStart() { - override fun apply(cursor: IDBCursor) { - cursor.`continue`(key.toJs()) - } - } - - public data class ContinuePrimaryKey( - val key: Key, - val primaryKey: Key, - ) : CursorStart() { - override fun apply(cursor: IDBCursor) { - cursor.continuePrimaryKey(key.toJs(), primaryKey.toJs()) - } - } -} diff --git a/core/src/jsMain/kotlin/Database.kt b/core/src/jsMain/kotlin/Database.kt deleted file mode 100644 index 9ede687..0000000 --- a/core/src/jsMain/kotlin/Database.kt +++ /dev/null @@ -1,123 +0,0 @@ -package com.juul.indexeddb - -import com.juul.indexeddb.external.IDBDatabase -import com.juul.indexeddb.external.IDBFactory -import com.juul.indexeddb.external.IDBVersionChangeEvent -import com.juul.indexeddb.external.indexedDB -import kotlinx.browser.window -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext - -/** - * Inside the [initialize] block, you must not call any `suspend` functions except for: - * - those provided by this library and scoped on [Transaction] (and its subclasses) - * - flow operations on the flows returns by [Transaction.openCursor] and [Transaction.openKeyCursor] - * - `suspend` functions composed entirely of other legal functions - */ -public suspend fun openDatabase( - name: String, - version: Int, - initialize: suspend VersionChangeTransaction.( - database: Database, - oldVersion: Int, - newVersion: Int, - ) -> Unit, -): Database = withContext(Dispatchers.Unconfined) { - val indexedDB: IDBFactory? = js("self.indexedDB || self.webkitIndexedDB") as? IDBFactory - val factory = checkNotNull(indexedDB) { "Your browser doesn't support IndexedDB." } - val request = factory.open(name, version) - val versionChangeEvent = request.onNextEvent("success", "upgradeneeded", "error", "blocked") { event -> - when (event.type) { - "upgradeneeded" -> event as IDBVersionChangeEvent - "error" -> throw ErrorEventException(event) - "blocked" -> throw OpenBlockedException(name, event) - else -> null - } - } - Database(request.result).also { database -> - if (versionChangeEvent != null) { - val transaction = VersionChangeTransaction(checkNotNull(request.transaction)) - transaction.initialize(database, versionChangeEvent.oldVersion, versionChangeEvent.newVersion) - transaction.awaitCompletion() - } - } -} - -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", "blocked") { event -> - when (event.type) { - "error", "blocked" -> throw ErrorEventException(event) - else -> null - } - } -} - -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: - * - those provided by this library and scoped on [Transaction] (and its subclasses) - * - flow operations on the flows returns by [Transaction.openCursor] and [Transaction.openKeyCursor] - * - `suspend` functions composed entirely of other legal functions - */ - public suspend fun transaction( - vararg store: String, - durability: Durability = Durability.Default, - action: suspend Transaction.() -> T, - ): T = withContext(Dispatchers.Unconfined) { - val transaction = Transaction( - ensureDatabase().transaction(arrayOf(*store), "readonly", transactionOptions(durability)), - ) - val result = transaction.action() - transaction.awaitCompletion() - result - } - - /** - * Inside the [action] block, you must not call any `suspend` functions except for: - * - those provided by this library and scoped on [Transaction] (and its subclasses) - * - flow operations on the flows returns by [Transaction.openCursor] and [Transaction.openKeyCursor] - * - `suspend` functions composed entirely of other legal functions - */ - public suspend fun writeTransaction( - vararg store: String, - durability: Durability = Durability.Default, - action: suspend WriteTransaction.() -> T, - ): T = withContext(Dispatchers.Unconfined) { - val transaction = WriteTransaction( - ensureDatabase().transaction(arrayOf(*store), "readwrite", transactionOptions(durability)), - ) - with(transaction) { - // Force overlapping transactions to not call `action` until prior transactions complete. - objectStore(store.first()) - .openKeyCursor(autoContinue = false) - .collect { it.close() } - } - val result = transaction.action() - transaction.awaitCompletion() - result - } - - public fun close() { - database?.close() - database = null - } -} - -private fun transactionOptions(durability: Durability): dynamic = jso { - this.durability = durability.jsValue -} diff --git a/core/src/jsMain/kotlin/Durability.kt b/core/src/jsMain/kotlin/Durability.kt deleted file mode 100644 index 2a2eecd..0000000 --- a/core/src/jsMain/kotlin/Durability.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.juul.indexeddb - -import com.juul.indexeddb.Durability.Default -import com.juul.indexeddb.Durability.Relaxed -import com.juul.indexeddb.Durability.Strict - -/** https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction/durability */ -public enum class Durability { - Default, - Strict, - Relaxed, -} - -internal val Durability.jsValue: String - get() = when (this) { - Default -> "default" - Strict -> "strict" - Relaxed -> "relaxed" - } diff --git a/core/src/jsMain/kotlin/Exceptions.kt b/core/src/jsMain/kotlin/Exceptions.kt deleted file mode 100644 index 2699062..0000000 --- a/core/src/jsMain/kotlin/Exceptions.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.juul.indexeddb - -import org.w3c.dom.events.Event - -public abstract class EventException( - message: String?, - cause: Throwable?, - public val event: Event, -) : Exception(message, cause) - -public class EventHandlerException( - cause: Throwable?, - event: Event, -) : EventException("An inner exception was thrown: $cause", cause, event) - -public class ErrorEventException( - event: Event, -) : EventException("An error event was received.", cause = null, event) -public class OpenBlockedException( - public val name: String, - event: Event, -) : EventException("Resource in use: $name.", cause = null, event) -public class AbortTransactionException( - event: Event, -) : EventException("Transaction aborted while waiting for completion.", cause = null, event) diff --git a/core/src/jsMain/kotlin/Index.kt b/core/src/jsMain/kotlin/Index.kt deleted file mode 100644 index 3579cd1..0000000 --- a/core/src/jsMain/kotlin/Index.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.juul.indexeddb - -import com.juul.indexeddb.external.IDBCursor -import com.juul.indexeddb.external.IDBCursorWithValue -import com.juul.indexeddb.external.IDBIndex - -public class Index internal constructor( - internal val index: IDBIndex, -) : Queryable() { - override fun requestGet(key: Key): Request = - Request(index.get(key.toJs())) - - override fun requestGetAll(query: Key?): Request> = - Request(index.getAll(query?.toJs())) - - override fun requestOpenCursor(query: Key?, direction: Cursor.Direction): Request = - Request(index.openCursor(query?.toJs(), direction.constant)) - - override fun requestOpenKeyCursor(query: Key?, direction: Cursor.Direction): Request = - Request(index.openKeyCursor(query?.toJs(), direction.constant)) - - override fun requestCount(query: Key?): Request = - Request(index.count(query?.toJs())) -} diff --git a/core/src/jsMain/kotlin/Jso.kt b/core/src/jsMain/kotlin/Jso.kt index 04c166f..ca659ef 100644 --- a/core/src/jsMain/kotlin/Jso.kt +++ b/core/src/jsMain/kotlin/Jso.kt @@ -1,9 +1,8 @@ -@file:Suppress("NOTHING_TO_INLINE") - package com.juul.indexeddb +import com.juul.indexeddb.external.JsAny + // Copied from: // https://github.com/JetBrains/kotlin-wrappers/blob/91b2c1568ec6f779af5ec10d89b5e2cbdfe785ff/kotlin-extensions/src/main/kotlin/kotlinx/js/jso.kt -internal inline fun jso(): T = js("({})") -internal inline fun jso(block: T.() -> Unit): T = jso().apply(block) +internal actual fun jso(): T = js("({})").unsafeCast() diff --git a/core/src/jsMain/kotlin/Key.kt b/core/src/jsMain/kotlin/Key.kt deleted file mode 100644 index 140d195..0000000 --- a/core/src/jsMain/kotlin/Key.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.juul.indexeddb - -import com.juul.indexeddb.external.IDBKeyRange -import kotlin.js.Date - -private fun Array.validateKeyTypes() { - for (value in this) when (value) { - null, is String, is Date, is Double, is ByteArray, is IDBKeyRange -> continue - is Array<*> -> (value as Array).validateKeyTypes() - else -> error("Illegal key: expected string, date, float, binary blob, or array of those types, but got $value.") - } -} - -public object AutoIncrement { - internal fun toJs(): dynamic = jso { autoIncrement = true } -} - -public class KeyPath private constructor( - private val paths: Array, -) { - init { - require(paths.isNotEmpty()) { "A key path must have at least one member." } - } - - public constructor(path: String, vararg morePaths: String) : this(arrayOf(path, *morePaths)) - - internal fun toWrappedJs(): dynamic = jso { keyPath = if (paths.size == 1) paths[0] else paths } - internal fun toUnwrappedJs(): dynamic = if (paths.size == 1) paths[0] else paths -} - -public class Key private constructor( - private val values: Array, -) { - init { - require(values.isNotEmpty()) { "A key must have at least one member." } - values.validateKeyTypes() - } - - public constructor(value: dynamic, vararg moreValues: dynamic) : this(arrayOf(value, *moreValues)) - - internal fun toJs(): dynamic = if (values.size == 1) values[0] else values -} - -public fun lowerBound( - x: dynamic, - open: Boolean = false, -): Key = Key(IDBKeyRange.lowerBound(x, open)) - -public fun upperBound( - y: dynamic, - open: Boolean = false, -): Key = Key(IDBKeyRange.upperBound(y, open)) - -public fun bound( - x: dynamic, - y: dynamic, - lowerOpen: Boolean = false, - upperOpen: Boolean = false, -): Key = Key(IDBKeyRange.bound(x, y, lowerOpen, upperOpen)) - -public fun only( - z: dynamic, -): Key = Key(IDBKeyRange.only(z)) diff --git a/core/src/jsMain/kotlin/ObjectStore.kt b/core/src/jsMain/kotlin/ObjectStore.kt deleted file mode 100644 index 0fb5aa3..0000000 --- a/core/src/jsMain/kotlin/ObjectStore.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.juul.indexeddb - -import com.juul.indexeddb.external.IDBCursor -import com.juul.indexeddb.external.IDBCursorWithValue -import com.juul.indexeddb.external.IDBObjectStore - -public class ObjectStore internal constructor( - internal val objectStore: IDBObjectStore, -) : Queryable() { - override fun requestGet(key: Key): Request = - Request(objectStore.get(key.toJs())) - - override fun requestGetAll(query: Key?): Request> = - Request(objectStore.getAll(query?.toJs())) - - override fun requestOpenCursor(query: Key?, direction: Cursor.Direction): Request = - Request(objectStore.openCursor(query?.toJs(), direction.constant)) - - override fun requestOpenKeyCursor(query: Key?, direction: Cursor.Direction): Request = - Request(objectStore.openKeyCursor(query?.toJs(), direction.constant)) - - override fun requestCount(query: Key?): Request = - Request(objectStore.count(query?.toJs())) -} diff --git a/core/src/jsMain/kotlin/OnNextEvent.kt b/core/src/jsMain/kotlin/OnNextEvent.kt deleted file mode 100644 index 3b740c9..0000000 --- a/core/src/jsMain/kotlin/OnNextEvent.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.juul.indexeddb - -import kotlinx.coroutines.suspendCancellableCoroutine -import org.w3c.dom.events.Event -import org.w3c.dom.events.EventTarget -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException - -/** Subscribes to events matching [types], unsubscribing immediately before [action] is called. */ -internal suspend fun EventTarget.onNextEvent( - vararg types: String, - action: (Event) -> T, -): T = suspendCancellableCoroutine { continuation -> - lateinit var callback: (Event) -> Unit - callback = { event -> - types.forEach { type -> removeEventListener(type, callback) } - try { - continuation.resume(action.invoke(event)) - } catch (t: Throwable) { - continuation.resumeWithException(EventHandlerException(t, event)) - } - } - types.forEach { type -> addEventListener(type, callback) } - continuation.invokeOnCancellation { - types.forEach { type -> removeEventListener(type, callback) } - } -} diff --git a/core/src/jsMain/kotlin/Platform.kt b/core/src/jsMain/kotlin/Platform.kt new file mode 100644 index 0000000..32096d9 --- /dev/null +++ b/core/src/jsMain/kotlin/Platform.kt @@ -0,0 +1,16 @@ +package com.juul.indexeddb + +import com.juul.indexeddb.external.IDBFactory +import com.juul.indexeddb.external.JsAny +import com.juul.indexeddb.external.JsArray +import com.juul.indexeddb.external.ReadonlyArray +import kotlin.js.unsafeCast as jsUnsafeCast + +internal actual val selfIndexedDB: IDBFactory? = run { + val indexedDB: dynamic = js("self.indexedDB || self.webkitIndexedDB") + indexedDB?.jsUnsafeCast() +} + +internal actual fun JsArray.toReadonlyArray(): ReadonlyArray = jsUnsafeCast>() + +internal actual fun JsAny.unsafeCast(): T = jsUnsafeCast() diff --git a/core/src/jsMain/kotlin/Queryable.kt b/core/src/jsMain/kotlin/Queryable.kt deleted file mode 100644 index 5130bb1..0000000 --- a/core/src/jsMain/kotlin/Queryable.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.juul.indexeddb - -import com.juul.indexeddb.external.IDBCursor -import com.juul.indexeddb.external.IDBCursorWithValue - -public sealed class Queryable { - internal abstract fun requestGet(key: Key): Request - internal abstract fun requestGetAll(query: Key?): Request> - internal abstract fun requestOpenCursor(query: Key?, direction: Cursor.Direction): Request - internal abstract fun requestOpenKeyCursor(query: Key?, direction: Cursor.Direction): Request - internal abstract fun requestCount(query: Key?): Request -} diff --git a/core/src/jsMain/kotlin/Request.kt b/core/src/jsMain/kotlin/Request.kt deleted file mode 100644 index c330ab0..0000000 --- a/core/src/jsMain/kotlin/Request.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.juul.indexeddb - -import com.juul.indexeddb.external.IDBRequest - -public class Request internal constructor( - internal val request: IDBRequest, -) diff --git a/core/src/jsMain/kotlin/Transaction.js.kt b/core/src/jsMain/kotlin/Transaction.js.kt new file mode 100644 index 0000000..acd1a0e --- /dev/null +++ b/core/src/jsMain/kotlin/Transaction.js.kt @@ -0,0 +1,14 @@ +import com.juul.indexeddb.external.JsAny + +/** + * Adds a new item to the database using an in-line or auto-incrementing key. If an item with the same + * key already exists, this will fail. + * + * This API is delicate. If you're passing in Kotlin objects directly, you're probably doing it wrong. + * + * Generally, you'll want to create an explicit `external interface` and pass that in, to guarantee that Kotlin + * doesn't mangle, prefix, or otherwise mess with your field names. + */ +public suspend fun WriteTransaction.add(objectStore: ObjectStore, item: dynamic): JsAny = + // TODO: this would benefit from context parameters + objectStore.add(item.unsafeCast()) diff --git a/core/src/jsMain/kotlin/Transaction.kt b/core/src/jsMain/kotlin/Transaction.kt deleted file mode 100644 index 8aee08a..0000000 --- a/core/src/jsMain/kotlin/Transaction.kt +++ /dev/null @@ -1,323 +0,0 @@ -package com.juul.indexeddb - -import com.juul.indexeddb.external.IDBCursor -import com.juul.indexeddb.external.IDBRequest -import com.juul.indexeddb.external.IDBTransaction -import kotlinx.coroutines.channels.SendChannel -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import org.w3c.dom.events.Event - -public open class Transaction internal constructor( - internal val transaction: IDBTransaction, -) { - - internal suspend fun awaitCompletion() { - transaction.onNextEvent("complete", "abort", "error") { event -> - when (event.type) { - "abort" -> throw AbortTransactionException(event) - "error" -> throw ErrorEventException(event) - else -> Unit - } - } - } - - public fun objectStore(name: String): ObjectStore = - ObjectStore(transaction.objectStore(name)) - - public suspend fun Queryable.get(key: Key): dynamic { - val request = requestGet(key).request - return request.onNextEvent("success", "error") { event -> - when (event.type) { - "error" -> throw ErrorEventException(event) - else -> request.result - } - } - } - - public suspend fun Queryable.getAll(query: Key? = null): Array { - val request = requestGetAll(query).request - return request.onNextEvent("success", "error") { event -> - when (event.type) { - "error" -> throw ErrorEventException(event) - else -> request.result - } - } - } - - @Deprecated( - "In the future, `autoContinue` will be a required parameter.", - ReplaceWith("openCursor(query, direction, cursorStart, autoContinue = true)"), - ) - public suspend fun Queryable.openCursor( - query: Key? = null, - direction: Cursor.Direction = Cursor.Direction.Next, - cursorStart: CursorStart? = null, - ): Flow = openCursor( - query, - direction, - cursorStart, - autoContinue = true, - ) - - /** - * When [autoContinue] is `true`, all values matching the query will emit automatically. It is important - * not to suspend in flow collection on _anything_ other than flow operators (such as `.toList`). - * - * Warning: when [autoContinue] is `false`, callers are responsible for data flow and must call `continue`, `advance`, - * or similar explicitly. The returned flow will terminate automatically after `continue` if no more elements - * remain. Otherwise, you must call `close` to terminate the flow. Failing to call `continue` or `close` - * will result in the flow stalling. - */ - public suspend fun Queryable.openCursor( - query: Key? = null, - direction: Cursor.Direction = Cursor.Direction.Next, - cursorStart: CursorStart? = null, - autoContinue: Boolean, - ): Flow = openCursorImpl( - query, - direction, - cursorStart, - open = this::requestOpenCursor, - wrap = ::CursorWithValue, - autoContinue, - ) - - @Deprecated( - "In the future, `autoContinue` will be a required parameter.", - ReplaceWith("openKeyCursor(query, direction, cursorStart, autoContinue = true)"), - ) - public suspend fun Queryable.openKeyCursor( - query: Key? = null, - direction: Cursor.Direction = Cursor.Direction.Next, - cursorStart: CursorStart? = null, - ): Flow = openKeyCursor( - query, - direction, - cursorStart, - autoContinue = true, - ) - - /** - * When [autoContinue] is `true`, all values matching the query will emit automatically. It is important - * not to suspend in flow collection on _anything_ other than flow operators (such as `.toList`). - * - * Warning: when [autoContinue] is `false`, callers are responsible for data flow and must call `continue`, `advance`, - * or similar explicitly. The returned flow will terminate automatically after `continue` if no more elements - * remain. Otherwise, you must call `close` to terminate the flow. Failing to call `continue` or `close` - * will result in the flow stalling. - */ - public suspend fun Queryable.openKeyCursor( - query: Key? = null, - direction: Cursor.Direction = Cursor.Direction.Next, - cursorStart: CursorStart? = null, - autoContinue: Boolean, - ): Flow = openCursorImpl( - query, - direction, - cursorStart, - open = this::requestOpenKeyCursor, - wrap = ::Cursor, - autoContinue, - ) - - private suspend fun openCursorImpl( - query: Key?, - direction: Cursor.Direction, - cursorStart: CursorStart?, - open: (Key?, Cursor.Direction) -> Request, - wrap: (U, SendChannel<*>) -> T, - autoContinue: Boolean, - ): Flow = callbackFlow { - var cursorStartAction = cursorStart - val request = open(query, direction).request - val onSuccess: (Event) -> Unit = { event -> - @Suppress("UNCHECKED_CAST") - val cursor = (event.target as IDBRequest).result - if (cursorStartAction != null && cursor != null) { - cursorStartAction?.apply(cursor) - cursorStartAction = null - } else if (cursor != null) { - val result = trySend(wrap(cursor, channel)) - when { - result.isSuccess -> if (autoContinue) cursor.`continue`() - result.isFailure -> channel.close(IllegalStateException("Send failed. Did you suspend illegally?")) - result.isClosed -> channel.close() - } - } else { - channel.close() - } - } - val onError: (Event) -> Unit = { event -> channel.close(ErrorEventException(event)) } - request.addEventListener("success", onSuccess) - request.addEventListener("error", onError) - awaitClose { - request.removeEventListener("success", onSuccess) - request.removeEventListener("error", onError) - } - } - - public suspend fun Queryable.count(query: Key? = null): Int { - val request = requestCount(query).request - return request.onNextEvent("success", "error") { event -> - when (event.type) { - "error" -> throw ErrorEventException(event) - else -> request.result - } - } - } - - public fun ObjectStore.index(name: String): Index = - Index(objectStore.index(name)) -} - -public open class WriteTransaction internal constructor( - transaction: IDBTransaction, -) : Transaction(transaction) { - - /** - * Adds a new item to the database using an in-line or auto-incrementing key. If an item with the same - * key already exists, this will fail. - * - * This API is delicate. If you're passing in Kotlin objects directly, you're probably doing it wrong. - * - * Generally, you'll want to create an explicit `external interface` and pass that in, to guarantee that Kotlin - * doesn't mangle, prefix, or otherwise mess with your field names. - */ - public suspend fun ObjectStore.add(item: dynamic): dynamic { - val request = objectStore.add(item) - return request.onNextEvent("success", "error") { event -> - when (event.type) { - "error" -> throw ErrorEventException(event) - else -> request.result - } - } - } - - /** - * Adds a new item to the database using an explicit out-of-line key. If an item with the same key already - * exists, this will fail. - * - * This API is delicate. If you're passing in Kotlin objects directly, you're probably doing it wrong. - * - * Generally, you'll want to create an explicit `external interface` and pass that in, to guarantee that Kotlin - * doesn't mangle, prefix, or otherwise mess with your field names. - */ - public suspend fun ObjectStore.add(item: dynamic, key: Key): dynamic { - val request = objectStore.add(item, key.toJs()) - return request.onNextEvent("success", "error") { event -> - when (event.type) { - "error" -> throw ErrorEventException(event) - else -> request.result - } - } - } - - /** - * Adds an item to or updates an item in the database using an in-line or auto-incrementing key. If an item - * with the same key already exists, this will replace that item. Note that with auto-incrementing keys a new - * item will always be inserted. - * - * This API is delicate. If you're passing in Kotlin objects directly, you're probably doing it wrong. - * - * Generally, you'll want to create an explicit `external interface` and pass that in, to guarantee that Kotlin - * doesn't mangle, prefix, or otherwise mess with your field names. - */ - public suspend fun ObjectStore.put(item: dynamic): dynamic { - val request = objectStore.put(item) - return request.onNextEvent("success", "error") { event -> - when (event.type) { - "error" -> throw ErrorEventException(event) - else -> request.result - } - } - } - - /** - * Adds an item to or updates an item in the database using an explicit out-of-line key. If an item with the - * same key already exists, this will replace that item. - * - * This API is delicate. If you're passing in Kotlin objects directly, you're probably doing it wrong. - * - * Generally, you'll want to create an explicit `external interface` and pass that in, to guarantee that Kotlin - * doesn't mangle, prefix, or otherwise mess with your field names. - */ - public suspend fun ObjectStore.put(item: dynamic, key: Key): dynamic { - val request = objectStore.put(item, key.toJs()) - return request.onNextEvent("success", "error") { event -> - when (event.type) { - "error" -> throw ErrorEventException(event) - else -> request.result - } - } - } - - public suspend fun ObjectStore.delete(key: Key) { - val request = objectStore.delete(key.toJs()) - request.onNextEvent("success", "error") { event -> - when (event.type) { - "error" -> throw ErrorEventException(event) - else -> Unit - } - } - } - - public suspend fun ObjectStore.clear() { - val request = objectStore.clear() - request.onNextEvent("success", "error") { event -> - when (event.type) { - "error" -> throw ErrorEventException(event) - else -> Unit - } - } - } - - public suspend fun CursorWithValue.delete() { - val request = cursor.delete() - request.onNextEvent("success", "error") { event -> - when (event.type) { - "error" -> throw ErrorEventException(event) - else -> Unit - } - } - } - - public suspend fun CursorWithValue.update(value: dynamic) { - val request = cursor.update(value) - request.onNextEvent("success", "error") { event -> - when (event.type) { - "error" -> throw ErrorEventException(event) - else -> Unit - } - } - } -} - -public class VersionChangeTransaction internal constructor( - transaction: IDBTransaction, -) : WriteTransaction(transaction) { - - /** Creates an object-store that uses explicit out-of-line keys. */ - public fun Database.createObjectStore(name: String): ObjectStore = - ObjectStore(ensureDatabase().createObjectStore(name)) - - /** Creates an object-store that uses in-line keys. */ - public fun Database.createObjectStore(name: String, keyPath: KeyPath): ObjectStore = - 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(ensureDatabase().createObjectStore(name, autoIncrement.toJs())) - - public fun Database.deleteObjectStore(name: String) { - ensureDatabase().deleteObjectStore(name) - } - - public fun ObjectStore.createIndex(name: String, keyPath: KeyPath, unique: Boolean): Index = - Index(objectStore.createIndex(name, keyPath.toUnwrappedJs(), jso { this.unique = unique })) - - public fun ObjectStore.deleteIndex(name: String) { - objectStore.deleteIndex(name) - } -} diff --git a/core/src/jsTest/kotlin/AutoIncrementKeyObjectStore.kt b/core/src/jsTest/kotlin/AutoIncrementKeyObjectStore.kt deleted file mode 100644 index fd3de61..0000000 --- a/core/src/jsTest/kotlin/AutoIncrementKeyObjectStore.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.juul.indexeddb - -import kotlin.test.Test -import kotlin.test.assertEquals - -class AutoIncrementKeyObjectStore { - - @Test - fun simpleReadWrite() = runTest { - val database = openDatabase("auto-increment-keys", 1) { database, oldVersion, newVersion -> - if (oldVersion < 1) { - database.createObjectStore("users", AutoIncrement) - } - } - onCleanup { - database.close() - deleteDatabase("auto-increment-keys") - } - - val id = database.writeTransaction("users") { - objectStore("users").add(jso { username = "Username" }) as Double - } - - val user = database.transaction("users") { - objectStore("users") - .get(Key(id)) - } - assertEquals("Username", user.username) - } -} diff --git a/core/src/jsTest/kotlin/Common.kt b/core/src/jsTest/kotlin/Common.kt deleted file mode 100644 index 6f53000..0000000 --- a/core/src/jsTest/kotlin/Common.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.juul.indexeddb - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.promise -import kotlin.js.Promise -import kotlin.test.AfterTest - -/** - * Runs a coroutine in a promise, which the Mocha runner is smart enough to block on. - * - * The [action] runs inside a [TestScope], which gives access to [TestScope.onCleanup]. This - * allows for test-specific [AfterTest] like behavior with access to `suspend` functions. - */ -@Suppress("EXPERIMENTAL_API_USAGE") -internal fun runTest( - action: suspend TestScope.() -> Unit, -): Promise = GlobalScope.promise { - val testScope = TestScope(this) - try { - action.invoke(testScope) - } finally { - testScope.cleanup() - } -} - -internal class TestScope( - inner: CoroutineScope, -) : CoroutineScope by inner { - - private val callbacks = mutableListOf Unit>() - - fun onCleanup(action: suspend () -> Unit) { - callbacks += action - } - - suspend fun cleanup() { - callbacks.asReversed().forEach { it.invoke() } - } -} diff --git a/core/src/jsTest/kotlin/InLineKeyObjectStore.kt b/core/src/jsTest/kotlin/InLineKeyObjectStore.kt deleted file mode 100644 index 6607370..0000000 --- a/core/src/jsTest/kotlin/InLineKeyObjectStore.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.juul.indexeddb - -import kotlin.test.Test -import kotlin.test.assertEquals - -class InLineKeyObjectStore { - - @Test - fun simpleReadWrite() = runTest { - val database = openDatabase("in-line-keys", 1) { database, oldVersion, newVersion -> - if (oldVersion < 1) { - database.createObjectStore("users", KeyPath("id")) - } - } - onCleanup { - database.close() - deleteDatabase("in-line-keys") - } - - database.writeTransaction("users") { - objectStore("users").add( - jso { - id = "7740f7c4-f889-498a-bc6d-f88dabdcfb9a" - username = "Username" - }, - ) - } - - val user = database.transaction("users") { - objectStore("users") - .get(Key("7740f7c4-f889-498a-bc6d-f88dabdcfb9a")) - } - assertEquals("Username", user.username) - } -} diff --git a/core/src/jsTest/kotlin/KeyTests.kt b/core/src/jsTest/kotlin/KeyTests.kt deleted file mode 100644 index 0118033..0000000 --- a/core/src/jsTest/kotlin/KeyTests.kt +++ /dev/null @@ -1,59 +0,0 @@ -package com.juul.indexeddb - -import com.juul.indexeddb.external.IDBKeyRange -import kotlin.js.Date -import kotlin.test.Test -import kotlin.test.assertFails - -public class KeyTests { - - @Test - public fun constructor_withObjectType_shouldFail() { - assertFails { Key(jso()) } - } - - @Test - public fun constructor_withArrayOfObjectType_shouldFail() { - assertFails { Key(arrayOf(jso())) } - } - - @Test - public fun constructor_withLong_shouldFail() { - assertFails { Key(4L) } - } - - @Test - public fun constructor_withString_completes() { - Key("string") - } - - @Test - public fun constructor_withDate_completes() { - Key(Date("2021-11-11T12:00:00")) - } - - @Test - public fun constructor_withNiceNumbers_completes() { - Key(1, 2f, 3.0) - } - - @Test - public fun constructor_withByteArray_completes() { - Key(byteArrayOf(1, 2, 3, 4, 5, 6)) - } - - @Test - public fun constructor_withArrayOfString_completes() { - Key(arrayOf(arrayOf("foo"), "bar")) - } - - @Test - public fun constructor_withRange_completes() { - Key(IDBKeyRange.upperBound("foobar", false)) - } - - @Test - public fun constructor_withNull_completes() { - Key(null) - } -} diff --git a/core/src/jsTest/kotlin/OutOfLineKeyObjectStore.kt b/core/src/jsTest/kotlin/OutOfLineKeyObjectStore.kt deleted file mode 100644 index 60941dc..0000000 --- a/core/src/jsTest/kotlin/OutOfLineKeyObjectStore.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.juul.indexeddb - -import kotlin.test.Test -import kotlin.test.assertEquals - -class OutOfLineKeyObjectStore { - - @Test - fun simpleReadWrite() = runTest { - val database = openDatabase("out-of-line-keys", 1) { database, oldVersion, newVersion -> - if (oldVersion < 1) { - database.createObjectStore("users") - } - } - onCleanup { - database.close() - deleteDatabase("out-of-line-keys") - } - - database.writeTransaction("users") { - objectStore("users").add(jso { username = "Username" }, Key("7740f7c4-f889-498a-bc6d-f88dabdcfb9a")) - } - - val user = database.transaction("users") { - objectStore("users") - .get(Key("7740f7c4-f889-498a-bc6d-f88dabdcfb9a")) - } - assertEquals("Username", user.username) - } -} diff --git a/core/src/wasmJsMain/kotlin/Index.kt b/core/src/wasmJsMain/kotlin/Index.kt deleted file mode 100644 index 347490d..0000000 --- a/core/src/wasmJsMain/kotlin/Index.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.juul.indexeddb - -import com.juul.indexeddb.external.IDBCursor -import com.juul.indexeddb.external.IDBCursorWithValue -import com.juul.indexeddb.external.IDBIndex -import com.juul.indexeddb.external.ReadonlyArray - -public class Index internal constructor( - internal val index: IDBIndex, -) : Queryable() { - override fun requestGet(key: Key): Request<*> = - Request(index.get(key.toJs())) - - override fun requestGetAll(query: Key?): Request> = - Request(index.getAll(query?.toJs())) - - override fun requestOpenCursor(query: Key?, direction: Cursor.Direction): Request = - Request(index.openCursor(query?.toJs(), direction.constant)) - - override fun requestOpenKeyCursor(query: Key?, direction: Cursor.Direction): Request = - Request(index.openKeyCursor(query?.toJs(), direction.constant)) - - override fun requestCount(query: Key?): Request = - Request(index.count(query?.toJs())) -} diff --git a/core/src/wasmJsMain/kotlin/JsArray.kt b/core/src/wasmJsMain/kotlin/JsArray.kt deleted file mode 100644 index c18d643..0000000 --- a/core/src/wasmJsMain/kotlin/JsArray.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.juul.indexeddb - -import com.juul.indexeddb.external.ReadonlyArray - -internal fun Iterable.toJsArray(): JsArray = - kotlin.js.JsArray().apply { - forEachIndexed { index, s -> - set(index, s?.toJsString()) - } - } - -internal fun JsArray(vararg values: T): JsArray = - kotlin.js.JsArray().apply { - for (i in values.indices) { - set(i, values[i]) - } - } - -internal fun ReadonlyArray(vararg values: T): ReadonlyArray = - kotlin.js.JsArray().apply { - for (i in values.indices) { - set(i, values[i]) - } - } diff --git a/core/src/wasmJsMain/kotlin/Jso.kt b/core/src/wasmJsMain/kotlin/Jso.kt index 6ebe3ea..6caf5e7 100644 --- a/core/src/wasmJsMain/kotlin/Jso.kt +++ b/core/src/wasmJsMain/kotlin/Jso.kt @@ -1,6 +1,6 @@ package com.juul.indexeddb + // Copied from: // https://github.com/JetBrains/kotlin-wrappers/blob/91b2c1568ec6f779af5ec10d89b5e2cbdfe785ff/kotlin-extensions/src/main/kotlin/kotlinx/js/jso.kt -internal fun jso(): T = js("({})") -internal fun jso(block: T.() -> Unit): T = jso().apply(block) +internal actual fun jso(): T = js("({})") diff --git a/core/src/wasmJsMain/kotlin/Key.kt b/core/src/wasmJsMain/kotlin/Key.kt deleted file mode 100644 index 979e9d5..0000000 --- a/core/src/wasmJsMain/kotlin/Key.kt +++ /dev/null @@ -1,68 +0,0 @@ -package com.juul.indexeddb - -import com.juul.indexeddb.external.IDBKey -import com.juul.indexeddb.external.IDBKeyRange -import org.khronos.webgl.Uint8Array - -public external class Date( - value: JsString, -) : JsAny - -private fun JsArray.validateKeyTypes() { - for (i in 0.. continue - is JsArray<*> -> (value as JsArray).validateKeyTypes() - else -> error("Illegal key: expected string, date, float, binary blob, or array of those types, but got $value.") - } - } -} - -public object AutoIncrement - -public class KeyPath private constructor( - private val paths: List, -) { - init { - require(paths.isNotEmpty()) { "A key path must have at least one member." } - } - - public constructor(path: String?, vararg morePaths: String?) : this(listOf(path, *morePaths)) - - internal fun toJs(): JsAny? = if (paths.size == 1) paths[0]?.toJsString() else paths.toJsArray() -} - -public class Key( - value: JsAny?, - vararg moreValues: JsAny?, -) { - private val values: JsArray = JsArray(value, *moreValues) - init { - require(values.length >= 0) { "A key must have at least one member." } - values.validateKeyTypes() - } - - internal fun toJs(): IDBKey = (if (values.length == 1) values[0]!! else values).unsafeCast() -} - -public fun lowerBound( - x: JsAny?, - open: Boolean = false, -): Key = Key(IDBKeyRange.lowerBound(x, open)) - -public fun upperBound( - y: JsAny?, - open: Boolean = false, -): Key = Key(IDBKeyRange.upperBound(y, open)) - -public fun bound( - x: JsAny?, - y: JsAny?, - lowerOpen: Boolean = false, - upperOpen: Boolean = false, -): Key = Key(IDBKeyRange.bound(x, y, lowerOpen, upperOpen)) - -public fun only( - z: JsAny?, -): Key = Key(IDBKeyRange.only(z)) diff --git a/core/src/wasmJsMain/kotlin/ObjectStore.kt b/core/src/wasmJsMain/kotlin/ObjectStore.kt deleted file mode 100644 index f85ed52..0000000 --- a/core/src/wasmJsMain/kotlin/ObjectStore.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.juul.indexeddb - -import com.juul.indexeddb.external.IDBCursor -import com.juul.indexeddb.external.IDBCursorWithValue -import com.juul.indexeddb.external.IDBObjectStore -import com.juul.indexeddb.external.ReadonlyArray - -public class ObjectStore internal constructor( - internal val objectStore: IDBObjectStore, -) : Queryable() { - override fun requestGet(key: Key): Request<*> = - Request(objectStore.get(key.toJs())) - - override fun requestGetAll(query: Key?): Request> = - Request(objectStore.getAll(query?.toJs())) - - override fun requestOpenCursor(query: Key?, direction: Cursor.Direction): Request = - Request(objectStore.openCursor(query?.toJs(), direction.constant)) - - override fun requestOpenKeyCursor(query: Key?, direction: Cursor.Direction): Request = - Request(objectStore.openKeyCursor(query?.toJs(), direction.constant)) - - override fun requestCount(query: Key?): Request = - Request(objectStore.count(query?.toJs())) -} diff --git a/core/src/wasmJsMain/kotlin/OnNextEvent.kt b/core/src/wasmJsMain/kotlin/OnNextEvent.kt deleted file mode 100644 index 3b740c9..0000000 --- a/core/src/wasmJsMain/kotlin/OnNextEvent.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.juul.indexeddb - -import kotlinx.coroutines.suspendCancellableCoroutine -import org.w3c.dom.events.Event -import org.w3c.dom.events.EventTarget -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException - -/** Subscribes to events matching [types], unsubscribing immediately before [action] is called. */ -internal suspend fun EventTarget.onNextEvent( - vararg types: String, - action: (Event) -> T, -): T = suspendCancellableCoroutine { continuation -> - lateinit var callback: (Event) -> Unit - callback = { event -> - types.forEach { type -> removeEventListener(type, callback) } - try { - continuation.resume(action.invoke(event)) - } catch (t: Throwable) { - continuation.resumeWithException(EventHandlerException(t, event)) - } - } - types.forEach { type -> addEventListener(type, callback) } - continuation.invokeOnCancellation { - types.forEach { type -> removeEventListener(type, callback) } - } -} diff --git a/core/src/wasmJsMain/kotlin/Platform.kt b/core/src/wasmJsMain/kotlin/Platform.kt new file mode 100644 index 0000000..5c182b1 --- /dev/null +++ b/core/src/wasmJsMain/kotlin/Platform.kt @@ -0,0 +1,11 @@ +package com.juul.indexeddb + +import com.juul.indexeddb.external.IDBFactory +import com.juul.indexeddb.external.ReadonlyArray +import kotlin.js.unsafeCast as jsUnsafeCast + +internal actual val selfIndexedDB: IDBFactory? = js("self.indexedDB || self.webkitIndexedDB") + +internal actual fun JsArray.toReadonlyArray(): ReadonlyArray = unsafeCast() + +internal actual fun JsAny.unsafeCast(): T = jsUnsafeCast() diff --git a/core/src/wasmJsMain/kotlin/Queryable.kt b/core/src/wasmJsMain/kotlin/Queryable.kt deleted file mode 100644 index 8012573..0000000 --- a/core/src/wasmJsMain/kotlin/Queryable.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.juul.indexeddb - -import com.juul.indexeddb.external.IDBCursor -import com.juul.indexeddb.external.IDBCursorWithValue -import com.juul.indexeddb.external.ReadonlyArray - -public sealed class Queryable { - internal abstract fun requestGet(key: Key): Request<*> - internal abstract fun requestGetAll(query: Key?): Request> - internal abstract fun requestOpenCursor(query: Key?, direction: Cursor.Direction): Request - internal abstract fun requestOpenKeyCursor(query: Key?, direction: Cursor.Direction): Request - internal abstract fun requestCount(query: Key?): Request -} diff --git a/core/src/wasmJsMain/kotlin/Request.kt b/core/src/wasmJsMain/kotlin/Request.kt deleted file mode 100644 index 32ea59e..0000000 --- a/core/src/wasmJsMain/kotlin/Request.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.juul.indexeddb - -import com.juul.indexeddb.external.IDBRequest - -public class Request internal constructor( - internal val request: IDBRequest, -) diff --git a/core/src/wasmJsTest/kotlin/Common.kt b/core/src/wasmJsTest/kotlin/Common.kt deleted file mode 100644 index 8fb9f48..0000000 --- a/core/src/wasmJsTest/kotlin/Common.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.juul.indexeddb - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.promise -import kotlin.js.Promise -import kotlin.test.AfterTest - -/** - * Runs a coroutine in a promise, which the Mocha runner is smart enough to block on. - * - * The [action] runs inside a [TestScope], which gives access to [TestScope.onCleanup]. This - * allows for test-specific [AfterTest] like behavior with access to `suspend` functions. - */ -@Suppress("EXPERIMENTAL_API_USAGE") -internal fun runTest( - action: suspend TestScope.() -> Unit, -): Promise = GlobalScope.promise { - val testScope = TestScope(this) - try { - action.invoke(testScope) - } finally { - testScope.cleanup() - } -} - -internal class TestScope( - inner: CoroutineScope, -) : CoroutineScope by inner { - - private val callbacks = mutableListOf Unit>() - - fun onCleanup(action: suspend () -> Unit) { - callbacks += action - } - - suspend fun cleanup() { - callbacks.asReversed().forEach { it.invoke() } - } -} diff --git a/core/src/wasmJsTest/kotlin/KeyTests.kt b/core/src/wasmJsTest/kotlin/KeyTests.kt deleted file mode 100644 index d2953a0..0000000 --- a/core/src/wasmJsTest/kotlin/KeyTests.kt +++ /dev/null @@ -1,70 +0,0 @@ -package com.juul.indexeddb - -import com.juul.indexeddb.external.IDBKeyRange -import org.khronos.webgl.Uint8Array -import kotlin.test.Test -import kotlin.test.assertFails - -public class KeyTests { - - @Test - public fun constructor_withObjectType_shouldFail() { - assertFails { Key(jso()) } - } - - @Test - public fun constructor_withArrayOfObjectType_shouldFail() { - assertFails { Key(JsArray(jso())) } - } - - @Test - public fun constructor_withLong_shouldFail() { - assertFails { Key(4L.toJsBigInt()) } - } - - @Test - public fun constructor_withString_completes() { - Key("string".toJsString()) - } - - @Test - public fun constructor_withDate_completes() { - Key(Date("2021-11-11T12:00:00".toJsString())) - } - - @Test - public fun constructor_withNiceNumbers_completes() { - Key(1.toJsNumber(), 3.0.toJsNumber()) - } - - @Test - public fun constructor_withByteArray_completes() { - Key( - Uint8Array( - JsArray( - 1.toJsNumber(), - 2.toJsNumber(), - 3.toJsNumber(), - 4.toJsNumber(), - 5.toJsNumber(), - 6.toJsNumber(), - ), - ), - ) - } - - @Test - public fun constructor_withArrayOfString_completes() { - Key(JsArray(JsArray("foo".toJsString()), "bar".toJsString())) - } - - @Test - public fun constructor_withRange_completes() { - Key(IDBKeyRange.upperBound("foobar".toJsString(), false)) - } - - @Test - public fun constructor_withNull_completes() { - Key(null) - } -} diff --git a/core/src/wasmJsTest/kotlin/Samples.kt b/core/src/wasmJsTest/kotlin/Samples.kt deleted file mode 100644 index 72e2477..0000000 --- a/core/src/wasmJsTest/kotlin/Samples.kt +++ /dev/null @@ -1,123 +0,0 @@ -package com.juul.indexeddb - -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.toList -import kotlin.test.Test -import kotlin.test.assertEquals - -// This file is not a good unit test. Instead, it serves as proof of the README's usage sample. - -external interface Customer : JsAny { - var ssn: JsString - var name: JsString - var age: JsNumber - var email: JsString -} - -@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE") -class Samples { - - @Test - fun simpleReadWrite() = runTest { - val database = openDatabase("your-database-name", 1) { database, oldVersion, newVersion -> - if (oldVersion < 1) { - val store = database.createObjectStore("customers", KeyPath("ssn")) - store.createIndex("name", KeyPath("name"), unique = false) - store.createIndex("age", KeyPath("age"), unique = false) - store.createIndex("email", KeyPath("email"), unique = true) - store.createIndex("unnecessary_index", KeyPath("unimporatant"), unique = true) - store.deleteIndex("unnecessary_index") - } - } - onCleanup { - database.close() - deleteDatabase("your-database-name") - } - - database.writeTransaction("customers") { - val store = objectStore("customers") - store.add( - jso { - ssn = "333-33-3333".toJsString() - name = "Alice".toJsString() - age = 33.toJsNumber() - email = "alice@company.com".toJsString() - }, - ) - store.add( - jso { - ssn = "444-44-4444".toJsString() - name = "Bill".toJsString() - age = 35.toJsNumber() - email = "bill@company.com".toJsString() - }, - ) - store.add( - jso { - ssn = "555-55-5555".toJsString() - name = "Charlie".toJsString() - age = 29.toJsNumber() - email = "charlie@home.org".toJsString() - }, - ) - store.add( - jso { - ssn = "666-66-6666".toJsString() - name = "Donna".toJsString() - age = 31.toJsNumber() - email = "donna@home.org".toJsString() - }, - ) - } - - val bill = database.transaction("customers") { - objectStore("customers").get(Key("444-44-4444".toJsString())) as Customer - } - assertEquals("Bill", bill.name.toString()) - - val donna = database.transaction("customers") { - objectStore("customers").index("age").get(bound(30.toJsNumber(), 32.toJsNumber())) as Customer - } - assertEquals("Donna", donna.name.toString()) - - val charlie = database.transaction("customers") { - objectStore("customers") - .index("name") - .openCursor(autoContinue = true) - .map { it.value as Customer } - .first { it.age.toInt() < 32 } - } - assertEquals("Charlie", charlie.name.toString()) - - val count = database.transaction("customers") { - objectStore("customers").count() - } - assertEquals(4, count.toInt()) - - val countBelowThirtyTwo = database.transaction("customers") { - objectStore("customers").index("age").count(upperBound(32.toJsNumber())) - } - assertEquals(2, countBelowThirtyTwo.toInt()) - - val skipTwoYoungest = database.transaction("customers") { - objectStore("customers") - .index("age") - .openCursor(cursorStart = CursorStart.Advance(2), autoContinue = true) - .map { it.value as Customer } - .map { it.name } - .toList() - } - assertEquals(listOf("Alice", "Bill"), skipTwoYoungest.map { it.toString() }) - - val skipUntil33 = database.transaction("customers") { - objectStore("customers") - .index("age") - .openCursor(cursorStart = CursorStart.Continue(Key(33.toJsNumber())), autoContinue = true) - .map { it.value as Customer } - .map { it.name } - .toList() - } - assertEquals(listOf("Alice", "Bill"), skipUntil33.map { it.toString() }) - } -} diff --git a/external/build.gradle.kts b/external/build.gradle.kts index b394d07..1330760 100644 --- a/external/build.gradle.kts +++ b/external/build.gradle.kts @@ -1,4 +1,5 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl +import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask plugins { kotlin("multiplatform") @@ -21,3 +22,9 @@ kotlin { binaries.library() } } + +tasks.withType>().configureEach { + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + } +} diff --git a/external/src/commonMain/kotlin/IDBCursor.kt b/external/src/commonMain/kotlin/IDBCursor.kt new file mode 100644 index 0000000..c3ec0e6 --- /dev/null +++ b/external/src/commonMain/kotlin/IDBCursor.kt @@ -0,0 +1,22 @@ +package com.juul.indexeddb.external + +/** https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor */ +public expect interface IDBCursor : JsAny { + public val key: IDBKey + public val primaryKey: IDBKey + + public fun advance(count: Int) + + public fun `continue`() + public fun `continue`(key: IDBKey) + + public fun continuePrimaryKey(key: IDBKey, primaryKey: IDBKey) +} + +/** https://developer.mozilla.org/en-US/docs/Web/API/IDBCursorWithValue */ +public expect interface IDBCursorWithValue : IDBCursor { + public val value: JsAny? + + public fun delete(): IDBRequest<*> + public fun update(value: JsAny?): IDBRequest +} diff --git a/external/src/commonMain/kotlin/IDBDatabase.kt b/external/src/commonMain/kotlin/IDBDatabase.kt new file mode 100644 index 0000000..4a94f9e --- /dev/null +++ b/external/src/commonMain/kotlin/IDBDatabase.kt @@ -0,0 +1,24 @@ +package com.juul.indexeddb.external + +/** https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase */ +public expect class IDBDatabase : EventTarget { + public val name: String + public val version: Int + public val objectStoreNames: JsArray + public fun close() + public fun createObjectStore(name: String): IDBObjectStore + public fun createObjectStore(name: String, options: IDBObjectStoreOptions?): IDBObjectStore + public fun deleteObjectStore(name: String) + + public fun transaction( + storeName: String, + mode: String, + options: IDBTransactionOptions, + ): IDBTransaction + + public fun transaction( + storeNames: ReadonlyArray, + mode: String, + options: IDBTransactionOptions, + ): IDBTransaction +} diff --git a/external/src/commonMain/kotlin/IDBFactory.kt b/external/src/commonMain/kotlin/IDBFactory.kt new file mode 100644 index 0000000..72c18ce --- /dev/null +++ b/external/src/commonMain/kotlin/IDBFactory.kt @@ -0,0 +1,7 @@ +package com.juul.indexeddb.external + +/** https://developer.mozilla.org/en-US/docs/Web/API/IDBFactory */ +public expect interface IDBFactory { + public fun open(name: String, version: Int): IDBOpenDBRequest + public fun deleteDatabase(name: String): IDBOpenDBRequest +} diff --git a/external/src/commonMain/kotlin/IDBIndex.kt b/external/src/commonMain/kotlin/IDBIndex.kt new file mode 100644 index 0000000..ec9f347 --- /dev/null +++ b/external/src/commonMain/kotlin/IDBIndex.kt @@ -0,0 +1,7 @@ +package com.juul.indexeddb.external + +/** https://developer.mozilla.org/en-US/docs/Web/API/IDBIndex */ +public expect interface IDBIndex : IDBQueryable { + public val name: String + public val objectStore: IDBObjectStore +} diff --git a/external/src/commonMain/kotlin/IDBIndexOptions.kt b/external/src/commonMain/kotlin/IDBIndexOptions.kt new file mode 100644 index 0000000..83102c7 --- /dev/null +++ b/external/src/commonMain/kotlin/IDBIndexOptions.kt @@ -0,0 +1,8 @@ +package com.juul.indexeddb.external + +public expect interface IDBIndexOptions { + public var multiEntry: Boolean? + public var unique: Boolean? +} + +public expect fun IDBIndexOptions(block: IDBIndexOptions.() -> Unit): IDBIndexOptions diff --git a/external/src/commonMain/kotlin/IDBKey.kt b/external/src/commonMain/kotlin/IDBKey.kt new file mode 100644 index 0000000..ccbccf6 --- /dev/null +++ b/external/src/commonMain/kotlin/IDBKey.kt @@ -0,0 +1,68 @@ +@file:Suppress("NOTHING_TO_INLINE") + +package com.juul.indexeddb.external + +public expect sealed interface IDBKey : JsAny + +public inline fun IDBKey( + value: UnsafeJsAny, +): IDBKey = value.unsafeCast() + +public inline fun IDBKey( + value: JsNumber, +): IDBKey = value.unsafeCast() + +public inline fun IDBKey( + value: Int, +): IDBKey = value.unsafeCast() + +public inline fun IDBKey( + value: Double, +): IDBKey = value.unsafeCast() + +public inline fun IDBKey( + value: JsString, +): IDBKey = value.unsafeCast() + +public inline fun IDBKey( + value: String, +): IDBKey = value.unsafeCast() + +public inline fun IDBKey( + value: JsDate, +): IDBKey = value.unsafeCast() + +public inline fun IDBKey( + value: JsByteArray, +): IDBKey = value.unsafeCast() + +public inline fun IDBKey( + value: ArrayBuffer, +): IDBKey = value.unsafeCast() + +public inline fun IDBKey( + value: ArrayBufferView, +): IDBKey = value.unsafeCast() + +public inline fun IDBKey( + value: JsArray, +): IDBKey = value.unsafeCast() + +public inline fun IDBKey( + value: Array, +): IDBKey = value.unsafeCast() + +public inline fun IDBKey( + value: IDBKeyRange, +): IDBKey = value.unsafeCast() + +public inline fun IDBKey( + key: IDBKey, + vararg moreKeys: IDBKey, +): IDBKey = JsArray() + .apply { + set(0, key) + for (i in moreKeys.indices) { + set(i + 1, moreKeys[i]) + } + }.unsafeCast() diff --git a/external/src/commonMain/kotlin/IDBKeyRange.kt b/external/src/commonMain/kotlin/IDBKeyRange.kt new file mode 100644 index 0000000..a264b18 --- /dev/null +++ b/external/src/commonMain/kotlin/IDBKeyRange.kt @@ -0,0 +1,57 @@ +package com.juul.indexeddb.external + +/** https://developer.mozilla.org/en-US/docs/Web/API/IDBKeyRange */ +public expect class IDBKeyRange : JsAny { + + public fun includes(value: JsAny?): Boolean + + public companion object { + public fun lowerBound(x: JsAny?, open: Boolean): IDBKeyRange + public fun upperBound(y: JsAny?, open: Boolean): IDBKeyRange + public fun bound(x: JsAny?, y: JsAny?, lowerOpen: Boolean, upperOpen: Boolean): IDBKeyRange + public fun only(z: JsAny?): IDBKeyRange + } +} + +public fun IDBKeyRange.Companion.lowerBound(x: String?, open: Boolean): IDBKeyRange = + lowerBound(x?.toJsString(), open) + +public fun IDBKeyRange.Companion.lowerBound(x: Int, open: Boolean): IDBKeyRange = + lowerBound(x.toJsNumber(), open) + +public fun IDBKeyRange.Companion.lowerBound(x: Double, open: Boolean): IDBKeyRange = + lowerBound(x.toJsNumber(), open) + +public fun IDBKeyRange.Companion.upperBound(x: String?, open: Boolean): IDBKeyRange = + upperBound(x?.toJsString(), open) + +public fun IDBKeyRange.Companion.upperBound(x: Int, open: Boolean): IDBKeyRange = + upperBound(x.toJsNumber(), open) + +public fun IDBKeyRange.Companion.upperBound(x: Double, open: Boolean): IDBKeyRange = + upperBound(x.toJsNumber(), open) + +public fun IDBKeyRange.Companion.bound( + x: String?, + y: String?, + lowerOpen: Boolean, + upperOpen: Boolean, +): IDBKeyRange = bound(x?.toJsString(), y?.toJsString(), lowerOpen, upperOpen) + +public fun IDBKeyRange.Companion.bound( + x: Int, + y: Int, + lowerOpen: Boolean, + upperOpen: Boolean, +): IDBKeyRange = bound(x.toJsNumber(), y.toJsNumber(), lowerOpen, upperOpen) + +public fun IDBKeyRange.Companion.bound( + x: Double, + y: Double, + lowerOpen: Boolean, + upperOpen: Boolean, +): IDBKeyRange = bound(x.toJsNumber(), y.toJsNumber(), lowerOpen, upperOpen) + +public fun IDBKeyRange.Companion.only(z: String?): IDBKeyRange = only(z?.toJsString()) +public fun IDBKeyRange.Companion.only(z: Int): IDBKeyRange = only(z.toJsNumber()) +public fun IDBKeyRange.Companion.only(z: Double): IDBKeyRange = only(z.toJsNumber()) diff --git a/external/src/commonMain/kotlin/IDBObjectStore.kt b/external/src/commonMain/kotlin/IDBObjectStore.kt new file mode 100644 index 0000000..ac91e07 --- /dev/null +++ b/external/src/commonMain/kotlin/IDBObjectStore.kt @@ -0,0 +1,21 @@ +package com.juul.indexeddb.external + +/** https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore */ +public expect interface IDBObjectStore : IDBQueryable { + public val name: String + + public fun add(value: JsAny?): IDBRequest + public fun add(value: JsAny?, key: IDBKey): IDBRequest + + public fun put(item: JsAny?): IDBRequest + public fun put(item: JsAny?, key: IDBKey): IDBRequest + + public fun delete(key: IDBKey): IDBRequest<*> + public fun delete(key: IDBKeyRange): IDBRequest<*> + + public fun clear(): IDBRequest<*> + + public fun index(name: String): IDBIndex + public fun deleteIndex(name: String) + public fun createIndex(name: String, keyPath: JsAny?, options: IDBIndexOptions): IDBIndex +} diff --git a/external/src/commonMain/kotlin/IDBObjectStoreOptions.kt b/external/src/commonMain/kotlin/IDBObjectStoreOptions.kt new file mode 100644 index 0000000..e3872fc --- /dev/null +++ b/external/src/commonMain/kotlin/IDBObjectStoreOptions.kt @@ -0,0 +1,15 @@ +package com.juul.indexeddb.external + +/** https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase/createObjectStore#parameters */ +public expect interface IDBObjectStoreOptions { + public val autoIncrement: Boolean? + public val keyPath: JsAny +} + +public expect fun IDBObjectStoreOptions( + autoIncrement: Boolean, +): IDBObjectStoreOptions + +public expect fun IDBObjectStoreOptions( + keyPath: JsAny?, +): IDBObjectStoreOptions diff --git a/external/src/commonMain/kotlin/IDBQueryable.kt b/external/src/commonMain/kotlin/IDBQueryable.kt new file mode 100644 index 0000000..9f23f7a --- /dev/null +++ b/external/src/commonMain/kotlin/IDBQueryable.kt @@ -0,0 +1,43 @@ +package com.juul.indexeddb.external + +/** Pseudo-interface for the shared query functionality between [IDBIndex] and [IDBObjectStore]. */ +public expect interface IDBQueryable { + + public fun count(): IDBRequest + public fun count(key: IDBKey?): IDBRequest + public fun count(key: IDBKeyRange): IDBRequest + + public fun get(key: IDBKey): IDBRequest<*> + public fun get(key: IDBKeyRange): IDBRequest<*> + + public fun getAll(): IDBRequest> + public fun getAll(query: IDBKey?): IDBRequest> + public fun getAll(query: IDBKey?, count: Int): IDBRequest> + + public fun getAll(query: IDBKeyRange?): IDBRequest> + public fun getAll(query: IDBKeyRange?, count: Int): IDBRequest> + + public fun getAllKeys(): IDBRequest> + public fun getAllKeys(query: IDBKey?): IDBRequest> + public fun getAllKeys(query: IDBKey?, count: Int): IDBRequest> + + public fun getAllKeys(query: IDBKeyRange?): IDBRequest> + public fun getAllKeys(query: IDBKeyRange?, count: Int): IDBRequest> + + public fun getKey(query: IDBKey): IDBRequest + public fun getKey(query: IDBKeyRange): IDBRequest + + public fun openCursor(): IDBRequest + public fun openCursor(query: IDBKey?): IDBRequest + public fun openCursor(query: IDBKey?, direction: String): IDBRequest + + public fun openCursor(query: IDBKeyRange?): IDBRequest + public fun openCursor(query: IDBKeyRange?, direction: String): IDBRequest + + public fun openKeyCursor(): IDBRequest + public fun openKeyCursor(query: IDBKey?): IDBRequest + public fun openKeyCursor(query: IDBKey?, direction: String): IDBRequest + + public fun openKeyCursor(query: IDBKeyRange?): IDBRequest + public fun openKeyCursor(query: IDBKeyRange?, direction: String): IDBRequest +} diff --git a/external/src/commonMain/kotlin/IDBRequest.kt b/external/src/commonMain/kotlin/IDBRequest.kt new file mode 100644 index 0000000..b870066 --- /dev/null +++ b/external/src/commonMain/kotlin/IDBRequest.kt @@ -0,0 +1,16 @@ +package com.juul.indexeddb.external + +/** https://developer.mozilla.org/en-US/docs/Web/API/IDBRequest */ +public expect open class IDBRequest : EventTarget { + public val error: JsAny? + public val transaction: IDBTransaction? + public val result: T + public var onerror: (Event) -> Unit + public var onsuccess: (Event) -> Unit +} + +/** https://developer.mozilla.org/en-US/docs/Web/API/IDBOpenDBRequest */ +public expect class IDBOpenDBRequest : IDBRequest { + public var onblocked: (Event) -> Unit + public var onupgradeneeded: (IDBVersionChangeEvent) -> Unit +} diff --git a/external/src/commonMain/kotlin/IDBTransaction.kt b/external/src/commonMain/kotlin/IDBTransaction.kt new file mode 100644 index 0000000..24a487e --- /dev/null +++ b/external/src/commonMain/kotlin/IDBTransaction.kt @@ -0,0 +1,14 @@ +package com.juul.indexeddb.external + +/** https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction */ +public expect class IDBTransaction : EventTarget { + public val objectStoreNames: JsArray // Actually a DOMStringList + public val db: IDBDatabase + public val error: JsAny? + public fun objectStore(name: String): IDBObjectStore + public fun abort() + public fun commit() + public var onabort: (Event) -> Unit + public var onerror: (Event) -> Unit + public var oncomplete: (Event) -> Unit +} diff --git a/external/src/commonMain/kotlin/IDBTransactionOptions.kt b/external/src/commonMain/kotlin/IDBTransactionOptions.kt new file mode 100644 index 0000000..20c39f2 --- /dev/null +++ b/external/src/commonMain/kotlin/IDBTransactionOptions.kt @@ -0,0 +1,16 @@ +package com.juul.indexeddb.external + +/** https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction/durability */ +public expect interface IDBTransactionOptions { + public val durability: String +} + +public enum class IDBTransactionDurability( + public val value: String, +) { + Default("default"), + Strict("strict"), + Relaxed("relaxed"), +} + +public expect fun IDBTransactionOptions(durability: IDBTransactionDurability): IDBTransactionOptions diff --git a/external/src/commonMain/kotlin/IDBVersionChangeEvent.kt b/external/src/commonMain/kotlin/IDBVersionChangeEvent.kt new file mode 100644 index 0000000..12a228a --- /dev/null +++ b/external/src/commonMain/kotlin/IDBVersionChangeEvent.kt @@ -0,0 +1,7 @@ +package com.juul.indexeddb.external + +/** https://developer.mozilla.org/en-US/docs/Web/API/IDBVersionChangeEvent */ +public expect abstract class IDBVersionChangeEvent : Event { + public val oldVersion: Int + public val newVersion: Int +} diff --git a/external/src/commonMain/kotlin/Interop.kt b/external/src/commonMain/kotlin/Interop.kt new file mode 100644 index 0000000..fe901f8 --- /dev/null +++ b/external/src/commonMain/kotlin/Interop.kt @@ -0,0 +1,73 @@ +package com.juul.indexeddb.external + +@PublishedApi +internal expect fun JsAny.unsafeCast(): IDBKey + +@PublishedApi +internal expect fun Int.unsafeCast(): IDBKey + +@PublishedApi +internal expect fun Double.unsafeCast(): IDBKey + +@PublishedApi +internal expect fun String.unsafeCast(): IDBKey + +@PublishedApi +internal expect fun Array.unsafeCast(): IDBKey + +public expect interface JsAny +public expect interface UnsafeJsAny : JsAny + +public expect class JsArray() : JsAny { + public val length: Int +} + +public expect operator fun JsArray.get(index: Int): T? + +public expect operator fun JsArray.set(index: Int, value: T) + +public expect class ReadonlyArray() : JsAny { + public val length: Int +} + +public expect interface ArrayBuffer : JsAny +public expect interface ArrayBufferView : JsAny + +public expect class JsNumber : JsAny +public expect fun JsNumber.toInt(): Int + +public expect fun Int.toJsNumber(): JsNumber +public expect fun Double.toJsNumber(): JsNumber + +public expect class JsBigInt : JsAny +public expect fun Long.toJsBigInt(): JsBigInt + +public expect class JsString : JsAny + +public expect fun String.toJsString(): JsString + +public expect class JsBoolean : JsAny + +public expect class JsByteArray : JsAny +public expect fun Array.toJsByteArray(): JsByteArray + +public expect class JsDate( + value: JsString, +) : JsAny + +public expect val definedExternally: Nothing + +public expect open class Event { + public open val type: String + public open val target: EventTarget? +} + +public expect abstract class EventTarget : JsAny { + public fun addEventListener(type: String, callback: ((Event) -> Unit)?) + public fun removeEventListener(type: String, callback: ((Event) -> Unit)?) +} + +public expect abstract class Window +public expect val window: Window +public expect val Window.indexedDB: IDBFactory? +public expect val indexedDB: IDBFactory? diff --git a/external/src/jsMain/kotlin/IDBCursor.kt b/external/src/jsMain/kotlin/IDBCursor.kt index ab0f7ed..ed2d5f4 100644 --- a/external/src/jsMain/kotlin/IDBCursor.kt +++ b/external/src/jsMain/kotlin/IDBCursor.kt @@ -1,21 +1,20 @@ package com.juul.indexeddb.external -/** https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor */ -public external interface IDBCursor { - public val key: dynamic - public val primaryKey: dynamic +public actual external interface IDBCursor : JsAny { + public actual val key: IDBKey + public actual val primaryKey: IDBKey - public fun advance(count: Int) + public actual fun advance(count: Int) - public fun `continue`(key: dynamic = definedExternally) + public actual fun `continue`() + public actual fun `continue`(key: IDBKey) - public fun continuePrimaryKey(key: dynamic, primaryKey: dynamic) + public actual fun continuePrimaryKey(key: IDBKey, primaryKey: IDBKey) } -/** https://developer.mozilla.org/en-US/docs/Web/API/IDBCursorWithValue */ -public external interface IDBCursorWithValue : IDBCursor { - public val value: dynamic +public actual external interface IDBCursorWithValue : IDBCursor { + public actual val value: JsAny? - public fun delete(): IDBRequest - public fun update(value: dynamic): IDBRequest + public actual fun delete(): IDBRequest<*> + public actual fun update(value: JsAny?): IDBRequest } diff --git a/external/src/jsMain/kotlin/IDBDatabase.kt b/external/src/jsMain/kotlin/IDBDatabase.kt index 6ee52a7..8ff7346 100644 --- a/external/src/jsMain/kotlin/IDBDatabase.kt +++ b/external/src/jsMain/kotlin/IDBDatabase.kt @@ -1,15 +1,23 @@ package com.juul.indexeddb.external -import org.w3c.dom.events.EventTarget +public actual external class IDBDatabase : EventTarget { + public actual val name: String + public actual val version: Int + public actual val objectStoreNames: JsArray + public actual fun close() + public actual fun createObjectStore(name: String): IDBObjectStore + public actual fun createObjectStore(name: String, options: IDBObjectStoreOptions?): IDBObjectStore + public actual fun deleteObjectStore(name: String) -/** https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase */ -public external class IDBDatabase : EventTarget { - public val name: String - public val version: Int - public val objectStoreNames: Array - public fun close() - public fun createObjectStore(name: String): IDBObjectStore - public fun createObjectStore(name: String, options: dynamic): IDBObjectStore - public fun deleteObjectStore(name: String) - public fun transaction(storeNames: Array, mode: String, options: dynamic): IDBTransaction + public actual fun transaction( + storeName: String, + mode: String, + options: IDBTransactionOptions, + ): IDBTransaction + + public actual fun transaction( + storeNames: ReadonlyArray, + mode: String, + options: IDBTransactionOptions, + ): IDBTransaction } diff --git a/external/src/jsMain/kotlin/IDBFactory.kt b/external/src/jsMain/kotlin/IDBFactory.kt index 409166d..7168c44 100644 --- a/external/src/jsMain/kotlin/IDBFactory.kt +++ b/external/src/jsMain/kotlin/IDBFactory.kt @@ -1,13 +1,6 @@ package com.juul.indexeddb.external -import org.w3c.dom.Window - -/** https://developer.mozilla.org/en-US/docs/Web/API/IDBFactory */ -public external interface IDBFactory { - public fun open(name: String, version: Int): IDBOpenDBRequest - public fun deleteDatabase(name: String): IDBOpenDBRequest +public actual external interface IDBFactory { + public actual fun open(name: String, version: Int): IDBOpenDBRequest + public actual fun deleteDatabase(name: String): IDBOpenDBRequest } - -@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE") -public val Window.indexedDB: IDBFactory? - get() = this.asDynamic().indexedDB as? IDBFactory diff --git a/external/src/jsMain/kotlin/IDBIndex.kt b/external/src/jsMain/kotlin/IDBIndex.kt index 448ecd0..f6f1570 100644 --- a/external/src/jsMain/kotlin/IDBIndex.kt +++ b/external/src/jsMain/kotlin/IDBIndex.kt @@ -1,7 +1,6 @@ package com.juul.indexeddb.external -/** https://developer.mozilla.org/en-US/docs/Web/API/IDBIndex */ -public external interface IDBIndex : IDBQueryable { - public val name: String - public val objectStore: IDBObjectStore +public actual external interface IDBIndex : IDBQueryable { + public actual val name: String + public actual val objectStore: IDBObjectStore } diff --git a/external/src/jsMain/kotlin/IDBIndexOptions.kt b/external/src/jsMain/kotlin/IDBIndexOptions.kt new file mode 100644 index 0000000..55cf56b --- /dev/null +++ b/external/src/jsMain/kotlin/IDBIndexOptions.kt @@ -0,0 +1,10 @@ +package com.juul.indexeddb.external + +public actual external interface IDBIndexOptions { + public actual var multiEntry: Boolean? + public actual var unique: Boolean? +} + +public actual fun IDBIndexOptions( + block: IDBIndexOptions.() -> Unit, +): IDBIndexOptions = jso(block) diff --git a/external/src/jsMain/kotlin/IDBKey.kt b/external/src/jsMain/kotlin/IDBKey.kt new file mode 100644 index 0000000..6373b84 --- /dev/null +++ b/external/src/jsMain/kotlin/IDBKey.kt @@ -0,0 +1,3 @@ +package com.juul.indexeddb.external + +public actual sealed external interface IDBKey : JsAny diff --git a/external/src/jsMain/kotlin/IDBKeyRange.kt b/external/src/jsMain/kotlin/IDBKeyRange.kt index c29a7a5..0980652 100644 --- a/external/src/jsMain/kotlin/IDBKeyRange.kt +++ b/external/src/jsMain/kotlin/IDBKeyRange.kt @@ -1,14 +1,13 @@ package com.juul.indexeddb.external -/** https://developer.mozilla.org/en-US/docs/Web/API/IDBKeyRange */ -public external class IDBKeyRange { +public actual external class IDBKeyRange : JsAny { - public fun includes(value: dynamic): Boolean + public actual fun includes(value: JsAny?): Boolean - public companion object { - public fun lowerBound(x: dynamic, open: Boolean): IDBKeyRange - public fun upperBound(y: dynamic, open: Boolean): IDBKeyRange - public fun bound(x: dynamic, y: dynamic, lowerOpen: Boolean, upperOpen: Boolean): IDBKeyRange - public fun only(z: dynamic): IDBKeyRange + public actual companion object { + public actual fun lowerBound(x: JsAny?, open: Boolean): IDBKeyRange + public actual fun upperBound(y: JsAny?, open: Boolean): IDBKeyRange + public actual fun bound(x: JsAny?, y: JsAny?, lowerOpen: Boolean, upperOpen: Boolean): IDBKeyRange + public actual fun only(z: JsAny?): IDBKeyRange } } diff --git a/external/src/jsMain/kotlin/IDBObjectStore.kt b/external/src/jsMain/kotlin/IDBObjectStore.kt index 9513117..d79d660 100644 --- a/external/src/jsMain/kotlin/IDBObjectStore.kt +++ b/external/src/jsMain/kotlin/IDBObjectStore.kt @@ -1,20 +1,20 @@ package com.juul.indexeddb.external -/** https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore */ -public external interface IDBObjectStore : IDBQueryable { - public val name: String +public actual external interface IDBObjectStore : IDBQueryable { + public actual val name: String - public fun add(value: dynamic): IDBRequest - public fun add(value: dynamic, key: dynamic): IDBRequest + public actual fun add(value: JsAny?): IDBRequest + public actual fun add(value: JsAny?, key: IDBKey): IDBRequest - public fun put(item: dynamic): IDBRequest - public fun put(item: dynamic, key: dynamic): IDBRequest + public actual fun put(item: JsAny?): IDBRequest + public actual fun put(item: JsAny?, key: IDBKey): IDBRequest - public fun delete(key: dynamic): IDBRequest + public actual fun delete(key: IDBKey): IDBRequest<*> + public actual fun delete(key: IDBKeyRange): IDBRequest<*> - public fun clear(): IDBRequest + public actual fun clear(): IDBRequest<*> - public fun index(name: String): IDBIndex - public fun deleteIndex(name: String): Unit - public fun createIndex(name: String, keyPath: dynamic, parameters: dynamic): IDBIndex + public actual fun index(name: String): IDBIndex + public actual fun deleteIndex(name: String) + public actual fun createIndex(name: String, keyPath: JsAny?, options: IDBIndexOptions): IDBIndex } diff --git a/external/src/jsMain/kotlin/IDBObjectStoreOptions.kt b/external/src/jsMain/kotlin/IDBObjectStoreOptions.kt new file mode 100644 index 0000000..bbda75e --- /dev/null +++ b/external/src/jsMain/kotlin/IDBObjectStoreOptions.kt @@ -0,0 +1,14 @@ +package com.juul.indexeddb.external + +public actual external interface IDBObjectStoreOptions : JsAny { + public actual val autoIncrement: Boolean? + public actual val keyPath: JsAny +} + +public actual fun IDBObjectStoreOptions( + autoIncrement: Boolean, +): IDBObjectStoreOptions = js("({ autoIncrement: autoIncrement })").unsafeCast() + +public actual fun IDBObjectStoreOptions( + keyPath: JsAny?, +): IDBObjectStoreOptions = js("({ keyPath: keyPath })").unsafeCast() diff --git a/external/src/jsMain/kotlin/IDBQueryable.kt b/external/src/jsMain/kotlin/IDBQueryable.kt index 2085468..43b70fc 100644 --- a/external/src/jsMain/kotlin/IDBQueryable.kt +++ b/external/src/jsMain/kotlin/IDBQueryable.kt @@ -1,28 +1,43 @@ package com.juul.indexeddb.external -/** Pseudo-interface for the shared query functionality between [IDBIndex] and [IDBObjectStore]. */ -public external interface IDBQueryable { +public actual external interface IDBQueryable { - public fun count(): IDBRequest - public fun count(key: dynamic): IDBRequest + public actual fun count(): IDBRequest + public actual fun count(key: IDBKey?): IDBRequest + public actual fun count(key: IDBKeyRange): IDBRequest - public fun get(key: dynamic): IDBRequest + public actual fun get(key: IDBKey): IDBRequest<*> + public actual fun get(key: IDBKeyRange): IDBRequest<*> - public fun getAll(): IDBRequest - public fun getAll(query: dynamic): IDBRequest - public fun getAll(query: dynamic, count: Int): IDBRequest + public actual fun getAll(): IDBRequest> + public actual fun getAll(query: IDBKey?): IDBRequest> + public actual fun getAll(query: IDBKey?, count: Int): IDBRequest> - public fun getAllKeys(): IDBRequest - public fun getAllKeys(query: dynamic): IDBRequest - public fun getAllKeys(query: dynamic, count: Int): IDBRequest + public actual fun getAll(query: IDBKeyRange?): IDBRequest> + public actual fun getAll(query: IDBKeyRange?, count: Int): IDBRequest> - public fun getKey(key: dynamic): IDBRequest + public actual fun getAllKeys(): IDBRequest> + public actual fun getAllKeys(query: IDBKey?): IDBRequest> + public actual fun getAllKeys(query: IDBKey?, count: Int): IDBRequest> - public fun openCursor(): IDBRequest - public fun openCursor(query: dynamic): IDBRequest - public fun openCursor(query: dynamic, direction: String): IDBRequest + public actual fun getAllKeys(query: IDBKeyRange?): IDBRequest> + public actual fun getAllKeys(query: IDBKeyRange?, count: Int): IDBRequest> - public fun openKeyCursor(): IDBRequest - public fun openKeyCursor(query: dynamic): IDBRequest - public fun openKeyCursor(query: dynamic, direction: String): IDBRequest + public actual fun getKey(query: IDBKey): IDBRequest + public actual fun getKey(query: IDBKeyRange): IDBRequest + + public actual fun openCursor(): IDBRequest + public actual fun openCursor(query: IDBKey?): IDBRequest + public actual fun openCursor(query: IDBKey?, direction: String): IDBRequest + + public actual fun openCursor(query: IDBKeyRange?): IDBRequest + + public actual fun openCursor(query: IDBKeyRange?, direction: String): IDBRequest + + public actual fun openKeyCursor(): IDBRequest + public actual fun openKeyCursor(query: IDBKey?): IDBRequest + public actual fun openKeyCursor(query: IDBKey?, direction: String): IDBRequest + + public actual fun openKeyCursor(query: IDBKeyRange?): IDBRequest + public actual fun openKeyCursor(query: IDBKeyRange?, direction: String): IDBRequest } diff --git a/external/src/jsMain/kotlin/IDBRequest.kt b/external/src/jsMain/kotlin/IDBRequest.kt index 89c8347..f4d4c9b 100644 --- a/external/src/jsMain/kotlin/IDBRequest.kt +++ b/external/src/jsMain/kotlin/IDBRequest.kt @@ -1,19 +1,14 @@ package com.juul.indexeddb.external -import org.w3c.dom.events.Event -import org.w3c.dom.events.EventTarget - -/** https://developer.mozilla.org/en-US/docs/Web/API/IDBRequest */ -public open external class IDBRequest : EventTarget { - public val error: Throwable? - public val transaction: IDBTransaction? - public val result: T - public var onerror: (Event) -> Unit - public var onsuccess: (Event) -> Unit +public actual open external class IDBRequest : EventTarget { + public actual val error: JsAny? + public actual val transaction: IDBTransaction? + public actual val result: T + public actual var onerror: (Event) -> Unit + public actual var onsuccess: (Event) -> Unit } -/** https://developer.mozilla.org/en-US/docs/Web/API/IDBOpenDBRequest */ -public external class IDBOpenDBRequest : IDBRequest { - public var onblocked: (Event) -> Unit - public var onupgradeneeded: (IDBVersionChangeEvent) -> Unit +public actual external class IDBOpenDBRequest : IDBRequest { + public actual var onblocked: (Event) -> Unit + public actual var onupgradeneeded: (IDBVersionChangeEvent) -> Unit } diff --git a/external/src/jsMain/kotlin/IDBTransaction.kt b/external/src/jsMain/kotlin/IDBTransaction.kt index 37144c9..7f299f6 100644 --- a/external/src/jsMain/kotlin/IDBTransaction.kt +++ b/external/src/jsMain/kotlin/IDBTransaction.kt @@ -1,17 +1,13 @@ package com.juul.indexeddb.external -import org.w3c.dom.events.Event -import org.w3c.dom.events.EventTarget - -/** https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction */ -public external class IDBTransaction : EventTarget { - public val objectStoreNames: Array // Actually a DOMStringList - public val db: IDBDatabase - public val error: Throwable? - public fun objectStore(name: String): IDBObjectStore - public fun abort(): Unit - public fun commit(): Unit - public var onabort: (Event) -> Unit - public var onerror: (Event) -> Unit - public var oncomplete: (Event) -> Unit +public actual external class IDBTransaction : EventTarget { + public actual val objectStoreNames: JsArray // Actually a DOMStringList + public actual val db: IDBDatabase + public actual val error: JsAny? + public actual fun objectStore(name: String): IDBObjectStore + public actual fun abort() + public actual fun commit() + public actual var onabort: (Event) -> Unit + public actual var onerror: (Event) -> Unit + public actual var oncomplete: (Event) -> Unit } diff --git a/external/src/jsMain/kotlin/IDBTransactionOptions.kt b/external/src/jsMain/kotlin/IDBTransactionOptions.kt new file mode 100644 index 0000000..de14bf3 --- /dev/null +++ b/external/src/jsMain/kotlin/IDBTransactionOptions.kt @@ -0,0 +1,11 @@ +package com.juul.indexeddb.external + +public actual external interface IDBTransactionOptions : JsAny { + public actual val durability: String +} + +private fun IDBTransactionOptions(durability: String): IDBTransactionOptions = + js("({ durability: durability })").unsafeCast() + +public actual fun IDBTransactionOptions(durability: IDBTransactionDurability): IDBTransactionOptions = + IDBTransactionOptions(durability.value) diff --git a/external/src/jsMain/kotlin/IDBVersionChangeEvent.kt b/external/src/jsMain/kotlin/IDBVersionChangeEvent.kt index 4b1495a..bfcec7c 100644 --- a/external/src/jsMain/kotlin/IDBVersionChangeEvent.kt +++ b/external/src/jsMain/kotlin/IDBVersionChangeEvent.kt @@ -1,9 +1,6 @@ package com.juul.indexeddb.external -import org.w3c.dom.events.Event - -/** https://developer.mozilla.org/en-US/docs/Web/API/IDBVersionChangeEvent */ -public abstract external class IDBVersionChangeEvent : Event { - public val oldVersion: Int - public val newVersion: Int +public actual abstract external class IDBVersionChangeEvent : Event { + public actual val oldVersion: Int + public actual val newVersion: Int } diff --git a/external/src/jsMain/kotlin/Interop.kt b/external/src/jsMain/kotlin/Interop.kt new file mode 100644 index 0000000..5eb3311 --- /dev/null +++ b/external/src/jsMain/kotlin/Interop.kt @@ -0,0 +1,106 @@ +package com.juul.indexeddb.external + +import kotlinx.browser.window +import org.khronos.webgl.Uint8Array +import kotlin.js.unsafeCast +import kotlin.js.unsafeCast as jsUnsafeCast + +@PublishedApi +internal actual fun JsAny.unsafeCast(): IDBKey = jsUnsafeCast() + +@PublishedApi +internal actual fun Int.unsafeCast(): IDBKey = jsUnsafeCast() + +@PublishedApi +internal actual fun Double.unsafeCast(): IDBKey = jsUnsafeCast() + +@PublishedApi +internal actual fun String.unsafeCast(): IDBKey = jsUnsafeCast() + +@PublishedApi +internal actual fun Array.unsafeCast(): IDBKey = jsUnsafeCast() + +public actual external interface JsAny +public actual external interface UnsafeJsAny : JsAny + +@JsName("Array") +public actual external class JsArray actual constructor() : JsAny { + public actual val length: Int +} + +@JsName("Array") +public actual external class ReadonlyArray actual constructor() : JsAny { + public actual val length: Int +} + +public actual operator fun JsArray.get(index: Int): T? = + jsArrayGet(this, index) + +public actual operator fun JsArray.set(index: Int, value: T) { + jsArraySet(this, index, value) +} + +@Suppress("UnsafeCastFromDynamic") +private fun jsArrayGet(array: JsArray, index: Int): T? = + js("array[index]") + +private fun jsArraySet(array: JsArray, index: Int, value: T) { + js("array[index] = value") +} + +@JsName("ArrayBuffer") +public actual external interface ArrayBuffer : JsAny + +@JsName("ArrayBufferView") +public actual external interface ArrayBufferView : JsAny + +@JsName("Boolean") +public actual external class JsBoolean : JsAny + +@JsName("Number") +public actual external class JsNumber : JsAny + +public actual fun JsNumber.toInt(): Int = this.unsafeCast().toInt() + +public actual fun Int.toJsNumber(): JsNumber = unsafeCast() +public actual fun Double.toJsNumber(): JsNumber = unsafeCast() + +@JsName("BigInt") +public actual external class JsBigInt : JsAny + +public actual fun Long.toJsBigInt(): JsBigInt = unsafeCast() + +@JsName("String") +public actual external class JsString : JsAny + +public actual fun String.toJsString(): JsString = this.jsUnsafeCast() + +@JsName("Uint8Array") +public actual external class JsByteArray : + Uint8Array, + JsAny + +public actual fun Array.toJsByteArray(): JsByteArray = Uint8Array(this).unsafeCast() + +@JsName("Date") +public actual external class JsDate actual constructor( + value: JsString, +) : JsAny + +public actual external val definedExternally: Nothing = kotlin.js.definedExternally + +public actual open external class Event { + public actual open val type: String + public actual open val target: EventTarget? +} + +public actual abstract external class EventTarget : JsAny { + public actual fun addEventListener(type: String, callback: ((Event) -> Unit)?) + public actual fun removeEventListener(type: String, callback: ((Event) -> Unit)?) +} + +public actual typealias Window = org.w3c.dom.Window + +public actual val window: Window = window +public actual val Window.indexedDB: IDBFactory? get() = com.juul.indexeddb.external.indexedDB +public actual val indexedDB: IDBFactory? = js("window.indexedDB").unsafeCast() diff --git a/external/src/jsMain/kotlin/Jso.kt b/external/src/jsMain/kotlin/Jso.kt new file mode 100644 index 0000000..95a7b9b --- /dev/null +++ b/external/src/jsMain/kotlin/Jso.kt @@ -0,0 +1,7 @@ +package com.juul.indexeddb.external + +// Copied from: +// https://github.com/JetBrains/kotlin-wrappers/blob/91b2c1568ec6f779af5ec10d89b5e2cbdfe785ff/kotlin-extensions/src/main/kotlin/kotlinx/js/jso.kt + +internal fun jso(): T = js("({})").unsafeCast() +internal fun jso(block: T.() -> Unit): T = jso().apply(block) diff --git a/external/src/wasmJsMain/kotlin/IDBCursor.kt b/external/src/wasmJsMain/kotlin/IDBCursor.kt index 0a74caa..ed2d5f4 100644 --- a/external/src/wasmJsMain/kotlin/IDBCursor.kt +++ b/external/src/wasmJsMain/kotlin/IDBCursor.kt @@ -1,21 +1,20 @@ package com.juul.indexeddb.external -/** https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor */ -public external interface IDBCursor : JsAny { - public val key: IDBKey - public val primaryKey: IDBKey +public actual external interface IDBCursor : JsAny { + public actual val key: IDBKey + public actual val primaryKey: IDBKey - public fun advance(count: Int) + public actual fun advance(count: Int) - public fun `continue`(key: IDBKey = definedExternally) + public actual fun `continue`() + public actual fun `continue`(key: IDBKey) - public fun continuePrimaryKey(key: IDBKey, primaryKey: IDBKey) + public actual fun continuePrimaryKey(key: IDBKey, primaryKey: IDBKey) } -/** https://developer.mozilla.org/en-US/docs/Web/API/IDBCursorWithValue */ -public external interface IDBCursorWithValue : IDBCursor { - public val value: JsAny? +public actual external interface IDBCursorWithValue : IDBCursor { + public actual val value: JsAny? - public fun delete(): IDBRequest<*> - public fun update(value: JsAny?): IDBRequest + public actual fun delete(): IDBRequest<*> + public actual fun update(value: JsAny?): IDBRequest } diff --git a/external/src/wasmJsMain/kotlin/IDBDatabase.kt b/external/src/wasmJsMain/kotlin/IDBDatabase.kt index ac9528a..8ff7346 100644 --- a/external/src/wasmJsMain/kotlin/IDBDatabase.kt +++ b/external/src/wasmJsMain/kotlin/IDBDatabase.kt @@ -1,20 +1,23 @@ package com.juul.indexeddb.external -import org.w3c.dom.events.EventTarget +public actual external class IDBDatabase : EventTarget { + public actual val name: String + public actual val version: Int + public actual val objectStoreNames: JsArray + public actual fun close() + public actual fun createObjectStore(name: String): IDBObjectStore + public actual fun createObjectStore(name: String, options: IDBObjectStoreOptions?): IDBObjectStore + public actual fun deleteObjectStore(name: String) -/** https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase */ -public external class IDBDatabase : EventTarget { - public val name: String - public val version: Int - public val objectStoreNames: JsArray - public fun close() - public fun createObjectStore(name: String): IDBObjectStore - public fun createObjectStore(name: String, options: IDBObjectStoreOptions?): IDBObjectStore - public fun deleteObjectStore(name: String) + public actual fun transaction( + storeName: String, + mode: String, + options: IDBTransactionOptions, + ): IDBTransaction - public fun transaction( + public actual fun transaction( storeNames: ReadonlyArray, - mode: String = definedExternally, - options: IDBTransactionOptions = definedExternally, + mode: String, + options: IDBTransactionOptions, ): IDBTransaction } diff --git a/external/src/wasmJsMain/kotlin/IDBFactory.kt b/external/src/wasmJsMain/kotlin/IDBFactory.kt index 9667639..69b19e2 100644 --- a/external/src/wasmJsMain/kotlin/IDBFactory.kt +++ b/external/src/wasmJsMain/kotlin/IDBFactory.kt @@ -1,14 +1,6 @@ package com.juul.indexeddb.external -import org.w3c.dom.Window - -/** https://developer.mozilla.org/en-US/docs/Web/API/IDBFactory */ -public external interface IDBFactory : JsAny { - public fun open(name: String, version: Int = definedExternally): IDBOpenDBRequest - public fun deleteDatabase(name: String): IDBOpenDBRequest +public actual external interface IDBFactory : JsAny { + public actual fun open(name: String, version: Int): IDBOpenDBRequest + public actual fun deleteDatabase(name: String): IDBOpenDBRequest } - -public val Window.indexedDB: IDBFactory? get() = com.juul.indexeddb.external.indexedDB - -@Suppress("RedundantNullableReturnType") -public val indexedDB: IDBFactory? = js("window.indexedDB") diff --git a/external/src/wasmJsMain/kotlin/IDBIndex.kt b/external/src/wasmJsMain/kotlin/IDBIndex.kt index 448ecd0..f6f1570 100644 --- a/external/src/wasmJsMain/kotlin/IDBIndex.kt +++ b/external/src/wasmJsMain/kotlin/IDBIndex.kt @@ -1,7 +1,6 @@ package com.juul.indexeddb.external -/** https://developer.mozilla.org/en-US/docs/Web/API/IDBIndex */ -public external interface IDBIndex : IDBQueryable { - public val name: String - public val objectStore: IDBObjectStore +public actual external interface IDBIndex : IDBQueryable { + public actual val name: String + public actual val objectStore: IDBObjectStore } diff --git a/external/src/wasmJsMain/kotlin/IDBIndexOptions.kt b/external/src/wasmJsMain/kotlin/IDBIndexOptions.kt index a7be6f8..ad5b5f8 100644 --- a/external/src/wasmJsMain/kotlin/IDBIndexOptions.kt +++ b/external/src/wasmJsMain/kotlin/IDBIndexOptions.kt @@ -1,6 +1,10 @@ package com.juul.indexeddb.external -public external interface IDBIndexOptions : JsAny { - public var multiEntry: Boolean? - public var unique: Boolean? +public actual external interface IDBIndexOptions : JsAny { + public actual var multiEntry: Boolean? + public actual var unique: Boolean? } + +public actual fun IDBIndexOptions( + block: IDBIndexOptions.() -> Unit, +): IDBIndexOptions = jso(block) diff --git a/external/src/wasmJsMain/kotlin/IDBKey.kt b/external/src/wasmJsMain/kotlin/IDBKey.kt index 972660a..6373b84 100644 --- a/external/src/wasmJsMain/kotlin/IDBKey.kt +++ b/external/src/wasmJsMain/kotlin/IDBKey.kt @@ -1,3 +1,3 @@ package com.juul.indexeddb.external -public external interface IDBKey : JsAny +public actual sealed external interface IDBKey : JsAny diff --git a/external/src/wasmJsMain/kotlin/IDBKeyRange.kt b/external/src/wasmJsMain/kotlin/IDBKeyRange.kt index 3315712..0980652 100644 --- a/external/src/wasmJsMain/kotlin/IDBKeyRange.kt +++ b/external/src/wasmJsMain/kotlin/IDBKeyRange.kt @@ -1,22 +1,13 @@ package com.juul.indexeddb.external -/** https://developer.mozilla.org/en-US/docs/Web/API/IDBKeyRange */ -public external class IDBKeyRange : JsAny { +public actual external class IDBKeyRange : JsAny { - public fun includes(value: JsAny?): Boolean + public actual fun includes(value: JsAny?): Boolean - public companion object { - public fun lowerBound(x: JsAny?, open: Boolean = definedExternally): IDBKeyRange - - public fun upperBound(y: JsAny?, open: Boolean = definedExternally): IDBKeyRange - - public fun bound( - x: JsAny?, - y: JsAny?, - lowerOpen: Boolean = definedExternally, - upperOpen: Boolean = definedExternally, - ): IDBKeyRange - - public fun only(z: JsAny?): IDBKeyRange + public actual companion object { + public actual fun lowerBound(x: JsAny?, open: Boolean): IDBKeyRange + public actual fun upperBound(y: JsAny?, open: Boolean): IDBKeyRange + public actual fun bound(x: JsAny?, y: JsAny?, lowerOpen: Boolean, upperOpen: Boolean): IDBKeyRange + public actual fun only(z: JsAny?): IDBKeyRange } } diff --git a/external/src/wasmJsMain/kotlin/IDBObjectStore.kt b/external/src/wasmJsMain/kotlin/IDBObjectStore.kt index a358815..d79d660 100644 --- a/external/src/wasmJsMain/kotlin/IDBObjectStore.kt +++ b/external/src/wasmJsMain/kotlin/IDBObjectStore.kt @@ -1,24 +1,20 @@ package com.juul.indexeddb.external -/** https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore */ -public external interface IDBObjectStore : IDBQueryable { - public val name: String +public actual external interface IDBObjectStore : IDBQueryable { + public actual val name: String - public fun add(value: JsAny?, key: IDBKey = definedExternally): IDBRequest + public actual fun add(value: JsAny?): IDBRequest + public actual fun add(value: JsAny?, key: IDBKey): IDBRequest - public fun put(item: JsAny?, key: IDBKey = definedExternally): IDBRequest + public actual fun put(item: JsAny?): IDBRequest + public actual fun put(item: JsAny?, key: IDBKey): IDBRequest - public fun delete(key: IDBKey): IDBRequest<*> - public fun delete(key: IDBKeyRange): IDBRequest<*> + public actual fun delete(key: IDBKey): IDBRequest<*> + public actual fun delete(key: IDBKeyRange): IDBRequest<*> - public fun clear(): IDBRequest<*> + public actual fun clear(): IDBRequest<*> - public fun index(name: String): IDBIndex - public fun deleteIndex(name: String) - - public fun createIndex( - name: String, - keyPath: JsAny?, - options: IDBIndexOptions = definedExternally, - ): IDBIndex + public actual fun index(name: String): IDBIndex + public actual fun deleteIndex(name: String) + public actual fun createIndex(name: String, keyPath: JsAny?, options: IDBIndexOptions): IDBIndex } diff --git a/external/src/wasmJsMain/kotlin/IDBObjectStoreOptions.kt b/external/src/wasmJsMain/kotlin/IDBObjectStoreOptions.kt index 3f20c4d..fb7b5ab 100644 --- a/external/src/wasmJsMain/kotlin/IDBObjectStoreOptions.kt +++ b/external/src/wasmJsMain/kotlin/IDBObjectStoreOptions.kt @@ -1,17 +1,14 @@ package com.juul.indexeddb.external -/** https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase/createObjectStore#parameters */ -public external interface IDBObjectStoreOptions : JsAny { - public val autoIncrement: Boolean? - public val keyPath: JsAny +public actual external interface IDBObjectStoreOptions : JsAny { + public actual val autoIncrement: Boolean? + public actual val keyPath: JsAny } -public fun IDBObjectStoreOptions( +public actual fun IDBObjectStoreOptions( autoIncrement: Boolean, -): IDBObjectStoreOptions = - js("({ autoIncrement: autoIncrement })") +): IDBObjectStoreOptions = js("({ autoIncrement: autoIncrement })") -public fun IDBObjectStoreOptions( - autoIncrement: Boolean, +public actual fun IDBObjectStoreOptions( keyPath: JsAny?, -): IDBObjectStoreOptions = js("({ autoIncrement: autoIncrement, keyPath: keyPath })") +): IDBObjectStoreOptions = js("({ keyPath: keyPath })") diff --git a/external/src/wasmJsMain/kotlin/IDBQueryable.kt b/external/src/wasmJsMain/kotlin/IDBQueryable.kt index 6d44918..9ca6602 100644 --- a/external/src/wasmJsMain/kotlin/IDBQueryable.kt +++ b/external/src/wasmJsMain/kotlin/IDBQueryable.kt @@ -1,54 +1,43 @@ package com.juul.indexeddb.external -/** Pseudo-interface for the shared query functionality between [IDBIndex] and [IDBObjectStore]. */ -public external interface IDBQueryable : JsAny { - - public fun count(key: IDBKey? = definedExternally): IDBRequest - public fun count(key: IDBKeyRange): IDBRequest - - public fun get(key: IDBKey): IDBRequest<*> - public fun get(key: IDBKeyRange): IDBRequest<*> - - public fun getAll( - query: IDBKey? = definedExternally, - count: Int = definedExternally, - ): IDBRequest> - - public fun getAll( - query: IDBKeyRange?, - count: Int = definedExternally, - ): IDBRequest> - - public fun getAllKeys( - query: IDBKey? = definedExternally, - count: Int = definedExternally, - ): IDBRequest> - - public fun getAllKeys( - query: IDBKeyRange?, - count: Int = definedExternally, - ): IDBRequest> - - public fun getKey(query: IDBKey): IDBRequest - public fun getKey(query: IDBKeyRange): IDBRequest - - public fun openCursor( - query: IDBKey? = definedExternally, - direction: String = definedExternally, - ): IDBRequest - - public fun openCursor( - query: IDBKeyRange?, - direction: String = definedExternally, - ): IDBRequest - - public fun openKeyCursor( - query: IDBKey? = definedExternally, - direction: String = definedExternally, - ): IDBRequest - - public fun openKeyCursor( - query: IDBKeyRange?, - direction: String = definedExternally, - ): IDBRequest +public actual external interface IDBQueryable : JsAny { + + public actual fun count(): IDBRequest + public actual fun count(key: IDBKey?): IDBRequest + public actual fun count(key: IDBKeyRange): IDBRequest + + public actual fun get(key: IDBKey): IDBRequest<*> + public actual fun get(key: IDBKeyRange): IDBRequest<*> + + public actual fun getAll(): IDBRequest> + public actual fun getAll(query: IDBKey?): IDBRequest> + public actual fun getAll(query: IDBKey?, count: Int): IDBRequest> + + public actual fun getAll(query: IDBKeyRange?): IDBRequest> + public actual fun getAll(query: IDBKeyRange?, count: Int): IDBRequest> + + public actual fun getAllKeys(): IDBRequest> + public actual fun getAllKeys(query: IDBKey?): IDBRequest> + public actual fun getAllKeys(query: IDBKey?, count: Int): IDBRequest> + + public actual fun getAllKeys(query: IDBKeyRange?): IDBRequest> + public actual fun getAllKeys(query: IDBKeyRange?, count: Int): IDBRequest> + + public actual fun getKey(query: IDBKey): IDBRequest + public actual fun getKey(query: IDBKeyRange): IDBRequest + + public actual fun openCursor(): IDBRequest + public actual fun openCursor(query: IDBKey?): IDBRequest + public actual fun openCursor(query: IDBKey?, direction: String): IDBRequest + + public actual fun openCursor(query: IDBKeyRange?): IDBRequest + + public actual fun openCursor(query: IDBKeyRange?, direction: String): IDBRequest + + public actual fun openKeyCursor(): IDBRequest + public actual fun openKeyCursor(query: IDBKey?): IDBRequest + public actual fun openKeyCursor(query: IDBKey?, direction: String): IDBRequest + + public actual fun openKeyCursor(query: IDBKeyRange?): IDBRequest + public actual fun openKeyCursor(query: IDBKeyRange?, direction: String): IDBRequest } diff --git a/external/src/wasmJsMain/kotlin/IDBRequest.kt b/external/src/wasmJsMain/kotlin/IDBRequest.kt index 0f93963..f4d4c9b 100644 --- a/external/src/wasmJsMain/kotlin/IDBRequest.kt +++ b/external/src/wasmJsMain/kotlin/IDBRequest.kt @@ -1,19 +1,14 @@ package com.juul.indexeddb.external -import org.w3c.dom.events.Event -import org.w3c.dom.events.EventTarget - -/** https://developer.mozilla.org/en-US/docs/Web/API/IDBRequest */ -public open external class IDBRequest : EventTarget { - public val error: JsAny? - public val transaction: IDBTransaction? - public val result: T - public var onerror: (Event) -> Unit - public var onsuccess: (Event) -> Unit +public actual open external class IDBRequest : EventTarget { + public actual val error: JsAny? + public actual val transaction: IDBTransaction? + public actual val result: T + public actual var onerror: (Event) -> Unit + public actual var onsuccess: (Event) -> Unit } -/** https://developer.mozilla.org/en-US/docs/Web/API/IDBOpenDBRequest */ -public external class IDBOpenDBRequest : IDBRequest { - public var onblocked: (Event) -> Unit - public var onupgradeneeded: (IDBVersionChangeEvent) -> Unit +public actual external class IDBOpenDBRequest : IDBRequest { + public actual var onblocked: (Event) -> Unit + public actual var onupgradeneeded: (IDBVersionChangeEvent) -> Unit } diff --git a/external/src/wasmJsMain/kotlin/IDBTransaction.kt b/external/src/wasmJsMain/kotlin/IDBTransaction.kt index 265f652..7f299f6 100644 --- a/external/src/wasmJsMain/kotlin/IDBTransaction.kt +++ b/external/src/wasmJsMain/kotlin/IDBTransaction.kt @@ -1,17 +1,13 @@ package com.juul.indexeddb.external -import org.w3c.dom.events.Event -import org.w3c.dom.events.EventTarget - -/** https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction */ -public external class IDBTransaction : EventTarget { - public val objectStoreNames: JsArray // Actually a DOMStringList - public val db: IDBDatabase - public val error: JsAny? - public fun objectStore(name: String): IDBObjectStore - public fun abort() - public fun commit() - public var onabort: (Event) -> Unit - public var onerror: (Event) -> Unit - public var oncomplete: (Event) -> Unit +public actual external class IDBTransaction : EventTarget { + public actual val objectStoreNames: JsArray // Actually a DOMStringList + public actual val db: IDBDatabase + public actual val error: JsAny? + public actual fun objectStore(name: String): IDBObjectStore + public actual fun abort() + public actual fun commit() + public actual var onabort: (Event) -> Unit + public actual var onerror: (Event) -> Unit + public actual var oncomplete: (Event) -> Unit } diff --git a/external/src/wasmJsMain/kotlin/IDBTransactionOptions.kt b/external/src/wasmJsMain/kotlin/IDBTransactionOptions.kt index ece80ff..5403a8f 100644 --- a/external/src/wasmJsMain/kotlin/IDBTransactionOptions.kt +++ b/external/src/wasmJsMain/kotlin/IDBTransactionOptions.kt @@ -1,21 +1,11 @@ package com.juul.indexeddb.external -/** https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction/durability */ -public external interface IDBTransactionOptions : JsAny { - public val durability: JsString +public actual external interface IDBTransactionOptions : JsAny { + public actual val durability: String } private fun IDBTransactionOptions(durability: String): IDBTransactionOptions = js("({ durability: durability })") -public fun IDBTransactionOptions(durability: IDBTransactionDurability): IDBTransactionOptions = +public actual fun IDBTransactionOptions(durability: IDBTransactionDurability): IDBTransactionOptions = IDBTransactionOptions(durability.value) - -/** https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction/durability */ -public enum class IDBTransactionDurability( - public val value: String, -) { - Default("default"), - Strict("strict"), - Relaxed("relaxed"), -} diff --git a/external/src/wasmJsMain/kotlin/IDBVersionChangeEvent.kt b/external/src/wasmJsMain/kotlin/IDBVersionChangeEvent.kt index 4b1495a..bfcec7c 100644 --- a/external/src/wasmJsMain/kotlin/IDBVersionChangeEvent.kt +++ b/external/src/wasmJsMain/kotlin/IDBVersionChangeEvent.kt @@ -1,9 +1,6 @@ package com.juul.indexeddb.external -import org.w3c.dom.events.Event - -/** https://developer.mozilla.org/en-US/docs/Web/API/IDBVersionChangeEvent */ -public abstract external class IDBVersionChangeEvent : Event { - public val oldVersion: Int - public val newVersion: Int +public actual abstract external class IDBVersionChangeEvent : Event { + public actual val oldVersion: Int + public actual val newVersion: Int } diff --git a/external/src/wasmJsMain/kotlin/Interop.kt b/external/src/wasmJsMain/kotlin/Interop.kt new file mode 100644 index 0000000..7581f19 --- /dev/null +++ b/external/src/wasmJsMain/kotlin/Interop.kt @@ -0,0 +1,103 @@ +package com.juul.indexeddb.external + +import kotlinx.browser.window +import org.khronos.webgl.Uint8Array +import kotlin.js.get as wasmJsGet +import kotlin.js.set as wasmJsSet +import kotlin.js.toInt as jsToInt +import kotlin.js.toJsBigInt as jsToJsBigInt +import kotlin.js.toJsNumber as jsToJsNumber +import kotlin.js.toJsString as jsToJsString +import kotlin.js.unsafeCast as jsUnsafeCast + +@PublishedApi +internal actual fun JsAny.unsafeCast(): IDBKey = jsUnsafeCast() + +@PublishedApi +internal actual fun Int.unsafeCast(): IDBKey = toJsNumber().jsUnsafeCast() + +@PublishedApi +internal actual fun Double.unsafeCast(): IDBKey = toJsNumber().jsUnsafeCast() + +@PublishedApi +internal actual fun String.unsafeCast(): IDBKey = toJsString().jsUnsafeCast() + +@PublishedApi +internal actual fun Array.unsafeCast(): IDBKey = + JsArray() + .also { ja -> + for (i in indices) { + ja[i] = this[i] + } + }.jsUnsafeCast() + +internal actual typealias JsAny = kotlin.js.JsAny + +public actual external interface UnsafeJsAny : JsAny + +public actual typealias JsArray = kotlin.js.JsArray + +public actual typealias ReadonlyArray = kotlin.js.JsArray + +public actual operator fun JsArray.get(index: Int): T? = this.wasmJsGet(index) + +public actual operator fun JsArray.set(index: Int, value: T) { + this.wasmJsSet(index, value) +} + +@JsName("ArrayBuffer") +public actual external interface ArrayBuffer : JsAny + +@JsName("ArrayBufferView") +public actual external interface ArrayBufferView : JsAny + +public actual typealias JsBoolean = kotlin.js.JsBoolean + +public actual typealias JsNumber = kotlin.js.JsNumber + +public actual fun JsNumber.toInt(): Int = jsToInt() + +public actual fun Int.toJsNumber(): JsNumber = jsToJsNumber() +public actual fun Double.toJsNumber(): JsNumber = jsToJsNumber() + +public actual typealias JsBigInt = kotlin.js.JsBigInt + +public actual fun Long.toJsBigInt(): JsBigInt = jsToJsBigInt() + +public actual typealias JsString = kotlin.js.JsString + +public actual fun String.toJsString(): JsString = jsToJsString() + +public actual typealias JsByteArray = Uint8Array + +public actual fun Array.toJsByteArray(): JsByteArray = Uint8Array( + JsArray().also { ja -> + for (i in indices) { + ja[i] = this[i].toInt().toJsNumber() + } + }, +) + +@JsName("Date") +public actual external class JsDate actual constructor( + value: JsString, +) : JsAny + +@Suppress("WRONG_JS_INTEROP_TYPE") +public actual external val definedExternally: Nothing = kotlin.js.definedExternally + +public actual open external class Event { + public actual open val type: String + public actual open val target: EventTarget? +} + +public actual abstract external class EventTarget : JsAny { + public actual fun addEventListener(type: String, callback: ((Event) -> Unit)?) + public actual fun removeEventListener(type: String, callback: ((Event) -> Unit)?) +} + +public actual typealias Window = org.w3c.dom.Window + +public actual val window: Window = window +public actual val Window.indexedDB: IDBFactory? get() = com.juul.indexeddb.external.indexedDB +public actual val indexedDB: IDBFactory? = js("window.indexedDB") diff --git a/external/src/wasmJsMain/kotlin/Jso.kt b/external/src/wasmJsMain/kotlin/Jso.kt new file mode 100644 index 0000000..3549bbb --- /dev/null +++ b/external/src/wasmJsMain/kotlin/Jso.kt @@ -0,0 +1,7 @@ +package com.juul.indexeddb.external + +// Copied from: +// https://github.com/JetBrains/kotlin-wrappers/blob/91b2c1568ec6f779af5ec10d89b5e2cbdfe785ff/kotlin-extensions/src/main/kotlin/kotlinx/js/jso.kt + +internal fun jso(): T = js("({})") +internal fun jso(block: T.() -> Unit): T = jso().apply(block) diff --git a/external/src/wasmJsMain/kotlin/ReadonlyArray.kt b/external/src/wasmJsMain/kotlin/ReadonlyArray.kt deleted file mode 100644 index 156fcf5..0000000 --- a/external/src/wasmJsMain/kotlin/ReadonlyArray.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.juul.indexeddb.external - -public typealias ReadonlyArray = JsArray diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8c909b1..fe3c8b9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,6 +4,7 @@ kotlin = "2.0.20" [libraries] coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } +coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" } [plugins] dokka = { id = "org.jetbrains.dokka", version = "1.9.20" }