Skip to content

S3Client: accept custom endpoint config (unblocks R2 / MinIO / DigitalOcean Spaces / Backblaze B2 in stacksjs/stacks#1896) #116

Description

@glennmichael123

Background

`S3Client` constructor today: `(region?: string, profile?: string)`. The endpoint is implicitly `s3..amazonaws.com`. Every S3-API-compatible store needs a custom endpoint:

Provider Endpoint format
Cloudflare R2 `https://.r2.cloudflarestorage.com`
MinIO self-hosted, arbitrary URL
DigitalOcean Spaces `https://.digitaloceanspaces.com`
Backblaze B2 `https://s3..backblazeb2.com`
Wasabi `https://s3..wasabisys.com`

Without a way to override the endpoint, downstream packages can't ship adapters for any of these — they're all S3-API-compatible but the request URL needs to point elsewhere.

This blocks stacksjs/stacks#1896 (R2 driver). The framework's per-tenant scoping wrapper (`ScopedStorageAdapter`) already works with any S3-shaped adapter — only the endpoint config is missing.

Proposed API

new S3Client({ region: 'auto', endpoint: 'https://<account-id>.r2.cloudflarestorage.com', credentials })

// Or as a third positional arg if the constructor must stay positional-compatible:
new S3Client(region, profile, { endpoint })

The signing logic stays unchanged — SigV4 against the configured endpoint host. Apps using the default AWS endpoint see no behavior change.

Acceptance

  • S3Client accepts a custom endpoint field (constructor or options)
  • When provided, the endpoint host wins over the default AWS-derived URL for all requests (getObject, putObject, multipart, presigned URLs)
  • SigV4 signing uses the host from the configured endpoint, not the derived AWS host
  • Path-style addressing supported for MinIO-shaped endpoints (where buckets aren't in the subdomain)
  • Tests covering an R2-shaped endpoint round-trip + a path-style MinIO-shaped endpoint round-trip

Once this lands, the Stacks-side R2 driver is ~30 lines extending S3StorageAdapter with the right config defaults.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions