feat(nats): expose JetStream DeliverPolicy on transport + listener#2711
Merged
jeremydmiller merged 1 commit intomainfrom May 8, 2026
Merged
feat(nats): expose JetStream DeliverPolicy on transport + listener#2711jeremydmiller merged 1 commit intomainfrom
jeremydmiller merged 1 commit intomainfrom
Conversation
…stener Until now, when Wolverine auto-provisioned a JetStream consumer for a listener it built the ConsumerConfig with `AckPolicy.Explicit` / `MaxDeliver` / `AckWait` hardcoded and `DeliverPolicy` left unset — which falls through to the NATS server default of `DeliverPolicy.All`. For a new listener attached to a long-running stream that means every message currently in the stream is replayed on first connect, which isn't typically what hosts want. The only workaround was to pre-create the consumer outside Wolverine and reference it by name. Adds a `ConsumerConfigDeliverPolicy?` knob at two levels: * **Transport-wide** via `JetStreamDefaults.DeliverPolicy`. Set it once on `UseJetStream(d => d.DeliverPolicy = ...)` and every auto- provisioned consumer under the transport inherits it. * **Per-listener** via the new `NatsListenerConfiguration.DeliverFrom(ConsumerConfigDeliverPolicy)` method. Wins over the transport-wide default for a single endpoint. Resolution lives on `NatsEndpoint.EffectiveDeliverPolicy` and is computed at access time so override mutations performed during host bootstrap are picked up regardless of ordering between transport and listener configuration calls. `JetStreamSubscriber.StartAsync` reads that property and applies it to the `ConsumerConfig` only when non-null — when both endpoint and transport leave it null Wolverine writes nothing and the NATS server default is preserved (no behaviour change for hosts that don't opt in). The override only applies to consumers Wolverine itself auto-provisions. Pre-created consumers referenced by name through `UseJetStream(streamName, consumerName)` keep their existing config — matches the existing reuse-by-name behaviour in `JetStreamSubscriber` (it `GetConsumerAsync` first, falls through to `CreateOrUpdateConsumerAsync` only on miss). The endpoint's `DeliverPolicy` is also propagated when the endpoint clones itself for tenanted setups in `CreateSender`, so per-listener overrides flow through to per-tenant endpoints. Tests: 11/11 in new `NatsDeliverPolicyTests` covering: * default null on endpoint + transport * `EffectiveDeliverPolicy` null when neither is set * fallback to transport when endpoint is null * endpoint override wins over transport default * `DeliverFrom(...)` round-trips through the configuration callback * parameterized round-trip across `All` / `Last` / `New` / `LastPerSubject` Docs: new "Consumer Deliver Policy" subsection under the JetStream Configuration section of `docs/guide/messaging/transports/nats.md`, including the `OptStartSeq` / `OptStartTime` caveat and a table of the relevant `ConsumerConfigDeliverPolicy` values. `vitepress build` clean.
This was referenced May 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the gap in the user question on Wolverine NATS — there was no way to make an auto-provisioned JetStream consumer use
DeliverPolicy.New(or anything other than the NATS-server default ofAll) without pre-creating the consumer outside Wolverine. This adds the knob at both the transport-wide and per-listener level.Why
JetStreamSubscriber.StartAsyncbuilds the consumer config withAckPolicy.Explicit/MaxDeliver/AckWaithardcoded andDeliverPolicyleft unset — so every auto-provisioned consumer falls through to NATS'sDeliverPolicy.Alland replays every message currently in the stream on first connect. That's rarely what a host wants when standing up a new listener against a long-running stream.What changed
A nullable
ConsumerConfigDeliverPolicy?at two levels:JetStreamDefaults.DeliverPolicy. Configure once viaUseJetStream(d => d.DeliverPolicy = …)and every auto-provisioned consumer inherits it.NatsListenerConfiguration.DeliverFrom(...). Wins over the transport-wide default for a single endpoint.Resolution lives on
NatsEndpoint.EffectiveDeliverPolicy(endpoint > transport > null), computed at access time so configuration ordering is irrelevant.JetStreamSubscriber.StartAsyncreads it and only writes toConsumerConfig.DeliverPolicywhen non-null — hosts that don't opt in see no behaviour change and the NATS-server default is preserved.The override applies only to consumers Wolverine itself auto-provisions. Pre-created consumers referenced by name through
UseJetStream(streamName, consumerName)keep their existing config — that path already doesGetConsumerAsyncfirst and only falls through toCreateOrUpdateConsumerAsyncon miss, so there's no behaviour change there either.The endpoint's
DeliverPolicyis also propagated when the endpoint clones itself for tenanted setups inCreateSender, so per-listener overrides flow through to per-tenant endpoints.Test plan
NatsDeliverPolicyTests11/11 on net9.0 — covers default-null,EffectiveDeliverPolicyresolution at every precedence point, listener-configuration round-trip, and parameterized cases forAll/Last/New/LastPerSubject.NatsEndpointUriTests,broker_role_tests).ConsumerConfig.DeliverPolicyis left untouched and the NATS server default applies, exactly as before.NatsTransportComplianceTests,BasicTests, etc.) — those need a NATS container which I don't have locally.Docs
New Consumer Deliver Policy subsection under JetStream Configuration in
docs/guide/messaging/transports/nats.md, with a code sample for both knobs and a table of the relevantConsumerConfigDeliverPolicyvalues (including theOptStartSeq/OptStartTimecaveat forByStartSequence/ByStartTime, which require pre-creating the consumer since those supplemental properties have no listener-configuration surface here).vitepress buildclean (17.29s).🤖 Generated with Claude Code