npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

understudy

v4.1.0

Published

action interceptor for dynamic extensible systems

Downloads

2,110

Readme

Understudy

A means to provide interceptors (i.e. hooks) when performing asynchronous actions.

  • All hooks are asynchronous
    • All logic done by perform-based actions is asynchronous.
    • Error first helpers.
    • Fail fast.
  • Consistency in arguments provided to hooks & actions
    • Hooks can only mutate arguments that they are passed, not the number of arguments.
    • Use of callback arguments to pass information removes focus on return values to mitigate this potentially odd behavior.
  • Opt-in behavior.
    • Only calls to .perform(action ... enable hooking.

By depending on understudy you are exposed to four methods: perform, before, after and waterfall

.perform(action, arg0, /* arg1, ... */, work, callback)

This is the core API for invoking hooks provided by Understudy. Each call to perform for the same action should have a consistent argument signature because this is what will be expected by each of the before and after hooks for the action. The overall flow control is:

  1. Call all before hooks for action.
  2. Call work function for action.
  3. Call all after hooks for action`.
  4. Call callback with results from work function.

.before(action, arg0, /* arg1, ... */, next)

Called before the work function is executed in perform with exactly the arguments passed to .perform. Nothing passed to next have an impact on the flow control above except any error is supplied short-circuits execution to the callback.

.after(action, arg0, /* arg1, ... */, next)

Called after the work function is executed in perform with exactly the arguments passed to .perform. Nothing passed to next have an impact on the flow control above except any error is supplied short-circuits execution to the callback.

While the above statement is true when using .perform, after hooks acquire a waterfall like behavior with .waterfall where the result of work function gets passed to the after hooks. Each after hook is then able to mutate the arguments passed to the next one. Strongly discouraged to change number of arguments for your user's sanity.

.waterfall(action, arg0, /* arg1, ... */, work, callback)

This is a slightly different perform that is very useful for when you have to modify state received from a function in a sequence of configurable hooks.

  1. Call all before hooks for action.
  2. Call work function for action.
  3. Call all after hooks for actionwith the result returned from thework` function.
  4. Call callback with results from the after hooks execution (if any) and otherwise the results from the work function.

Real-world Usage

Let's consider a real-world application with two interceptable actions:

  • start: Application has started
  • http:request: Application has received an incoming HTTP request.

We could easily implement this App behavior in Understudy:

var Understudy = require('understudy');

var App = module.exports = function App() {
  Understudy.call(this);
};

//
// Starts the application after running before and
// after hooks.
//
App.prototype.start = function (options, callback) {
  this.perform('start', options, function (next) {
    //
    // Here, `options` may have been mutated from the execution
    // of the before hooks.
    // ...
    // Do some other async thing
    // ...
    // These arguments are passed to the final callback unless
    // short-circuited by an error here, or in an after hook.
    //
    next(null, options);
  }, callback);
};

App.prototype.handle = function (req, res) {
  req.times = {
    start: process.hrtime()
  };

  this.perform('http:request', req, res, function (next) {
    req.times.middle = process.hrtime();
    req.times.begin  = process.hrtime(req.times.start);
    next();
  }, function (err) {
    if (err) {
      //
      // Do some error handling.
      //
    }

    req.times.total = process.hrtime(req.times.start);
    req.times.after = process.hrtime(req.times.middle);
    console.log([
      'Total time: %s',
      '  Before hooks: %s',
      '  After  hooks: %s'
    ].join('\n'), format(req.times.total), format(req.times.begin), format(req.times.after));

    res.end();
  });
}

//
// Now we consume a new app with hooks.
//
var http = require('http');

var app = new App();
app.before('start', function (options, next) {
  var server = options.server = http.createServer(function (req, res) {
    app.handle(req, res);
  });

  server.listen(options.port, next);
});

app.after('start', function (options, next) {
  console.log('App started on %s', options.port);
});

app.before('http:request', function (req, res, next) {
  //
  // Do something asynchronous.
  //
  next();
});

app.start({ port: 8080 }, function () {
  console.log('ok');
});

//
// Format process.hrtime()
//
function format(s) {
  return (s[0] * 1e3 + s[1] / 1e6) / 1e3;
}

More Examples

See the example directory

Error handling when no callback is provided

Each before and after hook can provide an optional error to short-circuit evaluation of the flow that would normally follow it. This error will be provided to your callback, when supplied. In the event that you DO NOT provide a callback and a before, after or work function responds with an Error IT WILL BE IGNORED AND FLOW WILL CONTINUE. e.g.

var Understudy = require('understudy');
var actor = new Understudy();

actor.before('always', function (next) {
  next(new Error('I always fail'));
});

actor.after('always', function (next) {
  console.log('I always get called. NO MATTER WHAT');
  console.log('BUT, only when no callback is supplied.');
  next(new Error('Another swallowed error'));
});

actor.perform('always', function (done) {
  done(new Error('Errors are ignored here too.'));
});

In other words (as in the above example): if you do not supply a callback to your .perform then understudy will consider all of your before, after and work functions as "fire and forget".

LICENSE: MIT
Author: Bradley Meck
Contributors: Jarrett Cruger, Charlie Robbins