ΛↃ LAMBDACOMBINE

Symbolic Systems Infrastructure

01 · SSE and patch-elements

SERVER-SENT EVENTS · PATCH-ELEMENTS · THE SIMPLEST PUSH
Pioneer Plaque, sent to outer space
Pionner Plaque

Server-Sent Events (SSE) are the transport layer for Datastar: an SSE response is a plain HTTP response with content type text/event-stream. The server keeps the connection open and writes formatted lines of text. The browser reads them and processes each event as it arrives.

Datastar opens an SSE connection when it evaluates @get() or @post() expressions. The data-init attribute runs an expression once when the element loads, making it the natural place to start a persistent stream.

with-sse and patch-elements

On the server, with-sse opens the SSE stream and binds a generator object. patch-elements sends an HTML fragment to the browser. The :selector argument is a CSS selector for the target element; :mode :inner replaces its children (as we will see later, this mode is not the default, and we can omit it to use outer, the default.

(d*:with-sse (gen hunchentoot:*request*)
  (loop
    (d*:patch-elements gen
                       "<span>new content</span>"
                       :selector "#target"
                       :mode :inner)
    (sleep 1)))

Build the HTML string with Spinneret (or CL-WHO, or anything else) before passing it:

(d*:patch-elements gen
  (spinneret:with-html-string (:span "content"))
  :selector "#target"
  :mode :inner)

On the client side, data-init on any element starts the connection when that element initializes. d*:sse-get builds the @get(...) expression string:

(:div :id "target"
      :data-init (d*:sse-get "/sse-endpoint")
      "waiting...")

Live Demo

A clock updated from the server every second via SSE. No client-side timer code; the loop runs entirely on the server.

Server clock:

waiting...

Full Source (standalone Hunchentoot)

A self-contained program. Load with sbcl --load demo-sse.lisp and visit http://localhost:8989.

;;;; -*- Mode: LISP; fill-column: 80; coding: utf-8 -*-

;;;; DEMO-SSE.LISP --- SSE streaming example
;;;;
;;;; Copyright (C) 2025, 2026 Frederico Muñoz / ΛↃ lambda combine
;;;;
;;;; This file is part of datastar-cl, the Common Lisp SDK for Datastar
;;;;
;;;; License: MIT

(ql:quickload '(:hunchentoot :spinneret :datastar-cl/hunchentoot))

(defpackage #:clock
  (:use #:cl #:hunchentoot)
  (:local-nicknames (:sp :spinneret) (:d* :datastar-cl)))
(in-package #:clock)

(hunchentoot:define-easy-handler
 (index :uri "/") ()
 (setf (hunchentoot:content-type*) "text/html")
 (sp:with-html-string
  (:doctype)
  (:html (:head (:script :type "module" :src (d*:datastar-url)))
         (:body :data-init (d*:sse-get "/sse")
                (:h1 "Datastar-CL SSE Demo")
                (:div :id "clock" "Waiting for time...")))))

(hunchentoot:define-easy-handler
 (sse-handler :uri "/sse") ()
 (d*:with-sse (gen hunchentoot:*request*)
   (loop
     (multiple-value-bind (s m h) (decode-universal-time (get-universal-time))
       (d*:patch-elements gen
                          (format nil "<span>~2,'0d:~2,'0d:~2,'0d</span>" h m s)
                          :selector "#clock"
                          :mode :inner))
     (sleep 1))))

(hunchentoot:start (make-instance 'hunchentoot:easy-acceptor :port 8989))
(format t "~&Server started on http://localhost:8989~%")

(defroute guide-sse (:get :text/html))