feat: V12 API modernization, AOT-safe assembly config, V10→V11 migration, and full coverage#1178
Merged
glennawatson merged 27 commits intomainfrom Apr 14, 2026
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR modernizes Akavache for a V12 release by removing implicit reflection-based assembly discovery in favor of explicit/AOT-safe configuration, introducing a V10→V11 migration pipeline, and expanding/strengthening test coverage across core and extension packages.
Changes:
- Adds AOT-safe builder/config APIs (explicit
applicationName, explicit executing assembly, connection abstractions) and updates related call sites. - Introduces new packages/abstractions (e.g.,
Akavache.SystemTextJson.Bson,IAkavacheConnection/IAkavacheTransaction) and refactors internals for testability. - Expands tests significantly and adjusts parallelization/isolation to reduce flakiness.
Reviewed changes
Copilot reviewed 89 out of 119 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Akavache.Tests/RequestCacheTests.cs | Adds coverage for in-flight request tracking/removal and disables parallel execution for isolation. |
| src/Akavache.Tests/RelativeTimeExtensionsTests.cs | Adds broad coverage for TimeSpan-based extension overloads and null-guard branches. |
| src/Akavache.Tests/PreserveAttributeTests.cs | Adds targeted unit tests for the internal PreserveAttribute surface. |
| src/Akavache.Tests/NewtonsoftJsonBuilderExtensionsTests.cs | Adds tests for Newtonsoft builder extensions and factory lambda execution coverage. |
| src/Akavache.Tests/NewtonsoftDateTimeContractResolverTests.cs | Adds tests for contract resolver converter behavior and recursion guard. |
| src/Akavache.Tests/Mocks/SerializerTestModel.cs | Adds a shared model to support serializer tests. |
| src/Akavache.Tests/Mocks/SerializerTestContext.cs | Adds source-gen JsonSerializerContext for AOT-style serializer tests. |
| src/Akavache.Tests/Mocks/InMemoryAkavacheTransaction.cs | Adds in-memory transaction mock for connection abstraction tests. |
| src/Akavache.Tests/Mocks/InMemoryAkavacheConnection.cs | Adds in-memory connection mock (incl. failure simulation) for sqlite cache logic tests. |
| src/Akavache.Tests/LoginInfoTests.cs | Adds constructor coverage for LoginInfo. |
| src/Akavache.Tests/LoginExtensionsTests.cs | Adds coverage for GetLogin null-deserialization branch. |
| src/Akavache.Tests/LegacyFileLocationTests.cs | Disables parallel execution for shared CacheDatabase state scenarios. |
| src/Akavache.Tests/ISerializerDefaultMethodTests.cs | Adds tests asserting default interface method behavior for new JsonTypeInfo overloads. |
| src/Akavache.Tests/HttpExtensionsTests.cs | Adds tests for HTTP extension argument validation and stream error paths. |
| src/Akavache.Tests/Executors/AkavacheTestExecutor.cs | Adds a test executor that resets global Akavache/Splat state for isolation. |
| src/Akavache.Tests/DownloadUrlExtensionsTests.cs | Updates initialization to require application name; disables parallel execution. |
| src/Akavache.Tests/BinaryHelpersTests.cs | Adds unit tests for BinaryHelpers little-endian decoders. |
| src/Akavache.Tests/BackwardCompatibilityTests.cs | Updates initialization API usage and disables parallel execution for global state. |
| src/Akavache.Tests/AssemblyInfoHelperTests.cs | Adds coverage for the obsolete reflection-based assembly helper. |
| src/Akavache.Tests/Akavache.Tests.csproj | Adds CS0618 suppression and TUnit executor usings; adds new project references. |
| src/Akavache.SystemTextJson/SystemJsonBsonSerializer.cs | Removes BSON serializer from base STJ package (split into dedicated project). |
| src/Akavache.SystemTextJson/AkavacheBuilderExtensions.cs | Registers serializers with UniversalSerializer and removes BSON builder APIs from this package. |
| src/Akavache.SystemTextJson/Akavache.SystemTextJson.csproj | Removes Newtonsoft deps and exposes internals to tests/BSON package. |
| src/Akavache.SystemTextJson.Bson/SystemJsonBsonSerializer.cs | Introduces BSON serializer implementation in the split package. |
| src/Akavache.SystemTextJson.Bson/AkavacheBuilderExtensions.cs | Adds builder extensions for configuring BSON serializer usage. |
| src/Akavache.SystemTextJson.Bson/Akavache.SystemTextJson.Bson.csproj | Adds new BSON project with Newtonsoft.Json.Bson dependencies. |
| src/Akavache.Sqlite3/SqliteAkavacheTransaction.cs | Adds sqlite implementation of the new transaction abstraction. |
| src/Akavache.Sqlite3/SqliteAkavacheConnection.cs | Adds sqlite implementation of the new connection abstraction and legacy V10 reads. |
| src/Akavache.Sqlite3/AkavacheBuilderExtensions.cs | Refactors sqlite cache creation and exposes internals for testing/migration. |
| src/Akavache.Sqlite3/Akavache.Sqlite3.csproj | Adds InternalsVisibleTo for migration project. |
| src/Akavache.Settings/Akavache.Settings.csproj | Adds InternalsVisibleTo for test projects. |
| src/Akavache.Settings.Tests/SettingsCacheTests.cs | Updates nullability annotations for applicationName pass-through. |
| src/Akavache.Settings.Tests/SettingsBaseFallbackTests.cs | Updates nullability annotations for applicationName pass-through. |
| src/Akavache.Settings.Tests/EncryptedSettingsCacheTests.cs | Updates nullability; adjusts expectations for obsolete assembly metadata defaults. |
| src/Akavache.Settings.Tests/AkavacheBuilderExtensionsTests.cs | Updates nullability annotations for applicationName pass-through. |
| src/Akavache.Settings.Tests/Akavache.Settings.Tests.csproj | Adds CS0618 suppression. |
| src/Akavache.NewtonsoftJson/NewtonsoftSerializer.cs | Refactors BSON/JSON fallback paths and exposes internals for tests. |
| src/Akavache.NewtonsoftJson/NewtonsoftDateTimeTickConverter.cs | Simplifies DateTime kind switch logic/comments. |
| src/Akavache.NewtonsoftJson/NewtonsoftDateTimeOffsetTickConverter.cs | Refactors object-token reading via JObject and exposes helper for tests. |
| src/Akavache.NewtonsoftJson/AkavacheBuilderExtensions.cs | Registers Newtonsoft serializers with UniversalSerializer. |
| src/Akavache.NewtonsoftJson/Akavache.NewtonsoftJson.csproj | Adds InternalsVisibleTo for tests. |
| src/Akavache.EncryptedSqlite3/Akavache.EncryptedSqlite3.csproj | Adds InternalsVisibleTo; includes new connection/transaction compilation units. |
| src/Akavache.Drawing/ImageCacheExtensions.cs | Replaces ad-hoc null checks with shared argument helper; refactors image buffer validation call. |
| src/Akavache.Drawing/BitmapImageExtensions.cs | Refactors image buffer validation into internal helpers and improves docs. |
| src/Akavache.Drawing/Akavache.Drawing.csproj | Adds InternalsVisibleTo for tests. |
| src/Akavache.Core/SerializerExtensions.cs | Removes reflection-based APIs and standardizes null-guards; extracts ShouldRefetchCachedValue. |
| src/Akavache.Core/RelativeTimeExtensions.cs | Standardizes null-guards via helper and expands method bodies for testability. |
| src/Akavache.Core/Polyfills/UnconditionalSuppressMessageAttribute.cs | Adds polyfill to enable consistent attribute usage on older TFMs. |
| src/Akavache.Core/Polyfills/RequiresUnreferencedCodeAttribute.cs | Adds polyfill to enable consistent attribute usage on older TFMs. |
| src/Akavache.Core/Polyfills/RequiresDynamicCodeAttribute.cs | Adds polyfill to enable consistent attribute usage on older TFMs. |
| src/Akavache.Core/InMemoryBlobCacheBase.cs | Extracts vacuum logic into internal static helpers for isolated testing. |
| src/Akavache.Core/ImageExtensions.cs | Standardizes null-guards and consolidates image buffer validation helpers. |
| src/Akavache.Core/ISerializer.cs | Adds JsonTypeInfo overloads as default interface methods for AOT-friendly usage. |
| src/Akavache.Core/IAkavacheTransaction.cs | Adds new transaction abstraction for backend implementations. |
| src/Akavache.Core/IAkavacheInstance.cs | Obsoletes assembly metadata properties and documents sentinel defaults. |
| src/Akavache.Core/IAkavacheConnection.cs | Adds new async connection abstraction and operational methods (checkpoint/compact/etc.). |
| src/Akavache.Core/IAkavacheBuilder.cs | Adds WithExecutingAssembly and updates serializer API surface docs. |
| src/Akavache.Core/HttpService.cs | Extracts request/response helpers and opens MakeWebRequest for testing. |
| src/Akavache.Core/Helpers/DateTimeHelpers.cs | Extracts DateTime conversion/recovery logic for shared reuse and testing. |
| src/Akavache.Core/Helpers/BinaryHelpers.cs | Adds endian-explicit binary read helpers for legacy framework support. |
| src/Akavache.Core/Helpers/AssemblyInfoHelper.cs | Adds obsolete reflection-based assembly helper retained for compat. |
| src/Akavache.Core/Helpers/ArgumentExceptionHelper.cs | Adds shared argument validation helper for consistent guard patterns. |
| src/Akavache.Core/Core/SecurityUtilities.cs | Removes redundant empty-name checks (relies on existing guards). |
| src/Akavache.Core/Core/RequestCache.cs | Exposes internal removal helper for tests. |
| src/Akavache.Core/Core/AkavacheBuilder.cs | Removes reflection-based discovery defaults; adds explicit executing assembly config and testable internals. |
| src/Akavache.Core/CheckpointMode.cs | Adds public enum for checkpoint strength abstraction. |
| src/Akavache.Core/CacheDatabase.cs | Requires explicit application name on initialization/builder creation; improves nullability invariants. |
| src/Akavache.Core/Akavache.csproj | Adds InternalsVisibleTo for migration project. |
| src/Akavache.Benchmarks.V10/Akavache.Benchmarks.V10.csproj | Marks benchmarks as not AOT-compatible and disables trim/AOT analyzers. |
| docs/migration-v11-to-v12.md | Adds v11→v12 migration documentation, including package split and serializer API changes. |
| README.md | Adds link to the new v11→v12 migration guide and clarifies v10→v11 wording. |
754f088 to
47b4fba
Compare
47b4fba to
03d28d5
Compare
03d28d5 to
8ec5fc9
Compare
f0ef88b to
2f7d4d0
Compare
…nization - Require explicit application name on CacheDatabase.Initialize and CreateBuilder APIs - Deprecate parameterless CreateBuilder() overload via [Obsolete] - Remove reflection-based SerializerExtensions.Get(IBlobCache, string, Type) method - Revert non-generic Deserialize(byte[], Type) from ISerializer interface - Split SystemTextJson.Bson into separate project (Akavache.SystemTextJson.Bson) - Add Akavache.V10toV11 migration project - Add polyfills for RequiresUnreferencedCode/RequiresDynamicCode on net462/net472/net481 - Replace Assembly.Location with AppContext.BaseDirectory + AssemblyFileVersionAttribute - Introduce IAkavacheConnection / IAkavacheTransaction abstractions so SqliteBlobCache can be driven by an in-memory connection in unit tests without touching disk - Add SqliteAkavacheConnection / SqliteAkavacheTransaction real-SQLite implementations and InMemoryAkavacheConnection / InMemoryAkavacheTransaction test mocks - Extract pure helpers (DateTimeHelpers, BinaryHelpers, AssemblyInfoHelper, ArgumentExceptionHelper) from UniversalSerializer/NewtonsoftSerializer/ SystemJsonBsonSerializer so the edge-case branches can be unit-tested directly - Deprecate reflection-based IAkavacheInstance.ExecutingAssembly/Name/Version and add AOT-safe IAkavacheBuilder.WithExecutingAssembly(Assembly) for explicit caller-owned assembly metadata; the builder no longer probes Assembly.GetEntryAssembly during construction - Convert method-group Select(...) calls in ImageExtensions/BitmapImageExtensions to explicit static lambdas and make ThrowOnBadImageBuffer/BytesToImage internal static so branch coverage instrumentation sees every path - Promote SqliteBlobCache's private helpers (Initialize, TryGetLegacyValueAsync) plus new GetOrCreateHttpService/ToExpiryValue to internal static methods taking explicit IAkavacheConnection/IScheduler parameters, and add direct tests for both the Sqlite3 and EncryptedSqlite3 variants - Refactor SystemJsonBsonSerializer wrapper unwrap to an internal static helper (TryUnwrapObjectWrapper) and NewtonsoftSerializer's equivalent (TryUnwrapSimpleObjectWrapper) with direct tests for both happy and error paths - Add AkavacheTestExecutor + [NotInParallel(\"CacheDatabaseState\")] wiring so every test that mutates CacheDatabase / AkavacheBuilder / AppLocator / UniversalSerializer global state serialises against the same group and no longer races - Fix flaky TryAlternativeSerializers test by moving UniversalSerializerTests into the shared CacheDatabaseState group - Mark IsolatedStorage cache directory methods with [ExcludeFromCodeCoverage] - Add [ExcludeFromCodeCoverage] to unreachable platform-dependent paths - Add NoWarn CS0618 to test projects for intentional deprecated API testing - Fix NewtonsoftSerializer.TryDeserializeFromOtherFormats value-type BSON check - Increase test count from ~1600 to 3726 with 0 failures - Reach 100% line and 100% branch coverage across every production assembly - Build produces 0 warnings and 0 errors across all target frameworks
2f7d4d0 to
3740685
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1178 +/- ##
============================================
+ Coverage 56.34% 100.00% +43.65%
============================================
Files 34 41 +7
Lines 3143 2864 -279
Branches 739 506 -233
============================================
+ Hits 1771 2864 +1093
+ Misses 1116 0 -1116
+ Partials 256 0 -256 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
…elpers Introduce AkavacheTestExecutorBase following the ReactiveUI BuilderTestExecutorBase pattern so global-state cleanup (CacheDatabase, AkavacheBuilder, Splat locator) lives in one place with virtual ResetStateAsync/ConfigureBuilder hooks. The standard AkavacheTestExecutor is now a sealed subclass. ResetStateAsync swaps in a fresh ModernDependencyResolver so contract-based ISerializer registrations no longer leak between tests. Also splits SettingsBase.GetBlobCacheForClass into internal helpers and SettingsStorage.InitializeAsync into an EagerLoadProperties helper so each strategy can be unit tested directly, and adds coverage tests for both.
|
Azure Pipelines: 1 pipeline(s) were filtered out due to trigger conditions. |
AkavacheTestExecutorBase.ResetStateAsync swaps in a fresh ModernDependencyResolver each run but previously abandoned the old one. Capture the previous locator via AppLocator.GetLocator() before replacement and dispose it if it implements IDisposable, so test runs don't accumulate orphaned resolver state.
…ttpServer SettingsBase gains a second constructor that accepts explicit Func<IBlobCache> resolvers for UserAccount / LocalMachine / InMemory, and GetBlobCacheForClass / TryGetFromCacheDatabase now have overloads that take those delegates. The parameterless constructor keeps wiring the ambient CacheDatabase resolvers, but each resolver is wrapped in TryReadAmbientCache so one throwing getter no longer short-circuits the remaining fallbacks (previously a bug: LocalMachine and InMemory paths were unreachable when UserAccount threw). Adds direct coverage tests for every branch plus EagerLoad, InitializeAsync, and the DeleteSettingsStore catch path. Replaces the HttpListener-based TestHttpServer with a TcpListener implementation. On .NET 8 Linux the managed HttpListener races its own read completion against Stop/Close and raises a NullReferenceException from HttpConnection.get_LocalEndPoint on a ThreadPool worker — unhandled, process-terminating, and impossible to catch from user code. A minimal TCP server eliminates the race entirely.
… gating
Drops the assembly-wide [NotInParallel] attribute on Akavache.Tests and
Akavache.Settings.Tests — it forced every test in the assembly to run serially,
which defeated TUnit's per-class parallel grouping and turned full runs into
3-minute jobs. Parallel gating is now per-class, scoped to the resource each
class actually contends for (CacheDatabaseState, NativeSqlite, BitmapLoader).
Converts every Sqlite/Encrypted blob cache test that previously opened a real
.db file to an InMemoryAkavacheConnection via the existing IAkavacheConnection
overload. This removes the native SQLCipher / SQLite provider from the parallel
path, which was the source of the intermittent AccessViolationException on CI
(the native provider is not safe under concurrent access from multiple managed
connections pointing at separate DBs). The genuine integration tests that still
need a real native DB — builder extensions, smoke tests, connection tests,
serialization compatibility, login extensions, update expiration — are marked
[NotInParallel("NativeSqlite")] so they serialize against each other.
Image/bitmap tests are marked [NotInParallel("BitmapLoader")] since they swap
the global BitmapLoader.Current for the duration of each test.
Main suite drops from ~2m50s to ~34s per TFM; coverage stays at 100%/100%.
- Repo restructure: tests/, benchmarks/, samples/, compat/ subfolders under src/; updated slnx, csproj refs, CLAUDE.md, codecov.yml; moved BENCHMARK_REPORT.md and PERFORMANCE_SUMMARY.md to docs/ - Enabled strict CA Design/Performance/Reliability/Usage/Security/Maintainability/Globalization/Interoperability/Naming, SYSLIB obsoletions, IDE language/naming rules, and a curated ReSharper inspection set in .editorconfig - Comprehensive RCS rule pass: enabled simplification/quality/performance/maintainability/documentation categories, disabled rules with a CA/SA equivalent, downgraded RCS1223 to suggestion, disabled RCS1046/RCS1255/RCS1226/RCS1249 with rationale - Added <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild> to Directory.Build.props so IDE* analyzers fire at compile time - Test-only editorconfig overrides: disabled IDE0200 (TUnit Assert.That method-group binding silently misses throw assertions), RCS1079 (intentional in fakes), RCS1223 (no DebuggerDisplay on test fixtures) - Akavache.Sqlite3/SqliteBlobCache.cs cleanup: moved using inside #if ENCRYPTED, replaced lazy backing field with C# 13 'field' keyword on HttpService property - Added Akavache.Core/Polyfills/HashCode.cs (xxHash32-based, NETFRAMEWORK gate, Combine<T1..T4> + AddBytes); Size.GetHashCode now delegates to HashCode.Combine - Removed obsolete PreserveAttribute (pre-MAUI linker hint) and its tests; cleaned up its sole usage in SqliteBlobCache - Suppressed CA2234 in tests where Akavache deliberately exercises the string-URL public API overloads; fixed Splat UnregisterAll(typeof) → UnregisterAll<> - Configured CA1852 with api_surface=private,internal so it only flags non-public; sealed 5 mock/test types - Inverted ArgumentExceptionHelper, DateTimeHelpers, AkavacheBuilder, and InMemoryBlobCacheBase null-guards to early-return form (RCS1208) - Sample WPF/MAUI value converters: replaced NotImplementedException ConvertBack with Binding.DoNothing / BindableProperty.UnsetValue (canonical one-way-binding sentinels recognised by the binding engine) - Test helper TaskAssertionExtensions.ShouldThrowAsync<TException> bypasses TUnit's Func<Task<T?>> overload (Task<T>/Task<T?> covariance); migrated 172 sites across 5 test files to fix all 344 CS8619 warnings - InMemoryBlobCacheBaseTests: converted try/finally DisposeAsync patterns to await using - Fixed test stub Assembly.GetCustomAttributes overrides to return Attribute[] (Array.Empty<Attribute>(), not [] which infers object[]) so System.Attribute.GetCustomAttribute<T>() succeeds - Corrected expected exception types: WithInMemoryShouldThrowWhenNoSerializerConfigured and StringConstructorShouldThrowWhenSerializerNotRegistered now expect ArgumentNullException / InvalidOperationException to match production - Restored using-statement preference and minor IDE0005/SA1515/SA1116 cleanups; ConcurrencyTests methods made instance (TUnit source-gen requires non-static), class sealed - All 3814 tests pass (3730 Akavache.Tests + 84 Akavache.Settings.Tests on net8/net9)
- Removed obsolete `AdditionalConcurrencyTests.cs` - Applied modern syntax improvements (e.g., target-typed `new()`, inline null checks, `static` lambdas) - Replaced conditional logic with shorter return statements - Consolidated redundant null and empty checks - Added `DebuggerDisplay` to `Size` struct for improved debugging - Updated SQLite connection checkpoint calls - Removed unnecessary parameter validations; replaced repetitive guards with `ArgumentExceptionHelper.ThrowIfNull` - Simplified test classes, cleanup, and structure to align with project standards - Ensured full test pass against all configurations
- Deleted obsolete `RunCompatTest.ps1` and redundant `tests.runsettings` - Simplified and modernized syntax across files (`u8.ToArray` for readability, inline validations, etc.) - Consolidated null/empty checks into `ArgumentExceptionHelper` - Added missing test cases for `IsWebP` functionality - Optimized validation logic and helper utility functions - Enhanced test reliability and reduced unnecessary overhead - Ensured consistency with the latest codebase standards and conventions
- Updated syntax with range expressions ([..]), `static` lambdas, and modern inline validations - Streamlined type references for consistency (e.g., `Akavache.*` → appropriate namespaces) - Optimized object initialization and refactored repetitive code patterns - Updated comments and XML docs to match project standards - Enhanced overall readability and reduced potential code duplication
…nd enhance test assertions
…y module and source paths
…cheEntry and LoginInfo
…streamline test cases
…d test for lazy initialization of HttpService property
…l compilation in RelativeTimeExtensions
…ion in AkavacheBuilderExtensions
…gex import in serializers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR is the V12 modernization pass for Akavache. The end-user surface is the four breaking-change sections below plus a migration guide; the rest — analyzer sweep, polyfills, cross-TFM build plumbing, test rewrite, coverage config — is consolidated under Maintainer notes.
Assembly.GetEntryAssembly()at construction time. There is a new AOT-safe fluent API.CacheDatabase.Initialize/CreateBuilder— the old parameterless overloads are marked[Obsolete].JsonTypeInfoserialization — exposed via extension methods inAkavache.SystemTextJson.Bson, soAkavache.Coreitself stays serializer-agnostic and Newtonsoft-only consumers do not transitively pull inSystem.Text.Json.A full user-facing migration walkthrough lives in
docs/migration-v11-to-v12.md.1. Reflection is out — use
WithExecutingAssemblyinsteadWhat changed.
IAkavacheInstance.ExecutingAssembly,ExecutingAssemblyNameandVersionare now[Obsolete]. TheAkavacheBuilderconstructor no longer callsAssembly.GetEntryAssembly()/Assembly.GetExecutingAssembly()/GetCustomAttribute<AssemblyFileVersionAttribute>(). By default these properties now return:ExecutingAssembly→ Akavache's own assembly (a sentinel)ExecutingAssemblyName→nullVersion→nullWhy. Reflection-based assembly discovery does not reliably yield the caller's assembly in trimmed or NativeAOT-published apps — the entry assembly can go missing, attributes can be trimmed, and the resulting cache paths shift at runtime. Worse, relying on
Assembly.GetEntryAssembly()adds a hidden reflection pin point that blocks trimming.What to do. If your app reads any of those three properties, stop reading them from Akavache and hold your own
Assemblyreference instead:If you want Akavache itself to track the value (so it flows through anything that still reads
IAkavacheInstance), use the new AOT-safe fluent method on the builder:Because the assembly reference is caller-owned, there is no runtime reflection discovery at all.
WithExecutingAssemblyreads the name andAssemblyFileVersionAttributeoff the instance you pass, which is trim/AOT-safe.The old behaviour is still available behind the legacy
AssemblyInfoHelper(also marked[Obsolete]), but nothing in the builder calls it by default anymore.2. V10 → V11 migration
Akavache V11 changed the on-disk format — the old sqlite3
CacheElementtable layout was replaced by the newCacheEntryschema, and the per-cache file names changed. If you ship V11+ to users who already have V10 data on disk, you need to migrate it.What ships in this PR.
Akavache.V10toV11project with aV10MigrationServicethat knows how to read the legacyCacheElement/CacheIndextables and project them into the new shape.IAkavacheBuilder.WithV10ToV11Migration(...)extension that wires the migration into the builder pipeline so it runs the first time the V11 cache is opened.SqliteBlobCache/EncryptedSqliteBlobCacheviaTryGetLegacyValueAsync, so keys that never got migrated still resolve against the legacy table as a fallback.How to opt in. If your app used V10 Akavache before upgrading, add the V10→V11 migration step when configuring the builder:
On first open, the migration service:
FileLocationOption.Legacyif your app is using the legacy directory layout).CacheElementrows plus the old file-name map.CacheEntrytable on the corresponding V11 cache, preserving keys, expiration, and type metadata.TryGetLegacyValueAsyncfallback insideSqliteBlobCache/EncryptedSqliteBlobCache.Migration characteristics.
WithV10ToV11Migration()on an already-migrated store is a no-op.EncryptedSqlite3flavour migrates the same way — you pass the V10 password through the same builder path.Build()time, synchronously from the caller's perspective, so it completes before your app starts hitting the cache for reads.3. Required application name
CacheDatabase.Initialize<T>()andCacheDatabase.CreateBuilder()now require an explicitapplicationNameargument. The old parameterlessCreateBuilder()overload is marked[Obsolete]but still compiles.This is the single biggest source of cache-path drift between reflection-based builds and NativeAOT — being explicit kills the ambiguity entirely.
4. AOT-safe
JsonTypeInfoserialization lives inAkavache.SystemTextJson.BsonISerializerinAkavache.Coredeliberately stays serializer-agnostic. It does not know aboutSystem.Text.Jsonand does not declare anyJsonTypeInfo<T>overloads. The base interface is just the two reflection-based members:The AOT-safe
JsonTypeInfo<T>path is provided by extension methods that ship inAkavache.SystemTextJson.Bson(which transitively brings inAkavache.SystemTextJson). Importingusing Akavache.SystemTextJson;gives you:Under the hood the extension methods type-check the runtime serializer:
SystemJsonSerializer→ routes to its staticDeserializeAot/SerializeAotmethods (pureJsonTypeInfopath, no reflection)SystemJsonBsonSerializer→ routes through the same path (BSON cannot be AOT-encoded, so the AOT overload emits plain JSON bytes)ISerializer(for example Newtonsoft-backed) → throwsNotSupportedException. Use the non-typedDeserialize<T>(byte[])/Serialize<T>(T)overloads on Newtonsoft.This indirection keeps
Akavache.Corefree of a hard dependency onSystem.Text.Json, so Newtonsoft-only consumers do not transitively pull it in.Other user-visible changes
SystemTextJson.Bsonsplit out into its ownAkavache.SystemTextJson.BsonNuGet package. If you were usingSystemJsonBsonSerializerfromAkavache.SystemTextJson, switch your package reference.ISerializer.Deserialize(byte[], Type)was reverted. The non-generic overload is gone — useDeserialize<T>(byte[]).SerializerExtensions.Get(IBlobCache, string, Type)(the reflection-based variant) was removed.CheckpointModeenum added for explicit control over SQLite WAL checkpoint strategy.IAkavacheConnection/IAkavacheTransactionabstractions let you driveSqliteBlobCache/EncryptedSqliteBlobCachewith a custom backing connection instead of the defaultSqliteAkavacheConnection/SqliteAkavacheTransactionimplementations shipped inAkavache.Sqlite3.System.Threading.Lockon net9+.AkavacheBuilder,InMemoryBlobCacheBase, andNewtonsoftSerializeruse C# 13'sLocktype under#if NET9_0_OR_GREATER, which lets the JIT use tighter lock-acquire codegen than the legacy object-monitor path on those targets.NewtonsoftSerializeradditionally stops locking on the user-suppliedJsonSerializerSettingsand uses a private lock object instead.x:DataTypeso MAUI's compiled-bindings analyzer is happy (no more XC0022 warnings onMainPage.xaml/EditTodoPage.xaml).Binding.DoNothing/BindableProperty.UnsetValuefromConvertBackinstead of throwing — the canonical one-way-converter idiom that the binding engine recognises without exception round-tripping.Maintainer notes
Test count and coverage
Test count is up from ~1600 to 3940 across net8.0 and net9.0, with 0 skipped tests (down from 14 skipped — the chronically-flaky
DateTimeSerializationShouldBeConsistentAndAccurateparameterised test and the duplicatedSettingsStorageTestsare gone). Production assemblies sit at 100% line / 100% branch coverage: 4438/4438 lines, 1714/1714 branches acrossAkavache.Core,Akavache.Drawing,Akavache.EncryptedSqlite3,Akavache.NewtonsoftJson,Akavache.Settings,Akavache.Sqlite3,Akavache.SystemTextJson, andAkavache.SystemTextJson.Bson.Most of the new coverage came from extracting previously-private helpers (
SqliteBlobCache.ToExpiryValue,GetOrCreateHttpService,TryGetLegacyValueAsync,InitializeDatabase;BitmapImageExtensions.ThrowOnBadImageBuffer/ThrowOnNullOrBadImageBuffer/BytesToImage;UniversalSerializer.CastAsDateTime/CastAsDateTimeOffset/FindKeyCandidates/TryDeserializeCandidate; theJsonTypeInfodispatch helpers; etc.) tointernal staticmethods with explicit parameters and adding direct unit tests for each.UniversalSerializerTestswas moved into the sharedCacheDatabaseStateNotInParallelgroup to fix a pre-existing flaky race against the builder-extension tests that also mutateUniversalSerializer._registeredSerializerFactories.VSTest → Microsoft.Testing.Platform (MTP) + TUnit
TestingPlatformDotnetTestSupportis enabled inDirectory.Build.props, MTP is discovered viasrc/global.json, andsrc/tests.runsettingswas deleted (legacy VSTest file).CacheDatabase/AppLocatoruses a sharedAkavacheTestExecutorbase together with TUnit's[NotInParallel("CacheDatabaseState")]so cross-test global-state mutation is serialized. Previously this was handled ad-hoc and leaked.NotInParallelgroups — no disk contention.TaskAssertionExtensions.ShouldThrowAsync<TException>(this Task)helper lets tests assert that a task faults with a specific exception without going through TUnit'sAssert.That<T>(Func<Task<T?>>)overload (which fails to unifyTask<T>withTask<T?>for non-nullableTand produces CS8619). The five test files exercising SQLite / encrypted / in-memory disposed-state and arg-validation paths use this helper end-to-end, so the suite builds clean withWarningsAsErrorsfor nullability.InMemoryBlobCacheBaseTestsconsistently usesawait using var cache = CreateCache();instead oftry { … } finally { await cache.DisposeAsync(); }.Assembly.GetCustomAttributesoverrides return a typed staticAttribute[]field — not a collection-expression literal[]— because the return type isobject[]and[]would be inferred asobject[], which breaks the framework's internal cast inCustomAttributeExtensions.GetCustomAttribute<T>(Assembly).ConcurrencyTestsis a sealed instance-method test class (TUnit's source generator instantiates fixtures, sostatictest methods would fail at codegen time).Code coverage configuration (
src/testconfig.json)The previous
testconfig.jsonused invalid top-level keys (platform,extensions[]) that are not part of the Microsoft.Testing.Platform schema and were silently ignored. The file has been rewritten to use the documentedcodeCoverage.Configuration.CodeCoverageschema withAttributes.Excludefor:System.CodeDom.Compiler.GeneratedCodeAttributeSystem.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttributeSystem.Diagnostics.DebuggerHiddenAttributeSystem.Diagnostics.DebuggerNonUserCodeAttributeThis correctly excludes source-generator output (notably
RegexGenerator.g.csfrom[GeneratedRegex]) from the coverage numerator and denominator.ModulePaths.Include/Excludeare intentionally not specified: per the docs, emptyIncludeclauses imply all, and MTP's defaultIncludeTestAssembly: falsealready drops the test assemblies without any custom filter.Cross-TFM build plumbing
<EnableWindowsTargeting>true</EnableWindowsTargeting>added toDirectory.Build.props. Combined with dropping theIsOsPlatform('Windows')guard onAkavacheWindowsDesktopTargets, this lets Linux / macOS CI and developer machines buildnet462/net472/net481andnet8.0/9.0/10.0-windows10.0.19041.0TFMs, mirroring ReactiveUI's setup. Previously those targets only compiled on Windows runners, so TFM-specific regressions surfaced only at PR time.Akavache.Core/Polyfillsships typed stand-ins forRequiresUnreferencedCodeAttribute,RequiresDynamicCodeAttribute,DoesNotReturnAttribute,NotNullAttribute,UnconditionalSuppressMessageAttribute, andCallerArgumentExpressionAttribute, gated#if !NET.InternalsVisibleTois set for every satellite assembly, soAkavache.NewtonsoftJson/SystemTextJson/SystemTextJson.Bson/Sqlite3/Settings/V10toV11can apply[RequiresUnreferencedCode]and friends unconditionally — the old#if NET6_0_OR_GREATERgates around every AOT-annotated method are gone (six duplicated method declarations removed fromAkavache.NewtonsoftJson/AkavacheBuilderExtensions.csalone).HashCodepolyfill (Akavache.Core/Polyfills/HashCode.cs,#if NETFRAMEWORK) — xxHash32-based withCombine<T1..T4>andAddBytes(ReadOnlySpan<byte>).Size.GetHashCode,LoginInfo, andCacheEntryhash through it. The polyfill usesRandomNumberGenerator.Create()for the per-process seed (notSystem.Random, to satisfy CA5394) and itsToHashCodeis markedreadonly.Source-generator regex split
DateTimeHelpers.Iso8601Regex()andSystemJsonBsonSerializer.GetDateRegex()both use[GeneratedRegex]on net7+, which doesn't exist onnet462/net472/net481/netstandard2.0. Each has been split into two sibling partial files —*.Regex.SourceGen.cs(#if NET7_0_OR_GREATER,[GeneratedRegex]partial method) and*.Regex.NetFramework.cs(#if !NET7_0_OR_GREATER, staticreadonly Regexfield + body method returning it) — so the main file stays TFM-clean and callers are unchanged.Netfx API parity fixes
Everything the netfx TFMs needed now that they actually compile cross-platform:
UniversalSerializer:ArgumentNullException.ThrowIfNullreplaced withArgumentExceptionHelper.ThrowIfNull(the BCL method is net6+). The"..."-quotedstring trimming path stopped usingstring.StartsWith(char)+ range indexer[1..^1](both net6+) and uses index access +Substringinstead.RequestCache.GetOrCreateRequest: the 3-argumentConcurrentDictionary.GetOrAdd(key, factory, arg)overload is net-core-only — replaced with the 2-argument overload that closes overfetchFuncdirectly. Same visible behaviour.InMemoryBlobCacheBase.VacuumExpiredEntries/CollectExpiredKeysandSqliteBlobCache.TryGetLegacyValueAsyncdrop thein DateTimeOffsetparameter modifier (RCS1242 —DateTimeOffsetis not areadonly struct, soinforces defensive copies at every member access).Akavache.Settings/AkavacheBuilderExtensions.GetLoadedSettingsStore:Dictionary<TKey,TValue>.GetValueOrDefaultis net6+ → switched toTryGetValue(..., out var) ? value : null.HttpService.ProcessWebResponse: decorated with[SuppressMessage("Style", "IDE0200", ...)]— the lambda aroundReadAsByteArrayAsync()cannot be collapsed to a method group because net6+ adds aCancellationTokenoverload that makes the method group ambiguous at theObservable.FromAsynccall site.BinaryHelpersXML doc: cref<see cref="BinaryPrimitives"/>→ plain<c>System.Buffers.Binary.BinaryPrimitives</c>(net472/net481 don't ship the type, so the cref won't resolve).samples/AkavacheTodoMaui/MauiProgram.cs: explicitusing Microsoft.Extensions.Logging;sobuilder.Logging.AddDebug()resolves onnet10.0-android, where the implicit-usings machinery doesn't pull it in.DoesNotReturnAttribute,NotNullAttribute) use theclass Foo : Attribute;shorthand (RCS1251).RequiresDynamicCodeAttributepolyfill's ctor has a<param name="message">doc (SA1611).Layout, analyzer enforcement, and code modernisation
src/tests/,src/benchmarks/,src/samples/,src/compat/subfolders.Akavache.slnx, csprojProjectReferencepaths,codecov.yml, andCLAUDE.mdupdated.BENCHMARK_REPORT.mdandPERFORMANCE_SUMMARY.mdlive underdocs/..editorconfigenforces the full Microsoft Code Analysis catalogue aserror: CA Design / Performance / Reliability / Usage / Security / Maintainability / Globalization / Interoperability / Naming, plus SYSLIB0xxx runtime obsoletions and SYSLIB1xxx source-generator diagnostics.error.<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>inDirectory.Build.propsruns them at compile time.error. Rules with a CA / SA equivalent are routed tononewith# handled by …comments so we never double-report..editorconfigis mapped 1:1 to the CA / SA / RCS / IDE rules where an equivalent exists, and disables the inspections that flag deliberate access-modifier or naming choices..editorconfigoverrides insrc/tests/:IDE0200(TUnitAssert.That(...).Throws<T>()requires the explicit lambda — the method-group overload binds to a differentAssert.Thatthat does not invoke the delegate),RCS1079(test fakes deliberately throwNotImplementedException),RCS1223(noDebuggerDisplayon test fixtures).Code modernisation sweep
With the analyzer catalogue promoted to
error, the whole codebase was pulled forward to modern C#:[..xs],[]) replace.ToArray()/.ToList()/Array.Empty<T>()where it compiles cleanly — except on the test stub classes mentioned above where the inferred type matters.staticlambdas on every anonymous function that captures nothing (IDE0320 enforced — ~250 call sites updated).fieldkeyword for lazy-init property backing storage.SqliteBlobCache.HttpServiceis the canonical example:get => field ??= new HttpService();with no manual backing field.await usingdeclarations replaceawait using (var x = ...) { ... }blocks where the scope allows.Equals/GetHashCodeonCacheEntryandLoginInfo—ReferenceEqualsshort-circuits,HashCode.AddBytes(ReadOnlySpan<byte>), and a privateValueEqualshelper for byte-array content equality.is not, property patterns,is { Version: var v }) replaces nested null checks andas+ null checks.+=,??=), inferred tuple member names, and target-typednew().SecureBlobCacheWrapper.Dispose/DisposeAsyncno longer guard againstinner is not IDisposable/IAsyncDisposable. Those checks were statically unreachable becauseIBlobCache : IDisposable, IAsyncDisposable— the compiler already enforces both interfaces on anyinnerthe wrapper sees.UniversalSerializer.TryBasicJsonDeserialization<T>restored to its original string/int/bool-only surface. An earlier refactor silently addeddouble/longcases that were never covered by tests and brokeTryBasicJsonDeserializationShouldReturnDefaultForDoubleType, which asserted that doubles returndefault(double).PreserveAttributeand its tests are removed;SqliteBlobCacheno longer references it.ArgumentExceptionHelper,DateTimeHelpers,AkavacheBuilder.ApplyForcedDateTimeKind, andInMemoryBlobCacheBase.ForcedDateTimeKindsetter use the early-return form (RCS1208).Assembly.Locationreplaced withAppContext.BaseDirectoryplusAssemblyFileVersionAttribute— trim/AOT-friendly and works on single-file publish.Test plan
Akavache.Testspasses (3940 tests, 0 failures, 0 skipped) on a fresh checkoutAkavache.Settings.Testspasses on a fresh checkoutdotnet test -- --coverage --coverage-output-format cobertura) reports 100% line / 100% branch (4438/4438 / 1714/1714) across production assemblies.WithV10ToV11Migration()and confirm legacy keys resolveExecutingAssemblybeing touched