Skip to content

andreasnicolaou/typescript-expression-language

TypeScript Symfony Expression Language

A TypeScript implementation of the popular Symfony Expression Language. This library allows you to evaluate complex expressions client-side, fully mirroring the functionality of the PHP version.

Use it to create dynamic and flexible expression-based logic on the frontend, perfectly synchronized with the server-side implementation in Symfony/PHP.

TypeScript GitHub contributors GitHub License GitHub Actions Workflow Status GitHub package.json version Known Vulnerabilities Bundle Size

ESLint Prettier Jest Maintenance codecov Socket Badge

NPM Downloads

πŸ“Š Code Coverage Visualizations

Tree View

Tree Coverage


Sunburst View

Sunburst Coverage


Icicle View

Icicle Coverage

GitHub Repo stars

πŸ§ͺ Demo

You can try this library live:

πŸ‘‰ Interactive Demo on StackBlitz

🌐 UMD Build Test on GitHub Pages


✨ Features

  • Full Symfony Compatibility: Write expressions that work the same way on both client and server.
  • Zero Dependencies: Self-contained library with no external runtime dependencies.
  • Rich Syntax Support: Includes numbers, strings, operators, functions, and advanced object/array access.
  • Customizable Operators: Define your own operators or extend existing ones.
  • Brackets and Nesting: Handle deeply nested brackets with accurate syntax validation.
  • Error Detection: Detect and report invalid syntax with meaningful error messages.
  • Word-Based Operators: Supports expressions like starts with, not in, ends with, contains, matches, xor, and more!
  • TypeScript Ready: Fully typed, ensuring seamless integration into TypeScript projects.
  • Universal Compatibility: Works in Node.js (ESM/CommonJS), browsers (UMD), and TypeScript projects.
  • Professional Build: Multiple output formats with tree-shaking support and optimized bundles.

πŸ“¦ Installation & Module Support

This library provides universal compatibility across all JavaScript environments:

Package Managers

# npm
npm install @andreasnicolaou/typescript-expression-language

# yarn
yarn add @andreasnicolaou/typescript-expression-language

# pnpm
pnpm add @andreasnicolaou/typescript-expression-language

CDN Usage

For direct browser usage without a build step:

<!-- unpkg CDN (latest version, unminified) -->
<script src="https://unpkg.com/@andreasnicolaou/typescript-expression-language/dist/index.umd.js"></script>

<!-- unpkg CDN (latest version, minified) -->
<script src="https://unpkg.com/@andreasnicolaou/typescript-expression-language/dist/index.umd.min.js"></script>

<!-- jsDelivr CDN (unminified) -->
<script src="https://cdn.jsdelivr.net/npm/@andreasnicolaou/typescript-expression-language/dist/index.umd.js"></script>

<!-- jsDelivr CDN (minified) -->
<script src="https://cdn.jsdelivr.net/npm/@andreasnicolaou/typescript-expression-language/dist/index.umd.min.js"></script>

CDN Benefits:

  • βœ… No build step required
  • βœ… Cached across websites for faster loading
  • βœ… Perfect for prototyping and demos
  • βœ… Works in any HTML page immediately
  • βœ… Choose minified (.min.js) for production, or unminified (.js) for debugging

Module Format Support

  • 🟒 ESM (ES Modules): For modern bundlers and Node.js
  • 🟒 CommonJS: For traditional Node.js projects
  • 🟒 UMD (Unminified & Minified): For direct browser usage via CDN - use .umd.js for debugging, .umd.min.js for production
  • 🟒 TypeScript: Complete type definitions included

Usage Examples

ES Modules (Recommended)

import { ExpressionLanguage } from '@andreasnicolaou/typescript-expression-language';

CommonJS

const { ExpressionLanguage } = require('@andreasnicolaou/typescript-expression-language');

Browser (UMD via CDN)

<!-- Use .umd.js for debugging, .umd.min.js for production -->
<script src="https://unpkg.com/@andreasnicolaou/typescript-expression-language/dist/index.umd.min.js"></script>
<script>
  const el = new typescriptExpressionLanguage.ExpressionLanguage();
  console.log(el.evaluate('1 + 2 * 3')); // 7
</script>

Browser (ES Modules via CDN)

<script type="module">
  import { ExpressionLanguage } from 'https://unpkg.com/@andreasnicolaou/typescript-expression-language/dist/index.js';

  const el = new ExpressionLanguage();
  console.log(el.evaluate('2 * (3 + 4)')); // 14
</script>

TypeScript

// Full type safety and IntelliSense support
import { ExpressionLanguage, ParsedExpression } from '@andreasnicolaou/typescript-expression-language';

πŸš€ Quick Start

Node.js (ESM)

import { ExpressionLanguage } from '@andreasnicolaou/typescript-expression-language';

const el = new ExpressionLanguage();
console.log(el.evaluate('1 + 2 * 3')); // 7

Node.js (CommonJS)

const { ExpressionLanguage } = require('@andreasnicolaou/typescript-expression-language');

const el = new ExpressionLanguage();
console.log(el.evaluate('x + y', { x: 10, y: 5 })); // 15

Browser (No Build Step)

<!-- Use .umd.js for debugging, .umd.min.js for production -->
<script src="https://unpkg.com/@andreasnicolaou/typescript-expression-language/dist/index.umd.min.js"></script>
<script>
  const el = new typescriptExpressionLanguage.ExpressionLanguage();
  console.log(el.evaluate('"Hello " + name', { name: 'World' })); // "Hello World"
</script>

Modern Browser (ES Modules)

<script type="module">
  import { ExpressionLanguage } from 'https://unpkg.com/@andreasnicolaou/typescript-expression-language/dist/index.js';

  const el = new ExpressionLanguage();
  console.log(el.evaluate('Math.pow(2, 3)')); // 8
</script>

πŸ”§ Setup

To get started, initialize the library in your project:

import { ExpressionLanguage } from '@andreasnicolaou/typescript-expression-language';

const expressionLanguage = new ExpressionLanguage();

πŸ“– Usage

Basic Evaluation

const result = expressionLanguage.evaluate('1 + 2');
console.log(result); // Outputs β†’ `3`

Multiple Clauses

const expression = 'array[2] === "three" && obj.method(array[1]) === "value"';
const context = {
  array: ['one', 'two', 'three'],
  obj: {
    method: (arg: string) => `value`,
  },
};
const result = expressionLanguage.evaluate(expression, context);
console.log(result); // Outputs β†’ `true`

Custom Functions

const expressionFunction = ExpressionFunction.fromJs('isEvenFunction', (x: number): boolean => x % 2 === 0, 'isEven');
expressionLanguage.addFunction(expressionFunction);

const expression = 'isEven(10)';
const result = expressionLanguage.evaluate(expression);
console.log(result); // Outputs β†’ `true`

βš™οΈ Configuration

Custom LRU Cache

By default, the library uses an internal LRU cache for expression parsing optimization. If you need to use a custom cache configuration, you'll need to install the lru-cache package separately and pass your own instance:

# Install lru-cache for custom cache usage
npm install lru-cache
import { LRUCache } from 'lru-cache';
import { ExpressionLanguage, ParsedExpression } from '@andreasnicolaou/typescript-expression-language';

const customCache = new LRUCache<string, ParsedExpression>({
  max: 1000,
  ttl: 1000 * 60 * 5,
});

const expressionLanguage = new ExpressionLanguage(customCache);

Custom Providers

Add a Simple Math Provider

import {
  ExpressionLanguage,
  ExpressionFunction,
  ExpressionFunctionProvider,
} from '@andreasnicolaou/typescript-expression-language';

class MathProvider implements ExpressionFunctionProvider {
  getFunctions() {
    return [
      ExpressionFunction.fromJs('square', (x: number) => x * x),
      ExpressionFunction.fromJs('add', (x: number, y: number) => x + y),
    ];
  }
}

const el = new ExpressionLanguage();
el.registerProvider(new MathProvider());

console.log(el.evaluate('square(5)')); // Outputs β†’ 25
console.log(el.evaluate('add(3, 4)')); // Outputs β†’ 7

Add a Provider with Array and String Utilities

import {
  ExpressionLanguage,
  ExpressionFunction,
  ExpressionFunctionProvider,
} from '@andreasnicolaou/typescript-expression-language';

class UtilsProvider implements ExpressionFunctionProvider {
  getFunctions() {
    return [
      ExpressionFunction.fromJs('isEven', (x: number) => x % 2 === 0),
      ExpressionFunction.fromJs('maxInArray', (arr: number[]) => Math.max(...arr)),
      ExpressionFunction.fromJs('join', (arr: string[], sep: string) => arr.join(sep)),
      ExpressionFunction.fromJs('strtolower', (str: string) => (str + '').toLowerCase()), // PHP strtolower
      ExpressionFunction.fromJs('strtoupper', (str: string) => (str + '').toUpperCase()), // PHP strtoupper
    ];
  }
}

const el = new ExpressionLanguage(null, [new UtilsProvider()]);

console.log(el.evaluate('isEven(10)')); // Outputs β†’ true
console.log(el.evaluate('maxInArray([1, 5, 3, 9])')); // Outputs β†’ 9
console.log(el.evaluate('join(["a", "b", "c"], ",")')); // Outputs β†’ "a,b,c"
console.log(el.evaluate('strtolower("HELLO")')); // Outputs β†’ "hello"
console.log(el.evaluate('strtoupper("world")')); // Outputs β†’ "WORLD"

πŸ“‹ Supported Syntax

Operators

Type Operators Description
Arithmetic +, -, *, /, % Basic math operations
Comparison ==, !=, ===, !==, <, <=, >, >= Value comparison
Logical &&, ||, !, xor Logical AND, OR, NOT, XOR
Word-Based starts with, ends with, contains, matches, not, in, not in, and, or, xor Word-based logic
Bitwise &, | , ^, ~, <<, >> Bitwise operations
Range .. Range (sequence)

Data Access

Syntax Description
array[0] Access array elements
array?.[0] Null-safe array element access
obj.property Access object properties
obj?.property Null-safe property access
obj.method(arg) Call object methods
obj?.method() Null-safe method call

Functions

Add and register custom functions for flexible application logic.


πŸ› οΈ Available Functions

The library provides access to a comprehensive set of JavaScript functions. Some are enabled by default, while others can be registered using ExpressionFunction.fromJs().

Function Category Enabled by Default Description Example
constant Core βœ… Access global constants and nested properties constant("CONFIG.API_URL")
enum Core βœ… Access PHP-style and TypeScript-style enums enum("Status.ACTIVE")
min Math βœ… Returns the smallest of zero or more numbers min(1, 2, 3) β†’ 1
max Math βœ… Returns the largest of zero or more numbers max(1, 2, 3) β†’ 3
now Date βœ… Returns the current timestamp now()
abs Math ❌ Returns the absolute value of a number abs(-5) β†’ 5
ceil Math ❌ Rounds a number up to the nearest integer ceil(3.2) β†’ 4
floor Math ❌ Rounds a number down to the nearest integer floor(3.8) β†’ 3
round Math ❌ Rounds a number to the nearest integer round(3.5) β†’ 4
random Math ❌ Returns a pseudo-random number between 0 and 1 random() β†’ 0.123...
sqrt Math ❌ Returns the square root of a number sqrt(9) β†’ 3
pow Math ❌ Returns base to the exponent power pow(2, 3) β†’ 8
sin Math ❌ Returns the sine of a number sin(Math.PI / 2) β†’ 1
cos Math ❌ Returns the cosine of a number cos(0) β†’ 1
tan Math ❌ Returns the tangent of a number tan(0) β†’ 0
keys Array ❌ Returns the keys of an object keys(obj)
values Array ❌ Returns the values of an object values(obj)
isArray Array ❌ Checks if a value is an array isArray(arr)
concat Array ❌ Merges multiple arrays concat(arr1, arr2)
from Array ❌ Creates an array from an iterable from(iterable)
of Array ❌ Creates a new array instance with the given elements of(1, 2, 3)
charAt String ❌ Returns the character at a specific index charAt('hello', 1) β†’ 'e'
charCodeAt String ❌ Returns the Unicode value of the character at an index charCodeAt('A', 0) β†’ 65
includes String ❌ Checks if a string contains a substring includes('hello', 'ell') β†’ true
indexOf String ❌ Returns the index of the first occurrence of a substring indexOf('hello', 'e') β†’ 1
split String ❌ Splits a string into an array by a separator split('a,b,c', ',') β†’ ['a', 'b', 'c']
trim String ❌ Removes whitespace from both ends of a string trim(' hello ') β†’ 'hello'
toUpperCase String ❌ Converts a string to uppercase toUpperCase('hello') β†’ 'HELLO'
toLowerCase String ❌ Converts a string to lowercase toLowerCase('HELLO') β†’ 'hello'
isFinite Number ❌ Checks if a value is a finite number isFinite(100) β†’ true
isInteger Number ❌ Checks if a value is an integer isInteger(100.5) β†’ false
isNaN Number ❌ Checks if a value is NaN isNaN(NaN) β†’ true
toFixed Number ❌ Formats a number to a fixed number of decimals toFixed(3.14159, 2) β†’ '3.14'
toISOString Date ❌ Converts a date to an ISO string toISOString(new Date())
toDateString Date ❌ Converts a date to a readable string toDateString(new Date())
getTime Date ❌ Gets the timestamp of a date getTime(new Date())
getFullYear Date ❌ Returns the year of a date getFullYear(new Date())
getMonth Date ❌ Returns the month of a date (0-based) getMonth(new Date())
getDay Date ❌ Returns the day of the week getDay(new Date())
getMinutes Date ❌ Returns the minutes of a date getMinutes(new Date())
stringify JSON ❌ Converts a JavaScript object to a JSON string stringify({ key: 'value' }) β†’ '{"key":"value"}'
parse JSON ❌ Parses a JSON string into an object parse('{"key":"value"}') β†’ { key: 'value' }
test RegExp ❌ Tests if a pattern matches a string test(/abc/, 'abcdef') β†’ true
exec RegExp ❌ Executes a pattern and returns the match exec(/abc/, 'abcdef') β†’ ['abc']
decodeURI URI ❌ Decodes a URI decodeURI('%20space') β†’ ' space'
encodeURI URI ❌ Encodes a URI encodeURI(' space') β†’ '%20space'
decodeURIComponent URI ❌ Decodes a URI component decodeURIComponent('%20space') β†’ ' space'
encodeURIComponent URI ❌ Encodes a URI component encodeURIComponent(' space') β†’ '%20space'

⚠️ IGNORE_UNKNOWN_VARIABLES & IGNORE_UNKNOWN_FUNCTIONS

When linting or parsing expressions, you may want to ignore errors about unknown variables or functions. The library provides two flags for this purpose:

  • Parser.IGNORE_UNKNOWN_VARIABLES: Ignores unknown variables during linting/parsing.
  • Parser.IGNORE_UNKNOWN_FUNCTIONS: Ignores unknown functions during linting/parsing.
  • You can combine both flags using the bitwise OR operator (|): Parser.IGNORE_UNKNOWN_VARIABLES | Parser.IGNORE_UNKNOWN_FUNCTIONS to ignore both unknown variables and functions.

Usage

Import the Parser and use the flags as the third argument to lint or parse:

import { ExpressionLanguage } from '@andreasnicolaou/typescript-expression-language';
import { Parser } from '@andreasnicolaou/typescript-expression-language/dist/parser';

const el = new ExpressionLanguage();

// Ignore unknown variables
el.lint('foo + 1', [], Parser.IGNORE_UNKNOWN_VARIABLES); // Does not throw
el.parse('foo + 1', [], Parser.IGNORE_UNKNOWN_VARIABLES); // Does not throw

// Ignore unknown functions
el.lint('myFunc(42)', [], Parser.IGNORE_UNKNOWN_FUNCTIONS); // Does not throw
el.parse('myFunc(42)', [], Parser.IGNORE_UNKNOWN_FUNCTIONS); // Does not throw

// Ignore both unknown variables and functions
el.lint('foo + myFunc(42)', [], Parser.IGNORE_UNKNOWN_VARIABLES | Parser.IGNORE_UNKNOWN_FUNCTIONS); // Does not throw
el.parse('foo + myFunc(42)', [], Parser.IGNORE_UNKNOWN_VARIABLES | Parser.IGNORE_UNKNOWN_FUNCTIONS); // Does not throw

These flags are available on both lint and parse methods. They are intended for static analysis and editor tooling only. At runtime, if a variable or function is actually missing during evaluation, an error will still be thrown.


πŸ› οΈ Error Handling

The library is equipped with robust error detection to ensure smooth debugging of invalid expressions. Below are the common error types and how they are reported:

πŸ”„ Common Errors

1. Unmatched Brackets

  • Description: The library throws a SyntaxError when brackets ((), {}, []) are unmatched or unbalanced in an expression.
  • Example:
    (a + b
    

2. Invalid Syntax

  • Description: The library throws a SyntaxError when an expression contains invalid syntax.
  • Example:
    a + b +
    

3. Undefined Variable

  • Description: The library throws a SyntaxError when it detects an invalid or misplaced character that does not belong to the syntax.
  • Example:
    a + 5 @
    

🎯 Use Cases

Here are some practical use cases where the TypeScript Symfony Expression Language can be applied:

1. Dynamic UI Logic

  • Description: Evaluate conditions to dynamically show or hide components based on user input or other variables.
  • Example: Show a form field only if a certain checkbox is checked or display a message when specific conditions are met.

2. Custom Filters

  • Description: Build advanced filtering systems for grids, tables, or reports, allowing users to filter data based on complex expressions.
  • Example: Create filters for product listings that use multiple criteria such as price range, availability, or category.

3. Formulas and Calculations

  • Description: Compute user-defined formulas directly on the client-side, such as calculating discounts, tax rates, or other financial values.
  • Example: Allow users to input values in a form and instantly calculate the total cost or apply discounts.

4. Interactive Widgets

  • Description: Power interactive components (such as sliders, charts, or dashboards) with user-defined expressions for maximum flexibility and real-time updates.
  • Example: Use a slider to dynamically adjust a value or a chart that updates based on user-selected filters or criteria.

These use cases demonstrate how the library can bring advanced, real-time logic directly to the frontend, providing a more interactive and dynamic user experience.


πŸ›‘οΈ Symfony Compatibility

This library ensures that expressions written in PHP's Symfony Expression Language are fully compatible with the client-side implementation. This enables seamless integration between server-side logic (written in Symfony/PHP) and client-side expression evaluation.

Key Benefits:

  • Consistency: Expressions behave the same way on both the client and the server.
  • Synchronization: Ensure business logic is applied consistently across both sides of the application without discrepancies.
  • Easy Integration: Easily synchronize the logic between your PHP backend and TypeScript frontend, without needing separate implementations.

This compatibility makes it easier to create unified and maintainable applications that share the same logic across the stack.


πŸ”§ Development

Building from Source

The library uses a professional build system with Rollup and TypeScript:

# Clone the repository
git clone https://github.com/andreasnicolaou/typescript-expression-language.git
cd typescript-expression-language

# Install dependencies
npm install

# Run the build (generates all formats)
npm run build

Build Output

The build process generates multiple optimized bundles:

  • dist/index.js - ESM bundle for modern environments
  • dist/index.cjs - CommonJS bundle for Node.js
  • dist/index.umd.js - UMD bundle (unminified) for browsers (debugging)
  • dist/index.umd.min.js - UMD bundle (minified) for browsers (production)
  • dist/index.d.ts - TypeScript declarations for full type support

Available Scripts

npm run build      # Build all formats (ESM, CJS, UMD, types)
npm test           # Run Jest test suite
npm run test:watch # Run tests in watch mode
npm run lint       # Run ESLint
npm run format     # Format code with Prettier

πŸ“¦ Contribution

Contributions are welcome! If you encounter issues or have ideas to enhance the library, feel free to submit an issue or pull request.

About

A TypeScript implementation of the popular Symfony Expression Language! This library allows you to evaluate complex expressions client-side, fully mirroring the functionality of the PHP version.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors