{"id":17458111,"url":"https://github.com/pocesar/js-chain-commander","last_synced_at":"2025-10-08T23:11:26.727Z","repository":{"id":13264393,"uuid":"15949659","full_name":"pocesar/js-chain-commander","owner":"pocesar","description":"Chain commander is a library based on bluebird promises library, to encapsulate business rules logic in form of javascript objects or JSON.","archived":false,"fork":false,"pushed_at":"2016-07-17T16:53:15.000Z","size":50,"stargazers_count":7,"open_issues_count":0,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-10-18T06:27:59.489Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pocesar.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-01-15T21:52:36.000Z","updated_at":"2018-11-24T02:43:09.000Z","dependencies_parsed_at":"2022-08-25T17:03:33.867Z","dependency_job_id":null,"html_url":"https://github.com/pocesar/js-chain-commander","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pocesar%2Fjs-chain-commander","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pocesar%2Fjs-chain-commander/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pocesar%2Fjs-chain-commander/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pocesar%2Fjs-chain-commander/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pocesar","download_url":"https://codeload.github.com/pocesar/js-chain-commander/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244109252,"owners_count":20399529,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-18T03:54:33.594Z","updated_at":"2025-10-08T23:11:21.687Z","avatar_url":"https://github.com/pocesar.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://travis-ci.org/pocesar/js-chain-commander.png?branch=master)](https://travis-ci.org/pocesar/js-chain-commander) [![Coverage Status](https://coveralls.io/repos/pocesar/js-chain-commander/badge.png)](https://coveralls.io/r/pocesar/js-chain-commander)\r\n\r\nChain Commander\r\n==================\r\n\r\nChain commander is a library based on [bluebird](https://github.com/petkaantonov/bluebird) library, to encapsulate\r\nbusiness rules logic in form of javascript objects or JSON.\r\n\r\nSince it uses promises, it can keep going until no more \"then\"s are found, while mutating the value it started with.\r\nYou can store your commands in JSON or in a POJO (plain old javascript object), and execute them whenever you need,\r\nasynchronously, associating them with a context.\r\n\r\nYou can change the business logic in your database/json definition files, and don't ever have to touch your frontend code,\r\nit all depends on the context and your defined functions.\r\n\r\n## Usage\r\n\r\nInstall from NPM:\r\n\r\n```\r\nnpm install chain-commander\r\n```\r\n\r\nRequire it in your node:\r\n\r\n```js\r\nvar\r\n    Q = require('bluebird'),\r\n    cc = require('chain-commander')(Q);\r\n```\r\n\r\nor browser, after Bluebird:\r\n\r\n```html\r\n\u003cscript src=\"bluebird.min.js\"\u003e\u003c/script\u003e\r\n\u003cscript src=\"chain-commander.js\"\u003e\u003c/script\u003e\r\n```\r\n\r\n## Business rules\r\n\r\nLet's pretend you have a lot of users and each user have their own triggers and conditional, so they can\r\nexecute their own \"code\" in your app, like give instructions to your app to execute code in order they are meant to.\r\n\r\nBasically, your definitions should not be aware of your object data, but your context functions should.\r\nThat means that if your object has an id, a name and some definitions, and if you want your function to\r\nknow the id, you should pass your objects to the `execute` function or a literal argument.\r\n\r\nYou can store anything in the the rules, from coordinates, sequence of commands, conditional statements, pairs of data.\r\n\r\nThe 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\r\nfor example. It's also meant to feel natural and familiar to javascript (`if` and `else`, brackets and strings).\r\n\r\nThey offload the dynamic nature of your applcation to where it belongs: your data. The application code should only be prepared\r\nto deal with whatever you throw at it.\r\n\r\n```js\r\nvar cmds = [ // It uses arrays because it's the only way it can ensure order.\r\n        {\r\n          if: {\r\n            // if (obj.check(\"first\") and obj.check(\"second\")) then\r\n\r\n            check: [\r\n              ['check', 'first'], // must match all\r\n              ['check', 'second']\r\n            ],\r\n\r\n            // will be executed if obj.check(\"first\") and obj.check(\"second\") returns true\r\n\r\n            // ALL checks can return promises as well\r\n\r\n            exec      : [\r\n              ['multiply', 10]\r\n            ],\r\n\r\n            // neither checks were present, make another check\r\n\r\n            // \"else\" works as \"if not\"\r\n\r\n            else      : {\r\n              if  : {\r\n\r\n                // else if (obj.check(\"third\")) then\r\n\r\n                check: [\r\n                  ['check', 'third'] // obj.check(\"third\")\r\n                ],\r\n\r\n                // will be executed only the obj.check(\"third\") returns true\r\n\r\n                exec      : [\r\n                  ['alert', {msg: 'aww yeah'}] // alert({msg: \"aww yeah\"})\r\n                ]\r\n              },\r\n\r\n              // this will be executed regardless of the above if\r\n\r\n              exec: [\r\n                ['add', 1],               // execute add(1)\r\n                ['alert', {msg: 'else'}]  // execute alert({msg: \"else\"})\r\n              ]\r\n            }\r\n          }\r\n        },\r\n        {\r\n          // exec the alert function\r\n          exec: [\r\n            ['alert', {msg: 'Doodidoo'}]\r\n          ]\r\n        },\r\n        {\r\n          // exec the startTimer\r\n          exec: [\r\n            ['startTimer']\r\n          ]\r\n        },\r\n        {\r\n          // go on! new values, it will start executing after 1s from startTimer\r\n          exec: [\r\n            ['goOn']\r\n          ]\r\n        }\r\n      ];\r\n```\r\n\r\nGiven the above object/JSON, you will create a new context that will deal with all the data in order:\r\n\r\n```js\r\n      var instance = {\r\n        myvalue   : 3,\r\n        check     : function (type, value){ // if in your definition, you call your function with paramters, the last param\r\n                                            // (in this case, called \"value\") will always be the current value in chain\r\n          if (type === 'first') {\r\n            return value \u003e 0;\r\n          } else if (type === 'second') {\r\n            return value \u003c 30;\r\n          }\r\n          return true;\r\n        },\r\n        getValue  : function (){\r\n          return this.myvalue === 3;\r\n        },\r\n        multiply  : function (number, value){\r\n          return value * number;\r\n        },\r\n        add       : function (number, value){\r\n          return value + number;\r\n        },\r\n        log     : function (args, value){\r\n          console.log(args.msg + ': ' + value); // does nothing, just logs to console\r\n          return value;\r\n        },\r\n        goOn      : function (value){\r\n          if (!this.secondPass) { // pay attention that the context is at the moment of the execution\r\n\r\n            this.secondPass = true; // important so it won't create an endless loop\r\n            return cc.execute(value, instance); // yes, you can execute the same CC and continue from there, start all over\r\n          }\r\n        },\r\n        startTimer: function (args, value){\r\n          var self = this;\r\n\r\n          return new Q(function(resolve, reject) {\r\n            if (self.secondPass === false) {\r\n                setTimeout(function (){\r\n                    resolve('Oops'); // this will be overwritten by the goOn \"exec\", that will continue from the previous value\r\n                }, 1000);\r\n            } else {\r\n                reject();\r\n            }\r\n          });\r\n        }\r\n      };\r\n\r\n      cc = CC(cmds);\r\n      cc.execute(10, instance).done(function (value){\r\n        console.log(value);\r\n        // outputs 101, since the value at goOn time is 100, so the first check fails, and adds 1\r\n        done();\r\n      });\r\n```\r\n\r\nThe above is a \"silly\" example (although a long one).\r\n\r\nFor a full-fledged practical example, check the `example` folder, there's a \"build your plan with combos and many activities\"\r\ntype of app created with Chain Commander and AngularJS (do a `bower install` inside that folder first)\r\n\r\n## API\r\n\r\n### new ChainCommander(definitions: Array|string, options: Object = null)\r\n\r\nCreates a new predefined chain that will execute your code when you call `execute`\r\n\r\n```js\r\nvar cc = new ChainCommander([\r\n    {\r\n        \"exec\":[\r\n            [\"oops\"]\r\n        ]\r\n    }\r\n]);\r\n```\r\n\r\nWhen creating a `ChainCommander` with the `member` function, all executed functions will be passed as the last\r\nparameter the current bound object, like so:\r\n\r\n```js\r\nvar\r\n  context = {\r\n    oops: function(value, thisobj){\r\n      // value = 0 (from execute)\r\n      // thisobj = obj\r\n    }\r\n  },\r\n  obj = {\r\n    some:'data',\r\n    defs:[\r\n        {\r\n            \"exec\":[\r\n                [\"oops\"]\r\n            ]\r\n        }\r\n    ]\r\n  }, cc = new ChainCommander(obj, {debug: true, throws: true, member: 'defs'});\r\n\r\ncc.execute(0, context);\r\n```\r\n\r\n### ChainCommander.prototype.execute(initialValue: any, context: Object|Function)\r\n\r\nExecutes your definitions on the given context. Returns a promise.\r\n\r\n```js\r\nvar\r\n    cc = new ChainCommander([\r\n        {\r\n            \"exec\":[\r\n                [\"oops\"]\r\n            ]\r\n        }\r\n    ], {debug: true, throws: true}),\r\n    context = {\r\n        oops: function(obj){\r\n            obj.initial = 'b';\r\n            obj.value++;\r\n\r\n            return obj;\r\n        }\r\n    };\r\n\r\n    cc.execute({initial: 'a', value: 0}, context).done(function(value){\r\n        console.log(value); // {initial: 'b', value: 1}\r\n    });\r\n```\r\n\r\n### ChainCommander.all(initialValue: any, arr: ChainCommander[], context: Object|Function, tap: Function = void 0)\r\n\r\nTaking that you have an array with many Chain Commanders, that you want to execute in order, with the same context\r\nreturning a promise in the end:\r\n\r\n```js\r\nvar arrayOfCommanders = [\r\n    new ChainCommander(defs),\r\n    new ChainCommander(defs2),\r\n    new ChainCommander(defs3),\r\n];\r\n\r\nChainCommander.all('initial value', arrayOfCommanders, context).done(function(value){\r\n  // initial value transformed\r\n});\r\n```\r\n\r\nthe arrayOfCommanders also allow an array of array of Commanders, that means, you can use it like this:\r\n\r\n```js\r\nfunction getSelectedAndReturnArray(arr){\r\n    return arr\r\n        .filter(function(i){ return i.selected; })\r\n        .map(function(i){ return i.cmds; });\r\n}\r\n\r\nvar arrayOfItemsThatGotCommanders1 = [createCommander(0), createCommander(1) /*...*/];\r\n\r\nChainCommander.all('initial value', [\r\n    getSelectedAndReturnArray(arrayOfItemsThatGotCommanders1),\r\n    commanderInstance\r\n], context).then(function(value){\r\n  // value = 'initial value' transformed\r\n});\r\n```\r\n\r\nThe optional `tap` parameter is a functions that after each item, will be called. It's a side effect\r\ncallback and you can use it to \"spy\" on the current commands being executed\r\n\r\n```js\r\nChainCommander.all('initial value', arrayofcmds, context, console.log); // spits out the current value (and the item in the last parameter if you used member)\r\n```\r\n\r\n## Debug\r\n\r\nThe definition and the executing code is \"forgiving\", it means, if you try to use a function that doesnt exists,\r\nit will fail silently and continue in the chain.\r\n\r\nYou can watch the flow in a verbose mode setting the `debug` option to `true`\r\n\r\n```js\r\nvar cc = ChainCommander(definitionArray, {debug: true, throws: true});\r\n```\r\n\r\nthen watch your console. If you want to spot undefined functions or mismatched arguments, set `throws` to `true`.\r\n\r\n**Never use any of those in production, since they might generate a lot of unresolved promises.**\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpocesar%2Fjs-chain-commander","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpocesar%2Fjs-chain-commander","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpocesar%2Fjs-chain-commander/lists"}