azurajs 2.1.2 → 2.2.0

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 (111) hide show
  1. package/README.md +244 -7
  2. package/dist/LoggingMiddleware-BQKJUBqT.d.ts +6 -0
  3. package/dist/LoggingMiddleware-CYNvypha.d.cts +6 -0
  4. package/dist/Server-Ba-EFdi2.d.ts +53 -0
  5. package/dist/Server-CY3k1FIL.d.cts +53 -0
  6. package/dist/common.type-BoV71o_C.d.ts +11 -0
  7. package/dist/common.type-Ct06XeYQ.d.cts +11 -0
  8. package/dist/config.cjs +89 -0
  9. package/dist/config.cjs.map +1 -0
  10. package/dist/config.d.cts +51 -0
  11. package/dist/config.d.ts +51 -0
  12. package/dist/config.js +83 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/cookies.cjs +32 -0
  15. package/dist/cookies.cjs.map +1 -0
  16. package/dist/cookies.d.cts +8 -0
  17. package/dist/cookies.d.ts +8 -0
  18. package/dist/cookies.js +29 -0
  19. package/dist/cookies.js.map +1 -0
  20. package/dist/cors.cjs +29 -0
  21. package/dist/cors.cjs.map +1 -0
  22. package/dist/cors.d.cts +14 -0
  23. package/dist/cors.d.ts +14 -0
  24. package/dist/cors.js +27 -0
  25. package/dist/cors.js.map +1 -0
  26. package/dist/decorators.cjs +141 -0
  27. package/dist/decorators.cjs.map +1 -0
  28. package/dist/decorators.d.cts +29 -0
  29. package/dist/decorators.d.ts +29 -0
  30. package/dist/decorators.js +122 -0
  31. package/dist/decorators.js.map +1 -0
  32. package/dist/http-error.cjs +14 -0
  33. package/dist/http-error.cjs.map +1 -0
  34. package/dist/http-error.d.cts +7 -0
  35. package/dist/http-error.d.ts +7 -0
  36. package/dist/http-error.js +12 -0
  37. package/dist/http-error.js.map +1 -0
  38. package/dist/index.cjs +910 -0
  39. package/dist/index.cjs.map +1 -0
  40. package/dist/index.d.cts +8 -0
  41. package/dist/index.d.ts +8 -0
  42. package/dist/index.js +899 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/infra.cjs +811 -0
  45. package/dist/infra.cjs.map +1 -0
  46. package/dist/infra.d.cts +8 -0
  47. package/dist/infra.d.ts +8 -0
  48. package/dist/infra.js +800 -0
  49. package/dist/infra.js.map +1 -0
  50. package/dist/logger.cjs +26 -0
  51. package/dist/logger.cjs.map +1 -0
  52. package/dist/logger.d.cts +8 -0
  53. package/dist/logger.d.ts +8 -0
  54. package/dist/logger.js +24 -0
  55. package/dist/logger.js.map +1 -0
  56. package/dist/middleware.cjs +117 -0
  57. package/dist/middleware.cjs.map +1 -0
  58. package/dist/middleware.d.cts +10 -0
  59. package/dist/middleware.d.ts +10 -0
  60. package/dist/middleware.js +114 -0
  61. package/dist/middleware.js.map +1 -0
  62. package/dist/plugins.cjs +52 -0
  63. package/dist/plugins.cjs.map +1 -0
  64. package/dist/plugins.d.cts +6 -0
  65. package/dist/plugins.d.ts +6 -0
  66. package/dist/plugins.js +49 -0
  67. package/dist/plugins.js.map +1 -0
  68. package/dist/rate-limit.cjs +27 -0
  69. package/dist/rate-limit.cjs.map +1 -0
  70. package/dist/rate-limit.d.cts +8 -0
  71. package/dist/rate-limit.d.ts +8 -0
  72. package/dist/rate-limit.js +25 -0
  73. package/dist/rate-limit.js.map +1 -0
  74. package/dist/request.type-CJ-EGGcM.d.cts +22 -0
  75. package/dist/request.type-CJ-EGGcM.d.ts +22 -0
  76. package/dist/response.type-d6e6eU9D.d.cts +33 -0
  77. package/dist/response.type-d6e6eU9D.d.ts +33 -0
  78. package/dist/router.cjs +111 -0
  79. package/dist/router.cjs.map +1 -0
  80. package/dist/router.d.cts +27 -0
  81. package/dist/router.d.ts +27 -0
  82. package/dist/router.js +109 -0
  83. package/dist/router.js.map +1 -0
  84. package/dist/routes.type-VPROfhnL.d.cts +14 -0
  85. package/dist/routes.type-VPROfhnL.d.ts +14 -0
  86. package/dist/types.cjs +4 -0
  87. package/dist/types.cjs.map +1 -0
  88. package/dist/types.d.cts +6 -0
  89. package/dist/types.d.ts +6 -0
  90. package/dist/types.js +3 -0
  91. package/dist/types.js.map +1 -0
  92. package/dist/utils.cjs +149 -0
  93. package/dist/utils.cjs.map +1 -0
  94. package/dist/utils.d.cts +10 -0
  95. package/dist/utils.d.ts +10 -0
  96. package/dist/utils.js +141 -0
  97. package/dist/utils.js.map +1 -0
  98. package/dist/validations.type-D4ZhF5g6.d.cts +6 -0
  99. package/dist/validations.type-D4ZhF5g6.d.ts +6 -0
  100. package/dist/validators.cjs +69 -0
  101. package/dist/validators.cjs.map +1 -0
  102. package/dist/validators.d.cts +11 -0
  103. package/dist/validators.d.ts +11 -0
  104. package/dist/validators.js +65 -0
  105. package/dist/validators.js.map +1 -0
  106. package/package.json +85 -66
  107. package/src/index.ts +1 -3
  108. package/src/infra/Router.ts +53 -3
  109. package/src/infra/Server.ts +37 -10
  110. package/src/infra/index.ts +1 -1
  111. package/src/shared/config/ConfigModule.ts +1 -0
package/dist/index.cjs ADDED
@@ -0,0 +1,910 @@
1
+ 'use strict';
2
+
3
+ var http = require('http');
4
+ var cluster2 = require('cluster');
5
+ var os = require('os');
6
+ var fs = require('fs');
7
+ var path = require('path');
8
+ require('net');
9
+
10
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
+
12
+ var http__default = /*#__PURE__*/_interopDefault(http);
13
+ var cluster2__default = /*#__PURE__*/_interopDefault(cluster2);
14
+ var os__default = /*#__PURE__*/_interopDefault(os);
15
+ var path__default = /*#__PURE__*/_interopDefault(path);
16
+
17
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
18
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
19
+ }) : x)(function(x) {
20
+ if (typeof require !== "undefined") return require.apply(this, arguments);
21
+ throw Error('Dynamic require of "' + x + '" is not supported');
22
+ });
23
+ var ConfigModule = class {
24
+ constructor() {
25
+ this.config = {};
26
+ }
27
+ /**
28
+ * Load config files first (azura.config.*)
29
+ * Recivied error if config file not found or invalid format
30
+ * @param configFiles
31
+ */
32
+ initSync() {
33
+ const cdw = process.cwd();
34
+ const configFiles = [
35
+ "azura.config.ts",
36
+ "azura.config.json",
37
+ "azura.config.yaml",
38
+ "azura.config.yml"
39
+ ];
40
+ let loaded = false;
41
+ for (const fileName of configFiles) {
42
+ const filePath = path__default.default.join(cdw, fileName);
43
+ if (!fs.existsSync(filePath)) continue;
44
+ const extension = path__default.default.extname(fileName);
45
+ const raw = fs.readFileSync(filePath, "utf8");
46
+ try {
47
+ let parsed;
48
+ switch (extension) {
49
+ case ".ts":
50
+ const mod = __require(filePath);
51
+ parsed = mod.default || mod;
52
+ break;
53
+ case ".json":
54
+ parsed = JSON.parse(raw);
55
+ break;
56
+ case ".yaml":
57
+ case ".yml":
58
+ parsed = __require("js-yaml").load(raw);
59
+ break;
60
+ default:
61
+ throw new Error(`Invalid config file extension: ${extension}`);
62
+ }
63
+ this.config = { ...this.config, ...parsed };
64
+ loaded = true;
65
+ break;
66
+ } catch (error) {
67
+ throw new Error(`Error loading config file: ${filePath}
68
+ ${error.message}`);
69
+ }
70
+ }
71
+ if (!loaded) {
72
+ throw new Error("Nothing config file found in the current directory.");
73
+ }
74
+ }
75
+ /**
76
+ * Get all configs from loaded config file
77
+ * @returns ConfigTypes
78
+ */
79
+ getAll() {
80
+ return this.config;
81
+ }
82
+ /**
83
+ * Return a specific config from loaded config file
84
+ *
85
+ * @template T
86
+ * @param {T} key - key of the config to retrieve
87
+ * @returns {ConfigTypes[T]}
88
+ */
89
+ get(key) {
90
+ return this.config[key];
91
+ }
92
+ };
93
+
94
+ // src/infra/utils/HttpError.ts
95
+ var HttpError = class extends Error {
96
+ constructor(status, message, payload) {
97
+ super(message ?? String(message ?? "Error"));
98
+ this.status = status;
99
+ this.payload = payload;
100
+ }
101
+ };
102
+
103
+ // src/utils/Logger.ts
104
+ var RESET = "\x1B[0m";
105
+ var COLORS = {
106
+ info: "\x1B[36m",
107
+ warn: "\x1B[33m",
108
+ error: "\x1B[31m"
109
+ };
110
+ var LEVEL_LABELS = {
111
+ info: "INFO",
112
+ warn: "WARN",
113
+ error: "ERROR"
114
+ };
115
+ function logger(level, msg) {
116
+ const color = COLORS[level];
117
+ const levelLabel = LEVEL_LABELS[level];
118
+ const prefix = `${color}[Azura:${levelLabel}]${RESET}`;
119
+ (level === "error" ? console.error : level === "warn" ? console.warn : console.log)(
120
+ `${prefix} ${msg}`
121
+ );
122
+ }
123
+
124
+ // src/utils/Parser.ts
125
+ function parseQS(qs) {
126
+ const out = {};
127
+ if (!qs) return out;
128
+ const parts = qs.split("&");
129
+ for (let i = 0; i < parts.length; i++) {
130
+ const p = parts[i];
131
+ if (!p) continue;
132
+ const idx = p.indexOf("=");
133
+ if (idx === -1) {
134
+ out[decodeURIComponent(p)] = "";
135
+ continue;
136
+ }
137
+ const k = decodeURIComponent(p.slice(0, idx));
138
+ const v = decodeURIComponent(p.slice(idx + 1));
139
+ const existing = out[k];
140
+ if (existing !== void 0) {
141
+ if (Array.isArray(existing)) {
142
+ existing.push(v);
143
+ } else {
144
+ out[k] = [existing, v];
145
+ }
146
+ } else {
147
+ out[k] = v;
148
+ }
149
+ }
150
+ return out;
151
+ }
152
+
153
+ // src/utils/cookies/SerializeCookie.ts
154
+ function serializeCookie(name, val, opts = {}) {
155
+ const encode = opts.encode ?? encodeURIComponent;
156
+ let str = `${name}=${encode(val)}`;
157
+ if (opts.maxAge != null && !Number.isNaN(Number(opts.maxAge)))
158
+ str += `; Max-Age=${Math.floor(Number(opts.maxAge))}`;
159
+ if (opts.domain) str += `; Domain=${opts.domain}`;
160
+ if (opts.path) str += `; Path=${opts.path}`;
161
+ if (opts.expires) str += `; Expires=${opts.expires.toUTCString()}`;
162
+ if (opts.httpOnly) str += `; HttpOnly`;
163
+ if (opts.secure) str += `; Secure`;
164
+ if (opts.sameSite) str += `; SameSite=${opts.sameSite}`;
165
+ return str;
166
+ }
167
+
168
+ // src/utils/cookies/ParserCookie.ts
169
+ function parseCookiesHeader(header) {
170
+ if (!header) return {};
171
+ return header.split(";").reduce((acc, pair) => {
172
+ const [k, ...vals] = pair.trim().split("=");
173
+ if (!k) return acc;
174
+ acc[k] = decodeURIComponent(vals.join("="));
175
+ return acc;
176
+ }, {});
177
+ }
178
+
179
+ // src/infra/utils/RequestHandler.ts
180
+ function adaptRequestHandler(mw) {
181
+ if (typeof mw !== "function") return mw;
182
+ return async (ctx) => {
183
+ try {
184
+ if (mw.length === 3) {
185
+ await new Promise((resolve, reject) => {
186
+ try {
187
+ const maybe = mw(ctx.request, ctx.response, (err) => {
188
+ if (err) reject(err);
189
+ else resolve();
190
+ });
191
+ if (maybe && typeof maybe.then === "function") {
192
+ maybe.then(() => resolve()).catch(reject);
193
+ }
194
+ } catch (e) {
195
+ reject(e);
196
+ }
197
+ });
198
+ } else {
199
+ await mw(ctx);
200
+ }
201
+ } catch (err) {
202
+ throw err;
203
+ }
204
+ };
205
+ }
206
+
207
+ // src/infra/utils/route/Node.ts
208
+ var Node = class {
209
+ constructor() {
210
+ this.children = /* @__PURE__ */ new Map();
211
+ this.handlers = /* @__PURE__ */ new Map();
212
+ }
213
+ };
214
+
215
+ // src/infra/Router.ts
216
+ var Router = class {
217
+ constructor(debug = false) {
218
+ this.root = new Node();
219
+ this.debug = debug;
220
+ }
221
+ add(method, path2, ...handlers) {
222
+ const segments = path2.split("/").filter(Boolean);
223
+ let node = this.root;
224
+ for (const seg of segments) {
225
+ let child;
226
+ if (seg.startsWith(":")) {
227
+ child = new Node();
228
+ child.isParam = true;
229
+ child.paramName = seg.slice(1);
230
+ } else {
231
+ child = node.children.get(seg) ?? new Node();
232
+ }
233
+ node.children.set(seg.startsWith(":") ? ":" : seg, child);
234
+ node = child;
235
+ }
236
+ node.handlers.set(method.toUpperCase(), handlers);
237
+ }
238
+ find(method, path2) {
239
+ const cleanPath = path2.split("?")[0];
240
+ const segments = cleanPath?.split("/").filter(Boolean) ?? [];
241
+ let node = this.root;
242
+ const params = {};
243
+ if (this.debug && segments?.length === 0 && node.handlers.size === 0) {
244
+ console.error("[Router:DEBUG] Root node has no handlers");
245
+ console.error("[Router:DEBUG] Available methods at root:", Array.from(node.handlers.keys()));
246
+ }
247
+ for (let i = 0; i < segments.length; i++) {
248
+ const seg = segments[i];
249
+ if (!seg) continue;
250
+ let child = node.children.get(seg);
251
+ if (child) {
252
+ node = child;
253
+ } else {
254
+ child = node.children.get(":");
255
+ if (child) {
256
+ node = child;
257
+ if (node.paramName) {
258
+ params[node.paramName] = seg;
259
+ }
260
+ } else {
261
+ if (this.debug) {
262
+ console.error(`[Router:DEBUG] Route not found for ${method} ${cleanPath}`);
263
+ console.error(`[Router:DEBUG] Failed at segment: "${seg}"`);
264
+ console.error(`[Router:DEBUG] Available children:`, Array.from(node.children.keys()));
265
+ }
266
+ throw new HttpError(404, "Route not found");
267
+ }
268
+ }
269
+ }
270
+ const handlers = node.handlers.get(method.toUpperCase());
271
+ if (!handlers) {
272
+ if (this.debug) {
273
+ console.error(
274
+ `[Router:DEBUG] No handlers for method ${method.toUpperCase()} at path ${cleanPath}`
275
+ );
276
+ console.error(
277
+ `[Router:DEBUG] Available methods at this path:`,
278
+ Array.from(node.handlers.keys())
279
+ );
280
+ console.error(`[Router:DEBUG] Segments matched:`, segments);
281
+ }
282
+ throw new HttpError(404, "Route not found");
283
+ }
284
+ return { handlers, params };
285
+ }
286
+ listRoutes() {
287
+ const routes = [];
288
+ const traverse = (node, path2) => {
289
+ if (node.handlers.size > 0) {
290
+ for (const method of node.handlers.keys()) {
291
+ routes.push({ method, path: path2 || "/" });
292
+ }
293
+ }
294
+ for (const [segment, child] of node.children) {
295
+ const newPath = path2 + "/" + (segment === ":" && child.paramName ? `:${child.paramName}` : segment);
296
+ traverse(child, newPath);
297
+ }
298
+ };
299
+ traverse(this.root, "");
300
+ return routes;
301
+ }
302
+ };
303
+ function getIP(port) {
304
+ const networkInterfaces = os__default.default.networkInterfaces();
305
+ Object.values(networkInterfaces).forEach((ifaceList) => {
306
+ ifaceList?.forEach((iface) => {
307
+ if (iface.family === "IPv4" && !iface.internal) {
308
+ const who = cluster2__default.default.isPrimary ? "master" : "worker";
309
+ logger("info", `[${who}] accessible at http://${iface.address}:${port}`);
310
+ }
311
+ });
312
+ });
313
+ }
314
+
315
+ // src/shared/plugins/CORSPlugin.ts
316
+ function cors(opts) {
317
+ const { origin, methods, allowedHeaders } = opts;
318
+ return (ctx, next) => {
319
+ ctx.response.setHeader(
320
+ "Access-Control-Allow-Origin",
321
+ Array.isArray(origin) ? origin.join(",") : origin
322
+ );
323
+ ctx.response.setHeader(
324
+ "Access-Control-Allow-Methods",
325
+ Array.isArray(methods) ? methods.join(",") : methods
326
+ );
327
+ ctx.response.setHeader(
328
+ "Access-Control-Allow-Headers",
329
+ Array.isArray(allowedHeaders) ? allowedHeaders.join(",") : allowedHeaders
330
+ );
331
+ if (ctx.request.method === "OPTIONS") {
332
+ ctx.response.writeHead(204);
333
+ return ctx.response.end();
334
+ }
335
+ return next();
336
+ };
337
+ }
338
+
339
+ // src/shared/plugins/RateLimitPlugin.ts
340
+ var store = /* @__PURE__ */ new Map();
341
+ function rateLimit(limit, ttl) {
342
+ return async (ctx, next) => {
343
+ const ip = ctx.request.socket.remoteAddress;
344
+ const now = Date.now();
345
+ const entry = store.get(ip) ?? { count: 0, ts: now };
346
+ if (now - entry.ts > ttl) {
347
+ entry.count = 1;
348
+ entry.ts = now;
349
+ } else {
350
+ entry.count++;
351
+ }
352
+ store.set(ip, entry);
353
+ if (entry.count > limit) {
354
+ ctx.response.status(429).send("Too many requests");
355
+ return;
356
+ }
357
+ await next();
358
+ };
359
+ }
360
+
361
+ // src/middleware/LoggingMiddleware.ts
362
+ function createLoggingMiddleware(config) {
363
+ const isEnabled = config.logging?.enabled ?? true;
364
+ const showDetails = config.logging?.showDetails ?? true;
365
+ const env = config.environment || "development";
366
+ const reset = "\x1B[0m";
367
+ const bold = "\x1B[1m";
368
+ const dim = "\x1B[2m";
369
+ const colors = {
370
+ GET: "\x1B[36m",
371
+ POST: "\x1B[32m",
372
+ PUT: "\x1B[33m",
373
+ DELETE: "\x1B[31m",
374
+ PATCH: "\x1B[35m",
375
+ DEFAULT: "\x1B[37m"
376
+ };
377
+ const fn = async (...args) => {
378
+ let req;
379
+ let res;
380
+ let next;
381
+ if (args.length === 1 && typeof args[0] === "object" && args[0] !== null) {
382
+ const ctx = args[0];
383
+ req = ctx.request || ctx.req;
384
+ res = ctx.response || ctx.res;
385
+ next = ctx.next;
386
+ } else {
387
+ req = args[0];
388
+ res = args[1];
389
+ next = args[2];
390
+ }
391
+ if (!req || !res) {
392
+ if (typeof next === "function") await next();
393
+ return;
394
+ }
395
+ if (!isEnabled) {
396
+ if (typeof next === "function") await next();
397
+ return;
398
+ }
399
+ const start = Date.now();
400
+ const methodRaw = String(req.method || "GET").toUpperCase();
401
+ const methodPad = methodRaw.padEnd(6, " ");
402
+ const methodColor = colors[methodRaw] || colors.DEFAULT;
403
+ const url = String(req.originalUrl || req.url || "/");
404
+ const ip = String(req.ip || "").replace("::ffff:", "");
405
+ const time = (/* @__PURE__ */ new Date()).toLocaleTimeString("pt-BR", { hour12: false });
406
+ try {
407
+ if (env === "development") {
408
+ const header = `${dim}\xB7${reset} ${bold}${time}${reset} ${methodColor}${methodPad}${reset} ${bold}${url}${reset}`;
409
+ console.log(header);
410
+ if (showDetails) {
411
+ try {
412
+ if (req.query && Object.keys(req.query).length > 0) {
413
+ console.log(`${dim} \u203A query:${reset} ${JSON.stringify(req.query)}`);
414
+ }
415
+ } catch {
416
+ }
417
+ try {
418
+ const params = req.params;
419
+ if (params && Object.keys(params).length > 0) {
420
+ console.log(`${dim} \u203A params:${reset} ${JSON.stringify(params)}`);
421
+ }
422
+ } catch {
423
+ }
424
+ try {
425
+ if (req.body && Object.keys(req.body).length > 0) {
426
+ console.log(`${dim} \u203A body:${reset} ${JSON.stringify(req.body)}`);
427
+ }
428
+ } catch {
429
+ }
430
+ }
431
+ } else {
432
+ console.log(
433
+ `${dim}\xB7${reset} ${time} ${methodColor}${methodPad}${reset} ${url} ${dim}\u2022 IP:${ip}${reset}`
434
+ );
435
+ }
436
+ } catch {
437
+ }
438
+ try {
439
+ if (typeof res.on === "function") {
440
+ res.on("finish", () => {
441
+ const duration = Date.now() - start;
442
+ const statusCode = Number(res.statusCode || 0);
443
+ const statusColor = statusCode >= 500 ? "\x1B[31m" : statusCode >= 400 ? "\x1B[33m" : "\x1B[32m";
444
+ const statusText = `${statusColor}${statusCode}${reset}`;
445
+ const durationText = `${dim}(${duration}ms)${reset}`;
446
+ const ipShort = ip || "-";
447
+ const line = `${dim}\u2192${reset} ${statusText} ${durationText} ${dim}\u2022 IP:${ipShort}${reset}`;
448
+ console.log(line);
449
+ });
450
+ }
451
+ } catch {
452
+ }
453
+ if (typeof next === "function") {
454
+ await next();
455
+ }
456
+ };
457
+ return fn;
458
+ }
459
+
460
+ // src/infra/Server.ts
461
+ var AzuraClient = class {
462
+ constructor() {
463
+ this.port = 3e3;
464
+ this.middlewares = [];
465
+ this.get = (p, ...h) => this.addRoute("GET", p, ...h);
466
+ this.post = (p, ...h) => this.addRoute("POST", p, ...h);
467
+ this.put = (p, ...h) => this.addRoute("PUT", p, ...h);
468
+ this.delete = (p, ...h) => this.addRoute("DELETE", p, ...h);
469
+ this.patch = (p, ...h) => this.addRoute("PATCH", p, ...h);
470
+ const config = new ConfigModule();
471
+ try {
472
+ config.initSync();
473
+ } catch (error) {
474
+ console.error("[Azura] \u274C Falha ao carregar configura\xE7\xE3o:");
475
+ console.error(" ", error.message);
476
+ process.exit(1);
477
+ }
478
+ this.opts = config.getAll();
479
+ this.router = new Router(this.opts.debug || false);
480
+ this.initPromise = this.init();
481
+ this.setupDefaultRoutes();
482
+ }
483
+ /**
484
+ * Configura rotas padrão para evitar erros 404 comuns
485
+ */
486
+ setupDefaultRoutes() {
487
+ this.router.add(
488
+ "GET",
489
+ "/favicon.ico",
490
+ adaptRequestHandler((ctx) => {
491
+ ctx.res.status(204).send();
492
+ })
493
+ );
494
+ }
495
+ getConfig() {
496
+ return this.opts;
497
+ }
498
+ async init() {
499
+ this.port = this.opts.server?.port || 3e3;
500
+ if (this.opts.server?.cluster && cluster2__default.default.isPrimary) {
501
+ for (let i = 0; i < os__default.default.cpus().length; i++) cluster2__default.default.fork();
502
+ cluster2__default.default.on("exit", () => cluster2__default.default.fork());
503
+ return;
504
+ }
505
+ if (this.opts.plugins?.cors?.enabled) {
506
+ cors({
507
+ origin: this.opts.plugins.cors.origins,
508
+ methods: this.opts.plugins.cors.methods,
509
+ allowedHeaders: this.opts.plugins.cors.allowedHeaders
510
+ });
511
+ logger("info", "CORS plugin enabled");
512
+ }
513
+ if (this.opts.plugins?.rateLimit?.enabled) {
514
+ rateLimit(this.opts.plugins.rateLimit.limit, this.opts.plugins.rateLimit.timeframe);
515
+ logger("info", "Rate Limit plugin enabled");
516
+ }
517
+ this.server = http__default.default.createServer();
518
+ this.server.on("request", this.handle.bind(this));
519
+ }
520
+ use(mw) {
521
+ this.middlewares.push(mw);
522
+ }
523
+ addRoute(method, path2, ...handlers) {
524
+ const adapted = handlers.map(adaptRequestHandler);
525
+ this.router.add(method, path2, ...adapted);
526
+ }
527
+ getRoutes() {
528
+ return this.router.listRoutes();
529
+ }
530
+ async listen(port = this.port) {
531
+ await this.initPromise;
532
+ if (!this.server) {
533
+ logger("error", "Server not initialized");
534
+ return;
535
+ }
536
+ this.server.on("error", (error) => {
537
+ if (error.code === "EADDRINUSE") {
538
+ logger("error", `\u274C Port ${port} is already in use. Please choose a different port.`);
539
+ } else {
540
+ logger("error", "Server failed to start: " + (error?.message || String(error)));
541
+ }
542
+ process.exit(1);
543
+ });
544
+ const who = cluster2__default.default.isPrimary ? "master" : "worker";
545
+ this.server.listen(port, () => {
546
+ logger("info", `[${who}] listening on http://localhost:${port}`);
547
+ if (this.opts.server?.ipHost) getIP(port);
548
+ const routes = this.getRoutes();
549
+ if (routes.length > 0) {
550
+ logger("info", `
551
+ \u{1F4CB} Registered routes (${routes.length}):`);
552
+ routes.forEach((r) => {
553
+ logger("info", ` ${r.method.padEnd(7)} ${r.path}`);
554
+ });
555
+ }
556
+ });
557
+ return this.server;
558
+ }
559
+ /**
560
+ * Fetch handler compatible with Web API (Bun, Deno, Cloudflare Workers, etc.)
561
+ * @example
562
+ * ```typescript
563
+ * const app = new AzuraClient();
564
+ * app.get('/', (req, res) => res.text('Hello World!'));
565
+ *
566
+ * // Use with Bun
567
+ * Bun.serve({
568
+ * port: 3000,
569
+ * fetch: app.fetch.bind(app),
570
+ * });
571
+ *
572
+ * // Use with Deno
573
+ * Deno.serve({ port: 3000 }, app.fetch.bind(app));
574
+ * ```
575
+ */
576
+ async fetch(request) {
577
+ await this.initPromise;
578
+ const url = new URL(request.url);
579
+ const urlPath = url.pathname;
580
+ const safeQuery = {};
581
+ if (url.search) {
582
+ const rawQuery = parseQS(url.search.slice(1));
583
+ for (const k in rawQuery) {
584
+ const v = rawQuery[k];
585
+ safeQuery[k] = Array.isArray(v) ? v[0] || "" : v;
586
+ }
587
+ }
588
+ const cookieHeader = request.headers.get("cookie") || "";
589
+ const cookies = parseCookiesHeader(cookieHeader);
590
+ let body = {};
591
+ if (["POST", "PUT", "PATCH"].includes(request.method.toUpperCase())) {
592
+ const contentType = request.headers.get("content-type") || "";
593
+ try {
594
+ if (contentType.includes("application/json")) {
595
+ body = await request.json();
596
+ } else if (contentType.includes("application/x-www-form-urlencoded")) {
597
+ const text = await request.text();
598
+ const parsed = parseQS(text || "");
599
+ const b = {};
600
+ for (const k in parsed) {
601
+ const v = parsed[k];
602
+ b[k] = Array.isArray(v) ? v[0] || "" : v || "";
603
+ }
604
+ body = b;
605
+ } else {
606
+ body = await request.text();
607
+ }
608
+ } catch {
609
+ body = {};
610
+ }
611
+ }
612
+ const protocol = url.protocol.slice(0, -1);
613
+ const headersObj = {};
614
+ request.headers.forEach((value, key) => {
615
+ headersObj[key] = value;
616
+ });
617
+ const rawReq = {
618
+ method: request.method,
619
+ url: url.pathname + url.search,
620
+ originalUrl: url.pathname + url.search,
621
+ path: urlPath || "/",
622
+ protocol,
623
+ secure: url.protocol === "https:",
624
+ hostname: url.hostname,
625
+ subdomains: url.hostname ? url.hostname.split(".").slice(0, -2) : [],
626
+ query: safeQuery,
627
+ cookies,
628
+ params: {},
629
+ body,
630
+ headers: headersObj,
631
+ get: (name) => request.headers.get(name.toLowerCase()) || void 0,
632
+ header: (name) => request.headers.get(name.toLowerCase()) || void 0,
633
+ ip: request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || "",
634
+ ips: request.headers.get("x-forwarded-for")?.split(/\s*,\s*/) || []
635
+ };
636
+ let statusCode = 200;
637
+ const responseHeaders = new Headers();
638
+ let responseBody = null;
639
+ const rawRes = {
640
+ statusCode,
641
+ status: (code) => {
642
+ statusCode = code;
643
+ return rawRes;
644
+ },
645
+ set: (field, value) => {
646
+ responseHeaders.set(field, String(value));
647
+ return rawRes;
648
+ },
649
+ header: (field, value) => {
650
+ responseHeaders.set(field, String(value));
651
+ return rawRes;
652
+ },
653
+ get: (field) => responseHeaders.get(field) || void 0,
654
+ type: (t) => {
655
+ responseHeaders.set("Content-Type", t);
656
+ return rawRes;
657
+ },
658
+ contentType: (t) => {
659
+ responseHeaders.set("Content-Type", t);
660
+ return rawRes;
661
+ },
662
+ location: (u) => {
663
+ responseHeaders.set("Location", u);
664
+ return rawRes;
665
+ },
666
+ redirect: ((a, b) => {
667
+ if (typeof a === "number") {
668
+ statusCode = a;
669
+ responseHeaders.set("Location", b);
670
+ } else {
671
+ statusCode = 302;
672
+ responseHeaders.set("Location", a);
673
+ }
674
+ return rawRes;
675
+ }),
676
+ cookie: (name, val, opts = {}) => {
677
+ const s = serializeCookie(name, val, opts);
678
+ const prev = responseHeaders.get("Set-Cookie");
679
+ if (prev) {
680
+ responseHeaders.append("Set-Cookie", s);
681
+ } else {
682
+ responseHeaders.set("Set-Cookie", s);
683
+ }
684
+ return rawRes;
685
+ },
686
+ clearCookie: (name, opts = {}) => {
687
+ return rawRes.cookie(name, "", { ...opts, expires: /* @__PURE__ */ new Date(1), maxAge: 0 });
688
+ },
689
+ send: (b) => {
690
+ if (b === void 0 || b === null) {
691
+ responseBody = "";
692
+ } else if (typeof b === "object") {
693
+ responseHeaders.set("Content-Type", "application/json");
694
+ responseBody = JSON.stringify(b);
695
+ } else {
696
+ responseBody = String(b);
697
+ }
698
+ return rawRes;
699
+ },
700
+ json: (b) => {
701
+ responseHeaders.set("Content-Type", "application/json");
702
+ responseBody = JSON.stringify(b);
703
+ return rawRes;
704
+ }
705
+ };
706
+ const errorHandler = (err) => {
707
+ statusCode = err instanceof HttpError ? err.status : 500;
708
+ responseHeaders.set("Content-Type", "application/json");
709
+ responseBody = JSON.stringify(
710
+ err instanceof HttpError ? err.payload ?? { error: err.message || "Internal Server Error" } : { error: err?.message || "Internal Server Error" }
711
+ );
712
+ };
713
+ try {
714
+ const { handlers, params } = this.router.find(request.method, urlPath || "/");
715
+ rawReq.params = params || {};
716
+ const chain = [
717
+ ...this.middlewares.map(adaptRequestHandler),
718
+ ...handlers.map(adaptRequestHandler)
719
+ ];
720
+ let idx = 0;
721
+ const next = async (err) => {
722
+ if (err) return errorHandler(err);
723
+ if (idx >= chain.length) return;
724
+ const fn = chain[idx++];
725
+ try {
726
+ await fn({
727
+ request: rawReq,
728
+ response: rawRes,
729
+ req: rawReq,
730
+ res: rawRes,
731
+ next
732
+ });
733
+ } catch (e) {
734
+ return errorHandler(e);
735
+ }
736
+ };
737
+ await next();
738
+ } catch (err) {
739
+ errorHandler(err);
740
+ }
741
+ return new Response(responseBody, {
742
+ status: statusCode,
743
+ headers: responseHeaders
744
+ });
745
+ }
746
+ async handle(rawReq, rawRes) {
747
+ rawReq.originalUrl = rawReq.url || "";
748
+ rawReq.protocol = this.opts.server?.https ? "https" : "http";
749
+ rawReq.secure = rawReq.protocol === "https";
750
+ rawReq.hostname = String(rawReq.headers["host"] || "").split(":")[0] || "";
751
+ rawReq.subdomains = rawReq.hostname ? rawReq.hostname.split(".").slice(0, -2) : [];
752
+ const ipsRaw = rawReq.headers["x-forwarded-for"];
753
+ rawReq.ips = typeof ipsRaw === "string" ? ipsRaw.split(/\s*,\s*/) : [];
754
+ rawReq.get = rawReq.header = (name) => {
755
+ const v = rawReq.headers[name.toLowerCase()];
756
+ if (Array.isArray(v)) return v[0];
757
+ return typeof v === "string" ? v : void 0;
758
+ };
759
+ rawRes.status = (code) => {
760
+ rawRes.statusCode = code;
761
+ return rawRes;
762
+ };
763
+ rawRes.set = rawRes.header = (field, value) => {
764
+ rawRes.setHeader(field, value);
765
+ return rawRes;
766
+ };
767
+ rawRes.get = (field) => {
768
+ const v = rawRes.getHeader(field);
769
+ if (Array.isArray(v)) return v[0];
770
+ return typeof v === "number" ? String(v) : v;
771
+ };
772
+ rawRes.type = rawRes.contentType = (t) => {
773
+ rawRes.setHeader("Content-Type", t);
774
+ return rawRes;
775
+ };
776
+ rawRes.location = (u) => {
777
+ rawRes.setHeader("Location", u);
778
+ return rawRes;
779
+ };
780
+ rawRes.redirect = ((a, b) => {
781
+ if (typeof a === "number") {
782
+ rawRes.statusCode = a;
783
+ rawRes.setHeader("Location", b);
784
+ } else {
785
+ rawRes.statusCode = 302;
786
+ rawRes.setHeader("Location", a);
787
+ }
788
+ rawRes.end();
789
+ return rawRes;
790
+ });
791
+ rawRes.cookie = (name, val, opts = {}) => {
792
+ const s = serializeCookie(name, val, opts);
793
+ const prev = rawRes.getHeader("Set-Cookie");
794
+ if (prev) {
795
+ const list = Array.isArray(prev) ? prev.concat(s) : [String(prev), s];
796
+ rawRes.setHeader("Set-Cookie", list);
797
+ } else {
798
+ rawRes.setHeader("Set-Cookie", s);
799
+ }
800
+ return rawRes;
801
+ };
802
+ rawRes.clearCookie = (name, opts = {}) => {
803
+ return rawRes.cookie(name, "", { ...opts, expires: /* @__PURE__ */ new Date(1), maxAge: 0 });
804
+ };
805
+ rawRes.send = (b) => {
806
+ if (b === void 0 || b === null) {
807
+ rawRes.end();
808
+ return rawRes;
809
+ }
810
+ if (typeof b === "object") {
811
+ rawRes.setHeader("Content-Type", "application/json");
812
+ rawRes.end(JSON.stringify(b));
813
+ } else {
814
+ rawRes.end(String(b));
815
+ }
816
+ return rawRes;
817
+ };
818
+ rawRes.json = (b) => {
819
+ rawRes.setHeader("Content-Type", "application/json");
820
+ rawRes.end(JSON.stringify(b));
821
+ return rawRes;
822
+ };
823
+ const [urlPath, qs] = (rawReq.url || "").split("?");
824
+ rawReq.path = urlPath || "/";
825
+ const rawQuery = parseQS(qs || "");
826
+ const safeQuery = {};
827
+ for (const k in rawQuery) {
828
+ const v = rawQuery[k];
829
+ safeQuery[k] = Array.isArray(v) ? v[0] || "" : v || "";
830
+ }
831
+ rawReq.query = safeQuery;
832
+ rawReq.cookies = parseCookiesHeader(rawReq.headers["cookie"] || "");
833
+ rawReq.params = {};
834
+ const ipRaw = rawReq.headers["x-forwarded-for"] || rawReq.socket.remoteAddress || "";
835
+ const ipStr = Array.isArray(ipRaw) ? ipRaw[0] : ipRaw;
836
+ rawReq.ip = String(ipStr).split(",")[0]?.trim() || "";
837
+ rawReq.body = {};
838
+ if (["POST", "PUT", "PATCH"].includes((rawReq.method || "").toUpperCase())) {
839
+ await new Promise((resolve) => {
840
+ let buf = "";
841
+ rawReq.on("data", (chunk) => {
842
+ buf += chunk;
843
+ });
844
+ rawReq.on("end", () => {
845
+ try {
846
+ const ct = String(rawReq.headers["content-type"] || "");
847
+ if (ct.includes("application/json")) {
848
+ rawReq.body = JSON.parse(buf || "{}");
849
+ } else {
850
+ const parsed = parseQS(buf || "");
851
+ const b = {};
852
+ for (const k in parsed) {
853
+ const v = parsed[k];
854
+ b[k] = Array.isArray(v) ? v[0] || "" : v || "";
855
+ }
856
+ rawReq.body = b;
857
+ }
858
+ } catch {
859
+ rawReq.body = {};
860
+ }
861
+ resolve();
862
+ });
863
+ rawReq.on("error", (err) => {
864
+ logger("error", "Body parse error: " + err.message);
865
+ resolve();
866
+ });
867
+ });
868
+ }
869
+ const errorHandler = (err) => {
870
+ logger("error", err?.message || String(err));
871
+ rawRes.status(err instanceof HttpError ? err.status : 500).json(
872
+ err instanceof HttpError ? err.payload ?? { error: err.message || "Internal Server Error" } : { error: err?.message || "Internal Server Error" }
873
+ );
874
+ };
875
+ try {
876
+ const { handlers, params } = this.router.find(rawReq.method || "GET", rawReq.path);
877
+ rawReq.params = params || {};
878
+ const chain = [
879
+ ...this.middlewares.map(adaptRequestHandler),
880
+ ...handlers.map(adaptRequestHandler)
881
+ ];
882
+ let idx = 0;
883
+ const next = async (err) => {
884
+ if (err) return errorHandler(err);
885
+ if (idx >= chain.length) return;
886
+ const fn = chain[idx++];
887
+ try {
888
+ await fn({
889
+ request: rawReq,
890
+ response: rawRes,
891
+ req: rawReq,
892
+ res: rawRes,
893
+ next
894
+ });
895
+ } catch (e) {
896
+ return errorHandler(e);
897
+ }
898
+ };
899
+ await next();
900
+ } catch (err) {
901
+ errorHandler(err);
902
+ }
903
+ }
904
+ };
905
+
906
+ exports.AzuraClient = AzuraClient;
907
+ exports.Router = Router;
908
+ exports.createLoggingMiddleware = createLoggingMiddleware;
909
+ //# sourceMappingURL=index.cjs.map
910
+ //# sourceMappingURL=index.cjs.map