UNPKG

@luvies/business-rules

Version:

A JS-expression-based rules engine

108 lines 3.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const dependency_graph_1 = require("./dependency-graph"); const evaluator_1 = require("@luvies/evaluator"); const result_listener_1 = require("./result-listener"); class Rules { constructor(context = {}, options = {}) { this._rules = new Map(); this._context = context; this._previous = options.previous; this.aliases = options.aliases || new Map(); } set(id, rule) { if (this.get(id)) { throw new Error(`Rule with id '${id}' already exists`); } this._rules.set(id, rule); return this; } get(id) { return this._rules.get(id); } has(id) { return this._rules.has(id); } delete(id) { return this._rules.delete(id); } async eval() { const graph = new dependency_graph_1.DependencyGraph(Array.from(this._rules.keys())); const resultListener = new result_listener_1.ResultListener(); const rawResults = await Promise.all(Array.from(this._rules).map(([id, rule]) => this._evalRule(id, rule, graph, resultListener))); const results = new Map(); const activated = []; const deactivated = []; const errors = new Map(); for (const rawResult of rawResults) { if (!rawResult.error) { results.set(rawResult.id, rawResult.value); const wasActivated = this._previous && this._previous.get(rawResult.id) === true; if (rawResult.activated) { if (!wasActivated) { activated.push(rawResult.id); } } else { if (wasActivated) { deactivated.push(rawResult.id); } } } else { errors.set(rawResult.id, rawResult.error); } } // Persist the state across runs so rules aren't constantly re-activated. this._previous = results; return { results, activated, deactivated, errors, }; } async _evalRule(id, rule, graph, resultListener) { const evaluator = new evaluator_1.ExpressionEvaluator({ // Build up context using custom functions and various defaults. context: { rule: async (targetId) => { // Resolve aliases. if (this.aliases) { const alias = this.aliases.get(targetId); if (alias !== undefined) { targetId = alias; } } // First check if it will create a circular dependency. // This throws an error if it will otherwise stores it. graph.addDependency(id, targetId); const ruleResult = await resultListener.wait(targetId); return ruleResult.value; }, ...evaluator_1.contexts, ...this._context, ...rule.context, }, // Load in standard member checks. memberChecks: evaluator_1.standardMemberChecks, }); const result = { id, rule, }; try { const evaluatorResult = await evaluator.eval(rule.expression); result.value = evaluatorResult.value; // Specifically true so utility rules aren't activated. result.activated = result.value === true; } catch (err) { result.error = err; } resultListener.onResult(id, result); return result; } } exports.Rules = Rules; //# sourceMappingURL=rules.js.map