diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 964fdbf98..c4cf8b1cd 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -20,7 +20,7 @@ env:
ALGOLIA_INDEX_NAME: 'prod_kotlin_rpc'
ALGOLIA_KEY: '${{ secrets.ALGOLIA_KEY }}'
CONFIG_JSON_PRODUCT: 'kotlinx-rpc'
- CONFIG_JSON_VERSION: '0.5.1'
+ CONFIG_JSON_VERSION: '0.6.0'
jobs:
build:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d9463a23f..100b1b24d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,29 @@
+# 0.6.0
+> Published 4 April 2025
+
+### Features ๐
+* 2.1.20 by @Mr3zee in https://github.com/Kotlin/kotlinx-rpc/pull/309
+* Non suspend flow by @Mr3zee in https://github.com/Kotlin/kotlinx-rpc/pull/299
+
+### Documentation ๐
+* Update gRPC Docs and Sample by @Mr3zee in https://github.com/Kotlin/kotlinx-rpc/pull/284
+
+### Infra ๐ง
+* Update monitior by @Mr3zee in https://github.com/Kotlin/kotlinx-rpc/pull/282
+* Fix build config for for-ide builds by @Mr3zee in https://github.com/Kotlin/kotlinx-rpc/pull/287
+* Update build for custom KC versions by @Mr3zee in https://github.com/Kotlin/kotlinx-rpc/pull/293
+* Fix kotlin master compilation by @Mr3zee in https://github.com/Kotlin/kotlinx-rpc/pull/306
+
+### Other Changes ๐งน
+* Upgrade Gradle to 8.12.1 by @Mr3zee in https://github.com/Kotlin/kotlinx-rpc/pull/286
+* Version 0.6.0-SNAPSHOT by @Mr3zee in https://github.com/Kotlin/kotlinx-rpc/pull/283
+* Update leftover sources from jvm-only to kmp by @Mr3zee in https://github.com/Kotlin/kotlinx-rpc/pull/300
+* KRPC-129 Move compatibility tests from Toolbox to Kotlin RPC repo by @Mr3zee in https://github.com/Kotlin/kotlinx-rpc/pull/304
+* Dependency bump by @Mr3zee in https://github.com/Kotlin/kotlinx-rpc/pull/303
+* Better compiler error message for checked annotations by @Mr3zee in https://github.com/Kotlin/kotlinx-rpc/pull/302
+
+**Full Changelog**: https://github.com/Kotlin/kotlinx-rpc/compare/0.5.1...0.6.0
+
# 0.5.1
> Published 12 February 2025
diff --git a/README.md b/README.md
index 03faef104..eade1ca6f 100644
--- a/README.md
+++ b/README.md
@@ -116,7 +116,7 @@ Example of a setup in a project's `build.gradle.kts`:
plugins {
kotlin("multiplatform") version "2.1.20"
kotlin("plugin.serialization") version "2.1.20"
- id("org.jetbrains.kotlinx.rpc.plugin") version "0.5.1"
+ id("org.jetbrains.kotlinx.rpc.plugin") version "0.6.0"
}
```
@@ -131,15 +131,15 @@ And now you can add dependencies to your project:
```kotlin
dependencies {
// Client API
- implementation("org.jetbrains.kotlinx:kotlinx-rpc-krpc-client:0.5.1")
+ implementation("org.jetbrains.kotlinx:kotlinx-rpc-krpc-client:0.6.0")
// Server API
- implementation("org.jetbrains.kotlinx:kotlinx-rpc-krpc-server:0.5.1")
+ implementation("org.jetbrains.kotlinx:kotlinx-rpc-krpc-server:0.6.0")
// Serialization module. Also, protobuf and cbor are provided
- implementation("org.jetbrains.kotlinx:kotlinx-rpc-krpc-serialization-json:0.5.1")
+ implementation("org.jetbrains.kotlinx:kotlinx-rpc-krpc-serialization-json:0.6.0")
// Transport implementation for Ktor
- implementation("org.jetbrains.kotlinx:kotlinx-rpc-krpc-ktor-client:0.5.1")
- implementation("org.jetbrains.kotlinx:kotlinx-rpc-krpc-ktor-server:0.5.1")
+ implementation("org.jetbrains.kotlinx:kotlinx-rpc-krpc-ktor-client:0.6.0")
+ implementation("org.jetbrains.kotlinx:kotlinx-rpc-krpc-ktor-server:0.6.0")
// Ktor API
implementation("io.ktor:ktor-client-cio-jvm:$ktor_version")
diff --git a/docs/pages/kotlinx-rpc/help-versions.json b/docs/pages/kotlinx-rpc/help-versions.json
index bc1ee7380..6db88babb 100644
--- a/docs/pages/kotlinx-rpc/help-versions.json
+++ b/docs/pages/kotlinx-rpc/help-versions.json
@@ -1,3 +1,3 @@
[
- {"version":"0.5.1","url":"/kotlinx-rpc/0.5.1/","isCurrent":true}
+ {"version":"0.6.0","url":"/kotlinx-rpc/0.6.0/","isCurrent":true}
]
diff --git a/docs/pages/kotlinx-rpc/rpc.tree b/docs/pages/kotlinx-rpc/rpc.tree
index aef7ccde8..aaf3fa079 100644
--- a/docs/pages/kotlinx-rpc/rpc.tree
+++ b/docs/pages/kotlinx-rpc/rpc.tree
@@ -1,6 +1,6 @@
+
-
\ No newline at end of file
+
diff --git a/docs/pages/kotlinx-rpc/topics/0-6-0.topic b/docs/pages/kotlinx-rpc/topics/0-6-0.topic
new file mode 100644
index 000000000..5b1935087
--- /dev/null
+++ b/docs/pages/kotlinx-rpc/topics/0-6-0.topic
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+ Version 0.6.0
introduces non-breaking changes that require migration.
+
+
+
+
+ Non-suspending server flows are now supported.
+ That begins the deprecation cycle for the suspending server flows and stream scopes.
+ For more details, refer to the Non-suspending server flows and Stream scopes management
+ sections in the topic.
+
+
+
diff --git a/docs/pages/kotlinx-rpc/topics/features.topic b/docs/pages/kotlinx-rpc/topics/features.topic
index 54b5830ac..8a11a73f9 100644
--- a/docs/pages/kotlinx-rpc/topics/features.topic
+++ b/docs/pages/kotlinx-rpc/topics/features.topic
@@ -1,6 +1,6 @@
Another requirement is that server-side steaming (flows that are returned from a function),
- must be the top-level type:
+ must be the top-level type and the function must be non-suspending:
@@ -45,7 +45,8 @@
@Rpc
interface MyService : RemoteService {
- suspend fun serverStream(): Flow<String> // ok
+ fun serverStream(): Flow<String> // ok
+ suspend fun serverStream(): Flow<String> // not ok
suspend fun serverStream(): StreamResult // not ok
}
@@ -55,57 +56,5 @@
Contextual
annotation.
-
-
- To use flows in your code, use the streamScoped
function
- that will provide your flows with their lifetime:
-
-
-
- @Rpc
- interface MyService : RemoteService {
- suspend fun sendFlow(flow: Flow<Int>)
- }
-
- val myService = rpcClient.withService<MyService>()
-
- streamScoped {
- val flow = flow {
- repeat(10) { i ->
- emit(i)
- }
- }
-
- myService.sendFlow(flow)
- }
-
-
- In that case all your flows, including incoming and outgoing,
- will work until the streamScoped
function completes.
- After that, all streams that are still live will be closed.
-
-
- You can have multiple RPC calls and flows inside the streamScoped
function, including those from
- different services.
-
-
- On the server side, you can use the invokeOnStreamScopeCompletion
handler inside your methods
- to execute code after streamScoped
on the client side has closed.
- It might be useful to clean resources, for example.
-
-
- Note that this API is experimental and may be removed in future releases.
-
-
- Another way of managing streams is to do it manually.
- For this, you can use the StreamScope
constructor function together with
- withStreamScope
:
-
-
- val streamScope = StreamScope(myJob)
- withStreamScope(streamScope) {
- // use streams here
- }
-
diff --git a/docs/pages/kotlinx-rpc/topics/plugins.topic b/docs/pages/kotlinx-rpc/topics/plugins.topic
index 2c0db9c33..67f4fe05d 100644
--- a/docs/pages/kotlinx-rpc/topics/plugins.topic
+++ b/docs/pages/kotlinx-rpc/topics/plugins.topic
@@ -24,9 +24,7 @@
}
- For multi-project setups
- where you define your RPC services in one set of subprojects and use them in
- another, you can add the plugin only to modules with service definitions.
+ For multi-project setups you must add the plugin to all modules where services are declared or used.
diff --git a/docs/pages/kotlinx-rpc/topics/strict-mode.topic b/docs/pages/kotlinx-rpc/topics/strict-mode.topic
index 18e8c46a1..9053e22d5 100644
--- a/docs/pages/kotlinx-rpc/topics/strict-mode.topic
+++ b/docs/pages/kotlinx-rpc/topics/strict-mode.topic
@@ -1,4 +1,8 @@
+
+
0.5.0, the library introduces major changes to the service APIs.
The following declarations will be gradually restricted:
-
-
- StateFlow
and SharedFlow
- Deprecation level: WARNING
-
- @Rpc
- interface Service : RemoteService {
- suspend fun old(): StateFlow<Int> // deprecated
-
- suspend fun new(): Flow<Int> // use .stateIn on the client side
- }
-
-
-
- Fields
- Deprecation level: WARNING
-
- @Rpc
- interface Service : RemoteService {
- val old: Flow<Int> // deprecated
-
- suspend fun new(): Flow<Int> // store flow locally
+
+ Deprecation level: WARNING
+
+ @Rpc
+ interface Service : RemoteService {
+ suspend fun old(): StateFlow<Int> // deprecated
+
+ suspend fun new(): Flow<Int> // use .stateIn on the client side
+ }
+
+
+
+
+ Deprecation level: WARNING
+
+ @Rpc
+ interface Service : RemoteService {
+ val old: Flow<Int> // deprecated
+
+ suspend fun new(): Flow<Int> // store flow locally
+ }
+
+
+
+ Deprecation level: WARNING
+
+ @Rpc
+ interface Service : RemoteService {
+ suspend fun old(): Flow<Flow<Int>> // deprecated
+
+ // no particular alternative, depends on the use case
+ }
+
+
+
+ Deprecation level: WARNING
+
+
+ data class SpotifyWrapped(val myMusicFlow: Flow<Rap>, val extra: Data)
+
+ @Rpc
+ interface Service : RemoteService {
+ suspend fun old(): SpotifyWrapped // deprecated
+
+ // one should consider message delivery order when calling these
+ suspend fun new(): Flow<Rap>
+ suspend fun getData(): Data
+ }
+
+
+
+ Deprecation level: WARNING
+
+
+ data class SpotifyWrapped(val extra: Data)
+
+ @Rpc
+ interface Service : RemoteService {
+ suspend fun old(): Flow<SpotifyWrapped> // deprecated
+
+ fun new(): Flow<SpotifyWrapped>
+ }
+
+
+
+ Deprecation level: WARNING
+
+
+ The next stream scope management structures are deprecated due to the introduction of
+ non-suspending server flows:
+
+
+ StreamScoped
class and function
+ streamScoped
function
+ invokeOnStreamScopeCompletion
function
+ withStreamScope
function
+
+
+ Stream collection and completion is now bound to the CoroutineScope
in which the flow was
+ collected (server-side flows) or produced (client-side flows).
+
+
+ 0.5.x:
+
+
+ @Rpc
+ interface Service : RemoteService {
+ suspend fun oldClient(flow: Flow<Int>)
+ suspend fun oldServer(): Flow<Int>
+ }
+
+ suspend fun consumer(service: Service) {
+ streamScoped {
+ service.oldClient(flow { /* ... */ })
+
+ service.oldServer().collect {
+ // ...
+ }
}
-
-
-
- Nested Flows
- Deprecation level: WARNING
-
- @Rpc
- interface Service : RemoteService {
- suspend fun old(): Flow<Flow<Int>> // deprecated
-
- // no particular alternative, depends on the use case
+ }
+
+
+ 0.6.x:
+
+
+ @Rpc
+ interface Service : RemoteService {
+ suspend fun newClient(flow: Flow<Int>)
+ fun newServer(): Flow<Int>
+ }
+
+ fun consumer(service: Service, scope: CoroutineScope) {
+ val flow = service.new()
+ scope.launch {
+ service.newClient(flow { /* ... */ })
+
+ flow.collect {
+ // ...
+ }
}
-
-
-
- Not top-level server flows
- Deprecation level: WARNING
-
-
- data class SpotifyWrapped(val myMusicFlow: Flow<Rap>, val extra: Data)
-
- @Rpc
- interface Service : RemoteService {
- suspend fun old(): SpotifyWrapped // deprecated
-
- // one should consider message delivery order when calling these
- suspend fun new(): Flow<Rap>
- suspend fun getData(): Data
+ }
+
+ // or
+ suspend fun consumer(service: Service) {
+ service.newClient(flow { /* ... */ })
+
+ service.new().collect {
+ // ...
}
-
-
-
+ }
+
+
-
- Deprecation levels are controlled by the Gradle rpc
extension:
-
-
+
+
+ Deprecation levels are controlled by the Gradle rpc
extension:
+
+
// build.gradle.kts
plugins {
id("org.jetbrains.kotlinx.rpc.plugin")
@@ -81,16 +163,19 @@
nestedFlow = RpcStrictMode.WARNING
notTopLevelServerFlow = RpcStrictMode.WARNING
fields = RpcStrictMode.WARNING
+ suspendingServerStreaming = RpcStrictMode.WARNING
+ streamScopedFunctions = RpcStrictMode.WARNING
}
}
-
- Modes RpcStrictMode.NONE
and RpcStrictMode.ERROR
are available.
-
+
+ Modes RpcStrictMode.NONE
and RpcStrictMode.ERROR
are available.
+
-
- Note that setting RpcStrictMode.NONE
should not be done permanently.
- All deprecated APIs will become errors in future without an option to suppress it.
- Consider your migration path in advance.
-
+
+ Note that setting RpcStrictMode.NONE
should not be done permanently.
+ All deprecated APIs will become errors in the future without an option to suppress them.
+ Consider your migration path in advance.
+
+
diff --git a/docs/pages/kotlinx-rpc/v.list b/docs/pages/kotlinx-rpc/v.list
index 7addb534a..0263aede8 100644
--- a/docs/pages/kotlinx-rpc/v.list
+++ b/docs/pages/kotlinx-rpc/v.list
@@ -1,6 +1,6 @@
@@ -14,6 +14,6 @@
-
+
diff --git a/docs/pages/kotlinx-rpc/writerside.cfg b/docs/pages/kotlinx-rpc/writerside.cfg
index b007eb914..c103bbe98 100644
--- a/docs/pages/kotlinx-rpc/writerside.cfg
+++ b/docs/pages/kotlinx-rpc/writerside.cfg
@@ -1,6 +1,6 @@
@@ -12,5 +12,5 @@
-
+
diff --git a/krpc/krpc-client/src/commonMain/kotlin/kotlinx/rpc/krpc/client/KrpcClient.kt b/krpc/krpc-client/src/commonMain/kotlin/kotlinx/rpc/krpc/client/KrpcClient.kt
index cd65fabcb..f159371fa 100644
--- a/krpc/krpc-client/src/commonMain/kotlin/kotlinx/rpc/krpc/client/KrpcClient.kt
+++ b/krpc/krpc-client/src/commonMain/kotlin/kotlinx/rpc/krpc/client/KrpcClient.kt
@@ -355,6 +355,7 @@ public abstract class KrpcClient(
} catch (e: CancellationException) {
// sendCancellation is not suspending, so no need for NonCancellable
sendCancellation(CancellationType.REQUEST, call.serviceId.toString(), callId)
+ connector.unsubscribeFromMessages(call.descriptor.fqName, callId)
throw e
}
diff --git a/krpc/krpc-core/src/commonMain/kotlin/kotlinx/rpc/krpc/StreamScope.kt b/krpc/krpc-core/src/commonMain/kotlin/kotlinx/rpc/krpc/StreamScope.kt
index 8cfb1968f..90b75b662 100644
--- a/krpc/krpc-core/src/commonMain/kotlin/kotlinx/rpc/krpc/StreamScope.kt
+++ b/krpc/krpc-core/src/commonMain/kotlin/kotlinx/rpc/krpc/StreamScope.kt
@@ -26,10 +26,14 @@ import kotlin.js.JsName
* Failure of one request will not cancel all streams in the others.
*/
@OptIn(InternalCoroutinesApi::class)
+@Deprecated(
+ "StreamScope is deprecated, see https://kotlin.github.io/kotlinx-rpc/0-6-0.html",
+ level = DeprecationLevel.WARNING
+)
public class StreamScope internal constructor(
parentContext: CoroutineContext,
internal val role: Role,
-): AutoCloseable {
+) : AutoCloseable {
internal class Element(internal val scope: StreamScope) : CoroutineContext.Element {
override val key: CoroutineContext.Key = Key
@@ -170,6 +174,10 @@ public suspend fun callScoped(callId: String, block: suspend CoroutineScope.
* }
* ```
*/
+@Deprecated(
+ "streamScoped is deprecated, see https://kotlin.github.io/kotlinx-rpc/0-6-0.html",
+ level = DeprecationLevel.WARNING
+)
@OptIn(ExperimentalContracts::class)
public suspend fun streamScoped(block: suspend CoroutineScope.() -> T): T {
contract {
@@ -205,6 +213,10 @@ private fun CoroutineContext.checkContextForStreamScope() {
*/
@JsName("StreamScope_fun")
@ExperimentalRpcApi
+@Deprecated(
+ "StreamScoped is deprecated, see https://kotlin.github.io/kotlinx-rpc/0-6-0.html",
+ level = DeprecationLevel.WARNING
+)
public fun StreamScope(parent: CoroutineContext): StreamScope {
parent.checkContextForStreamScope()
@@ -216,6 +228,10 @@ public fun StreamScope(parent: CoroutineContext): StreamScope {
*/
@OptIn(ExperimentalContracts::class)
@ExperimentalRpcApi
+@Deprecated(
+ "withStreamScope is deprecated, see https://kotlin.github.io/kotlinx-rpc/0-6-0.html",
+ level = DeprecationLevel.WARNING
+)
public suspend fun withStreamScope(scope: StreamScope, block: suspend CoroutineScope.() -> T): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
@@ -252,6 +268,10 @@ public suspend fun withStreamScope(scope: StreamScope, block: suspend Corout
* ```
*/
@ExperimentalRpcApi
+@Deprecated(
+ "invokeOnStreamScopeCompletion is deprecated, see https://kotlin.github.io/kotlinx-rpc/0-6-0.html",
+ level = DeprecationLevel.WARNING
+)
public suspend fun invokeOnStreamScopeCompletion(throwIfNoScope: Boolean = true, block: (Throwable?) -> Unit) {
val streamScope = streamScopeOrNull() ?: noStreamScopeError()
diff --git a/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/cancellation/CancellationTest.kt b/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/cancellation/CancellationTest.kt
index 3f690abeb..d30d7d667 100644
--- a/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/cancellation/CancellationTest.kt
+++ b/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/cancellation/CancellationTest.kt
@@ -694,6 +694,7 @@ class CancellationTest {
}
@Test
+ @Ignore // KRPC-169
fun testGCNonSuspendable() = runCancellationTest {
val firstDone = CompletableDeferred()
val latch = CompletableDeferred()
diff --git a/tests/compiler-plugin-tests/src/testData/diagnostics/rpcService.kt b/tests/compiler-plugin-tests/src/testData/diagnostics/rpcService.kt
index deca02223..8ad72ddb2 100644
--- a/tests/compiler-plugin-tests/src/testData/diagnostics/rpcService.kt
+++ b/tests/compiler-plugin-tests/src/testData/diagnostics/rpcService.kt
@@ -12,7 +12,7 @@ import kotlinx.serialization.Contextual
import kotlinx.rpc.annotations.Rpc
import kotlinx.rpc.krpc.streamScoped
import kotlinx.rpc.krpc.withStreamScope
-import kotlinx.rpc.krpc.StreamScope
+import kotlinx.rpc.krpc.StreamScope
import kotlinx.rpc.krpc.invokeOnStreamScopeCompletion
import kotlinx.rpc.internal.utils.ExperimentalRpcApi
diff --git a/tests/compiler-plugin-tests/src/testData/diagnostics/strictMode.kt b/tests/compiler-plugin-tests/src/testData/diagnostics/strictMode.kt
index 0e879fd2f..72baaaca6 100644
--- a/tests/compiler-plugin-tests/src/testData/diagnostics/strictMode.kt
+++ b/tests/compiler-plugin-tests/src/testData/diagnostics/strictMode.kt
@@ -29,7 +29,7 @@ import kotlinx.serialization.Contextual
import kotlinx.rpc.annotations.Rpc
import kotlinx.rpc.krpc.streamScoped
import kotlinx.rpc.krpc.withStreamScope
-import kotlinx.rpc.krpc.StreamScope
+import kotlinx.rpc.krpc.StreamScope
import kotlinx.rpc.krpc.invokeOnStreamScopeCompletion
import kotlinx.rpc.internal.utils.ExperimentalRpcApi
@@ -81,8 +81,8 @@ interface MyService {
}
fun main(): Unit = runBlocking {
- streamScoped {}
- val scope = StreamScope(Job())
- withStreamScope(scope) {}
- invokeOnStreamScopeCompletion {}
+ streamScoped {}
+ val scope = StreamScope(Job())
+ withStreamScope(scope) {}
+ invokeOnStreamScopeCompletion {}
}