v1.0.0-preview.1
Pre-release⚠️ Breaking changes since v0.3.0
Cache entries use a new binary envelope (#48, #54)
The CacheEntry envelope changed from JSON-with-a-base64-payload to compact binary framing —
[version:1][flags:1][absExpTicks:8?][sldExpTicks:8?][raw payload]. There is no read shim for the old format.
- Entries written by v0.3.0 or earlier are treated as a cache miss (logged at
Debug,EventId 102 UndeserializableEntry), never an error. A v0.3.0 node cannot read this release's envelope either, so during a rolling deploy each version only sees entries written in its own format. - Nothing is evicted and nothing throws — a node never deletes bytes it cannot read, so rolling deployments are safe and there is no migration step. TTL'd entries are reaped by NATS; TTL-less entries are overwritten by the next
Set. - Impact: expect a cold cache after upgrading. If that is unacceptable, pre-warm it or move to a new bucket /
CacheKeyPrefix. - Details: Cache Entry Format and Upgrades.
NATS.Client.KeyValueStore bumped 2.8.2 → 3.0.1 (major) (#56)
Apps that also reference NATS.Net packages directly (for example NATS.Extensions.Microsoft.DependencyInjection) must move to the matching 3.x versions. The bump required no source changes in this library. The minimum server version is unchanged (NATS 2.11+); integration tests now run against 2.14.3.
CacheEntryJsonContext removed (#48)
The public JsonSerializerContext for CacheEntry is gone, and CacheEntry is now a plain POCO — the [JsonPropertyName] attributes went with it. Code referencing CacheEntryJsonContext, or depending on the stored absexp / sldexp / data JSON property names, no longer compiles or no longer applies. The envelope is an internal storage detail.
A missing BucketName now fails fast (#51)
AddNatsDistributedCache — and AddNatsHybridCache, which delegates to it — now registers options with .Validate(...).ValidateOnStart().
- Under the Generic Host, a null / empty / whitespace
BucketNamethrowsOptionsValidationExceptionat host startup instead of surfacing at first cache use. - Direct
new NatsCache(...)throwsArgumentException("BucketName must be set") instead ofNullReferenceException. Anycatchkeyed onNullReferenceExceptionneeds updating.
TryGetAsync propagates caller cancellation (#52)
NatsCache.TryGetAsync(string, IBufferWriter<byte>, CancellationToken) previously caught everything and returned false silently. It still returns false for read failures — now logged at Warning (EventId 101) rather than silently — but an OperationCanceledException from the caller's cancelled token now propagates instead of masquerading as a cache miss.
Relatedly, Remove failures are now consistently logged at Error (previously inconsistent or not logged at all), and every failure is logged exactly once.
Expirations beyond ~68 years are rejected (#54)
NATS encodes message TTLs as (int)ttl.TotalSeconds, which overflows above int.MaxValue seconds. SlidingExpiration, AbsoluteExpiration, and AbsoluteExpirationRelativeToNow windows exceeding 24855.03:14:07 now throw ArgumentOutOfRangeException from Set/SetAsync, instead of writing an entry with an overflowed TTL header. The read path fails closed on the same ceiling, so nothing that can be written reads back as an undeserializable miss.
The MaxValue sentinels are the deliberate exception: DateTimeOffset.MaxValue (absolute) and TimeSpan.MaxValue (sliding / relative) are now normalized to "never expire" rather than producing an enormous TTL.
Expiration is computed from TimeProvider, in UTC, on an inclusive boundary (#46)
Every clock read moved from DateTimeOffset.Now to TimeProvider.GetUtcNow().
- If your container registers a
TimeProvider, the cache now uses it for all expiration math. See Controlling Expiration Timing. - The absolute-expiration read check is now inclusive (
>=): an entry whose absolute expiration is exactly "now" reads as expired, matching TTL computation and BCLMemoryCache. - The public
NatsCacheconstructor is unchanged (the clock is an internalinit-only property), so this is not an ABI break.
Also worth knowing (not breaking)
- DI registration shape changed (#60).
AddNatsDistributedCachenow registers the concreteNatsCacheas the singleton and forwards bothIDistributedCacheand the newINatsCacheMaintenanceto that one instance, so a purge and a cache read share the same KV store, key prefix, and key encoder. ResolvingIDistributedCachebehaves as before. - Both packages are now marked trimmable (#58) via
<IsAotCompatible>true</IsAotCompatible>(zero IL2xxx/IL3xxx warnings on net8.0 and net10.0). The key-validation regex moved fromRegexOptions.Compiledto source-generated[GeneratedRegex]. - New direct dependency:
System.Diagnostics.DiagnosticSource10.0.9 for the telemetry types (#56). It was already in the graph transitively, so this adds no new nodes. - Target frameworks are unchanged: net8.0 and net10.0.
What's Changed
- #40 Inject TimeProvider by @matthewdevenny in #46
- Add cache-envelope serialization benchmarks + manual workflow by @matthewdevenny in #47
- #37 Replace JSON+base64 cache envelope with compact binary framing by @matthewdevenny in #48
- #42 validate options by @matthewdevenny in #51
- #43 Log swallowed TryGetAsync exceptions by @matthewdevenny in #52
- #38 add bucket auto creation option by @matthewdevenny in #53
- CodeCargo: update code-cargo/cargowall-action to v1.3.3 by @matthewdevenny in #55
- #49 Address unserializable entries by @matthewdevenny in #54
- #39 Add OpenTelemetry by @matthewdevenny in #56
- CodeCargo: update code-cargo/cargowall-action to v1.3.5 by @matthewdevenny in #57
- #45 Add purge-by-prefix cache maintenance helper by @matthewdevenny in #60
- #45 Write TryGetAsync payload directly into the caller buffer by @matthewdevenny in #59
- #45 Enable AOT/trim analyzers and source-generate key regex by @matthewdevenny in #58
Full Changelog: v0.3.0...v1.0.0-preview.1