A powerful, flexible JavaScript rule engine for dynamic business logic evaluation. Build complex conditional logic with simple, readable syntax.
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);
npm install rule-engine-js
yarn add rule-engine-js
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
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'));
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')
);
Access nested data with dot notation:
const user = {
profile: {
settings: {
theme: 'dark',
notifications: true,
},
},
};
const rule = rules.eq('profile.settings.theme', 'dark');
| 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 |
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.
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'
| 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'] } |
triggered: Rule transitioned from false โ trueuntriggered: Rule transitioned from true โ falsechanged: Rule success state changedevaluated: Every rule evaluation (regardless of result)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);
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
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);
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');
// 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);
// 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
});
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')
);
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);
});
});
| 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
We welcome contributions! Please see our Contributing Guide for details.
git checkout -b feature/amazing-feature)npm test)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)This project is licensed under the MIT License - see the LICENSE file for details.
| ๐ Read the Full Documentation | ๐ View Examples | ๐ฌ Get Support |
Made with โค๏ธ by Crafts69Guy