error-ninja
Advanced tools
Comparing version 0.2.0 to 1.0.0
module.exports = { | ||
"extends": "eslint-config-recombix", | ||
"rules": { | ||
"id-length": 0, | ||
"no-console": 0, | ||
"no-process-exit": 0, | ||
"max-params": 0, | ||
} | ||
}; |
@@ -10,3 +10,3 @@ { | ||
"name": "error-ninja", | ||
"version": "0.2.0", | ||
"version": "1.0.0", | ||
"description": "Allows us to pre-define error types and error messages in each module.", | ||
@@ -27,2 +27,5 @@ "keywords": [ | ||
"main": "errorNinja.js", | ||
"scripts": { | ||
"example": "node ./examples/example.js" | ||
}, | ||
"author": "Josh Cole <saikojosh@gmail.com> (http://www.JoshuaCole.me)", | ||
@@ -33,11 +36,15 @@ "dependencies": {}, | ||
"eslint-config-recombix": "latest", | ||
"eslint-config-vue": "latest", | ||
"eslint-plugin-disable": "latest", | ||
"eslint-plugin-filenames": "latest", | ||
"eslint-plugin-html": "latest", | ||
"eslint-plugin-json": "latest", | ||
"eslint-plugin-node": "latest", | ||
"eslint-plugin-promise": "latest" | ||
"eslint-plugin-promise": "latest", | ||
"eslint-plugin-vue": "latest" | ||
}, | ||
"license": "MIT", | ||
"engines": { | ||
"node": ">6" | ||
"node": ">8.3" | ||
} | ||
} |
287
README.md
# Error-Ninja | ||
Error handling in Node can be a pain, it's tempting to just pass strings around so it's easier to handle, but using a proper Error object is a better way and Error Ninja can help with this. | ||
A handy library for creating custom error constructors that work across async boundaries in Node.js. JavaScript error handling has never been this nice! | ||
## What's the Point? | ||
1. Outputs a human-friendly error message in the terminal. | ||
2. Makes it easy to locate the source of errors in your code by assigning human-readable IDs to every error. | ||
3. Makes it easy for your frontend code to respond to errors without needing to understand the backend implementation. | ||
4. Allows you to decorate errors with extra properties which get printed to the terminal when the error gets thrown. | ||
5. Enables intelligent handling of errors using the extra properties added to the error e.g. `if (err.id === 'XXX') {}`. | ||
6. Errors from other modules or the Node.js core can be converted to ErrorNinja errors using `ErrorNinja.convert()`. | ||
7. Can be used with Promises to quit the process in a .catch() block without causing any warnings. | ||
8. Behaves just like a traditional JavaScript Error - can be thrown and caught. | ||
9. Makes it easy to debug in development AND production. | ||
10. Allows you to detect if a variable is a JavaScript error or an ErrorNinja error. | ||
## What's this for? | ||
This library is for you if you want to: | ||
## Define Your Error Messages | ||
First, at the top of a module define the dictionary of error IDs and error messages that will be available to that module. | ||
1) Maintain error stack traces across async boundaries. | ||
2) Throw and catch errors to indicate problems instead of relying on function return values. | ||
3) Implement custom error constructors e.g. `new DataStreamError()`. | ||
4) Attach data/properties to errors to give them additional context. | ||
5) Understand how bugs are triggered and how they bubble up your call stack. | ||
6) Work with native JavaScript errors as well as your own. | ||
7) Make your debugging life easier. | ||
8) Add an ID property to errors to make them easy to log and trace in production. | ||
9) Add more power to your errors without altering their native JavaScript and Node.js functionality. | ||
## Caveats | ||
* If you throw an error and fail to catch it you will only get the final stack trace in the terminal. This is designed behaviour so we don't impact the performance of your application by directly reading the stack traces from the full chain of errors. Bottom line: always catch errors! | ||
* Using `fullInsight: true` is recommended for development but not for production as it maintains a reference to every error that added to the chain. | ||
## Quick Start | ||
See the `./examples/example.js` file for a working example or execute: `npm run example`. | ||
```javascript | ||
const ErrorNinja = require('error-ninja').define({ | ||
INVALID_BLOG_ID: 'The given blog ID does not exist!', | ||
ATTACHMENT_TOO_BIG: 'You cannot upload an attachment that big!', | ||
SOME_ERROR: 'Woah, this code is buggy!', | ||
const setupErrorNinja = require(`error-ninja`); | ||
const createErrorClass = setupErrorNinja({ | ||
stackTraceLimit: 20, // Expand the maximum number of stack trace frames for all errors. | ||
fullInsight: true, // Capture full errors and stack traces instead of just the error name, ID and message. | ||
}); | ||
``` | ||
## Create and Throw Errors | ||
Now you can create an error. You can throw this error just like you would do normally. | ||
```javascript | ||
const err = new ErrorNinja('INVALID_BLOG_ID'); | ||
throw err; | ||
const DataStreamError = createErrorClass(`DataStreamError`); | ||
const FatalError = createErrorClass(`FatalError`); | ||
/* | ||
Outputs... | ||
Error [INVALID_BLOG_ID]: The given blog ID does not exist! | ||
at new ErrorNinja (/path/to/Error-Ninja/errorNinja.js:36:19) | ||
at Object.<anonymous> (/path/to/myScript.js:59:13) | ||
at Module._compile (module.js:456:26) | ||
at Object.Module._extensions..js (module.js:474:10) | ||
at Module.load (module.js:356:32) | ||
at Function.Module._load (module.js:312:12) | ||
at Function.Module.runMain (module.js:497:10) | ||
at startup (node.js:119:16) | ||
at node.js:906:3 | ||
*/ | ||
``` | ||
try { | ||
## Include Data in the Error | ||
If you need to include some extra properties in the error you can pass a second parameter to the error constructor. By default this data will be output to the terminal if the error is thrown, and you'll also be able to access it when handling your error. | ||
```javascript | ||
const err = new ErrorNinja('ATTACHMENT_TOO_BIG', { fileSize: 17483, maxSize: 1024, name: 'crash.log' }); | ||
console.log(err.data.fileSize); | ||
throw err; | ||
// ...something bad happens... | ||
/* | ||
Outputs... | ||
Error [ATTACHMENT_TOO_BIG]: You cannot upload an attachment that big! | ||
---------- | ||
{"fileSize":17483,"maxSize":1024,"name":"crash.log"} | ||
---------- | ||
at new ErrorNinja (/path/to/Error-Ninja/errorNinja.js:36:19) | ||
at Object.<anonymous> (/path/to/myScript.js:59:13) | ||
at Module._compile (module.js:456:26) | ||
at Object.Module._extensions..js (module.js:474:10) | ||
at Module.load (module.js:356:32) | ||
at Function.Module._load (module.js:312:12) | ||
at Function.Module.runMain (module.js:497:10) | ||
at startup (node.js:119:16) | ||
at node.js:906:3 | ||
*/ | ||
throw new DataStreamError(`NETWORK_DISC`, `The network disconnected.`, { status: 500, uri: `...` }); | ||
} | ||
catch (err) { | ||
const newErr = FatalError.chain(err, `DATA_INTERRUPTED`, `Unable to load the resource.`); | ||
console.error(newErr.pretty(true)); | ||
} | ||
``` | ||
## Turn Off Data Console Output | ||
To prevent the extra error data being output in the terminal you can do one of two things: | ||
## Example Code | ||
1) Pass _false_ as the 3rd argument when creating an error: | ||
#### Chaining errors together | ||
The magic of ErrorNinja occurs when you chain errors together. This allows you to throw and catch as much as you like and obtain a full understanding of how errors propagate through your call stack. | ||
```javascript | ||
const err = new ErrorNinja('SOME_ERROR', { abc: 'do-not-output-this' }, false); | ||
const someError = new Error(`Something bad happened`); | ||
const anotherError = DataStreamError.chain(someError, `DATA_INTERRUPTED`, `Failed to load the resource.`); | ||
const newError = FatalError.chain(someError, `FATAL`, `Unable to continue.`); | ||
``` | ||
2) Or you can turn off data output for all errors created by this instance of Error Ninja by passing in a config option when defining your error dictionary. | ||
#### Understanding the cause of an error | ||
By default err.cause() returns an array of the IDs and messages for the chained errors up to this point. If `true` is passed as the first parameter and the `fullInsight` option is enabled it will return the original error objects. | ||
```javascript | ||
const ErrorNinja = require('error-ninja').define({ ... }, { outputData: false }); | ||
const newErr = FatalError.chain(someError, `DATA_INTERRUPTED`, `Unable to load the resource.`); | ||
const causes = newErr.cause(); | ||
causes.forEach(cause => console.error(`Cause:`, cause)); | ||
``` | ||
## Access Useful Properties in the Error | ||
Apart from the usual error properties you can also access the following additional properties that should help with handling your errors. | ||
#### Pretty printing a chain of errors to the terminal | ||
By default err.pretty() returns a formatted string containing the IDs and messages for the chained errors up to this point. If `true` is passed as the first parameter and the `fullInsight` option is enabled it will add the stack traces to the output. | ||
```javascript | ||
const err = new ErrorNinja('ATTACHMENT_TOO_BIG', { fileSize: 17483, maxSize: 1024, name: 'crash.log' }); | ||
err.id; // "ATTACHMENT_TOO_BIG" | ||
err.isError; // True | ||
err.isNinja; // True | ||
err.human; // "You cannot upload an attachment that big!" | ||
err.data; // (mixed) e.g. { fileSize: 17483, maxSize: 1024, name: 'crash.log' } | ||
// data defaults to {} | ||
const newErr = FatalError.chain(someError, `DATA_INTERRUPTED`, `Unable to load the resource.`); | ||
const prettyOutput = newErr.pretty(true); | ||
console.error(prettyOutput); | ||
``` | ||
## Convert an existing error to an ErrorNinja | ||
You can convert ordinary errors created by the Node.js core or other modules to ErrorNinja errors without throwing them: | ||
## Example Output | ||
Some sample output from the `examples.js` file. | ||
#### Output when calling `newErr.cause()`: | ||
```javascript | ||
doSomethingAsync((err, result) => { | ||
err = ErrorNinja.convert(err, 'MY_ERROR_ID'/* , data, outputData */); | ||
ErrorNinja.isNinja(err); // true. | ||
throw err; | ||
}); | ||
[ { name: 'FatalError', | ||
id: 'CRASHED', | ||
message: 'A fatal error occured.', | ||
data: { someProperty: 123 }, | ||
__isLite: true }, | ||
{ name: 'VideoStreamError', | ||
id: 'CONNECT_FAILED', | ||
message: 'Unable to connect to the video server.', | ||
data: {}, | ||
__isLite: true }, | ||
{ name: 'UriError', | ||
id: 'INVALID_URI', | ||
message: 'The specified URI is not a string.', | ||
data: { uri: null, typeOf: 'object' }, | ||
__isLite: true } ] | ||
``` | ||
You can also convert and throw them immediately: | ||
#### Output when calling `newErr.cause(true)`: | ||
```javascript | ||
doSomethingAsync((err, result) => { | ||
if (err) { ErrorNinja.throw(err, 'MY_ERROR_ID'/* , data, outputData */); } | ||
}); | ||
[ { FatalError: [CRASHED] A fatal error occured. | ||
at startVideoStream (/Users/josh/Repositories/Personal/Error-Ninja/examples/example.js:71:31) | ||
at process._tickCallback (internal/process/next_tick.js:68:7) | ||
at Function.Module.runMain (internal/modules/cjs/loader.js:744:11) | ||
at startup (internal/bootstrap/node.js:285:19) | ||
at bootstrapNodeJSCore (internal/bootstrap/node.js:739:3) | ||
__options: { fullInsight: true }, | ||
name: 'FatalError', | ||
id: 'CRASHED', | ||
__originalMsg: 'A fatal error occured.', | ||
__chain: [ [NinjaError], [ErrorClass] ], | ||
__isErrorNinja: true, | ||
__isWrapped: false, | ||
__isLite: false, | ||
data: { someProperty: 123 } }, | ||
{ VideoStreamError: [CONNECT_FAILED] Unable to connect to the video server. | ||
at startVideoStream (/Users/josh/Repositories/Personal/Error-Ninja/examples/example.js:71:31) | ||
at process._tickCallback (internal/process/next_tick.js:68:7) | ||
at Function.Module.runMain (internal/modules/cjs/loader.js:744:11) | ||
at startup (internal/bootstrap/node.js:285:19) | ||
at bootstrapNodeJSCore (internal/bootstrap/node.js:739:3) | ||
__options: { fullInsight: true }, | ||
name: 'VideoStreamError', | ||
id: 'CONNECT_FAILED', | ||
__originalMsg: 'Unable to connect to the video server.', | ||
__chain: [ [ErrorClass] ], | ||
__isErrorNinja: true, | ||
__isWrapped: false, | ||
__isLite: false, | ||
data: undefined }, | ||
{ UriError: [INVALID_URI] The specified URI is not a string. | ||
at connectToVideoServer (/Users/josh/Repositories/Personal/Error-Ninja/examples/example.js:52:26) | ||
at startVideoStream (/Users/josh/Repositories/Personal/Error-Ninja/examples/example.js:66:9) | ||
at Object.<anonymous> (/Users/josh/Repositories/Personal/Error-Ninja/examples/example.js:97:1) | ||
at Module._compile (internal/modules/cjs/loader.js:688:30) | ||
at Object.Module._extensions..js (internal/modules/cjs/loader.js:699:10) | ||
at Module.load (internal/modules/cjs/loader.js:598:32) | ||
at tryModuleLoad (internal/modules/cjs/loader.js:537:12) | ||
at Function.Module._load (internal/modules/cjs/loader.js:529:3) | ||
at Function.Module.runMain (internal/modules/cjs/loader.js:741:12) | ||
at startup (internal/bootstrap/node.js:285:19) | ||
at bootstrapNodeJSCore (internal/bootstrap/node.js:739:3) | ||
__options: { fullInsight: true }, | ||
name: 'UriError', | ||
id: 'INVALID_URI', | ||
__originalMsg: 'The specified URI is not a string.', | ||
__chain: [], | ||
__isErrorNinja: true, | ||
__isWrapped: false, | ||
__isLite: false, | ||
data: { uri: null, typeOf: 'object' } } ] | ||
``` | ||
Alternatively, you can convert the error and quit the Node.js process with a non-zero error code (which will be the ID of your error) without throwing it. The error and its stack trace will be printed to the terminal in the same way they would if you had thrown the error. This prevents Node.js from complaining when you need to output an error in a Promise .catch() block whilst terminating the process. | ||
#### Output when calling `err.pretty()`: | ||
```javascript | ||
doSomethingAsync() | ||
.then(result => { | ||
// Do something else here... | ||
}) | ||
.catch(ErrorNinja.quit('MY_ERROR_ID')); // Takes the "err" param passed to the .catch() block and converts it. | ||
``` | ||
=========================================================================== | ||
<1> FatalError: [CRASHED] A fatal error occured. | ||
--------------------------------------------------------------------------- | ||
@property {number} someProperty: <123> | ||
=========================================================================== | ||
<2> VideoStreamError: [CONNECT_FAILED] Unable to connect to the video server. | ||
=========================================================================== | ||
<3> UriError: [INVALID_URI] The specified URI is not a string. | ||
--------------------------------------------------------------------------- | ||
@property {object} uri: <null> | ||
@property {string} typeOf: <object> | ||
=========================================================================== | ||
``` | ||
## How to Tell if a Variable is an Error? | ||
ErrorNinja provides two methods to detect if a variable is an error, or more specifically an ErrorNinja error. | ||
#### Output when calling `err.pretty(true)`: | ||
```javascript | ||
// Ordinary JavaScript errors. | ||
const err1 = new Error(); | ||
ErrorNinja.isError(err1)) // true | ||
ErrorNinja.isNinja(err1)) // false | ||
// ErrorNinja errors. | ||
const err2 = new ErrorNinja('...'); | ||
ErrorNinja.isError(err2)) // true | ||
ErrorNinja.isNinja(err2)) // true | ||
``` | ||
=========================================================================== | ||
<1> FatalError: [CRASHED] A fatal error occured. | ||
=========================================================================== | ||
@property {number} someProperty: <123> | ||
--------------------------------------------------------------------------- | ||
at startVideoStream (/Users/josh/Repositories/Personal/Error-Ninja/examples/example.js:71:31) | ||
at process._tickCallback (internal/process/next_tick.js:68:7) | ||
at Function.Module.runMain (internal/modules/cjs/loader.js:744:11) | ||
at startup (internal/bootstrap/node.js:285:19) | ||
at bootstrapNodeJSCore (internal/bootstrap/node.js:739:3) | ||
=========================================================================== | ||
<2> VideoStreamError: [CONNECT_FAILED] Unable to connect to the video server. | ||
--------------------------------------------------------------------------- | ||
at startVideoStream (/Users/josh/Repositories/Personal/Error-Ninja/examples/example.js:71:31) | ||
at process._tickCallback (internal/process/next_tick.js:68:7) | ||
at Function.Module.runMain (internal/modules/cjs/loader.js:744:11) | ||
at startup (internal/bootstrap/node.js:285:19) | ||
at bootstrapNodeJSCore (internal/bootstrap/node.js:739:3) | ||
=========================================================================== | ||
<3> UriError: [INVALID_URI] The specified URI is not a string. | ||
=========================================================================== | ||
@property {object} uri: <null> | ||
@property {string} typeOf: <object> | ||
--------------------------------------------------------------------------- | ||
at connectToVideoServer (/Users/josh/Repositories/Personal/Error-Ninja/examples/example.js:52:26) | ||
at startVideoStream (/Users/josh/Repositories/Personal/Error-Ninja/examples/example.js:66:9) | ||
at Object.<anonymous> (/Users/josh/Repositories/Personal/Error-Ninja/examples/example.js:97:1) | ||
at Module._compile (internal/modules/cjs/loader.js:688:30) | ||
at Object.Module._extensions..js (internal/modules/cjs/loader.js:699:10) | ||
at Module.load (internal/modules/cjs/loader.js:598:32) | ||
at tryModuleLoad (internal/modules/cjs/loader.js:537:12) | ||
at Function.Module._load (internal/modules/cjs/loader.js:529:3) | ||
at Function.Module.runMain (internal/modules/cjs/loader.js:741:12) | ||
at startup (internal/bootstrap/node.js:285:19) | ||
at bootstrapNodeJSCore (internal/bootstrap/node.js:739:3) | ||
=========================================================================== | ||
``` |
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
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
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
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
338
1
214
24012
10
2