# `Mob.State`
[🔗](https://github.com/genericjam/mob/blob/master/lib/mob/state.ex#L1)

Persistent key-value store for app state.

Backed by `:dets` — Erlang's disk-based term storage, part of OTP stdlib.
State survives app kills and restarts. Any Elixir term can be stored as a
value; no serialisation step required.

## When to use this vs. Ecto

- **`Mob.State`** — app preferences, UI choices, small per-user settings.
  Think: selected theme, onboarding complete flag, last-opened tab, cached
  user ID. Designed for O(dozens) of keys, not O(thousands) of rows.

- **Your Ecto Repo** — user records, structured data, anything you'd query,
  filter, or paginate. Use migrations and schemas for that.

## Usage

    # Store anything — atoms, maps, lists, nested structures.
    Mob.State.put(:theme, :citrus)
    Mob.State.put(:onboarded, true)
    Mob.State.put(:last_position, %{lat: 43.7, lng: -79.4})

    # Read back on next launch — returns `default` if not yet set.
    Mob.State.get(:theme, :obsidian)   #=> :citrus
    Mob.State.get(:missing_key, 0)     #=> 0

    # Remove a key.
    Mob.State.delete(:last_position)

## Lifecycle

Started automatically by `Mob.App.start/0` — no setup required.
The backing file is stored at `MOB_DATA_DIR/mob_state.dets` on device,
or `priv/repo/mob_state.dets` in local dev (same directory as the SQLite DB).

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `delete`

```elixir
@spec delete(term()) :: :ok
```

Delete a key.

No-op if the key is absent. The deletion is synchronised to disk before
this function returns.

    Mob.State.delete(:theme)
    Mob.State.get(:theme, :obsidian)   #=> :obsidian

# `get`

```elixir
@spec get(term(), term()) :: term()
```

Read a persisted value.

Returns `default` if the key has never been written. `default` is `nil`
unless explicitly provided.

    iex> Mob.State.get(:theme, :obsidian)
    :obsidian

    iex> Mob.State.put(:theme, :citrus)
    iex> Mob.State.get(:theme, :obsidian)
    :citrus

Reads go directly to the underlying `:dets` table without going through
the GenServer, so they never queue behind in-flight writes.

# `match`

```elixir
@spec match(term()) :: [{term(), term()}]
```

Return all `{key, value}` pairs whose key matches `pattern`.

`pattern` is a key-shape with `:_` placeholders for parts that vary —
e.g. `{:peer_profile, :_}` to fetch every peer profile that an app has
stored under `{:peer_profile, <pubkey>}` keys. Useful when an app uses
a tagged-tuple key convention to namespace records and wants to walk
the namespace.

    # All peer profiles
    Mob.State.match({:peer_profile, :_})
    #=> [{{:peer_profile, <<...>>}, %{...}}, ...]

Reads directly from DETS without going through the GenServer call
loop — fine for occasional listings (UI mounts, tests). Don't call it
in a tight loop.

# `put`

```elixir
@spec put(term(), term()) :: :ok
```

Persist a key-value pair to disk.

Any Elixir term is accepted for both key and value. Backed by `:dets`
(Erlang's disk-based term storage) with a `sync` after each write, so the
value is on disk before this function returns and survives an immediate
`SIGKILL` (Android OOM kill, iOS termination under memory pressure).

    Mob.State.put(:theme, :citrus)
    Mob.State.put(:onboarded, true)
    Mob.State.put({:last_seen, :home}, DateTime.utc_now())
    Mob.State.put(:prefs, %{font_size: 16, notifications: true})

Calling `put/2` with an existing key overwrites the previous value.

---

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