tezx 2.0.11 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/README.md +122 -89
  2. package/bun/getConnInfo.d.ts +21 -0
  3. package/bun/getConnInfo.js +9 -0
  4. package/bun/index.d.ts +10 -4
  5. package/bun/index.js +8 -4
  6. package/bun/ws.d.ts +48 -0
  7. package/bun/ws.js +58 -0
  8. package/cjs/bun/getConnInfo.js +12 -0
  9. package/cjs/bun/index.js +35 -7
  10. package/cjs/bun/ws.js +63 -0
  11. package/cjs/core/config.js +2 -12
  12. package/cjs/core/context.js +131 -379
  13. package/cjs/core/error.js +49 -0
  14. package/cjs/core/request.js +79 -131
  15. package/cjs/core/router.js +54 -387
  16. package/cjs/core/server.js +83 -202
  17. package/cjs/deno/env.js +4 -4
  18. package/cjs/deno/getConnInfo.js +18 -0
  19. package/cjs/deno/index.js +11 -18
  20. package/cjs/deno/serveStatic.js +53 -0
  21. package/cjs/deno/ws.js +39 -0
  22. package/cjs/helper/index.js +46 -10
  23. package/cjs/index.js +5 -7
  24. package/cjs/jwt/node.js +94 -0
  25. package/cjs/jwt/web.js +178 -0
  26. package/cjs/middleware/basic-auth.js +42 -0
  27. package/cjs/middleware/bearer-auth.js +34 -0
  28. package/cjs/middleware/cache-control.js +44 -0
  29. package/cjs/middleware/cors.js +11 -21
  30. package/cjs/middleware/detect-bot.js +57 -0
  31. package/cjs/middleware/i18n.js +73 -60
  32. package/cjs/middleware/index.js +8 -46
  33. package/cjs/middleware/logger.js +9 -4
  34. package/cjs/middleware/pagination.js +3 -2
  35. package/cjs/middleware/powered-by.js +3 -2
  36. package/cjs/middleware/rate-limiter.js +38 -0
  37. package/cjs/middleware/request-id.js +4 -5
  38. package/cjs/middleware/sanitize-headers.js +22 -0
  39. package/cjs/middleware/secure-headers copy.js +143 -0
  40. package/cjs/middleware/secure-headers.js +157 -0
  41. package/cjs/middleware/{xssProtection.js → xss-protection.js} +5 -8
  42. package/cjs/node/env.js +7 -7
  43. package/cjs/node/getConnInfo.js +16 -0
  44. package/cjs/node/index.js +17 -18
  45. package/cjs/node/mount-node.js +59 -0
  46. package/cjs/node/serveStatic.js +56 -0
  47. package/cjs/node/toWebRequest.js +25 -0
  48. package/cjs/node/ws.js +82 -0
  49. package/cjs/registry/RadixRouter.js +148 -0
  50. package/cjs/registry/index.js +17 -0
  51. package/cjs/types/headers.js +2 -0
  52. package/cjs/types/index.js +13 -0
  53. package/cjs/utils/buffer.js +17 -0
  54. package/cjs/utils/colors.js +2 -0
  55. package/cjs/utils/cookie.js +59 -0
  56. package/cjs/utils/file.js +136 -0
  57. package/cjs/utils/formData.js +60 -10
  58. package/cjs/utils/generateID.js +37 -0
  59. package/cjs/utils/low-level.js +115 -0
  60. package/cjs/utils/{staticFile.js → mimeTypes.js} +0 -87
  61. package/cjs/utils/rateLimit.js +41 -0
  62. package/cjs/utils/response.js +65 -0
  63. package/cjs/{core/environment.js → utils/runtime.js} +2 -1
  64. package/cjs/utils/url.js +65 -30
  65. package/core/config.d.ts +2 -7
  66. package/core/config.js +2 -12
  67. package/core/context.d.ts +209 -164
  68. package/core/context.js +131 -346
  69. package/core/error.d.ts +96 -0
  70. package/core/error.js +44 -0
  71. package/core/request.d.ts +67 -107
  72. package/core/request.js +78 -130
  73. package/core/router.d.ts +138 -133
  74. package/core/router.js +53 -352
  75. package/core/server.d.ts +99 -38
  76. package/core/server.js +83 -202
  77. package/deno/env.js +3 -3
  78. package/deno/getConnInfo.d.ts +21 -0
  79. package/deno/getConnInfo.js +15 -0
  80. package/deno/index.d.ts +9 -4
  81. package/deno/index.js +7 -4
  82. package/deno/serveStatic.d.ts +28 -0
  83. package/deno/serveStatic.js +49 -0
  84. package/deno/ws.d.ts +42 -0
  85. package/deno/ws.js +36 -0
  86. package/helper/index.d.ts +29 -15
  87. package/helper/index.js +27 -7
  88. package/index.d.ts +10 -8
  89. package/index.js +4 -5
  90. package/jwt/node.d.ts +39 -0
  91. package/jwt/node.js +87 -0
  92. package/jwt/web.d.ts +14 -0
  93. package/jwt/web.js +174 -0
  94. package/middleware/basic-auth.d.ts +56 -0
  95. package/middleware/basic-auth.js +38 -0
  96. package/middleware/bearer-auth.d.ts +53 -0
  97. package/middleware/bearer-auth.js +30 -0
  98. package/middleware/cache-control.d.ts +30 -0
  99. package/middleware/cache-control.js +40 -0
  100. package/middleware/cors.d.ts +30 -3
  101. package/middleware/cors.js +12 -22
  102. package/middleware/detect-bot.d.ts +113 -0
  103. package/middleware/detect-bot.js +53 -0
  104. package/middleware/i18n.d.ts +166 -73
  105. package/middleware/i18n.js +73 -60
  106. package/middleware/index.d.ts +8 -32
  107. package/middleware/index.js +8 -44
  108. package/middleware/logger.d.ts +5 -2
  109. package/middleware/logger.js +9 -4
  110. package/middleware/pagination.d.ts +9 -6
  111. package/middleware/pagination.js +3 -2
  112. package/middleware/powered-by.d.ts +2 -1
  113. package/middleware/powered-by.js +3 -2
  114. package/middleware/{rateLimiter.d.ts → rate-limiter.d.ts} +15 -9
  115. package/middleware/rate-limiter.js +34 -0
  116. package/middleware/request-id.d.ts +2 -1
  117. package/middleware/request-id.js +5 -6
  118. package/middleware/{sanitizeHeader.d.ts → sanitize-headers.d.ts} +5 -19
  119. package/middleware/sanitize-headers.js +18 -0
  120. package/middleware/secure-headers copy.d.ts +15 -0
  121. package/middleware/secure-headers copy.js +136 -0
  122. package/middleware/secure-headers.d.ts +132 -0
  123. package/middleware/secure-headers.js +153 -0
  124. package/middleware/{xssProtection.d.ts → xss-protection.d.ts} +2 -1
  125. package/middleware/xss-protection.js +19 -0
  126. package/node/env.js +4 -4
  127. package/node/getConnInfo.d.ts +21 -0
  128. package/node/getConnInfo.js +13 -0
  129. package/node/index.d.ts +13 -4
  130. package/node/index.js +11 -4
  131. package/node/mount-node.d.ts +11 -0
  132. package/node/mount-node.js +56 -0
  133. package/node/serveStatic.d.ts +36 -0
  134. package/node/serveStatic.js +52 -0
  135. package/node/toWebRequest.js +22 -0
  136. package/node/ws.d.ts +56 -0
  137. package/node/ws.js +46 -0
  138. package/package.json +39 -30
  139. package/registry/RadixRouter.d.ts +40 -0
  140. package/registry/RadixRouter.js +144 -0
  141. package/registry/index.d.ts +2 -0
  142. package/registry/index.js +1 -0
  143. package/types/headers.d.ts +2 -0
  144. package/types/headers.js +1 -0
  145. package/types/index.d.ts +318 -18
  146. package/types/index.js +12 -1
  147. package/utils/buffer.d.ts +1 -0
  148. package/utils/buffer.js +14 -0
  149. package/utils/colors.d.ts +24 -0
  150. package/utils/colors.js +2 -0
  151. package/utils/cookie.d.ts +55 -0
  152. package/utils/cookie.js +53 -0
  153. package/utils/file.d.ts +38 -0
  154. package/utils/file.js +96 -0
  155. package/utils/formData.d.ts +41 -1
  156. package/utils/formData.js +58 -9
  157. package/utils/generateID.d.ts +42 -0
  158. package/utils/generateID.js +32 -0
  159. package/utils/httpStatusMap.d.ts +14 -0
  160. package/utils/low-level.d.ts +58 -0
  161. package/utils/low-level.js +108 -0
  162. package/utils/mimeTypes.d.ts +4 -0
  163. package/utils/{staticFile.js → mimeTypes.js} +0 -53
  164. package/utils/rateLimit.d.ts +18 -0
  165. package/utils/rateLimit.js +37 -0
  166. package/utils/response.d.ts +18 -0
  167. package/utils/response.js +58 -0
  168. package/{core/environment.d.ts → utils/runtime.d.ts} +1 -0
  169. package/{core/environment.js → utils/runtime.js} +1 -0
  170. package/utils/url.d.ts +42 -14
  171. package/utils/url.js +61 -27
  172. package/bun/adapter.d.ts +0 -127
  173. package/bun/adapter.js +0 -97
  174. package/cjs/bun/adapter.js +0 -100
  175. package/cjs/core/MiddlewareConfigure.js +0 -68
  176. package/cjs/core/common.js +0 -15
  177. package/cjs/deno/adpater.js +0 -67
  178. package/cjs/helper/common.js +0 -17
  179. package/cjs/middleware/basicAuth.js +0 -71
  180. package/cjs/middleware/cacheControl.js +0 -90
  181. package/cjs/middleware/detectBot.js +0 -104
  182. package/cjs/middleware/detectLocale.js +0 -43
  183. package/cjs/middleware/lazyLoadModules.js +0 -73
  184. package/cjs/middleware/rateLimiter.js +0 -24
  185. package/cjs/middleware/requestTimeout.js +0 -42
  186. package/cjs/middleware/sanitizeHeader.js +0 -51
  187. package/cjs/middleware/secureHeaders.js +0 -42
  188. package/cjs/node/adapter.js +0 -138
  189. package/cjs/utils/regexRouter.js +0 -58
  190. package/cjs/utils/state.js +0 -34
  191. package/cjs/utils/toWebRequest.js +0 -35
  192. package/cjs/ws/deno.js +0 -20
  193. package/cjs/ws/index.js +0 -53
  194. package/cjs/ws/node.js +0 -65
  195. package/core/MiddlewareConfigure.d.ts +0 -15
  196. package/core/MiddlewareConfigure.js +0 -63
  197. package/core/common.d.ts +0 -21
  198. package/core/common.js +0 -11
  199. package/deno/adpater.d.ts +0 -38
  200. package/deno/adpater.js +0 -64
  201. package/helper/common.d.ts +0 -5
  202. package/helper/common.js +0 -14
  203. package/middleware/basicAuth.d.ts +0 -81
  204. package/middleware/basicAuth.js +0 -67
  205. package/middleware/cacheControl.d.ts +0 -48
  206. package/middleware/cacheControl.js +0 -53
  207. package/middleware/detectBot.d.ts +0 -121
  208. package/middleware/detectBot.js +0 -98
  209. package/middleware/detectLocale.d.ts +0 -55
  210. package/middleware/detectLocale.js +0 -39
  211. package/middleware/lazyLoadModules.d.ts +0 -72
  212. package/middleware/lazyLoadModules.js +0 -69
  213. package/middleware/rateLimiter.js +0 -20
  214. package/middleware/requestTimeout.d.ts +0 -25
  215. package/middleware/requestTimeout.js +0 -38
  216. package/middleware/sanitizeHeader.js +0 -47
  217. package/middleware/secureHeaders.d.ts +0 -78
  218. package/middleware/secureHeaders.js +0 -38
  219. package/middleware/xssProtection.js +0 -22
  220. package/node/adapter.d.ts +0 -46
  221. package/node/adapter.js +0 -102
  222. package/utils/regexRouter.d.ts +0 -66
  223. package/utils/regexRouter.js +0 -53
  224. package/utils/state.d.ts +0 -50
  225. package/utils/state.js +0 -30
  226. package/utils/staticFile.d.ts +0 -10
  227. package/utils/toWebRequest.js +0 -32
  228. package/ws/deno.d.ts +0 -6
  229. package/ws/deno.js +0 -16
  230. package/ws/index.d.ts +0 -180
  231. package/ws/index.js +0 -50
  232. package/ws/node.d.ts +0 -7
  233. package/ws/node.js +0 -28
  234. /package/{utils → node}/toWebRequest.d.ts +0 -0
@@ -1,10 +1,37 @@
1
- import { ctx as Context } from "../types/index.js";
1
+ import { Middleware } from "../types/index.js";
2
2
  export type CorsOptions = {
3
- origin?: string | RegExp | (string | RegExp)[] | ((reqOrigin: string) => boolean);
3
+ /**
4
+ * Allowed origins for CORS.
5
+ * Can be a string, an array of strings, or a function that returns a boolean.
6
+ */
7
+ origin?: string | string[] | ((reqOrigin: string) => boolean);
8
+ /**
9
+ * Allowed HTTP methods for CORS requests.
10
+ * Defaults to ['GET', 'POST', 'PUT', 'DELETE'].
11
+ */
4
12
  methods?: string[];
13
+ /**
14
+ * Allowed headers for CORS requests.
15
+ * Defaults to ['Content-Type', 'Authorization'].
16
+ */
5
17
  allowedHeaders?: string[];
18
+ /**
19
+ * Headers exposed to the browser.
20
+ */
6
21
  exposedHeaders?: string[];
22
+ /**
23
+ * Indicates whether credentials are allowed.
24
+ */
7
25
  credentials?: boolean;
26
+ /**
27
+ * Preflight cache duration in seconds.
28
+ */
8
29
  maxAge?: number;
9
30
  };
10
- export declare function cors(option?: CorsOptions): (ctx: Context, next: () => Promise<any>) => Promise<any>;
31
+ /**
32
+ * Middleware for handling Cross-Origin Resource Sharing (CORS).
33
+ *
34
+ * @param option - Configuration options for CORS.
35
+ */
36
+ declare function cors<T extends Record<string, any> = {}, Path extends string = any>(option?: CorsOptions): Middleware<T, Path>;
37
+ export { cors, cors as default };
@@ -1,33 +1,25 @@
1
- export function cors(option = {}) {
2
- const { methods, allowedHeaders, credentials, exposedHeaders, maxAge, origin, } = option;
1
+ function cors(option = {}) {
2
+ const { credentials, maxAge, origin } = option;
3
+ let methods = (option.methods || ["GET", "POST", "PUT", "DELETE"]).join(", ");
4
+ let allowedHeaders = (option.allowedHeaders || ["Content-Type", "Authorization"]).join(", ");
5
+ let exposedHeaders = option?.exposedHeaders?.join(", ");
3
6
  return async function cors(ctx, next) {
4
- const reqOrigin = ctx.req.headers.get("origin") || "";
7
+ const reqOrigin = ctx.req.header("origin") || "";
5
8
  let allowOrigin = "*";
6
9
  if (typeof origin === "string") {
7
10
  allowOrigin = origin;
8
11
  }
9
- else if (origin instanceof RegExp) {
10
- allowOrigin = origin.test(reqOrigin) ? reqOrigin : "";
11
- }
12
12
  else if (Array.isArray(origin)) {
13
- const isAllowed = origin.some((item) => {
14
- if (typeof item === "string") {
15
- return item === reqOrigin;
16
- }
17
- else if (item instanceof RegExp) {
18
- return item.test(reqOrigin);
19
- }
20
- });
21
- allowOrigin = isAllowed ? reqOrigin : "";
13
+ allowOrigin = origin.includes(reqOrigin) ? reqOrigin : "";
22
14
  }
23
15
  else if (typeof origin === "function") {
24
16
  allowOrigin = origin(reqOrigin) ? reqOrigin : "";
25
17
  }
26
18
  ctx.headers.set("Access-Control-Allow-Origin", allowOrigin);
27
- ctx.headers.set("Access-Control-Allow-Methods", (methods || ["GET", "POST", "PUT", "DELETE"]).join(", "));
28
- ctx.headers.set("Access-Control-Allow-Headers", (allowedHeaders || ["Content-Type", "Authorization"]).join(", "));
19
+ ctx.headers.set("Access-Control-Allow-Methods", methods);
20
+ ctx.headers.set("Access-Control-Allow-Headers", allowedHeaders);
29
21
  if (exposedHeaders) {
30
- ctx.headers.set("Access-Control-Expose-Headers", exposedHeaders.join(", "));
22
+ ctx.headers.set("Access-Control-Expose-Headers", exposedHeaders);
31
23
  }
32
24
  if (credentials) {
33
25
  ctx.headers.set("Access-Control-Allow-Credentials", "true");
@@ -36,11 +28,9 @@ export function cors(option = {}) {
36
28
  ctx.headers.set("Access-Control-Max-Age", maxAge.toString());
37
29
  }
38
30
  if (ctx.req.method === "OPTIONS") {
39
- return new Response(null, {
40
- status: 204,
41
- headers: ctx.headers,
42
- });
31
+ return new Response(null, { status: 204, headers: ctx.headers });
43
32
  }
44
33
  return await next();
45
34
  };
46
35
  }
36
+ export { cors, cors as default };
@@ -0,0 +1,113 @@
1
+ import { Context, Middleware } from "../index.js";
2
+ import { HttpBaseResponse } from "../types/index.js";
3
+ /**
4
+ * ⚙️ Configuration options for the `detectBot` middleware.
5
+ */
6
+ export type DetectBotOptions = {
7
+ /**
8
+ * 🤖 List of known bot-like User-Agent patterns.
9
+ * @default ["bot", "spider", "crawl", "slurp"]
10
+ * @example
11
+ * botUserAgents: ["bot", "crawler", "indexer"]
12
+ */
13
+ botUserAgents?: string[];
14
+ /**
15
+ * ⚖️ Enable rate-limiting based bot detection.
16
+ * Requires `getConnInfo()` import from TezX runtime.
17
+ * @default false
18
+ * @example
19
+ * enableRateLimiting: true
20
+ */
21
+ enableRateLimiting?: boolean;
22
+ /**
23
+ * ⚠️ Maximum allowed requests in the rate-limit window.
24
+ * Only used when `enableRateLimiting` is true.
25
+ * @default 30
26
+ */
27
+ maxRequests?: number;
28
+ /**
29
+ * ⏱️ Time window for rate-limiting (in milliseconds).
30
+ * @default 60000 (1 minute)
31
+ */
32
+ windowMs?: number;
33
+ /**
34
+ * 🔄 Custom rate-limit storage implementation.
35
+ * Allows integration with Redis, Memcached, or in-memory stores.
36
+ * @default In-memory Map
37
+ * @example
38
+ * storage: {
39
+ * get: (key) => myRedisClient.get(key),
40
+ * set: (key, value) => myRedisClient.set(key, value),
41
+ * clearExpired: () => {}
42
+ * }
43
+ */
44
+ storage?: {
45
+ get: (key: string) => {
46
+ count: number;
47
+ resetTime: number;
48
+ } | undefined;
49
+ set: (key: string, value: {
50
+ count: number;
51
+ resetTime: number;
52
+ }) => void;
53
+ clearExpired: () => void;
54
+ };
55
+ /**
56
+ * 🚫 Optional IP blacklist checker.
57
+ * Should return true if the current request’s IP is banned or suspicious.
58
+ * @default () => false
59
+ * @example
60
+ * isBlacklisted: (ctx) => ctx.req.remoteAddress.address === "192.168.0.10"
61
+ */
62
+ isBlacklisted?: (ctx: Context) => boolean | Promise<boolean>;
63
+ /**
64
+ * 🧠 Custom bot detector function.
65
+ * Use this for advanced heuristics (e.g., suspicious query params or headers).
66
+ * @example
67
+ * customBotDetector: (ctx) => ctx.query?.token === "weird"
68
+ */
69
+ customBotDetector?: (ctx: Context) => boolean | Promise<boolean>;
70
+ /**
71
+ * 🛡️ Action executed when a bot is detected.
72
+ * Can return a custom HTTP response (e.g., JSON, HTML, or redirect).
73
+ * @default Responds with 403 and JSON error.
74
+ * @example
75
+ * onBotDetected: (ctx, reason) => ctx.status(403).json({ error: `Blocked: ${reason}` })
76
+ */
77
+ onBotDetected?: (ctx: Context, reason: string) => HttpBaseResponse;
78
+ };
79
+ /**
80
+ * 🤖 Smart Bot Detection Middleware
81
+ *
82
+ * Detects automated or malicious requests using a combination of:
83
+ * - User-Agent analysis
84
+ * - IP blacklisting
85
+ * - Rate limiting
86
+ * - Custom detection logic
87
+ *
88
+ * 🧩 Supports:
89
+ * - Bun (uses precompiled RegExp for speed)
90
+ * - Node.js / Deno (optimized `includes()` loop)
91
+ *
92
+ * ⚙️ Requirements (for rate limiting):
93
+ * You must import `getConnInfo` from your runtime adapter:
94
+ * ```ts
95
+ * import { getConnInfo } from "tezx/bun";
96
+ * // or
97
+ * import { getConnInfo } from "tezx/node";
98
+ * // or
99
+ * import { getConnInfo } from "tezx/deno";
100
+ * ```
101
+ *
102
+ * 📦 Example Usage:
103
+ * ```ts
104
+ * import { detectBot } from "tezx/middleware/detectBot";
105
+ *
106
+ * app.use(detectBot({
107
+ * enableRateLimiting: true,
108
+ * botUserAgents: ["bot", "crawler"],
109
+ * onBotDetected: (ctx, reason) => ctx.status(403).json({ error: `Bot detected: ${reason}` })
110
+ * }));
111
+ * ```
112
+ */
113
+ export declare const detectBot: (opts?: DetectBotOptions) => Middleware;
@@ -0,0 +1,53 @@
1
+ import { GlobalConfig } from "../core/config.js";
2
+ import { createRateLimitDefaultStorage, isRateLimit, } from "../utils/rateLimit.js";
3
+ import { runtime } from "../utils/runtime.js";
4
+ export const detectBot = (opts = {}) => {
5
+ const botUAs = opts.botUserAgents || ["bot", "spider", "crawl", "slurp"];
6
+ let checkBot;
7
+ if (runtime === "bun") {
8
+ const botRegex = new RegExp(botUAs.join("|"), "i");
9
+ checkBot = (ua) => botRegex.test(ua);
10
+ }
11
+ else {
12
+ checkBot = (ua) => {
13
+ for (const b of botUAs)
14
+ if (ua.includes(b))
15
+ return true;
16
+ return false;
17
+ };
18
+ }
19
+ const maxReq = opts.maxRequests || 30;
20
+ const winMs = opts.windowMs || 60000;
21
+ const enableRL = !!opts.enableRateLimiting;
22
+ const onBot = opts.onBotDetected ??
23
+ ((ctx, reason) => ctx.status(403).json({ error: `Bot detected: ${reason}` }));
24
+ let store = opts.storage;
25
+ if (enableRL && !store)
26
+ store = createRateLimitDefaultStorage();
27
+ return async (ctx, next) => {
28
+ const ua = ctx.headers.get("user-agent") || "";
29
+ if (checkBot(ua)) {
30
+ return onBot?.(ctx, "User-Agent");
31
+ }
32
+ if (await opts?.isBlacklisted?.(ctx)) {
33
+ return onBot?.(ctx, "Blacklisted IP");
34
+ }
35
+ if (enableRL) {
36
+ const addr = ctx.req.remoteAddress;
37
+ if (!addr) {
38
+ GlobalConfig.debugging.warn("[TezX detectBot] Missing remoteAddress. Use `getConnInfo(ctx)` to enable rate limiting.");
39
+ }
40
+ else {
41
+ const key = `${addr.address}:${addr.port || 0}`;
42
+ const { check, entry } = isRateLimit(key, store, maxReq, winMs);
43
+ if (check && addr.address) {
44
+ return onBot?.(ctx, `Rate limit exceeded. Retry after ${Math.ceil((entry.resetTime - Date.now()) / 1000)} seconds.`);
45
+ }
46
+ }
47
+ }
48
+ if (await opts.customBotDetector?.(ctx)) {
49
+ return onBot?.(ctx, "Custom Detector");
50
+ }
51
+ return await next();
52
+ };
53
+ };
@@ -2,100 +2,193 @@ import { Context, Middleware } from "../index.js";
2
2
  export type TranslationMap = {
3
3
  [key: string]: string | TranslationMap;
4
4
  };
5
- export type loadTranslations = (language: string) => Promise<{
6
- translations: TranslationMap;
7
- expiresAt?: Date | number;
8
- }>;
9
- export type I18nOptions = {
5
+ export type loadTranslations = (language: string) => Promise<TranslationMap>;
6
+ /**
7
+ * 📦 Interface defining a pluggable cache adapter for i18n translations.
8
+ *
9
+ * You can implement this interface to provide custom caching logic —
10
+ * for example, in-memory, Redis, file-based, or distributed cache.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const redisCache: I18nCacheAdapter = {
15
+ * async get(lang) {
16
+ * const data = await redis.get(`i18n:${lang}`);
17
+ * return data ? JSON.parse(data) : null;
18
+ * },
19
+ * async set(lang, data) {
20
+ * await redis.set(`i18n:${lang}`, JSON.stringify(data), 'PX', data.expiresAt - Date.now());
21
+ * },
22
+ * async delete(lang) {
23
+ * await redis.del(`i18n:${lang}`);
24
+ * }
25
+ * };
26
+ * ```
27
+ */
28
+ export interface I18nCacheAdapter {
10
29
  /**
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
30
+ * Retrieve cached translations for a specific language.
31
+ *
32
+ * @param lang - The language code (e.g., `"en"`, `"bn"`, `"fr"`)
33
+ * @returns A Promise resolving to the cached translations object
34
+ * or `null` if not available or expired.
14
35
  */
15
- loadTranslations: (language: string) => Promise<{
36
+ get(lang: string): Promise<{
16
37
  translations: TranslationMap;
17
- expiresAt?: Date | number;
18
- }>;
19
- /**
20
- * ⏱️ Default cache duration in milliseconds
21
- * @default 3600000 (1 hour)
22
- */
23
- defaultCacheDuration?: number;
38
+ expiresAt: number;
39
+ } | null>;
24
40
  /**
25
- * 🔄 Function to check if cached translations are stale
26
- * @default Checks expiration time
27
- * @example
28
- * isCacheValid: (cached, lang) => {
29
- * return cached.expiresAt > Date.now() &&
30
- * cached.version === getCurrentVersion(lang);
31
- * }
41
+ * Store translations in cache for a specific language.
42
+ *
43
+ * @param lang - The language code (e.g., `"en"`, `"es"`)
44
+ * @param data - An object containing the translation map and expiration timestamp.
45
+ * @returns A Promise that resolves when caching is complete.
32
46
  */
33
- isCacheValid?: (cached: {
47
+ set(lang: string, data: {
34
48
  translations: TranslationMap;
35
49
  expiresAt: number;
36
- }, language: string) => boolean;
50
+ }): Promise<void>;
51
+ /**
52
+ * Remove cached translations for a specific language.
53
+ *
54
+ * @param lang - The language code to delete.
55
+ * @returns A Promise that resolves when deletion is complete.
56
+ */
57
+ delete(lang: string): Promise<void>;
58
+ }
59
+ /**
60
+ * ⚙️ Configuration options for the TezX i18n middleware.
61
+ *
62
+ * This configuration provides flexibility for:
63
+ * - dynamic translation loading
64
+ * - custom caching strategies
65
+ * - language detection logic
66
+ * - message formatting and interpolation
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * app.use(i18n({
71
+ * loadTranslations: lang => import(`./locales/${lang}.json`),
72
+ * detectLanguage: ctx => ctx.req.query.lang || 'en',
73
+ * cacheTranslations: true,
74
+ * cacheStorage: redisCache,
75
+ * defaultLanguage: 'en',
76
+ * formatMessage: (msg, vars) => msg.replace(/\{\{(\w+)\}\}/g, (_, k) => vars[k])
77
+ * }));
78
+ * ```
79
+ */
80
+ export type I18nOptions = {
81
+ /**
82
+ * 🌐 Function responsible for loading translation data dynamically.
83
+ *
84
+ * This can load translations from local JSON, database, API, etc.
85
+ *
86
+ * @param language - Language code to load (e.g., `"en"`, `"bn-BD"`)
87
+ * @returns Promise resolving to translation map for that language.
88
+ */
89
+ loadTranslations: loadTranslations;
90
+ /**
91
+ * ⏱️ Default cache duration in milliseconds.
92
+ *
93
+ * Defines how long translations remain valid before reloading.
94
+ *
95
+ * @default 3600000 (1 hour)
96
+ */
97
+ defaultCacheDuration?: number;
37
98
  /**
38
- * 🔍 Custom language detection function
39
- * @default Checks query param → cookie → Accept-Language header → default
99
+ * 🔍 Function to detect the preferred user language.
100
+ *
101
+ * Determines language priority from request context.
102
+ * Common strategies include:
103
+ * - URL query parameter
104
+ * - Cookie
105
+ * - HTTP `Accept-Language` header
106
+ *
40
107
  * @example
41
- * detectLanguage: (ctx) => ctx.cookies.get('user_lang') || 'en'
108
+ * detectLanguage: (ctx) => ctx.req.query.lang || ctx.cookies.lang || 'en'
42
109
  */
43
- detectLanguage?: (ctx: Context) => string;
110
+ detectLanguage: (ctx: Context) => string;
44
111
  /**
45
- * 🏠 Default fallback language
112
+ * 🏠 Default fallback language.
113
+ *
114
+ * Used when detected language translations are unavailable.
115
+ *
46
116
  * @default "en"
47
117
  */
48
118
  defaultLanguage?: string;
49
119
  /**
50
- * 🔀 Language fallback chain (most specific to least)
51
- * @example ["fr-CA", "fr", "en"] // Try Canadian French → French → English
52
- */
53
- fallbackChain?: string[];
54
- /**
55
- * 🗝️ Context property name for translation function
120
+ * 🗝️ Context property key where the translation function (`t`) is attached.
121
+ *
56
122
  * @default "t"
123
+ * @example
124
+ * ```ts
125
+ * ctx.t("greeting.hello");
126
+ * ctx.t("user.welcome", { name: "Rakibul" });
127
+ * ```
57
128
  */
58
129
  translationFunctionKey?: string;
59
130
  /**
60
- * 💬 Message formatting function
61
- * @default Basic template replacement ({{key}})
62
- * @example
63
- * formatMessage: (msg, vars) => {
64
- * return msg.replace(/\{(\w+)\}/g, (_, k) => vars[k]);
65
- * }
131
+ * 💬 Custom message formatting function.
132
+ *
133
+ * Handles variable interpolation (e.g., replacing `{{name}}` with actual value).
134
+ * You can override this for advanced templating logic.
135
+ *
136
+ * @param message - The base translation string.
137
+ * @param vars - Optional variable map for placeholders.
138
+ * @returns Formatted string with variables replaced.
139
+ *
140
+ * @default
141
+ * ```ts
142
+ * (msg, vars = {}) =>
143
+ * Object.entries(vars).reduce(
144
+ * (m, [k, v]) => m.replace(new RegExp(`{{${k}}}`, "g"), String(v)),
145
+ * msg
146
+ * );
147
+ * ```
66
148
  */
67
- formatMessage?: (message: string, options?: Record<string, any>) => string;
149
+ formatMessage?: (message: string, vars?: Record<string, any>) => string;
68
150
  /**
69
- * 🗃️ Cache loaded translations
70
- * @default true
151
+ * 🔒 Custom cache validation logic.
152
+ *
153
+ * Checks whether cached translations are still valid.
154
+ *
155
+ * @param cached - Cached object with translations and expiry timestamp.
156
+ * @param lang - The language being validated.
157
+ * @returns `true` if cache is valid, otherwise `false`.
158
+ *
159
+ * @default
160
+ * ```ts
161
+ * (cached) => cached.expiresAt > Date.now()
162
+ * ```
163
+ */
164
+ isCacheValid?: (cached: {
165
+ translations: TranslationMap;
166
+ expiresAt: number;
167
+ }, lang: string) => boolean;
168
+ /**
169
+ * 🗃️ Whether to enable caching for translations.
170
+ *
171
+ * If disabled, translations are always loaded fresh.
172
+ *
173
+ * @default false
71
174
  */
72
175
  cacheTranslations?: boolean;
176
+ /**
177
+ * 💾 Optional external cache adapter for distributed or persistent caching.
178
+ *
179
+ * Supports any system implementing `I18nCacheAdapter` interface —
180
+ * such as Redis, filesystem, in-memory store, or custom backends.
181
+ *
182
+ * @example
183
+ * ```ts
184
+ * app.use(i18n({
185
+ * loadTranslations,
186
+ * cacheTranslations: true,
187
+ * cacheStorage: redisCache
188
+ * }));
189
+ * ```
190
+ */
191
+ cacheStorage?: I18nCacheAdapter;
73
192
  };
74
- /**
75
- * 🌍 Advanced i18n middleware with dynamic loading and fallback support
76
- *
77
- * Features:
78
- * - Hierarchical language resolution
79
- * - Nested translation keys
80
- * - Template interpolation
81
- * - Optional caching
82
- * - Custom language detection
83
- *
84
- * @param {I18nOptions} options - Configuration options
85
- * @returns {Middleware} Configured middleware
86
- *
87
- * @example
88
- * // Basic usage
89
- * app.use(i18n({
90
- * loadTranslations: lang => import(`./locales/${lang}.json`),
91
- * defaultLanguage: 'en'
92
- * }));
93
- *
94
- * // With caching and custom detection
95
- * app.use(i18n({
96
- * loadTranslations: fetchTranslations,
97
- * detectLanguage: ctx => ctx.get('X-Language'),
98
- * cacheTranslations: true
99
- * }));
100
- */
101
- export declare const i18n: (options: I18nOptions) => Middleware;
193
+ declare const i18n: (options: I18nOptions) => Middleware;
194
+ export { i18n as default, i18n };