From 5e5d1523ef5b2038ec07d33a2b45e9574b249c64 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 31 Oct 2025 15:45:55 +0000 Subject: [PATCH 1/2] docs: enhance error-handling guide with transaction and CRUD operation errors Added comprehensive documentation for commonly encountered errors: Transaction Lifecycle Errors: - MissingMutationFunctionError - TransactionNotPendingMutateError - TransactionNotPendingCommitError - TransactionAlreadyCompletedRollbackError Sync Transaction Errors: - NoPendingSyncTransactionWriteError - SyncTransactionAlreadyCommittedWriteError - NoPendingSyncTransactionCommitError - SyncTransactionAlreadyCommittedError CRUD Operation Errors: - UndefinedKeyError - UpdateKeyNotFoundError - KeyUpdateNotAllowedError - DeleteKeyNotFoundError Storage Errors: - SerializationError All new error types include practical examples showing how to catch and handle them, plus guidance on recovery patterns. --- docs/guides/error-handling.md | 256 +++++++++++++++++++++++++++++++--- 1 file changed, 234 insertions(+), 22 deletions(-) diff --git a/docs/guides/error-handling.md b/docs/guides/error-handling.md index 7661c240b..9a7fb4955 100644 --- a/docs/guides/error-handling.md +++ b/docs/guides/error-handling.md @@ -258,9 +258,11 @@ todoCollection.insert(newTodo) // Error: Collection.insert called directly (not within an explicit transaction) but no 'onInsert' handler is configured ``` -### Duplicate Key Errors +### Insert Operation Errors -Inserting items with existing keys will throw: +#### DuplicateKeyError + +Thrown when inserting items with existing keys: ```ts import { DuplicateKeyError } from "@tanstack/db" @@ -270,28 +272,118 @@ try { } catch (error) { if (error instanceof DuplicateKeyError) { console.log(`Duplicate key: ${error.message}`) + // Consider using update() instead, or check if item exists first } } ``` -### Schema Validation Errors +#### UndefinedKeyError -Schema validation must be synchronous: +Thrown when an object is created without a defined key: ```ts -const todoCollection = createCollection({ +import { UndefinedKeyError } from "@tanstack/db" + +const collection = createCollection({ id: "todos", - getKey: (todo) => todo.id, - schema: { - "~standard": { - validate: async (data) => { // Async validation not allowed - // ... - } - } + getKey: (item) => item.id, +}) + +try { + collection.insert({ text: "Todo" }) // Missing 'id' field +} catch (error) { + if (error instanceof UndefinedKeyError) { + console.log("Item is missing required key field") + // Ensure your items have the key field defined by getKey + } +} +``` + +### Update Operation Errors + +#### UpdateKeyNotFoundError + +Thrown when trying to update a key that doesn't exist in the collection: + +```ts +import { UpdateKeyNotFoundError } from "@tanstack/db" + +try { + todoCollection.update("nonexistent-key", draft => { + draft.completed = true + }) +} catch (error) { + if (error instanceof UpdateKeyNotFoundError) { + console.log("Key not found - item may have been deleted") + // Consider using insert() if the item doesn't exist + } +} +``` + +#### KeyUpdateNotAllowedError + +Thrown when attempting to change an item's key (not allowed - delete and re-insert instead): + +```ts +import { KeyUpdateNotAllowedError } from "@tanstack/db" + +try { + todoCollection.update("todo-1", draft => { + draft.id = "todo-2" // Not allowed! + }) +} catch (error) { + if (error instanceof KeyUpdateNotAllowedError) { + console.log("Cannot change item keys") + // Instead, delete the old item and insert a new one } +} +``` + +### Delete Operation Errors + +#### DeleteKeyNotFoundError + +Thrown when trying to delete a key that doesn't exist: + +```ts +import { DeleteKeyNotFoundError } from "@tanstack/db" + +try { + todoCollection.delete("nonexistent-key") +} catch (error) { + if (error instanceof DeleteKeyNotFoundError) { + console.log("Key not found - item may have already been deleted") + // This may be acceptable in some scenarios (idempotent deletes) + } +} +``` + +### Storage Errors + +#### SerializationError + +Thrown when an item cannot be JSON serialized during storage operations: + +```ts +import { SerializationError } from "@tanstack/db" + +const collection = createCollection({ + id: "todos", + getKey: (item) => item.id, }) -// Will throw: Schema validation must be synchronous +// Objects with circular references or functions can't be serialized +const itemWithCircularRef = { id: "1", text: "Todo" } +itemWithCircularRef.self = itemWithCircularRef + +try { + collection.insert(itemWithCircularRef) +} catch (error) { + if (error instanceof SerializationError) { + console.log("Item contains non-serializable data") + // Remove circular references, functions, or other non-serializable data + } +} ``` ## Sync Error Handling @@ -432,22 +524,139 @@ tx2.mutate(() => collection.update("1", draft => { draft.value = "B" })) // Same tx1.rollback() // tx2 is automatically rolled back ``` -### Handling Invalid State Errors +### Transaction Lifecycle Errors + +Transactions validate their state before operations to prevent misuse. Here are the specific errors you may encounter: + +#### MissingMutationFunctionError + +Thrown when creating a transaction without a required `mutationFn`: + +```ts +import { MissingMutationFunctionError } from "@tanstack/db" + +try { + const tx = createTransaction({}) // Missing mutationFn +} catch (error) { + if (error instanceof MissingMutationFunctionError) { + console.log("mutationFn is required when creating a transaction") + } +} +``` + +#### TransactionNotPendingMutateError -Transactions validate their state before operations: +Thrown when calling `mutate()` after a transaction is no longer pending: ```ts +import { TransactionNotPendingMutateError } from "@tanstack/db" + const tx = createTransaction({ mutationFn: async () => {} }) -// Complete the transaction await tx.commit() -// These will throw: -tx.mutate(() => {}) // Error: You can no longer call .mutate() as the transaction is no longer pending -tx.commit() // Error: You can no longer call .commit() as the transaction is no longer pending -tx.rollback() // Error: You can no longer call .rollback() as the transaction is already completed +try { + tx.mutate(() => { + collection.insert({ id: "1", text: "Item" }) + }) +} catch (error) { + if (error instanceof TransactionNotPendingMutateError) { + console.log("Cannot mutate - transaction is no longer pending") + } +} +``` + +#### TransactionNotPendingCommitError + +Thrown when calling `commit()` after a transaction is no longer pending: + +```ts +import { TransactionNotPendingCommitError } from "@tanstack/db" + +const tx = createTransaction({ mutationFn: async () => {} }) +tx.mutate(() => collection.insert({ id: "1", text: "Item" })) + +await tx.commit() + +try { + await tx.commit() // Trying to commit again +} catch (error) { + if (error instanceof TransactionNotPendingCommitError) { + console.log("Transaction already committed") + } +} +``` + +#### TransactionAlreadyCompletedRollbackError + +Thrown when calling `rollback()` on a transaction that's already completed: + +```ts +import { TransactionAlreadyCompletedRollbackError } from "@tanstack/db" + +const tx = createTransaction({ mutationFn: async () => {} }) +tx.mutate(() => collection.insert({ id: "1", text: "Item" })) + +await tx.commit() + +try { + tx.rollback() // Can't rollback after commit +} catch (error) { + if (error instanceof TransactionAlreadyCompletedRollbackError) { + console.log("Cannot rollback - transaction already completed") + } +} +``` + +### Sync Transaction Errors + +When working with sync transactions, these errors can occur: + +#### NoPendingSyncTransactionWriteError + +Thrown when calling `write()` without an active sync transaction: + +```ts +const collection = createCollection({ + id: "todos", + sync: { + sync: ({ write }) => { + // Calling write without begin() first + write({ type: "insert", value: { id: "1", text: "Todo" } }) + // Error: No pending sync transaction to write to + } + } +}) ``` +#### SyncTransactionAlreadyCommittedWriteError + +Thrown when calling `write()` after the sync transaction is already committed: + +```ts +const collection = createCollection({ + id: "todos", + sync: { + sync: ({ begin, write, commit }) => { + begin() + commit() + + // Trying to write after commit + write({ type: "insert", value: { id: "1", text: "Todo" } }) + // Error: The pending sync transaction is already committed + } + } +}) +``` + +#### NoPendingSyncTransactionCommitError + +Thrown when calling `commit()` without an active sync transaction. + +#### SyncTransactionAlreadyCommittedError + +Thrown when calling `commit()` on a sync transaction that's already committed. + ## Best Practices 1. **Use instanceof checks** - Use `instanceof` instead of string matching for error handling: @@ -471,10 +680,13 @@ tx.rollback() // Error: You can no longer call .rollback() as the transaction is ## Example: Complete Error Handling ```tsx -import { - createCollection, +import { + createCollection, SchemaValidationError, DuplicateKeyError, + UpdateKeyNotFoundError, + DeleteKeyNotFoundError, + TransactionNotPendingCommitError, createTransaction } from "@tanstack/db" import { useLiveQuery } from "@tanstack/react-db" From 1f9fba3b70eb2137eca556d96ea093dddca8e272 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 31 Oct 2025 15:48:50 +0000 Subject: [PATCH 2/2] docs: remove SerializationError (specific to LocalStorage collection type) --- docs/guides/error-handling.md | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/docs/guides/error-handling.md b/docs/guides/error-handling.md index 9a7fb4955..9231e225b 100644 --- a/docs/guides/error-handling.md +++ b/docs/guides/error-handling.md @@ -358,34 +358,6 @@ try { } ``` -### Storage Errors - -#### SerializationError - -Thrown when an item cannot be JSON serialized during storage operations: - -```ts -import { SerializationError } from "@tanstack/db" - -const collection = createCollection({ - id: "todos", - getKey: (item) => item.id, -}) - -// Objects with circular references or functions can't be serialized -const itemWithCircularRef = { id: "1", text: "Todo" } -itemWithCircularRef.self = itemWithCircularRef - -try { - collection.insert(itemWithCircularRef) -} catch (error) { - if (error instanceof SerializationError) { - console.log("Item contains non-serializable data") - // Remove circular references, functions, or other non-serializable data - } -} -``` - ## Sync Error Handling ### Query Collection Sync Errors