genserver 🌟

Package Version Hex Docs CI

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

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

Agent Operations

Utilities

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

Comparison with Pure Gleam OTP

FeatureThis LibraryPure Gleam OTP
Maturity15+ 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! πŸš€``

✨ Search Document