Crates.io | turbocharger |
lib.rs | turbocharger |
version | 0.4.0 |
source | src |
created_at | 2021-01-14 09:48:31.459373 |
updated_at | 2022-07-23 01:04:07.915914 |
description | Autogenerated async RPC bindings that instantly connect a JS frontend to a Rust backend service via WebSockets and WASM. |
homepage | |
repository | https://github.com/trevyn/turbocharger |
max_upload_size | |
id | 341835 |
size | 34,320 |
Autogenerated async RPC bindings that instantly connect a JS or Rust/WASM frontend to a Rust backend via a WebSocket connection.
See https://github.com/trevyn/turbocharger-template-dioxus or https://github.com/trevyn/turbocharger-template-svelte for a full turnkey template repository.
Makes a Rust backend function, e.g.:
use turbocharger::prelude::*;
#[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
Rust/WASM:
let person = get_person(1).await;
JS:
// export function get_person(id: number): Promise<Person>;
let person = await backend.get_person(1);
A Rust frontend works with any types that are serializable with bincode
.
A JS frontend 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).
A proc macro auto-generates a frontend wasm-bindgen
module, which serializes the frontend's 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 backend function and serializes the response. This is sent back over the WebSocket and resolves the Promise or Future returned by the original frontend function call.
Multiple async requests can be simultaneously in-flight over a single multiplexed connection; it all just works.
See https://github.com/trevyn/turbocharger-template-dioxus or https://github.com/trevyn/turbocharger-template-svelte for a full turnkey template repository.
app.rs
use turbocharger::prelude::*;
use turbosql::Turbosql;
#[backend]
#[derive(Turbosql, Default)]
pub struct Person {
pub rowid: Option<i64>,
pub name: Option<String>,
}
#[backend(js)]
pub async fn insert_person(p: Person) -> Result<i64, tracked::StringError> {
Ok(p.insert()?) // returns rowid
}
#[backend(js)]
pub async fn get_person(rowid: i64) -> Result<Person, tracked::StringError> {
Ok(turbosql::select!(Person "WHERE rowid = ?", rowid)?)
}
server.rs
mod app;
#[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::serve::<Frontend>(&addr).await;
}
wasm.rs
mod app;
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());
})();
Start a new project using https://github.com/trevyn/turbocharger-template-dioxus or https://github.com/trevyn/turbocharger-template-svelte for the full project layout and build scripts.
Your app.rs
module is included in both the server bin
target in server.rs
and a WASM target in wasm.rs
. The #[backend]
macro outputs three functions:
bin
target; you can call it directly from other server code if you wish.bin
target providing the RPC dispatch glue.Note that app.rs
is compiled to both wasm32-unknown-unknown
and the host triple, and that you can annotate functions and structs in app.rs
with #[backend]
or #[frontend]
.
#[backend(js)]
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.
Returning Result<T, tracked::StringError>
is recommended, and will produce an error that includes the line number of the error location.