npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@dldc/erreur

v7.1.0

Published

Type safe custom errors

Downloads

652

Readme

🛑 Erreur

Type safe errors metadata

Type safe errors ?

In JavaScript and TypeScript you can throw anything so when you catch something you don't know what it is which make it hard to properly handle errors.

One way to fix this is to create custom classes that extends the Error class

class HttpError extends Error {
  // ...
}

try {
  // ...
} catch (error) {
  if (error instanceof HttpError) {
    // ...
  }
}

This works but it has a few drawbacks:

  1. You have to create a new class for each error type, which can be a lot of boilerplate. Also extending the Error class requires some tricks to make it work properly (Object.setPrototypeOf(this, new.target.prototype);)
  2. You can't add data to an existing error. For example, you might catch an HttpError and want to add the url of the request that failed to the error. You can't do that with this approach (at least not in a type-safe way).

To solve these issue, this librairy expose an ErreurStore (erreur is the french word for error) that let you link any data to an error (using a WeakMap internally) as well as some utility functions to create and handle errors.

Simple example

import { createErreurStore } from '@dldc/erreur'

const PermissionErrorStore = createErreurStore<{ userId: string }>()
const DeleteErrorStore = createErreurStore<{ postId: string }>()

function detelePost(userId: string, postId: string) {
  try {
    if (userId !== 'admin') {
      return PermissionErrorStore.setAndThrow(new Error('Permission denied'), { userId });
    }
  }
  if (postId === '1') {
    return DeleteErrorStore.setAndThrow('Cannot delete post', { postId })
  }
}

Library example

If you are building a library, you can export an ErreurStore instance to let users of your library handle your errors.

import { doStuff, StuffErreurStore } from 'my-library';

function main() {
  try {
    doStuff();
  } catch (error) {
    if (StuffErreurStore.has(error)) {
      console.log('Stuff error:', stuff);
    } else {
      console.error('Unknown error:', error);
    }
  }
}

Exposing readonly stores

If you want to expose an ErreurStore instance to users of your library but you don't want them to be able to set errors, you can expose a readonly version of the store.

import { createErreurStore } from '@dldc/erreur';

// This store can be used to set errors
const PermissionErrorStore = createErreurStore<{ userId: string }>();

// This store can only be used to read errors
export const ReadonlyPermissionErrorStore = PermissionErrorStore.asReadonly;

Using union types

Instead of creating multiple stores, you can use union types to match multiple errors at once.

import { createErreurStore } from '@dldc/erreur';

export type TDemoErreurData =
  | { kind: 'FirstErrorKind'; someData: string }
  | { kind: 'SecondErrorKind'; someOtherData: number };

const DemoErreurInternal = createErreurStore<TDemoErreurData>();

// only expose a readonly version of the store
export const DemoErreur = DemoErreurInternal.asReadonly;

// exposing functions to create errors

export function createFirstError(someData: string) {
  return DemoErreurInternal.setAndReturn(new Error('First error'), { kind: 'FirstErrorKind', someData });
}

export function createSecondError(someOtherData: number) {
  return DemoErreurInternal.setAndReturn(new Error('Second error'), { kind: 'SecondErrorKind', someOtherData });
}

Note: Keep in mind that you can set only once per error / store. If you need to set multiple errors you will need to create multiple stores.

Matching multiple stores at once

If you have multiple stores and you want to match any of them, you can use if else statements but we provide 2 utility functions to make it easier.

matchFirstErreur

import { matchFirstErreur } from '@dldc/erreur';

function handleError(error: Error) {
  // This will return the data of the first store that has the error
  const result = matchFirstErreur(error, [PermissionErrorStore, InternalErrorStore, NotFoundErrorStore]);
}

matchErreurs

function handleError(error: Error) {
  const result = matchFirstErreur(matchErreurs, {
    first: PermissionErrorStore,
    second: InternalErrorStore,
    third: NotFoundErrorStore,
  });
  // result is an object with the same keys and the matched data if any
  if (result.first) {
    // ...
  } else if (result.second) {
    // ...
  } else if (result.third) {
    // ...
  }
}

Creating a store with no data

If you don't need to store any data with your errors, you can use the createVoidErreurStore function.

import { createVoidErreurStore } from '@dldc/erreur';

const NotFoundErrorStore = createVoidErreurStore();

It works the same way as the regular ErreurStore but you don't need to pass any data when setting an error and the get method will return true if the error is set.