Skip to content

crafts69guy/rule-engine-js

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

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

  • 🎯 Zero Dependencies - Lightweight with no external dependencies
  • ⚑ High Performance - Intelligent caching with LRU eviction
  • πŸ”’ Security First - Built-in protection against prototype pollution
  • 🧩 Dynamic Field Comparison - Compare values across different data paths
  • πŸ“ˆ Stateful Rule Engine - Track state changes with event-driven architecture
  • πŸ”„ State Change Operators - Built-in operators for detecting value changes
  • πŸ“ Type Safe - Full TypeScript support with comprehensive type definitions
  • πŸ”§ Extensible - Easy custom operator registration
  • πŸ“Š Monitoring - Built-in performance metrics and cache statistics

πŸ“¦ Installation

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

🎯 Quick Start

import { createRuleEngine, createRuleHelpers, StatefulRuleEngine } 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
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
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
const validationRule = rules.and(
  rules.validation.required('firstName'),
  rules.validation.required('lastName'),
  rules.validation.email('email'),
  rules.validation.ageRange('age', 18, 120),
  rules.validation.minLength('password', 8),
  rules.validation.maxLength('username', 20),
  rules.field.equals('password', 'confirmPassword'),
  rules.isTrue('agreedToTerms')
);

const formData = {
  firstName: 'John',
  lastName: 'Doe',
  email: 'john@example.com',
  age: 25,
  username: 'johndoe',
  password: 'secret123',
  confirmPassword: 'secret123',
  agreedToTerms: true,
};

const isValid = engine.evaluateExpr(validationRule, formData);
🏦 Loan Approval Logic
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
State Change changed, changedBy, changedFrom, changedTo, increased, decreased Detect state changes between evaluations
Validation email, required, ageRange, oneOf, minLength, maxLength, lengthRange, exactLength Common validation patterns

πŸ”„ Stateful Rule Engine

The StatefulRuleEngine extends the base engine with state tracking and event-driven capabilities, perfect for monitoring data changes and triggering actions based on state transitions.

Key Features

  • State Tracking: Maintains previous states for comparison
  • Event System: Listen to rule state changes with events
  • State Change Operators: Specialized operators for detecting changes
  • History Management: Optional evaluation history storage
  • Flexible Triggering: Configure when rules should trigger

Basic Usage

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

// Create base engine and wrap with StatefulRuleEngine
const baseEngine = createRuleEngine();
const statefulEngine = new StatefulRuleEngine(baseEngine, {
  triggerOnEveryChange: false, // Trigger only on false β†’ true transitions
  storeHistory: true, // Keep evaluation history
  maxHistorySize: 100, // Limit history entries
});

// Listen for events
statefulEngine.on('triggered', (event) => {
  console.log(`Rule ${event.ruleId} was triggered!`);
});

statefulEngine.on('changed', (event) => {
  console.log(`Rule ${event.ruleId} state changed`);
});

// Define rules with state change operators
const temperatureAlert = {
  and: [
    { gte: ['temperature', 25] },
    { increased: ['temperature'] }, // Only trigger when temperature increases
  ],
};

const statusChanged = { changedFrom: ['user.status', 'pending'] };

// Evaluate rules with state tracking
let data = { temperature: 20, user: { status: 'pending' } };
statefulEngine.evaluate('temp-rule', temperatureAlert, data);

// Update data and evaluate again
data = { temperature: 26, user: { status: 'active' } };
const result = statefulEngine.evaluate('temp-rule', temperatureAlert, data);
// This will trigger since temperature increased and is now >= 25

const statusResult = statefulEngine.evaluate('status-rule', statusChanged, data);
// This will trigger since status changed from 'pending' to 'active'

State Change Operators

Operator Description Example
changed Detects any value change { changed: ['user.email'] }
changedBy Detects numeric change by amount { changedBy: ['score', 10] }
changedFrom Detects change from specific value { changedFrom: ['status', 'pending'] }
changedTo Detects change to specific value { changedTo: ['status', 'completed'] }
increased Detects numeric increase { increased: ['temperature'] }
decreased Detects numeric decrease { decreased: ['stock'] }

Event Types

  • triggered: Rule transitioned from false β†’ true
  • untriggered: Rule transitioned from true β†’ false
  • changed: Rule success state changed
  • evaluated: Every rule evaluation (regardless of result)

Real-World Example: Order Processing

const orderRules = {
  'payment-received': { changedTo: ['order.paymentStatus', 'paid'] },
  'inventory-low': {
    and: [{ decreased: ['product.stock'] }, { lte: ['product.stock', 10] }],
  },
  'price-drop': {
    and: [{ decreased: ['product.price'] }, { changedBy: ['product.price', 5] }],
  },
};

// Set up event handlers
statefulEngine.on('triggered', (event) => {
  switch (event.ruleId) {
    case 'payment-received':
      processOrder(event.context);
      break;
    case 'inventory-low':
      reorderStock(event.context.product);
      break;
    case 'price-drop':
      notifyCustomers(event.context.product);
      break;
  }
});

// Batch evaluate all rules
const orderData = {
  order: { paymentStatus: 'paid' },
  product: { stock: 8, price: 95 },
};

statefulEngine.evaluateBatch(orderRules, orderData);

⚑ Performance Features

  • LRU Caching: Expression results and path resolutions are cached
  • Regex Compilation: Patterns are compiled once and reused
  • Metrics Tracking: Monitor performance with built-in metrics
  • Bundle Optimization: Multiple output formats (UMD, ESM, CommonJS)
// 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

  • Prototype Pollution Protection: Automatically blocks dangerous paths
  • Function Access Prevention: Functions are blocked by default
  • Safe Path Resolution: Only accesses own properties
  • Configurable Security: Adjust security settings as needed
// 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
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
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'),
  username: rules.validation.lengthRange('username', 3, 20),
  password: rules.and(
    rules.validation.minLength('password', 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

  • Inspired by business rule engines and decision tables
  • Built for modern JavaScript applications
  • Designed with security and performance in mind

πŸ“š Related Projects

  • JSON Schema - For data validation
  • Joi - Object schema validation
  • Yup - Schema builder for runtime value parsing

πŸ“– Read the Full Documentation | πŸš€ View Examples | πŸ’¬ Get Support

Made with ❀️ by Crafts69Guy