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

evalla

v0.3.1

Published

Safe math evaluator with variables, dependencies, and precision

Readme

evalla

Safe math evaluator with variables, dependencies, and precision.

Live Demo

import { evalla } from 'evalla';

const result = await evalla([
  { name: 'a', expr: 'c + 5' },          // depends on c
  { name: 'b', expr: 'a * 2' },          // depends on a
  { name: 'c', expr: '1 + 1' }           // base constant
]);

console.log(result.values.a.toString()); // "7"
console.log(result.values.b.toString()); // "14"
console.log(result.values.c.toString()); // "2"
console.log(result.order);               // ['c', 'a', 'b']

Features

  • Decimal Precision: Uses decimal.js internally for accurate arithmetic
  • Result Formatting: Optional formatResults() function to format output to specific decimal places
  • Decimal, Boolean & Null Output: Results can be Decimal, boolean, or null for natural mathematical expressions
  • Variable References: Support dependencies between expressions
  • Dot-Traversal: Reference nested properties (e.g., point.x, offset.y)
  • Topological Ordering: Evaluates in correct dependency order (DAG)
  • Circular Detection: Throws error on circular dependencies
  • Safe Evaluation: Parses with Peggy parser, evaluates AST (no eval() or Function())
  • Keywords as Variables: Unlike JavaScript, keywords like return, if, etc. can be used as variable names
  • Namespaces: Built-in $math, $unit, and $angle functions

Installation

npm install evalla

Usage

Input Format

interface ExpressionInput {
  name: string;         // Variable name (cannot start with $)
  expr?: string;        // Math expression (optional if value is provided)
  value?: any;          // Direct value (optional if expr is provided)
}

You must provide either expr or value per item. The value property allows you to pass objects and arrays directly without stringifying them into expressions.

Important Type Restrictions:

  • Objects and arrays can ONLY be provided via value property
  • Expressions cannot create or return objects/arrays (only Decimal, boolean, or null)
  • Objects/arrays from value are stored in context for property access but not included in output values
  • This ensures clean output types: Record<string, Decimal | boolean | null>

Using expressions:

const result = await evalla([
  { name: 'width', expr: '100' },
  { name: 'height', expr: '50' }
]);

Using direct values:

const result = await evalla([
  { name: 'point', value: { x: 10, y: 20 } },
  { name: 'offset', value: { x: 5, y: 10 } }
]);

Mixing both:

const result = await evalla([
  { name: 'data', value: { width: 100, height: 50 } },
  { name: 'area', expr: 'data.width * data.height' }
]);

Output Format

interface EvaluationResult {
  values: Record<string, Decimal | boolean | null>;  // Computed values
  order: string[];                                   // Evaluation order (topologically sorted)
}

Results can be:

  • Decimal: Numeric values with arbitrary precision
  • boolean: Results from comparisons (a > b) or boolean literals (true, false)
  • null: The null literal or null branches in ternary expressions

Examples

Basic Arithmetic with Precision

const result = await evalla([
  { name: 'x', expr: '0.1 + 0.2' }
]);

console.log(result.values.x.toString()); // "0.3" (exact!)

Variable Dependencies

const result = await evalla([
  { name: 'd', expr: 'c * 2' },
  { name: 'b', expr: 'a + 10' },
  { name: 'c', expr: 'b * 3' },
  { name: 'a', expr: '5' }
]);

// Automatically orders: a, b, c, d
console.log(result.order); // ['a', 'b', 'c', 'd']
console.log(result.values.d.toString()); // "90"

Arrays and Objects (via value property)

Important: Arrays and objects can ONLY be supplied via the value property. They cannot be created in expressions.

Arrays passed via value can be accessed with computed property syntax:

const result = await evalla([
  { name: 'data', value: [10, 20, 30, 40, 50] },
  { name: 'first', expr: 'data[0]' },
  { name: 'sum', expr: 'data[0] + data[1] + data[2]' }
]);

console.log(result.values.first.toString()); // "10"
console.log(result.values.sum.toString());   // "60"
// Note: result.values.data is undefined (arrays from value property are not in output)

Object Property Access (via value property)

Objects must be passed via the value property, then accessed with dot notation:

const result = await evalla([
  { name: 'point', value: {x: 10, y: 20} },
  { name: 'offset', value: {x: 5, y: 10} },
  { name: 'resultX', expr: 'point.x + offset.x' },
  { name: 'resultY', expr: 'point.y + offset.y' }
]);

console.log(result.values.resultX.toString()); // "15"
console.log(result.values.resultY.toString()); // "30"
// Note: result.values.point and result.values.offset are undefined (objects from value property are not in output)

Special Property Names: Use string literals in computed property access for property names with special characters:

const result = await evalla([
  { name: 'obj', value: { 'y-y': 20, 'prop name': 42 } },
  { name: 'hyphen', expr: 'obj["y-y"]' },      // String literal for hyphenated property
  { name: 'space', expr: 'obj["prop name"]' }  // String literal for property with space
]);

console.log(result.values.hyphen.toString()); // "20"
console.log(result.values.space.toString());  // "42"

Note: Objects and arrays cannot be created within expressions. Use the value property to pass them, then access properties/elements in expressions.

Nested Property Access

const result = await evalla([
  { name: 'data', value: {pos: {x: 5, y: 10}, scale: 2} },
  { name: 'scaledX', expr: 'data.pos.x * data.scale' }
]);

console.log(result.values.scaledX.toString()); // "10"

Reserved Values

Algebra, not code: evalla is designed for natural mathematical expressions, not programming. A few special values are reserved as first-class mathematical primitives and cannot be used as variable names:

  • true - Boolean true value
  • false - Boolean false value
  • null - Null value (for missing/undefined results)
  • Infinity - Mathematical infinity

These reserved values allow evalla to return boolean results from comparisons and handle edge cases naturally, just like in mathematical notation.

// ❌ Cannot use reserved values as variable names
await evalla([{ name: 'true', expr: '10' }]);     // ValidationError
await evalla([{ name: 'false', expr: '10' }]);    // ValidationError
await evalla([{ name: 'null', expr: '10' }]);     // ValidationError
await evalla([{ name: 'Infinity', expr: '10' }]); // ValidationError

// ✅ Can use them as values
const result = await evalla([
  { name: 'isValid', expr: 'true' },
  { name: 'isEmpty', expr: 'false' },
  { name: 'missing', expr: 'null' }
]);

Why reserve these? Unlike programming keywords (which evalla allows as variable names), these mathematical primitives need special handling to support boolean logic and comparisons as first-class results.

Boolean Output

Results can now be boolean values, not just numbers. This enables natural mathematical questions:

Standalone Comparisons

Comparisons return true or false:

const result = await evalla([
  { name: 'slope', expr: '0.75' },
  { name: 'isSteep', expr: 'slope > 0.5' }  // Returns boolean true
]);

console.log(result.values.isSteep); // true
console.log(typeof result.values.isSteep); // "boolean"

All comparison operators return boolean:

  • <, >, <=, >= - Numeric comparisons
  • =, == - Equality (both work the same)
  • != - Inequality
  • &&, ||, ! - Logical operators

Boolean Branches in Ternary

Ternary expressions can have boolean or null branches:

const result = await evalla([
  { name: 'score', expr: '85' },
  { name: 'passed', expr: 'score >= 60 ? true : false' },
  { name: 'bonus', expr: 'score >= 90 ? 10 : null' }
]);

console.log(result.values.passed);  // true (boolean)
console.log(result.values.bonus);   // null

Boolean Literals

Use true and false directly:

const result = await evalla([
  { name: 'enabled', expr: 'true' },
  { name: 'disabled', expr: 'false' },
  { name: 'result', expr: 'enabled && !disabled' }
]);

console.log(result.values.result); // true

Equality Operators

evalla supports two equality operators that work identically:

Single Equals = (Algebraic)

In mathematics, = tests equality. evalla follows this convention:

const result = await evalla([
  { name: 'x', expr: '5' },
  { name: 'y', expr: '5' },
  { name: 'equal', expr: 'x = y' }  // Algebraic equality
]);

console.log(result.values.equal); // true

Double Equals == (Programmer-Friendly)

For programmers familiar with ==, it works exactly the same:

const result = await evalla([
  { name: 'a', expr: '10' },
  { name: 'b', expr: '20' },
  { name: 'test', expr: '(a + 10) == b' }
]);

console.log(result.values.test); // true

No loose vs. strict: Unlike JavaScript, there's no distinction between = and == in evalla. Both = and == perform the same strict equality check. Use whichever feels more natural.

Formatting Results

By default, evalla returns full-precision Decimal values. For display purposes, you can format results to a specific number of decimal places using the formatResults() function:

import { evalla, formatResults } from 'evalla';

// Evaluate with full precision
const result = await evalla([
  { name: 'pi', expr: '3.14159265358979323846' },
  { name: 'oneThird', expr: '1/3' }
]);

// Format for display
const formatted = formatResults(result, { decimalPlaces: 7 });

console.log(formatted.values.pi.toString());       // "3.1415927"
console.log(formatted.values.oneThird.toString()); // "0.3333333"

Key points:

  • formatResults() returns a new result object (doesn't mutate the original)
  • Internal calculations always use full precision to maintain accuracy
  • Boolean and null values are preserved unchanged
  • Infinity values are preserved unchanged
  • You can format the same result multiple times with different precision levels

Common use cases:

// Financial precision (2 decimal places)
const financial = formatResults(result, { decimalPlaces: 2 });

// Engineering precision (6-7 decimal places)
const engineering = formatResults(result, { decimalPlaces: 6 });

// Scientific precision (custom)
const scientific = formatResults(result, { decimalPlaces: 10 });

Namespaces

Variables may not begin with $, this is reserved for namespaces for built-in functions and constants.

$math Namespace

Mathematical constants and functions:

const result = await evalla([
  { name: 'circumference', expr: '2 * $math.PI * 10' },
  { name: 'absVal', expr: '$math.abs(-42)' },
  { name: 'sqrtVal', expr: '$math.sqrt(16)' },
  { name: 'maxVal', expr: '$math.max(10, 5, 20, 3)' }
]);

Available:

  • Constants: PI, E, SQRT2, SQRT1_2, LN2, LN10, LOG2E, LOG10E
  • Functions: abs, sqrt, cbrt, floor, ceil, round, trunc, sin, cos, tan, asin, acos, atan, atan2, exp, ln, log, log10, log2, pow, min, max

$unit Namespace

Unit conversion functions:

const result = await evalla([
  { name: 'inches', expr: '$unit.mmToInch(25.4)' },
  { name: 'mm', expr: '$unit.inchToMm(1)' }
]);

Available:

  • mmToInch, inchToMm
  • cmToInch, inchToCm
  • mToFt, ftToM

$angle Namespace

Angle conversion functions:

const result = await evalla([
  { name: 'radians', expr: '$angle.toRad(180)' },
  { name: 'degrees', expr: '$angle.toDeg($math.PI)' }
]);

Available:

  • toRad (degrees to radians)
  • toDeg (radians to degrees)

Circular Dependency Detection

try {
  await evalla([
    { name: 'a', expr: 'b + 1' },
    { name: 'b', expr: 'a + 1' }
  ]);
} catch (error) {
  console.log(error.message); // "Circular dependency detected: b -> a -> b"
}

Variable naming

Variables may not begin with a number, double underscore(__), or $ (see namespaces above).

Variables cannot use reserved values: true, false, null, Infinity (see Reserved Values).

Keywords as Variable Names

Unlike JavaScript, algebra-like variable names can include JavaScript keywords:

const result = await evalla([
  { name: 'return', expr: '10' },
  { name: 'if', expr: '20' },
  { name: 'for', expr: 'return + if' }
]);

console.log(result.values.for.toString()); // "30"

Security

Safe by design:

  • ❌ No access to eval(), Function(), or other dangerous globals
  • ❌ No access to process, require, or Node.js internals
  • ❌ No access to dangerous properties: prototype, __proto__, constructor, or any property starting with __
  • ❌ No function aliasing - namespace functions must be called, not assigned to variables
  • ✅ Only whitelisted functions in namespaces
  • ✅ Uses AST parsing (Peggy) + safe evaluation
  • ✅ Variable names cannot start with $ (reserved for system)
  • ✅ Sandboxed scope with Object.create(null)
  • ✅ No prototype pollution

Blocked property access examples:

// These will throw SecurityError
await evalla([{ name: 'bad', expr: 'obj.prototype' }]);
await evalla([{ name: 'bad', expr: 'obj.__proto__' }]);
await evalla([{ name: 'bad', expr: 'obj.constructor' }]);
await evalla([{ name: 'bad', expr: 'obj.__defineGetter__' }]);

Blocked function aliasing examples:

// These will throw SecurityError - functions must be called with ()
await evalla([{ name: 'myabs', expr: '$math.abs' }]);
await evalla([{ name: 'mysqrt', expr: '$math.sqrt' }]);
// Correct usage - call the function:
await evalla([{ name: 'result', expr: '$math.abs(-5)' }]); // ✅ Works

Blocked namespace head usage:

// Namespace heads cannot be used as standalone values
await evalla([{ name: 'a', expr: '$math' }]); // ❌ EvaluationError
await evalla([{ name: 'a', value: 5 }, { name: 'b', expr: 'a < $angle' }]); // ❌ EvaluationError
// Correct usage - access properties or call methods:
await evalla([{ name: 'pi', expr: '$math.PI' }]); // ✅ Works
await evalla([{ name: 'rad', expr: '$angle.toRad(180)' }]); // ✅ Works

Properties starting with __ are blocked because they typically provide access to JavaScript internals that could be exploited for prototype pollution or other security vulnerabilities.

API

evalla(inputs: ExpressionInput[]): Promise<EvaluationResult>

Evaluates an array of math expressions with dependencies.

Parameters:

  • inputs: Array of { name, expr } objects

Returns:

  • Promise resolving to { values, order }

Throws:

  • ValidationError - Invalid input (missing name, duplicate names, invalid variable names)
    • Properties: variableName
  • CircularDependencyError - Circular dependencies detected
    • Properties: cycle (array of variable names in the cycle)
  • ParseError - Syntax/parsing errors in expressions (extends EvaluationError)
    • Properties: variableName, expression, line, column
  • EvaluationError - Runtime evaluation errors (undefined variables, type errors)
    • Properties: variableName
  • SecurityError - Attempt to access blocked properties (prototype, proto, constructor, __*)
    • Properties: property

All errors include structured details for programmatic access - no need to parse error messages!

Error Handling:

import { evalla, ParseError, SecurityError, CircularDependencyError, ValidationError, EvaluationError } from 'evalla';

try {
  const result = await evalla(inputs);
} catch (error) {
  // Catch ParseError first since it extends EvaluationError
  if (error instanceof ParseError) {
    console.error(`Syntax error in "${error.variableName}" at ${error.line}:${error.column}`);
    console.error(`Expression: ${error.expression}`);
  } else if (error instanceof EvaluationError) {
    console.error(`Runtime error in "${error.variableName}"`);
  } else if (error instanceof SecurityError) {
    console.error(`Security violation: attempted to access "${error.property}"`);
  } else if (error instanceof CircularDependencyError) {
    console.error(`Circular dependency: ${error.cycle.join(' -> ')}`);
  } else if (error instanceof ValidationError) {
    console.error(`Invalid variable: "${error.variableName}"`);
  }
}

checkSyntax(expr: string): SyntaxCheckResult

Checks the syntax of an expression without evaluating it. Useful for text editors to validate expressions before sending them for evaluation.

Parameters:

  • expr: The expression string to check

Returns:

  • Object with the following properties:
    • valid: boolean - Whether the syntax is valid
    • error?: string - Error message enum key (e.g., "PARSE_ERROR_AT_LOCATION")
    • line?: number - Line number where error occurred (1-indexed)
    • column?: number - Column number where error occurred (1-indexed)
    • message?: string - Raw parser error message for formatting

Example:

import { checkSyntax, formatErrorMessage } from 'evalla';

// Valid expression
const result1 = checkSyntax('a + b * 2');
console.log(result1.valid); // true

// Invalid expression - missing closing parenthesis
const result2 = checkSyntax('(a + b');
console.log(result2.valid); // false
console.log(result2.error); // "PARSE_ERROR_AT_LOCATION"
console.log(result2.line); // 1
console.log(result2.column); // 7

// Get human-readable error message
const formatted = formatErrorMessage(result2.error, 'en', result2);
console.log(formatted);
// "Parse error at line 1, column 7: Expected ")", "+", "-", "*", "/", "%", "**", ".", "[", "?", "??", "&&", "||", "=", "==", "!=", "<", ">", "<=", ">=", or end of input but end of input found."

Formatting Error Messages:

The error field contains an enum key. To get a human-readable message with placeholders filled in, use formatErrorMessage():

import { checkSyntax, formatErrorMessage, ErrorMessage } from 'evalla';

const result = checkSyntax('a b');

if (!result.valid) {
  // Option 1: Get formatted error message
  const message = formatErrorMessage(result.error as ErrorMessage, 'en', result);
  console.log(message);
  // "Parse error at line 1, column 3: Expected "!=", "&&", "(", "**", ".", "<=", "=", "==", ">=", "?", "??", "[", "||", [%*/], [+\-], [<>], or end of input but "b" found."
  
  // Option 2: Use individual fields for custom formatting
  console.log(`Error at ${result.line}:${result.column}: ${result.message}`);
  // "Error at 1:3: Expected "!=", "&&", ... but "b" found."
}

The result object contains all the data needed for proper formatting:

  • error: The error key (e.g., PARSE_ERROR_AT_LOCATION)
  • line, column: Location information
  • message: The raw parser error message

Pass the entire result object to formatErrorMessage() as the third parameter to replace all placeholders ({line}, {column}, {message}) with actual values.

Usage Patterns:

When to use checkSyntax() vs just calling evalla():

import { checkSyntax, evalla, EvaluationError } from 'evalla';

// Pattern 1: Pre-validate for immediate user feedback (recommended for text editors/UI)
const inputs = [
  { name: 'a', expr: 'c + 5' },
  { name: 'b', expr: 'a * 2' }
];

// Check syntax of each expression before calling evalla
for (const input of inputs) {
  if (input.expr) {
    const check = checkSyntax(input.expr);
    if (!check.valid) {
      console.error(`Invalid syntax in "${input.name}": ${check.error}`);
      return; // Don't call evalla with invalid syntax
    }
  }
}

// All syntax valid, now evaluate
const result = await evalla(inputs);

// Pattern 2: Let evalla handle all validation (simpler for batch processing)
try {
  const result = await evalla(inputs);
  // Success - use result
} catch (error) {
  // Catch ParseError specifically to handle syntax errors
  if (error instanceof ParseError) {
    console.error(`Syntax error in "${error.variableName}" at ${error.line}:${error.column}`);
  } else if (error instanceof EvaluationError) {
    console.error('Runtime evaluation error:', error.message);
  }
  // Handle other error types (ValidationError, CircularDependencyError, etc.)
}

Note:

  • checkSyntax() only validates expression syntax. It does not check variable names, detect circular dependencies, or validate that referenced variables exist.
  • evalla() throws ParseError for syntax errors with the same details (line, column, message) as checkSyntax(), plus identifies which variable has the error.
  • ParseError extends EvaluationError, so catch ParseError first if you want to handle syntax errors differently from runtime errors.
  • Use checkSyntax() for pre-flight validation (e.g., real-time feedback as user types). Use evalla() for complete validation and evaluation.

checkVariableName(name: string): VariableNameCheckResult

Checks if a variable name is valid according to evalla's naming rules. Useful for text editors to validate variable names before evaluation and provide helpful error messages.

Parameters:

  • name: The variable name to check

Returns:

  • Object with the following properties:
    • valid: boolean - Whether the variable name is valid
    • error?: string - Detailed error message if the name is invalid

Example:

import { checkVariableName } from 'evalla';

// Valid variable name
const result1 = checkVariableName('myVar');
console.log(result1.valid); // true

// Invalid - starts with $
const result2 = checkVariableName('$myVar');
console.log(result2.valid); // false
console.log(result2.error); // "Variable names cannot start with $ (reserved for system namespaces)"

// Invalid - reserved value name
const result3 = checkVariableName('true');
console.log(result3.valid); // false
console.log(result3.error); // "Variable name cannot be a reserved value: true"

isValidName(name: string): boolean

Simple boolean check for variable name validity. This is a simpler alternative to checkVariableName() when you don't need detailed error messages.

Parameters:

  • name: The variable name to check

Returns:

  • true if the name is valid, false otherwise

Example:

import { isValidName } from 'evalla';

isValidName('myVar');      // true
isValidName('$myVar');     // false (starts with $)
isValidName('__private');  // false (starts with __)
isValidName('true');       // false (reserved value)

VALID_NAME_PATTERN: RegExp

Regular expression pattern for valid variable names. Useful when you need to validate names in your own code or UI.

Pattern: /^(?![_$]{2})[a-zA-Z_][a-zA-Z0-9_$]*$/

Matches names that:

  • Start with a letter or single underscore (not __ or $)
  • Followed by letters, digits, underscores, or $
  • Do not contain dots

Note: This pattern does not check for reserved value names (true, false, null, Infinity). Use isValidName() or checkVariableName() for complete validation.

Example:

import { VALID_NAME_PATTERN } from 'evalla';

VALID_NAME_PATTERN.test('myVar');     // true
VALID_NAME_PATTERN.test('var123');    // true
VALID_NAME_PATTERN.test('_private');  // true
VALID_NAME_PATTERN.test('my$var');    // true
VALID_NAME_PATTERN.test('$invalid');  // false (starts with $)
VALID_NAME_PATTERN.test('__proto__'); // false (starts with __)
VALID_NAME_PATTERN.test('123abc');    // false (starts with number)
VALID_NAME_PATTERN.test('a.b');       // false (contains dot)
VALID_NAME_PATTERN.test('true');      // true (pattern matches, but it's reserved - use isValidName())

RESERVED_VALUES: readonly ['true', 'false', 'null', 'Infinity']

Array of reserved value names that cannot be used as variable names. Frozen at runtime to prevent mutation.

Example:

import { RESERVED_VALUES } from 'evalla';

RESERVED_VALUES.includes('true');  // true
RESERVED_VALUES.includes('myVar'); // false

// Array is frozen - cannot be mutated
RESERVED_VALUES.push('newValue'); // throws TypeError

formatResults(result: EvaluationResult, decimalPlaces: number): EvaluationResult

Format numeric results to a specific number of decimal places. This is a presentation utility - evaluation always uses full precision internally.

Parameters:

  • result: The evaluation result from evalla()
  • decimalPlaces: Number of decimal places (0 or greater)

Returns:

  • New EvaluationResult with formatted values (non-numeric values unchanged)

Example:

import { evalla, formatResults } from 'evalla';

const result = await evalla([
  { name: 'pi', expr: '$math.PI' },
  { name: 'area', expr: 'pi * 10 * 10' }
]);

const formatted = formatResults(result, 2);
console.log(formatted.values.pi.toString());   // "3.14"
console.log(formatted.values.area.toString()); // "314.16"

Error Handling

evalla throws typed errors with programmatic enum keys. Error messages are enum keys (e.g., UNDEFINED_VARIABLE), with context available via error properties.

Error Types:

  • ValidationError - Invalid input (bad variable names, missing fields)
  • EvaluationError - Runtime errors (undefined variables, type mismatches)
  • SecurityError - Dangerous property access attempts
  • ParseError - Syntax errors in expressions
  • CircularDependencyError - Circular dependencies detected

Error Keys: See ErrorMessage enum for all 48 error keys.

Example:

try {
  await evalla([{ name: 'y', expr: 'x + 1' }]);
} catch (error) {
  console.log(error.message);      // "UNDEFINED_VARIABLE"
  console.log(error.variableName); // "x"
}

Optional i18n: Use formatErrorMessage(key, lang, params) to get human-readable messages.

Philosophy

  • Minimal: Bare minimum dependencies and code
  • Modular: Separated concerns (parser, evaluator, namespaces, toposort)
  • DRY: No code duplication
  • Testable: Small, focused functions with clear interfaces
  • Safe & Secure: No arbitrary code execution, whitelist-only approach
  • Efficient: Parse once, use for both dependency extraction and evaluation

Development

# Install dependencies
npm install

# Build
npm run build

# Test
npm test

For detailed API documentation and examples, see the sections above.

License

MIT