From 6fcd27b8ef851dd1e41f7ee0826a1568711f38f1 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Fri, 17 Oct 2025 17:38:20 +0200 Subject: [PATCH 1/2] Use common SDK for Swift build --- .../powersync/db/NativeConnectionFactory.kt | 38 +++++++++++++++++++ internal/PowerSyncKotlin/build.gradle.kts | 5 ++- .../src/appleMain/kotlin/com/powersync/SDK.kt | 30 +++++++++++++++ 3 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 common/src/nativeMain/kotlin/com/powersync/db/NativeConnectionFactory.kt diff --git a/common/src/nativeMain/kotlin/com/powersync/db/NativeConnectionFactory.kt b/common/src/nativeMain/kotlin/com/powersync/db/NativeConnectionFactory.kt new file mode 100644 index 00000000..e92eb10b --- /dev/null +++ b/common/src/nativeMain/kotlin/com/powersync/db/NativeConnectionFactory.kt @@ -0,0 +1,38 @@ +package com.powersync.db + +import androidx.sqlite.SQLiteConnection +import com.powersync.PersistentConnectionFactory +import com.powersync.PowerSyncException +import com.powersync.sqlite.Database + +/** + * A [PersistentConnectionFactory] implementation delegating to static `sqlite3_` invocations through cinterop. + */ +public abstract class NativeConnectionFactory: PersistentConnectionFactory { + override fun openConnection(path: String, openFlags: Int): SQLiteConnection { + val extensionPath = powersyncLoadableExtensionPath() + val db = Database.open(path, openFlags) + + if (extensionPath != null) { + try { + db.loadExtension(extensionPath, "sqlite3_powersync_init") + } catch (e: PowerSyncException) { + db.close() + throw e + } + } + + return db + } + + override fun openInMemoryConnection(): SQLiteConnection { + return openConnection(":memory:", 0x02) + } + + /** + * If the core extension should be loaded as a dynamic library, returns its path. + * + * Otherwise, installs the core extension as a static extension and returns null. + */ + protected abstract fun powersyncLoadableExtensionPath(): String? +} diff --git a/internal/PowerSyncKotlin/build.gradle.kts b/internal/PowerSyncKotlin/build.gradle.kts index 3fa8d56d..319e4b8c 100644 --- a/internal/PowerSyncKotlin/build.gradle.kts +++ b/internal/PowerSyncKotlin/build.gradle.kts @@ -25,7 +25,7 @@ kotlin { baseName = "PowerSyncKotlin" xcf.add(this) - export(project(":core")) + export(projects.common) isStatic = true binaryOption("bundleId", "PowerSyncKotlin") @@ -45,8 +45,9 @@ kotlin { sourceSets { commonMain.dependencies { - api(project(":core")) + api(projects.common) implementation(libs.ktor.client.logging) + implementation(libs.ktor.client.darwin) } all { diff --git a/internal/PowerSyncKotlin/src/appleMain/kotlin/com/powersync/SDK.kt b/internal/PowerSyncKotlin/src/appleMain/kotlin/com/powersync/SDK.kt index 81524368..c7cfb1f5 100644 --- a/internal/PowerSyncKotlin/src/appleMain/kotlin/com/powersync/SDK.kt +++ b/internal/PowerSyncKotlin/src/appleMain/kotlin/com/powersync/SDK.kt @@ -2,6 +2,9 @@ package com.powersync +import androidx.sqlite.SQLiteConnection +import androidx.sqlite.execSQL +import com.powersync.db.NativeConnectionFactory import com.powersync.db.crud.CrudTransaction import com.powersync.sync.SyncClientConfiguration import com.powersync.sync.SyncOptions @@ -12,6 +15,33 @@ import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.map import io.ktor.client.plugins.logging.Logger as KtorLogger +public fun sqlite3DatabaseFactory(initialStatements: List): PersistentConnectionFactory { + @OptIn(ExperimentalPowerSyncAPI::class) + return object : NativeConnectionFactory() { + override fun powersyncLoadableExtensionPath(): String? { + return resolvePowerSyncLoadableExtensionPath() + } + + override fun resolveDefaultDatabasePath(dbFilename: String): String { + return appleDefaultDatabasePath(dbFilename) + } + + override fun openConnection(path: String, openFlags: Int): SQLiteConnection { + val conn = super.openConnection(path, openFlags) + try { + for (statement in initialStatements) { + conn.execSQL(statement) + } + } catch (e: PowerSyncException) { + conn.close() + throw e + } + + return super.openConnection(path, openFlags) + } + } +} + /** * Helper class designed to bridge SKIEE methods and allow them to throw * `PowerSyncException`. This is necessary because these exceptions cannot From cdf6bfe14b4b558511cb4be638fbc6f521099b6b Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Thu, 23 Oct 2025 17:00:47 +0200 Subject: [PATCH 2/2] Simplify --- .../powersync/db/NativeConnectionFactory.kt | 25 ++++++++-------- .../powersync/DatabaseDriverFactory.native.kt | 29 ++----------------- .../src/appleMain/kotlin/com/powersync/SDK.kt | 13 ++++----- 3 files changed, 19 insertions(+), 48 deletions(-) diff --git a/common/src/nativeMain/kotlin/com/powersync/db/NativeConnectionFactory.kt b/common/src/nativeMain/kotlin/com/powersync/db/NativeConnectionFactory.kt index e92eb10b..357d404b 100644 --- a/common/src/nativeMain/kotlin/com/powersync/db/NativeConnectionFactory.kt +++ b/common/src/nativeMain/kotlin/com/powersync/db/NativeConnectionFactory.kt @@ -1,16 +1,24 @@ package com.powersync.db import androidx.sqlite.SQLiteConnection +import com.powersync.ExperimentalPowerSyncAPI import com.powersync.PersistentConnectionFactory import com.powersync.PowerSyncException +import com.powersync.resolvePowerSyncLoadableExtensionPath import com.powersync.sqlite.Database /** * A [PersistentConnectionFactory] implementation delegating to static `sqlite3_` invocations through cinterop. */ -public abstract class NativeConnectionFactory: PersistentConnectionFactory { - override fun openConnection(path: String, openFlags: Int): SQLiteConnection { - val extensionPath = powersyncLoadableExtensionPath() +public abstract class NativeConnectionFactory : PersistentConnectionFactory { + @OptIn(ExperimentalPowerSyncAPI::class) + override fun openConnection( + path: String, + openFlags: Int, + ): SQLiteConnection { + // On some platforms, most notably watchOS, there's no dynamic extension loading and the core extension is + // registered via sqlite3_auto_extension. + val extensionPath = resolvePowerSyncLoadableExtensionPath() val db = Database.open(path, openFlags) if (extensionPath != null) { @@ -25,14 +33,5 @@ public abstract class NativeConnectionFactory: PersistentConnectionFactory { return db } - override fun openInMemoryConnection(): SQLiteConnection { - return openConnection(":memory:", 0x02) - } - - /** - * If the core extension should be loaded as a dynamic library, returns its path. - * - * Otherwise, installs the core extension as a static extension and returns null. - */ - protected abstract fun powersyncLoadableExtensionPath(): String? + override fun openInMemoryConnection(): SQLiteConnection = openConnection(":memory:", 0x02) } diff --git a/core/src/nativeMain/kotlin/com/powersync/DatabaseDriverFactory.native.kt b/core/src/nativeMain/kotlin/com/powersync/DatabaseDriverFactory.native.kt index bc34bfd5..b8a6e035 100644 --- a/core/src/nativeMain/kotlin/com/powersync/DatabaseDriverFactory.native.kt +++ b/core/src/nativeMain/kotlin/com/powersync/DatabaseDriverFactory.native.kt @@ -1,35 +1,10 @@ package com.powersync -import androidx.sqlite.SQLiteConnection -import com.powersync.sqlite.Database +import com.powersync.db.NativeConnectionFactory @Suppress(names = ["EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING"]) -public actual class DatabaseDriverFactory : PersistentConnectionFactory { +public actual class DatabaseDriverFactory : NativeConnectionFactory() { actual override fun resolveDefaultDatabasePath(dbFilename: String): String = appleDefaultDatabasePath(dbFilename) - - @OptIn(ExperimentalPowerSyncAPI::class) - actual override fun openConnection( - path: String, - openFlags: Int, - ): SQLiteConnection { - // On some platforms, most notably watchOS, there's no dynamic extension loading and the core extension is - // registered via sqlite3_auto_extension. - val extensionPath = resolvePowerSyncLoadableExtensionPath() - - val db = Database.open(path, openFlags) - extensionPath?.let { path -> - try { - db.loadExtension(path, "sqlite3_powersync_init") - } catch (e: PowerSyncException) { - db.close() - throw e - } - } - - return db - } - - actual override fun openInMemoryConnection(): SQLiteConnection = openConnection(":memory:", 0x02) } internal actual val inMemoryDriver: InMemoryConnectionFactory = DatabaseDriverFactory() diff --git a/internal/PowerSyncKotlin/src/appleMain/kotlin/com/powersync/SDK.kt b/internal/PowerSyncKotlin/src/appleMain/kotlin/com/powersync/SDK.kt index c7cfb1f5..39382e2a 100644 --- a/internal/PowerSyncKotlin/src/appleMain/kotlin/com/powersync/SDK.kt +++ b/internal/PowerSyncKotlin/src/appleMain/kotlin/com/powersync/SDK.kt @@ -18,15 +18,12 @@ import io.ktor.client.plugins.logging.Logger as KtorLogger public fun sqlite3DatabaseFactory(initialStatements: List): PersistentConnectionFactory { @OptIn(ExperimentalPowerSyncAPI::class) return object : NativeConnectionFactory() { - override fun powersyncLoadableExtensionPath(): String? { - return resolvePowerSyncLoadableExtensionPath() - } - - override fun resolveDefaultDatabasePath(dbFilename: String): String { - return appleDefaultDatabasePath(dbFilename) - } + override fun resolveDefaultDatabasePath(dbFilename: String): String = appleDefaultDatabasePath(dbFilename) - override fun openConnection(path: String, openFlags: Int): SQLiteConnection { + override fun openConnection( + path: String, + openFlags: Int, + ): SQLiteConnection { val conn = super.openConnection(path, openFlags) try { for (statement in initialStatements) {