Skip to content

Commit 57a55bc

Browse files
authored
feat(functions): make FunctionsClient an actor (#831)
* feat(functions): make FunctionsClient and actor * drop disfavoredOverload
1 parent 4b76069 commit 57a55bc

File tree

4 files changed

+58
-51
lines changed

4 files changed

+58
-51
lines changed

Sources/Functions/FunctionsClient.swift

Lines changed: 26 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import ConcurrencyExtras
21
import Foundation
32
import HTTPTypes
43

@@ -9,11 +8,12 @@ import HTTPTypes
98
let version = Helpers.version
109

1110
/// An actor representing a client for invoking functions.
12-
public final class FunctionsClient: Sendable {
11+
public actor FunctionsClient {
1312
/// Fetch handler used to make requests.
14-
public typealias FetchHandler = @Sendable (_ request: URLRequest) async throws -> (
15-
Data, URLResponse
16-
)
13+
public typealias FetchHandler =
14+
@Sendable (_ request: URLRequest) async throws -> (
15+
Data, URLResponse
16+
)
1717

1818
/// Request idle timeout: 150s (If an Edge Function doesn't send a response before the timeout, 504 Gateway Timeout will be returned)
1919
///
@@ -26,19 +26,12 @@ public final class FunctionsClient: Sendable {
2626
/// The Region to invoke the functions in.
2727
let region: FunctionRegion?
2828

29-
struct MutableState {
30-
/// Headers to be included in the requests.
31-
var headers = HTTPFields()
32-
}
29+
/// Headers to be included in the requests.
30+
var headers = HTTPFields()
3331

3432
private let http: any HTTPClientType
35-
private let mutableState = LockIsolated(MutableState())
3633
private let sessionConfiguration: URLSessionConfiguration
3734

38-
var headers: HTTPFields {
39-
mutableState.headers
40-
}
41-
4235
/// Initializes a new instance of `FunctionsClient`.
4336
///
4437
/// - Parameters:
@@ -47,8 +40,7 @@ public final class FunctionsClient: Sendable {
4740
/// - region: The Region to invoke the functions in.
4841
/// - logger: SupabaseLogger instance to use.
4942
/// - fetch: The fetch handler used to make requests. (Default: URLSession.shared.data(for:))
50-
@_disfavoredOverload
51-
public convenience init(
43+
public init(
5244
url: URL,
5345
headers: [String: String] = [:],
5446
region: FunctionRegion? = nil,
@@ -65,7 +57,7 @@ public final class FunctionsClient: Sendable {
6557
)
6658
}
6759

68-
convenience init(
60+
init(
6961
url: URL,
7062
headers: [String: String] = [:],
7163
region: FunctionRegion? = nil,
@@ -101,24 +93,20 @@ public final class FunctionsClient: Sendable {
10193
self.http = http
10294
self.sessionConfiguration = sessionConfiguration
10395

104-
mutableState.withValue {
105-
$0.headers = HTTPFields(headers)
106-
if $0.headers[.xClientInfo] == nil {
107-
$0.headers[.xClientInfo] = "functions-swift/\(version)"
108-
}
96+
self.headers = HTTPFields(headers)
97+
if self.headers[.xClientInfo] == nil {
98+
self.headers[.xClientInfo] = "functions-swift/\(version)"
10999
}
110100
}
111101

112102
/// Updates the authorization header.
113103
///
114104
/// - Parameter token: The new JWT token sent in the authorization header.
115105
public func setAuth(token: String?) {
116-
mutableState.withValue {
117-
if let token {
118-
$0.headers[.authorization] = "Bearer \(token)"
119-
} else {
120-
$0.headers[.authorization] = nil
121-
}
106+
if let token {
107+
headers[.authorization] = "Bearer \(token)"
108+
} else {
109+
headers[.authorization] = nil
122110
}
123111
}
124112

@@ -136,7 +124,8 @@ public final class FunctionsClient: Sendable {
136124
decode: (Data, HTTPURLResponse) throws -> Response
137125
) async throws -> Response {
138126
let response = try await rawInvoke(
139-
functionName: functionName, invokeOptions: options
127+
functionName: functionName,
128+
invokeOptions: options
140129
)
141130
return try decode(response.data, response.underlyingResponse)
142131
}
@@ -208,7 +197,10 @@ public final class FunctionsClient: Sendable {
208197
let delegate = StreamResponseDelegate(continuation: continuation)
209198

210199
let session = URLSession(
211-
configuration: sessionConfiguration, delegate: delegate, delegateQueue: nil)
200+
configuration: sessionConfiguration,
201+
delegate: delegate,
202+
delegateQueue: nil
203+
)
212204

213205
let urlRequest = buildRequest(functionName: functionName, options: invokeOptions).urlRequest
214206

@@ -233,7 +225,7 @@ public final class FunctionsClient: Sendable {
233225
url: url.appendingPathComponent(functionName),
234226
method: FunctionInvokeOptions.httpMethod(options.method) ?? .post,
235227
query: query,
236-
headers: mutableState.headers.merging(with: options.headers),
228+
headers: headers.merging(with: options.headers),
237229
body: options.body,
238230
timeoutInterval: FunctionsClient.requestIdleTimeout
239231
)
@@ -264,7 +256,9 @@ final class StreamResponseDelegate: NSObject, URLSessionDataDelegate, Sendable {
264256
}
265257

266258
func urlSession(
267-
_: URLSession, dataTask _: URLSessionDataTask, didReceive response: URLResponse,
259+
_: URLSession,
260+
dataTask _: URLSessionDataTask,
261+
didReceive response: URLResponse,
268262
completionHandler: @escaping (URLSession.ResponseDisposition) -> Void
269263
) {
270264
defer {

Supabase.xcworkspace/xcshareddata/xcschemes/Functions.xcscheme

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@
2828
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
2929
shouldUseLaunchSchemeArgsEnv = "YES">
3030
<Testables>
31+
<TestableReference
32+
skipped = "NO">
33+
<BuildableReference
34+
BuildableIdentifier = "primary"
35+
BlueprintIdentifier = "FunctionsTests"
36+
BuildableName = "FunctionsTests"
37+
BlueprintName = "FunctionsTests"
38+
ReferencedContainer = "container:">
39+
</BuildableReference>
40+
</TestableReference>
3141
</Testables>
3242
</TestAction>
3343
<LaunchAction

Tests/FunctionsTests/FunctionsClientTests.swift

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,13 @@ final class FunctionsClientTests: XCTestCase {
4949
headers: ["apikey": apiKey],
5050
region: .saEast1
5151
)
52-
XCTAssertEqual(client.region?.rawValue, "sa-east-1")
5352

54-
XCTAssertEqual(client.headers[.init("apikey")!], apiKey)
55-
XCTAssertNotNil(client.headers[.init("X-Client-Info")!])
53+
let region = await client.region
54+
XCTAssertEqual(region?.rawValue, "sa-east-1")
55+
56+
let headers = await client.headers
57+
XCTAssertEqual(headers[.init("apikey")!], apiKey)
58+
XCTAssertNotNil(headers[.init("X-Client-Info")!])
5659
}
5760

5861
func testInvoke() async throws {
@@ -333,12 +336,15 @@ final class FunctionsClientTests: XCTestCase {
333336
}
334337
}
335338

336-
func test_setAuth() {
337-
sut.setAuth(token: "access.token")
338-
XCTAssertEqual(sut.headers[.authorization], "Bearer access.token")
339+
func test_setAuth() async {
340+
await sut.setAuth(token: "access.token")
341+
342+
var headers = await sut.headers
343+
XCTAssertEqual(headers[.authorization], "Bearer access.token")
339344

340-
sut.setAuth(token: nil)
341-
XCTAssertNil(sut.headers[.authorization])
345+
await sut.setAuth(token: nil)
346+
headers = await sut.headers
347+
XCTAssertNil(headers[.authorization])
342348
}
343349

344350
func testInvokeWithStreamedResponse() async throws {
@@ -358,9 +364,7 @@ final class FunctionsClientTests: XCTestCase {
358364
}
359365
.register()
360366

361-
let stream = sut._invokeWithStreamedResponse("stream")
362-
363-
for try await value in stream {
367+
for try await value in await sut._invokeWithStreamedResponse("stream") {
364368
XCTAssertEqual(String(decoding: value, as: UTF8.self), "hello world")
365369
}
366370
}
@@ -382,10 +386,8 @@ final class FunctionsClientTests: XCTestCase {
382386
}
383387
.register()
384388

385-
let stream = sut._invokeWithStreamedResponse("stream")
386-
387389
do {
388-
for try await _ in stream {
390+
for try await _ in await sut._invokeWithStreamedResponse("stream") {
389391
XCTFail("should throw error")
390392
}
391393
} catch let FunctionsError.httpError(code, _) {
@@ -413,10 +415,8 @@ final class FunctionsClientTests: XCTestCase {
413415
}
414416
.register()
415417

416-
let stream = sut._invokeWithStreamedResponse("stream")
417-
418418
do {
419-
for try await _ in stream {
419+
for try await _ in await sut._invokeWithStreamedResponse("stream") {
420420
XCTFail("should throw error")
421421
}
422422
} catch FunctionsError.relayError {

Tests/SupabaseTests/SupabaseClientTests.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,14 @@ final class SupabaseClientTests: XCTestCase {
7777
"""
7878
}
7979
expectNoDifference(client.headers, client.auth.configuration.headers)
80-
expectNoDifference(client.headers, client.functions.headers.dictionary)
80+
81+
let functionsHeaders = await client.functions.headers
82+
expectNoDifference(client.headers, functionsHeaders.dictionary)
8183
expectNoDifference(client.headers, client.storage.configuration.headers)
8284
expectNoDifference(client.headers, client.rest.configuration.headers)
8385

84-
XCTAssertEqual(client.functions.region?.rawValue, "ap-northeast-1")
86+
let functionsRegion = await client.functions.region
87+
XCTAssertEqual(functionsRegion?.rawValue, "ap-northeast-1")
8588

8689
let realtimeURL = client.realtimeV2.url
8790
XCTAssertEqual(realtimeURL.absoluteString, "https://project-ref.supabase.co/realtime/v1")

0 commit comments

Comments
 (0)