fed-sx-m1: Step 7b — gen_server-per-projection (start_link/3 + async_fold + query) + 11 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 30s

This commit is contained in:
2026-05-28 06:22:11 +00:00
parent 4956a6d8ae
commit c91683b885
3 changed files with 162 additions and 1 deletions

View File

@@ -1,6 +1,9 @@
-module(projection).
-behaviour(gen_server).
-export([new/2, new/3, fold_activity/2, replay/2,
name/1, state/1, fold_fn/1]).
-export([start_link/3, async_fold/2, query/1, stop/1]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2]).
%% Pure-functional projection driver per design §10.
%%
@@ -52,3 +55,43 @@ field(_, []) -> erlang:error(badkey).
set_field(K, V, [{K, _} | Rest]) -> [{K, V} | Rest];
set_field(K, V, [P | Rest]) -> [P | set_field(K, V, Rest)];
set_field(K, V, []) -> [{K, V}].
%% ── Step 7b: gen_server wrapper ─────────────────────────────────
%%
%% Each projection runs in its own gen_server, registered under the
%% projection's Name atom. `async_fold/2` casts an activity into the
%% process; `query/1` synchronously fetches the current state.
%%
%% Port notes (mirroring Step 5b on the registry): `gen_server:start_link`
%% returns the raw Pid; `?MODULE` macro is unsupported; spawned
%% processes don't survive across separate `erlang-eval-ast` calls
%% so tests must inline start_link with their operations.
start_link(Name, InitialState, FoldFn) ->
Pid = gen_server:start_link(projection, [Name, InitialState, FoldFn]),
erlang:register(Name, Pid),
Pid.
async_fold(Name, Activity) ->
gen_server:cast(Name, {fold, Activity}).
query(Name) ->
gen_server:call(Name, get_state).
stop(Name) ->
R = gen_server:call(Name, '$gen_stop'),
erlang:unregister(Name),
R.
%% gen_server callbacks
init([Name, InitialState, FoldFn]) ->
{ok, new(Name, InitialState, FoldFn)}.
handle_call(get_state, _From, Proj) ->
{reply, state(Proj), Proj}.
handle_cast({fold, Activity}, Proj) ->
{noreply, fold_activity(Proj, Activity)}.
handle_info(_, Proj) -> {noreply, Proj}.