@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/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;