tezx 1.0.35 → 1.0.37
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/cjs/core/context.js +11 -4
- package/cjs/core/request.js +36 -7
- package/cjs/core/router.js +2 -2
- package/cjs/core/server.js +8 -4
- package/cjs/index.js +1 -1
- package/cjs/middleware/i18nMiddleware.js +13 -13
- package/cjs/middleware/lazyLoadModules.js +5 -4
- package/cjs/middleware/pagination.js +29 -29
- package/cjs/middleware/rateLimiter.js +5 -6
- package/cjs/middleware/xssProtection.js +1 -1
- package/core/context.d.ts +12 -1
- package/core/context.js +11 -4
- package/core/request.d.ts +76 -2
- package/core/request.js +36 -7
- package/core/router.d.ts +21 -11
- package/core/router.js +2 -2
- package/core/server.js +8 -4
- package/index.js +1 -1
- package/middleware/i18nMiddleware.d.ts +4 -4
- package/middleware/i18nMiddleware.js +13 -13
- package/middleware/index.d.ts +1 -1
- package/middleware/index.js +1 -1
- package/middleware/lazyLoadModules.d.ts +0 -1
- package/middleware/lazyLoadModules.js +5 -4
- package/middleware/pagination.d.ts +19 -5
- package/middleware/pagination.js +29 -29
- package/middleware/rateLimiter.d.ts +23 -1
- package/middleware/rateLimiter.js +5 -6
- package/middleware/xssProtection.js +1 -1
- package/package.json +1 -1
package/cjs/core/context.js
CHANGED
|
@@ -82,7 +82,7 @@ class Context {
|
|
|
82
82
|
#status = 200;
|
|
83
83
|
state = new state_1.State();
|
|
84
84
|
#params = {};
|
|
85
|
-
|
|
85
|
+
#resBody;
|
|
86
86
|
#localAddress = {};
|
|
87
87
|
#remoteAddress = {};
|
|
88
88
|
constructor(req, connInfo) {
|
|
@@ -98,7 +98,7 @@ class Context {
|
|
|
98
98
|
return this;
|
|
99
99
|
}
|
|
100
100
|
get cookies() {
|
|
101
|
-
const c = this.headers.getAll("cookie");
|
|
101
|
+
const c = this.req.headers.getAll("cookie");
|
|
102
102
|
let cookies = {};
|
|
103
103
|
if (Array.isArray(c) && c.length != 0) {
|
|
104
104
|
const cookieHeader = c.join("; ").split(";");
|
|
@@ -167,8 +167,8 @@ class Context {
|
|
|
167
167
|
else if (typeof args[0] === "object") {
|
|
168
168
|
headers = args[0];
|
|
169
169
|
}
|
|
170
|
-
if (!headers["Content-Type"]) {
|
|
171
|
-
if (typeof body === "string") {
|
|
170
|
+
if (!headers["Content-Type"] && !headers["content-type"]) {
|
|
171
|
+
if (typeof body === "string" || typeof body == "number") {
|
|
172
172
|
headers["Content-Type"] = "text/plain;";
|
|
173
173
|
}
|
|
174
174
|
else if (typeof body === "object" && body !== null) {
|
|
@@ -388,6 +388,7 @@ class Context {
|
|
|
388
388
|
headers,
|
|
389
389
|
});
|
|
390
390
|
let clone = response.clone();
|
|
391
|
+
this.body = body;
|
|
391
392
|
this.res = response;
|
|
392
393
|
return clone;
|
|
393
394
|
}
|
|
@@ -397,6 +398,12 @@ class Context {
|
|
|
397
398
|
set params(params) {
|
|
398
399
|
this.#params = params;
|
|
399
400
|
}
|
|
401
|
+
set body(body) {
|
|
402
|
+
this.#resBody = body;
|
|
403
|
+
}
|
|
404
|
+
get body() {
|
|
405
|
+
return this.#resBody;
|
|
406
|
+
}
|
|
400
407
|
get params() {
|
|
401
408
|
return this.#params;
|
|
402
409
|
}
|
package/cjs/core/request.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Request = void 0;
|
|
4
|
-
const environment_1 = require("./environment");
|
|
5
|
-
const header_1 = require("./header");
|
|
6
4
|
const formData_1 = require("../utils/formData");
|
|
7
5
|
const url_1 = require("../utils/url");
|
|
6
|
+
const environment_1 = require("./environment");
|
|
7
|
+
const header_1 = require("./header");
|
|
8
8
|
class Request {
|
|
9
|
-
headers = new header_1.HeadersParser();
|
|
9
|
+
#headers = new header_1.HeadersParser();
|
|
10
10
|
url;
|
|
11
11
|
method;
|
|
12
12
|
urlRef = {
|
|
@@ -27,13 +27,13 @@ class Request {
|
|
|
27
27
|
remoteAddress = {};
|
|
28
28
|
constructor(req, params, remoteAddress) {
|
|
29
29
|
this.remoteAddress = remoteAddress;
|
|
30
|
-
this
|
|
30
|
+
this.#headers = new header_1.HeadersParser(req?.headers);
|
|
31
31
|
this.method = req?.method?.toUpperCase();
|
|
32
32
|
this.params = params;
|
|
33
33
|
this.rawRequest = req;
|
|
34
34
|
if (environment_1.EnvironmentDetector.getEnvironment == "node") {
|
|
35
35
|
const protocol = environment_1.EnvironmentDetector.detectProtocol(req);
|
|
36
|
-
const host = environment_1.EnvironmentDetector.getHost(this
|
|
36
|
+
const host = environment_1.EnvironmentDetector.getHost(this.#headers);
|
|
37
37
|
this.url = `${protocol}://${host}${req.url}`;
|
|
38
38
|
}
|
|
39
39
|
else {
|
|
@@ -42,11 +42,40 @@ class Request {
|
|
|
42
42
|
this.urlRef = (0, url_1.urlParse)(this.url);
|
|
43
43
|
this.query = this.urlRef.query;
|
|
44
44
|
}
|
|
45
|
+
get headers() {
|
|
46
|
+
let requestHeaders = this.#headers;
|
|
47
|
+
return {
|
|
48
|
+
get: function get(key) {
|
|
49
|
+
return requestHeaders.get(key.toLowerCase());
|
|
50
|
+
},
|
|
51
|
+
getAll: function getAll(key) {
|
|
52
|
+
return requestHeaders.get(key.toLowerCase()) || [];
|
|
53
|
+
},
|
|
54
|
+
has: function has(key) {
|
|
55
|
+
return requestHeaders.has(key.toLowerCase());
|
|
56
|
+
},
|
|
57
|
+
entries: function entries() {
|
|
58
|
+
return requestHeaders.entries();
|
|
59
|
+
},
|
|
60
|
+
keys: function keys() {
|
|
61
|
+
return requestHeaders.keys();
|
|
62
|
+
},
|
|
63
|
+
values: function values() {
|
|
64
|
+
return requestHeaders.values();
|
|
65
|
+
},
|
|
66
|
+
forEach: function forEach(callback) {
|
|
67
|
+
return requestHeaders.forEach(callback);
|
|
68
|
+
},
|
|
69
|
+
toObject: function toObject() {
|
|
70
|
+
return requestHeaders.toObject();
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
45
74
|
async text() {
|
|
46
75
|
return await (0, formData_1.parseTextBody)(this.rawRequest);
|
|
47
76
|
}
|
|
48
77
|
async json() {
|
|
49
|
-
const contentType = this
|
|
78
|
+
const contentType = this.#headers.get("content-type") || "";
|
|
50
79
|
if (contentType.includes("application/json")) {
|
|
51
80
|
return await (0, formData_1.parseJsonBody)(this.rawRequest);
|
|
52
81
|
}
|
|
@@ -55,7 +84,7 @@ class Request {
|
|
|
55
84
|
}
|
|
56
85
|
}
|
|
57
86
|
async formData(options) {
|
|
58
|
-
const contentType = this
|
|
87
|
+
const contentType = this.#headers.get("content-type") || "";
|
|
59
88
|
if (!contentType) {
|
|
60
89
|
throw Error("Invalid Content-Type");
|
|
61
90
|
}
|
package/cjs/core/router.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Router = void 0;
|
|
4
|
-
const config_1 = require("./config");
|
|
5
|
-
const MiddlewareConfigure_1 = require("./MiddlewareConfigure");
|
|
6
4
|
const staticFile_1 = require("../utils/staticFile");
|
|
7
5
|
const url_1 = require("../utils/url");
|
|
6
|
+
const config_1 = require("./config");
|
|
7
|
+
const MiddlewareConfigure_1 = require("./MiddlewareConfigure");
|
|
8
8
|
class TrieRouter {
|
|
9
9
|
children = new Map();
|
|
10
10
|
handlers = new Map();
|
package/cjs/core/server.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.TezX = void 0;
|
|
4
|
+
const colors_1 = require("../utils/colors");
|
|
4
5
|
const config_1 = require("./config");
|
|
5
6
|
const context_1 = require("./context");
|
|
6
7
|
const router_1 = require("./router");
|
|
7
|
-
const colors_1 = require("../utils/colors");
|
|
8
8
|
const params_1 = require("../utils/params");
|
|
9
9
|
class TezX extends router_1.Router {
|
|
10
10
|
constructor({ basePath = "/", env = {}, debugMode = false, allowDuplicateMw = false, overwriteMethod = true, } = {}) {
|
|
@@ -86,10 +86,14 @@ class TezX extends router_1.Router {
|
|
|
86
86
|
}
|
|
87
87
|
};
|
|
88
88
|
const response = await next();
|
|
89
|
-
if (
|
|
89
|
+
if (response instanceof Response) {
|
|
90
|
+
return response;
|
|
91
|
+
}
|
|
92
|
+
if (!response && !ctx.body) {
|
|
90
93
|
throw new Error(`Handler did not return a response or next() was not called. Path: ${ctx.pathname}, Method: ${ctx.method}`);
|
|
91
94
|
}
|
|
92
|
-
|
|
95
|
+
const resBody = response || ctx.body;
|
|
96
|
+
return ctx.send(resBody, ctx.headers.toObject());
|
|
93
97
|
};
|
|
94
98
|
}
|
|
95
99
|
#findMiddleware(pathname) {
|
|
@@ -129,7 +133,7 @@ class TezX extends router_1.Router {
|
|
|
129
133
|
return (await this.#createHandler(middlewares, callback)(ctx));
|
|
130
134
|
}
|
|
131
135
|
else {
|
|
132
|
-
let res = await config_1.GlobalConfig.notFound(ctx);
|
|
136
|
+
let res = (await config_1.GlobalConfig.notFound(ctx));
|
|
133
137
|
ctx.setStatus = res.status;
|
|
134
138
|
return res;
|
|
135
139
|
}
|
package/cjs/index.js
CHANGED
|
@@ -7,4 +7,4 @@ var server_1 = require("./core/server");
|
|
|
7
7
|
Object.defineProperty(exports, "TezX", { enumerable: true, get: function () { return server_1.TezX; } });
|
|
8
8
|
var params_1 = require("./utils/params");
|
|
9
9
|
Object.defineProperty(exports, "useParams", { enumerable: true, get: function () { return params_1.useParams; } });
|
|
10
|
-
exports.version = "1.0.
|
|
10
|
+
exports.version = "1.0.37";
|
|
@@ -4,12 +4,12 @@ exports.i18nMiddleware = void 0;
|
|
|
4
4
|
const config_1 = require("../core/config");
|
|
5
5
|
const i18nMiddleware = (options) => {
|
|
6
6
|
const { loadTranslations, defaultCacheDuration = 3600000, isCacheValid = (cached) => cached.expiresAt > Date.now(), detectLanguage = (ctx) => ctx.req.query.lang ||
|
|
7
|
-
ctx.cookies?.get(
|
|
8
|
-
ctx.headers.get(
|
|
7
|
+
ctx.cookies?.get("lang") ||
|
|
8
|
+
ctx.req.headers.get("accept-language")?.split(",")[0] ||
|
|
9
9
|
options.defaultLanguage ||
|
|
10
|
-
|
|
11
|
-
return Object.entries(options).reduce((msg, [key, value]) => msg.replace(new RegExp(`{{${key}}}`,
|
|
12
|
-
}, cacheTranslations = true } = options;
|
|
10
|
+
"en", defaultLanguage = "en", fallbackChain = [], translationFunctionKey = "t", formatMessage = (message, options = {}) => {
|
|
11
|
+
return Object.entries(options).reduce((msg, [key, value]) => msg.replace(new RegExp(`{{${key}}}`, "g"), String(value)), message);
|
|
12
|
+
}, cacheTranslations = true, } = options;
|
|
13
13
|
const translationCache = {};
|
|
14
14
|
return async (ctx, next) => {
|
|
15
15
|
try {
|
|
@@ -17,12 +17,12 @@ const i18nMiddleware = (options) => {
|
|
|
17
17
|
const languageChain = [
|
|
18
18
|
detectedLanguage,
|
|
19
19
|
...fallbackChain,
|
|
20
|
-
defaultLanguage
|
|
20
|
+
defaultLanguage,
|
|
21
21
|
].filter(Boolean);
|
|
22
22
|
let translations = null;
|
|
23
23
|
let selectedLanguage = defaultLanguage;
|
|
24
24
|
for (const lang of languageChain) {
|
|
25
|
-
const normalizedLang = lang.split(
|
|
25
|
+
const normalizedLang = lang.split("-")[0].toLowerCase();
|
|
26
26
|
if (cacheTranslations && translationCache[normalizedLang]) {
|
|
27
27
|
const cached = translationCache[normalizedLang];
|
|
28
28
|
if (isCacheValid(cached, normalizedLang)) {
|
|
@@ -38,7 +38,7 @@ const i18nMiddleware = (options) => {
|
|
|
38
38
|
if (expiresAt instanceof Date) {
|
|
39
39
|
expirationTime = expiresAt.getTime();
|
|
40
40
|
}
|
|
41
|
-
else if (typeof expiresAt ===
|
|
41
|
+
else if (typeof expiresAt === "number") {
|
|
42
42
|
expirationTime = expiresAt;
|
|
43
43
|
}
|
|
44
44
|
translations = loadedTranslations;
|
|
@@ -56,13 +56,13 @@ const i18nMiddleware = (options) => {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
if (!translations) {
|
|
59
|
-
throw new Error(
|
|
59
|
+
throw new Error("No translations available");
|
|
60
60
|
}
|
|
61
61
|
ctx[translationFunctionKey] = (key, options) => {
|
|
62
|
-
const value = key.split(
|
|
63
|
-
return
|
|
62
|
+
const value = key.split(".").reduce((acc, k) => {
|
|
63
|
+
return acc && typeof acc === "object" ? acc[k] : undefined;
|
|
64
64
|
}, translations);
|
|
65
|
-
const message = typeof value ===
|
|
65
|
+
const message = typeof value === "string" ? value : key;
|
|
66
66
|
return formatMessage(message, options);
|
|
67
67
|
};
|
|
68
68
|
ctx.language = selectedLanguage;
|
|
@@ -70,7 +70,7 @@ const i18nMiddleware = (options) => {
|
|
|
70
70
|
return await next();
|
|
71
71
|
}
|
|
72
72
|
catch (error) {
|
|
73
|
-
config_1.GlobalConfig.debugging.error(
|
|
73
|
+
config_1.GlobalConfig.debugging.error("i18n processing error:", error);
|
|
74
74
|
ctx.setStatus = 500;
|
|
75
75
|
throw error;
|
|
76
76
|
}
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.lazyLoadModules = void 0;
|
|
4
4
|
const config_1 = require("../core/config");
|
|
5
|
-
;
|
|
6
5
|
const lazyLoadModules = (options) => {
|
|
7
|
-
const { moduleKey = (ctx) => ctx.req.params[queryKeyModule] || ctx.req.query[queryKeyModule], getModuleLoader, queryKeyModule = "module", moduleContextKey = "module", cacheTTL = 3600000, dependencies = {}, enableCache = true, cacheStorage = new Map(), lifecycleHooks = {}, validateModule } = options;
|
|
6
|
+
const { moduleKey = (ctx) => ctx.req.params[queryKeyModule] || ctx.req.query[queryKeyModule], getModuleLoader, queryKeyModule = "module", moduleContextKey = "module", cacheTTL = 3600000, dependencies = {}, enableCache = true, cacheStorage = new Map(), lifecycleHooks = {}, validateModule, } = options;
|
|
8
7
|
return async (ctx, next) => {
|
|
9
|
-
let moduleName = moduleKey(ctx) ||
|
|
8
|
+
let moduleName = moduleKey(ctx) ||
|
|
9
|
+
ctx.req.params[queryKeyModule] ||
|
|
10
|
+
ctx.req.query[queryKeyModule];
|
|
10
11
|
if (!moduleName) {
|
|
11
12
|
config_1.GlobalConfig.debugging.warn("No module specified for lazy loading.");
|
|
12
13
|
return await next();
|
|
@@ -43,7 +44,7 @@ const lazyLoadModules = (options) => {
|
|
|
43
44
|
if (enableCache) {
|
|
44
45
|
cacheStorage.set(moduleName, {
|
|
45
46
|
module,
|
|
46
|
-
expiresAt: Date.now() + cacheTTL
|
|
47
|
+
expiresAt: Date.now() + cacheTTL,
|
|
47
48
|
});
|
|
48
49
|
lifecycleHooks.onCacheSet?.(moduleName, module, ctx);
|
|
49
50
|
}
|
|
@@ -12,40 +12,40 @@ const paginationHandler = (options = {}) => {
|
|
|
12
12
|
ctx.pagination = {
|
|
13
13
|
page,
|
|
14
14
|
limit,
|
|
15
|
-
offset
|
|
15
|
+
offset,
|
|
16
16
|
queryKeyPage,
|
|
17
|
-
queryKeyLimit
|
|
17
|
+
queryKeyLimit,
|
|
18
18
|
};
|
|
19
19
|
if (getDataSource) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
ctx.body =
|
|
45
|
-
|
|
20
|
+
const dataSourceResponse = await getDataSource(ctx, {
|
|
21
|
+
page,
|
|
22
|
+
limit,
|
|
23
|
+
offset,
|
|
24
|
+
});
|
|
25
|
+
const total = dataSourceResponse?.[countKey];
|
|
26
|
+
const data = dataSourceResponse?.[dataKey];
|
|
27
|
+
const pagination = {
|
|
28
|
+
page,
|
|
29
|
+
limit,
|
|
30
|
+
totalItems: total,
|
|
31
|
+
totalPages: Math.ceil(total / limit),
|
|
32
|
+
hasNextPage: page < Math.ceil(total / limit),
|
|
33
|
+
hasPrevPage: page > 1,
|
|
34
|
+
nextPage: page < Math.ceil(total / limit) ? page + 1 : null,
|
|
35
|
+
prevPage: page > 1 ? page - 1 : null,
|
|
36
|
+
};
|
|
37
|
+
ctx.pagination = pagination;
|
|
38
|
+
const body = {
|
|
39
|
+
[dataKey]: data,
|
|
40
|
+
[countKey]: total,
|
|
41
|
+
pagination,
|
|
42
|
+
};
|
|
43
|
+
if (next) {
|
|
44
|
+
ctx.body = body;
|
|
45
|
+
return await next();
|
|
46
46
|
}
|
|
47
|
+
return (ctx.body = body);
|
|
47
48
|
}
|
|
48
|
-
return await next();
|
|
49
49
|
};
|
|
50
50
|
};
|
|
51
51
|
exports.paginationHandler = paginationHandler;
|
|
@@ -2,21 +2,20 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.rateLimiter = void 0;
|
|
4
4
|
const rateLimiter = (options) => {
|
|
5
|
-
const { maxRequests, windowMs, keyGenerator = (ctx) => `${ctx.req.remoteAddress.address}:${ctx.req.remoteAddress.port}`, onError = (ctx, retryAfter, error) => {
|
|
5
|
+
const { maxRequests, windowMs, keyGenerator = (ctx) => `${ctx.req.remoteAddress.address}:${ctx.req.remoteAddress.port}`, cacheStorage = new Map(), onError = (ctx, retryAfter, error) => {
|
|
6
6
|
ctx.setStatus = 429;
|
|
7
7
|
throw new Error(`Rate limit exceeded. Try again in ${retryAfter} seconds.`);
|
|
8
8
|
}, } = options;
|
|
9
|
-
const memoryStore = new Map();
|
|
10
9
|
return async (ctx, next) => {
|
|
11
10
|
const key = keyGenerator(ctx);
|
|
12
11
|
let requestCount;
|
|
13
12
|
let resetTime;
|
|
14
|
-
for (const [key, entry] of
|
|
13
|
+
for (const [key, entry] of cacheStorage.entries()) {
|
|
15
14
|
if (Date.now() >= entry.resetTime) {
|
|
16
|
-
|
|
15
|
+
cacheStorage.delete(key);
|
|
17
16
|
}
|
|
18
17
|
}
|
|
19
|
-
const entry =
|
|
18
|
+
const entry = cacheStorage.get(key);
|
|
20
19
|
if (entry && Date.now() < entry.resetTime) {
|
|
21
20
|
requestCount = entry.count + 1;
|
|
22
21
|
resetTime = entry.resetTime;
|
|
@@ -24,7 +23,7 @@ const rateLimiter = (options) => {
|
|
|
24
23
|
else {
|
|
25
24
|
requestCount = 1;
|
|
26
25
|
resetTime = Date.now() + windowMs;
|
|
27
|
-
|
|
26
|
+
cacheStorage.set(key, { count: requestCount, resetTime });
|
|
28
27
|
}
|
|
29
28
|
if (requestCount > maxRequests) {
|
|
30
29
|
const retryAfter = Math.ceil((resetTime - Date.now()) / 1000);
|
|
@@ -14,7 +14,7 @@ const xssProtection = (options = {}) => {
|
|
|
14
14
|
ctx.headers.set("X-XSS-Protection", xssHeaderValue);
|
|
15
15
|
config_1.GlobalConfig.debugging.warn(`🟢 X-XSS-Protection set to: ${xssHeaderValue}`);
|
|
16
16
|
if (fallbackCSP) {
|
|
17
|
-
const existingCSP = ctx.headers.get("Content-Security-Policy");
|
|
17
|
+
const existingCSP = ctx.req.headers.get("Content-Security-Policy");
|
|
18
18
|
if (!existingCSP) {
|
|
19
19
|
ctx.headers.set("Content-Security-Policy", fallbackCSP);
|
|
20
20
|
config_1.GlobalConfig.debugging.warn(`🟣 Fallback CSP set to: ${fallbackCSP}`);
|
package/core/context.d.ts
CHANGED
|
@@ -51,7 +51,6 @@ export declare class Context<T extends Record<string, any> = {}> {
|
|
|
51
51
|
* @type {State}
|
|
52
52
|
*/
|
|
53
53
|
state: State;
|
|
54
|
-
body: Record<string, any>;
|
|
55
54
|
constructor(req: any, connInfo: ConnAddress);
|
|
56
55
|
/**
|
|
57
56
|
* Cookie handling utility with get/set/delete operations
|
|
@@ -192,5 +191,17 @@ export declare class Context<T extends Record<string, any> = {}> {
|
|
|
192
191
|
*/
|
|
193
192
|
get req(): Request;
|
|
194
193
|
protected set params(params: Record<string, any>);
|
|
194
|
+
/**
|
|
195
|
+
* Sets the HTTP response body to be returned to the client.
|
|
196
|
+
* This can be a string, object, or any serializable data.
|
|
197
|
+
* @param body - The response payload to be stored internally.
|
|
198
|
+
*/
|
|
199
|
+
set body(body: any | undefined);
|
|
200
|
+
/**
|
|
201
|
+
* Retrieves the current response body set for the outgoing HTTP response.
|
|
202
|
+
* This value will be used when sending the final response.
|
|
203
|
+
* @returns The internally stored response payload.
|
|
204
|
+
*/
|
|
205
|
+
get body(): any | undefined;
|
|
195
206
|
protected get params(): Record<string, any>;
|
|
196
207
|
}
|
package/core/context.js
CHANGED
|
@@ -79,7 +79,7 @@ export class Context {
|
|
|
79
79
|
#status = 200;
|
|
80
80
|
state = new State();
|
|
81
81
|
#params = {};
|
|
82
|
-
|
|
82
|
+
#resBody;
|
|
83
83
|
#localAddress = {};
|
|
84
84
|
#remoteAddress = {};
|
|
85
85
|
constructor(req, connInfo) {
|
|
@@ -95,7 +95,7 @@ export class Context {
|
|
|
95
95
|
return this;
|
|
96
96
|
}
|
|
97
97
|
get cookies() {
|
|
98
|
-
const c = this.headers.getAll("cookie");
|
|
98
|
+
const c = this.req.headers.getAll("cookie");
|
|
99
99
|
let cookies = {};
|
|
100
100
|
if (Array.isArray(c) && c.length != 0) {
|
|
101
101
|
const cookieHeader = c.join("; ").split(";");
|
|
@@ -164,8 +164,8 @@ export class Context {
|
|
|
164
164
|
else if (typeof args[0] === "object") {
|
|
165
165
|
headers = args[0];
|
|
166
166
|
}
|
|
167
|
-
if (!headers["Content-Type"]) {
|
|
168
|
-
if (typeof body === "string") {
|
|
167
|
+
if (!headers["Content-Type"] && !headers["content-type"]) {
|
|
168
|
+
if (typeof body === "string" || typeof body == "number") {
|
|
169
169
|
headers["Content-Type"] = "text/plain;";
|
|
170
170
|
}
|
|
171
171
|
else if (typeof body === "object" && body !== null) {
|
|
@@ -385,6 +385,7 @@ export class Context {
|
|
|
385
385
|
headers,
|
|
386
386
|
});
|
|
387
387
|
let clone = response.clone();
|
|
388
|
+
this.body = body;
|
|
388
389
|
this.res = response;
|
|
389
390
|
return clone;
|
|
390
391
|
}
|
|
@@ -394,6 +395,12 @@ export class Context {
|
|
|
394
395
|
set params(params) {
|
|
395
396
|
this.#params = params;
|
|
396
397
|
}
|
|
398
|
+
set body(body) {
|
|
399
|
+
this.#resBody = body;
|
|
400
|
+
}
|
|
401
|
+
get body() {
|
|
402
|
+
return this.#resBody;
|
|
403
|
+
}
|
|
397
404
|
get params() {
|
|
398
405
|
return this.#params;
|
|
399
406
|
}
|
package/core/request.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { HeadersParser } from "./header";
|
|
2
1
|
import { UrlRef } from "../utils/url";
|
|
3
2
|
export type FormDataOptions = {
|
|
4
3
|
maxSize?: number;
|
|
@@ -19,7 +18,7 @@ export type ConnAddress = {
|
|
|
19
18
|
};
|
|
20
19
|
export type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE" | "OPTIONS" | "PATCH" | "HEAD" | "ALL" | "TRACE" | "CONNECT" | string;
|
|
21
20
|
export declare class Request {
|
|
22
|
-
|
|
21
|
+
#private;
|
|
23
22
|
/**
|
|
24
23
|
* Full request URL including protocol and query string
|
|
25
24
|
* @type {string}
|
|
@@ -57,6 +56,81 @@ export declare class Request {
|
|
|
57
56
|
*/
|
|
58
57
|
remoteAddress: AddressType;
|
|
59
58
|
constructor(req: any, params: Record<string, any>, remoteAddress: AddressType);
|
|
59
|
+
get headers(): {
|
|
60
|
+
/**
|
|
61
|
+
* Retrieves the first value of a specific header.
|
|
62
|
+
* @param key - Header name to search for.
|
|
63
|
+
* @returns The first header value or undefined if not found.
|
|
64
|
+
* @example
|
|
65
|
+
* get('content-type') // returns 'application/json'
|
|
66
|
+
*/
|
|
67
|
+
get: (key: string) => string | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* Retrieves all values of a specific header.
|
|
70
|
+
* If multiple values exist for a header, all will be returned as an array.
|
|
71
|
+
* @param key - Header name to search for.
|
|
72
|
+
* @returns An array of all header values associated with the key.
|
|
73
|
+
* @example
|
|
74
|
+
* getAll('accept-language') // returns ['en-US', 'fr-CA']
|
|
75
|
+
*/
|
|
76
|
+
getAll: (key: string) => string | never[];
|
|
77
|
+
/**
|
|
78
|
+
* Checks if a header exists in the request.
|
|
79
|
+
* @param key - Header name to check for existence.
|
|
80
|
+
* @returns True if the header exists, false otherwise.
|
|
81
|
+
* @example
|
|
82
|
+
* has('Authorization') // returns true if 'Authorization' header exists
|
|
83
|
+
*/
|
|
84
|
+
has: (key: string) => boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Returns an iterator over all header entries.
|
|
87
|
+
* Each entry is a [key, value] pair where the value can be an array of strings.
|
|
88
|
+
* @returns IterableIterator for iterating over header key-value pairs.
|
|
89
|
+
* @example
|
|
90
|
+
* for (let [key, value] of headers.entries()) {
|
|
91
|
+
* console.log(key, value);
|
|
92
|
+
* }
|
|
93
|
+
*/
|
|
94
|
+
entries: () => IterableIterator<[string, string[]]>;
|
|
95
|
+
/**
|
|
96
|
+
* Returns an iterator over all header keys.
|
|
97
|
+
* This allows iteration over the names of all headers in the request.
|
|
98
|
+
* @returns IterableIterator of header names.
|
|
99
|
+
* @example
|
|
100
|
+
* for (let key of headers.keys()) {
|
|
101
|
+
* console.log(key);
|
|
102
|
+
* }
|
|
103
|
+
*/
|
|
104
|
+
keys: () => IterableIterator<string>;
|
|
105
|
+
/**
|
|
106
|
+
* Returns an iterator over all header values.
|
|
107
|
+
* This allows iteration over the values of all headers, with each value being an array of strings.
|
|
108
|
+
* @returns IterableIterator of header values.
|
|
109
|
+
* @example
|
|
110
|
+
* for (let value of headers.values()) {
|
|
111
|
+
* console.log(value);
|
|
112
|
+
* }
|
|
113
|
+
*/
|
|
114
|
+
values: () => IterableIterator<string[]>;
|
|
115
|
+
/**
|
|
116
|
+
* Iterates over each header and executes a callback for every header found.
|
|
117
|
+
* @param callback - Function to execute for each header. Receives the value array and key.
|
|
118
|
+
* @example
|
|
119
|
+
* headers.forEach((value, key) => {
|
|
120
|
+
* console.log(key, value);
|
|
121
|
+
* });
|
|
122
|
+
*/
|
|
123
|
+
forEach: (callback: (value: string[], key: string) => void) => void;
|
|
124
|
+
/**
|
|
125
|
+
* Converts all headers into a plain JavaScript object.
|
|
126
|
+
* Single-value headers are represented as a string, and multi-value headers as an array.
|
|
127
|
+
* @returns A plain object with header names as keys and their values as strings or arrays.
|
|
128
|
+
* @example
|
|
129
|
+
* const headersObject = headers.toObject();
|
|
130
|
+
* console.log(headersObject);
|
|
131
|
+
*/
|
|
132
|
+
toObject: () => Record<string, string | string[]>;
|
|
133
|
+
};
|
|
60
134
|
/**
|
|
61
135
|
* Parses the request body as plain text.
|
|
62
136
|
* @returns {Promise<string>} The text content of the request body.
|
package/core/request.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { EnvironmentDetector } from "./environment";
|
|
2
|
-
import { HeadersParser } from "./header";
|
|
3
1
|
import { parseJsonBody, parseMultipartBody, parseTextBody, parseUrlEncodedBody, } from "../utils/formData";
|
|
4
2
|
import { urlParse } from "../utils/url";
|
|
3
|
+
import { EnvironmentDetector } from "./environment";
|
|
4
|
+
import { HeadersParser } from "./header";
|
|
5
5
|
export class Request {
|
|
6
|
-
headers = new HeadersParser();
|
|
6
|
+
#headers = new HeadersParser();
|
|
7
7
|
url;
|
|
8
8
|
method;
|
|
9
9
|
urlRef = {
|
|
@@ -24,13 +24,13 @@ export class Request {
|
|
|
24
24
|
remoteAddress = {};
|
|
25
25
|
constructor(req, params, remoteAddress) {
|
|
26
26
|
this.remoteAddress = remoteAddress;
|
|
27
|
-
this
|
|
27
|
+
this.#headers = new HeadersParser(req?.headers);
|
|
28
28
|
this.method = req?.method?.toUpperCase();
|
|
29
29
|
this.params = params;
|
|
30
30
|
this.rawRequest = req;
|
|
31
31
|
if (EnvironmentDetector.getEnvironment == "node") {
|
|
32
32
|
const protocol = EnvironmentDetector.detectProtocol(req);
|
|
33
|
-
const host = EnvironmentDetector.getHost(this
|
|
33
|
+
const host = EnvironmentDetector.getHost(this.#headers);
|
|
34
34
|
this.url = `${protocol}://${host}${req.url}`;
|
|
35
35
|
}
|
|
36
36
|
else {
|
|
@@ -39,11 +39,40 @@ export class Request {
|
|
|
39
39
|
this.urlRef = urlParse(this.url);
|
|
40
40
|
this.query = this.urlRef.query;
|
|
41
41
|
}
|
|
42
|
+
get headers() {
|
|
43
|
+
let requestHeaders = this.#headers;
|
|
44
|
+
return {
|
|
45
|
+
get: function get(key) {
|
|
46
|
+
return requestHeaders.get(key.toLowerCase());
|
|
47
|
+
},
|
|
48
|
+
getAll: function getAll(key) {
|
|
49
|
+
return requestHeaders.get(key.toLowerCase()) || [];
|
|
50
|
+
},
|
|
51
|
+
has: function has(key) {
|
|
52
|
+
return requestHeaders.has(key.toLowerCase());
|
|
53
|
+
},
|
|
54
|
+
entries: function entries() {
|
|
55
|
+
return requestHeaders.entries();
|
|
56
|
+
},
|
|
57
|
+
keys: function keys() {
|
|
58
|
+
return requestHeaders.keys();
|
|
59
|
+
},
|
|
60
|
+
values: function values() {
|
|
61
|
+
return requestHeaders.values();
|
|
62
|
+
},
|
|
63
|
+
forEach: function forEach(callback) {
|
|
64
|
+
return requestHeaders.forEach(callback);
|
|
65
|
+
},
|
|
66
|
+
toObject: function toObject() {
|
|
67
|
+
return requestHeaders.toObject();
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
42
71
|
async text() {
|
|
43
72
|
return await parseTextBody(this.rawRequest);
|
|
44
73
|
}
|
|
45
74
|
async json() {
|
|
46
|
-
const contentType = this
|
|
75
|
+
const contentType = this.#headers.get("content-type") || "";
|
|
47
76
|
if (contentType.includes("application/json")) {
|
|
48
77
|
return await parseJsonBody(this.rawRequest);
|
|
49
78
|
}
|
|
@@ -52,7 +81,7 @@ export class Request {
|
|
|
52
81
|
}
|
|
53
82
|
}
|
|
54
83
|
async formData(options) {
|
|
55
|
-
const contentType = this
|
|
84
|
+
const contentType = this.#headers.get("content-type") || "";
|
|
56
85
|
if (!contentType) {
|
|
57
86
|
throw Error("Invalid Content-Type");
|
|
58
87
|
}
|
package/core/router.d.ts
CHANGED
|
@@ -3,8 +3,9 @@ import MiddlewareConfigure, { DuplicateMiddlewares, UniqueMiddlewares } from "./
|
|
|
3
3
|
import { HTTPMethod } from "./request";
|
|
4
4
|
export type NextCallback = () => Promise<any>;
|
|
5
5
|
export type ctx<T extends Record<string, any> = {}> = Context<T> & T;
|
|
6
|
-
export type
|
|
7
|
-
export type
|
|
6
|
+
export type CallbackReturnType = Promise<Response> | Response | string | Record<string, any>;
|
|
7
|
+
export type Callback<T extends Record<string, any> = {}> = (ctx: ctx<T>) => CallbackReturnType;
|
|
8
|
+
export type Middleware<T extends Record<string, any> = {}> = (ctx: ctx<T>, next: NextCallback) => NextCallback | Promise<NextCallback | Response> | Response | string | Record<string, any>;
|
|
8
9
|
export type RouterConfig = {
|
|
9
10
|
/**
|
|
10
11
|
* `env` allows you to define environment variables for the router.
|
|
@@ -75,64 +76,72 @@ export declare class Router<T extends Record<string, any> = {}> extends Middlewa
|
|
|
75
76
|
* app.get('/admin', [authMiddleware, adminMiddleware], (ctx) => { ... });
|
|
76
77
|
*/
|
|
77
78
|
get(path: string, callback: Callback<T>): this;
|
|
79
|
+
get(path: string, middleware: Middleware<T>): this;
|
|
80
|
+
get(path: string, middleware: Middleware<T>, callback: Callback<T>): this;
|
|
78
81
|
get(path: string, middlewares: Middleware<T>[], callback: Callback<T>): this;
|
|
79
|
-
get(path: string, middlewares: Middleware<T>, callback: Callback<T>): this;
|
|
80
82
|
/**
|
|
81
83
|
* Registers a POST route with optional middleware(s)
|
|
82
84
|
* @param path - URL path pattern
|
|
83
85
|
* @param args - Handler callback or middleware(s) + handler
|
|
84
86
|
*/
|
|
85
87
|
post(path: string, callback: Callback<T>): this;
|
|
88
|
+
post(path: string, middleware: Middleware<T>): this;
|
|
89
|
+
post(path: string, middleware: Middleware<T>, callback: Callback<T>): this;
|
|
86
90
|
post(path: string, middlewares: Middleware<T>[], callback: Callback<T>): this;
|
|
87
|
-
post(path: string, middlewares: Middleware<T>, callback: Callback<T>): this;
|
|
88
91
|
/**
|
|
89
92
|
* Registers a PUT route with optional middleware(s)
|
|
90
93
|
* @param path - URL path pattern
|
|
91
94
|
* @param args - Handler callback or middleware(s) + handler
|
|
92
95
|
*/
|
|
93
96
|
put(path: string, callback: Callback<T>): this;
|
|
97
|
+
put(path: string, middleware: Middleware<T>): this;
|
|
98
|
+
put(path: string, middleware: Middleware<T>, callback: Callback<T>): this;
|
|
94
99
|
put(path: string, middlewares: Middleware<T>[], callback: Callback<T>): this;
|
|
95
|
-
put(path: string, middlewares: Middleware<T>, callback: Callback<T>): this;
|
|
96
100
|
/**
|
|
97
101
|
* Registers a PATCH route with optional middleware(s)
|
|
98
102
|
* @param path - URL path pattern
|
|
99
103
|
* @param args - Handler callback or middleware(s) + handler
|
|
100
104
|
*/
|
|
101
105
|
patch(path: string, callback: Callback<T>): this;
|
|
106
|
+
patch(path: string, middleware: Middleware<T>): this;
|
|
107
|
+
patch(path: string, middleware: Middleware<T>, callback: Callback<T>): this;
|
|
102
108
|
patch(path: string, middlewares: Middleware<T>[], callback: Callback<T>): this;
|
|
103
|
-
patch(path: string, middlewares: Middleware<T>, callback: Callback<T>): this;
|
|
104
109
|
/**
|
|
105
110
|
* Registers a DELETE route with optional middleware(s)
|
|
106
111
|
* @param path - URL path pattern
|
|
107
112
|
* @param args - Handler callback or middleware(s) + handler
|
|
108
113
|
*/
|
|
109
114
|
delete(path: string, callback: Callback<T>): this;
|
|
115
|
+
delete(path: string, middleware: Middleware<T>): this;
|
|
116
|
+
delete(path: string, middleware: Middleware<T>, callback: Callback<T>): this;
|
|
110
117
|
delete(path: string, middlewares: Middleware<T>[], callback: Callback<T>): this;
|
|
111
|
-
delete(path: string, middlewares: Middleware<T>, callback: Callback<T>): this;
|
|
112
118
|
/**
|
|
113
119
|
* Registers an OPTIONS route (primarily for CORS preflight requests)
|
|
114
120
|
* @param path - URL path pattern
|
|
115
121
|
* @param args - Handler callback or middleware(s) + handler
|
|
116
122
|
*/
|
|
117
123
|
options(path: string, callback: Callback<T>): this;
|
|
124
|
+
options(path: string, middleware: Middleware<T>): this;
|
|
125
|
+
options(path: string, middleware: Middleware<T>, callback: Callback<T>): this;
|
|
118
126
|
options(path: string, middlewares: Middleware<T>[], callback: Callback<T>): this;
|
|
119
|
-
options(path: string, middlewares: Middleware<T>, callback: Callback<T>): this;
|
|
120
127
|
/**
|
|
121
128
|
* Registers a HEAD route (returns headers only)
|
|
122
129
|
* @param path - URL path pattern
|
|
123
130
|
* @param args - Handler callback or middleware(s) + handler
|
|
124
131
|
*/
|
|
125
132
|
head(path: string, callback: Callback<T>): this;
|
|
133
|
+
head(path: string, middleware: Middleware<T>): this;
|
|
134
|
+
head(path: string, middleware: Middleware<T>, callback: Callback<T>): this;
|
|
126
135
|
head(path: string, middlewares: Middleware<T>[], callback: Callback<T>): this;
|
|
127
|
-
head(path: string, middlewares: Middleware<T>, callback: Callback<T>): this;
|
|
128
136
|
/**
|
|
129
137
|
* Registers a route that responds to all HTTP methods
|
|
130
138
|
* @param path - URL path pattern
|
|
131
139
|
* @param args - Handler callback or middleware(s) + handler
|
|
132
140
|
*/
|
|
133
141
|
all(path: string, callback: Callback<T>): this;
|
|
142
|
+
all(path: string, middleware: Middleware<T>): this;
|
|
143
|
+
all(path: string, middleware: Middleware<T>, callback: Callback<T>): this;
|
|
134
144
|
all(path: string, middlewares: Middleware<T>[], callback: Callback<T>): this;
|
|
135
|
-
all(path: string, middlewares: Middleware<T>, callback: Callback<T>): this;
|
|
136
145
|
/**
|
|
137
146
|
* Generic method registration for custom HTTP methods
|
|
138
147
|
* @param method - HTTP method name (e.g., 'PURGE')
|
|
@@ -144,8 +153,9 @@ export declare class Router<T extends Record<string, any> = {}> extends Middlewa
|
|
|
144
153
|
* server.addRoute('PURGE', '/cache', purgeHandler);
|
|
145
154
|
*/
|
|
146
155
|
addRoute(method: HTTPMethod, path: string, callback: Callback<T>): this;
|
|
156
|
+
addRoute(method: HTTPMethod, path: string, middleware: Middleware<T>): this;
|
|
157
|
+
addRoute(method: HTTPMethod, path: string, middleware: Middleware<T>, callback: Callback<T>): this;
|
|
147
158
|
addRoute(method: HTTPMethod, path: string, middlewares: Middleware<T>[], callback: Callback<T>): this;
|
|
148
|
-
addRoute(method: HTTPMethod, path: string, middlewares: Middleware<T>, callback: Callback<T>): this;
|
|
149
159
|
/**
|
|
150
160
|
* Mount a sub-router at specific path prefix
|
|
151
161
|
* @param path - Base path for the sub-router
|
package/core/router.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { GlobalConfig } from "./config";
|
|
2
|
-
import MiddlewareConfigure, { TriMiddleware, } from "./MiddlewareConfigure";
|
|
3
1
|
import { getFiles } from "../utils/staticFile";
|
|
4
2
|
import { sanitizePathSplit } from "../utils/url";
|
|
3
|
+
import { GlobalConfig } from "./config";
|
|
4
|
+
import MiddlewareConfigure, { TriMiddleware, } from "./MiddlewareConfigure";
|
|
5
5
|
class TrieRouter {
|
|
6
6
|
children = new Map();
|
|
7
7
|
handlers = new Map();
|
package/core/server.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { COLORS } from "../utils/colors";
|
|
1
2
|
import { GlobalConfig } from "./config";
|
|
2
3
|
import { Context, httpStatusMap } from "./context";
|
|
3
4
|
import { Router } from "./router";
|
|
4
|
-
import { COLORS } from "../utils/colors";
|
|
5
5
|
import { useParams } from "../utils/params";
|
|
6
6
|
export class TezX extends Router {
|
|
7
7
|
constructor({ basePath = "/", env = {}, debugMode = false, allowDuplicateMw = false, overwriteMethod = true, } = {}) {
|
|
@@ -83,10 +83,14 @@ export class TezX extends Router {
|
|
|
83
83
|
}
|
|
84
84
|
};
|
|
85
85
|
const response = await next();
|
|
86
|
-
if (
|
|
86
|
+
if (response instanceof Response) {
|
|
87
|
+
return response;
|
|
88
|
+
}
|
|
89
|
+
if (!response && !ctx.body) {
|
|
87
90
|
throw new Error(`Handler did not return a response or next() was not called. Path: ${ctx.pathname}, Method: ${ctx.method}`);
|
|
88
91
|
}
|
|
89
|
-
|
|
92
|
+
const resBody = response || ctx.body;
|
|
93
|
+
return ctx.send(resBody, ctx.headers.toObject());
|
|
90
94
|
};
|
|
91
95
|
}
|
|
92
96
|
#findMiddleware(pathname) {
|
|
@@ -126,7 +130,7 @@ export class TezX extends Router {
|
|
|
126
130
|
return (await this.#createHandler(middlewares, callback)(ctx));
|
|
127
131
|
}
|
|
128
132
|
else {
|
|
129
|
-
let res = await GlobalConfig.notFound(ctx);
|
|
133
|
+
let res = (await GlobalConfig.notFound(ctx));
|
|
130
134
|
ctx.setStatus = res.status;
|
|
131
135
|
return res;
|
|
132
136
|
}
|
package/index.js
CHANGED
|
@@ -8,10 +8,10 @@ export type loadTranslations = (language: string) => Promise<{
|
|
|
8
8
|
}>;
|
|
9
9
|
export type I18nOptions = {
|
|
10
10
|
/**
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
* 🌐 Function to load translations dynamically
|
|
12
|
+
* @param language - Language code to load (e.g., "en-US")
|
|
13
|
+
* @returns Promise with translations map and optional expiration
|
|
14
|
+
*/
|
|
15
15
|
loadTranslations: (language: string) => Promise<{
|
|
16
16
|
translations: TranslationMap;
|
|
17
17
|
expiresAt?: Date | number;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { GlobalConfig } from "../core/config";
|
|
2
2
|
export const i18nMiddleware = (options) => {
|
|
3
3
|
const { loadTranslations, defaultCacheDuration = 3600000, isCacheValid = (cached) => cached.expiresAt > Date.now(), detectLanguage = (ctx) => ctx.req.query.lang ||
|
|
4
|
-
ctx.cookies?.get(
|
|
5
|
-
ctx.headers.get(
|
|
4
|
+
ctx.cookies?.get("lang") ||
|
|
5
|
+
ctx.req.headers.get("accept-language")?.split(",")[0] ||
|
|
6
6
|
options.defaultLanguage ||
|
|
7
|
-
|
|
8
|
-
return Object.entries(options).reduce((msg, [key, value]) => msg.replace(new RegExp(`{{${key}}}`,
|
|
9
|
-
}, cacheTranslations = true } = options;
|
|
7
|
+
"en", defaultLanguage = "en", fallbackChain = [], translationFunctionKey = "t", formatMessage = (message, options = {}) => {
|
|
8
|
+
return Object.entries(options).reduce((msg, [key, value]) => msg.replace(new RegExp(`{{${key}}}`, "g"), String(value)), message);
|
|
9
|
+
}, cacheTranslations = true, } = options;
|
|
10
10
|
const translationCache = {};
|
|
11
11
|
return async (ctx, next) => {
|
|
12
12
|
try {
|
|
@@ -14,12 +14,12 @@ export const i18nMiddleware = (options) => {
|
|
|
14
14
|
const languageChain = [
|
|
15
15
|
detectedLanguage,
|
|
16
16
|
...fallbackChain,
|
|
17
|
-
defaultLanguage
|
|
17
|
+
defaultLanguage,
|
|
18
18
|
].filter(Boolean);
|
|
19
19
|
let translations = null;
|
|
20
20
|
let selectedLanguage = defaultLanguage;
|
|
21
21
|
for (const lang of languageChain) {
|
|
22
|
-
const normalizedLang = lang.split(
|
|
22
|
+
const normalizedLang = lang.split("-")[0].toLowerCase();
|
|
23
23
|
if (cacheTranslations && translationCache[normalizedLang]) {
|
|
24
24
|
const cached = translationCache[normalizedLang];
|
|
25
25
|
if (isCacheValid(cached, normalizedLang)) {
|
|
@@ -35,7 +35,7 @@ export const i18nMiddleware = (options) => {
|
|
|
35
35
|
if (expiresAt instanceof Date) {
|
|
36
36
|
expirationTime = expiresAt.getTime();
|
|
37
37
|
}
|
|
38
|
-
else if (typeof expiresAt ===
|
|
38
|
+
else if (typeof expiresAt === "number") {
|
|
39
39
|
expirationTime = expiresAt;
|
|
40
40
|
}
|
|
41
41
|
translations = loadedTranslations;
|
|
@@ -53,13 +53,13 @@ export const i18nMiddleware = (options) => {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
if (!translations) {
|
|
56
|
-
throw new Error(
|
|
56
|
+
throw new Error("No translations available");
|
|
57
57
|
}
|
|
58
58
|
ctx[translationFunctionKey] = (key, options) => {
|
|
59
|
-
const value = key.split(
|
|
60
|
-
return
|
|
59
|
+
const value = key.split(".").reduce((acc, k) => {
|
|
60
|
+
return acc && typeof acc === "object" ? acc[k] : undefined;
|
|
61
61
|
}, translations);
|
|
62
|
-
const message = typeof value ===
|
|
62
|
+
const message = typeof value === "string" ? value : key;
|
|
63
63
|
return formatMessage(message, options);
|
|
64
64
|
};
|
|
65
65
|
ctx.language = selectedLanguage;
|
|
@@ -67,7 +67,7 @@ export const i18nMiddleware = (options) => {
|
|
|
67
67
|
return await next();
|
|
68
68
|
}
|
|
69
69
|
catch (error) {
|
|
70
|
-
GlobalConfig.debugging.error(
|
|
70
|
+
GlobalConfig.debugging.error("i18n processing error:", error);
|
|
71
71
|
ctx.setStatus = 500;
|
|
72
72
|
throw error;
|
|
73
73
|
}
|
package/middleware/index.d.ts
CHANGED
package/middleware/index.js
CHANGED
|
@@ -42,7 +42,6 @@ interface LazyLoadOptions<T> {
|
|
|
42
42
|
get: (key: string) => CacheItem<T> | undefined;
|
|
43
43
|
set: (key: string, value: CacheItem<T>) => void;
|
|
44
44
|
delete: (key: string) => void;
|
|
45
|
-
clear?: () => void;
|
|
46
45
|
};
|
|
47
46
|
/**
|
|
48
47
|
* ⏳ Cache Time-To-Live (TTL) in milliseconds. This determines how long cached modules are valid.
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { GlobalConfig } from "../core/config";
|
|
2
|
-
;
|
|
3
2
|
export const lazyLoadModules = (options) => {
|
|
4
|
-
const { moduleKey = (ctx) => ctx.req.params[queryKeyModule] || ctx.req.query[queryKeyModule], getModuleLoader, queryKeyModule = "module", moduleContextKey = "module", cacheTTL = 3600000, dependencies = {}, enableCache = true, cacheStorage = new Map(), lifecycleHooks = {}, validateModule } = options;
|
|
3
|
+
const { moduleKey = (ctx) => ctx.req.params[queryKeyModule] || ctx.req.query[queryKeyModule], getModuleLoader, queryKeyModule = "module", moduleContextKey = "module", cacheTTL = 3600000, dependencies = {}, enableCache = true, cacheStorage = new Map(), lifecycleHooks = {}, validateModule, } = options;
|
|
5
4
|
return async (ctx, next) => {
|
|
6
|
-
let moduleName = moduleKey(ctx) ||
|
|
5
|
+
let moduleName = moduleKey(ctx) ||
|
|
6
|
+
ctx.req.params[queryKeyModule] ||
|
|
7
|
+
ctx.req.query[queryKeyModule];
|
|
7
8
|
if (!moduleName) {
|
|
8
9
|
GlobalConfig.debugging.warn("No module specified for lazy loading.");
|
|
9
10
|
return await next();
|
|
@@ -40,7 +41,7 @@ export const lazyLoadModules = (options) => {
|
|
|
40
41
|
if (enableCache) {
|
|
41
42
|
cacheStorage.set(moduleName, {
|
|
42
43
|
module,
|
|
43
|
-
expiresAt: Date.now() + cacheTTL
|
|
44
|
+
expiresAt: Date.now() + cacheTTL,
|
|
44
45
|
});
|
|
45
46
|
lifecycleHooks.onCacheSet?.(moduleName, module, ctx);
|
|
46
47
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { Context
|
|
1
|
+
import { Context } from "..";
|
|
2
|
+
import { Middleware } from "../core/router";
|
|
2
3
|
export type PaginationOptions = {
|
|
3
4
|
/**
|
|
4
5
|
* 🔢 Default page number when not specified
|
|
@@ -52,7 +53,7 @@ export type PaginationOptions = {
|
|
|
52
53
|
* return db.find().skip((page-1)*limit).limit(limit);
|
|
53
54
|
* }
|
|
54
55
|
*/
|
|
55
|
-
getDataSource?: (ctx: Context
|
|
56
|
+
getDataSource?: <T extends Record<string, any> = {}>(ctx: Context<T>, pagination: {
|
|
56
57
|
page: number;
|
|
57
58
|
limit: number;
|
|
58
59
|
offset: number;
|
|
@@ -60,6 +61,19 @@ export type PaginationOptions = {
|
|
|
60
61
|
[key: string]: any;
|
|
61
62
|
}>;
|
|
62
63
|
};
|
|
64
|
+
export type PaginationBodyType = {
|
|
65
|
+
[x: string]: any;
|
|
66
|
+
pagination: {
|
|
67
|
+
page: number;
|
|
68
|
+
limit: number;
|
|
69
|
+
totalItems: any;
|
|
70
|
+
totalPages: number;
|
|
71
|
+
hasNextPage: boolean;
|
|
72
|
+
hasPrevPage: boolean;
|
|
73
|
+
nextPage: number | null;
|
|
74
|
+
prevPage: number | null;
|
|
75
|
+
};
|
|
76
|
+
};
|
|
63
77
|
/**
|
|
64
78
|
* 🗂️ Advanced pagination middleware with dynamic data fetching
|
|
65
79
|
*
|
|
@@ -69,12 +83,12 @@ export type PaginationOptions = {
|
|
|
69
83
|
* - Comprehensive pagination metadata
|
|
70
84
|
* - Built-in error handling
|
|
71
85
|
*
|
|
72
|
-
* @param {PaginationOptions} [options={}] - Configuration options
|
|
73
|
-
* @returns {
|
|
86
|
+
* @param {PaginationOptions} [options={}] - Configuration options for pagination behavior
|
|
87
|
+
* @returns {Callback} Middleware function that processes pagination and sets response
|
|
74
88
|
*
|
|
75
89
|
* @example
|
|
76
90
|
* // Basic usage
|
|
77
|
-
* app.get('/users', paginationHandler()
|
|
91
|
+
* app.get('/users', paginationHandler());
|
|
78
92
|
*
|
|
79
93
|
* // With dynamic data source
|
|
80
94
|
* app.get('/products', paginationHandler({
|
package/middleware/pagination.js
CHANGED
|
@@ -9,39 +9,39 @@ export const paginationHandler = (options = {}) => {
|
|
|
9
9
|
ctx.pagination = {
|
|
10
10
|
page,
|
|
11
11
|
limit,
|
|
12
|
-
offset
|
|
12
|
+
offset,
|
|
13
13
|
queryKeyPage,
|
|
14
|
-
queryKeyLimit
|
|
14
|
+
queryKeyLimit,
|
|
15
15
|
};
|
|
16
16
|
if (getDataSource) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
ctx.body =
|
|
42
|
-
|
|
17
|
+
const dataSourceResponse = await getDataSource(ctx, {
|
|
18
|
+
page,
|
|
19
|
+
limit,
|
|
20
|
+
offset,
|
|
21
|
+
});
|
|
22
|
+
const total = dataSourceResponse?.[countKey];
|
|
23
|
+
const data = dataSourceResponse?.[dataKey];
|
|
24
|
+
const pagination = {
|
|
25
|
+
page,
|
|
26
|
+
limit,
|
|
27
|
+
totalItems: total,
|
|
28
|
+
totalPages: Math.ceil(total / limit),
|
|
29
|
+
hasNextPage: page < Math.ceil(total / limit),
|
|
30
|
+
hasPrevPage: page > 1,
|
|
31
|
+
nextPage: page < Math.ceil(total / limit) ? page + 1 : null,
|
|
32
|
+
prevPage: page > 1 ? page - 1 : null,
|
|
33
|
+
};
|
|
34
|
+
ctx.pagination = pagination;
|
|
35
|
+
const body = {
|
|
36
|
+
[dataKey]: data,
|
|
37
|
+
[countKey]: total,
|
|
38
|
+
pagination,
|
|
39
|
+
};
|
|
40
|
+
if (next) {
|
|
41
|
+
ctx.body = body;
|
|
42
|
+
return await next();
|
|
43
43
|
}
|
|
44
|
+
return (ctx.body = body);
|
|
44
45
|
}
|
|
45
|
-
return await next();
|
|
46
46
|
};
|
|
47
47
|
};
|
|
@@ -24,13 +24,35 @@ export type RateLimiterOptions = {
|
|
|
24
24
|
// * ⚠️ (Future) Storage backend - currently memory only
|
|
25
25
|
// * @todo Implement Redis storage
|
|
26
26
|
// */
|
|
27
|
+
/**
|
|
28
|
+
* 🔄 Custom cache storage implementation (e.g., using `Map`, `Redis`, etc.).
|
|
29
|
+
* By default, it uses a `Map<string, { count: number; resetTime: number }>`.
|
|
30
|
+
*/
|
|
31
|
+
cacheStorage?: {
|
|
32
|
+
get: (key: string) => {
|
|
33
|
+
count: number;
|
|
34
|
+
resetTime: number;
|
|
35
|
+
} | undefined;
|
|
36
|
+
set: (key: string, value: {
|
|
37
|
+
count: number;
|
|
38
|
+
resetTime: number;
|
|
39
|
+
}) => void;
|
|
40
|
+
delete: (key: string) => void;
|
|
41
|
+
entries: () => IterableIterator<[
|
|
42
|
+
string,
|
|
43
|
+
{
|
|
44
|
+
count: number;
|
|
45
|
+
resetTime: number;
|
|
46
|
+
}
|
|
47
|
+
]>;
|
|
48
|
+
};
|
|
27
49
|
/**
|
|
28
50
|
* 🛑 Custom rate limit exceeded handler
|
|
29
51
|
* @default Sends 429 status with Retry-After header
|
|
30
52
|
* @example
|
|
31
53
|
* onError: (ctx, retryAfter) => {
|
|
32
54
|
* ctx.status = 429;
|
|
33
|
-
*
|
|
55
|
+
* throw new Error( `Rate limit exceeded. Try again in ${retryAfter} seconds.`);
|
|
34
56
|
* }
|
|
35
57
|
*/
|
|
36
58
|
onError?: (ctx: Context, retryAfter: number, error: Error) => void;
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
export const rateLimiter = (options) => {
|
|
2
|
-
const { maxRequests, windowMs, keyGenerator = (ctx) => `${ctx.req.remoteAddress.address}:${ctx.req.remoteAddress.port}`, onError = (ctx, retryAfter, error) => {
|
|
2
|
+
const { maxRequests, windowMs, keyGenerator = (ctx) => `${ctx.req.remoteAddress.address}:${ctx.req.remoteAddress.port}`, cacheStorage = new Map(), onError = (ctx, retryAfter, error) => {
|
|
3
3
|
ctx.setStatus = 429;
|
|
4
4
|
throw new Error(`Rate limit exceeded. Try again in ${retryAfter} seconds.`);
|
|
5
5
|
}, } = options;
|
|
6
|
-
const memoryStore = new Map();
|
|
7
6
|
return async (ctx, next) => {
|
|
8
7
|
const key = keyGenerator(ctx);
|
|
9
8
|
let requestCount;
|
|
10
9
|
let resetTime;
|
|
11
|
-
for (const [key, entry] of
|
|
10
|
+
for (const [key, entry] of cacheStorage.entries()) {
|
|
12
11
|
if (Date.now() >= entry.resetTime) {
|
|
13
|
-
|
|
12
|
+
cacheStorage.delete(key);
|
|
14
13
|
}
|
|
15
14
|
}
|
|
16
|
-
const entry =
|
|
15
|
+
const entry = cacheStorage.get(key);
|
|
17
16
|
if (entry && Date.now() < entry.resetTime) {
|
|
18
17
|
requestCount = entry.count + 1;
|
|
19
18
|
resetTime = entry.resetTime;
|
|
@@ -21,7 +20,7 @@ export const rateLimiter = (options) => {
|
|
|
21
20
|
else {
|
|
22
21
|
requestCount = 1;
|
|
23
22
|
resetTime = Date.now() + windowMs;
|
|
24
|
-
|
|
23
|
+
cacheStorage.set(key, { count: requestCount, resetTime });
|
|
25
24
|
}
|
|
26
25
|
if (requestCount > maxRequests) {
|
|
27
26
|
const retryAfter = Math.ceil((resetTime - Date.now()) / 1000);
|
|
@@ -11,7 +11,7 @@ export const xssProtection = (options = {}) => {
|
|
|
11
11
|
ctx.headers.set("X-XSS-Protection", xssHeaderValue);
|
|
12
12
|
GlobalConfig.debugging.warn(`🟢 X-XSS-Protection set to: ${xssHeaderValue}`);
|
|
13
13
|
if (fallbackCSP) {
|
|
14
|
-
const existingCSP = ctx.headers.get("Content-Security-Policy");
|
|
14
|
+
const existingCSP = ctx.req.headers.get("Content-Security-Policy");
|
|
15
15
|
if (!existingCSP) {
|
|
16
16
|
ctx.headers.set("Content-Security-Policy", fallbackCSP);
|
|
17
17
|
GlobalConfig.debugging.warn(`🟣 Fallback CSP set to: ${fallbackCSP}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tezx",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.37",
|
|
4
4
|
"description": "TezX is a high-performance, lightweight JavaScript framework designed for speed, scalability, and flexibility. It enables efficient routing, middleware management, and static file serving with minimal configuration. Fully compatible with Node.js, Deno, and Bun.",
|
|
5
5
|
"main": "cjs/index.js",
|
|
6
6
|
"module": "index.js",
|