Packages

Typed distributed messaging for Gleam on the BEAM.

Retired package: Deprecated - The project needs to be redesigned around a much smaller and clearer core.

Current section

Files

Jump to
distribute src distribute@discovery.erl
Raw

src/distribute@discovery.erl

-module(distribute@discovery).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/distribute/discovery.gleam").
-export([child_spec/0, start_link/0, subscribe/1, unsubscribe/1, snapshot/0, lookup/1, health/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" High-level discovery API for the distribute library.\n"
"\n"
" This module provides a unified facade for the discovery layer, managing\n"
" the default BEAM discovery adapter as a singleton. Other modules\n"
" can use these functions without explicitly passing adapter handles.\n"
"\n"
" ## Architecture\n"
"\n"
" The discovery layer is designed as a pluggable system:\n"
"\n"
" - `discovery/types` - Shared type definitions\n"
" - `discovery/behaviour` - Adapter contract (behaviour)\n"
" - `discovery/adapter` - Adapter utilities and defaults\n"
" - `discovery/beam_adapter` - BEAM node monitoring implementation\n"
" - `discovery` (this module) - High-level singleton facade\n"
"\n"
" ## Usage\n"
"\n"
" Start the discovery as part of your supervision tree:\n"
"\n"
" ```gleam\n"
" import distribute/discovery\n"
" import gleam/otp/static_supervisor as supervisor\n"
"\n"
" pub fn start_app() {\n"
" supervisor.new(supervisor.OneForOne)\n"
" |> supervisor.add(discovery.child_spec())\n"
" |> supervisor.start()\n"
" }\n"
" ```\n"
"\n"
" Then use the discovery functions:\n"
"\n"
" ```gleam\n"
" // Subscribe to peer events\n"
" let callback = fn(event) {\n"
" case event {\n"
" discovery.PeerUp(peer, _meta) -> io.println(\"Peer joined: \" <> peer)\n"
" discovery.PeerDown(peer, _reason) -> io.println(\"Peer left: \" <> peer)\n"
" _ -> Nil\n"
" }\n"
" }\n"
" let assert Ok(sub_id) = discovery.subscribe(callback)\n"
"\n"
" // Get current peers\n"
" let assert Ok(peers) = discovery.snapshot()\n"
"\n"
" // Check health\n"
" case discovery.health() {\n"
" discovery.Up -> io.println(\"Discovery is healthy\")\n"
" discovery.Degraded(reason) -> io.println(\"Degraded: \" <> reason)\n"
" discovery.Down(reason) -> io.println(\"Down: \" <> reason)\n"
" }\n"
" ```\n"
).
-file("src/distribute/discovery.gleam", 92).
?DOC(
" Create a child specification for starting the discovery adapter under\n"
" a supervisor.\n"
"\n"
" This is the recommended way to start the discovery layer. The adapter\n"
" will be automatically restarted if it crashes.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import distribute/discovery\n"
" import gleam/otp/static_supervisor as supervisor\n"
"\n"
" supervisor.new(supervisor.OneForOne)\n"
" |> supervisor.add(discovery.child_spec())\n"
" |> supervisor.start()\n"
" ```\n"
).
-spec child_spec() -> gleam@otp@supervision:child_specification(distribute@discovery@types:adapter_handle()).
child_spec() ->
Opts = begin
_pipe = distribute@discovery@adapter:default_options(
<<"distribute_discovery_adapter"/utf8>>
),
(fun(O) ->
{adapter_options,
<<"distribute_discovery_adapter"/utf8>>,
erlang:element(3, O),
erlang:element(4, O),
erlang:element(5, O),
erlang:element(6, O),
erlang:element(7, O),
erlang:element(8, O),
erlang:element(9, O)}
end)(_pipe)
end,
distribute@discovery@beam_adapter:child_spec(Opts).
-file("src/distribute/discovery.gleam", 106).
?DOC(
" Start the discovery adapter directly without supervision.\n"
"\n"
" **Note:** For production use, prefer `child_spec()` with a supervisor.\n"
" This function is primarily useful for testing or simple scripts.\n"
"\n"
" Returns the adapter handle on success, or an error if startup fails.\n"
).
-spec start_link() -> {ok, distribute@discovery@types:adapter_handle()} |
{error, distribute@discovery@types:discovery_error()}.
start_link() ->
Opts = begin
_pipe = distribute@discovery@adapter:default_options(
<<"distribute_discovery_adapter"/utf8>>
),
(fun(O) ->
{adapter_options,
<<"distribute_discovery_adapter"/utf8>>,
erlang:element(3, O),
erlang:element(4, O),
erlang:element(5, O),
erlang:element(6, O),
erlang:element(7, O),
erlang:element(8, O),
erlang:element(9, O)}
end)(_pipe)
end,
(erlang:element(2, distribute@discovery@beam_adapter:new()))(Opts).
-file("src/distribute/discovery.gleam", 242).
?DOC(" Get the handle for the default discovery adapter.\n").
-spec get_handle() -> {ok, distribute@discovery@types:adapter_handle()} |
{error, nil}.
get_handle() ->
distribute@discovery@beam_adapter:get_handle(
<<"distribute_discovery_adapter"/utf8>>
).
-file("src/distribute/discovery.gleam", 138).
?DOC(
" Subscribe to peer membership events.\n"
"\n"
" The callback will be invoked for each peer event (up, down, update).\n"
" Returns a subscription ID that can be used to unsubscribe later.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let callback = fn(event) {\n"
" case event {\n"
" types.PeerUp(peer, meta) -> \n"
" io.println(\"New peer: \" <> peer)\n"
" types.PeerDown(peer, reason) -> \n"
" io.println(\"Peer left: \" <> peer)\n"
" types.PeerUpdate(peer, meta) -> \n"
" io.println(\"Peer updated: \" <> peer)\n"
" }\n"
" }\n"
" let assert Ok(sub_id) = discovery.subscribe(callback)\n"
" ```\n"
).
-spec subscribe(fun((distribute@discovery@types:discovery_event()) -> nil)) -> {ok,
distribute@discovery@types:subscription_id()} |
{error, distribute@discovery@types:discovery_error()}.
subscribe(Callback) ->
gleam@result:'try'(
begin
_pipe = get_handle(),
gleam@result:map_error(
_pipe,
fun(_) ->
{subscription_failed, <<"Discovery not running"/utf8>>}
end
)
end,
fun(Handle) ->
(erlang:element(4, distribute@discovery@beam_adapter:new()))(
Handle,
Callback
)
end
).
-file("src/distribute/discovery.gleam", 154).
?DOC(
" Unsubscribe from peer events.\n"
"\n"
" After calling this function, the callback associated with the given\n"
" subscription ID will no longer receive events.\n"
).
-spec unsubscribe(distribute@discovery@types:subscription_id()) -> {ok, nil} |
{error, distribute@discovery@types:discovery_error()}.
unsubscribe(Id) ->
gleam@result:'try'(
begin
_pipe = get_handle(),
gleam@result:map_error(
_pipe,
fun(_) ->
{subscription_failed, <<"Discovery not running"/utf8>>}
end
)
end,
fun(Handle) ->
(erlang:element(5, distribute@discovery@beam_adapter:new()))(
Handle,
Id
)
end
).
-file("src/distribute/discovery.gleam", 181).
?DOC(
" Get a snapshot of all currently known peers.\n"
"\n"
" Returns a list of peers with their metadata at the current point in time.\n"
" This is a consistent snapshot - peers won't change during iteration.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let assert Ok(peers) = discovery.snapshot()\n"
" list.each(peers, fn(peer) {\n"
" io.println(\"Peer: \" <> peer.id)\n"
" })\n"
" ```\n"
).
-spec snapshot() -> {ok, list(distribute@discovery@types:peer_info())} |
{error, distribute@discovery@types:discovery_error()}.
snapshot() ->
gleam@result:'try'(
begin
_pipe = get_handle(),
gleam@result:map_error(
_pipe,
fun(_) -> {sync_failed, <<"Discovery not running"/utf8>>} end
)
end,
fun(Handle) ->
(erlang:element(6, distribute@discovery@beam_adapter:new()))(Handle)
end
).
-file("src/distribute/discovery.gleam", 202).
?DOC(
" Lookup a specific peer by ID.\n"
"\n"
" Returns the peer's info if known, or `PeerNotFound` error otherwise.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" case discovery.lookup(\"node2@host\") {\n"
" Ok(peer) -> io.println(\"Found peer with metadata\")\n"
" Error(types.PeerNotFound(_)) -> io.println(\"Peer not found\")\n"
" Error(_) -> io.println(\"Discovery error\")\n"
" }\n"
" ```\n"
).
-spec lookup(binary()) -> {ok, distribute@discovery@types:peer_info()} |
{error, distribute@discovery@types:discovery_error()}.
lookup(Peer_id) ->
gleam@result:'try'(
begin
_pipe = get_handle(),
gleam@result:map_error(
_pipe,
fun(_) -> {sync_failed, <<"Discovery not running"/utf8>>} end
)
end,
fun(Handle) ->
(erlang:element(7, distribute@discovery@beam_adapter:new()))(
Handle,
Peer_id
)
end
).
-file("src/distribute/discovery.gleam", 230).
?DOC(
" Get the health status of the discovery adapter.\n"
"\n"
" Returns:\n"
" - `Up` - Discovery is fully operational\n"
" - `Degraded(reason)` - Discovery is working but with issues\n"
" - `Down(reason)` - Discovery is not operational\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" case discovery.health() {\n"
" types.Up -> io.println(\"All good!\")\n"
" types.Degraded(r) -> io.println(\"Warning: \" <> r)\n"
" types.Down(r) -> io.println(\"Error: \" <> r)\n"
" }\n"
" ```\n"
).
-spec health() -> distribute@discovery@types:health_status().
health() ->
case get_handle() of
{ok, Handle} ->
(erlang:element(8, distribute@discovery@beam_adapter:new()))(Handle);
{error, _} ->
{down, <<"Discovery process not found"/utf8>>}
end.