invoke method

Future<FunctionResponse> invoke(
  1. String functionName, {
  2. Map<String, String>? headers,
  3. Object? body,
  4. Iterable<MultipartFile>? files,
  5. Map<String, dynamic>? queryParameters,
  6. HttpMethod method = HttpMethod.post,
  7. String? region,
})

Invokes a function

functionName is the name of the function to invoke

headers to send with the request

body of the request when files is null and can be of type String or an Object that is encodable to JSON with jsonEncode. If files is not null, body represents the fields of the MultipartRequest and must be of type Map<String, String>.

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.

// Call a standard function
final response = await supabase.functions.invoke('hello-world');
print(response.data);

// Listen to Server Sent Events
final response = await supabase.functions.invoke('sse-function');
response.data
    .transform(const Utf8Decoder())
    .listen((val) {
      print(val);
    });

To stream SSE on the web, you can use a custom HTTP client that is able to handle SSE such as fetch_client.

final fetchClient = FetchClient(mode: RequestMode.cors);
await Supabase.initialize(
  url: supabaseUrl,
  publishableKey: supabaseKey,
  httpClient: fetchClient,
);

Implementation

Future<FunctionResponse> invoke(
  String functionName, {
  Map<String, String>? headers,
  Object? body,
  Iterable<http.MultipartFile>? files,
  Map<String, dynamic>? queryParameters,
  HttpMethod method = HttpMethod.post,
  String? region,
}) async {
  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 (effectiveRegion != null && effectiveRegion != 'any')
      'x-region': effectiveRegion,
  };

  if (body != null &&
      (headers == null || headers.containsKey("Content-Type") == false)) {
    finalHeaders['Content-Type'] = switch (body) {
      Uint8List() => 'application/octet-stream',
      String() => 'text/plain',
      _ => 'application/json',
    };
  }
  final http.BaseRequest request;
  if (files != null) {
    assert(
      body == null || body is Map<String, String>,
      'body must be of type Map',
    );
    final fields = body as Map<String, String>?;

    request = http.MultipartRequest(method.name.toUpperCase(), uri)
      ..fields.addAll(fields ?? {})
      ..files.addAll(files);
  } else {
    final bodyRequest = http.Request(method.name.toUpperCase(), uri);

    if (body == null) {
      // No body to set
    } else if (body is String) {
      bodyRequest.body = body;
    } else if (body is Uint8List) {
      bodyRequest.bodyBytes = body;
    } else {
      final bodyStr = await _isolate.encode(body);
      bodyRequest.body = bodyStr;
    }
    request = bodyRequest;
  }

  finalHeaders.forEach((key, value) {
    request.headers[key] = value;
  });

  _log.finest('Request: ${request.method} ${request.url} ${request.headers}');

  final response = await (_httpClient?.send(request) ?? request.send());
  final responseType = (response.headers['Content-Type'] ??
          response.headers['content-type'] ??
          'text/plain')
      .split(';')[0]
      .trim();

  final dynamic data;

  if (responseType == 'application/json') {
    final bodyBytes = await response.stream.toBytes();
    data = bodyBytes.isEmpty
        ? ""
        : await _isolate.decode(utf8.decode(bodyBytes));
  } else if (responseType == 'application/octet-stream') {
    data = await response.stream.toBytes();
  } else if (responseType == 'text/event-stream') {
    data = response.stream;
  } else {
    final bodyBytes = await response.stream.toBytes();
    data = utf8.decode(bodyBytes);
  }

  if (200 <= response.statusCode && response.statusCode < 300) {
    return FunctionResponse(data: data, status: response.statusCode);
  } else {
    throw FunctionException(
      status: response.statusCode,
      details: data,
      reasonPhrase: response.reasonPhrase,
    );
  }
}