Introduction
We are excited to announce the latest update to Constela, bringing call expressions, lambda expressions, and the new array expressions to our compiler-first UI language. This update enables powerful data transformations and dynamic array construction while maintaining the security and determinism that define Constela.
What is Constela?
Constela is a compiler-first UI language designed for AI-generated UI. Unlike React or Next.js where you write UI with JavaScript, Constela uses a constrained JSON DSL that is validated, analyzed, and compiled into minimal runtime code.
| Constela | React / Next.js | |
|---|---|---|
| UI authoring | JSON DSL | JavaScript / JSX |
| Execution | compiler-driven | runtime-driven |
| State updates | declarative actions | arbitrary JS |
| Errors | structured errors | runtime exceptions |
Highlights
This release introduces three major expression types:
- Call Expression - Method calls on arrays, strings, Math, and Date
- Lambda Expression - Anonymous functions for array higher-order methods
- Array Expression - Dynamic array construction from expressions
Package Versions:
-
@constela/core: 0.15.2 -
@constela/compiler: 0.14.2 -
@constela/runtime: 0.18.3 -
@constela/server: 11.0.1
Call Expression
The call expression enables method invocation on target values with a clean, declarative syntax.
Syntax
{
"expr": "call",
"target": <Expression>,
"method": "<method_name>",
"args": [<Expression>, ...]
}
Basic Examples
Get array length:
{
"expr": "call",
"target": { "expr": "state", "name": "items" },
"method": "length"
}
Get element at index:
{
"expr": "call",
"target": { "expr": "state", "name": "items" },
"method": "at",
"args": [{ "expr": "lit", "value": 0 }]
}
Check if array includes a value:
{
"expr": "call",
"target": { "expr": "lit", "value": [1, 2, 3] },
"method": "includes",
"args": [{ "expr": "lit", "value": 2 }]
}
String Operations
Convert to uppercase:
{
"expr": "call",
"target": { "expr": "state", "name": "username" },
"method": "toUpperCase"
}
Split string into array:
{
"expr": "call",
"target": { "expr": "lit", "value": "react,typescript,node" },
"method": "split",
"args": [{ "expr": "lit", "value": "," }]
}
Trim whitespace:
{
"expr": "call",
"target": { "expr": "state", "name": "userInput" },
"method": "trim"
}
Math Operations
Calculate minimum value:
{
"expr": "call",
"target": { "expr": "var", "name": "Math" },
"method": "min",
"args": [
{ "expr": "lit", "value": 0 },
{ "expr": "state", "name": "count" }
]
}
Round a calculated value:
{
"expr": "call",
"target": { "expr": "var", "name": "Math" },
"method": "round",
"args": [
{
"expr": "bin",
"op": "*",
"left": { "expr": "state", "name": "price" },
"right": { "expr": "lit", "value": 1.1 }
}
]
}
Date Operations
Get current timestamp:
{
"expr": "call",
"target": { "expr": "var", "name": "Date" },
"method": "now"
}
Lambda Expression
The lambda expression enables anonymous functions for array higher-order methods like filter, map, find, and more.
Syntax
{
"expr": "lambda",
"param": "<parameter_name>",
"index": "<index_name>", // optional
"body": <Expression>
}
Filter Example
Filter items greater than a threshold:
{
"expr": "call",
"target": { "expr": "lit", "value": [1, 2, 3, 4, 5] },
"method": "filter",
"args": [
{
"expr": "lambda",
"param": "x",
"body": {
"expr": "bin",
"op": ">",
"left": { "expr": "var", "name": "x" },
"right": { "expr": "lit", "value": 2 }
}
}
]
}
Result: [3, 4, 5]
Map Example
Double each element:
{
"expr": "call",
"target": { "expr": "lit", "value": [1, 2, 3] },
"method": "map",
"args": [
{
"expr": "lambda",
"param": "x",
"body": {
"expr": "bin",
"op": "*",
"left": { "expr": "var", "name": "x" },
"right": { "expr": "lit", "value": 2 }
}
}
]
}
Result: [2, 4, 6]
Using Index Parameter
Map with index:
{
"expr": "call",
"target": { "expr": "lit", "value": ["a", "b", "c"] },
"method": "map",
"args": [
{
"expr": "lambda",
"param": "item",
"index": "i",
"body": { "expr": "var", "name": "i" }
}
]
}
Result: [0, 1, 2]
Accessing Object Properties
Extract names from user objects:
{
"expr": "call",
"target": { "expr": "state", "name": "users" },
"method": "map",
"args": [
{
"expr": "lambda",
"param": "user",
"body": {
"expr": "get",
"base": { "expr": "var", "name": "user" },
"path": "name"
}
}
]
}
Supported Methods
Array Methods
| Method | Description | Example |
|---|---|---|
length |
Get array length | items.length() |
at |
Get element at index (supports negative) | items.at(-1) |
includes |
Check if element exists | items.includes(value) |
slice |
Extract subarray | items.slice(1, 3) |
indexOf |
Find index of element | items.indexOf("foo") |
join |
Join elements into string | items.join(", ") |
filter |
Filter by predicate (lambda) | items.filter(x => x > 0) |
map |
Transform elements (lambda) | items.map(x => x * 2) |
find |
Find first matching (lambda) | items.find(x => x.id === 1) |
findIndex |
Find index of first matching | items.findIndex(x => x > 5) |
some |
Check if any match | items.some(x => x > 10) |
every |
Check if all match | items.every(x => x > 0) |
String Methods
| Method | Description | Example |
|---|---|---|
length |
Get string length | str.length() |
charAt |
Get character at index | str.charAt(0) |
substring |
Extract substring | str.substring(0, 5) |
slice |
Extract with negative index support | str.slice(-3) |
split |
Split into array | str.split(",") |
trim |
Remove whitespace | str.trim() |
toUpperCase |
Convert to uppercase | str.toUpperCase() |
toLowerCase |
Convert to lowercase | str.toLowerCase() |
replace |
Replace first occurrence | str.replace("a", "b") |
includes |
Check substring exists | str.includes("foo") |
startsWith |
Check prefix | str.startsWith("http") |
endsWith |
Check suffix | str.endsWith(".json") |
indexOf |
Find substring index | str.indexOf("@") |
Math Methods
| Method | Description | Example |
|---|---|---|
min |
Get minimum value | Math.min(a, b, c) |
max |
Get maximum value | Math.max(0, count) |
round |
Round to nearest integer | Math.round(3.7) |
floor |
Round down | Math.floor(3.9) |
ceil |
Round up | Math.ceil(3.1) |
abs |
Absolute value | Math.abs(-5) |
sqrt |
Square root | Math.sqrt(16) |
pow |
Power | Math.pow(2, 3) |
random |
Random 0-1 | Math.random() |
sin / cos / tan
|
Trigonometric functions | Math.sin(0) |
Date Methods
| Method | Description | Example |
|---|---|---|
now |
Current timestamp (ms) | Date.now() |
parse |
Parse date string | Date.parse("2024-01-01") |
toISOString |
ISO format string | date.toISOString() |
getTime |
Get timestamp | date.getTime() |
getFullYear |
Get year | date.getFullYear() |
Practical Examples
1. Filtering a TODO List
Display only incomplete tasks:
{
"version": "1.0",
"state": {
"todos": {
"type": "list",
"initial": [
{ "id": 1, "text": "Learn Constela", "completed": true },
{ "id": 2, "text": "Build app", "completed": false },
{ "id": 3, "text": "Deploy", "completed": false }
]
},
"showCompleted": { "type": "boolean", "initial": false }
},
"view": {
"kind": "each",
"items": {
"expr": "cond",
"if": { "expr": "state", "name": "showCompleted" },
"then": { "expr": "state", "name": "todos" },
"else": {
"expr": "call",
"target": { "expr": "state", "name": "todos" },
"method": "filter",
"args": [
{
"expr": "lambda",
"param": "todo",
"body": {
"expr": "not",
"operand": {
"expr": "get",
"base": { "expr": "var", "name": "todo" },
"path": "completed"
}
}
}
]
}
},
"as": "todo",
"body": {
"kind": "element",
"tag": "div",
"children": [
{
"kind": "text",
"value": {
"expr": "get",
"base": { "expr": "var", "name": "todo" },
"path": "text"
}
}
]
}
}
}
2. Search Filter with Case-Insensitive Matching
{
"expr": "call",
"target": { "expr": "state", "name": "products" },
"method": "filter",
"args": [
{
"expr": "lambda",
"param": "product",
"body": {
"expr": "call",
"target": {
"expr": "call",
"target": {
"expr": "get",
"base": { "expr": "var", "name": "product" },
"path": "name"
},
"method": "toLowerCase"
},
"method": "includes",
"args": [
{
"expr": "call",
"target": { "expr": "state", "name": "searchQuery" },
"method": "toLowerCase"
}
]
}
}
]
}
3. Chaining Methods
Filter then map:
{
"expr": "call",
"target": {
"expr": "call",
"target": { "expr": "lit", "value": [1, 2, 3, 4, 5] },
"method": "filter",
"args": [
{
"expr": "lambda",
"param": "x",
"body": {
"expr": "bin",
"op": ">",
"left": { "expr": "var", "name": "x" },
"right": { "expr": "lit", "value": 2 }
}
}
]
},
"method": "map",
"args": [
{
"expr": "lambda",
"param": "x",
"body": {
"expr": "bin",
"op": "*",
"left": { "expr": "var", "name": "x" },
"right": { "expr": "lit", "value": 2 }
}
}
]
}
Result: [6, 8, 10]
4. Dynamic Array Construction (Array Expression)
The new array expression enables dynamic array construction:
{
"expr": "array",
"elements": [
{ "expr": "var", "name": "basicSetup" },
{
"expr": "call",
"target": { "expr": "var", "name": "json" },
"method": "apply",
"args": []
}
]
}
This is equivalent to JavaScript's [basicSetup, json()].
Combining with state and literals:
{
"expr": "array",
"elements": [
{ "expr": "state", "name": "config" },
{ "expr": "lit", "value": "default" },
{ "expr": "var", "name": "customPlugin" }
]
}
Nested arrays:
{
"expr": "array",
"elements": [
{
"expr": "array",
"elements": [
{ "expr": "lit", "value": 1 },
{ "expr": "lit", "value": 2 }
]
},
{
"expr": "array",
"elements": [
{ "expr": "lit", "value": 3 },
{ "expr": "lit", "value": 4 }
]
}
]
}
5. Value Clamping
Ensure a value stays within bounds:
{
"expr": "call",
"target": { "expr": "var", "name": "Math" },
"method": "min",
"args": [
{ "expr": "lit", "value": 100 },
{
"expr": "call",
"target": { "expr": "var", "name": "Math" },
"method": "max",
"args": [
{ "expr": "lit", "value": 0 },
{ "expr": "state", "name": "value" }
]
}
]
}
Security Measures
Constela takes security seriously. All method calls are protected by multiple layers of defense:
1. Whitelist-Based Method Filtering
Only explicitly allowed methods can be called. Attempting to call a non-whitelisted method returns undefined:
{
"expr": "call",
"target": { "expr": "lit", "value": "hello" },
"method": "constructor" // Not allowed - returns undefined
}
The whitelist is defined at compile time and cannot be bypassed at runtime.
2. Prototype Pollution Prevention
Constela prevents access to prototype chain methods that could be used for injection attacks:
-
__proto__access is blocked -
constructoraccess is blocked -
prototypeaccess is blocked
3. Type-Aware Method Resolution
Methods are resolved based on the target value type, preventing type confusion attacks:
{
"expr": "call",
"target": { "expr": "lit", "value": 123 },
"method": "toUpperCase" // Returns undefined - numbers don't have this method
}
4. Safe Null/Undefined Handling
Calling methods on null or undefined returns undefined gracefully without throwing:
{
"expr": "call",
"target": { "expr": "state", "name": "nonexistent" },
"method": "length" // Returns undefined
}
Conclusion
This update to Constela brings powerful data transformation and array construction capabilities while maintaining the security-first approach that defines the framework. Call, lambda, and array expressions enable:
- Declarative data transformations - Filter, map, and transform data without JavaScript
- Dynamic array construction - Build arrays from variables, function results, and expressions
- Type-safe operations - Compile-time validation ensures correct usage
- Secure by default - Whitelist-based method filtering prevents injection attacks
- Composable expressions - Chain operations for complex transformations
Getting Started
# Create a new Constela project
npx create-constela my-app
cd my-app
npm run dev
Resources
We are excited to see what you build with Constela!
Constela is MIT licensed. Contributions welcome!
Top comments (0)