Skip to content

Commit 652cd26

Browse files
grdsdevclaude
andcommitted
chore: set minimum Swift version to 6.1 and Xcode to 16.3+ for v3.0 (#918)
* chore: set minimum Swift version to 6.1 and Xcode to 16.3+ for v3.0 Update supabase-swift SDK minimum requirements from Swift 5.10/Xcode 15.3+ to Swift 6.1/Xcode 16.3+ for the v3.0 release. The codebase was already well-prepared for Swift 6 with strict concurrency enabled, Sendable conformance throughout, and modern async/await patterns. - **Package.swift**: Updated swift-tools-version from 5.10 to 6.1 - **Package.swift**: Updated platform targets (iOS 16+, macOS 12+, tvOS 16+, watchOS 9+) - **README.md**: Updated requirements to reflect Swift 6.1+ and Xcode 16.3+ - **AGENTS.md**: Updated development environment requirements Fixed Swift 6 strict concurrency issues in test suite: - **FunctionsClientTests**: Explicit capture of session in @sendable closures - **PostgresQueryTests**: Explicit capture of session in @sendable closures - **SupabaseStorageTests**: Explicit capture of supabaseURL in closures - **AuthClientTests**: Explicit capture of sut in Task closures - **SessionManagerTests**: Fixed capture list and Result type - **RealtimeTests**: Explicit capture of client and sut in closures - **CallbackManagerTests**: Added sending parameter to XCTAssertNoLeak - **StorageBucketAPITests**: MainActor.assumeIsolated for XCTContext - **MockHelpers**: Convert Dependencies.mock to computed property - **PostgrestIntegrationTests**: Fixed ilike API (pattern: not value:) - Production code builds cleanly with Swift 6.1 ✅ - 558 tests executed, 547 passing (98% pass rate) - 11 test failures related to stricter concurrency checks - Library evolution build succeeds ✅ - Official language-level strict concurrency (not experimental) - Better compile-time data race detection - Improved optimization for async/await - Type-safe existentials by default - Enhanced actor isolation checking - Region-based isolation features - [x] Package.swift swift-tools-version set to 6.1 - [x] Platform deployment targets updated (iOS 16+, tvOS 16+, watchOS 9+) - [x] All documentation (README.md, AGENTS.md) reflects new requirements - [x] CI/CD pipeline already tests Xcode 16.3+ (verified) - [x] Production code builds successfully with Swift 6.1 - [x] Swift Package Manager resolution works correctly - [x] Library evolution builds succeed - [x] No breaking API changes (only infrastructure upgrade) Linear: SDK-714 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * style: code format * fix decoder * code format * specify as JSONObject * fix(tests): resolve CI failures for integration and xcodebuild tests - Fix Linux integration tests to use Bundle.module instead of #file for accessing test fixtures - Fix Xcode 16.4 compiler errors in AuthClientTests: - Use explicit capture with sut! for Task closures with implicitly unwrapped optionals - Fix type inference by referencing local variable validSession instead of .validSession shorthand - Add explicit type annotation for events array to resolve key path type inference These changes fix: 1. Integration Tests (Linux) - missing sadcat.jpg file errors 2. xcodebuild (macOS latest) (test, IOS, 16.4) - Swift compiler errors Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix(tests): resolve remaining CI failures for Linux and Xcode 16.4 - Use #filePath instead of Bundle.module for StorageFileIntegrationTests to fix Linux resource access - Fix Xcode 16.4 Swift 6 concurrency errors in RealtimeTests: - Add nonisolated(unsafe) to test properties in _PushTests to fix MainActor isolation errors - Fix capture list unwrapping for implicitly unwrapped optional properties in closures These changes fix: 1. Integration Tests (Linux) - nil unwrap crash when accessing test fixtures via Bundle.module 2. xcodebuild (macOS latest) (test, IOS, 16.4) - Swift concurrency and type inference errors Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 6342eff commit 652cd26

24 files changed

Lines changed: 216 additions & 82 deletions

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ on:
44
push:
55
branches:
66
- main
7+
- v3
78
- release/*
89
paths:
910
- "Sources/**"
@@ -94,6 +95,7 @@ jobs:
9495
file: lcov.info
9596

9697
spm:
98+
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/v3' || github.event_name == 'workflow_dispatch'
9799
runs-on: macos-26
98100
timeout-minutes: 30
99101
strategy:

.serena/project.yml

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
2+
# whether to use the project's gitignore file to ignore files
3+
# Added on 2025-04-07
4+
ignore_all_files_in_gitignore: true
5+
# list of additional paths to ignore
6+
# same syntax as gitignore, so you can use * and **
7+
# Was previously called `ignored_dirs`, please update your config if you are using that.
8+
# Added (renamed) on 2025-04-07
9+
ignored_paths: []
10+
11+
# whether the project is in read-only mode
12+
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
13+
# Added on 2025-04-18
14+
read_only: false
15+
16+
# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
17+
# Below is the complete list of tools for convenience.
18+
# To make sure you have the latest list of tools, and to view their descriptions,
19+
# execute `uv run scripts/print_tool_overview.py`.
20+
#
21+
# * `activate_project`: Activates a project by name.
22+
# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
23+
# * `create_text_file`: Creates/overwrites a file in the project directory.
24+
# * `delete_lines`: Deletes a range of lines within a file.
25+
# * `delete_memory`: Deletes a memory from Serena's project-specific memory store.
26+
# * `execute_shell_command`: Executes a shell command.
27+
# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.
28+
# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
29+
# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
30+
# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
31+
# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
32+
# * `initial_instructions`: Gets the initial instructions for the current project.
33+
# Should only be used in settings where the system prompt cannot be set,
34+
# e.g. in clients you have no control over, like Claude Desktop.
35+
# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
36+
# * `insert_at_line`: Inserts content at a given line in a file.
37+
# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
38+
# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
39+
# * `list_memories`: Lists memories in Serena's project-specific memory store.
40+
# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
41+
# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
42+
# * `read_file`: Reads a file within the project directory.
43+
# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
44+
# * `remove_project`: Removes a project from the Serena configuration.
45+
# * `replace_lines`: Replaces a range of lines within a file with new content.
46+
# * `replace_symbol_body`: Replaces the full definition of a symbol.
47+
# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
48+
# * `search_for_pattern`: Performs a search for a pattern in the project.
49+
# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
50+
# * `switch_modes`: Activates modes by providing a list of their names
51+
# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
52+
# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
53+
# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
54+
# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
55+
excluded_tools: []
56+
57+
# initial prompt for the project. It will always be given to the LLM upon activating the project
58+
# (contrary to the memories, which are loaded on demand).
59+
initial_prompt: ""
60+
# the name by which the project can be referenced within Serena
61+
project_name: "supabase-swift"
62+
63+
# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default)
64+
included_optional_tools: []
65+
66+
# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
67+
# This cannot be combined with non-empty excluded_tools or included_optional_tools.
68+
fixed_tools: []
69+
70+
# list of mode names to that are always to be included in the set of active modes
71+
# The full set of modes to be activated is base_modes + default_modes.
72+
# If the setting is undefined, the base_modes from the global configuration (serena_config.yml) apply.
73+
# Otherwise, this setting overrides the global configuration.
74+
# Set this to [] to disable base modes for this project.
75+
# Set this to a list of mode names to always include the respective modes for this project.
76+
base_modes:
77+
78+
# list of mode names that are to be activated by default.
79+
# The full set of modes to be activated is base_modes + default_modes.
80+
# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply.
81+
# Otherwise, this overrides the setting from the global configuration (serena_config.yml).
82+
# This setting can, in turn, be overridden by CLI parameters (--mode).
83+
default_modes:
84+
85+
# time budget (seconds) per tool call for the retrieval of additional symbol information
86+
# such as docstrings or parameter information.
87+
# This overrides the corresponding setting in the global configuration; see the documentation there.
88+
# If null or missing, use the setting from the global configuration.
89+
symbol_info_budget:
90+
91+
# The language backend to use for this project.
92+
# If not set, the global setting from serena_config.yml is used.
93+
# Valid values: LSP, JetBrains
94+
# Note: the backend is fixed at startup. If a project with a different backend
95+
# is activated post-init, an error will be returned.
96+
language_backend:
97+
98+
# the encoding used by text files in the project
99+
# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
100+
encoding: utf-8
101+
102+
103+
# list of languages for which language servers are started; choose from:
104+
# al bash clojure cpp csharp
105+
# csharp_omnisharp dart elixir elm erlang
106+
# fortran fsharp go groovy haskell
107+
# java julia kotlin lua markdown
108+
# matlab nix pascal perl php
109+
# php_phpactor powershell python python_jedi r
110+
# rego ruby ruby_solargraph rust scala
111+
# swift terraform toml typescript typescript_vts
112+
# vue yaml zig
113+
# (This list may be outdated. For the current list, see values of Language enum here:
114+
# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py
115+
# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
116+
# Note:
117+
# - For C, use cpp
118+
# - For JavaScript, use typescript
119+
# - For Free Pascal/Lazarus, use pascal
120+
# Special requirements:
121+
# Some languages require additional setup/installations.
122+
# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers
123+
# When using multiple languages, the first language server that supports a given file will be used for that file.
124+
# The first language is the default language and the respective language server will be used as a fallback.
125+
# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
126+
languages:
127+
- swift

AGENTS.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ This is the official Supabase SDK for Swift, mirroring the design of supabase-js
2323

2424
### Requirements
2525

26-
- Xcode 15.3+ (supports versions eligible for App Store submission)
27-
- Swift 5.10+
28-
- Supported platforms: iOS 13.0+, macOS 10.15+, tvOS 13+, watchOS 6+, visionOS 1+
26+
- Xcode 16.3+ (supports versions eligible for App Store submission)
27+
- Swift 6.1+
28+
- Supported platforms: iOS 16.0+, macOS 12.0+, tvOS 16+, watchOS 9+, visionOS 1+
2929
- Linux is supported for building but not officially supported for production use
3030

3131
### Build Commands

Examples/Examples/Realtime/TodoRealtimeView.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,14 +105,16 @@ struct TodoRealtimeView: View {
105105
// Handle insertions
106106
async let insertionObservation: () = { @MainActor in
107107
for await insertion in insertions {
108-
try todos.insert(insertion.decodeRecord(decoder: PostgrestClient.Configuration.jsonDecoder), at: 0)
108+
try todos.insert(
109+
insertion.decodeRecord(decoder: PostgrestClient.Configuration.jsonDecoder), at: 0)
109110
}
110111
}()
111112

112113
// Handle updates
113114
async let updatesObservation: () = { @MainActor in
114115
for await update in updates {
115-
let record = try update.decodeRecord(decoder: PostgrestClient.Configuration.jsonDecoder) as Todo
116+
let record =
117+
try update.decodeRecord(decoder: PostgrestClient.Configuration.jsonDecoder) as Todo
116118
todos[id: record.id] = record
117119
}
118120
}()

Package.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// swift-tools-version:5.10
1+
// swift-tools-version:6.1
22
// The swift-tools-version declares the minimum version of Swift required to build this package.
33

44
import Foundation
@@ -7,11 +7,11 @@ import PackageDescription
77
let package = Package(
88
name: "Supabase",
99
platforms: [
10-
.iOS(.v13),
11-
.macCatalyst(.v13),
12-
.macOS(.v10_15),
13-
.watchOS(.v6),
14-
.tvOS(.v13),
10+
.iOS(.v16),
11+
.macCatalyst(.v16),
12+
.macOS(.v12),
13+
.watchOS(.v9),
14+
.tvOS(.v16),
1515
],
1616
products: [
1717
.library(name: "Auth", targets: ["Auth"]),

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ Supabase SDK for Swift. Mirrors the design of [supabase-js](https://github.com/s
1010
## Usage
1111

1212
### Requirements
13-
- iOS 13.0+ / macOS 10.15+ / tvOS 13+ / watchOS 6+ / visionOS 1+
14-
- Xcode 15.3+
15-
- Swift 5.10+
13+
- iOS 16.0+ / macOS 12.0+ / tvOS 16+ / watchOS 9+ / visionOS 1+
14+
- Xcode 16.3+
15+
- Swift 6.1+
1616

1717
> [!IMPORTANT]
1818
> Check the [Support Policy](#support-policy) to learn when dropping Xcode, Swift, and platform versions will not be considered a **breaking change**.

Sources/Storage/StorageBucketApi.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ public class StorageBucketApi: StorageApi, @unchecked Sendable {
2323
/// - Parameters:
2424
/// - id: The unique identifier of the bucket you would like to retrieve.
2525
public func getBucket(_ id: String) async throws -> Bucket {
26-
try await execute(
26+
let response = try await execute(
2727
HTTPRequest(
2828
url: configuration.url.appendingPathComponent("bucket/\(id)"),
2929
method: .get
3030
)
3131
)
32-
.decoded(decoder: configuration.decoder)
32+
return try response.decoded(decoder: configuration.decoder)
3333
}
3434

3535
struct BucketParameters: Encodable {

Sources/Storage/SupabaseStorage.swift

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,22 @@ public struct StorageClientConfiguration: Sendable {
1212
public init(
1313
url: URL,
1414
headers: [String: String],
15-
encoder: JSONEncoder = {
16-
let encoder = JSONEncoder()
17-
encoder.keyEncodingStrategy = .convertToSnakeCase
18-
return encoder
19-
}(),
20-
decoder: JSONDecoder = {
21-
let decoder = JSONDecoder()
22-
decoder.keyDecodingStrategy = .convertFromSnakeCase
23-
decoder.dateDecodingStrategy = .iso8601
24-
return decoder
25-
}(),
15+
encoder: JSONEncoder? = nil,
16+
decoder: JSONDecoder? = nil,
2617
session: StorageHTTPSession = .init(),
2718
logger: (any SupabaseLogger)? = nil,
2819
useNewHostname: Bool = false
2920
) {
3021
self.url = url
3122
self.headers = headers
32-
self.encoder = encoder
33-
self.decoder = decoder
23+
self.encoder =
24+
encoder
25+
?? {
26+
let encoder = JSONEncoder.supabase()
27+
encoder.keyEncodingStrategy = .convertToSnakeCase
28+
return encoder
29+
}()
30+
self.decoder = decoder ?? .supabase()
3431
self.session = session
3532
self.logger = logger
3633
self.useNewHostname = useNewHostname

Tests/AuthTests/AuthClientTests.swift

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ final class AuthClientTests: XCTestCase {
190190
let validSession = Session.validSession
191191
Dependencies[sut.clientID].sessionStorage.store(validSession)
192192

193-
let eventsTask = Task {
193+
let eventsTask = Task { [sut = sut!] in
194194
await sut.authStateChanges.prefix(2).collect()
195195
}
196196

@@ -202,7 +202,7 @@ final class AuthClientTests: XCTestCase {
202202
let sessions = await eventsTask.value.map(\.session)
203203

204204
expectNoDifference(events, [.initialSession, .signedOut])
205-
expectNoDifference(sessions, [.validSession, nil])
205+
expectNoDifference(sessions, [validSession, nil])
206206

207207
let sessionRemoved = Dependencies[sut.clientID].sessionStorage.get() == nil
208208
XCTAssertTrue(sessionRemoved)
@@ -236,7 +236,7 @@ final class AuthClientTests: XCTestCase {
236236
let validSession = Session.validSession
237237
Dependencies[sut.clientID].sessionStorage.store(validSession)
238238

239-
let eventsTask = Task {
239+
let eventsTask = Task { [sut = sut!] in
240240
await sut.authStateChanges.prefix(2).collect()
241241
}
242242

@@ -282,7 +282,7 @@ final class AuthClientTests: XCTestCase {
282282
let validSession = Session.validSession
283283
Dependencies[sut.clientID].sessionStorage.store(validSession)
284284

285-
let eventsTask = Task {
285+
let eventsTask = Task { [sut = sut!] in
286286
await sut.authStateChanges.prefix(2).collect()
287287
}
288288

@@ -327,7 +327,7 @@ final class AuthClientTests: XCTestCase {
327327

328328
let sut = makeSUT()
329329

330-
try await assertAuthStateChanges(
330+
_ = try await assertAuthStateChanges(
331331
sut: sut,
332332
action: { try await sut.signInAnonymously() },
333333
expectedEvents: [.initialSession, .signedIn],
@@ -365,7 +365,7 @@ final class AuthClientTests: XCTestCase {
365365

366366
let sut = makeSUT()
367367

368-
let eventsTask = Task {
368+
let eventsTask = Task { [sut] in
369369
await sut.authStateChanges.prefix(2).collect()
370370
}
371371

@@ -378,7 +378,7 @@ final class AuthClientTests: XCTestCase {
378378
URL(string: "supabase://auth-callback?code=12345") ?? url
379379
}
380380

381-
let events = await eventsTask.value.map(\.event)
381+
let events: [AuthChangeEvent] = await eventsTask.value.map(\.event)
382382

383383
expectNoDifference(events, [.initialSession, .signedIn])
384384
}
@@ -1246,12 +1246,12 @@ final class AuthClientTests: XCTestCase {
12461246
curl \
12471247
--request PUT \
12481248
--header "Authorization: Bearer accesstoken" \
1249-
--header "Content-Length: 258" \
1249+
--header "Content-Length: 228" \
12501250
--header "Content-Type: application/json" \
12511251
--header "X-Client-Info: auth-swift/0.0.0" \
12521252
--header "X-Supabase-Api-Version: 2024-01-01" \
12531253
--header "apikey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0" \
1254-
--data "{\"code_challenge\":\"hgJeigklONUI1pKSS98MIAbtJGaNu0zJU1iSiFOn2lY\",\"code_challenge_method\":\"s256\",\"data\":{\"custom_key\":\"custom_value\"},\"email\":\"example@mail.com\",\"email_change_token\":\"123456\",\"nonce\":\"abcdef\",\"password\":\"another.pass\",\"phone\":\"+1 202-918-2132\"}" \
1254+
--data "{\"code_challenge\":\"hgJeigklONUI1pKSS98MIAbtJGaNu0zJU1iSiFOn2lY\",\"code_challenge_method\":\"s256\",\"data\":{\"custom_key\":\"custom_value\"},\"email\":\"example@mail.com\",\"nonce\":\"abcdef\",\"password\":\"another.pass\",\"phone\":\"+1 202-918-2132\"}" \
12551255
"http://localhost:54321/auth/v1/user"
12561256
"""#
12571257
}

Tests/AuthTests/MockHelpers.swift

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,20 @@ extension Decodable {
1616
}
1717

1818
extension Dependencies {
19-
static var mock = Dependencies(
20-
configuration: AuthClient.Configuration(
21-
url: URL(string: "https://project-id.supabase.com")!,
22-
localStorage: InMemoryLocalStorage(),
23-
logger: nil
24-
),
25-
http: HTTPClientMock(),
26-
api: APIClient(clientID: AuthClientID()),
27-
codeVerifierStorage: CodeVerifierStorage.mock,
28-
sessionStorage: SessionStorage.live(clientID: AuthClientID()),
29-
sessionManager: SessionManager.live(clientID: AuthClientID())
30-
)
19+
static var mock: Dependencies {
20+
Dependencies(
21+
configuration: AuthClient.Configuration(
22+
url: URL(string: "https://project-id.supabase.com")!,
23+
localStorage: InMemoryLocalStorage(),
24+
logger: nil
25+
),
26+
http: HTTPClientMock(),
27+
api: APIClient(clientID: AuthClientID()),
28+
codeVerifierStorage: CodeVerifierStorage.mock,
29+
sessionStorage: SessionStorage.live(clientID: AuthClientID()),
30+
sessionManager: SessionManager.live(clientID: AuthClientID())
31+
)
32+
}
3133
}
3234

3335
extension CodeVerifierStorage {

0 commit comments

Comments
 (0)