API reference — Storage

Nuvora.Nexus.Sentinel.Stores.ValKey

ValKey/Redis hot-state adapter for Nuvora Nexus Sentinel: fleet-wide rate counters and cache-invalidation pub/sub (works with any Redis-compatible server)

dotnet add package Nuvora.Nexus.Sentinel.Stores.ValKey

Nuvora.Nexus.Sentinel.Stores.ValKey

ValKeyCacheBus

public sealed class ValKeyCacheBus : ISentinelCacheBus

Fleet-wide ISentinelCacheBus over ValKey/Redis pub/sub. Publishes compact JSON {"topic":…,"entityId":…} on the configured channel.

Loopback (required by ISentinelCacheBus.Subscribe): Redis pub/sub delivers a published message to every subscriber of the channel, including subscribers registered on the publishing connection itself — StackExchange.Redis does not filter out same-connection deliveries. The bus therefore needs no separate local dispatch: the publishing node’s own handlers fire through the same server round trip as everyone else’s.

Constructors

ValKeyCacheBus(IOptions<ValKeyHotStateOptions> options, IConnectionMultiplexer connection)

Methods

IDisposable Subscribe(Action<CacheInvalidation> handler)

Subscriptions receive local publications too (loopback) — the publishing node’s own caches also need to drop entries.

ValueTask PublishAsync(CacheInvalidation invalidation, CancellationToken cancellationToken = default(CancellationToken))

ValKeyChallengeStore

public sealed class ValKeyChallengeStore : IChallengeStore

Fleet-wide IChallengeStore on ValKey/Redis (challenge codes are hot state and TTL-native: expiry IS the challenge lifetime, no sweeper). One hash per challenge (ch:{id}: fields hash + attempts) with the code’s ttl. The verify step is a Lua script, so compare / consume / decrement is one atomic server-side unit — concurrent guesses can never exceed the attempt budget (the port’s whole point).

Constructors

ValKeyChallengeStore(IOptions<ValKeyHotStateOptions> options, IConnectionMultiplexer connection)

Methods

ValueTask StoreAsync(string challengeId, string codeHash, TimeSpan ttl, int maxAttempts, CancellationToken cancellationToken = default(CancellationToken))

Creates (or replaces) the challenge. Re-storing the same id resets ttl and attempts — “resend code” semantics.

ValueTask<ChallengeVerifyResult> VerifyAsync(string challengeId, string codeHash, CancellationToken cancellationToken = default(CancellationToken))

Verifies one guess. ChallengeVerifyResult.Success and the terminal failures (ChallengeVerifyResult.ExpiredOrMissing, ChallengeVerifyResult.TooManyAttempts) consume the challenge; ChallengeVerifyResult.WrongCode decrements the remaining attempts atomically — concurrent wrong guesses must not exceed the budget.

ValKeyHotStateOptions

public sealed class ValKeyHotStateOptions

Options for the ValKey/Redis hot-state adapters (rate counters and cache bus). One shared connection serves all of them.

Properties

string CacheChannel { get; set; }

Pub/sub channel carrying cache-invalidation messages.

string ConnectionString { get; set; }

StackExchange.Redis connection string (e.g. localhost:6379). Required — a fleet-wide hot-state store has no safe default endpoint; registration fails fast when left empty.

string KeyPrefix { get; set; }

Key prefix namespacing all Sentinel hot-state keys per service/environment. The rate counter store appends its own rc: segment, so the default counter prefix is sentinel:rc:.

ValKeyRateCounterStore

public sealed class ValKeyRateCounterStore : IRateCounterStore

Fleet-wide IRateCounterStore on ValKey/Redis. Fixed-window INCR+EXPIRE counters — deliberately the same enforcement character as the in-memory default, so switching stores does not change how thresholds bite.

Backend failures propagate to the caller: AbuseProtection decides fail-open vs fail-closed per layer; this store never makes that policy call, so there is no catch here.

Constructors

ValKeyRateCounterStore(IOptions<ValKeyHotStateOptions> options, IConnectionMultiplexer connection)

Methods

ValueTask ResetAsync(string key, CancellationToken cancellationToken = default(CancellationToken))
ValueTask<long> GetAsync(string key, CancellationToken cancellationToken = default(CancellationToken))
ValueTask<long> IncrementAsync(string key, TimeSpan window, CancellationToken cancellationToken = default(CancellationToken))

Increments the counter and returns the new count within the window.

ValKeyRefreshTokenStore

public sealed class ValKeyRefreshTokenStore : IRefreshTokenStore

Fleet-wide IRefreshTokenStore on ValKey/Redis (refresh-token families are hot state and TTL-native: every record dies exactly at its own expiry with no sweeper). Layout per token: a hash at rt:{tokenHash} with fields data (JSON record) and used (0/1), TTL = the token’s remaining lifetime; plus three index SETs (family / session / subject → token hashes) that make the revocation sweeps O(family) instead of O(keyspace).

The no-TOCTOU contract of ValKeyRefreshTokenStore.TryMarkUsedAsync rides a Lua script: read-and-flip of the used field is one atomic server-side step, so two racing refreshes of the same token can never both rotate.

Constructors

ValKeyRefreshTokenStore(IOptions<ValKeyHotStateOptions> options, IConnectionMultiplexer connection, ISentinelClock clock)

Methods

ValueTask RevokeAllForSessionAsync(Guid sessionId, CancellationToken cancellationToken = default(CancellationToken))
ValueTask RevokeAllForSubjectAsync(Guid subjectId, CancellationToken cancellationToken = default(CancellationToken))
ValueTask RevokeFamilyAsync(Guid familyId, CancellationToken cancellationToken = default(CancellationToken))
ValueTask StoreAsync(RefreshTokenRecord record, CancellationToken cancellationToken = default(CancellationToken))
ValueTask<RefreshTokenRecord?> GetAsync(string tokenHash, CancellationToken cancellationToken = default(CancellationToken))
ValueTask<bool> TryMarkUsedAsync(string tokenHash, CancellationToken cancellationToken = default(CancellationToken))

Atomically transitions Used false→true; false when already used or missing.

Nuvora.Nexus.Sentinel.Stores.ValKey.DependencyInjection

ValKeyHotStateServiceCollectionExtensions

public static class ValKeyHotStateServiceCollectionExtensions

Registration helpers for the ValKey/Redis hot-state adapters: fleet-wide IRateCounterStore, ISentinelCacheBus, IRefreshTokenStore (refresh families are hot state) and IChallengeStore over one shared connection.

Methods

static IServiceCollection AddSentinelValKeyHotState(this IServiceCollection services, Action<ValKeyHotStateOptions> configure)

Registers the shared IConnectionMultiplexer plus the ValKey-backed IRateCounterStore, ISentinelCacheBus, IRefreshTokenStore and IChallengeStore. All registrations use TryAdd, so a host that already shares a multiplexer — or brings its own store/bus — keeps its registration; call BEFORE AddSentinel() and AddSentinelEfCoreStores() so these win over the in-process and EF fallbacks. The container owns the connection and disposes it on shutdown.