From bebd3aea6495495139f4d918dcb2ecafc027a8ef Mon Sep 17 00:00:00 2001 From: james Date: Tue, 15 Aug 2023 00:57:07 +0100 Subject: [PATCH 1/5] Adding lq --- .../kotlin/uk/gibby/driver/Surreal.kt | 48 ++++++++++++++++--- .../gibby/driver/rpc/functions/LiveQuery.kt | 18 +++++++ .../gibby/driver/rpc/model/LiveQueryAction.kt | 7 +++ .../uk/gibby/driver/rpc/model/RpcResponse.kt | 5 ++ .../driver/rpc/model/RpcResponseSerializer.kt | 8 +++- src/commonTest/kotlin/LiveQueryTest.kt | 27 +++++++++++ 6 files changed, 106 insertions(+), 7 deletions(-) create mode 100644 src/commonMain/kotlin/uk/gibby/driver/rpc/functions/LiveQuery.kt create mode 100644 src/commonMain/kotlin/uk/gibby/driver/rpc/model/LiveQueryAction.kt create mode 100644 src/commonTest/kotlin/LiveQueryTest.kt diff --git a/src/commonMain/kotlin/uk/gibby/driver/Surreal.kt b/src/commonMain/kotlin/uk/gibby/driver/Surreal.kt index eef928b..825af94 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/Surreal.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/Surreal.kt @@ -12,6 +12,7 @@ import uk.gibby.driver.rpc.model.RpcRequest import uk.gibby.driver.rpc.model.RpcResponse import uk.gibby.driver.rpc.model.RpcResponseSerializer import kotlinx.serialization.encodeToString +import uk.gibby.driver.rpc.model.LiveQueryAction /** * SurrealDB driver @@ -25,6 +26,7 @@ class Surreal(private val host: String, private val port: Int = 8000) { private var count = 0L private var connection: DefaultClientWebSocketSession? = null private val requests = ConcurrentMap>() + private val liveQueries = ConcurrentMap>() private val context = CoroutineScope(Dispatchers.Default) /** @@ -47,13 +49,36 @@ class Surreal(private val host: String, private val port: Int = 8000) { requests.forEach { (_, r) -> r.cancel(CancellationException("Failed to decode incoming response: ${it.readText()}\n${e.message}"))} throw e } - val request = requests[response.id] - if (request == null) requests.forEach { (_, r) -> r.cancel(CancellationException("Received a request with an unknown id: ${response.id} body: $response"))} - else when(response) { - is RpcResponse.Success -> request.send(response.result) - is RpcResponse.Error -> request.cancel(CancellationException("SurrealDB responded with an error: '${response.error}'")) + if(response is RpcResponse.Notification) { + val action = response.result + val liveQuery = liveQueries[action.id] + if (liveQuery != null) { + liveQuery.send(response.result) + } + else { + println("Couldn't find live query with id ${action.id}") + requests.forEach { + (_, r) -> r.cancel(CancellationException("Received a request with an unknown id: ${response.id} body: $response")) + } + } + } + if(response.id != null) { + val request = requests[response.id] + if (request != null) { + when(response) { + is RpcResponse.Success -> request.send(response.result) + is RpcResponse.Error -> request.cancel(CancellationException("SurrealDB responded with an error: '${response.error}'")) + else -> TODO() + } + requests.remove(response.id) + } + else { + if (response.id == null) println("SurrealDB: Received a response with no id: $response") + else requests.forEach { + (_, r) -> r.cancel(CancellationException("Received a request with an unknown id: ${response.id} body: $response")) + } + } } - requests.remove(response.id) } } } @@ -68,4 +93,15 @@ class Surreal(private val host: String, private val port: Int = 8000) { return channel.receive() } + internal fun subscribe(liveQueryId: String): Channel { + val channel = Channel() + println("Live query $liveQueryId created") + liveQueries[liveQueryId] = channel.apply { + invokeOnClose { + println("Live query $liveQueryId closed") + liveQueries.remove(liveQueryId) + } + } + return channel + } } \ No newline at end of file diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/LiveQuery.kt b/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/LiveQuery.kt new file mode 100644 index 0000000..fc2306b --- /dev/null +++ b/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/LiveQuery.kt @@ -0,0 +1,18 @@ +package uk.gibby.driver.rpc.functions + +import kotlinx.coroutines.channels.consumeEach +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.consumeAsFlow +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.add +import kotlinx.serialization.json.buildJsonArray +import kotlinx.serialization.json.decodeFromJsonElement +import uk.gibby.driver.Surreal +import uk.gibby.driver.rpc.model.LiveQueryAction +import uk.gibby.driver.surrealJson + +suspend fun Surreal.live(table: String): Flow { + val response = sendRequest("live", buildJsonArray{ add(table) }) + val id: String = surrealJson.decodeFromJsonElement(response) + return subscribe(id).consumeAsFlow() +} \ No newline at end of file diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/model/LiveQueryAction.kt b/src/commonMain/kotlin/uk/gibby/driver/rpc/model/LiveQueryAction.kt new file mode 100644 index 0000000..3c186e1 --- /dev/null +++ b/src/commonMain/kotlin/uk/gibby/driver/rpc/model/LiveQueryAction.kt @@ -0,0 +1,7 @@ +package uk.gibby.driver.rpc.model + +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonObject + +@Serializable +data class LiveQueryAction(val action: String, val id: String, val result: JsonObject? = null) \ No newline at end of file diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/model/RpcResponse.kt b/src/commonMain/kotlin/uk/gibby/driver/rpc/model/RpcResponse.kt index 2b8deae..6e4bc3a 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/model/RpcResponse.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/rpc/model/RpcResponse.kt @@ -13,5 +13,10 @@ sealed class RpcResponse { @Serializable data class Error(override val id: String, val error: JsonElement): RpcResponse() + + @Serializable + data class Notification(val result: LiveQueryAction): RpcResponse() { + override val id: String? = null + } } diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/model/RpcResponseSerializer.kt b/src/commonMain/kotlin/uk/gibby/driver/rpc/model/RpcResponseSerializer.kt index 3b7872b..4f44719 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/model/RpcResponseSerializer.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/rpc/model/RpcResponseSerializer.kt @@ -11,6 +11,7 @@ import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.encoding.decodeStructure import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.decodeFromJsonElement import kotlinx.serialization.json.encodeToJsonElement object RpcResponseSerializer: KSerializer { @@ -35,7 +36,12 @@ object RpcResponseSerializer: KSerializer { } return if(error != null) { RpcResponse.Error(id!!, surrealJson.encodeToJsonElement(error)) - } else RpcResponse.Success(id!!, result ?: surrealJson.encodeToJsonElement(String.serializer().nullable, null)) + } else if(id != null) { + RpcResponse.Success(id!!, result ?: surrealJson.encodeToJsonElement(String.serializer().nullable, null)) + } + else { + RpcResponse.Notification(surrealJson.decodeFromJsonElement(result!!)) + } } override fun serialize(encoder: Encoder, value: RpcResponse) { diff --git a/src/commonTest/kotlin/LiveQueryTest.kt b/src/commonTest/kotlin/LiveQueryTest.kt new file mode 100644 index 0000000..c815b47 --- /dev/null +++ b/src/commonTest/kotlin/LiveQueryTest.kt @@ -0,0 +1,27 @@ +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import uk.gibby.driver.Surreal +import uk.gibby.driver.rpc.functions.* +import uk.gibby.driver.rpc.model.bind +import utils.cleanDatabase +import kotlin.test.Test + +class LiveQueryTest { + @Test + fun testLiveQuery() = runTest { + cleanDatabase() + val connection = Surreal("localhost", 8000) + connection.connect() + connection.signin("root", "root") + connection.use("test", "test") + val incoming = connection.live("test") + connection.create("test").content(buildJsonObject { put("thing", "thing") }) + println(incoming.first()) + } +} \ No newline at end of file From b844dfb0e0a0f9aefe10f49b578ed5661a4c8d6f Mon Sep 17 00:00:00 2001 From: james Date: Tue, 22 Aug 2023 22:18:00 +0100 Subject: [PATCH 2/5] Basic lq working --- .../kotlin/uk/gibby/driver/Surreal.kt | 110 +++++++------ .../rpc/exception/LiveQueryKilledException.kt | 5 + .../uk/gibby/driver/rpc/functions/Kill.kt | 10 ++ .../uk/gibby/driver/rpc/functions/Live.kt | 56 +++++++ .../gibby/driver/rpc/functions/LiveQuery.kt | 18 --- .../gibby/driver/rpc/model/LiveQueryAction.kt | 67 +++++++- .../uk/gibby/driver/rpc/model/RpcResponse.kt | 2 +- src/commonTest/kotlin/KillTest.kt | 21 +++ src/commonTest/kotlin/LiveQueryTest.kt | 146 ++++++++++++++++-- 9 files changed, 355 insertions(+), 80 deletions(-) create mode 100644 src/commonMain/kotlin/uk/gibby/driver/rpc/exception/LiveQueryKilledException.kt create mode 100644 src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Kill.kt create mode 100644 src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Live.kt delete mode 100644 src/commonMain/kotlin/uk/gibby/driver/rpc/functions/LiveQuery.kt create mode 100644 src/commonTest/kotlin/KillTest.kt diff --git a/src/commonMain/kotlin/uk/gibby/driver/Surreal.kt b/src/commonMain/kotlin/uk/gibby/driver/Surreal.kt index 825af94..2907bf5 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/Surreal.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/Surreal.kt @@ -6,13 +6,11 @@ import io.ktor.util.collections.* import io.ktor.websocket.* import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.flow.* import kotlinx.serialization.json.* -import uk.gibby.driver.rpc.model.RpcRequest -import uk.gibby.driver.rpc.model.RpcResponse -import uk.gibby.driver.rpc.model.RpcResponseSerializer -import kotlinx.serialization.encodeToString -import uk.gibby.driver.rpc.model.LiveQueryAction +import uk.gibby.driver.rpc.exception.LiveQueryKilledException +import uk.gibby.driver.rpc.functions.kill +import uk.gibby.driver.rpc.model.* /** * SurrealDB driver @@ -26,7 +24,7 @@ class Surreal(private val host: String, private val port: Int = 8000) { private var count = 0L private var connection: DefaultClientWebSocketSession? = null private val requests = ConcurrentMap>() - private val liveQueries = ConcurrentMap>() + private val liveQueries = ConcurrentMap>>() private val context = CoroutineScope(Dispatchers.Default) /** @@ -46,44 +44,53 @@ class Surreal(private val host: String, private val port: Int = 8000) { val response = try { surrealJson.decodeFromString(RpcResponseSerializer, it.readText()) } catch (e: Exception) { + // In theory this could be an error for any request, so we cancel all of them requests.forEach { (_, r) -> r.cancel(CancellationException("Failed to decode incoming response: ${it.readText()}\n${e.message}"))} throw e } - if(response is RpcResponse.Notification) { - val action = response.result - val liveQuery = liveQueries[action.id] - if (liveQuery != null) { - liveQuery.send(response.result) - } - else { - println("Couldn't find live query with id ${action.id}") - requests.forEach { - (_, r) -> r.cancel(CancellationException("Received a request with an unknown id: ${response.id} body: $response")) - } - } - } - if(response.id != null) { - val request = requests[response.id] - if (request != null) { - when(response) { - is RpcResponse.Success -> request.send(response.result) - is RpcResponse.Error -> request.cancel(CancellationException("SurrealDB responded with an error: '${response.error}'")) - else -> TODO() - } - requests.remove(response.id) - } - else { - if (response.id == null) println("SurrealDB: Received a response with no id: $response") - else requests.forEach { - (_, r) -> r.cancel(CancellationException("Received a request with an unknown id: ${response.id} body: $response")) - } - } + when(response) { + is RpcResponse.Success -> handleSuccess(response) + is RpcResponse.Error -> handleError(response) + is RpcResponse.Notification -> handleNotification(response) } } } } } + private suspend fun handleSuccess(response: RpcResponse.Success) { + val request = requests[response.id] + if (request != null) { + request.send(response.result) + } else { + requests.forEach { + // In theory this could be an error for any request, so we cancel all of them + (_, r) -> + r.cancel(CancellationException("Received a request with an unknown id: ${response.id} body: $response")) + } + } + } + + private fun handleError(response: RpcResponse.Error) { + val request = requests[response.id] + if (request != null) { + request.cancel(CancellationException("SurrealDB responded with an error: '${response.error}'")) + } else { + requests.forEach { + // In theory this could be an error for any request, so we cancel all of them + (_, r) -> + r.cancel(CancellationException("Received a request with an unknown id: ${response.id} body: $response")) + } + } + requests.remove(response.id) + } + + private suspend fun handleNotification(response: RpcResponse.Notification) { + val action = response.result + val liveQuery = liveQueries.getOrPut(action.id) { Channel() } + context.launch { liveQuery.send(response.result) } + } + internal suspend fun sendRequest(method: String, params: JsonArray): JsonElement { val id = count++.toString() val request = RpcRequest(id, method, params) @@ -93,15 +100,24 @@ class Surreal(private val host: String, private val port: Int = 8000) { return channel.receive() } - internal fun subscribe(liveQueryId: String): Channel { - val channel = Channel() - println("Live query $liveQueryId created") - liveQueries[liveQueryId] = channel.apply { - invokeOnClose { - println("Live query $liveQueryId closed") - liveQueries.remove(liveQueryId) - } - } - return channel + fun subscribeAsJson(liveQueryId: String): Flow> { + val channel = liveQueries.getOrPut(liveQueryId) { Channel() } + return channel.receiveAsFlow() + } + + inline fun subscribe(liveQueryId: String): Flow> { + return subscribeAsJson(liveQueryId).map { it.asType() } + } + + fun unsubscribe(liveQueryId: String) { + val channel = liveQueries[liveQueryId] + channel?.cancel(LiveQueryKilledException) + liveQueries.remove(liveQueryId) + } + + internal fun triggerKill(liveQueryId: String) { + context.launch { kill(liveQueryId) } } -} \ No newline at end of file + +} + diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/exception/LiveQueryKilledException.kt b/src/commonMain/kotlin/uk/gibby/driver/rpc/exception/LiveQueryKilledException.kt new file mode 100644 index 0000000..1ea775b --- /dev/null +++ b/src/commonMain/kotlin/uk/gibby/driver/rpc/exception/LiveQueryKilledException.kt @@ -0,0 +1,5 @@ +package uk.gibby.driver.rpc.exception + +import kotlinx.coroutines.CancellationException + +object LiveQueryKilledException: CancellationException("Live query has been killed") \ No newline at end of file diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Kill.kt b/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Kill.kt new file mode 100644 index 0000000..b2e642c --- /dev/null +++ b/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Kill.kt @@ -0,0 +1,10 @@ +package uk.gibby.driver.rpc.functions + +import kotlinx.serialization.json.add +import kotlinx.serialization.json.buildJsonArray +import uk.gibby.driver.Surreal + +suspend fun Surreal.kill(liveQueryId: String) { + sendRequest("kill", buildJsonArray { add(liveQueryId) }) +} + diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Live.kt b/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Live.kt new file mode 100644 index 0000000..342c1a7 --- /dev/null +++ b/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Live.kt @@ -0,0 +1,56 @@ +package uk.gibby.driver.rpc.functions + +import io.ktor.utils.io.core.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.add +import kotlinx.serialization.json.buildJsonArray +import kotlinx.serialization.json.decodeFromJsonElement +import uk.gibby.driver.Surreal +import uk.gibby.driver.rpc.model.LiveQueryAction +import uk.gibby.driver.rpc.model.asType +import uk.gibby.driver.surrealJson +import kotlin.jvm.JvmName + + +suspend fun Surreal.live(table: String): String { + val response = sendRequest("live", buildJsonArray { add(table) }) + return surrealJson.decodeFromJsonElement(response) +} + + +@JvmName("observeJson") +suspend fun Surreal.observeAsJson(liveQueryId: String): LiveQueryFlow { + val id = live(liveQueryId) + return LiveQueryFlow( + flow = subscribe(id), + id = id, + connection = this + ) +} + +suspend inline fun Surreal.observe(table: String): LiveQueryFlow { + val jsonFlow = observeAsJson(table) + return jsonFlow.map { it.asType() } +} + +class LiveQueryFlow( + private val flow: Flow>, + val id: String, + private val connection: Surreal +): Flow> by flow, Closeable { + override fun close() { + connection.unsubscribe(id) + connection.triggerKill(id) + } + + fun map(transform: (LiveQueryAction) -> LiveQueryAction): LiveQueryFlow { + return LiveQueryFlow( + flow = flow.map { transform(it) }, + id = id, + connection = connection + ) + } +} \ No newline at end of file diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/LiveQuery.kt b/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/LiveQuery.kt deleted file mode 100644 index fc2306b..0000000 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/LiveQuery.kt +++ /dev/null @@ -1,18 +0,0 @@ -package uk.gibby.driver.rpc.functions - -import kotlinx.coroutines.channels.consumeEach -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.consumeAsFlow -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.add -import kotlinx.serialization.json.buildJsonArray -import kotlinx.serialization.json.decodeFromJsonElement -import uk.gibby.driver.Surreal -import uk.gibby.driver.rpc.model.LiveQueryAction -import uk.gibby.driver.surrealJson - -suspend fun Surreal.live(table: String): Flow { - val response = sendRequest("live", buildJsonArray{ add(table) }) - val id: String = surrealJson.decodeFromJsonElement(response) - return subscribe(id).consumeAsFlow() -} \ No newline at end of file diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/model/LiveQueryAction.kt b/src/commonMain/kotlin/uk/gibby/driver/rpc/model/LiveQueryAction.kt index 3c186e1..40b2546 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/model/LiveQueryAction.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/rpc/model/LiveQueryAction.kt @@ -1,7 +1,66 @@ package uk.gibby.driver.rpc.model -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.* +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.encoding.CompositeDecoder +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.decodeFromJsonElement +import uk.gibby.driver.surrealJson -@Serializable -data class LiveQueryAction(val action: String, val id: String, val result: JsonObject? = null) \ No newline at end of file +@Serializable(with = LiveQueryActionSerializer::class) +sealed class LiveQueryAction(val id: String) { + class Create(id: String, val result: T): LiveQueryAction(id) + class Update(id: String, val result: T): LiveQueryAction(id) + class Delete(id: String, val deletedId: String): LiveQueryAction(id) + +} + + +inline fun LiveQueryAction.asType(): LiveQueryAction { + return when(this) { + is LiveQueryAction.Delete -> this + is LiveQueryAction.Create -> LiveQueryAction.Create(id, surrealJson.decodeFromJsonElement(result)) + is LiveQueryAction.Update -> LiveQueryAction.Update(id, surrealJson.decodeFromJsonElement(result)) + } +} + +class LiveQueryActionSerializer(private val resultSerializer: KSerializer): KSerializer> { + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("LiveQueryAction") { + element("action", String.serializer().descriptor) + element("id", String.serializer().descriptor) + element("result", resultSerializer.descriptor) + } + + override fun deserialize(decoder: Decoder): LiveQueryAction { + val input = decoder.beginStructure(descriptor) + var action: String? = null + var id: String? = null + var result: Thing? = null + loop@ while (true) { + when (val i = input.decodeElementIndex(descriptor)) { + CompositeDecoder.DECODE_DONE -> break@loop + 0 -> action = input.decodeStringElement(descriptor, i) + 1 -> id = input.decodeStringElement(descriptor, i) + 2 -> result = input.decodeSerializableElement(descriptor, i, ThingSerializer(resultSerializer)) + else -> throw SerializationException("Unknown index $i") + } + } + input.endStructure(descriptor) + if (action == null || id == null || result == null) throw SerializationException("Missing fields") + return when(action) { + "CREATE" -> LiveQueryAction.Create(id, (result as Thing.Record).result) + "UPDATE" -> LiveQueryAction.Update(id, (result as Thing.Record).result) + "DELETE" -> LiveQueryAction.Delete(id, (result as Thing.Reference).id) + else -> throw SerializationException("Unknown action $action") + } + } + + override fun serialize(encoder: Encoder, value: LiveQueryAction) { + TODO("Not yet implemented") + } + +} \ No newline at end of file diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/model/RpcResponse.kt b/src/commonMain/kotlin/uk/gibby/driver/rpc/model/RpcResponse.kt index 6e4bc3a..0270832 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/model/RpcResponse.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/rpc/model/RpcResponse.kt @@ -15,7 +15,7 @@ sealed class RpcResponse { data class Error(override val id: String, val error: JsonElement): RpcResponse() @Serializable - data class Notification(val result: LiveQueryAction): RpcResponse() { + data class Notification(val result: LiveQueryAction): RpcResponse() { override val id: String? = null } } diff --git a/src/commonTest/kotlin/KillTest.kt b/src/commonTest/kotlin/KillTest.kt new file mode 100644 index 0000000..32427ae --- /dev/null +++ b/src/commonTest/kotlin/KillTest.kt @@ -0,0 +1,21 @@ +import kotlinx.coroutines.test.runTest +import uk.gibby.driver.Surreal +import uk.gibby.driver.rpc.functions.kill +import uk.gibby.driver.rpc.functions.live +import uk.gibby.driver.rpc.functions.signin +import uk.gibby.driver.rpc.functions.use +import utils.cleanDatabase +import kotlin.test.Test + +class KillTest { + @Test + fun testKill() = runTest { + cleanDatabase() + val connection = Surreal("localhost", 8000) + connection.connect() + connection.signin("root", "root") + connection.use("test", "test") + val liveQueryId = connection.live("test") + connection.kill(liveQueryId) + } +} \ No newline at end of file diff --git a/src/commonTest/kotlin/LiveQueryTest.kt b/src/commonTest/kotlin/LiveQueryTest.kt index c815b47..5dbc770 100644 --- a/src/commonTest/kotlin/LiveQueryTest.kt +++ b/src/commonTest/kotlin/LiveQueryTest.kt @@ -1,16 +1,13 @@ -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.put import uk.gibby.driver.Surreal +import uk.gibby.driver.rpc.exception.LiveQueryKilledException import uk.gibby.driver.rpc.functions.* +import uk.gibby.driver.rpc.model.LiveQueryAction import uk.gibby.driver.rpc.model.bind +import uk.gibby.driver.rpc.model.data import utils.cleanDatabase -import kotlin.test.Test +import kotlin.test.* class LiveQueryTest { @Test @@ -20,8 +17,137 @@ class LiveQueryTest { connection.connect() connection.signin("root", "root") connection.use("test", "test") - val incoming = connection.live("test") - connection.create("test").content(buildJsonObject { put("thing", "thing") }) - println(incoming.first()) + + val incoming = connection.observe("test") + connection.create("test", "first").content(TestClass("thing", 1)) + connection.create("test", "second").content(TestClass("thing", 2)) + connection.update("test", "first").patch { + replace("myText", "thing2") + } + connection.delete("test", "second") + + val first = incoming.first() + assertTrue { first is LiveQueryAction.Create } + first as LiveQueryAction.Create + assertEquals("thing", first.result.myText) + assertEquals(1, first.result.myNumber) + + val second = incoming.first() + assertTrue { second is LiveQueryAction.Create } + second as LiveQueryAction.Create + assertEquals("thing", second.result.myText) + assertEquals(2, second.result.myNumber) + + val updated = incoming.first() + assertTrue { updated is LiveQueryAction.Update } + updated as LiveQueryAction.Update + assertEquals("thing2", updated.result.myText) + assertEquals(1, updated.result.myNumber) + + val deleted = incoming.first() + assertTrue { deleted is LiveQueryAction.Delete } + deleted as LiveQueryAction.Delete + assertEquals("test:second", deleted.deletedId) + + incoming.close() + assertFailsWith { + incoming.first() + } } + + @Test + fun testLiveQueryAsPartOfRegularQuery() = runTest { + cleanDatabase() + val connection = Surreal("localhost", 8000) + connection.connect() + connection.signin("root", "root") + connection.use("test", "test") + val result = connection.query("LIVE SELECT * FROM test;") + val liveQueryId = result.first().data() + val incoming = connection.subscribe(liveQueryId) + + connection.create("test", "first").content(TestClass("thing", 1)) + connection.create("test", "second").content(TestClass("thing", 2)) + connection.update("test", "first").patch { + replace("myText", "thing2") + } + connection.delete("test", "second") + + val first = incoming.first() + assertTrue { first is LiveQueryAction.Create } + first as LiveQueryAction.Create + assertEquals("thing", first.result.myText) + assertEquals(1, first.result.myNumber) + + val second = incoming.first() + assertTrue { second is LiveQueryAction.Create } + second as LiveQueryAction.Create + assertEquals("thing", second.result.myText) + assertEquals(2, second.result.myNumber) + + val updated = incoming.first() + assertTrue { updated is LiveQueryAction.Update } + updated as LiveQueryAction.Update + assertEquals("thing2", updated.result.myText) + assertEquals(1, updated.result.myNumber) + + val deleted = incoming.first() + assertTrue { deleted is LiveQueryAction.Delete } + deleted as LiveQueryAction.Delete + assertEquals("test:second", deleted.deletedId) + + connection.query("KILL \$liveQueryId;", bind("liveQueryId", liveQueryId)) + connection.unsubscribe(liveQueryId) + assertFailsWith { + incoming.first() + } + } + + @Test + fun testLiveQueryUsingRpcFunctions() = runTest { + cleanDatabase() + val connection = Surreal("localhost", 8000) + connection.connect() + connection.signin("root", "root") + connection.use("test", "test") + val liveQueryId = connection.live("test") + val incoming = connection.subscribe(liveQueryId) + + connection.create("test", "first").content(TestClass("thing", 1)) + connection.create("test", "second").content(TestClass("thing", 2)) + connection.update("test", "first").patch { + replace("myText", "thing2") + } + connection.delete("test", "second") + + val first = incoming.first() + assertTrue { first is LiveQueryAction.Create } + first as LiveQueryAction.Create + assertEquals("thing", first.result.myText) + assertEquals(1, first.result.myNumber) + + val second = incoming.first() + assertTrue { second is LiveQueryAction.Create } + second as LiveQueryAction.Create + assertEquals("thing", second.result.myText) + assertEquals(2, second.result.myNumber) + + val updated = incoming.first() + assertTrue { updated is LiveQueryAction.Update } + updated as LiveQueryAction.Update + assertEquals("thing2", updated.result.myText) + assertEquals(1, updated.result.myNumber) + + val deleted = incoming.first() + assertTrue { deleted is LiveQueryAction.Delete } + deleted as LiveQueryAction.Delete + assertEquals("test:second", deleted.deletedId) + + connection.kill(liveQueryId) + connection.unsubscribe(liveQueryId) + assertFailsWith { + incoming.first() + } + } + } \ No newline at end of file From 55678f5408e8b7d346ae66a1083d058696dcac4a Mon Sep 17 00:00:00 2001 From: james Date: Wed, 23 Aug 2023 18:51:07 +0100 Subject: [PATCH 3/5] Refactor --- .github/workflows/gradle.yml | 2 +- .../kotlin/uk/gibby/driver/Surreal.kt | 14 ++- .../annotation/SurrealDbNightlyOnlyApi.kt | 6 + .../uk/gibby/driver/api/LiveQueryFlow.kt | 26 ++++ .../kotlin/uk/gibby/driver/api/Observe.kt | 29 +++++ .../exception/LiveQueryKilledException.kt | 2 +- .../{rpc => }/exception/QueryException.kt | 2 +- .../model/Patch.kt => model/JsonPatch.kt} | 2 +- .../gibby/driver/{rpc => }/model/RootAuth.kt | 2 +- .../uk/gibby/driver/{rpc => }/model/Thing.kt | 2 +- .../driver/{rpc/model => model/query}/Bind.kt | 2 +- .../model => model/query}/QueryResponse.kt | 4 +- .../model => model/rpc}/LiveQueryAction.kt | 25 ++-- .../{rpc/model => model/rpc}/RpcRequest.kt | 2 +- .../{rpc/model => model/rpc}/RpcResponse.kt | 2 +- .../rpc}/RpcResponseSerializer.kt | 2 +- .../rpc/{functions => }/Authenticate.kt | 2 +- .../driver/rpc/{functions => }/Create.kt | 2 +- .../driver/rpc/{functions => }/Delete.kt | 2 +- .../gibby/driver/rpc/{functions => }/Info.kt | 2 +- .../driver/rpc/{functions => }/Invalidate.kt | 2 +- .../gibby/driver/rpc/{functions => }/Kill.kt | 2 +- .../gibby/driver/rpc/{functions => }/Let.kt | 2 +- .../kotlin/uk/gibby/driver/rpc/Live.kt | 28 +++++ .../gibby/driver/rpc/{functions => }/Query.kt | 6 +- .../driver/rpc/{functions => }/Select.kt | 2 +- .../driver/rpc/{functions => }/SignIn.kt | 6 +- .../driver/rpc/{functions => }/Signup.kt | 4 +- .../gibby/driver/rpc/{functions => }/Unset.kt | 2 +- .../driver/rpc/{functions => }/Update.kt | 6 +- .../gibby/driver/rpc/{functions => }/Use.kt | 2 +- .../uk/gibby/driver/rpc/functions/Live.kt | 56 --------- src/commonTest/kotlin/AuthenticateTest.kt | 8 +- src/commonTest/kotlin/ConnectionTest.kt | 5 +- src/commonTest/kotlin/CreateTest.kt | 28 ++++- src/commonTest/kotlin/DeleteTest.kt | 2 +- src/commonTest/kotlin/FetchTest.kt | 9 +- src/commonTest/kotlin/InfoTest.kt | 10 +- src/commonTest/kotlin/KillTest.kt | 11 +- src/commonTest/kotlin/LetTest.kt | 10 +- src/commonTest/kotlin/LiveQueryTest.kt | 115 ++++++------------ src/commonTest/kotlin/MergeTest.kt | 7 +- src/commonTest/kotlin/PatchTest.kt | 1 + src/commonTest/kotlin/QueryTest.kt | 10 +- src/commonTest/kotlin/SelectTest.kt | 4 + src/commonTest/kotlin/SignInTest.kt | 6 +- src/commonTest/kotlin/SignUpTest.kt | 4 +- src/commonTest/kotlin/UnsetTest.kt | 4 +- src/commonTest/kotlin/UpdateTest.kt | 4 + src/commonTest/kotlin/utils/CleanDatabase.kt | 8 +- 50 files changed, 269 insertions(+), 227 deletions(-) create mode 100644 src/commonMain/kotlin/uk/gibby/driver/annotation/SurrealDbNightlyOnlyApi.kt create mode 100644 src/commonMain/kotlin/uk/gibby/driver/api/LiveQueryFlow.kt create mode 100644 src/commonMain/kotlin/uk/gibby/driver/api/Observe.kt rename src/commonMain/kotlin/uk/gibby/driver/{rpc => }/exception/LiveQueryKilledException.kt (77%) rename src/commonMain/kotlin/uk/gibby/driver/{rpc => }/exception/QueryException.kt (73%) rename src/commonMain/kotlin/uk/gibby/driver/{rpc/model/Patch.kt => model/JsonPatch.kt} (98%) rename src/commonMain/kotlin/uk/gibby/driver/{rpc => }/model/RootAuth.kt (85%) rename src/commonMain/kotlin/uk/gibby/driver/{rpc => }/model/Thing.kt (98%) rename src/commonMain/kotlin/uk/gibby/driver/{rpc/model => model/query}/Bind.kt (95%) rename src/commonMain/kotlin/uk/gibby/driver/{rpc/model => model/query}/QueryResponse.kt (90%) rename src/commonMain/kotlin/uk/gibby/driver/{rpc/model => model/rpc}/LiveQueryAction.kt (78%) rename src/commonMain/kotlin/uk/gibby/driver/{rpc/model => model/rpc}/RpcRequest.kt (84%) rename src/commonMain/kotlin/uk/gibby/driver/{rpc/model => model/rpc}/RpcResponse.kt (94%) rename src/commonMain/kotlin/uk/gibby/driver/{rpc/model => model/rpc}/RpcResponseSerializer.kt (98%) rename src/commonMain/kotlin/uk/gibby/driver/rpc/{functions => }/Authenticate.kt (91%) rename src/commonMain/kotlin/uk/gibby/driver/rpc/{functions => }/Create.kt (98%) rename src/commonMain/kotlin/uk/gibby/driver/rpc/{functions => }/Delete.kt (94%) rename src/commonMain/kotlin/uk/gibby/driver/rpc/{functions => }/Info.kt (95%) rename src/commonMain/kotlin/uk/gibby/driver/rpc/{functions => }/Invalidate.kt (87%) rename src/commonMain/kotlin/uk/gibby/driver/rpc/{functions => }/Kill.kt (85%) rename src/commonMain/kotlin/uk/gibby/driver/rpc/{functions => }/Let.kt (96%) create mode 100644 src/commonMain/kotlin/uk/gibby/driver/rpc/Live.kt rename src/commonMain/kotlin/uk/gibby/driver/rpc/{functions => }/Query.kt (89%) rename src/commonMain/kotlin/uk/gibby/driver/rpc/{functions => }/Select.kt (97%) rename src/commonMain/kotlin/uk/gibby/driver/rpc/{functions => }/SignIn.kt (93%) rename src/commonMain/kotlin/uk/gibby/driver/rpc/{functions => }/Signup.kt (94%) rename src/commonMain/kotlin/uk/gibby/driver/rpc/{functions => }/Unset.kt (90%) rename src/commonMain/kotlin/uk/gibby/driver/rpc/{functions => }/Update.kt (98%) rename src/commonMain/kotlin/uk/gibby/driver/rpc/{functions => }/Use.kt (91%) delete mode 100644 src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Live.kt diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index a0fccc6..870aa4d 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Start Surreal - run: docker run -d -p 8000:8000 surrealdb/surrealdb:latest start --user root --pass root -- "memory" + run: docker run -d -p 8000:8000 surrealdb/surrealdb:nightly start --user root --pass root -- "memory" - uses: actions/checkout@v3 - name: Set up JDK 11 uses: actions/setup-java@v3 diff --git a/src/commonMain/kotlin/uk/gibby/driver/Surreal.kt b/src/commonMain/kotlin/uk/gibby/driver/Surreal.kt index 2907bf5..c5ef719 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/Surreal.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/Surreal.kt @@ -8,9 +8,9 @@ import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.* import kotlinx.serialization.json.* -import uk.gibby.driver.rpc.exception.LiveQueryKilledException -import uk.gibby.driver.rpc.functions.kill -import uk.gibby.driver.rpc.model.* +import uk.gibby.driver.exception.LiveQueryKilledException +import uk.gibby.driver.model.rpc.* +import uk.gibby.driver.rpc.kill /** * SurrealDB driver @@ -41,6 +41,7 @@ class Surreal(private val host: String, private val port: Int = 8000) { context.launch { it.incoming.receiveAsFlow().collect { it as Frame.Text + println(it.readText()) val response = try { surrealJson.decodeFromString(RpcResponseSerializer, it.readText()) } catch (e: Exception) { @@ -96,17 +97,18 @@ class Surreal(private val host: String, private val port: Int = 8000) { val request = RpcRequest(id, method, params) val channel = Channel(1) requests[id] = channel + println(request) (connection ?: throw Exception("SurrealDB: Websocket not connected")).sendSerialized(request) return channel.receive() } - fun subscribeAsJson(liveQueryId: String): Flow> { + fun subscribeToTableAsJson(liveQueryId: String): Flow> { val channel = liveQueries.getOrPut(liveQueryId) { Channel() } return channel.receiveAsFlow() } - inline fun subscribe(liveQueryId: String): Flow> { - return subscribeAsJson(liveQueryId).map { it.asType() } + inline fun subscribeToTable(liveQueryId: String): Flow> { + return subscribeToTableAsJson(liveQueryId).map { it.asType() } } fun unsubscribe(liveQueryId: String) { diff --git a/src/commonMain/kotlin/uk/gibby/driver/annotation/SurrealDbNightlyOnlyApi.kt b/src/commonMain/kotlin/uk/gibby/driver/annotation/SurrealDbNightlyOnlyApi.kt new file mode 100644 index 0000000..cc0ee01 --- /dev/null +++ b/src/commonMain/kotlin/uk/gibby/driver/annotation/SurrealDbNightlyOnlyApi.kt @@ -0,0 +1,6 @@ +package uk.gibby.driver.annotation + +@RequiresOptIn(message = "This function is only available while using nightly builds of SurrealDB. See https://surrealdb.com/docs/installation/nightly.") +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.BINARY) +annotation class SurrealDbNightlyOnlyApi diff --git a/src/commonMain/kotlin/uk/gibby/driver/api/LiveQueryFlow.kt b/src/commonMain/kotlin/uk/gibby/driver/api/LiveQueryFlow.kt new file mode 100644 index 0000000..de05b5a --- /dev/null +++ b/src/commonMain/kotlin/uk/gibby/driver/api/LiveQueryFlow.kt @@ -0,0 +1,26 @@ +package uk.gibby.driver.api + +import io.ktor.utils.io.core.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import uk.gibby.driver.Surreal +import uk.gibby.driver.model.rpc.LiveQueryAction + +class LiveQueryFlow( + private val flow: Flow>, + val id: String, + private val connection: Surreal +): Flow> by flow, Closeable { + override fun close() { + connection.unsubscribe(id) + connection.triggerKill(id) + } + + fun map(transform: (LiveQueryAction) -> LiveQueryAction): LiveQueryFlow { + return LiveQueryFlow( + flow = flow.map { transform(it) }, + id = id, + connection = connection + ) + } +} \ No newline at end of file diff --git a/src/commonMain/kotlin/uk/gibby/driver/api/Observe.kt b/src/commonMain/kotlin/uk/gibby/driver/api/Observe.kt new file mode 100644 index 0000000..9f93bc8 --- /dev/null +++ b/src/commonMain/kotlin/uk/gibby/driver/api/Observe.kt @@ -0,0 +1,29 @@ +package uk.gibby.driver.api + +import kotlinx.serialization.json.JsonElement +import uk.gibby.driver.Surreal +import uk.gibby.driver.annotation.SurrealDbNightlyOnlyApi +import uk.gibby.driver.model.query.data +import uk.gibby.driver.model.rpc.LiveQueryAction +import uk.gibby.driver.rpc.live +import uk.gibby.driver.model.rpc.asType +import uk.gibby.driver.rpc.query +import kotlin.jvm.JvmName + +@JvmName("observeJson") +@SurrealDbNightlyOnlyApi +suspend fun Surreal.observeLiveQueryAsJson(table: String): LiveQueryFlow { + val liveQueryId = live(table) + return LiveQueryFlow( + flow = subscribeToTableAsJson(liveQueryId), + id = liveQueryId, + connection = this + ) +} + + +@SurrealDbNightlyOnlyApi +suspend inline fun Surreal.observeLiveQuery(table: String): LiveQueryFlow { + val jsonFlow = observeLiveQueryAsJson(table) + return jsonFlow.map { it.asType() } +} diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/exception/LiveQueryKilledException.kt b/src/commonMain/kotlin/uk/gibby/driver/exception/LiveQueryKilledException.kt similarity index 77% rename from src/commonMain/kotlin/uk/gibby/driver/rpc/exception/LiveQueryKilledException.kt rename to src/commonMain/kotlin/uk/gibby/driver/exception/LiveQueryKilledException.kt index 1ea775b..ee831f2 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/exception/LiveQueryKilledException.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/exception/LiveQueryKilledException.kt @@ -1,4 +1,4 @@ -package uk.gibby.driver.rpc.exception +package uk.gibby.driver.exception import kotlinx.coroutines.CancellationException diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/exception/QueryException.kt b/src/commonMain/kotlin/uk/gibby/driver/exception/QueryException.kt similarity index 73% rename from src/commonMain/kotlin/uk/gibby/driver/rpc/exception/QueryException.kt rename to src/commonMain/kotlin/uk/gibby/driver/exception/QueryException.kt index 2b200bb..934be77 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/exception/QueryException.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/exception/QueryException.kt @@ -1,3 +1,3 @@ -package uk.gibby.driver.rpc.exception +package uk.gibby.driver.exception data class QueryException(val detail: String): Exception("SurrealDB responded with an error: '$detail'") \ No newline at end of file diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/model/Patch.kt b/src/commonMain/kotlin/uk/gibby/driver/model/JsonPatch.kt similarity index 98% rename from src/commonMain/kotlin/uk/gibby/driver/rpc/model/Patch.kt rename to src/commonMain/kotlin/uk/gibby/driver/model/JsonPatch.kt index e9d91f4..fa68287 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/model/Patch.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/model/JsonPatch.kt @@ -1,4 +1,4 @@ -package uk.gibby.driver.rpc.model +package uk.gibby.driver.model import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/model/RootAuth.kt b/src/commonMain/kotlin/uk/gibby/driver/model/RootAuth.kt similarity index 85% rename from src/commonMain/kotlin/uk/gibby/driver/rpc/model/RootAuth.kt rename to src/commonMain/kotlin/uk/gibby/driver/model/RootAuth.kt index 3e8899c..0a77e54 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/model/RootAuth.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/model/RootAuth.kt @@ -1,4 +1,4 @@ -package uk.gibby.driver.rpc.model +package uk.gibby.driver.model import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonElement diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/model/Thing.kt b/src/commonMain/kotlin/uk/gibby/driver/model/Thing.kt similarity index 98% rename from src/commonMain/kotlin/uk/gibby/driver/rpc/model/Thing.kt rename to src/commonMain/kotlin/uk/gibby/driver/model/Thing.kt index be96f7d..3683c86 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/model/Thing.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/model/Thing.kt @@ -1,4 +1,4 @@ -package uk.gibby.driver.rpc.model +package uk.gibby.driver.model import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.KSerializer diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/model/Bind.kt b/src/commonMain/kotlin/uk/gibby/driver/model/query/Bind.kt similarity index 95% rename from src/commonMain/kotlin/uk/gibby/driver/rpc/model/Bind.kt rename to src/commonMain/kotlin/uk/gibby/driver/model/query/Bind.kt index c5d6ab8..0a5a442 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/model/Bind.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/model/query/Bind.kt @@ -1,4 +1,4 @@ -package uk.gibby.driver.rpc.model +package uk.gibby.driver.model.query import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.encodeToJsonElement diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/model/QueryResponse.kt b/src/commonMain/kotlin/uk/gibby/driver/model/query/QueryResponse.kt similarity index 90% rename from src/commonMain/kotlin/uk/gibby/driver/rpc/model/QueryResponse.kt rename to src/commonMain/kotlin/uk/gibby/driver/model/query/QueryResponse.kt index cf38fe5..e2e23be 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/model/QueryResponse.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/model/query/QueryResponse.kt @@ -1,10 +1,10 @@ -package uk.gibby.driver.rpc.model +package uk.gibby.driver.model.query import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.decodeFromJsonElement -import uk.gibby.driver.rpc.exception.QueryException +import uk.gibby.driver.exception.QueryException import uk.gibby.driver.surrealJson @Serializable diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/model/LiveQueryAction.kt b/src/commonMain/kotlin/uk/gibby/driver/model/rpc/LiveQueryAction.kt similarity index 78% rename from src/commonMain/kotlin/uk/gibby/driver/rpc/model/LiveQueryAction.kt rename to src/commonMain/kotlin/uk/gibby/driver/model/rpc/LiveQueryAction.kt index 40b2546..50f289f 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/model/LiveQueryAction.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/model/rpc/LiveQueryAction.kt @@ -1,6 +1,8 @@ -package uk.gibby.driver.rpc.model +package uk.gibby.driver.model.rpc -import kotlinx.serialization.* +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.SerializationException import kotlinx.serialization.builtins.serializer import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.buildClassSerialDescriptor @@ -9,18 +11,22 @@ import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.decodeFromJsonElement +import uk.gibby.driver.api.LiveQueryFlow +import uk.gibby.driver.model.Thing +import uk.gibby.driver.model.ThingSerializer import uk.gibby.driver.surrealJson -@Serializable(with = LiveQueryActionSerializer::class) -sealed class LiveQueryAction(val id: String) { - class Create(id: String, val result: T): LiveQueryAction(id) - class Update(id: String, val result: T): LiveQueryAction(id) - class Delete(id: String, val deletedId: String): LiveQueryAction(id) +@Serializable(with = LiveQueryActionSerializer::class) +sealed class LiveQueryAction { + abstract val id: String + data class Create(override val id: String, val result: T): LiveQueryAction() + data class Update(override val id: String, val result: T): LiveQueryAction() + data class Delete(override val id: String, val deletedId: String): LiveQueryAction() } -inline fun LiveQueryAction.asType(): LiveQueryAction { +inline fun LiveQueryAction.asType(): LiveQueryAction { return when(this) { is LiveQueryAction.Delete -> this is LiveQueryAction.Create -> LiveQueryAction.Create(id, surrealJson.decodeFromJsonElement(result)) @@ -63,4 +69,5 @@ class LiveQueryActionSerializer(private val resultSerializer: KSerialize TODO("Not yet implemented") } -} \ No newline at end of file +} + diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/model/RpcRequest.kt b/src/commonMain/kotlin/uk/gibby/driver/model/rpc/RpcRequest.kt similarity index 84% rename from src/commonMain/kotlin/uk/gibby/driver/rpc/model/RpcRequest.kt rename to src/commonMain/kotlin/uk/gibby/driver/model/rpc/RpcRequest.kt index fbe933f..5e55258 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/model/RpcRequest.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/model/rpc/RpcRequest.kt @@ -1,4 +1,4 @@ -package uk.gibby.driver.rpc.model +package uk.gibby.driver.model.rpc import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonArray diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/model/RpcResponse.kt b/src/commonMain/kotlin/uk/gibby/driver/model/rpc/RpcResponse.kt similarity index 94% rename from src/commonMain/kotlin/uk/gibby/driver/rpc/model/RpcResponse.kt rename to src/commonMain/kotlin/uk/gibby/driver/model/rpc/RpcResponse.kt index 0270832..c74c96f 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/model/RpcResponse.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/model/rpc/RpcResponse.kt @@ -1,4 +1,4 @@ -package uk.gibby.driver.rpc.model +package uk.gibby.driver.model.rpc import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonElement diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/model/RpcResponseSerializer.kt b/src/commonMain/kotlin/uk/gibby/driver/model/rpc/RpcResponseSerializer.kt similarity index 98% rename from src/commonMain/kotlin/uk/gibby/driver/rpc/model/RpcResponseSerializer.kt rename to src/commonMain/kotlin/uk/gibby/driver/model/rpc/RpcResponseSerializer.kt index 4f44719..c24de29 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/model/RpcResponseSerializer.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/model/rpc/RpcResponseSerializer.kt @@ -1,4 +1,4 @@ -package uk.gibby.driver.rpc.model +package uk.gibby.driver.model.rpc import uk.gibby.driver.surrealJson import kotlinx.serialization.KSerializer diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Authenticate.kt b/src/commonMain/kotlin/uk/gibby/driver/rpc/Authenticate.kt similarity index 91% rename from src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Authenticate.kt rename to src/commonMain/kotlin/uk/gibby/driver/rpc/Authenticate.kt index 89bf017..a73ca80 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Authenticate.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/rpc/Authenticate.kt @@ -1,4 +1,4 @@ -package uk.gibby.driver.rpc.functions +package uk.gibby.driver.rpc import kotlinx.serialization.json.add import kotlinx.serialization.json.buildJsonArray diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Create.kt b/src/commonMain/kotlin/uk/gibby/driver/rpc/Create.kt similarity index 98% rename from src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Create.kt rename to src/commonMain/kotlin/uk/gibby/driver/rpc/Create.kt index 894fcff..f432985 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Create.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/rpc/Create.kt @@ -1,4 +1,4 @@ -package uk.gibby.driver.rpc.functions +package uk.gibby.driver.rpc import kotlinx.serialization.json.* import uk.gibby.driver.Surreal diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Delete.kt b/src/commonMain/kotlin/uk/gibby/driver/rpc/Delete.kt similarity index 94% rename from src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Delete.kt rename to src/commonMain/kotlin/uk/gibby/driver/rpc/Delete.kt index 69e6b69..6a276da 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Delete.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/rpc/Delete.kt @@ -1,4 +1,4 @@ -package uk.gibby.driver.rpc.functions +package uk.gibby.driver.rpc import kotlinx.serialization.json.add import kotlinx.serialization.json.buildJsonArray diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Info.kt b/src/commonMain/kotlin/uk/gibby/driver/rpc/Info.kt similarity index 95% rename from src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Info.kt rename to src/commonMain/kotlin/uk/gibby/driver/rpc/Info.kt index b953a3e..289d3bf 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Info.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/rpc/Info.kt @@ -1,4 +1,4 @@ -package uk.gibby.driver.rpc.functions +package uk.gibby.driver.rpc import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.buildJsonArray diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Invalidate.kt b/src/commonMain/kotlin/uk/gibby/driver/rpc/Invalidate.kt similarity index 87% rename from src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Invalidate.kt rename to src/commonMain/kotlin/uk/gibby/driver/rpc/Invalidate.kt index 6ef1e1c..1556576 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Invalidate.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/rpc/Invalidate.kt @@ -1,4 +1,4 @@ -package uk.gibby.driver.rpc.functions +package uk.gibby.driver.rpc import kotlinx.serialization.json.JsonArray import uk.gibby.driver.Surreal diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Kill.kt b/src/commonMain/kotlin/uk/gibby/driver/rpc/Kill.kt similarity index 85% rename from src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Kill.kt rename to src/commonMain/kotlin/uk/gibby/driver/rpc/Kill.kt index b2e642c..9b9b89a 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Kill.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/rpc/Kill.kt @@ -1,4 +1,4 @@ -package uk.gibby.driver.rpc.functions +package uk.gibby.driver.rpc import kotlinx.serialization.json.add import kotlinx.serialization.json.buildJsonArray diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Let.kt b/src/commonMain/kotlin/uk/gibby/driver/rpc/Let.kt similarity index 96% rename from src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Let.kt rename to src/commonMain/kotlin/uk/gibby/driver/rpc/Let.kt index d8e8dc3..a647bd5 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Let.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/rpc/Let.kt @@ -1,4 +1,4 @@ -package uk.gibby.driver.rpc.functions +package uk.gibby.driver.rpc import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.add diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/Live.kt b/src/commonMain/kotlin/uk/gibby/driver/rpc/Live.kt new file mode 100644 index 0000000..603edde --- /dev/null +++ b/src/commonMain/kotlin/uk/gibby/driver/rpc/Live.kt @@ -0,0 +1,28 @@ +package uk.gibby.driver.rpc + +import kotlinx.serialization.json.add +import kotlinx.serialization.json.buildJsonArray +import kotlinx.serialization.json.decodeFromJsonElement +import uk.gibby.driver.Surreal +import uk.gibby.driver.annotation.SurrealDbNightlyOnlyApi +import uk.gibby.driver.model.Thing +import uk.gibby.driver.surrealJson + + +@SurrealDbNightlyOnlyApi +suspend fun Surreal.live(table: String): String { + val response = sendRequest("live", buildJsonArray { add(table) }) + return surrealJson.decodeFromJsonElement(response) +} + +@SurrealDbNightlyOnlyApi +suspend fun Surreal.live(table: String, id: String): String { + val response = sendRequest("live", buildJsonArray { add("$table:$id") }) + return surrealJson.decodeFromJsonElement(response) +} + +@SurrealDbNightlyOnlyApi +suspend fun Surreal.live(id: Thing<*>): String { + val response = sendRequest("live", buildJsonArray { add(id.id) }) + return surrealJson.decodeFromJsonElement(response) +} \ No newline at end of file diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Query.kt b/src/commonMain/kotlin/uk/gibby/driver/rpc/Query.kt similarity index 89% rename from src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Query.kt rename to src/commonMain/kotlin/uk/gibby/driver/rpc/Query.kt index 446112e..7f0697d 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Query.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/rpc/Query.kt @@ -1,9 +1,9 @@ -package uk.gibby.driver.rpc.functions +package uk.gibby.driver.rpc import kotlinx.serialization.json.* import uk.gibby.driver.Surreal -import uk.gibby.driver.rpc.model.Bind -import uk.gibby.driver.rpc.model.QueryResponse +import uk.gibby.driver.model.query.Bind +import uk.gibby.driver.model.query.QueryResponse import uk.gibby.driver.surrealJson /** diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Select.kt b/src/commonMain/kotlin/uk/gibby/driver/rpc/Select.kt similarity index 97% rename from src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Select.kt rename to src/commonMain/kotlin/uk/gibby/driver/rpc/Select.kt index 98e77f4..b4646e1 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Select.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/rpc/Select.kt @@ -1,4 +1,4 @@ -package uk.gibby.driver.rpc.functions +package uk.gibby.driver.rpc import kotlinx.serialization.json.* import uk.gibby.driver.Surreal diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/SignIn.kt b/src/commonMain/kotlin/uk/gibby/driver/rpc/SignIn.kt similarity index 93% rename from src/commonMain/kotlin/uk/gibby/driver/rpc/functions/SignIn.kt rename to src/commonMain/kotlin/uk/gibby/driver/rpc/SignIn.kt index fac8b24..2fc9a48 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/SignIn.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/rpc/SignIn.kt @@ -1,9 +1,9 @@ -package uk.gibby.driver.rpc.functions +package uk.gibby.driver.rpc import kotlinx.serialization.json.* import uk.gibby.driver.Surreal -import uk.gibby.driver.rpc.model.Bind -import uk.gibby.driver.rpc.model.RootAuth +import uk.gibby.driver.model.query.Bind +import uk.gibby.driver.model.RootAuth import uk.gibby.driver.surrealJson diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Signup.kt b/src/commonMain/kotlin/uk/gibby/driver/rpc/Signup.kt similarity index 94% rename from src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Signup.kt rename to src/commonMain/kotlin/uk/gibby/driver/rpc/Signup.kt index 9c811d8..860d2ef 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Signup.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/rpc/Signup.kt @@ -1,8 +1,8 @@ -package uk.gibby.driver.rpc.functions +package uk.gibby.driver.rpc import kotlinx.serialization.json.* import uk.gibby.driver.Surreal -import uk.gibby.driver.rpc.model.Bind +import uk.gibby.driver.model.query.Bind import uk.gibby.driver.surrealJson diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Unset.kt b/src/commonMain/kotlin/uk/gibby/driver/rpc/Unset.kt similarity index 90% rename from src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Unset.kt rename to src/commonMain/kotlin/uk/gibby/driver/rpc/Unset.kt index f3f66a6..9ef9acc 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Unset.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/rpc/Unset.kt @@ -1,4 +1,4 @@ -package uk.gibby.driver.rpc.functions +package uk.gibby.driver.rpc import kotlinx.serialization.json.add import kotlinx.serialization.json.buildJsonArray diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Update.kt b/src/commonMain/kotlin/uk/gibby/driver/rpc/Update.kt similarity index 98% rename from src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Update.kt rename to src/commonMain/kotlin/uk/gibby/driver/rpc/Update.kt index cc78eb2..3029a15 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Update.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/rpc/Update.kt @@ -1,9 +1,9 @@ -package uk.gibby.driver.rpc.functions +package uk.gibby.driver.rpc import kotlinx.serialization.json.* import uk.gibby.driver.Surreal -import uk.gibby.driver.rpc.model.Bind -import uk.gibby.driver.rpc.model.JsonPatch +import uk.gibby.driver.model.query.Bind +import uk.gibby.driver.model.JsonPatch import uk.gibby.driver.surrealJson import kotlin.jvm.JvmName diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Use.kt b/src/commonMain/kotlin/uk/gibby/driver/rpc/Use.kt similarity index 91% rename from src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Use.kt rename to src/commonMain/kotlin/uk/gibby/driver/rpc/Use.kt index 1782248..722adf5 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Use.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/rpc/Use.kt @@ -1,4 +1,4 @@ -package uk.gibby.driver.rpc.functions +package uk.gibby.driver.rpc import kotlinx.serialization.json.add import kotlinx.serialization.json.buildJsonArray diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Live.kt b/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Live.kt deleted file mode 100644 index 342c1a7..0000000 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/functions/Live.kt +++ /dev/null @@ -1,56 +0,0 @@ -package uk.gibby.driver.rpc.functions - -import io.ktor.utils.io.core.* -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.receiveAsFlow -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.add -import kotlinx.serialization.json.buildJsonArray -import kotlinx.serialization.json.decodeFromJsonElement -import uk.gibby.driver.Surreal -import uk.gibby.driver.rpc.model.LiveQueryAction -import uk.gibby.driver.rpc.model.asType -import uk.gibby.driver.surrealJson -import kotlin.jvm.JvmName - - -suspend fun Surreal.live(table: String): String { - val response = sendRequest("live", buildJsonArray { add(table) }) - return surrealJson.decodeFromJsonElement(response) -} - - -@JvmName("observeJson") -suspend fun Surreal.observeAsJson(liveQueryId: String): LiveQueryFlow { - val id = live(liveQueryId) - return LiveQueryFlow( - flow = subscribe(id), - id = id, - connection = this - ) -} - -suspend inline fun Surreal.observe(table: String): LiveQueryFlow { - val jsonFlow = observeAsJson(table) - return jsonFlow.map { it.asType() } -} - -class LiveQueryFlow( - private val flow: Flow>, - val id: String, - private val connection: Surreal -): Flow> by flow, Closeable { - override fun close() { - connection.unsubscribe(id) - connection.triggerKill(id) - } - - fun map(transform: (LiveQueryAction) -> LiveQueryAction): LiveQueryFlow { - return LiveQueryFlow( - flow = flow.map { transform(it) }, - id = id, - connection = connection - ) - } -} \ No newline at end of file diff --git a/src/commonTest/kotlin/AuthenticateTest.kt b/src/commonTest/kotlin/AuthenticateTest.kt index 84af7b5..3e4028c 100644 --- a/src/commonTest/kotlin/AuthenticateTest.kt +++ b/src/commonTest/kotlin/AuthenticateTest.kt @@ -1,9 +1,9 @@ import kotlinx.coroutines.test.runTest import uk.gibby.driver.Surreal -import uk.gibby.driver.rpc.functions.authenticate -import uk.gibby.driver.rpc.functions.info -import uk.gibby.driver.rpc.functions.signup -import uk.gibby.driver.rpc.model.bind +import uk.gibby.driver.rpc.authenticate +import uk.gibby.driver.rpc.info +import uk.gibby.driver.rpc.signup +import uk.gibby.driver.model.query.bind import utils.cleanDatabase import kotlin.test.Test import kotlin.test.assertEquals diff --git a/src/commonTest/kotlin/ConnectionTest.kt b/src/commonTest/kotlin/ConnectionTest.kt index 8f2b8d5..1e6f944 100644 --- a/src/commonTest/kotlin/ConnectionTest.kt +++ b/src/commonTest/kotlin/ConnectionTest.kt @@ -1,7 +1,8 @@ import kotlinx.coroutines.test.runTest import uk.gibby.driver.Surreal -import uk.gibby.driver.rpc.functions.* -import uk.gibby.driver.rpc.model.bind +import uk.gibby.driver.model.query.bind +import uk.gibby.driver.rpc.* +import kotlin.test.Ignore import kotlin.test.Test class ConnectionTest { diff --git a/src/commonTest/kotlin/CreateTest.kt b/src/commonTest/kotlin/CreateTest.kt index 1f33611..1c4388b 100644 --- a/src/commonTest/kotlin/CreateTest.kt +++ b/src/commonTest/kotlin/CreateTest.kt @@ -1,21 +1,37 @@ import kotlinx.coroutines.test.runTest import kotlinx.serialization.Serializable import uk.gibby.driver.Surreal -import uk.gibby.driver.rpc.functions.* -import uk.gibby.driver.rpc.model.Thing -import uk.gibby.driver.rpc.model.data -import uk.gibby.driver.rpc.model.unknown +import uk.gibby.driver.model.Thing +import uk.gibby.driver.model.query.data +import uk.gibby.driver.model.unknown +import uk.gibby.driver.rpc.create +import uk.gibby.driver.rpc.query +import uk.gibby.driver.rpc.signin +import uk.gibby.driver.rpc.use import utils.cleanDatabase import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue @Serializable -data class TestClass( +class TestClass( val myText: String, val myNumber: Int, val id: Thing = unknown(), -) +) { + override fun equals(other: Any?): Boolean { + if (other !is TestClass) return false + val idCheck = (id.id == other.id.id || id.id == "unknown" || other.id.id == "unknown") + return myText == other.myText && myNumber == other.myNumber && idCheck + } + + override fun hashCode(): Int { + var result = myText.hashCode() + result = 31 * result + myNumber + result = 31 * result + id.hashCode() + return result + } +} class CreateTest { diff --git a/src/commonTest/kotlin/DeleteTest.kt b/src/commonTest/kotlin/DeleteTest.kt index b779eb9..3883d27 100644 --- a/src/commonTest/kotlin/DeleteTest.kt +++ b/src/commonTest/kotlin/DeleteTest.kt @@ -1,6 +1,6 @@ import kotlinx.coroutines.test.runTest import uk.gibby.driver.Surreal -import uk.gibby.driver.rpc.functions.* +import uk.gibby.driver.rpc.* import utils.cleanDatabase import kotlin.test.Test import kotlin.test.assertEquals diff --git a/src/commonTest/kotlin/FetchTest.kt b/src/commonTest/kotlin/FetchTest.kt index 1edf555..63c043a 100644 --- a/src/commonTest/kotlin/FetchTest.kt +++ b/src/commonTest/kotlin/FetchTest.kt @@ -1,9 +1,12 @@ import kotlinx.coroutines.test.runTest import kotlinx.serialization.Serializable import uk.gibby.driver.Surreal -import uk.gibby.driver.rpc.functions.* -import uk.gibby.driver.rpc.model.Thing -import uk.gibby.driver.rpc.model.data +import uk.gibby.driver.model.Thing +import uk.gibby.driver.model.query.data +import uk.gibby.driver.rpc.create +import uk.gibby.driver.rpc.query +import uk.gibby.driver.rpc.signin +import uk.gibby.driver.rpc.use import utils.cleanDatabase import kotlin.test.Test import kotlin.test.assertEquals diff --git a/src/commonTest/kotlin/InfoTest.kt b/src/commonTest/kotlin/InfoTest.kt index 1fb393a..8e93eb0 100644 --- a/src/commonTest/kotlin/InfoTest.kt +++ b/src/commonTest/kotlin/InfoTest.kt @@ -1,11 +1,11 @@ import kotlinx.coroutines.test.runTest import kotlinx.serialization.Serializable import uk.gibby.driver.Surreal -import uk.gibby.driver.rpc.functions.info -import uk.gibby.driver.rpc.functions.signup -import uk.gibby.driver.rpc.model.Thing -import uk.gibby.driver.rpc.model.bind -import uk.gibby.driver.rpc.model.unknown +import uk.gibby.driver.rpc.info +import uk.gibby.driver.rpc.signup +import uk.gibby.driver.model.Thing +import uk.gibby.driver.model.query.bind +import uk.gibby.driver.model.unknown import utils.cleanDatabase import kotlin.test.Test import kotlin.test.assertEquals diff --git a/src/commonTest/kotlin/KillTest.kt b/src/commonTest/kotlin/KillTest.kt index 32427ae..f14de4b 100644 --- a/src/commonTest/kotlin/KillTest.kt +++ b/src/commonTest/kotlin/KillTest.kt @@ -1,13 +1,16 @@ import kotlinx.coroutines.test.runTest import uk.gibby.driver.Surreal -import uk.gibby.driver.rpc.functions.kill -import uk.gibby.driver.rpc.functions.live -import uk.gibby.driver.rpc.functions.signin -import uk.gibby.driver.rpc.functions.use +import uk.gibby.driver.annotation.SurrealDbNightlyOnlyApi +import uk.gibby.driver.rpc.kill +import uk.gibby.driver.rpc.live +import uk.gibby.driver.rpc.signin +import uk.gibby.driver.rpc.use import utils.cleanDatabase +import kotlin.test.Ignore import kotlin.test.Test class KillTest { + @OptIn(SurrealDbNightlyOnlyApi::class) @Test fun testKill() = runTest { cleanDatabase() diff --git a/src/commonTest/kotlin/LetTest.kt b/src/commonTest/kotlin/LetTest.kt index 290ffc5..571ba3a 100644 --- a/src/commonTest/kotlin/LetTest.kt +++ b/src/commonTest/kotlin/LetTest.kt @@ -1,10 +1,10 @@ import kotlinx.coroutines.test.runTest import uk.gibby.driver.Surreal -import uk.gibby.driver.rpc.functions.let -import uk.gibby.driver.rpc.functions.query -import uk.gibby.driver.rpc.functions.signin -import uk.gibby.driver.rpc.functions.use -import uk.gibby.driver.rpc.model.data +import uk.gibby.driver.rpc.let +import uk.gibby.driver.rpc.query +import uk.gibby.driver.rpc.signin +import uk.gibby.driver.rpc.use +import uk.gibby.driver.model.query.data import utils.cleanDatabase import kotlin.test.Test import kotlin.test.assertEquals diff --git a/src/commonTest/kotlin/LiveQueryTest.kt b/src/commonTest/kotlin/LiveQueryTest.kt index 5dbc770..ed3bda2 100644 --- a/src/commonTest/kotlin/LiveQueryTest.kt +++ b/src/commonTest/kotlin/LiveQueryTest.kt @@ -1,24 +1,27 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import uk.gibby.driver.Surreal -import uk.gibby.driver.rpc.exception.LiveQueryKilledException -import uk.gibby.driver.rpc.functions.* -import uk.gibby.driver.rpc.model.LiveQueryAction -import uk.gibby.driver.rpc.model.bind -import uk.gibby.driver.rpc.model.data +import uk.gibby.driver.annotation.SurrealDbNightlyOnlyApi +import uk.gibby.driver.api.observeLiveQuery +import uk.gibby.driver.exception.LiveQueryKilledException +import uk.gibby.driver.model.rpc.LiveQueryAction +import uk.gibby.driver.model.query.bind +import uk.gibby.driver.model.query.data +import uk.gibby.driver.rpc.* import utils.cleanDatabase import kotlin.test.* +@OptIn(SurrealDbNightlyOnlyApi::class) class LiveQueryTest { @Test - fun testLiveQuery() = runTest { + fun testObserve() = runTest { cleanDatabase() val connection = Surreal("localhost", 8000) connection.connect() connection.signin("root", "root") connection.use("test", "test") - val incoming = connection.observe("test") + val incoming = connection.observeLiveQuery("test") connection.create("test", "first").content(TestClass("thing", 1)) connection.create("test", "second").content(TestClass("thing", 2)) connection.update("test", "first").patch { @@ -26,28 +29,15 @@ class LiveQueryTest { } connection.delete("test", "second") - val first = incoming.first() - assertTrue { first is LiveQueryAction.Create } - first as LiveQueryAction.Create - assertEquals("thing", first.result.myText) - assertEquals(1, first.result.myNumber) - - val second = incoming.first() - assertTrue { second is LiveQueryAction.Create } - second as LiveQueryAction.Create - assertEquals("thing", second.result.myText) - assertEquals(2, second.result.myNumber) - - val updated = incoming.first() - assertTrue { updated is LiveQueryAction.Update } - updated as LiveQueryAction.Update - assertEquals("thing2", updated.result.myText) - assertEquals(1, updated.result.myNumber) - - val deleted = incoming.first() - assertTrue { deleted is LiveQueryAction.Delete } - deleted as LiveQueryAction.Delete - assertEquals("test:second", deleted.deletedId) + val all = List(4) { incoming.first() } + assertEquals(4, all.size) + val shouldReceive = listOf( + LiveQueryAction.Create(incoming.id, TestClass("thing", 1)), + LiveQueryAction.Create(incoming.id, TestClass("thing", 2)), + LiveQueryAction.Update(incoming.id, TestClass("thing2", 1)), + LiveQueryAction.Delete(incoming.id, "test:second") + ) + shouldReceive.forEach { assertContains(all, it) } incoming.close() assertFailsWith { @@ -64,7 +54,7 @@ class LiveQueryTest { connection.use("test", "test") val result = connection.query("LIVE SELECT * FROM test;") val liveQueryId = result.first().data() - val incoming = connection.subscribe(liveQueryId) + val incoming = connection.subscribeToTable(liveQueryId) connection.create("test", "first").content(TestClass("thing", 1)) connection.create("test", "second").content(TestClass("thing", 2)) @@ -73,28 +63,15 @@ class LiveQueryTest { } connection.delete("test", "second") - val first = incoming.first() - assertTrue { first is LiveQueryAction.Create } - first as LiveQueryAction.Create - assertEquals("thing", first.result.myText) - assertEquals(1, first.result.myNumber) - - val second = incoming.first() - assertTrue { second is LiveQueryAction.Create } - second as LiveQueryAction.Create - assertEquals("thing", second.result.myText) - assertEquals(2, second.result.myNumber) - - val updated = incoming.first() - assertTrue { updated is LiveQueryAction.Update } - updated as LiveQueryAction.Update - assertEquals("thing2", updated.result.myText) - assertEquals(1, updated.result.myNumber) - - val deleted = incoming.first() - assertTrue { deleted is LiveQueryAction.Delete } - deleted as LiveQueryAction.Delete - assertEquals("test:second", deleted.deletedId) + val all = List(4) { incoming.first() } + assertEquals(4, all.size) + val shouldReceive = listOf( + LiveQueryAction.Create(liveQueryId, TestClass("thing", 1)), + LiveQueryAction.Create(liveQueryId, TestClass("thing", 2)), + LiveQueryAction.Update(liveQueryId, TestClass("thing2", 1)), + LiveQueryAction.Delete(liveQueryId, "test:second") + ) + shouldReceive.forEach { assertContains(all, it) } connection.query("KILL \$liveQueryId;", bind("liveQueryId", liveQueryId)) connection.unsubscribe(liveQueryId) @@ -111,7 +88,7 @@ class LiveQueryTest { connection.signin("root", "root") connection.use("test", "test") val liveQueryId = connection.live("test") - val incoming = connection.subscribe(liveQueryId) + val incoming = connection.subscribeToTable(liveQueryId) connection.create("test", "first").content(TestClass("thing", 1)) connection.create("test", "second").content(TestClass("thing", 2)) @@ -120,28 +97,15 @@ class LiveQueryTest { } connection.delete("test", "second") - val first = incoming.first() - assertTrue { first is LiveQueryAction.Create } - first as LiveQueryAction.Create - assertEquals("thing", first.result.myText) - assertEquals(1, first.result.myNumber) - - val second = incoming.first() - assertTrue { second is LiveQueryAction.Create } - second as LiveQueryAction.Create - assertEquals("thing", second.result.myText) - assertEquals(2, second.result.myNumber) - - val updated = incoming.first() - assertTrue { updated is LiveQueryAction.Update } - updated as LiveQueryAction.Update - assertEquals("thing2", updated.result.myText) - assertEquals(1, updated.result.myNumber) - - val deleted = incoming.first() - assertTrue { deleted is LiveQueryAction.Delete } - deleted as LiveQueryAction.Delete - assertEquals("test:second", deleted.deletedId) + val all = List(4) { incoming.first() } + assertEquals(4, all.size) + val shouldReceive = listOf( + LiveQueryAction.Create(liveQueryId, TestClass("thing", 1)), + LiveQueryAction.Create(liveQueryId, TestClass("thing", 2)), + LiveQueryAction.Update(liveQueryId, TestClass("thing2", 1)), + LiveQueryAction.Delete(liveQueryId, "test:second") + ) + shouldReceive.forEach { assertContains(all, it) } connection.kill(liveQueryId) connection.unsubscribe(liveQueryId) @@ -149,5 +113,4 @@ class LiveQueryTest { incoming.first() } } - } \ No newline at end of file diff --git a/src/commonTest/kotlin/MergeTest.kt b/src/commonTest/kotlin/MergeTest.kt index 52d2bc6..bccc616 100644 --- a/src/commonTest/kotlin/MergeTest.kt +++ b/src/commonTest/kotlin/MergeTest.kt @@ -1,7 +1,10 @@ import kotlinx.coroutines.test.runTest import uk.gibby.driver.Surreal -import uk.gibby.driver.rpc.functions.* -import uk.gibby.driver.rpc.model.bind +import uk.gibby.driver.model.query.bind +import uk.gibby.driver.rpc.create +import uk.gibby.driver.rpc.signin +import uk.gibby.driver.rpc.update +import uk.gibby.driver.rpc.use import utils.cleanDatabase import kotlin.test.Test import kotlin.test.assertEquals diff --git a/src/commonTest/kotlin/PatchTest.kt b/src/commonTest/kotlin/PatchTest.kt index 908d79e..cbc5515 100644 --- a/src/commonTest/kotlin/PatchTest.kt +++ b/src/commonTest/kotlin/PatchTest.kt @@ -1,5 +1,6 @@ import kotlinx.coroutines.test.runTest import uk.gibby.driver.Surreal +import uk.gibby.driver.rpc.* import uk.gibby.driver.rpc.functions.* import utils.cleanDatabase import kotlin.test.Test diff --git a/src/commonTest/kotlin/QueryTest.kt b/src/commonTest/kotlin/QueryTest.kt index b509508..6e6afad 100644 --- a/src/commonTest/kotlin/QueryTest.kt +++ b/src/commonTest/kotlin/QueryTest.kt @@ -1,10 +1,10 @@ import kotlinx.coroutines.test.runTest import uk.gibby.driver.Surreal -import uk.gibby.driver.rpc.functions.query -import uk.gibby.driver.rpc.functions.signin -import uk.gibby.driver.rpc.functions.use -import uk.gibby.driver.rpc.model.bind -import uk.gibby.driver.rpc.model.data +import uk.gibby.driver.rpc.query +import uk.gibby.driver.rpc.signin +import uk.gibby.driver.rpc.use +import uk.gibby.driver.model.query.bind +import uk.gibby.driver.model.query.data import utils.cleanDatabase import kotlin.test.Test import kotlin.test.assertEquals diff --git a/src/commonTest/kotlin/SelectTest.kt b/src/commonTest/kotlin/SelectTest.kt index 9d01d6e..fef3253 100644 --- a/src/commonTest/kotlin/SelectTest.kt +++ b/src/commonTest/kotlin/SelectTest.kt @@ -1,6 +1,10 @@ import kotlinx.coroutines.test.runTest import uk.gibby.driver.Surreal +import uk.gibby.driver.rpc.create import uk.gibby.driver.rpc.functions.* +import uk.gibby.driver.rpc.select +import uk.gibby.driver.rpc.signin +import uk.gibby.driver.rpc.use import utils.cleanDatabase import kotlin.test.Test import kotlin.test.assertEquals diff --git a/src/commonTest/kotlin/SignInTest.kt b/src/commonTest/kotlin/SignInTest.kt index 7c9c3ec..11f10df 100644 --- a/src/commonTest/kotlin/SignInTest.kt +++ b/src/commonTest/kotlin/SignInTest.kt @@ -1,7 +1,9 @@ import kotlinx.coroutines.test.runTest import uk.gibby.driver.Surreal -import uk.gibby.driver.rpc.functions.* -import uk.gibby.driver.rpc.model.bind +import uk.gibby.driver.model.query.bind +import uk.gibby.driver.rpc.invalidate +import uk.gibby.driver.rpc.signin +import uk.gibby.driver.rpc.signup import utils.cleanDatabase import kotlin.test.Test diff --git a/src/commonTest/kotlin/SignUpTest.kt b/src/commonTest/kotlin/SignUpTest.kt index 908dc26..1af5be4 100644 --- a/src/commonTest/kotlin/SignUpTest.kt +++ b/src/commonTest/kotlin/SignUpTest.kt @@ -1,7 +1,7 @@ import kotlinx.coroutines.test.runTest import uk.gibby.driver.Surreal -import uk.gibby.driver.rpc.functions.* -import uk.gibby.driver.rpc.model.bind +import uk.gibby.driver.model.query.bind +import uk.gibby.driver.rpc.* import utils.cleanDatabase import kotlin.jvm.JvmStatic import kotlin.test.Test diff --git a/src/commonTest/kotlin/UnsetTest.kt b/src/commonTest/kotlin/UnsetTest.kt index c8d20de..0606b18 100644 --- a/src/commonTest/kotlin/UnsetTest.kt +++ b/src/commonTest/kotlin/UnsetTest.kt @@ -1,7 +1,7 @@ import kotlinx.coroutines.test.runTest import uk.gibby.driver.Surreal -import uk.gibby.driver.rpc.functions.* -import uk.gibby.driver.rpc.model.data +import uk.gibby.driver.model.query.data +import uk.gibby.driver.rpc.* import utils.cleanDatabase import kotlin.test.Test import kotlin.test.assertEquals diff --git a/src/commonTest/kotlin/UpdateTest.kt b/src/commonTest/kotlin/UpdateTest.kt index a31a030..3fa4a32 100644 --- a/src/commonTest/kotlin/UpdateTest.kt +++ b/src/commonTest/kotlin/UpdateTest.kt @@ -1,6 +1,10 @@ import kotlinx.coroutines.test.runTest import uk.gibby.driver.Surreal +import uk.gibby.driver.rpc.create import uk.gibby.driver.rpc.functions.* +import uk.gibby.driver.rpc.signin +import uk.gibby.driver.rpc.update +import uk.gibby.driver.rpc.use import utils.cleanDatabase import kotlin.test.Test import kotlin.test.assertEquals diff --git a/src/commonTest/kotlin/utils/CleanDatabase.kt b/src/commonTest/kotlin/utils/CleanDatabase.kt index 27b299c..250a2bf 100644 --- a/src/commonTest/kotlin/utils/CleanDatabase.kt +++ b/src/commonTest/kotlin/utils/CleanDatabase.kt @@ -1,10 +1,10 @@ package utils import uk.gibby.driver.Surreal -import uk.gibby.driver.rpc.functions.invalidate -import uk.gibby.driver.rpc.functions.query -import uk.gibby.driver.rpc.functions.signin -import uk.gibby.driver.rpc.functions.use +import uk.gibby.driver.rpc.invalidate +import uk.gibby.driver.rpc.query +import uk.gibby.driver.rpc.signin +import uk.gibby.driver.rpc.use suspend fun cleanDatabase() { val connection = Surreal("localhost") From ce4ee276e54339cfe4d1bc18583ed89f7681ebd1 Mon Sep 17 00:00:00 2001 From: mnbjhu Date: Wed, 23 Aug 2023 19:03:51 +0100 Subject: [PATCH 4/5] Solving conflicts --- src/commonMain/kotlin/uk/gibby/driver/rpc/Delete.kt | 2 +- src/commonMain/kotlin/uk/gibby/driver/rpc/Select.kt | 2 +- src/commonTest/kotlin/PatchTest.kt | 1 - src/commonTest/kotlin/SelectTest.kt | 1 - src/commonTest/kotlin/UpdateTest.kt | 1 - 5 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/Delete.kt b/src/commonMain/kotlin/uk/gibby/driver/rpc/Delete.kt index f9e50f1..3d65ad9 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/Delete.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/rpc/Delete.kt @@ -3,7 +3,7 @@ package uk.gibby.driver.rpc import kotlinx.serialization.json.add import kotlinx.serialization.json.buildJsonArray import uk.gibby.driver.Surreal -import uk.gibby.driver.rpc.model.Thing +import uk.gibby.driver.model.Thing /** * Delete diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/Select.kt b/src/commonMain/kotlin/uk/gibby/driver/rpc/Select.kt index 3d7b6f3..7330047 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/Select.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/rpc/Select.kt @@ -2,7 +2,7 @@ package uk.gibby.driver.rpc import kotlinx.serialization.json.* import uk.gibby.driver.Surreal -import uk.gibby.driver.rpc.model.Thing +import uk.gibby.driver.model.Thing import uk.gibby.driver.surrealJson /** diff --git a/src/commonTest/kotlin/PatchTest.kt b/src/commonTest/kotlin/PatchTest.kt index 4acd435..12e56f4 100644 --- a/src/commonTest/kotlin/PatchTest.kt +++ b/src/commonTest/kotlin/PatchTest.kt @@ -1,7 +1,6 @@ import kotlinx.coroutines.test.runTest import uk.gibby.driver.Surreal import uk.gibby.driver.rpc.* -import uk.gibby.driver.rpc.functions.* import utils.cleanDatabase import kotlin.test.Test import kotlin.test.assertEquals diff --git a/src/commonTest/kotlin/SelectTest.kt b/src/commonTest/kotlin/SelectTest.kt index 62bd318..1e7a216 100644 --- a/src/commonTest/kotlin/SelectTest.kt +++ b/src/commonTest/kotlin/SelectTest.kt @@ -1,7 +1,6 @@ import kotlinx.coroutines.test.runTest import uk.gibby.driver.Surreal import uk.gibby.driver.rpc.create -import uk.gibby.driver.rpc.functions.* import uk.gibby.driver.rpc.select import uk.gibby.driver.rpc.signin import uk.gibby.driver.rpc.use diff --git a/src/commonTest/kotlin/UpdateTest.kt b/src/commonTest/kotlin/UpdateTest.kt index 663cd56..6b5c41f 100644 --- a/src/commonTest/kotlin/UpdateTest.kt +++ b/src/commonTest/kotlin/UpdateTest.kt @@ -1,7 +1,6 @@ import kotlinx.coroutines.test.runTest import uk.gibby.driver.Surreal import uk.gibby.driver.rpc.create -import uk.gibby.driver.rpc.functions.* import uk.gibby.driver.rpc.signin import uk.gibby.driver.rpc.update import uk.gibby.driver.rpc.use From bc30507660d7629ff8e74df7dfa69494d23fa765 Mon Sep 17 00:00:00 2001 From: mnbjhu Date: Wed, 23 Aug 2023 20:10:48 +0100 Subject: [PATCH 5/5] Added Docs blocks and @SurrealDbOnlyApi to 'kill' --- .../kotlin/uk/gibby/driver/Surreal.kt | 11 ++- .../annotation/SurrealDbNightlyOnlyApi.kt | 2 +- .../uk/gibby/driver/api/LiveQueryFlow.kt | 30 ++++++++ .../kotlin/uk/gibby/driver/api/Observe.kt | 22 ++++-- .../gibby/driver/model/rpc/LiveQueryAction.kt | 71 ++++++------------- .../model/rpc/LiveQueryActionSerializer.kt | 49 +++++++++++++ .../kotlin/uk/gibby/driver/rpc/Kill.kt | 9 +++ .../kotlin/uk/gibby/driver/rpc/Live.kt | 21 +++--- src/commonTest/kotlin/KillTest.kt | 3 +- src/commonTest/kotlin/LiveQueryTest.kt | 4 +- 10 files changed, 146 insertions(+), 76 deletions(-) create mode 100644 src/commonMain/kotlin/uk/gibby/driver/model/rpc/LiveQueryActionSerializer.kt diff --git a/src/commonMain/kotlin/uk/gibby/driver/Surreal.kt b/src/commonMain/kotlin/uk/gibby/driver/Surreal.kt index c5ef719..b05a567 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/Surreal.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/Surreal.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.* import kotlinx.serialization.json.* +import uk.gibby.driver.annotation.SurrealDbNightlyOnlyApi import uk.gibby.driver.exception.LiveQueryKilledException import uk.gibby.driver.model.rpc.* import uk.gibby.driver.rpc.kill @@ -102,21 +103,25 @@ class Surreal(private val host: String, private val port: Int = 8000) { return channel.receive() } - fun subscribeToTableAsJson(liveQueryId: String): Flow> { + @SurrealDbNightlyOnlyApi + fun subscribeAsJson(liveQueryId: String): Flow> { val channel = liveQueries.getOrPut(liveQueryId) { Channel() } return channel.receiveAsFlow() } - inline fun subscribeToTable(liveQueryId: String): Flow> { - return subscribeToTableAsJson(liveQueryId).map { it.asType() } + @SurrealDbNightlyOnlyApi + inline fun subscribe(liveQueryId: String): Flow> { + return subscribeAsJson(liveQueryId).map { it.asType() } } + @SurrealDbNightlyOnlyApi fun unsubscribe(liveQueryId: String) { val channel = liveQueries[liveQueryId] channel?.cancel(LiveQueryKilledException) liveQueries.remove(liveQueryId) } + @SurrealDbNightlyOnlyApi internal fun triggerKill(liveQueryId: String) { context.launch { kill(liveQueryId) } } diff --git a/src/commonMain/kotlin/uk/gibby/driver/annotation/SurrealDbNightlyOnlyApi.kt b/src/commonMain/kotlin/uk/gibby/driver/annotation/SurrealDbNightlyOnlyApi.kt index cc0ee01..0914657 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/annotation/SurrealDbNightlyOnlyApi.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/annotation/SurrealDbNightlyOnlyApi.kt @@ -1,6 +1,6 @@ package uk.gibby.driver.annotation @RequiresOptIn(message = "This function is only available while using nightly builds of SurrealDB. See https://surrealdb.com/docs/installation/nightly.") -@Target(AnnotationTarget.FUNCTION) +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) @Retention(AnnotationRetention.BINARY) annotation class SurrealDbNightlyOnlyApi diff --git a/src/commonMain/kotlin/uk/gibby/driver/api/LiveQueryFlow.kt b/src/commonMain/kotlin/uk/gibby/driver/api/LiveQueryFlow.kt index de05b5a..7553832 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/api/LiveQueryFlow.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/api/LiveQueryFlow.kt @@ -4,18 +4,48 @@ import io.ktor.utils.io.core.* import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import uk.gibby.driver.Surreal +import uk.gibby.driver.annotation.SurrealDbNightlyOnlyApi import uk.gibby.driver.model.rpc.LiveQueryAction +/** + * Live query flow + * + * This class is a wrapper around a flow of [LiveQueryAction]s making it [Closeable]. + * + * @param T The type of the records + * @property flow The flow of [LiveQueryAction]s + * @property id The id of the live query + * @property connection The connection to the database + * @constructor Creates a new live query flow + */ +@SurrealDbNightlyOnlyApi class LiveQueryFlow( private val flow: Flow>, val id: String, private val connection: Surreal ): Flow> by flow, Closeable { + + /** + * Close + * + * This method will unsubscribe from the live query and trigger the kill RPC request. + */ + override fun close() { connection.unsubscribe(id) connection.triggerKill(id) } + /** + * Map + * + * Used to map the records of the live query flow form [T] to [R]. + * + * @param R The type of the records in the new flow + * @param transform The transform function from [T] to [R] + * @receiver The live query flow to map + * @return A new live query flow of [R]s + */ fun map(transform: (LiveQueryAction) -> LiveQueryAction): LiveQueryFlow { return LiveQueryFlow( flow = flow.map { transform(it) }, diff --git a/src/commonMain/kotlin/uk/gibby/driver/api/Observe.kt b/src/commonMain/kotlin/uk/gibby/driver/api/Observe.kt index 9f93bc8..9ab9dd7 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/api/Observe.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/api/Observe.kt @@ -3,25 +3,39 @@ package uk.gibby.driver.api import kotlinx.serialization.json.JsonElement import uk.gibby.driver.Surreal import uk.gibby.driver.annotation.SurrealDbNightlyOnlyApi -import uk.gibby.driver.model.query.data -import uk.gibby.driver.model.rpc.LiveQueryAction import uk.gibby.driver.rpc.live import uk.gibby.driver.model.rpc.asType -import uk.gibby.driver.rpc.query import kotlin.jvm.JvmName +/** + * Observe live query as json + * + * Creates a [LiveQueryFlow] for the given table. + * + * @param table Name of the table to 'LIVE SELECT' from + * @return A [LiveQueryFlow] of [JsonElement]s + */ @JvmName("observeJson") @SurrealDbNightlyOnlyApi suspend fun Surreal.observeLiveQueryAsJson(table: String): LiveQueryFlow { val liveQueryId = live(table) return LiveQueryFlow( - flow = subscribeToTableAsJson(liveQueryId), + flow = subscribeAsJson(liveQueryId), id = liveQueryId, connection = this ) } +/** + * Observe live query + * + * Creates a [LiveQueryFlow] for the given table. + * + * @param T The type of the records + * @param table Name of the table to 'LIVE SELECT' from + * @return A [LiveQueryFlow] of [T]s + */ @SurrealDbNightlyOnlyApi suspend inline fun Surreal.observeLiveQuery(table: String): LiveQueryFlow { val jsonFlow = observeLiveQueryAsJson(table) diff --git a/src/commonMain/kotlin/uk/gibby/driver/model/rpc/LiveQueryAction.kt b/src/commonMain/kotlin/uk/gibby/driver/model/rpc/LiveQueryAction.kt index 50f289f..4ac6187 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/model/rpc/LiveQueryAction.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/model/rpc/LiveQueryAction.kt @@ -1,22 +1,20 @@ package uk.gibby.driver.model.rpc -import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable -import kotlinx.serialization.SerializationException -import kotlinx.serialization.builtins.serializer -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.descriptors.buildClassSerialDescriptor -import kotlinx.serialization.encoding.CompositeDecoder -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.decodeFromJsonElement -import uk.gibby.driver.api.LiveQueryFlow -import uk.gibby.driver.model.Thing -import uk.gibby.driver.model.ThingSerializer +import uk.gibby.driver.model.rpc.LiveQueryAction.* import uk.gibby.driver.surrealJson +/** + * LiveQueryAction + * + * A live query action. Can be a [Create], [Update] or [Delete]. + * Holds the id of the record and the result if relevant. + * + * @param T The type of the result + */ @Serializable(with = LiveQueryActionSerializer::class) sealed class LiveQueryAction { abstract val id: String @@ -26,48 +24,19 @@ sealed class LiveQueryAction { } +/** + * As type + * + * Maps the live query to type [T] using [decodeFromJsonElement] + * + * @param T The new type + * @return The new live query action + */ inline fun LiveQueryAction.asType(): LiveQueryAction { return when(this) { - is LiveQueryAction.Delete -> this - is LiveQueryAction.Create -> LiveQueryAction.Create(id, surrealJson.decodeFromJsonElement(result)) - is LiveQueryAction.Update -> LiveQueryAction.Update(id, surrealJson.decodeFromJsonElement(result)) + is Delete -> this + is Create -> Create(id, surrealJson.decodeFromJsonElement(result)) + is Update -> Update(id, surrealJson.decodeFromJsonElement(result)) } } -class LiveQueryActionSerializer(private val resultSerializer: KSerializer): KSerializer> { - override val descriptor: SerialDescriptor = buildClassSerialDescriptor("LiveQueryAction") { - element("action", String.serializer().descriptor) - element("id", String.serializer().descriptor) - element("result", resultSerializer.descriptor) - } - - override fun deserialize(decoder: Decoder): LiveQueryAction { - val input = decoder.beginStructure(descriptor) - var action: String? = null - var id: String? = null - var result: Thing? = null - loop@ while (true) { - when (val i = input.decodeElementIndex(descriptor)) { - CompositeDecoder.DECODE_DONE -> break@loop - 0 -> action = input.decodeStringElement(descriptor, i) - 1 -> id = input.decodeStringElement(descriptor, i) - 2 -> result = input.decodeSerializableElement(descriptor, i, ThingSerializer(resultSerializer)) - else -> throw SerializationException("Unknown index $i") - } - } - input.endStructure(descriptor) - if (action == null || id == null || result == null) throw SerializationException("Missing fields") - return when(action) { - "CREATE" -> LiveQueryAction.Create(id, (result as Thing.Record).result) - "UPDATE" -> LiveQueryAction.Update(id, (result as Thing.Record).result) - "DELETE" -> LiveQueryAction.Delete(id, (result as Thing.Reference).id) - else -> throw SerializationException("Unknown action $action") - } - } - - override fun serialize(encoder: Encoder, value: LiveQueryAction) { - TODO("Not yet implemented") - } - -} - diff --git a/src/commonMain/kotlin/uk/gibby/driver/model/rpc/LiveQueryActionSerializer.kt b/src/commonMain/kotlin/uk/gibby/driver/model/rpc/LiveQueryActionSerializer.kt new file mode 100644 index 0000000..0d54858 --- /dev/null +++ b/src/commonMain/kotlin/uk/gibby/driver/model/rpc/LiveQueryActionSerializer.kt @@ -0,0 +1,49 @@ +package uk.gibby.driver.model.rpc + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerializationException +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.encoding.CompositeDecoder +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import uk.gibby.driver.model.Thing +import uk.gibby.driver.model.ThingSerializer + +class LiveQueryActionSerializer(private val resultSerializer: KSerializer): KSerializer> { + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("LiveQueryAction") { + element("action", String.serializer().descriptor) + element("id", String.serializer().descriptor) + element("result", resultSerializer.descriptor) + } + + override fun deserialize(decoder: Decoder): LiveQueryAction { + val input = decoder.beginStructure(descriptor) + var action: String? = null + var id: String? = null + var result: Thing? = null + loop@ while (true) { + when (val i = input.decodeElementIndex(descriptor)) { + CompositeDecoder.DECODE_DONE -> break@loop + 0 -> action = input.decodeStringElement(descriptor, i) + 1 -> id = input.decodeStringElement(descriptor, i) + 2 -> result = input.decodeSerializableElement(descriptor, i, ThingSerializer(resultSerializer)) + else -> throw SerializationException("Unknown index $i") + } + } + input.endStructure(descriptor) + if (action == null || id == null || result == null) throw SerializationException("Missing fields") + return when(action) { + "CREATE" -> LiveQueryAction.Create(id, (result as Thing.Record).result) + "UPDATE" -> LiveQueryAction.Update(id, (result as Thing.Record).result) + "DELETE" -> LiveQueryAction.Delete(id, (result as Thing.Reference).id) + else -> throw SerializationException("Unknown action $action") + } + } + + override fun serialize(encoder: Encoder, value: LiveQueryAction) { + TODO("Not yet implemented") + } + +} \ No newline at end of file diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/Kill.kt b/src/commonMain/kotlin/uk/gibby/driver/rpc/Kill.kt index 9b9b89a..49e0cf5 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/Kill.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/rpc/Kill.kt @@ -3,7 +3,16 @@ package uk.gibby.driver.rpc import kotlinx.serialization.json.add import kotlinx.serialization.json.buildJsonArray import uk.gibby.driver.Surreal +import uk.gibby.driver.annotation.SurrealDbNightlyOnlyApi +/** + * Kill + * + * This method will kill a live query + * + * @param liveQueryId The id of the live query to kill + */ +@SurrealDbNightlyOnlyApi suspend fun Surreal.kill(liveQueryId: String) { sendRequest("kill", buildJsonArray { add(liveQueryId) }) } diff --git a/src/commonMain/kotlin/uk/gibby/driver/rpc/Live.kt b/src/commonMain/kotlin/uk/gibby/driver/rpc/Live.kt index 603edde..6f51ac2 100644 --- a/src/commonMain/kotlin/uk/gibby/driver/rpc/Live.kt +++ b/src/commonMain/kotlin/uk/gibby/driver/rpc/Live.kt @@ -5,24 +5,19 @@ import kotlinx.serialization.json.buildJsonArray import kotlinx.serialization.json.decodeFromJsonElement import uk.gibby.driver.Surreal import uk.gibby.driver.annotation.SurrealDbNightlyOnlyApi -import uk.gibby.driver.model.Thing import uk.gibby.driver.surrealJson +/** + * Live + * + * This method will create a live query + * + * @param table Name of the table to 'LIVE SELECT' from + * @return The id of the live query + */ @SurrealDbNightlyOnlyApi suspend fun Surreal.live(table: String): String { val response = sendRequest("live", buildJsonArray { add(table) }) return surrealJson.decodeFromJsonElement(response) -} - -@SurrealDbNightlyOnlyApi -suspend fun Surreal.live(table: String, id: String): String { - val response = sendRequest("live", buildJsonArray { add("$table:$id") }) - return surrealJson.decodeFromJsonElement(response) -} - -@SurrealDbNightlyOnlyApi -suspend fun Surreal.live(id: Thing<*>): String { - val response = sendRequest("live", buildJsonArray { add(id.id) }) - return surrealJson.decodeFromJsonElement(response) } \ No newline at end of file diff --git a/src/commonTest/kotlin/KillTest.kt b/src/commonTest/kotlin/KillTest.kt index f14de4b..b299f57 100644 --- a/src/commonTest/kotlin/KillTest.kt +++ b/src/commonTest/kotlin/KillTest.kt @@ -6,11 +6,10 @@ import uk.gibby.driver.rpc.live import uk.gibby.driver.rpc.signin import uk.gibby.driver.rpc.use import utils.cleanDatabase -import kotlin.test.Ignore import kotlin.test.Test +@OptIn(SurrealDbNightlyOnlyApi::class) class KillTest { - @OptIn(SurrealDbNightlyOnlyApi::class) @Test fun testKill() = runTest { cleanDatabase() diff --git a/src/commonTest/kotlin/LiveQueryTest.kt b/src/commonTest/kotlin/LiveQueryTest.kt index ed3bda2..32c851a 100644 --- a/src/commonTest/kotlin/LiveQueryTest.kt +++ b/src/commonTest/kotlin/LiveQueryTest.kt @@ -54,7 +54,7 @@ class LiveQueryTest { connection.use("test", "test") val result = connection.query("LIVE SELECT * FROM test;") val liveQueryId = result.first().data() - val incoming = connection.subscribeToTable(liveQueryId) + val incoming = connection.subscribe(liveQueryId) connection.create("test", "first").content(TestClass("thing", 1)) connection.create("test", "second").content(TestClass("thing", 2)) @@ -88,7 +88,7 @@ class LiveQueryTest { connection.signin("root", "root") connection.use("test", "test") val liveQueryId = connection.live("test") - val incoming = connection.subscribeToTable(liveQueryId) + val incoming = connection.subscribe(liveQueryId) connection.create("test", "first").content(TestClass("thing", 1)) connection.create("test", "second").content(TestClass("thing", 2))