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.
Files changed (90) hide show
  1. package/bun/index.d.ts +4 -2
  2. package/bun/index.js +3 -2
  3. package/bun/ws.d.ts +37 -0
  4. package/bun/ws.js +23 -0
  5. package/cjs/bun/index.js +37 -5
  6. package/cjs/bun/ws.js +25 -0
  7. package/cjs/core/context.js +86 -88
  8. package/cjs/core/error.js +41 -0
  9. package/cjs/core/request.js +6 -6
  10. package/cjs/core/router.js +6 -6
  11. package/cjs/core/server.js +45 -63
  12. package/cjs/index.js +5 -2
  13. package/cjs/middleware/basic-auth.js +28 -54
  14. package/cjs/middleware/bearer-auth.js +34 -0
  15. package/cjs/middleware/cors.js +14 -24
  16. package/cjs/middleware/index.js +25 -0
  17. package/cjs/middleware/logger.js +6 -3
  18. package/cjs/middleware/pagination.js +1 -1
  19. package/cjs/middleware/powered-by.js +1 -1
  20. package/cjs/middleware/rate-limiter.js +20 -7
  21. package/cjs/middleware/request-id.js +4 -7
  22. package/cjs/middleware/sanitize-headers.js +8 -40
  23. package/cjs/middleware/xss-protection.js +2 -6
  24. package/cjs/registry/RadixRouter.js +72 -23
  25. package/cjs/utils/cookie.js +1 -1
  26. package/cjs/utils/rateLimit.js +2 -2
  27. package/cjs/utils/regexRouter.js +1 -0
  28. package/cjs/utils/response.js +21 -30
  29. package/core/context.d.ts +68 -68
  30. package/core/context.js +87 -89
  31. package/core/error.d.ts +95 -0
  32. package/core/error.js +37 -0
  33. package/core/request.d.ts +2 -2
  34. package/core/request.js +6 -6
  35. package/core/router.d.ts +11 -6
  36. package/core/router.js +6 -6
  37. package/core/server.js +45 -63
  38. package/index.d.ts +5 -3
  39. package/index.js +4 -2
  40. package/middleware/basic-auth.d.ts +38 -66
  41. package/middleware/basic-auth.js +28 -54
  42. package/middleware/bearer-auth.d.ts +52 -0
  43. package/middleware/bearer-auth.js +30 -0
  44. package/middleware/cors.d.ts +7 -21
  45. package/middleware/cors.js +14 -24
  46. package/middleware/index.d.ts +9 -0
  47. package/middleware/index.js +9 -0
  48. package/middleware/logger.d.ts +3 -1
  49. package/middleware/logger.js +6 -3
  50. package/middleware/pagination.d.ts +8 -6
  51. package/middleware/pagination.js +1 -1
  52. package/middleware/powered-by.js +1 -1
  53. package/middleware/rate-limiter.d.ts +0 -4
  54. package/middleware/rate-limiter.js +20 -7
  55. package/middleware/request-id.d.ts +1 -1
  56. package/middleware/request-id.js +3 -6
  57. package/middleware/sanitize-headers.d.ts +3 -11
  58. package/middleware/sanitize-headers.js +8 -40
  59. package/middleware/xss-protection.d.ts +1 -1
  60. package/middleware/xss-protection.js +1 -5
  61. package/package.json +6 -1
  62. package/registry/RadixRouter.js +72 -23
  63. package/types/index.d.ts +2 -1
  64. package/utils/cookie.js +1 -1
  65. package/utils/rateLimit.d.ts +1 -2
  66. package/utils/rateLimit.js +2 -2
  67. package/utils/regexRouter.js +1 -0
  68. package/utils/response.d.ts +12 -14
  69. package/utils/response.js +20 -29
  70. package/cjs/middleware/cache-control.js +0 -93
  71. package/cjs/middleware/detect-bot.js +0 -66
  72. package/cjs/middleware/detect-locale.js +0 -45
  73. package/cjs/middleware/i18n.js +0 -93
  74. package/cjs/middleware/lazy-loader.js +0 -74
  75. package/cjs/middleware/request-timeout.js +0 -43
  76. package/cjs/middleware/secure-headers.js +0 -43
  77. package/middleware/cache-control.d.ts +0 -56
  78. package/middleware/cache-control.js +0 -56
  79. package/middleware/detect-bot.d.ts +0 -111
  80. package/middleware/detect-bot.js +0 -62
  81. package/middleware/detect-locale.d.ts +0 -56
  82. package/middleware/detect-locale.js +0 -41
  83. package/middleware/i18n.d.ts +0 -102
  84. package/middleware/i18n.js +0 -89
  85. package/middleware/lazy-loader.d.ts +0 -73
  86. package/middleware/lazy-loader.js +0 -70
  87. package/middleware/request-timeout.d.ts +0 -26
  88. package/middleware/request-timeout.js +0 -39
  89. package/middleware/secure-headers.d.ts +0 -78
  90. 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, newResponse } from "../utils/response.js";
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 = { connection: "keep-alive" };
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, options) {
18
- this.#args = options?.args;
19
+ constructor(req, pathname, method, env, args) {
20
+ this.#args = args;
19
21
  this.rawRequest = req;
20
- this.pathname = options?.pathname;
21
- this.env = options?.env || {};
22
- this.method = options?.method;
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
- header(header) {
34
- if (header) {
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
- let _key = key.toLowerCase();
44
- let append = options?.append || _key == 'set-cookie';
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
- if (!this.res) {
67
- delete this.#headers[_key];
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
- this.res.headers.delete(_key);
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
- if (!this.#req) {
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 = { ...this.#headers, ...init.headers };
98
- const status = init.status || this.#status;
99
- const statusText = init.statusText;
100
- return new Response(body, { status, statusText, headers: headers });
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, this.#headers, this.#status);
111
+ return this.#newResponse(content, "text/plain; charset=utf-8", init);
104
112
  }
105
113
  html(strings, ...args) {
106
- let html = strings;
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(xml, init) {
120
- return newResponse(xml, "text/xml; charset=utf-8", init, this.#headers, this.#status);
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, this.#headers, this.#status);
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, {}, this.#headers, this.#status);
127
+ return this.#newResponse(_body, contentType, init);
131
128
  }
132
129
  redirect(url, status = 302) {
133
- return new Response(null, {
134
- status: status,
135
- headers: { Location: url },
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
- let buf = await getFileBuffer(filePath);
142
- return newResponse(buf, "application/octet-stream", {
143
- status: 200,
144
- headers: {
145
- "Content-Disposition": `attachment; filename="${filename}"`,
146
- "Content-Length": buf.byteLength.toString(),
147
- }
148
- }, this.#headers, this.#status);
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] || defaultMimeType;
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 || 200,
164
+ status: init?.status ?? this.#status,
167
165
  statusText: init?.statusText,
168
166
  headers,
169
167
  });
@@ -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, ReqHeaderKey, HTTPMethod, NetAddr, RequestHeaders } from "../types/index.js";
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, any>;
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?.toLowerCase());
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
- for (const [key, value] of this.#rawRequest.headers.entries()) {
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
- * Defaults to CombineRouteRegistry.
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
- * It is a record of key-value pairs where the key is the variable name
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 the base path for the router. This is useful for grouping
16
- * routes under a specific path prefix.
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, skip = false) {
129
+ #addRoute(method, path, handlers) {
130
130
  let pattern = `/${sanitizePathSplitBasePath(this.basePath, path).join("/")}`;
131
- if (!skip) {
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 && router.router?.name && this.router?.name !== router.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 #resolvePath(pathname) {
28
- let resolvePath = pathname;
29
- if (this.#pathResolver) {
30
- resolvePath = await this.#pathResolver(pathname);
31
- GlobalConfig.debugging.warn(`${colorText(" PATH RESOLVE ", "white")} ${colorText(pathname, "red")} ➞ ${colorText(resolvePath, "cyan")}`);
32
- }
33
- if (typeof resolvePath !== "string") {
34
- throw new Error(`Path resolution failed: expected a string, got ${typeof resolvePath}`);
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 at index ${i} must be a function`);
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
- const result = await fn(ctx, () => dispatch(i + 1));
57
- if (result instanceof Response) {
58
- ctx.res = result;
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
- await dispatch(0);
63
- return ctx.res;
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 = await this.#resolvePath(getPathname(req.url));
71
- let ctx = new Context(req, {
72
- pathname,
73
- method,
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
- if (!route || (route.handlers.length === 0 && route.middlewares.length === 0)) {
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 (route.handlers.length === 1 && route.middlewares.length === 0) {
88
- const result = await route.handlers[0](ctx);
89
- if (result)
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
- if (!res) {
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
- return this.#errorHandler(err, ctx);
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, ErrorHandler, FormDataOptions, HandlerType, HttpBaseResponse, HTTPMethod, Middleware, StaticFileArray, NextCallback, RequestHeaders, ResponseHeaders, ResponseInit, RouteMatchResult, RouteRegistry, Runtime, StaticServeOption, WebSocketCallback, WebSocketEvent, WebSocketOptions, } from "./types/index.js";
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 { Router, TezX };
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;