feat: complete source consumer API request generation (Gap 4.3)

Add BuildConsumerCreateRequest, BuildConsumerCreateSubject, and
GetDeliverySequence to SourceCoordinator, modelling Go's
setupSourceConsumer/trySetupSourceConsumer. Covers DeliverPolicy
resume-from-sequence, AckPolicy.None, push/flow-control/heartbeat
consumer fields, and the $JS.API.CONSUMER.CREATE.{name} subject format.
This commit is contained in:
Joseph Doherty
2026-02-25 11:21:21 -05:00
parent aad9cf17e4
commit a113dd686d
2 changed files with 260 additions and 0 deletions

View File

@@ -84,6 +84,12 @@ public sealed class SourceCoordinator : IAsyncDisposable
/// <summary>The source configuration driving this coordinator.</summary>
public StreamSourceConfig Config => _sourceConfig;
/// <summary>
/// Current delivery sequence counter — number of messages successfully written to the target store.
/// Go reference: server/stream.go si.dseq field
/// </summary>
public ulong GetDeliverySequence => _deliverySeq;
public SourceCoordinator(IStreamStore targetStore, StreamSourceConfig sourceConfig)
{
_targetStore = targetStore;
@@ -95,6 +101,47 @@ public sealed class SourceCoordinator : IAsyncDisposable
});
}
/// <summary>
/// Builds the consumer configuration that would be sent to the source stream to set up
/// a push consumer for consumption. This models the consumer create request generated by
/// Go's setupSourceConsumer / trySetupSourceConsumer.
/// Go reference: server/stream.go:3474-3720 (setupSourceConsumer, trySetupSourceConsumer)
/// </summary>
public ConsumerConfig BuildConsumerCreateRequest()
{
// Go: server/stream.go:3597-3598 — if ssi.FilterSubject != _EMPTY_ { req.Config.FilterSubject = ssi.FilterSubject }
var cfg = new ConsumerConfig
{
AckPolicy = AckPolicy.None,
Push = true,
FlowControl = true,
HeartbeatMs = (int)HeartbeatInterval.TotalMilliseconds,
};
if (!string.IsNullOrWhiteSpace(_sourceConfig.FilterSubject))
cfg.FilterSubject = _sourceConfig.FilterSubject;
// Go: server/stream.go:3573-3582 — resume from LastOriginSequence+1, or DeliverAll when starting fresh
if (LastOriginSequence == 0)
{
cfg.DeliverPolicy = DeliverPolicy.All;
}
else
{
cfg.DeliverPolicy = DeliverPolicy.ByStartSequence;
cfg.OptStartSeq = LastOriginSequence + 1;
}
return cfg;
}
/// <summary>
/// Returns the JetStream API subject used to create a consumer on the source stream.
/// Go reference: server/stream.go:3531 — $JS.API.CONSUMER.CREATE.{sourceName}
/// </summary>
public string BuildConsumerCreateSubject() =>
$"$JS.API.CONSUMER.CREATE.{_sourceConfig.Name}";
/// <summary>
/// Processes a single inbound message from the origin stream.
/// This is the direct-call path used when the origin and target are in the same process.