Security News
Supply Chain Attack Detected in Solana's web3.js Library
A supply chain attack has been detected in versions 1.95.6 and 1.95.7 of the popular @solana/web3.js library.
modern-errors
Advanced tools
Handle errors in a simple, stable, consistent way.
Please reach out if you're looking for a Node.js API or CLI engineer (11 years of experience). Most recently I have been Netlify Build's and Netlify Plugins' technical lead for 2.5 years. I am available for full-time remote positions.
Simple patterns to:
Stability:
modern-errors-cli
: Handle
errors in CLI modulesmodern-errors-process
:
Handle process errorsmodern-errors-bugs
: Print
where to report bugsmodern-errors-serialize
:
Serialize/parse errorsmodern-errors-clean
: Clean
stack tracesmodern-errors-http
: Create
HTTP error responsesmodern-errors-winston
:
Log errors with Winstonmodern-errors-switch
:
Execute class-specific logicCreate error classes.
import ModernError from 'modern-errors'
export const BaseError = ModernError.subclass('BaseError')
export const UnknownError = BaseError.subclass('UnknownError')
export const InputError = BaseError.subclass('InputError')
export const AuthError = BaseError.subclass('AuthError')
export const DatabaseError = BaseError.subclass('DatabaseError')
Set error properties.
throw new InputError('Invalid file path', { props: { filePath: '/...' } })
Wrap errors.
try {
// ...
} catch (cause) {
throw new InputError('Could not read the file.', { cause })
}
Normalize errors.
try {
throw 'Missing file path.'
} catch (error) {
// Normalized from a string to a `BaseError` instance
throw BaseError.normalize(error)
}
Use plugins.
import ModernError from 'modern-errors'
import modernErrorsSerialize from 'modern-errors-serialize'
export const BaseError = ModernError.subclass('BaseError', {
plugins: [modernErrorsSerialize],
})
// ...
// Serialize error as JSON, then back to identical error instance
const error = new InputError('Missing file path.')
const errorString = JSON.stringify(error)
const identicalError = BaseError.parse(JSON.parse(errorString))
npm install modern-errors
If any plugin is used, it must also be installed.
npm install modern-errors-{pluginName}
This package works in both Node.js >=18.18.0 and browsers.
This is an ES module. It must be loaded using
an import
or import()
statement,
not require()
. If TypeScript is used, it must be configured to
output ES modules,
not CommonJS.
import ModernError from 'modern-errors'
export const BaseError = ModernError.subclass('BaseError')
export const UnknownError = BaseError.subclass('UnknownError')
export const InputError = BaseError.subclass('InputError')
export const AuthError = BaseError.subclass('AuthError')
export const DatabaseError = BaseError.subclass('DatabaseError')
Exporting and documenting all error classes allows consumers to check them. This also enables sharing error classes between modules.
if (error instanceof InputError) {
// ...
}
ErrorClass.subclass()
returns a
subclass.
Parent classes' options are merged with their subclasses.
export const BaseError = ModernError.subclass('BaseError', {
props: { isError: true },
})
export const InputError = BaseError.subclass('InputError', {
props: { isUserError: true },
})
const error = new InputError('...')
console.log(error.isError) // true
console.log(error.isUserError) // true
console.log(error instanceof BaseError) // true
console.log(error instanceof InputError) // true
const InputError = BaseError.subclass('InputError', {
props: { isUserError: true },
})
const error = new InputError('...')
console.log(error.isUserError) // true
const error = new InputError('...', { props: { isUserError: true } })
console.log(error.isUserError) // true
Error properties that are internal or secret can be prefixed with _
. This
makes them
non-enumerable,
which prevents iterating or logging them.
const error = new InputError('...', {
props: { userId: 6, _isUserError: true },
})
console.log(error.userId) // 6
console.log(error._isUserError) // true
console.log(Object.keys(error)) // ['userId']
console.log(error) // `userId` is logged, but not `_isUserError`
throw new InputError('Missing file path.')
Any error's message, class and
options can be wrapped using the
standard
cause
option.
Instead of being set as a cause
property, the inner error is directly
merged to the outer error,
including its
message
,
stack
,
name
,
AggregateError.errors
and any additional property.
try {
// ...
} catch (cause) {
throw new InputError('Could not read the file.', { cause })
}
The outer error message is appended, unless it is empty. If the outer error
message ends with :
or :\n
, it is prepended instead.
const cause = new InputError('File does not exist.')
// InputError: File does not exist.
throw new InputError('', { cause })
// InputError: File does not exist.
// Could not read the file.
throw new InputError('Could not read the file.', { cause })
// InputError: Could not read the file: File does not exist.
throw new InputError(`Could not read the file:`, { cause })
// InputError: Could not read the file:
// File does not exist.
throw new InputError(`Could not read the file:\n`, { cause })
The outer error's class replaces the inner one.
try {
throw new AuthError('...')
} catch (cause) {
// Now an InputError
throw new InputError('...', { cause })
}
Except when the outer error's class is a parent class, such as
BaseError
.
try {
throw new AuthError('...')
} catch (cause) {
// Still an AuthError
throw new BaseError('...', { cause })
}
The outer error's props
and
plugin options are merged.
try {
throw new AuthError('...', innerOptions)
} catch (cause) {
// `outerOptions` are merged with `innerOptions`
throw new BaseError('...', { ...outerOptions, cause })
}
The errors
option aggregates multiple errors into one. This
is like
new AggregateError(errors)
except that it works with any error class.
const databaseError = new DatabaseError('...')
const authError = new AuthError('...')
throw new InputError('...', { errors: [databaseError, authError] })
// InputError: ... {
// [errors]: [
// DatabaseError: ...
// AuthError: ...
// ]
// }
Any error can be directly passed to the cause
or
errors
option, even if it is invalid,
unknown or not
normalized.
try {
// ...
} catch (cause) {
throw new InputError('...', { cause })
}
Manipulating errors that are not
Error
instances
or that have
invalid properties
can 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) {
const normalizedError = BaseError.normalize(invalidError)
// This works: 'Missing file path.'
// `normalizedError` is a `BaseError` instance.
console.log(normalizedError.message.trim())
}
Known errors should be handled in a try {} catch {}
block and
wrapped with a specific class.
That block should only cover the statement that might throw in order to prevent
catching other unrelated errors.
try {
return regExp.test(value)
} catch (error) {
// Now an `InputError` instance
throw new InputError('Invalid regular expression:', { cause: error })
}
If an error is not handled as described above, it is
considered unknown. This indicates an unexpected exception, usually a bug.
BaseError.normalize(error, UnknownError)
assigns the UnknownError
class to those errors.
export const UnknownError = BaseError.subclass('UnknownError')
try {
return regExp.test(value)
} catch (error) {
// Now an `UnknownError` instance
throw BaseError.normalize(error, UnknownError)
}
Wrapping a module's main functions with
BaseError.normalize(error, UnknownError)
ensures every error being thrown is valid, applies
plugins, and has a class that is either
known or UnknownError
.
export const main = () => {
try {
// ...
} catch (error) {
throw BaseError.normalize(error, UnknownError)
}
}
Plugins extend modern-errors
features. All available plugins are
listed here.
To use a plugin, please install it, then pass it to the
plugins
option.
npm install modern-errors-{pluginName}
import ModernError from 'modern-errors'
import modernErrorsBugs from 'modern-errors-bugs'
import modernErrorsSerialize from 'modern-errors-serialize'
export const BaseError = ModernError.subclass('BaseError', {
plugins: [modernErrorsBugs, modernErrorsSerialize],
})
// ...
Please see the following documentation to create your own plugin.
Most plugins can be configured with options. The option's name is the same as the plugin.
const options = {
// `modern-errors-bugs` options
bugs: 'https://github.com/my-name/my-project/issues',
// `props` can be configured and modified like plugin options
props: { userId: 5 },
}
Plugin options can apply to (in priority order):
ModernError.subclass()
export const BaseError = ModernError.subclass('BaseError', options)
ErrorClass.subclass()
export const InputError = BaseError.subclass('InputError', options)
new ErrorClass()
throw new InputError('...', options)
ErrorClass[methodName](...args, options[pluginName])
error[methodName](...args, options[pluginName])
The custom
option can be used to provide an error class
with additional methods, constructor
, properties or options.
export const InputError = BaseError.subclass('InputError', {
// The `class` must extend from the parent error class
custom: class extends BaseError {
// 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() {
// ...
}
},
})
const error = new InputError('Wrong user name', { suffix: ': example' })
console.log(error.message) // 'Wrong user name: example'
console.log(error.isUserInput())
Please see the following documentation for information about TypeScript types.
Top-level ErrorClass
.
name
: string
options
: ClassOptions?
Creates and returns a child ErrorClass
.
Type: object
Type: Plugin[]
Type: class extends ErrorClass {}
Custom class to add any methods, constructor
or properties.
Any plugin options can also be specified.
message
: string
options
: InstanceOptions?
Return value: Error
Type: object
Type: any
Inner error being wrapped.
Type: any[]
Array of errors being aggregated.
Any plugin options can also be specified.
error
: Error | any
NewErrorClass
: subclass of ErrorClass
Return value: Error
Normalizes invalid errors.
If the error
's class is a subclass of ErrorClass
, it is left as is.
Otherwise, it is converted to NewErrorClass
,
which defaults to ErrorClass
itself.
This framework brings together a collection of modules which can also be used individually:
error-custom-class
: Create
one error classerror-class-utils
: Utilities
to properly create error classeserror-serializer
: Convert
errors to/from plain objectsnormalize-exception
:
Normalize exceptions/errorsis-error-instance
: Check if
a value is an Error
instancemerge-error-cause
: Merge an
error with its cause
set-error-class
: Properly
update an error's classset-error-message
: Properly
update an error's messagewrap-error-message
:
Properly wrap an error's messageset-error-props
: Properly
update an error's propertiesset-error-stack
: Properly
update an error's stackhandle-cli-error
: 💣 Error
handler for CLI applications 💥log-process-errors
: Show
some ❤ to Node.js process errorserror-http-response
:
Create HTTP error responseswinston-error-format
: Log
errors with WinstonFor any question, don't hesitate to submit an issue on GitHub.
Everyone is welcome regardless of personal background. We enforce a Code of conduct in order to promote a positive and inclusive environment.
This project was made with ❤️. The simplest way to give back is by starring and sharing it online.
If the documentation is unclear or has a typo, please click on the page's Edit
button (pencil icon) and suggest a correction.
If you would like to help us fix a bug or add a new feature, please check our guidelines. Pull requests are welcome!
ehmicky 💻 🎨 🤔 📖 | const_var 🤔 💬 | Andy Brenneke 🤔 💬 | Graham Fisher 🐛 | renzor 💬 🤔 | Eugene 💻 🐛 | Jonathan Chambers ⚠️ 🐛 |
FAQs
Handle errors in a simple, stable, consistent way
We found that modern-errors demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
A supply chain attack has been detected in versions 1.95.6 and 1.95.7 of the popular @solana/web3.js library.
Research
Security News
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
Security News
Research
Socket researchers have discovered malicious npm packages targeting crypto developers, stealing credentials and wallet data using spyware delivered through typosquats of popular cryptographic libraries.