Richer errors with first-class Typescript support.
Errors are much more pleasant to handle when you have the right information. This package aims to make it easier and quicker for the developer to create custom errors and wrap existing errors with context, so that when they appear, you can work out why.
- Zero dependencies
- First-class Typescript support
- Compatible with Node.js and Browser (ES6+), including ES2022 Error.cause
- Extends the built-in
Error
class - Extendable to create custom classes (with the correct class name!)
- Works as you'd expect with the
instanceof
operator, even when extended - Chainable error messages and causes (searchable too!)
- Add type-safe structured data to the error
- Transform error objects into a structured JSON representation
See the examples in the Quickstart section below in action on CodeSandbox.
Install the package using your favourite package manager.
npm install exceptional-errors
yarn add exceptional-errors
pnpm add exceptional-errors
Import the base class EError. You can use this as a drop-in replacement for the built-in Error class.
import { EError } from "exceptional-errors";
const error = new EError("oh no!");
console.log(error);
This prints:
EError: oh no!
You can use EError to wrap existing errors to give context for where errors originate from if they are passed up through your exception handlers.
const cause = new Error("it was my fault");
const error = new EError("something went wrong", cause);
console.log(error);
This prints:
EError: something went wrong > Error: it was my fault
Pass a cause and structured data to EError to make it easier to
programatically handle and process errors. You can easily access the cause
and
info
for debugging.
const cause = new EError("invalid credentials");
const error = new EError("failed login", {
cause,
info: {
username: "obiwan",
date: Date.now(),
},
});
console.log(error);
console.log(error.cause);
console.log(error.info);
This prints:
EError: failed login > EError: invalid credentials
EError: invalid credentials
{ username: "obiwan", date: 1656146150075 }
Easily extend the EError class to make your own error classes. The name
property will be automatically set correctly, and you can use instanceof
to
check the type hierarchy.
class CustomError extends EError {}
const error = new EError("parameter x is invalid");
const wrapped = new CustomError("bad request", error);
console.log(wrapped.name);
console.log(wrapped);
console.log(error.name);
console.log(error);
console.log("wrapped instanceof CustomError", wrapped instanceof CustomError);
console.log("wrapped instanceof EError", wrapped instanceof EError);
console.log("wrapped instanceof Error", wrapped instanceof Error);
This prints:
CustomError
CustomError: bad request > EError: parameter x is invalid
EError
EError: parameter x is invalid
wrapped instanceof CustomError true
wrapped instanceof EError true
wrapped instanceof Error true
Use the toJSON()
method to get a JSON representation of the error, which can
be handy if you want to serialize an error and pass it to a monitoring system or
database.
class CustomRequestError extends EError<{
code: number;
path: string;
}> {}
const cause = new EError("parameter x is invalid");
const error = new CustomRequestError("bad request", {
cause,
info: {
code: 400,
path: "/test-endpoint",
},
});
console.log(error.toJSON());
This prints:
{
name: "CustomRequestError",
message: "bad request > EError: parameter x is invalid",
originalMessage: "bad request",
cause: {
name: "EError",
message: "parameter x is invalid",
originalMessage: "parameter x is invalid",
},
info: {
code: 400,
path: "/test-endpoint"
}
}
Use the findCause(type)
function to check if an error type exists somewhere in
an error's cause chain. More utility functions are available, and are listed in
the API reference.
class MyError extends EError {}
const root = new MyError("root cause", new Error("internal"));
const intermediate = new EError("intermediate cause", root);
const error = new EError("top level error", intermediate);
console.log(error.findCause(MyError));
This prints:
MyError: root cause > Error: internal
The main export from this package is the EError
class. Use it as a
constructor, or access static methods which can be used on error objects.
The EError
class is generic, meaning you can specify the exact type of the
info
and cause
if it suits your needs. If you are using the EError
class
directly, the generics are inferred, so you don't need to define them.
Alternatively, if you are extending the EError
class, you may want to pass on
the generics for improved type support, or define them to restrict them.
class EError<T = unknown, Cause extends Error = Error> {
// ...
}
import { EError } from "exceptional-errors";
// Pass on the generics
class MyCustomError<T, Cause extends Error> extends EError<T, Cause> {
// ...
}
// Or define them explicitly for your error
class MyCustomError extends EError<{ code: number }> {
// ...
}
/**
* Create an EError instance with an empty message.
*/
new EError()
/**
* Create an EError instance with the given message.
*/
new EError(message: string)
/**
* Create an EError instance with the given message and cause.
* The message of the causing error will be appended to this
* error's message.
*
* @param message The error message.
* @param error The causing error to wrap.
*/
new EError(message: string, error: Cause)
/**
* Create an EError instance with the given message and options.
* The message of the causing error, if provided, will be appended
* to this error's message.
*
* @param message The error message.
* @param options Data to pass to the error, such as `cause` and `info`.
*/
new EError(message: string, options: { cause?: Cause, info?: T })
/**
* Create an EError instance with the given options and an empty message.
*/
new EError(options: { cause?: Cause, info?: T })
/**
* Create an EError instance with the given cause and an empty message.
*/
new EError(error: Cause)
Property | Type | Description |
---|---|---|
name |
string | A name for the error type. This value can be used to programatically differentiate between different types of errors. |
message |
string | The error message. Provided when the error is instantiated, and will have the message of the cause appended (if available). |
stack |
string? | The stack trace. Populated by the JavaScript engine. |
originalMessage |
string | The original error message passed to the constructor, without the cause 's message appended. |
cause |
Cause ? |
A reference to the error that this error is wrapping. May be undefined if this error is not wrapping another error. |
info |
T ? |
Data that was passed to the error. May be undefined if no data was passed to the error. |
The following methods are available on any EError
instance.
class EError {
// ...
getCauses(filter?: (error: Error) => boolean): Error[];
}
Get the cause chain of the error, including the error itself. You can pass a filter function to filter the returned results.
class EError {
// ...
findCause<T extends Error>(type: AnyErrorConstructor<T>): T | null;
findCauses<T extends Error>(type: AnyErrorConstructor<T>): T[];
}
Find the first occurence or all occurrences of provided error type in the
error's cause chain, including the error itself. If not found, null
or an
empty array will be returned. type
should be a class definition, such as the
built-in Error
class, EError
, or a custom error class.
class EError {
// ...
findCauseByName(name: string): Error | null;
findCausesByName(name: string): Error[];
}
Find the first occurrence or all occurrences of a cause that has the given name
in the error's cause chain, including the error itself. If not found, null
or
an empty array will be returned.
class EError {
// ...
fullStack(): string;
}
Return the stack trace of the given error, including the stack traces of each cause in the cause chain.
class EError {
// ...
toJSON(options: { stack?: boolean; shallow?: boolean }): EErrorJSON;
}
type EErrorJSON = {
name: string;
message: string;
stack?: string[];
originalMessage?: string;
cause?: EErrorJSON;
info?: any;
};
Convert a given error into a normalised JSON output format. Set stack
to true
to include the stack trace in the output. Set shallow
to true to only include
the top most error (the cause
property will be omitted).
The stack trace is split by the newline character (\n
) into an array of
strings.
All methods available on an EError
instance are also available as static
methods on the EError
class, with the only difference being that the first
argument should be the error
object you want to use it on.
Yes, there are plenty (see some of the inspirations for this package below). But all in all, there wasn't one that quite fit all of the things on my wishlist - for example, some don't have first-class Typescript support, some have APIs that are not as lean as I would like them to be. So here I am, with my own implementation, heavily inspired by all of my favourite features from other similar packages.
I highly encourage you to have a look at some of the other packages out there as they might just fit your needs better than what I've designed here. No hard feelings!
This package has been tested to be compatible with ES6 and CommonJS. To run the tests in this module, you'll need to clone this repository and install the development dependencies.
To run tests for CommonJS and Typescript run the following command:
pnpm test
To run the tests for ES6 in a browser, run the following command:
pnpm test:browser
Thanks goes to these wonderful people (emoji key):
Ben Yap ๐ป |
benedictong42 ๐ค |
This project follows the all-contributors specification. Contributions of any kind welcome!
See LICENSE