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

supabase / supabase-js / 8395866041

22 Mar 2024 08:08PM UTC coverage: 65.55%. Remained the same
8395866041

Pull #984

github

web-flow
Merge 063289f0d into 393423a98
Pull Request #984: feat: typecheck table names in from clause

48 of 93 branches covered (51.61%)

Branch coverage included in aggregate %.

89 of 116 relevant lines covered (76.72%)

4.42 hits per line

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

65.0
/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 {
2✔
16
  DEFAULT_GLOBAL_OPTIONS,
17
  DEFAULT_DB_OPTIONS,
18
  DEFAULT_AUTH_OPTIONS,
19
  DEFAULT_REALTIME_OPTIONS,
20
} from './lib/constants'
21
import { fetchWithAuth } from './lib/fetch'
2✔
22
import { stripTrailingSlash, applySettingDefaults } from './lib/helpers'
2✔
23
import { SupabaseAuthClient } from './lib/SupabaseAuthClient'
2✔
24
import { Fetch, GenericSchema, SupabaseClientOptions, SupabaseAuthClientOptions } from './lib/types'
25

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

46
  protected realtimeUrl: string
47
  protected authUrl: string
48
  protected storageUrl: string
49
  protected functionsUrl: string
50
  protected rest: PostgrestClient<Database, SchemaName>
51
  protected storageKey: string
52
  protected fetch?: Fetch
53
  protected changedAccessToken?: string
54

55
  protected headers: Record<string, string>
56

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

77
    const _supabaseUrl = stripTrailingSlash(supabaseUrl)
7✔
78

79
    this.realtimeUrl = `${_supabaseUrl}/realtime/v1`.replace(/^http/i, 'ws')
7✔
80
    this.authUrl = `${_supabaseUrl}/auth/v1`
7✔
81
    this.storageUrl = `${_supabaseUrl}/storage/v1`
7✔
82
    this.functionsUrl = `${_supabaseUrl}/functions/v1`
7✔
83

84
    // default storage key uses the supabase project ref as a namespace
85
    const defaultStorageKey = `sb-${new URL(this.authUrl).hostname.split('.')[0]}-auth-token`
7✔
86
    const DEFAULTS = {
7✔
87
      db: DEFAULT_DB_OPTIONS,
88
      realtime: DEFAULT_REALTIME_OPTIONS,
89
      auth: { ...DEFAULT_AUTH_OPTIONS, storageKey: defaultStorageKey },
90
      global: DEFAULT_GLOBAL_OPTIONS,
91
    }
92

93
    const settings = applySettingDefaults(options ?? {}, DEFAULTS)
7✔
94

95
    this.storageKey = settings.auth?.storageKey ?? ''
7!
96
    this.headers = settings.global?.headers ?? {}
7!
97

98
    this.auth = this._initSupabaseAuthClient(
7✔
99
      settings.auth ?? {},
21!
100
      this.headers,
101
      settings.global?.fetch
21!
102
    )
103
    this.fetch = fetchWithAuth(supabaseKey, this._getAccessToken.bind(this), settings.global?.fetch)
7!
104

105
    this.realtime = this._initRealtimeClient({ headers: this.headers, ...settings.realtime })
7✔
106
    this.rest = new PostgrestClient(`${_supabaseUrl}/rest/v1`, {
7✔
107
      headers: this.headers,
108
      schema: settings.db?.schema,
21!
109
      fetch: this.fetch,
110
    })
111

112
    this._listenForAuthEvents()
7✔
113
  }
114

115
  /**
116
   * Supabase Functions allows you to deploy and invoke edge functions.
117
   */
118
  get functions() {
119
    return new FunctionsClient(this.functionsUrl, {
×
120
      headers: this.headers,
121
      customFetch: this.fetch,
122
    })
123
  }
124

125
  /**
126
   * Supabase Storage allows you to manage user-generated content, such as photos or videos.
127
   */
128
  get storage() {
129
    return new SupabaseStorageClient(this.storageUrl, this.headers, this.fetch)
×
130
  }
131

132
  // NOTE: signatures must be kept in sync with PostgrestClient.from
133
  from<
134
    TableName extends string & keyof Schema['Tables'],
135
    Table extends Schema['Tables'][TableName]
136
  >(relation: TableName): PostgrestQueryBuilder<Schema, Table, TableName>
137
  from<ViewName extends string & keyof Schema['Views'], View extends Schema['Views'][ViewName]>(
138
    relation: ViewName
139
  ): PostgrestQueryBuilder<Schema, View, ViewName>
140
  /**
141
   * Perform a query on a table or a view.
142
   *
143
   * @param relation - The table or view name to query
144
   */
145
  from(relation: string): PostgrestQueryBuilder<Schema, any, any> {
146
    return this.rest.from(relation)
×
147
  }
148

149
  // NOTE: signatures must be kept in sync with PostgrestClient.schema
150
  /**
151
   * Select a schema to query or perform an function (rpc) call.
152
   *
153
   * The schema needs to be on the list of exposed schemas inside Supabase.
154
   *
155
   * @param schema - The schema to query
156
   */
157
  schema<DynamicSchema extends string & keyof Database>(
158
    schema: DynamicSchema
159
  ): PostgrestClient<
160
    Database,
161
    DynamicSchema,
162
    Database[DynamicSchema] extends GenericSchema ? Database[DynamicSchema] : any
163
  > {
164
    return this.rest.schema<DynamicSchema>(schema)
2✔
165
  }
166

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

208
  /**
209
   * Creates a Realtime channel with Broadcast, Presence, and Postgres Changes.
210
   *
211
   * @param {string} name - The name of the Realtime channel.
212
   * @param {Object} opts - The options to pass to the Realtime channel.
213
   *
214
   */
215
  channel(name: string, opts: RealtimeChannelOptions = { config: {} }): RealtimeChannel {
×
216
    return this.realtime.channel(name, opts)
×
217
  }
218

219
  /**
220
   * Returns all Realtime channels.
221
   */
222
  getChannels(): RealtimeChannel[] {
223
    return this.realtime.getChannels()
×
224
  }
225

226
  /**
227
   * Unsubscribes and removes Realtime channel from Realtime client.
228
   *
229
   * @param {RealtimeChannel} channel - The name of the Realtime channel.
230
   *
231
   */
232
  removeChannel(channel: RealtimeChannel): Promise<'ok' | 'timed out' | 'error'> {
233
    return this.realtime.removeChannel(channel)
×
234
  }
235

236
  /**
237
   * Unsubscribes and removes all Realtime channels from Realtime client.
238
   */
239
  removeAllChannels(): Promise<('ok' | 'timed out' | 'error')[]> {
240
    return this.realtime.removeAllChannels()
×
241
  }
242

243
  private async _getAccessToken() {
244
    const { data } = await this.auth.getSession()
×
245

246
    return data.session?.access_token ?? null
×
247
  }
248

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

280
  private _initRealtimeClient(options: RealtimeClientOptions) {
281
    return new RealtimeClient(this.realtimeUrl, {
7✔
282
      ...options,
283
      params: { ...{ apikey: this.supabaseKey }, ...options?.params },
21!
284
    })
285
  }
286

287
  private _listenForAuthEvents() {
288
    let data = this.auth.onAuthStateChange((event, session) => {
7✔
289
      this._handleTokenChanged(event, 'CLIENT', session?.access_token)
7!
290
    })
291
    return data
7✔
292
  }
293

294
  private _handleTokenChanged(
295
    event: AuthChangeEvent,
296
    source: 'CLIENT' | 'STORAGE',
297
    token?: string
298
  ) {
299
    if (
7!
300
      (event === 'TOKEN_REFRESHED' || event === 'SIGNED_IN') &&
14!
301
      this.changedAccessToken !== token
302
    ) {
303
      // Token has changed
304
      this.realtime.setAuth(token ?? null)
×
305

306
      this.changedAccessToken = token
×
307
    } else if (event === 'SIGNED_OUT') {
7!
308
      // Token is removed
309
      this.realtime.setAuth(this.supabaseKey)
×
310
      if (source == 'STORAGE') this.auth.signOut()
×
311
      this.changedAccessToken = undefined
×
312
    }
313
  }
314
}
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

© 2026 Coveralls, Inc