From 5501424728c56b6cdb73585a731ca9a9f9a5d987 Mon Sep 17 00:00:00 2001 From: Alexander Sysoev Date: Tue, 10 Jun 2025 19:14:18 +0200 Subject: [PATCH 1/3] Update API docs --- .../commonMain/kotlin/kotlinx/rpc/RpcCall.kt | 6 +- .../kotlin/kotlinx/rpc/RpcClient.kt | 6 +- .../kotlin/kotlinx/rpc/RpcServer.kt | 24 ++++-- .../rpc/annotations/CheckedTypeAnnotation.kt | 7 +- .../kotlin/kotlinx/rpc/annotations/Rpc.kt | 19 ++++- .../kotlin/kotlinx/rpc/withService.kt | 12 ++- .../kotlinx/rpc/krpc/client/KrpcClient.kt | 77 ++++++++++--------- .../kotlin/kotlinx/rpc/krpc/KrpcConfig.kt | 74 ++++++++++++------ .../kotlin/kotlinx/rpc/krpc/KrpcTransport.kt | 25 ++++-- .../rpc/krpc/ktor/client/KtorClientDsl.kt | 18 +++-- .../rpc/krpc/ktor/client/KtorRpcClient.kt | 6 ++ .../kotlinx/rpc/krpc/ktor/KtorTransport.kt | 6 +- .../kotlinx/rpc/krpc/ktor/server/KrpcRoute.kt | 8 +- .../kotlinx/rpc/krpc/server/KrpcServer.kt | 20 ++--- 14 files changed, 197 insertions(+), 111 deletions(-) diff --git a/core/src/commonMain/kotlin/kotlinx/rpc/RpcCall.kt b/core/src/commonMain/kotlin/kotlinx/rpc/RpcCall.kt index 87b617f37..b7cc9a488 100644 --- a/core/src/commonMain/kotlin/kotlinx/rpc/RpcCall.kt +++ b/core/src/commonMain/kotlin/kotlinx/rpc/RpcCall.kt @@ -7,12 +7,12 @@ package kotlinx.rpc import kotlinx.rpc.descriptor.RpcServiceDescriptor /** - * Represents a method or field call of an RPC service. + * Represents a method call from an RPC service. * * @property descriptor [RpcServiceDescriptor] of a service that made the call. - * @property callableName The name of the callable. Can be the name of the method or field. + * @property callableName The name of the method being called. * @property data The data for the call. - * @property serviceId id of the service, that made the call. + * @property serviceId The id of the service that made the call. */ public data class RpcCall( val descriptor: RpcServiceDescriptor<*>, diff --git a/core/src/commonMain/kotlin/kotlinx/rpc/RpcClient.kt b/core/src/commonMain/kotlin/kotlinx/rpc/RpcClient.kt index 45685d418..034101f73 100644 --- a/core/src/commonMain/kotlin/kotlinx/rpc/RpcClient.kt +++ b/core/src/commonMain/kotlin/kotlinx/rpc/RpcClient.kt @@ -4,13 +4,11 @@ package kotlinx.rpc -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow /** * [RpcClient] represents an abstraction of an RPC client, that can handle requests from several RPC services, * transform them, send to the server and handle responses and errors. - * [CoroutineScope] defines the lifetime of the client. */ public interface RpcClient { /** @@ -19,7 +17,7 @@ public interface RpcClient { * @param T type of the result * @param call an object that contains all required information about the called method, * that is needed to route it properly to the server. - * @return actual result of the call, for example, data from the server. + * @return result of the call, for example, data from the server. */ public suspend fun call(call: RpcCall): T @@ -30,7 +28,7 @@ public interface RpcClient { * @param T type of the result * @param call an object that contains all required information about the called method, * that is needed to route it properly to the server. - * @return the actual result of the call, for example, data from the server + * @return result of the call, for example, data from the server */ public fun callServerStreaming(call: RpcCall): Flow } diff --git a/core/src/commonMain/kotlin/kotlinx/rpc/RpcServer.kt b/core/src/commonMain/kotlin/kotlinx/rpc/RpcServer.kt index d0ad7f77f..06b59f467 100644 --- a/core/src/commonMain/kotlin/kotlinx/rpc/RpcServer.kt +++ b/core/src/commonMain/kotlin/kotlinx/rpc/RpcServer.kt @@ -13,20 +13,32 @@ import kotlin.reflect.KClass */ public interface RpcServer { /** - * Registers new service to the server. Server will route all designated messages to it. + * Registers new service. Server will route all designated messages to it. * Service of any type should be unique on the server, but RpcServer doesn't specify the actual retention policy. * * @param Service the exact type of the server to be registered. - * For example, for service with `MyService` interface and `MyServiceImpl` implementation, - * type `MyService` should be specified explicitly. + * For example, for a service with `MyService` interface and `MyServiceImpl` implementation + * the type `MyService` should be specified explicitly. * @param serviceKClass [KClass] of the [Service]. * @param serviceFactory function that produces the actual implementation of the service that will handle the calls. + * + * @see kotlinx.rpc.annotations.CheckedTypeAnnotation */ public fun <@Rpc Service : Any> registerService( serviceKClass: KClass, serviceFactory: () -> Service, ) + /** + * Deregisters a service. Server will stop routing messages to it. + * + * @param Service the exact type of the server to be deregistered, the same one that was used for registration. + * For example, for a service with `MyService` interface and `MyServiceImpl` implementation + * the type `MyService` should be specified explicitly. + * @param serviceKClass [KClass] of the [Service]. + * + * @see kotlinx.rpc.annotations.CheckedTypeAnnotation + */ public fun <@Rpc Service : Any> deregisterService( serviceKClass: KClass, ) @@ -37,9 +49,11 @@ public interface RpcServer { * Service of any type should be unique on the server, but RpcServer doesn't specify the actual retention policy. * * @param Service the exact type of the server to be registered. - * For example, for service with `MyService` interface and `MyServiceImpl` implementation, - * type `MyService` should be specified explicitly. + * For example, for a service with `MyService` interface and `MyServiceImpl` implementation + * the type `MyService` should be specified explicitly. * @param serviceFactory function that produces the actual implementation of the service that will handle the calls. + * + * @see kotlinx.rpc.annotations.CheckedTypeAnnotation */ public inline fun <@Rpc reified Service : Any> RpcServer.registerService( noinline serviceFactory: () -> Service, diff --git a/core/src/commonMain/kotlin/kotlinx/rpc/annotations/CheckedTypeAnnotation.kt b/core/src/commonMain/kotlin/kotlinx/rpc/annotations/CheckedTypeAnnotation.kt index 3f461a6fd..0f29fa644 100644 --- a/core/src/commonMain/kotlin/kotlinx/rpc/annotations/CheckedTypeAnnotation.kt +++ b/core/src/commonMain/kotlin/kotlinx/rpc/annotations/CheckedTypeAnnotation.kt @@ -5,8 +5,11 @@ package kotlinx.rpc.annotations /** - * Marks an annotation as a one that marks - * a type argument as a one that requires its resolved type to be annotated this annotation. + * Meta annotation. + * Used to perform [annotation type-safety](https://kotlin.github.io/kotlinx-rpc/annotation-type-safety.html) checks. + * + * When an annotation class (for example, `@X`) is marked with `@CheckedTypeAnnotation` - + * Any other type can be marked with `@X` to perform safety checks on type parameters that are also marked with `@X`. * * Example: * ```kotlin diff --git a/core/src/commonMain/kotlin/kotlinx/rpc/annotations/Rpc.kt b/core/src/commonMain/kotlin/kotlinx/rpc/annotations/Rpc.kt index 35f3441ef..3eda61e3e 100644 --- a/core/src/commonMain/kotlin/kotlinx/rpc/annotations/Rpc.kt +++ b/core/src/commonMain/kotlin/kotlinx/rpc/annotations/Rpc.kt @@ -8,18 +8,24 @@ package kotlinx.rpc.annotations * Every [Rpc] annotated interface will have a code generation process run on it, * making the interface effectively usable for RPC calls. * - * Example usage: + * Example usage. + * + * Define an interface and mark it with `@Rpc`. + * Compiler plugin will ensure that all declarations inside the interface are valid. * ```kotlin - * // common code * @Rpc * interface MyService { * suspend fun sayHello(firstName: String, lastName: String, age: Int): String * } - * // client code + * ``` + * On the client side use [kotlinx.rpc.RpcClient] to get a generated instance of the service, and use it to make calls: + * ```kotlin * val rpcClient: RpcClient * val myService = rpcClient.withService() * val greetingFromServer = myService.sayHello("Alex", "Smith", 35) - * // server code + * ``` + * On the server side, define an implementation of this interface and register it on an [kotlinx.rpc.RpcServer]: + * ```kotlin * class MyServiceImpl : MyService { * override suspend fun sayHello(firstName: String, lastName: String, age: Int): String { * return "Hello, $firstName $lastName, of age $age. I am your server!" @@ -28,6 +34,11 @@ package kotlinx.rpc.annotations * val server: RpcServer * server.registerService { MyServiceImpl() } * ``` + * + * @see kotlinx.rpc.RpcClient + * @see kotlinx.rpc.RpcServer + * @see CheckedTypeAnnotation + * @see kotlinx.rpc.withService */ @CheckedTypeAnnotation @Target(AnnotationTarget.CLASS, AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.TYPE_PARAMETER) diff --git a/core/src/commonMain/kotlin/kotlinx/rpc/withService.kt b/core/src/commonMain/kotlin/kotlinx/rpc/withService.kt index 2b8a881ef..3aab53a8b 100644 --- a/core/src/commonMain/kotlin/kotlinx/rpc/withService.kt +++ b/core/src/commonMain/kotlin/kotlinx/rpc/withService.kt @@ -12,21 +12,25 @@ import kotlin.reflect.KClass import kotlin.reflect.KType /** - * Creates instance of the generated service [T], that is able to communicate with server using [RpcClient]. + * Creates an instance of the generated service [T], that is able to communicate with a server using this [RpcClient]. * * @param T the exact type of the service to be created. * @return instance of the generated service. + * + * @see kotlinx.rpc.annotations.CheckedTypeAnnotation */ public inline fun <@Rpc reified T : Any> RpcClient.withService(): T { return withService(T::class) } /** - * Creates instance of the generated service [T], that is able to communicate with server using [RpcClient]. + * Creates an instance of the generated service [T], that is able to communicate with a server using this [RpcClient]. * * @param T the exact type of the service to be created. * @param serviceKType [KType] of the service to be created. * @return instance of the generated service. + * + * @see kotlinx.rpc.annotations.CheckedTypeAnnotation */ public fun <@Rpc T : Any> RpcClient.withService(serviceKType: KType): T { return withService(serviceKType.rpcInternalKClass()) @@ -39,11 +43,13 @@ public fun <@Rpc T : Any> RpcClient.withService(serviceKType: KType): T { private val SERVICE_ID = atomic(0L) /** - * Creates instance of the generated service [T], that is able to communicate with server using [RpcClient]. + * Creates an instance of the generated service [T], that is able to communicate with a server using this [RpcClient]. * * @param T the exact type of the service to be created. * @param serviceKClass [KClass] of the service to be created. * @return instance of the generated service. + * + * @see kotlinx.rpc.annotations.CheckedTypeAnnotation */ public fun <@Rpc T : Any> RpcClient.withService(serviceKClass: KClass): T { val descriptor = serviceDescriptorOf(serviceKClass) 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 70d9bbe4a..6fa3eebac 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 @@ -37,36 +37,59 @@ import kotlin.coroutines.cancellation.CancellationException import kotlin.properties.Delegates /** - * Default implementation of [RpcClient]. - * Takes care of tracking requests and responses, - * serializing data, tracking streams, processing exceptions, and other protocol responsibilities. - * Leaves out the delivery of encoded messages to the specific implementations. - * - * A simple example of how this client may be implemented: - * ```kotlin - * class MyTransport : RpcTransport { /*...*/ } - * - * class MyClient(config: RpcConfig.Client): KrpcClient(config, MyTransport()) - * ``` + * Represents an initialized [KrpcClient] that wraps a predefined [config] and [transport] parameters. * * @param config configuration provided for that specific client. Applied to all services that use this client. + * See [KrpcClient.initializeConfig]. * @param transport [KrpcTransport] instance that will be used to send and receive RPC messages. - * IMPORTANT: Must be exclusive to this client, otherwise unexpected behavior may occur. + * See [KrpcClient.initializeTransport]. + */ +public abstract class InitializedKrpcClient( + private val transport: KrpcTransport, + private val config: KrpcConfig.Client, +): KrpcClient() { + final override suspend fun initializeTransport(): KrpcTransport { + return transport + } + + final override fun initializeConfig(): KrpcConfig.Client { + return config + } +} + +/** + * kRPC implementation of the [RpcClient]. + * Takes care of tracking requests and responses, + * serializing data, tracking streams, processing exceptions, and other protocol responsibilities. + * Leaves out the delivery of encoded messages to the specific implementations with [KrpcTransport]. */ @OptIn(InternalCoroutinesApi::class) public abstract class KrpcClient : RpcClient, KrpcEndpoint { /** - * Called once to provide [KrpcTransport] for this client + * Called once to provide [KrpcTransport] for this client. + * + * IMPORTANT: The provided instance must be exclusive to this client, otherwise unexpected behavior may occur. */ - public abstract suspend fun initializeTransport(): KrpcTransport - - private var isTransportReady: Boolean = false - private var transport: KrpcTransport by Delegates.notNull() + protected abstract suspend fun initializeTransport(): KrpcTransport /** - * Called once to provide [KrpcConfig.Client] for this client + * Called once to provide [KrpcConfig.Client] for this client. + * Called only after [initializeTransport]. + * + * Configuration is applied to all services that use this client. + */ + protected abstract fun initializeConfig(): KrpcConfig.Client + + /* + * ##################################################################### + * # # + * # INTERNALS AHEAD # + * # # + * ##################################################################### */ - public abstract fun initializeConfig(): KrpcConfig.Client + + private var isTransportReady: Boolean = false + private var transport: KrpcTransport by Delegates.notNull() private val config: KrpcConfig.Client by lazy { initializeConfig() @@ -476,19 +499,3 @@ public abstract class KrpcClient : RpcClient, KrpcEndpoint { } } } - -/** - * Represents an initialized RPC client that wraps a predefined configuration and transport. - */ -public abstract class InitializedKrpcClient( - private val config: KrpcConfig.Client, - private val transport: KrpcTransport, -): KrpcClient() { - final override suspend fun initializeTransport(): KrpcTransport { - return transport - } - - final override fun initializeConfig(): KrpcConfig.Client { - return config - } -} diff --git a/krpc/krpc-core/src/commonMain/kotlin/kotlinx/rpc/krpc/KrpcConfig.kt b/krpc/krpc-core/src/commonMain/kotlin/kotlinx/rpc/krpc/KrpcConfig.kt index 5a6f04b6a..566afada2 100644 --- a/krpc/krpc-core/src/commonMain/kotlin/kotlinx/rpc/krpc/KrpcConfig.kt +++ b/krpc/krpc-core/src/commonMain/kotlin/kotlinx/rpc/krpc/KrpcConfig.kt @@ -4,7 +4,6 @@ package kotlinx.rpc.krpc -import kotlinx.rpc.internal.utils.InternalRpcApi import kotlinx.rpc.krpc.serialization.KrpcSerialFormat import kotlinx.rpc.krpc.serialization.KrpcSerialFormatBuilder import kotlinx.rpc.krpc.serialization.KrpcSerialFormatConfiguration @@ -13,18 +12,6 @@ import kotlinx.rpc.krpc.serialization.KrpcSerialFormatConfiguration * Builder for [KrpcConfig]. Provides DSL to configure parameters for KrpcClient and/or KrpcServer. */ public sealed class KrpcConfigBuilder protected constructor() { - private var serialFormatInitializer: KrpcSerialFormatBuilder<*, *>? = null - - private val configuration = object : KrpcSerialFormatConfiguration { - override fun register(rpcSerialFormatInitializer: KrpcSerialFormatBuilder.Binary<*, *>) { - serialFormatInitializer = rpcSerialFormatInitializer - } - - override fun register(rpcSerialFormatInitializer: KrpcSerialFormatBuilder.String<*, *>) { - serialFormatInitializer = rpcSerialFormatInitializer - } - } - /** * DSL for serialization configuration. * @@ -44,18 +31,11 @@ public sealed class KrpcConfigBuilder protected constructor() { configuration.builder() } - protected fun rpcSerialFormat(): KrpcSerialFormatBuilder<*, *> { - return when (val format = serialFormatInitializer) { - null -> error("Please, choose serialization format") - else -> format - } - } - /** - * A flag indicating whether the client should wait for subscribers + * A flag indicating whether a client or a server should wait for subscribers * if no service is available to process a message immediately. - * If false, the endpoint that sent the message will receive call exception - * that says that there were no services to process its message. + * If `false`, the endpoint that sent the unprocessed message will receive a call exception + * saying there were no services to process the message. */ public var waitForServices: Boolean = true @@ -82,22 +62,60 @@ public sealed class KrpcConfigBuilder protected constructor() { ) } } + + /* + * ##################################################################### + * # # + * # CLASS INTERNALS AHEAD # + * # # + * ##################################################################### + */ + + private var serialFormatInitializer: KrpcSerialFormatBuilder<*, *>? = null + + private val configuration = object : KrpcSerialFormatConfiguration { + override fun register(rpcSerialFormatInitializer: KrpcSerialFormatBuilder.Binary<*, *>) { + serialFormatInitializer = rpcSerialFormatInitializer + } + + override fun register(rpcSerialFormatInitializer: KrpcSerialFormatBuilder.String<*, *>) { + serialFormatInitializer = rpcSerialFormatInitializer + } + } + + protected fun rpcSerialFormat(): KrpcSerialFormatBuilder<*, *> { + return when (val format = serialFormatInitializer) { + null -> error("Please, choose serialization format") + else -> format + } + } } /** * Configuration class that is used by kRPC protocol's client and server (KrpcClient and KrpcServer). */ public sealed interface KrpcConfig { - @InternalRpcApi + /** + * @see KrpcConfigBuilder.serialization + */ public val serialFormatInitializer: KrpcSerialFormatBuilder<*, *> - @InternalRpcApi + + /** + * @see KrpcConfigBuilder.waitForServices + */ public val waitForServices: Boolean /** * @see [KrpcConfig] */ public class Client internal constructor( + /** + * @see KrpcConfigBuilder.serialization + */ override val serialFormatInitializer: KrpcSerialFormatBuilder<*, *>, + /** + * @see KrpcConfigBuilder.waitForServices + */ override val waitForServices: Boolean, ) : KrpcConfig @@ -105,7 +123,13 @@ public sealed interface KrpcConfig { * @see [KrpcConfig] */ public class Server internal constructor( + /** + * @see KrpcConfigBuilder.serialization + */ override val serialFormatInitializer: KrpcSerialFormatBuilder<*, *>, + /** + * @see KrpcConfigBuilder.waitForServices + */ override val waitForServices: Boolean, ) : KrpcConfig } diff --git a/krpc/krpc-core/src/commonMain/kotlin/kotlinx/rpc/krpc/KrpcTransport.kt b/krpc/krpc-core/src/commonMain/kotlin/kotlinx/rpc/krpc/KrpcTransport.kt index 3ffc6485d..563913f19 100644 --- a/krpc/krpc-core/src/commonMain/kotlin/kotlinx/rpc/krpc/KrpcTransport.kt +++ b/krpc/krpc-core/src/commonMain/kotlin/kotlinx/rpc/krpc/KrpcTransport.kt @@ -11,8 +11,14 @@ import kotlinx.coroutines.CoroutineScope * Can be either of string or binary type. */ public sealed interface KrpcTransportMessage { + /** + * A single message of the string type that can be transferred from one RPC endpoint to another. + */ public class StringMessage(public val value: String) : KrpcTransportMessage + /** + * A single message of the binary type that can be transferred from one RPC endpoint to another. + */ public class BinaryMessage(public val value: ByteArray) : KrpcTransportMessage } @@ -22,33 +28,36 @@ public sealed interface KrpcTransportMessage { * For developers of custom transports: * - The implementation should be able to handle both binary and string formats, * though not necessary if you absolutely sure that only one will be supplied and received. - * - The KrpcClient and KrpcServer suppose that they have exclusive instance of transport. + * - The KrpcClient and KrpcServer suppose that they have an exclusive instance of transport. * That means that each client or/and server should have only one transport instance, * otherwise some messages may be lost or processed incorrectly. - * - The implementation should manage lifetime of the connection using its [CoroutineScope]. * - * Good example of the implementation is KtorTransport, that uses websocket protocol to deliver messages. + * [CoroutineScope] is used to define connection's lifetime. + * If canceled, no messages will be able to go to the other side, + * so ideally, it should be canceled only after its client or server is. + * + * A good example of the implementation is KtorTransport, that uses websocket protocol to deliver messages. */ public interface KrpcTransport : CoroutineScope { /** - * Sends a single encoded RPC message over network (or any other medium) to a peer endpoint. + * Sends a single encoded RPC message over a network (or any other medium) to a peer endpoint. * * @param message a message to send. Either of string or binary type. */ public suspend fun send(message: KrpcTransportMessage) /** - * Suspends until next RPC message from a peer endpoint is received and then returns it. + * Suspends until the next RPC message from a peer endpoint is received and then returns it. * - * @return received RPC message. + * @return the received RPC message. */ public suspend fun receive(): KrpcTransportMessage } /** - * Suspends until next RPC message from a peer endpoint is received and then returns it. + * Suspends until the next RPC message from a peer endpoint is received and then returns it. * - * @return received RPC message as a [Result]. + * @return the received RPC message as a [Result]. */ public suspend fun KrpcTransport.receiveCatching(): Result { return runCatching { receive() } diff --git a/krpc/krpc-ktor/krpc-ktor-client/src/commonMain/kotlin/kotlinx/rpc/krpc/ktor/client/KtorClientDsl.kt b/krpc/krpc-ktor/krpc-ktor-client/src/commonMain/kotlin/kotlinx/rpc/krpc/ktor/client/KtorClientDsl.kt index dcf3acc30..75eeb89b8 100644 --- a/krpc/krpc-ktor/krpc-ktor-client/src/commonMain/kotlin/kotlinx/rpc/krpc/ktor/client/KtorClientDsl.kt +++ b/krpc/krpc-ktor/krpc-ktor-client/src/commonMain/kotlin/kotlinx/rpc/krpc/ktor/client/KtorClientDsl.kt @@ -28,12 +28,15 @@ public fun HttpRequestBuilder.rpcConfig(configBuilder: KrpcConfigBuilder.Client. } /** - * Configures [RpcClient] for the following path. Provides means for additional configuration via [block]. + * Configures [KtorRpcClient] for the following path. Provides means for additional configuration via [block]. * Note that the [WebSockets] plugin is required for these calls. * * @param urlString The URL to use for the request. - * @param block Optional configuration for the - * @return An instance of [RpcClient] that is configured to send messages to the server. + * @param block Optional configuration for the [HttpRequestBuilder]. + * @return An instance of [KtorRpcClient] that is configured to send messages to the server. + * The instance is cold and will establish WebSocket connection on the first request. + * + * @See [KtorRpcClient] */ public fun HttpClient.rpc( urlString: String, @@ -46,11 +49,14 @@ public fun HttpClient.rpc( } /** - * Configures [RpcClient] for the following path. Provides means for additional configuration via [block]. + * Configures [KtorRpcClient] for the following path. Provides means for additional configuration via [block]. * Note that the [WebSockets] plugin is required for these calls. * - * @param block Optional configuration for the - * @return An instance of [RpcClient] that is configured to send messages to the server. + * @param block Optional configuration for the [HttpRequestBuilder]. + * @return An instance of [KtorRpcClient] that is configured to send messages to the server. + * The instance is cold and will establish WebSocket connection on the first request. + * + * @See [KtorRpcClient] */ public fun HttpClient.rpc( block: HttpRequestBuilder.() -> Unit = {}, diff --git a/krpc/krpc-ktor/krpc-ktor-client/src/commonMain/kotlin/kotlinx/rpc/krpc/ktor/client/KtorRpcClient.kt b/krpc/krpc-ktor/krpc-ktor-client/src/commonMain/kotlin/kotlinx/rpc/krpc/ktor/client/KtorRpcClient.kt index 169718dff..9c3a52b35 100644 --- a/krpc/krpc-ktor/krpc-ktor-client/src/commonMain/kotlin/kotlinx/rpc/krpc/ktor/client/KtorRpcClient.kt +++ b/krpc/krpc-ktor/krpc-ktor-client/src/commonMain/kotlin/kotlinx/rpc/krpc/ktor/client/KtorRpcClient.kt @@ -18,8 +18,14 @@ import kotlinx.rpc.krpc.rpcClientConfig /** * [RpcClient] implementation for Ktor, containing [webSocketSession] object, * that is used to maintain connection. + * + * Client is cold, meaning the connection will be established on the first request. + * [webSocketSession] will be completed when the connection is established. */ public interface KtorRpcClient : RpcClient { + /** + * Cold [WebSocketSession] object. Instantiated when the connection is established on the first request. + */ public val webSocketSession: Deferred } diff --git a/krpc/krpc-ktor/krpc-ktor-core/src/commonMain/kotlin/kotlinx/rpc/krpc/ktor/KtorTransport.kt b/krpc/krpc-ktor/krpc-ktor-core/src/commonMain/kotlin/kotlinx/rpc/krpc/ktor/KtorTransport.kt index ed12adc72..b46bd644b 100644 --- a/krpc/krpc-ktor/krpc-ktor-core/src/commonMain/kotlin/kotlinx/rpc/krpc/ktor/KtorTransport.kt +++ b/krpc/krpc-ktor/krpc-ktor-core/src/commonMain/kotlin/kotlinx/rpc/krpc/ktor/KtorTransport.kt @@ -16,7 +16,7 @@ public class KtorTransport( ) : KrpcTransport, CoroutineScope by webSocketSession { /** - * Sends a single encoded RPC message over network (or any other medium) to a peer endpoint. + * Sends a single encoded RPC message over a network (or any other medium) to a peer endpoint. * * @param message a message to send. Either of string or binary type. */ @@ -33,9 +33,9 @@ public class KtorTransport( } /** - * Suspends until next RPC message from a peer endpoint is received and then returns it. + * Suspends until the next RPC message from a peer endpoint is received and then returns it. * - * @return received RPC message. + * @return the received RPC message. */ override suspend fun receive(): KrpcTransportMessage { return when (val message = webSocketSession.incoming.receive()) { diff --git a/krpc/krpc-ktor/krpc-ktor-server/src/commonMain/kotlin/kotlinx/rpc/krpc/ktor/server/KrpcRoute.kt b/krpc/krpc-ktor/krpc-ktor-server/src/commonMain/kotlin/kotlinx/rpc/krpc/ktor/server/KrpcRoute.kt index 800bac856..bcd6e5088 100644 --- a/krpc/krpc-ktor/krpc-ktor-server/src/commonMain/kotlin/kotlinx/rpc/krpc/ktor/server/KrpcRoute.kt +++ b/krpc/krpc-ktor/krpc-ktor-server/src/commonMain/kotlin/kotlinx/rpc/krpc/ktor/server/KrpcRoute.kt @@ -11,8 +11,8 @@ import kotlinx.rpc.krpc.KrpcConfigBuilder import kotlin.reflect.KClass /** - * RpcRoute class represents an RPC server that is mounted in Ktor routing. - * This class provides API to register services and optionally setup configuration. + * [KrpcRoute] class represents an RPC server mounted in Ktor routing. + * This class provides an API to register services and optionally setup configuration. */ public class KrpcRoute( webSocketSession: DefaultWebSocketServerSession @@ -36,7 +36,7 @@ public class KrpcRoute( * Service of any type should be unique on the server, but RpcServer does not specify the actual retention policy. * * @param Service the exact type of the server to be registered. - * For example for service with `MyService` interface and `MyServiceImpl` implementation, + * For example, for service with `MyService` interface and `MyServiceImpl` implementation, * type `MyService` should be specified explicitly. * @param serviceKClass [KClass] of the [Service]. * @param serviceFactory function that produces the actual implementation of the service that will handle the calls. @@ -55,7 +55,7 @@ public class KrpcRoute( * Service of any type should be unique on the server, but RpcServer does not specify the actual retention policy. * * @param Service the exact type of the server to be registered. - * For example for service with `MyService` interface and `MyServiceImpl` implementation, + * For example, for service with `MyService` interface and `MyServiceImpl` implementation, * type `MyService` should be specified explicitly. * @param serviceFactory function that produces the actual implementation of the service that will handle the calls. */ diff --git a/krpc/krpc-server/src/commonMain/kotlin/kotlinx/rpc/krpc/server/KrpcServer.kt b/krpc/krpc-server/src/commonMain/kotlin/kotlinx/rpc/krpc/server/KrpcServer.kt index ded0c3715..9b00e88a7 100644 --- a/krpc/krpc-server/src/commonMain/kotlin/kotlinx/rpc/krpc/server/KrpcServer.kt +++ b/krpc/krpc-server/src/commonMain/kotlin/kotlinx/rpc/krpc/server/KrpcServer.kt @@ -21,18 +21,11 @@ import kotlin.concurrent.Volatile import kotlin.reflect.KClass /** - * Default implementation of [RpcServer]. + * kRPC implementation of the [RpcServer]. * Takes care of tracking requests and responses, * serializing data, tracking streams, processing exceptions, and other protocol responsibilities. * Routes resulting messages to the proper registered services. - * Leaves out the delivery of encoded messages to the specific implementations. - * - * A simple example of how this server may be implemented: - * ```kotlin - * class MyTransport : KrpcTransport { /*...*/ } - * - * class MyServer(config: KrpcConfig.Server): KrpcServer(config, MyTransport()) - * ``` + * Leaves out the delivery of encoded messages to the specific implementations with [KrpcTransport]. * * @param config configuration provided for that specific server. Applied to all services that use this server. * @param transport [KrpcTransport] instance that will be used to send and receive RPC messages. @@ -43,6 +36,15 @@ public abstract class KrpcServer( private val config: KrpcConfig.Server, transport: KrpcTransport, ) : RpcServer, KrpcEndpoint { + + /* + * ##################################################################### + * # # + * # INTERNALS AHEAD # + * # # + * ##################################################################### + */ + @InternalRpcApi public val internalScope: CoroutineScope = CoroutineScope(SupervisorJob(transport.coroutineContext.job)) From 200719c4aa5dfb037ca2f9fc52112460b1284f77 Mon Sep 17 00:00:00 2001 From: Alexander Sysoev Date: Tue, 10 Jun 2025 21:06:49 +0200 Subject: [PATCH 2/3] Update Writerside docs --- docs/pages/kotlinx-rpc/rpc.tree | 6 +- .../topics/annotation-type-safety.topic | 25 ++- .../kotlinx-rpc/topics/configuration.topic | 157 ++++++++---------- docs/pages/kotlinx-rpc/topics/features.topic | 6 +- .../kotlinx-rpc/topics/krpc-client.topic | 75 +++++++++ docs/pages/kotlinx-rpc/topics/krpc-ktor.topic | 151 +++++++++++++++++ .../kotlinx-rpc/topics/krpc-server.topic | 43 +++++ docs/pages/kotlinx-rpc/topics/plugins.topic | 42 +++-- .../kotlinx-rpc/topics/rpc-clients.topic | 68 ++------ .../kotlinx-rpc/topics/rpc-servers.topic | 64 +++---- docs/pages/kotlinx-rpc/topics/services.topic | 118 ++++++++----- .../kotlinx-rpc/topics/strict-mode.topic | 39 +---- docs/pages/kotlinx-rpc/topics/transport.topic | 150 ++--------------- docs/pages/kotlinx-rpc/topics/versions.topic | 75 +++++---- 14 files changed, 575 insertions(+), 444 deletions(-) create mode 100644 docs/pages/kotlinx-rpc/topics/krpc-client.topic create mode 100644 docs/pages/kotlinx-rpc/topics/krpc-ktor.topic create mode 100644 docs/pages/kotlinx-rpc/topics/krpc-server.topic diff --git a/docs/pages/kotlinx-rpc/rpc.tree b/docs/pages/kotlinx-rpc/rpc.tree index aaf3fa079..048244ace 100644 --- a/docs/pages/kotlinx-rpc/rpc.tree +++ b/docs/pages/kotlinx-rpc/rpc.tree @@ -24,7 +24,11 @@ - + + + + + diff --git a/docs/pages/kotlinx-rpc/topics/annotation-type-safety.topic b/docs/pages/kotlinx-rpc/topics/annotation-type-safety.topic index 135146a7b..be8df1ed2 100644 --- a/docs/pages/kotlinx-rpc/topics/annotation-type-safety.topic +++ b/docs/pages/kotlinx-rpc/topics/annotation-type-safety.topic @@ -29,7 +29,7 @@

@Rpc - interface MyService : RemoteService + interface MyService class MyServiceImpl : MyService @@ -50,12 +50,31 @@ // T is resolved to MyService, // but 'body' returns MyServiceImpl - registerService<MyService> { ctx -> MyServiceImpl(ctx) } + registerService<MyService> { MyServiceImpl() } // Error: T is resolved to MyServiceImpl - registerService { ctx -> MyServiceImpl(ctx) } + registerService { MyServiceImpl() } +

+ Annotation type-safety can be enforced recursively: +

+ + @Rpc + annotation class Grpc + + @Grpc + interface MyGrpcService + + fun <@Rpc T> acceptAnyRpcType() + fun <@Grpc T> acceptOnlyGrpcType() + + acceptAnyRpcType<MyService>() // OK + acceptAnyRpcType<MyGrpcService>() // OK + + acceptOnlyGrpcType<MyService>() // Compiler time error + acceptOnlyGrpcType<MyGrpcService>() // OK + This feature is highly experimental and may lead to unexpected behaviour. If you encounter any issues, diff --git a/docs/pages/kotlinx-rpc/topics/configuration.topic b/docs/pages/kotlinx-rpc/topics/configuration.topic index 0b382bd18..18f195cb9 100644 --- a/docs/pages/kotlinx-rpc/topics/configuration.topic +++ b/docs/pages/kotlinx-rpc/topics/configuration.topic @@ -8,97 +8,78 @@ -

KrpcConfig is a class used to configure KrpcClient and KrpcServer - (not to be confused with KrpcClient and KrpcServer). - It has two children: KrpcConfig.Client and KrpcConfig.Server. - Client and Server may have shared properties as well as distinct ones. - To create instances of these configurations, DSL builders are provided - (KrpcConfigBuilder.Client class with rpcClientConfig function - and KrpcConfigBuilder.Server class with rpcServerConfig function respectively): +

+ KrpcConfig is a class used to configure KrpcClient and KrpcServer + (not to be confused with RpcClient and RpcServer). +

+

+ It has two children: KrpcConfig.Client and KrpcConfig.Server. + Client and Server may have shared properties as well as distinct ones. + To create instances of these configurations, DSL builders are provided: +

+ +
  • + rpcClientConfig +
  • +
  • + rpcServerConfig +
  • +
    + + + val config: KrpcConfig.Client = rpcClientConfig { // same for KrpcConfig.Server with rpcServerConfig + waitForServices = true // default parameter + } + +

    + The following configuration options are available: +

    + + + <code>serialization</code> DSL + +

    + This parameter defines how serialization should work in RPC services + and is present in both client and server configurations. +

    +

    + The serialization process is used to encode and decode data in RPC requests, + so that the data can be transferred through the network. +

    +

    + Currently only StringFormat and BinaryFormat from + kotlinx.serialization are supported, + and by default you can choose from Json, Protobuf or Cbor formats:

    - val config: KrpcConfig.Client = rpcClientConfig { // same for KrpcConfig.Server with rpcServerConfig - waitForServices = true // default parameter + rpcClientConfig { + serialization { + json { /* this: JsonBuilder from kotlinx.serialization */ } + cbor { /* this: CborBuilder from kotlinx.serialization */ } + protobuf { /* this: ProtobufBuilder from kotlinx.serialization */ } + } } -

    The following configuration options are available:

    - - - <code>serialization</code> DSL - -

    This parameter defines how serialization should work in RPC services - and is present in both client and server configurations.

    -

    The serialization process is used to encode and decode data in RPC requests, - so that the data can be transferred through the network.

    -

    Currently only StringFormat and BinaryFormat from - kotlinx.serialization are supported, - and by default you can choose from Json, Protobuf or Cbor formats:

    - - - rpcClientConfig { - serialization { - json { /* this: JsonBuilder from kotlinx.serialization */ } - cbor { /* this: CborBuilder from kotlinx.serialization */ } - protobuf { /* this: ProtobufBuilder from kotlinx.serialization */ } - } - } - -

    Only last defined format will be used to serialize requests. - If no format is specified, the error will be thrown. - You can also define a custom format.

    -
    - - - <code>sharedFlowParameters</code> DSL - - - - These parameters are deprecated since 0.5.0. For more information, - see the migration guide. - - - - rpcClientConfig { - sharedFlowParameters { - replay = 1 // default parameter - extraBufferCapacity = 10 // default parameter - onBufferOverflow = BufferOverflow.SUSPEND // default parameter - } - } - -

    This parameter is needed to decode SharedFlow parameters received from a peer. - MutableSharedFlow, the default function to create a SharedFlow instance, - has the following signature:

    - - - fun <T> MutableSharedFlow( - replay: Int = 0, - extraBufferCapacity: Int = 0, - onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND - ): MutableSharedFlow<T> { /* ... */ - } - -

    It creates a SharedFlowImpl class that contains these parameters as properties, - but this class in internal in kotlinx.coroutines and neither SharedFlow, - nor MutableShatedFlow interfaces define these properties, - which makes it impossible (at least for now) to send these properties from one endpoint to another. - But instances of these flows when deserialized should be created somehow, - so to overcome this - configuration parameter is created. - Configuration builder allows defining these parameters - and produces a builder function that is then placed into the KrpcConfig.

    -
    - - - <code>waitForServices</code> DSL - -

    waitForServices parameter is available for both client and server. - It specifies the behavior for an endpoint in situations - when the message for a service is received, - but the service is not present in KrpcClient or KrpcServer. - If set to true, the message will be stored in memory, - otherwise, the error will be sent to a peer endpoint, - saying that the message was not handled. - Default value is true.

    -
    +

    + Only last defined format will be used to serialize requests. + If no format is specified, a runtime error will be thrown. + You can also define a custom format. +

    +
    + + + <code>waitForServices</code> DSL + +

    + waitForServices parameter is available for both client and server. + It specifies the behavior for an endpoint in situations + when the message for a service is received, + but the service is not present in KrpcClient or KrpcServer. + If set to true, the message will be stored in memory, + otherwise, the error will be sent to a peer endpoint, + saying that the message was not handled. + Default value is true. +

    +
    diff --git a/docs/pages/kotlinx-rpc/topics/features.topic b/docs/pages/kotlinx-rpc/topics/features.topic index 8a11a73f9..c0b0c29d3 100644 --- a/docs/pages/kotlinx-rpc/topics/features.topic +++ b/docs/pages/kotlinx-rpc/topics/features.topic @@ -24,7 +24,7 @@ ) @Rpc - interface MyService : RemoteService { + interface MyService { fun sendStream(stream: Flow<Int>): Flow<String> suspend fun streamRequest(request: StreamRequest) @@ -44,7 +44,7 @@ ) @Rpc - interface MyService : RemoteService { + interface MyService { fun serverStream(): Flow<String> // ok suspend fun serverStream(): Flow<String> // not ok suspend fun serverStream(): StreamResult // not ok @@ -52,7 +52,7 @@ - Note that flows that are declared in classes (like in StreamResult) require a + Note that flows that are declared in classes (like in StreamRequest) require a Contextual annotation. diff --git a/docs/pages/kotlinx-rpc/topics/krpc-client.topic b/docs/pages/kotlinx-rpc/topics/krpc-client.topic new file mode 100644 index 000000000..ee70d0493 --- /dev/null +++ b/docs/pages/kotlinx-rpc/topics/krpc-client.topic @@ -0,0 +1,75 @@ + + + + + + + +

    + KrpcClient is an abstract class that implements RpcClient and kRPC protocol + logic. +

    + +

    + The client comes in two forms: +

    + +
  • + KrpcClient +
  • +
  • + InitializedKrpcClient +
  • +
    +

    + The only difference between them is that KrpcClient allows to delay the initialization + of the transport until the first RPC request is sent. + InitializedKrpcClient is initialized right away with a ready KrpcTransport + instance. +

    +
    + +

    + The only thing required to be implemented is the transporting of the raw data. + Abstract transport is represented by KrpcTransport interface. +

    +

    + To implement your own KrpcTransport + you need to be able to transfer strings and/or raw bytes (Kotlin's ByteArray). + Additionally, the library will provide you with integrations with different + libraries that are used to work with the network. +

    + +

    + See below an example usage of kRPC with a custom transport: +

    + + + class MySimpleRpcTransport : KrpcTransport { + val outChannel = Channel<KrpcTransportMessage>() + val inChannel = Channel<KrpcTransportMessage>() + + override val coroutineContext: CoroutineContext = Job() + + override suspend fun send(message: KrpcTransportMessage) { + outChannel.send(message) + } + + override suspend fun receive(): KrpcTransportMessage { + return inChannel.receive() + } + } + + class MySimpleRpcClient : KrpcClient(rpcClientConfig(), MySimpleRpcTransport()) + + val client = MySimpleRpcClient() + val service: MyService = client.withService<MyService>() + +
    +
    +
    diff --git a/docs/pages/kotlinx-rpc/topics/krpc-ktor.topic b/docs/pages/kotlinx-rpc/topics/krpc-ktor.topic new file mode 100644 index 000000000..d747c4056 --- /dev/null +++ b/docs/pages/kotlinx-rpc/topics/krpc-ktor.topic @@ -0,0 +1,151 @@ + + + + + + +

    + The kotlinx.rpc library provides integration with the Ktor + framework. + This includes both server and client APIs. + Under the hood, the library uses WebSocket plugin + to create a KrpcTransport and send and receive messages through it. +

    + +

    + kotlinx.rpc provides a way to plug-in into existing Ktor clients with your RPC services. + To do that, the following DSL can be used: +

    + + + val ktorClient = HttpClient { + installKrpc { + waitForServices = true + } + } + + val rpcClient: KtorKrpcClient = + ktorClient.rpc("ws://localhost:4242/services") { + rpcConfig { + waitForServices = false + } + } + + // get an RPC service + val myService: MyService = rpcClient.withService<MyService>() + + + Note that in this example, only the latter defined KrpcConfig will be used. + +
    + +

    + kotlinx.rpc provides a way to plug-in into existing server routing with your RPC + services. + To do that, the following DSL can be used: +

    + + + fun Application.module() { + install(Krpc) { + waitForServices = true + } + + routing { + rpc("/services") { + rpcConfig { + waitForServices = false + } + + registerService<MyService> { MyServiceImpl() } + registerService<MyOtherService> { MyOtherServiceImpl() } + // more services if needed + } + } + } + +
    + +

    + An example code for a Ktor web application may look like this: +

    + +

    + In common code, shared classes and services are defined: +

    + + @Serializable + data class ProcessedImage( + val url: String, + val numberOfCats: Int, + val numberOfDogs: Int + ) + + @Rpc + interface ImageService : RemoteService { + suspend fun processImage(url: String): ProcessedImage + } + +

    + In client code, create an HTTP client and access the service on a the desired route: +

    + + val client = HttpClient { + installKrpc { + serialization { + json() + } + } + } + + val service = client + .rpc("/image-recognizer") + .withService<ImageService>() + + service.processImage(url = "https://catsanddogs.com/cats/1") + +

    + In server code, provide an implementation of the ImageService and register it on a route: +

    + + class ImageServiceImpl : ImageService { + // some user defined classes + private val downloader = Downloader() + private val recognizer = AnimalRecognizer() + + override suspend fun processImage(url: Srting): ProcessedImage { + val image = downloader.loadImage(url) + return ProcessedImage( + url, + recognizer.getNumberOfCatsOnImage(image), + recognizer.getNumberOfDogsOnImage(image) + ) + } + } + + fun main() { + embeddedServer(Netty, port = 8080) { + install(Krpc) { + serialization { + json() + } + } + + routing { + rpc("/image-recognizer") { + registerService<ImageService> { ImageServiceImpl() } + } + } + }.start(wait = true) + } + +

    + For more details and complete examples, see the code samples. +

    +
    +
    diff --git a/docs/pages/kotlinx-rpc/topics/krpc-server.topic b/docs/pages/kotlinx-rpc/topics/krpc-server.topic new file mode 100644 index 000000000..d7da1d6f8 --- /dev/null +++ b/docs/pages/kotlinx-rpc/topics/krpc-server.topic @@ -0,0 +1,43 @@ + + + + + + + +

    + KrpcServer abstract class implements RpcServer + and all the logic for processing RPC messages + and again leaves KrpcTransport methods for the specific implementations + (see transports). +

    + +

    + Example usage with custom transport: +

    + + + // same MySimpleRpcTransport as in the client example above + class MySimpleRpcServer : KrpcServer(rpcServerConfig(), MySimpleRpcTransport()) + + val server = MySimpleRpcServer() + server.registerService<MyService> { MyServiceImpl() } + + +

    + Note that here we pass explicit MyService type parameter to the registerService + method. + You must explicitly specify the type of the service interface here, + otherwise the server service will not be found. +

    +

    + See for more details. +

    +
    +
    +
    diff --git a/docs/pages/kotlinx-rpc/topics/plugins.topic b/docs/pages/kotlinx-rpc/topics/plugins.topic index 67f4fe05d..c85326af0 100644 --- a/docs/pages/kotlinx-rpc/topics/plugins.topic +++ b/docs/pages/kotlinx-rpc/topics/plugins.topic @@ -9,22 +9,32 @@ xsi:noNamespaceSchemaLocation="https://resources.jetbrains.com/writerside/1.0/topic.v2.xsd" title="Gradle plugin" id="plugins">

    - The kotlinx.rpc library offers a Gradle plugin - that simplifies project configuration by automating repetitive tasks: `org.jetbrains.kotlinx.rpc.plugin` + The kotlinx.rpc library offers a Gradle + plugin + that simplifies project configuration by automating repetitive tasks and configuring code generation.

    - -

    - The org.jetbrains.kotlinx.rpc.plugin plugin sets up code generation configurations. -

    - - plugins { - kotlin("jvm") version "%kotlin-version%" - id("org.jetbrains.kotlinx.rpc.plugin") version "%kotlinx-rpc-version%" - } - -

    - For multi-project setups you must add the plugin to all modules where services are declared or used. -

    -
    + + plugins { + kotlin("jvm") version "%kotlin-version%" + id("org.jetbrains.kotlinx.rpc.plugin") version "%kotlinx-rpc-version%" + } + + + For multi-project setups you must add the plugin to all modules where services are declared or used. + + + Plugin provides an rpc extension: + + rpc { + annotationTypeSafetyEnabled = true + } + +

    Options:

    + +
  • + annotationTypeSafetyEnabled - whether to enable type safety checks for annotations. + See . +
  • +
    diff --git a/docs/pages/kotlinx-rpc/topics/rpc-clients.topic b/docs/pages/kotlinx-rpc/topics/rpc-clients.topic index 7aa8c2024..30deafd23 100644 --- a/docs/pages/kotlinx-rpc/topics/rpc-clients.topic +++ b/docs/pages/kotlinx-rpc/topics/rpc-clients.topic @@ -8,22 +8,22 @@ -

    - For each declared service, kotlinx.rpc will generate an actual client implementation - that can be used to send requests to a server. You must not use generated code directly. Instead, - you should use the APIs that will provide you with the instance of your interface. This generated instance is - commonly called a stub in RPC. -

    +

    + For each declared service, kotlinx.rpc will generate an actual client implementation + that can be used to send requests to a server. This generated instance is + commonly called a stub in RPC. +

    - Note that we talk about generated stubs (service implementations on the client side) - that must not be called directly. - There might be a case when the generated code is not a stub, but a service declaration itself - (for example, the services generated from -
    .proto files). - In this case, you can use the generated code. + Note that we talk about generated stubs (service interface implementations on the client side) + that must not be called directly. + There might be a case when the generated code is not a stub, but a service declaration itself + (for example, the services generated from + .proto files). + In this case, you can use the generated code. + See .

    - To be able to obtain an instance of your service, you need to have an RpcClient. + To be able to get an instance of your service, you need to have an RpcClient. You can call the withService() method on your client:

    @@ -31,6 +31,9 @@ val myService: MyService = rpcClient.withService<MyService>() + + Note that the type parameter of withService must be annotated with @Rpc annotation. +

    Now you have your client instance that is able to send RPC requests to your server. RpcClient can have multiple services that communicate through it. @@ -42,42 +45,7 @@

    You can provide your own implementations of the RpcClient. But kotlinx.rpc already provides one out-of-the-box solution that uses - in-house RPC protocol (called kRPC), and we are working on supporting more protocols - (with priority on gRPC). + in-house RPC protocol (called kRPC), and we are working on supporting more + protocols (see ).

    - - -

    KrpcClient is an abstract class that implements RpcClient and kRPC protocol - logic. - The only thing required to be implemented is the transporting of the raw data. - Abstract transport is represented by KrpcTransport interface.

    -

    To implement your own KrpcTransport - you need to be able to transfer strings and/or raw bytes (Kotlin's ByteArray). - Additionally, the library will provide you with integrations with different - libraries that are used to work with the network. -

    -

    See below an example usage of kRPC with a custom transport:

    - - - class MySimpleRpcTransport : KrpcTransport { - val outChannel = Channel<KrpcTransportMessage>() - val inChannel = Channel<KrpcTransportMessage>() - - override val coroutineContext: CoroutineContext = Job() - - override suspend fun send(message: KrpcTransportMessage) { - outChannel.send(message) - } - - override suspend fun receive(): KrpcTransportMessage { - return inChannel.receive() - } - } - - class MySimpleRpcClient : KrpcClient(rpcClientConfig(), MySimpleRpcTransport()) - - val client = MySimpleRpcClient() - val service: MyService = client.withService<MyService>() - - diff --git a/docs/pages/kotlinx-rpc/topics/rpc-servers.topic b/docs/pages/kotlinx-rpc/topics/rpc-servers.topic index 7f2e3b97c..3d9d9e104 100644 --- a/docs/pages/kotlinx-rpc/topics/rpc-servers.topic +++ b/docs/pages/kotlinx-rpc/topics/rpc-servers.topic @@ -8,51 +8,39 @@ -

    RpcServer interface represents an RPC server, - that accepts RPC messages and may contain multiple services to route to. - RpcServer uses data from incoming RPC messages - and routes it to the designated service and sends service's response back to the corresponding client. -

    -

    You can provide your own RpcServer implementation - or use the one provided out of the box. - Note that client and server must use the same RPC protocol to communicate.

    -

    Use registerService function to add your own factory for implemented RPC services. - This factory function should accept CoroutineContext argument and pass it to the service, - which should use it to override coroutineContext property of parent interface. - This ensures proper application lifetime for your services.

    -

    Example usage:

    +

    + RpcServer interface represents an RPC server, + that accepts RPC messages and may contain multiple services to route to. + RpcServer uses data from incoming RPC messages + and routes it to the designated service and sends service's response back to the corresponding client. +

    +

    + You can provide your own RpcServer implementation + or use the one provided out of the box. + Note that client and server must use the same RPC protocol to communicate. +

    +

    + Use registerService function to add your own factory for implemented RPC services: +

    - - val server: RpcServer + + val server: RpcServer - server.registerService<MyService> { ctx: CoroutineContext -> MyServiceImpl(ctx) } - -

    The registerService function requires the explicit type of the declared RPC service. + server.registerService<MyService> { MyServiceImpl() } + + +

    + The registerService function requires the explicit type of the declared RPC service. That means that the code will not work if you provide it with the type of the service implementation:

    // Wrong! Should be `MyService` as type argument - server.registerService<MyServiceImpl> { ctx: CoroutineContext -> MyServiceImpl(ctx) } + server.registerService<MyServiceImpl> { MyServiceImpl() } - -

    KrpcServer abstract class implements RpcServer - and all the logic for processing RPC messages - and again leaves RpcTransport methods for the specific implementations - (see transports).

    -

    Example usage with custom transport:

    - - - // same MySimpleRpcTransport as in the client example above - class MySimpleRpcServer : KrpcServer(rpcServerConfig(), MySimpleRpcTransport()) - - val server = MySimpleRpcServer() - server.registerService<MyService> { ctx -> MyServiceImpl(ctx) } - -

    Note that here we pass explicit MyService type parameter to the registerService - method. - You must explicitly specify the type of the service interface here, - otherwise the server service will not be found.

    -
    +

    + See for more details. +

    +
    diff --git a/docs/pages/kotlinx-rpc/topics/services.topic b/docs/pages/kotlinx-rpc/topics/services.topic index 0cfa5ecb2..50bf9eb09 100644 --- a/docs/pages/kotlinx-rpc/topics/services.topic +++ b/docs/pages/kotlinx-rpc/topics/services.topic @@ -9,45 +9,87 @@ xsi:noNamespaceSchemaLocation="https://resources.jetbrains.com/writerside/1.0/topic.v2.xsd" title="Services" id="services">

    Services are the centerpieces of the library. - A service is an interface annotated with the @Rpc annotation, - and contains a set of methods and fields - that can be executed or accessed remotely. - Additionally, a service always has a type of RemoteService, - which can be specified explicitly, or assumed implicitly by the compiler. + A service is an interface annotated with the @Rpc annotation, + and contains a set of methods that can be executed remotely.

    - - Note that implicit typing is currently not supported in IDEs, but is a work in progress. -

    A simple service can be declared as follows:

    - - @Rpc - interface MyService : RemoteService { - suspend fun hello(name: String): String - } - -

    Here we declare the method hello, that accepts one argument and returns a string. - The method is marked with a suspend modifier, which means that we use - coroutines - to send RPC requests. - Note that for now, only suspend methods are supported in service interfaces.

    - -

    Depending on the type of the protocol you use, services may support different features and - declarations.

    -
    -

    To be able to use a service both in client and server code, - the interface should be placed in the common module - — kudos to Kotlin Multiplatform.

    -

    Now we can implement the service, so server knows how to process incoming requests.

    - - - class MyServiceImpl( - override val coroutineContext: CoroutineContext, - ) : MyService { - override suspend fun hello(name: String): String { - return "Hello, $name! I'm server!" - } - } - -

    The server will use that implementation to answer the client requests.

    + + @Rpc + interface MyService { + suspend fun hello(name: String): String + } + +

    + Here we declare the method hello, that accepts one argument and returns a string. + The method is marked with a suspend modifier, which means that we use + coroutines + to send RPC requests. +

    + +

    + Depending on the type of the protocol you use, services may support different features and + declarations. +

    +
    +

    + To be able to use a service both in client and server code, + the interface should be placed in the common module + — kudos to Kotlin Multiplatform. +

    +

    + Now we can implement the service, so the server knows how to process incoming requests. +

    + + class MyServiceImpl : MyService { + override suspend fun hello(name: String): String { + return "Hello, $name! I'm server!" + } + } + +

    + The server will use that implementation to answer the client requests. +

    + +

    + The library supports streaming of data. + To declare a streaming method, you need to use the Flow type. + For example, to declare a method that returns a stream of integers: +

    + + @Rpc + interface MyService { + fun stream(): Flow<Int> + } + +

    + Note that the method is not marked with the suspend modifier. +

    +

    + You can also pass a Flow as a parameter to the method. + For example, to declare a method that accepts a stream of integers: +

    + + @Rpc + interface MyService { + suspend fun sendStream(stream: Flow<Int>) + } + +

    + Or make streams bidirectional: +

    + + @Rpc + interface MyService { + fun bidi(stream: Flow<Int>): Flow<Int> + } + + +

    + Note that the function is not suspend + only when the return type is a Flow. + Flow in parameter list does not affect the function's signature. +

    +
    +
    diff --git a/docs/pages/kotlinx-rpc/topics/strict-mode.topic b/docs/pages/kotlinx-rpc/topics/strict-mode.topic index 0d512e3ba..33e660682 100644 --- a/docs/pages/kotlinx-rpc/topics/strict-mode.topic +++ b/docs/pages/kotlinx-rpc/topics/strict-mode.topic @@ -10,11 +10,11 @@ title="Strict mode" id="strict-mode">

    - Starting with version 0.5.0, the library introduces major changes to the service APIs. - The following declarations will be gradually restricted: + Starting with version 0.5.0, the library introduced major changes to the service APIs. + The following declarations are now restricted:

    - String mode will be enforced in the 0.8.0 release. + Strict mode is enforced irreversibly since 0.8.0.

    Deprecation level: ERROR

    @@ -148,37 +148,4 @@ }
    - - -

    - Deprecation levels are controlled by the Gradle rpc extension: -

    - - // build.gradle.kts - plugins { - id("org.jetbrains.kotlinx.rpc.plugin") - } - - rpc { - strict { - stateFlow = RpcStrictMode.ERROR - sharedFlow = RpcStrictMode.ERROR - nestedFlow = RpcStrictMode.ERROR - notTopLevelServerFlow = RpcStrictMode.ERROR - fields = RpcStrictMode.ERROR - suspendingServerStreaming = RpcStrictMode.ERROR - streamScopedFunctions = RpcStrictMode.ERROR - } - } - -

    - Modes RpcStrictMode.NONE and RpcStrictMode.WARNING are available. -

    - - - 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/topics/transport.topic b/docs/pages/kotlinx-rpc/topics/transport.topic index 8eb26bd7b..2beca318d 100644 --- a/docs/pages/kotlinx-rpc/topics/transport.topic +++ b/docs/pages/kotlinx-rpc/topics/transport.topic @@ -9,143 +9,21 @@ xsi:noNamespaceSchemaLocation="https://resources.jetbrains.com/writerside/1.0/topic.v2.xsd" title="Transport" id="transport"> -

    Transport layer exists to abstract from the RPC requests logic and focus on delivering and receiving - encoded RPC messages in kRPC Protocol. - This layer is represented by KrpcTransport interface. - It supports two message formats — string and binary, - and depending on which serialization format you choose, - one or the other will be used.

    - - -

    The kotlinx.rpc library provides integration with the Ktor - framework with the in-house RPC - protocol. - This includes both server and client APIs. - Under the hood, the library uses WebSocket plugin - to create a KrpcTransport and send and receive messages through it.

    - -

    kotlinx.rpc provides a way to plug-in into existing Ktor clients with your RPC services. - To do that, the following DSL can be used:

    - - - val ktorClient = HttpClient { - installKrpc { // this: KrpcConfigBuilder.Client - waitForServices = true - } - } - - val rpcClient: KtorKrpcClient = - ktorClient.rpc("ws://localhost:4242/services") { // this: HttpRequestBuilder - rpcConfig { // this: KrpcConfigBuilder.Client - waitForServices = false - } - } - - // access WebSocketSession that created the connection - rpcClient.webSocketSession - - // create RPC service - val myService: MyService = rpcClient.withService<MyService>() - -

    Note that in this example, only the latter defined KrpcConfig will be used.

    -
    - -

    kotlinx.rpc provides a way to plug-in into existing server routing with your RPC - services. - To do that, the following DSL can be used:

    - - - fun Application.module() { - install(Krpc) { // this: KrpcConfigBuilder.Server - waitForServices = true - } - - routing { - rpc("/services") { // this KrpcRoute, inherits WebSocketSession - rpcConfig { // this: KrpcConfigBuilder.Server - waitForServices = false - } - - registerService<MyService> { ctx -> MyServiceImpl(ctx) } - registerService<MyOtherService> { ctx - MyOtherServiceImpl(ctx) } - // etc - } - } - } - -
    - -

    An example code for a Ktor web application may look like this:

    - - - // ### COMMON CODE ### - @Serializable - data class ProcessedImage( - val url: String, - val numberOfCats: Int, - val numberOfDogs: Int - ) - - @Rpc - interface ImageService : RemoteService { - suspend fun processImage(url: String): ProcessedImage - } - - // ### CLIENT CODE ### - - val client = HttpClient { - installKrpc { - serialization { - json() - } - } - } - - val service = client.rpc("/image-recognizer").withService<ImageService>() - - service.processImage(url = "https://catsanddogs.com/cats/1") - - // ### SERVER CODE ### - - class ImageServiceImpl(override val coroutineContext: CoroutineContext) : ImageService { - // user defined classes - private val downloader = Downloader() - private val recognizer = AnimalRecognizer() - - override suspend fun processImage(url: Srting): ProcessedImage { - val image = downloader.loadImage(url) - return ProcessedImage( - url, - recognizer.getNumberOfCatsOnImage(image), - recognizer.getNumberOfDogsOnImage(image) - ) - } - } - - fun main() { - embeddedServer(Netty, port = 8080) { - install(Krpc) { - serialization { - json() - } - } - - routing { - rpc("/image-recognizer") { - registerService<ImageService> { ctx -> ImageServiceImpl(ctx) } - } - } - }.start(wait = true) - } - -

    For more details and complete examples, see the code samples. -

    -
    -
    - -

    Generally, there are no specific guidelines on how RPC should be set up for different transports, +

    + Transport layer exists to abstract from the RPC requests logic and focus on delivering and receiving + encoded RPC messages in kRPC Protocol. + This layer is represented by the KrpcTransport interface. + It supports two message formats — string and binary, + and depends on which serialization format you + choose. +

    + + +

    + Generally, there are no specific guidelines on how RPC should be set up for different transports, but structures and APIs used to develop integration with Ktor should outline the common approach. You can provide your own transport and even your own fully implemented protocols, - while the library will take care of code generation.

    + while the library will take care of code generation. +

    diff --git a/docs/pages/kotlinx-rpc/topics/versions.topic b/docs/pages/kotlinx-rpc/topics/versions.topic index 5532ec839..877238bb4 100644 --- a/docs/pages/kotlinx-rpc/topics/versions.topic +++ b/docs/pages/kotlinx-rpc/topics/versions.topic @@ -8,42 +8,47 @@ -

    As kotlinx.rpc uses Kotlin compiler plugin, - we rely on internal functionality that may change over time with any new Kotlin version. - To prevent the library from breaking with an incompatible Kotlin version, - we use version prefix for artifacts with code generating functionality. -

    -

    - We provide core version updates for all stable versions of - the last three - major Kotlin releases. - So if the last stable Kotlin version is %kotlin-version%, as at the time of writing this guide, - the following versions of Kotlin are supported: -

    - -
  • 2.0.0, 2.0.10, 2.0.20, 2.0.21
  • -
  • 2.1.0, 2.1.10, 2.1.20, 2.1.21
  • -
    +

    + As kotlinx.rpc uses Kotlin compiler plugin, + we rely on internal functionality that may change over time with any new Kotlin version. + To prevent the library from breaking with an incompatible Kotlin version, + we use version prefix for artifacts with code generating functionality. +

    +

    + We provide core version updates for all stable versions of + the last three + major Kotlin releases. + So if the last stable Kotlin version is %kotlin-version%, as at the time of writing this guide, + the following versions of Kotlin are supported: +

    + +
  • 2.0.0, 2.0.10, 2.0.20, 2.0.21
  • +
  • 2.1.0, 2.1.10, 2.1.20, 2.1.21
  • +
    +

    + Our code generation will support these versions (See more on code + generation artifacts). + Runtime artifacts are configured with + + language-version and api-version parameters + + for the oldest supported minor version of Kotlin. +

    + + The kotlinx-rpc library is currently not stable. + As a result, we cannot guarantee compatibility with all Kotlin versions for our runtime dependencies at this + time. + However, we are committed to maintaining compatibility as much as possible. + It is in our plans to ensure that all runtime dependencies are compatible with all supported Kotlin versions by + the time of the stable release. + +

    - Our code generation will support these versions (See more on code generation artifacts). - Runtime artifacts are configured with - - language-version and api-version parameters - - for the oldest supported minor version of Kotlin. + To simplify project configuration, our + is able to set proper runtime dependencies versions automatically based + on the project's Kotlin version and the Gradle plugin version + which is used as a core library version.

    - - The kotlinx-rpc library is currently not stable. - As a result, we cannot guarantee compatibility with all Kotlin versions for our runtime dependencies at this time. - However, we are committed to maintaining compatibility as much as possible. - It is in our plans to ensure that all runtime dependencies are compatible with all supported Kotlin versions by the time of the stable release. - - -

    - To simplify project configuration, both our Gradle plugins - are able to set proper runtime dependencies versions automatically based - on the project's Kotlin version and the Gradle plugin version - which is used as a core library version.

    plugins { // project's Kotlin plugin @@ -77,6 +82,6 @@ org.jetbrains.kotlinx:kotlinx-rpc-compiler-plugin-k2:%kotlin-version%-%kotlinx-rpc-version% org.jetbrains.kotlinx:kotlinx-rpc-compiler-plugin-backend:%kotlin-version%-%kotlinx-rpc-version% - Such dependencies are managed automatically, and should not be used explicitly. + These dependencies are managed automatically, and should not be used explicitly.
    From 125f9ea678c6b251517b26000debd8dcd41762fe Mon Sep 17 00:00:00 2001 From: Alexander Sysoev Date: Tue, 10 Jun 2025 22:23:17 +0200 Subject: [PATCH 3/3] Added 0.8.0 migration guide --- docs/pages/kotlinx-rpc/rpc.tree | 1 + docs/pages/kotlinx-rpc/topics/0-8-0.topic | 366 ++++++++++++++++++++++ 2 files changed, 367 insertions(+) create mode 100644 docs/pages/kotlinx-rpc/topics/0-8-0.topic diff --git a/docs/pages/kotlinx-rpc/rpc.tree b/docs/pages/kotlinx-rpc/rpc.tree index 048244ace..979360ba4 100644 --- a/docs/pages/kotlinx-rpc/rpc.tree +++ b/docs/pages/kotlinx-rpc/rpc.tree @@ -40,6 +40,7 @@ + diff --git a/docs/pages/kotlinx-rpc/topics/0-8-0.topic b/docs/pages/kotlinx-rpc/topics/0-8-0.topic new file mode 100644 index 000000000..3ae6ff98b --- /dev/null +++ b/docs/pages/kotlinx-rpc/topics/0-8-0.topic @@ -0,0 +1,366 @@ + + + + + + +

    + Version 0.8.0 brings a lot of changes, + mainly targeted to remove inherently broken functionality and simplify kRPC protocol where possible. + This is reflected in the number of breaking changes and deprecations. +

    +

    + This page aims to cover all such changes and associated migration instructions. +

    + + +

    + Strict mode is now enforced and can't be disabled. + See for detailed migrations. +

    +
    + + +

    + The following changes are reflected in the kRPC protocol on the wire: +

    + +
  • + KrpcServer doesn't send CANCELLATION_ACK messages anymore. +
  • +
  • + KrpcClient sends REQUEST cancellation messages for every individually finished call, + canceled or finished successfully +
  • +
    +

    + Though changes should not affect most users, + for those who like to look at the wire it might be useful to know. +

    +
    + + +

    + Some changes in the behavior of kRPC clients and servers: +

    + +
  • +

    + Lifetime for client-side streams is changed. +

    +

    + Previously, the stream scopes bounded client-side streams. + However, stream scopes are completely removed now, + so the client-side streams lifetime is now bound to the request's lifetime. + This means that when the function returns, every client stream is closed. +

    +
  • +
  • +

    + Serial format is now only constructed once per client. +

    +

    + Previously, the serial format was constructed once per RPC call. + The serial format can be passed using the KrpcConfig. + And the builder code was executed once per every call. +

    +

    + Now this behavior is removed. + The serial format is constructed once per client. +

    +
  • +
  • +

    + Services are now instantiated once per service type (Rpc FQ name) + and not once per client-side instance. +

    +

    + Services lost their CoroutineScopes (see ). + That means that there are no individual lifetimes for each service instance now. + Instead, now each service stub on a client is merely a proxy for function calls. + And on the server side, the service implementation is instantiated once per service type. + To control this behavior more granularly on the server, new deregisterService + function is introduced. +

    + + For kRPC servers, the factory function for service instances is now executed once per service type: + + rpcServer.registerService<MyService> { MyServiceImpl() } + + +
  • +
  • +

    + Handshake is now cold. +

    +

    + Previously, the handshake was executed on client creation. + Now, the handshake is executed on the first RPC request. +

    +
  • + +
    + + + +
  • +

    + RpcClient.callServerStreaming lost its default implementation: +

    + + + + interface RpcClient { + fun <T> callServerStreaming(call: RpcCall): Flow<T> { + error("Not implemented") + } + } + + + interface RpcClient { + fun <T> callServerStreaming(call: RpcCall): Flow<T> + } + + +
  • +
  • +

    + @Rpc services lost their CoroutineScope: +

    + + + val service = client.withService<MyService>() + assert(service is CoroutineScope) // OK + + + val service = client.withService<MyService>() + assert(service is CoroutineScope) // fail + + +
  • +
  • +

    + RpcClient lost its CoroutineScope: +

    + + + interface RpcClient : CoroutineScope + + + interface RpcClient + + +
  • +
  • +

    + RpcServer lost its CoroutineScope: +

    + + + interface RpcServer : CoroutineScope + + + interface RpcServer + + +
  • +
  • +

    + RpcServer.registerService factory parameter changes type + from (CoroutineContext) -> Service to () -> Service: +

    + + + interface RpcServer { + fun <@Rpc Service : Any> registerService( + serviceKClass: KClass<Service>, + serviceFactory: (CoroutineContext) -> Service, + ) + } + + inline fun <@Rpc reified Service : Any> RpcServer.registerService( + noinline serviceFactory: (CoroutineContext) -> Service, + ) { + registerService(Service::class, serviceFactory) + } + + + interface RpcServer { + fun <@Rpc Service : Any> registerService( + serviceKClass: KClass<Service>, + serviceFactory: () -> Service, + ) + } + + inline fun <@Rpc reified Service : Any> RpcServer.registerService( + noinline serviceFactory: () -> Service, + ) { + registerService(Service::class, serviceFactory) + } + + +
  • +
  • +

    + RpcServer.registerService lost its CoroutineContext parameter: +

    + + + interface RpcServer + + + interface RpcServer { + fun <@Rpc Service : Any> deregisterService( + serviceKClass: KClass<Service>, + ) + } + + +
  • +
  • +

    + For Ktor, HttpClient.rpc extension function is now non-suspendable. +

    +
  • +
  • + KtorRpcClient.webSocketSession is now wrapped in Deferred: + + + interface KtorRpcClient : RpcClient { + val webSocketSession: WebSocketSession + } + + + interface KtorRpcClient : RpcClient { + val webSocketSession: Deferred<WebSocketSession> + } + + +
  • +
  • +

    + KrpcClient abstract class has two new abstract methods: + initializeConfig and initializeTransport. + They can be used to delay transport initialization until the first RPC call. +

    +

    + To mimic old behavior, InitializedKrpcClient can be used: +

    + + + class MyClient( + config: KrpcConfig, + transport: KrpcTransport, + ) : KrpcClient(config, transport) + + + class MyClient( + config: KrpcConfig, + transport: KrpcTransport, + ) : InitializedKrpcClient(transport, config) + + + + Notice that the parameter order is reversed in new InitializedKrpcClient. + +
  • +
    +
    + + +

    + The following APIs are removed: +

    + +
  • kotlinx.rpc.RpcClient.callAsync - previously deprecated
  • +
  • kotlinx.rpc.RpcClient.provideStubContext
  • + +
  • kotlinx.rpc.registerPlainFlowField - previously deprecated
  • +
  • kotlinx.rpc.registerSharedFlowField - previously deprecated
  • +
  • kotlinx.rpc.registerStateFlowField - previously deprecated
  • +
  • kotlinx.rpc.awaitFieldInitialization - previously deprecated
  • +
  • kotlinx.rpc.UninitializedRpcFieldException - previously deprecated
  • +
  • kotlinx.rpc.UninitializedRPCFieldException - previously deprecated
  • +
  • kotlinx.rpc.RpcEagerField - previously deprecated
  • +
  • kotlinx.rpc.RPCCall - previously deprecated alias
  • +
  • kotlinx.rpc.RPCClient - previously deprecated alias
  • + +
  • kotlinx.rpc.descriptor.RpcInvokator.Field - previously deprecated
  • +
  • kotlinx.rpc.descriptor.RpcServiceDescriptor.getFields - previously deprecated
  • + +
  • kotlinx.rpc.krpc.StreamScope - previously deprecated
  • +
  • kotlinx.rpc.krpc.streamScoped - previously deprecated
  • +
  • kotlinx.rpc.krpc.withStreamScope - previously deprecated
  • +
  • kotlinx.rpc.krpc.invokeOnStreamScopeCompletion - previously deprecated
  • +
  • kotlinx.rpc.krpc.KrpcConfigBuilder.SharedFlowParametersBuilder - previously deprecated
  • +
  • kotlinx.rpc.krpc.KrpcConfigBuilder.sharedFlowParameters - previously deprecated
  • +
  • kotlinx.rpc.krpc.KrpcConfig.sharedFlowBuilder - previously deprecated
  • +
  • kotlinx.rpc.krpc.RPCTransport - previously deprecated alias
  • +
  • kotlinx.rpc.krpc.RPCTransportMessage - previously deprecated alias
  • +
  • kotlinx.rpc.krpc.RPCConfigBuilder - previously deprecated alias
  • +
  • kotlinx.rpc.krpc.client.KRPCClient - previously deprecated alias
  • +
  • kotlinx.rpc.krpc.server.RPCServer - previously deprecated alias
  • +
  • kotlinx.rpc.krpc.server.KRPCServer - previously deprecated alias
  • +
  • kotlinx.rpc.krpc.serialization.RPCSerialFormat - previously deprecated alias
  • +
  • kotlinx.rpc.krpc.serialization.RPCSerialFormatBuilder - previously deprecated alias
  • +
  • kotlinx.rpc.krpc.serialization.RPCSerialFormatConfiguration - previously deprecated alias
  • +
  • kotlinx.rpc.krpc.ktor.client.RPC - previously deprecated alias
  • +
  • kotlinx.rpc.krpc.ktor.client.installRPC - previously deprecated alias
  • +
  • kotlinx.rpc.krpc.ktor.server.RPC - previously deprecated alias
  • +
  • kotlinx.rpc.krpc.ktor.server.RPCRoute - previously deprecated alias
  • +
    +
    + + + +
  • +

    + Gradle's rpc.strict extension is deprecated with an error. + Strict mode is now enforced and can't be disabled. + See for detailed migrations. +

    +
  • +
  • +

    + RemoteService is deprecated with error; + services are no longer having this interface added during the compilation. + See for services' lifetime information. +

    +
  • + + + + + +
  • +

    + MISSING_RPC_ANNOTATION compiler inspection is removed. +

    +
  • +
  • +

    + ABI incompatible change: KrpcTransport.receiveCatching is now an extension function. +

    +
  • +
  • +

    + The following compiler plugin options are removed: +

    + +
  • strict-stateFlow
  • +
  • strict-sharedFlow
  • +
  • strict-nested-flow
  • +
  • strict-stream-scope
  • +
  • strict-suspending-server-streaming
  • +
  • strict-not-top-level-server-flow
  • +
  • strict-fields
  • +
    + + +
    +