feat: port sessions 21-23 — Streams, Consumers, MQTT, WebSocket & OCSP
Session 21 (402 features, IDs 3195-3387, 584-792): - JetStream/StreamTypes.cs: StreamInfo, ConsumerInfo, SequenceInfo, JSPubAckResponse, WaitQueue, ClusterInfo, PeerInfo, message types, ConsumerAction enum, CreateConsumerRequest, PriorityGroupState - JetStream/NatsStream.cs: NatsStream class (stub methods, IDisposable) - JetStream/NatsConsumer.cs: NatsConsumer class (stub methods, IDisposable) - Updated JetStreamApiTypes.cs: removed duplicate StreamInfo/ConsumerInfo stubs Session 22 (153 features, IDs 2252-2404): - Mqtt/MqttConstants.cs: all MQTT protocol constants, packet types, flags - Mqtt/MqttTypes.cs: MqttSession, MqttSubscription, MqttWill, MqttJsa, MqttAccountSessionManager, MqttHandler and supporting types - Mqtt/MqttHandler.cs: per-client MQTT state, MqttServerExtensions stubs Session 23 (97 features, IDs 3506-3543, 2443-2501): - WebSocket/WebSocketConstants.cs: WsOpCode enum, frame bits, close codes - WebSocket/WebSocketTypes.cs: WsReadInfo, SrvWebsocket (replaces stub), WebSocketHandler stubs - Auth/Ocsp/OcspTypes.cs: OcspMode, OcspMonitor (replaces stub), IOcspResponseCache (replaces stub), NoOpCache, LocalDirCache All features (3503 complete, 0 not_started). Phase 6 now at 58.9%.
This commit is contained in:
@@ -21,12 +21,6 @@ namespace ZB.MOM.NatsNet.Server;
|
||||
// Forward stubs for types defined in later sessions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Stub: full definition in session 20 (stream.go).</summary>
|
||||
public sealed class StreamInfo { }
|
||||
|
||||
/// <summary>Stub: full definition in session 20 (consumer.go).</summary>
|
||||
public sealed class ConsumerInfo { }
|
||||
|
||||
/// <summary>Stub: stored message type — full definition in session 20.</summary>
|
||||
public sealed class StoredMsg { }
|
||||
|
||||
|
||||
151
dotnet/src/ZB.MOM.NatsNet.Server/JetStream/NatsConsumer.cs
Normal file
151
dotnet/src/ZB.MOM.NatsNet.Server/JetStream/NatsConsumer.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
// Copyright 2019-2026 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Adapted from server/consumer.go in the NATS server Go source.
|
||||
|
||||
namespace ZB.MOM.NatsNet.Server;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a JetStream consumer, managing message delivery, ack tracking, and lifecycle.
|
||||
/// Mirrors the <c>consumer</c> struct in server/consumer.go.
|
||||
/// </summary>
|
||||
internal sealed class NatsConsumer : IDisposable
|
||||
{
|
||||
private readonly ReaderWriterLockSlim _mu = new(LockRecursionPolicy.SupportsRecursion);
|
||||
|
||||
public string Name { get; private set; } = string.Empty;
|
||||
public string Stream { get; private set; } = string.Empty;
|
||||
public ConsumerConfig Config { get; private set; } = new();
|
||||
public DateTime Created { get; private set; }
|
||||
|
||||
// Atomic counters — use Interlocked for thread-safe access
|
||||
internal long Delivered;
|
||||
internal long AckFloor;
|
||||
internal long NumAckPending;
|
||||
internal long NumRedelivered;
|
||||
|
||||
private bool _closed;
|
||||
|
||||
/// <summary>IRaftNode — stored as object to avoid cross-dependency on Raft session.</summary>
|
||||
private object? _node;
|
||||
|
||||
private CancellationTokenSource? _quitCts;
|
||||
|
||||
public NatsConsumer(string stream, ConsumerConfig config, DateTime created)
|
||||
{
|
||||
Stream = stream;
|
||||
Name = (config.Name is { Length: > 0 } name) ? name
|
||||
: (config.Durable ?? string.Empty);
|
||||
Config = config;
|
||||
Created = created;
|
||||
_quitCts = new CancellationTokenSource();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Factory
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="NatsConsumer"/> for the given stream.
|
||||
/// Returns null if the consumer cannot be created (stub: always throws).
|
||||
/// Mirrors <c>newConsumer</c> / <c>consumer.create</c> in server/consumer.go.
|
||||
/// </summary>
|
||||
public static NatsConsumer? Create(
|
||||
NatsStream stream,
|
||||
ConsumerConfig cfg,
|
||||
ConsumerAction action,
|
||||
ConsumerAssignment? sa)
|
||||
{
|
||||
throw new NotImplementedException("TODO: session 21 — consumer");
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Lifecycle
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Stops processing and tears down goroutines / timers.
|
||||
/// Mirrors <c>consumer.stop</c> in server/consumer.go.
|
||||
/// </summary>
|
||||
public void Stop() =>
|
||||
throw new NotImplementedException("TODO: session 21 — consumer");
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the consumer and all associated state permanently.
|
||||
/// Mirrors <c>consumer.delete</c> in server/consumer.go.
|
||||
/// </summary>
|
||||
public void Delete() =>
|
||||
throw new NotImplementedException("TODO: session 21 — consumer");
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Info / State
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Returns a snapshot of consumer info including config and delivery state.
|
||||
/// Mirrors <c>consumer.info</c> in server/consumer.go.
|
||||
/// </summary>
|
||||
public ConsumerInfo GetInfo() =>
|
||||
throw new NotImplementedException("TODO: session 21 — consumer");
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current consumer configuration.
|
||||
/// Mirrors <c>consumer.config</c> in server/consumer.go.
|
||||
/// </summary>
|
||||
public ConsumerConfig GetConfig() =>
|
||||
throw new NotImplementedException("TODO: session 21 — consumer");
|
||||
|
||||
/// <summary>
|
||||
/// Applies an updated configuration to the consumer.
|
||||
/// Mirrors <c>consumer.update</c> in server/consumer.go.
|
||||
/// </summary>
|
||||
public void UpdateConfig(ConsumerConfig config) =>
|
||||
throw new NotImplementedException("TODO: session 21 — consumer");
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current durable consumer state (delivered, ack_floor, pending, redelivered).
|
||||
/// Mirrors <c>consumer.state</c> in server/consumer.go.
|
||||
/// </summary>
|
||||
public ConsumerState GetConsumerState() =>
|
||||
throw new NotImplementedException("TODO: session 21 — consumer");
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Leadership
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this server is the current consumer leader.
|
||||
/// Mirrors <c>consumer.isLeader</c> in server/consumer.go.
|
||||
/// </summary>
|
||||
public bool IsLeader() =>
|
||||
throw new NotImplementedException("TODO: session 21 — consumer");
|
||||
|
||||
/// <summary>
|
||||
/// Transitions this consumer into or out of the leader role.
|
||||
/// Mirrors <c>consumer.setLeader</c> in server/consumer.go.
|
||||
/// </summary>
|
||||
public void SetLeader(bool isLeader, ulong term) =>
|
||||
throw new NotImplementedException("TODO: session 21 — consumer");
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// IDisposable
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_quitCts?.Cancel();
|
||||
_quitCts?.Dispose();
|
||||
_quitCts = null;
|
||||
_mu.Dispose();
|
||||
}
|
||||
}
|
||||
197
dotnet/src/ZB.MOM.NatsNet.Server/JetStream/NatsStream.cs
Normal file
197
dotnet/src/ZB.MOM.NatsNet.Server/JetStream/NatsStream.cs
Normal file
@@ -0,0 +1,197 @@
|
||||
// Copyright 2019-2026 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Adapted from server/stream.go in the NATS server Go source.
|
||||
|
||||
namespace ZB.MOM.NatsNet.Server;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a JetStream stream, managing message storage, replication, and lifecycle.
|
||||
/// Mirrors the <c>stream</c> struct in server/stream.go.
|
||||
/// </summary>
|
||||
internal sealed class NatsStream : IDisposable
|
||||
{
|
||||
private readonly ReaderWriterLockSlim _mu = new(LockRecursionPolicy.SupportsRecursion);
|
||||
|
||||
public Account Account { get; private set; }
|
||||
public string Name { get; private set; } = string.Empty;
|
||||
public StreamConfig Config { get; private set; } = new();
|
||||
public DateTime Created { get; private set; }
|
||||
internal IStreamStore? Store { get; private set; }
|
||||
|
||||
// Atomic counters — use Interlocked for thread-safe access
|
||||
internal long Msgs;
|
||||
internal long Bytes;
|
||||
internal long FirstSeq;
|
||||
internal long LastSeq;
|
||||
|
||||
internal bool IsMirror;
|
||||
|
||||
private bool _closed;
|
||||
private CancellationTokenSource? _quitCts;
|
||||
|
||||
/// <summary>IRaftNode — stored as object to avoid cross-dependency on Raft session.</summary>
|
||||
private object? _node;
|
||||
|
||||
public NatsStream(Account account, StreamConfig config, DateTime created)
|
||||
{
|
||||
Account = account;
|
||||
Name = config.Name ?? string.Empty;
|
||||
Config = config;
|
||||
Created = created;
|
||||
_quitCts = new CancellationTokenSource();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Factory
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="NatsStream"/> after validating the configuration.
|
||||
/// Returns null if the stream cannot be created (stub: always throws).
|
||||
/// Mirrors <c>newStream</c> / <c>stream.create</c> in server/stream.go.
|
||||
/// </summary>
|
||||
public static NatsStream? Create(
|
||||
Account acc,
|
||||
StreamConfig cfg,
|
||||
object? jsacc,
|
||||
IStreamStore? store,
|
||||
StreamAssignment? sa,
|
||||
object? server)
|
||||
{
|
||||
throw new NotImplementedException("TODO: session 21 — stream");
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Lifecycle
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Stops processing and tears down goroutines / timers.
|
||||
/// Mirrors <c>stream.stop</c> in server/stream.go.
|
||||
/// </summary>
|
||||
public void Stop() =>
|
||||
throw new NotImplementedException("TODO: session 21 — stream");
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the stream and all stored messages permanently.
|
||||
/// Mirrors <c>stream.delete</c> in server/stream.go.
|
||||
/// </summary>
|
||||
public void Delete() =>
|
||||
throw new NotImplementedException("TODO: session 21 — stream");
|
||||
|
||||
/// <summary>
|
||||
/// Purges messages from the stream according to the optional request filter.
|
||||
/// Mirrors <c>stream.purge</c> in server/stream.go.
|
||||
/// </summary>
|
||||
public void Purge(StreamPurgeRequest? req = null) =>
|
||||
throw new NotImplementedException("TODO: session 21 — stream");
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Info / State
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Returns a snapshot of stream info including config, state, and cluster information.
|
||||
/// Mirrors <c>stream.info</c> in server/stream.go.
|
||||
/// </summary>
|
||||
public StreamInfo GetInfo(bool includeDeleted = false) =>
|
||||
throw new NotImplementedException("TODO: session 21 — stream");
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously returns a snapshot of stream info.
|
||||
/// Mirrors <c>stream.info</c> (async path) in server/stream.go.
|
||||
/// </summary>
|
||||
public Task<StreamInfo> GetInfoAsync(bool includeDeleted = false, CancellationToken ct = default) =>
|
||||
throw new NotImplementedException("TODO: session 21 — stream");
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current stream state (message counts, byte totals, sequences).
|
||||
/// Mirrors <c>stream.state</c> in server/stream.go.
|
||||
/// </summary>
|
||||
public StreamState State() =>
|
||||
throw new NotImplementedException("TODO: session 21 — stream");
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Leadership
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Transitions this stream into or out of the leader role.
|
||||
/// Mirrors <c>stream.setLeader</c> in server/stream.go.
|
||||
/// </summary>
|
||||
public void SetLeader(bool isLeader, ulong term) =>
|
||||
throw new NotImplementedException("TODO: session 21 — stream");
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this server is the current stream leader.
|
||||
/// Mirrors <c>stream.isLeader</c> in server/stream.go.
|
||||
/// </summary>
|
||||
public bool IsLeader() =>
|
||||
throw new NotImplementedException("TODO: session 21 — stream");
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Configuration
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Returns the owning account.
|
||||
/// Mirrors <c>stream.account</c> in server/stream.go.
|
||||
/// </summary>
|
||||
public Account GetAccount() =>
|
||||
throw new NotImplementedException("TODO: session 21 — stream");
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current stream configuration.
|
||||
/// Mirrors <c>stream.config</c> in server/stream.go.
|
||||
/// </summary>
|
||||
public StreamConfig GetConfig() =>
|
||||
throw new NotImplementedException("TODO: session 21 — stream");
|
||||
|
||||
/// <summary>
|
||||
/// Applies an updated configuration to the stream.
|
||||
/// Mirrors <c>stream.update</c> in server/stream.go.
|
||||
/// </summary>
|
||||
public void UpdateConfig(StreamConfig config) =>
|
||||
throw new NotImplementedException("TODO: session 21 — stream");
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Sealed state
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the stream is sealed (no new messages accepted).
|
||||
/// Mirrors <c>stream.isSealed</c> in server/stream.go.
|
||||
/// </summary>
|
||||
public bool IsSealed() =>
|
||||
throw new NotImplementedException("TODO: session 21 — stream");
|
||||
|
||||
/// <summary>
|
||||
/// Seals the stream so that no new messages can be stored.
|
||||
/// Mirrors <c>stream.seal</c> in server/stream.go.
|
||||
/// </summary>
|
||||
public void Seal() =>
|
||||
throw new NotImplementedException("TODO: session 21 — stream");
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// IDisposable
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_quitCts?.Cancel();
|
||||
_quitCts?.Dispose();
|
||||
_quitCts = null;
|
||||
_mu.Dispose();
|
||||
}
|
||||
}
|
||||
484
dotnet/src/ZB.MOM.NatsNet.Server/JetStream/StreamTypes.cs
Normal file
484
dotnet/src/ZB.MOM.NatsNet.Server/JetStream/StreamTypes.cs
Normal file
@@ -0,0 +1,484 @@
|
||||
// Copyright 2019-2026 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Adapted from server/stream.go and server/consumer.go in the NATS server Go source.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ZB.MOM.NatsNet.Server;
|
||||
|
||||
// ============================================================================
|
||||
// Stream API types (from stream.go)
|
||||
// ============================================================================
|
||||
|
||||
/// <summary>
|
||||
/// A stream create request that extends <see cref="StreamConfig"/> with a pedantic flag.
|
||||
/// Mirrors <c>streamConfigRequest</c> in server/stream.go.
|
||||
/// </summary>
|
||||
public sealed class StreamConfigRequest
|
||||
{
|
||||
[JsonPropertyName("config")]
|
||||
public StreamConfig Config { get; set; } = new();
|
||||
|
||||
/// <summary>If true, strict validation is applied during stream creation/update.</summary>
|
||||
[JsonPropertyName("pedantic")]
|
||||
public bool Pedantic { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Information about a stream, returned from info requests.
|
||||
/// Mirrors <c>StreamInfo</c> in server/stream.go.
|
||||
/// </summary>
|
||||
public sealed class StreamInfo
|
||||
{
|
||||
[JsonPropertyName("config")]
|
||||
public StreamConfig Config { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("created")]
|
||||
public DateTime Created { get; set; }
|
||||
|
||||
[JsonPropertyName("state")]
|
||||
public StreamState State { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("mirror")]
|
||||
public StreamSourceInfo? Mirror { get; set; }
|
||||
|
||||
[JsonPropertyName("sources")]
|
||||
public StreamSourceInfo[]? Sources { get; set; }
|
||||
|
||||
[JsonPropertyName("cluster")]
|
||||
public ClusterInfo? Cluster { get; set; }
|
||||
|
||||
[JsonPropertyName("mirror_direct")]
|
||||
public bool Mirror_Direct { get; set; }
|
||||
|
||||
[JsonPropertyName("allow_direct")]
|
||||
public bool Allow_Direct { get; set; }
|
||||
|
||||
/// <summary>Alternate cluster name.</summary>
|
||||
[JsonPropertyName("alternates")]
|
||||
public string? Alt { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Information about a stream mirror or source.
|
||||
/// Mirrors <c>StreamSourceInfo</c> in server/stream.go.
|
||||
/// </summary>
|
||||
public sealed class StreamSourceInfo
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("filter_subject")]
|
||||
public string? FilterSubject { get; set; }
|
||||
|
||||
[JsonPropertyName("lag")]
|
||||
public ulong Lag { get; set; }
|
||||
|
||||
[JsonPropertyName("active")]
|
||||
public DateTime? Active { get; set; }
|
||||
|
||||
[JsonPropertyName("external")]
|
||||
public StreamSource? External { get; set; }
|
||||
|
||||
[JsonPropertyName("error")]
|
||||
public string? Error { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request parameters for stream info, allowing filtering.
|
||||
/// Mirrors <c>streamInfoRequest</c> in server/stream.go.
|
||||
/// </summary>
|
||||
public sealed class StreamInfoRequest
|
||||
{
|
||||
[JsonPropertyName("subjects_filter")]
|
||||
public string? SubjectsFilter { get; set; }
|
||||
|
||||
[JsonPropertyName("mirror_check_until")]
|
||||
public string? MirrorCheckUntil { get; set; }
|
||||
|
||||
[JsonPropertyName("deleted_details")]
|
||||
public bool DeletedDetails { get; set; }
|
||||
|
||||
[JsonPropertyName("subjects_detail")]
|
||||
public bool SubjectsDetail { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request parameters for purging a stream.
|
||||
/// Mirrors <c>StreamPurgeRequest</c> in server/stream.go.
|
||||
/// </summary>
|
||||
public sealed class StreamPurgeRequest
|
||||
{
|
||||
[JsonPropertyName("filter")]
|
||||
public string? Filter { get; set; }
|
||||
|
||||
[JsonPropertyName("seq")]
|
||||
public ulong Sequence { get; set; }
|
||||
|
||||
[JsonPropertyName("keep")]
|
||||
public ulong Keep { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request for deleting a specific stream message.
|
||||
/// Mirrors <c>StreamMsgDeleteRequest</c> in server/stream.go.
|
||||
/// </summary>
|
||||
public sealed class StreamMsgDeleteRequest
|
||||
{
|
||||
[JsonPropertyName("seq")]
|
||||
public ulong Seq { get; set; }
|
||||
|
||||
[JsonPropertyName("no_erase")]
|
||||
public bool NoErase { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request for retrieving a specific stream message.
|
||||
/// Mirrors <c>StreamGetMsgRequest</c> in server/stream.go.
|
||||
/// </summary>
|
||||
public sealed class StreamGetMsgRequest
|
||||
{
|
||||
[JsonPropertyName("seq")]
|
||||
public ulong Seq { get; set; }
|
||||
|
||||
[JsonPropertyName("last_by_subj")]
|
||||
public string? LastBySubject { get; set; }
|
||||
|
||||
[JsonPropertyName("next_by_subj")]
|
||||
public string? NextBySubject { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Publish acknowledgement response from JetStream.
|
||||
/// Mirrors <c>JSPubAckResponse</c> in server/stream.go.
|
||||
/// </summary>
|
||||
public sealed class JSPubAckResponse
|
||||
{
|
||||
[JsonPropertyName("stream")]
|
||||
public string Stream { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("seq")]
|
||||
public ulong Seq { get; set; }
|
||||
|
||||
[JsonPropertyName("duplicate")]
|
||||
public bool Duplicate { get; set; }
|
||||
|
||||
[JsonPropertyName("domain")]
|
||||
public string? Domain { get; set; }
|
||||
|
||||
[JsonPropertyName("error")]
|
||||
public JsApiError? PubAckError { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns an exception if the response contains an error, otherwise null.
|
||||
/// Mirrors <c>ToError()</c> helper pattern in NATS Go server.
|
||||
/// </summary>
|
||||
public Exception? ToError()
|
||||
{
|
||||
if (PubAckError is { ErrCode: > 0 })
|
||||
return new InvalidOperationException($"{PubAckError.Description} (errCode={PubAckError.ErrCode})");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A raw published message before JetStream processing.
|
||||
/// Mirrors <c>pubMsg</c> (JetStream variant) in server/stream.go.
|
||||
/// Note: renamed <c>JsStreamPubMsg</c> to avoid collision with the server-level
|
||||
/// <c>PubMsg</c> (events.go) which lives in the same namespace.
|
||||
/// </summary>
|
||||
public sealed class JsStreamPubMsg
|
||||
{
|
||||
public string Subject { get; set; } = string.Empty;
|
||||
public string? Reply { get; set; }
|
||||
public byte[]? Hdr { get; set; }
|
||||
public byte[]? Msg { get; set; }
|
||||
public Dictionary<string, string>? Meta { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A JetStream publish message with sync tracking.
|
||||
/// Mirrors <c>jsPubMsg</c> in server/stream.go.
|
||||
/// </summary>
|
||||
public sealed class JsPubMsg
|
||||
{
|
||||
public string Subject { get; set; } = string.Empty;
|
||||
public string? Reply { get; set; }
|
||||
public byte[]? Hdr { get; set; }
|
||||
public byte[]? Msg { get; set; }
|
||||
|
||||
/// <summary>Publish argument (opaque, set at runtime).</summary>
|
||||
public object? Pa { get; set; }
|
||||
|
||||
/// <summary>Sync/ack channel (opaque, set at runtime).</summary>
|
||||
public object? Sync { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An inbound message to be processed by the JetStream layer.
|
||||
/// Mirrors <c>inMsg</c> in server/stream.go.
|
||||
/// </summary>
|
||||
public sealed class InMsg
|
||||
{
|
||||
public string Subject { get; set; } = string.Empty;
|
||||
public string? Reply { get; set; }
|
||||
public byte[]? Hdr { get; set; }
|
||||
public byte[]? Msg { get; set; }
|
||||
|
||||
/// <summary>The originating client (opaque, set at runtime).</summary>
|
||||
public object? Client { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A cached/clustered message for replication.
|
||||
/// Mirrors <c>cMsg</c> in server/stream.go.
|
||||
/// </summary>
|
||||
public sealed class CMsg
|
||||
{
|
||||
public string Subject { get; set; } = string.Empty;
|
||||
public byte[]? Msg { get; set; }
|
||||
public ulong Seq { get; set; }
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Consumer API types (from consumer.go)
|
||||
// ============================================================================
|
||||
|
||||
/// <summary>
|
||||
/// Information about a consumer, returned from info requests.
|
||||
/// Mirrors <c>ConsumerInfo</c> in server/consumer.go.
|
||||
/// </summary>
|
||||
public sealed class ConsumerInfo
|
||||
{
|
||||
[JsonPropertyName("stream_name")]
|
||||
public string Stream { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("created")]
|
||||
public DateTime Created { get; set; }
|
||||
|
||||
[JsonPropertyName("config")]
|
||||
public ConsumerConfig? Config { get; set; }
|
||||
|
||||
[JsonPropertyName("delivered")]
|
||||
public SequenceInfo Delivered { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("ack_floor")]
|
||||
public SequenceInfo AckFloor { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("num_ack_pending")]
|
||||
public int NumAckPending { get; set; }
|
||||
|
||||
[JsonPropertyName("num_redelivered")]
|
||||
public int NumRedelivered { get; set; }
|
||||
|
||||
[JsonPropertyName("num_waiting")]
|
||||
public int NumWaiting { get; set; }
|
||||
|
||||
[JsonPropertyName("num_pending")]
|
||||
public ulong NumPending { get; set; }
|
||||
|
||||
[JsonPropertyName("cluster")]
|
||||
public ClusterInfo? Cluster { get; set; }
|
||||
|
||||
[JsonPropertyName("push_bound")]
|
||||
public bool PushBound { get; set; }
|
||||
|
||||
[JsonPropertyName("paused")]
|
||||
public bool Paused { get; set; }
|
||||
|
||||
[JsonPropertyName("pause_remaining")]
|
||||
public TimeSpan PauseRemaining { get; set; }
|
||||
|
||||
[JsonPropertyName("ts")]
|
||||
public DateTime TimeStamp { get; set; }
|
||||
|
||||
[JsonPropertyName("priority_groups")]
|
||||
public PriorityGroupState[]? PriorityGroups { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// State information for a priority group on a pull consumer.
|
||||
/// Mirrors <c>PriorityGroupState</c> in server/consumer.go.
|
||||
/// </summary>
|
||||
public sealed class PriorityGroupState
|
||||
{
|
||||
[JsonPropertyName("group")]
|
||||
public string Group { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("pinned_client_id")]
|
||||
public string? PinnedClientId { get; set; }
|
||||
|
||||
[JsonPropertyName("pinned_ts")]
|
||||
public DateTime PinnedTs { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sequence information for consumer delivered/ack_floor positions.
|
||||
/// Mirrors <c>SequenceInfo</c> in server/consumer.go.
|
||||
/// </summary>
|
||||
public sealed class SequenceInfo
|
||||
{
|
||||
[JsonPropertyName("consumer_seq")]
|
||||
public ulong Consumer { get; set; }
|
||||
|
||||
[JsonPropertyName("stream_seq")]
|
||||
public ulong Stream { get; set; }
|
||||
|
||||
[JsonPropertyName("last_active")]
|
||||
public DateTime? Last { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to create or update a consumer.
|
||||
/// Mirrors <c>CreateConsumerRequest</c> in server/consumer.go.
|
||||
/// </summary>
|
||||
public sealed class CreateConsumerRequest
|
||||
{
|
||||
[JsonPropertyName("stream_name")]
|
||||
public string Stream { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("config")]
|
||||
public ConsumerConfig Config { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("action")]
|
||||
public ConsumerAction Action { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the intended action when creating a consumer.
|
||||
/// Mirrors <c>ConsumerAction</c> in server/consumer.go.
|
||||
/// </summary>
|
||||
public enum ConsumerAction
|
||||
{
|
||||
/// <summary>Create a new consumer or update if it already exists.</summary>
|
||||
CreateOrUpdate = 0,
|
||||
|
||||
/// <summary>Create a new consumer; fail if it already exists.</summary>
|
||||
Create = 1,
|
||||
|
||||
/// <summary>Update an existing consumer; fail if it does not exist.</summary>
|
||||
Update = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response for a consumer deletion request.
|
||||
/// Mirrors <c>ConsumerDeleteResponse</c> in server/consumer.go.
|
||||
/// </summary>
|
||||
public sealed class ConsumerDeleteResponse
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A pending pull request waiting in the wait queue.
|
||||
/// Mirrors <c>waitingRequest</c> in server/consumer.go.
|
||||
/// </summary>
|
||||
public sealed class WaitingRequest
|
||||
{
|
||||
public string Subject { get; set; } = string.Empty;
|
||||
public string? Reply { get; set; }
|
||||
|
||||
/// <summary>Number of messages requested.</summary>
|
||||
public int N { get; set; }
|
||||
|
||||
/// <summary>Number of messages delivered so far.</summary>
|
||||
public int D { get; set; }
|
||||
|
||||
/// <summary>No-wait flag (1 = no wait).</summary>
|
||||
public int NoWait { get; set; }
|
||||
|
||||
public DateTime? Expires { get; set; }
|
||||
|
||||
/// <summary>Max byte limit for this batch.</summary>
|
||||
public int MaxBytes { get; set; }
|
||||
|
||||
/// <summary>Bytes accumulated so far.</summary>
|
||||
public int B { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A circular wait queue for pending pull requests.
|
||||
/// Mirrors <c>waitQueue</c> in server/consumer.go.
|
||||
/// </summary>
|
||||
public sealed class WaitQueue
|
||||
{
|
||||
private readonly List<WaitingRequest> _reqs = new();
|
||||
private int _head;
|
||||
private int _tail;
|
||||
|
||||
/// <summary>Number of pending requests in the queue.</summary>
|
||||
public int Len => _reqs.Count;
|
||||
|
||||
/// <summary>Add a waiting request to the tail of the queue.</summary>
|
||||
public void Add(WaitingRequest req) =>
|
||||
throw new NotImplementedException("TODO: session 21");
|
||||
|
||||
/// <summary>Peek at the head request without removing it.</summary>
|
||||
public WaitingRequest? Peek() =>
|
||||
throw new NotImplementedException("TODO: session 21");
|
||||
|
||||
/// <summary>Remove and return the head request.</summary>
|
||||
public WaitingRequest? Pop() =>
|
||||
throw new NotImplementedException("TODO: session 21");
|
||||
|
||||
/// <summary>Compact the internal backing list to reclaim removed slots.</summary>
|
||||
public void Compress() =>
|
||||
throw new NotImplementedException("TODO: session 21");
|
||||
|
||||
/// <summary>Returns true if the queue is at capacity (head == tail when full).</summary>
|
||||
public bool IsFull(int max) =>
|
||||
throw new NotImplementedException("TODO: session 21");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cluster membership and leadership information for a stream or consumer.
|
||||
/// Mirrors <c>ClusterInfo</c> in server/consumer.go and server/stream.go.
|
||||
/// </summary>
|
||||
public sealed class ClusterInfo
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string? Name { get; set; }
|
||||
|
||||
[JsonPropertyName("leader")]
|
||||
public string? Leader { get; set; }
|
||||
|
||||
[JsonPropertyName("replicas")]
|
||||
public PeerInfo[]? Replicas { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Information about a peer in a JetStream Raft group.
|
||||
/// Mirrors <c>PeerInfo</c> in server/consumer.go and server/stream.go.
|
||||
/// </summary>
|
||||
public sealed class PeerInfo
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("current")]
|
||||
public bool Current { get; set; }
|
||||
|
||||
[JsonPropertyName("offline")]
|
||||
public bool Offline { get; set; }
|
||||
|
||||
[JsonPropertyName("active")]
|
||||
public TimeSpan Active { get; set; }
|
||||
|
||||
[JsonPropertyName("lag")]
|
||||
public ulong Lag { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user