UNPKG

json-rules-engine

Version:
255 lines (210 loc) 8.72 kB
'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'); var _debug = require('./debug'); var _debug2 = _interopRequireDefault(_debug); var _jsonpathPlus = require('jsonpath-plus'); 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"); } } function defaultPathResolver(value, path) { return (0, _jsonpathPlus.JSONPath)({ path: path, json: value, wrap: false }); } /** * Fact results lookup * Triggers fact computations and saves the results * A new almanac is used for every engine run() */ var Almanac = function () { function Almanac() { var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; _classCallCheck(this, Almanac); this.factMap = new Map(); this.factResultsCache = new Map(); // { cacheKey: Promise<factValu> } this.allowUndefinedFacts = Boolean(options.allowUndefinedFacts); this.pathResolver = options.pathResolver || defaultPathResolver; this.events = { success: [], failure: [] }; this.ruleResults = []; } /** * Adds a success event * @param {Object} event */ _createClass(Almanac, [{ key: 'addEvent', value: function addEvent(event, outcome) { if (!outcome) throw new Error('outcome required: "success" | "failure"]'); this.events[outcome].push(event); } /** * retrieve successful events */ }, { key: 'getEvents', value: function getEvents() { var outcome = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; if (outcome) return this.events[outcome]; return this.events.success.concat(this.events.failure); } /** * Adds a rule result * @param {Object} event */ }, { key: 'addResult', value: function addResult(ruleResult) { this.ruleResults.push(ruleResult); } /** * retrieve successful events */ }, { key: 'getResults', value: function getResults() { return this.ruleResults; } /** * Retrieve fact by id, raising an exception if it DNE * @param {String} factId * @return {Fact} */ }, { key: '_getFact', value: function _getFact(factId) { return this.factMap.get(factId); } /** * 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); if (cacheKey) { this.factResultsCache.set(cacheKey, factValue); } return factValue; } /** * 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); } (0, _debug2.default)('almanac::addFact', { id: factId }); this.factMap.set(factId, fact); if (fact.isConstant()) { this._setFactValue(fact, {}, fact.value); } return this; } /** * Adds a constant fact during runtime. Can be used mid-run() to add additional information * @deprecated use addFact * @param {String} fact - fact identifier * @param {Mixed} value - constant value of the fact */ }, { key: 'addRuntimeFact', value: function addRuntimeFact(factId, value) { (0, _debug2.default)('almanac::addRuntimeFact', { id: factId }); 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 factValue(factId) { var _this = this; var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var path = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ''; var factValuePromise = void 0; var fact = this._getFact(factId); if (fact === undefined) { if (this.allowUndefinedFacts) { return Promise.resolve(undefined); } else { return Promise.reject(new _errors.UndefinedFactError('Undefined fact: ' + factId)); } } if (fact.isConstant()) { factValuePromise = Promise.resolve(fact.calculate(params, this)); } else { var cacheKey = fact.getCacheKey(params); var cacheVal = cacheKey && this.factResultsCache.get(cacheKey); if (cacheVal) { factValuePromise = Promise.resolve(cacheVal); (0, _debug2.default)('almanac::factValue cache hit for fact', { id: factId }); } else { (0, _debug2.default)('almanac::factValue cache miss, calculating', { id: factId }); factValuePromise = this._setFactValue(fact, params, fact.calculate(params, this)); } } if (path) { (0, _debug2.default)('condition::evaluate extracting object', { property: path }); return factValuePromise.then(function (factValue) { if (factValue != null && (typeof factValue === 'undefined' ? 'undefined' : _typeof(factValue)) === 'object') { var pathValue = _this.pathResolver(factValue, path); (0, _debug2.default)('condition::evaluate extracting object', { property: path, received: pathValue }); return pathValue; } else { (0, _debug2.default)('condition::evaluate could not compute object path of non-object', { path: path, factValue: factValue, type: typeof factValue === 'undefined' ? 'undefined' : _typeof(factValue) }); return factValue; } }); } return factValuePromise; } /** * Interprets value as either a primitive, or if a fact, retrieves the fact value */ }, { key: 'getValue', value: function getValue(value) { if (value != null && (typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object' && Object.prototype.hasOwnProperty.call(value, 'fact')) { // value = { fact: 'xyz' } return this.factValue(value.fact, value.params, value.path); } return Promise.resolve(value); } }]); return Almanac; }(); exports.default = Almanac;