New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More โ†’
Socket
Sign inDemoInstall
Socket

modern-errors

Package Overview
Dependencies
Maintainers
1
Versions
42
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

modern-errors

Handle errors like it's 2023 ๐Ÿ”ฎ

  • 4.1.1
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
3.7K
decreased by-40.05%
Maintainers
1
Weekly downloads
ย 
Created
Source
modern-errors logo

Node Browsers TypeScript Codecov Minified size Twitter Medium

Handle errors like it's 2023 ๐Ÿ”ฎ

Error handling framework that is pluggable, minimalist yet featureful.

Features

  • โ›‘๏ธ Create error classes (including with custom logic)
  • ๐Ÿท๏ธ Set properties on individual errors or on all errors of the same class
  • ๐ŸŽ€ Wrap errors' message, class and properties
  • ๐Ÿšจ Normalize invalid errors (not an Error instance, missing stack, etc.)
  • ๐Ÿž Separate known and unknown errors
  • ๐Ÿค“ Strict TypeScript types
  • ๐Ÿ“– Based on standard JavaScript: throw, try/catch, new Error(), error.cause, instanceof, class, toJSON()

Plugins

Example

Create error classes.

import modernErrors from 'modern-errors'

// Base error class
export const AnyError = modernErrors()

export const UnknownError = AnyError.subclass('UnknownError')
export const InputError = AnyError.subclass('InputError')
export const AuthError = AnyError.subclass('AuthError')
export const DatabaseError = AnyError.subclass('DatabaseError')

Throw errors.

throw new InputError('Missing file path.')

Wrap errors.

try {
  // ...
} catch (cause) {
  throw new InputError('Could not read the file.', { cause })
}

Normalize errors.

try {
  throw 'Missing file path.'
} catch (error) {
  // Normalized from a string to an `Error` instance
  throw AnyError.normalize(error)
}

Use plugins.

import modernErrors from 'modern-errors'
import modernErrorsSerialize from 'modern-errors-serialize'

// Use a plugin to serialize errors as JSON
export const AnyError = modernErrors([modernErrorsSerialize])

// ...

// Serialize error as JSON, then back to identical error instance
const error = new InputError('Missing file path.')
const errorString = JSON.stringify(error)
const identicalError = AnyError.parse(JSON.parse(errorString))

Install

npm install modern-errors

If any plugin is used, it must also be installed.

npm install modern-errors-{pluginName}

This package works in both Node.js >=14.18.0 and browsers. It is an ES module and must be loaded using an import or import() statement, not require().

API

modernErrors(plugins?, options?)

plugins: Plugin[]?
options: object?

Creates and returns AnyError.

Options:

AnyError

Base error class.

AnyError.subclass(name, options?)

name: string
options: object?
Return value: class extends AnyError {}

Creates and returns an error subclass. The first one must be named UnknownError.

Subclasses can also call ErrorClass.subclass() themselves.

Options:

AnyError.normalize(anyException)

anyException: any
Return value: AnyError

Normalizes invalid errors and assigns the UnknownError class to unknown errors.

new AnyError(message, options?)

message: string
options: object?
Return value: AnyError

Options:

Usage

โ›‘๏ธ Error classes

Create error classes

// Base error class
export const AnyError = modernErrors()

// The first error class must be named "UnknownError"
export const UnknownError = AnyError.subclass('UnknownError')
export const InputError = AnyError.subclass('InputError')
export const AuthError = AnyError.subclass('AuthError')
export const DatabaseError = AnyError.subclass('DatabaseError')

Export error classes

Exporting and documenting error classes (including AnyError and UnknownError) allows consumers to check them. This also enables sharing error classes between modules.

Check error classes

// Known `InputError`
if (error instanceof InputError) {
  // ...
}

// Unknown error (from a specific library)
if (error instanceof UnknownError) {
  // ...
}

// Any error (from a specific library)
if (error instanceof AnyError) {
  // ...
}

๐Ÿท๏ธ Throw errors

Simple errors

throw new InputError('Missing file path.')

Error instance properties

const error = new InputError('...', { props: { isUserError: true } })
console.log(error.isUserError) // true

Error class properties

const InputError = AnyError.subclass('InputError', {
  props: { isUserError: true },
})
const error = new InputError('...')
console.log(error.isUserError) // true

Aggregate errors

The errors option aggregates multiple errors into one. This is like new AggregateError(errors) except that it works with any error class.

const databaseError = new DatabaseError('...')
const authError = new AuthError('...')
throw new InputError('...', { errors: [databaseError, authError] })
// InputError: ... {
//   [errors]: [
//     DatabaseError: ...
//     AuthError: ...
//   ]
// }

๐ŸŽ€ Wrap errors

Wrap inner error

Any error's message, class and options can be wrapped using the standard cause option.

Instead of being set as a cause property, the inner error is directly merged to the outer error, including its message, stack, name, AggregateError.errors and any additional property.

try {
  // ...
} catch (cause) {
  throw new InputError('Could not read the file.', { cause })
}

Wrap error message

The outer error message is appended, unless it is empty. If the outer error message ends with : or :\n, it is prepended instead.

const cause = new InputError('File does not exist.')
// InputError: File does not exist.
throw new InputError('', { cause })
// InputError: File does not exist.
// Could not read the file.
throw new InputError('Could not read the file.', { cause })
// InputError: Could not read the file: File does not exist.
throw new InputError(`Could not read the file:`, { cause })
// InputError: Could not read the file:
// File does not exist.
throw new InputError(`Could not read the file:\n`, { cause })

Wrap error class

The outer error's class replaces the inner one's, unless the outer error's class is AnyError.

try {
  throw new AuthError('...')
} catch (cause) {
  // Now an InputError
  throw new InputError('...', { cause })
}
try {
  throw new AuthError('...')
} catch (cause) {
  // Still an AuthError
  throw new AnyError('...', { cause })
}

Wrap error options

The outer error's options (props and plugin options) replace the inner one's. If the outer error's class is AnyError, those are merged instead.

try {
  throw new AuthError('...', innerOptions)
} catch (cause) {
  // Options are now `outerOptions`. `innerOptions` are discarded.
  throw new InputError('...', { ...outerOptions, cause })
}
try {
  throw new AuthError('...', innerOptions)
} catch (cause) {
  // `outerOptions` are merged with `innerOptions`
  throw new AnyError('...', { ...outerOptions, cause })
}

๐Ÿšจ Normalize errors

Wrapped errors

Any error can be directly passed to the cause option, even if it is invalid, unknown or not normalized.

try {
  // ...
} catch (cause) {
  throw new InputError('...', { cause })
}

Invalid errors

Manipulating errors that are not Error instances or that have invalid properties can lead to unexpected bugs. AnyError.normalize() fixes that.

try {
  throw 'Missing file path.'
} catch (invalidError) {
  // This fails: `invalidError.message` is `undefined`
  console.log(invalidError.message.trim())
}
try {
  throw 'Missing file path.'
} catch (invalidError) {
  const normalizedError = AnyError.normalize(invalidError)
  // This works: 'Missing file path.'
  // `normalizedError` is an `Error` instance.
  console.log(normalizedError.message.trim())
}

Top-level error handler

Wrapping a module's main functions with AnyError.normalize() ensures every error being thrown is valid, applies plugins, and has a class that is either known or UnknownError.

export const main = function () {
  try {
    // ...
  } catch (error) {
    throw AnyError.normalize(error)
  }
}

๐Ÿž Unknown errors

Normalizing unknown errors

An error is unknown if its class was not created by AnyError.subclass(). This indicates an unexpected exception, usually a bug. AnyError.normalize() assigns the UnknownError class to any unknown error.

try {
  return regExp.test(value)
} catch (unknownError) {
  // Now an `UnknownError` instance
  throw AnyError.normalize(unknownError)
}

Handling unknown errors

Unknown errors should be handled in a try {} catch {} block and wrapped with a known class instead. That block should only cover the statement that might throw in order to prevent catching other unrelated unknown errors.

try {
  return regExp.test(value)
} catch (unknownError) {
  // Now an `InputError` instance
  throw new InputError('Invalid regular expression:', { cause: unknownError })
}

Using plugins with unknown errors

AnyError.normalize() is required for unknown errors to use plugins.

try {
  return regExp.test(value)
} catch (unknownError) {
  unknownError.examplePluginMethod() // This throws

  const normalizedError = AnyError.normalize(unknownError)
  normalizedError.examplePluginMethod() // This works
}

๐Ÿ”ง Custom logic

Class custom logic

The custom option can be used to provide an error class with additional methods, constructor or properties.

export const InputError = AnyError.subclass('InputError', {
  // The `class` must extend from `AnyError`
  custom: class extends AnyError {
    // If a `constructor` is defined, its parameters must be (message, options)
    // like `AnyError`
    constructor(message, options) {
      // Modifying `message` or `options` should be done before `super()`
      message += message.endsWith('.') ? '' : '.'

      // All arguments should be forwarded to `super()`, including any
      // custom `options` or additional `constructor` parameters
      super(message, options)

      // `name` is automatically added, so this is not necessary
      // this.name = 'InputError'
    }

    isUserInput() {
      // ...
    }
  },
})

const error = new InputError('Wrong user name')
console.log(error.message) // 'Wrong user name.'
console.log(error.isUserInput())

Shared custom logic

ErrorClass.subclass() can be used to share logic between error classes.

const SharedError = AnyError.subclass('SharedError', {
  custom: class extends AnyError {
    // ...
  },
})

export const InputError = SharedError.subclass('InputError')
export const AuthError = SharedError.subclass('AuthError')

๐Ÿ”Œ Plugins

List of plugins

Plugins extend modern-errors features. All available plugins are listed here.

Adding plugins

To use a plugin, please install it, then pass it to modernErrors()'s first argument.

npm install modern-errors-{pluginName}
import modernErrors from 'modern-errors'

import modernErrorsBugs from 'modern-errors-bugs'
import modernErrorsSerialize from 'modern-errors-serialize'

export const AnyError = modernErrors([modernErrorsBugs, modernErrorsSerialize])
// ...

Plugin options

Most plugins can be configured with options. The option's name is the same as the plugin.

const options = {
  // `modern-errors-bugs` options
  bugs: 'https://github.com/my-name/my-project/issues',
  // `props` can be configured and modified like plugin options
  props: { userId: 5 },
}

Plugin options can apply to (in priority order):

export const AnyError = modernErrors(plugins, options)
export const SharedError = AnyError.subclass('SharedError', options)

export const InputError = SharedError.subclass('InputError')
export const AuthError = SharedError.subclass('AuthError')
export const InputError = AnyError.subclass('InputError', options)
  • A specific error: second argument to the error's constructor
throw new InputError('...', options)
  • A plugin method call: last argument, passing only that plugin's options
AnyError[methodName](...args, options[pluginName])
error[methodName](...args, options[pluginName])

Custom plugins

Please see the following documentation to create your own plugin.

๐Ÿค“ TypeScript

Please see the following documentation for information about TypeScript types.

Modules

This framework brings together a collection of modules which can also be used individually:

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 08 Nov 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