Current section
Files
Jump to
Current section
Files
lib/adk/session/state.ex
defmodule ADK.Session.State do
@moduledoc """
Utilities for working with prefixed session state keys.
State keys use prefixes to control their scope:
- `app:` — shared across all users and sessions for an app
- `user:` — shared across all sessions for a specific user
- `temp:` — temporary, discarded after each invocation
- (no prefix) — session-local state
"""
@app_prefix "app:"
@user_prefix "user:"
@temp_prefix "temp:"
def app_prefix, do: @app_prefix
def user_prefix, do: @user_prefix
def temp_prefix, do: @temp_prefix
@doc """
Returns the scope of a state key.
## Examples
iex> ADK.Session.State.scope("app:model")
:app
iex> ADK.Session.State.scope("user:pref")
:user
iex> ADK.Session.State.scope("temp:scratch")
:temp
iex> ADK.Session.State.scope("counter")
:session
"""
@spec scope(String.t()) :: :app | :user | :temp | :session
def scope(@app_prefix <> _), do: :app
def scope(@user_prefix <> _), do: :user
def scope(@temp_prefix <> _), do: :temp
def scope(_), do: :session
@doc """
Splits a state delta map into `{app_delta, user_delta, session_delta}`.
Strips prefixes from app/user keys. Discards temp keys entirely.
Session-local keys (no prefix) are kept as-is.
"""
@spec extract_deltas(map()) :: {map(), map(), map()}
def extract_deltas(delta) when is_map(delta) do
Enum.reduce(delta, {%{}, %{}, %{}}, fn {key, value}, {app, user, session} ->
case scope(key) do
:app ->
{Map.put(app, strip_prefix(key, @app_prefix), value), user, session}
:user ->
{app, Map.put(user, strip_prefix(key, @user_prefix), value), session}
:temp ->
{app, user, session}
:session ->
{app, user, Map.put(session, key, value)}
end
end)
end
@doc """
Merges app, user, and session state maps back into a single map with prefixes.
"""
@spec merge_states(map(), map(), map()) :: map()
def merge_states(app_state, user_state, session_state) do
merged = %{}
merged =
Enum.reduce(app_state, merged, fn {k, v}, acc ->
Map.put(acc, @app_prefix <> k, v)
end)
merged =
Enum.reduce(user_state, merged, fn {k, v}, acc ->
Map.put(acc, @user_prefix <> k, v)
end)
Enum.reduce(session_state, merged, fn {k, v}, acc ->
Map.put(acc, k, v)
end)
end
@doc "Removes all keys with the temp prefix from a map."
@spec strip_temp(map()) :: map()
def strip_temp(state) do
Map.reject(state, fn {key, _} -> scope(key) == :temp end)
end
@doc "Removes all temp-prefixed keys from a delta map."
@spec trim_temp_delta(map()) :: map()
def trim_temp_delta(delta) do
Map.reject(delta, fn {key, _} -> scope(key) == :temp end)
end
@doc "Gets a value from state by key."
@spec get(map(), String.t()) :: any()
def get(state, key), do: Map.get(state, key)
@doc "Sets a value in state by key."
@spec put(map(), String.t(), any()) :: map()
def put(state, key, value), do: Map.put(state, key, value)
defp strip_prefix(key, prefix) do
String.replace_prefix(key, prefix, "")
end
end