Rulepilot
A simple yet powerful JSON rule processing engine for JavaScript
Install / Use
/learn @andrewbrg/RulepilotREADME
| Statements | Functions | Lines |
| --------------------------------------------------------------------------- | -------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| |
|
|
Overview
RulePilot is a fast and lightweight rule engine for JavaScript. It is designed to be simple to use and easy to
integrate into your application.
The rule engine evaluates human-readable JSON rules against a set of criteria. The rules are evaluated in a top-down fashion.
Simple rules can be written to evaluate to a boolean value (indicating whether the criteria tested passes the rule).
Otherwise, granular rules can be created, where each condition of the rule can evaluate to a boolean, number,
string, object or array. This is particularly useful when you want to evaluate a rule and return a result based on
each condition's evaluation.
Features
- Simple to use
- Written in TypeScript
- Human-readable JSON rules
- Runs in both Node & the browser
- Lightweight with zero dependencies & Fast (10,000 rule evaluations in ~35-40ms)
- Fluent rule builder tool (ORM style)
- Both Simple & Granular rule evaluation
- Sub-rules & infinitely nested conditions
- Dynamic criteria mutation
- Supports Criteria objects with nested properties
- Rule validation & debugging tools
- Supports
Any,All, andNonetype conditions - Supports
Equal,NotEqual,GreaterThan,GreaterThanOrEqual,LessThan,LessThanOrEqual,In,NotIn,Contains,Not Contains,ContainsAny,Not ContainsAny,MatchesandNot Matchesoperators
Usage
Installation
npm install rulepilot
yarn add rulepilot
Importing
import { RulePilot } from "rulepilot";
For TypeScript users, you can import the Rule interface to get type definitions for the rule JSON.
import { RulePilot, Rule } from "rulepilot";
const rule: Rule = {
// ...
};
Static vs Instantiated Approach
The RulePilot library can be used in two ways, either as a static class or as an instance.
The static class is great for most cases as long as you do not intend to use different mutations in multiple places in your codebase, (more on what mutations are further down).
Otherwise, you can create an instance of the RulePilot class and use it to evaluate rules.
import { RulePilot } from "rulepilot";
let result;
// Static
result = await RulePilot.evaluate(rule, criteria);
// Instance
const rulePilot = new RulePilot();
result = await rulePilot.evaluate(rule, criteria);
Basic Example
Here we are defining a rule which will evaluate to true or false based on the criteria provided. In this example,
we are checking whether a user is allowed to benefit from a discount at checkout of not.
For the discount to be applied, the user must be from either the UK or Finland, have a coupon and the total checkout
price must be greater than or equal to 120.00.
import { RulePilot, Rule } from "rulepilot";
// Define a rule which caters for your needs
const rule: Rule = {
conditions: {
all: [
{ field: "country", operator: "in", value: ["GB", "FI"] },
{ field: "hasCoupon", operator: "==", value: true },
{ field: "totalCheckoutPrice", operator: ">=", value: 120.0 },
],
},
};
// Define the criteria which will be evaluated against the rule
const criteria = {
country: "GB",
totalCheckoutPrice: 125.0,
hasCoupon: true,
};
/** Evaluate the criteria against the rule */
let result = await RulePilot.evaluate<boolean>(rule, criteria);
// result == true
// However, if any of the criteria do not pass the check, the result will be false
criteria.totalCheckoutPrice = 25.0;
/** Evaluate the new criteria against the rule */
result = await RulePilot.evaluate<boolean>(rule, criteria); // result == false
We can add additional conditions to the rule, for example apart from the above-mentioned conditions, we can also
say that if the user is either over 18 years old or has a valid student card then we will evaluate to true.
Take note of how the conditions property is now an array of objects.
import { RulePilot, Rule } from "rulepilot";
// Define a rule which caters for your needs
const rule: Rule = {
conditions: [
{
all: [
{ field: "country", operator: "in", value: ["GB", "FI"] },
{ field: "hasCoupon", operator: "==", value: true },
{ field: "totalCheckoutPrice", operator: ">=", value: 120.0 },
],
},
{
any: [
{ field: "age", operator: ">=", value: 18 },
{ field: "hasStudentCard", operator: "==", value: true },
],
},
],
};
// Define the criteria which will be evaluated against the rule
const criteria = {
country: "GB",
totalCheckoutPrice: 20.0,
hasCoupon: true,
};
/** Evaluate the criteria against the rule */
let result = await RulePilot.evaluate<boolean>(rule, criteria); // result == false
criteria.hasStudentCard = true;
/** Evaluate the new criteria against the rule */
result = await RulePilot.evaluate<boolean>(rule, criteria); // result == true
If we want to add additional requirements to the rule, we can do so by adding another any or all condition.
For example, we can add a requirement that a discount will also be given to all users from Sweden as long as they are 18+ or have a valid student card (irrelevant of any other conditions set).
const rule: Rule = {
conditions: [
{
any: [
{
all: [
{ field: "country", operator: "in", value: ["GB", "FI"] },
{ field: "hasCoupon", operator: "==", value: true },
{ field: "totalCheckoutPrice", operator: ">=", value: 120.0 },
],
},
{ field: "country", operator: "==", value: "SE" },
],
},
{
any: [
{ field: "age", operator: ">=", value: 18 },
{ field: "hasStudentCard", operator: "==", value: true },
],
},
],
};
The criteria can be narrowed down further by specifying Swedish users cannot be from Stockholm or Gothenburg
otherwise they must spend more than 200.00 at checkout.
const rule: Rule = {
conditions: [
{
any: [
{
all: [
{ field: "country", operator: "in", value: ["GB", "FI"] },
{ field: "hasCoupon", operator: "==", value: true },
{ field: "totalCheckoutPrice", operator: ">=", value: 120.0 },
],
},
{
any: [
{
all: [
{ field: "country", operator: "==", value: "SE" },
{ field: "city", operator: "not in", value: ["Stockholm", "Gothenburg"] },
],
},
{
all: [
{ field: "country", operator: "==", value: "SE" },
{ field: "city", operator: "totalCheckoutPrice", value: 200 },
],
},
],
},
],
},
{
any: [
{ field: "age", operator: ">=", value: 18 },
{ field: "hasStudentCard", operator: "==", value: true },
],
},
],
};
Granular Example
It might be the case that we want to give different discounts to people based on the criteria they meet. For example,
we want to give a 10% discount to all users who 18+ or have a student card and a 5% discount to the rest of the
users who meet the other criteria.
To accomplish this, we can assign a result to each condition which will be used to calculate the discount.
const rule: Rule = {
conditions: [
{
any: [
{
all: [
{ field: "country", operator: "in", value: ["GB", "FI"] },
{ field: "hasCoupon", operator: "==", value: true },
{ field: "totalCheckoutPrice", operator: ">=", value: 120.0 },
],
},
{
any: [
{
all: [
{ field: "country", operator: "==", value: "SE" },
{ field: "city", operator: "not in", value: ["Stockholm", "Gothenburg"] },
],
},
{
all: [
{ field: "country", operator: "==", value: "SE" },
{ field: "city", operator: "totalCheckoutPrice", value: 200 },
],
},
],
},
],
result: 5,
},
{
any: [
{ field: "age", operator: ">=", value: 18 },
{ field: "hasStudentCard", operator: "==", value: true },
],
result: 10,
},
],
};
In such a setup the result of our evaluation will be the value of the result property in condition which was met first.
import { RulePilot } from "rulepilot";
// Define the criteria which will be evaluated against the rule
const criteria = {
country: "GB",
totalCheckoutPrice: 340.22,
hasCoupon: true,
};
/** Evaluate the criteria against the rule */
let result = await RulePilot.evaluate<number>(rule, criteria); // result = 5
criteria.country = "SE";
criteria.city = "Linköping";
/** Evaluate the new criteria against the rule */
result = await RulePilot.evaluate<number>(rule, criteria); // result = 10
criteria.country = "IT";
criteria.
