🔥 Type safe pojo error will help you to easily create typed and serializable error.
Intro
The problem with exceptions is that once caught you don't know what type they are. You can of course create a bunch of custom error classes and use instanceof
to overcome this. The advantage of ts-pojo-error
is that you have a single error type PojoError
which can be easily typed and serialized.
Features
- Type safe & autocompletion
- Serializable output
- Stackable errors
- Node or Browser
- ESM or CJS
- Well tested
Usage
Installation
pnpm add @skarab/ts-pojo-error # yarn and npm also works
Defining an error factory
-
factory( errors:
PojoErrorTypes
) :PojoFactory
An error factory is constructed from a Record
where the key is the type of the error and the value is a callback
that defines the PojoError
.
The callback
parameters define the parameters passed at the creation of the error and the return value defines the data of the error.
// errors.ts
import { factory } from "@skarab/ts-pojo-error";
export const errors = factory({
UNKNOWN: () => ({ message: "Unknown error..." }),
WARNING: (message: string) => ({ message, time: Date.now() }),
FATAL: (message?: string) => ({ message: message ?? "Fatal error" }),
EXIT: (message: string, code: number) => ({ message, code }),
});
Instantiating & Throwing errors
-
new( type:
infered
, ...args:infered[]
) :PojoError
-
throw( type:
infered
, ...args:infered[]
) :never
The first parameter is always the type of error, the following parameters are the ones you set in the factory.
All parameters have support for autocompletion.
// action.ts
import { errors } from "./errors";
export function action() {
// Do your awsome stuff...
// ...something go wrong, throw an typed error
throw errors.new("FATAL");
// or with a custom error message
throw errors.new("FATAL", "Oupsy!");
// or by using the .throw helper
errors.throw("FATAL");
// or by using the fake enum
errors.throw(errors.type.FATAL);
}
Catching & Typing errors
-
is( type:
infered
, error:unknown
) : boolean -
has( error:
unknown
) : boolean
This is where it gets really interesting, the problem with exceptions is that once caught you don't know what type they are. You can of course create a bunch of custom error classes and use instanceof
to overcome this. The advantage of ts-pojo-error
is that you have a single error type PojoError
which can be easily typed and serialized.
The is
method is a type guard that will narrow the error to the specific type if the original type is compatible.
In the if block all properties are typed and have support for autocompletion.
// index.ts
import { action } from "./action";
import { errors } from "./errors";
try {
action();
} catch (error) {
error; // <- unknown type
if (errors.is("FATAL", error)) {
error; // <- PojoError instance with type "FATAL"
error.message; // "Oupsy!": string
error.type; // "FATAL": "FATAL"
error.args; // ["Oupsy!"] : [message?: string | undefined]
error.data; // { message: "Oupsy!" }: { message: string }
error.cause; // Error | undefined (see "Stacking of errors" below)
error.toObject(); // { type, args, data, stack?: string | undefined }
error.toJSON(); // string
}
if (errors.has(error)) {
error.type; // "UNKNOWN" | "WARNING" | "FATAL" | ...
}
if (error instanceof PojoError) {
error.type; // any (Bad!)
}
}
Stacking of errors
-
newFrom( cause: Error, type:
infered
, ...args:infered[]
) :PojoError
-
throwFrom( cause: Error, type:
infered
, ...args:infered[]
) :never
Basically it adds a .cause
property with the parent error to the newly created PojoError
, see Error Cause tc39 proposal for further information.
try {
throw myErrors.new("UNKNOWN");
} catch (error) {
throw myErrors.newFrom(error, "FATAL");
}
You can stack any type of error.
try {
throw new Error("Unknown error");
} catch (error) {
myErrors.throwFrom(error, "FATAL");
}
voxpelli/pony-cause
Usage withThis library coded by @voxpelli includes a couple of helpers inspired by VError, supporting both standard causes and VError causes.
import { stackWithCauses } from "pony-cause";
const error1 = myErrors.new("UNKNOWN");
const erorr2 = myErrors.newFrom(error1, "FATAL");
const error4 = myErrors.newFrom(error3, "WARNING", "Attention to danger !!!");
const error3 = myErrors.newFrom(
erorr2,
"PAGE_NOT_FOUND",
"http://www.prout.com",
);
console.log("We had a mishap:", stackWithCauses(error4));
To make the example more readable I have replaced the full stack with ...
but the actual output contains it.
We had a mishap: PojoError: Page Not Found: http://www.prout.com
at index.ts:191:31
at ...
caused by: PojoError: Attention to danger !!!
at index.ts:190:31
at ...
caused by: PojoError: Fatal error
at index.ts:189:31
at ...
caused by: PojoError: Unknown error...
at index.ts:188:34
at ...
💜
Contributing See CONTRIBUTING.md