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.
@@ -82,7 +82,7 @@ class Context {
82
82
  #status = 200;
83
83
  state = new state_1.State();
84
84
  #params = {};
85
- body = {};
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
  }
@@ -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.headers = new header_1.HeadersParser(req?.headers);
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.headers);
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.headers.get("content-type") || "";
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.headers.get("content-type") || "";
87
+ const contentType = this.#headers.get("content-type") || "";
59
88
  if (!contentType) {
60
89
  throw Error("Invalid Content-Type");
61
90
  }
@@ -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();
@@ -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 (!response) {
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
- return response;
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.35";
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('lang') ||
8
- ctx.headers.get('accept-language')?.split(',')[0] ||
7
+ ctx.cookies?.get("lang") ||
8
+ ctx.req.headers.get("accept-language")?.split(",")[0] ||
9
9
  options.defaultLanguage ||
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;
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('-')[0].toLowerCase();
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 === 'number') {
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('No translations available');
59
+ throw new Error("No translations available");
60
60
  }
61
61
  ctx[translationFunctionKey] = (key, options) => {
62
- const value = key.split('.').reduce((acc, k) => {
63
- return (acc && typeof acc === 'object') ? acc[k] : undefined;
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 === 'string' ? value : key;
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('i18n processing error:', 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) || ctx.req.params[queryKeyModule] || ctx.req.query[queryKeyModule];
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: offset,
15
+ offset,
16
16
  queryKeyPage,
17
- queryKeyLimit
17
+ queryKeyLimit,
18
18
  };
19
19
  if (getDataSource) {
20
- try {
21
- const dataSourceResponse = await getDataSource(ctx, { page, limit, offset });
22
- const total = dataSourceResponse?.[countKey];
23
- const data = dataSourceResponse?.[dataKey];
24
- if (typeof total !== "number" || !Array.isArray(data)) {
25
- throw new Error("Invalid data structure returned by getDataSource.");
26
- }
27
- ctx.body = {
28
- [dataKey]: data,
29
- [countKey]: total,
30
- pagination: {
31
- page,
32
- limit,
33
- totalItems: total,
34
- totalPages: Math.ceil(total / limit),
35
- hasNextPage: page < Math.ceil(total / limit),
36
- hasPrevPage: page > 1,
37
- nextPage: page < Math.ceil(total / limit) ? page + 1 : null,
38
- prevPage: page > 1 ? page - 1 : null,
39
- },
40
- };
41
- }
42
- catch (error) {
43
- ctx.setStatus = 500;
44
- ctx.body = { error: "Internal Server Error", };
45
- throw new Error("Error fetching or processing data:", error?.message);
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 memoryStore.entries()) {
13
+ for (const [key, entry] of cacheStorage.entries()) {
15
14
  if (Date.now() >= entry.resetTime) {
16
- memoryStore.delete(key);
15
+ cacheStorage.delete(key);
17
16
  }
18
17
  }
19
- const entry = memoryStore.get(key);
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
- memoryStore.set(key, { count: requestCount, resetTime });
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
- body = {};
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
- headers: HeadersParser;
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.headers = new HeadersParser(req?.headers);
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.headers);
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.headers.get("content-type") || "";
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.headers.get("content-type") || "";
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 Callback<T extends Record<string, any> = {}> = (ctx: ctx<T>) => Promise<Response> | Response;
7
- export type Middleware<T extends Record<string, any> = {}> = (ctx: ctx<T>, next: NextCallback) => NextCallback | Promise<NextCallback | Response> | Response;
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 (!response) {
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
- return response;
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
@@ -1,4 +1,4 @@
1
1
  export { Router } from "./core/router";
2
2
  export { TezX } from "./core/server";
3
3
  export { useParams } from "./utils/params";
4
- export let version = "1.0.35";
4
+ export let version = "1.0.37";
@@ -8,10 +8,10 @@ export type loadTranslations = (language: string) => Promise<{
8
8
  }>;
9
9
  export type I18nOptions = {
10
10
  /**
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
- */
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('lang') ||
5
- ctx.headers.get('accept-language')?.split(',')[0] ||
4
+ ctx.cookies?.get("lang") ||
5
+ ctx.req.headers.get("accept-language")?.split(",")[0] ||
6
6
  options.defaultLanguage ||
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;
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('-')[0].toLowerCase();
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 === 'number') {
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('No translations available');
56
+ throw new Error("No translations available");
57
57
  }
58
58
  ctx[translationFunctionKey] = (key, options) => {
59
- const value = key.split('.').reduce((acc, k) => {
60
- return (acc && typeof acc === 'object') ? acc[k] : undefined;
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 === 'string' ? value : key;
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('i18n processing error:', error);
70
+ GlobalConfig.debugging.error("i18n processing error:", error);
71
71
  ctx.setStatus = 500;
72
72
  throw error;
73
73
  }
@@ -9,4 +9,4 @@ export * from "./sanitizeHeader";
9
9
  export * from "./rateLimiter";
10
10
  export * from "./pagination";
11
11
  export * from "./i18nMiddleware";
12
- export * from './lazyLoadModules';
12
+ export * from "./lazyLoadModules";
@@ -8,4 +8,4 @@ export * from "./sanitizeHeader";
8
8
  export * from "./rateLimiter";
9
9
  export * from "./pagination";
10
10
  export * from "./i18nMiddleware";
11
- export * from './lazyLoadModules';
11
+ export * from "./lazyLoadModules";
@@ -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) || ctx.req.params[queryKeyModule] || ctx.req.query[queryKeyModule];
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, Middleware } from "..";
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, pagination: {
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 {Middleware} Configured middleware
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(), getUsers);
91
+ * app.get('/users', paginationHandler());
78
92
  *
79
93
  * // With dynamic data source
80
94
  * app.get('/products', paginationHandler({
@@ -9,39 +9,39 @@ export const paginationHandler = (options = {}) => {
9
9
  ctx.pagination = {
10
10
  page,
11
11
  limit,
12
- offset: offset,
12
+ offset,
13
13
  queryKeyPage,
14
- queryKeyLimit
14
+ queryKeyLimit,
15
15
  };
16
16
  if (getDataSource) {
17
- try {
18
- const dataSourceResponse = await getDataSource(ctx, { page, limit, offset });
19
- const total = dataSourceResponse?.[countKey];
20
- const data = dataSourceResponse?.[dataKey];
21
- if (typeof total !== "number" || !Array.isArray(data)) {
22
- throw new Error("Invalid data structure returned by getDataSource.");
23
- }
24
- ctx.body = {
25
- [dataKey]: data,
26
- [countKey]: total,
27
- 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
- };
38
- }
39
- catch (error) {
40
- ctx.setStatus = 500;
41
- ctx.body = { error: "Internal Server Error", };
42
- throw new Error("Error fetching or processing data:", error?.message);
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
- * ctx.body = { error: `Try again in ${retryAfter} seconds` };
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 memoryStore.entries()) {
10
+ for (const [key, entry] of cacheStorage.entries()) {
12
11
  if (Date.now() >= entry.resetTime) {
13
- memoryStore.delete(key);
12
+ cacheStorage.delete(key);
14
13
  }
15
14
  }
16
- const entry = memoryStore.get(key);
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
- memoryStore.set(key, { count: requestCount, resetTime });
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.35",
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",