Skip to main content

JavaScript

Inline JavaScript executed by the kernel. JavaScript.Script is a Telo.Invocable for per-request compute that is too complex for a CEL expression but does not warrant a dedicated controller.

Why use this

  • Invocable anywhere — usable from HTTP handlers, sequence steps, workflow nodes, or any invocable slot.
  • Typed inputs and outputsinputType / outputType accept inline JSON Schema or a named Type.JsonSchema; the runtime validates inputs before main runs.
  • Async-awaremain may be async; the kernel awaits the return value.
  • Structured errors — thrown Errors surface through the normal Run.Sequence try/catch flow.

Kinds

KindPurpose
JavaScript.ScriptRun an inline main({ ... }) JavaScript function as an invocable resource.

Example

kind: JavaScript.Script
metadata:
name: Add
inputType:
type: object
properties:
a: { type: number }
b: { type: number }
required: [a, b]
outputType:
type: object
properties:
sum: { type: number }
code: |
function main({ a, b }) {
return { sum: a + b };
}

The script contract

The code field must define a main function. The kernel calls it with the invocation inputs and uses the returned value as the result.

  • main may be async — the kernel awaits its return.
  • The returned value is the full result; property access (result.sum) works downstream.
  • Throwing an Error surfaces as an invocation error through the normal Run.Sequence try/catch flow.

Typed inputs and outputs

inputType and outputType accept either an inline JSON Schema or a named Type.JsonSchema reference. They drive analyzer validation and the editor's autocomplete — the runtime itself also validates inputs before main runs.

kind: Type.JsonSchema
metadata:
name: Email
schema:
type: object
properties:
email: { type: string }
required: [email]
---
kind: JavaScript.Script
metadata:
name: Normalize
inputType: Email
code: |
function main({ email }) {
return { normalized: email.trim().toLowerCase() };
}

Using it in a sequence

kind: Run.Sequence
metadata:
name: PriceItem
steps:
- name: compute
invoke:
kind: JavaScript.Script
inputs:
quantity: "${{ inputs.quantity }}"
unitPrice: "${{ inputs.unitPrice }}"
code: |
function main({ quantity, unitPrice }) {
const net = quantity * unitPrice;
return { net, gross: net * 1.23 };
}
outputs:
total: "${{ steps.compute.result.gross }}"

Notes

  • The Node.js controller compiles code via new Function. Scripts run in the host's global scope — they are not sandboxed. Treat JavaScript.Script as application code, not a trust boundary.
  • require and ESM import are not available (the code is a Function body, not a module). process, Buffer, and other globals are reachable if needed.
  • Scripts are compiled once at resource creation and reused across invocations, so avoid per-call top-level work — put state setup inside main if it depends on inputs.
  • For heavier logic (third-party libraries, typed models, shared helpers) write a dedicated controller package instead.