{"id":31733505,"url":"https://github.com/frankthelen/rools","last_synced_at":"2025-10-09T08:27:11.483Z","repository":{"id":43612462,"uuid":"115756763","full_name":"frankthelen/rools","owner":"frankthelen","description":"A small rule engine for Node.","archived":false,"fork":false,"pushed_at":"2023-05-31T23:22:50.000Z","size":862,"stargazers_count":145,"open_issues_count":8,"forks_count":20,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-06-29T12:47:27.654Z","etag":null,"topics":["rule-engine","rules","rules-engine"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/frankthelen.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-12-29T21:58:57.000Z","updated_at":"2025-05-12T22:29:36.000Z","dependencies_parsed_at":"2024-06-18T18:12:57.162Z","dependency_job_id":null,"html_url":"https://github.com/frankthelen/rools","commit_stats":{"total_commits":201,"total_committers":5,"mean_commits":40.2,"dds":0.02985074626865669,"last_synced_commit":"a9921602e089241b6db224903eff834bf43b02bb"},"previous_names":[],"tags_count":48,"template":false,"template_full_name":null,"purl":"pkg:github/frankthelen/rools","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/frankthelen%2Frools","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/frankthelen%2Frools/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/frankthelen%2Frools/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/frankthelen%2Frools/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/frankthelen","download_url":"https://codeload.github.com/frankthelen/rools/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/frankthelen%2Frools/sbom","scorecard":{"id":409697,"data":{"date":"2025-08-11","repo":{"name":"github.com/frankthelen/rools","commit":"a9921602e089241b6db224903eff834bf43b02bb"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.7,"checks":[{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":0,"reason":"Found 1/22 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/main.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":4,"reason":"dependency not pinned by hash detected -- score normalized to 4","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/main.yml:19: update your workflow using https://app.stepsecurity.io/secureworkflow/frankthelen/rools/main.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/main.yml:21: update your workflow using https://app.stepsecurity.io/secureworkflow/frankthelen/rools/main.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/main.yml:29: update your workflow using https://app.stepsecurity.io/secureworkflow/frankthelen/rools/main.yml/master?enable=pin","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 third-party GitHubAction dependencies pinned","Info:   1 out of   1 npmCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"License","score":0,"reason":"license file not detected","details":["Warn: project does not have a license file"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 11 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"21 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-4q6p-r6v2-jvc5","Warn: Project is vulnerable to: GHSA-896r-f27r-55mw","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-qrpm-p2h7-hrv2","Warn: Project is vulnerable to: GHSA-mwcw-c2x4-8c55","Warn: Project is vulnerable to: GHSA-9wv6-86v2-598j","Warn: Project is vulnerable to: GHSA-hrpp-h998-j3pp","Warn: Project is vulnerable to: GHSA-p8p7-x288-28g6","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-76p7-773f-r4q5","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3","Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-18T22:22:38.459Z","repository_id":43612462,"created_at":"2025-08-18T22:22:38.459Z","updated_at":"2025-08-18T22:22:38.459Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279001074,"owners_count":26082991,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-09T02:00:07.460Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["rule-engine","rules","rules-engine"],"created_at":"2025-10-09T08:27:10.382Z","updated_at":"2025-10-09T08:27:11.477Z","avatar_url":"https://github.com/frankthelen.png","language":"JavaScript","readme":"# rools\n\nA small rule engine for Node.\n\n![main workflow](https://github.com/frankthelen/rools/actions/workflows/main.yml/badge.svg)\n[![Coverage Status](https://coveralls.io/repos/github/frankthelen/rools/badge.svg?branch=master)](https://coveralls.io/github/frankthelen/rools?branch=master)\n[![dependencies Status](https://david-dm.org/frankthelen/rools/status.svg)](https://david-dm.org/frankthelen/rools)\n[![Maintainability](https://api.codeclimate.com/v1/badges/d1f858c321b03000fc63/maintainability)](https://codeclimate.com/github/frankthelen/rools/maintainability)\n[![node](https://img.shields.io/node/v/rools.svg)](https://nodejs.org)\n[![code style](https://img.shields.io/badge/code_style-airbnb-brightgreen.svg)](https://github.com/airbnb/javascript)\n[![Types](https://img.shields.io/npm/types/rools.svg)](https://www.npmjs.com/package/rools)\n[![License Status](http://img.shields.io/npm/l/rools.svg)]()\n\n*Primary goal* was to provide a nice and state-of-the-art interface for modern JavaScript (ES6).\n*Facts* are plain JavaScript or JSON objects or objects from ES6 classes with getters and setters.\n*Rules* are specified in pure JavaScript rather than in a separate, special-purpose language like DSL.\n\n*Secondary goal* was to provide [RETE](https://en.wikipedia.org/wiki/Rete_algorithm)-like efficiency and optimization.\n\nMission accomplished! JavaScript rocks!\n\nSee [migration info](#migration) for breaking changes between major versions 1.x.x and 2.x.x.\n\n## Install\n\n```bash\nnpm install rools\n```\n\n## Usage\n\nThis is a basic example.\n\n```javascript\n// import\nconst { Rools, Rule } = require('rools');\n\n// facts\nconst facts = {\n  user: {\n    name: 'frank',\n    stars: 347,\n  },\n  weather: {\n    temperature: 20,\n    windy: true,\n    rainy: false,\n  },\n};\n\n// rules\nconst ruleMoodGreat = new Rule({\n  name: 'mood is great if 200 stars or more',\n  when: (facts) =\u003e facts.user.stars \u003e= 200,\n  then: (facts) =\u003e {\n    facts.user.mood = 'great';\n  },\n});\nconst ruleGoWalking = new Rule({\n  name: 'go for a walk if mood is great and the weather is fine',\n  when: [\n    (facts) =\u003e facts.user.mood === 'great',\n    (facts) =\u003e facts.weather.temperature \u003e= 20,\n    (facts) =\u003e !facts.weather.rainy,\n  ],\n  then: (facts) =\u003e {\n    facts.goWalking = true;\n  },\n});\n\n// evaluation\nconst rools = new Rools();\nawait rools.register([ruleMoodGreat, ruleGoWalking]);\nawait rools.evaluate(facts);\n```\n\nThese are the resulting facts:\n\n```javascript\n{ user: { name: 'frank', stars: 347, mood: 'great' },\n  weather: { temperature: 20, windy: true, rainy: false },\n  goWalking: true,\n}\n```\n\n## Features\n\n### Rule engine\n\nThe engine does forward-chaining and works in the usual match-resolve-act cycle.\nIt tries to deduce as much knowledge as possible from the given facts and rules.\nIf there is no further knowledge to gain, it stops.\n\n### Facts and rules\n\nFacts are plain JavaScript or JSON objects or objects from ES6 classes with getters and setters.\n\nRules are specified in pure JavaScript via `new Rule()`.\nThey have premises (`when`) and actions (`then`).\nBoth are JavaScript functions, i.e., classic functions or ES6 arrow functions.\nActions can also be asynchronous.\n\nRules access the facts in both, premises (`when`) and actions (`then`).\nThey can access properties directly, e.g., `facts.user.salary`,\nor through getters and setters if applicable, e.g., `facts.user.getSalary()`.\n\n### Conflict resolution\n\nIf there is more than one rule ready to fire, i.e., the conflict set is greater 1, the following conflict resolution strategies are applied (by default, in this order):\n\n* Refraction -- Each rule will fire only once, at most, during any one match-resolve-act cycle.\n* Priority -- Rules with higher priority will fire first. Set the rule's property `priority` to an integer value. Default priority is `0`. Negative values are supported.\n* Specificity -- Rules which are more specific will fire first. For example, there is rule R1 with premises P1 and P2, and rule R2 with premises P1, P2 and P3. R2 is more specific than R1 and will fire first. R2 is more specific than R1 because it has *all* premises of R1 and additional ones.\n* Order of rules -- The rules that were registered first will fire first.\n\n### Final rules\n\nFor optimization purposes, it can be useful to stop the engine as soon as a specific rule has fired.\nThis can be achieved by settings the respective rules' property `final` to `true`.\nDefault, of course, is `false`.\n\n### Async actions\n\nWhile premises (`when`) are always working synchronously on the facts,\nactions (`then`) can be synchronous or asynchronous.\n\nExample: asynchronous action using async/await\n\n```javascript\nconst rule = new Rule({\n  name: 'check availability',\n  when: (facts) =\u003e facts.user.address.country === 'germany',\n  then: async (facts) =\u003e {\n    facts.products = await availabilityCheck(facts.user.address);\n  },\n});\n```\n\nExample: asynchronous action using promises\n\n```javascript\nconst rule = new Rule({\n  name: 'check availability',\n  when: (facts) =\u003e facts.user.address.country === 'germany',\n  then: (facts) =\u003e\n    availabilityCheck(facts.user.address)\n      .then((result) =\u003e {\n        facts.products = result;\n      }),\n});\n```\n\n### Extended rules\n\nIf a *rule is more specific* than another rule, you can *extend* it rather than having to repeat its premises.\nThe extended rule simply inherits all the premises from its parents (and their parents).\nUse the rule's `extend` property to set its parents.\n\nExample: extended rule\n\n```javascript\nconst baseRule = new Rule({\n  name: 'user lives in Germany',\n  when: (facts) =\u003e facts.user.address.country === 'germany',\n  ...\n});\nconst extendedRule = new Rule({\n  name: 'user lives in Hamburg, Germany',\n  extend: baseRule, // can also be an array of rules\n  when: (facts) =\u003e facts.user.address.city === 'hamburg',\n  ...\n});\n```\n\n### Activation groups\n\nOnly one rule within an activation group will fire during a match-resolve-act cycle, i.e.,\nthe first one to fire discards all other rules within the same activation group.\nUse the rule's `activationGroup` property to set its activation group.\n\n### Rule groups\n\nBesides activation groups, Rools has currently *no other concept of grouping rules* such as agenda groups or rule flow groups which you might know from other rule engines. And there are currently no plans to support such features.\n\nHowever, if that solves your needs, you can consecutively run different sets of rules against the same facts.\nRules in different instances of Rools are perfectly isolated and can, of course, run against the same facts.\n\nExample: evaluate different sets of rules on the same facts\n\n```javascript\nconst facts = {...};\nconst rools1 = new Rools();\nconst rools2 = new Rools();\nawait rools1.register(...); // rule set 1\nawait rools2.register(...); // rule set 2\nawait rools1.evaluate(facts);\nawait rools2.evaluate(facts);\n```\n\n### Optimization I\n\nIt is very common that different rules partially share the same premises.\nRools will automatically merge identical premises into one.\nYou are free to use references or just to repeat the same premise.\nBoth options are working fine.\n\nExample 1: by reference\n\n```javascript\nconst isApplicable = (facts) =\u003e facts.user.salary \u003e= 2000;\nconst rule1 = new Rule({\n  when: [\n    isApplicable,\n    ...\n  ],\n  ...\n});\nconst rule2 = new Rule({\n  when: [\n    isApplicable,\n    ...\n  ],\n  ...\n});\n```\n\nExample 2: repeat premise\n\n```javascript\nconst rule1 = new Rule({\n  when: [\n    (facts) =\u003e facts.user.salary \u003e= 2000,\n    ...\n  ],\n  ...\n});\nconst rule2 = new Rule({\n  when: [\n    (facts) =\u003e facts.user.salary \u003e= 2000,\n    ...\n  ],\n  ...\n});\n```\n\nFurthermore, it is recommended to de-compose premises with AND relations (`\u0026\u0026`).\nFor example:\n\n```javascript\n// this version works...\nconst rule = new Rule({\n  when: (facts) =\u003e facts.user.salary \u003e= 2000 \u0026\u0026 facts.user.age \u003e 25,\n  ...\n});\n// however, it's better to write it like this...\nconst rule = new Rule({\n  when: [\n    (facts) =\u003e facts.user.salary \u003e= 2000,\n    (facts) =\u003e facts.user.age \u003e 25,\n  ],\n  ...\n});\n```\n\n### Optimization II\n\nWhen actions fire, changes are made to the facts.\nThis requires re-evaluation of the premises.\nWhich may lead to further actions becoming ready to fire.\n\nTo avoid complete re-evaluation of all premises each time changes are made to the facts, Rools detects the parts of the facts (segments) that were actually changed and re-evaluates only those premises affected.\n\nChange detection is based on *level 1 of the facts*. In the example below, detected changes are based on `user`, `weather`, `posts` and so on. So, whenever a `user` detail changes, all premises and actions that rely on `user` are re-evaluated. But only those.\n\n```javascript\nconst facts = {\n  user: { ... },\n  weather: { ... },\n  posts: { ... },\n  ...\n};\n...\nawait rools.evaluate(facts);\n```\n\nThis optimization targets runtime performance.\nIt unfolds its full potential with a growing number of rules and fact segments.\n\n## Dos and don'ts\n\n### Be careful with non-local variables in premises\n\nIdeally, premises (`when`) are \"pure functions\" referring to `facts` only.\nThey should not refer to any other non-local variables.\n\nIf they do so, however, please note that non-local variables are resolved at\nevaluation time (`evaluate()`) and *not* at registration time (`register()`).\n\nFurthermore, please make sure that non-local variables are constant/stable\nduring evaluation. Otherwise, premises are not working deterministically.\n\nIn the example below, Rools will treat the two premises as identical\nassuming that both rules are referring to the exact same `value`.\n\n```javascript\nlet value = 2000;\nconst rule1 = new Rule({\n  when: (facts) =\u003e facts.user.salary \u003e= value,\n  ...\n});\nvalue = 3000;\nconst rule2 = new Rule({\n  when: (facts) =\u003e facts.user.salary \u003e= value,\n  ...\n});\n```\n\n### Don't \"generate\" rules / Don't create rules in closures\n\nThe example below does not work!\nRools would treat all premises (`when`) as identical\nassuming that all rules are referring to the exact same `value`.\n\n```javascript\nconst createRule = (value) =\u003e {\n  rules.push(new Rule({\n    name: `Rule evaluating ${value}`,\n    when: (facts) =\u003e facts.foo \u003e= value,\n    then: (facts) =\u003e // ...\n  }));\n};\n```\n\n### Don't mix premises and actions\n\nMake sure not to mix premises and actions.\nIn the example below, the if condition should be in `when`, *not* in `then`.\nIf you have such cases, think about splitting rules or extending rules.\n\n```javascript\nconst rule = new Rule({\n  name: \"rule\",\n  when: // ...\n  then: (facts) =\u003e {\n    if (facts.foo \u003c 0) { // not good here!\n      // ...\n    }\n  },\n});\n```\n\n## Interface\n\n### Create rule engine: `new Rools()`\n\nCalling `new Rools()` creates a new Rools instance, i.e., a new rule engine.\nYou usually do this once for a given set of rules.\n\nExample:\n\n```javascript\nconst { Rools } = require('rools');\nconst rools = new Rools();\n...\n```\n\n### Register rules: `register()`\n\nRules are created through `new Rule()` with the following properties:\n\n| Property    | Required | Default | Description |\n|-------------|----------|---------|-------------|\n| `name`      | yes      | -       | A string value identifying the rule. This is used for logging and debugging purposes only. |\n| `when`      | yes      | -       | A synchronous JavaScript function or an array of functions. These are the premises of your rule. The functions' interface is `(facts) =\u003e { ... }`. They must return a boolean value. |\n| `then`      | yes      | -       | A synchronous or asynchronous JavaScript function to be executed when the rule fires. The function's interface is `(facts) =\u003e { ... }` or `async (facts) =\u003e { ... }`. |\n| `priority`  | no       | `0`     | If during `evaluate()` there is more than one rule ready to fire, i.e., the conflict set is greater 1, rules with higher priority will fire first. Negative values are supported. |\n| `final`     | no       | `false` | Marks a rule as final. If during `evaluate()` a final rule fires, the engine will stop the evaluation. |\n| `extend`    | no       | []      | A reference to a rule or an array of rules. The new rule will inherit all premises from its parents (and their parents). |\n| `activationGroup` | no | -       | A string identifying an activation group. Only one rule within an activation group will fire. |\n\nRules access the facts in both, premises (`when`) and actions (`then`).\nThey can access properties directly, e.g., `facts.user.salary`,\nor through getters and setters if applicable, e.g., `facts.user.getSalary()`.\n\n`register()` registers one or more rules to the rule engine.\nIt can be called multiple time.\nNew rules will become effective immediately.\n\n`register()` is working asynchronously, i.e., it returns a promise.\nIf this promise is rejected, the affected Rools instance is inconsistent and should no longer be used.\n\nExample:\n\n```javascript\nconst { Rools, Rule } = require('rools');\nconst ruleMoodGreat = new Rule({\n  name: 'mood is great if 200 stars or more',\n  when: (facts) =\u003e facts.user.stars \u003e= 200,\n  then: (facts) =\u003e {\n    facts.user.mood = 'great';\n  },\n});\nconst ruleGoWalking = new Rule({\n  name: 'go for a walk if mood is great and the weather is fine',\n  when: [\n    (facts) =\u003e facts.user.mood === 'great',\n    (facts) =\u003e facts.weather.temperature \u003e= 20,\n    (facts) =\u003e !facts.weather.rainy,\n  ],\n  then: (facts) =\u003e {\n    facts.goWalking = true;\n  },\n});\nconst rools = new Rools();\nawait rools.register([ruleMoodGreat, ruleGoWalking]);\n```\n\n### Evaluate facts: `evaluate()`\n\nFacts are plain JavaScript or JSON objects or objects from ES6 classes with getters and setters.\nFor example:\n\n```javascript\nconst user = {\n  name: 'frank',\n  stars: 347,\n};\nconst weather = {\n  temperature: 20,\n  windy: true,\n  rainy: false,\n};\nconst rools = new Rools();\nawait rools.register(...);\nawait rools.evaluate({ user, weather });\n```\n\nPlease note that Rools reads the facts (`when`) as well as writes to the facts (`then`) during evaluation.\nPlease make sure you provide a fresh set of facts whenever you call `evaluate()`.\n\n`evaluate()` is working asynchronously, i.e., it returns a promise.\nIf a premise (`when`) fails, `evaluate()` will still *not* fail (for robustness reasons).\nIf an action (`then`) fails, `evaluate()` will reject its promise.\n\nIf there is more than one rule ready to fire, Rools applies a *conflict resolution strategy* to decide which rule/action to fire first. The default conflict resolution strategy is 'ps'.\n\n* 'ps' -- (1) priority, (2) specificity, (3) order of registration\n* 'sp' -- (1) specificity, (2) priority, (3) order of registration\n\nIf you don't like the default 'ps', you can change the conflict resolution strategy like this:\n\n```javascript\nawait rools.evaluate(facts, { strategy: 'sp' });\n```\n\n`evaluate()` returns an object providing some debug information about the past evaluation run:\n\n* `fired` -- the number of rules that were fired.\n* `elapsed` -- the number of milliseconds needed.\n* `accessedByPremises` -- the fact segments that were accessed by premises (`when`).\n* `accessedByActions` -- the fact segments that were accessed by actions (`then`). Formerly `updated` but renamed for clarification (but still provided for backward compatibility).\n\n```javascript\nconst { accessedByActions, fired, elapsed } = await rools.evaluate(facts);\nconsole.log(accessedByActions, fired, elapsed); // e.g., [\"user\"] 26 187\n```\n\n### Logging\n\nBy default, Rools is logging errors to the JavaScript `console`.\nThis can be configured like this.\n\n```javascript\nconst delegate = ({ level, message, rule, error }) =\u003e {\n  console.error(level, message, rule, error);\n};\nconst rools = new Rools({\n  logging: { error: true, debug: false, delegate },\n});\n...\n```\n\n`level` is either `debug` or `error`.\nThe error log reports failed actions or premises.\nThe debug log reports the entire evaluation process for debugging purposes.\n\n## TypeScript\n\nThis package provides types for TypeScript.\n\n```typescript\nimport { Rools, Rule } from \"rools\";\n\n// ...\n```\n\nFor this module to work, your **TypeScript compiler options** must include\n`\"target\": \"ES2015\"` (or later), `\"moduleResolution\": \"node\"`, and\n`\"esModuleInterop\": true`.\n\n## Migration\n\n### Version 1.x.x to Version 2.x.x\n\nThere are a few breaking changes that require changes to your code.\n\nRools exposes now two classes, `Rools` and `Rule`.\n\n```javascript\n// Version 1.x.x\nconst Rools = require('rools');\n// Version 2.x.x\nconst { Rools, Rule } = require('rools');\n```\n\nRules must now be created with `new Rule()`.\n\n```javascript\n// Version 1.x.x\nconst rule = {\n  name: 'my rule',\n  ...\n};\n// Version 2.x.x\nconst rule = new Rule({\n  name: 'my rule',\n  ...\n});\n```\n\n`register()` takes the rules to be registered as an array now.\nReason is to allow a second options parameter in future releases.\n\n```javascript\nconst rools = new Rools();\n...\n// Version 1.x.x\nawait rools.register(rule1, rule2, rule3);\n// Version 2.x.x\nawait rools.register([rule1, rule2, rule3]);\n```\n\n`evaluate()` does not return the facts anymore - which was only for convenience anyway.\nInstead, it returns an object with some useful information.\n\n```javascript\nconst rools = new Rools();\n...\n// Version 1.x.x\nconst facts = await rools.evaluate({ user, weather });\n// Version 2.x.x\nconst { updated, fired, elapsed } = await rools.evaluate({ user, weather });\nconsole.log(updated, fired, elapsed); // e.g., [\"user\"] 26 187\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffrankthelen%2Frools","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffrankthelen%2Frools","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffrankthelen%2Frools/lists"}