# genserver 🌟
[](https://hex.pm/packages/genserver)
[](https://hexdocs.pm/genserver/)
[](https://github.com/rjpruitt16/genserver/actions)
**Type-safe Elixir GenServer and Agent interop for Gleam**
Bridge the gap between Gleam's type safety and Elixir's battle-tested OTP. Call existing Elixir GenServers from Gleam with full type safety, or use Elixir Agents for simple state management.
## Why This Matters
- 🛡️ **Type Safety** - All GenServer operations are type-checked at compile time
- ⚡ **Zero Overhead** - Direct BEAM interop with no serialization
- 🏗️ **Production Ready** - Built on Elixir's 15+ years of OTP battle-testing
- 🔄 **Gradual Migration** - Use existing Elixir services from new Gleam code
- 📦 **Simple API** - Ergonomic wrappers around complex OTP patterns
## Installation
```sh
gleam add genserver
```
## Quick Start
### Using Elixir Agents (Simple State Management)
```gleam
import genserver
pub fn main() {
// Start an Agent with initial state
let assert Ok(counter) = genserver.agent_start(fn() { 0 })
// Update state safely
let assert Ok(_) = genserver.agent_update(counter, fn(n) { n + 1 })
// Get current state
let count = genserver.agent_get(counter, fn(n) { n })
// count = 1
// Clean up
let assert Ok(_) = genserver.agent_stop(counter)
}
```
### Calling Existing Elixir GenServers
First, create an Elixir GenServer:
```elixir
# lib/my_server.ex
defmodule MyServer do
use GenServer
def start_link(initial_state) do
GenServer.start_link(__MODULE__, initial_state)
end
def get_state(pid), do: GenServer.call(pid, :get_state)
def increment(pid), do: GenServer.cast(pid, :increment)
# Callbacks
def init(state), do: {:ok, state}
def handle_call(:get_state, _from, state), do: {:reply, state, state}
def handle_cast(:increment, state), do: {:noreply, state + 1}
end
```
Then call it from Gleam:
```gleam
import genserver
pub fn use_elixir_genserver() {
// Start the Elixir GenServer
let assert Ok(server) = genserver.start_link("MyServer", 42)
// Make type-safe calls
let assert Ok(state) = genserver.call(server, genserver.atom("get_state"))
// state = 42
// Send async messages
let assert Ok(_) = genserver.cast(server, genserver.atom("increment"))
// Verify the change
let assert Ok(new_state) = genserver.call(server, genserver.atom("get_state"))
// new_state = 43
}
```
## Real-World Example: HTTP Client with Connection Pooling
```gleam
import genserver
import gleam/http/request
import gleam/http/response
import gleam/result
pub type ConnectionPool =
genserver.Agent
pub fn create_pool(max_connections: Int) -> Result(ConnectionPool, _) {
genserver.agent_start(fn() {
#(0, max_connections) // current, max
})
}
pub fn get_connection(pool: ConnectionPool) -> Result(Bool, _) {
genserver.agent_get(pool, fn(state) {
let #(current, max) = state
current < max
})
}
pub fn checkout_connection(pool: ConnectionPool) -> Result(Nil, _) {
genserver.agent_update(pool, fn(state) {
let #(current, max) = state
case current < max {
True -> #(current + 1, max)
False -> state
}
})
}
pub fn return_connection(pool: ConnectionPool) -> Result(Nil, _) {
genserver.agent_update(pool, fn(state) {
let #(current, max) = state
#(int.max(0, current - 1), max)
})
}
```
## API Overview
### GenServer Operations
- `start_link(module, args)` - Start a GenServer using OTP supervision
- `start(module, args)` - Start a GenServer directly
- `call(server, request)` - Synchronous call with 5s timeout
- `call_timeout(server, request, ms)` - Synchronous call with custom timeout
- `cast(server, request)` - Asynchronous message
- `send_message(server, message)` - Raw message (triggers `handle_info`)
### Agent Operations
- `agent_start(initial_fn)` - Start an Agent with initial state
- `agent_get(agent, get_fn)` - Get state (can transform)
- `agent_update(agent, update_fn)` - Update state
- `agent_stop(agent)` - Stop the Agent
### Utilities
- `atom(name)` - Create atoms for Elixir interop
- `tagged_message(tag, from, content)` - Create tagged tuples
- `pid(server)` - Extract raw PID for advanced operations
## Error Handling
All operations return `Result(T, GenServerError)` for safe error handling:
```gleam
import genserver
case genserver.call_timeout(server, "slow_op", 1000) {
Ok(result) -> handle_success(result)
Error(genserver.CallTimeout) -> handle_timeout()
Error(genserver.CallError(reason)) -> handle_error(reason)
Error(genserver.StartError(reason)) -> handle_start_failure(reason)
}
```
## Use Cases
- **🔄 Gradual Migration** - Call existing Elixir services from new Gleam code
- **📊 State Management** - Use Agents for simple, concurrent state
- **🌐 HTTP Clients** - Connection pooling with type-safe operations
- **⚡ Real-time Systems** - Type-safe message passing between processes
- **🔌 External APIs** - Wrap Elixir GenServers with Gleam type safety
- **📈 Monitoring** - Collect metrics using battle-tested OTP patterns
## Comparison with Pure Gleam OTP
| Feature | This Library | Pure Gleam OTP |
|---------|-------------|----------------|
| **Maturity** | 15+ years (Elixir) | New, evolving |
| **Type Safety** | ✅ Full | ✅ Full |
| **Performance** | ✅ Zero overhead | ✅ Zero overhead |
| **Ecosystem** | ✅ Huge Elixir ecosystem | 🔄 Growing |
| **Learning Curve** | 📖 Familiar to Elixir devs | 📖 New patterns |
## Contributing
Contributions welcome! This library bridges an important gap in the BEAM ecosystem.
```sh
git clone https://github.com/rjpruitt16/genserver
cd genserver
gleam test
```
## License
MIT - Build awesome things! 🚀``