update comments and documentation for Golang SDK

Added comments to `execute.go` file. I will make similar changes to
other files. I submit those as a way of getting feedback.

The only public API change is the removal of execute script options
struct. It is changed to private by letter case because the options
pattern is used to configure script execution. The struct serves no
purpose and pollutes the public API.
This commit is contained in:
Dmitry Kotik 2025-04-11 23:22:30 +03:00
parent b2e737d760
commit 6dc3e1890e
2 changed files with 77 additions and 62 deletions

View File

@ -1,89 +1,87 @@
# Go SDK for Datastar # Go SDK for Datastar
[![Go [![Go Reference](https://pkg.go.dev/badge/github.com/starfederation/datastar.svg)](https://pkg.go.dev/github.com/starfederation/datastar)
Reference](https://pkg.go.dev/badge/github.com/starfederation/datastar.svg)](https://pkg.go.dev/github.com/starfederation/datastar)
Implements the [SDK spec](../README.md) and exposes an abstract Implements the [SDK spec](../README.md) and exposes an abstract
ServerSentEventGenerator struct that can be used to implement runtime specific ServerSentEventGenerator struct that can be used to implement runtime specific
classes. classes.
Usage is straightforward:
```go ```go
package main package main
import ( import (
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"log/slog" "log/slog"
"net/http" "net/http"
"os" "os"
"time" "time"
datastar "github.com/starfederation/datastar/sdk/go" datastar "github.com/starfederation/datastar/sdk/go"
) )
const port = 9001 const port = 9001
func main() { func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
mux := http.NewServeMux() mux := http.NewServeMux()
cdn := "https://cdn.jsdelivr.net/gh/starfederation/datastar@develop/bundles/datastar.js" cdn := "https://cdn.jsdelivr.net/gh/starfederation/datastar@develop/bundles/datastar.js"
style := "display:flex;flex-direction:column;background-color:oklch(25.3267% 0.015896 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';"
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(` page := []byte(fmt.Sprintf(`
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" /> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
<script type="module" defer src="%s"></script> <script type="module" defer src="%s"></script>
</head> </head>
<body style="%s"> <body style="%s">
<span id="feed" data-on-load="%s"></span> <span id="feed" data-on-load="%s"></span>
</body> </body>
</html> </html>
`, cdn, style, datastar.GetSSE("/stream"))) `, cdn, style, datastar.GetSSE("/stream")))
mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
w.Write(page) w.Write(page)
}) })
mux.HandleFunc("GET /stream", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("GET /stream", func(w http.ResponseWriter, r *http.Request) {
ticker := time.NewTicker(100 * time.Millisecond) ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop() defer ticker.Stop()
sse := datastar.NewSSE(w, r) sse := datastar.NewSSE(w, r)
for {
select {
case <-r.Context().Done():
logger.Debug("Client connection closed")
return
case <-ticker.C:
bytes := make([]byte, 3)
for { if _, err := rand.Read(bytes); err != nil {
select { logger.Error("Error generating random bytes: ", slog.String("error", err.Error()))
return
}
hexString := hex.EncodeToString(bytes)
frag := fmt.Sprintf(`<span id="feed" style="color:#%s;border:1px solid #%s;border-radius:0.25rem;padding:1rem;">%s</span>`, hexString, hexString, hexString)
case <-r.Context().Done(): logger.Debug("Client connection closed") return case <-ticker.C: bytes :=make([]byte, 3) _, sse.MergeFragments(frag)
err :=rand.Read(bytes) if err !=nil { logger.Error("Error generating random bytes: ", slog.String(" error", }
err.Error())) return } hexString :=hex.EncodeToString(bytes) frag :=fmt.Sprintf(`<span id="feed" }
style="color:#%s;border:1px solid #%s;border-radius:0.25rem;padding:1rem;">%s</span>`, hexString, hexString,
hexString)
sse.MergeFragments(frag)
}
}
}) })
logger.Info(fmt.Sprintf("Server starting at 0.0.0.0:%d", port)) logger.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 { if err := http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", port), mux); err != nil {
logger.Error("Error starting server:", slog.String("error", err.Error())) logger.Error("Error starting server:", slog.String("error", err.Error()))
} }
}
```
} ## Examples
```
## Examples The [Datastar website](https://data-star.dev) acts as a [set of examples](https://github.com/starfederation/datastar/tree/develop/site) for how to use the SDK.
The [Datastar website](https://data-star.dev) acts as a [set of
examples](https://github.com/starfederation/datastar/tree/develop/site) for how to use the SDK.

View File

@ -7,33 +7,48 @@ import (
"time" "time"
) )
type ExecuteScriptOptions struct { // executeScriptOptions hold script options that will be translated to [SSEEventOptions].
type executeScriptOptions struct {
EventID string EventID string
RetryDuration time.Duration RetryDuration time.Duration
Attributes []string Attributes []string
AutoRemove *bool AutoRemove *bool
} }
type ExecuteScriptOption func(*ExecuteScriptOptions) // ExecuteScriptOption configures script execution event that will be sent to the client.
type ExecuteScriptOption func(*executeScriptOptions)
// WithExecuteScriptEventID configures an optional event ID for the script execution event.
// The client message field [lastEventId] will be set to this value.
// If the next event does not have an event ID, the last used event ID will remain.
//
// [lastEventId]: https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent/lastEventId
func WithExecuteScriptEventID(id string) ExecuteScriptOption { func WithExecuteScriptEventID(id string) ExecuteScriptOption {
return func(o *ExecuteScriptOptions) { return func(o *executeScriptOptions) {
o.EventID = id o.EventID = id
} }
} }
// WithExecuteScriptRetryDuration overrides the [DefaultRetryDuration] for this script
// execution only.
func WithExecuteScriptRetryDuration(retryDuration time.Duration) ExecuteScriptOption { func WithExecuteScriptRetryDuration(retryDuration time.Duration) ExecuteScriptOption {
return func(o *ExecuteScriptOptions) { return func(o *executeScriptOptions) {
o.RetryDuration = retryDuration o.RetryDuration = retryDuration
} }
} }
// WithExecuteScriptAttributes overrides the default script attribute
// value `type module`, which renders as `<script type="module">` in client's browser.
// Each attribute should include a pair of plain words representing the attribute name and value
// without any formatting.
func WithExecuteScriptAttributes(attributes ...string) ExecuteScriptOption { func WithExecuteScriptAttributes(attributes ...string) ExecuteScriptOption {
return func(o *ExecuteScriptOptions) { return func(o *executeScriptOptions) {
o.Attributes = attributes o.Attributes = attributes
} }
} }
// WithExecuteScriptAttributeKVs is an alternative option for [WithExecuteScriptAttributes].
// Even parameters are keys, odd parameters are their values.
func WithExecuteScriptAttributeKVs(kvs ...string) ExecuteScriptOption { func WithExecuteScriptAttributeKVs(kvs ...string) ExecuteScriptOption {
if len(kvs)%2 != 0 { if len(kvs)%2 != 0 {
panic("WithExecuteScriptAttributeKVs requires an even number of arguments") panic("WithExecuteScriptAttributeKVs requires an even number of arguments")
@ -46,14 +61,16 @@ func WithExecuteScriptAttributeKVs(kvs ...string) ExecuteScriptOption {
return WithExecuteScriptAttributes(attributes...) return WithExecuteScriptAttributes(attributes...)
} }
// WithExecuteScriptAutoRemove requires the client to eliminate the script element after its execution.
func WithExecuteScriptAutoRemove(autoremove bool) ExecuteScriptOption { func WithExecuteScriptAutoRemove(autoremove bool) ExecuteScriptOption {
return func(o *ExecuteScriptOptions) { return func(o *executeScriptOptions) {
o.AutoRemove = &autoremove o.AutoRemove = &autoremove
} }
} }
// ExecuteScript runs a script in the client browser. Seperate commands with semicolons.
func (sse *ServerSentEventGenerator) ExecuteScript(scriptContents string, opts ...ExecuteScriptOption) error { func (sse *ServerSentEventGenerator) ExecuteScript(scriptContents string, opts ...ExecuteScriptOption) error {
options := &ExecuteScriptOptions{ options := &executeScriptOptions{
RetryDuration: DefaultSseRetryDuration, RetryDuration: DefaultSseRetryDuration,
Attributes: []string{"type module"}, Attributes: []string{"type module"},
} }