Chapter 36 · Volume V — Reference

AEL (ADL Expression Language)

36.1 Overview

AEL (ADL Expression Language) is a lightweight, sandboxed expression language embedded in the ADL runtime. It evaluates expressions in four contexts:

ContextUse Cases
ValidationCross-field validation rules (§14)
PermissionsRBAC expressions (§11)
Computed FieldsCalculated values (§13)
GuardsState machine conditions (§12)

AEL is not a general-purpose scripting language. It has no assignment, no loops, no mutation, and no I/O. Every expression is a pure function of its input context.


36.2 Grammar (EBNF)

expression     = conditional ;
conditional    = null_coalesce ( "?" expression ":" expression )? ;
null_coalesce  = logical_or ( "??" logical_or )* ;
logical_or     = logical_and ( "||" logical_and )* ;
logical_and    = equality ( "&&" equality )* ;
equality       = comparison ( ( "==" | "!=" ) comparison )* ;
comparison     = additive ( ( ">" | ">=" | "<" | "<=" | "in" ) additive )* ;
additive       = multiplicative ( ( "+" | "-" ) multiplicative )* ;
multiplicative = unary ( ( "*" | "/" | "%" ) unary )* ;
unary          = ( "!" | "-" ) unary | postfix ;
postfix        = primary ( "." IDENT | "?." IDENT | "[" expression "]"
                         | "(" arg_list? ")" )* ;
primary        = NUMBER | STRING | "true" | "false" | "null"
               | IDENT | "(" expression ")" | array | object
               | lambda ;
array          = "[" ( expression ( "," expression )* )? "]" ;
object         = "{" ( object_pair ( "," object_pair )* )? "}" ;
object_pair    = ( IDENT | STRING ) ":" expression ;
lambda         = IDENT "=>" expression
               | "(" param_list? ")" "=>" expression ;

36.3 Literals

TypeExamples
Number42, 3.14, -1, 1e6
String"hello", 'world'
Booleantrue, false
Nullnull
Array[1, 2, 3], ["a", "b"]
Object{ name: "Alice", age: 30 }

36.4 Operators

Arithmetic

+   Addition          2 + 3 → 5
-   Subtraction       5 - 2 → 3
*   Multiplication    3 * 4 → 12
/   Division          10 / 2 → 5
%   Modulo            10 % 3 → 1

Comparison

==  Equal             5 == 5 → true
!=  Not equal         5 != 3 → true
<   Less than         3 < 5 → true
<=  Less or equal     5 <= 5 → true
>   Greater than      5 > 3 → true
>=  Greater or equal  5 >= 5 → true
in  Membership        "a" in ["a", "b"] → true

Logical

&&  Logical AND       true && false → false
||  Logical OR        true || false → true
!   Logical NOT       !true → false

Special

??  Nullish coalesce  null ?? "default" → "default"
? : Ternary           age >= 18 ? "adult" : "minor"
?.  Optional chain    user?.address?.city

Property Access

.   Dot notation      record.title
[]  Bracket notation  record["field-name"]

Operator Precedence (highest to lowest)

  1. Property access (., ?., []), function calls, grouping ()
  2. Unary (!, -)
  3. Multiplicative (*, /, %)
  4. Additive (+, -)
  5. Comparison (<, <=, >, >=, in)
  6. Equality (==, !=)
  7. Logical AND (&&)
  8. Logical OR (||)
  9. Nullish coalescing (??)
  10. Ternary (? :)

36.5 Context Variables

Variables available depend on the evaluation context:

Validation Context

VariableTypeDescription
Field namesanyDirect field access: startDate, price, status
$thisobjectMerged record state (existing + new values)
$oldobject|nullRecord before update (null on create)
$opstringOperation: "create" or "update"

Example:

validation:
  dateOrder:
    rule: "endDate > startDate"
  futureOnly:
    rule: "$op != 'create' || startDate > now()"

Permission Context

VariableTypeDescription
userobjectCurrent user with id, username, email, roles
recordobjectRecord being accessed

Example:

permissions:
  update: "user.id == record.authorId || hasRole('admin')"

Guard Context (State Machines)

VariableTypeDescription
recordobjectCurrent record
userobjectUser attempting transition
transitionstringTransition name being attempted

Example:

transitions:
  publish:
    from: [draft, review]
    to: published
    guard: "hasRole('editor') && record.reviewedBy != null"

Computed Field Context

VariableTypeDescription
$thisobjectCurrent record
Field namesanyDirect field access

Example:

computed:
  fullName:
    type: string
    formula: "concat(firstName, ' ', lastName)"

36.6 Built-In Functions

String Functions (21)

FunctionExampleResult
len(s)len("hello")5
upper(s)upper("hello")"HELLO"
lower(s)lower("WORLD")"world"
trim(s)trim(" hi ")"hi"
substring(s, start, end?)substring("hello", 0, 3)"hel"
startsWith(s, prefix)startsWith("hello", "he")true
endsWith(s, suffix)endsWith("hello", "lo")true
contains(s, sub)contains("hello", "ell")true
indexOf(s, sub)indexOf("hello", "l")2
replace(s, search, repl)replace("hi", "i", "o")"ho"
split(s, delim)split("a,b,c", ",")["a","b","c"]
join(arr, delim)join(["a","b"], ",")"a,b"
concat(...)concat("a", " ", "b")"a b"
padStart(s, len, pad?)padStart("5", 3, "0")"005"
padEnd(s, len, pad?)padEnd("5", 3, "0")"500"
capitalize(s)capitalize("hello")"Hello"
camelCase(s)camelCase("hello world")"helloWorld"
snakeCase(s)snakeCase("HelloWorld")"hello_world"

Array Functions (21)

FunctionExampleResult
len(arr)len([1, 2, 3])3
first(arr)first([1, 2, 3])1
last(arr)last([1, 2, 3])3
at(arr, index)at([1, 2, 3], 1)2
slice(arr, start, end?)slice([1,2,3,4], 1, 3)[2,3]
concat(arr, ...arrs)concat([1], [2], [3])[1,2,3]
reverse(arr)reverse([1, 2, 3])[3,2,1]
sort(arr)sort([3, 1, 2])[1,2,3]
unique(arr)unique([1,2,2,3])[1,2,3]
flatten(arr, depth?)flatten([[1,2],[3,4]])[1,2,3,4]
includes(arr, val)includes([1,2,3], 2)true
map(arr, fn)map([1,2,3], x => x*2)[2,4,6]
filter(arr, fn)filter([1,2,3], x => x>1)[2,3]
find(arr, fn)find([1,2,3], x => x>1)2
every(arr, fn)every([2,4,6], x => x%2==0)true
some(arr, fn)some([1,2,3], x => x>2)true
reduce(arr, fn, init)reduce([1,2,3], (a,b)=>a+b, 0)6
sum(arr)sum([1, 2, 3])6
avg(arr)avg([1, 2, 3])2
min(arr)min([3, 1, 2])1
max(arr)max([3, 1, 2])3

Math Functions (14)

FunctionDescription
abs(n)Absolute value
ceil(n)Round up
floor(n)Round down
round(n, decimals?)Round to nearest
sqrt(n)Square root
pow(base, exp)Power
log(n)Natural logarithm
exp(n)e^n
sin(n), cos(n), tan(n)Trigonometric
min(...nums)Minimum
max(...nums)Maximum
random()Random 0-1
randomInt(min, max)Random integer

Date Functions (15)

FunctionExampleResult
now()now()Current timestamp
today()today()Current date (midnight)
dateAdd(date, n, unit)dateAdd(today(), 7, 'day')7 days from now
dateSub(date, n, unit)dateSub(today(), 1, 'month')1 month ago
dateDiff(d1, d2, unit)dateDiff(endDate, startDate, 'day')Days between
year(date)year(today())2026
month(date)month(today())1-12
day(date)day(today())1-31
hour(date)hour(now())0-23
minute(date)minute(now())0-59
second(date)second(now())0-59
dayOfWeek(date)dayOfWeek(today())0-6 (Sun-Sat)
isWeekend(date)isWeekend(today())true/false
formatDate(date, fmt)formatDate(today(), 'yyyy-MM-dd')"2026-05-17"
parseDate(str, fmt)parseDate("2026-05-17", 'yyyy-MM-dd')date value

Type Functions (8)

FunctionDescription
type(val)Returns "string", "number", "boolean", "null", "array", "object"
isString(val)Test if string
isNumber(val)Test if number
isBoolean(val)Test if boolean
isNull(val)Test if null
isArray(val)Test if array
isObject(val)Test if object
isDefined(val)Test if not null/undefined

ADL Context Functions

Authentication (3)

FunctionDescription
hasRole(role)Check if user has role
isOwner()Check if user owns record (user.id == record.createdBy)
isAuthenticated()Check if request has valid token

State Machine (3)

FunctionDescription
inState(state)Check if record is in state
canTransition(transition)Check if transition is available
currentState()Get current state value

Field Change (4)

FunctionDescription
fieldChanged(field)Check if field changed (update only)
oldValue(field)Get previous value (update only)
newValue(field)Get new value (update only)
isCreate()Check if operation is create
isUpdate()Check if operation is update

Query (2)

FunctionDescription
query(resource, filter)Query records (validation context only)
exists(resource, filter)Check if record exists (validation context only)

36.7 Lambda Expressions

AEL supports lambda expressions for collection operations:

Single parameter:      x => x * 2
Multiple parameters:   (a, b) => a + b

Example usage:

map([1, 2, 3], x => x * 2)                    → [2, 4, 6]
filter(items, x => x.price > 10)              → items with price > 10
reduce([1, 2, 3], (sum, x) => sum + x, 0)     → 6

36.8 Security Limits

LimitDefaultPurpose
Max expression length10,000 charsPrevent parser DoS
Max nesting depth32 levelsPrevent stack overflow
Max array size10,000 elementsPrevent memory exhaustion
Query timeout5 secondsPrevent long-running queries
Query result limit1,000 rowsPrevent memory exhaustion

36.9 Error Handling

Syntax Errors

Expression: "user.name +"
Error: Unexpected end of expression at line 1, column 12

Runtime Errors

Expression: "1 / 0"
Error: Division by zero

Expression: "user.address.city"
Context: { user: { name: "Alice" } }
Error: Cannot read property 'city' of undefined

Type Errors

Expression: "5 + 'hello'"
Error: Cannot add number and string

36.10 Integration Points

FeatureAEL Usage
Validation (§14)Cross-field validation rules
Permissions (§11)RBAC expressions with role/owner checks
Computed Fields (§13)Formula evaluation
State Machines (§12)Guard conditions on transitions
Rules (§22)Business rule conditions
Workflows (§25)Step conditions and data flow
Scopes (§20)Filter expressions
Integrations (§35)Data mapping and transform functions
Reports (§26)Highlight conditions and computed columns
Privacy (§33)Anonymization rules

End of Chapter 36.

Next: Chapter 37 — OpenAPI Generation