| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent a62d1ee commit 0d53cdb
6 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -12,14 +12,14 @@ The almanac for the current engine run is available as arguments passed to the f | |||
| 12 | 12 | ||
| 13 | 13 | ## Methods | |
| 14 | 14 | ||
| 15 | - ### almanac.factValue(Fact fact, Object params) -> Promise | ||
| 15 | + ### almanac.factValue(Fact fact, Object params, String path) -> Promise | ||
| 16 | 16 | ||
| 17 | - Computes the value of the provided fact + params. | ||
| 17 | + Computes the value of the provided fact + params. If "path" is provided, it will be used as a property accessor on the fact's return object. | ||
| 18 | 18 | ||
| 19 | 19 | ```js | |
| 20 | 20 | almanac | |
| 21 | - .factValue('account-information', { accountId: 1 }) | ||
| 22 | - .then( values => console.log(values)) | ||
| 21 | + .factValue('account-information', { accountId: 1 }, '.balance') | ||
| 22 | + .then( value => console.log(value)) | ||
| 23 | 23 | ``` | |
| 24 | 24 | ||
| 25 | 25 | ### almanac.addRuntimeFact(String factId, Mixed value) | |
@@ -37,7 +37,7 @@ almanac.addRuntimeFact('account-id', 1) | |||
| 37 | 37 | The most common use of the almanac is to access data computed by other facts during runtime. This allows | |
| 38 | 38 | leveraging the engine's caching mechanisms to design more efficient rules. | |
| 39 | 39 | ||
| 40 | - The [computed-facts](../examples/computed-facts) example demonstrates a real world application of this technique. | ||
| 40 | + The [fact-dependency](../examples/04-fact-dependency.js) example demonstrates a real world application of this technique. | ||
| 41 | 41 | ||
| 42 | 42 | For example, say there were two facts: _is-funded-account_ and _account-balance_. Both facts depend on the same _account-information_ data set. | |
| 43 | 43 | Using the Almanac, each fact can be defined to call a **base** fact responsible for loading the data. This causes the engine | |
@@ -97,7 +97,7 @@ engine.run({ accountId: 1 }) | |||
| 97 | 97 | When a rule evalutes truthy and its ```event``` is called, new facts may be defined by the event handler. | |
| 98 | 98 | Note that with this technique, the rule priority becomes important; if a rule is expected to | |
| 99 | 99 | define a fact value, it's important that rule be run prior to other rules that reference the fact. To | |
| 100 | - learn more about setting rule priorties, see the [rule documentation](./rule.md). | ||
| 100 | + learn more about setting rule priorities, see the [rule documentation](./rules.md). | ||
| 101 | 101 | ||
| 102 | 102 | ```js | |
| 103 | 103 | engine.on('success', (event, almanac) => { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -82,18 +82,20 @@ engine.addOperator('startsWithLetter', (factValue, jsonValue) => { | |||
| 82 | 82 | }) | |
| 83 | 83 | ||
| 84 | 84 | // and to use the operator... | |
| 85 | - rule.setConditions({ | ||
| 86 | - all: [ | ||
| 87 | - { | ||
| 88 | - fact: 'username', | ||
| 89 | - operator: 'startsWithLetter' // reference the operator name in the rule | ||
| 90 | - value: 'a' | ||
| 91 | - } | ||
| 92 | - ] | ||
| 93 | - }) | ||
| 85 | + let rule = new Rule( | ||
| 86 | + conditions: { | ||
| 87 | + all: [ | ||
| 88 | + { | ||
| 89 | + fact: 'username', | ||
| 90 | + operator: 'startsWithLetter', // reference the operator name in the rule | ||
| 91 | + value: 'a' | ||
| 92 | + } | ||
| 93 | + ] | ||
| 94 | + } | ||
| 95 | + ) | ||
| 94 | 96 | ``` | |
| 95 | 97 | ||
| 96 | - See the [operator example](../examples/custom-operators.js) | ||
| 98 | + See the [operator example](../examples/06-custom-operators.js) | ||
| 97 | 99 | ||
| 98 | 100 | ### engine.run([Object facts], [Object options]) -> Promise (Events) | |
| 99 | 101 | ||
@@ -128,19 +130,21 @@ engine.stop() | |||
| 128 | 130 | ||
| 129 | 131 | ### engine.on(String event, Function callback) -> Engine | |
| 130 | 132 | ||
| 131 | - Listens for events emitted as rules are being evaluated. "event" is determined by [rule.setEvent](./rules.md#seteventobject-event). | ||
| 133 | + Listens for events emitted as rules are being evaluated. "event" is determined by the [rule event](./rules.md#Events). | ||
| 132 | 134 | ||
| 133 | 135 | ```js | |
| 134 | - rule.setEvent({ | ||
| 135 | - type: 'my-event', | ||
| 136 | - params: { | ||
| 137 | - id: 1 | ||
| 136 | + let rule = new Rule({ | ||
| 137 | + event: { | ||
| 138 | + type: 'my-event', | ||
| 139 | + params: { | ||
| 140 | + customValue: 'my-custom-value' | ||
| 141 | + } | ||
| 138 | 142 | } | |
| 139 | 143 | }) | |
| 140 | 144 | ||
| 141 | - // whenever rule is evaluated and the conditions pass, 'my-event' will trigger | ||
| 145 | + // whenever rule is evaluated and conditions pass, 'my-event' will trigger | ||
| 142 | 146 | engine.on('my-event', function(params) { | |
| 143 | - console.log(params) // id: 1 | ||
| 147 | + console.log(params) // { customValue: 'my-custom-value' } | ||
| 144 | 148 | }) | |
| 145 | 149 | ``` | |
| 146 | 150 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,23 +1,23 @@ | |||
| 1 | 1 | # Facts | |
| 2 | 2 | ||
| 3 | - Facts are methods or constants registered with the engine prior to runtime and referenced within rule conditions. Each fact method should be a pure function that may return a computed value or promise. | ||
| 3 | + Facts are methods or constants registered with the engine prior to runtime and referenced within rule conditions. Each fact method should be a pure function that may return a either computed value, or promise that resolves to a computed value. | ||
| 4 | 4 | As rule conditions are evaluated during runtime, they retrieve fact values dynamically and use the condition _operator_ to compare the fact result with the condition _value_. | |
| 5 | 5 | ||
| 6 | 6 | ## Methods | |
| 7 | 7 | ||
| 8 | - ### constructor(String id, Constant|Function, [Object options]) -> instance | ||
| 8 | + ### constructor(String id, Constant|Function(Object params, Almanac almanac), [Object options]) -> instance | ||
| 9 | 9 | ||
| 10 | 10 | ```js | |
| 11 | 11 | // constant value facts | |
| 12 | 12 | let fact = new Fact('apiKey', '4feca34f9d67e99b8af2') | |
| 13 | 13 | ||
| 14 | 14 | // dynamic facts | |
| 15 | - let fact = new Fact('account-type', function getAccountType() { | ||
| 15 | + let fact = new Fact('account-type', (params, almanac) => { | ||
| 16 | 16 | // ... | |
| 17 | 17 | }) | |
| 18 | 18 | ||
| 19 | 19 | // facts with options: | |
| 20 | - engine.addFact('account-type', function getAccountType() { | ||
| 20 | + engine.addFact('account-type', (params, almanac) => { | ||
| 21 | 21 | // ... | |
| 22 | 22 | }, { cache: false, priority: 500 }) | |
| 23 | 23 | ``` | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -9,76 +9,217 @@ Rules contain a set of _conditions_ and a single _event_. When the engine is ru | |||
| 9 | 9 | Returns a new rule instance | |
| 10 | 10 | ||
| 11 | 11 | ```js | |
| 12 | + let options = { | ||
| 13 | + conditions: { | ||
| 14 | + all: [ | ||
| 15 | + { | ||
| 16 | + fact: 'my-fact', | ||
| 17 | + operator: 'equal', | ||
| 18 | + value: 'some-value' | ||
| 19 | + } | ||
| 20 | + ] | ||
| 21 | + }, | ||
| 22 | + event: { | ||
| 23 | + type: 'my-event', | ||
| 24 | + params: { | ||
| 25 | + customProperty: 'customValue' | ||
| 26 | + } | ||
| 27 | + }, | ||
| 28 | + priority: 1, // optional, default: 1 | ||
| 29 | + onSuccess: function (event, almanac) {}, // optional | ||
| 30 | + onFailure: function (event, almanac) {}, // optional | ||
| 31 | + } | ||
| 12 | 32 | let rule = new Rule(options) | |
| 13 | 33 | ``` | |
| 14 | 34 | ||
| 35 | + **options.conditions** : `[Object]` Rule conditions object | ||
| 36 | + | ||
| 37 | + **options.event** : `[Object]` Sets the `.on('success')` and `on('failure')` event argument emitted whenever the rule passes. Event objects must have a ```type``` property, and an optional ```params``` property. | ||
| 38 | + | ||
| 39 | + **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. | ||
| 40 | + | ||
| 41 | + **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. | ||
| 42 | + | ||
| 43 | + **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. | ||
| 44 | + | ||
| 15 | 45 | ### setConditions(Array conditions) | |
| 16 | 46 | ||
| 17 | - Assigns the rule conditions to the provided argument. The root condition must be a boolean operator (```all``` or ```any```) | ||
| 47 | + Helper for setting rule conditions. Alternative to passing the `conditions` option to the rule constructor. | ||
| 48 | + | ||
| 49 | + ### setEvent(Object event) | ||
| 50 | + | ||
| 51 | + Helper for setting rule event. Alternative to passing the `event` option to the rule constructor. | ||
| 52 | + | ||
| 53 | + ### setPriority(Integer priority = 1) | ||
| 54 | + | ||
| 55 | + Helper for setting rule priority. Alternative to passing the `priority` option to the rule constructor. | ||
| 56 | + | ||
| 57 | + ### toJSON(Boolean stringify = true) | ||
| 58 | + | ||
| 59 | + Serializes the rule into a JSON string. Often used when persisting rules. | ||
| 18 | 60 | ||
| 19 | 61 | ```js | |
| 20 | - rule.setConditions({ | ||
| 21 | - all: [ | ||
| 22 | - { | ||
| 23 | - fact: 'revenue', | ||
| 24 | - operator: 'greaterThanInclusive' | ||
| 25 | - value: 1000000 | ||
| 26 | - } | ||
| 27 | - ] | ||
| 28 | - }) | ||
| 62 | + let jsonString = rule.toJSON() // string: '{"conditions":{"all":[]},"priority":50 ... | ||
| 29 | 63 | ||
| 30 | - // if fact returns an object or array, providing a "path" key can be used for property traversal | ||
| 31 | - rule.setConditions({ | ||
| 32 | - all: [ | ||
| 33 | - { | ||
| 34 | - fact: 'userData', // 'userData' fact returns { profile: { addresses: [{ city: 'new york' }]}} | ||
| 35 | - operator: 'equal' | ||
| 36 | - value: 'new york', | ||
| 37 | - path: '.profile.addresses[0].city' // "path" navigates the data structure, down to the "city" property | ||
| 38 | - } | ||
| 39 | - ] | ||
| 40 | - }) | ||
| 64 | + let rule = new Rule(jsonString) // restored rule; same conditions, priority, event | ||
| 65 | + | ||
| 66 | + // without stringifying | ||
| 67 | + let jsonObject = rule.toJSON(false) // object: {conditions:{ all: [] }, priority: 50 ... | ||
| 41 | 68 | ``` | |
| 42 | 69 | ||
| 43 | - See the [fact dependency example](../examples/fact-dependency.js) | ||
| 70 | + ## Conditions | ||
| 44 | 71 | ||
| 45 | - ### setEvent(Object event) | ||
| 72 | + Rule conditions are a combination of facts, operators, and values that determine whether the rule is a `success` or a `failure`. | ||
| 46 | 73 | ||
| 47 | - Sets the event the engine should emit when the rule conditions pass. All events must have a ```type``` property, which denotes the event name to emit when the rule passes. | ||
| 74 | + ### Basic conditions | ||
| 48 | 75 | ||
| 49 | - Optionally, a ```params``` property may be provided as well. ```params``` will be passed to the event as an argument. | ||
| 76 | + The simplest form of a condition consists of a `fact`, an `operator`, and a `value`. When the engine runs, the operator is used to compare the fact against the value. | ||
| 50 | 77 | ||
| 51 | 78 | ```js | |
| 52 | - rule.setEvent({ | ||
| 53 | - type: 'string', //required | ||
| 54 | - params: { object } //optional | ||
| 79 | + // my-fact <= 1 | ||
| 80 | + let rule = new Rule({ | ||
| 81 | + conditions: { | ||
| 82 | + all: [ | ||
| 83 | + { | ||
| 84 | + fact: 'my-fact', | ||
| 85 | + operator: 'lessThanInclusive', | ||
| 86 | + value: 1 | ||
| 87 | + } | ||
| 88 | + ] | ||
| 89 | + } | ||
| 55 | 90 | }) | |
| 56 | 91 | ``` | |
| 57 | 92 | ||
| 58 | - ### setPriority(Integer priority = 1) | ||
| 93 | + See the [hello-world](../examples/01-hello-world.js) example. | ||
| 94 | + | ||
| 95 | + ### Boolean expressions: `all` and `any` | ||
| 59 | 96 | ||
| 60 | - Sets the rule priority. Priority must be a positive, non-zero integer. The higher the priority, the sooner the rule will run. If no priority is assigned to a Rule, it will receive a default priority of 1. | ||
| 97 | + 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. | ||
| 61 | 98 | ||
| 62 | 99 | ```js | |
| 63 | - rule.setPriority(100) | ||
| 100 | + // all: | ||
| 101 | + let rule = new Rule({ | ||
| 102 | + conditions: { | ||
| 103 | + all: [ | ||
| 104 | + { /* condition 1 */ }, | ||
| 105 | + { /* condition 2 */ }, | ||
| 106 | + { /* condition n */ }, | ||
| 107 | + ] | ||
| 108 | + } | ||
| 109 | + }) | ||
| 110 | + | ||
| 111 | + // any: | ||
| 112 | + let rule = new Rule({ | ||
| 113 | + conditions: { | ||
| 114 | + any: [ | ||
| 115 | + { /* condition 1 */ }, | ||
| 116 | + { /* condition 2 */ }, | ||
| 117 | + { /* condition n */ }, | ||
| 118 | + { | ||
| 119 | + all: [ /* more conditions */ ] | ||
| 120 | + } | ||
| 121 | + ] | ||
| 122 | + } | ||
| 123 | + }) | ||
| 64 | 124 | ``` | |
| 65 | 125 | ||
| 66 | - ### toJSON(Boolean stringify = true) | ||
| 126 | + 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. | ||
| 67 | 127 | ||
| 68 | - Serializes the rule into a JSON string. Usually used when persisting rules. | ||
| 128 | + ### Condition helpers: `params` | ||
| 129 | + | ||
| 130 | + Sometimes facts require additional input to perform calculations. For this, the `params` property is passed as an argument to the fact handler. `params` essentially functions as fact arguments, enabling fact handlers to be more generic and reusable. | ||
| 69 | 131 | ||
| 70 | 132 | ```js | |
| 71 | - let jsonString = rule.toJSON() // string: '{"conditions":{"all":[]},"priority":50 ... | ||
| 133 | + // product-price retrieves any product's price based on the "productId" in "params" | ||
| 134 | + engine.addFact('product-price', function (params, almanac) { | ||
| 135 | + return productLoader(params.productId) // loads the "widget" product | ||
| 136 | + .then(product => product.price) | ||
| 137 | + }) | ||
| 72 | 138 | ||
| 73 | - let rule = new Rule(jsonString) // restored rule; same conditions, priority, event | ||
| 139 | + // identifies whether the current widget price is above $100 | ||
| 140 | + let rule = new Rule({ | ||
| 141 | + conditions: { | ||
| 142 | + all: [ | ||
| 143 | + { | ||
| 144 | + fact: 'product-price', | ||
| 145 | + params: { | ||
| 146 | + productId: 'widget' // specifies which product to load | ||
| 147 | + }, | ||
| 148 | + operator: 'greaterThan', | ||
| 149 | + value: 100 | ||
| 150 | + } | ||
| 151 | + ] | ||
| 152 | + } | ||
| 153 | + }) | ||
| 154 | + ``` | ||
| 74 | 155 | ||
| 75 | - // without stringifying | ||
| 76 | - let jsonObject = rule.toJSON(false) // object: {conditions:{ all: [] }, priority: 50 ... | ||
| 156 | + See the [dynamic-facts](../examples/03-dynamic-facts) example | ||
| 157 | + | ||
| 158 | + ### Condition helpers: `path` | ||
| 159 | + | ||
| 160 | + In the `params` example above, the dynamic fact handler loads an object, then returns a specific object property. For more complex data structures, writing a separate fact handler for each object property can sometimes become unwieldy. | ||
| 161 | + | ||
| 162 | + To alleviate this overhead, a `path` property is provided for traversing objects and arrays returned by facts. The example above becomes simpler, and only one fact handler must be written by the developer to handle any number of properties. | ||
| 163 | + | ||
| 164 | + ```js | ||
| 165 | + | ||
| 166 | + // product-price retrieves any product's price based on the "productId" in "params" | ||
| 167 | + engine.addFact('product-price', function (params, almanac) { | ||
| 168 | + // NOTE: `then` is not required; .price is specified via "path" below | ||
| 169 | + return productLoader(params.productId) | ||
| 170 | + }) | ||
| 171 | + | ||
| 172 | + // identifies whether the current widget price is above $100 | ||
| 173 | + let rule = new Rule({ | ||
| 174 | + conditions: { | ||
| 175 | + all: [ | ||
| 176 | + { | ||
| 177 | + fact: 'product-price', | ||
| 178 | + params: { | ||
| 179 | + productId: 'widget', | ||
| 180 | + // Complex accessor are supported, e.g. '.profile.addresses[0].city' | ||
| 181 | + path: '.price' | ||
| 182 | + }, | ||
| 183 | + operator: 'greaterThan', | ||
| 184 | + value: 100 | ||
| 185 | + } | ||
| 186 | + ] | ||
| 187 | + } | ||
| 188 | + }) | ||
| 77 | 189 | ``` | |
| 190 | + See the [fact-dependency](../examples/04-fact-dependency.js) example | ||
| 78 | 191 | ||
| 79 | - ### Events | ||
| 192 | + ### Comparing facts | ||
| 80 | 193 | ||
| 81 | - Listen for 'success' and 'failure' events emitted when rule is evaluated. | ||
| 194 | + 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. | ||
| 195 | + | ||
| 196 | + ```js | ||
| 197 | + // identifies whether the current widget price is above a maximum | ||
| 198 | + let rule = new Rule({ | ||
| 199 | + conditions: { | ||
| 200 | + all: [ | ||
| 201 | + // widget-price > budget | ||
| 202 | + { | ||
| 203 | + fact: 'product-price', | ||
| 204 | + params: { | ||
| 205 | + productId: 'widget', | ||
| 206 | + path: '.price' | ||
| 207 | + }, | ||
| 208 | + operator: 'greaterThan', | ||
| 209 | + // "value" contains a fact | ||
| 210 | + value: { | ||
| 211 | + fact: 'budget' // "params" and "path" helpers are available as well | ||
| 212 | + } | ||
| 213 | + } | ||
| 214 | + ] | ||
| 215 | + } | ||
| 216 | + }) | ||
| 217 | + ``` | ||
| 218 | + See the [fact-comparison](../examples/08-fact-comparison.js) example | ||
| 219 | + | ||
| 220 | + ## Events | ||
| 221 | + | ||
| 222 | + Listen for `success` and `failure` events emitted when rule is evaluated. | ||
| 82 | 223 | ||
| 83 | 224 | #### ```rule.on('success', Function(Object event, Almanac almanac))``` | |
| 84 | 225 | ||
@@ -91,15 +232,15 @@ rule.on('success', function(event, almanac) { | |||
| 91 | 232 | ||
| 92 | 233 | #### ```rule.on('failure', Function(Object event, Almanac almanac))``` | |
| 93 | 234 | ||
| 94 | - Companion to 'success', except fires when the rule fails. | ||
| 235 | + Companion to `success`, except fires when the rule fails. | ||
| 95 | 236 | ||
| 96 | 237 | ```js | |
| 97 | 238 | engine.on('failure', function(event, almanac) { | |
| 98 | 239 | console.log(event) // { type: 'my-event', params: { id: 1 } | |
| 99 | 240 | }) | |
| 100 | 241 | ``` | |
| 101 | 242 | ||
| 102 | - ## Conditions | ||
| 243 | + ## Operators | ||
| 103 | 244 | ||
| 104 | 245 | Each rule condition must begin with a boolean operator(```all``` or ```any```) at its root. | |
| 105 | 246 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments