Skip to content

A tiny (≈ 1 KB gzipped), zero-dependency TypeScript library for evaluating rule trees expressed as JSON-serializable data.

License

Notifications You must be signed in to change notification settings

hosembafer/ruleset-engine

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

⚙️ Ruleset Engine

npm version npm size license

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

✨ Features

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).

📦 Install

# 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>

🚀 Quick start

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

📚 Rule syntax

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[]];

String-template values

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 }}' }

🔌 Extending operators

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);
  }
}

🛠 API

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.

About

A tiny (≈ 1 KB gzipped), zero-dependency TypeScript library for evaluating rule trees expressed as JSON-serializable data.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published