Skip to main content

src/glesia.erl

-module(glesia).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/glesia.gleam").
-export([create_schema/1, create_local_schema/0, delete_schema/1, delete_local_schema/0, set_dir/1, start/0, stop/0, create_table/4, create_ram_table/2, dirty_write/1, dirty_read/2, dirty_read_decoded/3, dirty_delete/2, insert_new/3, transaction/1]).
-export_type([mnesia_error/0, storage_type/0, table_type/0, schema_result/0, simple_result/0, table_create_result/0, read_result/0, insert_new_result/0, transaction_result/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(
    " Safe-ish Gleam bindings for Erlang Mnesia.\n"
    "\n"
    " Glesia wraps the common Mnesia lifecycle and data operations while keeping\n"
    " the dynamic BEAM boundary explicit. Table names and attributes are atoms;\n"
    " records cross the FFI boundary as `Dynamic` values and can be decoded by\n"
    " callers with normal Gleam decoders.\n"
).

-type mnesia_error() :: already_exists |
    not_found |
    timeout |
    {bad_type, binary()} |
    {abort, binary()} |
    {unknown, binary()}.

-type storage_type() :: ram_copies | disc_copies | disc_only_copies.

-type table_type() :: set | ordered_set | bag.

-type schema_result() :: schema_ok | {schema_error, gleam@dynamic:dynamic_()}.

-type simple_result() :: simple_ok | {simple_error, gleam@dynamic:dynamic_()}.

-type table_create_result() :: table_create_ok |
    table_create_already_exists |
    {table_create_error, gleam@dynamic:dynamic_()}.

-type read_result() :: {read_ok, list(gleam@dynamic:dynamic_())} |
    {read_error, gleam@dynamic:dynamic_()}.

-type insert_new_result() :: insert_new_inserted |
    insert_new_exists |
    {insert_new_error, gleam@dynamic:dynamic_()}.

-type transaction_result() :: {transaction_ok, gleam@dynamic:dynamic_()} |
    {transaction_abort, gleam@dynamic:dynamic_()}.

-file("src/glesia.gleam", 255).
-spec classify(gleam@dynamic:dynamic_()) -> mnesia_error().
classify(Reason) ->
    Inspected = gleam@string:inspect(Reason),
    case Inspected of
        <<"already_exists"/utf8>> ->
            already_exists;

        <<"not_found"/utf8>> ->
            not_found;

        <<"timeout"/utf8>> ->
            timeout;

        _ ->
            {unknown, Inspected}
    end.

-file("src/glesia.gleam", 106).
-spec create_schema(list(gleam@erlang@atom:atom_())) -> {ok, nil} |
    {error, mnesia_error()}.
create_schema(Nodes) ->
    case glesia_ffi:create_schema(Nodes) of
        schema_ok ->
            {ok, nil};

        {schema_error, Reason} ->
            {error, classify(Reason)}
    end.

-file("src/glesia.gleam", 113).
-spec create_local_schema() -> {ok, nil} | {error, mnesia_error()}.
create_local_schema() ->
    create_schema([erlang:node()]).

-file("src/glesia.gleam", 117).
-spec delete_schema(list(gleam@erlang@atom:atom_())) -> {ok, nil} |
    {error, mnesia_error()}.
delete_schema(Nodes) ->
    case glesia_ffi:delete_schema(Nodes) of
        schema_ok ->
            {ok, nil};

        {schema_error, Reason} ->
            {error, classify(Reason)}
    end.

-file("src/glesia.gleam", 124).
-spec delete_local_schema() -> {ok, nil} | {error, mnesia_error()}.
delete_local_schema() ->
    delete_schema([erlang:node()]).

-file("src/glesia.gleam", 128).
-spec set_dir(binary()) -> {ok, nil} | {error, mnesia_error()}.
set_dir(Path) ->
    case glesia_ffi:set_dir(Path) of
        simple_ok ->
            {ok, nil};

        {simple_error, Reason} ->
            {error, classify(Reason)}
    end.

-file("src/glesia.gleam", 135).
-spec start() -> {ok, nil} | {error, mnesia_error()}.
start() ->
    case glesia_ffi:start() of
        simple_ok ->
            {ok, nil};

        {simple_error, Reason} ->
            {error, classify(Reason)}
    end.

-file("src/glesia.gleam", 142).
-spec stop() -> {ok, nil} | {error, mnesia_error()}.
stop() ->
    case glesia_ffi:stop() of
        simple_ok ->
            {ok, nil};

        {simple_error, Reason} ->
            {error, classify(Reason)}
    end.

-file("src/glesia.gleam", 247).
-spec table_type_to_string(table_type()) -> binary().
table_type_to_string(Table_type) ->
    case Table_type of
        set ->
            <<"set"/utf8>>;

        ordered_set ->
            <<"ordered_set"/utf8>>;

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

-file("src/glesia.gleam", 239).
-spec storage_type_to_string(storage_type()) -> binary().
storage_type_to_string(Storage_type) ->
    case Storage_type of
        ram_copies ->
            <<"ram_copies"/utf8>>;

        disc_copies ->
            <<"disc_copies"/utf8>>;

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

-file("src/glesia.gleam", 149).
-spec create_table(
    gleam@erlang@atom:atom_(),
    list(gleam@erlang@atom:atom_()),
    storage_type(),
    table_type()
) -> {ok, nil} | {error, mnesia_error()}.
create_table(Table, Attributes, Storage_type, Table_type) ->
    case glesia_ffi:create_table(
        Table,
        Attributes,
        storage_type_to_string(Storage_type),
        table_type_to_string(Table_type)
    ) of
        table_create_ok ->
            {ok, nil};

        table_create_already_exists ->
            {error, already_exists};

        {table_create_error, Reason} ->
            {error, classify(Reason)}
    end.

-file("src/glesia.gleam", 169).
-spec create_ram_table(
    gleam@erlang@atom:atom_(),
    list(gleam@erlang@atom:atom_())
) -> {ok, nil} | {error, mnesia_error()}.
create_ram_table(Table, Attributes) ->
    create_table(Table, Attributes, ram_copies, set).

-file("src/glesia.gleam", 176).
-spec dirty_write(gleam@dynamic:dynamic_()) -> {ok, nil} |
    {error, mnesia_error()}.
dirty_write(Record) ->
    case glesia_ffi:dirty_write(Record) of
        simple_ok ->
            {ok, nil};

        {simple_error, Reason} ->
            {error, classify(Reason)}
    end.

-file("src/glesia.gleam", 183).
-spec dirty_read(gleam@erlang@atom:atom_(), gleam@dynamic:dynamic_()) -> {ok,
        list(gleam@dynamic:dynamic_())} |
    {error, mnesia_error()}.
dirty_read(Table, Key) ->
    case glesia_ffi:dirty_read(Table, Key) of
        {read_ok, Records} ->
            {ok, Records};

        {read_error, Reason} ->
            {error, classify(Reason)}
    end.

-file("src/glesia.gleam", 193).
-spec dirty_read_decoded(
    gleam@erlang@atom:atom_(),
    gleam@dynamic:dynamic_(),
    gleam@dynamic@decode:decoder(EZT)
) -> {ok, list(EZT)} | {error, mnesia_error()}.
dirty_read_decoded(Table, Key, Decoder) ->
    _pipe = dirty_read(Table, Key),
    gleam@result:'try'(_pipe, fun(Records) -> _pipe@1 = Records,
            gleam@list:try_map(
                _pipe@1,
                fun(Record) ->
                    _pipe@2 = gleam@dynamic@decode:run(Record, Decoder),
                    gleam@result:map_error(
                        _pipe@2,
                        fun(Error) ->
                            {bad_type, gleam@string:inspect(Error)}
                        end
                    )
                end
            ) end).

-file("src/glesia.gleam", 208).
-spec dirty_delete(gleam@erlang@atom:atom_(), gleam@dynamic:dynamic_()) -> {ok,
        nil} |
    {error, mnesia_error()}.
dirty_delete(Table, Key) ->
    case glesia_ffi:dirty_delete(Table, Key) of
        simple_ok ->
            {ok, nil};

        {simple_error, Reason} ->
            {error, classify(Reason)}
    end.

-file("src/glesia.gleam", 220).
?DOC(
    " Atomically write a record only when no record exists for the given key.\n"
    "\n"
    " This uses a transactional Mnesia read with a write lock, then writes the\n"
    " record inside the same transaction. It returns `Ok(True)` when inserted and\n"
    " `Ok(False)` when the key already exists.\n"
).
-spec insert_new(
    gleam@erlang@atom:atom_(),
    gleam@dynamic:dynamic_(),
    gleam@dynamic:dynamic_()
) -> {ok, boolean()} | {error, mnesia_error()}.
insert_new(Table, Key, Record) ->
    case glesia_ffi:insert_new(Table, Key, Record) of
        insert_new_inserted ->
            {ok, true};

        insert_new_exists ->
            {ok, false};

        {insert_new_error, Reason} ->
            {error, classify(Reason)}
    end.

-file("src/glesia.gleam", 232).
-spec transaction(fun(() -> any())) -> {ok, gleam@dynamic:dynamic_()} |
    {error, mnesia_error()}.
transaction(Fun) ->
    case glesia_ffi:transaction(Fun) of
        {transaction_ok, Value} ->
            {ok, Value};

        {transaction_abort, Reason} ->
            {error, {abort, gleam@string:inspect(Reason)}}
    end.