releases

mpenet 2026-02-17T08:15:38.629219Z

New Hirundo release - Adds SSE api (with brotli support) https://github.com/mpenet/hirundo?tab=readme-ov-file#sse-server-sent-events. It also includes a small demo of usage with Datastar https://github.com/mpenet/hirundo/tree/main/demo

🎉 3
lispyclouds 2026-02-18T09:23:10.780869Z

Thanks a lot for this, looks neat! Im trying to change this code here: https://github.com/bob-cd/bob/blob/682423a48524a595310fddbc2b38c7093bd495cd/apiserver/src/apiserver/handlers.clj#L388 to use the new features like so:

(defn events
  [{{:keys [^Environment env name]} :stream :as req}]
  (let [{:keys [input-ch close-ch]} (sse/stream! req)
        consumer (.. env
                     consumerBuilder
                     (stream name)
                     (offset (OffsetSpecification/first))
                     (messageHandler
                      (reify MessageHandler
                        (handle [_ _ message]
                          (async/>!! input-ch {:data [(String/new (.getBodyAsBinary message))]}))))
                     build)]
    (async/<!! close-ch) ; To hold on til client disconnect
    (.close consumer)))
works fine when i test with curl: curl -H "Accept:text/event-stream" But when i stop the client i see this exception:
Feb 18, 2026 9:18:25 AM io.helidon.common.socket.SocketContext log                                                     WARNING: [0x3ffa0375 0x6395bc02] Request failed: HttpPrologue[protocol=HTTP, protocolVersion=1.1, method=GET, uriPath=/events, query=, fragment=], cannot send error response, as response already sent
io.helidon.http.RequestException: OutputStream already obtained                                                                at io.helidon.http.RequestException$Builder.build(RequestException.java:157)
        at io.helidon.webserver.http.ErrorHandlers.unhandledError(ErrorHandlers.java:205)
        at io.helidon.webserver.http.ErrorHandlers.lambda$handleError$1(ErrorHandlers.java:180)                                at java.base/java.util.Optional.ifPresentOrElse(Optional.java:198)
        at io.helidon.webserver.http.ErrorHandlers.handleError(ErrorHandlers.java:179)
        at io.helidon.webserver.http.ErrorHandlers.runWithErrorHandling(ErrorHandlers.java:113)
        at io.helidon.webserver.http.Filters.filter(Filters.java:83)
        at io.helidon.webserver.http.HttpRoutingImpl.route(HttpRoutingImpl.java:74)
        at io.helidon.webserver.http1.Http1Connection.route(Http1Connection.java:548)
        at io.helidon.webserver.http1.Http1Connection.handle(Http1Connection.java:216)
        at io.helidon.webserver.ConnectionHandler.run(ConnectionHandler.java:190)
        at io.helidon.common.task.InterruptableTask.call(InterruptableTask.java:47)
        at io.helidon.webserver.ThreadPerTaskExecutor$ThreadBoundFuture.run(ThreadPerTaskExecutor.java:240)
        at java.base/java.lang.VirtualThread.run(VirtualThread.java:456)
Caused by: java.lang.IllegalStateException: OutputStream already obtained
        at io.helidon.webserver.http1.Http1ServerResponse.outputStream(Http1ServerResponse.java:445)
        at io.helidon.webserver.http1.Http1ServerResponse.outputStream(Http1ServerResponse.java:234)
        at s_exp.hirundo.http.response$write_body_BANG_.invokeStatic(response.clj:12)
        at s_exp.hirundo.http.response$write_body_BANG_.invoke(response.clj:10)
        at s_exp.hirundo.http.response$set_response_BANG_.invokeStatic(response.clj:37)
        at s_exp.hirundo.http.response$set_response_BANG_.invoke(response.clj:32)
        at s_exp.hirundo.http.routing$set_ring1_handler_BANG_$reify__31804.handle(routing.clj:23)
        at io.helidon.webserver.http.HttpRoutingImpl$RoutingExecutor.doRoute(HttpRoutingImpl.java:174)
        at io.helidon.webserver.http.HttpRoutingImpl$RoutingExecutor.call(HttpRoutingImpl.java:132)
        at io.helidon.webserver.http.HttpRoutingImpl$RoutingExecutor.call(HttpRoutingImpl.java:110)
        at io.helidon.webserver.http.ErrorHandlers.runWithErrorHandling(ErrorHandlers.java:76)
        ... 8 more

mpenet 2026-02-18T09:23:40.455639Z

you need to return nil from your handler

lispyclouds 2026-02-18T09:23:46.045379Z

ah

mpenet 2026-02-18T09:24:00.435759Z

I need to fix that one, but currently that's how it works

lispyclouds 2026-02-18T09:25:05.053509Z

still see it with this:

(defn events
  [{{:keys [^Environment env name]} :stream :as req}]
  (let [{:keys [input-ch close-ch]} (sse/stream! req)
        consumer (.. env
                     consumerBuilder
                     (stream name)
                     (offset (OffsetSpecification/first))
                     (messageHandler
                      (reify MessageHandler
                        (handle [_ _ message]
                          (async/>!! input-ch {:data [(String/new (.getBodyAsBinary message))]}))))
                     build)]
    (async/<!! close-ch) ; To hold on til client disconnect
    (.close consumer)
    nil))

lispyclouds 2026-02-18T09:26:55.911389Z

it all works fine from the client perspective though, just that stacktrace on the server logs

mpenet 2026-02-18T09:27:56.956819Z

hmm it should just work. Could you provide a more minimal repro?

lispyclouds 2026-02-18T09:28:06.962749Z

trying

lispyclouds 2026-02-18T09:28:49.164869Z

is the way to wait for client disconnect correct? the read from the close-chan?

lispyclouds 2026-02-18T09:29:42.199909Z

not sure if im doing that correctly, something i thought of, didnt see it in the examples. this is an endless stream til the client stops

mpenet 2026-02-18T09:47:19.367819Z

yes, the input-ch also closes on disconnects, on-close will allow you to close a bit earlier potentially

👍🏾 1
lispyclouds 2026-02-18T09:49:12.027919Z

will try to make a minimal repro soon

mpenet 2026-02-18T09:49:32.061139Z

Thanks. I can look into it later also, I am at work now

lispyclouds 2026-02-18T09:49:48.171439Z

same suffering here

mpenet 2026-02-18T11:44:54.188809Z

I just released a new version: it makes the return value of stream! Closeable, that should be a bit safer (if your handler exited too early you could have errors previously)

mpenet 2026-02-18T11:45:29.542209Z

you can do (with-open [s (stream! ...)] (do-stuff (:input-ch s)))

mpenet 2026-02-18T11:46:11.905169Z

the with-open scope would block until processing ends (until response lifecycle is done, either client disconnection or input-ch closed, or error)

mpenet 2026-02-17T08:16:35.061139Z

It's subject to changes, hence the alpha tag, but it works nicely.

lispyclouds 2026-02-19T08:19:33.300139Z

works perfectly fine in all aspects for this simple reitit thing:

(ns main
  (:require
   [clojure.core.async :as async]
   [muuntaja.core :as m]
   [reitit.coercion.malli :as malli]
   [reitit.http :as http]
   [reitit.http.coercion :as coercion]
   [reitit.http.interceptors.exception :as exception]
   [reitit.http.interceptors.muuntaja :as muuntaja]
   [reitit.http.interceptors.parameters :as parameters]
   [reitit.interceptor.sieppari :as sieppari]
   [reitit.ring :as ring]
   [s-exp.hirundo :as srv]
   [s-exp.hirundo.sse :as sse])
  (:gen-class))

(defn events
  [req]
  (with-open [stream (sse/stream! req)]
    (loop [m 1]
      (async/>!! (:input-ch stream) {:data [m]})
      (Thread/sleep 1000)
      (recur (inc m)))
    (async/<!! (:close-ch stream))))

(def routes
  [["/events" {:get events}]])

(def server
  (http/ring-handler
   (http/router
    routes
    {:data {:coercion malli/coercion
            :muuntaja m/instance
            :interceptors [(parameters/parameters-interceptor)
                           (muuntaja/format-negotiate-interceptor)
                           (muuntaja/format-response-interceptor)
                           (exception/exception-interceptor)
                           (muuntaja/format-request-interceptor)
                           (coercion/coerce-exceptions-interceptor)
                           (coercion/coerce-response-interceptor)
                           (coercion/coerce-request-interceptor)]}})
   (ring/routes
    (ring/create-default-handler
     {:not-found (constantly {:status 404
                              :headers {"Content-Type" "application/json"}
                              :body "{\"message\": \"Took a wrong turn?\"}"})}))
   {:executor sieppari/executor}))

(defn -main
  []
  (srv/start! {:http-handler server
               :host "0.0.0.0"
               :port 7777}))

(comment
  (set! *warn-on-reflection* true)

  (def s
    (srv/start! (var server)
                {:host "0.0.0.0"
                 :port 7777}))

  (srv/stop! s))
but somehow i still see the issue in the bob codebase. will try to dig deeper, maybe something with the rabbit producer

lispyclouds 2026-02-19T08:20:48.024539Z

was thinking if reitit was involved somehow but no

mpenet 2026-02-19T08:20:49.974429Z

strange, maybe one of the interceptors you use in that app tries to use the outputstream before the sse stream can start

lispyclouds 2026-02-19T08:21:17.737919Z

i think its the same set of interceptors, lemme double check

mpenet 2026-02-19T08:21:27.414099Z

or maybe it sets some header or something

lispyclouds 2026-02-19T08:32:13.731739Z

something with the rabbitmq thing, when i swap in the simple events generator as above, not issues. will report back later if ifind something

lispyclouds 2026-02-19T08:33:35.147559Z

not seeing it in the stacktrace is also weird

whilo 2026-02-17T09:02:39.066809Z

https://github.com/replikativ/briefkasten — local IMAP mirror with datahike, scriptum & yggdrasil 📬 We just open-sourced briefkasten, a Clojure library that syncs your IMAP mailbox to a local store combining: - datahike for structured metadata (datalog queries over subjects, dates, flags, threading headers) - scriptum for fulltext search (Lucene-backed, CoW snapshots) - yggdrasil for atomic composite versioning across both systems Useful for knowledge management, enterprise email tooling, and LLM agent pipelines that need structured access to email archives. Key features: - Incremental sync with UID diffing and batch fetching - Joint datalog queries across metadata + fulltext + raw .eml files - Time-travel via yggdrasil composite snapshots (persistent across restarts) - Structured logging via trove — plug in your own backend

[org.replikativ/briefkasten "0.1.2"]
Early beta — feedback welcome!

3
🎉 4
Ludger Solbach 2026-02-17T09:39:41.947129Z

https://github.com/lsolbach/qclojure-braket https://github.com/lsolbach/qclojure-braket/releases/tag/v0.3.0: AWS Braket backend for QClojure, lets you run quantum algorithms on real quantum computers. experimental Changes: • enhanced multi device handling • enhanced job/task result handling • rewritten pricing and cost estimation • updated dependencies • tested with IonQ Forte QPU and SV1 simulator

🎉 2
pez 2026-02-17T09:59:47.548299Z

Daniel Slutsky 2026-02-17T15:28:04.234959Z

https://scicloj.github.io/clay/: REPL-friendly literate programming and data visualization - version https://clojars.org/org.scicloj/clay/versions/2.0.11: • kind/test-last now supports passing a bare function (e.g., (kind/test-last pos?)) in addition to the existing vector form (`(kind/test-last [pos?])`)

🎉 2
pez 2026-02-17T16:49:52.590119Z

Daniel Slutsky 2026-02-17T17:23:32.739419Z

https://scicloj.github.io/tableplot/ - easy layered graphics for Tablecloth datasets - Version https://clojars.org/org.scicloj/tableplot/versions/1-beta16 • fixed scaling of 2d histograms - thanks, @holyjak

❤️ 1
🎉 2
Daniel Slutsky 2026-02-17T20:14:43.676339Z

https://scicloj.github.io/noj - a data science toolkit - Version 2-beta21 (https://clojars.org/org.scicloj/noj/versions/2-beta21, https://github.com/scicloj/noj/releases/tag/2-beta21) with @carsten.behring: • split the uberjar into separate uberjars: for clojupyter and for clay • use scikit-learn = "1.8.0" in integration tests • document better "disabled tests" • upgraded clojure version in uberjar to 1.12.4 • support Arrow, Excel, Parquet files out of-the-box with tablecloth • updated versions: Tablecloth, Kindly, Tableplot, SCI, Clay • removed dependency: Hanami (included transitively with exclusions through http://metamorph.ml/) • removed dependency: Emmy-viewers (was never fully supported anyway)

🎉 3
2026-02-17T21:18:51.696179Z

https://git.nmm.ee/asko/ruuter, a zero-dependency router for Clojure, ClojureScript and Babashka (and now Jank!!): • Now runs on https://jank-lang.org/https://git.nmm.ee/asko/ruuter/src/branch/master/BENCHMARKS.md#jank-built-from-source-latest-main to include Jank (though notably they are pretty bad, which is to be expected for alpha software)

3
🎉 6
2026-02-17T21:45:10.940689Z

Minor update for cljd-video-player: https://github.com/ianffcs/cljd-video-player • fix some possible nil attributes

🎉 3
tony.kay 2026-02-17T22:08:24.287389Z

https://github.com/fulcrologic/fulcro-spec 3.2.8 - CLJC BDD Library (wrappers for clojure.test) Mainly bugfix release. Moving towards facilities to reify coverage into the tests themselves (transitive checks for coverage). The overall goals of recent releases are to make LLMs better able to reason (in a deterministic way) about test coverage and potential breaking changes.