@nestjs-ssr/react 0.2.1 → 0.2.2

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
@@ -5,7 +5,7 @@ import { join, relative } from 'path';
5
5
  import { uneval } from 'devalue';
6
6
  import escapeHtml from 'escape-html';
7
7
  import { renderToStaticMarkup } from 'react-dom/server';
8
- import { createElement, createContext, useContext } from 'react';
8
+ import React, { createElement, createContext, useContext } from 'react';
9
9
  import { switchMap } from 'rxjs/operators';
10
10
 
11
11
  var __defProp = Object.defineProperty;
@@ -185,8 +185,6 @@ window.__LAYOUTS__ = ${uneval(layoutMetadata)};
185
185
  TemplateParserService = _ts_decorate([
186
186
  Injectable()
187
187
  ], TemplateParserService);
188
-
189
- // src/render/error-pages/error-page-development.tsx
190
188
  function ErrorPageDevelopment({ error, viewPath, phase }) {
191
189
  const stackLines = error.stack ? error.stack.split("\n").slice(1) : [];
192
190
  return /* @__PURE__ */ React.createElement("html", {
@@ -260,8 +258,6 @@ function ErrorPageDevelopment({ error, viewPath, phase }) {
260
258
  }, /* @__PURE__ */ React.createElement("p", null, /* @__PURE__ */ React.createElement("strong", null, "View Path:"), " ", viewPath), /* @__PURE__ */ React.createElement("p", null, /* @__PURE__ */ React.createElement("strong", null, "Error Phase:"), " ", phase === "shell" ? "Shell (before streaming started)" : "Streaming (during content delivery)"), /* @__PURE__ */ React.createElement("p", null, /* @__PURE__ */ React.createElement("strong", null, "Environment:"), " Development")))));
261
259
  }
262
260
  __name(ErrorPageDevelopment, "ErrorPageDevelopment");
263
-
264
- // src/render/error-pages/error-page-production.tsx
265
261
  function ErrorPageProduction() {
266
262
  return /* @__PURE__ */ React.createElement("html", {
267
263
  lang: "en"
@@ -671,60 +667,79 @@ var RenderService = class _RenderService {
671
667
  async renderToStream(viewComponent, data = {}, res, head) {
672
668
  const startTime = Date.now();
673
669
  let shellReadyTime = 0;
674
- try {
675
- let template = this.template;
676
- if (this.vite) {
677
- template = await this.vite.transformIndexHtml("/", template);
678
- }
679
- const templateParts = this.templateParser.parseTemplate(template);
680
- let renderModule;
681
- if (this.vite) {
682
- renderModule = await this.vite.ssrLoadModule(this.entryServerPath);
683
- } else {
684
- if (this.serverManifest) {
685
- const manifestEntry = Object.entries(this.serverManifest).find(([key, value]) => value.isEntry && key.includes("entry-server"));
686
- if (manifestEntry) {
687
- const [, entry] = manifestEntry;
688
- const serverPath = join(process.cwd(), "dist/server", entry.file);
689
- renderModule = await import(serverPath);
670
+ return new Promise((resolve, reject) => {
671
+ const executeStream = /* @__PURE__ */ __name(async () => {
672
+ let template = this.template;
673
+ if (this.vite) {
674
+ template = await this.vite.transformIndexHtml("/", template);
675
+ }
676
+ const templateParts = this.templateParser.parseTemplate(template);
677
+ let renderModule;
678
+ if (this.vite) {
679
+ renderModule = await this.vite.ssrLoadModule(this.entryServerPath);
680
+ } else {
681
+ if (this.serverManifest) {
682
+ const manifestEntry = Object.entries(this.serverManifest).find(([key, value]) => value.isEntry && key.includes("entry-server"));
683
+ if (manifestEntry) {
684
+ const [, entry] = manifestEntry;
685
+ const serverPath = join(process.cwd(), "dist/server", entry.file);
686
+ renderModule = await import(serverPath);
687
+ } else {
688
+ throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
689
+ }
690
690
  } else {
691
691
  throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
692
692
  }
693
- } else {
694
- throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
695
693
  }
696
- }
697
- const { data: pageData, __context: context, __layouts: layouts } = data;
698
- const componentName = viewComponent.displayName || viewComponent.name || "Component";
699
- const inlineScripts = this.templateParser.buildInlineScripts(pageData, context, componentName, layouts);
700
- const clientScript = this.templateParser.getClientScriptTag(this.isDevelopment, this.manifest);
701
- const stylesheetTags = this.templateParser.getStylesheetTags(this.isDevelopment, this.manifest);
702
- const headTags = this.templateParser.buildHeadTags(head);
703
- let didError = false;
704
- const { pipe, abort } = renderModule.renderComponentStream(viewComponent, data, {
705
- onShellReady: /* @__PURE__ */ __name(() => {
706
- shellReadyTime = Date.now();
707
- res.statusCode = didError ? 500 : 200;
708
- res.setHeader("Content-Type", "text/html; charset=utf-8");
709
- let htmlStart = templateParts.htmlStart;
710
- htmlStart = htmlStart.replace("<!--styles-->", stylesheetTags);
711
- htmlStart = htmlStart.replace("<!--head-meta-->", headTags);
712
- res.write(htmlStart);
713
- res.write(templateParts.rootStart);
714
- pipe(res);
715
- if (this.isDevelopment) {
716
- const ttfb = shellReadyTime - startTime;
717
- this.logger.log(`[SSR] ${componentName} shell ready in ${ttfb}ms (stream mode - TTFB)`);
694
+ const { data: pageData, __context: context, __layouts: layouts } = data;
695
+ const componentName = viewComponent.displayName || viewComponent.name || "Component";
696
+ const inlineScripts = this.templateParser.buildInlineScripts(pageData, context, componentName, layouts);
697
+ const clientScript = this.templateParser.getClientScriptTag(this.isDevelopment, this.manifest);
698
+ const stylesheetTags = this.templateParser.getStylesheetTags(this.isDevelopment, this.manifest);
699
+ const headTags = this.templateParser.buildHeadTags(head);
700
+ let didError = false;
701
+ let shellErrorOccurred = false;
702
+ const { PassThrough } = await import('stream');
703
+ const reactStream = new PassThrough();
704
+ let allReadyFired = false;
705
+ const { pipe, abort } = renderModule.renderComponentStream(viewComponent, data, {
706
+ onShellReady: /* @__PURE__ */ __name(() => {
707
+ shellReadyTime = Date.now();
708
+ if (!res.headersSent) {
709
+ res.statusCode = didError ? 500 : 200;
710
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
711
+ }
712
+ let htmlStart = templateParts.htmlStart;
713
+ htmlStart = htmlStart.replace("<!--styles-->", stylesheetTags);
714
+ htmlStart = htmlStart.replace("<!--head-meta-->", headTags);
715
+ res.write(htmlStart);
716
+ res.write(templateParts.rootStart);
717
+ pipe(reactStream);
718
+ reactStream.pipe(res, {
719
+ end: false
720
+ });
721
+ if (this.isDevelopment) {
722
+ const ttfb = shellReadyTime - startTime;
723
+ this.logger.log(`[SSR] ${componentName} shell ready in ${ttfb}ms (stream mode - TTFB)`);
724
+ }
725
+ }, "onShellReady"),
726
+ onShellError: /* @__PURE__ */ __name((error) => {
727
+ shellErrorOccurred = true;
728
+ this.streamingErrorHandler.handleShellError(error, res, componentName, this.isDevelopment);
729
+ resolve();
730
+ }, "onShellError"),
731
+ onError: /* @__PURE__ */ __name((error) => {
732
+ didError = true;
733
+ this.streamingErrorHandler.handleStreamError(error, componentName);
734
+ }, "onError"),
735
+ onAllReady: /* @__PURE__ */ __name(() => {
736
+ allReadyFired = true;
737
+ }, "onAllReady")
738
+ });
739
+ reactStream.on("end", () => {
740
+ if (shellErrorOccurred) {
741
+ return;
718
742
  }
719
- }, "onShellReady"),
720
- onShellError: /* @__PURE__ */ __name((error) => {
721
- this.streamingErrorHandler.handleShellError(error, res, componentName, this.isDevelopment);
722
- }, "onShellError"),
723
- onError: /* @__PURE__ */ __name((error) => {
724
- didError = true;
725
- this.streamingErrorHandler.handleStreamError(error, componentName);
726
- }, "onError"),
727
- onAllReady: /* @__PURE__ */ __name(() => {
728
743
  res.write(inlineScripts);
729
744
  res.write(clientScript);
730
745
  res.write(templateParts.rootEnd);
@@ -733,17 +748,25 @@ var RenderService = class _RenderService {
733
748
  if (this.isDevelopment) {
734
749
  const totalTime = Date.now() - startTime;
735
750
  const streamTime = Date.now() - shellReadyTime;
736
- this.logger.log(`[SSR] ${componentName} streaming complete in ${totalTime}ms total (${streamTime}ms streaming)`);
751
+ const viaAllReady = allReadyFired ? " (onAllReady fired)" : " (onAllReady never fired)";
752
+ this.logger.log(`[SSR] ${componentName} streaming complete in ${totalTime}ms total (${streamTime}ms streaming)${viaAllReady}`);
737
753
  }
738
- }, "onAllReady")
754
+ resolve();
755
+ });
756
+ reactStream.on("error", (error) => {
757
+ reject(error);
758
+ });
759
+ res.on("close", () => {
760
+ abort();
761
+ resolve();
762
+ });
763
+ }, "executeStream");
764
+ executeStream().catch((error) => {
765
+ const componentName = typeof viewComponent === "function" ? viewComponent.name : String(viewComponent);
766
+ this.streamingErrorHandler.handleShellError(error, res, componentName, this.isDevelopment);
767
+ resolve();
739
768
  });
740
- res.on("close", () => {
741
- abort();
742
- });
743
- } catch (error) {
744
- const componentName = typeof viewComponent === "function" ? viewComponent.name : String(viewComponent);
745
- this.streamingErrorHandler.handleShellError(error, res, componentName, this.isDevelopment);
746
- }
769
+ });
747
770
  }
748
771
  };
749
772
  RenderService = _ts_decorate3([
@@ -877,6 +900,9 @@ var RenderInterceptor = class {
877
900
  const httpContext = context.switchToHttp();
878
901
  const request = httpContext.getRequest();
879
902
  const response = httpContext.getResponse();
903
+ if (typeof data === "string") {
904
+ return data;
905
+ }
880
906
  const renderContext = {
881
907
  url: request.url,
882
908
  path: request.path,
@@ -967,11 +993,23 @@ var ViteInitializerService = class _ViteInitializerService {
967
993
  viteMode;
968
994
  vitePort;
969
995
  viteServer = null;
996
+ isShuttingDown = false;
970
997
  constructor(renderService, httpAdapterHost, viteConfig) {
971
998
  this.renderService = renderService;
972
999
  this.httpAdapterHost = httpAdapterHost;
973
1000
  this.viteMode = viteConfig?.mode || "embedded";
974
1001
  this.vitePort = viteConfig?.port || 5173;
1002
+ this.registerSignalHandlers();
1003
+ }
1004
+ registerSignalHandlers() {
1005
+ const cleanup = /* @__PURE__ */ __name(async (signal) => {
1006
+ if (this.isShuttingDown) return;
1007
+ this.isShuttingDown = true;
1008
+ this.logger.log(`Received ${signal}, closing Vite server...`);
1009
+ await this.closeViteServer();
1010
+ }, "cleanup");
1011
+ process.once("SIGTERM", () => cleanup("SIGTERM"));
1012
+ process.once("SIGINT", () => cleanup("SIGINT"));
975
1013
  }
976
1014
  async onModuleInit() {
977
1015
  const isDevelopment = process.env.NODE_ENV !== "production";
@@ -1060,9 +1098,23 @@ var ViteInitializerService = class _ViteInitializerService {
1060
1098
  * This prevents port conflicts on hot reload
1061
1099
  */
1062
1100
  async onModuleDestroy() {
1101
+ await this.closeViteServer();
1102
+ }
1103
+ /**
1104
+ * Cleanup: Close Vite server on application shutdown
1105
+ * Belt-and-suspenders approach with onModuleDestroy
1106
+ */
1107
+ async onApplicationShutdown() {
1108
+ await this.closeViteServer();
1109
+ }
1110
+ async closeViteServer() {
1111
+ if (this.isShuttingDown && !this.viteServer) return;
1112
+ this.isShuttingDown = true;
1063
1113
  if (this.viteServer) {
1064
1114
  try {
1115
+ this.renderService.setViteServer(null);
1065
1116
  await this.viteServer.close();
1117
+ this.viteServer = null;
1066
1118
  this.logger.log("\u2713 Vite server closed");
1067
1119
  } catch (error) {
1068
1120
  this.logger.warn(`Failed to close Vite server: ${error.message}`);
@@ -1,6 +1,7 @@
1
- export { E as ErrorPageDevelopment, f as ErrorPageProduction, c as RenderInterceptor, a as RenderModule, b as RenderService, S as StreamingErrorHandler, T as TemplateParserService } from '../index-C5Knql-9.mjs';
1
+ export { E as ErrorPageDevelopment, e as ErrorPageProduction, b as RenderInterceptor, R as RenderModule, a as RenderService, S as StreamingErrorHandler, T as TemplateParserService } from '../index-Dq1yt0sX.mjs';
2
2
  import '@nestjs/common';
3
3
  import 'react';
4
+ import '../render-response.interface-Dc-Kwb09.mjs';
4
5
  import 'vite';
5
6
  import 'express';
6
7
  import '@nestjs/core';
@@ -1,6 +1,7 @@
1
- export { E as ErrorPageDevelopment, f as ErrorPageProduction, c as RenderInterceptor, a as RenderModule, b as RenderService, S as StreamingErrorHandler, T as TemplateParserService } from '../index-C5Knql-9.js';
1
+ export { E as ErrorPageDevelopment, e as ErrorPageProduction, b as RenderInterceptor, R as RenderModule, a as RenderService, S as StreamingErrorHandler, T as TemplateParserService } from '../index-CaGD266H.js';
2
2
  import '@nestjs/common';
3
3
  import 'react';
4
+ import '../render-response.interface-Dc-Kwb09.js';
4
5
  import 'vite';
5
6
  import 'express';
6
7
  import '@nestjs/core';
@@ -7,12 +7,13 @@ var path = require('path');
7
7
  var devalue = require('devalue');
8
8
  var escapeHtml = require('escape-html');
9
9
  var server = require('react-dom/server');
10
- var react = require('react');
10
+ var React = require('react');
11
11
  var operators = require('rxjs/operators');
12
12
 
13
13
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
14
14
 
15
15
  var escapeHtml__default = /*#__PURE__*/_interopDefault(escapeHtml);
16
+ var React__default = /*#__PURE__*/_interopDefault(React);
16
17
 
17
18
  var __defProp = Object.defineProperty;
18
19
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
@@ -191,18 +192,16 @@ window.__LAYOUTS__ = ${devalue.uneval(layoutMetadata)};
191
192
  exports.TemplateParserService = _ts_decorate([
192
193
  common.Injectable()
193
194
  ], exports.TemplateParserService);
194
-
195
- // src/render/error-pages/error-page-development.tsx
196
195
  function ErrorPageDevelopment({ error, viewPath, phase }) {
197
196
  const stackLines = error.stack ? error.stack.split("\n").slice(1) : [];
198
- return /* @__PURE__ */ React.createElement("html", {
197
+ return /* @__PURE__ */ React__default.default.createElement("html", {
199
198
  lang: "en"
200
- }, /* @__PURE__ */ React.createElement("head", null, /* @__PURE__ */ React.createElement("meta", {
199
+ }, /* @__PURE__ */ React__default.default.createElement("head", null, /* @__PURE__ */ React__default.default.createElement("meta", {
201
200
  charSet: "UTF-8"
202
- }), /* @__PURE__ */ React.createElement("meta", {
201
+ }), /* @__PURE__ */ React__default.default.createElement("meta", {
203
202
  name: "viewport",
204
203
  content: "width=device-width, initial-scale=1.0"
205
- }), /* @__PURE__ */ React.createElement("title", null, `SSR Error - ${error.name}`), /* @__PURE__ */ React.createElement("style", {
204
+ }), /* @__PURE__ */ React__default.default.createElement("title", null, `SSR Error - ${error.name}`), /* @__PURE__ */ React__default.default.createElement("style", {
206
205
  dangerouslySetInnerHTML: {
207
206
  __html: `
208
207
  body {
@@ -253,30 +252,28 @@ function ErrorPageDevelopment({ error, viewPath, phase }) {
253
252
  }
254
253
  `
255
254
  }
256
- })), /* @__PURE__ */ React.createElement("body", null, /* @__PURE__ */ React.createElement("div", {
255
+ })), /* @__PURE__ */ React__default.default.createElement("body", null, /* @__PURE__ */ React__default.default.createElement("div", {
257
256
  className: "error-container"
258
- }, /* @__PURE__ */ React.createElement("h1", null, "Server-Side Rendering Error"), /* @__PURE__ */ React.createElement("div", {
257
+ }, /* @__PURE__ */ React__default.default.createElement("h1", null, "Server-Side Rendering Error"), /* @__PURE__ */ React__default.default.createElement("div", {
259
258
  className: "error-type"
260
- }, error.name), /* @__PURE__ */ React.createElement("div", {
259
+ }, error.name), /* @__PURE__ */ React__default.default.createElement("div", {
261
260
  className: "error-message"
262
- }, error.message), /* @__PURE__ */ React.createElement("h2", null, "Stack Trace"), /* @__PURE__ */ React.createElement("div", {
261
+ }, error.message), /* @__PURE__ */ React__default.default.createElement("h2", null, "Stack Trace"), /* @__PURE__ */ React__default.default.createElement("div", {
263
262
  className: "stack-trace"
264
- }, /* @__PURE__ */ React.createElement("pre", null, stackLines.join("\n"))), /* @__PURE__ */ React.createElement("div", {
263
+ }, /* @__PURE__ */ React__default.default.createElement("pre", null, stackLines.join("\n"))), /* @__PURE__ */ React__default.default.createElement("div", {
265
264
  className: "meta"
266
- }, /* @__PURE__ */ React.createElement("p", null, /* @__PURE__ */ React.createElement("strong", null, "View Path:"), " ", viewPath), /* @__PURE__ */ React.createElement("p", null, /* @__PURE__ */ React.createElement("strong", null, "Error Phase:"), " ", phase === "shell" ? "Shell (before streaming started)" : "Streaming (during content delivery)"), /* @__PURE__ */ React.createElement("p", null, /* @__PURE__ */ React.createElement("strong", null, "Environment:"), " Development")))));
265
+ }, /* @__PURE__ */ React__default.default.createElement("p", null, /* @__PURE__ */ React__default.default.createElement("strong", null, "View Path:"), " ", viewPath), /* @__PURE__ */ React__default.default.createElement("p", null, /* @__PURE__ */ React__default.default.createElement("strong", null, "Error Phase:"), " ", phase === "shell" ? "Shell (before streaming started)" : "Streaming (during content delivery)"), /* @__PURE__ */ React__default.default.createElement("p", null, /* @__PURE__ */ React__default.default.createElement("strong", null, "Environment:"), " Development")))));
267
266
  }
268
267
  __name(ErrorPageDevelopment, "ErrorPageDevelopment");
269
-
270
- // src/render/error-pages/error-page-production.tsx
271
268
  function ErrorPageProduction() {
272
- return /* @__PURE__ */ React.createElement("html", {
269
+ return /* @__PURE__ */ React__default.default.createElement("html", {
273
270
  lang: "en"
274
- }, /* @__PURE__ */ React.createElement("head", null, /* @__PURE__ */ React.createElement("meta", {
271
+ }, /* @__PURE__ */ React__default.default.createElement("head", null, /* @__PURE__ */ React__default.default.createElement("meta", {
275
272
  charSet: "UTF-8"
276
- }), /* @__PURE__ */ React.createElement("meta", {
273
+ }), /* @__PURE__ */ React__default.default.createElement("meta", {
277
274
  name: "viewport",
278
275
  content: "width=device-width, initial-scale=1.0"
279
- }), /* @__PURE__ */ React.createElement("title", null, "Error"), /* @__PURE__ */ React.createElement("style", {
276
+ }), /* @__PURE__ */ React__default.default.createElement("title", null, "Error"), /* @__PURE__ */ React__default.default.createElement("style", {
280
277
  dangerouslySetInnerHTML: {
281
278
  __html: `
282
279
  body {
@@ -303,9 +300,9 @@ function ErrorPageProduction() {
303
300
  }
304
301
  `
305
302
  }
306
- })), /* @__PURE__ */ React.createElement("body", null, /* @__PURE__ */ React.createElement("div", {
303
+ })), /* @__PURE__ */ React__default.default.createElement("body", null, /* @__PURE__ */ React__default.default.createElement("div", {
307
304
  className: "error-container"
308
- }, /* @__PURE__ */ React.createElement("h1", null, "500"), /* @__PURE__ */ React.createElement("p", null, "Internal Server Error"), /* @__PURE__ */ React.createElement("p", null, "Something went wrong while rendering this page."))));
305
+ }, /* @__PURE__ */ React__default.default.createElement("h1", null, "500"), /* @__PURE__ */ React__default.default.createElement("p", null, "Internal Server Error"), /* @__PURE__ */ React__default.default.createElement("p", null, "Something went wrong while rendering this page."))));
309
306
  }
310
307
  __name(ErrorPageProduction, "ErrorPageProduction");
311
308
 
@@ -364,7 +361,7 @@ exports.StreamingErrorHandler = class _StreamingErrorHandler {
364
361
  */
365
362
  renderDevelopmentErrorPage(error, viewPath, phase) {
366
363
  const ErrorComponent = this.errorPageDevelopment || ErrorPageDevelopment;
367
- const element = react.createElement(ErrorComponent, {
364
+ const element = React.createElement(ErrorComponent, {
368
365
  error,
369
366
  viewPath,
370
367
  phase
@@ -376,7 +373,7 @@ exports.StreamingErrorHandler = class _StreamingErrorHandler {
376
373
  */
377
374
  renderProductionErrorPage() {
378
375
  const ErrorComponent = this.errorPageProduction || ErrorPageProduction;
379
- const element = react.createElement(ErrorComponent);
376
+ const element = React.createElement(ErrorComponent);
380
377
  return "<!DOCTYPE html>\n" + server.renderToStaticMarkup(element);
381
378
  }
382
379
  };
@@ -677,60 +674,79 @@ exports.RenderService = class _RenderService {
677
674
  async renderToStream(viewComponent, data = {}, res, head) {
678
675
  const startTime = Date.now();
679
676
  let shellReadyTime = 0;
680
- try {
681
- let template = this.template;
682
- if (this.vite) {
683
- template = await this.vite.transformIndexHtml("/", template);
684
- }
685
- const templateParts = this.templateParser.parseTemplate(template);
686
- let renderModule;
687
- if (this.vite) {
688
- renderModule = await this.vite.ssrLoadModule(this.entryServerPath);
689
- } else {
690
- if (this.serverManifest) {
691
- const manifestEntry = Object.entries(this.serverManifest).find(([key, value]) => value.isEntry && key.includes("entry-server"));
692
- if (manifestEntry) {
693
- const [, entry] = manifestEntry;
694
- const serverPath = path.join(process.cwd(), "dist/server", entry.file);
695
- renderModule = await import(serverPath);
677
+ return new Promise((resolve, reject) => {
678
+ const executeStream = /* @__PURE__ */ __name(async () => {
679
+ let template = this.template;
680
+ if (this.vite) {
681
+ template = await this.vite.transformIndexHtml("/", template);
682
+ }
683
+ const templateParts = this.templateParser.parseTemplate(template);
684
+ let renderModule;
685
+ if (this.vite) {
686
+ renderModule = await this.vite.ssrLoadModule(this.entryServerPath);
687
+ } else {
688
+ if (this.serverManifest) {
689
+ const manifestEntry = Object.entries(this.serverManifest).find(([key, value]) => value.isEntry && key.includes("entry-server"));
690
+ if (manifestEntry) {
691
+ const [, entry] = manifestEntry;
692
+ const serverPath = path.join(process.cwd(), "dist/server", entry.file);
693
+ renderModule = await import(serverPath);
694
+ } else {
695
+ throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
696
+ }
696
697
  } else {
697
698
  throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
698
699
  }
699
- } else {
700
- throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
701
700
  }
702
- }
703
- const { data: pageData, __context: context, __layouts: layouts } = data;
704
- const componentName = viewComponent.displayName || viewComponent.name || "Component";
705
- const inlineScripts = this.templateParser.buildInlineScripts(pageData, context, componentName, layouts);
706
- const clientScript = this.templateParser.getClientScriptTag(this.isDevelopment, this.manifest);
707
- const stylesheetTags = this.templateParser.getStylesheetTags(this.isDevelopment, this.manifest);
708
- const headTags = this.templateParser.buildHeadTags(head);
709
- let didError = false;
710
- const { pipe, abort } = renderModule.renderComponentStream(viewComponent, data, {
711
- onShellReady: /* @__PURE__ */ __name(() => {
712
- shellReadyTime = Date.now();
713
- res.statusCode = didError ? 500 : 200;
714
- res.setHeader("Content-Type", "text/html; charset=utf-8");
715
- let htmlStart = templateParts.htmlStart;
716
- htmlStart = htmlStart.replace("<!--styles-->", stylesheetTags);
717
- htmlStart = htmlStart.replace("<!--head-meta-->", headTags);
718
- res.write(htmlStart);
719
- res.write(templateParts.rootStart);
720
- pipe(res);
721
- if (this.isDevelopment) {
722
- const ttfb = shellReadyTime - startTime;
723
- this.logger.log(`[SSR] ${componentName} shell ready in ${ttfb}ms (stream mode - TTFB)`);
701
+ const { data: pageData, __context: context, __layouts: layouts } = data;
702
+ const componentName = viewComponent.displayName || viewComponent.name || "Component";
703
+ const inlineScripts = this.templateParser.buildInlineScripts(pageData, context, componentName, layouts);
704
+ const clientScript = this.templateParser.getClientScriptTag(this.isDevelopment, this.manifest);
705
+ const stylesheetTags = this.templateParser.getStylesheetTags(this.isDevelopment, this.manifest);
706
+ const headTags = this.templateParser.buildHeadTags(head);
707
+ let didError = false;
708
+ let shellErrorOccurred = false;
709
+ const { PassThrough } = await import('stream');
710
+ const reactStream = new PassThrough();
711
+ let allReadyFired = false;
712
+ const { pipe, abort } = renderModule.renderComponentStream(viewComponent, data, {
713
+ onShellReady: /* @__PURE__ */ __name(() => {
714
+ shellReadyTime = Date.now();
715
+ if (!res.headersSent) {
716
+ res.statusCode = didError ? 500 : 200;
717
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
718
+ }
719
+ let htmlStart = templateParts.htmlStart;
720
+ htmlStart = htmlStart.replace("<!--styles-->", stylesheetTags);
721
+ htmlStart = htmlStart.replace("<!--head-meta-->", headTags);
722
+ res.write(htmlStart);
723
+ res.write(templateParts.rootStart);
724
+ pipe(reactStream);
725
+ reactStream.pipe(res, {
726
+ end: false
727
+ });
728
+ if (this.isDevelopment) {
729
+ const ttfb = shellReadyTime - startTime;
730
+ this.logger.log(`[SSR] ${componentName} shell ready in ${ttfb}ms (stream mode - TTFB)`);
731
+ }
732
+ }, "onShellReady"),
733
+ onShellError: /* @__PURE__ */ __name((error) => {
734
+ shellErrorOccurred = true;
735
+ this.streamingErrorHandler.handleShellError(error, res, componentName, this.isDevelopment);
736
+ resolve();
737
+ }, "onShellError"),
738
+ onError: /* @__PURE__ */ __name((error) => {
739
+ didError = true;
740
+ this.streamingErrorHandler.handleStreamError(error, componentName);
741
+ }, "onError"),
742
+ onAllReady: /* @__PURE__ */ __name(() => {
743
+ allReadyFired = true;
744
+ }, "onAllReady")
745
+ });
746
+ reactStream.on("end", () => {
747
+ if (shellErrorOccurred) {
748
+ return;
724
749
  }
725
- }, "onShellReady"),
726
- onShellError: /* @__PURE__ */ __name((error) => {
727
- this.streamingErrorHandler.handleShellError(error, res, componentName, this.isDevelopment);
728
- }, "onShellError"),
729
- onError: /* @__PURE__ */ __name((error) => {
730
- didError = true;
731
- this.streamingErrorHandler.handleStreamError(error, componentName);
732
- }, "onError"),
733
- onAllReady: /* @__PURE__ */ __name(() => {
734
750
  res.write(inlineScripts);
735
751
  res.write(clientScript);
736
752
  res.write(templateParts.rootEnd);
@@ -739,17 +755,25 @@ exports.RenderService = class _RenderService {
739
755
  if (this.isDevelopment) {
740
756
  const totalTime = Date.now() - startTime;
741
757
  const streamTime = Date.now() - shellReadyTime;
742
- this.logger.log(`[SSR] ${componentName} streaming complete in ${totalTime}ms total (${streamTime}ms streaming)`);
758
+ const viaAllReady = allReadyFired ? " (onAllReady fired)" : " (onAllReady never fired)";
759
+ this.logger.log(`[SSR] ${componentName} streaming complete in ${totalTime}ms total (${streamTime}ms streaming)${viaAllReady}`);
743
760
  }
744
- }, "onAllReady")
761
+ resolve();
762
+ });
763
+ reactStream.on("error", (error) => {
764
+ reject(error);
765
+ });
766
+ res.on("close", () => {
767
+ abort();
768
+ resolve();
769
+ });
770
+ }, "executeStream");
771
+ executeStream().catch((error) => {
772
+ const componentName = typeof viewComponent === "function" ? viewComponent.name : String(viewComponent);
773
+ this.streamingErrorHandler.handleShellError(error, res, componentName, this.isDevelopment);
774
+ resolve();
745
775
  });
746
- res.on("close", () => {
747
- abort();
748
- });
749
- } catch (error) {
750
- const componentName = typeof viewComponent === "function" ? viewComponent.name : String(viewComponent);
751
- this.streamingErrorHandler.handleShellError(error, res, componentName, this.isDevelopment);
752
- }
776
+ });
753
777
  }
754
778
  };
755
779
  exports.RenderService = _ts_decorate3([
@@ -865,6 +889,9 @@ exports.RenderInterceptor = class RenderInterceptor {
865
889
  const httpContext = context.switchToHttp();
866
890
  const request = httpContext.getRequest();
867
891
  const response = httpContext.getResponse();
892
+ if (typeof data === "string") {
893
+ return data;
894
+ }
868
895
  const renderContext = {
869
896
  url: request.url,
870
897
  path: request.path,
@@ -955,11 +982,23 @@ var ViteInitializerService = class _ViteInitializerService {
955
982
  viteMode;
956
983
  vitePort;
957
984
  viteServer = null;
985
+ isShuttingDown = false;
958
986
  constructor(renderService, httpAdapterHost, viteConfig) {
959
987
  this.renderService = renderService;
960
988
  this.httpAdapterHost = httpAdapterHost;
961
989
  this.viteMode = viteConfig?.mode || "embedded";
962
990
  this.vitePort = viteConfig?.port || 5173;
991
+ this.registerSignalHandlers();
992
+ }
993
+ registerSignalHandlers() {
994
+ const cleanup = /* @__PURE__ */ __name(async (signal) => {
995
+ if (this.isShuttingDown) return;
996
+ this.isShuttingDown = true;
997
+ this.logger.log(`Received ${signal}, closing Vite server...`);
998
+ await this.closeViteServer();
999
+ }, "cleanup");
1000
+ process.once("SIGTERM", () => cleanup("SIGTERM"));
1001
+ process.once("SIGINT", () => cleanup("SIGINT"));
963
1002
  }
964
1003
  async onModuleInit() {
965
1004
  const isDevelopment = process.env.NODE_ENV !== "production";
@@ -1048,9 +1087,23 @@ var ViteInitializerService = class _ViteInitializerService {
1048
1087
  * This prevents port conflicts on hot reload
1049
1088
  */
1050
1089
  async onModuleDestroy() {
1090
+ await this.closeViteServer();
1091
+ }
1092
+ /**
1093
+ * Cleanup: Close Vite server on application shutdown
1094
+ * Belt-and-suspenders approach with onModuleDestroy
1095
+ */
1096
+ async onApplicationShutdown() {
1097
+ await this.closeViteServer();
1098
+ }
1099
+ async closeViteServer() {
1100
+ if (this.isShuttingDown && !this.viteServer) return;
1101
+ this.isShuttingDown = true;
1051
1102
  if (this.viteServer) {
1052
1103
  try {
1104
+ this.renderService.setViteServer(null);
1053
1105
  await this.viteServer.close();
1106
+ this.viteServer = null;
1054
1107
  this.logger.log("\u2713 Vite server closed");
1055
1108
  } catch (error) {
1056
1109
  this.logger.warn(`Failed to close Vite server: ${error.message}`);