Data & Enterprise
Messaging & the Event Bus
Zornux has a built-in event bus. You declare an
event, subscribe to it with a handler, and
publish — and the language fans the message out to every
subscriber. It's the last major application programming model:
event-driven, publish/subscribe, and decoupled.
Events, handlers, publish
An event … end is a named message contract. A
handler Name for Event with e … end subscribes to it, reading
the payload through e. publish Event with …
raises it.
event UserRegistered
has user_id
has email
end
handler SendWelcome for UserRegistered with e
show "Welcome " + e.email
end
publish UserRegistered with user_id 42, email "[email protected]"
Publishing doesn't run the handlers — it enqueues one delivery per subscriber onto the background queue. Handlers run when work drains (end of the script, the end of a web request, or a serving worker), so publish never blocks. Every subscriber is notified, in declaration order.
Handlers are components
A handler is a first-class component, just like a service: it injects
dependencies with use and carries its own delivery
retry policy.
handler SendWelcome for UserRegistered with e retry 3 times waiting 30 seconds
use Mailer # inject a service, repository, or configuration
use AppConfig
Mailer.send(e.email, AppConfig.greeting)
end
| Clause | Role |
|---|---|
for Event | The event this handler subscribes to. |
with e | Binds the published payload (optional). |
use Name | Injects a dependency — a missing one is ZX2503, exactly like a service. |
retry N times [waiting M seconds] | Re-deliver on failure; the final failure is a dead-letter. |
Publish from anywhere
A publish works at the top level, inside a route, a job, or a
service method — so a request can announce a domain event and unrelated
handlers react.
controller Signups at "/signup"
on POST "/"
save user into Db.Users
publish UserRegistered with user_id user.id, email user.email
give back created
end
end
publish X on port N starts a web block; publish X with … (or a bare publish X) raises an event. The on tells them apart. No new reserved keywords — event and handler are contextual, so create event = 1 and create handler = 2 still bind ordinary variables.
Reliable delivery
Because deliveries ride the background queue, they inherit its whole story:
a failing handler is contained, retried on the
deterministic timeline, and dead-lettered on final
failure — with automatic messaging log events (a warning per
retry, an error on the dead-letter, each carrying the handler and event
names).
Introspection & health
Three built-ins read the bus, and GET /health gains an events block:
show events_published() # events raised this run
show handlers_pending() # deliveries awaiting the drain
show events_failed() # dead-lettered deliveries
{
"status": "healthy",
"events": { "published": 12, "pending": 0, "failed": 1 }
}
Diagnostics
| Code | Meaning |
|---|---|
ZX3200 | There's no event with that name. |
ZX3201 | The event has no such field. |
ZX3202 | Malformed publish (e.g. a field set twice). |
The v1 bus is in-process and in-memory — perfect for a single service and its background work. A distributed transport and durable event store are a documented future seam; this is the local half of that model.
Diagnostics are ZX3200–ZX3299. Built on background processing.