@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
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Error Handling Reference
|
|
2
|
+
|
|
3
|
+
| Issue | Cause | Solution |
|
|
4
|
+
|-------|-------|----------|
|
|
5
|
+
| Over-engineering | Wrong variant choice | Start simpler |
|
|
6
|
+
| Performance issues | Wrong layer | Add caching/async |
|
|
7
|
+
| Team friction | Complex architecture | Simplify or train |
|
|
8
|
+
| Deployment complexity | Microservice overhead | Consider service layer |
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
*[Tons of Skills](https://tonsofskills.com) by [Intent Solutions](https://intentsolutions.io) | [jeremylongshore.com](https://jeremylongshore.com)*
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
## Examples
|
|
2
|
+
|
|
3
|
+
### Quick Variant Check
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
# Count team size and DAU to select variant
|
|
7
|
+
echo "Team: $(git log --format='%ae' | sort -u | wc -l) developers"
|
|
8
|
+
echo "DAU: Check analytics dashboard"
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
*[Tons of Skills](https://tonsofskills.com) by [Intent Solutions](https://intentsolutions.io) | [jeremylongshore.com](https://jeremylongshore.com)*
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
## Serverless (Edge Functions) and Multi-Tenant Patterns
|
|
2
|
+
|
|
3
|
+
### Edge Functions with Per-Request Clients
|
|
4
|
+
|
|
5
|
+
Edge Functions create a new Supabase client per request, extracting the user's JWT from the Authorization header. This ensures proper RLS scoping.
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
// supabase/functions/api/index.ts
|
|
9
|
+
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
|
|
10
|
+
|
|
11
|
+
Deno.serve(async (req) => {
|
|
12
|
+
// Per-request client with the user's JWT for RLS
|
|
13
|
+
const supabase = createClient(
|
|
14
|
+
Deno.env.get('SUPABASE_URL')!,
|
|
15
|
+
Deno.env.get('SUPABASE_ANON_KEY')!,
|
|
16
|
+
{
|
|
17
|
+
global: {
|
|
18
|
+
headers: { Authorization: req.headers.get('Authorization')! },
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
// This client respects RLS using the user's JWT
|
|
24
|
+
const { data: { user } } = await supabase.auth.getUser()
|
|
25
|
+
if (!user) {
|
|
26
|
+
return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401 })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Queries are scoped to the authenticated user via RLS
|
|
30
|
+
const { data, error } = await supabase
|
|
31
|
+
.from('todos')
|
|
32
|
+
.select('id, title, is_complete')
|
|
33
|
+
.order('created_at', { ascending: false })
|
|
34
|
+
|
|
35
|
+
if (error) {
|
|
36
|
+
return new Response(JSON.stringify({ error: error.message }), { status: 500 })
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return new Response(JSON.stringify(data), {
|
|
40
|
+
headers: { 'Content-Type': 'application/json' },
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Edge Function with Admin Operations
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
// supabase/functions/admin-task/index.ts
|
|
49
|
+
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
|
|
50
|
+
|
|
51
|
+
Deno.serve(async (req) => {
|
|
52
|
+
// Verify the request has a valid admin JWT first
|
|
53
|
+
const userClient = createClient(
|
|
54
|
+
Deno.env.get('SUPABASE_URL')!,
|
|
55
|
+
Deno.env.get('SUPABASE_ANON_KEY')!,
|
|
56
|
+
{ global: { headers: { Authorization: req.headers.get('Authorization')! } } }
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
const { data: { user } } = await userClient.auth.getUser()
|
|
60
|
+
if (!user) {
|
|
61
|
+
return new Response('Unauthorized', { status: 401 })
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Check if user is admin
|
|
65
|
+
const { data: profile } = await userClient
|
|
66
|
+
.from('profiles')
|
|
67
|
+
.select('role')
|
|
68
|
+
.eq('id', user.id)
|
|
69
|
+
.single()
|
|
70
|
+
|
|
71
|
+
if (profile?.role !== 'admin') {
|
|
72
|
+
return new Response('Forbidden', { status: 403 })
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Now use service_role for admin operations
|
|
76
|
+
const adminClient = createClient(
|
|
77
|
+
Deno.env.get('SUPABASE_URL')!,
|
|
78
|
+
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
// Admin operation: get all users
|
|
82
|
+
const { data, error } = await adminClient.auth.admin.listUsers()
|
|
83
|
+
if (error) {
|
|
84
|
+
return new Response(JSON.stringify({ error: error.message }), { status: 500 })
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return new Response(JSON.stringify({ users: data.users.length }))
|
|
88
|
+
})
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Multi-Tenant: RLS-Based Isolation (Recommended)
|
|
92
|
+
|
|
93
|
+
The simplest multi-tenant approach uses a single database with RLS policies scoping all queries to the tenant.
|
|
94
|
+
|
|
95
|
+
```sql
|
|
96
|
+
-- Schema for RLS-based multi-tenancy
|
|
97
|
+
CREATE TABLE public.tenants (
|
|
98
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
99
|
+
name text NOT NULL,
|
|
100
|
+
slug text UNIQUE NOT NULL,
|
|
101
|
+
plan text DEFAULT 'free',
|
|
102
|
+
created_at timestamptz DEFAULT now()
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
CREATE TABLE public.tenant_members (
|
|
106
|
+
tenant_id uuid REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
107
|
+
user_id uuid REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
108
|
+
role text NOT NULL DEFAULT 'member' CHECK (role IN ('owner', 'admin', 'member')),
|
|
109
|
+
PRIMARY KEY (tenant_id, user_id)
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
-- Store current tenant in user's JWT claims (set via custom claims hook)
|
|
113
|
+
-- Or look up from tenant_members table
|
|
114
|
+
|
|
115
|
+
CREATE TABLE public.projects (
|
|
116
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
117
|
+
tenant_id uuid NOT NULL REFERENCES public.tenants(id),
|
|
118
|
+
name text NOT NULL,
|
|
119
|
+
created_at timestamptz DEFAULT now()
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
ALTER TABLE public.projects ENABLE ROW LEVEL SECURITY;
|
|
123
|
+
|
|
124
|
+
-- RLS: users can only see projects belonging to their tenant
|
|
125
|
+
CREATE POLICY "tenant_isolation" ON public.projects
|
|
126
|
+
FOR ALL USING (
|
|
127
|
+
tenant_id IN (
|
|
128
|
+
SELECT tenant_id FROM public.tenant_members
|
|
129
|
+
WHERE user_id = auth.uid()
|
|
130
|
+
)
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
CREATE INDEX idx_projects_tenant_id ON public.projects(tenant_id);
|
|
134
|
+
CREATE INDEX idx_tenant_members_user_id ON public.tenant_members(user_id);
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
// lib/supabase-tenant.ts
|
|
139
|
+
import { createClient } from '@supabase/supabase-js'
|
|
140
|
+
import type { Database } from './database.types'
|
|
141
|
+
|
|
142
|
+
const supabase = createClient<Database>(
|
|
143
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
144
|
+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
// All queries are automatically scoped to the user's tenant via RLS
|
|
148
|
+
export async function getTenantProjects() {
|
|
149
|
+
const { data, error } = await supabase
|
|
150
|
+
.from('projects')
|
|
151
|
+
.select('id, name, created_at, tenant_id')
|
|
152
|
+
.order('created_at', { ascending: false })
|
|
153
|
+
|
|
154
|
+
if (error) throw new Error(`Failed to load projects: ${error.message}`)
|
|
155
|
+
return data // Only returns projects for the authenticated user's tenant(s)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Switch active tenant (for users in multiple tenants)
|
|
159
|
+
export async function getUserTenants() {
|
|
160
|
+
const { data, error } = await supabase
|
|
161
|
+
.from('tenant_members')
|
|
162
|
+
.select(`
|
|
163
|
+
role,
|
|
164
|
+
tenants:tenant_id (id, name, slug, plan)
|
|
165
|
+
`)
|
|
166
|
+
|
|
167
|
+
if (error) throw new Error(`Failed to load tenants: ${error.message}`)
|
|
168
|
+
return data
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Create project in a specific tenant
|
|
172
|
+
export async function createProject(tenantId: string, name: string) {
|
|
173
|
+
const { data, error } = await supabase
|
|
174
|
+
.from('projects')
|
|
175
|
+
.insert({ tenant_id: tenantId, name })
|
|
176
|
+
.select('id, name, tenant_id, created_at')
|
|
177
|
+
.single()
|
|
178
|
+
|
|
179
|
+
if (error) throw new Error(`Failed to create project: ${error.message}`)
|
|
180
|
+
return data
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Output
|
|
185
|
+
|
|
186
|
+
- Next.js SSR setup with server client (cookies-based auth), browser client, and middleware
|
|
187
|
+
- Server Actions using admin client with service_role for privileged operations
|
|
188
|
+
- SPA pattern with singleton client, React Query integration, and auth state listener
|
|
189
|
+
- React Native setup with AsyncStorage, deep link OAuth, and in-app browser
|
|
190
|
+
- Edge Function patterns for per-request auth and admin escalation
|
|
191
|
+
- Multi-tenant RLS isolation with tenant_members lookup and scoped queries
|
|
192
|
+
- Decision matrix for choosing the right architecture per stack
|
|
193
|
+
|
|
194
|
+
## Error Handling
|
|
195
|
+
|
|
196
|
+
| Issue | Cause | Solution |
|
|
197
|
+
|-------|-------|----------|
|
|
198
|
+
| `AuthSessionMissingError` in Server Component | Cookies not passed to Supabase client | Use `createServerClient` from `@supabase/ssr` with cookie handlers |
|
|
199
|
+
| OAuth redirect fails in React Native | Missing deep link scheme | Add `scheme` to app.json and configure Supabase redirect URL |
|
|
200
|
+
| service_role key in client bundle | Wrong env var prefix (`NEXT_PUBLIC_`) | Remove `NEXT_PUBLIC_` prefix; only server code should access it |
|
|
201
|
+
| Multi-tenant data leak | Missing RLS policy or missing tenant_id filter | Verify RLS is enabled and policies check `tenant_members` |
|
|
202
|
+
| Edge Function `auth.getUser()` returns null | Missing Authorization header | Forward user's JWT from the client call |
|
|
203
|
+
| Session not persisting on mobile | AsyncStorage not configured | Pass `AsyncStorage` in auth config; ensure package is installed |
|
|
204
|
+
|
|
205
|
+
## Examples
|
|
206
|
+
|
|
207
|
+
### Test Auth Flow End-to-End (Next.js)
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
// app/auth/callback/route.ts
|
|
211
|
+
import { createSupabaseServer } from '@/lib/supabase/server'
|
|
212
|
+
import { NextResponse } from 'next/server'
|
|
213
|
+
|
|
214
|
+
export async function GET(request: Request) {
|
|
215
|
+
const { searchParams } = new URL(request.url)
|
|
216
|
+
const code = searchParams.get('code')
|
|
217
|
+
|
|
218
|
+
if (code) {
|
|
219
|
+
const supabase = await createSupabaseServer()
|
|
220
|
+
const { error } = await supabase.auth.exchangeCodeForSession(code)
|
|
221
|
+
if (error) {
|
|
222
|
+
return NextResponse.redirect(new URL('/login?error=auth_failed', request.url))
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return NextResponse.redirect(new URL('/dashboard', request.url))
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Verify Tenant Isolation
|
|
231
|
+
|
|
232
|
+
```sql
|
|
233
|
+
-- Test that RLS properly isolates tenants
|
|
234
|
+
SET request.jwt.claims = '{"sub": "user-uuid-1"}';
|
|
235
|
+
|
|
236
|
+
-- Should only return projects for user-uuid-1's tenant
|
|
237
|
+
SELECT * FROM public.projects;
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Resources
|
|
241
|
+
|
|
242
|
+
- [Supabase SSR (Next.js)](https://supabase.com/docs/guides/auth/server-side/nextjs)
|
|
243
|
+
- [Supabase React Native](https://supabase.com/docs/guides/getting-started/tutorials/with-expo-react-native)
|
|
244
|
+
- [Supabase Edge Functions](https://supabase.com/docs/guides/functions)
|
|
245
|
+
- [Multi-Tenant with RLS](https://supabase.com/docs/guides/database/postgres/row-level-security)
|
|
246
|
+
- [Supabase Auth Deep Linking](https://supabase.com/docs/guides/auth/native-mobile-deep-linking)
|
|
247
|
+
- [@supabase/ssr Package](https://supabase.com/docs/guides/auth/server-side/overview)
|
|
248
|
+
|
|
249
|
+
## Next Steps
|
|
250
|
+
|
|
251
|
+
For common mistakes and anti-patterns to avoid, see `supabase-known-pitfalls`.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Variant A: Monolith (Simple)
|
|
2
|
+
|
|
3
|
+
## Variant A: Monolith (Simple)
|
|
4
|
+
|
|
5
|
+
**Best for:** MVPs, small teams, < 10K daily active users
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
my-app/
|
|
9
|
+
├── src/
|
|
10
|
+
│ ├── supabase/
|
|
11
|
+
│ │ ├── client.ts # Singleton client
|
|
12
|
+
│ │ ├── types.ts # Types
|
|
13
|
+
│ │ └── middleware.ts # Express middleware
|
|
14
|
+
│ ├── routes/
|
|
15
|
+
│ │ └── api/
|
|
16
|
+
│ │ └── supabase.ts # API routes
|
|
17
|
+
│ └── index.ts
|
|
18
|
+
├── tests/
|
|
19
|
+
│ └── supabase.test.ts
|
|
20
|
+
└── package.json
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Key Characteristics
|
|
24
|
+
|
|
25
|
+
- Single deployment unit
|
|
26
|
+
- Synchronous Supabase calls in request path
|
|
27
|
+
- In-memory caching
|
|
28
|
+
- Simple error handling
|
|
29
|
+
|
|
30
|
+
### Code Pattern
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
// Direct integration in route handler
|
|
34
|
+
app.post('/api/create', async (req, res) => {
|
|
35
|
+
try {
|
|
36
|
+
const result = await supabaseClient.create(req.body);
|
|
37
|
+
res.json(result);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
res.status(500).json({ error: error.message });
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
package/skills/supabase-architecture-variants/references/variant-b-service-layer-(moderate).md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Variant B: Service Layer (Moderate)
|
|
2
|
+
|
|
3
|
+
## Variant B: Service Layer (Moderate)
|
|
4
|
+
|
|
5
|
+
**Best for:** Growing startups, 10K-100K DAU, multiple integrations
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
my-app/
|
|
9
|
+
├── src/
|
|
10
|
+
│ ├── services/
|
|
11
|
+
│ │ ├── supabase/
|
|
12
|
+
│ │ │ ├── client.ts # Client wrapper
|
|
13
|
+
│ │ │ ├── service.ts # Business logic
|
|
14
|
+
│ │ │ ├── repository.ts # Data access
|
|
15
|
+
│ │ │ └── types.ts
|
|
16
|
+
│ │ └── index.ts # Service exports
|
|
17
|
+
│ ├── controllers/
|
|
18
|
+
│ │ └── supabase.ts
|
|
19
|
+
│ ├── routes/
|
|
20
|
+
│ ├── middleware/
|
|
21
|
+
│ ├── queue/
|
|
22
|
+
│ │ └── supabase-processor.ts # Async processing
|
|
23
|
+
│ └── index.ts
|
|
24
|
+
├── config/
|
|
25
|
+
│ └── supabase/
|
|
26
|
+
└── package.json
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Key Characteristics
|
|
30
|
+
|
|
31
|
+
- Separation of concerns
|
|
32
|
+
- Background job processing
|
|
33
|
+
- Redis caching
|
|
34
|
+
- Circuit breaker pattern
|
|
35
|
+
- Structured error handling
|
|
36
|
+
|
|
37
|
+
### Code Pattern
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
// Service layer abstraction
|
|
41
|
+
class SupabaseService {
|
|
42
|
+
constructor(
|
|
43
|
+
private client: SupabaseClient,
|
|
44
|
+
private cache: CacheService,
|
|
45
|
+
private queue: QueueService
|
|
46
|
+
) {}
|
|
47
|
+
|
|
48
|
+
async createResource(data: CreateInput): Promise<Resource> {
|
|
49
|
+
// Business logic before API call
|
|
50
|
+
const validated = this.validate(data);
|
|
51
|
+
|
|
52
|
+
// Check cache
|
|
53
|
+
const cached = await this.cache.get(cacheKey);
|
|
54
|
+
if (cached) return cached;
|
|
55
|
+
|
|
56
|
+
// API call with retry
|
|
57
|
+
const result = await this.withRetry(() =>
|
|
58
|
+
this.client.create(validated)
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// Cache result
|
|
62
|
+
await this.cache.set(cacheKey, result, 300);
|
|
63
|
+
|
|
64
|
+
// Async follow-up
|
|
65
|
+
await this.queue.enqueue('supabase.post-create', result);
|
|
66
|
+
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Variant C: Microservice (Complex)
|
|
2
|
+
|
|
3
|
+
## Variant C: Microservice (Complex)
|
|
4
|
+
|
|
5
|
+
**Best for:** Enterprise, 100K+ DAU, strict SLAs
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
supabase-service/ # Dedicated microservice
|
|
9
|
+
├── src/
|
|
10
|
+
│ ├── api/
|
|
11
|
+
│ │ ├── grpc/
|
|
12
|
+
│ │ │ └── supabase.proto
|
|
13
|
+
│ │ └── rest/
|
|
14
|
+
│ │ └── routes.ts
|
|
15
|
+
│ ├── domain/
|
|
16
|
+
│ │ ├── entities/
|
|
17
|
+
│ │ ├── events/
|
|
18
|
+
│ │ └── services/
|
|
19
|
+
│ ├── infrastructure/
|
|
20
|
+
│ │ ├── supabase/
|
|
21
|
+
│ │ │ ├── client.ts
|
|
22
|
+
│ │ │ ├── mapper.ts
|
|
23
|
+
│ │ │ └── circuit-breaker.ts
|
|
24
|
+
│ │ ├── cache/
|
|
25
|
+
│ │ ├── queue/
|
|
26
|
+
│ │ └── database/
|
|
27
|
+
│ └── index.ts
|
|
28
|
+
├── config/
|
|
29
|
+
├── k8s/
|
|
30
|
+
│ ├── deployment.yaml
|
|
31
|
+
│ ├── service.yaml
|
|
32
|
+
│ └── hpa.yaml
|
|
33
|
+
└── package.json
|
|
34
|
+
|
|
35
|
+
other-services/
|
|
36
|
+
├── order-service/ # Calls supabase-service
|
|
37
|
+
├── payment-service/
|
|
38
|
+
└── notification-service/
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Key Characteristics
|
|
42
|
+
|
|
43
|
+
- Dedicated Supabase microservice
|
|
44
|
+
- gRPC for internal communication
|
|
45
|
+
- Event-driven architecture
|
|
46
|
+
- Database per service
|
|
47
|
+
- Kubernetes autoscaling
|
|
48
|
+
- Distributed tracing
|
|
49
|
+
- Circuit breaker per service
|
|
50
|
+
|
|
51
|
+
### Code Pattern
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// Event-driven with domain isolation
|
|
55
|
+
class SupabaseAggregate {
|
|
56
|
+
private events: DomainEvent[] = [];
|
|
57
|
+
|
|
58
|
+
process(command: SupabaseCommand): void {
|
|
59
|
+
// Domain logic
|
|
60
|
+
const result = this.execute(command);
|
|
61
|
+
|
|
62
|
+
// Emit domain event
|
|
63
|
+
this.events.push(new SupabaseProcessedEvent(result));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
getUncommittedEvents(): DomainEvent[] {
|
|
67
|
+
return [...this.events];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Event handler
|
|
72
|
+
@EventHandler(SupabaseProcessedEvent)
|
|
73
|
+
class SupabaseEventHandler {
|
|
74
|
+
async handle(event: SupabaseProcessedEvent): Promise<void> {
|
|
75
|
+
// Saga orchestration
|
|
76
|
+
await this.sagaOrchestrator.continue(event);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|