-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 960d81d
Showing
9 changed files
with
276 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# /node_modules/* and /bower_components/* ignored by default | ||
|
||
# Ignore built files except build/index.js | ||
coverage/* | ||
build/* | ||
builds/* | ||
bundle.js |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"extends": [ | ||
"airbnb-base" | ||
], | ||
"plugins": [ | ||
"promise" | ||
], | ||
"rules": { | ||
"no-param-reassign": 0 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# directories | ||
.idea/* | ||
.vscode/* | ||
local | ||
|
||
# generated files | ||
.DS_Store | ||
.vscode | ||
Desktop.ini | ||
Thumbs.db | ||
|
||
# specific file types | ||
*.backup | ||
*.bak | ||
*.log | ||
*.log.* | ||
*.tmp | ||
*.backup | ||
|
||
# node specific | ||
node_modules | ||
coverage | ||
*/config/local.js | ||
package-lock.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# rools |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
{ | ||
"name": "rools", | ||
"version": "1.0.0-alpha.0", | ||
"description": "A small business rules engine for Node", | ||
"main": "src/index.js", | ||
"author": "Frank Thelen", | ||
"license": "MIT", | ||
"repository": { | ||
"type": "git", | ||
"url": "git://github.com/frankthelen/rools" | ||
}, | ||
"keywords": [ | ||
"rules", | ||
"business", | ||
"rule", | ||
"engine" | ||
], | ||
"scripts": { | ||
"lint": "eslint . --ignore-path ./.eslintignore", | ||
"test": "NODE_ENV=test mocha --recursive test", | ||
"cover": "NODE_ENV=test istanbul cover _mocha -- --recursive test", | ||
"coveralls": "npm run cover -- --report lcovonly && cat ./coverage/lcov.info | coveralls", | ||
"preversion": "npm run lint && npm test" | ||
}, | ||
"engines": { | ||
"node": ">=8.9.0" | ||
}, | ||
"devDependencies": { | ||
"chai": "^4.1.2", | ||
"chai-as-promised": "^7.1.1", | ||
"coveralls": "^3.0.0", | ||
"eslint": "^4.14.0", | ||
"eslint-config-airbnb-base": "^12.1.0", | ||
"eslint-plugin-import": "^2.8.0", | ||
"eslint-plugin-promise": "^3.6.0", | ||
"eslint-plugin-should-promised": "^2.0.0", | ||
"istanbul": "^0.4.5", | ||
"mocha": "^4.0.1", | ||
"mocha-lcov-reporter": "^1.3.0", | ||
"sinon": "^4.1.3", | ||
"sinon-chai": "^2.14.0" | ||
}, | ||
"dependencies": { | ||
"md5": "^2.2.1", | ||
"uuid": "^3.1.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
class Rools { | ||
constructor() { | ||
this.actions = []; | ||
this.premises = []; | ||
} | ||
|
||
register(...rules) { | ||
rules.forEach((rule) => { | ||
const action = { | ||
name: rule.name, | ||
then: rule.then, | ||
premises: [], | ||
ready: false, // ready to fire | ||
fired: false, // not yet fired | ||
}; | ||
this.actions.push(action); | ||
const whens = Array.isArray(rule.when) ? rule.when : [rule.when]; | ||
whens.forEach((when) => { | ||
const premise = { | ||
when, | ||
value: undefined, // not yet evaluated | ||
}; | ||
action.premises.push(premise); | ||
this.premises.push(premise); | ||
}); | ||
}); | ||
} | ||
|
||
execute(facts) { | ||
this.actions.forEach((action) => { // init | ||
action.ready = false; | ||
action.fired = false; | ||
}); | ||
let iteration = 0; | ||
do { | ||
this.premises.forEach((premise) => { | ||
premise.value = premise.when(facts); | ||
}); | ||
this.actions.filter(action => !action.fired).forEach((action) => { | ||
const num = action.premises.length; | ||
const tru = action.premises.filter(premise => premise.value).length; | ||
action.ready = tru === num; | ||
}); | ||
const actionsToBeFired = this.actions.filter(action => !action.fired && action.ready); | ||
if (actionsToBeFired.length === 0) { | ||
break; // for --> all done | ||
} | ||
if (actionsToBeFired.length > 1) { | ||
console.error('conflict resolution missing!'); | ||
} | ||
actionsToBeFired.forEach((action) => { | ||
console.log(`firing ${action.name}`); | ||
action.fired = true; | ||
action.then(facts); | ||
}); | ||
iteration += 1; | ||
} while (iteration < 100); | ||
return facts; | ||
} | ||
} | ||
|
||
module.exports = Rools; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
const Rools = require('./Rools'); | ||
|
||
module.exports = Rools; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"extends": "../.eslintrc", | ||
"env": { | ||
"mocha": true | ||
}, | ||
"globals" : { | ||
"assert": false, | ||
"expect": false, | ||
"should": false, | ||
"sinon": false | ||
}, | ||
"plugins": [ | ||
"should-promised" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
const chai = require('chai'); | ||
const chaiAsPromised = require('chai-as-promised'); | ||
const sinon = require('sinon'); | ||
const sinonChai = require('sinon-chai'); | ||
const Rools = require('../src'); | ||
|
||
chai.use(chaiAsPromised); | ||
chai.use(sinonChai); | ||
|
||
global.chai = chai; | ||
global.sinon = sinon; | ||
global.expect = chai.expect; | ||
global.should = chai.should(); | ||
|
||
describe('Simple tests', () => { | ||
const frank = { | ||
name: 'frank', | ||
stars: 347, | ||
}; | ||
|
||
const michael = { | ||
name: 'michael', | ||
stars: 156, | ||
}; | ||
|
||
const weatherGood = { | ||
temperature: 20, | ||
windy: true, | ||
rainy: false, | ||
}; | ||
|
||
const weatherBad = { | ||
temperature: 9, | ||
windy: true, | ||
rainy: true, | ||
}; | ||
|
||
const ruleMoodGreat = { | ||
name: 'mood is great if stars is greater than 200', | ||
when: facts => facts.user.stars >= 200, | ||
then: (facts) => { | ||
facts.user.mood = 'great'; | ||
}, | ||
}; | ||
|
||
const ruleMoodSad = { | ||
name: 'mood is sad if stars is lower or equals 200', | ||
when: facts => facts.user.stars < 200, | ||
then: (facts) => { | ||
facts.user.mood = 'sad'; | ||
}, | ||
}; | ||
|
||
const ruleGoWalking = { | ||
name: 'go for a walk if mood is great and the weather is fine', | ||
when: [ | ||
facts => facts.user.mood === 'great', | ||
facts => facts.weather.temperature >= 20, | ||
facts => !facts.weather.rainy, | ||
], | ||
then: (facts) => { | ||
facts.goWalking = true; | ||
}, | ||
}; | ||
|
||
const ruleStayAtHome = { | ||
name: 'stay at home if mood is sad or the weather is bad', | ||
when: [ | ||
facts => facts.weather.rainy || facts.user.mood === 'sad', | ||
], | ||
then: (facts) => { | ||
facts.stayAtHome = true; | ||
}, | ||
}; | ||
|
||
let rools; | ||
|
||
before(() => { | ||
rools = new Rools(); | ||
rools.register(ruleMoodGreat, ruleMoodSad, ruleGoWalking, ruleStayAtHome); | ||
}); | ||
|
||
after(() => { | ||
}); | ||
|
||
it('test 1', () => { | ||
const result = rools.execute({ user: frank, weather: weatherGood }); | ||
console.log(result); | ||
expect(result.user.mood).to.be.equal('great'); | ||
expect(result.goWalking).to.be.equal(true); | ||
}); | ||
|
||
it('test 2', () => { | ||
const result = rools.execute({ user: michael, weather: weatherGood }); | ||
console.log(result); | ||
expect(result.user.mood).to.be.equal('sad'); | ||
expect(result.stayAtHome).to.be.equal(true); | ||
}); | ||
|
||
it('test 3', () => { | ||
const result = rools.execute({ user: frank, weather: weatherBad }); | ||
console.log(result); | ||
expect(result.user.mood).to.be.equal('great'); | ||
expect(result.stayAtHome).to.be.equal(true); | ||
}); | ||
}); |