DEV Community

Yuuichi Eguchi
Yuuichi Eguchi

Posted on

Constela: Call, Lambda, and Array Expressions for Type-Safe Data Operations

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:

  1. Call Expression - Method calls on arrays, strings, Math, and Date
  2. Lambda Expression - Anonymous functions for array higher-order methods
  3. 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>, ...]
}
Enter fullscreen mode Exit fullscreen mode

Basic Examples

Get array length:

{
  "expr": "call",
  "target": { "expr": "state", "name": "items" },
  "method": "length"
}
Enter fullscreen mode Exit fullscreen mode

Get element at index:

{
  "expr": "call",
  "target": { "expr": "state", "name": "items" },
  "method": "at",
  "args": [{ "expr": "lit", "value": 0 }]
}
Enter fullscreen mode Exit fullscreen mode

Check if array includes a value:

{
  "expr": "call",
  "target": { "expr": "lit", "value": [1, 2, 3] },
  "method": "includes",
  "args": [{ "expr": "lit", "value": 2 }]
}
Enter fullscreen mode Exit fullscreen mode

String Operations

Convert to uppercase:

{
  "expr": "call",
  "target": { "expr": "state", "name": "username" },
  "method": "toUpperCase"
}
Enter fullscreen mode Exit fullscreen mode

Split string into array:

{
  "expr": "call",
  "target": { "expr": "lit", "value": "react,typescript,node" },
  "method": "split",
  "args": [{ "expr": "lit", "value": "," }]
}
Enter fullscreen mode Exit fullscreen mode

Trim whitespace:

{
  "expr": "call",
  "target": { "expr": "state", "name": "userInput" },
  "method": "trim"
}
Enter fullscreen mode Exit fullscreen mode

Math Operations

Calculate minimum value:

{
  "expr": "call",
  "target": { "expr": "var", "name": "Math" },
  "method": "min",
  "args": [
    { "expr": "lit", "value": 0 },
    { "expr": "state", "name": "count" }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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 }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Date Operations

Get current timestamp:

{
  "expr": "call",
  "target": { "expr": "var", "name": "Date" },
  "method": "now"
}
Enter fullscreen mode Exit fullscreen mode

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>
}
Enter fullscreen mode Exit fullscreen mode

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 }
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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 }
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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" }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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"
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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"
          }
        }
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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"
          }
        ]
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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 }
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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": []
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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" }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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 }
      ]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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" }
      ]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
  • constructor access is blocked
  • prototype access 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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Resources

We are excited to see what you build with Constela!


Constela is MIT licensed. Contributions welcome!

Top comments (0)