Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/ehmicky/modern-errors
Handle errors in a simple, stable, consistent way
https://github.com/ehmicky/modern-errors
browser cause code-quality error error-classes error-handler error-handling error-monitoring error-reporting errors exceptions framework javascript library message monitoring nodejs plugins stacktrace typescript
Last synced: 7 days ago
JSON representation
Handle errors in a simple, stable, consistent way
- Host: GitHub
- URL: https://github.com/ehmicky/modern-errors
- Owner: ehmicky
- License: mit
- Created: 2022-06-10T16:10:06.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2024-10-28T18:27:16.000Z (8 days ago)
- Last Synced: 2024-10-28T19:38:05.866Z (8 days ago)
- Topics: browser, cause, code-quality, error, error-classes, error-handler, error-handling, error-monitoring, error-reporting, errors, exceptions, framework, javascript, library, message, monitoring, nodejs, plugins, stacktrace, typescript
- Language: JavaScript
- Homepage:
- Size: 7.68 MB
- Stars: 1,466
- Watchers: 8
- Forks: 18
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
- awesome-web-cn - modern-errors - 一个错误处理的库,让你使用简单,统一,稳定的方式 handle error (Uncategorized / Uncategorized)
README
[![Node](https://img.shields.io/badge/-Node.js-808080?logo=node.js&colorA=404040&logoColor=66cc33)](https://www.npmjs.com/package/modern-errors)
[![Browsers](https://img.shields.io/badge/-Browsers-808080?logo=firefox&colorA=404040)](https://unpkg.com/modern-errors?module)
[![TypeScript](https://img.shields.io/badge/-Typed-808080?logo=typescript&colorA=404040&logoColor=0096ff)](/src/main.d.ts)
[![Codecov](https://img.shields.io/badge/-Tested%20100%25-808080?logo=codecov&colorA=404040)](https://codecov.io/gh/ehmicky/modern-errors)
[![Minified size](https://img.shields.io/bundlephobia/minzip/modern-errors?label&colorA=404040&colorB=808080&logo=webpack)](https://bundlephobia.com/package/modern-errors)
[![Mastodon](https://img.shields.io/badge/-Mastodon-808080.svg?logo=mastodon&colorA=404040&logoColor=9590F9)](https://fosstodon.org/@ehmicky)
[![Medium](https://img.shields.io/badge/-Medium-808080.svg?logo=medium&colorA=404040)](https://medium.com/@ehmicky)Handle errors in a simple, stable, consistent way.
# Hire me
Please
[reach out](https://www.linkedin.com/feed/update/urn:li:activity:7117265228068716545/)
if you're looking for a Node.js API or CLI engineer (11 years of experience).
Most recently I have been [Netlify Build](https://github.com/netlify/build)'s
and [Netlify Plugins](https://www.netlify.com/products/build/plugins/)'
technical lead for 2.5 years. I am available for full-time remote positions.# Features
Simple patterns to:
- ⛑️ Create error [classes](#create-error-classes)
- 🏷️ Set error [properties](#%EF%B8%8F-error-properties)
- 🎀 [Wrap](#-wrap-errors) or [aggregate](#aggregate-errors) errors
- 🐞 Separate known and [unknown](#-unknown-errors) errorsStability:
- 🚨 [Normalize](#-normalize-errors) invalid errors
- 🛡️ 100% [test coverage](https://app.codecov.io/gh/ehmicky/modern-errors)
- 🤓 Strict [TypeScript types](docs/typescript.md)# Plugins
- [`modern-errors-cli`](https://github.com/ehmicky/modern-errors-cli): Handle
errors in CLI modules
- [`modern-errors-process`](https://github.com/ehmicky/modern-errors-process):
Handle process errors
- [`modern-errors-bugs`](https://github.com/ehmicky/modern-errors-bugs): Print
where to report bugs
- [`modern-errors-serialize`](https://github.com/ehmicky/modern-errors-serialize):
Serialize/parse errors
- [`modern-errors-clean`](https://github.com/ehmicky/modern-errors-clean): Clean
stack traces
- [`modern-errors-http`](https://github.com/ehmicky/modern-errors-http): Create
HTTP error responses
- [`modern-errors-winston`](https://github.com/ehmicky/modern-errors-winston):
Log errors with Winston
- [`modern-errors-switch`](https://github.com/ehmicky/modern-errors-switch):
Execute class-specific logic
- 🔌 Create your [own plugin](docs/plugins.md)# Example
Create error [classes](#%EF%B8%8F-error-classes).
```js
import ModernError from 'modern-errors'export const BaseError = ModernError.subclass('BaseError')
export const UnknownError = BaseError.subclass('UnknownError')
export const InputError = BaseError.subclass('InputError')
export const AuthError = BaseError.subclass('AuthError')
export const DatabaseError = BaseError.subclass('DatabaseError')
```Set error [properties](#%EF%B8%8F-error-properties).
```js
throw new InputError('Invalid file path', { props: { filePath: '/...' } })
```[Wrap](#-wrap-errors) errors.
```js
try {
// ...
} catch (cause) {
throw new InputError('Could not read the file.', { cause })
}
```[Normalize](#-normalize-errors) errors.
```js
try {
throw 'Missing file path.'
} catch (error) {
// Normalized from a string to a `BaseError` instance
throw BaseError.normalize(error)
}
```Use [plugins](#-plugins).
```js
import ModernError from 'modern-errors'
import modernErrorsSerialize from 'modern-errors-serialize'export const BaseError = ModernError.subclass('BaseError', {
plugins: [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 = BaseError.parse(JSON.parse(errorString))
```# Install
```bash
npm install modern-errors
```If any [plugin](#-plugins) is used, it must also be installed.
```bash
npm install modern-errors-{pluginName}
```This package works in both Node.js >=18.18.0 and
[browsers](https://raw.githubusercontent.com/ehmicky/dev-tasks/main/src/browserslist).This is an ES module. It must be loaded using
[an `import` or `import()` statement](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c),
not `require()`. If TypeScript is used, it must be configured to
[output ES modules](https://www.typescriptlang.org/docs/handbook/esm-node.html),
not CommonJS.# Usage
## ⛑️ Error classes
### Create error classes
```js
import ModernError from 'modern-errors'export const BaseError = ModernError.subclass('BaseError')
export const UnknownError = BaseError.subclass('UnknownError')
export const InputError = BaseError.subclass('InputError')
export const AuthError = BaseError.subclass('AuthError')
export const DatabaseError = BaseError.subclass('DatabaseError')
```### Export error classes
Exporting and documenting all error classes allows consumers to check them. This
also enables sharing error classes between modules.### Check error classes
```js
if (error instanceof InputError) {
// ...
}
```### Error subclasses
[`ErrorClass.subclass()`](#errorclasssubclassname-options) returns a
[subclass](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/extends).
Parent classes' [options](#options) are merged with their subclasses.```js
export const BaseError = ModernError.subclass('BaseError', {
props: { isError: true },
})
export const InputError = BaseError.subclass('InputError', {
props: { isUserError: true },
})const error = new InputError('...')
console.log(error.isError) // true
console.log(error.isUserError) // true
console.log(error instanceof BaseError) // true
console.log(error instanceof InputError) // true
```## 🏷️ Error properties
### Error class properties
```js
const InputError = BaseError.subclass('InputError', {
props: { isUserError: true },
})
const error = new InputError('...')
console.log(error.isUserError) // true
```### Error instance properties
```js
const error = new InputError('...', { props: { isUserError: true } })
console.log(error.isUserError) // true
```### Internal error properties
Error properties that are internal or secret can be prefixed with `_`. This
makes them
[non-enumerable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties),
which prevents iterating or logging them.```js
const error = new InputError('...', {
props: { userId: 6, _isUserError: true },
})
console.log(error.userId) // 6
console.log(error._isUserError) // true
console.log(Object.keys(error)) // ['userId']
console.log(error) // `userId` is logged, but not `_isUserError`
```## 🎀 Wrap errors
### Throw errors
```js
throw new InputError('Missing file path.')
```### Wrap inner error
Any error's [message](#wrap-error-message), [class](#wrap-error-class) and
[options](#wrap-error-options) can be wrapped using the
[standard](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause)
[`cause` option](#optionscause).Instead of being set as a `cause` property, the inner error is directly
[merged](https://github.com/ehmicky/merge-error-cause) to the outer error,
including its
[`message`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/message),
[`stack`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/stack),
[`name`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/name),
[`AggregateError.errors`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError)
and any [additional property](#%EF%B8%8F-error-properties).```js
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.```js
const cause = new InputError('File does not exist.')
// InputError: File does not exist.
throw new InputError('', { cause })
``````js
// InputError: File does not exist.
// Could not read the file.
throw new InputError('Could not read the file.', { cause })
``````js
// InputError: Could not read the file: File does not exist.
throw new InputError(`Could not read the file:`, { cause })
``````js
// 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.
```js
try {
throw new AuthError('...')
} catch (cause) {
// Now an InputError
throw new InputError('...', { cause })
}
```Except when the outer error's class is a parent class, such as
[`BaseError`](#create-error-classes).```js
try {
throw new AuthError('...')
} catch (cause) {
// Still an AuthError
throw new BaseError('...', { cause })
}
```### Wrap error options
The outer error's [`props`](#%EF%B8%8F-error-properties) and
[plugin options](#plugin-options) are merged.```js
try {
throw new AuthError('...', innerOptions)
} catch (cause) {
// `outerOptions` are merged with `innerOptions`
throw new BaseError('...', { ...outerOptions, cause })
}
```### Aggregate errors
The [`errors` option](#optionserrors) aggregates multiple errors into one. This
is like
[`new AggregateError(errors)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError/AggregateError)
except that it works with any error class.```js
const databaseError = new DatabaseError('...')
const authError = new AuthError('...')
throw new InputError('...', { errors: [databaseError, authError] })
// InputError: ... {
// [errors]: [
// DatabaseError: ...
// AuthError: ...
// ]
// }
```## 🚨 Normalize errors
### Wrapped errors
Any error can be directly passed to the [`cause`](#wrap-inner-error) or
[`errors`](#aggregate-errors) option, even if it is [invalid](#invalid-errors),
[unknown](#-unknown-errors) or not
[normalized](#errorclassnormalizeerror-newerrorclass).```js
try {
// ...
} catch (cause) {
throw new InputError('...', { cause })
}
```### Invalid errors
Manipulating errors that are not
[`Error` instances](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)
or that have
[invalid properties](https://github.com/ehmicky/normalize-exception#features)
can lead to unexpected bugs.
[`BaseError.normalize()`](#errorclassnormalizeerror-newerrorclass) fixes that.```js
try {
throw 'Missing file path.'
} catch (invalidError) {
// This fails: `invalidError.message` is `undefined`
console.log(invalidError.message.trim())
}
``````js
try {
throw 'Missing file path.'
} catch (invalidError) {
const normalizedError = BaseError.normalize(invalidError)
// This works: 'Missing file path.'
// `normalizedError` is a `BaseError` instance.
console.log(normalizedError.message.trim())
}
```## 🐞 Unknown errors
### Handling known errors
Known errors should be handled in a `try {} catch {}` block and
[wrapped](#wrap-error-class) with a [specific class](#create-error-classes).
That block should only cover the statement that might throw in order to prevent
catching other unrelated errors.```js
try {
return regExp.test(value)
} catch (error) {
// Now an `InputError` instance
throw new InputError('Invalid regular expression:', { cause: error })
}
```### Normalizing unknown errors
If an error is not handled as described [above](#handling-known-errors), it is
considered _unknown_. This indicates an unexpected exception, usually a bug.
[`BaseError.normalize(error, UnknownError)`](#errorclassnormalizeerror-newerrorclass)
assigns the `UnknownError` class to those errors.```js
export const UnknownError = BaseError.subclass('UnknownError')
``````js
try {
return regExp.test(value)
} catch (error) {
// Now an `UnknownError` instance
throw BaseError.normalize(error, UnknownError)
}
```### Top-level error handler
Wrapping a module's main functions with
[`BaseError.normalize(error, UnknownError)`](#errorclassnormalizeerror-newerrorclass)
ensures every error being thrown is [valid](#invalid-errors), applies
[plugins](#-plugins), and has a class that is either
[_known_](#create-error-classes) or [`UnknownError`](#-unknown-errors).```js
export const main = () => {
try {
// ...
} catch (error) {
throw BaseError.normalize(error, UnknownError)
}
}
```## 🔌 Plugins
### List of plugins
Plugins extend `modern-errors` features. All available plugins are
[listed here](#plugins).### Adding plugins
To use a plugin, please install it, then pass it to the
[`plugins` option](#optionsplugins).```bash
npm install modern-errors-{pluginName}
``````js
import ModernError from 'modern-errors'import modernErrorsBugs from 'modern-errors-bugs'
import modernErrorsSerialize from 'modern-errors-serialize'export const BaseError = ModernError.subclass('BaseError', {
plugins: [modernErrorsBugs, modernErrorsSerialize],
})
// ...
```### Custom plugins
Please see the [following documentation](docs/plugins.md) to create your own
plugin.### Plugin options
Most plugins can be configured with options. The option's name is the same as
the plugin.```js
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):
- Any error: second argument to [`ModernError.subclass()`](#options-1)
```js
export const BaseError = ModernError.subclass('BaseError', options)
```- Any error of a specific class (and its subclasses): second argument to
[`ErrorClass.subclass()`](#options-1)```js
export const InputError = BaseError.subclass('InputError', options)
```- A specific error: second argument to [`new ErrorClass()`](#options-3)
```js
throw new InputError('...', options)
```- A plugin method call: last argument, passing only that plugin's options
```js
ErrorClass[methodName](...args, options[pluginName])
``````js
error[methodName](...args, options[pluginName])
```## 🔧 Custom logic
The [`custom` option](#optionscustom) can be used to provide an error `class`
with additional methods, `constructor`, properties or options.```js
export const InputError = BaseError.subclass('InputError', {
// The `class` must extend from the parent error class
custom: class extends BaseError {
// If a `constructor` is defined, its parameters must be (message, options)
// Additional `options` can be defined.
constructor(message, options) {
message += options?.suffix ?? ''
super(message, options)
}isUserInput() {
// ...
}
},
})const error = new InputError('Wrong user name', { suffix: ': example' })
console.log(error.message) // 'Wrong user name: example'
console.log(error.isUserInput())
```## 🤓 TypeScript
Please see the [following documentation](docs/typescript.md) for information
about TypeScript types.# API
## ModernError
Top-level `ErrorClass`.
## ErrorClass.subclass(name, options?)
`name`: `string`\
`options`: [`ClassOptions?`](#options)Creates and returns a child `ErrorClass`.
### options
#### options.props
_Type_: `object`
[Error class properties](#error-class-properties).
#### options.plugins
_Type_: [`Plugin[]`](#-plugins)
#### options.custom
_Type_: `class extends ErrorClass {}`
[Custom class](#-custom-logic) to add any methods, `constructor` or properties.
#### options.\*
Any [plugin options](#plugin-options) can also be specified.
## new ErrorClass(message, options?)
`message`: `string`\
`options`: [`InstanceOptions?`](#options-2)\
_Return value_: `Error`### options
#### options.props
_Type_: `object`
[Error instance properties](#error-instance-properties).
#### options.cause
_Type_: [`any`](#wrapped-errors)
Inner error being [wrapped](#-wrap-errors).
#### options.errors
_Type_: `any[]`
Array of errors being [aggregated](#aggregate-errors).
#### options.\*
Any [plugin options](#plugin-options) can also be specified.
## ErrorClass.normalize(error, NewErrorClass?)
`error`: `Error | any`\
`NewErrorClass`: subclass of `ErrorClass`\
_Return value_: `Error`Normalizes [invalid errors](#invalid-errors).
If the `error`'s class is a subclass of `ErrorClass`, it is left as is.
Otherwise, it is [converted to `NewErrorClass`](#normalizing-unknown-errors),
which defaults to `ErrorClass` itself.# Modules
This framework brings together a collection of modules which can also be used
individually:- [`error-custom-class`](https://github.com/ehmicky/error-custom-class): Create
one error class
- [`error-class-utils`](https://github.com/ehmicky/error-class-utils): Utilities
to properly create error classes
- [`error-serializer`](https://github.com/ehmicky/error-serializer): Convert
errors to/from plain objects
- [`normalize-exception`](https://github.com/ehmicky/normalize-exception):
Normalize exceptions/errors
- [`is-error-instance`](https://github.com/ehmicky/is-error-instance): Check if
a value is an `Error` instance
- [`merge-error-cause`](https://github.com/ehmicky/merge-error-cause): Merge an
error with its `cause`
- [`set-error-class`](https://github.com/ehmicky/set-error-class): Properly
update an error's class
- [`set-error-message`](https://github.com/ehmicky/set-error-message): Properly
update an error's message
- [`wrap-error-message`](https://github.com/ehmicky/wrap-error-message):
Properly wrap an error's message
- [`set-error-props`](https://github.com/ehmicky/set-error-props): Properly
update an error's properties
- [`set-error-stack`](https://github.com/ehmicky/set-error-stack): Properly
update an error's stack
- [`handle-cli-error`](https://github.com/ehmicky/handle-cli-error): 💣 Error
handler for CLI applications 💥
- [`log-process-errors`](https://github.com/ehmicky/log-process-errors): Show
some ❤ to Node.js process errors
- [`error-http-response`](https://github.com/ehmicky/error-http-response):
Create HTTP error responses
- [`winston-error-format`](https://github.com/ehmicky/winston-error-format): Log
errors with Winston# Support
For any question, _don't hesitate_ to [submit an issue on GitHub](../../issues).
Everyone is welcome regardless of personal background. We enforce a
[Code of conduct](CODE_OF_CONDUCT.md) 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](CONTRIBUTING.md). Pull requests are welcome!
ehmicky
💻 🎨 🤔 📖
const_var
🤔 💬
Andy Brenneke
🤔 💬
Graham Fisher
🐛
renzor
💬 🤔
Eugene
💻 🐛
Jonathan Chambers
⚠️ 🐛