# `Cinder.BulkActionExecutor`
[🔗](https://github.com/sevenseacat/cinder/blob/v0.16.0/lib/cinder/bulk_action_executor.ex#L1)

Executes bulk actions on selected records.

This module is used internally by `Cinder.LiveComponent` to execute bulk actions
defined in `bulk_action` slots. It handles both Ash action atoms and function
captures.

## Atom Actions

When given an atom, introspects the resource to determine if it's an update or
destroy action, then calls `Ash.bulk_update/4` or `Ash.bulk_destroy/4`:

    BulkActionExecutor.execute(:archive,
      resource: MyApp.User,
      ids: ["1", "2"],
      actor: current_user
    )

## Function Actions

When given a function, calls it with `(query, opts)` matching the signature of
Ash code interface functions. The query is pre-filtered to the selected IDs:

    BulkActionExecutor.execute(&MyApp.Users.archive/2,
      resource: MyApp.User,
      ids: ["1", "2"],
      actor: current_user
    )

## Options Handling

- **Atom actions**: `action_opts` are merged directly into Ash options
- **Function actions**: `action_opts` are wrapped in `bulk_options: [...]` for
  code interface compatibility

# `action`

```elixir
@type action() :: atom() | (Ash.Query.t(), keyword() -&gt; any())
```

# `opts`

```elixir
@type opts() :: [
  resource: Ash.Resource.t(),
  ids: [String.t()],
  id_field: atom(),
  actor: any(),
  tenant: any(),
  scope: any(),
  action_opts: keyword()
]
```

# `build_query`

```elixir
@spec build_query(Ash.Resource.t(), [String.t()], atom()) :: Ash.Query.t()
```

Builds an Ash.Query filtered to the given IDs.

# `execute`

```elixir
@spec execute(action(), opts()) :: {:ok, any()} | {:error, any()}
```

Executes a bulk action on the given IDs.

## Options

- `:resource` - The Ash resource (required)
- `:ids` - List of record IDs to act on (required)
- `:id_field` - The field to filter on (default: `:id`)
- `:actor` - Actor for authorization
- `:tenant` - Tenant for multi-tenancy
- `:scope` - Ash scope; when present, its actor/tenant are used unless
  overridden by explicit `:actor`/`:tenant`, and its tracer/context/
  authorize? options are merged into the Ash call
- `:action_opts` - Additional options for the action (e.g., `[return_records?: true]`)

## Action Types

- **Atom**: Introspects the resource to determine the action type. Calls
  `Ash.bulk_update/4` for update actions or `Ash.bulk_destroy/4` for destroy
  actions. Action opts are merged directly into the Ash options.
- **Function/2**: Calls the function with `(query, opts)` where query is
  filtered to the selected IDs. Action opts are wrapped in `bulk_options: [...]`
  for code interface compatibility.

## Examples

    # Atom action - uses Ash.bulk_update
    execute(:archive, resource: MyApp.User, ids: ["1", "2"], actor: current_user)

    # With action options
    execute(:archive, resource: MyApp.User, ids: ["1", "2"], action_opts: [return_records?: true])

    # Function - receives filtered query
    execute(&MyApp.Users.archive/2, resource: MyApp.User, ids: ["1", "2"])

    # Destroy action
    execute(:destroy, resource: MyApp.User, ids: ["1", "2"])

# `normalize_result`

```elixir
@spec normalize_result(any()) :: {:ok, any()} | {:error, any()}
```

Normalizes a bulk action result to `{:ok, result}` or `{:error, reason}`.

---

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