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.
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 -85
  8. package/cjs/core/error.js +41 -0
  9. package/cjs/core/request.js +6 -6
  10. package/cjs/core/router.js +4 -2
  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 +16 -23
  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 +20 -29
  29. package/core/context.d.ts +68 -59
  30. package/core/context.js +87 -86
  31. package/core/error.d.ts +95 -0
  32. package/core/error.js +37 -0
  33. package/core/request.d.ts +4 -4
  34. package/core/request.js +6 -6
  35. package/core/router.d.ts +11 -6
  36. package/core/router.js +4 -2
  37. package/core/server.js +45 -63
  38. package/index.d.ts +4 -2
  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 +16 -23
  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 +19 -28
  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,44 +30,20 @@ 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
  }
@@ -75,10 +51,7 @@ export class Context {
75
51
  this.#params = params;
76
52
  }
77
53
  get req() {
78
- if (!this.#req) {
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 = { ...this.#headers, ...init.headers };
95
- const status = init.status || this.#status;
96
- const statusText = init.statusText;
97
- 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
+ });
98
109
  }
99
110
  text(content, init) {
100
- return newResponse(content, "text/plain; charset=utf-8", init, this.#headers, this.#status);
111
+ return this.#newResponse(content, "text/plain; charset=utf-8", init);
101
112
  }
102
113
  html(strings, ...args) {
103
- let html = strings;
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(xml, init) {
117
- 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]);
118
118
  }
119
119
  json(json, init) {
120
- 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);
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, {}, this.#headers, this.#status);
127
+ return this.#newResponse(_body, contentType, init);
128
128
  }
129
129
  redirect(url, status = 302) {
130
- return new Response(null, {
131
- status: status,
132
- headers: { ...this.#headers, Location: url },
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
- let buf = await getFileBuffer(filePath);
139
- return newResponse(buf, "application/octet-stream", {
140
- status: 200,
141
- headers: {
142
- "Content-Disposition": `attachment; filename="${filename}"`,
143
- "Content-Length": buf.byteLength.toString(),
144
- }
145
- }, 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
+ });
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] || defaultMimeType;
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 || 200,
164
+ status: init?.status ?? this.#status,
164
165
  statusText: init?.statusText,
165
166
  headers,
166
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
  *
@@ -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 | {}>} Parsed JSON or empty object if not application/json.
74
+ * @returns {Promise<T>} Parsed JSON or empty object if not application/json.
75
75
  */
76
- json<T = any>(): Promise<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?.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;
@@ -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 && 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) {
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 #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, NextCallback, RequestHeaders, ResponseHeaders, ResponseInit, RouteMatchResult, RouteRegistry, Runtime, StaticFileArray, 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
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.10-beta";
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
  };