Error Handling
Kysera provides a comprehensive error handling system with typed errors for different database operations. The error handling system includes improved error parsing for all supported databases.
Error Hierarchy
Error Classes
DatabaseError
Base error class for all database errors:
class DatabaseError extends Error {
readonly code: string
readonly detail?: string
constructor(message: string, code: string, detail?: string)
toJSON(): Record<string, unknown>
}
UniqueConstraintError
Thrown when a UNIQUE constraint is violated:
class UniqueConstraintError extends DatabaseError {
constraint: string // e.g., 'users_email_unique'
table: string // e.g., 'users'
columns: string[] // e.g., ['email']
}
ForeignKeyError
Thrown when a FOREIGN KEY constraint is violated:
class ForeignKeyError extends DatabaseError {
constraint: string // e.g., 'posts_user_id_fkey'
table: string // e.g., 'posts'
referencedTable: string // e.g., 'users'
}
NotFoundError
Thrown when an entity is not found:
class NotFoundError extends DatabaseError {
// Message includes entity name and optional filter details
// Example: "User not found" with detail: {"id": 123}
}
Constructor:
new NotFoundError(entity: string, filters?: Record<string, unknown>)
NotNullError
Thrown when a NOT NULL constraint is violated:
class NotNullError extends DatabaseError {
readonly column: string
readonly table?: string
constructor(column: string, table?: string)
}
CheckConstraintError
Thrown when a CHECK constraint is violated:
class CheckConstraintError extends DatabaseError {
readonly constraint: string
readonly table?: string
constructor(constraint: string, table?: string)
}
Parsing Database Errors
Use parseDatabaseError to convert raw database errors into typed errors. The error parser intelligently handles database-specific error formats:
import { parseDatabaseError, UniqueConstraintError, ForeignKeyError } from '@kysera/core'
// PostgreSQL example
try {
await db.insertInto('users').values({ email: 'duplicate@test.com' }).execute()
} catch (error) {
// parseDatabaseError handles all database-specific error formats
const dbError = parseDatabaseError(error, 'postgres')
if (dbError instanceof UniqueConstraintError) {
console.log('Duplicate:', dbError.constraint, dbError.columns)
// Handle duplicate entry
} else if (dbError instanceof ForeignKeyError) {
console.log('Invalid reference:', dbError.referencedTable)
// Handle invalid foreign key
} else {
// Handle other database errors
throw dbError
}
}
// MSSQL example
try {
await db.insertInto('users').values({ email: 'duplicate@example.com' }).execute()
} catch (error) {
const dbError = parseDatabaseError(error, 'mssql')
if (dbError instanceof UniqueConstraintError) {
console.log('Duplicate entry:', dbError.constraint)
}
}
Enhanced Error Parsing:
- Improved Truncate Handling - Better error detection for TRUNCATE operations
- Database-Specific Parsing - Handles PostgreSQL, MySQL, SQLite, and MSSQL error formats
- Detail Extraction - Extracts constraint names, columns, and referenced tables from error messages
Multi-Database Support
The error parser works with all supported databases:
PostgreSQL
const error = parseDatabaseError(pgError, 'postgres')
// Handles:
// - 23505 (unique constraint)
// - 23503 (foreign key violation)
// - 23502 (not null violation)
// - 23514 (check constraint)
// - Improved TRUNCATE error detection
MySQL
const error = parseDatabaseError(mysqlError, 'mysql')
// Handles:
// - ER_DUP_ENTRY (duplicate key)
// - ER_NO_REFERENCED_ROW (foreign key)
// - ER_BAD_NULL_ERROR (not null)
// - Enhanced constraint name extraction
SQLite
const error = parseDatabaseError(sqliteError, 'sqlite')
// Handles:
// - UNIQUE constraint failed
// - FOREIGN KEY constraint failed
// - NOT NULL constraint failed
// - Better column name extraction from error messages
MSSQL
const error = parseDatabaseError(mssqlError, 'mssql')
// Handles:
// - 2627, 2601 (unique constraint violation)
// - 515 (not null violation)
// - 547 (foreign key violation)
// - Constraint name extraction from error messages
Unified Error Codes
Kysera provides a unified error code system:
// Database errors
'DB_CONNECTION_FAILED'
'DB_QUERY_FAILED'
'DB_TRANSACTION_FAILED'
'DB_TIMEOUT'
'DB_POOL_EXHAUSTED'
'DB_UNKNOWN'
// Validation errors
'VALIDATION_UNIQUE_VIOLATION'
'VALIDATION_FOREIGN_KEY_VIOLATION'
'VALIDATION_NOT_NULL_VIOLATION'
'VALIDATION_CHECK_VIOLATION'
'VALIDATION_INVALID_INPUT'
'VALIDATION_REQUIRED_FIELD'
'VALIDATION_INVALID_TYPE'
// Resource errors
'RESOURCE_NOT_FOUND'
'RESOURCE_ALREADY_EXISTS'
'RESOURCE_CONFLICT'
'RESOURCE_BAD_REQUEST'
// Plugin errors
'SOFT_DELETE_ERROR'
'RECORD_NOT_DELETED'
'TIMESTAMPS_ERROR'
'TIMESTAMP_COLUMN_MISSING'
// Audit errors
'AUDIT_ERROR'
'AUDIT_RESTORE_ERROR'
'AUDIT_MISSING_VALUES'
Error Handling Patterns
In Repositories
async function createUser(data: CreateUserInput): Promise<User> {
try {
return await db.insertInto('users').values(data).returningAll().executeTakeFirstOrThrow()
} catch (error) {
const dbError = parseDatabaseError(error, dialect)
if (dbError instanceof UniqueConstraintError) {
throw new BadRequestError(`Email ${data.email} already exists`)
}
throw dbError
}
}
In API Handlers
app.post('/users', async (req, res) => {
try {
const user = await userRepo.create(req.body)
res.status(201).json(user)
} catch (error) {
if (error instanceof BadRequestError) {
return res.status(400).json({
error: error.message
})
}
if (error instanceof UniqueConstraintError) {
return res.status(409).json({
error: 'Resource already exists',
field: error.columns[0]
})
}
if (error instanceof NotFoundError) {
return res.status(404).json({
error: 'Resource not found'
})
}
// Log unexpected errors
logger.error('Unexpected error:', error)
res.status(500).json({ error: 'Internal server error' })
}
})
Error Logging
try {
await userRepo.update(userId, data)
} catch (error) {
logger.error('Failed to update user', {
userId,
data: { ...data, password: '[REDACTED]' }, // Don't log sensitive data
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
code: error instanceof DatabaseError ? error.code : undefined
})
throw error
}
JSON Serialization
All errors support JSON serialization for API responses:
const error = new UniqueConstraintError('users_email_unique', 'users', ['email'])
console.log(error.toJSON())
// {
// name: 'UniqueConstraintError',
// message: 'UNIQUE constraint violation on users',
// code: 'VALIDATION_UNIQUE_VIOLATION',
// constraint: 'users_email_unique',
// table: 'users',
// columns: ['email']
// }
Best Practices
1. Use Typed Errors
// Good: Specific error handling
if (error instanceof UniqueConstraintError) {
return { error: 'Email already exists' }
}
// Bad: Generic error handling
if (error.message.includes('duplicate')) {
return { error: 'Something is duplicate' }
}
2. Don't Swallow Errors
// Bad: Silent error swallowing
try {
await userRepo.delete(userId)
} catch (error) {
// Nothing here - dangerous!
}
// Good: Explicit handling
try {
await userRepo.delete(userId)
} catch (error) {
logger.warn('User deletion failed', { userId, error })
if (error instanceof DatabaseError) {
throw new ApplicationError('Failed to delete user')
}
throw error
}
3. Preserve Error Context
// Good: Wrap with context
try {
await processOrder(orderId)
} catch (error) {
throw new ApplicationError(`Failed to process order ${orderId}`, { cause: error })
}