tezx 3.0.9-beta → 3.0.11-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/bun/index.d.ts +4 -2
- package/bun/index.js +3 -2
- package/bun/ws.d.ts +37 -0
- package/bun/ws.js +23 -0
- package/cjs/bun/index.js +37 -5
- package/cjs/bun/ws.js +25 -0
- package/cjs/core/context.js +86 -88
- package/cjs/core/error.js +41 -0
- package/cjs/core/request.js +6 -6
- package/cjs/core/router.js +6 -6
- package/cjs/core/server.js +45 -63
- package/cjs/index.js +5 -2
- package/cjs/middleware/basic-auth.js +28 -54
- package/cjs/middleware/bearer-auth.js +34 -0
- package/cjs/middleware/cors.js +14 -24
- package/cjs/middleware/index.js +25 -0
- package/cjs/middleware/logger.js +6 -3
- package/cjs/middleware/pagination.js +1 -1
- package/cjs/middleware/powered-by.js +1 -1
- package/cjs/middleware/rate-limiter.js +20 -7
- package/cjs/middleware/request-id.js +4 -7
- package/cjs/middleware/sanitize-headers.js +8 -40
- package/cjs/middleware/xss-protection.js +2 -6
- package/cjs/registry/RadixRouter.js +72 -23
- package/cjs/utils/cookie.js +1 -1
- package/cjs/utils/rateLimit.js +2 -2
- package/cjs/utils/regexRouter.js +1 -0
- package/cjs/utils/response.js +21 -30
- package/core/context.d.ts +68 -68
- package/core/context.js +87 -89
- package/core/error.d.ts +95 -0
- package/core/error.js +37 -0
- package/core/request.d.ts +2 -2
- package/core/request.js +6 -6
- package/core/router.d.ts +11 -6
- package/core/router.js +6 -6
- package/core/server.js +45 -63
- package/index.d.ts +5 -3
- package/index.js +4 -2
- package/middleware/basic-auth.d.ts +38 -66
- package/middleware/basic-auth.js +28 -54
- package/middleware/bearer-auth.d.ts +52 -0
- package/middleware/bearer-auth.js +30 -0
- package/middleware/cors.d.ts +7 -21
- package/middleware/cors.js +14 -24
- package/middleware/index.d.ts +9 -0
- package/middleware/index.js +9 -0
- package/middleware/logger.d.ts +3 -1
- package/middleware/logger.js +6 -3
- package/middleware/pagination.d.ts +8 -6
- package/middleware/pagination.js +1 -1
- package/middleware/powered-by.js +1 -1
- package/middleware/rate-limiter.d.ts +0 -4
- package/middleware/rate-limiter.js +20 -7
- package/middleware/request-id.d.ts +1 -1
- package/middleware/request-id.js +3 -6
- package/middleware/sanitize-headers.d.ts +3 -11
- package/middleware/sanitize-headers.js +8 -40
- package/middleware/xss-protection.d.ts +1 -1
- package/middleware/xss-protection.js +1 -5
- package/package.json +6 -1
- package/registry/RadixRouter.js +72 -23
- package/types/index.d.ts +2 -1
- package/utils/cookie.js +1 -1
- package/utils/rateLimit.d.ts +1 -2
- package/utils/rateLimit.js +2 -2
- package/utils/regexRouter.js +1 -0
- package/utils/response.d.ts +12 -14
- package/utils/response.js +20 -29
- package/cjs/middleware/cache-control.js +0 -93
- package/cjs/middleware/detect-bot.js +0 -66
- package/cjs/middleware/detect-locale.js +0 -45
- package/cjs/middleware/i18n.js +0 -93
- package/cjs/middleware/lazy-loader.js +0 -74
- package/cjs/middleware/request-timeout.js +0 -43
- package/cjs/middleware/secure-headers.js +0 -43
- package/middleware/cache-control.d.ts +0 -56
- package/middleware/cache-control.js +0 -56
- package/middleware/detect-bot.d.ts +0 -111
- package/middleware/detect-bot.js +0 -62
- package/middleware/detect-locale.d.ts +0 -56
- package/middleware/detect-locale.js +0 -41
- package/middleware/i18n.d.ts +0 -102
- package/middleware/i18n.js +0 -89
- package/middleware/lazy-loader.d.ts +0 -73
- package/middleware/lazy-loader.js +0 -70
- package/middleware/request-timeout.d.ts +0 -26
- package/middleware/request-timeout.js +0 -39
- package/middleware/secure-headers.d.ts +0 -78
- package/middleware/secure-headers.js +0 -39
package/core/context.js
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
import { fileExists, fileSize, getFileBuffer, readStream, } from "../utils/file.js";
|
|
2
2
|
import { extensionExtract } from "../utils/low-level.js";
|
|
3
3
|
import { defaultMimeType, mimeTypes } from "../utils/mimeTypes.js";
|
|
4
|
-
import { determineContentTypeBody,
|
|
4
|
+
import { determineContentTypeBody, toString } from "../utils/response.js";
|
|
5
5
|
import { TezXRequest } from "./request.js";
|
|
6
6
|
export class Context {
|
|
7
7
|
#status = 200;
|
|
8
|
-
#headers
|
|
8
|
+
#headers;
|
|
9
9
|
#req = null;
|
|
10
10
|
#params = {};
|
|
11
11
|
rawRequest;
|
|
12
12
|
#args;
|
|
13
13
|
#body;
|
|
14
|
+
url;
|
|
15
|
+
res;
|
|
14
16
|
env = {};
|
|
15
17
|
pathname;
|
|
16
18
|
method;
|
|
17
|
-
constructor(req,
|
|
18
|
-
this.#args =
|
|
19
|
+
constructor(req, pathname, method, env, args) {
|
|
20
|
+
this.#args = args;
|
|
19
21
|
this.rawRequest = req;
|
|
20
|
-
this.
|
|
21
|
-
this.
|
|
22
|
-
this.
|
|
23
|
-
|
|
24
|
-
get url() {
|
|
25
|
-
return this.req.url;
|
|
22
|
+
this.url = req.url;
|
|
23
|
+
this.pathname = pathname;
|
|
24
|
+
this.env = env ?? {};
|
|
25
|
+
this.method = method;
|
|
26
26
|
}
|
|
27
27
|
get getStatus() {
|
|
28
28
|
return this.#status;
|
|
@@ -30,58 +30,28 @@ export class Context {
|
|
|
30
30
|
set setStatus(code) {
|
|
31
31
|
this.#status = code;
|
|
32
32
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return this.#headers[header?.toLowerCase()];
|
|
36
|
-
}
|
|
37
|
-
return this.#headers;
|
|
38
|
-
}
|
|
39
|
-
set clearHeader(header) {
|
|
40
|
-
this.#headers = (header || {});
|
|
33
|
+
get headers() {
|
|
34
|
+
return this.res?.headers ?? (this.#headers ??= new Headers());
|
|
41
35
|
}
|
|
42
36
|
setHeader(key, value, options) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (!this.res) {
|
|
46
|
-
if (append && this.#headers[_key]) {
|
|
47
|
-
this.#headers[_key] += `, ${value}`;
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
this.#headers[_key] = value;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
const resHeaders = this.res.headers;
|
|
55
|
-
if (append) {
|
|
56
|
-
resHeaders.append(_key, value);
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
resHeaders.set(_key, value);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return this;
|
|
63
|
-
}
|
|
64
|
-
deleteHeader(key) {
|
|
37
|
+
if (!value)
|
|
38
|
+
return this;
|
|
65
39
|
const _key = key.toLowerCase();
|
|
66
|
-
|
|
67
|
-
|
|
40
|
+
const append = options?.append || _key === "set-cookie";
|
|
41
|
+
const target = this.res?.headers ?? (this.#headers ??= new Headers());
|
|
42
|
+
if (append) {
|
|
43
|
+
target.append(_key, value);
|
|
68
44
|
}
|
|
69
45
|
else {
|
|
70
|
-
|
|
46
|
+
target.set(_key, value);
|
|
71
47
|
}
|
|
72
48
|
return this;
|
|
73
49
|
}
|
|
74
|
-
get params() {
|
|
75
|
-
return this.#params;
|
|
76
|
-
}
|
|
77
50
|
set params(params) {
|
|
78
51
|
this.#params = params;
|
|
79
52
|
}
|
|
80
53
|
get req() {
|
|
81
|
-
|
|
82
|
-
this.#req = new TezXRequest(this.rawRequest, this.method, this.pathname, this.#params);
|
|
83
|
-
}
|
|
84
|
-
return this.#req;
|
|
54
|
+
return (this.#req ??= new TezXRequest(this.rawRequest, this.method, this.pathname, this.#params));
|
|
85
55
|
}
|
|
86
56
|
get body() {
|
|
87
57
|
return this.#body;
|
|
@@ -93,66 +63,93 @@ export class Context {
|
|
|
93
63
|
this.#status = status;
|
|
94
64
|
return this;
|
|
95
65
|
};
|
|
66
|
+
#newResponse(body, type, init = {}) {
|
|
67
|
+
const headers = new Headers(this.#headers);
|
|
68
|
+
headers.set("Content-Type", type);
|
|
69
|
+
if (init.headers) {
|
|
70
|
+
for (const key in init.headers) {
|
|
71
|
+
const value = init.headers[key];
|
|
72
|
+
if (!value)
|
|
73
|
+
continue;
|
|
74
|
+
if (key.toLowerCase() === "set-cookie") {
|
|
75
|
+
headers.append(key, value);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
headers.set(key, value);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return new Response(body, {
|
|
83
|
+
status: init.status ?? this.#status,
|
|
84
|
+
statusText: init.statusText,
|
|
85
|
+
headers,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
96
88
|
newResponse(body, init = {}) {
|
|
97
|
-
const headers =
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
89
|
+
const headers = new Headers(this.#headers);
|
|
90
|
+
if (init.headers) {
|
|
91
|
+
for (const key in init.headers) {
|
|
92
|
+
const value = init.headers[key];
|
|
93
|
+
if (!value) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (key.toLowerCase() === "set-cookie") {
|
|
97
|
+
headers.append(key, value);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
headers.set(key, value);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return new Response(body, {
|
|
105
|
+
status: init.status ?? this.#status,
|
|
106
|
+
statusText: init.statusText,
|
|
107
|
+
headers,
|
|
108
|
+
});
|
|
101
109
|
}
|
|
102
110
|
text(content, init) {
|
|
103
|
-
return newResponse(content, "text/plain; charset=utf-8", init
|
|
111
|
+
return this.#newResponse(content, "text/plain; charset=utf-8", init);
|
|
104
112
|
}
|
|
105
113
|
html(strings, ...args) {
|
|
106
|
-
|
|
107
|
-
if (Array.isArray(strings)) {
|
|
108
|
-
html = strings.reduce((result, str, i) => {
|
|
109
|
-
const value = args?.[i] ?? "";
|
|
110
|
-
return result + str + value;
|
|
111
|
-
}, "");
|
|
112
|
-
return newResponse(html, "text/html; charset=utf-8", {}, this.#headers, this.#status);
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
let init = args?.[0];
|
|
116
|
-
return newResponse(html, "text/html; charset=utf-8", init, this.#headers, this.#status);
|
|
117
|
-
}
|
|
114
|
+
return this.#newResponse(toString(strings, args), "text/html; charset=utf-8", args?.[0]);
|
|
118
115
|
}
|
|
119
|
-
xml(
|
|
120
|
-
return newResponse(
|
|
116
|
+
xml(strings, ...args) {
|
|
117
|
+
return this.#newResponse(toString(strings, args), "text/html; charset=utf-8", args?.[0]);
|
|
121
118
|
}
|
|
122
119
|
json(json, init) {
|
|
123
|
-
return newResponse(JSON.stringify(json), "application/json; charset=utf-8", init
|
|
120
|
+
return this.#newResponse(JSON.stringify(json), "application/json; charset=utf-8", init);
|
|
124
121
|
}
|
|
125
122
|
send(body, init) {
|
|
126
123
|
let { body: _body, type } = determineContentTypeBody(body);
|
|
127
|
-
const contentType = init?.headers?.["Content-Type"]
|
|
128
|
-
init?.headers?.["content-type"]
|
|
124
|
+
const contentType = init?.headers?.["Content-Type"] ??
|
|
125
|
+
init?.headers?.["content-type"] ??
|
|
129
126
|
type;
|
|
130
|
-
return newResponse(_body, contentType,
|
|
127
|
+
return this.#newResponse(_body, contentType, init);
|
|
131
128
|
}
|
|
132
129
|
redirect(url, status = 302) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
});
|
|
130
|
+
const headers = new Headers(this.#headers);
|
|
131
|
+
headers.set("Location", url);
|
|
132
|
+
return new Response(null, { status, headers });
|
|
137
133
|
}
|
|
138
134
|
async download(filePath, filename) {
|
|
139
135
|
if (!(await fileExists(filePath)))
|
|
140
136
|
throw Error("File not found");
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
137
|
+
const buf = await getFileBuffer(filePath);
|
|
138
|
+
const headers = {
|
|
139
|
+
"Content-Disposition": `attachment; filename="${filename}"`,
|
|
140
|
+
"Content-Length": buf.byteLength.toString(),
|
|
141
|
+
};
|
|
142
|
+
return this.#newResponse(buf, "application/octet-stream", {
|
|
143
|
+
status: this.#status,
|
|
144
|
+
headers,
|
|
145
|
+
});
|
|
149
146
|
}
|
|
150
147
|
async sendFile(filePath, init) {
|
|
151
148
|
if (!(await fileExists(filePath)))
|
|
152
149
|
throw Error("File not found");
|
|
153
150
|
let size = await fileSize(filePath);
|
|
154
|
-
const ext = extensionExtract(filePath)
|
|
155
|
-
const mimeType = mimeTypes[ext]
|
|
151
|
+
const ext = extensionExtract(filePath);
|
|
152
|
+
const mimeType = mimeTypes[ext] ?? defaultMimeType;
|
|
156
153
|
let fileStream = await readStream(filePath);
|
|
157
154
|
let headers = {
|
|
158
155
|
"Content-Type": mimeType,
|
|
@@ -160,10 +157,11 @@ export class Context {
|
|
|
160
157
|
...init?.headers,
|
|
161
158
|
};
|
|
162
159
|
let filename = init?.filename;
|
|
163
|
-
if (filename)
|
|
160
|
+
if (filename) {
|
|
164
161
|
headers["Content-Disposition"] = `attachment; filename="${filename}"`;
|
|
162
|
+
}
|
|
165
163
|
return this.newResponse(fileStream, {
|
|
166
|
-
status: init?.status
|
|
164
|
+
status: init?.status ?? this.#status,
|
|
167
165
|
statusText: init?.statusText,
|
|
168
166
|
headers,
|
|
169
167
|
});
|
package/core/error.d.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error type used throughout the Tezx application.
|
|
3
|
+
*
|
|
4
|
+
* Extends the built-in `Error` and carries an HTTP status code plus optional
|
|
5
|
+
* structured `details` payload (useful for validation errors, metadata, etc.).
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* // Throw a 404
|
|
9
|
+
* throw TezXError.notFound("User not found", { userId });
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* // Create and throw a custom status
|
|
13
|
+
* throw new TezXError("Something went wrong", 502);
|
|
14
|
+
*
|
|
15
|
+
* @class TezXError
|
|
16
|
+
* @extends Error
|
|
17
|
+
*/
|
|
18
|
+
export declare class TezXError extends Error {
|
|
19
|
+
/**
|
|
20
|
+
* HTTP status code representing the error (defaults to 500).
|
|
21
|
+
*/
|
|
22
|
+
statusCode: number;
|
|
23
|
+
/**
|
|
24
|
+
* Optional additional details about the error (validation lists, meta, etc.).
|
|
25
|
+
*/
|
|
26
|
+
details?: any;
|
|
27
|
+
/**
|
|
28
|
+
* Create an instance of TezXError.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} message - Human readable error message.
|
|
31
|
+
* @param {number} [statusCode=500] - HTTP status code to associate with this error.
|
|
32
|
+
* @param {*} [details] - Optional additional error payload (e.g. validation errors).
|
|
33
|
+
*/
|
|
34
|
+
constructor(message: string, statusCode?: number, details?: any);
|
|
35
|
+
/**
|
|
36
|
+
* Create a 400 Bad Request error.
|
|
37
|
+
*
|
|
38
|
+
* @param {string} [message="Bad Request"]
|
|
39
|
+
* @param {*} [details]
|
|
40
|
+
* @returns {TezXError}
|
|
41
|
+
*/
|
|
42
|
+
static badRequest(message?: string, details?: any): TezXError;
|
|
43
|
+
/**
|
|
44
|
+
* Create a 401 Unauthorized error.
|
|
45
|
+
*
|
|
46
|
+
* @param {string} [message="Unauthorized"]
|
|
47
|
+
* @param {*} [details]
|
|
48
|
+
* @returns {TezXError}
|
|
49
|
+
*/
|
|
50
|
+
static unauthorized(message?: string, details?: any): TezXError;
|
|
51
|
+
/**
|
|
52
|
+
* Create a 403 Forbidden error.
|
|
53
|
+
*
|
|
54
|
+
* @param {string} [message="Forbidden"]
|
|
55
|
+
* @param {*} [details]
|
|
56
|
+
* @returns {TezXError}
|
|
57
|
+
*/
|
|
58
|
+
static forbidden(message?: string, details?: any): TezXError;
|
|
59
|
+
/**
|
|
60
|
+
* Create a 404 Not Found error.
|
|
61
|
+
*
|
|
62
|
+
* @param {string} [message="Resource Not Found"]
|
|
63
|
+
* @param {*} [details]
|
|
64
|
+
* @returns {TezXError}
|
|
65
|
+
*/
|
|
66
|
+
static notFound(message?: string, details?: any): TezXError;
|
|
67
|
+
/**
|
|
68
|
+
* Create a 409 Conflict error.
|
|
69
|
+
*
|
|
70
|
+
* @param {string} [message="Conflict"]
|
|
71
|
+
* @param {*} [details]
|
|
72
|
+
* @returns {TezXError}
|
|
73
|
+
*/
|
|
74
|
+
static conflict(message?: string, details?: any): TezXError;
|
|
75
|
+
/**
|
|
76
|
+
* Create a 500 Internal Server Error.
|
|
77
|
+
*
|
|
78
|
+
* @param {string} [message="Internal Server Error"]
|
|
79
|
+
* @param {*} [details]
|
|
80
|
+
* @returns {TezXError}
|
|
81
|
+
*/
|
|
82
|
+
static internal(message?: string, details?: any): TezXError;
|
|
83
|
+
/**
|
|
84
|
+
* Convert the error into a JSON-serializable object that can be sent
|
|
85
|
+
* in HTTP responses or logged safely.
|
|
86
|
+
*
|
|
87
|
+
* @returns {{ error: boolean, message: string, statusCode: number, details: any }}
|
|
88
|
+
*/
|
|
89
|
+
toJSON(): {
|
|
90
|
+
error: boolean;
|
|
91
|
+
message: string;
|
|
92
|
+
statusCode: number;
|
|
93
|
+
details: any;
|
|
94
|
+
};
|
|
95
|
+
}
|
package/core/error.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export class TezXError extends Error {
|
|
2
|
+
statusCode;
|
|
3
|
+
details;
|
|
4
|
+
constructor(message, statusCode = 500, details) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.statusCode = statusCode;
|
|
7
|
+
this.details = details;
|
|
8
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
9
|
+
Error.captureStackTrace(this, this.constructor);
|
|
10
|
+
}
|
|
11
|
+
static badRequest(message = "Bad Request", details) {
|
|
12
|
+
return new TezXError(message, 400, details);
|
|
13
|
+
}
|
|
14
|
+
static unauthorized(message = "Unauthorized", details) {
|
|
15
|
+
return new TezXError(message, 401, details);
|
|
16
|
+
}
|
|
17
|
+
static forbidden(message = "Forbidden", details) {
|
|
18
|
+
return new TezXError(message, 403, details);
|
|
19
|
+
}
|
|
20
|
+
static notFound(message = "Resource Not Found", details) {
|
|
21
|
+
return new TezXError(message, 404, details);
|
|
22
|
+
}
|
|
23
|
+
static conflict(message = "Conflict", details) {
|
|
24
|
+
return new TezXError(message, 409, details);
|
|
25
|
+
}
|
|
26
|
+
static internal(message = "Internal Server Error", details) {
|
|
27
|
+
return new TezXError(message, 500, details);
|
|
28
|
+
}
|
|
29
|
+
toJSON() {
|
|
30
|
+
return {
|
|
31
|
+
error: true,
|
|
32
|
+
message: this.message,
|
|
33
|
+
statusCode: this.statusCode,
|
|
34
|
+
details: this.details ?? null,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
package/core/request.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ExtractParamsFromPath,
|
|
1
|
+
import { ExtractParamsFromPath, HTTPMethod, NetAddr, ReqHeaderKey, RequestHeaders } from "../types/index.js";
|
|
2
2
|
/**
|
|
3
3
|
* A wrapper around the raw HTTP request that provides convenient access to URL, headers, body parsing, and route parameters.
|
|
4
4
|
*
|
|
@@ -60,7 +60,7 @@ export declare class TezXRequest<Path extends string = any> {
|
|
|
60
60
|
*
|
|
61
61
|
* @returns {Record<string, any>} Query parameters.
|
|
62
62
|
*/
|
|
63
|
-
get query(): Record<string,
|
|
63
|
+
get query(): Record<string, string | string[]>;
|
|
64
64
|
/**
|
|
65
65
|
* Parses the request body as plain text.
|
|
66
66
|
*
|
package/core/request.js
CHANGED
|
@@ -14,22 +14,22 @@ export class TezXRequest {
|
|
|
14
14
|
#headersCache;
|
|
15
15
|
#queryCache;
|
|
16
16
|
constructor(req, method, pathname, params) {
|
|
17
|
-
this.url = req.url
|
|
18
|
-
this.params = params
|
|
17
|
+
this.url = req.url;
|
|
18
|
+
this.params = params ?? {};
|
|
19
19
|
this.method = method;
|
|
20
20
|
this.#rawRequest = req;
|
|
21
|
-
this.pathname = pathname
|
|
21
|
+
this.pathname = pathname ?? "/";
|
|
22
22
|
}
|
|
23
23
|
header(header) {
|
|
24
24
|
if (header) {
|
|
25
|
-
return this.#rawRequest.headers.get(header
|
|
25
|
+
return this.#rawRequest.headers.get(header.toLowerCase());
|
|
26
26
|
}
|
|
27
27
|
if (this.#headersCache)
|
|
28
28
|
return this.#headersCache;
|
|
29
29
|
const obj = {};
|
|
30
|
-
|
|
30
|
+
this.#rawRequest.headers.forEach((value, key) => {
|
|
31
31
|
obj[key.toLowerCase()] = value;
|
|
32
|
-
}
|
|
32
|
+
});
|
|
33
33
|
this.#headersCache = obj;
|
|
34
34
|
return this.#headersCache;
|
|
35
35
|
}
|
package/core/router.d.ts
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
import { Callback, HandlerType, HTTPMethod, Middleware, RouteRegistry, ServeStatic } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Router configuration options.
|
|
4
|
+
*/
|
|
2
5
|
export type RouterConfig = {
|
|
3
6
|
/**
|
|
4
7
|
* Custom route registry instance used internally to store routes.
|
|
5
|
-
*
|
|
8
|
+
* If not provided, the router will use the default CombineRouteRegistry.
|
|
6
9
|
*/
|
|
7
10
|
routeRegistry?: RouteRegistry;
|
|
8
11
|
/**
|
|
9
|
-
* `env` allows you to define environment variables for the router.
|
|
10
|
-
*
|
|
11
|
-
* and the value can be either a string or a number.
|
|
12
|
+
* `env` allows you to define environment variables for the router instance.
|
|
13
|
+
* Example: `{ NODE_ENV: "production", API_VERSION: 2 }`
|
|
12
14
|
*/
|
|
13
15
|
env?: Record<string, string | number>;
|
|
14
16
|
/**
|
|
15
|
-
* `basePath` sets
|
|
16
|
-
*
|
|
17
|
+
* `basePath` sets a base path prefix for the router. Useful to group routes
|
|
18
|
+
* under a common prefix (for example when mounting the router on a sub-path).
|
|
19
|
+
*
|
|
20
|
+
* - Example: `basePath: "/api/v1"` will prefix all registered routes with `/api/v1`.
|
|
21
|
+
* - Should not end with a trailing slash — the router will normalize it.
|
|
17
22
|
*/
|
|
18
23
|
basePath?: string;
|
|
19
24
|
};
|
package/core/router.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { sanitizePathSplitBasePath } from "../utils/low-level.js";
|
|
2
1
|
import { RadixRouter } from "../registry/RadixRouter.js";
|
|
2
|
+
import { sanitizePathSplitBasePath } from "../utils/low-level.js";
|
|
3
3
|
export class Router {
|
|
4
4
|
env = {};
|
|
5
5
|
router;
|
|
@@ -126,11 +126,9 @@ export class Router {
|
|
|
126
126
|
}
|
|
127
127
|
return this;
|
|
128
128
|
}
|
|
129
|
-
#addRoute(method, path, handlers
|
|
129
|
+
#addRoute(method, path, handlers) {
|
|
130
130
|
let pattern = `/${sanitizePathSplitBasePath(this.basePath, path).join("/")}`;
|
|
131
|
-
|
|
132
|
-
this.router.addRoute(method, pattern, handlers);
|
|
133
|
-
}
|
|
131
|
+
this.router.addRoute(method, pattern, handlers);
|
|
134
132
|
this.route.push({
|
|
135
133
|
method: method,
|
|
136
134
|
pattern: pattern,
|
|
@@ -165,7 +163,9 @@ export class Router {
|
|
|
165
163
|
}
|
|
166
164
|
#routeAddTriNode(path, router) {
|
|
167
165
|
this.env = { ...this.env, ...router.env };
|
|
168
|
-
if (this.router?.name &&
|
|
166
|
+
if (this.router?.name &&
|
|
167
|
+
router.router?.name &&
|
|
168
|
+
this.router?.name !== router.router?.name) {
|
|
169
169
|
throw new Error(`Router name mismatch: expected "${this.router.name}", got "${router.router.name}"`);
|
|
170
170
|
}
|
|
171
171
|
if (!(router instanceof Router)) {
|
package/core/server.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { colorText } from "../utils/colors.js";
|
|
2
1
|
import { handleErrorResponse, notFoundResponse } from "../utils/response.js";
|
|
3
2
|
import { getPathname } from "../utils/url.js";
|
|
4
3
|
import { GlobalConfig } from "./config.js";
|
|
5
4
|
import { Context } from "./context.js";
|
|
5
|
+
import { TezXError } from "./error.js";
|
|
6
6
|
import { Router } from "./router.js";
|
|
7
7
|
export class TezX extends Router {
|
|
8
8
|
#pathResolver;
|
|
@@ -24,88 +24,70 @@ export class TezX extends Router {
|
|
|
24
24
|
this.#errorHandler = callback;
|
|
25
25
|
return this;
|
|
26
26
|
}
|
|
27
|
-
async #
|
|
28
|
-
let
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
return resolvePath;
|
|
37
|
-
}
|
|
38
|
-
#chain(middlewares) {
|
|
39
|
-
if (!Array.isArray(middlewares)) {
|
|
40
|
-
throw new TypeError("Middleware stack must be an array!");
|
|
41
|
-
}
|
|
42
|
-
const len = middlewares.length;
|
|
43
|
-
return async function (ctx) {
|
|
44
|
-
let index = -1;
|
|
45
|
-
async function dispatch(i) {
|
|
46
|
-
if (i <= index) {
|
|
47
|
-
throw new Error("next() called multiple times");
|
|
48
|
-
}
|
|
49
|
-
index = i;
|
|
50
|
-
if (i >= len)
|
|
51
|
-
return;
|
|
27
|
+
async #chain(ctx, mLen, middlewares, hLen, handlers) {
|
|
28
|
+
let index = -1;
|
|
29
|
+
let res;
|
|
30
|
+
async function dispatch(i) {
|
|
31
|
+
if (i <= index)
|
|
32
|
+
throw new Error("next() called multiple times");
|
|
33
|
+
index = i;
|
|
34
|
+
if (i < mLen) {
|
|
52
35
|
const fn = middlewares[i];
|
|
53
|
-
if (typeof fn !== "function")
|
|
54
|
-
throw new TypeError(`Middleware
|
|
36
|
+
if (typeof fn !== "function")
|
|
37
|
+
throw new TypeError(`Middleware[${i}] must be a function`);
|
|
38
|
+
res = (await fn(ctx, () => dispatch(i + 1)));
|
|
39
|
+
if (res !== undefined) {
|
|
40
|
+
ctx.res = res;
|
|
55
41
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
42
|
+
return ctx.res;
|
|
43
|
+
}
|
|
44
|
+
const hi = i - mLen;
|
|
45
|
+
if (hi < hLen) {
|
|
46
|
+
const fn = handlers[hi];
|
|
47
|
+
if (typeof fn !== "function")
|
|
48
|
+
throw new TypeError(`Handler[${hi}] must be a function`);
|
|
49
|
+
res = (await fn(ctx, () => dispatch(i + 1)));
|
|
50
|
+
if (res !== undefined) {
|
|
51
|
+
ctx.res = res;
|
|
59
52
|
}
|
|
60
53
|
return ctx.res;
|
|
61
54
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
55
|
+
}
|
|
56
|
+
await dispatch(0);
|
|
57
|
+
return (ctx.res ??
|
|
58
|
+
(ctx.body !== undefined ? ctx.send(ctx.body) : this.#notFound(ctx)));
|
|
65
59
|
}
|
|
66
60
|
async #handleRequest(req, method, args) {
|
|
67
|
-
if (!(req instanceof Request))
|
|
61
|
+
if (!(req instanceof Request))
|
|
68
62
|
throw new Error("Invalid request object provided to tezX server.");
|
|
69
|
-
|
|
70
|
-
const pathname =
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
env: this.env,
|
|
75
|
-
args,
|
|
76
|
-
});
|
|
63
|
+
const rawPath = getPathname(req.url);
|
|
64
|
+
const pathname = this.#pathResolver
|
|
65
|
+
? await this.#pathResolver(rawPath)
|
|
66
|
+
: rawPath;
|
|
67
|
+
const ctx = new Context(req, pathname, method, this.env, args);
|
|
77
68
|
try {
|
|
78
69
|
const staticHandler = this.staticFile?.[`${method} ${pathname}`];
|
|
79
70
|
if (staticHandler) {
|
|
80
71
|
return staticHandler(ctx);
|
|
81
72
|
}
|
|
82
73
|
const route = this.router.search(method, pathname);
|
|
83
|
-
|
|
74
|
+
const mLen = route?.middlewares?.length;
|
|
75
|
+
const hLen = route?.handlers?.length;
|
|
76
|
+
if (!route || (hLen === 0 && mLen === 0)) {
|
|
84
77
|
return this.#notFound(ctx);
|
|
85
78
|
}
|
|
86
79
|
ctx.params = route.params;
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return result;
|
|
91
|
-
if (ctx.body !== undefined)
|
|
92
|
-
return ctx.send(ctx.body);
|
|
93
|
-
return this.#notFound(ctx);
|
|
94
|
-
}
|
|
95
|
-
let res = await this.#chain([
|
|
96
|
-
...route.middlewares,
|
|
97
|
-
...route.handlers,
|
|
98
|
-
])(ctx);
|
|
99
|
-
if (!res && ctx.body !== undefined) {
|
|
100
|
-
res = ctx.send(ctx.body);
|
|
80
|
+
if (hLen === 1 && mLen === 0) {
|
|
81
|
+
return ((await route.handlers[0](ctx)) ??
|
|
82
|
+
(ctx.body !== undefined ? ctx.send(ctx.body) : this.#notFound(ctx)));
|
|
101
83
|
}
|
|
102
|
-
|
|
103
|
-
return this.#notFound(ctx);
|
|
104
|
-
}
|
|
105
|
-
return res;
|
|
84
|
+
return await this.#chain(ctx, mLen, route.middlewares, hLen, route.handlers);
|
|
106
85
|
}
|
|
107
86
|
catch (err) {
|
|
108
|
-
|
|
87
|
+
if (!(err instanceof TezXError)) {
|
|
88
|
+
return this.#errorHandler?.(TezXError.internal(err?.message, err?.stack), ctx);
|
|
89
|
+
}
|
|
90
|
+
return this.#errorHandler?.(err, ctx);
|
|
109
91
|
}
|
|
110
92
|
}
|
|
111
93
|
async serve(req, ...args) {
|
package/index.d.ts
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
+
import { TezXError } from "./core/error.js";
|
|
1
2
|
import { Router } from "./core/router.js";
|
|
2
3
|
import { TezX } from "./core/server.js";
|
|
3
4
|
export type { Context as BaseContext } from "./core/context.js";
|
|
4
|
-
export type { NetAddr as AddressType, Callback, Ctx as Context, CookieOptions,
|
|
5
|
+
export type { NetAddr as AddressType, Callback, Ctx as Context, CookieOptions, FormDataOptions, HandlerType, HttpBaseResponse, HTTPMethod, Middleware, NextCallback, RequestHeaders, ResponseHeaders, ResponseInit, RouteMatchResult, RouteRegistry, Runtime, StaticFileArray, StaticServeOption, WebSocketCallback, WebSocketEvent, ReqHeaderKey, ResHeaderKey, ErrorHandler, ServeStatic, WebSocketOptions, } from "./types/index.js";
|
|
5
6
|
export type { TezXConfig } from "./core/server.js";
|
|
6
|
-
export type { RouterConfig } from "./core/router.js";
|
|
7
7
|
export type { TezXRequest } from "./core/request.js";
|
|
8
|
-
export {
|
|
8
|
+
export type { RouterConfig } from "./core/router.js";
|
|
9
|
+
export { Router, TezX, TezXError };
|
|
9
10
|
export declare let version: string;
|
|
10
11
|
declare const _default: {
|
|
11
12
|
Router: typeof Router;
|
|
12
13
|
TezX: typeof TezX;
|
|
13
14
|
version: string;
|
|
15
|
+
TezXError: typeof TezXError;
|
|
14
16
|
};
|
|
15
17
|
export default _default;
|