Nutmeg Pumpkin Macchiato

    modern-errors
    TypeScript icon, indicating that this package has built-in type declarations

    3.1.1 • Public • Published
    modern-errors logo

    Codecov TypeScript Node Twitter Medium

    Handle errors like it's 2022 🔮

    Error handling framework that is minimalist yet featureful.

    Features

    Example

    Create custom error types.

    // `error.js`
    import modernErrors from 'modern-errors'
    
    export const { InputError, AuthError, DatabaseError, errorHandler, parse } =
      modernErrors(['InputError', 'AuthError', 'DatabaseError'])

    Wrap the main function with the error handler.

    import { errorHandler } from './error.js'
    
    export const main = async function (filePath) {
      try {
        return await readContents(filePath)
      } catch (error) {
        throw errorHandler(error)
      }
    }

    Throw/re-throw errors.

    import { InputError } from './error.js'
    
    const readContents = async function (filePath) {
      try {
        return await readFile(filePath)
      } catch (cause) {
        throw new InputError(`Could not read ${filePath}`, { cause })
      }
    }

    Install

    npm install modern-errors

    This package is an ES module and must be loaded using an import or import() statement, not require().

    API

    modernErrors(errorNames, options?)

    errorNames string[]
    options object
    Return value: object

    Creates custom error types.

    Return value

    Any error type

    Type: CustomErrorType

    Any error name passed as argument is returned as an error type.

    errorHandler

    Type: (anyException) => CustomError

    Error handler that should wrap each main function.

    parse

    Type: (errorObject) => Error

    Convert an error plain object into an Error instance.

    Options

    bugsUrl

    Type: string | URL | ((error: Error) => string | URL | void)

    URL where users should report unknown errors.

    onCreate

    Type: (error, parameters) => void

    Called on any new CustomErrorType('message', parameters). Can be used to customize error parameters or set error type properties. By default, any parameters are set as error properties.

    Usage

    Setup

    Create custom error types

    // error.js
    import modernErrors from 'modern-errors'
    
    export const { InputError, AuthError, DatabaseError, errorHandler, parse } =
      modernErrors(['InputError', 'AuthError', 'DatabaseError'])

    Error handler

    Each main function should be wrapped with the errorHandler().

    import { errorHandler } from './error.js'
    
    export const main = async function (filePath) {
      try {
        return await readContents(filePath)
      } catch (error) {
        // `errorHandler()` returns `error`, so `throw` must be used
        throw errorHandler(error)
      }
    }

    Throw errors

    Simple errors

    import { InputError } from './error.js'
    
    const validateFilePath = function (filePath) {
      if (filePath === '') {
        throw new InputError('Missing file path.')
      }
    }

    Invalid errors

    Invalid errors are normalized by errorHandler(). This includes errors that are not an Error instance or that have wrong/missing properties.

    import { errorHandler } from './error.js'
    
    export const main = function (filePath) {
      try {
        throw 'Missing file path.'
      } catch (error) {
        throw errorHandler(error) // Normalized to an `Error` instance
      }
    }

    Re-throw errors

    Errors are re-thrown using the standard cause parameter. This allows wrapping the error message, properties, or type.

    import { InputError } from './error.js'
    
    const readContents = async function (filePath) {
      try {
        return await readFile(filePath)
      } catch (cause) {
        throw new InputError(`Could not read ${filePath}`, { cause })
      }
    }

    The errorHandler() merges all error cause into a single error, including their message, stack, name, AggregateError.errors and any additional property. This ensures:

    Wrap error message

    The outer error message is appended.

    try {
      await readFile(filePath)
    } catch (cause) {
      throw new InputError(`Could not read ${filePath}`, { cause })
      // InputError: File does not exist.
      // Could not read /example/path
    }

    If the outer error message ends with :, it is prepended instead.

    throw new InputError(`Could not read ${filePath}:`, { cause })
    // InputError: Could not read /example/path: File does not exist.

    : can optionally be followed a newline.

    throw new InputError(`Could not read ${filePath}:\n`, { cause })
    // InputError: Could not read /example/path:
    // File does not exist.

    Error types

    Test error type

    Once errorHandler() has been applied, the error type can be checked by its name. Libraries should document their possible error names, but do not need to export their error types.

    if (error.name === 'InputError') {
      // ...
    } else if (error.name === 'UnknownError') {
      // ...
    }

    Set error type

    When re-throwing errors, the outer error type overrides the inner one.

    try {
      throw new AuthError('Could not authenticate.')
    } catch (cause) {
      throw new InputError('Could not read the file.', { cause })
      // Now an InputError
    }

    However, the inner error type is kept if the outer one is Error or AggregateError.

    try {
      throw new AuthError('Could not authenticate.')
    } catch (cause) {
      throw new Error('Could not read the file.', { cause })
      // Still an AuthError
    }

    Unknown errors

    All errors should use known types: the ones returned by modernErrors(). Errors with an unknown type should be handled in try {} catch {} and re-thrown with a known type instead.

    The errorHandler() assigns the UnknownError type to any error with an unknown type.

    const getUserId = function (user) {
      return user.id
    }
    
    getUserId(null) // UnknownError: Cannot read properties of null (reading 'id')

    Bug reports

    If the bugsUrl option is a string or URL, any unknown error will include the following message.

    modernErrors({ bugsUrl: 'https://github.com/my-name/my-project/issues' })
    Please report this bug at: https://github.com/my-name/my-project/issues
    

    If the bugsUrl option is a function returning a string or URL, any error (known or unknown) will include it, unless the return value is undefined.

    createErrorTypes({
      bugsUrl: (error) =>
        error.name === 'UnknownError' || error.name === 'PluginError'
          ? 'https://github.com/my-name/my-project/issues'
          : undefined,
    })

    Error properties

    Set error properties

    Unless the onCreate() option is defined, any parameter is set as an error property.

    const error = new InputError('Could not read the file.', { filePath: '/path' })
    console.log(error.filePath) // '/path'

    Wrap error properties

    Pass an empty message in order to set error properties without wrapping the message.

    try {
      await readFile(filePath)
    } catch (cause) {
      throw new Error('', { cause, filePath: '/path' })
    }

    Customize error parameters

    The onCreate() option can be used to validate and transform error parameters.

    modernErrors({
      onCreate(error, parameters) {
        const { filePath } = parameters
    
        if (typeof filePath !== 'string') {
          throw new Error('filePath must be a string.')
        }
    
        const hasFilePath = filePath !== undefined
        Object.assign(error, { filePath, hasFilePath })
      },
    })
    const error = new InputError('Could not read the file.', {
      filePath: '/path',
      unknownParam: true,
    })
    console.log(error.filePath) // '/path'
    console.log(error.hasFilePath) // true
    console.log(error.unknownParam) // undefined

    Type-specific logic

    The onCreate() option can trigger error type-specific logic.

    modernErrors({
      onCreate(error, parameters) {
        onCreateError[error.name](error, parameters)
      },
    })
    
    const onCreateError = {
      InputError(error, parameters) {
        // ...
      },
      AuthError(error, parameters) {
        // ...
      },
      // ...
    }

    Error type properties

    The onCreate() option can be used to set properties on all instances of a given error type.

    modernErrors({
      onCreate(error, parameters) {
        Object.assign(error, parameters, ERROR_PROPS[error.name])
      },
    })
    
    const ERROR_PROPS = {
      InputError: { isUser: true },
      AuthError: { isUser: true },
      DatabaseError: { isUser: false },
    }
    const error = new InputError('Could not read the file.')
    console.log(error.isUser) // true

    CLI errors

    CLI applications can assign a different exit code and log verbosity per error type by using handle-cli-error.

    #!/usr/bin/env node
    import handleCliError from 'handle-cli-error'
    
    // `programmaticMain()` must use `modern-errors`'s `errorHandler`
    import programmaticMain from './main.js'
    
    const cliMain = function () {
      try {
        const cliFlags = getCliFlags()
        programmaticMain(cliFlags)
      } catch (error) {
        // Print `error` then exit the process
        handleCliError(error, {
          types: {
            InputError: { exitCode: 1, short: true },
            DatabaseError: { exitCode: 2, short: true },
            default: { exitCode: 3 },
          },
        })
      }
    }
    
    cliMain()

    Serialization/parsing

    Serialize

    error.toJSON() converts errors to plain objects that are serializable to JSON (or YAML, etc.). It is automatically called by JSON.stringify(). All error properties are kept, including cause.

    The error must be from a known type. However, any other error (including Error, TypeError, RangeError, etc.) is also serializable providing it has been either passed to errorHandler(), or wrapped as an error.cause.

    try {
      await readFile(filePath)
    } catch (cause) {
      const error = new InputError('Could not read the file.', {
        cause,
        filePath: '/path',
      })
      const errorObject = error.toJSON()
      // {
      //   name: 'InputError',
      //   message: 'Could not read the file',
      //   stack: '...',
      //   cause: { name: 'Error', ... },
      //   filePath: '/path'
      // }
      const errorString = JSON.stringify(error)
      // '{"name":"InputError",...}'
    }

    Parse

    parse(errorObject) converts those error plain objects back to identical error instances.

    The original error type is generically preserved. However, it is converted to a generic Error if it is neither a native type (TypeError, RangeError, etc.) nor a known type.

    const newErrorObject = JSON.parse(errorString)
    const newError = parse(newErrorObject)
    // InputError: Could not read the file.
    //   filePath: '/path'
    //   [cause]: Error: ...

    Deep serialization/parsing

    Objects and arrays containing custom errors can be deeply serialized to JSON. They can then be deeply parsed back using JSON.parse()'s reviver.

    const error = new InputError('Could not read the file.')
    const deepObject = [{}, { error }]
    const jsonString = JSON.stringify(deepObject)
    const newDeepObject = JSON.parse(jsonString, (key, value) => parse(value))
    console.log(newDeepObject[1].error) // InputError: Could not read the file.

    Modules

    This framework brings together a collection of modules which can also be used individually:

    Related projects

    Support

    For any question, don't hesitate to submit an issue on GitHub.

    Everyone is welcome regardless of personal background. We enforce a Code of conduct in order to promote a positive and inclusive environment.

    Contributing

    This project was made with ❤️. The simplest way to give back is by starring and sharing it online.

    If the documentation is unclear or has a typo, please click on the page's Edit button (pencil icon) and suggest a correction.

    If you would like to help us fix a bug or add a new feature, please check our guidelines. Pull requests are welcome!

    Install

    npm i modern-errors

    DownloadsWeekly Downloads

    60

    Version

    3.1.1

    License

    Apache-2.0

    Unpacked Size

    37.8 kB

    Total Files

    6

    Last publish

    Collaborators

    • ehmicky