Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add TypeScript types #34

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions lib/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import type { Boom } from "@hapi/boom";
import type { AssertionError } from "assert";

export type BounceErrorType = Error | "system" | "boom" | Record<any, any>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error is incorrect, it should be ErrorConstructor. This doesn't actually change anything, since Record<any, any> matches both Error and ErrorConstructor.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Record<any, any> does not seem appropriate, as it is intended to construct an object type using keys from another object. I would do a simple: { [key: PropertyKey]: any }.


export interface BounceOptions {
/**
* An object which is assigned to the `err`, copying the properties onto the error.
*/
decorate?: Record<any, any>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, Record<any, any> seems to be used wrong. I would do { [key: string]: any }.


/**
* 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"languange" => "language"

* - `'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<
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. I would probably have used function overloads to handle this, but this seems cleaner.

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment line is redundant.

*
* @param err The error.
* @param types {@link BounceErrorTypes}
* @param options {@link BounceOptions}
*/
export function rethrow<TErr extends Error, TOpts extends BounceOptions>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would allow the any type for the err parameter to match the type in catch (err) {}, allowing for simple usage without casting:

try {
    // whatever
} catch (err) {
    Bounce.rethrow(err, 'system');
    // do your thing...
}

err: TErr,
types: BounceErrorTypes,
options?: TOpts
): BounceReturn<TErr, TOpts>;

/**
* 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<TErr extends Error, TOpts extends BounceOptions>(
err: TErr,
types: BounceErrorTypes,
options?: TOpts
): BounceReturn<TErr, TOpts>;

/**
* 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> | any,
action?: "rethrow" | "ignore",
types?: BounceErrorTypes,
options?: BounceOptions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be Omit<BounceOptions, 'return'>.

): Promise<void>;

/**
* 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;
kanongil marked this conversation as resolved.
Show resolved Hide resolved

/**
* Returns `true` when `err` is an error.
*
* @param err The error.
*/
export function isError(err: unknown): err is Error;
kanongil marked this conversation as resolved.
Show resolved Hide resolved

/**
* 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
Copy link
Contributor

@kanongil kanongil May 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The predicate is missing the Hoek.Error type.

In reality, I would just return a simple boolean. Consumers would need to narrow it further anyway.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I think this is the better:

export function isSystem(err: Error): boolean;
export function isSystem(err: unknown): err is Error;

| EvalError
| RangeError
| ReferenceError
| SyntaxError
| TypeError
| URIError
| AssertionError;
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
],
Expand All @@ -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"
Expand Down
161 changes: 161 additions & 0 deletions test/index.ts
Original file line number Diff line number Diff line change
@@ -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<void>(Bounce.rethrow(new Error(), "system"));
expect.type<void>(Bounce.rethrow(new Error(), "boom"));
expect.type<void>(Bounce.rethrow(new Error(), ["system", "boom"]));
expect.type<void>(Bounce.rethrow(new Error(), { prop: "prop" }));
expect.type<void>(
Bounce.rethrow(new Error(), "system", { decorate: { prop: "prop" } })
);
expect.type<void>(
Bounce.rethrow(new Error(), "system", {
decorate: { prop: "prop" },
override: new CustomErr(),
})
);
expect.type<CustomErr & { prop: string }>(
Bounce.rethrow(new Error(), "system", {
decorate: { prop: "prop" },
override: new CustomErr(),
return: true,
})
);
expect.type<CustomErr>(
Bounce.rethrow(new Error(), "system", {
override: new CustomErr(),
return: true,
})
);
expect.type<Error>(Bounce.rethrow(new Error(), "system", { return: true }));
expect.type<Error & { prop: string }>(
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<void>(Bounce.ignore(new TypeError(), "system"));
expect.type<void>(Bounce.ignore(Boom.internal(), "boom"));
expect.type<void>(Bounce.ignore(new CustomErr(), { customProp: "customProp" }));
expect.type<void>(
Bounce.ignore(new TypeError(), "system", { decorate: { prop: "prop" } })
);
expect.type<void>(
Bounce.ignore(new TypeError(), "system", {
decorate: { prop: "prop" },
override: new CustomErr(),
})
);
expect.type<CustomErr & { prop: string }>(
Bounce.ignore(new TypeError(), "system", {
decorate: { prop: "prop" },
override: new CustomErr(),
return: true,
})
);
expect.type<CustomErr>(
Bounce.ignore(new Error(), "system", {
override: new CustomErr(),
return: true,
})
);
expect.type<Error>(Bounce.ignore(new Error(), "system", { return: true }));
expect.type<Error & { prop: string }>(
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<Promise<void>>(Bounce.background(() => {}));
expect.type<Promise<void>>(Bounce.background(() => {}, "rethrow"));
expect.type<Promise<void>>(Bounce.background(() => {}, "ignore"));
expect.type<Promise<void>>(Bounce.background(() => {}, "rethrow", "system"));
expect.type<Promise<void>>(Bounce.background(() => {}, "rethrow", "boom"));
expect.type<Promise<void>>(
Bounce.background(() => {}, "rethrow", { prop: "prop" })
);
expect.type<Promise<void>>(
Bounce.background(() => {}, "rethrow", "system", {
decorate: { prop: "prop" },
})
);
expect.type<Promise<void>>(
Bounce.background(() => {}, "rethrow", "system", {
decorate: { prop: "prop" },
override: new CustomErr(),
})
);
expect.type<Promise<void>>(
Bounce.background(() => {}, "rethrow", "system", {
decorate: { prop: "prop" },
override: new CustomErr(),
return: true,
})
);
expect.type<Promise<void>>(
Bounce.background(() => {}, "rethrow", "system", {
override: new CustomErr(),
return: true,
})
);
expect.type<Promise<void>>(
Bounce.background(() => {}, "rethrow", "system", { return: true })
);
expect.type<Promise<void>>(
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<boolean>(Bounce.isBoom(""));

// isError
expect.type<boolean>(Bounce.isError(""));

// isSystem
expect.type<boolean>(Bounce.isError(""));