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 🙏

© 2026 – Pkg Stats / Ryan Hefner

filterchain

v0.1.0

Published

perform work before and after an operation

Readme

filterchain

Perform work before and/or after an operation.

Disclaimer

This is not meant to be used as async flow control, but is merely a way to dynamically upgrade the functionality of getters and setters.

This also means that errors will not stop the chain of events here, you must specifically cancel the current layer in order to avoid performing the core action.

Learn by example

The basic operation of filterchain goes something like this:

var chain = require('filterchain').createChain([
  function(data, next, cancel) {
    // forward to the next layer
    next('override');
  } // you can add more functions here
]);

chain('initial data', function(data) {
  console.log(data); // outputs 'override'
});

and here is the outer api

// Create a chain with no layers and no core function
var chain = filterchain.createChain(/* layers */, /* core */);

Where chain is a function that accepts an optional data and a optional callback with the arguments errors and data. The callback is called after the filter chain has been executed.

data can be any javascript value

// Excerpt to show what the chain callback looks like.
// `errors` is either null (no errors) or an array
chain('some optional data', function(errors, data) {})

And layers is an array of functions

var chain = require('filterchain').createChain([
  function(data, next, cancel) {

    // just forward the data, this is basically a no-op
    next(data);
  }
], core);

next is a function with the signature next(data[, bubbleFunction]) cancel is a function with the signature cancel([error])

And core is the function that will be run after the capture phase but before the bubble phase. The core method should accept data and fn.

var chain = require('filterchain').createChain([], function(data, fn) {

  // send the incoming back the same way it came
  if (data === true) {
    fn(null, 'tricked ya!')
  } else {
    fn('error!')
  }

});

chain(true, function(errors, data) {
  console.log(data); // outputs: 'tricked ya!'
});

chain(false, function(errors, data) {
  console.log(errors[0]); // outputs: 'error!'
  console.log(data); // outputs: undefined
});

What (else) does it do?

In a sense, filter chains are similar to onions. Passing data into the outer husk causes it to flow down through each layer toward the core. Each function (aka: layer) along the path as a chance to either manipulate or validate the data before forwarding it onto the next layer or canceling it.

Manipulate and forward data

var chain = require('filterchain').createChain([
  function(data, next, cancel) {

    // ignore the incoming data and forward something more
    // to our liking
    next('pass this along');
  }
]);

chain(function(errors, data) {
  console.log(data);  // outputs 'pass this along'
});

Cancel + bubble

Cancelling causes the flow of the chain to be reversed immediately.

var chain = require('filterchain').createChain([
  function(data, next, cancel) {

    // the first argument to cancel is an optional error.  The error
    // will be collected and sent to the final callback for processing
    cancel('fat chance');
  }
], function(data, fn) {

  // this is never called
  fn('some other thing');
});

chain(function(errors, data) {
  console.log(errors[0]); // ouputs 'fat chance'
});

Post process data

Passing a function as the second argument to next will cause the filter chain to call that method during the bubble phase

var fc = require('filterchain');
var chain = fc.createChain([
  function(data, next, cancel) {
    next(data, function(data, done) {
      // You can return a value here or perform
      // an async operation and send the result through done
      done(null, data + ' + post processing')
    });
  }
]);

chain('initial value', function(errors, data) {
  console.log(data); // outputs 'initial value + post processing'
});

The first argument to done is an error and the second is the data that will be bubbled back out to the outer husk of the filter chain.

Compose filter chains


var inner = require('filterchain').createChain([
  function(data, next) {
    next(data + ' (inner capture ->', function(outgoingData, done) {
      done(null, outgoingData + ' inner bubble)');
    });
  }
], function(data, fn) {
  fn(null, data + '[inner core] ->')
})

var outer = require('filterchain').createChain([
  function(data, next) {
    next(data + 'outer capture ->', function(outgoingData, done) {
      done(null, outgoingData + ' outer bubble');
    });
  },
  inner, // add a filterchain into the filterchain.. oh my!

], function(data, fn) {
  fn(null, data + ' [outer core] ->')
});

outer('run: ', function(errors, data) {
  // outputs: 'run: outer capture -> (inner capture ->[inner core] -> inner bubble) [outer core] -> outer bubble'
  console.log(data);
})

Contrived Use Cases

User creation example

var createUser = require('filterchain').createChain([
  function unique(username, next, cancel) {
    db.exists({ username : username }, function(err, result) {
      if (err) {
        cancel(err);
      } else if (result === true) {
        cancel('sorry, that username already exists')
      } else {
        next(username);
      }
    });
  }
], function(data, fn) {
  db.save({ username : username }, fn);
});

createUser('tmpvar', function(errors, data) {
  console.log(errors[0]); // outputs 'sorry, that username already exists'
});

createUser('tmpvar-not-taken', function(errors, data) {
  console.log(errors); // outputs null
  console.log(data); // outputs '{ _id : 2, username: "tmpvar-not-taken" }'
});

Calculated attriutes in backbone

note: this is purely conceptual


var Rectangle = Backbone.Model.extend({
  initialize : function() {
    this.calculatedAttributes = {
      area : filterchain.createChain(function(data, fn) {
        // Not only is the return value calculated, but you
        // can add filters and post processing to your values.
        //
        // Simply add filters to the chain during creation or
        // chain.layers.push(function(data, next, cancel) {});

        fn(data.width * data.height);
      });
    }
  },
  get : function(key) {
    var that = this;

    if (this.calculatedAttributes[key]) {
      this.calculatedAttributes[key](that.toJSON(), function(result) {
        value = result;
      });

      if (typeof value !== 'undefined') {
        return value;
      }
    }

    // fall back to the default backbone behavior
    return Backbone.Model.prototype.get.call(this, key);
  }
});

var a = new Rectangle({ x: 10, y : 4 });
console.log(a.get('area')); // outputs '40'

Install

Node.js

npm install filterchain

Browser

works with a plain ol' script tag and access it via window.createChain