Autogenerated async RPC bindings that instantly connect a JS frontend to a Rust backend service via WebSockets and WASM.

Overview

Turbocharger

github crates.io docs.rs

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]
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 structs 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 warp 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.

main.rs

use turbocharger::{backend, server_only};
use wasm_bindgen::prelude::*;

#[cfg(not(target_arch = "wasm32"))]
use turbosql::{select, Turbosql};

#[backend]
#[cfg_attr(not(target_arch = "wasm32"), derive(Turbosql))]
pub struct Person {
 pub rowid: Option<i64>,
 pub name: Option<String>,
}

#[backend]
async fn insert_person(p: Person) -> Result<i64, turbosql::Error> {
 p.insert() // returns rowid
}

#[backend]
async fn get_person(rowid: i64) -> Result<Person, turbosql::Error> {
 select!(Person "WHERE rowid = ?", rowid)
}

#[server_only]
#[tokio::main]
async fn main() {
 #[derive(rust_embed::RustEmbed)]
 #[folder = "build"]
 struct Frontend;

 eprintln!("Serving on http://127.0.0.1:8080");
 warp::serve(turbocharger::warp_routes(Frontend)).run(([127, 0, 0, 1], 8080)).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

Your main.rs file is the entry point for both the server bin target and a wasm-bindgen lib target. 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 frontend lib target that makes the RPC call and delivers the response.

Because the project is compiled to both wasm32-unknown-unknown and the host triple, all functions and structs in main.rs should be annotated 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> where T is a wasm-bindgen-compatible type and E is a type that implements 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.

Server

Currently, the server side is batteries-included with warp, but this could be decoupled in the future. If this decoupling would be useful to you, please open a GitHub issue describing a use case.

WASM-only functions

You can also easily add standard #[wasm-bindgen]-style Rust functions to the WASM module, accessible from the frontend only:

#[turbocharger::wasm_only]
async fn get_wasm_greeting() -> String {
 "Hello from WASM".to_string()
}

To Do / Future Directions

  • Better WebSocket status management / reconnect
  • Streaming responses with futures::stream
  • Many things tarpc does, particularly around timeouts and cancellation.

License: MIT OR Apache-2.0 OR CC0-1.0 (public domain)

Comments
  • JS Promise rejections should use `new Error()` equivalent instead of string

    JS Promise rejections should use `new Error()` equivalent instead of string

    Provides better backtrace info, see e.g.:

    • https://github.com/rustwasm/wasm-bindgen/issues/1742
    • https://stackoverflow.com/questions/591857/how-can-i-get-a-javascript-stack-trace-when-i-throw-an-exception
    good first issue 
    opened by trevyn 2
  • Allow streaming responses to a Svelte store

    Allow streaming responses to a Svelte store

    #[backend]
    fn stream_example() -> impl Stream<Item = u32> {
     async_stream::stream! {
      let mut i = 0;
      loop {
       yield i;
       i += 1;
       tokio::time::sleep(tokio::time::Duration::from_millis(2000)).await;
      }
     }
    }
    
    
    <script>
     import * as backend from "./turbocharger_generated";
     let store = new backend.stream_example();
    </script>
    
    Store -> {$store}
    

    See also: https://tokio.rs/tokio/tutorial/streams https://github.com/trevyn/svelte-store-experiments

    opened by trevyn 1
  • `mod backend` should actually emit the mod declaration

    `mod backend` should actually emit the mod declaration

    #[turbocharger::backend]
    mod backend {
     // ...
    }
    

    Right now it's not emitted:

    https://github.com/trevyn/turbocharger/blob/70573ae2cc446018ceaeffdc69c6f41eba5fc50d/turbocharger-impl/src/lib.rs#L75

    opened by trevyn 1
  • In `mod backend`, only export `pub` functions

    In `mod backend`, only export `pub` functions

    #[turbocharger::backend]
    mod backend {
    
     // This should be made available to frontend
     pub async fn insert_person(p: Person) -> Result<i64, turbosql::Error> {
      p.insert() // returns rowid
     }
    
     // This should be server-only and NOT be made directly available to frontend
     async fn get_person(rowid: i64) -> Result<Person, turbosql::Error> {
      select!(Person "WHERE rowid = ?", rowid)
     }
    }
    

    Also maybe require pub on structs.

    opened by trevyn 1
  • Roadmap

    Roadmap

    Hey, are there any plans to make this production ready? I have been waiting for a crate like this for quite a while now. I am using async-graphql and some typescript code generation at the moment but this solution requires a lot of boilerplate. I think turbocharger is a good POC at the moment but imho it needs most of the open issues to be resolved before usable in production. I would be willing to spend some time contributing if you wish. I think this project can be a game-changer!

    cheers, Nico

    opened by nicolaiunrein 3
Owner
null
WebAssembly Service Porter

WebAssembly Service Porter.

henrylee2cn 12 Dec 12, 2022
A template for kick starting a Rust and WebAssembly project using wasm-pack.

A template for kick starting a Rust and WebAssembly project using wasm-pack.

Haoxi Tan 1 Feb 14, 2022
{Wasm+Rust} Build and animate Conway's Game of Life

A self-guided learning project that includes Rust + Wasm together. Who knows, maybe Typescript and React joins too..

M.Yavuz Yagis 1 Feb 14, 2022
Dister builds and bundles your wasm web app.

dister Dister builds and bundles your wasm web app. Installation cargo install dister Requirements wasm32-unknown-unknown target: rustup target add wa

Mohammed Alyousef 1 Apr 9, 2022
Wasm runtime written in Rust

Wasm runtime written in Rust

Teppei Fukuda 1 Oct 29, 2021
Wasm video filter booth app written in Rust

Video effect booth written in Rust and WebAssembly Play with it here: https://mtharrison.github.io/wasmbooth/ Aim I wrote this purely to teach myself

Matt Harrison 75 Nov 21, 2022
The Wasm-Enabled, Elfin Allocator

wee_alloc The Wasm-Enabled, Elfin Allocator API Docs | Contributing | Chat Built with ?? ?? by The Rust and WebAssembly Working Group About wee_alloc:

Rust and WebAssembly 567 Dec 28, 2022
wasmy, easily customize my wasm app

wasmy wasmy, easily customize my wasm app! features Completely shield vm-wasm interaction details Simple and flexible ABI, supports freely adding vm a

henrylee2cn 21 Jul 22, 2022
Rust-based WebAssembly bindings to read and write Apache Parquet files

parquet-wasm WebAssembly bindings to read and write the Parquet format to Apache Arrow. This is designed to be used alongside a JavaScript Arrow imple

Kyle Barron 103 Dec 25, 2022
bn.js bindings for Rust & WebAssembly with primitive-types support

bn.rs bn.js bindings for Rust & WebAssembly with primitive-types support Write Rust code that uses BN use std::str::FromStr; use primitive_types::{H1

Alexey Shekhirin 23 Nov 22, 2022
Client for integrating private analytics in fast and reliable libraries and apps using Rust and WebAssembly

TelemetryDeck Client Client for integrating private analytics in fast and reliable libraries and apps using Rust and WebAssembly The library provides

Konstantin 2 Apr 20, 2022
A console and web-based Gomoku written in Rust and WebAssembly

?? rust-gomoku A console and web-based Gomoku written in Rust and WebAssembly Getting started with cargo & npm Install required program, run # install

namkyu1999 2 Jan 4, 2022
darkforest is a console and web-based Roguelike written in Rust and WebAssembly.

darkforest darkforest is a console and web-based Roguelike written in Rust and WebAssembly. Key Features TBA Quick Start TBA How To Contribute Contrib

Chris Ohk 5 Oct 5, 2021
Let's combine wasi-nn and witx-bindgen and see how it goes!

WASI-NN Experiment (API Docs) Experiments with wasmtime, the wasi-nn proposal, and tract. Getting Started To use this experiment, you will first need

Hammer of the Gods 3 Nov 4, 2022
Simple file sharing with client-side encryption, powered by Rust and WebAssembly

Hako Simple file sharing with client-side encryption, powered by Rust and WebAssembly Not feature-packed, but basic functionalities are just working.

Jaehyeon Park 30 Nov 25, 2022
A handy calculator, based on Rust and WebAssembly.

qubit ?? Visit Website To Use Calculator Example ?? Visit Website To Use Calculator 2 + 2

Abhimanyu Sharma 55 Dec 26, 2022
Rust WebGL2 wrapper with a focus on making high-performance WebAssembly graphics code easier to write and maintain

Limelight Limelight is a WebGL2 wrapper with a focus on making high-performance WebAssembly graphics code easier to write and maintain. demo.mov live

drifting in space 27 Dec 30, 2022
Zaplib is an open-source library for speeding up web applications using Rust and WebAssembly.

⚡ Zaplib Zaplib is an open-source library for speeding up web applications using Rust and WebAssembly. It lets you write high-performance code in Rust

Zaplib 1.2k Jan 5, 2023
Mini operating system with a graphical interface, for x64 platforms, in Rust and Assembly

osmini Mini operating system with a graphical interface, for x64 platforms, in Rust and Assembly Build Don't forget to install the dependencies. This

Anтo 6 Dec 10, 2022