A comprehensive error handling system providing Result types for type-safe error handling, HTTP exceptions, model exceptions, and structured error management.
Installation
bunadd@stacksjs/error-handling
Basic Usage
import{ok,err,Result,handleError}from'@stacksjs/error-handling'// Return successfunctiondivide(a:number,b:number):Result<number,Error>{if(b===0){returnerr(newError('Division by zero'))}returnok(a/b)}// Handle resultconstresult=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'// Success resultconstsuccess:Result<number,Error>=ok(42)// Error resultconstfailure:Result<number,Error>=err(newError('Something went wrong'))// Type inferencefunctionfetchUser(id:number):Result<User,Error>{constuser=database.find(id)if(!user){returnerr(newError('User not found'))}returnok(user)}
Checking Results
constresult=fetchUser(1)// Using isOk/isErrif(result.isOk){console.log('User:',result.value)}else{console.log('Error:',result.error.message)}// Using match patternresult.match((user)=>console.log('Found user:',user.name),(error)=>console.log('Error:',error.message))
Unwrapping Results
// Unwrap (throws if error)constvalue=result.unwrap()// Throws if isErr// Unwrap with defaultconstvalue=result.unwrapOr(defaultUser)// Unwrap or else (lazy default)constvalue=result.unwrapOrElse(()=>createDefaultUser())// Expect (unwrap with custom error message)constvalue=result.expect('User should exist')
import{HttpError}from'@stacksjs/error-handling'// Throw HTTP errorthrownewHttpError(404,'User not found')thrownewHttpError(401,'Unauthorized')thrownewHttpError(403,'Forbidden')thrownewHttpError(500,'Internal server error')// With additional datathrownewHttpError(422,'Validation failed',{errors:{email:['Invalid email format'],password:['Password too short']}})
Common HTTP Errors
// 400 Bad RequestthrownewHttpError(400,'Invalid request body')// 401 UnauthorizedthrownewHttpError(401,'Authentication required')// 403 ForbiddenthrownewHttpError(403,'Insufficient permissions')// 404 Not FoundthrownewHttpError(404,'Resource not found')// 422 Unprocessable EntitythrownewHttpError(422,'Validation failed',{errors})// 429 Too Many RequeststhrownewHttpError(429,'Rate limit exceeded')// 500 Internal Server ErrorthrownewHttpError(500,'Something went wrong')// 503 Service UnavailablethrownewHttpError(503,'Service temporarily unavailable')
import{ModelNotFoundException}from'@stacksjs/error-handling'asyncfunctiongetUser(id:number){constuser=awaitUser.find(id)if(!user){thrownewModelNotFoundException('User',id)}returnuser}// Handlingtry{constuser=awaitgetUser(999)}catch(error){if(errorinstanceofModelNotFoundException){console.log(`${error.model} with ID ${error.id} not found`)// "User with ID 999 not found"}}
Validation Errors
import{HttpError}from'@stacksjs/error-handling'functionvalidateUser(data:any){consterrors: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){// `details` is the third argument, and it is serialized alongside the// message - which is exactly why it exists: a client can render inline// field errors instead of parsing them back out of `.message`.thrownewHttpError(422,'Validation failed',errors)}}
Error Handler
Basic Error Handling
import{handleError}from'@stacksjs/error-handling'try{awaitriskyOperation()}catch(error){handleError(error)// Logs error and continues}// With optionshandleError(error,{shouldExit:true,// Exit processsilent:false,// Show in consolemessage:'Custom error context'})
Error Processing
import{handleError}from'@stacksjs/error-handling'// Process and return formatted errorconstprocessed=handleError(error)// Returns Error object with proper messageconsole.log(processed.message)console.log(processed.stack)
Utility Functions
Creating Errors
import{handleError,err,ok}from'@stacksjs/error-handling'// From stringconsterror=handleError('Something went wrong')// From objectconsterror=handleError({code:'ERR_001',message:'Failed'})// From Errorconsterror=handleError(newError('Original error'))// From unknownfunctionprocessError(unknown:unknown){consterror=handleError(unknown)returnerror.message}
Safe Execution
// Execute function and catch errors as ResultfunctionsafeExecute<T>(fn:()=>T):Result<T,Error>{try{returnok(fn())}catch(error){returnerr(handleError(error))}}constresult=safeExecute(()=>JSON.parse(data))
Patterns
Railway-Oriented Programming
// Chain operations that can failfunctionprocessOrder(orderId:string):Result<Receipt,Error>{returnfindOrder(orderId).andThen(validateOrder).andThen(calculateTotal).andThen(processPayment).andThen(generateReceipt)}// Each function returns Result<T, Error>functionfindOrder(id:string):Result<Order,Error>{constorder=orders.get(id)returnorder?ok(order):err(newError('Order not found'))}functionvalidateOrder(order:Order):Result<Order,Error>{if(order.items.length===0){returnerr(newError('Order has no items'))}returnok(order)}
Error Accumulation
// Collect multiple errorsfunctionvalidateForm(data:FormData):Result<FormData,string[]>{consterrors: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')returnerrors.length>0?err(errors):ok(data)}
Fallback Chain
// Try multiple sourcesasyncfunctionfetchData():Promise<Result<Data,Error>>{constcacheResult=awaitfromPromise(cache.get('data'))if(cacheResult.isOk)returncacheResultconstdbResult=awaitfromPromise(database.query())if(dbResult.isOk){awaitcache.set('data',dbResult.value)returndbResult}returnerr(newError('All data sources failed'))}
Edge Cases
Handling Unknown Errors
try{awaitexternalApi.call()}catch(error){// Error could be anythingconstprocessed=handleError(error)if(errorinstanceofTypeError){// Handle type error}elseif(typeoferror==='string'){// Handle string error}elseif(errorinstanceofError){// Handle Error object}else{// Handle unknownlog.error('Unknown error type:',error)}}
// Generic function returning ResultasyncfunctionfetchResource<T>(url:string):Promise<Result<T,Error>>{constresponse=awaitfromPromise(fetch(url))if(response.isErr)returnresponseasunknownasResult<T,Error>constdata=awaitfromPromise(response.value.json())returndataasResult<T,Error>}// Usage with type inferenceconstresult=awaitfetchResource<User>('/api/user/1')if(result.isOk){constuser:User=result.value}