-module(eventsourcing_sqlite). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). -export([load_events/2, load_aggregate_entity/2, new/9, create_event_table/1]). -export_type([sqlite_store/4]). -opaque sqlite_store(OYR, OYS, OYT, OYU) :: {sqlite_store, sqlight:connection(), eventsourcing:aggregate(OYR, OYS, OYT, OYU), fun((OYT) -> binary()), fun((binary()) -> {ok, OYT} | {error, list(gleam@dynamic:decode_error())}), binary(), binary(), binary()}. -spec wrap_events( sqlite_store(any(), any(), PBX, any()), binary(), list(PBX), integer(), list({binary(), binary()}) ) -> list(eventsourcing:event_envelop(PBX)). wrap_events(Postgres_store, Aggregate_id, Events, Sequence, Metadata) -> _pipe = gleam@list:map_fold( Events, Sequence, fun(Sequence@1, Event) -> Next_sequence = Sequence@1 + 1, {Next_sequence, {serialized_event_envelop, Aggregate_id, Sequence@1 + 1, Event, Metadata, erlang:element(6, Postgres_store), erlang:element(7, Postgres_store), erlang:element(8, Postgres_store)}} end ), gleam@pair:second(_pipe). -spec metadata_encoder(list({binary(), binary()})) -> binary(). metadata_encoder(Metadata) -> _pipe = gleam@json:array( Metadata, fun(Row) -> gleam@json:preprocessed_array( [gleam@json:string(erlang:element(1, Row)), gleam@json:string(erlang:element(2, Row))] ) end ), gleam@json:to_string(_pipe). -spec metadata_decoder(gleam@dynamic:dynamic_()) -> {ok, list({binary(), binary()})} | {error, list(gleam@dynamic:decode_error())}. metadata_decoder(Dyn_metadata) -> gleam@result:'try'( gleam@dynamic:string(Dyn_metadata), fun(Str_metadata) -> gleam@result:map( begin _pipe = gleam@json:decode( Str_metadata, gleam@dynamic:list( gleam@dynamic:list(fun gleam@dynamic:string/1) ) ), gleam@result:map_error( _pipe, fun(_) -> erlang:error(#{gleam_error => panic, message => <<"panic expression evaluated"/utf8>>, module => <<"eventsourcing_sqlite"/utf8>>, function => <<"metadata_decoder"/utf8>>, line => 290}) end ) end, fun(List_metadata) -> gleam@list:map( List_metadata, fun(Metadata) -> case Metadata of [Key, Value] -> {Key, Value}; _ -> erlang:error(#{gleam_error => panic, message => <<"panic expression evaluated"/utf8>>, module => <<"eventsourcing_sqlite"/utf8>>, function => <<"metadata_decoder"/utf8>>, line => 295}) end end ) end ) end ). -spec persist_events( sqlite_store(any(), any(), PCI, any()), list(eventsourcing:event_envelop(PCI)) ) -> list({ok, list(gleam@dynamic:dynamic_())} | {error, sqlight:error()}). persist_events(Sqlite_store, Wrapped_events) -> _pipe = Wrapped_events, gleam@list:map( _pipe, fun(Event) -> {serialized_event_envelop, Aggregate_id, Sequence, Payload, Metadata, Event_type, Event_version, Aggregate_type} = case Event of {serialized_event_envelop, _, _, _, _, _, _, _} -> Event; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"eventsourcing_sqlite"/utf8>>, function => <<"persist_events"/utf8>>, line => 247}) end, sqlight:'query'( <<" INSERT INTO event (aggregate_type, aggregate_id, sequence, event_type, event_version, payload, metadata) VALUES ($1, $2, $3, $4, $5, $6, $7) "/utf8>>, erlang:element(2, Sqlite_store), [sqlight:text(Aggregate_type), sqlight:text(Aggregate_id), sqlight:int(Sequence), sqlight:text(Event_type), sqlight:text(Event_version), sqlight:text( begin _pipe@1 = Payload, (erlang:element(4, Sqlite_store))(_pipe@1) end ), sqlight:text( begin _pipe@2 = Metadata, metadata_encoder(_pipe@2) end )], fun gleam@dynamic:dynamic/1 ) end ). -spec commit( sqlite_store(PBG, PBH, PBI, PBJ), eventsourcing:aggregate_context(PBG, PBH, PBI, PBJ), list(PBI), list({binary(), binary()}) ) -> list(eventsourcing:event_envelop(PBI)). commit(Sqlite_store, Context, Events, Metadata) -> {aggregate_context, Aggregate_id, _, Sequence} = Context, Wrapped_events = wrap_events( Sqlite_store, Aggregate_id, Events, Sequence, Metadata ), persist_events(Sqlite_store, Wrapped_events), gleam@io:println( <<<<<<<<"storing: "/utf8, (begin _pipe = Wrapped_events, _pipe@1 = erlang:length(_pipe), gleam@int:to_string(_pipe@1) end)/binary>>/binary, " events for Aggregate ID '"/utf8>>/binary, Aggregate_id/binary>>/binary, "'"/utf8>> ), Wrapped_events. -spec load_events(sqlite_store(any(), any(), PAN, any()), binary()) -> {ok, list(eventsourcing:event_envelop(PAN))} | {error, sqlight:error()}. load_events(Sqlite_store, Aggregate_id) -> gleam@result:map( sqlight:'query'( <<" SELECT aggregate_type, aggregate_id, sequence, event_type, event_version, payload, metadata FROM event WHERE aggregate_type = $1 AND aggregate_id = $2 ORDER BY sequence "/utf8>>, erlang:element(2, Sqlite_store), [sqlight:text(erlang:element(8, Sqlite_store)), sqlight:text(Aggregate_id)], gleam@dynamic:decode7( fun(Field@0, Field@1, Field@2, Field@3, Field@4, Field@5, Field@6) -> {serialized_event_envelop, Field@0, Field@1, Field@2, Field@3, Field@4, Field@5, Field@6} end, gleam@dynamic:element(1, fun gleam@dynamic:string/1), gleam@dynamic:element(2, fun gleam@dynamic:int/1), gleam@dynamic:element( 5, fun(Dyn) -> _assert_subject = begin _pipe = gleam@dynamic:string(Dyn), gleam@result:map( _pipe, erlang:element(5, Sqlite_store) ) end, {ok, Payload} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"eventsourcing_sqlite"/utf8>>, function => <<"load_events"/utf8>>, line => 155}) end, Payload end ), gleam@dynamic:element(6, fun metadata_decoder/1), gleam@dynamic:element(3, fun gleam@dynamic:string/1), gleam@dynamic:element(4, fun gleam@dynamic:string/1), gleam@dynamic:element(0, fun gleam@dynamic:string/1) ) ), fun(Resulted) -> Resulted end ). -spec load_aggregate_entity(sqlite_store(PAB, any(), any(), any()), binary()) -> {ok, PAB} | {error, nil}. load_aggregate_entity(Sqlite_store, Aggregate_id) -> _assert_subject = load_events(Sqlite_store, Aggregate_id), {ok, Commited_events} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"eventsourcing_sqlite"/utf8>>, function => <<"load_aggregate_entity"/utf8>>, line => 117}) end, gleam@bool:guard( begin _pipe = Commited_events, erlang:length(_pipe) end =:= 0, {error, nil}, fun() -> {Aggregate@1, Sequence} = gleam@list:fold( Commited_events, {erlang:element(3, Sqlite_store), 0}, fun(Aggregate_and_sequence, Event_envelop) -> {Aggregate, _} = Aggregate_and_sequence, {erlang:setelement( 2, Aggregate, (erlang:element(4, Aggregate))( erlang:element(2, Aggregate), erlang:element(4, Event_envelop) ) ), erlang:element(3, Event_envelop)} end ), _pipe@1 = erlang:element( 2, erlang:element( 3, {aggregate_context, Aggregate_id, Aggregate@1, Sequence} ) ), {ok, _pipe@1} end ). -spec load_aggregate(sqlite_store(PAU, PAV, PAW, PAX), binary()) -> eventsourcing:aggregate_context(PAU, PAV, PAW, PAX). load_aggregate(Sqlite_store, Aggregate_id) -> _assert_subject = load_events(Sqlite_store, Aggregate_id), {ok, Commited_events} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"eventsourcing_sqlite"/utf8>>, function => <<"load_aggregate"/utf8>>, line => 172}) end, {Aggregate@1, Sequence} = gleam@list:fold( Commited_events, {erlang:element(3, Sqlite_store), 0}, fun(Aggregate_and_sequence, Event_envelop) -> {Aggregate, _} = Aggregate_and_sequence, {erlang:setelement( 2, Aggregate, (erlang:element(4, Aggregate))( erlang:element(2, Aggregate), erlang:element(4, Event_envelop) ) ), erlang:element(3, Event_envelop)} end ), {aggregate_context, Aggregate_id, Aggregate@1, Sequence}. -spec new( sqlight:connection(), OYW, fun((OYW, OYX) -> {ok, list(OYY)} | {error, OYZ}), fun((OYW, OYY) -> OYW), fun((OYY) -> binary()), fun((binary()) -> {ok, OYY} | {error, list(gleam@dynamic:decode_error())}), binary(), binary(), binary() ) -> eventsourcing:event_store(sqlite_store(OYW, OYX, OYY, OYZ), OYW, OYX, OYY, OYZ). new( Sqlight_connection, Empty_entity, Handle, Apply, Event_encoder, Event_decoder, Event_type, Event_version, Aggregate_type ) -> Eventstore = {sqlite_store, Sqlight_connection, {aggregate, Empty_entity, Handle, Apply}, Event_encoder, Event_decoder, Event_type, Event_version, Aggregate_type}, {event_store, Eventstore, fun load_aggregate/2, fun commit/4}. -spec create_event_table(sqlite_store(any(), any(), any(), any())) -> {ok, list(gleam@dynamic:dynamic_())} | {error, sqlight:error()}. create_event_table(Sqlite_store) -> sqlight:'query'( <<" CREATE TABLE IF NOT EXISTS event ( aggregate_type text NOT NULL, aggregate_id text NOT NULL, sequence bigint CHECK (sequence >= 0) NOT NULL, event_type text NOT NULL, event_version text NOT NULL, payload text NOT NULL, metadata text, PRIMARY KEY (aggregate_type, aggregate_id, sequence) ); "/utf8>>, erlang:element(2, Sqlite_store), [], fun gleam@dynamic:dynamic/1 ).