Transform complex business logic into elegant, maintainable JSON rules. Stop hardcoding decisions, start building intelligent systems.
// From this mess...
if (user.tier === 'vip' && order.total > 100 && user.country === 'US') {
return { discount: 0.20, shipping: 'free' };
} else if (user.isNew && order.total > 50) {
return { discount: 0.10, shipping: 'standard' };
} // ... 50 more lines
// To this elegance...
const result = await RuleEngine.evaluate(discountRules, { user, order });
Built for Modern Developers
- 🎯 Zero Dependencies - No supply chain bloat, just pure JavaScript excellence
- 🏎️ Lightning Fast - 117,000+ evaluations per second
- 🛡️ TypeScript Native - Built-in generics for bulletproof type safety
- 🌐 Universal - Node.js, browsers, edge functions, Deno, Bun - everywhere JavaScript runs
Powerful Yet Intuitive
- 🔍 JSONPath Support - Navigate complex objects:
$.user.profile.settings.theme
- 🔗 Self-Referencing - Dynamic field references:
"value": "$.maxPrice"
- 🧩 121+ Operators - From basic comparisons to advanced pattern matching
- 🏗️ Fluent Builder - Construct rules programmatically with intuitive chains
Enterprise Ready
- 🔧 Extensible Core - Plugin custom operators without touching internals
- 📊 Rule Introspection - Reverse-engineer possible inputs from rule definitions
- ⚡ Performance Optimized - Optional validation bypass for trusted rules
- 🎭 Data Mutations - Preprocess data before evaluation
npm install @usex/rule-engine
import { RuleEngine } from '@usex/rule-engine';
// Define a discount rule
const discountRule = {
conditions: [
{
// VIP customers get 20% off orders over $100
and: [
{ field: "$.customer.tier", operator: "equals", value: "vip" },
{ field: "$.order.total", operator: "greater-than", value: 100 }
],
result: { discount: 0.20, message: "VIP discount applied! 🎉" }
},
{
// First-time buyers get 10% off orders over $50
and: [
{ field: "$.customer.orderCount", operator: "equals", value: 0 },
{ field: "$.order.total", operator: "greater-than", value: 50 }
],
result: { discount: 0.10, message: "Welcome! First order discount 🎁" }
}
],
default: { discount: 0, message: "No discount available" }
};
// Apply the rule
const orderData = {
customer: { tier: "vip", orderCount: 5 },
order: { total: 150, items: ["laptop", "mouse"] }
};
const result = await RuleEngine.evaluate(discountRule, orderData);
console.log(result);
// { value: { discount: 0.20, message: "VIP discount applied! 🎉" }, isPassed: true }
Every rule follows this pattern:
interface Rule<T = any> {
conditions: Condition<T> | Condition<T>[]; // What to check
default?: T; // Fallback result
}
interface Condition<T = any> {
and?: Array<Constraint | Condition<T>>; // ALL must match
or?: Array<Constraint | Condition<T>>; // ANY must match
none?: Array<Constraint | Condition<T>>; // NONE must match
result?: T; // What to return when matched
}
interface Constraint {
field: string; // Path to the data (supports JSONPath)
operator: string; // How to compare
value: any; // What to compare against
message?: string; // Optional validation message
}
Method | Description | Returns |
---|---|---|
RuleEngine.evaluate(rule, data, trustRule?) |
Full evaluation with metadata | Promise<EvaluationResult<T>> |
RuleEngine.checkIsPassed(rule, data, trustRule?) |
Quick boolean check | Promise<boolean> |
RuleEngine.getEvaluateResult(rule, data, trustRule?) |
Just the result value | Promise<T> |
RuleEngine.evaluateMany(rules, data, trustRule?) |
Batch evaluation | Promise<EvaluationResult<T>[]> |
RuleEngine.validate(rule) |
Validate rule structure | ValidationResult |
RuleEngine.introspect(rule) |
Analyze rule requirements | IntrospectionResult |
RuleEngine.builder() |
Get fluent builder | RuleBuilder |
const engine = new RuleEngine();
// All static methods available as instance methods
await engine.evaluate(rule, data);
// Basic string operations
{ field: "name", operator: "equals", value: "John" }
{ field: "email", operator: "like", value: "*@gmail.com" }
{ field: "description", operator: "matches", value: "^Product.*" }
// Validation
{ field: "email", operator: "email", value: true }
{ field: "url", operator: "url", value: true }
{ field: "uuid", operator: "uuid", value: true }
// Comparisons
{ field: "age", operator: "greater-than", value: 18 }
{ field: "price", operator: "between", value: [10, 100] }
// Types
{ field: "score", operator: "integer", value: true }
{ field: "rating", operator: "positive", value: true }
// Membership
{ field: "roles", operator: "contains", value: "admin" }
{ field: "status", operator: "in", value: ["active", "pending"] }
{ field: "tags", operator: "contains-all", value: ["urgent", "review"] }
{ field: "features", operator: "contains-any", value: ["premium", "beta"] }
// Date comparisons
{ field: "birthDate", operator: "date-after", value: "1990-01-01" }
{ field: "expiryDate", operator: "date-before-now", value: true }
{ field: "createdAt", operator: "date-between", value: ["2023-01-01", "2023-12-31"] }
// Time comparisons
{ field: "openTime", operator: "time-after", value: "09:00" }
{ field: "closeTime", operator: "time-before", value: "17:30" }
// Existence checks
{ field: "optional", operator: "exists", value: true }
{ field: "deprecated", operator: "not-exists", value: true }
// Null checks
{ field: "data", operator: "null-or-undefined", value: false }
{ field: "config", operator: "not-empty", value: true }
// String length
{ field: "password", operator: "min-length", value: 8 }
{ field: "username", operator: "max-length", value: 20 }
{ field: "code", operator: "string-length", value: 6 }
{ field: "description", operator: "length-between", value: [10, 500] }
const pricingRules = {
conditions: [
{
// Black Friday: 50% off everything
and: [
{ field: "$.event.name", operator: "equals", value: "black-friday" },
{ field: "$.event.active", operator: "equals", value: true }
],
result: {
discount: 0.50,
code: "BLACKFRIDAY50",
expires: "2024-11-30T23:59:59Z"
}
},
{
// Bulk orders: tiered discounts
or: [
{ field: "$.cart.quantity", operator: "greater-than", value: 50 },
{ field: "$.cart.value", operator: "greater-than", value: 1000 }
],
result: {
discount: 0.15,
code: "BULK15",
shipping: "free"
}
},
{
// New customer welcome
and: [
{ field: "$.customer.orderHistory.length", operator: "equals", value: 0 },
{ field: "$.cart.value", operator: "greater-than", value: 50 }
],
result: {
discount: 0.10,
code: "WELCOME10",
message: "Welcome! Enjoy 10% off your first order 🎉"
}
}
],
default: { discount: 0, message: "Regular pricing applies" }
};
const accessControlRules = {
conditions: [
{
// Super admin: full access
and: [
{ field: "role", operator: "equals", value: "super-admin" },
{ field: "status", operator: "equals", value: "active" }
],
result: {
permissions: ["read", "write", "delete", "admin"],
level: "unlimited",
expires: null
}
},
{
// Department manager: departmental access
and: [
{ field: "role", operator: "equals", value: "manager" },
{ field: "department", operator: "exists", value: true },
{ field: "$.session.loginTime", operator: "date-after-now", value: "-8h" }
],
result: {
permissions: ["read", "write"],
level: "department",
scope: "$.department",
expires: "$.session.loginTime + 8h"
}
},
{
// Regular user: read-only during business hours
and: [
{ field: "role", operator: "equals", value: "user" },
{ field: "$.currentTime", operator: "time-between", value: ["09:00", "17:00"] },
{ field: "$.currentTime", operator: "date-between", value: ["monday", "friday"] }
],
result: {
permissions: ["read"],
level: "limited",
expires: "17:00"
}
}
],
default: {
permissions: [],
level: "none",
message: "Access denied"
}
};
const registrationValidation = {
conditions: {
and: [
// Email validation with custom message
{
field: "email",
operator: "email",
value: true,
message: "Please enter a valid email address"
},
// Strong password requirements
{
and: [
{
field: "password",
operator: "min-length",
value: 8,
message: "Password must be at least 8 characters long"
},
{
field: "password",
operator: "matches",
value: ".*[A-Z].*",
message: "Password must contain at least one uppercase letter"
},
{
field: "password",
operator: "matches",
value: ".*[0-9].*",
message: "Password must contain at least one number"
}
]
},
// Age verification
{
field: "birthDate",
operator: "date-before",
value: "$.today - 18 years",
message: "You must be 18 or older to register"
},
// Terms acceptance
{
field: "acceptTerms",
operator: "equals",
value: true,
message: "You must accept our terms and conditions"
},
// Optional referral code validation
{
or: [
{ field: "referralCode", operator: "not-exists", value: true },
{ field: "referralCode", operator: "empty", value: true },
{
and: [
{ field: "referralCode", operator: "string-length", value: 8 },
{ field: "referralCode", operator: "alpha-numeric", value: true }
]
}
],
message: "Referral code must be 8 alphanumeric characters"
}
]
}
};
Compare fields against other fields dynamically:
const budgetRule = {
conditions: {
and: [
// Actual cost must not exceed budget
{
field: "$.project.actualCost",
operator: "less-than-or-equals",
value: "$.project.approvedBudget"
},
// Start date must be before end date
{
field: "$.project.startDate",
operator: "date-before",
value: "$.project.endDate"
},
// Team size appropriate for project scope
{
field: "$.project.teamSize",
operator: "greater-than-or-equals",
value: "$.project.minimumTeamSize"
}
]
}
};
Construct complex rules programmatically:
const complexRule = RuleEngine.builder()
.add({
and: [
{ field: "userType", operator: "equals", value: "premium" },
{ field: "subscriptionActive", operator: "equals", value: true }
],
result: { access: "premium", features: ["analytics", "api", "support"] }
})
.add({
and: [
{ field: "userType", operator: "equals", value: "basic" },
{ field: "trialExpired", operator: "equals", value: false }
],
result: { access: "basic", features: ["dashboard"] }
})
.default({ access: "none", features: [] })
.build(true); // Validate during build
Extend the engine with your own operators:
import { registerCustomOperator, OperatorCategory, BaseOperatorStrategy } from '@usex/rule-engine';
class CreditCardOperator extends BaseOperatorStrategy<string, void> {
readonly metadata = {
name: "credit-card",
displayName: "Credit Card Number",
category: OperatorCategory.PATTERN,
description: "Validates credit card numbers using Luhn algorithm",
acceptedFieldTypes: ["string"],
expectedValueType: "void",
requiresValue: false,
};
evaluate(context) {
const { fieldValue } = context;
return this.isValidCreditCard(fieldValue);
}
private isValidCreditCard(cardNumber: string): boolean {
// Luhn algorithm implementation
const digits = cardNumber.replace(/\D/g, '');
let sum = 0;
let isEven = false;
for (let i = digits.length - 1; i >= 0; i--) {
let digit = parseInt(digits[i]);
if (isEven) {
digit *= 2;
if (digit > 9) digit -= 9;
}
sum += digit;
isEven = !isEven;
}
return sum % 10 === 0;
}
}
// Register and use
registerCustomOperator(CreditCardOperator);
const paymentRule = {
conditions: {
and: [
{ field: "cardNumber", operator: "credit-card" },
{ field: "cvv", operator: "string-length", value: 3 }
]
}
};
Preprocess data before evaluation:
const engine = new RuleEngine();
// Add mutations for data preprocessing
engine.addMutation('normalizeEmail', (data) => {
if (data.email) {
data.email = data.email.toLowerCase().trim();
}
return data;
});
engine.addMutation('calculateAge', (data) => {
if (data.birthDate) {
const today = new Date();
const birth = new Date(data.birthDate);
data.age = today.getFullYear() - birth.getFullYear();
}
return data;
});
// Mutations are applied automatically before evaluation
const result = await engine.evaluate(rule, {
email: " USER@EXAMPLE.COM ",
birthDate: "1990-01-01"
});
Understand what your rules need:
const insights = RuleEngine.introspect(complexRule);
console.log(insights);
// {
// fields: ["userType", "subscriptionActive", "trialExpired"],
// operators: ["equals"],
// possibleResults: [
// { access: "premium", features: ["analytics", "api", "support"] },
// { access: "basic", features: ["dashboard"] },
// { access: "none", features: [] }
// ],
// complexity: "medium",
// estimatedPerformance: "fast"
// }
Operation | Records | Time | Throughput |
---|---|---|---|
Simple Rule (3 conditions) | 10,000 | 85ms | ~117,000/sec |
Complex Rule (15+ conditions) | 10,000 | 250ms | ~40,000/sec |
JSONPath Resolution | 10,000 | 120ms | ~83,000/sec |
With Mutations | 10,000 | 150ms | ~66,000/sec |
Custom Operators | 10,000 | 180ms | ~55,000/sec |
1. Trust Mode for Validated Rules
// Skip validation for 20% performance boost
const result = await RuleEngine.evaluate(rule, data, true);
2. Reuse Engine Instances
const engine = new RuleEngine();
// Reuse for better performance with mutations
3. Batch Processing
// Process multiple records at once
const results = await RuleEngine.evaluate(rule, arrayOfData);
4. Operator Selection
// Prefer specific operators over general ones
{ operator: "equals" } // ✅ Fast
{ operator: "matches" } // ⚠️ Slower for simple cases
Full type safety with intelligent inference:
interface UserPermissions {
canRead: boolean;
canWrite: boolean;
canDelete: boolean;
level: 'admin' | 'user' | 'guest';
}
// Type-safe rule definition
const accessRule: Rule<UserPermissions> = {
conditions: [
{
and: [
{ field: "role", operator: "equals", value: "admin" },
{ field: "active", operator: "equals", value: true }
],
result: {
canRead: true,
canWrite: true,
canDelete: true,
level: "admin"
}
}
],
default: {
canRead: false,
canWrite: false,
canDelete: false,
level: "guest"
}
};
// Type-safe evaluation
const result = await RuleEngine.evaluate<UserPermissions>(accessRule, userData);
// result.value is typed as UserPermissions ✅
const typedRule = RuleEngine.builder<UserPermissions>()
.add({
and: [{ field: "role", operator: "equals", value: "admin" }],
result: { canRead: true, canWrite: true, canDelete: true, level: "admin" }
})
.default({ canRead: false, canWrite: false, canDelete: false, level: "guest" })
.build();
import { describe, it, expect } from 'vitest';
import { RuleEngine } from '@usex/rule-engine';
describe('Discount Rules', () => {
it('should apply VIP discount for qualifying orders', async () => {
const result = await RuleEngine.evaluate(discountRule, {
customer: { tier: 'vip', orderCount: 5 },
order: { total: 150 }
});
expect(result.isPassed).toBe(true);
expect(result.value.discount).toBe(0.20);
expect(result.value.message).toContain('VIP');
});
it('should validate rule structure', () => {
const validation = RuleEngine.validate(discountRule);
expect(validation.isValid).toBe(true);
expect(validation.errors).toHaveLength(0);
});
it('should handle edge cases gracefully', async () => {
const result = await RuleEngine.evaluate(discountRule, {});
expect(result.value).toEqual({ discount: 0, message: "No discount available" });
});
});
- 📖 TypeScript Guide - Advanced TypeScript patterns and best practices
- 🔧 API Reference - Complete method documentation with examples
- 🚀 Migration Guide - Upgrading from v1 to v2
- 🎯 Operators Reference - Complete list of all 121+ operators
- 💡 Best Practices - Patterns for maintainable rule systems
- 📋 Changelog - Detailed version history
We love contributions! Whether it's:
- 🐛 Bug reports and fixes
- ✨ New operators or features
- 📖 Documentation improvements
- 🎨 Examples and tutorials
See our Contributing Guide for details.
# Clone and setup
git clone https://github.com/ali-master/rule-engine.git
cd rule-engine
pnpm install
# Run tests
pnpm test
# Run benchmarks
pnpm test:bench
# Build package
pnpm build
# Watch mode for development
pnpm dev
Feature | @usex/rule-engine | json-rules-engine | node-rules |
---|---|---|---|
Zero Dependencies | ✅ | ❌ | ❌ |
TypeScript Native | ✅ | ❌ | |
JSONPath Support | ✅ | ❌ | ❌ |
Self-Referencing | ✅ | ❌ | ❌ |
Custom Operators | ✅ | ❌ | |
Performance (ops/sec) | 117k+ | 45k | 30k |
Bundle Size | 12KB | 45KB | 38KB |
Browser Support | ✅ | ✅ | ❌ |
Rule Introspection | ✅ | ❌ | ❌ |
Fluent Builder | ✅ | ❌ | ❌ |
MIT © Ali Torki
Built with ❤️ by Ali Torki, for developers