# `Ash.Signals`
[🔗](https://github.com/ash-project/ash/blob/v3.36.1/lib/ash/signals.ex#L5)

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:

```elixir
defmodule MyApp.Shop.Signals do
  use Ash.Signals

  signals do
    signal :order_placed do
      argument :order_id, :uuid, allow_nil?: false
    end
  end
end
```

See the [signals guide](/documentation/topics/resources/signals.md) for how to emit and listen
to signals, and the [Signals DSL documentation](dsl-ash-signals.html) for every option.

### Options

* `:extensions` (list of module that adopts `Spark.Dsl.Extension`) - A list of DSL extensions to add to the `Spark.Dsl`

* `:otp_app` (`t:atom/0`) - The otp_app to use for any application configurable options

* `:fragments` (list of `t:module/0`) - Fragments to include in the `Spark.Dsl`. See the fragments guide for more.

# `signal_path`

```elixir
@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.

# `t`

```elixir
@type t() :: module()
```

# `dump`

```elixir
@spec dump(t(), atom(), [struct()]) :: {:ok, [map()]} | {:error, term()}
```

Dumps signal structs to maps that can be stored, for example in a job's arguments.

Load them again with `load/3`.

# `dump_signal_path`

```elixir
@spec dump_signal_path(signal_path()) :: [map()]
```

Dumps a `t: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`.

# `emit`

```elixir
@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:

```elixir
create :place do
  emit MyApp.Shop.Signals, :order_placed, dynamic?: true
  change MyApp.Shop.Changes.EmitOrderPlaced
end
```

How 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's `authorize?`.
* `: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 implementing `Ash.Scope.ToOpts`. Any of the above that are also given
  explicitly take precedence over the scope.

# `emit_many`

```elixir
@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
`c:Ash.Resource.Change.after_batch/3`:

```elixir
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
end
```

Every 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`.

# `listener_queries`

```elixir
@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.

# `load`

```elixir
@spec load(t(), atom(), [map()]) :: {:ok, [struct()]} | {:error, term()}
```

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.

# `load_signal_path`

```elixir
@spec load_signal_path([map()]) :: signal_path()
```

Loads a `t: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.

# `run_listener`

```elixir
@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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
