A tiny (≈ 1 KB gzipped), zero-dependency TypeScript library for evaluating rule trees expressed as JSON-serializable data.
import { RulesetEngine } from 'ruleset-engine';
const rules = [
'all',
{ fact: 'age', op: 'gte', value: 18 },
{ fact: 'country', op: 'eq', value: 'AM' }
] as const;
const engine = new RulesetEngine(rules);
engine.evaluate({ age: 21, country: 'AM' }); // → true
Feature | Notes |
---|---|
Declarative rules | Compose predicates with all , any , not_all , and not_any —no code in your data. |
Rich operator set | eq , gt , gte , lt , lte , in , has , and regex out of the box. |
String-template look-ups | Reference dynamic data with {{ path.to.value }} placeholders. |
TypeScript-first | Fully typed API—all rules are validated at compile time. |
Runtime-agnostic | Works in Node, Bun, Deno, or directly in the browser (IIFE/UMD/ESM). |
# ESM / Node ≥18
npm i ruleset-engine
# or
yarn add ruleset-engine
# or
pnpm add ruleset-engine
Via CDN (UMD):
<script src="https://cdn.jsdelivr.net/npm/ruleset-engine/dist-cdn/index.umd.js"></script>
<script>
const engine = new RulesetEngine(...);
/* … */
</script>
import { RulesetEngine, Ruleset } from 'ruleset-engine';
const discountRules: Ruleset = [
'any',
['all',
{ fact: 'user.segment', op: 'eq', value: 'vip' },
{ fact: 'order.total', op: 'gte', value: 100 }
],
{ fact: 'coupon', op: 'eq', value: 'SUMMER25' }
];
const engine = new RulesetEngine(discountRules);
engine.evaluate({
user: { segment: 'vip' },
order: { total: 130 },
coupon: null
}); // → true
type Conjunction = 'all' | 'any' | 'not_all' | 'not_any';
type Operator =
| 'eq' // strict equality
| 'gt' // >
| 'gte' // ≥
| 'lt' // <
| 'lte' // ≤
| 'in' // left ∈ right[]
| 'has' // right ⊂ left[]
| 'regex' // RegExp test
type Predicate = { fact: string; op: Operator; value: unknown };
export type Ruleset = Predicate | [Conjunction, ...Ruleset[]];
If value
is a string wrapped in {{ }}
, it is resolved against the current fact set at evaluation time:
{ fact: 'order.total', op: 'gt', value: '{{ user.maxSpend }}' }
Need a custom operator? Just derive your own class:
class ExtEngine extends RulesetEngine {
protected override test(p: Predicate, f: Record<string, unknown>) {
if (p.op === 'startsWith') {
const L = this.factValue(p.fact, f);
const R = String(this.tpl(p.value, f));
return String(L).startsWith(R);
}
return super.test(p, f);
}
}
Method | Description |
---|---|
new RulesetEngine(rules) |
Create an engine instance. rules is any valid Ruleset. |
.evaluate(facts, [ruleset]) |
Evaluate ruleset (defaults to the root) against a facts object. Returns boolean. |