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
Reference](https://pkg.go.dev/badge/github.com/starfederation/datastar.svg)](https://pkg.go.dev/github.com/starfederation/datastar)
[![Go 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
ServerSentEventGenerator struct that can be used to implement runtime specific
classes.
Usage is straightforward:
```go
package main
import (
"crypto/rand"
"encoding/hex"
"fmt"
"log/slog"
"net/http"
"os"
"time"
"crypto/rand"
"encoding/hex"
"fmt"
"log/slog"
"net/http"
"os"
"time"
datastar "github.com/starfederation/datastar/sdk/go"
datastar "github.com/starfederation/datastar/sdk/go"
)
const port = 9001
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
mux := http.NewServeMux()
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
mux := http.NewServeMux()
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
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';"
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 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(`
<!DOCTYPE html>
<html lang="en">
page := []byte(fmt.Sprintf(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
<script type="module" defer src="%s"></script>
</head>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
<script type="module" defer src="%s"></script>
</head>
<body style="%s">
<span id="feed" data-on-load="%s"></span>
</body>
<body style="%s">
<span id="feed" data-on-load="%s"></span>
</body>
</html>
`, cdn, style, datastar.GetSSE("/stream")))
</html>
`, cdn, style, datastar.GetSSE("/stream")))
mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
w.Write(page)
})
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()
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)
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 {
select {
if _, err := rand.Read(bytes); 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)
case <-r.Context().Done(): logger.Debug("Client connection closed") return case <-ticker.C: bytes :=make([]byte, 3) _,
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)
}
}
sse.MergeFragments(frag)
}
}
})
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 {
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"
)
type ExecuteScriptOptions struct {
// executeScriptOptions hold script options that will be translated to [SSEEventOptions].
type executeScriptOptions struct {
EventID string
RetryDuration time.Duration
Attributes []string
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 {
return func(o *ExecuteScriptOptions) {
return func(o *executeScriptOptions) {
o.EventID = id
}
}
// WithExecuteScriptRetryDuration overrides the [DefaultRetryDuration] for this script
// execution only.
func WithExecuteScriptRetryDuration(retryDuration time.Duration) ExecuteScriptOption {
return func(o *ExecuteScriptOptions) {
return func(o *executeScriptOptions) {
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 {
return func(o *ExecuteScriptOptions) {
return func(o *executeScriptOptions) {
o.Attributes = attributes
}
}
// WithExecuteScriptAttributeKVs is an alternative option for [WithExecuteScriptAttributes].
// Even parameters are keys, odd parameters are their values.
func WithExecuteScriptAttributeKVs(kvs ...string) ExecuteScriptOption {
if len(kvs)%2 != 0 {
panic("WithExecuteScriptAttributeKVs requires an even number of arguments")
@ -46,14 +61,16 @@ func WithExecuteScriptAttributeKVs(kvs ...string) ExecuteScriptOption {
return WithExecuteScriptAttributes(attributes...)
}
// WithExecuteScriptAutoRemove requires the client to eliminate the script element after its execution.
func WithExecuteScriptAutoRemove(autoremove bool) ExecuteScriptOption {
return func(o *ExecuteScriptOptions) {
return func(o *executeScriptOptions) {
o.AutoRemove = &autoremove
}
}
// ExecuteScript runs a script in the client browser. Seperate commands with semicolons.
func (sse *ServerSentEventGenerator) ExecuteScript(scriptContents string, opts ...ExecuteScriptOption) error {
options := &ExecuteScriptOptions{
options := &executeScriptOptions{
RetryDuration: DefaultSseRetryDuration,
Attributes: []string{"type module"},
}