Current section
Files
Jump to
Current section
Files
src/gleeth@watcher.erl
-module(gleeth@watcher).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleeth/watcher.gleam").
-export([default_config/0, 'receive'/2, stop/1, start_with_subject/3, start_with_config/2, start/1]).
-export_type([block_event/0, watcher_config/0, watcher/0, watcher_message/0, watcher_state/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(
" Block watcher using a BEAM actor (OTP gen_server).\n"
"\n"
" Spawns a supervised actor process that polls for new blocks and sends\n"
" events to a caller-owned Subject. Uses the OTP actor pattern for proper\n"
" lifecycle management and graceful shutdown.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" let assert Ok(w) = watcher.start(provider)\n"
"\n"
" // Block until a new block arrives (or timeout after 30s)\n"
" case watcher.receive(w, 30_000) {\n"
" Ok(watcher.NewBlock(number, hash)) -> io.println(\"Block: \" <> number)\n"
" Error(Nil) -> io.println(\"Timeout\")\n"
" }\n"
"\n"
" watcher.stop(w)\n"
" ```\n"
).
-type block_event() :: {new_block, binary(), binary()}.
-type watcher_config() :: {watcher_config, integer()}.
-opaque watcher() :: {watcher,
gleam@erlang@process:subject(block_event()),
gleam@erlang@process:subject(watcher_message())}.
-type watcher_message() :: {init,
gleam@erlang@process:subject(watcher_message())} |
poll |
stop.
-type watcher_state() :: {watcher_state,
gleeth@provider:provider(),
gleam@erlang@process:subject(block_event()),
gleam@erlang@process:subject(watcher_message()),
watcher_config(),
binary()}.
-file("src/gleeth/watcher.gleam", 42).
?DOC(" Default config: poll every 2 seconds.\n").
-spec default_config() -> watcher_config().
default_config() ->
{watcher_config, 2000}.
-file("src/gleeth/watcher.gleam", 143).
?DOC(
" Receive the next block event, blocking up to `timeout_ms` milliseconds.\n"
" Returns `Error(Nil)` on timeout.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" case watcher.receive(w, 10_000) {\n"
" Ok(watcher.NewBlock(number, hash)) -> // new block\n"
" Error(Nil) -> // timeout\n"
" }\n"
" ```\n"
).
-spec 'receive'(watcher(), integer()) -> {ok, block_event()} | {error, nil}.
'receive'(Watcher, Timeout_ms) ->
gleam@erlang@process:'receive'(erlang:element(2, Watcher), Timeout_ms).
-file("src/gleeth/watcher.gleam", 154).
?DOC(
" Stop the watcher gracefully. The actor process exits cleanly.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" watcher.stop(w)\n"
" ```\n"
).
-spec stop(watcher()) -> nil.
stop(Watcher) ->
gleam@erlang@process:send(erlang:element(3, Watcher), stop).
-file("src/gleeth/watcher.gleam", 223).
-spec emit_blocks_range(
gleeth@provider:provider(),
gleam@erlang@process:subject(block_event()),
integer(),
integer()
) -> nil.
emit_blocks_range(Provider, Events, From, To) ->
case From > To of
true ->
nil;
false ->
Block_hex = gleeth@utils@hex:from_int(From),
Hash = case gleeth@rpc@methods:get_block_by_number(
Provider,
Block_hex
) of
{ok, Block} ->
erlang:element(3, Block);
{error, _} ->
<<""/utf8>>
end,
gleam@erlang@process:send(Events, {new_block, Block_hex, Hash}),
emit_blocks_range(Provider, Events, From + 1, To)
end.
-file("src/gleeth/watcher.gleam", 210).
-spec emit_new_blocks(
gleeth@provider:provider(),
gleam@erlang@process:subject(block_event()),
binary(),
binary()
) -> nil.
emit_new_blocks(Provider, Events, Last_block, Current_block) ->
case {gleeth@utils@hex:to_int(Last_block),
gleeth@utils@hex:to_int(Current_block)} of
{{ok, Last_num}, {ok, Current_num}} ->
emit_blocks_range(Provider, Events, Last_num + 1, Current_num);
{_, _} ->
nil
end.
-file("src/gleeth/watcher.gleam", 243).
-spec is_newer(binary(), binary()) -> boolean().
is_newer(Current, Last) ->
case {gleeth@utils@hex:to_int(Current), gleeth@utils@hex:to_int(Last)} of
{{ok, C}, {ok, L}} ->
C > L;
{_, _} ->
false
end.
-file("src/gleeth/watcher.gleam", 162).
-spec handle_message(watcher_state(), watcher_message()) -> gleam@otp@actor:next(watcher_state(), watcher_message()).
handle_message(State, Message) ->
case Message of
stop ->
gleam@otp@actor:stop();
{init, Self} ->
New_state = {watcher_state,
erlang:element(2, State),
erlang:element(3, State),
Self,
erlang:element(5, State),
erlang:element(6, State)},
gleam@erlang@process:send_after(
Self,
erlang:element(2, erlang:element(5, New_state)),
poll
),
gleam@otp@actor:continue(New_state);
poll ->
New_state@1 = case gleeth@rpc@methods:get_block_number(
erlang:element(2, State)
) of
{ok, Current_block} ->
case is_newer(Current_block, erlang:element(6, State)) of
true ->
emit_new_blocks(
erlang:element(2, State),
erlang:element(3, State),
erlang:element(6, State),
Current_block
),
{watcher_state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
Current_block};
false ->
State
end;
{error, _} ->
State
end,
gleam@erlang@process:send_after(
erlang:element(4, New_state@1),
erlang:element(2, erlang:element(5, New_state@1)),
poll
),
gleam@otp@actor:continue(New_state@1)
end.
-file("src/gleeth/watcher.gleam", 98).
?DOC(
" Start watching, sending events to a caller-provided Subject.\n"
" Use this when another process needs to own the receiving Subject\n"
" (e.g. the event watcher actor).\n"
).
-spec start_with_subject(
gleeth@provider:provider(),
watcher_config(),
gleam@erlang@process:subject(block_event())
) -> {ok, watcher()} | {error, binary()}.
start_with_subject(Provider, Config, Events) ->
case gleeth@rpc@methods:get_block_number(Provider) of
{ok, Initial_block} ->
State = {watcher_state,
Provider,
Events,
gleam@erlang@process:new_subject(),
Config,
Initial_block},
case begin
_pipe = gleam@otp@actor:new(State),
_pipe@1 = gleam@otp@actor:on_message(
_pipe,
fun handle_message/2
),
gleam@otp@actor:start(_pipe@1)
end of
{ok, Started} ->
Actor_subject = erlang:element(3, Started),
gleam@erlang@process:send(
Actor_subject,
{init, Actor_subject}
),
{ok, {watcher, Events, Actor_subject}};
{error, _} ->
{error, <<"Failed to start watcher actor"/utf8>>}
end;
{error, _} ->
{error, <<"Failed to fetch initial block number"/utf8>>}
end.
-file("src/gleeth/watcher.gleam", 88).
?DOC(
" Start watching with custom configuration.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" let config = watcher.WatcherConfig(poll_interval_ms: 500)\n"
" let assert Ok(w) = watcher.start_with_config(provider, config)\n"
" ```\n"
).
-spec start_with_config(gleeth@provider:provider(), watcher_config()) -> {ok,
watcher()} |
{error, binary()}.
start_with_config(Provider, Config) ->
start_with_subject(Provider, Config, gleam@erlang@process:new_subject()).
-file("src/gleeth/watcher.gleam", 76).
?DOC(
" Start watching for new blocks with default config (poll every 2s).\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" let assert Ok(w) = watcher.start(provider)\n"
" ```\n"
).
-spec start(gleeth@provider:provider()) -> {ok, watcher()} | {error, binary()}.
start(Provider) ->
start_with_config(Provider, default_config()).