1.4.0 • Published 2 years ago

@skarab/ts-pojo-error v1.4.0

Weekly downloads
-
License
MIT
Repository
github
Last release
2 years ago

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");
}

Usage with voxpelli/pony-cause

This 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