Skip to content

Commit 2e05244

Browse files
authored
feat(functions_client): Add SSE support to invoke method (#905)
* feat: Add SSE support to invoke method * Add code sample in the comment docs * Add test * Update the comment on data of FunctionResponse and export ByteStream
1 parent 5697e20 commit 2e05244

File tree

5 files changed

+58
-47
lines changed

5 files changed

+58
-47
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
library functions_client;
22

3+
export 'package:http/http.dart' show ByteStream;
4+
35
export 'src/functions_client.dart';
46
export 'src/types.dart';

packages/functions_client/lib/src/functions_client.dart

Lines changed: 35 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import 'dart:convert';
33
import 'package:functions_client/src/constants.dart';
44
import 'package:functions_client/src/types.dart';
55
import 'package:http/http.dart' as http;
6-
import 'package:http/http.dart';
76
import 'package:yet_another_json_isolate/yet_another_json_isolate.dart';
87

98
class FunctionsClient {
@@ -44,6 +43,20 @@ class FunctionsClient {
4443
/// [headers]: object representing the headers to send with the request
4544
///
4645
/// [body]: the body of the request
46+
///
47+
/// ```dart
48+
/// // Call a standard function
49+
/// final response = await supabase.functions.invoke('hello-world');
50+
/// print(response.data);
51+
///
52+
/// // Listen to Server Sent Events
53+
/// final response = await supabase.functions.invoke('sse-function');
54+
/// response.data
55+
/// .transform(const Utf8Decoder())
56+
/// .listen((val) {
57+
/// print(val);
58+
/// });
59+
/// ```
4760
Future<FunctionResponse> invoke(
4861
String functionName, {
4962
Map<String, String>? headers,
@@ -52,67 +65,42 @@ class FunctionsClient {
5265
}) async {
5366
final bodyStr = body == null ? null : await _isolate.encode(body);
5467

55-
late final Response response;
5668
final uri = Uri.parse('$_url/$functionName');
5769

5870
final finalHeaders = <String, String>{
5971
..._headers,
6072
if (headers != null) ...headers
6173
};
6274

63-
switch (method) {
64-
case HttpMethod.post:
65-
response = await (_httpClient?.post ?? http.post)(
66-
uri,
67-
headers: finalHeaders,
68-
body: bodyStr,
69-
);
70-
break;
71-
72-
case HttpMethod.get:
73-
response = await (_httpClient?.get ?? http.get)(
74-
uri,
75-
headers: finalHeaders,
76-
);
77-
break;
78-
79-
case HttpMethod.put:
80-
response = await (_httpClient?.put ?? http.put)(
81-
uri,
82-
headers: finalHeaders,
83-
body: bodyStr,
84-
);
85-
break;
86-
87-
case HttpMethod.delete:
88-
response = await (_httpClient?.delete ?? http.delete)(
89-
uri,
90-
headers: finalHeaders,
91-
);
92-
break;
93-
94-
case HttpMethod.patch:
95-
response = await (_httpClient?.patch ?? http.patch)(
96-
uri,
97-
headers: finalHeaders,
98-
body: bodyStr,
99-
);
100-
break;
101-
}
75+
final request = http.Request(method.name, uri);
10276

77+
finalHeaders.forEach((key, value) {
78+
request.headers[key] = value;
79+
});
80+
if (bodyStr != null) request.body = bodyStr;
81+
final response = await (_httpClient?.send(request) ?? request.send());
10382
final responseType = (response.headers['Content-Type'] ??
10483
response.headers['content-type'] ??
10584
'text/plain')
10685
.split(';')[0]
10786
.trim();
10887

109-
final data = switch (responseType) {
110-
'application/json' => response.bodyBytes.isEmpty
88+
final dynamic data;
89+
90+
if (responseType == 'application/json') {
91+
final bodyBytes = await response.stream.toBytes();
92+
data = bodyBytes.isEmpty
11193
? ""
112-
: await _isolate.decode(utf8.decode(response.bodyBytes)),
113-
'application/octet-stream' => response.bodyBytes,
114-
_ => utf8.decode(response.bodyBytes),
115-
};
94+
: await _isolate.decode(utf8.decode(bodyBytes));
95+
} else if (responseType == 'application/octet-stream') {
96+
data = await response.stream.toBytes();
97+
} else if (responseType == 'text/event-stream') {
98+
data = response.stream;
99+
} else {
100+
final bodyBytes = await response.stream.toBytes();
101+
data = utf8.decode(bodyBytes);
102+
}
103+
116104
if (200 <= response.statusCode && response.statusCode < 300) {
117105
return FunctionResponse(data: data, status: response.statusCode);
118106
} else {

packages/functions_client/lib/src/types.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import 'dart:convert';
22
import 'dart:typed_data';
33

4+
import 'package:http/http.dart';
5+
46
enum HttpMethod {
57
get,
68
post,
@@ -14,6 +16,7 @@ class FunctionResponse {
1416
/// - 'text/plain': [String]
1517
/// - 'octet/stream': [Uint8List]
1618
/// - 'application/json': dynamic ([jsonDecode] is used)
19+
/// - 'text/event-stream': [ByteStream]
1720
final dynamic data;
1821
final int status;
1922

packages/functions_client/test/custom_http_client.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ class CustomHttpClient extends BaseClient {
1515
"Content-Type": "application/json",
1616
},
1717
);
18+
} else if (request.url.path.endsWith('sse')) {
19+
return StreamedResponse(
20+
Stream.fromIterable(['a', 'b', 'c'].map((e) => utf8.encode(e))), 200,
21+
request: request,
22+
headers: {
23+
"Content-Type": "text/event-stream",
24+
});
1825
} else {
1926
return StreamedResponse(
2027
Stream.value(utf8.encode(jsonEncode({"key": "Hello World"}))),

packages/functions_client/test/functions_dart_test.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'dart:convert';
2+
13
import 'package:functions_client/src/functions_client.dart';
24
import 'package:functions_client/src/types.dart';
35
import 'package:test/test.dart';
@@ -45,5 +47,14 @@ void main() {
4547
final res = await client.invoke('function1');
4648
expect(res.data, {'key': 'Hello World'});
4749
});
50+
51+
test('Listen to SSE event', () async {
52+
final res = await functionsCustomHttpClient.invoke('sse');
53+
expect(
54+
res.data.transform(const Utf8Decoder()),
55+
emitsInOrder(
56+
['a', 'b', 'c'],
57+
));
58+
});
4859
});
4960
}

0 commit comments

Comments
 (0)