# Mutations

use_mutation API reference for gpui-query: dispatching writes from GPUI components, success and error callbacks, and coordinating cache invalidation.

Mutations handle write operations: creating a user, updating a record, deleting a resource. Unlike [Queries](/docs/api/queries), mutations don't run automatically. You trigger them explicitly, and they manage their own loading/success/failure lifecycle.

A mutation does not touch the query cache on its own. If you need to invalidate or refetch queries after a mutation succeeds, do that in an `on_success` callback. This is by design: the library doesn't guess which queries a mutation should affect.

## `use_mutation`

```rust
pub fn use_mutation<V, T, E, C>(cx: &mut Context<C>) -> Entity<MutationResource<V, T, E>>
```

Creates a new `MutationResource` entity and returns it. The entity starts in the `Idle` state with a no-retry policy. Store it on your view struct and read from it during render to show loading spinners, error messages, or success state.

```rust
use gpui_query::{use_mutation, MutationResource};

struct UserForm {
    create_user: Entity<MutationResource<NewUser, User>>,
}

impl UserForm {
    fn new(cx: &mut Context<Self>) -> Self {
        Self {
            create_user: use_mutation(cx),
        }
    }
}
```

The returned entity is a GPUI `Entity`, so you observe it like any other entity: call `entity.read(cx)` to inspect status, data, and error fields during render.

### `use_mutation_with_options`

```rust
pub fn use_mutation_with_options<V, T, E, C>(
    options: &MutationOptions<V, T, E>,
    cx: &mut Context<C>,
) -> Entity<MutationResource<V, T, E>>
```

Same as `use_mutation` but accepts a `MutationOptions` to configure retry policy and garbage collection time. See the `MutationOptions` section below.

## `mutate`

```rust
pub fn mutate<V, T, E, C, F, Fut>(
    entity: &Entity<MutationResource<V, T, E>>,
    variables: V,
    mutator: F,
    cx: &mut Context<C>,
)
```

Triggers a mutation on the entity. It transitions the resource to `Loading`, spawns an async task running your mutator closure, and handles the outcome (including retries if the entity has a retry policy).

The `mutator` closure receives the variables and must return a `Future<Output = Result<T, E>>`. Each call to `mutate` overwrites any previous state on the entity: variables, error, and data are reset.

```rust
use gpui_query::{use_mutation, mutate};

mutate(&self.create_user, NewUser { name: "Alice" }, |vars| async move {
    let resp = reqwest::post("/api/users")
        .json(&vars)
        .send()
        .await?;
    let user: User = resp.json().await?;
    Ok::<User, MyError>(user)
}, cx);
```

`mutate` holds a `WeakEntity` reference internally, so dropping the entity before the async task finishes is safe. The task simply exits.

### Concurrency

There is no built-in guard against calling `mutate` while a previous mutation on the same entity is still in flight. The second call overwrites the entity's state immediately. If you need to prevent concurrent mutations, check `entity.read(cx).is_loading()` before calling `mutate`.

## `mutate_with_callbacks`

```rust
pub fn mutate_with_callbacks<V, T, E, C, F, Fut>(
    entity: &Entity<MutationResource<V, T, E>>,
    variables: V,
    mutator: F,
    callbacks: MutationCallbacks<T, E>,
    cx: &mut Context<C>,
)
```

Identical to `mutate` but accepts a `MutationCallbacks` struct that fires on the final outcome. Callbacks run after all retries are exhausted (on failure) or on first success. They do not fire on intermediate retry attempts.

This is where you invalidate query caches after a successful write:

```rust
use gpui_query::{mutate_with_callbacks, MutationCallbacks};

mutate_with_callbacks(
    &self.create_user,
    NewUser { name: "Alice" },
    |vars| async move {
        Ok::<User, MyError>(create_user(vars).await)
    },
    MutationCallbacks::new()
        .on_success(|data| {
            // invalidate user list queries so they refetch
            println!("created user: {:?}", data);
        })
        .on_error(|err| {
            eprintln!("mutation failed: {:?}", err);
        }),
    cx,
);
```

See the `MutationCallbacks` section for the full callback API.

## `use_mutation_state`

```rust
pub fn use_mutation_state<V, T, E, C>(cx: &mut Context<C>) -> Vec<Entity<MutationResource<V, T, E>>>
```

Returns a snapshot of all `MutationResource` entities matching the `(V, T, E)` type triple registered in the global `QueryClient`. Useful for devtools or status bars that need to observe all mutations of a particular kind across the entire app.

Returns an empty `Vec` if no mutations exist for that type triple or if no `QueryClient` is set up.

```rust
use gpui_query::use_mutation_state;

let active_mutations = use_mutation_state::<NewUser, User, MyError, _>(cx);
for entity in &active_mutations {
    let m = entity.read(cx);
    if m.is_loading() {
        // show a global spinner
    }
}
```

## `MutationResource<V, T, E>`

The core state container for a single mutation. Three type parameters:

- `V` is the variables (input) type. What you pass into the mutation.
- `T` is the success output type. The data returned on completion.
- `E` is the error type. Defaults to `QueryError`.

### Lifecycle

Every mutation goes through these states in order:

1. **Idle**: initial state. No mutation has been triggered.
2. **Loading**: `begin(variables, now_ms)` was called. The variables are stored, the error is cleared, and a fresh cancellation signal is created.
3. **Success** or **Failure**: the async task completes. `complete_success(data)` stores the result and clears the error. `complete_failure(error)` stores the error and increments the retry counter.

If the entity has a retry policy and retries remain after a failure, the internal loop calls `retry()` to transition back to `Loading` and runs the mutator again.

### Accessors

| Method | Return type | Description |
|---|---|---|
| `status()` | `MutationStatus` | Current status enum |
| `data()` | `Option<&T>` | Most recent success data |
| `error()` | `Option<&E>` | Most recent error |
| `variables()` | `Option<&V>` | Current or most recent variables |
| `retry_count()` | `u32` | How many retries have been attempted |
| `retry_policy()` | `&RetryPolicy` | The retry policy for this resource |
| `created_at()` | `u64` | Timestamp (ms) of the last `begin` call, or 0 |
| `signal()` | `Option<&QuerySignal>` | The cancellation signal, if the mutation is in flight |
| `key()` | `Option<&QueryKey>` | Optional query key for cache correlation |

### Boolean helpers

`is_idle()`, `is_loading()`, `is_success()`, `is_failure()` each return `bool`. These are convenience wrappers around `status()` checks.

### Methods

**`begin(variables, now_ms)`** transitions to `Loading`. Stores the variables, clears any previous error, records the timestamp, and creates a fresh `QuerySignal`.

**`complete_success(data)`** transitions to `Success`. Stores the result data and clears the signal.

**`complete_failure(error)`** transitions to `Failure`. Stores the error, increments `retry_count`, and clears the signal.

**`should_retry() -> bool`** checks whether `retry_count < max_retries` according to the retry policy.

**`retry() -> bool`** attempts to transition from `Failure` back to `Loading`. Only succeeds when `should_retry()` is `true` and the current status is `Failure`. Preserves the original variables and creates a fresh signal. Returns `true` if the retry was initiated.

**`cancel(error)`** forces the mutation into `Failure` with the given error. Cancels the signal so any in-flight async work can observe it.

**`reset()`** returns the resource to `Idle` and clears everything: data, error, variables, retry count, signal, and timestamp.

**`with_key(key)`** associate a `QueryKey` with this mutation for cache correlation. Returns `self` for builder-style chaining.

### `MutationStatus`

```rust
pub enum MutationStatus {
    Idle,
    Loading,
    Success,
    Failure,
}
```

Each variant has a `label()` method returning a `&'static str` (`"Idle"`, `"Loading"`, `"Success"`, `"Failure"`).

## `MutationOptions<V, T, E>`

Configuration struct passed to `use_mutation_with_options`.

| Field | Type | Default | Description |
|---|---|---|---|
| `retry_policy` | `RetryPolicy` | `RetryPolicy::no_retries()` | Controls automatic retry on failure |
| `gc_time_ms` | `u64` | `300_000` (5 minutes) | How long idle mutation resources survive before garbage collection |

Builder methods:

```rust
use gpui_query::{MutationOptions, RetryPolicy};

let opts: MutationOptions<NewUser, User> = MutationOptions::new()
    .retry_policy(RetryPolicy::new(3).with_exponential_backoff())
    .gc_time_ms(10 * 60 * 1_000); // 10 minutes
```

## `MutationCallbacks<T, E>`

A struct of optional closures that fire when a mutation settles. Construct it with the builder pattern:

```rust
use gpui_query::MutationCallbacks;

let callbacks = MutationCallbacks::new()
    .on_success(|data: &User| {
        // called on successful completion
    })
    .on_error(|err: &MyError| {
        // called after all retries exhausted
    })
    .on_settled(|data: Option<&User>, err: Option<&MyError>| {
        // always called, regardless of outcome
    });
```

Each field is an `Option<Box<dyn Fn(&T) + 'static>>` (or the equivalent for the error and settled variants).

- **`on_success`** receives `&T`. Fires once on first success.
- **`on_error`** receives `&E`. Fires once after all retries are exhausted.
- **`on_settled`** receives `Option<&T>` and `Option<&E>`. Exactly one will be `Some`. Fires after either `on_success` or `on_error`.

Callbacks fire on the final outcome only. Intermediate retry attempts do not trigger callbacks.

## `RetryPolicy`

Shared between queries and mutations. See the [Queries](/docs/api/queries) page for the full reference. The defaults for mutations differ: `use_mutation` creates a resource with `RetryPolicy::no_retries()`, while `MutationOptions::new()` also defaults to no retries.

To add retries:

```rust
use gpui_query::RetryPolicy;

// Fixed 1-second delay, up to 3 retries
let policy = RetryPolicy::new(3);

// Exponential backoff: 1s, 2s, 4s, capped at 30s
let policy = RetryPolicy::new(3)
    .with_delay(1000)
    .with_exponential_backoff()
    .with_max_delay(30_000);
```

## Putting it together

A complete example of a view that creates a user and invalidates the user list on success:

```rust
use gpui::{Context, Entity, View};
use gpui_query::{
    use_mutation, mutate_with_callbacks,
    MutationCallbacks, MutationResource,
};

struct UserForm {
    create_user: Entity<MutationResource<NewUser, User>>,
}

impl UserForm {
    fn new(cx: &mut Context<Self>) -> Self {
        Self {
            create_user: use_mutation(cx),
        }
    }

    fn handle_submit(&mut self, name: String, cx: &mut Context<Self>) {
        mutate_with_callbacks(
            &self.create_user,
            NewUser { name },
            |vars| async move {
                Ok::<User, MyError>(api::create_user(&vars).await)
            },
            MutationCallbacks::new()
                .on_success(|_user| {
                    // Trigger a refetch of the user list query.
                    // This is where you'd call invalidate_queries or
                    // refetch on the relevant query entity.
                })
                .on_error(|err| {
                    eprintln!("create user failed: {:?}", err);
                }),
            cx,
        );
    }
}
```

The mutation entity lives on the view struct. During render, read `self.create_user.read(cx).status()` to decide what to show. When the user submits the form, `handle_submit` fires the mutation. On success, the callback invalidates whatever queries need refreshing. The query cache is otherwise untouched.
