@amirulabu/create-recurring-rabbit-app 0.0.0-alpha
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/bin/index.js +2 -0
- package/dist/index.js +592 -0
- package/package.json +43 -0
- package/templates/default/.editorconfig +21 -0
- package/templates/default/.env.example +15 -0
- package/templates/default/.eslintrc.json +35 -0
- package/templates/default/.prettierrc.json +7 -0
- package/templates/default/README.md +346 -0
- package/templates/default/app.config.ts +20 -0
- package/templates/default/docs/adding-features.md +439 -0
- package/templates/default/docs/adr/001-use-sqlite-for-development-database.md +22 -0
- package/templates/default/docs/adr/002-use-tanstack-start-over-nextjs.md +22 -0
- package/templates/default/docs/adr/003-use-better-auth-over-nextauth.md +22 -0
- package/templates/default/docs/adr/004-use-drizzle-over-prisma.md +22 -0
- package/templates/default/docs/adr/005-use-trpc-for-api-layer.md +22 -0
- package/templates/default/docs/adr/006-use-tailwind-css-v4-with-shadcn-ui.md +22 -0
- package/templates/default/docs/architecture.md +241 -0
- package/templates/default/docs/database.md +376 -0
- package/templates/default/docs/deployment.md +435 -0
- package/templates/default/docs/troubleshooting.md +668 -0
- package/templates/default/drizzle/migrations/0001_initial_schema.sql +39 -0
- package/templates/default/drizzle/migrations/meta/0001_snapshot.json +225 -0
- package/templates/default/drizzle/migrations/meta/_journal.json +12 -0
- package/templates/default/drizzle.config.ts +10 -0
- package/templates/default/lighthouserc.json +78 -0
- package/templates/default/src/app/__root.tsx +32 -0
- package/templates/default/src/app/api/auth/$.ts +15 -0
- package/templates/default/src/app/api/trpc.server.ts +12 -0
- package/templates/default/src/app/auth/forgot-password.tsx +107 -0
- package/templates/default/src/app/auth/login.tsx +34 -0
- package/templates/default/src/app/auth/register.tsx +34 -0
- package/templates/default/src/app/auth/reset-password.tsx +171 -0
- package/templates/default/src/app/auth/verify-email.tsx +111 -0
- package/templates/default/src/app/dashboard/index.tsx +122 -0
- package/templates/default/src/app/dashboard/settings.tsx +161 -0
- package/templates/default/src/app/globals.css +55 -0
- package/templates/default/src/app/index.tsx +83 -0
- package/templates/default/src/components/features/auth/login-form.tsx +172 -0
- package/templates/default/src/components/features/auth/register-form.tsx +202 -0
- package/templates/default/src/components/layout/dashboard-layout.tsx +27 -0
- package/templates/default/src/components/layout/header.tsx +29 -0
- package/templates/default/src/components/layout/sidebar.tsx +38 -0
- package/templates/default/src/components/ui/button.tsx +57 -0
- package/templates/default/src/components/ui/card.tsx +79 -0
- package/templates/default/src/components/ui/input.tsx +24 -0
- package/templates/default/src/lib/api.ts +42 -0
- package/templates/default/src/lib/auth.ts +292 -0
- package/templates/default/src/lib/email.ts +221 -0
- package/templates/default/src/lib/env.ts +119 -0
- package/templates/default/src/lib/hydration-timing.ts +289 -0
- package/templates/default/src/lib/monitoring.ts +336 -0
- package/templates/default/src/lib/utils.ts +6 -0
- package/templates/default/src/server/api/root.ts +10 -0
- package/templates/default/src/server/api/routers/dashboard.ts +37 -0
- package/templates/default/src/server/api/routers/user.ts +31 -0
- package/templates/default/src/server/api/trpc.ts +132 -0
- package/templates/default/src/server/auth/config.ts +241 -0
- package/templates/default/src/server/db/index.ts +153 -0
- package/templates/default/src/server/db/migrate.ts +125 -0
- package/templates/default/src/server/db/schema.ts +170 -0
- package/templates/default/src/server/db/seed.ts +130 -0
- package/templates/default/src/types/global.d.ts +25 -0
- package/templates/default/tailwind.config.js +46 -0
- package/templates/default/tsconfig.json +36 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# Architecture Overview
|
|
2
|
+
|
|
3
|
+
## System Design
|
|
4
|
+
|
|
5
|
+
This project follows a **type-safe, full-stack architecture** where types flow seamlessly from database schema to UI components.
|
|
6
|
+
|
|
7
|
+
### Data Flow
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Database Schema (Drizzle) → tRPC Procedures → React Components
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
1. **Database layer** defines the source of truth for data types
|
|
14
|
+
2. **tRPC procedures** operate on typed database queries
|
|
15
|
+
3. **React components** receive fully typed data without manual interfaces
|
|
16
|
+
|
|
17
|
+
### Key Architectural Decisions
|
|
18
|
+
|
|
19
|
+
#### Why TanStack Start over Next.js?
|
|
20
|
+
|
|
21
|
+
- **Better TypeScript integration** with less configuration
|
|
22
|
+
- **File-based routing** without App Router complexity
|
|
23
|
+
- **Smaller bundle size** for micro-SaaS use cases
|
|
24
|
+
- **Simpler mental model** for developers
|
|
25
|
+
|
|
26
|
+
#### Why Better-auth over NextAuth?
|
|
27
|
+
|
|
28
|
+
- **Simpler configuration** for basic email/password auth
|
|
29
|
+
- **Better TypeScript support** with fewer generic type issues
|
|
30
|
+
- **Database session storage** built-in for scalability
|
|
31
|
+
- **Less overhead** for simple authentication needs
|
|
32
|
+
|
|
33
|
+
#### Why Drizzle over Prisma?
|
|
34
|
+
|
|
35
|
+
- **Zero runtime overhead** with compile-time query building
|
|
36
|
+
- **Better migration control** with SQL-first approach
|
|
37
|
+
- **Lighter weight** for simple SaaS data models
|
|
38
|
+
- **Type-safe queries** without code generation
|
|
39
|
+
|
|
40
|
+
## Security Patterns
|
|
41
|
+
|
|
42
|
+
### Authentication Flow
|
|
43
|
+
|
|
44
|
+
1. User registers with email/password
|
|
45
|
+
2. Email verification required before dashboard access
|
|
46
|
+
3. Sessions stored in database with automatic cleanup
|
|
47
|
+
4. CSRF protection enabled on all authenticated routes
|
|
48
|
+
|
|
49
|
+
### Environment Variable Management
|
|
50
|
+
|
|
51
|
+
- Server-only variables never accessible to client
|
|
52
|
+
- Type-safe validation with immediate feedback
|
|
53
|
+
- Auto-generation of secure secrets during setup
|
|
54
|
+
|
|
55
|
+
### Route Protection
|
|
56
|
+
|
|
57
|
+
Protected routes use `beforeLoad` guards to check authentication:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
export const Route = createFileRoute('/dashboard')({
|
|
61
|
+
beforeLoad: ({ context }) => {
|
|
62
|
+
if (!context.auth.user) {
|
|
63
|
+
throw redirect({ to: '/auth/login' })
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
component: Dashboard,
|
|
67
|
+
})
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Performance Considerations
|
|
71
|
+
|
|
72
|
+
### Database Optimization
|
|
73
|
+
|
|
74
|
+
- SQLite for development (zero-config, fast iteration)
|
|
75
|
+
- Connection pooling configured for Postgres production
|
|
76
|
+
- Indexes on frequently queried columns (user lookups)
|
|
77
|
+
|
|
78
|
+
### Frontend Optimization
|
|
79
|
+
|
|
80
|
+
- Automatic code splitting with TanStack Start
|
|
81
|
+
- Optimistic updates with TanStack Query
|
|
82
|
+
- CSS-in-JS avoided for better bundle size
|
|
83
|
+
- Lazy loading of routes
|
|
84
|
+
|
|
85
|
+
## Server-Side Organization
|
|
86
|
+
|
|
87
|
+
### tRPC Structure
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
src/server/api/
|
|
91
|
+
├── root.ts # Main router combining all sub-routers
|
|
92
|
+
├── trpc.ts # tRPC configuration, context, middleware
|
|
93
|
+
├── routers/
|
|
94
|
+
│ ├── user.ts # User management procedures
|
|
95
|
+
│ └── dashboard.ts # Dashboard data procedures
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Authentication Structure
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
src/server/auth/
|
|
102
|
+
└── config.ts # Better-auth configuration with Drizzle adapter
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Database Structure
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
src/server/db/
|
|
109
|
+
├── index.ts # Database connection factory
|
|
110
|
+
├── schema.ts # Drizzle schema definitions
|
|
111
|
+
├── migrate.ts # Migration runner
|
|
112
|
+
└── seed.ts # Database seeding
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Client-Side Organization
|
|
116
|
+
|
|
117
|
+
### Routes (TanStack Start)
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
src/app/
|
|
121
|
+
├── __root.tsx # Root layout with providers
|
|
122
|
+
├── index.tsx # Landing page
|
|
123
|
+
├── auth/ # Authentication routes
|
|
124
|
+
│ ├── login.tsx
|
|
125
|
+
│ └── register.tsx
|
|
126
|
+
└── dashboard/ # Protected dashboard routes
|
|
127
|
+
├── index.tsx
|
|
128
|
+
└── settings.tsx
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Components
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
src/components/
|
|
135
|
+
├── ui/ # Reusable shadcn/ui components
|
|
136
|
+
├── layout/ # Layout components (header, sidebar)
|
|
137
|
+
└── features/ # Feature-specific components
|
|
138
|
+
└── auth/ # Authentication forms
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Shared Utilities
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
src/lib/
|
|
145
|
+
├── api.ts # tRPC client setup
|
|
146
|
+
├── auth.ts # Better-auth React client
|
|
147
|
+
├── email.ts # Resend email integration
|
|
148
|
+
├── env.ts # Environment validation
|
|
149
|
+
└── utils.ts # Shared utility functions
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Type Flow Example
|
|
153
|
+
|
|
154
|
+
### Database Schema
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
export const users = sqliteTable('users', {
|
|
158
|
+
id: text('id').primaryKey(),
|
|
159
|
+
name: text('name').notNull(),
|
|
160
|
+
email: text('email').notNull(),
|
|
161
|
+
})
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### tRPC Procedure
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
export const userRouter = router({
|
|
168
|
+
getProfile: protectedProcedure.query(async ({ ctx }) => {
|
|
169
|
+
return ctx.db.select().from(users).where(eq(users.id, ctx.user.id))
|
|
170
|
+
}),
|
|
171
|
+
})
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### React Component
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
function UserProfile() {
|
|
178
|
+
const { data: user } = api.user.getProfile.useQuery()
|
|
179
|
+
|
|
180
|
+
return <div>{user?.name}</div>
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Types automatically flow from schema → tRPC → React without manual interfaces.
|
|
185
|
+
|
|
186
|
+
## Scaling Patterns
|
|
187
|
+
|
|
188
|
+
### Adding New Features
|
|
189
|
+
|
|
190
|
+
When adding new features, follow these patterns:
|
|
191
|
+
|
|
192
|
+
1. **Database**: Add table to `schema.ts`, generate migration
|
|
193
|
+
2. **tRPC**: Create router with procedures, add to root router
|
|
194
|
+
3. **UI**: Create components in `features/`, add routes in `app/`
|
|
195
|
+
4. **Types**: No manual types needed - everything inferred from database
|
|
196
|
+
|
|
197
|
+
### Multi-Tenancy (Future)
|
|
198
|
+
|
|
199
|
+
The architecture supports future multi-tenancy:
|
|
200
|
+
|
|
201
|
+
- Add `organizationId` foreign key to user tables
|
|
202
|
+
- Add organization procedures to tRPC routers
|
|
203
|
+
- Filter queries by organization context
|
|
204
|
+
|
|
205
|
+
### Background Jobs (Future)
|
|
206
|
+
|
|
207
|
+
For background jobs, consider:
|
|
208
|
+
|
|
209
|
+
- Add job queue (BullMQ, Agenda)
|
|
210
|
+
- Create worker processes
|
|
211
|
+
- Store job state in database
|
|
212
|
+
|
|
213
|
+
## Monitoring & Debugging
|
|
214
|
+
|
|
215
|
+
### Development Tools
|
|
216
|
+
|
|
217
|
+
- **tRPC DevTools**: Inspect API calls in browser
|
|
218
|
+
- **Drizzle Studio**: Visual database browser
|
|
219
|
+
- **React DevTools**: Component tree inspection
|
|
220
|
+
- **TanStack Query DevTools**: Query state inspection
|
|
221
|
+
|
|
222
|
+
### Logging
|
|
223
|
+
|
|
224
|
+
- Slow query logging in development
|
|
225
|
+
- Request logging in tRPC middleware
|
|
226
|
+
- Error tracking (can add Sentry)
|
|
227
|
+
|
|
228
|
+
## Deployment Architecture
|
|
229
|
+
|
|
230
|
+
### Development
|
|
231
|
+
|
|
232
|
+
- SQLite database in `data/` directory
|
|
233
|
+
- Hot reload with Vinxi
|
|
234
|
+
- Environment variables from `.env.local`
|
|
235
|
+
|
|
236
|
+
### Production
|
|
237
|
+
|
|
238
|
+
- PostgreSQL database (connection pooling configured)
|
|
239
|
+
- Static assets cached by platform (Vercel, Railway)
|
|
240
|
+
- Environment variables from platform dashboard
|
|
241
|
+
- Build artifacts optimized (code splitting, tree shaking)
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
# Database Schema
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This project uses Drizzle ORM with SQLite for development and PostgreSQL for production. All tables are defined in `src/server/db/schema.ts`.
|
|
6
|
+
|
|
7
|
+
## Tables
|
|
8
|
+
|
|
9
|
+
### users
|
|
10
|
+
|
|
11
|
+
Stores user account information with email verification status.
|
|
12
|
+
|
|
13
|
+
| Column | Type | Description |
|
|
14
|
+
| ------------- | --------------- | ----------------------------- |
|
|
15
|
+
| id | text (PK) | Unique user identifier (CUID) |
|
|
16
|
+
| name | text | User's display name |
|
|
17
|
+
| email | text | User's email address (unique) |
|
|
18
|
+
| emailVerified | boolean | Email verification status |
|
|
19
|
+
| image | text (optional) | Profile image URL |
|
|
20
|
+
| createdAt | timestamp | Account creation time |
|
|
21
|
+
| updatedAt | timestamp | Last update time |
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
export const users = sqliteTable('users', {
|
|
25
|
+
id: text('id')
|
|
26
|
+
.primaryKey()
|
|
27
|
+
.$defaultFn(() => createId()),
|
|
28
|
+
name: text('name').notNull(),
|
|
29
|
+
email: text('email').notNull(),
|
|
30
|
+
emailVerified: integer('email_verified', { mode: 'boolean' }).default(false),
|
|
31
|
+
image: text('image'),
|
|
32
|
+
createdAt: integer('created_at', { mode: 'timestamp' }).$defaultFn(
|
|
33
|
+
() => new Date()
|
|
34
|
+
),
|
|
35
|
+
updatedAt: integer('updated_at', { mode: 'timestamp' }).$defaultFn(
|
|
36
|
+
() => new Date()
|
|
37
|
+
),
|
|
38
|
+
})
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### sessions
|
|
42
|
+
|
|
43
|
+
Stores active user sessions for authentication.
|
|
44
|
+
|
|
45
|
+
| Column | Type | Description |
|
|
46
|
+
| --------- | --------- | -------------------------------- |
|
|
47
|
+
| id | text (PK) | Unique session identifier (CUID) |
|
|
48
|
+
| userId | text (FK) | Reference to users table |
|
|
49
|
+
| expiresAt | timestamp | Session expiration time |
|
|
50
|
+
| token | text | Session token |
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
export const sessions = sqliteTable('sessions', {
|
|
54
|
+
id: text('id')
|
|
55
|
+
.primaryKey()
|
|
56
|
+
.$defaultFn(() => createId()),
|
|
57
|
+
userId: text('user_id')
|
|
58
|
+
.references(() => users.id, { onDelete: 'cascade' })
|
|
59
|
+
.notNull(),
|
|
60
|
+
expiresAt: integer('expires_at', { mode: 'timestamp' }).notNull(),
|
|
61
|
+
token: text('token').notNull(),
|
|
62
|
+
})
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### accounts
|
|
66
|
+
|
|
67
|
+
Stores OAuth provider accounts (for future integration with Google, GitHub, etc.).
|
|
68
|
+
|
|
69
|
+
| Column | Type | Description |
|
|
70
|
+
| ---------- | --------- | ------------------------------------- |
|
|
71
|
+
| id | text (PK) | Unique account identifier (CUID) |
|
|
72
|
+
| userId | text (FK) | Reference to users table |
|
|
73
|
+
| accountId | text | Provider account ID |
|
|
74
|
+
| providerId | text | OAuth provider (google, github, etc.) |
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
export const accounts = sqliteTable('accounts', {
|
|
78
|
+
id: text('id')
|
|
79
|
+
.primaryKey()
|
|
80
|
+
.$defaultFn(() => createId()),
|
|
81
|
+
userId: text('user_id')
|
|
82
|
+
.references(() => users.id, { onDelete: 'cascade' })
|
|
83
|
+
.notNull(),
|
|
84
|
+
accountId: text('account_id').notNull(),
|
|
85
|
+
providerId: text('provider_id').notNull(),
|
|
86
|
+
})
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### verifications
|
|
90
|
+
|
|
91
|
+
Stores email verification and password reset tokens.
|
|
92
|
+
|
|
93
|
+
| Column | Type | Description |
|
|
94
|
+
| ---------- | --------- | ------------------------------------- |
|
|
95
|
+
| id | text (PK) | Unique verification identifier (CUID) |
|
|
96
|
+
| identifier | text | Email address |
|
|
97
|
+
| value | text | Verification code/token |
|
|
98
|
+
| expiresAt | timestamp | Token expiration time |
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
export const verifications = sqliteTable('verifications', {
|
|
102
|
+
id: text('id')
|
|
103
|
+
.primaryKey()
|
|
104
|
+
.$defaultFn(() => createId()),
|
|
105
|
+
identifier: text('identifier').notNull(),
|
|
106
|
+
value: text('value').notNull(),
|
|
107
|
+
expiresAt: integer('expires_at', { mode: 'timestamp' }).notNull(),
|
|
108
|
+
})
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Relationships
|
|
112
|
+
|
|
113
|
+
### users → sessions
|
|
114
|
+
|
|
115
|
+
One-to-many relationship: A user can have multiple sessions.
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
export const usersRelations = relations(users, ({ many }) => ({
|
|
119
|
+
sessions: many(sessions),
|
|
120
|
+
}))
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### users → accounts
|
|
124
|
+
|
|
125
|
+
One-to-many relationship: A user can have multiple OAuth accounts.
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
export const usersRelations = relations(users, ({ many }) => ({
|
|
129
|
+
accounts: many(accounts),
|
|
130
|
+
}))
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### users → verifications
|
|
134
|
+
|
|
135
|
+
One-to-many relationship: A user can have multiple verification tokens.
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
export const usersRelations = relations(users, ({ many }) => ({
|
|
139
|
+
verifications: many(verifications),
|
|
140
|
+
}))
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Database Connections
|
|
144
|
+
|
|
145
|
+
### Development (SQLite)
|
|
146
|
+
|
|
147
|
+
- **File:** `data/app.db`
|
|
148
|
+
- **Mode:** WAL (Write-Ahead Logging) for performance
|
|
149
|
+
- **Auto-created:** If `DATABASE_URL` not set
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
// src/server/db/index.ts
|
|
153
|
+
import Database from 'better-sqlite3'
|
|
154
|
+
|
|
155
|
+
const db = new Database('data/app.db', { readonly: false })
|
|
156
|
+
db.pragma('journal_mode = 'WAL')
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Production (PostgreSQL)
|
|
160
|
+
|
|
161
|
+
- **Connection Pooling:** 20 connections (configurable)
|
|
162
|
+
- **Environment Variable:** `DATABASE_URL`
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
// src/server/db/index.ts
|
|
166
|
+
import { drizzle } from 'drizzle-orm/postgres-js'
|
|
167
|
+
import { Pool } from 'postgres'
|
|
168
|
+
|
|
169
|
+
const pool = new Pool({
|
|
170
|
+
connectionString: process.env.DATABASE_URL,
|
|
171
|
+
max: 20,
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
const db = drizzle(pool, { schema })
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Migrations
|
|
178
|
+
|
|
179
|
+
### Running Migrations
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
# Generate migration files
|
|
183
|
+
npm run db:generate
|
|
184
|
+
|
|
185
|
+
# Apply migrations
|
|
186
|
+
npm run db:migrate
|
|
187
|
+
|
|
188
|
+
# Open Drizzle Studio to browse database
|
|
189
|
+
npm run db:studio
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Migration Files
|
|
193
|
+
|
|
194
|
+
Stored in `drizzle/migrations/` with SQL files:
|
|
195
|
+
|
|
196
|
+
- `0001_initial_schema.sql`
|
|
197
|
+
- `0002_add_feature.sql` (future migrations)
|
|
198
|
+
|
|
199
|
+
### SQLite vs PostgreSQL Migrations
|
|
200
|
+
|
|
201
|
+
The same schema works for both databases. Drizzle handles differences:
|
|
202
|
+
|
|
203
|
+
- SQLite: `sqlite_table`
|
|
204
|
+
- PostgreSQL: `pg_table`
|
|
205
|
+
|
|
206
|
+
Some features differ:
|
|
207
|
+
|
|
208
|
+
- **Auto-increment:** SQLite uses `INTEGER PRIMARY KEY`, PostgreSQL uses `SERIAL`
|
|
209
|
+
- **Text types:** SQLite `TEXT`, PostgreSQL `VARCHAR` or `TEXT`
|
|
210
|
+
- **Boolean:** SQLite `INTEGER (0/1)`, PostgreSQL `BOOLEAN`
|
|
211
|
+
|
|
212
|
+
## Common Operations
|
|
213
|
+
|
|
214
|
+
### Query Examples
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
// Get all users
|
|
218
|
+
const allUsers = await db.select().from(users)
|
|
219
|
+
|
|
220
|
+
// Get user by email
|
|
221
|
+
const user = await db
|
|
222
|
+
.select()
|
|
223
|
+
.from(users)
|
|
224
|
+
.where(eq(users.email, 'test@example.com'))
|
|
225
|
+
|
|
226
|
+
// Get user with sessions
|
|
227
|
+
const userWithSessions = await db
|
|
228
|
+
.select()
|
|
229
|
+
.from(users)
|
|
230
|
+
.leftJoin(sessions, eq(users.id, sessions.userId))
|
|
231
|
+
.where(eq(users.id, userId))
|
|
232
|
+
|
|
233
|
+
// Insert user
|
|
234
|
+
const newUser = await db
|
|
235
|
+
.insert(users)
|
|
236
|
+
.values({
|
|
237
|
+
name: 'John Doe',
|
|
238
|
+
email: 'john@example.com',
|
|
239
|
+
})
|
|
240
|
+
.returning()
|
|
241
|
+
|
|
242
|
+
// Update user
|
|
243
|
+
const updatedUser = await db
|
|
244
|
+
.update(users)
|
|
245
|
+
.set({ name: 'Jane Doe', updatedAt: new Date() })
|
|
246
|
+
.where(eq(users.id, userId))
|
|
247
|
+
.returning()
|
|
248
|
+
|
|
249
|
+
// Delete user
|
|
250
|
+
await db.delete(users).where(eq(users.id, userId))
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Using tRPC Procedures
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
// src/server/api/routers/user.ts
|
|
257
|
+
export const userRouter = router({
|
|
258
|
+
getProfile: protectedProcedure.query(async ({ ctx }) => {
|
|
259
|
+
return await db.select().from(users).where(eq(users.id, ctx.user.id))
|
|
260
|
+
}),
|
|
261
|
+
|
|
262
|
+
updateProfile: protectedProcedure
|
|
263
|
+
.input(z.object({ name: z.string() }))
|
|
264
|
+
.mutation(async ({ ctx, input }) => {
|
|
265
|
+
const result = await db
|
|
266
|
+
.update(users)
|
|
267
|
+
.set({ name: input.name, updatedAt: new Date() })
|
|
268
|
+
.where(eq(users.id, ctx.user.id))
|
|
269
|
+
.returning()
|
|
270
|
+
return result[0]
|
|
271
|
+
}),
|
|
272
|
+
})
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Performance Optimization
|
|
276
|
+
|
|
277
|
+
### Indexes
|
|
278
|
+
|
|
279
|
+
Add indexes for frequently queried columns:
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
export const users = sqliteTable(
|
|
283
|
+
'users',
|
|
284
|
+
{
|
|
285
|
+
id: text('id').primaryKey(),
|
|
286
|
+
email: text('email').notNull(),
|
|
287
|
+
},
|
|
288
|
+
(table) => ({
|
|
289
|
+
emailIdx: index('email_idx').on(table.email),
|
|
290
|
+
})
|
|
291
|
+
)
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Recommended indexes:
|
|
295
|
+
|
|
296
|
+
- `users.email` - For login queries
|
|
297
|
+
- `sessions.userId` - For session lookups
|
|
298
|
+
- `verifications.identifier` - For verification token lookups
|
|
299
|
+
|
|
300
|
+
### Connection Pooling
|
|
301
|
+
|
|
302
|
+
PostgreSQL production uses connection pooling:
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
const pool = new Pool({
|
|
306
|
+
connectionString: env.DATABASE_URL,
|
|
307
|
+
max: 20, // Adjust based on traffic
|
|
308
|
+
idleTimeout: 30000, // 30 seconds
|
|
309
|
+
connectionTimeoutMillis: 2000, // 2 seconds
|
|
310
|
+
})
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Query Optimization
|
|
314
|
+
|
|
315
|
+
Tips:
|
|
316
|
+
|
|
317
|
+
1. **Select specific columns** instead of `*`
|
|
318
|
+
2. **Use `where` clauses** to filter data early
|
|
319
|
+
3. **Limit results** when possible: `.limit(10)`
|
|
320
|
+
4. **Avoid N+1 queries** with proper joins
|
|
321
|
+
|
|
322
|
+
## Seed Data
|
|
323
|
+
|
|
324
|
+
The seed script creates sample users and dashboard data:
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
npm run db:seed
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Seed data location: `src/server/db/seed.ts`
|
|
331
|
+
|
|
332
|
+
## Troubleshooting
|
|
333
|
+
|
|
334
|
+
### Database Locked (SQLite)
|
|
335
|
+
|
|
336
|
+
**Error:** `Error: database is locked`
|
|
337
|
+
|
|
338
|
+
**Cause:** WAL file still open from previous dev session
|
|
339
|
+
|
|
340
|
+
**Solution:**
|
|
341
|
+
|
|
342
|
+
```bash
|
|
343
|
+
npm run clean
|
|
344
|
+
# This removes WAL files and restarts clean
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### Migration Conflicts
|
|
348
|
+
|
|
349
|
+
**Error:** Migration applies but data is lost
|
|
350
|
+
|
|
351
|
+
**Cause:** Schema changes incompatible with existing data
|
|
352
|
+
|
|
353
|
+
**Solution:**
|
|
354
|
+
|
|
355
|
+
1. Review migration file in `drizzle/migrations/`
|
|
356
|
+
2. Test migration on backup first
|
|
357
|
+
3. Rollback if needed: `npm run db:migrate:down` (if configured)
|
|
358
|
+
|
|
359
|
+
### Slow Queries
|
|
360
|
+
|
|
361
|
+
**Detection:**
|
|
362
|
+
|
|
363
|
+
- Development: Check Drizzle Studio query logs
|
|
364
|
+
- Production: Check database provider dashboard
|
|
365
|
+
|
|
366
|
+
**Common causes:**
|
|
367
|
+
|
|
368
|
+
- Missing indexes
|
|
369
|
+
- Unnecessary joins
|
|
370
|
+
- Returning too much data
|
|
371
|
+
|
|
372
|
+
**Solution:**
|
|
373
|
+
|
|
374
|
+
1. Add indexes
|
|
375
|
+
2. Optimize query structure
|
|
376
|
+
3. Use pagination for large datasets
|