diff --git a/lib/index.d.ts b/lib/index.d.ts new file mode 100644 index 0000000..6b85ced --- /dev/null +++ b/lib/index.d.ts @@ -0,0 +1,124 @@ +import type { Boom } from "@hapi/boom"; +import type { AssertionError } from "assert"; + +export type BounceErrorType = Error | "system" | "boom" | Record; + +export interface BounceOptions { + /** + * An object which is assigned to the `err`, copying the properties onto the error. + */ + decorate?: Record; + + /** + * An error used to override `err` when `err` matches. If used with `decorate`, the `override` object is modified. + */ + override?: Error; + + /** + * If `true`, the error is returned instead of thrown. Defaults to `false`. + * @defaultValue `false` + */ + return?: boolean; +} + +/** + * A single item or an array of items of: + * - An error constructor (e.g. `SyntaxError`). + * - `'system'` - matches any languange native error or node assertions. + * - `'boom'` - matches [**boom**](https://github.com/hapijs/boom) errors. + * - an object where each property is compared with the error and must match the error property + * value. All the properties in the object must match the error but do not need to include all + * the error properties. + */ +export type BounceErrorTypes = BounceErrorType | BounceErrorType[]; + +export type BounceReturn< + TErr extends Error, + TOpts extends BounceOptions +> = TOpts extends { return: true } + ? TOpts extends { decorate: any } + ? (TOpts extends { override: any } ? TOpts["override"] : TErr) & + TOpts["decorate"] + : TOpts extends { override: any } + ? TOpts["override"] + : TErr + : void; + +/** + * Throws the error passed if it matches any of the specified rules where: + * - `err` - the error. + * + * @param err The error. + * @param types {@link BounceErrorTypes} + * @param options {@link BounceOptions} + */ +export function rethrow( + err: TErr, + types: BounceErrorTypes, + options?: TOpts +): BounceReturn; + +/** + * The opposite action of {@link rethrow `rethrow()`}. Ignores any errors matching the specified `types`. Any error not matching is thrown after applying the `options`. + * + * @param err The error. + * @param types same as the {@link BounceErrorTypes `types`} argument passed to `rethrow()` + * @param options same as the {@link BounceOptions `options`} argument passed to `rethrow()` + */ +export function ignore( + err: TErr, + types: BounceErrorTypes, + options?: TOpts +): BounceReturn; + +/** + * Awaits for the value to resolve in the background and then apply either the `rethrow()` or `ignore()` actions. + * + * @param operation a function, promise, or value that is `await`ed on inside a `try...catch` and any error thrown processed by the `action` rule. + * @param action one of `'rethrow'` or `'ignore'`. Defaults to `'rethrow'`. + * @param types same as the `types` argument passed to `rethrow()` or `ignore()`. Defaults to `'system'`. + * @param options same as the {@link BounceOptions `options`} argument passed to `rethrow()` or `ignore()`. + */ +export function background( + operation: Function | Promise | any, + action?: "rethrow" | "ignore", + types?: BounceErrorTypes, + options?: BounceOptions +): Promise; + +/** + * Returns `true` when `err` is a [**boom**](https://github.com/hapijs/boom) error. + * + * @param err The error. + */ +export function isBoom(err: unknown): err is Boom; + +/** + * Returns `true` when `err` is an error. + * + * @param err The error. + */ +export function isError(err: unknown): err is Error; + +/** + * Return `true` when `err` is one of: + * - `EvalError` + * - `RangeError` + * - `ReferenceError` + * - `SyntaxError` + * - `TypeError` + * - `URIError` + * - Node's `AssertionError` + * + * @param err The error. + */ +export function isSystem( + err: unknown +): err is + | EvalError + | RangeError + | ReferenceError + | SyntaxError + | TypeError + | URIError + | AssertionError; diff --git a/package.json b/package.json index 84a82ec..887e937 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "version": "3.0.0", "repository": "git://github.com/hapijs/bounce", "main": "lib/index.js", + "types": "lib/index.d.ts", "files": [ "lib" ], @@ -23,10 +24,12 @@ "devDependencies": { "@hapi/code": "^9.0.0", "@hapi/eslint-plugin": "*", - "@hapi/lab": "25.0.0-beta.1" + "@hapi/lab": "^25.0.1", + "@types/node": "^14.18.18", + "typescript": "^4.7.2" }, "scripts": { - "test": "lab -a @hapi/code -t 100 -L", + "test": "lab -a @hapi/code -t 100 -L -Y", "test-cov-html": "lab -a @hapi/code -r html -o coverage.html -L" }, "license": "BSD-3-Clause" diff --git a/test/index.ts b/test/index.ts new file mode 100644 index 0000000..d77f0ef --- /dev/null +++ b/test/index.ts @@ -0,0 +1,161 @@ +import * as Bounce from ".."; +import * as Boom from "@hapi/boom"; +import * as Lab from "@hapi/lab"; + +const { expect } = Lab.types; + +class CustomErr extends Error { + customProp = "customProp"; +} + +// rethrow +expect.type(Bounce.rethrow(new Error(), "system")); +expect.type(Bounce.rethrow(new Error(), "boom")); +expect.type(Bounce.rethrow(new Error(), ["system", "boom"])); +expect.type(Bounce.rethrow(new Error(), { prop: "prop" })); +expect.type( + Bounce.rethrow(new Error(), "system", { decorate: { prop: "prop" } }) +); +expect.type( + Bounce.rethrow(new Error(), "system", { + decorate: { prop: "prop" }, + override: new CustomErr(), + }) +); +expect.type( + Bounce.rethrow(new Error(), "system", { + decorate: { prop: "prop" }, + override: new CustomErr(), + return: true, + }) +); +expect.type( + Bounce.rethrow(new Error(), "system", { + override: new CustomErr(), + return: true, + }) +); +expect.type(Bounce.rethrow(new Error(), "system", { return: true })); +expect.type( + Bounce.rethrow(new Error(), "system", { + decorate: { prop: "prop" }, + return: true, + }) +); + +expect.error(Bounce.rethrow(new Error(), "systm")); +expect.error(Bounce.rethrow(new Error(), "bom")); +expect.error(Bounce.rethrow(new Error(), ["system", "bom"])); +expect.error(Bounce.rethrow(new Error(), "system", { decorate: true })); +expect.error( + Bounce.rethrow(new Error(), "system", { override: { prop: "prop" } }) +); +expect.error(Bounce.rethrow(new Error(), "system", { return: 1 })); + +// ignore +expect.type(Bounce.ignore(new TypeError(), "system")); +expect.type(Bounce.ignore(Boom.internal(), "boom")); +expect.type(Bounce.ignore(new CustomErr(), { customProp: "customProp" })); +expect.type( + Bounce.ignore(new TypeError(), "system", { decorate: { prop: "prop" } }) +); +expect.type( + Bounce.ignore(new TypeError(), "system", { + decorate: { prop: "prop" }, + override: new CustomErr(), + }) +); +expect.type( + Bounce.ignore(new TypeError(), "system", { + decorate: { prop: "prop" }, + override: new CustomErr(), + return: true, + }) +); +expect.type( + Bounce.ignore(new Error(), "system", { + override: new CustomErr(), + return: true, + }) +); +expect.type(Bounce.ignore(new Error(), "system", { return: true })); +expect.type( + Bounce.ignore(new Error(), "system", { + decorate: { prop: "prop" }, + return: true, + }) +); + +expect.error(Bounce.ignore(new Error(), "systm")); +expect.error(Bounce.ignore(new Error(), "bom")); +expect.error(Bounce.ignore(new Error(), "system", { decorate: true })); +expect.error( + Bounce.ignore(new TypeError(), "system", { override: { prop: "prop" } }) +); +expect.error(Bounce.ignore(new Error(), "system", { return: 1 })); + +// background +expect.type>(Bounce.background(() => {})); +expect.type>(Bounce.background(() => {}, "rethrow")); +expect.type>(Bounce.background(() => {}, "ignore")); +expect.type>(Bounce.background(() => {}, "rethrow", "system")); +expect.type>(Bounce.background(() => {}, "rethrow", "boom")); +expect.type>( + Bounce.background(() => {}, "rethrow", { prop: "prop" }) +); +expect.type>( + Bounce.background(() => {}, "rethrow", "system", { + decorate: { prop: "prop" }, + }) +); +expect.type>( + Bounce.background(() => {}, "rethrow", "system", { + decorate: { prop: "prop" }, + override: new CustomErr(), + }) +); +expect.type>( + Bounce.background(() => {}, "rethrow", "system", { + decorate: { prop: "prop" }, + override: new CustomErr(), + return: true, + }) +); +expect.type>( + Bounce.background(() => {}, "rethrow", "system", { + override: new CustomErr(), + return: true, + }) +); +expect.type>( + Bounce.background(() => {}, "rethrow", "system", { return: true }) +); +expect.type>( + Bounce.background(() => {}, "rethrow", "system", { + decorate: { prop: "prop" }, + return: true, + }) +); + +expect.error(Bounce.background(() => {}, "rethro")); +expect.error(Bounce.background(() => {}, "ignor")); +expect.error(Bounce.background(() => {}, "rethrow", "systm")); +expect.error(Bounce.background(() => {}, "rethrow", "bom")); +expect.error( + Bounce.background(() => {}, "rethrow", "system", { decorate: true }) +); +expect.error( + Bounce.background(() => {}, "rethrow", "system", { + override: { prop: "prop" }, + }) +); +expect.error(Bounce.background(() => {}, "rethrow", "system", { return: 1 })); + +// isBoom +expect.type(Bounce.isBoom("")); + +// isError +expect.type(Bounce.isError("")); + +// isSystem +expect.type(Bounce.isError(""));