Skip to content
Merged
117 changes: 117 additions & 0 deletions .changeset/short-dingos-raise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
---
"@tanstack/trailbase-db-collection": patch
"@tanstack/electric-db-collection": patch
"@tanstack/query-db-collection": patch
"@tanstack/db": patch
---

feat: Replace string-based errors with named error classes for better error handling

This comprehensive update replaces all string-based error throws throughout the TanStack DB codebase with named error classes, providing better type safety and developer experience.

## New Features

- **Root `TanStackDBError` class** - all errors inherit from a common base for unified error handling
- **Named error classes** organized by package and functional area
- **Type-safe error handling** using `instanceof` checks instead of string matching
- **Package-specific error definitions** - each adapter has its own error classes
- **Better IDE support** with autocomplete for error types

## Package Structure

### Core Package (`@tanstack/db`)

Contains generic errors used across the ecosystem:

- Collection configuration, state, and operation errors
- Transaction lifecycle and mutation errors
- Query building, compilation, and execution errors
- Storage and serialization errors

### Adapter Packages

Each adapter now exports its own specific error classes:

- **`@tanstack/electric-db-collection`**: Electric-specific errors
- **`@tanstack/trailbase-db-collection`**: TrailBase-specific errors
- **`@tanstack/query-db-collection`**: Query collection specific errors

## Breaking Changes

- Error handling code using string matching will need to be updated to use `instanceof` checks
- Some error messages may have slight formatting changes
- Adapter-specific errors now need to be imported from their respective packages

## Migration Guide

### Core DB Errors

**Before:**

```ts
try {
collection.insert(data)
} catch (error) {
if (error.message.includes("already exists")) {
// Handle duplicate key error
}
}
```

**After:**

```ts
import { DuplicateKeyError } from "@tanstack/db"

try {
collection.insert(data)
} catch (error) {
if (error instanceof DuplicateKeyError) {
// Type-safe error handling
}
}
```

### Adapter-Specific Errors

**Before:**

```ts
// Electric collection errors were imported from @tanstack/db
import { ElectricInsertHandlerMustReturnTxIdError } from "@tanstack/db"
```

**After:**

```ts
// Now import from the specific adapter package
import { ElectricInsertHandlerMustReturnTxIdError } from "@tanstack/electric-db-collection"
```

### Unified Error Handling

**New:**

```ts
import { TanStackDBError } from "@tanstack/db"

try {
// Any TanStack DB operation
} catch (error) {
if (error instanceof TanStackDBError) {
// Handle all TanStack DB errors uniformly
console.log("TanStack DB error:", error.message)
}
}
```

## Benefits

- **Type Safety**: All errors now have specific types that can be caught with `instanceof`
- **Unified Error Handling**: Root `TanStackDBError` class allows catching all library errors with a single check
- **Better Package Separation**: Each adapter manages its own error types
- **Developer Experience**: Better IDE support with autocomplete for error types
- **Maintainability**: Error definitions are co-located with their usage
- **Consistency**: Uniform error handling patterns across the entire codebase

All error classes maintain the same error messages and behavior while providing better structure and package separation.
46 changes: 40 additions & 6 deletions docs/error-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,19 @@ TanStack DB provides comprehensive error handling capabilities to ensure robust

## Error Types

TanStack DB provides named error classes for better error handling and type safety. All error classes can be imported from `@tanstack/db` (or more commonly, the framework-specific package e.g. `@tanstack/react-db`):

```ts
import {
SchemaValidationError,
CollectionInErrorStateError,
DuplicateKeyError,
MissingHandlerError,
TransactionError,
// ... and many more
} from "@tanstack/db"
```

### SchemaValidationError

Thrown when data doesn't match the collection's schema during insert or update operations:
Expand Down Expand Up @@ -168,10 +181,12 @@ try {
Collections in an `error` state cannot perform operations and must be manually recovered:

```ts
import { CollectionInErrorStateError } from "@tanstack/db"

try {
todoCollection.insert(newTodo)
} catch (error) {
if (error.message.includes("collection is in error state")) {
if (error instanceof CollectionInErrorStateError) {
// Collection needs to be cleaned up and restarted
await todoCollection.cleanup()

Expand Down Expand Up @@ -202,10 +217,14 @@ todoCollection.insert(newTodo)
Inserting items with existing keys will throw:

```ts
import { DuplicateKeyError } from "@tanstack/db"

try {
todoCollection.insert({ id: "existing-id", text: "Todo" })
} catch (error) {
// Error: Cannot insert document with ID "existing-id" because it already exists in the collection
if (error instanceof DuplicateKeyError) {
console.log(`Duplicate key: ${error.message}`)
}
}
```

Expand Down Expand Up @@ -384,16 +403,31 @@ tx.rollback() // Error: You can no longer call .rollback() as the transaction is

## Best Practices

1. **Always handle SchemaValidationError** - Provide clear feedback for validation failures
2. **Check collection status** - Use `isError`, `isLoading`, `isReady` flags in React components
3. **Handle transaction promises** - Always handle `isPersisted.promise` rejections
1. **Use instanceof checks** - Use `instanceof` instead of string matching for error handling:
```ts
// ✅ Good - type-safe error handling
if (error instanceof SchemaValidationError) {
// Handle validation error
}

// ❌ Avoid - brittle string matching
if (error.message.includes("validation failed")) {
// Handle validation error
}
```

2. **Import specific error types** - Import only the error classes you need for better tree-shaking
3. **Always handle SchemaValidationError** - Provide clear feedback for validation failures
4. **Check collection status** - Use `isError`, `isLoading`, `isReady` flags in React components
5. **Handle transaction promises** - Always handle `isPersisted.promise` rejections

## Example: Complete Error Handling

```tsx
import {
createCollection,
SchemaValidationError,
DuplicateKeyError,
createTransaction
} from "@tanstack/db"
import { useLiveQuery } from "@tanstack/react-db"
Expand Down Expand Up @@ -442,7 +476,7 @@ const TodoApp = () => {
} catch (error) {
if (error instanceof SchemaValidationError) {
alert(`Validation error: ${error.issues[0]?.message}`)
} else if (error.message?.includes("already exists")) {
} else if (error instanceof DuplicateKeyError) {
alert("A todo with this ID already exists")
} else {
alert(`Failed to add todo: ${error.message}`)
Expand Down
Loading