Giter Site home page Giter Site logo

js-devtools / ono Goto Github PK

View Code? Open in Web Editor NEW
95.0 4.0 12.0 3 MB

Wrap errors without losing the original message, stack trace, or properties

Home Page: https://jstools.dev/ono/

License: MIT License

JavaScript 64.73% TypeScript 35.27%
errors javascript nodejs universal-javascript

ono's Introduction

ono (Oh No!)

Throw better errors.

npm License Buy us a tree

Build Status Coverage Status Dependencies

OS and Browser Compatibility

Features

  • Wrap and re-throw an error without losing the original error's type, message, stack trace, and properties

  • Add custom properties to errors — great for error numbers, status codes, etc.

  • Use format strings for error messages — great for localization

  • Enhanced support for JSON.stringify() and util.inspect() — great for logging

  • Supports and enhances your own custom error classes

  • Tested on Node.js and all modern web browsers on Mac, Windows, and Linux.

Example

const ono = require("@jsdevtools/ono");

// Throw an error with custom properties
throw ono({ code: "NOT_FOUND", status: 404 }, `Resource not found: ${url}`);

// Wrap an error without losing the original error's stack and props
throw ono(originalError, "An error occurred while saving your changes");

// Wrap an error and add custom properties
throw ono(originalError, { code: 404, status: "NOT_FOUND" });

// Wrap an error, add custom properties, and change the error message
throw ono(originalError, { code: 404, status: "NOT_FOUND" }, `Resource not found: ${url}`);

// Throw a specific Error subtype instead
// (works with any of the above signatures)
throw ono.range(...);                           // RangeError
throw ono.syntax(...);                          // SyntaxError
throw ono.reference(...);                       // ReferenceError

// Create an Ono method for your own custom error class
const { Ono } = require("@jsdevtools/ono");
class MyErrorClass extends Error {}
ono.myError = new Ono(MyErrorClass);

// And use it just like any other Ono method
throw ono.myError(...);                         // MyErrorClass

Installation

Install using npm:

npm install @jsdevtools/ono

Usage

When using Ono in Node.js apps, you'll probably want to use CommonJS syntax:

const ono = require("@jsdevtools/ono");

When using a transpiler such as Babel or TypeScript, or a bundler such as Webpack or Rollup, you can use ECMAScript modules syntax instead:

import ono from "@jsdevtools/ono";

Browser support

Ono supports recent versions of every major web browser. Older browsers may require Babel and/or polyfills.

To use Ono in a browser, you'll need to use a bundling tool such as Webpack, Rollup, Parcel, or Browserify. Some bundlers may require a bit of configuration, such as setting browser: true in rollup-plugin-resolve.

API

ono([originalError], [props], [message, ...])

Creates an Error object with the given properties.

  • originalError - (optional) The original error that occurred, if any. This error's message, stack trace, and properties will be copied to the new error. If this error's type is one of the known error types, then the new error will be of the same type.

  • props - (optional) An object whose properties will be copied to the new error. Properties can be anything, including objects and functions.

  • message - (optional) The error message string. If it contains placeholders, then pass each placeholder's value as an additional parameter. See the format option for more info.

Specific error types

The default ono() function may return an instance of the base Error class, or it may return a more specific sub-class, based on the type of the originalError argument. If you want to explicitly create a specific type of error, then you can use any of the following methods:

The method signatures and arguments are exactly the same as the default ono() function.

Method Return Type
ono.error() Error
ono.eval() EvalError
ono.range() RangeError
ono.reference() ReferenceError
ono.syntax() SyntaxError
ono.type() TypeError
ono.uri() URIError
ono.yourCustomErrorHere() Add your own custom error classes to ono

Ono(Error, [options])

The Ono constructor is used to create your own custom ono methods for custom error types, or to change the default behavior of the built-in methods.

Warning: Be sure not to confuse ono (lowercase) and Ono (capitalized). The latter one is a class.

  • Error - The Error sub-class that this Ono method will create instances of

  • options - (optional) An options object, which customizes the behavior of the Ono method

Options

The Ono constructor takes an optional options object as a second parameter. The object can have the following properties, all of which are optional:

Option Type Default Description
concatMessages boolean true When Ono is used to wrap an error, this setting determines whether the inner error's message is appended to the new error message.
format function or boolean util.format() in Node.js

false in web browsers
A function that replaces placeholders like in error messages with values.

If set to false, then error messages will be treated as literals and no placeholder replacement will occur.

concatMessages Option

When wrapping an error, Ono's default behavior is to append the error's message to your message, with a newline between them. For example:

const ono = require("@jsdevtools/ono");

function createArray(length) {
  try {
    return new Array(length);
  }
  catch (error) {
    // Wrap and re-throw the error
    throw ono(error, "Sorry, I was unable to create the array.");
  }
}

// Try to create an array with a negative length
createArray(-5);

The above code produces the following error message:

Sorry, I was unable to create the array.
Invalid array length;

If you'd rather not include the original message, then you can set the concatMessages option to false. For example:

const { ono, Ono } = require("@jsdevtools/ono");

// Override the default behavior for the RangeError
ono.range = new Ono(RangeError, { concatMessages: false });

function createArray(length) {
  try {
    return new Array(length);
  }
  catch (error) {
    // Wrap and re-throw the error
    throw ono(error, "Sorry, I was unable to create the array.");
  }
}

// Try to create an array with a negative length
createArray(-5);

Now the error only includes your message, not the original error message.

Sorry, I was unable to create the array.

format option

The format option let you set a format function, which replaces placeholders in error messages with values.

When running in Node.js, Ono uses the util.format() function by default, which lets you use placeholders such as %s, %d, and %j. You can provide the values for these placeholders when calling any Ono method:

throw ono("%s is invalid. Must be at least %d characters.", username, minLength);

Of course, the above example could be accomplished using ES6 template literals instead of format strings:

throw ono(`${username} is invalid. Must be at least ${minLength} characters.`);

Format strings are most useful when you don't alrady know the values at the time that you're writing the string. A common scenario is localization. Here's a simplistic example:

const errorMessages {
  invalidLength: {
    en: "%s is invalid. Must be at least %d characters.",
    es: "%s no es válido. Debe tener al menos %d caracteres.",
    zh: "%s 无效。 必须至少%d个字符。",
  }
}

let lang = getCurrentUsersLanguage();

throw ono(errorMessages.invalidLength[lang], username, minLength);

The format option in web browsers

Web browsers don't have a built-in equivalent of Node's util.format() function, so format strings are only supported in Node.js by default. However, you can set the format option to any compatible polyfill library to enable this functionality in web browsers too.

Here are some compatible polyfill libraries:

Custom format implementation

If the standard util.format() functionality isn't sufficient for your needs, then you can set the format option to your own custom implementation. Here's a simplistic example:

const { ono, Ono } = require("@jsdevtools/ono");

// This is a simple formatter that replaces $0, $1, $2, ... with the corresponding argument
let options = {
  format(message, ...args) {
    for (let [index, arg] of args.entries()) {
      message = message.replace("$" + index, arg);
    }
    return message;
  }
};

// Use your custom formatter for all of the built-in error types
ono.error = new Ono(Error, options);
ono.eval = new Ono(EvalError, options);
ono.range = new Ono(RangeError, options);
ono.reference = new Ono(ReferenceError, options);
ono.syntax = new Ono(SyntaxError, options);
ono.type = new Ono(TypeError, options);
ono.uri = new Ono(URIError, options);

// Now all Ono functions support your custom formatter
throw ono("$0 is invalid. Must be at least $1 characters.", username, minLength);

Custom Error Classes

There are two ways to use Ono with your own custom error classes. Which one you choose depends on what parameters your custom error class accepts, and whether you'd prefer to use ono.myError() syntax or new MyError() syntax.

Option 1: Standard Errors

Ono has built-in support for all of the built-in JavaScript Error types. For example, you can use ono.reference() to create a ReferenceError, or ono.syntax() to create a SyntaxError.

All of these built-in JavaScript Error types accept a single parameter: the error message string. If your own error classes also work this way, then you can create Ono methods for your custom error classes. Here's an example:

const { ono, Ono } = require("@jsdevtools/ono");
let counter = 0;

// A custom Error class that assigns a unique ID and timestamp to each error
class MyErrorClass extends Error {
  constructor(message) {
    super(message);
    this.id = ++counter;
    this.timestamp = new Date();
  }
}

// Create a new Ono method for your custom Error class
ono.myError = new Ono(MyErrorClass);

// You can use this method just like any other Ono method
throw ono.myError({ code: 404, status: "NOT_FOUND" }, `Resource not found: ${url}`);

The code above throws an instance of MyErrorClass that looks like this:

{
  "name": "MyErrorClass",
  "message": "Resource not found: xyz.html",
  "id": 1,
  "timestamp": "2019-01-01T12:30:00.456Z",
  "code": 404,
  "status": "NOT_FOUND",
  "stack": "MyErrorClass: Resource not found: xyz.html\n   at someFunction (index.js:24:5)",
}

Option 2: Enhanced Error Classes

If your custom error classes require more than just an error message string parameter, then you'll need to use Ono differently. Rather than creating a custom Ono method and using ono.myError() syntax, you'll use Ono inside your error class's constructor. This has a few benefits:

  • Your error class can accept whatever parameters you want
  • Ono is encapsulated within your error class
  • You can use new MyError() syntax rather than ono.myError() syntax
const { ono, Ono } = require("@jsdevtools/ono");

// A custom Error class for 404 Not Found
class NotFoundError extends Error {
  constructor(method, url) {
    super(`404: ${method} ${url} was not found`);

    // Add custom properties, enhance JSON.stringify() support, etc.
    Ono.extend(this, { statusCode: 404, method, url });
  }
}

// A custom Error class for 500 Server Error
class ServerError extends Error {
  constructor(originalError, method, url) {
    super(`500: A server error occurred while responding to ${method} ${url}`);

    // Append the stack trace and custom properties of the original error,
    // and add new custom properties, enhance JSON.stringify() support, etc.
    Ono.extend(this, originalError, { statusCode: 500, method, url });
  }
}

Contributing

Contributions, enhancements, and bug-fixes are welcome! Open an issue on GitHub and submit a pull request.

Building/Testing

To build/test the project locally on your computer:

  1. Clone this repo
    git clone https://github.com/JS-DevTools/ono.git

  2. Install dependencies
    npm install

  3. Run the build script
    npm run build

  4. Run the tests
    npm test

License

Ono is 100% free and open-source, under the MIT license. Use it however you want.

This package is Treeware. If you use it in production, then we ask that you buy the world a tree to thank us for our work. By contributing to the Treeware forest you’ll be creating employment for local families and restoring wildlife habitats.

Big Thanks To

Thanks to these awesome companies for their support of Open Source developers ❤

Travis CI SauceLabs Coveralls

ono's People

Contributors

glenjamin avatar jamesmessinger avatar qm3ster avatar rkrauskopf avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

ono's Issues

NPM Package not found

npm ERR! code E404
npm ERR! 404 Not Found - GET https://registry.npmjs.org/@jsdevtools%2fono - Not found
npm ERR! 404
npm ERR! 404 '@jsdevtools/ono@^7.1.3' is not in the npm registry.
npm ERR! 404 You should bug the author to publish it (or use the name yourself!)
npm ERR! 404 It was specified as a dependency of '@apidevtools/json-schema-ref-parser'
npm ERR! 404
npm ERR! 404 Note that you can also install from a
npm ERR! 404 tarball, folder, http url, or git url.

'isomorphic.node.ts' runtime error with webpack

I could swear this was working earlier this year, but after upgrading to latest node / types / webpack, simply importing or requiring 'isomorphic.node' triggers a runtime error

Cannot read property 'inspect' of undefined

It looks like import util from "util"; at the top of 'isomorphic.node.ts'
causes webpack to generate:
const inspectMethod = util_1.default.inspect.custom || Symbol.for("nodejs.util.inspect.custom");

As opposed to the expected:
const inspectMethod = util_1.inspect.custom || Symbol.for("nodejs.util.inspect.custom");

This could be resolved by changing the import to read import * as util from "util";

Note:
The same thing occurs with the export const formatter = util.format; statement a few lines down.

Custom Error

I've started to incorporate ono for error handling, and for most cases it seems good.

I have some custom error types that I created and use for handling specific scenarios.
How can I use ono to handle these custom types.

The docs have some defined types, but I'd like to be able to include some of my own.

Remove import of inspect from utils

Importing inspect form utils isn't supported in vite. This project is imported by some other projects and it breaks the build. Is it possible to remove the import?

Weird CJS module hack is bombing Rollup builds

ono/src/index.ts

Lines 10 to 13 in a1fa89a

// CommonJS default export hack
if (typeof module === "object" && typeof module.exports === "object") {
module.exports = Object.assign(module.exports.default, module.exports);
}

This code block is bombing my rollup build. Not caught during build. This code makes it into the bundle, and then it fails on runtime startup. module.exports.default is undefined and is killing the build.

/path-to-my-build/node_modules/@jsdevtools/ono/esm/index.js:12
 module.exports = Object.assign(module.exports.default, module.exports);
                                            ^
TypeError: Cannot convert undefined or null to object

I found this by preserving the modules from Rollup so I could get a good stacktrace to the code.

// my rollup
import commonjs from '@rollup/plugin-commonjs'
import nodeResolve from '@rollup/plugin-node-resolve'
import json from '@rollup/plugin-json'

export default {
  input: 'src/service.js',
  output: {
    // file: 'dist/service.js',
    dir: 'dist/',
    format: 'cjs',
    sourcemap: 'inline',
    preserveModules: true
  },
  plugins: [
    nodeResolve({ preferBuiltins: true }),
    commonjs({
      dynamicRequireTargets: ['node_modules/nconf/lib/nconf/stores/*.js']
    }),
    json()
  ]
}

I found if i do this it makes it past this part:

if (typeof module === "object" && typeof module.exports === "object") {
  module.exports.default = module.exports.default || {}
  module.exports = Object.assign(module.exports.default, module.exports); 
}

feat: preserve stack trace of all errors in chain

Hiya, thank you for this great library!

I was wondering if this is possible to do today or if it could be implemented as an enhancement?

Right now the stack trace is only preserved for the original error, but ideally I'd want to know all the stack traces of all errors in the chain in case the message does not uniquely identify the location in the code where the error was thrown. This is especially useful with longer chains where 3 or 4 or more re-throws happen before the exception is finally logged.

Remove format-util dependency

Now that JavaScript supports template strings, it's usually not necessary to use a string-formatting function.

Drop the format-util dependency, but continue to support ono.formatter. On Node.js, ono.formatter will default to util.format. In web browsers, it will default to undefined, but users can set it to format-util or their own implementation if they wish.

TypeScript linter fails with TS1169

I installed the recent version of ono (7.1.2) using npm and received the following error
ERROR in ../node_modules/@jsdevtools/ono/esm/types.d.ts:135:5 - error TS1169: A computed property name in an interface must refer to an expression whose type is a literal type or a 'unique symbol' type. during linting the project.

Symbol prop keys are not working

Thank you for the great library!
I have a usecase with symbols that is not currently working, here is some sample code:

const ono = require('ono');

const SOME_PROP = Symbol('symbol-name');
// const SOME_PROP = 'string-key'; // (string keys work correctly)
function fun() {
  try {
    throw new Error('original error');
  } catch (e) {
    throw ono(e, { [SOME_PROP]: true }, 'wrapped with [SOME_PROP]');
  }
}

try {
  fun();
} catch (e) {
  console.log(`Has prop: ${e[SOME_PROP] || false}`);
}

Add HTTP Errors

some like this

const ono = require('ono');

ono.badRequet(); // => ono( {status: 400, message: 'Bad Request'})
ono.unauthorized(); // => ono({status: 401, message: 'Unauthorized'})
ono.forbidden(); // => ono({status: 403, message: 'Forbidden'})
ono.notFound(); // => ono({status: 404, message: 'Not Found'})

Problematic Source maps

Description

I've detected problematic usage of source maps from @jsdevtools/[email protected]. Issue is manifesting when using for example Create React App to build the application using @jsdevtools/[email protected] as an npm package. I've inspected the source maps and according to my understanding the problem is that the source maps are missing sourcesContent field. This field should only be omitted if the tool using the source map can retrieve the sources via url or from the filesystem. Obviously this is not the case as the original source code under (src/ directory) is not part of npm distribution.

Expected result

No warning when bundling the @jsdevtools/ono using Create React App.

Actual result

Warnings like these are being emitted by the webpack@5:

Failed to parse source map from '/home/char0n/Documents/GitHub/test/node_modules/@jsdevtools/ono/src/constructor.ts' file: Error: ENOENT: no such file or directory, open '/home/char0n/Documents/GitHub/test/node_modules/@jsdevtools/ono/src/constructor.ts'

Failed to parse source map from '/home/char0n/Documents/GitHub/test/node_modules/@jsdevtools/ono/src/extend-error.ts' file: Error: ENOENT: no such file or directory, open '/home/char0n/Documents/GitHub/test/node_modules/@jsdevtools/ono/src/extend-error.ts'

Failed to parse source map from '/home/char0n/Documents/GitHub/test/node_modules/@jsdevtools/ono/src/index.ts' file: Error: ENOENT: no such file or directory, open '/home/char0n/Documents/GitHub/test/node_modules/@jsdevtools/ono/src/index.ts'

Steps to reproduce

I can create a repo demonstrating CRA@5 + @jsdevtools/[email protected] as StR POC.

Troubleshooting

CRA can be ejected, webpack.config.js edited and libraries can be excluded from source map processing:

      strictExportPresence: true,
      rules: [
        // Handle node_modules packages that contain sourcemaps
        shouldUseSourceMap && {
          enforce: 'pre',
          exclude: [
            /@babel(?:\/|\\{1,2})runtime/,
            /@jsdevtools\/ono/,
          ],
          test: /\.(js|mjs|jsx|ts|tsx|css)$/,
          loader: require.resolve('source-map-loader'),
        },

Output control for util.inspect / console.error ?

I see that the toJSON is deliberately used for the Symbol.for('nodejs.util.inspect.custom') custom formatter. This doesn't feel very conducive to clear and concise error messages. Take the following for example and its output:

const ono = require("ono");

const onoError = ono({ foo: 'bar' }, 'Test Error');
console.log(onoError);

const ogError = new Error('Test Error');
ogError.foo = 'bar';
console.log(ogError);
{
  stack: 'Error: Test Error\n' +
    '    at Object.<anonymous> (/home/jsanders/ferm10n/dweb-mirror/demo.js:4:18)\n' +
    '    at Module._compile (internal/modules/cjs/loader.js:1138:30)\n' +
    '    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1158:10)\n' +
    '    at Module.load (internal/modules/cjs/loader.js:986:32)\n' +
    '    at Function.Module._load (internal/modules/cjs/loader.js:879:14)\n' +
    '    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)\n' +
    '    at internal/main/run_main_module.js:17:47',
  message: 'Test Error',
  toJSON: [Function: toJSON],
  foo: 'bar',
  name: 'Error',
  toString: [Function: toString]
}
Error: Test Error
    at Object.<anonymous> (/home/jsanders/ferm10n/dweb-mirror/demo.js:7:17)
    at Module._compile (internal/modules/cjs/loader.js:1138:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1158:10)
    at Module.load (internal/modules/cjs/loader.js:986:32)
    at Function.Module._load (internal/modules/cjs/loader.js:879:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
    at internal/main/run_main_module.js:17:47 {
  foo: 'bar'
}

Same information is conveyed, but the ono error is a lot more verbose. Is there a reason why toJSON was used for the inspect method? Would it make sense to only have toJSON only present enumerable properties, like how JSON.stringify serializes objects?

Have a option to preserve stacktrace of inner errors, but keep only latest error message

Hey!

I have a case in my API project when I want to catch an error from some 3-d party library/ies and re-throw an ono error with original error as an inner error.

Because 3-d library/ies can throw any kinds of errors and their messages are not always something we want to expose as an api response I want to show nicer errors for api consumers, while also logging information about inner errors.

For example:

  async getData(): Promise<DataDto> {
    try {
      // Anything that can throw exception, e.g.
      throw new Error("unable to get name property of undefined");
    } catch (e) {
      throw ono(e, 'Error occurred while retrieving data');
    }
  }

In case above I would later in application errors filter get an error with concatenated message (2 or more), something like "Error occurred while retrieving data\nunable to get name property of undefined".

I'm interested in using ono because it preserves the stack trace, and that stack trace will also have the "unable to get name property of undefined" message, meaning we can trace the error and see separation between inner errors when this error is logged, but I would not want to show all concatenated errors as an API error response.

Do you think it makes sense for ono to support some sort of option to not concatenate messages while still combining stack traces? Or my use-case is a bit unique? :)

I know I can use '\n' as a separator and extract the first message, but there is also a possibility that ono-thrown error will have an \n of it's own in message, and that would cut of the message :)

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.