Skip to content

Latest commit

 

History

History
208 lines (168 loc) · 9.76 KB

File metadata and controls

208 lines (168 loc) · 9.76 KB

V3 Migration Guide

This document describes the breaking changes you need to be aware of when upgrading to v3 of the Supabase Swift SDK, together with the steps required to migrate your code. All modules (Auth, Storage, Realtime, PostgREST, Functions, Supabase) are covered here.

Note

v3 has not been released yet. This document is updated as breaking changes land on main, so treat it as the running list rather than the final one.

verifyOTP now returns VerifyOTPResponse instead of AuthResponse

verifyOTP and its overloads return a new VerifyOTPResponse type instead of AuthResponse.

GoTrue's /verify endpoint returns a body with only { msg, code } for the first of the two confirmations required by a secure email change — a shape AuthResponse can't represent, since it only ever holds a Session or a User. That made verifyOTP(type: .emailChange) throw a DecodingError instead of completing successfully. AuthResponse is also the return type of signUp and the Passkey methods, neither of which can ever produce that shape, so growing it to fit verifyOTP would have meant every caller of those unrelated methods handling a case only one specific call can trigger.

VerifyOTPResponse only has the two shapes verifyOTP can actually return:

public enum VerifyOTPResponse {
  case session(Session)
  case emailChangeConfirmationPending(EmailChangeConfirmation)
}

EmailChangeConfirmation carries the message/code GoTrue sends for that first confirmation. There's no bare-User case here — succeeding at /verify always means either a session was issued or the other email still needs to confirm; unlike signUp, it can never leave you with a user and no session.

This is a compile error, not a silent behavior change: the return type itself changed, so every call site using the result needs updating.

// Before
let response: AuthResponse = try await client.verifyOTP(tokenHash: hash, type: .emailChange)
let email = response.user.email

// After
let response: VerifyOTPResponse = try await client.verifyOTP(tokenHash: hash, type: .emailChange)
switch response {
case .session(let session):
  let email = session.user.email
case .emailChangeConfirmationPending(let confirmation):
  print("\(confirmation.message) (code: \(confirmation.code))")
}

AuthResponse itself is unchanged: signUp and the Passkey methods still return it, and user is still non-optional there, since neither endpoint can produce the confirmation-pending shape.

All previously-deprecated APIs have been removed

Every API that carried an @available(*, deprecated, ...) annotation ahead of v3 has now been removed outright. If your project still built without deprecation warnings, none of this affects you. If it built with warnings, each warning's replacement (already given in the deprecation message) is now mandatory. This is a compile error everywhere: the old symbols no longer exist.

Auth

Before After
GoTrueClient AuthClient
GoTrueMFA AuthMFA
GoTrueLocalStorage AuthLocalStorage
GoTrueMetaSecurity AuthMetaSecurity
GoTrueError AuthError
JSONEncoder.goTrue AuthClient.Configuration.jsonEncoder
JSONDecoder.goTrue AuthClient.Configuration.jsonDecoder
MFAEnrollParams MFATotpEnrollParams or MFAPhoneEnrollParams
AuthAdmin.deleteUser(id: String, shouldSoftDelete:) AuthAdmin.deleteUser(id: UUID, shouldSoftDelete:)
AuthError.sessionNotFound AuthError.sessionMissing
AuthError.pkce(_:) / AuthError.PKCEFailureReason AuthError.pkceGrantCodeExchange(message:error:code:)
AuthError.invalidImplicitGrantFlowURL AuthError.implicitGrantRedirect(message:)
AuthError.api(_ error: APIError) / AuthError.APIError AuthError.api(message:errorCode:underlyingData:underlyingResponse:)
UserAttributes.emailChangeToken (removed, no replacement — was unused by GoTrue)

Also removed, with no replacement, because they no longer represent something GoTrue can throw: AuthError.missingExpClaim, AuthError.malformedJWT, AuthError.missingURL, AuthError.invalidRedirectScheme.

UserCredentials was deprecated ("access will be removed on the next major release") and is now internal — it was only ever used by AuthClient itself to encode the request body for signIn(email:password:)/signIn(phone:password:)/session refresh, never something callers were meant to construct directly. Those signIn methods are unaffected; only direct use of the UserCredentials type itself no longer compiles.

Customizing Auth's JSON encoding/decoding is no longer supported at all: the AuthClient.Configuration.init/AuthClient.init overloads taking encoder:/decoder: parameters, AuthClient.Configuration.encoder/.decoder, and SupabaseClientOptions.AuthOptions.encoder/.decoder have all been removed. Auth always uses its internal JSON encoder/decoder now.

// Before
let client = AuthClient(
  url: url, localStorage: storage, encoder: myEncoder, decoder: myDecoder,
  fetch: { try await URLSession.shared.data(for: $0) }
)

// After
let client = AuthClient(url: url, localStorage: storage)

PostgREST

Before After
URLQueryRepresentable PostgrestFilterValue
PostgrestFilterValue.queryValue PostgrestFilterValue.rawValue
.like(_:value:) .like(_:pattern:)
.ilike(_:value:) .ilike(_:pattern:)
.in(_:value:) .in(_:values:)
.plfts(_:query:config:) .textSearch(_:query:config:type: .plain)
.phfts(_:query:config:) .textSearch(_:query:config:type: .phrase)
.wfts(_:query:config:) .textSearch(_:query:config:type: .websearch)
.explain(...format: String) .explain(...format: ExplainFormat), e.g. format: .json

The PostgrestClient.Configuration.init/PostgrestClient.init overloads taking encoder:/ decoder: were also removed, for the same reason as Auth above — customizing PostgREST's JSON codec is no longer supported.

// Before
try await client.from("users").select().ilike("email", value: "john%").execute()

// After
try await client.from("users").select().ilike("email", pattern: "john%").execute()

Storage

Before After
BucketOptions.public / init(public:...) BucketOptions.isPublic / init(isPublic:...)
createSignedURL/createSignedURLs/getPublicURL(..., download: Bool) download: DownloadBehavior? (.withOriginalName, .named("file.pdf"))
createSignedURLs(...) -> [URL] createSignedURLs(...) -> [SignedURLResult]
upload/update/uploadToSignedURL(...) -> String the overloads returning FileUploadResponse / SignedURLUploadResponse
SortBy.init(column:order: String?) SortBy.init(column:order: SortOrder?)
TransformOptions.init(...resize: String?..., format: String?) TransformOptions.init(...resize: ResizeMode?..., format: ImageFormat?)
JSONEncoder.defaultStorageEncoder / JSONDecoder.defaultStorageDecoder (removed, no public replacement — was only ever the client's internal default)
StorageClientConfiguration.init(...encoder:decoder:session:...) StorageClientConfiguration.init(...logger:...)
Storage.File / Storage.FormData MultipartFormData
// Before
let bucket = try await storage.createBucket("avatars", options: .init(public: true))
let url = try await storage.from("avatars").createSignedURL(path: "a.png", expiresIn: 60, download: true)

// After
let bucket = try await storage.createBucket("avatars", options: .init(isPublic: true))
let url = try await storage.from("avatars").createSignedURL(
  path: "a.png", expiresIn: 60, download: .withOriginalName
)

Supabase

SupabaseClient.database and SupabaseClient.realtime have been removed.

// Before
try await supabase.database.from("users").select().execute()
supabase.realtime.connect()

// After
try await supabase.from("users").select().execute()
supabase.realtimeV2.connect()

Realtime

The entire legacy v1 API has been removed: RealtimeClient, RealtimeChannel, Presence, and their supporting types (PhoenixTransport, Push, Delegated, HeartbeatTimer, TimeoutTimer, and the Message typealias). Use RealtimeClientV2, RealtimeChannelV2, and PresenceV2 — see the RealtimeV2 migration guide for the full v1-to-v2 walkthrough.

RealtimeClientV2 and RealtimeChannelV2 also had their own deprecated compatibility members removed:

Before After
RealtimeClientV2.subscriptions RealtimeClientV2.channels
RealtimeClientV2.Configuration RealtimeClientOptions
RealtimeClientV2.Status RealtimeClientStatus
RealtimeClientV2.init(config:) RealtimeClientV2.init(url:options:)
RealtimeClientV2.addChannel(_:) (removed — the client tracks channels automatically)
RealtimeChannelV2.Subscription RealtimeSubscription
RealtimeChannelV2.Status RealtimeChannelStatus
RealtimeChannelV2.subscribe() RealtimeChannelV2.subscribeWithError()
RealtimeChannelV2.updateAuth(jwt:) RealtimeClientV2.setAuth(_:)
postgresChange(_:schema:table:filter: String?:select:) postgresChange(_:schema:table:filter: RealtimePostgresFilter?:select:)
broadcast(event:) -> AsyncStream<JSONObject> broadcastStream(event:)
RealtimeMessageV2.eventType inspect the raw event value in RealtimeMessageV2.event instead
RealtimeMessageV2.EventType.tokenExpired now returned as .system; check the payload instead

Helpers

ObservationToken.remove() has been removed — use .cancel() instead. PostgrestError.detail and PostgrestError.init(detail:hint:code:message:) have been removed — use .details and init(details:hint:code:message:).