Hi folks! 👋 A couple library releases for today, enjoy! • https://www.taoensso.com/sente https://github.com/taoensso/sente/releases/tag/v1.22.0: Realtime web comms library for Clojure/Script • https://www.taoensso.com/tengen https://github.com/taoensso/tengen/releases/tag/v1.2.0: Simple React component DSL for Clojure/Script
https://github.com/abogoyavlensky/lgx 0.1.0 - a project and dependency manager for https://github.com/nooga/let-go (a Clojure dialect written in Go) I’m releasing the first public version of lgx. let-go is great for CLIs and scripts: no JVM required, with programs compiled into standalone binaries. But until now, there wasn’t much project tooling around it. lgx fills that gap in one binary:
lgx new myapp # scaffold a project from a template
lgx install # fetch deps from lgx.edn
lgx run # run :main with deps on the path
lgx nrepl # start an nREPL server
lgx build # bundle a standalone binary
lgx test # run tests under test/
lgx <task> # run a custom task from lgx.edn
Configuration lives in a single lgx.edn file, close in spirit to tools.deps:
• git deps pinned by :git/tag or :git/sha, plus :local/root for local dirs; transitive deps are resolved too
• :contexts - named overlays of extra paths/deps, similar to aliases; :dev and :test auto-apply where you’d expect
• :tasks - custom commands with typed positional args, replacing ad-hoc Makefiles
It already runs a real project: I built https://github.com/abogoyavlensky/wtr, a git worktree CLI, with let-go and lgx. It ships as prebuilt binaries for macOS and Linux.
Try it:
brew install abogoyavlensky/tap/lgx nooga/tap/let-go
lgx new hello
cd hello
lgx run
Also available via mise or an install script.
GitHub: https://github.com/abogoyavlensky/lgx
Site: https://lgx.bogoyavlensky.com
Happy to hear any feedback!Babashka https://github.com/babashka/fs: file system utility library for Clojure
0.5.34 (2026-07-02)
Highlight: https://github.com/babashka/fs/blob/master/README.md#nodejs
• Add Node.js support via ClojureScript and Squint support. Seehttps://github.com/babashka/fs/blob/master/README.md#nodejs.
◦ File times are BigInt nanoseconds, preserving sub-millisecond filesystem precision for`modified-since`
◦ Most functions are supported. Only those functions that require very specific JVM APIs are not.
◦ The behavior of babashka.fs on the JVM was taken as the reference implementation. This means all operations are synchronous.
◦ Glob syntax is implemented from scratch to match the JVM glob syntax.
◦ zipsupport was left out as Node.js doesn't have support for it natively.
• Add`spit`and`slurp`in both JVM and Node.js
• exec-pathsreturns`[]`when the`PATH`environment variable is unset or blank, instead of throwing
• https://github.com/babashka/fs/issues/197: docstring review - step 2: add missing links to JavaDocs (https://github.com/lread)
• https://github.com/babashka/fs/issues/197: docstring review - step 3: define/document undefined return values:
◦ copyreturns copied target file (and fixed for case when`source`is input stream)
◦ copy-treenow explicitly returns`target-dir`(previously`source-dir`)
◦ delete-treeexisting return value now documented
◦ gunzipnow explicitly returns the extracted file (previously`nil`)
◦ set-attributedocument that`path`is return
◦ set-creation-timedocument that`path`is return
◦ set-last-modified-timedocument that`path`is return
◦ set-posix-file-permissionsdocument that`path`is return
◦ touchdocument that`path`is return
◦ write-bytesdocument that`file`is return
◦ write-linesdocument that`file`is return
◦ unzipnow explicitly returns`target-dir`(previously`nil`)
◦ zipnow explicitly returns created`zip-file`(previously`nil`)
https://clojure.org/news/2026/07/02/clojure-1-13-alpha1 is now available! Checked keys You can now ensure that required keys are bound during map destructuring by using the new checked variants of the :keys/:syms/:strs directives - :keys!/:syms!/:strs!, which will throw if the key is not present. You can also, in all directives, specify keys after & which will not be bound, for documentation or checking purposes. • https://clojure.atlassian.net/browse/CLJ-2961 Checked keys • https://clojure.atlassian.net/browse/CLJ-2960 Specs for checked keys • https://clojure.atlassian.net/browse/CLJ-2949 req! - Variant of get that reports on key not found • https://clojure.atlassian.net/browse/CLJ-2954 let/loop/let* - disallow & as local binding Other changes since Clojure 1.12.5 • PersistentArrayMaps of only keyword keys now grow up to size 64 (previously was 8) before transitioning to PersistentHashMaps. PAM identity scans are more efficient than PHM lookups in this range, also makes more usage sites monomorphic and thus easier to optimize. • https://clojure.atlassian.net/browse/CLJ-2891 Remove ACC_FINAL designation from static initializer constants. This change was made as a prepatory step towards moving the Java bytecode baseline to address new verifier checks. • Runtime and test dependencies updated to latest versions
How definitive is the design / syntax? I might add support for it in the next clj-kondo and other Clojure dialects :)
Alphas are subject to change
Alpha 2 is on QA and today I'll be adopting :keys! in our code 🙂 We love life on the bleeding edge!
@borkdude note one unreferenceable gensym binding is (re)used in the expansion, for the non-binding checks
@borkdude the design has been under work for quite a while, so I don't expect change other than maybe additions. But still, it's alpha so people can give feedback.
To me it looks really compelling with the checked keys.
One of these changes that doesn't look right to me is the name req!. It seems like it is the adjective of an operation and omitting the operation which is more important.
Some names that would make more sense:
• rget
• sget (I saw this one around in the community about 8 years ago)
• get-req
• get!
• etc
get! had way too many collisions in the wild, was considered and rejected
req(uire)! seems like a decent name to me, given all the constraints on short, common words in the wild.
Naming is hard 🙂
Hmm so popular names in the wild prohibits intuitive new names in core? 😅
it's a consideration to be weighed
adding a new thing with the same name as many existing things, but with different meaning is potentially confusing. as an opposite example - when we added abs that was also a common function in the wild, but one that had the same meaning (and you could usually just start using core's)
Clojure should really prohibit non-core short convenient names.
I agree that req! is probably good enough and I understand that overlapping names is just one column in the decision matrix
req is commonly request also, not just require.
my presumption is that the main way you're using req! is not directly but via destructuring :keys! where you don't actually even say the name of the function at all
it's just in the emiited code
> PersistentArrayMaps of only keyword keys now grow up to size 64 (previously was 8) before transitioning to PersistentHashMaps That makes me somewhat wary, I remember seeing some (not good!) code out there, more than once, that implicitly or explicitly relied on the magic 8. Not saying that the change shouldn't have been done, of course - you know pros/cons better than most. Just kinda getting ready to answer some new "why did my code break after an update" questions. :)
feedback welcome. we run a regression suite over a big set of community libraries and have not found any issues related to this so far
if anyone does find issues with either changes or new features, happy to see questions at https://ask.clojure.org or feel free to open a thread in #clojure-dev if that makes more sense
I notice the exception for req! prints the key on failure
> that throws IllegalArgumentException "Expected key: keyname".
This is the kind of thing that can explode your repl if you typo a large or infinite value in the wrong arg spot. I remember at some point, it seemed like there was a movement away from printing inputs in exception messages and instead providing them as data with something like ex-info.
for a key!?
not the key, the typo that passed a large value as a key
the most common way you'll get here is via checked key destructuring where you've provided the key in the code
those keys must be (via spec) a key or symbol
user=> (let [{:keys! [a]} {}] a)
Execution error (IllegalArgumentException) at user/eval7363 (REPL:1).
Missing required key: :aSo it's not expected to be used like: (-> m (req! :foo) (req! :bar))?
I often make this kind of typo when I'm moving code between a single expression and a threading macro and vice versa.
req! must stand on its own. That said, I have to say there are or should be limits to the efforts made to accommodate broken programs. Adding a hop to get to the name of a missing key costs everyone else.
We could limit this message to kw/str/sym/number and put anything else somewhere else I guess, seems like busy work
> Adding a hop to get to the name of a missing key costs everyone else. I totally agree with this. This is something I can fix for my own IDE setup, but it's unlikely to get fixed for most IDE setups. The harder issues for me to fix in my setup are: • a thread printing out of control • providing better error messages without writing a parser
The most likely error of this sort in manual use of req! is transposing the coll and key, and associative colls are not infinite. This seems a theoretical problem only, but your point is understood.
@p-himik I think the thing most likely is a presumption that < 8 will e.g. be in insertion order. This will still be true.
But that's why we do alphas 🙂
"You can also, in all directives, specify keys after & which will not be bound, for documentation or checking purposes." -- what does that actually mean and is there documentation with examples somewhere?
Oh, that's an interesting addition...
:keys [a b & c d] just documents the acceptance of c/d, again w/o binding
Hence CLJ-2954 so that & could have a special meaning?
taken together these let you say/check everything at the 'front door' even if not consumed at that level
& has never been on the acceptable character list, even though it gets used
"without binding", that's great so no overhead. people have been complaining about "unused " warnings in clj-kondo, I'll redirect them to & in the future before they turn off that linter ;)
the & support also is needed to support the use case where you are doing a renaming binding (which you can't do in :keys et al) but still want a check, {a :ugly-a :keys! [& ugly-a]}
ditto a further destructuring binding
{[a b c] :abc :keys! [& abc]}
Nice!
No regressions for next.jdbc or HoneySQL. I'll update dev at work now and see if anything pops up.
@seancorfield the spec for let already precluded & but destructuring directly emits let* , which didn't check. Just tightened that up.
clinging to the little syntax we have 🙂
Looks like Rebel Readline might not be compatible with 1.13. I'll do some digging and follow up in #clojure-dev
Running clojure.repl/dir-fn on 'clojure.core produces two proxy objects/vars that have no metadata. See #clojure-dev
I'm not sure if someone might risk logging private data with that error message, eg (req! criminals "Dominic Monroe"). But potentially all of us who care already whitelist exception messages already, ex-data is handy so we can pick and choose (we would display the message, but retract some of the ex-data based on the key name - so we wouldn't whitelist :key).
You know the next thing is going to be req-in! (`req!-in`?) 😅 We definitely have some use cases for this in a nested context, but we actually care more about certain outer items being required than we do the nested ones.
https://clojure.org/news/2026/07/02/clojure-1-13-alpha2 is now available • reverted maven plugin changes that caused a bad release, otherwise same as 1.13.0-alpha1
https://github.com/borkdude/edamame: configurable EDN and Clojure parser with location metadata and more 1.6.40 • Add https://github.com/tensegritics/ClojureDart support clojuredart - this is part of an ongoing effort to bring SCI to ClojureDart. Eval some Clojure in a Dart app on your mobile phone soon...? • With`:auto-resolve-ns`, qualify bare syntax-quoted symbols (e.g.``foo`) with the current namespace, matching Clojure