UNPKG

verdict-rules

Version:

A small, zero-dependency, async-native rule-evaluation engine. Compose conditions into one explainable pass/fail verdict, with sequential evaluation and real short-circuiting as a guarantee.

193 lines (188 loc) • 5.61 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { AndRule: () => AndRule, FunctionRule: () => FunctionRule, OrRule: () => OrRule, RulesEngine: () => RulesEngine, UnknownLookupError: () => UnknownLookupError }); module.exports = __toCommonJS(index_exports); // src/errors.ts var UnknownLookupError = class extends Error { /** Whether the missing thing was a rule name or a group label. */ kind; /** The name or label that matched nothing. */ key; constructor(kind, key) { super( kind === "rule" ? `No rule named '${key}' in this engine` : `No rules in group '${key}' in this engine` ); this.name = "UnknownLookupError"; this.kind = kind; this.key = key; } }; // src/engine.ts var RulesEngine = class { #rules; #byName; #byGroup; constructor(rules) { this.#rules = [...rules]; this.#byName = new Map(rules.map((r) => [r.name, r])); this.#byGroup = /* @__PURE__ */ new Map(); for (const rule of rules) { if (rule.group === void 0 || rule.group === "") continue; const existing = this.#byGroup.get(rule.group); if (existing === void 0) this.#byGroup.set(rule.group, [rule]); else existing.push(rule); } } /** Every rule name registered here, in registration order. */ get ruleNames() { return [...this.#byName.keys()]; } /** Every group label carried by at least one rule, in first-seen order. */ get groupNames() { return [...this.#byGroup.keys()]; } /** Evaluate every registered rule. Never short-circuits. */ async runAll(context) { const results = []; for (const rule of this.#rules) { results.push(await rule.evaluate(context)); } return { passed: results.every((r) => r.passed), results }; } /** * Evaluate one rule by name, or return `undefined` if no such rule exists. * * `undefined` means absent, never failed. */ async tryRunNamed(name, context) { const rule = this.#byName.get(name); if (rule === void 0) return void 0; return rule.evaluate(context); } /** * Evaluate exactly one rule, looked up by name. * * @throws {UnknownLookupError} if no rule has this name. */ async runNamed(name, context) { const result = await this.tryRunNamed(name, context); if (result === void 0) { throw new UnknownLookupError("rule", name); } return result; } /** * Evaluate a group, or return `undefined` if no such group exists. * * `undefined` means absent, never vacuously passed. */ async tryRunGroup(group, context) { const rules = this.#byGroup.get(group); if (rules === void 0 || rules.length === 0) return void 0; const results = []; for (const rule of rules) { results.push(await rule.evaluate(context)); } return { passed: results.every((r) => r.passed), results }; } /** * Evaluate every rule sharing a group label. Never short-circuits. * * @throws {UnknownLookupError} if no rule carries this label. */ async runGroup(group, context) { const result = await this.tryRunGroup(group, context); if (result === void 0) { throw new UnknownLookupError("group", group); } return result; } }; // src/rule.ts var FunctionRule = class { name; group; #predicate; constructor(name, predicate, group) { this.name = name; this.group = group; this.#predicate = predicate; } /** Runs the wrapped predicate and returns whatever it returns, unchanged. */ evaluate(context) { return this.#predicate(context); } }; var AndRule = class { name; group; #rules; constructor(name, rules, group) { this.name = name; this.group = group; this.#rules = rules; } async evaluate(context) { const subResults = []; for (const rule of this.#rules) { const result = await rule.evaluate(context); subResults.push(result); if (!result.passed) { const detail = result.detail ? `'${rule.name}' failed: ${result.detail}` : `'${rule.name}' failed`; return { ruleName: this.name, passed: false, detail, data: subResults }; } } return { ruleName: this.name, passed: true, data: subResults }; } }; var OrRule = class { name; group; #rules; constructor(name, rules, group) { this.name = name; this.group = group; this.#rules = rules; } async evaluate(context) { const subResults = []; for (const rule of this.#rules) { const result = await rule.evaluate(context); subResults.push(result); if (result.passed) { return { ruleName: this.name, passed: true, data: subResults }; } } return { ruleName: this.name, passed: false, detail: "no sub-rule passed", data: subResults }; } }; //# sourceMappingURL=index.cjs.map