libero/wire
ETF (Erlang Term Format) wire codec for Libero RPC.
Encoding walks any Gleam value through erlang:term_to_binary/1,
which preserves the full Erlang type structure - atoms, tuples,
maps, lists - natively. Decoding uses erlang:binary_to_term/1
to reconstruct the original terms. No manual walk or rebuild is
needed because ETF is the BEAM’s native serialization format.
Wire shape:
- The call envelope is
{module_name_binary, msg_from_client_value}- a 2-tuple where the first element is a UTF-8 binary (Gleam String) naming the shared module, and the second is the typed MsgFromClient value serialized as a native ETF term. - The response is the Gleam value directly (e.g.
Ok(value)orError(MalformedRequest)), serialized as ETF.
Cross-target: encode and decode work on both Erlang and
JavaScript targets. The Erlang path uses the BEAM’s native
term_to_binary / binary_to_term. The JavaScript path uses
libero’s own ETF encoder/decoder in rpc_ffi.mjs, which requires
that any custom-type constructors in the value have been registered
via register_all() at boot (libero’s generator emits that
registration for every type reachable from the MsgFromClient/MsgFromServer type graph).
Types
pub type DecodeError {
DecodeError(message: String)
}
Constructors
-
DecodeError(message: String)
Values
pub fn coerce(value: dynamic.Dynamic) -> a
Cast a Dynamic value to any type. Used by generated server dispatch code to coerce the decoded MsgFromClient value to its typed form. Safe when client and server are built from the same source (the generator guarantees the types match).
pub fn decode(data: BitArray) -> a
Decode an ETF binary into an arbitrary Gleam value.
Works on both Erlang and JavaScript targets. Use this for non-RPC
paths - for example, reading server-rendered state from Lustre
flags on client boot. For decoding incoming RPC call envelopes
specifically, use decode_call instead.
Any custom types in the decoded value must be reachable from the
MsgFromClient/MsgFromServer type graph so their constructors are registered with the
JavaScript codec (via register_all() at boot). On Erlang this
is automatic because atoms are pre-registered by the generated
rpc_atoms module.
Panics on malformed input. In a typical libero deployment
both sides are controlled, so this is a sharp-edge check rather
than a user-facing error. For untrusted input, use decode_safe
which returns a Result.
pub fn decode_call(
data: BitArray,
) -> Result(#(String, dynamic.Dynamic), DecodeError)
Parse a {<<"module_name">>, toserver_value} tuple from an ETF binary.
Returns the module name and the raw Dynamic value to be coerced.
Since binary_to_term returns real Erlang terms, no rebuild step
is needed - atoms are atoms, tuples are tuples, maps are maps.
This is specifically for RPC call envelopes. For decoding
arbitrary values, use decode.
pub fn decode_safe(data: BitArray) -> Result(a, DecodeError)
Decode an ETF binary into an arbitrary Gleam value, returning a
Result instead of panicking on malformed input.
Use this for non-RPC paths where the input may be untrusted or user-influenced - for example, reading server-rendered state from Lustre flags on client boot where the binary may have been corrupted in transit.
pub fn encode(value: a) -> BitArray
Encode any Gleam value to an ETF binary.
Works on both Erlang and JavaScript targets. Used internally by libero to serialize RPC responses, and also available for non-RPC paths (e.g. passing server-rendered state into a Lustre SPA via flags, in the Elm “init flags” style).
pub fn encode_call(module module: String, msg msg: a) -> BitArray
Encode a call envelope: {module_name, msg} as ETF binary.
Used by generated client send_to_server functions to pack a MsgFromClient value
for transport to the server.
pub fn tag_push(module module: String, msg msg: a) -> BitArray
Tag a push frame so the JS client routes it to the push handler.
The value is an ETF-encoded {module_name, msg} tuple so the
client knows which handler to invoke.
pub fn tag_response(data: BitArray) -> BitArray
Tag a response frame so the JS client routes it to the FIFO callback queue.