Skip to content

Chust3r/rule-engine

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

17 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Rule Engine

A TypeScript-first Rule Engine inspired by Drizzle ORM. Provides a type-safe way to define schemas, rules, and conditions, and evaluate them against hydrated data contexts.


🏗️ Concept Overview

  • Schema: Defines fields, defaults, and hydration logic.
  • Rules: Declarative conditions with optional then callbacks and priorities.
  • Engine: Evaluates rules against a context and computes matching scores.
  • Operators: Comparison, logical, array, and database-inspired operators.

⚡ Quick Start

import {
	text,
	number,
	boolean,
	enumField,
	createSchema,
	createRules,
	createEngine,
	and,
	not,
	eq,
	gte,
} from 'src'

// 1️⃣ Define schema
const context = createSchema({
	id: text('id').notNull(),
	age: number('age').default(18),
	isActive: boolean('isActive').default(true),
	role: enumField('role', ['admin', 'user', 'vip']).default('user'),
})

// 2️⃣ Define conditions
const conditionAdult = gte(context.age, 18)
const conditionNotUser = not(eq(context.role, 'user'))
const conditionActiveVip = and(
	eq(context.isActive, true),
	eq(context.role, 'vip'),
)

// 3️⃣ Create rules
const rulesHelper = createRules(context)

const promoRule = rulesHelper.create('PROMO_ADULT', {
	when: [conditionAdult, conditionNotUser],
	priority: 10,
})

const discountRule = rulesHelper.create('DISCOUNT_VIP', {
	when: [conditionActiveVip],
	priority: 20,
})

// 4️⃣ Create engine and register rules
const engine = createEngine(context)
engine.rules(promoRule, discountRule)

// 5️⃣ Hydrate data
const hydrated = context.hydrate({
	id: '123',
	age: 25,
	isActive: true,
	role: 'vip',
})

// 6️⃣ Evaluate
const results = await engine.evaluate(hydrated)
console.log(results)
// Output: [{ id: 'DISCOUNT_VIP', score: 2 }, { id: 'PROMO_ADULT', score: 2 }]

🔧 API

createSchema(fields)

Builds a type-safe schema from field builders and adds a hydrate() helper.

Field Builders

Function Description
text(name) Create a string field
number(name) Create a number field
boolean(name) Create a boolean field
date(name) Create a date field
enumField(name, values) Create an enum field with literal union

createRules(schema)

Generates a helper to create rules bound to a schema.

const helper = createRules(schema)
const rule = helper.create('RULE_ID', { when: [condition], priority: 5 })

createEngine(schema)

Creates a RuleEngine instance to register and evaluate rules.

Methods:

Method Description
rule(r) Adds a single rule
rules(...r) Adds multiple rules
evaluate(ctx, id?) Returns all matching rules with { id, score }
getFirst(ctx, id?) Returns the first matching rule by priority
invalidate(id?) Clears cached condition evaluation

Condition Operators

Logical Operators

Operator Description
and(...) Recursively AND conditions
or(...) Recursively OR conditions
not(cond) Negates a condition

Comparison Operators

Operator SQL Equivalent Example
eq = eq(context.age, 18)
ne <> ne(context.role, 'user')
gt > gt(context.age, 21)
gte >= gte(context.age, 18)
lt < lt(context.age, 65)
lte <= lte(context.age, 65)

Array / Set Operators

Operator Description
inArray Checks value in array
notInArray Checks value not in array
arrayContains Checks if array contains values
arrayOverlaps Checks if array has any overlap
arrayContainedBy Checks if array is contained by another

TODO: Add examples and further explanation for how these operators work in rules.

Database-like Operators

Operator Description
between Value between min & max inclusive
notBetween Value outside min & max
like SQL LIKE pattern match
ilike Case-insensitive LIKE

TODO: Provide usage examples and clarify edge cases.


💡 Best Practices

  • Always hydrate your context before evaluation.
  • Use priority to control rule order.
  • Use invalidate() when reusing context IDs to prevent stale evaluation caching.
  • Combine operators with and, or, not for complex rules.

📂 Recommended Project Structure

rule-engine/
├── src/
│   ├── engine/        # RuleEngine, Rule, ConditionEvaluator
│   ├── fields/        # Field builders (TextFieldBuilder, NumberFieldBuilder, etc.)
│   ├── operators/     # Logical, comparison, array, DB-like operators
│   ├── schema/        # createSchema helper
│   ├── types/         # Rule types, AST types, inferred types
│   └── index.ts       # Public API exports

🚀 Example: Complex Rule

const complexRule = rulesHelper.create('COMPLEX', {
	when: [
		and(
			gte(context.age, 18),
			eq(context.role, 'vip'),
			or(eq(context.isActive, true), inArray(context.id, ['123', '456'])),
		),
	],
	priority: 30,
})
engine.rule(complexRule)

📝 Notes

  • Supports async then callbacks for side-effects.
  • Scores are calculated as the number of matching conditions.
  • First-match evaluation respects priority and flow: 'stop'.
  • Designed to be fully type-safe with inferred schema types.

⚡ Next Steps

  • Add missing operators like exists, notExists, notLike, etc.
  • Expand array operators for nested structures.
  • Implement advanced AST optimizations for large rule sets.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published