diff --git a/.gitignore b/.gitignore index af68f4b..3b94aca 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,5 @@ go.work static.go rawdata + +mva diff --git a/mvac/Editor _ Mermaid Chart-2025-06-02-073938.svg b/mvac/Editor _ Mermaid Chart-2025-06-02-073938.svg new file mode 100644 index 0000000..04ee90b --- /dev/null +++ b/mvac/Editor _ Mermaid Chart-2025-06-02-073938.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/mvac/Makefile b/mvac/Makefile new file mode 100644 index 0000000..382134c --- /dev/null +++ b/mvac/Makefile @@ -0,0 +1,5 @@ +run: + go run . + +build: + go build -tags prod . \ No newline at end of file diff --git a/mvac/basic/main.go b/mvac/basic/main.go new file mode 100644 index 0000000..1f8dfa1 --- /dev/null +++ b/mvac/basic/main.go @@ -0,0 +1,73 @@ +package main + +import ( + "crypto/rand" + "encoding/hex" + "fmt" + "log/slog" + "net/http" + "time" + + "github.com/starfederation/datastar/sdk/go/datastar" +) + +const ( + cdn = "https://cdn.jsdelivr.net/gh/starfederation/datastar@develop/bundles/datastar.js" + port = 9001 +) + +func main() { + mux := http.NewServeMux() + + style := "display:flex;flex-direction:column;background-color:oklch(25.3267% 0.015896 252.417568);height:100vh;justify-content:center;align-items:center;font-family:ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';" + + page := []byte(fmt.Sprintf(` + + + +
+ + + + + + + + + + `, cdn, style, datastar.GetSSE("/stream"))) + + mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) { + w.Write(page) + }) + + mux.HandleFunc("GET /stream", func(w http.ResponseWriter, r *http.Request) { + ticker := time.NewTicker(100 * time.Millisecond) + defer ticker.Stop() + + sse := datastar.NewSSE(w, r) + for { + select { + case <-r.Context().Done(): + slog.Debug("Client connection closed") + return + case <-ticker.C: + bytes := make([]byte, 3) + + if _, err := rand.Read(bytes); err != nil { + slog.Error("Error generating random bytes: ", slog.String("error", err.Error())) + return + } + hexString := hex.EncodeToString(bytes) + frag := fmt.Sprintf(`%s`, hexString, hexString, hexString) + + sse.MergeFragments(frag) + } + } + }) + + slog.Info(fmt.Sprintf("Server starting at 0.0.0.0:%d", port)) + if err := http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", port), mux); err != nil { + slog.Error("Error starting server:", slog.String("error", err.Error())) + } +} diff --git a/mvac/datastar.md b/mvac/datastar.md new file mode 100644 index 0000000..9442408 --- /dev/null +++ b/mvac/datastar.md @@ -0,0 +1,133 @@ +# Understanding Datastar: Backend and Frontend Synergy + +You've got it! Understanding how the Go `datastar` package on the server and `datastar.js` in the browser collaborate is key to grasping the "whole picture." Let's break down this synergistic relationship. + +## The Big Idea: Server-Driven Interactivity with Client-Side Reactivity + +Datastar enables you to build dynamic web UIs where the server often dictates changes, sending HTML fragments or instructions over Server-Sent Events (SSE). The client-side datastar.js intelligently applies these updates, manages a local reactive state (called "signals"), and provides declarative ways to bind this state and behavior to your HTML. + +## Step-by-Step Flow + +Here's a step-by-step flow and explanation of how they work together: + +### 1. Initial Page Load & Client-Side Setup (`datastar.js`) + +* **HTML Served**: Your Go application serves an initial HTML page. +* **`datastar.js` Execution**: The browser loads and executes `datastar.js`. +* **DOM Scan & Initialization** (`rt`, `Ie`, `dr` in `datastar.js`): + * `datastar.js` scans the DOM for special attributes (e.g., `data-datastar-show`, `data-datastar-on-click`, `data-datastar-bind`, `data-datastar-signals`). The prefix (`datastar-`) can be aliased using `Kt` (e.g., to just `data-`). + * For each recognized attribute, it initializes the corresponding client-side plugin: + * `data-signals`: Populates the client-side reactive "signal" store with initial values. Signals are like JavaScript variables, but when they change, parts of the UI that depend on them can automatically update. + * `data-computed`: Defines signals whose values are calculated from other signals. + * `data-show`, `data-text`, `data-attr`, `data-class`: These attributes take JavaScript-like expressions. `datastar.js` sets up "effects" that watch the signals mentioned in these expressions. When a relevant signal changes, the expression is re-evaluated, and the DOM is updated accordingly (e.g., an element is shown/hidden, its text content changes). + * `data-bind`: Creates two-way data binding between form input elements and signals. Changes in the input update the signal, and changes to the signal update the input's value. + * `data-on-{event}` (e.g., `data-on-click`): Attaches event listeners. The attribute's value is an expression that gets executed when the event fires. This expression can call "actions." + * `data-ref`: Stores a reference to the DOM element itself in a signal. +* **MutationObserver**: `datastar.js` sets up a `MutationObserver` to automatically initialize any new HTML content that gets added to the page later (e.g., through Datastar's own fragment merging). + +### 2. Client Interaction & Request to Server (`datastar.js` actions) + +* **User Action**: A user interacts with an element, for example, clicks a button: +html + +Action Triggered: The data-on-click directive executes its expression. +@post Action (cn plugin in datastar.js): +The @post(...) syntax calls a registered "action" plugin. datastar.js has built-in actions for HTTP methods (@get, @post, @put, @patch, @delete). +This action initiates an HTTP request to the specified URL (/api/submit-data). +Sending Data to Server (B function in datastar.js): +Headers: A Datastar-Request: true header is added to signal to the server that this is a Datastar-initiated request. The Accept header indicates preference for text/event-stream among others. +Payload: +If contentType: 'json' (default for POST/PUT/PATCH), datastar.js can automatically collect the current values of its signals, JSON-stringify them, and send them in the request body (unless excludeSignals: true). The Go server can then use datastar.ReadSignals() to parse this. +If contentType: 'form', it finds the closest