UNPKG

12.3 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.FINISHED = exports.RUNNING = exports.READY = undefined;
7
8var _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; }; }();
9
10var _fact = require('./fact');
11
12var _fact2 = _interopRequireDefault(_fact);
13
14var _rule = require('./rule');
15
16var _rule2 = _interopRequireDefault(_rule);
17
18var _operator = require('./operator');
19
20var _operator2 = _interopRequireDefault(_operator);
21
22var _almanac = require('./almanac');
23
24var _almanac2 = _interopRequireDefault(_almanac);
25
26var _events = require('events');
27
28var _engineFacts = require('./engine-facts');
29
30var _engineDefaultOperators = require('./engine-default-operators');
31
32var _engineDefaultOperators2 = _interopRequireDefault(_engineDefaultOperators);
33
34var _debug = require('./debug');
35
36var _debug2 = _interopRequireDefault(_debug);
37
38function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
39
40function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
41
42function _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; }
43
44function _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; }
45
46var READY = exports.READY = 'READY';
47var RUNNING = exports.RUNNING = 'RUNNING';
48var FINISHED = exports.FINISHED = 'FINISHED';
49
50var Engine = function (_EventEmitter) {
51 _inherits(Engine, _EventEmitter);
52
53 /**
54 * Returns a new Engine instance
55 * @param {Rule[]} rules - array of rules to initialize with
56 */
57 function Engine() {
58 var rules = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
59 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
60
61 _classCallCheck(this, Engine);
62
63 var _this = _possibleConstructorReturn(this, (Engine.__proto__ || Object.getPrototypeOf(Engine)).call(this));
64
65 _this.rules = [];
66 _this.allowUndefinedFacts = options.allowUndefinedFacts || false;
67 _this.operators = new Map();
68 _this.facts = new Map();
69 _this.status = READY;
70 rules.map(function (r) {
71 return _this.addRule(r);
72 });
73 _engineDefaultOperators2.default.map(function (o) {
74 return _this.addOperator(o);
75 });
76 return _this;
77 }
78
79 /**
80 * Add a rule definition to the engine
81 * @param {object|Rule} properties - rule definition. can be JSON representation, or instance of Rule
82 * @param {integer} properties.priority (>1) - higher runs sooner.
83 * @param {Object} properties.event - event to fire when rule evaluates as successful
84 * @param {string} properties.event.type - name of event to emit
85 * @param {string} properties.event.params - parameters to pass to the event listener
86 * @param {Object} properties.conditions - conditions to evaluate when processing this rule
87 */
88
89
90 _createClass(Engine, [{
91 key: 'addRule',
92 value: function addRule(properties) {
93 if (!properties) throw new Error('Engine: addRule() requires options');
94 if (!Object.prototype.hasOwnProperty.call(properties, 'conditions')) throw new Error('Engine: addRule() argument requires "conditions" property');
95 if (!Object.prototype.hasOwnProperty.call(properties, 'event')) throw new Error('Engine: addRule() argument requires "event" property');
96
97 var rule = void 0;
98 if (properties instanceof _rule2.default) {
99 rule = properties;
100 } else {
101 rule = new _rule2.default(properties);
102 }
103 rule.setEngine(this);
104
105 this.rules.push(rule);
106 this.prioritizedRules = null;
107 return this;
108 }
109
110 /**
111 * Remove a rule from the engine
112 * @param {object|Rule} rule - rule definition. Must be a instance of Rule
113 */
114
115 }, {
116 key: 'removeRule',
117 value: function removeRule(rule) {
118 if (rule instanceof _rule2.default === false) throw new Error('Engine: removeRule() rule must be a instance of Rule');
119
120 var index = this.rules.indexOf(rule);
121 if (index === -1) return false;
122 this.prioritizedRules = null;
123 return Boolean(this.rules.splice(index, 1).length);
124 }
125
126 /**
127 * Add a custom operator definition
128 * @param {string} operatorOrName - operator identifier within the condition; i.e. instead of 'equals', 'greaterThan', etc
129 * @param {function(factValue, jsonValue)} callback - the method to execute when the operator is encountered.
130 */
131
132 }, {
133 key: 'addOperator',
134 value: function addOperator(operatorOrName, cb) {
135 var operator = void 0;
136 if (operatorOrName instanceof _operator2.default) {
137 operator = operatorOrName;
138 } else {
139 operator = new _operator2.default(operatorOrName, cb);
140 }
141 (0, _debug2.default)('engine::addOperator name:' + operator.name);
142 this.operators.set(operator.name, operator);
143 }
144
145 /**
146 * Remove a custom operator definition
147 * @param {string} operatorOrName - operator identifier within the condition; i.e. instead of 'equals', 'greaterThan', etc
148 * @param {function(factValue, jsonValue)} callback - the method to execute when the operator is encountered.
149 */
150
151 }, {
152 key: 'removeOperator',
153 value: function removeOperator(operatorOrName) {
154 var operatorName = void 0;
155 if (operatorOrName instanceof _operator2.default) {
156 operatorName = operatorOrName.name;
157 } else {
158 operatorName = operatorOrName;
159 }
160
161 return this.operators.delete(operatorName);
162 }
163
164 /**
165 * Add a fact definition to the engine. Facts are called by rules as they are evaluated.
166 * @param {object|Fact} id - fact identifier or instance of Fact
167 * @param {function} definitionFunc - function to be called when computing the fact value for a given rule
168 * @param {Object} options - options to initialize the fact with. used when "id" is not a Fact instance
169 */
170
171 }, {
172 key: 'addFact',
173 value: function addFact(id, valueOrMethod, options) {
174 var factId = id;
175 var fact = void 0;
176 if (id instanceof _fact2.default) {
177 factId = id.id;
178 fact = id;
179 } else {
180 fact = new _fact2.default(id, valueOrMethod, options);
181 }
182 (0, _debug2.default)('engine::addFact id:' + factId);
183 this.facts.set(factId, fact);
184 return this;
185 }
186
187 /**
188 * Add a fact definition to the engine. Facts are called by rules as they are evaluated.
189 * @param {object|Fact} id - fact identifier or instance of Fact
190 */
191
192 }, {
193 key: 'removeFact',
194 value: function removeFact(factOrId) {
195 var factId = void 0;
196 if (!(factOrId instanceof _fact2.default)) {
197 factId = factOrId;
198 } else {
199 factId = factOrId.id;
200 }
201
202 return this.facts.delete(factId);
203 }
204
205 /**
206 * Iterates over the engine rules, organizing them by highest -> lowest priority
207 * @return {Rule[][]} two dimensional array of Rules.
208 * Each outer array element represents a single priority(integer). Inner array is
209 * all rules with that priority.
210 */
211
212 }, {
213 key: 'prioritizeRules',
214 value: function prioritizeRules() {
215 if (!this.prioritizedRules) {
216 var ruleSets = this.rules.reduce(function (sets, rule) {
217 var priority = rule.priority;
218 if (!sets[priority]) sets[priority] = [];
219 sets[priority].push(rule);
220 return sets;
221 }, {});
222 this.prioritizedRules = Object.keys(ruleSets).sort(function (a, b) {
223 return Number(a) > Number(b) ? -1 : 1; // order highest priority -> lowest
224 }).map(function (priority) {
225 return ruleSets[priority];
226 });
227 }
228 return this.prioritizedRules;
229 }
230
231 /**
232 * Stops the rules engine from running the next priority set of Rules. All remaining rules will be resolved as undefined,
233 * and no further events emitted. Since rules of the same priority are evaluated in parallel(not series), other rules of
234 * the same priority may still emit events, even though the engine is in a "finished" state.
235 * @return {Engine}
236 */
237
238 }, {
239 key: 'stop',
240 value: function stop() {
241 this.status = FINISHED;
242 return this;
243 }
244
245 /**
246 * Returns a fact by fact-id
247 * @param {string} factId - fact identifier
248 * @return {Fact} fact instance, or undefined if no such fact exists
249 */
250
251 }, {
252 key: 'getFact',
253 value: function getFact(factId) {
254 return this.facts.get(factId);
255 }
256
257 /**
258 * Runs an array of rules
259 * @param {Rule[]} array of rules to be evaluated
260 * @return {Promise} resolves when all rules in the array have been evaluated
261 */
262
263 }, {
264 key: 'evaluateRules',
265 value: function evaluateRules(ruleArray, almanac) {
266 var _this2 = this;
267
268 return Promise.all(ruleArray.map(function (rule) {
269 if (_this2.status !== RUNNING) {
270 (0, _debug2.default)('engine::run status:' + _this2.status + '; skipping remaining rules');
271 return;
272 }
273 return rule.evaluate(almanac).then(function (ruleResult) {
274 (0, _debug2.default)('engine::run ruleResult:' + ruleResult.result);
275 if (ruleResult.result) {
276 _this2.emit('success', rule.event, almanac, ruleResult);
277 _this2.emit(rule.event.type, rule.event.params, almanac, ruleResult);
278 almanac.factValue('success-events', { event: rule.event });
279 } else {
280 _this2.emit('failure', rule.event, almanac, ruleResult);
281 }
282 });
283 }));
284 }
285
286 /**
287 * Runs the rules engine
288 * @param {Object} runtimeFacts - fact values known at runtime
289 * @param {Object} runOptions - run options
290 * @return {Promise} resolves when the engine has completed running
291 */
292
293 }, {
294 key: 'run',
295 value: function run() {
296 var _this3 = this;
297
298 var runtimeFacts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
299
300 (0, _debug2.default)('engine::run started');
301 (0, _debug2.default)('engine::run runtimeFacts:', runtimeFacts);
302 runtimeFacts['success-events'] = new _fact2.default('success-events', (0, _engineFacts.SuccessEventFact)(), { cache: false });
303 this.status = RUNNING;
304 var almanac = new _almanac2.default(this.facts, runtimeFacts, { allowUndefinedFacts: this.allowUndefinedFacts });
305 var orderedSets = this.prioritizeRules();
306 var cursor = Promise.resolve();
307 // for each rule set, evaluate in parallel,
308 // before proceeding to the next priority set.
309 return new Promise(function (resolve, reject) {
310 orderedSets.map(function (set) {
311 cursor = cursor.then(function () {
312 return _this3.evaluateRules(set, almanac);
313 }).catch(reject);
314 return cursor;
315 });
316 cursor.then(function () {
317 _this3.status = FINISHED;
318 (0, _debug2.default)('engine::run completed');
319 return almanac.factValue('success-events');
320 }).then(function (events) {
321 resolve({
322 events: events,
323 almanac: almanac
324 });
325 }).catch(reject);
326 });
327 }
328 }]);
329
330 return Engine;
331}(_events.EventEmitter);
332
333exports.default = Engine;
\No newline at end of file