Packages
phoenix_session_process
1.0.0-rc.0
Session isolation and state management for Phoenix applications
Current section
Files
Jump to
Current section
Files
lib/phoenix/session_process/action.ex
defmodule Phoenix.SessionProcess.Action do
@moduledoc """
Internal action structure for fast pattern matching.
Actions are normalized to this struct internally for consistent handling
and optimized pattern matching in the BEAM VM.
## Fields
- `type` - Action type identifier (must be a binary string)
- `payload` - Action data (any term)
- `meta` - Action metadata:
- `async: true` - Route to handle_async/3 instead of handle_action/2
- `reducers: [:user, :cart]` - Only call these named reducers
- `reducer_prefix: "user"` - Only call reducers with this prefix
- Custom metadata for middleware/logging
## Usage
Actions are dispatched using the `dispatch/4` function:
# Basic action
dispatch(session_id, "user.reload")
# With payload
dispatch(session_id, "user.set", %{id: 123})
# With meta (async) - note: meta is a keyword list
dispatch(session_id, "user.fetch", %{page: 1}, async: true)
# Target specific reducers
dispatch(session_id, "reload", nil, reducers: [:user, :cart])
Reducers pattern match on the normalized Action struct for fast, consistent matching.
"""
@type t :: %__MODULE__{
type: String.t(),
payload: term(),
meta: map()
}
@enforce_keys [:type]
defstruct [:type, payload: nil, meta: %{}]
@doc """
Create an Action struct from type, payload, and meta.
Type must be a binary string. Payload and meta can be any term.
## Examples
iex> Action.new("user.reload", nil, %{})
%Action{type: "user.reload", payload: nil, meta: %{}}
iex> Action.new("user.set", %{id: 123}, %{})
%Action{type: "user.set", payload: %{id: 123}, meta: %{}}
iex> Action.new("fetch", %{page: 1}, %{async: true})
%Action{type: "fetch", payload: %{page: 1}, meta: %{async: true}}
"""
@spec new(String.t(), term(), map()) :: t()
def new(type, payload \\ nil, meta \\ %{})
def new(type, payload, meta) when is_binary(type) and is_map(meta) do
%__MODULE__{
type: type,
payload: payload,
meta: meta
}
end
def new(type, _payload, _meta) when not is_binary(type) do
raise ArgumentError, """
Action type must be a binary string, got: #{inspect(type)}
Examples:
Action.new("user.reload", nil, %{}) # Correct
Action.new(:reload, nil, %{}) # Wrong - atom
Action.new(123, nil, %{}) # Wrong - integer
"""
end
def new(_type, _payload, meta) when not is_map(meta) do
raise ArgumentError, """
Action meta must be a map, got: #{inspect(meta)}
Examples:
Action.new("reload", nil, %{}) # Correct
Action.new("reload", nil, async: true) # Wrong - keyword list
"""
end
@doc """
Check if action should be routed to handle_async/3.
Returns true if meta contains `async: true`.
"""
@spec async?(t()) :: boolean()
def async?(%__MODULE__{meta: %{async: true}}), do: true
def async?(_), do: false
@doc """
Get list of reducer names this action should be routed to.
Returns nil if action should be routed to all reducers.
Returns list of reducer names if meta contains `reducers: [...]`.
"""
@spec target_reducers(t()) :: [atom()] | nil
def target_reducers(%__MODULE__{meta: %{reducers: reducers}}) when is_list(reducers) do
reducers
end
def target_reducers(_), do: nil
@doc """
Get reducer prefix filter if specified.
Returns nil if no prefix filter.
Returns string prefix if meta contains `reducer_prefix: "user"`.
"""
@spec reducer_prefix(t()) :: String.t() | nil
def reducer_prefix(%__MODULE__{meta: %{reducer_prefix: prefix}}) when is_binary(prefix) do
prefix
end
def reducer_prefix(_), do: nil
end