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

nx-rules

v1.0.2

Published

A semi-strict, production-grade rules runtime for Node.js/TypeScript with internal registries, TTL state, and optional local LLM conditions

Readme

nx-rules

A semi-strict, production-grade rules engine for Node.js and TypeScript. Build complex decision logic with composable conditions, actions, and enrichments.

npm version License: MIT

Features

  • 🎯 Declarative Rule Definitions - Define rules as JSON/TypeScript objects
  • 🔧 Function & API Registries - Register custom functions and API integrations
  • 🎨 Rich Expression Language - Boolean logic, comparisons, path references, and more
  • 📊 Flexible Actions - Enrich, aggregate, detect patterns, and write to output
  • 🧠 Optional LLM Support - Built-in local LLM conditions for classification and extraction
  • 💾 TTL State Management - In-memory state store with automatic cleanup
  • 🔍 Comprehensive Tracing - Track rule execution with detailed trace events
  • 📦 TypeScript First - Full type safety with excellent IDE support

Installation

npm install nx-rules

Peer Dependencies

nx-rules uses external registries for functions and APIs:

npm install nx-functions x-api-registerer

These are automatically installed as dependencies.

Quick Start

import { 
  createEngine, 
  createFunctionRegistry, 
  createApiRegistry 
} from 'nx-rules';

// 1. Create registries
const fn = createFunctionRegistry();
const api = createApiRegistry();

// 2. Register custom functions
fn.register('is_premium_user', async (ctx) => {
  return ctx.input.tier === 'premium';
});

// 3. Register API integrations
api.register('fetch_user_profile', async (ctx) => {
  const userId = ctx.input.userId;
  // Your API logic here
  return { 
    ok: true, 
    data: { name: 'John Doe', credits: 100 } 
  };
});

// 4. Create the engine
const engine = createEngine({
  functionRegistry: fn,
  apiRegistry: api,
  config: {
    matchPolicy: 'ALL_MATCH',
    conflictPolicy: 'MERGE',
    trace: true
  }
});

// 5. Define rules
const rule = {
  ruleId: 'premium-user-bonus',
  ruleName: 'Award bonus to premium users',
  route: {
    eq: [{ path: 'input.action' }, { const: 'login' }]
  },
  conditions: [
    { expr: { callFn: ['is_premium_user'] } }
  ],
  actions: {
    onMatch: [
      {
        type: 'enrich',
        impl: { kind: 'api', id: 'fetch_user_profile' },
        write: { targetPath: 'output.profile', mode: 'replace' }
      }
    ]
  }
};

// 6. Add rule to engine
const ruleSet = engine.getRuleSet();
ruleSet.addRule(rule);

// 7. Evaluate
const result = await engine.evaluate({
  input: { userId: 'u123', action: 'login', tier: 'premium' }
});

console.log(result.output); // { profile: { name: 'John Doe', credits: 100 } }

Core Concepts

1. Rules

A rule consists of three main parts:

  • Route: Determines if the rule should be evaluated (fast pre-filter)
  • Conditions: Boolean expressions that must pass for the rule to match
  • Actions: Operations to perform when the rule matches (or doesn't match)
interface XRule {
  ruleId: string;
  ruleName?: string;
  priority?: number;
  enabled?: boolean;
  route?: XExpr;
  conditions?: XCondition[];
  actions?: {
    onMatch?: XAction[];
    onNoMatch?: XAction[];
  };
  params?: Record<string, unknown>;
}

2. Expression Language

The expression language supports:

Boolean Operators

{ and: [expr1, expr2, ...] }
{ or: [expr1, expr2, ...] }
{ not: expr }

Comparisons

{ eq: [left, right] }      // Equal
{ neq: [left, right] }     // Not equal
{ gt: [left, right] }      // Greater than
{ gte: [left, right] }     // Greater than or equal
{ lt: [left, right] }      // Less than
{ lte: [left, right] }     // Less than or equal
{ in: [value, array] }     // Value in array
{ exists: path }           // Path exists
{ regex: [value, pattern] } // Regex match

Value References

{ path: 'input.userId' }        // Access input data
{ param: 'threshold' }          // Access rule parameters
{ const: 'some-value' }         // Constant value
{ baggage: 'previousResult' }   // Access baggage from previous rules

Function Calls

{ callFn: ['functionName', arg1, arg2, ...] }

3. Actions

Actions are executed when a rule matches (or doesn't match):

Enrich Action

Call an API and write the result to output:

{
  type: 'enrich',
  impl: { kind: 'api', id: 'get_user_data' },
  write: { targetPath: 'output.userData', mode: 'replace' }
}

Detect Action

Pattern detection with windowing:

{
  type: 'detect',
  impl: {
    kind: 'window',
    id: 'login_attempts',
    window: { size: 5, unit: 'minutes' },
    threshold: 3
  },
  write: { targetPath: 'output.suspicious', mode: 'replace' }
}

Aggregate Action

Aggregate values over time:

{
  type: 'aggregate',
  impl: {
    kind: 'sum',
    id: 'total_spent',
    ttl: '1d'
  },
  write: { targetPath: 'output.totalSpent', mode: 'replace' }
}

4. Function Registry

Register custom functions that can be called from conditions:

import { createFunctionRegistry } from 'nx-rules';

const fn = createFunctionRegistry();

// Simple boolean function
fn.register('is_valid_email', async (ctx) => {
  const email = ctx.input.email;
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
});

// Function with baggage (additional data)
fn.register('calculate_risk_score', async (ctx) => {
  const score = ctx.input.amount * ctx.params.multiplier;
  return {
    passed: score > 100,
    baggage: { riskScore: score }
  };
});

Function Context:

interface XFnContext {
  input: Record<string, unknown>;      // Input data
  content?: string;                    // Optional content string
  facts?: Record<string, unknown>;     // Facts about the evaluation
  params?: Record<string, unknown>;    // Rule parameters
  baggage?: Record<string, unknown>;   // Accumulated baggage
  args: unknown[];                     // Function arguments
  now: () => number;                   // Current timestamp
  state?: StateStore;                  // State store
  llm?: LLMProvider;                   // LLM provider
  trace?: (event: any) => void;        // Trace function
}

5. API Registry

Register API integrations for enrichment:

import { createApiRegistry } from 'nx-rules';

const api = createApiRegistry();

// Successful API call
api.register('get_user_profile', async (ctx) => {
  const userId = ctx.input.userId;
  const profile = await fetchUserProfile(userId);
  
  return {
    ok: true,
    data: profile
  };
});

// API call with error handling
api.register('charge_payment', async (ctx) => {
  try {
    const result = await processPayment(ctx.input.amount);
    return { ok: true, data: result };
  } catch (error) {
    return {
      ok: false,
      error: {
        code: 'PAYMENT_FAILED',
        message: error.message
      }
    };
  }
});

API Result:

type XApiResult = 
  | { ok: true; data: any; meta?: Record<string, unknown> }
  | { ok: false; error: { code: string; message: string; details?: any } };

Advanced Features

State Management

Use the built-in state store for TTL-based memory:

import { createEngine, createStateStore } from 'nx-rules';

const state = createStateStore({
  maxEntries: 100000,
  cleanupIntervalMs: 60000
});

const engine = createEngine({
  functionRegistry: fn,
  apiRegistry: api,
  stateStore: state
});

// In your function
fn.register('track_login', async (ctx) => {
  const userId = ctx.input.userId;
  const key = `login:${userId}`;
  
  // Remember this login for 1 hour
  await ctx.state.remember(key, { timestamp: Date.now() }, '1h');
  
  // Recall previous logins
  const history = await ctx.state.recall({ key });
  
  return history.length > 5; // Flag if more than 5 logins in 1 hour
});

LLM Integration

Use local LLM models for classification and extraction:

import { 
  createEngine, 
  createLLMProvider, 
  createOllamaAdapter,
  registerLLMFunctions 
} from 'nx-rules';

// Create LLM provider with Ollama backend
const adapter = createOllamaAdapter('http://localhost:11434');
const llm = createLLMProvider({
  models: {
    llama2: 'llama2:7b',
    tiny: 'phi3:mini'
  }
}, adapter);

// Register LLM functions
const fn = createFunctionRegistry();
registerLLMFunctions(fn, llm);

const engine = createEngine({
  functionRegistry: fn,
  apiRegistry: api,
  llmProvider: llm
});

// Use in rules
const rule = {
  ruleId: 'classify-content',
  conditions: [
    {
      expr: {
        callFn: [
          'llm.tiny.classify',
          {
            scope: 'content',
            labels: ['spam', 'legitimate', 'promotional'],
            minConfidence: 0.7
          }
        ]
      }
    }
  ]
};

Templates

Use built-in templates for common patterns:

import { createDefaultTemplateRegistry } from 'nx-rules';

const templates = createDefaultTemplateRegistry();

// Create a regex detection rule from template
const rule = templates.apply('detect.regex.pipeline.v1', {
  ruleId: 'detect-email',
  route: { entityClass: 'Content', eventClass: 'Scan' },
  regex: '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}',
  findingType: 'EMAIL_ADDRESS',
  writeTargetPath: 'output.findings'
});

Tracing

Enable detailed execution tracing:

const result = await engine.evaluate({
  input: { userId: 'u123' },
  config: { trace: true }
});

// Examine trace events
result.trace.forEach(event => {
  console.log(event.type, event.ruleId, event.timestamp);
});

Trace Event Types:

  • route_start / route_end
  • condition_start / condition_end
  • action_start / action_end
  • write
  • error

Configuration

Engine Configuration

interface XEngineConfig {
  matchPolicy?: 'FIRST_MATCH' | 'ALL_MATCH';  // Stop after first match or evaluate all
  conflictPolicy?: 'FIRST_WRITE_WINS' | 'LAST_WRITE_WINS' | 'MERGE' | 'ERROR';
  defaultPriority?: number;
  trace?: boolean;
  maxRulesPerEvaluation?: number;
  maxActionsPerRule?: number;
}

Rule Priority

Rules are evaluated in priority order (higher priority first):

const highPriorityRule = {
  ruleId: 'critical-check',
  priority: 100,
  // ...
};

const normalRule = {
  ruleId: 'standard-check',
  priority: 0,  // default
  // ...
};

System Functions

Built-in functions available out of the box:

Conditions

  • always() - Always returns true
  • never() - Always returns false

Windowing

  • window.count(id, window, threshold) - Count events in time window
  • window.sum(id, window, threshold, valuePath) - Sum values in window
  • window.avg(id, window, threshold, valuePath) - Average values in window

Memory

  • memory.exists(key) - Check if key exists in state
  • memory.count(selector) - Count items matching selector
  • memory.recall(selector) - Recall items from state

Detection Helpers

  • detect.threshold(value, threshold) - Simple threshold check
  • detect.range(value, min, max) - Range check
  • detect.pattern(value, pattern) - Regex pattern match

Error Handling

The engine provides detailed error information:

const result = await engine.evaluate({ input: data });

if (result.hasErrors) {
  result.errors.forEach(error => {
    console.error(`Rule ${error.ruleId}, Step ${error.step}:`, error.error);
  });
}

Testing

Run the integration test:

npm run test:integration

Create your own tests:

import { describe, test, expect } from 'vitest';
import { createEngine, createFunctionRegistry, createApiRegistry } from 'nx-rules';

describe('My Rules', () => {
  test('should match premium users', async () => {
    const fn = createFunctionRegistry();
    const api = createApiRegistry();
    
    fn.register('is_premium', async (ctx) => ctx.input.tier === 'premium');
    
    const engine = createEngine({ functionRegistry: fn, apiRegistry: api });
    const ruleSet = engine.getRuleSet();
    
    ruleSet.addRule({
      ruleId: 'premium-check',
      conditions: [{ expr: { callFn: ['is_premium'] } }]
    });
    
    const result = await engine.evaluate({
      input: { tier: 'premium' }
    });
    
    expect(result.matchedRules).toHaveLength(1);
  });
});

Performance Tips

  1. Use Routes Effectively: Routes are evaluated before conditions, so use them to quickly filter out irrelevant rules
  2. Optimize Function Calls: Cache expensive computations in baggage
  3. Limit State Store Size: Configure maxEntries based on your memory constraints
  4. Use FIRST_MATCH: If you only need one rule to match, use FIRST_MATCH policy
  5. Batch Evaluations: Process multiple inputs in parallel when possible

TypeScript Support

Full TypeScript definitions are included:

import type {
  XRule,
  XExpr,
  XCondition,
  XAction,
  XEvaluationResult,
  XEngineConfig,
  FunctionRegistry,
  ApiRegistry
} from 'nx-rules';

API Reference

Core Exports

  • createEngine(options) - Create a rules engine
  • createFunctionRegistry() - Create a function registry
  • createApiRegistry() - Create an API registry
  • createStateStore(options?) - Create a state store
  • createLLMProvider(config?, adapter?) - Create an LLM provider
  • createOllamaAdapter(baseUrl?) - Create an Ollama adapter
  • createTemplateRegistry() - Create a template registry
  • registerSystemFunctions(registry) - Register built-in functions
  • registerLLMFunctions(registry, provider) - Register LLM functions

Utilities

  • getPath(obj, path) - Get value at path
  • setPath(obj, path, value) - Set value at path
  • deepMerge(obj1, obj2) - Deep merge objects
  • deepClone(obj) - Deep clone object
  • parseTtl(ttl) - Parse TTL string to milliseconds
  • hashObject(obj) - Hash object to string
  • generateId() - Generate unique ID

Examples

Check out the .tests/ directory for complete examples:

  • Basic integration test
  • Function and API registration
  • Rule evaluation
  • State management
  • LLM integration

Contributing

Contributions are welcome! Please see the repository for guidelines.

License

MIT

Links

Support

For questions and support, please open an issue on GitHub.