src/lightspeed@data@repository.erl

-module(lightspeed@data@repository).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/lightspeed/data/repository.gleam").
-export([tenant_scope/3, system_scope/1, new/1, seed/2, adapter/1, adapter_label/1, all/1, list_by_tenant/3, fetch_by_id/3, upsert/3, delete_by_id/3, role_label/1, scope_label/1, error_label/1]).
-export_type([adapter/0, role/0, scope/0, record/0, access_error/0, repository/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(" Scoped data-access patterns with Gleam/Elixir interoperability adapters.\n").

-type adapter() :: gleam_in_memory |
    {elixir_bridge, binary()} |
    {mixed_bridge, binary(), binary()}.

-type role() :: viewer | editor | tenant_admin.

-type scope() :: {tenant_scope, binary(), binary(), role()} |
    {system_scope, binary()}.

-type record() :: {record, binary(), binary(), binary(), binary(), binary()}.

-type access_error() :: {not_found, binary()} |
    {forbidden, binary(), binary(), binary()}.

-opaque repository() :: {repository, adapter(), list(record())}.

-file("src/lightspeed/data/repository.gleam", 48).
?DOC(" Build a tenant-scoped actor identity.\n").
-spec tenant_scope(binary(), binary(), role()) -> scope().
tenant_scope(Actor_id, Tenant_id, Role) ->
    {tenant_scope, Actor_id, Tenant_id, Role}.

-file("src/lightspeed/data/repository.gleam", 53).
?DOC(" Build a system-scoped actor identity.\n").
-spec system_scope(binary()) -> scope().
system_scope(Actor_id) ->
    {system_scope, Actor_id}.

-file("src/lightspeed/data/repository.gleam", 58).
?DOC(" Build an empty repository for one adapter profile.\n").
-spec new(adapter()) -> repository().
new(Adapter) ->
    {repository, Adapter, []}.

-file("src/lightspeed/data/repository.gleam", 63).
?DOC(" Build repository with seeded records.\n").
-spec seed(adapter(), list(record())) -> repository().
seed(Adapter, Rows) ->
    {repository, Adapter, lists:reverse(Rows)}.

-file("src/lightspeed/data/repository.gleam", 68).
?DOC(" Repository adapter profile.\n").
-spec adapter(repository()) -> adapter().
adapter(Repository) ->
    erlang:element(2, Repository).

-file("src/lightspeed/data/repository.gleam", 73).
?DOC(" Stable adapter label for fixtures and migration signatures.\n").
-spec adapter_label(adapter()) -> binary().
adapter_label(Adapter) ->
    case Adapter of
        gleam_in_memory ->
            <<"gleam_in_memory"/utf8>>;

        {elixir_bridge, Module} ->
            <<"elixir_bridge:"/utf8, Module/binary>>;

        {mixed_bridge, Read_module, Write_module} ->
            <<<<<<"mixed_bridge:"/utf8, Read_module/binary>>/binary, ":"/utf8>>/binary,
                Write_module/binary>>
    end.

-file("src/lightspeed/data/repository.gleam", 83).
?DOC(" All rows in stable order.\n").
-spec all(repository()) -> list(record()).
all(Repository) ->
    lists:reverse(erlang:element(3, Repository)).

-file("src/lightspeed/data/repository.gleam", 179).
-spec forbidden(scope(), binary(), binary()) -> access_error().
forbidden(Scope, Action, Tenant_id) ->
    case Scope of
        {tenant_scope, Actor_id, _, _} ->
            {forbidden, Actor_id, Action, Tenant_id};

        {system_scope, Actor_id@1} ->
            {forbidden, Actor_id@1, Action, Tenant_id}
    end.

-file("src/lightspeed/data/repository.gleam", 244).
-spec filter_tenant(list(record()), binary(), list(record())) -> list(record()).
filter_tenant(Rows, Tenant_id, Rows_rev) ->
    case Rows of
        [] ->
            lists:reverse(Rows_rev);

        [Row | Rest] ->
            case erlang:element(3, Row) =:= Tenant_id of
                true ->
                    filter_tenant(Rest, Tenant_id, [Row | Rows_rev]);

                false ->
                    filter_tenant(Rest, Tenant_id, Rows_rev)
            end
    end.

-file("src/lightspeed/data/repository.gleam", 188).
-spec can_list_tenant(scope(), binary()) -> boolean().
can_list_tenant(Scope, Tenant_id) ->
    case Scope of
        {system_scope, _} ->
            true;

        {tenant_scope, _, Scope_tenant, _} ->
            Scope_tenant =:= Tenant_id
    end.

-file("src/lightspeed/data/repository.gleam", 88).
?DOC(" List rows by tenant with scope validation.\n").
-spec list_by_tenant(repository(), scope(), binary()) -> {ok, list(record())} |
    {error, access_error()}.
list_by_tenant(Repository, Scope, Tenant_id) ->
    case can_list_tenant(Scope, Tenant_id) of
        true ->
            {ok, filter_tenant(all(Repository), Tenant_id, [])};

        false ->
            {error, forbidden(Scope, <<"list"/utf8>>, Tenant_id)}
    end.

-file("src/lightspeed/data/repository.gleam", 195).
-spec can_read(scope(), binary()) -> boolean().
can_read(Scope, Tenant_id) ->
    case Scope of
        {system_scope, _} ->
            true;

        {tenant_scope, _, Scope_tenant, _} ->
            Scope_tenant =:= Tenant_id
    end.

-file("src/lightspeed/data/repository.gleam", 233).
-spec lookup(list(record()), binary()) -> {ok, record()} | {error, nil}.
lookup(Rows, Id) ->
    case Rows of
        [] ->
            {error, nil};

        [Row | Rest] ->
            case erlang:element(2, Row) =:= Id of
                true ->
                    {ok, Row};

                false ->
                    lookup(Rest, Id)
            end
    end.

-file("src/lightspeed/data/repository.gleam", 100).
?DOC(" Fetch one row by id with scope validation.\n").
-spec fetch_by_id(repository(), scope(), binary()) -> {ok, record()} |
    {error, access_error()}.
fetch_by_id(Repository, Scope, Id) ->
    case lookup(all(Repository), Id) of
        {error, nil} ->
            {error, {not_found, Id}};

        {ok, Row} ->
            case can_read(Scope, erlang:element(3, Row)) of
                true ->
                    {ok, Row};

                false ->
                    {error,
                        forbidden(
                            Scope,
                            <<"read"/utf8>>,
                            erlang:element(3, Row)
                        )}
            end
    end.

-file("src/lightspeed/data/repository.gleam", 222).
-spec remove_id(list(record()), binary()) -> list(record()).
remove_id(Rows_rev, Id) ->
    case Rows_rev of
        [] ->
            [];

        [Row | Rest] ->
            case erlang:element(2, Row) =:= Id of
                true ->
                    remove_id(Rest, Id);

                false ->
                    [Row | remove_id(Rest, Id)]
            end
    end.

-file("src/lightspeed/data/repository.gleam", 218).
-spec upsert_row(list(record()), record()) -> list(record()).
upsert_row(Rows_rev, Next) ->
    [Next | remove_id(Rows_rev, erlang:element(2, Next))].

-file("src/lightspeed/data/repository.gleam", 210).
-spec role_can_write(role()) -> boolean().
role_can_write(Role) ->
    case Role of
        viewer ->
            false;

        editor ->
            true;

        tenant_admin ->
            true
    end.

-file("src/lightspeed/data/repository.gleam", 202).
-spec can_write(scope(), binary()) -> boolean().
can_write(Scope, Tenant_id) ->
    case Scope of
        {system_scope, _} ->
            true;

        {tenant_scope, _, Scope_tenant, Role} ->
            (Scope_tenant =:= Tenant_id) andalso role_can_write(Role)
    end.

-file("src/lightspeed/data/repository.gleam", 116).
?DOC(" Upsert one row with scope validation.\n").
-spec upsert(repository(), scope(), record()) -> {ok, repository()} |
    {error, access_error()}.
upsert(Repository, Scope, Row) ->
    case can_write(Scope, erlang:element(3, Row)) of
        false ->
            {error, forbidden(Scope, <<"write"/utf8>>, erlang:element(3, Row))};

        true ->
            {ok,
                {repository,
                    erlang:element(2, Repository),
                    upsert_row(erlang:element(3, Repository), Row)}}
    end.

-file("src/lightspeed/data/repository.gleam", 131).
?DOC(" Delete one row by id with scope validation.\n").
-spec delete_by_id(repository(), scope(), binary()) -> {ok, repository()} |
    {error, access_error()}.
delete_by_id(Repository, Scope, Id) ->
    case lookup(all(Repository), Id) of
        {error, nil} ->
            {error, {not_found, Id}};

        {ok, Row} ->
            case can_write(Scope, erlang:element(3, Row)) of
                false ->
                    {error,
                        forbidden(
                            Scope,
                            <<"delete"/utf8>>,
                            erlang:element(3, Row)
                        )};

                true ->
                    {ok,
                        {repository,
                            erlang:element(2, Repository),
                            remove_id(erlang:element(3, Repository), Id)}}
            end
    end.

-file("src/lightspeed/data/repository.gleam", 153).
?DOC(" Stable role label.\n").
-spec role_label(role()) -> binary().
role_label(Role) ->
    case Role of
        viewer ->
            <<"viewer"/utf8>>;

        editor ->
            <<"editor"/utf8>>;

        tenant_admin ->
            <<"tenant_admin"/utf8>>
    end.

-file("src/lightspeed/data/repository.gleam", 162).
?DOC(" Stable scope label.\n").
-spec scope_label(scope()) -> binary().
scope_label(Scope) ->
    case Scope of
        {tenant_scope, Actor_id, Tenant_id, Role} ->
            <<<<<<<<<<"tenant_scope:"/utf8, Actor_id/binary>>/binary, ":"/utf8>>/binary,
                        Tenant_id/binary>>/binary,
                    ":"/utf8>>/binary,
                (role_label(Role))/binary>>;

        {system_scope, Actor_id@1} ->
            <<"system_scope:"/utf8, Actor_id@1/binary>>
    end.

-file("src/lightspeed/data/repository.gleam", 171).
?DOC(" Stable error label.\n").
-spec error_label(access_error()) -> binary().
error_label(Error) ->
    case Error of
        {not_found, Id} ->
            <<"not_found:"/utf8, Id/binary>>;

        {forbidden, Actor_id, Action, Tenant_id} ->
            <<<<<<<<<<"forbidden:"/utf8, Actor_id/binary>>/binary, ":"/utf8>>/binary,
                        Action/binary>>/binary,
                    ":"/utf8>>/binary,
                Tenant_id/binary>>
    end.