@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,119 @@
1
+ /**
2
+ * Environment variable validation and configuration module.
3
+ *
4
+ * This module uses the @t3-oss/env-core library to provide type-safe environment variable
5
+ * validation at runtime. It ensures all required environment variables are present and
6
+ * properly typed before the application starts, failing fast with clear error messages
7
+ * if validation fails.
8
+ *
9
+ * Key patterns and conventions:
10
+ * - Environment variables are loaded from .env.local file (not .env) to prevent
11
+ * accidentally committing secrets
12
+ * - Server-side variables use default names, client-side use PUBLIC_ prefix
13
+ * - Zod schemas provide both runtime validation and TypeScript type inference
14
+ * - Empty strings are treated as undefined (emptyStringAsUndefined: true) to allow
15
+ * optional variables to be unset without empty string handling
16
+ *
17
+ * Security considerations:
18
+ * - BETTER_AUTH_SECRET must be at least 32 characters for JWT signing security
19
+ * - RESEND_API_KEY is loaded from environment, never hardcoded
20
+ * - DATABASE_URL is optional to support different database configurations
21
+ * - Skip validation flag (SKIP_ENV_VALIDATION) exists for CI/CD environments
22
+ * where not all env vars may be needed, but should NOT be used in production
23
+ * - Client-side variables (PUBLIC_ prefix) are exposed to browser - never put
24
+ * secrets there
25
+ *
26
+ * Configuration decisions:
27
+ * - Using @t3-oss/env-core instead of direct process.env access for:
28
+ * - Type safety with inferred TypeScript types
29
+ * - Runtime validation that fails fast on startup
30
+ * - Clear error messages when validation fails
31
+ * - Separation of server and client environment variables
32
+ * - .env.local path prevents git commits of secrets (already in .gitignore)
33
+ * - Better-auth requires at least 32 characters for secrets to match
34
+ * cryptographic best practices for JWT signing keys
35
+ */
36
+
37
+ import { createEnv } from '@t3-oss/env-core'
38
+ import { z } from 'zod'
39
+ import dotenv from 'dotenv'
40
+
41
+ dotenv.config({ path: '.env.local' })
42
+
43
+ /**
44
+ * Type-safe environment configuration object.
45
+ *
46
+ * This object is the single source of truth for all environment variables in the
47
+ * application. Access environment variables through this object to ensure type
48
+ * safety and proper validation.
49
+ *
50
+ * Server variables are only available on the server (Node.js runtime).
51
+ * Client variables (prefixed with PUBLIC_) are available in the browser.
52
+ *
53
+ * @example
54
+ * // Server-side usage
55
+ * const dbUrl = env.DATABASE_URL
56
+ * const secret = env.BETTER_AUTH_SECRET
57
+ *
58
+ * // Client-side usage (e.g., in React components)
59
+ * const appUrl = env.PUBLIC_APP_URL
60
+ *
61
+ * @throws {Error} If validation fails on startup (unless SKIP_ENV_VALIDATION=true)
62
+ */
63
+ export const env = createEnv({
64
+ server: {
65
+ /**
66
+ * Database connection URL.
67
+ * Optional to support different database configurations or test environments
68
+ * without a database connection.
69
+ */
70
+ DATABASE_URL: z.string().url().optional(),
71
+ /**
72
+ * Secret key for Better-Auth JWT signing and session encryption.
73
+ * Must be at least 32 characters to ensure sufficient entropy for
74
+ * cryptographic operations. This is critical for security - never commit
75
+ * this value to version control.
76
+ */
77
+ BETTER_AUTH_SECRET: z.string().min(32),
78
+ /**
79
+ * Base URL for the application, used by Better-Auth for OAuth redirects
80
+ * and callback URLs. Must be a valid URL including protocol (http:// or https://).
81
+ */
82
+ BETTER_AUTH_URL: z.string().url(),
83
+ /**
84
+ * API key for Resend email service. Used to send transactional emails.
85
+ * Required for email verification and password reset functionality.
86
+ * Never expose this value to the client.
87
+ */
88
+ RESEND_API_KEY: z.string().min(1),
89
+ /**
90
+ * Node environment that determines application behavior.
91
+ * - development: Enables debug logging, detailed error messages
92
+ * - test: Optimized for running test suites
93
+ * - production: Optimized for performance, minimal logging
94
+ */
95
+ NODE_ENV: z
96
+ .enum(['development', 'test', 'production'])
97
+ .default('development'),
98
+ },
99
+ clientPrefix: 'PUBLIC_',
100
+ client: {
101
+ /**
102
+ * Public base URL for the application.
103
+ * Available on both server and client. Used for:
104
+ * - Constructing absolute URLs in the browser
105
+ * - OAuth redirect URLs
106
+ * - Email verification and password reset links
107
+ * Note: Never include sensitive data in PUBLIC_ prefixed variables.
108
+ */
109
+ PUBLIC_APP_URL: z.string().url(),
110
+ },
111
+ runtimeEnv: process.env,
112
+ emptyStringAsUndefined: true,
113
+ /**
114
+ * Allows skipping validation in CI/CD environments where not all env vars
115
+ * are needed (e.g., running type checking without database).
116
+ * SECURITY WARNING: Never use this in production deployment.
117
+ */
118
+ skipValidation: process.env.SKIP_ENV_VALIDATION === 'true',
119
+ })
@@ -0,0 +1,289 @@
1
+ /**
2
+ * React Server Components (RSC) hydration timing module.
3
+ *
4
+ * This module provides utilities to measure and track the time it takes for React
5
+ * components to hydrate on the client side. Hydration is the process where React
6
+ * attaches event listeners to server-rendered HTML and makes the page interactive.
7
+ *
8
+ * Key patterns and conventions:
9
+ * - Timing is measured using performance.now() for sub-millisecond precision
10
+ * - Component names are used as keys to group measurements
11
+ * - Slow hydration warnings are only shown in development mode
12
+ * - Metrics are stored globally (hydrationTimings object) for easy access
13
+ * - Metrics persist across page renders for cumulative analysis
14
+ *
15
+ * Performance implications:
16
+ * - Negligible overhead: performance.now() calls take sub-microsecond time
17
+ * - No blocking operations: All timing is synchronous and non-blocking
18
+ * - Memory usage: Grows with the number of unique components measured
19
+ * - Development-only warnings: Console.warn is skipped in production
20
+ *
21
+ * Configuration decisions:
22
+ * - SLOW_HYDRATION_THRESHOLD of 100ms is chosen because:
23
+ * - React's target time to hydration is <100ms for optimal user experience
24
+ * - Users perceive delays >100ms as perceptible lag
25
+ * - Helps identify components that cause blocking JavaScript execution
26
+ * - Global storage (hydrationTimings object) simplifies access but means:
27
+ * - Metrics accumulate indefinitely during the session
28
+ * - Reset should be called between test runs or when starting fresh
29
+ * - Component name as key (not component instance) aggregates timing:
30
+ * - All instances of the same component share timing data
31
+ * - Useful for identifying slow component types, not specific instances
32
+ *
33
+ * Use cases:
34
+ * - Development: Identify components that cause slow hydration
35
+ * - Performance optimization: Target the slowest components for optimization
36
+ * - Testing: Assert that hydration times meet performance budgets
37
+ * - Production monitoring: Can be integrated with analytics (with sampling)
38
+ *
39
+ * How hydration timing works:
40
+ * 1. Server renders HTML and sends to client
41
+ * 2. Client receives HTML and renders it immediately
42
+ * 3. React downloads JavaScript and starts hydration
43
+ * 4. For each component, markHydrationStart() is called
44
+ * 5. Component processes and attaches event listeners
45
+ * 6. markHydrationEnd() is called with duration calculated
46
+ * 7. Slow hydrations (>100ms) are warned in development
47
+ *
48
+ * @example
49
+ * // In a component
50
+ * import { markHydrationStart, markHydrationEnd } from '@/lib/hydration-timing'
51
+ *
52
+ * export function MyComponent() {
53
+ * if (typeof window !== 'undefined') {
54
+ * markHydrationStart('MyComponent')
55
+ * }
56
+ * // ... component logic
57
+ * useEffect(() => {
58
+ * if (typeof window !== 'undefined') {
59
+ * markHydrationEnd('MyComponent')
60
+ * }
61
+ * }, [])
62
+ * }
63
+ */
64
+
65
+ /**
66
+ * Global storage for hydration timing metrics.
67
+ *
68
+ * This object stores both start times and durations for measured components.
69
+ * The naming convention is:
70
+ * - `${componentName}_start`: The timestamp when hydration started
71
+ * - `${componentName}_duration`: The calculated hydration duration in ms
72
+ *
73
+ * Why use a plain object instead of a Map:
74
+ * - Simpler to debug (visible in console as object)
75
+ * - Easy to serialize for analytics if needed
76
+ * - Sufficient for the expected number of components (<100 typically)
77
+ *
78
+ * Memory usage: O(n) where n is the number of unique components measured.
79
+ * Each entry stores a number (8 bytes) plus object overhead.
80
+ *
81
+ * Note: This object is mutable and accessed globally. Use the helper
82
+ * functions (markHydrationStart, markHydrationEnd) to interact with it
83
+ * to maintain consistent behavior.
84
+ */
85
+ export const hydrationTimings: Record<string, number> = {}
86
+
87
+ /**
88
+ * Marks the start of hydration for a component.
89
+ *
90
+ * This function records the current timestamp for a component, which will be
91
+ * used later to calculate the hydration duration when markHydrationEnd is called.
92
+ *
93
+ * Performance characteristics:
94
+ * - Time complexity: O(1)
95
+ * - Space complexity: O(1) per call (one number stored)
96
+ * - Overhead: Negligible (<1 microsecond)
97
+ *
98
+ * When to call:
99
+ * - At the very beginning of component hydration
100
+ * - Before any component logic that might block
101
+ * - Only on the client (check typeof window !== 'undefined')
102
+ * - For top-level components that significantly affect perceived performance
103
+ *
104
+ * Best practices:
105
+ * - Use consistent component names across your application
106
+ * - Avoid measuring every component (too much noise)
107
+ * - Focus on heavy components: lists, forms, data-heavy displays
108
+ * - Pair with markHydrationEnd to calculate duration
109
+ *
110
+ * @param componentName - The name of the component being hydrated.
111
+ * Use a consistent naming convention (e.g., PascalCase).
112
+ * This is used as the key for storing metrics.
113
+ *
114
+ * @example
115
+ * if (typeof window !== 'undefined') {
116
+ * markHydrationStart('UserDashboard')
117
+ * }
118
+ */
119
+ export function markHydrationStart(componentName: string) {
120
+ hydrationTimings[`${componentName}_start`] = performance.now()
121
+ }
122
+
123
+ /**
124
+ * Marks the end of hydration for a component and logs slow hydrations.
125
+ *
126
+ * This function calculates the hydration duration by comparing the current time
127
+ * with the previously recorded start time. If the component took longer than
128
+ * 100ms to hydrate, a warning is logged in development mode.
129
+ *
130
+ * Performance characteristics:
131
+ * - Time complexity: O(1)
132
+ * - Overhead: Negligible (<1 microsecond for calculation)
133
+ * - Console.warn is the only potentially expensive operation, but only in
134
+ * development and only for slow hydrations
135
+ *
136
+ * Slow hydration threshold (100ms):
137
+ * - Chosen based on user perception research: 100ms is the threshold for
138
+ * "instant" feeling
139
+ * - React's hydration should ideally complete within this window
140
+ * - Components exceeding this are candidates for optimization
141
+ *
142
+ * When to call:
143
+ * - After component logic completes
144
+ * - After all child components have hydrated
145
+ * - In a useEffect or similar to ensure it runs after render
146
+ * - Only on the client (check typeof window !== 'undefined')
147
+ *
148
+ * Edge cases:
149
+ * - If start time doesn't exist (markHydrationStart not called), this
150
+ * function does nothing silently
151
+ * - Multiple calls for the same component: Each call calculates duration
152
+ * from the most recent start time
153
+ *
154
+ * @param componentName - The name of the component that finished hydrating.
155
+ * Must match the name passed to markHydrationStart.
156
+ *
157
+ * @example
158
+ * useEffect(() => {
159
+ * if (typeof window !== 'undefined') {
160
+ * markHydrationEnd('UserDashboard')
161
+ * }
162
+ * }, [])
163
+ */
164
+ export function markHydrationEnd(componentName: string) {
165
+ const startTime = hydrationTimings[`${componentName}_start`]
166
+ if (startTime) {
167
+ const duration = performance.now() - startTime
168
+ hydrationTimings[`${componentName}_duration`] = duration
169
+
170
+ /**
171
+ * Warn about slow hydrations in development only.
172
+ *
173
+ * Why development-only:
174
+ * - Console.warn in production can impact performance
175
+ * - Production users don't need to see warnings
176
+ * - Production monitoring should use analytics tools instead
177
+ *
178
+ * Why 100ms threshold:
179
+ * - Based on research: users perceive delays >100ms as "laggy"
180
+ * - React's target: <100ms to interactivity
181
+ * - Helps identify blocking components for optimization
182
+ */
183
+ if (import.meta.env.DEV && duration > 100) {
184
+ console.warn(
185
+ `[Slow Hydration] ${componentName}: ${duration.toFixed(2)}ms`
186
+ )
187
+ }
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Retrieves all hydration metrics, sorted by duration descending.
193
+ *
194
+ * This function returns an array of component hydration durations, sorted
195
+ * so the slowest components appear first. This is useful for identifying
196
+ * which components have the biggest impact on page load performance.
197
+ *
198
+ * Performance characteristics:
199
+ * - Time complexity: O(n) for iterating through keys + O(n log n) for sorting
200
+ * - Space complexity: O(n) for the results array
201
+ * - Typically very fast for realistic numbers of components (<100)
202
+ *
203
+ * Return format:
204
+ * - Array of objects with component name and duration
205
+ * - Sorted by duration descending (slowest first)
206
+ * - Only includes components that have completed hydration
207
+ * - Durations are in milliseconds (raw values, not averaged)
208
+ *
209
+ * Use cases:
210
+ * - Development: Identify the slowest components to optimize
211
+ * - Testing: Assert that component hydration meets performance budgets
212
+ * - Analytics: Send hydration metrics to performance monitoring services
213
+ * - Debugging: Understand which components contribute most to TTI (Time to Interactive)
214
+ *
215
+ * Limitations:
216
+ * - Includes all historical measurements (accumulates during session)
217
+ * - Call resetHydrationMetrics() to start fresh
218
+ * - Averages are not calculated; returns individual durations
219
+ *
220
+ * @returns Array of hydration metrics, each containing:
221
+ * - component: The component name (without _duration suffix)
222
+ * - duration: The hydration time in milliseconds
223
+ *
224
+ * @example
225
+ * const metrics = getHydrationMetrics()
226
+ * console.log('Top 3 slowest components:')
227
+ * metrics.slice(0, 3).forEach(({ component, duration }) => {
228
+ * console.log(`${component}: ${duration.toFixed(2)}ms`)
229
+ * })
230
+ */
231
+ export function getHydrationMetrics() {
232
+ const metrics: Array<{ component: string; duration: number }> = []
233
+
234
+ /**
235
+ * Iterate through all recorded timings.
236
+ *
237
+ * Object.keys iteration order is not guaranteed, so we sort afterwards.
238
+ * We only include entries ending in '_duration' to exclude start times.
239
+ */
240
+ Object.keys(hydrationTimings).forEach((key) => {
241
+ if (key.endsWith('_duration')) {
242
+ const component = key.replace('_duration', '')
243
+ const duration = hydrationTimings[key] || 0
244
+ metrics.push({ component, duration })
245
+ }
246
+ })
247
+
248
+ /**
249
+ * Sort by duration descending.
250
+ *
251
+ * This puts the slowest components first, which is useful for:
252
+ * - Prioritizing optimization efforts
253
+ * - Quickly identifying the biggest performance bottlenecks
254
+ * - Focusing on components that have the most impact on TTI
255
+ */
256
+ return metrics.sort((a, b) => b.duration - a.duration)
257
+ }
258
+
259
+ /**
260
+ * Clears all recorded hydration metrics.
261
+ *
262
+ * This function removes all start times and durations from the global
263
+ * hydrationTimings object. Use this to start fresh measurements.
264
+ *
265
+ * Performance characteristics:
266
+ * - Time complexity: O(n) where n is the number of recorded metrics
267
+ * - Memory: Immediately eligible for garbage collection (subject to V8 GC)
268
+ *
269
+ * When to call:
270
+ * - Between test runs to ensure clean state
271
+ * - When starting a new performance measurement session
272
+ * - Before running specific performance tests
273
+ * - After gathering metrics for analysis
274
+ *
275
+ * CAUTION: This removes all metrics, including those from previous components.
276
+ * If you need to preserve certain metrics, copy them before calling this function.
277
+ *
278
+ * @example
279
+ * // Reset before a performance test
280
+ * resetHydrationMetrics()
281
+ * await runPageLoad()
282
+ * const metrics = getHydrationMetrics()
283
+ * console.log('Hydration times after reset:', metrics)
284
+ */
285
+ export function resetHydrationMetrics() {
286
+ Object.keys(hydrationTimings).forEach((key) => {
287
+ delete hydrationTimings[key]
288
+ })
289
+ }