-module(barnacle). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). -export([with_name/2, with_poll_interval/2, with_listener/2, with_connect_nodes_function/2, with_disconnect_nodes_function/2, with_list_nodes_function/2, refresh/2, pause/2, shutdown/2, run_once/1, start/1, child_spec/2, new_strategy/1, custom/1, local_epmd/0, epmd/1, dns/3, get_node_basename/1]). -export_type([barnacle/1, strategy/1, node_disconnect_error/0, message/1, barnacle_response/1, state/1, refresh_error/1]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -opaque barnacle(HJY) :: {barnacle, gleam@option:option(gleam@erlang@atom:atom_()), strategy(HJY), integer(), gleam@option:option(gleam@erlang@process:subject(barnacle_response(HJY)))}. -opaque strategy(HJZ) :: {strategy, fun(() -> {ok, list(gleam@erlang@atom:atom_())} | {error, HJZ}), fun((list(gleam@erlang@atom:atom_())) -> {ok, list(gleam@erlang@atom:atom_())} | {error, list({gleam@erlang@atom:atom_(), gleam@erlang@node:connect_error()})}), fun((list(gleam@erlang@atom:atom_())) -> {ok, list(gleam@erlang@atom:atom_())} | {error, list({gleam@erlang@atom:atom_(), node_disconnect_error()})}), fun(() -> {ok, list(gleam@erlang@atom:atom_())} | {error, HJZ})}. -type node_disconnect_error() :: failed_to_disconnect | local_node_is_not_alive. -opaque message(HKA) :: {refresh, gleam@option:option(gleam@erlang@process:subject({ok, list(gleam@erlang@atom:atom_())} | {error, refresh_error(HKA)}))} | {pause, gleam@option:option(gleam@erlang@process:subject(nil))} | {shutdown, gleam@option:option(gleam@erlang@process:subject(nil))}. -type barnacle_response(HKB) :: {refresh_response, {ok, list(gleam@erlang@atom:atom_())} | {error, refresh_error(HKB)}} | {pause_response, nil} | {shutdown_response, nil}. -type state(HKC) :: {state, gleam@erlang@process:subject(message(HKC)), barnacle(HKC), gleam@option:option(gleam@erlang@process:timer())}. -type refresh_error(HKD) :: {strategy_error, HKD} | {connect_error, list({gleam@erlang@atom:atom_(), gleam@erlang@node:connect_error()})} | {disconnect_error, list({gleam@erlang@atom:atom_(), node_disconnect_error()})}. -file("src/barnacle.gleam", 38). ?DOC(" Give the barnacle actor process a custom name.\n"). -spec with_name(barnacle(HKL), binary()) -> barnacle(HKL). with_name(Barnacle, Name) -> _record = Barnacle, {barnacle, {some, begin _pipe = Name, erlang:binary_to_atom(_pipe) end}, erlang:element(3, _record), erlang:element(4, _record), erlang:element(5, _record)}. -file("src/barnacle.gleam", 43). ?DOC(" Set the poll interval for the barnacle actor. Defaults to 5000ms.\n"). -spec with_poll_interval(barnacle(HKO), integer()) -> barnacle(HKO). with_poll_interval(Barnacle, Poll_interval) -> _record = Barnacle, {barnacle, erlang:element(2, _record), erlang:element(3, _record), Poll_interval, erlang:element(5, _record)}. -file("src/barnacle.gleam", 52). ?DOC( " Set the listener subject for your barnacle. This will be notified whenever\n" " the barnacle refreshes, is paused, or is shutdown.\n" ). -spec with_listener( barnacle(HKR), gleam@erlang@process:subject(barnacle_response(HKR)) ) -> barnacle(HKR). with_listener(Barnacle, Listener) -> _record = Barnacle, {barnacle, erlang:element(2, _record), erlang:element(3, _record), erlang:element(4, _record), {some, Listener}}. -file("src/barnacle.gleam", 98). ?DOC( " Add a custom node connection function to your strategy.\n" " Useful if you want to use an alternative to Distributed Erlang, such as\n" " [Partisan](https://github.com/lasp-lang/partisan).\n" ). -spec with_connect_nodes_function( strategy(HLD), fun((list(gleam@erlang@atom:atom_())) -> {ok, list(gleam@erlang@atom:atom_())} | {error, list({gleam@erlang@atom:atom_(), gleam@erlang@node:connect_error()})}) ) -> strategy(HLD). with_connect_nodes_function(Strategy, Connect_nodes) -> _record = Strategy, {strategy, erlang:element(2, _record), Connect_nodes, erlang:element(4, _record), erlang:element(5, _record)}. -file("src/barnacle.gleam", 109). ?DOC( " Add a custom node disconnection function to your strategy.\n" " Useful if you want to use an alternative to Distributed Erlang, such as\n" " [Partisan](https://github.com/lasp-lang/partisan).\n" ). -spec with_disconnect_nodes_function( strategy(HLL), fun((list(gleam@erlang@atom:atom_())) -> {ok, list(gleam@erlang@atom:atom_())} | {error, list({gleam@erlang@atom:atom_(), node_disconnect_error()})}) ) -> strategy(HLL). with_disconnect_nodes_function(Strategy, Disconnect_nodes) -> _record = Strategy, {strategy, erlang:element(2, _record), erlang:element(3, _record), Disconnect_nodes, erlang:element(5, _record)}. -file("src/barnacle.gleam", 120). ?DOC( " Add a custom node listing function to your strategy.\n" " Useful if you want to use an alternative to Distributed Erlang, such as\n" " [Partisan](https://github.com/lasp-lang/partisan).\n" ). -spec with_list_nodes_function( strategy(HLT), fun(() -> {ok, list(gleam@erlang@atom:atom_())} | {error, HLT}) ) -> strategy(HLT). with_list_nodes_function(Strategy, List_nodes) -> _record = Strategy, {strategy, erlang:element(2, _record), erlang:element(3, _record), erlang:element(4, _record), List_nodes}. -file("src/barnacle.gleam", 132). -spec list_nodes() -> {ok, list(gleam@erlang@atom:atom_())} | {error, any()}. list_nodes() -> _pipe = [erlang:node() | erlang:nodes()], _pipe@1 = gleam@list:map(_pipe, fun gleam_erlang_ffi:identity/1), {ok, _pipe@1}. -file("src/barnacle.gleam", 284). ?DOC( " Refresh a barnacle actor. This will attempt to connect to new nodes, and\n" " disconnect from nodes that are no longer available.\n" "\n" " This will reset the poll timer.\n" ). -spec refresh(gleam@erlang@process:subject(message(HND)), integer()) -> {ok, list(gleam@erlang@atom:atom_())} | {error, refresh_error(HND)}. refresh(Subject, Timeout) -> gleam@otp@actor:call( Subject, fun(Subj) -> {refresh, {some, Subj}} end, Timeout ). -file("src/barnacle.gleam", 289). ?DOC(" Pause a barnacle actor. Sending a [`refresh`](#refresh) will restart the actor.\n"). -spec pause(gleam@erlang@process:subject(message(any())), integer()) -> nil. pause(Subject, Timeout) -> gleam@otp@actor:call( Subject, fun(Subj) -> {pause, {some, Subj}} end, Timeout ). -file("src/barnacle.gleam", 295). ?DOC( " Shutdown a barnacle actor. This will stop the actor, and stop any future\n" " refreshes.\n" ). -spec shutdown(gleam@erlang@process:subject(message(any())), integer()) -> nil. shutdown(Subject, Timeout) -> gleam@otp@actor:call( Subject, fun(Subj) -> {shutdown, {some, Subj}} end, Timeout ). -file("src/barnacle.gleam", 357). -spec send_response(gleam@option:option(gleam@erlang@process:subject(HOA)), HOA) -> nil. send_response(Maybe_client, Response) -> gleam@option:map( Maybe_client, fun(_capture) -> gleam@erlang@process:send(_capture, Response) end ), nil. -file("src/barnacle.gleam", 362). -spec cancel_timer(gleam@option:option(gleam@erlang@process:timer())) -> nil. cancel_timer(Timer) -> gleam@option:map(Timer, fun gleam@erlang@process:cancel_timer/1), nil. -file("src/barnacle.gleam", 367). -spec refresh_nodes(barnacle(HOE)) -> {ok, list(gleam@erlang@atom:atom_())} | {error, refresh_error(HOE)}. refresh_nodes(Barnacle) -> gleam@result:'try'( begin _pipe = (erlang:element(2, erlang:element(3, Barnacle)))(), gleam@result:map_error( _pipe, fun(Field@0) -> {strategy_error, Field@0} end ) end, fun(Available_nodes_list) -> gleam@result:'try'( begin _pipe@1 = (erlang:element(5, erlang:element(3, Barnacle)))(), gleam@result:map_error( _pipe@1, fun(Field@0) -> {strategy_error, Field@0} end ) end, fun(Current_nodes_list) -> Self = begin _pipe@2 = erlang:node(), gleam_erlang_ffi:identity(_pipe@2) end, Available_nodes = begin _pipe@3 = Available_nodes_list, _pipe@4 = gleam@set:from_list(_pipe@3), gleam@set:delete(_pipe@4, Self) end, Current_nodes = begin _pipe@5 = Current_nodes_list, _pipe@6 = gleam@set:from_list(_pipe@5), gleam@set:delete(_pipe@6, Self) end, Nodes_to_add = gleam@set:difference( Available_nodes, Current_nodes ), Nodes_to_remove = gleam@set:difference( Current_nodes, Available_nodes ), Connect_results = (erlang:element( 3, erlang:element(3, Barnacle) ))( begin _pipe@7 = Nodes_to_add, gleam@set:to_list(_pipe@7) end ), gleam@result:'try'( begin _pipe@8 = Connect_results, gleam@result:map_error( _pipe@8, fun(Field@0) -> {connect_error, Field@0} end ) end, fun(_) -> Disconnect_results = (erlang:element( 4, erlang:element(3, Barnacle) ))( begin _pipe@9 = Nodes_to_remove, gleam@set:to_list(_pipe@9) end ), gleam@result:'try'( begin _pipe@10 = Disconnect_results, gleam@result:map_error( _pipe@10, fun(Field@0) -> {disconnect_error, Field@0} end ) end, fun(_) -> _pipe@11 = (erlang:element( 5, erlang:element(3, Barnacle) ))(), gleam@result:map_error( _pipe@11, fun(Field@0) -> {strategy_error, Field@0} end ) end ) end ) end ) end ). -file("src/barnacle.gleam", 275). ?DOC( " Run the refresh function once only. This will not start the actor, and will\n" " not do any polling.\n" "\n" " In this case, most configuration options are irrelevant and will be ignored.\n" ). -spec run_once(barnacle(HNA)) -> {ok, list(gleam@erlang@atom:atom_())} | {error, refresh_error(HNA)}. run_once(Barnacle) -> _pipe = Barnacle, refresh_nodes(_pipe). -file("src/barnacle.gleam", 306). -spec handle_message(message(HNP), state(HNP)) -> gleam@otp@actor:next(any(), state(HNP)). handle_message(Message, State) -> {state, Self, Barnacle, Timer} = State, case Message of {refresh, Return} -> Refresh_result = refresh_nodes(Barnacle), cancel_timer(Timer), Timer@1 = gleam@erlang@process:send_after( Self, erlang:element(4, Barnacle), {refresh, none} ), send_response(Return, Refresh_result), send_response( erlang:element(5, Barnacle), {refresh_response, Refresh_result} ), gleam@otp@actor:continue( begin _record = State, {state, erlang:element(2, _record), erlang:element(3, _record), {some, Timer@1}} end ); {pause, Return@1} -> cancel_timer(Timer), send_response(Return@1, nil), send_response(erlang:element(5, Barnacle), {pause_response, nil}), gleam@otp@actor:continue( begin _record@1 = State, {state, erlang:element(2, _record@1), erlang:element(3, _record@1), none} end ); {shutdown, Return@2} -> cancel_timer(Timer), send_response(Return@2, nil), send_response(erlang:element(5, Barnacle), {shutdown_response, nil}), {stop, normal} end. -file("src/barnacle.gleam", 335). -spec spec( barnacle(HNT), gleam@option:option(gleam@erlang@process:subject(gleam@erlang@process:subject(message(HNT)))) ) -> gleam@otp@actor:spec(state(HNT), message(HNT)). spec(Barnacle, Parent) -> {spec, fun() -> gleam@option:map( erlang:element(2, Barnacle), fun(_capture) -> gleam_erlang_ffi:register_process(erlang:self(), _capture) end ), Self = gleam@erlang@process:new_subject(), Selector = begin _pipe = gleam_erlang_ffi:new_selector(), gleam@erlang@process:selecting( _pipe, Self, fun gleam@function:identity/1 ) end, gleam@option:map( Parent, fun(_capture@1) -> gleam@erlang@process:send(_capture@1, Self) end ), Timer = gleam@erlang@process:send_after( Self, erlang:element(4, Barnacle), {refresh, none} ), {ready, {state, Self, Barnacle, {some, Timer}}, Selector} end, 10000, fun handle_message/2}. -file("src/barnacle.gleam", 253). ?DOC( " Start a barnacle actor. This will start the actor, and begin polling for\n" " nodes.\n" ). -spec start(barnacle(HMR)) -> {ok, gleam@erlang@process:subject(message(HMR))} | {error, gleam@otp@actor:start_error()}. start(Barnacle) -> _pipe = Barnacle, _pipe@1 = spec(_pipe, none), gleam@otp@actor:start_spec(_pipe@1). -file("src/barnacle.gleam", 260). ?DOC(" Create a child spec for your barnacle for use in a supervision tree.\n"). -spec child_spec( barnacle(HMU), gleam@erlang@process:subject(gleam@erlang@process:subject(message(HMU))) ) -> gleam@otp@supervisor:child_spec(message(HMU), HZQ, HZQ). child_spec(Barnacle, Parent) -> gleam@otp@supervisor:worker(fun(_) -> _pipe = Barnacle, _pipe@1 = spec(_pipe, {some, Parent}), gleam@otp@actor:start_spec(_pipe@1) end). -file("src/barnacle.gleam", 407). -spec result_apply(list({ok, HOH} | {error, HOI})) -> {ok, list(HOH)} | {error, list(HOI)}. result_apply(Results) -> case gleam@result:partition(Results) of {Vals, []} -> {ok, Vals}; {_, Errs} -> {error, Errs} end. -file("src/barnacle.gleam", 138). -spec connect_nodes(list(gleam@erlang@atom:atom_())) -> {ok, list(gleam@erlang@atom:atom_())} | {error, list({gleam@erlang@atom:atom_(), gleam@erlang@node:connect_error()})}. connect_nodes(Nodes) -> _pipe = Nodes, _pipe@1 = gleam@list:map( _pipe, fun(Node) -> case gleam_erlang_ffi:connect_node(Node) of {ok, _} -> {ok, Node}; {error, Err} -> {error, {Node, Err}} end end ), result_apply(_pipe@1). -file("src/barnacle.gleam", 149). -spec disconnect_nodes(list(gleam@erlang@atom:atom_())) -> {ok, list(gleam@erlang@atom:atom_())} | {error, list({gleam@erlang@atom:atom_(), node_disconnect_error()})}. disconnect_nodes(Nodes) -> _pipe = Nodes, _pipe@1 = gleam@list:map( _pipe, fun(Node) -> case barnacle_ffi:disconnect_from_node(Node) of {ok, _} -> {ok, Node}; {error, Err} -> {error, {Node, Err}} end end ), result_apply(_pipe@1). -file("src/barnacle.gleam", 79). -spec default_strategy() -> strategy(any()). default_strategy() -> {strategy, fun() -> {ok, []} end, fun connect_nodes/1, fun disconnect_nodes/1, fun list_nodes/0}. -file("src/barnacle.gleam", 28). -spec default_barnacle() -> barnacle(any()). default_barnacle() -> {barnacle, none, default_strategy(), 5000, none}. -file("src/barnacle.gleam", 89). ?DOC(" Create a new custom strategy with a single function to discover nodes.\n"). -spec new_strategy( fun(() -> {ok, list(gleam@erlang@atom:atom_())} | {error, HKZ}) ) -> strategy(HKZ). new_strategy(Discover_nodes) -> _record = default_strategy(), {strategy, Discover_nodes, erlang:element(3, _record), erlang:element(4, _record), erlang:element(5, _record)}. -file("src/barnacle.gleam", 171). ?DOC( " Create a barnacle with a custom strategy. This requires at least one function\n" " to discover nodes.\n" "\n" " You can also supply your own functions for connecting, disconnecting, and\n" " listing nodes. If these are not supplied, the default Distributed Erlang\n" " functions will be used.\n" "\n" " Barnacles are generic over their error type, known as the strategy error.\n" " Your strategy functions may use this to return custom errors.\n" ). -spec custom(strategy(HMJ)) -> barnacle(HMJ). custom(Strategy) -> _record = default_barnacle(), {barnacle, erlang:element(2, _record), Strategy, erlang:element(4, _record), erlang:element(5, _record)}. -file("src/barnacle.gleam", 177). ?DOC( " Create a barnacle that uses the local EPMD to discover nodes. This will discover\n" " nodes on the same hostname as the current node.\n" ). -spec local_epmd() -> barnacle(nil). local_epmd() -> _record = default_barnacle(), {barnacle, erlang:element(2, _record), begin _record@1 = default_strategy(), {strategy, fun barnacle@local_epmd:discover_nodes/0, erlang:element(3, _record@1), erlang:element(4, _record@1), erlang:element(5, _record@1)} end, erlang:element(4, _record), erlang:element(5, _record)}. -file("src/barnacle.gleam", 189). ?DOC( " Create a barnacle that connects to a known list of nodes. These may be\n" " remote nodes, or nodes on the same hostname as the current node.\n" ). -spec epmd(list(gleam@erlang@atom:atom_())) -> barnacle(nil). epmd(Nodes) -> _record = default_barnacle(), {barnacle, erlang:element(2, _record), begin _record@1 = default_strategy(), {strategy, fun() -> {ok, Nodes} end, erlang:element(3, _record@1), erlang:element(4, _record@1), erlang:element(5, _record@1)} end, erlang:element(4, _record), erlang:element(5, _record)}. -file("src/barnacle.gleam", 203). ?DOC( " Create a barnacle that discovers nodes using DNS. The first argument is the\n" " basename of the nodes. Currently, Barnacle only supports connecting to nodes\n" " with the same basename.\n" "\n" " The second argument is the hostname to query against. Both A and AAAA records\n" " will be queried. The final argument is an optional timeout for the DNS\n" " lookup. This defaults to 5000ms if not supplied.\n" ). -spec dns(binary(), binary(), gleam@option:option(integer())) -> barnacle(barnacle@dns:lookup_error()). dns(Basename, Hostname_query, Timeout) -> _record = default_barnacle(), {barnacle, erlang:element(2, _record), begin _record@1 = default_strategy(), {strategy, fun() -> barnacle@dns:discover_nodes( Basename, Hostname_query, gleam@option:unwrap(Timeout, 5000) ) end, erlang:element(3, _record@1), erlang:element(4, _record@1), erlang:element(5, _record@1)} end, erlang:element(4, _record), erlang:element(5, _record)}. -file("src/barnacle.gleam", 428). ?DOC( " Get the basename of a node.\n" "\n" " Useful for getting the basename of the current node for use with the DNS\n" " strategy.\n" "\n" " Example:\n" " ```gleam\n" " import barnacle\n" " import gleam/erlang/node\n" "\n" " pub fn main() {\n" " let assert Ok(basename) = barnacle.get_node_basename(node.self())\n" " }\n" " ```\n" ). -spec get_node_basename(gleam@erlang@node:node_()) -> {ok, binary()} | {error, nil}. get_node_basename(Node) -> _pipe = Node, _pipe@1 = gleam_erlang_ffi:identity(_pipe), _pipe@2 = erlang:atom_to_binary(_pipe@1), _pipe@3 = gleam@string:split_once(_pipe@2, <<"@"/utf8>>), gleam@result:map(_pipe@3, fun(Tuple) -> erlang:element(1, Tuple) end).