# `Chronicle.Transactions.UnitOfWork`
[🔗](https://github.com/Cratis/Chronicle.Elixir/blob/main/lib/chronicle/transactions/unit_of_work.ex#L4)

Buffers event appends and commits them as a single Chronicle append-many operation.

Use `begin/1` to create a unit of work and make it current for the calling
process. Calls to `Chronicle.append/3`, `Chronicle.append_many/3`, or a
transactional event sequence are buffered until `commit/1` is called.

# `commit_fun`

```elixir
@type commit_fun() :: (map() -&gt; {:ok, map()} | {:error, term()})
```

# `on_completed`

```elixir
@type on_completed() :: (t() -&gt; any())
```

# `t`

```elixir
@type t() :: pid()
```

# `add_decision_read`

```elixir
@spec add_decision_read(t(), Chronicle.ReadModels.DecisionRead.t()) :: :ok
```

Enrolls a `Chronicle.ReadModels.DecisionRead` as a decision dependency of the unit of work.

Reads of the same event source merge: the commit guards against anything appended since the
**earliest** of their boundaries, across the **union** of their event types. Raises
`ArgumentError` when the same event source already carries an explicit `:concurrency_scope`
from a buffered event — the two guards cannot both apply, and silently picking one would
hide that the caller asked for both.

# `add_event`

```elixir
@spec add_event(
  t(),
  String.t(),
  Chronicle.EventSequences.EventForEventSourceId.t(),
  keyword()
) :: :ok
```

Adds an event to the unit of work.

# `begin`

```elixir
@spec begin(keyword()) :: t()
```

Begins a new unit of work and makes it current for the calling process.

The caller owns the unit of work: complete it with `commit/1` or `rollback/1`, and stop its
process with `Agent.stop/1` when done. `decide/2` and `with_unit_of_work/2` do both for you.

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `commit`

```elixir
@spec commit(t()) :: :ok | {:error, term()}
```

Commits the buffered events.

When the commit itself fails (`{:error, reason}`, for example when the kernel is unreachable),
the unit of work stays open and current: retry `commit/1`, or call `rollback/1`.

# `correlation_id`

```elixir
@spec correlation_id(t()) :: Chronicle.Correlation.CorrelationId.t()
```

Returns the correlation id for a unit of work.

# `current`

```elixir
@spec current() :: t()
```

Returns the current unit of work for the calling process.

# `decide`

```elixir
@spec decide((t() -&gt; result), keyword()) :: {:ok, result} | {:error, term()}
when result: any()
```

Runs a function inside a unit of work and commits it, returning `{:ok, result}` on success.

Unlike `with_unit_of_work/2`, a conflict never raises: when the function enrolled at least one
decision read (`Chronicle.ReadModels.DecisionReads.get/3`) and the commit is rejected because
one of them is no longer current, this returns `{:error, %Chronicle.ReadModels.DecisionConflict{}}`
instead. Any other commit failure is returned as `{:error, reason}`, after rolling the unit of
work back so later appends from the process are not buffered into it.

Returning `{:error, reason}` from `fun` rejects the decision: the unit of work is rolled back,
nothing is written, and `{:error, reason}` is returned as-is. The unit of work is rolled back,
and the error re-raised, if `fun` itself raises. When `decide/2` returns, its unit of work is
completed and its process stopped, so don't keep the unit of work beyond `fun`.

    Chronicle.decide(fn _unit_of_work ->
      {:ok, read} = Chronicle.ReadModels.DecisionReads.get(MyApp.ReadModels.Seat, "seat-5")
      if read.instance == nil, do: Chronicle.EventSequences.EventLog.append("seat-5", %SeatBooked{})
    end)

# `get_decision_reads`

```elixir
@spec get_decision_reads(t()) :: %{
  required(String.t()) =&gt; Chronicle.Events.ConcurrencyScope.t()
}
```

Returns the merged concurrency scopes enrolled via `add_decision_read/2`, keyed by event
source id.

# `get_events`

```elixir
@spec get_events(t()) :: [Chronicle.EventSequences.EventForEventSourceId.t()]
```

Returns the buffered events in insertion order.

# `has_current?`

```elixir
@spec has_current?() :: boolean()
```

Returns whether the calling process currently has an active unit of work.

# `has_decision_reads?`

```elixir
@spec has_decision_reads?(t()) :: boolean()
```

Returns whether any decision read has been enrolled via `add_decision_read/2`.

# `is_completed?`

```elixir
@spec is_completed?(t()) :: boolean()
```

Returns whether the unit of work has been committed or rolled back.

# `is_success?`

```elixir
@spec is_success?(t()) :: boolean()
```

Returns whether the unit of work completed successfully.

# `last_committed_sequence_number`

```elixir
@spec last_committed_sequence_number(t()) :: non_neg_integer() | nil
```

Returns the last committed sequence number if available.

# `on_completed`

```elixir
@spec on_completed(t(), on_completed()) :: :ok
```

Registers a callback that runs when the unit of work is committed or rolled back.

# `rollback`

```elixir
@spec rollback(t()) :: :ok
```

Rolls back the unit of work and clears the buffered events.

# `set_current`

```elixir
@spec set_current(t()) :: t()
```

Sets an existing unit of work as current for the calling process.

# `try_get_for`

```elixir
@spec try_get_for(Chronicle.Correlation.CorrelationId.t() | String.t()) ::
  {:ok, t()} | :error
```

Tries to get a unit of work by correlation id.

# `with_unit_of_work`

```elixir
@spec with_unit_of_work((t() -&gt; result), keyword()) :: result when result: any()
```

Runs a function inside a unit of work and commits it if the function succeeds.
Rolls the unit of work back if the function raises. The function's return value is returned
as-is and committed whatever it is. When this returns, the unit of work is completed and its
process stopped.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
