@luvies/business-rules
Version:
A JS-expression-based rules engine
108 lines • 3.9 kB
JavaScript
;
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