@harpy-js/core 0.5.5 → 0.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/core/__tests__/redirect-logic.spec.d.ts +0 -0
  2. package/dist/core/__tests__/redirect-logic.spec.js +157 -0
  3. package/dist/core/app-setup.d.ts +6 -0
  4. package/dist/core/app-setup.js +66 -1
  5. package/dist/core/error-pages/default-401.d.ts +6 -0
  6. package/dist/core/error-pages/default-401.js +21 -0
  7. package/dist/core/error-pages/default-403.d.ts +6 -0
  8. package/dist/core/error-pages/default-403.js +21 -0
  9. package/dist/core/error-pages/default-404.d.ts +7 -0
  10. package/dist/core/error-pages/default-404.js +26 -0
  11. package/dist/core/error-pages/default-500.d.ts +7 -0
  12. package/dist/core/error-pages/default-500.js +28 -0
  13. package/dist/core/error-pages/error-layout.d.ts +6 -0
  14. package/dist/core/error-pages/error-layout.js +17 -0
  15. package/dist/core/jsx-exception.filter.d.ts +14 -0
  16. package/dist/core/jsx-exception.filter.js +115 -0
  17. package/dist/core/lazy-route-loader.service.d.ts +28 -0
  18. package/dist/core/lazy-route-loader.service.js +79 -0
  19. package/dist/core/lazy-routes.module.d.ts +2 -0
  20. package/dist/core/lazy-routes.module.js +21 -0
  21. package/dist/decorators/lazy-route.decorator.d.ts +12 -0
  22. package/dist/decorators/lazy-route.decorator.js +22 -0
  23. package/dist/index.d.ts +7 -0
  24. package/dist/index.js +13 -1
  25. package/package.json +1 -1
  26. package/src/core/__tests__/redirect-logic.spec.ts +200 -0
  27. package/src/core/app-setup.js.map +1 -0
  28. package/src/core/app-setup.ts +111 -1
  29. package/src/core/error-pages/default-401.js.map +1 -0
  30. package/src/core/error-pages/default-401.tsx +43 -0
  31. package/src/core/error-pages/default-403.js.map +1 -0
  32. package/src/core/error-pages/default-403.tsx +43 -0
  33. package/src/core/error-pages/default-404.js.map +1 -0
  34. package/src/core/error-pages/default-404.tsx +54 -0
  35. package/src/core/error-pages/default-500.js.map +1 -0
  36. package/src/core/error-pages/default-500.tsx +59 -0
  37. package/src/core/error-pages/error-layout.js.map +1 -0
  38. package/src/core/error-pages/error-layout.tsx +30 -0
  39. package/src/core/hydration-manifest.js.map +1 -0
  40. package/src/core/hydration.js.map +1 -0
  41. package/src/core/jsx-exception.filter.js.map +1 -0
  42. package/src/core/jsx-exception.filter.ts +130 -0
  43. package/src/core/jsx.engine.js.map +1 -0
  44. package/src/core/live-reload.controller.js.map +1 -0
  45. package/src/core/static-assets.controller.js.map +1 -0
  46. package/src/decorators/jsx.decorator.js.map +1 -0
  47. package/src/index.ts +9 -0
  48. package/src/types/jsx.types.js.map +1 -0
File without changes
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+ describe('Canonical Redirect Logic', () => {
3
+ function shouldRedirect(options) {
4
+ const { host, protocol, mainDomain, enforceHttps = true, redirectWww = true } = options;
5
+ const normalizedHost = host.replace(/:\d+$/, '');
6
+ if (normalizedHost === 'localhost' || normalizedHost === '127.0.0.1' || normalizedHost === '0.0.0.0') {
7
+ return { shouldRedirect: false };
8
+ }
9
+ const proto = protocol;
10
+ const isHttp = enforceHttps && proto !== 'https';
11
+ const isWww = redirectWww && normalizedHost.startsWith('www.');
12
+ const hostMismatch = mainDomain && normalizedHost !== mainDomain && normalizedHost !== `www.${mainDomain}`;
13
+ if (isHttp || isWww || hostMismatch) {
14
+ const targetHost = mainDomain ? mainDomain : normalizedHost.replace(/^www\./, '');
15
+ const targetProto = enforceHttps ? 'https' : proto;
16
+ const targetUrl = `${targetProto}://${targetHost}/`;
17
+ return { shouldRedirect: true, targetUrl };
18
+ }
19
+ return { shouldRedirect: false };
20
+ }
21
+ describe('HTTP to HTTPS redirect', () => {
22
+ it('should redirect http://harpyjs.org to https://harpyjs.org', () => {
23
+ const result = shouldRedirect({
24
+ host: 'harpyjs.org',
25
+ protocol: 'http',
26
+ mainDomain: 'harpyjs.org',
27
+ enforceHttps: true,
28
+ redirectWww: true,
29
+ });
30
+ expect(result.shouldRedirect).toBe(true);
31
+ expect(result.targetUrl).toBe('https://harpyjs.org/');
32
+ });
33
+ it('should not redirect https://harpyjs.org', () => {
34
+ const result = shouldRedirect({
35
+ host: 'harpyjs.org',
36
+ protocol: 'https',
37
+ mainDomain: 'harpyjs.org',
38
+ enforceHttps: true,
39
+ redirectWww: true,
40
+ });
41
+ expect(result.shouldRedirect).toBe(false);
42
+ });
43
+ });
44
+ describe('WWW to non-WWW redirect', () => {
45
+ it('should redirect www.harpyjs.org to harpyjs.org', () => {
46
+ const result = shouldRedirect({
47
+ host: 'www.harpyjs.org',
48
+ protocol: 'https',
49
+ mainDomain: 'harpyjs.org',
50
+ enforceHttps: true,
51
+ redirectWww: true,
52
+ });
53
+ expect(result.shouldRedirect).toBe(true);
54
+ expect(result.targetUrl).toBe('https://harpyjs.org/');
55
+ });
56
+ it('should not redirect when redirectWww is false', () => {
57
+ const result = shouldRedirect({
58
+ host: 'www.harpyjs.org',
59
+ protocol: 'https',
60
+ mainDomain: 'harpyjs.org',
61
+ enforceHttps: true,
62
+ redirectWww: false,
63
+ });
64
+ expect(result.shouldRedirect).toBe(false);
65
+ });
66
+ });
67
+ describe('Combined redirects', () => {
68
+ it('should redirect http://www.harpyjs.org to https://harpyjs.org', () => {
69
+ const result = shouldRedirect({
70
+ host: 'www.harpyjs.org',
71
+ protocol: 'http',
72
+ mainDomain: 'harpyjs.org',
73
+ enforceHttps: true,
74
+ redirectWww: true,
75
+ });
76
+ expect(result.shouldRedirect).toBe(true);
77
+ expect(result.targetUrl).toBe('https://harpyjs.org/');
78
+ });
79
+ });
80
+ describe('Localhost exemption', () => {
81
+ it('should not redirect localhost', () => {
82
+ const result = shouldRedirect({
83
+ host: 'localhost',
84
+ protocol: 'http',
85
+ mainDomain: 'harpyjs.org',
86
+ enforceHttps: true,
87
+ redirectWww: true,
88
+ });
89
+ expect(result.shouldRedirect).toBe(false);
90
+ });
91
+ it('should not redirect 127.0.0.1', () => {
92
+ const result = shouldRedirect({
93
+ host: '127.0.0.1',
94
+ protocol: 'http',
95
+ mainDomain: 'harpyjs.org',
96
+ enforceHttps: true,
97
+ redirectWww: true,
98
+ });
99
+ expect(result.shouldRedirect).toBe(false);
100
+ });
101
+ it('should not redirect localhost:3000', () => {
102
+ const result = shouldRedirect({
103
+ host: 'localhost:3000',
104
+ protocol: 'http',
105
+ mainDomain: 'harpyjs.org',
106
+ enforceHttps: true,
107
+ redirectWww: true,
108
+ });
109
+ expect(result.shouldRedirect).toBe(false);
110
+ });
111
+ });
112
+ describe('Domain enforcement', () => {
113
+ it('should redirect different domain to mainDomain', () => {
114
+ const result = shouldRedirect({
115
+ host: 'example.com',
116
+ protocol: 'https',
117
+ mainDomain: 'harpyjs.org',
118
+ enforceHttps: true,
119
+ redirectWww: true,
120
+ });
121
+ expect(result.shouldRedirect).toBe(true);
122
+ expect(result.targetUrl).toBe('https://harpyjs.org/');
123
+ });
124
+ it('should not redirect when host matches mainDomain', () => {
125
+ const result = shouldRedirect({
126
+ host: 'harpyjs.org',
127
+ protocol: 'https',
128
+ mainDomain: 'harpyjs.org',
129
+ enforceHttps: true,
130
+ redirectWww: true,
131
+ });
132
+ expect(result.shouldRedirect).toBe(false);
133
+ });
134
+ });
135
+ describe('Configuration variations', () => {
136
+ it('should not redirect http when enforceHttps is false', () => {
137
+ const result = shouldRedirect({
138
+ host: 'harpyjs.org',
139
+ protocol: 'http',
140
+ mainDomain: 'harpyjs.org',
141
+ enforceHttps: false,
142
+ redirectWww: true,
143
+ });
144
+ expect(result.shouldRedirect).toBe(false);
145
+ });
146
+ it('should work without mainDomain specified', () => {
147
+ const result = shouldRedirect({
148
+ host: 'www.example.com',
149
+ protocol: 'https',
150
+ enforceHttps: true,
151
+ redirectWww: true,
152
+ });
153
+ expect(result.shouldRedirect).toBe(true);
154
+ expect(result.targetUrl).toBe('https://example.com/');
155
+ });
156
+ });
157
+ });
@@ -1,8 +1,14 @@
1
1
  import type { NestFastifyApplication } from "@nestjs/platform-fastify";
2
+ import { ErrorPagesConfig } from "./jsx-exception.filter";
2
3
  export interface HarpyAppOptions {
3
4
  layout?: any;
4
5
  distDir?: string;
5
6
  publicDir?: string;
7
+ errorPages?: ErrorPagesConfig;
8
+ enforceRedirects?: boolean;
9
+ mainDomain?: string;
10
+ enforceHttps?: boolean;
11
+ redirectWww?: boolean;
6
12
  }
7
13
  export declare function configureHarpyApp(app: NestFastifyApplication, opts?: HarpyAppOptions): Promise<void>;
8
14
  export declare function setupHarpyApp(app: NestFastifyApplication, opts?: HarpyAppOptions): Promise<void>;
@@ -32,6 +32,9 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
35
38
  Object.defineProperty(exports, "__esModule", { value: true });
36
39
  exports.configureHarpyApp = configureHarpyApp;
37
40
  exports.setupHarpyApp = setupHarpyApp;
@@ -51,12 +54,72 @@ catch (e) {
51
54
  fastifyCookie = undefined;
52
55
  }
53
56
  const jsx_engine_1 = require("./jsx.engine");
57
+ const jsx_exception_filter_1 = require("./jsx-exception.filter");
58
+ const react_1 = __importDefault(require("react"));
59
+ const server_1 = require("react-dom/server");
60
+ const default_404_1 = __importDefault(require("./error-pages/default-404"));
61
+ const error_layout_1 = __importDefault(require("./error-pages/error-layout"));
54
62
  async function configureHarpyApp(app, opts = {}) {
55
- const { layout, distDir = "dist", publicDir } = opts;
63
+ const { layout, distDir = "dist", publicDir, errorPages } = opts;
56
64
  if (layout) {
57
65
  (0, jsx_engine_1.withJsxEngine)(app, layout);
58
66
  }
59
67
  const fastify = app.getHttpAdapter().getInstance();
68
+ const { enforceRedirects = false, mainDomain, enforceHttps = true, redirectWww = true, } = opts;
69
+ if (enforceRedirects && (mainDomain || enforceHttps || redirectWww)) {
70
+ fastify.addHook('onRequest', (req, reply, done) => {
71
+ try {
72
+ const hostHeader = (req.headers && (req.headers.host || '')).toString();
73
+ const normalizedHost = hostHeader.replace(/:\d+$/, '');
74
+ if (normalizedHost === 'localhost' || normalizedHost === '127.0.0.1' || normalizedHost === '0.0.0.0') {
75
+ done();
76
+ return;
77
+ }
78
+ const forwardedProto = (req.headers['x-forwarded-proto'] || '').toString();
79
+ const proto = forwardedProto || (req.raw && req.raw.socket && req.raw.socket.encrypted ? 'https' : 'http');
80
+ const isHttp = enforceHttps && proto !== 'https';
81
+ const isWww = redirectWww && normalizedHost.startsWith('www.');
82
+ const hostMismatch = mainDomain && normalizedHost !== mainDomain && normalizedHost !== `www.${mainDomain}`;
83
+ if (isHttp || isWww || hostMismatch) {
84
+ const targetHost = mainDomain ? mainDomain : normalizedHost.replace(/^www\./, '');
85
+ const targetProto = enforceHttps ? 'https' : proto;
86
+ const targetUrl = `${targetProto}://${targetHost}${req.url}`;
87
+ reply.status(301).header('Cache-Control', 'public, max-age=31536000').redirect(targetUrl);
88
+ return;
89
+ }
90
+ }
91
+ catch (e) {
92
+ console.warn('[harpy-core] redirect hook error', e);
93
+ }
94
+ done();
95
+ });
96
+ }
97
+ const NotFoundComponent = errorPages?.["404"] || default_404_1.default;
98
+ fastify.setErrorHandler((error, request, reply) => {
99
+ if (error?.statusCode === 404 || reply.statusCode === 404) {
100
+ try {
101
+ const props = {
102
+ message: "Page Not Found",
103
+ path: request.url,
104
+ };
105
+ const errorPageContent = react_1.default.createElement(NotFoundComponent, props);
106
+ const wrappedInLayout = react_1.default.createElement(error_layout_1.default, {
107
+ title: "404 - Page Not Found",
108
+ children: errorPageContent,
109
+ });
110
+ const html = (0, server_1.renderToString)(wrappedInLayout);
111
+ void reply
112
+ .status(404)
113
+ .header("Content-Type", "text/html; charset=utf-8")
114
+ .send(`<!DOCTYPE html>${html}`);
115
+ return;
116
+ }
117
+ catch (renderError) {
118
+ console.error("Error rendering 404 page:", renderError);
119
+ }
120
+ }
121
+ throw error;
122
+ });
60
123
  if (fastifyCookie) {
61
124
  await fastify.register(fastifyCookie);
62
125
  }
@@ -82,6 +145,8 @@ async function configureHarpyApp(app, opts = {}) {
82
145
  else {
83
146
  console.warn("[harpy-core] optional dependency `@fastify/static` is not installed; static `dist` handler not registered.");
84
147
  }
148
+ const exceptionFilter = new jsx_exception_filter_1.JsxExceptionFilter(errorPages);
149
+ app.useGlobalFilters(exceptionFilter);
85
150
  }
86
151
  async function setupHarpyApp(app, opts = {}) {
87
152
  return configureHarpyApp(app, opts);
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ import type { JsxLayoutProps } from '../../types/jsx.types';
3
+ export interface UnauthorizedPageProps extends JsxLayoutProps {
4
+ message?: string;
5
+ }
6
+ export default function Default401Page({ message, }: UnauthorizedPageProps): React.JSX.Element;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.default = Default401Page;
7
+ const react_1 = __importDefault(require("react"));
8
+ function Default401Page({ message = 'Unauthorized', }) {
9
+ return (react_1.default.createElement("div", { className: "min-h-screen bg-gradient-to-br from-orange-500 to-yellow-400 flex items-center justify-center p-6" },
10
+ react_1.default.createElement("div", { className: "max-w-2xl w-full text-center bg-white rounded-2xl shadow-2xl p-12" },
11
+ react_1.default.createElement("div", { className: "mb-8" },
12
+ react_1.default.createElement("div", { className: "inline-flex items-center justify-center w-28 h-28 bg-gradient-to-br from-orange-500 to-yellow-400 rounded-full shadow-lg mb-6" },
13
+ react_1.default.createElement("span", { className: "text-5xl" }, "\uD83D\uDD12"))),
14
+ react_1.default.createElement("h1", { className: "text-9xl font-extrabold text-transparent bg-clip-text bg-gradient-to-r from-orange-500 to-yellow-400 mb-4" }, "401"),
15
+ react_1.default.createElement("h2", { className: "text-3xl font-bold text-gray-900 mb-4" }, message),
16
+ react_1.default.createElement("p", { className: "text-lg text-gray-600 mb-8" }, "You need to be authenticated to access this resource."),
17
+ react_1.default.createElement("a", { href: "/login", className: "inline-block px-8 py-3 bg-gradient-to-r from-orange-500 to-yellow-400 text-white font-semibold rounded-lg shadow-md hover:shadow-lg transform hover:-translate-y-0.5 transition-all duration-200" }, "Go to Login"),
18
+ react_1.default.createElement("p", { className: "mt-12 text-sm text-gray-500" },
19
+ "Powered by ",
20
+ react_1.default.createElement("span", { className: "text-orange-600 font-semibold" }, "Harpy.js")))));
21
+ }
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ import type { JsxLayoutProps } from '../../types/jsx.types';
3
+ export interface ForbiddenPageProps extends JsxLayoutProps {
4
+ message?: string;
5
+ }
6
+ export default function Default403Page({ message, }: ForbiddenPageProps): React.JSX.Element;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.default = Default403Page;
7
+ const react_1 = __importDefault(require("react"));
8
+ function Default403Page({ message = 'Forbidden', }) {
9
+ return (react_1.default.createElement("div", { className: "min-h-screen bg-gradient-to-br from-red-600 to-orange-500 flex items-center justify-center p-6" },
10
+ react_1.default.createElement("div", { className: "max-w-2xl w-full text-center bg-white rounded-2xl shadow-2xl p-12" },
11
+ react_1.default.createElement("div", { className: "mb-8" },
12
+ react_1.default.createElement("div", { className: "inline-flex items-center justify-center w-28 h-28 bg-gradient-to-br from-red-600 to-orange-500 rounded-full shadow-lg mb-6" },
13
+ react_1.default.createElement("span", { className: "text-5xl" }, "\u26D4"))),
14
+ react_1.default.createElement("h1", { className: "text-9xl font-extrabold text-transparent bg-clip-text bg-gradient-to-r from-red-600 to-orange-500 mb-4" }, "403"),
15
+ react_1.default.createElement("h2", { className: "text-3xl font-bold text-gray-900 mb-4" }, message),
16
+ react_1.default.createElement("p", { className: "text-lg text-gray-600 mb-8" }, "You don't have permission to access this resource."),
17
+ react_1.default.createElement("a", { href: "/", className: "inline-block px-8 py-3 bg-gradient-to-r from-red-600 to-orange-500 text-white font-semibold rounded-lg shadow-md hover:shadow-lg transform hover:-translate-y-0.5 transition-all duration-200" }, "Go to Homepage"),
18
+ react_1.default.createElement("p", { className: "mt-12 text-sm text-gray-500" },
19
+ "Powered by ",
20
+ react_1.default.createElement("span", { className: "text-red-600 font-semibold" }, "Harpy.js")))));
21
+ }
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ import type { JsxLayoutProps } from '../../types/jsx.types';
3
+ export interface NotFoundPageProps extends JsxLayoutProps {
4
+ path?: string;
5
+ message?: string;
6
+ }
7
+ export default function Default404Page({ path, message, }: NotFoundPageProps): React.JSX.Element;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.default = Default404Page;
7
+ const react_1 = __importDefault(require("react"));
8
+ function Default404Page({ path, message = 'Page Not Found', }) {
9
+ return (react_1.default.createElement("div", { className: "min-h-screen bg-gradient-to-br from-purple-600 to-blue-600 flex items-center justify-center p-6" },
10
+ react_1.default.createElement("div", { className: "max-w-2xl w-full text-center bg-white rounded-2xl shadow-2xl p-12" },
11
+ react_1.default.createElement("div", { className: "mb-8" },
12
+ react_1.default.createElement("div", { className: "inline-flex items-center justify-center w-28 h-28 bg-gradient-to-br from-purple-600 to-blue-600 rounded-full shadow-lg mb-6" },
13
+ react_1.default.createElement("span", { className: "text-5xl font-bold text-white" }, "H"))),
14
+ react_1.default.createElement("h1", { className: "text-9xl font-extrabold text-transparent bg-clip-text bg-gradient-to-r from-purple-600 to-blue-600 mb-4" }, "404"),
15
+ react_1.default.createElement("h2", { className: "text-3xl font-bold text-gray-900 mb-4" }, message),
16
+ react_1.default.createElement("p", { className: "text-lg text-gray-600 mb-8" }, "The page you're looking for doesn't exist or has been moved."),
17
+ path && (react_1.default.createElement("div", { className: "bg-gray-100 border border-gray-300 rounded-lg p-4 mb-8" },
18
+ react_1.default.createElement("p", { className: "font-mono text-sm text-gray-700" },
19
+ react_1.default.createElement("span", { className: "text-gray-500" }, "Requested path:"),
20
+ ' ',
21
+ react_1.default.createElement("span", { className: "text-purple-600 font-semibold" }, path)))),
22
+ react_1.default.createElement("a", { href: "/", className: "inline-block px-8 py-3 bg-gradient-to-r from-purple-600 to-blue-600 text-white font-semibold rounded-lg shadow-md hover:shadow-lg transform hover:-translate-y-0.5 transition-all duration-200" }, "Go to Homepage"),
23
+ react_1.default.createElement("p", { className: "mt-12 text-sm text-gray-500" },
24
+ "Powered by ",
25
+ react_1.default.createElement("span", { className: "text-purple-600 font-semibold" }, "Harpy.js")))));
26
+ }
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ import type { JsxLayoutProps } from '../../types/jsx.types';
3
+ export interface ServerErrorPageProps extends JsxLayoutProps {
4
+ message?: string;
5
+ error?: string;
6
+ }
7
+ export default function Default500Page({ message, error, }: ServerErrorPageProps): React.JSX.Element;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.default = Default500Page;
7
+ const react_1 = __importDefault(require("react"));
8
+ function Default500Page({ message = 'Internal Server Error', error, }) {
9
+ return (react_1.default.createElement("div", { className: "min-h-screen bg-gradient-to-br from-red-500 to-pink-600 flex items-center justify-center p-6" },
10
+ react_1.default.createElement("div", { className: "max-w-2xl w-full text-center bg-white rounded-2xl shadow-2xl p-12" },
11
+ react_1.default.createElement("div", { className: "mb-8" },
12
+ react_1.default.createElement("div", { className: "inline-flex items-center justify-center w-28 h-28 bg-gradient-to-br from-red-500 to-pink-600 rounded-full shadow-lg mb-6" },
13
+ react_1.default.createElement("span", { className: "text-5xl font-bold text-white" }, "H"))),
14
+ react_1.default.createElement("h1", { className: "text-9xl font-extrabold text-transparent bg-clip-text bg-gradient-to-r from-red-500 to-pink-600 mb-4" }, "500"),
15
+ react_1.default.createElement("h2", { className: "text-3xl font-bold text-gray-900 mb-4" }, message),
16
+ react_1.default.createElement("p", { className: "text-lg text-gray-600 mb-8" }, "Something went wrong on our end. Please try again later."),
17
+ error && (react_1.default.createElement("div", { className: "bg-red-50 border-2 border-red-200 rounded-lg p-6 mb-8 text-left" },
18
+ react_1.default.createElement("div", { className: "flex items-start gap-3" },
19
+ react_1.default.createElement("svg", { className: "w-6 h-6 text-red-600 flex-shrink-0 mt-0.5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24" },
20
+ react_1.default.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" })),
21
+ react_1.default.createElement("div", { className: "flex-1" },
22
+ react_1.default.createElement("h3", { className: "text-sm font-semibold text-red-900 mb-2" }, "Error Details"),
23
+ react_1.default.createElement("pre", { className: "text-xs text-red-800 font-mono whitespace-pre-wrap break-words" }, error))))),
24
+ react_1.default.createElement("a", { href: "/", className: "inline-block px-8 py-3 bg-gradient-to-r from-red-500 to-pink-600 text-white font-semibold rounded-lg shadow-md hover:shadow-lg transform hover:-translate-y-0.5 transition-all duration-200" }, "Go to Homepage"),
25
+ react_1.default.createElement("p", { className: "mt-12 text-sm text-gray-500" },
26
+ "Powered by ",
27
+ react_1.default.createElement("span", { className: "text-red-600 font-semibold" }, "Harpy.js")))));
28
+ }
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ export interface ErrorLayoutProps {
3
+ children: React.ReactNode;
4
+ title?: string;
5
+ }
6
+ export default function ErrorLayout({ children, title, }: ErrorLayoutProps): React.JSX.Element;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.default = ErrorLayout;
7
+ const react_1 = __importDefault(require("react"));
8
+ function ErrorLayout({ children, title = 'Error', }) {
9
+ return (react_1.default.createElement("html", { lang: "en" },
10
+ react_1.default.createElement("head", null,
11
+ react_1.default.createElement("meta", { charSet: "utf-8" }),
12
+ react_1.default.createElement("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }),
13
+ react_1.default.createElement("title", null, title),
14
+ react_1.default.createElement("link", { rel: "stylesheet", href: "/styles/styles.css" }),
15
+ react_1.default.createElement("link", { rel: "stylesheet", href: "/assets/styles.css" })),
16
+ react_1.default.createElement("body", null, children)));
17
+ }
@@ -0,0 +1,14 @@
1
+ import { ExceptionFilter, ArgumentsHost } from '@nestjs/common';
2
+ import React from 'react';
3
+ export interface ErrorPagesConfig {
4
+ 404?: React.ComponentType<any>;
5
+ 500?: React.ComponentType<any>;
6
+ 401?: React.ComponentType<any>;
7
+ 403?: React.ComponentType<any>;
8
+ default?: React.ComponentType<any>;
9
+ }
10
+ export declare class JsxExceptionFilter implements ExceptionFilter {
11
+ private readonly errorPages;
12
+ constructor(errorPages?: ErrorPagesConfig);
13
+ catch(exception: unknown, host: ArgumentsHost): void;
14
+ }
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.JsxExceptionFilter = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const react_1 = __importDefault(require("react"));
18
+ const server_1 = require("react-dom/server");
19
+ const default_404_1 = __importDefault(require("./error-pages/default-404"));
20
+ const default_500_1 = __importDefault(require("./error-pages/default-500"));
21
+ const default_401_1 = __importDefault(require("./error-pages/default-401"));
22
+ const default_403_1 = __importDefault(require("./error-pages/default-403"));
23
+ const error_layout_1 = __importDefault(require("./error-pages/error-layout"));
24
+ let JsxExceptionFilter = class JsxExceptionFilter {
25
+ errorPages;
26
+ constructor(errorPages = {}) {
27
+ this.errorPages = errorPages;
28
+ }
29
+ catch(exception, host) {
30
+ const ctx = host.switchToHttp();
31
+ const reply = ctx.getResponse();
32
+ const request = ctx.getRequest();
33
+ let status;
34
+ let message;
35
+ let error;
36
+ if (exception instanceof common_1.HttpException) {
37
+ status = exception.getStatus();
38
+ const response = exception.getResponse();
39
+ if (typeof response === 'string') {
40
+ message = response;
41
+ }
42
+ else if (typeof response === 'object' && response !== null) {
43
+ message =
44
+ response.message || response.error || 'An error occurred';
45
+ error = response.error;
46
+ }
47
+ else {
48
+ message = 'An error occurred';
49
+ }
50
+ }
51
+ else if (exception instanceof Error) {
52
+ status = common_1.HttpStatus.INTERNAL_SERVER_ERROR;
53
+ message = 'Internal Server Error';
54
+ error = process.env.NODE_ENV === 'development' ? exception.message : undefined;
55
+ }
56
+ else {
57
+ status = common_1.HttpStatus.INTERNAL_SERVER_ERROR;
58
+ message = 'Unknown error occurred';
59
+ }
60
+ let ErrorComponent;
61
+ switch (status) {
62
+ case common_1.HttpStatus.NOT_FOUND:
63
+ ErrorComponent = this.errorPages[404] || default_404_1.default;
64
+ break;
65
+ case common_1.HttpStatus.UNAUTHORIZED:
66
+ ErrorComponent = this.errorPages[401] || default_401_1.default;
67
+ break;
68
+ case common_1.HttpStatus.FORBIDDEN:
69
+ ErrorComponent = this.errorPages[403] || default_403_1.default;
70
+ break;
71
+ case common_1.HttpStatus.INTERNAL_SERVER_ERROR:
72
+ ErrorComponent = this.errorPages[500] || default_500_1.default;
73
+ break;
74
+ default:
75
+ ErrorComponent = this.errorPages.default || this.errorPages[500] || default_500_1.default;
76
+ }
77
+ const props = {
78
+ message,
79
+ error,
80
+ path: request.url,
81
+ };
82
+ const titleMap = {
83
+ 404: '404 - Page Not Found',
84
+ 401: '401 - Unauthorized',
85
+ 403: '403 - Forbidden',
86
+ 500: '500 - Internal Server Error',
87
+ };
88
+ const title = titleMap[status] || `${status} - Error`;
89
+ try {
90
+ const errorPageContent = react_1.default.createElement(ErrorComponent, props);
91
+ const wrappedInLayout = react_1.default.createElement(error_layout_1.default, {
92
+ title,
93
+ children: errorPageContent,
94
+ });
95
+ const html = (0, server_1.renderToString)(wrappedInLayout);
96
+ void reply
97
+ .status(status)
98
+ .header('Content-Type', 'text/html; charset=utf-8')
99
+ .send(`<!DOCTYPE html>${html}`);
100
+ }
101
+ catch (renderError) {
102
+ console.error('Error rendering JSX error page:', renderError);
103
+ void reply.status(status).send({
104
+ statusCode: status,
105
+ message,
106
+ error,
107
+ });
108
+ }
109
+ }
110
+ };
111
+ exports.JsxExceptionFilter = JsxExceptionFilter;
112
+ exports.JsxExceptionFilter = JsxExceptionFilter = __decorate([
113
+ (0, common_1.Catch)(),
114
+ __metadata("design:paramtypes", [Object])
115
+ ], JsxExceptionFilter);
@@ -0,0 +1,28 @@
1
+ import { Type } from '@nestjs/common';
2
+ import { LazyModuleLoader } from '@nestjs/core';
3
+ import type { FastifyRequest, FastifyReply } from 'fastify';
4
+ export interface LazyRouteConfig {
5
+ id: string;
6
+ path: string;
7
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
8
+ moduleLoader: () => Promise<Type<any>>;
9
+ controllerLoader: () => Promise<Type<any>>;
10
+ handlerMethod: string;
11
+ }
12
+ export declare class LazyRouteLoaderService {
13
+ private readonly lazyModuleLoader;
14
+ private readonly logger;
15
+ private readonly loadedModules;
16
+ private readonly registeredRoutes;
17
+ constructor(lazyModuleLoader: LazyModuleLoader);
18
+ registerLazyRoute(config: LazyRouteConfig): void;
19
+ getRegisteredRoutes(): LazyRouteConfig[];
20
+ handleLazyRoute(config: LazyRouteConfig, req: FastifyRequest, reply: FastifyReply): Promise<any>;
21
+ isModuleLoaded(moduleId: string): boolean;
22
+ getStatistics(): {
23
+ totalRegistered: number;
24
+ totalLoaded: number;
25
+ loadedModules: string[];
26
+ registeredRoutes: string[];
27
+ };
28
+ }