diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JetStreamApiTypes.cs b/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JetStreamApiTypes.cs
new file mode 100644
index 0000000..e7bf85a
--- /dev/null
+++ b/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JetStreamApiTypes.cs
@@ -0,0 +1,620 @@
+// Copyright 2020-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/jetstream_api.go in the NATS server Go source.
+
+using System.Text.Json.Serialization;
+
+namespace ZB.MOM.NatsNet.Server;
+
+// ---------------------------------------------------------------------------
+// Forward stubs for types defined in later sessions
+// ---------------------------------------------------------------------------
+
+/// Stub: full definition in session 20 (stream.go).
+public sealed class StreamInfo { }
+
+/// Stub: full definition in session 20 (consumer.go).
+public sealed class ConsumerInfo { }
+
+/// Stub: stored message type — full definition in session 20.
+public sealed class StoredMsg { }
+
+/// Priority group for pull consumers — full definition in session 20.
+public sealed class PriorityGroup { }
+
+// ---------------------------------------------------------------------------
+// API subject constants
+// ---------------------------------------------------------------------------
+
+///
+/// JetStream API subject constants.
+/// Mirrors the const block at the top of server/jetstream_api.go.
+///
+public static class JsApiSubjects
+{
+ public const string JsAllApi = "$JS.API.>";
+ public const string JsApiPrefix = "$JS.API";
+ public const string JsApiAccountInfo = "$JS.API.INFO";
+
+ public const string JsApiStreamCreate = "$JS.API.STREAM.CREATE.*";
+ public const string JsApiStreamCreateT = "$JS.API.STREAM.CREATE.{0}";
+ public const string JsApiStreamUpdate = "$JS.API.STREAM.UPDATE.*";
+ public const string JsApiStreamUpdateT = "$JS.API.STREAM.UPDATE.{0}";
+ public const string JsApiStreams = "$JS.API.STREAM.NAMES";
+ public const string JsApiStreamList = "$JS.API.STREAM.LIST";
+ public const string JsApiStreamInfo = "$JS.API.STREAM.INFO.*";
+ public const string JsApiStreamInfoT = "$JS.API.STREAM.INFO.{0}";
+ public const string JsApiStreamDelete = "$JS.API.STREAM.DELETE.*";
+ public const string JsApiStreamDeleteT = "$JS.API.STREAM.DELETE.{0}";
+ public const string JsApiStreamPurge = "$JS.API.STREAM.PURGE.*";
+ public const string JsApiStreamPurgeT = "$JS.API.STREAM.PURGE.{0}";
+ public const string JsApiStreamSnapshot = "$JS.API.STREAM.SNAPSHOT.*";
+ public const string JsApiStreamSnapshotT = "$JS.API.STREAM.SNAPSHOT.{0}";
+ public const string JsApiStreamRestore = "$JS.API.STREAM.RESTORE.*";
+ public const string JsApiStreamRestoreT = "$JS.API.STREAM.RESTORE.{0}";
+ public const string JsApiMsgDelete = "$JS.API.STREAM.MSG.DELETE.*";
+ public const string JsApiMsgDeleteT = "$JS.API.STREAM.MSG.DELETE.{0}";
+ public const string JsApiMsgGet = "$JS.API.STREAM.MSG.GET.*";
+ public const string JsApiMsgGetT = "$JS.API.STREAM.MSG.GET.{0}";
+ public const string JsDirectMsgGet = "$JS.API.DIRECT.GET.*";
+ public const string JsDirectMsgGetT = "$JS.API.DIRECT.GET.{0}";
+ public const string JsDirectGetLastBySubject = "$JS.API.DIRECT.GET.*.>";
+ public const string JsDirectGetLastBySubjectT = "$JS.API.DIRECT.GET.{0}.{1}";
+
+ public const string JsApiConsumerCreate = "$JS.API.CONSUMER.CREATE.*";
+ public const string JsApiConsumerCreateT = "$JS.API.CONSUMER.CREATE.{0}";
+ public const string JsApiConsumerCreateEx = "$JS.API.CONSUMER.CREATE.*.>";
+ public const string JsApiConsumerCreateExT = "$JS.API.CONSUMER.CREATE.{0}.{1}.{2}";
+ public const string JsApiDurableCreate = "$JS.API.CONSUMER.DURABLE.CREATE.*.*";
+ public const string JsApiDurableCreateT = "$JS.API.CONSUMER.DURABLE.CREATE.{0}.{1}";
+ public const string JsApiConsumers = "$JS.API.CONSUMER.NAMES.*";
+ public const string JsApiConsumersT = "$JS.API.CONSUMER.NAMES.{0}";
+ public const string JsApiConsumerList = "$JS.API.CONSUMER.LIST.*";
+ public const string JsApiConsumerListT = "$JS.API.CONSUMER.LIST.{0}";
+ public const string JsApiConsumerInfo = "$JS.API.CONSUMER.INFO.*.*";
+ public const string JsApiConsumerInfoT = "$JS.API.CONSUMER.INFO.{0}.{1}";
+ public const string JsApiConsumerDelete = "$JS.API.CONSUMER.DELETE.*.*";
+ public const string JsApiConsumerDeleteT = "$JS.API.CONSUMER.DELETE.{0}.{1}";
+ public const string JsApiConsumerPause = "$JS.API.CONSUMER.PAUSE.*.*";
+ public const string JsApiConsumerPauseT = "$JS.API.CONSUMER.PAUSE.{0}.{1}";
+ public const string JsApiRequestNextT = "$JS.API.CONSUMER.MSG.NEXT.{0}.{1}";
+ public const string JsApiConsumerResetT = "$JS.API.CONSUMER.RESET.{0}.{1}";
+ public const string JsApiConsumerUnpin = "$JS.API.CONSUMER.UNPIN.*.*";
+ public const string JsApiConsumerUnpinT = "$JS.API.CONSUMER.UNPIN.{0}.{1}";
+
+ public const string JsApiStreamRemovePeer = "$JS.API.STREAM.PEER.REMOVE.*";
+ public const string JsApiStreamRemovePeerT = "$JS.API.STREAM.PEER.REMOVE.{0}";
+ public const string JsApiStreamLeaderStepDown = "$JS.API.STREAM.LEADER.STEPDOWN.*";
+ public const string JsApiStreamLeaderStepDownT = "$JS.API.STREAM.LEADER.STEPDOWN.{0}";
+ public const string JsApiConsumerLeaderStepDown = "$JS.API.CONSUMER.LEADER.STEPDOWN.*.*";
+ public const string JsApiConsumerLeaderStepDownT = "$JS.API.CONSUMER.LEADER.STEPDOWN.{0}.{1}";
+ public const string JsApiLeaderStepDown = "$JS.API.META.LEADER.STEPDOWN";
+ public const string JsApiRemoveServer = "$JS.API.SERVER.REMOVE";
+ public const string JsApiAccountPurge = "$JS.API.ACCOUNT.PURGE.*";
+ public const string JsApiAccountPurgeT = "$JS.API.ACCOUNT.PURGE.{0}";
+ public const string JsApiServerStreamMove = "$JS.API.ACCOUNT.STREAM.MOVE.*.*";
+ public const string JsApiServerStreamMoveT = "$JS.API.ACCOUNT.STREAM.MOVE.{0}.{1}";
+ public const string JsApiServerStreamCancelMove = "$JS.API.ACCOUNT.STREAM.CANCEL_MOVE.*.*";
+ public const string JsApiServerStreamCancelMoveT = "$JS.API.ACCOUNT.STREAM.CANCEL_MOVE.{0}.{1}";
+
+ // Advisory/metric subjects
+ public const string JsAdvisoryPrefix = "$JS.EVENT.ADVISORY";
+ public const string JsMetricPrefix = "$JS.EVENT.METRIC";
+ public const string JsMetricConsumerAckPre = "$JS.EVENT.METRIC.CONSUMER.ACK";
+ public const string JsAdvisoryConsumerMaxDelivery = "$JS.EVENT.ADVISORY.CONSUMER.MAX_DELIVERIES";
+ public const string JsAdvisoryConsumerMsgNak = "$JS.EVENT.ADVISORY.CONSUMER.MSG_NAKED";
+ public const string JsAdvisoryConsumerMsgTerminated = "$JS.EVENT.ADVISORY.CONSUMER.MSG_TERMINATED";
+ public const string JsAdvisoryStreamCreated = "$JS.EVENT.ADVISORY.STREAM.CREATED";
+ public const string JsAdvisoryStreamDeleted = "$JS.EVENT.ADVISORY.STREAM.DELETED";
+ public const string JsAdvisoryStreamUpdated = "$JS.EVENT.ADVISORY.STREAM.UPDATED";
+ public const string JsAdvisoryConsumerCreated = "$JS.EVENT.ADVISORY.CONSUMER.CREATED";
+ public const string JsAdvisoryConsumerDeleted = "$JS.EVENT.ADVISORY.CONSUMER.DELETED";
+ public const string JsAdvisoryConsumerPause = "$JS.EVENT.ADVISORY.CONSUMER.PAUSE";
+ public const string JsAdvisoryConsumerPinned = "$JS.EVENT.ADVISORY.CONSUMER.PINNED";
+ public const string JsAdvisoryConsumerUnpinned = "$JS.EVENT.ADVISORY.CONSUMER.UNPINNED";
+ public const string JsAdvisoryStreamSnapshotCreate = "$JS.EVENT.ADVISORY.STREAM.SNAPSHOT_CREATE";
+ public const string JsAdvisoryStreamSnapshotComplete = "$JS.EVENT.ADVISORY.STREAM.SNAPSHOT_COMPLETE";
+ public const string JsAdvisoryStreamRestoreCreate = "$JS.EVENT.ADVISORY.STREAM.RESTORE_CREATE";
+ public const string JsAdvisoryStreamRestoreComplete = "$JS.EVENT.ADVISORY.STREAM.RESTORE_COMPLETE";
+ public const string JsAdvisoryDomainLeaderElected = "$JS.EVENT.ADVISORY.DOMAIN.LEADER_ELECTED";
+ public const string JsAdvisoryStreamLeaderElected = "$JS.EVENT.ADVISORY.STREAM.LEADER_ELECTED";
+ public const string JsAdvisoryStreamQuorumLost = "$JS.EVENT.ADVISORY.STREAM.QUORUM_LOST";
+ public const string JsAdvisoryStreamBatchAbandoned = "$JS.EVENT.ADVISORY.STREAM.BATCH_ABANDONED";
+ public const string JsAdvisoryConsumerLeaderElected = "$JS.EVENT.ADVISORY.CONSUMER.LEADER_ELECTED";
+ public const string JsAdvisoryConsumerQuorumLost = "$JS.EVENT.ADVISORY.CONSUMER.QUORUM_LOST";
+ public const string JsAdvisoryServerOutOfStorage = "$JS.EVENT.ADVISORY.SERVER.OUT_OF_STORAGE";
+ public const string JsAdvisoryServerRemoved = "$JS.EVENT.ADVISORY.SERVER.REMOVED";
+ public const string JsAdvisoryApiLimitReached = "$JS.EVENT.ADVISORY.API.LIMIT_REACHED";
+ public const string JsAuditAdvisory = "$JS.EVENT.ADVISORY.API";
+
+ // Response type strings
+ public const string JsApiSystemResponseType = "io.nats.jetstream.api.v1.system_response";
+ public const string JsApiOverloadedType = "io.nats.jetstream.api.v1.system_overloaded";
+ public const string JsApiAccountInfoResponseType = "io.nats.jetstream.api.v1.account_info_response";
+ public const string JsApiStreamCreateResponseType = "io.nats.jetstream.api.v1.stream_create_response";
+ public const string JsApiStreamDeleteResponseType = "io.nats.jetstream.api.v1.stream_delete_response";
+ public const string JsApiStreamInfoResponseType = "io.nats.jetstream.api.v1.stream_info_response";
+ public const string JsApiStreamNamesResponseType = "io.nats.jetstream.api.v1.stream_names_response";
+ public const string JsApiStreamListResponseType = "io.nats.jetstream.api.v1.stream_list_response";
+ public const string JsApiStreamPurgeResponseType = "io.nats.jetstream.api.v1.stream_purge_response";
+ public const string JsApiStreamUpdateResponseType = "io.nats.jetstream.api.v1.stream_update_response";
+ public const string JsApiMsgDeleteResponseType = "io.nats.jetstream.api.v1.stream_msg_delete_response";
+ public const string JsApiStreamSnapshotResponseType = "io.nats.jetstream.api.v1.stream_snapshot_response";
+ public const string JsApiStreamRestoreResponseType = "io.nats.jetstream.api.v1.stream_restore_response";
+ public const string JsApiStreamRemovePeerResponseType = "io.nats.jetstream.api.v1.stream_remove_peer_response";
+ public const string JsApiStreamLeaderStepDownResponseType = "io.nats.jetstream.api.v1.stream_leader_stepdown_response";
+ public const string JsApiConsumerLeaderStepDownResponseType = "io.nats.jetstream.api.v1.consumer_leader_stepdown_response";
+ public const string JsApiLeaderStepDownResponseType = "io.nats.jetstream.api.v1.meta_leader_stepdown_response";
+ public const string JsApiMetaServerRemoveResponseType = "io.nats.jetstream.api.v1.meta_server_remove_response";
+ public const string JsApiAccountPurgeResponseType = "io.nats.jetstream.api.v1.account_purge_response";
+ public const string JsApiMsgGetResponseType = "io.nats.jetstream.api.v1.stream_msg_get_response";
+ public const string JsApiConsumerCreateResponseType = "io.nats.jetstream.api.v1.consumer_create_response";
+ public const string JsApiConsumerDeleteResponseType = "io.nats.jetstream.api.v1.consumer_delete_response";
+ public const string JsApiConsumerPauseResponseType = "io.nats.jetstream.api.v1.consumer_pause_response";
+ public const string JsApiConsumerInfoResponseType = "io.nats.jetstream.api.v1.consumer_info_response";
+ public const string JsApiConsumerNamesResponseType = "io.nats.jetstream.api.v1.consumer_names_response";
+ public const string JsApiConsumerListResponseType = "io.nats.jetstream.api.v1.consumer_list_response";
+ public const string JsApiConsumerUnpinResponseType = "io.nats.jetstream.api.v1.consumer_unpin_response";
+ public const string JsApiConsumerResetResponseType = "io.nats.jetstream.api.v1.consumer_reset_response";
+
+ // Limits
+ public const int JsApiNamesLimit = 1024;
+ public const int JsApiListLimit = 256;
+ public const int JsMaxSubjectDetails = 100_000;
+ public const int JsWaitQueueDefaultMax = 512;
+ public const int JsMaxDescriptionLen = 4 * 1024;
+ public const int JsMaxMetadataLen = 128 * 1024;
+ public const int JsMaxNameLen = 255;
+ public const int JsDefaultRequestQueueLimit = 10_000;
+
+ // Request headers
+ public const string JsRequiredApiLevel = "Nats-Required-Api-Level";
+}
+
+// ---------------------------------------------------------------------------
+// Base API types
+// ---------------------------------------------------------------------------
+
+///
+/// Standard base response from the JetStream JSON API.
+/// Mirrors ApiResponse in server/jetstream_api.go.
+///
+public sealed class ApiResponse
+{
+ [JsonPropertyName("type")] public string? Type { get; set; }
+ [JsonPropertyName("error")] public JsApiError? Error { get; set; }
+
+ /// Returns the as an exception, or null if none.
+ public Exception? ToError() =>
+ Error is null ? null : new InvalidOperationException($"{Error.Description} ({Error.ErrCode})");
+}
+
+///
+/// Paged response metadata included in list responses.
+/// Mirrors ApiPaged in server/jetstream_api.go.
+///
+public sealed class ApiPaged
+{
+ [JsonPropertyName("total")] public int Total { get; set; }
+ [JsonPropertyName("offset")] public int Offset { get; set; }
+ [JsonPropertyName("limit")] public int Limit { get; set; }
+}
+
+///
+/// Request parameters for paged API responses.
+/// Mirrors ApiPagedRequest in server/jetstream_api.go.
+///
+public sealed class ApiPagedRequest
+{
+ [JsonPropertyName("offset")] public int Offset { get; set; }
+}
+
+// ---------------------------------------------------------------------------
+// Account
+// ---------------------------------------------------------------------------
+
+/// Account info response.
+public sealed class JsApiAccountInfoResponse
+{
+ [JsonPropertyName("type")] public string? Type { get; set; }
+ [JsonPropertyName("error")] public JsApiError? Error { get; set; }
+
+ // JetStreamAccountStats fields (embedded in Go)
+ [JsonPropertyName("memory")] public ulong Memory { get; set; }
+ [JsonPropertyName("storage")] public ulong Store { get; set; }
+ [JsonPropertyName("reserved_memory")] public ulong ReservedMemory { get; set; }
+ [JsonPropertyName("reserved_storage")]public ulong ReservedStore { get; set; }
+ [JsonPropertyName("streams")] public int Streams { get; set; }
+ [JsonPropertyName("consumers")] public int Consumers { get; set; }
+ [JsonPropertyName("limits")] public JetStreamAccountLimits Limits { get; set; } = new();
+ [JsonPropertyName("domain")] public string? Domain { get; set; }
+ [JsonPropertyName("api")] public JetStreamApiStats Api { get; set; } = new();
+ [JsonPropertyName("tiers")] public Dictionary? Tiers { get; set; }
+}
+
+// ---------------------------------------------------------------------------
+// Stream API types
+// ---------------------------------------------------------------------------
+
+/// Stream creation response.
+public sealed class JsApiStreamCreateResponse
+{
+ [JsonPropertyName("type")] public string? Type { get; set; }
+ [JsonPropertyName("error")] public JsApiError? Error { get; set; }
+ [JsonPropertyName("config")] public StreamConfig? Config { get; set; }
+ [JsonPropertyName("state")] public StreamState? State { get; set; }
+ [JsonPropertyName("did_create")] public bool DidCreate { get; set; }
+}
+
+/// Stream deletion response.
+public sealed class JsApiStreamDeleteResponse
+{
+ [JsonPropertyName("type")] public string? Type { get; set; }
+ [JsonPropertyName("error")] public JsApiError? Error { get; set; }
+ [JsonPropertyName("success")] public bool Success { get; set; }
+}
+
+/// Stream info request with optional filtering.
+public sealed class JsApiStreamInfoRequest
+{
+ [JsonPropertyName("offset")] public int Offset { get; set; }
+ [JsonPropertyName("deleted_details")] public bool DeletedDetails { get; set; }
+ [JsonPropertyName("subjects_filter")] public string? SubjectsFilter { get; set; }
+}
+
+/// Stream info response.
+public sealed class JsApiStreamInfoResponse
+{
+ [JsonPropertyName("type")] public string? Type { get; set; }
+ [JsonPropertyName("error")] public JsApiError? Error { get; set; }
+ [JsonPropertyName("total")] public int Total { get; set; }
+ [JsonPropertyName("offset")] public int Offset { get; set; }
+ [JsonPropertyName("limit")] public int Limit { get; set; }
+ // StreamInfo fields embedded — delegated to StreamInfo stub for now
+ public StreamInfo? Info { get; set; }
+}
+
+/// Stream names list request.
+public sealed class JsApiStreamNamesRequest
+{
+ [JsonPropertyName("offset")] public int Offset { get; set; }
+ [JsonPropertyName("subject")] public string? Subject { get; set; }
+}
+
+/// Stream names list response.
+public sealed class JsApiStreamNamesResponse
+{
+ [JsonPropertyName("type")] public string? Type { get; set; }
+ [JsonPropertyName("error")] public JsApiError? Error { get; set; }
+ [JsonPropertyName("total")] public int Total { get; set; }
+ [JsonPropertyName("offset")] public int Offset { get; set; }
+ [JsonPropertyName("limit")] public int Limit { get; set; }
+ [JsonPropertyName("streams")] public List? Streams { get; set; }
+}
+
+/// Detailed stream list request.
+public sealed class JsApiStreamListRequest
+{
+ [JsonPropertyName("offset")] public int Offset { get; set; }
+ [JsonPropertyName("subject")] public string? Subject { get; set; }
+}
+
+/// Detailed stream list response.
+public sealed class JsApiStreamListResponse
+{
+ [JsonPropertyName("type")] public string? Type { get; set; }
+ [JsonPropertyName("error")] public JsApiError? Error { get; set; }
+ [JsonPropertyName("total")] public int Total { get; set; }
+ [JsonPropertyName("offset")] public int Offset { get; set; }
+ [JsonPropertyName("limit")] public int Limit { get; set; }
+ [JsonPropertyName("streams")] public List? Streams { get; set; }
+ [JsonPropertyName("missing")] public List? Missing { get; set; }
+ [JsonPropertyName("offline")] public Dictionary? Offline { get; set; }
+}
+
+/// Stream purge request.
+public sealed class JsApiStreamPurgeRequest
+{
+ [JsonPropertyName("seq")] public ulong Sequence { get; set; }
+ [JsonPropertyName("filter")] public string? Subject { get; set; }
+ [JsonPropertyName("keep")] public ulong Keep { get; set; }
+}
+
+/// Stream purge response.
+public sealed class JsApiStreamPurgeResponse
+{
+ [JsonPropertyName("type")] public string? Type { get; set; }
+ [JsonPropertyName("error")] public JsApiError? Error { get; set; }
+ [JsonPropertyName("success")] public bool Success { get; set; }
+ [JsonPropertyName("purged")] public ulong Purged { get; set; }
+}
+
+/// Stream update response.
+public sealed class JsApiStreamUpdateResponse
+{
+ [JsonPropertyName("type")] public string? Type { get; set; }
+ [JsonPropertyName("error")] public JsApiError? Error { get; set; }
+ public StreamInfo? Info { get; set; }
+}
+
+/// Message deletion request.
+public sealed class JsApiMsgDeleteRequest
+{
+ [JsonPropertyName("seq")] public ulong Seq { get; set; }
+ [JsonPropertyName("no_erase")] public bool NoErase { get; set; }
+}
+
+/// Message deletion response.
+public sealed class JsApiMsgDeleteResponse
+{
+ [JsonPropertyName("type")] public string? Type { get; set; }
+ [JsonPropertyName("error")] public JsApiError? Error { get; set; }
+ [JsonPropertyName("success")] public bool Success { get; set; }
+}
+
+/// Stream snapshot request.
+public sealed class JsApiStreamSnapshotRequest
+{
+ [JsonPropertyName("deliver_subject")] public string DeliverSubject { get; set; } = string.Empty;
+ [JsonPropertyName("no_consumers")] public bool NoConsumers { get; set; }
+ [JsonPropertyName("chunk_size")] public int ChunkSize { get; set; }
+ [JsonPropertyName("window_size")] public int WindowSize { get; set; }
+ [JsonPropertyName("jsck")] public bool CheckMsgs { get; set; }
+}
+
+/// Direct stream snapshot response.
+public sealed class JsApiStreamSnapshotResponse
+{
+ [JsonPropertyName("type")] public string? Type { get; set; }
+ [JsonPropertyName("error")] public JsApiError? Error { get; set; }
+ [JsonPropertyName("config")] public StreamConfig? Config { get; set; }
+ [JsonPropertyName("state")] public StreamState? State { get; set; }
+}
+
+/// Stream restore request.
+public sealed class JsApiStreamRestoreRequest
+{
+ [JsonPropertyName("config")] public StreamConfig Config { get; set; } = new();
+ [JsonPropertyName("state")] public StreamState State { get; set; } = new();
+}
+
+/// Stream restore response.
+public sealed class JsApiStreamRestoreResponse
+{
+ [JsonPropertyName("type")] public string? Type { get; set; }
+ [JsonPropertyName("error")] public JsApiError? Error { get; set; }
+ [JsonPropertyName("deliver_subject")] public string DeliverSubject { get; set; } = string.Empty;
+}
+
+/// Remove a peer from a stream.
+public sealed class JsApiStreamRemovePeerRequest
+{
+ [JsonPropertyName("peer")] public string Peer { get; set; } = string.Empty;
+}
+
+/// Response to remove peer request.
+public sealed class JsApiStreamRemovePeerResponse
+{
+ [JsonPropertyName("type")] public string? Type { get; set; }
+ [JsonPropertyName("error")] public JsApiError? Error { get; set; }
+ [JsonPropertyName("success")] public bool Success { get; set; }
+}
+
+/// Response to stream leader step-down.
+public sealed class JsApiStreamLeaderStepDownResponse
+{
+ [JsonPropertyName("type")] public string? Type { get; set; }
+ [JsonPropertyName("error")] public JsApiError? Error { get; set; }
+ [JsonPropertyName("success")] public bool Success { get; set; }
+}
+
+/// Response to consumer leader step-down.
+public sealed class JsApiConsumerLeaderStepDownResponse
+{
+ [JsonPropertyName("type")] public string? Type { get; set; }
+ [JsonPropertyName("error")] public JsApiError? Error { get; set; }
+ [JsonPropertyName("success")] public bool Success { get; set; }
+}
+
+/// Meta-leader step-down request with optional placement.
+public sealed class JsApiLeaderStepdownRequest
+{
+ [JsonPropertyName("placement")] public Placement? Placement { get; set; }
+}
+
+/// Response to meta-leader step-down.
+public sealed class JsApiLeaderStepDownResponse
+{
+ [JsonPropertyName("type")] public string? Type { get; set; }
+ [JsonPropertyName("error")] public JsApiError? Error { get; set; }
+ [JsonPropertyName("success")] public bool Success { get; set; }
+}
+
+/// Remove a peer server from the meta group.
+public sealed class JsApiMetaServerRemoveRequest
+{
+ [JsonPropertyName("peer")] public string Server { get; set; } = string.Empty;
+ [JsonPropertyName("peer_id")] public string? PeerId { get; set; }
+}
+
+/// Response to peer removal.
+public sealed class JsApiMetaServerRemoveResponse
+{
+ [JsonPropertyName("type")] public string? Type { get; set; }
+ [JsonPropertyName("error")] public JsApiError? Error { get; set; }
+ [JsonPropertyName("success")] public bool Success { get; set; }
+}
+
+/// Request to move a stream off a server.
+public sealed class JsApiMetaServerStreamMoveRequest
+{
+ [JsonPropertyName("server")] public string? Server { get; set; }
+ [JsonPropertyName("cluster")] public string? Cluster { get; set; }
+ [JsonPropertyName("domain")] public string? Domain { get; set; }
+ [JsonPropertyName("tags")] public string[]? Tags { get; set; }
+}
+
+/// Account purge response.
+public sealed class JsApiAccountPurgeResponse
+{
+ [JsonPropertyName("type")] public string? Type { get; set; }
+ [JsonPropertyName("error")] public JsApiError? Error { get; set; }
+ [JsonPropertyName("initiated")] public bool Initiated { get; set; }
+}
+
+// ---------------------------------------------------------------------------
+// Message get
+// ---------------------------------------------------------------------------
+
+///
+/// Direct message get request (by sequence, last-for-subject, next-for-subject, or batch).
+/// Mirrors JSApiMsgGetRequest in server/jetstream_api.go.
+///
+public sealed class JsApiMsgGetRequest
+{
+ [JsonPropertyName("seq")] public ulong Seq { get; set; }
+ [JsonPropertyName("last_by_subj")] public string? LastFor { get; set; }
+ [JsonPropertyName("next_by_subj")] public string? NextFor { get; set; }
+ [JsonPropertyName("batch")] public int Batch { get; set; }
+ [JsonPropertyName("max_bytes")] public int MaxBytes { get; set; }
+ [JsonPropertyName("start_time")] public DateTime? StartTime { get; set; }
+ [JsonPropertyName("multi_last")] public string[]? MultiLastFor { get; set; }
+ [JsonPropertyName("up_to_seq")] public ulong UpToSeq { get; set; }
+ [JsonPropertyName("up_to_time")] public DateTime? UpToTime { get; set; }
+ [JsonPropertyName("no_hdr")] public bool NoHeaders { get; set; }
+}
+
+/// Message get response.
+public sealed class JsApiMsgGetResponse
+{
+ [JsonPropertyName("type")] public string? Type { get; set; }
+ [JsonPropertyName("error")] public JsApiError? Error { get; set; }
+ [JsonPropertyName("message")] public StoredMsg? Message { get; set; }
+}
+
+// ---------------------------------------------------------------------------
+// Consumer API types
+// ---------------------------------------------------------------------------
+
+/// Consumer create/update response.
+public sealed class JsApiConsumerCreateResponse
+{
+ [JsonPropertyName("type")] public string? Type { get; set; }
+ [JsonPropertyName("error")] public JsApiError? Error { get; set; }
+ public ConsumerInfo? Info { get; set; }
+}
+
+/// Consumer delete response.
+public sealed class JsApiConsumerDeleteResponse
+{
+ [JsonPropertyName("type")] public string? Type { get; set; }
+ [JsonPropertyName("error")] public JsApiError? Error { get; set; }
+ [JsonPropertyName("success")] public bool Success { get; set; }
+}
+
+/// Consumer pause request.
+public sealed class JsApiConsumerPauseRequest
+{
+ [JsonPropertyName("pause_until")] public DateTime PauseUntil { get; set; }
+}
+
+/// Consumer pause response.
+public sealed class JsApiConsumerPauseResponse
+{
+ [JsonPropertyName("type")] public string? Type { get; set; }
+ [JsonPropertyName("error")] public JsApiError? Error { get; set; }
+ [JsonPropertyName("paused")] public bool Paused { get; set; }
+ [JsonPropertyName("pause_until")] public DateTime PauseUntil { get; set; }
+ [JsonPropertyName("pause_remaining")] public TimeSpan PauseRemaining { get; set; }
+}
+
+/// Consumer info response.
+public sealed class JsApiConsumerInfoResponse
+{
+ [JsonPropertyName("type")] public string? Type { get; set; }
+ [JsonPropertyName("error")] public JsApiError? Error { get; set; }
+ public ConsumerInfo? Info { get; set; }
+}
+
+/// Consumer names request (paged).
+public sealed class JsApiConsumersRequest
+{
+ [JsonPropertyName("offset")] public int Offset { get; set; }
+}
+
+/// Consumer names list response.
+public sealed class JsApiConsumerNamesResponse
+{
+ [JsonPropertyName("type")] public string? Type { get; set; }
+ [JsonPropertyName("error")] public JsApiError? Error { get; set; }
+ [JsonPropertyName("total")] public int Total { get; set; }
+ [JsonPropertyName("offset")] public int Offset { get; set; }
+ [JsonPropertyName("limit")] public int Limit { get; set; }
+ [JsonPropertyName("consumers")] public List? Consumers { get; set; }
+}
+
+/// Consumer list response.
+public sealed class JsApiConsumerListResponse
+{
+ [JsonPropertyName("type")] public string? Type { get; set; }
+ [JsonPropertyName("error")] public JsApiError? Error { get; set; }
+ [JsonPropertyName("total")] public int Total { get; set; }
+ [JsonPropertyName("offset")] public int Offset { get; set; }
+ [JsonPropertyName("limit")] public int Limit { get; set; }
+ [JsonPropertyName("consumers")] public List? Consumers { get; set; }
+ [JsonPropertyName("missing")] public List? Missing { get; set; }
+ [JsonPropertyName("offline")] public Dictionary? Offline { get; set; }
+}
+
+///
+/// Pull consumer next-message request.
+/// Mirrors JSApiConsumerGetNextRequest in server/jetstream_api.go.
+///
+public sealed class JsApiConsumerGetNextRequest
+{
+ [JsonPropertyName("expires")] public TimeSpan Expires { get; set; }
+ [JsonPropertyName("batch")] public int Batch { get; set; }
+ [JsonPropertyName("max_bytes")] public int MaxBytes { get; set; }
+ [JsonPropertyName("no_wait")] public bool NoWait { get; set; }
+ [JsonPropertyName("idle_heartbeat")] public TimeSpan Heartbeat { get; set; }
+ public PriorityGroup? Priority { get; set; }
+}
+
+/// Consumer reset (seek to sequence) request.
+public sealed class JsApiConsumerResetRequest
+{
+ [JsonPropertyName("seq")] public ulong Seq { get; set; }
+}
+
+/// Consumer reset response.
+public sealed class JsApiConsumerResetResponse
+{
+ [JsonPropertyName("type")] public string? Type { get; set; }
+ [JsonPropertyName("error")] public JsApiError? Error { get; set; }
+ [JsonPropertyName("reset_seq")] public ulong ResetSeq { get; set; }
+ public ConsumerInfo? Info { get; set; }
+}
+
+/// Consumer unpin (priority group) request.
+public sealed class JsApiConsumerUnpinRequest
+{
+ [JsonPropertyName("group")] public string Group { get; set; } = string.Empty;
+}
+
+/// Consumer unpin response.
+public sealed class JsApiConsumerUnpinResponse
+{
+ [JsonPropertyName("type")] public string? Type { get; set; }
+ [JsonPropertyName("error")] public JsApiError? Error { get; set; }
+}
diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JetStreamBatching.cs b/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JetStreamBatching.cs
new file mode 100644
index 0000000..53ca9e7
--- /dev/null
+++ b/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JetStreamBatching.cs
@@ -0,0 +1,132 @@
+// Copyright 2025 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/jetstream_batching.go in the NATS server Go source.
+
+namespace ZB.MOM.NatsNet.Server;
+
+// ---------------------------------------------------------------------------
+// Batching types
+// ---------------------------------------------------------------------------
+
+///
+/// Tracks in-progress atomic publish batch groups for a stream.
+/// Mirrors the batching struct in server/jetstream_batching.go.
+///
+internal sealed class Batching
+{
+ private readonly Lock _mu = new();
+ private readonly Dictionary _group = new(StringComparer.Ordinal);
+
+ public Lock Mu => _mu;
+ public Dictionary Group => _group;
+}
+
+///
+/// A single in-progress atomic batch: its temporary store and cleanup timer.
+/// Mirrors batchGroup in server/jetstream_batching.go.
+///
+internal sealed class BatchGroup
+{
+ /// Last proposed stream sequence for this batch.
+ public ulong LastSeq { get; set; }
+
+ /// Temporary backing store for the batch's messages.
+ public object? Store { get; set; } // IStreamStore — session 20
+
+ /// Timer that abandons the batch after the configured timeout.
+ public Timer? BatchTimer { get; set; }
+
+ ///
+ /// Stops the cleanup timer and flushes pending writes so the batch is
+ /// ready to be committed.
+ /// Mirrors batchGroup.readyForCommit.
+ ///
+ public bool ReadyForCommit()
+ {
+ // Stub — full implementation requires IStreamStore.FlushAllPending (session 20).
+ return BatchTimer?.Change(Timeout.Infinite, Timeout.Infinite) != null;
+ }
+}
+
+///
+/// Stages consistency-check data for a single atomic batch before it is committed.
+/// Mirrors batchStagedDiff in server/jetstream_batching.go.
+///
+internal sealed class BatchStagedDiff
+{
+ /// Message IDs seen in this batch, for duplicate detection.
+ public Dictionary? MsgIds { get; set; }
+
+ /// Running counter totals, keyed by subject.
+ public Dictionary? Counter { get; set; } // map[string]*msgCounterRunningTotal
+
+ /// Inflight subject byte/op totals for DiscardNew checks.
+ public Dictionary? Inflight { get; set; } // map[string]*inflightSubjectRunningTotal
+
+ /// Expected-last-seq-per-subject checks staged in this batch.
+ public Dictionary? ExpectedPerSubject { get; set; }
+}
+
+///
+/// Cached expected-last-sequence-per-subject result for a single subject within a batch.
+/// Mirrors batchExpectedPerSubject in server/jetstream_batching.go.
+///
+internal sealed class BatchExpectedPerSubject
+{
+ /// Stream sequence of the last message on this subject at proposal time.
+ public ulong SSeq { get; set; }
+
+ /// Clustered proposal sequence at which this check was computed.
+ public ulong ClSeq { get; set; }
+}
+
+///
+/// Tracks the in-progress application of a committed batch on the Raft apply path.
+/// Mirrors batchApply in server/jetstream_batching.go.
+///
+internal sealed class BatchApply
+{
+ private readonly Lock _mu = new();
+
+ /// ID of the current batch.
+ public string Id { get; set; } = string.Empty;
+
+ /// Number of entries expected in the batch (for consistency checks).
+ public ulong Count { get; set; }
+
+ /// Raft committed entries that make up this batch.
+ public List