Refactor: untangle framework library from todo example
The codebase is now split into two clear layers: Framework library (src/): - lib.rs — public API surface, re-exports all framework types - signal.rs — SignalOpts, Signal, SignalStore, mangle/global/client_only helpers - query.rs — Query trait, CacheKey, query_one!/query_all! macros (macro paths updated from $crate::framework::* to $crate::*) - context.rs — RenderContext, ComponentTree, ComponentNode - connection.rs — ConnectionManager, SubscriptionRegistry, ConnectionState - sse.rs — format_patch_elements, format_patch_signals - server.rs — AppState, SharedState, sse_handler (pure framework plumbing) Todo example (examples/todo/): - main.rs — SQLite setup, seeding, server bootstrap - app.rs — router, document_shell, resolve_and_render, render_page, action_then_render, and all HTTP handlers - queries.rs — Todo model, ListTodos/GetTodo queries, mutation helpers - todo_list.rs — TodoList page component - about.rs — About page component Run the example independently with: cargo run --example todo https://claude.ai/code/session_01JThiQbp3Bn9J1VhBpRuz4u
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
use livevue_rs::CacheKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Models
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// A single todo item. Maps to the `todos` SQLite table.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
|
||||
pub struct Todo {
|
||||
pub id: i64,
|
||||
pub title: String,
|
||||
pub completed: bool,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Read queries (go through cx.run / Query trait)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Fetch all todos, ordered by id.
|
||||
pub struct ListTodos;
|
||||
|
||||
livevue_rs::query_all!(ListTodos {
|
||||
sql: "SELECT id, title, completed FROM todos ORDER BY id",
|
||||
params: [],
|
||||
output: Todo,
|
||||
cache: |_s: &ListTodos| CacheKey::Table { table: "todos" },
|
||||
});
|
||||
|
||||
/// Fetch a single todo by id.
|
||||
pub struct GetTodo {
|
||||
pub id: i64,
|
||||
}
|
||||
|
||||
livevue_rs::query_one!(GetTodo {
|
||||
sql: "SELECT id, title, completed FROM todos WHERE id = ?",
|
||||
params: [|s: &GetTodo| s.id],
|
||||
output: Todo,
|
||||
cache: |s: &GetTodo| CacheKey::PrimaryKey { table: "todos", id: s.id },
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Write operations (plain sqlx, not through Query trait)
|
||||
// ---------------------------------------------------------------------------
|
||||
//
|
||||
// Mutations don't go through cx.run() because they're not reads and don't
|
||||
// produce subscription keys. They're called directly by action handlers,
|
||||
// which then trigger a full rerender.
|
||||
|
||||
/// Insert a new todo. Returns the new row's id.
|
||||
pub async fn create_todo(db: &sqlx::SqlitePool, title: &str) -> anyhow::Result<i64> {
|
||||
let result = sqlx::query("INSERT INTO todos (title, completed) VALUES (?, false)")
|
||||
.bind(title)
|
||||
.execute(db)
|
||||
.await?;
|
||||
Ok(result.last_insert_rowid())
|
||||
}
|
||||
|
||||
/// Toggle a todo's completed status.
|
||||
pub async fn toggle_todo(db: &sqlx::SqlitePool, id: i64) -> anyhow::Result<()> {
|
||||
sqlx::query("UPDATE todos SET completed = NOT completed WHERE id = ?")
|
||||
.bind(id)
|
||||
.execute(db)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete a todo by id.
|
||||
pub async fn delete_todo(db: &sqlx::SqlitePool, id: i64) -> anyhow::Result<()> {
|
||||
sqlx::query("DELETE FROM todos WHERE id = ?")
|
||||
.bind(id)
|
||||
.execute(db)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user