@hazeljs/core 0.2.0-alpha.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/LICENSE +192 -0
- package/README.md +560 -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 +1237 -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 +810 -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 +1183 -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 +166 -0
- package/dist/decorators.d.ts.map +1 -0
- package/dist/decorators.js +538 -0
- package/dist/errors/http.error.d.ts +34 -0
- package/dist/errors/http.error.d.ts.map +1 -0
- package/dist/errors/http.error.js +69 -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 +94 -0
- package/dist/hazel-app.d.ts.map +1 -0
- package/dist/hazel-app.js +516 -0
- package/dist/hazel-module.d.ts +29 -0
- package/dist/hazel-module.d.ts.map +1 -0
- package/dist/hazel-module.js +137 -0
- package/dist/hazel-response.d.ts +25 -0
- package/dist/hazel-response.d.ts.map +1 -0
- package/dist/hazel-response.js +89 -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 +159 -0
- package/dist/interceptors/interceptor.d.ts +30 -0
- package/dist/interceptors/interceptor.d.ts.map +1 -0
- package/dist/interceptors/interceptor.js +71 -0
- package/dist/logger.d.ts +8 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +261 -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 +22 -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 +506 -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 +82 -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 +67 -0
|
@@ -0,0 +1,516 @@
|
|
|
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
|
+
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
|
+
}
|
|
65
|
+
}
|
|
66
|
+
class HazelApp {
|
|
67
|
+
constructor(moduleType) {
|
|
68
|
+
this.moduleType = moduleType;
|
|
69
|
+
this.server = null;
|
|
70
|
+
this.requestTimeout = 30000; // 30 seconds default
|
|
71
|
+
this.corsEnabled = false;
|
|
72
|
+
this.earlyHandlers = [];
|
|
73
|
+
this.proxyHandlers = [];
|
|
74
|
+
logger_1.default.debug('Initializing HazelApp');
|
|
75
|
+
this.container = container_1.Container.getInstance();
|
|
76
|
+
this.container.register(HazelApp, this);
|
|
77
|
+
this.router = new router_1.Router(this.container);
|
|
78
|
+
this.requestParser = new request_parser_1.RequestParser();
|
|
79
|
+
this.module = new hazel_module_1.HazelModuleInstance(this.moduleType);
|
|
80
|
+
this.shutdownManager = new shutdown_1.ShutdownManager();
|
|
81
|
+
this.healthManager = new health_1.HealthCheckManager();
|
|
82
|
+
// Register built-in health checks
|
|
83
|
+
this.healthManager.registerCheck(health_1.BuiltInHealthChecks.memoryCheck());
|
|
84
|
+
this.healthManager.registerCheck(health_1.BuiltInHealthChecks.eventLoopCheck());
|
|
85
|
+
this.initialize();
|
|
86
|
+
}
|
|
87
|
+
initialize() {
|
|
88
|
+
logger_1.default.debug('Initializing module:', { moduleName: this.moduleType.name });
|
|
89
|
+
const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, this.moduleType) || {};
|
|
90
|
+
logger_1.default.debug('Module metadata:', metadata);
|
|
91
|
+
// Collect all controllers from the module tree (root + imports, recursively)
|
|
92
|
+
const allControllers = this.collectControllers(this.moduleType);
|
|
93
|
+
// Register all controllers with the router
|
|
94
|
+
if (allControllers.length > 0) {
|
|
95
|
+
logger_1.default.debug('Registering controllers:', {
|
|
96
|
+
controllers: allControllers.map((c) => c.name),
|
|
97
|
+
});
|
|
98
|
+
allControllers.forEach((controller) => {
|
|
99
|
+
this.router.registerController(controller);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
collectControllers(moduleRef, visited = new Set()) {
|
|
104
|
+
if (visited.has(moduleRef))
|
|
105
|
+
return [];
|
|
106
|
+
visited.add(moduleRef);
|
|
107
|
+
const metadata = (0, hazel_module_1.getModuleMetadata)(moduleRef) || {};
|
|
108
|
+
const controllers = [];
|
|
109
|
+
// Collect from imported modules first
|
|
110
|
+
if (metadata.imports) {
|
|
111
|
+
for (const importedModule of metadata.imports) {
|
|
112
|
+
controllers.push(...this.collectControllers(importedModule, visited));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Then collect from this module
|
|
116
|
+
if (metadata.controllers) {
|
|
117
|
+
controllers.push(...metadata.controllers);
|
|
118
|
+
}
|
|
119
|
+
return controllers;
|
|
120
|
+
}
|
|
121
|
+
register(component) {
|
|
122
|
+
const instance = new component();
|
|
123
|
+
this.container.register(component, instance);
|
|
124
|
+
return this;
|
|
125
|
+
}
|
|
126
|
+
get(path, ...handlers) {
|
|
127
|
+
this.router.get(path, handlers);
|
|
128
|
+
return this;
|
|
129
|
+
}
|
|
130
|
+
post(path, ...handlers) {
|
|
131
|
+
this.router.post(path, handlers);
|
|
132
|
+
return this;
|
|
133
|
+
}
|
|
134
|
+
put(path, ...handlers) {
|
|
135
|
+
this.router.put(path, handlers);
|
|
136
|
+
return this;
|
|
137
|
+
}
|
|
138
|
+
delete(path, ...handlers) {
|
|
139
|
+
this.router.delete(path, handlers);
|
|
140
|
+
return this;
|
|
141
|
+
}
|
|
142
|
+
async listen(port) {
|
|
143
|
+
return new Promise((resolve) => {
|
|
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
|
+
});
|
|
157
|
+
try {
|
|
158
|
+
if (!req.url) {
|
|
159
|
+
logger_1.default.warn('Invalid URL received');
|
|
160
|
+
res.writeHead(400);
|
|
161
|
+
res.end(JSON.stringify({ error: 'Invalid URL' }));
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
// Health check endpoints
|
|
165
|
+
if (req.url === '/health' && req.method === 'GET') {
|
|
166
|
+
const liveness = await this.healthManager.getLiveness();
|
|
167
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
168
|
+
res.end(JSON.stringify(liveness));
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (req.url === '/ready' && req.method === 'GET') {
|
|
172
|
+
const readiness = await this.healthManager.getReadiness();
|
|
173
|
+
const statusCode = readiness.status === 'healthy' ? 200 : 503;
|
|
174
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
175
|
+
res.end(JSON.stringify(readiness));
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (req.url === '/startup' && req.method === 'GET') {
|
|
179
|
+
const startup = await this.healthManager.getStartup();
|
|
180
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
181
|
+
res.end(JSON.stringify(startup));
|
|
182
|
+
return;
|
|
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
|
+
}
|
|
192
|
+
const { method, url, headers } = req;
|
|
193
|
+
logger_1.default.debug('Incoming request:', { method, url, headers });
|
|
194
|
+
// Handle CORS
|
|
195
|
+
if (this.corsEnabled) {
|
|
196
|
+
const origin = headers['origin'] || '*';
|
|
197
|
+
const allowedOrigin = this.corsOptions?.origin
|
|
198
|
+
? (typeof this.corsOptions.origin === 'string' ? this.corsOptions.origin : origin)
|
|
199
|
+
: '*';
|
|
200
|
+
res.setHeader('Access-Control-Allow-Origin', allowedOrigin);
|
|
201
|
+
res.setHeader('Access-Control-Allow-Methods', this.corsOptions?.methods?.join(', ') || 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
|
|
202
|
+
res.setHeader('Access-Control-Allow-Headers', this.corsOptions?.allowedHeaders?.join(', ') || 'Content-Type, Authorization');
|
|
203
|
+
if (this.corsOptions?.credentials) {
|
|
204
|
+
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
205
|
+
}
|
|
206
|
+
if (this.corsOptions?.maxAge) {
|
|
207
|
+
res.setHeader('Access-Control-Max-Age', String(this.corsOptions.maxAge));
|
|
208
|
+
}
|
|
209
|
+
// Handle preflight
|
|
210
|
+
if (method === 'OPTIONS') {
|
|
211
|
+
res.writeHead(204);
|
|
212
|
+
res.end();
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// Parse request body for POST/PUT/PATCH requests (skip multipart - let route handle it)
|
|
217
|
+
let body = undefined;
|
|
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) {
|
|
222
|
+
try {
|
|
223
|
+
const chunks = [];
|
|
224
|
+
req.on('data', (chunk) => chunks.push(chunk));
|
|
225
|
+
await new Promise((resolve, reject) => {
|
|
226
|
+
req.on('end', () => {
|
|
227
|
+
try {
|
|
228
|
+
const bodyStr = Buffer.concat(chunks).toString();
|
|
229
|
+
rawBody = bodyStr;
|
|
230
|
+
if (bodyStr) {
|
|
231
|
+
if (contentType.includes('application/json')) {
|
|
232
|
+
body = JSON.parse(bodyStr);
|
|
233
|
+
}
|
|
234
|
+
else if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
235
|
+
const params = new URLSearchParams(bodyStr);
|
|
236
|
+
body = Object.fromEntries(params.entries());
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
body = bodyStr;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
resolve();
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
reject(error);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
req.on('error', reject);
|
|
249
|
+
});
|
|
250
|
+
}
|
|
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
|
+
}
|
|
261
|
+
logger_1.default.error('Error parsing request body:', error);
|
|
262
|
+
if (!res.writableEnded) {
|
|
263
|
+
res.writeHead(400);
|
|
264
|
+
res.end(JSON.stringify({ error: 'Invalid request body' }));
|
|
265
|
+
}
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// Extend the original request object
|
|
270
|
+
Object.assign(req, {
|
|
271
|
+
body,
|
|
272
|
+
rawBody: rawBody || undefined,
|
|
273
|
+
params: {},
|
|
274
|
+
query: {},
|
|
275
|
+
});
|
|
276
|
+
let context;
|
|
277
|
+
try {
|
|
278
|
+
context = await request_parser_1.RequestParser.parseRequest(req);
|
|
279
|
+
logger_1.default.debug('Parsed request context:', context);
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
if (error instanceof http_error_1.HttpError) {
|
|
283
|
+
logger_1.default.error(`[${req.method}] ${req.url} - Request parsing error: ${error.message} (status: ${error.statusCode})`);
|
|
284
|
+
if (process.env.NODE_ENV === 'development' && error.stack) {
|
|
285
|
+
logger_1.default.debug(error.stack);
|
|
286
|
+
}
|
|
287
|
+
res.writeHead(error.statusCode, { 'Content-Type': 'application/json' });
|
|
288
|
+
res.end(JSON.stringify({
|
|
289
|
+
statusCode: error.statusCode,
|
|
290
|
+
message: error.message,
|
|
291
|
+
...(error.errors && { errors: error.errors }),
|
|
292
|
+
}));
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
throw error;
|
|
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
|
+
}
|
|
306
|
+
// Apply timeout middleware if configured
|
|
307
|
+
if (this.timeoutMiddleware) {
|
|
308
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
309
|
+
setTimeout(() => {
|
|
310
|
+
reject(new http_error_1.HttpError(408, 'Request Timeout'));
|
|
311
|
+
}, this.requestTimeout);
|
|
312
|
+
});
|
|
313
|
+
// Wrap remaining logic in a race against timeout
|
|
314
|
+
try {
|
|
315
|
+
await Promise.race([this.handleRoute(req, res, context), timeoutPromise]);
|
|
316
|
+
}
|
|
317
|
+
catch (timeoutError) {
|
|
318
|
+
if (timeoutError instanceof http_error_1.HttpError && timeoutError.statusCode === 408) {
|
|
319
|
+
res.writeHead(408, { 'Content-Type': 'application/json' });
|
|
320
|
+
res.end(JSON.stringify({ statusCode: 408, message: 'Request Timeout' }));
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
throw timeoutError;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
await this.handleRoute(req, res, context);
|
|
329
|
+
}
|
|
330
|
+
catch (error) {
|
|
331
|
+
logger_1.default.error('Unhandled error:', error);
|
|
332
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
333
|
+
res.end(JSON.stringify({
|
|
334
|
+
statusCode: 500,
|
|
335
|
+
message: 'Internal Server Error',
|
|
336
|
+
}));
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
this.server.listen(port, () => {
|
|
340
|
+
const localIp = getLocalIp();
|
|
341
|
+
logger_1.default.info(chalk_1.default.green.bold('Server listening on:'));
|
|
342
|
+
logger_1.default.info('');
|
|
343
|
+
logger_1.default.info(chalk_1.default.green(' → Local: ') + chalk_1.default.cyan.underline(`http://localhost:${port}`));
|
|
344
|
+
logger_1.default.info(chalk_1.default.green(' → Network: ') + chalk_1.default.cyan.underline(`http://${localIp}:${port}`));
|
|
345
|
+
logger_1.default.info('');
|
|
346
|
+
logger_1.default.info(chalk_1.default.gray('Health endpoints:'));
|
|
347
|
+
logger_1.default.info(chalk_1.default.gray(` → /health - Liveness probe`));
|
|
348
|
+
logger_1.default.info(chalk_1.default.gray(` → /ready - Readiness probe`));
|
|
349
|
+
logger_1.default.info(chalk_1.default.gray(` → /startup - Startup probe`));
|
|
350
|
+
logger_1.default.info('');
|
|
351
|
+
// Setup graceful shutdown
|
|
352
|
+
this.shutdownManager.setupSignalHandlers();
|
|
353
|
+
// Register server shutdown handler
|
|
354
|
+
this.shutdownManager.registerHandler({
|
|
355
|
+
name: 'http-server',
|
|
356
|
+
handler: async () => {
|
|
357
|
+
logger_1.default.debug('Closing HTTP server...');
|
|
358
|
+
await this.close();
|
|
359
|
+
logger_1.default.debug('HTTP server closed');
|
|
360
|
+
},
|
|
361
|
+
timeout: 10000,
|
|
362
|
+
});
|
|
363
|
+
resolve();
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
async handleRoute(req, res, context) {
|
|
368
|
+
let route;
|
|
369
|
+
try {
|
|
370
|
+
route = await this.router.match(req.method || 'GET', req.url || '/', context);
|
|
371
|
+
if (!route) {
|
|
372
|
+
if (req.url === '/.well-known/appspecific/com.chrome.devtools.json') {
|
|
373
|
+
res.writeHead(404);
|
|
374
|
+
res.end();
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
throw new Error('Route not found');
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
catch (error) {
|
|
381
|
+
const httpError = error;
|
|
382
|
+
if (process.env.NODE_ENV === 'development' && httpError.stack) {
|
|
383
|
+
logger_1.default.debug(`Route not found: ${req.method} ${req.url}`, httpError.stack);
|
|
384
|
+
}
|
|
385
|
+
const status = httpError.statusCode || 404;
|
|
386
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
387
|
+
res.end(JSON.stringify({
|
|
388
|
+
statusCode: status,
|
|
389
|
+
message: httpError.message,
|
|
390
|
+
}));
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
try {
|
|
394
|
+
const response = new HttpResponse(res);
|
|
395
|
+
const result = await route.handler(req, response);
|
|
396
|
+
logger_1.default.debug('Request handled successfully:', result);
|
|
397
|
+
if (result !== undefined) {
|
|
398
|
+
response.json(result);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
catch (error) {
|
|
402
|
+
logger_1.default.error('Error in route handler:', error instanceof http_error_1.HttpError);
|
|
403
|
+
if (error instanceof http_error_1.HttpError) {
|
|
404
|
+
logger_1.default.error('Error in route handler:', error.message);
|
|
405
|
+
res.writeHead(error.statusCode, { 'Content-Type': 'application/json' });
|
|
406
|
+
res.end(JSON.stringify({
|
|
407
|
+
statusCode: error.statusCode,
|
|
408
|
+
message: error.message,
|
|
409
|
+
...(error.errors && { errors: error.errors }),
|
|
410
|
+
}));
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
414
|
+
res.end(JSON.stringify({
|
|
415
|
+
statusCode: 500,
|
|
416
|
+
message: 'Internal Server Error',
|
|
417
|
+
}));
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
async close() {
|
|
421
|
+
if (this.server) {
|
|
422
|
+
return new Promise((resolve, reject) => {
|
|
423
|
+
this.server?.close((err) => {
|
|
424
|
+
if (err) {
|
|
425
|
+
reject(err);
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
logger_1.default.info('Server closed successfully');
|
|
429
|
+
resolve();
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Register a custom shutdown handler
|
|
437
|
+
*/
|
|
438
|
+
registerShutdownHandler(handler) {
|
|
439
|
+
this.shutdownManager.registerHandler(handler);
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Register a custom health check
|
|
443
|
+
*/
|
|
444
|
+
registerHealthCheck(check) {
|
|
445
|
+
this.healthManager.registerCheck(check);
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Set request timeout
|
|
449
|
+
*/
|
|
450
|
+
setRequestTimeout(timeout, options) {
|
|
451
|
+
this.requestTimeout = timeout;
|
|
452
|
+
this.timeoutMiddleware = new timeout_middleware_1.TimeoutMiddleware({ ...options, timeout });
|
|
453
|
+
logger_1.default.debug(`Request timeout set to ${timeout}ms`);
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Enable CORS
|
|
457
|
+
*/
|
|
458
|
+
enableCors(options) {
|
|
459
|
+
this.corsEnabled = true;
|
|
460
|
+
this.corsOptions = options;
|
|
461
|
+
logger_1.default.debug('CORS enabled', options);
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Disable CORS
|
|
465
|
+
*/
|
|
466
|
+
disableCors() {
|
|
467
|
+
this.corsEnabled = false;
|
|
468
|
+
this.corsOptions = undefined;
|
|
469
|
+
logger_1.default.debug('CORS disabled');
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Get health check manager
|
|
473
|
+
*/
|
|
474
|
+
getHealthManager() {
|
|
475
|
+
return this.healthManager;
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Get shutdown manager
|
|
479
|
+
*/
|
|
480
|
+
getShutdownManager() {
|
|
481
|
+
return this.shutdownManager;
|
|
482
|
+
}
|
|
483
|
+
getContainer() {
|
|
484
|
+
return this.container;
|
|
485
|
+
}
|
|
486
|
+
getRouter() {
|
|
487
|
+
return this.router;
|
|
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
|
+
}
|
|
504
|
+
}
|
|
505
|
+
exports.HazelApp = HazelApp;
|
|
506
|
+
function getLocalIp() {
|
|
507
|
+
const interfaces = os_1.default.networkInterfaces();
|
|
508
|
+
for (const name of Object.keys(interfaces)) {
|
|
509
|
+
for (const iface of interfaces[name] || []) {
|
|
510
|
+
if (iface.family === 'IPv4' && !iface.internal) {
|
|
511
|
+
return iface.address;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return 'localhost';
|
|
516
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { Type } from './types';
|
|
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
|
+
}
|
|
13
|
+
export interface ModuleOptions {
|
|
14
|
+
imports?: (Type<unknown> | DynamicModule)[];
|
|
15
|
+
controllers?: Type<unknown>[];
|
|
16
|
+
providers?: Type<unknown>[];
|
|
17
|
+
exports?: Type<unknown>[];
|
|
18
|
+
}
|
|
19
|
+
export declare function HazelModule(options: ModuleOptions): ClassDecorator;
|
|
20
|
+
export declare const Module: typeof HazelModule;
|
|
21
|
+
export declare function getModuleMetadata(target: object): ModuleOptions | undefined;
|
|
22
|
+
export declare class HazelModuleInstance {
|
|
23
|
+
private readonly moduleType;
|
|
24
|
+
private container;
|
|
25
|
+
constructor(moduleType: Type<unknown> | DynamicModule);
|
|
26
|
+
private initialize;
|
|
27
|
+
getContainer(): Container;
|
|
28
|
+
}
|
|
29
|
+
//# 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,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"}
|