# `Spark.Dsl.Transformer`
[🔗](https://github.com/ash-project/spark/blob/v2.7.0/lib/spark/dsl/transformer.ex#L5)

A transformer manipulates and/or validates the entire DSL state of a module at compile time.

Transformers run during compilation and can read, modify, and persist DSL state. They are the
primary mechanism for setting defaults, building entities programmatically, caching computed
values, and reshaping configuration before the module is finalized.

## Usage

    defmodule MyApp.MyExtension.Transformers.SetDefaults do
      use Spark.Dsl.Transformer

      def transform(dsl_state) do
        # Read entities and options
        actions = Spark.Dsl.Transformer.get_entities(dsl_state, [:actions])

        # Modify DSL state and return it
        dsl_state = Spark.Dsl.Transformer.persist(dsl_state, :action_count, length(actions))
        {:ok, dsl_state}
      end
    end

## Callbacks

- `transform/1` - Receives the DSL state map. Return `{:ok, dsl_state}` to continue with
  modifications, `:ok` to continue without modifications, `{:error, term}` to halt with an
  error, `{:warn, dsl_state, warnings}` to continue with warnings, or `:halt` to silently
  stop processing further transformers.

- `before?/1` - Return `true` if this transformer must run before the given transformer module.

- `after?/1` - Return `true` if this transformer must run after the given transformer module.

## Reading vs. Modifying State

Use the helper functions in this module rather than manipulating the DSL state map directly.
For reading: `get_entities/2`, `get_option/3`, `fetch_option/3`, `get_persisted/2`.
For writing: `add_entity/3`, `replace_entity/3`, `remove_entity/3`, `set_option/4`, `persist/3`.

## Transformers vs. Persisters vs. Verifiers

**Persisters** also use this behaviour (`use Spark.Dsl.Transformer`) but are declared under
`persisters:` in the extension rather than `transformers:`. They always run after all
transformers have completed, regardless of any `before?`/`after?` declarations targeting
transformer modules. By convention, persisters should only write to the persisted data map
via `persist/3` and should not mutate sections or entities. They support `before?`/`after?`
ordering relative to other persisters. See `Spark.Dsl.Extension` for more.

**Verifiers** (`Spark.Dsl.Verifier`) are for validation only — they run after compilation,
cannot modify DSL state, and do not create compile-time dependencies between modules.

# `warning`

```elixir
@type warning() :: String.t() | {String.t(), :erl_anno.anno()}
```

# `after?`

```elixir
@callback after?(module()) :: boolean()
```

# `after_compile?`

```elixir
@callback after_compile?() :: boolean()
```

# `before?`

```elixir
@callback before?(module()) :: boolean()
```

# `transform`

```elixir
@callback transform(map()) ::
  :ok
  | {:ok, map()}
  | {:error, term()}
  | {:warn, map(), warning() | [warning()]}
  | :halt
```

# `add_entity`

Adds an entity to the DSL state at the given section path.

By default, entities are prepended. Pass `type: :append` to append instead.

# `async_compile`

Runs the function in an async compiler.

Use this for compiling new modules and having them compiled
efficiently asynchronously.

# `build_entity`

Programmatically builds an entity struct defined in the given extension.

`path` is the section path (e.g. `[:actions]`), `name` is the entity name (e.g. `:read`),
and `opts` is a keyword list of options matching the entity's schema. The entity's schema
validations and transforms are applied.

Commonly used to add entities to DSL state in a transformer:

    {:ok, action} = Transformer.build_entity(Ash.Resource.Dsl, [:actions], :read, name: :read)
    dsl_state = Transformer.add_entity(dsl_state, [:actions], action)

# `build_entity!`

Same as `build_entity/4` but raises on error.

# `eval`

Add a quoted expression to be evaluated in the DSL module's context.

Use this *extremely sparingly*. It should almost never be necessary, unless building certain
extensions that *require* the module in question to define a given function.

What you likely want is either one of the DSL introspection functions, like `Spark.Dsl.Extension.get_entities/2`
or `Spark.Dsl.Extension.get_opt/5)`. If you simply want to store a custom value that can be retrieved easily, or
cache some precomputed information onto the resource, use `persist/3`.

Provide the dsl state, bindings that should be unquote-able, and the quoted block
to evaluate in the module. For example, if we wanted to support a `resource.primary_key()` function
that would return the primary key (this is unnecessary, just an example), we might do this:

```elixir
fields = the_primary_key_fields

dsl_state =
  Spark.Dsl.Transformer.eval(
    dsl_state,
    [fields: fields],
    quote do
      def primary_key() do
        unquote(fields)
      end
    end
  )
```

# `fetch_option`

Fetches a DSL option, returning `{:ok, value}` or `:error`.

# `fetch_persisted`

Fetches a persisted value, returning `{:ok, value}` or `:error`.

# `get_entities`

Returns all entities at the given section path.

# `get_opt_anno`

```elixir
@spec get_opt_anno(map(), [atom()], atom()) :: :erl_anno.anno() | nil
```

Returns the source location annotation for a specific option, useful for error reporting.

# `get_option`

Gets a DSL option value at the given section path, returning `default` if not set.

# `get_persisted`

Retrieves a value that was previously stored with `persist/3`.

Returns `default` if the key has not been persisted.

# `get_section_anno`

```elixir
@spec get_section_anno(map(), [atom()]) :: :erl_anno.anno() | nil
```

Returns the source location annotation for a section, useful for error reporting.

# `persist`

Saves a value into the dsl config with the given key.

This can be used to precompute some information and cache it onto the resource,
or simply store a computed value. It can later be retrieved with `Spark.Dsl.Extension.get_persisted/3`.

# `remove_entity`

Removes entities at the given path that match the filter function.

The function receives each entity and should return `true` for entities to remove.

# `replace_entity`

Replaces an entity at the given path with `replacement`.

By default, matches on struct type and `__identifier__`. Pass a custom `matcher`
function to control which entity gets replaced.

# `set_option`

Sets a DSL option value at the given section path.

---

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