using ZB.MOM.WW.MxGateway.Contracts.Proto; namespace ZB.MOM.WW.MxGateway.Client; /// /// One item yielded by the typed event stream. It is either a normal MXAccess /// or a reconnect-replay gap sentinel. /// /// /// /// The gateway emits a single sentinel at the head of a /// StreamEvents stream that was resumed via /// StreamEventsRequest.after_worker_sequence when the requested sequence /// predates the oldest event still retained in the session replay ring — i.e. /// events were evicted and cannot be replayed. On that sentinel the /// MxEvent.replay_gap field is set, is /// , the body oneof is unset, and /// no per-item fields are populated. /// /// /// This wrapper makes that sentinel observable instead of forcing the consumer /// to inspect the raw . It never synthesizes or swallows an /// event: the gap is exactly the gateway's own sentinel, exposed with /// set and populated. Every /// other event flows through unchanged with false. /// /// /// When is the consumer has /// missed events and MUST discard local state and re-snapshot. To resume without /// incurring another gap, reconnect with /// after_worker_sequence = ReplayGap.OldestAvailableSequence - 1. /// /// public sealed class MxEventStreamItem { private MxEventStreamItem(MxEvent gatewayEvent, ReplayGap? replayGap) { Event = gatewayEvent; ReplayGap = replayGap; } /// /// The underlying raw . For a normal event this is the /// MXAccess event itself; for a replay-gap item this is the gateway's /// sentinel event whose only meaningful payload is . /// Never . /// public MxEvent Event { get; } /// /// The reconnect-replay gap payload when this item is a gap sentinel; /// otherwise . Read /// and /// to learn /// which events were lost. /// public ReplayGap? ReplayGap { get; } /// /// when this item is a reconnect-replay gap sentinel: /// the consumer missed events and must discard local state and re-snapshot. /// Resume without another gap by reconnecting with /// after_worker_sequence = ReplayGap.OldestAvailableSequence - 1. /// public bool IsReplayGap => ReplayGap is not null; /// /// Wraps a raw stream as a typed item, classifying it /// as a replay-gap sentinel when MxEvent.replay_gap is present. /// /// The raw event from the StreamEvents stream. /// A typed item exposing either the normal event or the replay gap. internal static MxEventStreamItem From(MxEvent gatewayEvent) { ArgumentNullException.ThrowIfNull(gatewayEvent); // For a message-typed proto3 field, presence is a non-null reference. return gatewayEvent.ReplayGap is { } replayGap ? new MxEventStreamItem(gatewayEvent, replayGap) : new MxEventStreamItem(gatewayEvent, replayGap: null); } }