Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions packages/functions_client/lib/src/functions_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class FunctionsClient {
final http.Client? _httpClient;
final YAJsonIsolate _isolate;
final bool _hasCustomIsolate;
final String? _region;
final _log = Logger("supabase.functions");

/// In case you don't provide your own isolate, call [dispose] when you're done
Expand All @@ -23,12 +24,15 @@ class FunctionsClient {
Map<String, String> headers, {
http.Client? httpClient,
YAJsonIsolate? isolate,
String? region,
}) : _url = url,
_headers = {...Constants.defaultHeaders, ...headers},
_isolate = isolate ?? (YAJsonIsolate()..initialize()),
_hasCustomIsolate = isolate != null,
_httpClient = httpClient {
_log.config("Initialize FunctionsClient v$version with url: $url");
_httpClient = httpClient,
_region = region {
_log.config(
"Initialize FunctionsClient v$version with url '$url' and region '$region'");
_log.finest("Initialize with headers: $headers");
}

Expand Down Expand Up @@ -57,6 +61,8 @@ class FunctionsClient {
///
/// [files] to send in a `MultipartRequest`. [body] is used for the fields.
///
/// [region] optionally specify the region to invoke the function in.
/// When specified, adds both `x-region` header and `forceFunctionRegion` query parameter.
///
/// ```dart
/// // Call a standard function
Expand Down Expand Up @@ -88,13 +94,26 @@ class FunctionsClient {
Iterable<http.MultipartFile>? files,
Map<String, dynamic>? queryParameters,
HttpMethod method = HttpMethod.post,
String? region,
}) async {
final uri = Uri.parse('$_url/$functionName')
.replace(queryParameters: queryParameters);
final effectiveRegion = region ?? _region;

// Merge query parameters with forceFunctionRegion if region is specified
final effectiveQueryParams = <String, dynamic>{
if (queryParameters != null) ...queryParameters,
if (effectiveRegion != null && effectiveRegion != 'any')
'forceFunctionRegion': effectiveRegion,
};

final uri = Uri.parse('$_url/$functionName').replace(
queryParameters:
effectiveQueryParams.isNotEmpty ? effectiveQueryParams : null);

final finalHeaders = <String, String>{
..._headers,
if (headers != null) ...headers
if (headers != null) ...headers,
if (effectiveRegion != null && effectiveRegion != 'any')
'x-region': effectiveRegion,
};

if (body != null &&
Expand Down
74 changes: 74 additions & 0 deletions packages/functions_client/test/functions_dart_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,80 @@ void main() {
});
});

group('Region support', () {
test(
'region parameter adds x-region header and forceFunctionRegion query param',
() async {
await functionsCustomHttpClient.invoke(
'function',
region: 'us-west-1',
);

final req = customHttpClient.receivedRequests.last;
expect(req.headers['x-region'], 'us-west-1');
expect(req.url.queryParameters['forceFunctionRegion'], 'us-west-1');
});

test('region "any" does not add header or query param', () async {
await functionsCustomHttpClient.invoke(
'function',
region: 'any',
);

final req = customHttpClient.receivedRequests.last;
expect(req.headers.containsKey('x-region'), isFalse);
expect(req.url.queryParameters.containsKey('forceFunctionRegion'),
isFalse);
});

test('client region is used when invoke region is not specified',
() async {
final client = FunctionsClient(
"",
{},
httpClient: customHttpClient,
region: 'eu-west-1',
);

await client.invoke('function');

final req = customHttpClient.receivedRequests.last;
expect(req.headers['x-region'], 'eu-west-1');
expect(req.url.queryParameters['forceFunctionRegion'], 'eu-west-1');
});

test('invoke region overrides client region', () async {
final client = FunctionsClient(
"",
{},
httpClient: customHttpClient,
region: 'eu-west-1',
);

await client.invoke('function', region: 'us-east-1');

final req = customHttpClient.receivedRequests.last;
expect(req.headers['x-region'], 'us-east-1');
expect(req.url.queryParameters['forceFunctionRegion'], 'us-east-1');
});

test('region works with other query parameters', () async {
await functionsCustomHttpClient.invoke(
'function',
region: 'ap-south-1',
queryParameters: {'key': 'value', 'foo': 'bar'},
);

final req = customHttpClient.receivedRequests.last;
expect(req.headers['x-region'], 'ap-south-1');
expect(req.url.queryParameters, {
'key': 'value',
'foo': 'bar',
'forceFunctionRegion': 'ap-south-1',
});
});
});

group('Constructor variations', () {
test('constructor with all parameters', () {
final isolate = YAJsonIsolate();
Expand Down
4 changes: 4 additions & 0 deletions packages/supabase/lib/src/supabase_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import 'counter.dart';
class SupabaseClient {
final String _supabaseKey;
final PostgrestClientOptions _postgrestOptions;
final FunctionsClientOptions _functionsOptions;

final String _restUrl;
final String _realtimeUrl;
Expand Down Expand Up @@ -119,12 +120,14 @@ class SupabaseClient {
PostgrestClientOptions postgrestOptions = const PostgrestClientOptions(),
AuthClientOptions authOptions = const AuthClientOptions(),
StorageClientOptions storageOptions = const StorageClientOptions(),
FunctionsClientOptions functionsOptions = const FunctionsClientOptions(),
RealtimeClientOptions realtimeClientOptions = const RealtimeClientOptions(),
this.accessToken,
Map<String, String>? headers,
Client? httpClient,
YAJsonIsolate? isolate,
}) : _supabaseKey = supabaseKey,
_functionsOptions = functionsOptions,
_restUrl = '$supabaseUrl/rest/v1',
_realtimeUrl = '$supabaseUrl/realtime/v1'.replaceAll('http', 'ws'),
_authUrl = '$supabaseUrl/auth/v1',
Expand Down Expand Up @@ -309,6 +312,7 @@ class SupabaseClient {
{...headers},
httpClient: _authHttpClient,
isolate: _isolate,
region: _functionsOptions.region,
);
}

Expand Down
6 changes: 6 additions & 0 deletions packages/supabase/lib/src/supabase_client_options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,9 @@ class StorageClientOptions {

const StorageClientOptions({this.retryAttempts = 0});
}

class FunctionsClientOptions {
final String? region;

const FunctionsClientOptions({this.region});
}