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-engineVia 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
}); // → truetype 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. |