@hazeljs/core 0.2.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +522 -0
- package/dist/__tests__/container.test.d.ts +2 -0
- package/dist/__tests__/container.test.d.ts.map +1 -0
- package/dist/__tests__/container.test.js +454 -0
- package/dist/__tests__/decorators.test.d.ts +2 -0
- package/dist/__tests__/decorators.test.d.ts.map +1 -0
- package/dist/__tests__/decorators.test.js +693 -0
- package/dist/__tests__/errors/http.error.test.d.ts +2 -0
- package/dist/__tests__/errors/http.error.test.d.ts.map +1 -0
- package/dist/__tests__/errors/http.error.test.js +117 -0
- package/dist/__tests__/filters/exception-filter.test.d.ts +2 -0
- package/dist/__tests__/filters/exception-filter.test.d.ts.map +1 -0
- package/dist/__tests__/filters/exception-filter.test.js +135 -0
- package/dist/__tests__/filters/http-exception.filter.test.d.ts +2 -0
- package/dist/__tests__/filters/http-exception.filter.test.d.ts.map +1 -0
- package/dist/__tests__/filters/http-exception.filter.test.js +119 -0
- package/dist/__tests__/hazel-app.test.d.ts +2 -0
- package/dist/__tests__/hazel-app.test.d.ts.map +1 -0
- package/dist/__tests__/hazel-app.test.js +682 -0
- package/dist/__tests__/hazel-module.test.d.ts +2 -0
- package/dist/__tests__/hazel-module.test.d.ts.map +1 -0
- package/dist/__tests__/hazel-module.test.js +408 -0
- package/dist/__tests__/hazel-response.test.d.ts +2 -0
- package/dist/__tests__/hazel-response.test.d.ts.map +1 -0
- package/dist/__tests__/hazel-response.test.js +138 -0
- package/dist/__tests__/health.test.d.ts +2 -0
- package/dist/__tests__/health.test.d.ts.map +1 -0
- package/dist/__tests__/health.test.js +147 -0
- package/dist/__tests__/index.test.d.ts +2 -0
- package/dist/__tests__/index.test.d.ts.map +1 -0
- package/dist/__tests__/index.test.js +239 -0
- package/dist/__tests__/interceptors/interceptor.test.d.ts +2 -0
- package/dist/__tests__/interceptors/interceptor.test.d.ts.map +1 -0
- package/dist/__tests__/interceptors/interceptor.test.js +166 -0
- package/dist/__tests__/logger.test.d.ts +2 -0
- package/dist/__tests__/logger.test.d.ts.map +1 -0
- package/dist/__tests__/logger.test.js +141 -0
- package/dist/__tests__/middleware/cors.test.d.ts +2 -0
- package/dist/__tests__/middleware/cors.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/cors.test.js +129 -0
- package/dist/__tests__/middleware/csrf.test.d.ts +2 -0
- package/dist/__tests__/middleware/csrf.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/csrf.test.js +247 -0
- package/dist/__tests__/middleware/global-middleware.test.d.ts +2 -0
- package/dist/__tests__/middleware/global-middleware.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/global-middleware.test.js +259 -0
- package/dist/__tests__/middleware/rate-limit.test.d.ts +2 -0
- package/dist/__tests__/middleware/rate-limit.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/rate-limit.test.js +264 -0
- package/dist/__tests__/middleware/security-headers.test.d.ts +2 -0
- package/dist/__tests__/middleware/security-headers.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/security-headers.test.js +229 -0
- package/dist/__tests__/middleware/timeout.test.d.ts +2 -0
- package/dist/__tests__/middleware/timeout.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/timeout.test.js +132 -0
- package/dist/__tests__/middleware.test.d.ts +2 -0
- package/dist/__tests__/middleware.test.d.ts.map +1 -0
- package/dist/__tests__/middleware.test.js +180 -0
- package/dist/__tests__/pipes/pipe.test.d.ts +2 -0
- package/dist/__tests__/pipes/pipe.test.d.ts.map +1 -0
- package/dist/__tests__/pipes/pipe.test.js +245 -0
- package/dist/__tests__/pipes/validation.pipe.test.d.ts +2 -0
- package/dist/__tests__/pipes/validation.pipe.test.d.ts.map +1 -0
- package/dist/__tests__/pipes/validation.pipe.test.js +297 -0
- package/dist/__tests__/request-parser.test.d.ts +2 -0
- package/dist/__tests__/request-parser.test.d.ts.map +1 -0
- package/dist/__tests__/request-parser.test.js +182 -0
- package/dist/__tests__/router.test.d.ts +2 -0
- package/dist/__tests__/router.test.d.ts.map +1 -0
- package/dist/__tests__/router.test.js +680 -0
- package/dist/__tests__/routing/route-matcher.test.d.ts +2 -0
- package/dist/__tests__/routing/route-matcher.test.d.ts.map +1 -0
- package/dist/__tests__/routing/route-matcher.test.js +219 -0
- package/dist/__tests__/routing/version.decorator.test.d.ts +2 -0
- package/dist/__tests__/routing/version.decorator.test.d.ts.map +1 -0
- package/dist/__tests__/routing/version.decorator.test.js +298 -0
- package/dist/__tests__/service.test.d.ts +2 -0
- package/dist/__tests__/service.test.d.ts.map +1 -0
- package/dist/__tests__/service.test.js +121 -0
- package/dist/__tests__/shutdown.test.d.ts +2 -0
- package/dist/__tests__/shutdown.test.d.ts.map +1 -0
- package/dist/__tests__/shutdown.test.js +250 -0
- package/dist/__tests__/testing/testing.module.test.d.ts +2 -0
- package/dist/__tests__/testing/testing.module.test.d.ts.map +1 -0
- package/dist/__tests__/testing/testing.module.test.js +370 -0
- package/dist/__tests__/upload/file-upload.test.d.ts +2 -0
- package/dist/__tests__/upload/file-upload.test.d.ts.map +1 -0
- package/dist/__tests__/upload/file-upload.test.js +498 -0
- package/dist/__tests__/utils/sanitize.test.d.ts +2 -0
- package/dist/__tests__/utils/sanitize.test.d.ts.map +1 -0
- package/dist/__tests__/utils/sanitize.test.js +291 -0
- package/dist/__tests__/validator.test.d.ts +2 -0
- package/dist/__tests__/validator.test.d.ts.map +1 -0
- package/dist/__tests__/validator.test.js +300 -0
- package/dist/container.d.ts +80 -0
- package/dist/container.d.ts.map +1 -0
- package/dist/container.js +271 -0
- package/dist/decorators.d.ts +92 -0
- package/dist/decorators.d.ts.map +1 -0
- package/dist/decorators.js +343 -0
- package/dist/errors/http.error.d.ts +31 -0
- package/dist/errors/http.error.d.ts.map +1 -0
- package/dist/errors/http.error.js +62 -0
- package/dist/filters/exception-filter.d.ts +39 -0
- package/dist/filters/exception-filter.d.ts.map +1 -0
- package/dist/filters/exception-filter.js +38 -0
- package/dist/filters/http-exception.filter.d.ts +9 -0
- package/dist/filters/http-exception.filter.d.ts.map +1 -0
- package/dist/filters/http-exception.filter.js +42 -0
- package/dist/hazel-app.d.ts +78 -0
- package/dist/hazel-app.d.ts.map +1 -0
- package/dist/hazel-app.js +453 -0
- package/dist/hazel-module.d.ts +20 -0
- package/dist/hazel-module.d.ts.map +1 -0
- package/dist/hazel-module.js +109 -0
- package/dist/hazel-response.d.ts +20 -0
- package/dist/hazel-response.d.ts.map +1 -0
- package/dist/hazel-response.js +68 -0
- package/dist/health.d.ts +73 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +174 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +140 -0
- package/dist/interceptors/interceptor.d.ts +22 -0
- package/dist/interceptors/interceptor.d.ts.map +1 -0
- package/dist/interceptors/interceptor.js +46 -0
- package/dist/logger.d.ts +8 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +238 -0
- package/dist/middleware/cors.middleware.d.ts +44 -0
- package/dist/middleware/cors.middleware.d.ts.map +1 -0
- package/dist/middleware/cors.middleware.js +118 -0
- package/dist/middleware/csrf.middleware.d.ts +82 -0
- package/dist/middleware/csrf.middleware.d.ts.map +1 -0
- package/dist/middleware/csrf.middleware.js +183 -0
- package/dist/middleware/global-middleware.d.ts +111 -0
- package/dist/middleware/global-middleware.d.ts.map +1 -0
- package/dist/middleware/global-middleware.js +179 -0
- package/dist/middleware/rate-limit.middleware.d.ts +73 -0
- package/dist/middleware/rate-limit.middleware.d.ts.map +1 -0
- package/dist/middleware/rate-limit.middleware.js +124 -0
- package/dist/middleware/security-headers.middleware.d.ts +76 -0
- package/dist/middleware/security-headers.middleware.d.ts.map +1 -0
- package/dist/middleware/security-headers.middleware.js +123 -0
- package/dist/middleware/timeout.middleware.d.ts +25 -0
- package/dist/middleware/timeout.middleware.d.ts.map +1 -0
- package/dist/middleware/timeout.middleware.js +74 -0
- package/dist/middleware.d.ts +13 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +47 -0
- package/dist/pipes/pipe.d.ts +50 -0
- package/dist/pipes/pipe.d.ts.map +1 -0
- package/dist/pipes/pipe.js +96 -0
- package/dist/pipes/validation.pipe.d.ts +6 -0
- package/dist/pipes/validation.pipe.d.ts.map +1 -0
- package/dist/pipes/validation.pipe.js +61 -0
- package/dist/request-context.d.ts +17 -0
- package/dist/request-context.d.ts.map +1 -0
- package/dist/request-context.js +2 -0
- package/dist/request-parser.d.ts +7 -0
- package/dist/request-parser.d.ts.map +1 -0
- package/dist/request-parser.js +60 -0
- package/dist/router.d.ts +33 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +426 -0
- package/dist/routing/route-matcher.d.ts +39 -0
- package/dist/routing/route-matcher.d.ts.map +1 -0
- package/dist/routing/route-matcher.js +93 -0
- package/dist/routing/version.decorator.d.ts +36 -0
- package/dist/routing/version.decorator.d.ts.map +1 -0
- package/dist/routing/version.decorator.js +89 -0
- package/dist/service.d.ts +9 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +39 -0
- package/dist/shutdown.d.ts +32 -0
- package/dist/shutdown.d.ts.map +1 -0
- package/dist/shutdown.js +109 -0
- package/dist/testing/testing.module.d.ts +83 -0
- package/dist/testing/testing.module.d.ts.map +1 -0
- package/dist/testing/testing.module.js +164 -0
- package/dist/types.d.ts +76 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/upload/file-upload.d.ts +75 -0
- package/dist/upload/file-upload.d.ts.map +1 -0
- package/dist/upload/file-upload.js +261 -0
- package/dist/utils/sanitize.d.ts +45 -0
- package/dist/utils/sanitize.d.ts.map +1 -0
- package/dist/utils/sanitize.js +165 -0
- package/dist/validator.d.ts +7 -0
- package/dist/validator.d.ts.map +1 -0
- package/dist/validator.js +119 -0
- package/package.json +65 -0
|
@@ -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;
|