npm.io
1.0.0 • Published 5 months ago

errs

Licence
MIT
Version
1.0.0
Deps
0
Size
35 kB
Vulns
0
Weekly
0
Stars
75

errs

Simple error creation, boundaries, and formatting utilities for modern Node.js applications.

Features

  • Simple Error Creation - Create errors with custom properties in one line
  • Error Boundaries - Collect errors without immediate throwing
  • Type Guards - Runtime type checking for errors with TypeScript support
  • Result Pattern - Rust-style tryCatch for exception-free error handling
  • Multi-Format Output - Format errors for terminal, JSON, or HTML
  • Native Cause Chains - Built-in support for ES2022 Error.cause
  • Error Type Registration - Register and reuse custom error types
  • Zero Dependencies - No runtime dependencies

Installation

npm install errs

Quick Start

import errs from 'errs'

// Create errors with custom properties
const err = errs.create({
  message: 'User not found',
  status: 404,
  code: 'USER_NOT_FOUND'
})

// Collect multiple errors without throwing
const boundary = errs.boundary()
boundary.add(new Error('Validation failed'))
boundary.add(new Error('Invalid email'))
if (boundary.hasErrors()) {
  boundary.throwIfErrors()
}

// Format errors for different outputs
console.log(errs.format(err, { format: 'terminal' }))

API Reference

Creating Errors
errs.create(opts)

Creates a new error with custom properties.

// From a string
const err = errs.create('Something went wrong')

// From an object
const err = errs.create({
  message: 'Database connection failed',
  status: 500,
  code: 'DB_ERROR'
})

// From an array (joined with spaces)
const err = errs.create(['Invalid', 'request', 'parameters'])

// From a function
const err = errs.create(() => ({
  message: 'Dynamic error',
  timestamp: Date.now()
}))
errs.create(type, opts)

Creates an error of a registered type.

class ValidationError extends Error {
  name = 'ValidationError'
}

errs.register('validation', ValidationError)

const err = errs.create('validation', {
  message: 'Invalid input',
  field: 'email'
})
Merging Errors
errs.merge(error, opts)

Merges an existing error with new properties and sets native cause chain.

try {
  await fetchData()
} catch (original) {
  throw errs.merge(original, {
    message: 'Failed to load user data',
    userId: 123
  })
  // merged.cause === original
}
Error Boundaries
errs.boundary(options)

Creates an ErrorBoundary for collecting errors without immediate throwing.

const boundary = errs.boundary({
  maxErrors: 100,      // Maximum errors to collect (default: 1000)
  captureStack: true   // Capture creation point (default: true)
})

// Collect errors
try {
  validateEmail(data.email)
} catch (e) {
  boundary.add(e)
}

try {
  validateAge(data.age)
} catch (e) {
  boundary.add(e)
}

// Check and throw if errors exist
if (boundary.hasErrors()) {
  console.log(`Collected ${boundary.count} errors`)
  boundary.throwIfErrors('Validation failed')
}

// Or get as AggregateError
const aggregate = boundary.toAggregateError()

// Clear and reuse
boundary.clear()
ErrorBoundary Methods
  • add(error) - Add an error to the boundary
  • hasErrors() - Check if any errors collected
  • count - Get number of errors
  • errors - Get copy of all errors
  • toAggregateError(message) - Convert to AggregateError
  • throwIfErrors(message) - Throw if errors exist
  • clear() - Remove all errors
  • trap(fn) - Execute sync function, trap errors to boundary
  • trapAsync(fn) - Execute async function, trap errors to boundary
boundary.trap(fn) / boundary.trapAsync(fn)

Execute functions and automatically collect errors without throwing.

const boundary = errs.boundary()

// Sync operations
const result = boundary.trap(() => JSON.parse(userInput))
if (result.ok) {
  console.log('Parsed:', result.value)
} else {
  console.log('Parse failed, error collected')
}

// Async operations
const fetched = await boundary.trapAsync(() => fetch(url))
if (fetched.ok) {
  const data = await fetched.value.json()
}

// Process multiple and handle all errors at once
items.forEach(item => boundary.trap(() => processItem(item)))
boundary.throwIfErrors('Batch processing failed')
Type Guards
errs.isErrorType(error, ErrorClass)

Type guard that checks if an error is an instance of a specific class.

try {
  await fetchData()
} catch (err) {
  if (errs.isErrorType(err, TypeError)) {
    // TypeScript knows err is TypeError here
    console.log('Type error occurred')
  }
}
errs.isRegisteredType(error, typeName)

Check if an error matches a registered type name.

errs.register('validation', ValidationError)

if (errs.isRegisteredType(err, 'validation')) {
  // Handle validation error
}
errs.assertErrorType(error, ErrorClass, message?)

Assert error is a specific type, throws TypeError if not.

function handleHttpError(err) {
  // Throws TypeError if err is not HttpError
  const httpErr = errs.assertErrorType(err, HttpError)
  return httpErr.status // TypeScript knows this is HttpError
}
Result Pattern
errs.tryCatch(fn)

Wraps a sync function to return a Result instead of throwing.

const result = errs.tryCatch(() => JSON.parse(input))
if (result.ok) {
  console.log(result.value)
} else {
  console.log('Parse error:', result.error.message)
}
errs.tryCatchAsync(fn)

Wraps an async function to return a Result instead of throwing.

const result = await errs.tryCatchAsync(() => fetch(url))
if (result.ok) {
  const data = await result.value.json()
} else {
  console.log('Fetch failed:', result.error.message)
}
Parallel Operations
errs.parallel(promises, options)

Run multiple promises and collect all errors.

const { results, boundary } = await errs.parallel([
  fetchUser(1),
  fetchUser(2),
  fetchUser(3)
])

// Get successful values
const users = results
  .filter(r => r.status === 'fulfilled')
  .map(r => r.value)

// Check for errors
if (boundary.hasErrors()) {
  console.error(`${boundary.count} requests failed`)
}
Error Formatting
errs.format(error, options)

Format errors for different output targets.

// Terminal output with colors
console.log(errs.format(error, { format: 'terminal', colors: true }))

// JSON for logging
logger.error(errs.format(error, { format: 'json', indent: 2 }))

// HTML for error pages
res.send(errs.format(error, { format: 'html' }))

Terminal Output:

╭─ TypeError: Cannot read property 'x' of undefined
│
│  at processUser (src/users.js:42:15)
│  at async main (src/index.js:10:3)
│
├─ Caused by: NetworkError: Connection refused
│
│  at fetch (src/api.js:23:9)
│
╰─ 2 errors in chain
Error Handling
errs.handle(error, callback, stream)

Unified error handling for callbacks and EventEmitters.

// With callback
errs.handle(err, (e) => console.error(e))

// With EventEmitter
errs.handle(err, emitter)

// With both
errs.handle(err, callback, emitter)

// Returns emitter if no callback
const emitter = errs.handle(err)
emitter.on('error', handleError)
Type Registration
errs.register(type, ErrorClass)

Register a custom error type.

class HttpError extends Error {
  name = 'HttpError'
  constructor(message, status) {
    super(message)
    this.status = status
  }
}

errs.register('http', HttpError)

// Use registered type
const err = errs.create('http', {
  message: 'Not found',
  status: 404
})
errs.unregister(type)

Unregister an error type.

errs.unregister('http')
JSON Conversion
errs.toJSON(error)

Convert error to JSON-serializable object.

const err = new Error('Test error')
err.code = 'TEST_CODE'

const json = errs.toJSON(err)
// { message: 'Test error', stack: '...', code: 'TEST_CODE' }

Named Exports

All functions are available as named exports:

import {
  create,
  boundary,
  format,
  parallel,
  isErrorType,
  isRegisteredType,
  assertErrorType,
  tryCatch,
  tryCatchAsync
} from 'errs'

const err = create('Error message')
const bound = boundary()
const result = tryCatch(() => JSON.parse(data))

Subpath Exports

Import specific modules directly:

import { ErrorBoundary } from 'errs/boundary'
import { format } from 'errs/format'

Use Cases

Form Validation
const boundary = errs.boundary()

if (!email.includes('@')) {
  boundary.add(new Error('Invalid email'))
}
if (password.length < 8) {
  boundary.add(new Error('Password too short'))
}

boundary.throwIfErrors('Validation failed')
Batch Processing
const boundary = errs.boundary()

for (const item of items) {
  try {
    await processItem(item)
  } catch (err) {
    boundary.add(err)
  }
}

if (boundary.hasErrors()) {
  console.error(`${boundary.count} items failed`)
  await logErrors(boundary.errors)
}
API Error Formatting
app.use((err, req, res, next) => {
  res.status(err.status || 500).send(
    errs.format(err, { format: 'json' })
  )
})

Requirements

  • Node.js >= 20.0.0

Migration from v0.x

See MIGRATION.md for detailed migration guide.

License

MIT

Contributors

Keywords