Compendium.Adapters.S3 1.0.0-preview.0

This is a prerelease version of Compendium.Adapters.S3.
dotnet add package Compendium.Adapters.S3 --version 1.0.0-preview.0
                    
NuGet\Install-Package Compendium.Adapters.S3 -Version 1.0.0-preview.0
                    
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="Compendium.Adapters.S3" Version="1.0.0-preview.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Compendium.Adapters.S3" Version="1.0.0-preview.0" />
                    
Directory.Packages.props
<PackageReference Include="Compendium.Adapters.S3" />
                    
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 Compendium.Adapters.S3 --version 1.0.0-preview.0
                    
#r "nuget: Compendium.Adapters.S3, 1.0.0-preview.0"
                    
#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 Compendium.Adapters.S3@1.0.0-preview.0
                    
#: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=Compendium.Adapters.S3&version=1.0.0-preview.0&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Compendium.Adapters.S3&version=1.0.0-preview.0&prerelease
                    
Install as a Cake Tool

Compendium.Adapters.S3

S3-compatible object-store adapter for the Compendium framework. One adapter, five providers:

  • AWS S3 (default)
  • Cloudflare R2 (custom endpoint)
  • MinIO (custom endpoint + path-style)
  • Backblaze B2 (S3-compatible API)
  • Wasabi

Implements the IObjectStore port with tenant-scoped keys, streaming uploads, multipart, server-side encryption, and presigned URLs. First-ever consumer of the storage port shipped from Compendium.Abstractions.Storage (currently embedded in this package until the abstraction is published — see src/Compendium.Adapters.S3/Abstractions/IObjectStore.cs).


Quick start

dotnet add package Compendium.Adapters.S3
using Compendium.Adapters.S3.DependencyInjection;

var services = new ServiceCollection();
services.AddCompendiumMultitenancy(); // your tenant resolution strategy
services.AddCompendiumS3(o =>
{
    o.Bucket = "invoices";
    o.Region = "us-east-1";
    // Optional — omit to use the SDK's default credential chain.
    o.AccessKey = "...";
    o.SecretKey = "...";
});

Inject IObjectStore anywhere:

public sealed class InvoiceService(IObjectStore store)
{
    public async Task UploadAsync(string key, Stream content, CancellationToken ct)
    {
        var result = await store.PutAsync(key, content, "application/pdf", cancellationToken: ct);
        if (result.IsFailure)
        {
            // Result.Error.Type is NotFound / Conflict / Forbidden / ... etc.
        }
    }
}

Every key is automatically prefixed with the current tenant id (resolved from ITenantContext). Cross-tenant access is impossible by construction.


Options

Property Type Default Notes
Bucket string required Default bucket.
Region string? null AWS region, e.g. us-east-1. Required when no ServiceUrl.
ServiceUrl string? null Custom endpoint for R2/MinIO/B2/Wasabi.
ForcePathStyle bool false Required for MinIO.
AccessKey string? null Omit to use the SDK credential chain (IAM role, env, profile).
SecretKey string? null See above.
MultipartThresholdBytes long 8 MiB Uploads above this go multipart (5 MiB part size).
DefaultPresignedUrlExpiry TimeSpan 15m Default lifetime for presigned URLs.
ServerSideEncryption enum None None / Aes256 / AwsKms.
KmsKeyId string? null Required when ServerSideEncryption = AwsKms.

Bind from IConfiguration under the canonical section:

{
  "Compendium": {
    "Adapters": {
      "S3": {
        "Bucket": "invoices",
        "Region": "us-east-1",
        "ServerSideEncryption": "Aes256"
      }
    }
  }
}
services.AddCompendiumS3(configuration);

Provider configurations

AWS S3 (default)

services.AddCompendiumS3(o =>
{
    o.Bucket = "prod-invoices";
    o.Region = "us-east-1";
    // No credentials → uses IAM role / EC2 instance profile / EKS pod identity.
});

Cloudflare R2

services.AddCompendiumS3(o =>
{
    o.Bucket = "invoices";
    o.ServiceUrl = "https://<account-id>.r2.cloudflarestorage.com";
    o.AccessKey = "<r2-access-key>";
    o.SecretKey = "<r2-secret-key>";
    // R2 doesn't use AWS regions, but the SDK requires *some* region for signing.
    o.Region = "auto";
});

MinIO (self-hosted / local)

services.AddCompendiumS3(o =>
{
    o.Bucket = "invoices";
    o.ServiceUrl = "http://minio.internal:9000";
    o.ForcePathStyle = true; // MANDATORY for MinIO
    o.AccessKey = "minio";
    o.SecretKey = "<password>";
    o.Region = "us-east-1";
});

Backblaze B2 (S3-compatible)

services.AddCompendiumS3(o =>
{
    o.Bucket = "invoices";
    o.ServiceUrl = "https://s3.us-west-002.backblazeb2.com";
    o.AccessKey = "<b2-keyId>";
    o.SecretKey = "<b2-applicationKey>";
    o.Region = "us-west-002";
});

Wasabi

services.AddCompendiumS3(o =>
{
    o.Bucket = "invoices";
    o.ServiceUrl = "https://s3.us-east-1.wasabisys.com";
    o.AccessKey = "<wasabi-access-key>";
    o.SecretKey = "<wasabi-secret-key>";
    o.Region = "us-east-1";
});

Tenant isolation

Every operation on IObjectStore prefixes the supplied key with {tenantId}/:

  • store.PutAsync("invoice.pdf", ...)tenant-a/invoice.pdf
  • store.ListAsync("invoices/") → lists tenant-a/invoices/*, never tenant-b/...

The tenant id is validated against the same regex used by the PostgreSQL adapter — ^[a-zA-Z0-9_-]+$, max 255 chars. Malformed tenant ids and path-traversal segments (..) are rejected with Error.Forbidden.


Sample

See samples/01-tenant-invoice-roundtrip — uploads an invoice and generates a 1-hour presigned download URL.

# Start MinIO :
docker run -p 9000:9000 -e MINIO_ROOT_USER=minio -e MINIO_ROOT_PASSWORD=minio12345 \
  minio/minio:latest server /data

# Create the bucket once via the MinIO console or `mc mb`.

dotnet run --project samples/01-tenant-invoice-roundtrip

Production checklist

  • TLS everywhere. Never set ServiceUrl to http:// outside dev.
  • IAM roles, not access keys. Omit AccessKey/SecretKey in production and rely on the SDK credential chain — env vars on Kubernetes (via IRSA / pod identity), instance profiles on EC2, OIDC on GitHub Actions.
  • Bucket policy. Restrict the principal to the IAM role used by the service ; deny anything else. Block public access at the account level.
  • Server-side encryption. Set ServerSideEncryption = Aes256 minimum. Use AwsKms with a customer-managed key for regulated workloads.
  • Lifecycle policies. Configure via the AWS console / Terraform / awscli — out of scope for the adapter.
  • Versioning + object lock. Enable on the bucket if compliance requires immutability. Out of scope for the adapter.
  • Multi-region replication. Configure at bucket level. The adapter only talks to one endpoint at a time.
  • Presigned URL TTL. Default 15 minutes ; never grant > 1 hour for sensitive data. AWS hard-limits SigV4 URLs to 7 days.

Coverage

CI gate : ≥ 90 % line coverage on the unit-testable surface. Integration tests are excluded from the gate (Docker may be unavailable in CI environments without a daemon).


License

MIT.

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  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 was computed.  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
1.0.0-preview.0 31 5/17/2026