Current section
Files
Jump to
Current section
Files
src/telega@storage@ets.erl
-module(telega@storage@ets).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/telega/storage/ets.gleam").
-export([new/1]).
-export_type([ets_table/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(
" ETS-backed `KeyValueStorage` with lazy TTL expiration.\n"
"\n"
" Values live in a public, named ETS table for the lifetime of the VM but do\n"
" NOT survive a VM restart. TTL is enforced lazily: an expired entry is only\n"
" removed when it is next accessed via `get` or `scan`. For persistence\n"
" across restarts use a database-backed package (Postgres/SQLite/Redis).\n"
).
-type ets_table() :: any().
-file("src/telega/storage/ets.gleam", 59).
?DOC(" An `expires_at` of `0` means \"never expires\".\n").
-spec is_live(integer()) -> boolean().
is_live(Expires_at) ->
(Expires_at =:= 0) orelse (telega@internal@utils:current_time_ms() < Expires_at).
-file("src/telega/storage/ets.gleam", 77).
-spec do_scan(ets_table(), binary()) -> list(binary()).
do_scan(Table, Prefix) ->
_pipe = ets:tab2list(Table),
gleam@list:filter_map(
_pipe,
fun(Entry) ->
{Key, _, Expires_at} = Entry,
case gleam_stdlib:string_starts_with(Key, Prefix) andalso is_live(
Expires_at
) of
true ->
{ok, Key};
false ->
{error, nil}
end
end
).
-file("src/telega/storage/ets.gleam", 63).
-spec do_get(ets_table(), binary()) -> gleam@option:option(binary()).
do_get(Table, Key) ->
case ets:lookup(Table, Key) of
[] ->
none;
[{_, Value, Expires_at} | _] ->
case is_live(Expires_at) of
true ->
{some, Value};
false ->
ets:delete(Table, Key),
none
end
end.
-file("src/telega/storage/ets.gleam", 91).
-spec is_undefined(gleam@dynamic:dynamic_()) -> boolean().
is_undefined(Value) ->
case gleam_erlang_ffi:atom_from_string(<<"undefined"/utf8>>) of
{ok, Undefined} ->
Value =:= gleam_erlang_ffi:identity(Undefined);
{error, _} ->
false
end.
-file("src/telega/storage/ets.gleam", 46).
-spec start_table(gleam@erlang@atom:atom_()) -> ets_table().
start_table(Name) ->
case is_undefined(ets:whereis(Name)) of
true ->
ets:new(
Name,
[erlang:binary_to_atom(<<"set"/utf8>>),
erlang:binary_to_atom(<<"public"/utf8>>),
erlang:binary_to_atom(<<"named_table"/utf8>>)]
);
false ->
gleam_stdlib:identity(ets:whereis(Name))
end.
-file("src/telega/storage/ets.gleam", 24).
?DOC(
" Create an ETS-backed `KeyValueStorage`.\n"
"\n"
" `name` is the ETS table name; reusing the same name returns a handle to the\n"
" existing table. The error type stays generic because none of the operations\n"
" can fail, so the result composes with flows/sessions of any error type.\n"
).
-spec new(binary()) -> {ok, telega@storage:key_value_storage(any())} |
{error, telega@error:telega_error()}.
new(Name) ->
Table = start_table(erlang:binary_to_atom(Name)),
{ok,
{key_value_storage,
fun(Key) -> {ok, do_get(Table, Key)} end,
fun(Key@1, Value) ->
ets:insert(Table, {Key@1, Value, 0}),
{ok, nil}
end,
fun(Key@2, Value@1, Ttl_ms) ->
ets:insert(
Table,
{Key@2,
Value@1,
telega@internal@utils:current_time_ms() + Ttl_ms}
),
{ok, nil}
end,
fun(Key@3) ->
ets:delete(Table, Key@3),
{ok, nil}
end,
fun(Prefix) -> {ok, do_scan(Table, Prefix)} end}}.