Skip to content

Commit 18d7a90

Browse files
authored
Merge pull request #492 from supabase/j0_patch_revamped_constructor
Patch revamped constructor
2 parents 78d07e4 + d892c12 commit 18d7a90

File tree

6 files changed

+140
-30
lines changed

6 files changed

+140
-30
lines changed

src/SupabaseClient.ts

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,27 @@ import { RealtimeChannel, RealtimeClient, RealtimeClientOptions } from '@supabas
99
import { StorageClient as SupabaseStorageClient } from '@supabase/storage-js'
1010
import { DEFAULT_HEADERS } from './lib/constants'
1111
import { fetchWithAuth } from './lib/fetch'
12-
import { stripTrailingSlash } from './lib/helpers'
12+
import { stripTrailingSlash, applySettingDefaults } from './lib/helpers'
1313
import { SupabaseAuthClient } from './lib/SupabaseAuthClient'
1414
import { SupabaseRealtimeChannel } from './lib/SupabaseRealtimeChannel'
1515
import { Fetch, GenericSchema, SupabaseClientOptions, SupabaseAuthClientOptions } from './lib/types'
1616

17-
const DEFAULT_OPTIONS = {
17+
const DEFAULT_GLOBAL_OPTIONS = {
18+
headers: DEFAULT_HEADERS,
19+
}
20+
21+
const DEFAULT_DB_OPTIONS = {
1822
schema: 'public',
23+
}
24+
25+
const DEFAULT_AUTH_OPTIONS: SupabaseAuthClientOptions = {
1926
autoRefreshToken: true,
2027
persistSession: true,
2128
detectSessionInUrl: true,
22-
headers: DEFAULT_HEADERS,
2329
}
2430

31+
const DEFAULT_REALTIME_OPTIONS: RealtimeClientOptions = {}
32+
2533
/**
2634
* Supabase Client.
2735
*
@@ -59,13 +67,13 @@ export default class SupabaseClient<
5967
* Create a new client for use in the browser.
6068
* @param supabaseUrl The unique Supabase URL which is supplied when you create a new project in your project dashboard.
6169
* @param supabaseKey The unique Supabase Key which is supplied when you create a new project in your project dashboard.
62-
* @param options.schema You can switch in between schemas. The schema needs to be on the list of exposed schemas inside Supabase.
63-
* @param options.autoRefreshToken Set to "true" if you want to automatically refresh the token before expiring.
64-
* @param options.persistSession Set to "true" if you want to automatically save the user session into local storage.
65-
* @param options.detectSessionInUrl Set to "true" if you want to automatically detects OAuth grants in the URL and signs in the user.
66-
* @param options.headers Any additional headers to send with each network request.
70+
* @param options.db.schema You can switch in between schemas. The schema needs to be on the list of exposed schemas inside Supabase.
71+
* @param options.auth.autoRefreshToken Set to "true" if you want to automatically refresh the token before expiring.
72+
* @param options.auth.persistSession Set to "true" if you want to automatically save the user session into local storage.
73+
* @param options.auth.detectSessionInUrl Set to "true" if you want to automatically detects OAuth grants in the URL and signs in the user.
6774
* @param options.realtime Options passed along to realtime-js constructor.
68-
* @param options.fetch A custom fetch implementation.
75+
* @param options.global.fetch A custom fetch implementation.
76+
* @param options.global.headers Any additional headers to send with each network request.
6977
*/
7078
constructor(
7179
protected supabaseUrl: string,
@@ -90,19 +98,29 @@ export default class SupabaseClient<
9098
}
9199
// default storage key uses the supabase project ref as a namespace
92100
const defaultStorageKey = `sb-${new URL(this.authUrl).hostname.split('.')[0]}-auth-token`
93-
this.storageKey = options?.auth?.storageKey ?? defaultStorageKey
101+
const DEFAULTS = {
102+
db: DEFAULT_DB_OPTIONS,
103+
realtime: DEFAULT_REALTIME_OPTIONS,
104+
auth: { ...DEFAULT_AUTH_OPTIONS, storageKey: defaultStorageKey },
105+
global: DEFAULT_GLOBAL_OPTIONS,
106+
}
94107

95-
const settings = { ...DEFAULT_OPTIONS, ...options, storageKey: this.storageKey }
108+
const settings = applySettingDefaults(options ?? {}, DEFAULTS)
96109

97-
this.headers = { ...DEFAULT_HEADERS, ...options?.headers }
110+
this.storageKey = settings.auth?.storageKey ?? ''
111+
this.headers = settings.global?.headers ?? {}
98112

99-
this.auth = this._initSupabaseAuthClient(settings.auth || {}, this.headers, settings.fetch)
100-
this.fetch = fetchWithAuth(supabaseKey, this._getAccessToken.bind(this), settings.fetch)
113+
this.auth = this._initSupabaseAuthClient(
114+
settings.auth ?? {},
115+
this.headers,
116+
settings.global?.fetch
117+
)
118+
this.fetch = fetchWithAuth(supabaseKey, this._getAccessToken.bind(this), settings.global?.fetch)
101119

102120
this.realtime = this._initRealtimeClient({ headers: this.headers, ...settings.realtime })
103121
this.rest = new PostgrestClient(`${_supabaseUrl}/rest/v1`, {
104122
headers: this.headers,
105-
schema: options?.db?.schema,
123+
schema: settings.db?.schema,
106124
fetch: this.fetch,
107125
})
108126

src/lib/helpers.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// helpers.ts
2+
import { SupabaseClientOptions } from './types'
23

34
export function uuid() {
45
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
@@ -13,3 +14,45 @@ export function stripTrailingSlash(url: string): string {
1314
}
1415

1516
export const isBrowser = () => typeof window !== 'undefined'
17+
18+
export function applySettingDefaults<
19+
Database = any,
20+
SchemaName extends string & keyof Database = 'public' extends keyof Database
21+
? 'public'
22+
: string & keyof Database
23+
>(
24+
options: SupabaseClientOptions<SchemaName>,
25+
defaults: SupabaseClientOptions<any>
26+
): SupabaseClientOptions<SchemaName> {
27+
const {
28+
db: dbOptions,
29+
auth: authOptions,
30+
realtime: realtimeOptions,
31+
global: globalOptions,
32+
} = options
33+
const {
34+
db: DEFAULT_DB_OPTIONS,
35+
auth: DEFAULT_AUTH_OPTIONS,
36+
realtime: DEFAULT_REALTIME_OPTIONS,
37+
global: DEFAULT_GLOBAL_OPTIONS,
38+
} = defaults
39+
40+
return {
41+
db: {
42+
...DEFAULT_DB_OPTIONS,
43+
...dbOptions,
44+
},
45+
auth: {
46+
...DEFAULT_AUTH_OPTIONS,
47+
...authOptions,
48+
},
49+
realtime: {
50+
...DEFAULT_REALTIME_OPTIONS,
51+
...realtimeOptions,
52+
},
53+
global: {
54+
...DEFAULT_GLOBAL_OPTIONS,
55+
...globalOptions,
56+
},
57+
}
58+
}

src/lib/types.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,16 @@ export type SupabaseClientOptions<SchemaName> = {
4545
* Options passed to the realtime-js instance
4646
*/
4747
realtime?: RealtimeClientOptions
48-
/**
49-
* A custom `fetch` implementation.
50-
*/
51-
fetch?: Fetch
52-
/**
53-
* Optional headers for initializing the client.
54-
*/
55-
headers?: Record<string, string>
48+
global?: {
49+
/**
50+
* A custom `fetch` implementation.
51+
*/
52+
fetch?: Fetch
53+
/**
54+
* Optional headers for initializing the client.
55+
*/
56+
headers?: Record<string, string>
57+
}
5658
}
5759

5860
export type SupabaseRealtimePayload<T> = {

test/SupabaseAuthClient.test.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,25 @@
11
import { SupabaseAuthClient } from '../src/lib/SupabaseAuthClient'
2+
import { SupabaseClientOptions } from '../src/lib/types'
3+
import { DEFAULT_HEADERS } from '../src/lib/constants'
24

35
const DEFAULT_OPTIONS = {
4-
schema: 'public',
5-
autoRefreshToken: true,
6-
persistSession: true,
7-
detectSessionInUrl: true,
8-
headers: {},
6+
auth: {
7+
autoRefreshToken: true,
8+
persistSession: true,
9+
detectSessionInUrl: true,
10+
},
11+
global: {
12+
headers: DEFAULT_HEADERS,
13+
},
14+
db: {
15+
schema: 'public',
16+
},
917
}
1018
const settings = { ...DEFAULT_OPTIONS }
1119

20+
const authSettings = { ...settings.global, ...settings.auth }
21+
1222
test('it should create a new instance of the class', () => {
13-
const authClient = new SupabaseAuthClient(settings)
23+
const authClient = new SupabaseAuthClient(authSettings)
1424
expect(authClient).toBeInstanceOf(SupabaseAuthClient)
1525
})

test/client.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ describe('Custom Headers', () => {
1919
test('should have custom header set', () => {
2020
const customHeader = { 'X-Test-Header': 'value' }
2121

22-
const request = createClient(URL, KEY, { headers: customHeader }).rpc('')
22+
const request = createClient(URL, KEY, { global: { headers: customHeader } }).rpc('')
2323

2424
// @ts-ignore
2525
const getHeaders = request.headers

test/helpers.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,42 @@
11
import * as helpers from '../src/lib/helpers'
2+
import { DEFAULT_HEADERS } from '../src/lib/constants'
23

34
test('uuid', async () => {
45
expect(helpers.uuid()).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/)
56
})
7+
8+
test('override setting defaults', async () => {
9+
const DEFAULT_GLOBAL_OPTIONS = {
10+
headers: DEFAULT_HEADERS,
11+
}
12+
13+
const DEFAULT_DB_OPTIONS = {
14+
schema: 'public',
15+
}
16+
17+
const DEFAULT_AUTH_OPTIONS = {
18+
autoRefreshToken: true,
19+
persistSession: true,
20+
detectSessionInUrl: true,
21+
}
22+
23+
let defaults = {
24+
db: DEFAULT_DB_OPTIONS,
25+
auth: DEFAULT_AUTH_OPTIONS,
26+
global: DEFAULT_GLOBAL_OPTIONS,
27+
}
28+
29+
let autoRefreshOption = false
30+
let options = {
31+
auth: {
32+
autoRefreshToken: autoRefreshOption,
33+
},
34+
}
35+
let settings = helpers.applySettingDefaults(options, defaults)
36+
expect(settings?.auth?.autoRefreshToken).toBe(autoRefreshOption)
37+
// Existing default properties should not be overwritten
38+
expect(settings?.auth?.persistSession).not.toBeNull()
39+
expect(settings?.global?.headers).toBe(DEFAULT_HEADERS)
40+
// Existing property values should remain constant
41+
expect(settings?.db?.schema).toBe(defaults.db.schema)
42+
})

0 commit comments

Comments
 (0)