azurajs 3.0.1 → 3.0.2

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 (106) hide show
  1. package/dist/config/index.js +128 -6
  2. package/dist/config/index.js.map +1 -1
  3. package/dist/config/index.mjs +130 -1
  4. package/dist/config/index.mjs.map +1 -1
  5. package/dist/core/index.js +1100 -11
  6. package/dist/core/index.js.map +1 -1
  7. package/dist/core/index.mjs +1102 -3
  8. package/dist/core/index.mjs.map +1 -1
  9. package/dist/decorators/index.js +117 -87
  10. package/dist/decorators/index.js.map +1 -1
  11. package/dist/decorators/index.mjs +98 -1
  12. package/dist/decorators/index.mjs.map +1 -1
  13. package/dist/index.js +2592 -236
  14. package/dist/index.js.map +1 -1
  15. package/dist/index.mjs +2537 -9
  16. package/dist/index.mjs.map +1 -1
  17. package/dist/middleware/index.js +16 -7
  18. package/dist/middleware/index.js.map +1 -1
  19. package/dist/middleware/index.mjs +17 -1
  20. package/dist/middleware/index.mjs.map +1 -1
  21. package/dist/plugins/index.js +1056 -73
  22. package/dist/plugins/index.js.map +1 -1
  23. package/dist/plugins/index.mjs +1042 -1
  24. package/dist/plugins/index.mjs.map +1 -1
  25. package/dist/types/index.js +49 -12
  26. package/dist/types/index.js.map +1 -1
  27. package/dist/types/index.mjs +49 -2
  28. package/dist/types/index.mjs.map +1 -1
  29. package/dist/utils/index.js +551 -50
  30. package/dist/utils/index.js.map +1 -1
  31. package/dist/utils/index.mjs +541 -3
  32. package/dist/utils/index.mjs.map +1 -1
  33. package/package.json +35 -17
  34. package/{dist/chunk-DR254CWJ.mjs → src/config/ConfigModule.ts} +169 -132
  35. package/src/config/index.ts +1 -0
  36. package/src/core/index.ts +2 -0
  37. package/src/core/router.ts +284 -0
  38. package/{dist/chunk-EYAHUNC7.mjs → src/core/server.ts} +590 -699
  39. package/src/decorators/Route.ts +110 -0
  40. package/src/decorators/index.ts +23 -0
  41. package/src/index.ts +12 -0
  42. package/src/middleware/LoggingMiddleware.ts +20 -0
  43. package/src/middleware/index.ts +1 -0
  44. package/src/plugins/CORSPlugin.ts +56 -0
  45. package/src/plugins/CircuitBreakerPlugin.ts +84 -0
  46. package/src/plugins/CompressionPlugin.ts +80 -0
  47. package/src/plugins/ETagPlugin.ts +31 -0
  48. package/src/plugins/HealthCheckPlugin.ts +57 -0
  49. package/src/plugins/HelmetPlugin.ts +89 -0
  50. package/src/plugins/JWTPlugin.ts +132 -0
  51. package/src/plugins/MultipartPlugin.ts +168 -0
  52. package/src/plugins/ProxyPlugin.ts +89 -0
  53. package/src/plugins/RateLimitPlugin.ts +96 -0
  54. package/src/plugins/RequestIdPlugin.ts +21 -0
  55. package/src/plugins/SSEPlugin.ts +114 -0
  56. package/src/plugins/SessionPlugin.ts +98 -0
  57. package/src/plugins/StaticPlugin.ts +152 -0
  58. package/src/plugins/TimeoutPlugin.ts +33 -0
  59. package/src/plugins/index.ts +18 -0
  60. package/src/types/common.type.ts +82 -0
  61. package/src/types/config.type.ts +57 -0
  62. package/{dist/chunk-OWUGAI5V.mjs → src/types/http/status.ts} +49 -51
  63. package/src/types/index.ts +55 -0
  64. package/src/types/plugins/plugin.type.ts +170 -0
  65. package/src/types/reflect.d.ts +14 -0
  66. package/src/types/routes.type.ts +70 -0
  67. package/src/utils/HttpError.ts +62 -0
  68. package/src/utils/IpResolver.ts +30 -0
  69. package/src/utils/Logger.ts +144 -0
  70. package/src/utils/Parser.ts +182 -0
  71. package/src/utils/cookies/CookieManager.ts +48 -0
  72. package/src/utils/index.ts +9 -0
  73. package/{dist/chunk-UWIFSGSQ.mjs → src/utils/validators/DTOValidator.ts} +145 -141
  74. package/src/utils/validators/SchemaValidator.ts +45 -0
  75. package/dist/chunk-3UFAWS2V.js +0 -392
  76. package/dist/chunk-3UFAWS2V.js.map +0 -1
  77. package/dist/chunk-4LSFAAZW.js +0 -4
  78. package/dist/chunk-4LSFAAZW.js.map +0 -1
  79. package/dist/chunk-7NSRIVZM.js +0 -54
  80. package/dist/chunk-7NSRIVZM.js.map +0 -1
  81. package/dist/chunk-AOG6NYAM.js +0 -144
  82. package/dist/chunk-AOG6NYAM.js.map +0 -1
  83. package/dist/chunk-DR254CWJ.mjs.map +0 -1
  84. package/dist/chunk-EYAHUNC7.mjs.map +0 -1
  85. package/dist/chunk-HHDQPIJN.mjs +0 -19
  86. package/dist/chunk-HHDQPIJN.mjs.map +0 -1
  87. package/dist/chunk-HHZNAGGI.js +0 -702
  88. package/dist/chunk-HHZNAGGI.js.map +0 -1
  89. package/dist/chunk-KJM5XCAY.js +0 -21
  90. package/dist/chunk-KJM5XCAY.js.map +0 -1
  91. package/dist/chunk-NLSZKAPA.mjs +0 -1044
  92. package/dist/chunk-NLSZKAPA.mjs.map +0 -1
  93. package/dist/chunk-OWUGAI5V.mjs.map +0 -1
  94. package/dist/chunk-POPNQEOK.js +0 -1063
  95. package/dist/chunk-POPNQEOK.js.map +0 -1
  96. package/dist/chunk-QPRW4YU4.js +0 -134
  97. package/dist/chunk-QPRW4YU4.js.map +0 -1
  98. package/dist/chunk-REJDZUZ5.mjs +0 -382
  99. package/dist/chunk-REJDZUZ5.mjs.map +0 -1
  100. package/dist/chunk-TC6N6TJZ.mjs +0 -100
  101. package/dist/chunk-TC6N6TJZ.mjs.map +0 -1
  102. package/dist/chunk-TEUXKMXP.js +0 -122
  103. package/dist/chunk-TEUXKMXP.js.map +0 -1
  104. package/dist/chunk-UWIFSGSQ.mjs.map +0 -1
  105. package/dist/chunk-YPBKY4KY.mjs +0 -3
  106. package/dist/chunk-YPBKY4KY.mjs.map +0 -1
@@ -1,699 +1,590 @@
1
- import { Logger, parseCookies, resolveIp, serializeCookie, clearCookieHeader, parseUrl, parseQueryString, HttpError, parseBody } from './chunk-REJDZUZ5.mjs';
2
- import { createServer as createServer$1 } from 'http';
3
- import { createServer } from 'https';
4
- import { readFileSync } from 'fs';
5
-
6
- // src/core/router.ts
7
- var PARAM_PREFIX = 58;
8
- function createNode(segment = "") {
9
- return {
10
- segment,
11
- children: /* @__PURE__ */ new Map(),
12
- paramChild: null,
13
- paramName: "",
14
- handlers: /* @__PURE__ */ new Map(),
15
- wildcardHandler: null
16
- };
17
- }
18
- var LRUCache = class {
19
- capacity;
20
- map;
21
- head;
22
- tail;
23
- constructor(capacity) {
24
- this.capacity = capacity;
25
- this.map = /* @__PURE__ */ new Map();
26
- this.head = { prev: null, next: null };
27
- this.tail = { prev: null, next: null };
28
- this.head.next = this.tail;
29
- this.tail.prev = this.head;
30
- }
31
- get(key) {
32
- const node = this.map.get(key);
33
- if (!node) return void 0;
34
- this.moveToHead(node);
35
- return node.value;
36
- }
37
- set(key, value) {
38
- const existing = this.map.get(key);
39
- if (existing) {
40
- existing.value = value;
41
- this.moveToHead(existing);
42
- return;
43
- }
44
- const node = { value, key, prev: null, next: null };
45
- this.map.set(key, node);
46
- this.addToHead(node);
47
- if (this.map.size > this.capacity) {
48
- const removed = this.removeTail();
49
- if (removed) this.map.delete(removed.key);
50
- }
51
- }
52
- addToHead(node) {
53
- node.prev = this.head;
54
- node.next = this.head.next;
55
- this.head.next.prev = node;
56
- this.head.next = node;
57
- }
58
- removeNode(node) {
59
- node.prev.next = node.next;
60
- node.next.prev = node.prev;
61
- }
62
- moveToHead(node) {
63
- this.removeNode(node);
64
- this.addToHead(node);
65
- }
66
- removeTail() {
67
- const node = this.tail.prev;
68
- if (node === this.head) return null;
69
- this.removeNode(node);
70
- return node;
71
- }
72
- clear() {
73
- this.map.clear();
74
- this.head.next = this.tail;
75
- this.tail.prev = this.head;
76
- }
77
- };
78
- var Router = class {
79
- root = createNode();
80
- cache;
81
- staticRoutes = /* @__PURE__ */ new Map();
82
- constructor(cacheSize = 1024) {
83
- this.cache = new LRUCache(cacheSize);
84
- }
85
- add(method, path, handler, middlewares = [], meta) {
86
- this.cache.clear();
87
- const normalizedPath = this.normalizePath(path);
88
- const stored = { handler, middlewares, meta };
89
- if (!normalizedPath.includes(":") && !normalizedPath.includes("*")) {
90
- const key = `${method}:${normalizedPath}`;
91
- this.staticRoutes.set(key, stored);
92
- return;
93
- }
94
- const segments = normalizedPath.split("/").filter(Boolean);
95
- let node = this.root;
96
- for (let i = 0; i < segments.length; i++) {
97
- const seg = segments[i];
98
- if (seg === "*") {
99
- if (!node.wildcardHandler) {
100
- node.wildcardHandler = /* @__PURE__ */ new Map();
101
- }
102
- node.wildcardHandler.set(method, stored);
103
- return;
104
- }
105
- if (seg.charCodeAt(0) === PARAM_PREFIX) {
106
- if (!node.paramChild) {
107
- node.paramChild = createNode(seg);
108
- node.paramChild.paramName = seg.slice(1);
109
- }
110
- node = node.paramChild;
111
- continue;
112
- }
113
- let child = node.children.get(seg);
114
- if (!child) {
115
- child = createNode(seg);
116
- node.children.set(seg, child);
117
- }
118
- node = child;
119
- }
120
- node.handlers.set(method, stored);
121
- }
122
- find(method, path) {
123
- const normalizedPath = this.normalizePath(path);
124
- const staticKey = `${method}:${normalizedPath}`;
125
- const staticRoute = this.staticRoutes.get(staticKey);
126
- if (staticRoute) {
127
- return {
128
- handler: staticRoute.handler,
129
- params: {},
130
- middlewares: staticRoute.middlewares,
131
- meta: staticRoute.meta
132
- };
133
- }
134
- const cacheKey = staticKey;
135
- const cached = this.cache.get(cacheKey);
136
- if (cached !== void 0) {
137
- return cached ? { ...cached, params: { ...cached.params } } : null;
138
- }
139
- const segments = normalizedPath.split("/").filter(Boolean);
140
- const params = {};
141
- const result = this.matchNode(this.root, segments, 0, params, method);
142
- if (result) {
143
- const match = {
144
- handler: result.handler,
145
- params,
146
- middlewares: result.middlewares,
147
- meta: result.meta
148
- };
149
- this.cache.set(cacheKey, match);
150
- return { ...match, params: { ...params } };
151
- }
152
- this.cache.set(cacheKey, null);
153
- return null;
154
- }
155
- matchNode(node, segments, idx, params, method) {
156
- if (idx === segments.length) {
157
- return node.handlers.get(method) ?? null;
158
- }
159
- const seg = segments[idx];
160
- const child = node.children.get(seg);
161
- if (child) {
162
- const result = this.matchNode(child, segments, idx + 1, params, method);
163
- if (result) return result;
164
- }
165
- if (node.paramChild) {
166
- params[node.paramChild.paramName] = seg;
167
- const result = this.matchNode(node.paramChild, segments, idx + 1, params, method);
168
- if (result) return result;
169
- delete params[node.paramChild.paramName];
170
- }
171
- if (node.wildcardHandler) {
172
- const route = node.wildcardHandler.get(method);
173
- if (route) return route;
174
- }
175
- return null;
176
- }
177
- normalizePath(path) {
178
- if (path === "/") return "/";
179
- if (path.endsWith("/") && path.length > 1) return path.slice(0, -1);
180
- return path;
181
- }
182
- getAllRoutes() {
183
- const routes = [];
184
- for (const [key] of this.staticRoutes) {
185
- const [method, path] = key.split(":", 2);
186
- routes.push({ method, path });
187
- }
188
- this.collectRoutes(this.root, "", routes);
189
- return routes;
190
- }
191
- collectRoutes(node, prefix, routes) {
192
- for (const [method] of node.handlers) {
193
- routes.push({ method, path: prefix || "/" });
194
- }
195
- for (const [seg, child] of node.children) {
196
- this.collectRoutes(child, `${prefix}/${seg}`, routes);
197
- }
198
- if (node.paramChild) {
199
- this.collectRoutes(
200
- node.paramChild,
201
- `${prefix}/:${node.paramChild.paramName}`,
202
- routes
203
- );
204
- }
205
- if (node.wildcardHandler) {
206
- for (const [method] of node.wildcardHandler) {
207
- routes.push({ method, path: `${prefix}/*` });
208
- }
209
- }
210
- }
211
- };
212
- var CONTROLLER_META_KEY = "azura:controller";
213
- var ROUTES_META_KEY = "azura:routes";
214
- var PARAMS_META_KEY = "azura:params";
215
- var AzuraServer = class {
216
- server = null;
217
- router;
218
- logger;
219
- middlewares = [];
220
- plugins = [];
221
- errorHandler = null;
222
- config;
223
- compiledChain = null;
224
- constructor(config = {}) {
225
- this.config = {
226
- environment: config.environment ?? process.env.NODE_ENV ?? "development",
227
- server: {
228
- port: config.server?.port ?? 3e3,
229
- host: config.server?.host ?? "0.0.0.0",
230
- keepAliveTimeout: config.server?.keepAliveTimeout ?? 72e3,
231
- headersTimeout: config.server?.headersTimeout ?? 75e3,
232
- requestTimeout: config.server?.requestTimeout ?? 3e4,
233
- ...config.server
234
- },
235
- logging: {
236
- enabled: config.logging?.enabled ?? true,
237
- level: config.logging?.level ?? "info",
238
- timestamp: config.logging?.timestamp ?? true,
239
- colors: config.logging?.colors ?? true,
240
- requestLogging: config.logging?.requestLogging ?? true,
241
- ...config.logging
242
- },
243
- plugins: config.plugins ?? {},
244
- debug: config.debug ?? false
245
- };
246
- this.router = new Router();
247
- this.logger = new Logger({
248
- level: this.config.logging?.enabled === false ? "silent" : this.config.logging?.level,
249
- colors: this.config.logging?.colors,
250
- timestamp: this.config.logging?.timestamp
251
- });
252
- }
253
- use(handler) {
254
- this.compiledChain = null;
255
- if (handler.length <= 2) {
256
- this.plugins.push(handler);
257
- } else {
258
- this.middlewares.push(handler);
259
- }
260
- return this;
261
- }
262
- plugin(handler) {
263
- this.compiledChain = null;
264
- this.plugins.push(handler);
265
- return this;
266
- }
267
- onError(handler) {
268
- this.errorHandler = handler;
269
- return this;
270
- }
271
- get(path, ...handlers) {
272
- return this.route("GET", path, handlers);
273
- }
274
- post(path, ...handlers) {
275
- return this.route("POST", path, handlers);
276
- }
277
- put(path, ...handlers) {
278
- return this.route("PUT", path, handlers);
279
- }
280
- delete(path, ...handlers) {
281
- return this.route("DELETE", path, handlers);
282
- }
283
- patch(path, ...handlers) {
284
- return this.route("PATCH", path, handlers);
285
- }
286
- head(path, ...handlers) {
287
- return this.route("HEAD", path, handlers);
288
- }
289
- options(path, ...handlers) {
290
- return this.route("OPTIONS", path, handlers);
291
- }
292
- all(path, ...handlers) {
293
- const methods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
294
- for (const method of methods) {
295
- this.route(method, path, handlers);
296
- }
297
- return this;
298
- }
299
- route(method, path, handlers) {
300
- const routeHandler = handlers[handlers.length - 1];
301
- const middlewares = handlers.slice(0, -1);
302
- this.router.add(method, path, routeHandler, middlewares);
303
- return this;
304
- }
305
- register(...controllers) {
306
- for (const Controller of controllers) {
307
- this.registerController(Controller);
308
- }
309
- return this;
310
- }
311
- registerController(Controller) {
312
- const instance = new Controller();
313
- const prefix = Reflect.getMetadata?.(CONTROLLER_META_KEY, Controller) ?? "";
314
- const controllerMiddlewares = Reflect.getMetadata?.("azura:middlewares", Controller) ?? [];
315
- const prototype = Controller.prototype;
316
- const propertyNames = Object.getOwnPropertyNames(prototype).filter(
317
- (p) => p !== "constructor"
318
- );
319
- for (const propertyKey of propertyNames) {
320
- const routeMeta = Reflect.getMetadata?.(
321
- ROUTES_META_KEY,
322
- prototype,
323
- propertyKey
324
- );
325
- if (!routeMeta) continue;
326
- const fullPath = this.joinPaths(prefix, routeMeta.path);
327
- const paramsMeta = Reflect.getMetadata?.(PARAMS_META_KEY, prototype, propertyKey) ?? [];
328
- const routeMiddlewares = Reflect.getMetadata?.("azura:middlewares", prototype, propertyKey) ?? [];
329
- const allMiddlewares = [...controllerMiddlewares, ...routeMiddlewares];
330
- const handler = async (req, res) => {
331
- const args = this.resolveParams(paramsMeta, req, res);
332
- const result = await instance[propertyKey](...args);
333
- if (result !== void 0 && !res.sent) {
334
- if (typeof result === "object") {
335
- res.json(result);
336
- } else {
337
- res.send(String(result));
338
- }
339
- }
340
- };
341
- this.router.add(routeMeta.method, fullPath, handler, allMiddlewares, routeMeta.meta);
342
- }
343
- }
344
- resolveParams(paramsMeta, req, res) {
345
- if (!paramsMeta || paramsMeta.length === 0) return [req, res];
346
- const sorted = [...paramsMeta].sort((a, b) => a.index - b.index);
347
- const args = new Array(sorted.length);
348
- for (const meta of sorted) {
349
- let value;
350
- switch (meta.type) {
351
- case "body":
352
- value = meta.name ? req.body?.[meta.name] : req.body;
353
- break;
354
- case "query":
355
- value = meta.name ? req.query[meta.name] : req.query;
356
- break;
357
- case "param":
358
- value = meta.name ? req.params[meta.name] : req.params;
359
- break;
360
- case "header":
361
- value = meta.name ? req.headers[meta.name.toLowerCase()] : req.headers;
362
- break;
363
- case "cookie":
364
- value = meta.name ? req.cookies[meta.name] : req.cookies;
365
- break;
366
- case "req":
367
- value = req;
368
- break;
369
- case "res":
370
- value = res;
371
- break;
372
- case "next":
373
- value = () => {
374
- };
375
- break;
376
- case "ip":
377
- value = req.ip;
378
- break;
379
- case "session":
380
- value = req.session;
381
- break;
382
- default:
383
- value = void 0;
384
- }
385
- if (meta.pipes) {
386
- for (const pipe of meta.pipes) {
387
- value = pipe(value);
388
- }
389
- }
390
- args[meta.index] = value;
391
- }
392
- return args;
393
- }
394
- joinPaths(prefix, path) {
395
- const p = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
396
- const s = path.startsWith("/") ? path : `/${path}`;
397
- return `${p}${s}`;
398
- }
399
- extendRequest(req, pathname, query) {
400
- const azReq = req;
401
- azReq.pathname = pathname;
402
- azReq.query = query;
403
- azReq.params = {};
404
- azReq.cookies = parseCookies(req.headers.cookie);
405
- azReq.ip = resolveIp(req);
406
- azReq.protocol = req.socket.encrypted ? "https" : "http";
407
- azReq.secure = azReq.protocol === "https";
408
- azReq.hostname = (req.headers.host ?? "localhost").split(":")[0];
409
- azReq.startTime = process.hrtime.bigint();
410
- azReq.raw = req;
411
- return azReq;
412
- }
413
- extendResponse(res) {
414
- const azRes = res;
415
- azRes.locals = {};
416
- azRes.sent = false;
417
- azRes.status = function(code) {
418
- this.statusCode = code;
419
- return this;
420
- };
421
- azRes.json = function(data) {
422
- if (this.sent) return;
423
- this.sent = true;
424
- const body = JSON.stringify(data);
425
- this.setHeader("Content-Type", "application/json; charset=utf-8");
426
- this.setHeader("Content-Length", Buffer.byteLength(body));
427
- this.end(body);
428
- };
429
- azRes.send = function(body) {
430
- if (this.sent) return;
431
- this.sent = true;
432
- if (!this.getHeader("Content-Type")) {
433
- this.setHeader("Content-Type", typeof body === "string" ? "text/plain; charset=utf-8" : "application/octet-stream");
434
- }
435
- const buf = typeof body === "string" ? Buffer.from(body) : body;
436
- this.setHeader("Content-Length", buf.length);
437
- this.end(buf);
438
- };
439
- azRes.html = function(body) {
440
- if (this.sent) return;
441
- this.sent = true;
442
- this.setHeader("Content-Type", "text/html; charset=utf-8");
443
- this.setHeader("Content-Length", Buffer.byteLength(body));
444
- this.end(body);
445
- };
446
- azRes.redirect = function(url, code = 302) {
447
- if (this.sent) return;
448
- this.sent = true;
449
- this.statusCode = code;
450
- this.setHeader("Location", url);
451
- this.end();
452
- };
453
- azRes.cookie = function(name, value, options) {
454
- const header = serializeCookie(name, value, options);
455
- const existing = this.getHeader("Set-Cookie");
456
- if (existing) {
457
- const arr = Array.isArray(existing) ? existing : [String(existing)];
458
- arr.push(header);
459
- this.setHeader("Set-Cookie", arr);
460
- } else {
461
- this.setHeader("Set-Cookie", header);
462
- }
463
- return this;
464
- };
465
- azRes.clearCookie = function(name, options) {
466
- const header = clearCookieHeader(name, options);
467
- const existing = this.getHeader("Set-Cookie");
468
- if (existing) {
469
- const arr = Array.isArray(existing) ? existing : [String(existing)];
470
- arr.push(header);
471
- this.setHeader("Set-Cookie", arr);
472
- } else {
473
- this.setHeader("Set-Cookie", header);
474
- }
475
- return this;
476
- };
477
- azRes.header = function(name, value) {
478
- this.setHeader(name, value);
479
- return this;
480
- };
481
- azRes.type = function(contentType) {
482
- this.setHeader("Content-Type", contentType);
483
- return this;
484
- };
485
- azRes.attachment = function(filename) {
486
- if (filename) {
487
- this.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
488
- } else {
489
- this.setHeader("Content-Disposition", "attachment");
490
- }
491
- return this;
492
- };
493
- azRes.download = function(_path, _filename) {
494
- };
495
- return azRes;
496
- }
497
- compileMiddlewareChain() {
498
- if (this.compiledChain) return this.compiledChain;
499
- const self = this;
500
- const globalMiddlewares = [...this.middlewares];
501
- this.compiledChain = async (req, res) => {
502
- for (const plugin of self.plugins) {
503
- if (res.sent) return;
504
- await new Promise((resolve, reject) => {
505
- try {
506
- const result = plugin({ req, res }, (err) => {
507
- if (err) reject(err);
508
- else resolve();
509
- });
510
- if (result && typeof result.then === "function") {
511
- result.then(resolve, reject);
512
- }
513
- } catch (e) {
514
- reject(e);
515
- }
516
- });
517
- }
518
- if (res.sent) return;
519
- let idx = 0;
520
- const runMiddleware = async () => {
521
- if (idx >= globalMiddlewares.length || res.sent) return;
522
- const mw = globalMiddlewares[idx++];
523
- await new Promise((resolve, reject) => {
524
- try {
525
- const result = mw(req, res, (err) => {
526
- if (err) reject(err);
527
- else resolve();
528
- });
529
- if (result && typeof result.then === "function") {
530
- result.then(resolve, reject);
531
- }
532
- } catch (e) {
533
- reject(e);
534
- }
535
- });
536
- await runMiddleware();
537
- };
538
- await runMiddleware();
539
- };
540
- return this.compiledChain;
541
- }
542
- async handleRequest(req, res) {
543
- const { pathname, search } = parseUrl(req.url ?? "/");
544
- const query = parseQueryString(search);
545
- const azReq = this.extendRequest(req, pathname, query);
546
- const azRes = this.extendResponse(res);
547
- try {
548
- const chain = this.compileMiddlewareChain();
549
- await chain(azReq, azRes);
550
- if (azRes.sent) return;
551
- const method = (req.method ?? "GET").toUpperCase();
552
- const match = this.router.find(method, pathname);
553
- if (!match) {
554
- if (pathname === "/favicon.ico") {
555
- azRes.status(204).send("");
556
- return;
557
- }
558
- throw HttpError.notFound(`Route ${method} ${pathname} not found`);
559
- }
560
- azReq.params = match.params;
561
- if (method !== "GET" && method !== "HEAD" && method !== "OPTIONS") {
562
- azReq.body = await parseBody(req);
563
- }
564
- for (const mw of match.middlewares) {
565
- if (azRes.sent) return;
566
- await new Promise((resolve, reject) => {
567
- try {
568
- const result2 = mw(azReq, azRes, (err) => {
569
- if (err) reject(err);
570
- else resolve();
571
- });
572
- if (result2 && typeof result2.then === "function") {
573
- result2.then(resolve, reject);
574
- }
575
- } catch (e) {
576
- reject(e);
577
- }
578
- });
579
- }
580
- if (azRes.sent) return;
581
- const result = await match.handler(azReq, azRes);
582
- if (result !== void 0 && !azRes.sent) {
583
- if (typeof result === "object" && result !== null) {
584
- azRes.json(result);
585
- } else {
586
- azRes.send(String(result));
587
- }
588
- }
589
- } catch (err) {
590
- this.handleError(err, azReq, azRes);
591
- } finally {
592
- if (this.config.logging?.requestLogging) {
593
- const duration = process.hrtime.bigint() - azReq.startTime;
594
- this.logger.request(
595
- req.method ?? "GET",
596
- pathname,
597
- res.statusCode,
598
- duration
599
- );
600
- }
601
- }
602
- }
603
- handleError(err, req, res) {
604
- if (res.sent) return;
605
- if (this.errorHandler) {
606
- try {
607
- this.errorHandler(err, req, res, () => {
608
- });
609
- return;
610
- } catch (handlerErr) {
611
- this.logger.error("Error handler threw:", handlerErr);
612
- }
613
- }
614
- if (err instanceof HttpError) {
615
- res.status(err.statusCode).json(err.toJSON());
616
- return;
617
- }
618
- const statusCode = err?.statusCode ?? err?.status ?? 500;
619
- const message = this.config.environment === "production" ? "Internal Server Error" : err?.message ?? "Internal Server Error";
620
- this.logger.error(`Unhandled error: ${err?.message ?? err}`);
621
- if (this.config.debug && err?.stack) {
622
- this.logger.error(err.stack);
623
- }
624
- res.status(statusCode).json({
625
- error: {
626
- statusCode,
627
- message,
628
- ...this.config.debug ? { stack: err?.stack } : {}
629
- }
630
- });
631
- }
632
- async listen(port, callback) {
633
- const serverPort = port ?? this.config.server?.port ?? 3e3;
634
- const host = this.config.server?.host ?? "0.0.0.0";
635
- const handler = (req, res) => {
636
- this.handleRequest(req, res).catch((err) => {
637
- this.logger.error("Fatal request error:", err);
638
- if (!res.headersSent) {
639
- res.statusCode = 500;
640
- res.end('{"error":{"statusCode":500,"message":"Internal Server Error"}}');
641
- }
642
- });
643
- };
644
- if (this.config.server?.https) {
645
- const httpsConfig = this.config.server.https;
646
- this.server = createServer(
647
- {
648
- key: readFileSync(httpsConfig.key),
649
- cert: readFileSync(httpsConfig.cert),
650
- ...httpsConfig.ca ? { ca: readFileSync(httpsConfig.ca) } : {}
651
- },
652
- handler
653
- );
654
- } else {
655
- this.server = createServer$1(handler);
656
- }
657
- if (this.config.server?.keepAliveTimeout) {
658
- this.server.keepAliveTimeout = this.config.server.keepAliveTimeout;
659
- }
660
- if (this.config.server?.headersTimeout) {
661
- this.server.headersTimeout = this.config.server.headersTimeout;
662
- }
663
- return new Promise((resolve) => {
664
- this.server.listen(serverPort, host, () => {
665
- this.logger.banner(serverPort, host);
666
- callback?.();
667
- resolve(this.server);
668
- });
669
- });
670
- }
671
- async close() {
672
- return new Promise((resolve, reject) => {
673
- if (!this.server) {
674
- resolve();
675
- return;
676
- }
677
- this.server.close((err) => {
678
- if (err) reject(err);
679
- else resolve();
680
- });
681
- });
682
- }
683
- getRouter() {
684
- return this.router;
685
- }
686
- getLogger() {
687
- return this.logger;
688
- }
689
- getConfig() {
690
- return this.config;
691
- }
692
- getRoutes() {
693
- return this.router.getAllRoutes();
694
- }
695
- };
696
-
697
- export { AzuraServer, Router };
698
- //# sourceMappingURL=chunk-EYAHUNC7.mjs.map
699
- //# sourceMappingURL=chunk-EYAHUNC7.mjs.map
1
+ import { createServer, type Server, type IncomingMessage, type ServerResponse } from "node:http";
2
+ import { createServer as createHttpsServer } from "node:https";
3
+ import { readFileSync } from "node:fs";
4
+ import { Router } from "./router.js";
5
+ import { Logger } from "../utils/Logger.js";
6
+ import { HttpError } from "../utils/HttpError.js";
7
+ import { parseQueryString, parseCookies, parseBody, parseUrl } from "../utils/Parser.js";
8
+ import { resolveIp } from "../utils/IpResolver.js";
9
+ import { serializeCookie, clearCookieHeader } from "../utils/cookies/CookieManager.js";
10
+ import type {
11
+ AzuraRequest,
12
+ AzuraResponse,
13
+ NextFunction,
14
+ MiddlewareHandler,
15
+ ErrorHandler,
16
+ HttpMethod,
17
+ CookieOptions,
18
+ } from "../types/common.type.js";
19
+ import type { AzuraConfig } from "../types/config.type.js";
20
+ import type { PluginHandler } from "../types/plugins/plugin.type.js";
21
+ import type { ControllerMetadata, RouteMetadata } from "../types/routes.type.js";
22
+
23
+ const CONTROLLER_META_KEY = "azura:controller";
24
+ const ROUTES_META_KEY = "azura:routes";
25
+ const PARAMS_META_KEY = "azura:params";
26
+
27
+ export class AzuraServer {
28
+ private server: Server | null = null;
29
+ private router: Router;
30
+ private logger: Logger;
31
+ private middlewares: MiddlewareHandler[] = [];
32
+ private plugins: PluginHandler[] = [];
33
+ private errorHandler: ErrorHandler | null = null;
34
+ private config: AzuraConfig;
35
+ private compiledChain: ((req: AzuraRequest, res: AzuraResponse) => Promise<void>) | null = null;
36
+
37
+ constructor(config: AzuraConfig = {}) {
38
+ this.config = {
39
+ environment: config.environment ?? (process.env.NODE_ENV as any) ?? "development",
40
+ server: {
41
+ port: config.server?.port ?? 3000,
42
+ host: config.server?.host ?? "0.0.0.0",
43
+ keepAliveTimeout: config.server?.keepAliveTimeout ?? 72000,
44
+ headersTimeout: config.server?.headersTimeout ?? 75000,
45
+ requestTimeout: config.server?.requestTimeout ?? 30000,
46
+ ...config.server,
47
+ },
48
+ logging: {
49
+ enabled: config.logging?.enabled ?? true,
50
+ level: config.logging?.level ?? "info",
51
+ timestamp: config.logging?.timestamp ?? true,
52
+ colors: config.logging?.colors ?? true,
53
+ requestLogging: config.logging?.requestLogging ?? true,
54
+ ...config.logging,
55
+ },
56
+ plugins: config.plugins ?? {},
57
+ debug: config.debug ?? false,
58
+ };
59
+
60
+ this.router = new Router();
61
+ this.logger = new Logger({
62
+ level: this.config.logging?.enabled === false ? "silent" : this.config.logging?.level,
63
+ colors: this.config.logging?.colors,
64
+ timestamp: this.config.logging?.timestamp,
65
+ });
66
+ }
67
+
68
+ use(handler: MiddlewareHandler | PluginHandler): this {
69
+ this.compiledChain = null;
70
+ if (handler.length <= 2) {
71
+ this.plugins.push(handler as PluginHandler);
72
+ } else {
73
+ this.middlewares.push(handler as MiddlewareHandler);
74
+ }
75
+ return this;
76
+ }
77
+
78
+ plugin(handler: PluginHandler): this {
79
+ this.compiledChain = null;
80
+ this.plugins.push(handler);
81
+ return this;
82
+ }
83
+
84
+ onError(handler: ErrorHandler): this {
85
+ this.errorHandler = handler;
86
+ return this;
87
+ }
88
+
89
+ get(path: string, ...handlers: (MiddlewareHandler | Function)[]): this {
90
+ return this.route("GET", path, handlers);
91
+ }
92
+
93
+ post(path: string, ...handlers: (MiddlewareHandler | Function)[]): this {
94
+ return this.route("POST", path, handlers);
95
+ }
96
+
97
+ put(path: string, ...handlers: (MiddlewareHandler | Function)[]): this {
98
+ return this.route("PUT", path, handlers);
99
+ }
100
+
101
+ delete(path: string, ...handlers: (MiddlewareHandler | Function)[]): this {
102
+ return this.route("DELETE", path, handlers);
103
+ }
104
+
105
+ patch(path: string, ...handlers: (MiddlewareHandler | Function)[]): this {
106
+ return this.route("PATCH", path, handlers);
107
+ }
108
+
109
+ head(path: string, ...handlers: (MiddlewareHandler | Function)[]): this {
110
+ return this.route("HEAD", path, handlers);
111
+ }
112
+
113
+ options(path: string, ...handlers: (MiddlewareHandler | Function)[]): this {
114
+ return this.route("OPTIONS", path, handlers);
115
+ }
116
+
117
+ all(path: string, ...handlers: (MiddlewareHandler | Function)[]): this {
118
+ const methods: HttpMethod[] = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
119
+ for (const method of methods) {
120
+ this.route(method, path, handlers);
121
+ }
122
+ return this;
123
+ }
124
+
125
+ private route(method: HttpMethod, path: string, handlers: (MiddlewareHandler | Function)[]): this {
126
+ const routeHandler = handlers[handlers.length - 1] as any;
127
+ const middlewares = handlers.slice(0, -1) as MiddlewareHandler[];
128
+ this.router.add(method, path, routeHandler, middlewares);
129
+ return this;
130
+ }
131
+
132
+ register(...controllers: (new (...args: any[]) => any)[]): this {
133
+ for (const Controller of controllers) {
134
+ this.registerController(Controller);
135
+ }
136
+ return this;
137
+ }
138
+
139
+ private registerController(Controller: new (...args: any[]) => any): void {
140
+ const instance = new Controller();
141
+ const prefix: string = Reflect.getMetadata?.(CONTROLLER_META_KEY, Controller) ?? "";
142
+ const controllerMiddlewares: MiddlewareHandler[] =
143
+ Reflect.getMetadata?.("azura:middlewares", Controller) ?? [];
144
+
145
+ const prototype = Controller.prototype;
146
+ const propertyNames = Object.getOwnPropertyNames(prototype).filter(
147
+ (p) => p !== "constructor",
148
+ );
149
+
150
+ for (const propertyKey of propertyNames) {
151
+ const routeMeta: RouteMetadata | undefined = Reflect.getMetadata?.(
152
+ ROUTES_META_KEY,
153
+ prototype,
154
+ propertyKey,
155
+ );
156
+
157
+ if (!routeMeta) continue;
158
+
159
+ const fullPath = this.joinPaths(prefix, routeMeta.path);
160
+ const paramsMeta = Reflect.getMetadata?.(PARAMS_META_KEY, prototype, propertyKey) ?? [];
161
+ const routeMiddlewares: MiddlewareHandler[] =
162
+ Reflect.getMetadata?.("azura:middlewares", prototype, propertyKey) ?? [];
163
+
164
+ const allMiddlewares = [...controllerMiddlewares, ...routeMiddlewares];
165
+
166
+ const handler = async (req: AzuraRequest, res: AzuraResponse) => {
167
+ const args = this.resolveParams(paramsMeta, req, res);
168
+ const result = await instance[propertyKey](...args);
169
+ if (result !== undefined && !res.sent) {
170
+ if (typeof result === "object") {
171
+ res.json(result);
172
+ } else {
173
+ res.send(String(result));
174
+ }
175
+ }
176
+ };
177
+
178
+ this.router.add(routeMeta.method, fullPath, handler, allMiddlewares, routeMeta.meta);
179
+ }
180
+ }
181
+
182
+ private resolveParams(paramsMeta: any[], req: AzuraRequest, res: AzuraResponse): any[] {
183
+ if (!paramsMeta || paramsMeta.length === 0) return [req, res];
184
+
185
+ const sorted = [...paramsMeta].sort((a, b) => a.index - b.index);
186
+ const args: any[] = new Array(sorted.length);
187
+
188
+ for (const meta of sorted) {
189
+ let value: any;
190
+
191
+ switch (meta.type) {
192
+ case "body":
193
+ value = meta.name ? req.body?.[meta.name] : req.body;
194
+ break;
195
+ case "query":
196
+ value = meta.name ? req.query[meta.name] : req.query;
197
+ break;
198
+ case "param":
199
+ value = meta.name ? req.params[meta.name] : req.params;
200
+ break;
201
+ case "header":
202
+ value = meta.name ? req.headers[meta.name.toLowerCase()] : req.headers;
203
+ break;
204
+ case "cookie":
205
+ value = meta.name ? req.cookies[meta.name] : req.cookies;
206
+ break;
207
+ case "req":
208
+ value = req;
209
+ break;
210
+ case "res":
211
+ value = res;
212
+ break;
213
+ case "next":
214
+ value = () => {};
215
+ break;
216
+ case "ip":
217
+ value = req.ip;
218
+ break;
219
+ case "session":
220
+ value = req.session;
221
+ break;
222
+ default:
223
+ value = undefined;
224
+ }
225
+
226
+ if (meta.pipes) {
227
+ for (const pipe of meta.pipes) {
228
+ value = pipe(value);
229
+ }
230
+ }
231
+
232
+ args[meta.index] = value;
233
+ }
234
+
235
+ return args;
236
+ }
237
+
238
+ private joinPaths(prefix: string, path: string): string {
239
+ const p = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
240
+ const s = path.startsWith("/") ? path : `/${path}`;
241
+ return `${p}${s}`;
242
+ }
243
+
244
+ private extendRequest(req: IncomingMessage, pathname: string, query: Record<string, string | string[]>): AzuraRequest {
245
+ const azReq = req as AzuraRequest;
246
+ azReq.pathname = pathname;
247
+ azReq.query = query;
248
+ azReq.params = {};
249
+ azReq.cookies = parseCookies(req.headers.cookie);
250
+ azReq.ip = resolveIp(req);
251
+ azReq.protocol = (req.socket as any).encrypted ? "https" : "http";
252
+ azReq.secure = azReq.protocol === "https";
253
+ azReq.hostname = (req.headers.host ?? "localhost").split(":")[0];
254
+ azReq.startTime = process.hrtime.bigint();
255
+ azReq.raw = req;
256
+ return azReq;
257
+ }
258
+
259
+ private extendResponse(res: ServerResponse): AzuraResponse {
260
+ const azRes = res as AzuraResponse;
261
+ azRes.locals = {};
262
+ azRes.sent = false;
263
+
264
+ azRes.status = function (code: number) {
265
+ this.statusCode = code;
266
+ return this;
267
+ };
268
+
269
+ azRes.json = function (data: unknown) {
270
+ if (this.sent) return;
271
+ this.sent = true;
272
+ const body = JSON.stringify(data);
273
+ this.setHeader("Content-Type", "application/json; charset=utf-8");
274
+ this.setHeader("Content-Length", Buffer.byteLength(body));
275
+ this.end(body);
276
+ };
277
+
278
+ azRes.send = function (body: string | Buffer) {
279
+ if (this.sent) return;
280
+ this.sent = true;
281
+ if (!this.getHeader("Content-Type")) {
282
+ this.setHeader("Content-Type", typeof body === "string" ? "text/plain; charset=utf-8" : "application/octet-stream");
283
+ }
284
+ const buf = typeof body === "string" ? Buffer.from(body) : body;
285
+ this.setHeader("Content-Length", buf.length);
286
+ this.end(buf);
287
+ };
288
+
289
+ azRes.html = function (body: string) {
290
+ if (this.sent) return;
291
+ this.sent = true;
292
+ this.setHeader("Content-Type", "text/html; charset=utf-8");
293
+ this.setHeader("Content-Length", Buffer.byteLength(body));
294
+ this.end(body);
295
+ };
296
+
297
+ azRes.redirect = function (url: string, code = 302) {
298
+ if (this.sent) return;
299
+ this.sent = true;
300
+ this.statusCode = code;
301
+ this.setHeader("Location", url);
302
+ this.end();
303
+ };
304
+
305
+ azRes.cookie = function (name: string, value: string, options?: CookieOptions) {
306
+ const header = serializeCookie(name, value, options);
307
+ const existing = this.getHeader("Set-Cookie");
308
+ if (existing) {
309
+ const arr = Array.isArray(existing) ? existing : [String(existing)];
310
+ arr.push(header);
311
+ this.setHeader("Set-Cookie", arr);
312
+ } else {
313
+ this.setHeader("Set-Cookie", header);
314
+ }
315
+ return this;
316
+ };
317
+
318
+ azRes.clearCookie = function (name: string, options?: CookieOptions) {
319
+ const header = clearCookieHeader(name, options);
320
+ const existing = this.getHeader("Set-Cookie");
321
+ if (existing) {
322
+ const arr = Array.isArray(existing) ? existing : [String(existing)];
323
+ arr.push(header);
324
+ this.setHeader("Set-Cookie", arr);
325
+ } else {
326
+ this.setHeader("Set-Cookie", header);
327
+ }
328
+ return this;
329
+ };
330
+
331
+ azRes.header = function (name: string, value: string | number | string[]) {
332
+ this.setHeader(name, value as any);
333
+ return this;
334
+ };
335
+
336
+ azRes.type = function (contentType: string) {
337
+ this.setHeader("Content-Type", contentType);
338
+ return this;
339
+ };
340
+
341
+ azRes.attachment = function (filename?: string) {
342
+ if (filename) {
343
+ this.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
344
+ } else {
345
+ this.setHeader("Content-Disposition", "attachment");
346
+ }
347
+ return this;
348
+ };
349
+
350
+ azRes.download = function (_path: string, _filename?: string) {
351
+ // TODO: implement file streaming
352
+ };
353
+
354
+ return azRes;
355
+ }
356
+
357
+ private compileMiddlewareChain(): (req: AzuraRequest, res: AzuraResponse) => Promise<void> {
358
+ if (this.compiledChain) return this.compiledChain;
359
+
360
+ const self = this;
361
+ const globalMiddlewares = [...this.middlewares];
362
+
363
+ this.compiledChain = async (req: AzuraRequest, res: AzuraResponse) => {
364
+ for (const plugin of self.plugins) {
365
+ if (res.sent) return;
366
+ await new Promise<void>((resolve, reject) => {
367
+ try {
368
+ const result = plugin({ req, res }, (err) => {
369
+ if (err) reject(err);
370
+ else resolve();
371
+ });
372
+ if (result && typeof (result as any).then === "function") {
373
+ (result as Promise<void>).then(resolve, reject);
374
+ }
375
+ } catch (e) {
376
+ reject(e);
377
+ }
378
+ });
379
+ }
380
+
381
+ if (res.sent) return;
382
+
383
+ let idx = 0;
384
+ const runMiddleware = async (): Promise<void> => {
385
+ if (idx >= globalMiddlewares.length || res.sent) return;
386
+ const mw = globalMiddlewares[idx++];
387
+ await new Promise<void>((resolve, reject) => {
388
+ try {
389
+ const result = mw(req, res, (err) => {
390
+ if (err) reject(err);
391
+ else resolve();
392
+ });
393
+ if (result && typeof (result as any).then === "function") {
394
+ (result as Promise<void>).then(resolve, reject);
395
+ }
396
+ } catch (e) {
397
+ reject(e);
398
+ }
399
+ });
400
+ await runMiddleware();
401
+ };
402
+
403
+ await runMiddleware();
404
+ };
405
+
406
+ return this.compiledChain;
407
+ }
408
+
409
+ private async handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {
410
+ const { pathname, search } = parseUrl(req.url ?? "/");
411
+ const query = parseQueryString(search);
412
+
413
+ const azReq = this.extendRequest(req, pathname, query);
414
+ const azRes = this.extendResponse(res);
415
+
416
+ try {
417
+ const chain = this.compileMiddlewareChain();
418
+ await chain(azReq, azRes);
419
+
420
+ if (azRes.sent) return;
421
+
422
+ const method = (req.method ?? "GET").toUpperCase() as HttpMethod;
423
+ const match = this.router.find(method, pathname);
424
+
425
+ if (!match) {
426
+ if (pathname === "/favicon.ico") {
427
+ azRes.status(204).send("");
428
+ return;
429
+ }
430
+ throw HttpError.notFound(`Route ${method} ${pathname} not found`);
431
+ }
432
+
433
+ azReq.params = match.params;
434
+
435
+ if (method !== "GET" && method !== "HEAD" && method !== "OPTIONS") {
436
+ azReq.body = await parseBody(req);
437
+ }
438
+
439
+ for (const mw of match.middlewares) {
440
+ if (azRes.sent) return;
441
+ await new Promise<void>((resolve, reject) => {
442
+ try {
443
+ const result = mw(azReq, azRes, (err) => {
444
+ if (err) reject(err);
445
+ else resolve();
446
+ });
447
+ if (result && typeof (result as any).then === "function") {
448
+ (result as Promise<void>).then(resolve, reject);
449
+ }
450
+ } catch (e) {
451
+ reject(e);
452
+ }
453
+ });
454
+ }
455
+
456
+ if (azRes.sent) return;
457
+
458
+ const result = await match.handler(azReq, azRes);
459
+
460
+ if (result !== undefined && !azRes.sent) {
461
+ if (typeof result === "object" && result !== null) {
462
+ azRes.json(result);
463
+ } else {
464
+ azRes.send(String(result));
465
+ }
466
+ }
467
+ } catch (err) {
468
+ this.handleError(err, azReq, azRes);
469
+ } finally {
470
+ if (this.config.logging?.requestLogging) {
471
+ const duration = process.hrtime.bigint() - azReq.startTime;
472
+ this.logger.request(
473
+ req.method ?? "GET",
474
+ pathname,
475
+ res.statusCode,
476
+ duration,
477
+ );
478
+ }
479
+ }
480
+ }
481
+
482
+ private handleError(err: any, req: AzuraRequest, res: AzuraResponse): void {
483
+ if (res.sent) return;
484
+
485
+ if (this.errorHandler) {
486
+ try {
487
+ this.errorHandler(err, req, res, () => {});
488
+ return;
489
+ } catch (handlerErr) {
490
+ this.logger.error("Error handler threw:", handlerErr);
491
+ }
492
+ }
493
+
494
+ if (err instanceof HttpError) {
495
+ res.status(err.statusCode).json(err.toJSON());
496
+ return;
497
+ }
498
+
499
+ const statusCode = err?.statusCode ?? err?.status ?? 500;
500
+ const message = this.config.environment === "production"
501
+ ? "Internal Server Error"
502
+ : err?.message ?? "Internal Server Error";
503
+
504
+ this.logger.error(`Unhandled error: ${err?.message ?? err}`);
505
+ if (this.config.debug && err?.stack) {
506
+ this.logger.error(err.stack);
507
+ }
508
+
509
+ res.status(statusCode).json({
510
+ error: {
511
+ statusCode,
512
+ message,
513
+ ...(this.config.debug ? { stack: err?.stack } : {}),
514
+ },
515
+ });
516
+ }
517
+
518
+ async listen(port?: number, callback?: () => void): Promise<Server> {
519
+ const serverPort = port ?? this.config.server?.port ?? 3000;
520
+ const host = this.config.server?.host ?? "0.0.0.0";
521
+
522
+ const handler = (req: IncomingMessage, res: ServerResponse) => {
523
+ this.handleRequest(req, res).catch((err) => {
524
+ this.logger.error("Fatal request error:", err);
525
+ if (!res.headersSent) {
526
+ res.statusCode = 500;
527
+ res.end('{"error":{"statusCode":500,"message":"Internal Server Error"}}');
528
+ }
529
+ });
530
+ };
531
+
532
+ if (this.config.server?.https) {
533
+ const httpsConfig = this.config.server.https;
534
+ this.server = createHttpsServer(
535
+ {
536
+ key: readFileSync(httpsConfig.key),
537
+ cert: readFileSync(httpsConfig.cert),
538
+ ...(httpsConfig.ca ? { ca: readFileSync(httpsConfig.ca) } : {}),
539
+ },
540
+ handler,
541
+ );
542
+ } else {
543
+ this.server = createServer(handler);
544
+ }
545
+
546
+ if (this.config.server?.keepAliveTimeout) {
547
+ this.server.keepAliveTimeout = this.config.server.keepAliveTimeout;
548
+ }
549
+ if (this.config.server?.headersTimeout) {
550
+ this.server.headersTimeout = this.config.server.headersTimeout;
551
+ }
552
+
553
+ return new Promise((resolve) => {
554
+ this.server!.listen(serverPort, host, () => {
555
+ this.logger.banner(serverPort, host);
556
+ callback?.();
557
+ resolve(this.server!);
558
+ });
559
+ });
560
+ }
561
+
562
+ async close(): Promise<void> {
563
+ return new Promise((resolve, reject) => {
564
+ if (!this.server) {
565
+ resolve();
566
+ return;
567
+ }
568
+ this.server.close((err) => {
569
+ if (err) reject(err);
570
+ else resolve();
571
+ });
572
+ });
573
+ }
574
+
575
+ getRouter(): Router {
576
+ return this.router;
577
+ }
578
+
579
+ getLogger(): Logger {
580
+ return this.logger;
581
+ }
582
+
583
+ getConfig(): AzuraConfig {
584
+ return this.config;
585
+ }
586
+
587
+ getRoutes(): Array<{ method: HttpMethod; path: string }> {
588
+ return this.router.getAllRoutes();
589
+ }
590
+ }