{"id":30017940,"url":"https://github.com/dashersw/recht","last_synced_at":"2025-08-05T23:07:54.545Z","repository":{"id":45753164,"uuid":"140072982","full_name":"dashersw/recht","owner":"dashersw","description":"A concise rule engine to express and enforce rules for selections, permissions and the like","archived":false,"fork":false,"pushed_at":"2018-07-13T09:57:04.000Z","size":102,"stargazers_count":108,"open_issues_count":0,"forks_count":9,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-07-25T17:46:41.996Z","etag":null,"topics":["access-control","acl","decision-making","permissions","rule-set"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","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/dashersw.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}},"created_at":"2018-07-07T10:00:35.000Z","updated_at":"2025-06-17T14:47:39.000Z","dependencies_parsed_at":"2022-07-30T13:07:59.500Z","dependency_job_id":null,"html_url":"https://github.com/dashersw/recht","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/dashersw/recht","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dashersw%2Frecht","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dashersw%2Frecht/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dashersw%2Frecht/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dashersw%2Frecht/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dashersw","download_url":"https://codeload.github.com/dashersw/recht/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dashersw%2Frecht/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268987471,"owners_count":24340667,"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-08-05T02:00:12.334Z","response_time":2576,"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":["access-control","acl","decision-making","permissions","rule-set"],"created_at":"2025-08-05T23:07:53.822Z","updated_at":"2025-08-05T23:07:54.535Z","avatar_url":"https://github.com/dashersw.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Das Recht — A concise rule engine to express and enforce rules for selections, permissions and the like\n\n[![npm version](https://badge.fury.io/js/recht.svg)](https://badge.fury.io/js/recht)\n[![Build Status](https://travis-ci.org/dashersw/recht.svg?branch=master)](https://travis-ci.org/dashersw/recht)\n[![Coverage Status](https://coveralls.io/repos/github/dashersw/recht/badge.svg)](https://coveralls.io/github/dashersw/recht)\n[![dependencies Status](https://david-dm.org/dashersw/recht/status.svg)](https://david-dm.org/dashersw/recht)\n[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/dashersw/recht/master/LICENSE)\n\n**Das Recht enables you to customize your application's behavior based on simple rules, without the need of a database or an expensive query engine.**\n\n```js\nconst recht = new Recht()\nrecht.rules = [\n  ['DENY', 'T-shirts', 'S', ['Black', 'Blue']],\n  ['DENY', 'T-shirts', ['M', 'L'], 'Black'],\n  ['ALLOW', 'T-shirts', '*', '*']\n]\n\nrecht.check('T-shirts', 'S', 'Black') // false\nrecht.check('T-shirts', 'M', 'Blue') // true\nrecht.check('T-Shirts', 'L', 'Black') // false\n```\n\n## Features\n- Declarative, logic-less rules\n- Lightweight, zero dependencies\n- Generic engine for any application\n- Wildcard support for arbitary conditions\n- Simple recommendation engine for the closest alternative available\n- Ordered rules for lighter expressions\n\n## Possible use cases\n- Access control lists to manage whether a given user has permission on a resource\n- Feature toggling to determine which features to show in a certain environment (QA, UA, Production)\n- A/B testing to determine which features are available to which category of users\n- E-commerce applications where some product variants may be unavailable (disabling black shirts only for size S)\n\n## Table of Contents\n* [Features](#features)\n* [Possible use cases](#possible-use-cases)\n* [Usage](#usage)\n    * [Installation](#installation)\n    * [Setting up](#setting-up)\n    * [Defining rules](#defining-rules)\n    * [Checks](#checks)\n    * [Wildcards](#wildcards)\n    * [Dimensions](#dimensions)\n    * [Finding the closest alternatives available](#finding-the-closest-alternatives-available)\n* [Advanced options](#advanced-options)\n    * [Functional usage](#functional-usage)\n    * [Other methods for closest alternative](#other-methods-for-closest-alternative)\n* [API Documentation](#api-documentation)\n    * [Recht](#recht)\n      * [Instance properties](#instance-properties)\n          * [rules](#rules--arrayrule) → Array.\u003c\u003ca href=\"#rule\"\u003eRule\u003c/a\u003e\u003e\n          * [dimensions](#dimensions--arraydimension) → Array.\u003c\u003ca href=\"#dimension\"\u003eDimension\u003c/a\u003e\u003e\n      * [Instance methods](#instance-methods)\n          * [check(...conditions) → boolean](#checkconditions--boolean)\n          * [closest(...conditions) → \u003ca href=\"#conditionsresult\"\u003eConditionsResult\u003c/a\u003e](#closestconditions--conditionsresult)\n          * [closestValue(...conditions) → \u003ca href=\"#valueresult\"\u003eValueResult\u003c/a\u003e](#closestvalueconditions--valueresult)\n          * [closestVerbose(...conditions) → \u003ca href=\"#verboseresult\"\u003eVerboseResult\u003c/a\u003e](#closestverboseconditions--verboseresult)\n      * [Static methods](#static-methods)\n          * [Recht.check(definitions, ...conditions) → boolean](#rechtcheckdefinitions-conditions--boolean)\n          * [Recht.closest(definitions, ...conditions) → \u003ca href=\"#conditionsresult\"\u003eConditionsResult\u003c/a\u003e](#rechtclosestdefinitions-conditions--conditionsresult)\n          * [Recht.closestValue(definitions, ...conditions) → \u003ca href=\"#valueresult\"\u003eValueResult\u003c/a\u003e](#rechtclosestvaluedefinitions-conditions--valueresult)\n          * [Recht.closestVerbose(definitions, ...conditions) → \u003ca href=\"#verboseresult\"\u003eVerboseResult\u003c/a\u003e](#rechtclosestverbosedefinitions-conditions--verboseresult)\n    * [Type definitions](#type-definitions)\n* [Contribution](#contribution)\n* [MIT License](#mit-license)\n\n## Usage\n### Installation\nInstall Recht via npm:\n\n```bash\n$ npm install recht\n```\n### Setting up\nRequire and instantiate Recht.\n\n```js\nconst Recht = require('recht')\nconst recht = new Recht()\n```\n\n### Defining rules\nRecht accepts an array of rules, which are arrays of conditions in themselves. A rule may either be an `ALLOW` rule or a `DENY` rule, the first element of the rule array should always be either `ALLOW` or `DENY`.\n\nIf a condition matches an `ALLOW` rule, the check will return `true`. If a condition matches a `DENY` rule, the check will return `false`. Recht will go through the rules in the `rules` array one by one in the order of declaration, and return as soon as any of the rules match. Therefore, the order of the rules are very important. If there's no match, the check will fail and return `false`.\n\n```js\nconst Recht = require('recht')\nconst recht = new Recht()\n\nrecht.rules = [\n  ['ALLOW', ['Master', 'Developer'], 'push', '*'], // allow masters \u0026 developers to push to any branch\n  ['ALLOW', 'Master', 'force push', 'master'], // allow masters to force push to the master branch\n  ['DENY', 'QA', 'clone', 'production'], // disallow QA from cloning production\n  ['ALLOW', '*', 'clone'], // allow anyone to clone any branch\n]\n```\n\n### Checks\n\nOnce you define your rules, you can check any condition against them with the `check` method. The following example checks if the `'Developer'` can `'push'` to `'master'`.\n```js\nrecht.check('Developer', 'push', 'master')\n```\n\nIf you are only interested whether a `'Developer'` can push to any branch at all, you can omit the last argument and call the `check` function with only two arguments as the following:\n```js\nrecht.check('Developer', 'push')\n```\n\nThis feature is useful for checking group matches or hierarchical structures. It assumes the rest of the arguments can be of any value. If you wish to keep one of the latter arguments, but use the any value mechanism in one of the earlier arguments, you can use [wildcards][#wildcards].\n\n### Wildcards\nRecht accepts `'*'` as a wildcard condition. In this case, any value for that condition will be accepted as a match. The following example gives anybody clone access to any branch.\n\n```js\nrecht.rules = [\n  ['ALLOW', '*', 'clone']\n]\n```\n\nRead on to [Dimensions](#dimensions) to learn how to constraint the wildcard to only accept known values.\n\n### Dimensions\nEach rule in the rule set should include the same number of conditions in them. In the following example there are 3 conditions in each rule.\n\n```js\nrecht.rules = [\n  ['ALLOW', 'Gold member', ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], ['Swimming pool', 'Gym', 'Sauna']],\n  ['DENY', 'Guest', ['Mon', 'Tue'], 'Sauna'],\n  ['ALLOW', ['Guest', 'Regular member'], '*', '*']\n]\n```\n\nThe set of possible values for a condition is called a dimension. The first dimension is the membership type, the second dimension is the facilities a member has access to, and the last one is the days the the facilities can be used. These dimensions can be expressed as follows:\n\n```js\nconst memberships = ['Gold member', 'Regular member', 'Guest']\nconst days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri']\nconst facilities = ['Swimming pool', 'Gym', 'Sauna']\n```\n\nIf these values are known ahead of time and passed to Recht, Recht opens up two further features. The first one is constraining the wildcard: without defining dimensions, a wildcard accepts any arbitrary value, and this might be an unwanted case. The following example uses dimensions to constrain the wildcard to known values:\n\n```js\nrecht.check('Guest', 'Sat') // true, since no dimensions are defined yet\nrecht.dimensions = [memberships, days, facilities]\nrecht.check('Guest', 'Sat') // false, since Sat isn't included in the days dimension\n```\n\nThe first check passes, because initially we haven't defined the dimensions and the rules accept a wildcard for days. As we define the dimensions, the second check fails because `Sat` is not an element in the `days` dimension.\n\n```js\nrecht.dimensions = [memberships, facilities, days]\nrecht.check('')\n```\n\n### Finding the closest alternatives available\n\nOnce you have defined [dimensions](#dimensions), Recht can be used to predict the closest choice available. This is a very handy feature if you want to show what is possible for a given rule set. See the following example on how to make the best of this feature:\n\n```js\nconst Recht = require('recht')\nconst recht = new Recht()\n\nconst memberships = ['Gold Member', 'Regular member', 'Guest']\nconst days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri']\nconst facilities = ['Swimming pool', 'Gym', 'Sauna']\n\nrecht.dimensions = [memberships, days, facilities]\n\nrecht.rules = [\n  ['ALLOW', 'Gold member', '*', '*'],\n  ['DENY', 'Guest', ['Mon', 'Tue'], 'Sauna'],\n  ['ALLOW', ['Guest', 'Regular member'], '*', '*']\n]\n\nrecht.check('Guest', 'Mon', 'Sauna') // false\nrecht.closest('Guest', 'Mon', 'Sauna') // ['Guest, 'Wed', 'Sauna']\nrecht.closest('Guest', 'Mon', 'Sauna', facilities) // ['Guest', 'Mon', 'Swimming pool']\n```\n\nIn this example, Guests can't use the `Sauna` on `Mon`, therefore the check fails. Recht assumes that the last dimension, although the final chain in the hierarchy, is the stationary choice when we are looking for alternatives. Therefore, when invoked with the same arguments, `closest` gives us `Wed`, which is the next day a `Guest` can use the `Sauna`.\n\nIf we would like to start the search for the closest alternative from the last dimension (i.e, facilities), we can do so by indicating which dimension we want the `closest` search to start from. This happens as a result of passing the dimension as the last argument to the `closest` call. Notice that `closest` takes advantage of references, therefore the dimension that we pass in as the last argument to the `closest` call has to be a member of the original `dimensions` array.\n\nIn this case, the answer will be `Swimming pool`. This means that if a `Guest` is looking for alternatives that they can use on `Mon` only, they can use the `Swimming pool`.\n\nSince the last argument can be any dimension, one last example question we can ask `Recht` is the following: \"What kind of a membership do I need in order to be able to use the `Sauna` on `Mon`?\" This call is as follows:\n\n```js\nrecht.closest('Guest', 'Mon', 'Sauna', memberships) // ['Regular member', 'Mon', 'Sauna']\n```\n\nAs you see, Recht is capable of several advanced use cases. The `closest` search is very handy if you are building a feature set and want to guide your users to the right selection for certain features they want.\n\n## Advanced options\n### Functional usage\nRecht can be used as a functional library without instantiation. The following example also works:\n\n```js\nconst recht = require('recht')\n\nconst memberships = ['Gold Member', 'Regular member', 'Guest']\nconst days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri']\nconst facilities = ['Swimming pool', 'Gym', 'Sauna']\n\nconst dimensions = [memberships, days, facilities]\n\nconst rules = [\n  ['ALLOW', 'Gold member', '*', '*'],\n  ['DENY', 'Guest', ['Mon', 'Tue'], 'Sauna'],\n  ['ALLOW', ['Guest', 'Regular member'], '*', '*']\n]\n\nrecht.check({ rules }, 'Guest', 'Mon', 'Sauna') // false\nrecht.closest({ rules, dimensions }, 'Guest', 'Mon', 'Sauna') // ['Guest, 'Wed', 'Sauna']\nrecht.closest({ rules, dimensions }, 'Guest', 'Mon', 'Sauna', facilities) // ['Guest', 'Mon', 'Swimming pool']\n```\n\nHere we didn't have to instantiate a Recht instance and pass in the rules. Instead, we used the statically available `check` and `closest` methods and passed in rules and dimensions as parameters.\n\n### Other methods for closest alternative\nThe default `closest` method gives you an array of conditions that shows you exactly which conditions were matched. If you are using some sort of a state management solution, you can directly destruct this array and set it as your state. While this covers most of the use cases, certain other alternatives exist to get the best out of Recht.\n\n`closestValue` gives you the single, simple value that had to change in order to satisfy the condition. This is easier to use than the normal `closest` method in cases where the dimension of this value is known beforehand.\n\n`closestVerbose` gives you a more detailed object that also returns information about the dimension and the dimension index that the change had to take place.\n\n## API Documentation\n### Recht\nClass exposed by `require('recht')`. A concise rule engine to express and enforce rules for selections, permissions and the like.\n#### Example\n```js\nconst Recht = require('recht')\nconst recht = new Recht()\n```\n#### Instance properties\n##### rules → `Array.\u003c`[`Rule`](#rule)`\u003e`\n`Rule`s define which conditions to `ALLOW` or `DENY`.\n\n##### dimensions → `Array.\u003c`[`Dimension`](#dimension)`\u003e`\n`Dimension`s define the set of possible values for each `Condition` in a [`Rule`](#rule).\n\n#### Instance methods\n##### check(...conditions) → `boolean`\nCheck function receives an arbitrary number of conditions. Returns a `boolean` whose value depends on whether the given conditions match the definitions.\n###### Parameters\n*`...conditions`* `...string` Conditions to check if they are allowed within the given rule definition.\n###### Returns\n`boolean` Whether the given condition set is allowed according to the definitions.\n###### Throws\n`Error` Throws if no rules or conditions are provided.\n\n##### closest(...conditions) → [`ConditionsResult`](#conditionsresult)\nSearches for the closest alternative to a given condition. Requires `dimensions` to be set. It recursively searches an alternative starting from a specified dimension. If no dimensions are specified, the starting dimension is always the penultimate dimension. This method returns a set of conditions that is the closest alternative to the given set or null if no matches are found.\n###### Parameters\n*`...conditions`* `...string` Conditions to look for the closest alternative allowed by the `rules` set.\n###### Returns\n[`ConditionsResult`](#conditionsresult) The matching conditions as an array.\n###### Throws\n`Error` Throws if no dimensions are provided.\n\n##### closestValue(...conditions) → [`ValueResult`](#valueresult)\nSearches for the closest alternative to a given condition. Requires `dimensions` to be set. It recursively searches an alternative starting from a specified dimension. If no dimensions are specified, the starting dimension is always the penultimate dimension. This method returns a simple value of a given condition, or null if no matches are found. Since the return value is a simple value, this method is only useful if the dimension is not known beforehand. For more information on the search result, use `closest` or `closestVerbose`.\n###### Parameters\n*`...conditions`* `...string` Conditions to look for the closest alternative allowed by the `rules` set.\n###### Returns\n[`ValueResult`](#valueresult) The matching condition as a simple value.\n###### Throws\n`Error` Throws if no dimensions are provided.\n\n##### closestVerbose(...conditions) → [`VerboseResult`](#verboseresult)\nSearches for the closest alternative to a given condition. Requires `dimensions` to be set. It recursively searches an alternative starting from a specified dimension. If no dimensions are specified, the starting dimension is always the penultimate dimension. This method returns a verbose object (`VerboseResult`) that returns the `dimension`, `dimensionIndex`, `value` and `conditions` that make up the closest alternative.\n###### Parameters\n*`...conditions`* `...string` Conditions to look for the closest alternative allowed by the `rules` set.\n###### Returns\n[`VerboseResult`](#verboseresult) The matching condition as a simple value.\n###### Throws\n`Error` Throws if no dimensions are provided.\n\n#### Static methods\n##### Recht.check(definitions, ...conditions) → `boolean`\nCheck function receives a `Definitions` object with `dimensions` and `rules`, and an arbitrary number of conditions. Returns a `boolean` whose value depends on whether the given conditions match the definitions.\n###### Parameters\n*`definitions`* [`Definitions`](#definitions) An object with `dimensions` and `rules`.\n*`...conditions`* `...string` Conditions to check if they are allowed within the given rule definition.\n###### Returns\n`boolean` Whether the given condition set is allowed according to the definitions.\n###### Throws\n`Error` Throws if no rules or conditions are provided.\n\n##### Recht.closest(definitions, ...conditions) → [`ConditionsResult`](#conditionsresult)\nSearches for the closest alternative to a given condition. Requires `dimensions` to be set. It recursively searches an alternative starting from a specified dimension. If no dimensions are specified, the starting dimension is always the penultimate dimension. This method returns a set of conditions that is the closest alternative to the given set or null if no matches are found.\n###### Parameters\n*`definitions`* [`Definitions`](#definitions) An object with `dimensions` and `rules`.\n*`...conditions`* `...string` Conditions to look for the closest alternative allowed by the `rules` set.\n###### Returns\n[`ConditionsResult`](#conditionsresult) The matching conditions as an array.\n###### Throws\n`Error` Throws if no dimensions are provided.\n\n##### Recht.closestValue(definitions, ...conditions) → [`ValueResult`](#valueresult)\nSearches for the closest alternative to a given condition. Requires `dimensions` to be set. It recursively searches an alternative starting from a specified dimension. If no dimensions are specified, the starting dimension is always the penultimate dimension. This method returns a simple value of a given condition, or null if no matches are found. Since the return value is a simple value, this method is only useful if the dimension is not known beforehand. For more information on the search result, use `closest` or `closestVerbose`.\n###### Parameters\n*`definitions`* [`Definitions`](#definitions) An object with `dimensions` and `rules`.\n*`...conditions`* `...string` Conditions to look for the closest alternative allowed by the `rules` set.\n###### Returns\n[`ValueResult`](#valueresult) The matching condition as a simple value.\n###### Throws\n`Error` Throws if no dimensions are provided.\n\n##### Recht.closestVerbose(definitions, ...conditions) → [`VerboseResult`](#verboseresult)\nSearches for the closest alternative to a given condition. Requires `dimensions` to be set. It recursively searches an alternative starting from a specified dimension. If no dimensions are specified, the starting dimension is always the penultimate dimension. This method returns a verbose object (`VerboseResult`) that returns the `dimension`, `dimensionIndex`, `value` and `conditions` that make up the closest alternative.\n###### Parameters\n*`definitions`* [`Definitions`](#definitions) An object with `dimensions` and `rules`.\n*`...conditions`* `...string` Conditions to look for the closest alternative allowed by the `rules` set.\n###### Returns\n[`VerboseResult`](#verboseresult) The matching condition as a simple value.\n###### Throws\n`Error` Throws if no dimensions are provided.\n\n### Type definitions\nThis is a list of pseudo-types that are used throughout the documentation.\n\n#### Dimension\n`Array.\u003cstring\u003e`\n#### Rule\n`Array.\u003cstring\u003e`\n#### Definitions\n`{dimensions: Array.\u003cDimension\u003e, rules: Array.\u003cRule\u003e}`\n#### Conditions\n`Array.\u003cstring\u003e`\n#### ValueResult\n`string`\n#### ConditionsResult\n`Array.\u003cstring\u003e`\n#### VerboseResult\n`{dimension: Dimension, dimensionIndex: number, value: string, conditions: Conditions}`\n\n## Contribution\n\nRecht is under development, and is open to suggestions and contributions.\n\nIf you would like to see a feature implemented or want to contribute a new feature, you are welcome to open an issue to discuss it and we will be more than happy to help.\n\nIf you choose to make a contribution, please fork this repository, work on a feature and submit a pull request.\n\n## MIT License\n\nCopyright (c) 2018 Armagan Amcalar\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdashersw%2Frecht","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdashersw%2Frecht","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdashersw%2Frecht/lists"}