diff --git a/.gitignore b/.gitignore index 8c0fa8a..5277bf8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ npm-debug.log .vscode .idea -.DS_Store \ No newline at end of file +.DS_Store +dist \ No newline at end of file diff --git a/dist/almanac.js b/dist/almanac.js deleted file mode 100644 index 0318bbf..0000000 --- a/dist/almanac.js +++ /dev/null @@ -1,198 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -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; }; - -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; }; }(); - -var _fact = require('./fact'); - -var _fact2 = _interopRequireDefault(_fact); - -var _errors = require('./errors'); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -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) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -var debug = require('debug')('json-rules-engine'); -var verbose = require('debug')('json-rules-engine-verbose'); -var selectn = require('selectn'); -var isObjectLike = require('lodash.isobjectlike'); -var warn = require('debug')('json-rules-engine:warn'); - -/** - * Fact results lookup - * Triggers fact computations and saves the results - * A new almanac is used for every engine run() - */ - -var Almanac = function () { - function Almanac(factMap) { - var runtimeFacts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - - _classCallCheck(this, Almanac); - - this.factMap = new Map(factMap); - this.factResultsCache = new Map(); // { cacheKey: Promise } - - for (var factId in runtimeFacts) { - var fact = void 0; - if (runtimeFacts[factId] instanceof _fact2.default) { - fact = runtimeFacts[factId]; - } else { - fact = new _fact2.default(factId, runtimeFacts[factId]); - } - - this._addConstantFact(fact); - debug('almanac::constructor initialized runtime fact:' + fact.id + ' with ' + fact.value + '<' + _typeof(fact.value) + '>'); - } - } - - /** - * Retrieve fact by id, raising an exception if it DNE - * @param {String} factId - * @return {Fact} - */ - - - _createClass(Almanac, [{ - key: '_getFact', - value: function _getFact(factId) { - var fact = this.factMap.get(factId); - if (fact === undefined) { - throw new _errors.UndefinedFactError('Undefined fact: ' + factId); - } - return fact; - } - - /** - * Registers fact with the almanac - * @param {[type]} fact [description] - */ - - }, { - key: '_addConstantFact', - value: function _addConstantFact(fact) { - this.factMap.set(fact.id, fact); - this._setFactValue(fact, {}, fact.value); - } - - /** - * Sets the computed value of a fact - * @param {Fact} fact - * @param {Object} params - values for differentiating this fact value from others, used for cache key - * @param {Mixed} value - computed value - */ - - }, { - key: '_setFactValue', - value: function _setFactValue(fact, params, value) { - var cacheKey = fact.getCacheKey(params); - var factValue = Promise.resolve(value); - factValue.then(function (val) { - return debug('almanac::factValue fact:' + fact.id + ' calculated as: ' + JSON.stringify(val) + '<' + (typeof val === 'undefined' ? 'undefined' : _typeof(val)) + '>'); - }); - if (cacheKey) { - this.factResultsCache.set(cacheKey, factValue); - } - return factValue; - } - - /** - * Adds a constant fact during runtime. Can be used mid-run() to add additional information - * @param {String} fact - fact identifier - * @param {Mixed} value - constant value of the fact - */ - - }, { - key: 'addRuntimeFact', - value: function addRuntimeFact(factId, value) { - var fact = new _fact2.default(factId, value); - return this._addConstantFact(fact); - } - - /** - * Returns the value of a fact, based on the given parameters. Utilizes the 'almanac' maintained - * by the engine, which cache's fact computations based on parameters provided - * @param {string} factId - fact identifier - * @param {Object} params - parameters to feed into the fact. By default, these will also be used to compute the cache key - * @param {String} path - object - * @return {Promise} a promise which will resolve with the fact computation. - */ - - }, { - key: 'factValue', - value: function () { - var _ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee(factId) { - var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - var path = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ''; - var factValue, fact, cacheKey, cacheVal; - return regeneratorRuntime.wrap(function _callee$(_context) { - while (1) { - switch (_context.prev = _context.next) { - case 0: - factValue = void 0; - fact = this._getFact(factId); - cacheKey = fact.getCacheKey(params); - cacheVal = cacheKey && this.factResultsCache.get(cacheKey); - - if (!cacheVal) { - _context.next = 11; - break; - } - - _context.next = 7; - return cacheVal; - - case 7: - factValue = _context.sent; - - debug('almanac::factValue cache hit for fact:' + factId + ' value: ' + JSON.stringify(factValue) + '<' + (typeof factValue === 'undefined' ? 'undefined' : _typeof(factValue)) + '>'); - _context.next = 15; - break; - - case 11: - verbose('almanac::factValue cache miss for fact:' + factId + '; calculating'); - _context.next = 14; - return this._setFactValue(fact, params, fact.calculate(params, this)); - - case 14: - factValue = _context.sent; - - case 15: - if (path) { - if (isObjectLike(factValue)) { - factValue = selectn(path)(factValue); - debug('condition::evaluate extracting object property ' + path + ', received: ' + factValue); - } else { - warn('condition::evaluate could not compute object path(' + path + ') of non-object: ' + factValue + ' <' + (typeof factValue === 'undefined' ? 'undefined' : _typeof(factValue)) + '>; continuing with ' + factValue); - } - } - return _context.abrupt('return', factValue); - - case 17: - case 'end': - return _context.stop(); - } - } - }, _callee, this); - })); - - function factValue(_x2) { - return _ref.apply(this, arguments); - } - - return factValue; - }() - }]); - - return Almanac; -}(); - -exports.default = Almanac; \ No newline at end of file diff --git a/dist/condition.js b/dist/condition.js deleted file mode 100644 index 2cd7aab..0000000 --- a/dist/condition.js +++ /dev/null @@ -1,261 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -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; }; }(); - -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) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -var debug = require('debug')('json-rules-engine'); -var isObjectLike = require('lodash.isobjectlike'); - -var Condition = function () { - function Condition(properties) { - _classCallCheck(this, Condition); - - if (!properties) throw new Error('Condition: constructor options required'); - var booleanOperator = Condition.booleanOperator(properties); - Object.assign(this, properties); - if (booleanOperator) { - var subConditions = properties[booleanOperator]; - if (!(subConditions instanceof Array)) { - throw new Error('"' + booleanOperator + '" must be an array'); - } - this.operator = booleanOperator; - // boolean conditions always have a priority; default 1 - this.priority = parseInt(properties.priority, 10) || 1; - this[booleanOperator] = subConditions.map(function (c) { - return new Condition(c); - }); - } else { - if (!properties.hasOwnProperty('fact')) throw new Error('Condition: constructor "fact" property required'); - if (!properties.hasOwnProperty('operator')) throw new Error('Condition: constructor "operator" property required'); - if (!properties.hasOwnProperty('value')) throw new Error('Condition: constructor "value" property required'); - - // a non-boolean condition does not have a priority by default. this allows - // priority to be dictated by the fact definition - if (properties.hasOwnProperty('priority')) { - properties.priority = parseInt(properties.priority, 10); - } - } - } - - /** - * Converts the condition into a json-friendly structure - * @param {Boolean} stringify - whether to return as a json string - * @returns {string,object} json string or json-friendly object - */ - - - _createClass(Condition, [{ - key: 'toJSON', - value: function toJSON() { - var stringify = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; - - var props = {}; - if (this.priority) { - props.priority = this.priority; - } - var oper = Condition.booleanOperator(this); - if (oper) { - props[oper] = this[oper].map(function (c) { - return c.toJSON(stringify); - }); - } else { - props.operator = this.operator; - props.value = this.value; - props.fact = this.fact; - if (this.factResult !== undefined) { - props.factResult = this.factResult; - } - if (this.result !== undefined) { - props.result = this.result; - } - if (this.params) { - props.params = this.params; - } - if (this.path) { - props.path = this.path; - } - } - if (stringify) { - return JSON.stringify(props); - } - return props; - } - - /** - * Interprets .value as either a primitive, or if a fact, retrieves the fact value - */ - - }, { - key: '_getValue', - value: function () { - var _ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee(almanac) { - var value; - return regeneratorRuntime.wrap(function _callee$(_context) { - while (1) { - switch (_context.prev = _context.next) { - case 0: - value = this.value; - - if (!(isObjectLike(value) && value.hasOwnProperty('fact'))) { - _context.next = 5; - break; - } - - _context.next = 4; - return almanac.factValue(value.fact, value.params, value.path); - - case 4: - value = _context.sent; - - case 5: - return _context.abrupt('return', value); - - case 6: - case 'end': - return _context.stop(); - } - } - }, _callee, this); - })); - - function _getValue(_x2) { - return _ref.apply(this, arguments); - } - - return _getValue; - }() - - /** - * Takes the fact result and compares it to the condition 'value', using the operator - * LHS OPER RHS - * - * - * @param {Almanac} almanac - * @param {Map} operatorMap - map of available operators, keyed by operator name - * @returns {Boolean} - evaluation result - */ - - }, { - key: 'evaluate', - value: function () { - var _ref2 = _asyncToGenerator(regeneratorRuntime.mark(function _callee2(almanac, operatorMap) { - var op, rightHandSideValue, leftHandSideValue, result; - return regeneratorRuntime.wrap(function _callee2$(_context2) { - while (1) { - switch (_context2.prev = _context2.next) { - case 0: - if (almanac) { - _context2.next = 2; - break; - } - - throw new Error('almanac required'); - - case 2: - if (operatorMap) { - _context2.next = 4; - break; - } - - throw new Error('operatorMap required'); - - case 4: - if (!this.isBooleanOperator()) { - _context2.next = 6; - break; - } - - throw new Error('Cannot evaluate() a boolean condition'); - - case 6: - op = operatorMap.get(this.operator); - - if (op) { - _context2.next = 9; - break; - } - - throw new Error('Unknown operator: ' + this.operator); - - case 9: - _context2.next = 11; - return this._getValue(almanac); - - case 11: - rightHandSideValue = _context2.sent; - _context2.next = 14; - return almanac.factValue(this.fact, this.params, this.path); - - case 14: - leftHandSideValue = _context2.sent; - result = op.evaluate(leftHandSideValue, rightHandSideValue); - - debug('condition::evaluate <' + leftHandSideValue + ' ' + this.operator + ' ' + rightHandSideValue + '?> (' + result + ')'); - return _context2.abrupt('return', { result: result, leftHandSideValue: leftHandSideValue, rightHandSideValue: rightHandSideValue, operator: this.operator }); - - case 18: - case 'end': - return _context2.stop(); - } - } - }, _callee2, this); - })); - - function evaluate(_x3, _x4) { - return _ref2.apply(this, arguments); - } - - return evaluate; - }() - - /** - * Returns the boolean operator for the condition - * If the condition is not a boolean condition, the result will be 'undefined' - * @return {string 'all' or 'any'} - */ - - }, { - key: 'booleanOperator', - - - /** - * Returns the condition's boolean operator - * Instance version of Condition.isBooleanOperator - * @returns {string,undefined} - 'any', 'all', or undefined (if not a boolean condition) - */ - value: function booleanOperator() { - return Condition.booleanOperator(this); - } - - /** - * Whether the operator is boolean ('all', 'any') - * @returns {Boolean} - */ - - }, { - key: 'isBooleanOperator', - value: function isBooleanOperator() { - return Condition.booleanOperator(this) !== undefined; - } - }], [{ - key: 'booleanOperator', - value: function booleanOperator(condition) { - if (condition.hasOwnProperty('any')) { - return 'any'; - } else if (condition.hasOwnProperty('all')) { - return 'all'; - } - } - }]); - - return Condition; -}(); - -exports.default = Condition; \ No newline at end of file diff --git a/dist/engine-default-operators.js b/dist/engine-default-operators.js deleted file mode 100644 index 68449c2..0000000 --- a/dist/engine-default-operators.js +++ /dev/null @@ -1,50 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _operator = require('./operator'); - -var _operator2 = _interopRequireDefault(_operator); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var Operators = []; -Operators.push(new _operator2.default('equal', function (a, b) { - return a === b; -})); -Operators.push(new _operator2.default('notEqual', function (a, b) { - return a !== b; -})); -Operators.push(new _operator2.default('in', function (a, b) { - return b.indexOf(a) > -1; -})); -Operators.push(new _operator2.default('notIn', function (a, b) { - return b.indexOf(a) === -1; -})); - -Operators.push(new _operator2.default('contains', function (a, b) { - return a.indexOf(b) > -1; -}, Array.isArray)); -Operators.push(new _operator2.default('doesNotContain', function (a, b) { - return a.indexOf(b) === -1; -}, Array.isArray)); - -function numberValidator(factValue) { - return Number.parseFloat(factValue).toString() !== 'NaN'; -} -Operators.push(new _operator2.default('lessThan', function (a, b) { - return a < b; -}, numberValidator)); -Operators.push(new _operator2.default('lessThanInclusive', function (a, b) { - return a <= b; -}, numberValidator)); -Operators.push(new _operator2.default('greaterThan', function (a, b) { - return a > b; -}, numberValidator)); -Operators.push(new _operator2.default('greaterThanInclusive', function (a, b) { - return a >= b; -}, numberValidator)); - -exports.default = Operators; \ No newline at end of file diff --git a/dist/engine-facts.js b/dist/engine-facts.js deleted file mode 100644 index 0d1aed0..0000000 --- a/dist/engine-facts.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -var SuccessEventFact = function SuccessEventFact() { - var successTriggers = []; - return function () { - var params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - - if (params.event) { - successTriggers.push(params.event); - } - return successTriggers; - }; -}; - -exports.SuccessEventFact = SuccessEventFact; \ No newline at end of file diff --git a/dist/engine.js b/dist/engine.js deleted file mode 100644 index d899fdf..0000000 --- a/dist/engine.js +++ /dev/null @@ -1,314 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.FINISHED = exports.RUNNING = exports.READY = undefined; - -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; }; }(); - -var _fact = require('./fact'); - -var _fact2 = _interopRequireDefault(_fact); - -var _rule = require('./rule'); - -var _rule2 = _interopRequireDefault(_rule); - -var _operator = require('./operator'); - -var _operator2 = _interopRequireDefault(_operator); - -var _almanac = require('./almanac'); - -var _almanac2 = _interopRequireDefault(_almanac); - -var _events = require('events'); - -var _engineFacts = require('./engine-facts'); - -var _engineDefaultOperators = require('./engine-default-operators'); - -var _engineDefaultOperators2 = _interopRequireDefault(_engineDefaultOperators); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -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) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -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; } - -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; } - -var debug = require('debug')('json-rules-engine'); - -var READY = exports.READY = 'READY'; -var RUNNING = exports.RUNNING = 'RUNNING'; -var FINISHED = exports.FINISHED = 'FINISHED'; - -var Engine = function (_EventEmitter) { - _inherits(Engine, _EventEmitter); - - /** - * Returns a new Engine instance - * @param {Rule[]} rules - array of rules to initialize with - */ - function Engine() { - var rules = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - - _classCallCheck(this, Engine); - - var _this = _possibleConstructorReturn(this, (Engine.__proto__ || Object.getPrototypeOf(Engine)).call(this)); - - _this.rules = []; - _this.allowUndefinedFacts = options.allowUndefinedFacts || false; - _this.operators = new Map(); - _this.facts = new Map(); - _this.status = READY; - rules.map(function (r) { - return _this.addRule(r); - }); - _engineDefaultOperators2.default.map(function (o) { - return _this.addOperator(o); - }); - return _this; - } - - /** - * Add a rule definition to the engine - * @param {object|Rule} properties - rule definition. can be JSON representation, or instance of Rule - * @param {integer} properties.priority (>1) - higher runs sooner. - * @param {Object} properties.event - event to fire when rule evaluates as successful - * @param {string} properties.event.type - name of event to emit - * @param {string} properties.event.params - parameters to pass to the event listener - * @param {Object} properties.conditions - conditions to evaluate when processing this rule - */ - - - _createClass(Engine, [{ - key: 'addRule', - value: function addRule(properties) { - if (!properties) throw new Error('Engine: addRule() requires options'); - if (!properties.hasOwnProperty('conditions')) throw new Error('Engine: addRule() argument requires "conditions" property'); - if (!properties.hasOwnProperty('event')) throw new Error('Engine: addRule() argument requires "event" property'); - - var rule = void 0; - if (properties instanceof _rule2.default) { - rule = properties; - } else { - rule = new _rule2.default(properties); - } - rule.setEngine(this); - - this.rules.push(rule); - this.prioritizedRules = null; - return this; - } - - /** - * Add a custom operator definition - * @param {string} operatorOrName - operator identifier within the condition; i.e. instead of 'equals', 'greaterThan', etc - * @param {function(factValue, jsonValue)} callback - the method to execute when the operator is encountered. - */ - - }, { - key: 'addOperator', - value: function addOperator(operatorOrName, cb) { - var operator = void 0; - if (operatorOrName instanceof _operator2.default) { - operator = operatorOrName; - } else { - operator = new _operator2.default(operatorOrName, cb); - } - debug('engine::addOperator name:' + operator.name); - this.operators.set(operator.name, operator); - } - - /** - * Add a fact definition to the engine. Facts are called by rules as they are evaluated. - * @param {object|Fact} id - fact identifier or instance of Fact - * @param {function} definitionFunc - function to be called when computing the fact value for a given rule - * @param {Object} options - options to initialize the fact with. used when "id" is not a Fact instance - */ - - }, { - key: 'addFact', - value: function addFact(id, valueOrMethod, options) { - var factId = id; - var fact = void 0; - if (id instanceof _fact2.default) { - factId = id.id; - fact = id; - } else { - fact = new _fact2.default(id, valueOrMethod, options); - } - debug('engine::addFact id:' + factId); - this.facts.set(factId, fact); - return this; - } - - /** - * Iterates over the engine rules, organizing them by highest -> lowest priority - * @return {Rule[][]} two dimensional array of Rules. - * Each outer array element represents a single priority(integer). Inner array is - * all rules with that priority. - */ - - }, { - key: 'prioritizeRules', - value: function prioritizeRules() { - if (!this.prioritizedRules) { - var ruleSets = this.rules.reduce(function (sets, rule) { - var priority = rule.priority; - if (!sets[priority]) sets[priority] = []; - sets[priority].push(rule); - return sets; - }, {}); - this.prioritizedRules = Object.keys(ruleSets).sort(function (a, b) { - return Number(a) > Number(b) ? -1 : 1; // order highest priority -> lowest - }).map(function (priority) { - return ruleSets[priority]; - }); - } - return this.prioritizedRules; - } - - /** - * Stops the rules engine from running the next priority set of Rules. All remaining rules will be resolved as undefined, - * and no further events emitted. Since rules of the same priority are evaluated in parallel(not series), other rules of - * the same priority may still emit events, even though the engine is in a "finished" state. - * @return {Engine} - */ - - }, { - key: 'stop', - value: function stop() { - this.status = FINISHED; - return this; - } - - /** - * Returns a fact by fact-id - * @param {string} factId - fact identifier - * @return {Fact} fact instance, or undefined if no such fact exists - */ - - }, { - key: 'getFact', - value: function getFact(factId) { - return this.facts.get(factId); - } - - /** - * Runs an array of rules - * @param {Rule[]} array of rules to be evaluated - * @return {Promise} resolves when all rules in the array have been evaluated - */ - - }, { - key: 'evaluateRules', - value: function () { - var _ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee(ruleArray, almanac) { - var _this2 = this; - - return regeneratorRuntime.wrap(function _callee$(_context) { - while (1) { - switch (_context.prev = _context.next) { - case 0: - return _context.abrupt('return', Promise.all(ruleArray.map(function (rule) { - if (_this2.status !== RUNNING) { - debug('engine::run status:' + _this2.status + '; skipping remaining rules'); - return; - } - return rule.evaluate(almanac).then(function (ruleResult) { - debug('engine::run ruleResult:' + ruleResult.result); - if (ruleResult.result) { - _this2.emit('success', rule.event, almanac, ruleResult); - _this2.emit(rule.event.type, rule.event.params, almanac, ruleResult); - almanac.factValue('success-events', { event: rule.event }); - } else { - _this2.emit('failure', rule.event, almanac, ruleResult); - } - }); - }))); - - case 1: - case 'end': - return _context.stop(); - } - } - }, _callee, this); - })); - - function evaluateRules(_x3, _x4) { - return _ref.apply(this, arguments); - } - - return evaluateRules; - }() - - /** - * Runs the rules engine - * @param {Object} runtimeFacts - fact values known at runtime - * @param {Object} runOptions - run options - * @return {Promise} resolves when the engine has completed running - */ - - }, { - key: 'run', - value: function () { - var _ref2 = _asyncToGenerator(regeneratorRuntime.mark(function _callee2() { - var _this3 = this; - - var runtimeFacts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - var almanac, orderedSets, cursor; - return regeneratorRuntime.wrap(function _callee2$(_context2) { - while (1) { - switch (_context2.prev = _context2.next) { - case 0: - debug('engine::run started'); - debug('engine::run runtimeFacts:', runtimeFacts); - runtimeFacts['success-events'] = new _fact2.default('success-events', (0, _engineFacts.SuccessEventFact)(), { cache: false }); - this.status = RUNNING; - almanac = new _almanac2.default(this.facts, runtimeFacts); - orderedSets = this.prioritizeRules(); - cursor = Promise.resolve(); - // for each rule set, evaluate in parallel, - // before proceeding to the next priority set. - - return _context2.abrupt('return', new Promise(function (resolve, reject) { - orderedSets.map(function (set) { - cursor = cursor.then(function () { - return _this3.evaluateRules(set, almanac); - }).catch(reject); - return cursor; - }); - cursor.then(function () { - _this3.status = FINISHED; - debug('engine::run completed'); - resolve(almanac.factValue('success-events')); - }).catch(reject); - })); - - case 8: - case 'end': - return _context2.stop(); - } - } - }, _callee2, this); - })); - - function run() { - return _ref2.apply(this, arguments); - } - - return run; - }() - }]); - - return Engine; -}(_events.EventEmitter); - -exports.default = Engine; \ No newline at end of file diff --git a/dist/errors.js b/dist/errors.js deleted file mode 100644 index e298eb9..0000000 --- a/dist/errors.js +++ /dev/null @@ -1,32 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -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; } - -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; } - -var UndefinedFactError = exports.UndefinedFactError = function (_Error) { - _inherits(UndefinedFactError, _Error); - - function UndefinedFactError() { - var _ref; - - _classCallCheck(this, UndefinedFactError); - - for (var _len = arguments.length, props = Array(_len), _key = 0; _key < _len; _key++) { - props[_key] = arguments[_key]; - } - - var _this = _possibleConstructorReturn(this, (_ref = UndefinedFactError.__proto__ || Object.getPrototypeOf(UndefinedFactError)).call.apply(_ref, [this].concat(props))); - - _this.code = 'UNDEFINED_FACT'; - return _this; - } - - return UndefinedFactError; -}(Error); \ No newline at end of file diff --git a/dist/fact.js b/dist/fact.js deleted file mode 100644 index a9e4c55..0000000 --- a/dist/fact.js +++ /dev/null @@ -1,119 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -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; }; }(); - -var _objectHash = require('object-hash'); - -var _objectHash2 = _interopRequireDefault(_objectHash); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -var verbose = require('debug')('json-rules-engine-verbose'); - -var Fact = function () { - /** - * Returns a new fact instance - * @param {string} id - fact unique identifer - * @param {object} options - * @param {boolean} options.cache - whether to cache the fact's value for future rules - * @param {primitive|function} valueOrMethod - constant primitive, or method to call when computing the fact's value - * @return {Fact} - */ - function Fact(id, valueOrMethod, options) { - _classCallCheck(this, Fact); - - this.id = id; - var defaultOptions = { cache: true }; - if (typeof options === 'undefined') { - options = defaultOptions; - } - if (typeof valueOrMethod !== 'function') { - this.value = valueOrMethod; - } else { - this.calculationMethod = valueOrMethod; - } - - if (!this.id) throw new Error('factId required'); - if (typeof this.value === 'undefined' && typeof this.calculationMethod === 'undefined') { - throw new Error('facts must have a value or method'); - } - - this.priority = parseInt(options.priority || 1, 10); - this.options = Object.assign({}, defaultOptions, options); - this.cacheKeyMethod = this.defaultCacheKeys; - return this; - } - - /** - * Return the fact value, based on provided parameters - * @param {object} params - * @param {Almanac} almanac - * @return {any} calculation method results - */ - - - _createClass(Fact, [{ - key: 'calculate', - value: function calculate(params, almanac) { - // if constant fact w/set value, return immediately - if (this.hasOwnProperty('value')) { - return this.value; - } - return this.calculationMethod(params, almanac); - } - - /** - * Return a cache key (MD5 string) based on parameters - * @param {object} obj - properties to generate a hash key from - * @return {string} MD5 string based on the hash'd object - */ - - }, { - key: 'defaultCacheKeys', - - - /** - * Default properties to use when caching a fact - * Assumes every fact is a pure function, whose computed value will only - * change when input params are modified - * @param {string} id - fact unique identifer - * @param {object} params - parameters passed to fact calcution method - * @return {object} id + params - */ - value: function defaultCacheKeys(id, params) { - return { params: params, id: id }; - } - - /** - * Generates the fact's cache key(MD5 string) - * Returns nothing if the fact's caching has been disabled - * @param {object} params - parameters that would be passed to the computation method - * @return {string} cache key - */ - - }, { - key: 'getCacheKey', - value: function getCacheKey(params) { - if (this.options.cache === true) { - var cacheProperties = this.cacheKeyMethod(this.id, params); - return Fact.hashFromObject(cacheProperties); - } - } - }], [{ - key: 'hashFromObject', - value: function hashFromObject(obj) { - verbose('fact::hashFromObject generating cache key from:', obj); - return (0, _objectHash2.default)(obj); - } - }]); - - return Fact; -}(); - -exports.default = Fact; \ No newline at end of file diff --git a/dist/generator-runtime.js b/dist/generator-runtime.js deleted file mode 100644 index fb2ab55..0000000 --- a/dist/generator-runtime.js +++ /dev/null @@ -1,730 +0,0 @@ -/** - * Copyright (c) 2014, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * https://raw.github.com/facebook/regenerator/master/LICENSE file. An - * additional grant of patent rights can be found in the PATENTS file in - * the same directory. - */ - -!(function(global) { - "use strict"; - - var Op = Object.prototype; - var hasOwn = Op.hasOwnProperty; - var undefined; // More compressible than void 0. - var $Symbol = typeof Symbol === "function" ? Symbol : {}; - var iteratorSymbol = $Symbol.iterator || "@@iterator"; - var asyncIteratorSymbol = $Symbol.asyncIterator || "@@asyncIterator"; - var toStringTagSymbol = $Symbol.toStringTag || "@@toStringTag"; - - var inModule = typeof module === "object"; - var runtime = global.regeneratorRuntime; - if (runtime) { - if (inModule) { - // If regeneratorRuntime is defined globally and we're in a module, - // make the exports object identical to regeneratorRuntime. - module.exports = runtime; - } - // Don't bother evaluating the rest of this file if the runtime was - // already defined globally. - return; - } - - // Define the runtime globally (as expected by generated code) as either - // module.exports (if we're in a module) or a new, empty object. - runtime = global.regeneratorRuntime = inModule ? module.exports : {}; - - function wrap(innerFn, outerFn, self, tryLocsList) { - // If outerFn provided and outerFn.prototype is a Generator, then outerFn.prototype instanceof Generator. - var protoGenerator = outerFn && outerFn.prototype instanceof Generator ? outerFn : Generator; - var generator = Object.create(protoGenerator.prototype); - var context = new Context(tryLocsList || []); - - // The ._invoke method unifies the implementations of the .next, - // .throw, and .return methods. - generator._invoke = makeInvokeMethod(innerFn, self, context); - - return generator; - } - runtime.wrap = wrap; - - // Try/catch helper to minimize deoptimizations. Returns a completion - // record like context.tryEntries[i].completion. This interface could - // have been (and was previously) designed to take a closure to be - // invoked without arguments, but in all the cases we care about we - // already have an existing method we want to call, so there's no need - // to create a new function object. We can even get away with assuming - // the method takes exactly one argument, since that happens to be true - // in every case, so we don't have to touch the arguments object. The - // only additional allocation required is the completion record, which - // has a stable shape and so hopefully should be cheap to allocate. - function tryCatch(fn, obj, arg) { - try { - return { type: "normal", arg: fn.call(obj, arg) }; - } catch (err) { - return { type: "throw", arg: err }; - } - } - - var GenStateSuspendedStart = "suspendedStart"; - var GenStateSuspendedYield = "suspendedYield"; - var GenStateExecuting = "executing"; - var GenStateCompleted = "completed"; - - // Returning this object from the innerFn has the same effect as - // breaking out of the dispatch switch statement. - var ContinueSentinel = {}; - - // Dummy constructor functions that we use as the .constructor and - // .constructor.prototype properties for functions that return Generator - // objects. For full spec compliance, you may wish to configure your - // minifier not to mangle the names of these two functions. - function Generator() {} - function GeneratorFunction() {} - function GeneratorFunctionPrototype() {} - - // This is a polyfill for %IteratorPrototype% for environments that - // don't natively support it. - var IteratorPrototype = {}; - IteratorPrototype[iteratorSymbol] = function () { - return this; - }; - - var getProto = Object.getPrototypeOf; - var NativeIteratorPrototype = getProto && getProto(getProto(values([]))); - if (NativeIteratorPrototype && - NativeIteratorPrototype !== Op && - hasOwn.call(NativeIteratorPrototype, iteratorSymbol)) { - // This environment has a native %IteratorPrototype%; use it instead - // of the polyfill. - IteratorPrototype = NativeIteratorPrototype; - } - - var Gp = GeneratorFunctionPrototype.prototype = - Generator.prototype = Object.create(IteratorPrototype); - GeneratorFunction.prototype = Gp.constructor = GeneratorFunctionPrototype; - GeneratorFunctionPrototype.constructor = GeneratorFunction; - GeneratorFunctionPrototype[toStringTagSymbol] = - GeneratorFunction.displayName = "GeneratorFunction"; - - // Helper for defining the .next, .throw, and .return methods of the - // Iterator interface in terms of a single ._invoke method. - function defineIteratorMethods(prototype) { - ["next", "throw", "return"].forEach(function(method) { - prototype[method] = function(arg) { - return this._invoke(method, arg); - }; - }); - } - - runtime.isGeneratorFunction = function(genFun) { - var ctor = typeof genFun === "function" && genFun.constructor; - return ctor - ? ctor === GeneratorFunction || - // For the native GeneratorFunction constructor, the best we can - // do is to check its .name property. - (ctor.displayName || ctor.name) === "GeneratorFunction" - : false; - }; - - runtime.mark = function(genFun) { - if (Object.setPrototypeOf) { - Object.setPrototypeOf(genFun, GeneratorFunctionPrototype); - } else { - genFun.__proto__ = GeneratorFunctionPrototype; - if (!(toStringTagSymbol in genFun)) { - genFun[toStringTagSymbol] = "GeneratorFunction"; - } - } - genFun.prototype = Object.create(Gp); - return genFun; - }; - - // Within the body of any async function, `await x` is transformed to - // `yield regeneratorRuntime.awrap(x)`, so that the runtime can test - // `hasOwn.call(value, "__await")` to determine if the yielded value is - // meant to be awaited. - runtime.awrap = function(arg) { - return { __await: arg }; - }; - - function AsyncIterator(generator) { - function invoke(method, arg, resolve, reject) { - var record = tryCatch(generator[method], generator, arg); - if (record.type === "throw") { - reject(record.arg); - } else { - var result = record.arg; - var value = result.value; - if (value && - typeof value === "object" && - hasOwn.call(value, "__await")) { - return Promise.resolve(value.__await).then(function(value) { - invoke("next", value, resolve, reject); - }, function(err) { - invoke("throw", err, resolve, reject); - }); - } - - return Promise.resolve(value).then(function(unwrapped) { - // When a yielded Promise is resolved, its final value becomes - // the .value of the Promise<{value,done}> result for the - // current iteration. If the Promise is rejected, however, the - // result for this iteration will be rejected with the same - // reason. Note that rejections of yielded Promises are not - // thrown back into the generator function, as is the case - // when an awaited Promise is rejected. This difference in - // behavior between yield and await is important, because it - // allows the consumer to decide what to do with the yielded - // rejection (swallow it and continue, manually .throw it back - // into the generator, abandon iteration, whatever). With - // await, by contrast, there is no opportunity to examine the - // rejection reason outside the generator function, so the - // only option is to throw it from the await expression, and - // let the generator function handle the exception. - result.value = unwrapped; - resolve(result); - }, reject); - } - } - - var previousPromise; - - function enqueue(method, arg) { - function callInvokeWithMethodAndArg() { - return new Promise(function(resolve, reject) { - invoke(method, arg, resolve, reject); - }); - } - - return previousPromise = - // If enqueue has been called before, then we want to wait until - // all previous Promises have been resolved before calling invoke, - // so that results are always delivered in the correct order. If - // enqueue has not been called before, then it is important to - // call invoke immediately, without waiting on a callback to fire, - // so that the async generator function has the opportunity to do - // any necessary setup in a predictable way. This predictability - // is why the Promise constructor synchronously invokes its - // executor callback, and why async functions synchronously - // execute code before the first await. Since we implement simple - // async functions in terms of async generators, it is especially - // important to get this right, even though it requires care. - previousPromise ? previousPromise.then( - callInvokeWithMethodAndArg, - // Avoid propagating failures to Promises returned by later - // invocations of the iterator. - callInvokeWithMethodAndArg - ) : callInvokeWithMethodAndArg(); - } - - // Define the unified helper method that is used to implement .next, - // .throw, and .return (see defineIteratorMethods). - this._invoke = enqueue; - } - - defineIteratorMethods(AsyncIterator.prototype); - AsyncIterator.prototype[asyncIteratorSymbol] = function () { - return this; - }; - runtime.AsyncIterator = AsyncIterator; - - // Note that simple async functions are implemented on top of - // AsyncIterator objects; they just return a Promise for the value of - // the final result produced by the iterator. - runtime.async = function(innerFn, outerFn, self, tryLocsList) { - var iter = new AsyncIterator( - wrap(innerFn, outerFn, self, tryLocsList) - ); - - return runtime.isGeneratorFunction(outerFn) - ? iter // If outerFn is a generator, return the full iterator. - : iter.next().then(function(result) { - return result.done ? result.value : iter.next(); - }); - }; - - function makeInvokeMethod(innerFn, self, context) { - var state = GenStateSuspendedStart; - - return function invoke(method, arg) { - if (state === GenStateExecuting) { - throw new Error("Generator is already running"); - } - - if (state === GenStateCompleted) { - if (method === "throw") { - throw arg; - } - - // Be forgiving, per 25.3.3.3.3 of the spec: - // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-generatorresume - return doneResult(); - } - - context.method = method; - context.arg = arg; - - while (true) { - var delegate = context.delegate; - if (delegate) { - var delegateResult = maybeInvokeDelegate(delegate, context); - if (delegateResult) { - if (delegateResult === ContinueSentinel) continue; - return delegateResult; - } - } - - if (context.method === "next") { - // Setting context._sent for legacy support of Babel's - // function.sent implementation. - context.sent = context._sent = context.arg; - - } else if (context.method === "throw") { - if (state === GenStateSuspendedStart) { - state = GenStateCompleted; - throw context.arg; - } - - context.dispatchException(context.arg); - - } else if (context.method === "return") { - context.abrupt("return", context.arg); - } - - state = GenStateExecuting; - - var record = tryCatch(innerFn, self, context); - if (record.type === "normal") { - // If an exception is thrown from innerFn, we leave state === - // GenStateExecuting and loop back for another invocation. - state = context.done - ? GenStateCompleted - : GenStateSuspendedYield; - - if (record.arg === ContinueSentinel) { - continue; - } - - return { - value: record.arg, - done: context.done - }; - - } else if (record.type === "throw") { - state = GenStateCompleted; - // Dispatch the exception by looping back around to the - // context.dispatchException(context.arg) call above. - context.method = "throw"; - context.arg = record.arg; - } - } - }; - } - - // Call delegate.iterator[context.method](context.arg) and handle the - // result, either by returning a { value, done } result from the - // delegate iterator, or by modifying context.method and context.arg, - // setting context.delegate to null, and returning the ContinueSentinel. - function maybeInvokeDelegate(delegate, context) { - var method = delegate.iterator[context.method]; - if (method === undefined) { - // A .throw or .return when the delegate iterator has no .throw - // method always terminates the yield* loop. - context.delegate = null; - - if (context.method === "throw") { - if (delegate.iterator.return) { - // If the delegate iterator has a return method, give it a - // chance to clean up. - context.method = "return"; - context.arg = undefined; - maybeInvokeDelegate(delegate, context); - - if (context.method === "throw") { - // If maybeInvokeDelegate(context) changed context.method from - // "return" to "throw", let that override the TypeError below. - return ContinueSentinel; - } - } - - context.method = "throw"; - context.arg = new TypeError( - "The iterator does not provide a 'throw' method"); - } - - return ContinueSentinel; - } - - var record = tryCatch(method, delegate.iterator, context.arg); - - if (record.type === "throw") { - context.method = "throw"; - context.arg = record.arg; - context.delegate = null; - return ContinueSentinel; - } - - var info = record.arg; - - if (! info) { - context.method = "throw"; - context.arg = new TypeError("iterator result is not an object"); - context.delegate = null; - return ContinueSentinel; - } - - if (info.done) { - // Assign the result of the finished delegate to the temporary - // variable specified by delegate.resultName (see delegateYield). - context[delegate.resultName] = info.value; - - // Resume execution at the desired location (see delegateYield). - context.next = delegate.nextLoc; - - // If context.method was "throw" but the delegate handled the - // exception, let the outer generator proceed normally. If - // context.method was "next", forget context.arg since it has been - // "consumed" by the delegate iterator. If context.method was - // "return", allow the original .return call to continue in the - // outer generator. - if (context.method !== "return") { - context.method = "next"; - context.arg = undefined; - } - - } else { - // Re-yield the result returned by the delegate method. - return info; - } - - // The delegate iterator is finished, so forget it and continue with - // the outer generator. - context.delegate = null; - return ContinueSentinel; - } - - // Define Generator.prototype.{next,throw,return} in terms of the - // unified ._invoke helper method. - defineIteratorMethods(Gp); - - Gp[toStringTagSymbol] = "Generator"; - - // A Generator should always return itself as the iterator object when the - // @@iterator function is called on it. Some browsers' implementations of the - // iterator prototype chain incorrectly implement this, causing the Generator - // object to not be returned from this call. This ensures that doesn't happen. - // See https://github.com/facebook/regenerator/issues/274 for more details. - Gp[iteratorSymbol] = function() { - return this; - }; - - Gp.toString = function() { - return "[object Generator]"; - }; - - function pushTryEntry(locs) { - var entry = { tryLoc: locs[0] }; - - if (1 in locs) { - entry.catchLoc = locs[1]; - } - - if (2 in locs) { - entry.finallyLoc = locs[2]; - entry.afterLoc = locs[3]; - } - - this.tryEntries.push(entry); - } - - function resetTryEntry(entry) { - var record = entry.completion || {}; - record.type = "normal"; - delete record.arg; - entry.completion = record; - } - - function Context(tryLocsList) { - // The root entry object (effectively a try statement without a catch - // or a finally block) gives us a place to store values thrown from - // locations where there is no enclosing try statement. - this.tryEntries = [{ tryLoc: "root" }]; - tryLocsList.forEach(pushTryEntry, this); - this.reset(true); - } - - runtime.keys = function(object) { - var keys = []; - for (var key in object) { - keys.push(key); - } - keys.reverse(); - - // Rather than returning an object with a next method, we keep - // things simple and return the next function itself. - return function next() { - while (keys.length) { - var key = keys.pop(); - if (key in object) { - next.value = key; - next.done = false; - return next; - } - } - - // To avoid creating an additional object, we just hang the .value - // and .done properties off the next function object itself. This - // also ensures that the minifier will not anonymize the function. - next.done = true; - return next; - }; - }; - - function values(iterable) { - if (iterable) { - var iteratorMethod = iterable[iteratorSymbol]; - if (iteratorMethod) { - return iteratorMethod.call(iterable); - } - - if (typeof iterable.next === "function") { - return iterable; - } - - if (!isNaN(iterable.length)) { - var i = -1, next = function next() { - while (++i < iterable.length) { - if (hasOwn.call(iterable, i)) { - next.value = iterable[i]; - next.done = false; - return next; - } - } - - next.value = undefined; - next.done = true; - - return next; - }; - - return next.next = next; - } - } - - // Return an iterator with no values. - return { next: doneResult }; - } - runtime.values = values; - - function doneResult() { - return { value: undefined, done: true }; - } - - Context.prototype = { - constructor: Context, - - reset: function(skipTempReset) { - this.prev = 0; - this.next = 0; - // Resetting context._sent for legacy support of Babel's - // function.sent implementation. - this.sent = this._sent = undefined; - this.done = false; - this.delegate = null; - - this.method = "next"; - this.arg = undefined; - - this.tryEntries.forEach(resetTryEntry); - - if (!skipTempReset) { - for (var name in this) { - // Not sure about the optimal order of these conditions: - if (name.charAt(0) === "t" && - hasOwn.call(this, name) && - !isNaN(+name.slice(1))) { - this[name] = undefined; - } - } - } - }, - - stop: function() { - this.done = true; - - var rootEntry = this.tryEntries[0]; - var rootRecord = rootEntry.completion; - if (rootRecord.type === "throw") { - throw rootRecord.arg; - } - - return this.rval; - }, - - dispatchException: function(exception) { - if (this.done) { - throw exception; - } - - var context = this; - function handle(loc, caught) { - record.type = "throw"; - record.arg = exception; - context.next = loc; - - if (caught) { - // If the dispatched exception was caught by a catch block, - // then let that catch block handle the exception normally. - context.method = "next"; - context.arg = undefined; - } - - return !! caught; - } - - for (var i = this.tryEntries.length - 1; i >= 0; --i) { - var entry = this.tryEntries[i]; - var record = entry.completion; - - if (entry.tryLoc === "root") { - // Exception thrown outside of any try block that could handle - // it, so set the completion value of the entire function to - // throw the exception. - return handle("end"); - } - - if (entry.tryLoc <= this.prev) { - var hasCatch = hasOwn.call(entry, "catchLoc"); - var hasFinally = hasOwn.call(entry, "finallyLoc"); - - if (hasCatch && hasFinally) { - if (this.prev < entry.catchLoc) { - return handle(entry.catchLoc, true); - } else if (this.prev < entry.finallyLoc) { - return handle(entry.finallyLoc); - } - - } else if (hasCatch) { - if (this.prev < entry.catchLoc) { - return handle(entry.catchLoc, true); - } - - } else if (hasFinally) { - if (this.prev < entry.finallyLoc) { - return handle(entry.finallyLoc); - } - - } else { - throw new Error("try statement without catch or finally"); - } - } - } - }, - - abrupt: function(type, arg) { - for (var i = this.tryEntries.length - 1; i >= 0; --i) { - var entry = this.tryEntries[i]; - if (entry.tryLoc <= this.prev && - hasOwn.call(entry, "finallyLoc") && - this.prev < entry.finallyLoc) { - var finallyEntry = entry; - break; - } - } - - if (finallyEntry && - (type === "break" || - type === "continue") && - finallyEntry.tryLoc <= arg && - arg <= finallyEntry.finallyLoc) { - // Ignore the finally entry if control is not jumping to a - // location outside the try/catch block. - finallyEntry = null; - } - - var record = finallyEntry ? finallyEntry.completion : {}; - record.type = type; - record.arg = arg; - - if (finallyEntry) { - this.method = "next"; - this.next = finallyEntry.finallyLoc; - return ContinueSentinel; - } - - return this.complete(record); - }, - - complete: function(record, afterLoc) { - if (record.type === "throw") { - throw record.arg; - } - - if (record.type === "break" || - record.type === "continue") { - this.next = record.arg; - } else if (record.type === "return") { - this.rval = this.arg = record.arg; - this.method = "return"; - this.next = "end"; - } else if (record.type === "normal" && afterLoc) { - this.next = afterLoc; - } - - return ContinueSentinel; - }, - - finish: function(finallyLoc) { - for (var i = this.tryEntries.length - 1; i >= 0; --i) { - var entry = this.tryEntries[i]; - if (entry.finallyLoc === finallyLoc) { - this.complete(entry.completion, entry.afterLoc); - resetTryEntry(entry); - return ContinueSentinel; - } - } - }, - - "catch": function(tryLoc) { - for (var i = this.tryEntries.length - 1; i >= 0; --i) { - var entry = this.tryEntries[i]; - if (entry.tryLoc === tryLoc) { - var record = entry.completion; - if (record.type === "throw") { - var thrown = record.arg; - resetTryEntry(entry); - } - return thrown; - } - } - - // The context.catch method must only be called with a location - // argument that corresponds to a known catch block. - throw new Error("illegal catch attempt"); - }, - - delegateYield: function(iterable, resultName, nextLoc) { - this.delegate = { - iterator: values(iterable), - resultName: resultName, - nextLoc: nextLoc - }; - - if (this.method === "next") { - // Deliberately forget the last sent value so that we don't - // accidentally pass it on to the delegate. - this.arg = undefined; - } - - return ContinueSentinel; - } - }; -})( - // In sloppy mode, unbound `this` refers to the global object, fallback to - // Function constructor if we're in global strict mode. That is sadly a form - // of indirect eval which violates Content Security Policy. - (function() { return this })() || Function("return this")() -); diff --git a/dist/index.js b/dist/index.js deleted file mode 100644 index 3965275..0000000 --- a/dist/index.js +++ /dev/null @@ -1,4 +0,0 @@ -'use strict'; - -require('./generator-runtime'); -module.exports = require('./json-rules-engine'); \ No newline at end of file diff --git a/dist/json-rules-engine.js b/dist/json-rules-engine.js deleted file mode 100644 index baf2299..0000000 --- a/dist/json-rules-engine.js +++ /dev/null @@ -1,33 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.Engine = exports.Operator = exports.Rule = exports.Fact = undefined; - -exports.default = function (rules, options) { - return new _engine2.default(rules, options); -}; - -var _engine = require('./engine'); - -var _engine2 = _interopRequireDefault(_engine); - -var _fact = require('./fact'); - -var _fact2 = _interopRequireDefault(_fact); - -var _rule = require('./rule'); - -var _rule2 = _interopRequireDefault(_rule); - -var _operator = require('./operator'); - -var _operator2 = _interopRequireDefault(_operator); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -exports.Fact = _fact2.default; -exports.Rule = _rule2.default; -exports.Operator = _operator2.default; -exports.Engine = _engine2.default; \ No newline at end of file diff --git a/dist/operator.js b/dist/operator.js deleted file mode 100644 index 35db694..0000000 --- a/dist/operator.js +++ /dev/null @@ -1,50 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -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; }; }(); - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -var Operator = function () { - /** - * Constructor - * @param {string} name - operator identifier - * @param {function(factValue, jsonValue)} callback - operator evaluation method - * @param {function} [factValueValidator] - optional validator for asserting the data type of the fact - * @returns {Operator} - instance - */ - function Operator(name, cb, factValueValidator) { - _classCallCheck(this, Operator); - - this.name = String(name); - if (!name) throw new Error('Missing operator name'); - if (typeof cb !== 'function') throw new Error('Missing operator callback'); - this.cb = cb; - this.factValueValidator = factValueValidator; - if (!this.factValueValidator) this.factValueValidator = function () { - return true; - }; - } - - /** - * Takes the fact result and compares it to the condition 'value', using the callback - * @param {mixed} factValue - fact result - * @param {mixed} jsonValue - "value" property of the condition - * @returns {Boolean} - whether the values pass the operator test - */ - - - _createClass(Operator, [{ - key: 'evaluate', - value: function evaluate(factValue, jsonValue) { - return this.factValueValidator(factValue) && this.cb(factValue, jsonValue); - } - }]); - - return Operator; -}(); - -exports.default = Operator; \ No newline at end of file diff --git a/dist/rule-result.js b/dist/rule-result.js deleted file mode 100644 index 2d83749..0000000 --- a/dist/rule-result.js +++ /dev/null @@ -1,53 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -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; }; }(); - -var _clone = require('clone'); - -var _clone2 = _interopRequireDefault(_clone); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -var RuleResult = function () { - function RuleResult(conditions, event, priority) { - _classCallCheck(this, RuleResult); - - this.conditions = (0, _clone2.default)(conditions); - this.event = (0, _clone2.default)(event); - this.priority = (0, _clone2.default)(priority); - this.result = null; - } - - _createClass(RuleResult, [{ - key: 'setResult', - value: function setResult(result) { - this.result = result; - } - }, { - key: 'toJSON', - value: function toJSON() { - var stringify = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; - - var props = { - conditions: this.conditions.toJSON(false), - event: this.event, - priority: this.priority, - result: this.result - }; - if (stringify) { - return JSON.stringify(props); - } - return props; - } - }]); - - return RuleResult; -}(); - -exports.default = RuleResult; \ No newline at end of file diff --git a/dist/rule.js b/dist/rule.js deleted file mode 100644 index 30e7cef..0000000 --- a/dist/rule.js +++ /dev/null @@ -1,515 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -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; }; }(); - -var _condition = require('./condition'); - -var _condition2 = _interopRequireDefault(_condition); - -var _ruleResult = require('./rule-result'); - -var _ruleResult2 = _interopRequireDefault(_ruleResult); - -var _events = require('events'); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -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) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -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; } - -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; } - -var debug = require('debug')('json-rules-engine'); - -var Rule = function (_EventEmitter) { - _inherits(Rule, _EventEmitter); - - /** - * returns a new Rule instance - * @param {object,string} options, or json string that can be parsed into options - * @param {integer} options.priority (>1) - higher runs sooner. - * @param {Object} options.event - event to fire when rule evaluates as successful - * @param {string} options.event.type - name of event to emit - * @param {string} options.event.params - parameters to pass to the event listener - * @param {Object} options.conditions - conditions to evaluate when processing this rule - * @return {Rule} instance - */ - function Rule(options) { - _classCallCheck(this, Rule); - - var _this = _possibleConstructorReturn(this, (Rule.__proto__ || Object.getPrototypeOf(Rule)).call(this)); - - if (typeof options === 'string') { - options = JSON.parse(options); - } - if (options && options.conditions) { - _this.setConditions(options.conditions); - } - if (options && options.onSuccess) { - _this.on('success', options.onSuccess); - } - if (options && options.onFailure) { - _this.on('failure', options.onFailure); - } - - var priority = options && options.priority || 1; - _this.setPriority(priority); - - var event = options && options.event || { type: 'unknown' }; - _this.setEvent(event); - return _this; - } - - /** - * Sets the priority of the rule - * @param {integer} priority (>=1) - increasing the priority causes the rule to be run prior to other rules - */ - - - _createClass(Rule, [{ - key: 'setPriority', - value: function setPriority(priority) { - priority = parseInt(priority, 10); - if (priority <= 0) throw new Error('Priority must be greater than zero'); - this.priority = priority; - return this; - } - - /** - * Sets the conditions to run when evaluating the rule. - * @param {object} conditions - conditions, root element must be a boolean operator - */ - - }, { - key: 'setConditions', - value: function setConditions(conditions) { - if (!conditions.hasOwnProperty('all') && !conditions.hasOwnProperty('any')) { - throw new Error('"conditions" root must contain a single instance of "all" or "any"'); - } - this.conditions = new _condition2.default(conditions); - return this; - } - - /** - * Sets the event to emit when the conditions evaluate truthy - * @param {object} event - event to emit - * @param {string} event.type - event name to emit on - * @param {string} event.params - parameters to emit as the argument of the event emission - */ - - }, { - key: 'setEvent', - value: function setEvent(event) { - if (!event) throw new Error('Rule: setEvent() requires event object'); - if (!event.hasOwnProperty('type')) throw new Error('Rule: setEvent() requires event object with "type" property'); - this.event = { - type: event.type - }; - if (event.params) this.event.params = event.params; - return this; - } - - /** - * Sets the engine to run the rules under - * @param {object} engine - * @returns {Rule} - */ - - }, { - key: 'setEngine', - value: function setEngine(engine) { - this.engine = engine; - return this; - } - }, { - key: 'toJSON', - value: function toJSON() { - var stringify = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; - - var props = { - conditions: this.conditions.toJSON(false), - priority: this.priority, - event: this.event - }; - if (stringify) { - return JSON.stringify(props); - } - return props; - } - - /** - * Priorizes an array of conditions based on "priority" - * When no explicit priority is provided on the condition itself, the condition's priority is determine by its fact - * @param {Condition[]} conditions - * @return {Condition[][]} prioritized two-dimensional array of conditions - * Each outer array element represents a single priority(integer). Inner array is - * all conditions with that priority. - */ - - }, { - key: 'prioritizeConditions', - value: function prioritizeConditions(conditions) { - var _this2 = this; - - var factSets = conditions.reduce(function (sets, condition) { - // if a priority has been set on this specific condition, honor that first - // otherwise, use the fact's priority - var priority = condition.priority; - if (!priority) { - var fact = _this2.engine.getFact(condition.fact); - priority = fact && fact.priority || 1; - } - if (!sets[priority]) sets[priority] = []; - sets[priority].push(condition); - return sets; - }, {}); - return Object.keys(factSets).sort(function (a, b) { - return Number(a) > Number(b) ? -1 : 1; // order highest priority -> lowest - }).map(function (priority) { - return factSets[priority]; - }); - } - - /** - * Evaluates the rule, starting with the root boolean operator and recursing down - * All evaluation is done within the context of an almanac - * @return {Promise(RuleResult)} rule evaluation result - */ - - }, { - key: 'evaluate', - value: function () { - var _ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee6(almanac) { - var _this3 = this; - - var ruleResult, evaluateCondition, evaluateConditions, prioritizeAndRun, any, all, processResult, result, _result; - - return regeneratorRuntime.wrap(function _callee6$(_context6) { - while (1) { - switch (_context6.prev = _context6.next) { - case 0: - ruleResult = new _ruleResult2.default(this.conditions, this.event, this.priority); - - /** - * Evaluates the rule conditions - * @param {Condition} condition - condition to evaluate - * @return {Promise(true|false)} - resolves with the result of the condition evaluation - */ - - evaluateCondition = function () { - var _ref2 = _asyncToGenerator(regeneratorRuntime.mark(function _callee(condition) { - var comparisonValue, passes, subConditions, evaluationResult; - return regeneratorRuntime.wrap(function _callee$(_context) { - while (1) { - switch (_context.prev = _context.next) { - case 0: - comparisonValue = void 0; - passes = void 0; - - if (!condition.isBooleanOperator()) { - _context.next = 16; - break; - } - - subConditions = condition[condition.operator]; - - if (!(condition.operator === 'all')) { - _context.next = 10; - break; - } - - _context.next = 7; - return all(subConditions); - - case 7: - comparisonValue = _context.sent; - _context.next = 13; - break; - - case 10: - _context.next = 12; - return any(subConditions); - - case 12: - comparisonValue = _context.sent; - - case 13: - // for booleans, rule passing is determined by the all/any result - passes = comparisonValue === true; - _context.next = 31; - break; - - case 16: - _context.prev = 16; - _context.next = 19; - return condition.evaluate(almanac, _this3.engine.operators, comparisonValue); - - case 19: - evaluationResult = _context.sent; - - passes = evaluationResult.result; - condition.factResult = evaluationResult.leftHandSideValue; - _context.next = 31; - break; - - case 24: - _context.prev = 24; - _context.t0 = _context['catch'](16); - - if (!(_this3.engine.allowUndefinedFacts && _context.t0.code === 'UNDEFINED_FACT')) { - _context.next = 30; - break; - } - - passes = false; - _context.next = 31; - break; - - case 30: - throw _context.t0; - - case 31: - condition.result = passes; - return _context.abrupt('return', passes); - - case 33: - case 'end': - return _context.stop(); - } - } - }, _callee, _this3, [[16, 24]]); - })); - - return function evaluateCondition(_x3) { - return _ref2.apply(this, arguments); - }; - }(); - - /** - * Evalutes an array of conditions, using an 'every' or 'some' array operation - * @param {Condition[]} conditions - * @param {string(every|some)} array method to call for determining result - * @return {Promise(boolean)} whether conditions evaluated truthy or falsey based on condition evaluation + method - */ - - - evaluateConditions = function () { - var _ref3 = _asyncToGenerator(regeneratorRuntime.mark(function _callee2(conditions, method) { - var conditionResults; - return regeneratorRuntime.wrap(function _callee2$(_context2) { - while (1) { - switch (_context2.prev = _context2.next) { - case 0: - if (!Array.isArray(conditions)) conditions = [conditions]; - _context2.next = 3; - return Promise.all(conditions.map(function (condition) { - return evaluateCondition(condition); - })); - - case 3: - conditionResults = _context2.sent; - - debug('rule::evaluateConditions results', conditionResults); - return _context2.abrupt('return', method.call(conditionResults, function (result) { - return result === true; - })); - - case 6: - case 'end': - return _context2.stop(); - } - } - }, _callee2, _this3); - })); - - return function evaluateConditions(_x4, _x5) { - return _ref3.apply(this, arguments); - }; - }(); - - /** - * Evaluates a set of conditions based on an 'all' or 'any' operator. - * First, orders the top level conditions based on priority - * Iterates over each priority set, evaluating each condition - * If any condition results in the rule to be guaranteed truthy or falsey, - * it will short-circuit and not bother evaluating any additional rules - * @param {Condition[]} conditions - conditions to be evaluated - * @param {string('all'|'any')} operator - * @return {Promise(boolean)} rule evaluation result - */ - - - prioritizeAndRun = function () { - var _ref4 = _asyncToGenerator(regeneratorRuntime.mark(function _callee3(conditions, operator) { - var method, orderedSets, cursor; - return regeneratorRuntime.wrap(function _callee3$(_context3) { - while (1) { - switch (_context3.prev = _context3.next) { - case 0: - if (!(conditions.length === 0)) { - _context3.next = 2; - break; - } - - return _context3.abrupt('return', true); - - case 2: - method = Array.prototype.some; - - if (operator === 'all') { - method = Array.prototype.every; - } - orderedSets = _this3.prioritizeConditions(conditions); - cursor = Promise.resolve(); - - orderedSets.forEach(function (set) { - var stop = false; - cursor = cursor.then(function (setResult) { - // after the first set succeeds, don't fire off the remaining promises - if (operator === 'any' && setResult === true || stop) { - debug('prioritizeAndRun::detected truthy result; skipping remaining conditions'); - stop = true; - return true; - } - - // after the first set fails, don't fire off the remaining promises - if (operator === 'all' && setResult === false || stop) { - debug('prioritizeAndRun::detected falsey result; skipping remaining conditions'); - stop = true; - return false; - } - // all conditions passed; proceed with running next set in parallel - return evaluateConditions(set, method); - }); - }); - return _context3.abrupt('return', cursor); - - case 8: - case 'end': - return _context3.stop(); - } - } - }, _callee3, _this3); - })); - - return function prioritizeAndRun(_x6, _x7) { - return _ref4.apply(this, arguments); - }; - }(); - - /** - * Runs an 'any' boolean operator on an array of conditions - * @param {Condition[]} conditions to be evaluated - * @return {Promise(boolean)} condition evaluation result - */ - - - any = function () { - var _ref5 = _asyncToGenerator(regeneratorRuntime.mark(function _callee4(conditions) { - return regeneratorRuntime.wrap(function _callee4$(_context4) { - while (1) { - switch (_context4.prev = _context4.next) { - case 0: - return _context4.abrupt('return', prioritizeAndRun(conditions, 'any')); - - case 1: - case 'end': - return _context4.stop(); - } - } - }, _callee4, _this3); - })); - - return function any(_x8) { - return _ref5.apply(this, arguments); - }; - }(); - - /** - * Runs an 'all' boolean operator on an array of conditions - * @param {Condition[]} conditions to be evaluated - * @return {Promise(boolean)} condition evaluation result - */ - - - all = function () { - var _ref6 = _asyncToGenerator(regeneratorRuntime.mark(function _callee5(conditions) { - return regeneratorRuntime.wrap(function _callee5$(_context5) { - while (1) { - switch (_context5.prev = _context5.next) { - case 0: - return _context5.abrupt('return', prioritizeAndRun(conditions, 'all')); - - case 1: - case 'end': - return _context5.stop(); - } - } - }, _callee5, _this3); - })); - - return function all(_x9) { - return _ref6.apply(this, arguments); - }; - }(); - - /** - * Emits based on rule evaluation result, and decorates ruleResult with 'result' property - * @param {Boolean} result - */ - - - processResult = function processResult(result) { - ruleResult.setResult(result); - - if (result) _this3.emit('success', ruleResult.event, almanac, ruleResult);else _this3.emit('failure', ruleResult.event, almanac, ruleResult); - return ruleResult; - }; - - if (!ruleResult.conditions.any) { - _context6.next = 14; - break; - } - - _context6.next = 10; - return any(ruleResult.conditions.any); - - case 10: - result = _context6.sent; - return _context6.abrupt('return', processResult(result)); - - case 14: - _context6.next = 16; - return all(ruleResult.conditions.all); - - case 16: - _result = _context6.sent; - return _context6.abrupt('return', processResult(_result)); - - case 18: - case 'end': - return _context6.stop(); - } - } - }, _callee6, this); - })); - - function evaluate(_x2) { - return _ref.apply(this, arguments); - } - - return evaluate; - }() - }]); - - return Rule; -}(_events.EventEmitter); - -exports.default = Rule; \ No newline at end of file diff --git a/package.json b/package.json index 0d147fb..a8ff2b7 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "test": "mocha && npm run lint --silent", "lint": "standard --verbose | snazzy || true", "lint:fix": "standard --fix", - "prepublish": "npm run compile", + "prepublishOnly": "npm run compile", "compile": "babel --stage 1 -d dist/ src/ && regenerator --no-cache-dir --include-runtime src/generator-runtime.js > dist/generator-runtime.js" }, "repository": {