{"id":15294684,"url":"https://github.com/rnett/krosstalk","last_synced_at":"2025-04-13T14:54:21.420Z","repository":{"id":43684671,"uuid":"280773642","full_name":"rnett/krosstalk","owner":"rnett","description":"A pure Kotlin pluggable RPC library, with expect/actual support","archived":false,"fork":false,"pushed_at":"2023-11-12T03:10:14.000Z","size":20204,"stargazers_count":23,"open_issues_count":6,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-13T14:54:07.932Z","etag":null,"topics":["compiler-plugin","kotlin","kotlinx-serialization","ktor-client","ktor-server","plugable","rpc"],"latest_commit_sha":null,"homepage":"https://rnett.github.io/krosstalk/release/","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rnett.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-07-19T02:01:43.000Z","updated_at":"2024-05-24T17:43:31.000Z","dependencies_parsed_at":"2022-08-29T22:50:32.711Z","dependency_job_id":null,"html_url":"https://github.com/rnett/krosstalk","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rnett%2Fkrosstalk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rnett%2Fkrosstalk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rnett%2Fkrosstalk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rnett%2Fkrosstalk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rnett","download_url":"https://codeload.github.com/rnett/krosstalk/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248732512,"owners_count":21152851,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["compiler-plugin","kotlin","kotlinx-serialization","ktor-client","ktor-server","plugable","rpc"],"created_at":"2024-09-30T17:06:00.632Z","updated_at":"2025-04-13T14:54:21.397Z","avatar_url":"https://github.com/rnett.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Krosstalk: A pure Kotlin pluggable RPC library\n\n[![Maven Central](https://img.shields.io/maven-central/v/com.github.rnett.krosstalk/krosstalk)](https://search.maven.org/artifact/com.github.rnett.krosstalk/krosstalk)\n[![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/com.github.rnett.krosstalk/krosstalk?server=https%3A%2F%2Foss.sonatype.org)](https://oss.sonatype.org/content/repositories/snapshots/com/github/rnett/krosstalk/)\n[![GitHub Repo](https://img.shields.io/badge/GitHub-Krosstalk-blue?logo=github)](https://github.com/rnett/krosstalk)\n[![License](https://img.shields.io/badge/License-Apache%202.0-yellowgreen.svg)](https://opensource.org/licenses/Apache-2.0)\n[![Changelog](https://img.shields.io/badge/Changelog-CHANGELOG.md-green)](https://github.com/rnett/krosstalk/blob/main/CHANGELOG.md#changelog)\n\nKrosstalk allows you to easily create RPC methods using pure kotlin. Client, server, and serialization implementations\nare pluggable, and Kotlin's `expect/actual` modifiers can be used to ensure that client and server methods match.\n\nKtor client and server and Kotlinx Serialization plugins are provided, along\nwith [instructions on how to write your own](./WRITING_PLUGINS.md).\n\n### Compatibility\n\nKrosstalk is currently not compatible with Compose in the same module, because their compiler plugin does some weird\nstuff. To get around this, put all your Krosstalk stuff in other modules and depend on it in the modules with the\ncompiler plugin applied.\n[Tracked issue](https://issuetracker.google.com/issues/185609826).\n\n## Artifacts\n\n### The compiler plugin, which is loaded via the gradle plugin below, is required for anything to work!\n\n### Core ([Docs](https://rnett.github.io/krosstalk/release/core/index.html))\n\n[Snapshot Docs](https://rnett.github.io/krosstalk/snapshot/core/index.html)\n\n* Gradle plugin (**required**): `com.github.rnett.krosstalk` for the `plugins` block. Full coordinates\n  are `com.github.rnett.krosstalk:krosstalk-gradle-plugin`.\n* Core:`com.github.rnett.krosstalk:krosstalk`\n* Client: `com.github.rnett.krosstalk:krosstalk-client`\n* Server: `com.github.rnett.krosstalk:krosstalk-server`\n\nAny common source set defining an `expect` Krosstalk object will need to depend on the core artifact. The client and\nserver artifacts will generally be inherited from plugins.\n\nThe Krosstalk compiler plugin will automatically not apply to source sets that don't\ninclude `com.rnett.krosstalk. Krosstalk` as an accessible class (i.e. if you don't depend on any Krosstalk artifacts).\n\n### Plugins ([Docs](https://rnett.github.io/krosstalk/release/plugins/index.html))\n\n[Snapshot Docs](https://rnett.github.io/krosstalk/snapshot/plugins/index.html)\n\nPlugins provide handlers for serialization, clients, or servers that can be used in Krosstalk objects.\nThey control how your Krosstalk methods are actually executed.\n\n#### Serialization\n\n* Kotlinx serialization: `com.github.rnett.krosstalk:krosstalk-kotlinx-serialization`\n    * Includes JSON dependency, works with all formats.\n\n#### Client\n\n* Ktor: `com.github.rnett.krosstalk:krosstalk-ktor-client`\n    * Auth: `krosstalk-ktor-client-auth`\n\n#### Server\n\n* Ktor: `com.github.rnett.krosstalk:krosstalk-ktor-server`\n    * Auth: `krosstalk-ktor-server-auth`\n        * JWT: `krosstalk-ktor-server-auth-jwt`\n\n## Minimal example:\n\nCommon:\n\n```kotlin\n@Serializable\ndata class Data(val num: Int, val str: String)\n\nexpect object MyKrosstalk : Krosstalk {\n    override val serialization: KotlinxBinarySerializationHandler\n}\n\n@KrosstalkMethod(MyKrosstalk::class)\nexpect suspend fun basicTest(data: Data): List\u003cString\u003e\n```\n\nClient (i.e. JS):\n\n```kotlin\nactual object MyKrosstalk : Krosstalk(), KtorKrosstalkClient {\n    actual override val serialization = KotlinxBinarySerializationHandler(Cbor { })\n    override val serverUrl: String = \"http://localhost:8080\"\n\n    override val client = KtorClient()\n}\n\nactual suspend fun basicTest(data: Data): List\u003cString\u003e = krosstalkCall()\n```\n\nServer (i.e. JVM):\n\n```kotlin\nactual object MyKrosstalk : Krosstalk(), KtorKrosstalkServer {\n    actual override val serialization = KotlinxBinarySerializationHandler(Cbor { })\n    override val server = KtorServer\n}\n\nactual suspend fun basicTest(data: Data): List\u003cString\u003e {\n    return List(data.num) { data.str }\n}\n\nfun main() {\n    embeddedServer(CIO, 8080, \"localhost\") {\n        install(CORS) {\n            anyHost()\n        }\n        MyKrosstalk.defineKtor(this)\n    }.start(true)\n}\n```\n\nNote that clients and servers can be on any platform that has the needed plugins.\n\nThe projects in [tests](./tests) function as good examples of more advanced behavior, \nincluding use for microservices and a client-only example that calls a normal API.\n\n# Overview\n\nKrosstalk works by registering any methods annotated with `@KrosstalkMethod` with the class specified in the annotation,\nwhich must be an `object` that extends `Krosstalk` (referred to as the Krosstalk object), and **must be in the same module**. Client methods (which must\nhave a body of `krosstalkCall()`) will then have their bodies replaced with a call to the Krosstalk object, which will\nuse the client handler to send a request and return the response). Registering the server Krosstalk with your server\nimplementation (using a server plugin) will then cause incoming requests to be handled by the Krosstalk object, which\nwill call the requested method and respond with the returned value.\n\nThere are three types of Krosstalk objects, depending on how they are declared: common, client, and server:\n\n* Common Krosstalk objects are those that are declared `expect` in a common source set, and only extend `Krosstalk`. The\n  primary reason for doing this is that you can then use it with `expect` Krosstalk methods, and Kotlin's\n  `expect-actual`  mechanics will enforce that the client and server methods have exactly the same signature. To support\n  this, Krosstalk's configuration must be done on `expect` Krosstalk methods when available. Common Krosstalks (and\n  indeed all Krosstalks, since they all inherit from `Krosstalk`) must specify a serialization handler via\n  `serialization`, and can optionally specify a different serialization handler for url arguments\n  (`urlSerialization`) and a prefix to use in method endpoint urls (`prefix`, `\"krosstalk\"` by default).  **Note that\n  the value of `prefix` and the serialization formats must match on `actual` client and server Krosstalks.**  \n  It is not yet possible to declare these directly in the `expect` object, so take care. You can add a abstract class\n  between your Krosstalk object and `Krosstalk` to define these.  You can get serialization handlers from serialization plugins.\n* Client Krosstalks are those that implement `KrosstalkClient` in addition to `Krosstalk`. They can be declared as\n  standalone objects, or as the `actual` object of a common Krosstalk. They specify a client handler via the\n  `client` property and a server url via the `serverUrl` property. The server url is read each request, so it can\n  be `var`, but\n  using [server url parameters](https://rnett.github.io/krosstalk/release/core/krosstalk/-krosstalk/com.rnett.krosstalk.annotations/-server-u-r-l/index.html)\n  is recommended instead.  You get handlers from client plugins.\n* Server Krosstalks are those that implement `KrosstalkServer` in addition to `Krosstalk`. Like clients, than can be\n  standalone or `actual`. They define a server handler via `server`. This handler is usually just an object, since\n  server entrypoints and structure can vary, but should define methods to add your Krosstalk's methods to its server\n  implementation (i.e. `defineKtor`).  You get handlers from server plugins.\n\nThe `KrosstalkClient` and `KrosstalkServer` interfaces also require you to provide the scope class of your client or\nserver plugin, respectively. Plugins usually define their own interface or typealias that does this, i.e.\n`KtorKrosstalkClient`. Scopes are a mechanism to allow clients to modify requests depending on arguments and to allow\nservers to selectively match and extract data from requests. As such, their implementation will be plugin specific,\nrequiring you to specify the scope type. See the [Scope section](#scopes) for details.\n\n## Methods\n\nAs mentioned before, Krosstalk methods are methods annotated with `KrosstalkMethod`, which must be passed the Krosstalk\nobject to register them with. Krosstalk methods must also be `suspend` (because they will be converted to a HTTP\nrequest), and all parameters (including receivers) and the return type should be serializable by the serialization\nhandler of their Krosstalk object. Methods can be further configured using the annotations in\n`com.rnett.krosstalk.annotations`. All configuration (including the `KrosstalkMethod` annotation) should be done on the\n`expect` methods when using a common Krosstalk. I suggest reading\nover [the docs of `com.rnett.krosstalk. annotations`](https://rnett.github.io/krosstalk/release/core/krosstalk/-krosstalk/com.rnett.krosstalk.annotations/index.html)\nfor the details (click on an annotation to see the full docs) of what each annotation does; an overview of what is\npossible is provided below. Looking over\n[the common tests](tests/fullstack-test/src/commonMain/kotlin/com/rnett/krosstalk/fullstack_test/Test.kt) and\n[the client tests](tests/client-test/src/jsMain/kotlin/com/rnett/krosstalk/client_test/Test.kt) should give you an idea\nof what is possible, too.\n\n### Error Handling\n\nThere are two separate modes for error handling: using exceptions, like normal, and using `@ExplicitResult` and\nreturning a subclass of `KrosstalkResult`. In both, throwing `KrosstalkHttpError` or `KrosstalkServerException`\n(using `throwKrosstalkHttpError` and `throwKrosstalkServerException`) will result in an error response being sent, and\nthe exception being re-thrown on the client. However, methods using `@ExplicitResult` will usually include those in its\nreturn value instead of throwing exceptions. In this cause, the error response will still be sent, but the exception\nwill be part of the client's return value like on the server instead of being re-thrown. In all cases,\n`ServerException` responses are sent as 500s (with the `ServerException` being serialized to JSON), and `HttpError`\nresponses are sent with their status code and the message as the body.\n\nIf you throw an exception that is not `KrosstalkHttpError` or `KrosstalkServerException` **outside** of a\n`runKrosstalkCatching` block or some other block that would wrap it in a `KrosstalkServerException`, **the behavior of\nthe method when called on the client and server will differ**. If called on the server, the original exception will be\nthrown, while on the client, a `KrosstalkUncaughtServerException` wrapping the original exception will be thrown. This\nis unavoidable, since exceptions can't be serialized.\n\nIf an exception is thrown during a call from a client, the server will handle it according to the settings in\n`@ExceptionHandling`. If `propagateServerExceptions` is true, the underlying exception of any\n`ServerException` (from throwing a `KrosstalkServerException` or returning one with `@ExplicitResult`) will be\npropagated to your server handler's exception handler, which usually will at least log it.  `HttpError` results (via\nexception or return) won't be logged anywhere specific, but almost all servers provide methods to log error responses,\nand they will show up there. Any non-`KrosstalkServerException` or `KrosstalkHttpError` exceptions are re-thrown on the\nserver.  `includeStacktrace` controls whether the stack trace will be included in the responses. It is `false` by\ndefault to prevent leaking details, and a `false` value here will override any `true` values in\n`runKrosstalkCatching` or `throwKrosstalkServerException`. Note that it is impossible for a `true` value here to\noverride a `false` value elsewhere, so all other uses (i.e. `runKrosstalkCatching`) default to `true`. Also note that\nthis only applies on the client: the stack trace will always be present on the server.\n\nWhen using `@ExplicitResult`, the entire method should almost always be wrapped in `runKrosstalkCatching`. Methods\nlike `KrosstalkResult\u003cT\u003e.catchAsHttpError` or `when` blocks can be used to convert `ServerException`s to\n`HttpError`s.  `ServerException`s should generally be treated as an exceptional state, while `HttpError` is a less fatal\nerror. Helper methods like `KrosstalkResult\u003cT\u003e.handleHttpError` can be used to selectively handle error codes, so a\npattern like:\n\n```kotlin\n@KrosstalkMethod(MyKrosstalk::class)\n@ExplicitResult\nprivate expect fun _errorFunction(n: Int): KrosstalkResult\u003cInt\u003e\n\nfun errorFunction(n: Int): Int? = _errorFunction(n)\n    .throwOnServerException()\n    .handleHttpError(404) { null }\n    .valueOrThrow\n\n// server\nprivate actual fun _errorFunction(n: Int): KrosstalkResult\u003cInt\u003e = runKrosstalkCatching {\n    ...\n}.catchAsHttpError(NoSuchElementException::class, 404)\n```\n\nis fairly common. The server part can be omitted if the server code is changed to throw `KrosstalkHttpError`\ninstead of `NoSuchElementException`, i.e. by using `map[key] ?: throwKrosstalkHttpError(404)` instead\nof `map.getValue(key)`.\n\n### Endpoints\n\nThe endpoint a method uses (the relative URL and the HTTP method) can be configured using `@KrosstalkEndpoint`. The\nannotation takes the HTTP method and content type, and a template for the endpoint (which should be a relative URL).  \nThe template allows parameters to be used in the endpoint, using the following syntax:\n\n* Literals: text, plus `$krosstalkPrefix` to use the Krosstalk object's prefix value, and `$methodName` to use the\n  method's name plus the argument hash unless disabled in `@KrosstalkMethod`.\n* Parameter: `{name}` - encodes the value of the parameter with that name.\n* Parameter with name: `{{name}}` - is desugared into `/name/{name}` or `name={name}` depending on the URL region.\n* Optional: `[?name:...]` - Evaluates to the body (`...`) if `name` is present (not null if `name` is `@Optional` or not\n  a default if `name` is `@ServerDefault`), otherwise is empty. The body must be complete segments (i.e. between `/`s in\n  the body, or complete `key=value` int he tailcard).  `name` must be `@Optional` or `@ServerDefault`.\n* Optional parameter with name: `{{?name}}` - desugars to `[?name:{{name}}]`.  `name` must be `@Optional`\n  or `@ServerDefault`.\n\nParameter names must be a parameter of the method, `$instanceReceiver` if an instance/dispatch receiver is present, or\n`$extensionReceiver` if an extension receiver is present.  **Endpoint templates are checked for correctness at compile\ntime.**  You can't use \"special\" parameters (`@Ignored`, `@RequestHeaders`, `@ServerURL`) in the endpoint.\n\nThe default endpoint is `$krosstalkPrefix/$methodName`. A standard example\nis `$krosstalkPrefix/$methodName/?{{a}}\u0026{ {b}}` aka `$krosstalkPrefix/$methodName/?a={a}\u0026b={b}`. Almost all endpoints\nshould start with `$krosstalkPrefix/$methodName`.\n\nNote that in all strings here, the `$` should be escaped in Kotlin. However, we provide constants with the same names,\nso you can safely use `$krosstalkName` or `\\$krosstalkName`, as long as the first one resolves to our constant.\n\nURL parameters are serialized using the Krosstalk object's `urlSerialization`, which is `serialization` by default.\n\nParameters that are in the URL whenever they are not null will be passed only there and not in the body.\n\n`@EmptyBody` ensures that all parameters are passed in the URL, and is required to use the `GET` method unless there are\nno parameters.\n\n`contentType` may be empty, in which case the serialization handler's content type is used.\n\n### Optionals/Defaults\n\nOptionals and default values can be handled in two ways, both requiring `@Optional` on the parameter: nullable\nand `ServerDefault`, which require a nullable or `ServerDefault` type, respectively. Nullables are easier to use, but\nuses `null` to encode \"not present\", and so doesn't work for nullable data.  `ServerDefault` does.\n\nNullable and `ServerDefault` `@Optional`s are \"present\" when a non-null or non-default value is specified,\nrespectively.  \nThey can be used in endpoints (and as the predicate for optional blocks), but must not be used when they are not\npresent (i.e. they must be gated behind an optional).\n\nWhen `@Optional` parameters are not present, they are not passed at all, and the server uses\n`null` or the default, respectively.\n\n`ServerDefault` is essentially an optional type, but the\n`None` is hidden. The compiler plugin will replace the default value of the parameter with `None` on the client side,\nwhich will lead to it not being passed and the default being evaluated on the server.\n\n### Objects\n\nObject parameters, receivers, and return values are not passed by default.  **Note though, if the type of a parameter is\nan abstract class, object subclasses will still be passed.**  This only happens when the compiler can prove that the\nvalue will always be the same object. Objects are also ignored by the serialization logic, so not having serializers on\nthem will not cause issues.\n\nThis can be overridden using the `@PassObjects` annotation. By default, it only applies to parameters and receivers, but\nif `returnToo` is true it applies to return values as well.\n\n### Response Headers\n\nTo pass or get the response headers, return `WithHeaders\u003cT\u003e` and use `@RespondWithHeaders`. This wraps your result type\nand adds a `Headers` object. Any headers set in the return value on the server will be added to the response, and the\nvalue of the headers on the client will be parsed from the actual response headers.\n\nThis plays nice with `KrosstalkResult` for error handling. You can either have `WithHeaders\u003cKrosstalkResult\u003cT\u003e\u003e` to get\nthe headers on all responses, or `KrosstalkResult\u003cWithHeaders\u003cT\u003e\u003e` to only get the headers on success.\n\n### Request Headers\n\nTo send request headers, mark a parameter of type `Headers` with `@RequestHeaders`. It can't be used in endpoints.  \nThe value on the server will be parsed from the actual request headers.\n\n### Server URL\n\nTo send request headers, mark a parameter of type `String` or `String?` with `@ServerURL`. It can't be used in\nendpoints. If the argument is `null`, the server url from the `krosstalkCall()` or the Krosstalk object will be used (in\nthat order of precedence).\n\nThe value on the server will be parsed from the actual server url. The value will depend on your server implementation (\nand plugin) and hosting setup, so depending on it is unwise.\n\n### Ignore\n\n`@Ignore` can be applied to a nullable parameter or one with a default value. The argument then won't be passed (or\nserialized), and `null` or the default will be used on the server, respectively.\n\nThis is mainly useful for parameters used in `krosstalkCall()`.\n\n### `krosstalkCall()` arguments\n\nThe request headers, server url, and additional scopes can all be specified as arguments to `krosstalkCall()`. The\nrequest headers will be added to any specified with `@RequestHeaders`. The server url will be overridden by any\nnon-null `@ServerURL` parameters, and fall back to the Krosstalk object's server url if null. Specifying any scopes that\nare also specified by scope parameters will result in a runtime error when the method is called.\n\n## Scopes\n\nScopes are defined as nested `object`s in the Krosstalk objects. They must extend `Scope`, which is usually done\ntransitively. Note that for `actual` scopes this must be explicit because of\n[KT-20641](https://youtrack.jetbrains.com/issue/KT-20641). Client and server scopes (declared in their respective\nKrosstalk objects) must extend `ClientScope` and `ServerScope`, respectively. This is also usually done transitively,\nsince they also must extend their plugin's scope interface (or whatever was passed to `KrosstalkClient/Server`).\nBoth `ClientScope` and `ServerScope` have a type parameter; on `ClientScope`, this is the type of data that the scope\nrequires, on `ServerScope` it is the type of data the scope produces. The plugin's scope class will provide some methods\nto override to configure the request, which should make use of the data on client scopes or have a way to produce it on\nserver scopes.\nSee [KtorClientScope](plugins/ktor-client/krosstalk-ktor-client/src/commonMain/kotlin/com/rnett/krosstalk/ktor/client/Scopes.kt)\nand\n[KtorServerScope](plugins/ktor-server/krosstalk-ktor-server/src/main/kotlin/com/rnett/krosstalk/ktor/server/Scopes.kt)\nfor examples.\n\nGenerally, you will not extend a plugin's scope class directly. Most plugins should provide scope classes to configure\nthe wanted behavior, like `KtorClientBasicAuth`. You can then have your scope class extend this.\n\nScopes are passed to methods by adding a parameter of type `ScopeInstance\u003cT\u003e`, where `T` is the scope's type. Scope\ninstances can be created using the `invoke` methods of `ClientScope` and `ServerScope`. If you need to create a scope\ninstance in common code (when using a common Krosstalk), you must use `expect-actual`, since the data types of the\nscopes may differ between the client and server. To get the value extracted by the scope on the server, use\n`ScopeInstance.value`.\n\nScope parameters can be made optional by making their type nullable (i.e. `ScopeInstance\u003cT\u003e?`). If this is the case,\n`null` may be passed on the client resulting in no changes being made to the request, and the server may fail to get a\nvalue (like if `null` was passed by the client, but not limited to this) resulting in the value of the argument\nbeing `null`. This can be forbidden by some scope types by overriding `Scope.canBeOptional` and returning `false`.\n\nAdding an authentication scope to our initial example would look like this:\n\nCommon:\n\n```kotlin\n@Serializable\ndata class Data(val num: Int, val str: String)\n\nexpect object MyKrosstalk : Krosstalk {\n    override val serialization: KotlinxBinarySerializationHandler\n\n    object Auth : Scope\n}\n\n@KrosstalkMethod(MyKrosstalk::class)\nexpect suspend fun basicTest(data: Data, auth: ScopeInstance\u003cAuth\u003e): List\u003cString\u003e\n```\n\nClient (JS):\n\n```kotlin\nactual object MyKrosstalk : Krosstalk(), KrosstalkClient\u003cKtorClientScope\u003c*\u003e\u003e {\n    actual override val serialization = KotlinxBinarySerializationHandler(Cbor { })\n    override val serverUrl: String = \"http://localhost:8080\"\n\n    override val client = KtorClient()\n\n    actual object Auth : Scope, KtorClientBasicAuth()\n}\n\nactual suspend fun basicTest(data: Data, auth: ScopeInstance\u003cAuth\u003e): List\u003cString\u003e = krosstalkCall()\n```\n\nServer (JVM):\n\n```kotlin\nactual object MyKrosstalk : Krosstalk(), KrosstalkServer\u003cKtorServerScope\u003c*\u003e\u003e {\n    actual override val serialization = KotlinxBinarySerializationHandler(Cbor { })\n    override val server = KtorServer\n\n    actual object Auth : Scope, KtorServerBasicAuth\u003cUser\u003e(\"auth\") {\n        override fun BasicAuthenticationProvider.Configuration.configure() {\n            validate {\n                if (validUsers[it.name] == it.password)\n                    User(it.name)\n                else\n                    null\n            }\n        }\n    }\n}\n\ndata class User(val username: String) : Principal\n\nprivate val validUsers = mapOf(\"username\" to \"password\")\n\nactual suspend fun basicTest(data: Data, auth: ScopeInstance\u003cAuth\u003e): List\u003cString\u003e {\n    println(\"Request from: ${auth.value/*: User*/.username}\")\n    return List(data.num) { data.str }\n}\n\nfun main() {\n    embeddedServer(CIO, 8080, \"localhost\") {\n        install(CORS) {\n            anyHost()\n        }\n        MyKrosstalk.defineKtor(this)\n    }.start(true)\n}\n```\n\n## Resolution helpers\n\nMicroservices are a rather common use case of RPC, and to better support them, we provide some resolution helpers,\nvisible in the [microservices test](tests/microservices-test). These help manage dependencies, and can be seen in the\ntest's [buildscripts](tests/microservices-test/ping/build.gradle.kts) (each microservice's server depends on the other's\nclient).\n\nGenerally, for a microservice, you will want to have a common module with the Krosstalk and data definitions, a client\nmodule with just the Krosstalk client methods (and any other helpers for them), and a server module with the actual\nimplementation. Often, these will all be JVM modules (like in the test). However, Kotlin does not yet provide a good way\nto distinguish between source sets when using `project()` dependencies. This is where our helpers come in.\n\nTo declare a module as the krosstalk client or server module, call `krosstalkClient()` or `krosstalkServer()` in its\nKotlin target configuration, respectively. Then, to depend on one or the other, use `project().krosstalkClient()\n` or `.krosstalkServer()`, respectively. Gradle handles circular dependencies well, so having two microservices where\neach's server depends on the other's client works well. Again, you can see this in action in the microservice test (the\nbuildscripts are the interesting part).\n\nThese both use the `com.rnett.krosstalk.krosstalkTypeAttribute` attribute, which needs to be [registered in the dependencies block](https://docs.gradle.org/current/userguide/variant_attributes.html#creating_attributes_in_a_build_script_or_plugin) in some cases.\n**This is not done automatically**.\nWhich situations require this is not clear, but the most basic in-project use, as done in the [microservices test](tests/microservices-test), seems to work fine without it.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frnett%2Fkrosstalk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frnett%2Fkrosstalk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frnett%2Fkrosstalk/lists"}