@shepherdnerds/json-rules-engine 7.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.babelrc +3 -0
- package/.claude/settings.local.json +24 -0
- package/.github/workflows/deploy.yml +45 -0
- package/.github/workflows/node.js.yml +28 -0
- package/CHANGELOG.md +167 -0
- package/LICENSE +15 -0
- package/README.md +235 -0
- package/dist/almanac.js +269 -0
- package/dist/condition.js +331 -0
- package/dist/debug.js +18 -0
- package/dist/engine-default-operator-decorators.js +42 -0
- package/dist/engine-default-operators.js +50 -0
- package/dist/engine.js +451 -0
- package/dist/errors.js +32 -0
- package/dist/fact.js +129 -0
- package/dist/index.js +3 -0
- package/dist/json-rules-engine.js +48 -0
- package/dist/operator-decorator.js +60 -0
- package/dist/operator-map.js +178 -0
- package/dist/operator.js +50 -0
- package/dist/rule-result.js +80 -0
- package/dist/rule.js +525 -0
- package/dist/scoped-almanac.js +120 -0
- package/package.json +96 -0
- package/types/index.d.ts +312 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
var _operator = require('./operator');
|
|
8
|
+
|
|
9
|
+
var _operator2 = _interopRequireDefault(_operator);
|
|
10
|
+
|
|
11
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
12
|
+
|
|
13
|
+
var Operators = [];
|
|
14
|
+
Operators.push(new _operator2.default('equal', function (a, b) {
|
|
15
|
+
return a === b;
|
|
16
|
+
}));
|
|
17
|
+
Operators.push(new _operator2.default('notEqual', function (a, b) {
|
|
18
|
+
return a !== b;
|
|
19
|
+
}));
|
|
20
|
+
Operators.push(new _operator2.default('in', function (a, b) {
|
|
21
|
+
return b.indexOf(a) > -1;
|
|
22
|
+
}));
|
|
23
|
+
Operators.push(new _operator2.default('notIn', function (a, b) {
|
|
24
|
+
return b.indexOf(a) === -1;
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
Operators.push(new _operator2.default('contains', function (a, b) {
|
|
28
|
+
return a.indexOf(b) > -1;
|
|
29
|
+
}, Array.isArray));
|
|
30
|
+
Operators.push(new _operator2.default('doesNotContain', function (a, b) {
|
|
31
|
+
return a.indexOf(b) === -1;
|
|
32
|
+
}, Array.isArray));
|
|
33
|
+
|
|
34
|
+
function numberValidator(factValue) {
|
|
35
|
+
return Number.parseFloat(factValue).toString() !== 'NaN';
|
|
36
|
+
}
|
|
37
|
+
Operators.push(new _operator2.default('lessThan', function (a, b) {
|
|
38
|
+
return a < b;
|
|
39
|
+
}, numberValidator));
|
|
40
|
+
Operators.push(new _operator2.default('lessThanInclusive', function (a, b) {
|
|
41
|
+
return a <= b;
|
|
42
|
+
}, numberValidator));
|
|
43
|
+
Operators.push(new _operator2.default('greaterThan', function (a, b) {
|
|
44
|
+
return a > b;
|
|
45
|
+
}, numberValidator));
|
|
46
|
+
Operators.push(new _operator2.default('greaterThanInclusive', function (a, b) {
|
|
47
|
+
return a >= b;
|
|
48
|
+
}, numberValidator));
|
|
49
|
+
|
|
50
|
+
exports.default = Operators;
|
package/dist/engine.js
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.FINISHED = exports.RUNNING = exports.READY = undefined;
|
|
7
|
+
|
|
8
|
+
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
|
|
9
|
+
|
|
10
|
+
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
|
|
11
|
+
|
|
12
|
+
var _fact = require('./fact');
|
|
13
|
+
|
|
14
|
+
var _fact2 = _interopRequireDefault(_fact);
|
|
15
|
+
|
|
16
|
+
var _rule = require('./rule');
|
|
17
|
+
|
|
18
|
+
var _rule2 = _interopRequireDefault(_rule);
|
|
19
|
+
|
|
20
|
+
var _almanac = require('./almanac');
|
|
21
|
+
|
|
22
|
+
var _almanac2 = _interopRequireDefault(_almanac);
|
|
23
|
+
|
|
24
|
+
var _eventemitter = require('eventemitter2');
|
|
25
|
+
|
|
26
|
+
var _eventemitter2 = _interopRequireDefault(_eventemitter);
|
|
27
|
+
|
|
28
|
+
var _engineDefaultOperators = require('./engine-default-operators');
|
|
29
|
+
|
|
30
|
+
var _engineDefaultOperators2 = _interopRequireDefault(_engineDefaultOperators);
|
|
31
|
+
|
|
32
|
+
var _engineDefaultOperatorDecorators = require('./engine-default-operator-decorators');
|
|
33
|
+
|
|
34
|
+
var _engineDefaultOperatorDecorators2 = _interopRequireDefault(_engineDefaultOperatorDecorators);
|
|
35
|
+
|
|
36
|
+
var _debug = require('./debug');
|
|
37
|
+
|
|
38
|
+
var _debug2 = _interopRequireDefault(_debug);
|
|
39
|
+
|
|
40
|
+
var _condition = require('./condition');
|
|
41
|
+
|
|
42
|
+
var _condition2 = _interopRequireDefault(_condition);
|
|
43
|
+
|
|
44
|
+
var _operatorMap = require('./operator-map');
|
|
45
|
+
|
|
46
|
+
var _operatorMap2 = _interopRequireDefault(_operatorMap);
|
|
47
|
+
|
|
48
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
49
|
+
|
|
50
|
+
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
51
|
+
|
|
52
|
+
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; }
|
|
53
|
+
|
|
54
|
+
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; }
|
|
55
|
+
|
|
56
|
+
var READY = exports.READY = 'READY';
|
|
57
|
+
var RUNNING = exports.RUNNING = 'RUNNING';
|
|
58
|
+
var FINISHED = exports.FINISHED = 'FINISHED';
|
|
59
|
+
|
|
60
|
+
var Engine = function (_EventEmitter) {
|
|
61
|
+
_inherits(Engine, _EventEmitter);
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Returns a new Engine instance
|
|
65
|
+
* @param {Rule[]} rules - array of rules to initialize with
|
|
66
|
+
*/
|
|
67
|
+
function Engine() {
|
|
68
|
+
var rules = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
|
|
69
|
+
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
70
|
+
|
|
71
|
+
_classCallCheck(this, Engine);
|
|
72
|
+
|
|
73
|
+
var _this = _possibleConstructorReturn(this, (Engine.__proto__ || Object.getPrototypeOf(Engine)).call(this));
|
|
74
|
+
|
|
75
|
+
_this.rules = [];
|
|
76
|
+
_this.allowUndefinedFacts = options.allowUndefinedFacts || false;
|
|
77
|
+
_this.allowUndefinedConditions = options.allowUndefinedConditions || false;
|
|
78
|
+
_this.replaceFactsInEventParams = options.replaceFactsInEventParams || false;
|
|
79
|
+
_this.pathResolver = options.pathResolver;
|
|
80
|
+
_this.operators = new _operatorMap2.default();
|
|
81
|
+
_this.facts = new Map();
|
|
82
|
+
_this.conditions = new Map();
|
|
83
|
+
_this.status = READY;
|
|
84
|
+
rules.map(function (r) {
|
|
85
|
+
return _this.addRule(r);
|
|
86
|
+
});
|
|
87
|
+
_engineDefaultOperators2.default.map(function (o) {
|
|
88
|
+
return _this.addOperator(o);
|
|
89
|
+
});
|
|
90
|
+
_engineDefaultOperatorDecorators2.default.map(function (d) {
|
|
91
|
+
return _this.addOperatorDecorator(d);
|
|
92
|
+
});
|
|
93
|
+
return _this;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Add a rule definition to the engine
|
|
98
|
+
* @param {object|Rule} properties - rule definition. can be JSON representation, or instance of Rule
|
|
99
|
+
* @param {integer} properties.priority (>1) - higher runs sooner.
|
|
100
|
+
* @param {Object} properties.event - event to fire when rule evaluates as successful
|
|
101
|
+
* @param {string} properties.event.type - name of event to emit
|
|
102
|
+
* @param {string} properties.event.params - parameters to pass to the event listener
|
|
103
|
+
* @param {Object} properties.conditions - conditions to evaluate when processing this rule
|
|
104
|
+
*/
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
_createClass(Engine, [{
|
|
108
|
+
key: 'addRule',
|
|
109
|
+
value: function addRule(properties) {
|
|
110
|
+
if (!properties) throw new Error('Engine: addRule() requires options');
|
|
111
|
+
|
|
112
|
+
var rule = void 0;
|
|
113
|
+
if (properties instanceof _rule2.default) {
|
|
114
|
+
rule = properties;
|
|
115
|
+
} else {
|
|
116
|
+
if (!Object.prototype.hasOwnProperty.call(properties, 'event')) throw new Error('Engine: addRule() argument requires "event" property');
|
|
117
|
+
if (!Object.prototype.hasOwnProperty.call(properties, 'conditions')) throw new Error('Engine: addRule() argument requires "conditions" property');
|
|
118
|
+
rule = new _rule2.default(properties);
|
|
119
|
+
}
|
|
120
|
+
rule.setEngine(this);
|
|
121
|
+
this.rules.push(rule);
|
|
122
|
+
this.prioritizedRules = null;
|
|
123
|
+
return this;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* update a rule in the engine
|
|
128
|
+
* @param {object|Rule} rule - rule definition. Must be a instance of Rule
|
|
129
|
+
*/
|
|
130
|
+
|
|
131
|
+
}, {
|
|
132
|
+
key: 'updateRule',
|
|
133
|
+
value: function updateRule(rule) {
|
|
134
|
+
var ruleIndex = this.rules.findIndex(function (ruleInEngine) {
|
|
135
|
+
return ruleInEngine.name === rule.name;
|
|
136
|
+
});
|
|
137
|
+
if (ruleIndex > -1) {
|
|
138
|
+
this.rules.splice(ruleIndex, 1);
|
|
139
|
+
this.addRule(rule);
|
|
140
|
+
this.prioritizedRules = null;
|
|
141
|
+
} else {
|
|
142
|
+
throw new Error('Engine: updateRule() rule not found');
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Remove a rule from the engine
|
|
148
|
+
* @param {object|Rule|string} rule - rule definition. Must be a instance of Rule
|
|
149
|
+
*/
|
|
150
|
+
|
|
151
|
+
}, {
|
|
152
|
+
key: 'removeRule',
|
|
153
|
+
value: function removeRule(rule) {
|
|
154
|
+
var ruleRemoved = false;
|
|
155
|
+
if (!(rule instanceof _rule2.default)) {
|
|
156
|
+
var filteredRules = this.rules.filter(function (ruleInEngine) {
|
|
157
|
+
return ruleInEngine.name !== rule;
|
|
158
|
+
});
|
|
159
|
+
ruleRemoved = filteredRules.length !== this.rules.length;
|
|
160
|
+
this.rules = filteredRules;
|
|
161
|
+
} else {
|
|
162
|
+
var index = this.rules.indexOf(rule);
|
|
163
|
+
if (index > -1) {
|
|
164
|
+
ruleRemoved = Boolean(this.rules.splice(index, 1).length);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (ruleRemoved) {
|
|
168
|
+
this.prioritizedRules = null;
|
|
169
|
+
}
|
|
170
|
+
return ruleRemoved;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* sets a condition that can be referenced by the given name.
|
|
175
|
+
* If a condition with the given name has already been set this will replace it.
|
|
176
|
+
* @param {string} name - the name of the condition to be referenced by rules.
|
|
177
|
+
* @param {object} conditions - the conditions to use when the condition is referenced.
|
|
178
|
+
*/
|
|
179
|
+
|
|
180
|
+
}, {
|
|
181
|
+
key: 'setCondition',
|
|
182
|
+
value: function setCondition(name, conditions) {
|
|
183
|
+
if (!name) throw new Error('Engine: setCondition() requires name');
|
|
184
|
+
if (!conditions) throw new Error('Engine: setCondition() requires conditions');
|
|
185
|
+
if (!Object.prototype.hasOwnProperty.call(conditions, 'all') && !Object.prototype.hasOwnProperty.call(conditions, 'any') && !Object.prototype.hasOwnProperty.call(conditions, 'not') && !Object.prototype.hasOwnProperty.call(conditions, 'condition')) {
|
|
186
|
+
throw new Error('"conditions" root must contain a single instance of "all", "any", "not", or "condition"');
|
|
187
|
+
}
|
|
188
|
+
this.conditions.set(name, new _condition2.default(conditions));
|
|
189
|
+
return this;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Removes a condition that has previously been added to this engine
|
|
194
|
+
* @param {string} name - the name of the condition to remove.
|
|
195
|
+
* @returns true if the condition existed, otherwise false
|
|
196
|
+
*/
|
|
197
|
+
|
|
198
|
+
}, {
|
|
199
|
+
key: 'removeCondition',
|
|
200
|
+
value: function removeCondition(name) {
|
|
201
|
+
return this.conditions.delete(name);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Add a custom operator definition
|
|
206
|
+
* @param {string} operatorOrName - operator identifier within the condition; i.e. instead of 'equals', 'greaterThan', etc
|
|
207
|
+
* @param {function(factValue, jsonValue)} callback - the method to execute when the operator is encountered.
|
|
208
|
+
*/
|
|
209
|
+
|
|
210
|
+
}, {
|
|
211
|
+
key: 'addOperator',
|
|
212
|
+
value: function addOperator(operatorOrName, cb) {
|
|
213
|
+
this.operators.addOperator(operatorOrName, cb);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Remove a custom operator definition
|
|
218
|
+
* @param {string} operatorOrName - operator identifier within the condition; i.e. instead of 'equals', 'greaterThan', etc
|
|
219
|
+
*/
|
|
220
|
+
|
|
221
|
+
}, {
|
|
222
|
+
key: 'removeOperator',
|
|
223
|
+
value: function removeOperator(operatorOrName) {
|
|
224
|
+
return this.operators.removeOperator(operatorOrName);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Add a custom operator decorator
|
|
229
|
+
* @param {string} decoratorOrName - decorator identifier within the condition; i.e. instead of 'someFact', 'everyValue', etc
|
|
230
|
+
* @param {function(factValue, jsonValue, next)} callback - the method to execute when the decorator is encountered.
|
|
231
|
+
*/
|
|
232
|
+
|
|
233
|
+
}, {
|
|
234
|
+
key: 'addOperatorDecorator',
|
|
235
|
+
value: function addOperatorDecorator(decoratorOrName, cb) {
|
|
236
|
+
this.operators.addOperatorDecorator(decoratorOrName, cb);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Remove a custom operator decorator
|
|
241
|
+
* @param {string} decoratorOrName - decorator identifier within the condition; i.e. instead of 'someFact', 'everyValue', etc
|
|
242
|
+
*/
|
|
243
|
+
|
|
244
|
+
}, {
|
|
245
|
+
key: 'removeOperatorDecorator',
|
|
246
|
+
value: function removeOperatorDecorator(decoratorOrName) {
|
|
247
|
+
return this.operators.removeOperatorDecorator(decoratorOrName);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Add a fact definition to the engine. Facts are called by rules as they are evaluated.
|
|
252
|
+
* @param {object|Fact} id - fact identifier or instance of Fact
|
|
253
|
+
* @param {function} definitionFunc - function to be called when computing the fact value for a given rule
|
|
254
|
+
* @param {Object} options - options to initialize the fact with. used when "id" is not a Fact instance
|
|
255
|
+
*/
|
|
256
|
+
|
|
257
|
+
}, {
|
|
258
|
+
key: 'addFact',
|
|
259
|
+
value: function addFact(id, valueOrMethod, options) {
|
|
260
|
+
var factId = id;
|
|
261
|
+
var fact = void 0;
|
|
262
|
+
if (id instanceof _fact2.default) {
|
|
263
|
+
factId = id.id;
|
|
264
|
+
fact = id;
|
|
265
|
+
} else {
|
|
266
|
+
fact = new _fact2.default(id, valueOrMethod, options);
|
|
267
|
+
}
|
|
268
|
+
(0, _debug2.default)('engine::addFact', { id: factId });
|
|
269
|
+
this.facts.set(factId, fact);
|
|
270
|
+
return this;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Remove a fact definition to the engine. Facts are called by rules as they are evaluated.
|
|
275
|
+
* @param {object|Fact} id - fact identifier or instance of Fact
|
|
276
|
+
*/
|
|
277
|
+
|
|
278
|
+
}, {
|
|
279
|
+
key: 'removeFact',
|
|
280
|
+
value: function removeFact(factOrId) {
|
|
281
|
+
var factId = void 0;
|
|
282
|
+
if (!(factOrId instanceof _fact2.default)) {
|
|
283
|
+
factId = factOrId;
|
|
284
|
+
} else {
|
|
285
|
+
factId = factOrId.id;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return this.facts.delete(factId);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Iterates over the engine rules, organizing them by highest -> lowest priority
|
|
293
|
+
* @return {Rule[][]} two dimensional array of Rules.
|
|
294
|
+
* Each outer array element represents a single priority(integer). Inner array is
|
|
295
|
+
* all rules with that priority.
|
|
296
|
+
*/
|
|
297
|
+
|
|
298
|
+
}, {
|
|
299
|
+
key: 'prioritizeRules',
|
|
300
|
+
value: function prioritizeRules() {
|
|
301
|
+
if (!this.prioritizedRules) {
|
|
302
|
+
var ruleSets = this.rules.reduce(function (sets, rule) {
|
|
303
|
+
var priority = rule.priority;
|
|
304
|
+
if (!sets[priority]) sets[priority] = [];
|
|
305
|
+
sets[priority].push(rule);
|
|
306
|
+
return sets;
|
|
307
|
+
}, {});
|
|
308
|
+
this.prioritizedRules = Object.keys(ruleSets).sort(function (a, b) {
|
|
309
|
+
return Number(a) > Number(b) ? -1 : 1; // order highest priority -> lowest
|
|
310
|
+
}).map(function (priority) {
|
|
311
|
+
return ruleSets[priority];
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
return this.prioritizedRules;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Stops the rules engine from running the next priority set of Rules. All remaining rules will be resolved as undefined,
|
|
319
|
+
* and no further events emitted. Since rules of the same priority are evaluated in parallel(not series), other rules of
|
|
320
|
+
* the same priority may still emit events, even though the engine is in a "finished" state.
|
|
321
|
+
* @return {Engine}
|
|
322
|
+
*/
|
|
323
|
+
|
|
324
|
+
}, {
|
|
325
|
+
key: 'stop',
|
|
326
|
+
value: function stop() {
|
|
327
|
+
this.status = FINISHED;
|
|
328
|
+
return this;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Returns a fact by fact-id
|
|
333
|
+
* @param {string} factId - fact identifier
|
|
334
|
+
* @return {Fact} fact instance, or undefined if no such fact exists
|
|
335
|
+
*/
|
|
336
|
+
|
|
337
|
+
}, {
|
|
338
|
+
key: 'getFact',
|
|
339
|
+
value: function getFact(factId) {
|
|
340
|
+
return this.facts.get(factId);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Runs an array of rules
|
|
345
|
+
* @param {Rule[]} array of rules to be evaluated
|
|
346
|
+
* @return {Promise} resolves when all rules in the array have been evaluated
|
|
347
|
+
*/
|
|
348
|
+
|
|
349
|
+
}, {
|
|
350
|
+
key: 'evaluateRules',
|
|
351
|
+
value: function evaluateRules(ruleArray, almanac) {
|
|
352
|
+
var _this2 = this;
|
|
353
|
+
|
|
354
|
+
return Promise.all(ruleArray.map(function (rule) {
|
|
355
|
+
if (_this2.status !== RUNNING) {
|
|
356
|
+
(0, _debug2.default)('engine::run, skipping remaining rules', { status: _this2.status });
|
|
357
|
+
return Promise.resolve();
|
|
358
|
+
}
|
|
359
|
+
return rule.evaluate(almanac).then(function (ruleResult) {
|
|
360
|
+
(0, _debug2.default)('engine::run', { ruleResult: ruleResult.result });
|
|
361
|
+
almanac.addResult(ruleResult);
|
|
362
|
+
if (ruleResult.result) {
|
|
363
|
+
almanac.addEvent(ruleResult.event, 'success');
|
|
364
|
+
return _this2.emitAsync('success', ruleResult.event, almanac, ruleResult).then(function () {
|
|
365
|
+
return _this2.emitAsync(ruleResult.event.type, ruleResult.event.params, almanac, ruleResult);
|
|
366
|
+
});
|
|
367
|
+
} else {
|
|
368
|
+
almanac.addEvent(ruleResult.event, 'failure');
|
|
369
|
+
return _this2.emitAsync('failure', ruleResult.event, almanac, ruleResult);
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
}));
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Runs the rules engine
|
|
377
|
+
* @param {Object} runtimeFacts - fact values known at runtime
|
|
378
|
+
* @param {Object} runOptions - run options
|
|
379
|
+
* @return {Promise} resolves when the engine has completed running
|
|
380
|
+
*/
|
|
381
|
+
|
|
382
|
+
}, {
|
|
383
|
+
key: 'run',
|
|
384
|
+
value: function run() {
|
|
385
|
+
var _this3 = this;
|
|
386
|
+
|
|
387
|
+
var runtimeFacts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
388
|
+
var runOptions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
389
|
+
|
|
390
|
+
(0, _debug2.default)('engine::run started');
|
|
391
|
+
this.status = RUNNING;
|
|
392
|
+
|
|
393
|
+
var almanac = runOptions.almanac || new _almanac2.default({
|
|
394
|
+
allowUndefinedFacts: this.allowUndefinedFacts,
|
|
395
|
+
pathResolver: this.pathResolver
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
this.facts.forEach(function (fact) {
|
|
399
|
+
almanac.addFact(fact);
|
|
400
|
+
});
|
|
401
|
+
for (var factId in runtimeFacts) {
|
|
402
|
+
var fact = void 0;
|
|
403
|
+
if (runtimeFacts[factId] instanceof _fact2.default) {
|
|
404
|
+
fact = runtimeFacts[factId];
|
|
405
|
+
} else {
|
|
406
|
+
fact = new _fact2.default(factId, runtimeFacts[factId]);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
almanac.addFact(fact);
|
|
410
|
+
(0, _debug2.default)('engine::run initialized runtime fact', { id: fact.id, value: fact.value, type: _typeof(fact.value) });
|
|
411
|
+
}
|
|
412
|
+
var orderedSets = this.prioritizeRules();
|
|
413
|
+
var cursor = Promise.resolve();
|
|
414
|
+
// for each rule set, evaluate in parallel,
|
|
415
|
+
// before proceeding to the next priority set.
|
|
416
|
+
return new Promise(function (resolve, reject) {
|
|
417
|
+
orderedSets.map(function (set) {
|
|
418
|
+
cursor = cursor.then(function () {
|
|
419
|
+
return _this3.evaluateRules(set, almanac);
|
|
420
|
+
}).catch(reject);
|
|
421
|
+
return cursor;
|
|
422
|
+
});
|
|
423
|
+
cursor.then(function () {
|
|
424
|
+
_this3.status = FINISHED;
|
|
425
|
+
(0, _debug2.default)('engine::run completed');
|
|
426
|
+
var ruleResults = almanac.getResults();
|
|
427
|
+
|
|
428
|
+
var _ruleResults$reduce = ruleResults.reduce(function (hash, ruleResult) {
|
|
429
|
+
var group = ruleResult.result ? 'results' : 'failureResults';
|
|
430
|
+
hash[group].push(ruleResult);
|
|
431
|
+
return hash;
|
|
432
|
+
}, { results: [], failureResults: [] }),
|
|
433
|
+
results = _ruleResults$reduce.results,
|
|
434
|
+
failureResults = _ruleResults$reduce.failureResults;
|
|
435
|
+
|
|
436
|
+
resolve({
|
|
437
|
+
almanac: almanac,
|
|
438
|
+
results: results,
|
|
439
|
+
failureResults: failureResults,
|
|
440
|
+
events: almanac.getEvents('success'),
|
|
441
|
+
failureEvents: almanac.getEvents('failure')
|
|
442
|
+
});
|
|
443
|
+
}).catch(reject);
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
}]);
|
|
447
|
+
|
|
448
|
+
return Engine;
|
|
449
|
+
}(_eventemitter2.default);
|
|
450
|
+
|
|
451
|
+
exports.default = Engine;
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
8
|
+
|
|
9
|
+
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; }
|
|
10
|
+
|
|
11
|
+
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; }
|
|
12
|
+
|
|
13
|
+
var UndefinedFactError = exports.UndefinedFactError = function (_Error) {
|
|
14
|
+
_inherits(UndefinedFactError, _Error);
|
|
15
|
+
|
|
16
|
+
function UndefinedFactError() {
|
|
17
|
+
var _ref;
|
|
18
|
+
|
|
19
|
+
_classCallCheck(this, UndefinedFactError);
|
|
20
|
+
|
|
21
|
+
for (var _len = arguments.length, props = Array(_len), _key = 0; _key < _len; _key++) {
|
|
22
|
+
props[_key] = arguments[_key];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
var _this = _possibleConstructorReturn(this, (_ref = UndefinedFactError.__proto__ || Object.getPrototypeOf(UndefinedFactError)).call.apply(_ref, [this].concat(props)));
|
|
26
|
+
|
|
27
|
+
_this.code = 'UNDEFINED_FACT';
|
|
28
|
+
return _this;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return UndefinedFactError;
|
|
32
|
+
}(Error);
|
package/dist/fact.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
|
|
8
|
+
|
|
9
|
+
var _hashIt = require('hash-it');
|
|
10
|
+
|
|
11
|
+
var _hashIt2 = _interopRequireDefault(_hashIt);
|
|
12
|
+
|
|
13
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
14
|
+
|
|
15
|
+
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
16
|
+
|
|
17
|
+
var Fact = function () {
|
|
18
|
+
/**
|
|
19
|
+
* Returns a new fact instance
|
|
20
|
+
* @param {string} id - fact unique identifer
|
|
21
|
+
* @param {object} options
|
|
22
|
+
* @param {boolean} options.cache - whether to cache the fact's value for future rules
|
|
23
|
+
* @param {primitive|function} valueOrMethod - constant primitive, or method to call when computing the fact's value
|
|
24
|
+
* @return {Fact}
|
|
25
|
+
*/
|
|
26
|
+
function Fact(id, valueOrMethod, options) {
|
|
27
|
+
_classCallCheck(this, Fact);
|
|
28
|
+
|
|
29
|
+
this.id = id;
|
|
30
|
+
var defaultOptions = { cache: true };
|
|
31
|
+
if (typeof options === 'undefined') {
|
|
32
|
+
options = defaultOptions;
|
|
33
|
+
}
|
|
34
|
+
if (typeof valueOrMethod !== 'function') {
|
|
35
|
+
this.value = valueOrMethod;
|
|
36
|
+
this.type = this.constructor.CONSTANT;
|
|
37
|
+
} else {
|
|
38
|
+
this.calculationMethod = valueOrMethod;
|
|
39
|
+
this.type = this.constructor.DYNAMIC;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!this.id) throw new Error('factId required');
|
|
43
|
+
|
|
44
|
+
this.priority = parseInt(options.priority || 1, 10);
|
|
45
|
+
this.options = Object.assign({}, defaultOptions, options);
|
|
46
|
+
this.cacheKeyMethod = this.defaultCacheKeys;
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
_createClass(Fact, [{
|
|
51
|
+
key: 'isConstant',
|
|
52
|
+
value: function isConstant() {
|
|
53
|
+
return this.type === this.constructor.CONSTANT;
|
|
54
|
+
}
|
|
55
|
+
}, {
|
|
56
|
+
key: 'isDynamic',
|
|
57
|
+
value: function isDynamic() {
|
|
58
|
+
return this.type === this.constructor.DYNAMIC;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Return the fact value, based on provided parameters
|
|
63
|
+
* @param {object} params
|
|
64
|
+
* @param {Almanac} almanac
|
|
65
|
+
* @return {any} calculation method results
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
}, {
|
|
69
|
+
key: 'calculate',
|
|
70
|
+
value: function calculate(params, almanac) {
|
|
71
|
+
// if constant fact w/set value, return immediately
|
|
72
|
+
if (Object.prototype.hasOwnProperty.call(this, 'value')) {
|
|
73
|
+
return this.value;
|
|
74
|
+
}
|
|
75
|
+
return this.calculationMethod(params, almanac);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Return a cache key (MD5 string) based on parameters
|
|
80
|
+
* @param {object} obj - properties to generate a hash key from
|
|
81
|
+
* @return {string} MD5 string based on the hash'd object
|
|
82
|
+
*/
|
|
83
|
+
|
|
84
|
+
}, {
|
|
85
|
+
key: 'defaultCacheKeys',
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Default properties to use when caching a fact
|
|
90
|
+
* Assumes every fact is a pure function, whose computed value will only
|
|
91
|
+
* change when input params are modified
|
|
92
|
+
* @param {string} id - fact unique identifer
|
|
93
|
+
* @param {object} params - parameters passed to fact calcution method
|
|
94
|
+
* @return {object} id + params
|
|
95
|
+
*/
|
|
96
|
+
value: function defaultCacheKeys(id, params) {
|
|
97
|
+
return { params: params, id: id };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Generates the fact's cache key(MD5 string)
|
|
102
|
+
* Returns nothing if the fact's caching has been disabled
|
|
103
|
+
* @param {object} params - parameters that would be passed to the computation method
|
|
104
|
+
* @return {string} cache key
|
|
105
|
+
*/
|
|
106
|
+
|
|
107
|
+
}, {
|
|
108
|
+
key: 'getCacheKey',
|
|
109
|
+
value: function getCacheKey(params) {
|
|
110
|
+
if (this.options.cache === true) {
|
|
111
|
+
var cacheProperties = this.cacheKeyMethod(this.id, params);
|
|
112
|
+
var _hash = Fact.hashFromObject(cacheProperties);
|
|
113
|
+
return _hash;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}], [{
|
|
117
|
+
key: 'hashFromObject',
|
|
118
|
+
value: function hashFromObject(obj) {
|
|
119
|
+
return (0, _hashIt2.default)(obj);
|
|
120
|
+
}
|
|
121
|
+
}]);
|
|
122
|
+
|
|
123
|
+
return Fact;
|
|
124
|
+
}();
|
|
125
|
+
|
|
126
|
+
Fact.CONSTANT = 'CONSTANT';
|
|
127
|
+
Fact.DYNAMIC = 'DYNAMIC';
|
|
128
|
+
|
|
129
|
+
exports.default = Fact;
|
package/dist/index.js
ADDED