Skip to content
Merged
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
39 changes: 31 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
# Changelog

## 1.10.0 (unreleased)

- Add `appMetadata` parameter to `PowerSyncDatabase.connect()` to include application metadata in
sync requests. This metadata is merged into sync requests and displayed in PowerSync service logs.

Note: This requires a PowerSync service version `>=1.17.0` in order for logs to display metadata.

```kotlin
database.connect(
connector = connector,
appMetadata = mapOf(
"appVersion" to "1.0.0",
"deviceId" to "device456"
)
)
```

## 1.9.0

- Updated user agent string formats to allow viewing version distributions in the new PowerSync dashboard.
- Updated user agent string formats to allow viewing version distributions in the new PowerSync
dashboard.
- Sync options: `newClientImplementation` is now the default.
- Make `androidx.sqlite:sqlite-bundled` an API dependency of `:core` to avoid toolchain warnings.
- On Apple platforms, use a websocket protocol as a workaround to clients not supporting backpressure in HTTP response
- On Apple platforms, use a websocket protocol as a workaround to clients not supporting
backpressure in HTTP response
streams.

## 1.8.1
Expand All @@ -14,15 +33,19 @@

## 1.8.0

- Refactor SDK: `com.powersync:powersync-core` has an identical API, but now depends on
- Refactor SDK: `com.powersync:powersync-core` has an identical API, but now depends on
`com.powersync:powersync-common` where most logic is implemented.
- __POTENTIALLY BREAKING CHANGE__: If you were injecting a `DatabaseDriverFactory` into Koin or Dagger, note that the
`PowerSyncDatabase()` factory method now takes a more generic `PersistentConnectionFactory`.
- If you're using `PowerSyncDatabase.inMemory`, you explicitly have to import `com.powersync.inMemory` now.
- __POTENTIALLY BREAKING CHANGE__: If you were injecting a `DatabaseDriverFactory` into Koin or
Dagger, note that the
`PowerSyncDatabase()` factory method now takes a more generic `PersistentConnectionFactory`.
- If you're using `PowerSyncDatabase.inMemory`, you explicitly have to import
`com.powersync.inMemory` now.
- Update the PowerSync core extension to version 0.4.8.
- Add the `soft` flag to `disconnectAndClear()` which keeps an internal copy of synced data in the database, allowing
- Add the `soft` flag to `disconnectAndClear()` which keeps an internal copy of synced data in the
database, allowing
faster re-sync if a compatible token is used in the next `connect()` call.
- Add the `clear` parameter to `RawTable` to run a statement helping the core extension clear raw tables.
- Add the `clear` parameter to `RawTable` to run a statement helping the core extension clear raw
tables.

## 1.7.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,33 @@ abstract class BaseSyncIntegrationTest(
}
}

@Test
fun useAppMetadata() =
databaseTest {
database.connect(
connector,
options = getOptions(),
appMetadata =
mapOf(
"app_version" to "1.0.0",
),
)
turbineScope(timeout = 10.0.seconds) {
val turbine = database.currentStatus.asFlow().testIn(this)
turbine.waitFor { it.connected }
turbine.cancel()
}

requestedSyncStreams shouldHaveSingleElement {
val meta = it.jsonObject["app_metadata"]!!.jsonObject
meta.keys shouldHaveSingleElement "app_version"
meta.values
.first()
.jsonPrimitive.content shouldBe "1.0.0"
true
}
}

@Test
@OptIn(DelicateCoroutinesApi::class)
fun closesResponseStreamOnDatabaseClose() =
Expand Down
13 changes: 11 additions & 2 deletions common/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public interface PowerSyncDatabase : Queries {
* 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.
* Use @param [appMetadata] to specify application metadata that will be displayed in PowerSync service logs.
*
* Example usage:
* ```
Expand All @@ -91,11 +92,17 @@ public interface PowerSyncDatabase : Queries {
* )
* )
*
* val appMetadata = mapOf(
* "appVersion" to "1.0.0",
* "deviceId" to "device456"
* )
*
* connect(
* connector = connector,
* crudThrottleMs = 2000L,
* retryDelayMs = 10000L,
* params = params
* params = params,
* appMetadata = appMetadata
* )
* ```
*/
Expand All @@ -106,6 +113,7 @@ public interface PowerSyncDatabase : Queries {
retryDelayMs: Long = 5000L,
params: Map<String, JsonParam?> = emptyMap(),
options: SyncOptions = SyncOptions(),
appMetadata: Map<String, String> = emptyMap(),
)

/**
Expand Down Expand Up @@ -272,7 +280,8 @@ public interface PowerSyncDatabase : Queries {
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")
val collection =
ActiveDatabaseGroup.GroupsCollection().referenceDatabase(logger, "test")

return openedWithGroup(
SingleConnectionPool(factory.openInMemoryConnection()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ internal sealed interface PowerSyncControlArguments {
val includeDefaults: Boolean,
@SerialName("active_streams")
val activeStreams: List<StreamKey>,
@SerialName("app_metadata")
val appMetadata: Map<String, String>,
) : PowerSyncControlArguments {
override val sqlArguments: Pair<String, Any?>
get() = "start" to JsonUtil.json.encodeToString(this)
Expand Down Expand Up @@ -109,7 +111,8 @@ internal sealed interface PowerSyncControlArguments {
class UpdateSubscriptions(
activeStreams: List<StreamKey>,
) : PowerSyncControlArguments {
override val sqlArguments: Pair<String, Any?> = "update_subscriptions" to JsonUtil.json.encodeToString(activeStreams)
override val sqlArguments: Pair<String, Any?> =
"update_subscriptions" to JsonUtil.json.encodeToString(activeStreams)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ internal class PowerSyncDatabaseImpl(
retryDelayMs: Long,
params: Map<String, JsonParam?>,
options: SyncOptions,
appMetadata: Map<String, String>,
) {
waitReady()
mutex.withLock {
Expand All @@ -159,6 +160,7 @@ internal class PowerSyncDatabaseImpl(
options = options,
schema = schema,
activeSubscriptions = streams.currentlyReferencedStreams,
appMetadata = appMetadata,
)
}
}
Expand Down
20 changes: 17 additions & 3 deletions common/src/commonMain/kotlin/com/powersync/sync/StreamingSync.kt
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import kotlinx.io.readByteArray
import kotlinx.io.readIntLe
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.encodeToJsonElement
import kotlin.time.Clock

Expand All @@ -74,6 +75,7 @@ internal class StreamingSyncClient(
private val options: SyncOptions,
private val schema: Schema,
private val activeSubscriptions: StateFlow<List<SubscriptionGroup>>,
private val appMetadata: Map<String, String> = emptyMap(),
) {
private var isUploadingCrud = AtomicReference<PendingCrudUpload?>(null)
private var completedCrudUploads = Channel<Unit>(onBufferOverflow = BufferOverflow.DROP_OLDEST)
Expand All @@ -92,6 +94,7 @@ internal class StreamingSyncClient(
configureSyncHttpClient(options.userAgent)
config.block(this)
}

is SyncClientConfiguration.ExistingClient -> config.client
}

Expand Down Expand Up @@ -127,7 +130,13 @@ internal class StreamingSyncClient(
status.update { copy(downloadError = e) }
} finally {
if (!result.hideDisconnectStateAndReconnectImmediately) {
status.update { copy(connected = false, connecting = true, downloading = false) }
status.update {
copy(
connected = false,
connecting = true,
downloading = false,
)
}
delay(retryDelayMs)
}
}
Expand Down Expand Up @@ -297,7 +306,8 @@ internal class StreamingSyncClient(
} else {
// Use RSocket as a fallback to ensure we have backpressure on platforms that don't support it natively.
flow {
val credentials = requireNotNull(connector.getCredentialsCached()) { "Not logged in" }
val credentials =
requireNotNull(connector.getCredentialsCached()) { "Not logged in" }

emitAll(
httpClient.rSocketSyncStream(
Expand Down Expand Up @@ -367,6 +377,7 @@ internal class StreamingSyncClient(
schema = schema.toSerializable(),
includeDefaults = options.includeDefaultStreams,
activeStreams = subscriptions.map { it.key },
appMetadata = appMetadata,
),
)

Expand All @@ -375,7 +386,9 @@ internal class StreamingSyncClient(
activeSubscriptions.collect {
if (subscriptions !== it) {
subscriptions = it
controlInvocations.send(PowerSyncControlArguments.UpdateSubscriptions(activeSubscriptions.value.map { it.key }))
controlInvocations.send(
PowerSyncControlArguments.UpdateSubscriptions(activeSubscriptions.value.map { it.key }),
)
}
}
}
Expand Down Expand Up @@ -536,6 +549,7 @@ internal class StreamingSyncClient(
},
clientId = clientId!!,
parameters = params,
appMetadata = appMetadata,
)

lateinit var receiveLines: Job
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ internal data class StreamingSyncRequest(
@SerialName("include_checksum") val includeChecksum: Boolean = true,
@SerialName("client_id") val clientId: String,
val parameters: JsonObject = JsonObject(mapOf()),
@SerialName("app_metadata") val appMetadata: Map<String, String> = emptyMap(),
) {
@SerialName("raw_data")
private val rawData: Boolean = true
Expand Down
3 changes: 3 additions & 0 deletions demos/supabase-todolist/shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -123,5 +123,8 @@ buildkonfig {
stringConfigField("POWERSYNC_URL")
stringConfigField("SUPABASE_URL")
stringConfigField("SUPABASE_ANON_KEY")

// App version from Gradle project version
buildConfigField(STRING, "APP_VERSION", "\"${project.version}\"")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ internal class AuthViewModel(
}
},
),
appMetadata = mapOf(
"appVersion" to Config.APP_VERSION
),
)
}

Expand Down