All Projects → naman03malhotra → match-rules

naman03malhotra / match-rules

Licence: MIT License
A tiny 1kB zero dependency JavaScript utility that lets you write your conditional business logic in a declarative way (React like).

Programming Languages

javascript
184084 projects - #8 most used programming language

Projects that are alternatives of or similar to match-rules

ObservableComputations
Cross-platform .NET library for computations whose arguments and results are objects that implement INotifyPropertyChanged and INotifyCollectionChanged (ObservableCollection) interfaces.
Stars: ✭ 94 (+141.03%)
Mutual labels:  declarative, declarative-programming
wybe
A programming language supporting most of both declarative and imperative programming
Stars: ✭ 34 (-12.82%)
Mutual labels:  declarative-programming, imperative-programming
react-declarative
A React form builder which interacts with a JSON endpoint to generate nested 12-column grids with input fields and automatic state management in a declarative style. Endpoint is typed by TypeScript guards (IntelliSense available). This tool is based on material-ui components, so your application will look beautiful on any device...
Stars: ✭ 17 (-56.41%)
Mutual labels:  declarative, declarative-programming
sass-build
GitHub Action JavaScript wrapper runs Sass build with provided Inputs
Stars: ✭ 16 (-58.97%)
Mutual labels:  javascript-utility
conditional-expression
JavaScript functional conditional expression
Stars: ✭ 63 (+61.54%)
Mutual labels:  npm-module
express-mvc-generator
Express' Model View Controller Application Generator.
Stars: ✭ 46 (+17.95%)
Mutual labels:  npm-module
vue-pseudo-window
🖼 Declaratively interface window/document/body in your Vue template
Stars: ✭ 28 (-28.21%)
Mutual labels:  declarative
picsort
Organize your photos by date in one click 👏
Stars: ✭ 22 (-43.59%)
Mutual labels:  npm-module
recurse
re<urse is a declarative language for generating musical patterns
Stars: ✭ 32 (-17.95%)
Mutual labels:  declarative
pip
Pip: an imperative code-golf language
Stars: ✭ 22 (-43.59%)
Mutual labels:  imperative
react-innertext
Returns the innerText of a React JSX object.
Stars: ✭ 37 (-5.13%)
Mutual labels:  npm-module
metagraf
metaGraf is a opinionated specification for describing a software component and what its requirements are from the runtime environment. The mg command, turns metaGraf specifications into Kubernetes resources, supporting CI, CD and GitOps software delivery.
Stars: ✭ 15 (-61.54%)
Mutual labels:  declarative
Quote-of-the-Day
📖 Delivers the Quote of the Day to your terminal
Stars: ✭ 20 (-48.72%)
Mutual labels:  npm-module
Helm
A graph-based SwiftUI router
Stars: ✭ 64 (+64.1%)
Mutual labels:  declarative
harbor-master
🛥️ Harbor Master is a Docker Remote API client written in Node.js
Stars: ✭ 56 (+43.59%)
Mutual labels:  npm-module
mypluralize
A Node.js module that returns the plural or singular form of any noun
Stars: ✭ 13 (-66.67%)
Mutual labels:  npm-module
midtrans-node
Unoffficial Midtrans Payment API Client for Node JS | Alternative for Midtrans Official Module | https://midtrans.com
Stars: ✭ 15 (-61.54%)
Mutual labels:  npm-module
recycler-adapter
RecyclerView-driven declarative UIs
Stars: ✭ 124 (+217.95%)
Mutual labels:  declarative
edd
Erlang Declarative Debugger
Stars: ✭ 20 (-48.72%)
Mutual labels:  declarative
babel-plugin-source-map-support
A Babel plugin which automatically makes stack traces source-map aware
Stars: ✭ 41 (+5.13%)
Mutual labels:  npm-module

match-rules

A tiny (1kB GZipped) zero dependency JavaScript utility that lets you write your conditional business logic in a declarative way (React like).

It can be used with feature flags, complex conditions, conditional rendering, and the rest is your imagination.

I wrote a detailed blog post, please do read it to understand the thought process in depth (5 mins tops).

Install

Build Status Build Status Coverage Status npm npm NPM npm DeepScan grade

npm bundle size

Through Yarn yarn add match-rules

Through npm npm install match-rules --save

Usage

ES6

import matchRules from "match-rules";

ES5

const matchRules = require("match-rules").default;

TypeScript

import matchRules from "match-rules";

API

// returns a boolean value.
matchRules(
  sourceObject, // can be any object with data.
  RULES_OBJECT, // you can also pass multiple rules in an array [RULE_ONE, RULE_TWO],
  options // (optional)
);

const options = {
  operator: "and", // (optional, default: 'and') in case of multiple rules you can pass 'and' or 'or'. In case of 'or' your rules will be compared with 'or' operator. Default is 'and'
  debug: true, // (optional, default: false) when debug is true, it logs a trace object which will tell you which rule failed and with what values of source and rules object.
};

// NOTE: all the rules inside a single rule are concatenated by 'and' operator.

Live Playground

Live Example on Stackblitz

Server Side

This module can be used with Node as well.

Example (Usecase)

// rules object
import matchRules from "match-rules";

const SHOW_JOB_RULE = {
  hasVisa: true,
  profile: {
    country: "US",
    yearsOfExperience: (exp, sourceObject) => exp > 3,
  },
};

// source object
const user = {
  username: "someName",
  hasVisa: true,
  profile: {
    country: "US",
    yearsOfExperience: 5,
    yearOfGraduation: 2011,
  },
};

// pass source and rules
if (matchRules(user, SHOW_JOB_RULE)) {
  //... do something conditionally.
}

Features

  • Multiple rules support - you can pass multiple rules dealing with a common source object.

  • Graceful exit - returns false if the asked property in the rule doesn’t exist in the source object.

  • Debugging - when enabled logs a trace object for all the keys in the rule with a meaningful message of what went wrong.

  • Function support - you can pass custom functions in the rule for handling complex conditions.

  • Nested Rules - you can pass a rule no matter how deep your data source is.

  • Multiple operator support - you can pass or / and operators in case of multiple rules. Dillinger is a cloud-enabled, mobile-ready, offline-storage, AngularJS powered HTML5 Markdown editor.

Why would you want to use it in your projects.

  • Reduces cognitive complexity.

  • Easy to maintain, declarative in nature.

  • Code is more readable, you can separate conditional logic in a rules.js file.

  • You do not have to write unit tests for those conditions individually, just take a snapshot at max.

  • Reduce code redundancy (you can compose and extend rules).

  • You do not have to traverse nested objects, just write your rules with the same structure.

  • Any conditional (complex) case can be handled using a function.

  • Easily manage your AB testing logic.

Function Support:

So far it is capable to handle any condition since you can write your own functions in the rule.

when it encounters a function it passes the value (as the first parameter) and original source object (as the second parameter) from the source to that function matching the corresponding key of that level.

Using a combination of key's value and original source object you can handle complex conditions.

For example:

const SHOW_ADS_RULES = {
  profile: {
    age: (value, sourceObject) =>
      value > 18 && value < 55 && sourceObject.admin === true,
  },
};

const source = {
  profile: {
    age: 20,
  },
  admin: true,
};

// so value of 20 (First param) and complete source object (Second Param) will be passed to that function.
// NOTE: you should always return boolean value from the function you implement.

Extending Rules (avoid redundancy)

const SHOW_ADS_RULES = {
  onboarding: true,
  admin: false,
  profile: {
    country: "US",
    school: "MIT",
    age: (value) => value > 18 && value < 55,
  },
};

// show a different Ad if the country is India.
const SHOW_ADS_RULES_INDIA = {
  ...SHOW_ADS_RULES,
  profile: {
    ...SHOW_ADS_RULES.profile,
    country: "India",
  },
};

More examples

Ex 1. Feature Flags

import matchRules from "match-rules";

// this object can come from your app state
const sourceObject = {
  enable_unique_feature: true,
  when_user_is_admin: true,
  age_more_than_18: 25,
};

// Rule
const ENABLE_UNIQUE_FEATURE = {
  enable_unique_feature: true,
  when_user_is_admin: true,
  age_more_than_18: (value, sourceObject) => value > 18,
};

if (matchRules(sourceObject, ENABLE_UNIQUE_FEATURE)) {
  // render unique feature
}

Ex 2. Multiple Rules and functions implementation

import matchRules from "match-rules";

// this object can come from your app state
const sourceObject = {
  enable_unique_feature: true,
  profile: {
    age: 18,
  },
};

// Rules
const ENABLE_UNIQUE_FEATURE = {
  enable_unique_feature: true,
};

const ENABLE_UNIQUE_FEATURE_WITH_AGE_18YO = {
  profile: {
    age: (value, sourceObject) => value > 18,
  },
};

// by default multiple rules will be combined using AND operator
if (
  matchRules(sourceObject, [
    ENABLE_UNIQUE_FEATURE,
    ENABLE_UNIQUE_FEATURE_WITH_AGE_18YO,
  ])
) {
  // render unique feature
}

Ex 3. Multiple Rules using OR operator

import matchRules from "match-rules";

// this object can come from your app state
const sourceObject = {
  enable_unique_feature: true,
  profile: {
    age: 18,
    country: "US",
  },
};

// Rules
const ENABLE_UNIQUE_FEATURE_FOR_US = {
  profile: {
    country: "US",
  },
};

const ENABLE_UNIQUE_FEATURE_FOR_INDIA = {
  profile: {
    country: "IN",
  },
};

// to combine rules using OR, (display feature if user is from US or INDIA)
if (
  matchRules(
    sourceObject,
    [ENABLE_UNIQUE_FEATURE_FOR_US, ENABLE_UNIQUE_FEATURE_FOR_INDIA],
    { operator: "or" }
  )
) {
  // render unique feature
}

// you can pass as many rules you want

Ex 4. using functions

import matchRules from "match-rules";

// this object can come from your app state
const sourceObject = {
  enable_unique_feature: true,
  profile: {
    age: 18,
    country: "US",
  },
};

// Rules
const ENABLE_UNIQUE_FEATURE_FOR_US_OR_INDIA = {
  profile: {
    country: (value, sourceObject) => value === "US" || value === "IN",
  },
};

// to combine rules using OR, (display feature if user is from US or INDIA)
if (matchRules(sourceObject, ENABLE_UNIQUE_FEATURE_FOR_US_OR_INDIA)) {
  // render unique feature
}

// you can use functions to deal with complex scenarios

Ex 5. Rule for deep source objects

import matchRules from "match-rules";

// this object can come from your app state
const sourceObject = {
  enable_unique_feature: true,
  userData: {
    personalData: {
      profile: {
        age: 18,
        country: "US",
      },
    },
  },
};

// Rules
const ENABLE_UNIQUE_FEATURE_FOR_US_OR_INDIA = {
  userData: {
    personalData: {
      profile: {
        country: (value, sourceObject) => value === "US" || value === "IN",
      },
    },
  },
};

// to combine rules using OR, (display feature if user is from US or INDIA)
if (matchRules(sourceObject, ENABLE_UNIQUE_FEATURE_FOR_US_OR_INDIA)) {
  // render unique feature
}
// you can use functions to deal with complex scenarios

Ex 6. Dynamic Rules | RULES as functions

// example where you have to match the dynamically generated rule
const dataFromServer = {
  user_id: 123, // created by user
  item_id: '87df83b',
  item_type: 'Post'
}

const userSource = {
  id: 123,
}

const DYNAMIC_USER_RULE = (itemCreatedByUserParam) => {
  return {
    id: itemCreatedByUserParam.user_id,
  }
}

if(matchRules(userSource, DYNAMIC_USER_RULE(dataFromServer)) {
 // show edit option to creator of this post
}

Debugging

when enabled logs a trace object for all the keys in the rule with a meaningful message of what went right and wrong.

matchRules(sourceObject, RULES, { debug: true })

// sample trace object
{
  "0": {
    "company": {
      "enable_feature": {
        "enable_feature_for_user": {
          "value": true,
          "message": "Value equated for the given rule, Rule data: true (type: boolean), Source data: true (type: boolean)"
        }
      },
      "enable_people_management": {
        "value": true,
        "message": "Value equated for the given rule, Rule data: true (type: boolean), Source data: true (type: boolean)"
      }
    },
    "company_admin": {
      "value": true,
      "message": "Value equated for the given rule, Rule data: true (type: boolean), Source data: true (type: boolean)"
    },
    "enable_special_feature": {
      "value": true,
      "message": "Value equated for the given rule, Rule data: false (type: boolean), Source data: false (type: boolean)"
    },
    "temp": {
      "value": false,
      "message": "Function was executed for the given rule with value: 3 (type: number)"
    }
  }
}

Development

For development, please make the changes in the code and write appropriate unit test case. Feel free to send across a Pull Request if it doesn't fit your use-case.

Zero dependency library

match-rules does not have any dependency, it is just 1kB (GZipped) in size.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].