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

@axi-engine/expressions

v0.2.5

Published

[![NPM version](https://img.shields.io/npm/v/@axi-engine/expressions.svg)](https://www.npmjs.com/package/@axi-engine/expressions)

Downloads

256

Readme

@axi-engine/expressions

NPM version

A flexible, type-safe, and extensible engine for evaluating declarative logical expressions. It allows you to define complex game logic (like quest conditions, dialogue triggers, or AI behavior) in data files (e.g., JSON) instead of hard-coding it.

Key Features

  • Declarative Logic: Define complex conditions as data, making them easy to author, modify, and store.
  • Type-Safe: Built entirely with TypeScript, providing strong type checking and autocompletion.
  • Extensible: Easily add your own custom expression types and logic by creating new handlers.
  • Asynchronous by Design: Core evaluation is promise-based, allowing for future async operations.
  • Decoupled: Depends only on @axi-engine/utils for shared types and a simple DataSource interface, making it easy to integrate with any state management system.

Installation

npm install @axi-engine/expressions

Core Concepts

  • Expression: A plain JavaScript object that defines a logical condition (e.g., comparison, and, or).
  • DataSource: A simple interface ({ get(path), has(path) }) that provides the data against which expressions are evaluated. This can be your game's state manager, a local scope, or any other data source.
  • ExpressionEvaluator: The main class that takes an Expression and a DataSource and resolves them to a boolean result.

Usage

Here's how to set up the evaluator and resolve a simple expression.

import { createExpressionEvaluator } from '@axi-engine/expressions';
import type { Expression } from '@axi-engine/expressions';
import type { DataSource } from '@axi-engine/utils';

// 1. Use the builder to create an CoreExpressionEvaluator.
const evaluator = configureExpressions().withDefaults().build();

// 2. Define a data source that provides the state
const myGameDataSource: DataSource = {
  get: (path) => {
    const state = new Map<string, any>([
      ['player.level', 10],
      ['player.class', 'mage'],
      ['gate.locked', true],
    ]);
    return state.get(path.join('.'));
  },
  has: (path) => { /* ... */ }
};

// 3. Define an expression, for example in a JSON file or directly in code
const canOpenGate: Expression = {
  and: [
    {
      comparison: {
        op: '>=',
        left: { path: ['player', 'level'] },
        right: { value: 5 }
      }
    },
    {
      comparison: {
        op: '==',
        left: { path: ['gate', 'locked'] },
        right: { value: true }
      }
    }
  ]
};

// 4. Resolve the expression
async function checkCondition() {
  const result = await evaluator.resolve(canOpenGate, myGameDataSource);
  console.log('Can the player open the gate?', result); // -> true
}

checkCondition();

Built-in Expressions

Here are some examples of the core expression types available out of the box.

| Type | Example | Description | |:-----------------|:----------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------| | comparison | { "comparison": { "op": ">", "left": { "path": "p.hp" }, "right": { "value": 50 } } } | Compares two values. | | and | { "and": [ { ...expr1 }, { ...expr2 } ] } | Returns true if all child expressions are true. | | or | { "or": [ { ...expr1 }, { ...expr2 } ] } | Returns true if at least one child expression is true. | | not | { "not": { "exists": "p.curse" } } | Inverts the result of a child expression. | | exists | { "exists": "p.inventory.key" } | Returns true if a value exists at the given path. | | in | { "in": { "value": { "path": "p.class" }, "array": ["mage", "warlock"] } } | Checks if a value is present in an array. The array can also be a reference: "array": { "path": "q.valid_classes" } | | chance | { "chance": { "value": 15.5 } } | Returns true based on a 15.5% probability. | | literal | { "literal": true } | Directly returns true or false. Useful for debugging. |

Extending with Custom Expressions

Adding your own expression types is straightforward. Let's create a between expression.

1. Define the Expression Type Create an interface for your new expression.

// my-expressions.ts
import type { Operand } from '@axi-engine/expressions';

export interface BetweenExpression {
  between: {
    value: Operand,
    min: Operand,
    max: Operand
  }
}

2. Augment the Global Definitions

Use TypeScript's declaration merging to make the evaluator aware of your new type.

// my-expressions.ts
import type { ExpressionDefinitions } from '@axi-engine/expressions';

declare module '@axi-engine/expressions' {
  export interface ExpressionDefinitions {
    between: BetweenExpression;
  }
}

3. Create the Handler

Write the class that contains the evaluation logic.

// BetweenExpressionHandler.ts
import { ExpressionHandler, resolveOperandAsScalar } from '@axi-engine/expressions';
import { isNumber } from '@axi-engine/utils';

class BetweenExpressionHandler implements ExpressionHandler<BetweenExpression> {
  type: 'between' = 'between';

  async resolve(exp: BetweenExpression, context: ExpressionEvaluatorContext) {
    const value = resolveOperandAsScalar(exp.between.value, context.source());
    const min = resolveOperandAsScalar(exp.between.min, context.source());
    const max = resolveOperandAsScalar(exp.between.max, context.source());

    if (isNumber(value) && isNumber(min) && isNumber(max)) {
      return value >= min && value <= max;
    }
    return false;
  }
}

4. Register the Handler

Pass your new handler to the factory function during initialization.

import { createExpressionEvaluator } from '@axi-engine/expressions';

const myHandlers = [new BetweenExpressionHandler()];
const evaluator = createExpressionEvaluator(myHandlers);

// Now you can use it!
const expression = {
  between: { value: { path: 'player.level' }, min: { value: 10 }, max: { value: 20 } }
};

API Reference

Browse the API Documentation here

License

MIT