@nestjs-ssr/react 0.1.11 → 0.2.0

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