Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
2a80411
6.1.1
Jun 5, 2021
2f93213
Merge branch 'master' of https://github.com/CacheControl/json-rules-e…
Jun 5, 2021
beb656d
6.1.2
Jun 5, 2021
8485a04
Add tests for not condition
andycoopcode Jul 21, 2021
b6a8976
Implement logic for evaluating not condition
andycoopcode Jul 21, 2021
813281b
Update test for invalid root conditions
andycoopcode Jul 21, 2021
e08bd3a
Account for 'not' condition in Conditions class
andycoopcode Jul 21, 2021
721410d
Add NotConditions type
andycoopcode Jul 21, 2021
0132eb2
Update rules documentation with new condition
andycoopcode Jul 21, 2021
1b97403
Use 'not' condition in example 02
andycoopcode Jul 21, 2021
30e270b
Update almanac.md
bertday Oct 18, 2021
9e8c454
Merge pull request #283 from rbrtmrtn/patch-1
CacheControl Jun 28, 2023
919aba5
Merge pull request #275 from andycoopcode/add-not-condition
chris-pardy Jun 30, 2023
b31b902
Allow Named Conditions
Jun 14, 2023
3b1a68c
Upgrades to Dependencies
Jun 30, 2023
5f54101
Merge pull request #335 from RateGravity/depdency-updates
chris-pardy Jun 30, 2023
adcb2b6
Merge pull request #332 from RateGravity/named-conditions
chris-pardy Jun 30, 2023
8f7c4fa
Bump Package Version to 6.2.0
Jun 30, 2023
159c080
Decrease lower bound on performance tests
chamm-stripe Jul 3, 2023
5e3d0ed
lint
chamm-stripe Jul 3, 2023
e11171a
npm publishing actions workflow
chamm-stripe Jul 3, 2023
8e1cf47
run actions on latest lts version of node
chamm-stripe Jul 3, 2023
560802b
Add Linting to CI, split deployment
Jul 3, 2023
0f8ca50
Merge pull request #338 from RateGravity/lint-on-build
chris-pardy Jul 3, 2023
351ffc5
Merge pull request #337 from RateGravity/6-2-0
chris-pardy Jul 3, 2023
487fb7d
Fix link to Boolean Expressions
Jul 10, 2023
1d5d304
Point package.json for examples as the parent
Jul 10, 2023
96f5c17
Add Condition / ConditionReference Support
Jul 10, 2023
371243e
Add documentation around conditions
Jul 10, 2023
e500c1d
Merge pull request #339 from RateGravity/shared-conditions
chris-pardy Jul 12, 2023
8747b65
Update Version to 6.3.0
chris-pardy Jul 12, 2023
37b4342
Merge pull request #340 from RateGravity/6-3-0
Jul 13, 2023
48e50e9
Fix Top Level Condition Reference Failure
Jul 13, 2023
4f622e3
Merge pull request #341 from RateGravity/bug-fix-on-top-level-conditi…
Jul 13, 2023
a3c3807
Support Facts In Events
Aug 21, 2023
9a2beac
Upgrade Package Version to deploy minor change
Aug 23, 2023
e7263f4
Merge pull request #345 from RateGravity/facts-in-events
Aug 23, 2023
37df47d
Fix usage of lodash
Aug 26, 2023
106ea4e
Merge pull request #347 from RateGravity/fix-facts-in-events
Aug 26, 2023
3c18b69
Bump patch version and add missed typing
Sep 1, 2023
0de797c
Merge pull request #348 from RateGravity/fix-typing
Sep 1, 2023
597daa3
Add Almanac addFact method
chris-pardy Nov 5, 2023
25f9f7f
Move Almanac Initialization
chris-pardy Nov 5, 2023
3c1975e
Support passing a custom Almanac
chris-pardy Nov 5, 2023
fff169d
Merge pull request #357 from chris-pardy/custom-almanac
chris-pardy Nov 10, 2023
9655c81
Update package.json
chris-pardy Nov 10, 2023
d20bd68
Merge pull request #358 from CacheControl/release-custom-alamanc
chris-pardy Nov 10, 2023
7629c34
Fix typing of addOperator Methods
chris-pardy Oct 9, 2024
6dc1022
Remove lodash.isobjectlike package
trkic-zeiss Oct 10, 2024
87afb2f
Add Support for Operator Decorators
chris-pardy Oct 9, 2024
36987ef
Removed Lodash from almanac.js
chris-pardy Oct 11, 2024
c39b45a
Remove Lodash from rule result
chris-pardy Oct 11, 2024
cff1b67
updated on method overload to include generic method call
chris-pardy Aug 10, 2024
ed0429c
Commit @jagernet-ops suggestion to use use the instanceof operator
trkic-zeiss Oct 14, 2024
6ede34e
Based on @chris-pardy suggestion using typeof because its more perfor…
trkic-zeiss Oct 14, 2024
b75acfd
Merge pull request #370 from jagernet-ops/feature/Generic-on-method
chris-pardy Oct 15, 2024
194ebdb
Merge pull request #376 from chris-pardy/operator-decorators
chris-pardy Oct 15, 2024
9b603f5
Significantly simplify the prioritizeAndRun Method
chris-pardy Oct 11, 2024
86d210d
Improvements To Debug
chris-pardy Oct 11, 2024
a74cb51
Merge pull request #377 from trkic-zeiss/fix/remove-ladash-isobjectlike
chris-pardy Oct 15, 2024
9c9edd7
Merge pull request #378 from chris-pardy/bug-fixes
chris-pardy Oct 15, 2024
ec38ce0
Version 6.6.0
chris-pardy Oct 17, 2024
a7f7581
Merge pull request #382 from CacheControl/new-version
chris-pardy Oct 17, 2024
f1bf29f
Increment Major Version
chris-pardy Oct 22, 2024
bc36efa
Merge pull request #383 from chris-pardy/v7-baseline
chris-pardy Oct 24, 2024
c26e19a
Upgrade JSON-Path-Plus
chris-pardy Oct 24, 2024
bc7276e
Merge pull request #385 from chris-pardy/just-json-path
chris-pardy Oct 24, 2024
69bbc27
Release V7
chris-pardy Oct 24, 2024
ae8fa05
Merge pull request #386 from chris-pardy/release-v7
chris-pardy Oct 24, 2024
1d00bef
Squashed commit of the following:
emilefokkemanavara Nov 11, 2024
9d51178
Merge pull request #390 from emilefokkemanavara/fix/352/rule-result-json
chris-pardy Nov 12, 2024
8aec4e0
Upgrade Version Number to deploy 7.1.0
chris-pardy Nov 12, 2024
8f978b9
Upgrade JSON Path Plus to 10.1.0
chris-pardy Nov 12, 2024
91c7f38
Merge pull request #393 from CacheControl/verion7-1
chris-pardy Nov 13, 2024
0d35a58
Add factResult to fact-comparison values
thorin8k Nov 16, 2024
eeafc94
Fix test and comply with styling
thorin8k Nov 16, 2024
33eb4d2
Add valueResult unconditionally and more testing
thorin8k Nov 17, 2024
babfb32
Test for valueResult in factComparison suite
thorin8k Nov 17, 2024
3678a58
Add valueResult to condition serializer
thorin8k Nov 17, 2024
8e8b3f1
Fix serialized tests
thorin8k Nov 17, 2024
626604e
Merge pull request #396 from thorin8k/master
chris-pardy Nov 18, 2024
eadcd73
Bump Version to 7.2.0
chris-pardy Nov 18, 2024
5363e59
Merge pull request #400 from CacheControl/version-7-2
chris-pardy Nov 20, 2024
0d2c1c4
bump jsonpath-plus to v10.2.0
danish-khan-I Nov 21, 2024
444d405
bump jsonpath-plus to v10.2.0 and json-rules-engine to 7.2.1
danish-khan-I Nov 21, 2024
00061ac
Merge pull request #403 from danish-khan-I/features/v7.2
chris-pardy Nov 21, 2024
933105f
call Rule.ruleEvent by its correct name
emilefokkemanavara Nov 21, 2024
16a80a7
let Rule have both event and ruleEvent
emilefokkemanavara Dec 18, 2024
72d7b1a
deprecate `ruleEvent` property
emilefokkemanavara Dec 18, 2024
2a1ec93
Merge pull request #404 from emilefokkemanavara/fix/rule-type-rule-event
chris-pardy Dec 18, 2024
554b441
Increment minor version
chris-pardy Dec 18, 2024
0818adb
Typo - change to removeOperatorDecorator
Ben-CA Feb 7, 2025
7604636
Merge pull request #411 from Ben-CA/patch-1
chris-pardy Feb 13, 2025
1f0dbb4
Update package.json
danish-khan-I Feb 20, 2025
7b4880f
Merge branch 'master' into features/v7.3
danish-khan-I Feb 20, 2025
0a01da6
Merge pull request #414 from danish-khan-I/features/v7.3
chris-pardy Feb 20, 2025
f9ff458
syncing to upstream and adding our patch
dmay-trm May 22, 2025
62e92b4
keep our existing workflow files
dmay-trm May 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions docs/almanac.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* [Overview](#overview)
* [Methods](#methods)
* [almanac.factValue(Fact fact, Object params, String path) -> Promise](#almanacfactvaluefact-fact-object-params-string-path---promise)
* [almanac.addFact(String id, Function [definitionFunc], Object [options])](#almanacaddfactstring-id-function-definitionfunc-object-options)
* [almanac.addRuntimeFact(String factId, Mixed value)](#almanacaddruntimefactstring-factid-mixed-value)
* [almanac.getEvents(String outcome) -> Events[]](#almanacgeteventsstring-outcome---events)
* [almanac.getResults() -> RuleResults[]](#almanacgetresults---ruleresults)
Expand All @@ -14,8 +15,8 @@
## Overview

An almanac collects facts through an engine run cycle. As the engine computes fact values,
the results are stored in the almanac and cache'd. If the engine detects a fact computation has
been previously computed, it re-uses the cache'd result from the almanac. Every time ```engine.run()``` is invoked,
the results are stored in the almanac and cached. If the engine detects a fact computation has
been previously computed, it reuses the cached result from the almanac. Every time ```engine.run()``` is invoked,
a new almanac is instantiated.

The almanac for the current engine run is available as arguments passed to the fact evaluation methods and
Expand All @@ -33,8 +34,28 @@ almanac
.then( value => console.log(value))
```

### almanac.addFact(String id, Function [definitionFunc], Object [options])

Sets a fact in the almanac. Used in conjunction with rule and engine event emissions.

```js
// constant facts:
engine.addFact('speed-of-light', 299792458)

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

// facts with options:
engine.addFact('account-type', function getAccountType(params, almanac) {
// ...
}, { cache: false, priority: 500 })
```

### almanac.addRuntimeFact(String factId, Mixed value)

**Deprecated** Use `almanac.addFact` instead
Sets a constant fact mid-run. Often used in conjunction with rule and engine event emissions.

```js
Expand Down
142 changes: 142 additions & 0 deletions docs/engine.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ The Engine stores and executes rules, emits events, and maintains state.
* [engine.removeRule(Rule instance | String ruleId)](#engineremoverulerule-instance)
* [engine.addOperator(String operatorName, Function evaluateFunc(factValue, jsonValue))](#engineaddoperatorstring-operatorname-function-evaluatefuncfactvalue-jsonvalue)
* [engine.removeOperator(String operatorName)](#engineremoveoperatorstring-operatorname)
* [engine.addOperatorDecorator(String decoratorName, Function evaluateFunc(factValue, jsonValue, next))](#engineaddoperatordecoratorstring-decoratorname-function-evaluatefuncfactvalue-jsonvalue-next)
* [engine.removeOperatorDecorator(String decoratorName)](#engineremoveoperatordecoratorstring-decoratorname)
* [engine.setCondition(String name, Object conditions)](#enginesetconditionstring-name-object-conditions)
* [engine.removeCondition(String name)](#engineremovecondtionstring-name)
* [engine.run([Object facts], [Object options]) -> Promise ({ events: [], failureEvents: [], almanac: Almanac, results: [], failureResults: []})](#enginerunobject-facts-object-options---promise--events--failureevents--almanac-almanac-results--failureresults-)
* [engine.stop() -> Engine](#enginestop---engine)
* [engine.on('success', Function(Object event, Almanac almanac, RuleResult ruleResult))](#engineonsuccess-functionobject-event-almanac-almanac-ruleresult-ruleresult)
Expand Down Expand Up @@ -43,6 +47,13 @@ let engine = new Engine([Array rules], options)
an exception is thrown. Turning this option on will cause the engine to treat
undefined facts as `undefined`. (default: false)

`allowUndefinedConditions` - By default, when a running engine encounters a
condition reference that cannot be resolved an exception is thrown. Turning
this option on will cause the engine to treat unresolvable condition references
as failed conditions. (default: false)

`replaceFactsInEventParams` - By default when rules succeed or fail the events emitted are clones of the event in the rule declaration. When setting this option to true the parameters on the events will be have any fact references resolved. (default: false)

`pathResolver` - Allows a custom object path resolution library to be used. (default: `json-path` syntax). See [custom path resolver](./rules.md#condition-helpers-custom-path-resolver) docs.

### engine.addFact(String id, Function [definitionFunc], Object [options])
Expand Down Expand Up @@ -172,6 +183,127 @@ engine.addOperator('startsWithLetter', (factValue, jsonValue) => {
engine.removeOperator('startsWithLetter');
```

### engine.addOperatorDecorator(String decoratorName, Function evaluateFunc(factValue, jsonValue, next))

Adds a custom operator decorator to the engine.

```js
/*
* decoratorName - operator decorator identifier used in the rule condition
* evaluateFunc(factValue, jsonValue, next) - uses the decorated operator to compare the fact result to the condition 'value'
* factValue - the value returned from the fact
* jsonValue - the "value" property stored in the condition itself
* next - the evaluateFunc of the decorated operator
*/
engine.addOperatorDecorator('first', (factValue, jsonValue, next) => {
if (!factValue.length) return false
return next(factValue[0], jsonValue)
})

engine.addOperatorDecorator('caseInsensitive', (factValue, jsonValue, next) => {
return next(factValue.toLowerCase(), jsonValue.toLowerCase())
})

// and to use the decorator...
let rule = new Rule(
conditions: {
all: [
{
fact: 'username',
operator: 'first:caseInsensitive:equal', // reference the decorator:operator in the rule
value: 'a'
}
]
}
)
```

See the [operator decorator example](../examples/13-using-operator-decorators.js)



### engine.removeOperatorDecorator(String decoratorName)

Removes a operator decorator from the engine

```javascript
engine.addOperatorDecorator('first', (factValue, jsonValue, next) => {
if (!factValue.length) return false
return next(factValue[0], jsonValue)
})

engine.addOperatorDecorator('caseInsensitive', (factValue, jsonValue, next) => {
return next(factValue.toLowerCase(), jsonValue.toLowerCase())
})

engine.removeOperatorDecorator('first');
```

### engine.setCondition(String name, Object conditions)

Adds or updates a condition to the engine. Rules may include references to this condition. Conditions must start with `all`, `any`, `not`, or reference a condition.

```javascript
engine.setCondition('validLogin', {
all: [
{
operator: 'notEqual',
fact: 'loginToken',
value: null
},
{
operator: 'greaterThan',
fact: 'loginToken',
path: '$.expirationTime',
value: { fact: 'now' }
}
]
});

engine.addRule({
condtions: {
all: [
{
condition: 'validLogin'
},
{
operator: 'contains',
fact: 'loginToken',
path: '$.role',
value: 'admin'
}
]
},
event: {
type: 'AdminAccessAllowed'
}
})

```

### engine.removeCondition(String name)

Removes the condition that was previously added.

```javascript
engine.setCondition('validLogin', {
all: [
{
operator: 'notEqual',
fact: 'loginToken',
value: null
},
{
operator: 'greaterThan',
fact: 'loginToken',
path: '$.expirationTime',
value: { fact: 'now' }
}
]
});

engine.removeCondition('validLogin');
```


### engine.run([Object facts], [Object options]) -> Promise ({ events: [], failureEvents: [], almanac: Almanac, results: [], failureResults: []})
Expand All @@ -195,6 +327,16 @@ const {
```
Link to the [Almanac documentation](./almanac.md)

Optionally, you may specify a specific almanac instance via the almanac property.

```js
// create a custom Almanac
const myCustomAlmanac = new CustomAlmanac();

// run the engine with the custom almanac
await engine.run({}, { almanac: myCustomAlmanac })
```

### engine.stop() -> Engine

Stops the rules engine from running the next priority set of Rules. All remaining rules will be resolved as undefined,
Expand Down
102 changes: 96 additions & 6 deletions docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ Rules contain a set of _conditions_ and a single _event_. When the engine is ru
* [toJSON(Boolean stringify = true)](#tojsonboolean-stringify--true)
* [Conditions](#conditions)
* [Basic conditions](#basic-conditions)
* [Boolean expressions: all and any](#boolean-expressions-all-and-any)
* [Boolean expressions: all, any, and not](#boolean-expressions-all-any-and-not)
* [Condition Reference](#condition-reference)
* [Condition helpers: params](#condition-helpers-params)
* [Condition helpers: path](#condition-helpers-path)
* [Condition helpers: custom path resolver](#condition-helpers-custom-path-resolver)
Expand All @@ -26,6 +27,11 @@ Rules contain a set of _conditions_ and a single _event_. When the engine is ru
* [String and Numeric operators:](#string-and-numeric-operators)
* [Numeric operators:](#numeric-operators)
* [Array operators:](#array-operators)
* [Operator Decorators](#operator-decorators)
* [Array decorators:](#array-decorators)
* [Logical decorators:](#logical-decorators)
* [Utility decorators:](#utility-decorators)
* [Decorator composition:](#decorator-composition)
* [Rule Results](#rule-results)
* [Persisting](#persisting)

Expand Down Expand Up @@ -134,9 +140,9 @@ let rule = new Rule({

See the [hello-world](../examples/01-hello-world.js) example.

### Boolean expressions: `all` and `any`
### Boolean expressions: `all`, `any`, and `not`

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.
Each rule's conditions *must* have an `all` or `any` operator containing an array of conditions at its root, a `not` operator containing a single condition, or a condition reference. 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. The `not` operator will negate whatever condition it contains.

```js
// all:
Expand All @@ -158,14 +164,46 @@ let rule = new Rule({
{ /* condition 2 */ },
{ /* condition n */ },
{
all: [ /* more conditions */ ]
not: {
all: [ /* more conditions */ ]
}
}
]
}
})

// not:
let rule = new Rule({
conditions: {
not: { /* condition */ }
}
})
```

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.
Notice in the second example how `all`, `any`, and `not` can be nested within one another to produce complex boolean expressions. See the [nested-boolean-logic](../examples/02-nested-boolean-logic.js) example.

### Condition Reference

Rules may reference conditions based on their name.

```js
let rule = new Rule({
conditions: {
all: [
{ condition: 'conditionName' },
{ /* additional condition */ }
]
}
})
```

Before running the rule the condition should be added to the engine.

```js
engine.setCondition('conditionName', { /* conditions */ });
```

Conditions must start with `all`, `any`, `not`, or reference a condition.

### Condition helpers: `params`

Expand Down Expand Up @@ -316,9 +354,32 @@ engine.on('failure', function(event, almanac, ruleResult) {
})
```

### Referencing Facts In Events

With the engine option [`replaceFactsInEventParams`](./engine.md#options) the parameters of the event may include references to facts in the same form as [Comparing Facts](#comparing-facts). These references will be replaced with the value of the fact before the event is emitted.

```js
const engine = new Engine([], { replaceFactsInEventParams: true });
engine.addRule({
conditions: { /* ... */ },
event: {
type: "gameover",
params: {
initials: {
fact: "currentHighScore",
path: "$.initials",
params: { foo: 'bar' }
}
}
}
})
```

See [11-using-facts-in-events.js](../examples/11-using-facts-in-events.js) for a complete example.

## Operators

Each rule condition must begin with a boolean operator(```all``` or ```any```) at its root.
Each rule condition must begin with a boolean operator(```all```, ```any```, or ```not```) at its root.

The ```operator``` compares the value returned by the ```fact``` to what is stored in the ```value``` property. If the result is truthy, the condition passes.

Expand Down Expand Up @@ -350,6 +411,35 @@ The ```operator``` compares the value returned by the ```fact``` to what is stor

```doesNotContain``` - _fact_ (an array) must not include _value_

## Operator Decorators

Operator Decorators modify the behavior of an operator either by changing the input or the output. To specify one or more decorators prefix the name of the operator with them in the ```operator``` field and use the colon (```:```) symbol to separate decorators and the operator. For instance ```everyFact:greaterThan``` will produce an operator that checks that every element of the _fact_ is greater than the value.

See [12-using-operator-decorators.js](../examples/13-using-operator-decorators.js) for an example.

### Array Decorators:

```everyFact``` - _fact_ (an array) must have every element pass the decorated operator for _value_

```everyValue``` - _fact_ must pass the decorated operator for every element of _value_ (an array)

```someFact``` - _fact_ (an array) must have at-least one element pass the decorated operator for _value_

```someValue``` - _fact_ must pass the decorated operator for at-least one element of _value_ (an array)

### Logical Decorators

```not``` - negate the result of the decorated operator

### Utility Decorators
```swap``` - Swap _fact_ and _value_ for the decorated operator

### Decorator Composition

Operator Decorators can be composed by chaining them together with the colon to separate them. For example if you wanted to ensure that every number in an array was less than every number in another array you could use ```everyFact:everyValue:lessThan```.

```swap``` and ```not``` are useful when there are not symmetric or negated versions of custom operators, for instance you could check if a _value_ does not start with a letter contained in a _fact_ using the decorated custom operator ```swap:not:startsWithLetter```. This allows a single custom operator to have 4 permutations.

## Rule Results

After a rule is evaluated, a `rule result` object is provided to the `success` and `failure` events. This argument is similar to a regular rule, and contains additional metadata about how the rule was evaluated. Rule results can be used to extract the results of individual conditions, computed fact values, and boolean logic results. `name` can be used to easily identify a given rule.
Expand Down
14 changes: 9 additions & 5 deletions examples/02-nested-boolean-logic.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,21 @@ async function start () {
fact: 'personalFoulCount',
operator: 'greaterThanInclusive',
value: 5
}]
}],
name: 'short foul limit'
}, {
all: [{
fact: 'gameDuration',
operator: 'equal',
value: 48
}, {
fact: 'personalFoulCount',
operator: 'greaterThanInclusive',
value: 6
}]
not: {
fact: 'personalFoulCount',
operator: 'lessThan',
value: 6
}
}],
name: 'long foul limit'
}]
},
event: { // define the event to fire when the conditions evaluate truthy
Expand Down
Loading