@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,284 +1,463 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: supabase-architecture-variants
|
|
3
|
-
description:
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
description: 'Implement Supabase across different app architectures: Next.js SSR with
|
|
4
|
+
|
|
5
|
+
server components using service_role and client components with anon key,
|
|
6
|
+
|
|
7
|
+
SPA (React/Vue), mobile (React Native), serverless (Edge Functions),
|
|
8
|
+
|
|
9
|
+
and multi-tenant with schema-per-tenant or RLS isolation.
|
|
10
|
+
|
|
11
|
+
Use when choosing how to integrate Supabase into your specific stack,
|
|
12
|
+
|
|
13
|
+
setting up SSR auth flows, configuring mobile deep links,
|
|
14
|
+
|
|
15
|
+
or designing multi-tenant data isolation.
|
|
16
|
+
|
|
17
|
+
Trigger with phrases like "supabase next.js", "supabase SSR",
|
|
18
|
+
|
|
19
|
+
"supabase react native", "supabase SPA", "supabase serverless",
|
|
20
|
+
|
|
21
|
+
"supabase multi-tenant", "supabase server component",
|
|
22
|
+
|
|
23
|
+
"supabase architecture", "supabase service_role server".
|
|
24
|
+
|
|
25
|
+
'
|
|
26
|
+
allowed-tools: Read, Write, Edit, Bash(supabase:*), Bash(npx:*), Grep
|
|
10
27
|
version: 1.0.0
|
|
11
28
|
license: MIT
|
|
12
29
|
author: Jeremy Longshore <jeremy@intentsolutions.io>
|
|
30
|
+
tags:
|
|
31
|
+
- saas
|
|
32
|
+
- supabase
|
|
33
|
+
- architecture
|
|
34
|
+
- nextjs
|
|
35
|
+
- ssr
|
|
36
|
+
- spa
|
|
37
|
+
- mobile
|
|
38
|
+
- multi-tenant
|
|
39
|
+
- serverless
|
|
40
|
+
compatibility: Designed for Claude Code, also compatible with Codex and OpenClaw
|
|
13
41
|
---
|
|
14
|
-
|
|
15
42
|
# Supabase Architecture Variants
|
|
16
43
|
|
|
17
44
|
## Overview
|
|
18
|
-
|
|
45
|
+
|
|
46
|
+
Different application architectures require fundamentally different Supabase `createClient` configurations. The critical distinction is **where the client runs** (browser vs server) and **which key it uses** (anon key respects RLS; service_role bypasses it). This skill provides production-ready patterns for five architectures: **Next.js SSR** (server components with service_role, client components with anon), **SPA** (React/Vue with browser-only client), **Mobile** (React Native with deep link auth), **Serverless** (Edge Functions with per-request clients), and **Multi-tenant** (RLS-based or schema-per-tenant isolation).
|
|
19
47
|
|
|
20
48
|
## Prerequisites
|
|
21
|
-
- Understanding of team size and DAU requirements
|
|
22
|
-
- Knowledge of deployment infrastructure
|
|
23
|
-
- Clear SLA requirements
|
|
24
|
-
- Growth projections available
|
|
25
49
|
|
|
26
|
-
|
|
50
|
+
- `@supabase/supabase-js` v2+ installed
|
|
51
|
+
- `@supabase/ssr` package for Next.js SSR (v0.5+)
|
|
52
|
+
- Supabase project with URL, anon key, and service role key
|
|
53
|
+
- TypeScript project with generated database types (`supabase gen types typescript`)
|
|
54
|
+
- For mobile: React Native with Expo or bare workflow
|
|
27
55
|
|
|
28
|
-
|
|
56
|
+
## Step 1 — Next.js SSR (App Router with Server and Client Components)
|
|
29
57
|
|
|
30
|
-
|
|
31
|
-
my-app/
|
|
32
|
-
├── src/
|
|
33
|
-
│ ├── supabase/
|
|
34
|
-
│ │ ├── client.ts # Singleton client
|
|
35
|
-
│ │ ├── types.ts # Types
|
|
36
|
-
│ │ └── middleware.ts # Express middleware
|
|
37
|
-
│ ├── routes/
|
|
38
|
-
│ │ └── api/
|
|
39
|
-
│ │ └── supabase.ts # API routes
|
|
40
|
-
│ └── index.ts
|
|
41
|
-
├── tests/
|
|
42
|
-
│ └── supabase.test.ts
|
|
43
|
-
└── package.json
|
|
44
|
-
```
|
|
58
|
+
Next.js App Router requires **two separate clients**: a server-side client using cookies for auth (with `@supabase/ssr`) and a browser client for client components. Never expose `service_role` to the client.
|
|
45
59
|
|
|
46
|
-
###
|
|
47
|
-
- Single deployment unit
|
|
48
|
-
- Synchronous Supabase calls in request path
|
|
49
|
-
- In-memory caching
|
|
50
|
-
- Simple error handling
|
|
60
|
+
### Server-Side Client (for Server Components, Route Handlers, Server Actions)
|
|
51
61
|
|
|
52
|
-
### Code Pattern
|
|
53
62
|
```typescript
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
// lib/supabase/server.ts
|
|
64
|
+
import { createServerClient } from '@supabase/ssr'
|
|
65
|
+
import { cookies } from 'next/headers'
|
|
66
|
+
import type { Database } from '../database.types'
|
|
67
|
+
|
|
68
|
+
export async function createSupabaseServer() {
|
|
69
|
+
const cookieStore = await cookies()
|
|
70
|
+
|
|
71
|
+
return createServerClient<Database>(
|
|
72
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
73
|
+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
|
74
|
+
{
|
|
75
|
+
cookies: {
|
|
76
|
+
getAll() {
|
|
77
|
+
return cookieStore.getAll()
|
|
78
|
+
},
|
|
79
|
+
setAll(cookiesToSet) {
|
|
80
|
+
try {
|
|
81
|
+
cookiesToSet.forEach(({ name, value, options }) =>
|
|
82
|
+
cookieStore.set(name, value, options)
|
|
83
|
+
)
|
|
84
|
+
} catch {
|
|
85
|
+
// Called from Server Component — cookies are read-only
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Admin client for server-only operations (bypasses RLS)
|
|
94
|
+
// NEVER import this in client components or expose to the browser
|
|
95
|
+
import { createClient } from '@supabase/supabase-js'
|
|
96
|
+
|
|
97
|
+
export function createSupabaseAdmin() {
|
|
98
|
+
return createClient<Database>(
|
|
99
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
100
|
+
process.env.SUPABASE_SERVICE_ROLE_KEY!, // NOT NEXT_PUBLIC_ — server only
|
|
101
|
+
{
|
|
102
|
+
auth: { autoRefreshToken: false, persistSession: false },
|
|
103
|
+
}
|
|
104
|
+
)
|
|
105
|
+
}
|
|
63
106
|
```
|
|
64
107
|
|
|
65
|
-
|
|
108
|
+
### Client-Side Client (for Client Components)
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
// lib/supabase/client.ts
|
|
112
|
+
'use client'
|
|
113
|
+
|
|
114
|
+
import { createBrowserClient } from '@supabase/ssr'
|
|
115
|
+
import type { Database } from '../database.types'
|
|
116
|
+
|
|
117
|
+
let client: ReturnType<typeof createBrowserClient<Database>> | null = null
|
|
66
118
|
|
|
67
|
-
|
|
119
|
+
export function createSupabaseBrowser() {
|
|
120
|
+
if (client) return client
|
|
68
121
|
|
|
69
|
-
|
|
122
|
+
client = createBrowserClient<Database>(
|
|
123
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
124
|
+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! // anon key only — respects RLS
|
|
125
|
+
)
|
|
70
126
|
|
|
127
|
+
return client
|
|
128
|
+
}
|
|
71
129
|
```
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
130
|
+
|
|
131
|
+
### Middleware for Auth Session Refresh
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
// middleware.ts
|
|
135
|
+
import { createServerClient } from '@supabase/ssr'
|
|
136
|
+
import { NextResponse, type NextRequest } from 'next/server'
|
|
137
|
+
|
|
138
|
+
export async function middleware(request: NextRequest) {
|
|
139
|
+
let response = NextResponse.next({ request })
|
|
140
|
+
|
|
141
|
+
const supabase = createServerClient(
|
|
142
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
143
|
+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
|
144
|
+
{
|
|
145
|
+
cookies: {
|
|
146
|
+
getAll() {
|
|
147
|
+
return request.cookies.getAll()
|
|
148
|
+
},
|
|
149
|
+
setAll(cookiesToSet) {
|
|
150
|
+
cookiesToSet.forEach(({ name, value }) =>
|
|
151
|
+
request.cookies.set(name, value)
|
|
152
|
+
)
|
|
153
|
+
response = NextResponse.next({ request })
|
|
154
|
+
cookiesToSet.forEach(({ name, value, options }) =>
|
|
155
|
+
response.cookies.set(name, value, options)
|
|
156
|
+
)
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
}
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
// Refresh session — this is the critical call
|
|
163
|
+
await supabase.auth.getUser()
|
|
164
|
+
|
|
165
|
+
return response
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export const config = {
|
|
169
|
+
matcher: ['/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)'],
|
|
170
|
+
}
|
|
91
171
|
```
|
|
92
172
|
|
|
93
|
-
###
|
|
94
|
-
- Separation of concerns
|
|
95
|
-
- Background job processing
|
|
96
|
-
- Redis caching
|
|
97
|
-
- Circuit breaker pattern
|
|
98
|
-
- Structured error handling
|
|
173
|
+
### Server Component Usage
|
|
99
174
|
|
|
100
|
-
### Code Pattern
|
|
101
175
|
```typescript
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
// Async follow-up
|
|
127
|
-
await this.queue.enqueue('supabase.post-create', result);
|
|
128
|
-
|
|
129
|
-
return result;
|
|
130
|
-
}
|
|
176
|
+
// app/dashboard/page.tsx
|
|
177
|
+
import { createSupabaseServer } from '@/lib/supabase/server'
|
|
178
|
+
import { redirect } from 'next/navigation'
|
|
179
|
+
|
|
180
|
+
export default async function DashboardPage() {
|
|
181
|
+
const supabase = await createSupabaseServer()
|
|
182
|
+
|
|
183
|
+
const { data: { user } } = await supabase.auth.getUser()
|
|
184
|
+
if (!user) redirect('/login')
|
|
185
|
+
|
|
186
|
+
const { data: projects, error } = await supabase
|
|
187
|
+
.from('projects')
|
|
188
|
+
.select('id, name, status, created_at')
|
|
189
|
+
.eq('user_id', user.id)
|
|
190
|
+
.order('created_at', { ascending: false })
|
|
191
|
+
|
|
192
|
+
if (error) throw new Error(`Failed to load projects: ${error.message}`)
|
|
193
|
+
|
|
194
|
+
return (
|
|
195
|
+
<div>
|
|
196
|
+
<h1>My Projects</h1>
|
|
197
|
+
{projects.map(p => <ProjectCard key={p.id} project={p} />)}
|
|
198
|
+
</div>
|
|
199
|
+
)
|
|
131
200
|
}
|
|
132
201
|
```
|
|
133
202
|
|
|
134
|
-
|
|
203
|
+
### Server Action with Admin Client
|
|
135
204
|
|
|
136
|
-
|
|
205
|
+
```typescript
|
|
206
|
+
// app/actions/admin.ts
|
|
207
|
+
'use server'
|
|
137
208
|
|
|
138
|
-
|
|
209
|
+
import { createSupabaseAdmin } from '@/lib/supabase/server'
|
|
139
210
|
|
|
140
|
-
|
|
141
|
-
supabase
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
│ │ │ ├── mapper.ts
|
|
156
|
-
│ │ │ └── circuit-breaker.ts
|
|
157
|
-
│ │ ├── cache/
|
|
158
|
-
│ │ ├── queue/
|
|
159
|
-
│ │ └── database/
|
|
160
|
-
│ └── index.ts
|
|
161
|
-
├── config/
|
|
162
|
-
├── k8s/
|
|
163
|
-
│ ├── deployment.yaml
|
|
164
|
-
│ ├── service.yaml
|
|
165
|
-
│ └── hpa.yaml
|
|
166
|
-
└── package.json
|
|
167
|
-
|
|
168
|
-
other-services/
|
|
169
|
-
├── order-service/ # Calls supabase-service
|
|
170
|
-
├── payment-service/
|
|
171
|
-
└── notification-service/
|
|
211
|
+
export async function deleteUserAccount(userId: string) {
|
|
212
|
+
const supabase = createSupabaseAdmin()
|
|
213
|
+
|
|
214
|
+
// Admin operation — bypasses RLS
|
|
215
|
+
const { error: deleteError } = await supabase
|
|
216
|
+
.from('user_data')
|
|
217
|
+
.delete()
|
|
218
|
+
.eq('user_id', userId)
|
|
219
|
+
|
|
220
|
+
if (deleteError) throw new Error(`Data deletion failed: ${deleteError.message}`)
|
|
221
|
+
|
|
222
|
+
// Delete auth user
|
|
223
|
+
const { error: authError } = await supabase.auth.admin.deleteUser(userId)
|
|
224
|
+
if (authError) throw new Error(`Auth deletion failed: ${authError.message}`)
|
|
225
|
+
}
|
|
172
226
|
```
|
|
173
227
|
|
|
174
|
-
|
|
175
|
-
- Dedicated Supabase microservice
|
|
176
|
-
- gRPC for internal communication
|
|
177
|
-
- Event-driven architecture
|
|
178
|
-
- Database per service
|
|
179
|
-
- Kubernetes autoscaling
|
|
180
|
-
- Distributed tracing
|
|
181
|
-
- Circuit breaker per service
|
|
228
|
+
## Step 2 — SPA (React/Vue) and Mobile (React Native)
|
|
182
229
|
|
|
183
|
-
###
|
|
184
|
-
```typescript
|
|
185
|
-
// Event-driven with domain isolation
|
|
186
|
-
class SupabaseAggregate {
|
|
187
|
-
private events: DomainEvent[] = [];
|
|
230
|
+
### SPA Architecture (React with Vite)
|
|
188
231
|
|
|
189
|
-
|
|
190
|
-
// Domain logic
|
|
191
|
-
const result = this.execute(command);
|
|
232
|
+
SPAs use a single browser client with the anon key. All authorization is enforced via RLS. The service_role key is never present in the SPA bundle.
|
|
192
233
|
|
|
193
|
-
|
|
194
|
-
|
|
234
|
+
```typescript
|
|
235
|
+
// src/lib/supabase.ts
|
|
236
|
+
import { createClient } from '@supabase/supabase-js'
|
|
237
|
+
import type { Database } from './database.types'
|
|
238
|
+
|
|
239
|
+
// Singleton client — one instance for the entire SPA
|
|
240
|
+
export const supabase = createClient<Database>(
|
|
241
|
+
import.meta.env.VITE_SUPABASE_URL,
|
|
242
|
+
import.meta.env.VITE_SUPABASE_ANON_KEY,
|
|
243
|
+
{
|
|
244
|
+
auth: {
|
|
245
|
+
autoRefreshToken: true,
|
|
246
|
+
persistSession: true,
|
|
247
|
+
detectSessionInUrl: true, // handles OAuth redirects
|
|
248
|
+
storage: window.localStorage,
|
|
249
|
+
},
|
|
195
250
|
}
|
|
251
|
+
)
|
|
196
252
|
|
|
197
|
-
|
|
198
|
-
|
|
253
|
+
// Auth state listener — call once at app initialization
|
|
254
|
+
supabase.auth.onAuthStateChange((event, session) => {
|
|
255
|
+
if (event === 'SIGNED_OUT') {
|
|
256
|
+
// Clear local caches
|
|
257
|
+
queryClient.clear() // React Query
|
|
199
258
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
// Event handler
|
|
203
|
-
@EventHandler(SupabaseProcessedEvent)
|
|
204
|
-
class SupabaseEventHandler {
|
|
205
|
-
async handle(event: SupabaseProcessedEvent): Promise<void> {
|
|
206
|
-
// Saga orchestration
|
|
207
|
-
await this.sagaOrchestrator.continue(event);
|
|
259
|
+
if (event === 'TOKEN_REFRESHED') {
|
|
260
|
+
console.log('Token refreshed')
|
|
208
261
|
}
|
|
209
|
-
}
|
|
262
|
+
})
|
|
210
263
|
```
|
|
211
264
|
|
|
212
|
-
|
|
265
|
+
### React Hook for Auth-Protected Queries
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
// src/hooks/useSupabaseQuery.ts
|
|
269
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
270
|
+
import { supabase } from '../lib/supabase'
|
|
271
|
+
|
|
272
|
+
export function useTodos() {
|
|
273
|
+
return useQuery({
|
|
274
|
+
queryKey: ['todos'],
|
|
275
|
+
queryFn: async () => {
|
|
276
|
+
const { data, error } = await supabase
|
|
277
|
+
.from('todos')
|
|
278
|
+
.select('id, title, is_complete, created_at')
|
|
279
|
+
.order('created_at', { ascending: false })
|
|
280
|
+
|
|
281
|
+
if (error) throw new Error(`Failed to load todos: ${error.message}`)
|
|
282
|
+
return data
|
|
283
|
+
},
|
|
284
|
+
})
|
|
285
|
+
}
|
|
213
286
|
|
|
214
|
-
|
|
287
|
+
export function useCreateTodo() {
|
|
288
|
+
const queryClient = useQueryClient()
|
|
289
|
+
|
|
290
|
+
return useMutation({
|
|
291
|
+
mutationFn: async (title: string) => {
|
|
292
|
+
const { data, error } = await supabase
|
|
293
|
+
.from('todos')
|
|
294
|
+
.insert({ title })
|
|
295
|
+
.select('id, title, is_complete, created_at')
|
|
296
|
+
.single()
|
|
297
|
+
|
|
298
|
+
if (error) throw new Error(`Failed to create todo: ${error.message}`)
|
|
299
|
+
return data
|
|
300
|
+
},
|
|
301
|
+
onSuccess: () => {
|
|
302
|
+
queryClient.invalidateQueries({ queryKey: ['todos'] })
|
|
303
|
+
},
|
|
304
|
+
})
|
|
305
|
+
}
|
|
306
|
+
```
|
|
215
307
|
|
|
216
|
-
|
|
217
|
-
|--------|----------|---------------|--------------|
|
|
218
|
-
| Team Size | 1-5 | 5-20 | 20+ |
|
|
219
|
-
| DAU | < 10K | 10K-100K | 100K+ |
|
|
220
|
-
| Deployment Frequency | Weekly | Daily | Continuous |
|
|
221
|
-
| Failure Isolation | None | Partial | Full |
|
|
222
|
-
| Operational Complexity | Low | Medium | High |
|
|
223
|
-
| Time to Market | Fastest | Moderate | Slowest |
|
|
308
|
+
### Mobile Architecture (React Native with Expo)
|
|
224
309
|
|
|
225
|
-
|
|
310
|
+
React Native needs `AsyncStorage` for session persistence and deep link handling for OAuth.
|
|
226
311
|
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
312
|
+
```typescript
|
|
313
|
+
// lib/supabase.ts (React Native)
|
|
314
|
+
import { createClient } from '@supabase/supabase-js'
|
|
315
|
+
import AsyncStorage from '@react-native-async-storage/async-storage'
|
|
316
|
+
import type { Database } from './database.types'
|
|
317
|
+
|
|
318
|
+
export const supabase = createClient<Database>(
|
|
319
|
+
process.env.EXPO_PUBLIC_SUPABASE_URL!,
|
|
320
|
+
process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY!,
|
|
321
|
+
{
|
|
322
|
+
auth: {
|
|
323
|
+
storage: AsyncStorage,
|
|
324
|
+
autoRefreshToken: true,
|
|
325
|
+
persistSession: true,
|
|
326
|
+
detectSessionInUrl: false, // disabled for React Native
|
|
327
|
+
},
|
|
328
|
+
}
|
|
329
|
+
)
|
|
239
330
|
```
|
|
240
331
|
|
|
241
|
-
|
|
332
|
+
### Mobile OAuth with Deep Links
|
|
242
333
|
|
|
243
|
-
|
|
244
|
-
|
|
334
|
+
```typescript
|
|
335
|
+
// lib/auth.ts (React Native)
|
|
336
|
+
import { supabase } from './supabase'
|
|
337
|
+
import * as Linking from 'expo-linking'
|
|
338
|
+
import * as WebBrowser from 'expo-web-browser'
|
|
339
|
+
|
|
340
|
+
const redirectUrl = Linking.createURL('auth/callback')
|
|
341
|
+
|
|
342
|
+
export async function signInWithGoogle() {
|
|
343
|
+
const { data, error } = await supabase.auth.signInWithOAuth({
|
|
344
|
+
provider: 'google',
|
|
345
|
+
options: {
|
|
346
|
+
redirectTo: redirectUrl,
|
|
347
|
+
skipBrowserRedirect: true, // handle manually for RN
|
|
348
|
+
},
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
if (error) throw new Error(`OAuth failed: ${error.message}`)
|
|
352
|
+
if (!data.url) throw new Error('No OAuth URL returned')
|
|
353
|
+
|
|
354
|
+
// Open in-app browser
|
|
355
|
+
const result = await WebBrowser.openAuthSessionAsync(data.url, redirectUrl)
|
|
356
|
+
|
|
357
|
+
if (result.type === 'success') {
|
|
358
|
+
const url = new URL(result.url)
|
|
359
|
+
const params = new URLSearchParams(url.hash.substring(1))
|
|
360
|
+
const accessToken = params.get('access_token')
|
|
361
|
+
const refreshToken = params.get('refresh_token')
|
|
362
|
+
|
|
363
|
+
if (accessToken && refreshToken) {
|
|
364
|
+
const { error: sessionError } = await supabase.auth.setSession({
|
|
365
|
+
access_token: accessToken,
|
|
366
|
+
refresh_token: refreshToken,
|
|
367
|
+
})
|
|
368
|
+
if (sessionError) throw sessionError
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
```
|
|
245
373
|
|
|
246
|
-
###
|
|
247
|
-
|
|
374
|
+
### App.json Deep Link Configuration (Expo)
|
|
375
|
+
|
|
376
|
+
```json
|
|
377
|
+
{
|
|
378
|
+
"expo": {
|
|
379
|
+
"scheme": "myapp",
|
|
380
|
+
"plugins": [
|
|
381
|
+
[
|
|
382
|
+
"expo-linking",
|
|
383
|
+
{
|
|
384
|
+
"scheme": "myapp"
|
|
385
|
+
}
|
|
386
|
+
]
|
|
387
|
+
]
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
```
|
|
248
391
|
|
|
249
|
-
|
|
250
|
-
Set up project layout following the chosen blueprint.
|
|
392
|
+
## Step 3 — Serverless (Edge Functions) and Multi-Tenant
|
|
251
393
|
|
|
252
|
-
|
|
253
|
-
Document upgrade path for future scaling.
|
|
394
|
+
See [serverless and multi-tenant patterns](references/serverless-and-multi-tenant.md) for Edge Function per-request clients, admin operation escalation, RLS-based multi-tenant isolation with schema, and tenant-scoped SDK queries.
|
|
254
395
|
|
|
255
396
|
## Output
|
|
256
|
-
|
|
257
|
-
-
|
|
258
|
-
-
|
|
259
|
-
-
|
|
397
|
+
|
|
398
|
+
- Next.js SSR setup with server client (cookies-based auth), browser client, and middleware
|
|
399
|
+
- Server Actions using admin client with service_role for privileged operations
|
|
400
|
+
- SPA pattern with singleton client, React Query integration, and auth state listener
|
|
401
|
+
- React Native setup with AsyncStorage, deep link OAuth, and in-app browser
|
|
402
|
+
- Edge Function patterns for per-request auth and admin escalation
|
|
403
|
+
- Multi-tenant RLS isolation with tenant_members lookup and scoped queries
|
|
404
|
+
- Decision matrix for choosing the right architecture per stack
|
|
260
405
|
|
|
261
406
|
## Error Handling
|
|
407
|
+
|
|
262
408
|
| Issue | Cause | Solution |
|
|
263
409
|
|-------|-------|----------|
|
|
264
|
-
|
|
|
265
|
-
|
|
|
266
|
-
|
|
|
267
|
-
|
|
|
410
|
+
| `AuthSessionMissingError` in Server Component | Cookies not passed to Supabase client | Use `createServerClient` from `@supabase/ssr` with cookie handlers |
|
|
411
|
+
| OAuth redirect fails in React Native | Missing deep link scheme | Add `scheme` to app.json and configure Supabase redirect URL |
|
|
412
|
+
| service_role key in client bundle | Wrong env var prefix (`NEXT_PUBLIC_`) | Remove `NEXT_PUBLIC_` prefix; only server code should access it |
|
|
413
|
+
| Multi-tenant data leak | Missing RLS policy or missing tenant_id filter | Verify RLS is enabled and policies check `tenant_members` |
|
|
414
|
+
| Edge Function `auth.getUser()` returns null | Missing Authorization header | Forward user's JWT from the client call |
|
|
415
|
+
| Session not persisting on mobile | AsyncStorage not configured | Pass `AsyncStorage` in auth config; ensure package is installed |
|
|
268
416
|
|
|
269
417
|
## Examples
|
|
270
418
|
|
|
271
|
-
###
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
419
|
+
### Test Auth Flow End-to-End (Next.js)
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
// app/auth/callback/route.ts
|
|
423
|
+
import { createSupabaseServer } from '@/lib/supabase/server'
|
|
424
|
+
import { NextResponse } from 'next/server'
|
|
425
|
+
|
|
426
|
+
export async function GET(request: Request) {
|
|
427
|
+
const { searchParams } = new URL(request.url)
|
|
428
|
+
const code = searchParams.get('code')
|
|
429
|
+
|
|
430
|
+
if (code) {
|
|
431
|
+
const supabase = await createSupabaseServer()
|
|
432
|
+
const { error } = await supabase.auth.exchangeCodeForSession(code)
|
|
433
|
+
if (error) {
|
|
434
|
+
return NextResponse.redirect(new URL('/login?error=auth_failed', request.url))
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return NextResponse.redirect(new URL('/dashboard', request.url))
|
|
439
|
+
}
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### Verify Tenant Isolation
|
|
443
|
+
|
|
444
|
+
```sql
|
|
445
|
+
-- Test that RLS properly isolates tenants
|
|
446
|
+
SET request.jwt.claims = '{"sub": "user-uuid-1"}';
|
|
447
|
+
|
|
448
|
+
-- Should only return projects for user-uuid-1's tenant
|
|
449
|
+
SELECT * FROM public.projects;
|
|
276
450
|
```
|
|
277
451
|
|
|
278
452
|
## Resources
|
|
279
|
-
|
|
280
|
-
- [
|
|
281
|
-
- [Supabase
|
|
453
|
+
|
|
454
|
+
- [Supabase SSR (Next.js)](https://supabase.com/docs/guides/auth/server-side/nextjs)
|
|
455
|
+
- [Supabase React Native](https://supabase.com/docs/guides/getting-started/tutorials/with-expo-react-native)
|
|
456
|
+
- [Supabase Edge Functions](https://supabase.com/docs/guides/functions)
|
|
457
|
+
- [Multi-Tenant with RLS](https://supabase.com/docs/guides/database/postgres/row-level-security)
|
|
458
|
+
- [Supabase Auth Deep Linking](https://supabase.com/docs/guides/auth/native-mobile-deep-linking)
|
|
459
|
+
- [@supabase/ssr Package](https://supabase.com/docs/guides/auth/server-side/overview)
|
|
282
460
|
|
|
283
461
|
## Next Steps
|
|
284
|
-
|
|
462
|
+
|
|
463
|
+
For common mistakes and anti-patterns to avoid, see `supabase-known-pitfalls`.
|