@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.js CHANGED
@@ -4,7 +4,7 @@ var common = require('@nestjs/common');
4
4
  var core = require('@nestjs/core');
5
5
  var fs = require('fs');
6
6
  var path = require('path');
7
- var serialize = require('serialize-javascript');
7
+ var devalue = require('devalue');
8
8
  var escapeHtml = require('escape-html');
9
9
  var server = require('react-dom/server');
10
10
  var react = require('react');
@@ -12,7 +12,6 @@ var operators = require('rxjs/operators');
12
12
 
13
13
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
14
14
 
15
- var serialize__default = /*#__PURE__*/_interopDefault(serialize);
16
15
  var escapeHtml__default = /*#__PURE__*/_interopDefault(escapeHtml);
17
16
 
18
17
  var __defProp = Object.defineProperty;
@@ -105,21 +104,20 @@ exports.TemplateParserService = class TemplateParserService {
105
104
  /**
106
105
  * Build inline script that provides initial state to the client
107
106
  *
108
- * Safely serializes data using serialize-javascript to avoid XSS vulnerabilities.
109
- * This library handles all edge cases including escaping dangerous characters,
110
- * functions, dates, regexes, and prevents prototype pollution.
107
+ * Safely serializes data using devalue to avoid XSS vulnerabilities.
108
+ * Devalue is designed specifically for SSR, handling complex types safely
109
+ * while being faster and more secure than alternatives.
111
110
  */
112
- buildInlineScripts(data, context, componentName) {
111
+ buildInlineScripts(data, context, componentName, layouts) {
112
+ const layoutMetadata = layouts ? layouts.map((l) => ({
113
+ name: l.layout.displayName || l.layout.name || "default",
114
+ props: l.props
115
+ })) : [];
113
116
  return `<script>
114
- window.__INITIAL_STATE__ = ${serialize__default.default(data, {
115
- isJSON: true
116
- })};
117
- window.__CONTEXT__ = ${serialize__default.default(context, {
118
- isJSON: true
119
- })};
120
- window.__COMPONENT_NAME__ = ${serialize__default.default(componentName, {
121
- isJSON: true
122
- })};
117
+ window.__INITIAL_STATE__ = ${devalue.uneval(data)};
118
+ window.__CONTEXT__ = ${devalue.uneval(context)};
119
+ window.__COMPONENT_NAME__ = ${devalue.uneval(componentName)};
120
+ window.__LAYOUTS__ = ${devalue.uneval(layoutMetadata)};
123
121
  </script>`;
124
122
  }
125
123
  /**
@@ -428,12 +426,14 @@ exports.RenderService = class _RenderService {
428
426
  isDevelopment;
429
427
  ssrMode;
430
428
  entryServerPath;
431
- constructor(templateParser, streamingErrorHandler, ssrMode, defaultHead) {
429
+ rootLayout = void 0;
430
+ rootLayoutChecked = false;
431
+ constructor(templateParser, streamingErrorHandler, ssrMode, defaultHead, customTemplate) {
432
432
  this.templateParser = templateParser;
433
433
  this.streamingErrorHandler = streamingErrorHandler;
434
434
  this.defaultHead = defaultHead;
435
435
  this.isDevelopment = process.env.NODE_ENV !== "production";
436
- this.ssrMode = ssrMode || process.env.SSR_MODE || "string";
436
+ this.ssrMode = ssrMode || process.env.SSR_MODE || "stream";
437
437
  const absoluteServerPath = path.join(__dirname, "/templates/entry-server.tsx");
438
438
  const relativeServerPath = path.relative(process.cwd(), absoluteServerPath);
439
439
  if (relativeServerPath.startsWith("..")) {
@@ -441,36 +441,54 @@ exports.RenderService = class _RenderService {
441
441
  } else {
442
442
  this.entryServerPath = "/" + relativeServerPath.replace(/\\/g, "/");
443
443
  }
444
- let templatePath;
445
- if (this.isDevelopment) {
446
- const packageTemplatePaths = [
447
- path.join(__dirname, "../templates/index.html"),
448
- path.join(__dirname, "../src/templates/index.html"),
449
- path.join(__dirname, "../../src/templates/index.html")
450
- ];
451
- const localTemplatePath = path.join(process.cwd(), "src/views/index.html");
452
- const foundPackageTemplate = packageTemplatePaths.find((p) => fs.existsSync(p));
453
- if (foundPackageTemplate) {
454
- templatePath = foundPackageTemplate;
455
- } else if (fs.existsSync(localTemplatePath)) {
456
- templatePath = localTemplatePath;
444
+ if (customTemplate) {
445
+ if (customTemplate.includes("<!DOCTYPE") || customTemplate.includes("<html")) {
446
+ this.template = customTemplate;
447
+ this.logger.log(`\u2713 Loaded custom template (inline)`);
457
448
  } else {
458
- throw new Error(`Template file not found. Tried:
449
+ const customTemplatePath = customTemplate.startsWith("/") ? customTemplate : path.join(process.cwd(), customTemplate);
450
+ if (!fs.existsSync(customTemplatePath)) {
451
+ throw new Error(`Custom template file not found at ${customTemplatePath}`);
452
+ }
453
+ try {
454
+ this.template = fs.readFileSync(customTemplatePath, "utf-8");
455
+ this.logger.log(`\u2713 Loaded custom template from ${customTemplatePath}`);
456
+ } catch (error) {
457
+ throw new Error(`Failed to read custom template file at ${customTemplatePath}: ${error.message}`);
458
+ }
459
+ }
460
+ } else {
461
+ let templatePath;
462
+ if (this.isDevelopment) {
463
+ const packageTemplatePaths = [
464
+ path.join(__dirname, "../templates/index.html"),
465
+ path.join(__dirname, "../src/templates/index.html"),
466
+ path.join(__dirname, "../../src/templates/index.html")
467
+ ];
468
+ const localTemplatePath = path.join(process.cwd(), "src/views/index.html");
469
+ const foundPackageTemplate = packageTemplatePaths.find((p) => fs.existsSync(p));
470
+ if (foundPackageTemplate) {
471
+ templatePath = foundPackageTemplate;
472
+ } else if (fs.existsSync(localTemplatePath)) {
473
+ templatePath = localTemplatePath;
474
+ } else {
475
+ throw new Error(`Template file not found. Tried:
459
476
  ` + packageTemplatePaths.map((p) => ` - ${p} (package template)`).join("\n") + `
460
477
  - ${localTemplatePath} (local template)`);
478
+ }
479
+ } else {
480
+ templatePath = path.join(process.cwd(), "dist/client/index.html");
481
+ if (!fs.existsSync(templatePath)) {
482
+ throw new Error(`Template file not found at ${templatePath}. Make sure to run the build process first.`);
483
+ }
461
484
  }
462
- } else {
463
- templatePath = path.join(process.cwd(), "dist/client/index.html");
464
- if (!fs.existsSync(templatePath)) {
465
- throw new Error(`Template file not found at ${templatePath}. Make sure to run the build process first.`);
485
+ try {
486
+ this.template = fs.readFileSync(templatePath, "utf-8");
487
+ this.logger.log(`\u2713 Loaded template from ${templatePath}`);
488
+ } catch (error) {
489
+ throw new Error(`Failed to read template file at ${templatePath}: ${error.message}`);
466
490
  }
467
491
  }
468
- try {
469
- this.template = fs.readFileSync(templatePath, "utf-8");
470
- this.logger.log(`\u2713 Loaded template from ${templatePath}`);
471
- } catch (error) {
472
- throw new Error(`Failed to read template file at ${templatePath}: ${error.message}`);
473
- }
474
492
  if (!this.isDevelopment) {
475
493
  const manifestPath = path.join(process.cwd(), "dist/client/.vite/manifest.json");
476
494
  if (fs.existsSync(manifestPath)) {
@@ -490,6 +508,52 @@ exports.RenderService = class _RenderService {
490
508
  this.vite = vite;
491
509
  }
492
510
  /**
511
+ * Get the root layout component if it exists
512
+ * Auto-discovers layout files at conventional paths:
513
+ * - src/views/layout.tsx
514
+ * - src/views/layout/index.tsx
515
+ * - src/views/_layout.tsx
516
+ */
517
+ async getRootLayout() {
518
+ if (this.rootLayoutChecked) {
519
+ return this.rootLayout;
520
+ }
521
+ this.rootLayoutChecked = true;
522
+ const conventionalPaths = [
523
+ "src/views/layout.tsx",
524
+ "src/views/layout/index.tsx",
525
+ "src/views/_layout.tsx"
526
+ ];
527
+ try {
528
+ for (const path$1 of conventionalPaths) {
529
+ const absolutePath = path.join(process.cwd(), path$1);
530
+ if (!fs.existsSync(absolutePath)) {
531
+ continue;
532
+ }
533
+ this.logger.log(`\u2713 Found root layout at ${path$1}`);
534
+ if (this.vite) {
535
+ const layoutModule = await this.vite.ssrLoadModule("/" + path$1);
536
+ this.rootLayout = layoutModule.default;
537
+ return this.rootLayout;
538
+ } else {
539
+ const prodPath = path$1.replace("src/views", "dist/server/views").replace(".tsx", ".js");
540
+ const absoluteProdPath = path.join(process.cwd(), prodPath);
541
+ if (fs.existsSync(absoluteProdPath)) {
542
+ const layoutModule = await import(absoluteProdPath);
543
+ this.rootLayout = layoutModule.default;
544
+ return this.rootLayout;
545
+ }
546
+ }
547
+ }
548
+ this.rootLayout = null;
549
+ return null;
550
+ } catch (error) {
551
+ this.logger.warn(`\u26A0\uFE0F Error loading root layout: ${error.message}`);
552
+ this.rootLayout = null;
553
+ return null;
554
+ }
555
+ }
556
+ /**
493
557
  * Main render method that routes to string or stream mode
494
558
  */
495
559
  async render(viewComponent, data = {}, res, head) {
@@ -551,20 +615,19 @@ exports.RenderService = class _RenderService {
551
615
  throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
552
616
  }
553
617
  }
554
- const { data: pageData, __context: context } = data;
618
+ const { data: pageData, __context: context, __layouts: layouts } = data;
555
619
  const appHtml = await renderModule.renderComponent(viewComponent, data);
556
620
  const componentName = viewComponent.displayName || viewComponent.name || "Component";
621
+ const layoutMetadata = layouts ? layouts.map((l) => ({
622
+ name: l.layout.displayName || l.layout.name || "default",
623
+ props: l.props
624
+ })) : [];
557
625
  const initialStateScript = `
558
626
  <script>
559
- window.__INITIAL_STATE__ = ${serialize__default.default(pageData, {
560
- isJSON: true
561
- })};
562
- window.__CONTEXT__ = ${serialize__default.default(context, {
563
- isJSON: true
564
- })};
565
- window.__COMPONENT_NAME__ = ${serialize__default.default(componentName, {
566
- isJSON: true
567
- })};
627
+ window.__INITIAL_STATE__ = ${devalue.uneval(pageData)};
628
+ window.__CONTEXT__ = ${devalue.uneval(context)};
629
+ window.__COMPONENT_NAME__ = ${devalue.uneval(componentName)};
630
+ window.__LAYOUTS__ = ${devalue.uneval(layoutMetadata)};
568
631
  </script>
569
632
  `;
570
633
  let clientScript = "";
@@ -637,9 +700,9 @@ exports.RenderService = class _RenderService {
637
700
  throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
638
701
  }
639
702
  }
640
- const { data: pageData, __context: context } = data;
703
+ const { data: pageData, __context: context, __layouts: layouts } = data;
641
704
  const componentName = viewComponent.displayName || viewComponent.name || "Component";
642
- const inlineScripts = this.templateParser.buildInlineScripts(pageData, context, componentName);
705
+ const inlineScripts = this.templateParser.buildInlineScripts(pageData, context, componentName, layouts);
643
706
  const clientScript = this.templateParser.getClientScriptTag(this.isDevelopment, this.manifest);
644
707
  const stylesheetTags = this.templateParser.getStylesheetTags(this.isDevelopment, this.manifest);
645
708
  const headTags = this.templateParser.buildHeadTags(head);
@@ -695,22 +758,38 @@ exports.RenderService = _ts_decorate3([
695
758
  _ts_param2(2, common.Inject("SSR_MODE")),
696
759
  _ts_param2(3, common.Optional()),
697
760
  _ts_param2(3, common.Inject("DEFAULT_HEAD")),
761
+ _ts_param2(4, common.Optional()),
762
+ _ts_param2(4, common.Inject("CUSTOM_TEMPLATE")),
698
763
  _ts_metadata2("design:type", Function),
699
764
  _ts_metadata2("design:paramtypes", [
700
765
  typeof exports.TemplateParserService === "undefined" ? Object : exports.TemplateParserService,
701
766
  typeof exports.StreamingErrorHandler === "undefined" ? Object : exports.StreamingErrorHandler,
702
767
  typeof SSRMode === "undefined" ? Object : SSRMode,
703
- typeof HeadData === "undefined" ? Object : HeadData
768
+ typeof HeadData === "undefined" ? Object : HeadData,
769
+ String
704
770
  ])
705
771
  ], exports.RenderService);
706
772
  var RENDER_KEY = "render";
707
- function Render(component) {
773
+ var RENDER_OPTIONS_KEY = "render_options";
774
+ function Render(component, options) {
708
775
  return (target, propertyKey, descriptor) => {
709
776
  common.SetMetadata(RENDER_KEY, component)(target, propertyKey, descriptor);
777
+ if (options) {
778
+ common.SetMetadata(RENDER_OPTIONS_KEY, options)(target, propertyKey, descriptor);
779
+ }
710
780
  };
711
781
  }
712
782
  __name(Render, "Render");
713
- var ReactRender = Render;
783
+ var LAYOUT_KEY = "layout";
784
+ function Layout(layout, options) {
785
+ return (target) => {
786
+ common.SetMetadata(LAYOUT_KEY, {
787
+ layout,
788
+ options
789
+ })(target);
790
+ };
791
+ }
792
+ __name(Layout, "Layout");
714
793
 
715
794
  // src/render/render.interceptor.ts
716
795
  function _ts_decorate4(decorators, target, key, desc) {
@@ -724,6 +803,12 @@ function _ts_metadata3(k, v) {
724
803
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
725
804
  }
726
805
  __name(_ts_metadata3, "_ts_metadata");
806
+ function _ts_param3(paramIndex, decorator) {
807
+ return function(target, key) {
808
+ decorator(target, key, paramIndex);
809
+ };
810
+ }
811
+ __name(_ts_param3, "_ts_param");
727
812
  function isRenderResponse(data) {
728
813
  return data && typeof data === "object" && "props" in data;
729
814
  }
@@ -734,9 +819,60 @@ exports.RenderInterceptor = class RenderInterceptor {
734
819
  }
735
820
  reflector;
736
821
  renderService;
737
- constructor(reflector, renderService) {
822
+ allowedHeaders;
823
+ allowedCookies;
824
+ constructor(reflector, renderService, allowedHeaders, allowedCookies) {
738
825
  this.reflector = reflector;
739
826
  this.renderService = renderService;
827
+ this.allowedHeaders = allowedHeaders;
828
+ this.allowedCookies = allowedCookies;
829
+ }
830
+ /**
831
+ * Resolve the layout hierarchy for a given route
832
+ * Hierarchy: Root Layout → Controller Layout → Method Layout → Page
833
+ *
834
+ * Props are merged in priority order:
835
+ * 1. Static props from @Layout decorator (base)
836
+ * 2. Static props from @Render decorator (override)
837
+ * 3. Dynamic props from controller return (final override)
838
+ */
839
+ async resolveLayoutChain(context, dynamicLayoutProps) {
840
+ const layouts = [];
841
+ const rootLayout = await this.renderService.getRootLayout();
842
+ if (rootLayout) {
843
+ layouts.push({
844
+ layout: rootLayout,
845
+ props: dynamicLayoutProps || {}
846
+ });
847
+ }
848
+ const controllerLayoutMeta = this.reflector.get(LAYOUT_KEY, context.getClass());
849
+ const renderOptions = this.reflector.get(RENDER_OPTIONS_KEY, context.getHandler());
850
+ if (renderOptions?.layout === null) {
851
+ return [];
852
+ } else if (renderOptions?.layout === false) {
853
+ return layouts;
854
+ }
855
+ if (controllerLayoutMeta) {
856
+ const mergedProps = {
857
+ ...controllerLayoutMeta.options?.props || {},
858
+ ...dynamicLayoutProps || {}
859
+ };
860
+ layouts.push({
861
+ layout: controllerLayoutMeta.layout,
862
+ props: mergedProps
863
+ });
864
+ }
865
+ if (renderOptions?.layout) {
866
+ const mergedProps = {
867
+ ...renderOptions.layoutProps || {},
868
+ ...dynamicLayoutProps || {}
869
+ };
870
+ layouts.push({
871
+ layout: renderOptions.layout,
872
+ props: mergedProps
873
+ });
874
+ }
875
+ return layouts;
740
876
  }
741
877
  intercept(context, next) {
742
878
  const viewPathOrComponent = this.reflector.get(RENDER_KEY, context.getHandler());
@@ -752,16 +888,36 @@ exports.RenderInterceptor = class RenderInterceptor {
752
888
  path: request.path,
753
889
  query: request.query,
754
890
  params: request.params,
755
- userAgent: request.headers["user-agent"],
756
- acceptLanguage: request.headers["accept-language"],
757
- referer: request.headers.referer
891
+ method: request.method
758
892
  };
893
+ if (this.allowedHeaders?.length) {
894
+ for (const headerName of this.allowedHeaders) {
895
+ const value = request.headers[headerName.toLowerCase()];
896
+ if (value) {
897
+ renderContext[headerName] = Array.isArray(value) ? value.join(", ") : value;
898
+ }
899
+ }
900
+ }
901
+ if (this.allowedCookies?.length && request.cookies) {
902
+ const cookies = {};
903
+ for (const cookieName of this.allowedCookies) {
904
+ const value = request.cookies[cookieName];
905
+ if (value !== void 0) {
906
+ cookies[cookieName] = value;
907
+ }
908
+ }
909
+ if (Object.keys(cookies).length > 0) {
910
+ renderContext.cookies = cookies;
911
+ }
912
+ }
759
913
  const renderResponse = isRenderResponse(data) ? data : {
760
914
  props: data
761
915
  };
916
+ const layoutChain = await this.resolveLayoutChain(context, renderResponse.layoutProps);
762
917
  const fullData = {
763
918
  data: renderResponse.props,
764
- __context: renderContext
919
+ __context: renderContext,
920
+ __layouts: layoutChain
765
921
  };
766
922
  try {
767
923
  const html = await this.renderService.render(viewPathOrComponent, fullData, response, renderResponse.head);
@@ -778,10 +934,16 @@ exports.RenderInterceptor = class RenderInterceptor {
778
934
  };
779
935
  exports.RenderInterceptor = _ts_decorate4([
780
936
  common.Injectable(),
937
+ _ts_param3(2, common.Optional()),
938
+ _ts_param3(2, common.Inject("ALLOWED_HEADERS")),
939
+ _ts_param3(3, common.Optional()),
940
+ _ts_param3(3, common.Inject("ALLOWED_COOKIES")),
781
941
  _ts_metadata3("design:type", Function),
782
942
  _ts_metadata3("design:paramtypes", [
783
943
  typeof core.Reflector === "undefined" ? Object : core.Reflector,
784
- typeof exports.RenderService === "undefined" ? Object : exports.RenderService
944
+ typeof exports.RenderService === "undefined" ? Object : exports.RenderService,
945
+ Array,
946
+ Array
785
947
  ])
786
948
  ], exports.RenderInterceptor);
787
949
  function _ts_decorate5(decorators, target, key, desc) {
@@ -795,12 +957,12 @@ function _ts_metadata4(k, v) {
795
957
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
796
958
  }
797
959
  __name(_ts_metadata4, "_ts_metadata");
798
- function _ts_param3(paramIndex, decorator) {
960
+ function _ts_param4(paramIndex, decorator) {
799
961
  return function(target, key) {
800
962
  decorator(target, key, paramIndex);
801
963
  };
802
964
  }
803
- __name(_ts_param3, "_ts_param");
965
+ __name(_ts_param4, "_ts_param");
804
966
  var ViteInitializerService = class _ViteInitializerService {
805
967
  static {
806
968
  __name(this, "ViteInitializerService");
@@ -916,8 +1078,8 @@ var ViteInitializerService = class _ViteInitializerService {
916
1078
  };
917
1079
  ViteInitializerService = _ts_decorate5([
918
1080
  common.Injectable(),
919
- _ts_param3(2, common.Optional()),
920
- _ts_param3(2, common.Inject("VITE_CONFIG")),
1081
+ _ts_param4(2, common.Optional()),
1082
+ _ts_param4(2, common.Inject("VITE_CONFIG")),
921
1083
  _ts_metadata4("design:type", Function),
922
1084
  _ts_metadata4("design:paramtypes", [
923
1085
  typeof exports.RenderService === "undefined" ? Object : exports.RenderService,
@@ -1010,6 +1172,20 @@ exports.RenderModule = class _RenderModule {
1010
1172
  useValue: config.defaultHead
1011
1173
  });
1012
1174
  }
1175
+ if (config?.template) {
1176
+ providers.push({
1177
+ provide: "CUSTOM_TEMPLATE",
1178
+ useValue: config.template
1179
+ });
1180
+ }
1181
+ providers.push({
1182
+ provide: "ALLOWED_HEADERS",
1183
+ useValue: config?.allowedHeaders || []
1184
+ });
1185
+ providers.push({
1186
+ provide: "ALLOWED_COOKIES",
1187
+ useValue: config?.allowedCookies || []
1188
+ });
1013
1189
  return {
1014
1190
  global: true,
1015
1191
  module: _RenderModule,
@@ -1102,6 +1278,30 @@ exports.RenderModule = class _RenderModule {
1102
1278
  inject: [
1103
1279
  "RENDER_CONFIG"
1104
1280
  ]
1281
+ },
1282
+ // Custom template provider - reads from config
1283
+ {
1284
+ provide: "CUSTOM_TEMPLATE",
1285
+ useFactory: /* @__PURE__ */ __name((config) => config?.template, "useFactory"),
1286
+ inject: [
1287
+ "RENDER_CONFIG"
1288
+ ]
1289
+ },
1290
+ // Allowed headers provider - reads from config
1291
+ {
1292
+ provide: "ALLOWED_HEADERS",
1293
+ useFactory: /* @__PURE__ */ __name((config) => config?.allowedHeaders || [], "useFactory"),
1294
+ inject: [
1295
+ "RENDER_CONFIG"
1296
+ ]
1297
+ },
1298
+ // Allowed cookies provider - reads from config
1299
+ {
1300
+ provide: "ALLOWED_COOKIES",
1301
+ useFactory: /* @__PURE__ */ __name((config) => config?.allowedCookies || [], "useFactory"),
1302
+ inject: [
1303
+ "RENDER_CONFIG"
1304
+ ]
1105
1305
  }
1106
1306
  ];
1107
1307
  return {
@@ -1138,32 +1338,203 @@ exports.RenderModule = _ts_decorate6([
1138
1338
  })
1139
1339
  ], exports.RenderModule);
1140
1340
  var PageContext = /* @__PURE__ */ react.createContext(null);
1141
- function usePageContext() {
1142
- const context = react.useContext(PageContext);
1143
- if (!context) {
1144
- throw new Error("usePageContext must be used within PageContextProvider");
1145
- }
1146
- return context;
1341
+ function PageContextProvider({ context, children }) {
1342
+ return /* @__PURE__ */ React.createElement(PageContext.Provider, {
1343
+ value: context
1344
+ }, children);
1147
1345
  }
1148
- __name(usePageContext, "usePageContext");
1149
- function useParams() {
1150
- return usePageContext().params;
1151
- }
1152
- __name(useParams, "useParams");
1153
- function useQuery() {
1154
- return usePageContext().query;
1155
- }
1156
- __name(useQuery, "useQuery");
1157
- function useUserAgent() {
1158
- return usePageContext().userAgent;
1346
+ __name(PageContextProvider, "PageContextProvider");
1347
+ function createSSRHooks() {
1348
+ return {
1349
+ /**
1350
+ * Hook to access the full page context with your app's type.
1351
+ * Contains URL metadata, headers, and any custom properties you've added.
1352
+ */
1353
+ usePageContext: /* @__PURE__ */ __name(() => {
1354
+ const context = react.useContext(PageContext);
1355
+ if (!context) {
1356
+ throw new Error("usePageContext must be used within PageContextProvider");
1357
+ }
1358
+ return context;
1359
+ }, "usePageContext"),
1360
+ /**
1361
+ * Hook to access route parameters.
1362
+ *
1363
+ * @example
1364
+ * ```tsx
1365
+ * // Route: /users/:id
1366
+ * const params = useParams();
1367
+ * console.log(params.id); // '123'
1368
+ * ```
1369
+ */
1370
+ useParams: /* @__PURE__ */ __name(() => {
1371
+ const context = react.useContext(PageContext);
1372
+ if (!context) {
1373
+ throw new Error("useParams must be used within PageContextProvider");
1374
+ }
1375
+ return context.params;
1376
+ }, "useParams"),
1377
+ /**
1378
+ * Hook to access query string parameters.
1379
+ *
1380
+ * @example
1381
+ * ```tsx
1382
+ * // URL: /search?q=react&sort=date
1383
+ * const query = useQuery();
1384
+ * console.log(query.q); // 'react'
1385
+ * console.log(query.sort); // 'date'
1386
+ * ```
1387
+ */
1388
+ useQuery: /* @__PURE__ */ __name(() => {
1389
+ const context = react.useContext(PageContext);
1390
+ if (!context) {
1391
+ throw new Error("useQuery must be used within PageContextProvider");
1392
+ }
1393
+ return context.query;
1394
+ }, "useQuery"),
1395
+ /**
1396
+ * Alias for usePageContext() with a more intuitive name.
1397
+ * Returns the full request context with your app's type.
1398
+ *
1399
+ * @example
1400
+ * ```tsx
1401
+ * const request = useRequest();
1402
+ * console.log(request.path); // '/users/123'
1403
+ * console.log(request.method); // 'GET'
1404
+ * console.log(request.params); // { id: '123' }
1405
+ * console.log(request.query); // { search: 'foo' }
1406
+ * ```
1407
+ */
1408
+ useRequest: /* @__PURE__ */ __name(() => {
1409
+ const context = react.useContext(PageContext);
1410
+ if (!context) {
1411
+ throw new Error("useRequest must be used within PageContextProvider");
1412
+ }
1413
+ return context;
1414
+ }, "useRequest"),
1415
+ /**
1416
+ * Hook to access headers configured via allowedHeaders.
1417
+ * Returns all headers as a Record.
1418
+ *
1419
+ * Configure in module registration:
1420
+ * ```typescript
1421
+ * RenderModule.register({
1422
+ * allowedHeaders: ['user-agent', 'x-tenant-id', 'x-api-version']
1423
+ * })
1424
+ * ```
1425
+ *
1426
+ * @example
1427
+ * ```tsx
1428
+ * const headers = useHeaders();
1429
+ * console.log(headers['user-agent']); // 'Mozilla/5.0...'
1430
+ * console.log(headers['x-tenant-id']); // 'tenant-123'
1431
+ * console.log(headers['x-api-version']); // 'v2'
1432
+ * ```
1433
+ */
1434
+ useHeaders: /* @__PURE__ */ __name(() => {
1435
+ const context = react.useContext(PageContext);
1436
+ if (!context) {
1437
+ throw new Error("useHeaders must be used within PageContextProvider");
1438
+ }
1439
+ const baseKeys = /* @__PURE__ */ new Set([
1440
+ "url",
1441
+ "path",
1442
+ "query",
1443
+ "params",
1444
+ "method",
1445
+ "cookies"
1446
+ ]);
1447
+ const headers = {};
1448
+ for (const [key, value] of Object.entries(context)) {
1449
+ if (!baseKeys.has(key) && typeof value === "string") {
1450
+ headers[key] = value;
1451
+ }
1452
+ }
1453
+ return headers;
1454
+ }, "useHeaders"),
1455
+ /**
1456
+ * Hook to access a specific custom header by name.
1457
+ * Returns undefined if the header is not configured or not present.
1458
+ *
1459
+ * @param name - The header name (as configured in allowedHeaders)
1460
+ *
1461
+ * @example
1462
+ * ```tsx
1463
+ * const tenantId = useHeader('x-tenant-id');
1464
+ * if (tenantId) {
1465
+ * console.log(`Tenant: ${tenantId}`);
1466
+ * }
1467
+ * ```
1468
+ */
1469
+ useHeader: /* @__PURE__ */ __name((name) => {
1470
+ const context = react.useContext(PageContext);
1471
+ if (!context) {
1472
+ throw new Error("useHeader must be used within PageContextProvider");
1473
+ }
1474
+ const value = context[name];
1475
+ return typeof value === "string" ? value : void 0;
1476
+ }, "useHeader"),
1477
+ /**
1478
+ * Hook to access cookies configured via allowedCookies.
1479
+ * Returns all allowed cookies as a Record.
1480
+ *
1481
+ * Configure in module registration:
1482
+ * ```typescript
1483
+ * RenderModule.register({
1484
+ * allowedCookies: ['theme', 'locale', 'consent']
1485
+ * })
1486
+ * ```
1487
+ *
1488
+ * @example
1489
+ * ```tsx
1490
+ * const cookies = useCookies();
1491
+ * console.log(cookies.theme); // 'dark'
1492
+ * console.log(cookies.locale); // 'en-US'
1493
+ * ```
1494
+ */
1495
+ useCookies: /* @__PURE__ */ __name(() => {
1496
+ const context = react.useContext(PageContext);
1497
+ if (!context) {
1498
+ throw new Error("useCookies must be used within PageContextProvider");
1499
+ }
1500
+ const cookies = context.cookies;
1501
+ return typeof cookies === "object" && cookies !== null ? cookies : {};
1502
+ }, "useCookies"),
1503
+ /**
1504
+ * Hook to access a specific cookie by name.
1505
+ * Returns undefined if the cookie is not configured or not present.
1506
+ *
1507
+ * @param name - The cookie name (as configured in allowedCookies)
1508
+ *
1509
+ * @example
1510
+ * ```tsx
1511
+ * const theme = useCookie('theme');
1512
+ * if (theme === 'dark') {
1513
+ * console.log('Dark mode enabled');
1514
+ * }
1515
+ * ```
1516
+ */
1517
+ useCookie: /* @__PURE__ */ __name((name) => {
1518
+ const context = react.useContext(PageContext);
1519
+ if (!context) {
1520
+ throw new Error("useCookie must be used within PageContextProvider");
1521
+ }
1522
+ const contextObj = context;
1523
+ const cookies = contextObj.cookies;
1524
+ if (typeof cookies === "object" && cookies !== null && !Array.isArray(cookies)) {
1525
+ const cookiesRecord = cookies;
1526
+ const value = cookiesRecord[name];
1527
+ return typeof value === "string" ? value : void 0;
1528
+ }
1529
+ return void 0;
1530
+ }, "useCookie")
1531
+ };
1159
1532
  }
1160
- __name(useUserAgent, "useUserAgent");
1533
+ __name(createSSRHooks, "createSSRHooks");
1161
1534
 
1162
1535
  exports.ErrorPageDevelopment = ErrorPageDevelopment;
1163
1536
  exports.ErrorPageProduction = ErrorPageProduction;
1164
- exports.ReactRender = ReactRender;
1537
+ exports.Layout = Layout;
1538
+ exports.PageContextProvider = PageContextProvider;
1165
1539
  exports.Render = Render;
1166
- exports.usePageContext = usePageContext;
1167
- exports.useParams = useParams;
1168
- exports.useQuery = useQuery;
1169
- exports.useUserAgent = useUserAgent;
1540
+ exports.createSSRHooks = createSSRHooks;