npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@northscaler/error-support

v3.5.0

Published

Convenient base Error class and common subclasses, including message, code, info & cause.

Downloads

36

Readme

error-support

Convenient error classes and error class factories.

In addition to providing many common errors, this module provides a convenient function for creating Error subclasses that include additional properties

  • code to hold a programmatic symbol representing the error,
  • cause to hold the causing Error or Error[], and
  • info to hold any contextual information you may want to include.

Further, each error provides a toObject method that converts the error to a plain object literal and a toJson method that is guaranteed not to throw, because you don't want errors being thrown during your error handling.

Common error classes

This is a partial list of common error classes provided by this module:

  • AlreadyInitializedError
  • ClassNotExtendableError
  • IllegalArgumentError
  • IllegalArgumentTypeError
  • IllegalStateError
  • MethodNotImplementedError
  • MissingRequiredArgumentError
  • NotInitializedError

There may be more than these if this documentation isn't in sync with the code. Check the source for all errors provided by this module. All error classes can be found in ./errors.

Usage example of an error provided by this module:

const { IllegalArgumentError } = require('@northscaler/error-support')
throw new IllegalArgumentError({message: 'foobar', info: {sna: 'fu'}})

Error class factory

This folder contains a base error class, CodedError, upon which are built many other convenient error classes.

Here is an example of defining your own error classes using CodedError.

// in file SomethingWickedError.js:

const CodedError = require('@northscaler/error-support').CodedError

module.exports = CodedError({ code: 'E_SOMETHING_WICKED' })
// or module.exports = CodedError({ name: 'SomethingWickedError' })

NOTE: You must supply either code or name to the class factory. If you give name, the code will be generated as an upper-case snake format of the name with E_ prepended and the Error suffix removed, if present. If you give code, the name will be the leading-upper camel casing of the code, with the leading E_ removed, if present, and the suffix Error will be appended. For example, code E_FOOBAR causes the name to be FoobarError. Giving name BadnessError causes the code to be E_BADNESS. If you don't like these defaults, simply provide both name & code yourself.

You then use the class like this:

const SomethingWickedError = require('./SomethingWickedError')

function foobar() {
  try {
    // ...
  }
  catch (e) {
    throw new SomethingWickedError({
      message: 'boom',
      info: { some: 'contextual values here'},
      cause: e
    })
  }
}

function example() {
  try {
    foobar()
  } catch (e) {
    if (e.code === SomethingWickedError.CODE) { // 'E_SOMETHING_WICKED'
      // ...
    }
  }
}

Error subclass factory

You can also easily create subclasses of CodedError subclasses that were created from the CodedError class factory.

Here's how, using the SomethingWickedError class above:

// in file SomethingReallyWickedError.js:

const SomethingWickedError = require('./SomethingWickedError')

module.exports = SomethingWickedError.subclass({ code: 'E_SOMETHING_REALLY_WICKED' })

Now, any instance of SomethingReallyWicked is also an instanceof SomethingWicked.

Codes

Unfortunately, JavaScript's Error class only supports name (if you set it) & message to convey error information in a standard way. Folks haven't been exactly disciplined when it comes to the format of the message property.

A common solution to this is to subclass Error with one that supports a code property (among others, possibly). This is exactly what this library does, and more.

The code is guaranteed never to change, whereas the message can. Also, code can be anything you like, but we recommend strings like E_SOMETHING_BAD. Symbols or numbers aren't a bad idea, but Symbols don't toString() very well, and you always have to go look up a number to see what it means.

In Node.js, there is a well known issue that discusses this.

NOTE: never depend on the message property's content. Always use the code property in your error handling logic.

Messages

CodedError also provides for pretty well-formatted message properties, modeled somewhat after Node.js's message formats. By default, they don't include newlines or carriage returns, but provide as much detail of the error chain as possible as a simple string.

const BadError = CodedError({code: 'E_BAD'})

console.log(new BadError('foobar').message)
// 'E_BAD: foobar'

console.log(new BadError().message)
'E_BAD: NO_MESSAGE'

Causes

CodedError not only supports a code property, but also a cause property, which can be either an array or non-array. This provides for a cause chain, exactly in the same manner as Java's base java.lang.Exception class.

console.log(new BadError({message: 'this is bad', cause: new BadError('this is why')}).message)
// 'E_BAD: this is bad: E_BAD: this is why'

console.log(new BadError({message: 'this is bad', cause: new Error('this is why')}).message)
// 'E_BAD: this is bad: this is why'

console.log(new BadError({message: 'this is bad', cause: 13}).message)
// 'E_BAD: this is bad: 13'

Contextual information

CodedError also gives you a property, called info, to place arbitrary, contextual information that could be relevant to the error at hand.

new BadError({
  message: 'this is bad',
  info: {
    foo: 'bar',
    sna: { fu: 'goo' }
  }
})

Serializing

CodedError provides to convenient methods for converting itself to a POJO (plain, old JavaScript object).

toObject

Use the toObject method to convert the CodedError chain to a POJO. By default, the stack property is omitted transitively, but you can override that behavior via arguments to toObject.

console.log(new BadError({
  message: 'this is bad',
  info: {
    foo: 'bar',
    sna: { fu: 'goo' }
  }
}).toObject()
)

// returns:
// {
//   name: 'BadError',
//   code: 'E_BAD',
//   cause: undefined,
//   info: { foo: 'bar', sna: { fu: 'goo' } },
//   message: 'E_BAD: this is bad',
//   stack: null
// }

toJson

Since many folks log JSON to their log channels, CodedError has a convenient method that tries to JSON.stringify() itself. Note that this is not the same as JavaScript's toJSON protocol method.

new BadError({
  message: 'this is bad',
  info: {
    foo: 'bar',
    sna: { fu: 'goo' }
  }
}).toJson({ spaces: 2 }) // NOTE: this is NOT the same as toJSON!

// logs:
// {
//   "name": "BadError",
//   "code": "E_BAD",
//   "info": {
//     "foo": "bar",
//     "sna": {
//       "fu": "goo"
//     }
//   },
//   "message": "E_BAD: this is bad",
//   "stack": null
// }

NOTE: if you want to opt in to the toJSON protocol, simply have toJSON delegate to toObject.

Omitted properties in JSON

Notice how stack is omitted by default. A couple things about that:

  1. stack is omitted by default, because you usually only want stack traces in development, so the library makes a conservative choice here. Use your own configuration to decide what you'll be omitting in your system when logging.
  2. You can omit any properties recursively that you want. It's just that the default is ['stack'].
  3. Omitted properties are omitted all the way down the error chain, except in your info context objects. If properties need to be omitted in your info context objects, don't include them.
  4. When a property is omitted, the property name remains in the stringified object, but it's value is set to null, which is intended to express that the property was present but actively supressed.

Errors when handling errors

Sometimes, there could be circular references in the cause chain or any of the chain's info properties. Since you don't want your error handling to be throwing Errors when logging, toJson is guaranteed to always return valid JSON. If JSON.stringify worked, you'll get that result, but if it throws, you'll get a fallback string that is the JSON representation of the following, subject to your desired omissions:

{
  jsonStringifyError: {
    message: '...',
    name: '...',
    code: '...',
    stack: '...'
  },
  error: {
    message: '...',
    name: '...',
    code: '...',
    stack: '...'
  }
}

Here's an example.

const info = {}
info.circular = info // circular reference

console.log(new BadError({
  message: 'this is bad',
  info
}).toJson({ spaces: 2 }))

// logs:
// {
//   "jsonStringifyError": {
//     "message": "Converting circular structure to JSON",
//     "name": "TypeError",
//     "stack": null
//   },
//   "error": {
//     "message": "E_BAD: this is bad",
//     "code": "E_BAD",
//     "name": "BadError",
//     "stack": null
//   }
}