Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/clarify-queue-docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/db': patch
---

Clarify queueStrategy error handling behavior in documentation. Changed "guaranteed to persist" to "guaranteed to be attempted" and added explicit documentation about how failed mutations are handled (not retried, queue continues). Added new Retry Behavior section with example code for implementing custom retry logic.
50 changes: 48 additions & 2 deletions docs/guides/mutations.md
Original file line number Diff line number Diff line change
Expand Up @@ -1093,7 +1093,7 @@ function VolumeSlider() {

### Queue Strategy

The queue strategy creates a separate transaction for each mutation and processes them sequentially in order. Unlike debounce/throttle, **every mutation is guaranteed to persist**, making it ideal for workflows where you can't lose any operations.
The queue strategy creates a separate transaction for each mutation and processes them sequentially in order. Unlike debounce/throttle which may drop intermediate mutations, **every mutation is guaranteed to be attempted**, making it ideal for workflows where you can't skip any operations.

```tsx
import { usePacedMutations, queueStrategy } from "@tanstack/react-db"
Expand Down Expand Up @@ -1136,9 +1136,16 @@ function FileUploader() {
- Each mutation becomes its own transaction
- Processes sequentially in order (FIFO by default)
- Can configure to LIFO by setting `getItemsFrom: 'back'`
- All mutations guaranteed to persist
- All mutations guaranteed to be attempted (unlike debounce/throttle which may skip intermediate mutations)
- Waits for each transaction to complete before starting the next

**Error handling**:
- If a mutation fails, **it is not automatically retried** - the transaction transitions to "failed" state
- Failed mutations surface their error via `transaction.isPersisted.promise` (which will reject)
- **Subsequent mutations continue processing** - a single failure does not block the queue
- Each mutation is independent; there is no all-or-nothing transaction semantics across multiple mutations
- To implement retry logic, see [Retry Behavior](#retry-behavior)

### Choosing a Strategy

Use this guide to pick the right strategy for your use case:
Expand Down Expand Up @@ -1453,6 +1460,45 @@ If an error occurs: `pending` → `persisting` → `failed`

Failed transactions automatically rollback their optimistic state.

### Retry Behavior

**Important:** TanStack DB does not automatically retry failed mutations. If a mutation fails (network error, server error, etc.), the transaction transitions to `failed` state and the optimistic state is rolled back. This is by design—automatic retry logic varies significantly based on your use case (idempotency requirements, error types, backoff strategies, etc.).

To implement retry logic, wrap your API calls in your `mutationFn`:

```typescript
// Simple retry helper
async function withRetry<T>(
fn: () => Promise<T>,
maxRetries = 3,
delay = 1000
): Promise<T> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn()
} catch (error) {
if (attempt === maxRetries - 1) throw error
await new Promise(resolve => setTimeout(resolve, delay * (attempt + 1)))
}
}
throw new Error('Unreachable')
}

// Use in your collection
const todoCollection = createCollection({
id: "todos",
onUpdate: async ({ transaction }) => {
const mutation = transaction.mutations[0]
// Retry up to 3 times with exponential backoff
await withRetry(() =>
api.todos.update(mutation.original.id, mutation.changes)
)
},
})
```

For more sophisticated retry strategies, consider using a library like [p-retry](https://github.com/sindresorhus/p-retry) which supports exponential backoff, custom retry conditions, and abort signals.

## Handling Temporary IDs

When inserting new items into collections where the server generates the final ID, you'll need to handle the transition from temporary to real IDs carefully to avoid UI issues and operation failures.
Expand Down
10 changes: 8 additions & 2 deletions packages/db/src/strategies/queueStrategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@ import type { Transaction } from '../transactions'
* Creates a queue strategy that processes all mutations in order with proper serialization.
*
* Unlike other strategies that may drop executions, queue ensures every
* mutation is processed sequentially. Each transaction commit completes before
* mutation is attempted sequentially. Each transaction commit completes before
* the next one starts. Useful when data consistency is critical and
* every operation must complete in order.
* every operation must be attempted in order.
*
* **Error handling behavior:**
* - If a mutation fails, it is NOT automatically retried - the transaction transitions to "failed" state
* - Failed mutations surface their error via `transaction.isPersisted.promise` (which will reject)
* - Subsequent mutations continue processing - a single failure does not block the queue
* - Each mutation is independent; there is no all-or-nothing transaction semantics
*
* @param options - Configuration for queue behavior (FIFO/LIFO, timing, size limits)
* @returns A queue strategy instance
Expand Down
Loading