errors-utils
Advanced tools
Comparing version 0.1.0 to 0.2.0
@@ -0,1 +1,107 @@ | ||
/** | ||
* ```sh | ||
* npm install errors-utils | ||
* ``` | ||
* | ||
* ## Examples | ||
* | ||
* ### Library errors with metadata | ||
* | ||
* ```ts | ||
* import { assertAs, createNamespaceError } from 'errors-utils' | ||
* import { name, version } from '../package.json' // use metadata from package.json | ||
* | ||
* const LibError = createNamespaceError('LIB', { package: name, version }) | ||
* | ||
* const input = 'any' | ||
* try { | ||
* assertAs(typeof input === 'string', LibError, 120, 'Input must be string') | ||
* assertAs(input === 'foo', LibError, 123, 'Input must be foo') | ||
* } catch (error) { | ||
* console.log(error.toString()) // '[LIB123] Input must be foo' | ||
* throw new LibError(10, 'Input validation failed', error) // Wrap thrown error | ||
* } | ||
* ``` | ||
* | ||
* ### Custom assertions | ||
* | ||
* ```ts | ||
* import { StackError, assertAs, createNamespaceError } from 'errors-utils' | ||
* | ||
* function createAssert(ErrorClass: typeof StackError) { | ||
* // Our assert function will use the provided Error class and default message | ||
* function assert(condition: boolean, code: string | number, message = 'Assertion failed') { | ||
* return assertAs(condition, ErrorClass, code, message) | ||
* } | ||
* | ||
* assert.equal = (a, b, code = 11, msg = `${a} must be equal to ${b}`) => { | ||
* return assert(a === b, code, msg) | ||
* } | ||
* assert.notEqual = (a, b, code = 12, msg = `${a} must not be equal to ${b}`) => { | ||
* return assert(a !== b, code, msg) | ||
* } | ||
* // ... | ||
* | ||
* return assert | ||
* } | ||
* | ||
* const LibError = createNamespaceError('LIB') | ||
* const assert = createAssert(LibError) | ||
* assert.equal(a, b) | ||
* ``` | ||
* | ||
* ### Error classes extensions | ||
* | ||
* ```ts | ||
* import { assertAs, createNamespaceError } from 'errors-utils' | ||
* | ||
* // ProtocolError is used for clients interactions | ||
* | ||
* class ProtocolError extends createNamespaceError('PTL') { | ||
* toAPI(response) { ... } | ||
* } | ||
* | ||
* const PROTOCOL_VERSION = 2 | ||
* | ||
* function assertProtocolVersion(version: number) { | ||
* return assertAs(version === PROTOCOL_VERSION, ProtocolError, 1, `Invalid protocol version: expected ${PROTOCOL_VERSION}, got ${version}`) | ||
* } | ||
* | ||
* function handleAPICall(request, response) { | ||
* try { | ||
* assertProtocolVersion(request.body.version) | ||
* } catch (error) { | ||
* if (error instanceof ProtocolError) { | ||
* return error.toAPI(response) | ||
* } | ||
* } | ||
* } | ||
* | ||
* // InternalError is used for platform interactions | ||
* | ||
* class InternalError extends createNamespaceError('INT') { | ||
* // Attach logger to instance | ||
* logger: Logger = myLogger | ||
* | ||
* log(level = 'critical') { | ||
* this.logger.log({ level, code: this.code, message: this.message }) | ||
* } | ||
* } | ||
* | ||
* function assertValidService(service) { | ||
* return assertAs(service instanceof Service, InternalError, 123, 'Invalid service provided') | ||
* } | ||
* | ||
* function checkConfig(config) { | ||
* try { | ||
* assertValidService(config.myService) | ||
* ... | ||
* } catch (err) { | ||
* if (error instanceof InternalError) { | ||
* error.log() | ||
* } | ||
* } | ||
* } | ||
* ``` | ||
*/ | ||
/// <reference types="node" /> | ||
@@ -10,2 +116,6 @@ export declare type StackErrorJSON = { | ||
export declare class StackError extends Error { | ||
/** | ||
* Casts an `Error` to a `StackError`, using the given `code`. Calling this function with an | ||
* instance of `StackError` will return the input unchanged. | ||
*/ | ||
static from(error: Error, code?: string): StackError; | ||
@@ -19,5 +129,14 @@ static fromJSON(json: StackErrorJSON): StackError; | ||
toErrorStack(): Array<StackError>; | ||
/** | ||
* Serializes the error to JSON. By default the `errorStack` is included on a single level, | ||
* setting the `withStack` argument to `false` will serialize the error only, discarding the | ||
* stack. | ||
*/ | ||
toJSON(withStack?: boolean): StackErrorJSON; | ||
toString(): string; | ||
} | ||
/** | ||
* Factory for an Error class extending `StackError` with a given `namespace` and optional | ||
* `metadata`. | ||
*/ | ||
export declare function createNamespaceError(namespace: string, metadata?: Record<string, unknown>): { | ||
@@ -30,2 +149,7 @@ new (code: string | number, message: string, wrapError?: Error | undefined): { | ||
toErrorStack(): StackError[]; | ||
/** | ||
* Serializes the error to JSON. By default the `errorStack` is included on a single level, | ||
* setting the `withStack` argument to `false` will serialize the error only, discarding the | ||
* stack. | ||
*/ | ||
toJSON(withStack?: boolean): StackErrorJSON; | ||
@@ -36,2 +160,6 @@ toString(): string; | ||
}; | ||
/** | ||
* Casts an `Error` to a `StackError`, using the given `code`. Calling this function with an | ||
* instance of `StackError` will return the input unchanged. | ||
*/ | ||
from(error: Error, code?: string): StackError; | ||
@@ -43,3 +171,10 @@ fromJSON(json: StackErrorJSON): StackError; | ||
}; | ||
/** | ||
* Asserts the given `condition` is true or throws an Error with the given `message`. | ||
*/ | ||
export declare function assert(condition: boolean, message?: string): asserts condition; | ||
/** | ||
* Asserts the given `condition` is true or throws an Error using the given `ErrorClass` and | ||
* associated arguments. | ||
*/ | ||
export declare function assertAs<T extends typeof StackError>(condition: boolean, ErrorClass: T, ...args: ConstructorParameters<T>): asserts condition; |
@@ -1,8 +0,93 @@ | ||
'use strict' | ||
if (process.env.NODE_ENV === 'production') { | ||
module.exports = require('./errors-utils.cjs.production.min.js') | ||
} else { | ||
module.exports = require('./errors-utils.cjs.development.js') | ||
export class StackError extends Error { | ||
/** | ||
* Casts an `Error` to a `StackError`, using the given `code`. Calling this function with an | ||
* instance of `StackError` will return the input unchanged. | ||
*/ static from(error, code = 'SE0') { | ||
if (error instanceof StackError) { | ||
return error; | ||
} | ||
const se = new StackError(code, error.message); | ||
se.stack = error.stack; | ||
return se; | ||
} | ||
static fromJSON(json) { | ||
const error = new StackError(json.code, json.message); | ||
error.errorStack = (json.stack ?? []).reduceRight((stack, e)=>{ | ||
const err = StackError.fromJSON(e); | ||
err.errorStack = stack; | ||
return [ | ||
err, | ||
...stack | ||
]; | ||
}, []); | ||
error.metadata = json.metadata ?? {}; | ||
error.name = json.name ?? 'StackError'; | ||
return error; | ||
} | ||
toErrorStack() { | ||
return [ | ||
this, | ||
...this.errorStack | ||
]; | ||
} | ||
/** | ||
* Serializes the error to JSON. By default the `errorStack` is included on a single level, | ||
* setting the `withStack` argument to `false` will serialize the error only, discarding the | ||
* stack. | ||
*/ toJSON(withStack = true) { | ||
return { | ||
code: this.code, | ||
message: this.message, | ||
metadata: this.metadata, | ||
name: this.name, | ||
stack: withStack ? this.errorStack.map((e)=>e.toJSON(false) | ||
) : [] | ||
}; | ||
} | ||
toString() { | ||
return `[${this.code}] ${this.message}`; | ||
} | ||
constructor(code, message, wrapError){ | ||
super(message); | ||
this.metadata = {}; | ||
this.name = 'StackError'; | ||
Object.setPrototypeOf(this, StackError.prototype); | ||
if (Error.captureStackTrace) { | ||
Error.captureStackTrace(this, StackError); | ||
} | ||
this.code = code; | ||
this.errorStack = wrapError ? StackError.from(wrapError).toErrorStack() : []; | ||
} | ||
} | ||
/** | ||
* Factory for an Error class extending `StackError` with a given `namespace` and optional | ||
* `metadata`. | ||
*/ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type | ||
export function createNamespaceError(namespace, metadata = {}) { | ||
return class NamespaceError extends StackError { | ||
constructor(code, message, wrapError){ | ||
super(`${namespace}${code}`, message, wrapError); | ||
Object.setPrototypeOf(this, NamespaceError.prototype); | ||
if (Error.captureStackTrace) { | ||
Error.captureStackTrace(this, NamespaceError); | ||
} | ||
this.metadata = metadata; | ||
} | ||
}; | ||
} | ||
/** | ||
* Asserts the given `condition` is true or throws an Error with the given `message`. | ||
*/ export function assert(condition, message = 'Assertion failed') { | ||
if (!condition) { | ||
throw new Error(message); | ||
} | ||
} | ||
/** | ||
* Asserts the given `condition` is true or throws an Error using the given `ErrorClass` and | ||
* associated arguments. | ||
*/ export function assertAs(condition, ErrorClass, ...args) { | ||
if (!condition) { | ||
// @ts-ignore args | ||
throw new ErrorClass(...args); | ||
} | ||
} |
{ | ||
"name": "errors-utils", | ||
"version": "0.2.0", | ||
"author": "3Box Labs", | ||
"version": "0.1.0", | ||
"license": "MIT", | ||
"license": "(Apache-2.0 OR MIT)", | ||
"homepage": "https://github.com/ceramicnetwork/js-errors-utils#readme", | ||
"keywords": [ | ||
"error", | ||
"utilities" | ||
], | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/ceramicnetwork/js-errors-utils" | ||
}, | ||
"type": "module", | ||
"main": "dist/index.js", | ||
"module": "dist/errors-utils.esm.js", | ||
"typings": "dist/index.d.ts", | ||
"types": "dist/index.d.ts", | ||
"exports": { | ||
".": "./dist/index.js", | ||
"./*": "./dist/*.js" | ||
}, | ||
"files": [ | ||
"dist", | ||
"src" | ||
"dist" | ||
], | ||
"engines": { | ||
"node": ">=12" | ||
"node": ">=16" | ||
}, | ||
"sideEffects": false, | ||
"scripts": { | ||
"start": "tsdx watch", | ||
"build": "tsdx build", | ||
"test": "tsdx test", | ||
"lint": "tsdx lint src test", | ||
"prepare": "tsdx build", | ||
"size": "size-limit", | ||
"analyze": "size-limit --why" | ||
"build": "del dist && tsc --emitDeclarationOnly && swc src -d ./dist", | ||
"docs": "typedoc --tsconfig tsconfig.docs.json", | ||
"lint": "eslint src test --fix", | ||
"test": "yarn node --experimental-vm-modules $(yarn bin jest)", | ||
"prepare": "yarn build", | ||
"prepublishOnly": "package-check" | ||
}, | ||
"dependencies": {}, | ||
"devDependencies": { | ||
"@size-limit/preset-small-lib": "^4.7.0", | ||
"eslint-config-3box": "^0.2.0", | ||
"husky": "^4.3.0", | ||
"size-limit": "^4.7.0", | ||
"tsdx": "^0.14.1", | ||
"tslib": "^2.0.3", | ||
"typescript": "^4.0.5" | ||
"@skypack/package-check": "^0.2.2", | ||
"@swc/cli": "^0.1.55", | ||
"@swc/core": "^1.2.130", | ||
"@swc/jest": "^0.2.15", | ||
"@types/jest": "^27.4.0", | ||
"@types/nanoid": "^3.0.0", | ||
"del-cli": "^4.0.1", | ||
"eslint": "^8.7.0", | ||
"eslint-config-3box": "^0.4.0", | ||
"jest": "^27.4.7", | ||
"prettier": "^2.5.1", | ||
"typedoc": "^0.22.10", | ||
"typedoc-plugin-markdown": "^3.11.11", | ||
"typescript": "^4.5.4" | ||
}, | ||
"husky": { | ||
"hooks": { | ||
"pre-commit": "tsdx lint" | ||
} | ||
}, | ||
"size-limit": [ | ||
{ | ||
"path": "dist/errors-utils.cjs.production.min.js", | ||
"limit": "2 KB" | ||
"jest": { | ||
"extensionsToTreatAsEsm": [ | ||
".ts" | ||
], | ||
"moduleNameMapper": { | ||
"^(\\.{1,2}/.*)\\.js$": "$1" | ||
}, | ||
{ | ||
"path": "dist/errors-utils.esm.js", | ||
"limit": "2 KB" | ||
"transform": { | ||
"^.+\\.(t|j)s$": [ | ||
"@swc/jest" | ||
] | ||
} | ||
] | ||
} | ||
} |
214
README.md
@@ -11,218 +11,6 @@ # Errors utils | ||
## Examples | ||
## [Documentation](./docs/README.md) | ||
### Library errors with metadata | ||
```ts | ||
import { assertAs, createNamespaceError } from 'errors-utils' | ||
import { name, version } from '../package.json' // use metadata from package.json | ||
const LibError = createNamespaceError('LIB', { package: name, version }) | ||
const input = 'any' | ||
try { | ||
assertAs(typeof input === 'string', LibError, 120, 'Input must be string') | ||
assertAs(input === 'foo', LibError, 123, 'Input must be foo') | ||
} catch (error) { | ||
console.log(error.toString()) // '[LIB123] Input must be foo' | ||
throw new LibError(10, 'Input validation failed', error) // Wrap thrown error | ||
} | ||
``` | ||
### Custom assertions | ||
```ts | ||
import { StackError, assertAs, createNamespaceError } from 'errors-utils' | ||
function createAssert(ErrorClass: typeof StackError) { | ||
// Our assert function will use the provided Error class and default message | ||
function assert(condition: boolean, code: string | number, message = 'Assertion failed') { | ||
return assertAs(condition, ErrorClass, code, message) | ||
} | ||
assert.equal = (a, b, code = 11, msg = `${a} must be equal to ${b}`) => { | ||
return assert(a === b, code, msg) | ||
} | ||
assert.notEqual = (a, b, code = 12, msg = `${a} must not be equal to ${b}`) => { | ||
return assert(a !== b, code, msg) | ||
} | ||
// ... | ||
return assert | ||
} | ||
const LibError = createNamespaceError('LIB') | ||
const assert = createAssert(LibError) | ||
assert.equal(a, b) | ||
``` | ||
### Error classes extensions | ||
```ts | ||
import { assertAs, createNamespaceError } from 'errors-utils' | ||
// ProtocolError is used for clients interactions | ||
class ProtocolError extends createNamespaceError('PTL') { | ||
toAPI(response) { ... } | ||
} | ||
const PROTOCOL_VERSION = 2 | ||
function assertProtocolVersion(version: number) { | ||
return assertAs(version === PROTOCOL_VERSION, ProtocolError, 1, `Invalid protocol version: expected ${PROTOCOL_VERSION}, got ${version}`) | ||
} | ||
function handleAPICall(request, response) { | ||
try { | ||
assertProtocolVersion(request.body.version) | ||
} catch (error) { | ||
if (error instanceof ProtocolError) { | ||
return error.toAPI(response) | ||
} | ||
} | ||
} | ||
// InternalError is used for platform interactions | ||
class InternalError extends createNamespaceError('INT') { | ||
// Attach logger to instance | ||
logger: Logger = myLogger | ||
log(level = 'critical') { | ||
this.logger.log({ level, code: this.code, message: this.message }) | ||
} | ||
} | ||
function assertValidService(service) { | ||
return assertAs(service instanceof Service, InternalError, 123, 'Invalid service provided') | ||
} | ||
function checkConfig(config) { | ||
try { | ||
assertValidService(config.myService) | ||
... | ||
} catch (err) { | ||
if (error instanceof InternalError) { | ||
error.log() | ||
} | ||
} | ||
} | ||
``` | ||
## Types | ||
### StackErrorJSON | ||
```ts | ||
type StackErrorJSON = { | ||
code: string | ||
message: string | ||
metadata: Record<string, unknown> | ||
name: string | ||
stack: Array<StackErrorJSON> | ||
} | ||
``` | ||
## StackError class | ||
Extends the built-in `Error` class | ||
### StackError.from() | ||
Casts an `Error` to a `StackError`, using the given `code`. Calling this function with an instance of `StackError` will return the input unchanged. | ||
**Arguments** | ||
1. `error: Error` | ||
1. `code?: string = 'SE0'` | ||
**Returns** `StackError` | ||
### StackError.fromJSON() | ||
**Arguments** | ||
1. `json: StackErrorJSON` | ||
**Returns** `StackError` | ||
### new StackError() | ||
**Arguments** | ||
1. `code: string` | ||
1. `message: string` | ||
1. `wrapError?: Error` | ||
### .code | ||
**Returns** `string` | ||
### .message | ||
**Returns** `string` | ||
### .errorStack | ||
**Returns** `Array<StackError>` based on the `parentError` provided in constructor | ||
### .metadata | ||
**Returns** `Record<string, unknown>` | ||
### .toErrorStack() | ||
**Returns** `Array<StackError>` of all the errors in the stack | ||
### .toJSON() | ||
Serializes the error to JSON. By default the `errorStack` is included on a single level, setting the `withStack` argument to `false` will serialize the error only, discarding the stack. | ||
**Arguments** | ||
1. `withStack: boolean = true` | ||
**Returns** `StackErrorJSON` | ||
## Public APIs | ||
### assert() | ||
Asserts the given `condition` is true or throws an Error with the given `message`. | ||
**Arguments** | ||
1. `condition: boolean` | ||
1. `message?: string = 'Assertion error'` | ||
### assertAs() | ||
Asserts the given `condition` is true or throws an Error using the given `ErrorClass` and associated arguments. | ||
**Arguments** | ||
1. `condition: boolean` | ||
1. `ErrorClass: typeof StackError` | ||
1. `ErrorClass` arguments | ||
### createNamespaceError() | ||
Factory for an Error class extending `StackError` with a given `namespace` and optional `metadata`. | ||
**Arguments** | ||
1. `namespace: string` | ||
1. `metadata?: Record<string, unknown>` | ||
**Returns** `class NamespaceError extends StackError` | ||
The `NamespaceError` class constructor uses the following arguments: | ||
1. `code: string | number` | ||
1. `message: string` | ||
1. `wrapError?: Error` | ||
## License | ||
Apache-2.0 OR MIT |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
1
0
Yes
10737
14
4
268
16
1