@hazeljs/core 0.2.0-beta.8 → 0.2.0-beta.81
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/LICENSE +192 -21
- package/README.md +48 -10
- package/dist/__tests__/decorators.test.d.ts.map +1 -1
- package/dist/__tests__/decorators.test.js +544 -0
- package/dist/__tests__/hazel-app.test.js +128 -0
- package/dist/__tests__/router.test.js +503 -0
- package/dist/container.js +4 -4
- package/dist/decorators.d.ts +75 -1
- package/dist/decorators.d.ts.map +1 -1
- package/dist/decorators.js +196 -1
- package/dist/errors/http.error.d.ts +3 -0
- package/dist/errors/http.error.d.ts.map +1 -1
- package/dist/errors/http.error.js +8 -1
- package/dist/hazel-app.d.ts +17 -1
- package/dist/hazel-app.d.ts.map +1 -1
- package/dist/hazel-app.js +88 -25
- package/dist/hazel-module.d.ts +11 -2
- package/dist/hazel-module.d.ts.map +1 -1
- package/dist/hazel-module.js +47 -19
- package/dist/hazel-response.d.ts +5 -0
- package/dist/hazel-response.d.ts.map +1 -1
- package/dist/hazel-response.js +21 -0
- package/dist/health.js +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -2
- package/dist/interceptors/interceptor.d.ts +8 -0
- package/dist/interceptors/interceptor.d.ts.map +1 -1
- package/dist/interceptors/interceptor.js +26 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +51 -28
- package/dist/request-context.d.ts +5 -0
- package/dist/request-context.d.ts.map +1 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +56 -6
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -4
package/dist/router.js
CHANGED
|
@@ -17,6 +17,8 @@ const CONTROLLER_METADATA_KEY = 'hazel:controller';
|
|
|
17
17
|
const HTTP_CODE_METADATA_KEY = 'hazel:http-code';
|
|
18
18
|
const HEADER_METADATA_KEY = 'hazel:headers';
|
|
19
19
|
const REDIRECT_METADATA_KEY = 'hazel:redirect';
|
|
20
|
+
const TIMEOUT_METADATA_KEY = 'hazel:timeout';
|
|
21
|
+
const OPTIONAL_INDICES_METADATA_KEY = 'hazel:optional-indices';
|
|
20
22
|
class Router {
|
|
21
23
|
constructor(container) {
|
|
22
24
|
this.container = container;
|
|
@@ -31,7 +33,7 @@ class Router {
|
|
|
31
33
|
this.routesByMethod.set('PATCH', new Map());
|
|
32
34
|
}
|
|
33
35
|
registerController(controller) {
|
|
34
|
-
logger_1.default.
|
|
36
|
+
logger_1.default.debug(`Registering controller: ${controller.name}`);
|
|
35
37
|
const controllerMetadata = Reflect.getMetadata(CONTROLLER_METADATA_KEY, controller) || {};
|
|
36
38
|
const routes = Reflect.getMetadata(ROUTE_METADATA_KEY, controller) || [];
|
|
37
39
|
logger_1.default.debug('Controller metadata:', controllerMetadata);
|
|
@@ -41,7 +43,7 @@ class Router {
|
|
|
41
43
|
const basePath = controllerMetadata.path || '';
|
|
42
44
|
const routePath = path || '';
|
|
43
45
|
const fullPath = this.normalizePath(`${basePath}${routePath}`);
|
|
44
|
-
logger_1.default.
|
|
46
|
+
logger_1.default.debug(`Registering route: ${method} ${fullPath} (handler: ${String(propertyKey)})`);
|
|
45
47
|
// Get parameter types from TypeScript metadata
|
|
46
48
|
const paramTypes = Reflect.getMetadata('design:paramtypes', controller.prototype, propertyKey) || [];
|
|
47
49
|
logger_1.default.debug('Parameter types:', paramTypes.map((t) => t?.name || 'undefined'));
|
|
@@ -133,6 +135,7 @@ class Router {
|
|
|
133
135
|
method: req.method || 'GET',
|
|
134
136
|
url: req.url || '/',
|
|
135
137
|
};
|
|
138
|
+
context.retryOptions = Reflect.getMetadata('hazel:retry', controllerClass.prototype, methodName);
|
|
136
139
|
// Execute guards (class-level + method-level)
|
|
137
140
|
const classGuards = Reflect.getMetadata('hazel:guards', controllerClass) || [];
|
|
138
141
|
const methodGuards = Reflect.getMetadata('hazel:guards', controllerClass.prototype, methodName) || [];
|
|
@@ -142,6 +145,7 @@ class Router {
|
|
|
142
145
|
switchToHttp: () => ({
|
|
143
146
|
getRequest: () => req,
|
|
144
147
|
getResponse: () => res,
|
|
148
|
+
getContext: () => context,
|
|
145
149
|
}),
|
|
146
150
|
};
|
|
147
151
|
for (const guardType of allGuards) {
|
|
@@ -195,6 +199,10 @@ class Router {
|
|
|
195
199
|
args[i] = context.headers;
|
|
196
200
|
}
|
|
197
201
|
}
|
|
202
|
+
else if (injection.type === 'request') {
|
|
203
|
+
// Handle @Req() / @Request() decorator - raw request for multipart, etc.
|
|
204
|
+
args[i] = req;
|
|
205
|
+
}
|
|
198
206
|
else if (injection.type === 'response') {
|
|
199
207
|
// Handle @Res decorator
|
|
200
208
|
args[i] = new hazel_response_1.HazelExpressResponse(res);
|
|
@@ -231,6 +239,39 @@ class Router {
|
|
|
231
239
|
args[i] = queryValue;
|
|
232
240
|
}
|
|
233
241
|
}
|
|
242
|
+
else if (injection.type === 'user') {
|
|
243
|
+
// Handle @CurrentUser() decorator — reads from context.user (set by a guard)
|
|
244
|
+
const user = context.user ?? req.user;
|
|
245
|
+
args[i] = injection.field ? user?.[injection.field] : user;
|
|
246
|
+
}
|
|
247
|
+
else if (injection.type === 'ip') {
|
|
248
|
+
const r = req;
|
|
249
|
+
const forwarded = r.headers?.['x-forwarded-for'];
|
|
250
|
+
const ip = typeof forwarded === 'string'
|
|
251
|
+
? forwarded.split(',')[0].trim()
|
|
252
|
+
: Array.isArray(forwarded)
|
|
253
|
+
? forwarded[0]?.trim()
|
|
254
|
+
: r.socket?.remoteAddress;
|
|
255
|
+
args[i] = ip ?? undefined;
|
|
256
|
+
}
|
|
257
|
+
else if (injection.type === 'host') {
|
|
258
|
+
const host = req.headers?.['host'];
|
|
259
|
+
args[i] = typeof host === 'string' ? host : Array.isArray(host) ? host[0] : undefined;
|
|
260
|
+
}
|
|
261
|
+
else if (injection.type === 'session') {
|
|
262
|
+
args[i] = req.session;
|
|
263
|
+
}
|
|
264
|
+
else if (injection.type === 'custom' && typeof injection.resolve === 'function') {
|
|
265
|
+
// Handle custom parameter decorators (e.g. @Ability() from @hazeljs/casl).
|
|
266
|
+
// The decorator stores a resolver function; call it with request, context, container.
|
|
267
|
+
args[i] = await injection.resolve(req, context, this.container);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
const optionalIndices = Reflect.getMetadata(OPTIONAL_INDICES_METADATA_KEY, controllerClass, methodName) || [];
|
|
272
|
+
for (const i of optionalIndices) {
|
|
273
|
+
if (i < args.length && (args[i] === undefined || args[i] === null)) {
|
|
274
|
+
args[i] = undefined;
|
|
234
275
|
}
|
|
235
276
|
}
|
|
236
277
|
// Auto-inject RequestContext for undecorated parameters
|
|
@@ -253,10 +294,19 @@ class Router {
|
|
|
253
294
|
}
|
|
254
295
|
// Get the controller method
|
|
255
296
|
const method = controller[methodName];
|
|
256
|
-
|
|
257
|
-
|
|
297
|
+
const timeoutMs = Reflect.getMetadata(TIMEOUT_METADATA_KEY, controllerClass.prototype, methodName);
|
|
298
|
+
let handlerPromise = this.applyInterceptors(Reflect.getMetadata('hazel:interceptors', controllerClass, methodName) || [], context, async () => {
|
|
258
299
|
return method.apply(controller, args);
|
|
259
300
|
});
|
|
301
|
+
if (timeoutMs != null && timeoutMs > 0) {
|
|
302
|
+
handlerPromise = Promise.race([
|
|
303
|
+
handlerPromise,
|
|
304
|
+
new Promise((_, reject) => {
|
|
305
|
+
setTimeout(() => reject(new http_error_1.RequestTimeoutError(`Request timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
306
|
+
}),
|
|
307
|
+
]);
|
|
308
|
+
}
|
|
309
|
+
const result = await handlerPromise;
|
|
260
310
|
// Apply @Redirect metadata
|
|
261
311
|
const redirectMeta = Reflect.getMetadata(REDIRECT_METADATA_KEY, controllerClass.prototype, methodName);
|
|
262
312
|
if (redirectMeta) {
|
|
@@ -376,7 +426,7 @@ class Router {
|
|
|
376
426
|
return path.startsWith('/') ? path : `/${path}`;
|
|
377
427
|
}
|
|
378
428
|
async match(method, url, context) {
|
|
379
|
-
const path = url.split('?')[0];
|
|
429
|
+
const path = this.normalizePath(url.split('?')[0] || '/');
|
|
380
430
|
logger_1.default.debug(`Matching route: ${method} ${path}`);
|
|
381
431
|
// Use method-specific cache for O(1) method lookup instead of O(n)
|
|
382
432
|
const methodRoutes = this.routesByMethod.get(method);
|
|
@@ -432,7 +482,7 @@ class Router {
|
|
|
432
482
|
// Match the request method and URL
|
|
433
483
|
const match = await this.match(req.method || 'GET', req.url || '/', context);
|
|
434
484
|
if (!match) {
|
|
435
|
-
logger_1.default.
|
|
485
|
+
logger_1.default.debug(`No route found for ${req.method} ${req.url}`);
|
|
436
486
|
res.status(404).json({ error: 'Not Found' });
|
|
437
487
|
return;
|
|
438
488
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -56,6 +56,12 @@ export interface RequestContext {
|
|
|
56
56
|
[key: string]: unknown;
|
|
57
57
|
};
|
|
58
58
|
req?: Request;
|
|
59
|
+
/** Set by router from @Retry() metadata; consumed by RetryInterceptor */
|
|
60
|
+
retryOptions?: {
|
|
61
|
+
count: number;
|
|
62
|
+
delay?: number;
|
|
63
|
+
retryIf?: (err: Error) => boolean;
|
|
64
|
+
};
|
|
59
65
|
}
|
|
60
66
|
export interface ValidationRule {
|
|
61
67
|
type: 'string' | 'number' | 'boolean' | 'object' | 'array';
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hazeljs/core",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.81",
|
|
4
4
|
"description": "Core HazelJS framework - Dependency injection, routing, decorators, and base functionality",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"chalk": "^4.1.2",
|
|
22
|
+
"class-transformer": "^0.5.1",
|
|
23
|
+
"class-validator": "^0.14.1",
|
|
22
24
|
"dotenv": "^16.5.0",
|
|
23
25
|
"reflect-metadata": "^0.2.2",
|
|
24
26
|
"winston": "^3.17.0"
|
|
@@ -55,11 +57,11 @@
|
|
|
55
57
|
"decorators",
|
|
56
58
|
"routing"
|
|
57
59
|
],
|
|
58
|
-
"author": "Muhammad Arslan <
|
|
59
|
-
"license": "
|
|
60
|
+
"author": "Muhammad Arslan <muhammad.arslan@hazeljs.com>",
|
|
61
|
+
"license": "Apache-2.0",
|
|
60
62
|
"bugs": {
|
|
61
63
|
"url": "https://github.com/hazeljs/hazel-js/issues"
|
|
62
64
|
},
|
|
63
65
|
"homepage": "https://hazeljs.com",
|
|
64
|
-
"gitHead": "
|
|
66
|
+
"gitHead": "1054f957451360f973469d436a1d58e57cc9231a"
|
|
65
67
|
}
|