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-enginetype 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 greaterThanChecks if a field is greater than a value. lessThanChecks if a field is less than a value. equalsChecks equality with a value. notEqualsChecks inequality with a value. fieldExistsChecks if a field exists. fieldNullChecks if a field is null or undefined. inListChecks if the field value exists in an array. notInListChecks if the field value does NOT exist in an array. matchesRegexChecks if a field matches a regex. expressionEvaluates a dynamic JavaScript expression with placeholders. beforeDateChecks if a date is before a given date. afterDateChecks if a date is after a given date. startsWithChecks if a string starts with a value. endsWithChecks if a string ends with a value. containsChecks if a string contains a value. allInListChecks if all values in condition array exist in record field array. anyInListChecks if any value in condition array exists in record field array.
Action Description throwThrows an error with a dynamic message. logLogs a message to the console. setFieldSets a record field to a value or expression result. callFunctionCalls a custom function with the record and arguments. callWebhookSends 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.