Skip to content

[WIP] HTTP Client Config #229

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions PowerSyncKotlin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ kotlin {
sourceSets {
commonMain.dependencies {
api(project(":core"))
implementation(libs.ktor.client.logging)
}
}
}
Expand Down Expand Up @@ -78,8 +79,16 @@ listOf("Debug", "Release").forEach { buildType ->
val originalFramework = tasks.getByName("assemblePowerSyncKotlin${buildType}XCFramework")
dependsOn(originalFramework)

val source = project.layout.buildDirectory.map { it.dir("XCFrameworks/${buildType.lowercase()}") }.get().asFile
val archiveFile = project.layout.buildDirectory.map { it.file("FrameworkArchives/PowersyncKotlin$buildType.zip") }.get().asFile
val source =
project.layout.buildDirectory
.map { it.dir("XCFrameworks/${buildType.lowercase()}") }
.get()
.asFile
val archiveFile =
project.layout.buildDirectory
.map { it.file("FrameworkArchives/PowersyncKotlin$buildType.zip") }
.get()
.asFile

archiveFile.parentFile.mkdirs()
archiveFile.delete()
Expand Down
60 changes: 59 additions & 1 deletion PowerSyncKotlin/src/appleMain/kotlin/com/powersync/SDK.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

package com.powersync

import com.powersync.sync.SyncClientConfiguration
import com.powersync.sync.SyncOptions
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logging
import io.ktor.client.plugins.logging.Logger as KtorLogger

/**
* Helper class designed to bridge SKIEE methods and allow them to throw
Expand All @@ -17,16 +21,70 @@ import com.powersync.sync.SyncOptions
public fun throwPowerSyncException(exception: PowerSyncException): Unit = throw exception

/**
* Creates a [ConnectionMethod] based on simple booleans, because creating the actual instance with
* A small wrapper around the Ktor LogLevel enum to allow
* specifying the log level from Swift without exposing the Ktor plugin types.
*/
public enum class SwiftNetworkLogLevel {
ALL,
HEADERS,
BODY,
INFO,
NONE,
}

/**
* Mapper function to Ktor LogLevel
*/
internal fun SwiftNetworkLogLevel.toKtorLogLevel(): LogLevel =
when (this) {
SwiftNetworkLogLevel.ALL -> LogLevel.ALL
SwiftNetworkLogLevel.HEADERS -> LogLevel.HEADERS
SwiftNetworkLogLevel.BODY -> LogLevel.BODY
SwiftNetworkLogLevel.INFO -> LogLevel.INFO
SwiftNetworkLogLevel.NONE -> LogLevel.NONE
}

/**
* Configuration which is used to configure the Ktor logging plugin
*/
public data class SwiftNetworkLoggerConfig(
public val logLevel: SwiftNetworkLogLevel,
public val log: (message: String) -> Unit,
)

/**
* Creates a Ktor [SyncClientConfiguration.ExtendedConfig] that extends the default Ktor client.
* Specifying a [SwiftNetworkLoggerConfig] will install the Ktor logging plugin with the specified configuration.
*/
public fun createExtendedSyncClientConfiguration(loggingConfig: SwiftNetworkLoggerConfig? = null): SyncClientConfiguration =
SyncClientConfiguration.ExtendedConfig {
if (loggingConfig != null) {
install(Logging) {
// Pass everything to the provided logger. The logger controls the active level
level = loggingConfig.logLevel.toKtorLogLevel()
logger =
object : KtorLogger {
override fun log(message: String) {
loggingConfig.log(message)
}
}
}
}
}

/**
* Creates a [SyncOptions] based on simple parameters, because creating the actual instance with
* the default constructor is not possible from Swift due to an optional argument with an internal
* default value.
*/
@OptIn(ExperimentalPowerSyncAPI::class)
public fun createSyncOptions(
newClient: Boolean,
userAgent: String,
loggingConfig: SwiftNetworkLoggerConfig? = null,
): SyncOptions =
SyncOptions(
newClientImplementation = newClient,
userAgent = userAgent,
clientConfiguration = createExtendedSyncClientConfiguration(loggingConfig),
)
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.powersync.sync

import com.powersync.ExperimentalPowerSyncAPI
import com.powersync.testutils.ActiveDatabaseTest

/**
* Small utility to run tests both with the legacy Kotlin sync implementation and the new
Expand All @@ -11,7 +12,9 @@ abstract class AbstractSyncTest(
protected val useBson: Boolean = false,
) {
@OptIn(ExperimentalPowerSyncAPI::class)
val options: SyncOptions get() {
return SyncOptions(useNewSyncImplementation)
}
internal fun ActiveDatabaseTest.getOptions(): SyncOptions =
SyncOptions(
useNewSyncImplementation,
clientConfiguration = SyncClientConfiguration.ExistingClient(createSyncClient()),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ abstract class BaseSyncIntegrationTest(
databaseTest(createInitialDatabase = false) {
// Regression test for https://github.com/powersync-ja/powersync-kotlin/issues/169
val database = openDatabase()
database.connect(connector, options = options)
database.connect(connector, options = getOptions())

turbineScope(timeout = 10.0.seconds) {
val turbine = database.currentStatus.asFlow().testIn(this)
Expand All @@ -72,7 +72,11 @@ abstract class BaseSyncIntegrationTest(
@Test
fun useParameters() =
databaseTest {
database.connect(connector, options = options, params = mapOf("foo" to JsonParam.String("bar")))
database.connect(
connector,
options = getOptions(),
params = mapOf("foo" to JsonParam.String("bar")),
)
turbineScope(timeout = 10.0.seconds) {
val turbine = database.currentStatus.asFlow().testIn(this)
turbine.waitFor { it.connected }
Expand All @@ -93,7 +97,7 @@ abstract class BaseSyncIntegrationTest(
@OptIn(DelicateCoroutinesApi::class)
fun closesResponseStreamOnDatabaseClose() =
databaseTest {
database.connect(connector, options = options)
database.connect(connector, options = getOptions())

turbineScope(timeout = 10.0.seconds) {
val turbine = database.currentStatus.asFlow().testIn(this)
Expand All @@ -112,7 +116,7 @@ abstract class BaseSyncIntegrationTest(
@OptIn(DelicateCoroutinesApi::class)
fun cleansResourcesOnDisconnect() =
databaseTest {
database.connect(connector, options = options)
database.connect(connector, options = getOptions())

turbineScope(timeout = 10.0.seconds) {
val turbine = database.currentStatus.asFlow().testIn(this)
Expand All @@ -134,7 +138,7 @@ abstract class BaseSyncIntegrationTest(
@Test
fun cannotUpdateSchemaWhileConnected() =
databaseTest {
database.connect(connector, options = options)
database.connect(connector, options = getOptions())

turbineScope(timeout = 10.0.seconds) {
val turbine = database.currentStatus.asFlow().testIn(this)
Expand All @@ -152,7 +156,7 @@ abstract class BaseSyncIntegrationTest(
@Test
fun testPartialSync() =
databaseTest {
database.connect(connector, options = options)
database.connect(connector, options = getOptions())

val checksums =
buildList {
Expand Down Expand Up @@ -243,7 +247,7 @@ abstract class BaseSyncIntegrationTest(
@Test
fun testRemembersLastPartialSync() =
databaseTest {
database.connect(connector, options = options)
database.connect(connector, options = getOptions())

syncLines.send(
SyncLine.FullCheckpoint(
Expand Down Expand Up @@ -279,7 +283,7 @@ abstract class BaseSyncIntegrationTest(
@Test
fun setsDownloadingState() =
databaseTest {
database.connect(connector, options = options)
database.connect(connector, options = getOptions())

turbineScope(timeout = 10.0.seconds) {
val turbine = database.currentStatus.asFlow().testIn(this)
Expand Down Expand Up @@ -313,7 +317,7 @@ abstract class BaseSyncIntegrationTest(
turbineScope(timeout = 10.0.seconds) {
val turbine = database.currentStatus.asFlow().testIn(this)

database.connect(connector, options = options)
database.connect(connector, options = getOptions())
turbine.waitFor { it.connecting }

database.disconnect()
Expand All @@ -326,7 +330,7 @@ abstract class BaseSyncIntegrationTest(
@Test
fun testMultipleSyncsDoNotCreateMultipleStatusEntries() =
databaseTest {
database.connect(connector, options = options)
database.connect(connector, options = getOptions())

turbineScope(timeout = 10.0.seconds) {
val turbine = database.currentStatus.asFlow().testIn(this)
Expand Down Expand Up @@ -372,8 +376,8 @@ abstract class BaseSyncIntegrationTest(

turbineScope(timeout = 10.0.seconds) {
// Connect the first database
database.connect(connector, options = options)
db2.connect(connector, options = options)
database.connect(connector, options = getOptions())
db2.connect(connector, options = getOptions())

waitFor {
assertNotNull(
Expand All @@ -398,10 +402,10 @@ abstract class BaseSyncIntegrationTest(
val turbine2 = db2.currentStatus.asFlow().testIn(this)

// Connect the first database
database.connect(connector, options = options)
database.connect(connector, options = getOptions())

turbine1.waitFor { it.connecting }
db2.connect(connector, options = options)
db2.connect(connector, options = getOptions())

// Should not be connecting yet
db2.currentStatus.connecting shouldBe false
Expand All @@ -425,13 +429,13 @@ abstract class BaseSyncIntegrationTest(
turbineScope(timeout = 10.0.seconds) {
val turbine = database.currentStatus.asFlow().testIn(this)

database.connect(connector, 1000L, options = options)
database.connect(connector, 1000L, options = getOptions())
turbine.waitFor { it.connecting }

database.disconnect()
turbine.waitFor { !it.connecting }

database.connect(connector, 1000L, options = options)
database.connect(connector, 1000L, options = getOptions())
turbine.waitFor { it.connecting }
database.disconnect()
turbine.waitFor { !it.connecting }
Expand All @@ -446,10 +450,10 @@ abstract class BaseSyncIntegrationTest(
turbineScope(timeout = 10.0.seconds) {
val turbine = database.currentStatus.asFlow().testIn(this)

database.connect(connector, 1000L, retryDelayMs = 5000, options = options)
database.connect(connector, 1000L, retryDelayMs = 5000, options = getOptions())
turbine.waitFor { it.connecting }

database.connect(connector, 1000L, retryDelayMs = 5000, options = options)
database.connect(connector, 1000L, retryDelayMs = 5000, options = getOptions())
turbine.waitFor { it.connecting }

turbine.cancelAndIgnoreRemainingEvents()
Expand All @@ -462,7 +466,7 @@ abstract class BaseSyncIntegrationTest(
databaseTest {
val testConnector = TestConnector()
connector = testConnector
database.connect(testConnector, options = options)
database.connect(testConnector, options = getOptions())

suspend fun expectUserRows(amount: Int) {
val row = database.get("SELECT COUNT(*) FROM users") { it.getLong(0)!! }
Expand Down Expand Up @@ -500,7 +504,10 @@ abstract class BaseSyncIntegrationTest(
}
}

database.execute("INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)", listOf("local", "[email protected]"))
database.execute(
"INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)",
listOf("local", "[email protected]"),
)

expectUserRows(1)
uploadStarted.await()
Expand Down Expand Up @@ -591,14 +598,18 @@ abstract class BaseSyncIntegrationTest(
WriteCheckpointResponse(WriteCheckpointData("1"))
}

database.execute("INSERT INTO users (id, name) VALUES (uuid(), ?)", listOf("local write"))
database.connect(connector, options = options)
database.execute(
"INSERT INTO users (id, name) VALUES (uuid(), ?)",
listOf("local write"),
)
database.connect(connector, options = getOptions())

turbineScope(timeout = 10.0.seconds) {
val turbine = database.currentStatus.asFlow().testIn(scope)
turbine.waitFor { it.connected }

val query = database.watch("SELECT name FROM users") { it.getString(0)!! }.testIn(scope)
val query =
database.watch("SELECT name FROM users") { it.getString(0)!! }.testIn(scope)
query.awaitItem() shouldBe listOf("local write")

syncLines.send(SyncLine.KeepAlive(tokenExpiresIn = 1234))
Expand Down Expand Up @@ -652,7 +663,7 @@ abstract class BaseSyncIntegrationTest(
turbineScope(timeout = 10.0.seconds) {
val turbine = database.currentStatus.asFlow().testIn(this)

database.connect(connector, 1000L, retryDelayMs = 5000, options = options)
database.connect(connector, 1000L, retryDelayMs = 5000, options = getOptions())
turbine.waitFor { it.connecting }

syncLines.send(SyncLine.KeepAlive(tokenExpiresIn = 4000))
Expand Down Expand Up @@ -692,7 +703,7 @@ abstract class BaseSyncIntegrationTest(
turbineScope(timeout = 10.0.seconds) {
val turbine = database.currentStatus.asFlow().testIn(this)

database.connect(connector, 1000L, retryDelayMs = 5000, options = options)
database.connect(connector, 1000L, retryDelayMs = 5000, options = getOptions())
turbine.waitFor { it.downloadError != null }

database.currentStatus.downloadError?.toString() shouldContain "Expected exception from fetchCredentials"
Expand Down Expand Up @@ -736,7 +747,7 @@ class NewSyncIntegrationTest : BaseSyncIntegrationTest(true) {
turbineScope(timeout = 10.0.seconds) {
val turbine = database.currentStatus.asFlow().testIn(this)

database.connect(connector, 1000L, retryDelayMs = 5000, options = options)
database.connect(connector, 1000L, retryDelayMs = 5000, options = getOptions())
turbine.waitFor { it.connecting }

syncLines.send(SyncLine.KeepAlive(tokenExpiresIn = 4000))
Expand Down Expand Up @@ -771,7 +782,10 @@ class NewSyncIntegrationTest : BaseSyncIntegrationTest(true) {
put =
PendingStatement(
"INSERT OR REPLACE INTO lists (id, name) VALUES (?, ?)",
listOf(PendingStatementParameter.Id, PendingStatementParameter.Column("name")),
listOf(
PendingStatementParameter.Id,
PendingStatementParameter.Column("name"),
),
),
delete =
PendingStatement(
Expand All @@ -792,7 +806,7 @@ class NewSyncIntegrationTest : BaseSyncIntegrationTest(true) {
}.testIn(this)
query.awaitItem() shouldBe emptyList()

db.connect(connector, options = options)
db.connect(connector, options = getOptions())
syncLines.send(
SyncLine.FullCheckpoint(
Checkpoint(
Expand Down Expand Up @@ -877,7 +891,7 @@ class NewSyncIntegrationTest : BaseSyncIntegrationTest(true) {
}.testIn(this)
query.awaitItem() shouldBe emptyList()

database.connect(connector, options = options)
database.connect(connector, options = getOptions())

// {checkpoint: {last_op_id: 1, write_checkpoint: null, buckets: [{bucket: a, checksum: 0, priority: 3, count: null}]}}
syncLines.send(
Expand Down
Loading
Loading