Skip to content

Commit d4b7725

Browse files
DominicGBauerDominicGBauer
andauthored
feat(core): add client sync rules paramters (#60)
* feat(core): add client sync rules paramters * chore: name change * chore: rename variable to align with other sdks * chore: add arrays, lists and maps to function * feat: add JsonParam sealed class to provide type safety * chore: pr reverts --------- Co-authored-by: DominicGBauer <[email protected]>
1 parent 64db232 commit d4b7725

File tree

5 files changed

+240
-8
lines changed

5 files changed

+240
-8
lines changed

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

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.powersync.db.Queries
55
import com.powersync.db.crud.CrudBatch
66
import com.powersync.db.crud.CrudTransaction
77
import com.powersync.sync.SyncStatus
8+
import com.powersync.utils.JsonParam
89

910
/**
1011
* A PowerSync managed database.
@@ -27,14 +28,37 @@ public interface PowerSyncDatabase : Queries {
2728
*
2829
* The connection is automatically re-opened if it fails for any reason.
2930
*
31+
* Use @param [connector] to specify the [PowerSyncBackendConnector].
3032
* Use @param [crudThrottleMs] to specify the time between CRUD operations. Defaults to 1000ms.
3133
* Use @param [retryDelayMs] to specify the delay between retries after failure. Defaults to 5000ms.
34+
* Use @param [params] to specify sync parameters from the client.
3235
*
36+
* Example usage:
37+
* ```
38+
* val params = JsonParam.Map(
39+
* mapOf(
40+
* "name" to JsonParam.String("John Doe"),
41+
* "age" to JsonParam.Number(30),
42+
* "isStudent" to JsonParam.Boolean(false)
43+
* )
44+
* )
45+
*
46+
* connect(
47+
* connector = connector,
48+
* crudThrottleMs = 2000L,
49+
* retryDelayMs = 10000L,
50+
* params = params
51+
* )
52+
* ```
3353
* TODO: Internal Team - Status changes are reported on [statusStream].
3454
*/
3555

36-
public suspend fun connect(connector: PowerSyncBackendConnector, crudThrottleMs: Long = 1000L,
37-
retryDelayMs: Long = 5000L)
56+
public suspend fun connect(
57+
connector: PowerSyncBackendConnector,
58+
crudThrottleMs: Long = 1000L,
59+
retryDelayMs: Long = 5000L,
60+
params: Map<String, JsonParam?> = emptyMap()
61+
)
3862

3963

4064
/**
@@ -94,4 +118,4 @@ public interface PowerSyncDatabase : Queries {
94118
* To preserve data in local-only tables, set clearLocal to false.
95119
*/
96120
public suspend fun disconnectAndClear(clearLocal: Boolean = true)
97-
}
121+
}

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ import com.powersync.db.internal.PowerSyncTransaction
2020
import com.powersync.db.schema.Schema
2121
import com.powersync.sync.SyncStatus
2222
import com.powersync.sync.SyncStream
23+
import com.powersync.utils.JsonParam
2324
import com.powersync.utils.JsonUtil
24-
import com.powersync.utils.Strings.quoteIdentifier
25+
import com.powersync.utils.toJsonObject
2526
import kotlinx.coroutines.CoroutineScope
2627
import kotlinx.coroutines.FlowPreview
2728
import kotlinx.coroutines.Job
@@ -85,7 +86,12 @@ internal class PowerSyncDatabaseImpl(
8586
}
8687

8788
@OptIn(FlowPreview::class)
88-
override suspend fun connect(connector: PowerSyncBackendConnector, crudThrottleMs: Long, retryDelayMs: Long) {
89+
override suspend fun connect(
90+
connector: PowerSyncBackendConnector,
91+
crudThrottleMs: Long,
92+
retryDelayMs: Long,
93+
params: Map<String, JsonParam?>)
94+
{
8995
// close connection if one is open
9096
disconnect()
9197

@@ -95,7 +101,8 @@ internal class PowerSyncDatabaseImpl(
95101
connector = connector,
96102
uploadCrud = suspend { connector.uploadData(this) },
97103
retryDelayMs = retryDelayMs,
98-
logger = logger
104+
logger = logger,
105+
params = params.toJsonObject()
99106
)
100107

101108
syncJob = scope.launch {

core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import com.powersync.bucket.Checkpoint
88
import com.powersync.bucket.WriteCheckpointResponse
99
import co.touchlab.stately.concurrency.AtomicBoolean
1010
import com.powersync.connectors.PowerSyncBackendConnector
11+
import com.powersync.utils.JsonParam
1112
import com.powersync.utils.JsonUtil
13+
import com.powersync.utils.toJsonObject
1214
import io.ktor.client.HttpClient
1315
import io.ktor.client.call.body
1416
import io.ktor.client.plugins.HttpTimeout
@@ -41,7 +43,8 @@ internal class SyncStream(
4143
private val connector: PowerSyncBackendConnector,
4244
private val uploadCrud: suspend () -> Unit,
4345
private val retryDelayMs: Long = 5000L,
44-
private val logger: Logger
46+
private val logger: Logger,
47+
private val params: JsonObject
4548
) {
4649
private var isUploadingCrud = AtomicBoolean(false)
4750

@@ -245,7 +248,8 @@ internal class SyncStream(
245248

246249
val req = StreamingSyncRequest(
247250
buckets = initialBuckets.map { (bucket, after) -> BucketRequest(bucket, after) },
248-
clientId = clientId!!
251+
clientId = clientId!!,
252+
parameters = params
249253
)
250254

251255
streamingSyncRequest(req).collect { value ->

core/src/commonMain/kotlin/com/powersync/utils/Json.kt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
package com.powersync.utils
22

33
import kotlinx.serialization.json.Json
4+
import kotlinx.serialization.json.JsonArray
5+
import kotlinx.serialization.json.JsonElement
6+
import kotlinx.serialization.json.JsonNull
7+
import kotlinx.serialization.json.JsonObject
8+
import kotlinx.serialization.json.JsonPrimitive
49

510
/**
611
* A global instance of a JSON serializer.
@@ -12,3 +17,28 @@ internal object JsonUtil {
1217
}
1318
}
1419

20+
public sealed class JsonParam {
21+
public data class Number(val value: kotlin.Number) : JsonParam()
22+
public data class String(val value: kotlin.String) : JsonParam()
23+
public data class Boolean(val value: kotlin.Boolean) : JsonParam()
24+
public data class Map(val value: kotlin.collections.Map<kotlin.String, JsonParam>) : JsonParam()
25+
public data class Collection(val value: kotlin.collections.Collection<JsonParam>) : JsonParam()
26+
public data class JsonElement(val value: kotlinx.serialization.json.JsonElement) : JsonParam()
27+
public data object Null : JsonParam()
28+
29+
internal fun toJsonElement(): kotlinx.serialization.json.JsonElement = when (this) {
30+
is Number -> JsonPrimitive(value)
31+
is String -> JsonPrimitive(value)
32+
is Boolean -> JsonPrimitive(value)
33+
is Map -> JsonObject(value.mapValues { it.value.toJsonElement() })
34+
is Collection -> JsonArray(value.map { it.toJsonElement() })
35+
is JsonElement -> value
36+
Null -> JsonNull
37+
}
38+
}
39+
40+
public fun Map<String, JsonParam?>.toJsonObject(): JsonObject {
41+
return JsonObject(this.mapValues { (_, value) ->
42+
value?.toJsonElement() ?: JsonNull
43+
})
44+
}
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
package com.powersync.utils
2+
3+
4+
import kotlinx.serialization.json.*
5+
import kotlin.test.*
6+
7+
class JsonTest {
8+
@Test
9+
fun testNumberToJsonElement() {
10+
val number = JsonParam.Number(42)
11+
val jsonElement = number.toJsonElement()
12+
assertTrue(jsonElement is JsonPrimitive)
13+
assertEquals(42, jsonElement.int)
14+
}
15+
16+
@Test
17+
fun testStringToJsonElement() {
18+
val string = JsonParam.String("test")
19+
val jsonElement = string.toJsonElement()
20+
assertTrue(jsonElement is JsonPrimitive)
21+
assertEquals("test", jsonElement.content)
22+
}
23+
24+
@Test
25+
fun testBooleanToJsonElement() {
26+
val boolean = JsonParam.Boolean(true)
27+
val jsonElement = boolean.toJsonElement()
28+
assertTrue(jsonElement is JsonPrimitive)
29+
assertTrue(jsonElement.boolean)
30+
}
31+
32+
@Test
33+
fun testMapToJsonElement() {
34+
val map = JsonParam.Map(mapOf(
35+
"key1" to JsonParam.String("value1"),
36+
"key2" to JsonParam.Number(42)
37+
))
38+
val jsonElement = map.toJsonElement()
39+
assertTrue(jsonElement is JsonObject)
40+
assertEquals("value1", jsonElement["key1"]?.jsonPrimitive?.content)
41+
assertEquals(42, jsonElement["key2"]?.jsonPrimitive?.int)
42+
}
43+
44+
@Test
45+
fun testListToJsonElement() {
46+
val list = JsonParam.Collection(listOf(
47+
JsonParam.String("item1"),
48+
JsonParam.Number(42)
49+
))
50+
val jsonElement = list.toJsonElement()
51+
assertTrue(jsonElement is JsonArray)
52+
assertEquals("item1", jsonElement[0].jsonPrimitive.content)
53+
assertEquals(42, jsonElement[1].jsonPrimitive.int)
54+
}
55+
56+
@Test
57+
fun testJsonElementParamToJsonElement() {
58+
val originalJson = buildJsonObject {
59+
put("key", "value")
60+
}
61+
val jsonElementParam = JsonParam.JsonElement(originalJson)
62+
val jsonElement = jsonElementParam.toJsonElement()
63+
assertEquals(originalJson, jsonElement)
64+
}
65+
66+
@Test
67+
fun testNullToJsonElement() {
68+
val nullParam = JsonParam.Null
69+
val jsonElement = nullParam.toJsonElement()
70+
assertTrue(jsonElement is JsonNull)
71+
}
72+
73+
@Test
74+
fun testMapToJsonObject() {
75+
val params = mapOf(
76+
"string" to JsonParam.String("value"),
77+
"number" to JsonParam.Number(42),
78+
"boolean" to JsonParam.Boolean(true),
79+
"null" to JsonParam.Null
80+
)
81+
val jsonObject = params.toJsonObject()
82+
assertEquals("value", jsonObject["string"]?.jsonPrimitive?.content)
83+
assertEquals(42, jsonObject["number"]?.jsonPrimitive?.int)
84+
assertEquals(true, jsonObject["boolean"]?.jsonPrimitive?.boolean)
85+
assertTrue(jsonObject["null"] is JsonNull)
86+
}
87+
88+
@Test
89+
fun testComplexNestedMapToJsonObject() {
90+
val complexNestedMap = mapOf(
91+
"string" to JsonParam.String("value"),
92+
"number" to JsonParam.Number(42),
93+
"boolean" to JsonParam.Boolean(true),
94+
"null" to JsonParam.Null,
95+
"nestedMap" to JsonParam.Map(mapOf(
96+
"list" to JsonParam.Collection(listOf(
97+
JsonParam.Number(1),
98+
JsonParam.String("two"),
99+
JsonParam.Boolean(false)
100+
)),
101+
"deeplyNested" to JsonParam.Map(mapOf(
102+
"jsonElement" to JsonParam.JsonElement(buildJsonObject {
103+
put("key", "value")
104+
put("array", buildJsonArray {
105+
add(1)
106+
add("string")
107+
add(true)
108+
})
109+
}),
110+
"mixedList" to JsonParam.Collection(arrayListOf(
111+
JsonParam.Number(3.14),
112+
JsonParam.Map(mapOf(
113+
"key" to JsonParam.String("nestedValue")
114+
)),
115+
JsonParam.Null
116+
)
117+
)
118+
))
119+
))
120+
)
121+
122+
val jsonObject = complexNestedMap.toJsonObject()
123+
124+
// Verify top-level elements
125+
assertEquals("value", jsonObject["string"]?.jsonPrimitive?.content)
126+
assertEquals(42, jsonObject["number"]?.jsonPrimitive?.int)
127+
assertEquals(true, jsonObject["boolean"]?.jsonPrimitive?.boolean)
128+
assertTrue(jsonObject["null"] is JsonNull)
129+
130+
// Verify nested map
131+
val nestedMap = jsonObject["nestedMap"]?.jsonObject
132+
assertNotNull(nestedMap)
133+
134+
// Verify nested list
135+
val nestedList = nestedMap["list"]?.jsonArray
136+
assertNotNull(nestedList)
137+
assertEquals(3, nestedList.size)
138+
assertEquals(1, nestedList[0].jsonPrimitive.int)
139+
assertEquals("two", nestedList[1].jsonPrimitive.content)
140+
assertEquals(false, nestedList[2].jsonPrimitive.boolean)
141+
142+
// Verify deeply nested map
143+
val deeplyNested = nestedMap["deeplyNested"]?.jsonObject
144+
assertNotNull(deeplyNested)
145+
146+
// Verify JsonElement
147+
val jsonElement = deeplyNested["jsonElement"]?.jsonObject
148+
assertNotNull(jsonElement)
149+
assertEquals("value", jsonElement["key"]?.jsonPrimitive?.content)
150+
val jsonElementArray = jsonElement["array"]?.jsonArray
151+
assertNotNull(jsonElementArray)
152+
assertEquals(3, jsonElementArray.size)
153+
assertEquals(1, jsonElementArray[0].jsonPrimitive.int)
154+
assertEquals("string", jsonElementArray[1].jsonPrimitive.content)
155+
assertEquals(true, jsonElementArray[2].jsonPrimitive.boolean)
156+
157+
// Verify mixed list
158+
val mixedList = deeplyNested["mixedList"]?.jsonArray
159+
assertNotNull(mixedList)
160+
assertEquals(3, mixedList.size)
161+
assertEquals(3.14, mixedList[0].jsonPrimitive.double)
162+
val nestedMapInList = mixedList[1].jsonObject
163+
assertNotNull(nestedMapInList)
164+
assertEquals("nestedValue", nestedMapInList["key"]?.jsonPrimitive?.content)
165+
assertTrue(mixedList[2] is JsonNull)
166+
}
167+
}

0 commit comments

Comments
 (0)