JetStream
JetStream is an RPC framework designed to be a high performance, low latency, secure, and reliable RPC framework.
Transport Backends
JetStream supports multiple transport backends:
- quinn - QUIC transport with TLS/mTLS support
- iroh - P2P transport with built-in NAT traversal
- webtransport - WebTransport transport for browser and server environments
Features
- Bidirectional streaming
- 0-RTT
- mTLS
- Binary encoding
- Cross-platform (Linux, macOS, Windows, WebAssembly)
- Cross-language — TypeScript and Swift clients with wire-compatible codegen
For detailed API documentation, see the rustdoc documentation.
Examples
- echo - Basic QUIC-based echo service example
- iroh_echo - Echo service using iroh transport
- wasm_example - WebAssembly example
- wasm_example_bindings - WebAssembly bindings example
JetStream Iroh
Iroh is a peer-to-peer networking library that provides NAT traversal, hole punching, and relay fallback. JetStream integrates with Iroh to enable peer-to-peer RPC communication.
Features
- NAT Traversal: Automatic hole punching for direct peer-to-peer connections
- Relay Fallback: Falls back to relay servers when direct connection isn’t possible
- Discovery: Uses JetStream’s discovery service at
discovery.jetstream.rs - ALPN-based Protocol Negotiation: Each service defines its own protocol version
Example
Here’s a complete example of an echo service using Iroh transport:
#![cfg(feature = "iroh")]
use crate::square_protocol::{SquareChannel, SquareService};
use futures::stream::FuturesUnordered;
use futures::StreamExt;
use jetstream::prelude::*;
use jetstream_macros::service;
#[service(tracing)]
pub trait Square {
async fn square(&self, ctx: Context, i: u32) -> Result<String>;
}
#[derive(Debug, Clone)]
struct SquareServer;
impl Square for SquareServer {
async fn square(&self, _ctx: Context, i: u32) -> Result<String> {
Ok((i * i).to_string())
}
}
#[tokio::main]
async fn main() {
// Initialize tracing subscriber
tracing_subscriber::fmt()
.with_max_level(tracing::Level::DEBUG)
.with_thread_ids(true)
.with_span_events(
tracing_subscriber::fmt::format::FmtSpan::ENTER
| tracing_subscriber::fmt::format::FmtSpan::EXIT,
)
.init();
// Build the server router with the echo service
let router = jetstream_iroh::server_builder(SquareService {
inner: SquareServer {},
})
.await
.unwrap();
// get our own address. At this point we have a running router
// that's ready to accept connections.
let addr = router.endpoint().addr();
// Build client transport and connect
let transport = jetstream_iroh::client_builder::<SquareChannel>(addr)
.await
.unwrap();
let ec = SquareChannel::new(10, Box::new(transport));
let mut futures = FuturesUnordered::new();
for i in 0..1000 {
futures.push(ec.square(Context::default(), i));
}
while let Some(result) = futures.next().await {
let response = result.unwrap();
println!("Response: {}", response);
}
router.shutdown().await.unwrap();
}
Server Setup
To create an Iroh server, use the server_builder function:
#![allow(unused)]
fn main() {
use jetstream_iroh::server_builder;
let router = server_builder(EchoService { inner: EchoServer {} })
.await
.unwrap();
// Get the node address to share with clients
let addr = router.endpoint().node_addr();
}
Client Setup
To connect to an Iroh server, use the client_builder function:
#![allow(unused)]
fn main() {
use jetstream_iroh::client_builder;
let mut transport = client_builder::<EchoChannel>(addr)
.await
.unwrap();
}
Discovery
JetStream uses a custom discovery service for Iroh nodes. The discovery URL is:
https://discovery.jetstream.rs
This allows nodes to find each other using their public keys without needing to exchange IP addresses directly.
Feature Flag
To use Iroh transport, enable the iroh feature in your Cargo.toml:
[dependencies]
jetstream = { version = "10", features = ["iroh"] }
For more details, see the jetstream_iroh API documentation.
JetStream QUIC
QUIC is a modern transport protocol that provides multiplexed connections over UDP with built-in TLS 1.3 encryption. JetStream QUIC provides the transport layer using quinn.
Features
- ALPN-based Routing: Route connections to different handlers based on ALPN protocol negotiation
- TLS 1.3: Built-in secure transport with rustls
- 0-RTT: Support for zero round-trip connection resumption
- mTLS Support: Mutual TLS authentication for client certificate verification
- Peer Identity: Extract client certificate information (CN, fingerprint, SANs) in request handlers
Architecture
The crate is organized around these core components:
Server: The main QUIC server that accepts incoming connectionsRouter: Routes connections to protocol handlers based on ALPNProtocolHandler: Trait for implementing custom protocol handlersClient: QUIC client for connecting to servers
For HTTP/3 support, see jetstream_http.
Example
Here’s a complete echo service example:
use std::{net::SocketAddr, path::Path, sync::Arc};
use echo_protocol::EchoChannel;
use jetstream::prelude::*;
use jetstream_macros::service;
use jetstream_quic::{
Client, QuicRouter, QuicRouterHandler, QuicTransport, Server,
};
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
#[service]
pub trait Echo {
async fn ping(&mut self) -> Result<()>;
}
#[derive(Clone)]
struct EchoImpl {}
impl Echo for EchoImpl {
async fn ping(&mut self) -> Result<()> {
eprintln!("Ping received");
eprintln!("Pong sent");
Ok(())
}
}
pub static CA_CERT_PEM: &str =
concat!(env!("CARGO_MANIFEST_DIR"), "/certs/ca.pem");
pub static CLIENT_CERT_PEM: &str =
concat!(env!("CARGO_MANIFEST_DIR"), "/certs/client.pem");
pub static CLIENT_KEY_PEM: &str =
concat!(env!("CARGO_MANIFEST_DIR"), "/certs/client.key");
pub static SERVER_CERT_PEM: &str =
concat!(env!("CARGO_MANIFEST_DIR"), "/certs/server.pem");
pub static SERVER_KEY_PEM: &str =
concat!(env!("CARGO_MANIFEST_DIR"), "/certs/server.key");
fn load_certs(path: &str) -> Vec<CertificateDer<'static>> {
let data = std::fs::read(Path::new(path)).expect("Failed to read cert");
rustls_pemfile::certs(&mut &*data)
.filter_map(|r| r.ok())
.collect()
}
fn load_key(path: &str) -> PrivateKeyDer<'static> {
let data = std::fs::read(Path::new(path)).expect("Failed to read key");
rustls_pemfile::private_key(&mut &*data)
.expect("Failed to parse key")
.expect("No key found")
}
async fn server(
addr: SocketAddr,
) -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
let server_cert = load_certs(SERVER_CERT_PEM).pop().unwrap();
let server_key = load_key(SERVER_KEY_PEM);
let ca_cert = load_certs(CA_CERT_PEM).pop().unwrap();
let mut root_store = rustls::RootCertStore::empty();
root_store.add(ca_cert).expect("Failed to add CA cert");
let client_verifier =
rustls::server::WebPkiClientVerifier::builder(Arc::new(root_store))
.allow_unauthenticated()
.build()
.expect("Failed to build client verifier");
// Register the EchoService via the per-stream RPC router
let echo_service = echo_protocol::EchoService { inner: EchoImpl {} };
let rpc_router = Arc::new(
jetstream_rpc::Router::new()
.with_handler(echo_protocol::PROTOCOL_NAME, echo_service),
);
let quic_handler = QuicRouterHandler::new(rpc_router);
let mut router = QuicRouter::new();
router.register(Arc::new(quic_handler));
let server = Server::new_with_mtls(
vec![server_cert],
server_key,
client_verifier,
addr,
router,
);
eprintln!("Server listening on {}", addr);
server.run().await;
Ok(())
}
async fn client(
addr: SocketAddr,
) -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
// Wait for server to start
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
let ca_cert = load_certs(CA_CERT_PEM).pop().unwrap();
let client_cert = load_certs(CLIENT_CERT_PEM).pop().unwrap();
let client_key = load_key(CLIENT_KEY_PEM);
let alpn = vec![b"jetstream".to_vec()];
let bind_addr: SocketAddr = "0.0.0.0:0".parse().unwrap();
let client = Client::new_with_mtls(
ca_cert,
client_cert,
client_key,
alpn,
bind_addr,
)?;
let connection = client.connect(addr, "localhost").await?;
// Open a bidirectional stream and wrap it in QuicTransport
let (send, recv) = connection.open_bi().await?;
let transport: QuicTransport<EchoChannel> = (send, recv).into();
let mut chan = EchoChannel::new(10, Box::new(transport));
chan.negotiate_version(u32::MAX).await?;
eprintln!("Ping sent");
chan.ping().await?;
eprintln!("Pong received");
Ok(())
}
#[tokio::main]
async fn main() {
// Install the ring crypto provider for rustls
rustls::crypto::ring::default_provider()
.install_default()
.ok();
let addr: SocketAddr = "127.0.0.1:4433".parse().unwrap();
tokio::select! {
_ = server(addr) => {},
_ = client(addr) => {},
}
}
Defining a Service
Use the #[service] macro to define an RPC service:
#![allow(unused)]
fn main() {
use jetstream::prelude::*;
use jetstream_macros::service;
#[service]
pub trait Echo {
async fn ping(&mut self) -> Result<()>;
async fn echo(&mut self, message: String) -> Result<String>;
}
}
The macro generates:
EchoChannel- Client-side channel for calling methodsEchoService- Server-side wrapper for your implementation
Implementing the Service
#![allow(unused)]
fn main() {
#[derive(Clone)]
struct EchoImpl;
impl Echo for EchoImpl {
async fn ping(&mut self) -> Result<()> {
Ok(())
}
async fn echo(&mut self, message: String) -> Result<String> {
Ok(message)
}
}
}
Server Setup
#![allow(unused)]
fn main() {
use std::sync::Arc;
use jetstream_quic::{Server, Router};
// Create the service
let echo_service = echo_protocol::EchoService { inner: EchoImpl {} };
// Register with router
let mut router = Router::new();
router.register(Arc::new(echo_service));
// Create and run server
let server = Server::new_with_addr(cert, key, addr, router);
server.run().await;
}
Client Setup
#![allow(unused)]
fn main() {
use jetstream_quic::{Client, QuicTransport};
use jetstream_rpc::Protocol;
// Create client
let alpn = vec![EchoChannel::VERSION.as_bytes().to_vec()];
let client = Client::new_with_mtls(ca_cert, client_cert, client_key, alpn)?;
// Connect
let connection = client.connect(addr, "localhost").await?;
// Open stream and create channel
let (send, recv) = connection.open_bi().await?;
let transport: QuicTransport<EchoChannel> = (send, recv).into();
let mut chan = EchoChannel::new(10, Box::new(transport));
// Call methods
chan.ping().await?;
let response = chan.echo("Hello".to_string()).await?;
}
TLS Certificates
Generate development certificates:
cd certs
./generate_certs.sh
This generates:
ca.pem/ca.key- Certificate Authorityserver.pem/server.key- Server certificateclient.pem/client.key- Client certificateclient.p12- PKCS12 bundle for browser import
For production, use certificates from a trusted CA or Let’s Encrypt.
Mutual TLS (mTLS)
JetStream QUIC supports mutual TLS authentication:
#![allow(unused)]
fn main() {
// Build a client certificate verifier from a CA cert
let mut root_store = rustls::RootCertStore::empty();
root_store.add(ca_cert).expect("Failed to add CA cert");
let client_verifier =
rustls::server::WebPkiClientVerifier::builder(Arc::new(root_store))
.allow_unauthenticated()
.build()
.expect("Failed to build client verifier");
let server = Server::new_with_mtls(
server_cert,
server_key,
client_verifier, // any Arc<dyn ClientCertVerifier>
addr,
router,
);
}
Accessing Peer Certificate Info
#![allow(unused)]
fn main() {
use jetstream_rpc::context::{Context, Peer};
// In your service implementation or handler
if let Some(Peer::Tls(tls_peer)) = ctx.peer() {
if let Some(leaf) = tls_peer.leaf() {
println!("Client CN: {:?}", leaf.common_name);
println!("Fingerprint: {}", leaf.fingerprint);
println!("DNS SANs: {:?}", leaf.dns_names);
}
}
}
Dependencies
Add to your Cargo.toml:
[dependencies]
jetstream = "13"
jetstream_quic = "13"
jetstream_macros = "13"
tokio = { version = "1", features = ["full"] }
For more details, see the jetstream_quic API documentation.
JetStream HTTP
JetStream HTTP provides HTTP/2 and HTTP/3 server support with a unified Axum router interface. It enables serving both protocols on the same port using TCP for HTTP/2 and UDP (QUIC) for HTTP/3.
Features
- HTTP/2 + HTTP/3: Serve both protocols simultaneously on the same port
- Axum Integration: Use familiar Axum routers and handlers
- Alt-Svc Header: Automatically advertise HTTP/3 availability to HTTP/2 clients
- Context Extractor: Access connection metadata (peer certificates, remote address) in handlers
- mTLS Support: Mutual TLS authentication for both protocols
Architecture
The crate provides these components:
H3Service: HTTP/3 protocol handler for use withjetstream_quicAltSvcLayer: Tower layer that addsAlt-Svcheader to advertise HTTP/3JetStreamContext: Axum extractor for accessing connection context
Example
Here’s a complete example serving HTTP/2 and HTTP/3:
use std::fs;
use std::net::SocketAddr;
use std::path::Path;
use std::sync::Arc;
use axum::Router;
use hyper_util::rt::{TokioExecutor, TokioIo};
use hyper_util::server::conn::auto::Builder as HttpBuilder;
use hyper_util::service::TowerToHyperService;
use jetstream::prelude::*;
use jetstream_http::{AltSvcLayer, H3Service};
use jetstream_macros::service;
use tokio::net::TcpListener;
use tokio_rustls::TlsAcceptor;
use tower_http::services::ServeDir;
use tracing::{error, info};
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
// r[impl jetstream.webtransport.http-example]
#[service]
pub trait EchoHttp {
async fn ping(&mut self, message: String) -> Result<String>;
async fn add(&mut self, a: i32, b: i32) -> Result<i32>;
}
#[derive(Clone)]
struct EchoHttpImpl;
impl EchoHttp for EchoHttpImpl {
async fn ping(&mut self, message: String) -> Result<String> {
Ok(message)
}
async fn add(&mut self, a: i32, b: i32) -> Result<i32> {
Ok(a + b)
}
}
pub static CA_PEM: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/certs/ca.pem");
pub static SERVER_PEM: &str =
concat!(env!("CARGO_MANIFEST_DIR"), "/certs/server.pem");
pub static SERVER_KEY: &str =
concat!(env!("CARGO_MANIFEST_DIR"), "/certs/server.key");
fn load_certs(path: &str) -> Vec<CertificateDer<'static>> {
let data = fs::read(Path::new(path)).expect("Failed to read cert");
rustls_pemfile::certs(&mut &*data)
.filter_map(|r| r.ok())
.collect()
}
fn load_key(path: &str) -> PrivateKeyDer<'static> {
let data = fs::read(Path::new(path)).expect("Failed to read key");
rustls_pemfile::private_key(&mut &*data)
.expect("Failed to parse key")
.expect("No key found")
}
pub static APP_DIST: &str =
concat!(env!("CARGO_MANIFEST_DIR"), "/examples/app/dist");
/// Run HTTP/2 server with TLS
async fn run_http2_server(
addr: SocketAddr,
router: Router,
tls_acceptor: TlsAcceptor,
) -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
let listener = TcpListener::bind(addr).await?;
loop {
let (stream, _remote_addr) = listener.accept().await?;
let tls_acceptor = tls_acceptor.clone();
let router = router.clone();
tokio::spawn(async move {
let tls_stream = match tls_acceptor.accept(stream).await {
Ok(s) => s,
Err(e) => {
error!("TLS accept error: {}", e);
return;
}
};
let io = TokioIo::new(tls_stream);
let svc = TowerToHyperService::new(router);
if let Err(e) = HttpBuilder::new(TokioExecutor::new())
.serve_connection(io, svc)
.await
{
error!("HTTP/2 connection error: {}", e);
}
});
}
}
/// Run HTTP/3 server with QUIC + WebTransport
async fn run_http3_server(
addr: SocketAddr,
router: Router,
server_cert: CertificateDer<'static>,
server_key: PrivateKeyDer<'static>,
ca_cert: Option<CertificateDer<'static>>,
) {
// Register the EchoHttp service as a WebTransport handler
let echo = echohttp_protocol::EchoHttpService {
inner: EchoHttpImpl,
};
let rpc_router = Arc::new(
jetstream_rpc::Router::new()
.with_handler(echohttp_protocol::PROTOCOL_NAME, echo),
);
let mut quic_router = jetstream_quic::QuicRouter::new();
let server = if let Some(ca) = ca_cert {
let mut root_store = rustls::RootCertStore::empty();
root_store.add(ca).expect("Failed to add CA cert");
let client_verifier =
rustls::server::WebPkiClientVerifier::builder(Arc::new(root_store))
.allow_unauthenticated()
.build()
.expect("Failed to build client verifier");
let h3_service = Arc::new(H3Service::new_with_cert_verifier(
router,
rpc_router,
client_verifier.clone(),
));
quic_router.register(h3_service);
jetstream_quic::Server::new_with_mtls(
vec![server_cert],
server_key,
client_verifier,
addr,
quic_router,
)
} else {
let h3_service = Arc::new(H3Service::new(router, rpc_router));
quic_router.register(h3_service);
jetstream_quic::Server::new_with_addr(
vec![server_cert],
server_key,
addr,
quic_router,
)
};
server.run().await;
}
#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
// Initialize tracing
tracing_subscriber::fmt::init();
// Install the ring crypto provider for rustls
rustls::crypto::ring::default_provider()
.install_default()
.ok();
// Check for --mtls flag
let mtls_enabled = std::env::args().any(|arg| arg == "--mtls");
let server_certs = load_certs(SERVER_PEM);
let server_key = load_key(SERVER_KEY);
let ca_cert = if mtls_enabled {
Some(load_certs(CA_PEM).pop().unwrap())
} else {
None
};
// Create shared Axum router serving React app build + Alt-Svc header
let router = Router::new()
.fallback_service(ServeDir::new(APP_DIST))
.layer(AltSvcLayer::new(4433));
// Setup TLS config for HTTP/2
let mut tls_config = rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(server_certs.clone(), server_key.clone_key())?;
tls_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
let tls_acceptor = TlsAcceptor::from(Arc::new(tls_config));
let addr: SocketAddr = "127.0.0.1:4433".parse()?;
info!("=== JetStream HTTP + WebTransport Server ===");
info!("Listening on https://{}", addr);
info!(" - HTTP/2 over TLS (TCP)");
info!(" - HTTP/3 + WebTransport over QUIC (UDP)");
info!(
" - WebTransport protocol: {}",
echohttp_protocol::PROTOCOL_VERSION
);
if mtls_enabled {
info!("mTLS enabled - client certificates required");
} else {
info!("No client auth (use --mtls to enable)");
}
// Run both servers concurrently on the same port (TCP for HTTP/2, UDP for HTTP/3)
tokio::select! {
result = run_http2_server(addr, router.clone(), tls_acceptor) => {
if let Err(e) = result {
error!("HTTP/2 server error: {}", e);
}
}
_ = run_http3_server(
addr,
router,
server_certs.into_iter().next().unwrap(),
server_key,
ca_cert,
) => {}
}
Ok(())
}
Quick Start
1. Create an Axum Router
#![allow(unused)]
fn main() {
use axum::{routing::get, Router};
use jetstream_http::{AltSvcLayer, JetStreamContext};
async fn handler(ctx: JetStreamContext) -> &'static str {
println!("Request from: {:?}", ctx.remote());
"Hello, World!"
}
let router = Router::new()
.route("/", get(handler))
.layer(AltSvcLayer::new(4433));
}
2. Set Up HTTP/2 Server (TCP + TLS)
#![allow(unused)]
fn main() {
use hyper_util::rt::{TokioExecutor, TokioIo};
use hyper_util::server::conn::auto::Builder as HttpBuilder;
use hyper_util::service::TowerToHyperService;
use tokio::net::TcpListener;
use tokio_rustls::TlsAcceptor;
let listener = TcpListener::bind("127.0.0.1:4433").await?;
let tls_acceptor = TlsAcceptor::from(Arc::new(tls_config));
loop {
let (stream, _) = listener.accept().await?;
let tls_stream = tls_acceptor.accept(stream).await?;
let io = TokioIo::new(tls_stream);
let svc = TowerToHyperService::new(router.clone());
HttpBuilder::new(TokioExecutor::new())
.serve_connection(io, svc)
.await?;
}
}
3. Set Up HTTP/3 Server (QUIC)
#![allow(unused)]
fn main() {
use jetstream_http::H3Service;
use jetstream_quic::{Router as QuicRouter, Server};
let h3_service = Arc::new(H3Service::new(router));
let mut quic_router = QuicRouter::new();
quic_router.register(h3_service);
let server = Server::new_with_addr(cert, key, addr, quic_router);
server.run().await;
}
Alt-Svc Layer
The AltSvcLayer adds the Alt-Svc header to HTTP/2 responses, telling browsers that HTTP/3 is available:
#![allow(unused)]
fn main() {
use jetstream_http::AltSvcLayer;
// Advertise HTTP/3 on port 4433 with 24-hour max-age
let layer = AltSvcLayer::new(4433);
// Adds: Alt-Svc: h3=":4433"; ma=86400
// Or with custom value
let layer = AltSvcLayer::with_value("h3=\":443\"; ma=3600, h3-29=\":443\"");
}
When browsers receive this header over HTTP/2, they will attempt to upgrade to HTTP/3 on subsequent requests.
Context Extractor
Use JetStreamContext to access connection metadata in your handlers:
#![allow(unused)]
fn main() {
use jetstream_http::JetStreamContext;
use jetstream_rpc::context::{Peer, RemoteAddr};
async fn handler(ctx: JetStreamContext) -> String {
// Get remote address
let addr = match ctx.remote() {
Some(RemoteAddr::IpAddr(ip)) => ip.to_string(),
_ => "unknown".to_string(),
};
// Get peer certificate info (for mTLS)
if let Some(Peer::Tls(tls_peer)) = ctx.peer() {
if let Some(leaf) = tls_peer.leaf() {
return format!(
"Hello {}! (CN: {:?})",
addr,
leaf.common_name
);
}
}
format!("Hello {}!", addr)
}
}
TLS Configuration
HTTP/2 TLS Config
#![allow(unused)]
fn main() {
let mut tls_config = rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(certs, key)?;
// Important: set ALPN for HTTP/2
tls_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
}
HTTP/3 (QUIC) TLS
QUIC/HTTP/3 TLS is handled automatically by jetstream_quic::Server.
Certificates
Generate development certificates:
cd certs
./generate_certs.sh
Files created:
server.pem/server.key- Server certificateca.pem- CA certificate (for client trust)client.pem/client.key- Client certificate (for mTLS)client.p12- PKCS12 bundle for browser importserver.crt- DER format for Chrome
Testing with Chrome
Chrome supports HTTP/3. To test:
# Start the server
cargo run --example http --features quic
# Launch Chrome with QUIC enabled
./examples/launch_chrome.sh
The launch_chrome.sh script:
- Extracts the server’s SPKI hash
- Launches Chrome with
--origin-to-force-quic-onflag - Provides instructions for importing certificates
Dependencies
Add to your Cargo.toml:
[dependencies]
jetstream_http = "0.1"
jetstream_quic = "13"
axum = "0.8"
hyper-util = { version = "0.1", features = ["full"] }
tokio-rustls = "0.26"
tower = "0.5"
For more details, see the jetstream_http API documentation.
TypeScript
JetStream provides TypeScript packages for wire format encoding and RPC communication, published to the GitHub Package Registry under @sevki.
Installation
# Configure npm to use GitHub Package Registry for @sevki scope
echo "@sevki:registry=https://npm.pkg.github.com" >> .npmrc
pnpm add @sevki/jetstream-wireformat @sevki/jetstream-rpc
WireFormat Codecs
The @sevki/jetstream-wireformat package provides binary codecs for all JetStream primitive and composite types. Every codec implements the WireFormat<T> interface:
interface WireFormat<T> {
byteSize(value: T): number;
encode(value: T, writer: BinaryWriter): void;
decode(reader: BinaryReader): T;
}
Primitives
import { BinaryReader, BinaryWriter, u32Codec, stringCodec, boolCodec } from '@sevki/jetstream-wireformat';
// Encode a u32
const writer = new BinaryWriter();
u32Codec.encode(42, writer);
const bytes = writer.toUint8Array();
// Decode it back
const reader = new BinaryReader(bytes);
const value = u32Codec.decode(reader); // 42
Available primitive codecs: u8Codec, u16Codec, u32Codec, u64Codec, i16Codec, i32Codec, i64Codec, f32Codec, f64Codec, boolCodec, stringCodec.
Composite Types
Build codecs for collections and optional types:
import { vecCodec, optionCodec, mapCodec, stringCodec, u32Codec } from '@sevki/jetstream-wireformat';
// Vec<String>
const tagsCodec = vecCodec(stringCodec);
// Option<u32>
const maybeIdCodec = optionCodec(u32Codec);
// HashMap<String, u32>
const scoresCodec = mapCodec(stringCodec, u32Codec);
Structs and Enums
Use the structCodec and enumCodec helpers, or generate codecs from Rust types using jetstream_codegen:
import { BinaryReader, BinaryWriter, u32Codec } from '@sevki/jetstream-wireformat';
import type { WireFormat } from '@sevki/jetstream-wireformat';
// A manually defined codec for a Point struct
interface Point {
x: number;
y: number;
}
const pointCodec: WireFormat<Point> = {
byteSize(value: Point): number {
return u32Codec.byteSize(value.x) + u32Codec.byteSize(value.y);
},
encode(value: Point, writer: BinaryWriter): void {
u32Codec.encode(value.x, writer);
u32Codec.encode(value.y, writer);
},
decode(reader: BinaryReader): Point {
const x = u32Codec.decode(reader);
const y = u32Codec.decode(reader);
return { x, y };
},
};
Code Generation
Instead of writing codecs by hand, use jetstream_codegen to generate TypeScript types and codecs from Rust source files:
cargo run -p jetstream_codegen -- \
--input src/types.rs \
--ts-out generated/
Given a Rust file:
#![allow(unused)]
fn main() {
#[derive(JetStreamWireFormat)]
pub struct Point {
pub x: u32,
pub y: u32,
}
#[derive(JetStreamWireFormat)]
pub enum Shape {
Circle(u32),
Rectangle { width: u32, height: u32 },
}
}
The codegen produces TypeScript interfaces and WireFormat<T> codec objects that are wire-compatible with the Rust implementations.
RPC
The @sevki/jetstream-rpc package provides the RPC runtime for multiplexed request/response communication.
Generated Code
The codegen generates RPC client and handler types from #[service] trait definitions:
#![allow(unused)]
fn main() {
// Rust service definition
#[service]
pub trait EchoHttp {
async fn ping(&mut self, message: String) -> Result<String>;
async fn add(&mut self, a: i32, b: i32) -> Result<i32>;
}
}
cargo run -p jetstream_codegen -- \
--input examples/http.rs \
--ts-out generated/
This generates:
- Request/response types:
TPing,RPing,TAdd,RAddwith codecs - Frame unions:
Tmessage,Rmessagediscriminated unions withFramerCodecimplementations - Framer wrappers:
TmessageFramer,RmessageFramerclasses implementing theFramerinterface rmessageDecode: A decoder function for use withMuxandWebTransportTransport- Protocol constants:
PROTOCOL_NAME(e.g.,'rs.jetstream.proto/echohttp') andPROTOCOL_VERSION(e.g.,'rs.jetstream.proto/echohttp/15.0.0+bfd7d20e') EchoHttpClient: A typed client class with async methods and version negotiationEchoHttpHandler: A handler interface for implementing server-side dispatchdispatchEchoHttp: A dispatch function that routesTmessageframes to handler methods
Version Negotiation
Before making RPC calls, clients must perform a Tversion/Rversion handshake to negotiate the protocol version and maximum message size. The generated client provides a static negotiate method:
import { EchoHttpClient, rmessageDecode, PROTOCOL_NAME } from './generated/echohttp_rpc.js';
// Open a WebTransport session and bidi stream
const session = new WebTransport(`https://api.example.com:4433/${PROTOCOL_NAME}`);
await session.ready;
const stream = await session.createBidirectionalStream();
// Negotiate version on the raw stream before creating the Mux
const negotiated = await EchoHttpClient.negotiate(stream.readable, stream.writable);
console.log(`Negotiated: ${negotiated.version}, msize: ${negotiated.msize}`);
You can also call negotiateVersion directly from @sevki/jetstream-rpc:
import { negotiateVersion } from '@sevki/jetstream-rpc';
import { PROTOCOL_VERSION } from './generated/echohttp_rpc.js';
const negotiated = await negotiateVersion(stream.readable, stream.writable, PROTOCOL_VERSION);
After negotiation, the stream is ready for Mux framing.
Client Usage
import { Mux } from '@sevki/jetstream-rpc';
import { EchoHttpClient, rmessageDecode, PROTOCOL_NAME } from './generated/echohttp_rpc.js';
// 1. Connect
const session = new WebTransport(`https://api.example.com:4433/${PROTOCOL_NAME}`);
await session.ready;
const stream = await session.createBidirectionalStream();
// 2. Negotiate version
await EchoHttpClient.negotiate(stream.readable, stream.writable);
// 3. Create transport and mux
const transport = new WebTransportTransport(stream, rmessageDecode);
const mux = new Mux(transport);
await mux.start();
// 4. Create client and make RPC calls
const client = new EchoHttpClient(mux);
const reply = await client.ping("hello"); // "hello"
const sum = await client.add(2, 3); // 5
// 5. Cleanup
await mux.close();
session.close();
Handler (Server-Side)
Implement the generated handler interface to serve RPCs:
import { EchoHttpHandler, dispatchEchoHttp } from './generated/echohttp_rpc.js';
const handler: EchoHttpHandler = {
async ping(ctx, message) {
return message; // echo back
},
async add(ctx, a, b) {
return a + b;
},
};
Frame Wire Format
RPC frames follow the format [size:u32 LE][type:u8][tag:u16 LE][payload] where size includes itself (minimum 7 bytes). The tag field enables multiplexing concurrent requests over a single connection.
Special message types:
TVERSION(100) /RVERSION(101) — version negotiationMESSAGE_ID_START(102) — first service method IDRJETSTREAMERROR(5) — error response frames
Mux
The Mux class handles tag allocation, request/response matching, and concurrent RPC dispatch:
import { Mux } from '@sevki/jetstream-rpc';
const mux = new Mux(transport);
await mux.start();
// Each rpc() call acquires a tag, sends a frame, waits for the matching response, and releases the tag
const response = await mux.rpc(request);
await mux.close();
Protocol Interface
Every generated service implements the Protocol interface:
interface Protocol<TReq extends Framer, TRes extends Framer> {
readonly VERSION: string;
readonly NAME: string;
}
NAMEis the protocol name used for routing (e.g., URI path, ALPN)VERSIONis the full version string used during Tversion/Rversion negotiation
React
@sevki/jetstream-react provides React hooks for bidirectional RPC over WebTransport. Downstream (browser) can call upstream services, and upstream can push RPCs downstream.
Installation
pnpm add @sevki/jetstream-react @sevki/jetstream-rpc @sevki/jetstream-wireformat
react (>=18) is a peer dependency.
Quick Start
import { JetStreamProvider, useJetStream, useJetStreamStatus, useRPC } from '@sevki/jetstream-react';
import { EchoHttpClient, rmessageDecode, PROTOCOL_VERSION, PROTOCOL_NAME } from './generated/echohttp_rpc.js';
function App() {
return (
<JetStreamProvider url={`https://api.example.com:4433/${PROTOCOL_NAME}`}>
<EchoDemo />
</JetStreamProvider>
);
}
function EchoDemo() {
const status = useJetStreamStatus();
const echo = useJetStream(EchoHttpClient, rmessageDecode, PROTOCOL_VERSION);
const { data, error, isLoading } = useRPC(
() => (echo ? echo.ping('hello') : Promise.resolve('')),
[echo],
);
return (
<div>
<p>Connection: {status}</p>
{isLoading && <p>Loading...</p>}
{error && <p>Error: {error.message}</p>}
{data && <p>Echo: {data}</p>}
</div>
);
}
Provider
JetStreamProvider manages the WebTransport session lifecycle. It establishes the session on mount and closes it on unmount.
<JetStreamProvider url="https://api.example.com:4433/rs.jetstream.proto/echohttp">
<App />
</JetStreamProvider>
Props:
url(required) — the upstream WebTransport URL, typicallyhttps://host:port/{PROTOCOL_NAME}children— React children
The provider exposes connection state and protocol version through context:
session— theWebTransportinstance (ornullbefore connected)state—'connecting'|'connected'|'disconnected'|'error'protocolVersion— the negotiated protocol version string (ornullbefore negotiation)
useJetStreamStatus
Read the current connection state:
function StatusIndicator() {
const status = useJetStreamStatus();
return <span className={status}>{status}</span>;
}
useJetStream
Creates a memoized RPC client for calling upstream services. Handles stream creation, version negotiation, and Mux setup automatically.
const client = useJetStream(ClientClass, responseDecode, protocolVersion);
Parameters:
ClientClass— the generated client constructor (e.g.,EchoHttpClient)responseDecode— the generated response decoder function (e.g.,rmessageDecode)protocolVersion— the protocol version string for Tversion negotiation (e.g.,PROTOCOL_VERSION)
Returns: the client instance (or null while connecting).
The hook:
- Opens a bidirectional stream on the WebTransport session
- Performs Tversion/Rversion negotiation on the raw stream
- Stores the negotiated version in the provider context
- Creates a
WebTransportTransportandMuxover the stream - Constructs and returns the client
The returned client is stable across re-renders as long as the session and constructor remain the same.
import { EchoHttpClient, rmessageDecode, PROTOCOL_VERSION } from './generated/echohttp_rpc.js';
function MyComponent() {
const echo = useJetStream(EchoHttpClient, rmessageDecode, PROTOCOL_VERSION);
if (!echo) return <p>Connecting...</p>;
return <button onClick={() => echo.ping('hi')}>Ping</button>;
}
useRPC
Reactive wrapper around a single RPC call. Re-executes when dependencies change.
const { data, error, isLoading, refetch } = useRPC(fn, deps);
Parameters:
fn— a function returning aPromise<T>(the RPC call)deps— dependency array (likeuseEffect); the call re-runs when dependencies change
Returns:
data: T | undefined— the resolved valueerror: Error | undefined— the rejection reasonisLoading: boolean— whether a request is in flightrefetch: () => void— manually re-execute the call
When dependencies change, the previous in-flight request is discarded (stale results are never applied).
function SearchResults() {
const [query, setQuery] = useState('');
const search = useJetStream(SearchClient, rmessageDecode, PROTOCOL_VERSION);
const { data, error, isLoading } = useRPC(
() => (search ? search.find(query) : Promise.resolve([])),
[search, query],
);
return (
<div>
<input value={query} onChange={e => setQuery(e.target.value)} />
{isLoading && <p>Loading...</p>}
{error && <p>Error: {error.message}</p>}
{data?.map(item => <div key={item.id}>{item.name}</div>)}
</div>
);
}
useHandler
Registers a handler for incoming upstream-initiated RPCs (push notifications, cache invalidation, etc.).
import { NotificationHandler } from './generated/notification_rpc.js';
function NotificationBadge() {
const { events, error } = useHandler(NotificationHandler, {
async notify(ctx, title, body) {
return { ack: true };
},
});
const unread = events.filter(e => e.method === 'notify').length;
return <span>{unread > 0 && `${unread} new`}</span>;
}
The handler is registered on mount and unregistered on unmount. Incoming bidirectional streams from upstream are dispatched to the matching handler.
Imperative Calls and Mutations
For imperative operations (mutations, fire-and-forget), call the client directly. The generated client methods return plain Promises:
function AddButton() {
const echo = useJetStream(EchoHttpClient, rmessageDecode, PROTOCOL_VERSION);
const [sum, setSum] = useState<number>();
return (
<button
disabled={!echo}
onClick={async () => {
if (echo) setSum(await echo.add(2, 3));
}}
>
Add 2 + 3 {sum !== undefined && `= ${sum}`}
</button>
);
}
For richer mutation state (loading, error, retries, cache invalidation), use TanStack Query:
import { useMutation } from '@tanstack/react-query';
function AddForm() {
const echo = useJetStream(EchoHttpClient, rmessageDecode, PROTOCOL_VERSION);
const { mutate, data, isPending } = useMutation({
mutationFn: ({ a, b }: { a: number; b: number }) => echo!.add(a, b),
});
return (
<div>
<button onClick={() => mutate({ a: 2, b: 3 })} disabled={isPending || !echo}>
Add 2 + 3
</button>
{data !== undefined && <p>Sum: {data}</p>}
</div>
);
}
Full Example
import { useState } from 'react';
import {
JetStreamProvider,
useJetStream,
useJetStreamStatus,
useRPC,
} from '@sevki/jetstream-react';
import {
EchoHttpClient,
rmessageDecode,
PROTOCOL_VERSION,
PROTOCOL_NAME,
} from './generated/echohttp_rpc.js';
const SERVER_URL = `https://127.0.0.1:4433/${PROTOCOL_NAME}`;
function EchoDemo() {
const status = useJetStreamStatus();
const echo = useJetStream(EchoHttpClient, rmessageDecode, PROTOCOL_VERSION);
const [message, setMessage] = useState('hello');
const [sum, setSum] = useState<number | null>(null);
const { data, error, isLoading } = useRPC(
() => (echo ? echo.ping(message) : Promise.resolve('')),
[echo, message],
);
return (
<div>
<h1>JetStream Echo Demo</h1>
<p>Connection: {status}</p>
<p>Protocol: {PROTOCOL_VERSION}</p>
<h2>Ping</h2>
<input
value={message}
onChange={e => setMessage(e.target.value)}
placeholder="Type a message..."
/>
{isLoading && <p>Loading...</p>}
{error && <p>Error: {error.message}</p>}
{data && <p>Echo: {data}</p>}
<h2>Add</h2>
<button
disabled={!echo}
onClick={async () => {
if (echo) setSum(await echo.add(2, 3));
}}
>
Add 2 + 3
</button>
{sum !== null && <p>Sum: {sum}</p>}
</div>
);
}
export default function App() {
return (
<JetStreamProvider url={SERVER_URL}>
<EchoDemo />
</JetStreamProvider>
);
}
Package Structure
| Package | Description |
|---|---|
@sevki/jetstream-wireformat | Binary codecs for primitives and composite types |
@sevki/jetstream-rpc | RPC runtime: Mux, TagPool, framing, version negotiation |
@sevki/jetstream-react | React hooks: JetStreamProvider, useJetStream, useRPC, useHandler |
Swift
JetStream provides Swift packages for wire format encoding and RPC communication via Swift Package Manager.
Installation
Add the JetStream package to your Package.swift:
dependencies: [
.package(url: "https://github.com/sevki/jetstream.git", from: "15.0.0"),
],
targets: [
.target(
name: "MyApp",
dependencies: [
.product(name: "JetStreamWireFormat", package: "jetstream"),
.product(name: "JetStreamRpc", package: "jetstream"),
]
),
]
WireFormat Protocol
The JetStreamWireFormat module defines the WireFormat protocol that all serializable types conform to:
public protocol WireFormat: Sized {
func byteSize() -> UInt32
func encode(writer: inout BinaryWriter) throws
static func decode(reader: inout BinaryReader) throws -> Self
}
Primitives
All Swift standard types (UInt8, UInt16, UInt32, UInt64, Int16, Int32, Int64, Float, Double, Bool, String) conform to WireFormat:
import JetStreamWireFormat
// Encode a UInt32
var writer = BinaryWriter()
try UInt32(42).encode(writer: &writer)
let bytes = writer.bytes
// Decode it back
var reader = BinaryReader(data: bytes)
let value = try UInt32.decode(reader: &reader) // 42
Collections and Optionals
Arrays, Dictionaries, Sets, and Optionals conform to WireFormat when their elements do:
// Array<String>
let tags: [String] = ["hello", "world"]
try tags.encode(writer: &writer)
// Optional<UInt32>
let maybeId: UInt32? = 42
try maybeId.encode(writer: &writer)
// Dictionary<String, UInt32>
let scores: [String: UInt32] = ["alice": 100, "bob": 200]
try scores.encode(writer: &writer)
Structs and Enums
Define custom types conforming to WireFormat, or generate them from Rust types using jetstream_codegen:
import JetStreamWireFormat
public struct Point: WireFormat {
public var x: UInt32
public var y: UInt32
public func byteSize() -> UInt32 {
return x.byteSize() + y.byteSize()
}
public func encode(writer: inout BinaryWriter) throws {
try x.encode(writer: &writer)
try y.encode(writer: &writer)
}
public static func decode(reader: inout BinaryReader) throws -> Point {
let x = try UInt32.decode(reader: &reader)
let y = try UInt32.decode(reader: &reader)
return Point(x: x, y: y)
}
}
Code Generation
Instead of writing conformances by hand, use jetstream_codegen to generate Swift types from Rust source files:
cargo run -p jetstream_codegen -- \
--input src/types.rs \
--swift-out generated/
Given a Rust file:
#![allow(unused)]
fn main() {
#[derive(JetStreamWireFormat)]
pub struct Point {
pub x: u32,
pub y: u32,
}
#[derive(JetStreamWireFormat)]
pub enum Shape {
Circle(u32),
Rectangle { width: u32, height: u32 },
}
}
The codegen produces Swift structs and enums conforming to WireFormat that are wire-compatible with the Rust implementations.
RPC
The JetStreamRpc module provides the RPC runtime for multiplexed request/response communication.
Generated Client
The codegen generates RPC client classes and handler protocols from #[service] trait definitions:
#![allow(unused)]
fn main() {
// Rust service definition
#[service]
pub trait Echo {
async fn ping(&mut self, message: String) -> Result<String>;
async fn add(&mut self, a: u32, b: u32) -> Result<u32>;
}
}
cargo run -p jetstream_codegen -- \
--input examples/echo.rs \
--swift-out generated/
This generates a typed EchoClient class and an EchoHandler protocol:
// Generated client usage
let client = EchoClient(mux: mux)
let reply = try await client.ping(message: "hello")
let sum = try await client.add(a: 2, b: 3)
// Generated handler protocol — implement this for your server
public protocol EchoHandler {
func ping(message: String) async throws -> String
func add(a: UInt32, b: UInt32) async throws -> UInt32
}
Framer Protocol
The Framer protocol handles message type dispatch for RPC envelopes:
public protocol Framer {
func messageType() -> UInt8
func byteSize() -> UInt32
func encode(writer: inout BinaryWriter) throws
static func decode(reader: inout BinaryReader, type: UInt8) throws -> Self
}
Generated Tmessage and Rmessage enums conform to Framer and dispatch encoding/decoding based on the message type byte.
Frame Wire Format
RPC frames follow the format [size:u32 LE][type:u8][tag:u16 LE][payload] where size includes itself (minimum 7 bytes). The tag field enables multiplexing concurrent requests over a single connection.
Mux
The Mux class handles tag allocation, request/response matching, and concurrent RPC dispatch:
let mux = Mux(transport: transport)
// Each rpc() call acquires a tag, sends a frame, waits for the matching response, and releases the tag
let response = try await mux.rpc(request)
🦀 Crates
For detailed API documentation of all crates in this repository, please see the rustdoc documentation.
The main crates include:
- jetstream - The main crate with the
jetstream::preludemodule - jetstream_9p - 9P protocol implementation
- jetstream_error - Error types and handling
- jetstream_iroh - Iroh peer-to-peer transport
- jetstream_libc - libc bindings
- jetstream_macros - Procedural macros
- jetstream_quic - QUIC/HTTP3 transport
- jetstream_radar - Radar functionality
- jetstream_rpc - RPC framework core
- jetstream_ufs - UFS (Unix File System) implementation
- jetstream_websocket - WebSocket transport
- jetstream_wireformat - Encoding and decoding logic
📦 jetstream
📦 jetstream_9p
📦 jetstream_error
📦 jetstream_http
📦 jetstream_iroh
📦 jetstream_libc
📦 jetstream_macros
📦 jetstream_quic
📦 jetstream_rpc
📦 jetstream_ufs
📦 jetstream_wireformat
Changelog
16.0.0 (2026-02-22)
⚠ BREAKING CHANGES
- jetstream: WebTransportHandler trait removed in favor of Router
Features
- jetstream: add router-based dispatch and WebTransport cert auth (5bb5ff8)
- jetstream: add router-based dispatch and WebTransport cert auth (b6ca4f9)
15.2.0 (2026-02-12)
Features
- jetstream_react package and webtransport implmentation (3309a0c)
- jetstream_wireformat for swift and typescript (e0f73fc)
- move webtransport to it’s own package (2ddbdf2)
15.1.0 (2026-02-10)
Features
15.0.0 (2026-02-07)
⚠ BREAKING CHANGES
- mtls now accepts a client verifier
Features
- mtls now accepts a client verifier (3b8fe8c)
14.1.0 (2026-02-06)
Features
14.0.0 (2026-01-29)
⚠ BREAKING CHANGES
- fully implenent jetstream_quic, introduced related traits
Features
- fully implenent jetstream_quic, introduced related traits (2b1e4a6)
- introduce jetstream_http, supports http1.1,http2 and h3 (10c203b)
13.0.0 (2026-01-25)
⚠ BREAKING CHANGES
- add multiplexing ability to client.
- make error types more isomorphic
- move quic and webocket to their own crates.
- use more futures
- fix proken publish
- splits up packages
- move modules to sensible parents
- protocol -> coding
- merge all the creates
Features
- add cleanup code serverside. add cloudflare docs to mdbook (91da99c)
- add context (4771c84)
- add i16,i32,i64,i128 types (ba741bd)
- add i16,i32,i64,i128 types (3f0751a)
- add iroh docs (6b7392f)
- add multiple tag pool backends (c85aafb)
- add multiplexing ability to client. (91a238e)
- add prost_wireformat macro (beeb947)
- Add support for bool in WireFormat (640c6ca)
- Add tests for jetstream modules (18462d9)
- add tracing feature (5cb2907)
- add websocket transport (3f4054c)
- add wireformat for SystemTime (169f74a)
- added async_trait support service macro (9a86185)
- autopub (73a0844)
- better error handling (faae10a)
- channels can now be split (ef646a5)
- distributes jetsream (d1477d5)
- dt features, remove rustdoc_to_md_book (0398159)
- enum support (c4552d8)
- fix proken publish (a7272c0)
- hide filesystem behind a feautre-flag (9aa880d)
- introduce iroh examples (86e001f)
- introduce jetstream cloudflare module. (6e8cc01)
- Ip primitives (1c263b5)
- jetstream cluster (80b5727)
- jetstream distributed features (52b5e89)
- jetstream_distributed: placement (8a93788)
- jetstream_libc (aba123b)
- jetstream_rpc supports wasm (e97e6ca)
- macros: service macro to remove boilerplate code (e0a9295)
- modularize components (7262a66)
- move quic and webocket to their own crates. (7d0ba9f)
- prost_wireformat: make derives optional (12dfacb)
- publish npm package to gh (9b332b9)
- release please (5fe2180)
- release please (7d7bedd)
- revamp service (#147) (6d96be8)
- rust-clippy code scanning (3dfb39f)
- rustdoc_to_mdbook (32deadf)
- start quic server with config (#159) (3433981)
- update mdbook (f213c71)
- use more futures (467b6f5)
- use sccache (#142) (89f96ab)
- use serde_bytes::ByteBuf instead of Bytes (a1101d9)
- use tag pool in mux (cd5dd5d)
- virtio support (ce13217)
- wasm-support (#260) (5cbff0d)
- wireformat: add u128 (c76f6c4)
Bug Fixes
- add a script to update the workspace versions (9fc4ca2)
- add comment about cf exec model (c979856)
- add support for libc in windows and mac (1fb3b5b)
- add toasty types fix skip (0fe936a)
- auto-release (964036c)
- auto-release feature (6505b0f)
- benchmarks (af695e2)
- benchmarks (94767dd)
- benchmarks and add iroh_benchmarks (56dbc12)
- benchmarks code (c0d0f59)
- bothced update (b3b7003)
- broken lock file (6148cf7)
- broken okid lockfile (63bae5e)
- broken release-please (089bb22)
- broken use statement for async_trait (cce4df6)
- bump deps (0dbd81b)
- bump okid (7ed2940)
- bump zero copy (#140) (4bb933f)
- cargo lock resolver (327d8bd)
- cargo publish workspace (8db9367)
- cargo toml version issues (3481015)
- Change git user configuration in workflow (c3d92c1)
- change service shape (194252d)
- ci (e7418e5)
- ci release-please (de391e5)
- ci workflows (4c12f04)
- ci-builds, drop 1.76 (5664e57)
- ci/cd (#157) (3f7ff1e)
- cleanup code and remove unused mermaid (c8975d5)
- collections can only be u16::MAX (6ada71a)
- correct release workflow step ordering (0c7bbcd)
- criterion benchmark test, framed implementation (25b1611)
- Delete CHANGELOG.md (abcc79d)
- delete release-plz (a7e3199)
- dependency order (9a0e1f9)
- dependency order and cycle (6a6c997)
- dependency sudoku (7df5f9b)
- disable running echo on windows (828d6be)
- disable running echo on windows (7f44956)
- docs (995da2f)
- elide lifetimes in ufs (b778ff9)
- extern async_trait and trait_variant and lazy_static (71e93e2)
- extern_more (1bd58d8)
- extract git config to separate step (679d1a7)
- failing docs (232ddee)
- failing serde tests (81db0de)
- filesystem under feature flag, rm newline (de4cf79)
- formatting (049e584)
- formatting (04aace4)
- fuzz target (a494e15)
- hide documentation for iroh (47b104d)
- ignore e2e tests (e066dde)
- iroh example needs -F iroh (5246fb2)
- keep reciver types as is in generated code (7d95671)
- lint errors (4f50d0b)
- lint errors and rm unnecessary compiler_error (27aed1e)
- macros: protocol macro fix to spread fn args (b261a28)
- make data io::Read (12a864e)
- make data io::Read (910c75a)
- make data io::Read (77b3680)
- make error types more isomorphic (aee2fa2)
- make qid eq,hash (522de0d)
- make TagPool use a channel (beb6209)
- make the proto mod same vis as trait (a43c0a2)
- make websocket pub (6096ff9)
- make websocket pub again (21acf4b)
- mdbook changelog (5f03b6b)
- mdbook-changelog version issue (7043b8e)
- more tests (9a5071f)
- move term-transcript to dev, gate s2n windows (5fa7f4c)
- npm scoping issue (0d06884)
- only build docs in release (02b02bc)
- option<T> support (2e224ca)
- pin toasty (03b6b18)
- publish script (e8685cc)
- readme (dc77722)
- recursive self call (c51d455)
- reenable sccache (757bb7e)
- reexport tokio_util from rpc (cf28c40)
- release again (3a6e65e)
- release order (0973046)
- release please (53a698b)
- release please (92eeee5)
- release please (3a2f4df)
- release workflow (4abeb24)
- release-please-config.json (885527f)
- remove distributed (3aba1f6)
- remove expects (8a58cf5)
- remove jj (02fefd9)
- remove prost (5a5ff9b)
- remove redundant io imports and use fully qualified paths (7de4b9e)
- remove redundant push from version update step (923d696)
- remove tracing from sink and stream (97ac0a5)
- remove unsafe code (a286d93)
- revert serde_bytes to bytes with serde (2e02460)
- rollback toasty (ae0790b)
- rust.yml workflow (9dc7bc0)
- rustdoc to mdbook (2098ad1)
- semaphor should use permit.forget() (340323c)
- service macro works correctly with client calls (eb1fd0f)
- simpler workflows for github actions (17185dd)
- snapshots for macros (fab686d)
- some tests (c65ded1)
- switch to release-plz (a07aaec)
- target os cfg attrs (16e5c84)
- trait_variant use (#155) (3cd5665)
- typo (901ffbd)
- unused git (3fe908e)
- update 9p to use trait_variant (96db410)
- Update CHANGELOG.md (6ab562f)
- Update client_tests.rs (4c50132)
- update doc links (80a5e1e)
- update docs (74e2867)
- Update lib.rs for extern crates (ffb6777)
- update release flow (#144) (36dd4af)
- Update release-please-config.json (e592cf3)
- Update release.yml (b058b38)
- Update release.yml (#122) (566fe1f)
- update snapshot tests (b9fde4c)
- update to v2 upload sarif (e38bacb)
- use cargo publish workspace (e28a0f7)
- version (ca861a7)
- version (822bf0e)
- versioning issues (4a3111c)
- versions in workspace (8b90b5a)
- warnings (62d8013)
- wasm-pack build error. rm rustdoc2mdbook (68076cc)
- wasm32 feature gates (755f9c1)
- wireformat: from_bytes doesn’t require a mutable buf (437c35c)
Code Refactoring
- merge all the creates (faa0a1a)
- move modules to sensible parents (4eba5fb)
- protocol -> coding (5f86bc7)
12.1.0 (2026-01-25)
Features
- publish npm package to gh (9b332b9)
Bug Fixes
12.0.0 (2026-01-24)
⚠ BREAKING CHANGES
- add multiplexing ability to client.
Features
- add multiple tag pool backends (c85aafb)
- add multiplexing ability to client. (91a238e)
- use tag pool in mux (cd5dd5d)
Bug Fixes
- benchmarks code (c0d0f59)
- delete release-plz (a7e3199)
- lint errors and rm unnecessary compiler_error (27aed1e)
- make TagPool use a channel (beb6209)
- semaphor should use permit.forget() (340323c)
11.1.0 (2026-01-21)
Features
- add prost_wireformat macro (beeb947)
- add wireformat for SystemTime (169f74a)
- prost_wireformat: make derives optional (12dfacb)
11.0.0 (2026-01-20)
⚠ BREAKING CHANGES
- make error types more isomorphic
- move quic and webocket to their own crates.
- use more futures
- fix proken publish
- splits up packages
- move modules to sensible parents
- protocol -> coding
- merge all the creates
Features
- add cleanup code serverside. add cloudflare docs to mdbook (91da99c)
- add context (4771c84)
- add i16,i32,i64,i128 types (ba741bd)
- add i16,i32,i64,i128 types (3f0751a)
- add iroh docs (6b7392f)
- Add support for bool in WireFormat (640c6ca)
- Add tests for jetstream modules (18462d9)
- add tracing feature (5cb2907)
- add websocket transport (3f4054c)
- added async_trait support service macro (9a86185)
- autopub (73a0844)
- better error handling (faae10a)
- channels can now be split (ef646a5)
- distributes jetsream (d1477d5)
- dt features, remove rustdoc_to_md_book (0398159)
- enum support (c4552d8)
- fix proken publish (a7272c0)
- hide filesystem behind a feautre-flag (9aa880d)
- introduce iroh examples (86e001f)
- introduce jetstream cloudflare module. (6e8cc01)
- Ip primitives (1c263b5)
- jetstream cluster (80b5727)
- jetstream distributed features (52b5e89)
- jetstream_distributed: placement (8a93788)
- jetstream_libc (aba123b)
- jetstream_rpc supports wasm (e97e6ca)
- macros: service macro to remove boilerplate code (e0a9295)
- modularize components (7262a66)
- move quic and webocket to their own crates. (7d0ba9f)
- release please (5fe2180)
- release please (7d7bedd)
- release please (044cceb)
- revamp service (#147) (6d96be8)
- rust-clippy code scanning (3dfb39f)
- rustdoc_to_mdbook (32deadf)
- start quic server with config (#159) (3433981)
- update mdbook (f213c71)
- use more futures (467b6f5)
- use sccache (#142) (89f96ab)
- use serde_bytes::ByteBuf instead of Bytes (a1101d9)
- virtio support (ce13217)
- wasm-support (#260) (5cbff0d)
- wireformat: add u128 (c76f6c4)
Bug Fixes
- add a script to update the workspace versions (9fc4ca2)
- add comment about cf exec model (c979856)
- add support for libc in windows and mac (1fb3b5b)
- add toasty types fix skip (0fe936a)
- auto-release (964036c)
- auto-release feature (6505b0f)
- benchmarks (af695e2)
- benchmarks (94767dd)
- benchmarks and add iroh_benchmarks (56dbc12)
- bothced update (b3b7003)
- broken lock file (6148cf7)
- broken okid lockfile (63bae5e)
- broken release-please (089bb22)
- broken use statement for async_trait (cce4df6)
- bump deps (0dbd81b)
- bump okid (7ed2940)
- bump zero copy (#140) (4bb933f)
- cargo lock resolver (327d8bd)
- cargo publish workspace (8db9367)
- cargo toml version issues (3481015)
- change service shape (194252d)
- ci (e7418e5)
- ci release-please (de391e5)
- ci workflows (4c12f04)
- ci-builds, drop 1.76 (5664e57)
- ci/cd (#157) (3f7ff1e)
- cleanup code and remove unused mermaid (c8975d5)
- collections can only be u16::MAX (6ada71a)
- criterion benchmark test, framed implementation (25b1611)
- Delete CHANGELOG.md (abcc79d)
- dependency order (9a0e1f9)
- dependency order and cycle (6a6c997)
- dependency sudoku (7df5f9b)
- disable running echo on windows (828d6be)
- disable running echo on windows (7f44956)
- docs (995da2f)
- elide lifetimes in ufs (b778ff9)
- extern async_trait and trait_variant and lazy_static (71e93e2)
- extern_more (1bd58d8)
- failing docs (232ddee)
- failing serde tests (81db0de)
- filesystem under feature flag, rm newline (de4cf79)
- formatting (049e584)
- formatting (04aace4)
- fuzz target (a494e15)
- hide documentation for iroh (47b104d)
- ignore e2e tests (e066dde)
- iroh example needs -F iroh (5246fb2)
- keep reciver types as is in generated code (7d95671)
- lint errors (4f50d0b)
- macros: protocol macro fix to spread fn args (b261a28)
- make data io::Read (12a864e)
- make data io::Read (910c75a)
- make data io::Read (77b3680)
- make error types more isomorphic (aee2fa2)
- make qid eq,hash (522de0d)
- make the proto mod same vis as trait (a43c0a2)
- make websocket pub (6096ff9)
- make websocket pub again (21acf4b)
- mdbook changelog (5f03b6b)
- mdbook-changelog version issue (7043b8e)
- more tests (9a5071f)
- move term-transcript to dev, gate s2n windows (5fa7f4c)
- only build docs in release (02b02bc)
- option<T> support (2e224ca)
- pin toasty (03b6b18)
- publish script (e8685cc)
- readme (dc77722)
- recursive self call (c51d455)
- reenable sccache (757bb7e)
- reexport tokio_util from rpc (cf28c40)
- release again (3a6e65e)
- release please (53a698b)
- release please (92eeee5)
- release please (3a2f4df)
- release workflow (4abeb24)
- release-please-config.json (885527f)
- remove distributed (3aba1f6)
- remove expects (8a58cf5)
- remove jj (02fefd9)
- remove prost (5a5ff9b)
- remove redundant io imports and use fully qualified paths (7de4b9e)
- remove tracing from sink and stream (97ac0a5)
- remove unsafe code (a286d93)
- revert serde_bytes to bytes with serde (2e02460)
- rollback toasty (ae0790b)
- rust.yml workflow (9dc7bc0)
- rustdoc to mdbook (2098ad1)
- service macro works correctly with client calls (eb1fd0f)
- simpler workflows for github actions (17185dd)
- snapshots for macros (fab686d)
- some tests (c65ded1)
- target os cfg attrs (16e5c84)
- trait_variant use (#155) (3cd5665)
- typo (901ffbd)
- unused git (3fe908e)
- update 9p to use trait_variant (96db410)
- Update CHANGELOG.md (6ab562f)
- Update client_tests.rs (4c50132)
- update doc links (80a5e1e)
- update docs (74e2867)
- Update lib.rs for extern crates (ffb6777)
- update release flow (#144) (36dd4af)
- Update release-please-config.json (e592cf3)
- Update release.yml (b058b38)
- Update release.yml (#122) (566fe1f)
- update to v2 upload sarif (e38bacb)
- use cargo publish workspace (e28a0f7)
- version (ca861a7)
- version (822bf0e)
- versions in workspace (8b90b5a)
- warnings (62d8013)
- wasm-pack build error. rm rustdoc2mdbook (68076cc)
- wasm32 feature gates (755f9c1)
- wireformat: from_bytes doesn’t require a mutable buf (437c35c)
Code Refactoring
- merge all the creates (faa0a1a)
- move modules to sensible parents (4eba5fb)
- protocol -> coding (5f86bc7)
10.1.0 (2026-01-20)
Features
Bug Fixes
- benchmarks (af695e2)
- cargo publish workspace (8db9367)
- cleanup code and remove unused mermaid (c8975d5)
- mdbook-changelog version issue (7043b8e)
- move term-transcript to dev, gate s2n windows (5fa7f4c)
- publish script (e8685cc)
10.0.0 (2025-11-19)
⚠ BREAKING CHANGES
- make error types more isomorphic
Bug Fixes
- make error types more isomorphic (aee2fa2)
9.5.0 (2025-10-30)
Features
- add cleanup code serverside. add cloudflare docs to mdbook (91da99c)
- introduce jetstream cloudflare module. (6e8cc01)
Bug Fixes
- add comment about cf exec model (c979856)
- remove tracing from sink and stream (97ac0a5)
- remove unsafe code (a286d93)
9.4.2 (2025-10-28)
Bug Fixes
- dependency sudoku (7df5f9b)
9.4.1 (2025-10-17)
Bug Fixes
9.4.0 (2025-10-15)
Features
- add context (4771c84)
Bug Fixes
- cargo toml version issues (3481015)
- collections can only be u16::MAX (6ada71a)
- recursive self call (c51d455)
- wasm32 feature gates (755f9c1)
9.3.0 (2025-10-14)
Features
- add tracing feature (5cb2907)
9.2.0 (2025-10-05)
Features
- dt features, remove rustdoc_to_md_book (0398159)
Bug Fixes
9.1.2 (2025-10-05)
Bug Fixes
- failing docs (232ddee)
9.1.1 (2025-10-05)
Bug Fixes
9.1.0 (2025-10-02)
Features
- introduce iroh examples (86e001f)
Bug Fixes
- add support for libc in windows and mac (1fb3b5b)
- disable running echo on windows (828d6be)
- disable running echo on windows (7f44956)
9.0.0 (2025-09-29)
⚠ BREAKING CHANGES
- move quic and webocket to their own crates.
Features
- move quic and webocket to their own crates. (7d0ba9f)
Bug Fixes
- simpler workflows for github actions (17185dd)
8.3.1 (2025-06-06)
Bug Fixes
- update docs (74e2867)
8.3.0 (2025-05-31)
Features
- jetstream_libc (aba123b)
8.2.1 (2025-05-29)
Bug Fixes
- rustdoc to mdbook (2098ad1)
8.2.0 (2025-05-29)
Features
- jetstream distributed features (52b5e89)
Bug Fixes
- Update lib.rs for extern crates (ffb6777)
8.1.5 (2025-04-05)
Bug Fixes
8.1.4 (2025-04-04)
Bug Fixes
8.1.3 (2025-04-04)
Bug Fixes
8.1.2 (2025-02-15)
Bug Fixes
- make websocket pub again (21acf4b)
8.1.1 (2025-02-15)
Bug Fixes
- make websocket pub (6096ff9)
8.1.0 (2025-02-15)
Features
8.0.10 (2025-02-14)
Bug Fixes
- broken lock file (6148cf7)
8.0.9 (2025-02-14)
Bug Fixes
- remove prost (5a5ff9b)
8.0.8 (2025-02-01)
Bug Fixes
- remove expects (8a58cf5)
8.0.7 (2025-01-30)
Bug Fixes
- release please (53a698b)
8.0.6 (2025-01-30)
Bug Fixes
8.0.5 (2025-01-30)
Bug Fixes
- use cargo publish workspace (e28a0f7)
8.0.4 (2025-01-30)
Bug Fixes
- dependency order and cycle (6a6c997)
- formatting (049e584)
- reexport tokio_util from rpc (cf28c40)
- rust.yml workflow (9dc7bc0)
8.0.3 (2025-01-30)
Bug Fixes
8.0.2 (2025-01-30)
Bug Fixes
- criterion benchmark test, framed implementation (25b1611)
- failing serde tests (81db0de)
- reenable sccache (757bb7e)
- service macro works correctly with client calls (eb1fd0f)
- snapshots for macros (fab686d)
8.0.1 (2025-01-28)
Bug Fixes
- remove distributed (3aba1f6)
8.0.0 (2025-01-27)
⚠ BREAKING CHANGES
- use more futures
Features
- use more futures (467b6f5)
Bug Fixes
- formatting (04aace4)
7.4.0 (2025-01-18)
Features
- jetstream_rpc supports wasm (e97e6ca)
7.3.0 (2025-01-17)
Features
7.2.1 (2024-12-10)
Bug Fixes
- keep reciver types as is in generated code (7d95671)
7.2.0 (2024-12-09)
Features
- Ip primitives (1c263b5)
7.1.2 (2024-12-06)
Bug Fixes
- elide lifetimes in ufs (b778ff9)
7.1.1 (2024-11-25)
Bug Fixes
- broken use statement for async_trait (cce4df6)
7.1.0 (2024-11-25)
Features
- added async_trait support service macro (9a86185)
7.0.4 (2024-11-23)
Bug Fixes
- fuzz target (a494e15)
7.0.3 (2024-11-21)
Bug Fixes
7.0.1 (2024-11-21)
Bug Fixes
7.0.0 (2024-11-21)
⚠ BREAKING CHANGES
- fix proken publish
Features
- fix proken publish (a7272c0)
6.6.2 (2024-11-21)
Bug Fixes
- bump okid (7ed2940)
6.6.1 (2024-11-20)
Bug Fixes
- broken okid lockfile (63bae5e)
6.6.0 (2024-11-20)
Features
Bug Fixes
6.5.0 (2024-11-20)
Features
Bug Fixes
6.4.2 (2024-11-18)
Bug Fixes
- bump deps (0dbd81b)
6.4.1 (2024-11-11)
Bug Fixes
- make the proto mod same vis as trait (a43c0a2)
6.4.0 (2024-11-10)
Features
Bug Fixes
6.3.4 (2024-11-10)
Bug Fixes
- make qid eq,hash (522de0d)
6.3.3 (2024-11-10)
Bug Fixes
- update 9p to use trait_variant (96db410)
6.3.2 (2024-11-10)
Bug Fixes
- option<T> support (2e224ca)
6.3.1 (2024-11-09)
Bug Fixes
- ci workflows (4c12f04)
6.3.0 (2024-11-09)
Features
Bug Fixes
- remove jj (02fefd9)
6.2.0 (2024-11-08)
Features
- add i16,i32,i64,i128 types (3f0751a)
6.1.0 (2024-11-08)
Features
6.0.2 (2024-11-08)
Bug Fixes
6.0.1 (2024-11-08)
Bug Fixes
6.0.0 (2024-11-07)
⚠ BREAKING CHANGES
- splits up packages
- move modules to sensible parents
- protocol -> coding
- merge all the creates
Features
- autopub (73a0844)
- hide filesystem behind a feautre-flag (9aa880d)
- macros: service macro to remove boilerplate code (e0a9295)
- modularize components (7262a66)
- release please (7d7bedd)
- release please (044cceb)
- revamp service (#147) (6d96be8)
- rust-clippy code scanning (3dfb39f)
- use sccache (#142) (89f96ab)
- use serde_bytes::ByteBuf instead of Bytes (a1101d9)
- virtio support (ce13217)
- wireformat: add u128 (c76f6c4)
Bug Fixes
- auto-release (964036c)
- auto-release feature (6505b0f)
- bothced update (b3b7003)
- broken release-please (089bb22)
- bump zero copy (#140) (4bb933f)
- ci release-please (de391e5)
- filesystem under feature flag, rm newline (de4cf79)
- ignore e2e tests (e066dde)
- lint errors (4f50d0b)
- macros: protocol macro fix to spread fn args (b261a28)
- make data io::Read (12a864e)
- make data io::Read (910c75a)
- make data io::Read (77b3680)
- readme (dc77722)
- release again (3a6e65e)
- release workflow (4abeb24)
- revert serde_bytes to bytes with serde (2e02460)
- unused git (3fe908e)
- Update client_tests.rs (4c50132)
- update release flow (#144) (36dd4af)
- Update release.yml (b058b38)
- Update release.yml (#122) (566fe1f)
- update to v2 upload sarif (e38bacb)
- version (822bf0e)
- warnings (62d8013)
- wireformat: from_bytes doesn’t require a mutable buf (437c35c)
Code Refactoring
- merge all the creates (faa0a1a)
- move modules to sensible parents (4eba5fb)
- protocol -> coding (5f86bc7)
5.4.2 (2024-11-07)
Bug Fixes
- auto-release (964036c)
5.4.2 (2024-11-07)
Bug Fixes
- auto-release (964036c)
5.4.1 (2024-11-07)
Bug Fixes
- readme (dc77722)
5.4.0 (2024-11-07)
Features
5.3.0 (2024-10-23)
Features
Bug Fixes
5.2.3 (2024-10-10)
Bug Fixes
5.2.2 (2024-10-07)
Bug Fixes
- revert serde_bytes to bytes with serde (2e02460)
5.2.1 (2024-10-06)
Bug Fixes
- wireformat: from_bytes doesn’t require a mutable buf (437c35c)
5.2.0 (2024-10-06)
Features
- use serde_bytes::ByteBuf instead of Bytes (a1101d9)
Bug Fixes
- lint errors (4f50d0b)
5.1.4 (2024-10-03)
Bug Fixes
- lint errors (4f50d0b)
5.1.3 (2024-10-03)
Bug Fixes
- Update release.yml (b058b38)
5.1.2 (2024-10-03)
Bug Fixes
5.1.1 (2024-10-03)
Bug Fixes
5.1.0 (2024-10-03)
Features
- wireformat: add u128 (c76f6c4)
Bug Fixes
- release workflow (4abeb24)
5.0.0 (2024-10-03)
⚠ BREAKING CHANGES
- splits up packages
Features
- modularize components (7262a66)
Bug Fixes
- version (822bf0e)
4.0.0 (2024-10-01)
⚠ BREAKING CHANGES
- move modules to sensible parents
Code Refactoring
- move modules to sensible parents (4eba5fb)
3.0.0 (2024-03-30)
⚠ BREAKING CHANGES
- protocol -> coding
- merge all the creates
Features
- autopub (73a0844)
- hide filesystem behind a feautre-flag (9aa880d)
- macros: service macro to remove boilerplate code (e0a9295)
- release please (7d7bedd)
- release please (044cceb)
- rust-clippy code scanning (3dfb39f)
- virtio support (ce13217)
Bug Fixes
- auto-release feature (6505b0f)
- bothced update (b3b7003)
- broken release-please (089bb22)
- ci release-please (de391e5)
- filesystem under feature flag, rm newline (de4cf79)
- ignore e2e tests (e066dde)
- macros: protocol macro fix to spread fn args (b261a28)
- make data io::Read (12a864e)
- make data io::Read (910c75a)
- make data io::Read (77b3680)
- Update client_tests.rs (4c50132)
- update to v2 upload sarif (e38bacb)
Code Refactoring
2.0.2 (2024-03-30)
Bug Fixes
2.0.1 (2024-03-29)
Bug Fixes
- macros: protocol macro fix to spread fn args (b261a28)
2.0.0 (2024-03-29)
⚠ BREAKING CHANGES
- protocol -> coding
Code Refactoring
- protocol -> coding (5f86bc7)
1.1.1 (2024-03-29)
Bug Fixes
- ignore e2e tests (e066dde)
1.1.0 (2024-03-29)
Features
- macros: service macro to remove boilerplate code (e0a9295)
1.0.0 (2024-03-25)
⚠ BREAKING CHANGES
- merge all the creates
Code Refactoring
- merge all the creates (faa0a1a)
0.6.0 (2024-03-21)
Features
- virtio support (ce13217)
0.5.1 (2024-03-15)
Bug Fixes
0.5.0 (2024-03-15)
Features
- rust-clippy code scanning (3dfb39f)
Bug Fixes
- update to v2 upload sarif (e38bacb)
0.4.0 (2024-03-14)
Features
0.3.2 (2024-03-14)
Bug Fixes
- auto-release feature (6505b0f)
0.3.1 (2024-03-14)
Bug Fixes
0.3.0 (2024-03-14)
Features
0.2.0 (2024-03-14)
Features
- release please (044cceb)
JetStream Cloudflare
Let’s say you’ve defined a service like so
{{#include ../components/jetstream_radar/src/lib.rs}}
The glue code for running it on Cloudflare Workers is
{{#include ../components/jetstream_radar/src/server.rs}}
The code for connecting to it is as follows:
{{#include ../components/jetstream_radar/src/bin/client.rs}}