@odvi/create-dtt-framework 0.1.2 → 0.1.5
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/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +16 -13
- package/dist/commands/create.js.map +1 -1
- package/dist/utils/template.d.ts.map +1 -1
- package/dist/utils/template.js +5 -0
- package/dist/utils/template.js.map +1 -1
- package/package.json +3 -2
- package/template/.env.example +103 -0
- package/template/components.json +22 -0
- package/template/docs/framework/01-overview.md +289 -0
- package/template/docs/framework/02-techstack.md +503 -0
- package/template/docs/framework/api-layer.md +681 -0
- package/template/docs/framework/clerk-authentication.md +649 -0
- package/template/docs/framework/cli-installation.md +564 -0
- package/template/docs/framework/deployment/ci-cd.md +907 -0
- package/template/docs/framework/deployment/digitalocean.md +991 -0
- package/template/docs/framework/deployment/domain-setup.md +972 -0
- package/template/docs/framework/deployment/environment-variables.md +863 -0
- package/template/docs/framework/deployment/monitoring.md +927 -0
- package/template/docs/framework/deployment/production-checklist.md +649 -0
- package/template/docs/framework/deployment/vercel.md +791 -0
- package/template/docs/framework/environment-variables.md +658 -0
- package/template/docs/framework/health-check-system.md +582 -0
- package/template/docs/framework/implementation.md +559 -0
- package/template/docs/framework/snowflake-integration.md +591 -0
- package/template/docs/framework/state-management.md +615 -0
- package/template/docs/framework/supabase-integration.md +581 -0
- package/template/docs/framework/testing-guide.md +544 -0
- package/template/docs/framework/what-did-i-miss.md +526 -0
- package/template/drizzle.config.ts +12 -0
- package/template/next.config.js +21 -0
- package/template/postcss.config.js +5 -0
- package/template/prettier.config.js +4 -0
- package/template/public/favicon.ico +0 -0
- package/template/src/app/(auth)/layout.tsx +4 -0
- package/template/src/app/(auth)/sign-in/[[...sign-in]]/page.tsx +10 -0
- package/template/src/app/(auth)/sign-up/[[...sign-up]]/page.tsx +10 -0
- package/template/src/app/(dashboard)/dashboard/page.tsx +8 -0
- package/template/src/app/(dashboard)/health/page.tsx +16 -0
- package/template/src/app/(dashboard)/layout.tsx +17 -0
- package/template/src/app/api/[[...route]]/route.ts +11 -0
- package/template/src/app/api/debug-files/route.ts +33 -0
- package/template/src/app/api/webhooks/clerk/route.ts +112 -0
- package/template/src/app/layout.tsx +28 -0
- package/template/src/app/page.tsx +12 -0
- package/template/src/app/providers.tsx +20 -0
- package/template/src/components/layouts/navbar.tsx +14 -0
- package/template/src/components/shared/loading-spinner.tsx +6 -0
- package/template/src/components/ui/badge.tsx +46 -0
- package/template/src/components/ui/button.tsx +62 -0
- package/template/src/components/ui/card.tsx +92 -0
- package/template/src/components/ui/collapsible.tsx +33 -0
- package/template/src/components/ui/scroll-area.tsx +58 -0
- package/template/src/components/ui/sheet.tsx +139 -0
- package/template/src/config/__tests__/env.test.ts +166 -0
- package/template/src/config/__tests__/site.test.ts +46 -0
- package/template/src/config/env.ts +36 -0
- package/template/src/config/site.ts +10 -0
- package/template/src/env.js +44 -0
- package/template/src/features/__tests__/health-check-config.test.ts +142 -0
- package/template/src/features/__tests__/health-check-types.test.ts +201 -0
- package/template/src/features/documentation/components/doc-sidebar.tsx +109 -0
- package/template/src/features/documentation/components/doc-viewer.tsx +70 -0
- package/template/src/features/documentation/index.tsx +92 -0
- package/template/src/features/documentation/utils/doc-loader.ts +177 -0
- package/template/src/features/health-check/components/health-dashboard.tsx +363 -0
- package/template/src/features/health-check/config.ts +72 -0
- package/template/src/features/health-check/index.ts +4 -0
- package/template/src/features/health-check/stores/health-store.ts +14 -0
- package/template/src/features/health-check/types.ts +18 -0
- package/template/src/hooks/__tests__/use-debounce.test.tsx +28 -0
- package/template/src/hooks/queries/use-health-checks.ts +16 -0
- package/template/src/hooks/utils/use-debounce.ts +20 -0
- package/template/src/lib/__tests__/utils.test.ts +52 -0
- package/template/src/lib/__tests__/validators.test.ts +114 -0
- package/template/src/lib/nextbank/client.ts +37 -0
- package/template/src/lib/snowflake/client.ts +53 -0
- package/template/src/lib/supabase/admin.ts +7 -0
- package/template/src/lib/supabase/client.ts +7 -0
- package/template/src/lib/supabase/server.ts +23 -0
- package/template/src/lib/utils.ts +6 -0
- package/template/src/lib/validators.ts +9 -0
- package/template/src/middleware.ts +22 -0
- package/template/src/server/api/index.ts +22 -0
- package/template/src/server/api/middleware/auth.ts +19 -0
- package/template/src/server/api/middleware/logger.ts +4 -0
- package/template/src/server/api/routes/health/clerk.ts +214 -0
- package/template/src/server/api/routes/health/database.ts +117 -0
- package/template/src/server/api/routes/health/edge-functions.ts +75 -0
- package/template/src/server/api/routes/health/framework.ts +45 -0
- package/template/src/server/api/routes/health/index.ts +102 -0
- package/template/src/server/api/routes/health/nextbank.ts +67 -0
- package/template/src/server/api/routes/health/snowflake.ts +83 -0
- package/template/src/server/api/routes/health/storage.ts +163 -0
- package/template/src/server/api/routes/users.ts +95 -0
- package/template/src/server/db/index.ts +17 -0
- package/template/src/server/db/queries/users.ts +8 -0
- package/template/src/server/db/schema/__tests__/health-checks.test.ts +31 -0
- package/template/src/server/db/schema/__tests__/users.test.ts +46 -0
- package/template/src/server/db/schema/health-checks.ts +11 -0
- package/template/src/server/db/schema/index.ts +2 -0
- package/template/src/server/db/schema/users.ts +16 -0
- package/template/src/server/db/schema.ts +26 -0
- package/template/src/stores/__tests__/ui-store.test.ts +87 -0
- package/template/src/stores/ui-store.ts +14 -0
- package/template/src/styles/globals.css +129 -0
- package/template/src/test/mocks/clerk.ts +35 -0
- package/template/src/test/mocks/snowflake.ts +28 -0
- package/template/src/test/mocks/supabase.ts +37 -0
- package/template/src/test/setup.ts +69 -0
- package/template/src/test/utils/test-helpers.ts +158 -0
- package/template/src/types/index.ts +14 -0
- package/template/tsconfig.json +43 -0
- package/template/vitest.config.ts +44 -0
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
# DTT Framework - Supabase Integration
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The DTT Framework uses [Supabase](https://supabase.com/) as the primary database and storage solution. Supabase provides a PostgreSQL database, file storage, and edge functions, all managed through a single platform.
|
|
6
|
+
|
|
7
|
+
### Why Supabase?
|
|
8
|
+
|
|
9
|
+
- **PostgreSQL**: Powerful, open-source relational database
|
|
10
|
+
- **Managed Service**: No database administration required
|
|
11
|
+
- **Real-time**: Real-time database subscriptions (not currently used)
|
|
12
|
+
- **Storage**: Built-in S3-compatible file storage
|
|
13
|
+
- **Edge Functions**: Serverless compute with Deno runtime
|
|
14
|
+
- **Open Source**: Self-hostable if needed
|
|
15
|
+
- **Connection Pooling**: Transaction mode for serverless optimization
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Database Setup with Drizzle ORM
|
|
20
|
+
|
|
21
|
+
### 1. Create a Supabase Project
|
|
22
|
+
|
|
23
|
+
1. Go to [supabase.com](https://supabase.com/) and sign up
|
|
24
|
+
2. Create a new project
|
|
25
|
+
3. Wait for the database to be provisioned
|
|
26
|
+
4. Get your project URL and anon key from the dashboard
|
|
27
|
+
|
|
28
|
+
### 2. Install Dependencies
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pnpm add drizzle-orm postgres @supabase/supabase-js @supabase/ssr
|
|
32
|
+
pnpm add -D drizzle-kit
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 3. Configure Environment Variables
|
|
36
|
+
|
|
37
|
+
Add the following to your [`.env`](./environment-variables.md) file:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Supabase
|
|
41
|
+
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
|
|
42
|
+
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJxxx
|
|
43
|
+
SUPABASE_SERVICE_ROLE_KEY=eyJxxx
|
|
44
|
+
DATABASE_URL=postgresql://postgres.[ref]:[password]@aws-0-[region].pooler.supabase.com:6543/postgres
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Where to find these keys:**
|
|
48
|
+
|
|
49
|
+
- **Supabase URL**: Supabase Dashboard → Your Project → Settings → API → Project URL
|
|
50
|
+
- **Anon Key**: Supabase Dashboard → Your Project → Settings → API → anon/public key
|
|
51
|
+
- **Service Role Key**: Supabase Dashboard → Your Project → Settings → API → service_role key
|
|
52
|
+
- **Database URL**: Supabase Dashboard → Your Project → Settings → Database → Connection String → Transaction mode
|
|
53
|
+
|
|
54
|
+
### 4. Configure Drizzle
|
|
55
|
+
|
|
56
|
+
Create [`drizzle.config.ts`](../../drizzle.config.ts):
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { defineConfig } from 'drizzle-kit'
|
|
60
|
+
|
|
61
|
+
export default defineConfig({
|
|
62
|
+
schema: './src/server/db/schema/index.ts',
|
|
63
|
+
out: './src/server/db/migrations',
|
|
64
|
+
dialect: 'postgresql',
|
|
65
|
+
dbCredentials: { url: process.env.DATABASE_URL! },
|
|
66
|
+
})
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 5. Create Database Client
|
|
70
|
+
|
|
71
|
+
Create [`src/server/db/index.ts`](../../src/server/db/index.ts):
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { drizzle } from 'drizzle-orm/postgres-js'
|
|
75
|
+
import postgres from 'postgres'
|
|
76
|
+
import { env } from '@/config/env'
|
|
77
|
+
import * as schema from './schema'
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Cache the database connection in development. This avoids creating a new connection on every HMR
|
|
81
|
+
* update.
|
|
82
|
+
*/
|
|
83
|
+
const globalForDb = globalThis as unknown as {
|
|
84
|
+
conn: postgres.Sql | undefined
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const conn = globalForDb.conn ?? postgres(env.DATABASE_URL, { prepare: false })
|
|
88
|
+
if (env.NODE_ENV !== 'production') globalForDb.conn = conn
|
|
89
|
+
|
|
90
|
+
export const db = drizzle(conn, { schema })
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Important:** The `prepare: false` option is required for Supabase Transaction mode connection pooling.
|
|
94
|
+
|
|
95
|
+
### 6. Define Database Schema
|
|
96
|
+
|
|
97
|
+
Create schema files in [`src/server/db/schema/`](../../src/server/db/schema/):
|
|
98
|
+
|
|
99
|
+
**users.ts:**
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { pgTable, text, timestamp, varchar } from 'drizzle-orm/pg-core'
|
|
103
|
+
|
|
104
|
+
export const users = pgTable('users', {
|
|
105
|
+
id: text('id').primaryKey(), // Clerk user ID
|
|
106
|
+
email: varchar('email', { length: 255 }).notNull().unique(),
|
|
107
|
+
firstName: varchar('first_name', { length: 255 }),
|
|
108
|
+
lastName: varchar('last_name', { length: 255 }),
|
|
109
|
+
imageUrl: text('image_url'),
|
|
110
|
+
clerkOrgId: text('clerk_org_id'),
|
|
111
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
112
|
+
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
export type User = typeof users.$inferSelect
|
|
116
|
+
export type NewUser = typeof users.$inferInsert
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**health-checks.ts:**
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'
|
|
123
|
+
|
|
124
|
+
export const healthCheckTests = pgTable('health_check_tests', {
|
|
125
|
+
id: uuid('id').primaryKey().defaultRandom(),
|
|
126
|
+
testKey: text('test_key').notNull(),
|
|
127
|
+
testValue: text('test_value'),
|
|
128
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
export type HealthCheckTest = typeof healthCheckTests.$inferSelect
|
|
132
|
+
export type NewHealthCheckTest = typeof healthCheckTests.$inferInsert
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**index.ts:**
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
export * from './users'
|
|
139
|
+
export * from './health-checks'
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 7. Generate and Run Migrations
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
# Generate migration files
|
|
146
|
+
pnpm db:generate
|
|
147
|
+
|
|
148
|
+
# Push schema to database (development)
|
|
149
|
+
pnpm db:push
|
|
150
|
+
|
|
151
|
+
# Or run migrations (production)
|
|
152
|
+
pnpm db:migrate
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Storage Configuration
|
|
158
|
+
|
|
159
|
+
### Supabase Storage Setup
|
|
160
|
+
|
|
161
|
+
### 1. Create Storage Buckets
|
|
162
|
+
|
|
163
|
+
Go to Supabase Dashboard → Your Project → Storage → Create a new bucket:
|
|
164
|
+
|
|
165
|
+
- **Bucket Name**: `health-check-bucket` (or your preferred name)
|
|
166
|
+
- **Public Bucket**: No (for security)
|
|
167
|
+
|
|
168
|
+
### 2. Configure Storage Clients
|
|
169
|
+
|
|
170
|
+
Create [`src/lib/supabase/client.ts`](../../src/lib/supabase/client.ts):
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
import { createClient } from '@supabase/supabase-js'
|
|
174
|
+
import { env } from '@/config/env'
|
|
175
|
+
|
|
176
|
+
export const supabase = createClient(
|
|
177
|
+
env.NEXT_PUBLIC_SUPABASE_URL,
|
|
178
|
+
env.NEXT_PUBLIC_SUPABASE_ANON_KEY
|
|
179
|
+
)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Create [`src/lib/supabase/admin.ts`](../../src/lib/supabase/admin.ts):
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
import { createClient } from '@supabase/supabase-js'
|
|
186
|
+
import { env } from '@/config/env'
|
|
187
|
+
|
|
188
|
+
export const supabaseAdmin = createClient(
|
|
189
|
+
env.NEXT_PUBLIC_SUPABASE_URL,
|
|
190
|
+
env.SUPABASE_SERVICE_ROLE_KEY
|
|
191
|
+
)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**Note:** Use `supabaseAdmin` for server-side operations that bypass RLS (Row Level Security).
|
|
195
|
+
|
|
196
|
+
### 3. Storage Operations
|
|
197
|
+
|
|
198
|
+
**Upload a file:**
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
import { supabaseAdmin } from '@/lib/supabase/admin'
|
|
202
|
+
|
|
203
|
+
async function uploadFile(file: File, path: string) {
|
|
204
|
+
const { data, error } = await supabaseAdmin.storage
|
|
205
|
+
.from('your-bucket')
|
|
206
|
+
.upload(path, file, { upsert: true })
|
|
207
|
+
|
|
208
|
+
if (error) throw error
|
|
209
|
+
return data
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**Download a file (signed URL):**
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
async function getSignedUrl(path: string, expiresIn: number = 60) {
|
|
217
|
+
const { data, error } = await supabaseAdmin.storage
|
|
218
|
+
.from('your-bucket')
|
|
219
|
+
.createSignedUrl(path, expiresIn)
|
|
220
|
+
|
|
221
|
+
if (error) throw error
|
|
222
|
+
return data.signedUrl
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**Delete a file:**
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
async function deleteFile(path: string) {
|
|
230
|
+
const { error } = await supabaseAdmin.storage
|
|
231
|
+
.from('your-bucket')
|
|
232
|
+
.remove([path])
|
|
233
|
+
|
|
234
|
+
if (error) throw error
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**List files:**
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
async function listFiles(folder: string) {
|
|
242
|
+
const { data, error } = await supabaseAdmin.storage
|
|
243
|
+
.from('your-bucket')
|
|
244
|
+
.list(folder)
|
|
245
|
+
|
|
246
|
+
if (error) throw error
|
|
247
|
+
return data
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Edge Functions Setup
|
|
254
|
+
|
|
255
|
+
### Supabase Edge Functions
|
|
256
|
+
|
|
257
|
+
Supabase Edge Functions are serverless functions that run on Deno at the edge. They're useful for:
|
|
258
|
+
|
|
259
|
+
- Background processing
|
|
260
|
+
- Webhook handling
|
|
261
|
+
- Third-party API integrations
|
|
262
|
+
- Custom business logic
|
|
263
|
+
|
|
264
|
+
### 1. Install Supabase CLI
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
pnpm add -g supabase
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### 2. Initialize Edge Functions
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
supabase functions deploy
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### 3. Create an Edge Function
|
|
277
|
+
|
|
278
|
+
Create a function in `supabase/functions/your-function/index.ts`:
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
|
|
282
|
+
|
|
283
|
+
serve(async (req) => {
|
|
284
|
+
const { method } = req
|
|
285
|
+
|
|
286
|
+
if (method === 'POST') {
|
|
287
|
+
const { name } = await req.json()
|
|
288
|
+
|
|
289
|
+
return new Response(
|
|
290
|
+
JSON.stringify({ message: `Hello, ${name}!` }),
|
|
291
|
+
{ headers: { 'Content-Type': 'application/json' } }
|
|
292
|
+
)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return new Response('Method not allowed', { status: 405 })
|
|
296
|
+
})
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### 4. Deploy Edge Functions
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
supabase functions deploy your-function
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### 5. Invoke Edge Functions
|
|
306
|
+
|
|
307
|
+
**From the application:**
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
import { supabaseAdmin } from '@/lib/supabase/admin'
|
|
311
|
+
|
|
312
|
+
async function invokeEdgeFunction(functionName: string, payload: any) {
|
|
313
|
+
const { data, error } = await supabaseAdmin.functions.invoke(functionName, {
|
|
314
|
+
body: payload,
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
if (error) throw error
|
|
318
|
+
return data
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## Connection Pooling Configuration
|
|
325
|
+
|
|
326
|
+
### Supabase Connection Modes
|
|
327
|
+
|
|
328
|
+
Supabase offers two connection modes:
|
|
329
|
+
|
|
330
|
+
| Mode | Description | Use Case |
|
|
331
|
+
|------|-------------|----------|
|
|
332
|
+
| **Session Mode** | Direct connection to database | Serverful applications |
|
|
333
|
+
| **Transaction Mode** | Connection pooling | Serverless applications |
|
|
334
|
+
|
|
335
|
+
### Transaction Mode (Recommended for Next.js)
|
|
336
|
+
|
|
337
|
+
The framework uses Transaction mode for optimal performance in serverless environments.
|
|
338
|
+
|
|
339
|
+
**Database URL Format:**
|
|
340
|
+
|
|
341
|
+
```
|
|
342
|
+
postgresql://postgres.[ref]:[password]@aws-0-[region].pooler.supabase.com:6543/postgres
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
**Key Points:**
|
|
346
|
+
|
|
347
|
+
- Uses `.pooler.supabase.com` domain
|
|
348
|
+
- Port `6543` instead of `5432`
|
|
349
|
+
- Requires `prepare: false` in postgres client
|
|
350
|
+
|
|
351
|
+
### Connection Pooling Best Practices
|
|
352
|
+
|
|
353
|
+
1. **Use Transaction Mode**: Always use transaction mode for serverless
|
|
354
|
+
2. **Reuse Connections**: Cache connection in development (see [`src/server/db/index.ts`](../../src/server/db/index.ts))
|
|
355
|
+
3. **Close Connections**: Connections are automatically closed when the lambda function terminates
|
|
356
|
+
4. **Monitor Connections**: Check Supabase dashboard for connection usage
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## Schema Management
|
|
361
|
+
|
|
362
|
+
### Drizzle Migrations
|
|
363
|
+
|
|
364
|
+
### Generate Migration
|
|
365
|
+
|
|
366
|
+
```bash
|
|
367
|
+
pnpm db:generate
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
This creates a new migration file in `src/server/db/migrations/`.
|
|
371
|
+
|
|
372
|
+
### Push Schema (Development)
|
|
373
|
+
|
|
374
|
+
```bash
|
|
375
|
+
pnpm db:push
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
This pushes the schema directly to the database without creating a migration file. Useful for development.
|
|
379
|
+
|
|
380
|
+
### Run Migrations (Production)
|
|
381
|
+
|
|
382
|
+
```bash
|
|
383
|
+
pnpm db:migrate
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
This applies all pending migrations to the database.
|
|
387
|
+
|
|
388
|
+
### Rollback Migration
|
|
389
|
+
|
|
390
|
+
```bash
|
|
391
|
+
drizzle-kit drop
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### View Migrations
|
|
395
|
+
|
|
396
|
+
```bash
|
|
397
|
+
drizzle-kit studio
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
Opens Drizzle Studio to view and edit your database.
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
## Query Patterns
|
|
405
|
+
|
|
406
|
+
### Basic Queries
|
|
407
|
+
|
|
408
|
+
**Select:**
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
import { db } from '@/server/db'
|
|
412
|
+
import { users } from '@/server/db/schema'
|
|
413
|
+
|
|
414
|
+
// Select all users
|
|
415
|
+
const allUsers = await db.select().from(users)
|
|
416
|
+
|
|
417
|
+
// Select with where
|
|
418
|
+
const user = await db.select().from(users).where(eq(users.id, userId))
|
|
419
|
+
|
|
420
|
+
// Select specific columns
|
|
421
|
+
const userEmails = await db.select({ email: users.email }).from(users)
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
**Insert:**
|
|
425
|
+
|
|
426
|
+
```typescript
|
|
427
|
+
// Insert single row
|
|
428
|
+
const newUser = await db.insert(users).values({
|
|
429
|
+
id: 'user_123',
|
|
430
|
+
email: 'user@example.com',
|
|
431
|
+
firstName: 'John',
|
|
432
|
+
lastName: 'Doe',
|
|
433
|
+
}).returning()
|
|
434
|
+
|
|
435
|
+
// Insert multiple rows
|
|
436
|
+
await db.insert(users).values([
|
|
437
|
+
{ id: 'user_1', email: 'user1@example.com' },
|
|
438
|
+
{ id: 'user_2', email: 'user2@example.com' },
|
|
439
|
+
])
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
**Update:**
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
await db.update(users)
|
|
446
|
+
.set({ firstName: 'Jane' })
|
|
447
|
+
.where(eq(users.id, userId))
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
**Delete:**
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
await db.delete(users).where(eq(users.id, userId))
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### Advanced Queries
|
|
457
|
+
|
|
458
|
+
**Joins:**
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
const result = await db
|
|
462
|
+
.select({
|
|
463
|
+
user: users,
|
|
464
|
+
// other tables...
|
|
465
|
+
})
|
|
466
|
+
.from(users)
|
|
467
|
+
.leftJoin(otherTable, eq(users.id, otherTable.userId))
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
**Aggregations:**
|
|
471
|
+
|
|
472
|
+
```typescript
|
|
473
|
+
const result = await db
|
|
474
|
+
.select({ count: sql<number>`count(*)` })
|
|
475
|
+
.from(users)
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
**Transactions:**
|
|
479
|
+
|
|
480
|
+
```typescript
|
|
481
|
+
await db.transaction(async (tx) => {
|
|
482
|
+
await tx.insert(users).values({ id: 'user_1', email: 'user1@example.com' })
|
|
483
|
+
await tx.insert(users).values({ id: 'user_2', email: 'user2@example.com' })
|
|
484
|
+
})
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
---
|
|
488
|
+
|
|
489
|
+
## Health Check Endpoints
|
|
490
|
+
|
|
491
|
+
The framework includes health check endpoints for verifying Supabase integration:
|
|
492
|
+
|
|
493
|
+
### Database Health Checks
|
|
494
|
+
|
|
495
|
+
| Endpoint | Method | Description |
|
|
496
|
+
|----------|--------|-------------|
|
|
497
|
+
| `/api/health/database/write` | POST | Write test row to database |
|
|
498
|
+
| `/api/health/database/read` | GET | Read test row from database |
|
|
499
|
+
| `/api/health/database/delete` | DELETE | Delete test row from database |
|
|
500
|
+
|
|
501
|
+
### Storage Health Checks
|
|
502
|
+
|
|
503
|
+
| Endpoint | Method | Description |
|
|
504
|
+
|----------|--------|-------------|
|
|
505
|
+
| `/api/health/storage/upload` | POST | Upload test file to storage |
|
|
506
|
+
| `/api/health/storage/download` | GET | Generate signed URL for test file |
|
|
507
|
+
| `/api/health/storage/delete` | DELETE | Delete test file from storage |
|
|
508
|
+
|
|
509
|
+
### Edge Functions Health Checks
|
|
510
|
+
|
|
511
|
+
| Endpoint | Method | Description |
|
|
512
|
+
|----------|--------|-------------|
|
|
513
|
+
| `/api/health/edge/ping` | GET | Ping edge function (placeholder) |
|
|
514
|
+
| `/api/health/edge/auth` | GET | Test auth header passthrough (placeholder) |
|
|
515
|
+
|
|
516
|
+
---
|
|
517
|
+
|
|
518
|
+
## Security Considerations
|
|
519
|
+
|
|
520
|
+
### Row Level Security (RLS)
|
|
521
|
+
|
|
522
|
+
Supabase provides Row Level Security to control data access:
|
|
523
|
+
|
|
524
|
+
1. Enable RLS on tables in Supabase Dashboard
|
|
525
|
+
2. Create policies to control access
|
|
526
|
+
3. Use `supabase` client (not `supabaseAdmin`) for client-side queries
|
|
527
|
+
|
|
528
|
+
### Service Role Key
|
|
529
|
+
|
|
530
|
+
The `SUPABASE_SERVICE_ROLE_KEY` bypasses RLS:
|
|
531
|
+
|
|
532
|
+
- **Use**: Server-side operations that need full access
|
|
533
|
+
- **Never expose**: This key should never be in client code
|
|
534
|
+
- **Store securely**: Keep in environment variables only
|
|
535
|
+
|
|
536
|
+
### Anon Key
|
|
537
|
+
|
|
538
|
+
The `NEXT_PUBLIC_SUPABASE_ANON_KEY` is safe to expose:
|
|
539
|
+
|
|
540
|
+
- **Use**: Client-side queries
|
|
541
|
+
- **Limited access**: Respects RLS policies
|
|
542
|
+
- **Can be exposed**: Safe to include in client bundles
|
|
543
|
+
|
|
544
|
+
---
|
|
545
|
+
|
|
546
|
+
## Troubleshooting
|
|
547
|
+
|
|
548
|
+
### Common Issues
|
|
549
|
+
|
|
550
|
+
**Issue: Connection timeout**
|
|
551
|
+
|
|
552
|
+
- Verify `DATABASE_URL` is correct
|
|
553
|
+
- Check if you're using the correct connection mode (Transaction vs Session)
|
|
554
|
+
- Verify port is `6543` for Transaction mode
|
|
555
|
+
|
|
556
|
+
**Issue: "prepare: false" error**
|
|
557
|
+
|
|
558
|
+
- Ensure `prepare: false` is set in the postgres client
|
|
559
|
+
- This is required for Supabase Transaction mode
|
|
560
|
+
|
|
561
|
+
**Issue: Storage upload fails**
|
|
562
|
+
|
|
563
|
+
- Check if bucket exists
|
|
564
|
+
- Verify bucket permissions
|
|
565
|
+
- Ensure `supabaseAdmin` is used for server-side uploads
|
|
566
|
+
|
|
567
|
+
**Issue: Migration fails**
|
|
568
|
+
|
|
569
|
+
- Verify `DATABASE_URL` is set correctly
|
|
570
|
+
- Check if database is accessible
|
|
571
|
+
- Ensure you have write permissions
|
|
572
|
+
|
|
573
|
+
---
|
|
574
|
+
|
|
575
|
+
## Related Documentation
|
|
576
|
+
|
|
577
|
+
- [Environment Variables](./environment-variables.md) - Supabase environment variables
|
|
578
|
+
- [Clerk Authentication](./clerk-authentication.md) - User schema and webhooks
|
|
579
|
+
- [API Layer](./api-layer.md) - Database queries in API routes
|
|
580
|
+
- [Health Check System](./health-check-system.md) - Health check endpoints
|
|
581
|
+
- [Snowflake Integration](./snowflake-integration.md) - Data warehouse integration
|