using System;
using System.Threading;
using System.Threading.Tasks;
namespace Svrnty.CQRS.Events.Abstractions.Projections;
///
/// Stores and retrieves projection checkpoints to track processing progress.
///
///
///
/// Phase 7 Feature - Projection Checkpoints:
/// Checkpoints enable projections to resume from where they left off after restart
/// or failure. Each projection maintains its own checkpoint per stream.
///
///
/// Checkpoint Strategy:
/// - Checkpoints are updated after successfully processing each batch of events
/// - Checkpoint updates should be atomic with read model updates (same transaction)
/// - If checkpoint update fails, events will be reprocessed (idempotency required)
///
///
public interface IProjectionCheckpointStore
{
///
/// Gets the current checkpoint for a projection on a specific stream.
///
/// The unique name of the projection.
/// The name of the stream being consumed.
/// Cancellation token.
///
/// The checkpoint containing the last processed offset and timestamp,
/// or null if the projection has never processed this stream.
///
Task GetCheckpointAsync(
string projectionName,
string streamName,
CancellationToken cancellationToken = default);
///
/// Saves or updates the checkpoint for a projection on a specific stream.
///
/// The checkpoint to save.
/// Cancellation token.
/// A task representing the async operation.
///
/// This should be called after successfully processing a batch of events.
/// Ideally, this should be part of the same transaction as the read model update
/// to ensure exactly-once processing semantics.
///
Task SaveCheckpointAsync(
ProjectionCheckpoint checkpoint,
CancellationToken cancellationToken = default);
///
/// Resets the checkpoint for a projection on a specific stream.
///
/// The unique name of the projection.
/// The name of the stream being consumed.
/// Cancellation token.
/// A task representing the async operation.
///
/// This is used when rebuilding a projection. After reset, the projection
/// will start processing from offset 0.
///
Task ResetCheckpointAsync(
string projectionName,
string streamName,
CancellationToken cancellationToken = default);
///
/// Gets all checkpoints for a specific projection across all streams.
///
/// The unique name of the projection.
/// Cancellation token.
/// A list of all checkpoints for this projection.
///
/// Useful for monitoring projection progress across multiple streams.
///
Task GetAllCheckpointsAsync(
string projectionName,
CancellationToken cancellationToken = default);
}
///
/// Represents a checkpoint tracking a projection's progress on a stream.
///
public sealed record ProjectionCheckpoint
{
///
/// The unique name of the projection.
///
public required string ProjectionName { get; init; }
///
/// The name of the stream being consumed.
///
public required string StreamName { get; init; }
///
/// The last successfully processed offset in the stream.
///
///
/// Next time the projection runs, it should start from offset + 1.
///
public long LastProcessedOffset { get; init; }
///
/// The timestamp when this checkpoint was last updated.
///
public DateTimeOffset LastUpdated { get; init; }
///
/// The number of events processed by this projection.
///
///
/// Useful for monitoring and metrics.
///
public long EventsProcessed { get; init; }
///
/// Optional error information if the projection is in a failed state.
///
public string? LastError { get; init; }
///
/// The timestamp when the last error occurred.
///
public DateTimeOffset? LastErrorAt { get; init; }
///
/// Creates a new checkpoint with updated offset and timestamp.
///
public ProjectionCheckpoint WithOffset(long offset)
{
return this with
{
LastProcessedOffset = offset,
LastUpdated = DateTimeOffset.UtcNow,
EventsProcessed = EventsProcessed + 1,
LastError = null,
LastErrorAt = null
};
}
///
/// Creates a new checkpoint with error information.
///
public ProjectionCheckpoint WithError(string error)
{
return this with
{
LastError = error,
LastErrorAt = DateTimeOffset.UtcNow
};
}
}