- Notifications
You must be signed in to change notification settings - Fork19
Handle errors in a simple, stable, consistent way
License
ehmicky/modern-errors
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
Handle errors in a simple, stable, consistent way.
Simple patterns to:
- ⛑️ Create errorclasses
- 🏷️ Set errorproperties
- 🎀Wrap oraggregate errors
- 🐞 Separate known andunknown errors
Stability:
- 🚨Normalize invalid errors
- 🛡️ 100%test coverage
- 🤓 StrictTypeScript types
modern-errors-cli: Handleerrors in CLI modulesmodern-errors-beautiful:Prettify error messages and stacksmodern-errors-process:Handle process errorsmodern-errors-bugs: Printwhere to report bugsmodern-errors-serialize:Serialize/parse errorsmodern-errors-clean: Cleanstack tracesmodern-errors-http: CreateHTTP error responsesmodern-errors-winston:Log errors with Winstonmodern-errors-switch:Execute class-specific logic- 🔌 Create yourown plugin
Create errorclasses.
importModernErrorfrom'modern-errors'exportconstBaseError=ModernError.subclass('BaseError')exportconstUnknownError=BaseError.subclass('UnknownError')exportconstInputError=BaseError.subclass('InputError')exportconstAuthError=BaseError.subclass('AuthError')exportconstDatabaseError=BaseError.subclass('DatabaseError')
Set errorproperties.
thrownewInputError('Invalid file path',{props:{filePath:'/...'}})
Wrap errors.
try{// ...}catch(cause){thrownewInputError('Could not read the file.',{ cause})}
Normalize errors.
try{throw'Missing file path.'}catch(error){// Normalized from a string to a `BaseError` instancethrowBaseError.normalize(error)}
Useplugins.
importModernErrorfrom'modern-errors'importmodernErrorsSerializefrom'modern-errors-serialize'exportconstBaseError=ModernError.subclass('BaseError',{plugins:[modernErrorsSerialize],})// ...// Serialize error as JSON, then back to identical error instanceconsterror=newInputError('Missing file path.')consterrorString=JSON.stringify(error)constidenticalError=BaseError.parse(JSON.parse(errorString))
npm install modern-errors
If anyplugin is used, it must also be installed.
npm install modern-errors-{pluginName}This package works in both Node.js >=18.18.0 andbrowsers.
This is an ES module. It must be loaded usinganimport orimport() statement,notrequire(). If TypeScript is used, it must be configured tooutput ES modules,not CommonJS.
importModernErrorfrom'modern-errors'exportconstBaseError=ModernError.subclass('BaseError')exportconstUnknownError=BaseError.subclass('UnknownError')exportconstInputError=BaseError.subclass('InputError')exportconstAuthError=BaseError.subclass('AuthError')exportconstDatabaseError=BaseError.subclass('DatabaseError')
Exporting and documenting all error classes allows consumers to check them. Thisalso enables sharing error classes between modules.
if(errorinstanceofInputError){// ...}
ErrorClass.subclass() returns asubclass.Parent classes'options are merged with their subclasses.
exportconstBaseError=ModernError.subclass('BaseError',{props:{isError:true},})exportconstInputError=BaseError.subclass('InputError',{props:{isUserError:true},})consterror=newInputError('...')console.log(error.isError)// trueconsole.log(error.isUserError)// trueconsole.log(errorinstanceofBaseError)// trueconsole.log(errorinstanceofInputError)// true
constInputError=BaseError.subclass('InputError',{props:{isUserError:true},})consterror=newInputError('...')console.log(error.isUserError)// true
consterror=newInputError('...',{props:{isUserError:true}})console.log(error.isUserError)// true
Error properties that are internal or secret can be prefixed with_. Thismakes themnon-enumerable,which prevents iterating or logging them.
consterror=newInputError('...',{props:{userId:6,_isUserError:true},})console.log(error.userId)// 6console.log(error._isUserError)// trueconsole.log(Object.keys(error))// ['userId']console.log(error)// `userId` is logged, but not `_isUserError`
thrownewInputError('Missing file path.')
Any error'smessage,class andoptions can be wrapped using thestandardcause option.
Instead of being set as acause property, the inner error is directlymerged to the outer error,including itsmessage,stack,name,AggregateError.errorsand anyadditional property.
try{// ...}catch(cause){thrownewInputError('Could not read the file.',{ cause})}
The outer error message is appended, unless it is empty. If the outer errormessage ends with: or:\n, it is prepended instead.
constcause=newInputError('File does not exist.')// InputError: File does not exist.thrownewInputError('',{ cause})
// InputError: File does not exist.// Could not read the file.thrownewInputError('Could not read the file.',{ cause})
// InputError: Could not read the file: File does not exist.thrownewInputError(`Could not read the file:`,{ cause})
// InputError: Could not read the file:// File does not exist.thrownewInputError(`Could not read the file:\n`,{ cause})
The outer error's class replaces the inner one.
try{thrownewAuthError('...')}catch(cause){// Now an InputErrorthrownewInputError('...',{ cause})}
Except when the outer error's class is a parent class, such asBaseError.
try{thrownewAuthError('...')}catch(cause){// Still an AuthErrorthrownewBaseError('...',{ cause})}
The outer error'sprops andplugin options are merged.
try{thrownewAuthError('...',innerOptions)}catch(cause){// `outerOptions` are merged with `innerOptions`thrownewBaseError('...',{ ...outerOptions, cause})}
Theerrors option aggregates multiple errors into one. Thisis likenew AggregateError(errors)except that it works with any error class.
constdatabaseError=newDatabaseError('...')constauthError=newAuthError('...')thrownewInputError('...',{errors:[databaseError,authError]})// InputError: ... {// [errors]: [// DatabaseError: ...// AuthError: ...// ]// }
Any error can be directly passed to thecause orerrors option, even if it isinvalid,unknown or notnormalized.
try{// ...}catch(cause){thrownewInputError('...',{ cause})}
Manipulating errors that are notError instancesor that haveinvalid propertiescan lead to unexpected bugs.BaseError.normalize() fixes that.
try{throw'Missing file path.'}catch(invalidError){// This fails: `invalidError.message` is `undefined`console.log(invalidError.message.trim())}
try{throw'Missing file path.'}catch(invalidError){constnormalizedError=BaseError.normalize(invalidError)// This works: 'Missing file path.'// `normalizedError` is a `BaseError` instance.console.log(normalizedError.message.trim())}
Known errors should be handled in atry {} catch {} block andwrapped with aspecific class.That block should only cover the statement that might throw in order to preventcatching other unrelated errors.
try{returnregExp.test(value)}catch(error){// Now an `InputError` instancethrownewInputError('Invalid regular expression:',{cause:error})}
If an error is not handled as describedabove, it isconsideredunknown. This indicates an unexpected exception, usually a bug.BaseError.normalize(error, UnknownError)assigns theUnknownError class to those errors.
exportconstUnknownError=BaseError.subclass('UnknownError')
try{returnregExp.test(value)}catch(error){// Now an `UnknownError` instancethrowBaseError.normalize(error,UnknownError)}
Wrapping a module's main functions withBaseError.normalize(error, UnknownError)ensures every error being thrown isvalid, appliesplugins, and has a class that is eitherknown orUnknownError.
exportconstmain=()=>{try{// ...}catch(error){throwBaseError.normalize(error,UnknownError)}}
Plugins extendmodern-errors features. All available plugins arelisted here.
To use a plugin, please install it, then pass it to theplugins option.
npm install modern-errors-{pluginName}importModernErrorfrom'modern-errors'importmodernErrorsBugsfrom'modern-errors-bugs'importmodernErrorsSerializefrom'modern-errors-serialize'exportconstBaseError=ModernError.subclass('BaseError',{plugins:[modernErrorsBugs,modernErrorsSerialize],})// ...
Please see thefollowing documentation to create your ownplugin.
Most plugins can be configured with options. The option's name is the same asthe plugin.
constoptions={// `modern-errors-bugs` optionsbugs:'https://github.com/my-name/my-project/issues',// `props` can be configured and modified like plugin optionsprops:{userId:5},}
Plugin options can apply to (in priority order):
- Any error: second argument to
ModernError.subclass()
exportconstBaseError=ModernError.subclass('BaseError',options)
- Any error of a specific class (and its subclasses): second argument to
ErrorClass.subclass()
exportconstInputError=BaseError.subclass('InputError',options)
- A specific error: second argument to
new ErrorClass()
thrownewInputError('...',options)
- A plugin method call: last argument, passing only that plugin's options
ErrorClass[methodName](...args,options[pluginName])
error[methodName](...args,options[pluginName])
Thecustom option can be used to provide an errorclasswith additional methods,constructor, properties or options.
exportconstInputError=BaseError.subclass('InputError',{// The `class` must extend from the parent error classcustom:classextendsBaseError{// If a `constructor` is defined, its parameters must be (message, options)// Additional `options` can be defined.constructor(message,options){message+=options?.suffix??''super(message,options)}isUserInput(){// ...}},})consterror=newInputError('Wrong user name',{suffix:': example'})console.log(error.message)// 'Wrong user name: example'console.log(error.isUserInput())
Please see thefollowing documentation for informationabout TypeScript types.
Top-levelErrorClass.
name:stringoptions:ClassOptions?
Creates and returns a childErrorClass.
Type:object
Type:Plugin[]
Type:class extends ErrorClass {}
Custom class to add any methods,constructor or properties.
Anyplugin options can also be specified.
message:stringoptions:InstanceOptions?
Return value:Error
Type:object
Type:any
Inner error beingwrapped.
Type:any[]
Array of errors beingaggregated.
Anyplugin options can also be specified.
error:Error | anyNewErrorClass: subclass ofErrorClass
Return value:Error
Normalizesinvalid errors.
Iferror is an instance ofErrorClass (or one of its subclasses), it is leftas is. Otherwise, it isconverted toNewErrorClass, which defaults toErrorClass itself.
This framework brings together a collection of modules which can also be usedindividually:
error-custom-class: Createone error classerror-class-utils: Utilitiesto properly create error classeserror-serializer: Converterrors to/from plain objectsnormalize-exception:Normalize exceptions/errorsis-error-instance: Check ifa value is anErrorinstancemerge-error-cause: Merge anerror with itscauseset-error-class: Properlyupdate an error's classset-error-message: Properlyupdate an error's messagewrap-error-message:Properly wrap an error's messageset-error-props: Properlyupdate an error's propertiesset-error-stack: Properlyupdate an error's stackhandle-cli-error: 💣 Errorhandler for CLI applications 💥beautiful-error: Prettifyerror messages and stackslog-process-errors: Showsome ❤ to Node.js process errorserror-http-response:Create HTTP error responseswinston-error-format: Logerrors with Winston
For any question,don't hesitate tosubmit an issue on GitHub.
Everyone is welcome regardless of personal background. We enforce aCode of conduct in order to promote a positive andinclusive environment.
This project was made with ❤️. The simplest way to give back is by starring andsharing it online.
If the documentation is unclear or has a typo, please click on the page'sEditbutton (pencil icon) and suggest a correction.
If you would like to help us fix a bug or add a new feature, please check ourguidelines. Pull requests are welcome!
ehmicky 💻🎨🤔📖 | const_var 🤔💬 | Andy Brenneke 🤔💬🐛 | Graham Fisher 🐛 | renzor 💬🤔 | Eugene 💻🐛 | Jonathan Chambers |
heyhey123 🐛 | Benjamin Kroeger 🐛 |
About
Handle errors in a simple, stable, consistent way
Topics
Resources
License
Code of conduct
Contributing
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.