tezx 3.0.14-beta → 3.0.14-beta2

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 (102) hide show
  1. package/bun/env.d.ts +5 -0
  2. package/bun/env.js +44 -0
  3. package/bun/index.d.ts +1 -1
  4. package/bun/index.js +1 -1
  5. package/bun/ws.js +4 -3
  6. package/cjs/bun/env.js +47 -0
  7. package/cjs/bun/index.js +1 -1
  8. package/cjs/bun/ws.js +4 -3
  9. package/cjs/core/context.js +4 -3
  10. package/cjs/core/error.js +8 -0
  11. package/cjs/core/request.js +3 -2
  12. package/cjs/core/router.js +11 -17
  13. package/cjs/core/server.js +14 -9
  14. package/cjs/deno/env.js +2 -1
  15. package/cjs/deno/serveStatic.js +2 -1
  16. package/cjs/deno/ws.js +4 -3
  17. package/cjs/helper/index.js +12 -10
  18. package/cjs/index.js +1 -1
  19. package/cjs/jwt/node.js +94 -0
  20. package/cjs/jwt/web.js +178 -0
  21. package/cjs/middleware/basic-auth.js +9 -14
  22. package/cjs/middleware/bearer-auth.js +5 -5
  23. package/cjs/middleware/cache-control.js +44 -0
  24. package/cjs/middleware/cors.js +1 -1
  25. package/cjs/middleware/detect-bot.js +57 -0
  26. package/cjs/middleware/i18n.js +92 -0
  27. package/cjs/middleware/index.js +5 -0
  28. package/cjs/middleware/logger.js +3 -2
  29. package/cjs/middleware/rate-limiter.js +1 -1
  30. package/cjs/middleware/sanitize-headers.js +1 -1
  31. package/cjs/middleware/secure-headers copy.js +143 -0
  32. package/cjs/middleware/secure-headers.js +157 -0
  33. package/cjs/node/env.js +4 -3
  34. package/cjs/node/serveStatic.js +2 -1
  35. package/cjs/node/ws.js +3 -2
  36. package/cjs/registry/RadixRouter.js +2 -33
  37. package/cjs/utils/buffer.js +17 -0
  38. package/cjs/utils/file.js +28 -6
  39. package/cjs/utils/generateID.js +10 -0
  40. package/cjs/utils/response.js +3 -1
  41. package/core/context.d.ts +3 -3
  42. package/core/context.js +4 -3
  43. package/core/error.d.ts +1 -0
  44. package/core/error.js +7 -0
  45. package/core/request.js +3 -2
  46. package/core/router.d.ts +3 -8
  47. package/core/router.js +11 -17
  48. package/core/server.d.ts +10 -23
  49. package/core/server.js +15 -10
  50. package/deno/env.js +2 -1
  51. package/deno/index.d.ts +1 -1
  52. package/deno/serveStatic.js +2 -1
  53. package/deno/ws.d.ts +1 -1
  54. package/deno/ws.js +4 -3
  55. package/helper/index.d.ts +7 -6
  56. package/helper/index.js +7 -6
  57. package/index.d.ts +5 -2
  58. package/index.js +1 -1
  59. package/jwt/node.d.ts +39 -0
  60. package/jwt/node.js +87 -0
  61. package/jwt/web.d.ts +14 -0
  62. package/jwt/web.js +174 -0
  63. package/middleware/basic-auth.d.ts +2 -1
  64. package/middleware/basic-auth.js +9 -14
  65. package/middleware/bearer-auth.d.ts +2 -1
  66. package/middleware/bearer-auth.js +5 -5
  67. package/middleware/cache-control.d.ts +30 -0
  68. package/middleware/cache-control.js +40 -0
  69. package/middleware/cors.js +1 -1
  70. package/middleware/detect-bot.d.ts +113 -0
  71. package/middleware/detect-bot.js +53 -0
  72. package/middleware/i18n.d.ts +194 -0
  73. package/middleware/i18n.js +88 -0
  74. package/middleware/index.d.ts +5 -0
  75. package/middleware/index.js +5 -0
  76. package/middleware/logger.d.ts +1 -1
  77. package/middleware/logger.js +3 -2
  78. package/middleware/rate-limiter.d.ts +3 -2
  79. package/middleware/rate-limiter.js +1 -1
  80. package/middleware/sanitize-headers.js +1 -1
  81. package/middleware/secure-headers copy.d.ts +15 -0
  82. package/middleware/secure-headers copy.js +136 -0
  83. package/middleware/secure-headers.d.ts +132 -0
  84. package/middleware/secure-headers.js +153 -0
  85. package/node/env.js +4 -3
  86. package/node/serveStatic.d.ts +8 -0
  87. package/node/serveStatic.js +2 -1
  88. package/node/ws.d.ts +1 -1
  89. package/node/ws.js +3 -2
  90. package/package.json +12 -1
  91. package/registry/RadixRouter.js +2 -33
  92. package/types/index.d.ts +1 -1
  93. package/utils/buffer.d.ts +1 -0
  94. package/utils/buffer.js +14 -0
  95. package/utils/file.d.ts +6 -2
  96. package/utils/file.js +27 -6
  97. package/utils/generateID.d.ts +9 -0
  98. package/utils/generateID.js +9 -0
  99. package/utils/response.js +3 -1
  100. package/cjs/utils/regexRouter.js +0 -57
  101. package/utils/regexRouter.d.ts +0 -66
  102. package/utils/regexRouter.js +0 -52
@@ -1,3 +1,4 @@
1
+ import { TezXError, TezXErrorParse } from "../core/error.js";
1
2
  export const bearerAuth = (options) => {
2
3
  const { validate, realm = "API", onUnauthorized = (ctx, error) => {
3
4
  ctx.setStatus = 401;
@@ -7,23 +8,22 @@ export const bearerAuth = (options) => {
7
8
  return async (ctx, next) => {
8
9
  const auth = ctx.req.header("authorization");
9
10
  if (!auth || !auth.startsWith("Bearer ")) {
10
- return onUnauthorized(ctx, new Error("Bearer token required"));
11
+ return onUnauthorized(ctx, new TezXError("Bearer token required"));
11
12
  }
12
13
  const token = auth.slice(7).trim();
13
14
  if (!token) {
14
- return onUnauthorized(ctx, new Error("Empty token"));
15
+ return onUnauthorized(ctx, new TezXError("Empty token"));
15
16
  }
16
17
  try {
17
18
  const valid = await validate(token, ctx);
18
19
  if (!valid) {
19
- return onUnauthorized(ctx, new Error("Invalid or expired token"));
20
+ return onUnauthorized(ctx, new TezXError("Invalid or expired token"));
20
21
  }
21
22
  ctx.token = token;
22
23
  await next();
23
24
  }
24
25
  catch (err) {
25
- const error = err instanceof Error ? err : new Error(String(err));
26
- return onUnauthorized(ctx, error);
26
+ return onUnauthorized(ctx, TezXErrorParse(err));
27
27
  }
28
28
  };
29
29
  };
@@ -0,0 +1,30 @@
1
+ import { Context } from "../core/context.js";
2
+ import { TezXError } from "../core/error.js";
3
+ import { HttpBaseResponse, Middleware } from "../types/index.js";
4
+ export interface CacheRule {
5
+ /** 🎯 Condition to determine if this rule applies */
6
+ condition: (ctx: Context) => boolean;
7
+ /** ⏳ Max age (seconds) */
8
+ maxAge: number;
9
+ /** 🌐 Cache scope */
10
+ scope: "public" | "private";
11
+ /** 🏷️ Vary header list */
12
+ vary?: string[];
13
+ }
14
+ export interface CacheSettings extends Pick<CacheRule, "maxAge" | "scope" | "vary"> {
15
+ }
16
+ export interface CacheOptions {
17
+ /** 🧱 Default cache behavior */
18
+ defaultSettings: CacheSettings;
19
+ /** 🔧 Optional rules for dynamic caching */
20
+ rules?: readonly CacheRule[];
21
+ /** 📝 Logging hook */
22
+ /** 🚨 Error handler */
23
+ onError?: (error: TezXError, ctx: Context) => HttpBaseResponse;
24
+ }
25
+ /**
26
+ * ⚡ Ultra-lightweight, low-level cache control middleware.
27
+ * Adds 'Cache-Control', 'Expires', and optional 'Vary' headers.
28
+ */
29
+ export declare const cacheControl: (opts: CacheOptions) => Middleware;
30
+ export default cacheControl;
@@ -0,0 +1,40 @@
1
+ import { TezXErrorParse } from "../core/error.js";
2
+ export const cacheControl = (opts) => {
3
+ const { defaultSettings, rules = [], onError = (err, ctx) => {
4
+ ctx.setStatus = 500;
5
+ ctx.body = { error: err.message ?? "Cache middleware failed" };
6
+ }, } = opts;
7
+ const len = rules.length | 0;
8
+ return async function cacheControlMiddleware(ctx, next) {
9
+ const method = ctx.method;
10
+ if (method !== "GET" && method !== "HEAD") {
11
+ return await next();
12
+ }
13
+ try {
14
+ await next();
15
+ let matched;
16
+ for (let i = 0; i < len; i++) {
17
+ const rule = rules[i];
18
+ if (rule.condition(ctx)) {
19
+ matched = rule;
20
+ break;
21
+ }
22
+ }
23
+ const settings = matched ?? defaultSettings;
24
+ const maxAge = settings.maxAge;
25
+ const scope = settings.scope;
26
+ const vary = settings.vary;
27
+ const cacheValue = `${scope}, max-age=${maxAge}`;
28
+ const expiresValue = new Date(Date.now() + maxAge * 1000).toUTCString();
29
+ const headers = ctx.headers;
30
+ headers.set("Cache-Control", cacheValue);
31
+ headers.set("Expires", expiresValue);
32
+ if (vary && vary.length > 0)
33
+ headers.set("Vary", vary.join(", "));
34
+ }
35
+ catch (err) {
36
+ return onError(TezXErrorParse(err), ctx);
37
+ }
38
+ };
39
+ };
40
+ export default cacheControl;
@@ -1,5 +1,5 @@
1
1
  function cors(option = {}) {
2
- const { credentials, maxAge, origin, } = option;
2
+ const { credentials, maxAge, origin } = option;
3
3
  let methods = (option.methods || ["GET", "POST", "PUT", "DELETE"]).join(", ");
4
4
  let allowedHeaders = (option.allowedHeaders || ["Content-Type", "Authorization"]).join(", ");
5
5
  let exposedHeaders = option?.exposedHeaders?.join(", ");
@@ -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
+ };
@@ -0,0 +1,194 @@
1
+ import { Context, Middleware } from "../index.js";
2
+ export type TranslationMap = {
3
+ [key: string]: string | TranslationMap;
4
+ };
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 {
29
+ /**
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.
35
+ */
36
+ get(lang: string): Promise<{
37
+ translations: TranslationMap;
38
+ expiresAt: number;
39
+ } | null>;
40
+ /**
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.
46
+ */
47
+ set(lang: string, data: {
48
+ translations: TranslationMap;
49
+ expiresAt: number;
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;
98
+ /**
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
+ *
107
+ * @example
108
+ * detectLanguage: (ctx) => ctx.req.query.lang || ctx.cookies.lang || 'en'
109
+ */
110
+ detectLanguage: (ctx: Context) => string;
111
+ /**
112
+ * 🏠 Default fallback language.
113
+ *
114
+ * Used when detected language translations are unavailable.
115
+ *
116
+ * @default "en"
117
+ */
118
+ defaultLanguage?: string;
119
+ /**
120
+ * 🗝️ Context property key where the translation function (`t`) is attached.
121
+ *
122
+ * @default "t"
123
+ * @example
124
+ * ```ts
125
+ * ctx.t("greeting.hello");
126
+ * ctx.t("user.welcome", { name: "Rakibul" });
127
+ * ```
128
+ */
129
+ translationFunctionKey?: string;
130
+ /**
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
+ * ```
148
+ */
149
+ formatMessage?: (message: string, vars?: Record<string, any>) => string;
150
+ /**
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
174
+ */
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;
192
+ };
193
+ declare const i18n: (options: I18nOptions) => Middleware;
194
+ export { i18n as default, i18n };
@@ -0,0 +1,88 @@
1
+ import { TezXError } from "../index.js";
2
+ const i18n = (options) => {
3
+ const { loadTranslations, defaultCacheDuration = 3600000, detectLanguage, defaultLanguage = "en", translationFunctionKey = "t", formatMessage = (msg, vars = {}) => {
4
+ if (vars && msg.indexOf("{{") !== -1) {
5
+ let out = "";
6
+ let i = 0;
7
+ while (i < msg.length) {
8
+ const startVar = msg.indexOf("{{", i);
9
+ if (startVar === -1) {
10
+ out += msg.slice(i);
11
+ break;
12
+ }
13
+ const endVar = msg.indexOf("}}", startVar + 2);
14
+ if (endVar === -1) {
15
+ out += msg.slice(i);
16
+ break;
17
+ }
18
+ out += msg.slice(i, startVar);
19
+ const keyVar = msg.slice(startVar + 2, endVar).trim();
20
+ out += vars[keyVar] !== undefined ? String(vars[keyVar]) : "";
21
+ i = endVar + 2;
22
+ }
23
+ return out;
24
+ }
25
+ return msg;
26
+ }, isCacheValid = (cached) => cached.expiresAt > Date.now(), cacheTranslations = false, cacheStorage: externalCache = null, } = options;
27
+ let localCache = null;
28
+ if (cacheTranslations && !externalCache)
29
+ localCache = new Map();
30
+ return async function i18n(ctx, next) {
31
+ try {
32
+ const lang = detectLanguage(ctx) ?? defaultLanguage;
33
+ let translations = undefined;
34
+ if (cacheTranslations) {
35
+ if (externalCache) {
36
+ const cached = await externalCache.get(lang);
37
+ if (cached && isCacheValid(cached, lang))
38
+ translations = cached.translations;
39
+ }
40
+ else if (localCache?.get(lang)) {
41
+ const cached = localCache.get(lang);
42
+ if (isCacheValid(cached, lang))
43
+ translations = cached.translations;
44
+ else
45
+ localCache.delete(lang);
46
+ }
47
+ }
48
+ if (!translations) {
49
+ translations = await loadTranslations(lang);
50
+ const expiresAt = Date.now() + defaultCacheDuration;
51
+ if (cacheTranslations) {
52
+ if (externalCache) {
53
+ await externalCache.set(lang, { translations, expiresAt });
54
+ }
55
+ else {
56
+ localCache?.set(lang, { translations, expiresAt });
57
+ }
58
+ }
59
+ }
60
+ ctx[translationFunctionKey] = function translate(key, vars) {
61
+ let acc = translations;
62
+ let start = 0;
63
+ for (let i = 0; i <= key.length; i++) {
64
+ const c = key.charCodeAt(i);
65
+ if (c === 46 || i === key.length) {
66
+ const segment = key.slice(start, i);
67
+ if (acc && typeof acc === "object") {
68
+ acc = acc[segment];
69
+ }
70
+ else {
71
+ acc = undefined;
72
+ break;
73
+ }
74
+ start = i + 1;
75
+ }
76
+ }
77
+ let msg = typeof acc === "string" ? acc : key;
78
+ return formatMessage(msg, vars);
79
+ };
80
+ ctx.language = lang;
81
+ return await next();
82
+ }
83
+ catch (error) {
84
+ throw new TezXError(error?.message ?? "i18n failed", 500, error?.stack);
85
+ }
86
+ };
87
+ };
88
+ export { i18n as default, i18n };
@@ -1,9 +1,14 @@
1
1
  export * from "./basic-auth.js";
2
2
  export * from "./bearer-auth.js";
3
+ export * from "./cache-control.js";
3
4
  export * from "./cors.js";
5
+ export * from "./detect-bot.js";
6
+ export * from "./i18n.js";
4
7
  export * from "./logger.js";
5
8
  export * from "./pagination.js";
6
9
  export * from "./powered-by.js";
10
+ export * from "./rate-limiter.js";
7
11
  export * from "./request-id.js";
8
12
  export * from "./sanitize-headers.js";
13
+ export * from "./secure-headers.js";
9
14
  export * from "./xss-protection.js";
@@ -1,9 +1,14 @@
1
1
  export * from "./basic-auth.js";
2
2
  export * from "./bearer-auth.js";
3
+ export * from "./cache-control.js";
3
4
  export * from "./cors.js";
5
+ export * from "./detect-bot.js";
6
+ export * from "./i18n.js";
4
7
  export * from "./logger.js";
5
8
  export * from "./pagination.js";
6
9
  export * from "./powered-by.js";
10
+ export * from "./rate-limiter.js";
7
11
  export * from "./request-id.js";
8
12
  export * from "./sanitize-headers.js";
13
+ export * from "./secure-headers.js";
9
14
  export * from "./xss-protection.js";
@@ -15,4 +15,4 @@ import { Middleware } from "../types/index.js";
15
15
  declare function logger(options?: {
16
16
  enabled: boolean;
17
17
  }): Middleware;
18
- export { logger, logger as default };
18
+ export { logger as default, logger };
@@ -1,3 +1,4 @@
1
+ import { TezXError } from "../core/error.js";
1
2
  import { colorText } from "../utils/colors.js";
2
3
  function logger(options = { enabled: true }) {
3
4
  return async function logger(ctx, next) {
@@ -15,8 +16,8 @@ function logger(options = { enabled: true }) {
15
16
  }
16
17
  catch (err) {
17
18
  console.error(`${colorText("Error:", "red")}`, err.stack);
18
- throw new Error(err.stack);
19
+ throw new TezXError(err.stack);
19
20
  }
20
21
  };
21
22
  }
22
- export { logger, logger as default };
23
+ export { logger as default, logger };
@@ -1,4 +1,5 @@
1
1
  import { Context } from "../core/context.js";
2
+ import { TezXError } from "../core/error.js";
2
3
  import { HttpBaseResponse, Middleware } from "../types/index.js";
3
4
  export type RateLimiterOptions = {
4
5
  /**
@@ -41,10 +42,10 @@ export type RateLimiterOptions = {
41
42
  * @example
42
43
  * onError: (ctx, retryAfter) => {
43
44
  * ctx.status = 429;
44
- * throw new Error( `Rate limit exceeded. Try again in ${retryAfter} seconds.`);
45
+ * throw new TezXError( `Rate limit exceeded. Try again in ${retryAfter} seconds.`);
45
46
  * }
46
47
  */
47
- onError?: (ctx: Context, retryAfter: number, error: Error) => HttpBaseResponse;
48
+ onError?: (ctx: Context, retryAfter: number, error: TezXError) => HttpBaseResponse;
48
49
  };
49
50
  /**
50
51
  * 🚦 Rate limiting middleware for request throttling
@@ -23,7 +23,7 @@ const rateLimiter = (options) => {
23
23
  if (check) {
24
24
  const retryAfter = Math.ceil((entry.resetTime - Date.now()) / 1000);
25
25
  ctx.headers.set("Retry-After", retryAfter.toString());
26
- return onError(ctx, retryAfter, new Error(`Rate limit exceeded. Retry after ${retryAfter} seconds.`));
26
+ return onError(ctx, retryAfter, new TezXError(`Rate limit exceeded. Retry after ${retryAfter} seconds.`));
27
27
  }
28
28
  ctx.headers.set("X-RateLimit-Limit", maxRequests.toString());
29
29
  ctx.headers.set("X-RateLimit-Remaining", (maxRequests - entry.count).toString());
@@ -1,5 +1,5 @@
1
1
  const sanitizeHeaders = (options = {}) => {
2
- const { whitelist = [], blacklist = [], } = options;
2
+ const { whitelist = [], blacklist = [] } = options;
3
3
  const normalizedWhitelist = whitelist.map((h) => h.toLowerCase());
4
4
  const normalizedBlacklist = blacklist.map((h) => h.toLowerCase());
5
5
  let lWhite = normalizedWhitelist.length;
@@ -0,0 +1,15 @@
1
+ export type SecureHeadersOptions = {
2
+ preset?: "strict" | "balanced" | "dev";
3
+ hsts?: boolean;
4
+ hstsMaxAge?: number;
5
+ frameGuard?: "DENY" | "SAMEORIGIN" | string;
6
+ noSniff?: boolean;
7
+ xssProtection?: boolean;
8
+ referrerPolicy?: string;
9
+ permissionsPolicy?: string;
10
+ csp?: string | Record<string, string | string[]>;
11
+ cspReportOnly?: boolean;
12
+ cspUseNonce?: boolean;
13
+ ultraFastMode?: boolean;
14
+ };
15
+ export declare const secureHeaders: <T extends Record<string, any> = {}, Path extends string = any>(userOpts?: SecureHeadersOptions) => (ctx: any, next: () => Promise<any>) => Promise<any>;