From be8617e4aaf70e569f1a23601de68fc5b7b51ea0 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Fri, 17 Oct 2025 10:38:56 +0200 Subject: [PATCH 1/8] Start moving code into common --- common/build.gradle.kts | 277 +++++++++++++++++ .../src/androidMain/kotlin/BuildConfig.kt | 0 .../DatabaseDriverFactory.android.kt | 5 + .../com/powersync/sync/UserAgent.android.kt | 0 .../src/appleMain/kotlin/BuildConfig.kt | 0 .../kotlin/com/powersync/PathUtils.kt | 6 +- .../DatabaseDriverFactory.appleNonWatchOs.kt | 5 + .../kotlin/com/powersync/AttachmentsTest.kt | 0 .../kotlin/com/powersync/CrudTest.kt | 0 .../kotlin/com/powersync/DatabaseTest.kt | 0 .../kotlin/com/powersync/db/InMemoryTest.kt | 0 .../com/powersync/sync/AbstractSyncTest.kt | 0 .../com/powersync/sync/SyncIntegrationTest.kt | 2 - .../com/powersync/sync/SyncProgressTest.kt | 0 .../com/powersync/sync/SyncStreamTest.kt | 0 .../testutils/MockedRemoteStorage.kt | 0 .../com/powersync/testutils/TestUtils.kt | 5 +- .../kotlin/com/powersync/testutils/UserRow.kt | 0 .../db/ActiveInstanceStore.commonJava.kt | 0 .../src/commonMain/kotlin/BuildConfig.kt | 3 +- .../com/powersync/DatabaseDriverFactory.kt | 68 ++++ .../com/powersync/ExperimentalPowerSyncAPI.kt | 0 .../kotlin/com/powersync/PowerSyncDatabase.kt | 266 ++++++++++++++++ .../com/powersync/PowerSyncDatabaseFactory.kt | 4 +- .../com/powersync/PowerSyncException.kt | 0 .../com/powersync/attachments/Attachment.kt | 0 .../powersync/attachments/AttachmentQueue.kt | 0 .../attachments/AttachmentService.kt | 0 .../powersync/attachments/AttachmentTable.kt | 0 .../com/powersync/attachments/LocalStorage.kt | 0 .../com/powersync/attachments/README.md | 0 .../powersync/attachments/RemoteStorage.kt | 0 .../powersync/attachments/SyncErrorHandler.kt | 0 .../implementation/AttachmentContextImpl.kt | 0 .../implementation/AttachmentServiceImpl.kt | 0 .../storage/IOLocalStorageAdapter.kt | 0 .../attachments/sync/SyncingService.kt | 0 .../com/powersync/bucket/BucketChecksum.kt | 0 .../com/powersync/bucket/BucketRequest.kt | 0 .../com/powersync/bucket/BucketState.kt | 0 .../com/powersync/bucket/BucketStorage.kt | 0 .../com/powersync/bucket/BucketStorageImpl.kt | 0 .../kotlin/com/powersync/bucket/Checkpoint.kt | 0 .../com/powersync/bucket/ChecksumCache.kt | 0 .../bucket/LocalOperationCounters.kt | 0 .../kotlin/com/powersync/bucket/OpType.kt | 0 .../kotlin/com/powersync/bucket/OplogEntry.kt | 0 .../kotlin/com/powersync/bucket/SqliteOp.kt | 0 .../com/powersync/bucket/StreamPriority.kt | 0 .../powersync/bucket/WriteCheckpointResult.kt | 0 .../connectors/PowerSyncBackendConnector.kt | 0 .../connectors/PowerSyncCredentials.kt | 0 .../com/powersync/db/ActiveInstanceStore.kt | 30 +- .../kotlin/com/powersync/db/Functions.kt | 0 .../com/powersync/db/PowerSyncDatabaseImpl.kt | 0 .../kotlin/com/powersync/db/Queries.kt | 0 .../kotlin/com/powersync/db/SqlCursor.kt | 0 .../kotlin/com/powersync/db/StreamImpl.kt | 0 .../kotlin/com/powersync/db/crud/CrudBatch.kt | 0 .../kotlin/com/powersync/db/crud/CrudEntry.kt | 0 .../kotlin/com/powersync/db/crud/CrudRow.kt | 0 .../com/powersync/db/crud/CrudTransaction.kt | 0 .../com/powersync/db/crud/SerializedRow.kt | 0 .../com/powersync/db/crud/UpdateType.kt | 0 .../com/powersync/db/crud/UploadQueueStats.kt | 0 .../db/driver/InternalConnectionPool.kt | 8 +- .../com/powersync/db/driver/LazyPool.kt | 0 .../powersync/db/driver/RawConnectionLease.kt | 0 .../com/powersync/db/driver/ReadPool.kt | 0 .../db/driver/SQLiteConnectionPool.kt | 0 .../db/driver/SingleConnectionPool.kt | 5 +- .../db/internal/ConnectionContext.kt | 0 .../powersync/db/internal/InternalDatabase.kt | 0 .../db/internal/InternalDatabaseImpl.kt | 0 .../powersync/db/internal/InternalTable.kt | 0 .../db/internal/PowerSyncTransaction.kt | 0 .../powersync/db/internal/PowerSyncVersion.kt | 0 .../com/powersync/db/schema/BaseTable.kt | 0 .../kotlin/com/powersync/db/schema/Column.kt | 0 .../com/powersync/db/schema/ColumnType.kt | 0 .../kotlin/com/powersync/db/schema/Index.kt | 0 .../com/powersync/db/schema/IndexedColumn.kt | 0 .../com/powersync/db/schema/RawTable.kt | 0 .../kotlin/com/powersync/db/schema/Schema.kt | 0 .../kotlin/com/powersync/db/schema/Table.kt | 0 .../com/powersync/db/schema/validation.kt | 0 .../kotlin/com/powersync/sync/Instruction.kt | 0 .../sync/LegacySyncImplementation.kt | 0 .../kotlin/com/powersync/sync/Progress.kt | 0 .../kotlin/com/powersync/sync/Stream.kt | 0 .../com/powersync/sync/StreamingSync.kt | 0 .../powersync/sync/StreamingSyncRequest.kt | 0 .../com/powersync/sync/SyncDataBatch.kt | 0 .../kotlin/com/powersync/sync/SyncLine.kt | 0 .../powersync/sync/SyncLocalDatabaseResult.kt | 0 .../kotlin/com/powersync/sync/SyncOptions.kt | 0 .../kotlin/com/powersync/sync/SyncStatus.kt | 0 .../kotlin/com/powersync/sync/UserAgent.kt | 0 .../com/powersync/utils/AtomicMutableSet.kt | 0 .../kotlin/com/powersync/utils/Json.kt | 0 .../kotlin/com/powersync/utils/Log.kt | 0 .../kotlin/com/powersync/utils/Strings.kt | 0 .../com/powersync/utils/ThrottleFlow.kt | 0 .../src/jvmMain/kotlin/BuildConfig.kt | 2 +- .../powersync/DatabaseDriverFactory.jvm.kt | 9 + .../kotlin/com/powersync/ExtractLib.kt | 0 .../com/powersync/sync/UserAgent.jvm.kt | 0 .../src/nativeMain/interop/sqlite3.def | 0 .../src/nativeMain/interop/sqlite3.h | 0 .../db/ActiveInstanceStore.native.kt | 0 .../kotlin/com/powersync/sqlite/Database.kt | 10 +- .../com/powersync/sqlite/SqliteException.kt | 0 .../kotlin/com/powersync/sqlite/Statement.kt | 0 .../com/powersync/sync/UserAgent.native.kt | 0 .../com.powersync.sqlite}/DatabaseTest.kt | 0 .../com.powersync.sqlite}/StatementTest.kt | 0 .../DatabaseDriverFactory.watchos.kt | 25 ++ common/src/watchosMain/powersync_static.h | 1 + core/build.gradle.kts | 128 +------- .../DatabaseDriverFactory.android.kt | 13 +- .../DatabaseDriverFactory.appleNonWatchOs.kt | 16 +- .../powersync/DatabaseDriverFactoryTest.kt | 4 +- .../com/powersync/DatabaseDriverFactory.kt | 61 +--- .../kotlin/com/powersync/PowerSyncDatabase.kt | 294 ++---------------- .../kotlin/com/powersync/db/FunctionTest.kt | 1 - .../powersync/DatabaseDriverFactory.jvm.kt | 20 +- .../DatabaseDriverFactory.watchos.kt | 35 +-- settings.gradle.kts | 1 + 128 files changed, 760 insertions(+), 544 deletions(-) create mode 100644 common/build.gradle.kts rename {core => common}/src/androidMain/kotlin/BuildConfig.kt (100%) create mode 100644 common/src/androidMain/kotlin/com/powersync/DatabaseDriverFactory.android.kt rename {core => common}/src/androidMain/kotlin/com/powersync/sync/UserAgent.android.kt (100%) rename {core => common}/src/appleMain/kotlin/BuildConfig.kt (100%) rename core/src/appleMain/kotlin/com/powersync/DatabaseDriverFactory.apple.kt => common/src/appleMain/kotlin/com/powersync/PathUtils.kt (92%) create mode 100644 common/src/appleNonWatchOsMain/kotlin/com/powersync/DatabaseDriverFactory.appleNonWatchOs.kt rename {core => common}/src/commonIntegrationTest/kotlin/com/powersync/AttachmentsTest.kt (100%) rename {core => common}/src/commonIntegrationTest/kotlin/com/powersync/CrudTest.kt (100%) rename {core => common}/src/commonIntegrationTest/kotlin/com/powersync/DatabaseTest.kt (100%) rename {core => common}/src/commonIntegrationTest/kotlin/com/powersync/db/InMemoryTest.kt (100%) rename {core => common}/src/commonIntegrationTest/kotlin/com/powersync/sync/AbstractSyncTest.kt (100%) rename {core => common}/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncIntegrationTest.kt (99%) rename {core => common}/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncProgressTest.kt (100%) rename {core => common}/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncStreamTest.kt (100%) rename {core => common}/src/commonIntegrationTest/kotlin/com/powersync/testutils/MockedRemoteStorage.kt (100%) rename {core => common}/src/commonIntegrationTest/kotlin/com/powersync/testutils/TestUtils.kt (97%) rename {core => common}/src/commonIntegrationTest/kotlin/com/powersync/testutils/UserRow.kt (100%) rename {core => common}/src/commonJava/kotlin/com/powersync/db/ActiveInstanceStore.commonJava.kt (100%) rename {core => common}/src/commonMain/kotlin/BuildConfig.kt (50%) create mode 100644 common/src/commonMain/kotlin/com/powersync/DatabaseDriverFactory.kt rename {core => common}/src/commonMain/kotlin/com/powersync/ExperimentalPowerSyncAPI.kt (100%) create mode 100644 common/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt rename {core => common}/src/commonMain/kotlin/com/powersync/PowerSyncDatabaseFactory.kt (96%) rename {core => common}/src/commonMain/kotlin/com/powersync/PowerSyncException.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/attachments/Attachment.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/attachments/AttachmentQueue.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/attachments/AttachmentService.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/attachments/AttachmentTable.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/attachments/LocalStorage.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/attachments/README.md (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/attachments/RemoteStorage.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/attachments/SyncErrorHandler.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/attachments/implementation/AttachmentContextImpl.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/attachments/implementation/AttachmentServiceImpl.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/attachments/storage/IOLocalStorageAdapter.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/attachments/sync/SyncingService.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/bucket/BucketChecksum.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/bucket/BucketRequest.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/bucket/BucketState.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/bucket/BucketStorage.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/bucket/BucketStorageImpl.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/bucket/Checkpoint.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/bucket/ChecksumCache.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/bucket/LocalOperationCounters.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/bucket/OpType.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/bucket/OplogEntry.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/bucket/SqliteOp.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/bucket/StreamPriority.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/bucket/WriteCheckpointResult.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/connectors/PowerSyncBackendConnector.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/connectors/PowerSyncCredentials.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/ActiveInstanceStore.kt (77%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/Functions.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/Queries.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/SqlCursor.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/StreamImpl.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/crud/CrudBatch.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/crud/CrudEntry.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/crud/CrudRow.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/crud/CrudTransaction.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/crud/SerializedRow.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/crud/UpdateType.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/crud/UploadQueueStats.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/driver/InternalConnectionPool.kt (95%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/driver/LazyPool.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/driver/RawConnectionLease.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/driver/ReadPool.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/driver/SQLiteConnectionPool.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/driver/SingleConnectionPool.kt (95%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/internal/ConnectionContext.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/internal/InternalDatabase.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/internal/InternalDatabaseImpl.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/internal/InternalTable.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/internal/PowerSyncTransaction.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/internal/PowerSyncVersion.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/schema/BaseTable.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/schema/Column.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/schema/ColumnType.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/schema/Index.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/schema/IndexedColumn.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/schema/RawTable.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/schema/Schema.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/schema/Table.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/db/schema/validation.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/sync/Instruction.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/sync/LegacySyncImplementation.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/sync/Progress.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/sync/Stream.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/sync/StreamingSync.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/sync/StreamingSyncRequest.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/sync/SyncDataBatch.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/sync/SyncLine.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/sync/SyncLocalDatabaseResult.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/sync/SyncOptions.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/sync/SyncStatus.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/sync/UserAgent.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/utils/AtomicMutableSet.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/utils/Json.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/utils/Log.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/utils/Strings.kt (100%) rename {core => common}/src/commonMain/kotlin/com/powersync/utils/ThrottleFlow.kt (100%) rename {core => common}/src/jvmMain/kotlin/BuildConfig.kt (89%) create mode 100644 common/src/jvmMain/kotlin/com/powersync/DatabaseDriverFactory.jvm.kt rename {core => common}/src/jvmMain/kotlin/com/powersync/ExtractLib.kt (100%) rename {core => common}/src/jvmMain/kotlin/com/powersync/sync/UserAgent.jvm.kt (100%) rename {core => common}/src/nativeMain/interop/sqlite3.def (100%) rename {core => common}/src/nativeMain/interop/sqlite3.h (100%) rename {core => common}/src/nativeMain/kotlin/com/powersync/db/ActiveInstanceStore.native.kt (100%) rename {core => common}/src/nativeMain/kotlin/com/powersync/sqlite/Database.kt (96%) rename {core => common}/src/nativeMain/kotlin/com/powersync/sqlite/SqliteException.kt (100%) rename {core => common}/src/nativeMain/kotlin/com/powersync/sqlite/Statement.kt (100%) rename {core => common}/src/nativeMain/kotlin/com/powersync/sync/UserAgent.native.kt (100%) rename {core/src/appleTest/kotlin/com/powersync/sqlite => common/src/nativeTest/kotlin/com.powersync.sqlite}/DatabaseTest.kt (100%) rename {core/src/appleTest/kotlin/com/powersync/sqlite => common/src/nativeTest/kotlin/com.powersync.sqlite}/StatementTest.kt (100%) create mode 100644 common/src/watchosMain/kotlin/com/powersync/DatabaseDriverFactory.watchos.kt create mode 100644 common/src/watchosMain/powersync_static.h diff --git a/common/build.gradle.kts b/common/build.gradle.kts new file mode 100644 index 00000000..207b0518 --- /dev/null +++ b/common/build.gradle.kts @@ -0,0 +1,277 @@ +import com.powersync.plugins.utils.powersyncTargets +import de.undercouch.gradle.tasks.download.Download +import org.gradle.api.tasks.testing.logging.TestExceptionFormat +import org.gradle.internal.os.OperatingSystem +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget +import org.jetbrains.kotlin.gradle.tasks.KotlinTest +import org.jetbrains.kotlin.konan.target.Family +import java.nio.file.Path +import kotlin.io.path.createDirectories +import kotlin.io.path.writeText + +plugins { + alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.kotlinSerialization) + alias(libs.plugins.android.library) + alias(libs.plugins.mavenPublishPlugin) + alias(libs.plugins.downloadPlugin) + alias(libs.plugins.kotlinter) + id("com.powersync.plugins.sonatype") + id("com.powersync.plugins.sharedbuild") + alias(libs.plugins.mokkery) + alias(libs.plugins.kotlin.atomicfu) + id("dokka-convention") +} + +val binariesFolder = project.layout.buildDirectory.dir("binaries/desktop") +val downloadPowersyncDesktopBinaries by tasks.registering(Download::class) { + description = "Download PowerSync core extensions for JVM builds and releases" + + val coreVersion = + libs.versions.powersync.core + .get() + val linux_aarch64 = + "https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v$coreVersion/libpowersync_aarch64.so" + val linux_x64 = + "https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v$coreVersion/libpowersync_x64.so" + val macos_aarch64 = + "https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v$coreVersion/libpowersync_aarch64.dylib" + val macos_x64 = + "https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v$coreVersion/libpowersync_x64.dylib" + val windows_x64 = + "https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v$coreVersion/powersync_x64.dll" + + val includeAllPlatformsForJvmBuild = + project.findProperty("powersync.binaries.allPlatforms") == "true" + val os = OperatingSystem.current() + + // The jar we're releasing for JVM clients needs to include the core extension. For local tests, it's enough to only + // download the extension for the OS running the build. For releases, we want to include them all. + // We're not compiling native code for JVM builds here (we're doing that for Android only), so we just have to + // fetch prebuilt binaries from the powersync-sqlite-core repository. + if (includeAllPlatformsForJvmBuild) { + src(listOf(linux_aarch64, linux_x64, macos_aarch64, macos_x64, windows_x64)) + } else { + val (aarch64, x64) = + when { + os.isLinux -> linux_aarch64 to linux_x64 + os.isMacOsX -> macos_aarch64 to macos_x64 + os.isWindows -> null to windows_x64 + else -> error("Unknown operating system: $os") + } + val arch = System.getProperty("os.arch") + src( + when (arch) { + "aarch64" -> listOfNotNull(aarch64) + "amd64", "x86_64" -> listOfNotNull(x64) + else -> error("Unsupported architecture: $arch") + }, + ) + } + dest(binariesFolder.map { it.dir("powersync") }) + onlyIfModified(true) +} + +val generateVersionConstant by tasks.registering { + val target = project.layout.buildDirectory.dir("generated/constants") + val packageName = "com.powersync.build" + + outputs.dir(target) + val currentVersion = version.toString() + + doLast { + val dir = target.get().asFile + dir.mkdir() + val rootPath = dir.toPath() + + val source = + """ + package $packageName + + internal const val LIBRARY_VERSION: String = "$currentVersion" + + """.trimIndent() + + val packageRoot = packageName.split('.').fold(rootPath, Path::resolve) + packageRoot.createDirectories() + + packageRoot.resolve("BuildConstants.kt").writeText(source) + } +} + +kotlin { + powersyncTargets() + + targets.withType { + compilations.named("main") { + compileTaskProvider { + compilerOptions.freeCompilerArgs.add("-Xexport-kdoc") + } + + if (target.konanTarget.family == Family.WATCHOS) { + // We're linking the core extension statically, which means that we need a cinterop + // to call powersync_init_static + cinterops.create("powersync_static") { + packageName("com.powersync.static") + headers(file("src/watchosMain/powersync_static.h")) + } + } + + cinterops.create("sqlite3") { + packageName("com.powersync.internal.sqlite3") + includeDirs.allHeaders("src/nativeMain/interop/") + definitionFile.set(project.file("src/nativeMain/interop/sqlite3.def")) + } + } + } + + explicitApi() + + applyDefaultHierarchyTemplate() + sourceSets { + all { + languageSettings { + optIn("kotlinx.cinterop.ExperimentalForeignApi") + optIn("kotlin.time.ExperimentalTime") + optIn("kotlin.experimental.ExperimentalObjCRefinement") + } + } + + val commonIntegrationTest by creating { + dependsOn(commonTest.get()) + } + + val commonJava by creating { + dependsOn(commonMain.get()) + } + + commonMain.configure { + kotlin { + srcDir(generateVersionConstant) + } + + dependencies { + api(libs.androidx.sqlite.sqlite) + + implementation(libs.uuid) + implementation(libs.kotlin.stdlib) + implementation(libs.ktor.client.contentnegotiation) + implementation(libs.ktor.serialization.json) + implementation(libs.kotlinx.io) + implementation(libs.kotlinx.coroutines.core) + implementation(libs.kotlinx.datetime) + implementation(libs.stately.concurrency) + implementation(libs.configuration.annotations) + api(libs.ktor.client.core) + api(libs.kermit) + } + } + + androidMain { + dependsOn(commonJava) + dependencies { + api(libs.powersync.sqlite.core.android) + implementation(libs.ktor.client.okhttp) + implementation(libs.androidx.sqlite.bundled) + } + } + + jvmMain { + dependsOn(commonJava) + + dependencies { + implementation(libs.ktor.client.okhttp) + implementation(libs.androidx.sqlite.bundled) + } + } + + appleMain.dependencies { + implementation(libs.ktor.client.darwin) + + // We're not using the bundled SQLite library for Apple platforms. Instead, we depend on + // static-sqlite-driver to link SQLite and have our own bindings implementing the + // driver. The reason for this is that androidx.sqlite-bundled causes linker errors for + // our Swift SDK. + implementation(projects.staticSqliteDriver) + } + + // Common apple targets where we link the core extension dynamically + val appleNonWatchOsMain by creating { + dependsOn(appleMain.get()) + } + + macosMain.orNull?.dependsOn(appleNonWatchOsMain) + iosMain.orNull?.dependsOn(appleNonWatchOsMain) + tvosMain.orNull?.dependsOn(appleNonWatchOsMain) + + commonTest.dependencies { + implementation(libs.kotlin.test) + implementation(libs.test.coroutines) + implementation(libs.test.turbine) + implementation(libs.test.kotest.assertions) + implementation(libs.kermit.test) + implementation(libs.ktor.client.mock) + implementation(libs.test.turbine) + } + + // We're putting the native libraries into our JAR, so integration tests for the JVM can run as part of the unit + // tests. + jvmTest.get().dependsOn(commonIntegrationTest) + + // We have special setup in this build configuration to make these tests link the PowerSync extension, so they + // can run integration tests along with the executable for unit testing. + appleTest.orNull?.dependsOn(commonIntegrationTest) + } +} + +android { + compileOptions { + targetCompatibility = JavaVersion.VERSION_17 + } + + buildFeatures { + buildConfig = true + } + + buildTypes { + release { + buildConfigField("boolean", "DEBUG", "false") + } + debug { + buildConfigField("boolean", "DEBUG", "true") + } + } + + namespace = "com.powersync" + compileSdk = + libs.versions.android.compileSdk + .get() + .toInt() + defaultConfig { + minSdk = + libs.versions.android.minSdk + .get() + .toInt() + consumerProguardFiles("proguard-rules.pro") + } + + ndkVersion = "27.1.12297006" +} + +tasks.named(kotlin.jvm().compilations["main"].processResourcesTaskName) { + from(downloadPowersyncDesktopBinaries) +} + +tasks.withType { + testLogging { + events("PASSED", "FAILED", "SKIPPED") + exceptionFormat = TestExceptionFormat.FULL + showCauses = true + showStandardStreams = true + showStackTraces = true + } +} + +dokka { + moduleName.set("PowerSync Common") +} diff --git a/core/src/androidMain/kotlin/BuildConfig.kt b/common/src/androidMain/kotlin/BuildConfig.kt similarity index 100% rename from core/src/androidMain/kotlin/BuildConfig.kt rename to common/src/androidMain/kotlin/BuildConfig.kt diff --git a/common/src/androidMain/kotlin/com/powersync/DatabaseDriverFactory.android.kt b/common/src/androidMain/kotlin/com/powersync/DatabaseDriverFactory.android.kt new file mode 100644 index 00000000..2c35a546 --- /dev/null +++ b/common/src/androidMain/kotlin/com/powersync/DatabaseDriverFactory.android.kt @@ -0,0 +1,5 @@ +package com.powersync + +@ExperimentalPowerSyncAPI +@Throws(PowerSyncException::class) +public actual fun resolvePowerSyncLoadableExtensionPath(): String? = "libpowersync.so" diff --git a/core/src/androidMain/kotlin/com/powersync/sync/UserAgent.android.kt b/common/src/androidMain/kotlin/com/powersync/sync/UserAgent.android.kt similarity index 100% rename from core/src/androidMain/kotlin/com/powersync/sync/UserAgent.android.kt rename to common/src/androidMain/kotlin/com/powersync/sync/UserAgent.android.kt diff --git a/core/src/appleMain/kotlin/BuildConfig.kt b/common/src/appleMain/kotlin/BuildConfig.kt similarity index 100% rename from core/src/appleMain/kotlin/BuildConfig.kt rename to common/src/appleMain/kotlin/BuildConfig.kt diff --git a/core/src/appleMain/kotlin/com/powersync/DatabaseDriverFactory.apple.kt b/common/src/appleMain/kotlin/com/powersync/PathUtils.kt similarity index 92% rename from core/src/appleMain/kotlin/com/powersync/DatabaseDriverFactory.apple.kt rename to common/src/appleMain/kotlin/com/powersync/PathUtils.kt index 4149231f..6c9300bb 100644 --- a/core/src/appleMain/kotlin/com/powersync/DatabaseDriverFactory.apple.kt +++ b/common/src/appleMain/kotlin/com/powersync/PathUtils.kt @@ -1,7 +1,6 @@ package com.powersync import kotlinx.cinterop.UnsafeNumber -import kotlinx.io.files.FileSystem import platform.Foundation.NSApplicationSupportDirectory import platform.Foundation.NSBundle import platform.Foundation.NSFileManager @@ -9,8 +8,11 @@ import platform.Foundation.NSSearchPathForDirectoriesInDomains import platform.Foundation.NSUserDomainMask import kotlin.getValue +/** + * The default path to use for databases on Apple platforms. + */ @OptIn(UnsafeNumber::class) -internal fun appleDefaultDatabasePath(dbFilename: String): String { +public fun appleDefaultDatabasePath(dbFilename: String): String { // This needs to be compatible with https://github.com/touchlab/SQLiter/blob/a37bbe7e9c65e6a5a94c5bfcaccdaae55ad2bac9/sqliter-driver/src/appleMain/kotlin/co/touchlab/sqliter/DatabaseFileContext.kt#L36-L51 val paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, true) val documentsDirectory = paths[0] as String diff --git a/common/src/appleNonWatchOsMain/kotlin/com/powersync/DatabaseDriverFactory.appleNonWatchOs.kt b/common/src/appleNonWatchOsMain/kotlin/com/powersync/DatabaseDriverFactory.appleNonWatchOs.kt new file mode 100644 index 00000000..53cc0039 --- /dev/null +++ b/common/src/appleNonWatchOsMain/kotlin/com/powersync/DatabaseDriverFactory.appleNonWatchOs.kt @@ -0,0 +1,5 @@ +package com.powersync + +@ExperimentalPowerSyncAPI +@Throws(PowerSyncException::class) +public actual fun resolvePowerSyncLoadableExtensionPath(): String? = powerSyncExtensionPath diff --git a/core/src/commonIntegrationTest/kotlin/com/powersync/AttachmentsTest.kt b/common/src/commonIntegrationTest/kotlin/com/powersync/AttachmentsTest.kt similarity index 100% rename from core/src/commonIntegrationTest/kotlin/com/powersync/AttachmentsTest.kt rename to common/src/commonIntegrationTest/kotlin/com/powersync/AttachmentsTest.kt diff --git a/core/src/commonIntegrationTest/kotlin/com/powersync/CrudTest.kt b/common/src/commonIntegrationTest/kotlin/com/powersync/CrudTest.kt similarity index 100% rename from core/src/commonIntegrationTest/kotlin/com/powersync/CrudTest.kt rename to common/src/commonIntegrationTest/kotlin/com/powersync/CrudTest.kt diff --git a/core/src/commonIntegrationTest/kotlin/com/powersync/DatabaseTest.kt b/common/src/commonIntegrationTest/kotlin/com/powersync/DatabaseTest.kt similarity index 100% rename from core/src/commonIntegrationTest/kotlin/com/powersync/DatabaseTest.kt rename to common/src/commonIntegrationTest/kotlin/com/powersync/DatabaseTest.kt diff --git a/core/src/commonIntegrationTest/kotlin/com/powersync/db/InMemoryTest.kt b/common/src/commonIntegrationTest/kotlin/com/powersync/db/InMemoryTest.kt similarity index 100% rename from core/src/commonIntegrationTest/kotlin/com/powersync/db/InMemoryTest.kt rename to common/src/commonIntegrationTest/kotlin/com/powersync/db/InMemoryTest.kt diff --git a/core/src/commonIntegrationTest/kotlin/com/powersync/sync/AbstractSyncTest.kt b/common/src/commonIntegrationTest/kotlin/com/powersync/sync/AbstractSyncTest.kt similarity index 100% rename from core/src/commonIntegrationTest/kotlin/com/powersync/sync/AbstractSyncTest.kt rename to common/src/commonIntegrationTest/kotlin/com/powersync/sync/AbstractSyncTest.kt diff --git a/core/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncIntegrationTest.kt b/common/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncIntegrationTest.kt similarity index 99% rename from core/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncIntegrationTest.kt rename to common/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncIntegrationTest.kt index a1eb15d2..b33cc786 100644 --- a/core/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncIntegrationTest.kt +++ b/common/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncIntegrationTest.kt @@ -4,7 +4,6 @@ import app.cash.turbine.turbineScope import co.touchlab.kermit.ExperimentalKermitApi import com.powersync.ExperimentalPowerSyncAPI import com.powersync.PowerSyncDatabase -import com.powersync.PowerSyncException import com.powersync.TestConnector import com.powersync.bucket.BucketChecksum import com.powersync.bucket.Checkpoint @@ -15,7 +14,6 @@ import com.powersync.bucket.WriteCheckpointData import com.powersync.bucket.WriteCheckpointResponse import com.powersync.connectors.PowerSyncBackendConnector import com.powersync.connectors.PowerSyncCredentials -import com.powersync.db.PowerSyncDatabaseImpl import com.powersync.db.schema.PendingStatement import com.powersync.db.schema.PendingStatementParameter import com.powersync.db.schema.RawTable diff --git a/core/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncProgressTest.kt b/common/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncProgressTest.kt similarity index 100% rename from core/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncProgressTest.kt rename to common/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncProgressTest.kt diff --git a/core/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncStreamTest.kt b/common/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncStreamTest.kt similarity index 100% rename from core/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncStreamTest.kt rename to common/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncStreamTest.kt diff --git a/core/src/commonIntegrationTest/kotlin/com/powersync/testutils/MockedRemoteStorage.kt b/common/src/commonIntegrationTest/kotlin/com/powersync/testutils/MockedRemoteStorage.kt similarity index 100% rename from core/src/commonIntegrationTest/kotlin/com/powersync/testutils/MockedRemoteStorage.kt rename to common/src/commonIntegrationTest/kotlin/com/powersync/testutils/MockedRemoteStorage.kt diff --git a/core/src/commonIntegrationTest/kotlin/com/powersync/testutils/TestUtils.kt b/common/src/commonIntegrationTest/kotlin/com/powersync/testutils/TestUtils.kt similarity index 97% rename from core/src/commonIntegrationTest/kotlin/com/powersync/testutils/TestUtils.kt rename to common/src/commonIntegrationTest/kotlin/com/powersync/testutils/TestUtils.kt index fed689ff..a0fdbe91 100644 --- a/core/src/commonIntegrationTest/kotlin/com/powersync/testutils/TestUtils.kt +++ b/common/src/commonIntegrationTest/kotlin/com/powersync/testutils/TestUtils.kt @@ -7,14 +7,13 @@ import co.touchlab.kermit.LogWriter import co.touchlab.kermit.Logger import co.touchlab.kermit.Severity import co.touchlab.kermit.TestConfig -import com.powersync.DatabaseDriverFactory import com.powersync.ExperimentalPowerSyncAPI +import com.powersync.PersistentDriverFactory import com.powersync.PowerSyncTestLogWriter import com.powersync.TestConnector import com.powersync.bucket.WriteCheckpointData import com.powersync.bucket.WriteCheckpointResponse import com.powersync.createPowerSyncDatabaseImpl -import com.powersync.db.PowerSyncDatabaseImpl import com.powersync.db.schema.Schema import com.powersync.sync.LegacySyncImplementation import com.powersync.sync.configureSyncHttpClient @@ -30,7 +29,7 @@ import kotlinx.io.files.Path import kotlinx.serialization.json.JsonElement import kotlin.coroutines.resume -expect val factory: DatabaseDriverFactory +expect val factory: PersistentDriverFactory expect fun cleanup(path: String) diff --git a/core/src/commonIntegrationTest/kotlin/com/powersync/testutils/UserRow.kt b/common/src/commonIntegrationTest/kotlin/com/powersync/testutils/UserRow.kt similarity index 100% rename from core/src/commonIntegrationTest/kotlin/com/powersync/testutils/UserRow.kt rename to common/src/commonIntegrationTest/kotlin/com/powersync/testutils/UserRow.kt diff --git a/core/src/commonJava/kotlin/com/powersync/db/ActiveInstanceStore.commonJava.kt b/common/src/commonJava/kotlin/com/powersync/db/ActiveInstanceStore.commonJava.kt similarity index 100% rename from core/src/commonJava/kotlin/com/powersync/db/ActiveInstanceStore.commonJava.kt rename to common/src/commonJava/kotlin/com/powersync/db/ActiveInstanceStore.commonJava.kt diff --git a/core/src/commonMain/kotlin/BuildConfig.kt b/common/src/commonMain/kotlin/BuildConfig.kt similarity index 50% rename from core/src/commonMain/kotlin/BuildConfig.kt rename to common/src/commonMain/kotlin/BuildConfig.kt index 81f23796..4cec9226 100644 --- a/core/src/commonMain/kotlin/BuildConfig.kt +++ b/common/src/commonMain/kotlin/BuildConfig.kt @@ -1,4 +1,5 @@ -@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") +@Suppress + ("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") internal expect object BuildConfig { val isDebug: Boolean } diff --git a/common/src/commonMain/kotlin/com/powersync/DatabaseDriverFactory.kt b/common/src/commonMain/kotlin/com/powersync/DatabaseDriverFactory.kt new file mode 100644 index 00000000..85692afb --- /dev/null +++ b/common/src/commonMain/kotlin/com/powersync/DatabaseDriverFactory.kt @@ -0,0 +1,68 @@ +package com.powersync + +import androidx.sqlite.SQLiteConnection +import io.ktor.client.HttpClient +import io.ktor.client.HttpClientConfig + +public interface PowerSyncPlatform { + public fun openInMemoryConnection(): SQLiteConnection + + public fun configureHttpClient(block: HttpClientConfig<*>.() -> Unit): HttpClient +} + +public interface PersistentDriverFactory { + public val platform: PowerSyncPlatform + + public fun resolveDefaultDatabasePath(dbFilename: String): String + + /** + * Opens a SQLite connection on [path] with [openFlags]. + * + * The connection should have the PowerSync core extension loaded. + */ + public fun openConnection( + path: String, + openFlags: Int, + ): SQLiteConnection + + public fun openConnection( + dbFilename: String, + dbDirectory: String?, + readOnly: Boolean = false, + ): SQLiteConnection { + val dbPath = + if (dbDirectory != null) { + "$dbDirectory/$dbFilename" + } else { + resolveDefaultDatabasePath(dbFilename) + } + + return openConnection( + dbPath, + if (readOnly) { + SQLITE_OPEN_READONLY + } else { + SQLITE_OPEN_READWRITE or SQLITE_OPEN_CREATE + }, + ) + } +} + +/** + * Resolves a path to the loadable PowerSync core extension library. + * + * This library must be loaded on all databases using the PowerSync SDK. On platforms where the + * extension is linked statically (only watchOS at the moment), this returns `null`. + * + * When using the PowerSync SDK directly, there is no need to invoke this method. It is intended for + * configuring external database connections not managed by PowerSync to work with the PowerSync + * SDK. + */ +@ExperimentalPowerSyncAPI +@Throws(PowerSyncException::class) +public expect fun resolvePowerSyncLoadableExtensionPath(): String? + +private const val SQLITE_OPEN_READONLY = 0x01 +private const val SQLITE_OPEN_READWRITE = 0x02 +private const val SQLITE_OPEN_CREATE = 0x04 + diff --git a/core/src/commonMain/kotlin/com/powersync/ExperimentalPowerSyncAPI.kt b/common/src/commonMain/kotlin/com/powersync/ExperimentalPowerSyncAPI.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/ExperimentalPowerSyncAPI.kt rename to common/src/commonMain/kotlin/com/powersync/ExperimentalPowerSyncAPI.kt diff --git a/common/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt b/common/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt new file mode 100644 index 00000000..30d45980 --- /dev/null +++ b/common/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt @@ -0,0 +1,266 @@ +package com.powersync + +import co.touchlab.kermit.Logger +import com.powersync.bucket.StreamPriority +import com.powersync.connectors.PowerSyncBackendConnector +import com.powersync.db.ActiveDatabaseGroup +import com.powersync.db.ActiveDatabaseResource +import com.powersync.db.PowerSyncDatabaseImpl +import com.powersync.db.Queries +import com.powersync.db.crud.CrudBatch +import com.powersync.db.crud.CrudTransaction +import com.powersync.db.driver.SQLiteConnectionPool +import com.powersync.db.driver.SingleConnectionPool +import com.powersync.db.schema.Schema +import com.powersync.sync.SyncOptions +import com.powersync.sync.SyncStatus +import com.powersync.sync.SyncStream +import com.powersync.utils.JsonParam +import com.powersync.utils.generateLogger +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.firstOrNull +import kotlin.coroutines.cancellation.CancellationException + +/** + * A PowerSync managed database. + * + * Use one instance per database file. + * + * Use [PowerSyncDatabase.connect] to connect to the PowerSync service, to keep the local database in sync with the remote database. + * + * All changes to local tables are automatically recorded, whether connected or not. Once connected, the changes are uploaded. + */ +public interface PowerSyncDatabase : Queries { + /** + * Indicates if the PowerSync client has been closed. + * A new client is required after a client has been closed. + */ + public val closed: Boolean + + /** + * Identifies the database client. + * This is typically the database name. + */ + public val identifier: String + + /** + * The current sync status. + */ + public val currentStatus: SyncStatus + + /** + * Replace the schema with a new version. This is for advanced use cases - typically the schema + * should just be specified once in the constructor. + * + * Cannot be used while connected - this should only be called before connect. + */ + @Throws(PowerSyncException::class, CancellationException::class) + public suspend fun updateSchema(schema: Schema) + + /** + * Suspend function that resolves when the first sync has occurred + */ + @Throws(PowerSyncException::class, CancellationException::class) + public suspend fun waitForFirstSync() + + /** + * Suspend function that resolves when the first sync covering at least all buckets with the + * given [priority] (or a higher one, since those would be synchronized first) has completed. + */ + @Throws(PowerSyncException::class, CancellationException::class) + public suspend fun waitForFirstSync(priority: StreamPriority) + + /** + * Connect to the PowerSync service, and keep the databases in sync. + * + * The connection is automatically re-opened if it fails for any reason. + * + * Use @param [connector] to specify the [PowerSyncBackendConnector]. + * Use @param [crudThrottleMs] to specify the time between CRUD operations. Defaults to 1000ms. + * Use @param [retryDelayMs] to specify the delay between retries after failure. Defaults to 5000ms. + * Use @param [params] to specify sync parameters from the client. + * + * Example usage: + * ``` + * val params = JsonParam.Map( + * mapOf( + * "name" to JsonParam.String("John Doe"), + * "age" to JsonParam.Number(30), + * "isStudent" to JsonParam.Boolean(false) + * ) + * ) + * + * connect( + * connector = connector, + * crudThrottleMs = 2000L, + * retryDelayMs = 10000L, + * params = params + * ) + * ``` + * TODO: Internal Team - Status changes are reported on [statusStream]. + */ + @Throws(PowerSyncException::class, CancellationException::class) + public suspend fun connect( + connector: PowerSyncBackendConnector, + crudThrottleMs: Long = 1000L, + retryDelayMs: Long = 5000L, + params: Map = emptyMap(), + options: SyncOptions = SyncOptions.defaults, + ) + + /** + * Get a batch of crud data to upload. + * + * Returns null if there is no data to upload. + * + * Use this from the [PowerSyncBackendConnector.uploadData]` callback. + * + * Once the data have been successfully uploaded, call [CrudBatch.complete] before + * requesting the next batch. + * + * Use [limit] to specify the maximum number of updates to return in a single + * batch. Default is 100. + * + * This method does include transaction ids in the result, but does not group + * data by transaction. One batch may contain data from multiple transactions, + * and a single transaction may be split over multiple batches. + */ + @Throws(PowerSyncException::class, CancellationException::class) + public suspend fun getCrudBatch(limit: Int = 100): CrudBatch? + + /** + * Get the next recorded transaction to upload. + * + * Returns null if there is no data to upload. + * + * Use this from the [PowerSyncBackendConnector.uploadData] callback. + * + * Once the data have been successfully uploaded, call [CrudTransaction.complete] before + * requesting the next transaction. + * + * Unlike [getCrudBatch], this only returns data from a single transaction at a time. + * All data for the transaction is loaded into memory. + */ + @Throws(PowerSyncException::class, CancellationException::class) + public suspend fun getNextCrudTransaction(): CrudTransaction? = getCrudTransactions().firstOrNull() + + /** + * Obtains a flow emitting completed transactions with local writes against the database. + + * This is typically used from the [PowerSyncBackendConnector.uploadData] callback. + * Each entry emitted by the returned flow is a full transaction containing all local writes + * made while that transaction was active. + * + * Unlike [getNextCrudTransaction], which always returns the oldest transaction that hasn't + * been [CrudTransaction.complete]d yet, this flow can be used to collect multiple transactions. + * Calling [CrudTransaction.complete] will mark that and all prior transactions emitted by the + * flow as completed. + * + * This can be used to upload multiple transactions in a single batch, e.g with: + * + * ```Kotlin + * val batch = mutableListOf() + * var lastTx: CrudTransaction? = null + * + * database.getCrudTransactions().takeWhile { batch.size < 100 }.collect { + * batch.addAll(it.crud) + * lastTx = it + * } + * + * if (batch.isNotEmpty()) { + * uploadChanges(batch) + * lastTx!!.complete(null) + * } + * ```` + * + * If there is no local data to upload, returns an empty flow. + */ + public fun getCrudTransactions(): Flow + + /** + * Convenience method to get the current version of PowerSync. + */ + @Throws(PowerSyncException::class, CancellationException::class) + public suspend fun getPowerSyncVersion(): String + + /** + * Create a [SyncStream] instance for the given [name] and [parameters]. + * + * Use [SyncStream.subscribe] on the returned instance to subscribe to the stream. + */ + @ExperimentalPowerSyncAPI + public fun syncStream( + name: String, + parameters: Map? = null, + ): SyncStream + + /** + * Close the sync connection. + * + * Use [connect] to connect again. + */ + @Throws(PowerSyncException::class, CancellationException::class) + public suspend fun disconnect() + + /** + * Disconnect and clear the database. + * Use this when logging out. + * The database can still be queried after this is called, but the tables + * would be empty. + * + * To preserve data in local-only tables, set clearLocal to false. + */ + @Throws(PowerSyncException::class, CancellationException::class) + public suspend fun disconnectAndClear(clearLocal: Boolean = true) + + /** + * Close the database, releasing resources. + * Also disconnects any active connection. + * + * Once close is called, this database cannot be used again - a new one must be constructed. + */ + @Throws(PowerSyncException::class, CancellationException::class) + public suspend fun close() + + public companion object PowerSyncOpenFactory { + /** + * Creates a PowerSync database managed by an external connection pool. + * + * In this case, PowerSync will not open its own SQLite connections, but rather refer to + * connections in the [pool]. + * + * The `identifier` parameter should be a name identifying the path of the database. The + * PowerSync SDK will emit a warning if multiple databases are opened with the same + * identifier, and uses internal locks to ensure these two databases are not synced at the + * same time (which would be inefficient and can cause consistency issues). + */ + @ExperimentalPowerSyncAPI + public fun opened( + pool: SQLiteConnectionPool, + scope: CoroutineScope, + schema: Schema, + identifier: String, + logger: Logger, + ): PowerSyncDatabase { + val group = ActiveDatabaseGroup.referenceDatabase(logger, identifier) + return openedWithGroup(pool, scope, schema, logger, group) + } + + @ExperimentalPowerSyncAPI + public fun openedWithGroup( + pool: SQLiteConnectionPool, + scope: CoroutineScope, + schema: Schema, + logger: Logger, + group: Pair, + ): PowerSyncDatabase = + PowerSyncDatabaseImpl( + schema, + scope, + pool, + logger, + group, + ) + } +} diff --git a/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabaseFactory.kt b/common/src/commonMain/kotlin/com/powersync/PowerSyncDatabaseFactory.kt similarity index 96% rename from core/src/commonMain/kotlin/com/powersync/PowerSyncDatabaseFactory.kt rename to common/src/commonMain/kotlin/com/powersync/PowerSyncDatabaseFactory.kt index 6e0556d1..6fd6cb67 100644 --- a/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabaseFactory.kt +++ b/common/src/commonMain/kotlin/com/powersync/PowerSyncDatabaseFactory.kt @@ -20,7 +20,7 @@ public const val DEFAULT_DB_FILENAME: String = "powersync.db" @OptIn(DelicateCoroutinesApi::class) @DefaultArgumentInterop.Enabled public fun PowerSyncDatabase( - factory: DatabaseDriverFactory, + factory: PersistentDriverFactory, schema: Schema, dbFilename: String = DEFAULT_DB_FILENAME, scope: CoroutineScope = GlobalScope, @@ -45,7 +45,7 @@ public fun PowerSyncDatabase( @OptIn(ExperimentalPowerSyncAPI::class) internal fun createPowerSyncDatabaseImpl( - factory: DatabaseDriverFactory, + factory: PersistentDriverFactory, schema: Schema, dbFilename: String, scope: CoroutineScope, diff --git a/core/src/commonMain/kotlin/com/powersync/PowerSyncException.kt b/common/src/commonMain/kotlin/com/powersync/PowerSyncException.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/PowerSyncException.kt rename to common/src/commonMain/kotlin/com/powersync/PowerSyncException.kt diff --git a/core/src/commonMain/kotlin/com/powersync/attachments/Attachment.kt b/common/src/commonMain/kotlin/com/powersync/attachments/Attachment.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/attachments/Attachment.kt rename to common/src/commonMain/kotlin/com/powersync/attachments/Attachment.kt diff --git a/core/src/commonMain/kotlin/com/powersync/attachments/AttachmentQueue.kt b/common/src/commonMain/kotlin/com/powersync/attachments/AttachmentQueue.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/attachments/AttachmentQueue.kt rename to common/src/commonMain/kotlin/com/powersync/attachments/AttachmentQueue.kt diff --git a/core/src/commonMain/kotlin/com/powersync/attachments/AttachmentService.kt b/common/src/commonMain/kotlin/com/powersync/attachments/AttachmentService.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/attachments/AttachmentService.kt rename to common/src/commonMain/kotlin/com/powersync/attachments/AttachmentService.kt diff --git a/core/src/commonMain/kotlin/com/powersync/attachments/AttachmentTable.kt b/common/src/commonMain/kotlin/com/powersync/attachments/AttachmentTable.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/attachments/AttachmentTable.kt rename to common/src/commonMain/kotlin/com/powersync/attachments/AttachmentTable.kt diff --git a/core/src/commonMain/kotlin/com/powersync/attachments/LocalStorage.kt b/common/src/commonMain/kotlin/com/powersync/attachments/LocalStorage.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/attachments/LocalStorage.kt rename to common/src/commonMain/kotlin/com/powersync/attachments/LocalStorage.kt diff --git a/core/src/commonMain/kotlin/com/powersync/attachments/README.md b/common/src/commonMain/kotlin/com/powersync/attachments/README.md similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/attachments/README.md rename to common/src/commonMain/kotlin/com/powersync/attachments/README.md diff --git a/core/src/commonMain/kotlin/com/powersync/attachments/RemoteStorage.kt b/common/src/commonMain/kotlin/com/powersync/attachments/RemoteStorage.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/attachments/RemoteStorage.kt rename to common/src/commonMain/kotlin/com/powersync/attachments/RemoteStorage.kt diff --git a/core/src/commonMain/kotlin/com/powersync/attachments/SyncErrorHandler.kt b/common/src/commonMain/kotlin/com/powersync/attachments/SyncErrorHandler.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/attachments/SyncErrorHandler.kt rename to common/src/commonMain/kotlin/com/powersync/attachments/SyncErrorHandler.kt diff --git a/core/src/commonMain/kotlin/com/powersync/attachments/implementation/AttachmentContextImpl.kt b/common/src/commonMain/kotlin/com/powersync/attachments/implementation/AttachmentContextImpl.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/attachments/implementation/AttachmentContextImpl.kt rename to common/src/commonMain/kotlin/com/powersync/attachments/implementation/AttachmentContextImpl.kt diff --git a/core/src/commonMain/kotlin/com/powersync/attachments/implementation/AttachmentServiceImpl.kt b/common/src/commonMain/kotlin/com/powersync/attachments/implementation/AttachmentServiceImpl.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/attachments/implementation/AttachmentServiceImpl.kt rename to common/src/commonMain/kotlin/com/powersync/attachments/implementation/AttachmentServiceImpl.kt diff --git a/core/src/commonMain/kotlin/com/powersync/attachments/storage/IOLocalStorageAdapter.kt b/common/src/commonMain/kotlin/com/powersync/attachments/storage/IOLocalStorageAdapter.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/attachments/storage/IOLocalStorageAdapter.kt rename to common/src/commonMain/kotlin/com/powersync/attachments/storage/IOLocalStorageAdapter.kt diff --git a/core/src/commonMain/kotlin/com/powersync/attachments/sync/SyncingService.kt b/common/src/commonMain/kotlin/com/powersync/attachments/sync/SyncingService.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/attachments/sync/SyncingService.kt rename to common/src/commonMain/kotlin/com/powersync/attachments/sync/SyncingService.kt diff --git a/core/src/commonMain/kotlin/com/powersync/bucket/BucketChecksum.kt b/common/src/commonMain/kotlin/com/powersync/bucket/BucketChecksum.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/bucket/BucketChecksum.kt rename to common/src/commonMain/kotlin/com/powersync/bucket/BucketChecksum.kt diff --git a/core/src/commonMain/kotlin/com/powersync/bucket/BucketRequest.kt b/common/src/commonMain/kotlin/com/powersync/bucket/BucketRequest.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/bucket/BucketRequest.kt rename to common/src/commonMain/kotlin/com/powersync/bucket/BucketRequest.kt diff --git a/core/src/commonMain/kotlin/com/powersync/bucket/BucketState.kt b/common/src/commonMain/kotlin/com/powersync/bucket/BucketState.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/bucket/BucketState.kt rename to common/src/commonMain/kotlin/com/powersync/bucket/BucketState.kt diff --git a/core/src/commonMain/kotlin/com/powersync/bucket/BucketStorage.kt b/common/src/commonMain/kotlin/com/powersync/bucket/BucketStorage.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/bucket/BucketStorage.kt rename to common/src/commonMain/kotlin/com/powersync/bucket/BucketStorage.kt diff --git a/core/src/commonMain/kotlin/com/powersync/bucket/BucketStorageImpl.kt b/common/src/commonMain/kotlin/com/powersync/bucket/BucketStorageImpl.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/bucket/BucketStorageImpl.kt rename to common/src/commonMain/kotlin/com/powersync/bucket/BucketStorageImpl.kt diff --git a/core/src/commonMain/kotlin/com/powersync/bucket/Checkpoint.kt b/common/src/commonMain/kotlin/com/powersync/bucket/Checkpoint.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/bucket/Checkpoint.kt rename to common/src/commonMain/kotlin/com/powersync/bucket/Checkpoint.kt diff --git a/core/src/commonMain/kotlin/com/powersync/bucket/ChecksumCache.kt b/common/src/commonMain/kotlin/com/powersync/bucket/ChecksumCache.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/bucket/ChecksumCache.kt rename to common/src/commonMain/kotlin/com/powersync/bucket/ChecksumCache.kt diff --git a/core/src/commonMain/kotlin/com/powersync/bucket/LocalOperationCounters.kt b/common/src/commonMain/kotlin/com/powersync/bucket/LocalOperationCounters.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/bucket/LocalOperationCounters.kt rename to common/src/commonMain/kotlin/com/powersync/bucket/LocalOperationCounters.kt diff --git a/core/src/commonMain/kotlin/com/powersync/bucket/OpType.kt b/common/src/commonMain/kotlin/com/powersync/bucket/OpType.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/bucket/OpType.kt rename to common/src/commonMain/kotlin/com/powersync/bucket/OpType.kt diff --git a/core/src/commonMain/kotlin/com/powersync/bucket/OplogEntry.kt b/common/src/commonMain/kotlin/com/powersync/bucket/OplogEntry.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/bucket/OplogEntry.kt rename to common/src/commonMain/kotlin/com/powersync/bucket/OplogEntry.kt diff --git a/core/src/commonMain/kotlin/com/powersync/bucket/SqliteOp.kt b/common/src/commonMain/kotlin/com/powersync/bucket/SqliteOp.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/bucket/SqliteOp.kt rename to common/src/commonMain/kotlin/com/powersync/bucket/SqliteOp.kt diff --git a/core/src/commonMain/kotlin/com/powersync/bucket/StreamPriority.kt b/common/src/commonMain/kotlin/com/powersync/bucket/StreamPriority.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/bucket/StreamPriority.kt rename to common/src/commonMain/kotlin/com/powersync/bucket/StreamPriority.kt diff --git a/core/src/commonMain/kotlin/com/powersync/bucket/WriteCheckpointResult.kt b/common/src/commonMain/kotlin/com/powersync/bucket/WriteCheckpointResult.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/bucket/WriteCheckpointResult.kt rename to common/src/commonMain/kotlin/com/powersync/bucket/WriteCheckpointResult.kt diff --git a/core/src/commonMain/kotlin/com/powersync/connectors/PowerSyncBackendConnector.kt b/common/src/commonMain/kotlin/com/powersync/connectors/PowerSyncBackendConnector.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/connectors/PowerSyncBackendConnector.kt rename to common/src/commonMain/kotlin/com/powersync/connectors/PowerSyncBackendConnector.kt diff --git a/core/src/commonMain/kotlin/com/powersync/connectors/PowerSyncCredentials.kt b/common/src/commonMain/kotlin/com/powersync/connectors/PowerSyncCredentials.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/connectors/PowerSyncCredentials.kt rename to common/src/commonMain/kotlin/com/powersync/connectors/PowerSyncCredentials.kt diff --git a/core/src/commonMain/kotlin/com/powersync/db/ActiveInstanceStore.kt b/common/src/commonMain/kotlin/com/powersync/db/ActiveInstanceStore.kt similarity index 77% rename from core/src/commonMain/kotlin/com/powersync/db/ActiveInstanceStore.kt rename to common/src/commonMain/kotlin/com/powersync/db/ActiveInstanceStore.kt index 1ba3faed..e6d011d0 100644 --- a/core/src/commonMain/kotlin/com/powersync/db/ActiveInstanceStore.kt +++ b/common/src/commonMain/kotlin/com/powersync/db/ActiveInstanceStore.kt @@ -12,7 +12,7 @@ import kotlinx.coroutines.sync.Mutex internal expect fun disposeWhenDeallocated(resource: ActiveDatabaseResource): Any /** - * An collection of PowerSync databases with the same path / identifier. + * A collection of PowerSync databases with the same path / identifier. * * We expect that each group will only ever have one database because we encourage users to write their databases as * singletons. We print a warning when two databases are part of the same group. @@ -20,15 +20,15 @@ internal expect fun disposeWhenDeallocated(resource: ActiveDatabaseResource): An * duplicate resources being used. For this reason, each active database group has a coroutine mutex guarding the * sync job. */ -internal class ActiveDatabaseGroup( - val identifier: String, +public class ActiveDatabaseGroup internal constructor( + internal val identifier: String, private val collection: GroupsCollection, ) { internal var refCount = 0 // Guarded by companion object internal val syncMutex = Mutex() internal val writeLockMutex = Mutex() - fun removeUsage() { + internal fun removeUsage() { collection.synchronize { if (--refCount == 0) { collection.allGroups.remove(this) @@ -36,7 +36,13 @@ internal class ActiveDatabaseGroup( } } - internal open class GroupsCollection : Synchronizable() { + /** + * A collection of [ActiveDatabaseGroup]s. + * + * Typically, one uses the singleton instance that is the companion object of that class, but separate groups can be + * used for testing. + */ + public open class GroupsCollection : Synchronizable() { internal val allGroups = mutableListOf() private fun findGroup( @@ -61,7 +67,7 @@ internal class ActiveDatabaseGroup( resolvedGroup } - internal fun referenceDatabase( + public fun referenceDatabase( warnOnDuplicate: Logger, identifier: String, ): Pair { @@ -72,7 +78,7 @@ internal class ActiveDatabaseGroup( } } - companion object : GroupsCollection() { + public companion object : GroupsCollection() { internal val multipleInstancesMessage = """ Multiple PowerSync instances for the same database have been detected. @@ -82,13 +88,13 @@ internal class ActiveDatabaseGroup( } } -internal class ActiveDatabaseResource( - val group: ActiveDatabaseGroup, +public class ActiveDatabaseResource( + internal val group: ActiveDatabaseGroup, ) { - val disposed = AtomicBoolean(false) + internal val disposed = AtomicBoolean(false) - fun dispose() { - if (disposed.compareAndSet(false, true)) { + public fun dispose() { + if (disposed.compareAndSet(expected = false, new = true)) { group.removeUsage() } } diff --git a/core/src/commonMain/kotlin/com/powersync/db/Functions.kt b/common/src/commonMain/kotlin/com/powersync/db/Functions.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/db/Functions.kt rename to common/src/commonMain/kotlin/com/powersync/db/Functions.kt diff --git a/core/src/commonMain/kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt b/common/src/commonMain/kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt rename to common/src/commonMain/kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt diff --git a/core/src/commonMain/kotlin/com/powersync/db/Queries.kt b/common/src/commonMain/kotlin/com/powersync/db/Queries.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/db/Queries.kt rename to common/src/commonMain/kotlin/com/powersync/db/Queries.kt diff --git a/core/src/commonMain/kotlin/com/powersync/db/SqlCursor.kt b/common/src/commonMain/kotlin/com/powersync/db/SqlCursor.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/db/SqlCursor.kt rename to common/src/commonMain/kotlin/com/powersync/db/SqlCursor.kt diff --git a/core/src/commonMain/kotlin/com/powersync/db/StreamImpl.kt b/common/src/commonMain/kotlin/com/powersync/db/StreamImpl.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/db/StreamImpl.kt rename to common/src/commonMain/kotlin/com/powersync/db/StreamImpl.kt diff --git a/core/src/commonMain/kotlin/com/powersync/db/crud/CrudBatch.kt b/common/src/commonMain/kotlin/com/powersync/db/crud/CrudBatch.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/db/crud/CrudBatch.kt rename to common/src/commonMain/kotlin/com/powersync/db/crud/CrudBatch.kt diff --git a/core/src/commonMain/kotlin/com/powersync/db/crud/CrudEntry.kt b/common/src/commonMain/kotlin/com/powersync/db/crud/CrudEntry.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/db/crud/CrudEntry.kt rename to common/src/commonMain/kotlin/com/powersync/db/crud/CrudEntry.kt diff --git a/core/src/commonMain/kotlin/com/powersync/db/crud/CrudRow.kt b/common/src/commonMain/kotlin/com/powersync/db/crud/CrudRow.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/db/crud/CrudRow.kt rename to common/src/commonMain/kotlin/com/powersync/db/crud/CrudRow.kt diff --git a/core/src/commonMain/kotlin/com/powersync/db/crud/CrudTransaction.kt b/common/src/commonMain/kotlin/com/powersync/db/crud/CrudTransaction.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/db/crud/CrudTransaction.kt rename to common/src/commonMain/kotlin/com/powersync/db/crud/CrudTransaction.kt diff --git a/core/src/commonMain/kotlin/com/powersync/db/crud/SerializedRow.kt b/common/src/commonMain/kotlin/com/powersync/db/crud/SerializedRow.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/db/crud/SerializedRow.kt rename to common/src/commonMain/kotlin/com/powersync/db/crud/SerializedRow.kt diff --git a/core/src/commonMain/kotlin/com/powersync/db/crud/UpdateType.kt b/common/src/commonMain/kotlin/com/powersync/db/crud/UpdateType.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/db/crud/UpdateType.kt rename to common/src/commonMain/kotlin/com/powersync/db/crud/UpdateType.kt diff --git a/core/src/commonMain/kotlin/com/powersync/db/crud/UploadQueueStats.kt b/common/src/commonMain/kotlin/com/powersync/db/crud/UploadQueueStats.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/db/crud/UploadQueueStats.kt rename to common/src/commonMain/kotlin/com/powersync/db/crud/UploadQueueStats.kt diff --git a/core/src/commonMain/kotlin/com/powersync/db/driver/InternalConnectionPool.kt b/common/src/commonMain/kotlin/com/powersync/db/driver/InternalConnectionPool.kt similarity index 95% rename from core/src/commonMain/kotlin/com/powersync/db/driver/InternalConnectionPool.kt rename to common/src/commonMain/kotlin/com/powersync/db/driver/InternalConnectionPool.kt index 682d5163..1663d359 100644 --- a/core/src/commonMain/kotlin/com/powersync/db/driver/InternalConnectionPool.kt +++ b/common/src/commonMain/kotlin/com/powersync/db/driver/InternalConnectionPool.kt @@ -2,9 +2,8 @@ package com.powersync.db.driver import androidx.sqlite.SQLiteConnection import androidx.sqlite.execSQL -import com.powersync.DatabaseDriverFactory import com.powersync.ExperimentalPowerSyncAPI -import com.powersync.openDatabase +import com.powersync.PersistentDriverFactory import com.powersync.utils.JsonUtil import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableSharedFlow @@ -15,7 +14,7 @@ import kotlinx.coroutines.sync.withLock @OptIn(ExperimentalPowerSyncAPI::class) internal class InternalConnectionPool( - private val factory: DatabaseDriverFactory, + private val factory: PersistentDriverFactory, private val scope: CoroutineScope, private val dbFilename: String, private val dbDirectory: String?, @@ -29,8 +28,7 @@ internal class InternalConnectionPool( private fun newConnection(readOnly: Boolean): SQLiteConnection { val connection = - openDatabase( - factory = factory, + factory.openConnection( dbFilename = dbFilename, dbDirectory = dbDirectory, readOnly = false, diff --git a/core/src/commonMain/kotlin/com/powersync/db/driver/LazyPool.kt b/common/src/commonMain/kotlin/com/powersync/db/driver/LazyPool.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/db/driver/LazyPool.kt rename to common/src/commonMain/kotlin/com/powersync/db/driver/LazyPool.kt diff --git a/core/src/commonMain/kotlin/com/powersync/db/driver/RawConnectionLease.kt b/common/src/commonMain/kotlin/com/powersync/db/driver/RawConnectionLease.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/db/driver/RawConnectionLease.kt rename to common/src/commonMain/kotlin/com/powersync/db/driver/RawConnectionLease.kt diff --git a/core/src/commonMain/kotlin/com/powersync/db/driver/ReadPool.kt b/common/src/commonMain/kotlin/com/powersync/db/driver/ReadPool.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/db/driver/ReadPool.kt rename to common/src/commonMain/kotlin/com/powersync/db/driver/ReadPool.kt diff --git a/core/src/commonMain/kotlin/com/powersync/db/driver/SQLiteConnectionPool.kt b/common/src/commonMain/kotlin/com/powersync/db/driver/SQLiteConnectionPool.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/db/driver/SQLiteConnectionPool.kt rename to common/src/commonMain/kotlin/com/powersync/db/driver/SQLiteConnectionPool.kt diff --git a/core/src/commonMain/kotlin/com/powersync/db/driver/SingleConnectionPool.kt b/common/src/commonMain/kotlin/com/powersync/db/driver/SingleConnectionPool.kt similarity index 95% rename from core/src/commonMain/kotlin/com/powersync/db/driver/SingleConnectionPool.kt rename to common/src/commonMain/kotlin/com/powersync/db/driver/SingleConnectionPool.kt index 41337f96..4a9c2b36 100644 --- a/core/src/commonMain/kotlin/com/powersync/db/driver/SingleConnectionPool.kt +++ b/common/src/commonMain/kotlin/com/powersync/db/driver/SingleConnectionPool.kt @@ -13,7 +13,7 @@ import kotlinx.coroutines.sync.withLock * This does not provide any concurrency, but is still a reasonable implementation to use for e.g. tests. */ @OptIn(ExperimentalPowerSyncAPI::class) -internal class SingleConnectionPool( +public class SingleConnectionPool( private val conn: SQLiteConnection, ) : SQLiteConnectionPool { private val mutex: Mutex = Mutex() @@ -42,9 +42,8 @@ internal class SingleConnectionPool( override suspend fun withAllConnections( action: suspend (writer: SQLiteConnectionLease, readers: List) -> R, - ) = write { writer -> + ): Unit = write { writer -> action(writer, emptyList()) - Unit } override val updates: SharedFlow> diff --git a/core/src/commonMain/kotlin/com/powersync/db/internal/ConnectionContext.kt b/common/src/commonMain/kotlin/com/powersync/db/internal/ConnectionContext.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/db/internal/ConnectionContext.kt rename to common/src/commonMain/kotlin/com/powersync/db/internal/ConnectionContext.kt diff --git a/core/src/commonMain/kotlin/com/powersync/db/internal/InternalDatabase.kt b/common/src/commonMain/kotlin/com/powersync/db/internal/InternalDatabase.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/db/internal/InternalDatabase.kt rename to common/src/commonMain/kotlin/com/powersync/db/internal/InternalDatabase.kt diff --git a/core/src/commonMain/kotlin/com/powersync/db/internal/InternalDatabaseImpl.kt b/common/src/commonMain/kotlin/com/powersync/db/internal/InternalDatabaseImpl.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/db/internal/InternalDatabaseImpl.kt rename to common/src/commonMain/kotlin/com/powersync/db/internal/InternalDatabaseImpl.kt diff --git a/core/src/commonMain/kotlin/com/powersync/db/internal/InternalTable.kt b/common/src/commonMain/kotlin/com/powersync/db/internal/InternalTable.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/db/internal/InternalTable.kt rename to common/src/commonMain/kotlin/com/powersync/db/internal/InternalTable.kt diff --git a/core/src/commonMain/kotlin/com/powersync/db/internal/PowerSyncTransaction.kt b/common/src/commonMain/kotlin/com/powersync/db/internal/PowerSyncTransaction.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/db/internal/PowerSyncTransaction.kt rename to common/src/commonMain/kotlin/com/powersync/db/internal/PowerSyncTransaction.kt diff --git a/core/src/commonMain/kotlin/com/powersync/db/internal/PowerSyncVersion.kt b/common/src/commonMain/kotlin/com/powersync/db/internal/PowerSyncVersion.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/db/internal/PowerSyncVersion.kt rename to common/src/commonMain/kotlin/com/powersync/db/internal/PowerSyncVersion.kt diff --git a/core/src/commonMain/kotlin/com/powersync/db/schema/BaseTable.kt b/common/src/commonMain/kotlin/com/powersync/db/schema/BaseTable.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/db/schema/BaseTable.kt rename to common/src/commonMain/kotlin/com/powersync/db/schema/BaseTable.kt diff --git a/core/src/commonMain/kotlin/com/powersync/db/schema/Column.kt b/common/src/commonMain/kotlin/com/powersync/db/schema/Column.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/db/schema/Column.kt rename to common/src/commonMain/kotlin/com/powersync/db/schema/Column.kt diff --git a/core/src/commonMain/kotlin/com/powersync/db/schema/ColumnType.kt b/common/src/commonMain/kotlin/com/powersync/db/schema/ColumnType.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/db/schema/ColumnType.kt rename to common/src/commonMain/kotlin/com/powersync/db/schema/ColumnType.kt diff --git a/core/src/commonMain/kotlin/com/powersync/db/schema/Index.kt b/common/src/commonMain/kotlin/com/powersync/db/schema/Index.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/db/schema/Index.kt rename to common/src/commonMain/kotlin/com/powersync/db/schema/Index.kt diff --git a/core/src/commonMain/kotlin/com/powersync/db/schema/IndexedColumn.kt b/common/src/commonMain/kotlin/com/powersync/db/schema/IndexedColumn.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/db/schema/IndexedColumn.kt rename to common/src/commonMain/kotlin/com/powersync/db/schema/IndexedColumn.kt diff --git a/core/src/commonMain/kotlin/com/powersync/db/schema/RawTable.kt b/common/src/commonMain/kotlin/com/powersync/db/schema/RawTable.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/db/schema/RawTable.kt rename to common/src/commonMain/kotlin/com/powersync/db/schema/RawTable.kt diff --git a/core/src/commonMain/kotlin/com/powersync/db/schema/Schema.kt b/common/src/commonMain/kotlin/com/powersync/db/schema/Schema.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/db/schema/Schema.kt rename to common/src/commonMain/kotlin/com/powersync/db/schema/Schema.kt diff --git a/core/src/commonMain/kotlin/com/powersync/db/schema/Table.kt b/common/src/commonMain/kotlin/com/powersync/db/schema/Table.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/db/schema/Table.kt rename to common/src/commonMain/kotlin/com/powersync/db/schema/Table.kt diff --git a/core/src/commonMain/kotlin/com/powersync/db/schema/validation.kt b/common/src/commonMain/kotlin/com/powersync/db/schema/validation.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/db/schema/validation.kt rename to common/src/commonMain/kotlin/com/powersync/db/schema/validation.kt diff --git a/core/src/commonMain/kotlin/com/powersync/sync/Instruction.kt b/common/src/commonMain/kotlin/com/powersync/sync/Instruction.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/sync/Instruction.kt rename to common/src/commonMain/kotlin/com/powersync/sync/Instruction.kt diff --git a/core/src/commonMain/kotlin/com/powersync/sync/LegacySyncImplementation.kt b/common/src/commonMain/kotlin/com/powersync/sync/LegacySyncImplementation.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/sync/LegacySyncImplementation.kt rename to common/src/commonMain/kotlin/com/powersync/sync/LegacySyncImplementation.kt diff --git a/core/src/commonMain/kotlin/com/powersync/sync/Progress.kt b/common/src/commonMain/kotlin/com/powersync/sync/Progress.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/sync/Progress.kt rename to common/src/commonMain/kotlin/com/powersync/sync/Progress.kt diff --git a/core/src/commonMain/kotlin/com/powersync/sync/Stream.kt b/common/src/commonMain/kotlin/com/powersync/sync/Stream.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/sync/Stream.kt rename to common/src/commonMain/kotlin/com/powersync/sync/Stream.kt diff --git a/core/src/commonMain/kotlin/com/powersync/sync/StreamingSync.kt b/common/src/commonMain/kotlin/com/powersync/sync/StreamingSync.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/sync/StreamingSync.kt rename to common/src/commonMain/kotlin/com/powersync/sync/StreamingSync.kt diff --git a/core/src/commonMain/kotlin/com/powersync/sync/StreamingSyncRequest.kt b/common/src/commonMain/kotlin/com/powersync/sync/StreamingSyncRequest.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/sync/StreamingSyncRequest.kt rename to common/src/commonMain/kotlin/com/powersync/sync/StreamingSyncRequest.kt diff --git a/core/src/commonMain/kotlin/com/powersync/sync/SyncDataBatch.kt b/common/src/commonMain/kotlin/com/powersync/sync/SyncDataBatch.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/sync/SyncDataBatch.kt rename to common/src/commonMain/kotlin/com/powersync/sync/SyncDataBatch.kt diff --git a/core/src/commonMain/kotlin/com/powersync/sync/SyncLine.kt b/common/src/commonMain/kotlin/com/powersync/sync/SyncLine.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/sync/SyncLine.kt rename to common/src/commonMain/kotlin/com/powersync/sync/SyncLine.kt diff --git a/core/src/commonMain/kotlin/com/powersync/sync/SyncLocalDatabaseResult.kt b/common/src/commonMain/kotlin/com/powersync/sync/SyncLocalDatabaseResult.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/sync/SyncLocalDatabaseResult.kt rename to common/src/commonMain/kotlin/com/powersync/sync/SyncLocalDatabaseResult.kt diff --git a/core/src/commonMain/kotlin/com/powersync/sync/SyncOptions.kt b/common/src/commonMain/kotlin/com/powersync/sync/SyncOptions.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/sync/SyncOptions.kt rename to common/src/commonMain/kotlin/com/powersync/sync/SyncOptions.kt diff --git a/core/src/commonMain/kotlin/com/powersync/sync/SyncStatus.kt b/common/src/commonMain/kotlin/com/powersync/sync/SyncStatus.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/sync/SyncStatus.kt rename to common/src/commonMain/kotlin/com/powersync/sync/SyncStatus.kt diff --git a/core/src/commonMain/kotlin/com/powersync/sync/UserAgent.kt b/common/src/commonMain/kotlin/com/powersync/sync/UserAgent.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/sync/UserAgent.kt rename to common/src/commonMain/kotlin/com/powersync/sync/UserAgent.kt diff --git a/core/src/commonMain/kotlin/com/powersync/utils/AtomicMutableSet.kt b/common/src/commonMain/kotlin/com/powersync/utils/AtomicMutableSet.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/utils/AtomicMutableSet.kt rename to common/src/commonMain/kotlin/com/powersync/utils/AtomicMutableSet.kt diff --git a/core/src/commonMain/kotlin/com/powersync/utils/Json.kt b/common/src/commonMain/kotlin/com/powersync/utils/Json.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/utils/Json.kt rename to common/src/commonMain/kotlin/com/powersync/utils/Json.kt diff --git a/core/src/commonMain/kotlin/com/powersync/utils/Log.kt b/common/src/commonMain/kotlin/com/powersync/utils/Log.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/utils/Log.kt rename to common/src/commonMain/kotlin/com/powersync/utils/Log.kt diff --git a/core/src/commonMain/kotlin/com/powersync/utils/Strings.kt b/common/src/commonMain/kotlin/com/powersync/utils/Strings.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/utils/Strings.kt rename to common/src/commonMain/kotlin/com/powersync/utils/Strings.kt diff --git a/core/src/commonMain/kotlin/com/powersync/utils/ThrottleFlow.kt b/common/src/commonMain/kotlin/com/powersync/utils/ThrottleFlow.kt similarity index 100% rename from core/src/commonMain/kotlin/com/powersync/utils/ThrottleFlow.kt rename to common/src/commonMain/kotlin/com/powersync/utils/ThrottleFlow.kt diff --git a/core/src/jvmMain/kotlin/BuildConfig.kt b/common/src/jvmMain/kotlin/BuildConfig.kt similarity index 89% rename from core/src/jvmMain/kotlin/BuildConfig.kt rename to common/src/jvmMain/kotlin/BuildConfig.kt index 86924a14..6350a13c 100644 --- a/core/src/jvmMain/kotlin/BuildConfig.kt +++ b/common/src/jvmMain/kotlin/BuildConfig.kt @@ -8,5 +8,5 @@ internal actual object BuildConfig { */ actual val isDebug: Boolean = System.getProperty("com.powersync.debug") == "true" || - System.getenv("POWERSYNC_JVM_DEBUG") == "true" + System.getenv("POWERSYNC_JVM_DEBUG") == "true" } diff --git a/common/src/jvmMain/kotlin/com/powersync/DatabaseDriverFactory.jvm.kt b/common/src/jvmMain/kotlin/com/powersync/DatabaseDriverFactory.jvm.kt new file mode 100644 index 00000000..2ecb6741 --- /dev/null +++ b/common/src/jvmMain/kotlin/com/powersync/DatabaseDriverFactory.jvm.kt @@ -0,0 +1,9 @@ +package com.powersync + +import com.powersync.db.runWrapped + +@ExperimentalPowerSyncAPI +@Throws(PowerSyncException::class) +public actual fun resolvePowerSyncLoadableExtensionPath(): String? = runWrapped { powersyncExtension } + +private val powersyncExtension: String by lazy { extractLib("powersync") } diff --git a/core/src/jvmMain/kotlin/com/powersync/ExtractLib.kt b/common/src/jvmMain/kotlin/com/powersync/ExtractLib.kt similarity index 100% rename from core/src/jvmMain/kotlin/com/powersync/ExtractLib.kt rename to common/src/jvmMain/kotlin/com/powersync/ExtractLib.kt diff --git a/core/src/jvmMain/kotlin/com/powersync/sync/UserAgent.jvm.kt b/common/src/jvmMain/kotlin/com/powersync/sync/UserAgent.jvm.kt similarity index 100% rename from core/src/jvmMain/kotlin/com/powersync/sync/UserAgent.jvm.kt rename to common/src/jvmMain/kotlin/com/powersync/sync/UserAgent.jvm.kt diff --git a/core/src/nativeMain/interop/sqlite3.def b/common/src/nativeMain/interop/sqlite3.def similarity index 100% rename from core/src/nativeMain/interop/sqlite3.def rename to common/src/nativeMain/interop/sqlite3.def diff --git a/core/src/nativeMain/interop/sqlite3.h b/common/src/nativeMain/interop/sqlite3.h similarity index 100% rename from core/src/nativeMain/interop/sqlite3.h rename to common/src/nativeMain/interop/sqlite3.h diff --git a/core/src/nativeMain/kotlin/com/powersync/db/ActiveInstanceStore.native.kt b/common/src/nativeMain/kotlin/com/powersync/db/ActiveInstanceStore.native.kt similarity index 100% rename from core/src/nativeMain/kotlin/com/powersync/db/ActiveInstanceStore.native.kt rename to common/src/nativeMain/kotlin/com/powersync/db/ActiveInstanceStore.native.kt diff --git a/core/src/nativeMain/kotlin/com/powersync/sqlite/Database.kt b/common/src/nativeMain/kotlin/com/powersync/sqlite/Database.kt similarity index 96% rename from core/src/nativeMain/kotlin/com/powersync/sqlite/Database.kt rename to common/src/nativeMain/kotlin/com/powersync/sqlite/Database.kt index a08bf52a..27b8c9f4 100644 --- a/core/src/nativeMain/kotlin/com/powersync/sqlite/Database.kt +++ b/common/src/nativeMain/kotlin/com/powersync/sqlite/Database.kt @@ -34,7 +34,7 @@ import kotlinx.cinterop.value * [com.powersync.db.driver.InternalConnectionPool] and called from [kotlinx.coroutines.Dispatchers.IO] * to make these APIs asynchronous. */ -internal class Database( +public class Database( private val ptr: CPointer, ) : SQLiteConnection { override fun inTransaction(): Boolean { @@ -52,10 +52,10 @@ internal class Database( Statement(sql, ptr, stmtPtr.value!!) } - fun loadExtension( + public fun loadExtension( filename: String, entrypoint: String, - ) = memScoped { + ): Unit = memScoped { val errorMessagePointer = alloc>() val resultCode = sqlite3_load_extension(ptr, filename, entrypoint, errorMessagePointer.ptr) @@ -79,8 +79,8 @@ internal class Database( } } - companion object { - fun open( + public companion object { + public fun open( path: String, flags: Int, ): Database = diff --git a/core/src/nativeMain/kotlin/com/powersync/sqlite/SqliteException.kt b/common/src/nativeMain/kotlin/com/powersync/sqlite/SqliteException.kt similarity index 100% rename from core/src/nativeMain/kotlin/com/powersync/sqlite/SqliteException.kt rename to common/src/nativeMain/kotlin/com/powersync/sqlite/SqliteException.kt diff --git a/core/src/nativeMain/kotlin/com/powersync/sqlite/Statement.kt b/common/src/nativeMain/kotlin/com/powersync/sqlite/Statement.kt similarity index 100% rename from core/src/nativeMain/kotlin/com/powersync/sqlite/Statement.kt rename to common/src/nativeMain/kotlin/com/powersync/sqlite/Statement.kt diff --git a/core/src/nativeMain/kotlin/com/powersync/sync/UserAgent.native.kt b/common/src/nativeMain/kotlin/com/powersync/sync/UserAgent.native.kt similarity index 100% rename from core/src/nativeMain/kotlin/com/powersync/sync/UserAgent.native.kt rename to common/src/nativeMain/kotlin/com/powersync/sync/UserAgent.native.kt diff --git a/core/src/appleTest/kotlin/com/powersync/sqlite/DatabaseTest.kt b/common/src/nativeTest/kotlin/com.powersync.sqlite/DatabaseTest.kt similarity index 100% rename from core/src/appleTest/kotlin/com/powersync/sqlite/DatabaseTest.kt rename to common/src/nativeTest/kotlin/com.powersync.sqlite/DatabaseTest.kt diff --git a/core/src/appleTest/kotlin/com/powersync/sqlite/StatementTest.kt b/common/src/nativeTest/kotlin/com.powersync.sqlite/StatementTest.kt similarity index 100% rename from core/src/appleTest/kotlin/com/powersync/sqlite/StatementTest.kt rename to common/src/nativeTest/kotlin/com.powersync.sqlite/StatementTest.kt diff --git a/common/src/watchosMain/kotlin/com/powersync/DatabaseDriverFactory.watchos.kt b/common/src/watchosMain/kotlin/com/powersync/DatabaseDriverFactory.watchos.kt new file mode 100644 index 00000000..5758700f --- /dev/null +++ b/common/src/watchosMain/kotlin/com/powersync/DatabaseDriverFactory.watchos.kt @@ -0,0 +1,25 @@ +package com.powersync + +import com.powersync.static.powersync_init_static + +private val didLoadExtension by lazy { + val rc = powersync_init_static() + if (rc != 0) { + throw PowerSyncException( + "Could not load the PowerSync SQLite core extension", + cause = + Exception( + "Calling powersync_init_static returned result code $rc", + ), + ) + } + + true +} + +@ExperimentalPowerSyncAPI +@Throws(PowerSyncException::class) +public actual fun resolvePowerSyncLoadableExtensionPath(): String? { + didLoadExtension + return null +} diff --git a/common/src/watchosMain/powersync_static.h b/common/src/watchosMain/powersync_static.h new file mode 100644 index 00000000..9a1d3560 --- /dev/null +++ b/common/src/watchosMain/powersync_static.h @@ -0,0 +1 @@ +int powersync_init_static(); diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 77ca60b4..eeb742fb 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -1,14 +1,8 @@ import com.powersync.plugins.utils.powersyncTargets -import de.undercouch.gradle.tasks.download.Download import org.gradle.api.tasks.testing.logging.TestExceptionFormat -import org.gradle.internal.os.OperatingSystem import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget import org.jetbrains.kotlin.gradle.targets.jvm.tasks.KotlinJvmTest import org.jetbrains.kotlin.gradle.tasks.KotlinTest -import org.jetbrains.kotlin.konan.target.Family -import java.nio.file.Path -import kotlin.io.path.createDirectories -import kotlin.io.path.writeText plugins { alias(libs.plugins.kotlinMultiplatform) @@ -24,82 +18,6 @@ plugins { id("dokka-convention") } -val binariesFolder = project.layout.buildDirectory.dir("binaries/desktop") -val downloadPowersyncDesktopBinaries by tasks.registering(Download::class) { - description = "Download PowerSync core extensions for JVM builds and releases" - - val coreVersion = - libs.versions.powersync.core - .get() - val linux_aarch64 = - "https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v$coreVersion/libpowersync_aarch64.so" - val linux_x64 = - "https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v$coreVersion/libpowersync_x64.so" - val macos_aarch64 = - "https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v$coreVersion/libpowersync_aarch64.dylib" - val macos_x64 = - "https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v$coreVersion/libpowersync_x64.dylib" - val windows_x64 = - "https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v$coreVersion/powersync_x64.dll" - - val includeAllPlatformsForJvmBuild = - project.findProperty("powersync.binaries.allPlatforms") == "true" - val os = OperatingSystem.current() - - // The jar we're releasing for JVM clients needs to include the core extension. For local tests, it's enough to only - // download the extension for the OS running the build. For releases, we want to include them all. - // We're not compiling native code for JVM builds here (we're doing that for Android only), so we just have to - // fetch prebuilt binaries from the powersync-sqlite-core repository. - if (includeAllPlatformsForJvmBuild) { - src(listOf(linux_aarch64, linux_x64, macos_aarch64, macos_x64, windows_x64)) - } else { - val (aarch64, x64) = - when { - os.isLinux -> linux_aarch64 to linux_x64 - os.isMacOsX -> macos_aarch64 to macos_x64 - os.isWindows -> null to windows_x64 - else -> error("Unknown operating system: $os") - } - val arch = System.getProperty("os.arch") - src( - when (arch) { - "aarch64" -> listOfNotNull(aarch64) - "amd64", "x86_64" -> listOfNotNull(x64) - else -> error("Unsupported architecture: $arch") - }, - ) - } - dest(binariesFolder.map { it.dir("powersync") }) - onlyIfModified(true) -} - -val generateVersionConstant by tasks.registering { - val target = project.layout.buildDirectory.dir("generated/constants") - val packageName = "com.powersync.build" - - outputs.dir(target) - val currentVersion = version.toString() - - doLast { - val dir = target.get().asFile - dir.mkdir() - val rootPath = dir.toPath() - - val source = - """ - package $packageName - - internal const val LIBRARY_VERSION: String = "$currentVersion" - - """.trimIndent() - - val packageRoot = packageName.split('.').fold(rootPath, Path::resolve) - packageRoot.createDirectories() - - packageRoot.resolve("BuildConstants.kt").writeText(source) - } -} - kotlin { powersyncTargets() @@ -108,21 +26,6 @@ kotlin { compileTaskProvider { compilerOptions.freeCompilerArgs.add("-Xexport-kdoc") } - - if (target.konanTarget.family == Family.WATCHOS) { - // We're linking the core extension statically, which means that we need a cinterop - // to call powersync_init_static - cinterops.create("powersync_static") { - packageName("com.powersync.static") - headers(file("src/watchosMain/powersync_static.h")) - } - } - - cinterops.create("sqlite3") { - packageName("com.powersync.internal.sqlite3") - includeDirs.allHeaders("src/nativeMain/interop/") - definitionFile.set(project.file("src/nativeMain/interop/sqlite3.def")) - } } } @@ -142,34 +45,13 @@ kotlin { dependsOn(commonTest.get()) } - val commonJava by creating { - dependsOn(commonMain.get()) - } - commonMain.configure { - kotlin { - srcDir(generateVersionConstant) - } - dependencies { - api(libs.androidx.sqlite.sqlite) - - implementation(libs.uuid) - implementation(libs.kotlin.stdlib) - implementation(libs.ktor.client.contentnegotiation) - implementation(libs.ktor.serialization.json) - implementation(libs.kotlinx.io) - implementation(libs.kotlinx.coroutines.core) - implementation(libs.kotlinx.datetime) - implementation(libs.stately.concurrency) - implementation(libs.configuration.annotations) - api(libs.ktor.client.core) - api(libs.kermit) + api(projects.common) } } androidMain { - dependsOn(commonJava) dependencies { api(libs.powersync.sqlite.core.android) implementation(libs.ktor.client.okhttp) @@ -178,8 +60,6 @@ kotlin { } jvmMain { - dependsOn(commonJava) - dependencies { implementation(libs.ktor.client.okhttp) implementation(libs.androidx.sqlite.bundled) @@ -259,10 +139,6 @@ android { ndkVersion = "27.1.12297006" } -tasks.named(kotlin.jvm().compilations["main"].processResourcesTaskName) { - from(downloadPowersyncDesktopBinaries) -} - // We want to build with recent JDKs, but need to make sure we support Java 8. https://jakewharton.com/build-on-latest-java-test-through-lowest-java/ val testWithJava8 by tasks.registering(KotlinJvmTest::class) { javaLauncher = @@ -291,5 +167,5 @@ tasks.withType { } dokka { - moduleName.set("PowerSync Core") + moduleName.set("PowerSync") } diff --git a/core/src/androidMain/kotlin/com/powersync/DatabaseDriverFactory.android.kt b/core/src/androidMain/kotlin/com/powersync/DatabaseDriverFactory.android.kt index 8ee2b28f..16542735 100644 --- a/core/src/androidMain/kotlin/com/powersync/DatabaseDriverFactory.android.kt +++ b/core/src/androidMain/kotlin/com/powersync/DatabaseDriverFactory.android.kt @@ -8,12 +8,15 @@ import kotlin.Throws @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") public actual class DatabaseDriverFactory( private val context: Context, -) { +): PersistentDriverFactory { private val driver = BundledSQLiteDriver().also { it.addPowerSyncExtension() } - internal actual fun resolveDefaultDatabasePath(dbFilename: String): String = context.getDatabasePath(dbFilename).path + actual override val platform: PowerSyncPlatform + get() = BuiltinPlatform - internal actual fun openConnection( + actual override fun resolveDefaultDatabasePath(dbFilename: String): String = context.getDatabasePath(dbFilename).path + + actual override fun openConnection( path: String, openFlags: Int, ): SQLiteConnection = driver.open(path, openFlags) @@ -23,8 +26,4 @@ public fun BundledSQLiteDriver.addPowerSyncExtension() { addExtension("libpowersync.so", "sqlite3_powersync_init") } -@ExperimentalPowerSyncAPI -@Throws(PowerSyncException::class) -public actual fun resolvePowerSyncLoadableExtensionPath(): String? = "libpowersync.so" - internal actual fun openInMemoryConnection(): SQLiteConnection = BundledSQLiteDriver().also { it.addPowerSyncExtension() }.open(":memory:") diff --git a/core/src/appleNonWatchOsMain/kotlin/com/powersync/DatabaseDriverFactory.appleNonWatchOs.kt b/core/src/appleNonWatchOsMain/kotlin/com/powersync/DatabaseDriverFactory.appleNonWatchOs.kt index 54d2033b..89d76c6d 100644 --- a/core/src/appleNonWatchOsMain/kotlin/com/powersync/DatabaseDriverFactory.appleNonWatchOs.kt +++ b/core/src/appleNonWatchOsMain/kotlin/com/powersync/DatabaseDriverFactory.appleNonWatchOs.kt @@ -4,16 +4,20 @@ import androidx.sqlite.SQLiteConnection import com.powersync.sqlite.Database @Suppress(names = ["EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING"]) -public actual class DatabaseDriverFactory { - internal actual fun resolveDefaultDatabasePath(dbFilename: String): String = appleDefaultDatabasePath(dbFilename) +public actual class DatabaseDriverFactory: PersistentDriverFactory { + actual override val platform: PowerSyncPlatform + get() = BuiltinPlatform - internal actual fun openConnection( + actual override fun resolveDefaultDatabasePath(dbFilename: String): String = appleDefaultDatabasePath(dbFilename) + + @OptIn(ExperimentalPowerSyncAPI::class) + actual override fun openConnection( path: String, openFlags: Int, ): SQLiteConnection { val db = Database.open(path, openFlags) try { - db.loadExtension(powerSyncExtensionPath, "sqlite3_powersync_init") + db.loadExtension(resolvePowerSyncLoadableExtensionPath()!!, "sqlite3_powersync_init") } catch (e: PowerSyncException) { db.close() throw e @@ -22,8 +26,4 @@ public actual class DatabaseDriverFactory { } } -@ExperimentalPowerSyncAPI -@Throws(PowerSyncException::class) -public actual fun resolvePowerSyncLoadableExtensionPath(): String? = powerSyncExtensionPath - internal actual fun openInMemoryConnection(): SQLiteConnection = DatabaseDriverFactory().openConnection(":memory:", 0x02) diff --git a/core/src/appleTest/kotlin/com/powersync/DatabaseDriverFactoryTest.kt b/core/src/appleTest/kotlin/com/powersync/DatabaseDriverFactoryTest.kt index e3052ffe..d383feb3 100644 --- a/core/src/appleTest/kotlin/com/powersync/DatabaseDriverFactoryTest.kt +++ b/core/src/appleTest/kotlin/com/powersync/DatabaseDriverFactoryTest.kt @@ -4,13 +4,13 @@ import kotlin.experimental.ExperimentalNativeApi import kotlin.test.Test class DatabaseDriverFactoryTest { - @OptIn(ExperimentalNativeApi::class) + @OptIn(ExperimentalNativeApi::class, ExperimentalPowerSyncAPI::class) @Test fun findsPowerSyncFramework() { if (Platform.osFamily != OsFamily.WATCHOS) { // On watchOS targets, there's no special extension path because we expect to link the // PowerSync extension statically due to platform restrictions. - powerSyncExtensionPath + resolvePowerSyncLoadableExtensionPath()!! } } } diff --git a/core/src/commonMain/kotlin/com/powersync/DatabaseDriverFactory.kt b/core/src/commonMain/kotlin/com/powersync/DatabaseDriverFactory.kt index 18698c95..acfdd99e 100644 --- a/core/src/commonMain/kotlin/com/powersync/DatabaseDriverFactory.kt +++ b/core/src/commonMain/kotlin/com/powersync/DatabaseDriverFactory.kt @@ -1,62 +1,23 @@ package com.powersync import androidx.sqlite.SQLiteConnection +import io.ktor.client.HttpClient +import io.ktor.client.HttpClientConfig @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") -public expect class DatabaseDriverFactory { - internal fun resolveDefaultDatabasePath(dbFilename: String): String +public expect class DatabaseDriverFactory: PersistentDriverFactory { + override val platform: PowerSyncPlatform - /** - * Opens a SQLite connection on [path] with [openFlags]. - * - * The connection should have the PowerSync core extension loaded. - */ - internal fun openConnection( - path: String, - openFlags: Int, - ): SQLiteConnection + override fun resolveDefaultDatabasePath(dbFilename: String): String + override fun openConnection(path: String, openFlags: Int): SQLiteConnection } internal expect fun openInMemoryConnection(): SQLiteConnection -/** - * Resolves a path to the loadable PowerSync core extension library. - * - * This library must be loaded on all databases using the PowerSync SDK. On platforms where the - * extension is linked statically (only watchOS at the moment), this returns `null`. - * - * When using the PowerSync SDK directly, there is no need to invoke this method. It is intended for - * configuring external database connections not managed by PowerSync to work with the PowerSync - * SDK. - */ -@ExperimentalPowerSyncAPI -@Throws(PowerSyncException::class) -public expect fun resolvePowerSyncLoadableExtensionPath(): String? +internal object BuiltinPlatform: PowerSyncPlatform { + override fun openInMemoryConnection(): SQLiteConnection { + return com.powersync.openInMemoryConnection() + } -@OptIn(ExperimentalPowerSyncAPI::class) -internal fun openDatabase( - factory: DatabaseDriverFactory, - dbFilename: String, - dbDirectory: String?, - readOnly: Boolean = false, -): SQLiteConnection { - val dbPath = - if (dbDirectory != null) { - "$dbDirectory/$dbFilename" - } else { - factory.resolveDefaultDatabasePath(dbFilename) - } - - return factory.openConnection( - dbPath, - if (readOnly) { - SQLITE_OPEN_READONLY - } else { - SQLITE_OPEN_READWRITE or SQLITE_OPEN_CREATE - }, - ) + override fun configureHttpClient(block: HttpClientConfig<*>.() -> Unit) = HttpClient(block) } - -private const val SQLITE_OPEN_READONLY = 0x01 -private const val SQLITE_OPEN_READWRITE = 0x02 -private const val SQLITE_OPEN_CREATE = 0x04 diff --git a/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt b/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt index bdf8e66b..a750a771 100644 --- a/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt +++ b/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt @@ -1,289 +1,31 @@ package com.powersync import co.touchlab.kermit.Logger -import com.powersync.bucket.StreamPriority -import com.powersync.connectors.PowerSyncBackendConnector import com.powersync.db.ActiveDatabaseGroup -import com.powersync.db.ActiveDatabaseResource -import com.powersync.db.PowerSyncDatabaseImpl -import com.powersync.db.Queries -import com.powersync.db.crud.CrudBatch -import com.powersync.db.crud.CrudTransaction -import com.powersync.db.driver.SQLiteConnectionPool import com.powersync.db.driver.SingleConnectionPool import com.powersync.db.schema.Schema -import com.powersync.sync.SyncOptions -import com.powersync.sync.SyncStatus -import com.powersync.sync.SyncStream -import com.powersync.utils.JsonParam import com.powersync.utils.generateLogger import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.firstOrNull -import kotlin.coroutines.cancellation.CancellationException /** - * A PowerSync managed database. - * - * Use one instance per database file. - * - * Use [PowerSyncDatabase.connect] to connect to the PowerSync service, to keep the local database in sync with the remote database. - * - * All changes to local tables are automatically recorded, whether connected or not. Once connected, the changes are uploaded. + * Creates an in-memory PowerSync database instance, useful for testing. */ -public interface PowerSyncDatabase : Queries { - /** - * Indicates if the PowerSync client has been closed. - * A new client is required after a client has been closed. - */ - public val closed: Boolean - - /** - * Identifies the database client. - * This is typically the database name. - */ - public val identifier: String - - /** - * The current sync status. - */ - public val currentStatus: SyncStatus - - /** - * Replace the schema with a new version. This is for advanced use cases - typically the schema - * should just be specified once in the constructor. - * - * Cannot be used while connected - this should only be called before connect. - */ - @Throws(PowerSyncException::class, CancellationException::class) - public suspend fun updateSchema(schema: Schema) - - /** - * Suspend function that resolves when the first sync has occurred - */ - @Throws(PowerSyncException::class, CancellationException::class) - public suspend fun waitForFirstSync() - - /** - * Suspend function that resolves when the first sync covering at least all buckets with the - * given [priority] (or a higher one, since those would be synchronized first) has completed. - */ - @Throws(PowerSyncException::class, CancellationException::class) - public suspend fun waitForFirstSync(priority: StreamPriority) - - /** - * Connect to the PowerSync service, and keep the databases in sync. - * - * The connection is automatically re-opened if it fails for any reason. - * - * Use @param [connector] to specify the [PowerSyncBackendConnector]. - * Use @param [crudThrottleMs] to specify the time between CRUD operations. Defaults to 1000ms. - * Use @param [retryDelayMs] to specify the delay between retries after failure. Defaults to 5000ms. - * Use @param [params] to specify sync parameters from the client. - * - * Example usage: - * ``` - * val params = JsonParam.Map( - * mapOf( - * "name" to JsonParam.String("John Doe"), - * "age" to JsonParam.Number(30), - * "isStudent" to JsonParam.Boolean(false) - * ) - * ) - * - * connect( - * connector = connector, - * crudThrottleMs = 2000L, - * retryDelayMs = 10000L, - * params = params - * ) - * ``` - * TODO: Internal Team - Status changes are reported on [statusStream]. - */ - @Throws(PowerSyncException::class, CancellationException::class) - public suspend fun connect( - connector: PowerSyncBackendConnector, - crudThrottleMs: Long = 1000L, - retryDelayMs: Long = 5000L, - params: Map = emptyMap(), - options: SyncOptions = SyncOptions.defaults, +@OptIn(ExperimentalPowerSyncAPI::class) +public fun PowerSyncDatabase.PowerSyncOpenFactory.inMemory( + schema: Schema, + scope: CoroutineScope, + logger: Logger? = null, +): PowerSyncDatabase { + val logger = generateLogger(logger) + // Since this returns a fresh in-memory database every time, use a fresh group to avoid warnings about the + // same database being opened multiple times. + val collection = ActiveDatabaseGroup.GroupsCollection().referenceDatabase(logger, "test") + + return openedWithGroup( + SingleConnectionPool(openInMemoryConnection()), + scope, + schema, + logger, + collection, ) - - /** - * Get a batch of crud data to upload. - * - * Returns null if there is no data to upload. - * - * Use this from the [PowerSyncBackendConnector.uploadData]` callback. - * - * Once the data have been successfully uploaded, call [CrudBatch.complete] before - * requesting the next batch. - * - * Use [limit] to specify the maximum number of updates to return in a single - * batch. Default is 100. - * - * This method does include transaction ids in the result, but does not group - * data by transaction. One batch may contain data from multiple transactions, - * and a single transaction may be split over multiple batches. - */ - @Throws(PowerSyncException::class, CancellationException::class) - public suspend fun getCrudBatch(limit: Int = 100): CrudBatch? - - /** - * Get the next recorded transaction to upload. - * - * Returns null if there is no data to upload. - * - * Use this from the [PowerSyncBackendConnector.uploadData] callback. - * - * Once the data have been successfully uploaded, call [CrudTransaction.complete] before - * requesting the next transaction. - * - * Unlike [getCrudBatch], this only returns data from a single transaction at a time. - * All data for the transaction is loaded into memory. - */ - @Throws(PowerSyncException::class, CancellationException::class) - public suspend fun getNextCrudTransaction(): CrudTransaction? = getCrudTransactions().firstOrNull() - - /** - * Obtains a flow emitting completed transactions with local writes against the database. - - * This is typically used from the [PowerSyncBackendConnector.uploadData] callback. - * Each entry emitted by the returned flow is a full transaction containing all local writes - * made while that transaction was active. - * - * Unlike [getNextCrudTransaction], which always returns the oldest transaction that hasn't - * been [CrudTransaction.complete]d yet, this flow can be used to collect multiple transactions. - * Calling [CrudTransaction.complete] will mark that and all prior transactions emitted by the - * flow as completed. - * - * This can be used to upload multiple transactions in a single batch, e.g with: - * - * ```Kotlin - * val batch = mutableListOf() - * var lastTx: CrudTransaction? = null - * - * database.getCrudTransactions().takeWhile { batch.size < 100 }.collect { - * batch.addAll(it.crud) - * lastTx = it - * } - * - * if (batch.isNotEmpty()) { - * uploadChanges(batch) - * lastTx!!.complete(null) - * } - * ```` - * - * If there is no local data to upload, returns an empty flow. - */ - public fun getCrudTransactions(): Flow - - /** - * Convenience method to get the current version of PowerSync. - */ - @Throws(PowerSyncException::class, CancellationException::class) - public suspend fun getPowerSyncVersion(): String - - /** - * Create a [SyncStream] instance for the given [name] and [parameters]. - * - * Use [SyncStream.subscribe] on the returned instance to subscribe to the stream. - */ - @ExperimentalPowerSyncAPI - public fun syncStream( - name: String, - parameters: Map? = null, - ): SyncStream - - /** - * Close the sync connection. - * - * Use [connect] to connect again. - */ - @Throws(PowerSyncException::class, CancellationException::class) - public suspend fun disconnect() - - /** - * Disconnect and clear the database. - * Use this when logging out. - * The database can still be queried after this is called, but the tables - * would be empty. - * - * To preserve data in local-only tables, set clearLocal to false. - */ - @Throws(PowerSyncException::class, CancellationException::class) - public suspend fun disconnectAndClear(clearLocal: Boolean = true) - - /** - * Close the database, releasing resources. - * Also disconnects any active connection. - * - * Once close is called, this database cannot be used again - a new one must be constructed. - */ - @Throws(PowerSyncException::class, CancellationException::class) - public suspend fun close() - - public companion object { - /** - * Creates a PowerSync database managed by an external connection pool. - * - * In this case, PowerSync will not open its own SQLite connections, but rather refer to - * connections in the [pool]. - * - * The `identifier` parameter should be a name identifying the path of the database. The - * PowerSync SDK will emit a warning if multiple databases are opened with the same - * identifier, and uses internal locks to ensure these two databases are not synced at the - * same time (which would be inefficient and can cause consistency issues). - */ - @ExperimentalPowerSyncAPI - public fun opened( - pool: SQLiteConnectionPool, - scope: CoroutineScope, - schema: Schema, - identifier: String, - logger: Logger, - ): PowerSyncDatabase { - val group = ActiveDatabaseGroup.referenceDatabase(logger, identifier) - return openedWithGroup(pool, scope, schema, logger, group) - } - - /** - * Creates an in-memory PowerSync database instance, useful for testing. - */ - @OptIn(ExperimentalPowerSyncAPI::class) - public fun inMemory( - schema: Schema, - scope: CoroutineScope, - logger: Logger? = null, - ): PowerSyncDatabase { - val logger = generateLogger(logger) - // Since this returns a fresh in-memory database every time, use a fresh group to avoid warnings about the - // same database being opened multiple times. - val collection = ActiveDatabaseGroup.GroupsCollection().referenceDatabase(logger, "test") - - return openedWithGroup( - SingleConnectionPool(openInMemoryConnection()), - scope, - schema, - logger, - collection, - ) - } - - @ExperimentalPowerSyncAPI - internal fun openedWithGroup( - pool: SQLiteConnectionPool, - scope: CoroutineScope, - schema: Schema, - logger: Logger, - group: Pair, - ): PowerSyncDatabase = - PowerSyncDatabaseImpl( - schema, - scope, - pool, - logger, - group, - ) - } } diff --git a/core/src/commonTest/kotlin/com/powersync/db/FunctionTest.kt b/core/src/commonTest/kotlin/com/powersync/db/FunctionTest.kt index fec0a31d..fc5089a9 100644 --- a/core/src/commonTest/kotlin/com/powersync/db/FunctionTest.kt +++ b/core/src/commonTest/kotlin/com/powersync/db/FunctionTest.kt @@ -1,6 +1,5 @@ package com.powersync.db -import com.powersync.PowerSyncException import io.kotest.assertions.throwables.shouldThrow import kotlinx.coroutines.CancellationException import kotlin.test.Test diff --git a/core/src/jvmMain/kotlin/com/powersync/DatabaseDriverFactory.jvm.kt b/core/src/jvmMain/kotlin/com/powersync/DatabaseDriverFactory.jvm.kt index 3467e699..56b7d707 100644 --- a/core/src/jvmMain/kotlin/com/powersync/DatabaseDriverFactory.jvm.kt +++ b/core/src/jvmMain/kotlin/com/powersync/DatabaseDriverFactory.jvm.kt @@ -1,30 +1,26 @@ package com.powersync import androidx.sqlite.SQLiteConnection -import androidx.sqlite.driver.bundled.BundledSQLiteConnection import androidx.sqlite.driver.bundled.BundledSQLiteDriver -import com.powersync.db.runWrapped @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", "SqlNoDataSourceInspection") -public actual class DatabaseDriverFactory { +public actual class DatabaseDriverFactory: PersistentDriverFactory { + actual override val platform: PowerSyncPlatform + get() = BuiltinPlatform + private val driver = BundledSQLiteDriver().also { it.addPowerSyncExtension() } - internal actual fun resolveDefaultDatabasePath(dbFilename: String): String = dbFilename + actual override fun resolveDefaultDatabasePath(dbFilename: String): String = dbFilename - internal actual fun openConnection( + actual override fun openConnection( path: String, openFlags: Int, ): SQLiteConnection = driver.open(path, openFlags) } +@OptIn(ExperimentalPowerSyncAPI::class) public fun BundledSQLiteDriver.addPowerSyncExtension() { - addExtension(powersyncExtension, "sqlite3_powersync_init") + addExtension(resolvePowerSyncLoadableExtensionPath()!!, "sqlite3_powersync_init") } -private val powersyncExtension: String by lazy { extractLib("powersync") } - -@ExperimentalPowerSyncAPI -@Throws(PowerSyncException::class) -public actual fun resolvePowerSyncLoadableExtensionPath(): String? = runWrapped { powersyncExtension } - internal actual fun openInMemoryConnection(): SQLiteConnection = DatabaseDriverFactory().openConnection(":memory:", 0x02) diff --git a/core/src/watchosMain/kotlin/com/powersync/DatabaseDriverFactory.watchos.kt b/core/src/watchosMain/kotlin/com/powersync/DatabaseDriverFactory.watchos.kt index 29bd81ee..2dcc7e1f 100644 --- a/core/src/watchosMain/kotlin/com/powersync/DatabaseDriverFactory.watchos.kt +++ b/core/src/watchosMain/kotlin/com/powersync/DatabaseDriverFactory.watchos.kt @@ -5,38 +5,21 @@ import com.powersync.sqlite.Database import com.powersync.static.powersync_init_static @Suppress(names = ["EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING"]) -public actual class DatabaseDriverFactory { - internal actual fun resolveDefaultDatabasePath(dbFilename: String): String = appleDefaultDatabasePath(dbFilename) +public actual class DatabaseDriverFactory: PersistentDriverFactory { + actual override val platform: PowerSyncPlatform + get() = BuiltinPlatform - internal actual fun openConnection( + actual override fun resolveDefaultDatabasePath(dbFilename: String): String = appleDefaultDatabasePath(dbFilename) + + @OptIn(ExperimentalPowerSyncAPI::class) + actual override fun openConnection( path: String, openFlags: Int, ): SQLiteConnection { - didLoadExtension - return Database.open(path, openFlags) - } -} + resolvePowerSyncLoadableExtensionPath() -private val didLoadExtension by lazy { - val rc = powersync_init_static() - if (rc != 0) { - throw PowerSyncException( - "Could not load the PowerSync SQLite core extension", - cause = - Exception( - "Calling powersync_init_static returned result code $rc", - ), - ) + return Database.open(path, openFlags) } - - true -} - -@ExperimentalPowerSyncAPI -@Throws(PowerSyncException::class) -public actual fun resolvePowerSyncLoadableExtensionPath(): String? { - didLoadExtension - return null } internal actual fun openInMemoryConnection(): SQLiteConnection = DatabaseDriverFactory().openConnection(":memory:", 0x02) diff --git a/settings.gradle.kts b/settings.gradle.kts index 3a0d5b12..4621c775 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -30,6 +30,7 @@ rootProject.name = "powersync-root" include(":internal:download-core-extension") include(":internal:PowerSyncKotlin") +include(":common") include(":core") include(":core-tests-android") include(":integrations:room") From 97e7c1bde74a155ea9745da843c229eabadc533d Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Fri, 17 Oct 2025 11:18:42 +0200 Subject: [PATCH 2/8] Share common logic in test --- common/build.gradle.kts | 8 +-- .../kotlin/com/powersync/AttachmentsTest.kt | 2 +- .../kotlin/com/powersync/DatabaseTest.kt | 6 +-- .../com/powersync/sync/SyncIntegrationTest.kt | 5 +- .../com/powersync/testutils/TestUtils.kt | 17 +++--- .../powersync/testutils/MockSyncService.kt | 0 core/build.gradle.kts | 9 +--- .../kotlin/com/powersync/db/InMemoryTest.kt | 7 +-- .../powersync/sync/StreamingSyncClientTest.kt | 1 - internal/testutils/build.gradle.kts | 54 +++++++++++++++++++ .../powersync/test}/PowerSyncTestLogWriter.kt | 10 ++-- .../com/powersync/test}/TestConnector.kt | 3 +- .../kotlin/com/powersync/test}/WaitFor.kt | 4 +- .../kotlin/com/powersync/test/TestPlatform.kt | 18 +++++++ settings.gradle.kts | 1 + 15 files changed, 102 insertions(+), 43 deletions(-) rename {core => common}/src/commonTest/kotlin/com/powersync/testutils/MockSyncService.kt (100%) rename {common => core}/src/commonIntegrationTest/kotlin/com/powersync/db/InMemoryTest.kt (92%) create mode 100644 internal/testutils/build.gradle.kts rename {core/src/commonTest/kotlin/com/powersync => internal/testutils/src/commonMain/kotlin/com/powersync/test}/PowerSyncTestLogWriter.kt (77%) rename {core/src/commonTest/kotlin/com/powersync => internal/testutils/src/commonMain/kotlin/com/powersync/test}/TestConnector.kt (92%) rename {core/src/commonTest/kotlin/com/powersync/testutils => internal/testutils/src/commonMain/kotlin/com/powersync/test}/WaitFor.kt (89%) create mode 100644 internal/testutils/src/platformMain/kotlin/com/powersync/test/TestPlatform.kt diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 207b0518..b1a2c3cd 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -205,13 +205,7 @@ kotlin { tvosMain.orNull?.dependsOn(appleNonWatchOsMain) commonTest.dependencies { - implementation(libs.kotlin.test) - implementation(libs.test.coroutines) - implementation(libs.test.turbine) - implementation(libs.test.kotest.assertions) - implementation(libs.kermit.test) - implementation(libs.ktor.client.mock) - implementation(libs.test.turbine) + implementation(projects.internal.testutils) } // We're putting the native libraries into our JAR, so integration tests for the JVM can run as part of the unit diff --git a/common/src/commonIntegrationTest/kotlin/com/powersync/AttachmentsTest.kt b/common/src/commonIntegrationTest/kotlin/com/powersync/AttachmentsTest.kt index 92aafdb7..ae042074 100644 --- a/common/src/commonIntegrationTest/kotlin/com/powersync/AttachmentsTest.kt +++ b/common/src/commonIntegrationTest/kotlin/com/powersync/AttachmentsTest.kt @@ -16,7 +16,7 @@ import com.powersync.testutils.ActiveDatabaseTest import com.powersync.testutils.MockedRemoteStorage import com.powersync.testutils.UserRow import com.powersync.testutils.databaseTest -import com.powersync.testutils.getTempDir +import com.powersync.test.getTempDir import dev.mokkery.answering.throws import dev.mokkery.everySuspend import dev.mokkery.matcher.ArgMatchersScope diff --git a/common/src/commonIntegrationTest/kotlin/com/powersync/DatabaseTest.kt b/common/src/commonIntegrationTest/kotlin/com/powersync/DatabaseTest.kt index 97eee155..726eb318 100644 --- a/common/src/commonIntegrationTest/kotlin/com/powersync/DatabaseTest.kt +++ b/common/src/commonIntegrationTest/kotlin/com/powersync/DatabaseTest.kt @@ -10,9 +10,9 @@ import com.powersync.db.getString import com.powersync.db.schema.Schema import com.powersync.testutils.UserRow import com.powersync.testutils.databaseTest -import com.powersync.testutils.getTempDir -import com.powersync.testutils.isIOS -import com.powersync.testutils.waitFor +import com.powersync.test.getTempDir +import com.powersync.test.isIOS +import com.powersync.test.waitFor import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.shouldBe diff --git a/common/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncIntegrationTest.kt b/common/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncIntegrationTest.kt index b33cc786..cfe2e57a 100644 --- a/common/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncIntegrationTest.kt +++ b/common/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncIntegrationTest.kt @@ -4,7 +4,8 @@ import app.cash.turbine.turbineScope import co.touchlab.kermit.ExperimentalKermitApi import com.powersync.ExperimentalPowerSyncAPI import com.powersync.PowerSyncDatabase -import com.powersync.TestConnector +import com.powersync.PowerSyncException +import com.powersync.test.TestConnector import com.powersync.bucket.BucketChecksum import com.powersync.bucket.Checkpoint import com.powersync.bucket.OpType @@ -14,10 +15,12 @@ import com.powersync.bucket.WriteCheckpointData import com.powersync.bucket.WriteCheckpointResponse import com.powersync.connectors.PowerSyncBackendConnector import com.powersync.connectors.PowerSyncCredentials +import com.powersync.db.PowerSyncDatabaseImpl import com.powersync.db.schema.PendingStatement import com.powersync.db.schema.PendingStatementParameter import com.powersync.db.schema.RawTable import com.powersync.db.schema.Schema +import com.powersync.test.waitFor import com.powersync.testutils.UserRow import com.powersync.testutils.databaseTest import com.powersync.testutils.waitFor diff --git a/common/src/commonIntegrationTest/kotlin/com/powersync/testutils/TestUtils.kt b/common/src/commonIntegrationTest/kotlin/com/powersync/testutils/TestUtils.kt index a0fdbe91..fd218ab3 100644 --- a/common/src/commonIntegrationTest/kotlin/com/powersync/testutils/TestUtils.kt +++ b/common/src/commonIntegrationTest/kotlin/com/powersync/testutils/TestUtils.kt @@ -8,15 +8,18 @@ import co.touchlab.kermit.Logger import co.touchlab.kermit.Severity import co.touchlab.kermit.TestConfig import com.powersync.ExperimentalPowerSyncAPI -import com.powersync.PersistentDriverFactory -import com.powersync.PowerSyncTestLogWriter -import com.powersync.TestConnector +import com.powersync.test.PowerSyncTestLogWriter +import com.powersync.test.TestConnector import com.powersync.bucket.WriteCheckpointData import com.powersync.bucket.WriteCheckpointResponse import com.powersync.createPowerSyncDatabaseImpl +import com.powersync.db.PowerSyncDatabaseImpl import com.powersync.db.schema.Schema import com.powersync.sync.LegacySyncImplementation import com.powersync.sync.configureSyncHttpClient +import com.powersync.test.cleanup +import com.powersync.test.factory +import com.powersync.test.getTempDir import com.powersync.utils.JsonUtil import io.ktor.client.HttpClient import io.ktor.client.engine.mock.toByteArray @@ -29,14 +32,6 @@ import kotlinx.io.files.Path import kotlinx.serialization.json.JsonElement import kotlin.coroutines.resume -expect val factory: PersistentDriverFactory - -expect fun cleanup(path: String) - -expect fun getTempDir(): String - -expect fun isIOS(): Boolean - fun generatePrintLogWriter() = object : LogWriter() { override fun log( diff --git a/core/src/commonTest/kotlin/com/powersync/testutils/MockSyncService.kt b/common/src/commonTest/kotlin/com/powersync/testutils/MockSyncService.kt similarity index 100% rename from core/src/commonTest/kotlin/com/powersync/testutils/MockSyncService.kt rename to common/src/commonTest/kotlin/com/powersync/testutils/MockSyncService.kt diff --git a/core/build.gradle.kts b/core/build.gradle.kts index eeb742fb..8df85d51 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -6,7 +6,6 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinTest plugins { alias(libs.plugins.kotlinMultiplatform) - alias(libs.plugins.kotlinSerialization) alias(libs.plugins.android.library) alias(libs.plugins.mavenPublishPlugin) alias(libs.plugins.downloadPlugin) @@ -86,13 +85,7 @@ kotlin { tvosMain.orNull?.dependsOn(appleNonWatchOsMain) commonTest.dependencies { - implementation(libs.kotlin.test) - implementation(libs.test.coroutines) - implementation(libs.test.turbine) - implementation(libs.test.kotest.assertions) - implementation(libs.kermit.test) - implementation(libs.ktor.client.mock) - implementation(libs.test.turbine) + implementation(projects.internal.testutils) } // We're putting the native libraries into our JAR, so integration tests for the JVM can run as part of the unit diff --git a/common/src/commonIntegrationTest/kotlin/com/powersync/db/InMemoryTest.kt b/core/src/commonIntegrationTest/kotlin/com/powersync/db/InMemoryTest.kt similarity index 92% rename from common/src/commonIntegrationTest/kotlin/com/powersync/db/InMemoryTest.kt rename to core/src/commonIntegrationTest/kotlin/com/powersync/db/InMemoryTest.kt index ebc04960..4d09c9dc 100644 --- a/common/src/commonIntegrationTest/kotlin/com/powersync/db/InMemoryTest.kt +++ b/core/src/commonIntegrationTest/kotlin/com/powersync/db/InMemoryTest.kt @@ -10,6 +10,7 @@ import com.powersync.PowerSyncDatabase import com.powersync.db.schema.Column import com.powersync.db.schema.Schema import com.powersync.db.schema.Table +import com.powersync.inMemory import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.shouldBe import kotlinx.coroutines.test.runTest @@ -33,7 +34,7 @@ class InMemoryTest { @Test fun createsSchema() = runTest { - val db = PowerSyncDatabase.Companion.inMemory(schema, this, logger) + val db = PowerSyncDatabase.inMemory(schema, this, logger) try { db.getAll("SELECT * FROM users") { } shouldHaveSize 0 } finally { @@ -44,7 +45,7 @@ class InMemoryTest { @Test fun watch() = runTest { - val db = PowerSyncDatabase.Companion.inMemory(schema, this, logger) + val db = PowerSyncDatabase.inMemory(schema, this, logger) try { turbineScope { val turbine = @@ -73,4 +74,4 @@ class InMemoryTest { ), ) } -} +} \ No newline at end of file diff --git a/core/src/commonTest/kotlin/com/powersync/sync/StreamingSyncClientTest.kt b/core/src/commonTest/kotlin/com/powersync/sync/StreamingSyncClientTest.kt index 5592dfa6..19107e06 100644 --- a/core/src/commonTest/kotlin/com/powersync/sync/StreamingSyncClientTest.kt +++ b/core/src/commonTest/kotlin/com/powersync/sync/StreamingSyncClientTest.kt @@ -7,7 +7,6 @@ import co.touchlab.kermit.Severity import co.touchlab.kermit.TestConfig import co.touchlab.kermit.TestLogWriter import com.powersync.ExperimentalPowerSyncAPI -import com.powersync.TestConnector import com.powersync.bucket.BucketStorage import com.powersync.connectors.PowerSyncBackendConnector import com.powersync.db.crud.CrudEntry diff --git a/internal/testutils/build.gradle.kts b/internal/testutils/build.gradle.kts new file mode 100644 index 00000000..4590aabd --- /dev/null +++ b/internal/testutils/build.gradle.kts @@ -0,0 +1,54 @@ +import com.powersync.plugins.utils.powersyncTargets + +plugins { + alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.android.library) + alias(libs.plugins.kotlinter) + id("com.powersync.plugins.sharedbuild") + alias(libs.plugins.mokkery) + alias(libs.plugins.kotlin.atomicfu) +} + +kotlin { + powersyncTargets() + applyDefaultHierarchyTemplate() + + sourceSets { + commonMain.dependencies { + implementation(projects.common) + + api(libs.kotlin.test) + api(libs.test.coroutines) + api(libs.test.turbine) + api(libs.test.kotest.assertions) + api(libs.kermit.test) + api(libs.ktor.client.mock) + } + + val platformMain by creating { + dependsOn(commonMain.get()) + } + + jvmMain.get().dependsOn(platformMain) + nativeMain.orNull?.dependsOn(platformMain) + } +} + +android { + compileOptions { + targetCompatibility = JavaVersion.VERSION_17 + } + + buildTypes { + release { + } + debug { + } + } + + namespace = "com.powersync" + compileSdk = + libs.versions.android.compileSdk + .get() + .toInt() +} diff --git a/core/src/commonTest/kotlin/com/powersync/PowerSyncTestLogWriter.kt b/internal/testutils/src/commonMain/kotlin/com/powersync/test/PowerSyncTestLogWriter.kt similarity index 77% rename from core/src/commonTest/kotlin/com/powersync/PowerSyncTestLogWriter.kt rename to internal/testutils/src/commonMain/kotlin/com/powersync/test/PowerSyncTestLogWriter.kt index 10e2e602..da3d2ba5 100644 --- a/core/src/commonTest/kotlin/com/powersync/PowerSyncTestLogWriter.kt +++ b/internal/testutils/src/commonMain/kotlin/com/powersync/test/PowerSyncTestLogWriter.kt @@ -1,9 +1,9 @@ -package com.powersync +package com.powersync.test import co.touchlab.kermit.ExperimentalKermitApi import co.touchlab.kermit.LogWriter import co.touchlab.kermit.Severity -import co.touchlab.kermit.TestLogWriter.LogEntry +import co.touchlab.kermit.TestLogWriter import kotlinx.atomicfu.locks.reentrantLock import kotlinx.atomicfu.locks.withLock @@ -16,9 +16,9 @@ class PowerSyncTestLogWriter( private val loggable: Severity, ) : LogWriter() { private val lock = reentrantLock() - private val _logs = mutableListOf() + private val _logs = mutableListOf() - val logs: List + val logs: List get() = lock.withLock { _logs.toList() } override fun isLoggable( @@ -33,7 +33,7 @@ class PowerSyncTestLogWriter( throwable: Throwable?, ) { lock.withLock { - _logs.add(LogEntry(severity, message, tag, throwable)) + _logs.add(TestLogWriter.LogEntry(severity, message, tag, throwable)) } } } diff --git a/core/src/commonTest/kotlin/com/powersync/TestConnector.kt b/internal/testutils/src/commonMain/kotlin/com/powersync/test/TestConnector.kt similarity index 92% rename from core/src/commonTest/kotlin/com/powersync/TestConnector.kt rename to internal/testutils/src/commonMain/kotlin/com/powersync/test/TestConnector.kt index 2f8d81a5..297bdb8e 100644 --- a/core/src/commonTest/kotlin/com/powersync/TestConnector.kt +++ b/internal/testutils/src/commonMain/kotlin/com/powersync/test/TestConnector.kt @@ -1,5 +1,6 @@ -package com.powersync +package com.powersync.test +import com.powersync.PowerSyncDatabase import com.powersync.connectors.PowerSyncBackendConnector import com.powersync.connectors.PowerSyncCredentials diff --git a/core/src/commonTest/kotlin/com/powersync/testutils/WaitFor.kt b/internal/testutils/src/commonMain/kotlin/com/powersync/test/WaitFor.kt similarity index 89% rename from core/src/commonTest/kotlin/com/powersync/testutils/WaitFor.kt rename to internal/testutils/src/commonMain/kotlin/com/powersync/test/WaitFor.kt index 13f3e449..b8cf083f 100644 --- a/core/src/commonTest/kotlin/com/powersync/testutils/WaitFor.kt +++ b/internal/testutils/src/commonMain/kotlin/com/powersync/test/WaitFor.kt @@ -1,11 +1,11 @@ -package com.powersync.testutils +package com.powersync.test import kotlinx.coroutines.delay import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds import kotlin.time.TimeSource -internal suspend inline fun waitFor( +suspend inline fun waitFor( timeout: Duration = 500.milliseconds, interval: Duration = 100.milliseconds, test: () -> Unit, diff --git a/internal/testutils/src/platformMain/kotlin/com/powersync/test/TestPlatform.kt b/internal/testutils/src/platformMain/kotlin/com/powersync/test/TestPlatform.kt new file mode 100644 index 00000000..91cbd794 --- /dev/null +++ b/internal/testutils/src/platformMain/kotlin/com/powersync/test/TestPlatform.kt @@ -0,0 +1,18 @@ +package com.powersync.test + +import com.powersync.PersistentDriverFactory + +class TestPlatform { +} + +val factory: PersistentDriverFactory get() = TODO() + +fun cleanup(path: String) {} + +fun getTempDir(): String { + TODO() +} + +fun isIOS(): Boolean { + TODO() +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 4621c775..064dd7e2 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -29,6 +29,7 @@ rootProject.name = "powersync-root" include(":internal:download-core-extension") include(":internal:PowerSyncKotlin") +include(":internal:testutils") include(":common") include(":core") From f16eedf021fe7bc64d071a977059c46ae2de93d4 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Fri, 17 Oct 2025 12:02:07 +0200 Subject: [PATCH 3/8] Well, compiling works --- common/build.gradle.kts | 17 --------- .../kotlin/com/powersync/DatabaseTest.kt | 13 +------ .../com/powersync/DatabaseDriverFactory.kt | 20 +++++----- .../com/powersync/PowerSyncDatabaseFactory.kt | 4 +- .../db/driver/InternalConnectionPool.kt | 4 +- .../powersync/bucket/BucketStorageTest.kt | 0 .../powersync/db/ActiveDatabaseGroupTest.kt | 5 ++- .../kotlin}/powersync/db/FunctionTest.kt | 3 +- .../kotlin}/powersync/db/schema/SchemaTest.kt | 6 ++- .../kotlin}/powersync/db/schema/TableTest.kt | 38 +++++++++++++++---- .../kotlin}/powersync/sync/ProgressTest.kt | 3 +- .../powersync/sync/StreamingSyncClientTest.kt | 6 ++- .../sync/StreamingSyncRequestTest.kt | 0 .../kotlin}/powersync/sync/SyncLineTest.kt | 4 +- .../kotlin}/powersync/utils/JsonTest.kt | 4 +- .../kotlin}/powersync/utils/ThrottleTest.kt | 3 +- core/build.gradle.kts | 10 ----- .../DatabaseDriverFactory.android.kt | 11 ++---- .../powersync/testutils/TestUtils.apple.kt | 20 ---------- .../com/powersync/DatabaseDriverFactory.kt | 17 ++------- .../kotlin/com/powersync/PowerSyncDatabase.kt | 2 +- .../powersync/DatabaseDriverFactory.jvm.kt | 11 ++---- .../DatabaseDriverFactory.native.kt} | 11 +++--- .../DatabaseDriverFactory.watchos.kt | 25 ------------ core/src/watchosMain/powersync_static.h | 1 - internal/testutils/build.gradle.kts | 6 +++ .../com/powersync/test/TestPlatform.apple.kt | 6 +++ .../com/powersync/test/TestPlatform.jvm.kt | 6 +++ .../kotlin/com/powersync/test/TestPlatform.kt | 23 +++++------ 29 files changed, 119 insertions(+), 160 deletions(-) rename {core/src/commonTest/kotlin/com => common/src/commonTest/kotlin}/powersync/bucket/BucketStorageTest.kt (100%) rename {core/src/commonTest/kotlin/com => common/src/commonTest/kotlin}/powersync/db/ActiveDatabaseGroupTest.kt (93%) rename {core/src/commonTest/kotlin/com => common/src/commonTest/kotlin}/powersync/db/FunctionTest.kt (91%) rename {core/src/commonTest/kotlin/com => common/src/commonTest/kotlin}/powersync/db/schema/SchemaTest.kt (88%) rename {core/src/commonTest/kotlin/com => common/src/commonTest/kotlin}/powersync/db/schema/TableTest.kt (86%) rename {core/src/commonTest/kotlin/com => common/src/commonTest/kotlin}/powersync/sync/ProgressTest.kt (85%) rename {core/src/commonTest/kotlin/com => common/src/commonTest/kotlin}/powersync/sync/StreamingSyncClientTest.kt (97%) rename {core/src/commonTest/kotlin/com => common/src/commonTest/kotlin}/powersync/sync/StreamingSyncRequestTest.kt (100%) rename {core/src/commonTest/kotlin/com => common/src/commonTest/kotlin}/powersync/sync/SyncLineTest.kt (96%) rename {core/src/commonTest/kotlin/com => common/src/commonTest/kotlin}/powersync/utils/JsonTest.kt (98%) rename {core/src/commonTest/kotlin/com => common/src/commonTest/kotlin}/powersync/utils/ThrottleTest.kt (96%) delete mode 100644 core/src/appleTest/kotlin/com/powersync/testutils/TestUtils.apple.kt rename core/src/{appleNonWatchOsMain/kotlin/com/powersync/DatabaseDriverFactory.appleNonWatchOs.kt => nativeMain/kotlin/com/powersync/DatabaseDriverFactory.native.kt} (71%) delete mode 100644 core/src/watchosMain/kotlin/com/powersync/DatabaseDriverFactory.watchos.kt delete mode 100644 core/src/watchosMain/powersync_static.h create mode 100644 internal/testutils/src/appleMain/kotlin/com/powersync/test/TestPlatform.apple.kt create mode 100644 internal/testutils/src/jvmMain/kotlin/com/powersync/test/TestPlatform.jvm.kt diff --git a/common/build.gradle.kts b/common/build.gradle.kts index b1a2c3cd..4812b072 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -171,28 +171,11 @@ kotlin { dependsOn(commonJava) dependencies { api(libs.powersync.sqlite.core.android) - implementation(libs.ktor.client.okhttp) - implementation(libs.androidx.sqlite.bundled) } } jvmMain { dependsOn(commonJava) - - dependencies { - implementation(libs.ktor.client.okhttp) - implementation(libs.androidx.sqlite.bundled) - } - } - - appleMain.dependencies { - implementation(libs.ktor.client.darwin) - - // We're not using the bundled SQLite library for Apple platforms. Instead, we depend on - // static-sqlite-driver to link SQLite and have our own bindings implementing the - // driver. The reason for this is that androidx.sqlite-bundled causes linker errors for - // our Swift SDK. - implementation(projects.staticSqliteDriver) } // Common apple targets where we link the core extension dynamically diff --git a/common/src/commonIntegrationTest/kotlin/com/powersync/DatabaseTest.kt b/common/src/commonIntegrationTest/kotlin/com/powersync/DatabaseTest.kt index 726eb318..40604b4b 100644 --- a/common/src/commonIntegrationTest/kotlin/com/powersync/DatabaseTest.kt +++ b/common/src/commonIntegrationTest/kotlin/com/powersync/DatabaseTest.kt @@ -11,7 +11,6 @@ import com.powersync.db.schema.Schema import com.powersync.testutils.UserRow import com.powersync.testutils.databaseTest import com.powersync.test.getTempDir -import com.powersync.test.isIOS import com.powersync.test.waitFor import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.collections.shouldHaveSize @@ -266,17 +265,7 @@ class DatabaseTest { @Test fun openDBWithDirectory() = databaseTest { - val tempDir = - if (isIOS()) { - null - } else { - getTempDir() - } - - if (tempDir == null) { - // SQLiteR, which is used on iOS, does not support opening dbs from directories - return@databaseTest - } + val tempDir = getTempDir() // On platforms that support it, openDatabase() from our test utils should use a temporary // location. diff --git a/common/src/commonMain/kotlin/com/powersync/DatabaseDriverFactory.kt b/common/src/commonMain/kotlin/com/powersync/DatabaseDriverFactory.kt index 85692afb..6f1dcb79 100644 --- a/common/src/commonMain/kotlin/com/powersync/DatabaseDriverFactory.kt +++ b/common/src/commonMain/kotlin/com/powersync/DatabaseDriverFactory.kt @@ -1,18 +1,13 @@ package com.powersync import androidx.sqlite.SQLiteConnection -import io.ktor.client.HttpClient -import io.ktor.client.HttpClientConfig +import androidx.sqlite.SQLiteDriver -public interface PowerSyncPlatform { +public interface InMemoryConnectionFactory { public fun openInMemoryConnection(): SQLiteConnection - - public fun configureHttpClient(block: HttpClientConfig<*>.() -> Unit): HttpClient } -public interface PersistentDriverFactory { - public val platform: PowerSyncPlatform - +public interface PersistentConnectionFactory: InMemoryConnectionFactory { public fun resolveDefaultDatabasePath(dbFilename: String): String /** @@ -48,6 +43,14 @@ public interface PersistentDriverFactory { } } +public open class DriverBasedInMemoryFactory( + protected val driver: D, +): InMemoryConnectionFactory { + override fun openInMemoryConnection(): SQLiteConnection { + return driver.open(":memory:") + } +} + /** * Resolves a path to the loadable PowerSync core extension library. * @@ -65,4 +68,3 @@ public expect fun resolvePowerSyncLoadableExtensionPath(): String? private const val SQLITE_OPEN_READONLY = 0x01 private const val SQLITE_OPEN_READWRITE = 0x02 private const val SQLITE_OPEN_CREATE = 0x04 - diff --git a/common/src/commonMain/kotlin/com/powersync/PowerSyncDatabaseFactory.kt b/common/src/commonMain/kotlin/com/powersync/PowerSyncDatabaseFactory.kt index 6fd6cb67..824b6e04 100644 --- a/common/src/commonMain/kotlin/com/powersync/PowerSyncDatabaseFactory.kt +++ b/common/src/commonMain/kotlin/com/powersync/PowerSyncDatabaseFactory.kt @@ -20,7 +20,7 @@ public const val DEFAULT_DB_FILENAME: String = "powersync.db" @OptIn(DelicateCoroutinesApi::class) @DefaultArgumentInterop.Enabled public fun PowerSyncDatabase( - factory: PersistentDriverFactory, + factory: PersistentConnectionFactory, schema: Schema, dbFilename: String = DEFAULT_DB_FILENAME, scope: CoroutineScope = GlobalScope, @@ -45,7 +45,7 @@ public fun PowerSyncDatabase( @OptIn(ExperimentalPowerSyncAPI::class) internal fun createPowerSyncDatabaseImpl( - factory: PersistentDriverFactory, + factory: PersistentConnectionFactory, schema: Schema, dbFilename: String, scope: CoroutineScope, diff --git a/common/src/commonMain/kotlin/com/powersync/db/driver/InternalConnectionPool.kt b/common/src/commonMain/kotlin/com/powersync/db/driver/InternalConnectionPool.kt index 1663d359..00489429 100644 --- a/common/src/commonMain/kotlin/com/powersync/db/driver/InternalConnectionPool.kt +++ b/common/src/commonMain/kotlin/com/powersync/db/driver/InternalConnectionPool.kt @@ -3,7 +3,7 @@ package com.powersync.db.driver import androidx.sqlite.SQLiteConnection import androidx.sqlite.execSQL import com.powersync.ExperimentalPowerSyncAPI -import com.powersync.PersistentDriverFactory +import com.powersync.PersistentConnectionFactory import com.powersync.utils.JsonUtil import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableSharedFlow @@ -14,7 +14,7 @@ import kotlinx.coroutines.sync.withLock @OptIn(ExperimentalPowerSyncAPI::class) internal class InternalConnectionPool( - private val factory: PersistentDriverFactory, + private val factory: PersistentConnectionFactory, private val scope: CoroutineScope, private val dbFilename: String, private val dbDirectory: String?, diff --git a/core/src/commonTest/kotlin/com/powersync/bucket/BucketStorageTest.kt b/common/src/commonTest/kotlin/powersync/bucket/BucketStorageTest.kt similarity index 100% rename from core/src/commonTest/kotlin/com/powersync/bucket/BucketStorageTest.kt rename to common/src/commonTest/kotlin/powersync/bucket/BucketStorageTest.kt diff --git a/core/src/commonTest/kotlin/com/powersync/db/ActiveDatabaseGroupTest.kt b/common/src/commonTest/kotlin/powersync/db/ActiveDatabaseGroupTest.kt similarity index 93% rename from core/src/commonTest/kotlin/com/powersync/db/ActiveDatabaseGroupTest.kt rename to common/src/commonTest/kotlin/powersync/db/ActiveDatabaseGroupTest.kt index 87c84124..0b2a843f 100644 --- a/core/src/commonTest/kotlin/com/powersync/db/ActiveDatabaseGroupTest.kt +++ b/common/src/commonTest/kotlin/powersync/db/ActiveDatabaseGroupTest.kt @@ -1,10 +1,11 @@ -package com.powersync.db +package powersync.db import co.touchlab.kermit.ExperimentalKermitApi import co.touchlab.kermit.Logger import co.touchlab.kermit.Severity import co.touchlab.kermit.TestConfig import co.touchlab.kermit.TestLogWriter +import com.powersync.db.ActiveDatabaseGroup import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals @@ -52,7 +53,7 @@ class ActiveDatabaseGroupTest { val another = collection.referenceDatabase(logger, "test") assertNotNull( logWriter.logs.find { - it.message == ActiveDatabaseGroup.multipleInstancesMessage + it.message == ActiveDatabaseGroup.Companion.multipleInstancesMessage }, ) diff --git a/core/src/commonTest/kotlin/com/powersync/db/FunctionTest.kt b/common/src/commonTest/kotlin/powersync/db/FunctionTest.kt similarity index 91% rename from core/src/commonTest/kotlin/com/powersync/db/FunctionTest.kt rename to common/src/commonTest/kotlin/powersync/db/FunctionTest.kt index fc5089a9..7ab5c4f9 100644 --- a/core/src/commonTest/kotlin/com/powersync/db/FunctionTest.kt +++ b/common/src/commonTest/kotlin/powersync/db/FunctionTest.kt @@ -1,5 +1,6 @@ -package com.powersync.db +package powersync.db +import com.powersync.db.runWrapped import io.kotest.assertions.throwables.shouldThrow import kotlinx.coroutines.CancellationException import kotlin.test.Test diff --git a/core/src/commonTest/kotlin/com/powersync/db/schema/SchemaTest.kt b/common/src/commonTest/kotlin/powersync/db/schema/SchemaTest.kt similarity index 88% rename from core/src/commonTest/kotlin/com/powersync/db/schema/SchemaTest.kt rename to common/src/commonTest/kotlin/powersync/db/schema/SchemaTest.kt index 81cce024..6a424fc8 100644 --- a/core/src/commonTest/kotlin/com/powersync/db/schema/SchemaTest.kt +++ b/common/src/commonTest/kotlin/powersync/db/schema/SchemaTest.kt @@ -1,5 +1,9 @@ -package com.powersync.db.schema +package powersync.db.schema +import com.powersync.db.schema.Column +import com.powersync.db.schema.ColumnType +import com.powersync.db.schema.Schema +import com.powersync.db.schema.Table import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith diff --git a/core/src/commonTest/kotlin/com/powersync/db/schema/TableTest.kt b/common/src/commonTest/kotlin/powersync/db/schema/TableTest.kt similarity index 86% rename from core/src/commonTest/kotlin/com/powersync/db/schema/TableTest.kt rename to common/src/commonTest/kotlin/powersync/db/schema/TableTest.kt index 839be340..60e0d4e9 100644 --- a/core/src/commonTest/kotlin/com/powersync/db/schema/TableTest.kt +++ b/common/src/commonTest/kotlin/powersync/db/schema/TableTest.kt @@ -1,5 +1,13 @@ -package com.powersync.db.schema - +package powersync.db.schema + +import com.powersync.db.schema.Column +import com.powersync.db.schema.ColumnType +import com.powersync.db.schema.Index +import com.powersync.db.schema.IndexedColumn +import com.powersync.db.schema.SerializableTable +import com.powersync.db.schema.Table +import com.powersync.db.schema.TrackPreviousValuesOptions +import com.powersync.db.schema.toSerializable import com.powersync.utils.JsonUtil import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe @@ -33,7 +41,7 @@ class TableTest { @Test fun testLocalOnlyTable() { val columns = listOf(Column("content", ColumnType.TEXT)) - val table = Table.localOnly("notes", columns) + val table = Table.Companion.localOnly("notes", columns) assertTrue(table.internalName.startsWith("ps_data_local__")) assertEquals("notes", table.viewName) @@ -42,7 +50,7 @@ class TableTest { @Test fun testInsertOnlyTable() { val columns = listOf(Column("event", ColumnType.TEXT)) - val table = Table.insertOnly("logs", columns) + val table = Table.Companion.insertOnly("logs", columns) assertTrue(table.internalName.startsWith("ps_data__")) assertEquals("logs", table.viewName) @@ -166,7 +174,10 @@ class TableTest { @Test fun testValidationFailsDuplicateIndexColumn() { val columns = listOf(Column("name", ColumnType.TEXT)) - val indexes = listOf(Index("name_index", listOf(IndexedColumn("name"))), Index("name_index", listOf(IndexedColumn("name")))) + val indexes = listOf( + Index("name_index", listOf(IndexedColumn("name"))), + Index("name_index", listOf(IndexedColumn("name"))) + ) val table = Table("users", columns, indexes) val exception = @@ -192,7 +203,7 @@ class TableTest { @Test fun testValidationLocalOnlyWithMetadata() { - val table = Table("foo", listOf(Column.text("bar")), localOnly = true, trackMetadata = true) + val table = Table("foo", listOf(Column.Companion.text("bar")), localOnly = true, trackMetadata = true) val exception = shouldThrow { table.validate() } exception.message shouldBe "Can't track metadata for local-only tables." @@ -200,7 +211,12 @@ class TableTest { @Test fun testValidationLocalOnlyWithIncludeOld() { - val table = Table("foo", listOf(Column.text("bar")), localOnly = true, trackPreviousValues = TrackPreviousValuesOptions()) + val table = Table( + "foo", + listOf(Column.Companion.text("bar")), + localOnly = true, + trackPreviousValues = TrackPreviousValuesOptions() + ) val exception = shouldThrow { table.validate() } exception.message shouldBe "Can't track old values for local-only tables." @@ -219,7 +235,13 @@ class TableTest { it["include_old_only_when_changed"]!!.jsonPrimitive.boolean shouldBe false } - serialize(Table("foo", emptyList(), trackPreviousValues = TrackPreviousValuesOptions(columnFilter = listOf("foo", "bar")))).let { + serialize( + Table( + "foo", + emptyList(), + trackPreviousValues = TrackPreviousValuesOptions(columnFilter = listOf("foo", "bar")) + ) + ).let { it["include_old"]!!.jsonArray.map { e -> e.jsonPrimitive.content } shouldBe listOf("foo", "bar") it["include_old_only_when_changed"]!!.jsonPrimitive.boolean shouldBe false } diff --git a/core/src/commonTest/kotlin/com/powersync/sync/ProgressTest.kt b/common/src/commonTest/kotlin/powersync/sync/ProgressTest.kt similarity index 85% rename from core/src/commonTest/kotlin/com/powersync/sync/ProgressTest.kt rename to common/src/commonTest/kotlin/powersync/sync/ProgressTest.kt index c43e7c51..972182e7 100644 --- a/core/src/commonTest/kotlin/com/powersync/sync/ProgressTest.kt +++ b/common/src/commonTest/kotlin/powersync/sync/ProgressTest.kt @@ -1,5 +1,6 @@ -package com.powersync.sync +package powersync.sync +import com.powersync.sync.ProgressInfo import kotlin.test.Test import kotlin.test.assertEquals diff --git a/core/src/commonTest/kotlin/com/powersync/sync/StreamingSyncClientTest.kt b/common/src/commonTest/kotlin/powersync/sync/StreamingSyncClientTest.kt similarity index 97% rename from core/src/commonTest/kotlin/com/powersync/sync/StreamingSyncClientTest.kt rename to common/src/commonTest/kotlin/powersync/sync/StreamingSyncClientTest.kt index 19107e06..3da4a5cc 100644 --- a/core/src/commonTest/kotlin/com/powersync/sync/StreamingSyncClientTest.kt +++ b/common/src/commonTest/kotlin/powersync/sync/StreamingSyncClientTest.kt @@ -1,4 +1,4 @@ -package com.powersync.sync +package powersync.sync import app.cash.turbine.turbineScope import co.touchlab.kermit.ExperimentalKermitApi @@ -12,7 +12,11 @@ import com.powersync.connectors.PowerSyncBackendConnector import com.powersync.db.crud.CrudEntry import com.powersync.db.crud.UpdateType import com.powersync.db.schema.Schema +import com.powersync.sync.StreamingSyncClient import com.powersync.sync.StreamingSyncClient.Companion.bsonObjects +import com.powersync.sync.SyncClientConfiguration +import com.powersync.sync.SyncOptions +import com.powersync.sync.configureSyncHttpClient import dev.mokkery.answering.returns import dev.mokkery.everySuspend import dev.mokkery.mock diff --git a/core/src/commonTest/kotlin/com/powersync/sync/StreamingSyncRequestTest.kt b/common/src/commonTest/kotlin/powersync/sync/StreamingSyncRequestTest.kt similarity index 100% rename from core/src/commonTest/kotlin/com/powersync/sync/StreamingSyncRequestTest.kt rename to common/src/commonTest/kotlin/powersync/sync/StreamingSyncRequestTest.kt diff --git a/core/src/commonTest/kotlin/com/powersync/sync/SyncLineTest.kt b/common/src/commonTest/kotlin/powersync/sync/SyncLineTest.kt similarity index 96% rename from core/src/commonTest/kotlin/com/powersync/sync/SyncLineTest.kt rename to common/src/commonTest/kotlin/powersync/sync/SyncLineTest.kt index c5fff921..f69e0000 100644 --- a/core/src/commonTest/kotlin/com/powersync/sync/SyncLineTest.kt +++ b/common/src/commonTest/kotlin/powersync/sync/SyncLineTest.kt @@ -1,8 +1,10 @@ -package com.powersync.sync +package powersync.sync import com.powersync.bucket.BucketChecksum import com.powersync.bucket.Checkpoint import com.powersync.bucket.StreamPriority +import com.powersync.sync.LegacySyncImplementation +import com.powersync.sync.SyncLine import com.powersync.utils.JsonUtil import kotlin.test.Test import kotlin.test.assertEquals diff --git a/core/src/commonTest/kotlin/com/powersync/utils/JsonTest.kt b/common/src/commonTest/kotlin/powersync/utils/JsonTest.kt similarity index 98% rename from core/src/commonTest/kotlin/com/powersync/utils/JsonTest.kt rename to common/src/commonTest/kotlin/powersync/utils/JsonTest.kt index 0157012f..327c58dd 100644 --- a/core/src/commonTest/kotlin/com/powersync/utils/JsonTest.kt +++ b/common/src/commonTest/kotlin/powersync/utils/JsonTest.kt @@ -1,5 +1,7 @@ -package com.powersync.utils +package powersync.utils +import com.powersync.utils.JsonParam +import com.powersync.utils.toJsonObject import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonNull import kotlinx.serialization.json.JsonObject diff --git a/core/src/commonTest/kotlin/com/powersync/utils/ThrottleTest.kt b/common/src/commonTest/kotlin/powersync/utils/ThrottleTest.kt similarity index 96% rename from core/src/commonTest/kotlin/com/powersync/utils/ThrottleTest.kt rename to common/src/commonTest/kotlin/powersync/utils/ThrottleTest.kt index 8aa87b7a..45c0b114 100644 --- a/core/src/commonTest/kotlin/com/powersync/utils/ThrottleTest.kt +++ b/common/src/commonTest/kotlin/powersync/utils/ThrottleTest.kt @@ -1,5 +1,6 @@ -package com.powersync.utils +package powersync.utils +import com.powersync.utils.throttle import io.kotest.matchers.shouldBe import kotlinx.coroutines.delay import kotlinx.coroutines.flow.flow diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 8df85d51..5f4e993c 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -13,7 +13,6 @@ plugins { id("com.powersync.plugins.sonatype") id("com.powersync.plugins.sharedbuild") alias(libs.plugins.mokkery) - alias(libs.plugins.kotlin.atomicfu) id("dokka-convention") } @@ -75,15 +74,6 @@ kotlin { implementation(projects.staticSqliteDriver) } - // Common apple targets where we link the core extension dynamically - val appleNonWatchOsMain by creating { - dependsOn(appleMain.get()) - } - - macosMain.orNull?.dependsOn(appleNonWatchOsMain) - iosMain.orNull?.dependsOn(appleNonWatchOsMain) - tvosMain.orNull?.dependsOn(appleNonWatchOsMain) - commonTest.dependencies { implementation(projects.internal.testutils) } diff --git a/core/src/androidMain/kotlin/com/powersync/DatabaseDriverFactory.android.kt b/core/src/androidMain/kotlin/com/powersync/DatabaseDriverFactory.android.kt index 16542735..79b19311 100644 --- a/core/src/androidMain/kotlin/com/powersync/DatabaseDriverFactory.android.kt +++ b/core/src/androidMain/kotlin/com/powersync/DatabaseDriverFactory.android.kt @@ -8,12 +8,7 @@ import kotlin.Throws @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") public actual class DatabaseDriverFactory( private val context: Context, -): PersistentDriverFactory { - private val driver = BundledSQLiteDriver().also { it.addPowerSyncExtension() } - - actual override val platform: PowerSyncPlatform - get() = BuiltinPlatform - +): PersistentConnectionFactory, DriverBasedInMemoryFactory(newDriver()) { actual override fun resolveDefaultDatabasePath(dbFilename: String): String = context.getDatabasePath(dbFilename).path actual override fun openConnection( @@ -22,8 +17,10 @@ public actual class DatabaseDriverFactory( ): SQLiteConnection = driver.open(path, openFlags) } +private fun newDriver() = BundledSQLiteDriver().also { it.addPowerSyncExtension() } + public fun BundledSQLiteDriver.addPowerSyncExtension() { addExtension("libpowersync.so", "sqlite3_powersync_init") } -internal actual fun openInMemoryConnection(): SQLiteConnection = BundledSQLiteDriver().also { it.addPowerSyncExtension() }.open(":memory:") +internal actual val inMemoryDriver: InMemoryConnectionFactory = DriverBasedInMemoryFactory(newDriver()) diff --git a/core/src/appleTest/kotlin/com/powersync/testutils/TestUtils.apple.kt b/core/src/appleTest/kotlin/com/powersync/testutils/TestUtils.apple.kt deleted file mode 100644 index 396be545..00000000 --- a/core/src/appleTest/kotlin/com/powersync/testutils/TestUtils.apple.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.powersync.testutils - -import com.powersync.DatabaseDriverFactory -import kotlinx.io.files.Path -import kotlinx.io.files.SystemFileSystem -import platform.Foundation.NSTemporaryDirectory - -actual val factory: DatabaseDriverFactory - get() = DatabaseDriverFactory() - -actual fun cleanup(path: String) { - val resolved = Path(path) - if (SystemFileSystem.exists(resolved)) { - SystemFileSystem.delete(resolved) - } -} - -actual fun getTempDir(): String = NSTemporaryDirectory() - -actual fun isIOS(): Boolean = true diff --git a/core/src/commonMain/kotlin/com/powersync/DatabaseDriverFactory.kt b/core/src/commonMain/kotlin/com/powersync/DatabaseDriverFactory.kt index acfdd99e..71196cfc 100644 --- a/core/src/commonMain/kotlin/com/powersync/DatabaseDriverFactory.kt +++ b/core/src/commonMain/kotlin/com/powersync/DatabaseDriverFactory.kt @@ -1,23 +1,12 @@ package com.powersync import androidx.sqlite.SQLiteConnection -import io.ktor.client.HttpClient -import io.ktor.client.HttpClientConfig @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") -public expect class DatabaseDriverFactory: PersistentDriverFactory { - override val platform: PowerSyncPlatform - +public expect class DatabaseDriverFactory: PersistentConnectionFactory { + override fun openInMemoryConnection(): SQLiteConnection override fun resolveDefaultDatabasePath(dbFilename: String): String override fun openConnection(path: String, openFlags: Int): SQLiteConnection } -internal expect fun openInMemoryConnection(): SQLiteConnection - -internal object BuiltinPlatform: PowerSyncPlatform { - override fun openInMemoryConnection(): SQLiteConnection { - return com.powersync.openInMemoryConnection() - } - - override fun configureHttpClient(block: HttpClientConfig<*>.() -> Unit) = HttpClient(block) -} +internal expect val inMemoryDriver: InMemoryConnectionFactory diff --git a/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt b/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt index a750a771..34f19203 100644 --- a/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt +++ b/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt @@ -22,7 +22,7 @@ public fun PowerSyncDatabase.PowerSyncOpenFactory.inMemory( val collection = ActiveDatabaseGroup.GroupsCollection().referenceDatabase(logger, "test") return openedWithGroup( - SingleConnectionPool(openInMemoryConnection()), + SingleConnectionPool(inMemoryDriver.openInMemoryConnection()), scope, schema, logger, diff --git a/core/src/jvmMain/kotlin/com/powersync/DatabaseDriverFactory.jvm.kt b/core/src/jvmMain/kotlin/com/powersync/DatabaseDriverFactory.jvm.kt index 56b7d707..765960fb 100644 --- a/core/src/jvmMain/kotlin/com/powersync/DatabaseDriverFactory.jvm.kt +++ b/core/src/jvmMain/kotlin/com/powersync/DatabaseDriverFactory.jvm.kt @@ -4,12 +4,7 @@ import androidx.sqlite.SQLiteConnection import androidx.sqlite.driver.bundled.BundledSQLiteDriver @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", "SqlNoDataSourceInspection") -public actual class DatabaseDriverFactory: PersistentDriverFactory { - actual override val platform: PowerSyncPlatform - get() = BuiltinPlatform - - private val driver = BundledSQLiteDriver().also { it.addPowerSyncExtension() } - +public actual class DatabaseDriverFactory: PersistentConnectionFactory, DriverBasedInMemoryFactory(newDriver()) { actual override fun resolveDefaultDatabasePath(dbFilename: String): String = dbFilename actual override fun openConnection( @@ -18,9 +13,11 @@ public actual class DatabaseDriverFactory: PersistentDriverFactory { ): SQLiteConnection = driver.open(path, openFlags) } +private fun newDriver() = BundledSQLiteDriver().also { it.addPowerSyncExtension() } + @OptIn(ExperimentalPowerSyncAPI::class) public fun BundledSQLiteDriver.addPowerSyncExtension() { addExtension(resolvePowerSyncLoadableExtensionPath()!!, "sqlite3_powersync_init") } -internal actual fun openInMemoryConnection(): SQLiteConnection = DatabaseDriverFactory().openConnection(":memory:", 0x02) +internal actual val inMemoryDriver: InMemoryConnectionFactory = DriverBasedInMemoryFactory(newDriver()) diff --git a/core/src/appleNonWatchOsMain/kotlin/com/powersync/DatabaseDriverFactory.appleNonWatchOs.kt b/core/src/nativeMain/kotlin/com/powersync/DatabaseDriverFactory.native.kt similarity index 71% rename from core/src/appleNonWatchOsMain/kotlin/com/powersync/DatabaseDriverFactory.appleNonWatchOs.kt rename to core/src/nativeMain/kotlin/com/powersync/DatabaseDriverFactory.native.kt index 89d76c6d..86d6c093 100644 --- a/core/src/appleNonWatchOsMain/kotlin/com/powersync/DatabaseDriverFactory.appleNonWatchOs.kt +++ b/core/src/nativeMain/kotlin/com/powersync/DatabaseDriverFactory.native.kt @@ -4,10 +4,7 @@ import androidx.sqlite.SQLiteConnection import com.powersync.sqlite.Database @Suppress(names = ["EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING"]) -public actual class DatabaseDriverFactory: PersistentDriverFactory { - actual override val platform: PowerSyncPlatform - get() = BuiltinPlatform - +public actual class DatabaseDriverFactory: PersistentConnectionFactory { actual override fun resolveDefaultDatabasePath(dbFilename: String): String = appleDefaultDatabasePath(dbFilename) @OptIn(ExperimentalPowerSyncAPI::class) @@ -24,6 +21,10 @@ public actual class DatabaseDriverFactory: PersistentDriverFactory { } return db } + + actual override fun openInMemoryConnection(): SQLiteConnection { + return openConnection(":memory:", 0x02) + } } -internal actual fun openInMemoryConnection(): SQLiteConnection = DatabaseDriverFactory().openConnection(":memory:", 0x02) +internal actual val inMemoryDriver: InMemoryConnectionFactory = DatabaseDriverFactory() diff --git a/core/src/watchosMain/kotlin/com/powersync/DatabaseDriverFactory.watchos.kt b/core/src/watchosMain/kotlin/com/powersync/DatabaseDriverFactory.watchos.kt deleted file mode 100644 index 2dcc7e1f..00000000 --- a/core/src/watchosMain/kotlin/com/powersync/DatabaseDriverFactory.watchos.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.powersync - -import androidx.sqlite.SQLiteConnection -import com.powersync.sqlite.Database -import com.powersync.static.powersync_init_static - -@Suppress(names = ["EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING"]) -public actual class DatabaseDriverFactory: PersistentDriverFactory { - actual override val platform: PowerSyncPlatform - get() = BuiltinPlatform - - actual override fun resolveDefaultDatabasePath(dbFilename: String): String = appleDefaultDatabasePath(dbFilename) - - @OptIn(ExperimentalPowerSyncAPI::class) - actual override fun openConnection( - path: String, - openFlags: Int, - ): SQLiteConnection { - resolvePowerSyncLoadableExtensionPath() - - return Database.open(path, openFlags) - } -} - -internal actual fun openInMemoryConnection(): SQLiteConnection = DatabaseDriverFactory().openConnection(":memory:", 0x02) diff --git a/core/src/watchosMain/powersync_static.h b/core/src/watchosMain/powersync_static.h deleted file mode 100644 index 9a1d3560..00000000 --- a/core/src/watchosMain/powersync_static.h +++ /dev/null @@ -1 +0,0 @@ -int powersync_init_static(); diff --git a/internal/testutils/build.gradle.kts b/internal/testutils/build.gradle.kts index 4590aabd..b21974c7 100644 --- a/internal/testutils/build.gradle.kts +++ b/internal/testutils/build.gradle.kts @@ -27,6 +27,12 @@ kotlin { val platformMain by creating { dependsOn(commonMain.get()) + + dependencies { + // :core links SQLite, which is what we want for tests even in the :common project where the public API + // does not require linking SQLite. + api(projects.core) + } } jvmMain.get().dependsOn(platformMain) diff --git a/internal/testutils/src/appleMain/kotlin/com/powersync/test/TestPlatform.apple.kt b/internal/testutils/src/appleMain/kotlin/com/powersync/test/TestPlatform.apple.kt new file mode 100644 index 00000000..698e4890 --- /dev/null +++ b/internal/testutils/src/appleMain/kotlin/com/powersync/test/TestPlatform.apple.kt @@ -0,0 +1,6 @@ +package com.powersync.test + +import com.powersync.DatabaseDriverFactory +import com.powersync.PersistentConnectionFactory + +actual val factory: PersistentConnectionFactory = DatabaseDriverFactory() diff --git a/internal/testutils/src/jvmMain/kotlin/com/powersync/test/TestPlatform.jvm.kt b/internal/testutils/src/jvmMain/kotlin/com/powersync/test/TestPlatform.jvm.kt new file mode 100644 index 00000000..698e4890 --- /dev/null +++ b/internal/testutils/src/jvmMain/kotlin/com/powersync/test/TestPlatform.jvm.kt @@ -0,0 +1,6 @@ +package com.powersync.test + +import com.powersync.DatabaseDriverFactory +import com.powersync.PersistentConnectionFactory + +actual val factory: PersistentConnectionFactory = DatabaseDriverFactory() diff --git a/internal/testutils/src/platformMain/kotlin/com/powersync/test/TestPlatform.kt b/internal/testutils/src/platformMain/kotlin/com/powersync/test/TestPlatform.kt index 91cbd794..49789aad 100644 --- a/internal/testutils/src/platformMain/kotlin/com/powersync/test/TestPlatform.kt +++ b/internal/testutils/src/platformMain/kotlin/com/powersync/test/TestPlatform.kt @@ -1,18 +1,19 @@ package com.powersync.test -import com.powersync.PersistentDriverFactory +import com.powersync.PersistentConnectionFactory +import kotlinx.io.files.Path +import kotlinx.io.files.SystemFileSystem +import kotlinx.io.files.SystemTemporaryDirectory -class TestPlatform { -} - -val factory: PersistentDriverFactory get() = TODO() - -fun cleanup(path: String) {} +expect val factory: PersistentConnectionFactory -fun getTempDir(): String { - TODO() +fun cleanup(path: String) { + val resolved = Path(path) + if (SystemFileSystem.exists(resolved)) { + SystemFileSystem.delete(resolved) + } } -fun isIOS(): Boolean { - TODO() +fun getTempDir(): String { + return SystemTemporaryDirectory.name } From eaaeaed50b6be66599c7635da7b7deb7badc1293 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Fri, 17 Oct 2025 13:32:07 +0200 Subject: [PATCH 4/8] Fixing some tests --- README.md | 9 +++++++- common/build.gradle.kts | 23 ++++++++++++++++++- .../kotlin/powersync/db/FunctionTest.kt | 1 + .../powersync/sync/StreamingSyncClientTest.kt | 1 + .../powersync/DatabaseDriverFactory.jvm.kt | 17 ++++++++++---- .../kotlin/com/powersync/test/TestPlatform.kt | 2 +- 6 files changed, 46 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index ad0d92ed..f6c3012f 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,14 @@ and API documentation [here](https://powersync-ja.github.io/powersync-kotlin/). - [core](./core/) - - This is the Kotlin Multiplatform SDK implementation. + - This is the Kotlin Multiplatform SDK implementation, built by depending on `common` + and linking SQLite. + +- [common](./common/) + + - This is the Kotlin Multiplatform SDK implementation without a dependency on a fixed + SQLite bundle. This allows the SDK to be used with custom SQLite installations (like + e.g. SQLCipher). - [integrations](./integrations/) - [room](./integrations/room/README.md): Allows using the [Room database library](https://developer.android.com/jetpack/androidx/releases/room) with PowerSync, making it easier to run typed queries on the database. diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 4812b072..d2caf544 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -3,6 +3,7 @@ import de.undercouch.gradle.tasks.download.Download import org.gradle.api.tasks.testing.logging.TestExceptionFormat import org.gradle.internal.os.OperatingSystem import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget +import org.jetbrains.kotlin.gradle.targets.jvm.tasks.KotlinJvmTest import org.jetbrains.kotlin.gradle.tasks.KotlinTest import org.jetbrains.kotlin.konan.target.Family import java.nio.file.Path @@ -189,11 +190,14 @@ kotlin { commonTest.dependencies { implementation(projects.internal.testutils) + implementation(libs.kotlin.test) } // We're putting the native libraries into our JAR, so integration tests for the JVM can run as part of the unit // tests. - jvmTest.get().dependsOn(commonIntegrationTest) + jvmTest { + dependsOn(commonIntegrationTest) + } // We have special setup in this build configuration to make these tests link the PowerSync extension, so they // can run integration tests along with the executable for unit testing. @@ -239,6 +243,23 @@ tasks.named(kotlin.jvm().compilations["main"].processResources from(downloadPowersyncDesktopBinaries) } +// We want to build with recent JDKs, but need to make sure we support Java 8. https://jakewharton.com/build-on-latest-java-test-through-lowest-java/ +val testWithJava8 by tasks.registering(KotlinJvmTest::class) { + javaLauncher = + javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(8) + } + + description = "Run tests with Java 8" + group = LifecycleBasePlugin.VERIFICATION_GROUP + + // Copy inputs from the normal test task + val testTask = tasks.getByName("jvmTest") as KotlinJvmTest + classpath = testTask.classpath + testClassesDirs = testTask.testClassesDirs +} +tasks.named("check").configure { dependsOn(testWithJava8) } + tasks.withType { testLogging { events("PASSED", "FAILED", "SKIPPED") diff --git a/common/src/commonTest/kotlin/powersync/db/FunctionTest.kt b/common/src/commonTest/kotlin/powersync/db/FunctionTest.kt index 7ab5c4f9..c5210cd5 100644 --- a/common/src/commonTest/kotlin/powersync/db/FunctionTest.kt +++ b/common/src/commonTest/kotlin/powersync/db/FunctionTest.kt @@ -1,5 +1,6 @@ package powersync.db +import com.powersync.PowerSyncException import com.powersync.db.runWrapped import io.kotest.assertions.throwables.shouldThrow import kotlinx.coroutines.CancellationException diff --git a/common/src/commonTest/kotlin/powersync/sync/StreamingSyncClientTest.kt b/common/src/commonTest/kotlin/powersync/sync/StreamingSyncClientTest.kt index 3da4a5cc..cc239a13 100644 --- a/common/src/commonTest/kotlin/powersync/sync/StreamingSyncClientTest.kt +++ b/common/src/commonTest/kotlin/powersync/sync/StreamingSyncClientTest.kt @@ -17,6 +17,7 @@ import com.powersync.sync.StreamingSyncClient.Companion.bsonObjects import com.powersync.sync.SyncClientConfiguration import com.powersync.sync.SyncOptions import com.powersync.sync.configureSyncHttpClient +import com.powersync.test.TestConnector import dev.mokkery.answering.returns import dev.mokkery.everySuspend import dev.mokkery.mock diff --git a/core/src/jvmMain/kotlin/com/powersync/DatabaseDriverFactory.jvm.kt b/core/src/jvmMain/kotlin/com/powersync/DatabaseDriverFactory.jvm.kt index 765960fb..a346c561 100644 --- a/core/src/jvmMain/kotlin/com/powersync/DatabaseDriverFactory.jvm.kt +++ b/core/src/jvmMain/kotlin/com/powersync/DatabaseDriverFactory.jvm.kt @@ -11,13 +11,22 @@ public actual class DatabaseDriverFactory: PersistentConnectionFactory, DriverBa path: String, openFlags: Int, ): SQLiteConnection = driver.open(path, openFlags) -} -private fun newDriver() = BundledSQLiteDriver().also { it.addPowerSyncExtension() } + internal companion object { + fun newDriver(): BundledSQLiteDriver { + return BundledSQLiteDriver().also { addPowerSyncExtension(it) } + } + + @OptIn(ExperimentalPowerSyncAPI::class) + fun addPowerSyncExtension(driver: BundledSQLiteDriver) { + driver.addExtension(resolvePowerSyncLoadableExtensionPath()!!, "sqlite3_powersync_init") + } + } +} @OptIn(ExperimentalPowerSyncAPI::class) public fun BundledSQLiteDriver.addPowerSyncExtension() { - addExtension(resolvePowerSyncLoadableExtensionPath()!!, "sqlite3_powersync_init") + DatabaseDriverFactory.addPowerSyncExtension(this) } -internal actual val inMemoryDriver: InMemoryConnectionFactory = DriverBasedInMemoryFactory(newDriver()) +internal actual val inMemoryDriver: InMemoryConnectionFactory = DriverBasedInMemoryFactory(DatabaseDriverFactory.newDriver()) diff --git a/internal/testutils/src/platformMain/kotlin/com/powersync/test/TestPlatform.kt b/internal/testutils/src/platformMain/kotlin/com/powersync/test/TestPlatform.kt index 49789aad..9e4b6830 100644 --- a/internal/testutils/src/platformMain/kotlin/com/powersync/test/TestPlatform.kt +++ b/internal/testutils/src/platformMain/kotlin/com/powersync/test/TestPlatform.kt @@ -15,5 +15,5 @@ fun cleanup(path: String) { } fun getTempDir(): String { - return SystemTemporaryDirectory.name + return SystemTemporaryDirectory.toString() } From 13dd40ef4390affdaf64d947c7213f3587636c7b Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Fri, 17 Oct 2025 13:47:37 +0200 Subject: [PATCH 5/8] Update readmes --- common/README.md | 25 +++++++++++++++++++ ...ndroid.kt => ConnectionFactory.android.kt} | 0 .../com/powersync/sync/SyncIntegrationTest.kt | 7 +++--- ...eDriverFactory.kt => ConnectionFactory.kt} | 0 .../connectors/PowerSyncBackendConnector.kt | 5 ++++ ...actory.jvm.kt => ConnectionFactory.jvm.kt} | 0 ...atchos.kt => ConnectionFactory.watchos.kt} | 0 core/README.md | 23 +++-------------- core/build.gradle.kts | 1 + .../powersync/DatabaseDriverFactory.jvm.kt | 17 +++---------- .../com/powersync/testutils/TestUtils.jvm.kt | 15 ----------- 11 files changed, 43 insertions(+), 50 deletions(-) create mode 100644 common/README.md rename common/src/androidMain/kotlin/com/powersync/{DatabaseDriverFactory.android.kt => ConnectionFactory.android.kt} (100%) rename common/src/commonMain/kotlin/com/powersync/{DatabaseDriverFactory.kt => ConnectionFactory.kt} (100%) rename common/src/jvmMain/kotlin/com/powersync/{DatabaseDriverFactory.jvm.kt => ConnectionFactory.jvm.kt} (100%) rename common/src/watchosMain/kotlin/com/powersync/{DatabaseDriverFactory.watchos.kt => ConnectionFactory.watchos.kt} (100%) delete mode 100644 core/src/jvmTest/kotlin/com/powersync/testutils/TestUtils.jvm.kt diff --git a/common/README.md b/common/README.md new file mode 100644 index 00000000..c0b7a1d7 --- /dev/null +++ b/common/README.md @@ -0,0 +1,25 @@ +# PowerSync common + +This module contains core definitions for the PowerSync SDK, without linking or bundling a SQLite dependency. + +This allows the module to be used as a building block for PowerSync SDKs with and without encryption support. + +Users should typically depend on `:core` instead. + +## Structure + +This is a Kotlin Multiplatform project targeting Android, iOS platforms, with the following +structure: + +- `commonMain` - Shared code for all targets, which includes the `PowerSyncBackendConnector` + interface and `PowerSyncBuilder` for building a `PowerSync` instance. It also defines + the `DatabaseDriverFactory` class to be implemented in each platform. +- `commonJava` - Shared logic for Android and Java targets. +- `androidMain` - Android-specific code for loading the core extension. +- `jvmMain` - Java-specific code for loading the core extension. +- `nativeMain` - A SQLite driver implemented with cinterop calls to sqlite3. + +## Attachment Helpers + +This module contains attachment helpers under the `com.powersync.attachments` package. See +the [Attachment Helpers README](../common/src/commonMain/kotlin/com/powersync/attachments/README.md) diff --git a/common/src/androidMain/kotlin/com/powersync/DatabaseDriverFactory.android.kt b/common/src/androidMain/kotlin/com/powersync/ConnectionFactory.android.kt similarity index 100% rename from common/src/androidMain/kotlin/com/powersync/DatabaseDriverFactory.android.kt rename to common/src/androidMain/kotlin/com/powersync/ConnectionFactory.android.kt diff --git a/common/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncIntegrationTest.kt b/common/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncIntegrationTest.kt index cfe2e57a..73822cac 100644 --- a/common/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncIntegrationTest.kt +++ b/common/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncIntegrationTest.kt @@ -15,6 +15,7 @@ import com.powersync.bucket.WriteCheckpointData import com.powersync.bucket.WriteCheckpointResponse import com.powersync.connectors.PowerSyncBackendConnector import com.powersync.connectors.PowerSyncCredentials +import com.powersync.connectors.readCachedCredentials import com.powersync.db.PowerSyncDatabaseImpl import com.powersync.db.schema.PendingStatement import com.powersync.db.schema.PendingStatementParameter @@ -123,7 +124,7 @@ abstract class BaseSyncIntegrationTest( turbineScope(timeout = 10.0.seconds) { val turbine = database.currentStatus.asFlow().testIn(this) turbine.waitFor { it.connected } - connector.cachedCredentials shouldNotBe null + connector.readCachedCredentials() shouldNotBe null database.disconnect() turbine.waitFor { !it.connected } @@ -134,7 +135,7 @@ abstract class BaseSyncIntegrationTest( waitFor { syncLines.isClosedForSend shouldBe true } // And called invalidateCredentials on the connector - connector.cachedCredentials shouldBe null + connector.readCachedCredentials() shouldBe null } @Test @@ -674,7 +675,7 @@ abstract class BaseSyncIntegrationTest( // Should invalidate credentials when token expires syncLines.send(SyncLine.KeepAlive(tokenExpiresIn = 0)) turbine.waitFor { !it.connected } - connector.cachedCredentials shouldBe null + connector.readCachedCredentials() shouldBe null turbine.cancelAndIgnoreRemainingEvents() } diff --git a/common/src/commonMain/kotlin/com/powersync/DatabaseDriverFactory.kt b/common/src/commonMain/kotlin/com/powersync/ConnectionFactory.kt similarity index 100% rename from common/src/commonMain/kotlin/com/powersync/DatabaseDriverFactory.kt rename to common/src/commonMain/kotlin/com/powersync/ConnectionFactory.kt diff --git a/common/src/commonMain/kotlin/com/powersync/connectors/PowerSyncBackendConnector.kt b/common/src/commonMain/kotlin/com/powersync/connectors/PowerSyncBackendConnector.kt index 17f23302..1b4d74c0 100644 --- a/common/src/commonMain/kotlin/com/powersync/connectors/PowerSyncBackendConnector.kt +++ b/common/src/commonMain/kotlin/com/powersync/connectors/PowerSyncBackendConnector.kt @@ -128,3 +128,8 @@ public abstract class PowerSyncBackendConnector { @Throws(PowerSyncException::class, CancellationException::class) public abstract suspend fun uploadData(database: PowerSyncDatabase) } + +// Not using this indirection causes linker errors in tests: https://youtrack.jetbrains.com/issue/CMP-3318 +internal fun PowerSyncBackendConnector.readCachedCredentials(): PowerSyncCredentials? { + return this.cachedCredentials +} diff --git a/common/src/jvmMain/kotlin/com/powersync/DatabaseDriverFactory.jvm.kt b/common/src/jvmMain/kotlin/com/powersync/ConnectionFactory.jvm.kt similarity index 100% rename from common/src/jvmMain/kotlin/com/powersync/DatabaseDriverFactory.jvm.kt rename to common/src/jvmMain/kotlin/com/powersync/ConnectionFactory.jvm.kt diff --git a/common/src/watchosMain/kotlin/com/powersync/DatabaseDriverFactory.watchos.kt b/common/src/watchosMain/kotlin/com/powersync/ConnectionFactory.watchos.kt similarity index 100% rename from common/src/watchosMain/kotlin/com/powersync/DatabaseDriverFactory.watchos.kt rename to common/src/watchosMain/kotlin/com/powersync/ConnectionFactory.watchos.kt diff --git a/core/README.md b/core/README.md index ee09bf8a..c5bba791 100644 --- a/core/README.md +++ b/core/README.md @@ -11,27 +11,12 @@ structure: - `commonMain` - Shared code for all targets, which includes the `PowerSyncBackendConnector` interface and `PowerSyncBuilder` for building a `PowerSync` instance. It also defines the `DatabaseDriverFactory` class to be implemented in each platform. -- `commonJava` - Common Java code including a Java SQLite driver using - the [Xerial JDBC Driver](https://github.com/xerial/sqlite-jdbc). This is used by both the Android - and JVM drivers. - `androidMain` - Android specific code, which includes an implementation of - `DatabaseDriverFactory`. -- `jvmMain` - JVM specific code which includes an implementation of `DatabaseDriverFactory`. -- `iosMain` - iOS specific code, which includes am implementation of `DatabaseDriverFactory` class - that creates an instance of `app.cash.sqldelight.driver.native.NativeSqliteDriver` and also sets - up native SQLite bindings for iOS. - -## Note on SQLDelight - -The PowerSync core module, internally makes use -of [SQLDelight](https://sqldelight.github.io/sqldelight/latest/) for it database API and typesafe -database -query generation. - -The PowerSync core module does not currently support integrating with SQLDelight from client -applications. + `PersistentConnectionFactory`. +- `jvmMain` - JVM specific code which includes an implementation of `PersistentConnectionFactory`. +- `nativeMain` - iOS specific code, which includes am implementation of `PersistentConnectionFactory`. ## Attachment Helpers This module contains attachment helpers under the `com.powersync.attachments` package. See -the [Attachment Helpers README](./src/commonMain/kotlin/com/powersync/attachments/README.md) +the [Attachment Helpers README](../common/src/commonMain/kotlin/com/powersync/attachments/README.md) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 5f4e993c..4eb75e6f 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -76,6 +76,7 @@ kotlin { commonTest.dependencies { implementation(projects.internal.testutils) + implementation(libs.kotlin.test) } // We're putting the native libraries into our JAR, so integration tests for the JVM can run as part of the unit diff --git a/core/src/jvmMain/kotlin/com/powersync/DatabaseDriverFactory.jvm.kt b/core/src/jvmMain/kotlin/com/powersync/DatabaseDriverFactory.jvm.kt index a346c561..8999e12b 100644 --- a/core/src/jvmMain/kotlin/com/powersync/DatabaseDriverFactory.jvm.kt +++ b/core/src/jvmMain/kotlin/com/powersync/DatabaseDriverFactory.jvm.kt @@ -11,22 +11,13 @@ public actual class DatabaseDriverFactory: PersistentConnectionFactory, DriverBa path: String, openFlags: Int, ): SQLiteConnection = driver.open(path, openFlags) - - internal companion object { - fun newDriver(): BundledSQLiteDriver { - return BundledSQLiteDriver().also { addPowerSyncExtension(it) } - } - - @OptIn(ExperimentalPowerSyncAPI::class) - fun addPowerSyncExtension(driver: BundledSQLiteDriver) { - driver.addExtension(resolvePowerSyncLoadableExtensionPath()!!, "sqlite3_powersync_init") - } - } } +internal fun newDriver() = BundledSQLiteDriver().also { it.addPowerSyncExtension() } + @OptIn(ExperimentalPowerSyncAPI::class) public fun BundledSQLiteDriver.addPowerSyncExtension() { - DatabaseDriverFactory.addPowerSyncExtension(this) + addExtension(resolvePowerSyncLoadableExtensionPath()!!, "sqlite3_powersync_init") } -internal actual val inMemoryDriver: InMemoryConnectionFactory = DriverBasedInMemoryFactory(DatabaseDriverFactory.newDriver()) +internal actual val inMemoryDriver: InMemoryConnectionFactory = DriverBasedInMemoryFactory(newDriver()) diff --git a/core/src/jvmTest/kotlin/com/powersync/testutils/TestUtils.jvm.kt b/core/src/jvmTest/kotlin/com/powersync/testutils/TestUtils.jvm.kt deleted file mode 100644 index 9296387a..00000000 --- a/core/src/jvmTest/kotlin/com/powersync/testutils/TestUtils.jvm.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.powersync.testutils - -import com.powersync.DatabaseDriverFactory -import java.io.File - -actual val factory: DatabaseDriverFactory - get() = DatabaseDriverFactory() - -actual fun cleanup(path: String) { - File(path).delete() -} - -actual fun getTempDir(): String = System.getProperty("java.io.tmpdir") - -actual fun isIOS(): Boolean = false From fbaa0da58e6b7d331a09c3d5b031de2f5908013a Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 20 Oct 2025 09:15:42 +0200 Subject: [PATCH 6/8] Reformat --- .../kotlin/com/powersync/AttachmentsTest.kt | 2 +- .../kotlin/com/powersync/DatabaseTest.kt | 4 +-- .../com/powersync/sync/SyncIntegrationTest.kt | 2 +- .../com/powersync/testutils/TestUtils.kt | 4 +-- common/src/commonMain/kotlin/BuildConfig.kt | 2 +- .../kotlin/com/powersync/ConnectionFactory.kt | 10 +++---- .../connectors/PowerSyncBackendConnector.kt | 4 +-- .../db/driver/SingleConnectionPool.kt | 7 ++--- .../kotlin/powersync/db/schema/TableTest.kt | 26 ++++++++++--------- common/src/jvmMain/kotlin/BuildConfig.kt | 2 +- .../kotlin/com/powersync/sqlite/Database.kt | 23 ++++++++-------- core/build.gradle.kts | 13 ---------- .../DatabaseDriverFactory.android.kt | 3 ++- .../kotlin/com/powersync/db/InMemoryTest.kt | 2 +- .../com/powersync/DatabaseDriverFactory.kt | 9 +++++-- .../powersync/DatabaseDriverFactory.jvm.kt | 4 ++- .../powersync/DatabaseDriverFactory.native.kt | 6 ++--- .../integrations/sqldelight/SqlDelightTest.kt | 3 +-- .../kotlin/com/powersync/test/TestPlatform.kt | 4 +-- 19 files changed, 60 insertions(+), 70 deletions(-) diff --git a/common/src/commonIntegrationTest/kotlin/com/powersync/AttachmentsTest.kt b/common/src/commonIntegrationTest/kotlin/com/powersync/AttachmentsTest.kt index ae042074..47715fa6 100644 --- a/common/src/commonIntegrationTest/kotlin/com/powersync/AttachmentsTest.kt +++ b/common/src/commonIntegrationTest/kotlin/com/powersync/AttachmentsTest.kt @@ -12,11 +12,11 @@ import com.powersync.attachments.createAttachmentsTable import com.powersync.db.getString import com.powersync.db.schema.Schema import com.powersync.db.schema.Table +import com.powersync.test.getTempDir import com.powersync.testutils.ActiveDatabaseTest import com.powersync.testutils.MockedRemoteStorage import com.powersync.testutils.UserRow import com.powersync.testutils.databaseTest -import com.powersync.test.getTempDir import dev.mokkery.answering.throws import dev.mokkery.everySuspend import dev.mokkery.matcher.ArgMatchersScope diff --git a/common/src/commonIntegrationTest/kotlin/com/powersync/DatabaseTest.kt b/common/src/commonIntegrationTest/kotlin/com/powersync/DatabaseTest.kt index 40604b4b..538c4f4b 100644 --- a/common/src/commonIntegrationTest/kotlin/com/powersync/DatabaseTest.kt +++ b/common/src/commonIntegrationTest/kotlin/com/powersync/DatabaseTest.kt @@ -8,10 +8,10 @@ import com.powersync.db.crud.CrudEntry import com.powersync.db.crud.CrudTransaction import com.powersync.db.getString import com.powersync.db.schema.Schema -import com.powersync.testutils.UserRow -import com.powersync.testutils.databaseTest import com.powersync.test.getTempDir import com.powersync.test.waitFor +import com.powersync.testutils.UserRow +import com.powersync.testutils.databaseTest import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.shouldBe diff --git a/common/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncIntegrationTest.kt b/common/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncIntegrationTest.kt index 73822cac..2993a10c 100644 --- a/common/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncIntegrationTest.kt +++ b/common/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncIntegrationTest.kt @@ -5,7 +5,6 @@ import co.touchlab.kermit.ExperimentalKermitApi import com.powersync.ExperimentalPowerSyncAPI import com.powersync.PowerSyncDatabase import com.powersync.PowerSyncException -import com.powersync.test.TestConnector import com.powersync.bucket.BucketChecksum import com.powersync.bucket.Checkpoint import com.powersync.bucket.OpType @@ -21,6 +20,7 @@ import com.powersync.db.schema.PendingStatement import com.powersync.db.schema.PendingStatementParameter import com.powersync.db.schema.RawTable import com.powersync.db.schema.Schema +import com.powersync.test.TestConnector import com.powersync.test.waitFor import com.powersync.testutils.UserRow import com.powersync.testutils.databaseTest diff --git a/common/src/commonIntegrationTest/kotlin/com/powersync/testutils/TestUtils.kt b/common/src/commonIntegrationTest/kotlin/com/powersync/testutils/TestUtils.kt index fd218ab3..d71edc19 100644 --- a/common/src/commonIntegrationTest/kotlin/com/powersync/testutils/TestUtils.kt +++ b/common/src/commonIntegrationTest/kotlin/com/powersync/testutils/TestUtils.kt @@ -8,8 +8,6 @@ import co.touchlab.kermit.Logger import co.touchlab.kermit.Severity import co.touchlab.kermit.TestConfig import com.powersync.ExperimentalPowerSyncAPI -import com.powersync.test.PowerSyncTestLogWriter -import com.powersync.test.TestConnector import com.powersync.bucket.WriteCheckpointData import com.powersync.bucket.WriteCheckpointResponse import com.powersync.createPowerSyncDatabaseImpl @@ -17,6 +15,8 @@ import com.powersync.db.PowerSyncDatabaseImpl import com.powersync.db.schema.Schema import com.powersync.sync.LegacySyncImplementation import com.powersync.sync.configureSyncHttpClient +import com.powersync.test.PowerSyncTestLogWriter +import com.powersync.test.TestConnector import com.powersync.test.cleanup import com.powersync.test.factory import com.powersync.test.getTempDir diff --git a/common/src/commonMain/kotlin/BuildConfig.kt b/common/src/commonMain/kotlin/BuildConfig.kt index 4cec9226..0b9cc8d3 100644 --- a/common/src/commonMain/kotlin/BuildConfig.kt +++ b/common/src/commonMain/kotlin/BuildConfig.kt @@ -1,5 +1,5 @@ @Suppress - ("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") +("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") internal expect object BuildConfig { val isDebug: Boolean } diff --git a/common/src/commonMain/kotlin/com/powersync/ConnectionFactory.kt b/common/src/commonMain/kotlin/com/powersync/ConnectionFactory.kt index 6f1dcb79..1a8119dc 100644 --- a/common/src/commonMain/kotlin/com/powersync/ConnectionFactory.kt +++ b/common/src/commonMain/kotlin/com/powersync/ConnectionFactory.kt @@ -7,7 +7,7 @@ public interface InMemoryConnectionFactory { public fun openInMemoryConnection(): SQLiteConnection } -public interface PersistentConnectionFactory: InMemoryConnectionFactory { +public interface PersistentConnectionFactory : InMemoryConnectionFactory { public fun resolveDefaultDatabasePath(dbFilename: String): String /** @@ -43,12 +43,10 @@ public interface PersistentConnectionFactory: InMemoryConnectionFactory { } } -public open class DriverBasedInMemoryFactory( +public open class DriverBasedInMemoryFactory( protected val driver: D, -): InMemoryConnectionFactory { - override fun openInMemoryConnection(): SQLiteConnection { - return driver.open(":memory:") - } +) : InMemoryConnectionFactory { + override fun openInMemoryConnection(): SQLiteConnection = driver.open(":memory:") } /** diff --git a/common/src/commonMain/kotlin/com/powersync/connectors/PowerSyncBackendConnector.kt b/common/src/commonMain/kotlin/com/powersync/connectors/PowerSyncBackendConnector.kt index 1b4d74c0..49761ea1 100644 --- a/common/src/commonMain/kotlin/com/powersync/connectors/PowerSyncBackendConnector.kt +++ b/common/src/commonMain/kotlin/com/powersync/connectors/PowerSyncBackendConnector.kt @@ -130,6 +130,4 @@ public abstract class PowerSyncBackendConnector { } // Not using this indirection causes linker errors in tests: https://youtrack.jetbrains.com/issue/CMP-3318 -internal fun PowerSyncBackendConnector.readCachedCredentials(): PowerSyncCredentials? { - return this.cachedCredentials -} +internal fun PowerSyncBackendConnector.readCachedCredentials(): PowerSyncCredentials? = this.cachedCredentials diff --git a/common/src/commonMain/kotlin/com/powersync/db/driver/SingleConnectionPool.kt b/common/src/commonMain/kotlin/com/powersync/db/driver/SingleConnectionPool.kt index 4a9c2b36..f23a66d4 100644 --- a/common/src/commonMain/kotlin/com/powersync/db/driver/SingleConnectionPool.kt +++ b/common/src/commonMain/kotlin/com/powersync/db/driver/SingleConnectionPool.kt @@ -42,9 +42,10 @@ public class SingleConnectionPool( override suspend fun withAllConnections( action: suspend (writer: SQLiteConnectionLease, readers: List) -> R, - ): Unit = write { writer -> - action(writer, emptyList()) - } + ): Unit = + write { writer -> + action(writer, emptyList()) + } override val updates: SharedFlow> get() = tableUpdatesFlow diff --git a/common/src/commonTest/kotlin/powersync/db/schema/TableTest.kt b/common/src/commonTest/kotlin/powersync/db/schema/TableTest.kt index 60e0d4e9..6fa51754 100644 --- a/common/src/commonTest/kotlin/powersync/db/schema/TableTest.kt +++ b/common/src/commonTest/kotlin/powersync/db/schema/TableTest.kt @@ -174,10 +174,11 @@ class TableTest { @Test fun testValidationFailsDuplicateIndexColumn() { val columns = listOf(Column("name", ColumnType.TEXT)) - val indexes = listOf( - Index("name_index", listOf(IndexedColumn("name"))), - Index("name_index", listOf(IndexedColumn("name"))) - ) + val indexes = + listOf( + Index("name_index", listOf(IndexedColumn("name"))), + Index("name_index", listOf(IndexedColumn("name"))), + ) val table = Table("users", columns, indexes) val exception = @@ -211,12 +212,13 @@ class TableTest { @Test fun testValidationLocalOnlyWithIncludeOld() { - val table = Table( - "foo", - listOf(Column.Companion.text("bar")), - localOnly = true, - trackPreviousValues = TrackPreviousValuesOptions() - ) + val table = + Table( + "foo", + listOf(Column.Companion.text("bar")), + localOnly = true, + trackPreviousValues = TrackPreviousValuesOptions(), + ) val exception = shouldThrow { table.validate() } exception.message shouldBe "Can't track old values for local-only tables." @@ -239,8 +241,8 @@ class TableTest { Table( "foo", emptyList(), - trackPreviousValues = TrackPreviousValuesOptions(columnFilter = listOf("foo", "bar")) - ) + trackPreviousValues = TrackPreviousValuesOptions(columnFilter = listOf("foo", "bar")), + ), ).let { it["include_old"]!!.jsonArray.map { e -> e.jsonPrimitive.content } shouldBe listOf("foo", "bar") it["include_old_only_when_changed"]!!.jsonPrimitive.boolean shouldBe false diff --git a/common/src/jvmMain/kotlin/BuildConfig.kt b/common/src/jvmMain/kotlin/BuildConfig.kt index 6350a13c..86924a14 100644 --- a/common/src/jvmMain/kotlin/BuildConfig.kt +++ b/common/src/jvmMain/kotlin/BuildConfig.kt @@ -8,5 +8,5 @@ internal actual object BuildConfig { */ actual val isDebug: Boolean = System.getProperty("com.powersync.debug") == "true" || - System.getenv("POWERSYNC_JVM_DEBUG") == "true" + System.getenv("POWERSYNC_JVM_DEBUG") == "true" } diff --git a/common/src/nativeMain/kotlin/com/powersync/sqlite/Database.kt b/common/src/nativeMain/kotlin/com/powersync/sqlite/Database.kt index 27b8c9f4..d489ae9e 100644 --- a/common/src/nativeMain/kotlin/com/powersync/sqlite/Database.kt +++ b/common/src/nativeMain/kotlin/com/powersync/sqlite/Database.kt @@ -55,19 +55,20 @@ public class Database( public fun loadExtension( filename: String, entrypoint: String, - ): Unit = memScoped { - val errorMessagePointer = alloc>() - val resultCode = sqlite3_load_extension(ptr, filename, entrypoint, errorMessagePointer.ptr) - - if (resultCode != 0) { - val errorMessage = errorMessagePointer.value?.toKStringFromUtf8() - if (errorMessage != null) { - sqlite3_free(errorMessagePointer.value) - } + ): Unit = + memScoped { + val errorMessagePointer = alloc>() + val resultCode = sqlite3_load_extension(ptr, filename, entrypoint, errorMessagePointer.ptr) + + if (resultCode != 0) { + val errorMessage = errorMessagePointer.value?.toKStringFromUtf8() + if (errorMessage != null) { + sqlite3_free(errorMessagePointer.value) + } - throw PowerSyncException("Could not load extension ($resultCode): ${errorMessage ?: "unknown error"}", null) + throw PowerSyncException("Could not load extension ($resultCode): ${errorMessage ?: "unknown error"}", null) + } } - } override fun close() { sqlite3_close_v2(ptr) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 4eb75e6f..1193e3bf 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -94,19 +94,6 @@ android { targetCompatibility = JavaVersion.VERSION_17 } - buildFeatures { - buildConfig = true - } - - buildTypes { - release { - buildConfigField("boolean", "DEBUG", "false") - } - debug { - buildConfigField("boolean", "DEBUG", "true") - } - } - namespace = "com.powersync" compileSdk = libs.versions.android.compileSdk diff --git a/core/src/androidMain/kotlin/com/powersync/DatabaseDriverFactory.android.kt b/core/src/androidMain/kotlin/com/powersync/DatabaseDriverFactory.android.kt index 79b19311..dc98b663 100644 --- a/core/src/androidMain/kotlin/com/powersync/DatabaseDriverFactory.android.kt +++ b/core/src/androidMain/kotlin/com/powersync/DatabaseDriverFactory.android.kt @@ -8,7 +8,8 @@ import kotlin.Throws @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") public actual class DatabaseDriverFactory( private val context: Context, -): PersistentConnectionFactory, DriverBasedInMemoryFactory(newDriver()) { +) : DriverBasedInMemoryFactory(newDriver()), + PersistentConnectionFactory { actual override fun resolveDefaultDatabasePath(dbFilename: String): String = context.getDatabasePath(dbFilename).path actual override fun openConnection( diff --git a/core/src/commonIntegrationTest/kotlin/com/powersync/db/InMemoryTest.kt b/core/src/commonIntegrationTest/kotlin/com/powersync/db/InMemoryTest.kt index 4d09c9dc..179e49e2 100644 --- a/core/src/commonIntegrationTest/kotlin/com/powersync/db/InMemoryTest.kt +++ b/core/src/commonIntegrationTest/kotlin/com/powersync/db/InMemoryTest.kt @@ -74,4 +74,4 @@ class InMemoryTest { ), ) } -} \ No newline at end of file +} diff --git a/core/src/commonMain/kotlin/com/powersync/DatabaseDriverFactory.kt b/core/src/commonMain/kotlin/com/powersync/DatabaseDriverFactory.kt index 71196cfc..eef592be 100644 --- a/core/src/commonMain/kotlin/com/powersync/DatabaseDriverFactory.kt +++ b/core/src/commonMain/kotlin/com/powersync/DatabaseDriverFactory.kt @@ -3,10 +3,15 @@ package com.powersync import androidx.sqlite.SQLiteConnection @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") -public expect class DatabaseDriverFactory: PersistentConnectionFactory { +public expect class DatabaseDriverFactory : PersistentConnectionFactory { override fun openInMemoryConnection(): SQLiteConnection + override fun resolveDefaultDatabasePath(dbFilename: String): String - override fun openConnection(path: String, openFlags: Int): SQLiteConnection + + override fun openConnection( + path: String, + openFlags: Int, + ): SQLiteConnection } internal expect val inMemoryDriver: InMemoryConnectionFactory diff --git a/core/src/jvmMain/kotlin/com/powersync/DatabaseDriverFactory.jvm.kt b/core/src/jvmMain/kotlin/com/powersync/DatabaseDriverFactory.jvm.kt index 8999e12b..fd49f989 100644 --- a/core/src/jvmMain/kotlin/com/powersync/DatabaseDriverFactory.jvm.kt +++ b/core/src/jvmMain/kotlin/com/powersync/DatabaseDriverFactory.jvm.kt @@ -4,7 +4,9 @@ import androidx.sqlite.SQLiteConnection import androidx.sqlite.driver.bundled.BundledSQLiteDriver @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", "SqlNoDataSourceInspection") -public actual class DatabaseDriverFactory: PersistentConnectionFactory, DriverBasedInMemoryFactory(newDriver()) { +public actual class DatabaseDriverFactory : + DriverBasedInMemoryFactory(newDriver()), + PersistentConnectionFactory { actual override fun resolveDefaultDatabasePath(dbFilename: String): String = dbFilename actual override fun openConnection( diff --git a/core/src/nativeMain/kotlin/com/powersync/DatabaseDriverFactory.native.kt b/core/src/nativeMain/kotlin/com/powersync/DatabaseDriverFactory.native.kt index 86d6c093..fb2be2cd 100644 --- a/core/src/nativeMain/kotlin/com/powersync/DatabaseDriverFactory.native.kt +++ b/core/src/nativeMain/kotlin/com/powersync/DatabaseDriverFactory.native.kt @@ -4,7 +4,7 @@ import androidx.sqlite.SQLiteConnection import com.powersync.sqlite.Database @Suppress(names = ["EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING"]) -public actual class DatabaseDriverFactory: PersistentConnectionFactory { +public actual class DatabaseDriverFactory : PersistentConnectionFactory { actual override fun resolveDefaultDatabasePath(dbFilename: String): String = appleDefaultDatabasePath(dbFilename) @OptIn(ExperimentalPowerSyncAPI::class) @@ -22,9 +22,7 @@ public actual class DatabaseDriverFactory: PersistentConnectionFactory { return db } - actual override fun openInMemoryConnection(): SQLiteConnection { - return openConnection(":memory:", 0x02) - } + actual override fun openInMemoryConnection(): SQLiteConnection = openConnection(":memory:", 0x02) } internal actual val inMemoryDriver: InMemoryConnectionFactory = DatabaseDriverFactory() diff --git a/integrations/sqldelight/src/commonIntegrationTest/kotlin/com/powersync/integrations/sqldelight/SqlDelightTest.kt b/integrations/sqldelight/src/commonIntegrationTest/kotlin/com/powersync/integrations/sqldelight/SqlDelightTest.kt index 2cdbf85a..b09a1ae0 100644 --- a/integrations/sqldelight/src/commonIntegrationTest/kotlin/com/powersync/integrations/sqldelight/SqlDelightTest.kt +++ b/integrations/sqldelight/src/commonIntegrationTest/kotlin/com/powersync/integrations/sqldelight/SqlDelightTest.kt @@ -5,11 +5,11 @@ import app.cash.sqldelight.async.coroutines.awaitAsOne import app.cash.sqldelight.coroutines.asFlow import app.cash.sqldelight.coroutines.mapToList import app.cash.turbine.turbineScope -import com.powersync.DatabaseDriverFactory import com.powersync.PowerSyncDatabase import com.powersync.db.schema.Column import com.powersync.db.schema.Schema import com.powersync.db.schema.Table +import com.powersync.inMemory import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.properties.shouldHaveValue @@ -17,7 +17,6 @@ import io.kotest.matchers.shouldBe import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest -import kotlinx.io.files.SystemTemporaryDirectory import kotlin.test.Test class SqlDelightTest { diff --git a/internal/testutils/src/platformMain/kotlin/com/powersync/test/TestPlatform.kt b/internal/testutils/src/platformMain/kotlin/com/powersync/test/TestPlatform.kt index 9e4b6830..ae53fa33 100644 --- a/internal/testutils/src/platformMain/kotlin/com/powersync/test/TestPlatform.kt +++ b/internal/testutils/src/platformMain/kotlin/com/powersync/test/TestPlatform.kt @@ -14,6 +14,4 @@ fun cleanup(path: String) { } } -fun getTempDir(): String { - return SystemTemporaryDirectory.toString() -} +fun getTempDir(): String = SystemTemporaryDirectory.toString() From f5cd4c772a2b83ab6ea683635c1f2450b0053249 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 20 Oct 2025 09:34:05 +0200 Subject: [PATCH 7/8] Fix supabase test --- .../com/powersync/connector/supabase/SupabaseConnectorTest.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/integrations/supabase/src/commonIntegrationTest/kotlin/com/powersync/connector/supabase/SupabaseConnectorTest.kt b/integrations/supabase/src/commonIntegrationTest/kotlin/com/powersync/connector/supabase/SupabaseConnectorTest.kt index 14728a6b..711c17c9 100644 --- a/integrations/supabase/src/commonIntegrationTest/kotlin/com/powersync/connector/supabase/SupabaseConnectorTest.kt +++ b/integrations/supabase/src/commonIntegrationTest/kotlin/com/powersync/connector/supabase/SupabaseConnectorTest.kt @@ -6,6 +6,7 @@ import com.powersync.db.crud.CrudTransaction import com.powersync.db.schema.Column import com.powersync.db.schema.Schema import com.powersync.db.schema.Table +import com.powersync.inMemory import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.equals.shouldBeEqual import io.kotest.matchers.shouldBe From 23cb1d9d8caeb4a53ca1c658b5dfdf1d1c3ebf9a Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 20 Oct 2025 11:05:11 +0200 Subject: [PATCH 8/8] Fix watchos tests --- .../kotlin/com/powersync/PowerSyncDatabase.kt | 29 +++++++++++++++++- .../com/powersync/db/ActiveInstanceStore.kt | 30 ++++++++----------- .../kotlin/com/powersync/PowerSyncDatabase.kt | 22 ++++---------- .../powersync/DatabaseDriverFactory.native.kt | 17 +++++++---- 4 files changed, 58 insertions(+), 40 deletions(-) diff --git a/common/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt b/common/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt index 30d45980..ea96cbeb 100644 --- a/common/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt +++ b/common/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt @@ -247,8 +247,35 @@ public interface PowerSyncDatabase : Queries { return openedWithGroup(pool, scope, schema, logger, group) } + /** + * Creates a PowerSync database backed by a single in-memory database connection opened from the + * [InMemoryConnectionFactory]. + * + * This can be useful for writing tests relying on PowerSync databases. + */ + @ExperimentalPowerSyncAPI + public fun openInMemory( + factory: InMemoryConnectionFactory, + schema: Schema, + scope: CoroutineScope, + logger: Logger? = null, + ): PowerSyncDatabase { + val logger = generateLogger(logger) + // Since this returns a fresh in-memory database every time, use a fresh group to avoid warnings about the + // same database being opened multiple times. + val collection = ActiveDatabaseGroup.GroupsCollection().referenceDatabase(logger, "test") + + return openedWithGroup( + SingleConnectionPool(factory.openInMemoryConnection()), + scope, + schema, + logger, + collection, + ) + } + @ExperimentalPowerSyncAPI - public fun openedWithGroup( + internal fun openedWithGroup( pool: SQLiteConnectionPool, scope: CoroutineScope, schema: Schema, diff --git a/common/src/commonMain/kotlin/com/powersync/db/ActiveInstanceStore.kt b/common/src/commonMain/kotlin/com/powersync/db/ActiveInstanceStore.kt index e6d011d0..1ba3faed 100644 --- a/common/src/commonMain/kotlin/com/powersync/db/ActiveInstanceStore.kt +++ b/common/src/commonMain/kotlin/com/powersync/db/ActiveInstanceStore.kt @@ -12,7 +12,7 @@ import kotlinx.coroutines.sync.Mutex internal expect fun disposeWhenDeallocated(resource: ActiveDatabaseResource): Any /** - * A collection of PowerSync databases with the same path / identifier. + * An collection of PowerSync databases with the same path / identifier. * * We expect that each group will only ever have one database because we encourage users to write their databases as * singletons. We print a warning when two databases are part of the same group. @@ -20,15 +20,15 @@ internal expect fun disposeWhenDeallocated(resource: ActiveDatabaseResource): An * duplicate resources being used. For this reason, each active database group has a coroutine mutex guarding the * sync job. */ -public class ActiveDatabaseGroup internal constructor( - internal val identifier: String, +internal class ActiveDatabaseGroup( + val identifier: String, private val collection: GroupsCollection, ) { internal var refCount = 0 // Guarded by companion object internal val syncMutex = Mutex() internal val writeLockMutex = Mutex() - internal fun removeUsage() { + fun removeUsage() { collection.synchronize { if (--refCount == 0) { collection.allGroups.remove(this) @@ -36,13 +36,7 @@ public class ActiveDatabaseGroup internal constructor( } } - /** - * A collection of [ActiveDatabaseGroup]s. - * - * Typically, one uses the singleton instance that is the companion object of that class, but separate groups can be - * used for testing. - */ - public open class GroupsCollection : Synchronizable() { + internal open class GroupsCollection : Synchronizable() { internal val allGroups = mutableListOf() private fun findGroup( @@ -67,7 +61,7 @@ public class ActiveDatabaseGroup internal constructor( resolvedGroup } - public fun referenceDatabase( + internal fun referenceDatabase( warnOnDuplicate: Logger, identifier: String, ): Pair { @@ -78,7 +72,7 @@ public class ActiveDatabaseGroup internal constructor( } } - public companion object : GroupsCollection() { + companion object : GroupsCollection() { internal val multipleInstancesMessage = """ Multiple PowerSync instances for the same database have been detected. @@ -88,13 +82,13 @@ public class ActiveDatabaseGroup internal constructor( } } -public class ActiveDatabaseResource( - internal val group: ActiveDatabaseGroup, +internal class ActiveDatabaseResource( + val group: ActiveDatabaseGroup, ) { - internal val disposed = AtomicBoolean(false) + val disposed = AtomicBoolean(false) - public fun dispose() { - if (disposed.compareAndSet(expected = false, new = true)) { + fun dispose() { + if (disposed.compareAndSet(false, true)) { group.removeUsage() } } diff --git a/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt b/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt index 34f19203..e460d6f1 100644 --- a/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt +++ b/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt @@ -1,10 +1,7 @@ package com.powersync import co.touchlab.kermit.Logger -import com.powersync.db.ActiveDatabaseGroup -import com.powersync.db.driver.SingleConnectionPool import com.powersync.db.schema.Schema -import com.powersync.utils.generateLogger import kotlinx.coroutines.CoroutineScope /** @@ -15,17 +12,10 @@ public fun PowerSyncDatabase.PowerSyncOpenFactory.inMemory( schema: Schema, scope: CoroutineScope, logger: Logger? = null, -): PowerSyncDatabase { - val logger = generateLogger(logger) - // Since this returns a fresh in-memory database every time, use a fresh group to avoid warnings about the - // same database being opened multiple times. - val collection = ActiveDatabaseGroup.GroupsCollection().referenceDatabase(logger, "test") - - return openedWithGroup( - SingleConnectionPool(inMemoryDriver.openInMemoryConnection()), - scope, - schema, - logger, - collection, +): PowerSyncDatabase = + openInMemory( + factory = inMemoryDriver, + schema = schema, + scope = scope, + logger = logger, ) -} diff --git a/core/src/nativeMain/kotlin/com/powersync/DatabaseDriverFactory.native.kt b/core/src/nativeMain/kotlin/com/powersync/DatabaseDriverFactory.native.kt index fb2be2cd..bc34bfd5 100644 --- a/core/src/nativeMain/kotlin/com/powersync/DatabaseDriverFactory.native.kt +++ b/core/src/nativeMain/kotlin/com/powersync/DatabaseDriverFactory.native.kt @@ -12,13 +12,20 @@ public actual class DatabaseDriverFactory : PersistentConnectionFactory { 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) - try { - db.loadExtension(resolvePowerSyncLoadableExtensionPath()!!, "sqlite3_powersync_init") - } catch (e: PowerSyncException) { - db.close() - throw e + extensionPath?.let { path -> + try { + db.loadExtension(path, "sqlite3_powersync_init") + } catch (e: PowerSyncException) { + db.close() + throw e + } } + return db }