Skip to content

refactor(storage)!: make Storage client types stateless structs - #1246

Merged
grdsdev merged 10 commits into
mainfrom
claude/sources-storage-stateless-5b0058
Aug 20, 2026
Merged

grdsdev merged 10 commits into
mainfrom
claude/sources-storage-stateless-5b0058

Conversation

@grdsdev

@grdsdev grdsdev commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Converts Sources/Storage's public client types (StorageApi, StorageBucketApi, StorageFileApi, SupabaseStorageClient) from LockIsolated-backed classes to immutable structs, mirroring the FunctionsClient conversion in #1233. Part of the "supabase-swift v3" migration (tracked in SDK-1533).

  • StorageApi becomes a plain immutable struct (drops LockIsolated/@unchecked Sendable), and is now internal — nothing public ever accepted or returned one, so it was dead public surface (SDK-1534).
  • StorageBucketApi is removed; its methods move onto SupabaseStorageClient directly (SupabaseStorageClient+Buckets.swift).
  • SupabaseStorageClient and StorageFileApi become structs composing StorageApi, matching the shape already used by the Vectors trio (StorageVectorsClient/VectorBucketClient/VectorIndexClient, untouched).
  • setHeader(_:forKey:) is now non-mutating (returns a new value) and drops @discardableResult.
  • SupabaseClient.storage stops being memoized, mirroring the existing functions property.
  • Along the way, this fixes a real bug: SupabaseStorageClient.from(_:) used to silently drop headers set via setHeader on the top-level client (it only passed the stored configuration, not the live header state) — the composition-based rewrite fixes this asymmetry.
  • V3_MIGRATION.md documents all of the above as breaking changes.
  • Filed as follow-ups rather than scope-creeped into this PR: SDK-1536 (whether setHeader's name should change now that it's non-mutating).

Test plan

  • swift test — 1182/1182 passing, 9 known pre-existing issues, no new failures
  • ./scripts/test-docs.sh — clean, no DocC warnings
  • ./scripts/spell-check.sh — clean on all shipped files
  • ./scripts/format.sh — no changes
  • Examples Xcode workspace build (Supabase.xcworkspace, Examples scheme) — builds successfully
  • Full whole-branch code review completed, all Critical/Important findings addressed

@grdsdev
grdsdev requested a review from a team as a code owner August 20, 2026 13:44
@coderabbitai

coderabbitai Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: abfa2b46-7d18-48b6-a590-45a8ba358f7b

📥 Commits

Reviewing files that changed from the base of the PR and between 19bac7c and 1795f4f.

📒 Files selected for processing (2)
  • Sources/Supabase/SupabaseClient.swift
  • V3_MIGRATION.md

Included review availability: Your plan provides up to 4 included reviews per hour; 1 remains after this review.


📝 Walkthrough

Summary by CodeRabbit

  • Breaking Changes

    • Storage clients are now immutable value types.
    • setHeader(_:forKey:) returns a new client instead of modifying the existing one.
    • StorageBucketApi has been removed; bucket operations are now available directly on SupabaseStorageClient.
    • SupabaseClient.storage provides a fresh client value whenever accessed.
  • Documentation

    • Updated migration guidance and bucket API references to reflect the new storage interface.
  • Tests

    • Added coverage for configuration preservation and header propagation across storage requests.

Walkthrough

Storage APIs now use immutable Sendable structs instead of subclassable classes. StorageApi is an internal delegation layer. SupabaseStorageClient owns the shared API and exposes bucket methods directly. StorageFileApi uses the shared API for requests and decoding. Header changes return new configured values. SupabaseClient.storage creates a new value on each access. Tests and migration documentation cover the updated API.

Sequence Diagram(s)

sequenceDiagram
  participant SupabaseClient
  participant SupabaseStorageClient
  participant StorageFileApi
  participant StorageApi

  SupabaseClient->>SupabaseStorageClient: storage
  SupabaseStorageClient->>StorageFileApi: from(bucket)
  StorageFileApi->>StorageApi: execute(file request)
  StorageApi-->>StorageFileApi: response
  StorageFileApi-->>SupabaseStorageClient: decoded result
Loading

Possibly related PRs

Suggested labels: Storage

Merge Risk: ⚪ Minimal · up to 1795f

This change refactors storage client state handling and documents the breaking API updates; no actionable merge-blocking risk remains beyond normal checks and review.

✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

StorageApi, StorageBucketApi, StorageFileApi, and SupabaseStorageClient
convert from a LockIsolated-backed class hierarchy to immutable structs
composing a StorageApi value, mirroring the prior FunctionsClient
conversion (#1233). setHeader on StorageApi and StorageFileApi is now
non-mutating and returns a new instance instead of mutating in place.

Bucket-management operations move from the deleted StorageBucketApi
class into a SupabaseStorageClient extension in
SupabaseStorageClient+Buckets.swift.
…lient/StorageFileApi

The stateless-struct refactor made SupabaseStorageClient.configuration and
StorageFileApi.configuration unreachable without @testable import, an extra
undocumented breaking change beyond the refactor's scope since configuration
was public on the original class hierarchy. Add forwarding computed
properties on both types and drop the now-unnecessary .api. test workarounds.
The composition-based refactor dropped setHeader(_:forKey:) from
SupabaseStorageClient, which previously inherited it transitively
through StorageBucketApi -> StorageApi. Restore it as a public,
non-mutating method forwarding to the held StorageApi, matching the
same pattern already used for the restored configuration property.
Add a concrete "Why" to the V3_MIGRATION.md struct-conversion section:
the old SupabaseStorageClient.from(_:) built each StorageFileApi from
the immutable configuration alone, never the live header state
setHeader(_:forKey:) mutated, so a header set via setHeader silently
never reached file operations obtained via from(_:) (while it did
reach storage.vectors, which passed self through instead). The
composition-based rewrite passes the whole api value through both
paths, fixing the asymmetry.

Also soften the "StorageBucketApi was never constructed directly"
claim to scope it to this codebase (it was a public class with an
inherited public initializer, so external code could have constructed
one), and document that SupabaseClient.storage stopped memoizing its
result, so a header set via client.storage.setHeader(...) no longer
persists across later client.storage accesses.
DocC doesn't render doc comments on extension declarations, so the
"## Topics" block on the SupabaseStorageClient bucket-management
extension rendered nowhere. The same 6 symbol links already appear
under "Bucket management" in SupabaseStorageClient's own type-level
doc comment, so trim this one down to a plain descriptive comment.
SupabaseClient.storage stopped caching its result in mutableState, so
discarding client.storage immediately can no longer exercise the same
retain-cycle detection as the cached sub-clients (rest, functions,
realtimeV2): nothing keeps a self-capturing closure it might build
alive past the statement, cached or not. Move it out of the "cached
sub-clients" list and explain why it can't prove the same thing.
SupabaseStorageClient.configuration has direct test coverage; its
sibling StorageFileApi.configuration did not, despite being the exact
class of member that was accidentally dropped and restored once
already during the stateless-struct refactor.
StorageApi held no public API surface anyone outside Sources/Storage
could actually use: execute(_:) was already internal, and neither
SupabaseStorageClient nor StorageFileApi exposed a way to build one
from a standalone StorageApi value. Narrow it while v3 is still
unreleased, since this is the cheapest moment to do so.
@grdsdev
grdsdev enabled auto-merge (squash) August 20, 2026 14:40
@grdsdev
grdsdev force-pushed the claude/sources-storage-stateless-5b0058 branch from 19bac7c to 1795f4f Compare August 20, 2026 14:43
@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 32381856545

Coverage increased (+0.04%) to 87.138%

Details

  • Coverage increased (+0.04%) from the base build.
  • Patch coverage: 1 uncovered change across 1 file (86 of 87 lines covered, 98.85%).
  • No coverage regressions found.

Uncovered Changes

File Changed Covered %
Sources/Storage/SupabaseStorage.swift 12 11 91.67%
Total (5 files) 87 86 98.85%

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 10403
Covered Lines: 9065
Line Coverage: 87.14%
Coverage Strength: 1032308.28 hits per line

💛 - Coveralls

@grdsdev
grdsdev merged commit 1dac92d into main Aug 20, 2026
30 of 32 checks passed
@grdsdev
grdsdev deleted the claude/sources-storage-stateless-5b0058 branch August 20, 2026 14:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants