doc: create readme

This commit is contained in:
2026-05-26 23:29:29 +02:00
parent 9bff0c6fdf
commit 4d05eebe00
+341
View File
@@ -0,0 +1,341 @@
# 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](https://github.com/tokio-rs/axum), [Tokio](https://tokio.rs/), and [Maud](https://maud.lambda.world/) 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.toml` configuration with live reload on file changes (via `notify` crate)
- **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
```rust
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
```rust
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
```rust
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)
```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)
```bash
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 initialization
- `examples/todo/queries.rs` — Query definitions with cache keys
- `examples/todo/todo_list.rs` — Component rendering logic
### PostgreSQL Replication Example
```bash
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.
```rust
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:
```rust
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:
1. Monitors `AppState::fanout_rx` for cache invalidation events
2. Looks up all connections subscribed to that key
3. Schedules a render for each connection
4. Publishes patches via SSE with debouncing (configurable grace period)
### Brotli Compression
Streams are compressed on-the-fly with configurable settings:
```rust
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)
```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:
```toml
[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 connections
- **`spawn_fanout()`** — Start the fanout task
- **`ingest_signals()`** — Update signals from action payloads
- **`load_and_watch_config()`** — Load config with hot-reload
- **`brotli_compression()`** — Middleware for compression
## Development
### Running Tests & Examples
```bash
# 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)
```bash
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-postgres` for 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 `connId` to HTTP headers instead of query params
- [ ] Extract common example logic into framework
## Contributing
This is an early-stage project. Contributions welcome! Key areas:
1. **Testing** — Expand test coverage for fanout & compression
2. **Documentation** — Add more examples (auth, streaming uploads, etc.)
3. **Performance** — Optimize render scheduling, compression tuning
4. **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!**