A simple toolkit to work with custom errors. Definitely not a kid.
Declaring a custom error class in TypeScript involves more boilerplate than it should:
class TimeoutError extends Error {
constructor(public readonly duration: number) {
super(`Timed out: ${duration}ms`);
// Not inherited from the class, must be assigned manually.
this.name = 'TimeoutError';
// Required to keep `instanceof` working when targeting ES5.
// Easy to forget, and silently breaks error handling when omitted.
Object.setPrototypeOf(this, TimeoutError.prototype);
}
}
const err = new TimeoutError(1000);
if (err instanceof TimeoutError) {
// err is TimeoutError
}error-kid does all of it for you, and adds a typed is predicate along the way:
import { errorClassWithData } from 'error-kid';
class TimeoutError extends errorClassWithData<
{ duration: number }, [duration: number]
>({
name: 'TimeoutError',
data: duration => ({ duration }),
message: duration => `Timed out: ${duration}ms`,
}) {}
const err = new TimeoutError(1000);
if (TimeoutError.is(err)) {
// err is TimeoutError
}# yarn
yarn add error-kid
# pnpm
pnpm i error-kid
# npm
npm i error-kidA function used to create a new error class without custom data.
import { errorClass } from 'error-kid';
class UnknownError extends errorClass({ name: 'UnknownError' }) {}
UnknownError.name; // 'UnknownError'
const error = new UnknownError();
error.message; // ''
error.cause; // undefined
error instanceof Error; // true
UnknownError.is(new Error); // false
UnknownError.is(error); // trueBy default, created error class constructor accepts no arguments. It also passes nothing to
the Error super constructor.
Note
Note that all examples in this document use the class Err extends errorClass(...) {} form
instead of const Err = errorClass(...). This is intentional. The function returns a value,
so assigning it to a variable makes Err usable as a value only — using it as a type will
not work. Declaring a class, in turn, creates both a value and a type with the same name:
const ConstError = errorClass({ name: 'ConstError' });
// Error: 'ConstError' refers to a value, but is being used as a type.
function handle(error: ConstError) {}
class ClassError extends errorClass({ name: 'ClassError' }) {}
// Works as expected.
function handle(error: ClassError) {}To specify the error message, use the message option. It accepts either a static string, or
a function computing the message from the constructor arguments.
import { errorClass } from 'error-kid';
const TimeoutError = errorClass({ name: 'TimeoutError', message: 'Timed out' });
new TimeoutError().message; // 'Timed out'To compute the message dynamically, define the constructor arguments' type using the generic parameter. It must be any tuple, and it describes arguments passed to the error class constructor.
import { errorClass } from 'error-kid';
class ApiError extends errorClass<[
errorText: string,
retriesCount: number,
]>({
name: 'ApiError',
message: (errorText, retriesCount) => {
return `Request failed. Retries count: ${retriesCount}. Error text: ${errorText}`;
},
}) {}
const error = new ApiError('Ooopsie!', 3);
error.message; // "Request failed. Retries count: 3. Error text: Ooopsie!"The cause option is a function computing the error cause from the constructor arguments. Its
returned value is passed to the Error super constructor as the cause property of
ErrorOptions.
Most commonly the cause is one of the constructor arguments:
import { errorClass } from 'error-kid';
class ApiError extends errorClass<[status: number, cause?: unknown]>({
name: 'ApiError',
message: status => `Request failed with status ${status}`,
cause: (_status, cause) => cause,
}) {}
const error = new ApiError(500, new Error('ECONNRESET'));
error.message; // 'Request failed with status 500'
error.cause; // Error('ECONNRESET')To use a constant cause, return it from a function accepting no arguments:
import { errorClass } from 'error-kid';
class DatabaseError extends errorClass({
name: 'DatabaseError',
message: 'Failed to connect',
cause: () => new Error('ECONNREFUSED'),
}) {}
new DatabaseError().cause; // Error('ECONNREFUSED')When the option is omitted, cause is undefined.
Each created class has a static is method — a type predicate checking if the passed value is
an instance of this class. Being a standalone function, it can be passed anywhere a predicate
is expected.
import { errorClass } from 'error-kid';
class TimeoutError extends errorClass({ name: 'TimeoutError' }) {}
try {
// ...
} catch (error) {
if (TimeoutError.is(error)) {
// `error` is narrowed to TimeoutError here.
error.message;
}
}
// Narrowing works in predicate positions too.
const timeouts = errors.filter(TimeoutError.is);Important
Prefer is over instanceof. When a class is declared via
class Err extends errorClass(...) {}, error instanceof Err returns false, so is is
the reliable way to check the error type.
A function that creates a new error class with typed data. It enhances the result
of calling the errorClass function.
import { errorClassWithData } from 'error-kid';
class TimeoutError extends errorClassWithData<{ duration: number }, [duration: number]>({
name: 'TimeoutError',
data: duration => ({ duration }),
}) {}
const error = new TimeoutError(1000);
error.data; // { duration: 1000 }
TimeoutError.is(error); // trueNote
Unlike errorClass, this function accepts the data type as its first generic parameter,
and the constructor arguments tuple as the second one.
This function accepts the same message and cause options as the errorClass function, and
they behave exactly the same way.
import { errorClassWithData } from 'error-kid';
class TimeoutError extends errorClassWithData<
{ duration: number },
[duration: number, cause?: unknown]
>({
name: 'TimeoutError',
data: duration => ({ duration }),
message: duration => `Timed out: ${duration}ms`,
cause: (_duration, cause) => cause,
}) {}
const error = new TimeoutError(1000, new Error('Just because'));
error.data; // { duration: 1000 }
error.message; // "Timed out: 1000ms"
error.cause; // Error('Just because')The is predicate narrows the data property as well:
try {
// ...
} catch (error) {
if (TimeoutError.is(error)) {
error.data.duration; // number
}
}