Skip to content

Core concepts

OpexFlow is built around sources, transforms, sinks and one flow document that wires them — all normalized to CanonicalMachineData.

Updated View as Markdown

OpexFlow is built around four primitives. Everything else is plumbing.

Primitive Role
Source (adapter) Ingests events from a protocol: Modbus poll, OPC-UA polled read, HTTP REST poll, MQTT topic, NATS subject.
Transform (node) Reshapes each event before fan-out: a DSL program or a JavaScript function; alongside filter, branch, map and http nodes.
Sink (node) Delivers the result to one target: hub (WebSocket), HMAC-signed HTTP push, NATS, MQTT, SQLite, ClickHouse, Postgres.
Flow (document) The single wiring of sources → nodes → sinks, edited on the canvas and stored in <data_dir>/flow.json.

Every event is normalized to CanonicalMachineData at the source boundary, so transforms and sinks always see the same shape regardless of protocol.

One flow document

There is one routing model, the flow — there is no separate “transform route” vs “pipeline”. A canvas source node carries a match key:

match Picks
modbus:PLC-01 records of one machine ({source}:{machine.id})
modbus:* every record of one adapter
* everything not picked by a more specific source

Per record the engine looks up the exact key, then {source}:*, then *; match is unique across source nodes. A record no source matches is counted as unrouted (visible in the live stats) and dropped. The pipeline of a source is the subgraph reachable from it; a node may be shared by several sources, and a source with several wires fans out. Deploying validates and compiles every source first and swaps them all at once, so a bad edit never leaves the running flow half-changed.

The canvas can hold several named flows (tabs, flows: [{ id, name, disabled? }] in the document; each node carries its flow id). They only group nodes: routing is still global, so a wire never crosses flows, a match is unique across all of them, and disabling a flow switches off all its nodes at once.

On first start (no flow.json yet) the flow is seeded from the config’s transform section and the legacy pipelines.runtime.json / flows.layout.json with the same routing as before (a route without its own sinks inherits the default’s). From the first deploy on, flow.json is authoritative and the config’s transform routes are ignored.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close