npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@f-o-t/rules-engine

v4.0.0

Published

A fully type-safe, functional, rules orchestration engine for TypeScript. Built on top of `@f-o-t/condition-evaluator` for condition evaluation.

Downloads

256

Readme

@f-o-t/rules-engine

A fully type-safe, functional, rules orchestration engine for TypeScript. Built on top of @f-o-t/condition-evaluator for condition evaluation.

Features

  • Type-Safe: Full TypeScript support with autocomplete for consequences
  • Functional: Pure functions, immutable data structures, composable APIs
  • Fluent Builders: Chainable rule and condition builders
  • Caching: Built-in TTL-based caching with configurable eviction
  • Validation: Rule schema validation, conflict detection, integrity checks
  • Versioning: Track rule changes with rollback support
  • Simulation: Test rules without side effects, compare rule sets
  • Indexing: Fast rule lookups by field, tag, category, priority
  • Analysis: Rule complexity analysis, usage statistics

Installation

bun add @f-o-t/rules-engine @f-o-t/condition-evaluator

Migrating from 3.x to 4.0.0

Breaking Change: Re-exports of @f-o-t/condition-evaluator types and functions have been removed from @f-o-t/rules-engine.

What Changed

In v3.x, @f-o-t/rules-engine re-exported many types and functions from @f-o-t/condition-evaluator for convenience. This created confusion about which package was responsible for what functionality.

Starting in v4.0.0, you must import directly from the appropriate package.

Migration Guide

❌ BEFORE (v3.x - will break in v4.0.0):

import { createOperator, createEvaluator, Condition } from "@f-o-t/rules-engine";

✅ AFTER (v4.0.0):

import { createOperator, createEvaluator, Condition } from "@f-o-t/condition-evaluator";
import { createEngine } from "@f-o-t/rules-engine";

Import Guidelines

  • Import from @f-o-t/condition-evaluator: Operators, evaluators, condition types (createOperator, createEvaluator, Condition, ConditionGroup)
  • Import from @f-o-t/rules-engine: Engine, rules, rule builders, condition builders, consequences, rule management (createEngine, rule, num, str, bool, date, arr, all, any, and, or, conditions, RuleInput, Rule, Consequence, validation, simulation, versioning, indexing, analysis, serialization, filtering, sorting, grouping, utilities)

Quick Start

import { createEvaluator } from "@f-o-t/condition-evaluator";
import { createEngine } from "@f-o-t/rules-engine";
import { z } from "zod";

// Define your consequence types
const MyConsequences = {
  send_email: z.object({
    to: z.string().email(),
    subject: z.string(),
  }),
  apply_discount: z.object({
    percentage: z.number(),
  }),
};

// Create engine with built-in operators
const engine = createEngine({
  consequences: MyConsequences,
  evaluator: createEvaluator(), // Required!
});

// Or with custom operators
import { moneyOperators } from "@f-o-t/money/operators";

const engine = createEngine({
  consequences: MyConsequences,
  operators: moneyOperators, // Convenience: engine creates evaluator
});

// Add rules
engine.addRule({
  name: "high-value-customer",
  conditions: {
    id: "g1",
    operator: "AND",
    conditions: [
      {
        id: "c1",
        type: "number",
        field: "totalPurchases",
        operator: "gt",
        value: 1000,
      },
    ],
  },
  consequences: [
    {
      type: "apply_discount",
      payload: { percentage: 10 },
    },
  ],
});

// Evaluate
const result = await engine.evaluate({
  totalPurchases: 1500,
});

console.log(result.matchedRules); // Rules that matched
console.log(result.consequences); // Actions to take

Building Conditions

Shorthand Helpers

import { num, str, bool, date, arr, all, any } from "@f-o-t/rules-engine";

// Number conditions
num("amount", "gt", 100)      // amount > 100
num("count", "lte", 10)       // count <= 10
num("price", "eq", 50)        // price === 50

// String conditions
str("status", "eq", "active")
str("name", "contains", "test")
str("email", "ends_with", "@example.com")
str("role", "in", ["admin", "moderator"])

// Boolean conditions
bool("isActive", "eq", true)

// Date conditions
date("createdAt", "gt", "2024-01-01")
date("expiresAt", "between", ["2024-01-01", "2024-12-31"])

// Array conditions
arr("tags", "contains", "urgent")
arr("items", "is_not_empty", undefined)

// Combine with AND/OR
all(num("amount", "gt", 100), str("status", "eq", "approved"))
any(bool("isVip", "eq", true), num("orders", "gt", 10))

Fluent Condition Builder

import { and, or, conditions } from "@f-o-t/rules-engine";

// Using and/or with builder function
const complexCondition = and((c) =>
   c.number("amount", "gt", 100)
    .string("status", "eq", "active")
    .or((nested) =>
       nested.boolean("isVip", "eq", true)
             .number("loyaltyPoints", "gt", 1000)
    )
);

Building Rules

import { rule } from "@f-o-t/rules-engine";

const myRule = rule()
   .id("rule-001")                              // Optional custom ID
   .named("My Rule")                            // Required name
   .describedAs("Rule description")             // Optional description
   .when(conditions)                            // Required conditions
   .then("action_type", { payload: "data" })    // Required consequence(s)
   .then("another_action", {})                  // Multiple consequences
   .withPriority(100)                           // Higher = evaluated first
   .enabled()                                   // Enabled by default
   .stopOnMatch()                               // Stop evaluation on match
   .tagged("tag1", "tag2")                      // Categorization tags
   .inCategory("pricing")                       // Single category
   .withMetadata({ custom: "data" })            // Custom metadata
   .build();

Custom Operators

Use custom operators from libraries like @f-o-t/money:

import { createEngine } from "@f-o-t/rules-engine";
import { moneyOperators } from "@f-o-t/money/operators";

const engine = createEngine({
  operators: moneyOperators,
});

engine.addRule({
  name: "large-transaction",
  conditions: {
    id: "g1",
    operator: "AND",
    conditions: [
      {
        id: "c1",
        type: "custom",
        field: "amount",
        operator: "money_gt",
        value: { amount: "5000.00", currency: "BRL" },
      },
    ],
  },
  consequences: [
    { type: "flag_for_review", payload: {} },
  ],
});

Create your own custom operators:

import { createOperator } from "@f-o-t/condition-evaluator";
import { createEngine } from "@f-o-t/rules-engine";

const customOperator = createOperator({
  name: "is_valid_cpf",
  type: "custom",
  description: "Validate Brazilian CPF",
  evaluate: (actual, expected) => {
    // Your validation logic
    return validateCPF(actual as string);
  },
});

const engine = createEngine({
  operators: { is_valid_cpf: customOperator },
});

Engine API

Rule Management

const engine = createEngine();

// Add rules
const addedRule = engine.addRule(ruleInput);
const addedRules = engine.addRules([rule1, rule2]);

// Get rules
const singleRule = engine.getRule("rule-id");
const allRules = engine.getRules();
const filteredRules = engine.getRules({
   enabled: true,
   tags: ["pricing"],
   category: "discounts",
});

// Update rules
engine.updateRule("rule-id", { priority: 200 });
engine.enableRule("rule-id");
engine.disableRule("rule-id");

// Remove rules
engine.removeRule("rule-id");
engine.clearRules();

Evaluation

const result = await engine.evaluate(context, {
   skipDisabled: true,        // Skip disabled rules (default: true)
   bypassCache: false,        // Bypass cache (default: false)
   maxRules: 100,             // Limit rules evaluated
   tags: ["pricing"],         // Filter by tags
   category: "discounts",     // Filter by category
   ruleSetId: "set-001",      // Use specific rule set
   conflictResolution: "all", // "all" | "first-match" | "highest-priority"
});

// Result structure
result.matchedRules        // Rules that matched
result.consequences        // Aggregated consequences
result.totalRulesEvaluated // Count of rules evaluated
result.totalRulesMatched   // Count of matches
result.executionTimeMs     // Execution time
result.cacheHit            // Whether result was cached

Rule Sets

// Group rules into sets
engine.addRuleSet({
   name: "Holiday Promotions",
   ruleIds: ["rule-1", "rule-2", "rule-3"],
});

// Evaluate only rules in a set
await engine.evaluate(context, { ruleSetId: "set-id" });

Engine Configuration

import { createEngine } from "@f-o-t/rules-engine";
import { z } from "zod";

const engine = createEngine({
   // Type-safe consequence definitions
   consequences: {
      apply_discount: z.object({ percentage: z.number() }),
      send_email: z.object({ template: z.string(), to: z.string() }),
   },

   // Cache configuration
   cache: {
      enabled: true,
      ttl: 60000,      // 1 minute
      maxSize: 1000,
   },

   // Conflict resolution
   conflictResolution: "all", // "all" | "first-match" | "highest-priority"

   // Error handling
   continueOnError: true,

   // Performance monitoring
   slowRuleThresholdMs: 100,

   // Hook timeout (prevents slow hooks from blocking)
   hookTimeoutMs: 5000, // 5 second timeout

   // Lifecycle hooks
   hooks: {
      beforeEvaluation: async (context, rules) => {},
      afterEvaluation: async (result) => {},
      onRuleMatch: async (rule, context) => {},
      onRuleError: async (rule, error) => {},
      onCacheHit: async (key, result) => {},
      onSlowRule: async (rule, timeMs, threshold) => {},
      // Error handler for hook failures
      onHookError: (hookName, error) => {
         console.error(`Hook ${hookName} failed:`, error);
      },
   },
});

Zod Schemas

All configuration types are built from Zod schemas, enabling runtime validation and type inference:

import {
   // Config schemas
   CacheConfigSchema,
   ValidationConfigSchema,
   VersioningConfigSchema,
   LogLevelSchema,

   // Evaluation schemas
   ConflictResolutionStrategySchema,
   EvaluateOptionsSchema,

   // State schemas
   RuleStatsSchema,
   CacheStatsSchema,
   EngineStatsSchema,

   // Validation schemas
   ValidationErrorSchema,
   ValidationResultSchema,
   ValidationOptionsSchema,

   // Helper functions
   getDefaultCacheConfig,
   getDefaultValidationConfig,
   parseCacheConfig,
} from "@f-o-t/rules-engine";

// Parse and validate config with defaults
const cacheConfig = parseCacheConfig({ ttl: 30000 });
// Result: { enabled: true, ttl: 30000, maxSize: 1000 }

// Get default config
const defaults = getDefaultCacheConfig();
// Result: { enabled: true, ttl: 60000, maxSize: 1000 }

// Use schemas for custom validation
const result = CacheConfigSchema.safeParse(userInput);
if (!result.success) {
   console.error(result.error.issues);
}

Validation

import {
   validateRule,
   detectConflicts,
   checkIntegrity,
} from "@f-o-t/rules-engine";

// Validate single rule
const validation = validateRule(rule);
if (!validation.valid) {
   console.log(validation.errors);
}

// Detect conflicts between rules
const conflicts = detectConflicts(rules);
// Types: DUPLICATE_ID, DUPLICATE_CONDITIONS, OVERLAPPING_CONDITIONS,
//        PRIORITY_COLLISION, UNREACHABLE_RULE

// Check rule set integrity
const integrity = checkIntegrity(rules);
// Checks: negative priority, missing fields, invalid operators

Simulation

import { simulate, whatIf, batchSimulate } from "@f-o-t/rules-engine";

// Simulate without side effects
const result = simulate(rules, { data: context });

// Compare two rule sets
const comparison = whatIf(originalRules, modifiedRules, { data: context });
console.log(comparison.differences.newMatches);
console.log(comparison.differences.lostMatches);
console.log(comparison.differences.consequenceChanges);

// Test multiple contexts
const batchResults = batchSimulate(rules, [
   { data: { amount: 50 } },
   { data: { amount: 150 } },
   { data: { amount: 500 } },
]);

Versioning

import {
   createVersionStore,
   addVersion,
   getHistory,
   rollbackToVersion,
} from "@f-o-t/rules-engine";

let store = createVersionStore();

// Track changes
store = addVersion(store, rule, "create", { comment: "Initial version" });
store = addVersion(store, updatedRule, "update", { comment: "Increased priority" });

// Get history
const history = getHistory(store, rule.id);

// Rollback
const { store: newStore, rule: restoredRule } = rollbackToVersion(
   store,
   rule.id,
   1 // version number
);

Indexing & Optimization

import {
   buildIndex,
   getRulesByField,
   getRulesByTag,
   analyzeOptimizations,
} from "@f-o-t/rules-engine";

// Build index for fast lookups
const index = buildIndex(rules);

// Query by field
const amountRules = getRulesByField(index, "amount");

// Query by tag
const pricingRules = getRulesByTag(index, "pricing");

// Get optimization suggestions
const suggestions = analyzeOptimizations(rules);

Analysis

import {
   analyzeRuleSet,
   analyzeRuleComplexity,
   findMostComplexRules,
} from "@f-o-t/rules-engine";

// Analyze entire rule set
const analysis = analyzeRuleSet(rules);
console.log(analysis.ruleCount);
console.log(analysis.uniqueFields);
console.log(analysis.uniqueCategories);
console.log(analysis.fieldUsage);
console.log(analysis.operatorUsage);

// Find complex rules
const complexRules = findMostComplexRules(rules, 5);

Serialization

import {
   exportToJson,
   importFromJson,
   cloneRule,
   mergeRuleSets,
   diffRuleSets,
} from "@f-o-t/rules-engine";

// Export/Import
const json = exportToJson(rules, ruleSets);
const result = importFromJson(json, {
   generateNewIds: true, // Generate new IDs on import
});

if (result.success) {
   console.log("Imported rules:", result.rules);
   console.log("Imported ruleSets:", result.ruleSets);
}

// Check for orphaned references (ruleSets referencing missing rules)
if (result.orphanedReferences.length > 0) {
   for (const orphan of result.orphanedReferences) {
      console.warn(
         `RuleSet "${orphan.ruleSetName}" references missing rules:`,
         orphan.missingRuleIds
      );
   }
}

// Clone rule
const cloned = cloneRule(rule, { generateNewId: true });

// Merge rule sets
const merged = mergeRuleSets(rulesA, rulesB, {
   onConflict: "keep-first", // "keep-first" | "keep-second" | "keep-both"
});

// Diff rule sets
const diff = diffRuleSets(rulesA, rulesB);
console.log(diff.added);
console.log(diff.removed);
console.log(diff.modified);

Filtering, Sorting & Grouping

import {
   filterRules,
   filterByTags,
   filterByCategory,
   filterByEnabled,
   sortRules,
   sortByPriority,
   sortByName,
   sortByCreatedAt,
   groupRules,
   groupByCategory,
   groupByPriority,
   groupByEnabled,
} from "@f-o-t/rules-engine";

// Filter rules
const activeRules = filterByEnabled(rules, true);
const pricingRules = filterByTags(rules, ["pricing"]);
const discountRules = filterByCategory(rules, "discounts");

// Combined filters
const filtered = filterRules(rules, {
   enabled: true,
   tags: ["pricing"],
   category: "discounts",
});

// Sort rules
const byPriority = sortByPriority(rules, "desc"); // Highest first
const byName = sortByName(rules, "asc");
const byDate = sortByCreatedAt(rules, "desc");

// Custom sort
const sorted = sortRules(rules, { field: "priority", direction: "desc" });

// Group rules
const byCategory = groupByCategory(rules);
const byPriorityLevel = groupByPriority(rules);
const byStatus = groupByEnabled(rules);

Utilities

import {
   generateId,
   hashContext,
   hashRules,
   measureTime,
   measureTimeAsync,
   withTimeout,
} from "@f-o-t/rules-engine";

// Generate unique IDs
const id = generateId();

// Hash context for caching
const hash = hashContext({ amount: 100 });

// Measure execution time
const { result, durationMs } = measureTime(() => expensiveOperation());

// Async timing
const { result: asyncResult, durationMs: asyncTime } = await measureTimeAsync(
   () => fetchData()
);

// Timeout wrapper
const resultWithTimeout = await withTimeout(
   slowOperationPromise,
   5000, // 5 second timeout
   "Operation timed out" // optional error message
);

State Management (Functional API)

For functional programming without the engine wrapper:

import {
   createInitialState,
   addRule,
   addRules,
   updateRule,
   removeRule,
   getRule,
   getRules,
   enableRule,
   disableRule,
   cloneState,
} from "@f-o-t/rules-engine";

// Create initial state
let state = createInitialState();

// Add rules (returns new state)
state = addRule(state, ruleInput);
state = addRules(state, [rule1, rule2]);

// Query rules
const rule = getRule(state, "rule-id");
const allRules = getRules(state);

// Update rules
state = updateRule(state, "rule-id", { priority: 200 });
state = enableRule(state, "rule-id");
state = disableRule(state, "rule-id");

// Clone state for comparison
const clonedState = cloneState(state);

License

MIT