Error Handling & Safety
The ORM includes several safety features to prevent common database mistakes and provides custom error classes for better error handling.
Custom Error Classes
The ORM provides specific error classes to help you handle different types of errors appropriately.
Error Hierarchy
import {
TauriORMError, // Base error class
QueryBuilderError, // Query building errors
MissingWhereClauseError, // Missing WHERE in UPDATE/DELETE
ValidationError, // General validation errors
InsertValidationError, // Insert-specific validation
UpdateValidationError, // Update-specific validation
MigrationError, // Migration errors
RelationError, // Relation errors
ColumnNotFoundError, // Column doesn't exist
TableNotFoundError, // Table doesn't exist
} from '@type32/tauri-sqlite-orm';
Catching Specific Errors
try {
await db.update(users)
.set({ name: 'Bob' })
.execute(); // Missing WHERE clause
} catch (error) {
if (error instanceof MissingWhereClauseError) {
console.error('You forgot to add a WHERE clause!');
console.error(error.message);
// Handle this specific error type
} else if (error instanceof UpdateValidationError) {
console.error('Update validation failed');
} else {
// Re-throw unknown errors
throw error;
}
}
WHERE Clause Validation
Critical Safety Feature: UPDATE and DELETE operations require a WHERE clause by default to prevent accidental data loss.
The Problem
Without validation, it's easy to accidentally update or delete all rows:
// ❌ DANGEROUS: Would update ALL users if not prevented!
await db.update(users).set({ isActive: false }).execute();
// ❌ DANGEROUS: Would delete ALL posts if not prevented!
await db.delete(posts).execute();
The Solution
The ORM requires WHERE clauses for UPDATE and DELETE:
// ❌ Throws MissingWhereClauseError
await db.update(users)
.set({ isActive: false })
.execute();
// ✅ Works - has WHERE clause
await db.update(users)
.set({ isActive: false })
.where(eq(users._.columns.id, 123))
.execute();
// ✅ Works - explicit global operation
await db.update(users)
.set({ isActive: false })
.allowGlobalOperation() // ← Explicitly allows no WHERE clause
.execute();
Error Message
MissingWhereClauseError: UPDATE operation on table "users" requires a WHERE clause to prevent accidental data loss. Use .where() to specify conditions, or use .allowGlobalOperation() to explicitly allow operations without WHERE.
Intentional Global Operations
When you genuinely need to update/delete all rows, use .allowGlobalOperation():
// Reset all daily counts - documented and intentional
await db.update(userStats)
.set({ dailyCount: 0 })
.allowGlobalOperation()
.execute();
// Clear temporary cache
await db.delete(tempCache)
.allowGlobalOperation()
.execute();
Validation Errors
Insert Validation
try {
// ❌ No data provided
await db.insert(users).execute();
} catch (error) {
if (error instanceof InsertValidationError) {
console.error(error.message);
// "No data provided for insert. Use .values() to provide data."
}
}
Update Validation
try {
// ❌ No SET clause
await db.update(users)
.where(eq(users._.columns.id, 1))
.execute();
} catch (error) {
if (error instanceof UpdateValidationError) {
console.error(error.message);
// "Cannot execute an update query without a .set(), .increment(), or .decrement() call."
}
}
Column Not Found
try {
await db.update(users)
.increment('nonExistentColumn' as any)
.where(eq(users._.columns.id, 1))
.execute();
} catch (error) {
if (error instanceof ColumnNotFoundError) {
console.error(error.message);
// "Column \"nonExistentColumn\" does not exist on table \"users\""
}
}
Best Practices
1. Always Use WHERE Clauses
// ❌ BAD: Missing WHERE
await db.delete(users).execute();
// ✅ GOOD: Has WHERE
await db.delete(users)
.where(eq(users._.columns.id, userId))
.execute();
2. Catch Specific Errors
async function updateUser(userId: number, data: any) {
try {
return await db.update(users)
.set(data)
.where(eq(users._.columns.id, userId))
.execute();
} catch (error) {
if (error instanceof MissingWhereClauseError) {
// Log and handle missing WHERE
logger.error('Missing WHERE clause in update');
throw new Error('Invalid update operation');
} else if (error instanceof ColumnNotFoundError) {
// Handle invalid column
logger.error('Invalid column in update:', error.message);
throw new Error('Invalid field name');
} else {
// Re-throw database errors
throw error;
}
}
}
3. Validate Before Operations
async function safeDeleteUser(userId: number) {
// Check if user exists first
const exists = await db.select(users)
.where(eq(users._.columns.id, userId))
.exists();
if (!exists) {
throw new ValidationError('User not found');
}
// Delete with proper WHERE clause
return await db.delete(users)
.where(eq(users._.columns.id, userId))
.execute();
}
4. Document Global Operations
/**
* Resets all user statistics for a new day
* This intentionally updates ALL rows
*/
async function resetDailyStats() {
await db.update(userStats)
.set({
dailyViews: 0,
dailyLikes: 0
})
.allowGlobalOperation() // Documented intent
.execute();
}
5. Use Type-Safe Operations
// TypeScript will catch column name errors at compile time
await db.update(users)
.increment('views') // ✓ Type-checked column name
.where(eq(users._.columns.id, 1))
.execute();
// ❌ TypeScript error if column doesn't exist
await db.update(users)
.increment('invalidColumn') // Type error!
.execute();
Error Handling Pattern
Here's a comprehensive error handling pattern for production code:
import {
MissingWhereClauseError,
ColumnNotFoundError,
ValidationError,
TauriORMError
} from '@type32/tauri-sqlite-orm';
async function updateUserProfile(userId: number, updates: Partial<User>) {
try {
// Validate user exists
const exists = await db.select(users)
.where(eq(users._.columns.id, userId))
.exists();
if (!exists) {
throw new ValidationError('User not found');
}
// Perform update
const result = await db.update(users)
.set({
...updates,
updatedAt: new Date()
})
.where(eq(users._.columns.id, userId))
.returningFirst();
return result;
} catch (error) {
// Handle ORM-specific errors
if (error instanceof TauriORMError) {
logger.error('Database error:', {
type: error.name,
message: error.message,
userId
});
// Provide user-friendly message
if (error instanceof MissingWhereClauseError) {
throw new Error('Invalid update operation');
} else if (error instanceof ColumnNotFoundError) {
throw new Error('Invalid field name');
} else if (error instanceof ValidationError) {
throw error; // Re-throw validation errors as-is
}
}
// Handle other errors
logger.error('Unexpected error:', error);
throw new Error('Failed to update user');
}
}
Summary
The ORM provides multiple layers of safety:
- Custom Error Classes - Clear, specific error messages
- WHERE Validation - Prevents accidental mass updates/deletes
- Type Safety - TypeScript catches errors at compile time
- Explicit Global Operations -
.allowGlobalOperation()makes intent clear - Validation Errors - Helpful messages for common mistakes
These features work together to make your database operations safer and more reliable in production applications.