From b0cd7b78126cced4df1c780172eeb92b3037ed8f Mon Sep 17 00:00:00 2001 From: Ilya Gulya Date: Wed, 13 Mar 2024 20:29:40 +0500 Subject: [PATCH 01/13] Support wasmJs target. --- .gitignore | 1 + .../multiplatform/MultiplatformConventions.kt | 18 +- drivers/native-driver/build.gradle | 40 +-- drivers/web-worker-driver/build.gradle | 43 ++-- .../driver/worker/CreateWebWorkerDriver.kt | 5 + .../driver/worker/WebWorkerException.kt | 0 .../driver/worker/api/WorkerAction.kt | 27 ++ .../worker/CreateBadWebWorkerDriver.kt | 5 + .../drivers/worker/WebWorkerDriverTest.kt | 17 +- .../drivers/worker/WebWorkerTransacterTest.kt | 43 ++-- .../driver/worker/CreateWebWorkerDriver.kt | 8 + .../driver/worker/WebWorkerDriver.kt | 20 +- .../driver/worker/api/WorkerAction.kt | 29 +-- .../driver/worker/api/WorkerRequest.kt | 6 + .../worker/CreateBadWebWorkerDriver.kt | 10 + .../driver/worker/CreateWebWorkerDriver.kt | 11 + .../driver/worker/WebWorkerDriver.kt | 240 ++++++++++++++++++ .../driver/worker/api/WorkerAction.kt | 7 + .../driver/worker/api/WorkerRequest.kt | 28 ++ .../driver/worker/api/WorkerResponse.kt | 23 ++ .../driver/worker/api/WorkerResult.kt | 14 + .../sqldelight/driver/worker/util/JsUtils.kt | 33 +++ .../worker/CreateBadWebWorkerDriver.kt | 12 + .../src/wasmJsTest/resources/bad.worker.js | 26 ++ extensions/coroutines-extensions/build.gradle | 19 +- .../cash/sqldelight/coroutines/TestDriver.kt | 6 +- runtime/build.gradle | 17 +- .../sqldelight/internal/CurrentThreadId.kt | 0 .../app/cash/sqldelight/db/Closeable.kt | 0 .../app/cash/sqldelight/db/Closeable.kt | 26 -- sample-web/build.gradle | 45 ++-- sample-web/kotlin-js-store/yarn.lock | 10 +- .../example/sqldelight/hockey/data/Date.kt | 7 - .../com/example/sqldelight/hockey/db/Date.kt | 15 ++ .../sqldelight/hockey/db/DateFormatHelper.kt | 3 + .../example/sqldelight/hockey/db}/DbHelper.kt | 20 +- .../hockey/{data => db}/PlayerVals.kt | 2 +- .../example/sqldelight/hockey/db}/Player.sq | 4 +- .../com/example/sqldelight/hockey/db}/Team.sq | 4 +- .../sqldelight/migrations/1.sqm | 0 .../resources/index.html | 0 .../com/example/sqldelight/hockey/Main.kt | 10 +- .../example/sqldelight/hockey/data/Date.kt | 11 - .../com/example/sqldelight/hockey/db/Date.kt | 9 + .../sqldelight/hockey/db/DateFormatHelper.kt | 6 + .../example/sqldelight/hockey/db/DbHelper.kt | 12 + .../hockey/platform/DateFormatHelper.kt | 6 - .../example/sqldelight/hockey/TestHelper.kt | 2 +- .../com/example/sqldelight/hockey/Main.kt | 73 ++++++ .../com/example/sqldelight/hockey/db/Date.kt | 9 + .../sqldelight/hockey/db/DateFormatHelper.kt | 6 + .../example/sqldelight/hockey/db/DbHelper.kt | 13 + sample/common/common.podspec | 17 +- 53 files changed, 791 insertions(+), 227 deletions(-) create mode 100644 drivers/web-worker-driver/src/jsCommonMain/kotlin/app/cash/sqldelight/driver/worker/CreateWebWorkerDriver.kt rename drivers/web-worker-driver/src/{jsMain => jsCommonMain}/kotlin/app/cash/sqldelight/driver/worker/WebWorkerException.kt (100%) create mode 100644 drivers/web-worker-driver/src/jsCommonMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerAction.kt create mode 100644 drivers/web-worker-driver/src/jsCommonTest/kotlin/app/cash/sqldelight/drivers/worker/CreateBadWebWorkerDriver.kt rename drivers/web-worker-driver/src/{jsTest => jsCommonTest}/kotlin/app/cash/sqldelight/drivers/worker/WebWorkerDriverTest.kt (93%) rename drivers/web-worker-driver/src/{jsTest => jsCommonTest}/kotlin/app/cash/sqldelight/drivers/worker/WebWorkerTransacterTest.kt (76%) create mode 100644 drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/CreateWebWorkerDriver.kt create mode 100644 drivers/web-worker-driver/src/jsTest/kotlin/app/cash/sqldelight/drivers/worker/CreateBadWebWorkerDriver.kt create mode 100644 drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/CreateWebWorkerDriver.kt create mode 100644 drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/WebWorkerDriver.kt create mode 100644 drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerAction.kt create mode 100644 drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerRequest.kt create mode 100644 drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResponse.kt create mode 100644 drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResult.kt create mode 100644 drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/util/JsUtils.kt create mode 100644 drivers/web-worker-driver/src/wasmJsTest/kotlin/app/cash/sqldelight/drivers/worker/CreateBadWebWorkerDriver.kt create mode 100644 drivers/web-worker-driver/src/wasmJsTest/resources/bad.worker.js rename extensions/coroutines-extensions/src/{jsTest => jsCommonTest}/kotlin/app/cash/sqldelight/coroutines/TestDriver.kt (75%) rename runtime/src/{jsMain => jsCommonMain}/kotlin/app/cash/sqldelight/internal/CurrentThreadId.kt (100%) rename runtime/src/{jsMain => jsNativeCommonMain}/kotlin/app/cash/sqldelight/db/Closeable.kt (100%) delete mode 100644 runtime/src/nativeMain/kotlin/app/cash/sqldelight/db/Closeable.kt delete mode 100644 sample-web/src/commonMain/kotlin/com/example/sqldelight/hockey/data/Date.kt create mode 100644 sample-web/src/commonMain/kotlin/com/example/sqldelight/hockey/db/Date.kt create mode 100644 sample-web/src/commonMain/kotlin/com/example/sqldelight/hockey/db/DateFormatHelper.kt rename sample-web/src/{jsMain/kotlin/com/example/sqldelight/hockey/data => commonMain/kotlin/com/example/sqldelight/hockey/db}/DbHelper.kt (85%) rename sample-web/src/commonMain/kotlin/com/example/sqldelight/hockey/{data => db}/PlayerVals.kt (79%) rename sample-web/src/{jsMain/sqldelight/com/example/sqldelight/hockey/data => commonMain/sqldelight/com/example/sqldelight/hockey/db}/Player.sq (89%) rename sample-web/src/{jsMain/sqldelight/com/example/sqldelight/hockey/data => commonMain/sqldelight/com/example/sqldelight/hockey/db}/Team.sq (90%) rename sample-web/src/{jsMain => commonMain}/sqldelight/migrations/1.sqm (100%) rename sample-web/src/{jsMain => jsCommonMain}/resources/index.html (100%) delete mode 100644 sample-web/src/jsMain/kotlin/com/example/sqldelight/hockey/data/Date.kt create mode 100644 sample-web/src/jsMain/kotlin/com/example/sqldelight/hockey/db/Date.kt create mode 100644 sample-web/src/jsMain/kotlin/com/example/sqldelight/hockey/db/DateFormatHelper.kt create mode 100644 sample-web/src/jsMain/kotlin/com/example/sqldelight/hockey/db/DbHelper.kt delete mode 100644 sample-web/src/jsMain/kotlin/com/example/sqldelight/hockey/platform/DateFormatHelper.kt create mode 100644 sample-web/src/wasmJsMain/kotlin/com/example/sqldelight/hockey/Main.kt create mode 100644 sample-web/src/wasmJsMain/kotlin/com/example/sqldelight/hockey/db/Date.kt create mode 100644 sample-web/src/wasmJsMain/kotlin/com/example/sqldelight/hockey/db/DateFormatHelper.kt create mode 100644 sample-web/src/wasmJsMain/kotlin/com/example/sqldelight/hockey/db/DbHelper.kt diff --git a/.gitignore b/.gitignore index ea5759aa3d9..63c583ecb20 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,5 @@ docs/contributing.md # Jenv local setting .java-version + .intellijPlatform diff --git a/buildLogic/multiplatform-convention/src/main/kotlin/app/cash/sqldelight/multiplatform/MultiplatformConventions.kt b/buildLogic/multiplatform-convention/src/main/kotlin/app/cash/sqldelight/multiplatform/MultiplatformConventions.kt index 45e2fdd6635..6346a57648c 100644 --- a/buildLogic/multiplatform-convention/src/main/kotlin/app/cash/sqldelight/multiplatform/MultiplatformConventions.kt +++ b/buildLogic/multiplatform-convention/src/main/kotlin/app/cash/sqldelight/multiplatform/MultiplatformConventions.kt @@ -2,9 +2,12 @@ package app.cash.sqldelight.multiplatform import org.gradle.api.Plugin import org.gradle.api.Project +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.dsl.JsModuleKind import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.dsl.kotlinExtension +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl +import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsTargetDsl import org.jetbrains.kotlin.konan.target.HostManager class MultiplatformConventions : Plugin { @@ -14,7 +17,7 @@ class MultiplatformConventions : Plugin { (project.kotlinExtension as KotlinMultiplatformExtension).apply { jvm() - js { + val jsConfigure: KotlinJsTargetDsl.() -> Unit = { browser { testTask { it.useKarma { @@ -26,6 +29,19 @@ class MultiplatformConventions : Plugin { moduleKind.set(JsModuleKind.MODULE_UMD) } } + js(jsConfigure) + @OptIn(ExperimentalWasmDsl::class) + wasmJs(jsConfigure) + + @OptIn(ExperimentalKotlinGradlePluginApi::class) + applyDefaultHierarchyTemplate { + common { + group("jsCommon") { + withJs() + withWasm() + } + } + } // tier 1 linuxX64() diff --git a/drivers/native-driver/build.gradle b/drivers/native-driver/build.gradle index 1581b698725..5fae624b3e2 100644 --- a/drivers/native-driver/build.gradle +++ b/drivers/native-driver/build.gradle @@ -1,5 +1,6 @@ -import org.jetbrains.kotlin.konan.target.HostManager +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget import org.jetbrains.kotlin.gradle.plugin.mpp.NativeOutputKind +import org.jetbrains.kotlin.konan.target.HostManager plugins { alias(libs.plugins.kotlin.multiplatform) @@ -49,32 +50,31 @@ kotlin { } sourceSets { - commonMain { - dependencies { - api projects.runtime - api libs.stately.concurrency - api libs.sqliter - } + commonMain.dependencies { + api projects.runtime + api libs.stately.concurrency + api libs.sqliter } - commonTest { - dependencies { - implementation libs.kotlin.test - implementation libs.testhelp - implementation projects.drivers.driverTest - } + + commonTest.dependencies { + implementation libs.kotlin.test + implementation libs.testhelp + implementation projects.drivers.driverTest } } - configure([targets.iosX64, targets.iosArm64, targets.tvosX64, targets.tvosArm64, targets.watchosX64, targets.watchosArm32, targets.watchosArm64, targets.macosX64, targets.macosArm64, targets.iosSimulatorArm64, targets.watchosSimulatorArm64, targets.tvosSimulatorArm64]) { - binaries.configureEach { - // we only need to link sqlite for the test binaries - if (outputKind == NativeOutputKind.TEST) { - linkerOpts += ["-lsqlite3"] + targets.configureEach { + if (it instanceof KotlinNativeTarget && it.konanTarget.family.appleFamily) { + it.binaries.configureEach { + // we only need to link sqlite for the test binaries + if (outputKind == NativeOutputKind.TEST) { + linkerOpts += ["-lsqlite3"] + } } } } - configure([targets.linuxX64]) { + linuxX64 { compilations.configureEach { if (name == "test") { cinterops { @@ -88,7 +88,7 @@ kotlin { } } - configure([targets.mingwX64]) { + mingwX64 { binaries.configureEach { // we only need to link sqlite for the test binaries if (outputKind == NativeOutputKind.TEST) { diff --git a/drivers/web-worker-driver/build.gradle b/drivers/web-worker-driver/build.gradle index 2f546494611..c313bbb8dcc 100644 --- a/drivers/web-worker-driver/build.gradle +++ b/drivers/web-worker-driver/build.gradle @@ -14,24 +14,37 @@ kotlin { } } } + wasmJs { + browser { + testTask { + useKarma { + useChromeHeadless() + } + } + } + } + applyDefaultHierarchyTemplate { + it.common { + it.group("jsCommon") { + it.withJs() + it.withWasm() + } + } + } sourceSets { - jsMain { - dependencies { - api projects.runtime - implementation libs.kotlin.coroutines.core - } + jsCommonMain.dependencies { + api projects.runtime + implementation libs.kotlin.coroutines.core } - jsTest { - dependencies { - implementation libs.kotlin.test.js - implementation npm("sql.js", libs.versions.sqljs.get()) - implementation npm("@cashapp/sqldelight-sqljs-worker", project(":drivers:web-worker-driver:sqljs").projectDir) - implementation devNpm("copy-webpack-plugin", "9.1.0") - implementation libs.kotlin.coroutines.test - implementation project(":extensions:async-extensions") - } + jsCommonTest.dependencies { + implementation libs.kotlin.test + implementation npm("sql.js", libs.versions.sqljs.get()) + implementation npm("@cashapp/sqldelight-sqljs-worker", project(":drivers:web-worker-driver:sqljs").projectDir) + implementation devNpm("copy-webpack-plugin", "9.1.0") + implementation libs.kotlin.coroutines.test + implementation project(":extensions:async-extensions") } } } @@ -39,5 +52,5 @@ kotlin { apply from: "$rootDir/gradle/gradle-mvn-push.gradle" tasks.named("dokkaHtmlMultiModule") { - dependsOn(rootProject.tasks.named("dokkaHtmlMultiModule")) + dependsOn(rootProject.tasks.named("dokkaHtmlMultiModule")) } diff --git a/drivers/web-worker-driver/src/jsCommonMain/kotlin/app/cash/sqldelight/driver/worker/CreateWebWorkerDriver.kt b/drivers/web-worker-driver/src/jsCommonMain/kotlin/app/cash/sqldelight/driver/worker/CreateWebWorkerDriver.kt new file mode 100644 index 00000000000..32cdf3f6a1e --- /dev/null +++ b/drivers/web-worker-driver/src/jsCommonMain/kotlin/app/cash/sqldelight/driver/worker/CreateWebWorkerDriver.kt @@ -0,0 +1,5 @@ +package app.cash.sqldelight.driver.worker + +import app.cash.sqldelight.db.SqlDriver + +expect fun createWebWorkerDriver(): SqlDriver diff --git a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/WebWorkerException.kt b/drivers/web-worker-driver/src/jsCommonMain/kotlin/app/cash/sqldelight/driver/worker/WebWorkerException.kt similarity index 100% rename from drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/WebWorkerException.kt rename to drivers/web-worker-driver/src/jsCommonMain/kotlin/app/cash/sqldelight/driver/worker/WebWorkerException.kt diff --git a/drivers/web-worker-driver/src/jsCommonMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerAction.kt b/drivers/web-worker-driver/src/jsCommonMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerAction.kt new file mode 100644 index 00000000000..7ee2258796c --- /dev/null +++ b/drivers/web-worker-driver/src/jsCommonMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerAction.kt @@ -0,0 +1,27 @@ +package app.cash.sqldelight.driver.worker.api + +internal expect sealed interface WorkerAction + +internal expect inline fun WorkerAction(value: String): WorkerAction + +internal object WorkerActions { + /** + * Execute a SQL statement. + */ + inline val exec: WorkerAction get() = WorkerAction("exec") + + /** + * Begin a transaction in the underlying SQL implementation. + */ + inline val beginTransaction: WorkerAction get() = WorkerAction("begin_transaction") + + /** + * End or commit a transaction in the underlying SQL implementation. + */ + inline val endTransaction: WorkerAction get() = WorkerAction("end_transaction") + + /** + * Roll back a transaction in the underlying SQL implementation. + */ + inline val rollbackTransaction: WorkerAction get() = WorkerAction("rollback_transaction") +} diff --git a/drivers/web-worker-driver/src/jsCommonTest/kotlin/app/cash/sqldelight/drivers/worker/CreateBadWebWorkerDriver.kt b/drivers/web-worker-driver/src/jsCommonTest/kotlin/app/cash/sqldelight/drivers/worker/CreateBadWebWorkerDriver.kt new file mode 100644 index 00000000000..ad0babc89b4 --- /dev/null +++ b/drivers/web-worker-driver/src/jsCommonTest/kotlin/app/cash/sqldelight/drivers/worker/CreateBadWebWorkerDriver.kt @@ -0,0 +1,5 @@ +package app.cash.sqldelight.drivers.worker + +import app.cash.sqldelight.db.SqlDriver + +expect fun createBadWebWorkerDriver(): SqlDriver diff --git a/drivers/web-worker-driver/src/jsTest/kotlin/app/cash/sqldelight/drivers/worker/WebWorkerDriverTest.kt b/drivers/web-worker-driver/src/jsCommonTest/kotlin/app/cash/sqldelight/drivers/worker/WebWorkerDriverTest.kt similarity index 93% rename from drivers/web-worker-driver/src/jsTest/kotlin/app/cash/sqldelight/drivers/worker/WebWorkerDriverTest.kt rename to drivers/web-worker-driver/src/jsCommonTest/kotlin/app/cash/sqldelight/drivers/worker/WebWorkerDriverTest.kt index 227f81924df..47b225486f2 100644 --- a/drivers/web-worker-driver/src/jsTest/kotlin/app/cash/sqldelight/drivers/worker/WebWorkerDriverTest.kt +++ b/drivers/web-worker-driver/src/jsCommonTest/kotlin/app/cash/sqldelight/drivers/worker/WebWorkerDriverTest.kt @@ -10,8 +10,8 @@ import app.cash.sqldelight.db.SqlCursor import app.cash.sqldelight.db.SqlDriver import app.cash.sqldelight.db.SqlPreparedStatement import app.cash.sqldelight.db.SqlSchema -import app.cash.sqldelight.driver.worker.WebWorkerDriver import app.cash.sqldelight.driver.worker.WebWorkerException +import app.cash.sqldelight.driver.worker.createWebWorkerDriver import kotlin.test.Test import kotlin.test.assertContains import kotlin.test.assertEquals @@ -19,12 +19,9 @@ import kotlin.test.assertFailsWith import kotlin.test.assertFalse import kotlin.test.assertNull import kotlin.test.assertTrue -import kotlinx.coroutines.ExperimentalCoroutinesApi -import org.w3c.dom.Worker typealias InsertFunction = suspend (SqlPreparedStatement.() -> Unit) -> Unit -@OptIn(ExperimentalCoroutinesApi::class) class WebWorkerDriverTest { private val schema = object : SqlSchema> { override val version: Long = 1 @@ -64,9 +61,9 @@ class WebWorkerDriverTest { } private fun runTest(block: suspend (SqlDriver) -> Unit) = kotlinx.coroutines.test.runTest { - @Suppress("UnsafeCastFromDynamic") - val driver = WebWorkerDriver(Worker(js("""new URL("@cashapp/sqldelight-sqljs-worker/sqljs.worker.js", import.meta.url)"""))) - .also { schema.awaitCreate(it) } + val driver = + createWebWorkerDriver() + .also { schema.awaitCreate(it) } block(driver) driver.close() } @@ -279,9 +276,9 @@ class WebWorkerDriverTest { @Test fun bad_worker_results_values_throws_error() = kotlinx.coroutines.test.runTest { val exception = assertFailsWith { - @Suppress("UnsafeCastFromDynamic") - val driver = WebWorkerDriver(Worker(js("""new URL("./bad.worker.js", import.meta.url)"""))) - .also { schema.awaitCreate(it) } + val driver = + createBadWebWorkerDriver() + .also { schema.awaitCreate(it) } driver.close() } diff --git a/drivers/web-worker-driver/src/jsTest/kotlin/app/cash/sqldelight/drivers/worker/WebWorkerTransacterTest.kt b/drivers/web-worker-driver/src/jsCommonTest/kotlin/app/cash/sqldelight/drivers/worker/WebWorkerTransacterTest.kt similarity index 76% rename from drivers/web-worker-driver/src/jsTest/kotlin/app/cash/sqldelight/drivers/worker/WebWorkerTransacterTest.kt rename to drivers/web-worker-driver/src/jsCommonTest/kotlin/app/cash/sqldelight/drivers/worker/WebWorkerTransacterTest.kt index fe31438a9a5..e744668c939 100644 --- a/drivers/web-worker-driver/src/jsTest/kotlin/app/cash/sqldelight/drivers/worker/WebWorkerTransacterTest.kt +++ b/drivers/web-worker-driver/src/jsCommonTest/kotlin/app/cash/sqldelight/drivers/worker/WebWorkerTransacterTest.kt @@ -7,13 +7,12 @@ import app.cash.sqldelight.db.AfterVersion import app.cash.sqldelight.db.QueryResult import app.cash.sqldelight.db.SqlDriver import app.cash.sqldelight.db.SqlSchema -import app.cash.sqldelight.driver.worker.WebWorkerDriver +import app.cash.sqldelight.driver.worker.createWebWorkerDriver import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertTrue import kotlin.test.fail -import org.w3c.dom.Worker class WebWorkerTransacterTest { private val schema = object : SqlSchema> { @@ -29,16 +28,18 @@ class WebWorkerTransacterTest { private fun runTest(block: suspend (SqlDriver, SuspendingTransacter) -> Unit) = kotlinx.coroutines.test.runTest { - @Suppress("UnsafeCastFromDynamic") - val driver = WebWorkerDriver(Worker(js("""new URL("@cashapp/sqldelight-sqljs-worker/sqljs.worker.js", import.meta.url)"""))) - .also { schema.awaitCreate(it) } + val driver = + createWebWorkerDriver() + .also { schema.awaitCreate(it) } + val transacter = object : SuspendingTransacterImpl(driver) {} block(driver, transacter) driver.close() } - @Test fun afterCommit_runs_after_transaction_commits() = runTest { _, transacter -> + @Test + fun afterCommit_runs_after_transaction_commits() = runTest { _, transacter -> var counter = 0 transacter.transaction { afterCommit { counter++ } @@ -48,7 +49,8 @@ class WebWorkerTransacterTest { assertEquals(1, counter) } - @Test fun afterCommit_does_not_run_after_transaction_rollbacks() = runTest { _, transacter -> + @Test + fun afterCommit_does_not_run_after_transaction_rollbacks() = runTest { _, transacter -> var counter = 0 transacter.transaction { afterCommit { counter++ } @@ -59,7 +61,8 @@ class WebWorkerTransacterTest { assertEquals(0, counter) } - @Test fun afterCommit_runs_after_enclosing_transaction_commits() = runTest { _, transacter -> + @Test + fun afterCommit_runs_after_enclosing_transaction_commits() = runTest { _, transacter -> var counter = 0 transacter.transaction { afterCommit { counter++ } @@ -76,7 +79,8 @@ class WebWorkerTransacterTest { assertEquals(2, counter) } - @Test fun afterCommit_does_not_run_in_nested_transaction_when_enclosing_rolls_back() = + @Test + fun afterCommit_does_not_run_in_nested_transaction_when_enclosing_rolls_back() = runTest { _, transacter -> var counter = 0 transacter.transaction { @@ -93,7 +97,8 @@ class WebWorkerTransacterTest { assertEquals(0, counter) } - @Test fun afterCommit_does_not_run_in_nested_transaction_when_nested_rolls_back() = + @Test + fun afterCommit_does_not_run_in_nested_transaction_when_nested_rolls_back() = runTest { _, transacter -> var counter = 0 transacter.transaction { @@ -111,7 +116,8 @@ class WebWorkerTransacterTest { assertEquals(0, counter) } - @Test fun afterRollback_no_ops_if_the_transaction_never_rolls_back() = runTest { _, transacter -> + @Test + fun afterRollback_no_ops_if_the_transaction_never_rolls_back() = runTest { _, transacter -> var counter = 0 transacter.transaction { afterRollback { counter++ } @@ -120,7 +126,8 @@ class WebWorkerTransacterTest { assertEquals(0, counter) } - @Test fun afterRollback_runs_after_a_rollback_occurs() = runTest { _, transacter -> + @Test + fun afterRollback_runs_after_a_rollback_occurs() = runTest { _, transacter -> var counter = 0 transacter.transaction { afterRollback { counter++ } @@ -130,7 +137,8 @@ class WebWorkerTransacterTest { assertEquals(1, counter) } - @Test fun afterRollback_runs_after_an_inner_transaction_rolls_back() = runTest { _, transacter -> + @Test + fun afterRollback_runs_after_an_inner_transaction_rolls_back() = runTest { _, transacter -> var counter = 0 transacter.transaction { afterRollback { counter++ } @@ -143,7 +151,8 @@ class WebWorkerTransacterTest { assertEquals(1, counter) } - @Test fun afterRollback_runs_in_an_inner_transaction_when_the_outer_transaction_rolls_back() = + @Test + fun afterRollback_runs_in_an_inner_transaction_when_the_outer_transaction_rolls_back() = runTest { _, transacter -> var counter = 0 transacter.transaction { @@ -156,7 +165,8 @@ class WebWorkerTransacterTest { assertEquals(1, counter) } - @Test fun transactions_close_themselves_out_properly() = runTest { _, transacter -> + @Test + fun transactions_close_themselves_out_properly() = runTest { _, transacter -> var counter = 0 transacter.transaction { afterCommit { counter++ } @@ -169,7 +179,8 @@ class WebWorkerTransacterTest { assertEquals(2, counter) } - @Test fun setting_no_enclosing_fails_if_there_is_a_currently_running_transaction() = + @Test + fun setting_no_enclosing_fails_if_there_is_a_currently_running_transaction() = runTest { _, transacter -> transacter.transaction(noEnclosing = true) { assertFailsWith { diff --git a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/CreateWebWorkerDriver.kt b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/CreateWebWorkerDriver.kt new file mode 100644 index 00000000000..0b3e71f4bb5 --- /dev/null +++ b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/CreateWebWorkerDriver.kt @@ -0,0 +1,8 @@ +package app.cash.sqldelight.driver.worker + +import app.cash.sqldelight.db.SqlDriver +import org.w3c.dom.Worker + +actual fun createWebWorkerDriver(): SqlDriver { + return WebWorkerDriver(Worker(js("""new URL("@cashapp/sqldelight-sqljs-worker/sqljs.worker.js", import.meta.url)"""))) +} diff --git a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/WebWorkerDriver.kt b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/WebWorkerDriver.kt index 23e8701ca76..0a20d570f69 100644 --- a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/WebWorkerDriver.kt +++ b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/WebWorkerDriver.kt @@ -7,9 +7,10 @@ import app.cash.sqldelight.db.SqlCursor import app.cash.sqldelight.db.SqlDriver import app.cash.sqldelight.db.SqlPreparedStatement import app.cash.sqldelight.driver.worker.api.WorkerAction -import app.cash.sqldelight.driver.worker.api.WorkerRequest +import app.cash.sqldelight.driver.worker.api.WorkerActions import app.cash.sqldelight.driver.worker.api.WorkerResponse import app.cash.sqldelight.driver.worker.api.WorkerResult +import app.cash.sqldelight.driver.worker.api.buildRequest import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import kotlinx.coroutines.suspendCancellableCoroutine @@ -39,7 +40,7 @@ class WebWorkerDriver(private val worker: Worker) : SqlDriver { binders?.invoke(bound) return QueryResult.AsyncValue { - val response = worker.sendMessage(WorkerAction.exec) { + val response = worker.sendMessage(WorkerActions.exec) { this.sql = sql this.params = bound.parameters.toTypedArray() } @@ -53,7 +54,7 @@ class WebWorkerDriver(private val worker: Worker) : SqlDriver { binders?.invoke(bound) return QueryResult.AsyncValue { - val response = worker.sendMessage(WorkerAction.exec) { + val response = worker.sendMessage(WorkerActions.exec) { this.sql = sql this.params = bound.parameters.toTypedArray() } @@ -90,7 +91,7 @@ class WebWorkerDriver(private val worker: Worker) : SqlDriver { val transaction = Transaction(enclosing) this.transaction = transaction if (enclosing == null) { - worker.sendMessage(WorkerAction.beginTransaction) + worker.sendMessage(WorkerActions.beginTransaction) } return@AsyncValue transaction @@ -104,16 +105,16 @@ class WebWorkerDriver(private val worker: Worker) : SqlDriver { override fun endTransaction(successful: Boolean): QueryResult = QueryResult.AsyncValue { if (enclosingTransaction == null) { if (successful) { - worker.sendMessage(WorkerAction.endTransaction) + worker.sendMessage(WorkerActions.endTransaction) } else { - worker.sendMessage(WorkerAction.rollbackTransaction) + worker.sendMessage(WorkerActions.rollbackTransaction) } } transaction = enclosingTransaction } } - private suspend fun Worker.sendMessage(action: WorkerAction, message: RequestBuilder.() -> Unit = {}): WorkerResponse = suspendCancellableCoroutine { continuation -> + private suspend fun Worker.sendMessage(action: WorkerAction, builder: RequestBuilder.() -> Unit = {}): WorkerResponse = suspendCancellableCoroutine { continuation -> val id = messageCounter++ val messageListener = object : EventListener { override fun handleEvent(event: Event) { @@ -139,12 +140,11 @@ class WebWorkerDriver(private val worker: Worker) : SqlDriver { addEventListener("message", messageListener) addEventListener("error", errorListener) - val messageObject = js("{}").unsafeCast().apply { - this.unsafeCast().message() + val messageObject = buildRequest { + this.unsafeCast().builder() this.id = id this.action = action } - postMessage(messageObject) continuation.invokeOnCancellation { diff --git a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerAction.kt b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerAction.kt index bd0d3d64be2..46cfc8daa43 100644 --- a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerAction.kt +++ b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerAction.kt @@ -1,31 +1,6 @@ package app.cash.sqldelight.driver.worker.api -internal sealed interface WorkerAction { - companion object { - /** - * Execute a SQL statement. - */ - inline val exec: WorkerAction get() = WorkerAction("exec") - - /** - * Begin a transaction in the underlying SQL implementation. - */ - inline val beginTransaction: WorkerAction get() = WorkerAction("begin_transaction") - - /** - * End or commit a transaction in the underlying SQL implementation. - */ - inline val endTransaction: WorkerAction get() = WorkerAction("end_transaction") - - /** - * Roll back a transaction in the underlying SQL implementation. - */ - inline val rollbackTransaction: WorkerAction get() = WorkerAction("rollback_transaction") - } -} +internal actual sealed interface WorkerAction @Suppress("NOTHING_TO_INLINE", "FunctionName") -/** - * @suppress - */ -internal inline fun WorkerAction(value: String) = value.unsafeCast() +internal actual inline fun WorkerAction(value: String) = value.unsafeCast() diff --git a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerRequest.kt b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerRequest.kt index 5cf94006f61..88bccb8dab3 100644 --- a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerRequest.kt +++ b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerRequest.kt @@ -26,3 +26,9 @@ internal external interface WorkerRequest { */ var params: Array? } + +internal fun buildRequest(builder: WorkerRequest.() -> Unit): WorkerRequest { + val request = js("{}").unsafeCast() + builder(request) + return request +} diff --git a/drivers/web-worker-driver/src/jsTest/kotlin/app/cash/sqldelight/drivers/worker/CreateBadWebWorkerDriver.kt b/drivers/web-worker-driver/src/jsTest/kotlin/app/cash/sqldelight/drivers/worker/CreateBadWebWorkerDriver.kt new file mode 100644 index 00000000000..9c03ae20bd6 --- /dev/null +++ b/drivers/web-worker-driver/src/jsTest/kotlin/app/cash/sqldelight/drivers/worker/CreateBadWebWorkerDriver.kt @@ -0,0 +1,10 @@ +package app.cash.sqldelight.drivers.worker + +import app.cash.sqldelight.db.SqlDriver +import app.cash.sqldelight.driver.worker.WebWorkerDriver +import org.w3c.dom.Worker + +@Suppress("UnsafeCastFromDynamic") +actual fun createBadWebWorkerDriver(): SqlDriver { + return WebWorkerDriver(Worker(js("""new URL("./bad.worker.js", import.meta.url)"""))) +} diff --git a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/CreateWebWorkerDriver.kt b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/CreateWebWorkerDriver.kt new file mode 100644 index 00000000000..39861f35d6d --- /dev/null +++ b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/CreateWebWorkerDriver.kt @@ -0,0 +1,11 @@ +package app.cash.sqldelight.driver.worker + +import app.cash.sqldelight.db.SqlDriver +import org.w3c.dom.Worker + +actual fun createWebWorkerDriver(): SqlDriver { + return WebWorkerDriver(jsWorker()) +} + +internal fun jsWorker(): Worker = + js("""new Worker(new URL("@cashapp/sqldelight-sqljs-worker/sqljs.worker.js", import.meta.url))""") diff --git a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/WebWorkerDriver.kt b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/WebWorkerDriver.kt new file mode 100644 index 00000000000..5f33c878ef5 --- /dev/null +++ b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/WebWorkerDriver.kt @@ -0,0 +1,240 @@ +package app.cash.sqldelight.driver.worker + +import app.cash.sqldelight.Query +import app.cash.sqldelight.Transacter +import app.cash.sqldelight.db.QueryResult +import app.cash.sqldelight.db.SqlCursor +import app.cash.sqldelight.db.SqlDriver +import app.cash.sqldelight.db.SqlPreparedStatement +import app.cash.sqldelight.driver.worker.api.WorkerAction +import app.cash.sqldelight.driver.worker.api.WorkerActions +import app.cash.sqldelight.driver.worker.api.WorkerRequest +import app.cash.sqldelight.driver.worker.api.WorkerResponse +import app.cash.sqldelight.driver.worker.api.WorkerResult +import app.cash.sqldelight.driver.worker.util.add +import app.cash.sqldelight.driver.worker.util.instantiateObject +import app.cash.sqldelight.driver.worker.util.isArray +import app.cash.sqldelight.driver.worker.util.jsonStringify +import app.cash.sqldelight.driver.worker.util.objectEntries +import app.cash.sqldelight.driver.worker.util.toJsArray +import app.cash.sqldelight.driver.worker.util.toUint8Array +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlinx.coroutines.suspendCancellableCoroutine +import org.khronos.webgl.Uint8Array +import org.khronos.webgl.get +import org.w3c.dom.MessageEvent +import org.w3c.dom.Worker +import org.w3c.dom.events.Event + +/** + * A [SqlDriver] implementation for interacting with SQL databases running in a Web Worker. + * + * This driver is dialect-agnostic and is instead dependent on the Worker script's implementation + * to handle queries and send results back from the Worker. + * + * @property worker The Worker running a SQL implementation that this driver communicates with. + * @see [WebWorkerDriver.fromScriptUrl] + */ +class WebWorkerDriver(private val worker: Worker) : SqlDriver { + private val listeners = mutableMapOf>() + private var messageCounter = 0 + private var transaction: Transacter.Transaction? = null + + override fun executeQuery(identifier: Int?, sql: String, mapper: (SqlCursor) -> QueryResult, parameters: Int, binders: (SqlPreparedStatement.() -> Unit)?): QueryResult { + val bound = JsWorkerSqlPreparedStatement() + binders?.invoke(bound) + + return QueryResult.AsyncValue { + val response = worker.sendMessage(WorkerActions.exec) { + this.sql = sql + this.params = bound.parameters + } + + return@AsyncValue mapper(JsWorkerSqlCursor(checkWorkerResults(response.results))).await() + } + } + + override fun execute(identifier: Int?, sql: String, parameters: Int, binders: (SqlPreparedStatement.() -> Unit)?): QueryResult { + val bound = JsWorkerSqlPreparedStatement() + binders?.invoke(bound) + + return QueryResult.AsyncValue { + val response = worker.sendMessage(WorkerActions.exec) { + this.sql = sql + this.params = bound.parameters + } + val values = checkWorkerResults(response.results) + return@AsyncValue when { + values.length == 0 -> 0L + else -> values[0]?.get(0)?.unsafeCast()?.toDouble()?.toLong() ?: 0L + } + } + } + + override fun addListener(vararg queryKeys: String, listener: Query.Listener) { + queryKeys.forEach { + listeners.getOrPut(it) { mutableSetOf() }.add(listener) + } + } + + override fun removeListener(vararg queryKeys: String, listener: Query.Listener) { + queryKeys.forEach { + listeners[it]?.remove(listener) + } + } + + override fun notifyListeners(vararg queryKeys: String) { + queryKeys.flatMap { listeners[it].orEmpty() } + .distinct() + .forEach(Query.Listener::queryResultsChanged) + } + + override fun close() = worker.terminate() + + override fun newTransaction(): QueryResult = QueryResult.AsyncValue { + val enclosing = transaction + val transaction = Transaction(enclosing) + this.transaction = transaction + if (enclosing == null) { + worker.sendMessage(WorkerActions.beginTransaction) + } + + return@AsyncValue transaction + } + + override fun currentTransaction(): Transacter.Transaction? = transaction + + private inner class Transaction( + override val enclosingTransaction: Transacter.Transaction?, + ) : Transacter.Transaction() { + override fun endTransaction(successful: Boolean): QueryResult = QueryResult.AsyncValue { + if (enclosingTransaction == null) { + if (successful) { + worker.sendMessage(WorkerActions.endTransaction) + } else { + worker.sendMessage(WorkerActions.rollbackTransaction) + } + } + transaction = enclosingTransaction + } + } + + private suspend fun Worker.sendMessage(action: WorkerAction, message: RequestBuilder.() -> Unit = {}): WorkerResponse = suspendCancellableCoroutine { continuation -> + val id = messageCounter++ + var messageListener: ((Event) -> Unit)? = null + messageListener = { event: Event -> + val message = event.unsafeCast() + val data = message.data?.unsafeCast() + if (data == null) { + continuation.resumeWithException(WebWorkerException("Message ${message.type} data was null or not a WorkerResponse")) + } else { + if (data.id == id) { + removeEventListener("message", messageListener) + if (data.error != null) { + continuation.resumeWithException(WebWorkerException(jsonStringify(value = data.error, replacer = listOf("message", "arguments", "type", "name").toJsArray { it.toJsString() }))) + } else { + continuation.resume(data) + } + } + } + } + var errorListener: ((Event) -> Unit)? = null + errorListener = { event -> + removeEventListener("error", errorListener) + continuation.resumeWithException(WebWorkerException(jsonStringify(event, listOf("message", "arguments", "type", "name").toJsArray { it.toJsString() }) + objectEntries(event))) + } + addEventListener("message", messageListener) + addEventListener("error", errorListener) + + val messageObject = instantiateObject().apply { + this.unsafeCast().message() + this.id = id + this.action = action + } + + postMessage(messageObject) + + continuation.invokeOnCancellation { + removeEventListener("message", messageListener) + removeEventListener("error", errorListener) + } + } + + private fun checkWorkerResults(results: WorkerResult?): JsArray> { + checkNotNull(results) { "The worker result was null " } + val values = results.values + check(values != null && isArray(values)) { "The worker result values were not an array" } + return values + } +} + +private external interface RequestBuilder : JsAny { + var sql: String? + var params: JsArray? +} + +internal class JsWorkerSqlCursor(private val values: JsArray>) : SqlCursor { + private var currentRow = -1 + + override fun next(): QueryResult.Value = QueryResult.Value(++currentRow < values.length) + + override fun getString(index: Int): String? { + val currentRow = values[currentRow] ?: return null + return currentRow[index]?.unsafeCast()?.toString() + } + + override fun getLong(index: Int): Long? { + return getColumn(index) { + it.unsafeCast().toDouble().toLong() + } + } + + override fun getBytes(index: Int): ByteArray? { + return getColumn(index) { + val array = it.unsafeCast() + // TODO: avoid copying somehow? + ByteArray(array.length) { array[it] } + } + } + + override fun getDouble(index: Int): Double? { + return getColumn(index) { it.unsafeCast().toDouble() } + } + + override fun getBoolean(index: Int): Boolean? { + return getColumn(index) { it.unsafeCast().toBoolean() } + } + + private inline fun getColumn(index: Int, transformer: (JsAny) -> T): T? { + val column = values[currentRow]?.get(index) ?: return null + return transformer(column) + } +} + +internal class JsWorkerSqlPreparedStatement : SqlPreparedStatement { + + val parameters = JsArray() + + override fun bindBytes(index: Int, bytes: ByteArray?) { + parameters.add(bytes?.toUint8Array()) + } + + override fun bindLong(index: Int, long: Long?) { + // We convert Long to Double because Kotlin's Double is mapped to JS number + // whereas Kotlin's Long is implemented as a JS object + parameters.add(long?.toDouble()?.toJsNumber()) + } + + override fun bindDouble(index: Int, double: Double?) { + parameters.add(double?.toJsNumber()) + } + + override fun bindString(index: Int, string: String?) { + parameters.add(string?.toJsString()) + } + + override fun bindBoolean(index: Int, boolean: Boolean?) { + parameters.add(boolean?.toJsBoolean()) + } +} diff --git a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerAction.kt b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerAction.kt new file mode 100644 index 00000000000..78e934c2e9d --- /dev/null +++ b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerAction.kt @@ -0,0 +1,7 @@ +package app.cash.sqldelight.driver.worker.api + +@Suppress("ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_SUPERTYPES_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING") +internal actual sealed external interface WorkerAction : JsAny + +@Suppress("NOTHING_TO_INLINE", "FunctionName") +internal actual inline fun WorkerAction(value: String) = value.toJsString().unsafeCast() diff --git a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerRequest.kt b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerRequest.kt new file mode 100644 index 00000000000..f84d0c25316 --- /dev/null +++ b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerRequest.kt @@ -0,0 +1,28 @@ +package app.cash.sqldelight.driver.worker.api + +/** + * Messages sent by the SQLDelight driver to the worker. + */ +internal external interface WorkerRequest : JsAny { + /** + * A unique identifier used to identify responses to this message + * @see WorkerResponse.id + */ + var id: Int + + /** + * The action that the worker should run. + * @see WorkerAction + */ + var action: WorkerAction + + /** + * The SQL to execute + */ + var sql: String? + + /** + * SQL parameters to bind to the given [sql] + */ + var params: JsArray? +} diff --git a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResponse.kt b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResponse.kt new file mode 100644 index 00000000000..1af149d3d89 --- /dev/null +++ b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResponse.kt @@ -0,0 +1,23 @@ +package app.cash.sqldelight.driver.worker.api + +/** + * Data returned by the worker after posting a message. + */ +internal external interface WorkerResponse : JsAny { + /** + * An error returned by the worker, could be undefined. + */ + var error: JsString? + + /** + * The id of the message that this data is in response to. Matches the value that was posted in [WorkerRequest.id]. + * @see WorkerRequest.id + */ + var id: Int + + /** + * A [WorkerResult] containing any values that were returned by the worker. + * @see WorkerResult + */ + var results: WorkerResult +} diff --git a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResult.kt b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResult.kt new file mode 100644 index 00000000000..9de6c945cbc --- /dev/null +++ b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResult.kt @@ -0,0 +1,14 @@ +package app.cash.sqldelight.driver.worker.api + +/** + * The results of a SQL operation in the worker. + */ +internal external interface WorkerResult : JsAny { + /** + * The "table" of values in the result, as rows of columns. + * i.e. `values[row][col]` + * + * If the query returns no rows, then this should be an empty array. + */ + var values: JsArray>? +} diff --git a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/util/JsUtils.kt b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/util/JsUtils.kt new file mode 100644 index 00000000000..6cce47df9a5 --- /dev/null +++ b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/util/JsUtils.kt @@ -0,0 +1,33 @@ +package app.cash.sqldelight.driver.worker.util + +import org.khronos.webgl.Uint8Array +import org.khronos.webgl.set + +internal fun jsonStringify(value: JsAny?, replacer: JsArray? = null, space: String? = null): String = + js("JSON.stringify(value, replacer, space)") + +internal fun objectEntries(value: JsAny?): JsArray> = js("Object.entries(value)") + +internal fun isArray(value: JsAny?): Boolean = + js("Array.isArray(value)") + +internal fun JsArray.add(value: T) { + jsArrayPush(this, value) +} + +@Suppress("UNUSED_PARAMETER") +private fun jsArrayPush(array: JsArray, value: T) { + js("array.push(value)") +} + +internal fun ByteArray.toUint8Array(): Uint8Array = Uint8Array(size).apply { + forEachIndexed { index, byte -> this[index] = byte } +} + +internal fun Iterable.toJsArray(mapper: (T) -> R): JsArray = + JsArray().apply { + forEach { add(mapper(it)) } + } + +internal fun instantiateObject(): T = + js("({})") diff --git a/drivers/web-worker-driver/src/wasmJsTest/kotlin/app/cash/sqldelight/drivers/worker/CreateBadWebWorkerDriver.kt b/drivers/web-worker-driver/src/wasmJsTest/kotlin/app/cash/sqldelight/drivers/worker/CreateBadWebWorkerDriver.kt new file mode 100644 index 00000000000..770ccd36879 --- /dev/null +++ b/drivers/web-worker-driver/src/wasmJsTest/kotlin/app/cash/sqldelight/drivers/worker/CreateBadWebWorkerDriver.kt @@ -0,0 +1,12 @@ +package app.cash.sqldelight.drivers.worker + +import app.cash.sqldelight.db.SqlDriver +import app.cash.sqldelight.driver.worker.WebWorkerDriver +import org.w3c.dom.Worker + +actual fun createBadWebWorkerDriver(): SqlDriver { + return WebWorkerDriver(badJsWorker()) +} + +fun badJsWorker(): Worker = + js("""new Worker(new URL("./bad.worker.js", import.meta.url))""") diff --git a/drivers/web-worker-driver/src/wasmJsTest/resources/bad.worker.js b/drivers/web-worker-driver/src/wasmJsTest/resources/bad.worker.js new file mode 100644 index 00000000000..41d9323dd32 --- /dev/null +++ b/drivers/web-worker-driver/src/wasmJsTest/resources/bad.worker.js @@ -0,0 +1,26 @@ +async function initialize() { +} + +function handleMessage() { + postMessage({ + id: this.data.id, + results: { garbage: true }, + }); +} + +function handleError(err) { + return postMessage({ + id: this.data.id, + error: err, + }); +} + +if (typeof importScripts === "function") { + const ready = initialize(); + + self.onmessage = (event) => { + ready + .then(handleMessage.bind(event)) + .catch(handleError.bind(event)); + } +} diff --git a/extensions/coroutines-extensions/build.gradle b/extensions/coroutines-extensions/build.gradle index 431e8f51a39..80d46edc370 100644 --- a/extensions/coroutines-extensions/build.gradle +++ b/extensions/coroutines-extensions/build.gradle @@ -47,24 +47,19 @@ kotlin { dependencies { implementation libs.kotlin.test.junit implementation projects.drivers.sqliteDriver - implementation libs.stately.concurrency } languageSettings { optIn('kotlinx.coroutines.ExperimentalCoroutinesApi') } } - jsTest { - dependencies { - implementation libs.stately.concurrency - implementation projects.drivers.webWorkerDriver - implementation npm("sql.js", libs.versions.sqljs.get()) - implementation npm("@cashapp/sqldelight-sqljs-worker", projects.drivers.webWorkerDriver.sqljs.dependencyProject.projectDir) - } + jsCommonTest.dependencies { + implementation projects.drivers.webWorkerDriver + implementation npm("sql.js", libs.versions.sqljs.get()) + implementation npm("@cashapp/sqldelight-sqljs-worker", projects.drivers.webWorkerDriver.sqljs.dependencyProject.projectDir) } - testableNativeTest { - dependencies { - implementation projects.drivers.nativeDriver - } + + testableNativeTest.dependencies { + implementation projects.drivers.nativeDriver } } diff --git a/extensions/coroutines-extensions/src/jsTest/kotlin/app/cash/sqldelight/coroutines/TestDriver.kt b/extensions/coroutines-extensions/src/jsCommonTest/kotlin/app/cash/sqldelight/coroutines/TestDriver.kt similarity index 75% rename from extensions/coroutines-extensions/src/jsTest/kotlin/app/cash/sqldelight/coroutines/TestDriver.kt rename to extensions/coroutines-extensions/src/jsCommonTest/kotlin/app/cash/sqldelight/coroutines/TestDriver.kt index 6155575b38f..3b24a929b0a 100644 --- a/extensions/coroutines-extensions/src/jsTest/kotlin/app/cash/sqldelight/coroutines/TestDriver.kt +++ b/extensions/coroutines-extensions/src/jsCommonTest/kotlin/app/cash/sqldelight/coroutines/TestDriver.kt @@ -17,9 +17,7 @@ package app.cash.sqldelight.coroutines import app.cash.sqldelight.db.SqlDriver -import app.cash.sqldelight.driver.worker.WebWorkerDriver -import org.w3c.dom.Worker +import app.cash.sqldelight.driver.worker.createWebWorkerDriver -@Suppress("UnsafeCastFromDynamic") actual suspend fun testDriver(): SqlDriver = - WebWorkerDriver(Worker(js("""new URL("@cashapp/sqldelight-sqljs-worker/sqljs.worker.js", import.meta.url)"""))) + createWebWorkerDriver() diff --git a/runtime/build.gradle b/runtime/build.gradle index 0e8382a8d5c..1d15ac8ff12 100644 --- a/runtime/build.gradle +++ b/runtime/build.gradle @@ -16,19 +16,22 @@ apiValidation { } kotlin { - sourceSets { - commonMain { + applyDefaultHierarchyTemplate { + it.common { + it.group("jsNativeCommon") { + it.withJs() + it.withWasm() + it.withNative() + } } + } + + sourceSets { commonTest { dependencies { implementation libs.kotlin.test } } - jvmTest { - dependencies { - implementation libs.kotlin.test.junit - } - } } } diff --git a/runtime/src/jsMain/kotlin/app/cash/sqldelight/internal/CurrentThreadId.kt b/runtime/src/jsCommonMain/kotlin/app/cash/sqldelight/internal/CurrentThreadId.kt similarity index 100% rename from runtime/src/jsMain/kotlin/app/cash/sqldelight/internal/CurrentThreadId.kt rename to runtime/src/jsCommonMain/kotlin/app/cash/sqldelight/internal/CurrentThreadId.kt diff --git a/runtime/src/jsMain/kotlin/app/cash/sqldelight/db/Closeable.kt b/runtime/src/jsNativeCommonMain/kotlin/app/cash/sqldelight/db/Closeable.kt similarity index 100% rename from runtime/src/jsMain/kotlin/app/cash/sqldelight/db/Closeable.kt rename to runtime/src/jsNativeCommonMain/kotlin/app/cash/sqldelight/db/Closeable.kt diff --git a/runtime/src/nativeMain/kotlin/app/cash/sqldelight/db/Closeable.kt b/runtime/src/nativeMain/kotlin/app/cash/sqldelight/db/Closeable.kt deleted file mode 100644 index 13f878a1a56..00000000000 --- a/runtime/src/nativeMain/kotlin/app/cash/sqldelight/db/Closeable.kt +++ /dev/null @@ -1,26 +0,0 @@ -package app.cash.sqldelight.db - -actual interface Closeable { - actual fun close() -} - -actual inline fun T.use(body: (T) -> R): R { - var exception: Throwable? = null - try { - return body(this) - } catch (e: Throwable) { - exception = e - throw e - } finally { - when { - this == null -> {} - exception == null -> close() - else -> - try { - close() - } catch (closeException: Throwable) { - // Nothing to do... - } - } - } -} diff --git a/sample-web/build.gradle b/sample-web/build.gradle index 4c6b7e96732..b11ed4e6296 100644 --- a/sample-web/build.gradle +++ b/sample-web/build.gradle @@ -8,7 +8,7 @@ sqldelight { HockeyDb { packageName = "com.example.sqldelight.hockey" generateAsync = true - srcDirs "src/jsMain/sqldelight" + srcDirs "src/commonMain/sqldelight" } } } @@ -20,27 +20,38 @@ kotlin { binaries.executable() useCommonJs() } + wasmJs { + browser() + binaries.executable() + useCommonJs() + } + applyDefaultHierarchyTemplate { + it.common { + it.group("jsCommon") { + it.withJs() + it.withWasm() + } + } + } sourceSets { - jsMain { - dependencies { - implementation "app.cash.sqldelight:web-worker-driver" - implementation "app.cash.sqldelight:primitive-adapters" - implementation "org.jetbrains.kotlinx:kotlinx-html-js:0.11.0" - implementation devNpm("copy-webpack-plugin", "9.1.0") - implementation npm('dateformat', '3.0.3') - implementation npm("sql.js", libs.versions.sqljs.get()) + commonMain.dependencies { + implementation "org.jetbrains.kotlinx:kotlinx-datetime:0.5.0" + implementation "app.cash.sqldelight:primitive-adapters" + implementation "org.jetbrains.kotlinx:kotlinx-html:0.11.0" + } + jsCommonMain.dependencies { + implementation "app.cash.sqldelight:web-worker-driver" + implementation devNpm("copy-webpack-plugin", "9.1.0") + implementation npm("sql.js", libs.versions.sqljs.get()) - def sqljsWorker = file("${gradle.includedBuild('sqldelight').projectDir}/drivers/web-worker-driver/sqljs") - implementation npm("@cashapp/sqldelight-sqljs-worker", sqljsWorker) - } + def sqljsWorker = file("${gradle.includedBuild('sqldelight').projectDir}/drivers/web-worker-driver/sqljs") + implementation npm("@cashapp/sqldelight-sqljs-worker", sqljsWorker) } - jsTest { - dependencies { - implementation 'org.jetbrains.kotlin:kotlin-test' - implementation libs.kotlin.coroutines.test - } + jsCommonTest.dependencies { + implementation 'org.jetbrains.kotlin:kotlin-test' + implementation libs.kotlin.coroutines.test } } } diff --git a/sample-web/kotlin-js-store/yarn.lock b/sample-web/kotlin-js-store/yarn.lock index 57fb3b3c7db..5de28a64d56 100644 --- a/sample-web/kotlin-js-store/yarn.lock +++ b/sample-web/kotlin-js-store/yarn.lock @@ -75,6 +75,11 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@js-joda/core@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@js-joda/core/-/core-3.2.0.tgz#3e61e21b7b2b8a6be746df1335cf91d70db2a273" + integrity sha512-PMqgJ0sw5B7FKb2d5bWYIoxjri+QlW/Pys7+Rw82jSH0QN3rB05jZ/VrrsUdh1w4+i2kw9JOejXGq/KhDOX7Kg== + "@jsonjoy.com/base64@^1.1.1": version "1.1.2" resolved "https://registry.yarnpkg.com/@jsonjoy.com/base64/-/base64-1.1.2.tgz#cf8ea9dcb849b81c95f14fc0aaa151c6b54d2578" @@ -928,11 +933,6 @@ date-format@^4.0.14: resolved "https://registry.yarnpkg.com/date-format/-/date-format-4.0.14.tgz#7a8e584434fb169a521c8b7aa481f355810d9400" integrity sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg== -dateformat@3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" - integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== - debug@2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" diff --git a/sample-web/src/commonMain/kotlin/com/example/sqldelight/hockey/data/Date.kt b/sample-web/src/commonMain/kotlin/com/example/sqldelight/hockey/data/Date.kt deleted file mode 100644 index 082ec4a2993..00000000000 --- a/sample-web/src/commonMain/kotlin/com/example/sqldelight/hockey/data/Date.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.example.sqldelight.hockey.data - -import app.cash.sqldelight.ColumnAdapter - -expect class Date(year: Int, month: Int, day: Int) - -expect class DateAdapter() : ColumnAdapter diff --git a/sample-web/src/commonMain/kotlin/com/example/sqldelight/hockey/db/Date.kt b/sample-web/src/commonMain/kotlin/com/example/sqldelight/hockey/db/Date.kt new file mode 100644 index 00000000000..a574bc15892 --- /dev/null +++ b/sample-web/src/commonMain/kotlin/com/example/sqldelight/hockey/db/Date.kt @@ -0,0 +1,15 @@ +package com.example.sqldelight.hockey.db + +import app.cash.sqldelight.ColumnAdapter + +expect class Date { + fun toEpochDay(): Double +} + +expect fun Date(year: Int, month: Int, day: Int): Date +expect fun dateFromEpochDays(days: Int): Date + +class DateAdapter : ColumnAdapter { + override fun encode(value: Date) = value.toEpochDay().toLong() + override fun decode(databaseValue: Long) = dateFromEpochDays(databaseValue.toInt()) +} diff --git a/sample-web/src/commonMain/kotlin/com/example/sqldelight/hockey/db/DateFormatHelper.kt b/sample-web/src/commonMain/kotlin/com/example/sqldelight/hockey/db/DateFormatHelper.kt new file mode 100644 index 00000000000..5cddf58ad05 --- /dev/null +++ b/sample-web/src/commonMain/kotlin/com/example/sqldelight/hockey/db/DateFormatHelper.kt @@ -0,0 +1,3 @@ +package com.example.sqldelight.hockey.db + +expect fun Date.formatted(format: String): String diff --git a/sample-web/src/jsMain/kotlin/com/example/sqldelight/hockey/data/DbHelper.kt b/sample-web/src/commonMain/kotlin/com/example/sqldelight/hockey/db/DbHelper.kt similarity index 85% rename from sample-web/src/jsMain/kotlin/com/example/sqldelight/hockey/data/DbHelper.kt rename to sample-web/src/commonMain/kotlin/com/example/sqldelight/hockey/db/DbHelper.kt index 51352809812..2a78e5bfb2d 100644 --- a/sample-web/src/jsMain/kotlin/com/example/sqldelight/hockey/data/DbHelper.kt +++ b/sample-web/src/commonMain/kotlin/com/example/sqldelight/hockey/db/DbHelper.kt @@ -1,32 +1,24 @@ -package com.example.sqldelight.hockey.data +package com.example.sqldelight.hockey.db import app.cash.sqldelight.EnumColumnAdapter import app.cash.sqldelight.adapter.primitive.FloatColumnAdapter import app.cash.sqldelight.adapter.primitive.IntColumnAdapter import app.cash.sqldelight.async.coroutines.awaitCreate import app.cash.sqldelight.db.SqlDriver -import app.cash.sqldelight.driver.worker.WebWorkerDriver import com.example.sqldelight.hockey.HockeyDb -import com.example.sqldelight.hockey.data.PlayerVals.Position -import com.example.sqldelight.hockey.data.PlayerVals.Shoots -import kotlin.js.Date +import com.example.sqldelight.hockey.db.PlayerVals.Position +import com.example.sqldelight.hockey.db.PlayerVals.Shoots import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock -import org.w3c.dom.Worker + +expect fun webWorkerDriver(): SqlDriver class DbHelper { - private val driver: SqlDriver + private val driver: SqlDriver = webWorkerDriver() private var db: HockeyDb? = null private val mutex = Mutex() - init { - @Suppress("UnsafeCastFromDynamic") - driver = WebWorkerDriver( - Worker(js("""new URL("@cashapp/sqldelight-sqljs-worker/sqljs.worker.js", import.meta.url)""")), - ) - } - suspend fun withDatabase(block: suspend (HockeyDb) -> Unit): Unit = mutex.withLock { if (db == null) { db = createDb(driver) diff --git a/sample-web/src/commonMain/kotlin/com/example/sqldelight/hockey/data/PlayerVals.kt b/sample-web/src/commonMain/kotlin/com/example/sqldelight/hockey/db/PlayerVals.kt similarity index 79% rename from sample-web/src/commonMain/kotlin/com/example/sqldelight/hockey/data/PlayerVals.kt rename to sample-web/src/commonMain/kotlin/com/example/sqldelight/hockey/db/PlayerVals.kt index 1456b5a9b62..0c36086adba 100644 --- a/sample-web/src/commonMain/kotlin/com/example/sqldelight/hockey/data/PlayerVals.kt +++ b/sample-web/src/commonMain/kotlin/com/example/sqldelight/hockey/db/PlayerVals.kt @@ -1,4 +1,4 @@ -package com.example.sqldelight.hockey.data +package com.example.sqldelight.hockey.db interface PlayerVals { enum class Shoots { diff --git a/sample-web/src/jsMain/sqldelight/com/example/sqldelight/hockey/data/Player.sq b/sample-web/src/commonMain/sqldelight/com/example/sqldelight/hockey/db/Player.sq similarity index 89% rename from sample-web/src/jsMain/sqldelight/com/example/sqldelight/hockey/data/Player.sq rename to sample-web/src/commonMain/sqldelight/com/example/sqldelight/hockey/db/Player.sq index 917693694bb..c4342dc4d51 100644 --- a/sample-web/src/jsMain/sqldelight/com/example/sqldelight/hockey/data/Player.sq +++ b/sample-web/src/commonMain/sqldelight/com/example/sqldelight/hockey/db/Player.sq @@ -1,5 +1,5 @@ -import com.example.sqldelight.hockey.data.PlayerVals; -import com.example.sqldelight.hockey.data.Date; +import com.example.sqldelight.hockey.db.PlayerVals; +import com.example.sqldelight.hockey.db.Date; import kotlin.Float; import kotlin.Int; diff --git a/sample-web/src/jsMain/sqldelight/com/example/sqldelight/hockey/data/Team.sq b/sample-web/src/commonMain/sqldelight/com/example/sqldelight/hockey/db/Team.sq similarity index 90% rename from sample-web/src/jsMain/sqldelight/com/example/sqldelight/hockey/data/Team.sq rename to sample-web/src/commonMain/sqldelight/com/example/sqldelight/hockey/db/Team.sq index 39c5fe8aac7..9a15c5dab0b 100644 --- a/sample-web/src/jsMain/sqldelight/com/example/sqldelight/hockey/data/Team.sq +++ b/sample-web/src/commonMain/sqldelight/com/example/sqldelight/hockey/db/Team.sq @@ -1,4 +1,4 @@ -import com.example.sqldelight.hockey.data.Date; +import com.example.sqldelight.hockey.db.Date; import kotlin.Boolean; CREATE TABLE team ( @@ -27,4 +27,4 @@ FROM team; updateCoachForTeam: UPDATE team SET coach = ? -WHERE name = ?; \ No newline at end of file +WHERE name = ?; diff --git a/sample-web/src/jsMain/sqldelight/migrations/1.sqm b/sample-web/src/commonMain/sqldelight/migrations/1.sqm similarity index 100% rename from sample-web/src/jsMain/sqldelight/migrations/1.sqm rename to sample-web/src/commonMain/sqldelight/migrations/1.sqm diff --git a/sample-web/src/jsMain/resources/index.html b/sample-web/src/jsCommonMain/resources/index.html similarity index 100% rename from sample-web/src/jsMain/resources/index.html rename to sample-web/src/jsCommonMain/resources/index.html diff --git a/sample-web/src/jsMain/kotlin/com/example/sqldelight/hockey/Main.kt b/sample-web/src/jsMain/kotlin/com/example/sqldelight/hockey/Main.kt index 4b5699a1720..0bd6931d4f3 100644 --- a/sample-web/src/jsMain/kotlin/com/example/sqldelight/hockey/Main.kt +++ b/sample-web/src/jsMain/kotlin/com/example/sqldelight/hockey/Main.kt @@ -1,10 +1,10 @@ package com.example.sqldelight.hockey import app.cash.sqldelight.async.coroutines.awaitAsList -import com.example.sqldelight.hockey.data.DbHelper -import com.example.sqldelight.hockey.data.ForTeam -import com.example.sqldelight.hockey.data.Team -import com.example.sqldelight.hockey.platform.dateFormat +import com.example.sqldelight.hockey.db.DbHelper +import com.example.sqldelight.hockey.db.ForTeam +import com.example.sqldelight.hockey.db.Team +import com.example.sqldelight.hockey.db.formatted import kotlinx.browser.document import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch @@ -66,7 +66,7 @@ private fun buildCoachesTable(teams: List): HTMLTableElement = document.cr tr { td { +team.name } td { +team.coach } - td { +dateFormat(team.founded, "dd/mm/yyyy") } + td { +team.founded.formatted("dd/MM/yyyy") } } } } diff --git a/sample-web/src/jsMain/kotlin/com/example/sqldelight/hockey/data/Date.kt b/sample-web/src/jsMain/kotlin/com/example/sqldelight/hockey/data/Date.kt deleted file mode 100644 index 515985db86b..00000000000 --- a/sample-web/src/jsMain/kotlin/com/example/sqldelight/hockey/data/Date.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.example.sqldelight.hockey.data - -import app.cash.sqldelight.ColumnAdapter -import kotlin.js.Date - -actual typealias Date = kotlin.js.Date - -actual class DateAdapter : ColumnAdapter { - override fun encode(value: Date) = value.getTime().toLong() - override fun decode(databaseValue: Long) = Date(databaseValue) -} diff --git a/sample-web/src/jsMain/kotlin/com/example/sqldelight/hockey/db/Date.kt b/sample-web/src/jsMain/kotlin/com/example/sqldelight/hockey/db/Date.kt new file mode 100644 index 00000000000..4e19728a6fb --- /dev/null +++ b/sample-web/src/jsMain/kotlin/com/example/sqldelight/hockey/db/Date.kt @@ -0,0 +1,9 @@ +package com.example.sqldelight.hockey.db + +import kotlinx.datetime.internal.JSJoda.LocalDate + +actual typealias Date = LocalDate + +actual fun Date(year: Int, month: Int, day: Int): Date = Date.of(year, month, day) + +actual fun dateFromEpochDays(days: Int): Date = Date.ofEpochDay(days) diff --git a/sample-web/src/jsMain/kotlin/com/example/sqldelight/hockey/db/DateFormatHelper.kt b/sample-web/src/jsMain/kotlin/com/example/sqldelight/hockey/db/DateFormatHelper.kt new file mode 100644 index 00000000000..d02b2828b7a --- /dev/null +++ b/sample-web/src/jsMain/kotlin/com/example/sqldelight/hockey/db/DateFormatHelper.kt @@ -0,0 +1,6 @@ +package com.example.sqldelight.hockey.db + +import kotlinx.datetime.internal.JSJoda.DateTimeFormatter + +actual fun Date.formatted(format: String): String = + DateTimeFormatter.ofPattern(format).format(this) diff --git a/sample-web/src/jsMain/kotlin/com/example/sqldelight/hockey/db/DbHelper.kt b/sample-web/src/jsMain/kotlin/com/example/sqldelight/hockey/db/DbHelper.kt new file mode 100644 index 00000000000..e3f8494200b --- /dev/null +++ b/sample-web/src/jsMain/kotlin/com/example/sqldelight/hockey/db/DbHelper.kt @@ -0,0 +1,12 @@ +package com.example.sqldelight.hockey.db + +import app.cash.sqldelight.db.SqlDriver +import app.cash.sqldelight.driver.worker.WebWorkerDriver +import org.w3c.dom.Worker + +actual fun webWorkerDriver(): SqlDriver = + WebWorkerDriver( + Worker(js("""new URL("@cashapp/sqldelight-sqljs-worker/sqljs.worker.js", import.meta.url)""")), + ).apply { + console.log(js("""new URL("@cashapp/sqldelight-sqljs-worker/sqljs.worker.js", import.meta.url)""")) + } diff --git a/sample-web/src/jsMain/kotlin/com/example/sqldelight/hockey/platform/DateFormatHelper.kt b/sample-web/src/jsMain/kotlin/com/example/sqldelight/hockey/platform/DateFormatHelper.kt deleted file mode 100644 index c5faf8b91c4..00000000000 --- a/sample-web/src/jsMain/kotlin/com/example/sqldelight/hockey/platform/DateFormatHelper.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.example.sqldelight.hockey.platform - -import com.example.sqldelight.hockey.data.Date - -@JsModule("dateformat") -external fun dateFormat(date: Date, format: String): String diff --git a/sample-web/src/jsTest/kotlin/com/example/sqldelight/hockey/TestHelper.kt b/sample-web/src/jsTest/kotlin/com/example/sqldelight/hockey/TestHelper.kt index 28c697bc894..60d39027c3a 100644 --- a/sample-web/src/jsTest/kotlin/com/example/sqldelight/hockey/TestHelper.kt +++ b/sample-web/src/jsTest/kotlin/com/example/sqldelight/hockey/TestHelper.kt @@ -1,6 +1,6 @@ package com.example.sqldelight.hockey -import com.example.sqldelight.hockey.data.DbHelper +import com.example.sqldelight.hockey.db.DbHelper import kotlinx.coroutines.test.runTest fun testDb(block: suspend (database: HockeyDb) -> Unit) = runTest { diff --git a/sample-web/src/wasmJsMain/kotlin/com/example/sqldelight/hockey/Main.kt b/sample-web/src/wasmJsMain/kotlin/com/example/sqldelight/hockey/Main.kt new file mode 100644 index 00000000000..0bd6931d4f3 --- /dev/null +++ b/sample-web/src/wasmJsMain/kotlin/com/example/sqldelight/hockey/Main.kt @@ -0,0 +1,73 @@ +package com.example.sqldelight.hockey + +import app.cash.sqldelight.async.coroutines.awaitAsList +import com.example.sqldelight.hockey.db.DbHelper +import com.example.sqldelight.hockey.db.ForTeam +import com.example.sqldelight.hockey.db.Team +import com.example.sqldelight.hockey.db.formatted +import kotlinx.browser.document +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch +import kotlinx.html.dom.create +import kotlinx.html.js.table +import kotlinx.html.tbody +import kotlinx.html.td +import kotlinx.html.th +import kotlinx.html.thead +import kotlinx.html.tr +import org.w3c.dom.HTMLTableElement + +fun main() { + val scope = MainScope() + val dbHelper = DbHelper() + + scope.launch { + dbHelper.withDatabase { database -> + val players = database.playerQueries.forTeam(-1).awaitAsList() + document.getElementById("players")?.append(buildPlayersTable(players)) + + val teams = database.teamQueries.selectAll().awaitAsList() + document.getElementById("teams")?.append(buildCoachesTable(teams)) + } + } +} + +private fun buildPlayersTable(players: List): HTMLTableElement = document.create.table { + thead { + tr { + th { +"First Name" } + th { +"Last Name" } + th { +"Team Name" } + th { +"Number" } + } + } + tbody { + players.forEach { player -> + tr { + td { +player.first_name } + td { +player.last_name } + td { +player.teamName } + td { +player.number } + } + } + } +} + +private fun buildCoachesTable(teams: List): HTMLTableElement = document.create.table { + thead { + tr { + th { +"Name" } + th { +"Coach" } + th { +"Founded" } + } + } + tbody { + teams.forEach { team -> + tr { + td { +team.name } + td { +team.coach } + td { +team.founded.formatted("dd/MM/yyyy") } + } + } + } +} diff --git a/sample-web/src/wasmJsMain/kotlin/com/example/sqldelight/hockey/db/Date.kt b/sample-web/src/wasmJsMain/kotlin/com/example/sqldelight/hockey/db/Date.kt new file mode 100644 index 00000000000..4e19728a6fb --- /dev/null +++ b/sample-web/src/wasmJsMain/kotlin/com/example/sqldelight/hockey/db/Date.kt @@ -0,0 +1,9 @@ +package com.example.sqldelight.hockey.db + +import kotlinx.datetime.internal.JSJoda.LocalDate + +actual typealias Date = LocalDate + +actual fun Date(year: Int, month: Int, day: Int): Date = Date.of(year, month, day) + +actual fun dateFromEpochDays(days: Int): Date = Date.ofEpochDay(days) diff --git a/sample-web/src/wasmJsMain/kotlin/com/example/sqldelight/hockey/db/DateFormatHelper.kt b/sample-web/src/wasmJsMain/kotlin/com/example/sqldelight/hockey/db/DateFormatHelper.kt new file mode 100644 index 00000000000..d02b2828b7a --- /dev/null +++ b/sample-web/src/wasmJsMain/kotlin/com/example/sqldelight/hockey/db/DateFormatHelper.kt @@ -0,0 +1,6 @@ +package com.example.sqldelight.hockey.db + +import kotlinx.datetime.internal.JSJoda.DateTimeFormatter + +actual fun Date.formatted(format: String): String = + DateTimeFormatter.ofPattern(format).format(this) diff --git a/sample-web/src/wasmJsMain/kotlin/com/example/sqldelight/hockey/db/DbHelper.kt b/sample-web/src/wasmJsMain/kotlin/com/example/sqldelight/hockey/db/DbHelper.kt new file mode 100644 index 00000000000..0c43a2906f0 --- /dev/null +++ b/sample-web/src/wasmJsMain/kotlin/com/example/sqldelight/hockey/db/DbHelper.kt @@ -0,0 +1,13 @@ +package com.example.sqldelight.hockey.db + +import app.cash.sqldelight.db.SqlDriver +import app.cash.sqldelight.driver.worker.WebWorkerDriver +import org.w3c.dom.Worker + +actual fun webWorkerDriver(): SqlDriver = + WebWorkerDriver( + createWorker(), + ) + +fun createWorker(): Worker = + js("""new Worker(new URL("@cashapp/sqldelight-sqljs-worker/sqljs.worker.js", import.meta.url))""") diff --git a/sample/common/common.podspec b/sample/common/common.podspec index 3922f36a3be..9936f2bcc50 100644 --- a/sample/common/common.podspec +++ b/sample/common/common.podspec @@ -8,9 +8,9 @@ Pod::Spec.new do |spec| spec.summary = 'Common core for SQLDelight sample.' spec.vendored_frameworks = 'build/cocoapods/framework/common.framework' spec.libraries = 'c++' - - - + + + if !Dir.exist?('build/cocoapods/framework/common.framework') || Dir.empty?('build/cocoapods/framework/common.framework') raise " @@ -21,11 +21,11 @@ Pod::Spec.new do |spec| Alternatively, proper pod installation is performed during Gradle sync in the IDE (if Podfile location is set)" end - + spec.xcconfig = { 'ENABLE_USER_SCRIPT_SANDBOXING' => 'NO', } - + if !Dir.exist?('build/cocoapods/framework/common.framework') || Dir.empty?('build/cocoapods/framework/common.framework') raise " @@ -36,12 +36,12 @@ Pod::Spec.new do |spec| Alternatively, proper pod installation is performed during Gradle sync in the IDE (if Podfile location is set)" end - + spec.pod_target_xcconfig = { 'KOTLIN_PROJECT_PATH' => ':common', 'PRODUCT_MODULE_NAME' => 'common', } - + spec.script_phases = [ { :name => 'Build common', @@ -61,5 +61,6 @@ Pod::Spec.new do |spec| SCRIPT } ] - + end + From aae60b53153052e213d649efd4942656931cc584 Mon Sep 17 00:00:00 2001 From: Ilya Gulya Date: Mon, 19 Feb 2024 12:00:32 +0600 Subject: [PATCH 02/13] migrate sample to use kotlinx.datetime --- sample/common/build.gradle | 1 + sample/common/common.podspec | 1 + .../example/sqldelight/hockey/data/Date.kt | 10 ------- .../sqldelight/hockey/data/DateAdapter.kt | 14 +++++++++ .../example/sqldelight/hockey/data/Schema.kt | 17 ++++++----- .../hockey/platform/DateFormatHelper.kt | 4 +-- .../example/sqldelight/hockey/data/Player.sq | 4 +-- .../example/sqldelight/hockey/data/Team.sq | 6 ++-- .../hockey/platform/DateFormatHelperTest.kt | 4 +-- .../sqldelight/hockey/data/Date.ios.kt | 30 ------------------- .../hockey/platform/DateFormatHelper.kt | 5 ++-- .../sqldelight/hockey/data/Date.jvm.kt | 13 -------- .../hockey/platform/DateFormatHelper.kt | 10 +++++-- 13 files changed, 45 insertions(+), 74 deletions(-) delete mode 100644 sample/common/src/commonMain/kotlin/com/example/sqldelight/hockey/data/Date.kt create mode 100644 sample/common/src/commonMain/kotlin/com/example/sqldelight/hockey/data/DateAdapter.kt delete mode 100644 sample/common/src/iosMain/kotlin/com/example/sqldelight/hockey/data/Date.ios.kt delete mode 100644 sample/common/src/jvmMain/kotlin/com/example/sqldelight/hockey/data/Date.jvm.kt diff --git a/sample/common/build.gradle b/sample/common/build.gradle index 153b5b7432d..76487c9457a 100644 --- a/sample/common/build.gradle +++ b/sample/common/build.gradle @@ -24,6 +24,7 @@ kotlin { // Configure common. sourceSets.commonMain.dependencies { implementation 'app.cash.sqldelight:primitive-adapters' + api "org.jetbrains.kotlinx:kotlinx-datetime:0.5.0" } sourceSets.commonTest.dependencies { implementation 'org.jetbrains.kotlin:kotlin-test' diff --git a/sample/common/common.podspec b/sample/common/common.podspec index 9936f2bcc50..30525a61061 100644 --- a/sample/common/common.podspec +++ b/sample/common/common.podspec @@ -64,3 +64,4 @@ Pod::Spec.new do |spec| end + diff --git a/sample/common/src/commonMain/kotlin/com/example/sqldelight/hockey/data/Date.kt b/sample/common/src/commonMain/kotlin/com/example/sqldelight/hockey/data/Date.kt deleted file mode 100644 index fd3b6aebb49..00000000000 --- a/sample/common/src/commonMain/kotlin/com/example/sqldelight/hockey/data/Date.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.example.sqldelight.hockey.data - -import app.cash.sqldelight.ColumnAdapter - -expect class Date(year: Int, month: Int, day: Int) - -expect class DateAdapter() : ColumnAdapter { - override fun encode(value: Date): Long - override fun decode(databaseValue: Long): Date -} diff --git a/sample/common/src/commonMain/kotlin/com/example/sqldelight/hockey/data/DateAdapter.kt b/sample/common/src/commonMain/kotlin/com/example/sqldelight/hockey/data/DateAdapter.kt new file mode 100644 index 00000000000..bedddf11a2a --- /dev/null +++ b/sample/common/src/commonMain/kotlin/com/example/sqldelight/hockey/data/DateAdapter.kt @@ -0,0 +1,14 @@ +package com.example.sqldelight.hockey.data + +import app.cash.sqldelight.ColumnAdapter +import kotlinx.datetime.LocalDate + +class DateAdapter : ColumnAdapter { + override fun decode(databaseValue: Long): LocalDate { + return LocalDate.fromEpochDays(databaseValue.toInt()) + } + + override fun encode(value: LocalDate): Long { + return value.toEpochDays().toLong() + } +} diff --git a/sample/common/src/commonMain/kotlin/com/example/sqldelight/hockey/data/Schema.kt b/sample/common/src/commonMain/kotlin/com/example/sqldelight/hockey/data/Schema.kt index a91597424fa..751eee32f7f 100644 --- a/sample/common/src/commonMain/kotlin/com/example/sqldelight/hockey/data/Schema.kt +++ b/sample/common/src/commonMain/kotlin/com/example/sqldelight/hockey/data/Schema.kt @@ -9,6 +9,7 @@ import app.cash.sqldelight.db.SqlSchema import com.example.sqldelight.hockey.HockeyDb import com.example.sqldelight.hockey.data.PlayerVals.Position import com.example.sqldelight.hockey.data.PlayerVals.Shoots +import kotlinx.datetime.LocalDate fun createQueryWrapper(driver: SqlDriver): HockeyDb { return HockeyDb( @@ -38,33 +39,33 @@ object Schema : SqlSchema> by HockeyDb.Schema { val sharks = "San Jose Sharks" // Populate teams. - teamQueries.insertTeam(ducks, Date(1993, 3, 1), "Randy Carlyle", true) - teamQueries.insertTeam(pens, Date(1966, 2, 8), "Mike Sullivan", true) - teamQueries.insertTeam(sharks, Date(1990, 5, 5), "Peter DeBoer", false) + teamQueries.insertTeam(ducks, LocalDate(1993, 3, 1), "Randy Carlyle", true) + teamQueries.insertTeam(pens, LocalDate(1966, 2, 8), "Mike Sullivan", true) + teamQueries.insertTeam(sharks, LocalDate(1990, 5, 5), "Peter DeBoer", false) playerQueries.insertPlayer( - "Corey", "Perry", 10, ducks, 30, 210F, Date(1985, 5, 16), + "Corey", "Perry", 10, ducks, 30, 210F, LocalDate(1985, 5, 16), Shoots.RIGHT, Position.RIGHT_WING, ) playerQueries.insertPlayer( - "Ryan", "Getzlaf", 15, ducks, 30, 221F, Date(1985, 5, 10), + "Ryan", "Getzlaf", 15, ducks, 30, 221F, LocalDate(1985, 5, 10), Shoots.RIGHT, Position.CENTER, ) teamQueries.setCaptain(15, ducks) playerQueries.insertPlayer( - "Sidney", "Crosby", 87, pens, 28, 200F, Date(1987, 8, 7), + "Sidney", "Crosby", 87, pens, 28, 200F, LocalDate(1987, 8, 7), Shoots.LEFT, Position.CENTER, ) teamQueries.setCaptain(87, pens) playerQueries.insertPlayer( - "Erik", "Karlsson", 65, sharks, 28, 190F, Date(1990, 5, 31), + "Erik", "Karlsson", 65, sharks, 28, 190F, LocalDate(1990, 5, 31), Shoots.RIGHT, Position.DEFENSE, ) playerQueries.insertPlayer( - "Joe", "Pavelski", 8, sharks, 31, 194F, Date(1984, 7, 18), + "Joe", "Pavelski", 8, sharks, 31, 194F, LocalDate(1984, 7, 18), Shoots.RIGHT, Position.CENTER, ) teamQueries.setCaptain(8, sharks) diff --git a/sample/common/src/commonMain/kotlin/com/example/sqldelight/hockey/platform/DateFormatHelper.kt b/sample/common/src/commonMain/kotlin/com/example/sqldelight/hockey/platform/DateFormatHelper.kt index fbaadfe41c7..09715e0cb65 100644 --- a/sample/common/src/commonMain/kotlin/com/example/sqldelight/hockey/platform/DateFormatHelper.kt +++ b/sample/common/src/commonMain/kotlin/com/example/sqldelight/hockey/platform/DateFormatHelper.kt @@ -1,9 +1,9 @@ package com.example.sqldelight.hockey.platform -import com.example.sqldelight.hockey.data.Date +import kotlinx.datetime.LocalDate expect class DateFormatHelper(format: String) { - fun format(d: Date): String + fun format(d: LocalDate): String } val defaultFormatter = DateFormatHelper("dd/mm/yyyy") diff --git a/sample/common/src/commonMain/sqldelight/com/example/sqldelight/hockey/data/Player.sq b/sample/common/src/commonMain/sqldelight/com/example/sqldelight/hockey/data/Player.sq index 917693694bb..e260acaef8d 100644 --- a/sample/common/src/commonMain/sqldelight/com/example/sqldelight/hockey/data/Player.sq +++ b/sample/common/src/commonMain/sqldelight/com/example/sqldelight/hockey/data/Player.sq @@ -1,5 +1,5 @@ import com.example.sqldelight.hockey.data.PlayerVals; -import com.example.sqldelight.hockey.data.Date; +import kotlinx.datetime.LocalDate; import kotlin.Float; import kotlin.Int; @@ -10,7 +10,7 @@ CREATE TABLE player ( number INTEGER AS Int NOT NULL, team INTEGER, age INTEGER AS Int NOT NULL, - birth_date INTEGER AS Date NOT NULL, + birth_date INTEGER AS LocalDate NOT NULL, weight REAL AS Float NOT NULL, shoots TEXT AS PlayerVals.Shoots NOT NULL, position TEXT AS PlayerVals.Position NOT NULL, diff --git a/sample/common/src/commonMain/sqldelight/com/example/sqldelight/hockey/data/Team.sq b/sample/common/src/commonMain/sqldelight/com/example/sqldelight/hockey/data/Team.sq index 39c5fe8aac7..7bc823c12f5 100644 --- a/sample/common/src/commonMain/sqldelight/com/example/sqldelight/hockey/data/Team.sq +++ b/sample/common/src/commonMain/sqldelight/com/example/sqldelight/hockey/data/Team.sq @@ -1,10 +1,10 @@ -import com.example.sqldelight.hockey.data.Date; import kotlin.Boolean; +import kotlinx.datetime.LocalDate; CREATE TABLE team ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, - founded INTEGER AS Date NOT NULL, + founded INTEGER AS LocalDate NOT NULL, coach TEXT NOT NULL, captain INTEGER, won_cup INTEGER AS Boolean NOT NULL DEFAULT 0, @@ -27,4 +27,4 @@ FROM team; updateCoachForTeam: UPDATE team SET coach = ? -WHERE name = ?; \ No newline at end of file +WHERE name = ?; diff --git a/sample/common/src/commonTest/kotlin/com/example/sqldelight/hockey/platform/DateFormatHelperTest.kt b/sample/common/src/commonTest/kotlin/com/example/sqldelight/hockey/platform/DateFormatHelperTest.kt index 71c35d87696..d49212b7812 100644 --- a/sample/common/src/commonTest/kotlin/com/example/sqldelight/hockey/platform/DateFormatHelperTest.kt +++ b/sample/common/src/commonTest/kotlin/com/example/sqldelight/hockey/platform/DateFormatHelperTest.kt @@ -1,14 +1,14 @@ package com.example.sqldelight.hockey.platform -import com.example.sqldelight.hockey.data.Date import kotlin.test.Test import kotlin.test.assertEquals +import kotlinx.datetime.LocalDate class DateFormatHelperTest { @Test fun format() { val df = DateFormatHelper("yyyy-MM-dd") - val date = Date(2019, 3, 7) + val date = LocalDate(2019, 4, 7) val formatted = df.format(date) assertEquals("2019-04-07", formatted) diff --git a/sample/common/src/iosMain/kotlin/com/example/sqldelight/hockey/data/Date.ios.kt b/sample/common/src/iosMain/kotlin/com/example/sqldelight/hockey/data/Date.ios.kt deleted file mode 100644 index 98a885e3edb..00000000000 --- a/sample/common/src/iosMain/kotlin/com/example/sqldelight/hockey/data/Date.ios.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.example.sqldelight.hockey.data - -import app.cash.sqldelight.ColumnAdapter -import kotlin.math.floor -import platform.Foundation.NSCalendar -import platform.Foundation.NSDate -import platform.Foundation.NSDateComponents -import platform.Foundation.dateWithTimeIntervalSince1970 -import platform.Foundation.timeIntervalSince1970 - -actual class Date internal constructor(internal val nsDate: NSDate) { - actual constructor(year: Int, month: Int, day: Int) : this(partsToDate(year, month + 1, day)) -} - -internal fun partsToDate(year: Int, month: Int, day: Int): NSDate { - val cal = NSCalendar.currentCalendar - val comps = NSDateComponents() - comps.setDay(day.toLong()) - comps.setMonth(month.toLong()) - comps.setYear(year.toLong()) - return cal.dateFromComponents(comps)!! -} - -actual class DateAdapter actual constructor() : ColumnAdapter { - actual override fun decode(databaseValue: Long): Date = - Date(NSDate.dateWithTimeIntervalSince1970(databaseValue.toDouble() / 1000)) - - actual override fun encode(value: Date): Long = - floor(value.nsDate.timeIntervalSince1970 * 1000L).toLong() -} diff --git a/sample/common/src/iosMain/kotlin/com/example/sqldelight/hockey/platform/DateFormatHelper.kt b/sample/common/src/iosMain/kotlin/com/example/sqldelight/hockey/platform/DateFormatHelper.kt index 04a9dfbe1b4..d2941ec6c39 100644 --- a/sample/common/src/iosMain/kotlin/com/example/sqldelight/hockey/platform/DateFormatHelper.kt +++ b/sample/common/src/iosMain/kotlin/com/example/sqldelight/hockey/platform/DateFormatHelper.kt @@ -1,6 +1,7 @@ package com.example.sqldelight.hockey.platform -import com.example.sqldelight.hockey.data.Date +import kotlinx.datetime.LocalDate +import kotlinx.datetime.toNSDateComponents import platform.Foundation.NSDateFormatter actual class DateFormatHelper actual constructor(format: String) { @@ -10,5 +11,5 @@ actual class DateFormatHelper actual constructor(format: String) { formatter.dateFormat = format } - actual fun format(d: Date): String = formatter.stringFromDate(d.nsDate) + actual fun format(d: LocalDate): String = formatter.stringFromDate(d.toNSDateComponents().date!!) } diff --git a/sample/common/src/jvmMain/kotlin/com/example/sqldelight/hockey/data/Date.jvm.kt b/sample/common/src/jvmMain/kotlin/com/example/sqldelight/hockey/data/Date.jvm.kt deleted file mode 100644 index 839ecb0a369..00000000000 --- a/sample/common/src/jvmMain/kotlin/com/example/sqldelight/hockey/data/Date.jvm.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.example.sqldelight.hockey.data - -import app.cash.sqldelight.ColumnAdapter -import java.util.GregorianCalendar - -actual typealias Date = GregorianCalendar - -actual class DateAdapter actual constructor() : ColumnAdapter { - actual override fun encode(value: Date) = value.timeInMillis - actual override fun decode(databaseValue: Long) = Date.getInstance().apply { - timeInMillis = databaseValue - } as Date -} diff --git a/sample/common/src/jvmMain/kotlin/com/example/sqldelight/hockey/platform/DateFormatHelper.kt b/sample/common/src/jvmMain/kotlin/com/example/sqldelight/hockey/platform/DateFormatHelper.kt index 05e5019e7c3..e009cbc2464 100644 --- a/sample/common/src/jvmMain/kotlin/com/example/sqldelight/hockey/platform/DateFormatHelper.kt +++ b/sample/common/src/jvmMain/kotlin/com/example/sqldelight/hockey/platform/DateFormatHelper.kt @@ -1,13 +1,19 @@ package com.example.sqldelight.hockey.platform -import com.example.sqldelight.hockey.data.Date import java.text.DateFormat import java.text.SimpleDateFormat +import java.util.* +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.atStartOfDayIn actual class DateFormatHelper actual constructor(format: String) { val dateFormatter = object : ThreadLocal() { override fun initialValue(): DateFormat = SimpleDateFormat(format) } - actual fun format(d: Date): String = dateFormatter.get()!!.format(d.time) + actual fun format(d: LocalDate): String { + val epochMilliseconds = d.atStartOfDayIn(TimeZone.currentSystemDefault()).toEpochMilliseconds() + return dateFormatter.get()!!.format(Date(epochMilliseconds)) + } } From 7bb5f884d52ce862de8d79d541d08ca549db34dd Mon Sep 17 00:00:00 2001 From: Ilya Gulya Date: Mon, 19 Feb 2024 15:31:33 +0600 Subject: [PATCH 03/13] make android integration tests run on latest emulators (targetSdk higher than 23 is required) --- gradle/libs.versions.toml | 1 + .../src/test/integration-android-library/build.gradle | 1 + .../src/test/integration-android/build.gradle | 1 + 3 files changed, 3 insertions(+) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7f1ac198557..6cd1ed45328 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,6 +13,7 @@ ktlint = "1.3.1" agp = "8.7.0" compileSdk = "34" minSdk = "21" +targetSdk = "34" sqlPsi = "0.4.9" testContainers = "1.20.2" diff --git a/sqldelight-gradle-plugin/src/test/integration-android-library/build.gradle b/sqldelight-gradle-plugin/src/test/integration-android-library/build.gradle index 6ddc52cf4c7..590e3bd2488 100644 --- a/sqldelight-gradle-plugin/src/test/integration-android-library/build.gradle +++ b/sqldelight-gradle-plugin/src/test/integration-android-library/build.gradle @@ -31,6 +31,7 @@ android { defaultConfig { minSdk libs.versions.minSdk.get() as int + targetSdk libs.versions.targetSdk.get() as int testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } diff --git a/sqldelight-gradle-plugin/src/test/integration-android/build.gradle b/sqldelight-gradle-plugin/src/test/integration-android/build.gradle index d366ad220a3..95bcee709d3 100644 --- a/sqldelight-gradle-plugin/src/test/integration-android/build.gradle +++ b/sqldelight-gradle-plugin/src/test/integration-android/build.gradle @@ -31,6 +31,7 @@ android { defaultConfig { minSdk libs.versions.minSdk.get() as int + targetSdk libs.versions.targetSdk.get() as int testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } From 48deaa3051d547a004194cb23e3bfc46a9c9b07e Mon Sep 17 00:00:00 2001 From: Ilya Gulya Date: Wed, 13 Mar 2024 23:44:11 +0500 Subject: [PATCH 04/13] hide kotlin warning regarding expect/actual on classes --- .../sqldelight/multiplatform/MultiplatformConventions.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/buildLogic/multiplatform-convention/src/main/kotlin/app/cash/sqldelight/multiplatform/MultiplatformConventions.kt b/buildLogic/multiplatform-convention/src/main/kotlin/app/cash/sqldelight/multiplatform/MultiplatformConventions.kt index 6346a57648c..896017510e7 100644 --- a/buildLogic/multiplatform-convention/src/main/kotlin/app/cash/sqldelight/multiplatform/MultiplatformConventions.kt +++ b/buildLogic/multiplatform-convention/src/main/kotlin/app/cash/sqldelight/multiplatform/MultiplatformConventions.kt @@ -15,6 +15,12 @@ class MultiplatformConventions : Plugin { project.plugins.apply("org.jetbrains.kotlin.multiplatform") (project.kotlinExtension as KotlinMultiplatformExtension).apply { + compilerOptions { + this.freeCompilerArgs.addAll( + "-Xexpect-actual-classes", + ) + } + jvm() val jsConfigure: KotlinJsTargetDsl.() -> Unit = { From 5192a86dce08cf0e0ccbab25f530d44f0cb6dba8 Mon Sep 17 00:00:00 2001 From: Ilya Gulya Date: Wed, 13 Mar 2024 23:44:55 +0500 Subject: [PATCH 05/13] fix coroutines-extensions tests build failing because of SQLiter not available for androidNative --- extensions/coroutines-extensions/build.gradle | 23 +++++++++---------- .../cash/sqldelight/coroutines/TestDriver.kt | 23 +++++++++++++++++++ 2 files changed, 34 insertions(+), 12 deletions(-) create mode 100644 extensions/coroutines-extensions/src/androidNativeTest/kotlin/app/cash/sqldelight/coroutines/TestDriver.kt diff --git a/extensions/coroutines-extensions/build.gradle b/extensions/coroutines-extensions/build.gradle index 80d46edc370..ef540a12c01 100644 --- a/extensions/coroutines-extensions/build.gradle +++ b/extensions/coroutines-extensions/build.gradle @@ -29,20 +29,19 @@ kotlin { } sourceSets { - commonMain { - dependencies { - api projects.runtime - api libs.kotlin.coroutines.core - implementation project(":extensions:async-extensions") - } + commonMain.dependencies { + api projects.runtime + api libs.kotlin.coroutines.core + implementation project(":extensions:async-extensions") } - commonTest { - dependencies { - implementation libs.kotlin.coroutines.test - implementation libs.kotlin.test - implementation libs.turbine - } + + commonTest.dependencies { + implementation libs.kotlin.coroutines.test + implementation libs.kotlin.test + implementation libs.turbine + implementation libs.stately.concurrency } + jvmTest { dependencies { implementation libs.kotlin.test.junit diff --git a/extensions/coroutines-extensions/src/androidNativeTest/kotlin/app/cash/sqldelight/coroutines/TestDriver.kt b/extensions/coroutines-extensions/src/androidNativeTest/kotlin/app/cash/sqldelight/coroutines/TestDriver.kt new file mode 100644 index 00000000000..059953562d6 --- /dev/null +++ b/extensions/coroutines-extensions/src/androidNativeTest/kotlin/app/cash/sqldelight/coroutines/TestDriver.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2018 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package app.cash.sqldelight.coroutines + +import app.cash.sqldelight.db.SqlDriver + +actual suspend fun testDriver(): SqlDriver { + TODO("Sqliter does not support androidNative yet: https://github.com/touchlab/SQLiter/issues/117") +} From f3b1f43b2d1734d5e3a9dba2603781985e41401b Mon Sep 17 00:00:00 2001 From: Ilya Gulya Date: Mon, 25 Mar 2024 22:40:43 +0500 Subject: [PATCH 06/13] cleanup --- .../multiplatform/MultiplatformConventions.kt | 9 +++------ drivers/web-worker-driver/build.gradle | 13 ++----------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/buildLogic/multiplatform-convention/src/main/kotlin/app/cash/sqldelight/multiplatform/MultiplatformConventions.kt b/buildLogic/multiplatform-convention/src/main/kotlin/app/cash/sqldelight/multiplatform/MultiplatformConventions.kt index 896017510e7..09d26f71088 100644 --- a/buildLogic/multiplatform-convention/src/main/kotlin/app/cash/sqldelight/multiplatform/MultiplatformConventions.kt +++ b/buildLogic/multiplatform-convention/src/main/kotlin/app/cash/sqldelight/multiplatform/MultiplatformConventions.kt @@ -7,7 +7,6 @@ import org.jetbrains.kotlin.gradle.dsl.JsModuleKind import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.dsl.kotlinExtension import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl -import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsTargetDsl import org.jetbrains.kotlin.konan.target.HostManager class MultiplatformConventions : Plugin { @@ -23,8 +22,9 @@ class MultiplatformConventions : Plugin { jvm() - val jsConfigure: KotlinJsTargetDsl.() -> Unit = { - browser { + @OptIn(ExperimentalWasmDsl::class) + listOf(js(), wasmJs()).forEach { + it.browser { testTask { it.useKarma { useChromeHeadless() @@ -35,9 +35,6 @@ class MultiplatformConventions : Plugin { moduleKind.set(JsModuleKind.MODULE_UMD) } } - js(jsConfigure) - @OptIn(ExperimentalWasmDsl::class) - wasmJs(jsConfigure) @OptIn(ExperimentalKotlinGradlePluginApi::class) applyDefaultHierarchyTemplate { diff --git a/drivers/web-worker-driver/build.gradle b/drivers/web-worker-driver/build.gradle index c313bbb8dcc..854eba249f5 100644 --- a/drivers/web-worker-driver/build.gradle +++ b/drivers/web-worker-driver/build.gradle @@ -5,17 +5,8 @@ plugins { } kotlin { - js { - browser { - testTask { - useKarma { - useChromeHeadless() - } - } - } - } - wasmJs { - browser { + [js(), wasmJs()].forEach { + it.browser { testTask { useKarma { useChromeHeadless() From 88112dd8aa9b17e817c44fdda762a117013b085f Mon Sep 17 00:00:00 2001 From: Ilya Gulya Date: Sun, 26 May 2024 22:03:27 +0500 Subject: [PATCH 07/13] commonize WebWorkerDriver. rename `createWebWorkerDriver()` to `createDefaultWebWorkerDriver()`. remove unnecessary `jsCommon` group from web-worker-driver. --- .../multiplatform/MultiplatformConventions.kt | 1 + drivers/web-worker-driver/build.gradle | 18 +- .../driver/worker/CreateWebWorkerDriver.kt | 2 +- .../driver/worker/WebWorkerDriver.kt | 141 ++++++++++ .../driver/worker/WebWorkerException.kt | 0 .../sqldelight/driver/worker/WorkerWrapper.kt | 13 + .../driver/worker/api/WorkerAction.kt | 0 .../driver/worker/api/WorkerResult.kt | 3 + .../worker/api/WorkerResultWithRowCount.kt | 6 + .../driver/worker/api/WorkerWrapperRequest.kt | 28 ++ .../worker/expected/CheckWorkerResults.kt | 5 + .../worker/expected/JsWorkerSqlCursor.kt | 6 + .../expected/JsWorkerSqlPreparedStatement.kt | 5 + .../driver/worker/expected/Worker.kt | 3 + .../worker/CreateBadWebWorkerDriver.kt | 0 .../drivers/worker/WebWorkerDriverTest.kt | 4 +- .../drivers/worker/WebWorkerTransacterTest.kt | 11 +- ...ver.kt => CreateDefaultWebWorkerDriver.kt} | 2 +- .../driver/worker/WebWorkerDriver.kt | 209 --------------- .../sqldelight/driver/worker/WorkerWrapper.kt | 83 ++++++ .../{WorkerRequest.kt => JsWorkerRequest.kt} | 6 +- .../driver/worker/api/WorkerAction.kt | 2 +- .../driver/worker/api/WorkerResponse.kt | 2 +- .../driver/worker/api/WorkerResult.kt | 2 +- .../worker/expected/CheckWorkerResults.kt | 9 + .../expected/JsWorkerResultWithRowCount.kt | 19 ++ .../worker/expected/JsWorkerSqlCursor.kt | 25 ++ .../expected/JsWorkerSqlPreparedStatement.kt | 30 +++ .../driver/worker/expected/Worker.kt | 3 + .../driver/worker/CreateWebWorkerDriver.kt | 2 +- .../worker/WasmWorkerResultWithRowCount.kt | 17 ++ .../driver/worker/WebWorkerDriver.kt | 240 ------------------ .../sqldelight/driver/worker/WorkerWrapper.kt | 94 +++++++ ...{WorkerRequest.kt => WasmWorkerRequest.kt} | 4 +- ...orkerResponse.kt => WasmWorkerResponse.kt} | 2 +- .../driver/worker/api/WorkerResult.kt | 2 +- .../worker/expected/CheckWorkerResults.kt | 11 + .../worker/expected/JsWorkerSqlCursor.kt | 50 ++++ .../expected/JsWorkerSqlPreparedStatement.kt | 32 +++ .../driver/worker/expected/Worker.kt | 3 + .../cash/sqldelight/coroutines/TestDriver.kt | 4 +- 41 files changed, 624 insertions(+), 475 deletions(-) rename drivers/web-worker-driver/src/{jsCommonMain => commonMain}/kotlin/app/cash/sqldelight/driver/worker/CreateWebWorkerDriver.kt (61%) create mode 100644 drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/WebWorkerDriver.kt rename drivers/web-worker-driver/src/{jsCommonMain => commonMain}/kotlin/app/cash/sqldelight/driver/worker/WebWorkerException.kt (100%) create mode 100644 drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/WorkerWrapper.kt rename drivers/web-worker-driver/src/{jsCommonMain => commonMain}/kotlin/app/cash/sqldelight/driver/worker/api/WorkerAction.kt (100%) create mode 100644 drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResult.kt create mode 100644 drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResultWithRowCount.kt create mode 100644 drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerWrapperRequest.kt create mode 100644 drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/expected/CheckWorkerResults.kt create mode 100644 drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlCursor.kt create mode 100644 drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlPreparedStatement.kt create mode 100644 drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/expected/Worker.kt rename drivers/web-worker-driver/src/{jsCommonTest => commonTest}/kotlin/app/cash/sqldelight/drivers/worker/CreateBadWebWorkerDriver.kt (100%) rename drivers/web-worker-driver/src/{jsCommonTest => commonTest}/kotlin/app/cash/sqldelight/drivers/worker/WebWorkerDriverTest.kt (98%) rename drivers/web-worker-driver/src/{jsCommonTest => commonTest}/kotlin/app/cash/sqldelight/drivers/worker/WebWorkerTransacterTest.kt (95%) rename drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/{CreateWebWorkerDriver.kt => CreateDefaultWebWorkerDriver.kt} (80%) delete mode 100644 drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/WebWorkerDriver.kt create mode 100644 drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/WorkerWrapper.kt rename drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/{WorkerRequest.kt => JsWorkerRequest.kt} (74%) create mode 100644 drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/CheckWorkerResults.kt create mode 100644 drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerResultWithRowCount.kt create mode 100644 drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlCursor.kt create mode 100644 drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlPreparedStatement.kt create mode 100644 drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/Worker.kt create mode 100644 drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/WasmWorkerResultWithRowCount.kt delete mode 100644 drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/WebWorkerDriver.kt create mode 100644 drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/WorkerWrapper.kt rename drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/{WorkerRequest.kt => WasmWorkerRequest.kt} (84%) rename drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/{WorkerResponse.kt => WasmWorkerResponse.kt} (90%) create mode 100644 drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/expected/CheckWorkerResults.kt create mode 100644 drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlCursor.kt create mode 100644 drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlPreparedStatement.kt create mode 100644 drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/expected/Worker.kt diff --git a/buildLogic/multiplatform-convention/src/main/kotlin/app/cash/sqldelight/multiplatform/MultiplatformConventions.kt b/buildLogic/multiplatform-convention/src/main/kotlin/app/cash/sqldelight/multiplatform/MultiplatformConventions.kt index 09d26f71088..46429b03a41 100644 --- a/buildLogic/multiplatform-convention/src/main/kotlin/app/cash/sqldelight/multiplatform/MultiplatformConventions.kt +++ b/buildLogic/multiplatform-convention/src/main/kotlin/app/cash/sqldelight/multiplatform/MultiplatformConventions.kt @@ -14,6 +14,7 @@ class MultiplatformConventions : Plugin { project.plugins.apply("org.jetbrains.kotlin.multiplatform") (project.kotlinExtension as KotlinMultiplatformExtension).apply { + @OptIn(ExperimentalKotlinGradlePluginApi::class) compilerOptions { this.freeCompilerArgs.addAll( "-Xexpect-actual-classes", diff --git a/drivers/web-worker-driver/build.gradle b/drivers/web-worker-driver/build.gradle index 854eba249f5..13bc3f7b618 100644 --- a/drivers/web-worker-driver/build.gradle +++ b/drivers/web-worker-driver/build.gradle @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation + plugins { alias(libs.plugins.kotlin.multiplatform) alias(libs.plugins.publish) @@ -16,20 +18,18 @@ kotlin { } applyDefaultHierarchyTemplate { it.common { - it.group("jsCommon") { - it.withJs() - it.withWasm() - } + it.withJs() + it.withWasm() } } sourceSets { - jsCommonMain.dependencies { + commonMain.dependencies { api projects.runtime implementation libs.kotlin.coroutines.core } - jsCommonTest.dependencies { + commonTest.dependencies { implementation libs.kotlin.test implementation npm("sql.js", libs.versions.sqljs.get()) implementation npm("@cashapp/sqldelight-sqljs-worker", project(":drivers:web-worker-driver:sqljs").projectDir) @@ -40,6 +40,12 @@ kotlin { } } +tasks.withType(KotlinCompilation.class).configureEach { + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + } +} + apply from: "$rootDir/gradle/gradle-mvn-push.gradle" tasks.named("dokkaHtmlMultiModule") { diff --git a/drivers/web-worker-driver/src/jsCommonMain/kotlin/app/cash/sqldelight/driver/worker/CreateWebWorkerDriver.kt b/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/CreateWebWorkerDriver.kt similarity index 61% rename from drivers/web-worker-driver/src/jsCommonMain/kotlin/app/cash/sqldelight/driver/worker/CreateWebWorkerDriver.kt rename to drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/CreateWebWorkerDriver.kt index 32cdf3f6a1e..23331d523f8 100644 --- a/drivers/web-worker-driver/src/jsCommonMain/kotlin/app/cash/sqldelight/driver/worker/CreateWebWorkerDriver.kt +++ b/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/CreateWebWorkerDriver.kt @@ -2,4 +2,4 @@ package app.cash.sqldelight.driver.worker import app.cash.sqldelight.db.SqlDriver -expect fun createWebWorkerDriver(): SqlDriver +expect fun createDefaultWebWorkerDriver(): SqlDriver diff --git a/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/WebWorkerDriver.kt b/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/WebWorkerDriver.kt new file mode 100644 index 00000000000..61d1d78487c --- /dev/null +++ b/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/WebWorkerDriver.kt @@ -0,0 +1,141 @@ +package app.cash.sqldelight.driver.worker + +import app.cash.sqldelight.Query +import app.cash.sqldelight.Transacter +import app.cash.sqldelight.db.QueryResult +import app.cash.sqldelight.db.SqlCursor +import app.cash.sqldelight.db.SqlDriver +import app.cash.sqldelight.db.SqlPreparedStatement +import app.cash.sqldelight.driver.worker.api.WorkerAction +import app.cash.sqldelight.driver.worker.api.WorkerActions +import app.cash.sqldelight.driver.worker.api.WorkerResultWithRowCount +import app.cash.sqldelight.driver.worker.api.WorkerWrapperRequest +import app.cash.sqldelight.driver.worker.expected.JsWorkerSqlCursor +import app.cash.sqldelight.driver.worker.expected.JsWorkerSqlPreparedStatement +import app.cash.sqldelight.driver.worker.expected.Worker +import app.cash.sqldelight.driver.worker.expected.checkWorkerResults + +/** + * A [SqlDriver] implementation for interacting with SQL databases running in a Web Worker. + * + * This driver is dialect-agnostic and is instead dependent on the Worker script's implementation + * to handle queries and send results back from the Worker. + * + * @property worker The Worker running a SQL implementation that this driver communicates with. + * @see [WebWorkerDriver.fromScriptUrl] + */ +class WebWorkerDriver(private val worker: Worker) : SqlDriver { + private val listeners = mutableMapOf>() + private var messageCounter = 0 + private var transaction: Transacter.Transaction? = null + private val wrapper = WorkerWrapper(worker) + + override fun executeQuery( + identifier: Int?, + sql: String, + mapper: (SqlCursor) -> QueryResult, + parameters: Int, + binders: (SqlPreparedStatement.() -> Unit)?, + ): QueryResult { + val bound = JsWorkerSqlPreparedStatement() + binders?.invoke(bound) + + return QueryResult.AsyncValue { + val response = wrapper.sendMessage( + action = WorkerActions.exec, + sql = sql, + statement = bound, + ) + + return@AsyncValue mapper(JsWorkerSqlCursor(checkWorkerResults(response.result))).await() + } + } + + override fun execute( + identifier: Int?, + sql: String, + parameters: Int, + binders: (SqlPreparedStatement.() -> Unit)?, + ): QueryResult { + val bound = JsWorkerSqlPreparedStatement() + binders?.invoke(bound) + + return QueryResult.AsyncValue { + val response = wrapper.sendMessage( + action = WorkerActions.exec, + sql = sql, + statement = bound, + ) + checkWorkerResults(response.result) + return@AsyncValue response.rowCount + } + } + + override fun addListener(vararg queryKeys: String, listener: Query.Listener) { + queryKeys.forEach { + listeners.getOrPut(it) { mutableSetOf() }.add(listener) + } + } + + override fun removeListener(vararg queryKeys: String, listener: Query.Listener) { + queryKeys.forEach { + listeners[it]?.remove(listener) + } + } + + override fun notifyListeners(vararg queryKeys: String) { + queryKeys.flatMap { listeners[it].orEmpty() } + .distinct() + .forEach(Query.Listener::queryResultsChanged) + } + + override fun close() = wrapper.terminate() + + override fun newTransaction(): QueryResult = QueryResult.AsyncValue { + val enclosing = transaction + val transaction = Transaction(enclosing) + this.transaction = transaction + if (enclosing == null) { + wrapper.sendMessage(WorkerActions.beginTransaction) + } + + return@AsyncValue transaction + } + + override fun currentTransaction(): Transacter.Transaction? = transaction + + private inner class Transaction( + override val enclosingTransaction: Transacter.Transaction?, + ) : Transacter.Transaction() { + override fun endTransaction(successful: Boolean): QueryResult = QueryResult.AsyncValue { + if (enclosingTransaction == null) { + if (successful) { + wrapper.sendMessage(WorkerActions.endTransaction) + } else { + wrapper.sendMessage(WorkerActions.rollbackTransaction) + } + } + transaction = enclosingTransaction + } + } + + private suspend fun WorkerWrapper.sendMessage( + action: WorkerAction, + sql: String? = null, + statement: JsWorkerSqlPreparedStatement? = null, + ): WorkerResultWithRowCount { + val id = messageCounter++ + + println("beforeExecute") + + return execute( + WorkerWrapperRequest( + id = id, + action = action, + sql = sql, + statement = statement, + ), + ) + } + +} diff --git a/drivers/web-worker-driver/src/jsCommonMain/kotlin/app/cash/sqldelight/driver/worker/WebWorkerException.kt b/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/WebWorkerException.kt similarity index 100% rename from drivers/web-worker-driver/src/jsCommonMain/kotlin/app/cash/sqldelight/driver/worker/WebWorkerException.kt rename to drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/WebWorkerException.kt diff --git a/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/WorkerWrapper.kt b/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/WorkerWrapper.kt new file mode 100644 index 00000000000..7f8c59e302e --- /dev/null +++ b/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/WorkerWrapper.kt @@ -0,0 +1,13 @@ +package app.cash.sqldelight.driver.worker + +import app.cash.sqldelight.driver.worker.api.WorkerResultWithRowCount +import app.cash.sqldelight.driver.worker.api.WorkerWrapperRequest +import app.cash.sqldelight.driver.worker.expected.Worker + +internal expect class WorkerWrapper(worker: Worker) { + suspend fun execute( + request: WorkerWrapperRequest, + ): WorkerResultWithRowCount + + fun terminate() +} diff --git a/drivers/web-worker-driver/src/jsCommonMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerAction.kt b/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerAction.kt similarity index 100% rename from drivers/web-worker-driver/src/jsCommonMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerAction.kt rename to drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerAction.kt diff --git a/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResult.kt b/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResult.kt new file mode 100644 index 00000000000..d3728f58688 --- /dev/null +++ b/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResult.kt @@ -0,0 +1,3 @@ +package app.cash.sqldelight.driver.worker.api + +internal expect interface WorkerResult diff --git a/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResultWithRowCount.kt b/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResultWithRowCount.kt new file mode 100644 index 00000000000..40e0e7c5f9d --- /dev/null +++ b/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResultWithRowCount.kt @@ -0,0 +1,6 @@ +package app.cash.sqldelight.driver.worker.api + +internal interface WorkerResultWithRowCount { + val result: WorkerResult + val rowCount: Long +} diff --git a/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerWrapperRequest.kt b/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerWrapperRequest.kt new file mode 100644 index 00000000000..75b6199e4c3 --- /dev/null +++ b/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerWrapperRequest.kt @@ -0,0 +1,28 @@ +package app.cash.sqldelight.driver.worker.api + +import app.cash.sqldelight.driver.worker.expected.JsWorkerSqlPreparedStatement + +/** + * Messages sent by the SQLDelight driver to the worker. + */ +internal data class WorkerWrapperRequest( + /** + * A unique identifier used to identify responses to this message + * @see WorkerResponse.id + */ + val id: Int, + /** + * The action that the worker should run. + * @see WorkerAction + */ + val action: WorkerAction, + /** + * The SQL to execute + */ + var sql: String?, + + /** + * SQL parameters to bind to the given [sql] + */ + var statement: JsWorkerSqlPreparedStatement?, +) diff --git a/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/expected/CheckWorkerResults.kt b/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/expected/CheckWorkerResults.kt new file mode 100644 index 00000000000..1b5d90b6354 --- /dev/null +++ b/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/expected/CheckWorkerResults.kt @@ -0,0 +1,5 @@ +package app.cash.sqldelight.driver.worker.expected + +import app.cash.sqldelight.driver.worker.api.WorkerResult + +internal expect fun checkWorkerResults(results: WorkerResult?): WorkerResult diff --git a/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlCursor.kt b/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlCursor.kt new file mode 100644 index 00000000000..f3df38bd2cb --- /dev/null +++ b/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlCursor.kt @@ -0,0 +1,6 @@ +package app.cash.sqldelight.driver.worker.expected + +import app.cash.sqldelight.db.SqlCursor +import app.cash.sqldelight.driver.worker.api.WorkerResult + +internal expect class JsWorkerSqlCursor(result: WorkerResult) : SqlCursor diff --git a/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlPreparedStatement.kt b/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlPreparedStatement.kt new file mode 100644 index 00000000000..82eb669ed32 --- /dev/null +++ b/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlPreparedStatement.kt @@ -0,0 +1,5 @@ +package app.cash.sqldelight.driver.worker.expected + +import app.cash.sqldelight.db.SqlPreparedStatement + +internal expect class JsWorkerSqlPreparedStatement() : SqlPreparedStatement diff --git a/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/expected/Worker.kt b/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/expected/Worker.kt new file mode 100644 index 00000000000..ac59d152ee4 --- /dev/null +++ b/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/expected/Worker.kt @@ -0,0 +1,3 @@ +package app.cash.sqldelight.driver.worker.expected + +expect class Worker diff --git a/drivers/web-worker-driver/src/jsCommonTest/kotlin/app/cash/sqldelight/drivers/worker/CreateBadWebWorkerDriver.kt b/drivers/web-worker-driver/src/commonTest/kotlin/app/cash/sqldelight/drivers/worker/CreateBadWebWorkerDriver.kt similarity index 100% rename from drivers/web-worker-driver/src/jsCommonTest/kotlin/app/cash/sqldelight/drivers/worker/CreateBadWebWorkerDriver.kt rename to drivers/web-worker-driver/src/commonTest/kotlin/app/cash/sqldelight/drivers/worker/CreateBadWebWorkerDriver.kt diff --git a/drivers/web-worker-driver/src/jsCommonTest/kotlin/app/cash/sqldelight/drivers/worker/WebWorkerDriverTest.kt b/drivers/web-worker-driver/src/commonTest/kotlin/app/cash/sqldelight/drivers/worker/WebWorkerDriverTest.kt similarity index 98% rename from drivers/web-worker-driver/src/jsCommonTest/kotlin/app/cash/sqldelight/drivers/worker/WebWorkerDriverTest.kt rename to drivers/web-worker-driver/src/commonTest/kotlin/app/cash/sqldelight/drivers/worker/WebWorkerDriverTest.kt index 47b225486f2..3ab53154ba4 100644 --- a/drivers/web-worker-driver/src/jsCommonTest/kotlin/app/cash/sqldelight/drivers/worker/WebWorkerDriverTest.kt +++ b/drivers/web-worker-driver/src/commonTest/kotlin/app/cash/sqldelight/drivers/worker/WebWorkerDriverTest.kt @@ -11,7 +11,7 @@ import app.cash.sqldelight.db.SqlDriver import app.cash.sqldelight.db.SqlPreparedStatement import app.cash.sqldelight.db.SqlSchema import app.cash.sqldelight.driver.worker.WebWorkerException -import app.cash.sqldelight.driver.worker.createWebWorkerDriver +import app.cash.sqldelight.driver.worker.createDefaultWebWorkerDriver import kotlin.test.Test import kotlin.test.assertContains import kotlin.test.assertEquals @@ -62,7 +62,7 @@ class WebWorkerDriverTest { private fun runTest(block: suspend (SqlDriver) -> Unit) = kotlinx.coroutines.test.runTest { val driver = - createWebWorkerDriver() + createDefaultWebWorkerDriver() .also { schema.awaitCreate(it) } block(driver) driver.close() diff --git a/drivers/web-worker-driver/src/jsCommonTest/kotlin/app/cash/sqldelight/drivers/worker/WebWorkerTransacterTest.kt b/drivers/web-worker-driver/src/commonTest/kotlin/app/cash/sqldelight/drivers/worker/WebWorkerTransacterTest.kt similarity index 95% rename from drivers/web-worker-driver/src/jsCommonTest/kotlin/app/cash/sqldelight/drivers/worker/WebWorkerTransacterTest.kt rename to drivers/web-worker-driver/src/commonTest/kotlin/app/cash/sqldelight/drivers/worker/WebWorkerTransacterTest.kt index e744668c939..48017dc85ea 100644 --- a/drivers/web-worker-driver/src/jsCommonTest/kotlin/app/cash/sqldelight/drivers/worker/WebWorkerTransacterTest.kt +++ b/drivers/web-worker-driver/src/commonTest/kotlin/app/cash/sqldelight/drivers/worker/WebWorkerTransacterTest.kt @@ -7,7 +7,7 @@ import app.cash.sqldelight.db.AfterVersion import app.cash.sqldelight.db.QueryResult import app.cash.sqldelight.db.SqlDriver import app.cash.sqldelight.db.SqlSchema -import app.cash.sqldelight.driver.worker.createWebWorkerDriver +import app.cash.sqldelight.driver.worker.createDefaultWebWorkerDriver import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith @@ -28,10 +28,15 @@ class WebWorkerTransacterTest { private fun runTest(block: suspend (SqlDriver, SuspendingTransacter) -> Unit) = kotlinx.coroutines.test.runTest { + println("-2") val driver = - createWebWorkerDriver() - .also { schema.awaitCreate(it) } + createDefaultWebWorkerDriver() + .also { + println("-1") + schema.awaitCreate(it) + } + println("0") val transacter = object : SuspendingTransacterImpl(driver) {} block(driver, transacter) diff --git a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/CreateWebWorkerDriver.kt b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/CreateDefaultWebWorkerDriver.kt similarity index 80% rename from drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/CreateWebWorkerDriver.kt rename to drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/CreateDefaultWebWorkerDriver.kt index 0b3e71f4bb5..09a0247d57f 100644 --- a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/CreateWebWorkerDriver.kt +++ b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/CreateDefaultWebWorkerDriver.kt @@ -3,6 +3,6 @@ package app.cash.sqldelight.driver.worker import app.cash.sqldelight.db.SqlDriver import org.w3c.dom.Worker -actual fun createWebWorkerDriver(): SqlDriver { +actual fun createDefaultWebWorkerDriver(): SqlDriver { return WebWorkerDriver(Worker(js("""new URL("@cashapp/sqldelight-sqljs-worker/sqljs.worker.js", import.meta.url)"""))) } diff --git a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/WebWorkerDriver.kt b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/WebWorkerDriver.kt deleted file mode 100644 index 0a20d570f69..00000000000 --- a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/WebWorkerDriver.kt +++ /dev/null @@ -1,209 +0,0 @@ -package app.cash.sqldelight.driver.worker - -import app.cash.sqldelight.Query -import app.cash.sqldelight.Transacter -import app.cash.sqldelight.db.QueryResult -import app.cash.sqldelight.db.SqlCursor -import app.cash.sqldelight.db.SqlDriver -import app.cash.sqldelight.db.SqlPreparedStatement -import app.cash.sqldelight.driver.worker.api.WorkerAction -import app.cash.sqldelight.driver.worker.api.WorkerActions -import app.cash.sqldelight.driver.worker.api.WorkerResponse -import app.cash.sqldelight.driver.worker.api.WorkerResult -import app.cash.sqldelight.driver.worker.api.buildRequest -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlinx.coroutines.suspendCancellableCoroutine -import org.khronos.webgl.Int8Array -import org.khronos.webgl.Uint8Array -import org.w3c.dom.MessageEvent -import org.w3c.dom.Worker -import org.w3c.dom.events.Event -import org.w3c.dom.events.EventListener - -/** - * A [SqlDriver] implementation for interacting with SQL databases running in a Web Worker. - * - * This driver is dialect-agnostic and is instead dependent on the Worker script's implementation - * to handle queries and send results back from the Worker. - * - * @property worker The Worker running a SQL implementation that this driver communicates with. - * @see [WebWorkerDriver.fromScriptUrl] - */ -class WebWorkerDriver(private val worker: Worker) : SqlDriver { - private val listeners = mutableMapOf>() - private var messageCounter = 0 - private var transaction: Transacter.Transaction? = null - - override fun executeQuery(identifier: Int?, sql: String, mapper: (SqlCursor) -> QueryResult, parameters: Int, binders: (SqlPreparedStatement.() -> Unit)?): QueryResult { - val bound = JsWorkerSqlPreparedStatement() - binders?.invoke(bound) - - return QueryResult.AsyncValue { - val response = worker.sendMessage(WorkerActions.exec) { - this.sql = sql - this.params = bound.parameters.toTypedArray() - } - - return@AsyncValue mapper(JsWorkerSqlCursor(checkWorkerResults(response.results))).await() - } - } - - override fun execute(identifier: Int?, sql: String, parameters: Int, binders: (SqlPreparedStatement.() -> Unit)?): QueryResult { - val bound = JsWorkerSqlPreparedStatement() - binders?.invoke(bound) - - return QueryResult.AsyncValue { - val response = worker.sendMessage(WorkerActions.exec) { - this.sql = sql - this.params = bound.parameters.toTypedArray() - } - checkWorkerResults(response.results) - return@AsyncValue when { - response.results.values.isEmpty() -> 0L - else -> response.results.values[0][0].unsafeCast().toLong() - } - } - } - - override fun addListener(vararg queryKeys: String, listener: Query.Listener) { - queryKeys.forEach { - listeners.getOrPut(it) { mutableSetOf() }.add(listener) - } - } - - override fun removeListener(vararg queryKeys: String, listener: Query.Listener) { - queryKeys.forEach { - listeners[it]?.remove(listener) - } - } - - override fun notifyListeners(vararg queryKeys: String) { - queryKeys.flatMap { listeners[it].orEmpty() } - .distinct() - .forEach(Query.Listener::queryResultsChanged) - } - - override fun close() = worker.terminate() - - override fun newTransaction(): QueryResult = QueryResult.AsyncValue { - val enclosing = transaction - val transaction = Transaction(enclosing) - this.transaction = transaction - if (enclosing == null) { - worker.sendMessage(WorkerActions.beginTransaction) - } - - return@AsyncValue transaction - } - - override fun currentTransaction(): Transacter.Transaction? = transaction - - private inner class Transaction( - override val enclosingTransaction: Transacter.Transaction?, - ) : Transacter.Transaction() { - override fun endTransaction(successful: Boolean): QueryResult = QueryResult.AsyncValue { - if (enclosingTransaction == null) { - if (successful) { - worker.sendMessage(WorkerActions.endTransaction) - } else { - worker.sendMessage(WorkerActions.rollbackTransaction) - } - } - transaction = enclosingTransaction - } - } - - private suspend fun Worker.sendMessage(action: WorkerAction, builder: RequestBuilder.() -> Unit = {}): WorkerResponse = suspendCancellableCoroutine { continuation -> - val id = messageCounter++ - val messageListener = object : EventListener { - override fun handleEvent(event: Event) { - val data = event.unsafeCast().data.unsafeCast() - if (data.id == id) { - removeEventListener("message", this) - if (data.error != null) { - continuation.resumeWithException(WebWorkerException(JSON.stringify(data.error, arrayOf("message", "arguments", "type", "name")))) - } else { - continuation.resume(data) - } - } - } - } - - val errorListener = object : EventListener { - override fun handleEvent(event: Event) { - removeEventListener("error", this) - continuation.resumeWithException(WebWorkerException(JSON.stringify(event, arrayOf("message", "arguments", "type", "name")) + js("Object.entries(event)"))) - } - } - - addEventListener("message", messageListener) - addEventListener("error", errorListener) - - val messageObject = buildRequest { - this.unsafeCast().builder() - this.id = id - this.action = action - } - postMessage(messageObject) - - continuation.invokeOnCancellation { - removeEventListener("message", messageListener) - removeEventListener("error", errorListener) - } - } - - private fun checkWorkerResults(results: WorkerResult?): Array> { - checkNotNull(results) { "The worker result was null " } - check(js("Array.isArray(results.values)").unsafeCast()) { "The worker result values were not an array" } - return results.values - } -} - -private external interface RequestBuilder { - var sql: String? - var params: Array? -} - -internal class JsWorkerSqlCursor(private val values: Array>) : SqlCursor { - private var currentRow = -1 - - override fun next(): QueryResult.Value = QueryResult.Value(++currentRow < values.size) - - override fun getString(index: Int): String? = values[currentRow][index].unsafeCast() - - override fun getLong(index: Int): Long? = (values[currentRow][index] as? Double)?.toLong() - - override fun getBytes(index: Int): ByteArray? = (values[currentRow][index] as? Uint8Array)?.let { Int8Array(it.buffer).unsafeCast() } - - override fun getDouble(index: Int): Double? = values[currentRow][index].unsafeCast() - - override fun getBoolean(index: Int): Boolean? = values[currentRow][index].unsafeCast() -} - -internal class JsWorkerSqlPreparedStatement : SqlPreparedStatement { - - val parameters = mutableListOf() - - override fun bindBytes(index: Int, bytes: ByteArray?) { - parameters.add(bytes) - } - - override fun bindLong(index: Int, long: Long?) { - // We convert Long to Double because Kotlin's Double is mapped to JS number - // whereas Kotlin's Long is implemented as a JS object - parameters.add(long?.toDouble()) - } - - override fun bindDouble(index: Int, double: Double?) { - parameters.add(double) - } - - override fun bindString(index: Int, string: String?) { - parameters.add(string) - } - - override fun bindBoolean(index: Int, boolean: Boolean?) { - parameters.add(boolean) - } -} diff --git a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/WorkerWrapper.kt b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/WorkerWrapper.kt new file mode 100644 index 00000000000..882ad1ba901 --- /dev/null +++ b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/WorkerWrapper.kt @@ -0,0 +1,83 @@ +package app.cash.sqldelight.driver.worker + +import app.cash.sqldelight.driver.worker.api.JsWorkerResponse +import app.cash.sqldelight.driver.worker.api.WorkerResultWithRowCount +import app.cash.sqldelight.driver.worker.api.WorkerWrapperRequest +import app.cash.sqldelight.driver.worker.api.buildRequest +import app.cash.sqldelight.driver.worker.expected.JsWorkerResultWithRowCount +import app.cash.sqldelight.driver.worker.expected.Worker +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlinx.coroutines.suspendCancellableCoroutine +import org.w3c.dom.MessageEvent +import org.w3c.dom.events.Event +import org.w3c.dom.events.EventListener + +internal actual class WorkerWrapper actual constructor( + private val worker: Worker, +) { + actual suspend fun execute( + request: WorkerWrapperRequest, + ): WorkerResultWithRowCount { + return suspendCancellableCoroutine { continuation -> + val messageListener = object : EventListener { + override fun handleEvent(event: Event) { + val data = event.unsafeCast().data.unsafeCast() + if (data.id == request.id) { + worker.removeEventListener("message", this) + if (data.error != null) { + continuation.resumeWithException( + WebWorkerException( + JSON.stringify( + data.error, + arrayOf("message", "arguments", "type", "name"), + ), + ), + ) + } else { + continuation.resume( + JsWorkerResultWithRowCount(data), + ) + } + } + } + } + + val errorListener = object : EventListener { + override fun handleEvent(event: Event) { + worker.removeEventListener("error", this) + continuation.resumeWithException( + WebWorkerException( + JSON.stringify( + event, + arrayOf("message", "arguments", "type", "name"), + ) + js("Object.entries(event)"), + ), + ) + } + } + + worker.addEventListener("message", messageListener) + worker.addEventListener("error", errorListener) + + val messageObject = buildRequest { + this.id = request.id + this.action = request.action + this.sql = request.sql + this.params = request.statement?.parameters?.toTypedArray() + } + + worker.postMessage(messageObject) + + continuation.invokeOnCancellation { + worker.removeEventListener("message", messageListener) + worker.removeEventListener("error", errorListener) + } + + } + } + + actual fun terminate() { + worker.terminate() + } +} diff --git a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerRequest.kt b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/JsWorkerRequest.kt similarity index 74% rename from drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerRequest.kt rename to drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/JsWorkerRequest.kt index 88bccb8dab3..174df85ef6f 100644 --- a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerRequest.kt +++ b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/JsWorkerRequest.kt @@ -3,7 +3,7 @@ package app.cash.sqldelight.driver.worker.api /** * Messages sent by the SQLDelight driver to the worker. */ -internal external interface WorkerRequest { +internal external interface JsWorkerRequest { /** * A unique identifier used to identify responses to this message * @see WorkerResponse.id @@ -27,8 +27,8 @@ internal external interface WorkerRequest { var params: Array? } -internal fun buildRequest(builder: WorkerRequest.() -> Unit): WorkerRequest { - val request = js("{}").unsafeCast() +internal fun buildRequest(builder: JsWorkerRequest.() -> Unit): JsWorkerRequest { + val request = js("{}").unsafeCast() builder(request) return request } diff --git a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerAction.kt b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerAction.kt index 46cfc8daa43..0e7d2ae94b7 100644 --- a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerAction.kt +++ b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerAction.kt @@ -2,5 +2,5 @@ package app.cash.sqldelight.driver.worker.api internal actual sealed interface WorkerAction -@Suppress("NOTHING_TO_INLINE", "FunctionName") +@Suppress("NOTHING_TO_INLINE") internal actual inline fun WorkerAction(value: String) = value.unsafeCast() diff --git a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResponse.kt b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResponse.kt index 1261867c8a3..9939c2fb093 100644 --- a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResponse.kt +++ b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResponse.kt @@ -3,7 +3,7 @@ package app.cash.sqldelight.driver.worker.api /** * Data returned by the worker after posting a message. */ -internal external interface WorkerResponse { +internal external interface JsWorkerResponse { /** * An error returned by the worker, could be undefined. */ diff --git a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResult.kt b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResult.kt index 17badf75c73..6d71e7aa800 100644 --- a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResult.kt +++ b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResult.kt @@ -3,7 +3,7 @@ package app.cash.sqldelight.driver.worker.api /** * The results of a SQL operation in the worker. */ -internal external interface WorkerResult { +internal actual external interface WorkerResult { /** * The "table" of values in the result, as rows of columns. * i.e. `values[row][col]` diff --git a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/CheckWorkerResults.kt b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/CheckWorkerResults.kt new file mode 100644 index 00000000000..a578770eed3 --- /dev/null +++ b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/CheckWorkerResults.kt @@ -0,0 +1,9 @@ +package app.cash.sqldelight.driver.worker.expected + +import app.cash.sqldelight.driver.worker.api.WorkerResult + +internal actual fun checkWorkerResults(results: WorkerResult?): WorkerResult { + checkNotNull(results) { "The worker result was null " } + check(js("Array.isArray(results.values)").unsafeCast()) { "The worker result values were not an array" } + return results +} diff --git a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerResultWithRowCount.kt b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerResultWithRowCount.kt new file mode 100644 index 00000000000..86ea9203600 --- /dev/null +++ b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerResultWithRowCount.kt @@ -0,0 +1,19 @@ +package app.cash.sqldelight.driver.worker.expected + +import app.cash.sqldelight.driver.worker.api.JsWorkerResponse +import app.cash.sqldelight.driver.worker.api.WorkerResult +import app.cash.sqldelight.driver.worker.api.WorkerResultWithRowCount + +internal class JsWorkerResultWithRowCount( + private val data: JsWorkerResponse, +) : + WorkerResultWithRowCount { + override val rowCount: Long by lazy { + when { + data.results.values.isEmpty() -> 0L + else -> data.results.values[0][0].unsafeCast().toLong() + } + } + + override val result: WorkerResult = data.results +} diff --git a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlCursor.kt b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlCursor.kt new file mode 100644 index 00000000000..06da3ef1192 --- /dev/null +++ b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlCursor.kt @@ -0,0 +1,25 @@ +package app.cash.sqldelight.driver.worker.expected + +import app.cash.sqldelight.db.QueryResult +import app.cash.sqldelight.db.SqlCursor +import app.cash.sqldelight.driver.worker.api.WorkerResult +import org.khronos.webgl.Int8Array +import org.khronos.webgl.Uint8Array + +internal actual class JsWorkerSqlCursor actual constructor(result: WorkerResult) : SqlCursor { + private val values: Array> = result.values + private var currentRow = -1 + + override fun next(): QueryResult.Value = QueryResult.Value(++currentRow < values.size) + + override fun getString(index: Int): String? = values[currentRow][index].unsafeCast() + + override fun getLong(index: Int): Long? = (values[currentRow][index] as? Double)?.toLong() + + override fun getBytes(index: Int): ByteArray? = + (values[currentRow][index] as? Uint8Array)?.let { Int8Array(it.buffer).unsafeCast() } + + override fun getDouble(index: Int): Double? = values[currentRow][index].unsafeCast() + + override fun getBoolean(index: Int): Boolean? = values[currentRow][index].unsafeCast() +} diff --git a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlPreparedStatement.kt b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlPreparedStatement.kt new file mode 100644 index 00000000000..4397a1356f2 --- /dev/null +++ b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlPreparedStatement.kt @@ -0,0 +1,30 @@ +package app.cash.sqldelight.driver.worker.expected + +import app.cash.sqldelight.db.SqlPreparedStatement + +internal actual class JsWorkerSqlPreparedStatement : SqlPreparedStatement { + + val parameters = mutableListOf() + + override fun bindBytes(index: Int, bytes: ByteArray?) { + parameters.add(bytes) + } + + override fun bindLong(index: Int, long: Long?) { + // We convert Long to Double because Kotlin's Double is mapped to JS number + // whereas Kotlin's Long is implemented as a JS object + parameters.add(long?.toDouble()) + } + + override fun bindDouble(index: Int, double: Double?) { + parameters.add(double) + } + + override fun bindString(index: Int, string: String?) { + parameters.add(string) + } + + override fun bindBoolean(index: Int, boolean: Boolean?) { + parameters.add(boolean) + } +} diff --git a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/Worker.kt b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/Worker.kt new file mode 100644 index 00000000000..16a53969ba0 --- /dev/null +++ b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/Worker.kt @@ -0,0 +1,3 @@ +package app.cash.sqldelight.driver.worker.expected + +actual typealias Worker = org.w3c.dom.Worker diff --git a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/CreateWebWorkerDriver.kt b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/CreateWebWorkerDriver.kt index 39861f35d6d..7ed55b867b8 100644 --- a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/CreateWebWorkerDriver.kt +++ b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/CreateWebWorkerDriver.kt @@ -3,7 +3,7 @@ package app.cash.sqldelight.driver.worker import app.cash.sqldelight.db.SqlDriver import org.w3c.dom.Worker -actual fun createWebWorkerDriver(): SqlDriver { +actual fun createDefaultWebWorkerDriver(): SqlDriver { return WebWorkerDriver(jsWorker()) } diff --git a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/WasmWorkerResultWithRowCount.kt b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/WasmWorkerResultWithRowCount.kt new file mode 100644 index 00000000000..ee427655516 --- /dev/null +++ b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/WasmWorkerResultWithRowCount.kt @@ -0,0 +1,17 @@ +package app.cash.sqldelight.driver.worker + +import app.cash.sqldelight.driver.worker.api.WasmWorkerResponse +import app.cash.sqldelight.driver.worker.api.WorkerResultWithRowCount + +internal class WasmWorkerResultWithRowCount( + private val data: WasmWorkerResponse, +) : WorkerResultWithRowCount { + override val rowCount: Long + get() = when { + data.results.values?.length == 0 -> 0L + else -> data.results.values?.get(0)?.get(0)?.unsafeCast()?.toDouble() + ?.toLong() ?: 0L + } + + override val result = data.results +} diff --git a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/WebWorkerDriver.kt b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/WebWorkerDriver.kt deleted file mode 100644 index 5f33c878ef5..00000000000 --- a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/WebWorkerDriver.kt +++ /dev/null @@ -1,240 +0,0 @@ -package app.cash.sqldelight.driver.worker - -import app.cash.sqldelight.Query -import app.cash.sqldelight.Transacter -import app.cash.sqldelight.db.QueryResult -import app.cash.sqldelight.db.SqlCursor -import app.cash.sqldelight.db.SqlDriver -import app.cash.sqldelight.db.SqlPreparedStatement -import app.cash.sqldelight.driver.worker.api.WorkerAction -import app.cash.sqldelight.driver.worker.api.WorkerActions -import app.cash.sqldelight.driver.worker.api.WorkerRequest -import app.cash.sqldelight.driver.worker.api.WorkerResponse -import app.cash.sqldelight.driver.worker.api.WorkerResult -import app.cash.sqldelight.driver.worker.util.add -import app.cash.sqldelight.driver.worker.util.instantiateObject -import app.cash.sqldelight.driver.worker.util.isArray -import app.cash.sqldelight.driver.worker.util.jsonStringify -import app.cash.sqldelight.driver.worker.util.objectEntries -import app.cash.sqldelight.driver.worker.util.toJsArray -import app.cash.sqldelight.driver.worker.util.toUint8Array -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlinx.coroutines.suspendCancellableCoroutine -import org.khronos.webgl.Uint8Array -import org.khronos.webgl.get -import org.w3c.dom.MessageEvent -import org.w3c.dom.Worker -import org.w3c.dom.events.Event - -/** - * A [SqlDriver] implementation for interacting with SQL databases running in a Web Worker. - * - * This driver is dialect-agnostic and is instead dependent on the Worker script's implementation - * to handle queries and send results back from the Worker. - * - * @property worker The Worker running a SQL implementation that this driver communicates with. - * @see [WebWorkerDriver.fromScriptUrl] - */ -class WebWorkerDriver(private val worker: Worker) : SqlDriver { - private val listeners = mutableMapOf>() - private var messageCounter = 0 - private var transaction: Transacter.Transaction? = null - - override fun executeQuery(identifier: Int?, sql: String, mapper: (SqlCursor) -> QueryResult, parameters: Int, binders: (SqlPreparedStatement.() -> Unit)?): QueryResult { - val bound = JsWorkerSqlPreparedStatement() - binders?.invoke(bound) - - return QueryResult.AsyncValue { - val response = worker.sendMessage(WorkerActions.exec) { - this.sql = sql - this.params = bound.parameters - } - - return@AsyncValue mapper(JsWorkerSqlCursor(checkWorkerResults(response.results))).await() - } - } - - override fun execute(identifier: Int?, sql: String, parameters: Int, binders: (SqlPreparedStatement.() -> Unit)?): QueryResult { - val bound = JsWorkerSqlPreparedStatement() - binders?.invoke(bound) - - return QueryResult.AsyncValue { - val response = worker.sendMessage(WorkerActions.exec) { - this.sql = sql - this.params = bound.parameters - } - val values = checkWorkerResults(response.results) - return@AsyncValue when { - values.length == 0 -> 0L - else -> values[0]?.get(0)?.unsafeCast()?.toDouble()?.toLong() ?: 0L - } - } - } - - override fun addListener(vararg queryKeys: String, listener: Query.Listener) { - queryKeys.forEach { - listeners.getOrPut(it) { mutableSetOf() }.add(listener) - } - } - - override fun removeListener(vararg queryKeys: String, listener: Query.Listener) { - queryKeys.forEach { - listeners[it]?.remove(listener) - } - } - - override fun notifyListeners(vararg queryKeys: String) { - queryKeys.flatMap { listeners[it].orEmpty() } - .distinct() - .forEach(Query.Listener::queryResultsChanged) - } - - override fun close() = worker.terminate() - - override fun newTransaction(): QueryResult = QueryResult.AsyncValue { - val enclosing = transaction - val transaction = Transaction(enclosing) - this.transaction = transaction - if (enclosing == null) { - worker.sendMessage(WorkerActions.beginTransaction) - } - - return@AsyncValue transaction - } - - override fun currentTransaction(): Transacter.Transaction? = transaction - - private inner class Transaction( - override val enclosingTransaction: Transacter.Transaction?, - ) : Transacter.Transaction() { - override fun endTransaction(successful: Boolean): QueryResult = QueryResult.AsyncValue { - if (enclosingTransaction == null) { - if (successful) { - worker.sendMessage(WorkerActions.endTransaction) - } else { - worker.sendMessage(WorkerActions.rollbackTransaction) - } - } - transaction = enclosingTransaction - } - } - - private suspend fun Worker.sendMessage(action: WorkerAction, message: RequestBuilder.() -> Unit = {}): WorkerResponse = suspendCancellableCoroutine { continuation -> - val id = messageCounter++ - var messageListener: ((Event) -> Unit)? = null - messageListener = { event: Event -> - val message = event.unsafeCast() - val data = message.data?.unsafeCast() - if (data == null) { - continuation.resumeWithException(WebWorkerException("Message ${message.type} data was null or not a WorkerResponse")) - } else { - if (data.id == id) { - removeEventListener("message", messageListener) - if (data.error != null) { - continuation.resumeWithException(WebWorkerException(jsonStringify(value = data.error, replacer = listOf("message", "arguments", "type", "name").toJsArray { it.toJsString() }))) - } else { - continuation.resume(data) - } - } - } - } - var errorListener: ((Event) -> Unit)? = null - errorListener = { event -> - removeEventListener("error", errorListener) - continuation.resumeWithException(WebWorkerException(jsonStringify(event, listOf("message", "arguments", "type", "name").toJsArray { it.toJsString() }) + objectEntries(event))) - } - addEventListener("message", messageListener) - addEventListener("error", errorListener) - - val messageObject = instantiateObject().apply { - this.unsafeCast().message() - this.id = id - this.action = action - } - - postMessage(messageObject) - - continuation.invokeOnCancellation { - removeEventListener("message", messageListener) - removeEventListener("error", errorListener) - } - } - - private fun checkWorkerResults(results: WorkerResult?): JsArray> { - checkNotNull(results) { "The worker result was null " } - val values = results.values - check(values != null && isArray(values)) { "The worker result values were not an array" } - return values - } -} - -private external interface RequestBuilder : JsAny { - var sql: String? - var params: JsArray? -} - -internal class JsWorkerSqlCursor(private val values: JsArray>) : SqlCursor { - private var currentRow = -1 - - override fun next(): QueryResult.Value = QueryResult.Value(++currentRow < values.length) - - override fun getString(index: Int): String? { - val currentRow = values[currentRow] ?: return null - return currentRow[index]?.unsafeCast()?.toString() - } - - override fun getLong(index: Int): Long? { - return getColumn(index) { - it.unsafeCast().toDouble().toLong() - } - } - - override fun getBytes(index: Int): ByteArray? { - return getColumn(index) { - val array = it.unsafeCast() - // TODO: avoid copying somehow? - ByteArray(array.length) { array[it] } - } - } - - override fun getDouble(index: Int): Double? { - return getColumn(index) { it.unsafeCast().toDouble() } - } - - override fun getBoolean(index: Int): Boolean? { - return getColumn(index) { it.unsafeCast().toBoolean() } - } - - private inline fun getColumn(index: Int, transformer: (JsAny) -> T): T? { - val column = values[currentRow]?.get(index) ?: return null - return transformer(column) - } -} - -internal class JsWorkerSqlPreparedStatement : SqlPreparedStatement { - - val parameters = JsArray() - - override fun bindBytes(index: Int, bytes: ByteArray?) { - parameters.add(bytes?.toUint8Array()) - } - - override fun bindLong(index: Int, long: Long?) { - // We convert Long to Double because Kotlin's Double is mapped to JS number - // whereas Kotlin's Long is implemented as a JS object - parameters.add(long?.toDouble()?.toJsNumber()) - } - - override fun bindDouble(index: Int, double: Double?) { - parameters.add(double?.toJsNumber()) - } - - override fun bindString(index: Int, string: String?) { - parameters.add(string?.toJsString()) - } - - override fun bindBoolean(index: Int, boolean: Boolean?) { - parameters.add(boolean?.toJsBoolean()) - } -} diff --git a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/WorkerWrapper.kt b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/WorkerWrapper.kt new file mode 100644 index 00000000000..1bce3026895 --- /dev/null +++ b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/WorkerWrapper.kt @@ -0,0 +1,94 @@ +package app.cash.sqldelight.driver.worker + +import app.cash.sqldelight.driver.worker.api.WasmWorkerRequest +import app.cash.sqldelight.driver.worker.api.WasmWorkerResponse +import app.cash.sqldelight.driver.worker.api.WorkerResultWithRowCount +import app.cash.sqldelight.driver.worker.api.WorkerWrapperRequest +import app.cash.sqldelight.driver.worker.expected.Worker +import app.cash.sqldelight.driver.worker.util.instantiateObject +import app.cash.sqldelight.driver.worker.util.jsonStringify +import app.cash.sqldelight.driver.worker.util.objectEntries +import app.cash.sqldelight.driver.worker.util.toJsArray +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlinx.coroutines.suspendCancellableCoroutine +import org.w3c.dom.MessageEvent +import org.w3c.dom.events.Event + +internal actual class WorkerWrapper actual constructor( + private val worker: Worker, +) { + actual suspend fun execute( + request: WorkerWrapperRequest, + ): WorkerResultWithRowCount = suspendCancellableCoroutine { continuation -> + var messageListener: ((Event) -> Unit)? = null + messageListener = { event: Event -> + val message = event.unsafeCast() + val data = message.data?.unsafeCast() + if (data == null) { + continuation.resumeWithException(WebWorkerException("Message ${message.type} data was null or not a WorkerResponse")) + } else { + if (data.id == request.id) { + worker.removeEventListener("message", messageListener) + if (data.error != null) { + continuation.resumeWithException( + WebWorkerException( + jsonStringify( + value = data.error, + replacer = listOf( + "message", + "arguments", + "type", + "name", + ).toJsArray { it.toJsString() }, + ), + ), + ) + } else { + continuation.resume( + WasmWorkerResultWithRowCount(data), + ) + } + } + } + } + var errorListener: ((Event) -> Unit)? = null + errorListener = { event -> + worker.removeEventListener("error", errorListener) + continuation.resumeWithException( + WebWorkerException( + jsonStringify( + event, + listOf( + "message", + "arguments", + "type", + "name", + ).toJsArray { it.toJsString() }, + ) + objectEntries(event), + ), + ) + } + worker.addEventListener("message", messageListener) + worker.addEventListener("error", errorListener) + + val messageObject = instantiateObject().apply { + this.id = request.id + this.action = request.action + this.sql = request.sql + this.params = request.statement?.parameters + } + + worker.postMessage(messageObject) + + continuation.invokeOnCancellation { + worker.removeEventListener("message", messageListener) + worker.removeEventListener("error", errorListener) + } + } + + actual fun terminate() { + worker.terminate() + } + +} diff --git a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerRequest.kt b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WasmWorkerRequest.kt similarity index 84% rename from drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerRequest.kt rename to drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WasmWorkerRequest.kt index f84d0c25316..09309b01b01 100644 --- a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerRequest.kt +++ b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WasmWorkerRequest.kt @@ -3,10 +3,10 @@ package app.cash.sqldelight.driver.worker.api /** * Messages sent by the SQLDelight driver to the worker. */ -internal external interface WorkerRequest : JsAny { +internal external interface WasmWorkerRequest : JsAny { /** * A unique identifier used to identify responses to this message - * @see WorkerResponse.id + * @see WasmWorkerResponse.id */ var id: Int diff --git a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResponse.kt b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WasmWorkerResponse.kt similarity index 90% rename from drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResponse.kt rename to drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WasmWorkerResponse.kt index 1af149d3d89..23b46ebd3ac 100644 --- a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResponse.kt +++ b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WasmWorkerResponse.kt @@ -3,7 +3,7 @@ package app.cash.sqldelight.driver.worker.api /** * Data returned by the worker after posting a message. */ -internal external interface WorkerResponse : JsAny { +internal external interface WasmWorkerResponse : JsAny { /** * An error returned by the worker, could be undefined. */ diff --git a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResult.kt b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResult.kt index 9de6c945cbc..deff99b8087 100644 --- a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResult.kt +++ b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResult.kt @@ -3,7 +3,7 @@ package app.cash.sqldelight.driver.worker.api /** * The results of a SQL operation in the worker. */ -internal external interface WorkerResult : JsAny { +internal actual external interface WorkerResult : JsAny { /** * The "table" of values in the result, as rows of columns. * i.e. `values[row][col]` diff --git a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/expected/CheckWorkerResults.kt b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/expected/CheckWorkerResults.kt new file mode 100644 index 00000000000..c792d15d14b --- /dev/null +++ b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/expected/CheckWorkerResults.kt @@ -0,0 +1,11 @@ +package app.cash.sqldelight.driver.worker.expected + +import app.cash.sqldelight.driver.worker.api.WorkerResult +import app.cash.sqldelight.driver.worker.util.isArray + +internal actual fun checkWorkerResults(results: WorkerResult?): WorkerResult { + checkNotNull(results) { "The worker result was null " } + val values = results.values + check(values != null && isArray(values)) { "The worker result values were not an array" } + return results +} diff --git a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlCursor.kt b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlCursor.kt new file mode 100644 index 00000000000..5e35d7db6cd --- /dev/null +++ b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlCursor.kt @@ -0,0 +1,50 @@ +package app.cash.sqldelight.driver.worker.expected + +import app.cash.sqldelight.db.QueryResult +import app.cash.sqldelight.db.SqlCursor +import app.cash.sqldelight.driver.worker.api.WorkerResult +import org.khronos.webgl.Uint8Array +import org.khronos.webgl.get + +internal actual class JsWorkerSqlCursor actual constructor( + private val result: WorkerResult, +) : SqlCursor { + private var currentRow = -1 + private val values: JsArray> by lazy { + result.values!! + } + + override fun next(): QueryResult.Value = QueryResult.Value(++currentRow < values.length) + + override fun getString(index: Int): String? { + val currentRow = values[currentRow] ?: return null + return currentRow[index]?.unsafeCast()?.toString() + } + + override fun getLong(index: Int): Long? { + return getColumn(index) { + it.unsafeCast().toDouble().toLong() + } + } + + override fun getBytes(index: Int): ByteArray? { + return getColumn(index) { + val array = it.unsafeCast() + // TODO: avoid copying somehow? + ByteArray(array.length) { array[it] } + } + } + + override fun getDouble(index: Int): Double? { + return getColumn(index) { it.unsafeCast().toDouble() } + } + + override fun getBoolean(index: Int): Boolean? { + return getColumn(index) { it.unsafeCast().toBoolean() } + } + + private inline fun getColumn(index: Int, transformer: (JsAny) -> T): T? { + val column = values[currentRow]?.get(index) ?: return null + return transformer(column) + } +} diff --git a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlPreparedStatement.kt b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlPreparedStatement.kt new file mode 100644 index 00000000000..b6274e0726a --- /dev/null +++ b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlPreparedStatement.kt @@ -0,0 +1,32 @@ +package app.cash.sqldelight.driver.worker.expected + +import app.cash.sqldelight.db.SqlPreparedStatement +import app.cash.sqldelight.driver.worker.util.add +import app.cash.sqldelight.driver.worker.util.toUint8Array + +internal actual class JsWorkerSqlPreparedStatement : SqlPreparedStatement { + + val parameters = JsArray() + + override fun bindBytes(index: Int, bytes: ByteArray?) { + parameters.add(bytes?.toUint8Array()) + } + + override fun bindLong(index: Int, long: Long?) { + // We convert Long to Double because Kotlin's Double is mapped to JS number + // whereas Kotlin's Long is implemented as a JS object + parameters.add(long?.toDouble()?.toJsNumber()) + } + + override fun bindDouble(index: Int, double: Double?) { + parameters.add(double?.toJsNumber()) + } + + override fun bindString(index: Int, string: String?) { + parameters.add(string?.toJsString()) + } + + override fun bindBoolean(index: Int, boolean: Boolean?) { + parameters.add(boolean?.toJsBoolean()) + } +} diff --git a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/expected/Worker.kt b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/expected/Worker.kt new file mode 100644 index 00000000000..21d6b7cde76 --- /dev/null +++ b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/expected/Worker.kt @@ -0,0 +1,3 @@ +package app.cash.sqldelight.driver.worker.expected + +internal actual typealias Worker = org.w3c.dom.Worker diff --git a/extensions/coroutines-extensions/src/jsCommonTest/kotlin/app/cash/sqldelight/coroutines/TestDriver.kt b/extensions/coroutines-extensions/src/jsCommonTest/kotlin/app/cash/sqldelight/coroutines/TestDriver.kt index 3b24a929b0a..531cf0324f7 100644 --- a/extensions/coroutines-extensions/src/jsCommonTest/kotlin/app/cash/sqldelight/coroutines/TestDriver.kt +++ b/extensions/coroutines-extensions/src/jsCommonTest/kotlin/app/cash/sqldelight/coroutines/TestDriver.kt @@ -17,7 +17,7 @@ package app.cash.sqldelight.coroutines import app.cash.sqldelight.db.SqlDriver -import app.cash.sqldelight.driver.worker.createWebWorkerDriver +import app.cash.sqldelight.driver.worker.createDefaultWebWorkerDriver actual suspend fun testDriver(): SqlDriver = - createWebWorkerDriver() + createDefaultWebWorkerDriver() From 421db812be749866fe9516b21302b9556e11e9ad Mon Sep 17 00:00:00 2001 From: Ilya Gulya Date: Sun, 26 May 2024 22:04:26 +0500 Subject: [PATCH 08/13] remove unnecessary `jsCommon` group from sample-web. upgrade kotlinx-datetime to 0.6.0. remove expect/actual classes warning --- sample-web/build.gradle | 26 +++++++++---------- .../resources/index.html | 0 .../example/sqldelight/hockey/HockeyDbTest.kt | 0 .../example/sqldelight/hockey/TestHelper.kt | 0 4 files changed, 13 insertions(+), 13 deletions(-) rename sample-web/src/{jsCommonMain => commonMain}/resources/index.html (100%) rename sample-web/src/{jsTest => commonTest}/kotlin/com/example/sqldelight/hockey/HockeyDbTest.kt (100%) rename sample-web/src/{jsTest => commonTest}/kotlin/com/example/sqldelight/hockey/TestHelper.kt (100%) diff --git a/sample-web/build.gradle b/sample-web/build.gradle index b11ed4e6296..7c385e3879d 100644 --- a/sample-web/build.gradle +++ b/sample-web/build.gradle @@ -1,3 +1,6 @@ +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation +import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmInstallTask + plugins { alias(libs.plugins.kotlin.multiplatform) id("app.cash.sqldelight") @@ -25,22 +28,13 @@ kotlin { binaries.executable() useCommonJs() } - applyDefaultHierarchyTemplate { - it.common { - it.group("jsCommon") { - it.withJs() - it.withWasm() - } - } - } sourceSets { commonMain.dependencies { - implementation "org.jetbrains.kotlinx:kotlinx-datetime:0.5.0" + implementation "org.jetbrains.kotlinx:kotlinx-datetime:0.6.0" implementation "app.cash.sqldelight:primitive-adapters" implementation "org.jetbrains.kotlinx:kotlinx-html:0.11.0" - } - jsCommonMain.dependencies { + implementation "app.cash.sqldelight:web-worker-driver" implementation devNpm("copy-webpack-plugin", "9.1.0") implementation npm("sql.js", libs.versions.sqljs.get()) @@ -49,14 +43,20 @@ kotlin { implementation npm("@cashapp/sqldelight-sqljs-worker", sqljsWorker) } - jsCommonTest.dependencies { + commonTest.dependencies { implementation 'org.jetbrains.kotlin:kotlin-test' implementation libs.kotlin.coroutines.test } } } +tasks.withType(KotlinCompilation.class).configureEach { + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + } +} + // Workaround yarn concurrency issue - https://youtrack.jetbrains.com/issue/KT-43320 -tasks.withType(org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmInstallTask).configureEach { +tasks.withType(KotlinNpmInstallTask.class).configureEach { args.addAll(["--mutex", "file:${file("../build/.yarn-mutex")}".toString()]) } diff --git a/sample-web/src/jsCommonMain/resources/index.html b/sample-web/src/commonMain/resources/index.html similarity index 100% rename from sample-web/src/jsCommonMain/resources/index.html rename to sample-web/src/commonMain/resources/index.html diff --git a/sample-web/src/jsTest/kotlin/com/example/sqldelight/hockey/HockeyDbTest.kt b/sample-web/src/commonTest/kotlin/com/example/sqldelight/hockey/HockeyDbTest.kt similarity index 100% rename from sample-web/src/jsTest/kotlin/com/example/sqldelight/hockey/HockeyDbTest.kt rename to sample-web/src/commonTest/kotlin/com/example/sqldelight/hockey/HockeyDbTest.kt diff --git a/sample-web/src/jsTest/kotlin/com/example/sqldelight/hockey/TestHelper.kt b/sample-web/src/commonTest/kotlin/com/example/sqldelight/hockey/TestHelper.kt similarity index 100% rename from sample-web/src/jsTest/kotlin/com/example/sqldelight/hockey/TestHelper.kt rename to sample-web/src/commonTest/kotlin/com/example/sqldelight/hockey/TestHelper.kt From 2fd005d5a733bbe99a40248022bf69320996f7e4 Mon Sep 17 00:00:00 2001 From: Ilya Gulya Date: Sun, 26 May 2024 22:32:55 +0500 Subject: [PATCH 09/13] fix spotless violations --- .../app/cash/sqldelight/driver/worker/WebWorkerDriver.kt | 1 - .../kotlin/app/cash/sqldelight/driver/worker/WorkerWrapper.kt | 1 - .../worker/api/{WorkerResponse.kt => JsWorkerResponse.kt} | 0 .../app/cash/sqldelight/driver/worker/api/WorkerAction.kt | 2 +- .../driver/worker/expected/JsWorkerResultWithRowCount.kt | 4 ++-- .../kotlin/app/cash/sqldelight/driver/worker/WorkerWrapper.kt | 1 - .../app/cash/sqldelight/driver/worker/api/WorkerAction.kt | 2 +- .../sqldelight/driver/worker/expected/JsWorkerSqlCursor.kt | 2 +- 8 files changed, 5 insertions(+), 8 deletions(-) rename drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/{WorkerResponse.kt => JsWorkerResponse.kt} (100%) diff --git a/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/WebWorkerDriver.kt b/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/WebWorkerDriver.kt index 61d1d78487c..383ec3f2e45 100644 --- a/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/WebWorkerDriver.kt +++ b/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/WebWorkerDriver.kt @@ -137,5 +137,4 @@ class WebWorkerDriver(private val worker: Worker) : SqlDriver { ), ) } - } diff --git a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/WorkerWrapper.kt b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/WorkerWrapper.kt index 882ad1ba901..e1231940c59 100644 --- a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/WorkerWrapper.kt +++ b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/WorkerWrapper.kt @@ -73,7 +73,6 @@ internal actual class WorkerWrapper actual constructor( worker.removeEventListener("message", messageListener) worker.removeEventListener("error", errorListener) } - } } diff --git a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResponse.kt b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/JsWorkerResponse.kt similarity index 100% rename from drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerResponse.kt rename to drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/JsWorkerResponse.kt diff --git a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerAction.kt b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerAction.kt index 0e7d2ae94b7..aa233c8dd70 100644 --- a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerAction.kt +++ b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerAction.kt @@ -2,5 +2,5 @@ package app.cash.sqldelight.driver.worker.api internal actual sealed interface WorkerAction -@Suppress("NOTHING_TO_INLINE") +@Suppress("NOTHING_TO_INLINE", "FunctionName", "RedundantSuppression") internal actual inline fun WorkerAction(value: String) = value.unsafeCast() diff --git a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerResultWithRowCount.kt b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerResultWithRowCount.kt index 86ea9203600..97ceb526683 100644 --- a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerResultWithRowCount.kt +++ b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerResultWithRowCount.kt @@ -5,9 +5,9 @@ import app.cash.sqldelight.driver.worker.api.WorkerResult import app.cash.sqldelight.driver.worker.api.WorkerResultWithRowCount internal class JsWorkerResultWithRowCount( - private val data: JsWorkerResponse, + private val data: JsWorkerResponse, ) : - WorkerResultWithRowCount { + WorkerResultWithRowCount { override val rowCount: Long by lazy { when { data.results.values.isEmpty() -> 0L diff --git a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/WorkerWrapper.kt b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/WorkerWrapper.kt index 1bce3026895..fd687cf82a0 100644 --- a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/WorkerWrapper.kt +++ b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/WorkerWrapper.kt @@ -90,5 +90,4 @@ internal actual class WorkerWrapper actual constructor( actual fun terminate() { worker.terminate() } - } diff --git a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerAction.kt b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerAction.kt index 78e934c2e9d..0e92d171f7b 100644 --- a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerAction.kt +++ b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/api/WorkerAction.kt @@ -3,5 +3,5 @@ package app.cash.sqldelight.driver.worker.api @Suppress("ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_SUPERTYPES_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING") internal actual sealed external interface WorkerAction : JsAny -@Suppress("NOTHING_TO_INLINE", "FunctionName") +@Suppress("NOTHING_TO_INLINE", "FunctionName", "RedundantSuppression") internal actual inline fun WorkerAction(value: String) = value.toJsString().unsafeCast() diff --git a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlCursor.kt b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlCursor.kt index 5e35d7db6cd..8faffb1226f 100644 --- a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlCursor.kt +++ b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlCursor.kt @@ -7,7 +7,7 @@ import org.khronos.webgl.Uint8Array import org.khronos.webgl.get internal actual class JsWorkerSqlCursor actual constructor( - private val result: WorkerResult, + private val result: WorkerResult, ) : SqlCursor { private var currentRow = -1 private val values: JsArray> by lazy { From 9c09c3e32b6e979676a78d78e31ceffb240e0a87 Mon Sep 17 00:00:00 2001 From: Ilya Gulya Date: Fri, 26 Jul 2024 10:58:35 +0500 Subject: [PATCH 10/13] fix spotless violations --- .../driver/worker/expected/JsWorkerResultWithRowCount.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerResultWithRowCount.kt b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerResultWithRowCount.kt index 97ceb526683..0b4b49ea3e0 100644 --- a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerResultWithRowCount.kt +++ b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerResultWithRowCount.kt @@ -6,8 +6,7 @@ import app.cash.sqldelight.driver.worker.api.WorkerResultWithRowCount internal class JsWorkerResultWithRowCount( private val data: JsWorkerResponse, -) : - WorkerResultWithRowCount { +) : WorkerResultWithRowCount { override val rowCount: Long by lazy { when { data.results.values.isEmpty() -> 0L From 6f48c6eba72a76cb9e3d21e1c5f0fb0f552232a5 Mon Sep 17 00:00:00 2001 From: Ilya Gulya Date: Tue, 17 Sep 2024 21:29:52 +0500 Subject: [PATCH 11/13] fix multiplatform conventions --- .../multiplatform/MultiplatformConventions.kt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/buildLogic/multiplatform-convention/src/main/kotlin/app/cash/sqldelight/multiplatform/MultiplatformConventions.kt b/buildLogic/multiplatform-convention/src/main/kotlin/app/cash/sqldelight/multiplatform/MultiplatformConventions.kt index 46429b03a41..e1656189903 100644 --- a/buildLogic/multiplatform-convention/src/main/kotlin/app/cash/sqldelight/multiplatform/MultiplatformConventions.kt +++ b/buildLogic/multiplatform-convention/src/main/kotlin/app/cash/sqldelight/multiplatform/MultiplatformConventions.kt @@ -3,18 +3,21 @@ package app.cash.sqldelight.multiplatform import org.gradle.api.Plugin import org.gradle.api.Project import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.dsl.JsModuleKind import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.dsl.kotlinExtension -import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl import org.jetbrains.kotlin.konan.target.HostManager +@OptIn( + ExperimentalKotlinGradlePluginApi::class, + ExperimentalWasmDsl::class, +) class MultiplatformConventions : Plugin { override fun apply(project: Project) { project.plugins.apply("org.jetbrains.kotlin.multiplatform") (project.kotlinExtension as KotlinMultiplatformExtension).apply { - @OptIn(ExperimentalKotlinGradlePluginApi::class) compilerOptions { this.freeCompilerArgs.addAll( "-Xexpect-actual-classes", @@ -23,7 +26,6 @@ class MultiplatformConventions : Plugin { jvm() - @OptIn(ExperimentalWasmDsl::class) listOf(js(), wasmJs()).forEach { it.browser { testTask { @@ -32,17 +34,16 @@ class MultiplatformConventions : Plugin { } } } - compilerOptions { + it.compilerOptions { moduleKind.set(JsModuleKind.MODULE_UMD) } } - @OptIn(ExperimentalKotlinGradlePluginApi::class) applyDefaultHierarchyTemplate { common { group("jsCommon") { withJs() - withWasm() + withWasmJs() } } } From db938027d3fbad680ac0f6350443119faf6b7733 Mon Sep 17 00:00:00 2001 From: Ilya Gulya Date: Sat, 26 Oct 2024 19:40:18 +0500 Subject: [PATCH 12/13] expect overrides of SqlCursor and SqlPreparedStatement methods --- drivers/web-worker-driver/build.gradle | 10 ++++------ .../driver/worker/expected/JsWorkerSqlCursor.kt | 15 ++++++++++++++- .../expected/JsWorkerSqlPreparedStatement.kt | 12 +++++++++++- .../driver/worker/expected/JsWorkerSqlCursor.kt | 12 ++++++------ .../expected/JsWorkerSqlPreparedStatement.kt | 10 +++++----- .../driver/worker/expected/JsWorkerSqlCursor.kt | 12 ++++++------ .../expected/JsWorkerSqlPreparedStatement.kt | 10 +++++----- 7 files changed, 51 insertions(+), 30 deletions(-) diff --git a/drivers/web-worker-driver/build.gradle b/drivers/web-worker-driver/build.gradle index 13bc3f7b618..4677a3b6a76 100644 --- a/drivers/web-worker-driver/build.gradle +++ b/drivers/web-worker-driver/build.gradle @@ -23,6 +23,10 @@ kotlin { } } + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + } + sourceSets { commonMain.dependencies { api projects.runtime @@ -40,12 +44,6 @@ kotlin { } } -tasks.withType(KotlinCompilation.class).configureEach { - compilerOptions { - freeCompilerArgs.add("-Xexpect-actual-classes") - } -} - apply from: "$rootDir/gradle/gradle-mvn-push.gradle" tasks.named("dokkaHtmlMultiModule") { diff --git a/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlCursor.kt b/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlCursor.kt index f3df38bd2cb..6d1389f052f 100644 --- a/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlCursor.kt +++ b/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlCursor.kt @@ -1,6 +1,19 @@ package app.cash.sqldelight.driver.worker.expected +import app.cash.sqldelight.db.QueryResult import app.cash.sqldelight.db.SqlCursor import app.cash.sqldelight.driver.worker.api.WorkerResult -internal expect class JsWorkerSqlCursor(result: WorkerResult) : SqlCursor +internal expect class JsWorkerSqlCursor(result: WorkerResult) : SqlCursor { + override fun next(): QueryResult + + override fun getString(index: Int): String? + + override fun getLong(index: Int): Long? + + override fun getBytes(index: Int): ByteArray? + + override fun getDouble(index: Int): Double? + + override fun getBoolean(index: Int): Boolean? +} diff --git a/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlPreparedStatement.kt b/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlPreparedStatement.kt index 82eb669ed32..250c17ea857 100644 --- a/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlPreparedStatement.kt +++ b/drivers/web-worker-driver/src/commonMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlPreparedStatement.kt @@ -2,4 +2,14 @@ package app.cash.sqldelight.driver.worker.expected import app.cash.sqldelight.db.SqlPreparedStatement -internal expect class JsWorkerSqlPreparedStatement() : SqlPreparedStatement +internal expect class JsWorkerSqlPreparedStatement() : SqlPreparedStatement { + override fun bindBytes(index: Int, bytes: ByteArray?) + + override fun bindLong(index: Int, long: Long?) + + override fun bindDouble(index: Int, double: Double?) + + override fun bindString(index: Int, string: String?) + + override fun bindBoolean(index: Int, boolean: Boolean?) +} diff --git a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlCursor.kt b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlCursor.kt index 06da3ef1192..0337beb0c91 100644 --- a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlCursor.kt +++ b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlCursor.kt @@ -10,16 +10,16 @@ internal actual class JsWorkerSqlCursor actual constructor(result: WorkerResult) private val values: Array> = result.values private var currentRow = -1 - override fun next(): QueryResult.Value = QueryResult.Value(++currentRow < values.size) + actual override fun next(): QueryResult = QueryResult.Value(++currentRow < values.size) - override fun getString(index: Int): String? = values[currentRow][index].unsafeCast() + actual override fun getString(index: Int): String? = values[currentRow][index].unsafeCast() - override fun getLong(index: Int): Long? = (values[currentRow][index] as? Double)?.toLong() + actual override fun getLong(index: Int): Long? = (values[currentRow][index] as? Double)?.toLong() - override fun getBytes(index: Int): ByteArray? = + actual override fun getBytes(index: Int): ByteArray? = (values[currentRow][index] as? Uint8Array)?.let { Int8Array(it.buffer).unsafeCast() } - override fun getDouble(index: Int): Double? = values[currentRow][index].unsafeCast() + actual override fun getDouble(index: Int): Double? = values[currentRow][index].unsafeCast() - override fun getBoolean(index: Int): Boolean? = values[currentRow][index].unsafeCast() + actual override fun getBoolean(index: Int): Boolean? = values[currentRow][index].unsafeCast() } diff --git a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlPreparedStatement.kt b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlPreparedStatement.kt index 4397a1356f2..8375d357b09 100644 --- a/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlPreparedStatement.kt +++ b/drivers/web-worker-driver/src/jsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlPreparedStatement.kt @@ -6,25 +6,25 @@ internal actual class JsWorkerSqlPreparedStatement : SqlPreparedStatement { val parameters = mutableListOf() - override fun bindBytes(index: Int, bytes: ByteArray?) { + actual override fun bindBytes(index: Int, bytes: ByteArray?) { parameters.add(bytes) } - override fun bindLong(index: Int, long: Long?) { + actual override fun bindLong(index: Int, long: Long?) { // We convert Long to Double because Kotlin's Double is mapped to JS number // whereas Kotlin's Long is implemented as a JS object parameters.add(long?.toDouble()) } - override fun bindDouble(index: Int, double: Double?) { + actual override fun bindDouble(index: Int, double: Double?) { parameters.add(double) } - override fun bindString(index: Int, string: String?) { + actual override fun bindString(index: Int, string: String?) { parameters.add(string) } - override fun bindBoolean(index: Int, boolean: Boolean?) { + actual override fun bindBoolean(index: Int, boolean: Boolean?) { parameters.add(boolean) } } diff --git a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlCursor.kt b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlCursor.kt index 8faffb1226f..7effa2361a7 100644 --- a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlCursor.kt +++ b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlCursor.kt @@ -14,20 +14,20 @@ internal actual class JsWorkerSqlCursor actual constructor( result.values!! } - override fun next(): QueryResult.Value = QueryResult.Value(++currentRow < values.length) + actual override fun next(): QueryResult = QueryResult.Value(++currentRow < values.length) - override fun getString(index: Int): String? { + actual override fun getString(index: Int): String? { val currentRow = values[currentRow] ?: return null return currentRow[index]?.unsafeCast()?.toString() } - override fun getLong(index: Int): Long? { + actual override fun getLong(index: Int): Long? { return getColumn(index) { it.unsafeCast().toDouble().toLong() } } - override fun getBytes(index: Int): ByteArray? { + actual override fun getBytes(index: Int): ByteArray? { return getColumn(index) { val array = it.unsafeCast() // TODO: avoid copying somehow? @@ -35,11 +35,11 @@ internal actual class JsWorkerSqlCursor actual constructor( } } - override fun getDouble(index: Int): Double? { + actual override fun getDouble(index: Int): Double? { return getColumn(index) { it.unsafeCast().toDouble() } } - override fun getBoolean(index: Int): Boolean? { + actual override fun getBoolean(index: Int): Boolean? { return getColumn(index) { it.unsafeCast().toBoolean() } } diff --git a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlPreparedStatement.kt b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlPreparedStatement.kt index b6274e0726a..5505f963d12 100644 --- a/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlPreparedStatement.kt +++ b/drivers/web-worker-driver/src/wasmJsMain/kotlin/app/cash/sqldelight/driver/worker/expected/JsWorkerSqlPreparedStatement.kt @@ -8,25 +8,25 @@ internal actual class JsWorkerSqlPreparedStatement : SqlPreparedStatement { val parameters = JsArray() - override fun bindBytes(index: Int, bytes: ByteArray?) { + actual override fun bindBytes(index: Int, bytes: ByteArray?) { parameters.add(bytes?.toUint8Array()) } - override fun bindLong(index: Int, long: Long?) { + actual override fun bindLong(index: Int, long: Long?) { // We convert Long to Double because Kotlin's Double is mapped to JS number // whereas Kotlin's Long is implemented as a JS object parameters.add(long?.toDouble()?.toJsNumber()) } - override fun bindDouble(index: Int, double: Double?) { + actual override fun bindDouble(index: Int, double: Double?) { parameters.add(double?.toJsNumber()) } - override fun bindString(index: Int, string: String?) { + actual override fun bindString(index: Int, string: String?) { parameters.add(string?.toJsString()) } - override fun bindBoolean(index: Int, boolean: Boolean?) { + actual override fun bindBoolean(index: Int, boolean: Boolean?) { parameters.add(boolean?.toJsBoolean()) } } From d49102135c1ae912da16a33379c56a8fb581ef17 Mon Sep 17 00:00:00 2001 From: Ilya Gulya Date: Sat, 26 Oct 2024 19:44:20 +0500 Subject: [PATCH 13/13] Upgrade kotlinx-datetime to 0.6.1. Fix README for sample-web --- sample-web/README.md | 12 +- sample-web/build.gradle | 12 +- sample-web/kotlin-js-store/yarn.lock | 622 +++++++++------------------ 3 files changed, 221 insertions(+), 425 deletions(-) diff --git a/sample-web/README.md b/sample-web/README.md index 39d0c36cb26..fbd575e75b0 100644 --- a/sample-web/README.md +++ b/sample-web/README.md @@ -1,9 +1,9 @@ # Web Sample App -This sample shows how to build a Kotlin/JS app with SQLDelight using the [web-worker-driver](https://sqldelight.github.io/sqldelight/latest/js_sqlite/). +This sample shows how to build a Kotlin/JS or Kotlin/WasmJS app with SQLDelight using the [web-worker-driver](https://sqldelight.github.io/sqldelight/latest/js_sqlite/). Unlike the mobile sample, this project uses SQLDelight's `generateAsync` mode to enable asynchronous execution with a SQLite database running in a Web Worker. -## Running the web sample +## Running the Kotlin/JS web sample Launch the sample by running the following comand: @@ -11,4 +11,12 @@ Launch the sample by running the following comand: ./gradlew :jsBrowserRun ```` +## Running the Kotlin/WasmJS web sample + +Launch the sample by running the following comand: + +````shell +./gradlew :wasmJsBrowserRun +```` + The sample will be available at `http://localhost:8080`. diff --git a/sample-web/build.gradle b/sample-web/build.gradle index 7c385e3879d..57ec4cbcf16 100644 --- a/sample-web/build.gradle +++ b/sample-web/build.gradle @@ -29,9 +29,13 @@ kotlin { useCommonJs() } + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + } + sourceSets { commonMain.dependencies { - implementation "org.jetbrains.kotlinx:kotlinx-datetime:0.6.0" + implementation "org.jetbrains.kotlinx:kotlinx-datetime:0.6.1" implementation "app.cash.sqldelight:primitive-adapters" implementation "org.jetbrains.kotlinx:kotlinx-html:0.11.0" @@ -50,12 +54,6 @@ kotlin { } } -tasks.withType(KotlinCompilation.class).configureEach { - compilerOptions { - freeCompilerArgs.add("-Xexpect-actual-classes") - } -} - // Workaround yarn concurrency issue - https://youtrack.jetbrains.com/issue/KT-43320 tasks.withType(KotlinNpmInstallTask.class).configureEach { args.addAll(["--mutex", "file:${file("../build/.yarn-mutex")}".toString()]) diff --git a/sample-web/kotlin-js-store/yarn.lock b/sample-web/kotlin-js-store/yarn.lock index 5de28a64d56..f1d251f87e5 100644 --- a/sample-web/kotlin-js-store/yarn.lock +++ b/sample-web/kotlin-js-store/yarn.lock @@ -15,18 +15,6 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== -"@isaacs/cliui@^8.0.2": - version "8.0.2" - resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" - integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== - dependencies: - string-width "^5.1.2" - string-width-cjs "npm:string-width@^4.2.0" - strip-ansi "^7.0.1" - strip-ansi-cjs "npm:strip-ansi@^6.0.1" - wrap-ansi "^8.1.0" - wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" - "@jridgewell/gen-mapping@^0.3.0": version "0.3.3" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" @@ -80,26 +68,6 @@ resolved "https://registry.yarnpkg.com/@js-joda/core/-/core-3.2.0.tgz#3e61e21b7b2b8a6be746df1335cf91d70db2a273" integrity sha512-PMqgJ0sw5B7FKb2d5bWYIoxjri+QlW/Pys7+Rw82jSH0QN3rB05jZ/VrrsUdh1w4+i2kw9JOejXGq/KhDOX7Kg== -"@jsonjoy.com/base64@^1.1.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jsonjoy.com/base64/-/base64-1.1.2.tgz#cf8ea9dcb849b81c95f14fc0aaa151c6b54d2578" - integrity sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA== - -"@jsonjoy.com/json-pack@^1.0.3": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pack/-/json-pack-1.0.4.tgz#ab59c642a2e5368e8bcfd815d817143d4f3035d0" - integrity sha512-aOcSN4MeAtFROysrbqG137b7gaDDSmVrl5mpo6sT/w+kcXpWnzhMjmY/Fh/sDx26NBxyIE7MB1seqLeCAzy9Sg== - dependencies: - "@jsonjoy.com/base64" "^1.1.1" - "@jsonjoy.com/util" "^1.1.2" - hyperdyperid "^1.2.0" - thingies "^1.20.0" - -"@jsonjoy.com/util@^1.1.2": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@jsonjoy.com/util/-/util-1.1.3.tgz#75b1c3cf21b70e665789d1ad3eabeff8b7fd1429" - integrity sha512-g//kkF4kOwUjemValCtOc/xiYzmwMRmWq3Bn+YnzOzuZLHq2PpMOxxIayN3cKbo7Ko2Np65t6D9H81IvXbXhqg== - "@leichtgewicht/ip-codec@^2.0.1": version "2.0.4" resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" @@ -126,11 +94,6 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@pkgjs/parseargs@^0.11.0": - version "0.11.0" - resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" - integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== - "@socket.io/component-emitter@~3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" @@ -144,14 +107,14 @@ "@types/connect" "*" "@types/node" "*" -"@types/bonjour@^3.5.13": +"@types/bonjour@^3.5.9": version "3.5.13" resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.13.tgz#adf90ce1a105e81dd1f9c61fdc5afda1bfb92956" integrity sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ== dependencies: "@types/node" "*" -"@types/connect-history-api-fallback@^1.5.4": +"@types/connect-history-api-fallback@^1.3.5": version "1.5.4" resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz#7de71645a103056b48ac3ce07b3520b819c1d5b3" integrity sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw== @@ -224,7 +187,7 @@ "@types/qs" "*" "@types/serve-static" "*" -"@types/express@^4.17.21": +"@types/express@^4.17.13": version "4.17.21" resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== @@ -283,10 +246,10 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== -"@types/retry@0.12.2": - version "0.12.2" - resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.2.tgz#ed279a64fa438bb69f2480eda44937912bb7480a" - integrity sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow== +"@types/retry@0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" + integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== "@types/send@*": version "0.17.2" @@ -296,7 +259,7 @@ "@types/mime" "^1" "@types/node" "*" -"@types/serve-index@^1.9.4": +"@types/serve-index@^1.9.1": version "1.9.4" resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.4.tgz#e6ae13d5053cb06ed36392110b4f9a49ac4ec898" integrity sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug== @@ -312,7 +275,7 @@ "@types/mime" "*" "@types/node" "*" -"@types/serve-static@^1.15.5": +"@types/serve-static@^1.13.10": version "1.15.7" resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.7.tgz#22174bbd74fb97fe303109738e9b5c2f3064f714" integrity sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw== @@ -321,17 +284,17 @@ "@types/node" "*" "@types/send" "*" -"@types/sockjs@^0.3.36": +"@types/sockjs@^0.3.33": version "0.3.36" resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.36.tgz#ce322cf07bcc119d4cbf7f88954f3a3bd0f67535" integrity sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q== dependencies: "@types/node" "*" -"@types/ws@^8.5.10": - version "8.5.10" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787" - integrity sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A== +"@types/ws@^8.5.5": + version "8.5.12" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.12.tgz#619475fe98f35ccca2a2f6c137702d85ec247b7e" + integrity sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ== dependencies: "@types/node" "*" @@ -489,10 +452,10 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: mime-types "~2.1.34" negotiator "0.6.3" -acorn-import-assertions@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" - integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== +acorn-import-attributes@^1.9.5: + version "1.9.5" + resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" + integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== acorn@^8.7.1, acorn@^8.8.2: version "8.10.0" @@ -538,10 +501,10 @@ ajv@^8.0.0, ajv@^8.9.0: require-from-string "^2.0.2" uri-js "^4.2.2" -ansi-colors@4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" - integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== +ansi-colors@^4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" + integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== ansi-html-community@^0.0.8: version "0.0.8" @@ -553,11 +516,6 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-regex@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" - integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== - ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" @@ -565,11 +523,6 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -ansi-styles@^6.1.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" - integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== - anymatch@~3.1.2: version "3.1.3" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" @@ -649,7 +602,7 @@ body-parser@^1.19.0: type-is "~1.6.18" unpipe "1.0.0" -bonjour-service@^1.2.1: +bonjour-service@^1.0.11: version "1.2.1" resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.2.1.tgz#eb41b3085183df3321da1264719fbada12478d02" integrity sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw== @@ -679,7 +632,7 @@ braces@^3.0.2, braces@~3.0.2: dependencies: fill-range "^7.0.1" -browser-stdout@1.3.1: +browser-stdout@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== @@ -699,13 +652,6 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -bundle-name@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/bundle-name/-/bundle-name-4.1.0.tgz#f3b96b34160d6431a19d7688135af7cfb8797889" - integrity sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q== - dependencies: - run-applescript "^7.0.0" - bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" @@ -742,7 +688,7 @@ chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chokidar@3.5.3, chokidar@^3.5.1: +chokidar@^3.5.1: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== @@ -757,7 +703,7 @@ chokidar@3.5.3, chokidar@^3.5.1: optionalDependencies: fsevents "~2.3.2" -chokidar@^3.6.0: +chokidar@^3.5.3: version "3.6.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== @@ -914,7 +860,7 @@ cors@~2.8.5: object-assign "^4" vary "^1" -cross-spawn@^7.0.0, cross-spawn@^7.0.3: +cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -940,31 +886,25 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@4.3.4, debug@^4.1.0, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: +debug@^4.1.0, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" +debug@^4.3.5: + version "4.3.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== + dependencies: + ms "^2.1.3" + decamelize@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== -default-browser-id@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/default-browser-id/-/default-browser-id-5.0.0.tgz#a1d98bf960c15082d8a3fa69e83150ccccc3af26" - integrity sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA== - -default-browser@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/default-browser/-/default-browser-5.2.1.tgz#7b7ba61204ff3e425b556869ae6d3e9d9f1712cf" - integrity sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg== - dependencies: - bundle-name "^4.1.0" - default-browser-id "^5.0.0" - default-gateway@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71" @@ -972,10 +912,10 @@ default-gateway@^6.0.3: dependencies: execa "^5.0.0" -define-lazy-prop@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" - integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== +define-lazy-prop@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" + integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== depd@2.0.0: version "2.0.0" @@ -1002,10 +942,10 @@ di@^0.0.1: resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c" integrity sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA== -diff@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" - integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== +diff@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" + integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== dir-glob@^3.0.1: version "3.0.1" @@ -1031,11 +971,6 @@ dom-serialize@^2.2.1: extend "^3.0.0" void-elements "^2.0.0" -eastasianwidth@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" - integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== - ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -1051,11 +986,6 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -emoji-regex@^9.2.2: - version "9.2.2" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" - integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== - encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -1082,10 +1012,10 @@ engine.io@~6.5.2: engine.io-parser "~5.2.1" ws "~8.11.0" -enhanced-resolve@^5.16.0: - version "5.16.1" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz#e8bc63d51b826d6f1cbc0a150ecb5a8b0c62e567" - integrity sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw== +enhanced-resolve@^5.17.0: + version "5.17.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15" + integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -1115,7 +1045,7 @@ escape-html@~1.0.3: resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== -escape-string-regexp@4.0.0: +escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== @@ -1290,14 +1220,6 @@ finalhandler@1.2.0: statuses "2.0.1" unpipe "~1.0.0" -find-up@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - find-up@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" @@ -1306,6 +1228,14 @@ find-up@^4.0.0: locate-path "^5.0.0" path-exists "^4.0.0" +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + flat@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" @@ -1321,14 +1251,6 @@ follow-redirects@^1.0.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== -foreground-child@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d" - integrity sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg== - dependencies: - cross-spawn "^7.0.0" - signal-exit "^4.0.1" - format-util@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/format-util/-/format-util-1.0.5.tgz#1ffb450c8a03e7bccffe40643180918cc297d271" @@ -1353,6 +1275,11 @@ fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" +fs-monkey@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.6.tgz#8ead082953e88d992cf3ff844faa907b26756da2" + integrity sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg== + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -1407,28 +1334,6 @@ glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" - integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^5.0.1" - once "^1.3.0" - -glob@^10.3.7: - version "10.3.14" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.14.tgz#36501f871d373fe197fc5794588d0aa71e69ff68" - integrity sha512-4fkAqu93xe9Mk7le9v0y3VrPDqLKHarNi2s4Pv7f2yOvfhWfhc7hRPHC/JyqMqb8B/Dt/eGS4n7ykwf3fOsl8g== - dependencies: - foreground-child "^3.1.0" - jackspeak "^2.3.6" - minimatch "^9.0.1" - minipass "^7.0.4" - path-scurry "^1.11.0" - glob@^7.1.3, glob@^7.1.7: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -1441,6 +1346,17 @@ glob@^7.1.3, glob@^7.1.7: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + globby@^11.0.3: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" @@ -1485,7 +1401,7 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" -he@1.2.0: +he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== @@ -1500,7 +1416,7 @@ hpack.js@^2.1.6: readable-stream "^2.0.1" wbuf "^1.1.0" -html-entities@^2.4.0: +html-entities@^2.3.2: version "2.5.2" resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.5.2.tgz#201a3cf95d3a15be7099521620d19dfb4f65359f" integrity sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA== @@ -1561,11 +1477,6 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== -hyperdyperid@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/hyperdyperid/-/hyperdyperid-1.2.0.tgz#59668d323ada92228d2a869d3e474d5a33b69e6b" - integrity sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A== - iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -1621,7 +1532,7 @@ ipaddr.js@1.9.1: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== -ipaddr.js@^2.1.0: +ipaddr.js@^2.0.1: version "2.2.0" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz#d33fa7bac284f4de7af949638c9d68157c6b92e8" integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA== @@ -1640,10 +1551,10 @@ is-core-module@^2.13.0: dependencies: has "^1.0.3" -is-docker@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200" - integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== +is-docker@^2.0.0, is-docker@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== is-extglob@^2.1.1: version "2.1.1" @@ -1662,18 +1573,6 @@ is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" -is-inside-container@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4" - integrity sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA== - dependencies: - is-docker "^3.0.0" - -is-network-error@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-network-error/-/is-network-error-1.1.0.tgz#d26a760e3770226d11c169052f266a4803d9c997" - integrity sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g== - is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -1706,12 +1605,12 @@ is-unicode-supported@^0.1.0: resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== -is-wsl@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-3.1.0.tgz#e1c657e39c10090afcbedec61720f6b924c3cbd2" - integrity sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw== +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== dependencies: - is-inside-container "^1.0.0" + is-docker "^2.0.0" isarray@~1.0.0: version "1.0.0" @@ -1733,15 +1632,6 @@ isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== -jackspeak@^2.3.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8" - integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ== - dependencies: - "@isaacs/cliui" "^8.0.2" - optionalDependencies: - "@pkgjs/parseargs" "^0.11.0" - jest-worker@^27.4.5: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" @@ -1751,7 +1641,7 @@ jest-worker@^27.4.5: merge-stream "^2.0.0" supports-color "^8.0.0" -js-yaml@4.1.0: +js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== @@ -1845,10 +1735,10 @@ kind-of@^6.0.2: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== -launch-editor@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.6.1.tgz#f259c9ef95cbc9425620bbbd14b468fcdb4ffe3c" - integrity sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw== +launch-editor@^2.6.0: + version "2.9.1" + resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.9.1.tgz#253f173bd441e342d4344b4dae58291abb425047" + integrity sha512-Gcnl4Bd+hRO9P9icCP/RVVT2o8SFlPXofuCxvA2SaZuH45whSvf5p8x5oih5ftLiVhEI4sp5xDY+R+b3zJBh5w== dependencies: picocolors "^1.0.0" shell-quote "^1.8.1" @@ -1877,7 +1767,7 @@ lodash@^4.17.15, lodash@^4.17.21: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -log-symbols@4.1.0: +log-symbols@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== @@ -1896,25 +1786,17 @@ log4js@^6.4.1: rfdc "^1.3.0" streamroller "^3.1.5" -lru-cache@^10.2.0: - version "10.2.2" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.2.tgz#48206bc114c1252940c41b25b41af5b545aca878" - integrity sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ== - media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== -memfs@^4.6.0: - version "4.9.2" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-4.9.2.tgz#42e7b48207268dad8c9c48ea5d4952c5d3840433" - integrity sha512-f16coDZlTG1jskq3mxarwB+fGRrd0uXWt+o1WIhRfOwbXQZqUDsTVxQBFK9JjRQHblg8eAG2JSbprDXKjc7ijQ== +memfs@^3.4.3: + version "3.6.0" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6" + integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ== dependencies: - "@jsonjoy.com/json-pack" "^1.0.3" - "@jsonjoy.com/util" "^1.1.2" - sonic-forest "^1.0.0" - tslib "^2.0.0" + fs-monkey "^1.0.4" merge-descriptors@1.0.1: version "1.0.1" @@ -1976,13 +1858,6 @@ minimalistic-assert@^1.0.0: resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimatch@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" - integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g== - dependencies: - brace-expansion "^2.0.1" - minimatch@^3.0.4, minimatch@^3.1.1: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -1990,14 +1865,14 @@ minimatch@^3.0.4, minimatch@^3.1.1: dependencies: brace-expansion "^1.1.7" -minimatch@^5.0.1: +minimatch@^5.0.1, minimatch@^5.1.6: version "5.1.6" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== dependencies: brace-expansion "^2.0.1" -minimatch@^9.0.1, minimatch@^9.0.3: +minimatch@^9.0.3: version "9.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51" integrity sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw== @@ -2009,11 +1884,6 @@ minimist@^1.2.3, minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.4: - version "7.1.1" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.1.tgz#f7f85aff59aa22f110b20e27692465cf3bf89481" - integrity sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA== - mkdirp@^0.5.5: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" @@ -2021,31 +1891,31 @@ mkdirp@^0.5.5: dependencies: minimist "^1.2.6" -mocha@10.3.0: - version "10.3.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.3.0.tgz#0e185c49e6dccf582035c05fa91084a4ff6e3fe9" - integrity sha512-uF2XJs+7xSLsrmIvn37i/wnc91nw7XjOQB8ccyx5aEgdnohr7n+rEiZP23WkCYHjilR6+EboEnbq/ZQDz4LSbg== - dependencies: - ansi-colors "4.1.1" - browser-stdout "1.3.1" - chokidar "3.5.3" - debug "4.3.4" - diff "5.0.0" - escape-string-regexp "4.0.0" - find-up "5.0.0" - glob "8.1.0" - he "1.2.0" - js-yaml "4.1.0" - log-symbols "4.1.0" - minimatch "5.0.1" - ms "2.1.3" - serialize-javascript "6.0.0" - strip-json-comments "3.1.1" - supports-color "8.1.1" - workerpool "6.2.1" - yargs "16.2.0" - yargs-parser "20.2.4" - yargs-unparser "2.0.0" +mocha@10.7.0: + version "10.7.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.7.0.tgz#9e5cbed8fa9b37537a25bd1f7fb4f6fc45458b9a" + integrity sha512-v8/rBWr2VO5YkspYINnvu81inSz2y3ODJrhO175/Exzor1RcEZZkizgE2A+w/CAXXoESS8Kys5E62dOHGHzULA== + dependencies: + ansi-colors "^4.1.3" + browser-stdout "^1.3.1" + chokidar "^3.5.3" + debug "^4.3.5" + diff "^5.2.0" + escape-string-regexp "^4.0.0" + find-up "^5.0.0" + glob "^8.1.0" + he "^1.2.0" + js-yaml "^4.1.0" + log-symbols "^4.1.0" + minimatch "^5.1.6" + ms "^2.1.3" + serialize-javascript "^6.0.2" + strip-json-comments "^3.1.1" + supports-color "^8.1.1" + workerpool "^6.5.1" + yargs "^16.2.0" + yargs-parser "^20.2.9" + yargs-unparser "^2.0.0" ms@2.0.0: version "2.0.0" @@ -2057,7 +1927,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3: +ms@2.1.3, ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -2117,7 +1987,7 @@ obuf@^1.0.0, obuf@^1.1.2: resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== -on-finished@2.4.1, on-finished@^2.4.1: +on-finished@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== @@ -2150,15 +2020,14 @@ onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" -open@^10.0.3: - version "10.1.0" - resolved "https://registry.yarnpkg.com/open/-/open-10.1.0.tgz#a7795e6e5d519abe4286d9937bb24b51122598e1" - integrity sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw== +open@^8.0.9: + version "8.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9" + integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ== dependencies: - default-browser "^5.2.1" - define-lazy-prop "^3.0.0" - is-inside-container "^1.0.0" - is-wsl "^3.1.0" + define-lazy-prop "^2.0.0" + is-docker "^2.1.1" + is-wsl "^2.2.0" p-limit@^2.2.0: version "2.3.0" @@ -2188,13 +2057,12 @@ p-locate@^5.0.0: dependencies: p-limit "^3.0.2" -p-retry@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-6.2.0.tgz#8d6df01af298750009691ce2f9b3ad2d5968f3bd" - integrity sha512-JA6nkq6hKyWLLasXQXUrO4z8BUZGUt/LjlJxx8Gb2+2ntodU/SS63YZ8b0LUTbQ8ZB9iwOfhEPhg4ykKnn2KsA== +p-retry@^4.5.0: + version "4.6.2" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16" + integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== dependencies: - "@types/retry" "0.12.2" - is-network-error "^1.0.0" + "@types/retry" "0.12.0" retry "^0.13.1" p-try@^2.0.0: @@ -2227,14 +2095,6 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-scurry@^1.11.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.0.tgz#332d64e9726bf667fb348e5a1c71005c09ad741a" - integrity sha512-LNHTaVkzaYaLGlO+0u3rQTz7QrHTFOuKyba9JMTQutkmtNew8dw8wOD7mTU/5fCPZzCWpfW0XnQKzY61P0aTaw== - dependencies: - lru-cache "^10.2.0" - minipass "^5.0.0 || ^6.0.2 || ^7.0.0" - path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" @@ -2423,18 +2283,6 @@ rimraf@^3.0.0, rimraf@^3.0.2: dependencies: glob "^7.1.3" -rimraf@^5.0.5: - version "5.0.5" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.5.tgz#9be65d2d6e683447d2e9013da2bf451139a61ccf" - integrity sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A== - dependencies: - glob "^10.3.7" - -run-applescript@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-7.0.0.tgz#e5a553c2bffd620e169d276c1cd8f1b64778fbeb" - integrity sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A== - run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -2466,7 +2314,7 @@ schema-utils@^3.1.1, schema-utils@^3.2.0: ajv "^6.12.5" ajv-keywords "^3.5.2" -schema-utils@^4.0.0, schema-utils@^4.2.0: +schema-utils@^4.0.0: version "4.2.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.2.0.tgz#70d7c93e153a273a805801882ebd3bff20d89c8b" integrity sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw== @@ -2481,7 +2329,7 @@ select-hose@^2.0.0: resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== -selfsigned@^2.4.1: +selfsigned@^2.1.1: version "2.4.1" resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.4.1.tgz#560d90565442a3ed35b674034cec4e95dceb4ae0" integrity sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q== @@ -2508,13 +2356,6 @@ send@0.18.0: range-parser "~1.2.1" statuses "2.0.1" -serialize-javascript@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" - integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== - dependencies: - randombytes "^2.1.0" - serialize-javascript@^6.0.0, serialize-javascript@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c" @@ -2522,6 +2363,13 @@ serialize-javascript@^6.0.0, serialize-javascript@^6.0.1: dependencies: randombytes "^2.1.0" +serialize-javascript@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" + integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== + dependencies: + randombytes "^2.1.0" + serve-index@^1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" @@ -2593,11 +2441,6 @@ signal-exit@^3.0.3: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== -signal-exit@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" - integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== - slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -2640,13 +2483,6 @@ sockjs@^0.3.24: uuid "^8.3.2" websocket-driver "^0.7.4" -sonic-forest@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sonic-forest/-/sonic-forest-1.0.3.tgz#81363af60017daba39b794fce24627dc412563cb" - integrity sha512-dtwajos6IWMEWXdEbW1IkEkyL2gztCAgDplRIX+OT5aRKnEd5e7r7YCxRgXZdhRP1FBdOBf8axeTPhzDv8T4wQ== - dependencies: - tree-dump "^1.0.0" - source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" @@ -2720,7 +2556,7 @@ streamroller@^3.1.5: debug "^4.3.4" fs-extra "^8.1.0" -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0: +string-width@^4.1.0, string-width@^4.2.0: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -2729,15 +2565,6 @@ streamroller@^3.1.5: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^5.0.1, string-width@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" - integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== - dependencies: - eastasianwidth "^0.2.0" - emoji-regex "^9.2.2" - strip-ansi "^7.0.1" - string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -2752,37 +2579,23 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: ansi-regex "^5.0.1" -strip-ansi@^7.0.1: - version "7.1.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" - integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== - dependencies: - ansi-regex "^6.0.1" - strip-final-newline@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== -strip-json-comments@3.1.1: +strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -supports-color@8.1.1, supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" @@ -2790,6 +2603,13 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +supports-color@^8.0.0, supports-color@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" @@ -2821,11 +2641,6 @@ terser@^5.26.0: commander "^2.20.0" source-map-support "~0.5.20" -thingies@^1.20.0: - version "1.21.0" - resolved "https://registry.yarnpkg.com/thingies/-/thingies-1.21.0.tgz#e80fbe58fd6fdaaab8fad9b67bd0a5c943c445c1" - integrity sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g== - thunky@^1.0.2: version "1.1.0" resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" @@ -2850,16 +2665,6 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== -tree-dump@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/tree-dump/-/tree-dump-1.0.1.tgz#b448758da7495580e6b7830d6b7834fca4c45b96" - integrity sha512-WCkcRBVPSlHHq1dc/px9iOfqklvzCbdRwvlNfxGZsrHqf6aZttfPrd7DJTt6oR10dwUfpFFQeVTkPbBIZxX/YA== - -tslib@^2.0.0: - version "2.6.2" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" - integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== - type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -2868,10 +2673,10 @@ type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" -typescript@5.4.3: - version "5.4.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.3.tgz#5c6fedd4c87bee01cd7a528a30145521f8e0feff" - integrity sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg== +typescript@5.5.4: + version "5.5.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba" + integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== ua-parser-js@^0.7.30: version "0.7.36" @@ -2962,53 +2767,52 @@ webpack-cli@5.1.4: rechoir "^0.8.0" webpack-merge "^5.7.3" -webpack-dev-middleware@^7.1.0: - version "7.2.1" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-7.2.1.tgz#2af00538b6e4eda05f5afdd5d711dbebc05958f7" - integrity sha512-hRLz+jPQXo999Nx9fXVdKlg/aehsw1ajA9skAneGmT03xwmyuhvF93p6HUKKbWhXdcERtGTzUCtIQr+2IQegrA== +webpack-dev-middleware@^5.3.4: + version "5.3.4" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz#eb7b39281cbce10e104eb2b8bf2b63fce49a3517" + integrity sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q== dependencies: colorette "^2.0.10" - memfs "^4.6.0" + memfs "^3.4.3" mime-types "^2.1.31" - on-finished "^2.4.1" range-parser "^1.2.1" schema-utils "^4.0.0" -webpack-dev-server@5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-5.0.4.tgz#cb6ea47ff796b9251ec49a94f24a425e12e3c9b8" - integrity sha512-dljXhUgx3HqKP2d8J/fUMvhxGhzjeNVarDLcbO/EWMSgRizDkxHQDZQaLFL5VJY9tRBj2Gz+rvCEYYvhbqPHNA== - dependencies: - "@types/bonjour" "^3.5.13" - "@types/connect-history-api-fallback" "^1.5.4" - "@types/express" "^4.17.21" - "@types/serve-index" "^1.9.4" - "@types/serve-static" "^1.15.5" - "@types/sockjs" "^0.3.36" - "@types/ws" "^8.5.10" +webpack-dev-server@4.15.2: + version "4.15.2" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz#9e0c70a42a012560860adb186986da1248333173" + integrity sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g== + dependencies: + "@types/bonjour" "^3.5.9" + "@types/connect-history-api-fallback" "^1.3.5" + "@types/express" "^4.17.13" + "@types/serve-index" "^1.9.1" + "@types/serve-static" "^1.13.10" + "@types/sockjs" "^0.3.33" + "@types/ws" "^8.5.5" ansi-html-community "^0.0.8" - bonjour-service "^1.2.1" - chokidar "^3.6.0" + bonjour-service "^1.0.11" + chokidar "^3.5.3" colorette "^2.0.10" compression "^1.7.4" connect-history-api-fallback "^2.0.0" default-gateway "^6.0.3" express "^4.17.3" graceful-fs "^4.2.6" - html-entities "^2.4.0" + html-entities "^2.3.2" http-proxy-middleware "^2.0.3" - ipaddr.js "^2.1.0" - launch-editor "^2.6.1" - open "^10.0.3" - p-retry "^6.2.0" - rimraf "^5.0.5" - schema-utils "^4.2.0" - selfsigned "^2.4.1" + ipaddr.js "^2.0.1" + launch-editor "^2.6.0" + open "^8.0.9" + p-retry "^4.5.0" + rimraf "^3.0.2" + schema-utils "^4.0.0" + selfsigned "^2.1.1" serve-index "^1.9.1" sockjs "^0.3.24" spdy "^4.0.2" - webpack-dev-middleware "^7.1.0" - ws "^8.16.0" + webpack-dev-middleware "^5.3.4" + ws "^8.13.0" webpack-merge@^4.1.5: version "4.2.2" @@ -3030,10 +2834,10 @@ webpack-sources@^3.2.3: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== -webpack@5.91.0: - version "5.91.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.91.0.tgz#ffa92c1c618d18c878f06892bbdc3373c71a01d9" - integrity sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw== +webpack@5.93.0: + version "5.93.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.93.0.tgz#2e89ec7035579bdfba9760d26c63ac5c3462a5e5" + integrity sha512-Y0m5oEY1LRuwly578VqluorkXbvXKh7U3rLoQCEO04M97ScRr44afGVkI0FQFsXzysk5OgFAxjZAb9rsGQVihA== dependencies: "@types/eslint-scope" "^3.7.3" "@types/estree" "^1.0.5" @@ -3041,10 +2845,10 @@ webpack@5.91.0: "@webassemblyjs/wasm-edit" "^1.12.1" "@webassemblyjs/wasm-parser" "^1.12.1" acorn "^8.7.1" - acorn-import-assertions "^1.9.0" + acorn-import-attributes "^1.9.5" browserslist "^4.21.10" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.16.0" + enhanced-resolve "^5.17.0" es-module-lexer "^1.2.1" eslint-scope "5.1.1" events "^3.2.0" @@ -3093,12 +2897,12 @@ wildcard@^2.0.0: resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67" integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== -workerpool@6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" - integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== +workerpool@^6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" + integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -3107,24 +2911,15 @@ workerpool@6.2.1: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" - integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== - dependencies: - ansi-styles "^6.1.0" - string-width "^5.0.1" - strip-ansi "^7.0.1" - wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -ws@^8.16.0: - version "8.17.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.0.tgz#d145d18eca2ed25aaf791a183903f7be5e295fea" - integrity sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow== +ws@^8.13.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== ws@~8.11.0: version "8.11.0" @@ -3136,17 +2931,12 @@ y18n@^5.0.5: resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== -yargs-parser@20.2.4: - version "20.2.4" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" - integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== - -yargs-parser@^20.2.2: +yargs-parser@^20.2.2, yargs-parser@^20.2.9: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== -yargs-unparser@2.0.0: +yargs-unparser@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== @@ -3156,7 +2946,7 @@ yargs-unparser@2.0.0: flat "^5.0.2" is-plain-obj "^2.1.0" -yargs@16.2.0, yargs@^16.1.1: +yargs@^16.1.1, yargs@^16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==