Releases: ThatScalaGuy/fs2-nats
Release list
v0.4.0
The headline of this release is fs2.nats.micro — typed NATS micro services
implementing ADR-32.
Alongside it, another round of hot-path work landed across core pub/sub, JetStream,
and Object Store, and 0.4 starts a new binary-compatibility series.
Micro services (fs2.nats.micro)
- An endpoint is described once as a plain
Rpc[P, I, E, O]value — subject pattern
with typed captures, request payload, typed error, response payload — and interpreted
on both sides:rpc.handle(...)inside aNatsService,Micro(client).call(rpc)(...)
from the caller. Definitions can live in a shared module (#57) - Subject patterns are checked at compile time:
pattern["orders.*"].bind[OrderId]
rejects malformed literals, computes the capture arity, and binds captures through
TokenCodecs (String,Int,Long,UUID, or your own) - Payloads via
Payload.empty/bytes/string/json[A](jsoniter-scala),
with optional schema text published in the endpoint INFO metadata - Errors are typed end to end: handlers return
F[Either[E, O]], errors travel as
ADR-32 error headers and come back asLeft(E)at the caller — transport failures
are the only thing raised inF - Full ADR-32 discovery: every instance answers
$SRV.PING/INFO/STATSin all
nine subject forms with the standard response types, sonats microCLI tooling
works out of the box; per-endpoint stats are also available locally via
NatsService#stats - Instances load-balance through a queue group by default; service and endpoint
metadata per ADR-33; request headers are visible to handlers, and response headers
can be set viaReply
The module ships inside the fs2-nats artifact and adds no new dependency
(jsoniter-scala was already on the classpath for JetStream). See
docs/micro.md
for the full tour.
Performance
- Dialled sockets now set
TCP_NODELAY, removing Nagle-induced latency (up to
~40 ms per round trip) from request/reply; socket options are configurable via
the newTransportConfig.socketOptions(#44) JetStream.publishAsync— and Object Storeput/ KVputAsyncon top of it —
no longer starts a supervised fiber per message; correlation is registered up
front and the reply completes on the shared inbox drain fiber (#47)- Request correlation moved from four CAS loops over an immutable-map
Refto a
ConcurrentHashMapplusAtomicLong, so pipelined request/reply scales instead
of contending on one serialization point (#49) - Subscription lookup on the delivery path uses a primitive-keyed
LongMapand a
single volatile read instead of boxing the sid and reading twoRefs per
message (#50) - JetStream API replies are decoded in place in a single jsoniter pass instead of
a payload copy plus two passes per PubAck (#51) - Object Store
putfeeds SHA-256 by wrapping each chunk's backing arrays instead
of flattening them, dropping the extra full copy of all object data on the way
to the wire (#53)
Compatibility
- 0.4 starts a new early-semver series: not binary compatible with 0.3.x,
recompile against 0.4.0. Source-level, typical users need no changes TransportConfiggained asocketOptionsfield (default:TCP_NODELAYon for
sockets the library dials — overridesocketOptionsto opt out). Named-argument
construction is unaffected; only full-arity positional construction needs the
new argument- The
NatsClienttrait gained a package-private abstract method, so hand-written
implementations of the trait outsidefs2.nats(e.g. test stubs) no longer
compile — delegate to a real client instead - Scala 3, cats-effect 3.7, fs2 3.13; JDK 11+
- Dependency refresh: jsoniter-scala 2.38.17 → 2.40.1; munit 1.3.5, sbt 1.12.15,
scalafmt 3.11.5 (test/build-scoped only)
What's Changed
- Minor & major updates by @ThatScalaGuy in #39
- Test library updates by @ThatScalaGuy in #37
- Patch updates by @ThatScalaGuy in #38
- Patch updates by @ThatScalaGuy in #40
- Minor & major updates by @ThatScalaGuy in #41
- Test library updates by @ThatScalaGuy in #43
- Add fs2.nats.micro: typed NATS micro services (ADR-32) by @ThatScalaGuy in #57
Full Changelog: v0.3.1...v0.4.0
v0.3.1
A maintenance release. There are no library code changes — this is a dependency
refresh on top of 0.3.0, plus a couple of build/CI fixes.
Dependency updates
- Bouncy Castle 1.84 → 1.85 (
bcprov-jdk18on, used for NKey Ed25519 signing) - jsoniter-scala 2.38.14 → 2.38.17
- Scala 3.3.7 → 3.3.8
- munit 1.3.2 → 1.3.4, scalafmt 3.11.1 → 3.11.3
- sbt 1.12.11 → 1.12.13, sbt-typelevel 0.8.6 → 0.8.7
Build & CI
- Scala Steward now pins sbt to the 1.x line. sbt 2 is a breaking rewrite of the
build definition, so that move stays a deliberate migration rather than
something picked up from an automated dependency PR (#36) - The
Validate Steward ConfigCI job runs on JDK 17 — it was failing on JDK 11,
which scala-steward no longer supports
Compatibility
- Binary compatible with 0.3.0 (MiMa-checked); no source changes required
- Scala 3, cats-effect 3.7, fs2 3.13; JDK 11+
What's Changed
- Patch updates by @ThatScalaGuy in #28
- Test library updates by @ThatScalaGuy in #27
- Patch updates by @ThatScalaGuy in #29
- Patch updates by @ThatScalaGuy in #31
- Typelevel & FS2 updates by @ThatScalaGuy in #34
- Pin sbt to the 1.x line for Scala Steward by @ThatScalaGuy in #36
- Patch updates by @ThatScalaGuy in #33
- Test library updates by @ThatScalaGuy in #32
- Other dependency updates by @ThatScalaGuy in #35
Full Changelog: v0.3.0...v0.3.1
v0.3.0
This release is almost entirely about performance. The client was benchmarked
head-to-head against the official Java client (jnats) across core pub/sub,
JetStream, and Object Store workloads, and the hot paths were rebuilt based on
what the profiles showed. There are no public API changes — everything
below is internal.
Performance
Core pub/sub
- The protocol parser keeps a single flat array carry instead of an immutable
chunk queue, fixing an O(n²) scan under streaming load — 2.4× parser
throughput and −82% allocations in JMH, ~+40% end-to-end receive (#18) - Publishes enqueue a small write descriptor instead of building a combined
header+payload array per message; the single writer fiber assembles batches
into one reused buffer and flushes them in one socket write — per-publish
allocation is now constant at 112 B regardless of payload size (#20, #21, #22) - The receive path builds the user-facing message once, directly in the parser,
instead of materializing an intermediate frame that gets re-wrapped (#23) - Sends take a lock-free fast path while the connection is online (#19)
- Sockets are read in 64 KiB requests instead of fs2-io's 8 KiB default,
cutting async read round-trips 8× on large-payload streams (#26) - Internal bounded queues stay on cats-effect's lock-free implementation at
any capacity.Queue.boundedsilently degrades to a CAS-retry queue at
capacities ≥ 65534, which cost >30% of client CPU under sustained load with
largequeueCapacitysettings — high-capacity configs are now ~2× faster
under CPU pressure (#24)
JetStream
consumeprocesses inbox messages chunk-at-a-time instead of one effect per
message: 2.1× consume throughput (#25)
Object Store
- GET benefits from the larger socket reads and now hashes chunks without
copying them — streaming GET reaches parity with jnats at steady state
(~1.3 GB/s in our benchmarks) (#26)
Other changes
- New documentation site: https://thatscalaguy.github.io/fs2-nats (#17)
- README: expanded feature overview and compatibility matrix
- Apache 2.0 LICENSE file added to the repository
- Test library updates (#16)
Compatibility
- Binary compatible with 0.2.x (MiMa-checked); no source changes required
- Scala 3, cats-effect 3.6, fs2 3.13; JDK 11+
What's Changed
- docs: add sbt-typelevel-site documentation site by @ThatScalaGuy in #17
- Test library updates by @ThatScalaGuy in #16
- perf(parser): flat-array incremental decoder (fixes O(n²) control-line scan) by @ThatScalaGuy in #18
- perf(client): skip the connRef.modify CAS on the Online publish path by @ThatScalaGuy in #19
- perf(transport): reuse the writer coalescing buffer across drains by @ThatScalaGuy in #20
- perf(transport): drop the per-publish Some box from the write queue by @ThatScalaGuy in #21
- perf(publish): enqueue a write descriptor instead of a per-publish combined array by @ThatScalaGuy in #22
- perf(receive): build the user NatsMessage once on the MSG/HMSG data path by @ThatScalaGuy in #23
- perf(queues): keep hot-path queues on cats-effect's lock-free implementations at any capacity by @ThatScalaGuy in #24
- perf(jetstream): process pull-consume deliveries a chunk at a time by @ThatScalaGuy in #25
- perf(receive): 64 KiB socket reads + copy-free Object Store GET digest by @ThatScalaGuy in #26
Full Changelog: v0.2.0...v0.3.0
v0.2.0
What's Changed
- perf: close the gap to the Java NATS client (write coalescing + leaner hot paths) by @ThatScalaGuy in #1
- feat: JetStream support (request/reply, streams, consumers, pull/push) by @ThatScalaGuy in #2
- perf(json): replace circe with jsoniter-scala 2.38.14 by @ThatScalaGuy in #3
- feat(kv): add Key-Value store over JetStream by @ThatScalaGuy in #4
- feat(jetstream): gap-resetting ordered push consumer by @ThatScalaGuy in #5
- feat(jetstream): model mirror/sources/republish/subject-transform/placement/sealed on StreamConfig by @ThatScalaGuy in #6
- feat(objectstore): NATS Object Store over JetStream by @ThatScalaGuy in #7
- ci: add Scala Steward for grouped dependency updates by @ThatScalaGuy in #8
- feat: implement NKey (Ed25519) authentication signing by @ThatScalaGuy in #9
- Complete the auth stack: .creds loader + INFO-first TLS fix by @ThatScalaGuy in #10
- Cluster failover, discovery, and reconnect write buffer by @ThatScalaGuy in #11
- Test library updates by @ThatScalaGuy in #13
- Minor & major updates by @ThatScalaGuy in #14
- Other dependency updates by @ThatScalaGuy in #15
- Typelevel & FS2 updates by @ThatScalaGuy in #12
New Contributors
- @ThatScalaGuy made their first contribution in #1
Full Changelog: v0.1.0...v0.2.0
v0.1.0
refactor(CI): :white_check_mark: Remove unused imports from test spec…