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

supabase / supabase-js / 6234159930

19 Sep 2023 10:10AM UTC coverage: 65.385%. First build
6234159930

Pull #854

github

GitHub
Merge ccf4cab47 into 8cf2b9688
Pull Request #854:

47 of 92 branches covered (51.09%)

Branch coverage included in aggregate %.

6 of 9 new or added lines in 1 file covered. (66.67%)

89 of 116 relevant lines covered (76.72%)

4.35 hits per line

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

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

21
const DEFAULT_GLOBAL_OPTIONS = {
2✔
22
  headers: DEFAULT_HEADERS,
23
}
24

25
const DEFAULT_DB_OPTIONS = {
2✔
26
  schema: 'public',
27
}
28

29
const DEFAULT_AUTH_OPTIONS: SupabaseAuthClientOptions = {
2✔
30
  autoRefreshToken: true,
31
  persistSession: true,
32
  detectSessionInUrl: true,
33
  flowType: 'implicit',
34
}
35

36
const DEFAULT_REALTIME_OPTIONS: RealtimeClientOptions = {}
2✔
37

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

58
  protected realtimeUrl: string
59
  protected authUrl: string
60
  protected storageUrl: string
61
  protected functionsUrl: string
62
  protected rest: PostgrestClient<Database, SchemaName>
63
  protected storageKey: string
64
  protected fetch?: Fetch
65
  protected changedAccessToken?: string
66

67
  protected headers: {
68
    [key: string]: string
69
  }
70

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

91
    const _supabaseUrl = stripTrailingSlash(supabaseUrl)
7✔
92

93
    this.realtimeUrl = `${_supabaseUrl}/realtime/v1`.replace(/^http/i, 'ws')
7✔
94
    this.authUrl = `${_supabaseUrl}/auth/v1`
7✔
95
    this.storageUrl = `${_supabaseUrl}/storage/v1`
7✔
96
    this.functionsUrl = `${_supabaseUrl}/functions/v1`
7✔
97

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

107
    const settings = applySettingDefaults(options ?? {}, DEFAULTS)
7✔
108

109
    this.storageKey = settings.auth?.storageKey ?? ''
7!
110
    this.headers = settings.global?.headers ?? {}
7!
111

112
    this.auth = this._initSupabaseAuthClient(
7✔
113
      settings.auth ?? {},
21!
114
      this.headers,
115
      settings.global?.fetch
21!
116
    )
117
    this.fetch = fetchWithAuth(supabaseKey, this._getAccessToken.bind(this), settings.global?.fetch)
7!
118

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

126
    this._listenForAuthEvents()
7✔
127
  }
128

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

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

146
  from<
147
    TableName extends string & keyof Schema['Tables'],
148
    Table extends Schema['Tables'][TableName]
149
  >(relation: TableName): PostgrestQueryBuilder<Schema, Table>
150
  from<ViewName extends string & keyof Schema['Views'], View extends Schema['Views'][ViewName]>(
151
    relation: ViewName
152
  ): PostgrestQueryBuilder<Schema, View>
153
  from(relation: string): PostgrestQueryBuilder<Schema, any>
154
  /**
155
   * Perform a query on a table or a view.
156
   *
157
   * @param relation - The table or view name to query
158
   */
159
  from(relation: string): PostgrestQueryBuilder<Schema, any> {
160
    return this.rest.from(relation)
×
161
  }
162

163
  /**
164
   * Perform a query on a schema distinct from the default schema supplied via
165
   * the `options.db.schema` constructor parameter.
166
   *
167
   * The schema needs to be on the list of exposed schemas inside Supabase.
168
   *
169
   * @param schema - The name of the schema to query
170
   */
171
  schema<DynamicSchema extends string & keyof Database>(
172
    schema: DynamicSchema
173
  ): PostgrestClient<
174
    Database,
175
    DynamicSchema,
176
    Database[DynamicSchema] extends GenericSchema ? Database[DynamicSchema] : any
177
  > {
178
    return this.rest.schema<DynamicSchema>(schema)
2✔
179
  }
180

181
  /**
182
   * Perform a function call.
183
   *
184
   * @param fn - The function name to call
185
   * @param args - The arguments to pass to the function call
186
   * @param options - Named parameters
187
   * @param options.head - When set to `true`, `data` will not be returned.
188
   * Useful if you only need the count.
189
   * @param options.count - Count algorithm to use to count rows returned by the
190
   * function. Only applicable for [set-returning
191
   * functions](https://www.postgresql.org/docs/current/functions-srf.html).
192
   *
193
   * `"exact"`: Exact but slow count algorithm. Performs a `COUNT(*)` under the
194
   * hood.
195
   *
196
   * `"planned"`: Approximated but fast count algorithm. Uses the Postgres
197
   * statistics under the hood.
198
   *
199
   * `"estimated"`: Uses exact count for low numbers and planned count for high
200
   * numbers.
201
   */
202
  rpc<
203
    FunctionName extends string & keyof Schema['Functions'],
204
    Function_ extends Schema['Functions'][FunctionName]
205
  >(
206
    fn: FunctionName,
207
    args: Function_['Args'] = {},
1✔
208
    options?: {
209
      head?: boolean
210
      count?: 'exact' | 'planned' | 'estimated'
211
    }
212
  ): PostgrestFilterBuilder<
213
    Schema,
214
    Function_['Returns'] extends any[]
215
      ? Function_['Returns'][number] extends Record<string, unknown>
216
        ? Function_['Returns'][number]
217
        : never
218
      : never,
219
    Function_['Returns']
220
  > {
221
    return this.rest.rpc(fn, args, options)
1✔
222
  }
223

224
  /**
225
   * Creates a Realtime channel with Broadcast, Presence, and Postgres Changes.
226
   *
227
   * @param {string} name - The name of the Realtime channel.
228
   * @param {Object} opts - The options to pass to the Realtime channel.
229
   *
230
   */
231
  channel(name: string, opts: RealtimeChannelOptions = { config: {} }): RealtimeChannel {
×
232
    return this.realtime.channel(name, opts)
×
233
  }
234

235
  /**
236
   * Returns all Realtime channels.
237
   */
238
  getChannels(): RealtimeChannel[] {
239
    return this.realtime.getChannels()
×
240
  }
241

242
  /**
243
   * Unsubscribes and removes Realtime channel from Realtime client.
244
   *
245
   * @param {RealtimeChannel} channel - The name of the Realtime channel.
246
   *
247
   */
248
  removeChannel(channel: RealtimeChannel): Promise<'ok' | 'timed out' | 'error'> {
249
    return this.realtime.removeChannel(channel)
×
250
  }
251

252
  /**
253
   * Unsubscribes and removes all Realtime channels from Realtime client.
254
   */
255
  removeAllChannels(): Promise<('ok' | 'timed out' | 'error')[]> {
256
    return this.realtime.removeAllChannels()
×
257
  }
258

259
  private async _getAccessToken() {
260
    const { data } = await this.auth.getSession()
×
261

262
    return data.session?.access_token ?? null
×
263
  }
264

265
  private _initSupabaseAuthClient(
266
    {
267
      autoRefreshToken,
268
      persistSession,
269
      detectSessionInUrl,
270
      storage,
271
      storageKey,
272
      flowType,
273
      debug,
274
    }: SupabaseAuthClientOptions,
275
    headers?: Record<string, string>,
276
    fetch?: Fetch
277
  ) {
278
    const authHeaders = {
8✔
279
      Authorization: `Bearer ${this.supabaseKey}`,
280
      apikey: `${this.supabaseKey}`,
281
    }
282
    return new SupabaseAuthClient({
8✔
283
      url: this.authUrl,
284
      headers: { ...authHeaders, ...headers },
285
      storageKey: storageKey,
286
      autoRefreshToken,
287
      persistSession,
288
      detectSessionInUrl,
289
      storage,
290
      flowType,
291
      debug,
292
      fetch,
293
    })
294
  }
295

296
  private _initRealtimeClient(options: RealtimeClientOptions) {
297
    return new RealtimeClient(this.realtimeUrl, {
7✔
298
      ...options,
299
      params: { ...{ apikey: this.supabaseKey }, ...options?.params },
21!
300
    })
301
  }
302

303
  private _listenForAuthEvents() {
304
    let data = this.auth.onAuthStateChange((event, session) => {
7✔
305
      this._handleTokenChanged(event, 'CLIENT', session?.access_token)
7!
306
    })
307
    return data
7✔
308
  }
309

310
  private _handleTokenChanged(
311
    event: AuthChangeEvent,
312
    source: 'CLIENT' | 'STORAGE',
313
    token?: string
314
  ) {
315
    if (
7!
316
      (event === 'TOKEN_REFRESHED' || event === 'SIGNED_IN') &&
14!
317
      this.changedAccessToken !== token
318
    ) {
319
      // Token has changed
320
      this.realtime.setAuth(token ?? null)
×
321

322
      this.changedAccessToken = token
×
323
    } else if (event === 'SIGNED_OUT') {
7!
324
      // Token is removed
325
      this.realtime.setAuth(this.supabaseKey)
×
326
      if (source == 'STORAGE') this.auth.signOut()
×
327
      this.changedAccessToken = undefined
×
328
    }
329
  }
330
}
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