Expression query language - Enterprise

The expression language is a small, dependency-free query language your users author through the UI. It is the connective tissue behind alerts (and, in time, styled and calculated columns): one model for predicates, scalar maths, and change detection, evaluated the same way everywhere.

It ships in @svgrid/enterprise. Leaf comparisons delegate to the grid's own applyExcelFilter, so an expression's operators mean exactly what the column filter menu means.

The editor

<SvExpressionEditor> offers two modes over the same value:

Both show a live "matches N of M" count against a sample rows array.

<script lang="ts">
  import { SvExpressionEditor, type ExprColumn, type PredicateExpr } from '@svgrid/enterprise'

  const columns: ExprColumn[] = [
    { id: 'price', name: 'Price', type: 'number' },
    { id: 'region', name: 'Region', type: 'text' },
  ]
  let expr = $state<PredicateExpr>({ kind: 'const', value: true })
</script>

<SvExpressionEditor {columns} bind:value={expr} rows={sample} />

Text syntax

Column references are a bare id (price) or a bracketed label ([Unit Price]) when the name has spaces.

price > 100 AND region IN ("EU", "US")
name CONTAINS "widget" OR name STARTSWITH "gadget"
price BETWEEN 10 AND 20
name ISBLANK
price / qty >= 40           -- cross-column maths
SUM(amount) > 10000         -- aggregate over the rows in scope

A column = literal comparison parses to a grid-filter cmp node (so it folds accents/case like the filter row); general comparisons and maths parse to a scalarCmp.

The model

The canonical form is a JSON AST - no parser is needed at runtime. Three families:

Evaluating expressions

import { evaluatePredicate, parsePredicate, validateExpression } from '@svgrid/enterprise'

const expr = parsePredicate('price > 100 AND region = "EU"', columns)
validateExpression(expr, columns)                 // [] when sound
evaluatePredicate(expr, { row: { price: 120, region: 'EU' } })  // true

evaluateScalar and evaluateChange cover the other two families. All three are pure functions - safe to unit-test and to run in a Web Worker.

Advanced scalar functions can be injected per evaluation via the context's functions map (for example, to delegate a formula to HyperFormula) without making it a hard dependency.

Try it

Build a predicate in the visual editor and watch the AST underneath. The same value round-trips through the text mode, which is what makes it storable.

<script lang="ts">
  import { SvExpressionEditor } from '@svgrid/enterprise'
  import type { ExprColumn, PredicateExpr } from '@svgrid/enterprise'

  const columns: ExprColumn[] = [
    { id: 'name',       name: 'Name',       type: 'text' },
    { id: 'department', name: 'Department', type: 'text' },
    { id: 'age',        name: 'Age',        type: 'number' },
    { id: 'salary',     name: 'Salary',     type: 'number' },
  ]

  const rows = [
    { name: 'Ada Lovelace',   department: 'Engineering', age: 36, salary: 142000 },
    { name: 'Grace Hopper',   department: 'Engineering', age: 45, salary: 168000 },
    { name: 'Linus Torvalds', department: 'Platform',    age: 54, salary: 155000 },
  ]

  let expr = $state<PredicateExpr>({ kind: 'const', value: true })
</script>

<SvExpressionEditor {columns} {rows} bind:value={expr} />

<pre>{JSON.stringify(expr, null, 2)}</pre>

The AST is plain JSON, so a rule authored here is something you can put in a database and evaluate later - which is exactly what alerts do with it.

See also

Related articles