azurajs 3.0.2 → 3.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -0
- package/dist/IpResolver-BVgnGnpf.d.mts +5 -0
- package/dist/IpResolver-BVgnGnpf.d.ts +5 -0
- package/dist/SwaggerPlugin-C0UZTjaZ.d.ts +6 -0
- package/dist/SwaggerPlugin-wr9S4SRG.d.mts +6 -0
- package/dist/cookies/index.d.mts +7 -0
- package/dist/cookies/index.d.ts +7 -0
- package/dist/cookies/index.js +38 -0
- package/dist/cookies/index.js.map +1 -0
- package/dist/cookies/index.mjs +35 -0
- package/dist/cookies/index.mjs.map +1 -0
- package/dist/core/index.d.mts +18 -27
- package/dist/core/index.d.ts +18 -27
- package/dist/core/index.js +214 -14
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +214 -14
- package/dist/core/index.mjs.map +1 -1
- package/dist/cors/index.d.mts +7 -0
- package/dist/cors/index.d.ts +7 -0
- package/dist/cors/index.js +52 -0
- package/dist/cors/index.js.map +1 -0
- package/dist/cors/index.mjs +50 -0
- package/dist/cors/index.mjs.map +1 -0
- package/dist/decorators/index.d.mts +2 -0
- package/dist/decorators/index.d.ts +2 -0
- package/dist/decorators/index.js +25 -0
- package/dist/decorators/index.js.map +1 -1
- package/dist/decorators/index.mjs +24 -1
- package/dist/decorators/index.mjs.map +1 -1
- package/dist/decorators-B6l3CbxC.d.ts +13 -0
- package/dist/decorators-D5nY109r.d.mts +13 -0
- package/dist/http-error/index.d.mts +18 -0
- package/dist/http-error/index.d.ts +18 -0
- package/dist/http-error/index.js +81 -0
- package/dist/http-error/index.js.map +1 -0
- package/dist/http-error/index.mjs +79 -0
- package/dist/http-error/index.mjs.map +1 -0
- package/dist/index-j6QGMhZU.d.mts +30 -0
- package/dist/index-tpPZS_UK.d.ts +30 -0
- package/dist/index.d.mts +16 -5
- package/dist/index.d.ts +16 -5
- package/dist/index.js +1178 -14
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1176 -15
- package/dist/index.mjs.map +1 -1
- package/dist/infra/index.d.mts +6 -0
- package/dist/infra/index.d.ts +6 -0
- package/dist/infra/index.js +162 -0
- package/dist/infra/index.js.map +1 -0
- package/dist/infra/index.mjs +159 -0
- package/dist/infra/index.mjs.map +1 -0
- package/dist/{Logger-iEQNVVSc.d.mts → logger/index.d.mts} +2 -1
- package/dist/{Logger-iEQNVVSc.d.ts → logger/index.d.ts} +2 -1
- package/dist/logger/index.js +123 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/logger/index.mjs +120 -0
- package/dist/logger/index.mjs.map +1 -0
- package/dist/plugins/index.d.mts +8 -6
- package/dist/plugins/index.d.ts +8 -6
- package/dist/plugins/index.js +1101 -0
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/index.mjs +1101 -1
- package/dist/plugins/index.mjs.map +1 -1
- package/dist/rate-limit/index.d.mts +7 -0
- package/dist/rate-limit/index.d.ts +7 -0
- package/dist/rate-limit/index.js +81 -0
- package/dist/rate-limit/index.js.map +1 -0
- package/dist/rate-limit/index.mjs +79 -0
- package/dist/rate-limit/index.mjs.map +1 -0
- package/dist/router/index.d.mts +4 -0
- package/dist/router/index.d.ts +4 -0
- package/dist/router/index.js +218 -0
- package/dist/router/index.js.map +1 -0
- package/dist/router/index.mjs +216 -0
- package/dist/router/index.mjs.map +1 -0
- package/dist/routes.type-DZO5VBW2.d.mts +58 -0
- package/dist/routes.type-DzHNkCag.d.ts +58 -0
- package/dist/swagger/index.d.mts +30 -0
- package/dist/swagger/index.d.ts +30 -0
- package/dist/swagger/index.js +1136 -0
- package/dist/swagger/index.js.map +1 -0
- package/dist/swagger/index.mjs +1126 -0
- package/dist/swagger/index.mjs.map +1 -0
- package/dist/swagger/swagger-ui-modern.html +894 -0
- package/dist/swagger.type-Bfn5nGR8.d.mts +42 -0
- package/dist/swagger.type-CfDbFCZC.d.ts +42 -0
- package/dist/types/index.d.mts +5 -58
- package/dist/types/index.d.ts +5 -58
- package/dist/utils/index.d.mts +7 -72
- package/dist/utils/index.d.ts +7 -72
- package/dist/validators/index.d.mts +48 -0
- package/dist/validators/index.d.ts +48 -0
- package/dist/validators/index.js +144 -0
- package/dist/validators/index.js.map +1 -0
- package/dist/validators/index.mjs +141 -0
- package/dist/validators/index.mjs.map +1 -0
- package/package.json +86 -2
- package/src/cookies/index.ts +1 -0
- package/src/core/index.ts +1 -0
- package/src/core/router.ts +26 -15
- package/src/core/server.ts +64 -14
- package/src/cors/index.ts +2 -0
- package/src/decorators/index.ts +2 -0
- package/src/http-error/index.ts +1 -0
- package/src/infra/index.ts +3 -0
- package/src/logger/index.ts +1 -0
- package/src/plugins/SwaggerPlugin.ts +45 -0
- package/src/plugins/index.ts +1 -0
- package/src/rate-limit/index.ts +2 -0
- package/src/router/index.ts +1 -0
- package/src/swagger/constants.ts +8 -0
- package/src/swagger/decorators.ts +35 -0
- package/src/swagger/index.ts +10 -0
- package/src/swagger/openapi-builder.ts +199 -0
- package/src/swagger/swagger-ui-html.ts +24 -0
- package/src/swagger/swagger-ui-modern.html +894 -0
- package/src/swagger/swagger-ui-template.ts +5 -0
- package/src/types/index.ts +7 -0
- package/src/types/swagger.type.ts +36 -0
- package/src/validators/index.ts +4 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/plugins/RateLimitPlugin.ts
|
|
4
|
+
var MemoryStore = class {
|
|
5
|
+
store = /* @__PURE__ */ new Map();
|
|
6
|
+
cleanupInterval;
|
|
7
|
+
constructor(windowMs) {
|
|
8
|
+
this.cleanupInterval = setInterval(() => {
|
|
9
|
+
const now = Date.now();
|
|
10
|
+
for (const [key, entry] of this.store) {
|
|
11
|
+
if (now >= entry.resetTime) this.store.delete(key);
|
|
12
|
+
}
|
|
13
|
+
}, windowMs);
|
|
14
|
+
if (this.cleanupInterval.unref) this.cleanupInterval.unref();
|
|
15
|
+
}
|
|
16
|
+
increment(key, windowMs) {
|
|
17
|
+
const now = Date.now();
|
|
18
|
+
const entry = this.store.get(key);
|
|
19
|
+
if (!entry || now >= entry.resetTime) {
|
|
20
|
+
const resetTime = now + windowMs;
|
|
21
|
+
this.store.set(key, { count: 1, resetTime });
|
|
22
|
+
return { totalHits: 1, resetTime: new Date(resetTime) };
|
|
23
|
+
}
|
|
24
|
+
entry.count++;
|
|
25
|
+
return { totalHits: entry.count, resetTime: new Date(entry.resetTime) };
|
|
26
|
+
}
|
|
27
|
+
decrement(key) {
|
|
28
|
+
const entry = this.store.get(key);
|
|
29
|
+
if (entry && entry.count > 0) entry.count--;
|
|
30
|
+
}
|
|
31
|
+
resetKey(key) {
|
|
32
|
+
this.store.delete(key);
|
|
33
|
+
}
|
|
34
|
+
destroy() {
|
|
35
|
+
clearInterval(this.cleanupInterval);
|
|
36
|
+
this.store.clear();
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
function RateLimitPlugin(options = {}) {
|
|
40
|
+
const {
|
|
41
|
+
windowMs = 6e4,
|
|
42
|
+
max = 100,
|
|
43
|
+
message = { error: { statusCode: 429, message: "Too many requests, please try again later" } },
|
|
44
|
+
statusCode = 429,
|
|
45
|
+
keyGenerator = (req) => req.ip,
|
|
46
|
+
skipSuccessfulRequests = false,
|
|
47
|
+
skipFailedRequests = false
|
|
48
|
+
} = options;
|
|
49
|
+
const memStore = new MemoryStore(windowMs);
|
|
50
|
+
return (ctx, next) => {
|
|
51
|
+
const { req, res } = ctx;
|
|
52
|
+
const key = keyGenerator(req);
|
|
53
|
+
const { totalHits, resetTime } = memStore.increment(key, windowMs);
|
|
54
|
+
res.setHeader("X-RateLimit-Limit", String(max));
|
|
55
|
+
res.setHeader("X-RateLimit-Remaining", String(Math.max(0, max - totalHits)));
|
|
56
|
+
res.setHeader("X-RateLimit-Reset", String(Math.ceil(resetTime.getTime() / 1e3)));
|
|
57
|
+
if (totalHits > max) {
|
|
58
|
+
res.setHeader("Retry-After", String(Math.ceil(windowMs / 1e3)));
|
|
59
|
+
res.statusCode = statusCode;
|
|
60
|
+
const body = typeof message === "string" ? message : JSON.stringify(message);
|
|
61
|
+
res.setHeader("Content-Type", typeof message === "string" ? "text/plain" : "application/json");
|
|
62
|
+
res.end(body);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const origEnd = res.end;
|
|
66
|
+
res.end = function(...args) {
|
|
67
|
+
if (skipSuccessfulRequests && res.statusCode < 400) {
|
|
68
|
+
memStore.decrement(key);
|
|
69
|
+
}
|
|
70
|
+
if (skipFailedRequests && res.statusCode >= 400) {
|
|
71
|
+
memStore.decrement(key);
|
|
72
|
+
}
|
|
73
|
+
return origEnd.apply(this, args);
|
|
74
|
+
};
|
|
75
|
+
next();
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
exports.RateLimitPlugin = RateLimitPlugin;
|
|
80
|
+
//# sourceMappingURL=index.js.map
|
|
81
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/plugins/RateLimitPlugin.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { RateLimitOptions, PluginHandler } from \"../types/plugins/plugin.type.js\";\r\nimport type { AzuraRequest } from \"../types/common.type.js\";\r\n\r\ninterface SlidingWindowEntry {\r\n count: number;\r\n resetTime: number;\r\n}\r\n\r\nclass MemoryStore {\r\n private store = new Map<string, SlidingWindowEntry>();\r\n private cleanupInterval: ReturnType<typeof setInterval>;\r\n\r\n constructor(windowMs: number) {\r\n this.cleanupInterval = setInterval(() => {\r\n const now = Date.now();\r\n for (const [key, entry] of this.store) {\r\n if (now >= entry.resetTime) this.store.delete(key);\r\n }\r\n }, windowMs);\r\n if (this.cleanupInterval.unref) this.cleanupInterval.unref();\r\n }\r\n\r\n increment(key: string, windowMs: number): { totalHits: number; resetTime: Date } {\r\n const now = Date.now();\r\n const entry = this.store.get(key);\r\n\r\n if (!entry || now >= entry.resetTime) {\r\n const resetTime = now + windowMs;\r\n this.store.set(key, { count: 1, resetTime });\r\n return { totalHits: 1, resetTime: new Date(resetTime) };\r\n }\r\n\r\n entry.count++;\r\n return { totalHits: entry.count, resetTime: new Date(entry.resetTime) };\r\n }\r\n\r\n decrement(key: string): void {\r\n const entry = this.store.get(key);\r\n if (entry && entry.count > 0) entry.count--;\r\n }\r\n\r\n resetKey(key: string): void {\r\n this.store.delete(key);\r\n }\r\n\r\n destroy(): void {\r\n clearInterval(this.cleanupInterval);\r\n this.store.clear();\r\n }\r\n}\r\n\r\nexport function RateLimitPlugin(options: RateLimitOptions = {}): PluginHandler {\r\n const {\r\n windowMs = 60_000,\r\n max = 100,\r\n message = { error: { statusCode: 429, message: \"Too many requests, please try again later\" } },\r\n statusCode = 429,\r\n keyGenerator = (req: AzuraRequest) => req.ip,\r\n skipSuccessfulRequests = false,\r\n skipFailedRequests = false,\r\n } = options;\r\n\r\n const memStore = new MemoryStore(windowMs);\r\n\r\n return (ctx, next) => {\r\n const { req, res } = ctx;\r\n const key = keyGenerator(req);\r\n const { totalHits, resetTime } = memStore.increment(key, windowMs);\r\n\r\n res.setHeader(\"X-RateLimit-Limit\", String(max));\r\n res.setHeader(\"X-RateLimit-Remaining\", String(Math.max(0, max - totalHits)));\r\n res.setHeader(\"X-RateLimit-Reset\", String(Math.ceil(resetTime.getTime() / 1000)));\r\n\r\n if (totalHits > max) {\r\n res.setHeader(\"Retry-After\", String(Math.ceil(windowMs / 1000)));\r\n res.statusCode = statusCode;\r\n const body = typeof message === \"string\" ? message : JSON.stringify(message);\r\n res.setHeader(\"Content-Type\", typeof message === \"string\" ? \"text/plain\" : \"application/json\");\r\n res.end(body);\r\n return;\r\n }\r\n\r\n const origEnd = res.end;\r\n res.end = function (...args: any[]) {\r\n if (skipSuccessfulRequests && res.statusCode < 400) {\r\n memStore.decrement(key);\r\n }\r\n if (skipFailedRequests && res.statusCode >= 400) {\r\n memStore.decrement(key);\r\n }\r\n return (origEnd as any).apply(this, args);\r\n };\r\n\r\n next();\r\n };\r\n}\r\n"]}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// src/plugins/RateLimitPlugin.ts
|
|
2
|
+
var MemoryStore = class {
|
|
3
|
+
store = /* @__PURE__ */ new Map();
|
|
4
|
+
cleanupInterval;
|
|
5
|
+
constructor(windowMs) {
|
|
6
|
+
this.cleanupInterval = setInterval(() => {
|
|
7
|
+
const now = Date.now();
|
|
8
|
+
for (const [key, entry] of this.store) {
|
|
9
|
+
if (now >= entry.resetTime) this.store.delete(key);
|
|
10
|
+
}
|
|
11
|
+
}, windowMs);
|
|
12
|
+
if (this.cleanupInterval.unref) this.cleanupInterval.unref();
|
|
13
|
+
}
|
|
14
|
+
increment(key, windowMs) {
|
|
15
|
+
const now = Date.now();
|
|
16
|
+
const entry = this.store.get(key);
|
|
17
|
+
if (!entry || now >= entry.resetTime) {
|
|
18
|
+
const resetTime = now + windowMs;
|
|
19
|
+
this.store.set(key, { count: 1, resetTime });
|
|
20
|
+
return { totalHits: 1, resetTime: new Date(resetTime) };
|
|
21
|
+
}
|
|
22
|
+
entry.count++;
|
|
23
|
+
return { totalHits: entry.count, resetTime: new Date(entry.resetTime) };
|
|
24
|
+
}
|
|
25
|
+
decrement(key) {
|
|
26
|
+
const entry = this.store.get(key);
|
|
27
|
+
if (entry && entry.count > 0) entry.count--;
|
|
28
|
+
}
|
|
29
|
+
resetKey(key) {
|
|
30
|
+
this.store.delete(key);
|
|
31
|
+
}
|
|
32
|
+
destroy() {
|
|
33
|
+
clearInterval(this.cleanupInterval);
|
|
34
|
+
this.store.clear();
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
function RateLimitPlugin(options = {}) {
|
|
38
|
+
const {
|
|
39
|
+
windowMs = 6e4,
|
|
40
|
+
max = 100,
|
|
41
|
+
message = { error: { statusCode: 429, message: "Too many requests, please try again later" } },
|
|
42
|
+
statusCode = 429,
|
|
43
|
+
keyGenerator = (req) => req.ip,
|
|
44
|
+
skipSuccessfulRequests = false,
|
|
45
|
+
skipFailedRequests = false
|
|
46
|
+
} = options;
|
|
47
|
+
const memStore = new MemoryStore(windowMs);
|
|
48
|
+
return (ctx, next) => {
|
|
49
|
+
const { req, res } = ctx;
|
|
50
|
+
const key = keyGenerator(req);
|
|
51
|
+
const { totalHits, resetTime } = memStore.increment(key, windowMs);
|
|
52
|
+
res.setHeader("X-RateLimit-Limit", String(max));
|
|
53
|
+
res.setHeader("X-RateLimit-Remaining", String(Math.max(0, max - totalHits)));
|
|
54
|
+
res.setHeader("X-RateLimit-Reset", String(Math.ceil(resetTime.getTime() / 1e3)));
|
|
55
|
+
if (totalHits > max) {
|
|
56
|
+
res.setHeader("Retry-After", String(Math.ceil(windowMs / 1e3)));
|
|
57
|
+
res.statusCode = statusCode;
|
|
58
|
+
const body = typeof message === "string" ? message : JSON.stringify(message);
|
|
59
|
+
res.setHeader("Content-Type", typeof message === "string" ? "text/plain" : "application/json");
|
|
60
|
+
res.end(body);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const origEnd = res.end;
|
|
64
|
+
res.end = function(...args) {
|
|
65
|
+
if (skipSuccessfulRequests && res.statusCode < 400) {
|
|
66
|
+
memStore.decrement(key);
|
|
67
|
+
}
|
|
68
|
+
if (skipFailedRequests && res.statusCode >= 400) {
|
|
69
|
+
memStore.decrement(key);
|
|
70
|
+
}
|
|
71
|
+
return origEnd.apply(this, args);
|
|
72
|
+
};
|
|
73
|
+
next();
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export { RateLimitPlugin };
|
|
78
|
+
//# sourceMappingURL=index.mjs.map
|
|
79
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/plugins/RateLimitPlugin.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import type { RateLimitOptions, PluginHandler } from \"../types/plugins/plugin.type.js\";\r\nimport type { AzuraRequest } from \"../types/common.type.js\";\r\n\r\ninterface SlidingWindowEntry {\r\n count: number;\r\n resetTime: number;\r\n}\r\n\r\nclass MemoryStore {\r\n private store = new Map<string, SlidingWindowEntry>();\r\n private cleanupInterval: ReturnType<typeof setInterval>;\r\n\r\n constructor(windowMs: number) {\r\n this.cleanupInterval = setInterval(() => {\r\n const now = Date.now();\r\n for (const [key, entry] of this.store) {\r\n if (now >= entry.resetTime) this.store.delete(key);\r\n }\r\n }, windowMs);\r\n if (this.cleanupInterval.unref) this.cleanupInterval.unref();\r\n }\r\n\r\n increment(key: string, windowMs: number): { totalHits: number; resetTime: Date } {\r\n const now = Date.now();\r\n const entry = this.store.get(key);\r\n\r\n if (!entry || now >= entry.resetTime) {\r\n const resetTime = now + windowMs;\r\n this.store.set(key, { count: 1, resetTime });\r\n return { totalHits: 1, resetTime: new Date(resetTime) };\r\n }\r\n\r\n entry.count++;\r\n return { totalHits: entry.count, resetTime: new Date(entry.resetTime) };\r\n }\r\n\r\n decrement(key: string): void {\r\n const entry = this.store.get(key);\r\n if (entry && entry.count > 0) entry.count--;\r\n }\r\n\r\n resetKey(key: string): void {\r\n this.store.delete(key);\r\n }\r\n\r\n destroy(): void {\r\n clearInterval(this.cleanupInterval);\r\n this.store.clear();\r\n }\r\n}\r\n\r\nexport function RateLimitPlugin(options: RateLimitOptions = {}): PluginHandler {\r\n const {\r\n windowMs = 60_000,\r\n max = 100,\r\n message = { error: { statusCode: 429, message: \"Too many requests, please try again later\" } },\r\n statusCode = 429,\r\n keyGenerator = (req: AzuraRequest) => req.ip,\r\n skipSuccessfulRequests = false,\r\n skipFailedRequests = false,\r\n } = options;\r\n\r\n const memStore = new MemoryStore(windowMs);\r\n\r\n return (ctx, next) => {\r\n const { req, res } = ctx;\r\n const key = keyGenerator(req);\r\n const { totalHits, resetTime } = memStore.increment(key, windowMs);\r\n\r\n res.setHeader(\"X-RateLimit-Limit\", String(max));\r\n res.setHeader(\"X-RateLimit-Remaining\", String(Math.max(0, max - totalHits)));\r\n res.setHeader(\"X-RateLimit-Reset\", String(Math.ceil(resetTime.getTime() / 1000)));\r\n\r\n if (totalHits > max) {\r\n res.setHeader(\"Retry-After\", String(Math.ceil(windowMs / 1000)));\r\n res.statusCode = statusCode;\r\n const body = typeof message === \"string\" ? message : JSON.stringify(message);\r\n res.setHeader(\"Content-Type\", typeof message === \"string\" ? \"text/plain\" : \"application/json\");\r\n res.end(body);\r\n return;\r\n }\r\n\r\n const origEnd = res.end;\r\n res.end = function (...args: any[]) {\r\n if (skipSuccessfulRequests && res.statusCode < 400) {\r\n memStore.decrement(key);\r\n }\r\n if (skipFailedRequests && res.statusCode >= 400) {\r\n memStore.decrement(key);\r\n }\r\n return (origEnd as any).apply(this, args);\r\n };\r\n\r\n next();\r\n };\r\n}\r\n"]}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/core/router.ts
|
|
4
|
+
var PARAM_PREFIX = 58;
|
|
5
|
+
function createNode(segment = "") {
|
|
6
|
+
return {
|
|
7
|
+
segment,
|
|
8
|
+
children: /* @__PURE__ */ new Map(),
|
|
9
|
+
paramChild: null,
|
|
10
|
+
paramName: "",
|
|
11
|
+
handlers: /* @__PURE__ */ new Map(),
|
|
12
|
+
wildcardHandler: null
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
var LRUCache = class {
|
|
16
|
+
capacity;
|
|
17
|
+
map;
|
|
18
|
+
head;
|
|
19
|
+
tail;
|
|
20
|
+
constructor(capacity) {
|
|
21
|
+
this.capacity = capacity;
|
|
22
|
+
this.map = /* @__PURE__ */ new Map();
|
|
23
|
+
this.head = { prev: null, next: null };
|
|
24
|
+
this.tail = { prev: null, next: null };
|
|
25
|
+
this.head.next = this.tail;
|
|
26
|
+
this.tail.prev = this.head;
|
|
27
|
+
}
|
|
28
|
+
get(key) {
|
|
29
|
+
const node = this.map.get(key);
|
|
30
|
+
if (!node) return void 0;
|
|
31
|
+
this.moveToHead(node);
|
|
32
|
+
return node.value;
|
|
33
|
+
}
|
|
34
|
+
set(key, value) {
|
|
35
|
+
const existing = this.map.get(key);
|
|
36
|
+
if (existing) {
|
|
37
|
+
existing.value = value;
|
|
38
|
+
this.moveToHead(existing);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const node = { value, key, prev: null, next: null };
|
|
42
|
+
this.map.set(key, node);
|
|
43
|
+
this.addToHead(node);
|
|
44
|
+
if (this.map.size > this.capacity) {
|
|
45
|
+
const removed = this.removeTail();
|
|
46
|
+
if (removed) this.map.delete(removed.key);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
addToHead(node) {
|
|
50
|
+
node.prev = this.head;
|
|
51
|
+
node.next = this.head.next;
|
|
52
|
+
this.head.next.prev = node;
|
|
53
|
+
this.head.next = node;
|
|
54
|
+
}
|
|
55
|
+
removeNode(node) {
|
|
56
|
+
node.prev.next = node.next;
|
|
57
|
+
node.next.prev = node.prev;
|
|
58
|
+
}
|
|
59
|
+
moveToHead(node) {
|
|
60
|
+
this.removeNode(node);
|
|
61
|
+
this.addToHead(node);
|
|
62
|
+
}
|
|
63
|
+
removeTail() {
|
|
64
|
+
const node = this.tail.prev;
|
|
65
|
+
if (node === this.head) return null;
|
|
66
|
+
this.removeNode(node);
|
|
67
|
+
return node;
|
|
68
|
+
}
|
|
69
|
+
clear() {
|
|
70
|
+
this.map.clear();
|
|
71
|
+
this.head.next = this.tail;
|
|
72
|
+
this.tail.prev = this.head;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
var Router = class {
|
|
76
|
+
root = createNode();
|
|
77
|
+
cache;
|
|
78
|
+
staticRoutes = /* @__PURE__ */ new Map();
|
|
79
|
+
constructor(cacheSize = 1024) {
|
|
80
|
+
this.cache = new LRUCache(cacheSize);
|
|
81
|
+
}
|
|
82
|
+
add(method, path, handler, middlewares = [], meta) {
|
|
83
|
+
this.cache.clear();
|
|
84
|
+
const normalizedPath = this.normalizePath(path);
|
|
85
|
+
const stored = { handler, middlewares, meta };
|
|
86
|
+
if (!normalizedPath.includes(":") && !normalizedPath.includes("*")) {
|
|
87
|
+
const key = `${method}:${normalizedPath}`;
|
|
88
|
+
this.staticRoutes.set(key, stored);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const segments = normalizedPath.split("/").filter(Boolean);
|
|
92
|
+
let node = this.root;
|
|
93
|
+
for (let i = 0; i < segments.length; i++) {
|
|
94
|
+
const seg = segments[i];
|
|
95
|
+
if (seg === "*") {
|
|
96
|
+
if (!node.wildcardHandler) {
|
|
97
|
+
node.wildcardHandler = /* @__PURE__ */ new Map();
|
|
98
|
+
}
|
|
99
|
+
node.wildcardHandler.set(method, stored);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (seg.charCodeAt(0) === PARAM_PREFIX) {
|
|
103
|
+
if (!node.paramChild) {
|
|
104
|
+
node.paramChild = createNode(seg);
|
|
105
|
+
node.paramChild.paramName = seg.slice(1);
|
|
106
|
+
}
|
|
107
|
+
node = node.paramChild;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
let child = node.children.get(seg);
|
|
111
|
+
if (!child) {
|
|
112
|
+
child = createNode(seg);
|
|
113
|
+
node.children.set(seg, child);
|
|
114
|
+
}
|
|
115
|
+
node = child;
|
|
116
|
+
}
|
|
117
|
+
node.handlers.set(method, stored);
|
|
118
|
+
}
|
|
119
|
+
find(method, path) {
|
|
120
|
+
const normalizedPath = this.normalizePath(path);
|
|
121
|
+
const staticKey = `${method}:${normalizedPath}`;
|
|
122
|
+
const staticRoute = this.staticRoutes.get(staticKey);
|
|
123
|
+
if (staticRoute) {
|
|
124
|
+
return {
|
|
125
|
+
handler: staticRoute.handler,
|
|
126
|
+
params: {},
|
|
127
|
+
middlewares: staticRoute.middlewares,
|
|
128
|
+
meta: staticRoute.meta
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
const cacheKey = staticKey;
|
|
132
|
+
const cached = this.cache.get(cacheKey);
|
|
133
|
+
if (cached !== void 0) {
|
|
134
|
+
return cached ? { ...cached, params: { ...cached.params } } : null;
|
|
135
|
+
}
|
|
136
|
+
const segments = normalizedPath.split("/").filter(Boolean);
|
|
137
|
+
const params = {};
|
|
138
|
+
const result = this.matchNode(this.root, segments, 0, params, method);
|
|
139
|
+
if (result) {
|
|
140
|
+
const match = {
|
|
141
|
+
handler: result.handler,
|
|
142
|
+
params,
|
|
143
|
+
middlewares: result.middlewares,
|
|
144
|
+
meta: result.meta
|
|
145
|
+
};
|
|
146
|
+
this.cache.set(cacheKey, match);
|
|
147
|
+
return { ...match, params: { ...params } };
|
|
148
|
+
}
|
|
149
|
+
this.cache.set(cacheKey, null);
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
matchNode(node, segments, idx, params, method) {
|
|
153
|
+
if (idx === segments.length) {
|
|
154
|
+
return node.handlers.get(method) ?? null;
|
|
155
|
+
}
|
|
156
|
+
const seg = segments[idx];
|
|
157
|
+
const child = node.children.get(seg);
|
|
158
|
+
if (child) {
|
|
159
|
+
const result = this.matchNode(child, segments, idx + 1, params, method);
|
|
160
|
+
if (result) return result;
|
|
161
|
+
}
|
|
162
|
+
if (node.paramChild) {
|
|
163
|
+
params[node.paramChild.paramName] = seg;
|
|
164
|
+
const result = this.matchNode(node.paramChild, segments, idx + 1, params, method);
|
|
165
|
+
if (result) return result;
|
|
166
|
+
delete params[node.paramChild.paramName];
|
|
167
|
+
}
|
|
168
|
+
if (node.wildcardHandler) {
|
|
169
|
+
const route = node.wildcardHandler.get(method);
|
|
170
|
+
if (route) return route;
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
normalizePath(path) {
|
|
175
|
+
if (path === "/") return "/";
|
|
176
|
+
if (path.endsWith("/") && path.length > 1) return path.slice(0, -1);
|
|
177
|
+
return path;
|
|
178
|
+
}
|
|
179
|
+
getAllRoutes() {
|
|
180
|
+
return this.getRouteDocuments().map(({ method, path }) => ({ method, path }));
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Todas as rotas com metadados (ex.: OpenAPI) associados ao handler.
|
|
184
|
+
*/
|
|
185
|
+
getRouteDocuments() {
|
|
186
|
+
const routes = [];
|
|
187
|
+
for (const [key, stored] of this.staticRoutes) {
|
|
188
|
+
const [method, path] = key.split(":", 2);
|
|
189
|
+
routes.push({ method, path, meta: stored.meta });
|
|
190
|
+
}
|
|
191
|
+
this.collectRoutesWithMeta(this.root, "", routes);
|
|
192
|
+
return routes;
|
|
193
|
+
}
|
|
194
|
+
collectRoutesWithMeta(node, prefix, routes) {
|
|
195
|
+
for (const [method, stored] of node.handlers) {
|
|
196
|
+
routes.push({ method, path: prefix || "/", meta: stored.meta });
|
|
197
|
+
}
|
|
198
|
+
for (const [seg, child] of node.children) {
|
|
199
|
+
this.collectRoutesWithMeta(child, `${prefix}/${seg}`, routes);
|
|
200
|
+
}
|
|
201
|
+
if (node.paramChild) {
|
|
202
|
+
this.collectRoutesWithMeta(
|
|
203
|
+
node.paramChild,
|
|
204
|
+
`${prefix}/:${node.paramChild.paramName}`,
|
|
205
|
+
routes
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
if (node.wildcardHandler) {
|
|
209
|
+
for (const [method, stored] of node.wildcardHandler) {
|
|
210
|
+
routes.push({ method, path: `${prefix}/*`, meta: stored.meta });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
exports.Router = Router;
|
|
217
|
+
//# sourceMappingURL=index.js.map
|
|
218
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/router.ts"],"names":[],"mappings":";;;AAUA,IAAM,YAAA,GAAe,EAAA;AAiBrB,SAAS,UAAA,CAAW,UAAkB,EAAA,EAAe;AACnD,EAAA,OAAO;AAAA,IACL,OAAA;AAAA,IACA,QAAA,sBAAc,GAAA,EAAI;AAAA,IAClB,UAAA,EAAY,IAAA;AAAA,IACZ,SAAA,EAAW,EAAA;AAAA,IACX,QAAA,sBAAc,GAAA,EAAI;AAAA,IAClB,eAAA,EAAiB;AAAA,GACnB;AACF;AAMA,IAAM,WAAN,MAAqB;AAAA,EACX,QAAA;AAAA,EACA,GAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EAER,YAAY,QAAA,EAAkB;AAC5B,IAAA,IAAA,CAAK,QAAA,GAAW,QAAA;AAChB,IAAA,IAAA,CAAK,GAAA,uBAAU,GAAA,EAAI;AACnB,IAAA,IAAA,CAAK,IAAA,GAAO,EAAE,IAAA,EAAM,IAAA,EAAM,MAAM,IAAA,EAAK;AACrC,IAAA,IAAA,CAAK,IAAA,GAAO,EAAE,IAAA,EAAM,IAAA,EAAM,MAAM,IAAA,EAAK;AACrC,IAAA,IAAA,CAAK,IAAA,CAAK,OAAO,IAAA,CAAK,IAAA;AACtB,IAAA,IAAA,CAAK,IAAA,CAAK,OAAO,IAAA,CAAK,IAAA;AAAA,EACxB;AAAA,EAEA,IAAI,GAAA,EAAuB;AACzB,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,GAAG,CAAA;AAC7B,IAAA,IAAI,CAAC,MAAM,OAAO,MAAA;AAClB,IAAA,IAAA,CAAK,WAAW,IAAI,CAAA;AACpB,IAAA,OAAO,IAAA,CAAK,KAAA;AAAA,EACd;AAAA,EAEA,GAAA,CAAI,KAAQ,KAAA,EAAgB;AAC1B,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,GAAG,CAAA;AACjC,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,QAAA,CAAS,KAAA,GAAQ,KAAA;AACjB,MAAA,IAAA,CAAK,WAAW,QAAQ,CAAA;AACxB,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,OAAO,EAAE,KAAA,EAAO,KAAK,IAAA,EAAM,IAAA,EAAa,MAAM,IAAA,EAAY;AAChE,IAAA,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,GAAA,EAAK,IAAI,CAAA;AACtB,IAAA,IAAA,CAAK,UAAU,IAAI,CAAA;AAEnB,IAAA,IAAI,IAAA,CAAK,GAAA,CAAI,IAAA,GAAO,IAAA,CAAK,QAAA,EAAU;AACjC,MAAA,MAAM,OAAA,GAAU,KAAK,UAAA,EAAW;AAChC,MAAA,IAAI,OAAA,EAAS,IAAA,CAAK,GAAA,CAAI,MAAA,CAAO,QAAQ,GAAG,CAAA;AAAA,IAC1C;AAAA,EACF;AAAA,EAEQ,UAAU,IAAA,EAAiB;AACjC,IAAA,IAAA,CAAK,OAAO,IAAA,CAAK,IAAA;AACjB,IAAA,IAAA,CAAK,IAAA,GAAO,KAAK,IAAA,CAAK,IAAA;AACtB,IAAA,IAAA,CAAK,IAAA,CAAK,KAAK,IAAA,GAAO,IAAA;AACtB,IAAA,IAAA,CAAK,KAAK,IAAA,GAAO,IAAA;AAAA,EACnB;AAAA,EAEQ,WAAW,IAAA,EAAiB;AAClC,IAAA,IAAA,CAAK,IAAA,CAAK,OAAO,IAAA,CAAK,IAAA;AACtB,IAAA,IAAA,CAAK,IAAA,CAAK,OAAO,IAAA,CAAK,IAAA;AAAA,EACxB;AAAA,EAEQ,WAAW,IAAA,EAAiB;AAClC,IAAA,IAAA,CAAK,WAAW,IAAI,CAAA;AACpB,IAAA,IAAA,CAAK,UAAU,IAAI,CAAA;AAAA,EACrB;AAAA,EAEQ,UAAA,GAAkB;AACxB,IAAA,MAAM,IAAA,GAAO,KAAK,IAAA,CAAK,IAAA;AACvB,IAAA,IAAI,IAAA,KAAS,IAAA,CAAK,IAAA,EAAM,OAAO,IAAA;AAC/B,IAAA,IAAA,CAAK,WAAW,IAAI,CAAA;AACpB,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,KAAA,GAAc;AACZ,IAAA,IAAA,CAAK,IAAI,KAAA,EAAM;AACf,IAAA,IAAA,CAAK,IAAA,CAAK,OAAO,IAAA,CAAK,IAAA;AACtB,IAAA,IAAA,CAAK,IAAA,CAAK,OAAO,IAAA,CAAK,IAAA;AAAA,EACxB;AACF,CAAA;AAEO,IAAM,SAAN,MAAa;AAAA,EACV,OAAkB,UAAA,EAAW;AAAA,EAC7B,KAAA;AAAA,EACA,YAAA,uBAA6C,GAAA,EAAI;AAAA,EAEzD,WAAA,CAAY,YAAoB,IAAA,EAAM;AACpC,IAAA,IAAA,CAAK,KAAA,GAAQ,IAAI,QAAA,CAAS,SAAS,CAAA;AAAA,EACrC;AAAA,EAEA,IACE,MAAA,EACA,IAAA,EACA,SACA,WAAA,GAAmC,IACnC,IAAA,EACM;AACN,IAAA,IAAA,CAAK,MAAM,KAAA,EAAM;AACjB,IAAA,MAAM,cAAA,GAAiB,IAAA,CAAK,aAAA,CAAc,IAAI,CAAA;AAC9C,IAAA,MAAM,MAAA,GAAsB,EAAE,OAAA,EAAS,WAAA,EAAa,IAAA,EAAK;AAEzD,IAAA,IAAI,CAAC,eAAe,QAAA,CAAS,GAAG,KAAK,CAAC,cAAA,CAAe,QAAA,CAAS,GAAG,CAAA,EAAG;AAClE,MAAA,MAAM,GAAA,GAAM,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,cAAc,CAAA,CAAA;AACvC,MAAA,IAAA,CAAK,YAAA,CAAa,GAAA,CAAI,GAAA,EAAK,MAAM,CAAA;AACjC,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,WAAW,cAAA,CAAe,KAAA,CAAM,GAAG,CAAA,CAAE,OAAO,OAAO,CAAA;AACzD,IAAA,IAAI,OAAO,IAAA,CAAK,IAAA;AAEhB,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,QAAA,CAAS,QAAQ,CAAA,EAAA,EAAK;AACxC,MAAA,MAAM,GAAA,GAAM,SAAS,CAAC,CAAA;AAEtB,MAAA,IAAI,QAAQ,GAAA,EAAK;AACf,QAAA,IAAI,CAAC,KAAK,eAAA,EAAiB;AACzB,UAAA,IAAA,CAAK,eAAA,uBAAsB,GAAA,EAAI;AAAA,QACjC;AACA,QAAA,IAAA,CAAK,eAAA,CAAgB,GAAA,CAAI,MAAA,EAAQ,MAAM,CAAA;AACvC,QAAA;AAAA,MACF;AAEA,MAAA,IAAI,GAAA,CAAI,UAAA,CAAW,CAAC,CAAA,KAAM,YAAA,EAAc;AACtC,QAAA,IAAI,CAAC,KAAK,UAAA,EAAY;AACpB,UAAA,IAAA,CAAK,UAAA,GAAa,WAAW,GAAG,CAAA;AAChC,UAAA,IAAA,CAAK,UAAA,CAAW,SAAA,GAAY,GAAA,CAAI,KAAA,CAAM,CAAC,CAAA;AAAA,QACzC;AACA,QAAA,IAAA,GAAO,IAAA,CAAK,UAAA;AACZ,QAAA;AAAA,MACF;AAEA,MAAA,IAAI,KAAA,GAAQ,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,GAAG,CAAA;AACjC,MAAA,IAAI,CAAC,KAAA,EAAO;AACV,QAAA,KAAA,GAAQ,WAAW,GAAG,CAAA;AACtB,QAAA,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,GAAA,EAAK,KAAK,CAAA;AAAA,MAC9B;AACA,MAAA,IAAA,GAAO,KAAA;AAAA,IACT;AAEA,IAAA,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,MAAA,EAAQ,MAAM,CAAA;AAAA,EAClC;AAAA,EAEA,IAAA,CAAK,QAAoB,IAAA,EAAiC;AACxD,IAAA,MAAM,cAAA,GAAiB,IAAA,CAAK,aAAA,CAAc,IAAI,CAAA;AAE9C,IAAA,MAAM,SAAA,GAAY,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,cAAc,CAAA,CAAA;AAC7C,IAAA,MAAM,WAAA,GAAc,IAAA,CAAK,YAAA,CAAa,GAAA,CAAI,SAAS,CAAA;AACnD,IAAA,IAAI,WAAA,EAAa;AACf,MAAA,OAAO;AAAA,QACL,SAAS,WAAA,CAAY,OAAA;AAAA,QACrB,QAAQ,EAAC;AAAA,QACT,aAAa,WAAA,CAAY,WAAA;AAAA,QACzB,MAAM,WAAA,CAAY;AAAA,OACpB;AAAA,IACF;AAEA,IAAA,MAAM,QAAA,GAAW,SAAA;AACjB,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,QAAQ,CAAA;AACtC,IAAA,IAAI,WAAW,MAAA,EAAW;AACxB,MAAA,OAAO,MAAA,GAAS,EAAE,GAAG,MAAA,EAAQ,MAAA,EAAQ,EAAE,GAAG,MAAA,CAAO,MAAA,EAAO,EAAE,GAAI,IAAA;AAAA,IAChE;AAEA,IAAA,MAAM,WAAW,cAAA,CAAe,KAAA,CAAM,GAAG,CAAA,CAAE,OAAO,OAAO,CAAA;AACzD,IAAA,MAAM,SAAiC,EAAC;AACxC,IAAA,MAAM,MAAA,GAAS,KAAK,SAAA,CAAU,IAAA,CAAK,MAAM,QAAA,EAAU,CAAA,EAAG,QAAQ,MAAM,CAAA;AAEpE,IAAA,IAAI,MAAA,EAAQ;AACV,MAAA,MAAM,KAAA,GAAoB;AAAA,QACxB,SAAS,MAAA,CAAO,OAAA;AAAA,QAChB,MAAA;AAAA,QACA,aAAa,MAAA,CAAO,WAAA;AAAA,QACpB,MAAM,MAAA,CAAO;AAAA,OACf;AACA,MAAA,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,QAAA,EAAU,KAAK,CAAA;AAC9B,MAAA,OAAO,EAAE,GAAG,KAAA,EAAO,QAAQ,EAAE,GAAG,QAAO,EAAE;AAAA,IAC3C;AAEA,IAAA,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,QAAA,EAAU,IAAI,CAAA;AAC7B,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEQ,SAAA,CACN,IAAA,EACA,QAAA,EACA,GAAA,EACA,QACA,MAAA,EACoB;AACpB,IAAA,IAAI,GAAA,KAAQ,SAAS,MAAA,EAAQ;AAC3B,MAAA,OAAO,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,MAAM,CAAA,IAAK,IAAA;AAAA,IACtC;AAEA,IAAA,MAAM,GAAA,GAAM,SAAS,GAAG,CAAA;AAExB,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,GAAG,CAAA;AACnC,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,MAAM,MAAA,GAAS,KAAK,SAAA,CAAU,KAAA,EAAO,UAAU,GAAA,GAAM,CAAA,EAAG,QAAQ,MAAM,CAAA;AACtE,MAAA,IAAI,QAAQ,OAAO,MAAA;AAAA,IACrB;AAEA,IAAA,IAAI,KAAK,UAAA,EAAY;AACnB,MAAA,MAAA,CAAO,IAAA,CAAK,UAAA,CAAW,SAAS,CAAA,GAAI,GAAA;AACpC,MAAA,MAAM,MAAA,GAAS,KAAK,SAAA,CAAU,IAAA,CAAK,YAAY,QAAA,EAAU,GAAA,GAAM,CAAA,EAAG,MAAA,EAAQ,MAAM,CAAA;AAChF,MAAA,IAAI,QAAQ,OAAO,MAAA;AACnB,MAAA,OAAO,MAAA,CAAO,IAAA,CAAK,UAAA,CAAW,SAAS,CAAA;AAAA,IACzC;AAEA,IAAA,IAAI,KAAK,eAAA,EAAiB;AACxB,MAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,eAAA,CAAgB,GAAA,CAAI,MAAM,CAAA;AAC7C,MAAA,IAAI,OAAO,OAAO,KAAA;AAAA,IACpB;AAEA,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEQ,cAAc,IAAA,EAAsB;AAC1C,IAAA,IAAI,IAAA,KAAS,KAAK,OAAO,GAAA;AACzB,IAAA,IAAI,IAAA,CAAK,QAAA,CAAS,GAAG,CAAA,IAAK,IAAA,CAAK,MAAA,GAAS,CAAA,EAAG,OAAO,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA;AAClE,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,YAAA,GAA4D;AAC1D,IAAA,OAAO,IAAA,CAAK,iBAAA,EAAkB,CAAE,GAAA,CAAI,CAAC,EAAE,MAAA,EAAQ,IAAA,EAAK,MAAO,EAAE,MAAA,EAAQ,IAAA,EAAK,CAAE,CAAA;AAAA,EAC9E;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAA,GAAqC;AACnC,IAAA,MAAM,SAA0B,EAAC;AAEjC,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,MAAM,CAAA,IAAK,KAAK,YAAA,EAAc;AAC7C,MAAA,MAAM,CAAC,MAAA,EAAQ,IAAI,IAAI,GAAA,CAAI,KAAA,CAAM,KAAK,CAAC,CAAA;AACvC,MAAA,MAAA,CAAO,KAAK,EAAE,MAAA,EAAQ,MAAM,IAAA,EAAM,MAAA,CAAO,MAAM,CAAA;AAAA,IACjD;AAEA,IAAA,IAAA,CAAK,qBAAA,CAAsB,IAAA,CAAK,IAAA,EAAM,EAAA,EAAI,MAAM,CAAA;AAChD,IAAA,OAAO,MAAA;AAAA,EACT;AAAA,EAEQ,qBAAA,CAAsB,IAAA,EAAiB,MAAA,EAAgB,MAAA,EAA+B;AAC5F,IAAA,KAAA,MAAW,CAAC,MAAA,EAAQ,MAAM,CAAA,IAAK,KAAK,QAAA,EAAU;AAC5C,MAAA,MAAA,CAAO,IAAA,CAAK,EAAE,MAAA,EAAQ,IAAA,EAAM,UAAU,GAAA,EAAK,IAAA,EAAM,MAAA,CAAO,IAAA,EAAM,CAAA;AAAA,IAChE;AAEA,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,CAAA,IAAK,KAAK,QAAA,EAAU;AACxC,MAAA,IAAA,CAAK,sBAAsB,KAAA,EAAO,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,GAAG,IAAI,MAAM,CAAA;AAAA,IAC9D;AAEA,IAAA,IAAI,KAAK,UAAA,EAAY;AACnB,MAAA,IAAA,CAAK,qBAAA;AAAA,QACH,IAAA,CAAK,UAAA;AAAA,QACL,CAAA,EAAG,MAAM,CAAA,EAAA,EAAK,IAAA,CAAK,WAAW,SAAS,CAAA,CAAA;AAAA,QACvC;AAAA,OACF;AAAA,IACF;AAEA,IAAA,IAAI,KAAK,eAAA,EAAiB;AACxB,MAAA,KAAA,MAAW,CAAC,MAAA,EAAQ,MAAM,CAAA,IAAK,KAAK,eAAA,EAAiB;AACnD,QAAA,MAAA,CAAO,IAAA,CAAK,EAAE,MAAA,EAAQ,IAAA,EAAM,CAAA,EAAG,MAAM,CAAA,EAAA,CAAA,EAAM,IAAA,EAAM,MAAA,CAAO,IAAA,EAAM,CAAA;AAAA,MAChE;AAAA,IACF;AAAA,EACF;AACF","file":"index.js","sourcesContent":["import type { HttpMethod, RouteHandler, MiddlewareHandler, RouteMatch } from \"../types/common.type.js\";\r\nimport type { RouteMeta } from \"../types/routes.type.js\";\r\n\r\n/** Rota registada com metadados opcionais (OpenAPI / documentação). */\r\nexport interface RouteDocument {\r\n method: HttpMethod;\r\n path: string;\r\n meta?: RouteMeta;\r\n}\r\n\r\nconst PARAM_PREFIX = 58; // ':'\r\n\r\ninterface RadixNode {\r\n segment: string;\r\n children: Map<string, RadixNode>;\r\n paramChild: RadixNode | null;\r\n paramName: string;\r\n handlers: Map<HttpMethod, StoredRoute>;\r\n wildcardHandler: Map<HttpMethod, StoredRoute> | null;\r\n}\r\n\r\ninterface StoredRoute {\r\n handler: RouteHandler;\r\n middlewares: MiddlewareHandler[];\r\n meta?: Record<string, any>;\r\n}\r\n\r\nfunction createNode(segment: string = \"\"): RadixNode {\r\n return {\r\n segment,\r\n children: new Map(),\r\n paramChild: null,\r\n paramName: \"\",\r\n handlers: new Map(),\r\n wildcardHandler: null,\r\n };\r\n}\r\n\r\n/**\r\n * LRU Cache with O(1) get/set via doubly linked list + hash map.\r\n * Used for route lookup caching in hot paths.\r\n */\r\nclass LRUCache<K, V> {\r\n private capacity: number;\r\n private map: Map<K, { value: V; key: K; prev: any; next: any }>;\r\n private head: any;\r\n private tail: any;\r\n\r\n constructor(capacity: number) {\r\n this.capacity = capacity;\r\n this.map = new Map();\r\n this.head = { prev: null, next: null };\r\n this.tail = { prev: null, next: null };\r\n this.head.next = this.tail;\r\n this.tail.prev = this.head;\r\n }\r\n\r\n get(key: K): V | undefined {\r\n const node = this.map.get(key);\r\n if (!node) return undefined;\r\n this.moveToHead(node);\r\n return node.value;\r\n }\r\n\r\n set(key: K, value: V): void {\r\n const existing = this.map.get(key);\r\n if (existing) {\r\n existing.value = value;\r\n this.moveToHead(existing);\r\n return;\r\n }\r\n\r\n const node = { value, key, prev: null as any, next: null as any };\r\n this.map.set(key, node);\r\n this.addToHead(node);\r\n\r\n if (this.map.size > this.capacity) {\r\n const removed = this.removeTail();\r\n if (removed) this.map.delete(removed.key);\r\n }\r\n }\r\n\r\n private addToHead(node: any): void {\r\n node.prev = this.head;\r\n node.next = this.head.next;\r\n this.head.next.prev = node;\r\n this.head.next = node;\r\n }\r\n\r\n private removeNode(node: any): void {\r\n node.prev.next = node.next;\r\n node.next.prev = node.prev;\r\n }\r\n\r\n private moveToHead(node: any): void {\r\n this.removeNode(node);\r\n this.addToHead(node);\r\n }\r\n\r\n private removeTail(): any {\r\n const node = this.tail.prev;\r\n if (node === this.head) return null;\r\n this.removeNode(node);\r\n return node;\r\n }\r\n\r\n clear(): void {\r\n this.map.clear();\r\n this.head.next = this.tail;\r\n this.tail.prev = this.head;\r\n }\r\n}\r\n\r\nexport class Router {\r\n private root: RadixNode = createNode();\r\n private cache: LRUCache<string, RouteMatch | null>;\r\n private staticRoutes: Map<string, StoredRoute> = new Map();\r\n\r\n constructor(cacheSize: number = 1024) {\r\n this.cache = new LRUCache(cacheSize);\r\n }\r\n\r\n add(\r\n method: HttpMethod,\r\n path: string,\r\n handler: RouteHandler,\r\n middlewares: MiddlewareHandler[] = [],\r\n meta?: Record<string, any>,\r\n ): void {\r\n this.cache.clear();\r\n const normalizedPath = this.normalizePath(path);\r\n const stored: StoredRoute = { handler, middlewares, meta };\r\n\r\n if (!normalizedPath.includes(\":\") && !normalizedPath.includes(\"*\")) {\r\n const key = `${method}:${normalizedPath}`;\r\n this.staticRoutes.set(key, stored);\r\n return;\r\n }\r\n\r\n const segments = normalizedPath.split(\"/\").filter(Boolean);\r\n let node = this.root;\r\n\r\n for (let i = 0; i < segments.length; i++) {\r\n const seg = segments[i];\r\n\r\n if (seg === \"*\") {\r\n if (!node.wildcardHandler) {\r\n node.wildcardHandler = new Map();\r\n }\r\n node.wildcardHandler.set(method, stored);\r\n return;\r\n }\r\n\r\n if (seg.charCodeAt(0) === PARAM_PREFIX) {\r\n if (!node.paramChild) {\r\n node.paramChild = createNode(seg);\r\n node.paramChild.paramName = seg.slice(1);\r\n }\r\n node = node.paramChild;\r\n continue;\r\n }\r\n\r\n let child = node.children.get(seg);\r\n if (!child) {\r\n child = createNode(seg);\r\n node.children.set(seg, child);\r\n }\r\n node = child;\r\n }\r\n\r\n node.handlers.set(method, stored);\r\n }\r\n\r\n find(method: HttpMethod, path: string): RouteMatch | null {\r\n const normalizedPath = this.normalizePath(path);\r\n\r\n const staticKey = `${method}:${normalizedPath}`;\r\n const staticRoute = this.staticRoutes.get(staticKey);\r\n if (staticRoute) {\r\n return {\r\n handler: staticRoute.handler,\r\n params: {},\r\n middlewares: staticRoute.middlewares,\r\n meta: staticRoute.meta,\r\n };\r\n }\r\n\r\n const cacheKey = staticKey;\r\n const cached = this.cache.get(cacheKey);\r\n if (cached !== undefined) {\r\n return cached ? { ...cached, params: { ...cached.params } } : null;\r\n }\r\n\r\n const segments = normalizedPath.split(\"/\").filter(Boolean);\r\n const params: Record<string, string> = {};\r\n const result = this.matchNode(this.root, segments, 0, params, method);\r\n\r\n if (result) {\r\n const match: RouteMatch = {\r\n handler: result.handler,\r\n params,\r\n middlewares: result.middlewares,\r\n meta: result.meta,\r\n };\r\n this.cache.set(cacheKey, match);\r\n return { ...match, params: { ...params } };\r\n }\r\n\r\n this.cache.set(cacheKey, null);\r\n return null;\r\n }\r\n\r\n private matchNode(\r\n node: RadixNode,\r\n segments: string[],\r\n idx: number,\r\n params: Record<string, string>,\r\n method: HttpMethod,\r\n ): StoredRoute | null {\r\n if (idx === segments.length) {\r\n return node.handlers.get(method) ?? null;\r\n }\r\n\r\n const seg = segments[idx];\r\n\r\n const child = node.children.get(seg);\r\n if (child) {\r\n const result = this.matchNode(child, segments, idx + 1, params, method);\r\n if (result) return result;\r\n }\r\n\r\n if (node.paramChild) {\r\n params[node.paramChild.paramName] = seg;\r\n const result = this.matchNode(node.paramChild, segments, idx + 1, params, method);\r\n if (result) return result;\r\n delete params[node.paramChild.paramName];\r\n }\r\n\r\n if (node.wildcardHandler) {\r\n const route = node.wildcardHandler.get(method);\r\n if (route) return route;\r\n }\r\n\r\n return null;\r\n }\r\n\r\n private normalizePath(path: string): string {\r\n if (path === \"/\") return \"/\";\r\n if (path.endsWith(\"/\") && path.length > 1) return path.slice(0, -1);\r\n return path;\r\n }\r\n\r\n getAllRoutes(): Array<{ method: HttpMethod; path: string }> {\r\n return this.getRouteDocuments().map(({ method, path }) => ({ method, path }));\r\n }\r\n\r\n /**\r\n * Todas as rotas com metadados (ex.: OpenAPI) associados ao handler.\r\n */\r\n getRouteDocuments(): RouteDocument[] {\r\n const routes: RouteDocument[] = [];\r\n\r\n for (const [key, stored] of this.staticRoutes) {\r\n const [method, path] = key.split(\":\", 2) as [HttpMethod, string];\r\n routes.push({ method, path, meta: stored.meta });\r\n }\r\n\r\n this.collectRoutesWithMeta(this.root, \"\", routes);\r\n return routes;\r\n }\r\n\r\n private collectRoutesWithMeta(node: RadixNode, prefix: string, routes: RouteDocument[]): void {\r\n for (const [method, stored] of node.handlers) {\r\n routes.push({ method, path: prefix || \"/\", meta: stored.meta });\r\n }\r\n\r\n for (const [seg, child] of node.children) {\r\n this.collectRoutesWithMeta(child, `${prefix}/${seg}`, routes);\r\n }\r\n\r\n if (node.paramChild) {\r\n this.collectRoutesWithMeta(\r\n node.paramChild,\r\n `${prefix}/:${node.paramChild.paramName}`,\r\n routes,\r\n );\r\n }\r\n\r\n if (node.wildcardHandler) {\r\n for (const [method, stored] of node.wildcardHandler) {\r\n routes.push({ method, path: `${prefix}/*`, meta: stored.meta });\r\n }\r\n }\r\n }\r\n}\r\n"]}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// src/core/router.ts
|
|
2
|
+
var PARAM_PREFIX = 58;
|
|
3
|
+
function createNode(segment = "") {
|
|
4
|
+
return {
|
|
5
|
+
segment,
|
|
6
|
+
children: /* @__PURE__ */ new Map(),
|
|
7
|
+
paramChild: null,
|
|
8
|
+
paramName: "",
|
|
9
|
+
handlers: /* @__PURE__ */ new Map(),
|
|
10
|
+
wildcardHandler: null
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
var LRUCache = class {
|
|
14
|
+
capacity;
|
|
15
|
+
map;
|
|
16
|
+
head;
|
|
17
|
+
tail;
|
|
18
|
+
constructor(capacity) {
|
|
19
|
+
this.capacity = capacity;
|
|
20
|
+
this.map = /* @__PURE__ */ new Map();
|
|
21
|
+
this.head = { prev: null, next: null };
|
|
22
|
+
this.tail = { prev: null, next: null };
|
|
23
|
+
this.head.next = this.tail;
|
|
24
|
+
this.tail.prev = this.head;
|
|
25
|
+
}
|
|
26
|
+
get(key) {
|
|
27
|
+
const node = this.map.get(key);
|
|
28
|
+
if (!node) return void 0;
|
|
29
|
+
this.moveToHead(node);
|
|
30
|
+
return node.value;
|
|
31
|
+
}
|
|
32
|
+
set(key, value) {
|
|
33
|
+
const existing = this.map.get(key);
|
|
34
|
+
if (existing) {
|
|
35
|
+
existing.value = value;
|
|
36
|
+
this.moveToHead(existing);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const node = { value, key, prev: null, next: null };
|
|
40
|
+
this.map.set(key, node);
|
|
41
|
+
this.addToHead(node);
|
|
42
|
+
if (this.map.size > this.capacity) {
|
|
43
|
+
const removed = this.removeTail();
|
|
44
|
+
if (removed) this.map.delete(removed.key);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
addToHead(node) {
|
|
48
|
+
node.prev = this.head;
|
|
49
|
+
node.next = this.head.next;
|
|
50
|
+
this.head.next.prev = node;
|
|
51
|
+
this.head.next = node;
|
|
52
|
+
}
|
|
53
|
+
removeNode(node) {
|
|
54
|
+
node.prev.next = node.next;
|
|
55
|
+
node.next.prev = node.prev;
|
|
56
|
+
}
|
|
57
|
+
moveToHead(node) {
|
|
58
|
+
this.removeNode(node);
|
|
59
|
+
this.addToHead(node);
|
|
60
|
+
}
|
|
61
|
+
removeTail() {
|
|
62
|
+
const node = this.tail.prev;
|
|
63
|
+
if (node === this.head) return null;
|
|
64
|
+
this.removeNode(node);
|
|
65
|
+
return node;
|
|
66
|
+
}
|
|
67
|
+
clear() {
|
|
68
|
+
this.map.clear();
|
|
69
|
+
this.head.next = this.tail;
|
|
70
|
+
this.tail.prev = this.head;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var Router = class {
|
|
74
|
+
root = createNode();
|
|
75
|
+
cache;
|
|
76
|
+
staticRoutes = /* @__PURE__ */ new Map();
|
|
77
|
+
constructor(cacheSize = 1024) {
|
|
78
|
+
this.cache = new LRUCache(cacheSize);
|
|
79
|
+
}
|
|
80
|
+
add(method, path, handler, middlewares = [], meta) {
|
|
81
|
+
this.cache.clear();
|
|
82
|
+
const normalizedPath = this.normalizePath(path);
|
|
83
|
+
const stored = { handler, middlewares, meta };
|
|
84
|
+
if (!normalizedPath.includes(":") && !normalizedPath.includes("*")) {
|
|
85
|
+
const key = `${method}:${normalizedPath}`;
|
|
86
|
+
this.staticRoutes.set(key, stored);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const segments = normalizedPath.split("/").filter(Boolean);
|
|
90
|
+
let node = this.root;
|
|
91
|
+
for (let i = 0; i < segments.length; i++) {
|
|
92
|
+
const seg = segments[i];
|
|
93
|
+
if (seg === "*") {
|
|
94
|
+
if (!node.wildcardHandler) {
|
|
95
|
+
node.wildcardHandler = /* @__PURE__ */ new Map();
|
|
96
|
+
}
|
|
97
|
+
node.wildcardHandler.set(method, stored);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (seg.charCodeAt(0) === PARAM_PREFIX) {
|
|
101
|
+
if (!node.paramChild) {
|
|
102
|
+
node.paramChild = createNode(seg);
|
|
103
|
+
node.paramChild.paramName = seg.slice(1);
|
|
104
|
+
}
|
|
105
|
+
node = node.paramChild;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
let child = node.children.get(seg);
|
|
109
|
+
if (!child) {
|
|
110
|
+
child = createNode(seg);
|
|
111
|
+
node.children.set(seg, child);
|
|
112
|
+
}
|
|
113
|
+
node = child;
|
|
114
|
+
}
|
|
115
|
+
node.handlers.set(method, stored);
|
|
116
|
+
}
|
|
117
|
+
find(method, path) {
|
|
118
|
+
const normalizedPath = this.normalizePath(path);
|
|
119
|
+
const staticKey = `${method}:${normalizedPath}`;
|
|
120
|
+
const staticRoute = this.staticRoutes.get(staticKey);
|
|
121
|
+
if (staticRoute) {
|
|
122
|
+
return {
|
|
123
|
+
handler: staticRoute.handler,
|
|
124
|
+
params: {},
|
|
125
|
+
middlewares: staticRoute.middlewares,
|
|
126
|
+
meta: staticRoute.meta
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
const cacheKey = staticKey;
|
|
130
|
+
const cached = this.cache.get(cacheKey);
|
|
131
|
+
if (cached !== void 0) {
|
|
132
|
+
return cached ? { ...cached, params: { ...cached.params } } : null;
|
|
133
|
+
}
|
|
134
|
+
const segments = normalizedPath.split("/").filter(Boolean);
|
|
135
|
+
const params = {};
|
|
136
|
+
const result = this.matchNode(this.root, segments, 0, params, method);
|
|
137
|
+
if (result) {
|
|
138
|
+
const match = {
|
|
139
|
+
handler: result.handler,
|
|
140
|
+
params,
|
|
141
|
+
middlewares: result.middlewares,
|
|
142
|
+
meta: result.meta
|
|
143
|
+
};
|
|
144
|
+
this.cache.set(cacheKey, match);
|
|
145
|
+
return { ...match, params: { ...params } };
|
|
146
|
+
}
|
|
147
|
+
this.cache.set(cacheKey, null);
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
matchNode(node, segments, idx, params, method) {
|
|
151
|
+
if (idx === segments.length) {
|
|
152
|
+
return node.handlers.get(method) ?? null;
|
|
153
|
+
}
|
|
154
|
+
const seg = segments[idx];
|
|
155
|
+
const child = node.children.get(seg);
|
|
156
|
+
if (child) {
|
|
157
|
+
const result = this.matchNode(child, segments, idx + 1, params, method);
|
|
158
|
+
if (result) return result;
|
|
159
|
+
}
|
|
160
|
+
if (node.paramChild) {
|
|
161
|
+
params[node.paramChild.paramName] = seg;
|
|
162
|
+
const result = this.matchNode(node.paramChild, segments, idx + 1, params, method);
|
|
163
|
+
if (result) return result;
|
|
164
|
+
delete params[node.paramChild.paramName];
|
|
165
|
+
}
|
|
166
|
+
if (node.wildcardHandler) {
|
|
167
|
+
const route = node.wildcardHandler.get(method);
|
|
168
|
+
if (route) return route;
|
|
169
|
+
}
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
normalizePath(path) {
|
|
173
|
+
if (path === "/") return "/";
|
|
174
|
+
if (path.endsWith("/") && path.length > 1) return path.slice(0, -1);
|
|
175
|
+
return path;
|
|
176
|
+
}
|
|
177
|
+
getAllRoutes() {
|
|
178
|
+
return this.getRouteDocuments().map(({ method, path }) => ({ method, path }));
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Todas as rotas com metadados (ex.: OpenAPI) associados ao handler.
|
|
182
|
+
*/
|
|
183
|
+
getRouteDocuments() {
|
|
184
|
+
const routes = [];
|
|
185
|
+
for (const [key, stored] of this.staticRoutes) {
|
|
186
|
+
const [method, path] = key.split(":", 2);
|
|
187
|
+
routes.push({ method, path, meta: stored.meta });
|
|
188
|
+
}
|
|
189
|
+
this.collectRoutesWithMeta(this.root, "", routes);
|
|
190
|
+
return routes;
|
|
191
|
+
}
|
|
192
|
+
collectRoutesWithMeta(node, prefix, routes) {
|
|
193
|
+
for (const [method, stored] of node.handlers) {
|
|
194
|
+
routes.push({ method, path: prefix || "/", meta: stored.meta });
|
|
195
|
+
}
|
|
196
|
+
for (const [seg, child] of node.children) {
|
|
197
|
+
this.collectRoutesWithMeta(child, `${prefix}/${seg}`, routes);
|
|
198
|
+
}
|
|
199
|
+
if (node.paramChild) {
|
|
200
|
+
this.collectRoutesWithMeta(
|
|
201
|
+
node.paramChild,
|
|
202
|
+
`${prefix}/:${node.paramChild.paramName}`,
|
|
203
|
+
routes
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
if (node.wildcardHandler) {
|
|
207
|
+
for (const [method, stored] of node.wildcardHandler) {
|
|
208
|
+
routes.push({ method, path: `${prefix}/*`, meta: stored.meta });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
export { Router };
|
|
215
|
+
//# sourceMappingURL=index.mjs.map
|
|
216
|
+
//# sourceMappingURL=index.mjs.map
|