using System; using System.Collections.Generic; using System.Linq; namespace Svrnty.CQRS.Events.Abstractions.Subscriptions; /// /// Represents a persistent subscription to correlated events. /// Survives client disconnection and delivers missed events on reconnect. /// public sealed class PersistentSubscription { /// /// Unique subscription identifier. /// public required string Id { get; init; } /// /// User/client identifier who owns this subscription. /// public required string SubscriberId { get; init; } /// /// Correlation ID to filter events by. /// Only events with this correlation ID will be delivered. /// public required string CorrelationId { get; init; } /// /// Event type names the subscriber wants to receive. /// If empty or null, all events for the correlation are delivered. /// public HashSet EventTypes { get; init; } = new(); /// /// Event types that complete/close the subscription. /// When one of these events is delivered, the subscription is marked as Completed. /// public HashSet TerminalEventTypes { get; init; } = new(); /// /// How events should be delivered to the client. /// public DeliveryMode DeliveryMode { get; init; } = DeliveryMode.Immediate; /// /// When the subscription was created. /// public DateTimeOffset CreatedAt { get; init; } /// /// Optional expiration time for the subscription. /// If set, subscription will be marked as Expired after this time. /// public DateTimeOffset? ExpiresAt { get; init; } /// /// When the subscription completed, expired, or was cancelled. /// public DateTimeOffset? CompletedAt { get; private set; } /// /// Last event sequence number successfully delivered to the client. /// Used for catch-up on reconnect. /// public long LastDeliveredSequence { get; private set; } /// /// Current status of the subscription. /// public SubscriptionStatus Status { get; private set; } = SubscriptionStatus.Active; /// /// Optional connection ID if client is currently connected. /// public string? ConnectionId { get; set; } /// /// Optional data source ID for client-side routing. /// public string? DataSourceId { get; init; } /// /// Mark a sequence number as successfully delivered. /// public void MarkDelivered(long sequence) { if (sequence > LastDeliveredSequence) { LastDeliveredSequence = sequence; } } /// /// Mark subscription as completed (terminal event received). /// public void Complete() { if (Status == SubscriptionStatus.Active || Status == SubscriptionStatus.Paused) { Status = SubscriptionStatus.Completed; CompletedAt = DateTimeOffset.UtcNow; } } /// /// Mark subscription as cancelled by user. /// public void Cancel() { if (Status == SubscriptionStatus.Active || Status == SubscriptionStatus.Paused) { Status = SubscriptionStatus.Cancelled; CompletedAt = DateTimeOffset.UtcNow; } } /// /// Mark subscription as expired (TTL reached). /// public void Expire() { if (Status == SubscriptionStatus.Active || Status == SubscriptionStatus.Paused) { Status = SubscriptionStatus.Expired; CompletedAt = DateTimeOffset.UtcNow; } } /// /// Pause the subscription (stops event delivery). /// public void Pause() { if (Status == SubscriptionStatus.Active) { Status = SubscriptionStatus.Paused; } } /// /// Resume a paused subscription. /// public void Resume() { if (Status == SubscriptionStatus.Paused) { Status = SubscriptionStatus.Active; } } /// /// Check if the subscription has expired. /// public bool IsExpired => ExpiresAt.HasValue && DateTimeOffset.UtcNow > ExpiresAt.Value; /// /// Check if this event type should be delivered to the subscriber. /// public bool ShouldDeliverEventType(string eventTypeName) { // If no filter specified, deliver all events if (EventTypes == null || EventTypes.Count == 0) return true; // Check if event type is in the filter list return EventTypes.Contains(eventTypeName); } /// /// Check if this event type is a terminal event. /// public bool IsTerminalEvent(string eventTypeName) { return TerminalEventTypes != null && TerminalEventTypes.Contains(eventTypeName); } /// /// Check if subscription can receive events. /// public bool CanReceiveEvents => Status == SubscriptionStatus.Active; }