@shepherdnerds/json-rules-engine 7.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.babelrc +3 -0
- package/.claude/settings.local.json +24 -0
- package/.github/workflows/deploy.yml +45 -0
- package/.github/workflows/node.js.yml +28 -0
- package/CHANGELOG.md +167 -0
- package/LICENSE +15 -0
- package/README.md +235 -0
- package/dist/almanac.js +269 -0
- package/dist/condition.js +331 -0
- package/dist/debug.js +18 -0
- package/dist/engine-default-operator-decorators.js +42 -0
- package/dist/engine-default-operators.js +50 -0
- package/dist/engine.js +451 -0
- package/dist/errors.js +32 -0
- package/dist/fact.js +129 -0
- package/dist/index.js +3 -0
- package/dist/json-rules-engine.js +48 -0
- package/dist/operator-decorator.js +60 -0
- package/dist/operator-map.js +178 -0
- package/dist/operator.js +50 -0
- package/dist/rule-result.js +80 -0
- package/dist/rule.js +525 -0
- package/dist/scoped-almanac.js +120 -0
- package/package.json +96 -0
- package/types/index.d.ts +312 -0
package/dist/rule.js
ADDED
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
|
|
8
|
+
|
|
9
|
+
var _condition = require('./condition');
|
|
10
|
+
|
|
11
|
+
var _condition2 = _interopRequireDefault(_condition);
|
|
12
|
+
|
|
13
|
+
var _ruleResult = require('./rule-result');
|
|
14
|
+
|
|
15
|
+
var _ruleResult2 = _interopRequireDefault(_ruleResult);
|
|
16
|
+
|
|
17
|
+
var _scopedAlmanac = require('./scoped-almanac');
|
|
18
|
+
|
|
19
|
+
var _scopedAlmanac2 = _interopRequireDefault(_scopedAlmanac);
|
|
20
|
+
|
|
21
|
+
var _debug = require('./debug');
|
|
22
|
+
|
|
23
|
+
var _debug2 = _interopRequireDefault(_debug);
|
|
24
|
+
|
|
25
|
+
var _clone = require('clone');
|
|
26
|
+
|
|
27
|
+
var _clone2 = _interopRequireDefault(_clone);
|
|
28
|
+
|
|
29
|
+
var _eventemitter = require('eventemitter2');
|
|
30
|
+
|
|
31
|
+
var _eventemitter2 = _interopRequireDefault(_eventemitter);
|
|
32
|
+
|
|
33
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
34
|
+
|
|
35
|
+
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
36
|
+
|
|
37
|
+
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
|
|
38
|
+
|
|
39
|
+
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
|
|
40
|
+
|
|
41
|
+
var Rule = function (_EventEmitter) {
|
|
42
|
+
_inherits(Rule, _EventEmitter);
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* returns a new Rule instance
|
|
46
|
+
* @param {object,string} options, or json string that can be parsed into options
|
|
47
|
+
* @param {integer} options.priority (>1) - higher runs sooner.
|
|
48
|
+
* @param {Object} options.event - event to fire when rule evaluates as successful
|
|
49
|
+
* @param {string} options.event.type - name of event to emit
|
|
50
|
+
* @param {string} options.event.params - parameters to pass to the event listener
|
|
51
|
+
* @param {Object} options.conditions - conditions to evaluate when processing this rule
|
|
52
|
+
* @param {any} options.name - identifier for a particular rule, particularly valuable in RuleResult output
|
|
53
|
+
* @return {Rule} instance
|
|
54
|
+
*/
|
|
55
|
+
function Rule(options) {
|
|
56
|
+
_classCallCheck(this, Rule);
|
|
57
|
+
|
|
58
|
+
var _this = _possibleConstructorReturn(this, (Rule.__proto__ || Object.getPrototypeOf(Rule)).call(this));
|
|
59
|
+
|
|
60
|
+
if (typeof options === 'string') {
|
|
61
|
+
options = JSON.parse(options);
|
|
62
|
+
}
|
|
63
|
+
if (options && options.conditions) {
|
|
64
|
+
_this.setConditions(options.conditions);
|
|
65
|
+
}
|
|
66
|
+
if (options && options.onSuccess) {
|
|
67
|
+
_this.on('success', options.onSuccess);
|
|
68
|
+
}
|
|
69
|
+
if (options && options.onFailure) {
|
|
70
|
+
_this.on('failure', options.onFailure);
|
|
71
|
+
}
|
|
72
|
+
if (options && (options.name || options.name === 0)) {
|
|
73
|
+
_this.setName(options.name);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
var priority = options && options.priority || 1;
|
|
77
|
+
_this.setPriority(priority);
|
|
78
|
+
|
|
79
|
+
var event = options && options.event || { type: 'unknown' };
|
|
80
|
+
_this.setEvent(event);
|
|
81
|
+
return _this;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Sets the priority of the rule
|
|
86
|
+
* @param {integer} priority (>=1) - increasing the priority causes the rule to be run prior to other rules
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
_createClass(Rule, [{
|
|
91
|
+
key: 'setPriority',
|
|
92
|
+
value: function setPriority(priority) {
|
|
93
|
+
priority = parseInt(priority, 10);
|
|
94
|
+
if (priority <= 0) throw new Error('Priority must be greater than zero');
|
|
95
|
+
this.priority = priority;
|
|
96
|
+
return this;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Sets the name of the rule
|
|
101
|
+
* @param {any} name - any truthy input and zero is allowed
|
|
102
|
+
*/
|
|
103
|
+
|
|
104
|
+
}, {
|
|
105
|
+
key: 'setName',
|
|
106
|
+
value: function setName(name) {
|
|
107
|
+
if (!name && name !== 0) {
|
|
108
|
+
throw new Error('Rule "name" must be defined');
|
|
109
|
+
}
|
|
110
|
+
this.name = name;
|
|
111
|
+
return this;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Sets the conditions to run when evaluating the rule.
|
|
116
|
+
* @param {object} conditions - conditions, root element must be a boolean operator
|
|
117
|
+
*/
|
|
118
|
+
|
|
119
|
+
}, {
|
|
120
|
+
key: 'setConditions',
|
|
121
|
+
value: function setConditions(conditions) {
|
|
122
|
+
if (!Object.prototype.hasOwnProperty.call(conditions, 'all') && !Object.prototype.hasOwnProperty.call(conditions, 'any') && !Object.prototype.hasOwnProperty.call(conditions, 'not') && !Object.prototype.hasOwnProperty.call(conditions, 'condition')) {
|
|
123
|
+
throw new Error('"conditions" root must contain a single instance of "all", "any", "not", or "condition"');
|
|
124
|
+
}
|
|
125
|
+
this.conditions = new _condition2.default(conditions);
|
|
126
|
+
return this;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Sets the event to emit when the conditions evaluate truthy
|
|
131
|
+
* @param {object} event - event to emit
|
|
132
|
+
* @param {string} event.type - event name to emit on
|
|
133
|
+
* @param {string} event.params - parameters to emit as the argument of the event emission
|
|
134
|
+
*/
|
|
135
|
+
|
|
136
|
+
}, {
|
|
137
|
+
key: 'setEvent',
|
|
138
|
+
value: function setEvent(event) {
|
|
139
|
+
if (!event) throw new Error('Rule: setEvent() requires event object');
|
|
140
|
+
if (!Object.prototype.hasOwnProperty.call(event, 'type')) {
|
|
141
|
+
throw new Error('Rule: setEvent() requires event object with "type" property');
|
|
142
|
+
}
|
|
143
|
+
this.ruleEvent = {
|
|
144
|
+
type: event.type
|
|
145
|
+
};
|
|
146
|
+
this.event = this.ruleEvent;
|
|
147
|
+
if (event.params) this.ruleEvent.params = event.params;
|
|
148
|
+
return this;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* returns the event object
|
|
153
|
+
* @returns {Object} event
|
|
154
|
+
*/
|
|
155
|
+
|
|
156
|
+
}, {
|
|
157
|
+
key: 'getEvent',
|
|
158
|
+
value: function getEvent() {
|
|
159
|
+
return this.ruleEvent;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* returns the priority
|
|
164
|
+
* @returns {Number} priority
|
|
165
|
+
*/
|
|
166
|
+
|
|
167
|
+
}, {
|
|
168
|
+
key: 'getPriority',
|
|
169
|
+
value: function getPriority() {
|
|
170
|
+
return this.priority;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* returns the event object
|
|
175
|
+
* @returns {Object} event
|
|
176
|
+
*/
|
|
177
|
+
|
|
178
|
+
}, {
|
|
179
|
+
key: 'getConditions',
|
|
180
|
+
value: function getConditions() {
|
|
181
|
+
return this.conditions;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* returns the engine object
|
|
186
|
+
* @returns {Object} engine
|
|
187
|
+
*/
|
|
188
|
+
|
|
189
|
+
}, {
|
|
190
|
+
key: 'getEngine',
|
|
191
|
+
value: function getEngine() {
|
|
192
|
+
return this.engine;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Sets the engine to run the rules under
|
|
197
|
+
* @param {object} engine
|
|
198
|
+
* @returns {Rule}
|
|
199
|
+
*/
|
|
200
|
+
|
|
201
|
+
}, {
|
|
202
|
+
key: 'setEngine',
|
|
203
|
+
value: function setEngine(engine) {
|
|
204
|
+
this.engine = engine;
|
|
205
|
+
return this;
|
|
206
|
+
}
|
|
207
|
+
}, {
|
|
208
|
+
key: 'toJSON',
|
|
209
|
+
value: function toJSON() {
|
|
210
|
+
var stringify = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
|
|
211
|
+
|
|
212
|
+
var props = {
|
|
213
|
+
conditions: this.conditions.toJSON(false),
|
|
214
|
+
priority: this.priority,
|
|
215
|
+
event: this.ruleEvent,
|
|
216
|
+
name: this.name
|
|
217
|
+
};
|
|
218
|
+
if (stringify) {
|
|
219
|
+
return JSON.stringify(props);
|
|
220
|
+
}
|
|
221
|
+
return props;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Priorizes an array of conditions based on "priority"
|
|
226
|
+
* When no explicit priority is provided on the condition itself, the condition's priority is determine by its fact
|
|
227
|
+
* @param {Condition[]} conditions
|
|
228
|
+
* @return {Condition[][]} prioritized two-dimensional array of conditions
|
|
229
|
+
* Each outer array element represents a single priority(integer). Inner array is
|
|
230
|
+
* all conditions with that priority.
|
|
231
|
+
*/
|
|
232
|
+
|
|
233
|
+
}, {
|
|
234
|
+
key: 'prioritizeConditions',
|
|
235
|
+
value: function prioritizeConditions(conditions) {
|
|
236
|
+
var _this2 = this;
|
|
237
|
+
|
|
238
|
+
var factSets = conditions.reduce(function (sets, condition) {
|
|
239
|
+
// if a priority has been set on this specific condition, honor that first
|
|
240
|
+
// otherwise, use the fact's priority
|
|
241
|
+
var priority = condition.priority;
|
|
242
|
+
if (!priority) {
|
|
243
|
+
var fact = _this2.engine.getFact(condition.fact);
|
|
244
|
+
priority = fact && fact.priority || 1;
|
|
245
|
+
}
|
|
246
|
+
if (!sets[priority]) sets[priority] = [];
|
|
247
|
+
sets[priority].push(condition);
|
|
248
|
+
return sets;
|
|
249
|
+
}, {});
|
|
250
|
+
return Object.keys(factSets).sort(function (a, b) {
|
|
251
|
+
return Number(a) > Number(b) ? -1 : 1; // order highest priority -> lowest
|
|
252
|
+
}).map(function (priority) {
|
|
253
|
+
return factSets[priority];
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Evaluates the rule, starting with the root boolean operator and recursing down
|
|
259
|
+
* All evaluation is done within the context of an almanac
|
|
260
|
+
* @return {Promise(RuleResult)} rule evaluation result
|
|
261
|
+
*/
|
|
262
|
+
|
|
263
|
+
}, {
|
|
264
|
+
key: 'evaluate',
|
|
265
|
+
value: function evaluate(almanac) {
|
|
266
|
+
var _this3 = this;
|
|
267
|
+
|
|
268
|
+
var ruleResult = new _ruleResult2.default(this.conditions, this.ruleEvent, this.priority, this.name);
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Evaluates the rule conditions
|
|
272
|
+
* @param {Condition} condition - condition to evaluate
|
|
273
|
+
* @return {Promise(true|false)} - resolves with the result of the condition evaluation
|
|
274
|
+
*/
|
|
275
|
+
var evaluateCondition = function evaluateCondition(condition) {
|
|
276
|
+
var currentAlmanac = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : almanac;
|
|
277
|
+
|
|
278
|
+
if (condition.isConditionReference()) {
|
|
279
|
+
return realize(condition, currentAlmanac);
|
|
280
|
+
} else if (condition.isBooleanOperator()) {
|
|
281
|
+
var subConditions = condition[condition.operator];
|
|
282
|
+
var comparisonPromise = void 0;
|
|
283
|
+
if (condition.operator === 'all') {
|
|
284
|
+
comparisonPromise = all(subConditions, currentAlmanac);
|
|
285
|
+
} else if (condition.operator === 'any') {
|
|
286
|
+
comparisonPromise = any(subConditions, currentAlmanac);
|
|
287
|
+
} else {
|
|
288
|
+
comparisonPromise = not(subConditions, currentAlmanac);
|
|
289
|
+
}
|
|
290
|
+
// for booleans, rule passing is determined by the all/any/not result
|
|
291
|
+
return comparisonPromise.then(function (comparisonValue) {
|
|
292
|
+
var passes = comparisonValue === true;
|
|
293
|
+
condition.result = passes;
|
|
294
|
+
return passes;
|
|
295
|
+
});
|
|
296
|
+
} else if (condition.isNestedCondition()) {
|
|
297
|
+
// Handle nested conditions (operator: 'some')
|
|
298
|
+
return evaluateNestedCondition(condition, currentAlmanac);
|
|
299
|
+
} else {
|
|
300
|
+
return condition.evaluate(currentAlmanac, _this3.engine.operators).then(function (evaluationResult) {
|
|
301
|
+
var passes = evaluationResult.result;
|
|
302
|
+
condition.factResult = evaluationResult.leftHandSideValue;
|
|
303
|
+
condition.valueResult = evaluationResult.rightHandSideValue;
|
|
304
|
+
condition.result = passes;
|
|
305
|
+
return passes;
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Evaluates a nested condition (operator: 'some')
|
|
312
|
+
* @param {Condition} condition - the nested condition to evaluate
|
|
313
|
+
* @param {Almanac} currentAlmanac - the almanac to use for fact resolution
|
|
314
|
+
* @return {Promise(boolean)} - resolves with true if any array item matches
|
|
315
|
+
*/
|
|
316
|
+
var evaluateNestedCondition = function evaluateNestedCondition(condition, currentAlmanac) {
|
|
317
|
+
// Resolve the array fact
|
|
318
|
+
return currentAlmanac.factValue(condition.fact, condition.params, condition.path).then(function (arrayValue) {
|
|
319
|
+
if (!Array.isArray(arrayValue)) {
|
|
320
|
+
(0, _debug2.default)('rule::evaluateNestedCondition fact is not an array', { fact: condition.fact, value: arrayValue });
|
|
321
|
+
condition.result = false;
|
|
322
|
+
condition.factResult = arrayValue;
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
(0, _debug2.default)('rule::evaluateNestedCondition evaluating', { fact: condition.fact, arrayLength: arrayValue.length });
|
|
327
|
+
|
|
328
|
+
// For 'some' operator: return true if any item matches all nested conditions
|
|
329
|
+
// Use sequential evaluation to check items one by one
|
|
330
|
+
var evaluateItemsSequentially = function evaluateItemsSequentially(items, index) {
|
|
331
|
+
if (index >= items.length) {
|
|
332
|
+
// No items matched
|
|
333
|
+
(0, _debug2.default)('rule::evaluateNestedCondition no matching items found');
|
|
334
|
+
condition.result = false;
|
|
335
|
+
condition.factResult = arrayValue;
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
var item = items[index];
|
|
340
|
+
var scopedAlmanac = new _scopedAlmanac2.default(currentAlmanac, item);
|
|
341
|
+
return evaluateCondition(condition.conditions, scopedAlmanac).then(function (result) {
|
|
342
|
+
if (result) {
|
|
343
|
+
(0, _debug2.default)('rule::evaluateNestedCondition found matching item', { item: item });
|
|
344
|
+
condition.result = true;
|
|
345
|
+
condition.factResult = arrayValue;
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
// Try next item
|
|
349
|
+
return evaluateItemsSequentially(items, index + 1);
|
|
350
|
+
});
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
return evaluateItemsSequentially(arrayValue, 0);
|
|
354
|
+
});
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Evalutes an array of conditions, using an 'every' or 'some' array operation
|
|
359
|
+
* @param {Condition[]} conditions
|
|
360
|
+
* @param {string(every|some)} array method to call for determining result
|
|
361
|
+
* @param {Almanac} currentAlmanac - the almanac to use for fact resolution
|
|
362
|
+
* @return {Promise(boolean)} whether conditions evaluated truthy or falsey based on condition evaluation + method
|
|
363
|
+
*/
|
|
364
|
+
var evaluateConditions = function evaluateConditions(conditions, method) {
|
|
365
|
+
var currentAlmanac = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : almanac;
|
|
366
|
+
|
|
367
|
+
if (!Array.isArray(conditions)) conditions = [conditions];
|
|
368
|
+
|
|
369
|
+
return Promise.all(conditions.map(function (condition) {
|
|
370
|
+
return evaluateCondition(condition, currentAlmanac);
|
|
371
|
+
})).then(function (conditionResults) {
|
|
372
|
+
(0, _debug2.default)('rule::evaluateConditions', { results: conditionResults });
|
|
373
|
+
return method.call(conditionResults, function (result) {
|
|
374
|
+
return result === true;
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Evaluates a set of conditions based on an 'all', 'any', or 'not' operator.
|
|
381
|
+
* First, orders the top level conditions based on priority
|
|
382
|
+
* Iterates over each priority set, evaluating each condition
|
|
383
|
+
* If any condition results in the rule to be guaranteed truthy or falsey,
|
|
384
|
+
* it will short-circuit and not bother evaluating any additional rules
|
|
385
|
+
* @param {Condition[]} conditions - conditions to be evaluated
|
|
386
|
+
* @param {string('all'|'any'|'not')} operator
|
|
387
|
+
* @param {Almanac} currentAlmanac - the almanac to use for fact resolution
|
|
388
|
+
* @return {Promise(boolean)} rule evaluation result
|
|
389
|
+
*/
|
|
390
|
+
var prioritizeAndRun = function prioritizeAndRun(conditions, operator) {
|
|
391
|
+
var currentAlmanac = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : almanac;
|
|
392
|
+
|
|
393
|
+
if (conditions.length === 0) {
|
|
394
|
+
return Promise.resolve(true);
|
|
395
|
+
}
|
|
396
|
+
if (conditions.length === 1) {
|
|
397
|
+
// no prioritizing is necessary, just evaluate the single condition
|
|
398
|
+
// 'all' and 'any' will give the same results with a single condition so no method is necessary
|
|
399
|
+
// this also covers the 'not' case which should only ever have a single condition
|
|
400
|
+
return evaluateCondition(conditions[0], currentAlmanac);
|
|
401
|
+
}
|
|
402
|
+
var orderedSets = _this3.prioritizeConditions(conditions);
|
|
403
|
+
var cursor = Promise.resolve(operator === 'all');
|
|
404
|
+
// use for() loop over Array.forEach to support IE8 without polyfill
|
|
405
|
+
|
|
406
|
+
var _loop = function _loop(i) {
|
|
407
|
+
var set = orderedSets[i];
|
|
408
|
+
cursor = cursor.then(function (setResult) {
|
|
409
|
+
// rely on the short-circuiting behavior of || and && to avoid evaluating subsequent conditions
|
|
410
|
+
return operator === 'any' ? setResult || evaluateConditions(set, Array.prototype.some, currentAlmanac) : setResult && evaluateConditions(set, Array.prototype.every, currentAlmanac);
|
|
411
|
+
});
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
for (var i = 0; i < orderedSets.length; i++) {
|
|
415
|
+
_loop(i);
|
|
416
|
+
}
|
|
417
|
+
return cursor;
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Runs an 'any' boolean operator on an array of conditions
|
|
422
|
+
* @param {Condition[]} conditions to be evaluated
|
|
423
|
+
* @param {Almanac} currentAlmanac - the almanac to use for fact resolution
|
|
424
|
+
* @return {Promise(boolean)} condition evaluation result
|
|
425
|
+
*/
|
|
426
|
+
var any = function any(conditions) {
|
|
427
|
+
var currentAlmanac = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : almanac;
|
|
428
|
+
|
|
429
|
+
return prioritizeAndRun(conditions, 'any', currentAlmanac);
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Runs an 'all' boolean operator on an array of conditions
|
|
434
|
+
* @param {Condition[]} conditions to be evaluated
|
|
435
|
+
* @param {Almanac} currentAlmanac - the almanac to use for fact resolution
|
|
436
|
+
* @return {Promise(boolean)} condition evaluation result
|
|
437
|
+
*/
|
|
438
|
+
var all = function all(conditions) {
|
|
439
|
+
var currentAlmanac = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : almanac;
|
|
440
|
+
|
|
441
|
+
return prioritizeAndRun(conditions, 'all', currentAlmanac);
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Runs a 'not' boolean operator on a single condition
|
|
446
|
+
* @param {Condition} condition to be evaluated
|
|
447
|
+
* @param {Almanac} currentAlmanac - the almanac to use for fact resolution
|
|
448
|
+
* @return {Promise(boolean)} condition evaluation result
|
|
449
|
+
*/
|
|
450
|
+
var not = function not(condition) {
|
|
451
|
+
var currentAlmanac = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : almanac;
|
|
452
|
+
|
|
453
|
+
return prioritizeAndRun([condition], 'not', currentAlmanac).then(function (result) {
|
|
454
|
+
return !result;
|
|
455
|
+
});
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Dereferences the condition reference and then evaluates it.
|
|
460
|
+
* @param {Condition} conditionReference
|
|
461
|
+
* @param {Almanac} currentAlmanac - the almanac to use for fact resolution
|
|
462
|
+
* @returns {Promise(boolean)} condition evaluation result
|
|
463
|
+
*/
|
|
464
|
+
var realize = function realize(conditionReference) {
|
|
465
|
+
var currentAlmanac = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : almanac;
|
|
466
|
+
|
|
467
|
+
var condition = _this3.engine.conditions.get(conditionReference.condition);
|
|
468
|
+
if (!condition) {
|
|
469
|
+
if (_this3.engine.allowUndefinedConditions) {
|
|
470
|
+
// undefined conditions always fail
|
|
471
|
+
conditionReference.result = false;
|
|
472
|
+
return Promise.resolve(false);
|
|
473
|
+
} else {
|
|
474
|
+
throw new Error('No condition ' + conditionReference.condition + ' exists');
|
|
475
|
+
}
|
|
476
|
+
} else {
|
|
477
|
+
// project the referenced condition onto reference object and evaluate it.
|
|
478
|
+
delete conditionReference.condition;
|
|
479
|
+
Object.assign(conditionReference, (0, _clone2.default)(condition));
|
|
480
|
+
return evaluateCondition(conditionReference, currentAlmanac);
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Emits based on rule evaluation result, and decorates ruleResult with 'result' property
|
|
486
|
+
* @param {RuleResult} ruleResult
|
|
487
|
+
*/
|
|
488
|
+
var processResult = function processResult(result) {
|
|
489
|
+
ruleResult.setResult(result);
|
|
490
|
+
var processEvent = Promise.resolve();
|
|
491
|
+
if (_this3.engine.replaceFactsInEventParams) {
|
|
492
|
+
processEvent = ruleResult.resolveEventParams(almanac);
|
|
493
|
+
}
|
|
494
|
+
var event = result ? 'success' : 'failure';
|
|
495
|
+
return processEvent.then(function () {
|
|
496
|
+
return _this3.emitAsync(event, ruleResult.event, almanac, ruleResult);
|
|
497
|
+
}).then(function () {
|
|
498
|
+
return ruleResult;
|
|
499
|
+
});
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
if (ruleResult.conditions.any) {
|
|
503
|
+
return any(ruleResult.conditions.any).then(function (result) {
|
|
504
|
+
return processResult(result);
|
|
505
|
+
});
|
|
506
|
+
} else if (ruleResult.conditions.all) {
|
|
507
|
+
return all(ruleResult.conditions.all).then(function (result) {
|
|
508
|
+
return processResult(result);
|
|
509
|
+
});
|
|
510
|
+
} else if (ruleResult.conditions.not) {
|
|
511
|
+
return not(ruleResult.conditions.not).then(function (result) {
|
|
512
|
+
return processResult(result);
|
|
513
|
+
});
|
|
514
|
+
} else {
|
|
515
|
+
return realize(ruleResult.conditions).then(function (result) {
|
|
516
|
+
return processResult(result);
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}]);
|
|
521
|
+
|
|
522
|
+
return Rule;
|
|
523
|
+
}(_eventemitter2.default);
|
|
524
|
+
|
|
525
|
+
exports.default = Rule;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
|
|
8
|
+
|
|
9
|
+
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
|
|
10
|
+
|
|
11
|
+
var _debug = require('./debug');
|
|
12
|
+
|
|
13
|
+
var _debug2 = _interopRequireDefault(_debug);
|
|
14
|
+
|
|
15
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
16
|
+
|
|
17
|
+
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Scoped Almanac for nested condition evaluation
|
|
21
|
+
* Wraps a parent almanac but prioritizes item properties for fact resolution
|
|
22
|
+
*/
|
|
23
|
+
var ScopedAlmanac = function () {
|
|
24
|
+
function ScopedAlmanac(parentAlmanac, item) {
|
|
25
|
+
_classCallCheck(this, ScopedAlmanac);
|
|
26
|
+
|
|
27
|
+
this.parentAlmanac = parentAlmanac;
|
|
28
|
+
this.item = item;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Resolves a path directly on the current scoped item
|
|
33
|
+
* Used by scoped conditions that have path but no fact
|
|
34
|
+
* @param {string} path - JSONPath to resolve on the item (e.g., '$.state' or '$.nested.property')
|
|
35
|
+
* @return {Promise} resolves with the value at the path
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
_createClass(ScopedAlmanac, [{
|
|
40
|
+
key: 'resolvePath',
|
|
41
|
+
value: function resolvePath(path) {
|
|
42
|
+
if (this.item == null) {
|
|
43
|
+
(0, _debug2.default)('scoped-almanac::resolvePath item is null');
|
|
44
|
+
return Promise.resolve(undefined);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
var value = this.parentAlmanac.pathResolver(this.item, path);
|
|
48
|
+
(0, _debug2.default)('scoped-almanac::resolvePath', { path: path, value: value, item: this.item });
|
|
49
|
+
return Promise.resolve(value);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Retrieves a fact value, first checking if it's a property on the current item
|
|
54
|
+
* @param {string} factId - fact identifier
|
|
55
|
+
* @param {Object} params - parameters to feed into the fact
|
|
56
|
+
* @param {string} path - object path
|
|
57
|
+
* @return {Promise} resolves with the fact value
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
}, {
|
|
61
|
+
key: 'factValue',
|
|
62
|
+
value: function factValue(factId) {
|
|
63
|
+
var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
64
|
+
var path = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
|
|
65
|
+
|
|
66
|
+
// First check if factId is a property on the current item
|
|
67
|
+
if (this.item != null && _typeof(this.item) === 'object' && Object.prototype.hasOwnProperty.call(this.item, factId)) {
|
|
68
|
+
var value = this.item[factId];
|
|
69
|
+
(0, _debug2.default)('scoped-almanac::factValue found property on item', { factId: factId, value: value });
|
|
70
|
+
// Apply path if provided
|
|
71
|
+
if (path) {
|
|
72
|
+
value = this.parentAlmanac.pathResolver(value, path);
|
|
73
|
+
(0, _debug2.default)('scoped-almanac::factValue resolved path', { path: path, value: value });
|
|
74
|
+
}
|
|
75
|
+
return Promise.resolve(value);
|
|
76
|
+
}
|
|
77
|
+
// Fall back to parent almanac
|
|
78
|
+
(0, _debug2.default)('scoped-almanac::factValue falling back to parent almanac', { factId: factId });
|
|
79
|
+
return this.parentAlmanac.factValue(factId, params, path);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Interprets value as either a primitive, or if a fact, retrieves the fact value
|
|
84
|
+
* Handles both fact references and path-only references for scoped conditions
|
|
85
|
+
* @param {*} value - the value to interpret
|
|
86
|
+
* @return {Promise} resolves with the value
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
}, {
|
|
90
|
+
key: 'getValue',
|
|
91
|
+
value: function getValue(value) {
|
|
92
|
+
if (value != null && (typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object') {
|
|
93
|
+
// If value references a fact, resolve through scoped factValue
|
|
94
|
+
if (Object.prototype.hasOwnProperty.call(value, 'fact')) {
|
|
95
|
+
return this.factValue(value.fact, value.params, value.path);
|
|
96
|
+
}
|
|
97
|
+
// If value only has a path (for scoped comparisons), resolve directly on item
|
|
98
|
+
if (Object.prototype.hasOwnProperty.call(value, 'path') && !Object.prototype.hasOwnProperty.call(value, 'fact')) {
|
|
99
|
+
return this.resolvePath(value.path);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return Promise.resolve(value);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Expose pathResolver from parent almanac
|
|
107
|
+
* @returns {Function} the path resolver function
|
|
108
|
+
*/
|
|
109
|
+
|
|
110
|
+
}, {
|
|
111
|
+
key: 'pathResolver',
|
|
112
|
+
get: function get() {
|
|
113
|
+
return this.parentAlmanac.pathResolver;
|
|
114
|
+
}
|
|
115
|
+
}]);
|
|
116
|
+
|
|
117
|
+
return ScopedAlmanac;
|
|
118
|
+
}();
|
|
119
|
+
|
|
120
|
+
exports.default = ScopedAlmanac;
|