Typed signals that resources emit, and other resources listen to with their own actions.
A signal module declares signals, and is the contract between the resources that emit them and the resources that listen to them:
defmodule MyApp.Shop.Signals do
use Ash.Signals
signals do
signal :order_placed do
argument :order_id, :uuid, allow_nil?: false
end
end
endSee the signals guide for how to emit and listen to signals, and the Signals DSL documentation for every option.
Options
:extensions(list of module that adoptsSpark.Dsl.Extension) - A list of DSL extensions to add to theSpark.Dsl:otp_app(atom/0) - The otp_app to use for any application configurable options:fragments(list ofmodule/0) - Fragments to include in theSpark.Dsl. See the fragments guide for more.
Summary
Types
The signals that led to a listener running, starting with the one it received, followed by the
signals that led to it. Listeners have it in the signal_path key of their context.
Functions
Dumps signal structs to maps that can be stored, for example in a job's arguments.
Dumps a signal_path/0 to maps of strings that can be stored, for example in a job's
arguments.
Emits a signal, delivering it to its listeners.
Emits a signal once for each payload, delivering them to its listeners together.
Returns the queries that select the records an update or destroy listener runs on.
Loads signal structs from maps produced by dump/3.
Loads a signal_path/0 from maps produced by dump_signal_path/1.
Runs a listener for the given signals, which is what the default delivery does for each listener.
Types
@type signal_path() :: [ %{ signal_module: t(), signal: atom(), resource: Ash.Resource.t() | nil, action: atom() | nil } ]
The signals that led to a listener running, starting with the one it received, followed by the
signals that led to it. Listeners have it in the signal_path key of their context.
Each entry has the signal's signal_module and signal name, and the resource and action
that emitted it, which are nil for signals delivered by Ash.Signals.Test.react_to_signal/4.
It doesn't contain the signals themselves, because a listener can receive many signals at once.
@type t() :: module()
Functions
Dumps signal structs to maps that can be stored, for example in a job's arguments.
Load them again with load/3.
@spec dump_signal_path(signal_path()) :: [map()]
Dumps a signal_path/0 to maps of strings that can be stored, for example in a job's
arguments.
Load it again with load_signal_path/1.
@spec emit( Ash.Changeset.t() | Ash.ActionInput.t(), t(), atom(), map() | Keyword.t(), Keyword.t() ) :: :ok | {:error, term()}
Emits a signal, delivering it to its listeners.
Takes the changeset of a create, update or destroy action, or the input of a generic action. Must be called during the phase declared by the signal, by an action that declares that it emits the signal dynamically:
create :place do
emit MyApp.Shop.Signals, :order_placed, dynamic?: true
change MyApp.Shop.Changes.EmitOrderPlaced
endHow each listener receives the signal depends on its delivery. By default, listeners run
immediately. See Ash.Signals.Delivery.
Every signal has a signal_id, a UUIDv7 generated when it is emitted. Include signal_id in
the payload to set it yourself, for example to emit the same signal again.
Options
:actor- The actor to run the listeners as. Defaults to the emitting action's actor.:tenant- Defaults to the emitting action's tenant.:authorize?- Defaults to the emitting action'sauthorize?.:tracer- Defaults to the emitting action's tracer.:context- Context to pass to the listeners, merged into the emitting action's shared context.:scope- A value implementingAsh.Scope.ToOpts. Any of the above that are also given explicitly take precedence over the scope.
@spec emit_many( Ash.Changeset.t() | Ash.ActionInput.t(), t(), atom(), [map() | Keyword.t()], Keyword.t() ) :: :ok | {:error, term()}
Emits a signal once for each payload, delivering them to its listeners together.
Use this from the batch callbacks of a change, so that a bulk action emits its signals
together. Pass any one of the batch's changesets as the subject, for example in
Ash.Resource.Change.after_batch/3:
def after_batch([{changeset, _} | _] = changesets_and_posts, _opts, _context) do
payloads = Enum.map(changesets_and_posts, fn {_, post} -> %{post_id: post.id} end)
case Ash.Signals.emit_many(changeset, MyApp.Blog.Signals, :post_created, payloads) do
:ok -> Enum.map(changesets_and_posts, fn {_, post} -> {:ok, post} end)
{:error, error} -> [{:error, error}]
end
endEvery payload is cast before the signals are delivered, so an invalid payload emits nothing. With the default delivery, each listener then handles every signal before the next listener runs.
Takes the same options as emit/5.
@spec listener_queries(Ash.Resource.SignalListener.t(), [struct()], Keyword.t()) :: [ {Ash.Query.t(), map(), [struct()]} ]
Returns the queries that select the records an update or destroy listener runs on.
Signals are grouped by the arguments they give the listener's read action and the inputs they
give its action. Returns a {query, inputs, signals} tuple for each group.
Loads signal structs from maps produced by dump/3.
Fields the signal no longer has are ignored, and a required field that is missing is an error, so maps dumped by an older version of the signal load if they are still valid.
@spec load_signal_path([map()]) :: signal_path()
Loads a signal_path/0 from maps produced by dump_signal_path/1.
This creates the atoms the path refers to, so only load what dump_signal_path/1 produced,
from storage you trust.
@spec run_listener(Ash.Resource.SignalListener.t(), [struct()], Keyword.t()) :: :ok | {:error, term()}
Runs a listener for the given signals, which is what the default delivery does for each listener.
Deliveries that run listeners later, for example in a job, call this to run them the same way.
Takes the options of the action the listener runs: :actor, :tenant, :authorize?,
:tracer and :context, which should contain the signal_path of the delivery context.