@hazeljs/core 0.2.0-beta.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/README.md +522 -0
- package/dist/__tests__/container.test.d.ts +2 -0
- package/dist/__tests__/container.test.d.ts.map +1 -0
- package/dist/__tests__/container.test.js +454 -0
- package/dist/__tests__/decorators.test.d.ts +2 -0
- package/dist/__tests__/decorators.test.d.ts.map +1 -0
- package/dist/__tests__/decorators.test.js +693 -0
- package/dist/__tests__/errors/http.error.test.d.ts +2 -0
- package/dist/__tests__/errors/http.error.test.d.ts.map +1 -0
- package/dist/__tests__/errors/http.error.test.js +117 -0
- package/dist/__tests__/filters/exception-filter.test.d.ts +2 -0
- package/dist/__tests__/filters/exception-filter.test.d.ts.map +1 -0
- package/dist/__tests__/filters/exception-filter.test.js +135 -0
- package/dist/__tests__/filters/http-exception.filter.test.d.ts +2 -0
- package/dist/__tests__/filters/http-exception.filter.test.d.ts.map +1 -0
- package/dist/__tests__/filters/http-exception.filter.test.js +119 -0
- package/dist/__tests__/hazel-app.test.d.ts +2 -0
- package/dist/__tests__/hazel-app.test.d.ts.map +1 -0
- package/dist/__tests__/hazel-app.test.js +682 -0
- package/dist/__tests__/hazel-module.test.d.ts +2 -0
- package/dist/__tests__/hazel-module.test.d.ts.map +1 -0
- package/dist/__tests__/hazel-module.test.js +408 -0
- package/dist/__tests__/hazel-response.test.d.ts +2 -0
- package/dist/__tests__/hazel-response.test.d.ts.map +1 -0
- package/dist/__tests__/hazel-response.test.js +138 -0
- package/dist/__tests__/health.test.d.ts +2 -0
- package/dist/__tests__/health.test.d.ts.map +1 -0
- package/dist/__tests__/health.test.js +147 -0
- package/dist/__tests__/index.test.d.ts +2 -0
- package/dist/__tests__/index.test.d.ts.map +1 -0
- package/dist/__tests__/index.test.js +239 -0
- package/dist/__tests__/interceptors/interceptor.test.d.ts +2 -0
- package/dist/__tests__/interceptors/interceptor.test.d.ts.map +1 -0
- package/dist/__tests__/interceptors/interceptor.test.js +166 -0
- package/dist/__tests__/logger.test.d.ts +2 -0
- package/dist/__tests__/logger.test.d.ts.map +1 -0
- package/dist/__tests__/logger.test.js +141 -0
- package/dist/__tests__/middleware/cors.test.d.ts +2 -0
- package/dist/__tests__/middleware/cors.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/cors.test.js +129 -0
- package/dist/__tests__/middleware/csrf.test.d.ts +2 -0
- package/dist/__tests__/middleware/csrf.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/csrf.test.js +247 -0
- package/dist/__tests__/middleware/global-middleware.test.d.ts +2 -0
- package/dist/__tests__/middleware/global-middleware.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/global-middleware.test.js +259 -0
- package/dist/__tests__/middleware/rate-limit.test.d.ts +2 -0
- package/dist/__tests__/middleware/rate-limit.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/rate-limit.test.js +264 -0
- package/dist/__tests__/middleware/security-headers.test.d.ts +2 -0
- package/dist/__tests__/middleware/security-headers.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/security-headers.test.js +229 -0
- package/dist/__tests__/middleware/timeout.test.d.ts +2 -0
- package/dist/__tests__/middleware/timeout.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/timeout.test.js +132 -0
- package/dist/__tests__/middleware.test.d.ts +2 -0
- package/dist/__tests__/middleware.test.d.ts.map +1 -0
- package/dist/__tests__/middleware.test.js +180 -0
- package/dist/__tests__/pipes/pipe.test.d.ts +2 -0
- package/dist/__tests__/pipes/pipe.test.d.ts.map +1 -0
- package/dist/__tests__/pipes/pipe.test.js +245 -0
- package/dist/__tests__/pipes/validation.pipe.test.d.ts +2 -0
- package/dist/__tests__/pipes/validation.pipe.test.d.ts.map +1 -0
- package/dist/__tests__/pipes/validation.pipe.test.js +297 -0
- package/dist/__tests__/request-parser.test.d.ts +2 -0
- package/dist/__tests__/request-parser.test.d.ts.map +1 -0
- package/dist/__tests__/request-parser.test.js +182 -0
- package/dist/__tests__/router.test.d.ts +2 -0
- package/dist/__tests__/router.test.d.ts.map +1 -0
- package/dist/__tests__/router.test.js +680 -0
- package/dist/__tests__/routing/route-matcher.test.d.ts +2 -0
- package/dist/__tests__/routing/route-matcher.test.d.ts.map +1 -0
- package/dist/__tests__/routing/route-matcher.test.js +219 -0
- package/dist/__tests__/routing/version.decorator.test.d.ts +2 -0
- package/dist/__tests__/routing/version.decorator.test.d.ts.map +1 -0
- package/dist/__tests__/routing/version.decorator.test.js +298 -0
- package/dist/__tests__/service.test.d.ts +2 -0
- package/dist/__tests__/service.test.d.ts.map +1 -0
- package/dist/__tests__/service.test.js +121 -0
- package/dist/__tests__/shutdown.test.d.ts +2 -0
- package/dist/__tests__/shutdown.test.d.ts.map +1 -0
- package/dist/__tests__/shutdown.test.js +250 -0
- package/dist/__tests__/testing/testing.module.test.d.ts +2 -0
- package/dist/__tests__/testing/testing.module.test.d.ts.map +1 -0
- package/dist/__tests__/testing/testing.module.test.js +370 -0
- package/dist/__tests__/upload/file-upload.test.d.ts +2 -0
- package/dist/__tests__/upload/file-upload.test.d.ts.map +1 -0
- package/dist/__tests__/upload/file-upload.test.js +498 -0
- package/dist/__tests__/utils/sanitize.test.d.ts +2 -0
- package/dist/__tests__/utils/sanitize.test.d.ts.map +1 -0
- package/dist/__tests__/utils/sanitize.test.js +291 -0
- package/dist/__tests__/validator.test.d.ts +2 -0
- package/dist/__tests__/validator.test.d.ts.map +1 -0
- package/dist/__tests__/validator.test.js +300 -0
- package/dist/container.d.ts +80 -0
- package/dist/container.d.ts.map +1 -0
- package/dist/container.js +271 -0
- package/dist/decorators.d.ts +92 -0
- package/dist/decorators.d.ts.map +1 -0
- package/dist/decorators.js +343 -0
- package/dist/errors/http.error.d.ts +31 -0
- package/dist/errors/http.error.d.ts.map +1 -0
- package/dist/errors/http.error.js +62 -0
- package/dist/filters/exception-filter.d.ts +39 -0
- package/dist/filters/exception-filter.d.ts.map +1 -0
- package/dist/filters/exception-filter.js +38 -0
- package/dist/filters/http-exception.filter.d.ts +9 -0
- package/dist/filters/http-exception.filter.d.ts.map +1 -0
- package/dist/filters/http-exception.filter.js +42 -0
- package/dist/hazel-app.d.ts +78 -0
- package/dist/hazel-app.d.ts.map +1 -0
- package/dist/hazel-app.js +453 -0
- package/dist/hazel-module.d.ts +20 -0
- package/dist/hazel-module.d.ts.map +1 -0
- package/dist/hazel-module.js +109 -0
- package/dist/hazel-response.d.ts +20 -0
- package/dist/hazel-response.d.ts.map +1 -0
- package/dist/hazel-response.js +68 -0
- package/dist/health.d.ts +73 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +174 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +140 -0
- package/dist/interceptors/interceptor.d.ts +22 -0
- package/dist/interceptors/interceptor.d.ts.map +1 -0
- package/dist/interceptors/interceptor.js +46 -0
- package/dist/logger.d.ts +8 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +238 -0
- package/dist/middleware/cors.middleware.d.ts +44 -0
- package/dist/middleware/cors.middleware.d.ts.map +1 -0
- package/dist/middleware/cors.middleware.js +118 -0
- package/dist/middleware/csrf.middleware.d.ts +82 -0
- package/dist/middleware/csrf.middleware.d.ts.map +1 -0
- package/dist/middleware/csrf.middleware.js +183 -0
- package/dist/middleware/global-middleware.d.ts +111 -0
- package/dist/middleware/global-middleware.d.ts.map +1 -0
- package/dist/middleware/global-middleware.js +179 -0
- package/dist/middleware/rate-limit.middleware.d.ts +73 -0
- package/dist/middleware/rate-limit.middleware.d.ts.map +1 -0
- package/dist/middleware/rate-limit.middleware.js +124 -0
- package/dist/middleware/security-headers.middleware.d.ts +76 -0
- package/dist/middleware/security-headers.middleware.d.ts.map +1 -0
- package/dist/middleware/security-headers.middleware.js +123 -0
- package/dist/middleware/timeout.middleware.d.ts +25 -0
- package/dist/middleware/timeout.middleware.d.ts.map +1 -0
- package/dist/middleware/timeout.middleware.js +74 -0
- package/dist/middleware.d.ts +13 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +47 -0
- package/dist/pipes/pipe.d.ts +50 -0
- package/dist/pipes/pipe.d.ts.map +1 -0
- package/dist/pipes/pipe.js +96 -0
- package/dist/pipes/validation.pipe.d.ts +6 -0
- package/dist/pipes/validation.pipe.d.ts.map +1 -0
- package/dist/pipes/validation.pipe.js +61 -0
- package/dist/request-context.d.ts +17 -0
- package/dist/request-context.d.ts.map +1 -0
- package/dist/request-context.js +2 -0
- package/dist/request-parser.d.ts +7 -0
- package/dist/request-parser.d.ts.map +1 -0
- package/dist/request-parser.js +60 -0
- package/dist/router.d.ts +33 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +426 -0
- package/dist/routing/route-matcher.d.ts +39 -0
- package/dist/routing/route-matcher.d.ts.map +1 -0
- package/dist/routing/route-matcher.js +93 -0
- package/dist/routing/version.decorator.d.ts +36 -0
- package/dist/routing/version.decorator.d.ts.map +1 -0
- package/dist/routing/version.decorator.js +89 -0
- package/dist/service.d.ts +9 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +39 -0
- package/dist/shutdown.d.ts +32 -0
- package/dist/shutdown.d.ts.map +1 -0
- package/dist/shutdown.js +109 -0
- package/dist/testing/testing.module.d.ts +83 -0
- package/dist/testing/testing.module.d.ts.map +1 -0
- package/dist/testing/testing.module.js +164 -0
- package/dist/types.d.ts +76 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/upload/file-upload.d.ts +75 -0
- package/dist/upload/file-upload.d.ts.map +1 -0
- package/dist/upload/file-upload.js +261 -0
- package/dist/utils/sanitize.d.ts +45 -0
- package/dist/utils/sanitize.d.ts.map +1 -0
- package/dist/utils/sanitize.js +165 -0
- package/dist/validator.d.ts +7 -0
- package/dist/validator.d.ts.map +1 -0
- package/dist/validator.js +119 -0
- package/package.json +65 -0
package/dist/router.js
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
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.Router = void 0;
|
|
7
|
+
const middleware_1 = require("./middleware");
|
|
8
|
+
const pipe_1 = require("./pipes/pipe");
|
|
9
|
+
const http_error_1 = require("./errors/http.error");
|
|
10
|
+
require("reflect-metadata");
|
|
11
|
+
const logger_1 = __importDefault(require("./logger"));
|
|
12
|
+
const request_parser_1 = require("./request-parser");
|
|
13
|
+
const hazel_response_1 = require("./hazel-response");
|
|
14
|
+
const validation_pipe_1 = require("./pipes/validation.pipe");
|
|
15
|
+
const ROUTE_METADATA_KEY = 'hazel:routes';
|
|
16
|
+
const CONTROLLER_METADATA_KEY = 'hazel:controller';
|
|
17
|
+
const HTTP_CODE_METADATA_KEY = 'hazel:http-code';
|
|
18
|
+
const HEADER_METADATA_KEY = 'hazel:headers';
|
|
19
|
+
const REDIRECT_METADATA_KEY = 'hazel:redirect';
|
|
20
|
+
class Router {
|
|
21
|
+
constructor(container) {
|
|
22
|
+
this.container = container;
|
|
23
|
+
this.routes = new Map();
|
|
24
|
+
this.routesByMethod = new Map();
|
|
25
|
+
this.middlewareHandler = new middleware_1.MiddlewareHandler(container);
|
|
26
|
+
// Initialize route cache for common HTTP methods
|
|
27
|
+
this.routesByMethod.set('GET', new Map());
|
|
28
|
+
this.routesByMethod.set('POST', new Map());
|
|
29
|
+
this.routesByMethod.set('PUT', new Map());
|
|
30
|
+
this.routesByMethod.set('DELETE', new Map());
|
|
31
|
+
this.routesByMethod.set('PATCH', new Map());
|
|
32
|
+
}
|
|
33
|
+
registerController(controller) {
|
|
34
|
+
logger_1.default.info(`Registering controller: ${controller.name}`);
|
|
35
|
+
const controllerMetadata = Reflect.getMetadata(CONTROLLER_METADATA_KEY, controller) || {};
|
|
36
|
+
const routes = Reflect.getMetadata(ROUTE_METADATA_KEY, controller) || [];
|
|
37
|
+
logger_1.default.debug('Controller metadata:', controllerMetadata);
|
|
38
|
+
logger_1.default.debug('Found routes:', routes);
|
|
39
|
+
routes.forEach((route) => {
|
|
40
|
+
const { method, path, propertyKey } = route;
|
|
41
|
+
const basePath = controllerMetadata.path || '';
|
|
42
|
+
const routePath = path || '';
|
|
43
|
+
const fullPath = this.normalizePath(`${basePath}${routePath}`);
|
|
44
|
+
logger_1.default.info(`Registering route: ${method} ${fullPath} (handler: ${String(propertyKey)})`);
|
|
45
|
+
// Get parameter types from TypeScript metadata
|
|
46
|
+
const paramTypes = Reflect.getMetadata('design:paramtypes', controller.prototype, propertyKey) || [];
|
|
47
|
+
logger_1.default.debug('Parameter types:', paramTypes.map((t) => t?.name || 'undefined'));
|
|
48
|
+
// Get parameter injections
|
|
49
|
+
const injections = Reflect.getMetadata('hazel:inject', controller, propertyKey) || [];
|
|
50
|
+
logger_1.default.debug('Parameter injections:', injections);
|
|
51
|
+
// Create a route handler for this specific route
|
|
52
|
+
// Pass the controller class, not instance, so it can be resolved per-request
|
|
53
|
+
const handler = this.createRouteHandler(controller, propertyKey);
|
|
54
|
+
const methodUpper = method.toUpperCase();
|
|
55
|
+
const routeKey = `${methodUpper} ${fullPath}`;
|
|
56
|
+
// Store in both maps for compatibility
|
|
57
|
+
this.routes.set(routeKey, [handler]);
|
|
58
|
+
// Store in method-specific cache for faster lookup
|
|
59
|
+
let methodRoutes = this.routesByMethod.get(methodUpper);
|
|
60
|
+
if (!methodRoutes) {
|
|
61
|
+
methodRoutes = new Map();
|
|
62
|
+
this.routesByMethod.set(methodUpper, methodRoutes);
|
|
63
|
+
}
|
|
64
|
+
methodRoutes.set(fullPath, [handler]);
|
|
65
|
+
// Log the route pattern for debugging
|
|
66
|
+
const pattern = this.createRoutePattern(fullPath);
|
|
67
|
+
logger_1.default.debug('Route pattern:', {
|
|
68
|
+
path: fullPath,
|
|
69
|
+
pattern: pattern.toString(),
|
|
70
|
+
pipes: route.pipes?.map((p) => p.type.name) || [],
|
|
71
|
+
paramTypes: paramTypes.map((t) => t?.name || 'undefined'),
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
logger_1.default.debug('All registered routes:');
|
|
75
|
+
this.routes.forEach((handlers, route) => {
|
|
76
|
+
logger_1.default.debug(`${route}`);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
async applyPipes(value, pipes, context) {
|
|
80
|
+
let result = value;
|
|
81
|
+
// Get route-specific pipes
|
|
82
|
+
const allPipes = pipes.map((p) => this.container.resolve(p.type));
|
|
83
|
+
for (const pipe of allPipes) {
|
|
84
|
+
try {
|
|
85
|
+
result = await pipe.transform(result, context);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
logger_1.default.error('Validation error:', error);
|
|
89
|
+
// Re-throw the error to be caught by the route handler
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
async applyInterceptors(interceptors, context, next) {
|
|
96
|
+
if (interceptors.length === 0) {
|
|
97
|
+
return next();
|
|
98
|
+
}
|
|
99
|
+
const [current, ...remaining] = interceptors;
|
|
100
|
+
const interceptor = this.container.resolve(current.type);
|
|
101
|
+
return interceptor.intercept(context, () => this.applyInterceptors(remaining, context, next));
|
|
102
|
+
}
|
|
103
|
+
createRouteHandler(controllerClass, methodName) {
|
|
104
|
+
return async (req, res, matchedContext) => {
|
|
105
|
+
// Generate a unique request ID for request-scoped providers
|
|
106
|
+
const requestId = `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
107
|
+
try {
|
|
108
|
+
logger_1.default.debug('=== Request Handler Start ===');
|
|
109
|
+
logger_1.default.debug(`Method: ${req.method}, URL: ${req.url}`);
|
|
110
|
+
logger_1.default.debug('Matched context:', matchedContext);
|
|
111
|
+
// Optimize: Only serialize for debug logging
|
|
112
|
+
if (logger_1.default.isDebugEnabled && logger_1.default.isDebugEnabled()) {
|
|
113
|
+
const safeBody = JSON.parse(JSON.stringify(req.body || {}));
|
|
114
|
+
logger_1.default.debug(`Request body: ${JSON.stringify(safeBody, null, 2)}`);
|
|
115
|
+
}
|
|
116
|
+
// Resolve controller instance per-request with requestId
|
|
117
|
+
const controller = this.container.resolve(controllerClass, requestId);
|
|
118
|
+
const routes = Reflect.getMetadata(ROUTE_METADATA_KEY, controllerClass) || [];
|
|
119
|
+
const route = routes.find((r) => r.propertyKey === methodName);
|
|
120
|
+
if (!route) {
|
|
121
|
+
throw new Error(`Route not found for method ${String(methodName)}`);
|
|
122
|
+
}
|
|
123
|
+
// Get parameter injections
|
|
124
|
+
const injections = Reflect.getMetadata('hazel:inject', controllerClass, methodName) || [];
|
|
125
|
+
// Use the matched context if available, otherwise create a new one
|
|
126
|
+
const context = matchedContext || {
|
|
127
|
+
params: req.params || {},
|
|
128
|
+
query: req.query || {},
|
|
129
|
+
body: req.body || {},
|
|
130
|
+
headers: Object.fromEntries(Object.entries(req.headers || {})
|
|
131
|
+
.filter(([key]) => !key.toLowerCase().includes('authorization'))
|
|
132
|
+
.map(([key, value]) => [key, String(value)])),
|
|
133
|
+
method: req.method || 'GET',
|
|
134
|
+
url: req.url || '/',
|
|
135
|
+
};
|
|
136
|
+
// Set DTO type from the first parameter that has a DTO type
|
|
137
|
+
for (const injection of injections) {
|
|
138
|
+
if (injection?.dtoType) {
|
|
139
|
+
context.dtoType = injection.dtoType;
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// Get pipes for this route
|
|
144
|
+
const routePipes = route.pipes || [];
|
|
145
|
+
// Prepare arguments for the controller method
|
|
146
|
+
const args = [];
|
|
147
|
+
for (let i = 0; i < injections.length; i++) {
|
|
148
|
+
const injection = injections[i];
|
|
149
|
+
if (typeof injection === 'string') {
|
|
150
|
+
// Handle @Body, @Param, @Query decorators
|
|
151
|
+
if (injection === 'body') {
|
|
152
|
+
args[i] = context.body;
|
|
153
|
+
}
|
|
154
|
+
else if (injection === 'param') {
|
|
155
|
+
args[i] = context.params;
|
|
156
|
+
}
|
|
157
|
+
else if (injection === 'query') {
|
|
158
|
+
args[i] = context.query;
|
|
159
|
+
}
|
|
160
|
+
else if (injection === 'headers') {
|
|
161
|
+
args[i] = context.headers;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else if (typeof injection === 'object' && injection !== null) {
|
|
165
|
+
if (injection.type === 'headers') {
|
|
166
|
+
// Handle @Headers decorator
|
|
167
|
+
const headerName = injection.name;
|
|
168
|
+
if (headerName) {
|
|
169
|
+
args[i] = context.headers[headerName.toLowerCase()];
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
args[i] = context.headers;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
else if (injection.type === 'response') {
|
|
176
|
+
// Handle @Res decorator
|
|
177
|
+
args[i] = new hazel_response_1.HazelExpressResponse(res);
|
|
178
|
+
}
|
|
179
|
+
else if (injection.type === 'body') {
|
|
180
|
+
// Handle @Body decorator with DTO type
|
|
181
|
+
if (injection.dtoType) {
|
|
182
|
+
context.dtoType = injection.dtoType;
|
|
183
|
+
logger_1.default.debug('Setting DTO type in context:', injection.dtoType.name);
|
|
184
|
+
}
|
|
185
|
+
args[i] = context.body;
|
|
186
|
+
}
|
|
187
|
+
else if (injection.type === 'param') {
|
|
188
|
+
// Handle @Param decorator with pipe
|
|
189
|
+
const paramName = injection.name;
|
|
190
|
+
const paramValue = matchedContext?.params[paramName] || context.params[paramName];
|
|
191
|
+
if (injection.pipe) {
|
|
192
|
+
const pipe = this.container.resolve(injection.pipe);
|
|
193
|
+
args[i] = await pipe.transform(paramValue, context);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
args[i] = paramValue;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
else if (injection.type === 'query') {
|
|
200
|
+
// Handle @Query decorator with pipe
|
|
201
|
+
const paramName = injection.name;
|
|
202
|
+
const queryValue = paramName ? context.query[paramName] : context.query;
|
|
203
|
+
if (injection.pipe) {
|
|
204
|
+
const pipe = this.container.resolve(injection.pipe);
|
|
205
|
+
args[i] = await pipe.transform(queryValue, context);
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
args[i] = queryValue;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// Apply ValidationPipe to the body if a DTO type is present
|
|
214
|
+
if (context.body && context.dtoType) {
|
|
215
|
+
logger_1.default.debug('Applying ValidationPipe with DTO type:', context.dtoType.name);
|
|
216
|
+
const validationPipe = this.container.resolve(validation_pipe_1.ValidationPipe);
|
|
217
|
+
context.body = await validationPipe.transform(context.body, context);
|
|
218
|
+
args[injections.findIndex((i) => typeof i === 'object' && i !== null && i.type === 'body')] = context.body;
|
|
219
|
+
}
|
|
220
|
+
// Apply other pipes to the body if it exists
|
|
221
|
+
if (context.body) {
|
|
222
|
+
context.body = await this.applyPipes(context.body, routePipes, context);
|
|
223
|
+
}
|
|
224
|
+
// Get the controller method
|
|
225
|
+
const method = controller[methodName];
|
|
226
|
+
// Execute the controller method with interceptors
|
|
227
|
+
const result = await this.applyInterceptors(Reflect.getMetadata('hazel:interceptors', controllerClass, methodName) || [], context, async () => {
|
|
228
|
+
return method.apply(controller, args);
|
|
229
|
+
});
|
|
230
|
+
// Apply @Redirect metadata
|
|
231
|
+
const redirectMeta = Reflect.getMetadata(REDIRECT_METADATA_KEY, controllerClass.prototype, methodName);
|
|
232
|
+
if (redirectMeta) {
|
|
233
|
+
res.status(redirectMeta.statusCode).setHeader('Location', redirectMeta.url);
|
|
234
|
+
res.end();
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
// Apply @Header metadata (response headers)
|
|
238
|
+
const headersMeta = Reflect.getMetadata(HEADER_METADATA_KEY, controllerClass.prototype, methodName);
|
|
239
|
+
if (headersMeta) {
|
|
240
|
+
for (const h of headersMeta) {
|
|
241
|
+
res.setHeader(h.name, h.value);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// Apply @HttpCode metadata
|
|
245
|
+
const httpCode = Reflect.getMetadata(HTTP_CODE_METADATA_KEY, controllerClass.prototype, methodName);
|
|
246
|
+
// Handle the response
|
|
247
|
+
if (result !== undefined) {
|
|
248
|
+
if (httpCode) {
|
|
249
|
+
res.status(httpCode);
|
|
250
|
+
}
|
|
251
|
+
if (typeof result === 'string' && result.trim().startsWith('<!DOCTYPE html>')) {
|
|
252
|
+
// Handle HTML response
|
|
253
|
+
res.setHeader('Content-Type', 'text/html');
|
|
254
|
+
res.send(result);
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
// Handle JSON response
|
|
258
|
+
res.json(result);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
else if (httpCode) {
|
|
262
|
+
res.status(httpCode).end();
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
logger_1.default.error('Request handler error:', error);
|
|
267
|
+
if (error instanceof pipe_1.ValidationError) {
|
|
268
|
+
const errorResponse = error.toJSON();
|
|
269
|
+
logger_1.default.error('Validation error response:', errorResponse);
|
|
270
|
+
res.status(400).json({
|
|
271
|
+
statusCode: 400,
|
|
272
|
+
message: errorResponse.message,
|
|
273
|
+
errors: errorResponse.errors,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
else if (error instanceof http_error_1.HttpError) {
|
|
277
|
+
res.status(error.statusCode).json({
|
|
278
|
+
statusCode: error.statusCode,
|
|
279
|
+
message: error.message,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
// Log unhandled errors with full context
|
|
284
|
+
logger_1.default.error('Unhandled error:', {
|
|
285
|
+
error: error instanceof Error ? error.message : String(error),
|
|
286
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
287
|
+
requestId,
|
|
288
|
+
method: req.method,
|
|
289
|
+
url: req.url,
|
|
290
|
+
});
|
|
291
|
+
res.status(500).json({
|
|
292
|
+
statusCode: 500,
|
|
293
|
+
message: process.env.NODE_ENV === 'production'
|
|
294
|
+
? 'Internal server error'
|
|
295
|
+
: error instanceof Error ? error.message : 'Unknown error',
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
finally {
|
|
300
|
+
// CRITICAL: Clean up request-scoped providers to prevent memory leaks
|
|
301
|
+
this.container.clearRequestScope(requestId);
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
extractParams(path, routePath) {
|
|
306
|
+
const params = {};
|
|
307
|
+
const pathParts = path.split('/');
|
|
308
|
+
const routeParts = routePath.split('/');
|
|
309
|
+
for (let i = 0; i < routeParts.length; i++) {
|
|
310
|
+
if (routeParts[i].startsWith(':')) {
|
|
311
|
+
const paramName = routeParts[i].slice(1);
|
|
312
|
+
params[paramName] = pathParts[i];
|
|
313
|
+
logger_1.default.debug(`Extracted param ${paramName}:`, pathParts[i]);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
logger_1.default.debug('Extracted params:', params);
|
|
317
|
+
return params;
|
|
318
|
+
}
|
|
319
|
+
matchPath(path, routePath) {
|
|
320
|
+
const pathParts = path.split('/');
|
|
321
|
+
const routeParts = routePath.split('/');
|
|
322
|
+
if (pathParts.length !== routeParts.length) {
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
for (let i = 0; i < routeParts.length; i++) {
|
|
326
|
+
if (!routeParts[i].startsWith(':') && routeParts[i] !== pathParts[i]) {
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
332
|
+
createRoutePattern(path) {
|
|
333
|
+
if (path === '/') {
|
|
334
|
+
return /^\/?$/;
|
|
335
|
+
}
|
|
336
|
+
// Escape forward slashes and convert :param to ([^/]+)
|
|
337
|
+
const pattern = path
|
|
338
|
+
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // Escape special regex characters
|
|
339
|
+
.replace(/:[^/]+/g, '([^/]+)');
|
|
340
|
+
return new RegExp(`^${pattern}/?$`);
|
|
341
|
+
}
|
|
342
|
+
normalizePath(path) {
|
|
343
|
+
if (path.length > 1 && path.endsWith('/')) {
|
|
344
|
+
path = path.slice(0, -1);
|
|
345
|
+
}
|
|
346
|
+
return path.startsWith('/') ? path : `/${path}`;
|
|
347
|
+
}
|
|
348
|
+
async match(method, url, context) {
|
|
349
|
+
const path = url.split('?')[0];
|
|
350
|
+
logger_1.default.debug(`Matching route: ${method} ${path}`);
|
|
351
|
+
// Use method-specific cache for O(1) method lookup instead of O(n)
|
|
352
|
+
const methodRoutes = this.routesByMethod.get(method);
|
|
353
|
+
if (!methodRoutes) {
|
|
354
|
+
logger_1.default.debug(`No routes registered for method: ${method}`);
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
// Now only iterate through routes for this specific HTTP method
|
|
358
|
+
for (const [routePath, handlers] of methodRoutes.entries()) {
|
|
359
|
+
if (this.matchPath(path, routePath)) {
|
|
360
|
+
const params = this.extractParams(path, routePath);
|
|
361
|
+
logger_1.default.debug('Matched route:', { method, url, params });
|
|
362
|
+
// Create a new context with the matched parameters
|
|
363
|
+
const matchedContext = {
|
|
364
|
+
...context,
|
|
365
|
+
params: params, // Don't merge, just use the extracted params
|
|
366
|
+
};
|
|
367
|
+
logger_1.default.debug('Created matched context:', matchedContext);
|
|
368
|
+
// Create a new handler that ensures the context is passed
|
|
369
|
+
const handler = handlers[0];
|
|
370
|
+
const wrappedHandler = async (req, res, ctx) => {
|
|
371
|
+
logger_1.default.debug('Wrapped handler called with context:', ctx);
|
|
372
|
+
await handler(req, res, matchedContext);
|
|
373
|
+
};
|
|
374
|
+
return { handler: wrappedHandler, context: matchedContext };
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
get(path, handlers) {
|
|
380
|
+
this.addRoute('GET', path, handlers);
|
|
381
|
+
}
|
|
382
|
+
post(path, handlers) {
|
|
383
|
+
this.addRoute('POST', path, handlers);
|
|
384
|
+
}
|
|
385
|
+
put(path, handlers) {
|
|
386
|
+
this.addRoute('PUT', path, handlers);
|
|
387
|
+
}
|
|
388
|
+
delete(path, handlers) {
|
|
389
|
+
this.addRoute('DELETE', path, handlers);
|
|
390
|
+
}
|
|
391
|
+
addRoute(method, path, handlers) {
|
|
392
|
+
const normalizedPath = this.normalizePath(path);
|
|
393
|
+
this.routes.set(`${method.toUpperCase()} ${normalizedPath}`, handlers);
|
|
394
|
+
}
|
|
395
|
+
async handleRequest(req, res) {
|
|
396
|
+
try {
|
|
397
|
+
logger_1.default.debug('=== Request Handler Start ===');
|
|
398
|
+
logger_1.default.debug(`Method: ${req.method}, URL: ${req.url}`);
|
|
399
|
+
// Parse the request to get the initial context
|
|
400
|
+
const context = await request_parser_1.RequestParser.parseRequest(req);
|
|
401
|
+
logger_1.default.debug('Initial context:', context);
|
|
402
|
+
// Match the request method and URL
|
|
403
|
+
const match = await this.match(req.method || 'GET', req.url || '/', context);
|
|
404
|
+
if (!match) {
|
|
405
|
+
logger_1.default.warn(`No route found for ${req.method} ${req.url}`);
|
|
406
|
+
res.status(404).json({ error: 'Not Found' });
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
// Log the matched context before passing it
|
|
410
|
+
logger_1.default.debug('Matched context before handler:', match.context);
|
|
411
|
+
// Update request params with matched parameters
|
|
412
|
+
req.params = match.context.params;
|
|
413
|
+
logger_1.default.debug('Updated request params:', req.params);
|
|
414
|
+
// Call the matched handler with the matched context
|
|
415
|
+
const handler = match.handler;
|
|
416
|
+
await handler(req, res, match.context);
|
|
417
|
+
}
|
|
418
|
+
catch (error) {
|
|
419
|
+
logger_1.default.error('Error handling request:', error);
|
|
420
|
+
res
|
|
421
|
+
.status(500)
|
|
422
|
+
.json({ error: error instanceof Error ? error.message : 'Internal Server Error' });
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
exports.Router = Router;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route pattern matcher with support for:
|
|
3
|
+
* - Named parameters (:id)
|
|
4
|
+
* - Optional parameters (:id?)
|
|
5
|
+
* - Wildcard routes (*)
|
|
6
|
+
* - Regex patterns
|
|
7
|
+
*/
|
|
8
|
+
export declare class RouteMatcher {
|
|
9
|
+
private readonly path;
|
|
10
|
+
private pattern;
|
|
11
|
+
private paramNames;
|
|
12
|
+
private isWildcard;
|
|
13
|
+
constructor(path: string);
|
|
14
|
+
/**
|
|
15
|
+
* Compile the route path into a regex pattern
|
|
16
|
+
*/
|
|
17
|
+
private compile;
|
|
18
|
+
/**
|
|
19
|
+
* Match a URL path against this route
|
|
20
|
+
*/
|
|
21
|
+
match(path: string): RouteMatch | null;
|
|
22
|
+
/**
|
|
23
|
+
* Get the route path
|
|
24
|
+
*/
|
|
25
|
+
getPath(): string;
|
|
26
|
+
/**
|
|
27
|
+
* Check if route has parameters
|
|
28
|
+
*/
|
|
29
|
+
hasParams(): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Get parameter names
|
|
32
|
+
*/
|
|
33
|
+
getParamNames(): string[];
|
|
34
|
+
}
|
|
35
|
+
export interface RouteMatch {
|
|
36
|
+
params: Record<string, string>;
|
|
37
|
+
path: string;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=route-matcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-matcher.d.ts","sourceRoot":"","sources":["../../src/routing/route-matcher.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,qBAAa,YAAY;IAKX,OAAO,CAAC,QAAQ,CAAC,IAAI;IAJjC,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,UAAU,CAAgB;IAClC,OAAO,CAAC,UAAU,CAAS;gBAEE,IAAI,EAAE,MAAM;IAIzC;;OAEG;IACH,OAAO,CAAC,OAAO;IAgCf;;OAEG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI;IA4BtC;;OAEG;IACH,OAAO,IAAI,MAAM;IAIjB;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;OAEG;IACH,aAAa,IAAI,MAAM,EAAE;CAG1B;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,IAAI,EAAE,MAAM,CAAC;CACd"}
|
|
@@ -0,0 +1,93 @@
|
|
|
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.RouteMatcher = void 0;
|
|
7
|
+
const logger_1 = __importDefault(require("../logger"));
|
|
8
|
+
/**
|
|
9
|
+
* Route pattern matcher with support for:
|
|
10
|
+
* - Named parameters (:id)
|
|
11
|
+
* - Optional parameters (:id?)
|
|
12
|
+
* - Wildcard routes (*)
|
|
13
|
+
* - Regex patterns
|
|
14
|
+
*/
|
|
15
|
+
class RouteMatcher {
|
|
16
|
+
constructor(path) {
|
|
17
|
+
this.path = path;
|
|
18
|
+
this.paramNames = [];
|
|
19
|
+
this.isWildcard = false;
|
|
20
|
+
this.compile();
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Compile the route path into a regex pattern
|
|
24
|
+
*/
|
|
25
|
+
compile() {
|
|
26
|
+
// Check for wildcard
|
|
27
|
+
if (this.path.includes('*')) {
|
|
28
|
+
this.isWildcard = true;
|
|
29
|
+
}
|
|
30
|
+
let pattern = this.path;
|
|
31
|
+
// Escape special regex characters except : and *
|
|
32
|
+
pattern = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
|
|
33
|
+
// Handle optional parameters (:id?)
|
|
34
|
+
pattern = pattern.replace(/:(\w+)\?/g, (_, paramName) => {
|
|
35
|
+
this.paramNames.push(paramName);
|
|
36
|
+
return '(?:/([^/]+))?';
|
|
37
|
+
});
|
|
38
|
+
// Handle required parameters (:id)
|
|
39
|
+
pattern = pattern.replace(/:(\w+)/g, (_, paramName) => {
|
|
40
|
+
this.paramNames.push(paramName);
|
|
41
|
+
return '([^/]+)';
|
|
42
|
+
});
|
|
43
|
+
// Handle wildcards (*)
|
|
44
|
+
pattern = pattern.replace(/\*/g, '(.*)');
|
|
45
|
+
// Ensure exact match with optional trailing slash
|
|
46
|
+
this.pattern = new RegExp(`^${pattern}/?$`);
|
|
47
|
+
logger_1.default.debug(`Compiled route pattern: ${this.path} -> ${this.pattern}`);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Match a URL path against this route
|
|
51
|
+
*/
|
|
52
|
+
match(path) {
|
|
53
|
+
const match = this.pattern.exec(path);
|
|
54
|
+
if (!match) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const params = {};
|
|
58
|
+
// Extract parameters
|
|
59
|
+
for (let i = 0; i < this.paramNames.length; i++) {
|
|
60
|
+
const value = match[i + 1];
|
|
61
|
+
if (value !== undefined) {
|
|
62
|
+
params[this.paramNames[i]] = decodeURIComponent(value);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Handle wildcard
|
|
66
|
+
if (this.isWildcard && match[match.length - 1]) {
|
|
67
|
+
params['*'] = match[match.length - 1];
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
params,
|
|
71
|
+
path: this.path,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Get the route path
|
|
76
|
+
*/
|
|
77
|
+
getPath() {
|
|
78
|
+
return this.path;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Check if route has parameters
|
|
82
|
+
*/
|
|
83
|
+
hasParams() {
|
|
84
|
+
return this.paramNames.length > 0;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Get parameter names
|
|
88
|
+
*/
|
|
89
|
+
getParamNames() {
|
|
90
|
+
return [...this.paramNames];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
exports.RouteMatcher = RouteMatcher;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
/**
|
|
3
|
+
* Version types
|
|
4
|
+
*/
|
|
5
|
+
export declare enum VersioningType {
|
|
6
|
+
URI = "uri",
|
|
7
|
+
HEADER = "header",
|
|
8
|
+
MEDIA_TYPE = "media-type",
|
|
9
|
+
CUSTOM = "custom"
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Versioning options
|
|
13
|
+
*/
|
|
14
|
+
export interface VersioningOptions {
|
|
15
|
+
type: VersioningType;
|
|
16
|
+
header?: string;
|
|
17
|
+
key?: string;
|
|
18
|
+
extractor?: (request: unknown) => string | undefined;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Version decorator for controllers and routes
|
|
22
|
+
*/
|
|
23
|
+
export declare function Version(version: string | string[]): MethodDecorator & ClassDecorator;
|
|
24
|
+
/**
|
|
25
|
+
* Get version metadata from a class or method
|
|
26
|
+
*/
|
|
27
|
+
export declare function getVersionMetadata(target: object | (new (...args: unknown[]) => object), propertyKey?: string | symbol): string[] | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* Check if a version matches the requested version
|
|
30
|
+
*/
|
|
31
|
+
export declare function matchVersion(routeVersions: string[] | undefined, requestedVersion: string | undefined, _options?: VersioningOptions): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Extract version from request based on versioning type
|
|
34
|
+
*/
|
|
35
|
+
export declare function extractVersion(request: unknown, options: VersioningOptions): string | undefined;
|
|
36
|
+
//# sourceMappingURL=version.decorator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.decorator.d.ts","sourceRoot":"","sources":["../../src/routing/version.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAE1B;;GAEG;AACH,oBAAY,cAAc;IACxB,GAAG,QAAQ;IACX,MAAM,WAAW;IACjB,UAAU,eAAe;IACzB,MAAM,WAAW;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,cAAc,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAC;CACtD;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,eAAe,GAAG,cAAc,CAmBpF;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,GAAG,CAAC,KAAK,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,MAAM,CAAC,EACrD,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,GAC5B,MAAM,EAAE,GAAG,SAAS,CAKtB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,aAAa,EAAE,MAAM,EAAE,GAAG,SAAS,EACnC,gBAAgB,EAAE,MAAM,GAAG,SAAS,EACpC,QAAQ,CAAC,EAAE,iBAAiB,GAC3B,OAAO,CAaT;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,iBAAiB,GAAG,MAAM,GAAG,SAAS,CA6B/F"}
|