<div align="center">

# @run-js/expressions

![Version](https://img.shields.io/badge/version-2.4.1-green)&nbsp;
[![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-blue.svg)](https://www.typescriptlang.org/)&nbsp;
[![Zero Dependencies](https://img.shields.io/badge/no%20dependencies-%E2%9C%94-green.svg)](#)&nbsp;
[![No AI](https://img.shields.io/badge/code-no%20AI%20generated%20vibe%20code%20%3A%29-d946ef)](#)&nbsp;
[![Test Coverage](https://img.shields.io/badge/test%20coverage-100%25-brightgreen)](#)&nbsp;
[![License](https://img.shields.io/badge/license-EULA-blue.svg)](LICENSE.md)

*A powerful expression parser and cross-dialect converter for TypeScript and JavaScript*

Parse filter queries from one dialect, convert them to another — or build expression trees programmatically.

[🏠 Homepage](http://expressions.run-js.com) • [📖 Documentation](#-table-of-contents) • [🚀 Quick Start](#-quick-start) • [🎮 Live Demo](https://expressions-demo.run-js.com)

[🔗 LinqToQuerystring](#-linqtoquerystring) · [📘 TypeScript](#-typescript) · [🐬 MySql](#-mysql) · [🗄️ MsSql](#️-mssql)

</div>

---

## 📋 Table of Contents

- [✨ Features](#-features)
- [📦 Installation](#-installation)
  - [🔧 Package Compatibility](#-package-compatibility)
  - [🔑 License Key](#-license-key)
- [🚀 Quick Start](#-quick-start)
  - [Parse and Convert](#parse-and-convert)
  - [Build Expressions Programmatically](#build-expressions-programmatically)
- [🏗️ ExpressionService Setup](#️-expressionservice-setup)
  - [Configuration Flow](#configuration-flow)
  - [Registering Multiple Dialects](#registering-multiple-dialects)
- [🔨 Expression Builder](#-expression-builder)
  - [Available Methods](#available-methods)
  - [Auto-Conversion](#auto-conversion)
  - [Composing Complex Expressions](#composing-complex-expressions)
- [🗣️ Supported Dialects](#️-supported-dialects)
  - [🔗 LinqToQuerystring](#-linqtoquerystring)
    - [Data Types](#data-types)
    - [Comparisons](#comparisons)
    - [Logical Operators](#logical-operators)
    - [String Functions](#string-functions)
    - [Collection Operations](#collection-operations)
  - [📘 TypeScript](#-typescript)
    - [Data Types](#data-types-1)
    - [Comparisons](#comparisons-1)
    - [Logical Operators](#logical-operators-1)
    - [String Functions](#string-functions-1)
    - [Collection Operations](#collection-operations-1)
  - [🐬 MySql](#-mysql)
    - [Data Types](#data-types-2)
    - [Comparisons](#comparisons-2)
    - [Logical Operators](#logical-operators-2)
    - [String Functions](#string-functions-2)
    - [Collection Operations](#collection-operations-2)
  - [🗄️ MsSql](#️-mssql)
    - [Data Types](#data-types-3)
    - [Comparisons](#comparisons-3)
    - [Logical Operators](#logical-operators-3)
    - [String Functions](#string-functions-3)
    - [Collection Operations](#collection-operations-3)
- [📊 Dialect Comparison Matrix](#-dialect-comparison-matrix)
  - [Data Types](#data-types-4)
  - [Operators & Logical](#operators--logical)
  - [String Functions](#string-functions-4)
  - [Collection Operations](#collection-operations-4)
- [🔄 Cross-Dialect Conversion Examples](#-cross-dialect-conversion-examples)
- [🧩 Combining Expressions](#-combining-expressions)
- [⚡ TypeScript Array Extensions](#-typescript-array-extensions)
- [📖 API Reference](#-api-reference)
  - [ExpressionService](#expressionservice)
  - [ExpressionBuilder](#expressionbuilder)
  - [Expression Types](#expression-types)
- [⚠️ Known Limitations](#️-known-limitations)
- [📄 License Information](#-license-information)
- [🌐 About Rudolf Run Ltd](#-about-rudolf-run-ltd)

---

## ✨ Features

- 🎯 **Zero Dependencies** — Lightweight and self-contained
- 🔒 **100% Test Coverage** — Over 2,000 unit tests, because quality matters
- � **Dual Package Support** — Works in both ESM and CommonJS environments (Node.js, Jest, Vite, modern bundlers)
- �🗣️ **4 Dialects** — LinqToQuerystring, TypeScript, MySql, MsSql
- 🔄 **Cross-Dialect Conversion** — Parse one dialect, stringify to another
- 🏗️ **Expression Builder** — Fluent API for building expression trees programmatically
- 🔍 **Auto-Detection** — Register multiple dialects and the parser detects the syntax automatically
- 🔧 **Fully Customizable** — Modify predefined dialects or create entirely new ones
- 📦 **Universal AST** — One expression tree format that all dialects share
- 🧩 **Expression Combining** — Merge two expressions (AND filters, intersect projections, etc.)
- ⚡ **Array Extensions** — Execute expressions directly on TypeScript arrays
- 🔑 **Licensed** - requires a Premium license key, validated offline without a license server

---

## 📦 Installation

```bash
npm install @run-js/expressions
```

> 🤖 **AI coding assistants:** the package ships an `llms.txt` with the rules and the API in compact form. Point your assistant to `node_modules/@run-js/expressions/llms.txt` so it generates correct setup, dialect syntax and builder calls.

### 🔧 Package Compatibility

This package supports both **CommonJS** and **ESM** environments through dual package exports:

- 🚀 **ESM (Modern)**: Works with Vite, modern bundlers, and `import` statements
- 🏗️ **CommonJS (Legacy)**: Compatible with Node.js, Jest, and `require()` statements
- 📦 **Automatic Detection**: The correct format is selected automatically based on your environment

**Usage Examples:**
```typescript
// ESM (import) - for modern bundlers, Vite, etc.
import { ExpressionBuilder, ExpressionService } from '@run-js/expressions';

// CommonJS (require) - for Node.js, Jest, etc.
const { ExpressionBuilder, ExpressionService } = require('@run-js/expressions');
```

Both approaches provide identical functionality and TypeScript support.

### 🔑 License Key

The package requires a valid Premium license key. `parse()`, `stringify()`, `combineExpression()` and `findExpressionByType()` throw without one. Set the key once before the first call; it applies to every `ExpressionService` instance in the process:

```typescript
const service: IExpressionService = new ExpressionService()
  .useDefaultConfiguration(defaultParserConfigurationCollection)
  .useDialect(linqToQuerystringDialect);

await service.setLicense('your-license-key');
```

The key is validated offline, no license server is contacted. An invalid key rejects with `Invalid license key: <reason>`.

The package is not sold publicly; licenses are issued to dedicated customers only. Contact [support@run-js.com](mailto:support@run-js.com) to obtain one.

---

## 🚀 Quick Start

### Parse and Convert

```typescript
import {
  ExpressionService,
  IExpressionService,
  IExpression,
  defaultParserConfigurationCollection,
  linqToQuerystringDialect,
  typeScriptDialect,
  mySqlDialect,
  msSqlDialect
} from '@run-js/expressions';

// 1. Create and configure the service
const service: IExpressionService = new ExpressionService()
  .useDefaultConfiguration(defaultParserConfigurationCollection)
  .useDialect(linqToQuerystringDialect)
  .useDialect(mySqlDialect);

// 2. Set the license key
await service.setLicense('your-license-key');

// 3. Parse a LinqToQuerystring expression
const expression: IExpression = service.parse(`Firstname eq 'John' and Age gt 35`);

// 4. Convert to MySql
const mysqlQuery: string = service.stringify(expression, mySqlDialect);
console.log(mysqlQuery);
// Output: Firstname = 'John' AND Age > 35
```

### Build Expressions Programmatically

```typescript
import { ExpressionBuilder, ExpressionService, defaultParserConfigurationCollection, mySqlDialect } from '@run-js/expressions';

const eb = new ExpressionBuilder();
const service = new ExpressionService()
  .useDefaultConfiguration(defaultParserConfigurationCollection)
  .useDialect(mySqlDialect);
await service.setLicense('your-license-key');

// Build: Firstname = 'John' AND (Age > 35 OR City = 'London')
const expression = eb.and(
  eb.equals('Firstname', 'John'),
  eb.or(
    eb.greaterThan('Age', 35),
    eb.equals('City', 'London')
  )
);

console.log(service.stringify(expression, mySqlDialect));
// Output: Firstname = 'John' AND (Age > 35 OR City = 'London')
```

---

## 🏗️ ExpressionService Setup

### Configuration Flow

The ExpressionService must be configured before use. The configuration is a two-step process:

```typescript
const service: IExpressionService = new ExpressionService()
  // Step 1: Load the default parser configuration (priorities, groups, value converters)
  .useDefaultConfiguration(defaultParserConfigurationCollection)
  // Step 2: Register one or more dialects
  .useDialect(linqToQuerystringDialect)
  .useDialect(typeScriptDialect)
  .useDialect(mySqlDialect)
  .useDialect(msSqlDialect);

// Step 3: Set the license key, before the first parse or stringify
await service.setLicense('your-license-key');
```

Each dialect call can optionally control whether that dialect is used for parsing, stringifying, or both:

```typescript
// Use for parsing only (not for stringify output)
service.useDialect(linqToQuerystringDialect, /* useForParse */ true, /* useForStringify */ false);

// Use for stringify only (not for parsing input)
service.useDialect(mySqlDialect, /* useForParse */ false, /* useForStringify */ true);
```

### Registering Multiple Dialects

When multiple dialects are registered for parsing, the service tries all parsers sorted by priority. This means you can parse **mixed-notation** queries:

```typescript
const service = new ExpressionService()
  .useDefaultConfiguration(defaultParserConfigurationCollection)
  .useDialect(linqToQuerystringDialect)
  .useDialect(typeScriptDialect);

// Mixed: LinqToQuerystring 'eq' + TypeScript '==' in the same expression
const expression = service.parse(`prop1 eq true and (prop2 == true or prop3 == true)`);
```

---

## 🔨 Expression Builder

The `ExpressionBuilder` creates expression tree objects (the same AST that `parse()` produces). You compose expressions by nesting method calls.

### Available Methods

#### Value Expressions

| Method | Parameters | Result |
|---|---|---|
| `empty()` | — | Empty expression |
| `null()` | — | Null value |
| `true()` | — | Boolean true |
| `false()` | — | Boolean false |
| `string(value)` | `string` | String value |
| `stringEmpty()` | — | Empty string `''` |
| `number(value)` | `number` | Numeric value |
| `dateTime(value?)` | `Date`, `string`, `number`, or omit for `new Date()` | DateTime value |
| `property(name)` | `string` | Property reference |

#### Comparison Expressions

| Method | Parameters | Result |
|---|---|---|
| `equals(left, right)` | left: expression, `string`, `number` or `Date`, right: expression or literal | Equality comparison |
| `notEquals(left, right)` | same | Inequality comparison |
| `greaterThan(left, right)` | same | Greater than |
| `greaterThanOrEqual(left, right)` | same | Greater than or equal |
| `lessThan(left, right)` | same | Less than |
| `lessThanOrEqual(left, right)` | same | Less than or equal |

#### Logical Expressions

| Method | Parameters | Result |
|---|---|---|
| `and(...expressions)` | 1+ boolean expressions | AND chain (auto-groups OR children) |
| `or(...expressions)` | 1+ boolean expressions | OR chain |
| `not(inner)` | boolean expression | Logical NOT |
| `group(inner)` | boolean expression | Parenthesized group |

#### String Function Expressions

| Method | Parameters | Result |
|---|---|---|
| `stringContains(source, search)` | source: expression or `string` (→ property), search: expression or `string` | Contains check |
| `stringStartsWith(source, search)` | same | Starts-with check |
| `stringEndsWith(source, search)` | same | Ends-with check |
| `stringMatches(source, search)` | same, search is the regular expression pattern | Regular expression match |
| `stringToLower(source)` | expression or `string` | Lowercase conversion |
| `stringToUpper(source)` | expression or `string` | Uppercase conversion |
| `propertyIn(property, ...values)` | property: expression or `string`, values: `string[]` or `number[]` | IN check (auto-deduplicates, filters null) |

#### Collection Expressions

| Method | Parameters | Result |
|---|---|---|
| `entityCollection(name)` | `string` | Named collection/table |
| `filter(inner, filter, parameterName?)` | inner: expression, filter: boolean expression | Filter operation |
| `orderBy(inner, order, direction?)` | inner: expression, order: expression or `string` | Primary sort |
| `orderThenBy(inner, order, direction?)` | inner: ordered expression, order: expression or `string` | Secondary sort |
| `orderByM(inner, ...orders)` | inner: expression, orders: `{order, direction?}[]` | Multi-column sort (convenience) |
| `skip(inner, count)` | inner: expression, count: `number` | Skip N rows |
| `take(inner, count)` | inner: expression, count: `number` | Take N rows |
| `projection(inner, ...properties)` | inner: expression, properties: expressions or `string[]` | Select specific columns |

### Auto-Conversion

The builder automatically converts primitive parameters to the correct expression types:

```typescript
const eb = new ExpressionBuilder();

// String on the LEFT → converted to PropertyExpression
eb.equals('myProp', 'myValue');
// Same as:
eb.equals(eb.property('myProp'), eb.string('myValue'));

// String on the LEFT that is no valid property name → converted to StringExpression
eb.equals('my value', 'myValue');
// Same as:
eb.equals(eb.string('my value'), eb.string('myValue'));

// Different types on the RIGHT → auto-detected
eb.equals('prop', 42);           // number → INumberExpression
eb.equals('prop', true);         // boolean → ITrueExpression
eb.equals('prop', new Date());   // Date → IDateTimeExpression
eb.equals('prop', null);         // null/undefined → INullExpression
```

### Composing Complex Expressions

```typescript
const eb = new ExpressionBuilder();

// Build a full query: SELECT Firstname, Age FROM Users WHERE Age > 18 ORDER BY Age DESC LIMIT 10
const query = eb.take(
  eb.orderBy(
    eb.filter(
      eb.entityCollection('Users'),
      eb.greaterThan('Age', 18)
    ),
    'Age', 'desc'
  ),
  10
);

// Multi-column sort with the convenience method
const sorted = eb.orderByM(
  eb.filter(eb.entityCollection('Products'), eb.equals('active', true)),
  { order: 'category', direction: 'asc' },
  { order: 'price', direction: 'desc' }
);
```

---

## 🗣️ Supported Dialects

> Every example below uses `service.parse()` and `service.stringify()` to demonstrate parsing expression strings and converting them between dialects.

### 🔗 LinqToQuerystring

LinqToQuerystring uses a subset of OData query syntax, making it compatible with KendoUI, BreezeJS, and similar client libraries.

> Operators are **case-sensitive** and must be separated from operands by spaces.

#### Data Types

| Type | Example |
|---|---|
| Null | `null` |
| Boolean | `true`, `false` |
| Number | `123`, `1234.567`, `234.567f`, `345.67M`, `45L` |
| String | `'Hello World'` |
| DateTime | `datetime'2000-12-12T12:34:56Z'` |

#### Comparisons

```typescript
// Equals — also supports bracket notation: [Firstname] eq 'John'
const expr = service.parse(`Firstname eq 'John'`);
service.stringify(expr, mySqlDialect);      // → Firstname = 'John'
service.stringify(expr, typeScriptDialect); // → Firstname == 'John'

// Not equals
const expr = service.parse(`Firstname ne 'John'`);
service.stringify(expr, mySqlDialect);      // → Firstname <> 'John'

// Greater than
const expr = service.parse(`Age gt 35`);
service.stringify(expr, typeScriptDialect); // → Age > 35

// Greater than or equal
const expr = service.parse(`Age ge 35`);
service.stringify(expr, typeScriptDialect); // → Age >= 35

// Less than
const expr = service.parse(`Age lt 35`);
service.stringify(expr, mySqlDialect);      // → Age < 35

// Less than or equal
const expr = service.parse(`Age le 35`);
service.stringify(expr, mySqlDialect);      // → Age <= 35
```

#### Logical Operators

```typescript
const expr = service.parse(`Firstname eq 'John' and Age gt 35`);
service.stringify(expr, typeScriptDialect); // → Firstname == 'John' && Age > 35
service.stringify(expr, mySqlDialect);      // → Firstname = 'John' AND Age > 35

const expr = service.parse(`Firstname eq 'John' or Age gt 35`);
service.stringify(expr, typeScriptDialect); // → Firstname == 'John' || Age > 35

const expr = service.parse(`not Firstname eq 'John'`);
service.stringify(expr, typeScriptDialect); // → !(Firstname == 'John')

const expr = service.parse(`(Firstname eq 'John' or Age gt 35) and Lastname eq 'Doe'`);
service.stringify(expr, mySqlDialect);      // → (Firstname = 'John' OR Age > 35) AND Lastname = 'Doe'
```

#### String Functions

```typescript
const expr = service.parse(`contains(Firstname, 'John')`);
service.stringify(expr, mySqlDialect);      // → Firstname LIKE '%John%'
service.stringify(expr, typeScriptDialect); // → Firstname.includes('John')

const expr = service.parse(`startswith(Firstname, 'John')`);
service.stringify(expr, mySqlDialect);      // → Firstname LIKE 'John%'

const expr = service.parse(`endswith(Firstname, 'ohn')`);
service.stringify(expr, typeScriptDialect); // → Firstname.endsWith('ohn')

const expr = service.parse(`matchesPattern(Firstname, '^J.*n$')`);
service.stringify(expr, mySqlDialect);      // → Firstname REGEXP '^J.*n$'
service.stringify(expr, typeScriptDialect); // → new RegExp('^J.*n$').test(Firstname)

const expr = service.parse(`tolower(Firstname)`);
service.stringify(expr, mySqlDialect);      // → LOWER(Firstname)

const expr = service.parse(`toupper(Firstname)`);
service.stringify(expr, typeScriptDialect); // → Firstname.toUpperCase()
```

> Also supported for parsing: `substringof()`, `substring()`

```typescript
const expr = service.parse(`valueIn(Firstname, 'John', 'Jane', 'Tom')`);
service.stringify(expr, mySqlDialect);      // → Firstname IN ('John', 'Jane', 'Tom')
service.stringify(expr, typeScriptDialect); // → ['John', 'Jane', 'Tom'].indexOf(Firstname) >= 0
```

#### Collection Operations

Supported syntax for collection queries:

| Operation | Syntax | Alternative |
|---|---|---|
| **Collection** | `/Products` | — |
| **Filter** | `?$filter=Price gt 100` | `&$filter=...` |
| **Order By** | `orderby Name desc` | `$orderby=Name` |
| **Pagination** | `skip 10 top 5` | `$skip=10&$top=5` |
| **Projection** | `select Name, Price` | `$select=Name,Price` |

#### Full Example

```typescript
const expr = service.parse(
  `/Products?$filter=Price gt 100 and Category eq 'Electronics' orderby Name skip 20 top 10 select Name, Price`
);

service.stringify(expr, mySqlDialect);
// → SELECT Name, Price FROM Products WHERE Price > 100 AND Category = 'Electronics' ORDER BY Name LIMIT 10 OFFSET 20

service.stringify(expr, typeScriptDialect);
// → Products.filter(p => p.Price > 100 && p.Category == 'Electronics').sortBy('Name').slice(20, 10).selectProperties('Name', 'Price')
```

---

### 📘 TypeScript

The TypeScript dialect uses native TypeScript/JavaScript method syntax for expressions.

> Parsing is **case-sensitive**.

#### Data Types

| Type | Example |
|---|---|
| Null | `null`, `undefined` (both parse to null) |
| Boolean | `true`, `false` |
| Number | `123`, `1234.567` |
| String | `'value'`, `"value"`, `` `value` `` |
| DateTime | `new Date('2000-12-12T12:00')`, `new Date(1234567890)`, `new Date()` |

#### Comparisons

```typescript
// Equals — supports ==, ===, bracket notation: ['Firstname'] == 'John'
const expr = service.parse(`Firstname == 'John'`);
service.stringify(expr, mySqlDialect);            // → Firstname = 'John'
service.stringify(expr, linqToQuerystringDialect); // → Firstname eq 'John'

// Not equals (!= or !==)
const expr = service.parse(`Firstname != 'John'`);
service.stringify(expr, mySqlDialect);            // → Firstname <> 'John'

// Greater than
const expr = service.parse(`Age > 35`);
service.stringify(expr, linqToQuerystringDialect); // → Age gt 35

// Greater than or equal
const expr = service.parse(`Age >= 35`);
service.stringify(expr, linqToQuerystringDialect); // → Age ge 35

// Less than
const expr = service.parse(`Age < 35`);
service.stringify(expr, mySqlDialect);            // → Age < 35

// Less than or equal
const expr = service.parse(`Age <= 35`);
service.stringify(expr, linqToQuerystringDialect); // → Age le 35
```

#### Logical Operators

```typescript
const expr = service.parse(`Firstname == 'John' && Age > 35`);
service.stringify(expr, linqToQuerystringDialect); // → Firstname eq 'John' and Age gt 35
service.stringify(expr, mySqlDialect);             // → Firstname = 'John' AND Age > 35

const expr = service.parse(`Firstname == 'John' || Age > 35`);
service.stringify(expr, linqToQuerystringDialect); // → Firstname eq 'John' or Age gt 35

const expr = service.parse(`!(Firstname == 'John')`);
service.stringify(expr, linqToQuerystringDialect); // → not Firstname eq 'John'

const expr = service.parse(`(Firstname == 'John' || Age > 35) && Lastname == 'Doe'`);
service.stringify(expr, mySqlDialect);             // → (Firstname = 'John' OR Age > 35) AND Lastname = 'Doe'
```

#### String Functions

```typescript
const expr = service.parse(`Firstname.includes('John')`);
service.stringify(expr, mySqlDialect);            // → Firstname LIKE '%John%'
service.stringify(expr, linqToQuerystringDialect); // → contains(Firstname, 'John')

const expr = service.parse(`Firstname.startsWith('John')`);
service.stringify(expr, mySqlDialect);            // → Firstname LIKE 'John%'

const expr = service.parse(`Firstname.endsWith('ohn')`);
service.stringify(expr, linqToQuerystringDialect); // → endswith(Firstname, 'ohn')

// Regular expression: parses new RegExp('...').test(), RegExp('...').test() and /.../.test()
const expr = service.parse(`/^J.*n$/.test(Firstname)`);
service.stringify(expr, mySqlDialect);            // → Firstname REGEXP '^J.*n$'
service.stringify(expr, linqToQuerystringDialect); // → matchesPattern(Firstname, '^J.*n$')

const expr = service.parse(`Firstname.toLowerCase()`);
service.stringify(expr, mySqlDialect);            // → LOWER(Firstname)

const expr = service.parse(`Firstname.toUpperCase()`);
service.stringify(expr, linqToQuerystringDialect); // → toupper(Firstname)

const expr = service.parse(`['John', 'Jane', 'Tom'].indexOf(Firstname) >= 0`);
service.stringify(expr, mySqlDialect);            // → Firstname IN ('John', 'Jane', 'Tom')
service.stringify(expr, linqToQuerystringDialect); // → valueIn(Firstname, 'John', 'Jane', 'Tom')
```

#### Collection Operations

Supported syntax for collection queries:

| Operation | Syntax |
|---|---|
| **Collection** | `Products` |
| **Filter** | `.filter(p => p.Price > 100)` |
| **Order By** | `.sortBy('Name', 'desc')` |
| **Then By** | `.thenBy('Price', 'asc')` |
| **Pagination** | `.slice(20, 10)` (skip 20, take 10) |
| **Projection** | `.selectProperties('Name', 'Price')` |

#### Full Example

```typescript
const expr = service.parse(
  `Products.filter(p => p.Price > 100 && p.Category == 'Electronics').sortBy('Name').slice(20, 10).selectProperties('Name', 'Price')`
);

service.stringify(expr, mySqlDialect);
// → SELECT Name, Price FROM Products WHERE Price > 100 AND Category = 'Electronics' ORDER BY Name LIMIT 10 OFFSET 20

service.stringify(expr, linqToQuerystringDialect);
// → /Products?$filter=Price gt 100 and Category eq 'Electronics' orderby Name skip 20 top 10 select Name, Price
```

---

### 🐬 MySql

The MySql dialect generates and parses standard MySQL query fragments.

> Parsing is **case-insensitive**.

#### Data Types

| Type | Example |
|---|---|
| Null | `NULL` |
| Boolean | `TRUE`, `FALSE` |
| Number | `123`, `1234.567` |
| String | `'Hello World'`, `"Hello World"` |
| DateTime | `'2000-12-12T12:34:56.000'`, `'2000-12-12 12:34:56'` |

> ⚠️ **Dangerous: `NO_BACKSLASH_ESCAPES` is not supported.** Strings are escaped with backslashes (`\'`, `\\`). With the SQL mode `NO_BACKSLASH_ESCAPES` enabled on the server, a backslash is a plain character and a string value can break out of its quotes (SQL injection). Only use the MySql dialect with the default SQL mode.

#### Comparisons

```typescript
// Equals
const expr = service.parse(`Firstname = 'John'`);
service.stringify(expr, linqToQuerystringDialect); // → Firstname eq 'John'
service.stringify(expr, typeScriptDialect);        // → Firstname == 'John'

// Null-safe equals
const expr = service.parse(`Firstname <=> 'John'`);
service.stringify(expr, linqToQuerystringDialect); // → Firstname eq 'John'

// Null comparison (auto-converted from = NULL)
const expr = service.parse(`Firstname IS NULL`);
service.stringify(expr, typeScriptDialect);        // → Firstname == null

// Not equals (or !=)
const expr = service.parse(`Firstname <> 'John'`);
service.stringify(expr, linqToQuerystringDialect); // → Firstname ne 'John'

// Not null (auto-converted from <> NULL)
const expr = service.parse(`Firstname IS NOT NULL`);
service.stringify(expr, typeScriptDialect);        // → Firstname != null

// Greater than / Greater or equal / Less than / Less or equal
const expr = service.parse(`Age > 35`);
service.stringify(expr, linqToQuerystringDialect); // → Age gt 35

const expr = service.parse(`Age >= 35`);
service.stringify(expr, linqToQuerystringDialect); // → Age ge 35
```

#### Logical Operators

```typescript
const expr = service.parse(`Firstname = 'John' AND Age > 35`);
service.stringify(expr, linqToQuerystringDialect); // → Firstname eq 'John' and Age gt 35
service.stringify(expr, typeScriptDialect);        // → Firstname == 'John' && Age > 35

const expr = service.parse(`Firstname = 'John' OR Age > 35`);
service.stringify(expr, typeScriptDialect);        // → Firstname == 'John' || Age > 35

const expr = service.parse(`NOT Firstname = 'John'`);
service.stringify(expr, typeScriptDialect);        // → !(Firstname == 'John')

const expr = service.parse(`(Firstname = 'John' OR Age > 35) AND Lastname = 'Doe'`);
service.stringify(expr, linqToQuerystringDialect); // → (Firstname eq 'John' or Age gt 35) and Lastname eq 'Doe'
```

> Also supported for parsing: `&&` (AND), `||` (OR), `!` (NOT)

#### String Functions

```typescript
// Contains — parses INSTR() and LIKE '%...%'
const expr = service.parse(`Firstname LIKE '%John%'`);
service.stringify(expr, linqToQuerystringDialect); // → contains(Firstname, 'John')
service.stringify(expr, typeScriptDialect);        // → Firstname.includes('John')

// Starts with — parses LEFT(...) = '...' and LIKE '...%'
const expr = service.parse(`Firstname LIKE 'John%'`);
service.stringify(expr, linqToQuerystringDialect); // → startswith(Firstname, 'John')

// Ends with — parses RIGHT(...) = '...' and LIKE '%...'
const expr = service.parse(`Firstname LIKE '%ohn'`);
service.stringify(expr, typeScriptDialect);        // → Firstname.endsWith('ohn')

// Regular expression: parses REGEXP, RLIKE and REGEXP_LIKE()
const expr = service.parse(`Firstname REGEXP '^J.*n$'`);
service.stringify(expr, linqToQuerystringDialect); // → matchesPattern(Firstname, '^J.*n$')
service.stringify(expr, typeScriptDialect);        // → new RegExp('^J.*n$').test(Firstname)

// Case conversion
const expr = service.parse(`LOWER(Firstname)`);
service.stringify(expr, linqToQuerystringDialect); // → tolower(Firstname)

const expr = service.parse(`UPPER(Firstname)`);
service.stringify(expr, typeScriptDialect);        // → Firstname.toUpperCase()

const expr = service.parse(`Firstname IN ('John', 'Jane', 'Tom')`);
service.stringify(expr, linqToQuerystringDialect); // → valueIn(Firstname, 'John', 'Jane', 'Tom')
service.stringify(expr, typeScriptDialect);        // → ['John', 'Jane', 'Tom'].indexOf(Firstname) >= 0
```

#### Collection Operations

Supported syntax for collection queries:

| Operation | Syntax |
|---|---|
| **Collection** | `FROM Products` |
| **Filter** | `WHERE Price > 100` |
| **Projection** | `SELECT Name, Price` / `SELECT *` |
| **Order By** | `ORDER BY Name DESC` |
| **Pagination** | `LIMIT 10`, `LIMIT 10 OFFSET 20`, `LIMIT 20, 10` |

> ⚠️ Skip without take is **not supported** in MySql dialect.

#### Full Example

```typescript
const expr = service.parse(
  `SELECT Name, Price FROM Products WHERE Price > 100 AND Category = 'Electronics' ORDER BY Name LIMIT 10 OFFSET 20`
);

service.stringify(expr, typeScriptDialect);
// → Products.filter(p => p.Price > 100 && p.Category == 'Electronics').sortBy('Name').slice(20, 10).selectProperties('Name', 'Price')

service.stringify(expr, linqToQuerystringDialect);
// → /Products?$filter=Price gt 100 and Category eq 'Electronics' orderby Name skip 20 top 10 select Name, Price
```

---

### 🗄️ MsSql

The MsSql dialect generates and parses Microsoft SQL Server query fragments.

> Parsing is **case-insensitive**.

#### Data Types

| Type | Example |
|---|---|
| Null | `NULL` |
| Boolean | `'TRUE'`, `'FALSE'` (quoted strings) |
| Number | `123`, `1234.567` |
| String | `'Hello World'` |
| DateTime | `'2000-12-12T12:34:56.000'`, `'2000-12-12 12:34:56'` |

> Note: MsSql booleans are represented as quoted strings (`'TRUE'`, `'FALSE'`), unlike MySql which uses unquoted `TRUE`/`FALSE`.

> Note: The unicode prefix `N'Hello World'` is supported. Stringify writes it only for strings with characters outside ASCII (`N'Grüße'`), so those characters are kept.

#### Comparisons

```typescript
// Equals
const expr = service.parse(`Firstname = 'John'`);
service.stringify(expr, linqToQuerystringDialect); // → Firstname eq 'John'
service.stringify(expr, typeScriptDialect);        // → Firstname == 'John'

// Null comparison (auto-converted from = NULL)
const expr = service.parse(`Firstname IS NULL`);
service.stringify(expr, typeScriptDialect);        // → Firstname == null

// Not equals
const expr = service.parse(`Firstname <> 'John'`);
service.stringify(expr, linqToQuerystringDialect); // → Firstname ne 'John'

// Not null (auto-converted from <> NULL)
const expr = service.parse(`Firstname IS NOT NULL`);
service.stringify(expr, typeScriptDialect);        // → Firstname != null

// Greater than / Greater or equal / Less than / Less or equal
const expr = service.parse(`Age > 35`);
service.stringify(expr, linqToQuerystringDialect); // → Age gt 35

const expr = service.parse(`Age >= 35`);
service.stringify(expr, linqToQuerystringDialect); // → Age ge 35
```

#### Logical Operators

```typescript
const expr = service.parse(`Firstname = 'John' AND Age > 35`);
service.stringify(expr, linqToQuerystringDialect); // → Firstname eq 'John' and Age gt 35
service.stringify(expr, typeScriptDialect);        // → Firstname == 'John' && Age > 35

const expr = service.parse(`Firstname = 'John' OR Age > 35`);
service.stringify(expr, typeScriptDialect);        // → Firstname == 'John' || Age > 35

const expr = service.parse(`NOT Firstname = 'John'`);
service.stringify(expr, typeScriptDialect);        // → !(Firstname == 'John')

const expr = service.parse(`(Firstname = 'John' OR Age > 35) AND Lastname = 'Doe'`);
service.stringify(expr, linqToQuerystringDialect); // → (Firstname eq 'John' or Age gt 35) and Lastname eq 'Doe'
```

> Also supported for parsing: `&&` (AND), `||` (OR), `!` (NOT)

#### String Functions

```typescript
// Contains — parses CONTAINS() and LIKE '%...%'
const expr = service.parse(`Firstname LIKE '%John%'`);
service.stringify(expr, linqToQuerystringDialect); // → contains(Firstname, 'John')
service.stringify(expr, typeScriptDialect);        // → Firstname.includes('John')

// Starts with — parses LEFT(x, LEN(y)) = y and LIKE '...%' (note: LEN, not LENGTH)
const expr = service.parse(`Firstname LIKE 'John%'`);
service.stringify(expr, linqToQuerystringDialect); // → startswith(Firstname, 'John')

// Ends with — parses RIGHT(x, LEN(y)) = y and LIKE '%...'
const expr = service.parse(`Firstname LIKE '%ohn'`);
service.stringify(expr, typeScriptDialect);        // → Firstname.endsWith('ohn')

// Regular expression: REGEXP_LIKE() (SQL Server 2025 and Azure SQL)
const expr = service.parse(`REGEXP_LIKE(Firstname, '^J.*n$')`);
service.stringify(expr, linqToQuerystringDialect); // → matchesPattern(Firstname, '^J.*n$')
service.stringify(expr, typeScriptDialect);        // → new RegExp('^J.*n$').test(Firstname)

// Case conversion
const expr = service.parse(`LOWER(Firstname)`);
service.stringify(expr, linqToQuerystringDialect); // → tolower(Firstname)

const expr = service.parse(`UPPER(Firstname)`);
service.stringify(expr, typeScriptDialect);        // → Firstname.toUpperCase()

const expr = service.parse(`Firstname IN ('John', 'Jane', 'Tom')`);
service.stringify(expr, linqToQuerystringDialect); // → valueIn(Firstname, 'John', 'Jane', 'Tom')
service.stringify(expr, typeScriptDialect);        // → ['John', 'Jane', 'Tom'].indexOf(Firstname) >= 0
```

#### Collection Operations

Supported syntax for collection queries:

| Operation | Syntax |
|---|---|
| **Collection** | `FROM [Products]` (brackets around table name) |
| **Filter** | `WHERE Price > 100` |
| **Projection** | `SELECT Name, Price` / `SELECT *` |
| **Order By** | `ORDER BY Name DESC` |
| **Pagination** | `OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY` |

> ⚠️ MsSql `OFFSET`/`FETCH` **requires** an `ORDER BY` clause. If no skip is specified, `OFFSET 0` is auto-inserted.

#### Full Example

```typescript
const expr = service.parse(
  `SELECT Name, Price FROM [Products] WHERE Price > 100 AND Category = 'Electronics' ORDER BY Name OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY`
);

service.stringify(expr, mySqlDialect);
// → SELECT Name, Price FROM Products WHERE Price > 100 AND Category = 'Electronics' ORDER BY Name LIMIT 10 OFFSET 20

service.stringify(expr, typeScriptDialect);
// → Products.filter(p => p.Price > 100 && p.Category == 'Electronics').sortBy('Name').slice(20, 10).selectProperties('Name', 'Price')
```

---

## 📊 Dialect Comparison Matrix

### Data Types

| Data Type | LinqToQuerystring | TypeScript | MySql | MsSql |
|---|---|---|---|---|
| **Null** | `null` | `null`, `undefined` | `NULL` | `NULL` |
| **True** | `true` | `true` | `TRUE` | `'TRUE'` |
| **False** | `false` | `false` | `FALSE` | `'FALSE'` |
| **Number** | `123`, `45L`, `2.5f`, `3.14M` | `123`, `3.14` | `123`, `3.14` | `123`, `3.14` |
| **String** | `'value'` | `'val'`, `"val"`, `` `val` `` | `'val'`, `"val"` | `'val'` |
| **DateTime** | `datetime'2000-12-12T12:00'` | `new Date('2000-12-12')` | `'2000-12-12T12:00:00.000'` | `'2000-12-12T12:00:00.000'` |
| **Property** | `prop`, `[prop]` | `prop`, `['prop']` | `prop` | `prop` |

### Operators & Logical

| Operation | LinqToQuerystring | TypeScript | MySql | MsSql |
|---|---|---|---|---|
| **Equals** | `eq` | `==`, `===` | `=`, `<=>`, `IS` | `=`, `<=>`, `IS` |
| **Not Equals** | `ne` | `!=`, `!==` | `<>`, `!=`, `IS NOT` | `<>`, `!=`, `IS NOT` |
| **Greater Than** | `gt` | `>` | `>` | `>` |
| **Greater or Equal** | `ge` | `>=` | `>=` | `>=` |
| **Less Than** | `lt` | `<` | `<` | `<` |
| **Less or Equal** | `le` | `<=` | `<=` | `<=` |
| **AND** | `and` | `&&` | `AND`, `&&` | `AND`, `&&` |
| **OR** | `or` | `\|\|` | `OR`, `\|\|` | `OR`, `\|\|` |
| **NOT** | `not` | `!` | `NOT`, `!` | `NOT`, `!` |
| **Grouping** | `( )` | `( )` | `( )` | `( )` |
| **IN** | `valueIn(a, ...)` | `[...].indexOf(a) >= 0` | `a IN (...)` | `a IN (...)` |
| **Case Sensitive** | ✅ | ✅ | ❌ | ❌ |

### String Functions

| Function | LinqToQuerystring | TypeScript | MySql | MsSql |
|---|---|---|---|---|
| **Contains** | `contains(a, b)` | `a.includes(b)` | `a LIKE '%b%'` | `a LIKE '%b%'` |
| **Starts With** | `startswith(a, b)` | `a.startsWith(b)` | `a LIKE 'b%'` | `a LIKE 'b%'` |
| **Ends With** | `endswith(a, b)` | `a.endsWith(b)` | `a LIKE '%b'` | `a LIKE '%b'` |
| **Matches (regular expression)** | `matchesPattern(a, b)` | `new RegExp(b).test(a)` | `a REGEXP b` | `REGEXP_LIKE(a, b)` |
| **To Lower** | `tolower(a)` | `a.toLowerCase()` | `LOWER(a)` | `LOWER(a)` |
| **To Upper** | `toupper(a)` | `a.toUpperCase()` | `UPPER(a)` | `UPPER(a)` |

### Collection Operations

| Operation | LinqToQuerystring | TypeScript | MySql | MsSql |
|---|---|---|---|---|
| **Collection** | `/Name` | `Name` | `FROM Name` | `FROM [Name]` |
| **Filter** | `?$filter=expr` | `.filter(p => expr)` | `WHERE expr` | `WHERE expr` |
| **Order By** | `orderby col [dir]` | `.sortBy('col', 'dir')` | `ORDER BY col [DIR]` | `ORDER BY col [DIR]` |
| **Then By** | `orderby a, b` | `.thenBy('col', 'dir')` | `ORDER BY a, b` | `ORDER BY a, b` |
| **Skip** | `skip N` | `.slice(N)` | `OFFSET N` ⚠️ | `OFFSET N ROWS` ⚠️ |
| **Take** | `top N` | `.slice(skip, N)` | `LIMIT N` | `FETCH NEXT N ROWS ONLY` ⚠️ |
| **Projection** | `select a, b` | `.selectProperties('a','b')` | `SELECT a, b` | `SELECT a, b` |

> ⚠️ **MySql**: Skip requires take (`LIMIT` must be present for `OFFSET`)
> ⚠️ **MsSql**: Skip/Take requires `ORDER BY`; `OFFSET` auto-inserts `0` if not specified

---

## 🔄 Cross-Dialect Conversion Examples

The core power of this library is converting between dialects. Parse in one format, output in another:

#### LinqToQuerystring → MySql
```typescript
const service = new ExpressionService()
  .useDefaultConfiguration(defaultParserConfigurationCollection)
  .useDialect(linqToQuerystringDialect)
  .useDialect(mySqlDialect);

const expression = service.parse(`Name eq 'Test' and Price gt 100`);
console.log(service.stringify(expression, mySqlDialect));
// Output: Name = 'Test' AND Price > 100
```

#### TypeScript → LinqToQuerystring
```typescript
const expression = service.parse(`Name == 'Test' && Price > 100`);
console.log(service.stringify(expression, linqToQuerystringDialect));
// Output: Name eq 'Test' and Price gt 100
```

#### Null Handling Across Dialects
```typescript
// TypeScript 'undefined' → MySql 'IS NULL'
const expression = service.parse(`Status == undefined`);
console.log(service.stringify(expression, mySqlDialect));
// Output: Status IS NULL
```

---

## 🧩 Combining Expressions

The `combineExpression` method allows merging two expression trees:

```typescript
const eb = new ExpressionBuilder();
const service = new ExpressionService()
  .useDefaultConfiguration(defaultParserConfigurationCollection)
  .useDialect(linqToQuerystringDialect);

const expr1 = eb.filter(eb.entityCollection('Products'), eb.equals('active', true));
const expr2 = eb.filter(eb.entityCollection('Products'), eb.greaterThan('price', 100));

const combined = service.combineExpression(expr1, expr2);
console.log(service.stringify(combined));
// Filters are AND-combined: active eq true and price gt 100
```

#### Combination Rules

| Component | Default Behavior | With Override Flag |
|---|---|---|
| **Filters** | AND-combined | — |
| **OrderBy** | Keeps expression 1 | `CombineExpressionFlags.OrderOverwrite` uses expression 2 |
| **Skip** | Keeps expression 1 | `CombineExpressionFlags.SkipOverwrite` uses expression 2 |
| **Take** | Keeps expression 1 | `CombineExpressionFlags.TakeOverwrite` uses expression 2 |
| **Projection** | Intersection of properties | — |
| **EntityCollection** | Must be the same (or only one present) | — |

You can also pass strings instead of expression trees — they will be auto-parsed:

```typescript
const combined = service.combineExpression(`Name eq 'John'`, `Age gt 35`);
```

---

## ⚡ TypeScript Array Extensions

The library extends `Array.prototype` with `executeExpression()`, letting you run parsed expressions directly on in-memory arrays:

```typescript
import '@run-js/expressions';

// Setup: register as default instance
const service = new ExpressionService()
  .useDefaultConfiguration(defaultParserConfigurationCollection)
  .useDialect(typeScriptDialect)
  .registerAsDefaultInstance();

const data = [
  { name: 'Alice', age: 30, city: 'London' },
  { name: 'Bob', age: 25, city: 'Paris' },
  { name: 'Charlie', age: 35, city: 'London' }
];

// Requires ExpressionService.defaultInstance to be set (via registerAsDefaultInstance)
const result = data.executeExpression(`age > 28`);
// [{ name: 'Alice', age: 30, ... }, { name: 'Charlie', age: 35, ... }]
```

---

## 📖 API Reference

### ExpressionService

```typescript
class ExpressionService implements IExpressionService {
  static defaultInstance: IExpressionService;

  packageVersion: string;

  useDefaultConfiguration(config: IDefaultParserConfigurationCollection): IExpressionService;
  useDialect(dialect: IExpressionDialectConfigurationCollection,
             useForParse?: boolean,
             useForStringify?: boolean,
             isCaseSensitive?: boolean): IExpressionService;
  useValueConverter(valueConverter: IValueConverter,
                    forParse?: boolean,
                    forStringify?: boolean): IExpressionService;
  registerAsDefaultInstance(): IExpressionService;
  setLicense(licenseKey: string): Promise<IExpressionService>;

  parse(expression: string): IExpression;
  stringify(expression: IExpression,
            dialect?: IExpressionDialectConfigurationCollection): string;

  findExpressionByType<T extends IExpression>(expression: IExpression,
                                              ...expressionTypes: ExpressionType[]): T;
  combineExpression(expression1: IExpression | string,
                    expression2: IExpression | string,
                    optionFlags?: CombineExpressionFlags): IExpression;
}
```

### ExpressionBuilder

```typescript
class ExpressionBuilder implements IExpressionBuilder {
  // Values
  empty(): IEmptyExpression;
  null(): INullExpression;
  true(): ITrueExpression;
  false(): IFalseExpression;
  string(value: string): IStringExpression;
  stringEmpty(): IStringExpression;
  number(value: number): INumberExpression;
  dateTime(value?: Date | string | number): IDateTimeExpression;
  property(propertyName: string): IPropertyExpression;

  // Comparisons (left auto-converts a property name string → property, other primitives like right)
  equals(left, right): IEqualsExpression;
  notEquals(left, right): INotEqualsExpression;
  greaterThan(left, right): IGreaterThanExpression;
  greaterThanOrEqual(left, right): IGreaterThanOrEqualExpression;
  lessThan(left, right): ILessThanExpression;
  lessThanOrEqual(left, right): ILessThanOrEqualExpression;

  // Logical
  and(...expressions: AnyBooleanResultInterface[]): AnyBooleanResultInterface;
  or(...expressions: AnyBooleanResultInterface[]): AnyBooleanResultInterface;
  not(inner: AnyBooleanResultInterface): INotExpression;
  group(inner: AnyBooleanResultInterface): IGroupExpression;

  // String functions
  stringContains(source, searchString): IStringContainsExpression;
  stringStartsWith(source, searchString): IStringStartsWithExpression;
  stringEndsWith(source, searchString): IStringEndsWithExpression;
  stringMatches(source, searchString): IStringMatchesExpression;
  stringToLower(source): IStringToLowerExpression;
  stringToUpper(source): IStringToUpperExpression;

  // Comparisons (continued)
  propertyIn(property, ...values): IPropertyInExpression;

  // Collection operations
  entityCollection(collectionName: string): ICollectionExpression;
  filter(inner, filter, parameterName?): IFilterExpression;
  orderBy(inner, order, direction?): IOrderByExpression;
  orderThenBy(inner, order, direction?): IOrderThenByExpression;
  orderByM(inner, ...orders): IOrderByBaseExpression;
  skip(inner, skipCount: number): ISkipExpression;
  take(inner, takeCount: number): ITakeExpression;
  projection(inner, ...properties): IProjectionExpression;
}
```

### Expression Types

All expression types returned by `parse()` and the builder implement `IExpression`:

```typescript
interface IExpression { type: ExpressionType; }

// Values
interface IStringExpression extends IExpression { value: string; }
interface INumberExpression extends IExpression { value: number; }
interface IDateTimeExpression extends IExpression { value: Date; }
interface IPropertyExpression extends IExpression { propertyName: string; }

// Two-part (comparisons, AND, OR)
interface IEqualsExpression extends IExpression {
  left: AnyValueOrBooleanInterface;
  right: AnyValueOrBooleanInterface;
}

// Unary (NOT, GROUP)
interface INotExpression extends IExpression { inner: AnyBooleanResultInterface; }
interface IGroupExpression extends IExpression { inner: AnyBooleanResultInterface; }

// String functions
interface IStringContainsExpression extends IExpression {
  source: IStringFunctionSource;
  searchString: IStringOrValueExpression;
}

interface IPropertyInExpression extends IExpression {
  property: IPropertyExpression;
  values: IStringExpression[] | INumberExpression[];
}

// Collection
interface IFilterExpression extends IExpression {
  inner?: IExpression;
  filter: AnyBooleanResultInterface;
  parameterName?: string;
}
interface IOrderByExpression extends IExpression {
  inner?: IExpression;
  order: IExpression;
  direction?: CollectionOrderDirection;
}
interface ISkipExpression extends IExpression { inner?: IExpression; skipCount: number; }
interface ITakeExpression extends IExpression { inner?: IExpression; takeCount: number; }
interface IProjectionExpression extends IExpression {
  inner?: IExpression;
  properties: IPropertyExpression[];
}
```

---

## ⚠️ Known Limitations

| Limitation | Details |
|---|---|
| **MySql: `NO_BACKSLASH_ESCAPES` (dangerous)** | Not supported. String escaping relies on backslashes, with this SQL mode enabled on the server string values can break out of their quotes (SQL injection). |
| **MySql: Skip requires Take** | `OFFSET` is only valid with `LIMIT` in MySQL. Skip-only expressions cannot be stringified. |
| **MsSql: Skip/Take requires ORDER BY** | `OFFSET`/`FETCH` requires an `ORDER BY` clause in MSSQL. |
| **LinqToQuerystring Extensions: Page/PageSize** | Pagination (`$pagesize=N&$page=M`) is parse-only — cannot be stringified back. |
| **Nested properties** | Deep property paths like `a.b.c` are not natively supported. The TypeScript dialect strips the filter parameter name (e.g., `p.Name` → `Name`), but general dot-notation is not available. |
| **Aggregate functions** | `COUNT`, `SUM`, `AVG`, `MIN`, `MAX` are not supported. |
| **JOIN** | Cross-table joins are not supported. |
| **Subqueries** | Nested queries are not supported. |
| **LinqToQuerystring: exponent numbers** | Very large and very small numbers (`1e21`, `0.0000001`) are stringified in exponent notation (`1e+21`, `1e-7`). The LinqToQuerystring .NET library cannot read exponent notation. |
| **NaN and Infinity** | Not supported, only the TypeScript dialect could express them. The builder throws, parsing and stringifying fail. |
| **SQL: DateTime in UTC** | MySql and MsSql write DateTime values in UTC without time zone designator (`'2000-12-12T12:34:56.000'`). |
| **BETWEEN** | The `BETWEEN` operator is not supported (use `>=` and `<=` with `AND`). |
| **LIKE (pattern matching)** | Generic LIKE patterns are only used internally for `startsWith`, `endsWith`, and `contains`. |
| **SQL: property as search value** | In MySql and MsSql, `contains`, `startsWith`, `endsWith` and `matches` only accept a string as search value. A property (`contains(name, other)`) cannot be stringified and throws. |
| **Regular expression flags** | Patterns carry no flags (`i`, `m`, ...). Matching follows the defaults of the executing engine. Input with flags (`/a/i`, `REGEXP_LIKE(a, b, 'i')`) is not parsed. |
| **Regular expression support** | MsSql `REGEXP_LIKE` requires SQL Server 2025 or Azure SQL. `matchesPattern` is an OData 4.01 function and is not supported by the LinqToQuerystring .NET library. |

---

## 📄 License Information

This project is licensed under the [End-User License Agreement](LICENSE.md).

Every use of the package requires a valid Premium license key, see [License Key](#-license-key). The package is not sold publicly; licenses are issued to dedicated customers only. Contact [support@run-js.com](mailto:support@run-js.com) for a license.

>Code and documentation copyright 2022-2026 by [Rudolf Run Ltd](https://expressions.run-js.com).

## 🌐 About Rudolf Run Ltd

>We stand for uncompromising commitment to quality.<br>
>Our company's philosophy is that software should be designed to meet the highest standards, without cutting corners or sacrificing functionality.<br>
>And because we believe in fairness, everyone should have access to the best software, regardless of their budget.

Web: [https://expressions.run-js.com](https://expressions.run-js.com)<br>
Email: [support@run-js.com](mailto:support@run-js.com)

---

## 🏷️ Keywords

<div align="center">

`expression-parser` • `expression-builder` • `query-builder` • `query-converter` • `filter` •
`expression` • `expressions` • `expression-tree` • `AST` • `abstract-syntax-tree` • `parser` •
`parse` • `stringify` • `convert` • `converter` • `cross-dialect` • `dialect` •
`TypeScript` • `JavaScript` • `SQL` • `MySql` • `MsSql` • `linq` • `LinqToQueryString` •
`OData` • `odata-filter` • `odata-query` • `query-parser` • `query-builder` • `query-language` •
`sql-query` • `sql-builder` • `sql-filter` • `where-clause` • `filter-parser` • `filter-expression` •
`zero-dependencies` • `lightweight` • `fluent-api` • `100%-test-coverage` •
`node` • `nodejs` • `ts` • `js` • `universal-AST` • `cross-platform` • `database` •
`orderby` • `skip` • `take` • `projection` • `select` • `filtering` • `sorting` •
`collection` • `array-extensions` • `expression-combining` • `operators` • `comparison` •
`logical-operators` • `boolean-expression` • `and` • `or` • `not` • `equals` •
`greaterThan` • `lessThan` • `IN` • `propertyIn` •
`string-functions` • `contains` • `startsWith` • `endsWith` • `toLower` • `toUpper` • `regex` • `stringMatches` •
`datetime` • `null` • `grouping` • `union` • `intersection` •
`dsl` • `domain-specific-language`

---

**Made by [Rudolf Run Ltd](https://rudolf.run) with 🤯🤬😎 fighting JavaScript so you don't have to 😃.**
<br>
**Code and documentation copyright 2026 by [Rudolf Run Ltd](http://rudolf.run)**

</div>
