Skip to main content
Version: 1.0.8

Message Bus New in 1.0.8

The message bus carries named messages between the three places logic runs in QUBIQ: pipelines, Designer views, and scripts. A button on a screen can start a pipeline; a pipeline can update every screen that is listening; a script can ask a pipeline a question and wait for the answer. None of them needs to know where the others are.

It replaces the 1.0.7 arrangement, in which a view's Send message stayed inside the browser tab and a separate system.message.send pushed from the gateway to browsers. There is now one bus and one call shape everywhere.

Subjects​

A message is addressed by a dotted subject: line1.motor.start, batch.released, report.ready.

RuleDetail
SegmentsLetters, digits, _ and -. No spaces, no other punctuation.
LengthAt most 200 characters and 16 segments.
SendingAn exact subject — no wildcards.
ReceivingA pattern: * matches exactly one segment, > matches one or more trailing segments and must come last.
PatternHears
line1.motor.startExactly that subject
line1.*.startline1.motor.start, line1.pump.start — not line1.motor.a.start
line1.>Everything under line1

Design subjects like tag paths: general to specific, so a receiver can take a whole branch with one >.

Scope​

Every message belongs to the project it was sent from. The server fills the project in from the sender's verified identity — the pipeline's own project, the operator's session, the script's run — so nothing a browser or a script writes can address another project, and a receiver's wildcard can never reach past its own project, not even with >.

ScopeReaches
All projects (all) — the defaultReceivers in every project that listen for broadcasts
This project (project)Receivers in the sender's project only

A view can also send locally — to one component, this view, or this browser tab — and a local message never leaves the browser. → Send message

A message with no scope broadcasts

all is the default for every sender, so a message sent without a scope reaches every project on the server and every pipeline listening on that subject. Pass This project (project) unless the message genuinely concerns the whole site.

Send, and request/reply​

A sender either sends — fire and forget, returning as soon as the message is on the bus — or requests: it waits for the first reply.

DefaultRange
Request timeout5000 ms100–60000 ms

A request fails rather than hanging:

  • Nobody is listening — it fails at once with "no receiver is listening for this request", without waiting out its timeout.
  • The receiving pipeline finished without replying — it fails at once, saying why: the run failed (with the error), or no Message Reply was reached. A pipeline that holds a Timer node is the exception, since a deferred branch may still reply; then the request waits for its timeout.
  • The receiver rejected it — the error it sent back, plus its details (see Errors that travel back).
  • No reply in time — "request timed out waiting for a reply".

Only a pipeline answers a request. A view's On message handlers hear sent messages, never requests, so a request whose subject only views listen on fails with no receiver.

Pipelines​

Three nodes in the Message Bus category of the palette:

NodeDoes
Message InStarts the pipeline when a message arrives on its Subject pattern. The payload is the message's payload; msg.topic is the exact subject that matched.
Message OutSends the incoming msg.payload on a Subject — or, in Request mode, waits for a reply and outputs it.
Message ReplyAnswers the request that started this run, with msg.payload or with an error.

Message In settings:

SettingDefaultMeaning
Subject— (required)The pattern to listen on.
Also receive messages sent to all projectsOnOff: only this project's messages.
Load-sharing groupEmptyEmpty: every receiver gets its own copy. Set: receivers in this project with the same group share the messages, each handled once.

Message Out settings:

SettingDefaultMeaning
SubjectEmptyAn exact subject. Empty reuses the incoming msg.topic — handy downstream of a Message In.
Send toAll projectsOr This project.
ModeSend — fire and forgetThe input passes through. Request — wait for reply: the reply becomes the output, and a timeout, no receiver or an error reply fails the node, which a Catch can handle.
Timeout (ms)5000Request mode only.

A Message Out in Request mode is never retried: a request that timed out may still have been acted on.

Message Reply answers the sender of the message that started the run. If that sender did a plain send there is nothing to answer, and the message passes through. In a run that a Message In did not start, the node fails.

A pipeline that answers a question
Message In ("motor.status") → SQL Query (latest state) → Message Reply (OK)
Asked from a client script
const status = await context.message.request("motor.status", { id: "m1" }, { scope: "project" });

When the receiving pipeline is too busy to take another message, a requester is told "receiving pipeline is busy" straight away.

Errors that travel back​

Set Message Reply's Reply with to Error — reject the request and wire a Catch node into it, and the error the Catch caught goes back to the sender: its message (unless you type one in Error message) plus the details — what failed and its traceback. Without a Catch, the sender gets the Error message text, or "request rejected".

Message In ("calc.run") → Python Script → Message Reply (OK)
Catch ──────────────────────────────────→ Message Reply (Error)
What the sender sees
try {
await system.message.request("calc.run", { x: 1 });
} catch (err) {
console.log(err.message, err.details?.source?.name); // the node that failed
}

In Python the exception is a SystemCallError, and the same details are on its details attribute.

Views​

A Designer Send message action sends on the bus with a subject, a scope, and optionally request mode with a Reply to client variable that keeps the answer for a widget to bind to. An On message handler on any widget listens on a pattern. → Send message · On message

A browser registers the patterns its handlers listen on, and the server forwards each one only the messages it handles — a busy pipeline does not flood every open screen. A network message reaches every open browser in scope, so a handler fires once per screen that has it open.

Scripts​

The same (subject, payload, opts) shape in every runtime:

CallWhereDoes
system.message.send(subject, payload?, opts?)Python and JavaScriptSend on the bus. opts.scope: all (default) or project.
system.message.request(subject, payload?, opts?)Python and JavaScriptSend and wait for the reply. opts.timeoutMs as above.
context.message.send(subject, payload?, opts?)BrowserAs system.message.send, plus the local scopes tab, view and component.
context.message.request(...)BrowserAs system.message.request.
context.message.onMessage(pattern, cb)BrowserListen from a script.

→ system.message · context.message

Limits and safeguards​

LimitValueWhy
Payload256 KB, JSONThe bus carries events, not files — save a file and send its URL.
Hops8A message sent because one was received carries a hop count. The ninth re-send is dropped, so two receivers answering each other cannot loop forever.
Browser send rate50 per second, bursts of 100Bounds a view-side loop, which no call-depth guard can see.

Who may send and listen:

  • Browsers send through their own session. An operator needs the Operate write level, the same as any other action that can change something — a message can start a pipeline that writes tags. An anonymous visitor of a public project can neither send nor listen.
  • Scripts need the script:execute scope. Python scripts do not publish on the bus themselves; they ask the message service in QBQ-RealtimeGateway, which resolves the project from the database.

What changed from 1.0.7​

Review scripts that send or listen for messages

Views are migrated the first time they are opened: a Send message action's type becomes its subject with every character a subject cannot hold turned into _ (Motor Start → Motor_Start), handlers are renamed the same way, and the old in-browser scopes become This browser tab only.

Script code is not rewritten:

  • A subject with a space, : or / is refused. system.message.send(...) and a network context.message.send(...) reject with "may only contain letters, digits, '_' and '-'"; a local context.message.send(..., {scope: "tab"}) or context.message.onMessage(...) only logs that warning to the browser console. Use the subject the migrated handler now has.
  • scope: "session" or "page" in context.message.send is not delivered — use "tab".
  • system.message.send(..., {"scope": "user"}) fails: the bus cannot deliver to one user. Send with project and put the user in the payload for the receiver to check.
  • A script that sends with no scope now broadcasts. context.message.send("reload", p) stayed in the tab in 1.0.7; it now reaches every project. Add {scope: "tab"} to keep it local.
  • The projectId, userId and from options of the old system.message.send are gone. The project is always the sender's own.

The Link node is confined to its own project as well: a Link-in that names a Link-out from another project hears nothing.

Next​

→ Variables · REST endpoints