Datastar Racket SDK
| (require datastar) | package: datastar |
This package provides a Racket SDK for working with Datastar.
1 Usage
Example usage (adapted from the Datastar Go SDK docs)
#lang racket (require datastar web-server/http web-server/safety-limits web-server/web-server json) (struct store (message count) #:transparent) (define (handler req) ; Read signals from the request (define signals-data (read-signals req)) (define current-store (store (hash-ref signals-data 'message "") (hash-ref signals-data 'count 0))) ; Create a Server-Sent Event response (datastar-sse req (lambda (sse) ; Patch elements in the DOM (patch-elements sse "<div id=\"output\">Hello from Datastar!</div>") ; Remove elements from the DOM (remove-elements sse "#temporary-element") ; Patch signals (update client-side state) (patch-signals sse (hash 'message "Updated message" 'count (+ (store-count current-store) 1))) ; Execute JavaScript in the browser (execute-script sse "console.log(\"Hello from server!\")") ; Redirect the browser (redirect sse "/new-page")))) (define stop (serve #:dispatch (dispatch/datastar handler) #:listen-ip "127.0.0.1" #:port 8000 #:connection-close? #t #:safety-limits (make-safety-limits #:response-timeout +inf.0 #:response-send-timeout +inf.0))) (with-handlers ([exn:break? (lambda (e) (stop))]) (sync/enable-break never-evt))
For more advanced usage with streaming updates, use the callback to loop directly. If the client disconnects or a send fails, an exception is raised which datastar-sse catches automatically, triggering cleanup via on-close:
(define (streaming-handler req) (datastar-sse req (lambda (sse) (for ([i (in-range 10)]) (patch-elements sse (format "<div id=\"counter\">Count: ~a</div>" i)) (patch-signals sse (hash 'counter i)) (sleep 1)))))
You can also use the #:on-close callback for cleanup when the connection ends:
(define connections (mutable-set)) (define (streaming-handler req) (datastar-sse req (lambda (sse) (set-add! connections sse) (console-log sse "connected")) #:on-close (lambda (sse) (set-remove! connections sse))))
For more examples, see the examples directory on GitHub.
2 Dispatcher
procedure
(dispatch/datastar servlet) → (-> connection? request? any)
servlet : (-> request? can-be-response?)
When used, datastar-sse automatically monitors the underlying TCP connection and detects client disconnections immediately. This ensures that the on-open callback is interrupted and on-close fires as soon as the client goes away, rather than waiting for the next failed write.
dispatch/datastar can be composed with the standard web server dispatchers (dispatch-sequencer, dispatch-filter, dispatch-files, etc.) for routing and static file serving. See the examples directory for a full example.
If dispatch/servlet is used instead of dispatch/datastar, everything still works but immediate disconnect detection is not available; disconnections will only be detected on the next failed write.
3 SSE Generator
procedure
(datastar-sse request on-open [ #:on-close on-close #:write-profile write-profile]) → response? request : request? on-open : (-> sse? any) on-close : (or/c (-> sse? any) #f) = #f write-profile : write-profile? = basic-write-profile
When the server is set up with dispatch/datastar, client disconnections are detected immediately: the SDK monitors the TCP input port and interrupts on-open as soon as the client goes away, ensuring prompt cleanup via on-close.
The write-profile controls how SSE bytes are written to the connection. The default basic-write-profile writes uncompressed. Custom write profiles can add compression (see Write Profiles). If the client does not advertise support for the profile’s content encoding in Accept-Encoding, the SDK automatically falls back to basic-write-profile.
Important: When using serve, two settings are required for SSE to work correctly:
#:connection-close? must be #t. Without this, the web server uses chunked transfer encoding with an internal pipe that silently absorbs writes to dead connections, preventing disconnect detection from working and on-close from firing.
#:safety-limits must disable both #:response-timeout and #:response-send-timeout (set to +inf.0). The defaults of 60 seconds will kill idle SSE connections. #:response-timeout limits the total time a handler can run, and #:response-send-timeout limits the time between successive writes. Both must be infinite for long-lived SSE connections.
4 Sending Events
All send functions take an sse? generator as their first argument and return All send functions take an sse? generator as their first argument. If the connection is closed or an I/O error occurs, an exception is raised. Within datastar-sse, these exceptions are caught automatically, triggering cleanup via on-close. Sends are thread-safe: multiple threads can send events through the same generator and delivery order is serialized.
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 : (or/c string? #f) = #f
mode : (or/c "outer" "inner" "remove" "replace" "prepend" "append" "before" "after" #f) = #f namespace : (or/c "html" "svg" "mathml" #f) = #f use-view-transitions : (or/c boolean? #f) = #f event-id : (or/c string? #f) = #f retry-duration : (or/c exact-positive-integer? #f) = #f
The #:mode parameter controls how elements are patched. Valid modes are "outer" (morph entire element, default), "inner" (morph inner HTML), "replace" (replace entire element), "prepend", "append", "before", "after", and "remove". When #f or "outer", the mode data line is omitted.
The #:namespace parameter specifies the namespace for creating new elements: "html" (default), "svg", or "mathml".
procedure
(remove-elements sse selector [ #:event-id event-id #:retry-duration retry-duration]) → void? sse : sse? selector : string? event-id : (or/c string? #f) = #f retry-duration : (or/c exact-positive-integer? #f) = #f
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 : (or/c string? #f) = #f only-if-missing : (or/c boolean? #f) = #f retry-duration : (or/c exact-positive-integer? #f) = #f
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?) #f) = #f event-id : (or/c string? #f) = #f retry-duration : (or/c exact-positive-integer? #f) = #f
procedure
(console-log sse message) → void?
sse : sse? message : string?
5 Reading Signals
procedure
(read-signals request) → jsexpr?
request : request?
6 Action Helpers
Convenience functions for generating Datastar backend action attribute strings.
`(main ((id "main") (data-init ,(sse-get "/events"))) (form ((data-on:submit ,(sse-post "/todo/create"))) (button ((data-on:click ,(sse-post (format "/todo/delete/~a" tid)))) "Delete")))
(sse-get "/events") ; => "@get('/events')" (sse-get "/events" "{includeLocal: true}") ; => "@get('/events', {includeLocal: true})"
procedure
(sse-delete url [args]) → string?
url : string? args : string? = #f
7 Write Profiles
A write profile controls how SSE bytes are written to the underlying connection. This abstraction allows pluggable compression (or other transforms) without changing the event-sending code. The #:write-profile parameter on datastar-sse accepts any write-profile? value.
procedure
(write-profile? v) → boolean?
v : any/c
For compression support, see the datastar-brotli package, which provides a Brotli write profile.
8 Constants
8.1 Defaults
value
DEFAULT-ELEMENT-PATCH-MODE : string? = "outer"
value
DEFAULT-ELEMENT-NAMESPACE : string? = "html"
8.2 Event Types
value
EVENT-TYPE-PATCH-ELEMENTS : string? = "datastar-patch-elements"
value
EVENT-TYPE-PATCH-SIGNALS : string? = "datastar-patch-signals"