@hazeljs/core 0.2.0-beta.8 → 0.2.0-beta.80

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.
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.HazelApp = exports.Module = exports.HazelModule = void 0;
6
+ exports.HazelApp = exports.SkipAuth = exports.Module = exports.HazelModule = exports.CUSTOM_METADATA_PREFIX = void 0;
7
7
  exports.Controller = Controller;
8
8
  exports.Injectable = Injectable;
9
9
  exports.Get = Get;
@@ -27,8 +27,21 @@ exports.HttpCode = HttpCode;
27
27
  exports.Header = Header;
28
28
  exports.Redirect = Redirect;
29
29
  exports.Res = Res;
30
+ exports.Ip = Ip;
31
+ exports.Host = Host;
32
+ exports.Public = Public;
33
+ exports.Timeout = Timeout;
34
+ exports.Optional = Optional;
35
+ exports.Session = Session;
36
+ exports.Retry = Retry;
37
+ exports.ApiTags = ApiTags;
38
+ exports.ApiOperation = ApiOperation;
39
+ exports.SetMetadata = SetMetadata;
40
+ exports.getMetadata = getMetadata;
41
+ exports.createParamDecorator = createParamDecorator;
30
42
  require("reflect-metadata");
31
43
  const logger_1 = __importDefault(require("./logger"));
44
+ const interceptor_1 = require("./interceptors/interceptor");
32
45
  const hazel_app_1 = require("./hazel-app");
33
46
  Object.defineProperty(exports, "HazelApp", { enumerable: true, get: function () { return hazel_app_1.HazelApp; } });
34
47
  const CONTROLLER_METADATA_KEY = 'hazel:controller';
@@ -42,6 +55,13 @@ const CLASS_INTERCEPTOR_METADATA_KEY = 'hazel:class-interceptors';
42
55
  const HTTP_CODE_METADATA_KEY = 'hazel:http-code';
43
56
  const HEADER_METADATA_KEY = 'hazel:headers';
44
57
  const REDIRECT_METADATA_KEY = 'hazel:redirect';
58
+ const PUBLIC_METADATA_KEY = 'hazel:public';
59
+ const TIMEOUT_METADATA_KEY = 'hazel:timeout';
60
+ const OPTIONAL_INDICES_METADATA_KEY = 'hazel:optional-indices';
61
+ const RETRY_METADATA_KEY = 'hazel:retry';
62
+ const API_TAGS_METADATA_KEY = 'hazel:api:tags';
63
+ const API_OPERATION_METADATA_KEY = 'hazel:api:operation';
64
+ exports.CUSTOM_METADATA_PREFIX = 'hazel:meta:';
45
65
  // Re-export from hazel-module for backward compatibility
46
66
  var hazel_module_1 = require("./hazel-module");
47
67
  Object.defineProperty(exports, "HazelModule", { enumerable: true, get: function () { return hazel_module_1.HazelModule; } });
@@ -313,6 +333,181 @@ function Res() {
313
333
  logger_1.default.debug('Updated injections:', JSON.stringify(injections, null, 2));
314
334
  };
315
335
  }
336
+ function Ip() {
337
+ return (target, propertyKey, parameterIndex) => {
338
+ if (!propertyKey) {
339
+ throw new Error('Ip decorator must be used on a method parameter');
340
+ }
341
+ const constructor = target
342
+ .constructor;
343
+ const injections = Reflect.getMetadata(INJECT_METADATA_KEY, constructor, propertyKey) || [];
344
+ injections[parameterIndex] = { type: 'ip' };
345
+ Reflect.defineMetadata(INJECT_METADATA_KEY, injections, constructor, propertyKey);
346
+ };
347
+ }
348
+ function Host() {
349
+ return (target, propertyKey, parameterIndex) => {
350
+ if (!propertyKey) {
351
+ throw new Error('Host decorator must be used on a method parameter');
352
+ }
353
+ const constructor = target
354
+ .constructor;
355
+ const injections = Reflect.getMetadata(INJECT_METADATA_KEY, constructor, propertyKey) || [];
356
+ injections[parameterIndex] = { type: 'host' };
357
+ Reflect.defineMetadata(INJECT_METADATA_KEY, injections, constructor, propertyKey);
358
+ };
359
+ }
360
+ /**
361
+ * Marks a controller or route as public (no auth required).
362
+ * Guards should check Reflect.getMetadata(PUBLIC_METADATA_KEY, target, propertyKey)
363
+ * or Reflect.getMetadata(PUBLIC_METADATA_KEY, target) and allow the request when true.
364
+ */
365
+ function Public() {
366
+ const setPublic = (target, propertyKey) => {
367
+ if (propertyKey === undefined) {
368
+ Reflect.defineMetadata(PUBLIC_METADATA_KEY, true, target);
369
+ }
370
+ else {
371
+ Reflect.defineMetadata(PUBLIC_METADATA_KEY, true, target, propertyKey);
372
+ }
373
+ };
374
+ const decorator = (target, propertyKey, descriptor) => {
375
+ if (propertyKey !== undefined && descriptor !== undefined) {
376
+ setPublic(target, propertyKey);
377
+ return descriptor;
378
+ }
379
+ setPublic(target);
380
+ };
381
+ return decorator;
382
+ }
383
+ /** Alias for @Public(). Use when you want to skip auth for specific routes. */
384
+ exports.SkipAuth = Public;
385
+ function Timeout(ms) {
386
+ return (target, propertyKey, descriptor) => {
387
+ Reflect.defineMetadata(TIMEOUT_METADATA_KEY, ms, target, propertyKey);
388
+ return descriptor;
389
+ };
390
+ }
391
+ function Optional() {
392
+ return (target, propertyKey, parameterIndex) => {
393
+ if (!propertyKey) {
394
+ throw new Error('Optional decorator must be used on a method parameter');
395
+ }
396
+ const constructor = target
397
+ .constructor;
398
+ const indices = Reflect.getMetadata(OPTIONAL_INDICES_METADATA_KEY, constructor, propertyKey) || [];
399
+ if (!indices.includes(parameterIndex)) {
400
+ indices.push(parameterIndex);
401
+ }
402
+ Reflect.defineMetadata(OPTIONAL_INDICES_METADATA_KEY, indices, constructor, propertyKey);
403
+ };
404
+ }
405
+ function Session() {
406
+ return (target, propertyKey, parameterIndex) => {
407
+ if (!propertyKey) {
408
+ throw new Error('Session decorator must be used on a method parameter');
409
+ }
410
+ const constructor = target
411
+ .constructor;
412
+ const injections = Reflect.getMetadata(INJECT_METADATA_KEY, constructor, propertyKey) || [];
413
+ injections[parameterIndex] = { type: 'session' };
414
+ Reflect.defineMetadata(INJECT_METADATA_KEY, injections, constructor, propertyKey);
415
+ };
416
+ }
417
+ function Retry(options) {
418
+ return (target, propertyKey, descriptor) => {
419
+ Reflect.defineMetadata(RETRY_METADATA_KEY, options, target, propertyKey);
420
+ const routes = Reflect.getMetadata(ROUTE_METADATA_KEY, target.constructor) || [];
421
+ const route = routes.find((r) => r.propertyKey === propertyKey);
422
+ if (route) {
423
+ route.interceptors = route.interceptors || [];
424
+ route.interceptors.unshift({ type: interceptor_1.RetryInterceptor, options });
425
+ Reflect.defineMetadata(ROUTE_METADATA_KEY, routes, target.constructor);
426
+ }
427
+ return descriptor;
428
+ };
429
+ }
430
+ function ApiTags(...tags) {
431
+ const setTags = (target, propertyKey) => {
432
+ if (propertyKey === undefined) {
433
+ Reflect.defineMetadata(API_TAGS_METADATA_KEY, tags, target);
434
+ }
435
+ else {
436
+ Reflect.defineMetadata(API_TAGS_METADATA_KEY, tags, target, propertyKey);
437
+ }
438
+ };
439
+ const decorator = (target, propertyKey, descriptor) => {
440
+ if (propertyKey !== undefined && descriptor !== undefined) {
441
+ setTags(target, propertyKey);
442
+ return descriptor;
443
+ }
444
+ setTags(target);
445
+ };
446
+ return decorator;
447
+ }
448
+ function ApiOperation(options) {
449
+ const opts = typeof options === 'string' ? { summary: options } : options;
450
+ return (target, propertyKey, descriptor) => {
451
+ Reflect.defineMetadata(API_OPERATION_METADATA_KEY, opts, target, propertyKey);
452
+ return descriptor;
453
+ };
454
+ }
455
+ /**
456
+ * Sets arbitrary metadata on a class or method.
457
+ * Guards, interceptors, and other components can read it via getMetadata(key, target, propertyKey?).
458
+ *
459
+ * @param key - Metadata key (stored under hazel:meta:<key> to avoid collisions)
460
+ * @param value - Value to store (any serializable or object)
461
+ * @example
462
+ * SetMetadata('roles', ['admin'])(MyController)
463
+ * SetMetadata('roles', ['user'])(MyController.prototype, 'getProfile')
464
+ */
465
+ function SetMetadata(key, value) {
466
+ const metaKey = `${exports.CUSTOM_METADATA_PREFIX}${key}`;
467
+ const decorator = (target, propertyKey, descriptor) => {
468
+ if (propertyKey !== undefined && descriptor !== undefined) {
469
+ Reflect.defineMetadata(metaKey, value, target, propertyKey);
470
+ return descriptor;
471
+ }
472
+ Reflect.defineMetadata(metaKey, value, target);
473
+ };
474
+ return decorator;
475
+ }
476
+ /**
477
+ * Reads custom metadata set with SetMetadata.
478
+ *
479
+ * @param key - Key passed to SetMetadata(key, value)
480
+ * @param target - Class or prototype
481
+ * @param propertyKey - Optional method name (for method-level metadata)
482
+ */
483
+ function getMetadata(key, target, propertyKey) {
484
+ const metaKey = `${exports.CUSTOM_METADATA_PREFIX}${key}`;
485
+ if (propertyKey !== undefined) {
486
+ return Reflect.getMetadata(metaKey, target, propertyKey);
487
+ }
488
+ return Reflect.getMetadata(metaKey, target);
489
+ }
490
+ /**
491
+ * Creates a custom parameter decorator that injects a value computed from the request.
492
+ * The resolver receives the raw request, parsed request context, and the DI container.
493
+ * Return value can be a Promise for async resolution (e.g. loading the current user from DB).
494
+ *
495
+ * @param resolve - Function (req, context, container) => value | Promise<value>
496
+ * @example
497
+ * const CurrentUser = createParamDecorator(async (req, ctx, container) => ctx.user ?? req.user);
498
+ * // In controller: getProfile(@CurrentUser() user: User) { ... }
499
+ */
500
+ function createParamDecorator(resolve) {
501
+ return (target, propertyKey, parameterIndex) => {
502
+ if (!propertyKey) {
503
+ throw new Error('createParamDecorator must be used on a method parameter');
504
+ }
505
+ const constructor = target.constructor;
506
+ const injections = Reflect.getMetadata(INJECT_METADATA_KEY, constructor, propertyKey) || [];
507
+ injections[parameterIndex] = { type: 'custom', resolve };
508
+ Reflect.defineMetadata(INJECT_METADATA_KEY, injections, constructor, propertyKey);
509
+ };
510
+ }
316
511
  function createRouteDecorator(method, options) {
317
512
  return (target, propertyKey, descriptor) => {
318
513
  logger_1.default.debug(`Registering ${method} route: ${String(propertyKey)}`);
@@ -18,6 +18,9 @@ export declare class NotFoundError extends HttpError {
18
18
  export declare class ConflictError extends HttpError {
19
19
  constructor(message: string);
20
20
  }
21
+ export declare class RequestTimeoutError extends HttpError {
22
+ constructor(message?: string);
23
+ }
21
24
  export declare class InternalServerError extends HttpError {
22
25
  constructor(message?: string);
23
26
  }
@@ -1 +1 @@
1
- {"version":3,"file":"http.error.d.ts","sourceRoot":"","sources":["../../src/errors/http.error.ts"],"names":[],"mappings":"AAAA,qBAAa,SAAU,SAAQ,KAAK;aAEhB,UAAU,EAAE,MAAM;aAElB,MAAM,CAAC,EAAE,MAAM,EAAE;gBAFjB,UAAU,EAAE,MAAM,EAClC,OAAO,EAAE,MAAM,EACC,MAAM,CAAC,EAAE,MAAM,EAAE,YAAA;CAKpC;AAED,qBAAa,eAAgB,SAAQ,SAAS;gBAChC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE;CAI/C;AAED,qBAAa,iBAAkB,SAAQ,SAAS;gBAClC,OAAO,GAAE,MAAuB;CAI7C;AAED,qBAAa,cAAe,SAAQ,SAAS;gBAC/B,OAAO,GAAE,MAAoB;CAI1C;AAED,qBAAa,aAAc,SAAQ,SAAS;gBAC9B,OAAO,GAAE,MAAoB;CAI1C;AAED,qBAAa,aAAc,SAAQ,SAAS;gBAC9B,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,mBAAoB,SAAQ,SAAS;gBACpC,OAAO,GAAE,MAAgC;CAItD;AAGD,eAAO,MAAM,aAAa,kBAAY,CAAC;AACvC,eAAO,MAAM,mBAAmB,wBAAkB,CAAC;AACnD,eAAO,MAAM,qBAAqB,0BAAoB,CAAC;AACvD,eAAO,MAAM,kBAAkB,uBAAiB,CAAC;AACjD,eAAO,MAAM,iBAAiB,sBAAgB,CAAC;AAC/C,eAAO,MAAM,iBAAiB,sBAAgB,CAAC;AAC/C,eAAO,MAAM,4BAA4B,4BAAsB,CAAC"}
1
+ {"version":3,"file":"http.error.d.ts","sourceRoot":"","sources":["../../src/errors/http.error.ts"],"names":[],"mappings":"AAAA,qBAAa,SAAU,SAAQ,KAAK;aAEhB,UAAU,EAAE,MAAM;aAElB,MAAM,CAAC,EAAE,MAAM,EAAE;gBAFjB,UAAU,EAAE,MAAM,EAClC,OAAO,EAAE,MAAM,EACC,MAAM,CAAC,EAAE,MAAM,EAAE,YAAA;CAKpC;AAED,qBAAa,eAAgB,SAAQ,SAAS;gBAChC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE;CAI/C;AAED,qBAAa,iBAAkB,SAAQ,SAAS;gBAClC,OAAO,GAAE,MAAuB;CAI7C;AAED,qBAAa,cAAe,SAAQ,SAAS;gBAC/B,OAAO,GAAE,MAAoB;CAI1C;AAED,qBAAa,aAAc,SAAQ,SAAS;gBAC9B,OAAO,GAAE,MAAoB;CAI1C;AAED,qBAAa,aAAc,SAAQ,SAAS;gBAC9B,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,mBAAoB,SAAQ,SAAS;gBACpC,OAAO,GAAE,MAA0B;CAIhD;AAED,qBAAa,mBAAoB,SAAQ,SAAS;gBACpC,OAAO,GAAE,MAAgC;CAItD;AAGD,eAAO,MAAM,aAAa,kBAAY,CAAC;AACvC,eAAO,MAAM,mBAAmB,wBAAkB,CAAC;AACnD,eAAO,MAAM,qBAAqB,0BAAoB,CAAC;AACvD,eAAO,MAAM,kBAAkB,uBAAiB,CAAC;AACjD,eAAO,MAAM,iBAAiB,sBAAgB,CAAC;AAC/C,eAAO,MAAM,iBAAiB,sBAAgB,CAAC;AAC/C,eAAO,MAAM,4BAA4B,4BAAsB,CAAC"}
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.InternalServerErrorException = exports.ConflictException = exports.NotFoundException = exports.ForbiddenException = exports.UnauthorizedException = exports.BadRequestException = exports.HttpException = exports.InternalServerError = exports.ConflictError = exports.NotFoundError = exports.ForbiddenError = exports.UnauthorizedError = exports.BadRequestError = exports.HttpError = void 0;
3
+ exports.InternalServerErrorException = exports.ConflictException = exports.NotFoundException = exports.ForbiddenException = exports.UnauthorizedException = exports.BadRequestException = exports.HttpException = exports.InternalServerError = exports.RequestTimeoutError = exports.ConflictError = exports.NotFoundError = exports.ForbiddenError = exports.UnauthorizedError = exports.BadRequestError = exports.HttpError = void 0;
4
4
  class HttpError extends Error {
5
5
  constructor(statusCode, message, errors) {
6
6
  super(message);
@@ -45,6 +45,13 @@ class ConflictError extends HttpError {
45
45
  }
46
46
  }
47
47
  exports.ConflictError = ConflictError;
48
+ class RequestTimeoutError extends HttpError {
49
+ constructor(message = 'Request Timeout') {
50
+ super(408, message);
51
+ this.name = 'RequestTimeoutError';
52
+ }
53
+ }
54
+ exports.RequestTimeoutError = RequestTimeoutError;
48
55
  class InternalServerError extends HttpError {
49
56
  constructor(message = 'Internal Server Error') {
50
57
  super(500, message);
@@ -1,12 +1,17 @@
1
1
  import { Type } from './types';
2
2
  import { Container } from './container';
3
3
  import { Router } from './router';
4
+ import { IncomingMessage, ServerResponse } from 'http';
4
5
  import 'reflect-metadata';
5
- import { Request, Response } from './types';
6
+ import { Request, Response, RequestContext } from './types';
6
7
  import { ShutdownManager } from './shutdown';
7
8
  import { HealthCheckManager } from './health';
8
9
  import { TimeoutOptions } from './middleware/timeout.middleware';
9
10
  import { CorsOptions } from './middleware/cors.middleware';
11
+ /** Early HTTP handler (e.g. for GraphQL) - receives raw req/res before body parsing */
12
+ export type EarlyHttpHandler = (req: IncomingMessage, res: ServerResponse) => void | Promise<void>;
13
+ /** Proxy handler - runs after body parsing, receives (req, res, context). Returns true if handled. */
14
+ export type ProxyHandler = (req: IncomingMessage, res: ServerResponse, context: RequestContext) => Promise<boolean>;
10
15
  export declare class HazelApp {
11
16
  private readonly moduleType;
12
17
  private container;
@@ -20,6 +25,8 @@ export declare class HazelApp {
20
25
  private corsEnabled;
21
26
  private corsOptions?;
22
27
  private timeoutMiddleware?;
28
+ private earlyHandlers;
29
+ private proxyHandlers;
23
30
  constructor(moduleType: Type<unknown>);
24
31
  private initialize;
25
32
  private collectControllers;
@@ -74,5 +81,14 @@ export declare class HazelApp {
74
81
  getShutdownManager(): ShutdownManager;
75
82
  getContainer(): Container;
76
83
  getRouter(): Router;
84
+ /**
85
+ * Add an early HTTP handler (runs before body parsing, for GraphQL etc.)
86
+ */
87
+ addEarlyHandler(path: string, handler: EarlyHttpHandler): void;
88
+ /**
89
+ * Add a proxy handler (runs after body parsing, before router).
90
+ * Use with @hazeljs/gateway: app.addProxyHandler('/api', createGatewayHandler(gateway))
91
+ */
92
+ addProxyHandler(pathPrefix: string, handler: ProxyHandler): void;
77
93
  }
78
94
  //# sourceMappingURL=hazel-app.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hazel-app.d.ts","sourceRoot":"","sources":["../src/hazel-app.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hazel-app.d.ts","sourceRoot":"","sources":["../src/hazel-app.ts"],"names":[],"mappings":"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"}
package/dist/hazel-app.js CHANGED
@@ -55,6 +55,13 @@ class HttpResponse {
55
55
  this.headers[name] = value;
56
56
  }
57
57
  }
58
+ redirect(url, statusCode = 302) {
59
+ if (this.headersSent)
60
+ return;
61
+ this.headersSent = true;
62
+ this.res.writeHead(statusCode, { ...this.headers, Location: url });
63
+ this.res.end();
64
+ }
58
65
  }
59
66
  class HazelApp {
60
67
  constructor(moduleType) {
@@ -62,8 +69,11 @@ class HazelApp {
62
69
  this.server = null;
63
70
  this.requestTimeout = 30000; // 30 seconds default
64
71
  this.corsEnabled = false;
65
- logger_1.default.info('Initializing HazelApp');
72
+ this.earlyHandlers = [];
73
+ this.proxyHandlers = [];
74
+ logger_1.default.debug('Initializing HazelApp');
66
75
  this.container = container_1.Container.getInstance();
76
+ this.container.register(HazelApp, this);
67
77
  this.router = new router_1.Router(this.container);
68
78
  this.requestParser = new request_parser_1.RequestParser();
69
79
  this.module = new hazel_module_1.HazelModuleInstance(this.moduleType);
@@ -75,14 +85,14 @@ class HazelApp {
75
85
  this.initialize();
76
86
  }
77
87
  initialize() {
78
- logger_1.default.info('Initializing module:', { moduleName: this.moduleType.name });
88
+ logger_1.default.debug('Initializing module:', { moduleName: this.moduleType.name });
79
89
  const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, this.moduleType) || {};
80
90
  logger_1.default.debug('Module metadata:', metadata);
81
91
  // Collect all controllers from the module tree (root + imports, recursively)
82
92
  const allControllers = this.collectControllers(this.moduleType);
83
93
  // Register all controllers with the router
84
94
  if (allControllers.length > 0) {
85
- logger_1.default.info('Registering controllers:', {
95
+ logger_1.default.debug('Registering controllers:', {
86
96
  controllers: allControllers.map((c) => c.name),
87
97
  });
88
98
  allControllers.forEach((controller) => {
@@ -90,11 +100,11 @@ class HazelApp {
90
100
  });
91
101
  }
92
102
  }
93
- collectControllers(moduleType, visited = new Set()) {
94
- if (visited.has(moduleType))
103
+ collectControllers(moduleRef, visited = new Set()) {
104
+ if (visited.has(moduleRef))
95
105
  return [];
96
- visited.add(moduleType);
97
- const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, moduleType) || {};
106
+ visited.add(moduleRef);
107
+ const metadata = (0, hazel_module_1.getModuleMetadata)(moduleRef) || {};
98
108
  const controllers = [];
99
109
  // Collect from imported modules first
100
110
  if (metadata.imports) {
@@ -132,6 +142,18 @@ class HazelApp {
132
142
  async listen(port) {
133
143
  return new Promise((resolve) => {
134
144
  this.server = new http_1.Server(async (req, res) => {
145
+ const startTime = Date.now();
146
+ const method = req.method || 'GET';
147
+ const url = req.url || '/';
148
+ const path = url.split('?')[0];
149
+ res.once('finish', () => {
150
+ if (process.env.LOG_HTTP === 'false')
151
+ return;
152
+ const duration = Date.now() - startTime;
153
+ const status = res.statusCode || 0;
154
+ const statusColor = status >= 500 ? chalk_1.default.red : status >= 400 ? chalk_1.default.yellow : chalk_1.default.green;
155
+ logger_1.default.info(`${chalk_1.default.bold(method)} ${path} ${statusColor(String(status))} ${chalk_1.default.gray(duration + 'ms')}`);
156
+ });
135
157
  try {
136
158
  if (!req.url) {
137
159
  logger_1.default.warn('Invalid URL received');
@@ -159,8 +181,16 @@ class HazelApp {
159
181
  res.end(JSON.stringify(startup));
160
182
  return;
161
183
  }
184
+ // Early handlers (e.g. GraphQL) - must run before body parsing
185
+ for (const { path, handler } of this.earlyHandlers) {
186
+ const pathname = req.url?.split('?')[0] ?? '';
187
+ if (pathname === path || pathname.startsWith(path + '/')) {
188
+ await handler(req, res);
189
+ return;
190
+ }
191
+ }
162
192
  const { method, url, headers } = req;
163
- logger_1.default.info('Incoming request:', { method, url, headers });
193
+ logger_1.default.debug('Incoming request:', { method, url, headers });
164
194
  // Handle CORS
165
195
  if (this.corsEnabled) {
166
196
  const origin = headers['origin'] || '*';
@@ -183,9 +213,12 @@ class HazelApp {
183
213
  return;
184
214
  }
185
215
  }
186
- // Parse request body for POST/PUT/PATCH requests
216
+ // Parse request body for POST/PUT/PATCH requests (skip multipart - let route handle it)
187
217
  let body = undefined;
188
- if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
218
+ let rawBody = '';
219
+ const contentType = headers['content-type'] || '';
220
+ const isMultipart = contentType.includes('multipart/form-data');
221
+ if ((method === 'POST' || method === 'PUT' || method === 'PATCH') && !isMultipart) {
189
222
  try {
190
223
  const chunks = [];
191
224
  req.on('data', (chunk) => chunks.push(chunk));
@@ -193,8 +226,8 @@ class HazelApp {
193
226
  req.on('end', () => {
194
227
  try {
195
228
  const bodyStr = Buffer.concat(chunks).toString();
229
+ rawBody = bodyStr;
196
230
  if (bodyStr) {
197
- const contentType = headers['content-type'] || '';
198
231
  if (contentType.includes('application/json')) {
199
232
  body = JSON.parse(bodyStr);
200
233
  }
@@ -216,15 +249,27 @@ class HazelApp {
216
249
  });
217
250
  }
218
251
  catch (error) {
252
+ const err = error;
253
+ const msg = err?.message ?? (err && typeof err.message === 'string' ? err.message : '');
254
+ const isClientAbort = err?.code === 'ECONNRESET' ||
255
+ err?.code === 'EPIPE' ||
256
+ (typeof msg === 'string' && (msg === 'aborted' || msg.includes('aborted')));
257
+ if (isClientAbort) {
258
+ logger_1.default.debug('Client disconnected while sending request body');
259
+ return;
260
+ }
219
261
  logger_1.default.error('Error parsing request body:', error);
220
- res.writeHead(400);
221
- res.end(JSON.stringify({ error: 'Invalid request body' }));
262
+ if (!res.writableEnded) {
263
+ res.writeHead(400);
264
+ res.end(JSON.stringify({ error: 'Invalid request body' }));
265
+ }
222
266
  return;
223
267
  }
224
268
  }
225
269
  // Extend the original request object
226
270
  Object.assign(req, {
227
271
  body,
272
+ rawBody: rawBody || undefined,
228
273
  params: {},
229
274
  query: {},
230
275
  });
@@ -249,6 +294,15 @@ class HazelApp {
249
294
  }
250
295
  throw error;
251
296
  }
297
+ // Proxy handlers (e.g. API gateway) - run before router
298
+ const pathname = (req.url || '/').split('?')[0];
299
+ for (const { pathPrefix, handler } of this.proxyHandlers) {
300
+ if (pathname === pathPrefix || pathname.startsWith(pathPrefix + '/')) {
301
+ const handled = await handler(req, res, context);
302
+ if (handled)
303
+ return;
304
+ }
305
+ }
252
306
  // Apply timeout middleware if configured
253
307
  if (this.timeoutMiddleware) {
254
308
  const timeoutPromise = new Promise((_, reject) => {
@@ -300,9 +354,9 @@ class HazelApp {
300
354
  this.shutdownManager.registerHandler({
301
355
  name: 'http-server',
302
356
  handler: async () => {
303
- logger_1.default.info('Closing HTTP server...');
357
+ logger_1.default.debug('Closing HTTP server...');
304
358
  await this.close();
305
- logger_1.default.info('HTTP server closed');
359
+ logger_1.default.debug('HTTP server closed');
306
360
  },
307
361
  timeout: 10000,
308
362
  });
@@ -325,9 +379,8 @@ class HazelApp {
325
379
  }
326
380
  catch (error) {
327
381
  const httpError = error;
328
- logger_1.default.error(`[${req.method}] ${req.url} - Route matching error: ${httpError.message} (status: ${httpError.statusCode || 404})`);
329
382
  if (process.env.NODE_ENV === 'development' && httpError.stack) {
330
- logger_1.default.debug(httpError.stack);
383
+ logger_1.default.debug(`Route not found: ${req.method} ${req.url}`, httpError.stack);
331
384
  }
332
385
  const status = httpError.statusCode || 404;
333
386
  res.writeHead(status, { 'Content-Type': 'application/json' });
@@ -337,11 +390,6 @@ class HazelApp {
337
390
  }));
338
391
  return;
339
392
  }
340
- logger_1.default.info('Matched route:', {
341
- method: req.method,
342
- url: req.url,
343
- params: context.params,
344
- });
345
393
  try {
346
394
  const response = new HttpResponse(res);
347
395
  const result = await route.handler(req, response);
@@ -402,7 +450,7 @@ class HazelApp {
402
450
  setRequestTimeout(timeout, options) {
403
451
  this.requestTimeout = timeout;
404
452
  this.timeoutMiddleware = new timeout_middleware_1.TimeoutMiddleware({ ...options, timeout });
405
- logger_1.default.info(`Request timeout set to ${timeout}ms`);
453
+ logger_1.default.debug(`Request timeout set to ${timeout}ms`);
406
454
  }
407
455
  /**
408
456
  * Enable CORS
@@ -410,7 +458,7 @@ class HazelApp {
410
458
  enableCors(options) {
411
459
  this.corsEnabled = true;
412
460
  this.corsOptions = options;
413
- logger_1.default.info('CORS enabled', options);
461
+ logger_1.default.debug('CORS enabled', options);
414
462
  }
415
463
  /**
416
464
  * Disable CORS
@@ -418,7 +466,7 @@ class HazelApp {
418
466
  disableCors() {
419
467
  this.corsEnabled = false;
420
468
  this.corsOptions = undefined;
421
- logger_1.default.info('CORS disabled');
469
+ logger_1.default.debug('CORS disabled');
422
470
  }
423
471
  /**
424
472
  * Get health check manager
@@ -438,6 +486,21 @@ class HazelApp {
438
486
  getRouter() {
439
487
  return this.router;
440
488
  }
489
+ /**
490
+ * Add an early HTTP handler (runs before body parsing, for GraphQL etc.)
491
+ */
492
+ addEarlyHandler(path, handler) {
493
+ this.earlyHandlers.push({ path, handler });
494
+ logger_1.default.debug('Early handler registered', { path });
495
+ }
496
+ /**
497
+ * Add a proxy handler (runs after body parsing, before router).
498
+ * Use with @hazeljs/gateway: app.addProxyHandler('/api', createGatewayHandler(gateway))
499
+ */
500
+ addProxyHandler(pathPrefix, handler) {
501
+ this.proxyHandlers.push({ pathPrefix, handler });
502
+ logger_1.default.debug('Proxy handler registered', { pathPrefix });
503
+ }
441
504
  }
442
505
  exports.HazelApp = HazelApp;
443
506
  function getLocalIp() {
@@ -1,8 +1,17 @@
1
1
  import 'reflect-metadata';
2
2
  import { Type } from './types';
3
3
  import { Container } from './container';
4
+ /** Dynamic module returned by forRoot() / forRootAsync() */
5
+ export interface DynamicModule {
6
+ module: Type<unknown>;
7
+ providers?: unknown[];
8
+ controllers?: Type<unknown>[];
9
+ imports?: (Type<unknown> | DynamicModule)[];
10
+ exports?: unknown[];
11
+ global?: boolean;
12
+ }
4
13
  export interface ModuleOptions {
5
- imports?: Type<unknown>[];
14
+ imports?: (Type<unknown> | DynamicModule)[];
6
15
  controllers?: Type<unknown>[];
7
16
  providers?: Type<unknown>[];
8
17
  exports?: Type<unknown>[];
@@ -13,7 +22,7 @@ export declare function getModuleMetadata(target: object): ModuleOptions | undef
13
22
  export declare class HazelModuleInstance {
14
23
  private readonly moduleType;
15
24
  private container;
16
- constructor(moduleType: Type<unknown>);
25
+ constructor(moduleType: Type<unknown> | DynamicModule);
17
26
  private initialize;
18
27
  getContainer(): Container;
19
28
  }
@@ -1 +1 @@
1
- {"version":3,"file":"hazel-module.d.ts","sourceRoot":"","sources":["../src/hazel-module.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAC/B,OAAO,EAAE,SAAS,EAAS,MAAM,aAAa,CAAC;AAM/C,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;IAC1B,WAAW,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;IAC9B,SAAS,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;IAC5B,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;CAC3B;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,cAAc,CAIlE;AAGD,eAAO,MAAM,MAAM,oBAAc,CAAC;AAElC,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAE3E;AAED,qBAAa,mBAAmB;IAGlB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAFvC,OAAO,CAAC,SAAS,CAAY;gBAEA,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC;IAMtD,OAAO,CAAC,UAAU;IA8FlB,YAAY,IAAI,SAAS;CAG1B"}
1
+ {"version":3,"file":"hazel-module.d.ts","sourceRoot":"","sources":["../src/hazel-module.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAC/B,OAAO,EAAE,SAAS,EAAS,MAAM,aAAa,CAAC;AAM/C,4DAA4D;AAC5D,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IACtB,SAAS,CAAC,EAAE,OAAO,EAAE,CAAC;IACtB,WAAW,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;IAC9B,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,aAAa,CAAC,EAAE,CAAC;IAC5C,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,aAAa,CAAC,EAAE,CAAC;IAC5C,WAAW,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;IAC9B,SAAS,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;IAC5B,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;CAC3B;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,cAAc,CAIlE;AAGD,eAAO,MAAM,MAAM,oBAAc,CAAC;AAElC,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAa3E;AAED,qBAAa,mBAAmB;IAGlB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAFvC,OAAO,CAAC,SAAS,CAAY;gBAEA,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,aAAa;IAOtE,OAAO,CAAC,UAAU;IA6GlB,YAAY,IAAI,SAAS;CAG1B"}