@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,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
|
+
}
|