Compare commits

...
2 Commits
Author SHA1 Message Date
Markk116 c84d3b4bcf add license 2026-05-26 23:31:41 +02:00
Markk116 4d05eebe00 doc: create readme 2026-05-26 23:29:29 +02:00
2 changed files with 359 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
MIT License
Copyright (c) 2026 Markk116
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
+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!**