Statsig.PersistentStorage behaviour (statsig_elixir v0.22.0)

Behaviour and helper APIs for receiving persistent-assignment (sticky value) updates from the Statsig SDK.

Configuring %Statsig.Options{persistent_storage: reference} enables persistent assignment: user_persisted_values supplied via Statsig.ExperimentEvaluationOptions / Statsig.LayerEvaluationOptions are honored, and the SDK notifies this process when sticky values should be saved or deleted.

Reads are NOT bridged through the SDK evaluation path: callers load values from their own store and pass them per call via the evaluation options. get_values_for_user/3 is the caller-side helper for that read — it derives the storage key from the user and calls the implementation's load/2. handle_save/4 receives the sticky values as a decoded map, matching the shape the SDK expects back in user_persisted_values.

Storage keys have the format "<unit id>:<id type>" — see storage_key/2.

Important: once persistent storage is configured, evaluating an experiment or layer with user_persisted_values: nil signals that the caller has no persisted values, and the SDK issues a handle_delete for that config. Always pass the loaded values (or an empty map for a user with nothing stored) when sticky assignment should stay active.

Use start_link/3 to launch the bridge process and pass the returned %Statsig.PersistentStorage.Reference{} into Statsig.Options.

Summary

Types

Opaque state returned from user callbacks.

Sticky values for one config, as a decoded map.

Map of config name to sticky values for one storage key.

Functions

Loads the persisted values for a user, deriving the storage key via storage_key/2 and calling the implementation's load/2.

Starts a bridge process for the provided implementation module.

Stops the bridge process for the given reference.

Derives the storage key for a user and ID type, in the format "<unit id>:<id type>" used by the SDK when issuing save/delete notifications.

Types

state()

@type state() :: term()

Opaque state returned from user callbacks.

sticky_values()

@type sticky_values() :: %{optional(String.t()) => term()}

Sticky values for one config, as a decoded map.

user_persisted_values()

@type user_persisted_values() :: %{optional(String.t()) => sticky_values()}

Map of config name to sticky values for one storage key.

Callbacks

handle_delete(key, config_name, state)

(optional)
@callback handle_delete(key :: String.t(), config_name :: String.t(), state()) ::
  {:ok, state()} | {:error, term()}

handle_save(key, config_name, sticky_values, state)

@callback handle_save(
  key :: String.t(),
  config_name :: String.t(),
  sticky_values :: sticky_values(),
  state()
) :: {:ok, state()} | {:error, term()}

init(init_arg)

@callback init(init_arg :: term()) :: {:ok, state()} | {:error, term()}

load(key, state)

@callback load(key :: String.t(), state()) ::
  {:ok, user_persisted_values() | nil, state()} | {:error, term()}

Functions

get_values_for_user(reference, user, id_type \\ "userID", timeout \\ 5000)

@spec get_values_for_user(
  Statsig.PersistentStorage.Reference.t(),
  Statsig.User.t(),
  String.t(),
  timeout()
) :: {:ok, user_persisted_values() | nil} | {:error, term()}

Loads the persisted values for a user, deriving the storage key via storage_key/2 and calling the implementation's load/2.

This is a synchronous call intended for the caller side of an evaluation: fetch the values here, then pass them as user_persisted_values in the experiment/layer evaluation options.

start_link(module, init_arg \\ nil, opts \\ [])

@spec start_link(module(), term(), Keyword.t()) ::
  {:ok, Statsig.PersistentStorage.Reference.t()} | {:error, term()}

Starts a bridge process for the provided implementation module.

Returns {:ok, %Statsig.PersistentStorage.Reference{}} which can be assigned to %Statsig.Options{persistent_storage: reference}.

stop(reference, reason \\ :normal, timeout \\ 5000)

@spec stop(Statsig.PersistentStorage.Reference.t(), term(), non_neg_integer()) :: :ok

Stops the bridge process for the given reference.

storage_key(user, id_type)

@spec storage_key(Statsig.User.t(), String.t()) :: String.t()

Derives the storage key for a user and ID type, in the format "<unit id>:<id type>" used by the SDK when issuing save/delete notifications.

For "userID" (any casing, or "user_id") the unit ID is the user's user_id; for any other ID type it is looked up in custom_ids. A missing unit ID yields an empty string, mirroring the other SDK bindings.