2.0.4 • Published 2 years ago

rules-engine-ts v2.0.4

Weekly downloads
-
License
MIT
Repository
github
Last release
2 years ago

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'

PropertyValueDescription
type'string'The type of the value to be evaluated.
fieldstringThe 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.
valuestringThe value to compare against.
ignore_casebooleanWhether to ignore case when comparing strings.

type = 'number'

PropertyValueDescription
type'number'The type of the value to be evaluated.
fieldstringThe 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.
valuenumberThe value to compare against.

type = 'boolean'

PropertyValueDescription
type'boolean'The type of the value to be evaluated.
fieldstringThe 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'

PropertyValueDescription
type'array_value'The type of the value to be evaluated.
fieldstringThe field to check. Supports nested properties, e.g. users.admins.
operator'contains' 'does_not_contain' 'contains_all'The operator to use.
valueanyThe value to compare against.

type = 'array_length'

PropertyValueDescription
type'array_length'The type of the value to be evaluated.
fieldstringThe 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.
valuenumberThe value to compare against.

type = 'object_key'

PropertyValueDescription
type'object_key'The type of the value to be evaluated.
fieldstringThe field to check. Supports nested properties, e.g. users.admins[0].
operator'contains' 'does_not_contain'The operator to use.
valuestringThe value to compare against.

type = 'object_value'

PropertyValueDescription
type'object_value'The type of the value to be evaluated.
fieldstringThe field to check. Supports nested properties, e.g. users.admins.
operator'contains' 'does_not_contain'The operator to use.
valueanyThe value to compare against.

type = 'object_key_value'

PropertyValueDescription
type'object_key_value'The type of the value to be evaluated.
fieldstringThe 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'

PropertyValueDescription
type'generic_comparison'The type of the value to be evaluated.
fieldstringThe 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'

PropertyValueDescription
type'generic_type'The type of the value to be evaluated.
fieldstringThe 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.
valueanyThe 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.

2.0.4

2 years ago

2.0.3

2 years ago

2.0.2

2 years ago

2.0.1

2 years ago

2.0.0

2 years ago

1.2.0

2 years ago

1.1.1

2 years ago

1.1.0

2 years ago

1.0.4

2 years ago

1.0.3

2 years ago

1.0.2

2 years ago

1.0.1

2 years ago

1.0.0

2 years ago