@icrules/core

ICRules

Intuitive Compact Rules

An extensible JavaScript rules engine with serialized compact JSON container with a React based rules editor to generate rules.

Quick Start

# Install
npm i @icrules/core

Rules include a quantifier and a list of rules that are evaluated individually against the facts.

Here is sample of what constructing a rule manually against a small set of facts would look like...

import { processRules } from '@icrules/core';

const facts = {
market: 'en-US',
color: 'blue',
diameter: 10
};

const rules = {
any: [
['market', 'eq', 'en-US'],
['diameter', 'gt', 5]
]
};

const result = processRules(facts, rules);

// result.pass is true if market is en-US and diameter is greater than 5
console.log(result.pass)

Rule Schema

The rules schema utilizes a compact human readable JSON compatible JavaScript model.

A Rule Group has a index key quantifier of all or any and references a Rule or RuleGroup

A simple rule looks like this given facts are { grass: 'green' }

{ all: [['grass', 'eq', 'green']] }
^ ^ ^ ^ ^
| | | | term => term that is evaluated against subject
| | | operator ===> evaluates condition
| | subject ===> reference facts field
| group ===> allows us to combine multiple rules using the quantifier
quantifier ===> evaluates rule list as "all" ([].every) or "any" ([].some)

More complex rules can be constructed with nested rule groups.

// In the below rule the user must have selected language as "en" and 
// either must be signed-in or have a guestid
const rule = {
all: [
['language', 'eq', 'en'],
{
any: [
['signedin', 'eq', true],
['guestid', 'eq', true]
]
}
]
}

Note the JSON construct tends to be compact and easy to read using this schema.

Using this scheme allows us to construct more complex rules. It's as simple as providing a list of rules for each quantifier to evaluate against the facts. Each quantifier references an array of rules or rule groups.

ICRules API

processRules()

import { processRules, Facts, Rules, Plugin } from '@icrules/core';

const facts: Facts = {
market: 'en-US',
signedIn: false
}

const rules: Rules = {
all: [
['market', 'eq', 'en-US'],
['signedIn', 'eq', true],
]
}

const plugins = [] as Plugin[];
const results: ProcessResult = processRules(facts, rules, plugins);

if(results.pass){
// do the thing that needs done
}

processVerbose()

ICRules() instance

Extensibility

There is a plugin model that allows us to extend the results of the rules being processed. That plugin model is used internally to create a verbosePlugin result.

import { processRules } from '@icrules/core';

// sample facts
const facts = {
language: 'en',
signedin: false,
guestid: true
}

// sample rule
const rule = {
all: [
['language', 'eq', 'en'],
{
any: [
['signedin', 'eq', true],
['guestid', 'eq', true]
]
}
]
};

// the verbose plugin is fairly basic in that it simply returns the
// values sent in on each level with zero logic, filters and processing
const verbosePlugin = ({ pass, rule, group }) => ({ pass, rule, group });
const processResult = processRules(facts, rule, [verbosePlugin]);

The plugin or plugins are processed for each rule and group evaluated.

With no plugin, processResult value would simply be { pass: true }.

With the verbose plugin it will return each rule evaluated { rule } and if it passed or failed { pass } along with each group evaluated { group }. We can use this information to evaluate which rules are passing/failing to debug the facts/rules used. This is what the editor uses to inform the user what rules and groups are passing in real time against facts while creating rules.

The results from the verbose plugin look like this...

{
"pass": true,
"rule": {
"all": [
["language", "eq","en"],
{
"any": [
["signedin", "eq", true ],
["guestid", "eq", true ]
]
}
]
},
"group": { // group gives us insights into all rules evaluated
"all": [
{
"pass": true,
"rule": ["language", "eq", "en"]
},
{
"pass": true,
"rule": {
"any": [
["signedin", "eq", true],
["guestid", "eq", true]
]
},
"group": {
"any": [
{
"pass": false, // <-- this is the only evalutation that failed
"rule": ["signedin", "eq", true]
},
{
"pass": true,
"rule": ["guestid", "eq", true]
}
],
"pass": true // <-- this group passed
}
}
],
"pass": true // <-- this parent or root group passed
}
}

Here are the Rule and RuleGroup types might help one understand the schema construct a bit better.

export type Subject = string;
export type Term = any;
export type Rule = [Subject, Operator, Term];
export type Quantifiers = 'all' | 'any';
export type Operator = 'eq' | 'neq' | 'gt' | 'lt' | 'gte' | 'lte' | 'has' | 'nhas' | 'in' | 'nit';
export type Rules = (Rule | RuleGroup)[];
export type RuleGroup = { all?: Rules, any?: Rules };

Using the Operators

The editor uses these labels to each operator which better explain the general usage of operators

operator label JS
eq equals ===
neq not equals !==
lt less than <
lte less or equal <=
gt greater than >
gte greater or equal >=
has contains fact.includes
nhas not contains !fact.includes
in in term term.includes
nit not in term !term.includes

Examples

Plugin support