Skip to main content

Overview

State change operators detect changes between evaluations. Requires StatefulRuleEngine or manual _previous context.

changed

Any value change

changedBy

Change by threshold

changedFrom

Changed from value

changedTo

Changed to value

increased

Numeric increase

decreased

Numeric decrease

Requirements

StatefulRuleEngine Required: These operators need previous state. Use StatefulRuleEngine or pass _previous context manually.
import { createRuleEngine, StatefulRuleEngine } from 'rule-engine-js';

// Create stateful engine
const baseEngine = createRuleEngine();
const statefulEngine = new StatefulRuleEngine(baseEngine);

// Use state change operators
const rule = { changed: ['temperature'] };
statefulEngine.evaluate('temp-rule', rule, { temperature: 25 });
Source: src/operators/state.js | Tests: tests/unit/operators/state.test.js

Operators

changed - Any Change

Detects any value change.
{ changed: ['fieldPath'] }
Example:
// First evaluation
statefulEngine.evaluate('rule1', { changed: ['status'] }, { status: 'pending' });
// Result: { success: false } - no previous state

// Second evaluation
statefulEngine.evaluate('rule1', { changed: ['status'] }, { status: 'active' });
// Result: { success: true } - "pending" → "active"

// Third evaluation (same value)
statefulEngine.evaluate('rule1', { changed: ['status'] }, { status: 'active' });
// Result: { success: false } - no change

changedBy - Threshold Change

Detects numeric change by minimum amount (absolute value).
{ changedBy: ['fieldPath', threshold] }
Example:
// Temperature changed by at least 5 degrees
const rule = { changedBy: ['temperature', 5] };

// Previous: 20, Current: 23 → change = 3
statefulEngine.evaluate('temp', rule, { temperature: 23 });
// Result: { success: false } - changed by 3 (< 5)

// Previous: 23, Current: 29 → change = 6
statefulEngine.evaluate('temp', rule, { temperature: 29 });
// Result: { success: true } - changed by 6 (≥ 5)

changedFrom - From Specific Value

Detects transition from a specific value.
{ changedFrom: ['fieldPath', fromValue] }
Example:
// Detect order leaving "pending" status
const rule = { changedFrom: ['order.status', 'pending'] };

// Previous: "pending", Current: "processing"
statefulEngine.evaluate('order', rule, { order: { status: 'processing' } });
// Result: { success: true }

// Previous: "processing", Current: "shipped"
statefulEngine.evaluate('order', rule, { order: { status: 'shipped' } });
// Result: { success: false } - didn't change FROM pending

changedTo - To Specific Value

Detects transition to a specific value.
{ changedTo: ['fieldPath', toValue] }
Example:
// Detect order completion
const rule = { changedTo: ['order.status', 'completed'] };

// Previous: "processing", Current: "completed"
statefulEngine.evaluate('order', rule, { order: { status: 'completed' } });
// Result: { success: true }

// Previous: "completed", Current: "completed"
statefulEngine.evaluate('order', rule, { order: { status: 'completed' } });
// Result: { success: false } - already was completed

increased - Numeric Increase

Detects any numeric increase.
{ increased: ['fieldPath'] }
Example:
const rule = { increased: ['score'] };

// Previous: 85, Current: 90
statefulEngine.evaluate('score', rule, { score: 90 });
// Result: { success: true }

// Previous: 90, Current: 90
statefulEngine.evaluate('score', rule, { score: 90 });
// Result: { success: false } - no change

// Previous: 90, Current: 85
statefulEngine.evaluate('score', rule, { score: 85 });
// Result: { success: false } - decreased, not increased

decreased - Numeric Decrease

Detects any numeric decrease.
{ decreased: ['fieldPath'] }
Example:
const rule = { decreased: ['stock'] };

// Previous: 100, Current: 95
statefulEngine.evaluate('inventory', rule, { stock: 95 });
// Result: { success: true }

// Previous: 95, Current: 95
statefulEngine.evaluate('inventory', rule, { stock: 95 });
// Result: { success: false } - no change

Common Use Cases

  • Temperature Monitoring
  • Order Status Tracking
  • Inventory Management
  • Price Monitoring
// Alert on significant temp change
const tempAlert = {
  and: [
    { gte: ['temperature', 25] },
    { increased: ['temperature'] }
  ]
};

// Or threshold-based
const significantChange = {
  changedBy: ['temperature', 5]
};

StatefulRuleEngine Usage

Basic Setup

import { createRuleEngine, StatefulRuleEngine } from 'rule-engine-js';

const engine = createRuleEngine();
const statefulEngine = new StatefulRuleEngine(engine);

// Define rule
const rule = {
  and: [
    { gte: ['temperature', 25] },
    { increased: ['temperature'] }
  ]
};

// First evaluation
statefulEngine.evaluate('temp-rule', rule, { temperature: 20 });
// Result: { success: false, triggered: false }

// Second evaluation - temp increased and now >= 25
statefulEngine.evaluate('temp-rule', rule, { temperature: 26 });
// Result: { success: true, triggered: true }
// Event 'triggered' emitted

Event System

// Listen for state changes
statefulEngine.on('triggered', (event) => {
  console.log(`Rule ${event.ruleId} triggered!`);
  console.log('Context:', event.context);
});

statefulEngine.on('changed', (event) => {
  console.log(`Rule ${event.ruleId} state changed`);
});

Batch Evaluation

const rules = {
  'temp-increased': { increased: ['temperature'] },
  'temp-critical': { gte: ['temperature', 30] },
  'temp-changed': { changedBy: ['temperature', 5] }
};

const results = statefulEngine.evaluateBatch(rules, { temperature: 28 });
// Returns results for all rules

Manual Previous Context

Without StatefulRuleEngine, pass _previous manually:
const context = {
  temperature: 26,
  _previous: {
    temperature: 20
  }
};

engine.evaluateExpr({ increased: ['temperature'] }, context);
// Result: { success: true }

Quick Reference

OperatorArgsDetectsExample
changed1Any change{ changed: ['status'] }
changedBy2Change ≥ threshold{ changedBy: ['temp', 5] }
changedFrom2From specific value{ changedFrom: ['status', 'pending'] }
changedTo2To specific value{ changedTo: ['status', 'done'] }
increased1Numeric increase{ increased: ['score'] }
decreased1Numeric decrease{ decreased: ['stock'] }

Error Handling

// First evaluation - no previous state
const result = statefulEngine.evaluate('rule', { changed: ['value'] }, { value: 10 });
// Result: { success: false } - returns false, not error
const context = {
  name: 'John',
  _previous: { name: 'Jane' }
};

const result = engine.evaluateExpr({ changedBy: ['name', 5] }, context);
// Error: "CHANGED_BY requires numeric values"

API Reference