112 lines
4.1 KiB
Rust
112 lines
4.1 KiB
Rust
mod app;
|
|
mod queries;
|
|
mod todo_list;
|
|
mod about;
|
|
|
|
use livevue_rs::{AppState, CacheKey, RenderContext, Store, spawn_fanout};
|
|
use crate::app::{app_wrapper, resolve_and_render};
|
|
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
|
|
#[tokio::main]
|
|
async fn main() -> anyhow::Result<()> {
|
|
// -----------------------------------------------------------------------
|
|
// Database setup
|
|
// -----------------------------------------------------------------------
|
|
|
|
let db = sqlx::SqlitePool::connect("sqlite:todo.db?mode=rwc").await?;
|
|
|
|
sqlx::query(
|
|
"CREATE TABLE IF NOT EXISTS todos (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
title TEXT NOT NULL,
|
|
completed BOOLEAN NOT NULL DEFAULT FALSE
|
|
)",
|
|
)
|
|
.execute(&db)
|
|
.await?;
|
|
|
|
let count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM todos")
|
|
.fetch_one(&db)
|
|
.await?;
|
|
if count.0 == 0 {
|
|
for title in &["Learn Rust", "Build LiveVue.rs", "Ship v0"] {
|
|
sqlx::query("INSERT INTO todos (title, completed) VALUES (?, false)")
|
|
.bind(title)
|
|
.execute(&db)
|
|
.await?;
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Store
|
|
// -----------------------------------------------------------------------
|
|
|
|
let store = Store::new(db);
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Render function
|
|
// -----------------------------------------------------------------------
|
|
|
|
// The render_fn is the app's half of the framework contract. It receives
|
|
// a RenderContext (with signals, tree, store access) and returns the
|
|
// rendered inner HTML plus the accumulated subscription keys.
|
|
//
|
|
// The fanout task calls this for every server-initiated rerender.
|
|
let render_fn: livevue_rs::RenderFn = Arc::new(|mut cx: RenderContext| {
|
|
Box::pin(async move {
|
|
println!("Render Called");
|
|
let signals_json = cx.signals.to_json_string();
|
|
let conn_id = cx.connection_id;
|
|
let html = resolve_and_render(&mut cx).await?;
|
|
let keys = cx.take_subscription_keys();
|
|
// Wrap in app_wrapper so DataStar morphs the full #app element.
|
|
// Signals are re-emitted from the stored json so the client stays
|
|
// in sync even on server-initiated rerenders.
|
|
let wrapped = app_wrapper(conn_id, &signals_json, &html);
|
|
Ok((wrapped, keys))
|
|
})
|
|
});
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Application state
|
|
// -----------------------------------------------------------------------
|
|
|
|
let state = AppState::new(store.clone(), render_fn);
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Fanout task
|
|
// -----------------------------------------------------------------------
|
|
|
|
spawn_fanout(state.clone());
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Clock ticker
|
|
//
|
|
// Publishes CacheKey::Channel { key: "clock" } every second. Any
|
|
// connection whose last render called cx.kv_run("clock") will be
|
|
// scheduled for a rerender by the fanout task.
|
|
// -----------------------------------------------------------------------
|
|
|
|
let clock_store = store.clone();
|
|
tokio::spawn(async move {
|
|
let mut interval = tokio::time::interval(Duration::from_secs(1));
|
|
loop {
|
|
interval.tick().await;
|
|
let now = chrono::Utc::now().format("%H:%M:%S UTC").to_string();
|
|
// set() writes the value and publishes the invalidation event.
|
|
clock_store.kv.set("clock", serde_json::json!(now));
|
|
}
|
|
});
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Server
|
|
// -----------------------------------------------------------------------
|
|
|
|
let app = app::router(state);
|
|
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
|
|
println!("Todo example listening on http://localhost:3000");
|
|
axum::serve(listener, app).await?;
|
|
|
|
Ok(())
|
|
} |