SkillAgentSearch skills...

Flaverr

Utility library for building tastier errors and stack traces in JavaScript.

Install / Use

/learn @mikermcneil/Flaverr
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

flaverr

Utility library for JavaScript Errors, stack traces, and omens.

Take your JavaScript Errors to "flavortown".

Installation   NPM version

$ npm install flaverr

Usage

The most basic usage of flaverr is just to call it as a function.

Basics

Flavor an Error instance with the specified error code string or dictionary of customizations:

var flaverr = require('flaverr');

var someError = new Error('Whoa whoa whoa!');
//…
someError = flaverr({
  name: 'CompatibilityError',
  code: 'E_WHOA_WHOA_WHOA'
}, someError);
  • If you provide a string as the first argument, that string will be set as the Error's code.
  • If you provide a dictionary as the first argument, that dictionary's keys will get folded into the Error as properties.

Attach an error code

var err = new Error('Could not find user with the specified id.');
err = flaverr('E_NOT_FOUND', err);
// => assert(err.code === 'E_NOT_FOUND' && err.message === 'Could not find user with the specified id.')
// => assert(err.constructor.name === 'Error')

Attach arbitrary properties

var err = flaverr({
  code: 'E_NOT_FOUND',
  raw: { foo: 'bar' }
}, new Error('Could not find user with the specified id.'));
// => assert(err.code === 'E_NOT_FOUND' && err.message === 'Could not find user with the specified id.')
// => assert(err.raw.foo === 'bar')
// => assert(err.constructor.name === 'Error')

Improve the error message

var err = new Error('Could not find user with the specified id.');
err = flaverr({
  name: 'ConsistencyViolation',
  message: 'Logged-in user has gone missing!',
  code: 'E_NOT_FOUND',
  raw: { id: 123 }
}, err);
// => assert(err.code === 'E_NOT_FOUND' && err.message === 'Logged-in user has gone missing!')
// => assert(err.raw.id === 123 && err.name === 'ConsistencyViolation')
// => assert(err.constructor.name === 'Error')

Build a new Error

var err = flaverr({
  name: 'ConsistencyViolation',
  message: 'Logged-in user has gone missing!',
  raw: { id: 123 }
}, err);
// => assert(err.code === 'notFound' && err.message === 'Logged-in user has gone missing!')
// => assert(err.raw.id === 123 && err.name === 'ConsistencyViolation')
// => assert(err.constructor.name === 'Error')

A few examples of common use cases

In .intercept()

var html = await sails.renderView('emails/email-verify-account', { token: '…' })
.intercept('ENOENT', (err)=>flaverr({
  code: 'E_MISSING_TEMPLATE_OR_LAYOUT',
  message: 'Could not locate either template or layout file on disk.  '+err.message
}));

In a try statement

try {
  _.each(paths, function (thisPath) {
    var isDirectory = fs.statFileSync(path.resolve(thisPath)).isDirectory();
    if (!isDirectory) {
      throw flaverr('notADirectory', new Error('One of the provided paths (`'+path.resolve(thisPath)+'`) points to something other than a directory.'));
    }
  });
} catch (e) {
  switch (e.code) {
    case 'ENOENT': return exits.notFound();
    case 'notADirectory': return exits.invalidPath(e);
    default: return exits.error(e);
  }
}

Tagging an error with a code before sending it through an asynchronous callback

if (err) { return done(err); }
if (!user) {
  return done(flaverr('notFound', new Error('Could not find a user with that id (`'+req.param('id')+'`).')));
}

In a traditional asynchronous loop

This is less of a thing now that we have async/await! But still leaving this example here for reference.

async.eachSeries(userRecords, function (user, next) {

  if (user.pets.length === 0) {
    return next(flaverr('noPets', new Error('User (`'+user.id+'`) has no pets yet!')));
  }

  if (!user.hobby) {
    return next(flaverr('noHobby', new Error('Consistency violation: User (`'+user.id+'`) has no hobby!')));
  }

  async.each(user.pets, function (pet, next){
    Pet.update().where({ id: pet.id })
    .set({ likelyHobby: user.hobby })
    .exec(next);
  }, function (err){
    if (err) { return next(err); }
    if (err.code === 'E_UNIQUE') { return next(flaverr('nonUniquePetHobby', err)); }
    return next();
  });

}, function afterwards(err) {
  if (err) {
    switch (err.code) {
      case 'noPets': return res.send(409, err.message);
      case 'noHobby': return res.serverError(err);
      case 'nonUniquePetHobby': return res.send(409, 'A pet already exists with that hobby.');
      default: return res.serverError(err);
    }
  }//--•

  return res.ok();
});

Advanced

So, flaverr() can be used for more than just flavoring Error instances.

Some of this stuff is pretty low-level, and intended to be used in building higher level libraries (not necessarily from app-level Node.js or browser JavaScript code).

But in the interest of completeness, here's what you can do:

flaverr(…, …, caller)

If an optional third argument is passed in, it is understood as the caller-- i.e. the function where you called flaverr(). If provided, this function will be used to improve the stack trace of the provided error.

This is particularly useful for customizing a stack trace; e.g. for building better omens. By "omen", I mean an Error instance instantiated at an earlier time, so that when you use it at a later time, it has the right stack trace, and hasn't been "cliffed out" at an EventEmitter, setTimeout, setImmediate, etc.

Note: This is not a particularly speedy operation in JavaScript! For most usages, it won't matter at all. But for very hot code paths, or use cases that are highly sensitive to performance, you should consider avoiding this feature-- at least some of the time.

For example, in parts of Waterline ORM and the machine runner, this argument is omitted when running in a production environment:

var omen;
if (process.env.NODE_ENV!=='production' || process.env.DEBUG) {
  omen = flaverr({}, new Error('omen'), theCurrentFunction);
}

//…

In the example above, the stack trace of our omen will be snipped based on the instruction where this was invoked (i.e. whatever called "theCurrentFunction").

flaverr.buildOmen()

Build an omen.

This is just a convenience method.

doSomethingThatRandomlyFailsSometimes();
function doSomethingThatRandomlyFailsSometimes() {
  var omen;
  if (process.env.NODE_ENV !== 'production' || process.env.DEBUG) {
    omen = flaverr.buildOmen(doSomethingThatRandomlyFailsSometimes);
  }

  // …

  if (Math.random() > 0.5) {
    throw flaverr({
      message: 'Wulp, it randomly failed.'
    }, omen);
  }

}

flaverr.parseError()

There are certain Error-like values (e.g. from the bluebird library) that aren't quite ready to use as normal Errors, but can be easily parsed without being forced to construct a new Error instance or mutating anything.

This method provides a way to normalize errors like that. If it determines that it cannot, then it just returns undefined.

// …
err = flaverr.parseError(err) || err;

if (_.isError(err)) {
  throw flaverr({
    message: 'Something went wrong in aisle 6: '+err.message
  }, omen);
}
else {
  throw flaverr({
    message: 'Something went wrong in aisle 6: '+util.inspect(err, {depth:5})
  }, omen);
}

flaverr.parseOrBuildError()

Sometimes, you really want to make sure you have an Error instance, no matter what-- even if you have to construct one!

This method does just that.

// …

err = flaverr.parseOrBuildError(err, omen);
throw flaverr({
  message: 'Something went wrong in aisle 6: '+err.message
}, err);

flaverr.getBareTrace()

Return the bare stack trace of an Error, with the identifying preamble (.name + colon + space + .message) trimmed off, leaving only the info about stack frames.

This is particularly useful for including proper context within warning messages. (More rarely, it's useful for situations where you want an error to contain more than one trace-- although in that case, it's usually best to store the nested error as a separate property, such as .raw. See .wrap() for ideas.)

If you pass in an error, its stack trace will be harvested:

var err = new Error('Some error');

flaverr.getBareTrace(err);
//=>
//'    at repl:1:28\n    at ContextifyScript.Script.runInThisContext (vm.js:44:33)\n    at REPLServer.defaultEval (repl.js:239:29)\n    at bound (domain.js:301:14)\n    at REPLServer.runBound [as eval] (domain.js:314:12)\n    at REPLServer.onLine (repl.js:433:10)\n    at emitOne (events.js:120:20)\n    at REPLServer.emit (events.js:210:7)\n    at REPLServer.Interface._onLine (readline.js:278:10)\n    at REPLServer.Interface._line (readline.js:625:8)'
// ^^^ this is a string

If nothing is passed in, a new Error will be instantiated on the fly and its stack will be used:

flaverr.getBareTrace();
//=>
//'    at repl:1:28\n    at ContextifyScript.Script.runInThisContext (vm.js:44:33)\n    at REPLServer.defaultEval (repl.js:239:29)\n    at bound (domain.js:301:14)\n    at REPLServer.runBound [as eval] (domain.js:314:12)\n    at REPLServer.onLine (repl.js:433:10)\n    at emitOne (events.js:120:20)\n    at REPLServer.emit (events.js:210:7)\n    at REPLServer.Interface._onLine (readline.js:278:10)\n    at REPLServer.Interface._line (readline.js:625:8)'

If a function is passed in instead of an Error, it is understood to be a "caller" from somewhere on the current call stack. A new Error will be instantiated instead, and the provided function will be used to align the stack trace of that new Error so that it begins at the point the provided function was called:

function foo() {
  console.log(flaverr.getBareTrace(foo));
}

foo();
//=>
//'    at repl:1:28\n    at ContextifyScript.Script.runInTh
View on GitHub
GitHub Stars22
CategoryDevelopment
Updated2y ago
Forks3

Languages

JavaScript

Security Score

60/100

Audited on May 11, 2023

No findings