JavaScript Runtime
Package: @xpr-lang/xpr · GitHub · v0.5.0
Install
bash
npm install @xpr-lang/xpr
# or
bun add @xpr-lang/xprAPI
new Xpr()
Creates a new XPR engine instance.
typescript
import { Xpr } from '@xpr-lang/xpr';
const engine = new Xpr();engine.evaluate(expression, context?)
Evaluates an XPR expression string against an optional context object. Returns the result value.
Throws XprError if the expression is invalid or a runtime error occurs.
typescript
engine.evaluate('1 + 2')
// → 3
engine.evaluate('user.name ?? "anonymous"', { user: { name: null } })
// → "anonymous"
engine.evaluate('[1,2,3].map(x => x * 2)')
// → [2, 4, 6]
engine.evaluate('items.filter(x => x.active).map(x => x.name)', {
items: [
{ name: 'a', active: true },
{ name: 'b', active: false },
]
})
// → ['a']engine.addFunction(name, fn)
Registers a custom function that can be called from expressions.
typescript
engine.addFunction('slugify', (s: unknown) =>
String(s).toLowerCase().replace(/\s+/g, '-')
);
engine.evaluate('slugify(product.name)', { product: { name: 'Hello World' } })
// → 'hello-world'XprError
Thrown on parse errors and runtime errors. Has a position property (character offset).
typescript
import { Xpr, XprError } from '@xpr-lang/xpr';
try {
engine.evaluate('1 / 0');
} catch (e) {
if (e instanceof XprError) {
console.error(e.message); // "Division by zero"
}
}TypeScript
The package ships with full TypeScript declarations.
typescript
import { Xpr, XprError } from '@xpr-lang/xpr';
const engine = new Xpr();
const result: unknown = engine.evaluate('1 + 2');v0.2 Features
Let Bindings
typescript
engine.evaluate('let x = 1; let y = x + 1; y')
// → 2
engine.evaluate('let f = (x) => x * 2; f(5)')
// → 10
engine.evaluate('let items = [1,2,3,4,5]; items.filter(x => x > 2).map(x => x * 10)')
// → [30, 40, 50]Spread Operator
typescript
engine.evaluate('[...[1,2], ...[3,4]]')
// → [1, 2, 3, 4]
engine.evaluate('{...defaults, ...overrides}', {
defaults: { color: 'blue', size: 10 },
overrides: { color: 'red' },
})
// → { color: 'red', size: 10 }New Array Methods
typescript
engine.evaluate('[1,2,3].includes(2)') // → true
engine.evaluate('[1,2,3].indexOf(2)') // → 1
engine.evaluate('[1,2,3,4,5].slice(1, 3)') // → [2, 3]
engine.evaluate('[1,2,3].join(", ")') // → "1, 2, 3"
engine.evaluate('[1,2].concat([3,4])') // → [1, 2, 3, 4]
engine.evaluate('[[1,2],[3,4]].flat()') // → [1, 2, 3, 4]
engine.evaluate('[1,2,1,3].unique()') // → [1, 2, 3]
engine.evaluate('[1,2,3].zip([4,5,6])') // → [[1,4],[2,5],[3,6]]
engine.evaluate('[1,2,3,4,5].chunk(2)') // → [[1,2],[3,4],[5]]
engine.evaluate('[1,2,3].groupBy(x => x > 1 ? "big" : "small")')
// → { big: [2, 3], small: [1] }New String Methods
typescript
engine.evaluate('"hello".indexOf("ll")') // → 2
engine.evaluate('"ab".repeat(3)') // → "ababab"
engine.evaluate('" hi ".trimStart()') // → "hi "
engine.evaluate('" hi ".trimEnd()') // → " hi"
engine.evaluate('"hello".charAt(1)') // → "e"
engine.evaluate('"42".padStart(5, "0")') // → "00042"
engine.evaluate('"hi".padEnd(5, ".")') // → "hi..."New Object Methods
typescript
engine.evaluate('{"b": 2, "a": 1}.entries()') // → [["a",1],["b",2]]
engine.evaluate('{"a": 1}.has("a")') // → true
engine.evaluate('{"a": 1}.has("b")') // → falserange() Function
typescript
engine.evaluate('range(5)') // → [0, 1, 2, 3, 4]
engine.evaluate('range(1, 5)') // → [1, 2, 3, 4]
engine.evaluate('range(0, 10, 2)') // → [0, 2, 4, 6, 8]
engine.evaluate('range(5, 0, -1)') // → [5, 4, 3, 2, 1]v0.3 Features
Date/Time
Dates are epoch milliseconds (UTC only). ICU format tokens: yyyy, MM, dd, HH, mm, ss.
typescript
engine.evaluate('formatDate(now(), "yyyy-MM-dd")')
// → "2026-03-15"
engine.evaluate('dateDiff(parseDate("2024-01-01T00:00:00Z"), now(), "days")')
// → 439
engine.evaluate('dateAdd(parseDate("2024-01-31T00:00:00Z"), 1, "months")')
// → 1709337600000Regex Functions
RE2 flavor. Double-escape backslashes in JS string literals.
typescript
engine.evaluate('matches("hello 42", "\\\\d+")') // → true
engine.evaluate('match("order-123", "\\\\d+")') // → "123"
engine.evaluate('matchAll("a1b2c3", "\\\\d")') // → ["1", "2", "3"]
engine.evaluate('replacePattern("2024-01-15", "(\\\\d{4})-(\\\\d{2})-(\\\\d{2})", "$3/$2/$1")')
// → "15/01/2024"Negative Indexing and Spread in Calls
typescript
engine.evaluate('[1,2,3][-1]') // → 3
engine.evaluate('max(...[1, 5, 3, 2])') // → 5v0.4 Features
Destructuring
typescript
engine.evaluate('let {name, age} = user; name', {user: {name: 'Alice', age: 30}})
// → 'Alice'
engine.evaluate('users.map(({name, age}) => `${name}: ${age}`)',
{users: [{name: 'Alice', age: 30}, {name: 'Bob', age: 25}]})
// → ['Alice: 30', 'Bob: 25']
engine.evaluate('let [head, ...tail] = items; tail', {items: [1, 2, 3]})
// → [2, 3]
engine.evaluate('let {address: {city}} = user; city', {user: {address: {city: 'NYC'}}})
// → 'NYC'Regex Literals
typescript
engine.evaluate('/\\\\d+/.test("order-123")') // → true
engine.evaluate('"2024-01-15".match(/\\\\d{4}/)') // → "2024"
engine.evaluate('"hello world".replace(/o/, "0")') // → "hell0 w0rld"v0.5 Features
Math Functions and Constants
typescript
engine.evaluate('sqrt(16)') // → 4
engine.evaluate('log(E)') // → 1
engine.evaluate('pow(2, 10)') // → 1024
engine.evaluate('PI * pow(5, 2)') // → 78.53981633974483
engine.evaluate('sign(-7)') // → -1
engine.evaluate('trunc(3.9)') // → 3Type Predicates
typescript
engine.evaluate('isNumber(42)') // → true
engine.evaluate('isString("x")') // → true
engine.evaluate('isArray([1,2])') // → true
engine.evaluate('isObject([1,2])') // → false (arrays are "array" type)
engine.evaluate('isNull(null)') // → true
engine.evaluate('isRegex(/\\\\d+/)') // → trueNew Array Methods
typescript
engine.evaluate('[3,null,1,null,5].compact().sortBy(x => x)') // → [1, 3, 5]
engine.evaluate('[1,2,3,4,5].take(3)') // → [1, 2, 3]
engine.evaluate('[1,2,3,4,5].drop(2)') // → [3, 4, 5]
engine.evaluate('[1,2,3,4].sum()') // → 10
engine.evaluate('[1,2,3,4].avg()') // → 2.5
engine.evaluate('[1,2,3,4].partition(x => x > 2)') // → [[3, 4], [1, 2]]
engine.evaluate('items.keyBy(x => x.id)', {items: [{id:'a',v:1},{id:'b',v:2}]})
// → { a: {id:'a',v:1}, b: {id:'b',v:2} }
engine.evaluate('[3,1,2].first()') // → 3
engine.evaluate('[3,1,2].last()') // → 2
engine.evaluate('[1,2,3].count(x => x > 1)') // → 2fromEntries, str.split(/regex/), Rest Parameters
typescript
engine.evaluate('fromEntries([["a", 1], ["b", 2]])') // → { a: 1, b: 2 }
engine.evaluate('"a1b2c3".split(/\\\\d+/)') // → ["a", "b", "c"]
engine.evaluate('[1,2,3].map((...args) => args.length)') // → [1, 1, 1]