@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,439 @@
1
+ # Adding Features
2
+
3
+ ## Creating New Database Tables
4
+
5
+ 1. **Define schema** in `src/server/db/schema.ts`
6
+ 2. **Generate migration** with `npm run db:generate`
7
+ 3. **Apply migration** with `npm run db:migrate`
8
+
9
+ Example:
10
+
11
+ ```typescript
12
+ import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'
13
+ import { createId } from '@paralleldrive/cuid2'
14
+
15
+ export const posts = sqliteTable('posts', {
16
+ id: text('id')
17
+ .primaryKey()
18
+ .$defaultFn(() => createId()),
19
+ title: text('title').notNull(),
20
+ content: text('content').notNull(),
21
+ published: integer('published', { mode: 'boolean' }).default(false),
22
+ userId: text('user_id').references(() => users.id, { onDelete: 'cascade' }),
23
+ createdAt: integer('created_at', { mode: 'timestamp' }).$defaultFn(
24
+ () => new Date()
25
+ ),
26
+ updatedAt: integer('updated_at', { mode: 'timestamp' }).$defaultFn(
27
+ () => new Date()
28
+ ),
29
+ })
30
+ ```
31
+
32
+ ### Adding Relations
33
+
34
+ ```typescript
35
+ import { relations } from 'drizzle-orm'
36
+
37
+ export const usersRelations = relations(users, ({ many }) => ({
38
+ posts: many(posts),
39
+ }))
40
+ ```
41
+
42
+ ## Adding tRPC Procedures
43
+
44
+ 1. **Create router** in `src/server/api/[feature].ts`
45
+ 2. **Add to root router** in `src/server/api/root.ts`
46
+ 3. **Use in components** via `api.[feature].[procedure].useQuery()`
47
+
48
+ Example:
49
+
50
+ ```typescript
51
+ import { z } from 'zod'
52
+ import { router, protectedProcedure } from '../trpc'
53
+ import { db } from '@/server/db'
54
+ import { posts, users } from '@/server/db/schema'
55
+ import { eq } from 'drizzle-orm'
56
+
57
+ export const postsRouter = router({
58
+ list: protectedProcedure.query(async ({ ctx }) => {
59
+ return await db.select().from(posts).where(eq(posts.userId, ctx.user.id))
60
+ }),
61
+
62
+ create: protectedProcedure
63
+ .input(z.object({ title: z.string(), content: z.string() }))
64
+ .mutation(async ({ ctx, input }) => {
65
+ const result = await db
66
+ .insert(posts)
67
+ .values({
68
+ ...input,
69
+ userId: ctx.user.id,
70
+ })
71
+ .returning()
72
+ return result[0]
73
+ }),
74
+
75
+ update: protectedProcedure
76
+ .input(z.object({ id: z.string(), title: z.string(), content: z.string() }))
77
+ .mutation(async ({ ctx, input }) => {
78
+ const result = await db
79
+ .update(posts)
80
+ .set({
81
+ title: input.title,
82
+ content: input.content,
83
+ updatedAt: new Date(),
84
+ })
85
+ .where(eq(posts.id, input.id))
86
+ .returning()
87
+ return result[0]
88
+ }),
89
+
90
+ delete: protectedProcedure
91
+ .input(z.object({ id: z.string() }))
92
+ .mutation(async ({ ctx, input }) => {
93
+ await db.delete(posts).where(eq(posts.id, input.id))
94
+ return { success: true }
95
+ }),
96
+ })
97
+ ```
98
+
99
+ Add to root router:
100
+
101
+ ```typescript
102
+ import { postsRouter } from './routers/posts'
103
+
104
+ export const appRouter = router({
105
+ user: userRouter,
106
+ dashboard: dashboardRouter,
107
+ posts: postsRouter,
108
+ })
109
+ ```
110
+
111
+ ## Creating New Pages
112
+
113
+ 1. **Add route file** in `src/app/[route].tsx`
114
+ 2. **Implement component** with TanStack Start conventions
115
+ 3. **Add navigation** in layout components
116
+
117
+ Example:
118
+
119
+ ```typescript
120
+ import { createFileRoute } from '@tanstack/react-router'
121
+
122
+ export const Route = createFileRoute('/posts')({
123
+ component: PostsPage,
124
+ })
125
+
126
+ function PostsPage() {
127
+ return (
128
+ <div>
129
+ <h1>Posts</h1>
130
+ <PostsList />
131
+ </div>
132
+ )
133
+ }
134
+ ```
135
+
136
+ ## Protected Pages
137
+
138
+ Protected pages require authentication and use `beforeLoad` guards:
139
+
140
+ ```typescript
141
+ import { createFileRoute, redirect } from '@tanstack/react-router'
142
+ import { api } from '@/lib/api'
143
+
144
+ export const Route = createFileRoute('/dashboard/settings')({
145
+ beforeLoad: async ({ context }) => {
146
+ if (!context.auth.user) {
147
+ throw redirect({ to: '/auth/login' })
148
+ }
149
+ },
150
+ component: SettingsPage,
151
+ })
152
+
153
+ function SettingsPage() {
154
+ const { data: user } = api.user.getProfile.useQuery()
155
+
156
+ return <div>Settings for {user?.name}</div>
157
+ }
158
+ ```
159
+
160
+ ## Adding UI Components
161
+
162
+ ### Using shadcn/ui Components
163
+
164
+ 1. Install shadcn component: `npx shadcn-ui add [component]`
165
+ 2. Use in your code with imports from `@/components/ui/[component]`
166
+
167
+ Example:
168
+
169
+ ```bash
170
+ npx shadcn-ui add dialog
171
+ npx shadcn-ui add select
172
+ ```
173
+
174
+ ```typescript
175
+ import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
176
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
177
+
178
+ function MyComponent() {
179
+ return (
180
+ <Dialog>
181
+ <DialogContent>
182
+ <DialogHeader>
183
+ <DialogTitle>Select Option</DialogTitle>
184
+ </DialogHeader>
185
+ <Select>
186
+ <SelectTrigger>
187
+ <SelectValue />
188
+ </SelectTrigger>
189
+ <SelectContent>
190
+ <SelectItem value="option1">Option 1</SelectItem>
191
+ <SelectItem value="option2">Option 2</SelectItem>
192
+ </SelectContent>
193
+ </Select>
194
+ </DialogContent>
195
+ </Dialog>
196
+ )
197
+ }
198
+ ```
199
+
200
+ ### Creating Feature-Specific Components
201
+
202
+ Create components in `src/components/features/[feature]/` to keep related code together:
203
+
204
+ ```typescript
205
+ import { Button } from '@/components/ui/button'
206
+ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
207
+
208
+ interface PostCardProps {
209
+ post: {
210
+ id: string
211
+ title: string
212
+ content: string
213
+ createdAt: Date
214
+ }
215
+ }
216
+
217
+ export function PostCard({ post }: PostCardProps) {
218
+ return (
219
+ <Card>
220
+ <CardHeader>
221
+ <CardTitle>{post.title}</CardTitle>
222
+ </CardHeader>
223
+ <CardContent>
224
+ <p>{post.content}</p>
225
+ <p className="text-sm text-gray-500">{new Date(post.createdAt).toLocaleDateString()}</p>
226
+ </CardContent>
227
+ </Card>
228
+ )
229
+ }
230
+ ```
231
+
232
+ ## Data Fetching Patterns
233
+
234
+ ### Query with tRPC
235
+
236
+ ```typescript
237
+ function PostsList() {
238
+ const { data: posts, isLoading, error } = api.posts.list.useQuery()
239
+
240
+ if (isLoading) return <div>Loading...</div>
241
+ if (error) return <div>Error loading posts</div>
242
+
243
+ return (
244
+ <div>
245
+ {posts?.map((post) => (
246
+ <PostCard key={post.id} post={post} />
247
+ ))}
248
+ </div>
249
+ )
250
+ }
251
+ ```
252
+
253
+ ### Mutation with tRPC
254
+
255
+ ```typescript
256
+ function CreatePost() {
257
+ const createPost = api.posts.create.useMutation({
258
+ onSuccess: () => {
259
+ api.posts.list.invalidate()
260
+ },
261
+ })
262
+
263
+ const handleSubmit = (data: { title: string, content: string }) => {
264
+ createPost.mutate(data)
265
+ }
266
+
267
+ return (
268
+ <form onSubmit={(e) => {
269
+ e.preventDefault()
270
+ const formData = new FormData(e.currentTarget)
271
+ handleSubmit({
272
+ title: String(formData.get('title')),
273
+ content: String(formData.get('content')),
274
+ })
275
+ }}>
276
+ {/* form fields */}
277
+ </form>
278
+ )
279
+ }
280
+ ```
281
+
282
+ ### Optimistic Updates
283
+
284
+ ```typescript
285
+ const deletePost = api.posts.delete.useMutation({
286
+ onMutate: async (deletedId) => {
287
+ await queryClient.cancelQueries({ queryKey: ['posts.list'] })
288
+ const previousPosts = queryClient.getQueryData(['posts.list']) as Post[]
289
+
290
+ queryClient.setQueryData(
291
+ ['posts.list'],
292
+ previousPosts?.filter((p) => p.id !== deletedId.id)
293
+ )
294
+
295
+ return { previousPosts }
296
+ },
297
+ onError: (err, variables, context) => {
298
+ queryClient.setQueryData(['posts.list'], context?.previousPosts)
299
+ },
300
+ onSettled: () => {
301
+ queryClient.invalidateQueries({ queryKey: ['posts.list'] })
302
+ },
303
+ })
304
+ ```
305
+
306
+ ## Common Patterns
307
+
308
+ ### Form Handling
309
+
310
+ ```typescript
311
+ import { useState } from 'react'
312
+
313
+ function MyForm() {
314
+ const [formData, setFormData] = useState({ name: '', email: '' })
315
+ const [errors, setErrors] = useState<Record<string, string>>({})
316
+ const [loading, setLoading] = useState(false)
317
+
318
+ const handleSubmit = async (e: React.FormEvent) => {
319
+ e.preventDefault()
320
+ setLoading(true)
321
+ setErrors({})
322
+
323
+ try {
324
+ await api.myProcedure.mutateAsync(formData)
325
+ } catch (error) {
326
+ setErrors({ submit: 'An error occurred' })
327
+ } finally {
328
+ setLoading(false)
329
+ }
330
+ }
331
+
332
+ return (
333
+ <form onSubmit={handleSubmit}>
334
+ {/* form fields */}
335
+ {errors.submit && <div className="error">{errors.submit}</div>}
336
+ </form>
337
+ )
338
+ }
339
+ ```
340
+
341
+ ### Error Handling
342
+
343
+ ```typescript
344
+ function MyComponent() {
345
+ const { data, error, isLoading } = api.myProcedure.useQuery()
346
+
347
+ if (error) {
348
+ return (
349
+ <div className="p-4 bg-red-50 border border-red-200 rounded-md">
350
+ <p className="text-red-600">Error: {error.message}</p>
351
+ <button onClick={() => api.myProcedure.refetch()}>Retry</button>
352
+ </div>
353
+ )
354
+ }
355
+
356
+ // render content
357
+ }
358
+ ```
359
+
360
+ ### Loading States
361
+
362
+ ```typescript
363
+ function MyComponent() {
364
+ const { data, isLoading } = api.myProcedure.useQuery()
365
+
366
+ if (isLoading) {
367
+ return (
368
+ <div className="flex items-center justify-center p-8">
369
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600" />
370
+ <span className="ml-2">Loading...</span>
371
+ </div>
372
+ )
373
+ }
374
+
375
+ return <div>{data?.content}</div>
376
+ }
377
+ ```
378
+
379
+ ## Adding Third-Party Integrations
380
+
381
+ ### Stripe for Payments
382
+
383
+ 1. Install Stripe SDK: `npm install stripe`
384
+ 2. Add environment variable: `STRIPE_SECRET_KEY`
385
+ 3. Create webhook handler in `src/app/api/stripe-webhook.ts`
386
+ 4. Add procedures to handle payments
387
+
388
+ ```typescript
389
+ import Stripe from 'stripe'
390
+
391
+ const stripe = new Stripe(env.STRIPE_SECRET_KEY)
392
+
393
+ export const paymentRouter = router({
394
+ createCheckout: protectedProcedure
395
+ .input(z.object({ priceId: z.string() }))
396
+ .mutation(async ({ ctx, input }) => {
397
+ const session = await stripe.checkout.sessions.create({
398
+ customer_email: ctx.user.email,
399
+ mode: 'payment',
400
+ line_items: [{ price: input.priceId, quantity: 1 }],
401
+ success_url: `${env.PUBLIC_APP_URL}/success`,
402
+ cancel_url: `${env.PUBLIC_APP_URL}/cancel`,
403
+ })
404
+ return { url: session.url }
405
+ }),
406
+ })
407
+ ```
408
+
409
+ ### Email Service Extensions
410
+
411
+ Customize email templates in `src/lib/email-templates/`:
412
+
413
+ ```typescript
414
+ import { Resend } from 'resend'
415
+
416
+ const resend = new Resend(env.RESEND_API_KEY)
417
+
418
+ await resend.emails.send({
419
+ from: 'your-app@yourdomain.com',
420
+ to: 'user@example.com',
421
+ subject: 'Welcome!',
422
+ html: '<h1>Welcome to our app</h1>',
423
+ })
424
+ ```
425
+
426
+ ## Testing New Features
427
+
428
+ 1. **Add table** → `npm run db:generate && npm run db:migrate`
429
+ 2. **Add tRPC procedure** → Test in tRPC DevTools
430
+ 3. **Add UI component** → Test in isolation
431
+ 4. **Add page** → Test navigation and rendering
432
+ 5. **Update types** → Verify type safety throughout
433
+
434
+ Best practices:
435
+
436
+ - Test each layer independently (database → tRPC → UI)
437
+ - Use TypeScript to catch errors at compile time
438
+ - Add loading and error states for better UX
439
+ - Invalidate queries after mutations for data consistency
@@ -0,0 +1,22 @@
1
+ # ADR-001: Use SQLite for Development Database
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Context
8
+
9
+ Need development database with zero external dependencies but can migrate to production PostgreSQL
10
+
11
+ ## Decision
12
+
13
+ Use SQLite for local development with automatic file creation
14
+
15
+ ## Consequences
16
+
17
+ - **Positive**: Instant setup, no Docker/external services needed
18
+ - **Negative**: Some PostgreSQL features not available in development
19
+
20
+ ## Implementation
21
+
22
+ Auto-create data/app.db if DATABASE_URL not provided, use same Drizzle schema for both SQLite and PostgreSQL
@@ -0,0 +1,22 @@
1
+ # ADR-002: Use TanStack Start over Next.js
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Context
8
+
9
+ Need modern React framework with SSR and file-based routing
10
+
11
+ ## Decision
12
+
13
+ Use TanStack Start instead of Next.js
14
+
15
+ ## Consequences
16
+
17
+ - **Positive**: Better TypeScript integration, file-based routing without App Router complexity, smaller bundle size
18
+ - **Negative**: Smaller community, less documentation
19
+
20
+ ## Implementation
21
+
22
+ Configure Vinxi bundler, file-based routing in src/app/, SSR enabled by default
@@ -0,0 +1,22 @@
1
+ # ADR-003: Use Better-auth over NextAuth
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Context
8
+
9
+ Need authentication library with email/password, session management, and TypeScript support
10
+
11
+ ## Decision
12
+
13
+ Use Better-auth instead of NextAuth
14
+
15
+ ## Consequences
16
+
17
+ - **Positive**: Simpler configuration for basic email/password auth, better TypeScript support, database session storage built-in
18
+ - **Negative**: Less mature, smaller ecosystem
19
+
20
+ ## Implementation
21
+
22
+ Configure with Drizzle adapter, email verification flow, session management
@@ -0,0 +1,22 @@
1
+ # ADR-004: Use Drizzle over Prisma
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Context
8
+
9
+ Need ORM with type safety and migration management for micro-SaaS
10
+
11
+ ## Decision
12
+
13
+ Use Drizzle ORM instead of Prisma
14
+
15
+ ## Consequences
16
+
17
+ - **Positive**: Zero runtime overhead with compile-time query building, better migration control with SQL-first approach, lighter weight
18
+ - **Negative**: Less intuitive API, fewer built-in features
19
+
20
+ ## Implementation
21
+
22
+ Define schema in src/server/db/schema.ts, use drizzle-kit for migrations
@@ -0,0 +1,22 @@
1
+ # ADR-005: Use tRPC for API Layer
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Context
8
+
9
+ Need type-safe API with zero manual interface definitions
10
+
11
+ ## Decision
12
+
13
+ Use tRPC v11 for API layer
14
+
15
+ ## Consequences
16
+
17
+ - **Positive**: End-to-end type safety from database to UI, automatic type inference, zero code generation
18
+ - **Negative**: Learning curve, different patterns from REST APIs
19
+
20
+ ## Implementation
21
+
22
+ Define routers in src/server/api/routers/, integrate with TanStack Query
@@ -0,0 +1,22 @@
1
+ # ADR-006: Use Tailwind CSS v4 with shadcn/ui
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Context
8
+
9
+ Need styling system with rapid development and professional components
10
+
11
+ ## Decision
12
+
13
+ Use Tailwind CSS v4 with shadcn/ui components
14
+
15
+ ## Consequences
16
+
17
+ - **Positive**: Rapid development, consistent design system, no need for custom CSS
18
+ - **Negative**: Large HTML classes, potential bundle size increase
19
+
20
+ ## Implementation
21
+
22
+ Configure Tailwind v4, install shadcn/ui components, use for all UI