Skip to content

Commit 2c78bb2

Browse files
author
Guilherme Souza
committed
SupabaseClient and PostgrestBuilder conform to sendable
1 parent f694143 commit 2c78bb2

File tree

11 files changed

+372
-247
lines changed

11 files changed

+372
-247
lines changed

Examples/Examples/AuthView.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ final class AuthController: ObservableObject {
2121
}
2222

2323
func observeAuth() async {
24-
for await event in await supabase.auth.onAuthStateChange() {
24+
for await (event, session) in await supabase.auth.onAuthStateChange() {
2525
guard event == .signedIn || event == .signedOut else {
2626
return
2727
}
2828

29-
session = try? await supabase.auth.session
29+
self.session = session
3030
}
3131
}
3232
}

Sources/GoTrue/GoTrueClient.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,10 @@ public actor GoTrueClient {
173173
/// Listen for auth state changes.
174174
///
175175
/// An `.initialSession` is always emitted when this method is called.
176-
public func onAuthStateChange() async -> AsyncStream<(event: AuthChangeEvent, session: Session?)> {
176+
public func onAuthStateChange() async -> AsyncStream<(
177+
event: AuthChangeEvent,
178+
session: Session?
179+
)> {
177180
let (id, stream) = await eventEmitter.attachListener()
178181

179182
Task { [id] in

Sources/GoTrue/Internal/EventEmitter.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ import Foundation
22
@_spi(Internal) import _Helpers
33

44
struct EventEmitter: Sendable {
5-
var attachListener: @Sendable () async -> (id: UUID, stream: AsyncStream<(event: AuthChangeEvent, session: Session?)>)
5+
var attachListener: @Sendable () async -> (
6+
id: UUID,
7+
stream: AsyncStream<(event: AuthChangeEvent, session: Session?)>
8+
)
69
var emit: @Sendable (_ event: AuthChangeEvent, _ session: Session?, _ id: UUID?) async -> Void
710
}
811

@@ -14,13 +17,15 @@ extension EventEmitter {
1417

1518
extension EventEmitter {
1619
static var live: Self = {
17-
let continuations = ActorIsolated([UUID: AsyncStream<(event: AuthChangeEvent, session: Session?)>.Continuation]())
20+
let continuations =
21+
ActorIsolated([UUID: AsyncStream<(event: AuthChangeEvent, session: Session?)>.Continuation]())
1822

1923
return Self(
2024
attachListener: {
2125
let id = UUID()
2226

23-
let (stream, continuation) = AsyncStream<(event: AuthChangeEvent, session: Session?)>.makeStream()
27+
let (stream, continuation) = AsyncStream<(event: AuthChangeEvent, session: Session?)>
28+
.makeStream()
2429

2530
continuation.onTermination = { [id] _ in
2631
continuations.withValue {

Sources/PostgREST/PostgrestBuilder.swift

Lines changed: 67 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,42 @@
11
import Foundation
2+
@_spi(Internal) import _Helpers
23

34
#if canImport(FoundationNetworking)
45
import FoundationNetworking
56
#endif
67

78
/// The builder class for creating and executing requests to a PostgREST server.
8-
public class PostgrestBuilder {
9+
public class PostgrestBuilder: @unchecked Sendable {
910
/// The configuration for the PostgREST client.
1011
let configuration: PostgrestClient.Configuration
11-
/// The URL for the request.
12-
let url: URL
13-
/// The query parameters for the request.
14-
var queryParams: [(name: String, value: String?)]
15-
/// The headers for the request.
16-
var headers: [String: String]
17-
/// The HTTP method for the request.
18-
var method: String
19-
/// The body data for the request.
20-
var body: Data?
21-
22-
/// The options for fetching data from the PostgREST server.
23-
var fetchOptions = FetchOptions()
12+
13+
struct MutableState {
14+
var request: Request
15+
16+
/// The options for fetching data from the PostgREST server.
17+
var fetchOptions: FetchOptions
18+
}
19+
20+
let mutableState: ActorIsolated<MutableState>
2421

2522
init(
2623
configuration: PostgrestClient.Configuration,
27-
url: URL,
28-
queryParams: [(name: String, value: String?)],
29-
headers: [String: String],
30-
method: String,
31-
body: Data?
24+
request: Request
3225
) {
3326
self.configuration = configuration
34-
self.url = url
35-
self.queryParams = queryParams
36-
self.headers = headers
37-
self.method = method
38-
self.body = body
27+
28+
mutableState = ActorIsolated(
29+
MutableState(
30+
request: request,
31+
fetchOptions: FetchOptions()
32+
)
33+
)
3934
}
4035

4136
convenience init(_ other: PostgrestBuilder) {
4237
self.init(
4338
configuration: other.configuration,
44-
url: other.url,
45-
queryParams: other.queryParams,
46-
headers: other.headers,
47-
method: other.method,
48-
body: other.body
39+
request: other.mutableState.value.request
4940
)
5041
}
5142

@@ -57,7 +48,10 @@ public class PostgrestBuilder {
5748
public func execute(
5849
options: FetchOptions = FetchOptions()
5950
) async throws -> PostgrestResponse<Void> {
60-
fetchOptions = options
51+
mutableState.withValue {
52+
$0.fetchOptions = options
53+
}
54+
6155
return try await execute { _ in () }
6256
}
6357

@@ -69,39 +63,40 @@ public class PostgrestBuilder {
6963
public func execute<T: Decodable>(
7064
options: FetchOptions = FetchOptions()
7165
) async throws -> PostgrestResponse<T> {
72-
fetchOptions = options
66+
mutableState.withValue {
67+
$0.fetchOptions = options
68+
}
69+
7370
return try await execute { [configuration] data in
7471
try configuration.decoder.decode(T.self, from: data)
7572
}
7673
}
7774

78-
func appendSearchParams(name: String, value: String) {
79-
queryParams.append((name, value))
80-
}
81-
8275
private func execute<T>(decode: (Data) throws -> T) async throws -> PostgrestResponse<T> {
83-
if fetchOptions.head {
84-
method = "HEAD"
85-
}
86-
87-
if let count = fetchOptions.count {
88-
if let prefer = headers["Prefer"] {
89-
headers["Prefer"] = "\(prefer),count=\(count.rawValue)"
90-
} else {
91-
headers["Prefer"] = "count=\(count.rawValue)"
76+
mutableState.withValue {
77+
if $0.fetchOptions.head {
78+
$0.request.method = "HEAD"
9279
}
93-
}
9480

95-
if headers["Accept"] == nil {
96-
headers["Accept"] = "application/json"
97-
}
98-
headers["Content-Type"] = "application/json"
81+
if let count = $0.fetchOptions.count {
82+
if let prefer = $0.request.headers["Prefer"] {
83+
$0.request.headers["Prefer"] = "\(prefer),count=\(count.rawValue)"
84+
} else {
85+
$0.request.headers["Prefer"] = "count=\(count.rawValue)"
86+
}
87+
}
9988

100-
if let schema = configuration.schema {
101-
if method == "GET" || method == "HEAD" {
102-
headers["Accept-Profile"] = schema
103-
} else {
104-
headers["Content-Profile"] = schema
89+
if $0.request.headers["Accept"] == nil {
90+
$0.request.headers["Accept"] = "application/json"
91+
}
92+
$0.request.headers["Content-Type"] = "application/json"
93+
94+
if let schema = configuration.schema {
95+
if $0.request.method == "GET" || $0.request.method == "HEAD" {
96+
$0.request.headers["Accept-Profile"] = schema
97+
} else {
98+
$0.request.headers["Content-Profile"] = schema
99+
}
105100
}
106101
}
107102

@@ -122,13 +117,18 @@ public class PostgrestBuilder {
122117
}
123118

124119
private func makeURLRequest() throws -> URLRequest {
125-
guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
120+
let request = mutableState.value.request
121+
122+
guard var components = URLComponents(
123+
url: configuration.url.appendingPathComponent(request.path),
124+
resolvingAgainstBaseURL: false
125+
) else {
126126
throw URLError(.badURL)
127127
}
128128

129-
if !queryParams.isEmpty {
129+
if !request.query.isEmpty {
130130
let percentEncodedQuery =
131-
(components.percentEncodedQuery.map { $0 + "&" } ?? "") + query(queryParams)
131+
(components.percentEncodedQuery.map { $0 + "&" } ?? "") + query(request.query)
132132
components.percentEncodedQuery = percentEncodedQuery
133133
}
134134

@@ -138,13 +138,13 @@ public class PostgrestBuilder {
138138

139139
var urlRequest = URLRequest(url: url)
140140

141-
for (key, value) in headers {
141+
for (key, value) in request.headers {
142142
urlRequest.setValue(value, forHTTPHeaderField: key)
143143
}
144144

145-
urlRequest.httpMethod = method
145+
urlRequest.httpMethod = request.method
146146

147-
if let body {
147+
if let body = request.body {
148148
urlRequest.httpBody = body
149149
}
150150

@@ -155,17 +155,17 @@ public class PostgrestBuilder {
155155
string.addingPercentEncoding(withAllowedCharacters: .postgrestURLQueryAllowed) ?? string
156156
}
157157

158-
private func query(_ parameters: [(String, String?)]) -> String {
159-
parameters.compactMap { key, value in
160-
if let value {
161-
return (key, value)
158+
private func query(_ parameters: [URLQueryItem]) -> String {
159+
parameters.compactMap { query in
160+
if let value = query.value {
161+
return (query.name, value)
162162
}
163163
return nil
164164
}
165-
.map { key, value -> String in
166-
let escapedKey = escape(key)
165+
.map { name, value -> String in
166+
let escapedName = escape(name)
167167
let escapedValue = escape(value)
168-
return "\(escapedKey)=\(escapedValue)"
168+
return "\(escapedName)=\(escapedValue)"
169169
}
170170
.joined(separator: "&")
171171
}

Sources/PostgREST/PostgrestClient.swift

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,7 @@ public actor PostgrestClient {
102102
public func from(_ table: String) -> PostgrestQueryBuilder {
103103
PostgrestQueryBuilder(
104104
configuration: configuration,
105-
url: configuration.url.appendingPathComponent(table),
106-
queryParams: [],
107-
headers: configuration.headers,
108-
method: "GET",
109-
body: nil
105+
request: .init(path: table, method: "GET", headers: configuration.headers)
110106
)
111107
}
112108

@@ -125,11 +121,7 @@ public actor PostgrestClient {
125121
) throws -> PostgrestTransformBuilder {
126122
try PostgrestRpcBuilder(
127123
configuration: configuration,
128-
url: configuration.url.appendingPathComponent("rpc").appendingPathComponent(fn),
129-
queryParams: [],
130-
headers: configuration.headers,
131-
method: "POST",
132-
body: nil
124+
request: Request(path: "/rpc/\(fn)", method: "POST", headers: configuration.headers)
133125
).rpc(params: params, count: count)
134126
}
135127

0 commit comments

Comments
 (0)