@nestjs-ssr/react 0.3.4 → 0.3.5
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/client.d.mts +2 -2
- package/dist/client.d.ts +2 -2
- package/dist/client.js +50 -8
- package/dist/client.mjs +50 -8
- package/dist/{index-DdE--mA2.d.mts → index-CiYcz-1T.d.mts} +89 -4
- package/dist/{index-BzOLOiIZ.d.ts → index-Dq2qZSge.d.ts} +89 -4
- package/dist/index.d.mts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +54 -4
- package/dist/index.mjs +55 -5
- package/dist/render/index.d.mts +3 -3
- package/dist/render/index.d.ts +3 -3
- package/dist/render/index.js +35 -4
- package/dist/render/index.mjs +36 -5
- package/dist/{render-response.interface-CxbuKGnV.d.mts → render-response.interface-ClWJXKL4.d.mts} +19 -10
- package/dist/{render-response.interface-CxbuKGnV.d.ts → render-response.interface-ClWJXKL4.d.ts} +19 -10
- package/dist/templates/entry-client.tsx +23 -4
- package/dist/templates/entry-server.tsx +25 -8
- package/dist/{use-page-context-CGT9woWe.d.mts → use-page-context-CVC9DHcL.d.mts} +2 -1
- package/dist/{use-page-context-05ODF4zW.d.ts → use-page-context-DChgHhL9.d.ts} +2 -1
- package/package.json +1 -1
- package/src/templates/entry-client.tsx +23 -4
- package/src/templates/entry-server.tsx +25 -8
package/dist/client.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export { a as PageContextProvider, P as PageProps, c as createSSRHooks, j as updatePageContext, i as useCookie, h as useCookies, g as useHeader, f as useHeaders, u as usePageContext, b as useParams, d as useQuery, e as useRequest } from './use-page-context-
|
|
1
|
+
export { a as PageContextProvider, P as PageProps, c as createSSRHooks, j as updatePageContext, i as useCookie, h as useCookies, g as useHeader, f as useHeaders, u as usePageContext, b as useParams, d as useQuery, e as useRequest } from './use-page-context-CVC9DHcL.mjs';
|
|
2
2
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
3
|
import React from 'react';
|
|
4
|
-
export { R as RenderContext } from './render-response.interface-
|
|
4
|
+
export { R as RenderContext } from './render-response.interface-ClWJXKL4.mjs';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Provider component for navigation state.
|
package/dist/client.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export { a as PageContextProvider, P as PageProps, c as createSSRHooks, j as updatePageContext, i as useCookie, h as useCookies, g as useHeader, f as useHeaders, u as usePageContext, b as useParams, d as useQuery, e as useRequest } from './use-page-context-
|
|
1
|
+
export { a as PageContextProvider, P as PageProps, c as createSSRHooks, j as updatePageContext, i as useCookie, h as useCookies, g as useHeader, f as useHeaders, u as usePageContext, b as useParams, d as useQuery, e as useRequest } from './use-page-context-DChgHhL9.js';
|
|
2
2
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
3
|
import React from 'react';
|
|
4
|
-
export { R as RenderContext } from './render-response.interface-
|
|
4
|
+
export { R as RenderContext } from './render-response.interface-ClWJXKL4.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Provider component for navigation state.
|
package/dist/client.js
CHANGED
|
@@ -28,11 +28,31 @@ function updatePageContext(context) {
|
|
|
28
28
|
setPageContextState?.(context);
|
|
29
29
|
}
|
|
30
30
|
__name(updatePageContext, "updatePageContext");
|
|
31
|
+
var segmentSetters = /* @__PURE__ */ new Set();
|
|
32
|
+
function registerSegmentSetter(setter) {
|
|
33
|
+
segmentSetters.add(setter);
|
|
34
|
+
}
|
|
35
|
+
__name(registerSegmentSetter, "registerSegmentSetter");
|
|
36
|
+
function unregisterSegmentSetter(setter) {
|
|
37
|
+
segmentSetters.delete(setter);
|
|
38
|
+
}
|
|
39
|
+
__name(unregisterSegmentSetter, "unregisterSegmentSetter");
|
|
40
|
+
function broadcastToSegments(context) {
|
|
41
|
+
segmentSetters.forEach((setter) => setter(context));
|
|
42
|
+
}
|
|
43
|
+
__name(broadcastToSegments, "broadcastToSegments");
|
|
31
44
|
function PageContextProvider({ context: initialContext, children, isSegment = false }) {
|
|
32
45
|
const [context, setContext] = React2.useState(initialContext);
|
|
33
46
|
React2.useEffect(() => {
|
|
34
47
|
if (!isSegment) {
|
|
35
|
-
registerPageContextState(
|
|
48
|
+
registerPageContextState((newContext) => {
|
|
49
|
+
setContext(newContext);
|
|
50
|
+
broadcastToSegments(newContext);
|
|
51
|
+
});
|
|
52
|
+
return void 0;
|
|
53
|
+
} else {
|
|
54
|
+
registerSegmentSetter(setContext);
|
|
55
|
+
return () => unregisterSegmentSetter(setContext);
|
|
36
56
|
}
|
|
37
57
|
}, [
|
|
38
58
|
isSegment
|
|
@@ -239,7 +259,7 @@ var useHeader = defaultHooks.useHeader;
|
|
|
239
259
|
var useCookies = defaultHooks.useCookies;
|
|
240
260
|
var useCookie = defaultHooks.useCookie;
|
|
241
261
|
var rootRegistry = /* @__PURE__ */ new WeakMap();
|
|
242
|
-
function hydrateSegment(outlet, componentName, props) {
|
|
262
|
+
function hydrateSegment(outlet, componentName, props, layouts) {
|
|
243
263
|
const modules = window.__MODULES__;
|
|
244
264
|
if (!modules) {
|
|
245
265
|
console.warn("[navigation] Module registry not available for segment hydration. Make sure entry-client.tsx exports window.__MODULES__.");
|
|
@@ -254,10 +274,11 @@ function hydrateSegment(outlet, componentName, props) {
|
|
|
254
274
|
return;
|
|
255
275
|
}
|
|
256
276
|
const context = window.__CONTEXT__ || {};
|
|
277
|
+
const composedElement = composeWithLayouts(ViewComponent, props, layouts || [], context, modules);
|
|
257
278
|
const element = /* @__PURE__ */ React2__default.default.createElement(PageContextProvider, {
|
|
258
279
|
context,
|
|
259
280
|
isSegment: true
|
|
260
|
-
},
|
|
281
|
+
}, composedElement);
|
|
261
282
|
let wrapper = outlet.querySelector("[data-segment-root]");
|
|
262
283
|
if (wrapper) {
|
|
263
284
|
const existingRoot = rootRegistry.get(wrapper);
|
|
@@ -275,6 +296,27 @@ function hydrateSegment(outlet, componentName, props) {
|
|
|
275
296
|
rootRegistry.set(wrapper, root);
|
|
276
297
|
}
|
|
277
298
|
__name(hydrateSegment, "hydrateSegment");
|
|
299
|
+
function composeWithLayouts(ViewComponent, props, layouts, context, modules) {
|
|
300
|
+
let result = /* @__PURE__ */ React2__default.default.createElement(ViewComponent, props);
|
|
301
|
+
for (let i = layouts.length - 1; i >= 0; i--) {
|
|
302
|
+
const { name: layoutName, props: layoutProps } = layouts[i];
|
|
303
|
+
const Layout = resolveComponent(layoutName, modules);
|
|
304
|
+
if (!Layout) {
|
|
305
|
+
console.warn(`[navigation] Layout "${layoutName}" not found for hydration`);
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
result = /* @__PURE__ */ React2__default.default.createElement("div", {
|
|
309
|
+
"data-layout": layoutName
|
|
310
|
+
}, /* @__PURE__ */ React2__default.default.createElement(Layout, {
|
|
311
|
+
context,
|
|
312
|
+
layoutProps
|
|
313
|
+
}, /* @__PURE__ */ React2__default.default.createElement("div", {
|
|
314
|
+
"data-outlet": layoutName
|
|
315
|
+
}, result)));
|
|
316
|
+
}
|
|
317
|
+
return result;
|
|
318
|
+
}
|
|
319
|
+
__name(composeWithLayouts, "composeWithLayouts");
|
|
278
320
|
function resolveComponent(name, modules) {
|
|
279
321
|
const componentMap = Object.entries(modules).filter(([path, module]) => {
|
|
280
322
|
const filename = path.split("/").pop();
|
|
@@ -342,8 +384,12 @@ async function navigate(url, options = {}) {
|
|
|
342
384
|
return;
|
|
343
385
|
}
|
|
344
386
|
const outlet = await swapContent(response.html, response.swapTarget);
|
|
387
|
+
if (response.context) {
|
|
388
|
+
updatePageContext(response.context);
|
|
389
|
+
window.__CONTEXT__ = response.context;
|
|
390
|
+
}
|
|
345
391
|
if (outlet) {
|
|
346
|
-
hydrateSegment(outlet, response.componentName, response.props);
|
|
392
|
+
hydrateSegment(outlet, response.componentName, response.props, response.layouts);
|
|
347
393
|
}
|
|
348
394
|
if (replace) {
|
|
349
395
|
history.replaceState({
|
|
@@ -354,10 +400,6 @@ async function navigate(url, options = {}) {
|
|
|
354
400
|
url
|
|
355
401
|
}, "", url);
|
|
356
402
|
}
|
|
357
|
-
if (response.context) {
|
|
358
|
-
updatePageContext(response.context);
|
|
359
|
-
window.__CONTEXT__ = response.context;
|
|
360
|
-
}
|
|
361
403
|
if (response.head) {
|
|
362
404
|
updateHead(response.head);
|
|
363
405
|
}
|
package/dist/client.mjs
CHANGED
|
@@ -22,11 +22,31 @@ function updatePageContext(context) {
|
|
|
22
22
|
setPageContextState?.(context);
|
|
23
23
|
}
|
|
24
24
|
__name(updatePageContext, "updatePageContext");
|
|
25
|
+
var segmentSetters = /* @__PURE__ */ new Set();
|
|
26
|
+
function registerSegmentSetter(setter) {
|
|
27
|
+
segmentSetters.add(setter);
|
|
28
|
+
}
|
|
29
|
+
__name(registerSegmentSetter, "registerSegmentSetter");
|
|
30
|
+
function unregisterSegmentSetter(setter) {
|
|
31
|
+
segmentSetters.delete(setter);
|
|
32
|
+
}
|
|
33
|
+
__name(unregisterSegmentSetter, "unregisterSegmentSetter");
|
|
34
|
+
function broadcastToSegments(context) {
|
|
35
|
+
segmentSetters.forEach((setter) => setter(context));
|
|
36
|
+
}
|
|
37
|
+
__name(broadcastToSegments, "broadcastToSegments");
|
|
25
38
|
function PageContextProvider({ context: initialContext, children, isSegment = false }) {
|
|
26
39
|
const [context, setContext] = useState(initialContext);
|
|
27
40
|
useEffect(() => {
|
|
28
41
|
if (!isSegment) {
|
|
29
|
-
registerPageContextState(
|
|
42
|
+
registerPageContextState((newContext) => {
|
|
43
|
+
setContext(newContext);
|
|
44
|
+
broadcastToSegments(newContext);
|
|
45
|
+
});
|
|
46
|
+
return void 0;
|
|
47
|
+
} else {
|
|
48
|
+
registerSegmentSetter(setContext);
|
|
49
|
+
return () => unregisterSegmentSetter(setContext);
|
|
30
50
|
}
|
|
31
51
|
}, [
|
|
32
52
|
isSegment
|
|
@@ -233,7 +253,7 @@ var useHeader = defaultHooks.useHeader;
|
|
|
233
253
|
var useCookies = defaultHooks.useCookies;
|
|
234
254
|
var useCookie = defaultHooks.useCookie;
|
|
235
255
|
var rootRegistry = /* @__PURE__ */ new WeakMap();
|
|
236
|
-
function hydrateSegment(outlet, componentName, props) {
|
|
256
|
+
function hydrateSegment(outlet, componentName, props, layouts) {
|
|
237
257
|
const modules = window.__MODULES__;
|
|
238
258
|
if (!modules) {
|
|
239
259
|
console.warn("[navigation] Module registry not available for segment hydration. Make sure entry-client.tsx exports window.__MODULES__.");
|
|
@@ -248,10 +268,11 @@ function hydrateSegment(outlet, componentName, props) {
|
|
|
248
268
|
return;
|
|
249
269
|
}
|
|
250
270
|
const context = window.__CONTEXT__ || {};
|
|
271
|
+
const composedElement = composeWithLayouts(ViewComponent, props, layouts || [], context, modules);
|
|
251
272
|
const element = /* @__PURE__ */ React2.createElement(PageContextProvider, {
|
|
252
273
|
context,
|
|
253
274
|
isSegment: true
|
|
254
|
-
},
|
|
275
|
+
}, composedElement);
|
|
255
276
|
let wrapper = outlet.querySelector("[data-segment-root]");
|
|
256
277
|
if (wrapper) {
|
|
257
278
|
const existingRoot = rootRegistry.get(wrapper);
|
|
@@ -269,6 +290,27 @@ function hydrateSegment(outlet, componentName, props) {
|
|
|
269
290
|
rootRegistry.set(wrapper, root);
|
|
270
291
|
}
|
|
271
292
|
__name(hydrateSegment, "hydrateSegment");
|
|
293
|
+
function composeWithLayouts(ViewComponent, props, layouts, context, modules) {
|
|
294
|
+
let result = /* @__PURE__ */ React2.createElement(ViewComponent, props);
|
|
295
|
+
for (let i = layouts.length - 1; i >= 0; i--) {
|
|
296
|
+
const { name: layoutName, props: layoutProps } = layouts[i];
|
|
297
|
+
const Layout = resolveComponent(layoutName, modules);
|
|
298
|
+
if (!Layout) {
|
|
299
|
+
console.warn(`[navigation] Layout "${layoutName}" not found for hydration`);
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
result = /* @__PURE__ */ React2.createElement("div", {
|
|
303
|
+
"data-layout": layoutName
|
|
304
|
+
}, /* @__PURE__ */ React2.createElement(Layout, {
|
|
305
|
+
context,
|
|
306
|
+
layoutProps
|
|
307
|
+
}, /* @__PURE__ */ React2.createElement("div", {
|
|
308
|
+
"data-outlet": layoutName
|
|
309
|
+
}, result)));
|
|
310
|
+
}
|
|
311
|
+
return result;
|
|
312
|
+
}
|
|
313
|
+
__name(composeWithLayouts, "composeWithLayouts");
|
|
272
314
|
function resolveComponent(name, modules) {
|
|
273
315
|
const componentMap = Object.entries(modules).filter(([path, module]) => {
|
|
274
316
|
const filename = path.split("/").pop();
|
|
@@ -336,8 +378,12 @@ async function navigate(url, options = {}) {
|
|
|
336
378
|
return;
|
|
337
379
|
}
|
|
338
380
|
const outlet = await swapContent(response.html, response.swapTarget);
|
|
381
|
+
if (response.context) {
|
|
382
|
+
updatePageContext(response.context);
|
|
383
|
+
window.__CONTEXT__ = response.context;
|
|
384
|
+
}
|
|
339
385
|
if (outlet) {
|
|
340
|
-
hydrateSegment(outlet, response.componentName, response.props);
|
|
386
|
+
hydrateSegment(outlet, response.componentName, response.props, response.layouts);
|
|
341
387
|
}
|
|
342
388
|
if (replace) {
|
|
343
389
|
history.replaceState({
|
|
@@ -348,10 +394,6 @@ async function navigate(url, options = {}) {
|
|
|
348
394
|
url
|
|
349
395
|
}, "", url);
|
|
350
396
|
}
|
|
351
|
-
if (response.context) {
|
|
352
|
-
updatePageContext(response.context);
|
|
353
|
-
window.__CONTEXT__ = response.context;
|
|
354
|
-
}
|
|
355
397
|
if (response.head) {
|
|
356
398
|
updateHead(response.head);
|
|
357
399
|
}
|
|
@@ -1,12 +1,45 @@
|
|
|
1
1
|
import { DynamicModule, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
|
|
2
2
|
import { ComponentType } from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { Request, Response } from 'express';
|
|
4
|
+
import { H as HeadData, R as RenderContext } from './render-response.interface-ClWJXKL4.mjs';
|
|
4
5
|
import { ViteDevServer } from 'vite';
|
|
5
|
-
import { Response } from 'express';
|
|
6
6
|
import { Reflector } from '@nestjs/core';
|
|
7
7
|
import { Observable } from 'rxjs';
|
|
8
8
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Custom context properties that can be added via context factory.
|
|
12
|
+
* Allows any properties to be merged into RenderContext.
|
|
13
|
+
*/
|
|
14
|
+
type CustomContextProperties = Record<string, unknown>;
|
|
15
|
+
/**
|
|
16
|
+
* Context factory function signature
|
|
17
|
+
* Called for each request to build custom context properties
|
|
18
|
+
*
|
|
19
|
+
* @param params - Object containing the Express request
|
|
20
|
+
* @returns Custom context properties to merge into RenderContext (sync or async)
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* // Simple: pass user from Passport
|
|
25
|
+
* context: ({ req }) => ({ user: req.user })
|
|
26
|
+
*
|
|
27
|
+
* // With CLS
|
|
28
|
+
* context: ({ req }) => ({
|
|
29
|
+
* user: req.user,
|
|
30
|
+
* tenant: cls.get('tenant'),
|
|
31
|
+
* })
|
|
32
|
+
*
|
|
33
|
+
* // Async with services
|
|
34
|
+
* context: async ({ req }) => ({
|
|
35
|
+
* user: req.user,
|
|
36
|
+
* permissions: await permissionService.getForUser(req.user),
|
|
37
|
+
* })
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
type ContextFactory = (params: {
|
|
41
|
+
req: Request;
|
|
42
|
+
}) => CustomContextProperties | Promise<CustomContextProperties>;
|
|
10
43
|
/**
|
|
11
44
|
* SSR rendering mode configuration
|
|
12
45
|
*/
|
|
@@ -173,6 +206,52 @@ interface RenderConfig {
|
|
|
173
206
|
* ```
|
|
174
207
|
*/
|
|
175
208
|
allowedCookies?: string[];
|
|
209
|
+
/**
|
|
210
|
+
* Context factory function to enrich RenderContext with custom properties
|
|
211
|
+
*
|
|
212
|
+
* This is called for each request and the result is merged into the base
|
|
213
|
+
* RenderContext (url, path, query, params, method, headers, cookies).
|
|
214
|
+
*
|
|
215
|
+
* Use this to add user data, feature flags, tenant info, or any other
|
|
216
|
+
* request-scoped data that should be available in React components via usePageContext().
|
|
217
|
+
*
|
|
218
|
+
* Similar to @nestjs/graphql context option - you choose how to get the data:
|
|
219
|
+
* - From request object (req.user from Passport)
|
|
220
|
+
* - From CLS (nestjs-cls)
|
|
221
|
+
* - From request-scoped services
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* ```typescript
|
|
225
|
+
* // Simple: pass user from Passport JWT strategy
|
|
226
|
+
* RenderModule.forRoot({
|
|
227
|
+
* context: ({ req }) => ({
|
|
228
|
+
* user: req.user,
|
|
229
|
+
* }),
|
|
230
|
+
* })
|
|
231
|
+
*
|
|
232
|
+
* // With nestjs-cls
|
|
233
|
+
* RenderModule.forRoot({
|
|
234
|
+
* context: ({ req }) => ({
|
|
235
|
+
* user: req.user,
|
|
236
|
+
* tenant: cls.get('tenant'),
|
|
237
|
+
* featureFlags: cls.get('featureFlags'),
|
|
238
|
+
* }),
|
|
239
|
+
* })
|
|
240
|
+
*
|
|
241
|
+
* // Async with services (use forRootAsync)
|
|
242
|
+
* RenderModule.forRootAsync({
|
|
243
|
+
* imports: [PermissionModule],
|
|
244
|
+
* inject: [PermissionService],
|
|
245
|
+
* useFactory: (permissionService: PermissionService) => ({
|
|
246
|
+
* context: async ({ req }) => ({
|
|
247
|
+
* user: req.user,
|
|
248
|
+
* permissions: await permissionService.getForUser(req.user),
|
|
249
|
+
* }),
|
|
250
|
+
* }),
|
|
251
|
+
* })
|
|
252
|
+
* ```
|
|
253
|
+
*/
|
|
254
|
+
context?: ContextFactory;
|
|
176
255
|
}
|
|
177
256
|
/**
|
|
178
257
|
* Template parts for streaming SSR
|
|
@@ -206,6 +285,11 @@ interface SegmentResponse {
|
|
|
206
285
|
componentName: string;
|
|
207
286
|
/** Page context for updating hooks (path, params, query, etc.) */
|
|
208
287
|
context?: RenderContext;
|
|
288
|
+
/** Layouts below the swap target that need to be composed on client */
|
|
289
|
+
layouts?: Array<{
|
|
290
|
+
name: string;
|
|
291
|
+
props?: any;
|
|
292
|
+
}>;
|
|
209
293
|
}
|
|
210
294
|
|
|
211
295
|
declare class RenderModule {
|
|
@@ -555,7 +639,8 @@ declare class RenderInterceptor implements NestInterceptor {
|
|
|
555
639
|
private renderService;
|
|
556
640
|
private allowedHeaders?;
|
|
557
641
|
private allowedCookies?;
|
|
558
|
-
|
|
642
|
+
private contextFactory?;
|
|
643
|
+
constructor(reflector: Reflector, renderService: RenderService, allowedHeaders?: string[] | undefined, allowedCookies?: string[] | undefined, contextFactory?: ContextFactory | undefined);
|
|
559
644
|
/**
|
|
560
645
|
* Resolve the layout hierarchy for a given route
|
|
561
646
|
* Hierarchy: Root Layout → Controller Layout → Method Layout → Page
|
|
@@ -606,4 +691,4 @@ declare function ErrorPageDevelopment({ error, viewPath, phase, }: ErrorPageDeve
|
|
|
606
691
|
*/
|
|
607
692
|
declare function ErrorPageProduction(): react_jsx_runtime.JSX.Element;
|
|
608
693
|
|
|
609
|
-
export { ErrorPageDevelopment as E, RenderModule as R, StreamingErrorHandler as S, TemplateParserService as T, RenderService as a, RenderInterceptor as b, type RenderConfig as c, type SSRMode as d, ErrorPageProduction as e };
|
|
694
|
+
export { type ContextFactory as C, ErrorPageDevelopment as E, RenderModule as R, StreamingErrorHandler as S, TemplateParserService as T, RenderService as a, RenderInterceptor as b, type RenderConfig as c, type SSRMode as d, ErrorPageProduction as e };
|
|
@@ -1,12 +1,45 @@
|
|
|
1
1
|
import { DynamicModule, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
|
|
2
2
|
import { ComponentType } from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { Request, Response } from 'express';
|
|
4
|
+
import { H as HeadData, R as RenderContext } from './render-response.interface-ClWJXKL4.js';
|
|
4
5
|
import { ViteDevServer } from 'vite';
|
|
5
|
-
import { Response } from 'express';
|
|
6
6
|
import { Reflector } from '@nestjs/core';
|
|
7
7
|
import { Observable } from 'rxjs';
|
|
8
8
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Custom context properties that can be added via context factory.
|
|
12
|
+
* Allows any properties to be merged into RenderContext.
|
|
13
|
+
*/
|
|
14
|
+
type CustomContextProperties = Record<string, unknown>;
|
|
15
|
+
/**
|
|
16
|
+
* Context factory function signature
|
|
17
|
+
* Called for each request to build custom context properties
|
|
18
|
+
*
|
|
19
|
+
* @param params - Object containing the Express request
|
|
20
|
+
* @returns Custom context properties to merge into RenderContext (sync or async)
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* // Simple: pass user from Passport
|
|
25
|
+
* context: ({ req }) => ({ user: req.user })
|
|
26
|
+
*
|
|
27
|
+
* // With CLS
|
|
28
|
+
* context: ({ req }) => ({
|
|
29
|
+
* user: req.user,
|
|
30
|
+
* tenant: cls.get('tenant'),
|
|
31
|
+
* })
|
|
32
|
+
*
|
|
33
|
+
* // Async with services
|
|
34
|
+
* context: async ({ req }) => ({
|
|
35
|
+
* user: req.user,
|
|
36
|
+
* permissions: await permissionService.getForUser(req.user),
|
|
37
|
+
* })
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
type ContextFactory = (params: {
|
|
41
|
+
req: Request;
|
|
42
|
+
}) => CustomContextProperties | Promise<CustomContextProperties>;
|
|
10
43
|
/**
|
|
11
44
|
* SSR rendering mode configuration
|
|
12
45
|
*/
|
|
@@ -173,6 +206,52 @@ interface RenderConfig {
|
|
|
173
206
|
* ```
|
|
174
207
|
*/
|
|
175
208
|
allowedCookies?: string[];
|
|
209
|
+
/**
|
|
210
|
+
* Context factory function to enrich RenderContext with custom properties
|
|
211
|
+
*
|
|
212
|
+
* This is called for each request and the result is merged into the base
|
|
213
|
+
* RenderContext (url, path, query, params, method, headers, cookies).
|
|
214
|
+
*
|
|
215
|
+
* Use this to add user data, feature flags, tenant info, or any other
|
|
216
|
+
* request-scoped data that should be available in React components via usePageContext().
|
|
217
|
+
*
|
|
218
|
+
* Similar to @nestjs/graphql context option - you choose how to get the data:
|
|
219
|
+
* - From request object (req.user from Passport)
|
|
220
|
+
* - From CLS (nestjs-cls)
|
|
221
|
+
* - From request-scoped services
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* ```typescript
|
|
225
|
+
* // Simple: pass user from Passport JWT strategy
|
|
226
|
+
* RenderModule.forRoot({
|
|
227
|
+
* context: ({ req }) => ({
|
|
228
|
+
* user: req.user,
|
|
229
|
+
* }),
|
|
230
|
+
* })
|
|
231
|
+
*
|
|
232
|
+
* // With nestjs-cls
|
|
233
|
+
* RenderModule.forRoot({
|
|
234
|
+
* context: ({ req }) => ({
|
|
235
|
+
* user: req.user,
|
|
236
|
+
* tenant: cls.get('tenant'),
|
|
237
|
+
* featureFlags: cls.get('featureFlags'),
|
|
238
|
+
* }),
|
|
239
|
+
* })
|
|
240
|
+
*
|
|
241
|
+
* // Async with services (use forRootAsync)
|
|
242
|
+
* RenderModule.forRootAsync({
|
|
243
|
+
* imports: [PermissionModule],
|
|
244
|
+
* inject: [PermissionService],
|
|
245
|
+
* useFactory: (permissionService: PermissionService) => ({
|
|
246
|
+
* context: async ({ req }) => ({
|
|
247
|
+
* user: req.user,
|
|
248
|
+
* permissions: await permissionService.getForUser(req.user),
|
|
249
|
+
* }),
|
|
250
|
+
* }),
|
|
251
|
+
* })
|
|
252
|
+
* ```
|
|
253
|
+
*/
|
|
254
|
+
context?: ContextFactory;
|
|
176
255
|
}
|
|
177
256
|
/**
|
|
178
257
|
* Template parts for streaming SSR
|
|
@@ -206,6 +285,11 @@ interface SegmentResponse {
|
|
|
206
285
|
componentName: string;
|
|
207
286
|
/** Page context for updating hooks (path, params, query, etc.) */
|
|
208
287
|
context?: RenderContext;
|
|
288
|
+
/** Layouts below the swap target that need to be composed on client */
|
|
289
|
+
layouts?: Array<{
|
|
290
|
+
name: string;
|
|
291
|
+
props?: any;
|
|
292
|
+
}>;
|
|
209
293
|
}
|
|
210
294
|
|
|
211
295
|
declare class RenderModule {
|
|
@@ -555,7 +639,8 @@ declare class RenderInterceptor implements NestInterceptor {
|
|
|
555
639
|
private renderService;
|
|
556
640
|
private allowedHeaders?;
|
|
557
641
|
private allowedCookies?;
|
|
558
|
-
|
|
642
|
+
private contextFactory?;
|
|
643
|
+
constructor(reflector: Reflector, renderService: RenderService, allowedHeaders?: string[] | undefined, allowedCookies?: string[] | undefined, contextFactory?: ContextFactory | undefined);
|
|
559
644
|
/**
|
|
560
645
|
* Resolve the layout hierarchy for a given route
|
|
561
646
|
* Hierarchy: Root Layout → Controller Layout → Method Layout → Page
|
|
@@ -606,4 +691,4 @@ declare function ErrorPageDevelopment({ error, viewPath, phase, }: ErrorPageDeve
|
|
|
606
691
|
*/
|
|
607
692
|
declare function ErrorPageProduction(): react_jsx_runtime.JSX.Element;
|
|
608
693
|
|
|
609
|
-
export { ErrorPageDevelopment as E, RenderModule as R, StreamingErrorHandler as S, TemplateParserService as T, RenderService as a, RenderInterceptor as b, type RenderConfig as c, type SSRMode as d, ErrorPageProduction as e };
|
|
694
|
+
export { type ContextFactory as C, ErrorPageDevelopment as E, RenderModule as R, StreamingErrorHandler as S, TemplateParserService as T, RenderService as a, RenderInterceptor as b, type RenderConfig as c, type SSRMode as d, ErrorPageProduction as e };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
export { E as ErrorPageDevelopment, e as ErrorPageProduction, c as RenderConfig, b as RenderInterceptor, R as RenderModule, a as RenderService, d as SSRMode, S as StreamingErrorHandler, T as TemplateParserService } from './index-
|
|
1
|
+
export { C as ContextFactory, E as ErrorPageDevelopment, e as ErrorPageProduction, c as RenderConfig, b as RenderInterceptor, R as RenderModule, a as RenderService, d as SSRMode, S as StreamingErrorHandler, T as TemplateParserService } from './index-CiYcz-1T.mjs';
|
|
2
2
|
import React, { ComponentType, ReactNode } from 'react';
|
|
3
|
-
import { P as PageProps } from './use-page-context-
|
|
4
|
-
export { a as PageContextProvider, c as createSSRHooks, i as useCookie, h as useCookies, g as useHeader, f as useHeaders, u as usePageContext, b as useParams, d as useQuery, e as useRequest } from './use-page-context-
|
|
5
|
-
import { R as RenderContext, H as HeadData, a as RenderResponse } from './render-response.interface-
|
|
3
|
+
import { P as PageProps } from './use-page-context-CVC9DHcL.mjs';
|
|
4
|
+
export { a as PageContextProvider, c as createSSRHooks, i as useCookie, h as useCookies, g as useHeader, f as useHeaders, u as usePageContext, b as useParams, d as useQuery, e as useRequest } from './use-page-context-CVC9DHcL.mjs';
|
|
5
|
+
import { R as RenderContext, H as HeadData, a as RenderResponse } from './render-response.interface-ClWJXKL4.mjs';
|
|
6
6
|
import '@nestjs/common';
|
|
7
|
-
import 'vite';
|
|
8
7
|
import 'express';
|
|
8
|
+
import 'vite';
|
|
9
9
|
import '@nestjs/core';
|
|
10
10
|
import 'rxjs';
|
|
11
11
|
import 'react/jsx-runtime';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
export { E as ErrorPageDevelopment, e as ErrorPageProduction, c as RenderConfig, b as RenderInterceptor, R as RenderModule, a as RenderService, d as SSRMode, S as StreamingErrorHandler, T as TemplateParserService } from './index-
|
|
1
|
+
export { C as ContextFactory, E as ErrorPageDevelopment, e as ErrorPageProduction, c as RenderConfig, b as RenderInterceptor, R as RenderModule, a as RenderService, d as SSRMode, S as StreamingErrorHandler, T as TemplateParserService } from './index-Dq2qZSge.js';
|
|
2
2
|
import React, { ComponentType, ReactNode } from 'react';
|
|
3
|
-
import { P as PageProps } from './use-page-context-
|
|
4
|
-
export { a as PageContextProvider, c as createSSRHooks, i as useCookie, h as useCookies, g as useHeader, f as useHeaders, u as usePageContext, b as useParams, d as useQuery, e as useRequest } from './use-page-context-
|
|
5
|
-
import { R as RenderContext, H as HeadData, a as RenderResponse } from './render-response.interface-
|
|
3
|
+
import { P as PageProps } from './use-page-context-DChgHhL9.js';
|
|
4
|
+
export { a as PageContextProvider, c as createSSRHooks, i as useCookie, h as useCookies, g as useHeader, f as useHeaders, u as usePageContext, b as useParams, d as useQuery, e as useRequest } from './use-page-context-DChgHhL9.js';
|
|
5
|
+
import { R as RenderContext, H as HeadData, a as RenderResponse } from './render-response.interface-ClWJXKL4.js';
|
|
6
6
|
import '@nestjs/common';
|
|
7
|
-
import 'vite';
|
|
8
7
|
import 'express';
|
|
8
|
+
import 'vite';
|
|
9
9
|
import '@nestjs/core';
|
|
10
10
|
import 'rxjs';
|
|
11
11
|
import 'react/jsx-runtime';
|
package/dist/index.js
CHANGED
|
@@ -315,9 +315,13 @@ var StringRenderer = class _StringRenderer {
|
|
|
315
315
|
throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
|
|
316
316
|
}
|
|
317
317
|
}
|
|
318
|
-
const { data: pageData, __context: pageContext } = data;
|
|
318
|
+
const { data: pageData, __context: pageContext, __layouts: layouts } = data;
|
|
319
319
|
const html = await renderModule.renderSegment(viewComponent, data);
|
|
320
320
|
const componentName = viewComponent.displayName || viewComponent.name || "Component";
|
|
321
|
+
const layoutMetadata = layouts ? layouts.map((l) => ({
|
|
322
|
+
name: l.layout.displayName || l.layout.name || "default",
|
|
323
|
+
props: l.props
|
|
324
|
+
})) : [];
|
|
321
325
|
if (context.isDevelopment) {
|
|
322
326
|
const duration = Date.now() - startTime;
|
|
323
327
|
this.logger.log(`[SSR] ${componentName} segment rendered in ${duration}ms`);
|
|
@@ -328,7 +332,8 @@ var StringRenderer = class _StringRenderer {
|
|
|
328
332
|
props: pageData,
|
|
329
333
|
swapTarget,
|
|
330
334
|
componentName,
|
|
331
|
-
context: pageContext
|
|
335
|
+
context: pageContext,
|
|
336
|
+
layouts: layoutMetadata
|
|
332
337
|
};
|
|
333
338
|
}
|
|
334
339
|
};
|
|
@@ -1097,11 +1102,13 @@ exports.RenderInterceptor = class RenderInterceptor {
|
|
|
1097
1102
|
renderService;
|
|
1098
1103
|
allowedHeaders;
|
|
1099
1104
|
allowedCookies;
|
|
1100
|
-
|
|
1105
|
+
contextFactory;
|
|
1106
|
+
constructor(reflector, renderService, allowedHeaders, allowedCookies, contextFactory) {
|
|
1101
1107
|
this.reflector = reflector;
|
|
1102
1108
|
this.renderService = renderService;
|
|
1103
1109
|
this.allowedHeaders = allowedHeaders;
|
|
1104
1110
|
this.allowedCookies = allowedCookies;
|
|
1111
|
+
this.contextFactory = contextFactory;
|
|
1105
1112
|
}
|
|
1106
1113
|
/**
|
|
1107
1114
|
* Resolve the layout hierarchy for a given route
|
|
@@ -1241,6 +1248,14 @@ exports.RenderInterceptor = class RenderInterceptor {
|
|
|
1241
1248
|
renderContext.cookies = cookies;
|
|
1242
1249
|
}
|
|
1243
1250
|
}
|
|
1251
|
+
if (this.contextFactory) {
|
|
1252
|
+
const customContext = await this.contextFactory({
|
|
1253
|
+
req: request
|
|
1254
|
+
});
|
|
1255
|
+
if (customContext) {
|
|
1256
|
+
Object.assign(renderContext, customContext);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1244
1259
|
const renderResponse = isRenderResponse(data) ? data : {
|
|
1245
1260
|
props: data
|
|
1246
1261
|
};
|
|
@@ -1287,12 +1302,15 @@ exports.RenderInterceptor = _ts_decorate6([
|
|
|
1287
1302
|
_ts_param3(2, common.Inject("ALLOWED_HEADERS")),
|
|
1288
1303
|
_ts_param3(3, common.Optional()),
|
|
1289
1304
|
_ts_param3(3, common.Inject("ALLOWED_COOKIES")),
|
|
1305
|
+
_ts_param3(4, common.Optional()),
|
|
1306
|
+
_ts_param3(4, common.Inject("CONTEXT_FACTORY")),
|
|
1290
1307
|
_ts_metadata5("design:type", Function),
|
|
1291
1308
|
_ts_metadata5("design:paramtypes", [
|
|
1292
1309
|
typeof core.Reflector === "undefined" ? Object : core.Reflector,
|
|
1293
1310
|
typeof exports.RenderService === "undefined" ? Object : exports.RenderService,
|
|
1294
1311
|
Array,
|
|
1295
|
-
Array
|
|
1312
|
+
Array,
|
|
1313
|
+
typeof ContextFactory === "undefined" ? Object : ContextFactory
|
|
1296
1314
|
])
|
|
1297
1315
|
], exports.RenderInterceptor);
|
|
1298
1316
|
function _ts_decorate7(decorators, target, key, desc) {
|
|
@@ -1536,6 +1554,12 @@ exports.RenderModule = class _RenderModule {
|
|
|
1536
1554
|
provide: "ALLOWED_COOKIES",
|
|
1537
1555
|
useValue: config?.allowedCookies || []
|
|
1538
1556
|
});
|
|
1557
|
+
if (config?.context) {
|
|
1558
|
+
providers.push({
|
|
1559
|
+
provide: "CONTEXT_FACTORY",
|
|
1560
|
+
useValue: config.context
|
|
1561
|
+
});
|
|
1562
|
+
}
|
|
1539
1563
|
return {
|
|
1540
1564
|
global: true,
|
|
1541
1565
|
module: _RenderModule,
|
|
@@ -1654,6 +1678,13 @@ exports.RenderModule = class _RenderModule {
|
|
|
1654
1678
|
inject: [
|
|
1655
1679
|
"RENDER_CONFIG"
|
|
1656
1680
|
]
|
|
1681
|
+
},
|
|
1682
|
+
{
|
|
1683
|
+
provide: "CONTEXT_FACTORY",
|
|
1684
|
+
useFactory: /* @__PURE__ */ __name((config) => config?.context, "useFactory"),
|
|
1685
|
+
inject: [
|
|
1686
|
+
"RENDER_CONFIG"
|
|
1687
|
+
]
|
|
1657
1688
|
}
|
|
1658
1689
|
];
|
|
1659
1690
|
return {
|
|
@@ -1716,9 +1747,28 @@ var PageContext = getOrCreateContext();
|
|
|
1716
1747
|
function registerPageContextState(setter) {
|
|
1717
1748
|
}
|
|
1718
1749
|
__name(registerPageContextState, "registerPageContextState");
|
|
1750
|
+
var segmentSetters = /* @__PURE__ */ new Set();
|
|
1751
|
+
function registerSegmentSetter(setter) {
|
|
1752
|
+
segmentSetters.add(setter);
|
|
1753
|
+
}
|
|
1754
|
+
__name(registerSegmentSetter, "registerSegmentSetter");
|
|
1755
|
+
function unregisterSegmentSetter(setter) {
|
|
1756
|
+
segmentSetters.delete(setter);
|
|
1757
|
+
}
|
|
1758
|
+
__name(unregisterSegmentSetter, "unregisterSegmentSetter");
|
|
1759
|
+
function broadcastToSegments(context) {
|
|
1760
|
+
segmentSetters.forEach((setter) => setter(context));
|
|
1761
|
+
}
|
|
1762
|
+
__name(broadcastToSegments, "broadcastToSegments");
|
|
1719
1763
|
function PageContextProvider({ context: initialContext, children, isSegment = false }) {
|
|
1720
1764
|
const [context, setContext] = React2.useState(initialContext);
|
|
1721
1765
|
React2.useEffect(() => {
|
|
1766
|
+
if (!isSegment) {
|
|
1767
|
+
return void 0;
|
|
1768
|
+
} else {
|
|
1769
|
+
registerSegmentSetter(setContext);
|
|
1770
|
+
return () => unregisterSegmentSetter(setContext);
|
|
1771
|
+
}
|
|
1722
1772
|
}, [
|
|
1723
1773
|
isSegment
|
|
1724
1774
|
]);
|