Skip to content
Open
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
19 changes: 18 additions & 1 deletion packages/core/postgrest-js/src/PostgrestBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 27 additions & 0 deletions packages/core/postgrest-js/test/fetch-errors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Database>('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'))
Expand Down
Loading