@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.
Files changed (194) hide show
  1. package/README.md +522 -0
  2. package/dist/__tests__/container.test.d.ts +2 -0
  3. package/dist/__tests__/container.test.d.ts.map +1 -0
  4. package/dist/__tests__/container.test.js +454 -0
  5. package/dist/__tests__/decorators.test.d.ts +2 -0
  6. package/dist/__tests__/decorators.test.d.ts.map +1 -0
  7. package/dist/__tests__/decorators.test.js +693 -0
  8. package/dist/__tests__/errors/http.error.test.d.ts +2 -0
  9. package/dist/__tests__/errors/http.error.test.d.ts.map +1 -0
  10. package/dist/__tests__/errors/http.error.test.js +117 -0
  11. package/dist/__tests__/filters/exception-filter.test.d.ts +2 -0
  12. package/dist/__tests__/filters/exception-filter.test.d.ts.map +1 -0
  13. package/dist/__tests__/filters/exception-filter.test.js +135 -0
  14. package/dist/__tests__/filters/http-exception.filter.test.d.ts +2 -0
  15. package/dist/__tests__/filters/http-exception.filter.test.d.ts.map +1 -0
  16. package/dist/__tests__/filters/http-exception.filter.test.js +119 -0
  17. package/dist/__tests__/hazel-app.test.d.ts +2 -0
  18. package/dist/__tests__/hazel-app.test.d.ts.map +1 -0
  19. package/dist/__tests__/hazel-app.test.js +682 -0
  20. package/dist/__tests__/hazel-module.test.d.ts +2 -0
  21. package/dist/__tests__/hazel-module.test.d.ts.map +1 -0
  22. package/dist/__tests__/hazel-module.test.js +408 -0
  23. package/dist/__tests__/hazel-response.test.d.ts +2 -0
  24. package/dist/__tests__/hazel-response.test.d.ts.map +1 -0
  25. package/dist/__tests__/hazel-response.test.js +138 -0
  26. package/dist/__tests__/health.test.d.ts +2 -0
  27. package/dist/__tests__/health.test.d.ts.map +1 -0
  28. package/dist/__tests__/health.test.js +147 -0
  29. package/dist/__tests__/index.test.d.ts +2 -0
  30. package/dist/__tests__/index.test.d.ts.map +1 -0
  31. package/dist/__tests__/index.test.js +239 -0
  32. package/dist/__tests__/interceptors/interceptor.test.d.ts +2 -0
  33. package/dist/__tests__/interceptors/interceptor.test.d.ts.map +1 -0
  34. package/dist/__tests__/interceptors/interceptor.test.js +166 -0
  35. package/dist/__tests__/logger.test.d.ts +2 -0
  36. package/dist/__tests__/logger.test.d.ts.map +1 -0
  37. package/dist/__tests__/logger.test.js +141 -0
  38. package/dist/__tests__/middleware/cors.test.d.ts +2 -0
  39. package/dist/__tests__/middleware/cors.test.d.ts.map +1 -0
  40. package/dist/__tests__/middleware/cors.test.js +129 -0
  41. package/dist/__tests__/middleware/csrf.test.d.ts +2 -0
  42. package/dist/__tests__/middleware/csrf.test.d.ts.map +1 -0
  43. package/dist/__tests__/middleware/csrf.test.js +247 -0
  44. package/dist/__tests__/middleware/global-middleware.test.d.ts +2 -0
  45. package/dist/__tests__/middleware/global-middleware.test.d.ts.map +1 -0
  46. package/dist/__tests__/middleware/global-middleware.test.js +259 -0
  47. package/dist/__tests__/middleware/rate-limit.test.d.ts +2 -0
  48. package/dist/__tests__/middleware/rate-limit.test.d.ts.map +1 -0
  49. package/dist/__tests__/middleware/rate-limit.test.js +264 -0
  50. package/dist/__tests__/middleware/security-headers.test.d.ts +2 -0
  51. package/dist/__tests__/middleware/security-headers.test.d.ts.map +1 -0
  52. package/dist/__tests__/middleware/security-headers.test.js +229 -0
  53. package/dist/__tests__/middleware/timeout.test.d.ts +2 -0
  54. package/dist/__tests__/middleware/timeout.test.d.ts.map +1 -0
  55. package/dist/__tests__/middleware/timeout.test.js +132 -0
  56. package/dist/__tests__/middleware.test.d.ts +2 -0
  57. package/dist/__tests__/middleware.test.d.ts.map +1 -0
  58. package/dist/__tests__/middleware.test.js +180 -0
  59. package/dist/__tests__/pipes/pipe.test.d.ts +2 -0
  60. package/dist/__tests__/pipes/pipe.test.d.ts.map +1 -0
  61. package/dist/__tests__/pipes/pipe.test.js +245 -0
  62. package/dist/__tests__/pipes/validation.pipe.test.d.ts +2 -0
  63. package/dist/__tests__/pipes/validation.pipe.test.d.ts.map +1 -0
  64. package/dist/__tests__/pipes/validation.pipe.test.js +297 -0
  65. package/dist/__tests__/request-parser.test.d.ts +2 -0
  66. package/dist/__tests__/request-parser.test.d.ts.map +1 -0
  67. package/dist/__tests__/request-parser.test.js +182 -0
  68. package/dist/__tests__/router.test.d.ts +2 -0
  69. package/dist/__tests__/router.test.d.ts.map +1 -0
  70. package/dist/__tests__/router.test.js +680 -0
  71. package/dist/__tests__/routing/route-matcher.test.d.ts +2 -0
  72. package/dist/__tests__/routing/route-matcher.test.d.ts.map +1 -0
  73. package/dist/__tests__/routing/route-matcher.test.js +219 -0
  74. package/dist/__tests__/routing/version.decorator.test.d.ts +2 -0
  75. package/dist/__tests__/routing/version.decorator.test.d.ts.map +1 -0
  76. package/dist/__tests__/routing/version.decorator.test.js +298 -0
  77. package/dist/__tests__/service.test.d.ts +2 -0
  78. package/dist/__tests__/service.test.d.ts.map +1 -0
  79. package/dist/__tests__/service.test.js +121 -0
  80. package/dist/__tests__/shutdown.test.d.ts +2 -0
  81. package/dist/__tests__/shutdown.test.d.ts.map +1 -0
  82. package/dist/__tests__/shutdown.test.js +250 -0
  83. package/dist/__tests__/testing/testing.module.test.d.ts +2 -0
  84. package/dist/__tests__/testing/testing.module.test.d.ts.map +1 -0
  85. package/dist/__tests__/testing/testing.module.test.js +370 -0
  86. package/dist/__tests__/upload/file-upload.test.d.ts +2 -0
  87. package/dist/__tests__/upload/file-upload.test.d.ts.map +1 -0
  88. package/dist/__tests__/upload/file-upload.test.js +498 -0
  89. package/dist/__tests__/utils/sanitize.test.d.ts +2 -0
  90. package/dist/__tests__/utils/sanitize.test.d.ts.map +1 -0
  91. package/dist/__tests__/utils/sanitize.test.js +291 -0
  92. package/dist/__tests__/validator.test.d.ts +2 -0
  93. package/dist/__tests__/validator.test.d.ts.map +1 -0
  94. package/dist/__tests__/validator.test.js +300 -0
  95. package/dist/container.d.ts +80 -0
  96. package/dist/container.d.ts.map +1 -0
  97. package/dist/container.js +271 -0
  98. package/dist/decorators.d.ts +92 -0
  99. package/dist/decorators.d.ts.map +1 -0
  100. package/dist/decorators.js +343 -0
  101. package/dist/errors/http.error.d.ts +31 -0
  102. package/dist/errors/http.error.d.ts.map +1 -0
  103. package/dist/errors/http.error.js +62 -0
  104. package/dist/filters/exception-filter.d.ts +39 -0
  105. package/dist/filters/exception-filter.d.ts.map +1 -0
  106. package/dist/filters/exception-filter.js +38 -0
  107. package/dist/filters/http-exception.filter.d.ts +9 -0
  108. package/dist/filters/http-exception.filter.d.ts.map +1 -0
  109. package/dist/filters/http-exception.filter.js +42 -0
  110. package/dist/hazel-app.d.ts +78 -0
  111. package/dist/hazel-app.d.ts.map +1 -0
  112. package/dist/hazel-app.js +453 -0
  113. package/dist/hazel-module.d.ts +20 -0
  114. package/dist/hazel-module.d.ts.map +1 -0
  115. package/dist/hazel-module.js +109 -0
  116. package/dist/hazel-response.d.ts +20 -0
  117. package/dist/hazel-response.d.ts.map +1 -0
  118. package/dist/hazel-response.js +68 -0
  119. package/dist/health.d.ts +73 -0
  120. package/dist/health.d.ts.map +1 -0
  121. package/dist/health.js +174 -0
  122. package/dist/index.d.ts +41 -0
  123. package/dist/index.d.ts.map +1 -0
  124. package/dist/index.js +140 -0
  125. package/dist/interceptors/interceptor.d.ts +22 -0
  126. package/dist/interceptors/interceptor.d.ts.map +1 -0
  127. package/dist/interceptors/interceptor.js +46 -0
  128. package/dist/logger.d.ts +8 -0
  129. package/dist/logger.d.ts.map +1 -0
  130. package/dist/logger.js +238 -0
  131. package/dist/middleware/cors.middleware.d.ts +44 -0
  132. package/dist/middleware/cors.middleware.d.ts.map +1 -0
  133. package/dist/middleware/cors.middleware.js +118 -0
  134. package/dist/middleware/csrf.middleware.d.ts +82 -0
  135. package/dist/middleware/csrf.middleware.d.ts.map +1 -0
  136. package/dist/middleware/csrf.middleware.js +183 -0
  137. package/dist/middleware/global-middleware.d.ts +111 -0
  138. package/dist/middleware/global-middleware.d.ts.map +1 -0
  139. package/dist/middleware/global-middleware.js +179 -0
  140. package/dist/middleware/rate-limit.middleware.d.ts +73 -0
  141. package/dist/middleware/rate-limit.middleware.d.ts.map +1 -0
  142. package/dist/middleware/rate-limit.middleware.js +124 -0
  143. package/dist/middleware/security-headers.middleware.d.ts +76 -0
  144. package/dist/middleware/security-headers.middleware.d.ts.map +1 -0
  145. package/dist/middleware/security-headers.middleware.js +123 -0
  146. package/dist/middleware/timeout.middleware.d.ts +25 -0
  147. package/dist/middleware/timeout.middleware.d.ts.map +1 -0
  148. package/dist/middleware/timeout.middleware.js +74 -0
  149. package/dist/middleware.d.ts +13 -0
  150. package/dist/middleware.d.ts.map +1 -0
  151. package/dist/middleware.js +47 -0
  152. package/dist/pipes/pipe.d.ts +50 -0
  153. package/dist/pipes/pipe.d.ts.map +1 -0
  154. package/dist/pipes/pipe.js +96 -0
  155. package/dist/pipes/validation.pipe.d.ts +6 -0
  156. package/dist/pipes/validation.pipe.d.ts.map +1 -0
  157. package/dist/pipes/validation.pipe.js +61 -0
  158. package/dist/request-context.d.ts +17 -0
  159. package/dist/request-context.d.ts.map +1 -0
  160. package/dist/request-context.js +2 -0
  161. package/dist/request-parser.d.ts +7 -0
  162. package/dist/request-parser.d.ts.map +1 -0
  163. package/dist/request-parser.js +60 -0
  164. package/dist/router.d.ts +33 -0
  165. package/dist/router.d.ts.map +1 -0
  166. package/dist/router.js +426 -0
  167. package/dist/routing/route-matcher.d.ts +39 -0
  168. package/dist/routing/route-matcher.d.ts.map +1 -0
  169. package/dist/routing/route-matcher.js +93 -0
  170. package/dist/routing/version.decorator.d.ts +36 -0
  171. package/dist/routing/version.decorator.d.ts.map +1 -0
  172. package/dist/routing/version.decorator.js +89 -0
  173. package/dist/service.d.ts +9 -0
  174. package/dist/service.d.ts.map +1 -0
  175. package/dist/service.js +39 -0
  176. package/dist/shutdown.d.ts +32 -0
  177. package/dist/shutdown.d.ts.map +1 -0
  178. package/dist/shutdown.js +109 -0
  179. package/dist/testing/testing.module.d.ts +83 -0
  180. package/dist/testing/testing.module.d.ts.map +1 -0
  181. package/dist/testing/testing.module.js +164 -0
  182. package/dist/types.d.ts +76 -0
  183. package/dist/types.d.ts.map +1 -0
  184. package/dist/types.js +2 -0
  185. package/dist/upload/file-upload.d.ts +75 -0
  186. package/dist/upload/file-upload.d.ts.map +1 -0
  187. package/dist/upload/file-upload.js +261 -0
  188. package/dist/utils/sanitize.d.ts +45 -0
  189. package/dist/utils/sanitize.d.ts.map +1 -0
  190. package/dist/utils/sanitize.js +165 -0
  191. package/dist/validator.d.ts +7 -0
  192. package/dist/validator.d.ts.map +1 -0
  193. package/dist/validator.js +119 -0
  194. package/package.json +65 -0
@@ -0,0 +1,453 @@
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.HazelApp = void 0;
7
+ const hazel_module_1 = require("./hazel-module");
8
+ const container_1 = require("./container");
9
+ const router_1 = require("./router");
10
+ const request_parser_1 = require("./request-parser");
11
+ const http_1 = require("http");
12
+ const logger_1 = __importDefault(require("./logger"));
13
+ require("reflect-metadata");
14
+ const http_error_1 = require("./errors/http.error");
15
+ const os_1 = __importDefault(require("os"));
16
+ const chalk_1 = __importDefault(require("chalk"));
17
+ const shutdown_1 = require("./shutdown");
18
+ const health_1 = require("./health");
19
+ const timeout_middleware_1 = require("./middleware/timeout.middleware");
20
+ const MODULE_METADATA_KEY = 'hazel:module';
21
+ class HttpResponse {
22
+ constructor(res) {
23
+ this.res = res;
24
+ this.statusCode = 200;
25
+ this.headers = { 'Content-Type': 'application/json' };
26
+ this.headersSent = false;
27
+ }
28
+ status(code) {
29
+ this.statusCode = code;
30
+ return this;
31
+ }
32
+ json(data) {
33
+ if (this.headersSent)
34
+ return;
35
+ this.headersSent = true;
36
+ this.res.writeHead(this.statusCode, this.headers);
37
+ this.res.end(JSON.stringify(data));
38
+ }
39
+ send(data) {
40
+ if (!this.headersSent) {
41
+ this.headersSent = true;
42
+ this.res.writeHead(this.statusCode, this.headers);
43
+ }
44
+ this.res.write(data);
45
+ }
46
+ end() {
47
+ if (!this.headersSent) {
48
+ this.headersSent = true;
49
+ this.res.writeHead(this.statusCode, this.headers);
50
+ }
51
+ this.res.end();
52
+ }
53
+ setHeader(name, value) {
54
+ if (!this.headersSent) {
55
+ this.headers[name] = value;
56
+ }
57
+ }
58
+ }
59
+ class HazelApp {
60
+ constructor(moduleType) {
61
+ this.moduleType = moduleType;
62
+ this.server = null;
63
+ this.requestTimeout = 30000; // 30 seconds default
64
+ this.corsEnabled = false;
65
+ logger_1.default.info('Initializing HazelApp');
66
+ this.container = container_1.Container.getInstance();
67
+ this.router = new router_1.Router(this.container);
68
+ this.requestParser = new request_parser_1.RequestParser();
69
+ this.module = new hazel_module_1.HazelModuleInstance(this.moduleType);
70
+ this.shutdownManager = new shutdown_1.ShutdownManager();
71
+ this.healthManager = new health_1.HealthCheckManager();
72
+ // Register built-in health checks
73
+ this.healthManager.registerCheck(health_1.BuiltInHealthChecks.memoryCheck());
74
+ this.healthManager.registerCheck(health_1.BuiltInHealthChecks.eventLoopCheck());
75
+ this.initialize();
76
+ }
77
+ initialize() {
78
+ logger_1.default.info('Initializing module:', { moduleName: this.moduleType.name });
79
+ const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, this.moduleType) || {};
80
+ logger_1.default.debug('Module metadata:', metadata);
81
+ // Collect all controllers from the module tree (root + imports, recursively)
82
+ const allControllers = this.collectControllers(this.moduleType);
83
+ // Register all controllers with the router
84
+ if (allControllers.length > 0) {
85
+ logger_1.default.info('Registering controllers:', {
86
+ controllers: allControllers.map((c) => c.name),
87
+ });
88
+ allControllers.forEach((controller) => {
89
+ this.router.registerController(controller);
90
+ });
91
+ }
92
+ }
93
+ collectControllers(moduleType, visited = new Set()) {
94
+ if (visited.has(moduleType))
95
+ return [];
96
+ visited.add(moduleType);
97
+ const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, moduleType) || {};
98
+ const controllers = [];
99
+ // Collect from imported modules first
100
+ if (metadata.imports) {
101
+ for (const importedModule of metadata.imports) {
102
+ controllers.push(...this.collectControllers(importedModule, visited));
103
+ }
104
+ }
105
+ // Then collect from this module
106
+ if (metadata.controllers) {
107
+ controllers.push(...metadata.controllers);
108
+ }
109
+ return controllers;
110
+ }
111
+ register(component) {
112
+ const instance = new component();
113
+ this.container.register(component, instance);
114
+ return this;
115
+ }
116
+ get(path, ...handlers) {
117
+ this.router.get(path, handlers);
118
+ return this;
119
+ }
120
+ post(path, ...handlers) {
121
+ this.router.post(path, handlers);
122
+ return this;
123
+ }
124
+ put(path, ...handlers) {
125
+ this.router.put(path, handlers);
126
+ return this;
127
+ }
128
+ delete(path, ...handlers) {
129
+ this.router.delete(path, handlers);
130
+ return this;
131
+ }
132
+ async listen(port) {
133
+ return new Promise((resolve) => {
134
+ this.server = new http_1.Server(async (req, res) => {
135
+ try {
136
+ if (!req.url) {
137
+ logger_1.default.warn('Invalid URL received');
138
+ res.writeHead(400);
139
+ res.end(JSON.stringify({ error: 'Invalid URL' }));
140
+ return;
141
+ }
142
+ // Health check endpoints
143
+ if (req.url === '/health' && req.method === 'GET') {
144
+ const liveness = await this.healthManager.getLiveness();
145
+ res.writeHead(200, { 'Content-Type': 'application/json' });
146
+ res.end(JSON.stringify(liveness));
147
+ return;
148
+ }
149
+ if (req.url === '/ready' && req.method === 'GET') {
150
+ const readiness = await this.healthManager.getReadiness();
151
+ const statusCode = readiness.status === 'healthy' ? 200 : 503;
152
+ res.writeHead(statusCode, { 'Content-Type': 'application/json' });
153
+ res.end(JSON.stringify(readiness));
154
+ return;
155
+ }
156
+ if (req.url === '/startup' && req.method === 'GET') {
157
+ const startup = await this.healthManager.getStartup();
158
+ res.writeHead(200, { 'Content-Type': 'application/json' });
159
+ res.end(JSON.stringify(startup));
160
+ return;
161
+ }
162
+ const { method, url, headers } = req;
163
+ logger_1.default.info('Incoming request:', { method, url, headers });
164
+ // Handle CORS
165
+ if (this.corsEnabled) {
166
+ const origin = headers['origin'] || '*';
167
+ const allowedOrigin = this.corsOptions?.origin
168
+ ? (typeof this.corsOptions.origin === 'string' ? this.corsOptions.origin : origin)
169
+ : '*';
170
+ res.setHeader('Access-Control-Allow-Origin', allowedOrigin);
171
+ res.setHeader('Access-Control-Allow-Methods', this.corsOptions?.methods?.join(', ') || 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
172
+ res.setHeader('Access-Control-Allow-Headers', this.corsOptions?.allowedHeaders?.join(', ') || 'Content-Type, Authorization');
173
+ if (this.corsOptions?.credentials) {
174
+ res.setHeader('Access-Control-Allow-Credentials', 'true');
175
+ }
176
+ if (this.corsOptions?.maxAge) {
177
+ res.setHeader('Access-Control-Max-Age', String(this.corsOptions.maxAge));
178
+ }
179
+ // Handle preflight
180
+ if (method === 'OPTIONS') {
181
+ res.writeHead(204);
182
+ res.end();
183
+ return;
184
+ }
185
+ }
186
+ // Parse request body for POST/PUT/PATCH requests
187
+ let body = undefined;
188
+ if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
189
+ try {
190
+ const chunks = [];
191
+ req.on('data', (chunk) => chunks.push(chunk));
192
+ await new Promise((resolve, reject) => {
193
+ req.on('end', () => {
194
+ try {
195
+ const bodyStr = Buffer.concat(chunks).toString();
196
+ if (bodyStr) {
197
+ const contentType = headers['content-type'] || '';
198
+ if (contentType.includes('application/json')) {
199
+ body = JSON.parse(bodyStr);
200
+ }
201
+ else if (contentType.includes('application/x-www-form-urlencoded')) {
202
+ const params = new URLSearchParams(bodyStr);
203
+ body = Object.fromEntries(params.entries());
204
+ }
205
+ else {
206
+ body = bodyStr;
207
+ }
208
+ }
209
+ resolve();
210
+ }
211
+ catch (error) {
212
+ reject(error);
213
+ }
214
+ });
215
+ req.on('error', reject);
216
+ });
217
+ }
218
+ catch (error) {
219
+ logger_1.default.error('Error parsing request body:', error);
220
+ res.writeHead(400);
221
+ res.end(JSON.stringify({ error: 'Invalid request body' }));
222
+ return;
223
+ }
224
+ }
225
+ // Extend the original request object
226
+ Object.assign(req, {
227
+ body,
228
+ params: {},
229
+ query: {},
230
+ });
231
+ let context;
232
+ try {
233
+ context = await request_parser_1.RequestParser.parseRequest(req);
234
+ logger_1.default.debug('Parsed request context:', context);
235
+ }
236
+ catch (error) {
237
+ if (error instanceof http_error_1.HttpError) {
238
+ logger_1.default.error(`[${req.method}] ${req.url} - Request parsing error: ${error.message} (status: ${error.statusCode})`);
239
+ if (process.env.NODE_ENV === 'development' && error.stack) {
240
+ logger_1.default.debug(error.stack);
241
+ }
242
+ res.writeHead(error.statusCode, { 'Content-Type': 'application/json' });
243
+ res.end(JSON.stringify({
244
+ statusCode: error.statusCode,
245
+ message: error.message,
246
+ ...(error.errors && { errors: error.errors }),
247
+ }));
248
+ return;
249
+ }
250
+ throw error;
251
+ }
252
+ // Apply timeout middleware if configured
253
+ if (this.timeoutMiddleware) {
254
+ const timeoutPromise = new Promise((_, reject) => {
255
+ setTimeout(() => {
256
+ reject(new http_error_1.HttpError(408, 'Request Timeout'));
257
+ }, this.requestTimeout);
258
+ });
259
+ // Wrap remaining logic in a race against timeout
260
+ try {
261
+ await Promise.race([this.handleRoute(req, res, context), timeoutPromise]);
262
+ }
263
+ catch (timeoutError) {
264
+ if (timeoutError instanceof http_error_1.HttpError && timeoutError.statusCode === 408) {
265
+ res.writeHead(408, { 'Content-Type': 'application/json' });
266
+ res.end(JSON.stringify({ statusCode: 408, message: 'Request Timeout' }));
267
+ }
268
+ else {
269
+ throw timeoutError;
270
+ }
271
+ }
272
+ return;
273
+ }
274
+ await this.handleRoute(req, res, context);
275
+ }
276
+ catch (error) {
277
+ logger_1.default.error('Unhandled error:', error);
278
+ res.writeHead(500, { 'Content-Type': 'application/json' });
279
+ res.end(JSON.stringify({
280
+ statusCode: 500,
281
+ message: 'Internal Server Error',
282
+ }));
283
+ }
284
+ });
285
+ this.server.listen(port, () => {
286
+ const localIp = getLocalIp();
287
+ logger_1.default.info(chalk_1.default.green.bold('Server listening on:'));
288
+ logger_1.default.info('');
289
+ logger_1.default.info(chalk_1.default.green(' → Local: ') + chalk_1.default.cyan.underline(`http://localhost:${port}`));
290
+ logger_1.default.info(chalk_1.default.green(' → Network: ') + chalk_1.default.cyan.underline(`http://${localIp}:${port}`));
291
+ logger_1.default.info('');
292
+ logger_1.default.info(chalk_1.default.gray('Health endpoints:'));
293
+ logger_1.default.info(chalk_1.default.gray(` → /health - Liveness probe`));
294
+ logger_1.default.info(chalk_1.default.gray(` → /ready - Readiness probe`));
295
+ logger_1.default.info(chalk_1.default.gray(` → /startup - Startup probe`));
296
+ logger_1.default.info('');
297
+ // Setup graceful shutdown
298
+ this.shutdownManager.setupSignalHandlers();
299
+ // Register server shutdown handler
300
+ this.shutdownManager.registerHandler({
301
+ name: 'http-server',
302
+ handler: async () => {
303
+ logger_1.default.info('Closing HTTP server...');
304
+ await this.close();
305
+ logger_1.default.info('HTTP server closed');
306
+ },
307
+ timeout: 10000,
308
+ });
309
+ resolve();
310
+ });
311
+ });
312
+ }
313
+ async handleRoute(req, res, context) {
314
+ let route;
315
+ try {
316
+ route = await this.router.match(req.method || 'GET', req.url || '/', context);
317
+ if (!route) {
318
+ if (req.url === '/.well-known/appspecific/com.chrome.devtools.json') {
319
+ res.writeHead(404);
320
+ res.end();
321
+ return;
322
+ }
323
+ throw new Error('Route not found');
324
+ }
325
+ }
326
+ catch (error) {
327
+ const httpError = error;
328
+ logger_1.default.error(`[${req.method}] ${req.url} - Route matching error: ${httpError.message} (status: ${httpError.statusCode || 404})`);
329
+ if (process.env.NODE_ENV === 'development' && httpError.stack) {
330
+ logger_1.default.debug(httpError.stack);
331
+ }
332
+ const status = httpError.statusCode || 404;
333
+ res.writeHead(status, { 'Content-Type': 'application/json' });
334
+ res.end(JSON.stringify({
335
+ statusCode: status,
336
+ message: httpError.message,
337
+ }));
338
+ return;
339
+ }
340
+ logger_1.default.info('Matched route:', {
341
+ method: req.method,
342
+ url: req.url,
343
+ params: context.params,
344
+ });
345
+ try {
346
+ const response = new HttpResponse(res);
347
+ const result = await route.handler(req, response);
348
+ logger_1.default.debug('Request handled successfully:', result);
349
+ if (result !== undefined) {
350
+ response.json(result);
351
+ }
352
+ }
353
+ catch (error) {
354
+ logger_1.default.error('Error in route handler:', error instanceof http_error_1.HttpError);
355
+ if (error instanceof http_error_1.HttpError) {
356
+ logger_1.default.error('Error in route handler:', error.message);
357
+ res.writeHead(error.statusCode, { 'Content-Type': 'application/json' });
358
+ res.end(JSON.stringify({
359
+ statusCode: error.statusCode,
360
+ message: error.message,
361
+ ...(error.errors && { errors: error.errors }),
362
+ }));
363
+ return;
364
+ }
365
+ res.writeHead(500, { 'Content-Type': 'application/json' });
366
+ res.end(JSON.stringify({
367
+ statusCode: 500,
368
+ message: 'Internal Server Error',
369
+ }));
370
+ }
371
+ }
372
+ async close() {
373
+ if (this.server) {
374
+ return new Promise((resolve, reject) => {
375
+ this.server?.close((err) => {
376
+ if (err) {
377
+ reject(err);
378
+ }
379
+ else {
380
+ logger_1.default.info('Server closed successfully');
381
+ resolve();
382
+ }
383
+ });
384
+ });
385
+ }
386
+ }
387
+ /**
388
+ * Register a custom shutdown handler
389
+ */
390
+ registerShutdownHandler(handler) {
391
+ this.shutdownManager.registerHandler(handler);
392
+ }
393
+ /**
394
+ * Register a custom health check
395
+ */
396
+ registerHealthCheck(check) {
397
+ this.healthManager.registerCheck(check);
398
+ }
399
+ /**
400
+ * Set request timeout
401
+ */
402
+ setRequestTimeout(timeout, options) {
403
+ this.requestTimeout = timeout;
404
+ this.timeoutMiddleware = new timeout_middleware_1.TimeoutMiddleware({ ...options, timeout });
405
+ logger_1.default.info(`Request timeout set to ${timeout}ms`);
406
+ }
407
+ /**
408
+ * Enable CORS
409
+ */
410
+ enableCors(options) {
411
+ this.corsEnabled = true;
412
+ this.corsOptions = options;
413
+ logger_1.default.info('CORS enabled', options);
414
+ }
415
+ /**
416
+ * Disable CORS
417
+ */
418
+ disableCors() {
419
+ this.corsEnabled = false;
420
+ this.corsOptions = undefined;
421
+ logger_1.default.info('CORS disabled');
422
+ }
423
+ /**
424
+ * Get health check manager
425
+ */
426
+ getHealthManager() {
427
+ return this.healthManager;
428
+ }
429
+ /**
430
+ * Get shutdown manager
431
+ */
432
+ getShutdownManager() {
433
+ return this.shutdownManager;
434
+ }
435
+ getContainer() {
436
+ return this.container;
437
+ }
438
+ getRouter() {
439
+ return this.router;
440
+ }
441
+ }
442
+ exports.HazelApp = HazelApp;
443
+ function getLocalIp() {
444
+ const interfaces = os_1.default.networkInterfaces();
445
+ for (const name of Object.keys(interfaces)) {
446
+ for (const iface of interfaces[name] || []) {
447
+ if (iface.family === 'IPv4' && !iface.internal) {
448
+ return iface.address;
449
+ }
450
+ }
451
+ }
452
+ return 'localhost';
453
+ }
@@ -0,0 +1,20 @@
1
+ import 'reflect-metadata';
2
+ import { Type } from './types';
3
+ import { Container } from './container';
4
+ export interface ModuleOptions {
5
+ imports?: Type<unknown>[];
6
+ controllers?: Type<unknown>[];
7
+ providers?: Type<unknown>[];
8
+ exports?: Type<unknown>[];
9
+ }
10
+ export declare function HazelModule(options: ModuleOptions): ClassDecorator;
11
+ export declare const Module: typeof HazelModule;
12
+ export declare function getModuleMetadata(target: object): ModuleOptions | undefined;
13
+ export declare class HazelModuleInstance {
14
+ private readonly moduleType;
15
+ private container;
16
+ constructor(moduleType: Type<unknown>);
17
+ private initialize;
18
+ getContainer(): Container;
19
+ }
20
+ //# sourceMappingURL=hazel-module.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,109 @@
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.HazelModuleInstance = exports.Module = void 0;
7
+ exports.HazelModule = HazelModule;
8
+ exports.getModuleMetadata = getModuleMetadata;
9
+ require("reflect-metadata");
10
+ const container_1 = require("./container");
11
+ const logger_1 = __importDefault(require("./logger"));
12
+ const MODULE_METADATA_KEY = 'hazel:module';
13
+ const ROUTE_METADATA_KEY = 'hazel:route';
14
+ function HazelModule(options) {
15
+ return (target) => {
16
+ Reflect.defineMetadata(MODULE_METADATA_KEY, options, target);
17
+ };
18
+ }
19
+ // Alias for backward compatibility
20
+ exports.Module = HazelModule;
21
+ function getModuleMetadata(target) {
22
+ return Reflect.getMetadata(MODULE_METADATA_KEY, target);
23
+ }
24
+ class HazelModuleInstance {
25
+ constructor(moduleType) {
26
+ this.moduleType = moduleType;
27
+ logger_1.default.debug(`Initializing HazelModule: ${moduleType.name}`);
28
+ this.container = container_1.Container.getInstance();
29
+ this.initialize();
30
+ }
31
+ initialize() {
32
+ const metadata = getModuleMetadata(this.moduleType) || {};
33
+ logger_1.default.debug('Module metadata:', metadata);
34
+ // Register providers
35
+ if (metadata.providers) {
36
+ logger_1.default.debug('Registering providers:', metadata.providers.map((p) => p.name));
37
+ metadata.providers.forEach((provider) => {
38
+ logger_1.default.debug(`Registering provider: ${provider.name}`);
39
+ // Check if provider is request-scoped
40
+ const scope = Reflect.getMetadata('hazel:scope', provider);
41
+ if (scope === 'request') {
42
+ // Don't eagerly resolve request-scoped providers
43
+ // They will be resolved per-request by the container
44
+ logger_1.default.debug(`Skipping eager resolution for request-scoped provider: ${provider.name}`);
45
+ // Just register the class itself, not an instance
46
+ this.container.registerProvider({
47
+ token: provider,
48
+ useClass: provider,
49
+ scope: container_1.Scope.REQUEST,
50
+ });
51
+ }
52
+ else {
53
+ // Eagerly resolve singleton and transient providers
54
+ this.container.register(provider, this.container.resolve(provider));
55
+ }
56
+ });
57
+ }
58
+ // Register controllers
59
+ if (metadata.controllers) {
60
+ logger_1.default.debug('Registering controllers:', metadata.controllers.map((c) => c.name));
61
+ metadata.controllers.forEach((controller) => {
62
+ logger_1.default.debug(`Registering controller: ${controller.name}`);
63
+ // Check if controller has request-scoped dependencies
64
+ const paramTypes = Reflect.getMetadata('design:paramtypes', controller) || [];
65
+ const hasRequestScopedDeps = paramTypes.some((paramType) => {
66
+ if (!paramType)
67
+ return false;
68
+ const scope = Reflect.getMetadata('hazel:scope', paramType);
69
+ return scope === 'request';
70
+ });
71
+ if (hasRequestScopedDeps) {
72
+ // Don't eagerly resolve controllers with request-scoped dependencies
73
+ logger_1.default.debug(`Skipping eager resolution for controller with request-scoped deps: ${controller.name}`);
74
+ // Register as a class provider so it gets resolved per-request
75
+ this.container.registerProvider({
76
+ token: controller,
77
+ useClass: controller,
78
+ scope: container_1.Scope.SINGLETON, // Controller itself is singleton, but deps are request-scoped
79
+ });
80
+ }
81
+ else {
82
+ // Eagerly resolve controllers without request-scoped dependencies
83
+ const instance = this.container.resolve(controller);
84
+ this.container.register(controller, instance);
85
+ }
86
+ // Register controller routes
87
+ const prototype = controller.prototype;
88
+ const methodNames = Object.getOwnPropertyNames(prototype).filter((name) => name !== 'constructor' && typeof prototype[name] === 'function');
89
+ methodNames.forEach((methodName) => {
90
+ const route = Reflect.getMetadata(ROUTE_METADATA_KEY, prototype, methodName);
91
+ if (route) {
92
+ logger_1.default.debug(`Registering route: ${route.method} ${route.path}`);
93
+ }
94
+ });
95
+ });
96
+ }
97
+ // Initialize imported modules
98
+ if (metadata.imports) {
99
+ logger_1.default.debug('Initializing imported modules:', metadata.imports.map((m) => m.name));
100
+ metadata.imports.forEach((moduleType) => {
101
+ new HazelModuleInstance(moduleType);
102
+ });
103
+ }
104
+ }
105
+ getContainer() {
106
+ return this.container;
107
+ }
108
+ }
109
+ exports.HazelModuleInstance = HazelModuleInstance;
@@ -0,0 +1,20 @@
1
+ import { Response } from './types';
2
+ export interface HazelResponse {
3
+ setHeader(name: string, value: string): void;
4
+ write(chunk: string): void;
5
+ end(): void;
6
+ status(code: number): HazelResponse;
7
+ json(data: unknown): void;
8
+ }
9
+ export declare class HazelExpressResponse implements HazelResponse {
10
+ private res;
11
+ private isStreaming;
12
+ private headersSent;
13
+ constructor(res: Response);
14
+ setHeader(name: string, value: string): void;
15
+ write(chunk: string): void;
16
+ end(): void;
17
+ status(code: number): HazelResponse;
18
+ json(data: unknown): void;
19
+ }
20
+ //# sourceMappingURL=hazel-response.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hazel-response.d.ts","sourceRoot":"","sources":["../src/hazel-response.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,GAAG,IAAI,IAAI,CAAC;IACZ,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAAC;IACpC,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;CAC3B;AAED,qBAAa,oBAAqB,YAAW,aAAa;IAI5C,OAAO,CAAC,GAAG;IAHvB,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,WAAW,CAAkB;gBAEjB,GAAG,EAAE,QAAQ;IAEjC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAM5C,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAY1B,GAAG,IAAI,IAAI;IAMX,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa;IAOnC,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;CA+B1B"}
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HazelExpressResponse = void 0;
4
+ class HazelExpressResponse {
5
+ constructor(res) {
6
+ this.res = res;
7
+ this.isStreaming = false;
8
+ this.headersSent = false;
9
+ }
10
+ setHeader(name, value) {
11
+ if (!this.headersSent) {
12
+ this.res.setHeader(name, value);
13
+ }
14
+ }
15
+ write(chunk) {
16
+ if (!this.isStreaming) {
17
+ this.isStreaming = true;
18
+ this.headersSent = true;
19
+ this.res.setHeader('Content-Type', 'text/plain');
20
+ this.res.setHeader('Transfer-Encoding', 'chunked');
21
+ this.res.send(chunk);
22
+ }
23
+ else {
24
+ this.res.send(chunk);
25
+ }
26
+ }
27
+ end() {
28
+ if (this.isStreaming) {
29
+ this.res.end();
30
+ }
31
+ }
32
+ status(code) {
33
+ if (!this.headersSent) {
34
+ this.res.status(code);
35
+ }
36
+ return this;
37
+ }
38
+ json(data) {
39
+ if (this.isStreaming || this.headersSent) {
40
+ return; // Don't try to send JSON if we're already streaming or headers are sent
41
+ }
42
+ try {
43
+ if (data && typeof data === 'object') {
44
+ // Handle error responses specially
45
+ if ('error' in data) {
46
+ this.res.json({ error: data.error });
47
+ return;
48
+ }
49
+ // For other objects, use a safe replacer
50
+ const safeData = JSON.parse(JSON.stringify(data, (key, value) => {
51
+ if (key === 'res' || value === this.res) {
52
+ return '[Response Object]';
53
+ }
54
+ return value;
55
+ }));
56
+ this.res.json(safeData);
57
+ }
58
+ else {
59
+ this.res.json(data);
60
+ }
61
+ }
62
+ catch {
63
+ // If JSON stringify fails, send a simple error message
64
+ this.res.json({ error: 'Failed to serialize response' });
65
+ }
66
+ }
67
+ }
68
+ exports.HazelExpressResponse = HazelExpressResponse;