@intentsolutionsio/supabase-pack 1.0.0 → 1.0.3
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.
- package/LICENSE +1 -1
- package/README.md +73 -47
- package/package.json +4 -4
- package/skills/supabase-advanced-troubleshooting/SKILL.md +404 -200
- package/skills/supabase-advanced-troubleshooting/references/errors.md +11 -0
- package/skills/supabase-advanced-troubleshooting/references/evidence-collection-framework.md +34 -0
- package/skills/supabase-advanced-troubleshooting/references/examples.md +11 -0
- package/skills/supabase-advanced-troubleshooting/references/rls-edge-functions-realtime.md +363 -0
- package/skills/supabase-advanced-troubleshooting/references/systematic-isolation.md +56 -0
- package/skills/supabase-advanced-troubleshooting/references/timing-analysis.md +35 -0
- package/skills/supabase-architecture-variants/SKILL.md +395 -216
- package/skills/supabase-architecture-variants/references/errors.md +11 -0
- package/skills/supabase-architecture-variants/references/examples.md +12 -0
- package/skills/supabase-architecture-variants/references/serverless-and-multi-tenant.md +251 -0
- package/skills/supabase-architecture-variants/references/variant-a-monolith-(simple).md +44 -0
- package/skills/supabase-architecture-variants/references/variant-b-service-layer-(moderate).md +72 -0
- package/skills/supabase-architecture-variants/references/variant-c-microservice-(complex).md +81 -0
- package/skills/supabase-auth-storage-realtime-core/SKILL.md +471 -37
- package/skills/supabase-ci-integration/SKILL.md +315 -67
- package/skills/supabase-ci-integration/references/errors.md +10 -0
- package/skills/supabase-ci-integration/references/examples.md +36 -0
- package/skills/supabase-ci-integration/references/implementation.md +54 -0
- package/skills/supabase-common-errors/SKILL.md +320 -62
- package/skills/supabase-common-errors/references/errors.md +53 -0
- package/skills/supabase-common-errors/references/examples.md +23 -0
- package/skills/supabase-cost-tuning/SKILL.md +365 -131
- package/skills/supabase-cost-tuning/references/cost-estimation.md +34 -0
- package/skills/supabase-cost-tuning/references/cost-reduction-strategies.md +40 -0
- package/skills/supabase-cost-tuning/references/errors.md +11 -0
- package/skills/supabase-cost-tuning/references/examples.md +15 -0
- package/skills/supabase-data-handling/SKILL.md +378 -145
- package/skills/supabase-data-handling/references/errors.md +11 -0
- package/skills/supabase-data-handling/references/examples.md +27 -0
- package/skills/supabase-data-handling/references/implementation.md +223 -0
- package/skills/supabase-data-handling/references/retention-and-backup.md +221 -0
- package/skills/supabase-debug-bundle/SKILL.md +267 -73
- package/skills/supabase-debug-bundle/references/errors.md +12 -0
- package/skills/supabase-debug-bundle/references/examples.md +24 -0
- package/skills/supabase-debug-bundle/references/implementation.md +54 -0
- package/skills/supabase-deploy-integration/SKILL.md +258 -147
- package/skills/supabase-deploy-integration/references/errors.md +11 -0
- package/skills/supabase-deploy-integration/references/examples.md +21 -0
- package/skills/supabase-deploy-integration/references/google-cloud-run.md +36 -0
- package/skills/supabase-deploy-integration/references/vercel-deployment.md +35 -0
- package/skills/supabase-enterprise-rbac/SKILL.md +327 -160
- package/skills/supabase-enterprise-rbac/references/api-scoping-and-enforcement.md +255 -0
- package/skills/supabase-enterprise-rbac/references/errors.md +11 -0
- package/skills/supabase-enterprise-rbac/references/examples.md +12 -0
- package/skills/supabase-enterprise-rbac/references/role-implementation.md +33 -0
- package/skills/supabase-enterprise-rbac/references/sso-integration.md +35 -0
- package/skills/supabase-hello-world/SKILL.md +160 -54
- package/skills/supabase-incident-runbook/SKILL.md +453 -131
- package/skills/supabase-incident-runbook/references/errors.md +11 -0
- package/skills/supabase-incident-runbook/references/examples.md +10 -0
- package/skills/supabase-incident-runbook/references/immediate-actions-by-error-type.md +41 -0
- package/skills/supabase-install-auth/SKILL.md +186 -50
- package/skills/supabase-install-auth/references/examples.md +102 -0
- package/skills/supabase-known-pitfalls/SKILL.md +411 -241
- package/skills/supabase-known-pitfalls/references/errors.md +11 -0
- package/skills/supabase-known-pitfalls/references/examples.md +12 -0
- package/skills/supabase-load-scale/SKILL.md +346 -217
- package/skills/supabase-load-scale/references/capacity-planning.md +47 -0
- package/skills/supabase-load-scale/references/errors.md +11 -0
- package/skills/supabase-load-scale/references/examples.md +26 -0
- package/skills/supabase-load-scale/references/load-testing-with-k6.md +59 -0
- package/skills/supabase-load-scale/references/scaling-patterns.md +65 -0
- package/skills/supabase-load-scale/references/table-partitioning.md +263 -0
- package/skills/supabase-local-dev-loop/SKILL.md +272 -73
- package/skills/supabase-local-dev-loop/references/errors.md +11 -0
- package/skills/supabase-local-dev-loop/references/examples.md +21 -0
- package/skills/supabase-local-dev-loop/references/implementation.md +60 -0
- package/skills/supabase-migration-deep-dive/SKILL.md +338 -177
- package/skills/supabase-migration-deep-dive/references/backfill-versioning-rollback.md +258 -0
- package/skills/supabase-migration-deep-dive/references/errors.md +11 -0
- package/skills/supabase-migration-deep-dive/references/examples.md +12 -0
- package/skills/supabase-migration-deep-dive/references/implementation-plan.md +80 -0
- package/skills/supabase-migration-deep-dive/references/pre-migration-assessment.md +39 -0
- package/skills/supabase-multi-env-setup/SKILL.md +393 -152
- package/skills/supabase-multi-env-setup/references/configuration-structure.md +59 -0
- package/skills/supabase-multi-env-setup/references/errors.md +11 -0
- package/skills/supabase-multi-env-setup/references/examples.md +11 -0
- package/skills/supabase-observability/SKILL.md +318 -196
- package/skills/supabase-observability/references/alert-configuration.md +40 -0
- package/skills/supabase-observability/references/errors.md +11 -0
- package/skills/supabase-observability/references/examples.md +13 -0
- package/skills/supabase-observability/references/metrics-collection.md +65 -0
- package/skills/supabase-performance-tuning/SKILL.md +304 -160
- package/skills/supabase-performance-tuning/references/caching-strategy.md +49 -0
- package/skills/supabase-performance-tuning/references/errors.md +11 -0
- package/skills/supabase-performance-tuning/references/examples.md +13 -0
- package/skills/supabase-policy-guardrails/SKILL.md +248 -221
- package/skills/supabase-policy-guardrails/references/ci-cost-security.md +484 -0
- package/skills/supabase-policy-guardrails/references/errors.md +11 -0
- package/skills/supabase-policy-guardrails/references/eslint-rules.md +46 -0
- package/skills/supabase-policy-guardrails/references/examples.md +10 -0
- package/skills/supabase-prod-checklist/SKILL.md +474 -84
- package/skills/supabase-prod-checklist/references/errors.md +63 -0
- package/skills/supabase-prod-checklist/references/examples.md +153 -0
- package/skills/supabase-prod-checklist/references/implementation.md +113 -0
- package/skills/supabase-rate-limits/SKILL.md +311 -98
- package/skills/supabase-rate-limits/references/errors.md +11 -0
- package/skills/supabase-rate-limits/references/examples.md +46 -0
- package/skills/supabase-rate-limits/references/implementation.md +66 -0
- package/skills/supabase-reference-architecture/SKILL.md +249 -182
- package/skills/supabase-reference-architecture/references/errors.md +29 -0
- package/skills/supabase-reference-architecture/references/examples.md +116 -0
- package/skills/supabase-reference-architecture/references/key-components.md +244 -0
- package/skills/supabase-reference-architecture/references/project-structure.md +109 -0
- package/skills/supabase-reliability-patterns/SKILL.md +229 -234
- package/skills/supabase-reliability-patterns/references/circuit-breaker.md +36 -0
- package/skills/supabase-reliability-patterns/references/dead-letter-queue.md +48 -0
- package/skills/supabase-reliability-patterns/references/errors.md +11 -0
- package/skills/supabase-reliability-patterns/references/examples.md +11 -0
- package/skills/supabase-reliability-patterns/references/idempotency-keys.md +36 -0
- package/skills/supabase-reliability-patterns/references/offline-degradation-health-dualwrite.md +489 -0
- package/skills/supabase-schema-from-requirements/SKILL.md +373 -34
- package/skills/supabase-sdk-patterns/SKILL.md +388 -99
- package/skills/supabase-sdk-patterns/references/errors.md +11 -0
- package/skills/supabase-sdk-patterns/references/examples.md +45 -0
- package/skills/supabase-sdk-patterns/references/implementation.md +67 -0
- package/skills/supabase-security-basics/SKILL.md +282 -102
- package/skills/supabase-security-basics/references/errors.md +10 -0
- package/skills/supabase-security-basics/references/examples.md +70 -0
- package/skills/supabase-security-basics/references/implementation.md +39 -0
- package/skills/supabase-upgrade-migration/SKILL.md +248 -66
- package/skills/supabase-upgrade-migration/references/errors.md +10 -0
- package/skills/supabase-upgrade-migration/references/examples.md +51 -0
- package/skills/supabase-upgrade-migration/references/implementation.md +29 -0
- package/skills/supabase-webhooks-events/SKILL.md +412 -138
- package/skills/supabase-webhooks-events/references/errors.md +55 -0
- package/skills/supabase-webhooks-events/references/event-handler-pattern.md +106 -0
- package/skills/supabase-webhooks-events/references/examples.md +133 -0
- package/skills/supabase-webhooks-events/references/signature-verification.md +165 -0
|
@@ -1,73 +1,507 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: supabase-auth-storage-realtime-core
|
|
3
|
-
description:
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
description: 'Implement Supabase Auth (signUp, signIn, OAuth, session management),
|
|
4
|
+
Storage
|
|
5
|
+
|
|
6
|
+
(upload, download, signed URLs, bucket policies), and Realtime (Postgres changes,
|
|
7
|
+
|
|
8
|
+
broadcast, presence). Use when building user auth flows, file upload features,
|
|
9
|
+
|
|
10
|
+
or live-updating UIs with Supabase. Trigger with phrases like "supabase auth",
|
|
11
|
+
|
|
12
|
+
"supabase storage upload", "supabase realtime subscribe", "supabase oauth",
|
|
13
|
+
|
|
14
|
+
"supabase file upload", "supabase presence", "supabase rls storage".
|
|
15
|
+
|
|
16
|
+
'
|
|
17
|
+
allowed-tools: Read, Write, Edit, Bash(npm:*), Bash(supabase:*), Grep
|
|
10
18
|
version: 1.0.0
|
|
11
19
|
license: MIT
|
|
12
20
|
author: Jeremy Longshore <jeremy@intentsolutions.io>
|
|
21
|
+
tags:
|
|
22
|
+
- saas
|
|
23
|
+
- supabase
|
|
24
|
+
- auth
|
|
25
|
+
- storage
|
|
26
|
+
- realtime
|
|
27
|
+
- rls
|
|
28
|
+
compatibility: Designed for Claude Code, also compatible with Cursor
|
|
13
29
|
---
|
|
14
|
-
|
|
15
|
-
# Supabase Auth + Storage + Realtime
|
|
30
|
+
# Supabase Auth + Storage + Realtime Core
|
|
16
31
|
|
|
17
32
|
## Overview
|
|
18
|
-
Implement the core Supabase trifecta: authentication, file storage,
|
|
19
|
-
and real-time subscriptions in a single cohesive setup.
|
|
20
33
|
|
|
34
|
+
Implement the three pillars that turn a Supabase database into a full application backend: user authentication (email/password, OAuth, magic links, session lifecycle), file storage (uploads, downloads, signed URLs, bucket-level RLS policies), and real-time subscriptions (Postgres change events, client-to-client broadcast, presence tracking). Every operation integrates with Row-Level Security through `auth.uid()`.
|
|
21
35
|
|
|
22
36
|
## Prerequisites
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
37
|
+
|
|
38
|
+
- Supabase project created at [supabase.com/dashboard](https://supabase.com/dashboard)
|
|
39
|
+
- `@supabase/supabase-js` v2 installed (`npm install @supabase/supabase-js`)
|
|
40
|
+
- `SUPABASE_URL` and `SUPABASE_ANON_KEY` available from project Settings > API
|
|
41
|
+
- For Python: `pip install supabase` (wraps `postgrest-py`, `gotrue-py`, `storage3`, `realtime-py`)
|
|
26
42
|
|
|
27
43
|
## Instructions
|
|
28
44
|
|
|
29
|
-
### Step 1:
|
|
45
|
+
### Step 1: Auth — User Registration, Login, and OAuth
|
|
46
|
+
|
|
47
|
+
Initialize the client and implement the three primary auth flows: email/password, OAuth provider, and passwordless magic link.
|
|
48
|
+
|
|
49
|
+
**TypeScript**
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { createClient } from '@supabase/supabase-js'
|
|
53
|
+
|
|
54
|
+
const supabase = createClient(
|
|
55
|
+
process.env.SUPABASE_URL!,
|
|
56
|
+
process.env.SUPABASE_ANON_KEY!
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
// ── Sign up a new user ──
|
|
60
|
+
const { data: signUpData, error: signUpError } = await supabase.auth.signUp({
|
|
61
|
+
email: 'user@example.com',
|
|
62
|
+
password: 'secure-password-123',
|
|
63
|
+
options: {
|
|
64
|
+
data: { username: 'newuser', full_name: 'New User' }, // → raw_user_meta_data
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
// If email confirmation enabled: data.user exists but data.session is null
|
|
68
|
+
// If email confirmation disabled: both data.user and data.session are present
|
|
69
|
+
|
|
70
|
+
// ── Sign in with password ──
|
|
71
|
+
const { data: signInData, error: signInError } = await supabase.auth.signInWithPassword({
|
|
72
|
+
email: 'user@example.com',
|
|
73
|
+
password: 'secure-password-123',
|
|
74
|
+
})
|
|
75
|
+
const { user, session } = signInData
|
|
76
|
+
// session.access_token → JWT for authenticated API calls
|
|
77
|
+
|
|
78
|
+
// ── Sign in with OAuth (Google) ──
|
|
79
|
+
const { data: oauthData, error: oauthError } = await supabase.auth.signInWithOAuth({
|
|
80
|
+
provider: 'google',
|
|
81
|
+
options: {
|
|
82
|
+
redirectTo: 'https://myapp.com/auth/callback',
|
|
83
|
+
queryParams: { access_type: 'offline', prompt: 'consent' },
|
|
84
|
+
},
|
|
85
|
+
})
|
|
86
|
+
// Redirect user to oauthData.url in the browser
|
|
87
|
+
|
|
88
|
+
// ── Sign in with GitHub ──
|
|
89
|
+
const { data, error } = await supabase.auth.signInWithOAuth({
|
|
90
|
+
provider: 'github',
|
|
91
|
+
options: { redirectTo: 'https://myapp.com/auth/callback' },
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
// ── Passwordless magic link ──
|
|
95
|
+
const { error: otpError } = await supabase.auth.signInWithOtp({
|
|
96
|
+
email: 'user@example.com',
|
|
97
|
+
options: { emailRedirectTo: 'https://myapp.com/auth/callback' },
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
// ── Handle OAuth/magic link callback (in /auth/callback route) ──
|
|
101
|
+
const { data: { session: cbSession }, error: cbError } =
|
|
102
|
+
await supabase.auth.exchangeCodeForSession(code)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Session management — every app needs these:**
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
// Get current session (reads from local storage, no network call)
|
|
109
|
+
const { data: { session } } = await supabase.auth.getSession()
|
|
110
|
+
|
|
111
|
+
// Get current user (validates JWT against server)
|
|
112
|
+
const { data: { user } } = await supabase.auth.getUser()
|
|
113
|
+
|
|
114
|
+
// Listen for auth state changes — critical for reactive UIs
|
|
115
|
+
const { data: { subscription } } = supabase.auth.onAuthStateChange(
|
|
116
|
+
(event, session) => {
|
|
117
|
+
// event: 'SIGNED_IN' | 'SIGNED_OUT' | 'TOKEN_REFRESHED' | 'USER_UPDATED'
|
|
118
|
+
// 'INITIAL_SESSION' | 'PASSWORD_RECOVERY' | 'MFA_CHALLENGE_VERIFIED'
|
|
119
|
+
console.log('Auth event:', event, session?.user?.email)
|
|
120
|
+
}
|
|
121
|
+
)
|
|
122
|
+
// Clean up when component unmounts
|
|
123
|
+
subscription.unsubscribe()
|
|
124
|
+
|
|
125
|
+
// Sign out (clears session from storage)
|
|
126
|
+
await supabase.auth.signOut()
|
|
127
|
+
|
|
128
|
+
// Password reset (sends email with reset link)
|
|
129
|
+
await supabase.auth.resetPasswordForEmail('user@example.com', {
|
|
130
|
+
redirectTo: 'https://myapp.com/auth/reset-password',
|
|
131
|
+
})
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Python**
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from supabase import create_client
|
|
138
|
+
|
|
139
|
+
supabase = create_client(
|
|
140
|
+
"https://your-project.supabase.co",
|
|
141
|
+
"your-anon-key"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Sign up
|
|
145
|
+
result = supabase.auth.sign_up({
|
|
146
|
+
"email": "user@example.com",
|
|
147
|
+
"password": "secure-password-123",
|
|
148
|
+
"options": {"data": {"username": "newuser"}},
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
# Sign in with password
|
|
152
|
+
result = supabase.auth.sign_in_with_password({
|
|
153
|
+
"email": "user@example.com",
|
|
154
|
+
"password": "secure-password-123",
|
|
155
|
+
})
|
|
156
|
+
access_token = result.session.access_token
|
|
157
|
+
|
|
158
|
+
# Get current session
|
|
159
|
+
session = supabase.auth.get_session()
|
|
160
|
+
|
|
161
|
+
# Sign out
|
|
162
|
+
supabase.auth.sign_out()
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Step 2: Storage — Upload, Download, and Secure with Bucket Policies
|
|
166
|
+
|
|
167
|
+
Supabase Storage organizes files into buckets. Public buckets serve files via CDN URLs; private buckets require signed URLs or authenticated requests.
|
|
168
|
+
|
|
169
|
+
**TypeScript**
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
// ── Upload a file ──
|
|
173
|
+
const file = new File(['hello world'], 'hello.txt', { type: 'text/plain' })
|
|
174
|
+
const { data, error } = await supabase.storage
|
|
175
|
+
.from('avatars') // bucket name
|
|
176
|
+
.upload('user123/avatar.png', file, {
|
|
177
|
+
cacheControl: '3600',
|
|
178
|
+
upsert: false, // true → overwrite existing
|
|
179
|
+
contentType: 'image/png',
|
|
180
|
+
})
|
|
181
|
+
// data.path → 'user123/avatar.png'
|
|
182
|
+
|
|
183
|
+
// ── Download a file ──
|
|
184
|
+
const { data: blob, error: dlError } = await supabase.storage
|
|
185
|
+
.from('avatars')
|
|
186
|
+
.download('user123/avatar.png')
|
|
187
|
+
// blob is a Blob object — use URL.createObjectURL(blob) for display
|
|
188
|
+
|
|
189
|
+
// ── Get public URL (public buckets only, no auth required) ──
|
|
190
|
+
const { data: { publicUrl } } = supabase.storage
|
|
191
|
+
.from('avatars')
|
|
192
|
+
.getPublicUrl('user123/avatar.png')
|
|
193
|
+
// publicUrl → 'https://<project>.supabase.co/storage/v1/object/public/avatars/user123/avatar.png'
|
|
194
|
+
|
|
195
|
+
// ── Create signed URL (private buckets, time-limited access) ──
|
|
196
|
+
const { data: signedUrlData, error: signError } = await supabase.storage
|
|
197
|
+
.from('documents')
|
|
198
|
+
.createSignedUrl('reports/q4-2025.pdf', 3600) // expires in 1 hour
|
|
199
|
+
// signedUrlData.signedUrl → one-time use URL with token parameter
|
|
200
|
+
|
|
201
|
+
// ── List files in a path ──
|
|
202
|
+
const { data: files, error: listError } = await supabase.storage
|
|
203
|
+
.from('documents')
|
|
204
|
+
.list('reports', {
|
|
205
|
+
limit: 100,
|
|
206
|
+
offset: 0,
|
|
207
|
+
sortBy: { column: 'name', order: 'asc' },
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
// ── Delete files ──
|
|
211
|
+
const { error: removeError } = await supabase.storage
|
|
212
|
+
.from('documents')
|
|
213
|
+
.remove(['reports/old-report.pdf', 'reports/draft.docx'])
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**Bucket RLS policies — enforce access control in SQL migrations:**
|
|
217
|
+
|
|
218
|
+
```sql
|
|
219
|
+
-- Create buckets (run in a migration or SQL editor)
|
|
220
|
+
INSERT INTO storage.buckets (id, name, public)
|
|
221
|
+
VALUES ('avatars', 'avatars', true); -- public: anyone can read
|
|
222
|
+
|
|
223
|
+
INSERT INTO storage.buckets (id, name, public)
|
|
224
|
+
VALUES ('documents', 'documents', false); -- private: signed URLs only
|
|
225
|
+
|
|
226
|
+
-- Allow authenticated users to upload to their own folder
|
|
227
|
+
-- Convention: store files at <user_id>/filename.ext
|
|
228
|
+
CREATE POLICY "avatar_upload"
|
|
229
|
+
ON storage.objects FOR INSERT
|
|
230
|
+
WITH CHECK (
|
|
231
|
+
bucket_id = 'avatars'
|
|
232
|
+
AND auth.uid()::text = (storage.foldername(name))[1]
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
-- Allow anyone to view avatars (public bucket)
|
|
236
|
+
CREATE POLICY "avatar_public_read"
|
|
237
|
+
ON storage.objects FOR SELECT
|
|
238
|
+
USING (bucket_id = 'avatars');
|
|
239
|
+
|
|
240
|
+
-- Allow users to manage only their own documents (all operations)
|
|
241
|
+
CREATE POLICY "documents_user_crud"
|
|
242
|
+
ON storage.objects FOR ALL
|
|
243
|
+
USING (
|
|
244
|
+
bucket_id = 'documents'
|
|
245
|
+
AND auth.uid()::text = (storage.foldername(name))[1]
|
|
246
|
+
)
|
|
247
|
+
WITH CHECK (
|
|
248
|
+
bucket_id = 'documents'
|
|
249
|
+
AND auth.uid()::text = (storage.foldername(name))[1]
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
-- Allow users to delete only files they uploaded
|
|
253
|
+
CREATE POLICY "documents_owner_delete"
|
|
254
|
+
ON storage.objects FOR DELETE
|
|
255
|
+
USING (
|
|
256
|
+
bucket_id = 'documents'
|
|
257
|
+
AND auth.uid() = owner
|
|
258
|
+
);
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**Python**
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
# Upload
|
|
265
|
+
with open("report.pdf", "rb") as f:
|
|
266
|
+
result = supabase.storage.from_("documents").upload(
|
|
267
|
+
"user123/report.pdf", f,
|
|
268
|
+
{"content-type": "application/pdf", "cache-control": "3600"}
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# Download
|
|
272
|
+
data = supabase.storage.from_("documents").download("user123/report.pdf")
|
|
273
|
+
|
|
274
|
+
# Public URL
|
|
275
|
+
url = supabase.storage.from_("avatars").get_public_url("user123/avatar.png")
|
|
276
|
+
|
|
277
|
+
# Signed URL (3600 seconds)
|
|
278
|
+
result = supabase.storage.from_("documents").create_signed_url(
|
|
279
|
+
"user123/report.pdf", 3600
|
|
280
|
+
)
|
|
281
|
+
signed_url = result["signedURL"]
|
|
282
|
+
|
|
283
|
+
# List files
|
|
284
|
+
files = supabase.storage.from_("documents").list("user123")
|
|
285
|
+
|
|
286
|
+
# Delete
|
|
287
|
+
supabase.storage.from_("documents").remove(["user123/old-file.pdf"])
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Step 3: Realtime — Postgres Changes, Broadcast, and Presence
|
|
291
|
+
|
|
292
|
+
Supabase Realtime provides three channel types: database change listeners, client-to-client broadcast, and presence tracking for online status.
|
|
293
|
+
|
|
294
|
+
**Postgres Changes (listen to INSERT/UPDATE/DELETE on tables):**
|
|
295
|
+
|
|
30
296
|
```typescript
|
|
31
|
-
//
|
|
297
|
+
// Subscribe to new messages in a chat table
|
|
298
|
+
const channel = supabase
|
|
299
|
+
.channel('chat-room')
|
|
300
|
+
.on(
|
|
301
|
+
'postgres_changes',
|
|
302
|
+
{
|
|
303
|
+
event: 'INSERT',
|
|
304
|
+
schema: 'public',
|
|
305
|
+
table: 'messages',
|
|
306
|
+
},
|
|
307
|
+
(payload) => {
|
|
308
|
+
console.log('New message:', payload.new)
|
|
309
|
+
// payload.new → full row data
|
|
310
|
+
// payload.old → null for INSERT
|
|
311
|
+
}
|
|
312
|
+
)
|
|
313
|
+
.on(
|
|
314
|
+
'postgres_changes',
|
|
315
|
+
{
|
|
316
|
+
event: 'UPDATE',
|
|
317
|
+
schema: 'public',
|
|
318
|
+
table: 'messages',
|
|
319
|
+
filter: 'room_id=eq.42', // server-side filter
|
|
320
|
+
},
|
|
321
|
+
(payload) => {
|
|
322
|
+
console.log('Updated:', payload.old, '→', payload.new)
|
|
323
|
+
}
|
|
324
|
+
)
|
|
325
|
+
.on(
|
|
326
|
+
'postgres_changes',
|
|
327
|
+
{
|
|
328
|
+
event: 'DELETE',
|
|
329
|
+
schema: 'public',
|
|
330
|
+
table: 'messages',
|
|
331
|
+
},
|
|
332
|
+
(payload) => {
|
|
333
|
+
console.log('Deleted:', payload.old)
|
|
334
|
+
// payload.new → null for DELETE
|
|
335
|
+
}
|
|
336
|
+
)
|
|
337
|
+
.subscribe((status) => {
|
|
338
|
+
console.log('Channel status:', status)
|
|
339
|
+
// 'SUBSCRIBED' | 'TIMED_OUT' | 'CLOSED' | 'CHANNEL_ERROR'
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
// Enable Realtime on the table (required one-time setup in SQL)
|
|
343
|
+
// ALTER PUBLICATION supabase_realtime ADD TABLE messages;
|
|
344
|
+
|
|
345
|
+
// Unsubscribe when done
|
|
346
|
+
supabase.removeChannel(channel)
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
**RLS integration — Realtime respects row-level security:**
|
|
350
|
+
|
|
351
|
+
```sql
|
|
352
|
+
-- Only receive changes for rows the user owns
|
|
353
|
+
CREATE POLICY "users_own_messages"
|
|
354
|
+
ON messages FOR SELECT
|
|
355
|
+
USING (auth.uid() = user_id);
|
|
356
|
+
|
|
357
|
+
-- The Realtime listener will only fire for rows passing this policy
|
|
32
358
|
```
|
|
33
359
|
|
|
34
|
-
|
|
360
|
+
**Broadcast (client-to-client, no database involved):**
|
|
361
|
+
|
|
35
362
|
```typescript
|
|
36
|
-
|
|
363
|
+
const room = supabase.channel('collab-room')
|
|
364
|
+
|
|
365
|
+
// Listen for cursor movements from other users
|
|
366
|
+
room.on('broadcast', { event: 'cursor-move' }, ({ payload }) => {
|
|
367
|
+
console.log(`User ${payload.userId} at (${payload.x}, ${payload.y})`)
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
// Subscribe first, then send
|
|
371
|
+
room.subscribe((status) => {
|
|
372
|
+
if (status === 'SUBSCRIBED') {
|
|
373
|
+
// Send cursor position to all other clients
|
|
374
|
+
room.send({
|
|
375
|
+
type: 'broadcast',
|
|
376
|
+
event: 'cursor-move',
|
|
377
|
+
payload: { userId: 'abc', x: 120, y: 340 },
|
|
378
|
+
})
|
|
379
|
+
}
|
|
380
|
+
})
|
|
37
381
|
```
|
|
38
382
|
|
|
39
|
-
|
|
383
|
+
**Presence (track who is online):**
|
|
384
|
+
|
|
40
385
|
```typescript
|
|
41
|
-
|
|
386
|
+
const room = supabase.channel('app-presence')
|
|
387
|
+
|
|
388
|
+
// Sync event fires whenever the presence state changes
|
|
389
|
+
room.on('presence', { event: 'sync' }, () => {
|
|
390
|
+
const state = room.presenceState()
|
|
391
|
+
// state → { 'user-abc': [{ user_id: 'abc', online_at: '...' }], ... }
|
|
392
|
+
const onlineUsers = Object.keys(state)
|
|
393
|
+
console.log('Online:', onlineUsers.length, 'users')
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
room.on('presence', { event: 'join' }, ({ key, newPresences }) => {
|
|
397
|
+
console.log('Joined:', key, newPresences)
|
|
398
|
+
})
|
|
399
|
+
|
|
400
|
+
room.on('presence', { event: 'leave' }, ({ key, leftPresences }) => {
|
|
401
|
+
console.log('Left:', key, leftPresences)
|
|
402
|
+
})
|
|
403
|
+
|
|
404
|
+
// Subscribe and track this user's presence
|
|
405
|
+
room.subscribe(async (status) => {
|
|
406
|
+
if (status === 'SUBSCRIBED') {
|
|
407
|
+
await room.track({
|
|
408
|
+
user_id: currentUser.id,
|
|
409
|
+
username: currentUser.email,
|
|
410
|
+
online_at: new Date().toISOString(),
|
|
411
|
+
})
|
|
412
|
+
}
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
// Untrack when leaving (e.g., on component unmount)
|
|
416
|
+
await room.untrack()
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
**Python Realtime:**
|
|
420
|
+
|
|
421
|
+
```python
|
|
422
|
+
# Python realtime uses callbacks (requires running event loop)
|
|
423
|
+
def handle_insert(payload):
|
|
424
|
+
print("New row:", payload["new"])
|
|
425
|
+
|
|
426
|
+
channel = supabase.channel("room")
|
|
427
|
+
channel.on_postgres_changes(
|
|
428
|
+
event="INSERT",
|
|
429
|
+
schema="public",
|
|
430
|
+
table="messages",
|
|
431
|
+
callback=handle_insert,
|
|
432
|
+
)
|
|
433
|
+
channel.subscribe()
|
|
434
|
+
|
|
435
|
+
# When done
|
|
436
|
+
supabase.remove_channel(channel)
|
|
42
437
|
```
|
|
43
438
|
|
|
44
439
|
## Output
|
|
45
|
-
|
|
46
|
-
-
|
|
47
|
-
-
|
|
440
|
+
|
|
441
|
+
- Auth: user registration, password login, OAuth flow (Google/GitHub), magic link, session lifecycle with `onAuthStateChange`, password reset
|
|
442
|
+
- Storage: file upload/download, public URLs for CDN-served assets, time-limited signed URLs for private files, bucket-level RLS policies using `auth.uid()` and `storage.foldername()`
|
|
443
|
+
- Realtime: Postgres change subscriptions with server-side filters, broadcast channels for client-to-client messaging, presence tracking for online status
|
|
48
444
|
|
|
49
445
|
## Error Handling
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
|
54
|
-
|
|
|
446
|
+
|
|
447
|
+
| Error | Cause | Solution |
|
|
448
|
+
|-------|-------|----------|
|
|
449
|
+
| `AuthApiError: User already registered` | Duplicate email signup | Use `signInWithPassword` or check existence first |
|
|
450
|
+
| `AuthApiError: Invalid login credentials` | Wrong email or password | Verify credentials; check email confirmation status |
|
|
451
|
+
| `AuthApiError: Email not confirmed` | User has not clicked confirmation link | Resend with `resend({ type: 'signup', email })` |
|
|
452
|
+
| `StorageApiError: Bucket not found` | Bucket does not exist | Create via dashboard or `INSERT INTO storage.buckets` |
|
|
453
|
+
| `StorageApiError: new row violates row-level security` | RLS policy blocking the operation | Verify `storage.objects` policies match the user and bucket |
|
|
454
|
+
| `StorageApiError: The resource already exists` | File exists and `upsert: false` | Set `upsert: true` to overwrite or use a unique path |
|
|
455
|
+
| `Realtime: channel error` or `TIMED_OUT` | Network issues or Realtime not enabled | Check `ALTER PUBLICATION supabase_realtime ADD TABLE <name>` |
|
|
456
|
+
| `Realtime: too many channels` | Exceeded concurrent channel limit | Unsubscribe unused channels with `removeChannel()` |
|
|
55
457
|
|
|
56
458
|
## Examples
|
|
57
459
|
|
|
58
|
-
|
|
59
|
-
```typescript
|
|
60
|
-
// Complete workflow example
|
|
61
|
-
```
|
|
460
|
+
**Full auth + protected upload flow:**
|
|
62
461
|
|
|
63
|
-
### Error Recovery
|
|
64
462
|
```typescript
|
|
65
|
-
//
|
|
463
|
+
// 1. Sign in
|
|
464
|
+
const { data: { session } } = await supabase.auth.signInWithPassword({
|
|
465
|
+
email: 'user@example.com',
|
|
466
|
+
password: 'secure-password-123',
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
// 2. Upload avatar to user's folder (RLS enforces ownership)
|
|
470
|
+
const { data } = await supabase.storage
|
|
471
|
+
.from('avatars')
|
|
472
|
+
.upload(`${session.user.id}/avatar.png`, file, { upsert: true })
|
|
473
|
+
|
|
474
|
+
// 3. Get public URL for display
|
|
475
|
+
const { data: { publicUrl } } = supabase.storage
|
|
476
|
+
.from('avatars')
|
|
477
|
+
.getPublicUrl(`${session.user.id}/avatar.png`)
|
|
478
|
+
|
|
479
|
+
// 4. Subscribe to profile changes in real time
|
|
480
|
+
const channel = supabase
|
|
481
|
+
.channel('profile-updates')
|
|
482
|
+
.on('postgres_changes', {
|
|
483
|
+
event: 'UPDATE',
|
|
484
|
+
schema: 'public',
|
|
485
|
+
table: 'profiles',
|
|
486
|
+
filter: `id=eq.${session.user.id}`,
|
|
487
|
+
}, (payload) => {
|
|
488
|
+
console.log('Profile updated:', payload.new)
|
|
489
|
+
})
|
|
490
|
+
.subscribe()
|
|
66
491
|
```
|
|
67
492
|
|
|
68
493
|
## Resources
|
|
69
|
-
|
|
70
|
-
- [
|
|
494
|
+
|
|
495
|
+
- [Auth Guide](https://supabase.com/docs/guides/auth)
|
|
496
|
+
- [Auth API Reference](https://supabase.com/docs/reference/javascript/auth-signup)
|
|
497
|
+
- [Storage Guide](https://supabase.com/docs/guides/storage)
|
|
498
|
+
- [Storage Access Control](https://supabase.com/docs/guides/storage/security/access-control)
|
|
499
|
+
- [Realtime Guide](https://supabase.com/docs/guides/realtime)
|
|
500
|
+
- [Realtime Postgres Changes](https://supabase.com/docs/guides/realtime/postgres-changes)
|
|
501
|
+
- [Realtime Broadcast](https://supabase.com/docs/guides/realtime/broadcast)
|
|
502
|
+
- [Realtime Presence](https://supabase.com/docs/guides/realtime/presence)
|
|
71
503
|
|
|
72
504
|
## Next Steps
|
|
73
|
-
|
|
505
|
+
|
|
506
|
+
For common Supabase errors and debugging, see `supabase-common-errors`.
|
|
507
|
+
For database queries and CRUD operations, see `supabase-crud-core`.
|