Skip to content

Instantly share code, notes, and snippets.

@HassanYA
Last active April 16, 2025 11:30
Show Gist options
  • Save HassanYA/10a04080c3ef00131944797c313b7579 to your computer and use it in GitHub Desktop.
Save HassanYA/10a04080c3ef00131944797c313b7579 to your computer and use it in GitHub Desktop.
on-close datastar clj test
;; 8b849864109e7a5dd4da40b00269125837611ac2
(ns app.web.realtime
(:require [starfederation.datastar.clojure.adapter.http-kit :as hk-gen :refer [->sse-response]]
[starfederation.datastar.clojure.api :refer [console-log!]]
[tick.core :as t]
[chime.core :refer [chime-at periodic-seq]]))
(def !conn-user-map (atom {}))
(defn- assoc-user-conn
[conn-map k v]
(assoc conn-map k (conj (get conn-map k []) v)))
(defn- dissoc-user-conn
[conn-map k v]
(let [conns (get conn-map k [])
updated-conns (remove (partial = v) conns)]
(if (empty? updated-conns)
(dissoc conn-map k)
(assoc conn-map k updated-conns))))
(defn broadcast! [user-id]
(fn [f & args]
(for [sse-gen (get @!conn-user-map user-id [])]
(apply f sse-gen args))))
(defn connect-handler [{:keys [user] :as req}]
(->sse-response req
{hk-gen/on-open
(fn [sse-gen]
(swap! !conn-user-map assoc-user-conn (:db/id user) sse-gen))
hk-gen/on-close
(fn [sse-gen status]
(println "I am being unused")
(swap! !conn-user-map dissoc-user-conn (:db/id user) sse-gen))}))
(chime-at (rest
(periodic-seq (t/now) (t/new-duration 5 :seconds)))
(fn [dtime]
(println (str "Pinging Connections: " dtime))
#p (->> @!conn-user-map
vals
(reduce concat)
(mapv #(console-log! % "pinged")))))
@JeremS
Copy link

JeremS commented Apr 16, 2025

Hi,

I don't see any obvious mistake with your use of the SDK.

2 quick pointers:

  • For broadcasting I would use doseq instead of for here. Clojure's for is lazy and should be used for creating a new collection, not for side effects. Because of Clojure's lazy/chunked sequences you might not see the difference until you store to many connections and events don't go through for some of them. If you need the sequence of connections for some reason you can keep the for and do doall, vec or just use mapv.
  • I would use sets instead of vectors to store the connections, the removal would become a call to disj instead of scanning the whole vector with remove.

To test the code you could try setting up a simple endpoint using this and curl to see what happens. With http-kit, terminating the curl process is sufficient to get the on-close callback call. There is an example in the SDK for a simple broacast here and a simpler one testing with storing just one connection here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment