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

chain-commander

v3.0.2

Published

Chain commander is a library based on Bluebird promise library, to encapsulate business rules logic in form of javascript objects or JSON.

Downloads

51

Readme

Build Status Coverage Status

Chain Commander

Chain commander is a library based on bluebird library, to encapsulate business rules logic in form of javascript objects or JSON.

Since it uses promises, it can keep going until no more "then"s are found, while mutating the value it started with. You can store your commands in JSON or in a POJO (plain old javascript object), and execute them whenever you need, asynchronously, associating them with a context.

You can change the business logic in your database/json definition files, and don't ever have to touch your frontend code, it all depends on the context and your defined functions.

Usage

Install from NPM:

npm install chain-commander

Require it in your node:

var
    Q = require('bluebird'),
    cc = require('chain-commander')(Q);

or browser, after Bluebird:

<script src="bluebird.min.js"></script>
<script src="chain-commander.js"></script>

Business rules

Let's pretend you have a lot of users and each user have their own triggers and conditional, so they can execute their own "code" in your app, like give instructions to your app to execute code in order they are meant to.

Basically, your definitions should not be aware of your object data, but your context functions should. That means that if your object has an id, a name and some definitions, and if you want your function to know the id, you should pass your objects to the execute function or a literal argument.

You can store anything in the the rules, from coordinates, sequence of commands, conditional statements, pairs of data.

The rules are meant to be "non verbose", to be easy to write and adapt, when testing with new rules on the database or json file for example. It's also meant to feel natural and familiar to javascript (if and else, brackets and strings).

They offload the dynamic nature of your applcation to where it belongs: your data. The application code should only be prepared to deal with whatever you throw at it.

var cmds = [ // It uses arrays because it's the only way it can ensure order.
        {
          if: {
            // if (obj.check("first") and obj.check("second")) then

            check: [
              ['check', 'first'], // must match all
              ['check', 'second']
            ],

            // will be executed if obj.check("first") and obj.check("second") returns true

            // ALL checks can return promises as well

            exec      : [
              ['multiply', 10]
            ],

            // neither checks were present, make another check

            // "else" works as "if not"

            else      : {
              if  : {

                // else if (obj.check("third")) then

                check: [
                  ['check', 'third'] // obj.check("third")
                ],

                // will be executed only the obj.check("third") returns true

                exec      : [
                  ['alert', {msg: 'aww yeah'}] // alert({msg: "aww yeah"})
                ]
              },

              // this will be executed regardless of the above if

              exec: [
                ['add', 1],               // execute add(1)
                ['alert', {msg: 'else'}]  // execute alert({msg: "else"})
              ]
            }
          }
        },
        {
          // exec the alert function
          exec: [
            ['alert', {msg: 'Doodidoo'}]
          ]
        },
        {
          // exec the startTimer
          exec: [
            ['startTimer']
          ]
        },
        {
          // go on! new values, it will start executing after 1s from startTimer
          exec: [
            ['goOn']
          ]
        }
      ];

Given the above object/JSON, you will create a new context that will deal with all the data in order:

      var instance = {
        myvalue   : 3,
        check     : function (type, value){ // if in your definition, you call your function with paramters, the last param
                                            // (in this case, called "value") will always be the current value in chain
          if (type === 'first') {
            return value > 0;
          } else if (type === 'second') {
            return value < 30;
          }
          return true;
        },
        getValue  : function (){
          return this.myvalue === 3;
        },
        multiply  : function (number, value){
          return value * number;
        },
        add       : function (number, value){
          return value + number;
        },
        log     : function (args, value){
          console.log(args.msg + ': ' + value); // does nothing, just logs to console
          return value;
        },
        goOn      : function (value){
          if (!this.secondPass) { // pay attention that the context is at the moment of the execution

            this.secondPass = true; // important so it won't create an endless loop
            return cc.execute(value, instance); // yes, you can execute the same CC and continue from there, start all over
          }
        },
        startTimer: function (args, value){
          var self = this;

          return new Q(function(resolve, reject) {
            if (self.secondPass === false) {
                setTimeout(function (){
                    resolve('Oops'); // this will be overwritten by the goOn "exec", that will continue from the previous value
                }, 1000);
            } else {
                reject();
            }
          });
        }
      };

      cc = CC(cmds);
      cc.execute(10, instance).done(function (value){
        console.log(value);
        // outputs 101, since the value at goOn time is 100, so the first check fails, and adds 1
        done();
      });

The above is a "silly" example (although a long one).

For a full-fledged practical example, check the example folder, there's a "build your plan with combos and many activities" type of app created with Chain Commander and AngularJS (do a bower install inside that folder first)

API

new ChainCommander(definitions: Array|string, options: Object = null)

Creates a new predefined chain that will execute your code when you call execute

var cc = new ChainCommander([
    {
        "exec":[
            ["oops"]
        ]
    }
]);

When creating a ChainCommander with the member function, all executed functions will be passed as the last parameter the current bound object, like so:

var
  context = {
    oops: function(value, thisobj){
      // value = 0 (from execute)
      // thisobj = obj
    }
  },
  obj = {
    some:'data',
    defs:[
        {
            "exec":[
                ["oops"]
            ]
        }
    ]
  }, cc = new ChainCommander(obj, {debug: true, throws: true, member: 'defs'});

cc.execute(0, context);

ChainCommander.prototype.execute(initialValue: any, context: Object|Function)

Executes your definitions on the given context. Returns a promise.

var
    cc = new ChainCommander([
        {
            "exec":[
                ["oops"]
            ]
        }
    ], {debug: true, throws: true}),
    context = {
        oops: function(obj){
            obj.initial = 'b';
            obj.value++;

            return obj;
        }
    };

    cc.execute({initial: 'a', value: 0}, context).done(function(value){
        console.log(value); // {initial: 'b', value: 1}
    });

ChainCommander.all(initialValue: any, arr: ChainCommander[], context: Object|Function, tap: Function = void 0)

Taking that you have an array with many Chain Commanders, that you want to execute in order, with the same context returning a promise in the end:

var arrayOfCommanders = [
    new ChainCommander(defs),
    new ChainCommander(defs2),
    new ChainCommander(defs3),
];

ChainCommander.all('initial value', arrayOfCommanders, context).done(function(value){
  // initial value transformed
});

the arrayOfCommanders also allow an array of array of Commanders, that means, you can use it like this:

function getSelectedAndReturnArray(arr){
    return arr
        .filter(function(i){ return i.selected; })
        .map(function(i){ return i.cmds; });
}

var arrayOfItemsThatGotCommanders1 = [createCommander(0), createCommander(1) /*...*/];

ChainCommander.all('initial value', [
    getSelectedAndReturnArray(arrayOfItemsThatGotCommanders1),
    commanderInstance
], context).then(function(value){
  // value = 'initial value' transformed
});

The optional tap parameter is a functions that after each item, will be called. It's a side effect callback and you can use it to "spy" on the current commands being executed

ChainCommander.all('initial value', arrayofcmds, context, console.log); // spits out the current value (and the item in the last parameter if you used member)

Debug

The definition and the executing code is "forgiving", it means, if you try to use a function that doesnt exists, it will fail silently and continue in the chain.

You can watch the flow in a verbose mode setting the debug option to true

var cc = ChainCommander(definitionArray, {debug: true, throws: true});

then watch your console. If you want to spot undefined functions or mismatched arguments, set throws to true.

Never use any of those in production, since they might generate a lot of unresolved promises.