{"id":20315949,"url":"https://github.com/andrewvo89/rules-engine-ts","last_synced_at":"2026-03-09T20:31:43.801Z","repository":{"id":142293770,"uuid":"610142657","full_name":"andrewvo89/rules-engine-ts","owner":"andrewvo89","description":"Strongly typed rules engine for evaluating deep and complex rules.","archived":false,"fork":false,"pushed_at":"2023-03-17T00:37:32.000Z","size":86,"stargazers_count":18,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-09-29T11:47:10.417Z","etag":null,"topics":["nodejs","rules-engine","typescript"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/rules-engine-ts","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/andrewvo89.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-03-06T07:16:08.000Z","updated_at":"2025-09-10T21:40:24.000Z","dependencies_parsed_at":null,"dependency_job_id":"9e78c260-36b9-48ab-91ca-609d48c6836c","html_url":"https://github.com/andrewvo89/rules-engine-ts","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/andrewvo89/rules-engine-ts","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrewvo89%2Frules-engine-ts","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrewvo89%2Frules-engine-ts/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrewvo89%2Frules-engine-ts/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrewvo89%2Frules-engine-ts/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/andrewvo89","download_url":"https://codeload.github.com/andrewvo89/rules-engine-ts/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrewvo89%2Frules-engine-ts/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30310756,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-09T20:05:46.299Z","status":"ssl_error","status_checked_at":"2026-03-09T19:57:04.425Z","response_time":61,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["nodejs","rules-engine","typescript"],"created_at":"2024-11-14T18:23:19.555Z","updated_at":"2026-03-09T20:31:43.760Z","avatar_url":"https://github.com/andrewvo89.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"![rules-engine-ts](https://user-images.githubusercontent.com/48583281/225731748-6467e9bf-ee0e-4839-8982-500e0342324b.jpg)\n\n\u003cdiv align=\"center\"\u003e\n\n[![Status](https://img.shields.io/badge/status-active-blue)](https://github.com/andrewvo89/rules-engine-ts)\n[![GitHub Issues](https://img.shields.io/github/issues/andrewvo89/rules-engine-ts?color=blue)](https://github.com/andrewvo89/rules-engine-ts/issues)\n[![GitHub Pull Requests](https://img.shields.io/github/issues-pr/andrewvo89/rules-engine-ts?color=blue)](https://github.com/andrewvo89/rules-engine-ts/pulls)\n[![License](https://img.shields.io/github/license/andrewvo89/rules-engine-ts?color=blue)](/LICENSE)\n\n\u003c/div\u003e\n\n---\n\n\u003cp align=\"center\"\u003eStrongly typed rules engine for evaluating deep and complex rules.\u003c/p\u003e\n\n## Table of Contents\n\n- [About](#about)\n- [Terminology](#terminology)\n- [Basic Usage](#basic-usage)\n- [UI Implementation Example](#ui-implementation-example)\n- [Installation](#installation)\n- [Usage](#usage)\n- [Rules Specification](#rule-specification)\n- [TypeScript Usage](#typescript-usage)\n- [Authors](#authors)\n\n## About\n\nRules 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.\n\n## Terminology\n\n### Rule\n\nA rule is a single condition that can be evaluated. A rule can be of the following types:\n\n- string\n- number\n- boolean\n- array_value\n- array_length\n- object_key\n- object_value\n- object_key_value\n- generic_comparison\n- generic_type\n\nDepending on the `type`, certain operators are available. For example, the `string` type has the following operators:\n\n- equals_to\n- does_not_equal_to\n- contains\n- not_contains\n- starts_with\n- ends_with\n\nRefer to the [Rules Specification](#rules-specification) section for more information on the available properties.\n\n### Union\n\nA 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.\n\n### Root Union\n\nThe root union is the top level union. It contains the same properties of a regular union but does not have a `parent_id` property.\n\n### Parent\n\nThe 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.\n\n## Basic Usage\n\nThe 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.\n\nA rules engine can be configured and run like so:\n\n```js\nimport { addRuleToUnion, addRulesToUnion, addUnionToUnion, createRoot, run } from 'rules-engine-ts';\n\n// Create root union\nconst root = createRoot({ connector: 'and' });\n\n// Add a rule to the root union\naddRuleToUnion(root, { type: 'number', field: 'age', operator: 'greater_than', value: 18 });\n\n// Add a union to the root union (creates a nested ruleset)\nconst union = addUnionToUnion(root, { connector: 'or' });\n\n// Add nested rules to the nested union\naddRulesToUnion(union, [\n  { type: 'string', field: 'name', value: 'bob', operator: 'equals_to', ignore_case: true },\n  { type: 'string', field: 'name', value: 'alice', operator: 'equals_to', ignore_case: true },\n]);\n\n// Run the rules engine\nconst pass = run(root, { age: 19, name: 'Bob' });\nconst fail = run(root, { age: 19, name: 'Carol' });\n\nconsole.log(pass); // true\nconsole.log(fail); // false\n```\n\nThis is what the state of the Rule Engine looks like:\n\n```json\n{\n  \"entity\": \"root_union\",\n  \"id\": \"0d7428af-10e4-481b-84a7-056946bd4f12\",\n  \"connector\": \"and\",\n  \"rules\": [\n    {\n      \"entity\": \"rule\",\n      \"id\": \"82e96b0d-886e-4a2e-bf8c-f81b02ef11ce\",\n      \"parent_id\": \"0d7428af-10e4-481b-84a7-056946bd4f12\",\n      \"type\": \"number\",\n      \"field\": \"age\",\n      \"operator\": \"greater_than\",\n      \"value\": 18\n    },\n    {\n      \"entity\": \"union\",\n      \"id\": \"7c493486-409b-48df-bd66-7f4a16500c5e\",\n      \"parent_id\": \"0d7428af-10e4-481b-84a7-056946bd4f12\",\n      \"connector\": \"or\",\n      \"rules\": [\n        {\n          \"entity\": \"rule\",\n          \"id\": \"3abc4e64-d6c8-4303-9d07-b573a571f19a\",\n          \"parent_id\": \"7c493486-409b-48df-bd66-7f4a16500c5e\",\n          \"type\": \"string\",\n          \"field\": \"name\",\n          \"operator\": \"equals_to\",\n          \"value\": \"bob\",\n          \"ignore_case\": true\n        },\n        {\n          \"entity\": \"rule\",\n          \"id\": \"a3995445-55ca-49b2-8381-3d6758750413\",\n          \"parent_id\": \"7c493486-409b-48df-bd66-7f4a16500c5e\",\n          \"type\": \"string\",\n          \"field\": \"name\",\n          \"operator\": \"equals_to\",\n          \"value\": \"alice\",\n          \"ignore_case\": true\n        }\n      ]\n    }\n  ]\n}\n```\n\n## UI Implementation Example\n\n![rules-engine-ui-example](https://user-images.githubusercontent.com/48583281/225781473-7ab13ad4-901f-44c6-bd1d-1ac2832106a2.gif)\n\nThe rules can then be persisted into a database in JSON format:\n\n```json\n{\n  \"entity\": \"root_union\",\n  \"id\": \"598444ae-032c-4ae5-85da-644cf90ab920\",\n  \"connector\": \"or\",\n  \"rules\": [\n    {\n      \"entity\": \"rule\",\n      \"id\": \"03fcb9b5-a3fe-4d63-97f3-dfce431c331d\",\n      \"parent_id\": \"598444ae-032c-4ae5-85da-644cf90ab920\",\n      \"type\": \"string\",\n      \"field\": \"user_display_name\",\n      \"operator\": \"equals_to\",\n      \"value\": \"Alice\",\n      \"ignore_case\": true\n    },\n    {\n      \"id\": \"d60639aa-8239-40c7-9cc3-ec89f8f8c58d\",\n      \"entity\": \"union\",\n      \"connector\": \"and\",\n      \"parent_id\": \"598444ae-032c-4ae5-85da-644cf90ab920\",\n      \"rules\": [\n        {\n          \"entity\": \"rule\",\n          \"id\": \"1821d9da-9f37-4689-a118-bf436ca37e89\",\n          \"parent_id\": \"d60639aa-8239-40c7-9cc3-ec89f8f8c58d\",\n          \"type\": \"string\",\n          \"field\": \"user_display_name\",\n          \"operator\": \"equals_to\",\n          \"value\": \"Bob\",\n          \"ignore_case\": true\n        },\n        {\n          \"entity\": \"rule\",\n          \"id\": \"c2a058ab-6005-44a4-94ae-b75736dce536\",\n          \"parent_id\": \"d60639aa-8239-40c7-9cc3-ec89f8f8c58d\",\n          \"type\": \"number\",\n          \"field\": \"total_challenges\",\n          \"operator\": \"greater_than_or_equal_to\",\n          \"value\": 5\n        }\n      ]\n    }\n  ]\n}\n```\n\nAt a later date, the rules can retrieved from the database and can be run by the rules engine like this:\n\n```js\nimport { run } from 'rules-engine-ts';\n\nconst rules = getRulesFromDatabase();\n\nconst pass = run(rules, { user_display_name: 'alice', total_challenges: 0 });\nconst fail = run(rules, { user_display_name: 'bob', total_challenges: 0 });\n\nif (pass) {\n  // do something\n}\n\nif (fail) {\n  //do somehting else\n}\n```\n\n## Installation\n\nInstall the package using your favorite package manager:\n\n```\nnpm install rules-engine-ts\nyarn add rules-engine-ts\npnpm add rules-engine-ts\n```\n\n## Usage\n\n### `createRoot(connector: 'and' | 'or'): RootUnion`\n\nCreates a root union. This is the entry point for creating a rules engine.\n\n```js\nimport { createRoot } from 'rules-engine-ts';\n\nconst root = createRoot({ connector: 'and' });\n```\n\nState of the Rules Engine:\n\n```json\n{\n  \"entity\": \"root_union\",\n  \"id\": \"0d7428af-10e4-481b-84a7-056946bd4f12\",\n  \"connector\": \"and\",\n  \"rules\": []\n}\n```\n\n### `addRuleToUnion(parent: RootUnion | Union, newRule: NewRule): Rule`\n\nAdds 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.\n\n\u003e Note: This function mutates the input union. Clone the union before passing it in if you want to maintain its original state.\n\n```js\nimport { addRuleToUnion, createRoot } from 'rules-engine-ts';\n\nconst root = createRoot({ connector: 'and' });\n\naddRuleToUnion(root, { type: 'number', field: 'age', operator: 'greater_than', value: 18 });\n```\n\nState of the Rules Engine:\n\n```json\n{\n  \"entity\": \"root_union\",\n  \"id\": \"0d7428af-10e4-481b-84a7-056946bd4f12\",\n  \"connector\": \"and\",\n  \"rules\": [\n    {\n      \"entity\": \"rule\",\n      \"id\": \"82e96b0d-886e-4a2e-bf8c-f81b02ef11ce\",\n      \"parent_id\": \"0d7428af-10e4-481b-84a7-056946bd4f12\",\n      \"type\": \"number\",\n      \"field\": \"age\",\n      \"operator\": \"greater_than\",\n      \"value\": 18\n    }\n  ]\n}\n```\n\n### `addRulesToUnion(parent: RootUnion | Union, newRules: NewRule[]): Rule[]`\n\nAdds 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.\n\n\u003e Note: This function mutates the input union. Clone the union before passing it in if you want to maintain its original state.\n\n```js\nimport { addRulesToUnion, createRoot } from 'rules-engine-ts';\n\nconst root = createRoot({ connector: 'and' });\n\naddRulesToUnion(root, [\n  { type: 'string', field: 'name', value: 'bob', operator: 'equals_to', ignore_case: true },\n  { type: 'string', field: 'name', value: 'alice', operator: 'equals_to', ignore_case: true },\n]);\n```\n\nState of the Rules Engine:\n\n```json\n{\n  \"entity\": \"root_union\",\n  \"id\": \"61dadd25-22a0-4e84-abe5-92fcfd6cac9e\",\n  \"connector\": \"and\",\n  \"rules\": [\n    {\n      \"entity\": \"rule\",\n      \"id\": \"50158f7e-1d87-4ca8-aaca-ef1bbb41c9c2\",\n      \"parent_id\": \"61dadd25-22a0-4e84-abe5-92fcfd6cac9e\",\n      \"type\": \"string\",\n      \"field\": \"name\",\n      \"operator\": \"equals_to\",\n      \"value\": \"bob\",\n      \"ignore_case\": true\n    },\n    {\n      \"entity\": \"rule\",\n      \"id\": \"5f6ac1d1-7ce7-40a5-a94c-5e4a47a45e28\",\n      \"parent_id\": \"61dadd25-22a0-4e84-abe5-92fcfd6cac9e\",\n      \"type\": \"string\",\n      \"field\": \"name\",\n      \"operator\": \"equals_to\",\n      \"value\": \"alice\",\n      \"ignore_case\": true\n    }\n  ]\n}\n```\n\n### `addUnionToUnion(parent: RootUnion | Union, newUnion: NewUnion): Union`\n\nAdds a union to an existing union or root union. Returns the rule that was added.\n\n\u003e Note: This function mutates the input union. Clone the union before passing it in if you want to maintain its original state.\n\n```js\nimport { addRuleToUnion, addRulesToUnion, addUnionToUnion, createRoot } from 'rules-engine-ts';\n\nconst root = createRoot({ connector: 'and' });\n\nconst union = addUnionToUnion(root, { connector: 'or' });\naddRuleToUnion(union, { type: 'string', field: 'name', value: 'bob', operator: 'equals_to', ignore_case: true });\naddRuleToUnion(union, { type: 'string', field: 'name', value: 'alice', operator: 'equals_to', ignore_case: true });\n```\n\nState of the Rules Engine:\n\n```json\n{\n  \"entity\": \"root_union\",\n  \"id\": \"0d7428af-10e4-481b-84a7-056946bd4f12\",\n  \"connector\": \"and\",\n  \"rules\": [\n    {\n      \"entity\": \"union\",\n      \"id\": \"7c493486-409b-48df-bd66-7f4a16500c5e\",\n      \"parent_id\": \"0d7428af-10e4-481b-84a7-056946bd4f12\",\n      \"connector\": \"or\",\n      \"rules\": [\n        {\n          \"entity\": \"rule\",\n          \"id\": \"3abc4e64-d6c8-4303-9d07-b573a571f19a\",\n          \"parent_id\": \"7c493486-409b-48df-bd66-7f4a16500c5e\",\n          \"type\": \"string\",\n          \"field\": \"name\",\n          \"operator\": \"equals_to\",\n          \"value\": \"bob\",\n          \"ignore_case\": true\n        },\n        {\n          \"entity\": \"rule\",\n          \"id\": \"a3995445-55ca-49b2-8381-3d6758750413\",\n          \"parent_id\": \"7c493486-409b-48df-bd66-7f4a16500c5e\",\n          \"type\": \"string\",\n          \"field\": \"name\",\n          \"operator\": \"equals_to\",\n          \"value\": \"alice\",\n          \"ignore_case\": true\n        }\n      ]\n    }\n  ]\n}\n```\n\n### `addUnionsToUnion(parent: RootUnion | Union, newUnions: NewUnion[]): Union[]`\n\nAdds many unions to an existing union or root union. Returns the list of unions that were added.\n\n\u003e Note: This function mutates the input union. Clone the union before passing it in if you want to maintain its original state.\n\n```js\nimport { addRulesToUnion, addUnionsToUnion, createRoot } from 'rules-engine-ts';\n\nconst root = createRoot({ connector: 'and' });\n\nconst unions = addUnionsToUnion(root, [{ connector: 'or' }, { connector: 'or' }]);\naddRulesToUnion(unions[0], [\n  { type: 'string', field: 'name', value: 'bob', operator: 'equals_to', ignore_case: true },\n  { type: 'string', field: 'name', value: 'alice', operator: 'equals_to', ignore_case: true },\n]);\naddRulesToUnion(unions[1], [\n  { type: 'number', field: 'age', value: 18, operator: 'equals_to' },\n  { type: 'number', field: 'age', value: 21, operator: 'equals_to' },\n]);\n```\n\nState of the Rules Engine:\n\n```json\n{\n  \"entity\": \"root_union\",\n  \"id\": \"28a9ae06-594a-4520-8d73-2fd871804634\",\n  \"connector\": \"and\",\n  \"rules\": [\n    {\n      \"entity\": \"union\",\n      \"id\": \"8e5e66dd-e86c-4f9e-acc7-7baa852fdfe8\",\n      \"parent_id\": \"28a9ae06-594a-4520-8d73-2fd871804634\",\n      \"connector\": \"or\",\n      \"rules\": [\n        {\n          \"entity\": \"rule\",\n          \"id\": \"a8ecbafd-0a1a-4e9e-bb70-8bd11a62f274\",\n          \"parent_id\": \"8e5e66dd-e86c-4f9e-acc7-7baa852fdfe8\",\n          \"type\": \"string\",\n          \"field\": \"name\",\n          \"operator\": \"equals_to\",\n          \"value\": \"bob\",\n          \"ignore_case\": true\n        },\n        {\n          \"entity\": \"rule\",\n          \"id\": \"d4fd56bf-af82-4382-a1cb-93d80cb87ef4\",\n          \"parent_id\": \"8e5e66dd-e86c-4f9e-acc7-7baa852fdfe8\",\n          \"type\": \"string\",\n          \"field\": \"name\",\n          \"operator\": \"equals_to\",\n          \"value\": \"alice\",\n          \"ignore_case\": true\n        }\n      ]\n    },\n    {\n      \"entity\": \"union\",\n      \"id\": \"5e5d7f00-d0f6-40d9-84b3-39600241a92f\",\n      \"parent_id\": \"28a9ae06-594a-4520-8d73-2fd871804634\",\n      \"connector\": \"or\",\n      \"rules\": [\n        {\n          \"entity\": \"rule\",\n          \"id\": \"7ea03690-d9c4-4a3e-97eb-d927cf6845e8\",\n          \"parent_id\": \"5e5d7f00-d0f6-40d9-84b3-39600241a92f\",\n          \"type\": \"number\",\n          \"field\": \"age\",\n          \"operator\": \"equals_to\",\n          \"value\": 18\n        },\n        {\n          \"entity\": \"rule\",\n          \"id\": \"bb63d7da-b0dc-4d00-824c-4de09151c609\",\n          \"parent_id\": \"5e5d7f00-d0f6-40d9-84b3-39600241a92f\",\n          \"type\": \"number\",\n          \"field\": \"age\",\n          \"operator\": \"equals_to\",\n          \"value\": 21\n        }\n      ]\n    }\n  ]\n}\n```\n\n### `addAnyToUnion(parent: RootUnion | Union, newRuleOrUnion: NewRule | NewUnion): Rule | Union`\n\nAdds a rule or a union to an existing union or root union. Returns the rule or union that was added.\n\n\u003e Note: This function mutates the input union. Clone the union before passing it in if you want to maintain its original state.\n\n```js\nimport { addAnyToUnion, createRoot } from 'rules-engine-ts';\n\nconst root = createRoot({ connector: 'and' });\n\nconst any = addAnyToUnion(root, { connector: 'or' });\nif (any.entity === 'union') {\n  addAnyToUnion(any, { type: 'string', field: 'name', value: 'bob', operator: 'equals_to', ignore_case: true });\n  addAnyToUnion(any, { type: 'string', field: 'name', value: 'alice', operator: 'equals_to', ignore_case: true });\n}\n```\n\nState of the Rules Engine:\n\n```json\n{\n  \"entity\": \"root_union\",\n  \"id\": \"825ef3d8-3151-4367-b751-1deae8b308c1\",\n  \"connector\": \"and\",\n  \"rules\": [\n    {\n      \"entity\": \"union\",\n      \"id\": \"71b5296a-5358-4399-878d-f535c9f21faf\",\n      \"parent_id\": \"825ef3d8-3151-4367-b751-1deae8b308c1\",\n      \"connector\": \"or\",\n      \"rules\": [\n        {\n          \"entity\": \"rule\",\n          \"id\": \"3cd0463f-c5e7-4dd9-98b8-9e7cf79417b5\",\n          \"parent_id\": \"71b5296a-5358-4399-878d-f535c9f21faf\",\n          \"type\": \"string\",\n          \"field\": \"name\",\n          \"operator\": \"equals_to\",\n          \"value\": \"bob\",\n          \"ignore_case\": true\n        },\n        {\n          \"entity\": \"rule\",\n          \"id\": \"f10e7cec-c737-4c2c-b137-d7ab0e26e045\",\n          \"parent_id\": \"71b5296a-5358-4399-878d-f535c9f21faf\",\n          \"type\": \"string\",\n          \"field\": \"name\",\n          \"operator\": \"equals_to\",\n          \"value\": \"alice\",\n          \"ignore_case\": true\n        }\n      ]\n    }\n  ]\n}\n```\n\n### `addManyToUnion(parent: RootUnion | Union, newRulesOrUnions: (NewRule | NewUnion)[]): (Rule | Union)[]`\n\nAdds many rules or unions to an existing union or root union. Returns the list of rules or unions that were added.\n\n\u003e Note: This function mutates the input union. Clone the union before passing it in if you want to maintain its original state.\n\n```js\nimport { addManyToUnion, createRoot } from 'rules-engine-ts';\n\nconst root = createRoot({ connector: 'and' });\n\naddManyToUnion(root, [\n  { type: 'string', field: 'name', value: 'bob', operator: 'equals_to', ignore_case: true },\n  { type: 'string', field: 'name', value: 'alice', operator: 'equals_to', ignore_case: true },\n  { connector: 'or' },\n]);\n```\n\nState of the Rules Engine:\n\n```json\n{\n  \"entity\": \"root_union\",\n  \"id\": \"26252c95-37da-47d4-b361-7ad82ae13a9b\",\n  \"connector\": \"and\",\n  \"rules\": [\n    {\n      \"entity\": \"rule\",\n      \"id\": \"edbf5239-e931-480a-b231-119af2c1a1d1\",\n      \"parent_id\": \"26252c95-37da-47d4-b361-7ad82ae13a9b\",\n      \"type\": \"string\",\n      \"field\": \"name\",\n      \"operator\": \"equals_to\",\n      \"value\": \"bob\",\n      \"ignore_case\": true\n    },\n    {\n      \"entity\": \"rule\",\n      \"id\": \"1e9109fa-30cf-41a9-9e78-e8395a423d6d\",\n      \"parent_id\": \"26252c95-37da-47d4-b361-7ad82ae13a9b\",\n      \"type\": \"string\",\n      \"field\": \"name\",\n      \"operator\": \"equals_to\",\n      \"value\": \"alice\",\n      \"ignore_case\": true\n    },\n    {\n      \"entity\": \"union\",\n      \"id\": \"0bb57d03-ac07-41af-941a-6a2625bac130\",\n      \"parent_id\": \"26252c95-37da-47d4-b361-7ad82ae13a9b\",\n      \"connector\": \"or\",\n      \"rules\": []\n    }\n  ]\n}\n```\n\n### `run(union: RootUnion | Union, value: any): boolean`\n\nEvaluates 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.\n\n```js\nimport { addRuleToUnion, addRulesToUnion, addUnionToUnion, createRoot, run } from 'rules-engine-ts';\n\nconst root = createRoot({ connector: 'and' });\naddRuleToUnion(root, { type: 'number', field: 'age', operator: 'greater_than', value: 18 });\n\nconst union = addUnionToUnion(root, { connector: 'or' });\naddRulesToUnion(union, [\n  { type: 'string', field: 'name', value: 'bob', operator: 'equals_to', ignore_case: true },\n  { type: 'string', field: 'name', value: 'alice', operator: 'equals_to', ignore_case: true },\n]);\n\nconst pass = run(root, { age: 19, name: 'Bob' });\nconst fail = run(root, { age: 19, name: 'Carol' });\n\nconsole.log(pass); // true\nconsole.log(fail); // false\n```\n\n### `findAnyById(union: RootUnion | Union, id: string): RootUnion | Union | Rule | undefined`\n\nFinds any rule or union by id. Returns the rule or union if found, otherwise returns undefined.\n\n```js\nimport { addRuleToUnion, addUnionToUnion, createRoot, findAnyById } from 'rules-engine-ts';\n\nconst root = createRoot({ connector: 'and' });\nconst rule = addRuleToUnion(root, { type: 'number', field: 'age', operator: 'greater_than', value: 18 });\nconst union = addUnionToUnion(root, { connector: 'or' });\n\nconst foundRule = findAnyById(root, rule.id);\nconsole.log(foundRule === rule); // true\n\nconst foundUnion = findAnyById(root, union.id);\nconsole.log(foundUnion === union); // true\n```\n\n### `findRuleById(union: RootUnion | Union, id: string): Rule | undefined`\n\nFinds a rule by id. Returns the rule if found, otherwise returns undefined.\n\n```js\nimport { addRuleToUnion, addUnionToUnion, createRoot, findRuleById } from 'rules-engine-ts';\n\nconst root = createRoot({ connector: 'and' });\nconst rule = addRuleToUnion(root, { type: 'number', field: 'age', operator: 'greater_than', value: 18 });\nconst union = addUnionToUnion(root, { connector: 'or' });\n\nconst foundRule = findRuleById(root, rule.id);\nconsole.log(foundRule === rule); // true\n\nconst foundUnion = findRuleById(root, union.id);\nconsole.log(foundUnion); // undefined\n```\n\n### `findUnionById(union: RootUnion | Union, id: string): RootUnion | Union | undefined`\n\nFinds a union by id. Returns the union if found, otherwise returns undefined.\n\n```js\nimport { addRuleToUnion, addUnionToUnion, createRoot, findUnionById } from 'rules-engine-ts';\n\nconst root = createRoot({ connector: 'and' });\nconst rule = addRuleToUnion(root, { type: 'number', field: 'age', operator: 'greater_than', value: 18 });\nconst union = addUnionToUnion(root, { connector: 'or' });\n\nconst foundUnion = findUnionById(root, union.id);\nconsole.log(foundUnion === union); // true;\n\nconst foundRule = findUnionById(root, rule.id);\nconsole.log(foundRule); // undefined;\n```\n\n### `validate(root: RootUnion): { isValid: true } | { isValid: false; reason: string }`\n\nValidates 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.\n\n```js\nimport { addRuleToUnion, createRoot, validate } from 'rules-engine-ts';\n\nconst root = createRoot({ connector: 'and' });\nconst rule = addRuleToUnion(root, { type: 'number', field: 'age', operator: 'greater_than', value: 18 });\n\nconsole.log(validate(root));\n// { isValid: true }\n\nrule.type = 'string';\n\nconsole.log(validate(root));\n// {\n//   isValid: false,\n//   reason: 'Code: invalid_union ~ Path: rules[0] ~ Message: Invalid input'\n// }\n```\n\n### `normalize\u003cT extends Union | RootUnion\u003e(union: T, options?: Options): T`\n\nNormalization is a process that ensures that the ruleset is in a consistent state. It performs the following updates recursively in the following order:\n\n- Removes any rules or unions that do not conform to the type system. `options.remove_failed_validations`\n- Removes any unions without any rules. `options.remove_empty_unions`\n- Converts any union with a single rule to a rule. `options.promote_single_rule_unions`\n- Updates all parent ids to match the parent union `options.update_parent_ids`\n\nAll 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.\n\n\u003e Note: This function mutates the input union. Clone the union before passing it in if you want to maintain its original state.\n\n```ts\nimport { addRuleToUnion, addUnionToUnion, createRoot, normalize } from 'rules-engine-ts';\n\nimport { v4 as uuidv4 } from 'uuid';\n\nconst root = createRoot({ connector: 'or' });\n\nconst rule1 = addRuleToUnion(root, { field: 'name', operator: 'contains', type: 'string', value: 'bob' });\nconst union = addUnionToUnion(root, { connector: 'and' });\nconst rule2 = addRuleToUnion(union, { field: 'name', operator: 'contains', type: 'string', value: 'alice' });\n\nrule1.parent_id = uuidv4();\nrule2.type = 'number';\n// @ts-expect-error\nunion.connector = 'invalid';\n\nconsole.log(root); // Before normalization\nnormalize(root, {\n  // Normalization options (optional)\n  promote_single_rule_unions: true,\n  remove_empty_unions: true,\n  remove_failed_validations: true,\n  update_parent_ids: true,\n});\nconsole.log(root); // After normalization\n```\n\nBefore normalization:\n\n```json\n{\n  \"entity\": \"root_union\",\n  \"id\": \"70cf2539-b960-4831-b0f2-3b201aea550a\",\n  \"connector\": \"or\",\n  \"rules\": [\n    {\n      \"entity\": \"rule\",\n      \"id\": \"4b644371-6bc2-46b1-b855-c2098df80fb3\",\n      \"parent_id\": \"8ca677c2-b01c-4cf2-91ec-9c95b6ff7dff\",\n      \"type\": \"string\",\n      \"field\": \"name\",\n      \"operator\": \"contains\",\n      \"value\": \"bob\"\n    },\n    {\n      \"entity\": \"union\",\n      \"id\": \"c43e8705-6b4b-42b5-941c-3295c17cf5db\",\n      \"parent_id\": \"70cf2539-b960-4831-b0f2-3b201aea550a\",\n      \"connector\": \"invalid\",\n      \"rules\": [\n        {\n          \"entity\": \"rule\",\n          \"id\": \"8589e28c-a1d5-4a0b-b930-24c5931eaadb\",\n          \"parent_id\": \"c43e8705-6b4b-42b5-941c-3295c17cf5db\",\n          \"type\": \"number\",\n          \"field\": \"name\",\n          \"operator\": \"contains\",\n          \"value\": \"alice\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nAfter normalization:\n\n```json\n{\n  \"entity\": \"root_union\",\n  \"id\": \"70cf2539-b960-4831-b0f2-3b201aea550a\",\n  \"connector\": \"or\",\n  \"rules\": [\n    {\n      \"entity\": \"rule\",\n      \"id\": \"4b644371-6bc2-46b1-b855-c2098df80fb3\",\n      \"parent_id\": \"70cf2539-b960-4831-b0f2-3b201aea550a\",\n      \"type\": \"string\",\n      \"field\": \"name\",\n      \"operator\": \"contains\",\n      \"value\": \"bob\"\n    }\n  ]\n}\n```\n\n### `updateRuleById(root: RootUnion, id: string, values: NewRule): Rule | undefined`\n\nUpdates a rule by id. Returns the updated rule if found, otherwise returns undefined.\n\n\u003e Note: This function mutates the input root union. Clone the union before passing it in if you want to maintain its original state.\n\n```js\nimport { addRuleToUnion, createRoot, updateRuleById } from 'rules-engine-ts';\n\nconst root = createRoot({ connector: 'and' });\nconst rule = addRuleToUnion(root, { type: 'number', field: 'age', operator: 'greater_than', value: 18 });\n\nconsole.log(root.rules[0]); // Before update\nupdateRuleById(root, rule.id, { type: 'number', field: 'age', operator: 'less_than', value: 30 });\nconsole.log(root.rules[0]); // After update\n```\n\nBefore update:\n\n```json\n{\n  \"entity\": \"rule\",\n  \"id\": \"cc3d4bab-783a-4683-a223-8dee979b0bf0\",\n  \"parent_id\": \"e0da0708-1fbf-4e64-887c-d7684b17dd00\",\n  \"type\": \"number\",\n  \"field\": \"age\",\n  \"operator\": \"greater_than\",\n  \"value\": 18\n}\n```\n\nAfter update:\n\n```json\n{\n  \"entity\": \"rule\",\n  \"id\": \"cc3d4bab-783a-4683-a223-8dee979b0bf0\",\n  \"parent_id\": \"e0da0708-1fbf-4e64-887c-d7684b17dd00\",\n  \"type\": \"number\",\n  \"field\": \"age\",\n  \"operator\": \"less_than\",\n  \"value\": 30\n}\n```\n\n### `updateUnionById(root: RootUnion, id: string, values: NewUnion): Union | RootUnion | undefined`\n\nUpdates a union by id. Returns the updated union if found, otherwise returns undefined.\n\n\u003e Note: This function mutates the input root union. Clone the union before passing it in if you want to maintain its original state.\n\n```js\nimport { addUnionToUnion, createRoot, updateUnionById } from 'rules-engine-ts';\n\nconst root = createRoot({ connector: 'and' });\nconst union = addUnionToUnion(root, { connector: 'and' });\n\nconsole.log(root.rules[0]); // Before update\nupdateUnionById(root, union.id, { connector: 'or' });\nconsole.log(root.rules[0]); // After update\n```\n\nBefore update:\n\n```json\n{\n  \"entity\": \"union\",\n  \"id\": \"b0a289a5-f02e-4bb4-bbbf-d148d1fc570f\",\n  \"parent_id\": \"1ac8dad7-46c0-430b-9ad1-fdb8f1fd721a\",\n  \"connector\": \"and\",\n  \"rules\": []\n}\n```\n\nAfter update:\n\n```json\n{\n  \"entity\": \"union\",\n  \"id\": \"b0a289a5-f02e-4bb4-bbbf-d148d1fc570f\",\n  \"parent_id\": \"1ac8dad7-46c0-430b-9ad1-fdb8f1fd721a\",\n  \"connector\": \"or\",\n  \"rules\": []\n}\n```\n\n### `removeAllById\u003cT extends RootUnion | Union\u003e(union: T, id: string): T`\n\nRemoves all rules and unions of a given id from a ruleset. Returns the updated ruleset.\n\n\u003e Note: This function mutates the input union. Clone the union before passing it in if you want to maintain its original state.\n\n```js\nimport { addRuleToUnion, addUnionToUnion, createRoot, removeAllById } from 'rules-engine-ts';\n\nconst root = createRoot({ connector: 'and' });\nconst union = addUnionToUnion(root, { connector: 'or' });\nconst rule = addRuleToUnion(union, { type: 'number', field: 'age', operator: 'greater_than', value: 18 });\n\nconsole.log(union.rules.length); // 1\nremoveAllById(root, rule.id);\nconsole.log(union.rules.length); // 0\n```\n\n## Rules Specification\n\nThe 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.\n\n### type = 'string'\n\n| Property    | Value                                                                                                                 | Description                                                                  |\n| ----------- | --------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |\n| type        | 'string'                                                                                                              | The type of the value to be evaluated.                                       |\n| field       | string                                                                                                                | The field to check. Supports nested properties, e.g. `users.admins[0].name`. |\n| operator    | 'equals_to' \u003cbr/\u003e 'does_not_equal_to' \u003cbr/\u003e 'contains' \u003cbr/\u003e 'does_not_contain' \u003cbr/\u003e 'starts_with' \u003cbr/\u003e 'ends_with' | The operator to use.                                                         |\n| value       | string                                                                                                                | The value to compare against.                                                |\n| ignore_case | boolean                                                                                                               | Whether to ignore case when comparing strings.                               |\n\n### type = 'number'\n\n| Property | Value                                                                                                                                       | Description                                                                 |\n| -------- | ------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |\n| type     | 'number'                                                                                                                                    | The type of the value to be evaluated.                                      |\n| field    | string                                                                                                                                      | The field to check. Supports nested properties, e.g. `users.admins[0].age`. |\n| operator | 'equals_to' \u003cbr/\u003e 'does_not_equal_to' \u003cbr/\u003e 'greater_than' \u003cbr/\u003e 'greater_than_or_equal_to' \u003cbr/\u003e 'less_than' \u003cbr/\u003e 'less_than_or_equal_to' | The operator to use.                                                        |\n| value    | number                                                                                                                                      | The value to compare against.                                               |\n\n### type = 'boolean'\n\n| Property | Value                      | Description                                                                       |\n| -------- | -------------------------- | --------------------------------------------------------------------------------- |\n| type     | 'boolean'                  | The type of the value to be evaluated.                                            |\n| field    | string                     | The field to check. Supports nested properties, e.g. `users.admins[0].is_active`. |\n| operator | 'is_true' \u003cbr/\u003e 'is_false' | The operator to use.                                                              |\n\n### type = 'array_value'\n\n| Property | Value                                                    | Description                                                          |\n| -------- | -------------------------------------------------------- | -------------------------------------------------------------------- |\n| type     | 'array_value'                                            | The type of the value to be evaluated.                               |\n| field    | string                                                   | The field to check. Supports nested properties, e.g. `users.admins`. |\n| operator | 'contains' \u003cbr/\u003e 'does_not_contain' \u003cbr/\u003e 'contains_all' | The operator to use.                                                 |\n| value    | any                                                      | The value to compare against.                                        |\n\n### type = 'array_length'\n\n| Property | Value                                                                                                                                       | Description                                                          |\n| -------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |\n| type     | 'array_length'                                                                                                                              | The type of the value to be evaluated.                               |\n| field    | string                                                                                                                                      | The field to check. Supports nested properties, e.g. `users.admins`. |\n| operator | 'equals_to' \u003cbr/\u003e 'does_not_equal_to' \u003cbr/\u003e 'greater_than' \u003cbr/\u003e 'greater_than_or_equal_to' \u003cbr/\u003e 'less_than' \u003cbr/\u003e 'less_than_or_equal_to' | The operator to use.                                                 |\n| value    | number                                                                                                                                      | The value to compare against.                                        |\n\n### type = 'object_key'\n\n| Property | Value                               | Description                                                             |\n| -------- | ----------------------------------- | ----------------------------------------------------------------------- |\n| type     | 'object_key'                        | The type of the value to be evaluated.                                  |\n| field    | string                              | The field to check. Supports nested properties, e.g. `users.admins[0]`. |\n| operator | 'contains' \u003cbr/\u003e 'does_not_contain' | The operator to use.                                                    |\n| value    | string                              | The value to compare against.                                           |\n\n### type = 'object_value'\n\n| Property | Value                               | Description                                                          |\n| -------- | ----------------------------------- | -------------------------------------------------------------------- |\n| type     | 'object_value'                      | The type of the value to be evaluated.                               |\n| field    | string                              | The field to check. Supports nested properties, e.g. `users.admins`. |\n| operator | 'contains' \u003cbr/\u003e 'does_not_contain' | The operator to use.                                                 |\n| value    | any                                 | The value to compare against.                                        |\n\n### type = 'object_key_value'\n\n| Property | Value                               | Description                                                          |\n| -------- | ----------------------------------- | -------------------------------------------------------------------- |\n| type     | 'object_key_value'                  | The type of the value to be evaluated.                               |\n| field    | string                              | The field to check. Supports nested properties, e.g. `users.admins`. |\n| operator | 'contains' \u003cbr/\u003e 'does_not_contain' | The operator to use.                                                 |\n| value    | { key: 'string', value: 'any' }     | The value to compare against.                                        |\n\n### type = 'generic_comparison'\n\n| Property | Value                                                                                                                                       | Description                                                                              |\n| -------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |\n| type     | 'generic_comparison'                                                                                                                        | The type of the value to be evaluated.                                                   |\n| field    | string                                                                                                                                      | The field to check. Supports nested properties, e.g. `users.admins[0].unknown_property`. |\n| operator | 'equals_to' \u003cbr/\u003e 'does_not_equal_to' \u003cbr/\u003e 'greater_than' \u003cbr/\u003e 'greater_than_or_equal_to' \u003cbr/\u003e 'less_than' \u003cbr/\u003e 'less_than_or_equal_to' | The operator to use.                                                                     |\n\n### type = 'generic_type'\n\n| Property | Value                                                                                                                                                                                                                                                                                                                   | Description                                                                              |\n| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |\n| type     | 'generic_type'                                                                                                                                                                                                                                                                                                          | The type of the value to be evaluated.                                                   |\n| field    | string                                                                                                                                                                                                                                                                                                                  | The field to check. Supports nested properties, e.g. `users.admins[0].unknown_property`. |\n| operator | 'is_truthy' \u003cbr/\u003e 'is_falsey' \u003cbr/\u003e 'is_null' \u003cbr/\u003e 'is_not_null' \u003cbr/\u003e 'is_undefined' \u003cbr/\u003e 'is_not_undefined' \u003cbr/\u003e 'is_string' \u003cbr/\u003e 'is_not_string' \u003cbr/\u003e 'is_number' \u003cbr/\u003e 'is_not_number' \u003cbr/\u003e 'is_boolean' \u003cbr/\u003e 'is_not_boolean' \u003cbr/\u003e 'is_array' \u003cbr/\u003e 'is_not_array' \u003cbr/\u003e 'is_object' \u003cbr/\u003e 'is_not_object' | The operator to use.                                                                     |\n| value    | any                                                                                                                                                                                                                                                                                                                     | The value to compare against.                                                            |\n\n## TypeScript Usage\n\nRules can be pre-composed using type anotations before adding it to the rules engine:\n\n```ts\nimport { NewNumberRule, NewRule, addRuleToUnion, createRoot } from 'rules-engine-ts';\n\nconst root = createRoot({ connector: 'and' });\n\nconst wideTypingRule: NewRule = { type: 'number', field: 'age', operator: 'greater_than', value: 18 };\nconst narrowTypingRule: NewNumberRule = { type: 'number', field: 'age', operator: 'less_than', value: 30 };\n\nconst wideAfterAdding = addRuleToUnion(root, wideTypingRule);\nconst narrowAfterAdding = addRuleToUnion(root, narrowTypingRule);\n\nconsole.log(wideAfterAdding);\nconsole.log(narrowAfterAdding);\n```\n\n```json\n{\n  \"entity\": \"rule\",\n  \"id\": \"560f4e04-f786-4269-bbdd-704ad9793518\",\n  \"parent_id\": \"6fa1aaa6-cfab-4647-a30c-a58af3e0a4d4\",\n  \"type\": \"number\",\n  \"field\": \"age\",\n  \"operator\": \"greater_than\",\n  \"value\": 18\n}\n```\n\n```json\n{\n  \"entity\": \"rule\",\n  \"id\": \"46a36441-3f28-4dd7-8420-b1d584527a74\",\n  \"parent_id\": \"6fa1aaa6-cfab-4647-a30c-a58af3e0a4d4\",\n  \"type\": \"number\",\n  \"field\": \"age\",\n  \"operator\": \"less_than\",\n  \"value\": 30\n}\n```\n\nSimilarly, a union can also be pre-composed before adding it to the rules engine:\n\n```ts\nimport { NewUnion, addUnionToUnion, createRoot } from 'rules-engine-ts';\n\nconst userSelectsAnd = false;\n\nconst root = createRoot({ connector: 'and' });\nconst union: NewUnion = { connector: userSelectsAnd ? 'and' : 'or' };\n\nconst unionAfterAdding = addUnionToUnion(root, union);\nconsole.log(unionAfterAdding);\n```\n\n```json\n{\n  \"entity\": \"union\",\n  \"id\": \"d2ce2a4e-ec53-4a64-9677-e9051c634bd1\",\n  \"parent_id\": \"8b32fdc4-8e92-424f-9c00-1204838759e0\",\n  \"connector\": \"or\",\n  \"rules\": []\n}\n```\n\n## To Do\n\n- [ ] Create recipe examples\n- [ ] Create function to detect conflicting or redundant rules\n- [ ] Create a UI builder tool\n\n## Authors\n\n- [@andrewvo89](https://github.com/andrewvo89) - Idea \u0026 Initial work.\n\nSee also the list of [contributors](https://github.com/andrewvo89/rules-engine-ts/contributors) who participated in this project.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandrewvo89%2Frules-engine-ts","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandrewvo89%2Frules-engine-ts","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandrewvo89%2Frules-engine-ts/lists"}