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
  • πŸ’Ύ State Management - TTL-based expiration, deep copy protection, listener management
  • βš™οΈ Concurrency Control - Automatic queue management with timeout protection
  • πŸ” Error Recovery - Retry strategies, circuit breaker, and fallback mechanisms
  • πŸ“ 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);

πŸš€ Phase 3 Production Features

State Management (Phase 3.1)

Advanced memory management and resource cleanup:

const statefulEngine = new StatefulRuleEngine(baseEngine, {
  // State TTL and automatic cleanup
  stateExpirationMs: 3600000, // 1 hour TTL
  cleanupIntervalMs: 300000, // Cleanup every 5 minutes

  // Context protection
  enableDeepCopy: true, // Prevent mutation (handles circular refs)

  // Listener management
  maxListeners: 100, // Warn on high listener counts
});

// Monitor memory usage
const stats = statefulEngine.getStateStats();
console.log('Memory estimate:', stats.memoryEstimate);

// Manual cleanup
const result = statefulEngine.cleanupExpiredStates();
console.log('Removed:', result.removedCount, 'expired states');

// Graceful shutdown
await statefulEngine.destroy(); // Cleanup timers, listeners, state

Concurrency Control (Phase 3.2)

Manage concurrent evaluations with automatic queue management:

const statefulEngine = new StatefulRuleEngine(baseEngine, {
  concurrency: {
    maxConcurrent: 10, // Max concurrent evaluations per rule
    timeout: 30000, // 30 second timeout

    onTimeout: (ruleId) => {
      console.error(`Rule ${ruleId} timed out`);
    },

    onQueueFull: (ruleId, queueSize) => {
      console.warn(`Queue full for ${ruleId}: ${queueSize} pending`);
    },
  },
});

// All evaluation methods are now async
const result = await statefulEngine.evaluate('rule-1', rule, context);

// Monitor concurrency
const stats = statefulEngine.getConcurrencyStats();
console.log('Active evaluations:', stats);

Error Recovery (Phase 3.3)

Comprehensive error handling with retry, circuit breaker, and fallback strategies:

const statefulEngine = new StatefulRuleEngine(baseEngine, {
  errorRecovery: {
    // Retry with exponential backoff
    retry: {
      enabled: true,
      maxAttempts: 3,
      strategy: 'exponential', // 'exponential', 'fixed', 'linear'
      initialDelay: 100,
      maxDelay: 5000,

      onRetry: (attempt, error, ruleId) => {
        console.log(`Retry ${attempt}/${3} for ${ruleId}`);
      },
    },

    // Circuit breaker pattern
    circuitBreaker: {
      enabled: true,
      failureThreshold: 5, // Open after 5 failures
      resetTimeout: 60000, // Try again after 1 minute

      onCircuitOpen: (ruleId, info) => {
        console.error(`Circuit opened for ${ruleId}`, info);
      },
    },

    // Fallback strategies
    fallback: {
      enabled: true,
      defaultValue: { success: false, fallback: true },

      onFallback: (ruleId, type, value) => {
        console.log(`Using ${type} fallback for ${ruleId}`);
      },
    },
  },
});

// Register fallback rules
statefulEngine.registerFallbackRule('primary-rule', fallbackRule);
statefulEngine.registerFallbackValue('backup-rule', { safe: true });

// Monitor error rates
const errorRate = statefulEngine.getErrorRate('rule-1');
if (errorRate && errorRate.rate > 0.1) {
  console.warn('High error rate:', errorRate);
}

// Check circuit state
const state = statefulEngine.getCircuitState('rule-1');
// Returns: 'closed', 'open', or 'half-open'

// Manual circuit reset
statefulEngine.resetCircuit('rule-1');

⚑ 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