tezx 1.0.73 → 1.0.75-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/adapter/bun.d.ts +29 -0
- package/adapter/deno.d.ts +30 -0
- package/adapter/index.d.ts +1 -1
- package/adapter/index.js +1 -1
- package/adapter/node/index.d.ts +47 -0
- package/adapter/{node.js → node/index.js} +2 -2
- package/cjs/adapter/index.js +1 -1
- package/cjs/adapter/{node.js → node/index.js} +2 -2
- package/cjs/core/context.js +13 -97
- package/cjs/core/environment.js +0 -8
- package/cjs/core/request.js +69 -25
- package/cjs/core/router.js +5 -0
- package/cjs/core/server.js +6 -5
- package/cjs/index.js +1 -1
- package/cjs/middleware/basicAuth.js +1 -1
- package/cjs/middleware/cacheControl.js +1 -1
- package/cjs/middleware/cors.js +2 -2
- package/cjs/middleware/detectBot.js +1 -1
- package/cjs/middleware/detectLocale.js +1 -1
- package/cjs/middleware/{i18nMiddleware.js → i18n.js} +4 -4
- package/cjs/middleware/index.js +1 -1
- package/cjs/middleware/lazyLoadModules.js +1 -1
- package/cjs/middleware/logger.js +1 -1
- package/cjs/middleware/pagination.js +1 -1
- package/cjs/middleware/powered-by.js +1 -1
- package/cjs/middleware/rateLimiter.js +1 -1
- package/cjs/middleware/request-id.js +1 -1
- package/cjs/middleware/requestTimeout.js +1 -1
- package/cjs/middleware/sanitizeHeader.js +3 -3
- package/cjs/middleware/secureHeaders.js +1 -1
- package/cjs/middleware/xssProtection.js +1 -1
- package/cjs/utils/formData.js +0 -235
- package/cjs/utils/httpStatusMap.js +68 -0
- package/cjs/utils/staticFile.js +4 -1
- package/cjs/utils/toWebRequest.js +35 -0
- package/core/context.d.ts +5 -6
- package/core/context.js +14 -98
- package/core/environment.d.ts +0 -2
- package/core/environment.js +0 -8
- package/core/request.d.ts +11 -32
- package/core/request.js +70 -26
- package/core/router.d.ts +30 -0
- package/core/router.js +5 -0
- package/core/server.js +5 -4
- package/index.js +1 -1
- package/middleware/basicAuth.js +1 -1
- package/middleware/cacheControl.js +1 -1
- package/middleware/cors.js +2 -2
- package/middleware/detectBot.js +1 -1
- package/middleware/detectLocale.js +1 -1
- package/middleware/{i18nMiddleware.d.ts → i18n.d.ts} +3 -3
- package/middleware/{i18nMiddleware.js → i18n.js} +2 -2
- package/middleware/index.d.ts +1 -1
- package/middleware/index.js +1 -1
- package/middleware/lazyLoadModules.js +1 -1
- package/middleware/logger.js +1 -1
- package/middleware/pagination.js +1 -1
- package/middleware/powered-by.js +1 -1
- package/middleware/rateLimiter.js +1 -1
- package/middleware/request-id.js +1 -1
- package/middleware/requestTimeout.js +1 -1
- package/middleware/sanitizeHeader.js +3 -3
- package/middleware/secureHeaders.js +1 -1
- package/middleware/xssProtection.js +1 -1
- package/package.json +1 -1
- package/utils/formData.d.ts +0 -5
- package/utils/formData.js +0 -231
- package/utils/httpStatusMap.d.ts +1 -0
- package/utils/httpStatusMap.js +65 -0
- package/utils/staticFile.js +4 -1
- package/utils/toWebRequest.d.ts +11 -0
- package/utils/toWebRequest.js +32 -0
- package/adapter/node.d.ts +0 -19
- package/cjs/core/header.js +0 -92
- package/core/header.d.ts +0 -77
- package/core/header.js +0 -88
package/core/request.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { UrlRef } from "../utils/url.js";
|
|
2
|
-
import { HeadersParser } from "./header.js";
|
|
3
2
|
import { TezXServeOptions } from "./server.js";
|
|
4
3
|
export type FormDataOptions = {
|
|
5
4
|
maxSize?: number;
|
|
@@ -20,7 +19,6 @@ export type ConnAddress = {
|
|
|
20
19
|
};
|
|
21
20
|
export type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE" | "OPTIONS" | "PATCH" | "HEAD" | "ALL" | "TRACE" | "CONNECT" | string;
|
|
22
21
|
export declare class Request {
|
|
23
|
-
#private;
|
|
24
22
|
/**
|
|
25
23
|
* Full request URL including protocol and query string
|
|
26
24
|
* @type {string}
|
|
@@ -57,31 +55,21 @@ export declare class Request {
|
|
|
57
55
|
* ```
|
|
58
56
|
*/
|
|
59
57
|
remoteAddress: NetAddr;
|
|
60
|
-
constructor({
|
|
58
|
+
constructor({ params, method, req, options, }: {
|
|
59
|
+
method: string;
|
|
61
60
|
req: any;
|
|
62
61
|
params: Record<string, any>;
|
|
63
|
-
headers: HeadersParser;
|
|
64
|
-
urlRef: UrlRef;
|
|
65
62
|
options: TezXServeOptions;
|
|
66
63
|
});
|
|
67
64
|
get headers(): {
|
|
68
65
|
/**
|
|
69
66
|
* Retrieves the first value of a specific header.
|
|
70
67
|
* @param key - Header name to search for.
|
|
71
|
-
* @returns The first header value or
|
|
68
|
+
* @returns The first header value or null if not found.
|
|
72
69
|
* @example
|
|
73
70
|
* get('content-type') // returns 'application/json'
|
|
74
71
|
*/
|
|
75
|
-
get: (key: string) => string |
|
|
76
|
-
/**
|
|
77
|
-
* Retrieves all values of a specific header.
|
|
78
|
-
* If multiple values exist for a header, all will be returned as an array.
|
|
79
|
-
* @param key - Header name to search for.
|
|
80
|
-
* @returns An array of all header values associated with the key.
|
|
81
|
-
* @example
|
|
82
|
-
* getAll('accept-language') // returns ['en-US', 'fr-CA']
|
|
83
|
-
*/
|
|
84
|
-
getAll: (key: string) => string | never[];
|
|
72
|
+
get: (key: string) => string | null;
|
|
85
73
|
/**
|
|
86
74
|
* Checks if a header exists in the request.
|
|
87
75
|
* @param key - Header name to check for existence.
|
|
@@ -93,13 +81,13 @@ export declare class Request {
|
|
|
93
81
|
/**
|
|
94
82
|
* Returns an iterator over all header entries.
|
|
95
83
|
* Each entry is a [key, value] pair where the value can be an array of strings.
|
|
96
|
-
* @returns
|
|
84
|
+
* @returns HeadersIterator for iterating over header key-value pairs.
|
|
97
85
|
* @example
|
|
98
86
|
* for (let [key, value] of headers.entries()) {
|
|
99
87
|
* console.log(key, value);
|
|
100
88
|
* }
|
|
101
89
|
*/
|
|
102
|
-
entries: () =>
|
|
90
|
+
entries: () => HeadersIterator<[string, string]>;
|
|
103
91
|
/**
|
|
104
92
|
* Returns an iterator over all header keys.
|
|
105
93
|
* This allows iteration over the names of all headers in the request.
|
|
@@ -113,13 +101,13 @@ export declare class Request {
|
|
|
113
101
|
/**
|
|
114
102
|
* Returns an iterator over all header values.
|
|
115
103
|
* This allows iteration over the values of all headers, with each value being an array of strings.
|
|
116
|
-
* @returns
|
|
104
|
+
* @returns HeadersIterator<string> of header values.
|
|
117
105
|
* @example
|
|
118
106
|
* for (let value of headers.values()) {
|
|
119
107
|
* console.log(value);
|
|
120
108
|
* }
|
|
121
109
|
*/
|
|
122
|
-
values: () =>
|
|
110
|
+
values: () => HeadersIterator<string>;
|
|
123
111
|
/**
|
|
124
112
|
* Iterates over each header and executes a callback for every header found.
|
|
125
113
|
* @param callback - Function to execute for each header. Receives the value array and key.
|
|
@@ -128,34 +116,25 @@ export declare class Request {
|
|
|
128
116
|
* console.log(key, value);
|
|
129
117
|
* });
|
|
130
118
|
*/
|
|
131
|
-
forEach: (
|
|
119
|
+
forEach: (callbackfn: (value: string, key: string, parent: Headers) => void) => void;
|
|
132
120
|
/**
|
|
133
121
|
* Converts headers to a JSON-safe plain object (only single string values).
|
|
134
122
|
* Multi-value headers are joined by commas.
|
|
135
123
|
* @returns A record of headers with string values.
|
|
136
124
|
*/
|
|
137
125
|
toJSON(): Record<string, string>;
|
|
138
|
-
/**
|
|
139
|
-
* Converts all headers into a plain JavaScript object.
|
|
140
|
-
* Single-value headers are represented as a string, and multi-value headers as an array.
|
|
141
|
-
* @returns A plain object with header names as keys and their values as strings or arrays.
|
|
142
|
-
* @example
|
|
143
|
-
* const headersObject = headers.toObject();
|
|
144
|
-
* console.log(headersObject);
|
|
145
|
-
*/
|
|
146
|
-
toObject: () => Record<string, string | string[]>;
|
|
147
126
|
};
|
|
148
127
|
/**
|
|
149
128
|
* Parses the request body as plain text.
|
|
150
129
|
* @returns {Promise<string>} The text content of the request body.
|
|
151
130
|
*/
|
|
152
|
-
text(): Promise<
|
|
131
|
+
text(): Promise<any>;
|
|
153
132
|
/**
|
|
154
133
|
* Parses the request body as JSON.
|
|
155
134
|
* @returns {Promise<Record<string, any>>} The parsed JSON object.
|
|
156
135
|
* If the Content-Type is not 'application/json', it returns an empty object.
|
|
157
136
|
*/
|
|
158
|
-
json(): Promise<
|
|
137
|
+
json(): Promise<any>;
|
|
159
138
|
/**
|
|
160
139
|
* Parses the request body based on Content-Type.
|
|
161
140
|
* Supports:
|
package/core/request.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { sanitized } from "../utils/formData.js";
|
|
2
|
+
import { urlParse } from "../utils/url.js";
|
|
3
3
|
export class Request {
|
|
4
|
-
#headers = new HeadersParser();
|
|
5
4
|
url;
|
|
6
5
|
method;
|
|
7
6
|
urlRef = {
|
|
@@ -17,25 +16,23 @@ export class Request {
|
|
|
17
16
|
rawRequest;
|
|
18
17
|
params = {};
|
|
19
18
|
remoteAddress = {};
|
|
20
|
-
constructor({
|
|
19
|
+
constructor({ params, method, req, options, }) {
|
|
20
|
+
let parse = urlParse(req.url);
|
|
21
|
+
let url = parse?.href;
|
|
21
22
|
this.remoteAddress = options?.connInfo?.remoteAddr;
|
|
22
|
-
this
|
|
23
|
-
this.
|
|
24
|
-
this.
|
|
25
|
-
this.method = req?.method?.toUpperCase();
|
|
23
|
+
this.url = url || "";
|
|
24
|
+
this.urlRef = parse;
|
|
25
|
+
this.method = method;
|
|
26
26
|
this.params = params;
|
|
27
27
|
this.rawRequest = req;
|
|
28
|
-
this.query =
|
|
28
|
+
this.query = parse.query;
|
|
29
29
|
}
|
|
30
30
|
get headers() {
|
|
31
|
-
let requestHeaders = this
|
|
31
|
+
let requestHeaders = this.rawRequest.headers;
|
|
32
32
|
return {
|
|
33
33
|
get: function get(key) {
|
|
34
34
|
return requestHeaders.get(key.toLowerCase());
|
|
35
35
|
},
|
|
36
|
-
getAll: function getAll(key) {
|
|
37
|
-
return requestHeaders.get(key.toLowerCase()) || [];
|
|
38
|
-
},
|
|
39
36
|
has: function has(key) {
|
|
40
37
|
return requestHeaders.has(key.toLowerCase());
|
|
41
38
|
},
|
|
@@ -48,47 +45,94 @@ export class Request {
|
|
|
48
45
|
values: function values() {
|
|
49
46
|
return requestHeaders.values();
|
|
50
47
|
},
|
|
51
|
-
forEach: function forEach(
|
|
52
|
-
return requestHeaders.forEach(
|
|
48
|
+
forEach: function forEach(callbackfn) {
|
|
49
|
+
return requestHeaders.forEach(callbackfn);
|
|
53
50
|
},
|
|
54
51
|
toJSON() {
|
|
55
52
|
return requestHeaders.toJSON();
|
|
56
53
|
},
|
|
57
|
-
toObject: function toObject() {
|
|
58
|
-
return requestHeaders.toObject();
|
|
59
|
-
},
|
|
60
54
|
};
|
|
61
55
|
}
|
|
62
56
|
async text() {
|
|
63
|
-
return await
|
|
57
|
+
return await this.rawRequest.text();
|
|
64
58
|
}
|
|
65
59
|
async json() {
|
|
66
|
-
const contentType = this
|
|
60
|
+
const contentType = this.rawRequest.headers.get("content-type") || "";
|
|
67
61
|
if (contentType.includes("application/json")) {
|
|
68
|
-
return await
|
|
62
|
+
return await this.rawRequest.json();
|
|
69
63
|
}
|
|
70
64
|
else {
|
|
71
65
|
return {};
|
|
72
66
|
}
|
|
73
67
|
}
|
|
74
68
|
async formData(options) {
|
|
75
|
-
const contentType = this
|
|
69
|
+
const contentType = this.rawRequest.headers.get("content-type") || "";
|
|
76
70
|
if (!contentType) {
|
|
77
71
|
throw Error("Invalid Content-Type");
|
|
78
72
|
}
|
|
79
73
|
if (contentType.includes("application/json")) {
|
|
80
|
-
return await
|
|
74
|
+
return await this.rawRequest.json();
|
|
81
75
|
}
|
|
82
76
|
else if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
83
|
-
|
|
77
|
+
const formData = await this.rawRequest.formData();
|
|
78
|
+
const result = {};
|
|
79
|
+
for (const [key, value] of formData.entries()) {
|
|
80
|
+
result[key] = value;
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
84
83
|
}
|
|
85
84
|
else if (contentType.includes("multipart/form-data")) {
|
|
86
85
|
const boundaryMatch = contentType.match(/boundary=([^;]+)/);
|
|
87
86
|
if (!boundaryMatch) {
|
|
88
87
|
throw new Error("Boundary not found in multipart/form-data");
|
|
89
88
|
}
|
|
90
|
-
const
|
|
91
|
-
|
|
89
|
+
const formData = await this.rawRequest.formData();
|
|
90
|
+
const result = {};
|
|
91
|
+
for (const [key, value] of formData.entries()) {
|
|
92
|
+
let val = value;
|
|
93
|
+
if (val instanceof File && typeof options == "object") {
|
|
94
|
+
let filename = val.name;
|
|
95
|
+
if (options?.sanitized) {
|
|
96
|
+
filename = `${Date.now()}-${sanitized(filename)}`;
|
|
97
|
+
}
|
|
98
|
+
if (Array.isArray(options?.allowedTypes) &&
|
|
99
|
+
!options.allowedTypes?.includes(val.type)) {
|
|
100
|
+
throw new Error(`Invalid file type: "${val.type}". Allowed types: ${options.allowedTypes.join(", ")}`);
|
|
101
|
+
}
|
|
102
|
+
if (typeof options?.maxSize !== "undefined" &&
|
|
103
|
+
val.size > options.maxSize) {
|
|
104
|
+
throw new Error(`File size exceeds the limit: ${val.size} bytes (Max: ${options.maxSize} bytes)`);
|
|
105
|
+
}
|
|
106
|
+
if (typeof options?.maxFiles != "undefined" && options.maxFiles == 0) {
|
|
107
|
+
throw new Error(`Field "${key}" exceeds the maximum allowed file count of ${options.maxFiles}.`);
|
|
108
|
+
}
|
|
109
|
+
val = new File([await val.arrayBuffer()], filename, {
|
|
110
|
+
type: val.type,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
if (result[key]) {
|
|
114
|
+
if (Array.isArray(result[key])) {
|
|
115
|
+
if (val instanceof File &&
|
|
116
|
+
typeof options?.maxFiles != "undefined" &&
|
|
117
|
+
result[key]?.length >= options.maxFiles) {
|
|
118
|
+
throw new Error(`Field "${key}" exceeds the maximum allowed file count of ${options.maxFiles}.`);
|
|
119
|
+
}
|
|
120
|
+
result[key].push(val);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
if (val instanceof File &&
|
|
124
|
+
typeof options?.maxFiles != "undefined" &&
|
|
125
|
+
options.maxFiles == 1) {
|
|
126
|
+
throw new Error(`Field "${key}" exceeds the maximum allowed file count of ${options.maxFiles}.`);
|
|
127
|
+
}
|
|
128
|
+
result[key] = [result[key], val];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
result[key] = val;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return result;
|
|
92
136
|
}
|
|
93
137
|
else {
|
|
94
138
|
return {};
|
package/core/router.d.ts
CHANGED
|
@@ -79,6 +79,35 @@ export declare class Router<T extends Record<string, any> = {}> extends Middlewa
|
|
|
79
79
|
get(path: string, callback: Callback<T>): this;
|
|
80
80
|
get(path: string, middleware: Middleware<T>, callback: Callback<T>): this;
|
|
81
81
|
get(path: string, middlewares: Middleware<T>[], callback: Callback<T>): this;
|
|
82
|
+
/**
|
|
83
|
+
* Registers a Server-Sent Events (SSE) route handler for the given path.
|
|
84
|
+
*
|
|
85
|
+
* This method sets up an HTTP GET route that sends real-time updates to the client
|
|
86
|
+
* over a persistent HTTP connection using the SSE protocol.
|
|
87
|
+
*
|
|
88
|
+
* ### Example:
|
|
89
|
+
* ```ts
|
|
90
|
+
* app.sse("/events", async (ctx) => {
|
|
91
|
+
* const stream = new ReadableStream({
|
|
92
|
+
* start(controller) {
|
|
93
|
+
* controller.enqueue(new TextEncoder().encode("data: Hello\n\n"));
|
|
94
|
+
* },
|
|
95
|
+
* });
|
|
96
|
+
*
|
|
97
|
+
* return ctx.send(stream, {
|
|
98
|
+
* headers: {
|
|
99
|
+
* "Content-Type": "text/event-stream",
|
|
100
|
+
* "Cache-Control": "no-cache",
|
|
101
|
+
* "Connection": "keep-alive",
|
|
102
|
+
* },
|
|
103
|
+
* });
|
|
104
|
+
* });
|
|
105
|
+
* ```
|
|
106
|
+
*
|
|
107
|
+
* @param {string} path - The route path for SSE (e.g. `/events`).
|
|
108
|
+
* @param {(ctx: Context) => any} handler - A handler function that returns a streamed response.
|
|
109
|
+
*/
|
|
110
|
+
sse(path: string, handler: (ctx: Context) => any): void;
|
|
82
111
|
/**
|
|
83
112
|
* Registers a POST route with optional middleware(s)
|
|
84
113
|
* @param path - URL path pattern
|
|
@@ -183,6 +212,7 @@ export declare class Router<T extends Record<string, any> = {}> extends Middlewa
|
|
|
183
212
|
use(path: string, middlewares: Middleware<T>[], callback: Callback<T> | Router<T | any>): this;
|
|
184
213
|
use(path: string, middleware: Middleware<T>, callback: Callback<T> | Router<T | any>): this;
|
|
185
214
|
use(path: string, middlewares: Middleware<T>[]): this;
|
|
215
|
+
use(path: string, middlewares: Middleware<T>): this;
|
|
186
216
|
use(path: string, callback: Callback<T> | Router<T | any>): this;
|
|
187
217
|
use(middlewares: Middleware<T>[], callback: Callback<T> | Router<T | any>): this;
|
|
188
218
|
use(middleware: Middleware<T>, callback: Callback<T> | Router<T | any>): this;
|
package/core/router.js
CHANGED
|
@@ -59,6 +59,11 @@ export class Router extends MiddlewareConfigure {
|
|
|
59
59
|
this.#registerRoute("GET", path, ...args);
|
|
60
60
|
return this;
|
|
61
61
|
}
|
|
62
|
+
sse(path, handler) {
|
|
63
|
+
this.get(path, async (ctx) => {
|
|
64
|
+
return handler(ctx);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
62
67
|
post(path, ...args) {
|
|
63
68
|
this.#registerRoute("POST", path, ...args);
|
|
64
69
|
return this;
|
package/core/server.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { COLORS } from "../utils/colors.js";
|
|
2
|
+
import { httpStatusMap } from "../utils/httpStatusMap.js";
|
|
2
3
|
import { useParams } from "../utils/params.js";
|
|
3
4
|
import { GlobalConfig } from "./config.js";
|
|
4
|
-
import { Context
|
|
5
|
+
import { Context } from "./context.js";
|
|
5
6
|
import { Router } from "./router.js";
|
|
6
7
|
export class TezX extends Router {
|
|
7
8
|
#onPathResolve;
|
|
@@ -194,12 +195,12 @@ export class TezX extends Router {
|
|
|
194
195
|
}
|
|
195
196
|
let finalResponse = () => {
|
|
196
197
|
return (ctx) => {
|
|
197
|
-
|
|
198
|
-
ctx.headers.
|
|
198
|
+
for (const [key, value] of response?.headers.entries()) {
|
|
199
|
+
ctx.headers.set(key, value);
|
|
199
200
|
}
|
|
200
201
|
const statusText = response?.statusText || httpStatusMap[response?.status] || "";
|
|
201
202
|
const status = response.status || ctx.getStatus;
|
|
202
|
-
let headers = ctx.headers.
|
|
203
|
+
let headers = ctx.headers.toJSON();
|
|
203
204
|
return new Response(response.body, {
|
|
204
205
|
status,
|
|
205
206
|
statusText,
|
package/index.js
CHANGED
package/middleware/basicAuth.js
CHANGED
|
@@ -12,7 +12,7 @@ export const basicAuth = (options) => {
|
|
|
12
12
|
if (rateLimit && !rateLimit.storage) {
|
|
13
13
|
storage = createRateLimitDefaultStorage();
|
|
14
14
|
}
|
|
15
|
-
return async (ctx, next)
|
|
15
|
+
return async function basicAuth(ctx, next) {
|
|
16
16
|
let authMethod;
|
|
17
17
|
let credentials = {};
|
|
18
18
|
const authHeader = ctx.req.headers.get("authorization");
|
|
@@ -8,7 +8,7 @@ export const cacheControl = (options) => {
|
|
|
8
8
|
GlobalConfig.debugging.success(`[CACHE] ${event.toUpperCase()} for ${ctx.method} ${ctx.pathname}`);
|
|
9
9
|
}
|
|
10
10
|
}, } = options;
|
|
11
|
-
return async (ctx, next)
|
|
11
|
+
return async function cacheControl(ctx, next) {
|
|
12
12
|
if (!["GET", "HEAD"].includes(ctx.method)) {
|
|
13
13
|
return await next();
|
|
14
14
|
}
|
package/middleware/cors.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export function cors(option = {}) {
|
|
2
2
|
const { methods, allowedHeaders, credentials, exposedHeaders, maxAge, origin, } = option;
|
|
3
|
-
return async (ctx, next)
|
|
3
|
+
return async function cors(ctx, next) {
|
|
4
4
|
const reqOrigin = ctx.req.headers.get("origin") || "";
|
|
5
5
|
let allowOrigin = "*";
|
|
6
6
|
if (typeof origin === "string") {
|
|
@@ -38,7 +38,7 @@ export function cors(option = {}) {
|
|
|
38
38
|
if (ctx.req.method === "OPTIONS") {
|
|
39
39
|
return new Response(null, {
|
|
40
40
|
status: 204,
|
|
41
|
-
headers: ctx.headers.
|
|
41
|
+
headers: ctx.headers.toJSON(),
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
return await next();
|
package/middleware/detectBot.js
CHANGED
|
@@ -8,7 +8,7 @@ export const detectBot = (options = {}) => {
|
|
|
8
8
|
if (enableRateLimiting) {
|
|
9
9
|
store = createRateLimitDefaultStorage();
|
|
10
10
|
}
|
|
11
|
-
return async (ctx, next)
|
|
11
|
+
return async function detectBot(ctx, next) {
|
|
12
12
|
const detectionResult = {
|
|
13
13
|
isBot: false,
|
|
14
14
|
indicators: [],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { GlobalConfig } from "../core/config.js";
|
|
2
2
|
export const detectLocale = (options) => {
|
|
3
3
|
const { supportedLocales, defaultLocale = "en", queryKeyLocale = "lang", cookieKeyLocale = "locale", localeContextKey = "locale", customLocaleDetector, } = options;
|
|
4
|
-
return async (ctx, next)
|
|
4
|
+
return async function detectLocale(ctx, next) {
|
|
5
5
|
let detectedLocale;
|
|
6
6
|
const queryLocale = ctx.req.query[queryKeyLocale];
|
|
7
7
|
if (queryLocale && supportedLocales.includes(queryLocale)) {
|
|
@@ -86,16 +86,16 @@ export type I18nOptions = {
|
|
|
86
86
|
*
|
|
87
87
|
* @example
|
|
88
88
|
* // Basic usage
|
|
89
|
-
* app.use(
|
|
89
|
+
* app.use(i18n({
|
|
90
90
|
* loadTranslations: lang => import(`./locales/${lang}.json`),
|
|
91
91
|
* defaultLanguage: 'en'
|
|
92
92
|
* }));
|
|
93
93
|
*
|
|
94
94
|
* // With caching and custom detection
|
|
95
|
-
* app.use(
|
|
95
|
+
* app.use(i18n({
|
|
96
96
|
* loadTranslations: fetchTranslations,
|
|
97
97
|
* detectLanguage: ctx => ctx.get('X-Language'),
|
|
98
98
|
* cacheTranslations: true
|
|
99
99
|
* }));
|
|
100
100
|
*/
|
|
101
|
-
export declare const
|
|
101
|
+
export declare const i18n: (options: I18nOptions) => Middleware;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { GlobalConfig } from "../core/config.js";
|
|
2
|
-
export const
|
|
2
|
+
export const i18n = (options) => {
|
|
3
3
|
const { loadTranslations, defaultCacheDuration = 3600000, isCacheValid = (cached) => cached.expiresAt > Date.now(), detectLanguage = (ctx) => ctx.req.query.lang ||
|
|
4
4
|
ctx.cookies?.get("lang") ||
|
|
5
5
|
ctx.req.headers.get("accept-language")?.split(",")[0] ||
|
|
@@ -8,7 +8,7 @@ export const i18nMiddleware = (options) => {
|
|
|
8
8
|
return Object.entries(options).reduce((msg, [key, value]) => msg.replace(new RegExp(`{{${key}}}`, "g"), String(value)), message);
|
|
9
9
|
}, cacheTranslations = true, } = options;
|
|
10
10
|
const translationCache = {};
|
|
11
|
-
return async (ctx, next)
|
|
11
|
+
return async function i18n(ctx, next) {
|
|
12
12
|
try {
|
|
13
13
|
const detectedLanguage = detectLanguage(ctx);
|
|
14
14
|
const languageChain = [
|
package/middleware/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export type { CorsOptions } from "./cors.js";
|
|
|
4
4
|
export { detectBot } from "./detectBot.js";
|
|
5
5
|
export type { DetectBotReason } from "./detectBot.js";
|
|
6
6
|
export * from "./detectLocale.js";
|
|
7
|
-
export * from "./
|
|
7
|
+
export * from "./i18n.js";
|
|
8
8
|
export * from "./lazyLoadModules.js";
|
|
9
9
|
export * from "./logger.js";
|
|
10
10
|
export * from "./pagination.js";
|
package/middleware/index.js
CHANGED
|
@@ -2,7 +2,7 @@ export * from "./basicAuth.js";
|
|
|
2
2
|
export { cors } from "./cors.js";
|
|
3
3
|
export { detectBot } from "./detectBot.js";
|
|
4
4
|
export * from "./detectLocale.js";
|
|
5
|
-
export * from "./
|
|
5
|
+
export * from "./i18n.js";
|
|
6
6
|
export * from "./lazyLoadModules.js";
|
|
7
7
|
export * from "./logger.js";
|
|
8
8
|
export * from "./pagination.js";
|
|
@@ -5,7 +5,7 @@ export const lazyLoadModules = (options) => {
|
|
|
5
5
|
if (enableCache && !cacheStorage) {
|
|
6
6
|
storage = new Map();
|
|
7
7
|
}
|
|
8
|
-
return async (ctx, next)
|
|
8
|
+
return async function lazyLoadModules(ctx, next) {
|
|
9
9
|
let moduleName = moduleKey(ctx) ||
|
|
10
10
|
ctx.req.params[queryKeyModule] ||
|
|
11
11
|
ctx.req.query[queryKeyModule];
|
package/middleware/logger.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { COLORS } from "../utils/colors.js";
|
|
2
2
|
export function logger() {
|
|
3
|
-
return async (ctx, next)
|
|
3
|
+
return async function logger(ctx, next) {
|
|
4
4
|
try {
|
|
5
5
|
console.log(`${COLORS.bold}<-- ${COLORS.reset}${COLORS.bgMagenta} ${ctx.method} ${COLORS.reset} ${ctx.pathname}`);
|
|
6
6
|
const startTime = performance.now();
|
package/middleware/pagination.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const paginationHandler = (options = {}) => {
|
|
2
2
|
const { defaultPage = 1, defaultLimit = 10, maxLimit = 100, queryKeyPage = "page", queryKeyLimit = "limit", countKey = "total", dataKey = "data", getDataSource, } = options;
|
|
3
|
-
return async (ctx, next)
|
|
3
|
+
return async function paginationHandler(ctx, next) {
|
|
4
4
|
const rawPage = ctx.req.query[queryKeyPage];
|
|
5
5
|
const rawLimit = ctx.req.query[queryKeyLimit];
|
|
6
6
|
const page = Math.max(parseInt(rawPage || `${defaultPage}`, 10), 1);
|
package/middleware/powered-by.js
CHANGED
|
@@ -4,7 +4,7 @@ export const rateLimiter = (options) => {
|
|
|
4
4
|
ctx.setStatus = 429;
|
|
5
5
|
throw new Error(`Rate limit exceeded. Try again in ${retryAfter} seconds.`);
|
|
6
6
|
}, } = options;
|
|
7
|
-
return async (ctx, next)
|
|
7
|
+
return async function rateLimiter(ctx, next) {
|
|
8
8
|
const key = keyGenerator(ctx);
|
|
9
9
|
const { check, entry } = isRateLimit(ctx, key, storage, maxRequests, windowMs);
|
|
10
10
|
if (check) {
|
package/middleware/request-id.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { generateID } from "../helper/index.js";
|
|
2
2
|
export const requestID = (headerName = "X-Request-ID", contextKey = "requestID") => {
|
|
3
|
-
return (ctx, next)
|
|
3
|
+
return function requestID(ctx, next) {
|
|
4
4
|
const existingID = ctx.headers?.get(headerName.toLowerCase()) ||
|
|
5
5
|
ctx.headers?.get(headerName);
|
|
6
6
|
const requestId = existingID || `req-${generateID()}`;
|
|
@@ -7,7 +7,7 @@ export const requestTimeout = (options) => {
|
|
|
7
7
|
GlobalConfig.debugging.warn(`[TIMEOUT] ${error.message}: ${ctx.method} ${ctx.path}`);
|
|
8
8
|
}, cleanup = () => {
|
|
9
9
|
}, } = options;
|
|
10
|
-
return async (ctx, next)
|
|
10
|
+
return async function requestTimeout(ctx, next) {
|
|
11
11
|
let timeoutId = null;
|
|
12
12
|
try {
|
|
13
13
|
const timeout = getTimeout(ctx);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { GlobalConfig } from "../core/config.js";
|
|
2
2
|
export const sanitizeHeaders = (options = {}) => {
|
|
3
3
|
const { whitelist = [], blacklist = [], normalizeKeys = true, allowUnsafeCharacters = false, } = options;
|
|
4
|
-
return async (ctx, next)
|
|
4
|
+
return async function sanitizeHeaders(ctx, next) {
|
|
5
5
|
const sanitizedHeaders = new Map();
|
|
6
6
|
for (const [key, values] of ctx.headers.entries()) {
|
|
7
7
|
if (!Array.isArray(values) || values.length === 0) {
|
|
@@ -28,9 +28,9 @@ export const sanitizeHeaders = (options = {}) => {
|
|
|
28
28
|
GlobalConfig.debugging.warn(`⚠️ All values for "${normalizedKey}" invalid - removed`);
|
|
29
29
|
continue;
|
|
30
30
|
}
|
|
31
|
-
sanitizedHeaders.set(normalizedKey, sanitizedValues);
|
|
31
|
+
sanitizedHeaders.set(normalizedKey, sanitizedValues?.join(", "));
|
|
32
32
|
}
|
|
33
|
-
ctx.headers
|
|
33
|
+
ctx.headers = new Headers(sanitizedHeaders);
|
|
34
34
|
return await next();
|
|
35
35
|
};
|
|
36
36
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { GlobalConfig } from "../core/config.js";
|
|
2
2
|
export const xssProtection = (options = {}) => {
|
|
3
3
|
const { enabled = true, mode = "block", fallbackCSP = "default-src 'self'; script-src 'self';", } = options;
|
|
4
|
-
return async (ctx, next)
|
|
4
|
+
return async function xssProtection(ctx, next) {
|
|
5
5
|
const isEnabled = typeof enabled === "function" ? enabled(ctx) : enabled;
|
|
6
6
|
if (!isEnabled) {
|
|
7
7
|
GlobalConfig.debugging.warn("🟠 XSS protection is disabled.");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tezx",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.75-beta",
|
|
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",
|
package/utils/formData.d.ts
CHANGED
|
@@ -1,6 +1 @@
|
|
|
1
|
-
import { FormDataOptions } from "../core/request.js";
|
|
2
|
-
export declare function parseJsonBody(req: any): Promise<Record<string, any>>;
|
|
3
|
-
export declare function parseTextBody(req: any): Promise<string>;
|
|
4
|
-
export declare function parseUrlEncodedBody(req: any): Promise<Record<string, any>>;
|
|
5
1
|
export declare function sanitized(title: string): string;
|
|
6
|
-
export declare function parseMultipartBody(req: any, boundary: string, options?: FormDataOptions): Promise<Record<string, any>>;
|