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

elixir

v0.1.2

Published

A polymorphic mapper and event handler.

Downloads

143

Readme


Elixir

Build Status

Preface

Elixir is based on a rewrite of Director by Flatiron. It depends solely on RaptorJuice, which is a lightweight (around 4.5k), self contained iterator and utility library. RaptorJuice originated as the general utility functions for Elixer.

At this point, Elixer is not intended to act as a drop-in replacement for Director, though it could easily be extended to do so. Rather, it should be viewed as refinement of the original logic which exists in Director.

Purpose

Elixir is a flexible router which is:

  • Based on regular expressions.
  • Segmented by a customizable delimiter.
  • Entirely asynchronous.

Development

  • Ensure that you have Node.js on your system.

  • Prepare your environment by executing the following commands:

    git clone https://github.com/junglefresh/Elixir.js elixir
    cd elixir
    npm install
    sudo npm install -g grunt-cli mocha

Testing

To automatically run tests upon saving changes to the source or tests run the following:

mocha --reporter spec ./spec.js -w
  • Upon execution of the following command, after each write:

    • The unit tests will be confirmed.

Building

To begin automatically building the project, run:

grunt watch
  • Please note that a Java Runtime is required due to the jsdoc dependency.

  • By executing the following command, upon each write:

    • The unit tests will be confirmed.
    • Documentation will be regenerated if all tests pass.

Demonstration

Introduction

Elixer is a router which treats any segmented string, such as the path portion of a URL, as a trajectory into a tree structure. This trajectory is defined by the segments of the path. A segment is any portion of a path which lies between two delimiting sequence. For a URL, the common delimiting sequence is a /, but with Elixir it can be defined to suit your needs.

In summary, Elixir projects a segmented string into its routing table.

Adding and Removing

Elixir allows us to attach functions to any part of a route's tree structure. A route is defined as a segmented string, so it is similar to a path, yet its segments are regular expressions.

var potion = new Elixir();
potion.add('/test', 'on', function() {
  Elixir.R.log('Hello world!');
});
Elixir.R.log(potion.table.segments);

First, we create a new Elixir object and call it potion. Then, we add an on state handler to the /test route in potions routing table. By logging the segments of potions routing table in the last line of the example, you can see that the segments of the route were created, and our handler was added to the on state.

Trigger and Context

So what now? We've added an event handler to potion, lets trigger it with a path that matches its route.

  • Context...
potion.trigger('/test', null, { }, function() {
  if(this.error) {
    Elixir.R.log('Fout:' + this.error.message);
  }
  Elixir.R.log('Tot ziens, wereld!');
});

Well, it kinda works, but why isn't the callback with our goodbye message being executed?

Asynchronous Functionality

The answer lies in the fact that Elixir has been built entirely asynchronously. Every handler function in the routing table must call the next function supplied with its context.

var potion = new Elixir();

potion.add('/test', 'on', function() {
  Elixir.R.log('Hello world!');
  this.next();                    // The fix.
});

potion.trigger('/test', null, { }, function() {
  if(this.error) {
    Elixir.R.log('Fout:' + this.error.message);
  }
  Elixir.R.log('Tot ziens, wereld!');
});

By calling next without any arguments, we ensure that the proceeding function on our trajectory is invoked. On the other hand, we can diverge from our or trajectory and invoke the callback supplied to trigger immediately, by supplying any truthy value to next.

var potion = new Elixir();

potion.add('/test', 'on', function() {
  Elixir.R.log('Hello world!');
  this.next({ message : 'An error!' });                    // The fix.
});

potion.trigger('/test', null, { }, function() {
  if(this.error) {
    Elixir.R.log('Fout: ' + this.error.message);
  }
  Elixir.R.log('Tot ziens, wereld!');
});

In this example, by passing an object to next, the error property of the triggered event's context is set to the argument supplied to next. Any subsequent handlers on our current trajectory (in this case, there are none) will by bypassed.

Tree Structure

To understand the structure of a routing table, we need to take a step back and look at a path for what it really is. The segments of a path (as described in the introduction) project onto a series of nodes in a tree. This series of nodes, or projection, can then be traversed in any method to achieve the desired order of event handlers.

var potion = new Elixir();

potion.add('/test', 'on', function() {
  Elixir.R.log('I am test!');
});

potion.add('/test/ing', 'on', function() {
  Elixir.R.log('I am ing!');
});

potion.trigger('/test');
potion.trigger('/test/ing');

The cool part about routes is that they are actually regular expressions. What this means is that we can create generic nodes or segments to handle a range of inputs.

potion.add('/test/ing/(\\d+)', 'on', function(numbers) {
  Elixir.R.log('I am ' + numbers);
});

potion.trigger('/test/ing/123');

As an artifact of a route's basis an regular expressions, we can create create segments which contain the delimiting sequence.

potion.add('/(test/ing/(\\d+))', 'on', function(match) {
  Elixir.R.log('I am test-ing-' + match);
});

potion.trigger('/test/ing/123');

Notice two things. First, the route containing two occurances of the delimiting sequence took precidence over the routes with fewer occurances. Second, the entire match was presented as the parameter to the handler, not the interion match.

States

The magic really starts to happen when we throw in some states. The idea of states allow us to create structured routes and repeat very littly code.

The default states are before, on and after, in that order.

var potion = new Elixir();

potion.add('/', 'before', function() {
  this.output = 'I ';
  this.next();
});

potion.add('/Jamaican', 'before', function() {
  this.output += 'am ';
  this.next();
});

potion.add('/Jamaican', 'on', function(match) {
  this.output += match;
  this.next();
});

potion.add('/Jamaican', 'after', function() {
  this.output += ', maaaannn.';
  this.next();
});

potion.add('/', 'after', function() {
  Elixir.R.log(this.output);
  this.next();
});

potion.trigger('/Jamaican');

We can add more routes and have them easily printed. Note that the root segment is assumed in any route.

potion.add('/(fly/high)', 'before', function() {
  this.output += '... Uhhh';
  this.next();
});

potion.add('/(fly/high)', 'on', function() {
  this.output += '... ';
  this.next();
});

potion.add('/(fly/high)', 'after', function() {
  this.output += 'What was I gonna say?';
  this.next();
});

potion.trigger('fly/high');

Methods

Methods allow us to to perform more specific actions on a trajectory.

var potion = new Elixir({ methods : [ 'talk', 'speak' ] });

potion.add('/samson', 'before', function() {
  this.output = 'I ';
  this.next();
})

potion.add('/samson', 'before', function() {
  this.output += 'wanna ';
  this.next();
})

potion.add('/samson', 'talk', 'on', function() {
  this.output += 'talk ';
  this.next();
})

potion.add('/samson', 'speak', 'on', function() {
  this.output += 'speak ';
  this.next();
})

potion.add('/samson', 'after', function() {
  this.output += 'to ';
  this.next();
})

potion.add('/samson', 'after', function() {
  this.output += 'Samson.';
  Elixir.R.log(this.output);
  this.next();
})

potion.trigger('/samson', 'talk');
potion.trigger('/samson', 'speak');

Stacked Handlers

The previous example could have been more consisely written by grouping our stacked handlers.

var potion = new Elixir({ methods : [ 'talk', 'speak' ] });

potion.add('/samson', 'before', [ 
  function() {
    this.output = 'I ';
    this.next();
  },
  function() {
    this.output += 'wanna ';
    this.next();
  }
]);

potion.add('/samson', 'talk', 'on', function() {
  this.output += 'talk ';
  this.next();
})

potion.add('/samson', 'speak', 'on', function() {
  this.output += 'speak ';
  this.next();
})

potion.add('/samson', 'after', [
  function() {
    this.output += 'to ';
    this.next();
  },
  function() {
    this.output += 'Samson.';
    Elixir.R.log(this.output);
    this.next();
  }
])

potion.trigger('/samson', 'talk');
potion.trigger('/samson', 'speak');

Customization

Both methods and states can be customized on a per-router basis. Each router has three state categories. We can change the states a router uses upon initialization.

var potion = new Elixir({
  methods : [ 'eightfold', 'path' ],
  before : [ 'pre' ],
  on : [ 'event' ],
  after : [ 'post' ]
});

Elixir.R.log(potion.states);
Elixir.R.log(potion.methods);

As you might imagine, each state category can actually contain multiple states and order is preserved.

var potion = new Elixir({
  methods : [ 'eightfold', 'path' ],
  before : [ 'pre', 'setup' ],
  on : [ 'event', 'on' ],
  after : [ 'destroy', 'post' ]
});

Elixir.R.log(potion.states);
Elixir.R.log(potion.methods);

Conclusion

Elixir is a flexible, asynchronous, structured event handler. By combining states and methods with routes and paths we can drastically reduce code repitition.