Error Handling Package
A comprehensive error handling system providing Result types for type-safe error handling, HTTP exceptions, model exceptions, and structured error management.
Installation
bun add @stacksjs/error-handling
Basic Usage
import { ok, err, Result, handleError } from '@stacksjs/error-handling'
function divide(a: number, b: number): Result<number, Error> {
if (b === 0) {
return err(new Error('Division by zero'))
}
return ok(a / b)
}
const result = divide(10, 2)
if (result.isOk) {
console.log('Result:', result.value)
} else {
console.log('Error:', result.error.message)
}
Result Types
Creating Results
import { ok, err, Result } from '@stacksjs/error-handling'
const success: Result<number, Error> = ok(42)
const failure: Result<number, Error> = err(new Error('Something went wrong'))
function fetchUser(id: number): Result<User, Error> {
const user = database.find(id)
if (!user) {
return err(new Error('User not found'))
}
return ok(user)
}
Checking Results
const result = fetchUser(1)
if (result.isOk) {
console.log('User:', result.value)
} else {
console.log('Error:', result.error.message)
}
result.match(
(user) => console.log('Found user:', user.name),
(error) => console.log('Error:', error.message)
)
Unwrapping Results
const value = result.unwrap()
const value = result.unwrapOr(defaultUser)
const value = result.unwrapOrElse(() => createDefaultUser())
const value = result.expect('User should exist')
const nameResult = userResult.map(user => user.name)
const friendlyError = userResult.mapErr(err =>
new Error(`User lookup failed: ${err.message}`)
)
const profileResult = userResult.andThen(user =>
fetchProfile(user.id)
)
const result = fetchUser(1)
.andThen(user => fetchProfile(user.id))
.map(profile => profile.avatar)
.mapErr(err => new Error(`Failed: ${err.message}`))
Async Result
Promise Integration
import { fromPromise, ResultAsync } from '@stacksjs/error-handling'
const result = await fromPromise(
fetch('/api/user/1'),
(error) => new Error(`Fetch failed: ${error}`)
)
if (result.isOk) {
const response = result.value
}
Async Operations
const result = await fromPromise(fetch('/api/user/1'))
.andThen(response => fromPromise(response.json()))
.map(data => data.user)
const [user, posts] = await Promise.all([
fromPromise(fetchUser(1)),
fromPromise(fetchPosts(1))
])
HTTP Errors
HTTP Error Class
import { HttpError } from '@stacksjs/error-handling'
throw new HttpError(404, 'User not found')
throw new HttpError(401, 'Unauthorized')
throw new HttpError(403, 'Forbidden')
throw new HttpError(500, 'Internal server error')
throw new HttpError(422, 'Validation failed', {
errors: {
email: ['Invalid email format'],
password: ['Password too short']
}
})
Common HTTP Errors
throw new HttpError(400, 'Invalid request body')
throw new HttpError(401, 'Authentication required')
throw new HttpError(403, 'Insufficient permissions')
throw new HttpError(404, 'Resource not found')
throw new HttpError(422, 'Validation failed', { errors })
throw new HttpError(429, 'Rate limit exceeded')
throw new HttpError(500, 'Something went wrong')
throw new HttpError(503, 'Service temporarily unavailable')
Handling HTTP Errors
try {
await performAction()
} catch (error) {
if (error instanceof HttpError) {
return Response.json(
{ error: error.message, ...error.data },
{ status: error.statusCode }
)
}
throw error
}
Model Errors
Model Not Found
import { ModelNotFoundException } from '@stacksjs/error-handling'
async function getUser(id: number) {
const user = await User.find(id)
if (!user) {
throw new ModelNotFoundException('User', id)
}
return user
}
try {
const user = await getUser(999)
} catch (error) {
if (error instanceof ModelNotFoundException) {
console.log(`${error.model} with ID ${error.id} not found`)
}
}
Validation Errors
import { ValidationException } from '@stacksjs/error-handling'
function validateUser(data: any) {
const errors: Record<string, string[]> = {}
if (!data.email) {
errors.email = ['Email is required']
}
if (!data.password || data.password.length < 8) {
errors.password = ['Password must be at least 8 characters']
}
if (Object.keys(errors).length > 0) {
throw new ValidationException(errors)
}
}
Error Handler
Basic Error Handling
import { handleError } from '@stacksjs/error-handling'
try {
await riskyOperation()
} catch (error) {
handleError(error)
}
handleError(error, {
shouldExit: true,
silent: false,
message: 'Custom error context'
})
Error Processing
import { handleError } from '@stacksjs/error-handling'
const processed = handleError(error)
console.log(processed.message)
console.log(processed.stack)
Utility Functions
Creating Errors
import { handleError, err, ok } from '@stacksjs/error-handling'
const error = handleError('Something went wrong')
const error = handleError({ code: 'ERR_001', message: 'Failed' })
const error = handleError(new Error('Original error'))
function processError(unknown: unknown) {
const error = handleError(unknown)
return error.message
}
Safe Execution
function safeExecute<T>(fn: () => T): Result<T, Error> {
try {
return ok(fn())
} catch (error) {
return err(handleError(error))
}
}
const result = safeExecute(() => JSON.parse(data))
Patterns
Railway-Oriented Programming
function processOrder(orderId: string): Result<Receipt, Error> {
return findOrder(orderId)
.andThen(validateOrder)
.andThen(calculateTotal)
.andThen(processPayment)
.andThen(generateReceipt)
}
function findOrder(id: string): Result<Order, Error> {
const order = orders.get(id)
return order ? ok(order) : err(new Error('Order not found'))
}
function validateOrder(order: Order): Result<Order, Error> {
if (order.items.length === 0) {
return err(new Error('Order has no items'))
}
return ok(order)
}
Error Accumulation
function validateForm(data: FormData): Result<FormData, string[]> {
const errors: string[] = []
if (!data.name) errors.push('Name is required')
if (!data.email) errors.push('Email is required')
if (!data.password) errors.push('Password is required')
return errors.length > 0 ? err(errors) : ok(data)
}
Fallback Chain
async function fetchData(): Promise<Result<Data, Error>> {
const cacheResult = await fromPromise(cache.get('data'))
if (cacheResult.isOk) return cacheResult
const dbResult = await fromPromise(database.query())
if (dbResult.isOk) {
await cache.set('data', dbResult.value)
return dbResult
}
return err(new Error('All data sources failed'))
}
Edge Cases
Handling Unknown Errors
try {
await externalApi.call()
} catch (error) {
const processed = handleError(error)
if (error instanceof TypeError) {
} else if (typeof error === 'string') {
} else if (error instanceof Error) {
} else {
log.error('Unknown error type:', error)
}
}
Nested Errors
function wrapError(original: Error, context: string): Error {
const wrapped = new Error(`${context}: ${original.message}`)
wrapped.cause = original
return wrapped
}
try {
await database.query()
} catch (error) {
throw wrapError(error, 'Database query failed')
}
Timeout Handling
import { fromPromise } from '@stacksjs/error-handling'
async function fetchWithTimeout(url: string, ms: number) {
const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), ms)
const result = await fromPromise(
fetch(url, { signal: controller.signal }),
(error) => {
if (error.name === 'AbortError') {
return new Error('Request timed out')
}
return error
}
)
clearTimeout(timeout)
return result
}
Retrying Operations
async function withRetry<T>(
fn: () => Promise<Result<T, Error>>,
maxAttempts: number = 3
): Promise<Result<T, Error>> {
let lastError: Error
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
const result = await fn()
if (result.isOk) return result
lastError = result.error
await sleep(attempt * 1000)
}
return err(new Error(`Failed after ${maxAttempts} attempts: ${lastError.message}`))
}
const result = await withRetry(() => fetchUser(id))
TypeScript Integration
Type-Safe Error Handling
type ApiError =
| { type: 'network'; message: string }
| { type: 'validation'; errors: string[] }
| { type: 'auth'; message: string }
function handleApiError(error: ApiError) {
switch (error.type) {
case 'network':
return `Network error: ${error.message}`
case 'validation':
return `Validation errors: ${error.errors.join(', ')}`
case 'auth':
return `Authentication error: ${error.message}`
}
}
Generic Result Types
async function fetchResource<T>(url: string): Promise<Result<T, Error>> {
const response = await fromPromise(fetch(url))
if (response.isErr) return response as unknown as Result<T, Error>
const data = await fromPromise(response.value.json())
return data as Result<T, Error>
}
const result = await fetchResource<User>('/api/user/1')
if (result.isOk) {
const user: User = result.value
}
API Reference
Result Type
| Property/Method | Description |
isOk | True if success |
isErr | True if error |
value | Success value (if isOk) |
error | Error value (if isErr) |
unwrap() | Get value or throw |
unwrapOr(default) | Get value or default |
unwrapOrElse(fn) | Get value or compute |
expect(msg) | Get value or throw with message |
map(fn) | Transform value |
mapErr(fn) | Transform error |
andThen(fn) | Chain Result operations |
match(ok, err) | Pattern match |
Factory Functions
| Function | Description |
ok(value) | Create success Result |
err(error) | Create error Result |
fromPromise(promise, errFn?) | Convert Promise to Result |
Error Classes
| Class | Description |
HttpError | HTTP status error |
ModelNotFoundException | Model not found |
ValidationException | Validation errors |
Utility Functions
| Function | Description |
handleError(err, opts?) | Process any error |