@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.
Files changed (64) hide show
  1. package/bin/index.js +2 -0
  2. package/dist/index.js +592 -0
  3. package/package.json +43 -0
  4. package/templates/default/.editorconfig +21 -0
  5. package/templates/default/.env.example +15 -0
  6. package/templates/default/.eslintrc.json +35 -0
  7. package/templates/default/.prettierrc.json +7 -0
  8. package/templates/default/README.md +346 -0
  9. package/templates/default/app.config.ts +20 -0
  10. package/templates/default/docs/adding-features.md +439 -0
  11. package/templates/default/docs/adr/001-use-sqlite-for-development-database.md +22 -0
  12. package/templates/default/docs/adr/002-use-tanstack-start-over-nextjs.md +22 -0
  13. package/templates/default/docs/adr/003-use-better-auth-over-nextauth.md +22 -0
  14. package/templates/default/docs/adr/004-use-drizzle-over-prisma.md +22 -0
  15. package/templates/default/docs/adr/005-use-trpc-for-api-layer.md +22 -0
  16. package/templates/default/docs/adr/006-use-tailwind-css-v4-with-shadcn-ui.md +22 -0
  17. package/templates/default/docs/architecture.md +241 -0
  18. package/templates/default/docs/database.md +376 -0
  19. package/templates/default/docs/deployment.md +435 -0
  20. package/templates/default/docs/troubleshooting.md +668 -0
  21. package/templates/default/drizzle/migrations/0001_initial_schema.sql +39 -0
  22. package/templates/default/drizzle/migrations/meta/0001_snapshot.json +225 -0
  23. package/templates/default/drizzle/migrations/meta/_journal.json +12 -0
  24. package/templates/default/drizzle.config.ts +10 -0
  25. package/templates/default/lighthouserc.json +78 -0
  26. package/templates/default/src/app/__root.tsx +32 -0
  27. package/templates/default/src/app/api/auth/$.ts +15 -0
  28. package/templates/default/src/app/api/trpc.server.ts +12 -0
  29. package/templates/default/src/app/auth/forgot-password.tsx +107 -0
  30. package/templates/default/src/app/auth/login.tsx +34 -0
  31. package/templates/default/src/app/auth/register.tsx +34 -0
  32. package/templates/default/src/app/auth/reset-password.tsx +171 -0
  33. package/templates/default/src/app/auth/verify-email.tsx +111 -0
  34. package/templates/default/src/app/dashboard/index.tsx +122 -0
  35. package/templates/default/src/app/dashboard/settings.tsx +161 -0
  36. package/templates/default/src/app/globals.css +55 -0
  37. package/templates/default/src/app/index.tsx +83 -0
  38. package/templates/default/src/components/features/auth/login-form.tsx +172 -0
  39. package/templates/default/src/components/features/auth/register-form.tsx +202 -0
  40. package/templates/default/src/components/layout/dashboard-layout.tsx +27 -0
  41. package/templates/default/src/components/layout/header.tsx +29 -0
  42. package/templates/default/src/components/layout/sidebar.tsx +38 -0
  43. package/templates/default/src/components/ui/button.tsx +57 -0
  44. package/templates/default/src/components/ui/card.tsx +79 -0
  45. package/templates/default/src/components/ui/input.tsx +24 -0
  46. package/templates/default/src/lib/api.ts +42 -0
  47. package/templates/default/src/lib/auth.ts +292 -0
  48. package/templates/default/src/lib/email.ts +221 -0
  49. package/templates/default/src/lib/env.ts +119 -0
  50. package/templates/default/src/lib/hydration-timing.ts +289 -0
  51. package/templates/default/src/lib/monitoring.ts +336 -0
  52. package/templates/default/src/lib/utils.ts +6 -0
  53. package/templates/default/src/server/api/root.ts +10 -0
  54. package/templates/default/src/server/api/routers/dashboard.ts +37 -0
  55. package/templates/default/src/server/api/routers/user.ts +31 -0
  56. package/templates/default/src/server/api/trpc.ts +132 -0
  57. package/templates/default/src/server/auth/config.ts +241 -0
  58. package/templates/default/src/server/db/index.ts +153 -0
  59. package/templates/default/src/server/db/migrate.ts +125 -0
  60. package/templates/default/src/server/db/schema.ts +170 -0
  61. package/templates/default/src/server/db/seed.ts +130 -0
  62. package/templates/default/src/types/global.d.ts +25 -0
  63. package/templates/default/tailwind.config.js +46 -0
  64. 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