Typed distributed messaging for Gleam on the BEAM.
distribute is a thin typed safety layer over Erlang's distribution
primitives. It puts binary codecs in front of :global and Subject so
the compiler catches protocol mismatches before messages leave the
process. Payload size and dead-target detection are enforced at every
I/O boundary, so a misbehaving peer cannot OOM your node or hang it.
gleam add distributeRequires Gleam 1.16 or newer, Erlang targetÉ only.
import distribute
import distribute/codec
import distribute/receiver
let greeter = distribute.named("greeter", codec.string())
let assert Ok(_gs) =
distribute.start_registered(greeter, Nil, fn(msg, _state) {
io.println("got: " <> msg)
receiver.Continue(Nil)
})
let assert Ok(target) = distribute.lookup(greeter)
let assert Ok(Nil) = distribute.send(target, "hello")For the full walkthrough see docs/quickstart.md.
All long-form docs live in docs/:
- Quickstart. Boot, configure, send.
- Recipes. Counter, pool, versioned protocol, cluster events, custom records.
- Actors and registry.
- Messaging.
send/receive/call. - Codecs and types. Wire format, custom records, tagged messages.
- Safety and limits. Payload limits, threat model, recommended sizing.
- Typed boundary.
TypedName(msg)binds a name to a codec. Registration and lookup share the samemsgtype, and the compiler rejects mismatches. - Hard payload caps.
max_payload_size_bytesis enforced before encode and before decode on every path:send,receive,call,reply, actor handlers, selectors. - Fast-fail calls.
global.callmonitors the target. A dead target returnsError(TargetDown)immediately. Late replies and DOWN messages are drained from the caller's mailbox. - OTP-native. Real OTP gen_server-flavored actors via
gleam_otp/actor, real supervisors, real child specs.observer,sys:get_status, restart strategies all work. No magic. - Single-source codec. Encoding and decoding happen through one
Codec(a)value. Combinators (map,list,option,tuple,tagged) cover the common cases without macros.
Seamlessly encode and decode your Algebraic Data Types (enums) with a fluent builder.
pub type MyMessage {
Text(String)
Ping
}
import distribute/codec
import distribute/codec/variant
let my_codec =
variant.new()
|> variant.add(0, "Text", codec.string(), Text, fn(m) {
case m { Text(s) -> Ok(s); _ -> Error(Nil) }
})
|> variant.unit(1, "Ping", Ping, fn(m) { m == Ping })
|> variant.build()Subscribe to cluster events (NodeUp, NodeDown) to react to node topology changes.
import distribute
import distribute/cluster/monitor
let subj = process.new_subject()
let assert Ok(m) = distribute.subscribe(subj)
// In your actor/process
case process.receive(subj, 5000) {
Ok(monitor.NodeUp(node)) -> io.println("Node joined: " <> node)
Ok(monitor.NodeDown(node)) -> io.println("Node left: " <> node)
_ -> Nil
}
// Later
distribute.unsubscribe(m)- Two codebases, one wire.
TypedNameenforces type safety inside one codebase. Mismatched codecs across separate codebases produce runtime decode errors, not compile errors. - No auto-derive. Gleam has no macros, so complex codecs are manual. The combinators keep them short.
terminatecaveat ingleam_otp/actor1.x. External shutdown paths do not currently invoke an OTP-styleterminatecallback. If an actor owns files, sockets, ETS tables, ports, or other external resources, use the linked resource-owner pattern documented in docs/recipes.md and docs/safety_and_limits.md.- One internal coupling. We construct
Subjectfrom a remote PID via one Erlang FFI function. That is the single point that depends ongleam_erlang's subject layout.
gleam test # full suite (includes mandatory real-cluster Z2/Z3)
epmd -daemon # start Erlang distribution daemon if not already running
gleam dev # multi-node playground
gleam docs build # local API docsMIT. See LICENSE.
