-module(telega_storage_redis).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/telega_storage_redis.gleam").
-export([new_with_timeout/2, new/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.
?MODULEDOC(
" Redis/Valkey storage adapter for Telega.\n"
"\n"
" Implements `telega/storage.KeyValueStorage` on top of a Valkyrie connection\n"
" pool. TTL is handled natively by the server (`EXPIRE`), so expired keys are\n"
" removed automatically — no lazy cleanup needed. `scan` uses cursor-based\n"
" `SCAN` over a key prefix, which is safe for production unlike `KEYS`.\n"
).
-file("src/telega_storage_redis.gleam", 66).
-spec scan_all(
valkyrie:connection(),
binary(),
integer(),
list(binary()),
integer()
) -> {ok, list(binary())} | {error, valkyrie:error()}.
scan_all(Conn, Pattern, Cursor, Acc, Timeout) ->
case valkyrie:scan(Conn, Cursor, {some, Pattern}, 100, none, Timeout) of
{ok, {Keys, Next_cursor}} ->
Acc@1 = lists:append(Acc, Keys),
case Next_cursor of
0 ->
{ok, Acc@1};
_ ->
scan_all(Conn, Pattern, Next_cursor, Acc@1, Timeout)
end;
{error, Err} ->
{error, Err}
end.
-file("src/telega_storage_redis.gleam", 87).
?DOC(
" Redis `EXPIRE` works in whole seconds; round up so a sub-second TTL still\n"
" lives for at least one second. A non-positive TTL expires immediately.\n"
).
-spec ms_to_seconds(integer()) -> integer().
ms_to_seconds(Ttl_ms) ->
case Ttl_ms =< 0 of
true ->
0;
false ->
(Ttl_ms + 999) div 1000
end.
-file("src/telega_storage_redis.gleam", 26).
?DOC(" Like `new`, but with a custom per-command timeout in milliseconds.\n").
-spec new_with_timeout(valkyrie:connection(), integer()) -> telega@storage:key_value_storage(valkyrie:error()).
new_with_timeout(Conn, Timeout) ->
{key_value_storage, fun(Key) -> case valkyrie:get(Conn, Key, Timeout) of
{ok, Value} ->
{ok, {some, Value}};
{error, not_found} ->
{ok, none};
{error, Err} ->
{error, Err}
end end, fun(Key@1, Value@1) ->
case valkyrie:set(Conn, Key@1, Value@1, none, Timeout) of
{ok, _} ->
{ok, nil};
{error, Err@1} ->
{error, Err@1}
end
end, fun(Key@2, Value@2, Ttl_ms) ->
case valkyrie:set(Conn, Key@2, Value@2, none, Timeout) of
{ok, _} ->
case valkyrie:expire(
Conn,
Key@2,
ms_to_seconds(Ttl_ms),
none,
Timeout
) of
{ok, _} ->
{ok, nil};
{error, Err@2} ->
{error, Err@2}
end;
{error, Err@3} ->
{error, Err@3}
end
end, fun(Key@3) -> case valkyrie:del(Conn, [Key@3], Timeout) of
{ok, _} ->
{ok, nil};
{error, Err@4} ->
{error, Err@4}
end end, fun(Prefix) ->
scan_all(Conn, <<Prefix/binary, "*"/utf8>>, 0, [], Timeout)
end}.
-file("src/telega_storage_redis.gleam", 21).
?DOC(
" Build a `KeyValueStorage` from a Valkyrie connection (default 5s timeout).\n"
"\n"
" The caller owns the connection pool (typically started under a supervisor);\n"
" see the valkyrie docs for setup.\n"
).
-spec new(valkyrie:connection()) -> telega@storage:key_value_storage(valkyrie:error()).
new(Conn) ->
new_with_timeout(Conn, 5000).