11 KiB
livevue-rs
A reactive server-side rendering framework for Rust that powers live-updating UIs with real-time data synchronization via Server-Sent Events (SSE). Built with Axum, Tokio, and Maud for efficient, async HTML rendering.
Status: Early development (v0.1.0) — actively under development with frequent improvements.
Key Features
- Server-Driven Reactivity: Stream UI updates directly from the server to clients via SSE
- Reactive Signals: Type-safe signal system with client-side synchronization
- Query Caching & Invalidation: Automatic cache key subscription during render, with smart fanout
- PostgreSQL Logical Replication (opt-in): Real-time cache invalidation via PostgreSQL's logical replication protocol
- Hot-Reloadable Config:
livevue.tomlconfiguration with live reload on file changes (vianotifycrate) - Streaming Brotli Compression: Configurable compression for SSE streams with custom window sizes
- SQLite & PostgreSQL Support: Query any SQL database seamlessly
- Per-Connection Task Handling: Debounced rerenders with reconnection grace periods
Architecture
┌─────────────────────────────────────────────────────┐
│ Client (Browser) │
│ • DataStar morphing │
│ • Signals (frontend state) │
│ • SSE listener │
└─────────────────────┬─────────────────────────────┘
│ SSE
│
┌─────────────────────▼─────────────────────────────┐
│ Axum Server (livevue-rs) │
├─────────────────────────────────────────────────┤
│ HTTP Routes: │
│ • GET /route?signals={json} → Initial HTML │
│ • POST /action → Signal updates │
│ • GET /sse?connId={uuid} → SSE stream │
├─────────────────────────────────────────────────┤
│ Internal Systems: │
│ • RenderContext: Tracks signal subscriptions │
│ • ConnectionManager: Per-connection state │
│ • Fanout Task: Publishes cache invalidations │
│ • Store: KV cache + DB pool │
└─────────────────────┬─────────────────────────────┘
│
┌─────────────┴──────────────┐
│ │
┌───▼───────┐ ┌──────▼─────┐
│ SQLite │ │ PostgreSQL │
│ DB │ │ (+ PgR) │
└───────────┘ └─────────────┘
Quick Start
1. Create a Render Function
use livevue_rs::RenderContext;
use std::sync::Arc;
let render_fn: livevue_rs::RenderFn = Arc::new(|mut cx: RenderContext| {
Box::pin(async move {
// Run queries subscribed automatically to cache keys
let todos: Vec<Todo> = cx.run(MyQuery::list_todos()).await?;
// Render HTML (using Maud)
let html = html! {
ul {
@for todo in &todos {
li { (todo.title) }
}
}
};
let keys = cx.take_subscription_keys();
Ok((html, keys))
})
});
2. Set Up Application State
use livevue_rs::{AppState, Store, load_and_watch_config, spawn_fanout};
let db = sqlx::SqlitePool::connect("sqlite:app.db").await?;
let store = Store::new(db);
let config = load_and_watch_config("livevue.toml")?;
let state = AppState::new(store.clone(), render_fn, config.clone());
// Start the fanout task for server-initiated rerenders
spawn_fanout(state.clone());
3. Create Routes
use livevue_rs::sse_handler;
use axum::{Router, routing::{get, post}, middleware};
let app = Router::new()
.route("/", get(/* initial render handler */))
.route("/sse", get(sse_handler))
.route("/action", post(/* action handler */))
.with_state(state)
.layer(middleware::from_fn_with_state(
state.clone(),
brotli_compression,
));
4. Configure (livevue.toml)
[server]
bind_addr = "0.0.0.0"
bind_port = 3000
[compression]
enabled = true
window_bits = 22 # Window size (20-24)
quality = 11 # Compression level (0-11)
Examples
Todo App (SQLite)
cargo run --example todo
# Open http://localhost:3000
Features: Add/complete/delete todos, real-time list updates via SSE.
Files:
examples/todo/main.rs— Server setup & database initializationexamples/todo/queries.rs— Query definitions with cache keysexamples/todo/todo_list.rs— Component rendering logic
PostgreSQL Replication Example
cargo run --example pg_replication --features pg_replication
Demonstrates logical replication for real-time cache invalidation directly from the database.
Core Concepts
Signals
Type-safe reactive values synchronized between server and client.
use livevue_rs::{Signal, SignalOpts};
#[derive(Signal)]
#[signal(opts = "SignalOpts { mangle: true, ..Default::default() }")]
struct MySignal {
count: i32,
name: String,
}
Signals are automatically mangled for client safety and synced via SSE.
Queries & Cache Keys
Queries register cache subscription keys during render:
pub struct MyQuery;
impl Query for MyQuery {
type Item = Todo;
async fn run(store: &Store) -> anyhow::Result<Vec<Self::Item>> {
sqlx::query_as::<_, Todo>("SELECT * FROM todos")
.fetch_all(&store.db)
.await
.map_err(Into::into)
}
fn cache_key() -> CacheKey {
CacheKey::Channel { key: "todos" }
}
}
When the cache key is invalidated, all subscribed connections are scheduled for rerender.
Fanout & Invalidation
The fanout task:
- Monitors
AppState::fanout_rxfor cache invalidation events - Looks up all connections subscribed to that key
- Schedules a render for each connection
- Publishes patches via SSE with debouncing (configurable grace period)
Brotli Compression
Streams are compressed on-the-fly with configurable settings:
use livevue_rs::brotli_compression;
let app = app.layer(middleware::from_fn_with_state(
state.clone(),
brotli_compression,
));
Window size (20-24 bits) and quality (0-11) are configurable in livevue.toml.
Configuration (livevue.toml)
[server]
bind_addr = "127.0.0.1" # Bind address
bind_port = 3000 # Bind port
[compression]
enabled = true # Enable brotli compression
window_bits = 22 # Window size (20-24, default 22)
quality = 11 # Compression level (0-11, default 11)
[connection]
rerender_debounce_ms = 100 # Debounce duration for rerenders
reconnect_grace_period_ms = 5000 # Grace period for reconnection
Hot-reloaded on file changes; trigger with SIGHUP to force reload.
Features
Default
- Basic server-side rendering with SQLite support
- Signal management and caching
- Brotli compression
pg_replication
Enable PostgreSQL logical replication for real-time cache invalidation:
[dependencies]
livevue-rs = { features = ["pg_replication"] }
Use cx.run_pg(pool, query) in render functions to execute PgQuery objects with automatic replication-based invalidation.
API Overview
Key Exports
| Module | Purpose |
|---|---|
signal |
Signal trait & macros for reactive values |
query |
Query trait & CacheKey types |
store |
Store (DB pool + KV cache) & KVStore trait |
context |
RenderContext for render functions |
connection |
Connection manager & subscription registry |
server |
HTTP handlers, fanout, config |
config |
Config loading, hot-reload |
brotli_layer |
Compression middleware |
pg_replication |
PostgreSQL replication (feature-gated) |
pg_query |
PostgreSQL query macros (feature-gated) |
Main Functions
sse_handler()— Axum handler for SSE connectionsspawn_fanout()— Start the fanout taskingest_signals()— Update signals from action payloadsload_and_watch_config()— Load config with hot-reloadbrotli_compression()— Middleware for compression
Development
Running Tests & Examples
# Run the todo example
cargo run --example todo
# Run with PostgreSQL replication
cargo run --example pg_replication --features pg_replication
# Build release
cargo build --release
Development Environment (Nix)
nix-shell shell.nix
cargo build
Dependencies
- Runtime: Axum, Tokio, Maud, SQLx, Serde, Dashmap, Brotli, Notify
- Async: Tokio, Tokio-stream, Futures
- DB: SQLx (SQLite + PostgreSQL), optional
tokio-postgresfor replication - Compression: Brotli with configurable streaming
- Config: TOML parsing with hot-reload via
notify
Known Limitations & TODO
See todo.md for pending improvements:
- Refactor query contract for better pg_replication + SQLite differentiation
- Clean up SSE module (currently inlined in
server.rs) - Fix pg_replication example to defer signal delivery over SSE
- Implement cache-backed query examples
- Move
connIdto HTTP headers instead of query params - Extract common example logic into framework
Contributing
This is an early-stage project. Contributions welcome! Key areas:
- Testing — Expand test coverage for fanout & compression
- Documentation — Add more examples (auth, streaming uploads, etc.)
- Performance — Optimize render scheduling, compression tuning
- PostgreSQL — Refine replication reliability
License
Check the repository for license details.
Similar Projects
- Phoenix LiveView (Elixir) — Inspirational real-time rendering approach
- Leptos (Rust) — Full-stack reactive framework
- Dioxus (Rust) — Component-based with SSR support
- HTMX (JavaScript) — Client-side hypermedia enhancement
Start building live-updating Rust UIs with livevue-rs!