Skip to content

Commit 6a826fd

Browse files
authoredDec 22, 2020
Merge pull request #242 from CacheControl/engine-run-results
2 parents 22dcd7d + c7ef090 commit 6a826fd

24 files changed

+967
-744
lines changed
 

‎CHANGELOG.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,13 @@
1010
(fixes #205)
1111
* Engine and Rule events `on('success')`, `on('failure')`, and Rule callbacks `onSuccess` and `onFailure` now honor returned promises; any event handler that returns a promise will be waited upon to resolve before engine execution continues. (fixes #235)
1212
* Private `rule.event` property renamed. Use `rule.getEvent()` to avoid breaking changes in the future.
13-
* The 'success-events' fact used to store successful events has been converted to an internal data structure and will no longer appear in the almanac's facts. (fixes #187)
13+
* The `success-events` fact used to store successful events has been converted to an internal data structure and will no longer appear in the almanac's facts. (fixes #187)
1414
* NEW FEATURES
1515
* Engine constructor now accepts a `pathResolver` option for resolving condition `path` properties. Read more [here](./docs/rules.md#condition-helpers-custom-path-resolver). (fixes #210)
16+
* Engine.run() now returns three additional data structures:
17+
* `failureEvents`, an array of all failed rules events. (fixes #192)
18+
* `results`, an array of RuleResults for each successful rule (fixes #216)
19+
* `failureResults`, an array of RuleResults for each failed rule
1620
1721
1822
#### 5.3.0 / 2020-12-02

‎README.md

+4-6
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,8 @@ let facts = {
8686
// Run the engine to evaluate
8787
engine
8888
.run(facts)
89-
.then(results => {
90-
// 'results' is an object containing successful events, and an Almanac instance containing facts
91-
results.events.map(event => console.log(event.params.message))
89+
.then(({ events }) => {
90+
events.map(event => console.log(event.params.message))
9291
})
9392

9493
/*
@@ -171,10 +170,9 @@ engine.addFact('account-information', function (params, almanac) {
171170
let facts = { accountId: 'lincoln' }
172171
engine
173172
.run(facts)
174-
.then((results) => {
175-
console.log(facts.accountId + ' is a ' + results.events.map(event => event.params.message))
173+
.then(({ events }) => {
174+
console.log(facts.accountId + ' is a ' + events.map(event => event.params.message))
176175
})
177-
.catch(err => console.log(err.stack))
178176

179177
/*
180178
* OUTPUT:

‎docs/almanac.md

+29-7
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
* [Methods](#methods)
55
* [almanac.factValue(Fact fact, Object params, String path) -> Promise](#almanacfactvaluefact-fact-object-params-string-path---promise)
66
* [almanac.addRuntimeFact(String factId, Mixed value)](#almanacaddruntimefactstring-factid-mixed-value)
7+
* [almanac.getEvents(String outcome) -> Events[]](#almanacgeteventsstring-outcome---events)
8+
* [almanac.getResults() -> RuleResults[]](#almanacgetresults---ruleresults)
79
* [Common Use Cases](#common-use-cases)
810
* [Fact dependencies](#fact-dependencies)
911
* [Retrieve fact values when handling events](#retrieve-fact-values-when-handling-events)
@@ -39,6 +41,26 @@ Sets a constant fact mid-run. Often used in conjunction with rule and engine ev
3941
almanac.addRuntimeFact('account-id', 1)
4042
```
4143

44+
### almanac.getEvents(String outcome) -> Events[]
45+
46+
Returns events by outcome ("success" or "failure") for the current engine run()
47+
48+
```js
49+
almanac.getEvents() // all events for every rule evaluated thus far
50+
51+
almanac.getEvents('success') // array of success events
52+
53+
almanac.getEvents('failure') // array of failure events
54+
```
55+
56+
### almanac.getResults() -> RuleResults[]
57+
58+
Returns [rule results](./rules#rule-results) for the current engine run()
59+
60+
```js
61+
almanac.getResults()
62+
```
63+
4264
## Common Use Cases
4365

4466
### Fact dependencies
@@ -110,13 +132,13 @@ When a rule evalutes truthy and its ```event``` is called, new facts may be defi
110132
111133
```js
112134
engine.on('success', (event, almanac) => {
113-
// Handle the event using a fact value to process
114-
// Retrieve user's account info and make an api call using the results
115-
almanac
116-
.factValue('account-information', event.params.accountId)
117-
.then(info => {
118-
return request.post({ url: `http://my-service/toggle?funded=${!info.funded}`)
119-
})
135+
// Retrieve user's account info based on the event params
136+
const info = await almanac.factValue('account-information', event.params.accountId)
137+
138+
// make an api call using the results
139+
await request.post({ url: `http://my-service/toggle?funded=${!info.funded}`)
140+
141+
// engine execution continues when promise returned to 'success' callback resolves
120142
})
121143
```
122144

‎docs/engine.md

+23-26
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,18 @@
33
The Engine stores and executes rules, emits events, and maintains state.
44

55
* [Methods](#methods)
6-
* [constructor([Array rules], Object [options])](#constructorarray-rules-object-options)
7-
* [Options](#options)
8-
* [engine.addFact(String id, Function [definitionFunc], Object [options])](#engineaddfactstring-id-function-definitionfunc-object-options)
9-
* [engine.removeFact(String id)](#engineremovefactstring-id)
10-
* [engine.addRule(Rule instance|Object options)](#engineaddrulerule-instanceobject-options)
11-
* [engine.removeRule(Rule instance)](#engineremoverulerule-instance)
12-
* [engine.addOperator(String operatorName, Function evaluateFunc(factValue, jsonValue))](#engineaddoperatorstring-operatorname-function-evaluatefuncfactvalue-jsonvalue)
13-
* [engine.removeOperator(String operatorName)](#engineremoveoperatorstring-operatorname)
14-
* [engine.run([Object facts], [Object options]) -> Promise ({ events: Events, almanac: Almanac})](#enginerunobject-facts-object-options---promise--events-events-almanac-almanac)
15-
* [engine.stop() -> Engine](#enginestop---engine)
16-
* [engine.on('success', Function(Object event, Almanac almanac, RuleResult ruleResult))](#engineonsuccess-functionobject-event-almanac-almanac-ruleresult-ruleresult)
17-
* [engine.on('failure', Function(Object event, Almanac almanac, RuleResult ruleResult))](#engineonfailure-functionobject-event-almanac-almanac-ruleresult-ruleresult)
6+
* [constructor([Array rules], Object [options])](#constructorarray-rules-object-options)
7+
* [Options](#options)
8+
* [engine.addFact(String id, Function [definitionFunc], Object [options])](#engineaddfactstring-id-function-definitionfunc-object-options)
9+
* [engine.removeFact(String id)](#engineremovefactstring-id)
10+
* [engine.addRule(Rule instance|Object options)](#engineaddrulerule-instanceobject-options)
11+
* [engine.removeRule(Rule instance)](#engineremoverulerule-instance)
12+
* [engine.addOperator(String operatorName, Function evaluateFunc(factValue, jsonValue))](#engineaddoperatorstring-operatorname-function-evaluatefuncfactvalue-jsonvalue)
13+
* [engine.removeOperator(String operatorName)](#engineremoveoperatorstring-operatorname)
14+
* [engine.run([Object facts], [Object options]) -> Promise ({ events: [], failureEvents: [], almanac: Almanac, results: [], failureResults: []})](#enginerunobject-facts-object-options---promise--events--failureevents--almanac-almanac-results--failureresults-)
15+
* [engine.stop() -> Engine](#enginestop---engine)
16+
* [engine.on('success', Function(Object event, Almanac almanac, RuleResult ruleResult))](#engineonsuccess-functionobject-event-almanac-almanac-ruleresult-ruleresult)
17+
* [engine.on('failure', Function(Object event, Almanac almanac, RuleResult ruleResult))](#engineonfailure-functionobject-event-almanac-almanac-ruleresult-ruleresult)
1818

1919
## Methods
2020

@@ -104,9 +104,6 @@ engine.addRule(rule)
104104
engine.removeRule(rule)
105105
```
106106

107-
108-
109-
110107
### engine.addOperator(String operatorName, Function evaluateFunc(factValue, jsonValue))
111108

112109
Adds a custom operator to the engine. For situations that require going beyond the generic, built-in operators (`equal`, `greaterThan`, etc).
@@ -156,24 +153,24 @@ engine.removeOperator('startsWithLetter');
156153

157154

158155

159-
### engine.run([Object facts], [Object options]) -> Promise ({ events: Events, almanac: Almanac})
156+
### engine.run([Object facts], [Object options]) -> Promise ({ events: [], failureEvents: [], almanac: Almanac, results: [], failureResults: []})
160157

161158
Runs the rules engine. Returns a promise which resolves when all rules have been run.
162159

163160
```js
164161
// run the engine
165-
engine.run()
162+
await engine.run()
166163

167164
// with constant facts
168-
engine.run({ userId: 1 })
169-
170-
// returns rule events that were triggered
171-
engine
172-
.run({ userId: 1 })
173-
.then(function(results) {
174-
console.log(results.events)
175-
// almanac available via results.almanac to interact with as defined in Almanac docs
176-
})
165+
await engine.run({ userId: 1 })
166+
167+
const {
168+
results, // rule results for successful rules
169+
failureResults, // rule results for failed rules
170+
events, // array of successful rule events
171+
failureEvents, // array of failed rule events
172+
almanac // Almanac instance representing the run
173+
} = await engine.run({ userId: 1 })
177174
```
178175
Link to the [Almanac documentation](./almanac.md)
179176

‎docs/rules.md

+19-19
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,28 @@
44
Rules contain a set of _conditions_ and a single _event_. When the engine is run, each rule condition is evaluated. If the results are truthy, the rule's _event_ is triggered.
55

66
* [Methods](#methods)
7-
* [constructor([Object options|String json])](#constructorobject-optionsstring-json)
8-
* [setConditions(Array conditions)](#setconditionsarray-conditions)
9-
* [getConditions() -> Object](#getconditions---object)
10-
* [setEvent(Object event)](#seteventobject-event)
11-
* [getEvent() -> Object](#getevent---object)
12-
* [setPriority(Integer priority = 1)](#setpriorityinteger-priority--1)
13-
* [getPriority() -> Integer](#getpriority---integer)
14-
* [toJSON(Boolean stringify = true)](#tojsonboolean-stringify--true)
7+
* [constructor([Object options|String json])](#constructorobject-optionsstring-json)
8+
* [setConditions(Array conditions)](#setconditionsarray-conditions)
9+
* [getConditions() -> Object](#getconditions---object)
10+
* [setEvent(Object event)](#seteventobject-event)
11+
* [getEvent() -> Object](#getevent---object)
12+
* [setPriority(Integer priority = 1)](#setpriorityinteger-priority--1)
13+
* [getPriority() -> Integer](#getpriority---integer)
14+
* [toJSON(Boolean stringify = true)](#tojsonboolean-stringify--true)
1515
* [Conditions](#conditions)
16-
* [Basic conditions](#basic-conditions)
17-
* [Boolean expressions: all and any](#boolean-expressions-all-and-any)
18-
* [Condition helpers: params](#condition-helpers-params)
19-
* [Condition helpers: path](#condition-helpers-path)
20-
* [Condition helpers: custom path resolver](#condition-helpers-custom-path-resolver)
21-
* [Comparing facts](#comparing-facts)
16+
* [Basic conditions](#basic-conditions)
17+
* [Boolean expressions: all and any](#boolean-expressions-all-and-any)
18+
* [Condition helpers: params](#condition-helpers-params)
19+
* [Condition helpers: path](#condition-helpers-path)
20+
* [Condition helpers: custom path resolver](#condition-helpers-custom-path-resolver)
21+
* [Comparing facts](#comparing-facts)
2222
* [Events](#events)
23-
* [rule.on('success', Function(Object event, Almanac almanac, RuleResult ruleResult))](#ruleonsuccess-functionobject-event-almanac-almanac-ruleresult-ruleresult)
24-
* [rule.on('failure', Function(Object event, Almanac almanac, RuleResult ruleResult))](#ruleonfailure-functionobject-event-almanac-almanac-ruleresult-ruleresult)
23+
* [rule.on('success', Function(Object event, Almanac almanac, RuleResult ruleResult))](#ruleonsuccess-functionobject-event-almanac-almanac-ruleresult-ruleresult)
24+
* [rule.on('failure', Function(Object event, Almanac almanac, RuleResult ruleResult))](#ruleonfailure-functionobject-event-almanac-almanac-ruleresult-ruleresult)
2525
* [Operators](#operators)
26-
* [String and Numeric operators:](#string-and-numeric-operators)
27-
* [Numeric operators:](#numeric-operators)
28-
* [Array operators:](#array-operators)
26+
* [String and Numeric operators:](#string-and-numeric-operators)
27+
* [Numeric operators:](#numeric-operators)
28+
* [Array operators:](#array-operators)
2929
* [Rule Results](#rule-results)
3030

3131
## Methods

‎docs/walkthrough.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ engine.on('young-adult-rocky-mnts', (params) => {
138138
// - OR -
139139

140140
// subscribe to any event emitted by the engine
141-
engine.on('success', function (event, engine) {
141+
engine.on('success', function (event, almanac, ruleResult) {
142142
console.log('Success event:\n', event);
143143
// event: {
144144
// type: "young-adult-rocky-mnts",
@@ -162,8 +162,8 @@ Running an engine executes the rules, and fires off event events for conditions
162162
engine.run({ userId: 1 }); // any time a rule condition requires 'userId', '1' will be returned
163163

164164
// run() returns a promise
165-
engine.run({ userId: 4 }).then((results) => {
166-
console.log('all rules executed; the following events were triggered: ', results.events)
165+
engine.run({ userId: 4 }).then(({ events }) => {
166+
console.log('all rules executed; the following events were triggered: ', events.map(result => JSON.stringify(event)))
167167
});
168168
```
169169
Helper methods (for this example)

‎examples/01-hello-world.js

+36-39
Original file line numberDiff line numberDiff line change
@@ -10,52 +10,49 @@
1010
*/
1111

1212
require('colors')
13-
const { Engine, Rule } = require('json-rules-engine')
13+
const { Engine } = require('json-rules-engine')
1414

15-
/**
16-
* Setup a new engine
17-
*/
18-
const engine = new Engine()
15+
async function start () {
16+
/**
17+
* Setup a new engine
18+
*/
19+
const engine = new Engine()
1920

20-
/**
21-
* Create a rule
22-
*/
23-
const rule = new Rule({
24-
// define the 'conditions' for when "hello world" should display
25-
conditions: {
26-
all: [{
27-
fact: 'displayMessage',
28-
operator: 'equal',
29-
value: true
30-
}]
31-
},
32-
// define the 'event' that will fire when the condition evaluates truthy
33-
event: {
34-
type: 'message',
35-
params: {
36-
data: 'hello-world!'
21+
/**
22+
* Create a rule
23+
*/
24+
engine.addRule({
25+
// define the 'conditions' for when "hello world" should display
26+
conditions: {
27+
all: [{
28+
fact: 'displayMessage',
29+
operator: 'equal',
30+
value: true
31+
}]
32+
},
33+
// define the 'event' that will fire when the condition evaluates truthy
34+
event: {
35+
type: 'message',
36+
params: {
37+
data: 'hello-world!'
38+
}
3739
}
38-
}
39-
})
40+
})
4041

41-
// add rule to engine
42-
engine.addRule(rule)
42+
/**
43+
* Define a 'displayMessage' as a constant value
44+
* Fact values do NOT need to be known at engine runtime; see the
45+
* 03-dynamic-facts.js example for how to pull in data asynchronously during runtime
46+
*/
47+
const facts = { displayMessage: true }
4348

44-
/**
45-
* Define a 'displayMessage' as a constant value
46-
* Fact values do NOT need to be known at engine runtime; see the
47-
* 03-dynamic-facts.js example for how to pull in data asynchronously during runtime
48-
*/
49-
const facts = { displayMessage: true }
49+
// engine.run() evaluates the rule using the facts provided
50+
const { events } = await engine.run(facts)
5051

51-
// run the engine
52-
engine
53-
.run(facts)
54-
.then(results => { // engine returns an object with a list of events with truthy conditions
55-
results.events.map(event => console.log(event.params.data.green))
56-
})
57-
.catch(console.log)
52+
events.map(event => console.log(event.params.data.green))
53+
}
5854

55+
start()
5956
/*
6057
* OUTPUT:
6158
*

0 commit comments

Comments
 (0)
Please sign in to comment.