npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

rules-engine-ts

v2.0.4

Published

Strongly typed rules engine for evaluating deep and complex rules

Downloads

22

Readme

rules-engine-ts

Status GitHub Issues GitHub Pull Requests License


Table of Contents

About

Rules Engine TS is a strongly typed rules engine for evaluating deep and complex rules. With the power of Typescript you can create type safe rules that are easy to read and maintain.

Terminology

Rule

A rule is a single condition that can be evaluated. A rule can be of the following types:

  • string
  • number
  • boolean
  • array_value
  • array_length
  • object_key
  • object_value
  • object_key_value
  • generic_comparison
  • generic_type

Depending on the type, certain operators are available. For example, the string type has the following operators:

  • equals_to
  • does_not_equal_to
  • contains
  • not_contains
  • starts_with
  • ends_with

Refer to the Rules Specification section for more information on the available properties.

Union

A union is a collection of rules and/or other unions. A union can have a connector of and or or. If the connector is and then all rules and unions must evaluate to true. If the connector is or then only one rule or union must evaluate to true.

Root Union

The root union is the top level union. It contains the same properties of a regular union but does not have a parent_id property.

Parent

The parent of a rule or union is the union that contains it. Any rule or union can be linked back to its parent with its parent_id property. The parent union should contain the rule or union in its rules array.

Basic Usage

The recommended way to consume rules-engine-ts is in a TypeScript environment. TypeScript will warn you when your rules are missing properties or if the types of your properties are incorrect. That isn't to say that rules-engine-ts can't be run with JavaScript. You will still get autocomplete on the available properties, but you will not get any warnings if you are missing properties or if the types of your properties are incorrect.

A rules engine can be configured and run like so:

import { addRuleToUnion, addRulesToUnion, addUnionToUnion, createRoot, run } from 'rules-engine-ts';

// Create root union
const root = createRoot({ connector: 'and' });

// Add a rule to the root union
addRuleToUnion(root, { type: 'number', field: 'age', operator: 'greater_than', value: 18 });

// Add a union to the root union (creates a nested ruleset)
const union = addUnionToUnion(root, { connector: 'or' });

// Add nested rules to the nested union
addRulesToUnion(union, [
  { type: 'string', field: 'name', value: 'bob', operator: 'equals_to', ignore_case: true },
  { type: 'string', field: 'name', value: 'alice', operator: 'equals_to', ignore_case: true },
]);

// Run the rules engine
const pass = run(root, { age: 19, name: 'Bob' });
const fail = run(root, { age: 19, name: 'Carol' });

console.log(pass); // true
console.log(fail); // false

This is what the state of the Rule Engine looks like:

{
  "entity": "root_union",
  "id": "0d7428af-10e4-481b-84a7-056946bd4f12",
  "connector": "and",
  "rules": [
    {
      "entity": "rule",
      "id": "82e96b0d-886e-4a2e-bf8c-f81b02ef11ce",
      "parent_id": "0d7428af-10e4-481b-84a7-056946bd4f12",
      "type": "number",
      "field": "age",
      "operator": "greater_than",
      "value": 18
    },
    {
      "entity": "union",
      "id": "7c493486-409b-48df-bd66-7f4a16500c5e",
      "parent_id": "0d7428af-10e4-481b-84a7-056946bd4f12",
      "connector": "or",
      "rules": [
        {
          "entity": "rule",
          "id": "3abc4e64-d6c8-4303-9d07-b573a571f19a",
          "parent_id": "7c493486-409b-48df-bd66-7f4a16500c5e",
          "type": "string",
          "field": "name",
          "operator": "equals_to",
          "value": "bob",
          "ignore_case": true
        },
        {
          "entity": "rule",
          "id": "a3995445-55ca-49b2-8381-3d6758750413",
          "parent_id": "7c493486-409b-48df-bd66-7f4a16500c5e",
          "type": "string",
          "field": "name",
          "operator": "equals_to",
          "value": "alice",
          "ignore_case": true
        }
      ]
    }
  ]
}

UI Implementation Example

rules-engine-ui-example

The rules can then be persisted into a database in JSON format:

{
  "entity": "root_union",
  "id": "598444ae-032c-4ae5-85da-644cf90ab920",
  "connector": "or",
  "rules": [
    {
      "entity": "rule",
      "id": "03fcb9b5-a3fe-4d63-97f3-dfce431c331d",
      "parent_id": "598444ae-032c-4ae5-85da-644cf90ab920",
      "type": "string",
      "field": "user_display_name",
      "operator": "equals_to",
      "value": "Alice",
      "ignore_case": true
    },
    {
      "id": "d60639aa-8239-40c7-9cc3-ec89f8f8c58d",
      "entity": "union",
      "connector": "and",
      "parent_id": "598444ae-032c-4ae5-85da-644cf90ab920",
      "rules": [
        {
          "entity": "rule",
          "id": "1821d9da-9f37-4689-a118-bf436ca37e89",
          "parent_id": "d60639aa-8239-40c7-9cc3-ec89f8f8c58d",
          "type": "string",
          "field": "user_display_name",
          "operator": "equals_to",
          "value": "Bob",
          "ignore_case": true
        },
        {
          "entity": "rule",
          "id": "c2a058ab-6005-44a4-94ae-b75736dce536",
          "parent_id": "d60639aa-8239-40c7-9cc3-ec89f8f8c58d",
          "type": "number",
          "field": "total_challenges",
          "operator": "greater_than_or_equal_to",
          "value": 5
        }
      ]
    }
  ]
}

At a later date, the rules can retrieved from the database and can be run by the rules engine like this:

import { run } from 'rules-engine-ts';

const rules = getRulesFromDatabase();

const pass = run(rules, { user_display_name: 'alice', total_challenges: 0 });
const fail = run(rules, { user_display_name: 'bob', total_challenges: 0 });

if (pass) {
  // do something
}

if (fail) {
  //do somehting else
}

Installation

Install the package using your favorite package manager:

npm install rules-engine-ts
yarn add rules-engine-ts
pnpm add rules-engine-ts

Usage

createRoot(connector: 'and' | 'or'): RootUnion

Creates a root union. This is the entry point for creating a rules engine.

import { createRoot } from 'rules-engine-ts';

const root = createRoot({ connector: 'and' });

State of the Rules Engine:

{
  "entity": "root_union",
  "id": "0d7428af-10e4-481b-84a7-056946bd4f12",
  "connector": "and",
  "rules": []
}

addRuleToUnion(parent: RootUnion | Union, newRule: NewRule): Rule

Adds a rule to a union or root union. The rules engine assigns a unique ID and automatically tags it with a parent_id. Returns the rule that was added.

Note: This function mutates the input union. Clone the union before passing it in if you want to maintain its original state.

import { addRuleToUnion, createRoot } from 'rules-engine-ts';

const root = createRoot({ connector: 'and' });

addRuleToUnion(root, { type: 'number', field: 'age', operator: 'greater_than', value: 18 });

State of the Rules Engine:

{
  "entity": "root_union",
  "id": "0d7428af-10e4-481b-84a7-056946bd4f12",
  "connector": "and",
  "rules": [
    {
      "entity": "rule",
      "id": "82e96b0d-886e-4a2e-bf8c-f81b02ef11ce",
      "parent_id": "0d7428af-10e4-481b-84a7-056946bd4f12",
      "type": "number",
      "field": "age",
      "operator": "greater_than",
      "value": 18
    }
  ]
}

addRulesToUnion(parent: RootUnion | Union, newRules: NewRule[]): Rule[]

Adds many rules to a union or root union. The rules engine assigns a unique ID and automatically tags it with a parent_id. Returns the list of rules that were added.

Note: This function mutates the input union. Clone the union before passing it in if you want to maintain its original state.

import { addRulesToUnion, createRoot } from 'rules-engine-ts';

const root = createRoot({ connector: 'and' });

addRulesToUnion(root, [
  { type: 'string', field: 'name', value: 'bob', operator: 'equals_to', ignore_case: true },
  { type: 'string', field: 'name', value: 'alice', operator: 'equals_to', ignore_case: true },
]);

State of the Rules Engine:

{
  "entity": "root_union",
  "id": "61dadd25-22a0-4e84-abe5-92fcfd6cac9e",
  "connector": "and",
  "rules": [
    {
      "entity": "rule",
      "id": "50158f7e-1d87-4ca8-aaca-ef1bbb41c9c2",
      "parent_id": "61dadd25-22a0-4e84-abe5-92fcfd6cac9e",
      "type": "string",
      "field": "name",
      "operator": "equals_to",
      "value": "bob",
      "ignore_case": true
    },
    {
      "entity": "rule",
      "id": "5f6ac1d1-7ce7-40a5-a94c-5e4a47a45e28",
      "parent_id": "61dadd25-22a0-4e84-abe5-92fcfd6cac9e",
      "type": "string",
      "field": "name",
      "operator": "equals_to",
      "value": "alice",
      "ignore_case": true
    }
  ]
}

addUnionToUnion(parent: RootUnion | Union, newUnion: NewUnion): Union

Adds a union to an existing union or root union. Returns the rule that was added.

Note: This function mutates the input union. Clone the union before passing it in if you want to maintain its original state.

import { addRuleToUnion, addRulesToUnion, addUnionToUnion, createRoot } from 'rules-engine-ts';

const root = createRoot({ connector: 'and' });

const union = addUnionToUnion(root, { connector: 'or' });
addRuleToUnion(union, { type: 'string', field: 'name', value: 'bob', operator: 'equals_to', ignore_case: true });
addRuleToUnion(union, { type: 'string', field: 'name', value: 'alice', operator: 'equals_to', ignore_case: true });

State of the Rules Engine:

{
  "entity": "root_union",
  "id": "0d7428af-10e4-481b-84a7-056946bd4f12",
  "connector": "and",
  "rules": [
    {
      "entity": "union",
      "id": "7c493486-409b-48df-bd66-7f4a16500c5e",
      "parent_id": "0d7428af-10e4-481b-84a7-056946bd4f12",
      "connector": "or",
      "rules": [
        {
          "entity": "rule",
          "id": "3abc4e64-d6c8-4303-9d07-b573a571f19a",
          "parent_id": "7c493486-409b-48df-bd66-7f4a16500c5e",
          "type": "string",
          "field": "name",
          "operator": "equals_to",
          "value": "bob",
          "ignore_case": true
        },
        {
          "entity": "rule",
          "id": "a3995445-55ca-49b2-8381-3d6758750413",
          "parent_id": "7c493486-409b-48df-bd66-7f4a16500c5e",
          "type": "string",
          "field": "name",
          "operator": "equals_to",
          "value": "alice",
          "ignore_case": true
        }
      ]
    }
  ]
}

addUnionsToUnion(parent: RootUnion | Union, newUnions: NewUnion[]): Union[]

Adds many unions to an existing union or root union. Returns the list of unions that were added.

Note: This function mutates the input union. Clone the union before passing it in if you want to maintain its original state.

import { addRulesToUnion, addUnionsToUnion, createRoot } from 'rules-engine-ts';

const root = createRoot({ connector: 'and' });

const unions = addUnionsToUnion(root, [{ connector: 'or' }, { connector: 'or' }]);
addRulesToUnion(unions[0], [
  { type: 'string', field: 'name', value: 'bob', operator: 'equals_to', ignore_case: true },
  { type: 'string', field: 'name', value: 'alice', operator: 'equals_to', ignore_case: true },
]);
addRulesToUnion(unions[1], [
  { type: 'number', field: 'age', value: 18, operator: 'equals_to' },
  { type: 'number', field: 'age', value: 21, operator: 'equals_to' },
]);

State of the Rules Engine:

{
  "entity": "root_union",
  "id": "28a9ae06-594a-4520-8d73-2fd871804634",
  "connector": "and",
  "rules": [
    {
      "entity": "union",
      "id": "8e5e66dd-e86c-4f9e-acc7-7baa852fdfe8",
      "parent_id": "28a9ae06-594a-4520-8d73-2fd871804634",
      "connector": "or",
      "rules": [
        {
          "entity": "rule",
          "id": "a8ecbafd-0a1a-4e9e-bb70-8bd11a62f274",
          "parent_id": "8e5e66dd-e86c-4f9e-acc7-7baa852fdfe8",
          "type": "string",
          "field": "name",
          "operator": "equals_to",
          "value": "bob",
          "ignore_case": true
        },
        {
          "entity": "rule",
          "id": "d4fd56bf-af82-4382-a1cb-93d80cb87ef4",
          "parent_id": "8e5e66dd-e86c-4f9e-acc7-7baa852fdfe8",
          "type": "string",
          "field": "name",
          "operator": "equals_to",
          "value": "alice",
          "ignore_case": true
        }
      ]
    },
    {
      "entity": "union",
      "id": "5e5d7f00-d0f6-40d9-84b3-39600241a92f",
      "parent_id": "28a9ae06-594a-4520-8d73-2fd871804634",
      "connector": "or",
      "rules": [
        {
          "entity": "rule",
          "id": "7ea03690-d9c4-4a3e-97eb-d927cf6845e8",
          "parent_id": "5e5d7f00-d0f6-40d9-84b3-39600241a92f",
          "type": "number",
          "field": "age",
          "operator": "equals_to",
          "value": 18
        },
        {
          "entity": "rule",
          "id": "bb63d7da-b0dc-4d00-824c-4de09151c609",
          "parent_id": "5e5d7f00-d0f6-40d9-84b3-39600241a92f",
          "type": "number",
          "field": "age",
          "operator": "equals_to",
          "value": 21
        }
      ]
    }
  ]
}

addAnyToUnion(parent: RootUnion | Union, newRuleOrUnion: NewRule | NewUnion): Rule | Union

Adds a rule or a union to an existing union or root union. Returns the rule or union that was added.

Note: This function mutates the input union. Clone the union before passing it in if you want to maintain its original state.

import { addAnyToUnion, createRoot } from 'rules-engine-ts';

const root = createRoot({ connector: 'and' });

const any = addAnyToUnion(root, { connector: 'or' });
if (any.entity === 'union') {
  addAnyToUnion(any, { type: 'string', field: 'name', value: 'bob', operator: 'equals_to', ignore_case: true });
  addAnyToUnion(any, { type: 'string', field: 'name', value: 'alice', operator: 'equals_to', ignore_case: true });
}

State of the Rules Engine:

{
  "entity": "root_union",
  "id": "825ef3d8-3151-4367-b751-1deae8b308c1",
  "connector": "and",
  "rules": [
    {
      "entity": "union",
      "id": "71b5296a-5358-4399-878d-f535c9f21faf",
      "parent_id": "825ef3d8-3151-4367-b751-1deae8b308c1",
      "connector": "or",
      "rules": [
        {
          "entity": "rule",
          "id": "3cd0463f-c5e7-4dd9-98b8-9e7cf79417b5",
          "parent_id": "71b5296a-5358-4399-878d-f535c9f21faf",
          "type": "string",
          "field": "name",
          "operator": "equals_to",
          "value": "bob",
          "ignore_case": true
        },
        {
          "entity": "rule",
          "id": "f10e7cec-c737-4c2c-b137-d7ab0e26e045",
          "parent_id": "71b5296a-5358-4399-878d-f535c9f21faf",
          "type": "string",
          "field": "name",
          "operator": "equals_to",
          "value": "alice",
          "ignore_case": true
        }
      ]
    }
  ]
}

addManyToUnion(parent: RootUnion | Union, newRulesOrUnions: (NewRule | NewUnion)[]): (Rule | Union)[]

Adds many rules or unions to an existing union or root union. Returns the list of rules or unions that were added.

Note: This function mutates the input union. Clone the union before passing it in if you want to maintain its original state.

import { addManyToUnion, createRoot } from 'rules-engine-ts';

const root = createRoot({ connector: 'and' });

addManyToUnion(root, [
  { type: 'string', field: 'name', value: 'bob', operator: 'equals_to', ignore_case: true },
  { type: 'string', field: 'name', value: 'alice', operator: 'equals_to', ignore_case: true },
  { connector: 'or' },
]);

State of the Rules Engine:

{
  "entity": "root_union",
  "id": "26252c95-37da-47d4-b361-7ad82ae13a9b",
  "connector": "and",
  "rules": [
    {
      "entity": "rule",
      "id": "edbf5239-e931-480a-b231-119af2c1a1d1",
      "parent_id": "26252c95-37da-47d4-b361-7ad82ae13a9b",
      "type": "string",
      "field": "name",
      "operator": "equals_to",
      "value": "bob",
      "ignore_case": true
    },
    {
      "entity": "rule",
      "id": "1e9109fa-30cf-41a9-9e78-e8395a423d6d",
      "parent_id": "26252c95-37da-47d4-b361-7ad82ae13a9b",
      "type": "string",
      "field": "name",
      "operator": "equals_to",
      "value": "alice",
      "ignore_case": true
    },
    {
      "entity": "union",
      "id": "0bb57d03-ac07-41af-941a-6a2625bac130",
      "parent_id": "26252c95-37da-47d4-b361-7ad82ae13a9b",
      "connector": "or",
      "rules": []
    }
  ]
}

run(union: RootUnion | Union, value: any): boolean

Evaluates a set of rules against a value. The value can be of any type (object, array, string, number, boolean, etc). Returns a boolean indicating whether the value passes the rules.

import { addRuleToUnion, addRulesToUnion, addUnionToUnion, createRoot, run } from 'rules-engine-ts';

const root = createRoot({ connector: 'and' });
addRuleToUnion(root, { type: 'number', field: 'age', operator: 'greater_than', value: 18 });

const union = addUnionToUnion(root, { connector: 'or' });
addRulesToUnion(union, [
  { type: 'string', field: 'name', value: 'bob', operator: 'equals_to', ignore_case: true },
  { type: 'string', field: 'name', value: 'alice', operator: 'equals_to', ignore_case: true },
]);

const pass = run(root, { age: 19, name: 'Bob' });
const fail = run(root, { age: 19, name: 'Carol' });

console.log(pass); // true
console.log(fail); // false

findAnyById(union: RootUnion | Union, id: string): RootUnion | Union | Rule | undefined

Finds any rule or union by id. Returns the rule or union if found, otherwise returns undefined.

import { addRuleToUnion, addUnionToUnion, createRoot, findAnyById } from 'rules-engine-ts';

const root = createRoot({ connector: 'and' });
const rule = addRuleToUnion(root, { type: 'number', field: 'age', operator: 'greater_than', value: 18 });
const union = addUnionToUnion(root, { connector: 'or' });

const foundRule = findAnyById(root, rule.id);
console.log(foundRule === rule); // true

const foundUnion = findAnyById(root, union.id);
console.log(foundUnion === union); // true

findRuleById(union: RootUnion | Union, id: string): Rule | undefined

Finds a rule by id. Returns the rule if found, otherwise returns undefined.

import { addRuleToUnion, addUnionToUnion, createRoot, findRuleById } from 'rules-engine-ts';

const root = createRoot({ connector: 'and' });
const rule = addRuleToUnion(root, { type: 'number', field: 'age', operator: 'greater_than', value: 18 });
const union = addUnionToUnion(root, { connector: 'or' });

const foundRule = findRuleById(root, rule.id);
console.log(foundRule === rule); // true

const foundUnion = findRuleById(root, union.id);
console.log(foundUnion); // undefined

findUnionById(union: RootUnion | Union, id: string): RootUnion | Union | undefined

Finds a union by id. Returns the union if found, otherwise returns undefined.

import { addRuleToUnion, addUnionToUnion, createRoot, findUnionById } from 'rules-engine-ts';

const root = createRoot({ connector: 'and' });
const rule = addRuleToUnion(root, { type: 'number', field: 'age', operator: 'greater_than', value: 18 });
const union = addUnionToUnion(root, { connector: 'or' });

const foundUnion = findUnionById(root, union.id);
console.log(foundUnion === union); // true;

const foundRule = findUnionById(root, rule.id);
console.log(foundRule); // undefined;

validate(root: RootUnion): { isValid: true } | { isValid: false; reason: string }

Validates the structure of a ruleset. Returns an object with a boolean indicating whether the ruleset is valid, and a reason if the ruleset is invalid.

import { addRuleToUnion, createRoot, validate } from 'rules-engine-ts';

const root = createRoot({ connector: 'and' });
const rule = addRuleToUnion(root, { type: 'number', field: 'age', operator: 'greater_than', value: 18 });

console.log(validate(root));
// { isValid: true }

rule.type = 'string';

console.log(validate(root));
// {
//   isValid: false,
//   reason: 'Code: invalid_union ~ Path: rules[0] ~ Message: Invalid input'
// }

normalize<T extends Union | RootUnion>(union: T, options?: Options): T

Normalization is a process that ensures that the ruleset is in a consistent state. It performs the following updates recursively in the following order:

  • Removes any rules or unions that do not conform to the type system. options.remove_failed_validations
  • Removes any unions without any rules. options.remove_empty_unions
  • Converts any union with a single rule to a rule. options.promote_single_rule_unions
  • Updates all parent ids to match the parent union options.update_parent_ids

All these updates are turned on by default. You can disable them by passing in an options object as the second argument with the corresponding properties set to false.

Note: This function mutates the input union. Clone the union before passing it in if you want to maintain its original state.

import { addRuleToUnion, addUnionToUnion, createRoot, normalize } from 'rules-engine-ts';

import { v4 as uuidv4 } from 'uuid';

const root = createRoot({ connector: 'or' });

const rule1 = addRuleToUnion(root, { field: 'name', operator: 'contains', type: 'string', value: 'bob' });
const union = addUnionToUnion(root, { connector: 'and' });
const rule2 = addRuleToUnion(union, { field: 'name', operator: 'contains', type: 'string', value: 'alice' });

rule1.parent_id = uuidv4();
rule2.type = 'number';
// @ts-expect-error
union.connector = 'invalid';

console.log(root); // Before normalization
normalize(root, {
  // Normalization options (optional)
  promote_single_rule_unions: true,
  remove_empty_unions: true,
  remove_failed_validations: true,
  update_parent_ids: true,
});
console.log(root); // After normalization

Before normalization:

{
  "entity": "root_union",
  "id": "70cf2539-b960-4831-b0f2-3b201aea550a",
  "connector": "or",
  "rules": [
    {
      "entity": "rule",
      "id": "4b644371-6bc2-46b1-b855-c2098df80fb3",
      "parent_id": "8ca677c2-b01c-4cf2-91ec-9c95b6ff7dff",
      "type": "string",
      "field": "name",
      "operator": "contains",
      "value": "bob"
    },
    {
      "entity": "union",
      "id": "c43e8705-6b4b-42b5-941c-3295c17cf5db",
      "parent_id": "70cf2539-b960-4831-b0f2-3b201aea550a",
      "connector": "invalid",
      "rules": [
        {
          "entity": "rule",
          "id": "8589e28c-a1d5-4a0b-b930-24c5931eaadb",
          "parent_id": "c43e8705-6b4b-42b5-941c-3295c17cf5db",
          "type": "number",
          "field": "name",
          "operator": "contains",
          "value": "alice"
        }
      ]
    }
  ]
}

After normalization:

{
  "entity": "root_union",
  "id": "70cf2539-b960-4831-b0f2-3b201aea550a",
  "connector": "or",
  "rules": [
    {
      "entity": "rule",
      "id": "4b644371-6bc2-46b1-b855-c2098df80fb3",
      "parent_id": "70cf2539-b960-4831-b0f2-3b201aea550a",
      "type": "string",
      "field": "name",
      "operator": "contains",
      "value": "bob"
    }
  ]
}

updateRuleById(root: RootUnion, id: string, values: NewRule): Rule | undefined

Updates a rule by id. Returns the updated rule if found, otherwise returns undefined.

Note: This function mutates the input root union. Clone the union before passing it in if you want to maintain its original state.

import { addRuleToUnion, createRoot, updateRuleById } from 'rules-engine-ts';

const root = createRoot({ connector: 'and' });
const rule = addRuleToUnion(root, { type: 'number', field: 'age', operator: 'greater_than', value: 18 });

console.log(root.rules[0]); // Before update
updateRuleById(root, rule.id, { type: 'number', field: 'age', operator: 'less_than', value: 30 });
console.log(root.rules[0]); // After update

Before update:

{
  "entity": "rule",
  "id": "cc3d4bab-783a-4683-a223-8dee979b0bf0",
  "parent_id": "e0da0708-1fbf-4e64-887c-d7684b17dd00",
  "type": "number",
  "field": "age",
  "operator": "greater_than",
  "value": 18
}

After update:

{
  "entity": "rule",
  "id": "cc3d4bab-783a-4683-a223-8dee979b0bf0",
  "parent_id": "e0da0708-1fbf-4e64-887c-d7684b17dd00",
  "type": "number",
  "field": "age",
  "operator": "less_than",
  "value": 30
}

updateUnionById(root: RootUnion, id: string, values: NewUnion): Union | RootUnion | undefined

Updates a union by id. Returns the updated union if found, otherwise returns undefined.

Note: This function mutates the input root union. Clone the union before passing it in if you want to maintain its original state.

import { addUnionToUnion, createRoot, updateUnionById } from 'rules-engine-ts';

const root = createRoot({ connector: 'and' });
const union = addUnionToUnion(root, { connector: 'and' });

console.log(root.rules[0]); // Before update
updateUnionById(root, union.id, { connector: 'or' });
console.log(root.rules[0]); // After update

Before update:

{
  "entity": "union",
  "id": "b0a289a5-f02e-4bb4-bbbf-d148d1fc570f",
  "parent_id": "1ac8dad7-46c0-430b-9ad1-fdb8f1fd721a",
  "connector": "and",
  "rules": []
}

After update:

{
  "entity": "union",
  "id": "b0a289a5-f02e-4bb4-bbbf-d148d1fc570f",
  "parent_id": "1ac8dad7-46c0-430b-9ad1-fdb8f1fd721a",
  "connector": "or",
  "rules": []
}

removeAllById<T extends RootUnion | Union>(union: T, id: string): T

Removes all rules and unions of a given id from a ruleset. Returns the updated ruleset.

Note: This function mutates the input union. Clone the union before passing it in if you want to maintain its original state.

import { addRuleToUnion, addUnionToUnion, createRoot, removeAllById } from 'rules-engine-ts';

const root = createRoot({ connector: 'and' });
const union = addUnionToUnion(root, { connector: 'or' });
const rule = addRuleToUnion(union, { type: 'number', field: 'age', operator: 'greater_than', value: 18 });

console.log(union.rules.length); // 1
removeAllById(root, rule.id);
console.log(union.rules.length); // 0

Rules Specification

The properties of a rule change depending on the type field. The type field acts as a discriminator to determine which properties are valid for a given rule.

type = 'string'

| Property | Value | Description | | ----------- | --------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | | type | 'string' | The type of the value to be evaluated. | | field | string | The field to check. Supports nested properties, e.g. users.admins[0].name. | | operator | 'equals_to' 'does_not_equal_to' 'contains' 'does_not_contain' 'starts_with' 'ends_with' | The operator to use. | | value | string | The value to compare against. | | ignore_case | boolean | Whether to ignore case when comparing strings. |

type = 'number'

| Property | Value | Description | | -------- | ------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | | type | 'number' | The type of the value to be evaluated. | | field | string | The field to check. Supports nested properties, e.g. users.admins[0].age. | | operator | 'equals_to' 'does_not_equal_to' 'greater_than' 'greater_than_or_equal_to' 'less_than' 'less_than_or_equal_to' | The operator to use. | | value | number | The value to compare against. |

type = 'boolean'

| Property | Value | Description | | -------- | -------------------------- | --------------------------------------------------------------------------------- | | type | 'boolean' | The type of the value to be evaluated. | | field | string | The field to check. Supports nested properties, e.g. users.admins[0].is_active. | | operator | 'is_true' 'is_false' | The operator to use. |

type = 'array_value'

| Property | Value | Description | | -------- | -------------------------------------------------------- | -------------------------------------------------------------------- | | type | 'array_value' | The type of the value to be evaluated. | | field | string | The field to check. Supports nested properties, e.g. users.admins. | | operator | 'contains' 'does_not_contain' 'contains_all' | The operator to use. | | value | any | The value to compare against. |

type = 'array_length'

| Property | Value | Description | | -------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | | type | 'array_length' | The type of the value to be evaluated. | | field | string | The field to check. Supports nested properties, e.g. users.admins. | | operator | 'equals_to' 'does_not_equal_to' 'greater_than' 'greater_than_or_equal_to' 'less_than' 'less_than_or_equal_to' | The operator to use. | | value | number | The value to compare against. |

type = 'object_key'

| Property | Value | Description | | -------- | ----------------------------------- | ----------------------------------------------------------------------- | | type | 'object_key' | The type of the value to be evaluated. | | field | string | The field to check. Supports nested properties, e.g. users.admins[0]. | | operator | 'contains' 'does_not_contain' | The operator to use. | | value | string | The value to compare against. |

type = 'object_value'

| Property | Value | Description | | -------- | ----------------------------------- | -------------------------------------------------------------------- | | type | 'object_value' | The type of the value to be evaluated. | | field | string | The field to check. Supports nested properties, e.g. users.admins. | | operator | 'contains' 'does_not_contain' | The operator to use. | | value | any | The value to compare against. |

type = 'object_key_value'

| Property | Value | Description | | -------- | ----------------------------------- | -------------------------------------------------------------------- | | type | 'object_key_value' | The type of the value to be evaluated. | | field | string | The field to check. Supports nested properties, e.g. users.admins. | | operator | 'contains' 'does_not_contain' | The operator to use. | | value | { key: 'string', value: 'any' } | The value to compare against. |

type = 'generic_comparison'

| Property | Value | Description | | -------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | | type | 'generic_comparison' | The type of the value to be evaluated. | | field | string | The field to check. Supports nested properties, e.g. users.admins[0].unknown_property. | | operator | 'equals_to' 'does_not_equal_to' 'greater_than' 'greater_than_or_equal_to' 'less_than' 'less_than_or_equal_to' | The operator to use. |

type = 'generic_type'

| Property | Value | Description | | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | | type | 'generic_type' | The type of the value to be evaluated. | | field | string | The field to check. Supports nested properties, e.g. users.admins[0].unknown_property. | | operator | 'is_truthy' 'is_falsey' 'is_null' 'is_not_null' 'is_undefined' 'is_not_undefined' 'is_string' 'is_not_string' 'is_number' 'is_not_number' 'is_boolean' 'is_not_boolean' 'is_array' 'is_not_array' 'is_object' 'is_not_object' | The operator to use. | | value | any | The value to compare against. |

TypeScript Usage

Rules can be pre-composed using type anotations before adding it to the rules engine:

import { NewNumberRule, NewRule, addRuleToUnion, createRoot } from 'rules-engine-ts';

const root = createRoot({ connector: 'and' });

const wideTypingRule: NewRule = { type: 'number', field: 'age', operator: 'greater_than', value: 18 };
const narrowTypingRule: NewNumberRule = { type: 'number', field: 'age', operator: 'less_than', value: 30 };

const wideAfterAdding = addRuleToUnion(root, wideTypingRule);
const narrowAfterAdding = addRuleToUnion(root, narrowTypingRule);

console.log(wideAfterAdding);
console.log(narrowAfterAdding);
{
  "entity": "rule",
  "id": "560f4e04-f786-4269-bbdd-704ad9793518",
  "parent_id": "6fa1aaa6-cfab-4647-a30c-a58af3e0a4d4",
  "type": "number",
  "field": "age",
  "operator": "greater_than",
  "value": 18
}
{
  "entity": "rule",
  "id": "46a36441-3f28-4dd7-8420-b1d584527a74",
  "parent_id": "6fa1aaa6-cfab-4647-a30c-a58af3e0a4d4",
  "type": "number",
  "field": "age",
  "operator": "less_than",
  "value": 30
}

Similarly, a union can also be pre-composed before adding it to the rules engine:

import { NewUnion, addUnionToUnion, createRoot } from 'rules-engine-ts';

const userSelectsAnd = false;

const root = createRoot({ connector: 'and' });
const union: NewUnion = { connector: userSelectsAnd ? 'and' : 'or' };

const unionAfterAdding = addUnionToUnion(root, union);
console.log(unionAfterAdding);
{
  "entity": "union",
  "id": "d2ce2a4e-ec53-4a64-9677-e9051c634bd1",
  "parent_id": "8b32fdc4-8e92-424f-9c00-1204838759e0",
  "connector": "or",
  "rules": []
}

To Do

  • [ ] Create recipe examples
  • [ ] Create function to detect conflicting or redundant rules
  • [ ] Create a UI builder tool

Authors

See also the list of contributors who participated in this project.