fix up pg replication example

This commit is contained in:
2026-03-10 22:47:17 +01:00
parent a1e2effe96
commit ae9064ee76
9 changed files with 70 additions and 1509 deletions
+23 -24
View File
@@ -37,13 +37,12 @@ use axum::{
Router,
};
use maud::{html, Markup, DOCTYPE};
use serde::Deserialize;
use sqlx::FromRow;
use uuid::Uuid;
use livevue_rs::{
pg_replication::{pg_table_key, PgReplicationConfig, PgReplicationListener},
server::{ingest_signals, ActionParams, AppState, SharedState},
server::{ingest_signals, patch_signal, AppState, SharedState},
spawn_fanout, RenderContext, Store,
};
@@ -112,6 +111,7 @@ async fn render_page(cx: &mut RenderContext, rs: &RenderState) -> anyhow::Result
.await?;
let new_msg = cx.signal("newMsg", livevue_rs::global());
let _conn_id_signal = cx.signal("connId", livevue_rs::global());
Ok(html! {
div #app {
@@ -127,11 +127,10 @@ async fn render_page(cx: &mut RenderContext, rs: &RenderState) -> anyhow::Result
type="text"
placeholder="Type a message…"
data-bind=(new_msg.name)
data-on:keydown="{if (event.key === 'Enter') $post('/action/post_message')}"
data-on:keydown="{if (event.key === 'Enter') @post('/action/post_message')}"
;
button
data-on:click="$post('/action/post_message')"
data-attr=(format!("{{disabled: {}.trim() === ''}}", new_msg.val))
data-on:click="@post('/action/post_message')"
{ "Send" }
}
@@ -179,16 +178,14 @@ fn page_shell(conn_id: Uuid, signals_json: &str, inner: &Markup) -> String {
// DataStar SDK (loaded from CDN for the example)
script
type="module"
src="https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.0-beta.11/bundles/datastar.js"
src="https://cdn.jsdelivr.net/gh/starfederation/datastar@1.0.0-RC.8/bundles/datastar.js"
{}
}
body {
div
data-signals=(signals_json)
data-on:load=(format!("@get('/sse?conn={conn_id}')"))
{
(inner)
}
body
data-signals=(signals_json)
data-init=(format!("@get('/sse?conn={conn_id}')"))
{
(inner)
}
}
}
@@ -202,7 +199,7 @@ fn page_shell(conn_id: Uuid, signals_json: &str, inner: &Markup) -> String {
async fn index_handler(State(state): State<(SharedState, RenderState)>) -> impl IntoResponse {
let (app_state, rs) = &state;
let conn_id = Uuid::new_v4();
let signals_json = r#"{"newMsg":""}"#;
let signals_json = &format!(r#"{{"newMsg":"","connId":"{}"}}"#, conn_id);
app_state.connections.insert(conn_id, {
// We need a sender for the connection, but the SSE handler will replace
// it. Use a dummy channel that immediately drops.
@@ -214,7 +211,7 @@ async fn index_handler(State(state): State<(SharedState, RenderState)>) -> impl
.update_signals(&conn_id, signals_json.to_string());
let mut cx =
RenderContext::new(conn_id, livevue_rs::SignalStore::from_json(serde_json::json!({"newMsg": ""})), Default::default(), app_state.store.clone());
RenderContext::new(conn_id, livevue_rs::SignalStore::from_json(serde_json::json!({"newMsg": "", "connId": conn_id.to_string()})), Default::default(), app_state.store.clone());
let inner = render_page(&mut cx, rs).await.unwrap_or_else(|e| {
html! { p { "Render error: " (e) } }
@@ -232,18 +229,17 @@ async fn sse_adapter(
livevue_rs::sse_handler(State(state.0), query).await
}
#[derive(Deserialize)]
struct PostMessageBody {
#[serde(rename = "newMsg")]
new_msg: Option<String>,
}
async fn post_message_handler(
State(state): State<(SharedState, RenderState)>,
axum::extract::Query(params): axum::extract::Query<ActionParams>,
Json(body): Json<serde_json::Value>,
) -> impl IntoResponse {
let signals = ingest_signals(&state.0, params.conn, body);
let conn_id = body
.get("connId")
.and_then(|v| v.as_str())
.and_then(|s| s.parse().ok())
.unwrap_or_else(Uuid::new_v4);
let signals = ingest_signals(&state.0, conn_id, body);
let msg: String = signals
.get("newMsg")
.and_then(|v| v.as_str().map(|s| s.trim().to_string()))
@@ -262,6 +258,9 @@ async fn post_message_handler(
return StatusCode::INTERNAL_SERVER_ERROR;
}
// Update the stored signals to reset the input field
patch_signal(&state.0, &conn_id, "newMsg", serde_json::json!(""));
// Note: we do NOT call store.publish() here — the PgReplicationListener
// will detect the INSERT through the WAL stream and fire the invalidation
// automatically. This demonstrates the key value of the module: mutations
@@ -384,4 +383,4 @@ async fn main() -> anyhow::Result<()> {
axum::serve(listener_tcp, app).await?;
Ok(())
}
}