Skip to content

AppError

Type : class

The AppError class is designed to manage and handle errors of a specific type in a structured way within an application. It provides mechanisms to collect, report multiple errors, while also supporting error scopes for more granular error management. The class includes methods to push new errors, aggregate errors, and convert them to a string representation. It also provides functionality to catch and throw errors, with custom options for error handling behavior.

The main use case for this class is to create a central place to handle errors in an application, making it easier to manage and report errors in a consistent and structured way.

Let’s take an example without using the AppError class:

validateUserData.ts
if (!user.email) // handle error;
if (!user.password) // handle error;
... other validation checks

Now the problem is that we need to do the same thing for all other functions that might throw errors like this example:

createUser.ts
try {
validateUserData(user);
const isUserExist = await isUserExist(user);
if (isUserExist) {'do something'};
const user = await createUser(user);
} catch (error) {
'handle errors'
}

Each of the functions used in the previous example might throw it’s own errors, and if we don’t handle each of them separately we don’t really know which error is the one that actually happened. and it will be difficult to handle if we need to return all related errors together to the client.

Now let’s use the AppError class:

validateUserData.ts
import { AppError } from '@mustib/utils'
const appError = new AppError();
if (!user.email) appError.push('Validation', 'email is required');
if (!user.password) appError.push('Validation', 'password is required')
... other validation checks
appError.end()
createUser.ts
AppError.aggregate(async (appError) => {
await appError.catch(() => {
validateUserData(user);
});
const isUserExist = await isUserExist(user);
if (isUserExist) appError.push('Duplication', 'user already exist');
else await appError.catch(async () => {
await createUser(user)
})
})

Now we only need to handle the AppError instance, and we can return all related errors together to the client like this.

try {
... implementations
} catch (error) {
if (error instanceof AppError) {
return error.toString({
includesScope: ['user', 'Validation'],
excludesScope: ['secret scope'],
});
} else {'send generic error message'}
}

With that we only need to handle errors in a central place.

Definition

export class AppError<ErrorTypes extends Capitalize<string>> extends Error {}

Generics

  • ErrorTypes - a capitalized string union of error types that are allowed to be pushed to the error instance
    • For Example
      type ErrorTypes = 'Validation' | 'Duplication';
      const appError = new AppError<ErrorTypes>();
      appError.push('Validation', 'email is required');

Constructor()

type AppError = new (options?: ErrorOptions): AppError

Properties

length

visibility : protected

Type : number

default: : 0

Indicates the number of error types present in the instance

message

visibility : public

Type : string

  • a getter that returns the error message containing all errors

errors

visibility : protected

type errors = {[key in ErrorTypes]?: { message: string; scope?: ErrorScope }[]}
  • an object that contains all errors of the instance. The keys of the object are the error types, and the values are arrays of objects with the following properties:
    • message: the error message
    • scope?: the scope of the error

Methods

throw()

visibility : public static

type throw = <ErrorTypes extends Capitalize<string>>(
type: ErrorTypes,
error: string | string[],
options?: ErrorOptions & { pushOptions?: PushOptions },
): never

This method is used to create a new AppError instance, push the error to it, and throw it instead of having to it that manually.

Usage:

AppError.throw('Error Type', 'Error Message')

aggregate()

visibility : public static

type aggregate = async <ErrorTypes extends Capitalize<string>>(
aggregateFunc: (
appError: Omit<AppError<ErrorTypes>, 'end'>,
) => void | Promise<void>,
options?: ErrorOptions,
): Promise<void>

This method is used if you don’t want to create a new AppError instance and end it manually by calling the end() method at the end of your implementations as it does it for you.

  • parameters:

    1. aggregateFunc: (appError: Omit<AppError<ErrorTypes>, ‘end’>) => void | Promise<void>
      • a function that will be called with a new AppError instance as a parameter.
      • it can be async or normal function.
    2. options?: ErrorOptions
  • returns: Promise<void>

Usage:

AppError.aggregate(async (appError) => {
appError.push('Error Type', 'Error Message');
appError.catch(async () => {
await doSomething();
})
})

catch()

visibility : public

type catch = async (catchFunc: () => void | Promise<void>): Promise<void>

This method is used to catch errors thrown from other AppError instances and add them to the current AppError instance.

  • parameters:

    1. catchFunc: () => void | Promise<void>
      • it will be called inside a try catch block to catch errors thrown from other AppError instances.
      • it can be async or normal function.
  • returns: Promise<void>

Usage:

const appError = new AppError();
await appError.catch(async () => {
await doSomething();
});

push()

visibility : public

type push = (type: ErrorTypes, error: string | string[], options?: PushOptions): void

This method is used to add new errors to the AppError instance’s errors property.

  • parameters:

    1. type: ErrorTypes
    2. error: string | string[]
      • string - the error message to be added
      • string[] - an array of error messages to be added
    3. options?: PushOptions
  • returns: void

Usage:

const appError = new AppError();
appError.push('Error Type', ['Error Message 1', 'Error Message 2'], {scope: ['user']});
appError.push('Another Error Type', 'Another Error Message', {scope: ['validation']});

matchesScope()

visibility: protected

type matchesScope = (options: {
errScope: ErrorScope;
includesScope?: ErrorScope;
excludesScope?: ErrorScope;
}): boolean

This method check if the error can be displayed or not based on the includesScope and excludesScope provided.
It returns true if the error scope has any scope in includesScope and has no scope in excludesScope or there is no includes or excludes scope and returns false otherwise.

  • parameters:
    1. options: an object with the following properties:
      • errScope: ErrorScope - the scope of the error to check
      • includesScope?: ErrorScope - an optional array of scopes to include
      • excludesScope?: ErrorScope - an optional array of scopes to exclude
  • returns: boolean

toString()

visibility : public

type toString = (options?: Omit<
Parameters<AppError<ErrorTypes>['matchesScope']>['0'],
'errScope'
>,
): string

This method is used to get a string representation of the AppError instance.

  • parameters:

    1. options?: the same as the matchesScope method, but without the errScope property as it will be the scope of the current error.
  • returns: string

Usage:

const appError = new AppError();
const allErrors = appError.toString();
const validationErrors = appError.toString({includesScope: ['validation']});
const otherErrors = appError.toString({excludesScope: ['validation']});

throw()

visibility : protected

type throw = (): never

throws the AppError instance even if there are no errors.

  • parameters: undefined
  • returns: never

end()

visibility : public

type end (): void

throws the AppError instance if there are errors.

  • parameters: undefined
  • returns: void

Usage:

const appError = new AppError();
appError.end();

ErrorScope

type ErrorScope = (string | symbol)[] | undefined

The error scope is an array of strings and/or symbols that can be used to filter errors based on their scope.

ErrorOptions

type ErrorOptions = {
indentation?: number;
stackTraceConstructor?: Func;
};
  • indentation

    • used to indent the error message for better readability
    • default value is: 4
  • stackTraceConstructor

    • used as Error.captureStackTrace(error, stackTraceConstructor) when throwing an error
    • default value is: AppError.prototype.throw

PushOptions

type PushOptions = { scope?: ErrorScope }