1 HTTP
| (require datastar/http) | package: datastar-lib |
Provides Datastar backend HTTP modules for reading incoming signals and producing SSE or one-shot responses.
Requests (datastar/http/request) —
Parse Datastar action request signals from incoming HTTP requests. SSE event streams (datastar/http/sse) —
text/event-stream responses for Datastar events, from single immediate updates to long-lived streams. One-shot HTTP responses (datastar/http/response) —
non-SSE responses with Datastar-aware headers using text/html, application/json, or text/javascript.
For protocol context, see Datastar Backend Requests and Datastar Response Handling.
1.1 Requests
| (require datastar/http/request) | package: datastar-lib |
Read Datastar request payloads from incoming backend action calls. For request/transport details, see Datastar Backend Requests.
procedure
(read-signals request) → jsexpr?
request : request?
procedure
(datastar-request? request) → boolean?
request : request?
1.2 SSE
| (require datastar/http/sse) | package: datastar-lib |
Send Datastar SSE events from backend actions. See the Datastar SSE events reference for event formats and data-line semantics.
1.2.1 Server Setup
Use dispatch/servlet from web-server/servlet-dispatch to convert your servlet into a dispatcher, then choose a serve configuration that matches your stream behavior.
Decision guide
Short-lived SSE (send one/few events, then return): defaults usually work; these transport knobs are optional.
Long-lived or mostly idle streams (subscriptions, optional CQRS read-model updates): configure #:safety-limits so response timeouts do not terminate idle handlers (for example, +inf.0, or periodic chunks/heartbeats).
Blocked on-open loop + prompt disconnect cleanup: use both #:connection-close? #t and #:tcp@ datastar-tcp@.
Minimal setup
(serve #:dispatch (dispatch/servlet handler))
Robust setup (long-lived streams / prompt blocked-loop disconnect cleanup)
(serve #:dispatch (dispatch/servlet handler) #:tcp@ datastar-tcp@ #:connection-close? #t #:safety-limits (make-safety-limits #:response-timeout +inf.0 #:response-send-timeout +inf.0))
value
datastar-tcp@ : (unit/c (import) (export tcp^))
When provided, datastar-sse monitors the underlying TCP input port and interrupts on-open when the client disconnects, so on-close can run sooner.
1.2.2 SSE Generator
Create SSE responses and send events through an sse? generator.
procedure
(datastar-sse on-open #:on-close on-close) → response?
on-open : (-> sse? any) on-close : (-> sse? any)
Configure serve using Server Setup: minimal setup for short-lived streams, robust setup for long-lived streams or fast disconnect handling.
procedure
(sse-closed? sse) → boolean?
sse : sse?
1.2.3 Locking
Coordinate concurrent senders with explicit SSE locking. All send functions are thread-safe: multiple threads can send events through the same generator and delivery order is serialized. If multiple threads share a single generator, use with-sse-lock to send a group of events without interleaving.
procedure
(call-with-sse-lock sse thunk) → any
sse : sse? thunk : (-> any)
Use this only when multiple threads send through the same sse? generator. If each generator is used by a single thread (the common case), individual sends are already thread-safe and this locking is unnecessary.
The lock is re-entrant: all send functions (patch-elements, patch-signals, etc.) use call-with-sse-lock internally, so calling them inside a locked region does not deadlock.
(call-with-sse-lock sse (lambda () (patch-elements/xexprs sse '(div ((id "a")) "part 1")) (patch-elements/xexprs sse '(div ((id "b")) "part 2")) (patch-signals sse (hash 'status "updated"))))
If an exception is raised inside thunk, the lock is released via dynamic-wind, so subsequent sends can still proceed.
syntax
(with-sse-lock sse body ...)
(with-sse-lock sse (patch-elements/xexprs sse '(div ((id "a")) "part 1")) (patch-elements/xexprs sse '(div ((id "b")) "part 2")) (patch-signals sse (hash 'status "updated")))
1.2.4 Signals
Send signal patch events to add, update, or remove client-side signals.
procedure
(patch-signals sse signals #:event-id event-id [ #:only-if-missing? only-if-missing?] #:retry-duration retry-duration) → void? sse : sse? signals : (or/c string? jsexpr?) event-id : string? only-if-missing? : boolean? = #f retry-duration : exact-positive-integer?
Serializer behavior omits default-equivalent wire fields: data: onlyIfMissing ... is emitted only when #:only-if-missing? is #t, and retry: ... is emitted only when #:retry-duration is non-default.
procedure
(remove-signals sse signal-path-or-paths #:event-id event-id #:retry-duration retry-duration) → void? sse : sse? signal-path-or-paths : (or/c string? (listof string?)) event-id : string? retry-duration : exact-positive-integer?
Dot notation paths are expanded to nested objects, so "user.name" becomes {"user":{"name":null}}.
1.2.5 Elements
Send element patch events to morph, insert, replace, or remove DOM content.
procedure
(patch-elements sse elements #:selector selector #:mode mode #:namespace namespace [ #:use-view-transitions? use-view-transitions?] #:event-id event-id #:retry-duration retry-duration) → void? sse : sse? elements : (or/c string? #f) selector : string? mode : element-patch-mode/c namespace : element-namespace/c use-view-transitions? : boolean? = #f event-id : string? retry-duration : exact-positive-integer?
The #:mode parameter controls how elements are patched. Use symbol values; see element-patch-mode/c for allowed values.
Serializer behavior omits default-equivalent wire fields: data: mode ... is omitted for 'outer, data: namespace ... is omitted for 'html, data: useViewTransition ... is omitted for #f, and retry: ... is omitted for the default retry duration.
(patch-elements sse "<div id=\"out\">hello</div>") (patch-elements sse "<svg>...</svg>" #:namespace 'svg) (patch-elements sse "<li>item</li>" #:selector "#list" #:mode 'append)
procedure
(patch-elements/xexprs sse xexpr-or-xexprs #:selector selector #:mode mode #:namespace namespace [ #:use-view-transitions? use-view-transitions?] #:event-id event-id #:retry-duration retry-duration) → void? sse : sse? xexpr-or-xexprs : (or/c xexpr/c (listof xexpr/c)) selector : string? mode : element-patch-mode/c namespace : element-namespace/c use-view-transitions? : boolean? = #f event-id : string? retry-duration : exact-positive-integer?
(patch-elements/xexprs sse '(div ((id "out")) "hello")) (patch-elements/xexprs sse '(svg "...") #:namespace 'svg) (patch-elements/xexprs sse '((li "one") (li "two")) #:selector "#list" #:mode 'append)
Contracts used by patch-elements and patch-elements/xexprs option arguments:
procedure
(remove-elements sse selector #:event-id event-id #:retry-duration retry-duration) → void? sse : sse? selector : string? event-id : string? retry-duration : exact-positive-integer?
1.2.6 Scripts
Run browser-side scripts from SSE responses. See Datastar script execution guidance for expression semantics.
procedure
(execute-script sse script [ #:auto-remove? auto-remove?] #:attributes attributes #:event-id event-id #:retry-duration retry-duration) → void? sse : sse? script : string? auto-remove? : boolean? = #t attributes : (or/c (hash/c symbol? any/c) (listof string?)) event-id : string? retry-duration : exact-positive-integer?
procedure
(console-log sse message) → void?
sse : sse? message : string?
procedure
(console-error sse message) → void?
sse : sse? message : string?
procedure
(replace-url sse location) → void?
sse : sse? location : string?
1.2.7 Compression
Compress SSE responses by wrapping your app handler with wrap-compress from web-server-compress:
(require datastar web-server-compress web-server/dispatch web-server/servlet-dispatch web-server/web-server) (define (events-handler req) (datastar-sse (lambda (sse) ...))) (define-values (app _uri) (dispatch-rules ...)) (serve #:dispatch (dispatch/servlet (wrap-compress app)) ...)
By default, wrap-compress negotiates with the client’s Accept-Encoding header using server-priority order: zstd first, then Brotli. If the client doesn’t support either, responses pass through uncompressed. Each SSE event is flushed to the client immediately.
To customize the encoding priority or tune per-encoding parameters:
(wrap-compress app #:encodings '(br zstd) #:brotli-quality 9 #:zstd-level 6)
For single-encoding use, wrap-brotli-compress and wrap-zstd-compress are also available. See the web-server-compress docs for the full API.
1.3 One-Shot Responses (Non-SSE)
| (require datastar/http/response) | package: datastar-lib |
Build Datastar-compatible non-SSE responses when a regular HTTP body is sufficient. The Tao of Datastar recommends using SSE event streams by default; see Datastar Response Handling for client-side behavior.
Non-SSE responses can still be useful when:
you are integrating with an existing non-SSE endpoint
a single immediate update is sufficient and you want a regular HTTP body/headers response
These helpers build regular HTTP responses for Datastar backend actions without opening an SSE stream.
procedure
(response/datastar-elements elements #:selector selector #:mode mode #:namespace namespace [ #:use-view-transitions? use-view-transitions? #:status status] #:headers headers) → response? elements : (or/c string? bytes?) selector : string? mode : element-patch-mode/c namespace : element-namespace/c use-view-transitions? : boolean? = #f status : exact-positive-integer? = 200 headers : (hash/c string? string?)
datastar-selector
datastar-mode
datastar-namespace
datastar-use-view-transition
Serializer behavior matches SSE omission defaults: datastar-mode is omitted for 'outer, datastar-namespace is omitted for 'html, and datastar-use-view-transition is omitted for #f.
procedure
(response/datastar-elements/xexprs xexpr-or-xexprs #:selector selector #:mode mode #:namespace namespace [ #:use-view-transitions? use-view-transitions? #:status status] #:headers headers) → response? xexpr-or-xexprs : (or/c xexpr/c (listof xexpr/c)) selector : string? mode : element-patch-mode/c namespace : element-namespace/c use-view-transitions? : boolean? = #f status : exact-positive-integer? = 200 headers : (hash/c string? string?)
procedure
(response/datastar-signals signals [ #:only-if-missing? only-if-missing? #:status status] #:headers headers) → response? signals : (or/c string? bytes? jsexpr?) only-if-missing? : boolean? = #f status : exact-positive-integer? = 200 headers : (hash/c string? string?)
Serializer behavior matches SSE omission defaults: datastar-only-if-missing is emitted only when #:only-if-missing? is #t (explicit #f is omitted).
procedure
(response/datastar-script script #:attributes attributes [ #:status status] #:headers headers) → response? script : (or/c string? bytes?) attributes : (hash/c (or/c symbol? string?) any/c) status : exact-positive-integer? = 200 headers : (hash/c string? string?)
When provided, #:attributes is JSON-encoded into the datastar-script-attributes response header.