Skip to main content

src/telega_storage_redis.erl

-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).