From 4bf9624965d093a662b767c789dd7c5ce9e91b89 Mon Sep 17 00:00:00 2001 From: lyc Date: Sat, 9 Nov 2024 21:44:35 +0800 Subject: [PATCH] chore(lib/fetch): dynamic import `@supabase/node-fetch` (#1303) --- src/lib/fetch.ts | 29 +++--------------- src/lib/helpers.ts | 25 ++++++++++++++- test/helpers.test.ts | 72 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 26 deletions(-) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index c3ec5f42..3cb477f1 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -1,27 +1,5 @@ -// @ts-ignore -import nodeFetch, { Headers as NodeFetchHeaders } from '@supabase/node-fetch' - -type Fetch = typeof fetch - -export const resolveFetch = (customFetch?: Fetch): Fetch => { - let _fetch: Fetch - if (customFetch) { - _fetch = customFetch - } else if (typeof fetch === 'undefined') { - _fetch = nodeFetch as unknown as Fetch - } else { - _fetch = fetch - } - return (...args: Parameters) => _fetch(...args) -} - -export const resolveHeadersConstructor = () => { - if (typeof Headers === 'undefined') { - return NodeFetchHeaders - } - - return Headers -} +import { resolveFetch, resolveHeadersConstructor } from './helpers' +import { Fetch } from './types' export const fetchWithAuth = ( supabaseKey: string, @@ -29,10 +7,11 @@ export const fetchWithAuth = ( customFetch?: Fetch ): Fetch => { const fetch = resolveFetch(customFetch) - const HeadersConstructor = resolveHeadersConstructor() return async (input, init) => { const accessToken = (await getAccessToken()) ?? supabaseKey + const HeadersConstructor = await resolveHeadersConstructor() + let headers = new HeadersConstructor(init?.headers) if (!headers.has('apikey')) { diff --git a/src/lib/helpers.ts b/src/lib/helpers.ts index d1161d0c..29430f2c 100644 --- a/src/lib/helpers.ts +++ b/src/lib/helpers.ts @@ -1,5 +1,5 @@ // helpers.ts -import { SupabaseClientOptions } from './types' +import { Fetch, SupabaseClientOptions } from './types' export function uuid() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { @@ -66,3 +66,26 @@ export function applySettingDefaults< return result } + +export const resolveFetch = (customFetch?: Fetch): Fetch => { + let _fetch: Fetch + if (customFetch) { + _fetch = customFetch + } else if (typeof fetch === 'undefined') { + _fetch = (...args) => + import('@supabase/node-fetch' as any).then(({ default: fetch }) => fetch(...args)) + } else { + _fetch = fetch + } + return (...args: Parameters) => _fetch(...args) +} + +export const resolveHeadersConstructor = async () => { + if (typeof Headers === 'undefined') { + return import('@supabase/node-fetch' as any).then( + ({ Headers: NodeFetchHeaders }) => NodeFetchHeaders as typeof Headers + ) + } + + return Headers +} diff --git a/test/helpers.test.ts b/test/helpers.test.ts index efe6a23e..7140e159 100644 --- a/test/helpers.test.ts +++ b/test/helpers.test.ts @@ -40,3 +40,75 @@ test('override setting defaults', async () => { // Existing property values should remain constant expect(settings.db.schema).toBe(defaults.db.schema) }) + +describe('resolveFetch', () => { + const TEST_URL = 'https://example.com' + const TEST_OPTIONS = { method: 'GET' } + + beforeEach(() => { + // Reset any mocks between tests + jest.resetModules() + jest.clearAllMocks() + }) + + test('should use custom fetch if provided', async () => { + const customFetch = jest.fn() + const resolvedFetch = helpers.resolveFetch(customFetch) + + await resolvedFetch(TEST_URL, TEST_OPTIONS) + + expect(customFetch).toHaveBeenCalledTimes(1) + expect(customFetch).toHaveBeenCalledWith(TEST_URL, TEST_OPTIONS) + }) + + test('should use global fetch if no custom fetch is provided', async () => { + const globalFetch = jest.fn() + global.fetch = globalFetch + const resolvedFetch = helpers.resolveFetch() + + await resolvedFetch(TEST_URL, TEST_OPTIONS) + + expect(globalFetch).toHaveBeenCalledTimes(1) + expect(globalFetch).toHaveBeenCalledWith(TEST_URL, TEST_OPTIONS) + }) + + test('should use node-fetch if global fetch is not available', async () => { + const nodeFetch = jest.fn() + jest.mock('@supabase/node-fetch', () => nodeFetch) + + global.fetch = undefined as any + const resolvedFetch = helpers.resolveFetch() + + await resolvedFetch(TEST_URL, TEST_OPTIONS) + + expect(nodeFetch).toHaveBeenCalledTimes(1) + expect(nodeFetch).toHaveBeenCalledWith(TEST_URL, TEST_OPTIONS) + }) +}) + +describe('resolveHeadersConstructor', () => { + beforeEach(() => { + // Reset any mocks between tests + jest.resetModules() + jest.clearAllMocks() + }) + + test('should use Headers if available', async () => { + const resolvedHeadersConstructor = await helpers.resolveHeadersConstructor() + expect(resolvedHeadersConstructor).toBe(Headers) + }) + + test('should use node-fetch Headers if global Headers is not available', async () => { + const MockHeaders = jest.fn() + jest.mock('@supabase/node-fetch', () => ({ + Headers: MockHeaders, + })) + + // Cannot assign read-only property, delete is available + // @ts-ignore + delete global.Headers + + const resolvedHeadersConstructor = await helpers.resolveHeadersConstructor() + expect(resolvedHeadersConstructor).toBe(MockHeaders) + }) +})