NATS carries bytes, and raw bytes are not a uniform type across platforms (byte[] on the JVM, Uint8Array on ClojureScript). So the API is codec-centric: a pluggable codec converts between Clojure values and wire bytes. A default codec lives on the connection and is overridable per publish / subscribe / request.
Codecs split into two tiers by what they cost the consumer:
:bytes (passthrough), :string (UTF-8), :edn. Always available, implemented with only what ships in Clojure/ClojureScript core (pr-str + clojure.edn / cljs.reader). The default is :edn.:transit (needs com.cognitect/transit-clj on the JVM, transit-cljs on ClojureScript) and :json (the JVM has no built-in JSON, so it needs a JSON library; ClojureScript uses ambient js/JSON). Their impls live in their own namespaces (nats-cljc.codec.transit, nats-cljc.codec.json) that a consumer requires after adding the dependency. The keyword resolves only once that namespace is loaded; using it unloaded yields an actionable error.This keeps the library's forced dependency footprint to just the native NATS clients (ADR 0002 does the same on the async side). A consumer who wants Transit's compactness or a polyglot JSON wire opts in and pays for it; everyone else carries nothing.
The common case — (publish conn "subj" {:any :clojure-data}) round-tripping end to end — should need no ceremony and no dependency. EDN is the only structured option that is both: it round-trips ordinary Clojure data and keeps the delivered :data a portable Clojure value on all three platforms, using nothing outside core. Decode with clojure.edn/read-string (not core/read-string) so it never evaluates.
:bytes default — the purest "honest transport" stance, but :data would then be the platform-native byte type (byte[] vs Uint8Array), so the no-override experience stops being write-once — the opposite of the library's reason for being. :bytes is the right explicit codec, not the default.:string default — portable and dependency-free, but discards structure; rejected in favour of :edn, which keeps the data-in/data-out feel at the same dependency cost (none).:transit / :json users add one.:json, or to :string / :bytes. The EDN envelope on a shared subject is the same foot-gun the Transit default had, and is the documented reason overrides exist.:bytes.:json's documented lossiness is platform-asymmetric for large integers: a JSON integer beyond 2^53 keeps full precision on the JVM (data.json → Long) but rounds to the nearest f64 on cljs (js/JSON.parse), so an integer ID past 2^53 does not survive a JVM→cljs leg. Documented, not guarded — JSON is a polyglot wire, not a Clojure round-trip format, and forcing symmetry means either crippling the JVM reader or adding cljs BigInt handling, both out of proportion to an already-lossy opt-in codec.:bytes accepts each leg's native wire-byte type, and the match is platform-specific by design: a primitive byte[] on the JVM (exact — ByteBuffer/boxed Byte[] rejected), any js/Uint8Array incl. a Node Buffer on cljs. Values never cross legs, so no single value is accepted on one and rejected on the other. Documented, not aligned — rejecting Node Buffers or coercing arbitrary JVM byte containers would fight the native types each client actually uses.Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |