# `Plushie.State`
[🔗](https://github.com/plushie-ui/plushie-elixir/blob/v0.6.0/lib/plushie/state.ex#L1)

Path-based state management with revision tracking and transactions.

A lightweight wrapper around a plain map that tracks a monotonically
increasing revision number on every mutation. Useful for detecting
changes and implementing optimistic concurrency.

## Transactions

`begin_transaction/1` captures a snapshot of the current data and revision.
Subsequent mutations increment the revision as usual. `commit_transaction/1`
finalises the transaction (bumping the revision once from the pre-transaction
value). `rollback_transaction/1` restores the snapshot exactly.

## Example

    state = Plushie.State.new(%{count: 0})
    state = Plushie.State.put(state, [:count], 5)
    Plushie.State.get(state, [:count])
    #=> 5
    Plushie.State.revision(state)
    #=> 1

# `t`

```elixir
@type t() :: %Plushie.State{
  data: map(),
  revision: non_neg_integer(),
  transaction: map() | nil
}
```

# `begin_transaction`

```elixir
@spec begin_transaction(state :: t()) :: t() | {:error, :transaction_already_active}
```

Begins a transaction by capturing the current data and revision.

Returns `{:error, :transaction_already_active}` if a transaction is
already in progress.

# `commit_transaction`

```elixir
@spec commit_transaction(state :: t()) :: t()
```

Commits the active transaction, setting the revision to one past the
pre-transaction value.

# `delete`

```elixir
@spec delete(state :: t(), path :: list()) :: t()
```

Removes the key at the end of `path`, incrementing the revision.

Uses `Kernel.pop_in/2` to remove the key. If the path doesn't exist,
the revision is still incremented for consistency.

# `get`

```elixir
@spec get(state :: t(), path :: list()) :: term()
```

Reads the value at `path` in the state data.

An empty path returns the entire data map. Path elements are keys
passed to `Kernel.get_in/2`.

# `new`

```elixir
@spec new(data :: map()) :: t()
```

Creates a new state container wrapping `data`.

The initial revision is 0.

# `put`

```elixir
@spec put(state :: t(), path :: list(), value :: term()) :: t()
```

Sets the value at `path` to `value`, incrementing the revision.

# `revision`

```elixir
@spec revision(state :: t()) :: non_neg_integer()
```

Returns the current revision number.

# `rollback_transaction`

```elixir
@spec rollback_transaction(state :: t()) :: t()
```

Rolls back the active transaction, restoring the data and revision
to their pre-transaction values.

# `update`

```elixir
@spec update(state :: t(), path :: list(), fun :: (term() -&gt; term())) :: t()
```

Applies `fun` to the value at `path`, incrementing the revision.

`fun` receives the current value and must return the new value.

---

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