Skip to content
MACHHUB MACHHUB MACHHUB
Contribute to this page

The Process model

A Process is a serverless function that MACHHUB runs for you. You write only the function body; the platform resolves your inputs beforehand, runs your code in an isolated worker, and applies your outputs afterward. (Processes are distinct from Flows, which are Node-RED pipelines.)

flowchart LR
  T["Trigger"] --> I["Inputs resolved"]
  I --> E["execute(context)"]
  E --> O["Outputs applied"]

A process record is defined by these fields:

FieldMeaning
nameDomain-scoped name, stored as domainKey.processName
languagepython or typescript
enabledWhether automatic (triggered) execution is on
log_enabledWhether execution logging is on
codeYour execute body
versionAuto-incremented on each code change
triggersWhat causes the process to run
inputsData resolved before your code runs
outputsWhat is done with the return value after your code runs

A trigger determines when a process runs. A process may have several, or none (in which case it is run manually).

TriggerRuns when…Key config
cronA cron schedule firescron_expression (e.g. */5 * * * *)
intervalA fixed interval elapsesinterval_value + interval_unit
tag_changeA subscribed tag changestag (supports MQTT wildcards + and #)
httpA request hits the process endpointendpoint (→ POST /process/<slug>)
manualYou run it explicitly (console Run, or the SDK)

Inputs are resolved before your code runs and injected into context.inputs, keyed by the input’s name.

TypeReadsConfig
tagThe current value of a tagtag (wildcards return a map keyed by concrete topic)
sqlThe result of a domain-scoped database queryquery
name: "temperature" type: "tag" config.tag: "sensors/room1/temperature"
→ context.inputs.temperature = 25.4
name: "latestReading" type: "sql" config.query: "SELECT * FROM myapp.readings ORDER BY created_dt DESC LIMIT 1;"
→ context.inputs.latestReading = [{ id: "...", value: 25.4 }]

A wildcard tag input (sensors/+/temperature) resolves to a map: { "sensors/room1/temperature": 25.4, "sensors/room2/temperature": 22.1 }.

Outputs are processed after your code returns, using the return value.

TypeActionConfigTemplating
sqlExecute a queryquery{{output.field}}returnValue.field
tag_writeWrite a value to a tagtag, fielddot-notation, e.g. result.value
# SQL output
config.query: "UPDATE myapp.status SET value = '{{output.status}}' WHERE id = 'myapp.status:main';"
# Tag write — writes returnValue.status to the tag
config.tag: "alerts/room1/status"
config.field: "status" # dot-notation: "result.nested.value" also works

Processes run in Python or TypeScript. The platform wraps your body:

// TypeScript
async function execute(context: ProcessContext): Promise<any> {
const { inputs, trigger } = context;
// YOUR CODE HERE
}
# Python
def execute(context):
inputs = context['inputs']
trigger = context['trigger']
# YOUR CODE HERE

The context carries inputs, trigger (type, config, and runtime-only data), plus timestamp, domain_id, and process_name. See Python processes and TypeScript processes for full examples.

Each domain runs its own isolated Python and TypeScript worker processes:

  • Workers start automatically when the first process for a domain is created or enabled, and stop when the last one is deleted or disabled.
  • Workers restart after package installation (pip install / npm install).
  • Each domain has its own isolated Python and TypeScript environments, and communicates with the API internally, per domain.

Saving a code change auto-increments the process version and creates a new file — processes are version-controlled.

flowchart TB
  subgraph DomainA["Domain A"]
    PA["Python worker (venv)"]
    TA["TypeScript worker (node_modules)"]
  end
  subgraph DomainB["Domain B"]
    PB["Python worker (venv)"]
    TB["TypeScript worker (node_modules)"]
  end
  API["MACHHUB Platform"] <-->|messaging| PA
  API <-->|messaging| TA
  API <-->|messaging| PB
  API <-->|messaging| TB