Skip to content

Commit bcde857

Browse files
author
Cache Hamm
authored
Merge pull request #13 from CacheControl/rule-chaining
Rule chaining
2 parents ac6b8e4 + 1e25204 commit bcde857

16 files changed

Lines changed: 382 additions & 85 deletions

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,24 @@
1+
1.3.0 / 2016-10-24
2+
==================
3+
4+
* Rule event emissions
5+
* Rule chaining
6+
7+
1.2.1 / 2016-10-22
8+
==================
9+
10+
* Use Array.indexOf instead of Array.includes for older node version compatibility
11+
12+
1.2.0 / 2016-09-13
13+
==================
14+
15+
* Fact path support
16+
17+
1.1.0 / 2016-09-11
18+
==================
19+
20+
* Custom operator support
21+
122
1.0.4 / 2016-06-18
223
==================
324

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ engine
9393
*/
9494
```
9595

96-
Run this example [here](./examples/nested-boolean-logic.js)
96+
This is available in the [examples](./examples/02-nested-boolean-logic.js)
9797

9898
## Advanced Example
9999

@@ -179,7 +179,7 @@ engine
179179
*/
180180
```
181181

182-
Run this example [here](./examples/nested-boolean-logic.js)
182+
This is available in the [examples](./examples/03-dynamic-facts)
183183

184184
## Docs
185185

dist/rule.js

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,23 @@ var _condition = require('./condition');
1414

1515
var _condition2 = _interopRequireDefault(_condition);
1616

17+
var _events = require('events');
18+
1719
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
1820

1921
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { return step("next", value); }, function (err) { return step("throw", err); }); } } return step("next"); }); }; }
2022

2123
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
2224

25+
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
26+
27+
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
28+
2329
var debug = require('debug')('json-rules-engine');
2430

25-
var Rule = function () {
31+
var Rule = function (_EventEmitter) {
32+
_inherits(Rule, _EventEmitter);
33+
2634
/**
2735
* returns a new Rule instance
2836
* @param {object,string} options, or json string that can be parsed into options
@@ -36,18 +44,27 @@ var Rule = function () {
3644
function Rule(options) {
3745
_classCallCheck(this, Rule);
3846

47+
var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(Rule).call(this));
48+
3949
if (typeof options === 'string') {
4050
options = JSON.parse(options);
4151
}
4252
if (options && options.conditions) {
43-
this.setConditions(options.conditions);
53+
_this.setConditions(options.conditions);
54+
}
55+
if (options && options.onSuccess) {
56+
_this.on('success', options.onSuccess);
57+
}
58+
if (options && options.onFailure) {
59+
_this.on('failure', options.onFailure);
4460
}
4561

4662
var priority = options && options.priority || 1;
47-
this.setPriority(priority);
63+
_this.setPriority(priority);
4864

4965
var event = options && options.event || { type: 'unknown' };
50-
this.setEvent(event);
66+
_this.setEvent(event);
67+
return _this;
5168
}
5269

5370
/**
@@ -134,14 +151,14 @@ var Rule = function () {
134151
}, {
135152
key: 'prioritizeConditions',
136153
value: function prioritizeConditions(conditions) {
137-
var _this = this;
154+
var _this2 = this;
138155

139156
var factSets = conditions.reduce(function (sets, condition) {
140157
// if a priority has been set on this specific condition, honor that first
141158
// otherwise, use the fact's priority
142159
var priority = condition.priority;
143160
if (!priority) {
144-
var fact = _this.engine.getFact(condition.fact);
161+
var fact = _this2.engine.getFact(condition.fact);
145162
priority = fact && fact.priority || 1;
146163
}
147164
if (!sets[priority]) sets[priority] = [];
@@ -165,7 +182,7 @@ var Rule = function () {
165182
key: 'evaluate',
166183
value: function () {
167184
var _ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee6(almanac) {
168-
var _this2 = this;
185+
var _this3 = this;
169186

170187
var evaluateCondition, evaluateConditions, prioritizeAndRun, any, all;
171188
return regeneratorRuntime.wrap(function _callee6$(_context6) {
@@ -179,7 +196,7 @@ var Rule = function () {
179196
*/
180197
evaluateCondition = function () {
181198
var _ref2 = _asyncToGenerator(regeneratorRuntime.mark(function _callee(condition) {
182-
var comparisonValue, subConditions;
199+
var comparisonValue, subConditions, passes;
183200
return regeneratorRuntime.wrap(function _callee$(_context) {
184201
while (1) {
185202
switch (_context.prev = _context.next) {
@@ -225,14 +242,25 @@ var Rule = function () {
225242
comparisonValue = _context.sent;
226243

227244
case 17:
228-
return _context.abrupt('return', condition.evaluate(comparisonValue, _this2.engine.operators));
245+
_context.next = 19;
246+
return condition.evaluate(comparisonValue, _this3.engine.operators);
247+
248+
case 19:
249+
passes = _context.sent;
250+
251+
if (passes) {
252+
_this3.emit('success', _this3.event, almanac);
253+
} else {
254+
_this3.emit('failure', _this3.event, almanac);
255+
}
256+
return _context.abrupt('return', passes);
229257

230-
case 18:
258+
case 22:
231259
case 'end':
232260
return _context.stop();
233261
}
234262
}
235-
}, _callee, _this2);
263+
}, _callee, _this3);
236264
}));
237265

238266
return function evaluateCondition(_x3) {
@@ -274,7 +302,7 @@ var Rule = function () {
274302
return _context2.stop();
275303
}
276304
}
277-
}, _callee2, _this2);
305+
}, _callee2, _this3);
278306
}));
279307

280308
return function evaluateConditions(_x4, _x5) {
@@ -314,7 +342,7 @@ var Rule = function () {
314342
if (operator === 'all') {
315343
method = Array.prototype.every;
316344
}
317-
orderedSets = _this2.prioritizeConditions(conditions);
345+
orderedSets = _this3.prioritizeConditions(conditions);
318346
cursor = Promise.resolve();
319347

320348
orderedSets.forEach(function (set) {
@@ -344,7 +372,7 @@ var Rule = function () {
344372
return _context3.stop();
345373
}
346374
}
347-
}, _callee3, _this2);
375+
}, _callee3, _this3);
348376
}));
349377

350378
return function prioritizeAndRun(_x6, _x7) {
@@ -372,7 +400,7 @@ var Rule = function () {
372400
return _context4.stop();
373401
}
374402
}
375-
}, _callee4, _this2);
403+
}, _callee4, _this3);
376404
}));
377405

378406
return function any(_x8) {
@@ -400,7 +428,7 @@ var Rule = function () {
400428
return _context5.stop();
401429
}
402430
}
403-
}, _callee5, _this2);
431+
}, _callee5, _this3);
404432
}));
405433

406434
return function all(_x9) {
@@ -443,6 +471,6 @@ var Rule = function () {
443471
}]);
444472

445473
return Rule;
446-
}();
474+
}(_events.EventEmitter);
447475

448476
exports.default = Rule;

docs/almanac.md

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ almanac
2222
.then( values => console.log(values))
2323
```
2424

25+
### almanac.addRuntimeFact(String factId, Mixed value)
26+
27+
Sets a constant fact mid-run. Often used in conjunction with rule and engine event emissions.
28+
29+
```js
30+
almanac.addRuntimeFact('account-id', 1)
31+
```
32+
2533
## Common Use Cases
2634

2735
### Fact dependencies
@@ -101,4 +109,39 @@ engine.on('success', (event, almanac) => {
101109
return request.post({ url: `http://my-service/toggle?funded=${!info.funded}`)
102110
})
103111
})
104-
```
112+
```
113+
114+
### Rule Chaining
115+
116+
The `almanac.addRuntimeFact()` method may be used in conjunction with event emissions to
117+
set fact values during runtime, effectively enabling _rule-chaining_. Note that ordering
118+
of rule execution is enabled via the `priority` option, and is crucial component to propertly
119+
configuring rule chaining.
120+
121+
```js
122+
engine.addRule({
123+
conditions,
124+
event,
125+
onSuccess: function (event, almanac) {
126+
almanac.addRuntimeFact('rule-1-passed', true) // track that the rule passed
127+
},
128+
onFailure: function (event, almanac) {
129+
almanac.addRuntimeFact('rule-1-passed', false) // track that the rule failed
130+
},
131+
priority: 10 // a higher priority ensures this rule will be run prior to subsequent rules
132+
})
133+
134+
// in a later rule:
135+
engine.addRule({
136+
conditions: {
137+
all: [{
138+
fact: 'rule-1-passed',
139+
operator: 'equal',
140+
value: true
141+
}
142+
},
143+
priority: 1 // lower priority ensures this is run AFTER its predecessor
144+
}
145+
```
146+
147+
See the [full example](../examples/07-rule-chaining.js)

docs/engine.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ let Rule = require('json-rules-engine').Rule
4343
engine.addRule({
4444
conditions: {},
4545
event: {},
46-
priority: 1
46+
priority: 1, // optional, default: 1
47+
onSuccess: function (event, almanac) {}, // optional
48+
onFailure: function (event, almanac) {}, // optional
4749
})
4850

4951
// or rule instance:

docs/rules.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,29 @@ let rule = new Rule(jsonString) // restored rule; same conditions, priority, eve
7676
let jsonObject = rule.toJSON(false) // object: {conditions:{ all: [] }, priority: 50 ...
7777
```
7878

79+
### Events
80+
81+
Listen for 'success' and 'failure' events emitted when rule is evaluated.
82+
83+
#### ```rule.on('success', Function(Object event, Almanac almanac))```
84+
85+
```js
86+
// whenever rule is evaluated and the conditions pass, 'success' will trigger
87+
rule.on('success', function(event, almanac) {
88+
console.log(event) // { type: 'my-event', params: { id: 1 }
89+
})
90+
```
91+
92+
#### ```rule.on('failure', Function(Object event, Almanac almanac))```
93+
94+
Companion to 'success', except fires when the rule fails.
95+
96+
```js
97+
engine.on('failure', function(event, almanac) {
98+
console.log(event) // { type: 'my-event', params: { id: 1 }
99+
})
100+
```
101+
79102
## Conditions
80103

81104
Each rule condition must begin with a boolean operator(```all``` or ```any```) at its root.

examples/01-hello-world.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
* This is the hello-world example from the README.
55
*
66
* Usage:
7-
* node ./examples/hello-world.js
7+
* node ./examples/01-hello-world.js
88
*
99
* For detailed output:
10-
* DEBUG=json-rules-engine node ./examples/hello-world.js
10+
* DEBUG=json-rules-engine node ./examples/01-hello-world.js
1111
*/
1212

1313
require('colors')

examples/02-nested-boolean-logic.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
* This example demonstates nested boolean logic - e.g. (x OR y) AND (a OR b).
55
*
66
* Usage:
7-
* node ./examples/nested-boolean-logic.js
7+
* node ./examples/02-nested-boolean-logic.js
88
*
99
* For detailed output:
10-
* DEBUG=json-rules-engine node ./examples/nested-boolean-logic.js
10+
* DEBUG=json-rules-engine node ./examples/02-nested-boolean-logic.js
1111
*/
1212

1313
require('colors')

examples/03-dynamic-facts.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
* to select object properties returned by facts
66
*
77
* Usage:
8-
* node ./examples/dynamic-facts.js
8+
* node ./examples/03-dynamic-facts.js
99
*
1010
* For detailed output:
11-
* DEBUG=json-rules-engine node ./examples/dynamic-facts.js
11+
* DEBUG=json-rules-engine node ./examples/03-dynamic-facts.js
1212
*/
1313

1414
require('colors')

examples/04-fact-dependency.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
* from outside sources (api's, databases, etc)
77
*
88
* Usage:
9-
* node ./examples/fact-dependency.js
9+
* node ./examples/04-fact-dependency.js
1010
*
1111
* For detailed output:
12-
* DEBUG=json-rules-engine node ./examples/fact-dependency.js
12+
* DEBUG=json-rules-engine node ./examples/04-fact-dependency.js
1313
*/
1414

1515
require('colors')

0 commit comments

Comments
 (0)