@nestjs-ssr/react 0.1.5 → 0.1.7
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/README.md +60 -73
- package/dist/{index-Bptct1Q3.d.mts → index-Bpzo1KfR.d.mts} +4 -2
- package/dist/{index-Bptct1Q3.d.ts → index-Bpzo1KfR.d.ts} +4 -2
- package/dist/index.d.mts +31 -22
- package/dist/index.d.ts +31 -22
- package/dist/index.js +77 -7046
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +70 -7018
- package/dist/index.mjs.map +1 -1
- package/dist/render/index.d.mts +1 -1
- package/dist/render/index.d.ts +1 -1
- package/dist/render/index.js +61 -26
- package/dist/render/index.js.map +1 -1
- package/dist/render/index.mjs +62 -27
- package/dist/render/index.mjs.map +1 -1
- package/dist/templates/entry-client.tsx +37 -4
- package/dist/templates/entry-server.tsx +4 -8
- package/package.json +1 -1
- package/src/global.d.ts +2 -10
- package/src/templates/entry-client.tsx +37 -4
- package/src/templates/entry-server.tsx +4 -8
- package/dist/vite/index.d.mts +0 -11
- package/dist/vite/index.d.ts +0 -11
- package/dist/vite/index.js +0 -7022
- package/dist/vite/index.js.map +0 -1
- package/dist/vite/index.mjs +0 -6997
- package/dist/vite/index.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -12,23 +12,29 @@ If you value [Uncle Bob's Clean Architecture](https://blog.cleancoder.com/uncle-
|
|
|
12
12
|
|
|
13
13
|
**Clear Separation of Concerns:**
|
|
14
14
|
```typescript
|
|
15
|
+
// View component with typed props (Presentation Layer)
|
|
16
|
+
export interface UserProfileProps {
|
|
17
|
+
user: User;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default function UserProfile(props: PageProps<UserProfileProps>) {
|
|
21
|
+
return <div>{props.user.name}</div>; // Pure presentation
|
|
22
|
+
}
|
|
23
|
+
|
|
15
24
|
// Server logic stays in controllers (Application Layer)
|
|
25
|
+
import UserProfile from './views/user-profile';
|
|
26
|
+
|
|
16
27
|
@Controller()
|
|
17
28
|
export class UserController {
|
|
18
29
|
constructor(private userService: UserService) {} // Proper DI
|
|
19
30
|
|
|
20
31
|
@Get('/users/:id')
|
|
21
|
-
@Render(
|
|
32
|
+
@Render(UserProfile) // Type-safe! Cmd+Click to navigate
|
|
22
33
|
async getUserProfile(@Param('id') id: string) {
|
|
23
34
|
const user = await this.userService.findById(id); // Business logic
|
|
24
|
-
return { user }; //
|
|
35
|
+
return { user }; // ✅ TypeScript validates this matches UserProfileProps
|
|
25
36
|
}
|
|
26
37
|
}
|
|
27
|
-
|
|
28
|
-
// View logic stays in React components (Presentation Layer)
|
|
29
|
-
export default function UserProfile({ data }: PageProps<{ user: User }>) {
|
|
30
|
-
return <div>{data.user.name}</div>; // Pure presentation
|
|
31
|
-
}
|
|
32
38
|
```
|
|
33
39
|
|
|
34
40
|
**No Server/Client Confusion:**
|
|
@@ -73,24 +79,30 @@ export default function Page() {
|
|
|
73
79
|
|
|
74
80
|
**NestJS SSR maintains boundaries:**
|
|
75
81
|
```tsx
|
|
82
|
+
// ✅ View: Client-only, pure presentation
|
|
83
|
+
export interface ProductsProps {
|
|
84
|
+
products: Product[];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export default function Products(props: PageProps<ProductsProps>) {
|
|
88
|
+
const [selected, setSelected] = useState(null);
|
|
89
|
+
return <ProductList products={props.products} onSelect={setSelected} />;
|
|
90
|
+
}
|
|
91
|
+
|
|
76
92
|
// ✅ Controller: Server-only, testable, uses DI
|
|
93
|
+
import Products from './views/products';
|
|
94
|
+
|
|
77
95
|
@Controller()
|
|
78
96
|
export class ProductController {
|
|
79
97
|
constructor(private productService: ProductService) {}
|
|
80
98
|
|
|
81
99
|
@Get('/products')
|
|
82
100
|
@UseGuards(AuthGuard) // Proper middleware
|
|
83
|
-
@Render(
|
|
101
|
+
@Render(Products) // Type-safe component reference
|
|
84
102
|
async list() {
|
|
85
103
|
return { products: await this.productService.findAll() };
|
|
86
104
|
}
|
|
87
105
|
}
|
|
88
|
-
|
|
89
|
-
// ✅ View: Client-only, pure presentation
|
|
90
|
-
export default function Products({ data }: PageProps) {
|
|
91
|
-
const [selected, setSelected] = useState(null);
|
|
92
|
-
return <ProductList products={data.products} onSelect={setSelected} />;
|
|
93
|
-
}
|
|
94
106
|
```
|
|
95
107
|
|
|
96
108
|
### Performance as a Bonus
|
|
@@ -105,6 +117,8 @@ Following clean architecture doesn't mean sacrificing performance. In our benchm
|
|
|
105
117
|
|
|
106
118
|
## Features
|
|
107
119
|
|
|
120
|
+
✅ **Type-Safe Props** - Automatic validation of controller return types against component props
|
|
121
|
+
✅ **IDE Navigation** - Cmd+Click on components to jump to view files
|
|
108
122
|
✅ **Architectural Integrity** - Respects SOLID and Clean Architecture principles
|
|
109
123
|
✅ **Dependency Injection** - Full NestJS DI throughout your application
|
|
110
124
|
✅ **Clear Boundaries** - Server code is server, client code is client
|
|
@@ -129,10 +143,15 @@ npm install @nestjs-ssr/react react react-dom vite
|
|
|
129
143
|
// vite.config.ts
|
|
130
144
|
import { defineConfig } from 'vite';
|
|
131
145
|
import react from '@vitejs/plugin-react';
|
|
132
|
-
import {
|
|
146
|
+
import { resolve } from 'path';
|
|
133
147
|
|
|
134
148
|
export default defineConfig({
|
|
135
|
-
plugins: [react()
|
|
149
|
+
plugins: [react()],
|
|
150
|
+
resolve: {
|
|
151
|
+
alias: {
|
|
152
|
+
'@': resolve(__dirname, 'src'),
|
|
153
|
+
},
|
|
154
|
+
},
|
|
136
155
|
});
|
|
137
156
|
```
|
|
138
157
|
|
|
@@ -151,18 +170,18 @@ import { RenderModule } from '@nestjs-ssr/react';
|
|
|
151
170
|
export class AppModule {}
|
|
152
171
|
```
|
|
153
172
|
|
|
154
|
-
### 3. Create a View
|
|
173
|
+
### 3. Create a View Component
|
|
155
174
|
|
|
156
175
|
```typescript
|
|
157
176
|
// src/views/home.tsx
|
|
158
177
|
import type { PageProps } from '@nestjs-ssr/react';
|
|
159
178
|
|
|
160
|
-
interface
|
|
179
|
+
export interface HomeProps {
|
|
161
180
|
message: string;
|
|
162
181
|
}
|
|
163
182
|
|
|
164
|
-
export default function Home(
|
|
165
|
-
return <h1>{
|
|
183
|
+
export default function Home(props: PageProps<HomeProps>) {
|
|
184
|
+
return <h1>{props.message}</h1>;
|
|
166
185
|
}
|
|
167
186
|
```
|
|
168
187
|
|
|
@@ -172,61 +191,21 @@ export default function Home({ data }: PageProps<HomeData>) {
|
|
|
172
191
|
// app.controller.ts
|
|
173
192
|
import { Controller, Get } from '@nestjs/common';
|
|
174
193
|
import { Render } from '@nestjs-ssr/react';
|
|
194
|
+
import Home from './views/home';
|
|
175
195
|
|
|
176
196
|
@Controller()
|
|
177
197
|
export class AppController {
|
|
178
198
|
@Get()
|
|
179
|
-
@Render(
|
|
199
|
+
@Render(Home) // Type-safe! Cmd+Click to navigate to view
|
|
180
200
|
getHome() {
|
|
181
201
|
return { message: 'Hello from NestJS SSR!' };
|
|
182
202
|
}
|
|
183
203
|
}
|
|
184
204
|
```
|
|
185
205
|
|
|
186
|
-
|
|
206
|
+
That's it! No manual view registry or entry files needed - everything is handled automatically.
|
|
187
207
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
**entry-client.tsx**:
|
|
191
|
-
```typescript
|
|
192
|
-
import { StrictMode } from 'react';
|
|
193
|
-
import { hydrateRoot } from 'react-dom/client';
|
|
194
|
-
import { viewRegistry } from './view-registry.generated';
|
|
195
|
-
|
|
196
|
-
const viewPath = window.__COMPONENT_PATH__;
|
|
197
|
-
const initialProps = window.__INITIAL_STATE__ || {};
|
|
198
|
-
const renderContext = window.__CONTEXT__ || {};
|
|
199
|
-
|
|
200
|
-
const ViewComponent = viewRegistry[viewPath];
|
|
201
|
-
|
|
202
|
-
if (!ViewComponent) {
|
|
203
|
-
throw new Error(`View "${viewPath}" not found in registry`);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
hydrateRoot(
|
|
207
|
-
document.getElementById('root')!,
|
|
208
|
-
<StrictMode>
|
|
209
|
-
<ViewComponent data={initialProps} context={renderContext} />
|
|
210
|
-
</StrictMode>
|
|
211
|
-
);
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
**entry-server.tsx**:
|
|
215
|
-
```typescript
|
|
216
|
-
import { viewRegistry } from './view-registry.generated';
|
|
217
|
-
|
|
218
|
-
export function render(viewPath: string, props: any, context: any) {
|
|
219
|
-
const ViewComponent = viewRegistry[viewPath];
|
|
220
|
-
|
|
221
|
-
if (!ViewComponent) {
|
|
222
|
-
throw new Error(`View "${viewPath}" not found in registry`);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
return <ViewComponent data={props} context={context} />;
|
|
226
|
-
}
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
### 6. Run
|
|
208
|
+
### 5. Run
|
|
230
209
|
|
|
231
210
|
```bash
|
|
232
211
|
npm run dev
|
|
@@ -238,38 +217,46 @@ Visit [http://localhost:3000](http://localhost:3000) 🎉
|
|
|
238
217
|
|
|
239
218
|
### The `@Render` Decorator
|
|
240
219
|
|
|
241
|
-
The decorator
|
|
220
|
+
The decorator takes a React component and automatically validates that your controller returns the correct props:
|
|
242
221
|
|
|
243
222
|
```typescript
|
|
223
|
+
import UserProfile from './views/user-profile';
|
|
224
|
+
|
|
244
225
|
@Get('/users/:id')
|
|
245
|
-
@Render(
|
|
226
|
+
@Render(UserProfile) // Type-safe! Cmd+Click to navigate
|
|
246
227
|
async getUser(@Param('id') id: string) {
|
|
247
228
|
const user = await this.userService.findOne(id);
|
|
248
|
-
return { user }; //
|
|
229
|
+
return { user }; // ✅ TypeScript validates this matches component props
|
|
249
230
|
}
|
|
250
231
|
```
|
|
251
232
|
|
|
252
233
|
### Type-Safe Props
|
|
253
234
|
|
|
254
|
-
Components receive props with full TypeScript support:
|
|
235
|
+
Components receive props with full TypeScript support and validation:
|
|
255
236
|
|
|
256
237
|
```typescript
|
|
257
|
-
import type { PageProps
|
|
238
|
+
import type { PageProps } from '@nestjs-ssr/react';
|
|
258
239
|
|
|
259
|
-
interface
|
|
240
|
+
export interface UserProfileProps {
|
|
260
241
|
user: User;
|
|
261
242
|
}
|
|
262
243
|
|
|
263
|
-
export default function UserProfile(
|
|
244
|
+
export default function UserProfile(props: PageProps<UserProfileProps>) {
|
|
264
245
|
return (
|
|
265
246
|
<div>
|
|
266
|
-
<h1>{
|
|
267
|
-
<p>Requested from: {context.path}</p>
|
|
247
|
+
<h1>{props.user.name}</h1>
|
|
248
|
+
<p>Requested from: {props.context.path}</p>
|
|
268
249
|
</div>
|
|
269
250
|
);
|
|
270
251
|
}
|
|
271
252
|
```
|
|
272
253
|
|
|
254
|
+
**Benefits:**
|
|
255
|
+
- ✅ Build-time validation - wrong props = compilation error
|
|
256
|
+
- ✅ Cmd+Click navigation from controller to view file
|
|
257
|
+
- ✅ No manual type annotations needed
|
|
258
|
+
- ✅ Refactoring-friendly - rename props with confidence
|
|
259
|
+
|
|
273
260
|
### Request Context
|
|
274
261
|
|
|
275
262
|
Every component receives the request context:
|
|
@@ -295,7 +295,7 @@ declare class TemplateParserService {
|
|
|
295
295
|
* This library handles all edge cases including escaping dangerous characters,
|
|
296
296
|
* functions, dates, regexes, and prevents prototype pollution.
|
|
297
297
|
*/
|
|
298
|
-
buildInlineScripts(data: any, context: any,
|
|
298
|
+
buildInlineScripts(data: any, context: any, componentName: string): string;
|
|
299
299
|
/**
|
|
300
300
|
* Get client script tag for hydration
|
|
301
301
|
*
|
|
@@ -367,12 +367,14 @@ declare class RenderService {
|
|
|
367
367
|
private serverManifest;
|
|
368
368
|
private isDevelopment;
|
|
369
369
|
private ssrMode;
|
|
370
|
+
private readonly entryServerPath;
|
|
371
|
+
private readonly entryClientPath;
|
|
370
372
|
constructor(templateParser: TemplateParserService, streamingErrorHandler: StreamingErrorHandler, ssrMode?: SSRMode, defaultHead?: HeadData | undefined);
|
|
371
373
|
setViteServer(vite: ViteDevServer): void;
|
|
372
374
|
/**
|
|
373
375
|
* Main render method that routes to string or stream mode
|
|
374
376
|
*/
|
|
375
|
-
render(
|
|
377
|
+
render(viewComponent: any, data?: any, res?: Response, head?: HeadData): Promise<string | void>;
|
|
376
378
|
/**
|
|
377
379
|
* Merge default head with page-specific head
|
|
378
380
|
* Page-specific head values override defaults
|
|
@@ -295,7 +295,7 @@ declare class TemplateParserService {
|
|
|
295
295
|
* This library handles all edge cases including escaping dangerous characters,
|
|
296
296
|
* functions, dates, regexes, and prevents prototype pollution.
|
|
297
297
|
*/
|
|
298
|
-
buildInlineScripts(data: any, context: any,
|
|
298
|
+
buildInlineScripts(data: any, context: any, componentName: string): string;
|
|
299
299
|
/**
|
|
300
300
|
* Get client script tag for hydration
|
|
301
301
|
*
|
|
@@ -367,12 +367,14 @@ declare class RenderService {
|
|
|
367
367
|
private serverManifest;
|
|
368
368
|
private isDevelopment;
|
|
369
369
|
private ssrMode;
|
|
370
|
+
private readonly entryServerPath;
|
|
371
|
+
private readonly entryClientPath;
|
|
370
372
|
constructor(templateParser: TemplateParserService, streamingErrorHandler: StreamingErrorHandler, ssrMode?: SSRMode, defaultHead?: HeadData | undefined);
|
|
371
373
|
setViteServer(vite: ViteDevServer): void;
|
|
372
374
|
/**
|
|
373
375
|
* Main render method that routes to string or stream mode
|
|
374
376
|
*/
|
|
375
|
-
render(
|
|
377
|
+
render(viewComponent: any, data?: any, res?: Response, head?: HeadData): Promise<string | void>;
|
|
376
378
|
/**
|
|
377
379
|
* Merge default head with page-specific head
|
|
378
380
|
* Page-specific head values override defaults
|
package/dist/index.d.mts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { H as HeadData } from './index-
|
|
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-
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
import 'react';
|
|
1
|
+
import { H as HeadData } from './index-Bpzo1KfR.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-Bpzo1KfR.mjs';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import '@nestjs/common';
|
|
6
5
|
import 'vite';
|
|
7
6
|
import 'express';
|
|
8
7
|
import '@nestjs/core';
|
|
@@ -122,39 +121,49 @@ type PageProps<TProps = {}> = TProps & {
|
|
|
122
121
|
};
|
|
123
122
|
|
|
124
123
|
/**
|
|
125
|
-
*
|
|
126
|
-
*
|
|
124
|
+
* Extract the data type T from PageProps<T>.
|
|
125
|
+
* PageProps<T> = T & { head?, context }, so we extract T by removing those keys.
|
|
127
126
|
*/
|
|
128
|
-
|
|
129
|
-
|
|
127
|
+
type ExtractPagePropsData<P> = P extends PageProps<infer T> ? T : P extends {
|
|
128
|
+
head?: any;
|
|
129
|
+
context: any;
|
|
130
|
+
} ? Omit<P, 'head' | 'context'> : P;
|
|
130
131
|
/**
|
|
131
|
-
*
|
|
132
|
-
* This is populated via module augmentation from the generated view registry.
|
|
132
|
+
* Extract controller return type from a React component's props.
|
|
133
133
|
*/
|
|
134
|
-
type
|
|
134
|
+
type ExtractComponentData<T> = T extends React.ComponentType<infer P> ? ExtractPagePropsData<P> : never;
|
|
135
135
|
/**
|
|
136
136
|
* Decorator to render a React component as the response.
|
|
137
|
-
* Provides IDE autocomplete and type checking for view paths.
|
|
138
137
|
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
138
|
+
* Import the component directly for Cmd+Click navigation in your IDE.
|
|
139
|
+
* TypeScript automatically validates your controller returns the correct props.
|
|
141
140
|
*
|
|
142
|
-
* @param
|
|
141
|
+
* @param component - The React component to render
|
|
143
142
|
*
|
|
144
143
|
* @example
|
|
145
144
|
* ```typescript
|
|
145
|
+
* // Your view component (views/home.tsx)
|
|
146
|
+
* export interface HomeProps {
|
|
147
|
+
* message: string;
|
|
148
|
+
* }
|
|
149
|
+
* export default function Home(props: PageProps<HomeProps>) { ... }
|
|
150
|
+
*
|
|
151
|
+
* // Your controller - Cmd+Click on Home navigates to the view file!
|
|
152
|
+
* import Home from './views/home';
|
|
153
|
+
*
|
|
146
154
|
* @Get()
|
|
147
|
-
* @Render(
|
|
148
|
-
*
|
|
149
|
-
* return {
|
|
155
|
+
* @Render(Home) // Type-safe! Wrong props = build error
|
|
156
|
+
* getHome() {
|
|
157
|
+
* return { message: 'Hello' }; // ✅ Correct
|
|
158
|
+
* // return { wrong: 'prop' }; // ❌ Type error!
|
|
150
159
|
* }
|
|
151
160
|
* ```
|
|
152
161
|
*/
|
|
153
|
-
declare
|
|
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;
|
|
154
163
|
/**
|
|
155
164
|
* @deprecated Use `Render` instead. This alias will be removed in a future version.
|
|
156
165
|
*/
|
|
157
|
-
declare const ReactRender:
|
|
166
|
+
declare const ReactRender: typeof Render;
|
|
158
167
|
|
|
159
168
|
/**
|
|
160
169
|
* Hook to access the full page context.
|
|
@@ -217,4 +226,4 @@ declare function useQuery(): Record<string, string | string[]>;
|
|
|
217
226
|
*/
|
|
218
227
|
declare function useUserAgent(): string | undefined;
|
|
219
228
|
|
|
220
|
-
export { HeadData, type PageProps, ReactRender, Render, type RenderContext,
|
|
229
|
+
export { HeadData, type PageProps, ReactRender, Render, type RenderContext, usePageContext, useParams, useQuery, useUserAgent };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { H as HeadData } from './index-
|
|
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-
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
import 'react';
|
|
1
|
+
import { H as HeadData } from './index-Bpzo1KfR.js';
|
|
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-Bpzo1KfR.js';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import '@nestjs/common';
|
|
6
5
|
import 'vite';
|
|
7
6
|
import 'express';
|
|
8
7
|
import '@nestjs/core';
|
|
@@ -122,39 +121,49 @@ type PageProps<TProps = {}> = TProps & {
|
|
|
122
121
|
};
|
|
123
122
|
|
|
124
123
|
/**
|
|
125
|
-
*
|
|
126
|
-
*
|
|
124
|
+
* Extract the data type T from PageProps<T>.
|
|
125
|
+
* PageProps<T> = T & { head?, context }, so we extract T by removing those keys.
|
|
127
126
|
*/
|
|
128
|
-
|
|
129
|
-
|
|
127
|
+
type ExtractPagePropsData<P> = P extends PageProps<infer T> ? T : P extends {
|
|
128
|
+
head?: any;
|
|
129
|
+
context: any;
|
|
130
|
+
} ? Omit<P, 'head' | 'context'> : P;
|
|
130
131
|
/**
|
|
131
|
-
*
|
|
132
|
-
* This is populated via module augmentation from the generated view registry.
|
|
132
|
+
* Extract controller return type from a React component's props.
|
|
133
133
|
*/
|
|
134
|
-
type
|
|
134
|
+
type ExtractComponentData<T> = T extends React.ComponentType<infer P> ? ExtractPagePropsData<P> : never;
|
|
135
135
|
/**
|
|
136
136
|
* Decorator to render a React component as the response.
|
|
137
|
-
* Provides IDE autocomplete and type checking for view paths.
|
|
138
137
|
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
138
|
+
* Import the component directly for Cmd+Click navigation in your IDE.
|
|
139
|
+
* TypeScript automatically validates your controller returns the correct props.
|
|
141
140
|
*
|
|
142
|
-
* @param
|
|
141
|
+
* @param component - The React component to render
|
|
143
142
|
*
|
|
144
143
|
* @example
|
|
145
144
|
* ```typescript
|
|
145
|
+
* // Your view component (views/home.tsx)
|
|
146
|
+
* export interface HomeProps {
|
|
147
|
+
* message: string;
|
|
148
|
+
* }
|
|
149
|
+
* export default function Home(props: PageProps<HomeProps>) { ... }
|
|
150
|
+
*
|
|
151
|
+
* // Your controller - Cmd+Click on Home navigates to the view file!
|
|
152
|
+
* import Home from './views/home';
|
|
153
|
+
*
|
|
146
154
|
* @Get()
|
|
147
|
-
* @Render(
|
|
148
|
-
*
|
|
149
|
-
* return {
|
|
155
|
+
* @Render(Home) // Type-safe! Wrong props = build error
|
|
156
|
+
* getHome() {
|
|
157
|
+
* return { message: 'Hello' }; // ✅ Correct
|
|
158
|
+
* // return { wrong: 'prop' }; // ❌ Type error!
|
|
150
159
|
* }
|
|
151
160
|
* ```
|
|
152
161
|
*/
|
|
153
|
-
declare
|
|
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;
|
|
154
163
|
/**
|
|
155
164
|
* @deprecated Use `Render` instead. This alias will be removed in a future version.
|
|
156
165
|
*/
|
|
157
|
-
declare const ReactRender:
|
|
166
|
+
declare const ReactRender: typeof Render;
|
|
158
167
|
|
|
159
168
|
/**
|
|
160
169
|
* Hook to access the full page context.
|
|
@@ -217,4 +226,4 @@ declare function useQuery(): Record<string, string | string[]>;
|
|
|
217
226
|
*/
|
|
218
227
|
declare function useUserAgent(): string | undefined;
|
|
219
228
|
|
|
220
|
-
export { HeadData, type PageProps, ReactRender, Render, type RenderContext,
|
|
229
|
+
export { HeadData, type PageProps, ReactRender, Render, type RenderContext, usePageContext, useParams, useQuery, useUserAgent };
|