A flexible rule engine (zero dependencies) for evaluating dynamic conditions and executing actions on records. Ideal for validation, automation, and configurable workflows in TypeScript/Node.js projects.
Feature
Description
Simple & Composite Conditions
Supports and, or, not logical operators.
Dynamic Expressions
Use placeholders like {{field}} in conditions and actions.
Built-in Actions
throw, log, setField, callFunction, callWebhook.
Extensibility
Register custom conditions and actions.
Async Support
Handle asynchronous API calls or external tasks.
Zero Dependencies
Works out-of-the-box without extra packages.
# Using npm
npm install scriba-rule-engine
# Using yarn
yarn add scriba-rule-engine
type Condition = {
type ?: string ;
field ?: string ;
value ?: any ;
valueFrom ?: string ;
expression ?: string ;
and ?: Condition [ ] ;
or ?: Condition [ ] ;
not ?: Condition ;
} ;
type ActionPayload = Record < string , any > ;
type ActionFn = ( params : { record : any ; payload : ActionPayload } ) => Promise < void > | void ;
type Rule = {
conditions : Condition | Condition [ ] ;
actions : Array < { type : string } & ActionPayload > ;
priority ?: number ;
} ;
import { RuleEngine , defaultConditions , defaultActions } from "scriba-rule-engine" ;
const engine = new RuleEngine ( {
conditions : defaultConditions ,
actions : defaultActions ,
} ) ;
const record = { name : "Alice" , age : 25 , status : "active" } ;
const rules = [
{
conditions : { type : "greaterThan" , field : "age" , value : 18 } ,
actions : [
{ type : "log" , message : "User {{name}} is an adult" } ,
{ type : "setField" , field : "verified" , value : true }
]
} ,
{
conditions : { type : "fieldExists" , field : "status" } ,
actions : [
{ type : "callFunction" , fn : async ( r ) => console . log ( "Processing" , r . name ) }
]
}
] ;
await engine . applyRules ( record , rules ) ;
console . log ( record ) ;
// Output: { name: "Alice", age: 25, status: "active", verified: true }
π οΈ Adding Custom Conditions or Actions
// Custom Condition
engine . registerCondition ( "isEven" , ( record , cond ) => record [ cond . field ! ] % 2 === 0 ) ;
// Custom Action
engine . registerAction ( "customAction" , async ( { record } ) => {
console . log ( "Custom action executed for:" , record ) ;
} ) ;
Condition
Description
greaterThan
Checks if a field is greater than a value.
lessThan
Checks if a field is less than a value.
equals
Checks equality with a value.
notEquals
Checks inequality with a value.
fieldExists
Checks if a field exists.
fieldNull
Checks if a field is null or undefined.
inList
Checks if the field value exists in an array.
notInList
Checks if the field value does NOT exist in an array.
matchesRegex
Checks if a field matches a regex.
expression
Evaluates a dynamic JavaScript expression with placeholders.
beforeDate
Checks if a date is before a given date.
afterDate
Checks if a date is after a given date.
startsWith
Checks if a string starts with a value.
endsWith
Checks if a string ends with a value.
contains
Checks if a string contains a value.
allInList
Checks if all values in condition array exist in record field array.
anyInList
Checks if any value in condition array exists in record field array.
Action
Description
throw
Throws an error with a dynamic message.
log
Logs a message to the console.
setField
Sets a record field to a value or expression result.
callFunction
Calls a custom function with the record and arguments.
callWebhook
Sends a POST request to a webhook with dynamic payload.
const rec = { age : 30 , status : 'inactive' } ;
const rules = [
{
conditions : {
and : [
{ type : 'greaterThan' , field : 'age' , value : 18 } ,
{ not : { type : 'equals' , field : 'status' , value : 'active' } }
]
} ,
actions : [ { type : 'setField' , field : 'canAccess' , value : true } ]
}
] ;
await engine . applyRules ( rec , rules ) ;
console . log ( rec . canAccess ) ; // true
const rec = { age : 70 , profile : { } } ;
const rules = [
{
conditions : { type : 'greaterThan' , field : 'age' , value : 65 } ,
actions : [
{ type : 'setField' , field : 'status' , value : 'blocked' } ,
{ type : 'setField' , field : 'profile.category' , value : 'senior' }
]
}
] ;
await engine . applyRules ( rec , rules ) ;
console . log ( rec . status , rec . profile . category ) ; // blocked senior
const rec = { score : 5 } ;
const rules = [
{ conditions : { expression : '{{score}} > 0' } , actions : [ { type : 'setField' , field : 'bonus' , expression : '{{score}} * 10' } ] }
] ;
await engine . applyRules ( rec , rules ) ;
console . log ( rec . bonus ) ; // 50
Expressions use double curly braces {{field}} to dynamically evaluate record values.
Rules can have optional priority for execution order.
Actions can be asynchronous, supporting external API calls.
Fully extensible and lightweight, with zero dependencies.