@nestjs-ssr/react 0.1.12 → 0.2.1

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/dist/index.d.mts CHANGED
@@ -1,17 +1,20 @@
1
- import { H as HeadData } from './index-BiaVDe9J.mjs';
2
- export { E as ErrorPageDevelopment, f as ErrorPageProduction, c as RenderConfig, b as RenderInterceptor, R as RenderModule, e as RenderResponse, a as RenderService, d as SSRMode, S as StreamingErrorHandler, T as TemplateParserService } from './index-BiaVDe9J.mjs';
3
- import React from 'react';
1
+ import { H as HeadData, R as RenderResponse } from './index-C5Knql-9.mjs';
2
+ export { E as ErrorPageDevelopment, f as ErrorPageProduction, d as RenderConfig, c as RenderInterceptor, a as RenderModule, b as RenderService, e as SSRMode, S as StreamingErrorHandler, T as TemplateParserService } from './index-C5Knql-9.mjs';
3
+ import React$1, { ComponentType, ReactNode } from 'react';
4
+ import * as react_jsx_runtime from 'react/jsx-runtime';
4
5
  import '@nestjs/common';
5
6
  import 'vite';
6
7
  import 'express';
7
8
  import '@nestjs/core';
8
9
  import 'rxjs';
9
- import 'react/jsx-runtime';
10
10
 
11
11
  /**
12
12
  * Request context available to all React components.
13
13
  * Contains safe request metadata that can be exposed to the client.
14
14
  *
15
+ * Extend this interface to add app-specific properties (user, tenant, feature flags, etc.).
16
+ * Use module configuration to pass additional headers or cookies safely.
17
+ *
15
18
  * @example
16
19
  * // Basic usage - use as-is
17
20
  * const context: RenderContext = {
@@ -19,6 +22,7 @@ import 'react/jsx-runtime';
19
22
  * path: '/users/123',
20
23
  * query: { tab: 'profile' },
21
24
  * params: { id: '123' },
25
+ * method: 'GET',
22
26
  * };
23
27
  *
24
28
  * @example
@@ -34,15 +38,22 @@ import 'react/jsx-runtime';
34
38
  * id: string;
35
39
  * name: string;
36
40
  * };
37
- * locale?: string;
41
+ * featureFlags?: Record<string, boolean>;
42
+ * theme?: string; // From cookie
38
43
  * }
39
44
  *
40
- * // Use in interceptor
45
+ * // Configure module to pass specific cookies/headers
46
+ * ReactSSRModule.forRoot({
47
+ * allowedCookies: ['theme', 'locale'],
48
+ * allowedHeaders: ['x-tenant-id'],
49
+ * })
50
+ *
51
+ * // Use in interceptor/controller
41
52
  * const context: AppRenderContext = {
42
53
  * ...baseContext,
43
54
  * user: req.user,
44
55
  * tenant: req.tenant,
45
- * locale: req.locale,
56
+ * featureFlags: await featureFlagService.getFlags(req),
46
57
  * };
47
58
  */
48
59
  interface RenderContext {
@@ -50,27 +61,40 @@ interface RenderContext {
50
61
  path: string;
51
62
  query: Record<string, string | string[]>;
52
63
  params: Record<string, string>;
53
- userAgent?: string;
54
- acceptLanguage?: string;
55
- referer?: string;
56
- [key: string]: any;
64
+ method: string;
57
65
  }
58
66
 
59
67
  /**
60
68
  * Generic type for React page component props.
61
69
  * Spreads controller data directly as props (React-standard pattern).
62
70
  *
71
+ * Request context is available via typed hooks created with createSSRHooks().
72
+ *
63
73
  * @template TProps - The shape of props returned by the controller
64
74
  *
65
75
  * @example
66
76
  * ```typescript
77
+ * // src/lib/ssr-hooks.ts
78
+ * import { createSSRHooks, RenderContext } from '@nestjs-ssr/react';
79
+ *
80
+ * interface AppRenderContext extends RenderContext {
81
+ * user?: User;
82
+ * }
83
+ *
84
+ * export const { usePageContext } = createSSRHooks<AppRenderContext>();
85
+ *
86
+ * // src/views/product.tsx
87
+ * import { usePageContext } from '@/lib/ssr-hooks';
88
+ *
67
89
  * interface ProductPageProps {
68
90
  * product: Product;
69
91
  * relatedProducts: Product[];
70
92
  * }
71
93
  *
72
94
  * export default function ProductDetail(props: PageProps<ProductPageProps>) {
73
- * const { product, relatedProducts, head, context } = props;
95
+ * const { product, relatedProducts, head } = props;
96
+ * const context = usePageContext(); // Fully typed!
97
+ *
74
98
  * return (
75
99
  * <html>
76
100
  * <head>
@@ -78,6 +102,7 @@ interface RenderContext {
78
102
  * </head>
79
103
  * <body>
80
104
  * <h1>{product.name}</h1>
105
+ * <p>Current path: {context.path}</p>
81
106
  * </body>
82
107
  * </html>
83
108
  * );
@@ -108,17 +133,98 @@ type PageProps<TProps = {}> = TProps & {
108
133
  * ```
109
134
  */
110
135
  head?: HeadData;
136
+ };
137
+
138
+ /**
139
+ * Props passed to layout components
140
+ *
141
+ * Layout components receive children and can access context/head data.
142
+ * Additional props can be specified via layoutProps static property.
143
+ *
144
+ * @example
145
+ * ```tsx
146
+ * export default function MainLayout({ children, title }: LayoutProps<{ title: string }>) {
147
+ * return (
148
+ * <html>
149
+ * <head>
150
+ * <title>{title || 'Default Title'}</title>
151
+ * </head>
152
+ * <body>
153
+ * <nav>...</nav>
154
+ * <main>{children}</main>
155
+ * </body>
156
+ * </html>
157
+ * );
158
+ * }
159
+ * ```
160
+ */
161
+ interface LayoutProps<TProps = {}> {
111
162
  /**
112
- * Request context containing URL metadata and safe headers.
113
- * Always available on every page component.
114
- *
115
- * @example
116
- * ```typescript
117
- * const { path, query, method } = props.context;
118
- * ```
163
+ * Child content to render (the page component or nested layout)
119
164
  */
120
- context: RenderContext;
121
- };
165
+ children: ReactNode;
166
+ /**
167
+ * Layout-specific props passed via component.layoutProps
168
+ */
169
+ layoutProps?: TProps;
170
+ /**
171
+ * Request context available to all layouts
172
+ */
173
+ context?: RenderContext;
174
+ /**
175
+ * Head metadata that can be read by layouts
176
+ */
177
+ head?: HeadData;
178
+ }
179
+ /**
180
+ * Layout component type
181
+ *
182
+ * A layout is a React component that wraps page content.
183
+ * Page components can declare their layout using static properties.
184
+ *
185
+ * @example
186
+ * ```tsx
187
+ * // Layout definition
188
+ * const MainLayout: LayoutComponent<{ title: string }> = ({ children, title }) => (
189
+ * <html>
190
+ * <body>
191
+ * <h1>{title}</h1>
192
+ * {children}
193
+ * </body>
194
+ * </html>
195
+ * );
196
+ *
197
+ * // Page using the layout
198
+ * function HomePage() {
199
+ * return <div>Welcome</div>;
200
+ * }
201
+ * HomePage.layout = MainLayout;
202
+ * HomePage.layoutProps = { title: 'Home' };
203
+ * ```
204
+ */
205
+ type LayoutComponent<TProps = {}> = ComponentType<LayoutProps<TProps>>;
206
+ /**
207
+ * Enhanced page component with layout support
208
+ *
209
+ * Page components can optionally specify a layout via static properties.
210
+ * The framework will automatically wrap the page in the specified layout.
211
+ */
212
+ interface PageComponentWithLayout<TPageProps = {}, TLayoutProps = {}> {
213
+ /**
214
+ * The page component function
215
+ */
216
+ (props: TPageProps): ReactNode;
217
+ /**
218
+ * Optional layout component to wrap this page
219
+ * If not specified, the page renders without a layout wrapper.
220
+ */
221
+ layout?: LayoutComponent<TLayoutProps>;
222
+ /**
223
+ * Optional props to pass to the layout component
224
+ * These props are available as layoutProps in the LayoutProps.
225
+ */
226
+ layoutProps?: TLayoutProps;
227
+ }
122
228
 
123
229
  /**
124
230
  * Extract the data type T from PageProps<T>.
@@ -131,7 +237,29 @@ type ExtractPagePropsData<P> = P extends PageProps<infer T> ? T : P extends {
131
237
  /**
132
238
  * Extract controller return type from a React component's props.
133
239
  */
134
- type ExtractComponentData<T> = T extends React.ComponentType<infer P> ? ExtractPagePropsData<P> : never;
240
+ type ExtractComponentData<T> = T extends React$1.ComponentType<infer P> ? ExtractPagePropsData<P> : never;
241
+ /**
242
+ * Valid return types for a @Render decorated controller method.
243
+ * Supports both simple props format and RenderResponse format with layoutProps.
244
+ */
245
+ type RenderReturnType<T> = T | RenderResponse<T>;
246
+ /**
247
+ * Options for the Render decorator
248
+ */
249
+ interface RenderOptions {
250
+ /**
251
+ * Layout component to wrap this specific route.
252
+ * - LayoutComponent: Use this layout (replaces controller layout if any)
253
+ * - false: Skip controller layout, keep root layout only
254
+ * - null: Skip all layouts (render page only)
255
+ * - undefined: Use controller layout (default)
256
+ */
257
+ layout?: LayoutComponent<any> | false | null;
258
+ /**
259
+ * Props to pass to the layout component
260
+ */
261
+ layoutProps?: Record<string, any>;
262
+ }
135
263
  /**
136
264
  * Decorator to render a React component as the response.
137
265
  *
@@ -139,6 +267,7 @@ type ExtractComponentData<T> = T extends React.ComponentType<infer P> ? ExtractP
139
267
  * TypeScript automatically validates your controller returns the correct props.
140
268
  *
141
269
  * @param component - The React component to render
270
+ * @param options - Optional rendering options (layout overrides, etc.)
142
271
  *
143
272
  * @example
144
273
  * ```typescript
@@ -157,73 +286,252 @@ type ExtractComponentData<T> = T extends React.ComponentType<infer P> ? ExtractP
157
286
  * return { message: 'Hello' }; // ✅ Correct
158
287
  * // return { wrong: 'prop' }; // ❌ Type error!
159
288
  * }
289
+ *
290
+ * // With layout override
291
+ * @Get('custom')
292
+ * @Render(CustomPage, { layout: CustomLayout })
293
+ * getCustom() {
294
+ * return { data: 'custom' };
295
+ * }
296
+ *
297
+ * // Skip all layouts
298
+ * @Get('raw')
299
+ * @Render(RawPage, { layout: null })
300
+ * getRaw() {
301
+ * return { json: {...} };
302
+ * }
160
303
  * ```
161
304
  */
162
- declare function Render<T extends React.ComponentType<any>>(component: T): <TMethod extends (...args: any[]) => ExtractComponentData<T> | Promise<ExtractComponentData<T>>>(target: any, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<TMethod>) => TypedPropertyDescriptor<TMethod> | void;
305
+ declare function Render<T extends React$1.ComponentType<any>>(component: T, options?: RenderOptions): <TMethod extends (...args: any[]) => RenderReturnType<ExtractComponentData<T>> | Promise<RenderReturnType<ExtractComponentData<T>>>>(target: any, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<TMethod>) => TypedPropertyDescriptor<TMethod> | void;
306
+
163
307
  /**
164
- * @deprecated Use `Render` instead. This alias will be removed in a future version.
308
+ * Options for the Layout decorator
165
309
  */
166
- declare const ReactRender: typeof Render;
167
-
310
+ interface LayoutDecoratorOptions {
311
+ /**
312
+ * Whether to skip the root layout for this controller
313
+ * @default false
314
+ */
315
+ skipRoot?: boolean;
316
+ /**
317
+ * Props to pass to the layout component
318
+ */
319
+ props?: Record<string, any>;
320
+ }
168
321
  /**
169
- * Hook to access the full page context.
170
- * Contains URL metadata and request headers.
171
- *
172
- * For apps with authentication, extend RenderContext and create custom hooks.
322
+ * Controller-level decorator to apply a layout to all routes in the controller.
173
323
  *
174
- * @throws Error if used outside PageContextProvider
324
+ * The layout hierarchy is: Root Layout → Controller Layout → Method Layout → Page
175
325
  *
176
- * @example
177
- * ```tsx
178
- * const context = usePageContext();
179
- * console.log(context.path); // '/users/123'
180
- * console.log(context.query); // { search: 'foo' }
181
- * ```
326
+ * @param layout - The layout component to wrap all routes in this controller
327
+ * @param options - Optional configuration for the layout
182
328
  *
183
329
  * @example
184
- * // Custom hook for extended context
185
- * interface AppRenderContext extends RenderContext {
186
- * user?: { id: string; name: string };
330
+ * ```typescript
331
+ * // Simple usage
332
+ * @Controller('dashboard')
333
+ * @Layout(DashboardLayout)
334
+ * export class DashboardController {
335
+ * @Get()
336
+ * @Render(DashboardPage) // Renders: Root > DashboardLayout > Page
337
+ * getDashboard() {
338
+ * return { stats: {...} };
339
+ * }
187
340
  * }
188
341
  *
189
- * export function useUser() {
190
- * return (usePageContext() as AppRenderContext).user;
191
- * }
342
+ * // With options
343
+ * @Controller('admin')
344
+ * @Layout(AdminLayout, { skipRoot: false, props: { theme: 'dark' } })
345
+ * export class AdminController { }
346
+ * ```
192
347
  */
193
- declare function usePageContext(): RenderContext;
348
+ declare function Layout(layout: LayoutComponent<any>, options?: LayoutDecoratorOptions): ClassDecorator;
349
+
194
350
  /**
195
- * Hook to access route parameters.
196
- *
197
- * @example
198
- * ```tsx
199
- * // Route: /users/:id
200
- * const params = useParams();
201
- * console.log(params.id); // '123'
202
- * ```
351
+ * Provider component that makes page context available to all child components.
352
+ * Should wrap the entire app in entry-server and entry-client.
203
353
  */
204
- declare function useParams(): Record<string, string>;
354
+ declare function PageContextProvider({ context, children, }: {
355
+ context: RenderContext;
356
+ children: React.ReactNode;
357
+ }): react_jsx_runtime.JSX.Element;
205
358
  /**
206
- * Hook to access query string parameters.
359
+ * Factory function to create typed SSR hooks bound to your app's context type.
360
+ * Use this once in your app to create hooks with full type safety.
361
+ *
362
+ * This eliminates the need to pass generic types to every hook call,
363
+ * providing excellent DX with full IntelliSense support.
364
+ *
365
+ * @template T - Your extended RenderContext type with app-specific properties
207
366
  *
208
367
  * @example
209
- * ```tsx
210
- * // URL: /search?q=react&sort=date
211
- * const query = useQuery();
212
- * console.log(query.q); // 'react'
213
- * console.log(query.sort); // 'date'
368
+ * ```typescript
369
+ * // src/lib/ssr-hooks.ts - Define once
370
+ * import { createSSRHooks, RenderContext } from '@nestjs-ssr/react';
371
+ *
372
+ * interface AppRenderContext extends RenderContext {
373
+ * user?: {
374
+ * id: string;
375
+ * name: string;
376
+ * email: string;
377
+ * };
378
+ * tenant?: { id: string; name: string };
379
+ * featureFlags?: Record<string, boolean>;
380
+ * theme?: string; // From cookie
381
+ * }
382
+ *
383
+ * export const {
384
+ * usePageContext,
385
+ * useParams,
386
+ * useQuery,
387
+ * useRequest,
388
+ * useHeaders,
389
+ * useHeader,
390
+ * useCookies,
391
+ * useCookie,
392
+ * } = createSSRHooks<AppRenderContext>();
393
+ *
394
+ * // Create custom helper hooks
395
+ * export const useUser = () => usePageContext().user;
396
+ * export const useTheme = () => useCookie('theme');
397
+ * export const useUserAgent = () => useHeader('user-agent');
214
398
  * ```
215
- */
216
- declare function useQuery(): Record<string, string | string[]>;
217
- /**
218
- * Hook to access the User-Agent header.
219
- * Useful for device detection or analytics.
220
399
  *
221
400
  * @example
222
- * ```tsx
223
- * const userAgent = useUserAgent();
224
- * const isMobile = /Mobile/.test(userAgent || '');
401
+ * ```typescript
402
+ * // src/views/home.tsx - Use everywhere with full types
403
+ * import { usePageContext, useUser, useTheme, useCookie, useHeader } from '@/lib/ssr-hooks';
404
+ *
405
+ * export default function Home() {
406
+ * const { user, featureFlags } = usePageContext(); // ✅ Fully typed!
407
+ * const user = useUser(); // ✅ Also typed!
408
+ * const theme = useTheme(); // ✅ From cookie
409
+ * const locale = useCookie('locale'); // ✅ Access specific cookie
410
+ * const tenantId = useHeader('x-tenant-id'); // ✅ Access specific header
411
+ *
412
+ * return (
413
+ * <div>
414
+ * <h1>Welcome {user?.name}</h1>
415
+ * <p>Theme: {theme}</p>
416
+ * <p>Locale: {locale}</p>
417
+ * <p>Tenant: {tenantId}</p>
418
+ * </div>
419
+ * );
420
+ * }
225
421
  * ```
226
422
  */
227
- declare function useUserAgent(): string | undefined;
423
+ declare function createSSRHooks<T extends RenderContext = RenderContext>(): {
424
+ /**
425
+ * Hook to access the full page context with your app's type.
426
+ * Contains URL metadata, headers, and any custom properties you've added.
427
+ */
428
+ usePageContext: () => T;
429
+ /**
430
+ * Hook to access route parameters.
431
+ *
432
+ * @example
433
+ * ```tsx
434
+ * // Route: /users/:id
435
+ * const params = useParams();
436
+ * console.log(params.id); // '123'
437
+ * ```
438
+ */
439
+ useParams: () => Record<string, string>;
440
+ /**
441
+ * Hook to access query string parameters.
442
+ *
443
+ * @example
444
+ * ```tsx
445
+ * // URL: /search?q=react&sort=date
446
+ * const query = useQuery();
447
+ * console.log(query.q); // 'react'
448
+ * console.log(query.sort); // 'date'
449
+ * ```
450
+ */
451
+ useQuery: () => Record<string, string | string[]>;
452
+ /**
453
+ * Alias for usePageContext() with a more intuitive name.
454
+ * Returns the full request context with your app's type.
455
+ *
456
+ * @example
457
+ * ```tsx
458
+ * const request = useRequest();
459
+ * console.log(request.path); // '/users/123'
460
+ * console.log(request.method); // 'GET'
461
+ * console.log(request.params); // { id: '123' }
462
+ * console.log(request.query); // { search: 'foo' }
463
+ * ```
464
+ */
465
+ useRequest: () => T;
466
+ /**
467
+ * Hook to access headers configured via allowedHeaders.
468
+ * Returns all headers as a Record.
469
+ *
470
+ * Configure in module registration:
471
+ * ```typescript
472
+ * RenderModule.register({
473
+ * allowedHeaders: ['user-agent', 'x-tenant-id', 'x-api-version']
474
+ * })
475
+ * ```
476
+ *
477
+ * @example
478
+ * ```tsx
479
+ * const headers = useHeaders();
480
+ * console.log(headers['user-agent']); // 'Mozilla/5.0...'
481
+ * console.log(headers['x-tenant-id']); // 'tenant-123'
482
+ * console.log(headers['x-api-version']); // 'v2'
483
+ * ```
484
+ */
485
+ useHeaders: () => Record<string, string>;
486
+ /**
487
+ * Hook to access a specific custom header by name.
488
+ * Returns undefined if the header is not configured or not present.
489
+ *
490
+ * @param name - The header name (as configured in allowedHeaders)
491
+ *
492
+ * @example
493
+ * ```tsx
494
+ * const tenantId = useHeader('x-tenant-id');
495
+ * if (tenantId) {
496
+ * console.log(`Tenant: ${tenantId}`);
497
+ * }
498
+ * ```
499
+ */
500
+ useHeader: (name: string) => string | undefined;
501
+ /**
502
+ * Hook to access cookies configured via allowedCookies.
503
+ * Returns all allowed cookies as a Record.
504
+ *
505
+ * Configure in module registration:
506
+ * ```typescript
507
+ * RenderModule.register({
508
+ * allowedCookies: ['theme', 'locale', 'consent']
509
+ * })
510
+ * ```
511
+ *
512
+ * @example
513
+ * ```tsx
514
+ * const cookies = useCookies();
515
+ * console.log(cookies.theme); // 'dark'
516
+ * console.log(cookies.locale); // 'en-US'
517
+ * ```
518
+ */
519
+ useCookies: () => Record<string, string>;
520
+ /**
521
+ * Hook to access a specific cookie by name.
522
+ * Returns undefined if the cookie is not configured or not present.
523
+ *
524
+ * @param name - The cookie name (as configured in allowedCookies)
525
+ *
526
+ * @example
527
+ * ```tsx
528
+ * const theme = useCookie('theme');
529
+ * if (theme === 'dark') {
530
+ * console.log('Dark mode enabled');
531
+ * }
532
+ * ```
533
+ */
534
+ useCookie: (name: string) => string | undefined;
535
+ };
228
536
 
229
- export { HeadData, type PageProps, ReactRender, Render, type RenderContext, usePageContext, useParams, useQuery, useUserAgent };
537
+ export { HeadData, Layout, type LayoutComponent, type LayoutDecoratorOptions, type LayoutProps, type PageComponentWithLayout, PageContextProvider, type PageProps, Render, type RenderContext, type RenderOptions, RenderResponse, createSSRHooks };