# AshKotlinMultiplatform
[](https://hex.pm/packages/ash_kotlin_multiplatform)
[](https://hexdocs.pm/ash_kotlin_multiplatform)
Generate type-safe Kotlin Multiplatform clients from your Ash resources.
> **Alpha Software Notice**
>
> This library is in **alpha** and under active development. The API may change
> between versions without notice. While functional and tested, it is not yet
> recommended for production use. Please report issues and feedback on
> [GitHub](https://github.com/udin-io/ash_kotlin_multiplatform/issues).
## Features
- **Automatic Kotlin Multiplatform generation** from Elixir Ash resources
- **End-to-end type safety** between backend (Elixir) and frontend (Kotlin)
- **Type-safe RPC client generation** using Ktor and coroutines
- **kotlinx.serialization** integration for JSON handling
- **Phoenix Channel support** for real-time applications
- **Kotlin Multiplatform** support (JVM, iOS, JS, Native)
- **Type-safe filtering** with generated filter types
- **Pagination support** for offset and keyset pagination
- **Validation** with optional javax.validation annotations
## Requirements
- Elixir 1.15+
- Ash 3.7+
- Kotlin 1.9+ (for generated code)
- Ktor 2.0+ (for HTTP client)
- kotlinx.serialization 1.6+ (for JSON)
## Installation
Add `ash_kotlin_multiplatform` to your dependencies in `mix.exs`:
```elixir
def deps do
[
{:ash_kotlin_multiplatform, "~> 0.1.0"}
]
end
```
## Quick Start
### 1. Add the Resource Extension
Configure your Ash resources with the `AshKotlinMultiplatform.Resource` extension:
```elixir
defmodule MyApp.Todo do
use Ash.Resource,
domain: MyApp.Domain,
extensions: [AshKotlinMultiplatform.Resource]
kotlin_multiplatform do
# Optional: customize the Kotlin class name
type_name "Todo"
# Optional: map Elixir field names to valid Kotlin identifiers
field_names %{
is_done: :isDone,
created_at: :createdAt
}
end
attributes do
uuid_primary_key :id
attribute :title, :string, allow_nil?: false
attribute :is_done, :boolean, default: false
attribute :created_at, :utc_datetime_usec
end
actions do
defaults [:read, :create, :update, :destroy]
read :list do
pagination offset?: true, default_limit: 25
end
end
end
```
### 2. Add the Domain Extension
Configure your Ash domain with the `AshKotlinMultiplatform.Rpc` extension to expose actions via RPC:
```elixir
defmodule MyApp.Domain do
use Ash.Domain, extensions: [AshKotlinMultiplatform.Rpc]
kotlin_rpc do
resource MyApp.Todo do
# Map RPC endpoint names to Ash actions
rpc_action :list_todos, :list
rpc_action :create_todo, :create
rpc_action :get_todo, :read
rpc_action :update_todo, :update
rpc_action :delete_todo, :destroy
# Optional: define typed queries for common operations
typed_query :active_todos, :list do
filter is_done: false
end
end
end
resources do
resource MyApp.Todo
end
end
```
### 3. Generate Kotlin Code
Run the code generation mix task:
```bash
mix ash_kotlin_multiplatform.codegen
```
This generates a Kotlin file (default: `lib/generated/AshRpc.kt`) containing all your types and RPC functions.
## Generated Code Example
The generator produces idiomatic Kotlin code:
```kotlin
package com.myapp.ash
import kotlinx.serialization.*
import kotlinx.datetime.*
import io.ktor.client.*
// Data class with serialization support
@Serializable
data class Todo(
val id: String,
val title: String,
val isDone: Boolean? = false,
val createdAt: Instant? = null
)
// Configuration for actions
@Serializable
data class CreateTodoConfig(
val title: String,
val isDone: Boolean? = null
)
// Sealed class for type-safe results
sealed class CreateTodoResult {
data class Ok(val data: Todo) : CreateTodoResult()
data class Error(val errors: List<RpcError>) : CreateTodoResult()
}
// Functional API
suspend fun createTodo(
client: HttpClient,
config: CreateTodoConfig
): CreateTodoResult { ... }
// Object-oriented API wrapper
object TodoRpc {
suspend fun create(client: HttpClient, config: CreateTodoConfig) =
createTodo(client, config)
suspend fun list(client: HttpClient, config: ListTodosConfig) =
listTodos(client, config)
}
```
## Configuration
Configure the generator in your `config/config.exs`:
```elixir
config :ash_kotlin_multiplatform,
# Output settings
output_file: "lib/generated/AshRpc.kt",
package_name: "com.myapp.ash",
# Code generation options
generate_phoenix_channel_client: true,
generate_validation_functions: true,
generate_filter_types: false,
generate_validation_annotations: false,
# Type handling
datetime_library: :kotlinx_datetime, # or :java_time
nullable_strategy: :explicit, # or :platform
# RPC endpoints
run_endpoint: "/rpc/run",
validate_endpoint: "/rpc/validate",
# Warnings
warn_on_missing_rpc_config: true,
warn_on_non_rpc_references: true
```
### Configuration Options
| Option | Default | Description |
|--------|---------|-------------|
| `output_file` | `"lib/generated/AshRpc.kt"` | Path for generated Kotlin file |
| `package_name` | Auto-generated | Kotlin package name |
| `generate_phoenix_channel_client` | `true` | Generate WebSocket client code |
| `generate_validation_functions` | `true` | Generate input validation functions |
| `generate_filter_types` | `false` | Generate type-safe filter types |
| `generate_validation_annotations` | `false` | Add javax.validation annotations |
| `datetime_library` | `:kotlinx_datetime` | Date/time library (`:kotlinx_datetime` or `:java_time`) |
| `nullable_strategy` | `:explicit` | Nullable handling (`:explicit` for `T?`, `:platform` for `T!`) |
| `run_endpoint` | `"/rpc/run"` | RPC execution endpoint |
| `validate_endpoint` | `"/rpc/validate"` | Validation endpoint |
| `type_mapping_overrides` | `[]` | Custom Ash to Kotlin type mappings |
| `input_field_formatter` | `:camel_case` | Input field name format |
| `output_field_formatter` | `:camel_case` | Output field name format |
## DSL Reference
### Resource Extension (`AshKotlinMultiplatform.Resource`)
```elixir
kotlin_multiplatform do
# Custom Kotlin class name (default: resource module name)
type_name "MyCustomName"
# Map field names to valid Kotlin identifiers
field_names %{
elixir_name: :kotlinName
}
# Map argument names per action
argument_names %{
create: %{from_date: :fromDate}
}
end
```
### Domain Extension (`AshKotlinMultiplatform.Rpc`)
```elixir
kotlin_rpc do
resource MyApp.Resource do
# Map RPC actions to Ash actions
rpc_action :rpc_name, :ash_action_name
# Expose metadata fields
rpc_action :list_items, :read do
expose_metadata [:total_count]
end
# Define typed queries with preset filters
typed_query :active_items, :read do
filter status: :active
end
end
end
```
## Phoenix Channel Support
When `generate_phoenix_channel_client: true`, the generator creates a full Phoenix Channel client:
```kotlin
// Connect to Phoenix Channel
val channel = AshRpcChannel(
httpClient = client,
baseUrl = "ws://localhost:4000/socket/websocket"
)
// Join the RPC topic
channel.join("rpc:lobby")
// Make RPC calls over WebSocket
val result = channel.call<CreateTodoResult>("create_todo", config)
```
## Advanced Features
### Type-Safe Filters
Enable with `generate_filter_types: true`:
```kotlin
val filter = TodoFilter(
title = StringFilter(contains = "important"),
isDone = BooleanFilter(eq = false)
)
val result = listTodos(client, ListTodosConfig(filter = filter))
```
### Custom Type Mappings
Map custom Ash types to Kotlin:
```elixir
config :ash_kotlin_multiplatform,
type_mapping_overrides: [
{MyApp.Types.Money, "BigDecimal"},
{MyApp.Types.Email, "String"}
]
```
### Lifecycle Hooks
Add hooks for request processing:
```elixir
config :ash_kotlin_multiplatform,
rpc_action_before_request_hook: "authInterceptor",
rpc_action_after_request_hook: "responseLogger"
```
## Related Packages
- [ash](https://hex.pm/packages/ash) - The Ash Framework
- [ash_phoenix](https://hex.pm/packages/ash_phoenix) - Phoenix integration for Ash
- [ash_json_api](https://hex.pm/packages/ash_json_api) - JSON:API support for Ash
## Contributing
Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
## License
MIT License - see [LICENSE](LICENSE) for details.