chub-dev 0.2.0-beta.3 → 0.2.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,1606 +0,0 @@
1
- ---
2
- name: sdk
3
- description: "Open-source Firebase alternative with PostgreSQL backend, authentication, storage, realtime subscriptions, and edge functions"
4
- metadata:
5
- languages: "javascript"
6
- versions: "2.76.1"
7
- updated-on: "2025-10-26"
8
- source: maintainer
9
- tags: "supabase,sdk,database,auth,storage,realtime"
10
- ---
11
- # Supabase JavaScript SDK Coding Guidelines
12
-
13
- You are a Supabase coding expert. Help me write code using the Supabase JavaScript SDK, which provides a complete backend-as-a-service solution with authentication, database, storage, realtime subscriptions, and edge functions.
14
-
15
- You can find the official SDK documentation here:
16
- https://supabase.com/docs/reference/javascript/introduction
17
-
18
- ## Golden Rule: Use the Correct and Current SDK
19
-
20
- Always use the official Supabase JavaScript SDK (`@supabase/supabase-js`) for all Supabase interactions. Do not use deprecated libraries or unofficial packages.
21
-
22
- - **Library Name:** Supabase JavaScript SDK
23
- - **NPM Package:** `@supabase/supabase-js`
24
- - **Current Version:** 2.76.1
25
- - **Legacy Libraries:** Do not use `@supabase/auth-js`, `@supabase/postgrest-js`, `@supabase/storage-js`, or other individual packages directly - these are bundled in the main SDK
26
-
27
- **Installation:**
28
-
29
- ```bash
30
- npm install @supabase/supabase-js
31
- ```
32
-
33
- **APIs and Usage:**
34
-
35
- - **Correct:** `import { createClient } from '@supabase/supabase-js'`
36
- - **Correct:** `const supabase = createClient(url, anonKey)`
37
- - **Correct:** `await supabase.from('table').select()`
38
- - **Correct:** `await supabase.auth.signInWithPassword()`
39
- - **Incorrect:** Using individual package imports
40
- - **Incorrect:** `new Supabase()` or `SupabaseClient()`
41
-
42
- ## Initialization
43
-
44
- The Supabase SDK requires your project URL and public anon key for initialization.
45
-
46
- ### Basic Client Setup
47
-
48
- ```javascript
49
- import { createClient } from '@supabase/supabase-js'
50
-
51
- const supabaseUrl = process.env.SUPABASE_URL
52
- const supabaseAnonKey = process.env.SUPABASE_ANON_KEY
53
-
54
- const supabase = createClient(supabaseUrl, supabaseAnonKey)
55
- ```
56
-
57
- ### Environment Variables
58
-
59
- Set these environment variables in your `.env` file:
60
-
61
- ```bash
62
- SUPABASE_URL=https://xyzcompany.supabase.co
63
- SUPABASE_ANON_KEY=your-anon-key-here
64
- ```
65
-
66
- ### Client Initialization with Options
67
-
68
- ```javascript
69
- import { createClient } from '@supabase/supabase-js'
70
-
71
- const supabase = createClient(supabaseUrl, supabaseAnonKey, {
72
- auth: {
73
- autoRefreshToken: true,
74
- persistSession: true,
75
- detectSessionInUrl: true
76
- },
77
- db: {
78
- schema: 'public'
79
- },
80
- global: {
81
- headers: { 'x-my-custom-header': 'my-app-name' }
82
- }
83
- })
84
- ```
85
-
86
- ### Server-Side Client (No Session Persistence)
87
-
88
- ```javascript
89
- import { createClient } from '@supabase/supabase-js'
90
-
91
- const supabase = createClient(supabaseUrl, supabaseAnonKey, {
92
- auth: {
93
- autoRefreshToken: false,
94
- persistSession: false,
95
- detectSessionInUrl: false
96
- }
97
- })
98
- ```
99
-
100
- ## Authentication
101
-
102
- The Supabase SDK provides comprehensive authentication through `supabase.auth`.
103
-
104
- ### Sign Up with Email and Password
105
-
106
- ```javascript
107
- const { data, error } = await supabase.auth.signUp({
108
- email: 'user@example.com',
109
- password: 'secure-password-123'
110
- })
111
-
112
- if (error) {
113
- console.error('Error signing up:', error.message)
114
- } else {
115
- console.log('User created:', data.user)
116
- }
117
- ```
118
-
119
- ### Sign Up with Additional User Metadata
120
-
121
- ```javascript
122
- const { data, error } = await supabase.auth.signUp({
123
- email: 'user@example.com',
124
- password: 'secure-password-123',
125
- options: {
126
- data: {
127
- first_name: 'John',
128
- age: 27
129
- }
130
- }
131
- })
132
- ```
133
-
134
- ### Sign In with Email and Password
135
-
136
- ```javascript
137
- const { data, error } = await supabase.auth.signInWithPassword({
138
- email: 'user@example.com',
139
- password: 'secure-password-123'
140
- })
141
-
142
- if (error) {
143
- console.error('Error signing in:', error.message)
144
- } else {
145
- console.log('User signed in:', data.user)
146
- console.log('Session:', data.session)
147
- }
148
- ```
149
-
150
- ### Sign In with OAuth
151
-
152
- ```javascript
153
- const { data, error } = await supabase.auth.signInWithOAuth({
154
- provider: 'google'
155
- })
156
-
157
- // Available providers: google, github, gitlab, bitbucket, azure,
158
- // facebook, twitter, discord, slack, spotify, twitch, linkedin, etc.
159
- ```
160
-
161
- ### Sign In with OAuth and Options
162
-
163
- ```javascript
164
- const { data, error } = await supabase.auth.signInWithOAuth({
165
- provider: 'google',
166
- options: {
167
- redirectTo: 'https://example.com/auth/callback',
168
- scopes: 'https://www.googleapis.com/auth/calendar',
169
- queryParams: {
170
- access_type: 'offline',
171
- prompt: 'consent'
172
- }
173
- }
174
- })
175
- ```
176
-
177
- ### Sign In with Magic Link (OTP)
178
-
179
- ```javascript
180
- const { data, error } = await supabase.auth.signInWithOtp({
181
- email: 'user@example.com',
182
- options: {
183
- emailRedirectTo: 'https://example.com/welcome'
184
- }
185
- })
186
- ```
187
-
188
- ### Sign In with Phone OTP
189
-
190
- ```javascript
191
- // Send OTP
192
- const { data, error } = await supabase.auth.signInWithOtp({
193
- phone: '+1234567890'
194
- })
195
-
196
- // Verify OTP
197
- const { data, error } = await supabase.auth.verifyOtp({
198
- phone: '+1234567890',
199
- token: '123456',
200
- type: 'sms'
201
- })
202
- ```
203
-
204
- ### Anonymous Sign In
205
-
206
- ```javascript
207
- const { data, error } = await supabase.auth.signInAnonymously()
208
-
209
- console.log('Anonymous user:', data.user)
210
- ```
211
-
212
- ### Get Current User
213
-
214
- ```javascript
215
- const { data: { user } } = await supabase.auth.getUser()
216
-
217
- if (user) {
218
- console.log('Current user:', user)
219
- } else {
220
- console.log('No user logged in')
221
- }
222
- ```
223
-
224
- ### Get Current Session
225
-
226
- ```javascript
227
- const { data: { session } } = await supabase.auth.getSession()
228
-
229
- if (session) {
230
- console.log('Access token:', session.access_token)
231
- console.log('Refresh token:', session.refresh_token)
232
- console.log('Expires at:', session.expires_at)
233
- }
234
- ```
235
-
236
- ### Update User
237
-
238
- ```javascript
239
- const { data, error } = await supabase.auth.updateUser({
240
- email: 'newemail@example.com',
241
- password: 'new-password-456',
242
- data: {
243
- first_name: 'Jane',
244
- age: 28
245
- }
246
- })
247
- ```
248
-
249
- ### Sign Out
250
-
251
- ```javascript
252
- const { error } = await supabase.auth.signOut()
253
-
254
- if (error) {
255
- console.error('Error signing out:', error.message)
256
- } else {
257
- console.log('User signed out successfully')
258
- }
259
- ```
260
-
261
- ### Password Reset
262
-
263
- ```javascript
264
- // Send password reset email
265
- const { data, error } = await supabase.auth.resetPasswordForEmail(
266
- 'user@example.com',
267
- {
268
- redirectTo: 'https://example.com/reset-password'
269
- }
270
- )
271
- ```
272
-
273
- ### Auth State Changes Listener
274
-
275
- ```javascript
276
- const { data: { subscription } } = supabase.auth.onAuthStateChange(
277
- (event, session) => {
278
- console.log('Auth event:', event)
279
- console.log('Session:', session)
280
-
281
- if (event === 'SIGNED_IN') {
282
- console.log('User signed in!')
283
- }
284
- if (event === 'SIGNED_OUT') {
285
- console.log('User signed out!')
286
- }
287
- }
288
- )
289
-
290
- // Unsubscribe when done
291
- subscription.unsubscribe()
292
- ```
293
-
294
- ### Refresh Session
295
-
296
- ```javascript
297
- const { data, error } = await supabase.auth.refreshSession()
298
-
299
- console.log('New session:', data.session)
300
- ```
301
-
302
- ### Exchange Auth Code (OAuth Flow)
303
-
304
- ```javascript
305
- const { data, error } = await supabase.auth.exchangeCodeForSession(code)
306
- ```
307
-
308
- ## Database Queries
309
-
310
- The Supabase SDK uses PostgREST for database operations with a chainable API.
311
-
312
- ### Select All Rows
313
-
314
- ```javascript
315
- const { data, error } = await supabase
316
- .from('countries')
317
- .select('*')
318
-
319
- if (error) {
320
- console.error('Error fetching data:', error.message)
321
- } else {
322
- console.log('Countries:', data)
323
- }
324
- ```
325
-
326
- ### Select Specific Columns
327
-
328
- ```javascript
329
- const { data, error } = await supabase
330
- .from('countries')
331
- .select('id, name, capital')
332
- ```
333
-
334
- ### Select with Column Alias
335
-
336
- ```javascript
337
- const { data, error } = await supabase
338
- .from('countries')
339
- .select('country_name:name, country_id:id')
340
- ```
341
-
342
- ### Select with Foreign Table Relations
343
-
344
- ```javascript
345
- const { data, error } = await supabase
346
- .from('countries')
347
- .select(`
348
- name,
349
- cities (
350
- name,
351
- population
352
- )
353
- `)
354
- ```
355
-
356
- ### Select with Multiple Foreign Key References
357
-
358
- ```javascript
359
- const { data, error } = await supabase
360
- .from('messages')
361
- .select(`
362
- content,
363
- from:sender_id(name),
364
- to:receiver_id(name)
365
- `)
366
- ```
367
-
368
- ### Select with Nested Foreign Tables
369
-
370
- ```javascript
371
- const { data, error } = await supabase
372
- .from('countries')
373
- .select(`
374
- name,
375
- cities (
376
- name,
377
- buildings (
378
- name,
379
- floors
380
- )
381
- )
382
- `)
383
- ```
384
-
385
- ### Select with Filters
386
-
387
- ```javascript
388
- const { data, error } = await supabase
389
- .from('countries')
390
- .select('*')
391
- .eq('name', 'United States')
392
- ```
393
-
394
- ### Filtering Operators
395
-
396
- ```javascript
397
- // Equals
398
- .eq('column', 'value')
399
-
400
- // Not equals
401
- .neq('column', 'value')
402
-
403
- // Greater than
404
- .gt('column', 10)
405
-
406
- // Greater than or equal
407
- .gte('column', 10)
408
-
409
- // Less than
410
- .lt('column', 10)
411
-
412
- // Less than or equal
413
- .lte('column', 10)
414
-
415
- // Pattern matching (case-sensitive)
416
- .like('column', '%pattern%')
417
-
418
- // Pattern matching (case-insensitive)
419
- .ilike('column', '%pattern%')
420
-
421
- // Is null
422
- .is('column', null)
423
-
424
- // In array
425
- .in('column', ['value1', 'value2', 'value3'])
426
-
427
- // Contains (for arrays)
428
- .contains('column', ['value1', 'value2'])
429
-
430
- // Contained by (for arrays)
431
- .containedBy('column', ['value1', 'value2', 'value3'])
432
-
433
- // Range overlap
434
- .overlaps('column', [start, end])
435
-
436
- // Full text search
437
- .textSearch('column', 'search terms')
438
- ```
439
-
440
- ### Multiple Filters (AND)
441
-
442
- ```javascript
443
- const { data, error } = await supabase
444
- .from('countries')
445
- .select('*')
446
- .eq('continent', 'Europe')
447
- .gt('population', 1000000)
448
- ```
449
-
450
- ### OR Filters
451
-
452
- ```javascript
453
- const { data, error } = await supabase
454
- .from('countries')
455
- .select('*')
456
- .or('continent.eq.Europe,continent.eq.Asia')
457
- ```
458
-
459
- ### Filter on Foreign Table Columns
460
-
461
- ```javascript
462
- const { data, error } = await supabase
463
- .from('countries')
464
- .select('name, cities(*)')
465
- .eq('cities.name', 'Paris')
466
- ```
467
-
468
- ### Inner Join (Only Return Rows with Matches)
469
-
470
- ```javascript
471
- const { data, error } = await supabase
472
- .from('countries')
473
- .select('name, cities!inner(name)')
474
- .eq('cities.name', 'Paris')
475
- ```
476
-
477
- ### Ordering Results
478
-
479
- ```javascript
480
- // Ascending order
481
- const { data, error } = await supabase
482
- .from('countries')
483
- .select('*')
484
- .order('name', { ascending: true })
485
-
486
- // Descending order
487
- const { data, error } = await supabase
488
- .from('countries')
489
- .select('*')
490
- .order('population', { ascending: false })
491
- ```
492
-
493
- ### Ordering with Nulls
494
-
495
- ```javascript
496
- const { data, error } = await supabase
497
- .from('countries')
498
- .select('*')
499
- .order('name', { ascending: true, nullsFirst: false })
500
- ```
501
-
502
- ### Order Foreign Table Columns
503
-
504
- ```javascript
505
- const { data, error } = await supabase
506
- .from('countries')
507
- .select('name, cities(name)')
508
- .order('name', { foreignTable: 'cities' })
509
- ```
510
-
511
- ### Limit Results
512
-
513
- ```javascript
514
- const { data, error } = await supabase
515
- .from('countries')
516
- .select('*')
517
- .limit(10)
518
- ```
519
-
520
- ### Limit Foreign Table Rows
521
-
522
- ```javascript
523
- const { data, error } = await supabase
524
- .from('countries')
525
- .select('name, cities(name)')
526
- .limit(5, { foreignTable: 'cities' })
527
- ```
528
-
529
- ### Pagination with Range
530
-
531
- ```javascript
532
- // Get rows 0-9
533
- const { data, error } = await supabase
534
- .from('countries')
535
- .select('*')
536
- .range(0, 9)
537
-
538
- // Get rows 10-19
539
- const { data, error } = await supabase
540
- .from('countries')
541
- .select('*')
542
- .range(10, 19)
543
- ```
544
-
545
- ### Get Single Row
546
-
547
- ```javascript
548
- const { data, error } = await supabase
549
- .from('countries')
550
- .select('*')
551
- .eq('id', 1)
552
- .single()
553
-
554
- // Returns single object instead of array
555
- ```
556
-
557
- ### Maybe Single (No Error if Not Found)
558
-
559
- ```javascript
560
- const { data, error } = await supabase
561
- .from('countries')
562
- .select('*')
563
- .eq('id', 1)
564
- .maybeSingle()
565
- ```
566
-
567
- ### Count Rows
568
-
569
- ```javascript
570
- const { count, error } = await supabase
571
- .from('countries')
572
- .select('*', { count: 'exact', head: true })
573
-
574
- console.log('Total countries:', count)
575
- ```
576
-
577
- ### Query JSON Columns
578
-
579
- ```javascript
580
- const { data, error } = await supabase
581
- .from('users')
582
- .select('id, name, address->city, address->country')
583
- ```
584
-
585
- ### Query Array Columns
586
-
587
- ```javascript
588
- const { data, error } = await supabase
589
- .from('users')
590
- .select('*')
591
- .contains('tags', ['developer', 'designer'])
592
- ```
593
-
594
- ### Use Different Schema
595
-
596
- ```javascript
597
- const { data, error } = await supabase
598
- .schema('private')
599
- .from('employees')
600
- .select('*')
601
- ```
602
-
603
- ## Insert Data
604
-
605
- ### Insert Single Row
606
-
607
- ```javascript
608
- const { data, error } = await supabase
609
- .from('countries')
610
- .insert({
611
- name: 'Canada',
612
- capital: 'Ottawa',
613
- population: 38000000
614
- })
615
- ```
616
-
617
- ### Insert Single Row and Return Data
618
-
619
- ```javascript
620
- const { data, error } = await supabase
621
- .from('countries')
622
- .insert({
623
- name: 'Canada',
624
- capital: 'Ottawa'
625
- })
626
- .select()
627
-
628
- console.log('Inserted row:', data)
629
- ```
630
-
631
- ### Insert Multiple Rows
632
-
633
- ```javascript
634
- const { data, error } = await supabase
635
- .from('countries')
636
- .insert([
637
- { name: 'Canada', capital: 'Ottawa' },
638
- { name: 'Mexico', capital: 'Mexico City' },
639
- { name: 'Brazil', capital: 'Brasilia' }
640
- ])
641
- .select()
642
- ```
643
-
644
- ### Insert and Return Specific Columns
645
-
646
- ```javascript
647
- const { data, error } = await supabase
648
- .from('countries')
649
- .insert({ name: 'Canada', capital: 'Ottawa' })
650
- .select('id, name')
651
- ```
652
-
653
- ## Update Data
654
-
655
- ### Update Matching Rows
656
-
657
- ```javascript
658
- const { data, error } = await supabase
659
- .from('countries')
660
- .update({ capital: 'New Capital' })
661
- .eq('name', 'Canada')
662
- ```
663
-
664
- ### Update with Multiple Filters
665
-
666
- ```javascript
667
- const { data, error } = await supabase
668
- .from('countries')
669
- .update({ population: 39000000 })
670
- .eq('name', 'Canada')
671
- .lt('population', 40000000)
672
- ```
673
-
674
- ### Update and Return Data
675
-
676
- ```javascript
677
- const { data, error } = await supabase
678
- .from('countries')
679
- .update({ capital: 'New Capital' })
680
- .eq('id', 1)
681
- .select()
682
- ```
683
-
684
- ### Update JSON Column
685
-
686
- ```javascript
687
- const { data, error } = await supabase
688
- .from('users')
689
- .update({
690
- settings: { theme: 'dark', language: 'en' }
691
- })
692
- .eq('id', 1)
693
- ```
694
-
695
- ## Upsert Data
696
-
697
- ### Upsert (Insert or Update)
698
-
699
- ```javascript
700
- const { data, error } = await supabase
701
- .from('countries')
702
- .upsert({
703
- id: 1,
704
- name: 'Canada',
705
- capital: 'Ottawa'
706
- })
707
- ```
708
-
709
- ### Upsert Multiple Rows
710
-
711
- ```javascript
712
- const { data, error } = await supabase
713
- .from('countries')
714
- .upsert([
715
- { id: 1, name: 'Canada', capital: 'Ottawa' },
716
- { id: 2, name: 'Mexico', capital: 'Mexico City' }
717
- ])
718
- ```
719
-
720
- ### Upsert with Conflict Resolution
721
-
722
- ```javascript
723
- const { data, error } = await supabase
724
- .from('countries')
725
- .upsert(
726
- { id: 1, name: 'Canada' },
727
- { onConflict: 'id' }
728
- )
729
- ```
730
-
731
- ### Upsert and Ignore Duplicates
732
-
733
- ```javascript
734
- const { data, error } = await supabase
735
- .from('countries')
736
- .upsert(
737
- { name: 'Canada', capital: 'Ottawa' },
738
- { onConflict: 'name', ignoreDuplicates: true }
739
- )
740
- ```
741
-
742
- ## Delete Data
743
-
744
- ### Delete Matching Rows
745
-
746
- ```javascript
747
- const { error } = await supabase
748
- .from('countries')
749
- .delete()
750
- .eq('name', 'Canada')
751
- ```
752
-
753
- ### Delete with Multiple Filters
754
-
755
- ```javascript
756
- const { error } = await supabase
757
- .from('countries')
758
- .delete()
759
- .eq('continent', 'Europe')
760
- .lt('population', 100000)
761
- ```
762
-
763
- ### Delete and Return Data
764
-
765
- ```javascript
766
- const { data, error } = await supabase
767
- .from('countries')
768
- .delete()
769
- .eq('id', 1)
770
- .select()
771
- ```
772
-
773
- ## RPC (Call Postgres Functions)
774
-
775
- ### Call Function Without Parameters
776
-
777
- ```javascript
778
- const { data, error } = await supabase.rpc('hello_world')
779
-
780
- console.log('Function result:', data)
781
- ```
782
-
783
- ### Call Function with Parameters
784
-
785
- ```javascript
786
- const { data, error } = await supabase.rpc('add_numbers', {
787
- a: 5,
788
- b: 10
789
- })
790
-
791
- console.log('Sum:', data)
792
- ```
793
-
794
- ### Call Function with Array Parameter
795
-
796
- ```javascript
797
- const { data, error } = await supabase.rpc('add_one_each', {
798
- arr: [1, 2, 3, 4, 5]
799
- })
800
-
801
- console.log('Result:', data)
802
- ```
803
-
804
- ### Chain Filters with RPC
805
-
806
- ```javascript
807
- const { data, error } = await supabase
808
- .rpc('get_countries')
809
- .eq('continent', 'Europe')
810
- .order('population', { ascending: false })
811
- .limit(10)
812
- ```
813
-
814
- ### RPC with Single Result
815
-
816
- ```javascript
817
- const { data, error } = await supabase
818
- .rpc('get_country_by_id', { country_id: 1 })
819
- .single()
820
- ```
821
-
822
- ### Call Function on Read Replica
823
-
824
- ```javascript
825
- const { data, error } = await supabase.rpc(
826
- 'get_data',
827
- {},
828
- { get: true }
829
- )
830
- ```
831
-
832
- ## Realtime Subscriptions
833
-
834
- The Supabase SDK provides realtime functionality through channels.
835
-
836
- ### Subscribe to Database Changes
837
-
838
- ```javascript
839
- const channel = supabase
840
- .channel('db-changes')
841
- .on(
842
- 'postgres_changes',
843
- {
844
- event: '*',
845
- schema: 'public',
846
- table: 'countries'
847
- },
848
- (payload) => {
849
- console.log('Change received!', payload)
850
- }
851
- )
852
- .subscribe()
853
- ```
854
-
855
- ### Subscribe to INSERT Events
856
-
857
- ```javascript
858
- const channel = supabase
859
- .channel('db-inserts')
860
- .on(
861
- 'postgres_changes',
862
- {
863
- event: 'INSERT',
864
- schema: 'public',
865
- table: 'countries'
866
- },
867
- (payload) => {
868
- console.log('New record:', payload.new)
869
- }
870
- )
871
- .subscribe()
872
- ```
873
-
874
- ### Subscribe to UPDATE Events
875
-
876
- ```javascript
877
- const channel = supabase
878
- .channel('db-updates')
879
- .on(
880
- 'postgres_changes',
881
- {
882
- event: 'UPDATE',
883
- schema: 'public',
884
- table: 'countries'
885
- },
886
- (payload) => {
887
- console.log('Old record:', payload.old)
888
- console.log('New record:', payload.new)
889
- }
890
- )
891
- .subscribe()
892
- ```
893
-
894
- ### Subscribe to DELETE Events
895
-
896
- ```javascript
897
- const channel = supabase
898
- .channel('db-deletes')
899
- .on(
900
- 'postgres_changes',
901
- {
902
- event: 'DELETE',
903
- schema: 'public',
904
- table: 'countries'
905
- },
906
- (payload) => {
907
- console.log('Deleted record:', payload.old)
908
- }
909
- )
910
- .subscribe()
911
- ```
912
-
913
- ### Subscribe with Row-Level Filter
914
-
915
- ```javascript
916
- const channel = supabase
917
- .channel('specific-row')
918
- .on(
919
- 'postgres_changes',
920
- {
921
- event: '*',
922
- schema: 'public',
923
- table: 'countries',
924
- filter: 'id=eq.1'
925
- },
926
- (payload) => {
927
- console.log('Change to specific row:', payload)
928
- }
929
- )
930
- .subscribe()
931
- ```
932
-
933
- ### Subscribe to Multiple Tables
934
-
935
- ```javascript
936
- const channel = supabase
937
- .channel('multi-table')
938
- .on(
939
- 'postgres_changes',
940
- { event: '*', schema: 'public', table: 'countries' },
941
- handleCountryChange
942
- )
943
- .on(
944
- 'postgres_changes',
945
- { event: '*', schema: 'public', table: 'cities' },
946
- handleCityChange
947
- )
948
- .subscribe()
949
- ```
950
-
951
- ### Unsubscribe from Changes
952
-
953
- ```javascript
954
- const channel = supabase.channel('my-channel')
955
-
956
- // ... set up subscriptions
957
-
958
- // Later, unsubscribe
959
- await supabase.removeChannel(channel)
960
- ```
961
-
962
- ### Broadcast Messages
963
-
964
- ```javascript
965
- // Send broadcast
966
- const channel = supabase.channel('room-1')
967
-
968
- channel.subscribe((status) => {
969
- if (status === 'SUBSCRIBED') {
970
- channel.send({
971
- type: 'broadcast',
972
- event: 'cursor-pos',
973
- payload: { x: 100, y: 200 }
974
- })
975
- }
976
- })
977
-
978
- // Receive broadcast
979
- channel.on('broadcast', { event: 'cursor-pos' }, (payload) => {
980
- console.log('Cursor position:', payload)
981
- })
982
- ```
983
-
984
- ### Presence Tracking
985
-
986
- ```javascript
987
- const channel = supabase.channel('room-1')
988
-
989
- // Track user presence
990
- channel.on('presence', { event: 'sync' }, () => {
991
- const state = channel.presenceState()
992
- console.log('Online users:', state)
993
- })
994
-
995
- channel.on('presence', { event: 'join' }, ({ key, newPresences }) => {
996
- console.log('User joined:', newPresences)
997
- })
998
-
999
- channel.on('presence', { event: 'leave' }, ({ key, leftPresences }) => {
1000
- console.log('User left:', leftPresences)
1001
- })
1002
-
1003
- // Subscribe and track current user
1004
- channel.subscribe(async (status) => {
1005
- if (status === 'SUBSCRIBED') {
1006
- await channel.track({
1007
- user_id: 1,
1008
- online_at: new Date().toISOString()
1009
- })
1010
- }
1011
- })
1012
- ```
1013
-
1014
- ### Untrack Presence
1015
-
1016
- ```javascript
1017
- await channel.untrack()
1018
- ```
1019
-
1020
- ### Channel Status Callbacks
1021
-
1022
- ```javascript
1023
- const channel = supabase
1024
- .channel('my-channel')
1025
- .subscribe((status, err) => {
1026
- if (status === 'SUBSCRIBED') {
1027
- console.log('Connected!')
1028
- }
1029
- if (status === 'CHANNEL_ERROR') {
1030
- console.error('Connection error:', err)
1031
- }
1032
- if (status === 'TIMED_OUT') {
1033
- console.log('Connection timed out')
1034
- }
1035
- if (status === 'CLOSED') {
1036
- console.log('Channel closed')
1037
- }
1038
- })
1039
- ```
1040
-
1041
- ## Storage
1042
-
1043
- The Supabase SDK provides file storage through `supabase.storage`.
1044
-
1045
- ### List Buckets
1046
-
1047
- ```javascript
1048
- const { data, error } = await supabase
1049
- .storage
1050
- .listBuckets()
1051
-
1052
- console.log('Buckets:', data)
1053
- ```
1054
-
1055
- ### Get Bucket
1056
-
1057
- ```javascript
1058
- const { data, error } = await supabase
1059
- .storage
1060
- .getBucket('avatars')
1061
-
1062
- console.log('Bucket:', data)
1063
- ```
1064
-
1065
- ### Create Bucket
1066
-
1067
- ```javascript
1068
- const { data, error } = await supabase
1069
- .storage
1070
- .createBucket('avatars', {
1071
- public: false,
1072
- fileSizeLimit: 1024000
1073
- })
1074
- ```
1075
-
1076
- ### Update Bucket
1077
-
1078
- ```javascript
1079
- const { data, error } = await supabase
1080
- .storage
1081
- .updateBucket('avatars', {
1082
- public: true
1083
- })
1084
- ```
1085
-
1086
- ### Delete Bucket
1087
-
1088
- ```javascript
1089
- // Must empty bucket first
1090
- const { data: files } = await supabase
1091
- .storage
1092
- .from('avatars')
1093
- .list()
1094
-
1095
- await supabase
1096
- .storage
1097
- .from('avatars')
1098
- .remove(files.map(file => file.name))
1099
-
1100
- // Then delete bucket
1101
- const { data, error } = await supabase
1102
- .storage
1103
- .deleteBucket('avatars')
1104
- ```
1105
-
1106
- ### Empty Bucket
1107
-
1108
- ```javascript
1109
- const { data, error } = await supabase
1110
- .storage
1111
- .emptyBucket('avatars')
1112
- ```
1113
-
1114
- ### Upload File
1115
-
1116
- ```javascript
1117
- const file = event.target.files[0]
1118
-
1119
- const { data, error } = await supabase
1120
- .storage
1121
- .from('avatars')
1122
- .upload('public/avatar1.png', file, {
1123
- cacheControl: '3600',
1124
- upsert: false
1125
- })
1126
-
1127
- if (error) {
1128
- console.error('Error uploading:', error.message)
1129
- } else {
1130
- console.log('File path:', data.path)
1131
- }
1132
- ```
1133
-
1134
- ### Upload File with Content Type
1135
-
1136
- ```javascript
1137
- const { data, error } = await supabase
1138
- .storage
1139
- .from('avatars')
1140
- .upload('folder/file.pdf', file, {
1141
- contentType: 'application/pdf'
1142
- })
1143
- ```
1144
-
1145
- ### Upload and Overwrite Existing File
1146
-
1147
- ```javascript
1148
- const { data, error } = await supabase
1149
- .storage
1150
- .from('avatars')
1151
- .upload('public/avatar1.png', file, {
1152
- upsert: true
1153
- })
1154
- ```
1155
-
1156
- ### Upload from Base64
1157
-
1158
- ```javascript
1159
- const base64 = 'data:image/png;base64,iVBORw0KGgoAAAANS...'
1160
-
1161
- const { data, error } = await supabase
1162
- .storage
1163
- .from('avatars')
1164
- .upload('public/avatar1.png', base64, {
1165
- contentType: 'image/png'
1166
- })
1167
- ```
1168
-
1169
- ### Download File
1170
-
1171
- ```javascript
1172
- const { data, error } = await supabase
1173
- .storage
1174
- .from('avatars')
1175
- .download('folder/avatar1.png')
1176
-
1177
- if (error) {
1178
- console.error('Error downloading:', error.message)
1179
- } else {
1180
- // data is a Blob
1181
- const url = URL.createObjectURL(data)
1182
- console.log('File URL:', url)
1183
- }
1184
- ```
1185
-
1186
- ### Get Public URL
1187
-
1188
- ```javascript
1189
- const { data } = supabase
1190
- .storage
1191
- .from('avatars')
1192
- .getPublicUrl('folder/avatar1.png')
1193
-
1194
- console.log('Public URL:', data.publicUrl)
1195
- ```
1196
-
1197
- ### Get Public URL with Transform
1198
-
1199
- ```javascript
1200
- const { data } = supabase
1201
- .storage
1202
- .from('avatars')
1203
- .getPublicUrl('folder/avatar1.png', {
1204
- transform: {
1205
- width: 200,
1206
- height: 200,
1207
- resize: 'cover'
1208
- }
1209
- })
1210
- ```
1211
-
1212
- ### Create Signed URL
1213
-
1214
- ```javascript
1215
- const { data, error } = await supabase
1216
- .storage
1217
- .from('avatars')
1218
- .createSignedUrl('folder/avatar1.png', 3600)
1219
-
1220
- console.log('Signed URL:', data.signedUrl)
1221
- console.log('Expires at:', new Date(Date.now() + 3600 * 1000))
1222
- ```
1223
-
1224
- ### Create Signed URLs (Multiple)
1225
-
1226
- ```javascript
1227
- const { data, error } = await supabase
1228
- .storage
1229
- .from('avatars')
1230
- .createSignedUrls(['folder/avatar1.png', 'folder/avatar2.png'], 3600)
1231
-
1232
- console.log('Signed URLs:', data)
1233
- ```
1234
-
1235
- ### Create Signed Upload URL
1236
-
1237
- ```javascript
1238
- const { data, error } = await supabase
1239
- .storage
1240
- .from('avatars')
1241
- .createSignedUploadUrl('folder/avatar1.png')
1242
-
1243
- console.log('Upload URL:', data.signedUrl)
1244
- console.log('Upload token:', data.token)
1245
- ```
1246
-
1247
- ### Upload to Signed URL
1248
-
1249
- ```javascript
1250
- const { data: signedData } = await supabase
1251
- .storage
1252
- .from('avatars')
1253
- .createSignedUploadUrl('folder/avatar1.png')
1254
-
1255
- const { data, error } = await supabase
1256
- .storage
1257
- .from('avatars')
1258
- .uploadToSignedUrl(signedData.path, signedData.token, file)
1259
- ```
1260
-
1261
- ### List Files in Bucket
1262
-
1263
- ```javascript
1264
- const { data, error } = await supabase
1265
- .storage
1266
- .from('avatars')
1267
- .list('folder', {
1268
- limit: 100,
1269
- offset: 0,
1270
- sortBy: { column: 'name', order: 'asc' }
1271
- })
1272
-
1273
- console.log('Files:', data)
1274
- ```
1275
-
1276
- ### Search Files
1277
-
1278
- ```javascript
1279
- const { data, error } = await supabase
1280
- .storage
1281
- .from('avatars')
1282
- .list('folder', {
1283
- search: 'avatar'
1284
- })
1285
- ```
1286
-
1287
- ### Move File
1288
-
1289
- ```javascript
1290
- const { data, error } = await supabase
1291
- .storage
1292
- .from('avatars')
1293
- .move('old/path/avatar.png', 'new/path/avatar.png')
1294
- ```
1295
-
1296
- ### Copy File
1297
-
1298
- ```javascript
1299
- const { data, error } = await supabase
1300
- .storage
1301
- .from('avatars')
1302
- .copy('original/avatar.png', 'backup/avatar.png')
1303
- ```
1304
-
1305
- ### Delete Files
1306
-
1307
- ```javascript
1308
- const { data, error } = await supabase
1309
- .storage
1310
- .from('avatars')
1311
- .remove(['folder/avatar1.png', 'folder/avatar2.png'])
1312
- ```
1313
-
1314
- ### Get File Metadata
1315
-
1316
- ```javascript
1317
- const { data, error } = await supabase
1318
- .storage
1319
- .from('avatars')
1320
- .list('folder', {
1321
- limit: 1
1322
- })
1323
-
1324
- console.log('Metadata:', data[0])
1325
- ```
1326
-
1327
- ## Edge Functions
1328
-
1329
- The Supabase SDK allows invoking Edge Functions.
1330
-
1331
- ### Invoke Function
1332
-
1333
- ```javascript
1334
- const { data, error } = await supabase.functions.invoke('hello-world')
1335
-
1336
- if (error) {
1337
- console.error('Error invoking function:', error.message)
1338
- } else {
1339
- console.log('Function response:', data)
1340
- }
1341
- ```
1342
-
1343
- ### Invoke Function with Body
1344
-
1345
- ```javascript
1346
- const { data, error } = await supabase.functions.invoke('hello-world', {
1347
- body: {
1348
- name: 'JavaScript',
1349
- message: 'Hello from client'
1350
- }
1351
- })
1352
-
1353
- console.log('Response:', data)
1354
- ```
1355
-
1356
- ### Invoke Function with Custom Headers
1357
-
1358
- ```javascript
1359
- const { data, error } = await supabase.functions.invoke('hello-world', {
1360
- headers: {
1361
- 'X-Custom-Header': 'my-value'
1362
- },
1363
- body: { name: 'JavaScript' }
1364
- })
1365
- ```
1366
-
1367
- ### Invoke Function with Method
1368
-
1369
- ```javascript
1370
- const { data, error } = await supabase.functions.invoke('hello-world', {
1371
- method: 'POST',
1372
- body: { data: 'test' }
1373
- })
1374
- ```
1375
-
1376
- ### Invoke Function on Specific Region
1377
-
1378
- ```javascript
1379
- const { data, error } = await supabase.functions.invoke('hello-world', {
1380
- region: 'us-west-1'
1381
- })
1382
- ```
1383
-
1384
- ## Error Handling
1385
-
1386
- All Supabase operations return an object with `data` and `error` properties.
1387
-
1388
- ### Basic Error Handling
1389
-
1390
- ```javascript
1391
- const { data, error } = await supabase
1392
- .from('countries')
1393
- .select('*')
1394
-
1395
- if (error) {
1396
- console.error('Database error:', error.message)
1397
- console.error('Error details:', error.details)
1398
- console.error('Error hint:', error.hint)
1399
- } else {
1400
- console.log('Data:', data)
1401
- }
1402
- ```
1403
-
1404
- ### Error Properties
1405
-
1406
- ```javascript
1407
- if (error) {
1408
- console.log('Message:', error.message)
1409
- console.log('Details:', error.details)
1410
- console.log('Hint:', error.hint)
1411
- console.log('Code:', error.code)
1412
- }
1413
- ```
1414
-
1415
- ### Try-Catch with Async/Await
1416
-
1417
- ```javascript
1418
- try {
1419
- const { data, error } = await supabase
1420
- .from('countries')
1421
- .select('*')
1422
-
1423
- if (error) throw error
1424
-
1425
- console.log('Data:', data)
1426
- } catch (error) {
1427
- console.error('Error:', error.message)
1428
- }
1429
- ```
1430
-
1431
- ### Auth Error Handling
1432
-
1433
- ```javascript
1434
- const { data, error } = await supabase.auth.signInWithPassword({
1435
- email: 'user@example.com',
1436
- password: 'wrong-password'
1437
- })
1438
-
1439
- if (error) {
1440
- if (error.message.includes('Invalid login credentials')) {
1441
- console.log('Wrong email or password')
1442
- } else if (error.message.includes('Email not confirmed')) {
1443
- console.log('Please verify your email')
1444
- } else {
1445
- console.error('Auth error:', error.message)
1446
- }
1447
- }
1448
- ```
1449
-
1450
- ### Storage Error Handling
1451
-
1452
- ```javascript
1453
- const { data, error } = await supabase
1454
- .storage
1455
- .from('avatars')
1456
- .upload('file.png', file)
1457
-
1458
- if (error) {
1459
- if (error.message.includes('Duplicate')) {
1460
- console.log('File already exists')
1461
- } else if (error.message.includes('Size')) {
1462
- console.log('File too large')
1463
- } else {
1464
- console.error('Storage error:', error.message)
1465
- }
1466
- }
1467
- ```
1468
-
1469
- ## TypeScript Support
1470
-
1471
- The Supabase SDK has full TypeScript support with automatic type inference.
1472
-
1473
- ### Define Database Types
1474
-
1475
- ```typescript
1476
- import { createClient } from '@supabase/supabase-js'
1477
-
1478
- interface Database {
1479
- public: {
1480
- Tables: {
1481
- countries: {
1482
- Row: {
1483
- id: number
1484
- name: string
1485
- capital: string | null
1486
- population: number | null
1487
- }
1488
- Insert: {
1489
- id?: number
1490
- name: string
1491
- capital?: string | null
1492
- population?: number | null
1493
- }
1494
- Update: {
1495
- id?: number
1496
- name?: string
1497
- capital?: string | null
1498
- population?: number | null
1499
- }
1500
- }
1501
- }
1502
- }
1503
- }
1504
-
1505
- const supabase = createClient<Database>(supabaseUrl, supabaseAnonKey)
1506
- ```
1507
-
1508
- ### Type-Safe Queries
1509
-
1510
- ```typescript
1511
- const { data, error } = await supabase
1512
- .from('countries')
1513
- .select('*')
1514
-
1515
- // data is automatically typed as Database['public']['Tables']['countries']['Row'][]
1516
- ```
1517
-
1518
- ### Generate Types from Database
1519
-
1520
- Use the Supabase CLI to generate types:
1521
-
1522
- ```bash
1523
- supabase gen types typescript --project-id your-project-id > types/supabase.ts
1524
- ```
1525
-
1526
- Then import and use:
1527
-
1528
- ```typescript
1529
- import { Database } from './types/supabase'
1530
-
1531
- const supabase = createClient<Database>(supabaseUrl, supabaseAnonKey)
1532
- ```
1533
-
1534
- ## Advanced Configuration
1535
-
1536
- ### Custom Fetch Implementation
1537
-
1538
- ```javascript
1539
- import { createClient } from '@supabase/supabase-js'
1540
-
1541
- const supabase = createClient(supabaseUrl, supabaseAnonKey, {
1542
- global: {
1543
- fetch: customFetch
1544
- }
1545
- })
1546
- ```
1547
-
1548
- ### Custom Headers
1549
-
1550
- ```javascript
1551
- const supabase = createClient(supabaseUrl, supabaseAnonKey, {
1552
- global: {
1553
- headers: {
1554
- 'x-application-name': 'my-app'
1555
- }
1556
- }
1557
- })
1558
- ```
1559
-
1560
- ### Schema Configuration
1561
-
1562
- ```javascript
1563
- const supabase = createClient(supabaseUrl, supabaseAnonKey, {
1564
- db: {
1565
- schema: 'private'
1566
- }
1567
- })
1568
- ```
1569
-
1570
- ### Auth Configuration
1571
-
1572
- ```javascript
1573
- const supabase = createClient(supabaseUrl, supabaseAnonKey, {
1574
- auth: {
1575
- autoRefreshToken: true,
1576
- persistSession: true,
1577
- detectSessionInUrl: true,
1578
- storage: customStorageAdapter,
1579
- storageKey: 'my-app-auth-token',
1580
- flowType: 'pkce'
1581
- }
1582
- })
1583
- ```
1584
-
1585
- ### Realtime Configuration
1586
-
1587
- ```javascript
1588
- const supabase = createClient(supabaseUrl, supabaseAnonKey, {
1589
- realtime: {
1590
- params: {
1591
- eventsPerSecond: 10
1592
- }
1593
- }
1594
- })
1595
- ```
1596
-
1597
- ## Useful Links
1598
-
1599
- - Documentation: https://supabase.com/docs
1600
- - JavaScript SDK Reference: https://supabase.com/docs/reference/javascript/introduction
1601
- - API Reference: https://supabase.com/docs/guides/api
1602
- - Auth Documentation: https://supabase.com/docs/guides/auth
1603
- - Database Documentation: https://supabase.com/docs/guides/database
1604
- - Storage Documentation: https://supabase.com/docs/guides/storage
1605
- - Realtime Documentation: https://supabase.com/docs/guides/realtime
1606
- - Edge Functions: https://supabase.com/docs/guides/functions