TotalBase
Steps

Code

Execute function, Execute JavaScript and Execute SQL.

In the admin: Workflows › a Workflow › a step

Three steps run code of your own: a PostgreSQL function, a piece of JavaScript, or a SQL statement. Prefer them in that order. A function keeps the logic in PostgreSQL, under permissions and audit; JavaScript transforms data without touching anything; SQL bypasses permissions altogether.

StepRunsPermissionsResult, as {{step.data}}
Execute functiona PostgreSQL function exposed as a resourcethe executor's EXECUTEits return value
Execute JavaScriptJavaScript in an isolated sandboxnone needed: it can't reach any datawhat the code returns
Execute SQLa raw SQL statementnone checkedthe rows returned

Execute function

Field
FunctionRequired. An ACTIVE function resource the executor may call
ArgumentsA JSON object: argument names and their values, with {{…}}

It's the same call as POST /api/{schema}/function/{name} in the Data API: arguments by name, in one transaction, with an audit entry. The executor needs EXECUTE on the function, otherwise the step fails with FUNCTION_EXECUTION_DENIED. A function that raises an exception fails the step with its message. See Calling functions.

The result is the value for a function returning one value, or the rows for a set-returning function.

Acme's Order completed creates the invoice this way:

FieldValue
Functioncreate_invoice_from_work_order
Arguments{"work_order": "{{start.data.id}}"}

The new invoice's id is then {{create_invoice.data}}.

A function call never starts Database event workflows, including through the rows the function writes.

Execute JavaScript

Field
CodeThe body of a function that receives trigger and results, and returns plain JSON
  • trigger is the run's input, the same value as {{start.data}}.
  • results holds every step that has run, by Step ID, each as its result envelope: results.read_orders.data, results.start.data.
// The selected customers' ids, without duplicates - a filter's _in takes the list as it is
return { ids: [...new Set(trigger.ids)] }
// A one-line summary per overdue order for the SLA email
return results.read_overdue.data.map(o => `${o.number}: due ${o.due_at}`).join("\n")

The sandbox

The code runs in its own V8 isolate (isolated-vm), separate from TotalBase's:

  • no require, no process, no file system, no network, no timers, no database;
  • 5 seconds at most per step;
  • 32 MiB of memory, for all the JavaScript steps of a run;
  • each step starts with a fresh context: nothing one step defines is visible to the next.

What the code returns is converted to JSON inside the sandbox, so only plain data leaves it: functions and class instances are dropped, and a circular value fails the step. An exception, a timeout or running out of memory fails the step with INTERNAL_ERROR and the error's message.

The code is taken literally: {{…}} isn't replaced inside it. Read values through trigger and results instead.

For HTTP calls, records, emails and files, use their own steps: JavaScript is for reshaping data between them.

Execute SQL

Field
Execute asThe user the statement is recorded under, in the SQL Console's history. Empty, the run's own user: its trigger's Execute as, or who ran it
StatementThe SQL to run
Execute SQL has no permission checks. The statement runs with the installation's own PostgreSQL role, as in the SQL Console. Execute as is recorded for attribution only; it doesn't restrict what the statement can do. Its writes have no per-record audit entries and start no Database event workflows. Anyone who can edit the workflow can change the statement.

It exists because a workflow is built by an administrator, who could run the same SQL in the SQL Console. Use it for what the other steps can't express, such as a maintenance statement on a schedule:

DELETE FROM vehicle_positions WHERE recorded_at < now() - interval '90 days'

The statement is taken literally: {{…}} isn't replaced inside it, so it can't take values from the trigger or earlier steps. For work that depends on them, write a PostgreSQL function and call it with Execute function, where the values are passed as arguments and permissions apply.

The result is the rows the statement returned, if any.

Next steps

  • Values: the {{…}} that Execute function's arguments take.
  • Functions: writing a function to call.