From b8b37d803c7efe676380d807103e7490636d8d59 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Jan 2026 22:39:30 +0000 Subject: [PATCH 1/5] docs: clarify queueStrategy error handling behavior The previous documentation implied that "every mutation is guaranteed to persist" which is misleading. This clarifies that: - Every mutation is guaranteed to be ATTEMPTED (not dropped) - Failed mutations are NOT automatically retried - Failed mutations transition to "failed" state - Subsequent mutations continue processing (queue doesn't stop) - Each mutation is independent (no all-or-nothing semantics) Fixes #1071 --- docs/guides/mutations.md | 10 ++++++++-- docs/reference/functions/queueStrategy.md | 10 ++++++++-- packages/db/src/strategies/queueStrategy.ts | 10 ++++++++-- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/docs/guides/mutations.md b/docs/guides/mutations.md index 4c1662560..cf43a66c1 100644 --- a/docs/guides/mutations.md +++ b/docs/guides/mutations.md @@ -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" @@ -1136,9 +1136,15 @@ 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 + ### Choosing a Strategy Use this guide to pick the right strategy for your use case: diff --git a/docs/reference/functions/queueStrategy.md b/docs/reference/functions/queueStrategy.md index 524ca194f..632138d00 100644 --- a/docs/reference/functions/queueStrategy.md +++ b/docs/reference/functions/queueStrategy.md @@ -14,9 +14,15 @@ Defined in: [packages/db/src/strategies/queueStrategy.ts:46](https://github.com/ 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 ## Parameters diff --git a/packages/db/src/strategies/queueStrategy.ts b/packages/db/src/strategies/queueStrategy.ts index 2f67848f4..2a374d8ee 100644 --- a/packages/db/src/strategies/queueStrategy.ts +++ b/packages/db/src/strategies/queueStrategy.ts @@ -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 From 5c1b89e4b9701f9c9a94c379916b455203bd21a0 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Jan 2026 22:45:44 +0000 Subject: [PATCH 2/5] docs: add retry behavior documentation and examples TanStack DB does not automatically retry failed mutations - this is by design since retry strategies vary by use case. This commit: - Adds a "Retry Behavior" section explaining why auto-retry isn't built-in - Provides a simple retry helper example with exponential backoff - Adds a queue strategy-specific retry example for file uploads - References p-retry library for more sophisticated strategies This helps answer the question from #1071 about what happens when mutations fail and how to handle retries. --- docs/guides/mutations.md | 63 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/docs/guides/mutations.md b/docs/guides/mutations.md index cf43a66c1..137101a06 100644 --- a/docs/guides/mutations.md +++ b/docs/guides/mutations.md @@ -1145,6 +1145,30 @@ function FileUploader() { - **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 add retry logic to queued mutations, implement it in your `mutationFn`: + +```tsx +const mutate = usePacedMutations({ + onMutate: (file) => { + uploadCollection.insert({ id: crypto.randomUUID(), file, status: 'pending' }) + }, + mutationFn: async ({ transaction }) => { + const mutation = transaction.mutations[0] + // Retry up to 3 times with backoff + for (let attempt = 0; attempt < 3; attempt++) { + try { + await api.files.upload(mutation.modified) + return // Success + } catch (error) { + if (attempt === 2) throw error // Final attempt, propagate error + await new Promise(r => setTimeout(r, 1000 * (attempt + 1))) + } + } + }, + strategy: queueStrategy({ wait: 500 }), +}) +``` + ### Choosing a Strategy Use this guide to pick the right strategy for your use case: @@ -1459,6 +1483,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( + fn: () => Promise, + maxRetries = 3, + delay = 1000 +): Promise { + 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. From 811bdcedae3b8dfbd18c468f7aedc3031f4a2fb5 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Jan 2026 22:51:58 +0000 Subject: [PATCH 3/5] docs: remove duplicative retry example from queue strategy section Link to the Retry Behavior section instead of duplicating the example. --- docs/guides/mutations.md | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/docs/guides/mutations.md b/docs/guides/mutations.md index 137101a06..35615e249 100644 --- a/docs/guides/mutations.md +++ b/docs/guides/mutations.md @@ -1144,30 +1144,7 @@ function FileUploader() { - 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 add retry logic to queued mutations, implement it in your `mutationFn`: - -```tsx -const mutate = usePacedMutations({ - onMutate: (file) => { - uploadCollection.insert({ id: crypto.randomUUID(), file, status: 'pending' }) - }, - mutationFn: async ({ transaction }) => { - const mutation = transaction.mutations[0] - // Retry up to 3 times with backoff - for (let attempt = 0; attempt < 3; attempt++) { - try { - await api.files.upload(mutation.modified) - return // Success - } catch (error) { - if (attempt === 2) throw error // Final attempt, propagate error - await new Promise(r => setTimeout(r, 1000 * (attempt + 1))) - } - } - }, - strategy: queueStrategy({ wait: 500 }), -}) -``` +- To implement retry logic, see [Retry Behavior](#retry-behavior) ### Choosing a Strategy From 797e11bd284bd7cc16ee5c7a5f0e21da414e521c Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Jan 2026 23:02:20 +0000 Subject: [PATCH 4/5] docs: revert manual edit to generated reference doc The queueStrategy.md reference doc is auto-generated from JSDoc in the source file - the source already has the updated documentation. --- docs/reference/functions/queueStrategy.md | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/docs/reference/functions/queueStrategy.md b/docs/reference/functions/queueStrategy.md index 632138d00..524ca194f 100644 --- a/docs/reference/functions/queueStrategy.md +++ b/docs/reference/functions/queueStrategy.md @@ -14,15 +14,9 @@ Defined in: [packages/db/src/strategies/queueStrategy.ts:46](https://github.com/ 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 attempted sequentially. Each transaction commit completes before +mutation is processed sequentially. Each transaction commit completes before the next one starts. Useful when data consistency is critical and -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 +every operation must complete in order. ## Parameters From 5778cd4bb0345cf61ac14db6b7f80c32f65b7689 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Thu, 8 Jan 2026 16:10:14 -0700 Subject: [PATCH 5/5] chore: add changeset for queue strategy docs clarification Co-Authored-By: Claude Opus 4.5 --- .changeset/clarify-queue-docs.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/clarify-queue-docs.md diff --git a/.changeset/clarify-queue-docs.md b/.changeset/clarify-queue-docs.md new file mode 100644 index 000000000..2230dbacb --- /dev/null +++ b/.changeset/clarify-queue-docs.md @@ -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.