Datastar Racket SDK
1 Usage
2 Dispatcher
dispatch/  datastar
3 SSE Generator
datastar-sse
sse?
close-sse
4 Sending Events
patch-elements
remove-elements
patch-signals
execute-script
redirect
console-log
5 Reading Signals
read-signals
6 Action Helpers
sse-get
sse-post
sse-put
sse-patch
sse-delete
7 Write Profiles
write-profile?
basic-write-profile
8 Constants
8.1 Defaults
DEFAULT-ELEMENT-PATCH-MODE
DEFAULT-ELEMENT-NAMESPACE
8.2 Event Types
EVENT-TYPE-PATCH-ELEMENTS
EVENT-TYPE-PATCH-SIGNALS
9.1

Datastar Racket SDK🔗ℹ

Jay Bonthius <jay@jmbmail.com>

 (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?)
Wraps a servlet into a dispatcher for use with serve. This is the recommended way to run Datastar applications.

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
Creates an HTTP response with proper SSE headers. Calls on-open with a fresh sse? generator that can be used to send events to the client. When on-open returns (or raises an exception), the connection is closed and on-close is called if provided.

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.

procedure

(sse? v)  boolean?

  v : any/c
Returns #t if v is an SSE generator created by datastar-sse.

procedure

(close-sse sse)  void?

  sse : sse?
Explicitly closes the SSE connection. This is called automatically when the on-open callback returns, but can be called earlier if needed. Safe to call multiple times.

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
Patches HTML elements into the DOM.

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
Removes elements from the DOM by CSS selector. This is a convenience function that calls patch-elements with mode remove.

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
Patches signals into the signal store using RFC 7386 JSON Merge Patch semantics. Supports add/update operations, removal by setting to null, and nested recursive patching.

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
Executes JavaScript in the browser by injecting a script tag. The script is automatically removed after execution unless auto-remove is #f.

procedure

(redirect sse location)  void?

  sse : sse?
  location : string?
Redirects the browser to a new location using window.location. This is a convenience function that calls execute-script.

procedure

(console-log sse message)  void?

  sse : sse?
  message : string?
Logs a message to the browser console via console.log. The message is automatically quoted as a JavaScript string. This is a convenience function that calls execute-script.

5 Reading Signals🔗ℹ

procedure

(read-signals request)  jsexpr?

  request : request?
Parses incoming signal data from the browser. For GET requests, extracts data from the datastar query parameter. For other methods, parses the request body as JSON. This is a standalone function that operates on the request and does not require an SSE generator.

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")))

procedure

(sse-get url [args])  string?

  url : string?
  args : string? = #f
Returns a @get action string. When args is provided, it is included as a second argument.

(sse-get "/events")           ; => "@get('/events')"
(sse-get "/events" "{includeLocal: true}")
                              ; => "@get('/events', {includeLocal: true})"

procedure

(sse-post url [args])  string?

  url : string?
  args : string? = #f
Returns a @post action string.

procedure

(sse-put url [args])  string?

  url : string?
  args : string? = #f
Returns a @put action string.

procedure

(sse-patch url [args])  string?

  url : string?
  args : string? = #f
Returns a @patch action string.

procedure

(sse-delete url [args])  string?

  url : string?
  args : string? = #f
Returns a @delete action string.

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
Returns #t if v is a write profile.

The default write profile. Writes SSE events uncompressed with no transformation.

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"

The default element patch mode. When a #:mode of "outer" or #f is passed to patch-elements, the mode data line is omitted from the SSE event.

The default element namespace.

8.2 Event Types🔗ℹ

value

EVENT-TYPE-PATCH-ELEMENTS : string? = "datastar-patch-elements"

An event for patching HTML elements into the DOM.

value

EVENT-TYPE-PATCH-SIGNALS : string? = "datastar-patch-signals"

An event for patching signals.