# `MishkaCmsCore.Runtime.States.SettingsState`
[🔗](https://github.com/mishka-group/mishka_cms_core/blob/v0.0.1/lib/mishka_cms_core/runtime/states/settings_state.ex#L5)

ETS-backed cache of the CMS settings, loaded once at boot and refreshed on change so reads are
lock-free on every request (mirrors `MishkaCmsCore.Runtime.States.SiteState`). The durable source
of truth is `MishkaCmsCore.Runtime.Setting`; application code should go through the
`MishkaCmsCore.Settings` facade rather than this module directly.

Settings are multitenant (see `MishkaCmsCore.Runtime.Setting`), so each entry is keyed by
`{site_id, key}` — `site_id = nil` is the GLOBAL value, a `site_id` is that site's override. A read
for a site resolves **site value → global value → default**, all from ETS with no GenServer hop.

# `all_global`

```elixir
@spec all_global() :: %{optional(String.t()) =&gt; term()}
```

All GLOBAL settings (site_id nil) as `%{key => value_map}` — used for default seeding.

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `delete`

```elixir
@spec delete(term(), String.t()) :: :ok
```

Drop a setting from the cache.

# `get`

```elixir
@spec get(term(), String.t(), term()) :: term()
```

Lock-free read of a setting's stored map for `site_id` (nil = global). A per-site lookup that misses
falls back to the global value, then to `default`.

# `get_own`

```elixir
@spec get_own(String.t(), String.t(), term()) :: term()
```

Lock-free read of a site's OWN stored map, with no fallback to the global value: `default` when the
site holds none.

# `put`

```elixir
@spec put(term(), String.t(), term()) :: :ok
```

Upsert a setting into the cache directly (test/seed helper; the façade uses `refresh_key/2`).

# `refresh`

```elixir
@spec refresh() :: :ok
```

Reload every setting from the database into ETS.

# `refresh_key`

```elixir
@spec refresh_key(term(), String.t()) :: :ok
```

Reconcile one `{site_id, key}` entry with the database (called by the facade after a durable write).
Re-reading inside the serialized GenServer makes the cache reflect DB-commit order, not caller
arrival order — so concurrent writes to the same key converge on the committed value, and a removed
row drops from the cache.

# `restore`

```elixir
@spec restore([{{String.t() | nil, String.t()}, term()}]) :: :ok
```

Puts the cache back to a `snapshot/0` (test helper). It reads no database, so it works from an
`on_exit`, which a `refresh/0` there does not: that one runs before the test's transaction rolls
back, and loads the test's own writes back into the cache.

# `snapshot`

```elixir
@spec snapshot() :: [{{String.t() | nil, String.t()}, term()}]
```

Everything the cache holds now, for `restore/1` (test helper).

    snapshot = SettingsState.snapshot()
    on_exit(fn -> SettingsState.restore(snapshot) end)

# `start_link`

---

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