Created
April 10, 2025 11:18
-
-
Save Ramblurr/23fa7d6fc215fd4b63d3ba1a5397155c to your computer and use it in GitHub Desktop.
a quicky example of per-tab session state with datastar when using hyperlith style page rendering
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;; a quicky example of per-tab session state with datastar | |
;; when using hyperlith style page rendering | |
(:require | |
[clojure.core.async :as a] | |
[chime.core :as chime]) | |
(def !page-state (atom {})) | |
(defn state-transact! | |
"Helper function to be used by your command/action handlers. | |
Example: | |
(d*/state-transact! req #(assoc-in % [:some-state-flag] true)) | |
" | |
[req f] | |
(if-let [tab-id (-> req :body-params :tab-id)] | |
(swap! !page-state update tab-id (fn [state] | |
(-> state | |
(f) | |
(assoc ::modified (System/currentTimeMillis))))) | |
(throw (ex-info "No tab-id in request" {})))) | |
(defn init-tab-state! | |
"Initializes a new tab state session. <ch is a channel that will be written to when the tab state changes." | |
[<ch tab-id] | |
(swap! !page-state assoc tab-id {::created (System/currentTimeMillis)}) | |
(add-watch !page-state tab-id (fn [watch-key _ _ _] | |
(when-not (a/>!! <ch :state-changed) | |
(remove-watch !page-state watch-key))))) | |
(defn remove-tab-state! | |
"Cleans up a tab state session" | |
[tab-id] | |
(swap! !page-state dissoc tab-id) | |
(remove-watch !page-state tab-id)) | |
;; Tab sessions older than this are pruned | |
(def STALE-THRESHOLD-HOURS 12) | |
(defn stale? [now created modified] | |
(> (- now (or modified created)) (* STALE-THRESHOLD-HOURS 3600000))) | |
(defn clean-stale-page-state | |
"Removes tab-ids that are stale, where stale is defined as not having been modified or created in the last 24 hours." | |
[page-state] | |
(let [now (System/currentTimeMillis)] | |
(reduce-kv (fn [acc tab-id {:keys [::created ::modified]}] | |
(if (stale? now created modified) | |
(dissoc acc tab-id) | |
acc)) | |
page-state | |
page-state))) | |
(defn clean-stale-watches! | |
"Removes watches for tab-ids that are no longer in the page state." | |
[] | |
(let [watches (-> !page-state .getWatches keys) | |
stale-watches (remove #(get @!page-state %) watches)] | |
(doseq [watch-key stale-watches] | |
(remove-watch !page-state watch-key)))) | |
(defn start-clean-page-state-job | |
"Starts a job that cleans stale page state every 10 seconds." | |
[] | |
(chime/chime-at (chime/periodic-seq (Instant/now) (Duration/ofSeconds 60)) | |
(fn [_] | |
(swap! !page-state clean-stale-page-state) | |
(clean-stale-watches!)))) | |
(comment | |
;; inside render handler | |
;; create your tab-id | |
(str (random-uuid)) | |
;; in hk on-open call | |
;; <ch is the tapped channel | |
(init-tab-state! <ch tab-id) | |
;; in hk on-close cleanup | |
(remove-tab-state! tab-id) | |
) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment