rule-engine-js

Rule Engine JS

npm version License: MIT Build Status Coverage Status

A powerful, flexible JavaScript rule engine for dynamic business logic evaluation. Build complex conditional logic with simple, readable syntax.

๐Ÿš€ Why Rule Engine JS?

Stop hardcoding business logic. Rule Engine JS lets you define complex conditional logic as data, making your applications more flexible, maintainable, and business-friendly.

// Instead of this hardcoded logic...
if (user.age >= 18 && user.role === 'admin' && user.permissions.includes('write')) {
  return true;
}

// Write this declarative rule...
const rule = rules.and(
  rules.gte('age', 18),
  rules.eq('role', 'admin'),
  rules.in('write', 'permissions')
);

const result = engine.evaluateExpr(rule, user);

โœจ Key Features

๐Ÿ“ฆ Installation

npm install rule-engine-js
yarn add rule-engine-js

๐ŸŽฏ Quick Start

import { createRuleEngine, createRuleHelpers } from 'rule-engine-js';

// Create engine and helpers
const engine = createRuleEngine();
const rules = createRuleHelpers();

// Your data
const user = {
  name: 'John Doe',
  age: 28,
  role: 'admin',
  email: 'john@company.com',
  permissions: ['read', 'write', 'delete'],
};

// Simple rule
const isAdult = rules.gte('age', 18);
console.log(engine.evaluateExpr(isAdult, user).success); // true

// Complex rule
const canAccess = rules.and(
  rules.gte('age', 18),
  rules.eq('role', 'admin'),
  rules.validation.email('email'),
  rules.in('write', 'permissions')
);

console.log(engine.evaluateExpr(canAccess, user).success); // true

๐Ÿ—๏ธ Core Concepts

Rules are Data

Rules are simple JSON objects that describe conditions:

// This rule...
const rule = { and: [{ gte: ['age', 18] }, { eq: ['role', 'admin'] }] };

// Is equivalent to this helper syntax...
const rule = rules.and(rules.gte('age', 18), rules.eq('role', 'admin'));

Dynamic Field Comparison

Compare values from different paths in your data:

const formData = {
  password: 'secret123',
  confirmPassword: 'secret123',
  score: 85,
  maxScore: 100,
};

const rule = rules.and(
  rules.field.equals('password', 'confirmPassword'),
  rules.lt('score', 'maxScore')
);

Path Resolution

Access nested data with dot notation:

const user = {
  profile: {
    settings: {
      theme: 'dark',
      notifications: true,
    },
  },
};

const rule = rules.eq('profile.settings.theme', 'dark');

๐Ÿ› ๏ธ Common Use Cases

๐Ÿ” User Access Control ```javascript const accessRule = rules.and( // User must be active rules.isTrue('user.isActive'), // Either admin OR (department match AND has permission) rules.or( rules.eq('user.role', 'admin'), rules.and( rules.field.equals('user.department', 'resource.department'), rules.in('write', 'user.permissions') ) ) ); const context = { user: { isActive: true, role: 'editor', department: 'engineering', permissions: ['read', 'write'], }, resource: { department: 'engineering' }, }; const hasAccess = engine.evaluateExpr(accessRule, context); ```
๐Ÿ’ฐ Dynamic Pricing & Discounts ```javascript const discountRule = rules.or( // VIP customers with minimum order rules.and(rules.eq('customer.type', 'vip'), rules.gte('order.total', 100)), // High loyalty points rules.gte('customer.loyaltyPoints', 1000), // Large orders rules.gte('order.total', 200) ); const orderData = { customer: { type: 'vip', loyaltyPoints: 500 }, order: { total: 150 }, }; const eligible = engine.evaluateExpr(discountRule, orderData); ```
๐Ÿ“ Form Validation ```javascript const validationRule = rules.and( rules.validation.required('firstName'), rules.validation.required('lastName'), rules.validation.email('email'), rules.validation.ageRange('age', 18, 120), rules.field.equals('password', 'confirmPassword'), rules.isTrue('agreedToTerms') ); const formData = { firstName: 'John', lastName: 'Doe', email: 'john@example.com', age: 25, password: 'secret123', confirmPassword: 'secret123', agreedToTerms: true, }; const isValid = engine.evaluateExpr(validationRule, formData); ```
๐Ÿฆ Loan Approval Logic ```javascript const approvalRule = rules.and( rules.gte('applicant.creditScore', 650), rules.gte('applicant.income', 50000), rules.lte('applicant.debtRatio', 0.4), rules.gte('applicant.employmentYears', 2), rules.between('applicant.age', [18, 70]), rules.in('loan.purpose', ['home', 'car', 'education']) ); const application = { applicant: { creditScore: 720, income: 75000, debtRatio: 0.25, employmentYears: 3, age: 32, }, loan: { amount: 250000, purpose: 'home', }, }; const approved = engine.evaluateExpr(approvalRule, application); ```

๐Ÿ“š Available Operators

Category Operators Description
Comparison eq, neq, gt, gte, lt, lte Compare values with type coercion support
Logical and, or, not Combine multiple conditions
String contains, startsWith, endsWith, regex Text pattern matching
Array in, notIn Check membership in arrays
Special between, isNull, isNotNull Range and null checking
Validation email, required, ageRange, oneOf Common validation patterns

โšก Performance Features

// Get performance metrics
const metrics = engine.getMetrics();
console.log({
  evaluations: metrics.evaluations,
  cacheHits: metrics.cacheHits,
  avgTime: metrics.avgTime,
});

// Get cache statistics
const cacheStats = engine.getCacheStats();
console.log(cacheStats);

๐Ÿ”’ Security Features

// Secure by default
const maliciousData = { __proto__: { isAdmin: true } };
engine.resolvePath(maliciousData, '__proto__.isAdmin'); // Returns undefined

// Configure security
const engine = createRuleEngine({
  allowPrototypeAccess: false, // Always false in production
  strict: true, // Enable strict type checking
  maxDepth: 10, // Prevent deep recursion
  maxOperators: 100, // Limit complexity
});

๐ŸŽจ Custom Operators

Extend the engine with your own business logic:

// Register business-specific logic
engine.registerOperator('isBusinessHours', (args, context) => {
  const [timezone = 'UTC'] = args;
  const now = new Date();
  const hour = now.getUTCHours();
  return hour >= 9 && hour < 17; // 9 AM to 5 PM UTC
});

// Usage
const rule = rules.and(
  { isBusinessHours: ['America/New_York'] },
  rules.isTrue('support.available')
);

๐Ÿ“– Documentation

๐Ÿš€ Framework Integration

Express.js Middleware ```javascript import { createRuleEngine, createRuleHelpers } from 'rule-engine-js'; const engine = createRuleEngine(); const rules = createRuleHelpers(); function createAccessMiddleware(accessRule) { return (req, res, next) => { const result = engine.evaluateExpr(accessRule, req.user); if (result.success) { next(); } else { res.status(403).json({ error: 'Access denied' }); } }; } // Usage const adminRule = rules.eq('role', 'admin'); app.get('/admin/*', createAccessMiddleware(adminRule)); ```
React Form Validation ```javascript import { createRuleEngine, createRuleHelpers } from 'rule-engine-js'; const engine = createRuleEngine(); const rules = createRuleHelpers(); function useFormValidation(validationRules) { const validateForm = (formData) => { const results = {}; Object.entries(validationRules).forEach(([field, rule]) => { const result = engine.evaluateExpr(rule, formData); results[field] = { isValid: result.success, error: result.success ? null : result.error, }; }); return results; }; return { validateForm }; } // Usage const validationRules = { email: rules.validation.email('email'), password: rules.and( rules.gte('password.length', 8), rules.regex('password', '(?=.*[0-9])(?=.*[a-zA-Z])') ), }; ```

๐Ÿงช Testing

import { createRuleEngine, createRuleHelpers } from 'rule-engine-js';

describe('User Access Rules', () => {
  const engine = createRuleEngine();
  const rules = createRuleHelpers();

  test('admin has full access', () => {
    const user = { role: 'admin' };
    const rule = rules.eq('role', 'admin');

    const result = engine.evaluateExpr(rule, user);
    expect(result.success).toBe(true);
  });
});

๐Ÿ“Š Benchmarks

Operation Speed Cache Hit Rate
Simple equality ~0.1ms 95%
Complex nested rules ~2ms 85%
Regex operations ~0.5ms 90%
Path resolution ~0.05ms 98%

Benchmarks run on Node.js 18, Intel i7, with 1000 rule evaluations

๐Ÿค Contributing

We welcome contributions! Please see our Contributing Guide for details.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Write tests for your changes
  4. Ensure all tests pass (npm test)
  5. Commit your changes (git commit -m 'Add amazing feature')
  6. Push to the branch (git push origin feature/amazing-feature)
  7. Open a Pull Request

๐Ÿ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

๐Ÿ™ Acknowledgments


๐Ÿ“– Read the Full Documentation ๐Ÿš€ View Examples ๐Ÿ’ฌ Get Support

Made with โค๏ธ by Crafts69Guy