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

@arbitre/core

v0.2.0

Published

Rete-inspired production rule engine for declarative state governance

Readme

@arbitre/core

Rete-inspired production rule engine for declarative state governance.

What's New in 0.2.0

  • Beta network / multi-fact joins — Rules can match across multiple fact types with join constraints
  • Temporal operators — Time-based conditions (since, within, after) with clock abstraction
  • Clock abstraction — Real and virtual clocks for deterministic testing
  • Windowed accumulation — Aggregate facts over sliding time windows
  • Cross-type accumulation — Accumulate across multiple fact types in a single rule
  • Custom accumulate functions — Register domain-specific aggregation logic
  • Pattern validation — Compile-time validation of fact patterns and join constraints

Overview

@arbitre/core implements a forward-chaining production rule engine based on the RETE algorithm. It evaluates rules against a working memory of state and facts, resolving conflicts via salience and activation groups, and applies changes through a pipeline-style action system.

Key features:

  • Match-Resolve-Act cycle with configurable limits and conflict resolution
  • Alpha network for fast single-condition filtering
  • Beta network for multi-fact joins with bind variables
  • Truth Maintenance System (TMS) for automatic retraction of derived state
  • Namespaced state with dot-path access
  • Temporal operators for time-based reasoning
  • Accumulate nodes for aggregation patterns

Quick Start

import { createSession } from "@arbitre/core";

const session = createSession({
  rules: [
    {
      name: "high-temp-alert",
      when: { $gt: [{ $path: "sensors.temperature" }, 100] },
      then: [{ $set: { "alerts.overheating": true } }],
      salience: 10,
    },
  ],
  initialState: {
    sensors: { temperature: 105 },
    alerts: { overheating: false },
  },
});

const result = session.fire();
// result.rulesFired === 1
// session.getPath("alerts.overheating") === true

Multi-Fact Joins

const session = createSession({
  factTypes: [
    { type: "order", fields: { customerId: "string", total: "number" } },
    { type: "customer", fields: { id: "string", tier: "string" } },
  ],
  rules: [
    {
      name: "vip-large-order",
      patterns: [
        { type: "order", bind: "o", constraints: [{ field: "total", op: "gt", value: 1000 }] },
        { type: "customer", bind: "c", constraints: [{ field: "tier", op: "eq", value: "vip" }] },
      ],
      when: { $eq: [{ $path: "o.customerId" }, { $path: "c.id" }] },
      then: [{ $set: { "notifications.vipLargeOrder": true } }],
    },
  ],
});

session.assertFact("customer", { id: "c1", tier: "vip" });
session.assertFact("order", { customerId: "c1", total: 2500 });
session.fire();

Temporal Operators

import { createSession, createVirtualClock } from "@arbitre/core";

const clock = createVirtualClock(0);
const session = createSession({
  clock,
  rules: [
    {
      name: "idle-timeout",
      when: { $since: [{ $path: "user.lastActive" }, 30000] },
      then: [{ $set: { "user.status": "idle" } }],
    },
  ],
  initialState: { user: { lastActive: 0, status: "active" } },
});

clock.advance(31000);
session.tick();
// user.status === "idle"

Architecture

Match-Resolve-Act Cycle

Each call to session.fire() runs a forward-chaining loop:

  1. Match — Evaluate all rule conditions against current state/facts
  2. Resolve — Collect activated rules into the agenda, order by salience
  3. Act — Fire the highest-priority rule, apply then stages, repeat

The cycle terminates when no rules activate or limits are reached.

Alpha Network

Single-condition nodes that filter facts by type and field constraints. Each fact assertion propagates through alpha nodes to determine which rules could potentially match.

Beta Network

Join nodes that combine partial matches from multiple alpha nodes. Beta nodes maintain a token memory of partial matches and produce complete matches when join constraints are satisfied.

Agenda

Priority queue of activated rule instances. Conflict resolution strategy:

  • Salience — Higher salience fires first (default: 0)
  • Activation groups — Only one rule per group fires per cycle
  • Recency — Among equal-salience rules, most recently activated wins

Truth Maintenance System (TMS)

Tracks which rules produced which state changes. When a rule's conditions become false, TMS can automatically retract its contributions:

  • autoRetract: "all" — Retract all derived state when justification is lost
  • autoRetract: "ui-contributions" — Retract only UI-namespace contributions

Namespaces

State is organized as a flat dot-path map. Paths like "sensors.temperature" and "alerts.overheating" provide logical grouping without nested object complexity.

API Reference

Exports

// Main entry point
import { createSession } from "@arbitre/core";

// Testing utilities
import { createTestSession, fireWith, assertRuleFired, assertRuleNotFired, assertState } from "@arbitre/core/testing";

// Debug utilities
import { explainResult, formatChanges, dumpState } from "@arbitre/core/debug";

createSession(config: SessionConfig): RuleSession

Factory function that compiles rules and returns a session instance.

SessionConfig:

| Field | Type | Description | |-------|------|-------------| | rules | ProductionRule[] | Rules to compile into the network | | initialState | Record<string, unknown> | Starting state | | operators | OperatorRegistryConfig | Custom expression operators | | limits | SessionLimits | Cycle/firing limits | | tms | TmsConfig | Truth maintenance configuration | | errorHandling | "strict" \| "lenient" | Error behavior | | factTypes | FactTypeDefinition[] | Fact type schemas | | accumulates | AccumulateConfig[] | Accumulation definitions | | accumulateFunctions | Record<string, CustomAccumulateFunction> | Custom aggregation functions | | clock | ArbiterClock | Clock for temporal operators | | autoFireOnFactChange | boolean | Auto-fire on fact assert/retract |

RuleSession

| Method | Description | |--------|-------------| | registerRule(rule) | Add a rule at runtime | | removeRule(name) | Remove a rule by name | | assert(path, value) | Assert a state value | | retract(path) | Retract a state value | | fire() | Run the match-resolve-act cycle | | subscribe(path, cb) | Watch a path for changes | | update(path, value) | Assert + fire in one call | | getState() | Get full state snapshot | | getPath(path) | Get value at a dot-path | | setFocus(group) | Set the active activation group | | dispose() | Clean up resources | | assertFact(type, data) | Assert a fact, returns fact ID | | retractFact(id) | Retract a fact by ID | | getFacts(type) | Get all facts of a type | | tick(now?) | Advance temporal evaluation | | scheduleRule(name, opts) | Schedule a rule for future firing | | cancelSchedule(name) | Cancel a scheduled rule |

ProductionRule

interface ProductionRule<TState> {
  name: string;
  when: TypedQuery<TState>;          // kuery expression
  then: ThenStage<TState>[];         // pipeline stages
  else?: ThenStage<TState>[];        // stages when condition is false
  salience?: number;                  // priority (default: 0)
  activationGroup?: string;           // mutex group
  onConflict?: "override" | "warn" | "error";
  enabled?: boolean;
  description?: string;
  expires?: number;                   // TTL in ms
  patterns?: FactPattern[];           // multi-fact patterns
  accumulate?: AccumulateConfig[];    // aggregation configs
}

Action Types (ThenStage operators)

| Operator | Description | |----------|-------------| | $set | Set values at paths | | $unset | Remove values at paths | | $inc | Increment numeric values | | $push | Append to arrays | | $pull | Remove from arrays | | $merge | Shallow merge objects |

Expression Operators

Conditions use kuery expression syntax:

| Operator | Description | |----------|-------------| | $eq | Equality | | $ne | Not equal | | $gt / $gte | Greater than (or equal) | | $lt / $lte | Less than (or equal) | | $and / $or / $not | Logical combinators | | $in / $nin | Set membership | | $exists | Path existence check | | $path | Resolve a dot-path value | | $regex | Regular expression match |

Temporal Operators

| Operator | Description | |----------|-------------| | $since | True if time elapsed since a timestamp exceeds threshold | | $within | True if event occurred within a time window | | $after | True if current time is after a given timestamp |

Error Codes

| Code | Description | |------|-------------| | ARBITER_RULE_COMPILATION_FAILED | Rule could not be compiled | | ARBITER_INVALID_PATH | Invalid dot-path | | ARBITER_INVALID_OPERATOR | Unknown operator in then/when | | ARBITER_CYCLE_LIMIT_EXCEEDED | Max cycles reached | | ARBITER_FIRING_LIMIT_EXCEEDED | Max firings reached | | ARBITER_WRITE_CONFLICT | Multiple rules writing same path | | ARBITER_TMS_RETRACT_FAILED | TMS retraction error | | ARBITER_INVALID_NAMESPACE | Invalid namespace in path | | ARBITER_SESSION_DISPOSED | Operation on disposed session | | ARBITER_PROTOTYPE_POLLUTION | Prototype pollution attempt blocked | | ARBITER_EXPRESSION_EVAL_FAILED | Expression evaluation error | | ARBITER_RULE_NOT_FOUND | Referenced rule does not exist | | ARBITER_INVALID_CLOCK_OPERATION | Invalid clock operation |

Testing Utilities

Import from @arbitre/core/testing:

import { createTestSession, fireWith, assertRuleFired, assertRuleNotFired, assertState } from "@arbitre/core/testing";

// Quick session for tests
const session = createTestSession(rules, { counter: 0 });

// Fire rules and get result in one call
const result = fireWith(rules, { temperature: 150 });

// Assertions
assertRuleFired(result, "high-temp-alert");
assertRuleNotFired(result, "low-temp-alert");
assertState(session, "counter", 1);

Debug Utilities

Import from @arbitre/core/debug:

import { explainResult, formatChanges, dumpState } from "@arbitre/core/debug";

const result = session.fire();

console.log(explainResult(result));
// Fired 2 rules in 1 cycles
// Changes:
//   alerts.overheating: false → true (by high-temp-alert)
// Warnings: none

console.log(formatChanges(result));
console.log(dumpState(session));

Performance

The RETE network provides efficient incremental evaluation:

  • Alpha network — O(1) per fact type lookup, linear in constraints per node
  • Beta network — Incremental join; only new tokens propagate
  • State changes — Only affected rules re-evaluate (no full re-scan)
  • Cycle limits — Configurable guards prevent runaway inference

Typical performance for rule sets under 100 rules with moderate fact counts:

  • Single-fact evaluation: < 1ms
  • Multi-fact joins (10 facts × 10 facts): < 5ms
  • Full cycle with TMS: < 10ms

Rule Builder

For programmatic rule construction, use the defineRule fluent builder:

import { defineRule } from "@arbitre/core";

const rule = defineRule("high-value-order")
  .when({ $gt: [{ $path: "order.total" }, 1000] })
  .then([{ $set: { "flags.highValue": true } }])
  .salience(5)
  .description("Flag orders over $1000")
  .build();

Observability

Lifecycle Hooks

import { createSession } from "@arbitre/core";
import type { SessionHooks } from "@arbitre/core";

const hooks: SessionHooks = {
  onRuleActivated: (e) => console.log(`Activated: ${e.ruleName}`),
  onRuleFired: (e) => console.log(`Fired: ${e.ruleName}`),
  onRuleDeactivated: (e) => console.log(`Deactivated: ${e.ruleName}`),
  onFactAsserted: (e) => console.log(`Fact+: ${e.factType}#${e.factId}`),
  onFactRetracted: (e) => console.log(`Fact-: ${e.factType}#${e.factId}`),
  onCycleStart: (e) => console.log(`Cycle ${e.cycleNumber} start`),
  onCycleEnd: (e) => console.log(`Cycle ${e.cycleNumber} end`),
};

const session = createSession({ rules, hooks });

Custom Logger

import type { ArbiterLogger } from "@arbitre/core";

const logger: ArbiterLogger = {
  debug: (msg, ctx) => myLogger.debug(msg, ctx),
  info: (msg, ctx) => myLogger.info(msg, ctx),
  warn: (msg, ctx) => myLogger.warn(msg, ctx),
  error: (msg, ctx) => myLogger.error(msg, ctx),
};

const session = createSession({ rules, logger });

Introspection

const session = createSession({ rules, initialState });
session.fire();

const i = session.introspect;
console.log(i.getRegisteredRules());  // ["rule-a", "rule-b"]
console.log(i.getActiveRules());      // ["rule-a"]
console.log(i.getAgendaEntries());    // [] (empty after fire)
console.log(i.getFactCounts());       // { order: 3 }
console.log(i.getMetrics());          // { totalRulesFired: 2, ... }

Dependencies

| Dependency | Role | |------------|------| | kuery | Expression language for rule conditions (when clauses) |

License

MIT