Skip to content

Instantly share code, notes, and snippets.

@delaneyj
Created January 22, 2026 03:24
Show Gist options
  • Select an option

  • Save delaneyj/b3f754fbd0d15762f14c5d218199e91c to your computer and use it in GitHub Desktop.

Select an option

Save delaneyj/b3f754fbd0d15762f14c5d218199e91c to your computer and use it in GitHub Desktop.
package examples
import (
"context"
"fmt"
"log"
"net/http"
"os"
"strconv"
"time"
"github.com/delaneyj/toolbelt"
"github.com/go-chi/chi/v5"
"github.com/goccy/go-json"
"github.com/gorilla/sessions"
"github.com/samber/lo"
"github.com/starfederation/datastar-dev/site/auth"
. "github.com/starfederation/datastar-dev/site/shared"
"github.com/starfederation/datastar-go/datastar"
"zombiezen.com/go/sqlite"
)
type todoViewMode int
const (
todoViewModeAll todoViewMode = iota
todoViewModePending
todoViewModeCompleted
todoViewModeLast
)
var todoViewModeStrings = []string{"All", "Pending", "Completed"}
type todoEntry struct {
Text string `json:"text"`
Completed bool `json:"completed"`
}
type todoMVC struct {
Todos []*todoEntry `json:"todos"`
EditingIdx int `json:"editingIdx"`
Mode todoViewMode `json:"mode"`
}
// Package-level event bus for todo updates
var todoEventBus = NewEventBusAsync[string]()
func setupTodoMVC(
examplesRouter chi.Router,
sessionStore *sessions.CookieStore,
) {
// Setup SQLite database
filename := "todos.sqlite"
os.RemoveAll(filename) // Remove old database file if it exists
ctx := context.Background()
db, err := toolbelt.NewDatabase(ctx, filename, []string{
`
CREATE TABLE IF NOT EXISTS todos (
session_id TEXT PRIMARY KEY,
data TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_todos_created_at ON todos(created_at);
CREATE INDEX IF NOT EXISTS idx_todos_updated_at ON todos(updated_at);
`,
})
if err != nil {
panic(fmt.Errorf("failed to open SQLite database: %w", err))
}
go func() {
ticker := time.NewTicker(24 * time.Hour)
defer ticker.Stop()
cleanupOldEntries := func() {
if err := db.WriteTX(ctx, func(tx *sqlite.Conn) error {
stmt := tx.Prep("DELETE FROM todos WHERE created_at < datetime('now', '-1 day')")
defer stmt.Reset()
_, err := stmt.Step()
return err
}); err != nil {
log.Printf("Error cleaning up old todo entries: %v", err)
}
}
cleanupOldEntries()
for range ticker.C {
cleanupOldEntries()
}
}()
saveMVC := func(ctx context.Context, sessionID string, mvc *todoMVC) error {
b, err := json.Marshal(mvc)
if err != nil {
return fmt.Errorf("failed to marshal mvc: %w", err)
}
// Save to SQLite
if err := db.WriteTX(ctx, func(tx *sqlite.Conn) error {
stmt := tx.Prep(`
INSERT INTO todos (session_id, data, updated_at)
VALUES ($session_id, $data, CURRENT_TIMESTAMP)
ON CONFLICT(session_id) DO UPDATE SET
data = excluded.data,
updated_at = CURRENT_TIMESTAMP
`)
defer stmt.Reset()
stmt.SetText("$session_id", sessionID)
stmt.SetText("$data", string(b))
_, err := stmt.Step()
return err
}); err != nil {
return fmt.Errorf("failed to save to database: %w", err)
}
// Emit update event
if err := todoEventBus.Emit(ctx, sessionID); err != nil {
log.Printf("Error emitting todo update event: %v", err)
}
return nil
}
resetMVC := func(mvc *todoMVC) {
mvc.Mode = todoViewModeAll
mvc.Todos = []*todoEntry{
{Text: "Learn any backend language", Completed: true},
{Text: "Learn Datastar", Completed: false},
{Text: "???", Completed: false},
{Text: "Profit", Completed: false},
}
mvc.EditingIdx = -1
}
mvcSession := func(w http.ResponseWriter, r *http.Request) (string, *todoMVC, error) {
session, err := sessionStore.Get(r, "connections")
if err != nil {
return "", nil, fmt.Errorf("failed to get session: %w", err)
}
id, ok := session.Values["id"].(string)
if !ok {
id = toolbelt.NextEncodedID()
session.Values["id"] = id
if err := session.Save(r, w); err != nil {
return "", nil, fmt.Errorf("failed to save session: %w", err)
}
}
mvc := &todoMVC{}
ctx := r.Context()
// Load from SQLite
found := false
if err := db.ReadTX(ctx, func(tx *sqlite.Conn) error {
stmt := tx.Prep("SELECT data FROM todos WHERE session_id = $session_id")
defer stmt.Reset()
stmt.SetText("$session_id", id)
hasRow, err := stmt.Step()
if err != nil {
return err
}
if hasRow {
data := stmt.GetText("data")
if err := json.Unmarshal([]byte(data), mvc); err != nil {
return fmt.Errorf("failed to unmarshal mvc: %w", err)
}
found = true
}
return nil
}); err != nil {
return "", nil, fmt.Errorf("failed to read from database: %w", err)
}
if !found {
resetMVC(mvc)
if err := saveMVC(ctx, id, mvc); err != nil {
return "", nil, fmt.Errorf("failed to save mvc: %w", err)
}
}
return id, mvc, nil
}
examplesRouter.Route("/todomvc", func(todoMVCRouter chi.Router) {
todoMVCRouter.Get("/", func(w http.ResponseWriter, r *http.Request) {
_, mvc, err := mvcSession(w, r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
u := auth.UserFromContext(r.Context())
RenderPage(todo(u, mvc), w, r)
})
todoMVCRouter.Get("/updates", func(w http.ResponseWriter, r *http.Request) {
sessionID, mvc, err := mvcSession(w, r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Subscribe to updates for this session
ctx := r.Context()
sse := datastar.NewSSE(w, r, datastar.WithCompression())
// Send initial data
c := todoMVCView(mvc)
if err := sse.PatchElementTempl(c); err != nil {
sse.ConsoleError(err)
return
}
// Subscribe to updates
cancel := todoEventBus.Subscribe(context.Background(), func(updatedSessionID string) error {
if updatedSessionID != sessionID {
return nil // Not our session
}
// Load updated data from database
if err := db.ReadTX(ctx, func(tx *sqlite.Conn) error {
stmt := tx.Prep("SELECT data FROM todos WHERE session_id = $session_id")
defer stmt.Reset()
stmt.SetText("$session_id", sessionID)
hasRow, err := stmt.Step()
if err != nil {
return err
}
if hasRow {
data := stmt.GetText("data")
if err := json.Unmarshal([]byte(data), mvc); err != nil {
return fmt.Errorf("failed to unmarshal mvc: %w", err)
}
}
return nil
}); err != nil {
sse.ConsoleError(fmt.Errorf("failed to read updated data: %w", err))
return err
}
// Send update
c := todoMVCView(mvc)
if err := sse.PatchElementTempl(c); err != nil {
sse.ConsoleError(err)
return err
}
return nil
})
defer cancel()
// Keep connection open
<-ctx.Done()
})
todoMVCRouter.Put("/reset", func(w http.ResponseWriter, r *http.Request) {
sessionID, mvc, err := mvcSession(w, r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
resetMVC(mvc)
if err := saveMVC(r.Context(), sessionID, mvc); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(204)
})
todoMVCRouter.Put("/cancel", func(w http.ResponseWriter, r *http.Request) {
sessionID, mvc, err := mvcSession(w, r)
if err != nil {
sse := datastar.NewSSE(w, r)
sse.ConsoleError(err)
return
}
mvc.EditingIdx = -1
if err := saveMVC(r.Context(), sessionID, mvc); err != nil {
sse := datastar.NewSSE(w, r)
sse.ConsoleError(err)
return
}
w.WriteHeader(204)
})
todoMVCRouter.Put("/mode/{mode}", func(w http.ResponseWriter, r *http.Request) {
sessionID, mvc, err := mvcSession(w, r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
modeStr := chi.URLParam(r, "mode")
modeRaw, err := strconv.Atoi(modeStr)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
mode := todoViewMode(modeRaw)
if mode < todoViewModeAll || mode > todoViewModeCompleted {
http.Error(w, "invalid mode", http.StatusBadRequest)
return
}
mvc.Mode = mode
if err := saveMVC(r.Context(), sessionID, mvc); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(204)
})
todoMVCRouter.Route("/{idx}", func(idxRouter chi.Router) {
routeIndex := func(w http.ResponseWriter, r *http.Request) (int, error) {
idx := chi.URLParam(r, "idx")
i, err := strconv.Atoi(idx)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return 0, err
}
return i, nil
}
idxRouter.Get("/", func(w http.ResponseWriter, r *http.Request) {
sessionID, mvc, err := mvcSession(w, r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
i, err := routeIndex(w, r)
if err != nil {
return
}
mvc.EditingIdx = i
saveMVC(r.Context(), sessionID, mvc)
w.WriteHeader(204)
})
idxRouter.Patch("/", func(w http.ResponseWriter, r *http.Request) {
type Signals struct {
Input string `json:"input"`
}
signals := &Signals{}
if err := datastar.ReadSignals(r, signals); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if signals.Input == "" {
return
}
sessionID, mvc, err := mvcSession(w, r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
i, err := routeIndex(w, r)
if err != nil {
return
}
if i >= 0 {
mvc.Todos[i].Text = signals.Input
} else {
mvc.Todos = append(mvc.Todos, &todoEntry{
Text: signals.Input,
Completed: false,
})
}
mvc.EditingIdx = -1
saveMVC(r.Context(), sessionID, mvc)
w.WriteHeader(204)
})
idxRouter.Delete("/", func(w http.ResponseWriter, r *http.Request) {
i, err := routeIndex(w, r)
if err != nil {
return
}
sessionID, mvc, err := mvcSession(w, r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if i >= 0 {
mvc.Todos = append(mvc.Todos[:i], mvc.Todos[i+1:]...)
} else {
mvc.Todos = lo.Filter(mvc.Todos, func(todo *todoEntry, i int) bool {
return !todo.Completed
})
}
saveMVC(r.Context(), sessionID, mvc)
w.WriteHeader(204)
})
idxRouter.Post("/toggle", func(w http.ResponseWriter, r *http.Request) {
sessionID, mvc, err := mvcSession(w, r)
if err != nil {
sse := datastar.NewSSE(w, r)
sse.ConsoleError(err)
return
}
i, err := routeIndex(w, r)
if err != nil {
sse := datastar.NewSSE(w, r)
sse.ConsoleError(err)
return
}
if i < 0 {
setCompletedTo := false
for _, todo := range mvc.Todos {
if !todo.Completed {
setCompletedTo = true
break
}
}
for _, todo := range mvc.Todos {
todo.Completed = setCompletedTo
}
} else {
todo := mvc.Todos[i]
todo.Completed = !todo.Completed
}
saveMVC(r.Context(), sessionID, mvc)
w.WriteHeader(204)
})
})
})
}
templ todoMVCView(mvc *todoMVC) {
{{
todoLen := len(mvc.Todos)
anyTodos := todoLen > 0
isCompleted := func(todo *todoEntry) bool { return todo.Completed }
anyCompleted := lo.SomeBy(mvc.Todos, isCompleted)
allCompleted := lo.EveryBy(mvc.Todos, isCompleted)
anyEditing := mvc.EditingIdx != -1
remaining := todoLen - lo.CountBy(mvc.Todos, isCompleted)
}}
<section
id="todomvc"
data-init="@get('/examples/todomvc/updates')"
>
<header id="todo-header">
if anyTodos {
<input
type="checkbox"
data-on:click__prevent="@post('/examples/todomvc/-1/toggle')"
data-init={ fmt.Sprintf("el.checked = %t", allCompleted) }
/>
}
<input
id="new-todo"
type="text"
placeholder="What needs to be done?"
if !anyEditing {
data-signals:input
data-bind:input
data-on:keydown="
evt.key === 'Enter' && $input.trim() && @patch('/examples/todomvc/-1') && ($input = '');
"
}
/>
</header>
if anyTodos {
<ul id="todo-list">
for i, todo := range mvc.Todos {
if i == mvc.EditingIdx {
@todoMVCItem(i) {
<input
id="edit-todo"
type="text"
data-signals:input={ fmt.Sprintf("'%s'", mvc.Todos[i].Text) }
data-bind:input
data-init="el.focus()"
data-on:blur="@put('/examples/todomvc/cancel')"
data-on:keydown={ fmt.Sprintf(`
if (evt.key === 'Escape') {
el.blur();
} else if (evt.key === 'Enter' && $input.trim()) {
@patch('/examples/todomvc/%d');
}
`, i ) }
/>
}
} else if (mvc.Mode == todoViewModeAll) ||
(mvc.Mode == todoViewModePending && !todo.Completed) ||
(mvc.Mode == todoViewModeCompleted && todo.Completed) {
@todoMVCItem(i) {
{{
id := fmt.Sprintf("todo-checkbox-%d", i)
}}
<input
id={ id }
type="checkbox"
data-init={ fmt.Sprintf("el.checked = %t", todo.Completed) }
data-on:click__prevent={ datastar.PostSSE(
"/examples/todomvc/%d/toggle",
i,
) }
/>
<label for={ id }>{ todo.Text }</label>
<button
class="error small"
data-on:click={ datastar.DeleteSSE("/examples/todomvc/%d", i) }
>
@Icon("pixelarticons:close")
</button>
}
}
}
</ul>
}
<div id="todo-actions">
if anyTodos {
<span>
<strong>{ remaining }</strong>
if remaining == 1 {
item
} else {
items
}
pending
</span>
for i := todoViewModeAll; i < todoViewModeLast; i++ {
<button
if i == mvc.Mode {
class="small info"
} else {
class="small"
}
data-on:click={ datastar.PutSSE("/examples/todomvc/mode/%d", i) }
>
{ todoViewModeStrings[i] }
</button>
}
<button
class="error small"
if anyCompleted {
data-on:click="@delete('/examples/todomvc/-1')"
} else {
aria-disabled="true"
}
>
@Icon("pixelarticons:trash")
Delete
</button>
}
<button
class="warning small"
data-on:click="@put('/examples/todomvc/reset')"
>
@Icon("pixelarticons:reload")
Reset
</button>
</div>
</section>
}
templ todoMVCItem(i int) {
<li
role="button"
tabindex="0"
data-on:dblclick={ fmt.Sprintf("evt.target === el && @get('/examples/todomvc/%d')", i) }
>
{ children... }
</li>
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment