Mutations

Use #[rpc_mutation] for write operations (create, update, delete). Unlike queries, mutations are triggered explicitly via .mutate() — they don't auto-refetch.

rust
#[rpc_mutation]
async fn create_order(input: OrderInput) -> Order {
    db::insert_order(input).await
}
ts
// Direct call
const order = await rpc.mutate('create_order', orderInput);

// With options
const order = await rpc.mutate('create_order', orderInput, {
  timeout: 10000,
});

// Void-input mutation
await rpc.mutate('reset');

// Result<T, E> — errors throw RpcError
try {
  await rpc.mutate('delete_user', userId);
} catch (e) {
  if (e instanceof RpcError) {
    console.error(e.status, e.data);
  }
}
ts
// Reactive wrapper — triggered explicitly via .mutate()
const order = createMutation(rpc, 'create_order', {
  onSuccess: (data) => console.log('Created:', data),
  onError: (err) => console.error(err),
});

// Fire-and-forget
order.mutate(orderInput);

// Await the result
const result = await order.mutateAsync(orderInput);

// Access state
// order.data       — Order | undefined
// order.error      — RpcError | undefined
// order.isLoading  — boolean
// order.isSuccess  — boolean
// order.isError    — boolean
// order.reset()    — clear state
Note: Input structs must derive both Deserialize (for JSON parsing) and Serialize (so the CLI scanner can emit TypeScript interfaces). Without Serialize, the struct won't appear in the generated types.

Void Input & Error Handling

Mutations can take no input (void) or return Result<T, E> for typed error handling. Errors are propagated as RpcError on the client side.

rust
// Void-input mutation — no arguments needed
#[rpc_mutation]
async fn reset() -> bool {
    cache::clear_all().await;
    true
}

// Result error handling in mutations
#[rpc_mutation]
async fn delete_user(id: u64) -> Result<(), String> {
    if !db::user_exists(id).await {
        return Err("User not found".into());
    }
    db::delete_user(id).await;
    Ok(())
}

Per-Call Options

Every mutate() call accepts an optional trailing CallOptions object to override client-level defaults.

ts
interface CallOptions {
  headers?: Record<string, string>;  // merged with client headers
  timeout?: number;                   // override client timeout (ms)
  signal?: AbortSignal;              // combined with client signal
  dedupe?: boolean;                  // per-call dedup override
}
ts
// Mutation with input + options
await rpc.mutate('create_order', orderInput, {
  signal: abortController.signal,
  dedupe: false,
});

Try it

Echo — Struct Mutation

Send a message, optionally uppercase it. Demonstrates createMutation with struct input/output.

visit GitHub to learn more about metaxy