create-claude-webapp 1.0.0 → 1.0.1
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/.claude/agents/acceptance-test-generator.md +256 -0
- package/.claude/agents/auth-flow-designer.md +93 -0
- package/.claude/agents/code-reviewer.md +193 -0
- package/.claude/agents/code-verifier.md +194 -0
- package/.claude/agents/deployment-executor.md +90 -0
- package/.claude/agents/design-sync.md +226 -0
- package/.claude/agents/document-reviewer.md +304 -0
- package/.claude/agents/environment-validator.md +100 -0
- package/.claude/agents/integration-test-reviewer.md +196 -0
- package/.claude/agents/investigator.md +162 -0
- package/.claude/agents/prd-creator.md +220 -0
- package/.claude/agents/quality-fixer-frontend.md +323 -0
- package/.claude/agents/quality-fixer.md +280 -0
- package/.claude/agents/requirement-analyzer.md +149 -0
- package/.claude/agents/rls-policy-designer.md +86 -0
- package/.claude/agents/rule-advisor.md +123 -0
- package/.claude/agents/scope-discoverer.md +231 -0
- package/.claude/agents/solver.md +173 -0
- package/.claude/agents/supabase-migration-generator.md +85 -0
- package/.claude/agents/task-decomposer.md +246 -0
- package/.claude/agents/task-executor-frontend.md +264 -0
- package/.claude/agents/task-executor.md +261 -0
- package/.claude/agents/technical-designer-frontend.md +444 -0
- package/.claude/agents/technical-designer.md +370 -0
- package/.claude/agents/verifier.md +193 -0
- package/.claude/agents/work-planner.md +211 -0
- package/.claude/commands/add-integration-tests.md +116 -0
- package/.claude/commands/build.md +77 -0
- package/.claude/commands/db-migrate.md +96 -0
- package/.claude/commands/deploy.md +95 -0
- package/.claude/commands/design.md +75 -0
- package/.claude/commands/diagnose.md +202 -0
- package/.claude/commands/front-build.md +116 -0
- package/.claude/commands/front-design.md +61 -0
- package/.claude/commands/front-plan.md +53 -0
- package/.claude/commands/front-reverse-design.md +183 -0
- package/.claude/commands/front-review.md +89 -0
- package/.claude/commands/implement.md +80 -0
- package/.claude/commands/local-dev.md +94 -0
- package/.claude/commands/plan.md +61 -0
- package/.claude/commands/project-inject.md +76 -0
- package/.claude/commands/refine-skill.md +207 -0
- package/.claude/commands/reverse-engineer.md +301 -0
- package/.claude/commands/review.md +88 -0
- package/.claude/commands/setup-auth.md +68 -0
- package/.claude/commands/setup-supabase.md +66 -0
- package/.claude/commands/setup-vercel.md +71 -0
- package/.claude/commands/sync-skills.md +116 -0
- package/.claude/commands/task.md +13 -0
- package/.claude/skills/coding-standards/SKILL.md +246 -0
- package/.claude/skills/documentation-criteria/SKILL.md +184 -0
- package/.claude/skills/documentation-criteria/references/adr-template.md +64 -0
- package/.claude/skills/documentation-criteria/references/design-template.md +263 -0
- package/.claude/skills/documentation-criteria/references/plan-template.md +130 -0
- package/.claude/skills/documentation-criteria/references/prd-template.md +109 -0
- package/.claude/skills/documentation-criteria/references/task-template.md +38 -0
- package/.claude/skills/frontend/technical-spec/SKILL.md +147 -0
- package/.claude/skills/frontend/typescript-rules/SKILL.md +136 -0
- package/.claude/skills/frontend/typescript-testing/SKILL.md +129 -0
- package/.claude/skills/fullstack-integration/SKILL.md +466 -0
- package/.claude/skills/implementation-approach/SKILL.md +141 -0
- package/.claude/skills/integration-e2e-testing/SKILL.md +146 -0
- package/.claude/skills/interview/SKILL.md +345 -0
- package/.claude/skills/project-context/SKILL.md +53 -0
- package/.claude/skills/stack-auth/SKILL.md +519 -0
- package/.claude/skills/subagents-orchestration-guide/SKILL.md +218 -0
- package/.claude/skills/supabase/SKILL.md +289 -0
- package/.claude/skills/supabase-edge-functions/SKILL.md +386 -0
- package/.claude/skills/supabase-local/SKILL.md +328 -0
- package/.claude/skills/supabase-testing/SKILL.md +513 -0
- package/.claude/skills/task-analyzer/SKILL.md +131 -0
- package/.claude/skills/task-analyzer/references/skills-index.yaml +375 -0
- package/.claude/skills/technical-spec/SKILL.md +86 -0
- package/.claude/skills/typescript-rules/SKILL.md +121 -0
- package/.claude/skills/typescript-testing/SKILL.md +155 -0
- package/.claude/skills/vercel-deployment/SKILL.md +355 -0
- package/.claude/skills/vercel-edge/SKILL.md +407 -0
- package/README.md +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: supabase-edge-functions
|
|
3
|
+
description: Supabase Edge Functions with Deno runtime. Use when creating serverless functions, webhooks, or API endpoints that run on Supabase Edge.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Supabase Edge Functions
|
|
7
|
+
|
|
8
|
+
## Function Structure
|
|
9
|
+
|
|
10
|
+
### Directory Layout
|
|
11
|
+
```
|
|
12
|
+
supabase/
|
|
13
|
+
functions/
|
|
14
|
+
_shared/ # Shared code between functions
|
|
15
|
+
cors.ts
|
|
16
|
+
supabase.ts
|
|
17
|
+
function-name/
|
|
18
|
+
index.ts # Entry point (required)
|
|
19
|
+
another-function/
|
|
20
|
+
index.ts
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Basic Function Template
|
|
24
|
+
```typescript
|
|
25
|
+
// supabase/functions/hello-world/index.ts
|
|
26
|
+
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
|
|
27
|
+
|
|
28
|
+
serve(async (req: Request) => {
|
|
29
|
+
const { name } = await req.json()
|
|
30
|
+
|
|
31
|
+
return new Response(
|
|
32
|
+
JSON.stringify({ message: `Hello ${name}!` }),
|
|
33
|
+
{
|
|
34
|
+
headers: { 'Content-Type': 'application/json' },
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Type Safety in Deno
|
|
41
|
+
|
|
42
|
+
### Import Maps
|
|
43
|
+
```json
|
|
44
|
+
// supabase/functions/import_map.json
|
|
45
|
+
{
|
|
46
|
+
"imports": {
|
|
47
|
+
"@supabase/supabase-js": "https://esm.sh/@supabase/supabase-js@2",
|
|
48
|
+
"shared/": "../_shared/"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Type Definitions
|
|
54
|
+
```typescript
|
|
55
|
+
// supabase/functions/_shared/types.ts
|
|
56
|
+
export interface ApiResponse<T> {
|
|
57
|
+
data: T | null
|
|
58
|
+
error: string | null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface UserPayload {
|
|
62
|
+
userId: string
|
|
63
|
+
email: string
|
|
64
|
+
action: string
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Using Types in Functions
|
|
69
|
+
```typescript
|
|
70
|
+
// supabase/functions/process-user/index.ts
|
|
71
|
+
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
|
|
72
|
+
import type { UserPayload, ApiResponse } from 'shared/types.ts'
|
|
73
|
+
|
|
74
|
+
serve(async (req: Request): Promise<Response> => {
|
|
75
|
+
try {
|
|
76
|
+
const payload: UserPayload = await req.json()
|
|
77
|
+
|
|
78
|
+
// Process...
|
|
79
|
+
|
|
80
|
+
const response: ApiResponse<{ processed: boolean }> = {
|
|
81
|
+
data: { processed: true },
|
|
82
|
+
error: null,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return new Response(JSON.stringify(response), {
|
|
86
|
+
headers: { 'Content-Type': 'application/json' },
|
|
87
|
+
})
|
|
88
|
+
} catch (error) {
|
|
89
|
+
const response: ApiResponse<null> = {
|
|
90
|
+
data: null,
|
|
91
|
+
error: error.message,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return new Response(JSON.stringify(response), {
|
|
95
|
+
status: 400,
|
|
96
|
+
headers: { 'Content-Type': 'application/json' },
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Authentication in Edge Functions
|
|
103
|
+
|
|
104
|
+
### Extracting User from JWT
|
|
105
|
+
```typescript
|
|
106
|
+
// supabase/functions/_shared/supabase.ts
|
|
107
|
+
import { createClient } from '@supabase/supabase-js'
|
|
108
|
+
|
|
109
|
+
export const getSupabaseClient = (authHeader: string | null) => {
|
|
110
|
+
return createClient(
|
|
111
|
+
Deno.env.get('SUPABASE_URL')!,
|
|
112
|
+
Deno.env.get('SUPABASE_ANON_KEY')!,
|
|
113
|
+
{
|
|
114
|
+
global: {
|
|
115
|
+
headers: authHeader ? { Authorization: authHeader } : {},
|
|
116
|
+
},
|
|
117
|
+
}
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export const getUser = async (req: Request) => {
|
|
122
|
+
const authHeader = req.headers.get('Authorization')
|
|
123
|
+
|
|
124
|
+
if (!authHeader) {
|
|
125
|
+
return { user: null, error: 'No authorization header' }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const supabase = getSupabaseClient(authHeader)
|
|
129
|
+
const { data: { user }, error } = await supabase.auth.getUser()
|
|
130
|
+
|
|
131
|
+
return { user, error: error?.message ?? null }
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Protected Function Example
|
|
136
|
+
```typescript
|
|
137
|
+
// supabase/functions/protected/index.ts
|
|
138
|
+
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
|
|
139
|
+
import { getUser, getSupabaseClient } from 'shared/supabase.ts'
|
|
140
|
+
import { corsHeaders } from 'shared/cors.ts'
|
|
141
|
+
|
|
142
|
+
serve(async (req: Request) => {
|
|
143
|
+
// Handle CORS preflight
|
|
144
|
+
if (req.method === 'OPTIONS') {
|
|
145
|
+
return new Response('ok', { headers: corsHeaders })
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const { user, error } = await getUser(req)
|
|
149
|
+
|
|
150
|
+
if (error || !user) {
|
|
151
|
+
return new Response(
|
|
152
|
+
JSON.stringify({ error: 'Unauthorized' }),
|
|
153
|
+
{ status: 401, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// User is authenticated, proceed with logic
|
|
158
|
+
const supabase = getSupabaseClient(req.headers.get('Authorization'))
|
|
159
|
+
|
|
160
|
+
const { data, error: dbError } = await supabase
|
|
161
|
+
.from('user_data')
|
|
162
|
+
.select('*')
|
|
163
|
+
.eq('user_id', user.id)
|
|
164
|
+
|
|
165
|
+
return new Response(
|
|
166
|
+
JSON.stringify({ data }),
|
|
167
|
+
{ headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
168
|
+
)
|
|
169
|
+
})
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Service Role Access
|
|
173
|
+
```typescript
|
|
174
|
+
// For admin operations, use service role key
|
|
175
|
+
import { createClient } from '@supabase/supabase-js'
|
|
176
|
+
|
|
177
|
+
const supabaseAdmin = createClient(
|
|
178
|
+
Deno.env.get('SUPABASE_URL')!,
|
|
179
|
+
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
// Bypasses RLS
|
|
183
|
+
const { data } = await supabaseAdmin.from('users').select('*')
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## CORS Handling
|
|
187
|
+
|
|
188
|
+
### Shared CORS Configuration
|
|
189
|
+
```typescript
|
|
190
|
+
// supabase/functions/_shared/cors.ts
|
|
191
|
+
export const corsHeaders = {
|
|
192
|
+
'Access-Control-Allow-Origin': '*',
|
|
193
|
+
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export const handleCors = (req: Request): Response | null => {
|
|
197
|
+
if (req.method === 'OPTIONS') {
|
|
198
|
+
return new Response('ok', { headers: corsHeaders })
|
|
199
|
+
}
|
|
200
|
+
return null
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Using CORS in Functions
|
|
205
|
+
```typescript
|
|
206
|
+
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
|
|
207
|
+
import { corsHeaders, handleCors } from 'shared/cors.ts'
|
|
208
|
+
|
|
209
|
+
serve(async (req: Request) => {
|
|
210
|
+
const corsResponse = handleCors(req)
|
|
211
|
+
if (corsResponse) return corsResponse
|
|
212
|
+
|
|
213
|
+
// Function logic...
|
|
214
|
+
|
|
215
|
+
return new Response(
|
|
216
|
+
JSON.stringify({ success: true }),
|
|
217
|
+
{ headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
218
|
+
)
|
|
219
|
+
})
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Local Development
|
|
223
|
+
|
|
224
|
+
### Running Functions Locally
|
|
225
|
+
```bash
|
|
226
|
+
# Start all functions
|
|
227
|
+
supabase functions serve
|
|
228
|
+
|
|
229
|
+
# Start specific function
|
|
230
|
+
supabase functions serve hello-world
|
|
231
|
+
|
|
232
|
+
# With environment variables
|
|
233
|
+
supabase functions serve --env-file .env.local
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Testing Locally
|
|
237
|
+
```bash
|
|
238
|
+
# Invoke function
|
|
239
|
+
curl -i --location --request POST 'http://127.0.0.1:54321/functions/v1/hello-world' \
|
|
240
|
+
--header 'Authorization: Bearer YOUR_ANON_KEY' \
|
|
241
|
+
--header 'Content-Type: application/json' \
|
|
242
|
+
--data '{"name": "World"}'
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Deployment
|
|
246
|
+
|
|
247
|
+
### Deploy Single Function
|
|
248
|
+
```bash
|
|
249
|
+
supabase functions deploy hello-world
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Deploy All Functions
|
|
253
|
+
```bash
|
|
254
|
+
supabase functions deploy
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Setting Secrets
|
|
258
|
+
```bash
|
|
259
|
+
# Set secret
|
|
260
|
+
supabase secrets set MY_SECRET=value
|
|
261
|
+
|
|
262
|
+
# List secrets
|
|
263
|
+
supabase secrets list
|
|
264
|
+
|
|
265
|
+
# Unset secret
|
|
266
|
+
supabase secrets unset MY_SECRET
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Webhooks Pattern
|
|
270
|
+
|
|
271
|
+
### Database Webhook Handler
|
|
272
|
+
```typescript
|
|
273
|
+
// supabase/functions/on-user-created/index.ts
|
|
274
|
+
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
|
|
275
|
+
|
|
276
|
+
interface WebhookPayload {
|
|
277
|
+
type: 'INSERT' | 'UPDATE' | 'DELETE'
|
|
278
|
+
table: string
|
|
279
|
+
record: Record<string, unknown>
|
|
280
|
+
schema: string
|
|
281
|
+
old_record: Record<string, unknown> | null
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
serve(async (req: Request) => {
|
|
285
|
+
const payload: WebhookPayload = await req.json()
|
|
286
|
+
|
|
287
|
+
if (payload.type === 'INSERT' && payload.table === 'users') {
|
|
288
|
+
// Send welcome email, create related records, etc.
|
|
289
|
+
console.log('New user created:', payload.record)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return new Response(JSON.stringify({ received: true }), {
|
|
293
|
+
headers: { 'Content-Type': 'application/json' },
|
|
294
|
+
})
|
|
295
|
+
})
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Setting Up Database Webhook
|
|
299
|
+
```sql
|
|
300
|
+
-- In Supabase Studio or migration
|
|
301
|
+
CREATE OR REPLACE FUNCTION notify_user_created()
|
|
302
|
+
RETURNS trigger AS $$
|
|
303
|
+
BEGIN
|
|
304
|
+
PERFORM net.http_post(
|
|
305
|
+
url := 'https://YOUR_PROJECT.supabase.co/functions/v1/on-user-created',
|
|
306
|
+
headers := '{"Authorization": "Bearer ' || current_setting('app.service_role_key') || '"}'::jsonb,
|
|
307
|
+
body := jsonb_build_object(
|
|
308
|
+
'type', TG_OP,
|
|
309
|
+
'table', TG_TABLE_NAME,
|
|
310
|
+
'record', row_to_json(NEW),
|
|
311
|
+
'old_record', row_to_json(OLD)
|
|
312
|
+
)
|
|
313
|
+
);
|
|
314
|
+
RETURN NEW;
|
|
315
|
+
END;
|
|
316
|
+
$$ LANGUAGE plpgsql;
|
|
317
|
+
|
|
318
|
+
CREATE TRIGGER on_user_created
|
|
319
|
+
AFTER INSERT ON users
|
|
320
|
+
FOR EACH ROW
|
|
321
|
+
EXECUTE FUNCTION notify_user_created();
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## Environment Variables
|
|
325
|
+
|
|
326
|
+
### Available by Default
|
|
327
|
+
| Variable | Description |
|
|
328
|
+
|----------|-------------|
|
|
329
|
+
| `SUPABASE_URL` | Project URL |
|
|
330
|
+
| `SUPABASE_ANON_KEY` | Anon/public key |
|
|
331
|
+
| `SUPABASE_SERVICE_ROLE_KEY` | Service role key |
|
|
332
|
+
| `SUPABASE_DB_URL` | Direct database connection |
|
|
333
|
+
|
|
334
|
+
### Accessing Environment Variables
|
|
335
|
+
```typescript
|
|
336
|
+
const supabaseUrl = Deno.env.get('SUPABASE_URL')!
|
|
337
|
+
const customSecret = Deno.env.get('MY_CUSTOM_SECRET')!
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## Error Handling Best Practices
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
serve(async (req: Request) => {
|
|
344
|
+
try {
|
|
345
|
+
// Validate request method
|
|
346
|
+
if (req.method !== 'POST') {
|
|
347
|
+
return new Response(
|
|
348
|
+
JSON.stringify({ error: 'Method not allowed' }),
|
|
349
|
+
{ status: 405, headers: { 'Content-Type': 'application/json' } }
|
|
350
|
+
)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Validate content type
|
|
354
|
+
const contentType = req.headers.get('content-type')
|
|
355
|
+
if (!contentType?.includes('application/json')) {
|
|
356
|
+
return new Response(
|
|
357
|
+
JSON.stringify({ error: 'Content-Type must be application/json' }),
|
|
358
|
+
{ status: 400, headers: { 'Content-Type': 'application/json' } }
|
|
359
|
+
)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Parse and validate body
|
|
363
|
+
const body = await req.json()
|
|
364
|
+
if (!body.requiredField) {
|
|
365
|
+
return new Response(
|
|
366
|
+
JSON.stringify({ error: 'requiredField is required' }),
|
|
367
|
+
{ status: 400, headers: { 'Content-Type': 'application/json' } }
|
|
368
|
+
)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Process...
|
|
372
|
+
return new Response(
|
|
373
|
+
JSON.stringify({ success: true }),
|
|
374
|
+
{ headers: { 'Content-Type': 'application/json' } }
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
} catch (error) {
|
|
378
|
+
console.error('Function error:', error)
|
|
379
|
+
|
|
380
|
+
return new Response(
|
|
381
|
+
JSON.stringify({ error: 'Internal server error' }),
|
|
382
|
+
{ status: 500, headers: { 'Content-Type': 'application/json' } }
|
|
383
|
+
)
|
|
384
|
+
}
|
|
385
|
+
})
|
|
386
|
+
```
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: supabase-local
|
|
3
|
+
description: Local Supabase development with CLI, migrations, and seeding. Use when setting up local development environment, running migrations, or managing local Supabase instance.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Supabase Local Development
|
|
7
|
+
|
|
8
|
+
## CLI Commands Reference
|
|
9
|
+
|
|
10
|
+
### Essential Commands
|
|
11
|
+
|
|
12
|
+
| Command | Purpose |
|
|
13
|
+
|---------|---------|
|
|
14
|
+
| `supabase start` | Start local Supabase stack |
|
|
15
|
+
| `supabase stop` | Stop local Supabase stack |
|
|
16
|
+
| `supabase status` | Show running services and URLs |
|
|
17
|
+
| `supabase db reset` | Reset database and apply migrations |
|
|
18
|
+
| `supabase db diff` | Generate migration from schema changes |
|
|
19
|
+
| `supabase db push` | Push migrations to remote |
|
|
20
|
+
|
|
21
|
+
### Starting Local Development
|
|
22
|
+
```bash
|
|
23
|
+
# Initialize Supabase in project (first time)
|
|
24
|
+
supabase init
|
|
25
|
+
|
|
26
|
+
# Start local stack
|
|
27
|
+
supabase start
|
|
28
|
+
|
|
29
|
+
# Output includes local URLs and keys:
|
|
30
|
+
# API URL: http://127.0.0.1:54321
|
|
31
|
+
# GraphQL URL: http://127.0.0.1:54321/graphql/v1
|
|
32
|
+
# DB URL: postgresql://postgres:postgres@127.0.0.1:54322/postgres
|
|
33
|
+
# Studio URL: http://127.0.0.1:54323
|
|
34
|
+
# Inbucket URL: http://127.0.0.1:54324
|
|
35
|
+
# anon key: eyJ...
|
|
36
|
+
# service_role key: eyJ...
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Stopping and Resetting
|
|
40
|
+
```bash
|
|
41
|
+
# Stop (preserves data)
|
|
42
|
+
supabase stop
|
|
43
|
+
|
|
44
|
+
# Stop and reset all data
|
|
45
|
+
supabase stop --no-backup
|
|
46
|
+
|
|
47
|
+
# Reset database (reapply all migrations + seed)
|
|
48
|
+
supabase db reset
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Local Configuration
|
|
52
|
+
|
|
53
|
+
### config.toml Structure
|
|
54
|
+
```toml
|
|
55
|
+
# supabase/config.toml
|
|
56
|
+
|
|
57
|
+
[api]
|
|
58
|
+
enabled = true
|
|
59
|
+
port = 54321
|
|
60
|
+
schemas = ["public", "graphql_public"]
|
|
61
|
+
extra_search_path = ["public", "extensions"]
|
|
62
|
+
max_rows = 1000
|
|
63
|
+
|
|
64
|
+
[db]
|
|
65
|
+
port = 54322
|
|
66
|
+
shadow_port = 54320
|
|
67
|
+
major_version = 15
|
|
68
|
+
|
|
69
|
+
[studio]
|
|
70
|
+
enabled = true
|
|
71
|
+
port = 54323
|
|
72
|
+
api_url = "http://127.0.0.1"
|
|
73
|
+
|
|
74
|
+
[inbucket]
|
|
75
|
+
enabled = true
|
|
76
|
+
port = 54324
|
|
77
|
+
smtp_port = 54325
|
|
78
|
+
pop3_port = 54326
|
|
79
|
+
|
|
80
|
+
[auth]
|
|
81
|
+
enabled = true
|
|
82
|
+
site_url = "http://127.0.0.1:3000"
|
|
83
|
+
additional_redirect_urls = ["https://127.0.0.1:3000"]
|
|
84
|
+
jwt_expiry = 3600
|
|
85
|
+
enable_signup = true
|
|
86
|
+
enable_anonymous_sign_ins = false
|
|
87
|
+
|
|
88
|
+
[auth.email]
|
|
89
|
+
enable_signup = true
|
|
90
|
+
double_confirm_changes = true
|
|
91
|
+
enable_confirmations = false
|
|
92
|
+
|
|
93
|
+
[auth.sms]
|
|
94
|
+
enable_signup = false
|
|
95
|
+
enable_confirmations = false
|
|
96
|
+
|
|
97
|
+
[storage]
|
|
98
|
+
enabled = true
|
|
99
|
+
file_size_limit = "50MiB"
|
|
100
|
+
|
|
101
|
+
[edge_runtime]
|
|
102
|
+
enabled = true
|
|
103
|
+
policy = "per_worker"
|
|
104
|
+
inspector_port = 8083
|
|
105
|
+
|
|
106
|
+
[analytics]
|
|
107
|
+
enabled = false
|
|
108
|
+
port = 54327
|
|
109
|
+
vector_port = 54328
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Port Allocation Summary
|
|
113
|
+
|
|
114
|
+
| Service | Default Port |
|
|
115
|
+
|---------|-------------|
|
|
116
|
+
| API | 54321 |
|
|
117
|
+
| Database | 54322 |
|
|
118
|
+
| Studio | 54323 |
|
|
119
|
+
| Inbucket | 54324 |
|
|
120
|
+
| SMTP | 54325 |
|
|
121
|
+
| POP3 | 54326 |
|
|
122
|
+
| Analytics | 54327 |
|
|
123
|
+
|
|
124
|
+
## Migration Workflow
|
|
125
|
+
|
|
126
|
+
### Creating Migrations
|
|
127
|
+
|
|
128
|
+
**Option 1: Manual Creation**
|
|
129
|
+
```bash
|
|
130
|
+
# Create empty migration file
|
|
131
|
+
supabase migration new create_users_table
|
|
132
|
+
|
|
133
|
+
# Edit: supabase/migrations/TIMESTAMP_create_users_table.sql
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Option 2: Generate from Schema Changes**
|
|
137
|
+
```bash
|
|
138
|
+
# Make changes in Studio, then:
|
|
139
|
+
supabase db diff -f add_avatar_to_users
|
|
140
|
+
|
|
141
|
+
# Creates: supabase/migrations/TIMESTAMP_add_avatar_to_users.sql
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Option 3: Pull from Remote**
|
|
145
|
+
```bash
|
|
146
|
+
# Pull schema from remote project
|
|
147
|
+
supabase db pull
|
|
148
|
+
|
|
149
|
+
# Creates migration files from remote schema
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Applying Migrations
|
|
153
|
+
```bash
|
|
154
|
+
# Apply to local
|
|
155
|
+
supabase db reset # Resets and applies all migrations
|
|
156
|
+
|
|
157
|
+
# Push to remote
|
|
158
|
+
supabase db push
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Migration File Example
|
|
162
|
+
```sql
|
|
163
|
+
-- supabase/migrations/20240115120000_create_users_table.sql
|
|
164
|
+
|
|
165
|
+
-- Create users table
|
|
166
|
+
CREATE TABLE public.users (
|
|
167
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
168
|
+
email TEXT UNIQUE NOT NULL,
|
|
169
|
+
name TEXT,
|
|
170
|
+
avatar_url TEXT,
|
|
171
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
172
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
-- Enable RLS
|
|
176
|
+
ALTER TABLE public.users ENABLE ROW LEVEL SECURITY;
|
|
177
|
+
|
|
178
|
+
-- Create policies
|
|
179
|
+
CREATE POLICY "Users can view own profile"
|
|
180
|
+
ON public.users FOR SELECT
|
|
181
|
+
TO authenticated
|
|
182
|
+
USING (auth.uid() = id);
|
|
183
|
+
|
|
184
|
+
CREATE POLICY "Users can update own profile"
|
|
185
|
+
ON public.users FOR UPDATE
|
|
186
|
+
TO authenticated
|
|
187
|
+
USING (auth.uid() = id)
|
|
188
|
+
WITH CHECK (auth.uid() = id);
|
|
189
|
+
|
|
190
|
+
-- Create updated_at trigger
|
|
191
|
+
CREATE OR REPLACE FUNCTION update_updated_at()
|
|
192
|
+
RETURNS TRIGGER AS $$
|
|
193
|
+
BEGIN
|
|
194
|
+
NEW.updated_at = now();
|
|
195
|
+
RETURN NEW;
|
|
196
|
+
END;
|
|
197
|
+
$$ LANGUAGE plpgsql;
|
|
198
|
+
|
|
199
|
+
CREATE TRIGGER update_users_updated_at
|
|
200
|
+
BEFORE UPDATE ON public.users
|
|
201
|
+
FOR EACH ROW
|
|
202
|
+
EXECUTE FUNCTION update_updated_at();
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Seeding Data
|
|
206
|
+
|
|
207
|
+
### Seed File Location
|
|
208
|
+
```
|
|
209
|
+
supabase/seed.sql
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Seed File Example
|
|
213
|
+
```sql
|
|
214
|
+
-- supabase/seed.sql
|
|
215
|
+
|
|
216
|
+
-- Insert test users (for development only)
|
|
217
|
+
INSERT INTO auth.users (id, email, encrypted_password, email_confirmed_at)
|
|
218
|
+
VALUES
|
|
219
|
+
('d0d8c4c0-0000-0000-0000-000000000001', 'alice@example.com', crypt('password123', gen_salt('bf')), now()),
|
|
220
|
+
('d0d8c4c0-0000-0000-0000-000000000002', 'bob@example.com', crypt('password123', gen_salt('bf')), now());
|
|
221
|
+
|
|
222
|
+
-- Insert corresponding profiles
|
|
223
|
+
INSERT INTO public.users (id, email, name)
|
|
224
|
+
VALUES
|
|
225
|
+
('d0d8c4c0-0000-0000-0000-000000000001', 'alice@example.com', 'Alice'),
|
|
226
|
+
('d0d8c4c0-0000-0000-0000-000000000002', 'bob@example.com', 'Bob');
|
|
227
|
+
|
|
228
|
+
-- Insert sample data
|
|
229
|
+
INSERT INTO public.posts (id, user_id, title, content, published)
|
|
230
|
+
VALUES
|
|
231
|
+
(gen_random_uuid(), 'd0d8c4c0-0000-0000-0000-000000000001', 'First Post', 'Hello World!', true),
|
|
232
|
+
(gen_random_uuid(), 'd0d8c4c0-0000-0000-0000-000000000002', 'Another Post', 'Testing...', false);
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Running Seeds
|
|
236
|
+
```bash
|
|
237
|
+
# Seeds run automatically on db reset
|
|
238
|
+
supabase db reset
|
|
239
|
+
|
|
240
|
+
# Or manually
|
|
241
|
+
psql -h 127.0.0.1 -p 54322 -U postgres -d postgres -f supabase/seed.sql
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## Local vs Production Parity
|
|
245
|
+
|
|
246
|
+
### Environment-Specific Configurations
|
|
247
|
+
|
|
248
|
+
| Setting | Local | Production |
|
|
249
|
+
|---------|-------|------------|
|
|
250
|
+
| Email confirmation | Disabled | Enabled |
|
|
251
|
+
| JWT expiry | 3600s (testing) | 3600s+ (security) |
|
|
252
|
+
| Anonymous sign-ins | Disabled | As needed |
|
|
253
|
+
| SMTP | Inbucket (fake) | Real provider |
|
|
254
|
+
|
|
255
|
+
### Testing Email Locally
|
|
256
|
+
```bash
|
|
257
|
+
# Emails captured in Inbucket
|
|
258
|
+
# Access: http://127.0.0.1:54324
|
|
259
|
+
|
|
260
|
+
# No real emails sent in local development
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Environment Variables for Local
|
|
264
|
+
```env
|
|
265
|
+
# .env.local
|
|
266
|
+
NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321
|
|
267
|
+
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ... # From supabase start output
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Common Local Development Tasks
|
|
271
|
+
|
|
272
|
+
### Inspect Database
|
|
273
|
+
```bash
|
|
274
|
+
# Connect to local database
|
|
275
|
+
psql postgresql://postgres:postgres@127.0.0.1:54322/postgres
|
|
276
|
+
|
|
277
|
+
# Or use Studio
|
|
278
|
+
open http://127.0.0.1:54323
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### View Logs
|
|
282
|
+
```bash
|
|
283
|
+
# All services
|
|
284
|
+
supabase logs
|
|
285
|
+
|
|
286
|
+
# Specific service
|
|
287
|
+
supabase logs --api
|
|
288
|
+
supabase logs --db
|
|
289
|
+
supabase logs --auth
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Type Generation
|
|
293
|
+
```bash
|
|
294
|
+
# Generate types from local schema
|
|
295
|
+
supabase gen types typescript --local > src/types/database.types.ts
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## Troubleshooting
|
|
299
|
+
|
|
300
|
+
### Port Conflicts
|
|
301
|
+
```bash
|
|
302
|
+
# Check what's using the port
|
|
303
|
+
lsof -i :54321
|
|
304
|
+
|
|
305
|
+
# Use different ports in config.toml
|
|
306
|
+
[api]
|
|
307
|
+
port = 54421
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Docker Issues
|
|
311
|
+
```bash
|
|
312
|
+
# Ensure Docker is running
|
|
313
|
+
docker info
|
|
314
|
+
|
|
315
|
+
# Reset Supabase Docker containers
|
|
316
|
+
supabase stop --no-backup
|
|
317
|
+
docker system prune -f
|
|
318
|
+
supabase start
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Migration Conflicts
|
|
322
|
+
```bash
|
|
323
|
+
# If migrations are out of sync
|
|
324
|
+
supabase db reset # Local only
|
|
325
|
+
|
|
326
|
+
# For remote, may need manual intervention
|
|
327
|
+
supabase migration repair --status reverted MIGRATION_VERSION
|
|
328
|
+
```
|