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.
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 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, _ string) http.Handler
- func ScriptTag(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) ExecuteScript(script string, opts ...ScriptPatchOption) error
- func (r *Response) MarshalAndPatchSignals(v any, opts ...SignalsPatchOption) 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) 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 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" )
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.
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. ErrBodyReadAfterClose = 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 = 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 ErrorResponse ¶
ErrorResponse sends a signals patch with error information that the DataStar client can display.
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.
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.
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.
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 cancelability (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 when DispatchCustomEventPatch.Event is called.
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.
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) 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) 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.
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 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 |
|---|---|
|
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. |
|
static
module
|