genserver π
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
gleam add genserver
Quick Start
Using Elixir Agents (Simple State Management)
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:
# 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:
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
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 supervisionstart(module, args)
- Start a GenServer directlycall(server, request)
- Synchronous call with 5s timeoutcall_timeout(server, request, ms)
- Synchronous call with custom timeoutcast(server, request)
- Asynchronous messagesend_message(server, message)
- Raw message (triggershandle_info
)
Agent Operations
agent_start(initial_fn)
- Start an Agent with initial stateagent_get(agent, get_fn)
- Get state (can transform)agent_update(agent, update_fn)
- Update stateagent_stop(agent)
- Stop the Agent
Utilities
atom(name)
- Create atoms for Elixir interoptagged_message(tag, from, content)
- Create tagged tuplespid(server)
- Extract raw PID for advanced operations
Error Handling
All operations return Result(T, GenServerError)
for safe error handling:
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.
git clone https://github.com/rjpruitt16/genserver
cd genserver
gleam test
License
MIT - Build awesome things! π``