Skip to content

Commit ec9e222

Browse files
feat: add appMetadata for service logs
1 parent 1a6ee24 commit ec9e222

File tree

6 files changed

+85
-6
lines changed

6 files changed

+85
-6
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@
44

55
- Sync options: `newClientImplementation` is now the default.
66
- Make `androidx.sqlite:sqlite-bundled` an API dependency of `:core` to avoid toolchain warnings.
7+
- 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.
8+
9+
```kotlin
10+
database.connect(
11+
connector = connector,
12+
appMetadata = mapOf(
13+
"appVersion" to "1.0.0",
14+
"deviceId" to "device456"
15+
)
16+
)
17+
```
718

819
## 1.8.1
920

common/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ public interface PowerSyncDatabase : Queries {
8080
* Use @param [crudThrottleMs] to specify the time between CRUD operations. Defaults to 1000ms.
8181
* Use @param [retryDelayMs] to specify the delay between retries after failure. Defaults to 5000ms.
8282
* Use @param [params] to specify sync parameters from the client.
83+
* Use @param [appMetadata] to specify application metadata that will be displayed in PowerSync service logs.
8384
*
8485
* Example usage:
8586
* ```
@@ -91,11 +92,17 @@ public interface PowerSyncDatabase : Queries {
9192
* )
9293
* )
9394
*
95+
* val appMetadata = mapOf(
96+
* "appVersion" to "1.0.0",
97+
* "deviceId" to "device456"
98+
* )
99+
*
94100
* connect(
95101
* connector = connector,
96102
* crudThrottleMs = 2000L,
97103
* retryDelayMs = 10000L,
98-
* params = params
104+
* params = params,
105+
* appMetadata = appMetadata
99106
* )
100107
* ```
101108
*/
@@ -106,6 +113,7 @@ public interface PowerSyncDatabase : Queries {
106113
retryDelayMs: Long = 5000L,
107114
params: Map<String, JsonParam?> = emptyMap(),
108115
options: SyncOptions = SyncOptions(),
116+
appMetadata: Map<String, String> = emptyMap(),
109117
)
110118

111119
/**
@@ -272,7 +280,8 @@ public interface PowerSyncDatabase : Queries {
272280
val logger = generateLogger(logger)
273281
// Since this returns a fresh in-memory database every time, use a fresh group to avoid warnings about the
274282
// same database being opened multiple times.
275-
val collection = ActiveDatabaseGroup.GroupsCollection().referenceDatabase(logger, "test")
283+
val collection =
284+
ActiveDatabaseGroup.GroupsCollection().referenceDatabase(logger, "test")
276285

277286
return openedWithGroup(
278287
SingleConnectionPool(factory.openInMemoryConnection()),

common/src/commonMain/kotlin/com/powersync/bucket/BucketStorage.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ internal sealed interface PowerSyncControlArguments {
7171
val includeDefaults: Boolean,
7272
@SerialName("active_streams")
7373
val activeStreams: List<StreamKey>,
74+
@SerialName("app_metadata")
75+
val appMetadata: Map<String, String>,
7476
) : PowerSyncControlArguments {
7577
override val sqlArguments: Pair<String, Any?>
7678
get() = "start" to JsonUtil.json.encodeToString(this)
@@ -109,7 +111,8 @@ internal sealed interface PowerSyncControlArguments {
109111
class UpdateSubscriptions(
110112
activeStreams: List<StreamKey>,
111113
) : PowerSyncControlArguments {
112-
override val sqlArguments: Pair<String, Any?> = "update_subscriptions" to JsonUtil.json.encodeToString(activeStreams)
114+
override val sqlArguments: Pair<String, Any?> =
115+
"update_subscriptions" to JsonUtil.json.encodeToString(activeStreams)
113116
}
114117
}
115118

common/src/commonMain/kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ internal class PowerSyncDatabaseImpl(
142142
retryDelayMs: Long,
143143
params: Map<String, JsonParam?>,
144144
options: SyncOptions,
145+
appMetadata: Map<String, String>,
145146
) {
146147
waitReady()
147148
mutex.withLock {

common/src/commonMain/kotlin/com/powersync/sync/StreamingSync.kt

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ import kotlinx.io.readByteArray
6363
import kotlinx.io.readIntLe
6464
import kotlinx.serialization.json.JsonElement
6565
import kotlinx.serialization.json.JsonObject
66+
import kotlinx.serialization.json.JsonPrimitive
6667
import kotlinx.serialization.json.encodeToJsonElement
6768
import kotlin.experimental.ExperimentalObjCRefinement
6869
import kotlin.native.HiddenFromObjC
@@ -115,6 +116,7 @@ internal class StreamingSyncClient(
115116
private val options: SyncOptions,
116117
private val schema: Schema,
117118
private val activeSubscriptions: StateFlow<List<SubscriptionGroup>>,
119+
private val appMetadata: Map<String, String>,
118120
) {
119121
private var isUploadingCrud = AtomicReference<PendingCrudUpload?>(null)
120122
private var completedCrudUploads = Channel<Unit>(onBufferOverflow = BufferOverflow.DROP_OLDEST)
@@ -176,7 +178,13 @@ internal class StreamingSyncClient(
176178
status.update { copy(downloadError = e) }
177179
} finally {
178180
if (!result.hideDisconnectStateAndReconnectImmediately) {
179-
status.update { copy(connected = false, connecting = true, downloading = false) }
181+
status.update {
182+
copy(
183+
connected = false,
184+
connecting = true,
185+
downloading = false,
186+
)
187+
}
180188
delay(retryDelayMs)
181189
}
182190
}
@@ -397,6 +405,7 @@ internal class StreamingSyncClient(
397405
schema = schema.toSerializable(),
398406
includeDefaults = options.includeDefaultStreams,
399407
activeStreams = subscriptions.map { it.key },
408+
appMetadata = appMetadata,
400409
),
401410
)
402411

@@ -405,7 +414,9 @@ internal class StreamingSyncClient(
405414
activeSubscriptions.collect {
406415
if (subscriptions !== it) {
407416
subscriptions = it
408-
controlInvocations.send(PowerSyncControlArguments.UpdateSubscriptions(activeSubscriptions.value.map { it.key }))
417+
controlInvocations.send(
418+
PowerSyncControlArguments.UpdateSubscriptions(activeSubscriptions.value.map { it.key }),
419+
)
409420
}
410421
}
411422
}
@@ -525,10 +536,52 @@ internal class StreamingSyncClient(
525536
}
526537

527538
private suspend fun connect(start: Instruction.EstablishSyncStream) {
528-
receiveTextOrBinaryLines(start.request).collect {
539+
// Merge local appMetadata from StreamingSyncClient into the request before sending
540+
val mergedRequest = mergeAppMetadata(start.request)
541+
receiveTextOrBinaryLines(mergedRequest).collect {
529542
controlInvocations.send(it)
530543
}
531544
}
545+
546+
/**
547+
* FIXME, the Rust implementation does not yet pass app_metadata to the sync instruction
548+
*
549+
* Merges local appMetadata into the request JsonObject.
550+
* If the request already has app_metadata, the local appMetadata will be merged into it
551+
* (with local values taking precedence for duplicate keys).
552+
*/
553+
private fun mergeAppMetadata(request: JsonObject): JsonObject {
554+
if (appMetadata.isEmpty()) {
555+
return request
556+
}
557+
558+
// Convert local appMetadata to JsonObject
559+
val localAppMetadataJson =
560+
JsonObject(
561+
appMetadata.mapValues { (_, value) -> JsonPrimitive(value) },
562+
)
563+
564+
// Get existing app_metadata from request, if any
565+
val existingAppMetadata =
566+
request["app_metadata"] as? JsonObject ?: JsonObject(emptyMap())
567+
568+
// Merge: existing first, then local (local takes precedence)
569+
val mergedAppMetadata =
570+
JsonObject(
571+
buildMap {
572+
putAll(existingAppMetadata)
573+
putAll(localAppMetadataJson)
574+
},
575+
)
576+
577+
// Create new request with merged app_metadata
578+
return JsonObject(
579+
buildMap {
580+
putAll(request)
581+
put("app_metadata", mergedAppMetadata)
582+
},
583+
)
584+
}
532585
}
533586

534587
@LegacySyncImplementation
@@ -566,6 +619,7 @@ internal class StreamingSyncClient(
566619
},
567620
clientId = clientId!!,
568621
parameters = params,
622+
appMetadata = appMetadata,
569623
)
570624

571625
lateinit var receiveLines: Job

common/src/commonMain/kotlin/com/powersync/sync/StreamingSyncRequest.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ internal data class StreamingSyncRequest(
1212
@SerialName("include_checksum") val includeChecksum: Boolean = true,
1313
@SerialName("client_id") val clientId: String,
1414
val parameters: JsonObject = JsonObject(mapOf()),
15+
@SerialName("app_metadata") val appMetadata: Map<String, String>,
1516
) {
1617
@SerialName("raw_data")
1718
private val rawData: Boolean = true

0 commit comments

Comments
 (0)