@arunkumar_h/rule-engine
Breaking Change: Please move to v3.1.0 or later.
A lightweight and extensible rule engine built with TypeScript and Node.js. Define complex business rules and evaluate conditions easily using a simple JSON structure.
📦 Installation
npm install @arunkumar_h/rule-engine
yarn add @arunkumar_h/rule-engine
🧠 Features
- ✅ Logical condition support (and, or, nested expressions)
- 🔧 Custom operators and named conditions
- 📜 Fully typed with TypeScript
- 🚀 Lightweight and dependency-aware
- 🔎 Native JMESPath support for data querying
- 🧰 Built-in caching using
lru-cache
for better performance
Feature / Capability | @arunkumar_h/rule-engine |
---|---|
✅ Written in TypeScript | ✅ Native TypeScript with full type safety |
⚙️ Custom Operators | ✅ Built-in support, sync or async |
🧠 Named Conditions | ✅ Supports reusable named conditions |
🧱 Nested Logical Trees | ✅ Fully supported (and, or, deeply nested) |
🔍 Data Query Language | ✅ Built-in JMESPath support |
🚀 Performance Optimizations | ✅ Rule-level cache with lru-cache |
🧰 Extensibility | ✅ Add custom operators, conditions dynamically |
⚖️ Lightweight | ✅ Small and focused build |
🧪 Testing Coverage Ready | ✅ Easy to unit test each rule block |
🔁 Dynamic Rule Loading | ✅ Add/modify rules at runtime |
🔄 Async Support | ✅ Full async engine and operators |
📦 Modern Packaging | ✅ ESM + CJS + .d.ts types out of the box |
⚙️ Default Operators
The following operators are available by default:
Operator | Description |
---|---|
=== | Strict equality |
!== | Strict inequality |
== | Loose equality |
!= | Loose inequality |
> | Greater than |
>= | Greater than or equal to |
< | Less than |
<= | Less than or equal to |
%like | Starts with |
like% | Ends with |
%like% | Contains |
in | Value is in the array |
!in | Value is not in the array |
includes | Array includes value |
!includes | Array does not include value |
🔨 Basic Usage
condition
This containesand
andor
as main block.onSuccess
value that will be returned or function that will be invoked if the condition is satisfied.onFail
value that will be returned or function that will be invoked if the condition fails.cache
as default this will be set totrue
and can be disabled for rule wisefalse
import { Engine } from "@arunkumar_h/rule-engine";
const engineObj = new Engine();
const rule = {
testRule: {
condition: {
and: [
{ path: "age", operator: "!==", value: 10 },
{
and: [
{ path: "age", operator: ">", value: 15 },
{
or: [
{ path: "age", operator: "!==", value: 30 },
{ path: "skills", operator: "includes", value: "ts" },
],
},
],
},
{ path: "language", operator: "in", value: ["tamil", "english"] },
],
},
onSuccess: (fact, ruleName) => "Success", // onSuccess: { id: 23 }
onFail: (fact, ruleName) => "Fail", // onFail: "Error"
cache: false, // default will be true
}
};
engine.addRule(rule);
const fact = {age: 16, skills: ["ts", "php"], language: "tamil"}; // Your data to be validated
const result = await engineObj.run(fact, "testRule");
🔧 Custom Operator Example
engine.addOperator({
isEven: (factValue) => factValue % 2 === 0,
});
const rule = {
evenCheck: {
condition: {
and: [
{ path: "number", operator: "isEven" },
],
},
onSuccess: "Number is even",
onFail: "Number is odd",
},
};
const result = await engine.run({ number: 8 }, "evenCheck");
🔍 API Overview
flowchart TB
Rule --> onSuccess
Rule --> onFail
Rule --> Condition --> AND --> Operation
Condition --> OR --> Operation
Engine API
let engine = new Engine()
addRule({ rule1, rule2, ... })
- Add named rules dynamically.
addCondition({ condition1, condition2, ... })
- Add reusable named conditions.
- Conditions can reference other named conditions.
addOperator({ customOperator1, customOperator2, ... })
- Add custom (sync or async) operators.
run(fact, ruleName)
- Executes a given rule against the provided fact
⚡ Advanced Usage
- Adding named conditions.
- Adding named operators.
- Rule wise cache disabling.
import { Engine } from "@arunkumar_h/rule-engine";
const engineObj = new Engine();
const condition1 = {
condition1: {
and: [
{ path: "age", operator: "!==", value: 10 },
{
and: [
{ path: "age", operator: ">", value: 15 },
{
or: [
{ path: "age", operator: "!==", value: 30 },
{ path: "skills", operator: "includes", value: "ts" },
],
},
],
},
{ path: "language", operator: "in", value: ["tamil", "english"] },
],
}
};
engine.addCondition(condition1); // adding named condition
const rule = {
testRule: {
condition: "condition1", // Using named condition
onSuccess: "Success", // can be a function or a data
onFail: "Fail", // can be a function or a data
cache: false // disable cache for this rule
}
};
engine.addRule(rule);
const fact = {age: 16, skills: ["ts", "php"], language: "tamil"}; // Your data to be validated
const result = await engineObj.run(fact, "testRule");
🧪 Test Coverage
Badges above represent live coverage stats for:
Author
Arunkumar H
📄 License
- Code: Licensed under the MIT License
- Assets & Documentation: Licensed under the CC BY-SA 4.0 License
Some non-code content (e.g. diagrams, images, markdown docs) is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
See https://creativecommons.org/licenses/by-sa/4.0/ for more info.
The detailed list of Open Source dependencies can be found in Fossa report.