idempotent

Mutations only. Marks the mutation as safe to retry on network errors. By default, mutations are never retried — even with a retry policy configured. Queries (GET) are always retryable.

Basic Usage

rust
// Safe to retry on failure — repeated calls produce the same result
#[rpc_mutation(idempotent)]
async fn upsert_user(input: UpsertInput) -> User { ... }

// NOT safe to retry (default) — each call creates a new resource
#[rpc_mutation]
async fn create_order(input: OrderInput) -> Order { ... }

Good Candidates

Mark mutations as idempotent when repeated calls produce the same result: upserts, overwrites, deletes.

rust
// Good candidates for idempotent:
#[rpc_mutation(idempotent)]
async fn set_preference(input: PrefInput) -> Pref { ... }  // upsert

#[rpc_mutation(idempotent)]
async fn update_status(input: StatusInput) -> Status { ... }  // overwrite

#[rpc_mutation(idempotent)]
async fn delete_item(id: u64) -> bool { ... }  // delete is idempotent

// BAD — do NOT mark as idempotent:
// #[rpc_mutation(idempotent)]
// async fn transfer_money(input: TransferInput) -> Receipt { ... }

Retry Behavior

When marked idempotent, the mutation follows the same retry policy as queries. Without it, the mutation is never retried regardless of the retry configuration.

ts
// Without idempotent: mutation is NEVER retried, even with retry policy
// With idempotent: mutation follows the same retry policy as queries

const rpc = createRpcClient({
  baseUrl: '/api',
  retry: { attempts: 3, delay: 1000 },
});

// upsert_user (idempotent) — will retry up to 3 times on failure
await rpc.mutate('upsert_user', input);

// create_order (not idempotent) — will NOT retry, even though retry is configured
await rpc.mutate('create_order', input);

Combining Attributes

rust
// Combine with other attributes
#[rpc_mutation(idempotent, timeout = "10s")]
async fn upsert_record(input: RecordInput) -> Record { ... }

#[rpc_mutation(idempotent, init = "setup_db")]
async fn sync_profile(pool: &PgPool, input: ProfileInput) -> Profile { ... }

// ❌ Compile error — idempotent is mutations only
// #[rpc_query(idempotent)]
// async fn get_data() -> Data { ... }

Try it

Idempotent Upsert

This mutation stores a value on the server (upsert). Click the same button twice — the result is identical, proving it's safe to retry. The call counter increments, but the stored value stays the same.

visit GitHub to learn more about metaxy