Architecture
Elyra is a Laravel-shaped desktop framework: the same ergonomic building blocks — an application container, service providers, middleware, a typed client — but compiled to a single native binary with no runtime interpreter.
The Laravel map
| Laravel | Elyra |
|---|---|
| Application + container | App + Container (ctx.get::<T>()) |
| ServiceProvider | Provider (register / boot) |
| routes/web.php | commands![...] |
| Controller action | #[command] async fn |
| Middleware | pipeline in CommandRegistry::dispatch |
| Broadcasting + Echo | EventBus + channel() store |
| Facades / HTTP client | generated api.* |
| Eloquent | #[derive(Model)] |
| Artisan | Ratatosk (rata) |
| Blade / Livewire | Svelte 5 (runes) |
Processes and threads
- The event loop + webview live on the main thread (required on macOS).
- A separate multi-thread tokio runtime owns all IPC work.
- The custom-protocol handler is asynchronous: each request is spawned onto tokio and responded to from there, so the UI thread never blocks on a command or an event long-poll.
┌─────────────── main thread ───────────────┐ ┌──── tokio runtime ────┐
│ tao event loop ── wry webview (WebKit) │ │ command dispatch │
│ └ elyra:// custom protocol handler ──────┼────▶│ middleware pipeline │
│ │◀────┼─ EventBus flushes │
└────────────────────────────────────────────┘ └───────────────────────┘
Request lifecycle (a command)
- Frontend calls
invoke("name", ...args)(orapi.name(...)). @elyra/runtimeMessagePack-encodes the args andfetcheselyra://localhost/__cmd/name.- The protocol handler spawns the request on tokio.
CommandRegistry::dispatchruns the middleware pipeline, then the command.#[command]decodes the arg tuple, runs yourasync fn, and MessagePack-encodes the result (or maps aResult::Errto an error response).- The bytes are returned; the runtime decodes them into the resolved value.
See wire format for the exact bytes.
State ownership
Rust owns the state; the frontend is a projection. Instead of one IPC round per
change, the EventBus accumulates events and flushes them as a
single batch to a long-poll the frontend holds open — binary, no base64.
Crates
framework/ elyra App, Container/Ctx, Command, EventBus, shell (tao+wry),
windows, tray, updater, codegen
macros/ elyra-macros #[command], #[derive(Model)]
database/ elyra-db Database (sqlx Any), migrations, models — no GUI deps,
so the CLI can use it without linking tao/wry
ratatosk/ ratatosk the `rata` CLI
runtime/ @elyra/runtime invoke(), channel(), the generated api.*
elyra-db is deliberately GUI-free so rata migrate (and any headless tool) can
drive the database without pulling in the windowing stack.
Performance principles
- Binary IPC — MessagePack over the custom protocol via
fetch(). No JSON in the hot path; streaming andArrayBufferfor free. - Rust owns state — diffs pushed via the
EventBus, batched per flush. - Assets from memory —
rust-embed+ protocol handler, no disk I/O at start. - Never block the UI thread — every command runs on tokio.