• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

supabase / supabase-js / 7382975635

02 Jan 2024 06:42AM UTC coverage: 65.877%. First build
7382975635

Pull #935

github

web-flow
Merge aff436136 into 80050b59e
Pull Request #935: fix: stop using unstable type params from postgrest-js

47 of 92 branches covered (0.0%)

Branch coverage included in aggregate %.

92 of 119 relevant lines covered (77.31%)

4.47 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

66.67
/src/SupabaseClient.ts
1
import { FunctionsClient } from '@supabase/functions-js'
2✔
2
import { AuthChangeEvent } from '@supabase/gotrue-js'
3
import { PostgrestClient } from '@supabase/postgrest-js'
2✔
4
import {
2✔
5
  RealtimeChannel,
6
  RealtimeChannelOptions,
7
  RealtimeClient,
8
  RealtimeClientOptions,
9
} from '@supabase/realtime-js'
10
import { StorageClient as SupabaseStorageClient } from '@supabase/storage-js'
2✔
11
import { DEFAULT_HEADERS } from './lib/constants'
2✔
12
import { fetchWithAuth } from './lib/fetch'
2✔
13
import { stripTrailingSlash, applySettingDefaults } from './lib/helpers'
2✔
14
import { SupabaseAuthClient } from './lib/SupabaseAuthClient'
2✔
15
import { Fetch, GenericSchema, SupabaseClientOptions, SupabaseAuthClientOptions } from './lib/types'
16

17
const DEFAULT_GLOBAL_OPTIONS = {
2✔
18
  headers: DEFAULT_HEADERS,
19
}
20

21
const DEFAULT_DB_OPTIONS = {
2✔
22
  schema: 'public',
23
}
24

25
const DEFAULT_AUTH_OPTIONS: SupabaseAuthClientOptions = {
2✔
26
  autoRefreshToken: true,
27
  persistSession: true,
28
  detectSessionInUrl: true,
29
  flowType: 'implicit',
30
}
31

32
const DEFAULT_REALTIME_OPTIONS: RealtimeClientOptions = {}
2✔
33

34
/**
35
 * Supabase Client.
36
 *
37
 * An isomorphic Javascript client for interacting with Postgres.
38
 */
39
export default class SupabaseClient<
2✔
40
  Database = any,
41
  SchemaName extends string & keyof Database = 'public' extends keyof Database
42
    ? 'public'
43
    : string & keyof Database,
44
  Schema extends GenericSchema = Database[SchemaName] extends GenericSchema
45
    ? Database[SchemaName]
46
    : any
47
> {
48
  /**
49
   * Supabase Auth allows you to create and manage user sessions for access to data that is secured by access policies.
50
   */
51
  auth: SupabaseAuthClient
52
  realtime: RealtimeClient
53

54
  protected realtimeUrl: string
55
  protected authUrl: string
56
  protected storageUrl: string
57
  protected functionsUrl: string
58
  protected rest: PostgrestClient<Database, SchemaName>
59
  protected storageKey: string
60
  protected fetch?: Fetch
61
  protected changedAccessToken?: string
62

63
  protected headers: {
64
    [key: string]: string
65
  }
66

67
  /**
68
   * Create a new client for use in the browser.
69
   * @param supabaseUrl The unique Supabase URL which is supplied when you create a new project in your project dashboard.
70
   * @param supabaseKey The unique Supabase Key which is supplied when you create a new project in your project dashboard.
71
   * @param options.db.schema You can switch in between schemas. The schema needs to be on the list of exposed schemas inside Supabase.
72
   * @param options.auth.autoRefreshToken Set to "true" if you want to automatically refresh the token before expiring.
73
   * @param options.auth.persistSession Set to "true" if you want to automatically save the user session into local storage.
74
   * @param options.auth.detectSessionInUrl Set to "true" if you want to automatically detects OAuth grants in the URL and signs in the user.
75
   * @param options.realtime Options passed along to realtime-js constructor.
76
   * @param options.global.fetch A custom fetch implementation.
77
   * @param options.global.headers Any additional headers to send with each network request.
78
   */
79
  constructor(
80
    protected supabaseUrl: string,
9✔
81
    protected supabaseKey: string,
9✔
82
    options?: SupabaseClientOptions<SchemaName>
83
  ) {
84
    if (!supabaseUrl) throw new Error('supabaseUrl is required.')
9✔
85
    if (!supabaseKey) throw new Error('supabaseKey is required.')
8✔
86

87
    const _supabaseUrl = stripTrailingSlash(supabaseUrl)
7✔
88

89
    this.realtimeUrl = `${_supabaseUrl}/realtime/v1`.replace(/^http/i, 'ws')
7✔
90
    this.authUrl = `${_supabaseUrl}/auth/v1`
7✔
91
    this.storageUrl = `${_supabaseUrl}/storage/v1`
7✔
92
    this.functionsUrl = `${_supabaseUrl}/functions/v1`
7✔
93

94
    // default storage key uses the supabase project ref as a namespace
95
    const defaultStorageKey = `sb-${new URL(this.authUrl).hostname.split('.')[0]}-auth-token`
7✔
96
    const DEFAULTS = {
7✔
97
      db: DEFAULT_DB_OPTIONS,
98
      realtime: DEFAULT_REALTIME_OPTIONS,
99
      auth: { ...DEFAULT_AUTH_OPTIONS, storageKey: defaultStorageKey },
100
      global: DEFAULT_GLOBAL_OPTIONS,
101
    }
102

103
    const settings = applySettingDefaults(options ?? {}, DEFAULTS)
7✔
104

105
    this.storageKey = settings.auth?.storageKey ?? ''
7!
106
    this.headers = settings.global?.headers ?? {}
7!
107

108
    this.auth = this._initSupabaseAuthClient(
7✔
109
      settings.auth ?? {},
21!
110
      this.headers,
111
      settings.global?.fetch
21!
112
    )
113
    this.fetch = fetchWithAuth(supabaseKey, this._getAccessToken.bind(this), settings.global?.fetch)
7!
114

115
    this.realtime = this._initRealtimeClient({ headers: this.headers, ...settings.realtime })
7✔
116
    this.rest = new PostgrestClient(`${_supabaseUrl}/rest/v1`, {
7✔
117
      headers: this.headers,
118
      schema: settings.db?.schema,
21!
119
      fetch: this.fetch,
120
    })
121

122
    this._listenForAuthEvents()
7✔
123
  }
124

125
  /**
126
   * Supabase Functions allows you to deploy and invoke edge functions.
127
   */
128
  get functions() {
129
    return new FunctionsClient(this.functionsUrl, {
×
130
      headers: this.headers,
131
      customFetch: this.fetch,
132
    })
133
  }
134

135
  /**
136
   * Supabase Storage allows you to manage user-generated content, such as photos or videos.
137
   */
138
  get storage() {
139
    return new SupabaseStorageClient(this.storageUrl, this.headers, this.fetch)
×
140
  }
141

142
  /**
143
   * Perform a query on a table or a view.
144
   *
145
   * @param relation - The table or view name to query
146
   */
147
  from: PostgrestClient<Database, SchemaName>['from'] = (relation: string) => {
9✔
148
    return this.rest.from(relation)
×
149
  }
150

151
  /**
152
   * Perform a query on a schema distinct from the default schema supplied via
153
   * the `options.db.schema` constructor parameter.
154
   *
155
   * The schema needs to be on the list of exposed schemas inside Supabase.
156
   *
157
   * @param schema - The name of the schema to query
158
   */
159
  schema: PostgrestClient<Database, SchemaName>['schema'] = <
9✔
160
    DynamicSchema extends string & keyof Database
161
  >(
162
    schema: DynamicSchema
163
  ) => {
164
    return this.rest.schema<DynamicSchema>(schema)
2✔
165
  }
166

167
  /**
168
   * Perform a function call.
169
   *
170
   * @param fn - The function name to call
171
   * @param args - The arguments to pass to the function call
172
   * @param options - Named parameters
173
   * @param options.head - When set to `true`, `data` will not be returned.
174
   * Useful if you only need the count.
175
   * @param options.count - Count algorithm to use to count rows returned by the
176
   * function. Only applicable for [set-returning
177
   * functions](https://www.postgresql.org/docs/current/functions-srf.html).
178
   *
179
   * `"exact"`: Exact but slow count algorithm. Performs a `COUNT(*)` under the
180
   * hood.
181
   *
182
   * `"planned"`: Approximated but fast count algorithm. Uses the Postgres
183
   * statistics under the hood.
184
   *
185
   * `"estimated"`: Uses exact count for low numbers and planned count for high
186
   * numbers.
187
   */
188
  rpc: PostgrestClient<Database, SchemaName>['rpc'] = <
9✔
189
    FunctionName extends string & keyof Schema['Functions'],
190
    Function_ extends Schema['Functions'][FunctionName]
191
  >(
192
    fn: FunctionName,
193
    args: Function_['Args'] = {},
1✔
194
    options?: {
195
      head?: boolean
196
      count?: 'exact' | 'planned' | 'estimated'
197
    }
198
  ) => {
199
    return this.rest.rpc(fn, args, options)
1✔
200
  }
201

202
  /**
203
   * Creates a Realtime channel with Broadcast, Presence, and Postgres Changes.
204
   *
205
   * @param {string} name - The name of the Realtime channel.
206
   * @param {Object} opts - The options to pass to the Realtime channel.
207
   *
208
   */
209
  channel(name: string, opts: RealtimeChannelOptions = { config: {} }): RealtimeChannel {
×
210
    return this.realtime.channel(name, opts)
×
211
  }
212

213
  /**
214
   * Returns all Realtime channels.
215
   */
216
  getChannels(): RealtimeChannel[] {
217
    return this.realtime.getChannels()
×
218
  }
219

220
  /**
221
   * Unsubscribes and removes Realtime channel from Realtime client.
222
   *
223
   * @param {RealtimeChannel} channel - The name of the Realtime channel.
224
   *
225
   */
226
  removeChannel(channel: RealtimeChannel): Promise<'ok' | 'timed out' | 'error'> {
227
    return this.realtime.removeChannel(channel)
×
228
  }
229

230
  /**
231
   * Unsubscribes and removes all Realtime channels from Realtime client.
232
   */
233
  removeAllChannels(): Promise<('ok' | 'timed out' | 'error')[]> {
234
    return this.realtime.removeAllChannels()
×
235
  }
236

237
  private async _getAccessToken() {
238
    const { data } = await this.auth.getSession()
×
239

240
    return data.session?.access_token ?? null
×
241
  }
242

243
  private _initSupabaseAuthClient(
244
    {
245
      autoRefreshToken,
246
      persistSession,
247
      detectSessionInUrl,
248
      storage,
249
      storageKey,
250
      flowType,
251
      debug,
252
    }: SupabaseAuthClientOptions,
253
    headers?: Record<string, string>,
254
    fetch?: Fetch
255
  ) {
256
    const authHeaders = {
8✔
257
      Authorization: `Bearer ${this.supabaseKey}`,
258
      apikey: `${this.supabaseKey}`,
259
    }
260
    return new SupabaseAuthClient({
8✔
261
      url: this.authUrl,
262
      headers: { ...authHeaders, ...headers },
263
      storageKey: storageKey,
264
      autoRefreshToken,
265
      persistSession,
266
      detectSessionInUrl,
267
      storage,
268
      flowType,
269
      debug,
270
      fetch,
271
    })
272
  }
273

274
  private _initRealtimeClient(options: RealtimeClientOptions) {
275
    return new RealtimeClient(this.realtimeUrl, {
7✔
276
      ...options,
277
      params: { ...{ apikey: this.supabaseKey }, ...options?.params },
21!
278
    })
279
  }
280

281
  private _listenForAuthEvents() {
282
    let data = this.auth.onAuthStateChange((event, session) => {
7✔
283
      this._handleTokenChanged(event, 'CLIENT', session?.access_token)
7!
284
    })
285
    return data
7✔
286
  }
287

288
  private _handleTokenChanged(
289
    event: AuthChangeEvent,
290
    source: 'CLIENT' | 'STORAGE',
291
    token?: string
292
  ) {
293
    if (
7!
294
      (event === 'TOKEN_REFRESHED' || event === 'SIGNED_IN') &&
14!
295
      this.changedAccessToken !== token
296
    ) {
297
      // Token has changed
298
      this.realtime.setAuth(token ?? null)
×
299

300
      this.changedAccessToken = token
×
301
    } else if (event === 'SIGNED_OUT') {
7!
302
      // Token is removed
303
      this.realtime.setAuth(this.supabaseKey)
×
304
      if (source == 'STORAGE') this.auth.signOut()
×
305
      this.changedAccessToken = undefined
×
306
    }
307
  }
308
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc