Glixir
Easy Gleam wrapper around elixir otp
Install / Use
/learn @rjpruitt16/GlixirREADME
Glixir 🌟
A Safe(ish) OTP interop between Gleam and Elixir/Erlang
Bridge the gap between Gleam's type safety and the battle-tested OTP ecosystem. Use GenServers, Supervisors, Agents, Registry, and more from Gleam with confidence.
Features
- ✅ GenServer - Type-safe calls to Elixir GenServers
- ✅ DynamicSupervisor - Runtime process supervision and management
- ✅ Agent (now generic!) - Type-safe state management with async operations
- ✅ Registry - Dynamic process registration and Subject lookup
- 🚧 Task - Async task execution (coming soon)
- ✅ Phoenix.PubSub - Distributed messaging with JSON-based type safety
- ✅ libcluster - Automatic node discovery and clustering for distributed systems
- ✅ Zero overhead - Direct BEAM interop with clean Elixir helpers
- ✅ Gradual adoption - Use alongside existing Elixir code
- ✅ syn - Distributed process registry and PubSub coordination
Type Safety & Phantom Types
A note from your neighborhood type enthusiast:
Some
glixirAPIs (notably process calls, GenServer, Registry) still require passingDynamicvalues—this is the price of seamless BEAM interop and runtime dynamism. While decoders on return values help catch mismatches, full compile-time type safety isn't always possible... yet.But here's the good news: We're actively rolling out phantom types and generics across the API, banishing
Dynamicwherever possible and making misused actors a compile-time relic.
Type safety level
GenServer: [■■■■■■■■□□] 80% - Request/reply types enforced by Gleam, decoder required, but runtime BEAM interop can still fail if types disagree.
Supervisor: [■■■■■■■■■□] 90% - Phantom-typed with compile-time child spec validation, args/replies bounded by generics.
Registry: [■■■■■■■■■□] 90% - Phantom-typed with compile-time key/message validation, requires key encoders.
Agent: [■■■■■■■■■■] 100% - State and API fully generic and type safe!
PubSub: [■■■■■■■■□□] 80% - JSON-based type safety with user-defined encoders/decoders. Phantom-typed for message_type.
libcluster: [■■■■■■■□□□] 70% - Node discovery works, cluster membership is runtime dynamic
syn: [■■■■■■■■□□] 80% - Distributed coordination with type-safe message patterns, runtime node discovery.
Task: [□□□□□□□□□□] 0% - Not started.
Full green bars are the dream; until then, decoders are your seatbelt!
Why Not 100% Type-Safe Now?
Blame Erlang! (Just kidding, blame dynamic process boundaries.) You get strict safety inside Gleam, but as soon as you jump the BEAM-to-BEAM fence, the runtime is your playground. So, until Gleam's type system can tame Elixir's wild world, we work with decoders and are pushing hard to get you closer to total type bliss.
⚠️ Caveats: BEAM Interop Trade-offs
Glixir provides bounded type safety - the maximum safety possible while maintaining full OTP functionality. Some limitations are inherent to BEAM interop, not design flaws:
The Safety Spectrum
- Pure Gleam: 100% compile-time safe, but no distributed features
- Glixir: 70-90% compile-time safe + runtime validation, full OTP power
- Raw FFI: ~20% safe, full OTP power, high risk
Unavoidable BEAM Realities
- Process discovery - distributed systems have runtime process existence
- Module loading - OTP's dynamic nature requires string module names
- Cross-language boundaries - serialization between Gleam and Elixir
What You Get
✅ Compile-time: Prevents category errors, type mixing, wrong message types
⚠️ Runtime: Process existence, module validity, message format compatibility
Bottom Line: Glixir is the sweet spot - maximum practical safety with essential functionality that core Gleam simply doesn't provide.
Want to help speed this up? File issues, suggest API improvements, or just cheer us on in the repo!
⚡ Atom Safety & Fail-Fast API
glixir now requires all process, registry, and module names to be existing atoms—typos or missing modules crash immediately with {bad_atom, ...}.
- Avoids BEAM atom leaks and silent bugs.
- If you typo or use a non-existent module, you'll see a loud error.
- Pro-tip: Always reference the real module, or pre-load it in Elixir before calling from Gleam.
This makes glixir safer by default—don't trust user input as atom names!
libcluster - Automatic Node Discovery & Clustering
Scale your Gleam apps across multiple nodes with zero configuration! 🌐 libcluster provides automatic node discovery and clustering for distributed BEAM applications, with built-in support for Fly.io, Kubernetes, AWS, and local development.
import glixir/libcluster
import gleam/erlang/os
import logging
pub fn main() {
// Auto-detect environment and start clustering
let _ = case os.get_env("FLY_APP_NAME") {
Ok(app_name) -> {
// Running on Fly.io - automatic DNS-based discovery
libcluster.start_clustering_fly(app_name)
}
Error(_) -> {
// Local development - use EPMD for simplicity
libcluster.start_clustering_local("my_app")
}
}
// Check cluster status
io.println("Node: " <> libcluster.current_node_name())
io.println("Connected to: " <> string.inspect(libcluster.connected_node_names()))
// Your distributed app starts here
// syn will automatically sync across all discovered nodes!
start_distributed_app()
}
// Production deployment with custom DNS
pub fn production_clustering() {
// DNS-based discovery for production
let assert Ok(_) = libcluster.start_clustering_dns(
app_name: "my_app",
query: "my_app.service.consul", // Consul, Kubernetes DNS, etc
polling_interval: 5000
)
// Monitor cluster membership
case libcluster.is_clustered() {
True -> {
let nodes = libcluster.connected_nodes()
logging.info("Clustered with " <> int.to_string(list.length(nodes)) <> " nodes")
}
False -> {
logging.warning("Running in single-node mode")
}
}
}
// Combine with syn for distributed process coordination
pub fn distributed_workers() {
// Start clustering
let assert Ok(_) = libcluster.start_clustering_fly("worker_pool")
// syn automatically discovers all nodes via libcluster!
syn.init_scopes(["workers"])
// This registration is visible across ALL nodes
syn.register("workers", "worker_1", self())
// Find workers on ANY node in the cluster
case syn.whereis("workers", "worker_1") {
Ok(#(pid, node)) -> {
io.println("Found worker on node: " <> atom.to_string(node))
}
Error(_) -> io.println("Worker not found in cluster")
}
}
Supported Clustering Strategies:
- Fly.io - Automatic DNS discovery via APP_NAME.internal
- Kubernetes - Service discovery via DNS or API
- AWS EC2 - Tag-based instance discovery
- Local Dev - EPMD for testing multi-node locally
- Custom DNS - Any DNS-based service discovery Use Cases:
- Distributed job queues across multiple machines
- Fault-tolerant services with automatic failover
- Load balancing across geographic regions
- Consensus algorithms and leader election
- Real-time data synchronization
syn - Distributed Process Coordination
Distributed service discovery and event streaming across BEAM nodes! 🌐
Use Erlang's battle-tested syn library for distributed coordination, consensus algorithms, and fault-tolerant process management.
import glixir/syn
import gleam/json
pub fn distributed_example() {
// Initialize scopes at application startup
syn.init_scopes(["worker_pools", "coordination"])
// Register this process in a distributed pool
let load_info = #(cpu_usage: 0.3, queue_size: 5)
let assert Ok(_) = syn.register_worker("image_processors", "worker_1", load_info)
// Find workers across the cluster
case syn.find_worker("image_processors", "worker_1") {
Ok(#(pid, #(cpu_usage, queue_size))) -> {
// Send work to the least loaded worker
process.send(pid, ProcessImage("photo.jpg"))
}
Error(_) -> // Worker not available, try another
}
// Join coordination group for consensus
let assert Ok(_) = syn.join_coordination("leader_election")
// Broadcast status for distributed coordination
let status = json.object([
#("node", json.string("worker_node_1")),
#("load", json.float(0.3)),
#("available", json.bool(True))
])
let assert Ok(nodes_notified) = syn.broadcast_status("health_check", status)
io.println("Status sent to " <> int.to_string(nodes_notified) <> " nodes")
}
// Advanced: Custom coordination patterns
pub fn consensus_example() {
// Register with metadata for consensus algorithms
let machine_status = #(queue_lengths: #(0, 5, 2), capacity: 100)
let assert Ok(_) = syn.register("machines", "machine_1", machine_status)
// Publish for distributed decision making
let queue_status = json.object([
#("machine_id", json.string("machine_1")),
#("free_queue", json.int(0)),
#("paid_queue", json.int(5)),
#("capacity", json.int(100))
])
let assert Ok(_) = syn.publish_json("coordination", "load_balancing", queue_status, fn(j) { j })
}
## Installation
```sh
gleam add glixir
Add the Elixir helper modules to your project:
# lib/glixir_supervisor.ex and lib/glixir_registry.ex
# Copy the helper modules from the docs
Quick Start
PubSub - Type-Safe Distributed Messaging
Real-time event broadcasting with JSON-based type safety! 📡
import glixir
import gleam/json
import gleam/dynamic/decode
import gleam/erlang/atom
// Define your message types
pub type UserEvent {
PageView(user_id: String, page: String)
Purchase(user_id: String, amount: Float
