Add streaming brotli compression and hot-reloadable config (livevue.toml)

Key changes:

- **src/config.rs** – `LiveVueConfig` (server host/port, brotli quality +
  window size), loaded from `livevue.toml`. A notify-based file watcher
  reloads the config on any file change; a SIGHUP handler does the same on
  demand. Falls back to compiled-in defaults when the file is absent.

- **src/brotli_layer.rs** – `BrotliBody`, a custom `http_body::Body` wrapper
  that streams brotli-compressed data through a single persistent
  `brotli::CompressorWriter`. The encoder survives across SSE event flushes,
  so its sliding window accumulates context from all prior events —
  progressively better compression as the stream grows. Both quality (0–11)
  and window size (lgwin 10–24, i.e. 1 KB – 16 MB) are taken from config.
  `brotli_compression` is an axum `from_fn_with_state` middleware that
  activates only when the client sends `Accept-Encoding: br`.

- **src/server.rs** – `AppState` gains a `config: SharedConfig` field.
  `AppState::new` takes the config; `AppState::new_default` is a zero-config
  convenience constructor.

- **livevue.toml** – documented example config with inline comments
  explaining each field and the brotli window-size tradeoff table.

- **examples/todo** – loads config via `load_and_watch_config`, derives the
  bind address from `config.server`, and applies `brotli_compression` as a
  router layer.

https://claude.ai/code/session_01UnQQkkwts64FPUsSzFfdQb
This commit is contained in:
Claude
2026-03-12 07:06:10 +00:00
parent a09ac32bef
commit 6d91fd9c36
9 changed files with 886 additions and 23 deletions
+8 -3
View File
@@ -1,9 +1,10 @@
use livevue_rs::{
ComponentTree, RenderContext, SharedState, SignalStore,
ingest_signals, sse_handler,
ComponentTree, RenderContext, SharedConfig, SharedState, SignalStore,
brotli_compression, ingest_signals, sse_handler,
};
use axum::extract::{Query as AxumQuery, State};
use axum::middleware;
use axum::response::{Html, IntoResponse};
use axum::routing::{get, post};
use axum::{Json, Router};
@@ -14,7 +15,7 @@ use uuid::Uuid;
// Router
// ---------------------------------------------------------------------------
pub fn router(state: SharedState) -> Router {
pub fn router(state: SharedState, config: SharedConfig) -> Router {
Router::new()
.route("/", get(initial_page_load))
.route("/about", get(initial_about_load))
@@ -22,6 +23,10 @@ pub fn router(state: SharedState) -> Router {
.route("/action/add_todo", post(action_add_todo))
.route("/action/toggle_todo", post(action_toggle_todo))
.route("/action/delete_todo", post(action_delete_todo))
.layer(middleware::from_fn_with_state(
config,
brotli_compression,
))
.with_state(state)
}