Skip to content

Commit

Permalink
first drop
Browse files Browse the repository at this point in the history
  • Loading branch information
frankthelen committed Dec 28, 2017
0 parents commit 960d81d
Show file tree
Hide file tree
Showing 9 changed files with 276 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .eslintignore
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
11 changes: 11 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": [
"airbnb-base"
],
"plugins": [
"promise"
],
"rules": {
"no-param-reassign": 0
}
}
24 changes: 24 additions & 0 deletions .gitignore
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# rools
47 changes: 47 additions & 0 deletions package.json
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"
}
}
62 changes: 62 additions & 0 deletions src/Rools.js
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;
3 changes: 3 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const Rools = require('./Rools');

module.exports = Rools;
15 changes: 15 additions & 0 deletions test/.eslintrc
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"
]
}
106 changes: 106 additions & 0 deletions test/simple.spec.js
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);
});
});

0 comments on commit 960d81d

Please sign in to comment.