up
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
namespace StellaOps.Messaging.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Transport-agnostic atomic token store for one-time consumable tokens.
|
||||
/// Supports issuing tokens with TTL and atomic consumption (single use).
|
||||
/// </summary>
|
||||
/// <typeparam name="TPayload">The type of metadata payload stored with the token.</typeparam>
|
||||
public interface IAtomicTokenStore<TPayload>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the provider name for diagnostics (e.g., "valkey", "postgres", "inmemory").
|
||||
/// </summary>
|
||||
string ProviderName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Issues a token with the given payload and TTL.
|
||||
/// </summary>
|
||||
/// <param name="key">The storage key for the token.</param>
|
||||
/// <param name="payload">The metadata payload to store with the token.</param>
|
||||
/// <param name="ttl">The time-to-live for the token.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The result containing the generated token.</returns>
|
||||
ValueTask<TokenIssueResult> IssueAsync(
|
||||
string key,
|
||||
TPayload payload,
|
||||
TimeSpan ttl,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Stores a caller-provided token with payload and TTL.
|
||||
/// Use when the token must be generated externally (e.g., cryptographic nonces).
|
||||
/// </summary>
|
||||
/// <param name="key">The storage key for the token.</param>
|
||||
/// <param name="token">The caller-provided token value.</param>
|
||||
/// <param name="payload">The metadata payload to store with the token.</param>
|
||||
/// <param name="ttl">The time-to-live for the token.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The result containing the stored token information.</returns>
|
||||
ValueTask<TokenIssueResult> StoreAsync(
|
||||
string key,
|
||||
string token,
|
||||
TPayload payload,
|
||||
TimeSpan ttl,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Atomically consumes a token if it exists and matches.
|
||||
/// The token is deleted after successful consumption (single use).
|
||||
/// </summary>
|
||||
/// <param name="key">The storage key for the token.</param>
|
||||
/// <param name="expectedToken">The token value to match.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The result of the consumption attempt.</returns>
|
||||
ValueTask<TokenConsumeResult<TPayload>> TryConsumeAsync(
|
||||
string key,
|
||||
string expectedToken,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a token exists without consuming it.
|
||||
/// </summary>
|
||||
/// <param name="key">The storage key for the token.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>True if the token exists.</returns>
|
||||
ValueTask<bool> ExistsAsync(
|
||||
string key,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Revokes a token before it expires.
|
||||
/// </summary>
|
||||
/// <param name="key">The storage key for the token.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>True if the token existed and was revoked.</returns>
|
||||
ValueTask<bool> RevokeAsync(
|
||||
string key,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
namespace StellaOps.Messaging.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Transport-agnostic event stream interface.
|
||||
/// Provides fire-and-forget event publishing without consumer group semantics.
|
||||
/// Unlike <see cref="IMessageQueue{TMessage}"/>, events are not acknowledged and may be consumed by multiple subscribers.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEvent">The event type.</typeparam>
|
||||
public interface IEventStream<TEvent> where TEvent : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the provider name for diagnostics (e.g., "valkey", "postgres", "inmemory").
|
||||
/// </summary>
|
||||
string ProviderName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the stream name.
|
||||
/// </summary>
|
||||
string StreamName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Publishes an event to the stream.
|
||||
/// </summary>
|
||||
/// <param name="event">The event to publish.</param>
|
||||
/// <param name="options">Optional publish options.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The result of the publish operation.</returns>
|
||||
ValueTask<EventPublishResult> PublishAsync(
|
||||
TEvent @event,
|
||||
EventPublishOptions? options = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Publishes multiple events to the stream.
|
||||
/// </summary>
|
||||
/// <param name="events">The events to publish.</param>
|
||||
/// <param name="options">Optional publish options (applied to all events).</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The results of the publish operations.</returns>
|
||||
ValueTask<IReadOnlyList<EventPublishResult>> PublishBatchAsync(
|
||||
IEnumerable<TEvent> events,
|
||||
EventPublishOptions? options = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Subscribes to events from a position.
|
||||
/// Events are delivered as they become available.
|
||||
/// </summary>
|
||||
/// <param name="position">The stream position to start from.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>An async enumerable of events.</returns>
|
||||
IAsyncEnumerable<StreamEvent<TEvent>> SubscribeAsync(
|
||||
StreamPosition position,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets stream metadata.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Information about the stream.</returns>
|
||||
ValueTask<StreamInfo> GetInfoAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Trims stream to approximate max length.
|
||||
/// </summary>
|
||||
/// <param name="maxLength">The maximum length to retain.</param>
|
||||
/// <param name="approximate">Whether to use approximate trimming (more efficient).</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The number of entries removed.</returns>
|
||||
ValueTask<long> TrimAsync(
|
||||
long maxLength,
|
||||
bool approximate = true,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
namespace StellaOps.Messaging.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Transport-agnostic idempotency store interface.
|
||||
/// Provides deduplication keys with configurable time windows.
|
||||
/// </summary>
|
||||
public interface IIdempotencyStore
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the provider name for diagnostics (e.g., "valkey", "postgres", "inmemory").
|
||||
/// </summary>
|
||||
string ProviderName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to claim an idempotency key.
|
||||
/// If the key doesn't exist, it's claimed for the duration of the window.
|
||||
/// </summary>
|
||||
/// <param name="key">The idempotency key.</param>
|
||||
/// <param name="value">The value to store (e.g., message ID, operation ID).</param>
|
||||
/// <param name="window">The idempotency window duration.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The result indicating whether this was the first claim.</returns>
|
||||
ValueTask<IdempotencyResult> TryClaimAsync(
|
||||
string key,
|
||||
string value,
|
||||
TimeSpan window,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a key was already claimed.
|
||||
/// </summary>
|
||||
/// <param name="key">The idempotency key.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>True if the key exists (was previously claimed).</returns>
|
||||
ValueTask<bool> ExistsAsync(
|
||||
string key,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value for a claimed key.
|
||||
/// </summary>
|
||||
/// <param name="key">The idempotency key.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The stored value, or null if the key doesn't exist.</returns>
|
||||
ValueTask<string?> GetAsync(
|
||||
string key,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Releases a claimed key before the window expires.
|
||||
/// </summary>
|
||||
/// <param name="key">The idempotency key.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>True if the key existed and was released.</returns>
|
||||
ValueTask<bool> ReleaseAsync(
|
||||
string key,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Extends the window for a claimed key.
|
||||
/// </summary>
|
||||
/// <param name="key">The idempotency key.</param>
|
||||
/// <param name="extension">The time to extend by.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>True if the key existed and was extended.</returns>
|
||||
ValueTask<bool> ExtendAsync(
|
||||
string key,
|
||||
TimeSpan extension,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -46,3 +46,120 @@ public interface IDistributedCacheFactory
|
||||
/// <returns>A configured distributed cache instance.</returns>
|
||||
IDistributedCache<TValue> Create<TValue>(CacheOptions options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Factory for creating rate limiter instances.
|
||||
/// </summary>
|
||||
public interface IRateLimiterFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the provider name for this factory.
|
||||
/// </summary>
|
||||
string ProviderName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a rate limiter with the specified name.
|
||||
/// </summary>
|
||||
/// <param name="name">The rate limiter name (used as key prefix).</param>
|
||||
/// <returns>A configured rate limiter instance.</returns>
|
||||
IRateLimiter Create(string name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Factory for creating atomic token store instances.
|
||||
/// </summary>
|
||||
public interface IAtomicTokenStoreFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the provider name for this factory.
|
||||
/// </summary>
|
||||
string ProviderName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates an atomic token store for the specified payload type.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPayload">The payload type.</typeparam>
|
||||
/// <param name="name">The store name (used as key prefix).</param>
|
||||
/// <returns>A configured atomic token store instance.</returns>
|
||||
IAtomicTokenStore<TPayload> Create<TPayload>(string name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Factory for creating sorted index instances.
|
||||
/// </summary>
|
||||
public interface ISortedIndexFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the provider name for this factory.
|
||||
/// </summary>
|
||||
string ProviderName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a sorted index for the specified key and element types.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The index key type.</typeparam>
|
||||
/// <typeparam name="TElement">The element type.</typeparam>
|
||||
/// <param name="name">The index name (used as key prefix).</param>
|
||||
/// <returns>A configured sorted index instance.</returns>
|
||||
ISortedIndex<TKey, TElement> Create<TKey, TElement>(string name)
|
||||
where TKey : notnull
|
||||
where TElement : notnull;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Factory for creating set store instances.
|
||||
/// </summary>
|
||||
public interface ISetStoreFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the provider name for this factory.
|
||||
/// </summary>
|
||||
string ProviderName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a set store for the specified key and element types.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The set key type.</typeparam>
|
||||
/// <typeparam name="TElement">The element type.</typeparam>
|
||||
/// <param name="name">The store name (used as key prefix).</param>
|
||||
/// <returns>A configured set store instance.</returns>
|
||||
ISetStore<TKey, TElement> Create<TKey, TElement>(string name)
|
||||
where TKey : notnull;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Factory for creating event stream instances.
|
||||
/// </summary>
|
||||
public interface IEventStreamFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the provider name for this factory.
|
||||
/// </summary>
|
||||
string ProviderName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates an event stream for the specified event type.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEvent">The event type.</typeparam>
|
||||
/// <param name="options">The event stream options.</param>
|
||||
/// <returns>A configured event stream instance.</returns>
|
||||
IEventStream<TEvent> Create<TEvent>(EventStreamOptions options) where TEvent : class;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Factory for creating idempotency store instances.
|
||||
/// </summary>
|
||||
public interface IIdempotencyStoreFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the provider name for this factory.
|
||||
/// </summary>
|
||||
string ProviderName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates an idempotency store with the specified name.
|
||||
/// </summary>
|
||||
/// <param name="name">The store name (used as key prefix).</param>
|
||||
/// <returns>A configured idempotency store instance.</returns>
|
||||
IIdempotencyStore Create(string name);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
namespace StellaOps.Messaging.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Transport-agnostic rate limiter interface.
|
||||
/// Implements sliding window rate limiting with configurable policies.
|
||||
/// </summary>
|
||||
public interface IRateLimiter
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the provider name for diagnostics (e.g., "valkey", "postgres", "inmemory").
|
||||
/// </summary>
|
||||
string ProviderName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to acquire a permit from the rate limiter.
|
||||
/// </summary>
|
||||
/// <param name="key">The rate limit key (e.g., user ID, IP address, resource identifier).</param>
|
||||
/// <param name="policy">The rate limit policy defining max permits and window.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The result of the rate limit check.</returns>
|
||||
ValueTask<RateLimitResult> TryAcquireAsync(
|
||||
string key,
|
||||
RateLimitPolicy policy,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets current usage for a key without consuming a permit.
|
||||
/// </summary>
|
||||
/// <param name="key">The rate limit key.</param>
|
||||
/// <param name="policy">The rate limit policy.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The current rate limit status.</returns>
|
||||
ValueTask<RateLimitStatus> GetStatusAsync(
|
||||
string key,
|
||||
RateLimitPolicy policy,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Resets the rate limit counter for a key.
|
||||
/// </summary>
|
||||
/// <param name="key">The rate limit key.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>True if the key existed and was reset.</returns>
|
||||
ValueTask<bool> ResetAsync(
|
||||
string key,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
116
src/__Libraries/StellaOps.Messaging/Abstractions/ISetStore.cs
Normal file
116
src/__Libraries/StellaOps.Messaging/Abstractions/ISetStore.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
namespace StellaOps.Messaging.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Transport-agnostic set store interface.
|
||||
/// Provides unordered set membership operations.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The set key type.</typeparam>
|
||||
/// <typeparam name="TElement">The element type stored in the set.</typeparam>
|
||||
public interface ISetStore<TKey, TElement>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the provider name for diagnostics (e.g., "valkey", "postgres", "inmemory").
|
||||
/// </summary>
|
||||
string ProviderName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Adds an element to the set.
|
||||
/// </summary>
|
||||
/// <param name="setKey">The set key.</param>
|
||||
/// <param name="element">The element to add.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>True if the element was added (false if already existed).</returns>
|
||||
ValueTask<bool> AddAsync(
|
||||
TKey setKey,
|
||||
TElement element,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Adds multiple elements to the set.
|
||||
/// </summary>
|
||||
/// <param name="setKey">The set key.</param>
|
||||
/// <param name="elements">The elements to add.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The number of elements added (not already present).</returns>
|
||||
ValueTask<long> AddRangeAsync(
|
||||
TKey setKey,
|
||||
IEnumerable<TElement> elements,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all members of the set.
|
||||
/// </summary>
|
||||
/// <param name="setKey">The set key.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>All elements in the set.</returns>
|
||||
ValueTask<IReadOnlySet<TElement>> GetMembersAsync(
|
||||
TKey setKey,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if an element exists in the set.
|
||||
/// </summary>
|
||||
/// <param name="setKey">The set key.</param>
|
||||
/// <param name="element">The element to check.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>True if the element is a member of the set.</returns>
|
||||
ValueTask<bool> ContainsAsync(
|
||||
TKey setKey,
|
||||
TElement element,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Removes an element from the set.
|
||||
/// </summary>
|
||||
/// <param name="setKey">The set key.</param>
|
||||
/// <param name="element">The element to remove.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>True if the element was removed.</returns>
|
||||
ValueTask<bool> RemoveAsync(
|
||||
TKey setKey,
|
||||
TElement element,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Removes multiple elements from the set.
|
||||
/// </summary>
|
||||
/// <param name="setKey">The set key.</param>
|
||||
/// <param name="elements">The elements to remove.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The number of elements removed.</returns>
|
||||
ValueTask<long> RemoveRangeAsync(
|
||||
TKey setKey,
|
||||
IEnumerable<TElement> elements,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the entire set.
|
||||
/// </summary>
|
||||
/// <param name="setKey">The set key.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>True if the set existed and was deleted.</returns>
|
||||
ValueTask<bool> DeleteAsync(
|
||||
TKey setKey,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cardinality (count) of the set.
|
||||
/// </summary>
|
||||
/// <param name="setKey">The set key.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The number of elements in the set.</returns>
|
||||
ValueTask<long> CountAsync(
|
||||
TKey setKey,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Sets TTL on the set key.
|
||||
/// </summary>
|
||||
/// <param name="setKey">The set key.</param>
|
||||
/// <param name="ttl">The time-to-live.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
ValueTask SetExpirationAsync(
|
||||
TKey setKey,
|
||||
TimeSpan ttl,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
180
src/__Libraries/StellaOps.Messaging/Abstractions/ISortedIndex.cs
Normal file
180
src/__Libraries/StellaOps.Messaging/Abstractions/ISortedIndex.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
namespace StellaOps.Messaging.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Transport-agnostic sorted index interface.
|
||||
/// Provides score-ordered collections with range queries.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The index key type.</typeparam>
|
||||
/// <typeparam name="TElement">The element type stored in the index.</typeparam>
|
||||
public interface ISortedIndex<TKey, TElement>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the provider name for diagnostics (e.g., "valkey", "postgres", "inmemory").
|
||||
/// </summary>
|
||||
string ProviderName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Adds an element with a score.
|
||||
/// </summary>
|
||||
/// <param name="indexKey">The index key.</param>
|
||||
/// <param name="element">The element to add.</param>
|
||||
/// <param name="score">The score for ordering.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>True if the element was added (false if updated).</returns>
|
||||
ValueTask<bool> AddAsync(
|
||||
TKey indexKey,
|
||||
TElement element,
|
||||
double score,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Adds multiple elements with scores atomically.
|
||||
/// </summary>
|
||||
/// <param name="indexKey">The index key.</param>
|
||||
/// <param name="elements">The elements with scores to add.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The number of elements added (not updated).</returns>
|
||||
ValueTask<long> AddRangeAsync(
|
||||
TKey indexKey,
|
||||
IEnumerable<ScoredElement<TElement>> elements,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets elements by rank range (0-based, inclusive).
|
||||
/// </summary>
|
||||
/// <param name="indexKey">The index key.</param>
|
||||
/// <param name="start">The start rank (0-based).</param>
|
||||
/// <param name="stop">The stop rank (inclusive, use -1 for last).</param>
|
||||
/// <param name="order">The sort order.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Elements within the rank range.</returns>
|
||||
ValueTask<IReadOnlyList<ScoredElement<TElement>>> GetByRankAsync(
|
||||
TKey indexKey,
|
||||
long start,
|
||||
long stop,
|
||||
SortOrder order = SortOrder.Ascending,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets elements by score range.
|
||||
/// </summary>
|
||||
/// <param name="indexKey">The index key.</param>
|
||||
/// <param name="minScore">The minimum score (inclusive).</param>
|
||||
/// <param name="maxScore">The maximum score (inclusive).</param>
|
||||
/// <param name="order">The sort order.</param>
|
||||
/// <param name="limit">Optional limit on returned elements.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Elements within the score range.</returns>
|
||||
ValueTask<IReadOnlyList<ScoredElement<TElement>>> GetByScoreAsync(
|
||||
TKey indexKey,
|
||||
double minScore,
|
||||
double maxScore,
|
||||
SortOrder order = SortOrder.Ascending,
|
||||
int? limit = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the score of an element.
|
||||
/// </summary>
|
||||
/// <param name="indexKey">The index key.</param>
|
||||
/// <param name="element">The element to look up.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The score, or null if the element doesn't exist.</returns>
|
||||
ValueTask<double?> GetScoreAsync(
|
||||
TKey indexKey,
|
||||
TElement element,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Removes an element from the index.
|
||||
/// </summary>
|
||||
/// <param name="indexKey">The index key.</param>
|
||||
/// <param name="element">The element to remove.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>True if the element was removed.</returns>
|
||||
ValueTask<bool> RemoveAsync(
|
||||
TKey indexKey,
|
||||
TElement element,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Removes multiple elements from the index.
|
||||
/// </summary>
|
||||
/// <param name="indexKey">The index key.</param>
|
||||
/// <param name="elements">The elements to remove.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The number of elements removed.</returns>
|
||||
ValueTask<long> RemoveRangeAsync(
|
||||
TKey indexKey,
|
||||
IEnumerable<TElement> elements,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Removes elements by score range.
|
||||
/// </summary>
|
||||
/// <param name="indexKey">The index key.</param>
|
||||
/// <param name="minScore">The minimum score (inclusive).</param>
|
||||
/// <param name="maxScore">The maximum score (inclusive).</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The number of elements removed.</returns>
|
||||
ValueTask<long> RemoveByScoreAsync(
|
||||
TKey indexKey,
|
||||
double minScore,
|
||||
double maxScore,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total count of elements in the index.
|
||||
/// </summary>
|
||||
/// <param name="indexKey">The index key.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The element count.</returns>
|
||||
ValueTask<long> CountAsync(
|
||||
TKey indexKey,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the entire index.
|
||||
/// </summary>
|
||||
/// <param name="indexKey">The index key.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>True if the index existed and was deleted.</returns>
|
||||
ValueTask<bool> DeleteAsync(
|
||||
TKey indexKey,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Sets TTL on the index key.
|
||||
/// </summary>
|
||||
/// <param name="indexKey">The index key.</param>
|
||||
/// <param name="ttl">The time-to-live.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
ValueTask SetExpirationAsync(
|
||||
TKey indexKey,
|
||||
TimeSpan ttl,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An element with an associated score.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The element type.</typeparam>
|
||||
/// <param name="Element">The element value.</param>
|
||||
/// <param name="Score">The score for ordering.</param>
|
||||
public readonly record struct ScoredElement<T>(T Element, double Score);
|
||||
|
||||
/// <summary>
|
||||
/// Sort order for index queries.
|
||||
/// </summary>
|
||||
public enum SortOrder
|
||||
{
|
||||
/// <summary>
|
||||
/// Sort by ascending score (lowest first).
|
||||
/// </summary>
|
||||
Ascending,
|
||||
|
||||
/// <summary>
|
||||
/// Sort by descending score (highest first).
|
||||
/// </summary>
|
||||
Descending
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
namespace StellaOps.Messaging;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration options for event streams.
|
||||
/// </summary>
|
||||
public sealed class EventStreamOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the stream name.
|
||||
/// </summary>
|
||||
public required string StreamName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum stream length.
|
||||
/// When set, the stream is automatically trimmed to this approximate length.
|
||||
/// </summary>
|
||||
public long? MaxLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to use approximate trimming (more efficient).
|
||||
/// Default is true.
|
||||
/// </summary>
|
||||
public bool ApproximateTrimming { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the polling interval for subscription (when applicable).
|
||||
/// Default is 100ms.
|
||||
/// </summary>
|
||||
public TimeSpan PollInterval { get; set; } = TimeSpan.FromMilliseconds(100);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the idempotency window for duplicate detection.
|
||||
/// Default is 5 minutes.
|
||||
/// </summary>
|
||||
public TimeSpan IdempotencyWindow { get; set; } = TimeSpan.FromMinutes(5);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether idempotency checking is enabled.
|
||||
/// Default is false.
|
||||
/// </summary>
|
||||
public bool EnableIdempotency { get; set; }
|
||||
}
|
||||
177
src/__Libraries/StellaOps.Messaging/Results/EventStreamResult.cs
Normal file
177
src/__Libraries/StellaOps.Messaging/Results/EventStreamResult.cs
Normal file
@@ -0,0 +1,177 @@
|
||||
namespace StellaOps.Messaging;
|
||||
|
||||
/// <summary>
|
||||
/// Options for publishing events to a stream.
|
||||
/// </summary>
|
||||
public sealed record EventPublishOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the idempotency key for deduplication.
|
||||
/// </summary>
|
||||
public string? IdempotencyKey { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the tenant identifier.
|
||||
/// </summary>
|
||||
public string? TenantId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the correlation identifier for tracing.
|
||||
/// </summary>
|
||||
public string? CorrelationId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets additional headers.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, string>? Headers { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum stream length (triggers trimming).
|
||||
/// </summary>
|
||||
public long? MaxStreamLength { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of an event publish operation.
|
||||
/// </summary>
|
||||
public readonly struct EventPublishResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets whether the publish was successful.
|
||||
/// </summary>
|
||||
public bool Success { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entry ID assigned by the stream.
|
||||
/// </summary>
|
||||
public string? EntryId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether this was a duplicate (based on idempotency key).
|
||||
/// </summary>
|
||||
public bool WasDeduplicated { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the error message if the operation failed.
|
||||
/// </summary>
|
||||
public string? Error { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a successful publish result.
|
||||
/// </summary>
|
||||
public static EventPublishResult Succeeded(string entryId, bool wasDeduplicated = false) =>
|
||||
new()
|
||||
{
|
||||
Success = true,
|
||||
EntryId = entryId,
|
||||
WasDeduplicated = wasDeduplicated
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Creates a failed publish result.
|
||||
/// </summary>
|
||||
public static EventPublishResult Failed(string error) =>
|
||||
new()
|
||||
{
|
||||
Success = false,
|
||||
Error = error
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Creates a deduplicated result.
|
||||
/// </summary>
|
||||
public static EventPublishResult Deduplicated(string existingEntryId) =>
|
||||
new()
|
||||
{
|
||||
Success = true,
|
||||
EntryId = existingEntryId,
|
||||
WasDeduplicated = true
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An event from the stream with metadata.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The event type.</typeparam>
|
||||
/// <param name="EntryId">The stream entry identifier.</param>
|
||||
/// <param name="Event">The event payload.</param>
|
||||
/// <param name="Timestamp">When the event was published.</param>
|
||||
/// <param name="TenantId">The tenant identifier, if present.</param>
|
||||
/// <param name="CorrelationId">The correlation identifier, if present.</param>
|
||||
public sealed record StreamEvent<T>(
|
||||
string EntryId,
|
||||
T Event,
|
||||
DateTimeOffset Timestamp,
|
||||
string? TenantId,
|
||||
string? CorrelationId);
|
||||
|
||||
/// <summary>
|
||||
/// Represents a position in the stream.
|
||||
/// </summary>
|
||||
public readonly struct StreamPosition : IEquatable<StreamPosition>
|
||||
{
|
||||
/// <summary>
|
||||
/// Position at the beginning of the stream (read all).
|
||||
/// </summary>
|
||||
public static StreamPosition Beginning => new("0");
|
||||
|
||||
/// <summary>
|
||||
/// Position at the end of the stream (only new entries).
|
||||
/// </summary>
|
||||
public static StreamPosition End => new("$");
|
||||
|
||||
/// <summary>
|
||||
/// Creates a position after a specific entry ID.
|
||||
/// </summary>
|
||||
public static StreamPosition After(string entryId) => new(entryId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the position value.
|
||||
/// </summary>
|
||||
public string Value { get; }
|
||||
|
||||
private StreamPosition(string value) => Value = value;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Equals(StreamPosition other) => Value == other.Value;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object? obj) => obj is StreamPosition other && Equals(other);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode() => Value?.GetHashCode() ?? 0;
|
||||
|
||||
/// <summary>
|
||||
/// Equality operator.
|
||||
/// </summary>
|
||||
public static bool operator ==(StreamPosition left, StreamPosition right) => left.Equals(right);
|
||||
|
||||
/// <summary>
|
||||
/// Inequality operator.
|
||||
/// </summary>
|
||||
public static bool operator !=(StreamPosition left, StreamPosition right) => !left.Equals(right);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Information about a stream.
|
||||
/// </summary>
|
||||
/// <param name="Length">The number of entries in the stream.</param>
|
||||
/// <param name="FirstEntryId">The ID of the first entry, if any.</param>
|
||||
/// <param name="LastEntryId">The ID of the last entry, if any.</param>
|
||||
/// <param name="FirstEntryTimestamp">The timestamp of the first entry, if available.</param>
|
||||
/// <param name="LastEntryTimestamp">The timestamp of the last entry, if available.</param>
|
||||
public sealed record StreamInfo(
|
||||
long Length,
|
||||
string? FirstEntryId,
|
||||
string? LastEntryId,
|
||||
DateTimeOffset? FirstEntryTimestamp,
|
||||
DateTimeOffset? LastEntryTimestamp)
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an empty stream info.
|
||||
/// </summary>
|
||||
public static StreamInfo Empty => new(0, null, null, null, null);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
namespace StellaOps.Messaging;
|
||||
|
||||
/// <summary>
|
||||
/// Result of an idempotency claim attempt.
|
||||
/// </summary>
|
||||
public readonly struct IdempotencyResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets whether this was the first claim (not a duplicate).
|
||||
/// </summary>
|
||||
public bool IsFirstClaim { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the existing value if this was a duplicate.
|
||||
/// </summary>
|
||||
public string? ExistingValue { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether this was a duplicate (key already claimed).
|
||||
/// </summary>
|
||||
public bool IsDuplicate => !IsFirstClaim;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a result indicating the key was successfully claimed.
|
||||
/// </summary>
|
||||
public static IdempotencyResult Claimed() =>
|
||||
new()
|
||||
{
|
||||
IsFirstClaim = true
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Creates a result indicating the key was already claimed (duplicate).
|
||||
/// </summary>
|
||||
public static IdempotencyResult Duplicate(string existingValue) =>
|
||||
new()
|
||||
{
|
||||
IsFirstClaim = false,
|
||||
ExistingValue = existingValue
|
||||
};
|
||||
}
|
||||
127
src/__Libraries/StellaOps.Messaging/Results/RateLimitResult.cs
Normal file
127
src/__Libraries/StellaOps.Messaging/Results/RateLimitResult.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
namespace StellaOps.Messaging;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a rate limit policy.
|
||||
/// </summary>
|
||||
/// <param name="MaxPermits">Maximum number of permits allowed within the window.</param>
|
||||
/// <param name="Window">The time window for rate limiting.</param>
|
||||
public sealed record RateLimitPolicy(int MaxPermits, TimeSpan Window)
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a per-second rate limit policy.
|
||||
/// </summary>
|
||||
public static RateLimitPolicy PerSecond(int maxPermits) =>
|
||||
new(maxPermits, TimeSpan.FromSeconds(1));
|
||||
|
||||
/// <summary>
|
||||
/// Creates a per-minute rate limit policy.
|
||||
/// </summary>
|
||||
public static RateLimitPolicy PerMinute(int maxPermits) =>
|
||||
new(maxPermits, TimeSpan.FromMinutes(1));
|
||||
|
||||
/// <summary>
|
||||
/// Creates a per-hour rate limit policy.
|
||||
/// </summary>
|
||||
public static RateLimitPolicy PerHour(int maxPermits) =>
|
||||
new(maxPermits, TimeSpan.FromHours(1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of a rate limit acquisition attempt.
|
||||
/// </summary>
|
||||
public readonly struct RateLimitResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets whether the permit was acquired (request allowed).
|
||||
/// </summary>
|
||||
public bool IsAllowed { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current count of permits used in the window.
|
||||
/// </summary>
|
||||
public int CurrentCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of remaining permits in the window.
|
||||
/// </summary>
|
||||
public int RemainingPermits { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the suggested time to wait before retrying (when denied).
|
||||
/// </summary>
|
||||
public TimeSpan? RetryAfter { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a result indicating the permit was acquired.
|
||||
/// </summary>
|
||||
public static RateLimitResult Allowed(int currentCount, int remainingPermits) =>
|
||||
new()
|
||||
{
|
||||
IsAllowed = true,
|
||||
CurrentCount = currentCount,
|
||||
RemainingPermits = remainingPermits,
|
||||
RetryAfter = null
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Creates a result indicating the request was denied.
|
||||
/// </summary>
|
||||
public static RateLimitResult Denied(int currentCount, TimeSpan retryAfter) =>
|
||||
new()
|
||||
{
|
||||
IsAllowed = false,
|
||||
CurrentCount = currentCount,
|
||||
RemainingPermits = 0,
|
||||
RetryAfter = retryAfter
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current status of a rate limit key.
|
||||
/// </summary>
|
||||
public readonly struct RateLimitStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the current count of permits used in the window.
|
||||
/// </summary>
|
||||
public int CurrentCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of remaining permits in the window.
|
||||
/// </summary>
|
||||
public int RemainingPermits { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time remaining until the window resets.
|
||||
/// </summary>
|
||||
public TimeSpan WindowRemaining { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the key exists (has any usage).
|
||||
/// </summary>
|
||||
public bool Exists { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a status for an existing key.
|
||||
/// </summary>
|
||||
public static RateLimitStatus WithUsage(int currentCount, int remainingPermits, TimeSpan windowRemaining) =>
|
||||
new()
|
||||
{
|
||||
CurrentCount = currentCount,
|
||||
RemainingPermits = remainingPermits,
|
||||
WindowRemaining = windowRemaining,
|
||||
Exists = true
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Creates a status for a key with no usage.
|
||||
/// </summary>
|
||||
public static RateLimitStatus Empty(int maxPermits) =>
|
||||
new()
|
||||
{
|
||||
CurrentCount = 0,
|
||||
RemainingPermits = maxPermits,
|
||||
WindowRemaining = TimeSpan.Zero,
|
||||
Exists = false
|
||||
};
|
||||
}
|
||||
148
src/__Libraries/StellaOps.Messaging/Results/TokenResult.cs
Normal file
148
src/__Libraries/StellaOps.Messaging/Results/TokenResult.cs
Normal file
@@ -0,0 +1,148 @@
|
||||
namespace StellaOps.Messaging;
|
||||
|
||||
/// <summary>
|
||||
/// Result of a token issuance operation.
|
||||
/// </summary>
|
||||
public readonly struct TokenIssueResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets whether the token was issued successfully.
|
||||
/// </summary>
|
||||
public bool Success { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the generated token value.
|
||||
/// </summary>
|
||||
public string Token { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets when the token expires.
|
||||
/// </summary>
|
||||
public DateTimeOffset ExpiresAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the error message if issuance failed.
|
||||
/// </summary>
|
||||
public string? Error { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a successful issuance result.
|
||||
/// </summary>
|
||||
public static TokenIssueResult Succeeded(string token, DateTimeOffset expiresAt) =>
|
||||
new()
|
||||
{
|
||||
Success = true,
|
||||
Token = token,
|
||||
ExpiresAt = expiresAt
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Creates a failed issuance result.
|
||||
/// </summary>
|
||||
public static TokenIssueResult Failed(string error) =>
|
||||
new()
|
||||
{
|
||||
Success = false,
|
||||
Token = string.Empty,
|
||||
Error = error
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of a token consumption attempt.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPayload">The type of metadata payload stored with the token.</typeparam>
|
||||
public readonly struct TokenConsumeResult<TPayload>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the status of the consumption attempt.
|
||||
/// </summary>
|
||||
public TokenConsumeStatus Status { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the payload associated with the token (when successful).
|
||||
/// </summary>
|
||||
public TPayload? Payload { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets when the token was issued (when available).
|
||||
/// </summary>
|
||||
public DateTimeOffset? IssuedAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets when the token expires/expired (when available).
|
||||
/// </summary>
|
||||
public DateTimeOffset? ExpiresAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the consumption was successful.
|
||||
/// </summary>
|
||||
public bool IsSuccess => Status == TokenConsumeStatus.Success;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a successful consumption result.
|
||||
/// </summary>
|
||||
public static TokenConsumeResult<TPayload> Success(TPayload payload, DateTimeOffset issuedAt, DateTimeOffset expiresAt) =>
|
||||
new()
|
||||
{
|
||||
Status = TokenConsumeStatus.Success,
|
||||
Payload = payload,
|
||||
IssuedAt = issuedAt,
|
||||
ExpiresAt = expiresAt
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Creates a not found result.
|
||||
/// </summary>
|
||||
public static TokenConsumeResult<TPayload> NotFound() =>
|
||||
new()
|
||||
{
|
||||
Status = TokenConsumeStatus.NotFound
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Creates an expired result.
|
||||
/// </summary>
|
||||
public static TokenConsumeResult<TPayload> Expired(DateTimeOffset issuedAt, DateTimeOffset expiresAt) =>
|
||||
new()
|
||||
{
|
||||
Status = TokenConsumeStatus.Expired,
|
||||
IssuedAt = issuedAt,
|
||||
ExpiresAt = expiresAt
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Creates a mismatch result (token exists but value doesn't match).
|
||||
/// </summary>
|
||||
public static TokenConsumeResult<TPayload> Mismatch() =>
|
||||
new()
|
||||
{
|
||||
Status = TokenConsumeStatus.Mismatch
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Status of a token consumption attempt.
|
||||
/// </summary>
|
||||
public enum TokenConsumeStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Token was consumed successfully.
|
||||
/// </summary>
|
||||
Success,
|
||||
|
||||
/// <summary>
|
||||
/// Token was not found (doesn't exist or already consumed).
|
||||
/// </summary>
|
||||
NotFound,
|
||||
|
||||
/// <summary>
|
||||
/// Token has expired.
|
||||
/// </summary>
|
||||
Expired,
|
||||
|
||||
/// <summary>
|
||||
/// Token exists but the provided value doesn't match.
|
||||
/// </summary>
|
||||
Mismatch
|
||||
}
|
||||
Reference in New Issue
Block a user