applySchema method

Future<void> applySchema({
  1. required String projectRef,
  2. required String accessToken,
  3. required String schemaSql,
})

Applies schemaSql to the project identified by projectRef.

accessToken may be either a Supabase personal access token (PAT) or an organization access token — both use the same Bearer format. The token is NOT stored anywhere; it is used once for this request and then falls out of scope.

Throws ProvisioningException with a user-friendly message on any failure. The token is never included in exception messages or logs.

Implementation

Future<void> applySchema({
  required String projectRef,
  required String accessToken,
  required String schemaSql,
}) async {
  final url = '$_baseUrl/v1/projects/$projectRef/database/query';

  try {
    final response = await _dio.post<dynamic>(
      url,
      data: {'query': schemaSql},
      options: Options(
        headers: {
          'Authorization': 'Bearer $accessToken',
          'Content-Type': 'application/json',
          'User-Agent': 'dutato-setup',
        },
        sendTimeout: _timeout,
        receiveTimeout: _timeout,
        // Don't throw on non-2xx; we handle status mapping below so we can
        // surface a useful error to the user.
        validateStatus: (_) => true,
      ),
    );

    final status = response.statusCode ?? 0;
    if (status >= 200 && status < 300) {
      dev.log(
        'Schema applied to project $projectRef (HTTP $status).',
        name: 'SupabaseProvisioner',
      );
      return;
    }
    // Idempotency: if the failure is the bundler's own bail-out guard
    // ("project already has _schema_meta"), there's nothing to apply.
    // Treat it as a no-op success so callers don't have to special-case
    // already-bootstrapped projects.
    if (_isAlreadyBootstrapped(response.data)) {
      dev.log(
        'Schema already bootstrapped on project $projectRef — skipping.',
        name: 'SupabaseProvisioner',
      );
      return;
    }
    throw ProvisioningException(
      _mapStatus(status, projectRef, response.data),
    );
  } on DioException catch (e) {
    if (e.type == DioExceptionType.connectionTimeout ||
        e.type == DioExceptionType.sendTimeout ||
        e.type == DioExceptionType.receiveTimeout) {
      throw ProvisioningException(
        'Timed out while applying the schema. Try again, or use the '
        'manual fallback.',
      );
    }
    if (e.type == DioExceptionType.connectionError) {
      throw ProvisioningException(
        'Could not reach Supabase. Check your network and try again.',
      );
    }
    // Log without the auth header.
    dev.log(
      'Dio error during provisioning: ${e.type} ${e.message}',
      name: 'SupabaseProvisioner',
    );
    throw ProvisioningException(
      'Network error while applying the schema. Try again.',
    );
  }
}