Skip to content

Expression Syntax

TinySystems uses a powerful expression language for data transformation in flow edges. Expressions are evaluated at runtime to transform data as it flows between components.

Basic Syntax

Expressions are wrapped in double curly braces:

{{expression}}

Root Reference

The $ symbol refers to the message data from the source port:

{{$}}           // Entire message
{{$.field}}     // Field access
{{$.nested.field}} // Nested field access

Path Access

Dot Notation

{{$.user.name}}
{{$.items.length}}
{{$.config.settings.timeout}}

Bracket Notation

For dynamic keys or special characters:

{{$["field-name"]}}
{{$[variableKey]}}
{{$.items[0]}}

Array Access

{{$.items[0]}}      // First item
{{$.items[-1]}}     // Last item
{{$.items[1:3]}}    // Slice (items 1 and 2)

Edge Configuration

In TinyNode edges, expressions define data mapping:

yaml
edges:
  - from: source-node.output
    to: target-node.input
    data:
      # Direct expression
      name: "{{$.user.name}}"

      # Computed value
      fullName: "{{$.firstName + ' ' + $.lastName}}"

      # Static value
      type: "user"

      # Nested object
      meta:
        timestamp: "{{$.createdAt}}"
        source: "api"

Expression Types

Simple Path

yaml
data:
  value: "{{$.data.value}}"

String Concatenation

yaml
data:
  greeting: "{{'Hello, ' + $.name + '!'}}"

Conditional

yaml
data:
  status: "{{$.active ? 'active' : 'inactive'}}"

Arithmetic

yaml
data:
  total: "{{$.price * $.quantity}}"
  discounted: "{{$.total * (1 - $.discount)}}"

Object Construction

yaml
data:
  user:
    id: "{{$.userId}}"
    email: "{{$.userEmail}}"
    active: true

Special Variables

Root Data

$           - The full message from source port
$.field     - Access field in message

Environment

$.env.VARIABLE   - Access environment variable

Context

$.context.key    - Access flow context values

Common Patterns

Pass-Through

Send entire message unchanged:

yaml
data: "{{$}}"

Field Extraction

Extract specific fields:

yaml
data:
  id: "{{$.user.id}}"
  name: "{{$.user.name}}"

Field Renaming

Rename fields in output:

yaml
data:
  userId: "{{$.id}}"           # id -> userId
  userName: "{{$.name}}"       # name -> userName

Default Values

Provide defaults for missing fields:

yaml
data:
  timeout: "{{$.timeout || 5000}}"
  retries: "{{$.retries || 3}}"

Null Coalescing

Handle null values:

yaml
data:
  name: "{{$.name ?? 'Unknown'}}"

Optional Chaining

Safe access to nested fields:

yaml
data:
  city: "{{$.address?.city ?? 'N/A'}}"

Array Mapping

Transform array items:

yaml
data:
  ids: "{{$.items.map(item => item.id)}}"
  names: "{{$.users.map(u => u.name)}}"

Array Filtering

Filter array items:

yaml
data:
  active: "{{$.users.filter(u => u.active)}}"
  adults: "{{$.people.filter(p => p.age >= 18)}}"

Object Spread

Merge objects:

yaml
data: "{{...$.original, timestamp: Date.now()}}"

Computed Properties

Dynamic field names:

yaml
data:
  "[$.fieldName]": "{{$.fieldValue}}"

Visual Editor

In the visual editor, expressions are entered in the edge configuration panel:

┌────────────────────────────────────────────────────────────────┐
│  Edge: http-request.response → transform.input                 │
├────────────────────────────────────────────────────────────────┤
│  Data Mapping                                                   │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │  {                                                        │  │
│  │    "status": "{{$.statusCode}}",                         │  │
│  │    "body": "{{$.body}}",                                 │  │
│  │    "success": "{{$.statusCode >= 200 && $.statusCode < 300}}" │
│  │  }                                                        │  │
│  └──────────────────────────────────────────────────────────┘  │
└────────────────────────────────────────────────────────────────┘

TinyNode YAML

Full edge configuration in a TinyNode:

yaml
apiVersion: operator.tinysystems.io/v1alpha1
kind: TinyNode
metadata:
  name: my-flow-abc123
spec:
  component: github.com/myorg/my-module/transformer
  module: my-module-v1
  edges:
    - from: _settings
      data:
        inputField: name
        outputField: result

    - from: upstream-node.output
      to: my-node.input
      data:
        inputValue: "{{$.value}}"
        computed: "{{$.a + $.b}}"
        formatted: "{{'Result: ' + $.result}}"

Expression Evaluation

┌─────────────────────────────────────────────────────────────────────────────┐
│                         EXPRESSION EVALUATION                                │
└─────────────────────────────────────────────────────────────────────────────┘

Source Port Output          Edge Expression              Target Port Input
┌─────────────┐           ┌──────────────────┐         ┌─────────────┐
│ {           │           │ data:            │         │ {           │
│   "name":   │  ─────▶   │   name: {{$.name}}   ─────▶  │   "name":   │
│     "John"  │           │   upper: {{...}} │         │     "John", │
│ }           │           └──────────────────┘         │   "upper":  │
└─────────────┘                                        │     "JOHN"  │
                                                       │ }           │
                                                       └─────────────┘

Error Handling

Invalid Path

yaml
# If $.nonexistent doesn't exist, result is undefined
data:
  value: "{{$.nonexistent}}"  # undefined

Safe Access

yaml
# Use optional chaining for safety
data:
  value: "{{$.maybe?.nested?.field ?? 'default'}}"

Type Coercion

yaml
# String to number
data:
  count: "{{parseInt($.countString)}}"

# Number to string
data:
  label: "{{String($.count)}}"

Performance Tips

  1. Keep Expressions Simple: Complex expressions run on every message
  2. Avoid Deep Nesting: $.a.b.c.d.e is slower than flat structures
  3. Use Filtering Carefully: Large array operations can be slow
  4. Cache Computed Values: Use intermediate nodes for reused computations

Next Steps

Build flow-based applications on Kubernetes