From d8190c92ac17fc52a96dea6ce458357adf923550 Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Thu, 11 Dec 2025 18:57:59 -0300 Subject: [PATCH 1/2] feat(storage_client): use dedicated storage host for storage lib (allows >50GB uploads) Transform legacy storage URLs to use the dedicated storage host which disables request buffering and allows uploads up to 500GB. Changes: - Added _transformStorageUrl() method to transform legacy URLs - Legacy format: project-ref.supabase.co/storage/v1 - New format: project-ref.storage.supabase.co/v1 - Only transforms Supabase platform hosts (supabase.co, supabase.in, supabase.red) - Preserves non-platform hosts and localhost URLs unchanged - Added comprehensive tests for URL transformation scenarios This matches the implementation in storage-js PR #230 and enables large file uploads (>50GB) by using the dedicated storage infrastructure. Closes SDK-230 --- .../lib/src/storage_client.dart | 33 ++++++++++- packages/storage_client/test/basic_test.dart | 56 +++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/packages/storage_client/lib/src/storage_client.dart b/packages/storage_client/lib/src/storage_client.dart index 89b7db955..ef6627b75 100644 --- a/packages/storage_client/lib/src/storage_client.dart +++ b/packages/storage_client/lib/src/storage_client.dart @@ -42,7 +42,7 @@ class SupabaseStorageClient extends StorageBucketApi { ), _defaultRetryAttempts = retryAttempts, super( - url, + _transformStorageUrl(url), {...Constants.defaultHeaders, ...headers}, httpClient: httpClient, ) { @@ -51,6 +51,37 @@ class SupabaseStorageClient extends StorageBucketApi { _log.finest('Initialize with headers: $headers'); } + /// Transforms legacy storage URLs to use the dedicated storage host. + /// + /// If legacy URI is used, replace with new storage host (disables request buffering to allow > 50GB uploads). + /// "project-ref.supabase.co/storage/v1" becomes "project-ref.storage.supabase.co/v1" + static String _transformStorageUrl(String url) { + final uri = Uri.parse(url); + final hostname = uri.host; + + // Check if it's a Supabase host (supabase.co, supabase.in, or supabase.red) + final isSupabaseHost = RegExp(r'supabase\.(co|in|red)$').hasMatch(hostname); + + // If it's a legacy storage URL, transform it + const legacyStoragePrefix = '/storage'; + if (isSupabaseHost && + !hostname.contains('storage.supabase.') && + uri.path.startsWith(legacyStoragePrefix)) { + // Remove /storage from pathname + final newPath = uri.path.substring(legacyStoragePrefix.length); + // Replace supabase. with storage.supabase. in hostname + final newHostname = hostname.replaceAll('supabase.', 'storage.supabase.'); + + // Reconstruct the URI + return uri.replace( + host: newHostname, + path: newPath, + ).toString(); + } + + return url; + } + /// Perform file operation in a bucket. /// /// [id] The bucket id to operate on. diff --git a/packages/storage_client/test/basic_test.dart b/packages/storage_client/test/basic_test.dart index 62080dc69..b530e22c7 100644 --- a/packages/storage_client/test/basic_test.dart +++ b/packages/storage_client/test/basic_test.dart @@ -287,4 +287,60 @@ void main() { expect(client.headers['X-Client-Info'], 'supabase-dart/0.0.0'); }); }); + + group('URL Construction', () { + test('should update legacy prod host to new host', () { + const inputUrl = 'https://blah.supabase.co/storage/v1'; + const expectedUrl = 'https://blah.storage.supabase.co/v1'; + client = SupabaseStorageClient(inputUrl, { + 'Authorization': 'Bearer $supabaseKey', + }); + expect(client.url, expectedUrl); + }); + + test('should update legacy staging host to new host', () { + const inputUrl = 'https://blah.supabase.red/storage/v1'; + const expectedUrl = 'https://blah.storage.supabase.red/v1'; + client = SupabaseStorageClient(inputUrl, { + 'Authorization': 'Bearer $supabaseKey', + }); + expect(client.url, expectedUrl); + }); + + test('should accept new host without modification', () { + const inputUrl = 'https://blah.storage.supabase.co/v1'; + const expectedUrl = 'https://blah.storage.supabase.co/v1'; + client = SupabaseStorageClient(inputUrl, { + 'Authorization': 'Bearer $supabaseKey', + }); + expect(client.url, expectedUrl); + }); + + test('should not modify non-platform hosts', () { + const inputUrl = 'https://blah.supabase.co.example.com/storage/v1'; + const expectedUrl = 'https://blah.supabase.co.example.com/storage/v1'; + client = SupabaseStorageClient(inputUrl, { + 'Authorization': 'Bearer $supabaseKey', + }); + expect(client.url, expectedUrl); + }); + + test('should support local host with port without modification', () { + const inputUrl = 'http://localhost:1234/storage/v1'; + const expectedUrl = 'http://localhost:1234/storage/v1'; + client = SupabaseStorageClient(inputUrl, { + 'Authorization': 'Bearer $supabaseKey', + }); + expect(client.url, expectedUrl); + }); + + test('should update legacy supabase.in host to new host', () { + const inputUrl = 'https://blah.supabase.in/storage/v1'; + const expectedUrl = 'https://blah.storage.supabase.in/v1'; + client = SupabaseStorageClient(inputUrl, { + 'Authorization': 'Bearer $supabaseKey', + }); + expect(client.url, expectedUrl); + }); + }); } From 6707e92af86fb47fae65a5b3b3f6cc9c4aab4695 Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Thu, 11 Dec 2025 19:03:09 -0300 Subject: [PATCH 2/2] style: dart fmt --- packages/storage_client/lib/src/storage_client.dart | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/storage_client/lib/src/storage_client.dart b/packages/storage_client/lib/src/storage_client.dart index ef6627b75..1665777b8 100644 --- a/packages/storage_client/lib/src/storage_client.dart +++ b/packages/storage_client/lib/src/storage_client.dart @@ -73,10 +73,12 @@ class SupabaseStorageClient extends StorageBucketApi { final newHostname = hostname.replaceAll('supabase.', 'storage.supabase.'); // Reconstruct the URI - return uri.replace( - host: newHostname, - path: newPath, - ).toString(); + return uri + .replace( + host: newHostname, + path: newPath, + ) + .toString(); } return url;