• Stars
    star
    103
  • Rank 331,104 (Top 7 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created about 6 years ago
  • Updated about 6 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A concise rule engine to express and enforce rules for selections, permissions and the like

Das Recht โ€” A concise rule engine to express and enforce rules for selections, permissions and the like

npm version Build Status Coverage Status dependencies Status GitHub license

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.

const recht = new Recht()
recht.rules = [
  ['DENY', 'T-shirts', 'S', ['Black', 'Blue']],
  ['DENY', 'T-shirts', ['M', 'L'], 'Black'],
  ['ALLOW', 'T-shirts', '*', '*']
]

recht.check('T-shirts', 'S', 'Black') // false
recht.check('T-shirts', 'M', 'Blue') // true
recht.check('T-Shirts', 'L', 'Black') // false

Features

  • Declarative, logic-less rules
  • Lightweight, zero dependencies
  • Generic engine for any application
  • Wildcard support for arbitary conditions
  • Simple recommendation engine for the closest alternative available
  • Ordered rules for lighter expressions

Possible use cases

  • Access control lists to manage whether a given user has permission on a resource
  • Feature toggling to determine which features to show in a certain environment (QA, UA, Production)
  • A/B testing to determine which features are available to which category of users
  • E-commerce applications where some product variants may be unavailable (disabling black shirts only for size S)

Table of Contents

Usage

Installation

Install Recht via npm:

$ npm install recht

Setting up

Require and instantiate Recht.

const Recht = require('recht')
const recht = new Recht()

Defining rules

Recht 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.

If 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.

const Recht = require('recht')
const recht = new Recht()

recht.rules = [
  ['ALLOW', ['Master', 'Developer'], 'push', '*'], // allow masters & developers to push to any branch
  ['ALLOW', 'Master', 'force push', 'master'], // allow masters to force push to the master branch
  ['DENY', 'QA', 'clone', 'production'], // disallow QA from cloning production
  ['ALLOW', '*', 'clone'], // allow anyone to clone any branch
]

Checks

Once 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'.

recht.check('Developer', 'push', 'master')

If 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:

recht.check('Developer', 'push')

This 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].

Wildcards

Recht 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.

recht.rules = [
  ['ALLOW', '*', 'clone']
]

Read on to Dimensions to learn how to constraint the wildcard to only accept known values.

Dimensions

Each 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.

recht.rules = [
  ['ALLOW', 'Gold member', ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], ['Swimming pool', 'Gym', 'Sauna']],
  ['DENY', 'Guest', ['Mon', 'Tue'], 'Sauna'],
  ['ALLOW', ['Guest', 'Regular member'], '*', '*']
]

The 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:

const memberships = ['Gold member', 'Regular member', 'Guest']
const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri']
const facilities = ['Swimming pool', 'Gym', 'Sauna']

If 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:

recht.check('Guest', 'Sat') // true, since no dimensions are defined yet
recht.dimensions = [memberships, days, facilities]
recht.check('Guest', 'Sat') // false, since Sat isn't included in the days dimension

The 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.

recht.dimensions = [memberships, facilities, days]
recht.check('')

Finding the closest alternatives available

Once you have defined 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:

const Recht = require('recht')
const recht = new Recht()

const memberships = ['Gold Member', 'Regular member', 'Guest']
const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri']
const facilities = ['Swimming pool', 'Gym', 'Sauna']

recht.dimensions = [memberships, days, facilities]

recht.rules = [
  ['ALLOW', 'Gold member', '*', '*'],
  ['DENY', 'Guest', ['Mon', 'Tue'], 'Sauna'],
  ['ALLOW', ['Guest', 'Regular member'], '*', '*']
]

recht.check('Guest', 'Mon', 'Sauna') // false
recht.closest('Guest', 'Mon', 'Sauna') // ['Guest, 'Wed', 'Sauna']
recht.closest('Guest', 'Mon', 'Sauna', facilities) // ['Guest', 'Mon', 'Swimming pool']

In 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.

If 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.

In 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.

Since 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:

recht.closest('Guest', 'Mon', 'Sauna', memberships) // ['Regular member', 'Mon', 'Sauna']

As 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.

Advanced options

Functional usage

Recht can be used as a functional library without instantiation. The following example also works:

const recht = require('recht')

const memberships = ['Gold Member', 'Regular member', 'Guest']
const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri']
const facilities = ['Swimming pool', 'Gym', 'Sauna']

const dimensions = [memberships, days, facilities]

const rules = [
  ['ALLOW', 'Gold member', '*', '*'],
  ['DENY', 'Guest', ['Mon', 'Tue'], 'Sauna'],
  ['ALLOW', ['Guest', 'Regular member'], '*', '*']
]

recht.check({ rules }, 'Guest', 'Mon', 'Sauna') // false
recht.closest({ rules, dimensions }, 'Guest', 'Mon', 'Sauna') // ['Guest, 'Wed', 'Sauna']
recht.closest({ rules, dimensions }, 'Guest', 'Mon', 'Sauna', facilities) // ['Guest', 'Mon', 'Swimming pool']

Here 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.

Other methods for closest alternative

The 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.

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.

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.

API Documentation

Recht

Class exposed by require('recht'). A concise rule engine to express and enforce rules for selections, permissions and the like.

Example

const Recht = require('recht')
const recht = new Recht()

Instance properties

rules โ†’ Array.<Rule>

Rules define which conditions to ALLOW or DENY.

dimensions โ†’ Array.<Dimension>

Dimensions define the set of possible values for each Condition in a Rule.

Instance methods

check(...conditions) โ†’ boolean

Check function receives an arbitrary number of conditions. Returns a boolean whose value depends on whether the given conditions match the definitions.

Parameters

...conditions ...string Conditions to check if they are allowed within the given rule definition.

Returns

boolean Whether the given condition set is allowed according to the definitions.

Throws

Error Throws if no rules or conditions are provided.

closest(...conditions) โ†’ ConditionsResult

Searches 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.

Parameters

...conditions ...string Conditions to look for the closest alternative allowed by the rules set.

Returns

ConditionsResult The matching conditions as an array.

Throws

Error Throws if no dimensions are provided.

closestValue(...conditions) โ†’ ValueResult

Searches 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.

Parameters

...conditions ...string Conditions to look for the closest alternative allowed by the rules set.

Returns

ValueResult The matching condition as a simple value.

Throws

Error Throws if no dimensions are provided.

closestVerbose(...conditions) โ†’ VerboseResult

Searches 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.

Parameters

...conditions ...string Conditions to look for the closest alternative allowed by the rules set.

Returns

VerboseResult The matching condition as a simple value.

Throws

Error Throws if no dimensions are provided.

Static methods

Recht.check(definitions, ...conditions) โ†’ boolean

Check 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.

Parameters

definitions Definitions An object with dimensions and rules. ...conditions ...string Conditions to check if they are allowed within the given rule definition.

Returns

boolean Whether the given condition set is allowed according to the definitions.

Throws

Error Throws if no rules or conditions are provided.

Recht.closest(definitions, ...conditions) โ†’ ConditionsResult

Searches 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.

Parameters

definitions Definitions An object with dimensions and rules. ...conditions ...string Conditions to look for the closest alternative allowed by the rules set.

Returns

ConditionsResult The matching conditions as an array.

Throws

Error Throws if no dimensions are provided.

Recht.closestValue(definitions, ...conditions) โ†’ ValueResult

Searches 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.

Parameters

definitions Definitions An object with dimensions and rules. ...conditions ...string Conditions to look for the closest alternative allowed by the rules set.

Returns

ValueResult The matching condition as a simple value.

Throws

Error Throws if no dimensions are provided.

Recht.closestVerbose(definitions, ...conditions) โ†’ VerboseResult

Searches 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.

Parameters

definitions Definitions An object with dimensions and rules. ...conditions ...string Conditions to look for the closest alternative allowed by the rules set.

Returns

VerboseResult The matching condition as a simple value.

Throws

Error Throws if no dimensions are provided.

Type definitions

This is a list of pseudo-types that are used throughout the documentation.

Dimension

Array.<string>

Rule

Array.<string>

Definitions

{dimensions: Array.<Dimension>, rules: Array.<Rule>}

Conditions

Array.<string>

ValueResult

string

ConditionsResult

Array.<string>

VerboseResult

{dimension: Dimension, dimensionIndex: number, value: string, conditions: Conditions}

Contribution

Recht is under development, and is open to suggestions and contributions.

If 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.

If you choose to make a contribution, please fork this repository, work on a feature and submit a pull request.

MIT License

Copyright (c) 2018 Armagan Amcalar

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

More Repositories

1

cote

A Node.js library for building zero-configuration microservices.
JavaScript
2,302
star
2

pedalboard.js

Open source JavaScript framework for developing audio effects for guitars using the Web Audio API.
JavaScript
830
star
3

mogollar

A MongoDB UI built with Electron
JavaScript
279
star
4

erste

Your first choice for hybrid mobile applications
JavaScript
271
star
5

cote-workshop

Microservices case study with cote.js
JavaScript
254
star
6

brain-bits

A P300 online spelling mechanism for Emotiv headsets. It's completely written in Node.js, and the GUI is based on Electron and Vue.
JavaScript
168
star
7

biri

A unique, static client ID generator for browser applications
JavaScript
139
star
8

brain-monitor

A terminal app written in Node.js to monitor brain signals in real-time
JavaScript
133
star
9

wits

A Node.js library that reads your mind with Emotiv EPOC EEG headset
C++
90
star
10

docker-node-pm2

A pm2 application container for docker.
Shell
75
star
11

stack

A starter repository for MongoDB, Node.js, and Vue.js, with a local environment based on Docker.
JavaScript
57
star
12

microservices-workshop

An example microservices implementation with Node.js and Docker
JavaScript
54
star
13

regie

An observable state management tool for vanilla JS applications based on Proxies
JavaScript
49
star
14

vuelve

A declarative syntax for the Composition API in Vue 3.
JavaScript
46
star
15

hakki

An opinionated, modern, and scalable alternative to node_acl.
JavaScript
35
star
16

docker-nextjs

JavaScript
29
star
17

tombala

A simple tombola game
JavaScript
27
star
18

geneJS

Code generator for PlantUML
JavaScript
24
star
19

vue-node-starter

JavaScript
20
star
20

erste-demo

A sample app that showcases how to use erste
CSS
18
star
21

vue-starter

Vue
16
star
22

plantuml

Git mirror of plantuml's SVN repo. Updated seldomly, whenever I need the new source.
Java
13
star
23

aktivite-akis-ornegi

Node.js ile aktivite akฤฑลŸ รถrneฤŸi
JavaScript
10
star
24

jira-bot

Jira bot is a library that bridges Jira and XMPP chat.
JavaScript
10
star
25

hax.js

Haxball clone with JavaScript; Canvas and Node.js
JavaScript
8
star
26

puckjs-automatic-page-turner

An automatic page turner BLE HID Peripheral for Puck.js
JavaScript
8
star
27

dockercloud-microservices

An example workflow to build & deploy Node.js microservices to Docker Cloud.
JavaScript
7
star
28

kotelett

Simplest microservices ever.
JavaScript
7
star
29

wtmbjs-4

JavaScript
6
star
30

mindy

JavaScript
6
star
31

midi-experiments

JavaScript
6
star
32

dashMVC

MVC doodlings with Javascript
JavaScript
5
star
33

node-scale

Examples for Scaling Node.js applications with Redis, RabbitMQ and cote.js
JavaScript
5
star
34

cote-examples-currency-conversion

An example microservices application with cote
JavaScript
5
star
35

use-the-force-luke

A brain-wave app that lets you use the force
JavaScript
4
star
36

erste-starter

A starter repository for erste
CSS
4
star
37

BoilerPlate

Shell
4
star
38

IT537

JavaScript
4
star
39

epocx-experiments

Experiments with Emotiv EPOC X headset
3
star
40

wtmbjsa

JavaScript
3
star
41

fse-visualizer

A visual engine for footballSimulationEngine
JavaScript
3
star
42

node-closure-compiler

JavaScript
3
star
43

ibwturkey

JavaScript
3
star
44

wain

A topic-based news aggregator with AI.
JavaScript
3
star
45

docker-node-pm2-keymetrics

A docker image for PM2 and Keymetrics
Shell
3
star
46

node-webinar-examples

Examples for the webinar Scaling and Managing Node js Applications with Microservices
JavaScript
3
star
47

berlin-nodejs-meetup-cote

Examples for the talk "Implementing Microservices With Cote"
JavaScript
2
star
48

wtmjs

CSS
2
star
49

PapazKacti

2
star
50

docker-spa-server

2
star
51

angular-seed

2
star
52

wtmbjsa-3

Bridging APIs
JavaScript
2
star
53

docker-nodejs-build-tools

A docker image that includes various build tools for Node.js projects.
2
star
54

tartjs-presentation

JavaScript
2
star
55

baking-soda-paste

The definitive solvent for disgusting rubber-band scrolling on iOS.
2
star
56

politburo

A micro library for building applications with Vieux architecture
JavaScript
2
star
57

nodeRemote

2
star
58

Spicefinder

JavaScript
1
star
59

existing-repo

1
star
60

fs2017-microservices

Microservices example used in FullStack 2017 microservices workshop
JavaScript
1
star
61

multi-host-docker-cloud

JavaScript
1
star
62

docker-cloud-multicast

C
1
star
63

multicast-problem

JavaScript
1
star
64

rabbit-fn

JavaScript
1
star
65

wtmbjsa-5

MongoDB examples
JavaScript
1
star
66

devopspro-workshop

JavaScript
1
star
67

erste-boilerplate

HTML
1
star
68

google-maps-tsp-solver

Automatically exported from code.google.com/p/google-maps-tsp-solver
JavaScript
1
star
69

closure-test

JavaScript
1
star
70

gitfstest

1
star
71

docker-cote-monitoring-tool

Docker image for cote monitoring GUI
JavaScript
1
star
72

nomadcommerce

creative.nmdapps
CSS
1
star
73

web

JavaScript
1
star
74

colors

Rethinking Colors in Design Systems: A CSS Approach to Idiomatic Design
HTML
1
star