Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

merge-error-cause

Package Overview
Dependencies
Maintainers
1
Versions
27
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

merge-error-cause

Merge an error with its inner cause

  • 1.0.0
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
6.4K
decreased by-7.17%
Maintainers
1
Weekly downloads
 
Created
Source

Codecov Build Node Twitter Medium

Merge an error with its cause.

This merges error.cause recursively with its parent error, including its message, stack, name and errors.

Example

import mergeErrorCause from 'merge-error-cause'

const main = function (userId) {
  try {
    return createUser(userId)
  } catch (error) {
    throw mergeErrorCause(error)
    // Printed as:
    //   TypeError: Invalid user id: false
    //   Could not create user.
  }
}

const createUser = function (userId) {
  try {
    validateUserId(userId)
    return sendDatabaseRequest('create', userId)
  } catch (cause) {
    throw new Error('Could not create user.', { cause })
  }
}

const validateUserId = function (userId) {
  if (typeof userId !== 'string') {
    throw new TypeError(`Invalid user id: ${userId}.`)
  }
}

main(false)

Install

npm install merge-error-cause

This package is an ES module and must be loaded using an import or import() statement, not require().

API

mergeErrorCause(error)

error any
Return value: Error

This never throws.

If error is an Error instance and has a cause, error is modified then returned. Otherwise, a new error is created and returned.

Background

error.cause is a recent JavaScript feature to wrap error messages and properties.

try {
  validateUserId(userId)
  sendDatabaseRequest('create', userId)
} catch (cause) {
  throw new Error('Could not create user.', { cause })
}

However, it comes with a few issues.

Traversing error.cause

Problem

Consumers need to traverse error.cause.

try {
  createUser(userId)
} catch (error) {
  if (error.code === 'E101' || (error.cause && error.cause.code === 'E101')) {
    // Checking for properties requires traversing `error.cause`
  }

  if (
    error.name === 'UserError' ||
    (error.cause && error.cause.name === 'UserError')
  ) {
    // So does checking for error type
  }
}

This is tricky to get right. For example:

  • error.cause.cause might also exist (and so on)
  • If error is not an Error instance, error.name will throw
  • Recursing over error.cause might be infinite

Solution

This library merges error.cause recursively. It also ensures error is an Error instance. Consumers can then handle errors without checking its cause.

try {
  createUser(userId)
} catch (error) {
  if (error.code === 'E101') {
  }

  if (error.name === 'UserError') {
  }
}

Verbose stack trace

Problem

Stack traces with multiple error.cause can be quite verbose.

Error: Could not create user group.
    at createUserGroup (/home/user/app/user_group.js:19:9)
    at createGroups (/home/user/app/user_group.js:101:10)
    at startApp (/home/user/app/app.js:35:20)
    at main (/home/user/app/app.js:3:4) {
  [cause]: Error: Could not create user.
      at newUser (/home/user/app/user.js:52:7)
      at createUser (/home/user/app/user.js:43:5)
      at createUserGroup (/home/user/app/user_group.js:17:11)
      at createGroups (/home/user/app/user_group.js:101:10)
      at startApp (/home/user/app/app.js:35:20)
      at main (/home/user/app/app.js:3:4) {
    [cause]: Error: Invalid user.
        at validateUser (/home/user/app/user.js:159:8)
        at userInstance (/home/user/app/user.js:20:4)
        at newUser (/home/user/app/user.js:50:7)
        at createUser (/home/user/app/user.js:43:5)
        at createUserGroup (/home/user/app/user_group.js:17:11)
        at createGroups (/home/user/app/user_group.js:101:10)
        at startApp (/home/user/app/app.js:35:20)
        at main (/home/user/app/app.js:3:4) {
      [cause]: UserError: User "15" does not exist.
          at checkUserId (/home/user/app/user.js:195:3)
          at checkUserExist (/home/user/app/user.js:170:10)
          at validateUser (/home/user/app/user.js:157:23)
          at userInstance (/home/user/app/user.js:20:4)
          at newUser (/home/user/app/user.js:50:7)
          at createUser (/home/user/app/user.js:43:5)
          at createUserGroup (/home/user/app/user_group.js:17:11)
          at createGroups (/home/user/app/user_group.js:101:10)
          at startApp (/home/user/app/app.js:35:20)
          at main (/home/user/app/app.js:3:4)
    }
  }
}

Each error cause is indented and printed separately.

  • The stack traces mostly repeat each other since the function calls are part of the same line execution
  • The most relevant message (innermost) is harder to find since it is shown last

Solution

This library only keeps the innermost stack trace. Error messages are concatenated by default from innermost to outermost. This results in much simpler stack traces without losing any information.

UserError: User "15" does not exist.
Invalid user.
Could not create user.
Could not create user group.
    at checkUserId (/home/user/app/user.js:195:3)
    at checkUserExist (/home/user/app/user.js:170:10)
    at validateUser (/home/user/app/user.js:157:23)
    at userInstance (/home/user/app/user.js:20:4)
    at newUser (/home/user/app/user.js:50:7)
    at createUser (/home/user/app/user.js:43:5)
    at createUserGroup (/home/user/app/user_group.js:17:11)
    at createGroups (/home/user/app/user_group.js:101:10)
    at startApp (/home/user/app/app.js:35:20)
    at main (/home/user/app/app.js:3:4)

Features

Stack traces

Only the innermost stack trace is kept.

Please make sure you use async/await instead of new Promise() or callbacks to prevent truncated stack traces.

Messages

Child error messages are printed first.

try {
  throw new Error('Invalid user id.')
} catch (cause) {
  throw new Error('Could not create user.', { cause })
  // Printed as:
  //   Error: Invalid user id.
  //   Could not create user.
}

If the parent error message ends with :, it is prepended instead.

try {
  throw new Error('Invalid user id.')
} catch (cause) {
  throw new Error('Could not create user:', { cause })
  // Printed as:
  //   Error: Could not create user: Invalid user id.
}

: can optionally be followed by a newline.

try {
  throw new Error('Invalid user id.')
} catch (cause) {
  throw new Error('Could not create user:\n', { cause })
  // Printed as:
  //   Error: Could not create user:
  //   Invalid user id.
}

Error type

By default, the parent error type is used.

try {
  throw new TypeError('User id is not a string.')
} catch (cause) {
  const error = new UserError('Could not create user.', { cause })
  const mergedError = mergeErrorCause(error)
  console.log(mergedError instanceof UserError) // true
  console.log(mergedError.name) // 'UserError'
}

If the parent error type is Error or AggregateError, the child type is used instead. This allows wrapping the error message or properties while keeping its type.

try {
  throw new TypeError('User id is not a string.')
} catch (cause) {
  const error = new Error('Could not create user.', { cause })
  console.log(mergeErrorCause(error) instanceof TypeError) // true
}

The error instance is created with new ErrorType(message). If this throws, new Error(message) is used instead.

Error properties

Error properties are shallowly merged.

try {
  const error = new Error('Invalid user id.')
  // This is kept
  error.userId = '5'
  throw error
} catch (cause) {
  const parentError = new Error('Could not create user.', { cause })
  // This is kept too
  parentError.invalidUser = true
  throw parentError
}

Empty error messages are ignored. This is useful when wrapping error properties.

try {
  throw new Error('Invalid user id.')
} catch (cause) {
  const parentError = new Error('', { cause })
  parentError.invalidUser = true
  throw parentError
}

Aggregate errors

Any aggregateError.errors[*].cause is processed recursively. However, aggregateError.errors are not merged with each other since those are different from each other.

If both error.errors and error.cause.errors exist, they are concatenated.

Normalization

Invalid errors are normalized to proper Error instances.

try {
  throw 'Invalid user id.'
} catch (error) {
  console.log(mergeErrorCause(error)) // Error: Invalid user id.
}

Support

For any question, don't hesitate to submit an issue on GitHub.

Everyone is welcome regardless of personal background. We enforce a Code of conduct in order to promote a positive and inclusive environment.

Contributing

This project was made with ❤️. The simplest way to give back is by starring and sharing it online.

If the documentation is unclear or has a typo, please click on the page's Edit button (pencil icon) and suggest a correction.

If you would like to help us fix a bug or add a new feature, please check our guidelines. Pull requests are welcome!

Keywords

FAQs

Package last updated on 10 Jun 2022

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc