New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

json-rules-engine

Package Overview
Dependencies
Maintainers
1
Versions
82
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

json-rules-engine - npm Package Compare versions

Comparing version 1.4.0 to 1.5.0

examples/08-fact-comparison.js

5

CHANGELOG.md

@@ -0,1 +1,6 @@

1.5.0 / 2017-03-12
==================
* Add fact comparison conditions
1.4.0 / 2017-01-23

@@ -2,0 +7,0 @@ ==================

25

dist/almanac.js

@@ -25,2 +25,5 @@ 'use strict';

var verbose = require('debug')('json-rules-engine-verbose');
var selectn = require('selectn');
var isPlainObject = require('lodash.isplainobject');
var warn = require('debug')('json-rules-engine:warn');

@@ -122,2 +125,3 @@ /**

* @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.

@@ -131,3 +135,4 @@ */

var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var fact, cacheKey, cacheVal;
var path = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
var fact, cacheKey, cacheVal, factValue;
return regeneratorRuntime.wrap(function _callee$(_context) {

@@ -153,5 +158,19 @@ while (1) {

verbose('almanac::factValue cache miss for fact:' + factId + '; calculating');
return _context.abrupt('return', this._setFactValue(fact, params, fact.calculate(params, this)));
_context.next = 9;
return this._setFactValue(fact, params, fact.calculate(params, this));
case 8:
case 9:
factValue = _context.sent;
if (path) {
if (isPlainObject(factValue) || Array.isArray(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 12:
case 'end':

@@ -158,0 +177,0 @@ return _context.stop();

151

dist/condition.js

@@ -7,20 +7,11 @@ 'use strict';

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 _params = require('params');
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"); }); }; }
var _params2 = _interopRequireDefault(_params);
var _selectn = require('selectn');
var _selectn2 = _interopRequireDefault(_selectn);
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 params = require('params');
var debug = require('debug')('json-rules-engine');
var warn = require('debug')('json-rules-engine:warn');
var isPlainObject = require('lodash.isplainobject');

@@ -45,3 +36,3 @@ var Condition = function () {

} else {
properties = (0, _params2.default)(properties).require(['fact', 'operator', 'value']);
properties = params(properties).require(['fact', 'operator', 'value']);
// a non-boolean condition does not have a priority by default. this allows

@@ -94,4 +85,51 @@ // priority to be dictated by the fact definition

/**
* 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 (!(isPlainObject(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
* @param {mixed} comparisonValue - fact result
* LHS OPER RHS
* <fact + params + path> <operator> <value>
*
* @param {Almanac} almanac
* @param {Map} operatorMap - map of available operators, keyed by operator name

@@ -103,26 +141,73 @@ * @returns {Boolean} - evaluation result

key: 'evaluate',
value: function evaluate(comparisonValue, operatorMap) {
// for any/all, simply comparisonValue that the sub-condition array evaluated truthy
if (this.isBooleanOperator()) return comparisonValue === true;
value: function () {
var _ref2 = _asyncToGenerator(regeneratorRuntime.mark(function _callee2(almanac, operatorMap) {
var op, rightHandSideValue, leftHandSideValue, evaluationResult;
return regeneratorRuntime.wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
if (almanac) {
_context2.next = 2;
break;
}
// if the fact has provided an object, and a path is specified, retrieve the object property
if (this.path) {
if ((typeof comparisonValue === 'undefined' ? 'undefined' : _typeof(comparisonValue)) === 'object') {
comparisonValue = (0, _selectn2.default)(this.path)(comparisonValue);
debug('condition::evaluate extracting object property ' + this.path + ', received: ' + comparisonValue);
} else {
warn('condition::evaluate could not compute object path(' + this.path + ') of non-object: ' + comparisonValue + ' <' + (typeof comparisonValue === 'undefined' ? 'undefined' : _typeof(comparisonValue)) + '>; continuing with ' + comparisonValue);
}
}
throw new Error('almanac required');
var op = operatorMap.get(this.operator);
if (!op) throw new Error('Unknown operator: ' + this.operator);
case 2:
if (operatorMap) {
_context2.next = 4;
break;
}
var evaluationResult = op.evaluate(comparisonValue, this.value);
if (!this.isBooleanOperator()) {
debug('condition::evaluate <' + comparisonValue + ' ' + this.operator + ' ' + this.value + '?> (' + evaluationResult + ')');
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;
evaluationResult = op.evaluate(leftHandSideValue, rightHandSideValue);
debug('condition::evaluate <' + leftHandSideValue + ' ' + this.operator + ' ' + rightHandSideValue + '?> (' + evaluationResult + ')');
return _context2.abrupt('return', evaluationResult);
case 18:
case 'end':
return _context2.stop();
}
}
}, _callee2, this);
}));
function evaluate(_x3, _x4) {
return _ref2.apply(this, arguments);
}
return evaluationResult;
}
return evaluate;
}()
/**

@@ -129,0 +214,0 @@ * Returns the boolean operator for the condition

@@ -164,18 +164,14 @@ 'use strict';

value: function prioritizeRules() {
var _this2 = this;
if (!this.prioritizedRules) {
(function () {
var ruleSets = _this2.rules.reduce(function (sets, rule) {
var priority = rule.priority;
if (!sets[priority]) sets[priority] = [];
sets[priority].push(rule);
return sets;
}, {});
_this2.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];
});
})();
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];
});
}

@@ -221,3 +217,3 @@ return this.prioritizedRules;

var _ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee(ruleArray, almanac) {
var _this3 = this;
var _this2 = this;

@@ -229,4 +225,4 @@ return regeneratorRuntime.wrap(function _callee$(_context) {

return _context.abrupt('return', Promise.all(ruleArray.map(function (rule) {
if (_this3.status !== RUNNING) {
debug('engine::run status:' + _this3.status + '; skipping remaining rules');
if (_this2.status !== RUNNING) {
debug('engine::run status:' + _this2.status + '; skipping remaining rules');
return;

@@ -237,7 +233,7 @@ }

if (rulePasses) {
_this3.emit('success', rule.event, almanac);
_this3.emit(rule.event.type, rule.event.params, _this3);
_this2.emit('success', rule.event, almanac);
_this2.emit(rule.event.type, rule.event.params, _this2);
almanac.factValue('success-events', { event: rule.event });
}
if (!rulePasses) _this3.emit('failure', rule, almanac);
if (!rulePasses) _this2.emit('failure', rule, almanac);
});

@@ -272,3 +268,3 @@ })));

var _ref2 = _asyncToGenerator(regeneratorRuntime.mark(function _callee2() {
var _this4 = this;
var _this3 = this;

@@ -294,3 +290,3 @@ var runtimeFacts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

cursor = cursor.then(function () {
return _this4.evaluateRules(set, almanac);
return _this3.evaluateRules(set, almanac);
}).catch(reject);

@@ -300,3 +296,3 @@ return cursor;

cursor.then(function () {
_this4.status = FINISHED;
_this3.status = FINISHED;
debug('engine::run completed');

@@ -303,0 +299,0 @@ resolve(almanac.factValue('success-events'));

@@ -208,3 +208,3 @@ 'use strict';

if (!condition.isBooleanOperator()) {
_context.next = 15;
_context.next = 16;
break;

@@ -236,21 +236,23 @@ }

case 13:
_context.next = 28;
// for booleans, rule passing is determined by the all/any result
passes = comparisonValue === true;
_context.next = 29;
break;
case 15:
_context.prev = 15;
_context.next = 18;
return almanac.factValue(condition.fact, condition.params);
case 16:
_context.prev = 16;
_context.next = 19;
return condition.evaluate(almanac, _this3.engine.operators, comparisonValue);
case 18:
comparisonValue = _context.sent;
_context.next = 28;
case 19:
passes = _context.sent;
_context.next = 29;
break;
case 21:
_context.prev = 21;
_context.t0 = _context['catch'](15);
case 22:
_context.prev = 22;
_context.t0 = _context['catch'](16);
if (!(_this3.engine.allowUndefinedFacts && _context.t0.code === 'UNDEFINED_FACT')) {
_context.next = 27;
_context.next = 28;
break;

@@ -260,21 +262,10 @@ }

passes = false;
_context.next = 28;
_context.next = 29;
break;
case 27:
case 28:
throw _context.t0;
case 28:
if (!(passes === undefined)) {
_context.next = 32;
break;
}
case 29:
_context.next = 31;
return condition.evaluate(comparisonValue, _this3.engine.operators);
case 31:
passes = _context.sent;
case 32:
if (passes) {

@@ -287,3 +278,3 @@ _this3.emit('success', _this3.event, almanac);

case 34:
case 31:
case 'end':

@@ -293,3 +284,3 @@ return _context.stop();

}
}, _callee, _this3, [[15, 21]]);
}, _callee, _this3, [[16, 22]]);
}));

@@ -296,0 +287,0 @@

@@ -15,10 +15,10 @@ # Almanac

### almanac.factValue(Fact fact, Object params) -> Promise
### almanac.factValue(Fact fact, Object params, String path) -> Promise
Computes the value of the provided fact + params.
Computes the value of the provided fact + params. If "path" is provided, it will be used as a property accessor on the fact's return object.
```js
almanac
.factValue('account-information', { accountId: 1 })
.then( values => console.log(values))
.factValue('account-information', { accountId: 1 }, '.balance')
.then( value => console.log(value))
```

@@ -41,3 +41,3 @@

The [computed-facts](../examples/computed-facts) example demonstrates a real world application of this technique.
The [fact-dependency](../examples/04-fact-dependency.js) example demonstrates a real world application of this technique.

@@ -102,3 +102,3 @@ For example, say there were two facts: _is-funded-account_ and _account-balance_. Both facts depend on the same _account-information_ data set.

define a fact value, it's important that rule be run prior to other rules that reference the fact. To
learn more about setting rule priorties, see the [rule documentation](./rule.md).
learn more about setting rule priorities, see the [rule documentation](./rules.md).

@@ -105,0 +105,0 @@ ```js

@@ -37,3 +37,3 @@ # Engine

// facts computed via function
engine.addFact('account-type', function getAccountType() {
engine.addFact('account-type', function getAccountType(params, almanac) {
// ...

@@ -43,3 +43,3 @@ })

// facts with options:
engine.addFact('account-type', function getAccountType() {
engine.addFact('account-type', function getAccountType(params, almanac) {
// ...

@@ -87,14 +87,16 @@ }, { cache: false, priority: 500 })

// and to use the operator...
rule.setConditions({
all: [
{
fact: 'username',
operator: 'startsWithLetter' // reference the operator name in the rule
value: 'a'
}
]
})
let rule = new Rule(
conditions: {
all: [
{
fact: 'username',
operator: 'startsWithLetter', // reference the operator name in the rule
value: 'a'
}
]
}
)
```
See the [operator example](../examples/custom-operators.js)
See the [operator example](../examples/06-custom-operators.js)

@@ -134,15 +136,17 @@ ### engine.run([Object facts], [Object options]) -> Promise (Events)

Listens for events emitted as rules are being evaluated. "event" is determined by [rule.setEvent](./rules.md#seteventobject-event).
Listens for events emitted as rules are being evaluated. "event" is determined by the [rule event](./rules.md#Events).
```js
rule.setEvent({
type: 'my-event',
params: {
id: 1
let rule = new Rule({
event: {
type: 'my-event',
params: {
customValue: 'my-custom-value'
}
}
})
// whenever rule is evaluated and the conditions pass, 'my-event' will trigger
// whenever rule is evaluated and conditions pass, 'my-event' will trigger
engine.on('my-event', function(params) {
console.log(params) // id: 1
console.log(params) // { customValue: 'my-custom-value' }
})

@@ -149,0 +153,0 @@ ```

# Facts
Facts are methods or constants registered with the engine prior to runtime and referenced within rule conditions. Each fact method should be a pure function that may return a computed value or promise.
Facts are methods or constants registered with the engine prior to runtime and referenced within rule conditions. Each fact method should be a pure function that may return a either computed value, or promise that resolves to a computed value.
As rule conditions are evaluated during runtime, they retrieve fact values dynamically and use the condition _operator_ to compare the fact result with the condition _value_.

@@ -8,3 +8,3 @@

### constructor(String id, Constant|Function, [Object options]) -> instance
### constructor(String id, Constant|Function(Object params, Almanac almanac), [Object options]) -> instance

@@ -16,3 +16,3 @@ ```js

// dynamic facts
let fact = new Fact('account-type', function getAccountType() {
let fact = new Fact('account-type', (params, almanac) => {
// ...

@@ -22,3 +22,3 @@ })

// facts with options:
engine.addFact('account-type', function getAccountType() {
engine.addFact('account-type', (params, almanac) => {
// ...

@@ -25,0 +25,0 @@ }, { cache: false, priority: 500 })

@@ -12,73 +12,214 @@ # Rules

```js
let options = {
conditions: {
all: [
{
fact: 'my-fact',
operator: 'equal',
value: 'some-value'
}
]
},
event: {
type: 'my-event',
params: {
customProperty: 'customValue'
}
},
priority: 1, // optional, default: 1
onSuccess: function (event, almanac) {}, // optional
onFailure: function (event, almanac) {}, // optional
}
let rule = new Rule(options)
```
**options.conditions** : `[Object]` Rule conditions object
**options.event** : `[Object]` Sets the `.on('success')` and `on('failure')` event argument emitted whenever the rule passes. Event objects must have a ```type``` property, and an optional ```params``` property.
**options.priority** : `[Number, default 1]` Dictates when rule should be run, relative to other rules. Higher priority rules are run before lower priority rules. Rules with the same priority are run in parallel. Priority must be a positive, non-zero integer.
**options.onSuccess** : `[Function(Object event, Almanac almanac)]` Registers callback with the rule's `on('success')` listener. The rule's `event` property and the current [Almanac](./almanac.md) are passed as arguments.
**options.onFailure** : `[Function(Object event, Almanac almanac)]` Registers callback with the rule's `on('failure')` listener. The rule's `event` property and the current [Almanac](./almanac.md) are passed as arguments.
### setConditions(Array conditions)
Assigns the rule conditions to the provided argument. The root condition must be a boolean operator (```all``` or ```any```)
Helper for setting rule conditions. Alternative to passing the `conditions` option to the rule constructor.
### setEvent(Object event)
Helper for setting rule event. Alternative to passing the `event` option to the rule constructor.
### setPriority(Integer priority = 1)
Helper for setting rule priority. Alternative to passing the `priority` option to the rule constructor.
### toJSON(Boolean stringify = true)
Serializes the rule into a JSON string. Often used when persisting rules.
```js
rule.setConditions({
all: [
{
fact: 'revenue',
operator: 'greaterThanInclusive'
value: 1000000
}
]
})
let jsonString = rule.toJSON() // string: '{"conditions":{"all":[]},"priority":50 ...
// if fact returns an object or array, providing a "path" key can be used for property traversal
rule.setConditions({
all: [
{
fact: 'userData', // 'userData' fact returns { profile: { addresses: [{ city: 'new york' }]}}
operator: 'equal'
value: 'new york',
path: '.profile.addresses[0].city' // "path" navigates the data structure, down to the "city" property
}
]
})
let rule = new Rule(jsonString) // restored rule; same conditions, priority, event
// without stringifying
let jsonObject = rule.toJSON(false) // object: {conditions:{ all: [] }, priority: 50 ...
```
See the [fact dependency example](../examples/fact-dependency.js)
## Conditions
### setEvent(Object event)
Rule conditions are a combination of facts, operators, and values that determine whether the rule is a `success` or a `failure`.
Sets the event the engine should emit when the rule conditions pass. All events must have a ```type``` property, which denotes the event name to emit when the rule passes.
### Basic conditions
Optionally, a ```params``` property may be provided as well. ```params``` will be passed to the event as an argument.
The simplest form of a condition consists of a `fact`, an `operator`, and a `value`. When the engine runs, the operator is used to compare the fact against the value.
```js
rule.setEvent({
type: 'string', //required
params: { object } //optional
// my-fact <= 1
let rule = new Rule({
conditions: {
all: [
{
fact: 'my-fact',
operator: 'lessThanInclusive',
value: 1
}
]
}
})
```
### setPriority(Integer priority = 1)
See the [hello-world](../examples/01-hello-world.js) example.
Sets the rule priority. Priority must be a positive, non-zero integer. The higher the priority, the sooner the rule will run. If no priority is assigned to a Rule, it will receive a default priority of 1.
### Boolean expressions: `all` and `any`
Each rule's conditions *must* have either an `all` or an `any` operator at its root, containing an array of conditions. The `all` operator specifies that all conditions contained within must be truthy for the rule to be considered a `success`. The `any` operator only requires one condition to be truthy for the rule to succeed.
```js
rule.setPriority(100)
// all:
let rule = new Rule({
conditions: {
all: [
{ /* condition 1 */ },
{ /* condition 2 */ },
{ /* condition n */ },
]
}
})
// any:
let rule = new Rule({
conditions: {
any: [
{ /* condition 1 */ },
{ /* condition 2 */ },
{ /* condition n */ },
{
all: [ /* more conditions */ ]
}
]
}
})
```
### toJSON(Boolean stringify = true)
Notice in the second example how `all` and `any` can be nested within one another to produce complex boolean expressions. See the [nested-boolean-logic](../examples/02-nested-boolean-logic.js) example.
Serializes the rule into a JSON string. Usually used when persisting rules.
### Condition helpers: `params`
Sometimes facts require additional input to perform calculations. For this, the `params` property is passed as an argument to the fact handler. `params` essentially functions as fact arguments, enabling fact handlers to be more generic and reusable.
```js
let jsonString = rule.toJSON() // string: '{"conditions":{"all":[]},"priority":50 ...
// product-price retrieves any product's price based on the "productId" in "params"
engine.addFact('product-price', function (params, almanac) {
return productLoader(params.productId) // loads the "widget" product
.then(product => product.price)
})
let rule = new Rule(jsonString) // restored rule; same conditions, priority, event
// identifies whether the current widget price is above $100
let rule = new Rule({
conditions: {
all: [
{
fact: 'product-price',
params: {
productId: 'widget' // specifies which product to load
},
operator: 'greaterThan',
value: 100
}
]
}
})
```
// without stringifying
let jsonObject = rule.toJSON(false) // object: {conditions:{ all: [] }, priority: 50 ...
See the [dynamic-facts](../examples/03-dynamic-facts) example
### Condition helpers: `path`
In the `params` example above, the dynamic fact handler loads an object, then returns a specific object property. For more complex data structures, writing a separate fact handler for each object property can sometimes become unwieldy.
To alleviate this overhead, a `path` property is provided for traversing objects and arrays returned by facts. The example above becomes simpler, and only one fact handler must be written by the developer to handle any number of properties.
```js
// product-price retrieves any product's price based on the "productId" in "params"
engine.addFact('product-price', function (params, almanac) {
// NOTE: `then` is not required; .price is specified via "path" below
return productLoader(params.productId)
})
// identifies whether the current widget price is above $100
let rule = new Rule({
conditions: {
all: [
{
fact: 'product-price',
params: {
productId: 'widget',
// Complex accessor are supported, e.g. '.profile.addresses[0].city'
path: '.price'
},
operator: 'greaterThan',
value: 100
}
]
}
})
```
See the [fact-dependency](../examples/04-fact-dependency.js) example
### Events
### Comparing facts
Listen for 'success' and 'failure' events emitted when rule is evaluated.
Sometimes it is necessary to compare facts against others facts. This can be accomplished by nesting the second fact within the `value` property. This second fact has access to the same `params` and `path` helpers as the primary fact.
```js
// identifies whether the current widget price is above a maximum
let rule = new Rule({
conditions: {
all: [
// widget-price > budget
{
fact: 'product-price',
params: {
productId: 'widget',
path: '.price'
},
operator: 'greaterThan',
// "value" contains a fact
value: {
fact: 'budget' // "params" and "path" helpers are available as well
}
}
]
}
})
```
See the [fact-comparison](../examples/08-fact-comparison.js) example
## Events
Listen for `success` and `failure` events emitted when rule is evaluated.
#### ```rule.on('success', Function(Object event, Almanac almanac))```

@@ -95,3 +236,3 @@

Companion to 'success', except fires when the rule fails.
Companion to `success`, except fires when the rule fails.

@@ -104,3 +245,3 @@ ```js

## Conditions
## Operators

@@ -107,0 +248,0 @@ Each rule condition must begin with a boolean operator(```all``` or ```any```) at its root.

@@ -25,19 +25,20 @@ 'use strict'

*/
let rule = new Rule()
// define the 'conditions' for when "hello world" should display
rule.setConditions({
all: [{
fact: 'displayMessage',
operator: 'equal',
value: true
}]
})
// define the 'event' that will fire when the condition evaluates truthy
rule.setEvent({
type: 'message',
params: {
data: 'hello-world!'
let rule = new Rule({
// define the 'conditions' for when "hello world" should display
conditions: {
all: [{
fact: 'displayMessage',
operator: 'equal',
value: true
}]
},
// define the 'event' that will fire when the condition evaluates truthy
event: {
type: 'message',
params: {
data: 'hello-world!'
}
}
})
// add rule to engine

@@ -49,3 +50,3 @@ engine.addRule(rule)

* Fact values do NOT need to be known at engine runtime; see the
* examples for how to pull in data asynchronously as the engine runs
* 03-dynamic-facts.js example for how to pull in data asynchronously during runtime
*/

@@ -52,0 +53,0 @@ let facts = { displayMessage: true }

{
"name": "json-rules-engine",
"version": "1.4.0",
"version": "1.5.0",
"description": "Rules Engine expressed in simple json",

@@ -48,3 +48,3 @@ "main": "dist/index.js",

"babel-core": "~6.9.1",
"babel-eslint": "^5.0.0-beta6",
"babel-eslint": "^7.1.1",
"babel-loader": "~6.2.4",

@@ -56,6 +56,7 @@ "babel-plugin-transform-async-to-generator": "^6.4.6",

"babel-register": "^6.4.3",
"chai": "3.4.1",
"chai-as-promised": "^5.2.0",
"chai": "^3.5.0",
"chai-as-promised": "^6.0.0",
"colors": "~1.1.2",
"mocha": "2.3.4",
"dirty-chai": "1.2.2",
"mocha": "^3.2.0",
"regenerator": "~0.8.46",

@@ -65,6 +66,7 @@ "sinon": "^1.17.2",

"snazzy": "^2.0.1",
"standard": "^5.4.1"
"standard": "^9.0.0"
},
"dependencies": {
"debug": "2.2.0",
"lodash.isplainobject": "4.0.6",
"object-hash": "1.1.5",

@@ -71,0 +73,0 @@ "params": "0.1.1",

@@ -210,1 +210,4 @@ ![json-rules-engine](http://i.imgur.com/MAzq7l2.png)

```
## License
[ISC](./LICENSE)

@@ -116,3 +116,3 @@ import { Fact } from '../src/index'

let promise = almanac._setFactValue(fact, {}, FACT_VALUE)
expect(almanac.factResultsCache.values().next().value).to.be.undefined
expect(almanac.factResultsCache.values().next().value).to.be.undefined()
promise.then(value => expect(value).to.equal(FACT_VALUE)).then(_ => done()).catch(done)

@@ -139,3 +139,3 @@ })

almanac.factValue('foo')
expect(factSpy).to.have.been.calledThrice
expect(factSpy).to.have.been.calledThrice()
})

@@ -148,5 +148,5 @@

almanac.factValue('foo')
expect(factSpy).to.have.been.calledOnce
expect(factSpy).to.have.been.calledOnce()
})
})
})

@@ -5,2 +5,4 @@ 'use strict'

import defaultOperators from '../src/engine-default-operators'
import Almanac from '../src/almanac'
import Fact from '../src/fact'

@@ -42,2 +44,20 @@ let operators = new Map()

describe('toJSON', () => {
it('converts the condition into a json string', () => {
let properties = factories.condition({
fact: 'age',
value: {
fact: 'weight',
params: {
unit: 'lbs'
},
path: '.value'
}
})
let condition = new Condition(properties)
let json = condition.toJSON()
expect(json).to.equal('{"operator":"equal","value":{"fact":"weight","params":{"unit":"lbs"},"path":".value"},"fact":"age"}')
})
})
describe('evaluate', () => {

@@ -49,109 +69,131 @@ let conditionBase = factories.condition({

let condition
function setup (options) {
let almanac
function setup (options, factValue) {
if (typeof factValue === 'undefined') factValue = 1
let properties = Object.assign({}, conditionBase, options)
condition = new Condition(properties)
let fact = new Fact(conditionBase.fact, factValue)
almanac = new Almanac(new Map([[fact.id, fact]]))
}
it('evaluates "equal"', () => {
setup({ operator: 'equal' })
expect(condition.evaluate(50, operators)).to.equal(true)
expect(condition.evaluate(5, operators)).to.equal(false)
context('validations', () => {
beforeEach(() => setup())
it('throws when missing an almanac', () => {
return expect(condition.evaluate(undefined, operators)).to.be.rejectedWith('almanac required')
})
it('throws when missing operators', () => {
return expect(condition.evaluate(almanac, undefined)).to.be.rejectedWith('operatorMap required')
})
it('throws when run against a boolean operator', () => {
condition.all = []
return expect(condition.evaluate(almanac, operators)).to.be.rejectedWith('Cannot evaluate() a boolean condition')
})
})
it('evaluates "notEqual"', () => {
setup({ operator: 'notEqual' })
expect(condition.evaluate(50, operators)).to.equal(false)
expect(condition.evaluate(5, operators)).to.equal(true)
it('evaluates "equal"', async () => {
setup({ operator: 'equal' }, 50)
expect(await condition.evaluate(almanac, operators, 50)).to.equal(true)
setup({ operator: 'equal' }, 5)
expect(await condition.evaluate(almanac, operators, 5)).to.equal(false)
})
it('evaluates "in"', () => {
setup({
operator: 'in',
value: [5, 10, 15, 20]
})
expect(condition.evaluate(15, operators)).to.equal(true)
expect(condition.evaluate(99, operators)).to.equal(false)
it('evaluates "notEqual"', async () => {
setup({ operator: 'notEqual' }, 50)
expect(await condition.evaluate(almanac, operators)).to.equal(false)
setup({ operator: 'notEqual' }, 5)
expect(await condition.evaluate(almanac, operators)).to.equal(true)
})
it('evaluates "contains"', () => {
setup({
operator: 'contains',
value: 10
})
expect(condition.evaluate([5, 10, 15], operators)).to.equal(true)
expect(condition.evaluate([1, 2, 3], operators)).to.equal(false)
it('evaluates "in"', async () => {
setup({ operator: 'in', value: [5, 10, 15, 20] }, 15)
expect(await condition.evaluate(almanac, operators)).to.equal(true)
setup({ operator: 'in', value: [5, 10, 15, 20] }, 99)
expect(await condition.evaluate(almanac, operators)).to.equal(false)
})
it('evaluates "doesNotContain"', () => {
setup({
operator: 'doesNotContain',
value: 10
})
expect(condition.evaluate([5, 10, 15], operators)).to.equal(false)
expect(condition.evaluate([1, 2, 3], operators)).to.equal(true)
it('evaluates "contains"', async () => {
setup({ operator: 'contains', value: 10 }, [5, 10, 15])
expect(await condition.evaluate(almanac, operators)).to.equal(true)
setup({ operator: 'contains', value: 10 }, [1, 2, 3])
expect(await condition.evaluate(almanac, operators)).to.equal(false)
})
it('evaluates "notIn"', () => {
setup({
operator: 'notIn',
value: [5, 10, 15, 20]
})
expect(condition.evaluate(15, operators)).to.equal(false)
expect(condition.evaluate(99, operators)).to.equal(true)
it('evaluates "doesNotContain"', async () => {
setup({ operator: 'doesNotContain', value: 10 }, [5, 10, 15])
expect(await condition.evaluate(almanac, operators)).to.equal(false)
setup({ operator: 'doesNotContain', value: 10 }, [1, 2, 3])
expect(await condition.evaluate(almanac, operators)).to.equal(true)
})
it('evaluates "lessThan"', () => {
setup({ operator: 'lessThan' })
expect(condition.evaluate(49, operators)).to.equal(true)
expect(condition.evaluate(50, operators)).to.equal(false)
expect(condition.evaluate(51, operators)).to.equal(false)
it('evaluates "notIn"', async () => {
setup({ operator: 'notIn', value: [5, 10, 15, 20] }, 15)
expect(await condition.evaluate(almanac, operators)).to.equal(false)
setup({ operator: 'notIn', value: [5, 10, 15, 20] }, 99)
expect(await condition.evaluate(almanac, operators)).to.equal(true)
})
it('evaluates "lessThanInclusive"', () => {
setup({ operator: 'lessThanInclusive' })
expect(condition.evaluate(49, operators)).to.equal(true)
expect(condition.evaluate(50, operators)).to.equal(true)
expect(condition.evaluate(51, operators)).to.equal(false)
it('evaluates "lessThan"', async () => {
setup({ operator: 'lessThan' }, 49)
expect(await condition.evaluate(almanac, operators)).to.equal(true)
setup({ operator: 'lessThan' }, 50)
expect(await condition.evaluate(almanac, operators)).to.equal(false)
setup({ operator: 'lessThan' }, 51)
expect(await condition.evaluate(almanac, operators)).to.equal(false)
})
it('evaluates "greaterThan"', () => {
setup({ operator: 'greaterThan' })
expect(condition.evaluate(51, operators)).to.equal(true)
expect(condition.evaluate(49, operators)).to.equal(false)
expect(condition.evaluate(50, operators)).to.equal(false)
it('evaluates "lessThanInclusive"', async () => {
setup({ operator: 'lessThanInclusive' }, 49)
expect(await condition.evaluate(almanac, operators)).to.equal(true)
setup({ operator: 'lessThanInclusive' }, 50)
expect(await condition.evaluate(almanac, operators)).to.equal(true)
setup({ operator: 'lessThanInclusive' }, 51)
expect(await condition.evaluate(almanac, operators)).to.equal(false)
})
it('evaluates "greaterThanInclusive"', () => {
setup({operator: 'greaterThanInclusive'})
expect(condition.evaluate(51, operators)).to.equal(true)
expect(condition.evaluate(50, operators)).to.equal(true)
expect(condition.evaluate(49, operators)).to.equal(false)
it('evaluates "greaterThan"', async () => {
setup({ operator: 'greaterThan' }, 51)
expect(await condition.evaluate(almanac, operators)).to.equal(true)
setup({ operator: 'greaterThan' }, 49)
expect(await condition.evaluate(almanac, operators)).to.equal(false)
setup({ operator: 'greaterThan' }, 50)
expect(await condition.evaluate(almanac, operators)).to.equal(false)
})
it('evaluates "greaterThanInclusive"', async () => {
setup({ operator: 'greaterThanInclusive' }, 51)
expect(await condition.evaluate(almanac, operators)).to.equal(true)
setup({ operator: 'greaterThanInclusive' }, 50)
expect(await condition.evaluate(almanac, operators)).to.equal(true)
setup({ operator: 'greaterThanInclusive' }, 49)
expect(await condition.evaluate(almanac, operators)).to.equal(false)
})
describe('invalid comparisonValues', () => {
it('returns false when using contains or doesNotContain with a non-array', () => {
setup({operator: 'contains'})
expect(condition.evaluate(null, operators)).to.equal(false)
setup({operator: 'doesNotContain'})
expect(condition.evaluate(null, operators)).to.equal(false)
it('returns false when using contains or doesNotContain with a non-array', async () => {
setup({ operator: 'contains' }, null)
expect(await condition.evaluate(almanac, operators)).to.equal(false)
setup({ operator: 'doesNotContain' }, null)
expect(await condition.evaluate(almanac, operators)).to.equal(false)
})
it('returns false when using comparison operators with null', () => {
setup({operator: 'lessThan'})
expect(condition.evaluate(null, operators)).to.equal(false)
setup({operator: 'lessThanInclusive'})
expect(condition.evaluate(null, operators)).to.equal(false)
setup({operator: 'greaterThan'})
expect(condition.evaluate(null, operators)).to.equal(false)
setup({operator: 'greaterThanInclusive'})
expect(condition.evaluate(null, operators)).to.equal(false)
it('returns false when using comparison operators with null', async () => {
setup({ operator: 'lessThan' }, null)
expect(await condition.evaluate(almanac, operators)).to.equal(false)
setup({ operator: 'lessThanInclusive' }, null)
expect(await condition.evaluate(almanac, operators)).to.equal(false)
setup({ operator: 'greaterThan' }, null)
expect(await condition.evaluate(almanac, operators)).to.equal(false)
setup({ operator: 'greaterThanInclusive' }, null)
expect(await condition.evaluate(almanac, operators)).to.equal(false)
})
it('returns false when using comparison operators with non-numbers', () => {
setup({operator: 'lessThan'})
expect(condition.evaluate('non-number', operators)).to.equal(false)
setup({operator: 'lessThan'})
expect(condition.evaluate(undefined, operators)).to.equal(false)
setup({operator: 'lessThan'})
expect(condition.evaluate([], operators)).to.equal(false)
setup({operator: 'lessThan'})
expect(condition.evaluate({}, operators)).to.equal(false)
it('returns false when using comparison operators with non-numbers', async () => {
setup({operator: 'lessThan'}, 'non-number')
expect(await condition.evaluate(almanac, operators)).to.equal(false)
setup({operator: 'lessThan'}, null)
expect(await condition.evaluate(almanac, operators)).to.equal(false)
setup({operator: 'lessThan'}, [])
expect(await condition.evaluate(almanac, operators)).to.equal(false)
setup({operator: 'lessThan'}, {})
expect(await condition.evaluate(almanac, operators)).to.equal(false)
})

@@ -162,17 +204,23 @@ })

describe('objects', () => {
it('extracts the object property values using its "path" property', () => {
let factData = [{ id: 50 }, { id: 60 }]
it('extracts the object property values using its "path" property', async () => {
let condition = new Condition({operator: 'equal', path: '[0].id', fact: 'age', value: 50})
expect(condition.evaluate(factData, operators)).to.equal(true)
let ageFact = new Fact('age', [{ id: 50 }, { id: 60 }])
let facts = new Map([[ageFact.id, ageFact]])
let almanac = new Almanac(facts)
expect(await condition.evaluate(almanac, operators)).to.equal(true)
condition.value = 100 // negative case
expect(condition.evaluate(factData, operators)).to.equal(false)
expect(await condition.evaluate(almanac, operators)).to.equal(false)
})
it('ignores "path" when non-objects are returned by the fact', () => {
it('ignores "path" when non-objects are returned by the fact', async () => {
let ageFact = new Fact('age', 50)
let facts = new Map([[ageFact.id, ageFact]])
let almanac = new Almanac(facts)
let condition = new Condition({operator: 'equal', path: '[0].id', fact: 'age', value: 50})
expect(condition.evaluate(50, operators)).to.equal(true)
expect(await condition.evaluate(almanac, operators, 50)).to.equal(true)
condition.value = 100 // negative case
expect(condition.evaluate(50, operators)).to.equal(false)
expect(await condition.evaluate(almanac, operators, 50)).to.equal(false)
})

@@ -241,3 +289,3 @@ })

it('recursively parses nested conditions', () => {
expect(() => new Condition(complexCondition())).to.not.throw
expect(() => new Condition(complexCondition())).to.not.throw()
})

@@ -244,0 +292,0 @@

@@ -38,4 +38,4 @@ 'use strict'

await engine.run()
expect(eventSpy).to.have.been.calledThrice
expect(factSpy).to.have.been.calledOnce
expect(eventSpy).to.have.been.calledThrice()
expect(factSpy).to.have.been.calledOnce()
})

@@ -46,5 +46,5 @@

await engine.run()
expect(eventSpy).to.have.been.calledThrice
expect(factSpy).to.have.been.calledThrice
expect(eventSpy).to.have.been.calledThrice()
expect(factSpy).to.have.been.calledThrice()
})
})

@@ -53,7 +53,7 @@ 'use strict'

await engine.run()
expect(eventSpy).to.have.been.calledOnce
expect(ageStub).to.have.been.calledOnce
expect(segmentStub).to.not.have.been.called
expect(eventSpy).to.have.been.calledOnce()
expect(ageStub).to.have.been.calledOnce()
expect(segmentStub).to.not.have.been.called()
})
})
})

@@ -54,7 +54,7 @@ 'use strict'

await engine.run()
expect(failureSpy).to.have.been.called
expect(eventSpy).to.not.have.been.called
expect(ageStub).to.have.been.calledOnce
expect(segmentStub).to.not.have.been.called
expect(accountTypeStub).to.not.have.been.called
expect(failureSpy).to.have.been.called()
expect(eventSpy).to.not.have.been.called()
expect(ageStub).to.have.been.calledOnce()
expect(segmentStub).to.not.have.been.called()
expect(accountTypeStub).to.not.have.been.called()
})

@@ -67,7 +67,7 @@

await engine.run()
expect(failureSpy).to.have.been.called
expect(eventSpy).to.not.have.been.called
expect(ageStub).to.have.been.calledOnce
expect(segmentStub).to.have.been.calledOnce
expect(accountTypeStub).to.not.have.been.called
expect(failureSpy).to.have.been.called()
expect(eventSpy).to.not.have.been.called()
expect(ageStub).to.have.been.calledOnce()
expect(segmentStub).to.have.been.calledOnce()
expect(accountTypeStub).to.not.have.been.called()
})

@@ -101,7 +101,7 @@

await engine.run()
expect(failureSpy).to.have.been.called
expect(eventSpy).to.not.have.been.called
expect(ageStub).to.have.been.calledOnce
expect(segmentStub).to.have.been.calledOnce
expect(accountTypeStub).to.not.have.been.called
expect(failureSpy).to.have.been.called()
expect(eventSpy).to.not.have.been.called()
expect(ageStub).to.have.been.calledOnce()
expect(segmentStub).to.have.been.calledOnce()
expect(accountTypeStub).to.not.have.been.called()
})

@@ -131,7 +131,7 @@ })

await engine.run()
expect(eventSpy).to.have.been.calledOnce
expect(failureSpy).to.not.have.been.called
expect(ageStub).to.have.been.calledOnce
expect(segmentStub).to.not.have.been.called
expect(accountTypeStub).to.not.have.been.called
expect(eventSpy).to.have.been.calledOnce()
expect(failureSpy).to.not.have.been.called()
expect(ageStub).to.have.been.calledOnce()
expect(segmentStub).to.not.have.been.called()
expect(accountTypeStub).to.not.have.been.called()
})

@@ -144,7 +144,7 @@

await engine.run()
expect(eventSpy).to.have.been.calledOnce
expect(failureSpy).to.not.have.been.called
expect(ageStub).to.have.been.calledOnce
expect(segmentStub).to.have.been.calledOnce
expect(accountTypeStub).to.not.have.been.called
expect(eventSpy).to.have.been.calledOnce()
expect(failureSpy).to.not.have.been.called()
expect(ageStub).to.have.been.calledOnce()
expect(segmentStub).to.have.been.calledOnce()
expect(accountTypeStub).to.not.have.been.called()
})

@@ -178,7 +178,7 @@

await engine.run()
expect(failureSpy).to.not.have.been.called
expect(eventSpy).to.have.been.called
expect(ageStub).to.have.been.calledOnce
expect(segmentStub).to.have.been.calledOnce
expect(accountTypeStub).to.not.have.been.called
expect(failureSpy).to.not.have.been.called()
expect(eventSpy).to.have.been.called()
expect(ageStub).to.have.been.calledOnce()
expect(segmentStub).to.have.been.calledOnce()
expect(accountTypeStub).to.not.have.been.called()
})

@@ -185,0 +185,0 @@ })

@@ -95,4 +95,4 @@ 'use strict'

await engine.run()
expect(successSpy).to.have.been.called
expect(failureSpy).to.not.have.been.called
expect(successSpy).to.have.been.called()
expect(failureSpy).to.not.have.been.called()
})

@@ -110,4 +110,4 @@

await engine.run()
expect(successSpy).to.not.have.been.called
expect(failureSpy).to.have.been.called
expect(successSpy).to.not.have.been.called()
expect(failureSpy).to.have.been.called()
})

@@ -130,3 +130,3 @@ })

await engine.run()
expect(successSpy).to.not.have.been.called
expect(successSpy).to.not.have.been.called()
})

@@ -160,3 +160,3 @@ })

await engine.run()
expect(successSpy).to.not.have.been.called
expect(successSpy).to.not.have.been.called()
})

@@ -217,3 +217,3 @@

await engine.run()
expect(successSpy).to.have.been.called
expect(successSpy).to.have.been.called()
})

@@ -230,3 +230,3 @@ })

await engine.run()
expect(successSpy).to.have.been.called
expect(successSpy).to.have.been.called()
})

@@ -241,5 +241,5 @@

await engine.run()
expect(successSpy).to.not.have.been.called
expect(successSpy).to.not.have.been.called()
})
})
})
'use strict'
import engineFactory from '../src/index'
import { Fact } from '../src/index'
import engineFactory, { Fact } from '../src/index'
import sinon from 'sinon'

@@ -63,5 +62,5 @@

await engine.run()
expect(eventSpy).to.have.been.calledOnce
expect(demographicDataSpy).to.have.been.calledOnce
expect(demographicSpy).to.have.been.calledTwice
expect(eventSpy).to.have.been.calledOnce()
expect(demographicDataSpy).to.have.been.calledOnce()
expect(demographicSpy).to.have.been.calledTwice()
})

@@ -86,8 +85,8 @@ })

await engine.run()
expect(eventSpy).to.have.been.calledTwice
expect(demographicDataSpy).to.have.been.calledOnce
expect(demographicSpy).to.have.been.calledTwice
expect(demographicDataSpy).to.have.been.calledOnce
expect(eventSpy).to.have.been.calledTwice()
expect(demographicDataSpy).to.have.been.calledOnce()
expect(demographicSpy).to.have.been.calledTwice()
expect(demographicDataSpy).to.have.been.calledOnce()
})
})
})

@@ -36,4 +36,4 @@ 'use strict'

await engine.run()
expect(failureSpy).to.not.have.been.calledOnce
expect(failureSpy).to.not.have.been.calledOnce()
})
})

@@ -47,4 +47,4 @@ 'use strict'

await engine.run()
expect(eventSpy).to.have.been.calledOnce
expect(factSpy).to.have.been.calledThrice
expect(eventSpy).to.have.been.calledOnce()
expect(factSpy).to.have.been.calledThrice()
})

@@ -55,4 +55,4 @@

await engine.run()
expect(eventSpy).to.have.been.calledOnce
expect(factSpy).to.have.been.calledOnce
expect(eventSpy).to.have.been.calledOnce()
expect(factSpy).to.have.been.calledOnce()
})

@@ -75,6 +75,6 @@ })

await engine.run()
expect(eventSpy).to.have.been.calledTwice
expect(factSpy).to.have.been.calledOnce
expect(eventSpy).to.have.been.calledTwice()
expect(factSpy).to.have.been.calledOnce()
})
})
})

@@ -55,3 +55,3 @@ 'use strict'

await engine.run()
expect(eventSpy).to.have.been.calledOnce
expect(eventSpy).to.have.been.calledOnce()
})

@@ -65,3 +65,3 @@

await engine.run()
expect(eventSpy).to.not.have.been.calledOnce
expect(eventSpy).to.not.have.been.calledOnce()
})

@@ -106,3 +106,3 @@ })

await engine.run()
expect(eventSpy).to.have.been.calledOnce
expect(eventSpy).to.have.been.calledOnce()
})

@@ -116,3 +116,3 @@

await engine.run()
expect(eventSpy).to.not.have.been.calledOnce
expect(eventSpy).to.not.have.been.calledOnce()
})

@@ -150,3 +150,3 @@ })

await engine.run()
expect(eventSpy).to.have.been.calledOnce
expect(eventSpy).to.have.been.calledOnce()
})

@@ -159,5 +159,5 @@

await engine.run()
expect(eventSpy).to.not.have.been.calledOnce
expect(eventSpy).to.not.have.been.calledOnce()
})
})
})

@@ -37,3 +37,3 @@ 'use strict'

setup()
expect(engine.prioritizedRules).to.be.null
expect(engine.prioritizedRules).to.be.null()
engine.prioritizeRules()

@@ -50,4 +50,4 @@ expect(engine.prioritizedRules.length).to.equal(3)

engine.addRule(factories.rule())
expect(engine.prioritizedRules).to.be.null
expect(engine.prioritizedRules).to.be.null()
})
})

@@ -39,3 +39,3 @@ 'use strict'

await Promise.all([50, 10, 12, 30, 14, 15, 25].map((age) => engine.run({age})))
expect(eventSpy).to.have.been.calledThrice
expect(eventSpy).to.have.been.calledThrice()
})

@@ -47,7 +47,7 @@

await engine.run({ age: 85 }) // override 'age' with runtime fact
expect(eventSpy).to.have.been.calledTwice
expect(eventSpy).to.have.been.calledTwice()
eventSpy.reset()
await engine.run() // no runtime fact; revert to age: 30
expect(eventSpy).to.have.been.calledOnce
expect(eventSpy).to.have.been.calledOnce()

@@ -54,0 +54,0 @@ eventSpy.reset()

'use strict'
import sinon from 'sinon'
import engineFactory from '../src/index'
import { Fact } from '../src/index'
import { Rule } from '../src/index'
import { Operator } from '../src/index'
import engineFactory, { Fact, Rule, Operator } from '../src/index'

@@ -84,3 +81,3 @@ describe('Engine', () => {

expect(engine.operators.size).to.equal(11)
expect(engine.operators.get('startsWithLetter')).to.exist
expect(engine.operators.get('startsWithLetter')).to.exist()
expect(engine.operators.get('startsWithLetter')).to.be.an.instanceof(Operator)

@@ -104,3 +101,3 @@ })

expect(engine.facts.size).to.equal(1)
expect(engine.facts.has(FACT_NAME)).to.be.true
expect(engine.facts.has(FACT_NAME)).to.be.true()
}

@@ -127,3 +124,3 @@

assertFact(engine)
expect(engine.facts.get(FACT_NAME).value).to.be.undefined
expect(engine.facts.get(FACT_NAME).value).to.be.undefined()
})

@@ -138,3 +135,3 @@

expect(engine.facts.get(FACT_NAME).options).to.eql(options)
expect(engine.facts.get(FACT_NAME).value).to.be.undefined
expect(engine.facts.get(FACT_NAME).value).to.be.undefined()
})

@@ -147,3 +144,3 @@

assertFact(engine)
expect(engine.facts.get(FACT_NAME)).to.exist
expect(engine.facts.get(FACT_NAME)).to.exist()
expect(engine.facts.get(FACT_NAME).options).to.eql(options)

@@ -150,0 +147,0 @@ })

@@ -156,3 +156,3 @@ 'use strict'

await engine.run()
expect(eventSpy).to.have.been.calledOnce
expect(eventSpy).to.have.been.calledOnce()
})

@@ -159,0 +159,0 @@ })

@@ -6,5 +6,6 @@ 'use strict'

let chaiAsPromised = require('chai-as-promised')
let dirtyChai = require('dirty-chai')
chai.use(chaiAsPromised)
chai.use(sinonChai)
chai.use(dirtyChai)
global.expect = chai.expect

@@ -11,0 +12,0 @@ global.factories = {

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc