@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,147 +1,436 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: supabase-sdk-patterns
|
|
3
|
-
description:
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
description: 'Apply production-ready Supabase SDK patterns for TypeScript and Python
|
|
4
|
+
projects.
|
|
5
|
+
|
|
6
|
+
Use when implementing queries, auth, realtime, storage, or RPC calls
|
|
7
|
+
|
|
8
|
+
with @supabase/supabase-js or supabase-py.
|
|
9
|
+
|
|
10
|
+
Trigger with phrases like "supabase SDK patterns", "supabase query",
|
|
11
|
+
|
|
12
|
+
"supabase typescript", "supabase python", "supabase client setup",
|
|
13
|
+
|
|
14
|
+
"supabase realtime", "supabase auth", "supabase storage".
|
|
15
|
+
|
|
16
|
+
'
|
|
17
|
+
allowed-tools: Read, Write, Edit, 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
|
+
- typescript
|
|
25
|
+
- python
|
|
26
|
+
- sdk
|
|
27
|
+
- patterns
|
|
28
|
+
compatibility: Designed for Claude Code, also compatible with Cursor
|
|
13
29
|
---
|
|
14
|
-
|
|
15
30
|
# Supabase SDK Patterns
|
|
16
31
|
|
|
17
32
|
## Overview
|
|
18
|
-
|
|
33
|
+
|
|
34
|
+
Production patterns for `@supabase/supabase-js` v2 and `supabase-py`. Every Supabase query returns `{ data, error }` — never assume success. This skill covers client initialization, CRUD with filters, auth, realtime subscriptions, storage, RPC, and the Python equivalent for each pattern.
|
|
19
35
|
|
|
20
36
|
## Prerequisites
|
|
21
|
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
37
|
+
|
|
38
|
+
- Supabase project with URL and anon key (or service role key for server-side)
|
|
39
|
+
- `@supabase/supabase-js` v2 installed (TypeScript) or `supabase` pip package (Python)
|
|
40
|
+
- TypeScript projects: generated database types via `supabase gen types typescript`
|
|
24
41
|
|
|
25
42
|
## Instructions
|
|
26
43
|
|
|
27
|
-
### Step 1:
|
|
44
|
+
### Step 1: Initialize a Typed Singleton Client
|
|
45
|
+
|
|
46
|
+
Create one client instance and reuse it. Never call `createClient` per-request.
|
|
47
|
+
|
|
28
48
|
```typescript
|
|
29
|
-
//
|
|
30
|
-
import {
|
|
49
|
+
// lib/supabase.ts
|
|
50
|
+
import { createClient } from '@supabase/supabase-js'
|
|
51
|
+
import type { Database } from './database.types'
|
|
31
52
|
|
|
32
|
-
let
|
|
53
|
+
let supabase: ReturnType<typeof createClient<Database>>
|
|
33
54
|
|
|
34
|
-
export function
|
|
35
|
-
if (!
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
55
|
+
export function getSupabase() {
|
|
56
|
+
if (!supabase) {
|
|
57
|
+
supabase = createClient<Database>(
|
|
58
|
+
process.env.SUPABASE_URL!,
|
|
59
|
+
process.env.SUPABASE_ANON_KEY!,
|
|
60
|
+
{
|
|
61
|
+
auth: { autoRefreshToken: true, persistSession: true },
|
|
62
|
+
db: { schema: 'public' },
|
|
63
|
+
global: { headers: { 'x-app-name': 'my-app' } },
|
|
64
|
+
}
|
|
65
|
+
)
|
|
40
66
|
}
|
|
41
|
-
return
|
|
67
|
+
return supabase
|
|
42
68
|
}
|
|
43
69
|
```
|
|
44
70
|
|
|
45
|
-
|
|
71
|
+
**Python equivalent:**
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from supabase import create_client, Client
|
|
75
|
+
|
|
76
|
+
_client: Client | None = None
|
|
77
|
+
|
|
78
|
+
def get_supabase() -> Client:
|
|
79
|
+
global _client
|
|
80
|
+
if _client is None:
|
|
81
|
+
_client = create_client(
|
|
82
|
+
os.environ["SUPABASE_URL"],
|
|
83
|
+
os.environ["SUPABASE_ANON_KEY"],
|
|
84
|
+
)
|
|
85
|
+
return _client
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Step 2: Query, Filter, and Mutate Data
|
|
89
|
+
|
|
90
|
+
**All queries return `{ data, error }`.** Always destructure and check error before using data.
|
|
91
|
+
|
|
92
|
+
**Select with filters and chaining:**
|
|
93
|
+
|
|
46
94
|
```typescript
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
95
|
+
const { data, error } = await getSupabase()
|
|
96
|
+
.from('users')
|
|
97
|
+
.select('id, name, email')
|
|
98
|
+
.eq('active', true) // WHERE active = true
|
|
99
|
+
.gt('age', 18) // AND age > 18
|
|
100
|
+
.ilike('name', '%john%') // AND name ILIKE '%john%'
|
|
101
|
+
.in('role', ['admin', 'editor']) // AND role IN (...)
|
|
102
|
+
.order('name', { ascending: true })
|
|
103
|
+
.limit(10)
|
|
104
|
+
|
|
105
|
+
if (error) throw error
|
|
106
|
+
// data is typed as Pick<User, 'id' | 'name' | 'email'>[]
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Insert with select (return the inserted row):**
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
const { data: newUser, error } = await getSupabase()
|
|
113
|
+
.from('users')
|
|
114
|
+
.insert({ name: 'Alice', email: 'alice@example.com', active: true })
|
|
115
|
+
.select() // Without .select(), data is null
|
|
116
|
+
.single() // Unwrap from array to single object
|
|
117
|
+
|
|
118
|
+
if (error) throw error
|
|
119
|
+
// newUser is the full row with server-generated id, created_at, etc.
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Upsert (insert or update on conflict):**
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
const { data, error } = await getSupabase()
|
|
126
|
+
.from('users')
|
|
127
|
+
.upsert(
|
|
128
|
+
{ email: 'alice@example.com', name: 'Alice Updated' },
|
|
129
|
+
{ onConflict: 'email' } // Match on unique column
|
|
130
|
+
)
|
|
131
|
+
.select()
|
|
132
|
+
.single()
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Update and delete:**
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
// Update
|
|
139
|
+
const { data, error } = await getSupabase()
|
|
140
|
+
.from('users')
|
|
141
|
+
.update({ active: false })
|
|
142
|
+
.eq('id', userId)
|
|
143
|
+
.select()
|
|
144
|
+
.single()
|
|
145
|
+
|
|
146
|
+
// Delete
|
|
147
|
+
const { error } = await getSupabase()
|
|
148
|
+
.from('users')
|
|
149
|
+
.delete()
|
|
150
|
+
.eq('id', userId)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**RPC — call a Postgres function:**
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
const { data, error } = await getSupabase()
|
|
157
|
+
.rpc('my_function', { arg1: 'value', arg2: 42 })
|
|
158
|
+
|
|
159
|
+
if (error) throw error
|
|
160
|
+
// data is the function's return value
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Complete filter reference:**
|
|
164
|
+
|
|
165
|
+
| Filter | SQL Equivalent | Example |
|
|
166
|
+
|--------|---------------|---------|
|
|
167
|
+
| `.eq(col, val)` | `= val` | `.eq('status', 'active')` |
|
|
168
|
+
| `.neq(col, val)` | `!= val` | `.neq('role', 'guest')` |
|
|
169
|
+
| `.gt(col, val)` | `> val` | `.gt('age', 18)` |
|
|
170
|
+
| `.gte(col, val)` | `>= val` | `.gte('score', 90)` |
|
|
171
|
+
| `.lt(col, val)` | `< val` | `.lt('price', 100)` |
|
|
172
|
+
| `.lte(col, val)` | `<= val` | `.lte('quantity', 0)` |
|
|
173
|
+
| `.like(col, pat)` | `LIKE pat` | `.like('name', '%son')` |
|
|
174
|
+
| `.ilike(col, pat)` | `ILIKE pat` | `.ilike('email', '%@gmail%')` |
|
|
175
|
+
| `.is(col, val)` | `IS val` | `.is('deleted_at', null)` |
|
|
176
|
+
| `.in(col, arr)` | `IN (...)` | `.in('id', [1, 2, 3])` |
|
|
177
|
+
| `.contains(col, val)` | `@> val` | `.contains('tags', ['urgent'])` |
|
|
178
|
+
| `.range(from, to)` | `OFFSET/LIMIT` | `.range(0, 9)` (first 10 rows) |
|
|
179
|
+
|
|
180
|
+
**Python equivalent:**
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
# Select with filters
|
|
184
|
+
result = get_supabase() \
|
|
185
|
+
.table('users') \
|
|
186
|
+
.select('id, name, email') \
|
|
187
|
+
.eq('active', True) \
|
|
188
|
+
.gt('age', 18) \
|
|
189
|
+
.order('name') \
|
|
190
|
+
.limit(10) \
|
|
191
|
+
.execute()
|
|
192
|
+
|
|
193
|
+
if result.data is None:
|
|
194
|
+
raise Exception(f"Query failed")
|
|
195
|
+
|
|
196
|
+
# Insert
|
|
197
|
+
result = get_supabase().table('users').insert({
|
|
198
|
+
"name": "Alice", "email": "alice@example.com"
|
|
199
|
+
}).execute()
|
|
200
|
+
|
|
201
|
+
# Upsert
|
|
202
|
+
result = get_supabase().table('users').upsert({
|
|
203
|
+
"email": "alice@example.com", "name": "Alice Updated"
|
|
204
|
+
}).execute()
|
|
205
|
+
|
|
206
|
+
# RPC
|
|
207
|
+
result = get_supabase().rpc('my_function', {"arg1": "value"}).execute()
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Step 3: Auth, Realtime, and Storage
|
|
211
|
+
|
|
212
|
+
**Auth — sign up, sign in, get session:**
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
// Sign up
|
|
216
|
+
const { data, error } = await getSupabase().auth.signUp({
|
|
217
|
+
email: 'user@example.com',
|
|
218
|
+
password: 'securepassword',
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
// Sign in with password
|
|
222
|
+
const { data, error } = await getSupabase().auth.signInWithPassword({
|
|
223
|
+
email: 'user@example.com',
|
|
224
|
+
password: 'securepassword',
|
|
225
|
+
})
|
|
226
|
+
// data.session contains access_token, refresh_token
|
|
227
|
+
// data.user contains user metadata
|
|
228
|
+
|
|
229
|
+
// Get current session
|
|
230
|
+
const { data: { session } } = await getSupabase().auth.getSession()
|
|
231
|
+
if (!session) {
|
|
232
|
+
// User is not authenticated
|
|
64
233
|
}
|
|
234
|
+
|
|
235
|
+
// Sign out
|
|
236
|
+
await getSupabase().auth.signOut()
|
|
237
|
+
|
|
238
|
+
// Listen for auth changes
|
|
239
|
+
getSupabase().auth.onAuthStateChange((event, session) => {
|
|
240
|
+
// event: 'SIGNED_IN' | 'SIGNED_OUT' | 'TOKEN_REFRESHED' | ...
|
|
241
|
+
console.log('Auth event:', event, session?.user?.email)
|
|
242
|
+
})
|
|
65
243
|
```
|
|
66
244
|
|
|
67
|
-
|
|
245
|
+
**Realtime — subscribe to database changes:**
|
|
246
|
+
|
|
68
247
|
```typescript
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
248
|
+
const channel = getSupabase()
|
|
249
|
+
.channel('room-messages')
|
|
250
|
+
.on(
|
|
251
|
+
'postgres_changes',
|
|
252
|
+
{
|
|
253
|
+
event: '*', // 'INSERT' | 'UPDATE' | 'DELETE' | '*'
|
|
254
|
+
schema: 'public',
|
|
255
|
+
table: 'messages',
|
|
256
|
+
filter: 'room_id=eq.42', // Optional row-level filter
|
|
257
|
+
},
|
|
258
|
+
(payload) => {
|
|
259
|
+
console.log('Change:', payload.eventType, payload.new)
|
|
260
|
+
// payload.new = the new row (INSERT/UPDATE)
|
|
261
|
+
// payload.old = the old row (UPDATE/DELETE)
|
|
81
262
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
263
|
+
)
|
|
264
|
+
.subscribe((status) => {
|
|
265
|
+
// status: 'SUBSCRIBED' | 'CLOSED' | 'CHANNEL_ERROR'
|
|
266
|
+
console.log('Subscription status:', status)
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
// Clean up when done
|
|
270
|
+
await getSupabase().removeChannel(channel)
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
**Storage — upload, download, get public URL:**
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
// Upload a file
|
|
277
|
+
const { data, error } = await getSupabase().storage
|
|
278
|
+
.from('avatars') // bucket name
|
|
279
|
+
.upload('users/avatar.png', file, {
|
|
280
|
+
cacheControl: '3600',
|
|
281
|
+
upsert: true, // overwrite if exists
|
|
282
|
+
contentType: 'image/png',
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
// Download a file
|
|
286
|
+
const { data, error } = await getSupabase().storage
|
|
287
|
+
.from('avatars')
|
|
288
|
+
.download('users/avatar.png')
|
|
289
|
+
// data is a Blob
|
|
290
|
+
|
|
291
|
+
// Get public URL (no auth required if bucket is public)
|
|
292
|
+
const { data: { publicUrl } } = getSupabase().storage
|
|
293
|
+
.from('avatars')
|
|
294
|
+
.getPublicUrl('users/avatar.png')
|
|
295
|
+
|
|
296
|
+
// Get signed URL (time-limited access for private buckets)
|
|
297
|
+
const { data, error } = await getSupabase().storage
|
|
298
|
+
.from('documents')
|
|
299
|
+
.createSignedUrl('reports/q4.pdf', 3600) // expires in 1 hour
|
|
300
|
+
// data.signedUrl
|
|
85
301
|
```
|
|
86
302
|
|
|
87
303
|
## Output
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
-
|
|
304
|
+
|
|
305
|
+
After applying these patterns you will have:
|
|
306
|
+
|
|
307
|
+
- Type-safe singleton client with `Database` generics
|
|
308
|
+
- CRUD operations using the full filter chain (eq, gt, in, ilike, etc.)
|
|
309
|
+
- Insert-with-select and upsert patterns that return the affected row
|
|
310
|
+
- Auth flows for sign-up, sign-in, session management, and state listeners
|
|
311
|
+
- Realtime subscriptions with row-level filtering and cleanup
|
|
312
|
+
- Storage upload/download with signed URLs for private buckets
|
|
313
|
+
- Python equivalents for all query patterns
|
|
92
314
|
|
|
93
315
|
## Error Handling
|
|
94
|
-
| Pattern | Use Case | Benefit |
|
|
95
|
-
|---------|----------|---------|
|
|
96
|
-
| Safe wrapper | All API calls | Prevents uncaught exceptions |
|
|
97
|
-
| Retry logic | Transient failures | Improves reliability |
|
|
98
|
-
| Type guards | Response validation | Catches API changes |
|
|
99
|
-
| Logging | All operations | Debugging and monitoring |
|
|
100
316
|
|
|
101
|
-
|
|
317
|
+
Every Supabase call returns `{ data, error }`. Never skip the error check.
|
|
102
318
|
|
|
103
|
-
### Factory Pattern (Multi-tenant)
|
|
104
319
|
```typescript
|
|
105
|
-
const
|
|
320
|
+
const { data, error } = await getSupabase().from('users').select('*')
|
|
106
321
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
322
|
+
if (error) {
|
|
323
|
+
// error is a PostgrestError with these fields:
|
|
324
|
+
// error.message — human-readable description
|
|
325
|
+
// error.code — Postgres error code (e.g., '23505')
|
|
326
|
+
// error.details — additional context
|
|
327
|
+
// error.hint — suggested fix from Postgres
|
|
328
|
+
console.error(`Query failed [${error.code}]: ${error.message}`)
|
|
329
|
+
throw error
|
|
113
330
|
}
|
|
331
|
+
|
|
332
|
+
// Only safe to use data after error check
|
|
114
333
|
```
|
|
115
334
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
335
|
+
| Error Code | Meaning | What to Do |
|
|
336
|
+
|------------|---------|------------|
|
|
337
|
+
| `PGRST116` | No rows found (`.single()`) | Return null or 404, don't throw |
|
|
338
|
+
| `23505` | Unique constraint violation | Use `.upsert()` or show conflict error |
|
|
339
|
+
| `42501` | RLS policy violation | Check auth state and RLS policies |
|
|
340
|
+
| `PGRST000` | Connection error | Retry with exponential backoff |
|
|
341
|
+
| `42P01` | Table does not exist | Verify table name and run migrations |
|
|
342
|
+
| `23503` | Foreign key violation | Ensure referenced row exists first |
|
|
343
|
+
| `42703` | Column does not exist | Check column name, regenerate types |
|
|
344
|
+
|
|
345
|
+
## Examples
|
|
346
|
+
|
|
347
|
+
**Service layer pattern (recommended for production):**
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
// services/user-service.ts
|
|
351
|
+
import type { Database } from '../lib/database.types'
|
|
352
|
+
|
|
353
|
+
type User = Database['public']['Tables']['users']['Row']
|
|
354
|
+
type UserInsert = Database['public']['Tables']['users']['Insert']
|
|
355
|
+
|
|
356
|
+
export const UserService = {
|
|
357
|
+
async getById(id: string): Promise<User | null> {
|
|
358
|
+
const { data, error } = await getSupabase()
|
|
359
|
+
.from('users')
|
|
360
|
+
.select('*')
|
|
361
|
+
.eq('id', id)
|
|
362
|
+
.single()
|
|
363
|
+
|
|
364
|
+
if (error?.code === 'PGRST116') return null // Not found
|
|
365
|
+
if (error) throw error
|
|
366
|
+
return data
|
|
367
|
+
},
|
|
368
|
+
|
|
369
|
+
async search(query: string, limit = 20): Promise<User[]> {
|
|
370
|
+
const { data, error } = await getSupabase()
|
|
371
|
+
.from('users')
|
|
372
|
+
.select('id, name, email, avatar_url')
|
|
373
|
+
.or(`name.ilike.%${query}%,email.ilike.%${query}%`)
|
|
374
|
+
.order('name')
|
|
375
|
+
.limit(limit)
|
|
376
|
+
|
|
377
|
+
if (error) throw error
|
|
378
|
+
return data
|
|
379
|
+
},
|
|
380
|
+
|
|
381
|
+
async createOrUpdate(user: UserInsert): Promise<User> {
|
|
382
|
+
const { data, error } = await getSupabase()
|
|
383
|
+
.from('users')
|
|
384
|
+
.upsert(user, { onConflict: 'email' })
|
|
385
|
+
.select()
|
|
386
|
+
.single()
|
|
387
|
+
|
|
388
|
+
if (error) throw error
|
|
389
|
+
return data
|
|
390
|
+
},
|
|
391
|
+
}
|
|
128
392
|
```
|
|
129
393
|
|
|
130
|
-
|
|
394
|
+
**Pagination helper:**
|
|
395
|
+
|
|
131
396
|
```typescript
|
|
132
|
-
|
|
397
|
+
async function paginate<T>(
|
|
398
|
+
table: string,
|
|
399
|
+
select: string,
|
|
400
|
+
{ page = 1, pageSize = 20, orderBy = 'id' } = {}
|
|
401
|
+
) {
|
|
402
|
+
const from = (page - 1) * pageSize
|
|
403
|
+
const to = from + pageSize - 1
|
|
133
404
|
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
405
|
+
const { data, error, count } = await getSupabase()
|
|
406
|
+
.from(table)
|
|
407
|
+
.select(select, { count: 'exact' })
|
|
408
|
+
.order(orderBy)
|
|
409
|
+
.range(from, to)
|
|
410
|
+
|
|
411
|
+
if (error) throw error
|
|
412
|
+
return {
|
|
413
|
+
data: data as T[],
|
|
414
|
+
page,
|
|
415
|
+
pageSize,
|
|
416
|
+
total: count ?? 0,
|
|
417
|
+
totalPages: Math.ceil((count ?? 0) / pageSize),
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Usage
|
|
422
|
+
const result = await paginate<User>('users', 'id, name, email', { page: 2 })
|
|
139
423
|
```
|
|
140
424
|
|
|
141
425
|
## Resources
|
|
142
|
-
|
|
143
|
-
- [Supabase
|
|
144
|
-
- [
|
|
426
|
+
|
|
427
|
+
- [Supabase JS Client Reference](https://supabase.com/docs/reference/javascript/initializing)
|
|
428
|
+
- [TypeScript Support & Type Generation](https://supabase.com/docs/reference/javascript/typescript-support)
|
|
429
|
+
- [Supabase Auth Reference](https://supabase.com/docs/reference/javascript/auth-signup)
|
|
430
|
+
- [Realtime Guide](https://supabase.com/docs/guides/realtime)
|
|
431
|
+
- [Storage Guide](https://supabase.com/docs/guides/storage)
|
|
432
|
+
- [Python Client Reference](https://supabase.com/docs/reference/python/initializing)
|
|
145
433
|
|
|
146
434
|
## Next Steps
|
|
147
|
-
|
|
435
|
+
|
|
436
|
+
For database schema design, see `supabase-schema-from-requirements`. For auth deep-dive with RLS policies, see `supabase-install-auth`. For realtime architecture patterns, see `supabase-auth-storage-realtime-core`.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Error Handling Reference
|
|
2
|
+
|
|
3
|
+
| Pattern | Use Case | Benefit |
|
|
4
|
+
|---------|----------|---------|
|
|
5
|
+
| Safe wrapper | All API calls | Prevents uncaught exceptions |
|
|
6
|
+
| Retry logic | Transient failures | Improves reliability |
|
|
7
|
+
| Type guards | Response validation | Catches API changes |
|
|
8
|
+
| Logging | All operations | Debugging and monitoring |
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
*[Tons of Skills](https://tonsofskills.com) by [Intent Solutions](https://intentsolutions.io) | [jeremylongshore.com](https://jeremylongshore.com)*
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
## Examples
|
|
2
|
+
|
|
3
|
+
### Factory Pattern (Multi-tenant)
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
const clients = new Map<string, SupabaseClient>();
|
|
7
|
+
|
|
8
|
+
export function getClientForTenant(tenantId: string): SupabaseClient {
|
|
9
|
+
if (!clients.has(tenantId)) {
|
|
10
|
+
const apiKey = getTenantApiKey(tenantId);
|
|
11
|
+
clients.set(tenantId, new SupabaseClient({ apiKey }));
|
|
12
|
+
}
|
|
13
|
+
return clients.get(tenantId)!;
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Python Context Manager
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from contextlib import asynccontextmanager
|
|
21
|
+
from supabase import SupabaseClient
|
|
22
|
+
|
|
23
|
+
@asynccontextmanager
|
|
24
|
+
async def get_supabase_client():
|
|
25
|
+
client = SupabaseClient()
|
|
26
|
+
try:
|
|
27
|
+
yield client
|
|
28
|
+
finally:
|
|
29
|
+
await client.close()
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Zod Validation
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { z } from 'zod';
|
|
36
|
+
|
|
37
|
+
const supabaseResponseSchema = z.object({
|
|
38
|
+
id: z.string(),
|
|
39
|
+
status: z.enum(['active', 'inactive']),
|
|
40
|
+
createdAt: z.string().datetime(),
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
*[Tons of Skills](https://tonsofskills.com) by [Intent Solutions](https://intentsolutions.io) | [jeremylongshore.com](https://jeremylongshore.com)*
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
## Implementation Guide
|
|
2
|
+
|
|
3
|
+
### Step 1: Implement Singleton Pattern (Recommended)
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
// src/supabase/client.ts
|
|
7
|
+
import { SupabaseClient } from '@supabase/supabase-js';
|
|
8
|
+
|
|
9
|
+
let instance: SupabaseClient | null = null;
|
|
10
|
+
|
|
11
|
+
export function getSupabaseClient(): SupabaseClient {
|
|
12
|
+
if (!instance) {
|
|
13
|
+
instance = new SupabaseClient({
|
|
14
|
+
apiKey: process.env.SUPABASE_API_KEY!,
|
|
15
|
+
// Additional options
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return instance;
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Step 2: Add Error Handling Wrapper
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { SupabaseError } from '@supabase/supabase-js';
|
|
26
|
+
|
|
27
|
+
async function safeSupabaseCall<T>(
|
|
28
|
+
operation: () => Promise<T>
|
|
29
|
+
): Promise<{ data: T | null; error: Error | null }> {
|
|
30
|
+
try {
|
|
31
|
+
const data = await operation();
|
|
32
|
+
return { data, error: null };
|
|
33
|
+
} catch (err) {
|
|
34
|
+
if (err instanceof SupabaseError) {
|
|
35
|
+
console.error({
|
|
36
|
+
code: err.code,
|
|
37
|
+
message: err.message,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return { data: null, error: err as Error };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Step 3: Implement Retry Logic
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
async function withRetry<T>(
|
|
49
|
+
operation: () => Promise<T>,
|
|
50
|
+
maxRetries = 3,
|
|
51
|
+
backoffMs = 1000
|
|
52
|
+
): Promise<T> {
|
|
53
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
54
|
+
try {
|
|
55
|
+
return await operation();
|
|
56
|
+
} catch (err) {
|
|
57
|
+
if (attempt === maxRetries) throw err;
|
|
58
|
+
const delay = backoffMs * Math.pow(2, attempt - 1);
|
|
59
|
+
await new Promise(r => setTimeout(r, delay));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
throw new Error('Unreachable');
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
*[Tons of Skills](https://tonsofskills.com) by [Intent Solutions](https://intentsolutions.io) | [jeremylongshore.com](https://jeremylongshore.com)*
|