Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 20s
64 lines
2.3 KiB
Erlang
64 lines
2.3 KiB
Erlang
-module(log).
|
|
-export([open/2, append/2, tip/1, replay/3, entries/1]).
|
|
|
|
%% Per-actor activity log — the canonical record of everything an
|
|
%% actor has emitted, in chronological order. Per design §15.2 this
|
|
%% lives on disk as a JSONL segment file; v1 starts with an in-memory
|
|
%% backend so the API and seq-number machinery can be locked down
|
|
%% before the on-disk format is added (Step 3b).
|
|
%%
|
|
%% State shape (a property list):
|
|
%% [{actor, ActorId}, {base, BasePath}, {seq, NextSeq}, {entries, [Act|...]}]
|
|
%%
|
|
%% `entries` stores activities in append order — i.e. oldest first.
|
|
%% `seq` is the next sequence number that will be assigned by append.
|
|
%% `base` is kept on the state for forward-compatibility with 3b
|
|
%% (where it becomes the segment-file directory).
|
|
%%
|
|
%% open/2 takes ActorId + BasePath and returns {ok, LogState} starting
|
|
%% with seq=0 and no entries.
|
|
%%
|
|
%% append/2 returns {ok, NewLogState, AssignedSeq}.
|
|
%%
|
|
%% tip/1 returns the next seq the log would assign (== count of entries).
|
|
%%
|
|
%% replay/3 folds Fun(Activity, AssignedSeq, Acc) over every entry in
|
|
%% append order. Three-arity rather than two-arity because the plan's
|
|
%% example test is "sequence numbers gap-free across replay" — having
|
|
%% the seq number visible in the fold makes that test direct.
|
|
%%
|
|
%% entries/1 is a debug accessor returning [Activity, ...] in append
|
|
%% order. Not part of the public API contract.
|
|
|
|
open(ActorId, BasePath) ->
|
|
{ok, [{actor, ActorId}, {base, BasePath}, {seq, 0}, {entries, []}]}.
|
|
|
|
append(LogState, Activity) ->
|
|
Seq = field(seq, LogState),
|
|
Entries = field(entries, LogState),
|
|
NewState = replace_field(seq, Seq + 1,
|
|
replace_field(entries, Entries ++ [Activity], LogState)),
|
|
{ok, NewState, Seq}.
|
|
|
|
tip(LogState) ->
|
|
field(seq, LogState).
|
|
|
|
replay(LogState, InitAcc, Fun) ->
|
|
Entries = field(entries, LogState),
|
|
replay_loop(Entries, 0, InitAcc, Fun).
|
|
|
|
replay_loop([], _, Acc, _) -> Acc;
|
|
replay_loop([Act | Rest], Seq, Acc, Fun) ->
|
|
replay_loop(Rest, Seq + 1, Fun(Act, Seq, Acc), Fun).
|
|
|
|
entries(LogState) ->
|
|
field(entries, LogState).
|
|
|
|
field(K, [{K, V} | _]) -> V;
|
|
field(K, [_ | Rest]) -> field(K, Rest);
|
|
field(_, []) -> erlang:error(badkey).
|
|
|
|
replace_field(K, V, []) -> [{K, V}];
|
|
replace_field(K, V, [{K, _} | Rest]) -> [{K, V} | Rest];
|
|
replace_field(K, V, [P | Rest]) -> [P | replace_field(K, V, Rest)].
|