Skip to content

The in-process transport

architect::local — serve a LayerRouter over a vox memory link: typed clients, no server, no socket.

1 min read

architect::local (the local feature, implies vox) serves a LayerRouter over a vox in-memory link, so the generated typed clients — ExampleRepoClient, AuthServiceClient, any <Trait>Client — consume a backend running in the same process. No HTTP server, no WebSocket, no port.

This is the consumer-facing half of idioms §7 — inject the transport, not the backend: screens, CLIs, and tests program against the client types; where the backend lives is decided once, at the root.

The surface#

Two items:

use architect::{LocalServer, Scope, serve_local};

// 1. Serve an already-built router…
let local = LocalServer::serve(router, scope.clone());

// 2. …or the shortcut for a `Services` backend (= backend.into_router()):
let local = serve_local(ExampleRepoMemory::new(), &scope);

// Either way: establish typed clients, same as over a WebSocket.
let repo: ExampleRepoClient = local.establish().await?;

LocalServer is Clone — hand it to whatever owns the app root. Each establish::<C>() opens a fresh memory-link pair plus an acceptor task (one client per session, mirroring the remote per-service connection shape); those tasks are registered on the Scope and aborted when it closes. Same acceptor dance as axum_ws::serve, just over vox_core::memory_link_pair instead of a socket.

Full round-trip, no server#

The same router the axum server mounts, identical asserts to the WebSocket path (the live, minimal version is over_vox in examples/greeter/tests/round_trip.rs):

use app_server::service_router;
use architect::{LocalServer, Scope};

let scope = Scope::new();
let local = LocalServer::serve(
    service_router(ExampleRepoMemory::new(), &app_server::Collab::ephemeral()),
    scope.clone(),
);
let repo: ExampleRepoClient = local.establish().await?;
let service: ExampleServiceClient = local.establish().await?;

let created = repo.create(ExampleCreate {
    name: "local".into(),
    description: "in-process".into(),
}).await?;
assert_eq!(repo.get(created.id).await?.name, "local");

let hits = service.search("local".into(), 10).await?;   // hand-written rpc
assert_eq!(hits.len(), 1);

scope.close().await;   // aborts the acceptor tasks

Every byte still goes through facet encoding and vox dispatch — this is a real wire test minus the network, which is exactly what makes it fast enough to live in the default test run. (It’s also how streaming subscriptions are tested: establish a <T>EventsClient / <T>StreamClient the same way — see server push.)

When to reach for it#

SituationWhy local
Desktop appwhole stack in-process; ship one binary, no server to launch
CLI toolscommand runs against an embedded backend (or a remote one — same client code)
Testsfull-wire coverage at unit-test speed; see testing strata
Servers, internallya server-side consumer of its own services without loopback HTTP

A desktop app is the headline use. Its root is the entire difference from the web build:

#[component]
fn Root() -> Element {
    let transport = use_hook(|| {
        let scope = Scope::new();
        let router = service_router(ExampleRepoMemory::new(), &app_server::Collab::ephemeral());
        Transport::Local(LocalServer::serve(router, scope))
    });
    use_context_provider(|| transport.clone());
    rsx! { App {} }
}

Same App, same screens, same client types as the browser — only the Transport differs, proving remote and local are behaviour-identical from the UI’s point of view.

Native and wasm#

architect::local builds on both targets. Native serves over vox_core::memory_link_pair; vox’s MemoryLink isn’t compiled for wasm, so on wasm32 it serves over architect’s own in-memory link (crates/architect/src/memory_link.rs, a port of vox’s channel logic without fd passing) and spawns with spawn_local (crates/architect/src/local.rs LocalServer). A browser client can run a backend in the page the same way a desktop shell does; a shell that only talks to a server keeps Transport::Remote(url) over WsLink.

Linked from

architect·Published with architect