Documentation
¶
Overview ¶
Package datastar implements the DataStar protocol layer on top of go-sse.
DataStar is a hypermedia framework that uses Server-Sent Events (SSE) to push DOM patches, signal updates, and script execution commands from the server to the browser. This library provides the Go server-side vocabulary for that protocol.
Patches are values ¶
The core design principle is that patches are first-class values, not method calls on a live connection. Every patch implements the Patch interface:
type Patch interface {
Event() sse.Event
}
This means patches can be stored, queued, filtered, replayed, and broadcast using go-sse's Broadcaster[T], EventStore, SubscribeFilter, and Shutdown infrastructure — none of which is possible with the upstream starfederation/datastar-go SDK, where patches are methods bound to a live SSE generator.
Quick start ¶
import (
"github.com/larsartmann/go-datastar"
"github.com/larsartmann/go-sse"
)
// Broadcast patches to multiple connections
broadcaster := sse.NewBroadcaster[sse.Event]()
// Create a patch as a value
patch := datastar.NewElementsPatch("<div>Hello</div>",
datastar.WithSelector("#feed"),
datastar.WithMode(datastar.ElementPatchModeInner),
)
// Broadcast it — every subscriber receives the same sse.Event
broadcaster.Broadcast(patch.Event())
Wire format parity ¶
go-datastar reproduces the exact DataStar wire format expected by the DataStar JavaScript client. The data-line construction order, mode/namespace gating, retry logic, and script wrapping all match the upstream SDK behavior.
Modules ¶
The embedded DataStar JavaScript client lives in the separate zero-dependency module github.com/larsartmann/go-datastar/static (static.Bytes, static.Version), served ready-to-use via ScriptHandler. ScriptTagVersioned renders the cache-busting <script> tag whose src carries static.Version as a query parameter, so each bundle release is a distinct browser cache key. The E2E test helpers for consumers live in github.com/larsartmann/go-datastar/datastartest.
Classified errors ¶
Every error returned by this library is a classified *errorfamily.Error carrying a stable machine-readable code, a behavioral family (Rejection, Transient, Orchestration), and structured context. Consumers can match by code, sentinel, or family:
// By stable code:
if errorfamily.Code(err) == datastar.CodeSignalsMarshalFailed { ... }
// By sentinel (errors.Is matches by code+family):
if errors.Is(err, datastar.ErrEventNameRequired) { ... }
// By behavioral family (retryable? whose fault?):
if errorfamily.IsRetryable(err) { /* backoff + retry */ }
See the Error System section in AGENTS.md for the full catalog.
Companion to go-sse ¶
go-datastar depends on go-sse for the SSE transport layer (Stream, Broadcaster, EventStore, Replay). go-sse owns the wire format; go-datastar owns the protocol vocabulary. This separation keeps each library focused and composable.
Example (ErrorHandlingByCode) ¶
Example_errorHandlingByCode shows how to match errors by their stable code. Codes are string constants that never change between versions, making them safe for programmatic branching.
package main
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"github.com/larsartmann/go-datastar"
errorfamily "github.com/larsartmann/go-error-family"
)
func main() {
// Simulate a request with malformed JSON signals
req := httptest.NewRequestWithContext(
context.Background(), http.MethodPost, "/", strings.NewReader("{bad json"))
var target map[string]any
err := datastar.ReadSignals(req, &target)
switch errorfamily.Code(err) {
case datastar.CodeSignalsUnmarshalFailed:
fmt.Println("bad JSON — ask the user to fix their input")
case datastar.CodeBodyReadFailed:
fmt.Println("transient I/O — safe to retry")
default:
fmt.Println("unexpected error")
}
}
Output: bad JSON — ask the user to fix their input
Example (ErrorHandlingByFamily) ¶
Example_errorHandlingByFamily shows how to use the behavioral family to decide whether to retry. Transient errors (temporary I/O) are safe to retry with backoff; Rejection errors are not (the caller's input is wrong).
package main
import (
"fmt"
"github.com/larsartmann/go-datastar"
errorfamily "github.com/larsartmann/go-error-family"
)
func main() {
// MarshalSignals with an unmarshallable value (channel)
_, err := datastar.MarshalSignals(make(chan int))
if errorfamily.IsRetryable(err) {
fmt.Println("retrying...")
} else {
fmt.Println("not retryable — fix the input")
}
}
Output: not retryable — fix the input
Example (ErrorHandlingBySentinel) ¶
Example_errorHandlingBySentinel shows how to match errors by sentinel value. errors.Is works because errorfamily compares by (code, family), so even a context-enriched clone of a sentinel still matches.
package main
import (
"errors"
"fmt"
"github.com/larsartmann/go-datastar"
)
func main() {
_, err := datastar.NewDispatchCustomEventPatch("", nil)
if errors.Is(err, datastar.ErrEventNameRequired) {
fmt.Println("eventName is required")
}
}
Output: eventName is required
Index ¶
- Constants
- Variables
- func DeleteSSE(urlFormat string, args ...any) string
- func ETagFor(bundle []byte) string
- func ErrorResponse(stream *sse.Stream, message string, code string) error
- func ErrorResponseFromError(stream *sse.Stream, err error) error
- func GetSSE(urlFormat string, args ...any) string
- func LastEventID(req *http.Request) sse.EventID
- func MarshalSignals(v any) ([]byte, error)
- func NotificationResponse(stream *sse.Stream, message string, kind string) error
- func PatchSSE(urlFormat string, args ...any) string
- func PostSSE(urlFormat string, args ...any) string
- func PutSSE(urlFormat string, args ...any) string
- func ReadSignals(req *http.Request, signals any) error
- func ScriptHandler() http.Handler
- func ScriptHandlerWith(scriptBytes []byte) http.Handler
- func ScriptTag(path string) string
- func ScriptTagVersioned(path string) string
- func Version() string
- type DispatchCustomEventOption
- func WithCustomEventBubbles(b bool) DispatchCustomEventOption
- func WithCustomEventCancelable(b bool) DispatchCustomEventOption
- func WithCustomEventComposed(b bool) DispatchCustomEventOption
- func WithCustomEventEventID(id string) DispatchCustomEventOption
- func WithCustomEventSelector(s string) DispatchCustomEventOption
- type DispatchCustomEventPatch
- type ElementPatchMode
- type ElementPatchOption
- func WithElementsEventID(id string) ElementPatchOption
- func WithElementsRetryDuration(d time.Duration) ElementPatchOption
- func WithMode(mode ElementPatchMode) ElementPatchOption
- func WithModeAfter() ElementPatchOption
- func WithModeAppend() ElementPatchOption
- func WithModeBefore() ElementPatchOption
- func WithModeInner() ElementPatchOption
- func WithModeOuter() ElementPatchOption
- func WithModePrepend() ElementPatchOption
- func WithModeRemove() ElementPatchOption
- func WithModeReplace() ElementPatchOption
- func WithNamespace(ns Namespace) ElementPatchOption
- func WithNamespaceHTML() ElementPatchOption
- func WithNamespaceMathML() ElementPatchOption
- func WithNamespaceSVG() ElementPatchOption
- func WithSelector(selector string) ElementPatchOption
- func WithSelectorID(id string) ElementPatchOption
- func WithSelectorf(format string, args ...any) ElementPatchOption
- func WithViewTransitionSelector(selector string) ElementPatchOption
- func WithViewTransitions(enable bool) ElementPatchOption
- func WithViewTransitionsEnabled() ElementPatchOption
- func WithoutViewTransitions() ElementPatchOption
- type ElementsPatch
- func ElementsFromGostar(r GoStarElementRenderer, opts ...ElementPatchOption) (ElementsPatch, error)
- func ElementsFromTempl(c TemplComponent, opts ...ElementPatchOption) (ElementsPatch, error)
- func NewElementsPatch(html string, opts ...ElementPatchOption) ElementsPatch
- func NewRemoveByIDPatch(id string, opts ...ElementPatchOption) ElementsPatch
- func NewRemovePatch(selector string, opts ...ElementPatchOption) ElementsPatch
- type EventType
- type GoStarElementRenderer
- type MemoryStore
- type Namespace
- type Patch
- type Response
- func (r *Response) ApplyPatches(patches ...Patch) error
- func (r *Response) ConsoleError(err error, opts ...ScriptPatchOption) error
- func (r *Response) ConsoleLog(msg string, opts ...ScriptPatchOption) error
- func (r *Response) DispatchCustomEvent(eventName string, detail any, opts ...DispatchCustomEventOption) error
- func (r *Response) ErrorResponse(message, code string) error
- func (r *Response) ErrorResponseFromError(err error) error
- func (r *Response) ExecuteScript(script string, opts ...ScriptPatchOption) error
- func (r *Response) MarshalAndPatchSignals(v any, opts ...SignalsPatchOption) error
- func (r *Response) NotificationResponse(message, kind string) error
- func (r *Response) PatchElements(html string, opts ...ElementPatchOption) error
- func (r *Response) PatchElementsTempl(c TemplComponent, opts ...ElementPatchOption) error
- func (r *Response) PatchSignals(signalsJSON []byte, opts ...SignalsPatchOption) error
- func (r *Response) Prefetch(urls ...string) error
- func (r *Response) Redirect(targetURL string, opts ...ScriptPatchOption) error
- func (r *Response) RemoveElement(selector string, opts ...ElementPatchOption) error
- func (r *Response) RemoveElementByID(id string, opts ...ElementPatchOption) error
- func (r *Response) ReplaceURL(u url.URL, opts ...ScriptPatchOption) error
- func (r *Response) ReplaceURLQuerystring(req *http.Request, values url.Values, opts ...ScriptPatchOption) error
- func (r *Response) Send(evt sse.Event) error
- func (r *Response) Stream() *sse.Stream
- type ScriptPatch
- func NewConsoleErrorPatch(err error, opts ...ScriptPatchOption) ScriptPatch
- func NewConsoleLogPatch(msg string, opts ...ScriptPatchOption) ScriptPatch
- func NewConsoleLogfPatch(format string, args ...any) ScriptPatch
- func NewPrefetchPatch(urls ...string) ScriptPatch
- func NewRedirectPatch(targetURL string, opts ...ScriptPatchOption) ScriptPatch
- func NewRedirectfPatch(format string, args ...any) ScriptPatch
- func NewReplaceURLPatch(u url.URL, opts ...ScriptPatchOption) ScriptPatch
- func NewReplaceURLQuerystringPatch(r *http.Request, values url.Values, opts ...ScriptPatchOption) ScriptPatch
- func NewScriptPatch(script string, opts ...ScriptPatchOption) ScriptPatch
- type ScriptPatchOption
- type SignalsPatch
- type SignalsPatchOption
- type TemplComponent
Examples ¶
Constants ¶
const ( SelectorDatalineKey = "selector " ModeDatalineKey = "mode " NamespaceDatalineKey = "namespace " UseViewTransitionDatalineKey = "useViewTransition " ViewTransitionSelectorDatalineKey = "viewTransitionSelector " ElementsDatalineKey = "elements " SignalsDatalineKey = "signals " OnlyIfMissingDatalineKey = "onlyIfMissing " )
Dataline key constants. These have a trailing space baked in, matching the DataStar wire format (e.g., "selector #feed" is emitted as a data line).
const ( // CodeTemplRenderFailed: a [TemplComponent] failed to render to HTML. CodeTemplRenderFailed = "datastar.templ_render_failed" // CodeGostarRenderFailed: a [GoStarElementRenderer] failed to render to HTML. CodeGostarRenderFailed = "datastar.gostar_render_failed" // CodeBodyReadAfterClose: [ReadSignals] read the request body after it was // already closed, typically because an SSE stream consumed it first. CodeBodyReadAfterClose = "datastar.body_read_after_close" // CodeBodyReadFailed: [ReadSignals] could not read the request body. CodeBodyReadFailed = "datastar.body_read_failed" // CodeSignalsUnmarshalFailed: the inbound signals JSON could not be // unmarshaled into the caller's target. CodeSignalsUnmarshalFailed = "datastar.signals_unmarshal_failed" // CodeSignalsMarshalFailed: a Go value could not be marshaled to JSON for a // signals patch (e.g. a channel, function, or cyclic reference). CodeSignalsMarshalFailed = "datastar.signals_marshal_failed" // CodeEventNameRequired: [NewDispatchCustomEventPatch] was called with an // empty event name. CodeEventNameRequired = "datastar.event_name_required" // CodeCustomEventDetailMarshalFailed: [NewDispatchCustomEventPatch] could not // marshal the detail value to JSON (e.g. a channel, function, or cyclic reference). CodeCustomEventDetailMarshalFailed = "datastar.custom_event_detail_marshal_failed" // CodeElementPatchModeInvalid: [ElementPatchModeFromString] received an // unrecognized mode string. CodeElementPatchModeInvalid = "datastar.element_patch_mode_invalid" // CodeNamespaceInvalid: [NamespaceFromString] received an unrecognized // namespace string. CodeNamespaceInvalid = "datastar.namespace_invalid" // CodeStreamSendFailed: [Response] could not deliver an SSE event to the // underlying stream. Wraps any error returned by the transport. CodeStreamSendFailed = "datastar.stream_send_failed" // CodeErrorResponseNilError: [ErrorResponseFromError] was called with a nil // error — caller misuse, rejected instead of dereferenced. CodeErrorResponseNilError = "datastar.error_response_nil_error" )
Error codes for go-datastar. Each is a stable string accessible via errorfamily.Code, enabling programmatic handling, metrics, and structured logging without string matching on human-readable messages.
const DatastarJSVersion = static.Version
DatastarJSVersion is the version of the embedded DataStar JavaScript client. It re-exports static.Version for backward compatibility with the root API.
Deprecated: use Version (which returns the same string) or github.com/larsartmann/go-datastar/static.Version directly. This constant exists only so existing consumers keep compiling; it will be removed in the next minor release after the migration window.
const DatastarKey = "datastar"
DatastarKey is the query parameter key for DataStar signals on GET/DELETE requests.
const DefaultMemoryStoreCapacity = 128
DefaultMemoryStoreCapacity is the default number of events retained for reconnection replay when no capacity is specified.
const DefaultRetryDuration = 1000 * time.Millisecond
DefaultRetryDuration is the default SSE retry interval the DataStar client uses after a connection reset (1 second). Patches that leave RetryDuration at this value do not emit a retry field; only deviations from this default are sent on the wire.
Variables ¶
var ( // ErrBodyReadAfterClose is returned by [ReadSignals] when the request body // was already closed before reading. This usually means an SSE stream was // created before ReadSignals ran; re-order so ReadSignals reads the body // first. The underlying [http.ErrBodyReadAfterClose] is preserved as the cause. // // Declared as the error interface (not *errorfamily.Error) so sentinel // matching is purely value-based, matching the go-sse convention. ErrBodyReadAfterClose error = errorfamily.WrapRejection( http.ErrBodyReadAfterClose, CodeBodyReadAfterClose, "request body already closed (create the SSE stream after calling ReadSignals)", ) // ErrEventNameRequired is returned by [NewDispatchCustomEventPatch] when the // event name argument is empty. ErrEventNameRequired error = errorfamily.NewRejection( CodeEventNameRequired, "eventName is required", ) )
Sentinel errors for fixed-message failure modes. Match with errors.Is:
if errors.Is(err, datastar.ErrEventNameRequired) { ... }
errorfamily errors compare by (code, family), so a context-enriched clone returned by errorfamily.Error.WithContext still satisfies errors.Is against the sentinel. Dynamic-message errors (render failures, parse failures with the offending value) are constructed inline at their call site using the code constants above.
var ValidElementPatchModes = []ElementPatchMode{ ElementPatchModeOuter, ElementPatchModeInner, ElementPatchModeRemove, ElementPatchModePrepend, ElementPatchModeAppend, ElementPatchModeBefore, ElementPatchModeAfter, ElementPatchModeReplace, }
ValidElementPatchModes lists all valid element patch modes. It is a public package-level slice so callers can iterate the set without depending on the private definition. Treated as immutable.
var ValidNamespaces = []Namespace{ NamespaceHTML, NamespaceSVG, NamespaceMathML, }
ValidNamespaces lists all valid namespaces. It is a public package-level slice so callers can iterate the set without depending on the private definition. Treated as immutable.
Functions ¶
func ETagFor ¶ added in v0.7.0
ETagFor returns the ETag header value the script handlers serve for the given bundle: a quoted, truncated SHA-256 digest, computed once at handler construction. It is exported so consumer tests (via github.com/larsartmann/go-datastar/datastartest) can assert the caching setup around their own ScriptHandlerWith bundles without duplicating the digest algorithm.
func ErrorResponse ¶
ErrorResponse sends a signals patch with error information that the DataStar client can display. The message and code are sanitized to valid UTF-8 (invalid bytes become U+FFFD): this is a best-effort reporting path that must never fail on its own payload.
func ErrorResponseFromError ¶ added in v0.0.3
ErrorResponseFromError sends a signals patch with error metadata extracted from a Go error using errorfamily classification. The payload includes the error message, stable code, behavioral family, retryability, and the HTTP status code that the family maps to — giving the DataStar client enough context to render an appropriate error UI.
For non-errorfamily errors, code will be empty and Classify defaults to Transient (fail-open for retry), so family will be "transient", retryable will be true, and HTTPStatus will be 503.
A nil error is caller misuse and returns a classified Rejection (CodeErrorResponseNilError) without sending anything.
The error message and code are sanitized to valid UTF-8 (invalid bytes become U+FFFD, matching encoding/json v1 semantics): both are diagnostic text that must never make this reporting path itself fail — and json/v2 rejects invalid UTF-8 where v1 replaced it.
func LastEventID ¶
LastEventID extracts the last event ID from an HTTP request. It checks the standard "Last-Event-ID" header first, then falls back to the "lastEventId" query parameter (which the DataStar JS client sends on reconnection).
Returns an empty sse.EventID if no event ID is present.
func MarshalSignals ¶
MarshalSignals marshals a Go value to JSON for use as a DataStar signals payload. Returns an error instead of panicking.
func NotificationResponse ¶
NotificationResponse sends a signals patch with a notification message. The message and kind are sanitized to valid UTF-8 (invalid bytes become U+FFFD): this is a best-effort reporting path that must never fail on its own payload.
func ReadSignals ¶
ReadSignals extracts DataStar signals from an HTTP request and unmarshals them into the signals target (a pointer to a struct).
For GET and DELETE requests, signals are read from the "datastar" query parameter. For all other methods, signals are read from the JSON request body.
Returns nil (with no data written) if no signals are present (empty query param or empty body).
Example ¶
ExampleReadSignals demonstrates extracting signals from an inbound request body.
package main
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"github.com/larsartmann/go-datastar"
)
func main() {
req := httptest.NewRequestWithContext(
context.Background(),
http.MethodPost,
"/api",
strings.NewReader(`{"count":2}`),
)
var signals struct {
Count int `json:"count"`
}
if err := datastar.ReadSignals(req, &signals); err != nil {
return
}
fmt.Println(signals.Count)
}
Output: 2
func ScriptHandler ¶
ScriptHandler returns an http.Handler that serves the embedded DataStar JavaScript client bundle with correct Content-Type, ETag, and Cache-Control headers. Only GET and HEAD requests are allowed; all others return 405.
func ScriptHandlerWith ¶
ScriptHandlerWith returns an http.Handler that serves a custom JavaScript bundle. Use this to serve a different version of the DataStar client. The bundle's content identity is the ETag ETagFor computes, so serve it under a URL you version yourself — ScriptTagVersioned pins the embedded bundle's version, not a custom bundle's.
func ScriptTag ¶
ScriptTag returns an HTML <script> tag that loads the DataStar client from the given path. The path is used verbatim: no normalization, no escaping; callers own URL correctness. Prefer ScriptTagVersioned so a browser cache cannot serve a stale client after an upgrade.
func ScriptTagVersioned ¶ added in v0.7.0
ScriptTagVersioned returns an HTML <script> tag like ScriptTag, with the pinned DataStar client version as a cache-busting query parameter in the src URL. ScriptHandler serves the bundle with Cache-Control max-age=86400, so a browser that fetched the bundle skips ETag revalidation for up to 24h; a versioned URL turns each release into a distinct cache key and collapses that stale window to zero for pages rendered per request. If the path already carries a v parameter, its value is replaced with the pinned version — the result always carries exactly the pinned version, so the versioned URL is idempotent; any other existing query joins with &, and a fragment is preserved by inserting the parameter before it. The path is otherwise used verbatim — no normalization, no escaping — so a malformed path yields a malformed src, and callers own URL correctness. The version is always the embedded static.Version: pair this tag with ScriptHandler or a serving setup for `static.Bytes`; a custom bundle served via ScriptHandlerWith carries a different content identity than the URL advertises, so version the URL yourself in that setup.
Example ¶
ExampleScriptTagVersioned renders the cache-busting script tag: the pinned client version rides the URL as a query parameter, so a browser cache cannot serve a stale client after an upgrade. An existing v value is replaced with the pinned version, making the transform idempotent.
package main
import (
"fmt"
"github.com/larsartmann/go-datastar"
)
func main() {
fmt.Println(datastar.ScriptTagVersioned("/datastar.js"))
fmt.Println(datastar.ScriptTagVersioned("/datastar.js?v=0.9.0"))
}
Output: <script type="module" src="/datastar.js?v=1.0.4"></script> <script type="module" src="/datastar.js?v=1.0.4"></script>
func Version ¶
func Version() string
Version returns the version of the embedded DataStar JavaScript client. It reads static.Version directly; the deprecated DatastarJSVersion constant is an alias of the same value.
Types ¶
type DispatchCustomEventOption ¶
type DispatchCustomEventOption func(*DispatchCustomEventPatch)
DispatchCustomEventOption configures a DispatchCustomEventPatch.
func WithCustomEventBubbles ¶
func WithCustomEventBubbles(b bool) DispatchCustomEventOption
WithCustomEventBubbles overrides the default bubbling (true).
func WithCustomEventCancelable ¶
func WithCustomEventCancelable(b bool) DispatchCustomEventOption
WithCustomEventCancelable overrides the default cancellability (true).
func WithCustomEventComposed ¶
func WithCustomEventComposed(b bool) DispatchCustomEventOption
WithCustomEventComposed overrides the default composed flag (true).
func WithCustomEventEventID ¶
func WithCustomEventEventID(id string) DispatchCustomEventOption
WithCustomEventEventID sets the SSE event ID.
func WithCustomEventSelector ¶
func WithCustomEventSelector(s string) DispatchCustomEventOption
WithCustomEventSelector replaces the default target (document) with a CSS selector.
type DispatchCustomEventPatch ¶
type DispatchCustomEventPatch struct {
EventName string
Detail any
Selector string
Bubbles bool
Cancelable bool
Composed bool
EventID string
RetryDuration int64 // milliseconds; 0 = default
// contains filtered or unexported fields
}
DispatchCustomEventPatch dispatches a custom DOM event on the client via script execution. The detail value is marshaled to JSON in NewDispatchCustomEventPatch and passed as the event's detail property.
func NewDispatchCustomEventPatch ¶
func NewDispatchCustomEventPatch( eventName string, detail any, opts ...DispatchCustomEventOption, ) (DispatchCustomEventPatch, error)
NewDispatchCustomEventPatch creates a DispatchCustomEventPatch with the given event name and detail value. The detail is marshaled to JSON here, in the constructor; marshal failures return a classified error immediately instead of surfacing at send time.
type ElementPatchMode ¶
type ElementPatchMode string
ElementPatchMode controls how an element is merged into the DOM.
const ( // DefaultElementPatchMode is the default mode (outer). DefaultElementPatchMode ElementPatchMode = ElementPatchModeOuter // ElementPatchModeOuter morphs the element into the existing element. ElementPatchModeOuter ElementPatchMode = "outer" // ElementPatchModeInner replaces the inner HTML of the existing element. ElementPatchModeInner ElementPatchMode = "inner" // ElementPatchModeRemove removes the existing element. ElementPatchModeRemove ElementPatchMode = "remove" // ElementPatchModeReplace replaces the existing element with the new element. ElementPatchModeReplace ElementPatchMode = "replace" // ElementPatchModePrepend prepends the element inside the existing element. ElementPatchModePrepend ElementPatchMode = "prepend" // ElementPatchModeAppend appends the element inside the existing element. ElementPatchModeAppend ElementPatchMode = "append" // ElementPatchModeBefore inserts the element before the existing element. ElementPatchModeBefore ElementPatchMode = "before" // ElementPatchModeAfter inserts the element after the existing element. ElementPatchModeAfter ElementPatchMode = "after" )
func ElementPatchModeFromString ¶
func ElementPatchModeFromString(modeStr string) (ElementPatchMode, error)
ElementPatchModeFromString converts a string to an ElementPatchMode. Returns an error for invalid mode strings.
type ElementPatchOption ¶
type ElementPatchOption func(*ElementsPatch)
ElementPatchOption configures an ElementsPatch.
func WithElementsEventID ¶
func WithElementsEventID(id string) ElementPatchOption
WithElementsEventID sets the SSE event ID for the element patch.
func WithElementsRetryDuration ¶
func WithElementsRetryDuration(d time.Duration) ElementPatchOption
WithElementsRetryDuration overrides the SSE retry duration for the element patch.
func WithMode ¶
func WithMode(mode ElementPatchMode) ElementPatchOption
WithMode overrides the DefaultElementPatchMode for the element.
func WithModeAfter ¶
func WithModeAfter() ElementPatchOption
WithModeAfter creates an option that inserts after the target element.
func WithModeAppend ¶
func WithModeAppend() ElementPatchOption
WithModeAppend creates an option that appends inside the target element.
func WithModeBefore ¶
func WithModeBefore() ElementPatchOption
WithModeBefore creates an option that inserts before the target element.
func WithModeInner ¶
func WithModeInner() ElementPatchOption
WithModeInner creates an option that replaces the inner HTML of the target.
func WithModeOuter ¶
func WithModeOuter() ElementPatchOption
WithModeOuter creates an option that uses the outer merge mode (default).
func WithModePrepend ¶
func WithModePrepend() ElementPatchOption
WithModePrepend creates an option that prepends inside the target element.
func WithModeRemove ¶
func WithModeRemove() ElementPatchOption
WithModeRemove creates an option that removes the target element.
func WithModeReplace ¶
func WithModeReplace() ElementPatchOption
WithModeReplace creates an option that replaces the target element without morphing.
func WithNamespace ¶
func WithNamespace(ns Namespace) ElementPatchOption
WithNamespace specifies the XML namespace for the element.
func WithNamespaceHTML ¶
func WithNamespaceHTML() ElementPatchOption
WithNamespaceHTML sets the namespace to HTML (the default — usually a no-op).
func WithNamespaceMathML ¶
func WithNamespaceMathML() ElementPatchOption
WithNamespaceMathML sets the namespace to MathML.
func WithNamespaceSVG ¶
func WithNamespaceSVG() ElementPatchOption
WithNamespaceSVG sets the namespace to SVG.
func WithSelector ¶
func WithSelector(selector string) ElementPatchOption
WithSelector sets the CSS selector for the element patch target.
func WithSelectorID ¶
func WithSelectorID(id string) ElementPatchOption
WithSelectorID is a convenience for WithSelector("#" + id).
func WithSelectorf ¶
func WithSelectorf(format string, args ...any) ElementPatchOption
WithSelectorf is a printf-style variant of WithSelector.
func WithViewTransitionSelector ¶
func WithViewTransitionSelector(selector string) ElementPatchOption
WithViewTransitionSelector scopes the view transition to a CSS selector.
func WithViewTransitions ¶
func WithViewTransitions(enable bool) ElementPatchOption
WithViewTransitions enables the View Transition API for the merge.
func WithViewTransitionsEnabled ¶
func WithViewTransitionsEnabled() ElementPatchOption
WithViewTransitionsEnabled is shorthand for WithViewTransitions(true).
func WithoutViewTransitions ¶
func WithoutViewTransitions() ElementPatchOption
WithoutViewTransitions is shorthand for WithViewTransitions(false).
type ElementsPatch ¶
type ElementsPatch struct {
// Selector is the CSS selector for the target element.
// Empty means no selector data line is emitted.
Selector string
// Mode controls how the element is merged. The default (outer) is never
// emitted on the wire — it is the DataStar client's default behavior.
Mode ElementPatchMode
// Namespace specifies the XML namespace. The default (html) is never
// emitted on the wire.
Namespace Namespace
// UseViewTransitions enables the View Transition API for the merge.
UseViewTransitions bool
// ViewTransitionSelector scopes the view transition to a specific element.
ViewTransitionSelector string
// HTML is the element content to patch into the DOM.
HTML string
// EventID is an optional SSE event identifier for reconnection replay.
EventID string
// RetryDuration overrides the default SSE retry interval. Only emitted
// on the wire when > 0 and != [DefaultRetryDuration].
RetryDuration time.Duration
}
ElementsPatch patches HTML elements into the DOM on the DataStar client. It is the most-used DataStar patch type.
Construct one with NewElementsPatch and functional options:
patch := datastar.NewElementsPatch("<div>Hello</div>",
datastar.WithSelector("#feed"),
datastar.WithMode(datastar.ElementPatchModeInner),
)
stream.Send(patch.Event())
Example ¶
ExampleElementsPatch demonstrates the library's keystone: a patch is a value you construct without a connection. Its wire format is fully determined by the struct fields and options.
package main
import (
"fmt"
"github.com/larsartmann/go-datastar"
)
func main() {
patch := datastar.NewElementsPatch("<div>Hello</div>",
datastar.WithSelector("#feed"),
datastar.WithModePrepend(),
)
fmt.Println(patch.Event().Data)
}
Output: selector #feed mode prepend elements <div>Hello</div>
func ElementsFromGostar ¶
func ElementsFromGostar( r GoStarElementRenderer, opts ...ElementPatchOption, ) (ElementsPatch, error)
ElementsFromGostar renders a GoStarElementRenderer to HTML and creates an ElementsPatch from the result.
func ElementsFromTempl ¶
func ElementsFromTempl(c TemplComponent, opts ...ElementPatchOption) (ElementsPatch, error)
ElementsFromTempl renders a TemplComponent to HTML and creates an ElementsPatch from the result.
func NewElementsPatch ¶
func NewElementsPatch(html string, opts ...ElementPatchOption) ElementsPatch
NewElementsPatch creates an ElementsPatch with the given HTML and options. The default mode is DefaultElementPatchMode (outer), which is never emitted on the wire.
func NewRemoveByIDPatch ¶
func NewRemoveByIDPatch(id string, opts ...ElementPatchOption) ElementsPatch
NewRemoveByIDPatch creates an ElementsPatch that removes the element with the given ID. Equivalent to NewRemovePatch("#" + id).
func NewRemovePatch ¶
func NewRemovePatch(selector string, opts ...ElementPatchOption) ElementsPatch
NewRemovePatch creates an ElementsPatch that removes the element matching the given CSS selector from the DOM. It is equivalent to:
NewElementsPatch("", WithModeRemove(), WithSelector(selector))
func (ElementsPatch) Event ¶
func (p ElementsPatch) Event() sse.Event
Event returns the sse.Event for this element patch. The data lines are constructed in the exact order the DataStar JS client expects:
- selector (if non-empty)
- mode (if not outer)
- namespace (if non-empty and not html)
- useViewTransition true (if enabled)
- viewTransitionSelector (if non-empty and view transitions enabled)
- elements <line> (one per line of HTML)
type EventType ¶
type EventType string
EventType is the DataStar protocol event type sent as the SSE event: field.
type GoStarElementRenderer ¶
GoStarElementRenderer satisfies the component rendering interface for the GoStar template engine.
type MemoryStore ¶
type MemoryStore struct {
// contains filtered or unexported fields
}
MemoryStore is an in-memory ring buffer implementing sse.EventStore. It keeps the last N events for SSE reconnection replay.
Events are sequenced by their numeric sse.EventID: EventsAfter returns all stored events whose ID is strictly greater than the requested lastID. Non-numeric IDs are treated as zero, so an empty or non-numeric lastID replays the entire buffer.
MemoryStore is safe for concurrent use. It is intended for single-process deployments and demos. For multi-instance setups, use a shared store (Redis, Postgres) that implements sse.EventStore.
Example ¶
ExampleMemoryStore demonstrates replay for reconnecting clients: the store keeps the last N events, and EventsAfter returns everything after a given Last-Event-ID.
package main
import (
"fmt"
"github.com/larsartmann/go-datastar"
"github.com/larsartmann/go-sse"
)
func main() {
store := datastar.NewMemoryStore(16)
first := datastar.NewElementsPatch("<div>one</div>").Event()
first.ID = sse.NewEventID("1")
second, err := datastar.NewSignalsIfMissingPatch(map[string]any{"count": 2})
if err != nil {
panic(err)
}
secondEvt := second.Event()
secondEvt.ID = sse.NewEventID("2")
store.Append(first)
store.Append(secondEvt)
fmt.Println("stored:", store.Len())
replay, err := store.EventsAfter(sse.NewEventID("1"))
if err != nil {
panic(err)
}
for _, evt := range replay {
fmt.Println("replay after 1:", evt.Event)
}
}
Output: stored: 2 replay after 1: datastar-patch-signals
func NewMemoryStore ¶
func NewMemoryStore(capacity int) *MemoryStore
NewMemoryStore creates a MemoryStore that retains the last capacity events. If capacity is non-positive, DefaultMemoryStoreCapacity is used.
func (*MemoryStore) Append ¶
func (s *MemoryStore) Append(evt sse.Event)
Append stores an event for later replay. If the buffer is full, the oldest event is evicted.
func (*MemoryStore) EventsAfter ¶
EventsAfter returns stored events with IDs strictly greater than lastID, ordered ascending by sequence number. Non-numeric lastID values are treated as zero, replaying the entire buffer.
func (*MemoryStore) Len ¶
func (s *MemoryStore) Len() int
Len returns the number of events currently stored.
type Namespace ¶
type Namespace string
Namespace is the XML namespace to use when patching elements into the DOM.
type Patch ¶
type Patch interface {
// Event returns the SSE wire-format event for this patch. The returned
// [sse.Event] contains the event type, data lines, optional event ID, and
// optional retry duration — everything go-sse needs to serialize the patch.
Event() sse.Event
}
Patch is the core interface of go-datastar. Every DataStar protocol message (element patches, signal patches, script execution, redirects, etc.) implements this interface.
The key design principle: patches are first-class VALUES, not method calls on a live connection. A Patch can be constructed, stored in a slice, filtered by a predicate, replayed from an EventStore, and broadcast through a sse.Broadcaster — all without an open HTTP connection.
Call Patch.Event to produce the final sse.Event that go-sse serializes to the wire:
patch := datastar.NewElementsPatch("<div>hi</div>", datastar.WithSelector("#feed"))
broadcaster := sse.NewBroadcaster[sse.Event]()
broadcaster.Broadcast(patch.Event())
Or broadcast patches directly for typed filtering:
patchCaster := sse.NewBroadcaster[datastar.Patch]() patchCaster.Broadcast(patch)
type Response ¶
type Response struct {
// contains filtered or unexported fields
}
Response wraps an sse.Stream and provides fluent methods for sending DataStar patches on a single HTTP connection. Each method constructs a Patch, calls its Patch.Event method, and sends the resulting sse.Event via the underlying stream.
Create one per HTTP handler:
func handler(w http.ResponseWriter, r *http.Request) {
stream := sse.NewStream(w, r)
defer func() { _ = stream.Close() }()
resp := datastar.NewResponse(stream)
if err := resp.PatchElements("<div>Hello</div>", datastar.WithSelector("#feed")); err != nil {
log.Printf("patch elements: %v", err)
return
}
if err := resp.MarshalAndPatchSignals(map[string]any{"count": 1}); err != nil {
log.Printf("patch signals: %v", err)
}
}
func NewResponse ¶
NewResponse creates a Response wrapping the given sse.Stream.
func NewResponseFromHTTP ¶
func NewResponseFromHTTP(w http.ResponseWriter, r *http.Request) *Response
NewResponseFromHTTP is a convenience that creates an sse.Stream from the ResponseWriter and Request, then wraps it in a Response.
func (*Response) ApplyPatches ¶
ApplyPatches sends multiple patches in sequence.
func (*Response) ConsoleError ¶
func (r *Response) ConsoleError(err error, opts ...ScriptPatchOption) error
ConsoleError sends a console.error ScriptPatch.
func (*Response) ConsoleLog ¶
func (r *Response) ConsoleLog(msg string, opts ...ScriptPatchOption) error
ConsoleLog sends a console.log ScriptPatch.
func (*Response) DispatchCustomEvent ¶
func (r *Response) DispatchCustomEvent( eventName string, detail any, opts ...DispatchCustomEventOption, ) error
DispatchCustomEvent dispatches a custom DOM event on the client.
func (*Response) ErrorResponse ¶ added in v0.5.0
ErrorResponse is the method form of ErrorResponse on this response's stream — the fluent spelling when you already hold a Response.
func (*Response) ErrorResponseFromError ¶ added in v0.5.0
ErrorResponseFromError is the method form of ErrorResponseFromError on this response's stream.
func (*Response) ExecuteScript ¶
func (r *Response) ExecuteScript(script string, opts ...ScriptPatchOption) error
ExecuteScript sends a ScriptPatch on the underlying stream.
func (*Response) MarshalAndPatchSignals ¶
func (r *Response) MarshalAndPatchSignals(v any, opts ...SignalsPatchOption) error
MarshalAndPatchSignals marshals a Go value to JSON and sends it as a SignalsPatch. Returns an error if marshaling fails.
func (*Response) NotificationResponse ¶ added in v0.5.0
NotificationResponse is the method form of NotificationResponse on this response's stream.
func (*Response) PatchElements ¶
func (r *Response) PatchElements(html string, opts ...ElementPatchOption) error
func (*Response) PatchElementsTempl ¶
func (r *Response) PatchElementsTempl(c TemplComponent, opts ...ElementPatchOption) error
PatchElementsTempl renders a TemplComponent to HTML and sends it as an ElementsPatch.
func (*Response) PatchSignals ¶
func (r *Response) PatchSignals(signalsJSON []byte, opts ...SignalsPatchOption) error
PatchSignals sends a SignalsPatch with the given pre-encoded JSON.
func (*Response) Prefetch ¶
Prefetch sends a speculation rules ScriptPatch to prefetch the given URLs.
func (*Response) Redirect ¶
func (r *Response) Redirect(targetURL string, opts ...ScriptPatchOption) error
Redirect sends a redirect ScriptPatch.
func (*Response) RemoveElement ¶
func (r *Response) RemoveElement(selector string, opts ...ElementPatchOption) error
RemoveElement sends an ElementsPatch that removes the given selector.
func (*Response) RemoveElementByID ¶
func (r *Response) RemoveElementByID(id string, opts ...ElementPatchOption) error
RemoveElementByID sends an ElementsPatch that removes the element with the given ID.
func (*Response) ReplaceURL ¶
func (r *Response) ReplaceURL(u url.URL, opts ...ScriptPatchOption) error
ReplaceURL sends a replaceState ScriptPatch.
func (*Response) ReplaceURLQuerystring ¶ added in v0.4.0
func (r *Response) ReplaceURLQuerystring( req *http.Request, values url.Values, opts ...ScriptPatchOption, ) error
ReplaceURLQuerystring replaces the browser URL's query string with the encoded values, preserving the request's path (upstream-parity convenience over NewReplaceURLQuerystringPatch).
type ScriptPatch ¶
type ScriptPatch struct {
// Script is the JavaScript source code to execute.
Script string
// AutoRemove controls whether the script element self-removes after
// execution. nil (the default) and true both add data-effect="el.remove()".
// Set to false to keep the element.
AutoRemove *bool
// Attributes are additional HTML attributes for the <script> tag.
// Each should be a complete attribute (e.g., `type="module"`).
Attributes []string
// EventID is an optional SSE event identifier.
EventID string
// RetryDuration overrides the default SSE retry interval.
RetryDuration time.Duration
}
ScriptPatch executes JavaScript on the DataStar client by injecting a <script> element into the DOM (patched via ElementsPatch with selector=body, mode=append).
By default the script element auto-removes itself after execution (data-effect="el.remove()"). Use WithScriptAutoRemove(false) to keep the element.
Construct one with NewScriptPatch:
patch := datastar.NewScriptPatch("console.log('hi')")
stream.Send(patch.Event())
func NewConsoleErrorPatch ¶
func NewConsoleErrorPatch(err error, opts ...ScriptPatchOption) ScriptPatch
NewConsoleErrorPatch creates a ScriptPatch that calls console.error with the given error's message. The message is JS-quoted via %q.
func NewConsoleLogPatch ¶
func NewConsoleLogPatch(msg string, opts ...ScriptPatchOption) ScriptPatch
NewConsoleLogPatch creates a ScriptPatch that calls console.log with the given message. The message is JS-quoted via %q.
func NewConsoleLogfPatch ¶
func NewConsoleLogfPatch(format string, args ...any) ScriptPatch
NewConsoleLogfPatch is a printf-style variant of NewConsoleLogPatch.
func NewPrefetchPatch ¶
func NewPrefetchPatch(urls ...string) ScriptPatch
NewPrefetchPatch creates a ScriptPatch that injects a speculation rules JSON block to prefetch the given URLs.
func NewRedirectPatch ¶
func NewRedirectPatch(targetURL string, opts ...ScriptPatchOption) ScriptPatch
NewRedirectPatch creates a ScriptPatch that redirects the browser to the given URL using setTimeout.
func NewRedirectfPatch ¶
func NewRedirectfPatch(format string, args ...any) ScriptPatch
NewRedirectfPatch is a printf-style variant of NewRedirectPatch.
func NewReplaceURLPatch ¶
func NewReplaceURLPatch(u url.URL, opts ...ScriptPatchOption) ScriptPatch
NewReplaceURLPatch creates a ScriptPatch that replaces the browser URL using history.replaceState.
func NewReplaceURLQuerystringPatch ¶ added in v0.4.0
func NewReplaceURLQuerystringPatch( r *http.Request, values url.Values, opts ...ScriptPatchOption, ) ScriptPatch
NewReplaceURLQuerystringPatch creates a ScriptPatch that replaces the browser URL's query string with the encoded values, preserving the request's path. It mirrors the upstream SDK's ReplaceURLQuerystring: the request URL is cloned, RawQuery is set from values.Encode(), and the result is sent through NewReplaceURLPatch.
Example ¶
ExampleNewReplaceURLQuerystringPatch replaces the browser URL's query string while preserving the request path (upstream-parity convenience).
package main
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"github.com/larsartmann/go-datastar"
)
func main() {
req := httptest.NewRequestWithContext(
context.Background(),
http.MethodGet,
"/items?filter=old",
nil,
)
patch := datastar.NewReplaceURLQuerystringPatch(req, url.Values{"filter": {"new"}})
fmt.Println(patch.Event().Data)
}
Output: selector body mode append elements <script data-effect="el.remove()">window.history.replaceState({}, "", "/items?filter=new")</script>
func NewScriptPatch ¶
func NewScriptPatch(script string, opts ...ScriptPatchOption) ScriptPatch
NewScriptPatch creates a ScriptPatch with the given JavaScript source and options. The default retry duration is DefaultRetryDuration.
type ScriptPatchOption ¶
type ScriptPatchOption func(*ScriptPatch)
ScriptPatchOption configures a ScriptPatch.
func WithScriptAttributeKVs ¶
func WithScriptAttributeKVs(kvs ...string) ScriptPatchOption
WithScriptAttributeKVs sets script attributes from key-value pairs. If the argument count is odd, the final unpaired key is silently dropped.
Prefer WithScriptAttributes for pre-formatted attributes.
func WithScriptAttributes ¶
func WithScriptAttributes(attrs ...string) ScriptPatchOption
WithScriptAttributes sets additional HTML attributes for the <script> tag. Each should be a complete key="value" pair (e.g., `type="module"`).
func WithScriptAutoRemove ¶
func WithScriptAutoRemove(b bool) ScriptPatchOption
WithScriptAutoRemove controls whether the script element self-removes. Pass false to keep the element.
func WithScriptEventID ¶
func WithScriptEventID(id string) ScriptPatchOption
WithScriptEventID sets the SSE event ID for the script patch.
func WithScriptRetryDuration ¶
func WithScriptRetryDuration(d time.Duration) ScriptPatchOption
WithScriptRetryDuration overrides the SSE retry duration for the script patch.
type SignalsPatch ¶
type SignalsPatch struct {
// Signals is the JSON-encoded signal payload.
Signals []byte
// OnlyIfMissing instructs the client to only set signals that don't
// already exist.
OnlyIfMissing bool
// EventID is an optional SSE event identifier.
EventID string
// RetryDuration overrides the default SSE retry interval. Only emitted
// when > 0 and != [DefaultRetryDuration].
RetryDuration time.Duration
}
SignalsPatch patches reactive signals on the DataStar client. The signals payload must be JSON-encoded bytes.
Construct one with NewSignalsPatch (marshals a Go value) or directly (if you already have JSON bytes):
// From a Go struct:
patch, err := datastar.NewSignalsPatch(map[string]any{"count": 42})
// From pre-encoded JSON:
patch := datastar.SignalsPatch{Signals: []byte(`{"count":42}`)}
Example ¶
ExampleSignalsPatch shows pre-encoded JSON signals emitted as a wire event.
package main
import (
"fmt"
"github.com/larsartmann/go-datastar"
)
func main() {
patch := datastar.SignalsPatch{Signals: []byte(`{"count":1}`)}
fmt.Println(patch.Event().Data)
}
Output: signals {"count":1}
func NewSignalsIfMissingPatch ¶
func NewSignalsIfMissingPatch(v any, opts ...SignalsPatchOption) (SignalsPatch, error)
NewSignalsIfMissingPatch creates a SignalsPatch with OnlyIfMissing=true.
func NewSignalsPatch ¶
func NewSignalsPatch(v any, opts ...SignalsPatchOption) (SignalsPatch, error)
NewSignalsPatch creates a SignalsPatch from a Go value, marshaling it to JSON. Returns an error if marshaling fails (unlike the SDK's panicking MarshalAndPatchSignals).
type SignalsPatchOption ¶
type SignalsPatchOption func(*SignalsPatch)
SignalsPatchOption configures a SignalsPatch.
func WithOnlyIfMissing ¶
func WithOnlyIfMissing(onlyIfMissing bool) SignalsPatchOption
WithOnlyIfMissing instructs the client to only patch signals that are missing.
func WithSignalsEventID ¶
func WithSignalsEventID(id string) SignalsPatchOption
WithSignalsEventID sets the SSE event ID for the signals patch.
func WithSignalsRetryDuration ¶
func WithSignalsRetryDuration(d time.Duration) SignalsPatchOption
WithSignalsRetryDuration overrides the SSE retry duration for the signals patch.
type TemplComponent ¶
TemplComponent satisfies the component rendering interface for the Templ template engine. This separate type ensures compatibility with Templ without imposing a dependency on those who prefer a different template engine.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
broadcast
module
|
|
|
datastartest
module
|
|
|
Example: live feed using go-datastar patches as values with go-sse Broadcaster.
|
Example: live feed using go-datastar patches as values with go-sse Broadcaster. |
|
domain-adapter
command
Command domain-adapter demonstrates the "patches as values" architecture: a domain layer speaks its own events, an EventBridge translates them into go-datastar Patch values, and the transport layer stores/replays/broadcasts those values without knowing anything about the domain.
|
Command domain-adapter demonstrates the "patches as values" architecture: a domain layer speaks its own events, an EventBridge translates them into go-datastar Patch values, and the transport layer stores/replays/broadcasts those values without knowing anything about the domain. |
|
static
module
|
|
|
Package version exposes the build-time version of the go-datastar example binary (and any consumer binary that links this package and sets it via -ldflags).
|
Package version exposes the build-time version of the go-datastar example binary (and any consumer binary that links this package and sets it via -ldflags). |