Crate turbocharger
source · [−]Expand description
Turbocharger
Autogenerated async RPC bindings that instantly connect a JS frontend to a Rust backend service via WebSockets and WASM.
See https://github.com/trevyn/turbocharger-template for a full turnkey template repository.
Makes a Rust backend function, e.g.:
#[turbocharger::backend]
pub async fn get_person(id: i64) -> Person {
// ... write any async backend code here; ...
// ... query a remote database, API, etc. ...
Person { name: "Bob", age: 21 }
}
instantly available, with no additional boilerplate, to a frontend as
- an async JavaScript function
- with full TypeScript type definitions
- that calls the backend over the network:
// export function get_person(id: number): Promise<Person>;
let person = await backend.get_person(1);
Works with any types that are supported by wasm-bindgen
, which includes most basic types and custom struct
s with fields of supported types, but not yet enum
variants with values (which would come out the other end as TypeScript discriminated unions).
How It Works
A proc macro auto-generates a frontend wasm-bindgen
module, which serializes the JS function call parameters with bincode
. These requests are sent over a shared WebSocket connection to a provided axum
endpoint on the backend server, which calls your Rust function and serializes the response. This is sent back over the WebSocket and resolves the Promise returned by the original function call.
Multiple async requests can be simultaneously in-flight over a single multiplexed connection; it all just works.
Complete Example: A full SQLite-powered backend with frontend bindings
See https://github.com/trevyn/turbocharger-template for a full turnkey template repository.
backend.rs
use turbocharger::backend;
#[backend]
#[derive(turbosql::Turbosql, Default)]
pub struct Person {
pub rowid: Option<i64>,
pub name: Option<String>,
}
#[backend]
pub async fn insert_person(p: Person) -> Result<i64, turbosql::Error> {
p.insert() // returns rowid
}
#[backend]
pub async fn get_person(rowid: i64) -> Result<Person, turbosql::Error> {
turbosql::select!(Person "WHERE rowid = ?", rowid)
}
server.rs
mod backend;
#[tokio::main]
async fn main() {
#[derive(rust_embed::RustEmbed)]
#[folder = "build"]
struct Frontend;
eprintln!("Serving on http://127.0.0.1:8080");
let addr = std::net::SocketAddr::from(([0, 0, 0, 0], 8080));
turbocharger::axum_server::serve::<Frontend>(&addr).await;
}
index.js
import turbocharger_init, * as backend from "./turbocharger_generated";
(async () => {
await turbocharger_init();
let person = Object.assign(new backend.Person(), { name: "Bob" });
let rowid = await backend.insert_person(person);
console.log((await backend.get_person(rowid)).toJSON());
})();
Usage
Start a new project using https://github.com/trevyn/turbocharger-template for the full project layout and build scripts.
Your backend.rs
module is included in both the server-side bin
target in server.rs
and a wasm-bindgen
lib
target in wasm.rs
. The #[backend]
macro outputs three functions:
- Your function, unchanged, for the server
bin
target; you can call it directly from other server code if you wish. - An internal function for the server
bin
target providing the RPC dispatch glue. - A
#[wasm_bindgen]
function for the frontendlib
target that makes the RPC call and delivers the response.
Note that backend.rs
is compiled to both wasm32-unknown-unknown
and the host triple, and that you can annotate functions and structs in backend.rs
with one of #[backend]
, #[server_only]
, or #[wasm_only]
.
Error Handling
#[backend]
functions that need to return an error can return a Result<T, E: Display>
where T
is a wasm-bindgen
-compatible type and E
is a type that implements Display
, including any type implementing std::error::Error
, including Box<dyn std::error::Error>>
and anyhow::Error
. Errors crossing the network boundary are converted to a String
representation on the server via their to_string()
method and delivered as a Promise rejection on the JS side.
WASM-only functions
You can also easily add standard #[wasm_bindgen]
-style Rust functions to wasm.rs
, accessible from the frontend only:
#[wasm_bindgen]
pub async fn get_wasm_greeting() -> String {
"Hello from WASM".to_string()
}
License: MIT OR Apache-2.0 OR CC0-1.0 (public domain)
Attribute Macros
Apply this to a pub async fn
to make it available (over the network) to the JS frontend. Also apply to any struct
s used in backend function signatures.
Apply this to an item to make it available on the server target only.
Apply this to an item to make it available on the wasm target only.