Connections

WebSocket connections for real-time communication in Constela

Overview

Connections enable real-time bidirectional communication through WebSocket. Define connections at the root level of your Constela program to establish persistent connections with WebSocket servers.

json
{
  "connections": {
    "chat": {
      "url": { "expr": "lit", "value": "wss://api.example.com/ws" },
      "onMessage": "handleMessage",
      "onOpen": "onConnected",
      "onClose": "onDisconnected",
      "onError": "onConnectionError"
    }
  }
}

WebSocket Connection Definition

NameTypeRequiredDefaultDescription
urlExpressionYes-WebSocket server URL. Supports dynamic URLs via expressions.
onMessagestringNo-Action name to execute when a message is received.
onOpenstringNo-Action name to execute when the connection opens.
onClosestringNo-Action name to execute when the connection closes.
onErrorstringNo-Action name to execute when an error occurs.

Event Handlers

onMessage

Called when a message is received from the server. The message data is available via the message parameter.

json
{
  "name": "handleMessage",
  "steps": [
    {
      "do": "update",
      "target": "messages",
      "operation": "push",
      "value": { "expr": "param", "name": "message" }
    }
  ]
}
NameTypeRequiredDefaultDescription
messageanyYes-The parsed message data from the WebSocket. JSON messages are automatically parsed.

onOpen

Called when the WebSocket connection is successfully established.

json
{
  "name": "onConnected",
  "steps": [
    {
      "do": "set",
      "target": "isConnected",
      "value": { "expr": "lit", "value": true }
    }
  ]
}

onClose

Called when the WebSocket connection is closed. The close event details are available via the event parameter.

json
{
  "name": "onDisconnected",
  "steps": [
    {
      "do": "set",
      "target": "isConnected",
      "value": { "expr": "lit", "value": false }
    }
  ]
}
NameTypeRequiredDefaultDescription
event.codenumberYes-WebSocket close code (e.g., 1000 for normal closure).
event.reasonstringYes-Human-readable close reason.

onError

Called when a WebSocket error occurs.

json
{
  "name": "onConnectionError",
  "steps": [
    {
      "do": "set",
      "target": "error",
      "value": { "expr": "lit", "value": "Connection failed" }
    }
  ]
}

Sending Messages

send Step

Sends a message through a WebSocket connection.

json
{
  "do": "send",
  "connection": "chat",
  "data": { "expr": "state", "name": "messageInput" }
}
NameTypeRequiredDefaultDescription
do"send"Yes-Step type identifier.
connectionstringYes-Name of the connection to send through.
dataExpressionYes-Data to send. Objects are automatically JSON stringified.

close Step

Closes a WebSocket connection.

json
{
  "do": "close",
  "connection": "chat"
}
NameTypeRequiredDefaultDescription
do"close"Yes-Step type identifier.
connectionstringYes-Name of the connection to close.
codeExpressionNo-Close status code (default: 1000).
reasonExpressionNo-Human-readable close reason.

Dynamic URLs

Connection URLs can be dynamically constructed using expressions:

json
{
  "connections": {
    "room": {
      "url": {
        "expr": "bin",
        "op": "+",
        "left": { "expr": "lit", "value": "wss://api.example.com/rooms/" },
        "right": { "expr": "state", "name": "roomId" }
      },
      "onMessage": "handleRoomMessage"
    }
  }
}

Complete Example: Chat Application

json
{
  "version": "1.0",
  "state": {
    "messages": { "type": "list", "initial": [] },
    "messageInput": { "type": "string", "initial": "" },
    "isConnected": { "type": "boolean", "initial": false },
    "username": { "type": "string", "initial": "Anonymous" }
  },
  "connections": {
    "chat": {
      "url": { "expr": "lit", "value": "wss://chat.example.com/ws" },
      "onMessage": "receiveMessage",
      "onOpen": "onConnected",
      "onClose": "onDisconnected"
    }
  },
  "actions": [
    {
      "name": "receiveMessage",
      "steps": [
        {
          "do": "update",
          "target": "messages",
          "operation": "push",
          "value": { "expr": "param", "name": "message" }
        }
      ]
    },
    {
      "name": "onConnected",
      "steps": [
        {
          "do": "set",
          "target": "isConnected",
          "value": { "expr": "lit", "value": true }
        }
      ]
    },
    {
      "name": "onDisconnected",
      "steps": [
        {
          "do": "set",
          "target": "isConnected",
          "value": { "expr": "lit", "value": false }
        }
      ]
    },
    {
      "name": "sendMessage",
      "steps": [
        {
          "do": "send",
          "connection": "chat",
          "data": {
            "expr": "obj",
            "properties": {
              "user": { "expr": "state", "name": "username" },
              "text": { "expr": "state", "name": "messageInput" },
              "timestamp": { "expr": "call", "fn": "Date.now" }
            }
          }
        },
        {
          "do": "set",
          "target": "messageInput",
          "value": { "expr": "lit", "value": "" }
        }
      ]
    },
    {
      "name": "updateInput",
      "steps": [
        {
          "do": "set",
          "target": "messageInput",
          "value": { "expr": "param", "name": "event", "path": "target.value" }
        }
      ]
    }
  ],
  "view": {
    "kind": "element",
    "tag": "div",
    "props": { "class": { "expr": "lit", "value": "chat-container" } },
    "children": [
      {
        "kind": "if",
        "condition": { "expr": "state", "name": "isConnected" },
        "then": {
          "kind": "text",
          "value": { "expr": "lit", "value": "Connected" }
        },
        "else": {
          "kind": "text",
          "value": { "expr": "lit", "value": "Disconnected" }
        }
      },
      {
        "kind": "element",
        "tag": "div",
        "props": { "class": { "expr": "lit", "value": "messages" } },
        "children": [
          {
            "kind": "each",
            "items": { "expr": "state", "name": "messages" },
            "as": "msg",
            "key": { "expr": "var", "name": "msg", "path": "timestamp" },
            "body": {
              "kind": "element",
              "tag": "div",
              "props": { "class": { "expr": "lit", "value": "message" } },
              "children": [
                {
                  "kind": "text",
                  "value": {
                    "expr": "bin",
                    "op": "+",
                    "left": {
                      "expr": "bin",
                      "op": "+",
                      "left": { "expr": "var", "name": "msg", "path": "user" },
                      "right": { "expr": "lit", "value": ": " }
                    },
                    "right": { "expr": "var", "name": "msg", "path": "text" }
                  }
                }
              ]
            }
          }
        ]
      },
      {
        "kind": "element",
        "tag": "input",
        "props": {
          "type": { "expr": "lit", "value": "text" },
          "value": { "expr": "state", "name": "messageInput" },
          "onInput": { "event": "input", "action": "updateInput" },
          "placeholder": { "expr": "lit", "value": "Type a message..." }
        }
      },
      {
        "kind": "element",
        "tag": "button",
        "props": {
          "onClick": { "event": "click", "action": "sendMessage" }
        },
        "children": [
          { "kind": "text", "value": { "expr": "lit", "value": "Send" } }
        ]
      }
    ]
  }
}

Advanced: TypedStateStore Integration

When integrating with external JavaScript libraries that need direct WebSocket access, you can obtain the underlying WebSocket instance through the runtime's TypedStateStore. This is typically only needed when:

  • Interfacing with libraries that require a native WebSocket object
  • Implementing custom binary protocols
  • Building debugging/monitoring tools

The $store variable provides access to the runtime's state store, which manages all connections. Use getConnection to retrieve a connection by name:

json
{
  "actions": [
    {
      "name": "handleComplexMessage",
      "steps": [
        {
          "do": "call",
          "target": { "expr": "var", "name": "$store", "path": "getConnection" },
          "args": [{ "expr": "lit", "value": "chat" }],
          "result": "wsConnection",
          "onSuccess": [
            {
              "do": "call",
              "target": { "expr": "var", "name": "wsConnection", "path": "send" },
              "args": [{ "expr": "state", "name": "complexData" }]
            }
          ]
        }
      ]
    }
  ]
}

TypedStateStore Methods

MethodDescription
getConnection(name)Returns the WebSocket instance for the named connection
getState(name)Returns the current value of a state field
setState(name, value)Updates a state field programmatically