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
@@ -39,20 +39,18 @@ class RadixRouter {
39
39
  search(method, path) {
40
40
  let params = {};
41
41
  let middlewares = [];
42
- const { success, node } = this._match(method, this.root, (0, index_js_1.sanitizePathSplit)(path), 0, params, middlewares, new Set());
42
+ const { success, node } = this._match(method, this.root, path?.split("/")?.filter(Boolean), 0, params, middlewares);
43
43
  if (success && node) {
44
44
  const handlers = node.handlers?.[method] ?? [];
45
45
  return { method, params, handlers, middlewares };
46
46
  }
47
47
  return { method, params: {}, handlers: [], middlewares };
48
48
  }
49
- _match(method, node, segments, index, params, middlewares, seen) {
50
- if (node.handlers?.ALL) {
51
- for (const mw of node.handlers.ALL) {
52
- if (!seen.has(mw)) {
53
- seen.add(mw);
54
- middlewares.push(mw);
55
- }
49
+ _match(method, node, segments, index, params, middlewares) {
50
+ if (node?.handlers?.ALL) {
51
+ const mw = node.handlers?.ALL;
52
+ for (let i = 0; i < mw.length; i++) {
53
+ middlewares.push(mw[i]);
56
54
  }
57
55
  }
58
56
  if (index === segments.length) {
@@ -61,40 +59,59 @@ class RadixRouter {
61
59
  const opt = node.children[":"];
62
60
  if (opt?.isOptional) {
63
61
  params[opt.paramName] = null;
64
- return this._match(method, opt, segments, index, params, middlewares, seen);
62
+ return this._match(method, opt, segments, index, params, middlewares);
65
63
  }
66
64
  return { success: false, node: node };
67
65
  }
68
66
  const wc = node.children["*"];
69
- if (wc?.handlers?.ALL) {
70
- for (const mw of wc.handlers.ALL) {
71
- if (!seen.has(mw)) {
72
- seen.add(mw);
73
- middlewares.push(mw);
74
- }
75
- }
76
- }
77
67
  const seg = segments[index];
78
68
  if (node.children[seg]) {
79
- const res = this._match(method, node.children[seg], segments, index + 1, params, middlewares, seen);
80
- if (res.success)
69
+ const res = this._match(method, node.children[seg], segments, index + 1, params, middlewares);
70
+ if (res.success) {
71
+ if (wc?.handlers?.ALL) {
72
+ const mw = wc.handlers?.ALL;
73
+ for (let i = 0; i < mw.length; i++) {
74
+ middlewares.push(mw[i]);
75
+ }
76
+ }
81
77
  return res;
78
+ }
82
79
  }
83
80
  const dyn = node.children[":"];
84
81
  if (dyn) {
85
82
  params[dyn.paramName] = seg;
86
- const res = this._match(method, dyn, segments, index + 1, params, middlewares, seen);
87
- if (res.success)
83
+ const res = this._match(method, dyn, segments, index + 1, params, middlewares);
84
+ if (res.success) {
85
+ if (wc?.handlers?.ALL) {
86
+ const mw = wc.handlers?.ALL;
87
+ for (let i = 0; i < mw.length; i++) {
88
+ middlewares.push(mw[i]);
89
+ }
90
+ }
88
91
  return res;
92
+ }
89
93
  if (dyn.isOptional) {
90
94
  params[dyn.paramName] = null;
91
- const skip = this._match(method, dyn, segments, index, params, middlewares, seen);
92
- if (skip.success)
95
+ const skip = this._match(method, dyn, segments, index, params, middlewares);
96
+ if (skip.success) {
97
+ if (wc?.handlers?.ALL) {
98
+ const mw = wc.handlers?.ALL;
99
+ for (let i = 0; i < mw.length; i++) {
100
+ middlewares.push(mw[i]);
101
+ }
102
+ }
93
103
  return skip;
104
+ }
94
105
  }
95
106
  }
96
107
  if (wc) {
97
108
  let wildcard = segments.slice(index).join("/");
109
+ if (wc?.handlers?.ALL) {
110
+ const mw = wc.handlers?.ALL;
111
+ for (let i = 0; i < mw.length; i++) {
112
+ middlewares.push(mw[i]);
113
+ }
114
+ }
98
115
  if (wildcard) {
99
116
  params[wc.paramName] = wildcard;
100
117
  return { node: wc, success: true };
@@ -128,3 +145,35 @@ class RadixRouter {
128
145
  }
129
146
  }
130
147
  exports.RadixRouter = RadixRouter;
148
+ const routes = [
149
+ "/",
150
+ "/users",
151
+ "/users/:id",
152
+ "/users/:id/profile",
153
+ "/posts/:postId?",
154
+ "/files/*",
155
+ "/admin/settings",
156
+ "/search/:term?",
157
+ "/categories/:categoryId/products/:productId",
158
+ "/about",
159
+ ];
160
+ const testPaths = [
161
+ "/",
162
+ "/users",
163
+ "/users/123",
164
+ "/users/123/profile",
165
+ "/posts",
166
+ "/posts/456",
167
+ "/files/path/to/file.txt",
168
+ "/admin/settings",
169
+ "/search",
170
+ "/search/nodejs",
171
+ "/categories/12/products/999",
172
+ "/notfound",
173
+ ];
174
+ const router = new RadixRouter();
175
+ let x = function xx() {
176
+ return {
177
+ body: "3453455",
178
+ };
179
+ };
@@ -34,7 +34,7 @@ function allCookies(ctx) {
34
34
  return cookies;
35
35
  }
36
36
  function setCookie(ctx, name, value, options) {
37
- ctx.setHeader("Set-Cookie", `${name}=${value}; ${serializeOptions(options || {})}`);
37
+ ctx.setHeader("Set-Cookie", `${name}=${value}; ${serializeOptions(options ?? {})}`);
38
38
  }
39
39
  function deleteCookie(ctx, name, options) {
40
40
  ctx.setHeader("Set-Cookie", `${name}=; ${serializeOptions({ ...options, maxAge: 0, expires: new Date(0) })}`);
@@ -17,8 +17,8 @@ function createRateLimitDefaultStorage() {
17
17
  },
18
18
  };
19
19
  }
20
- function isRateLimit(ctx, key, store, maxRequests, windowMs) {
21
- store.clearExpired();
20
+ function isRateLimit(key, store, maxRequests, windowMs) {
21
+ store?.clearExpired();
22
22
  const now = Date.now();
23
23
  let entry = store.get(key) || { count: 0, resetTime: now + windowMs };
24
24
  if (now < entry.resetTime) {
@@ -19,6 +19,7 @@ function compileRegexRoute(seg) {
19
19
  const name = seg.slice(1) || "*";
20
20
  paramNames.push(name);
21
21
  regexStr += `\\/(.+)`;
22
+ break;
22
23
  }
23
24
  else {
24
25
  regexStr += `\\/${seg}`;
@@ -2,8 +2,10 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.notFoundResponse = void 0;
4
4
  exports.handleErrorResponse = handleErrorResponse;
5
+ exports.toString = toString;
5
6
  exports.determineContentTypeBody = determineContentTypeBody;
6
- exports.newResponse = newResponse;
7
+ const config_js_1 = require("../core/config.js");
8
+ const error_js_1 = require("../core/error.js");
7
9
  let notFoundResponse = (ctx) => {
8
10
  const { method, pathname } = ctx;
9
11
  return ctx.text(`${method}: '${pathname}' could not find\n`, {
@@ -11,14 +13,24 @@ let notFoundResponse = (ctx) => {
11
13
  });
12
14
  };
13
15
  exports.notFoundResponse = notFoundResponse;
14
- async function handleErrorResponse(message = new Error("Internal Server Error"), ctx) {
15
- let error = message;
16
- if (message instanceof Error) {
17
- error = message.stack;
16
+ async function handleErrorResponse(err = error_js_1.TezXError.internal(), ctx) {
17
+ if (err instanceof error_js_1.TezXError) {
18
+ config_js_1.GlobalConfig.debugging.error(err.details ?? err?.message);
19
+ return ctx.status(err.statusCode ?? 500).send(err.details ?? err?.message ?? "Internal Server Error");
18
20
  }
19
- return ctx.text(error, {
20
- status: 500,
21
- });
21
+ return await handleErrorResponse(error_js_1.TezXError.internal(), ctx);
22
+ }
23
+ function toString(input, values) {
24
+ if (typeof input === "string") {
25
+ return input;
26
+ }
27
+ let result = "";
28
+ for (let i = 0; i < input.length; i++) {
29
+ result += input[i];
30
+ if (i < values.length)
31
+ result += values[i];
32
+ }
33
+ return result;
22
34
  }
23
35
  function determineContentTypeBody(body) {
24
36
  if (typeof body === "string" ||
@@ -38,7 +50,7 @@ function determineContentTypeBody(body) {
38
50
  if (typeof Blob !== "undefined" && body instanceof Blob) {
39
51
  return { type: body.type || "application/octet-stream", body };
40
52
  }
41
- if (typeof body === "object" && typeof body.pipe === "function") {
53
+ if (typeof body === "object" && typeof body?.pipe === "function") {
42
54
  return { type: "application/octet-stream", body };
43
55
  }
44
56
  if (typeof body === "object") {
@@ -49,24 +61,3 @@ function determineContentTypeBody(body) {
49
61
  }
50
62
  return { type: "text/plain; charset=utf-8", body: String(body ?? "") };
51
63
  }
52
- function newResponse(body, type, init = {}, baseHeaders, defaultStatus) {
53
- let headers;
54
- if (init.headers) {
55
- headers = {
56
- "Content-Type": type,
57
- ...baseHeaders,
58
- ...init.headers,
59
- };
60
- }
61
- else {
62
- headers = {
63
- "Content-Type": type,
64
- ...baseHeaders,
65
- };
66
- }
67
- return new Response(body, {
68
- status: init.status || defaultStatus,
69
- statusText: init.statusText,
70
- headers,
71
- });
72
- }
package/core/context.d.ts CHANGED
@@ -1,19 +1,6 @@
1
- import { HttpBaseResponse, ResHeaderKey, ResponseHeaders, ResponseInit } from "../types/index.js";
1
+ import { HttpBaseResponse, ResHeaderKey, ResponseInit } from "../types/index.js";
2
2
  import { TezXRequest } from "./request.js";
3
- export type ContextOptions<T> = {
4
- pathname: string;
5
- method: string;
6
- env: Record<string, any> & T;
7
- args?: any[];
8
- };
9
- /**
10
- * Represents the context of an HTTP request lifecycle.
11
- * Provides utilities to handle request, response, headers, and body.
12
- *
13
- * @template T - The type for environment variables or shared state.
14
- * @template Path - The string literal type for route paths.
15
- */
16
- export declare class Context<T extends Record<string, any> = {}, Path extends string = any> {
3
+ export declare class Context<TEnv extends Record<string, any> = {}, TPath extends string = any> {
17
4
  #private;
18
5
  [key: string]: any;
19
6
  /**
@@ -22,11 +9,29 @@ export declare class Context<T extends Record<string, any> = {}, Path extends st
22
9
  * @type {Request}
23
10
  */
24
11
  rawRequest: Request;
12
+ /**
13
+ * The URL associated with the current context.
14
+ *
15
+ * This is a read-only string representing the resource location or endpoint.
16
+ */
17
+ readonly url: string;
18
+ /**
19
+ * The native Response object associated with this context, if available.
20
+ *
21
+ * This property is set when a response has been created or is being manipulated directly.
22
+ * It may be undefined if the response has not yet been constructed.
23
+ *
24
+ * @type {Response | undefined}
25
+ * @remarks
26
+ * - When present, headers and status should be set directly on this object.
27
+ * - When undefined, internal header and status management is used.
28
+ */
29
+ res?: Response;
25
30
  /**
26
31
  * Environment variables or shared state accessible in context.
27
- * @type {Record<string, any> & T}
32
+ * @type {Record<string, any> & TEnv}
28
33
  */
29
- env: Record<string, any> & T;
34
+ env: Record<string, any> & TEnv;
30
35
  /**
31
36
  * Request pathname (URL path without domain).
32
37
  * @readonly
@@ -43,14 +48,9 @@ export declare class Context<T extends Record<string, any> = {}, Path extends st
43
48
  * Creates a new context instance.
44
49
  *
45
50
  * @param {Request} req - The native Request object.
46
- * @param {ContextOptions<T>} options - Context options including pathname, method, env, params, and args.
51
+ * @param {ContextOptions<TEnv>} options - Context options including pathname, method, env, params, and args.
47
52
  */
48
- constructor(req: Request, options: ContextOptions<T>);
49
- /**
50
- * Returns the full URL string of the request, including query parameters.
51
- * @type {string}
52
- */
53
- get url(): string;
53
+ constructor(req: Request, pathname: string, method: string, env: Record<string, any> & TEnv, args: any[]);
54
54
  /**
55
55
  * Gets the current HTTP status code.
56
56
  * @returns {number} The HTTP status code.
@@ -62,19 +62,34 @@ export declare class Context<T extends Record<string, any> = {}, Path extends st
62
62
  */
63
63
  set setStatus(code: number);
64
64
  /**
65
- * Retrieves a specific header value or all headers.
65
+ * Access the response headers.
66
66
  *
67
- * @param {ResHeaderKey} [header] - Optional header name (case-insensitive, internally lowercased).
68
- * @returns {string | undefined | ResponseHeaders} - Returns the header value if key is provided,
69
- * or the entire headers object if no key is passed.
67
+ * @remarks
68
+ * This returns the native {@link Headers} object associated with the response.
69
+ * It gives you full control over reading, setting, appending, and deleting headers
70
+ * using the standard Web Headers API.
70
71
  *
71
72
  * @example
72
- * ctx.header('content-type'); // → 'application/json'
73
- * ctx.header(); // { 'content-type': 'application/json' }
73
+ * ```ts
74
+ * // Get a header value
75
+ * const contentType = ctx.headers.get("content-type")
76
+ *
77
+ * // Set or overwrite a header
78
+ * ctx.headers.set("x-powered-by", "tezx")
79
+ *
80
+ * // Append multiple values
81
+ * ctx.headers.append("set-cookie", "id=123")
82
+ * ctx.headers.append("set-cookie", "token=xyz")
83
+ *
84
+ * // Iterate all headers
85
+ * for (const [key, value] of ctx.headers.entries()) {
86
+ * console.log(key, value)
87
+ * }
88
+ * ```
89
+ *
90
+ * @returns {Headers} The response headers object.
74
91
  */
75
- header(): Record<string, string>;
76
- header(header: ResHeaderKey): string | undefined;
77
- protected set clearHeader(header: ResponseHeaders);
92
+ get headers(): Headers;
78
93
  /**
79
94
  * Sets or appends a header to the response.
80
95
  *
@@ -91,35 +106,15 @@ export declare class Context<T extends Record<string, any> = {}, Path extends st
91
106
  setHeader(key: ResHeaderKey, value: string, options?: {
92
107
  append?: boolean;
93
108
  }): this;
94
- /**
95
- * Deletes a response header by key.
96
- *
97
- * If the native Response object is not yet available (`this.res` is falsy),
98
- * it removes the header from the internal headers store.
99
- * If the native Response object is available, it removes the header directly from the response headers.
100
- *
101
- * @param {ResHeaderKey} key - The name of the header to delete (case-insensitive).
102
- * @returns {this} Returns the current instance for method chaining.
103
- */
104
- deleteHeader(key: ResHeaderKey): this;
105
- /**
106
- * Gets the route parameters extracted from the URL.
107
- *
108
- * @returns {Record<string, any>} An object containing key-value pairs of route parameters.
109
- *
110
- * @example
111
- * // For route `/user/:id` and URL `/user/123`, it returns: { id: "123" }
112
- */
113
- get params(): Record<string, any>;
114
109
  protected set params(params: Record<string, any>);
115
110
  /**
116
111
  * Gets the wrapped request object (`TezXRequest`).
117
112
  *
118
113
  * Lazily initializes the wrapped request on first access.
119
114
  *
120
- * @returns {TezXRequest<Path>} The wrapped request.
115
+ * @returns {TezXRequest<TPath>} The wrapped request.
121
116
  */
122
- get req(): TezXRequest<Path>;
117
+ get req(): TezXRequest<TPath>;
123
118
  /**
124
119
  * Gets the response body.
125
120
  * @returns {*} The response body.
@@ -149,7 +144,7 @@ export declare class Context<T extends Record<string, any> = {}, Path extends st
149
144
  * @returns {HttpBaseResponse} Response object suitable for runtime.
150
145
  * @protected
151
146
  */
152
- newResponse(body: BodyInit | null, init?: ResponseInit): HttpBaseResponse;
147
+ newResponse(body: BodyInit | null, init?: ResponseInit): Response;
153
148
  /**
154
149
  * Sends a plain text response.
155
150
  *
@@ -163,28 +158,33 @@ export declare class Context<T extends Record<string, any> = {}, Path extends st
163
158
  /**
164
159
  * Sends an HTML response.
165
160
  *
166
- * Can be used as a template literal or simple HTML string.
161
+ * Supports both:
162
+ * - Simple string: `ctx.html("<h1>Hello</h1>")`
163
+ * - Template literal: `ctx.html`<h1>${title}</h1>``
167
164
  *
168
- * @param {string | readonly string[]} strings - HTML string or template literals.
169
- * @param {...any[]} [args] - Optional values for template literals or ResponseInit.
170
- * @returns {HttpBaseResponse} A properly constructed HTML response.
165
+ * Minimizes intermediate string allocations for lower GC overhead.
171
166
  *
172
- * @example
173
- * ctx.html("<h1>Hello</h1>");
174
- * ctx.html`<h1>${title}</h1>`;
167
+ * @param {string | readonly string[]} strings - HTML string or template literal array.
168
+ * @param {...any[]} args - Values for template literals or a ResponseInit object if using a string.
169
+ * @returns {HttpBaseResponse} Constructed HTML response.
175
170
  */
176
171
  html(strings: string, init?: ResponseInit): HttpBaseResponse;
177
172
  html(strings: readonly string[], ...values: any[]): HttpBaseResponse;
178
173
  /**
179
174
  * Sends an XML response.
180
175
  *
181
- * Automatically sets Content-Type to `application/xml; charset=utf-8`.
176
+ * Supports both:
177
+ * - Simple string: `ctx.xml("<note><to>User</to></note>")`
178
+ * - Template literal: `ctx.xml`<note><to>${user}</to></note>``
182
179
  *
183
- * @param {string} xml - XML string.
184
- * @param {ResponseInit} [init] - Optional response init.
185
- * @returns {HttpBaseResponse} The response object.
180
+ * Minimizes intermediate string allocations for lower GC overhead.
181
+ *
182
+ * @param {string | readonly string[]} strings - XML string or template literal array.
183
+ * @param {...any[]} args - Values for template literals or a ResponseInit object if using a string.
184
+ * @returns {HttpBaseResponse} Constructed XML response.
186
185
  */
187
- xml(xml: string, init?: ResponseInit): HttpBaseResponse;
186
+ xml(strings: string, init?: ResponseInit): HttpBaseResponse;
187
+ xml(strings: readonly string[], ...values: any[]): HttpBaseResponse;
188
188
  /**
189
189
  * Sends a JSON response.
190
190
  *