MishkaCmsCore.Runtime.States.SettingsState (Mishka CMS Core v0.0.1)

Copy Markdown View Source

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.

Summary

Functions

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

Returns a specification to start this module under a supervisor.

Drop a setting from the cache.

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.

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

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

Reload every setting from the database into ETS.

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.

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.

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

Functions

all_global()

@spec all_global() :: %{optional(String.t()) => term()}

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

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

delete(site_id, key)

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

Drop a setting from the cache.

get(site_id, key, default \\ nil)

@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(site_id, key, default \\ nil)

@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(site_id, key, value)

@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()

@spec refresh() :: :ok

Reload every setting from the database into ETS.

refresh_key(site_id, key)

@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(entries)

@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()

@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(opts \\ [])