Themia.Storage.Cloudflare 0.31.3

dotnet add package Themia.Storage.Cloudflare --version 0.31.3
                    
NuGet\Install-Package Themia.Storage.Cloudflare -Version 0.31.3
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Themia.Storage.Cloudflare" Version="0.31.3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Themia.Storage.Cloudflare" Version="0.31.3" />
                    
Directory.Packages.props
<PackageReference Include="Themia.Storage.Cloudflare" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Themia.Storage.Cloudflare --version 0.31.3
                    
#r "nuget: Themia.Storage.Cloudflare, 0.31.3"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Themia.Storage.Cloudflare@0.31.3
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Themia.Storage.Cloudflare&version=0.31.3
                    
Install as a Cake Addin
#tool nuget:?package=Themia.Storage.Cloudflare&version=0.31.3
                    
Install as a Cake Tool

Themia.Storage.Cloudflare

Purges a public object's URL from Cloudflare's edge cache after the object is deleted from storage (coord #0153). IStorageProvider stays the seam your code injects; no call site changes.

Packages: the abstraction (ICdnPurger, CdnPurgeException, PurgingStorageProvider) is in Themia.Storage; this package is the Cloudflare implementation.

Wiring

services.AddThemiaStorageCloudflarePurge(o =>
{
    o.Enabled = true;                  // false (the default) registers a no-op purger
    o.ZoneId = "<zone id>";
    o.ApiToken = "<token scoped Zone > Cache Purge on that zone>";   // NOT the R2 key
});

// Split storage: wrap the PUBLIC slot only. Register the inner provider as its own factory-built
// singleton so the container disposes it; Themia never disposes what it wraps.
services.AddSingleton(_ => new S3StorageProvider(publicOptions));
services.AddThemiaSplitStorage(
    sp => new PurgingStorageProvider(sp.GetRequiredService<S3StorageProvider>(), sp.GetRequiredService<ICdnPurger>()),
    sp => new LocalStorageProvider(privateOptions));

// Single provider: wrap it whole.
services.AddSingleton(_ => new S3StorageProvider(options));
services.AddSingleton<IStorageProvider>(sp =>
    new PurgingStorageProvider(sp.GetRequiredService<S3StorageProvider>(), sp.GetRequiredService<ICdnPurger>()));

Wrap once. Wrap the public slot or the sole provider, never both: wrapping both purges every delete twice, and a refused second call reports a false "cache stale".

Two S3 slots. The split example registers S3StorageProvider by its concrete type. If both slots are S3 (for example R2 for public and another bucket for private), register them as keyed services (AddKeyedSingleton) instead: by concrete type the last registration wins and both slots resolve to the same bucket. A slot built inside the AddThemiaSplitStorage lambda is never disposed by Themia, which is why the example registers the inner provider as its own factory-built singleton.

Bind CloudflarePurgeOptions from configuration in your app. With Enabled=true, a blank ZoneId, or an ApiToken that is blank or contains whitespace or control characters (trim a trailing newline read from a secret file), fails host start.

What a delete does

For a public/ key: build inner.GetPublicUrl(key), delete the object, then purge that URL. Any other key is only deleted. A purge Cloudflare refuses, answers with success:false, or answers with something that is not its purge response throws CdnPurgeException, which means the object is deleted and the edge may still serve it. Repeat the delete and purge; delete is idempotent. Any other failure of the purge step after the delete (for example a resilience handler's BrokenCircuitException) is also reported as CdnPurgeException, with the original as InnerException. Caller cancellation throws OperationCanceledException. There is no retry inside; the caller owns it (Cloudflare rate-limits purge per account, error code 1134).

What it does not do

  • Themia.Modules.Storage swallows the failure. TenantStorage.DeleteAsync commits the soft-delete and then only logs a provider-delete exception, so under that module a refused purge is logged and lost. The "failure surfaces" guarantee holds for callers of IStorageProvider.DeleteAsync directly.
  • App-wide HttpClient defaults apply. The Cloudflare client is a named client, so handlers added with ConfigureHttpClientDefaults (resilience, retry) also apply to it. A retry handler means more than one POST per purge, and any failure it raises is reported as CdnPurgeException by the decorator.
  • Only the exact URL is purged: query-string variants, /cdn-cgi/image paths, a Transform-Rule-rewritten URL, and objects under a custom cache key built from headers or cookies are not.
  • A 200 with success:true means Cloudflare accepted the request, not that removal was confirmed.
  • A request in flight across the purge can refill the edge for the full TTL.
  • Only a delete purges. Overwriting an object at an existing public/ key leaves the old content at the edge for the full TTL, so use a new key per upload (propertiezy and ezy-assets do) or delete first.
  • The URL's host must be served by the zone. What Cloudflare answers for a host outside the zone is not documented in what Themia reviewed, so a mismatch is expected to surface as a CdnPurgeException on the first delete, carrying Cloudflare's code and message, but that is not verified.
  • Not run against the live Cloudflare API in Themia's tests; request and response shapes follow Cloudflare's documentation.

Design and review record: docs/superpowers/specs/2026-10-03-storage-cdn-purge-design.md.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.31.3 0 10/14/2026
0.31.2 0 10/13/2026
0.31.1 0 10/13/2026
0.31.0 20 10/13/2026
0.30.5 33 10/13/2026
0.30.4 101 10/4/2026
0.30.3 86 10/4/2026