decision-core
v0.0.8-alpha.0
Published
The ultimate JSON-based rule engine for Node.js that turns complex business logic into declarative configurations. Built for backend developers who believe code should be expressive, not repetitive.
Readme
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 });🚀 Why Rule Engine?
Built for Modern Developers
- 🎯 Zero Dependencies - No supply chain bloat, just pure JavaScript excellence
- 🏎️ Lightning Fast - 17,000+ rule evaluations per second with complex JSONPath support at 55,000+ ops/sec
- 🛡️ 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
🎬 Quick Start
npm install @usex/rule-engineYour First Rule in 30 Seconds
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 }🏗️ Core Concepts
Rules Structure
Every rule follows this pattern:
interface Rule<T = any> {
conditions: Condition<T> | Condition<T>[]; // What to check
default?: T; // Fallback result
}Conditions: Your Logic Building Blocks
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
}Constraints: The Evaluation Units
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
}🛠️ API Reference
Static Methods (Recommended)
| 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 |
Instance Methods
const engine = new RuleEngine();
// All static methods available as instance methods
await engine.evaluate(rule, data);🔧 Operators Showcase
String & Text
// 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 }Numbers & Ranges
// 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 }Arrays & Collections
// 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 & Time
// 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 & Nullability
// 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 }Length & Size
// 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] }🎯 Real-World Examples
🛒 E-commerce Pricing Engine
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: "2025-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" }
};🔐 Dynamic Access Control
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"
}
};✅ Smart Form Validation
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"
}
]
}
};🎨 Advanced Features
🔗 Self-Referencing Magic
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"
}
]
}
};🏗️ Fluent Builder Pattern
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🔧 Custom Operators (V2)
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 }
]
}
};🎭 Data Mutations
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: " [email protected] ",
birthDate: "1990-01-01"
});📊 Rule Introspection
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"
// }🏎️ Performance & Optimization
Real-World Benchmarks
Performance data from actual benchmark runs (10,000 iterations each on modern hardware)
Core Rule Evaluation
| Operation | Hz (ops/sec) | Avg Time | Performance Grade | |-----------|-------------|----------|-------------------| | Simple Rules (3-5 conditions) | ~16,900 | 0.059ms | 🚀 Lightning Fast | | Complex Rules (nested evaluation) | ~17,400 | 0.057ms | 🔥 Blazing | | Complex Rules (priority-based) | ~8,000 | 0.126ms | ⚡ Very Fast | | Array Operations | ~45,400 | 0.022ms | 🚀 Ultra Fast |
Advanced Features
| Feature | Hz (ops/sec) | Avg Time | Performance Grade | |---------|-------------|----------|-------------------| | JSONPath Resolution (simple) | ~55,000 | 0.018ms | 🔥 Blazing Fast | | JSONPath Deep Nested Access | ~54,000 | 0.019ms | 🔥 Blazing Fast | | JSONPath Array Processing | ~49,500 | 0.020ms | 🚀 Ultra Fast | | Self-Referencing (complex) | ~33,600 | 0.030ms | 🚀 Excellent | | Self-Referencing (simple) | ~30,900 | 0.032ms | 🚀 Excellent |
Data Processing & Validation
| Operation | Hz (ops/sec) | Avg Time | Use Case | |-----------|-------------|----------|----------| | Rule Builder (simple) | ~12,000,000 | 0.0001ms | Rule Construction | | Rule Builder (complex) | ~94,300 | 0.011ms | Complex Rule Building | | Rule Validation | ~67,000 | 0.015ms | Schema Validation | | Data Mutations (simple) | ~16,300 | 0.061ms | Data Preprocessing | | Data Mutations (complex) | ~34,100 | 0.029ms | Advanced Transformations | | Text Interpolation | ~796,000 | 0.0013ms | Dynamic Messages |
Error Handling & Edge Cases
| Operation | Hz (ops/sec) | Performance Grade | |-----------|-------------|-------------------| | Unknown Operator Handling | ~1,661,000 | ⚡ Instant Response | | Invalid Ruleset Handling | ~214,000 | 🚀 Very Fast | | Exists/NotExists Operators | ~41,400 | 🔥 Blazing |
All benchmarks run on 10,000 iterations with statistical accuracy. Performance may vary based on hardware and rule complexity.
Bundle Size
| Build Type | Size | |------------|------| | Minified | 102.7kB | | Minified + Gzipped | 18.4kB |
Zero dependencies = predictable bundle size with tree-shaking support
Optimization Tips
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 mutations3. 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🎓 TypeScript Support
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 ✅Generic Builder Pattern
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();🧪 Testing Your Rules
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" });
});
});📚 Documentation & Resources
- 📖 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
🤝 Contributing
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.
Development Setup
# 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🆚 Why Choose this lib Over Alternatives?
| Feature | @usex/rule-engine | json-rules-engine | node-rules | |---------|-------------------|-------------------|------------| | Zero Dependencies | ✅ | ❌ | ❌ | | TypeScript Native | ✅ | ⚠️ Partial | ❌ | | JSONPath Support | ✅ | ❌ | ❌ | | Self-Referencing | ✅ | ❌ | ❌ | | Custom Operators | ✅ | ⚠️ Limited | ❌ | | Performance (ops/sec) | 17k+ (55k+ JSONPath) | 45k | 30k | | Bundle Size | 18.4KB | 45KB | 38KB | | Browser Support | ✅ | ✅ | ❌ | | Rule Introspection | ✅ | ❌ | ❌ | | Fluent Builder | ✅ | ❌ | ❌ |
📄 License
MIT © Ali Torki
Built with ❤️ by Ali Torki, for developers
