From 91e7cf4b73d7d0bb601af95fb605face244669b6 Mon Sep 17 00:00:00 2001 From: Cache Hamm Date: Sat, 28 Nov 2020 21:13:21 -0700 Subject: [PATCH 01/18] Convert engine events to use EventEmitter2 and wait for returned promises to resolve --- package-lock.json | 95 +++++++++++++++++++----------------- package.json | 1 + src/engine.js | 10 ++-- test/engine-rule-priority.js | 57 ++++++++++++++++++---- test/engine-run.test.js | 2 - 5 files changed, 105 insertions(+), 60 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3af0c60..8a0f26a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -497,7 +497,7 @@ }, "arrify": { "version": "1.0.1", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/arrify/-/arrify-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true }, @@ -1971,7 +1971,7 @@ }, "commondir": { "version": "1.0.1", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/commondir/-/commondir-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, @@ -2046,7 +2046,7 @@ }, "contains-path": { "version": "0.1.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/contains-path/-/contains-path-0.1.0.tgz", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", "dev": true }, @@ -2125,19 +2125,19 @@ }, "debug-log": { "version": "1.0.1", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/debug-log/-/debug-log-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/debug-log/-/debug-log-1.0.1.tgz", "integrity": "sha1-IwdjLUwEOCuN+KMvcLiVBG1SdF8=", "dev": true }, "decamelize": { "version": "1.2.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/decamelize/-/decamelize-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, "decamelize-keys": { "version": "1.1.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", "dev": true, "requires": { @@ -2147,7 +2147,7 @@ "dependencies": { "map-obj": { "version": "1.0.1", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/map-obj/-/map-obj-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", "dev": true } @@ -2186,7 +2186,7 @@ }, "deep-is": { "version": "0.1.3", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/deep-is/-/deep-is-0.1.3.tgz", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, @@ -2344,7 +2344,7 @@ }, "duplexer3": { "version": "0.1.4", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/duplexer3/-/duplexer3-0.1.4.tgz", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", "dev": true }, @@ -2996,6 +2996,11 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, + "eventemitter2": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.3.tgz", + "integrity": "sha512-t0A2msp6BzOf+QAcI6z9XMktLj52OjGQg+8SJH6v5+3uxNpWYRR3wQmfA+6xtMU9kOC59qk9licus5dYcrYkMQ==" + }, "events": { "version": "3.2.0", "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/events/-/events-3.2.0.tgz", @@ -3162,7 +3167,7 @@ }, "fast-levenshtein": { "version": "2.0.6", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, @@ -3869,7 +3874,7 @@ }, "functional-red-black-tree": { "version": "1.0.1", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, @@ -4187,13 +4192,13 @@ }, "import-lazy": { "version": "2.1.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/import-lazy/-/import-lazy-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", "dev": true }, "imurmurhash": { "version": "0.1.4", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/imurmurhash/-/imurmurhash-0.1.4.tgz", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, @@ -4375,7 +4380,7 @@ }, "is-arrayish": { "version": "0.2.1", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/is-arrayish/-/is-arrayish-0.2.1.tgz", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, @@ -4490,7 +4495,7 @@ }, "is-fullwidth-code-point": { "version": "2.0.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, @@ -4550,7 +4555,7 @@ }, "is-plain-obj": { "version": "1.1.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", "dev": true }, @@ -4644,7 +4649,7 @@ }, "isexe": { "version": "2.0.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/isexe/-/isexe-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, @@ -4722,7 +4727,7 @@ }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, @@ -4783,7 +4788,7 @@ }, "levn": { "version": "0.3.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/levn/-/levn-0.3.0.tgz", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", "dev": true, "requires": { @@ -4799,7 +4804,7 @@ }, "load-json-file": { "version": "2.0.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/load-json-file/-/load-json-file-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", "dev": true, "requires": { @@ -4811,7 +4816,7 @@ "dependencies": { "pify": { "version": "2.3.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } @@ -5520,7 +5525,7 @@ }, "natural-compare": { "version": "1.4.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/natural-compare/-/natural-compare-1.4.0.tgz", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, @@ -5844,7 +5849,7 @@ }, "parse-json": { "version": "2.2.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/parse-json/-/parse-json-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, "requires": { @@ -5860,7 +5865,7 @@ }, "path-exists": { "version": "3.0.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/path-exists/-/path-exists-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true }, @@ -5872,7 +5877,7 @@ }, "path-key": { "version": "2.0.1", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/path-key/-/path-key-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", "dev": true }, @@ -5901,7 +5906,7 @@ }, "path-type": { "version": "2.0.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/path-type/-/path-type-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", "dev": true, "requires": { @@ -5910,7 +5915,7 @@ "dependencies": { "pify": { "version": "2.3.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } @@ -5983,7 +5988,7 @@ }, "pkg-config": { "version": "1.1.1", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/pkg-config/-/pkg-config-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/pkg-config/-/pkg-config-1.1.1.tgz", "integrity": "sha1-VX7yLXPaPIg3EHdmxS6tq94pj+Q=", "dev": true, "requires": { @@ -6019,7 +6024,7 @@ }, "prelude-ls": { "version": "1.1.2", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/prelude-ls/-/prelude-ls-1.1.2.tgz", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "dev": true }, @@ -6181,7 +6186,7 @@ }, "read-pkg": { "version": "2.0.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/read-pkg/-/read-pkg-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", "dev": true, "requires": { @@ -6192,7 +6197,7 @@ }, "read-pkg-up": { "version": "2.0.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", "dev": true, "requires": { @@ -6202,7 +6207,7 @@ "dependencies": { "find-up": { "version": "2.1.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/find-up/-/find-up-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { @@ -6211,7 +6216,7 @@ }, "locate-path": { "version": "2.0.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/locate-path/-/locate-path-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, "requires": { @@ -6230,7 +6235,7 @@ }, "p-locate": { "version": "2.0.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/p-locate/-/p-locate-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { @@ -6239,7 +6244,7 @@ }, "p-try": { "version": "1.0.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/p-try/-/p-try-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "dev": true } @@ -6719,7 +6724,7 @@ }, "require-directory": { "version": "2.1.1", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/require-directory/-/require-directory-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, @@ -6880,7 +6885,7 @@ }, "set-blocking": { "version": "2.0.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/set-blocking/-/set-blocking-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, @@ -6911,7 +6916,7 @@ }, "shebang-command": { "version": "1.2.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/shebang-command/-/shebang-command-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "dev": true, "requires": { @@ -6920,7 +6925,7 @@ }, "shebang-regex": { "version": "1.0.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/shebang-regex/-/shebang-regex-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, @@ -7273,7 +7278,7 @@ }, "sprintf-js": { "version": "1.0.3", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/sprintf-js/-/sprintf-js-1.0.3.tgz", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, @@ -7413,7 +7418,7 @@ }, "strip-bom": { "version": "3.0.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/strip-bom/-/strip-bom-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true }, @@ -7519,7 +7524,7 @@ }, "through": { "version": "2.3.8", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/through/-/through-2.3.8.tgz", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, @@ -7703,7 +7708,7 @@ }, "type-check": { "version": "0.3.2", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/type-check/-/type-check-0.3.2.tgz", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", "dev": true, "requires": { @@ -7752,7 +7757,7 @@ }, "uniq": { "version": "1.0.1", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/uniq/-/uniq-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", "dev": true }, @@ -7969,7 +7974,7 @@ }, "which-module": { "version": "2.0.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/which-module/-/which-module-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, diff --git a/package.json b/package.json index 8be97f4..e8008cf 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,7 @@ }, "dependencies": { "clone": "^2.1.2", + "eventemitter2": "6.4.3", "events": "^3.2.0", "hash-it": "^4.0.5", "jsonpath-plus": "^4.0.0", diff --git a/src/engine.js b/src/engine.js index 4a949ac..dd63611 100644 --- a/src/engine.js +++ b/src/engine.js @@ -4,7 +4,7 @@ import Fact from './fact' import Rule from './rule' import Operator from './operator' import Almanac from './almanac' -import { EventEmitter } from 'events' +import EventEmitter from 'eventemitter2' import { SuccessEventFact } from './engine-facts' import defaultOperators from './engine-default-operators' import debug from './debug' @@ -191,11 +191,13 @@ class Engine extends EventEmitter { return rule.evaluate(almanac).then((ruleResult) => { debug(`engine::run ruleResult:${ruleResult.result}`) if (ruleResult.result) { - this.emit('success', rule.event, almanac, ruleResult) - this.emit(rule.event.type, rule.event.params, almanac, ruleResult) almanac.factValue('success-events', { event: rule.event }) + return Promise.all([ + this.emitAsync('success', rule.event, almanac, ruleResult), + this.emitAsync(rule.event.type, rule.event.params, almanac, ruleResult) + ]) } else { - this.emit('failure', rule.event, almanac, ruleResult) + return this.emitAsync('failure', rule.event, almanac, ruleResult) } }) })) diff --git a/test/engine-rule-priority.js b/test/engine-rule-priority.js index c5f7313..df96f99 100644 --- a/test/engine-rule-priority.js +++ b/test/engine-rule-priority.js @@ -3,11 +3,12 @@ import engineFactory from '../src/index' import sinon from 'sinon' -describe('Engine: cache', () => { +describe('Engine: rule priorities', () => { let engine - const event = { type: 'setDrinkingFlag' } - const collegeSeniorEvent = { type: 'isCollegeSenior' } + const highPriorityEvent = { type: 'highPriorityEvent' } + const midPriorityEvent = { type: 'midPriorityEvent' } + const lowestPriorityEvent = { type: 'lowestPriorityEvent' } const conditions = { any: [{ fact: 'age', @@ -28,12 +29,16 @@ describe('Engine: cache', () => { const factSpy = sandbox.stub().returns(22) const eventSpy = sandbox.spy() engine = engineFactory() - const over20 = factories.rule({ conditions, event: collegeSeniorEvent, priority: 50 }) - engine.addRule(over20) - const determineDrinkingAge = factories.rule({ conditions, event, priority: 100 }) - engine.addRule(determineDrinkingAge) - const determineCollegeSenior = factories.rule({ conditions, event: collegeSeniorEvent, priority: 1 }) - engine.addRule(determineCollegeSenior) + + const highPriorityRule = factories.rule({ conditions, event: midPriorityEvent, priority: 50 }) + engine.addRule(highPriorityRule) + + const midPriorityRule = factories.rule({ conditions, event: highPriorityEvent, priority: 100 }) + engine.addRule(midPriorityRule) + + const lowPriorityRule = factories.rule({ conditions, event: lowestPriorityEvent, priority: 1 }) + engine.addRule(lowPriorityRule) + engine.addFact('age', factSpy) engine.on('success', eventSpy) } @@ -54,4 +59,38 @@ describe('Engine: cache', () => { engine.addRule(factories.rule()) expect(engine.prioritizedRules).to.be.null() }) + + it('resolves all events returning promises before executing the next rule', async () => { + setup() + + const highPrioritySpy = sandbox.spy() + const midPrioritySpy = sandbox.spy() + const lowPrioritySpy = sandbox.spy() + + engine.on(highPriorityEvent.type, () => { + return new Promise(function (resolve) { + setTimeout(function () { + highPrioritySpy() + resolve() + }, 10) // wait longest + }) + }) + engine.on(midPriorityEvent.type, () => { + return new Promise(function (resolve) { + setTimeout(function () { + midPrioritySpy() + resolve() + }, 5) // wait half as much + }) + }) + + engine.on(lowestPriorityEvent.type, () => { + lowPrioritySpy() // emit immediately. this event should still be triggered last + }) + + await engine.run() + + expect(highPrioritySpy).to.be.calledBefore(midPrioritySpy) + expect(midPrioritySpy).to.be.calledBefore(lowPrioritySpy) + }) }) diff --git a/test/engine-run.test.js b/test/engine-run.test.js index d508dcf..d3282c1 100644 --- a/test/engine-run.test.js +++ b/test/engine-run.test.js @@ -94,7 +94,6 @@ describe('Engine: run', () => { describe('facts updated during run', () => { beforeEach(() => { engine.on('success', (event, almanac, ruleResult) => { - console.log(ruleResult) // Assign unique runtime facts per event almanac.addRuntimeFact(`runtime-fact-${event.type}`, ruleResult.conditions.any[0].value) }) @@ -102,7 +101,6 @@ describe('Engine: run', () => { it('returns an almanac with runtime facts added', () => { return engine.run({ age: 90 }).then(results => { - console.log(results) return Promise.all([ results.almanac.factValue('runtime-fact-generic1'), results.almanac.factValue('runtime-fact-generic2') From 163ee10cb9b41d4701caff48ab68b8e4d3919e9e Mon Sep 17 00:00:00 2001 From: Cache Hamm Date: Sat, 28 Nov 2020 21:22:15 -0700 Subject: [PATCH 02/18] 5.0.5-next-major-alpha1.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8a0f26a..4298a39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "json-rules-engine", - "version": "5.0.4", + "version": "5.0.5-next-major-alpha1.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index e8008cf..4121278 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "json-rules-engine", - "version": "5.0.4", + "version": "5.0.5-next-major-alpha1.0", "description": "Rules Engine expressed in simple json", "main": "dist/index.js", "types": "types/index.d.ts", From 0fe1d6dd165b5b96f9d114c581f5de0eb02019b7 Mon Sep 17 00:00:00 2001 From: Cache Hamm Date: Sun, 29 Nov 2020 06:56:54 -0700 Subject: [PATCH 03/18] 6.0.0-alpha-1 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4298a39..9bd8330 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "json-rules-engine", - "version": "5.0.5-next-major-alpha1.0", + "version": "6.0.0-alpha-1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 4121278..7ebe274 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "json-rules-engine", - "version": "5.0.5-next-major-alpha1.0", + "version": "6.0.0-alpha-1", "description": "Rules Engine expressed in simple json", "main": "dist/index.js", "types": "types/index.d.ts", From 51a38a88c162ff6272a1916d9fb5dd56343ae2fe Mon Sep 17 00:00:00 2001 From: Cache Hamm Date: Sun, 29 Nov 2020 07:26:48 -0700 Subject: [PATCH 04/18] Convert rule event emissions to use EventEmitter2 and emitAsync --- package-lock.json | 26 +++++++++++++------------- src/rule.js | 10 ++++------ test/rule.test.js | 39 +++++++++++++++++++++++++++++++++++---- 3 files changed, 52 insertions(+), 23 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9bd8330..1d5809d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -197,7 +197,7 @@ }, "to-fast-properties": { "version": "2.0.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", "dev": true } @@ -2549,7 +2549,7 @@ }, "is-extglob": { "version": "2.1.1", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/is-extglob/-/is-extglob-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, @@ -2733,7 +2733,7 @@ "dependencies": { "find-up": { "version": "2.1.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/find-up/-/find-up-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { @@ -2742,7 +2742,7 @@ }, "locate-path": { "version": "2.0.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/locate-path/-/locate-path-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, "requires": { @@ -2761,7 +2761,7 @@ }, "p-locate": { "version": "2.0.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/p-locate/-/p-locate-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { @@ -2770,13 +2770,13 @@ }, "p-try": { "version": "1.0.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/p-try/-/p-try-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "dev": true }, "pkg-dir": { "version": "2.0.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/pkg-dir/-/pkg-dir-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", "dev": true, "requires": { @@ -2824,7 +2824,7 @@ "dependencies": { "doctrine": { "version": "1.5.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/doctrine/-/doctrine-1.5.0.tgz", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", "dev": true, "requires": { @@ -3119,7 +3119,7 @@ }, "is-extglob": { "version": "2.1.1", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/is-extglob/-/is-extglob-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, @@ -5898,7 +5898,7 @@ "dependencies": { "isarray": { "version": "0.0.1", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/isarray/-/isarray-0.0.1.tgz", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", "dev": true } @@ -5970,7 +5970,7 @@ }, "parse-json": { "version": "4.0.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/parse-json/-/parse-json-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", "dev": true, "requires": { @@ -7363,13 +7363,13 @@ "dependencies": { "ansi-regex": { "version": "3.0.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/ansi-regex/-/ansi-regex-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, "strip-ansi": { "version": "4.0.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/strip-ansi/-/strip-ansi-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { diff --git a/src/rule.js b/src/rule.js index d34dac0..73880e3 100644 --- a/src/rule.js +++ b/src/rule.js @@ -2,8 +2,8 @@ import Condition from './condition' import RuleResult from './rule-result' -import { EventEmitter } from 'events' import debug from './debug' +import EventEmitter from 'eventemitter2' class Rule extends EventEmitter { /** @@ -262,14 +262,12 @@ class Rule extends EventEmitter { /** * Emits based on rule evaluation result, and decorates ruleResult with 'result' property - * @param {Boolean} result + * @param {RuleResult} ruleResult */ const processResult = (result) => { ruleResult.setResult(result) - - if (result) this.emit('success', ruleResult.event, almanac, ruleResult) - else this.emit('failure', ruleResult.event, almanac, ruleResult) - return ruleResult + const event = result ? 'success' : 'failure' + return this.emitAsync(event, ruleResult.event, almanac, ruleResult).then(() => ruleResult) } if (ruleResult.conditions.any) { diff --git a/test/rule.test.js b/test/rule.test.js index fe48e7f..ea7d56d 100644 --- a/test/rule.test.js +++ b/test/rule.test.js @@ -188,17 +188,48 @@ describe('Rule', () => { }) describe('evaluate()', () => { - it('evalutes truthy when there are no conditions', async () => { - const eventSpy = sinon.spy() + function setup () { const engine = new Engine() const rule = new Rule() rule.setConditions({ all: [] }) engine.addRule(rule) - engine.on('success', eventSpy) + + return { engine, rule } + } + it('evalutes truthy when there are no conditions', async () => { + const engineSuccessSpy = sinon.spy() + const { engine } = setup() + + engine.on('success', engineSuccessSpy) + await engine.run() - expect(eventSpy).to.have.been.calledOnce() + + expect(engineSuccessSpy).to.have.been.calledOnce() + }) + + it('waits for all on("success") event promises to be resolved', async () => { + const engineSuccessSpy = sinon.spy() + const ruleSuccessSpy = sinon.spy() + const engineRunSpy = sinon.spy() + const { engine, rule } = setup() + rule.on('success', () => { + return new Promise(function (resolve) { + setTimeout(function () { + ruleSuccessSpy() + resolve() + }, 5) + }) + }) + engine.on('success', engineSuccessSpy) + + await engine.run().then(() => engineRunSpy()) + + expect(ruleSuccessSpy).to.have.been.calledOnce() + expect(engineSuccessSpy).to.have.been.calledOnce() + expect(ruleSuccessSpy).to.have.been.calledBefore(engineRunSpy) + expect(ruleSuccessSpy).to.have.been.calledBefore(engineSuccessSpy) }) }) From 5e988fdf124ad6f75b768f9fc2dc7fa980d8fd43 Mon Sep 17 00:00:00 2001 From: Cache Hamm Date: Sun, 29 Nov 2020 07:27:34 -0700 Subject: [PATCH 05/18] Remove unused dependency "events" --- package-lock.json | 5 ----- package.json | 1 - 2 files changed, 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1d5809d..ee51660 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3001,11 +3001,6 @@ "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.3.tgz", "integrity": "sha512-t0A2msp6BzOf+QAcI6z9XMktLj52OjGQg+8SJH6v5+3uxNpWYRR3wQmfA+6xtMU9kOC59qk9licus5dYcrYkMQ==" }, - "events": { - "version": "3.2.0", - "resolved": "http://artifactory.shuttercorp.net/artifactory/api/npm/npm-composite/events/-/events-3.2.0.tgz", - "integrity": "sha1-k7h8GPjvzUICpGGuxN/AVWtjk3k=" - }, "expand-brackets": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", diff --git a/package.json b/package.json index 7ebe274..fd57169 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,6 @@ "dependencies": { "clone": "^2.1.2", "eventemitter2": "6.4.3", - "events": "^3.2.0", "hash-it": "^4.0.5", "jsonpath-plus": "^4.0.0", "lodash.isobjectlike": "^4.0.0" From 4da04d64c3a7021a61cf4596e2faa15b66257602 Mon Sep 17 00:00:00 2001 From: Cache Hamm Date: Sun, 29 Nov 2020 09:26:03 -0700 Subject: [PATCH 06/18] 6.0.0-alpha-2 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index ee51660..e896219 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "json-rules-engine", - "version": "6.0.0-alpha-1", + "version": "6.0.0-alpha-2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index fd57169..4a3f4da 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "json-rules-engine", - "version": "6.0.0-alpha-1", + "version": "6.0.0-alpha-2", "description": "Rules Engine expressed in simple json", "main": "dist/index.js", "types": "types/index.d.ts", From 054e1965961afb2529ffc9711d81c6d9d65d8896 Mon Sep 17 00:00:00 2001 From: Cache Hamm Date: Sun, 29 Nov 2020 09:34:30 -0700 Subject: [PATCH 07/18] allow flexbile semver range on eventemitter2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4a3f4da..da90bdc 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ }, "dependencies": { "clone": "^2.1.2", - "eventemitter2": "6.4.3", + "eventemitter2": "^6.4.3", "hash-it": "^4.0.5", "jsonpath-plus": "^4.0.0", "lodash.isobjectlike": "^4.0.0" From 80e84b3ada7df7fca20aba21e37d8b5ee4257023 Mon Sep 17 00:00:00 2001 From: Cache Hamm Date: Sun, 29 Nov 2020 11:17:19 -0700 Subject: [PATCH 08/18] Rename rule.event => rule.ruleEvent for event-emitter-2 compatibility --- src/engine.js | 13 +++++++------ src/rule.js | 8 ++++---- test/engine-failure.test.js | 2 +- test/rule.test.js | 8 ++++---- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/engine.js b/src/engine.js index dd63611..18eadc3 100644 --- a/src/engine.js +++ b/src/engine.js @@ -40,13 +40,14 @@ class Engine extends EventEmitter { */ addRule (properties) { if (!properties) throw new Error('Engine: addRule() requires options') - if (!Object.prototype.hasOwnProperty.call(properties, 'conditions')) throw new Error('Engine: addRule() argument requires "conditions" property') - if (!Object.prototype.hasOwnProperty.call(properties, 'event')) throw new Error('Engine: addRule() argument requires "event" property') let rule if (properties instanceof Rule) { rule = properties } else { + if (!Object.prototype.hasOwnProperty.call(properties, 'event')) throw new Error('Engine: addRule() argument requires "event" property') + if (!Object.prototype.hasOwnProperty.call(properties, 'conditions')) throw new Error('Engine: addRule() argument requires "conditions" property') + rule = new Rule(properties) } rule.setEngine(this) @@ -191,13 +192,13 @@ class Engine extends EventEmitter { return rule.evaluate(almanac).then((ruleResult) => { debug(`engine::run ruleResult:${ruleResult.result}`) if (ruleResult.result) { - almanac.factValue('success-events', { event: rule.event }) + almanac.factValue('success-events', { event: ruleResult.event }) return Promise.all([ - this.emitAsync('success', rule.event, almanac, ruleResult), - this.emitAsync(rule.event.type, rule.event.params, almanac, ruleResult) + this.emitAsync('success', ruleResult.event, almanac, ruleResult), + this.emitAsync(ruleResult.event.type, ruleResult.event.params, almanac, ruleResult) ]) } else { - return this.emitAsync('failure', rule.event, almanac, ruleResult) + return this.emitAsync('failure', ruleResult.event, almanac, ruleResult) } }) })) diff --git a/src/rule.js b/src/rule.js index 73880e3..1627397 100644 --- a/src/rule.js +++ b/src/rule.js @@ -86,10 +86,10 @@ class Rule extends EventEmitter { setEvent (event) { if (!event) throw new Error('Rule: setEvent() requires event object') if (!Object.prototype.hasOwnProperty.call(event, 'type')) throw new Error('Rule: setEvent() requires event object with "type" property') - this.event = { + this.ruleEvent = { type: event.type } - if (event.params) this.event.params = event.params + if (event.params) this.ruleEvent.params = event.params return this } @@ -107,7 +107,7 @@ class Rule extends EventEmitter { const props = { conditions: this.conditions.toJSON(false), priority: this.priority, - event: this.event, + event: this.ruleEvent, name: this.name } if (stringify) { @@ -148,7 +148,7 @@ class Rule extends EventEmitter { * @return {Promise(RuleResult)} rule evaluation result */ evaluate (almanac) { - const ruleResult = new RuleResult(this.conditions, this.event, this.priority, this.name) + const ruleResult = new RuleResult(this.conditions, this.ruleEvent, this.priority, this.name) /** * Evaluates the rule conditions diff --git a/test/engine-failure.test.js b/test/engine-failure.test.js index 1f1907b..86b3f23 100644 --- a/test/engine-failure.test.js +++ b/test/engine-failure.test.js @@ -32,7 +32,7 @@ describe('Engine: failure', () => { const failureSpy = sandbox.spy() engine.on('failure', failureSpy) await engine.run() - expect(failureSpy).to.have.been.calledWith(engine.rules[0].event) + expect(failureSpy).to.have.been.calledWith(engine.rules[0].ruleEvent) }) it('does not emit when a rule passes', async () => { diff --git a/test/rule.test.js b/test/rule.test.js index ea7d56d..5a4e168 100644 --- a/test/rule.test.js +++ b/test/rule.test.js @@ -29,7 +29,7 @@ describe('Rule', () => { const rule = new Rule(opts) expect(rule.priority).to.eql(opts.priority) expect(rule.conditions).to.eql(opts.conditions) - expect(rule.event).to.eql(opts.event) + expect(rule.ruleEvent).to.eql(opts.event) expect(rule.name).to.eql(opts.name) }) @@ -51,7 +51,7 @@ describe('Rule', () => { const rule = new Rule(json) expect(rule.priority).to.eql(opts.priority) expect(rule.conditions).to.eql(opts.conditions) - expect(rule.event).to.eql(opts.event) + expect(rule.ruleEvent).to.eql(opts.event) expect(rule.name).to.eql(opts.name) }) }) @@ -287,7 +287,7 @@ describe('Rule', () => { const hydratedRule = new Rule(jsonString) expect(hydratedRule.conditions).to.eql(rule.conditions) expect(hydratedRule.priority).to.eql(rule.priority) - expect(hydratedRule.event).to.eql(rule.event) + expect(hydratedRule.ruleEvent).to.eql(rule.ruleEvent) expect(hydratedRule.name).to.eql(rule.name) }) @@ -298,7 +298,7 @@ describe('Rule', () => { const hydratedRule = new Rule(json) expect(hydratedRule.conditions).to.eql(rule.conditions) expect(hydratedRule.priority).to.eql(rule.priority) - expect(hydratedRule.event).to.eql(rule.event) + expect(hydratedRule.ruleEvent).to.eql(rule.ruleEvent) expect(hydratedRule.name).to.eql(rule.name) }) }) From 43f09f7d427f8808dde44f81d7e61f239a2b1073 Mon Sep 17 00:00:00 2001 From: Cache Hamm Date: Sun, 29 Nov 2020 11:17:26 -0700 Subject: [PATCH 09/18] 6.0.0-alpha-3 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index e896219..89cfed8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "json-rules-engine", - "version": "6.0.0-alpha-2", + "version": "6.0.0-alpha-3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index da90bdc..cd7c509 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "json-rules-engine", - "version": "6.0.0-alpha-2", + "version": "6.0.0-alpha-3", "description": "Rules Engine expressed in simple json", "main": "dist/index.js", "types": "types/index.d.ts", From 9f318f814e7affb71ff0560fcd250488b5d924e5 Mon Sep 17 00:00:00 2001 From: Cache Hamm Date: Sun, 29 Nov 2020 14:29:43 -0700 Subject: [PATCH 10/18] Update rule chaining example to demonstrate asychronous callbacks --- examples/07-rule-chaining.js | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/examples/07-rule-chaining.js b/examples/07-rule-chaining.js index bc99c76..d4d8ffa 100644 --- a/examples/07-rule-chaining.js +++ b/examples/07-rule-chaining.js @@ -37,8 +37,16 @@ const drinkRule = { }, event: { type: 'drinks-screwdrivers' }, priority: 10, // IMPORTANT! Set a higher priority for the drinkRule, so it runs first - onSuccess: function (event, almanac) { + onSuccess: async function (event, almanac) { almanac.addRuntimeFact('screwdriverAficionado', true) + + // asychronous operations can be performed within callbacks + // execution will not proceed until returned promises are resolved + const zipCode = await almanac.factValue('zipCode') + if (zipCode > 80014 && zipCode < 80642) { + const hometown = 'Denver' + almanac.addRuntimeFact('hometown', hometown) // overrides default 'hometown' fact value + } }, onFailure: function (event, almanac) { almanac.addRuntimeFact('screwdriverAficionado', false) @@ -61,6 +69,10 @@ const inviteRule = { fact: 'isSociable', operator: 'equal', value: true + }, { + fact: 'hometown', + operator: 'equal', + value: 'Denver' }] }, event: { type: 'invite-to-screwdriver-social' }, @@ -81,7 +93,7 @@ engine }) // define fact(s) known at runtime -facts = { accountId: 'washington', drinksOrangeJuice: true, enjoysVodka: true, isSociable: true } +facts = { accountId: 'washington', drinksOrangeJuice: true, enjoysVodka: true, isSociable: true, zipCode: 80211, hometown: 'unknown' } engine .run(facts) // first run, using washington's facts .then((results) => { @@ -90,10 +102,10 @@ engine return results.almanac.factValue('screwdriverAficionado') }) .then(isScrewdriverAficionado => { - console.log(`${facts.accountId} ${isScrewdriverAficionado ? 'IS'.green : 'IS NOT'.red} a screwdriver aficionado`) + console.log(`${facts.accountId} ${isScrewdriverAficionado ? 'IS'.green : 'IS NOT'.red} a screwdriver aficionado in Denver`) }) .then(() => { - facts = { accountId: 'jefferson', drinksOrangeJuice: true, enjoysVodka: false, isSociable: true } + facts = { accountId: 'jefferson', drinksOrangeJuice: true, enjoysVodka: false, isSociable: true, zipCode: 80248, hometown: 'unknown' } return engine.run(facts) // second run, using jefferson's facts; facts & evaluation are independent of the first run }) .then((results) => { @@ -102,7 +114,7 @@ engine return results.almanac.factValue('screwdriverAficionado') }) .then(isScrewdriverAficionado => { - console.log(`${facts.accountId} ${isScrewdriverAficionado ? 'IS'.green : 'IS NOT'.red} a screwdriver aficionado`) + console.log(`${facts.accountId} ${isScrewdriverAficionado ? 'IS'.green : 'IS NOT'.red} a screwdriver aficionado in Denver`) }) .catch(console.log) @@ -111,8 +123,8 @@ engine * * washington DID meet conditions for the drinks-screwdrivers rule. * washington DID meet conditions for the invite-to-screwdriver-social rule. - * washington IS a screwdriver aficionado + * washington IS a screwdriver aficionado in Denver * jefferson did NOT meet conditions for the drinks-screwdrivers rule. * jefferson did NOT meet conditions for the invite-to-screwdriver-social rule. - * jefferson IS NOT a screwdriver aficionado + * jefferson IS NOT a screwdriver aficionado in Denver */ From 9d4acb5d198e0919ffa783d1e23a6a27afc20e00 Mon Sep 17 00:00:00 2001 From: Cache Hamm Date: Sun, 29 Nov 2020 20:06:12 -0700 Subject: [PATCH 11/18] Add rule accessors --- docs/rules.md | 12 ++++++++++++ src/rule.js | 32 ++++++++++++++++++++++++++++++++ test/rule.test.js | 24 ++++++++++++++++++++++++ 3 files changed, 68 insertions(+) diff --git a/docs/rules.md b/docs/rules.md index 7111553..2fdce40 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -60,14 +60,26 @@ let rule = new Rule(options) Helper for setting rule conditions. Alternative to passing the `conditions` option to the rule constructor. +### getConditions() -> Object + +Retrieves rule condition set by constructor or `setCondition()` + ### setEvent(Object event) Helper for setting rule event. Alternative to passing the `event` option to the rule constructor. +### getEvent() -> Object + +Retrieves rule event set by constructor or `setEvent()` + ### setPriority(Integer priority = 1) Helper for setting rule priority. Alternative to passing the `priority` option to the rule constructor. +### getPriority() -> Integer + +Retrieves rule priority set by constructor or `setPriority()` + ### toJSON(Boolean stringify = true) Serializes the rule into a JSON string. Often used when persisting rules. diff --git a/src/rule.js b/src/rule.js index 1627397..5db0191 100644 --- a/src/rule.js +++ b/src/rule.js @@ -93,6 +93,38 @@ class Rule extends EventEmitter { return this } + /** + * returns the event object + * @returns {Object} event + */ + getEvent () { + return this.ruleEvent + } + + /** + * returns the priority + * @returns {Number} priority + */ + getPriority () { + return this.priority + } + + /** + * returns the event object + * @returns {Object} event + */ + getConditions () { + return this.conditions + } + + /** + * returns the engine object + * @returns {Object} engine + */ + getEngine () { + return this.engine + } + /** * Sets the engine to run the rules under * @param {object} engine diff --git a/test/rule.test.js b/test/rule.test.js index 5a4e168..382aead 100644 --- a/test/rule.test.js +++ b/test/rule.test.js @@ -119,6 +119,30 @@ describe('Rule', () => { }) }) + describe('accessors', () => { + it('retrieves event', () => { + const event = { type: 'e', params: { a: 'b' } } + rule.setEvent(event) + expect(rule.getEvent()).to.deep.equal(event) + }) + + it('retrieves priority', () => { + const priority = 100 + rule.setPriority(priority) + expect(rule.getPriority()).to.equal(priority) + }) + + it('retrieves conditions', () => { + const condition = { all: [] } + rule.setConditions(condition) + expect(rule.getConditions()).to.deep.equal({ + all: [], + operator: 'all', + priority: 1 + }) + }) + }) + describe('setName', () => { it('defaults to undefined', () => { expect(rule.name).to.equal(undefined) From aa9859d5b1cda4c804b64d1b8d4927573bce4e5d Mon Sep 17 00:00:00 2001 From: Cache Hamm Date: Sun, 29 Nov 2020 20:11:39 -0700 Subject: [PATCH 12/18] Draft of 6.0.0 changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 181fe45..e003583 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +#### 6.0. / 2020-12-XX + * BREAKING CHANGES + * Private `rule.event` property renamed. Use `rule.getEvent()` to avoid breaking changes in the future. + * 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. + #### 5.0.4 / 2020-09-26 * Upgrade dependencies to latest From 0bdccb3a357d921973e0ec86eb956c35fc1770dc Mon Sep 17 00:00:00 2001 From: Cache Hamm Date: Sun, 29 Nov 2020 20:33:04 -0700 Subject: [PATCH 13/18] Update rule chaining example with async/await and accountInformation query --- examples/07-rule-chaining.js | 81 +++++++++++++++++------------------- 1 file changed, 39 insertions(+), 42 deletions(-) diff --git a/examples/07-rule-chaining.js b/examples/07-rule-chaining.js index 45c345c..6f9aadc 100644 --- a/examples/07-rule-chaining.js +++ b/examples/07-rule-chaining.js @@ -13,6 +13,7 @@ require('colors') const { Engine } = require('json-rules-engine') +const { getAccountInformation } = require('./support/account-api-client') /** * Setup a new engine @@ -40,12 +41,10 @@ const drinkRule = { almanac.addRuntimeFact('screwdriverAficionado', true) // asychronous operations can be performed within callbacks - // execution will not proceed until returned promises are resolved - const zipCode = await almanac.factValue('zipCode') - if (zipCode > 80014 && zipCode < 80642) { - const hometown = 'Denver' - almanac.addRuntimeFact('hometown', hometown) // overrides default 'hometown' fact value - } + // engine execution will not proceed until the returned promises is resolved + const accountId = await almanac.factValue('accountId') + const accountInfo = await getAccountInformation(accountId) + almanac.addRuntimeFact('accountInfo', accountInfo) }, onFailure: function (event, almanac) { almanac.addRuntimeFact('screwdriverAficionado', false) @@ -69,9 +68,10 @@ const inviteRule = { operator: 'equal', value: true }, { - fact: 'hometown', + fact: 'accountInfo', + path: '$.company', operator: 'equal', - value: 'Denver' + value: 'microsoft' }] }, event: { type: 'invite-to-screwdriver-social' }, @@ -82,48 +82,45 @@ engine.addRule(inviteRule) /** * Register listeners with the engine for rule success and failure */ -let facts engine - .on('success', (event, almanac) => { - console.log(facts.accountId + ' DID '.green + 'meet conditions for the ' + event.type.underline + ' rule.') + .on('success', async (event, almanac) => { + const accountInfo = await almanac.factValue('accountInfo') + const accountId = await almanac.factValue('accountId') + console.log(`${accountId}(${accountInfo.company}) ` + 'DID'.green + ` meet conditions for the ${event.type.underline} rule.`) }) - .on('failure', event => { - console.log(facts.accountId + ' did ' + 'NOT'.red + ' meet conditions for the ' + event.type.underline + ' rule.') + .on('failure', async (event, almanac) => { + const accountId = await almanac.factValue('accountId') + console.log(`${accountId} did ` + 'NOT'.red + ` meet conditions for the ${event.type.underline} rule.`) }) -// define fact(s) known at runtime -facts = { accountId: 'washington', drinksOrangeJuice: true, enjoysVodka: true, isSociable: true, zipCode: 80211, hometown: 'unknown' } -engine - .run(facts) // first run, using washington's facts - .then((results) => { - // access whether washington is a screwdriverAficionado, - // which was determined at runtime via the rules `drinkRules` - return results.almanac.factValue('screwdriverAficionado') - }) - .then(isScrewdriverAficionado => { - console.log(`${facts.accountId} ${isScrewdriverAficionado ? 'IS'.green : 'IS NOT'.red} a screwdriver aficionado in Denver`) - }) - .then(() => { - facts = { accountId: 'jefferson', drinksOrangeJuice: true, enjoysVodka: false, isSociable: true, zipCode: 80248, hometown: 'unknown' } - return engine.run(facts) // second run, using jefferson's facts; facts & evaluation are independent of the first run - }) - .then((results) => { - // access whether jefferson is a screwdriverAficionado, - // which was determined at runtime via the rules `drinkRules` - return results.almanac.factValue('screwdriverAficionado') - }) - .then(isScrewdriverAficionado => { - console.log(`${facts.accountId} ${isScrewdriverAficionado ? 'IS'.green : 'IS NOT'.red} a screwdriver aficionado in Denver`) - }) - .catch(console.log) +async function run () { + // define fact(s) known at runtime + let facts = { accountId: 'washington', drinksOrangeJuice: true, enjoysVodka: true, isSociable: true, accountInfo: {} } + + // first run, using washington's facts + let results = await engine.run(facts) + + // isScrewdriverAficionado was a fact set by engine.run() + let isScrewdriverAficionado = results.almanac.factValue('screwdriverAficionado') + console.log(`${facts.accountId} ${isScrewdriverAficionado ? 'IS'.green : 'IS NOT'.red} a screwdriver aficionado`) + + facts = { accountId: 'jefferson', drinksOrangeJuice: true, enjoysVodka: false, isSociable: true, accountInfo: {} } + results = await engine.run(facts) // second run, using jefferson's facts; facts & evaluation are independent of the first run + + isScrewdriverAficionado = await results.almanac.factValue('screwdriverAficionado') + console.log(`${facts.accountId} ${isScrewdriverAficionado ? 'IS'.green : 'IS NOT'.red} a screwdriver aficionado`) +} + +run().catch(console.log) /* * OUTPUT: * - * washington DID meet conditions for the drinks-screwdrivers rule. - * washington DID meet conditions for the invite-to-screwdriver-social rule. - * washington IS a screwdriver aficionado in Denver + * loading account information for "washington" + * washington(microsoft) DID meet conditions for the drinks-screwdrivers rule. + * washington(microsoft) DID meet conditions for the invite-to-screwdriver-social rule. + * washington IS a screwdriver aficionado * jefferson did NOT meet conditions for the drinks-screwdrivers rule. * jefferson did NOT meet conditions for the invite-to-screwdriver-social rule. - * jefferson IS NOT a screwdriver aficionado in Denver + * jefferson IS NOT a screwdriver aficionado */ From b4bfae12346f4c94ef588b3cb364245993b09163 Mon Sep 17 00:00:00 2001 From: Cache Hamm Date: Sun, 29 Nov 2020 20:36:35 -0700 Subject: [PATCH 14/18] Update docs for event promise support --- docs/engine.md | 4 ++-- docs/rules.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/engine.md b/docs/engine.md index 23fd467..c96b72d 100644 --- a/docs/engine.md +++ b/docs/engine.md @@ -176,7 +176,7 @@ There are two generic event emissions that trigger automatically: #### ```engine.on('success', Function(Object event, Almanac almanac, RuleResult ruleResult))``` -Fires when a rule passes. The callback will receive the event object, the current [Almanac](./almanac.md), and the [Rule Result](./rules.md#rule-results). +Fires when a rule passes. The callback will receive the event object, the current [Almanac](./almanac.md), and the [Rule Result](./rules.md#rule-results). Any promise returned by the callback will be waited on to resolve before execution continues. ```js engine.on('success', function(event, almanac, ruleResult) { @@ -186,7 +186,7 @@ engine.on('success', function(event, almanac, ruleResult) { #### ```engine.on('failure', Function(Object event, Almanac almanac, RuleResult ruleResult))``` -Companion to 'success', except fires when a rule fails. The callback will receive the event object, the current [Almanac](./almanac.md), and the [Rule Result](./rules.md#rule-results). +Companion to 'success', except fires when a rule fails. The callback will receive the event object, the current [Almanac](./almanac.md), and the [Rule Result](./rules.md#rule-results). Any promise returned by the callback will be waited on to resolve before execution continues. ```js engine.on('failure', function(event, almanac, ruleResult) { diff --git a/docs/rules.md b/docs/rules.md index 2fdce40..3379854 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -50,9 +50,9 @@ let rule = new Rule(options) **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.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. Any promise returned by the callback will be waited on to resolve before execution continues. -**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. +**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. Any promise returned by the callback will be waited on to resolve before execution continues. **options.name** : `[Any]` A way of naming your rules, allowing them to be easily identifiable in [Rule Results](#rule-results). This is usually of type `String`, but could also be `Object`, `Array`, or `Number`. Note that the name need not be unique, and that it has no impact on execution of the rule. From e18aed474120f1f36ca1a6ea491f0f2434d0bf12 Mon Sep 17 00:00:00 2001 From: Cache Hamm Date: Mon, 7 Dec 2020 08:07:00 -0700 Subject: [PATCH 15/18] Add acceptance tests --- docs/rules.md | 2 +- package.json | 1 + src/almanac.js | 2 +- src/condition.js | 2 +- src/engine.js | 1 - test/acceptance/acceptance.js | 176 ++++++++++++++++++++++++++++++++++ 6 files changed, 180 insertions(+), 4 deletions(-) create mode 100644 test/acceptance/acceptance.js diff --git a/docs/rules.md b/docs/rules.md index 3379854..6110936 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -219,7 +219,7 @@ For an example, see [fact-dependency](../examples/04-fact-dependency.js) ### Comparing facts -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. +Sometimes it is necessary to compare facts against other 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 diff --git a/package.json b/package.json index d4ea653..036d066 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ ], "file": "./test/support/bootstrap.js", "checkLeaks": true, + "recursive": true, "globals": [ "expect" ] diff --git a/src/almanac.js b/src/almanac.js index 30b4625..296ff94 100644 --- a/src/almanac.js +++ b/src/almanac.js @@ -114,7 +114,7 @@ export default class Almanac { .then(factValue => { if (isObjectLike(factValue)) { const pathValue = JSONPath({ path, json: factValue, wrap: false }) - debug(`condition::evaluate extracting object property ${path}, received: ${pathValue}`) + debug(`condition::evaluate extracting object property ${path}, received: ${JSON.stringify(pathValue)}`) return pathValue } else { debug(`condition::evaluate could not compute object path(${path}) of non-object: ${factValue} <${typeof factValue}>; continuing with ${factValue}`) diff --git a/src/condition.js b/src/condition.js index cd6f428..98a7690 100644 --- a/src/condition.js +++ b/src/condition.js @@ -101,7 +101,7 @@ export default class Condition { return almanac.factValue(this.fact, this.params, this.path) .then(leftHandSideValue => { const result = op.evaluate(leftHandSideValue, rightHandSideValue) - debug(`condition::evaluate <${leftHandSideValue} ${this.operator} ${rightHandSideValue}?> (${result})`) + debug(`condition::evaluate <${JSON.stringify(leftHandSideValue)} ${this.operator} ${JSON.stringify(rightHandSideValue)}?> (${result})`) return { result, leftHandSideValue, rightHandSideValue, operator: this.operator } }) }) diff --git a/src/engine.js b/src/engine.js index 18eadc3..2a3c59e 100644 --- a/src/engine.js +++ b/src/engine.js @@ -212,7 +212,6 @@ class Engine extends EventEmitter { */ run (runtimeFacts = {}) { debug('engine::run started') - debug('engine::run runtimeFacts:', runtimeFacts) runtimeFacts['success-events'] = new Fact('success-events', SuccessEventFact(), { cache: false }) this.status = RUNNING const almanac = new Almanac(this.facts, runtimeFacts, { allowUndefinedFacts: this.allowUndefinedFacts }) diff --git a/test/acceptance/acceptance.js b/test/acceptance/acceptance.js new file mode 100644 index 0000000..01e3f8f --- /dev/null +++ b/test/acceptance/acceptance.js @@ -0,0 +1,176 @@ +'use strict' + +import sinon from 'sinon' +import { expect } from 'chai' +import { Engine } from '../../src/index' + +/** + * acceptance tests are intended to use features that, when used in combination, + * could cause integration bugs not caught by the rest of the test suite + */ +describe('Acceptance', () => { + let sandbox + before(() => { + sandbox = sinon.createSandbox() + }) + afterEach(() => { + sandbox.restore() + }) + const factParam = 1 + const event1 = { + type: 'event-1', + params: { + eventParam: 1 + } + } + const event2 = { + type: 'event-2' + } + const expectedFirstRuleResult = { + all: [{ + fact: 'high-priority', + params: { + factParam + }, + operator: 'contains', + path: '$.values', + value: 2, + factResult: [2], + result: true + }, + { + fact: 'low-priority', + operator: 'in', + value: [2], + factResult: 2, + result: true + } + ], + operator: 'all', + priority: 1 + } + let successSpy + let failureSpy + let highPrioritySpy + let lowPrioritySpy + + function delay (value) { + return new Promise(resolve => setTimeout(() => resolve(value), 5)) + } + + function setup (options = {}) { + const engine = new Engine() + highPrioritySpy = sandbox.spy() + lowPrioritySpy = sandbox.spy() + + engine.addRule({ + name: 'first', + priority: 10, + conditions: { + all: [{ + fact: 'high-priority', + params: { + factParam + }, + operator: 'contains', + path: '$.values', + value: options.highPriorityValue + }, { + fact: 'low-priority', + operator: 'in', + value: options.lowPriorityValue + }] + }, + event: event1, + onSuccess: async (event, almanac, ruleResults) => { + expect(ruleResults.name).to.equal('first') + expect(ruleResults.event).to.deep.equal(event1) + expect(ruleResults.priority).to.equal(10) + expect(ruleResults.conditions).to.deep.equal(expectedFirstRuleResult) + + return delay(almanac.addRuntimeFact('rule-created-fact', { array: options.highPriorityValue })) + } + }) + + engine.addRule({ + name: 'second', + priority: 1, + conditions: { + all: [{ + fact: 'high-priority', + params: { + factParam + }, + operator: 'containsDivisibleValuesOf', + path: '$.values', + value: { + fact: 'rule-created-fact', + path: '$.array' // set by 'success' of first rule + } + }] + }, + event: event2 + }) + + engine.addOperator('containsDivisibleValuesOf', (factValue, jsonValue) => { + return factValue.some(v => v % jsonValue === 0) + }) + + engine.addFact('high-priority', async function (params, almanac) { + highPrioritySpy(params) + const idx = await almanac.factValue('sub-fact') + return delay({ values: [idx + params.factParam] }) // { values: [baseIndex + factParam] } + }, { priority: 2 }) + + engine.addFact('low-priority', async function (params, almanac) { + lowPrioritySpy(params) + const idx = await almanac.factValue('sub-fact') + return delay(idx + 1) // baseIndex + 1 + }, { priority: 1 }) + + engine.addFact('sub-fact', async function (params, almanac) { + const baseIndex = await almanac.factValue('baseIndex') + return delay(baseIndex) + }) + successSpy = sandbox.spy() + failureSpy = sandbox.spy() + engine.on('success', successSpy) + engine.on('failure', failureSpy) + + return engine + } + + it('succeeds', async () => { + const engine = setup({ + highPriorityValue: 2, + lowPriorityValue: [2] + }) + + const engineResult = await engine.run({ baseIndex: 1 }) + + expect(engineResult.events.length).to.equal(2) + expect(engineResult.events[0]).to.deep.equal(event1) + expect(engineResult.events[1]).to.deep.equal(event2) + expect(successSpy).to.have.been.calledTwice() + expect(successSpy).to.have.been.calledWith(event1) + expect(successSpy).to.have.been.calledWith(event2) + expect(highPrioritySpy).to.have.been.calledBefore(lowPrioritySpy) + expect(failureSpy).to.not.have.been.called() + }) + + it('fails', async () => { + const engine = setup({ + highPriorityValue: 2, + lowPriorityValue: [3] // falsey + }) + + const engineResult = await engine.run({ baseIndex: 1, 'rule-created-fact': '' }) + + expect(engineResult.events.length).to.equal(0) + expect(failureSpy).to.have.been.calledTwice() + expect(failureSpy).to.have.been.calledWith(event1) + expect(failureSpy).to.have.been.calledWith(event2) + expect(highPrioritySpy).to.have.been.calledBefore(lowPrioritySpy) + expect(successSpy).to.not.have.been.called() + }) +}) From 2d72d78e87bf7a8cdc020d652f5ad4ee085d7311 Mon Sep 17 00:00:00 2001 From: Cache Hamm Date: Sat, 12 Dec 2020 15:26:51 -0700 Subject: [PATCH 16/18] Emit "success" engine event after event specific event --- src/engine.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/engine.js b/src/engine.js index 2a3c59e..20e03c3 100644 --- a/src/engine.js +++ b/src/engine.js @@ -192,11 +192,10 @@ class Engine extends EventEmitter { return rule.evaluate(almanac).then((ruleResult) => { debug(`engine::run ruleResult:${ruleResult.result}`) if (ruleResult.result) { - almanac.factValue('success-events', { event: ruleResult.event }) return Promise.all([ - this.emitAsync('success', ruleResult.event, almanac, ruleResult), + almanac.factValue('success-events', { event: ruleResult.event }), this.emitAsync(ruleResult.event.type, ruleResult.event.params, almanac, ruleResult) - ]) + ]).then(() => this.emitAsync('success', ruleResult.event, almanac, ruleResult)) } else { return this.emitAsync('failure', ruleResult.event, almanac, ruleResult) } From 5421e727d5f7731668de54446aa238e35568d327 Mon Sep 17 00:00:00 2001 From: Cache Hamm Date: Sat, 12 Dec 2020 15:32:57 -0700 Subject: [PATCH 17/18] Add next-major to CI --- .github/workflows/node.js.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 673bd33..39f9e87 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -5,9 +5,9 @@ name: Node.js CI on: push: - branches: [ master ] + branches: [ master, next-major ] pull_request: - branches: [ master ] + branches: [ master, next-major ] jobs: build: From 3eac30cc33efe329d6d965323e8030a91d511645 Mon Sep 17 00:00:00 2001 From: Cache Hamm Date: Sat, 12 Dec 2020 15:36:07 -0700 Subject: [PATCH 18/18] Emit "success" engine event before event specific event --- src/engine.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine.js b/src/engine.js index 20e03c3..e63f22f 100644 --- a/src/engine.js +++ b/src/engine.js @@ -194,8 +194,8 @@ class Engine extends EventEmitter { if (ruleResult.result) { return Promise.all([ almanac.factValue('success-events', { event: ruleResult.event }), - this.emitAsync(ruleResult.event.type, ruleResult.event.params, almanac, ruleResult) - ]).then(() => this.emitAsync('success', ruleResult.event, almanac, ruleResult)) + this.emitAsync('success', ruleResult.event, almanac, ruleResult) + ]).then(() => this.emitAsync(ruleResult.event.type, ruleResult.event.params, almanac, ruleResult)) } else { return this.emitAsync('failure', ruleResult.event, almanac, ruleResult) }