Quick Start
import { createRuleEngine, StatefulRuleEngine } from 'rule-engine-js';
// Create base engine
const baseEngine = createRuleEngine();
// Wrap with stateful engine
const statefulEngine = new StatefulRuleEngine(baseEngine);
// Define rule with state change
const rule = {
and: [
{ gte: ['temperature', 25] },
{ increased: ['temperature'] }
]
};
// First evaluation
statefulEngine.evaluate('temp', rule, { temperature: 20 });
// { success: false, triggered: false }
// Second evaluation - temperature increased to 26
statefulEngine.evaluate('temp', rule, { temperature: 26 });
// { success: true, triggered: true }
Event System
triggered
Rule becomes true (false → true)
untriggered
Rule becomes false (true → false)
changed
Rule result changed
evaluated
Every evaluation
Listening to Events
// Listen for triggers
statefulEngine.on('triggered', (event) => {
console.log(`Rule ${event.ruleId} triggered!`);
console.log('Context:', event.context);
console.log('Result:', event.result);
});
// Listen for state changes
statefulEngine.on('changed', (event) => {
console.log(`Rule ${event.ruleId} state changed`);
console.log('Previous:', event.previousResult);
console.log('Current:', event.result);
});
// Listen to all evaluations
statefulEngine.on('evaluated', (event) => {
console.log(`Rule ${event.ruleId} evaluated`);
});
Temperature Monitoring
const tempAlert = {
and: [
{ gte: ['temperature', 30] },
{ increased: ['temperature'] }
]
};
statefulEngine.on('triggered', (event) => {
if (event.ruleId === 'temp-alert') {
console.log('⚠️ Temperature rising!', event.context.temperature);
}
});
// Monitor temperature changes
statefulEngine.evaluate('temp-alert', tempAlert, { temperature: 25 });
// No trigger
statefulEngine.evaluate('temp-alert', tempAlert, { temperature: 32 });
// ⚠️ Temperature rising! 32
Order Status Tracking
const orderCompleteRule = {
changedTo: ['order.status', 'completed']
};
statefulEngine.on('triggered', (event) => {
if (event.ruleId === 'order-complete') {
console.log('📦 Order completed!', event.context.order.id);
// Send notification, update inventory, etc.
}
});
// Track order progression
const order = { order: { id: 123, status: 'pending' } };
statefulEngine.evaluate('order-complete', orderCompleteRule, order);
// No trigger
order.order.status = 'completed';
statefulEngine.evaluate('order-complete', orderCompleteRule, order);
// 📦 Order completed! 123
Stock Monitoring
const lowStockRule = {
and: [
{ decreased: ['product.stock'] },
{ lte: ['product.stock', 10] }
]
};
statefulEngine.on('triggered', (event) => {
if (event.ruleId === 'low-stock') {
console.log('📉 Low stock alert!', event.context.product);
// Reorder inventory
}
});
let product = { product: { stock: 50, name: 'Widget' } };
statefulEngine.evaluate('low-stock', lowStockRule, product);
// No trigger (stock is high)
product.product.stock = 8;
statefulEngine.evaluate('low-stock', lowStockRule, product);
// 📉 Low stock alert! { stock: 8, name: 'Widget' }
Price Change Detection
const priceDrop = {
and: [
{ decreased: ['price'] },
{ changedBy: ['price', 5] }
]
};
statefulEngine.on('triggered', (event) => {
if (event.ruleId === 'price-drop') {
console.log('💰 Price dropped by $5+');
}
});
statefulEngine.evaluate('price-drop', priceDrop, { price: 100 });
// No trigger
statefulEngine.evaluate('price-drop', priceDrop, { price: 94 });
// 💰 Price dropped by $5+
User Activity Tracking
const loginDetection = {
changedFrom: ['user.status', 'offline']
};
const logoutDetection = {
changedTo: ['user.status', 'offline']
};
statefulEngine.on('triggered', (event) => {
if (event.ruleId === 'login') {
console.log('👤 User logged in');
}
if (event.ruleId === 'logout') {
console.log('👋 User logged out');
}
});
let user = { user: { status: 'offline' } };
statefulEngine.evaluate('login', loginDetection, user);
statefulEngine.evaluate('logout', logoutDetection, user);
user.user.status = 'online';
statefulEngine.evaluate('login', loginDetection, user);
// 👤 User logged in
user.user.status = 'offline';
statefulEngine.evaluate('logout', logoutDetection, user);
// 👋 User logged out
Batch Evaluation
Monitor multiple rules at once.const rules = {
'payment-received': {
changedTo: ['order.paymentStatus', 'paid']
},
'inventory-low': {
and: [
{ decreased: ['product.stock'] },
{ lte: ['product.stock', 10] }
]
},
'price-change': {
changed: ['product.price']
}
};
const data = {
order: { paymentStatus: 'paid' },
product: { stock: 8, price: 99 }
};
const results = statefulEngine.evaluateBatch(rules, data);
console.log(results);
// {
// 'payment-received': { success: true, triggered: true },
// 'inventory-low': { success: true, triggered: true },
// 'price-change': { success: true, triggered: true }
// }
Analytics Tracking
const pageViewIncrease = {
and: [
{ increased: ['analytics.pageViews'] },
{ changedBy: ['analytics.pageViews', 100] }
]
};
statefulEngine.on('triggered', (event) => {
if (event.ruleId === 'viral') {
console.log('🔥 Page views spiked by 100+!');
}
});
let analytics = { analytics: { pageViews: 500 } };
statefulEngine.evaluate('viral', pageViewIncrease, analytics);
// No trigger
analytics.analytics.pageViews = 650;
statefulEngine.evaluate('viral', pageViewIncrease, analytics);
// 🔥 Page views spiked by 100+!
Sensor Data Monitoring
const sensorAlert = {
and: [
{ gte: ['sensor.value', 100] },
{ increased: ['sensor.value'] },
{ changedBy: ['sensor.value', 10] }
]
};
statefulEngine.on('triggered', (event) => {
console.log('🚨 Sensor threshold exceeded!');
console.log('Value:', event.context.sensor.value);
});
let reading = { sensor: { value: 95 } };
statefulEngine.evaluate('sensor', sensorAlert, reading);
// No trigger
reading.sensor.value = 110;
statefulEngine.evaluate('sensor', sensorAlert, reading);
// 🚨 Sensor threshold exceeded!
// Value: 110
History Tracking
Enable history to review past evaluations.const engine = new StatefulRuleEngine(baseEngine, {
storeHistory: true,
maxHistorySize: 100
});
const rule = { changed: ['value'] };
// Evaluate multiple times
engine.evaluate('track', rule, { value: 10 });
engine.evaluate('track', rule, { value: 20 });
engine.evaluate('track', rule, { value: 30 });
// Get history
const history = engine.getHistory('track');
console.log(history);
// [
// { context: { value: 10 }, result: { success: false }, timestamp: ... },
// { context: { value: 20 }, result: { success: true }, timestamp: ... },
// { context: { value: 30 }, result: { success: true }, timestamp: ... }
// ]
State Management
// Get current state for a rule
const state = statefulEngine.getState('temp-alert');
// Clear state for specific rule
statefulEngine.clearState('temp-alert');
// Clear all states
statefulEngine.clearState();
// Remove event listeners
statefulEngine.removeAllListeners('triggered');
Trigger Modes
Default Mode (false → true only)
const engine = new StatefulRuleEngine(baseEngine, {
triggerOnEveryChange: false // Default
});
const rule = { eq: ['status', 'active'] };
// false → true: triggers
engine.evaluate('status', rule, { status: 'inactive' }); // false, no trigger
engine.evaluate('status', rule, { status: 'active' }); // true, TRIGGERS
// true → true: no trigger
engine.evaluate('status', rule, { status: 'active' }); // true, no trigger
// true → false: untriggers
engine.evaluate('status', rule, { status: 'inactive' }); // false, UNTRIGGERS
Every Change Mode
const engine = new StatefulRuleEngine(baseEngine, {
triggerOnEveryChange: true
});
// Triggers on ANY result change
engine.evaluate('status', rule, { status: 'inactive' }); // false, triggers (first eval)
engine.evaluate('status', rule, { status: 'active' }); // true, triggers (changed)
engine.evaluate('status', rule, { status: 'active' }); // true, no trigger (no change)
engine.evaluate('status', rule, { status: 'inactive' }); // false, triggers (changed)
Complete Example
import { createRuleEngine, StatefulRuleEngine } from 'rule-engine-js';
const baseEngine = createRuleEngine();
const statefulEngine = new StatefulRuleEngine(baseEngine, {
storeHistory: true,
maxHistorySize: 50
});
// Define rules
const rules = {
'high-temp': {
and: [
{ gte: ['temperature', 30] },
{ increased: ['temperature'] }
]
},
'low-stock': {
and: [
{ decreased: ['stock'] },
{ lte: ['stock', 10] }
]
},
'order-complete': {
changedTo: ['status', 'completed']
}
};
// Set up event handlers
statefulEngine.on('triggered', (event) => {
console.log(`🔔 Rule triggered: ${event.ruleId}`);
switch (event.ruleId) {
case 'high-temp':
console.log('⚠️ Temperature alert!');
break;
case 'low-stock':
console.log('📦 Reorder needed!');
break;
case 'order-complete':
console.log('✅ Order completed!');
break;
}
});
// Monitor data changes
let data = {
temperature: 25,
stock: 50,
status: 'pending'
};
// Initial evaluation
Object.keys(rules).forEach(ruleId => {
statefulEngine.evaluate(ruleId, rules[ruleId], data);
});
// Update data
data.temperature = 32;
data.stock = 8;
data.status = 'completed';
// Re-evaluate
Object.keys(rules).forEach(ruleId => {
statefulEngine.evaluate(ruleId, rules[ruleId], data);
});
// 🔔 Rule triggered: high-temp
// ⚠️ Temperature alert!
// 🔔 Rule triggered: low-stock
// 📦 Reorder needed!
// 🔔 Rule triggered: order-complete
// ✅ Order completed!
Best Practices
1. Use Specific Rule IDs
1. Use Specific Rule IDs
Use descriptive IDs like ‘temp-alert’ not ‘rule1’.
2. Combine State and Value Checks
2. Combine State and Value Checks
Use
and to check both state change and threshold.3. Clear State When Needed
3. Clear State When Needed
Clear state when data schema changes or resets.
4. Limit History Size
4. Limit History Size
Set
maxHistorySize to prevent memory issues.5. Remove Listeners
5. Remove Listeners
Clean up event listeners when done.
6. Use Batch for Multiple Rules
6. Use Batch for Multiple Rules
evaluateBatch() is more efficient than multiple calls.Related
State Operators
changed, increased, decreased
StatefulRuleEngine API
Full API reference
Basic Rules
Simple validation examples
Real-World Scenarios
Production examples
