announcements

borkdude 2026-02-17T11:25:24.970889Z

https://github.com/babashka/babashka: Native, fast starting Clojure interpreter for scripting 1.12.215 (2026-02-17) One of the most exciting babashka releases thus far! Read the blog post for this release https://blog.michielborkent.nl/babashka-1.12.215.html. • https://github.com/babashka/babashka/issues/1909: add https://github.com/jline/jline3 for TUI support • Console REPL (`bb repl`) improvements: multi-line editing, tab completion, ghost text, eldoc, doc-at-point (`C-x` C-d), persistent history • Add keyword completions to nREPL and console REPL (`:foo`, :ns/foo, ::foo, ::alias/foo) • https://github.com/babashka/babashka/issues/1299: add new babashka.terminal namespace that exposes tty? with arguments :stdin, :stdout or :stderr • Compatibility with https://github.com/TimoKramer/charm.clj • Support deftype with map interfaces (e.g. IPersistentMap, ILookup, Associative). Libraries like https://github.com/clojure/core.cache and https://github.com/frankiesardo/linked now work in babashka. • Compatibility with https://github.com/clj-commons/riddley • Compatibility with https://github.com/cloverage/cloverage (pending https://github.com/cloverage/cloverage/pull/356) More changes in 🧵 !

8
🚀 1
2
🎉 36
4
borkdude 2026-02-18T11:08:10.038529Z

I needed to make changes to rebel's source code a bit, and added some classes to bb too, but now I have it working for the first time.

Filipe Silva 2026-02-18T11:09:45.673759Z

that's awesome! I ended up not looking at it yesterday with the bb support for sqlatom, but I did try the new bb repl and indeed found it very similar

borkdude 2026-02-18T11:14:42.330099Z

I still need to go through a lot of things and clean things up though

borkdude 2026-02-17T11:26:27.500319Z

• SCI: deftype now macroexpands to deftype*, matching JVM Clojure, enabling code walkers like riddley • SCI: case now macroexpands to JVM-compatible case* format, enabling tools like riddley and cloverage • SCI: macroexpand-1 now accepts an optional env map as first argument, enabling riddley compatibility • SCI: macroexpand-1 of (.method ClassName) now wraps class targets in identity, matching Clojure behavior • SCI: macroexpand-1 now expands (ClassName. args) to (new ClassName args), matching JVM Clojure • SCI: support functional interface adaptation for instance targets (e.g. (let [^Predicate p even?] (.test p 42))) • SCI: infer type tags from let binding values to binding names • SCI: fix .method on class objects routing to static instead of instance method path • SCI: fix read with nil or false as eof-value throwing instead of returning the eof-value • SCI: fix letfn with duplicate function names crashing with ClassCastException • SCI: fix ns-map not reflecting vars that shadow referred vars • SCI: preserve :tag metadata in copy-var • SCI: fix NPE in resolve when :outer-idens is nil • Support multiple catch i.c.m. ^:sci/error • Fix satisfies? on protocol on proxyhttps://github.com/babashka/babashka/issues/1923: support reify with java.time.temporal.TemporalQuery • Fix reify with methods returning int/`short`/`byte`/`float` when Clojure fn returns long/`double` • Fix https://github.com/babashka/babashka.nrepl/issues/71: nREPL server now uses non-daemon threads so the process stays alive without @(promise) • Add clojure.test.junit as built-in source namespace • Add JLine reify support: Parser, Completer, Highlighter, Widget, ParsedLine • Add JLine classes for rebel-readline compatibility: LineReader$Option, Attributes$InputFlag, Attributes$LocalFlag • Add cp437 (IBM437) charset support in native binary via selective GraalVM charset Feature (see https://github.com/babashka/babashka/blob/master/doc/adr/0006-selective-charset-support/decision.md) • Add java.lang.ref.SoftReference • Add java.lang.reflect.Field methods: setAccessible, get, sethttps://github.com/babashka/babashka/issues/1919: add java.nio.file.attribute.UserPrincipal and GroupPrincipalhttps://github.com/babashka/babashka/issues/1920: add java.nio.file.FileSystemNotFoundException • Bump deps.clj • Bump fs • Bump transit-clj to 1.1.347 • Bump Selmer to 1.13.1

📜 1
Dustin Getz (Hyperfiddle) 2026-02-17T13:11:36.926169Z

exciting release @borkdude!

🙏 1
2026-02-17T13:18:51.550529Z

congrats on figuring out deftype! that's really big

borkdude 2026-02-17T13:32:08.236249Z

deftype is still restricted to a few selected patterns, like creating custom map types. it's not fully supported in general

Filipe Silva 2026-02-17T15:26:51.481299Z

whoa the jline include might mean https://github.com/bhauman/rebel-readline now works in bb

borkdude 2026-02-17T15:29:07.628309Z

@filipematossilva almost. there are few things I need to address for this. you can find more about that here: https://github.com/babashka/babashka/blob/9b70c1fe672898171b19c84f5ad59edc549b04f1/doc/adr/0001-jline-providers/decision.md#rebel-readline-compatibility the current console repl of bb itself looks a lot like rebel now though

Filipe Silva 2026-02-17T15:30:27.598329Z

oh I have to try it out then, I had to do some really gnarly process wrangling to get rebel-readline working on invoker for spawned babashka processes

Filipe Silva 2026-02-17T15:30:44.618859Z

does the bb console repl also work in clj?

borkdude 2026-02-17T15:31:13.009219Z

I think I could make it work ;)

borkdude 2026-02-17T15:31:33.550949Z

but then it would be a library which probably would look a lot like rebel

Filipe Silva 2026-02-17T15:32:28.120769Z

what's stopping it from working right now though?

borkdude 2026-02-17T15:32:30.412679Z

what gnarly thing did you have to do to get rebel-readline working for spawned bb processes. shell should imo work out of the box and you could also uses exec to hand of process execution to rebel

borkdude 2026-02-17T15:32:42.502089Z

I just posted a link with the details?

Filipe Silva 2026-02-17T15:33:13.396919Z

no sorry, I meant "what's stopping the bb console repl from working in clj"

Filipe Silva 2026-02-17T15:33:27.346709Z

at least I thought the link was about rebel not working yet

borkdude 2026-02-17T15:33:35.614259Z

it's just being hard-couped to bb's console REPL stuff

borkdude 2026-02-17T15:33:45.149159Z

and SCI stuff probably

Filipe Silva 2026-02-17T15:33:52.363459Z

got it

borkdude 2026-02-17T15:34:43.638869Z

https://github.com/babashka/babashka/blob/master/src/babashka/impl/repl.clj lots of "impl" there. I could extract this out into a library but the autocompletion is also SCI specific here

Filipe Silva 2026-02-17T15:35:03.077159Z

the gnarly stuff I did with rebel had to do with how invoker will spawn either a bb or clj process with a nrepl server

Filipe Silva 2026-02-17T15:35:24.433979Z

but rebel is clj only, and wants to own the parent process

Filipe Silva 2026-02-17T15:35:29.969349Z

I think due to how jline works

Filipe Silva 2026-02-17T15:35:51.635059Z

ideally I'd be able to spawn a bb process with a nrepl server, and spawn a clj process with rebel connected to the other one

Filipe Silva 2026-02-17T15:36:52.959119Z

but instead I needed to replace the parent process with the clj process using exec, then spawn a bb process from within the rebel one

Filipe Silva 2026-02-17T15:37:11.148639Z

I also had to do some weird stuff to support ctrl+c/ctrl+d

Filipe Silva 2026-02-17T15:37:44.318969Z

rebel does stuff with them, but by default they are also sent to child processes automatically

borkdude 2026-02-17T15:38:15.274189Z

I think I can make rebel work with bb

borkdude 2026-02-17T15:38:19.545709Z

from source

borkdude 2026-02-17T15:38:33.914889Z

I just need to put in some extra support for proxy-super in SCI

borkdude 2026-02-17T15:38:57.934259Z

and rebel should not use some .impl classes in the non-dev code where it can

Filipe Silva 2026-02-17T15:39:22.329319Z

is either of those things something I could help with?

Filipe Silva 2026-02-17T15:39:52.891079Z

on the rebel side I could make a PR that removes some .impl classes

Filipe Silva 2026-02-17T15:47:46.615079Z

also just wanted to mention how delightful it is that you went the extra mile to make charm work, I was looking at it a while ago and was sad to see it used jline and thus wouldn't work in bb, but now... 😄

borkdude 2026-02-17T15:56:43.111329Z

yeah on the rebel side help to avoid those impl classes that the link mentions would be good :)

🙏 1
borkdude 2026-02-17T15:57:27.398439Z

So just: • org.jline.reader.impl.BufferImplorg.jline.terminal.impl.DumbTerminal

borkdude 2026-02-17T15:57:35.006949Z

the document gives hints how to avoid those

Filipe Silva 2026-02-17T16:09:54.152669Z

roger, will take a look and keep you posted

borkdude 2026-02-17T16:10:38.407299Z

(= "dumb" (.getType...)) is one way to get rid of the DumbTerminal

borkdude 2026-02-17T16:10:44.740409Z

BufferImpl is only used in dev mode

borkdude 2026-02-17T16:11:41.712589Z

I'm also looking with Claude at it. It seems the impl.DefaultParser can also be avoided

borkdude 2026-02-17T16:14:14.953529Z

see https://github.com/borkdude/rebel-readline/tree/babashka

borkdude 2026-02-17T16:14:28.582469Z

tests still work with these changes

borkdude 2026-02-17T16:54:39.597809Z

@filipematossilva boy, adding support for proxy-super was super easy, I didn't realize that

borkdude 2026-02-17T17:06:46.566749Z

ooops, spoke too soon ;)

Filipe Silva 2026-02-17T17:49:55.796189Z

you jinxed it 😄

Filipe Silva 2026-02-17T16:14:27.825129Z

https://github.com/filipesilva/sqlatom v1 is out 🎉 sqlatom is a Clojure and Babashka library that stores atoms in a SQLite database:

(ns app
  (:require [filipesilva.sqlatom :as sqlatom]))

(defonce state (sqlatom/atom :state {}))
This will create a sqlatom/atoms.db in the project root if there isn't one yet, then initialize :state as {} if there is no value for it yet, or read the existing value for :state. All atom operations are supported, with the following semantics: - swap!, compare-and-set!, swap-vals! have transaction semantics and are safe to use between atoms/threads/processes - deref will read from the database if the value has been updated since last read - add-watch watchers see updates from other atoms only when reading/updating, and will not be called for unseen updates Values are stored as edn, and use the readers for the current process.

🆒 6
👀 1
🎉 19
borkdude 2026-02-18T08:47:01.704789Z

wait you have bb support already? ;)

Filipe Silva 2026-02-18T09:22:52.159209Z

I can do IAtom2 with reify, but can't do the other two so in bb these don't work: add-watch/remove-watch/set-validator!/get-validator/meta/alter-meta!/reset-meta!

borkdude 2026-02-18T09:23:21.326799Z

how important are these for the core features in your lib?

Filipe Silva 2026-02-18T09:25:07.566399Z

The validator can still be set with an option, I don't think the atom metadata is very used, but there's no replacement for watchers.

Filipe Silva 2026-02-18T09:25:41.968979Z

I guess polling it

Filipe Silva 2026-02-18T09:26:37.230469Z

Guess is could add an option to add the watcher when creating the atom too

borkdude 2026-02-18T09:27:01.362809Z

I guess we could add support for the general "custom atom" story in deftype like I did for the "custom map" story

borkdude 2026-02-18T09:27:15.331559Z

assuming you are using deftype

Filipe Silva 2026-02-18T09:28:12.343869Z

I'm not, I think at some point I tried to but had problems with 2 interfaces implementing Meta or something

Filipe Silva 2026-02-18T09:29:01.703559Z

But it's no problem to use a different mechanism for bb and another for clj, doing that already with reify/proxy

borkdude 2026-02-18T09:29:40.402469Z

can you link me to the clojure code that uses deftype

Filipe Silva 2026-02-18T09:30:30.346189Z

Sure, gimme a sec not at a computer atm

borkdude 2026-02-18T09:31:00.355929Z

no hurry

Filipe Silva 2026-02-18T10:18:33.285949Z

can't repro whatever issue I had with meta anymore, here's what a full custom atom impl with deftype in clojure looks like

borkdude 2026-02-18T10:20:22.921879Z

can you attach this file to the issue and rename the issue to something like "implementing custom atoms in babashka" or so

Filipe Silva 2026-02-18T10:20:40.791639Z

roger, will make it deftest as well if you wanna use them

Filipe Silva 2026-02-18T11:53:02.464719Z

@john wrt perf, added a perf test that resets! and swap! a 20mb edn datascript backup:

filipesilva@m4 ~/r/p/sqlatom (master) [1]> clj test/performance_test.clj
WARNING: Implicit use of clojure.main with options is deprecated, use -M test/performance_test.clj
Reading test/roam-book-club-2026-02-18-11-31-58.edn ...
  File size: 20.3 MB
  EDN parse: 456 ms
  Top-level keys: (:schema :datoms)

reset!: 314 ms
swap!:  649 ms
bb fails though:
----- Error --------------------------------------------------------------------
Type:     java.lang.RuntimeException
Message:  com.fasterxml.jackson.core.exc.StreamConstraintsException: String value length (20051112) exceeds the maximum allowed (20000000, from `StreamReadConstraints.getMaxStringLength()`)
Location: /Users/filipesilva/repos/personal/sqlatom/src/filipesilva/sqlatom.cljc:59:3

----- Context ------------------------------------------------------------------
55:              (set-params! stmt (rest sql-params))
56:              (.executeUpdate stmt))))
57:
58: (defn- sql-query [conn sql-params]
59:   #?(:bb  (sqlite/query conn sql-params)
      ^--- com.fasterxml.jackson.core.exc.StreamConstraintsException: String value length (20051112) exceeds the maximum allowed (20000000, from `StreamReadConstraints.getMaxStringLength()`)
60:      :clj (with-open [stmt (.prepareStatement conn (first sql-params))]
61:              (set-params! stmt (rest sql-params))
62:              (with-open [rs (.executeQuery stmt)]
63:                (resultset->maps rs)))))
64:
This looks like a limit somewhere in the sqlite pod, or maybe on the pod communication mechanism. Could probably do something about it. The limit seems to be 20mb, and this file is a bit larger. I don't think it's super important though, 20mb is a reasonable limit in bb for a atom. But at any rate, 20mb atom operations in a fraction of a second looks pretty good.

👀 1
borkdude 2026-02-18T12:03:36.363349Z

It's a limit set by Jackson. You can find this in Cheshire github issues

👍 2
Filipe Silva 2026-02-18T12:05:26.909359Z

got it https://github.com/dakrone/cheshire/issues/210

Filipe Silva 2026-02-18T12:09:47.851039Z

looks like I can change the limit, I guess I'll... 10x them

Filipe Silva 2026-02-18T12:23:09.294529Z

hm tried setting the factory with custom limits but it didn't seem to work

67: (defn- sql-query [conn sql-params]
68:   #?(:bb  (binding [factory/*json-factory* pod-factory]
69:             (sqlite/query conn sql-params))
                ^--- com.fasterxml.jackson.core.exc.StreamConstraintsException: String value length (20051112) exceeds the maximum allowed (20000000, from `StreamReadConstraints.getMaxStringLength()`)
70:      :clj (with-open [stmt (.prepareStatement conn (first sql-params))]
71:              (set-params! stmt (rest sql-params))
72:              (with-open [rs (.executeQuery stmt)]
73:                (resultset->maps rs)))))
74:

----- Stack trace --------------------------------------------------------------
com.cognitect.transit.impl.ReaderFactory/ReaderImpl - <built-in>
cognitect.transit/read                              - <built-in>
babashka.pods.impl/transit-json-read                - <built-in>
babashka.pods.impl/processor/fn--31617              - <built-in>
babashka.pods.impl/processor/fn--31646              - <built-in>
At any rate I don't think it's particularly important, will just note and point to it. If it matters for someone I can try again later.

borkdude 2026-02-18T14:27:44.515139Z

@filipematossilva The JSON limit you're hitting here is with the transit communication back and forth between bb and the pod

🙏 1
Casey 2026-03-12T14:10:54.009169Z

I think it would be very interesting to swap out the JDBC usage with @andersmurphy native https://github.com/andersmurphy/sqlite4clj and see if there is a substantial perf difference

Casey 2026-03-12T14:12:00.061069Z

.. though since it uses coffi (ffi/ffm) I suppose it wouldn't work in bb 😞

borkdude 2026-03-12T14:21:33.329529Z

you can do different things for bb and clojure

Filipe Silva 2026-03-12T14:55:03.928439Z

yeah, already have different codepaths for clj and bb

Filipe Silva 2026-03-12T14:55:26.982159Z

I think most of the perf here is actually edn serialization, but haven't measured

2026-02-17T16:21:25.958609Z

this is incredibly cool

Filipe Silva 2026-02-17T16:22:24.431029Z

we already had https://github.com/jimpil/duratom, but as far as I can tell it was always intended to work on a single clj process, not multiple

Filipe Silva 2026-02-17T16:24:26.563999Z

the idea for this actually came when I was reading through the https://github.com/mtgred/netrunner/blob/5653c24ce6f9cc5304c3746ccd9c3e6320b5a73f/src/clj/web/app_state.clj web.app-state namespace and thought about how so much was being done with just those atoms

👀 1
2026-02-17T16:37:19.000339Z

hah i based all of that on re-frame, and didn't feel like putting in the effort to keep it consistent with mongodb. this is a much better solution

Filipe Silva 2026-02-17T16:38:51.520709Z

it's a really neat way of doing things in clojure imho

john 2026-02-17T18:37:14.745469Z

This in bb would be potent

borkdude 2026-02-17T18:37:57.639549Z

we were just discussing adding sqlite support to bb, see #babashka in the thread about the announcement ;)

john 2026-02-17T18:41:18.683249Z

"Values stored as edn" I wonder what it'd look like to build clojure data structures directly over tables.

john 2026-02-17T18:41:36.682169Z

group-by could be super fast

Filipe Silva 2026-02-17T18:41:40.389389Z

Oh sweet! I was looking at the pod approach but it felt a bit hard, so I'm definitely interested in supporting bb

borkdude 2026-02-17T18:42:16.328839Z

take a look at the discussion, it's not that easy and won't happen soon. so pod it is for now

Filipe Silva 2026-02-17T18:47:09.707279Z

Yeah just finished reading, the zerofs thing also looks like it'd make it very hard to make work cross process

Filipe Silva 2026-02-17T19:05:39.680059Z

@borkdude what's the recommendation for bb libraries that need pods: should the consumer load the pod, or the library?

borkdude 2026-02-17T19:29:12.142849Z

library I'd say

👍 1
Ben Sless 2026-02-17T20:52:59.744749Z

Gives a new meaning to persistent data structures Now freeze REPL state, hmmm

2026-02-17T20:53:52.016499Z

i just started using it and it's real slick. might wanna call out that the data it holds has to be serializable (no sets, for example)

john 2026-02-17T20:53:57.614189Z

I've had this multi process duratom idea ruminating in my head for days now too

Filipe Silva 2026-02-17T21:31:02.844759Z

@nbtheduke sets should work:

filipesilva@m4 ~/r/s/alex-connections (master) [SIGINT]> clj
Clojure 1.12.3
user=> (require '[filipesilva.sqlatom :as sqlatom])
nil
user=> (defonce *atom (sqlatom/atom :sets nil))
#'user/*atom
user=> (reset! *atom #{1 2 3})
#{1 3 2}
user=> @*atom
#{1 3 2}
user=>
filipesilva@m4 ~/r/s/alex-connections (master) [SIGINT]> clj
user=> (require '[filipesilva.sqlatom :as sqlatom])
nil
user=> (defonce *atom (sqlatom/atom :sets nil))
#'user/*atom
user=> @*atom
#{1 3 2}

Filipe Silva 2026-02-17T21:32:08.067149Z

metadata works too

Filipe Silva 2026-02-17T21:32:40.258569Z

it stores a str printed with

(defn- pr-str-meta [v]
  (binding [*print-meta* true]
    (pr-str v)))
and reads with readers
(defn- read-edn [s]
  (edn/read-string {:readers *data-readers*} s))

2026-02-17T21:34:38.193539Z

huh, that didn't work for me, so i'm not sure what i was doing wrong

2026-02-17T21:34:41.495169Z

but thank you, that's cool

Filipe Silva 2026-02-17T21:36:19.487509Z

if you can repro let me know and I'll go fix it, the goal is for it to really be edn-level support

Filipe Silva 2026-02-17T21:41:28.952539Z

@borkdude I gave bb support a stab. The sqlite pod was no problem at all, but the proxy was. sqlatom currently https://github.com/filipesilva/sqlatom/blob/2481ab0a53de6177041535afc83979a8f17f644d/src/filipesilva/sqlatom.clj#L155 these:

(proxy [Object clojure.lang.IAtom2 clojure.lang.IRef clojure.lang.IReference] []
But in bb I can only proxy Object of those. I managed to reify clojure.lang.IAtom2, but bb only lets me reify one thing at a time, so the IRef and IReference bits (`add-watch`, validator, meta) won't work. The rest works though! I'll need to clean it up a bit but should be able to put out a bb version with that missing functionality.

borkdude 2026-02-17T22:11:19.077219Z

I may be able to support it, I'd have to look into it. issue welcome

Filipe Silva 2026-02-17T22:33:20.280369Z

@borkdude here it is https://github.com/babashka/babashka/issues/1931

Filipe Silva 2026-02-17T23:29:55.932139Z

v1.1.0 is out with babashka support:

## Babashka

The following operations are not supported in Babashka:
- `add-watch`, `remove-watch`
- `set-validator!`, `get-validator`
- `meta`, `alter-meta!`, `reset-meta!
`

Filipe Silva 2026-02-17T23:30:57.556409Z

but besides that, the atom stuff works, and you can even use the sqlatom to pass data between clj and bb processes, and the cross-process test now exercises this

john 2026-02-17T23:32:39.711949Z

Nice. So you could like quickly save some info to the db at the cli with an eval string. I wonder how fast that would be? Under a second?

Filipe Silva 2026-02-17T23:34:20.298749Z

filipesilva@m4 ~/r/p/sqlatom (master)> time bb test:cross-process
Testing 30 processes x 50 increments = 1500 expected
PASS: counter = 1500

________________________________________________________
Executed in    4.88 secs    fish           external
   usr time   28.97 secs    0.53 millis   28.97 secs
   sys time    2.66 secs    2.45 millis    2.66 secs
this is the cross-process test, each process does this:
(require '[filipesilva.sqlatom :as sqlatom])

(def id (random-uuid))

(let [[dir n-str] *command-line-args*
      n (parse-long n-str)
      a (sqlatom/atom :counter 0 :dir dir)]
  (dotimes [i n]
    (println id i)
    (swap! a inc)))

Filipe Silva 2026-02-17T23:34:59.292179Z

note that it was executed in 5s, so I expect it should be fast for what you asked

john 2026-02-17T23:35:45.778859Z

How long does it take 1 proc to assoc a small map?

john 2026-02-17T23:36:10.401189Z

(if you don't mind me asking - I can download it and try as well)

john 2026-02-17T23:37:17.931209Z

This seems useful in some agentic cli skills I want to work on.

john 2026-02-17T23:37:55.810299Z

I sometimes have an agent working in lots of super small bb scripts

Filipe Silva 2026-02-17T23:39:24.995279Z

(require '[filipesilva.sqlatom :as sqlatom])
(def a (sqlatom/atom ::a-map {}))
(println @a)
(swap! (sqlatom/atom ::a-map {}) assoc :foo (random-uuid))
(println @a)
.
filipesilva@m4 ~/r/p/sqlatom (master)> time bb test/cross_process_worker.clj
{:foo #uuid "0f673a78-7a6b-488a-8ab8-97c5a47e6d22"}
{:foo #uuid "ed907f5c-a980-4d2b-942f-345452cd8748"}

________________________________________________________
Executed in  117.09 millis    fish           external
   usr time   18.99 millis    0.48 millis   18.51 millis
   sys time   22.82 millis    2.37 millis   20.45 millis

🏁 1
john 2026-02-17T23:39:49.544739Z

bang bang!

2026-02-17T23:41:28.410649Z

i wonder if it can handle 38k clojure maps lol

john 2026-02-17T23:41:58.612229Z

ser/der might be problematic

john 2026-02-17T23:42:19.023549Z

if it's one giant blob

Filipe Silva 2026-02-17T23:43:43.209029Z

there's faster edn readers/writers than pr-str/`edn/read-string` , as long as they support data readers and metadata it should be fine to change

Filipe Silva 2026-02-17T23:43:55.213379Z

but yes, giant string

Filipe Silva 2026-02-17T23:44:43.462529Z

I'd be interested in optimizations if someone can repro it being slow, I imagine it's not that hard, it's just that premature optimization etc etc

john 2026-02-17T23:45:31.199469Z

Anything large is going to fall over with contention

john 2026-02-17T23:46:00.539749Z

Which is fine, if it's usually, mostly single user at a time

john 2026-02-17T23:47:53.854419Z

If you built clojure data structures over the tables, so structure was shared, that'd work

Filipe Silva 2026-02-17T23:48:34.861339Z

I don't doubt there's a threshold, but started getting a bit skeptical that these thresholds are as low as we expect... computers have gotten really really fast

john 2026-02-17T23:49:25.809779Z

Well, it's forcing a single point of serialization, right? Callers never actually have parallel access. Which can be fast still.

Filipe Silva 2026-02-17T23:49:39.561479Z

I think there's some optimizations to be done for the retries over large objects too, like not serializing them more than once etc

Filipe Silva 2026-02-17T23:49:54.998999Z

readers have parallel access, but single writer

john 2026-02-17T23:50:05.476569Z

right

john 2026-02-17T23:50:57.838959Z

Yeah, optimistic write and then try to move the root pointer. If it fails, try to see if your change was associative and graft it on the later snapshot

john 2026-02-17T23:51:39.104729Z

diff it

2026-02-17T23:52:38.645809Z

i'm at the point where i should just write some sql myself but who wants to do that lol

Filipe Silva 2026-02-17T23:52:43.146609Z

that sounds possible, and I remember someone that tried something similar over firebase, but I don't quite have the brain capacity for it, and suspect that at that size you want the query leverage of a real db

john 2026-02-17T23:52:54.540879Z

I think that ship has sailed

john 2026-02-17T23:52:58.020839Z

lol

Filipe Silva 2026-02-17T23:53:53.430029Z

@nbtheduke if you want to write some datalog instead, I also made a lib that makes it really easy to do datomic over sqlite

😍 1
Filipe Silva 2026-02-17T23:53:54.555179Z

https://github.com/filipesilva/datomic-pro-manager

Filipe Silva 2026-02-17T23:57:45.062479Z

you can even call that lib from within your app process, and it will download datomic, start it, etc, as shown in https://github.com/filipesilva/invoker#datomic

(ns app
  (:require
   [datomic.api :as d]
   [filipesilva.datomic-pro-manager :as dpm]))

(def db-uri "datomic:")
(defonce *conn (atom nil))

;; 
(def schema
  [,,,])

(defn start []
  (future (dpm/up))
  (dpm/wait-for-up)
  (d/create-database db-uri)
  (reset! *conn (d/connect db-uri))
  @(d/transact @*conn schema))

2026-02-17T23:58:38.429419Z

wow, you are a font of good libraries

Filipe Silva 2026-02-17T23:59:49.547059Z

I just think local fast dev is nifty!

Filipe Silva 2026-04-16T17:08:02.918189Z

was looking into it today - turns out it's local to the sqlite connection

Filipe Silva 2026-04-16T17:08:51.302869Z

see on this test, where they check that inserting into the second connection does not register on the first connections listener https://github.com/xerial/sqlite-jdbc/blob/6e61f29696c14d7d77be8eb971842a6c842629e0/src/test/java/org/sqlite/ListenerTest.java#L100-L136

Filipe Silva 2026-04-16T17:09:15.122399Z

so this wouldn't do anything for cross process watches, and in-process watches are covered by the atom itself

Filipe Silva 2026-04-15T08:14:58.656069Z

I expect the json serialization to lose some fidelity, like how right now metadata works with edn

Filipe Silva 2026-04-15T08:15:08.560179Z

that hook looks really interesting though!

Filipe Silva 2026-04-15T08:18:56.122859Z

I'll try implementing that

🙌 1
Felipe 2026-04-15T01:05:19.908549Z

I had a similar idea today so I got a big smile on my face when a slack search for sqlite atom returned this thread! some thoughts: I found out recently that SQLite supports JSONPath syntax and has a bunch of other cool JSON functions: https://sqlite.org/json1.html. I wonder how hard it would be to be able to store atom contents as JSON in SQLite to unlock these. of course having to think about how to serialize sets etc opens a can of worms re add-watch not reacting to database updates, it seems like SQLite exposes a hook you can use to listen to db changes: https://sqlite.org/c3ref/update_hook.html it seems to be supported by the Xerial driver. test example: https://github.com/xerial/sqlite-jdbc/blob/6e61f29696c14d7d77be8eb971842a6c842629e0/src/test/java/org/sqlite/ListenerTest.java#L47-L91

Felipe 2026-04-17T14:02:40.054389Z

ah mehhhh

🤣 1
Felipe 2026-04-17T14:49:46.182389Z

on the other idea, I found there's a library that tries to preserve data edn<->json round-trips: https://github.com/wilkerlucio/edn-json the source mentions two shortcomings:

- metadata is lost
- number keys on maps will be turned into strings on the conversion back
but it seems promising!

👀 1
2026-02-17T18:42:49.282209Z

https://git.nmm.ee/asko/ruuter, a zero-dependency router for Clojure, ClojureScript and Babashka, is out with big changes! 💥 • Best-match routing: Routes are now matched by specificity instead of first-match-wins. Literal segments beat parameters, parameters beat optionals, optionals beat wildcards. Route order in the vector no longer matters. • Segment trie: Routes are compiled into a trie (prefix tree) data structure for O(path-depth) matching instead of O(N) linear scan. This yields huge performance improvements depending on route count and match type. • compile-routes function: New public function for explicit route compilation. Routes are also compiled implicitly and cached via memoization when using routedirectly. • Single wildcard constraint: Wildcard parameters (`:name*`) must now be the last segment in a path. Multiple wildcards per path are no longer supported. • No regex: Route matching no longer uses regular expressions. Matching is done via direct string comparison of path segments against a trie. • deps.edn only: Leiningen (`project.clj`) has been retired. Performance improvements are huge: • In Clojure (JVM): ◦ Small route sets: 1.6–4.1x faster ◦ Medium route sets: 39–139x faster ◦ Large route sets: 162–345x faster • In ClojureScript (Node.js): ◦ Small route sets: 0.9–6.5x faster ◦ Medium route sets: 14–40x faster ◦ Large route sets: 38–167x faster • In Babashka: ◦ Small route sets: 2.0–6.4x faster ◦ Medium route sets: 11–32x faster ◦ Large route sets: 32–182x faster More info on benchmarks https://git.nmm.ee/asko/ruuter/src/branch/master/BENCHMARKS.md.

🚀 17
🎉 1
2026-02-27T20:24:14.508619Z

Yup that's a very good point! I checked and currently if you have conflicting routes, the behavior is pretty bad. It will match the first route, but the param will be of the last one. So it will match /api/users/:id but then the data you get is {:x "something"} . Definitely not good. I think I'll change the behavior in such a case that if there's multiple, it will always match first AND use the first matches param, but that it would log a warning during compile-time.

👀 1
roklenarcic 2026-02-27T20:25:30.352819Z

You could just throw ambiguous route exception let the user know

2026-02-27T20:27:46.119609Z

I thought about that, but I like software that keeps on working, and tries to self-heal if it can. In this particular case there is an option for a continuing-to-work path.

roklenarcic 2026-02-27T20:36:48.227999Z

Well imagine you have a PR for your server that introduces an ambiguous path that is same as existing path. Would you want tests to fail with an exception or would you want tests to pass with a logged warning?

2026-02-27T21:08:05.542879Z

Good point, didn't think from that perspective

2026-02-17T18:48:34.442349Z

While the user-facing API is exactly the same as before, I've still tagged it as a breaking change (2.0) because the behavior changes drastically, but just so you know that upgrading to 2.0, in the best case, should not need any changes from you the user.

❤️ 1
2026-02-17T18:54:07.625599Z

this looks cool. what made you want to build this vs use something like reitit?

2026-02-17T18:57:39.318889Z

I wanted something small and that I could use in any clojure-native environment, which basically meant that I needed something that does not rely on any interop to the host language. At the time (4 years ago) I could not find any router that could do that, so I made my own. I'm not sure if there are other routers now that do run on different Clojure runtimes or not, maybe there are, but that's why I made this one. I do have a pending task to get this to run on Jank as well. Last time I tried I couldn't get tests to work, but with Jank being in alpha it might just be time to try again.

👍 1
2026-02-17T21:24:56.546079Z

I just tagged 2.1.0 which now also runs on Jank. Thus, Ruuter is now a 4-runtime Router!

🎉 13
roklenarcic 2026-02-26T09:27:34.800239Z

Perhaps add checks that user didn’t specify multiple conflicting routes, e.g. /api/users/:id and /api/users/:x . only one of those will be in the resulting tree and with large number of routes this can lead to silent failures (i.e. a handler no longer being resolvable).

roklenarcic 2026-02-26T09:29:52.231759Z

same thing with optional segments and wildcards

roklenarcic 2026-02-26T09:32:36.473959Z

optional segments is an interesting concept, I’ve haven’t seen it in other routing libraries

tony.kay 2026-02-17T22:03:23.727279Z

https://github.com/fulcrologic/statecharts 1.3.0 - CLJC statecharts conforming to the W3C standard. A big release for Fulcrologic statecharts. The big new feature is a plug-in engine that enables you to park statecharts on async operations. This is aimed at making charts much simpler for CLJS (CLJ already had the ability to “block” in executable content). The list of notable changes are: • Chart elements: If, else, elseif, foreach. • Async “parking” support (with expanded Fulcro operations, if you use Fulcro) • (alpha) Fulcro integration support for composing an application as a chart (routes as states)

🎉 12
tony.kay 2026-02-17T22:15:37.137929Z

https://github.com/fulcrologic/fulcro 3.9.3 - Data-Driven Full-Stack Applications Recent work in Fulcro has been aimed at fast verification via LLMs. To that end the entire ecosystem is being refined to ensure that you can start one or more Fulcro applications within the SAME JVM as your server, and render to hiccup, and then use that to write tests or have the LLM interact with the app with NO need for insanely slow things like playwright, image capture (and the insane token overhead), etc. UI look is easy to refine in isolation from the logic, and IMO it makes no sense to constantly deal with that overhead when working on logic. Fulcro’s always been about “manage the full state data model, and the view is a trivial projection of that”. Humans like to click, so many people still want to drive the UI around, but LLMs are VERY fast at raw data analysis, so rendering the live app to Hiccup in the JVM lets on REPL serve full-stack purposes. I have Full e2e tests (with trivial servers) that start the server, start the app, run some UI operations, and shut it all down in 10ms. Do that with Playwright. This release continue this with: • Improved headless support • Added CLJ http remote for true headless integration testing • Bug fixes in CLJ hiccup rendering • A “REPL Inspect Tool” (e.g. examining the running Fulcro app on the JVM to see things like history of transactions, network interactions, etc.)

🎉 19
tony.kay 2026-02-18T12:19:13.943969Z

The idea is that we’re not rendering to a browser at all. We’re rendering to a data structure (a tree-based on that can be converted to either strings or hiccup or used as is) in tests or at the REPL (no browser involved). The goal is fast-running tests that do not involve a browser (if you need e2e tests) and fast interaction with an LLM (since it’s just data that the LLM can navigate directly or with helper functions). You are not typing hiccup or this data structure (you’re still writing Fulcro as-is), but the renderer is replaced by one that just records the render frames as data, and the entire SPA is run in the JVM. Fulcro has always been capable of doing this (I wrote it in CLJC with just these sorts of intentions, before LLMs existed) and never really polished the library itself because most people were happy clicking through the browser. Now that LLMs need better direct access to “what is going on”, I think Fulcro is very well situated to make that better…Also, the verification story for LLMs NEEDs a lot more e2e tests (because humans are used to “trying it in the browser continuously”), so the verification step that reduces hallucination of a “working system” in the current “state of the art” is image based and very token heavy. You could technically have it play directly with the DOM via browser plug-ins or playwright (it can read the console as well), but again that’s pretty heavy on memory, and just loading a browser MCP consumes a lot of tokens.

tony.kay 2026-02-18T12:27:46.756169Z

Here’s a complete test from a demo app using the new async statecharts:

(use-fixtures :once
  (with-test-system {:port 9844}))

(specification "Application startup"
  (let [app (test-client 9844)]

    (h/render-frame! app)

    (assertions
      "Loads the application configuration"
      (get (app/current-state app) :application/config) => {:url ""}

      "the statechart is in the landing page state"
      (contains? (scf/current-configuration app uir/session-id) :dataico.ui.root/LandingPage) => true

      "The welcome page is rendered"
      (hic/find-nth-by-text (h/hiccup-frame app) "Welcome" 0) => [:div {} "Welcome:" "1"])))
Starts the server on port 9844, starts the SPA (in the JVM) talking to the same port. The code for the SPA is 100% CLJC. The app would actually render within 16ms, but forcing a render-frame is cleaner than a random sleep. See the last assertion: I’m verifying the rendering of the landing page. The app talks to the server using loopback networking (so the entire Fulcro and http middleware are used). This example loads the application config from the server. The entire test runs in a few ms. The default route is this component.
(defsc LandingPage [this {:keys [x]}]
  {:query         [:x]
   :ident         (fn [] [:component/id ::LandingPage])
   :initial-state {:x 1}}
  (dom/div "Welcome:" x))
and the app is using the async statecharts where the load of the application config parks the chart (along with custom statechart nodes that handle UI routing):
(def system-statechart
  (statechart {}
    (state {:id :state/top}
      (transition {:event :error}
        (script-fn [env data & _]
          (log/error "Unexpected error: " (:_event data))))

      (on-entry {}
        (script-fn [{:fulcro/keys [app]} & _]
          (setup-RAD app)
          [(fops/apply-action assoc :ui/ready? true)
           ;; NOTE: afops BLOCK statechart as-if the async operation were synchronous. This keeps us from trying to route
           ;; until the application config is loaded.
           (afops/load :application/config nil {::sc/ok-event :event/configuration-loaded})]))

      (uir/routing-regions
        (uir/routes {:id           :state/root
                     :routing/root `dataico.ui.root/Routes}

          (uir/rstate {:route/target `dataico.ui.root/LandingPage})  ;; First (deepest) state (default route)

          (state {:id :state/logged-in}
            (sfr/report-state {:route/target AccountList})
            (sfr/form-state {:route/target AccountForm})))))))
The CLJC setup for the Fulcro app sets a synchronous transaction processor. I’m still refining it a bit, but the goal is minimal need for sleep/async support in the tests or LLM/REPL interactions.

sheluchin 2026-02-18T02:17:38.900959Z

I'm having a hard time wrapping my brain around exactly what rendering to hiccup means here and how that's different from "classic Fulcro" (for lack of a better term) which renders a ui tree in html/react to show the corresponding data tree returned from a query.