11#include <unordered_set>
15#include "../attributes.hpp"
16#include "../core/completion.hpp"
18#include "../core/executor.hpp"
20namespace morph::journal {
29inline constexpr std::uint32_t kLogFormatVersion = 1;
38enum class Outcome : std::uint8_t { Succeeded, Failed };
120 std::uint32_t
v = kLogFormatVersion;
153 using std::runtime_error::runtime_error;
209 [[nodiscard]]
virtual std::vector<LogEntry>
entries(std::string_view entityKey = {})
const = 0;
219 [[nodiscard]] virtual ::morph::exec::IExecutor*
owner() const noexcept {
return nullptr; }
233 return askEntries(replyExec, std::move(entityKey));
256 [[nodiscard]] virtual ::morph::async::Completion<std::vector<LogEntry>>
askEntries(
259 std::string entityKey)
const {
262 settleable.second.resolve(
entries(entityKey));
264 settleable.second.reject(std::current_exception());
266 return std::move(settleable.first);
278 settleable.second.resolve(
true);
280 settleable.second.reject(std::current_exception());
282 return std::move(settleable.first);
294[[nodiscard]]
inline std::vector<LogEntry> entriesFor(
const std::vector<LogEntry>& all, std::string_view entityKey) {
295 if (entityKey.empty()) {
298 std::vector<LogEntry> out;
299 for (
const auto& entry : all) {
300 if (entry.entityKey == entityKey) {
301 out.push_back(entry);
331 : _owned{
owner, std::make_shared<State>()} {}
337 _owned.apply(
"InMemoryActionLog::append", [entry = std::move(entry)](State& state)
mutable {
341 entry.
seq = ++state.nextSeq;
342 state.entries.push_back(std::move(entry));
352 [[nodiscard]] std::vector<LogEntry>
entries(std::string_view entityKey = {})
const override {
353 return detail::entriesFor(_owned.read(
"InMemoryActionLog::entries").entries, entityKey);
366 std::string entityKey)
const override {
367 return _owned.ask<std::vector<LogEntry>>(
368 "InMemoryActionLog::entries", replyExec,
369 [entityKey = std::move(entityKey)](State& state) {
return detail::entriesFor(state.entries, entityKey); });
375 std::vector<LogEntry> entries;
377 std::unordered_set<std::string> seenIdempotencyKeys;
380 ::morph::exec::detail::OwnedState<State> _owned;
388inline std::pair<std::mutex, std::shared_ptr<IActionLog>>& defaultActionLogState() {
389 static std::pair<std::mutex, std::shared_ptr<IActionLog>> state;
410inline void setActionLog(std::shared_ptr<IActionLog> log) {
411 auto& [mtx,
slot] = detail::defaultActionLogState();
412 std::scoped_lock
const lock{mtx};
413 slot = std::move(log);
418[[nodiscard]]
inline std::shared_ptr<IActionLog> defaultActionLog() {
419 auto& [mtx,
slot] = detail::defaultActionLogState();
420 std::scoped_lock
const lock{mtx};
441 explicit ScopedActionLog(std::shared_ptr<IActionLog> log) : _previous{defaultActionLog()} {
442 setActionLog(std::move(log));
454 std::shared_ptr<IActionLog> _previous;
Move-only handle representing the eventual result of an asynchronous operation.
static std::pair< Completion< T >, Promise > makeSettleable(::morph::exec::IExecutor *execPtr)
Constructs a Completion<T>/Promise<T> pair sharing one settleable state.
void flush() override
No-op — there is no external backend to flush to. Callable anywhere.
void append(LogEntry entry) override
Appends entry, assigning a monotonically increasing seq, on the owner.
::morph::exec::IExecutor * owner() const noexcept override
The executor given at construction.
std::vector< LogEntry > entries(std::string_view entityKey={}) const override
Returns a snapshot of matching entries in append order. On the owner.
InMemoryActionLog(::morph::exec::IExecutor &owner)
Constructs an empty log belonging to owner.
::morph::async::Completion< std::vector< LogEntry > > askEntries(::morph::exec::IExecutor &replyExec, std::string entityKey) const override
Reads the entries on the owner, delivered on replyExec.
RAII helper that installs a default action log for its lifetime and restores the previous one on dest...
ScopedActionLog(std::shared_ptr< IActionLog > log)
Installs log as the default, saving whatever was there before.
~ScopedActionLog()
Restores the saved default.
OwnedState: a component's state, and the one executor it is touched on.
Abstract executor interface.
Interface for durable storage of executed-action entries.
virtual void flush()=0
Pushes any buffered entries to the durable backend. No-op for sinks with nothing to buffer (e....
::morph::async::Completion< std::vector< LogEntry > > entries(::morph::exec::IExecutor &replyExec, std::string entityKey={}) const
Returns recorded entries in append order, read on the log's owner and delivered on replyExec.
virtual ::morph::async::Completion< bool > askFlush(::morph::exec::IExecutor &replyExec)
What flush(replyExec) asks. The default flushes where it is called, for an implementation with no own...
::morph::async::Completion< bool > flush(::morph::exec::IExecutor &replyExec)
Pushes buffered entries to the durable backend on the log's owner; the answer is delivered on replyEx...
virtual ::morph::exec::IExecutor * owner() const noexcept
The executor this log's state belongs to, or null for an implementation that has none.
virtual std::vector< LogEntry > entries(std::string_view entityKey={}) const =0
Returns recorded entries in append order.
virtual void append(LogEntry entry)=0
Appends entry. Implementations assign entry.seq.
virtual ::morph::async::Completion< std::vector< LogEntry > > askEntries(::morph::exec::IExecutor &replyExec, std::string entityKey) const
What entries(replyExec, entityKey) asks. The default reads entries(entityKey) where it is called,...
One recorded execution of an action against a model instance.
std::string principal
Auth principal from morph::session::current(), if any. Empty if unset.
std::string entityKey
Stable identity of the model instance (e.g. an account id), stamped from the value passed to attachAc...
std::string payload
JSON-encoded request (ActionTraits<A>::toJson).
std::string modelType
String type-id of the model the action ran against (ModelTraits<M>::typeId()).
std::string causalParentId
Identity of the "trigger" entry that caused this entry to be recorded, or empty (the sentinel) if thi...
std::string idempotencyKey
Optional dedup token for outbox-relayed entries. Empty by default; ordinary auto-appended entries (fr...
std::string schema
Structural fingerprint of the action payload's shape at the moment payload was encoded (morph::model:...
std::string error
std::exception::what() from the exception that rejected the action. Empty unless outcome == Outcome::...
std::string actionType
String type-id of the executed action (ActionTraits<A>::typeId()).
Outcome outcome
Whether this execution succeeded or was rejected/threw. Defaults to Succeeded so a pre-existing on-di...
std::uint32_t v
Line-format version this entry was written at.
int64_t timestampMs
Wall-clock time of execution, milliseconds since the Unix epoch.
std::string result
JSON-encoded result (ActionTraits<A>::resultToJson), captured after successful execution....
uint64_t seq
Monotonic order assigned by the sink on append(). Callers pass 0.
Thrown by toJson/fromJson when LogEntry (de)serialisation fails.
SlotBinding< std::decay_t< F > > slot(SlotId slotId, F &&read)
A slot with an id.