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,1044 +0,0 @@
1
- import { createHmac, createHash, randomUUID, randomBytes } from 'crypto';
2
- import { createGzip, createDeflate } from 'zlib';
3
- import { existsSync, statSync, createReadStream, mkdirSync, writeFileSync } from 'fs';
4
- import { resolve, join, extname } from 'path';
5
- import { request as request$1 } from 'http';
6
- import { request } from 'https';
7
- import { URL } from 'url';
8
-
9
- // src/plugins/CORSPlugin.ts
10
- var DEFAULT_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
11
- var DEFAULT_HEADERS = ["Content-Type", "Authorization", "Accept", "X-Requested-With"];
12
- function CORSPlugin(options = {}) {
13
- const {
14
- origins = "*",
15
- methods = DEFAULT_METHODS,
16
- allowedHeaders = DEFAULT_HEADERS,
17
- exposedHeaders = [],
18
- credentials = false,
19
- maxAge = 86400,
20
- preflightContinue = false
21
- } = options;
22
- const methodsStr = methods.join(", ");
23
- const headersStr = allowedHeaders.join(", ");
24
- const exposedStr = exposedHeaders.length > 0 ? exposedHeaders.join(", ") : "";
25
- const isAllowedOrigin = (origin) => {
26
- if (origins === "*") return credentials ? origin : "*";
27
- if (typeof origins === "function") return origins(origin) ? origin : false;
28
- if (typeof origins === "string") return origin === origins ? origin : false;
29
- if (Array.isArray(origins)) return origins.includes(origin) ? origin : false;
30
- return false;
31
- };
32
- return (ctx, next) => {
33
- const { req, res } = ctx;
34
- const origin = req.headers.origin ?? "";
35
- const allowedOrigin = isAllowedOrigin(origin);
36
- if (allowedOrigin) {
37
- res.setHeader("Access-Control-Allow-Origin", allowedOrigin);
38
- if (credentials) res.setHeader("Access-Control-Allow-Credentials", "true");
39
- if (exposedStr) res.setHeader("Access-Control-Expose-Headers", exposedStr);
40
- if (allowedOrigin !== "*") res.setHeader("Vary", "Origin");
41
- }
42
- if (req.method === "OPTIONS") {
43
- res.setHeader("Access-Control-Allow-Methods", methodsStr);
44
- res.setHeader("Access-Control-Allow-Headers", headersStr);
45
- res.setHeader("Access-Control-Max-Age", String(maxAge));
46
- if (!preflightContinue) {
47
- res.statusCode = 204;
48
- res.end();
49
- return;
50
- }
51
- }
52
- next();
53
- };
54
- }
55
-
56
- // src/plugins/RateLimitPlugin.ts
57
- var MemoryStore = class {
58
- store = /* @__PURE__ */ new Map();
59
- cleanupInterval;
60
- constructor(windowMs) {
61
- this.cleanupInterval = setInterval(() => {
62
- const now = Date.now();
63
- for (const [key, entry] of this.store) {
64
- if (now >= entry.resetTime) this.store.delete(key);
65
- }
66
- }, windowMs);
67
- if (this.cleanupInterval.unref) this.cleanupInterval.unref();
68
- }
69
- increment(key, windowMs) {
70
- const now = Date.now();
71
- const entry = this.store.get(key);
72
- if (!entry || now >= entry.resetTime) {
73
- const resetTime = now + windowMs;
74
- this.store.set(key, { count: 1, resetTime });
75
- return { totalHits: 1, resetTime: new Date(resetTime) };
76
- }
77
- entry.count++;
78
- return { totalHits: entry.count, resetTime: new Date(entry.resetTime) };
79
- }
80
- decrement(key) {
81
- const entry = this.store.get(key);
82
- if (entry && entry.count > 0) entry.count--;
83
- }
84
- resetKey(key) {
85
- this.store.delete(key);
86
- }
87
- destroy() {
88
- clearInterval(this.cleanupInterval);
89
- this.store.clear();
90
- }
91
- };
92
- function RateLimitPlugin(options = {}) {
93
- const {
94
- windowMs = 6e4,
95
- max = 100,
96
- message = { error: { statusCode: 429, message: "Too many requests, please try again later" } },
97
- statusCode = 429,
98
- keyGenerator = (req) => req.ip,
99
- skipSuccessfulRequests = false,
100
- skipFailedRequests = false
101
- } = options;
102
- const memStore = new MemoryStore(windowMs);
103
- return (ctx, next) => {
104
- const { req, res } = ctx;
105
- const key = keyGenerator(req);
106
- const { totalHits, resetTime } = memStore.increment(key, windowMs);
107
- res.setHeader("X-RateLimit-Limit", String(max));
108
- res.setHeader("X-RateLimit-Remaining", String(Math.max(0, max - totalHits)));
109
- res.setHeader("X-RateLimit-Reset", String(Math.ceil(resetTime.getTime() / 1e3)));
110
- if (totalHits > max) {
111
- res.setHeader("Retry-After", String(Math.ceil(windowMs / 1e3)));
112
- res.statusCode = statusCode;
113
- const body = typeof message === "string" ? message : JSON.stringify(message);
114
- res.setHeader("Content-Type", typeof message === "string" ? "text/plain" : "application/json");
115
- res.end(body);
116
- return;
117
- }
118
- const origEnd = res.end;
119
- res.end = function(...args) {
120
- if (skipSuccessfulRequests && res.statusCode < 400) {
121
- memStore.decrement(key);
122
- }
123
- if (skipFailedRequests && res.statusCode >= 400) {
124
- memStore.decrement(key);
125
- }
126
- return origEnd.apply(this, args);
127
- };
128
- next();
129
- };
130
- }
131
-
132
- // src/plugins/HelmetPlugin.ts
133
- function HelmetPlugin(options = {}) {
134
- const headers = [];
135
- if (options.noSniff !== false) {
136
- headers.push(["X-Content-Type-Options", "nosniff"]);
137
- }
138
- if (options.xssFilter !== false) {
139
- headers.push(["X-XSS-Protection", "0"]);
140
- }
141
- if (options.ieNoOpen !== false) {
142
- headers.push(["X-Download-Options", "noopen"]);
143
- }
144
- const frameguard = options.frameguard ?? true;
145
- if (frameguard !== false) {
146
- const action = typeof frameguard === "object" ? frameguard.action : "sameorigin";
147
- headers.push(["X-Frame-Options", action.toUpperCase()]);
148
- }
149
- if (options.dnsPrefetchControl !== false) {
150
- const allow = typeof options.dnsPrefetchControl === "object" ? options.dnsPrefetchControl.allow : false;
151
- headers.push(["X-DNS-Prefetch-Control", allow ? "on" : "off"]);
152
- }
153
- const hsts = options.hsts ?? true;
154
- if (hsts !== false) {
155
- const maxAge = typeof hsts === "object" ? hsts.maxAge : 15552e3;
156
- const includeSubDomains = typeof hsts === "object" ? hsts.includeSubDomains !== false : true;
157
- const preload = typeof hsts === "object" ? hsts.preload : false;
158
- let value = `max-age=${maxAge}`;
159
- if (includeSubDomains) value += "; includeSubDomains";
160
- if (preload) value += "; preload";
161
- headers.push(["Strict-Transport-Security", value]);
162
- }
163
- if (options.crossOriginEmbedderPolicy !== false) {
164
- headers.push(["Cross-Origin-Embedder-Policy", "require-corp"]);
165
- }
166
- if (options.crossOriginOpenerPolicy !== false) {
167
- const policy = typeof options.crossOriginOpenerPolicy === "object" ? options.crossOriginOpenerPolicy.policy : "same-origin";
168
- headers.push(["Cross-Origin-Opener-Policy", policy]);
169
- }
170
- if (options.crossOriginResourcePolicy !== false) {
171
- const policy = typeof options.crossOriginResourcePolicy === "object" ? options.crossOriginResourcePolicy.policy : "same-origin";
172
- headers.push(["Cross-Origin-Resource-Policy", policy]);
173
- }
174
- const referrer = options.referrerPolicy ?? true;
175
- if (referrer !== false) {
176
- const policy = typeof referrer === "object" ? Array.isArray(referrer.policy) ? referrer.policy.join(", ") : referrer.policy : "no-referrer";
177
- headers.push(["Referrer-Policy", policy]);
178
- }
179
- if (options.contentSecurityPolicy !== false && options.contentSecurityPolicy) {
180
- const csp = options.contentSecurityPolicy;
181
- const directives = Object.entries(csp).map(([key, values]) => `${key} ${values.join(" ")}`).join("; ");
182
- headers.push(["Content-Security-Policy", directives]);
183
- }
184
- if (options.permissionsPolicy) {
185
- const pp = Object.entries(options.permissionsPolicy).map(([key, values]) => `${key}=(${values.join(" ")})`).join(", ");
186
- headers.push(["Permissions-Policy", pp]);
187
- }
188
- return (ctx, next) => {
189
- for (const [name, value] of headers) {
190
- ctx.res.setHeader(name, value);
191
- }
192
- next();
193
- };
194
- }
195
- function base64urlEncode(data) {
196
- const buf = typeof data === "string" ? Buffer.from(data) : data;
197
- return buf.toString("base64url");
198
- }
199
- function base64urlDecode(str) {
200
- return Buffer.from(str, "base64url").toString("utf-8");
201
- }
202
- var ALGO_MAP = {
203
- HS256: "sha256",
204
- HS384: "sha384",
205
- HS512: "sha512"
206
- };
207
- function signJWT(payload, secret, options = {}) {
208
- const algorithm = options.algorithm ?? "HS256";
209
- const header = base64urlEncode(JSON.stringify({ alg: algorithm, typ: "JWT" }));
210
- const now = Math.floor(Date.now() / 1e3);
211
- const body = { ...payload, iat: now };
212
- if (options.expiresIn) {
213
- body.exp = now + parseExpiration(options.expiresIn);
214
- }
215
- const encodedPayload = base64urlEncode(JSON.stringify(body));
216
- const signature = createHmac(ALGO_MAP[algorithm] ?? "sha256", secret).update(`${header}.${encodedPayload}`).digest("base64url");
217
- return `${header}.${encodedPayload}.${signature}`;
218
- }
219
- function verifyJWT(token, secret, options = {}) {
220
- const parts = token.split(".");
221
- if (parts.length !== 3) throw new Error("Invalid token format");
222
- const [header, payload, signature] = parts;
223
- const algorithm = options.algorithm ?? "HS256";
224
- const expectedSig = createHmac(ALGO_MAP[algorithm] ?? "sha256", secret).update(`${header}.${payload}`).digest("base64url");
225
- if (signature !== expectedSig) throw new Error("Invalid token signature");
226
- const decoded = JSON.parse(base64urlDecode(payload));
227
- if (decoded.exp && decoded.exp < Math.floor(Date.now() / 1e3)) {
228
- throw new Error("Token expired");
229
- }
230
- return decoded;
231
- }
232
- function parseExpiration(exp) {
233
- if (typeof exp === "number") return exp;
234
- const match = exp.match(/^(\d+)(s|m|h|d)$/);
235
- if (!match) return 3600;
236
- const val = parseInt(match[1], 10);
237
- switch (match[2]) {
238
- case "s":
239
- return val;
240
- case "m":
241
- return val * 60;
242
- case "h":
243
- return val * 3600;
244
- case "d":
245
- return val * 86400;
246
- default:
247
- return 3600;
248
- }
249
- }
250
- function extractToken(req) {
251
- const auth = req.headers.authorization;
252
- if (auth?.startsWith("Bearer ")) return auth.slice(7);
253
- return req.query.token ?? req.cookies?.token ?? null;
254
- }
255
- function JWTPlugin(options) {
256
- const {
257
- secret,
258
- algorithm = "HS256",
259
- paths = [],
260
- exclude = [],
261
- getToken
262
- } = options;
263
- return (ctx, next) => {
264
- const { req, res } = ctx;
265
- const path = req.pathname;
266
- if (exclude.some((p) => path.startsWith(p))) {
267
- next();
268
- return;
269
- }
270
- if (paths.length > 0 && !paths.some((p) => path.startsWith(p))) {
271
- next();
272
- return;
273
- }
274
- const token = getToken ? getToken(req) : extractToken(req);
275
- if (!token) {
276
- res.statusCode = 401;
277
- res.setHeader("Content-Type", "application/json");
278
- res.end(JSON.stringify({ error: { statusCode: 401, message: "No token provided" } }));
279
- return;
280
- }
281
- try {
282
- const payload = verifyJWT(token, secret, { algorithm });
283
- req.user = payload;
284
- next();
285
- } catch (err) {
286
- res.statusCode = 401;
287
- res.setHeader("Content-Type", "application/json");
288
- res.end(JSON.stringify({ error: { statusCode: 401, message: err.message ?? "Invalid token" } }));
289
- }
290
- };
291
- }
292
- var MemorySessionStore = class {
293
- sessions = /* @__PURE__ */ new Map();
294
- cleanupTimer;
295
- constructor() {
296
- this.cleanupTimer = setInterval(() => {
297
- const now = Date.now();
298
- for (const [id, session] of this.sessions) {
299
- if (now >= session.expires) this.sessions.delete(id);
300
- }
301
- }, 6e4);
302
- if (this.cleanupTimer.unref) this.cleanupTimer.unref();
303
- }
304
- async get(id) {
305
- const session = this.sessions.get(id);
306
- if (!session) return null;
307
- if (Date.now() >= session.expires) {
308
- this.sessions.delete(id);
309
- return null;
310
- }
311
- return session.data;
312
- }
313
- async set(id, data, maxAge) {
314
- this.sessions.set(id, { data, expires: Date.now() + maxAge });
315
- }
316
- async destroy(id) {
317
- this.sessions.delete(id);
318
- }
319
- async touch(id, maxAge) {
320
- const session = this.sessions.get(id);
321
- if (session) session.expires = Date.now() + maxAge;
322
- }
323
- };
324
- function generateSessionId() {
325
- return randomBytes(24).toString("hex");
326
- }
327
- function SessionPlugin(options) {
328
- const {
329
- secret: _secret,
330
- name = "azura.sid",
331
- maxAge = 864e5,
332
- secure = false,
333
- httpOnly = true,
334
- sameSite = "Lax",
335
- store = new MemorySessionStore()
336
- } = options;
337
- return async (ctx, next) => {
338
- const { req, res } = ctx;
339
- let sessionId = req.cookies[name];
340
- let sessionData = null;
341
- if (sessionId) {
342
- sessionData = await store.get(sessionId);
343
- }
344
- if (!sessionData) {
345
- sessionId = generateSessionId();
346
- sessionData = {};
347
- }
348
- req.session = sessionData;
349
- const originalEnd = res.end;
350
- res.end = async function(...args) {
351
- await store.set(sessionId, req.session ?? {}, maxAge);
352
- const cookieParts = [
353
- `${name}=${sessionId}`,
354
- `Path=/`,
355
- `Max-Age=${Math.floor(maxAge / 1e3)}`,
356
- `SameSite=${sameSite}`
357
- ];
358
- if (httpOnly) cookieParts.push("HttpOnly");
359
- if (secure) cookieParts.push("Secure");
360
- const existing = res.getHeader("Set-Cookie");
361
- const cookies = existing ? [...Array.isArray(existing) ? existing : [String(existing)], cookieParts.join("; ")] : [cookieParts.join("; ")];
362
- res.setHeader("Set-Cookie", cookies);
363
- return originalEnd.apply(this, args);
364
- };
365
- next();
366
- };
367
- }
368
- var COMPRESSIBLE_TYPES = /^text\/|application\/json|application\/javascript|application\/xml|image\/svg\+xml/;
369
- function CompressionPlugin(options = {}) {
370
- const {
371
- threshold = 1024,
372
- level = 6,
373
- algorithms = ["gzip", "deflate"],
374
- filter
375
- } = options;
376
- return (ctx, next) => {
377
- const { req, res } = ctx;
378
- if (filter && !filter(req, res)) {
379
- next();
380
- return;
381
- }
382
- const acceptEncoding = req.headers["accept-encoding"] ?? "";
383
- let encoding = null;
384
- for (const algo of algorithms) {
385
- if (typeof acceptEncoding === "string" && acceptEncoding.includes(algo)) {
386
- encoding = algo;
387
- break;
388
- }
389
- }
390
- if (!encoding) {
391
- next();
392
- return;
393
- }
394
- const originalEnd = res.end;
395
- res.write;
396
- res.end = function(chunk, encodingArg, callback) {
397
- if (!chunk || typeof chunk !== "string" && !Buffer.isBuffer(chunk)) {
398
- return originalEnd.call(this, chunk, encodingArg, callback);
399
- }
400
- const buf = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
401
- if (buf.length < threshold) {
402
- return originalEnd.call(this, chunk, encodingArg, callback);
403
- }
404
- const contentType = String(res.getHeader("Content-Type") ?? "");
405
- if (!COMPRESSIBLE_TYPES.test(contentType)) {
406
- return originalEnd.call(this, chunk, encodingArg, callback);
407
- }
408
- res.removeHeader("Content-Length");
409
- res.setHeader("Content-Encoding", encoding);
410
- res.setHeader("Vary", "Accept-Encoding");
411
- let stream;
412
- if (encoding === "gzip") {
413
- stream = createGzip({ level });
414
- } else {
415
- stream = createDeflate({ level });
416
- }
417
- const chunks = [];
418
- stream.on("data", (c) => chunks.push(c));
419
- stream.on("end", () => {
420
- const compressed = Buffer.concat(chunks);
421
- res.setHeader("Content-Length", compressed.length);
422
- originalEnd.call(res, compressed);
423
- });
424
- stream.end(buf);
425
- };
426
- next();
427
- };
428
- }
429
- var MIME_TYPES = {
430
- ".html": "text/html; charset=utf-8",
431
- ".htm": "text/html; charset=utf-8",
432
- ".css": "text/css; charset=utf-8",
433
- ".js": "application/javascript; charset=utf-8",
434
- ".mjs": "application/javascript; charset=utf-8",
435
- ".json": "application/json; charset=utf-8",
436
- ".xml": "application/xml; charset=utf-8",
437
- ".txt": "text/plain; charset=utf-8",
438
- ".csv": "text/csv; charset=utf-8",
439
- ".png": "image/png",
440
- ".jpg": "image/jpeg",
441
- ".jpeg": "image/jpeg",
442
- ".gif": "image/gif",
443
- ".svg": "image/svg+xml",
444
- ".ico": "image/x-icon",
445
- ".webp": "image/webp",
446
- ".avif": "image/avif",
447
- ".woff": "font/woff",
448
- ".woff2": "font/woff2",
449
- ".ttf": "font/ttf",
450
- ".otf": "font/otf",
451
- ".eot": "application/vnd.ms-fontobject",
452
- ".mp4": "video/mp4",
453
- ".webm": "video/webm",
454
- ".mp3": "audio/mpeg",
455
- ".ogg": "audio/ogg",
456
- ".wav": "audio/wav",
457
- ".pdf": "application/pdf",
458
- ".zip": "application/zip",
459
- ".gz": "application/gzip",
460
- ".wasm": "application/wasm",
461
- ".map": "application/json"
462
- };
463
- function StaticPlugin(options) {
464
- const {
465
- root,
466
- prefix = "/",
467
- index = ["index.html"],
468
- dotfiles = "ignore",
469
- maxAge = 0,
470
- etag = true,
471
- lastModified = true,
472
- fallthrough = true,
473
- immutable = false
474
- } = options;
475
- const resolvedRoot = resolve(root);
476
- const indexFiles = Array.isArray(index) ? index : [index];
477
- const normalizedPrefix = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
478
- return (ctx, next) => {
479
- const { req, res } = ctx;
480
- if (req.method !== "GET" && req.method !== "HEAD") {
481
- next();
482
- return;
483
- }
484
- const path = req.pathname;
485
- if (!path.startsWith(normalizedPrefix)) {
486
- next();
487
- return;
488
- }
489
- const relativePath = path.slice(normalizedPrefix.length) || "/";
490
- if (relativePath.includes("..")) {
491
- next();
492
- return;
493
- }
494
- if (dotfiles !== "allow" && relativePath.split("/").some((s) => s.startsWith("."))) {
495
- if (dotfiles === "deny") {
496
- res.statusCode = 403;
497
- res.end("Forbidden");
498
- return;
499
- }
500
- next();
501
- return;
502
- }
503
- let filePath = join(resolvedRoot, relativePath);
504
- if (!existsSync(filePath)) {
505
- if (fallthrough) {
506
- next();
507
- return;
508
- }
509
- res.statusCode = 404;
510
- res.end("Not Found");
511
- return;
512
- }
513
- let stat = statSync(filePath);
514
- if (stat.isDirectory()) {
515
- let found = false;
516
- for (const idx of indexFiles) {
517
- const candidate = join(filePath, idx);
518
- if (existsSync(candidate)) {
519
- filePath = candidate;
520
- stat = statSync(filePath);
521
- found = true;
522
- break;
523
- }
524
- }
525
- if (!found) {
526
- if (fallthrough) {
527
- next();
528
- return;
529
- }
530
- res.statusCode = 404;
531
- res.end("Not Found");
532
- return;
533
- }
534
- }
535
- const ext = extname(filePath).toLowerCase();
536
- const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
537
- res.setHeader("Content-Type", contentType);
538
- res.setHeader("Content-Length", stat.size);
539
- if (lastModified) {
540
- res.setHeader("Last-Modified", stat.mtime.toUTCString());
541
- }
542
- if (etag) {
543
- const etagValue = `W/"${stat.size.toString(16)}-${stat.mtime.getTime().toString(16)}"`;
544
- res.setHeader("ETag", etagValue);
545
- const ifNoneMatch = req.headers["if-none-match"];
546
- if (ifNoneMatch === etagValue) {
547
- res.statusCode = 304;
548
- res.end();
549
- return;
550
- }
551
- }
552
- let cacheControl = `public, max-age=${maxAge}`;
553
- if (immutable) cacheControl += ", immutable";
554
- res.setHeader("Cache-Control", cacheControl);
555
- if (req.method === "HEAD") {
556
- res.end();
557
- return;
558
- }
559
- const stream = createReadStream(filePath);
560
- stream.pipe(res);
561
- };
562
- }
563
- function ETagPlugin(options = {}) {
564
- const { weak = true } = options;
565
- return (ctx, next) => {
566
- const { req, res } = ctx;
567
- const originalEnd = res.end;
568
- res.end = function(chunk, encoding, callback) {
569
- if (chunk && (typeof chunk === "string" || Buffer.isBuffer(chunk)) && res.statusCode === 200) {
570
- const buf = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
571
- const hash = createHash("md5").update(buf).digest("hex").slice(0, 16);
572
- const etag = weak ? `W/"${hash}"` : `"${hash}"`;
573
- res.setHeader("ETag", etag);
574
- const ifNoneMatch = req.headers["if-none-match"];
575
- if (ifNoneMatch === etag) {
576
- res.statusCode = 304;
577
- return originalEnd.call(this);
578
- }
579
- }
580
- return originalEnd.call(this, chunk, encoding, callback);
581
- };
582
- next();
583
- };
584
- }
585
- function RequestIdPlugin(options = {}) {
586
- const {
587
- header = "X-Request-Id",
588
- generator = randomUUID
589
- } = options;
590
- return (ctx, next) => {
591
- const { req, res } = ctx;
592
- const existingId = req.headers[header.toLowerCase()];
593
- const requestId = existingId ?? generator();
594
- req.requestId = requestId;
595
- res.setHeader(header, requestId);
596
- next();
597
- };
598
- }
599
-
600
- // src/plugins/TimeoutPlugin.ts
601
- function TimeoutPlugin(options) {
602
- const {
603
- timeout,
604
- message = { error: { statusCode: 408, message: "Request Timeout" } },
605
- statusCode = 408
606
- } = options;
607
- return (ctx, next) => {
608
- const { res } = ctx;
609
- let timedOut = false;
610
- const timer = setTimeout(() => {
611
- timedOut = true;
612
- if (!res.headersSent) {
613
- res.statusCode = statusCode;
614
- res.setHeader("Content-Type", typeof message === "string" ? "text/plain" : "application/json");
615
- res.end(typeof message === "string" ? message : JSON.stringify(message));
616
- }
617
- }, timeout);
618
- const originalEnd = res.end;
619
- res.end = function(...args) {
620
- clearTimeout(timer);
621
- if (!timedOut) {
622
- return originalEnd.apply(this, args);
623
- }
624
- };
625
- next();
626
- };
627
- }
628
-
629
- // src/plugins/HealthCheckPlugin.ts
630
- function HealthCheckPlugin(options = {}) {
631
- const {
632
- path = "/health",
633
- checks = {},
634
- timeout: checkTimeout = 5e3
635
- } = options;
636
- return async (ctx, next) => {
637
- const { req, res } = ctx;
638
- if (req.pathname !== path) {
639
- next();
640
- return;
641
- }
642
- const results = {};
643
- let allHealthy = true;
644
- for (const [name, check] of Object.entries(checks)) {
645
- const start = Date.now();
646
- try {
647
- const result = await Promise.race([
648
- Promise.resolve(check()),
649
- new Promise(
650
- (_, reject) => setTimeout(() => reject(new Error("timeout")), checkTimeout)
651
- )
652
- ]);
653
- results[name] = {
654
- status: result ? "healthy" : "unhealthy",
655
- duration: Date.now() - start
656
- };
657
- if (!result) allHealthy = false;
658
- } catch {
659
- results[name] = {
660
- status: "unhealthy",
661
- duration: Date.now() - start
662
- };
663
- allHealthy = false;
664
- }
665
- }
666
- const body = JSON.stringify({
667
- status: allHealthy ? "healthy" : "unhealthy",
668
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
669
- uptime: process.uptime(),
670
- memory: process.memoryUsage(),
671
- checks: results
672
- });
673
- res.statusCode = allHealthy ? 200 : 503;
674
- res.setHeader("Content-Type", "application/json");
675
- res.setHeader("Cache-Control", "no-cache, no-store");
676
- res.end(body);
677
- };
678
- }
679
-
680
- // src/plugins/CircuitBreakerPlugin.ts
681
- function CircuitBreakerPlugin(options = {}) {
682
- const {
683
- threshold = 5,
684
- timeout: breakerTimeout = 3e4,
685
- resetTimeout = 6e4,
686
- halfOpenRequests = 1,
687
- monitor
688
- } = options;
689
- let state = "CLOSED";
690
- let failures = 0;
691
- let successes = 0;
692
- let lastFailureTime = 0;
693
- let halfOpenCount = 0;
694
- const transition = (newState) => {
695
- if (state !== newState) {
696
- state = newState;
697
- monitor?.(state);
698
- }
699
- };
700
- return (ctx, next) => {
701
- const { res } = ctx;
702
- if (state === "OPEN") {
703
- if (Date.now() - lastFailureTime >= resetTimeout) {
704
- transition("HALF_OPEN");
705
- halfOpenCount = 0;
706
- } else {
707
- res.statusCode = 503;
708
- res.setHeader("Content-Type", "application/json");
709
- res.setHeader("Retry-After", String(Math.ceil(resetTimeout / 1e3)));
710
- res.end(
711
- JSON.stringify({
712
- error: { statusCode: 503, message: "Service temporarily unavailable (circuit open)" }
713
- })
714
- );
715
- return;
716
- }
717
- }
718
- if (state === "HALF_OPEN" && halfOpenCount >= halfOpenRequests) {
719
- res.statusCode = 503;
720
- res.setHeader("Content-Type", "application/json");
721
- res.end(
722
- JSON.stringify({
723
- error: { statusCode: 503, message: "Service temporarily unavailable (circuit half-open)" }
724
- })
725
- );
726
- return;
727
- }
728
- if (state === "HALF_OPEN") halfOpenCount++;
729
- const originalEnd = res.end;
730
- res.end = function(...args) {
731
- if (res.statusCode >= 500) {
732
- failures++;
733
- lastFailureTime = Date.now();
734
- if (state === "HALF_OPEN" || failures >= threshold) {
735
- transition("OPEN");
736
- }
737
- } else {
738
- if (state === "HALF_OPEN") {
739
- successes++;
740
- if (successes >= halfOpenRequests) {
741
- failures = 0;
742
- successes = 0;
743
- transition("CLOSED");
744
- }
745
- } else {
746
- failures = Math.max(0, failures - 1);
747
- }
748
- }
749
- return originalEnd.apply(this, args);
750
- };
751
- next();
752
- };
753
- }
754
-
755
- // src/plugins/SSEPlugin.ts
756
- var SSEManager = class {
757
- clients = /* @__PURE__ */ new Map();
758
- clientCounter = 0;
759
- addClient(res) {
760
- const id = `sse-${++this.clientCounter}-${Date.now()}`;
761
- res.setHeader("Content-Type", "text/event-stream");
762
- res.setHeader("Cache-Control", "no-cache");
763
- res.setHeader("Connection", "keep-alive");
764
- res.setHeader("X-Accel-Buffering", "no");
765
- res.statusCode = 200;
766
- res.flushHeaders();
767
- const client = {
768
- id,
769
- res,
770
- send(event, data, eventId) {
771
- const serialized = typeof data === "string" ? data : JSON.stringify(data);
772
- let message = "";
773
- if (eventId) message += `id: ${eventId}
774
- `;
775
- message += `event: ${event}
776
- `;
777
- message += `data: ${serialized}
778
-
779
- `;
780
- res.write(message);
781
- },
782
- close() {
783
- res.end();
784
- }
785
- };
786
- this.clients.set(id, client);
787
- res.on("close", () => {
788
- this.clients.delete(id);
789
- });
790
- return client;
791
- }
792
- broadcast(event, data, id) {
793
- for (const client of this.clients.values()) {
794
- client.send(event, data, id);
795
- }
796
- }
797
- getClient(id) {
798
- return this.clients.get(id);
799
- }
800
- getClientCount() {
801
- return this.clients.size;
802
- }
803
- closeAll() {
804
- for (const client of this.clients.values()) {
805
- client.close();
806
- }
807
- this.clients.clear();
808
- }
809
- };
810
- function SSEPlugin(options) {
811
- const {
812
- path,
813
- heartbeatInterval = 3e4,
814
- retry = 3e3,
815
- maxClients = 1e3
816
- } = options;
817
- const manager = new SSEManager();
818
- let heartbeat = null;
819
- if (heartbeatInterval > 0) {
820
- heartbeat = setInterval(() => {
821
- manager.broadcast("heartbeat", { time: Date.now() });
822
- }, heartbeatInterval);
823
- if (heartbeat.unref) heartbeat.unref();
824
- }
825
- const plugin = Object.assign(
826
- (ctx, next) => {
827
- const { req, res } = ctx;
828
- if (req.pathname !== path || req.method !== "GET") {
829
- next();
830
- return;
831
- }
832
- if (manager.getClientCount() >= maxClients) {
833
- res.statusCode = 503;
834
- res.setHeader("Content-Type", "application/json");
835
- res.end(JSON.stringify({ error: { statusCode: 503, message: "Too many SSE connections" } }));
836
- return;
837
- }
838
- const client = manager.addClient(res);
839
- res.write(`retry: ${retry}
840
-
841
- `);
842
- client.send("connected", { clientId: client.id });
843
- },
844
- { manager }
845
- );
846
- return plugin;
847
- }
848
- function ProxyPlugin(options) {
849
- const {
850
- target,
851
- pathRewrite = {},
852
- changeOrigin = true,
853
- timeout = 3e4,
854
- headers: extraHeaders = {},
855
- onProxyReq,
856
- onProxyRes
857
- } = options;
858
- const targetUrl = new URL(target);
859
- const isHttps = targetUrl.protocol === "https:";
860
- const requestFn = isHttps ? request : request$1;
861
- return (ctx, next) => {
862
- const { req, res } = ctx;
863
- let targetPath = req.pathname;
864
- for (const [from, to] of Object.entries(pathRewrite)) {
865
- targetPath = targetPath.replace(new RegExp(from), to);
866
- }
867
- const search = req.url?.includes("?") ? req.url.slice(req.url.indexOf("?")) : "";
868
- const fullPath = targetPath + search;
869
- const proxyHeaders = {};
870
- for (const [key, val] of Object.entries(req.headers)) {
871
- if (val) proxyHeaders[key] = Array.isArray(val) ? val.join(", ") : val;
872
- }
873
- if (changeOrigin) {
874
- proxyHeaders.host = targetUrl.host;
875
- }
876
- Object.assign(proxyHeaders, extraHeaders);
877
- const proxyReq = requestFn(
878
- {
879
- hostname: targetUrl.hostname,
880
- port: targetUrl.port || (isHttps ? 443 : 80),
881
- path: fullPath,
882
- method: req.method,
883
- headers: proxyHeaders,
884
- timeout
885
- },
886
- (proxyRes) => {
887
- onProxyRes?.(proxyRes, res);
888
- res.statusCode = proxyRes.statusCode ?? 502;
889
- for (const [key, val] of Object.entries(proxyRes.headers)) {
890
- if (val) res.setHeader(key, val);
891
- }
892
- proxyRes.pipe(res);
893
- }
894
- );
895
- proxyReq.on("error", (err) => {
896
- if (!res.headersSent) {
897
- res.statusCode = 502;
898
- res.setHeader("Content-Type", "application/json");
899
- res.end(JSON.stringify({ error: { statusCode: 502, message: "Bad Gateway", details: err.message } }));
900
- }
901
- });
902
- proxyReq.on("timeout", () => {
903
- proxyReq.destroy();
904
- if (!res.headersSent) {
905
- res.statusCode = 504;
906
- res.setHeader("Content-Type", "application/json");
907
- res.end(JSON.stringify({ error: { statusCode: 504, message: "Gateway Timeout" } }));
908
- }
909
- });
910
- onProxyReq?.(proxyReq, req);
911
- if (req.method !== "GET" && req.method !== "HEAD") {
912
- req.pipe(proxyReq);
913
- } else {
914
- proxyReq.end();
915
- }
916
- };
917
- }
918
- function MultipartPlugin(options = {}) {
919
- const {
920
- maxFileSize = 10 * 1024 * 1024,
921
- maxFiles = 10,
922
- maxFieldSize = 1024 * 1024,
923
- maxFields = 50,
924
- allowedMimeTypes,
925
- uploadDir
926
- } = options;
927
- if (uploadDir && !existsSync(uploadDir)) {
928
- mkdirSync(uploadDir, { recursive: true });
929
- }
930
- return async (ctx, next) => {
931
- const { req } = ctx;
932
- const contentType = req.headers["content-type"] ?? "";
933
- if (!contentType.startsWith("multipart/form-data")) {
934
- next();
935
- return;
936
- }
937
- const boundaryMatch = contentType.match(/boundary=([^\s;]+)/);
938
- if (!boundaryMatch) {
939
- next();
940
- return;
941
- }
942
- const boundary = boundaryMatch[1];
943
- try {
944
- const { fields, files } = await parseMultipart(req, boundary, {
945
- maxFileSize,
946
- maxFiles,
947
- maxFieldSize,
948
- maxFields,
949
- allowedMimeTypes
950
- });
951
- if (uploadDir) {
952
- for (const file of files) {
953
- const ext = file.filename.includes(".") ? file.filename.slice(file.filename.lastIndexOf(".")) : "";
954
- const savedName = `${randomBytes(16).toString("hex")}${ext}`;
955
- const savePath = join(uploadDir, savedName);
956
- writeFileSync(savePath, file.buffer);
957
- file.path = savePath;
958
- }
959
- }
960
- req.files = files;
961
- req.body = { ...req.body ?? {}, ...fields };
962
- next();
963
- } catch (err) {
964
- const { res } = ctx;
965
- res.statusCode = 400;
966
- res.setHeader("Content-Type", "application/json");
967
- res.end(JSON.stringify({ error: { statusCode: 400, message: err.message } }));
968
- }
969
- };
970
- }
971
- async function parseMultipart(req, boundary, opts) {
972
- const raw = await collectBody(req);
973
- const delimiter = Buffer.from(`--${boundary}`);
974
- const endDelimiter = Buffer.from(`--${boundary}--`);
975
- const fields = {};
976
- const files = [];
977
- let fieldCount = 0;
978
- let start = bufferIndexOf(raw, delimiter, 0);
979
- if (start === -1) return { fields, files };
980
- start += delimiter.length + 2;
981
- while (start < raw.length) {
982
- const end = bufferIndexOf(raw, delimiter, start);
983
- if (end === -1) break;
984
- const part = raw.subarray(start, end - 2);
985
- const headerEnd = bufferIndexOf(part, Buffer.from("\r\n\r\n"), 0);
986
- if (headerEnd === -1) {
987
- start = end + delimiter.length + 2;
988
- continue;
989
- }
990
- const headerStr = part.subarray(0, headerEnd).toString("utf-8");
991
- const body = part.subarray(headerEnd + 4);
992
- const nameMatch = headerStr.match(/name="([^"]+)"/);
993
- const filenameMatch = headerStr.match(/filename="([^"]*)"/);
994
- const typeMatch = headerStr.match(/Content-Type:\s*(.+)/i);
995
- if (filenameMatch && nameMatch) {
996
- if (files.length >= opts.maxFiles) throw new Error("Too many files");
997
- if (body.length > opts.maxFileSize) throw new Error(`File ${filenameMatch[1]} exceeds max size`);
998
- const mimetype = typeMatch?.[1]?.trim() ?? "application/octet-stream";
999
- if (opts.allowedMimeTypes && !opts.allowedMimeTypes.includes(mimetype)) {
1000
- throw new Error(`File type ${mimetype} not allowed`);
1001
- }
1002
- files.push({
1003
- fieldname: nameMatch[1],
1004
- filename: filenameMatch[1],
1005
- mimetype,
1006
- size: body.length,
1007
- buffer: Buffer.from(body)
1008
- });
1009
- } else if (nameMatch) {
1010
- if (++fieldCount > opts.maxFields) throw new Error("Too many fields");
1011
- if (body.length > opts.maxFieldSize) throw new Error(`Field ${nameMatch[1]} exceeds max size`);
1012
- fields[nameMatch[1]] = body.toString("utf-8");
1013
- }
1014
- start = end + delimiter.length;
1015
- if (bufferIndexOf(raw, endDelimiter, end) === end) break;
1016
- start += 2;
1017
- }
1018
- return { fields, files };
1019
- }
1020
- function collectBody(stream) {
1021
- return new Promise((resolve2, reject) => {
1022
- const chunks = [];
1023
- stream.on("data", (chunk) => chunks.push(chunk));
1024
- stream.on("end", () => resolve2(Buffer.concat(chunks)));
1025
- stream.on("error", reject);
1026
- });
1027
- }
1028
- function bufferIndexOf(buf, search, offset) {
1029
- for (let i = offset; i <= buf.length - search.length; i++) {
1030
- let found = true;
1031
- for (let j = 0; j < search.length; j++) {
1032
- if (buf[i + j] !== search[j]) {
1033
- found = false;
1034
- break;
1035
- }
1036
- }
1037
- if (found) return i;
1038
- }
1039
- return -1;
1040
- }
1041
-
1042
- export { CORSPlugin, CircuitBreakerPlugin, CompressionPlugin, ETagPlugin, HealthCheckPlugin, HelmetPlugin, JWTPlugin, MultipartPlugin, ProxyPlugin, RateLimitPlugin, RequestIdPlugin, SSEManager, SSEPlugin, SessionPlugin, StaticPlugin, TimeoutPlugin, signJWT, verifyJWT };
1043
- //# sourceMappingURL=chunk-NLSZKAPA.mjs.map
1044
- //# sourceMappingURL=chunk-NLSZKAPA.mjs.map