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