[](https://github.com/allsey87/comlink-rust/actions)
[](https://crates.io/crates/comlink)
[](https://docs.rs/comlink/)
[](LICENSE)
# comlink
Rust bindings for [Comlink](https://github.com/GoogleChromeLabs/comlink), plus an RPC layer on top of them: define a service as a trait, annotate it, and the macro generates the client and the service. Calls cross as comlink messages, so the peer on the other end of the endpoint can equally well be JavaScript. `JsValue`-bearing arguments are passed through untouched, byte buffers cross as `Uint8Array` (`Option<Vec<u8>>` as `Uint8Array` or `undefined`), and everything else is serialized with [serde-wasm-bindgen](https://github.com/RReverser/serde-wasm-bindgen). A method returning `Result` maps onto the call's promise: `Ok` resolves with the encoded value, `Err` rejects with the encoded error, so a JavaScript peer just returns or throws. Every rejection decodes into the error type, a thrown `Error` first reduced to its `message`, so keep that type a serde one such as `String`. Put the trait definition in a shared crate so both ends agree on it.
```rust
#[comlink::service]
pub trait Calculator {
async fn add(&self, left: u32, right: u32) -> u32;
}
```
The macro generates `CalculatorClient`, `CalculatorService`, and a `Calculator` trait you implement on the serving side:
```rust
#[derive(Clone)]
struct CalculatorImpl;
impl Calculator for CalculatorImpl {
async fn add(&self, left: u32, right: u32) -> u32 { left + right }
}
```
Expose it on the worker's global scope, then announce that the listener is attached:
```rust
let scope: web_sys::DedicatedWorkerGlobalScope = js_sys::global().unchecked_into();
CalculatorService::expose(CalculatorImpl, &scope);
comlink::ready::announce(&scope);
```
Wrap the worker on the other side once that announcement arrives:
```rust
let worker = web_sys::Worker::new("./worker.js").unwrap();
comlink::ready::wait(&worker).await;
let client = CalculatorClient::wrap(&worker);
assert_eq!(client.add(41, 1).await, 42);
```
## Features
- **Requests, notifications and streams**: a method with a return type is a request, a method returning nothing is fire-and-forget, and a method returning `impl Stream<Item = T>` forwards items over a callback proxy until the receiver is dropped.
- **Automatic argument routing**: `&str` crosses as a JavaScript string, `&[u8]` and `Vec<u8>` as a `Uint8Array`, anything `AsRef<JsValue>` untouched, everything else through serde.
- **Transfer semantics** via `#[transfer(param)]` on a method, naming the owned JavaScript arguments to hand over rather than clone.
- **Conditional methods**: `#[cfg(feature = "...")]` on a trait method is propagated onto the generated client and service.
- **A readiness handshake**: comlink has none of its own, so a request posted before the remote's listener attaches is simply lost. `comlink::ready::{announce, wait}` closes that window with a sentinel message both comlink listeners ignore.
## The crates
- **`comlink-sys`**: the raw `wasm_bindgen` bindings and nothing else: `expose`, `wrap`, `transfer`, `proxy`, `windowEndpoint`, and comlink's exported symbols. Also re-exported as `comlink::sys`.
- **`comlink`**: the macro re-exported, plus the runtime support the generated code calls into.
- **`comlink-macro`**: the `#[comlink::service]` attribute macro.
The npm [`comlink`](https://www.npmjs.com/package/comlink) package has to be resolvable by whatever bundler or module loader serves the generated JavaScript, since the bindings import it by that bare specifier.
Need help with your latest project? Get in touch via [contact@allwright.io](mailto:contact@allwright.io). I'm available for new assignments.