tezx 3.0.10-beta → 3.0.12-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 -85
- package/cjs/core/error.js +41 -0
- package/cjs/core/request.js +6 -6
- package/cjs/core/router.js +4 -2
- 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 +16 -23
- 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 +20 -29
- package/core/context.d.ts +68 -59
- package/core/context.js +87 -86
- package/core/error.d.ts +95 -0
- package/core/error.js +37 -0
- package/core/request.d.ts +4 -4
- package/core/request.js +6 -6
- package/core/router.d.ts +11 -6
- package/core/router.js +4 -2
- package/core/server.js +45 -63
- package/index.d.ts +4 -2
- 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 +16 -23
- 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 +19 -28
- 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,44 +30,20 @@ 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
|
}
|
|
@@ -75,10 +51,7 @@ export class Context {
|
|
|
75
51
|
this.#params = params;
|
|
76
52
|
}
|
|
77
53
|
get req() {
|
|
78
|
-
|
|
79
|
-
this.#req = new TezXRequest(this.rawRequest, this.method, this.pathname, this.#params);
|
|
80
|
-
}
|
|
81
|
-
return this.#req;
|
|
54
|
+
return (this.#req ??= new TezXRequest(this.rawRequest, this.method, this.pathname, this.#params));
|
|
82
55
|
}
|
|
83
56
|
get body() {
|
|
84
57
|
return this.#body;
|
|
@@ -90,66 +63,93 @@ export class Context {
|
|
|
90
63
|
this.#status = status;
|
|
91
64
|
return this;
|
|
92
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
|
+
}
|
|
93
88
|
newResponse(body, init = {}) {
|
|
94
|
-
const headers =
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
+
});
|
|
98
109
|
}
|
|
99
110
|
text(content, init) {
|
|
100
|
-
return newResponse(content, "text/plain; charset=utf-8", init
|
|
111
|
+
return this.#newResponse(content, "text/plain; charset=utf-8", init);
|
|
101
112
|
}
|
|
102
113
|
html(strings, ...args) {
|
|
103
|
-
|
|
104
|
-
if (Array.isArray(strings)) {
|
|
105
|
-
html = strings.reduce((result, str, i) => {
|
|
106
|
-
const value = args?.[i] ?? "";
|
|
107
|
-
return result + str + value;
|
|
108
|
-
}, "");
|
|
109
|
-
return newResponse(html, "text/html; charset=utf-8", {}, this.#headers, this.#status);
|
|
110
|
-
}
|
|
111
|
-
else {
|
|
112
|
-
let init = args?.[0];
|
|
113
|
-
return newResponse(html, "text/html; charset=utf-8", init, this.#headers, this.#status);
|
|
114
|
-
}
|
|
114
|
+
return this.#newResponse(toString(strings, args), "text/html; charset=utf-8", args?.[0]);
|
|
115
115
|
}
|
|
116
|
-
xml(
|
|
117
|
-
return newResponse(
|
|
116
|
+
xml(strings, ...args) {
|
|
117
|
+
return this.#newResponse(toString(strings, args), "text/html; charset=utf-8", args?.[0]);
|
|
118
118
|
}
|
|
119
119
|
json(json, init) {
|
|
120
|
-
return newResponse(JSON.stringify(json), "application/json; charset=utf-8", init
|
|
120
|
+
return this.#newResponse(JSON.stringify(json), "application/json; charset=utf-8", init);
|
|
121
121
|
}
|
|
122
122
|
send(body, init) {
|
|
123
123
|
let { body: _body, type } = determineContentTypeBody(body);
|
|
124
|
-
const contentType = init?.headers?.["Content-Type"]
|
|
125
|
-
init?.headers?.["content-type"]
|
|
124
|
+
const contentType = init?.headers?.["Content-Type"] ??
|
|
125
|
+
init?.headers?.["content-type"] ??
|
|
126
126
|
type;
|
|
127
|
-
return newResponse(_body, contentType,
|
|
127
|
+
return this.#newResponse(_body, contentType, init);
|
|
128
128
|
}
|
|
129
129
|
redirect(url, status = 302) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
});
|
|
130
|
+
const headers = new Headers(this.#headers);
|
|
131
|
+
headers.set("Location", url);
|
|
132
|
+
return new Response(null, { status, headers });
|
|
134
133
|
}
|
|
135
134
|
async download(filePath, filename) {
|
|
136
135
|
if (!(await fileExists(filePath)))
|
|
137
136
|
throw Error("File not found");
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
+
});
|
|
146
146
|
}
|
|
147
147
|
async sendFile(filePath, init) {
|
|
148
148
|
if (!(await fileExists(filePath)))
|
|
149
149
|
throw Error("File not found");
|
|
150
150
|
let size = await fileSize(filePath);
|
|
151
|
-
const ext = extensionExtract(filePath)
|
|
152
|
-
const mimeType = mimeTypes[ext]
|
|
151
|
+
const ext = extensionExtract(filePath);
|
|
152
|
+
const mimeType = mimeTypes[ext] ?? defaultMimeType;
|
|
153
153
|
let fileStream = await readStream(filePath);
|
|
154
154
|
let headers = {
|
|
155
155
|
"Content-Type": mimeType,
|
|
@@ -157,10 +157,11 @@ export class Context {
|
|
|
157
157
|
...init?.headers,
|
|
158
158
|
};
|
|
159
159
|
let filename = init?.filename;
|
|
160
|
-
if (filename)
|
|
160
|
+
if (filename) {
|
|
161
161
|
headers["Content-Disposition"] = `attachment; filename="${filename}"`;
|
|
162
|
+
}
|
|
162
163
|
return this.newResponse(fileStream, {
|
|
163
|
-
status: init?.status
|
|
164
|
+
status: init?.status ?? this.#status,
|
|
164
165
|
statusText: init?.statusText,
|
|
165
166
|
headers,
|
|
166
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
|
*
|
|
@@ -71,9 +71,9 @@ export declare class TezXRequest<Path extends string = any> {
|
|
|
71
71
|
* Parses the request body as JSON.
|
|
72
72
|
*
|
|
73
73
|
* @template T - Expected type of parsed JSON object.
|
|
74
|
-
* @returns {Promise<T
|
|
74
|
+
* @returns {Promise<T>} Parsed JSON or empty object if not application/json.
|
|
75
75
|
*/
|
|
76
|
-
json<T
|
|
76
|
+
json<T extends Record<string, any>>(): Promise<T>;
|
|
77
77
|
/**
|
|
78
78
|
* Parses and returns the form data from the incoming HTTP request.
|
|
79
79
|
*
|
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;
|
|
@@ -163,7 +163,9 @@ export class Router {
|
|
|
163
163
|
}
|
|
164
164
|
#routeAddTriNode(path, router) {
|
|
165
165
|
this.env = { ...this.env, ...router.env };
|
|
166
|
-
if (this.router?.name &&
|
|
166
|
+
if (this.router?.name &&
|
|
167
|
+
router.router?.name &&
|
|
168
|
+
this.router?.name !== router.router?.name) {
|
|
167
169
|
throw new Error(`Router name mismatch: expected "${this.router.name}", got "${router.router.name}"`);
|
|
168
170
|
}
|
|
169
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
7
|
export type { TezXRequest } from "./core/request.js";
|
|
7
8
|
export type { RouterConfig } from "./core/router.js";
|
|
8
|
-
export { Router, TezX };
|
|
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;
|
package/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { TezXError } from "./core/error.js";
|
|
1
2
|
import { Router } from "./core/router.js";
|
|
2
3
|
import { TezX } from "./core/server.js";
|
|
3
|
-
export { Router, TezX };
|
|
4
|
-
export let version = "3.0.
|
|
4
|
+
export { Router, TezX, TezXError };
|
|
5
|
+
export let version = "3.0.12-beta";
|
|
5
6
|
export default {
|
|
6
7
|
Router,
|
|
7
8
|
TezX,
|
|
8
9
|
version,
|
|
10
|
+
TezXError,
|
|
9
11
|
};
|