diff --git a/packages/core/postgrest-js/src/PostgrestBuilder.ts b/packages/core/postgrest-js/src/PostgrestBuilder.ts index 42336a89ef..8e88ae9307 100644 --- a/packages/core/postgrest-js/src/PostgrestBuilder.ts +++ b/packages/core/postgrest-js/src/PostgrestBuilder.ts @@ -411,8 +411,25 @@ export default abstract class PostgrestBuilder< errorDetails += `\n${cause.stack}` } } else { - // No cause available, just include the error stack + // No cause available — include the error stack. + // In browsers, DNS failures (ERR_NAME_NOT_RESOLVED) surface as a plain + // TypeError("Failed to fetch") with no cause and no error code. + // Detect this pattern and surface a human-readable hint so developers + // know to check their project URL / DNS rather than their code. errorDetails = fetchError?.stack ?? '' + + const msg: string = fetchError?.message ?? '' + if ( + msg === 'Failed to fetch' || // Chrome / Edge + msg === 'NetworkError when attempting to fetch resource.' || // Firefox + msg === 'Load failed' // Safari + ) { + hint = + 'Network request failed — the Supabase project URL could not be reached. ' + + 'This is usually caused by a DNS resolution failure (e.g. ERR_NAME_NOT_RESOLVED in Chrome). ' + + 'Verify that your SUPABASE_URL is correct and that the project is active in the Supabase dashboard. ' + + 'If the issue persists, check https://status.supabase.com for platform outages.' + } } // Get URL length for potential hints diff --git a/packages/core/postgrest-js/test/fetch-errors.test.ts b/packages/core/postgrest-js/test/fetch-errors.test.ts index 097a830968..a5bf64cdbd 100644 --- a/packages/core/postgrest-js/test/fetch-errors.test.ts +++ b/packages/core/postgrest-js/test/fetch-errors.test.ts @@ -133,7 +133,34 @@ describe('Fetch error handling', () => { // When no cause, details should still have the stack trace expect(res.error!.details).toBeTruthy() }) + // Browser environments (Chrome/Edge/Firefox/Safari) surface DNS failures as a plain + // TypeError with no `cause` and no error code. The message varies by browser. + const browserDnsMessages = [ + ['Chrome/Edge', 'Failed to fetch'], + ['Firefox', 'NetworkError when attempting to fetch resource.'], + ['Safari', 'Load failed'], + ] + + test.each(browserDnsMessages)( + 'should surface a helpful hint for %s DNS failure (%s)', + async (_browser, msg) => { + const mockFetch = jest.fn().mockRejectedValue(new TypeError(msg)) + + const postgrest = new PostgrestClient('https://nonexistent-project.supabase.co', { + fetch: mockFetch as any, + }) + const res = await postgrest.from('users').select() + + expect(res.error).toBeTruthy() + expect(res.data).toBeNull() + expect(res.status).toBe(0) + expect(res.error!.message).toContain(msg) + expect(res.error!.hint).toContain('DNS resolution failure') + expect(res.error!.hint).toContain('SUPABASE_URL') + expect(res.error!.hint).toContain('status.supabase.com') + } + ) test('should handle generic errors without code', async () => { // Simulate a generic error const mockFetch = jest.fn().mockRejectedValue(new Error('Something went wrong'))