Feature Request: JetStream Consumer Support via DSL
Background
Leopard's DSL is elegant and safe for NATS request/reply — it handles threading through a Concurrent::FixedThreadPool, enforces Dry::Monads::Result return values, and removes entire categories of concurrency bugs. The endpoint block pattern is easy to reason about and hard to misuse.
However, Leopard currently has no support for NATS JetStream consumers. Users who need async, durable message processing (e.g., work queues, event-driven pipelines, at-least-once delivery) must drop down to raw nats-pure JetStream code. This means hand-rolling thread management, ack/nak handling, and consumer lifecycle — all the things Leopard protects against in the request/reply world.
Problem
When a team adopts Leopard for its safety guarantees and then needs JetStream, they hit a cliff. There is no way to express a JetStream consumer in the DSL, so they write a full handler class from scratch. This reintroduces the threading pitfalls Leopard was designed to eliminate, and fragments the codebase between "Leopard-style" and "raw NATS" services.
Proposed Solution
The framework would:
- Ack on
Success, Nak on Failure (with configurable backoff / retry limit)
- Term on unhandled exception (to avoid poison-pill loops), with optional rescue callback
- Reuse the existing
Concurrent::FixedThreadPool / instances: scaling model
- Pass the same
MessageWrapper interface so middleware and existing handler patterns are unchanged
Why This Matters
- Users already inside the Leopard ecosystem shouldn't need to leave it to do async work
- The threading model that makes request/reply safe applies equally to JetStream — there is no reason to give that up
- Consistent use of
Dry::Monads::Result across both sync and async handlers reduces surface area for bugs
- Middleware (auth, logging, metrics) would apply uniformly without extra wiring
Acceptance Criteria
Feature Request: JetStream Consumer Support via DSL
Background
Leopard's DSL is elegant and safe for NATS request/reply — it handles threading through a
Concurrent::FixedThreadPool, enforcesDry::Monads::Resultreturn values, and removes entire categories of concurrency bugs. Theendpointblock pattern is easy to reason about and hard to misuse.However, Leopard currently has no support for NATS JetStream consumers. Users who need async, durable message processing (e.g., work queues, event-driven pipelines, at-least-once delivery) must drop down to raw
nats-pureJetStream code. This means hand-rolling thread management, ack/nak handling, and consumer lifecycle — all the things Leopard protects against in the request/reply world.Problem
When a team adopts Leopard for its safety guarantees and then needs JetStream, they hit a cliff. There is no way to express a JetStream consumer in the DSL, so they write a full handler class from scratch. This reintroduces the threading pitfalls Leopard was designed to eliminate, and fragments the codebase between "Leopard-style" and "raw NATS" services.
Proposed Solution
The framework would:
Success, Nak onFailure(with configurable backoff / retry limit)Concurrent::FixedThreadPool/instances:scaling modelMessageWrapperinterface so middleware and existing handler patterns are unchangedWhy This Matters
Dry::Monads::Resultacross both sync and async handlers reduces surface area for bugsAcceptance Criteria
jetstream_endpointDSL method (or equivalent) withstream:,subject:, anddurable:optionsSuccess, nak onFailure, term on unhandled exceptioninstances:concurrency modelexamples/