tezx 3.0.14-beta → 3.0.14-beta2

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 (102) hide show
  1. package/bun/env.d.ts +5 -0
  2. package/bun/env.js +44 -0
  3. package/bun/index.d.ts +1 -1
  4. package/bun/index.js +1 -1
  5. package/bun/ws.js +4 -3
  6. package/cjs/bun/env.js +47 -0
  7. package/cjs/bun/index.js +1 -1
  8. package/cjs/bun/ws.js +4 -3
  9. package/cjs/core/context.js +4 -3
  10. package/cjs/core/error.js +8 -0
  11. package/cjs/core/request.js +3 -2
  12. package/cjs/core/router.js +11 -17
  13. package/cjs/core/server.js +14 -9
  14. package/cjs/deno/env.js +2 -1
  15. package/cjs/deno/serveStatic.js +2 -1
  16. package/cjs/deno/ws.js +4 -3
  17. package/cjs/helper/index.js +12 -10
  18. package/cjs/index.js +1 -1
  19. package/cjs/jwt/node.js +94 -0
  20. package/cjs/jwt/web.js +178 -0
  21. package/cjs/middleware/basic-auth.js +9 -14
  22. package/cjs/middleware/bearer-auth.js +5 -5
  23. package/cjs/middleware/cache-control.js +44 -0
  24. package/cjs/middleware/cors.js +1 -1
  25. package/cjs/middleware/detect-bot.js +57 -0
  26. package/cjs/middleware/i18n.js +92 -0
  27. package/cjs/middleware/index.js +5 -0
  28. package/cjs/middleware/logger.js +3 -2
  29. package/cjs/middleware/rate-limiter.js +1 -1
  30. package/cjs/middleware/sanitize-headers.js +1 -1
  31. package/cjs/middleware/secure-headers copy.js +143 -0
  32. package/cjs/middleware/secure-headers.js +157 -0
  33. package/cjs/node/env.js +4 -3
  34. package/cjs/node/serveStatic.js +2 -1
  35. package/cjs/node/ws.js +3 -2
  36. package/cjs/registry/RadixRouter.js +2 -33
  37. package/cjs/utils/buffer.js +17 -0
  38. package/cjs/utils/file.js +28 -6
  39. package/cjs/utils/generateID.js +10 -0
  40. package/cjs/utils/response.js +3 -1
  41. package/core/context.d.ts +3 -3
  42. package/core/context.js +4 -3
  43. package/core/error.d.ts +1 -0
  44. package/core/error.js +7 -0
  45. package/core/request.js +3 -2
  46. package/core/router.d.ts +3 -8
  47. package/core/router.js +11 -17
  48. package/core/server.d.ts +10 -23
  49. package/core/server.js +15 -10
  50. package/deno/env.js +2 -1
  51. package/deno/index.d.ts +1 -1
  52. package/deno/serveStatic.js +2 -1
  53. package/deno/ws.d.ts +1 -1
  54. package/deno/ws.js +4 -3
  55. package/helper/index.d.ts +7 -6
  56. package/helper/index.js +7 -6
  57. package/index.d.ts +5 -2
  58. package/index.js +1 -1
  59. package/jwt/node.d.ts +39 -0
  60. package/jwt/node.js +87 -0
  61. package/jwt/web.d.ts +14 -0
  62. package/jwt/web.js +174 -0
  63. package/middleware/basic-auth.d.ts +2 -1
  64. package/middleware/basic-auth.js +9 -14
  65. package/middleware/bearer-auth.d.ts +2 -1
  66. package/middleware/bearer-auth.js +5 -5
  67. package/middleware/cache-control.d.ts +30 -0
  68. package/middleware/cache-control.js +40 -0
  69. package/middleware/cors.js +1 -1
  70. package/middleware/detect-bot.d.ts +113 -0
  71. package/middleware/detect-bot.js +53 -0
  72. package/middleware/i18n.d.ts +194 -0
  73. package/middleware/i18n.js +88 -0
  74. package/middleware/index.d.ts +5 -0
  75. package/middleware/index.js +5 -0
  76. package/middleware/logger.d.ts +1 -1
  77. package/middleware/logger.js +3 -2
  78. package/middleware/rate-limiter.d.ts +3 -2
  79. package/middleware/rate-limiter.js +1 -1
  80. package/middleware/sanitize-headers.js +1 -1
  81. package/middleware/secure-headers copy.d.ts +15 -0
  82. package/middleware/secure-headers copy.js +136 -0
  83. package/middleware/secure-headers.d.ts +132 -0
  84. package/middleware/secure-headers.js +153 -0
  85. package/node/env.js +4 -3
  86. package/node/serveStatic.d.ts +8 -0
  87. package/node/serveStatic.js +2 -1
  88. package/node/ws.d.ts +1 -1
  89. package/node/ws.js +3 -2
  90. package/package.json +12 -1
  91. package/registry/RadixRouter.js +2 -33
  92. package/types/index.d.ts +1 -1
  93. package/utils/buffer.d.ts +1 -0
  94. package/utils/buffer.js +14 -0
  95. package/utils/file.d.ts +6 -2
  96. package/utils/file.js +27 -6
  97. package/utils/generateID.d.ts +9 -0
  98. package/utils/generateID.js +9 -0
  99. package/utils/response.js +3 -1
  100. package/cjs/utils/regexRouter.js +0 -57
  101. package/utils/regexRouter.d.ts +0 -66
  102. package/utils/regexRouter.js +0 -52
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.secureHeaders = void 0;
4
+ const index_js_1 = require("../helper/index.js");
5
+ const joinSrc = (v) => typeof v === "string" ? v : v.join(" ");
6
+ const buildCSPString = (cspObj) => {
7
+ const parts = [];
8
+ for (const key in cspObj)
9
+ parts.push(`${key} ${joinSrc(cspObj[key])}`);
10
+ return parts.join("; ");
11
+ };
12
+ const secureHeaders = (userOpts = {}) => {
13
+ const defaultPresets = {
14
+ strict: {
15
+ preset: "strict",
16
+ hsts: { maxAge: 63072000, includeSubDomains: true, preload: true },
17
+ frameGuard: "DENY",
18
+ noSniff: true,
19
+ xssProtection: true,
20
+ referrerPolicy: "strict-origin-when-cross-origin",
21
+ permissionsPolicy: "geolocation=(), microphone=(), camera=(), usb=()",
22
+ csp: {
23
+ "default-src": ["'self'"],
24
+ "script-src": ["'self'"],
25
+ "style-src": ["'self'", "'unsafe-inline'"],
26
+ "img-src": ["'self'", "data:", "blob:"],
27
+ "font-src": ["'self'"],
28
+ "connect-src": ["'self'"],
29
+ "object-src": ["'none'"],
30
+ "frame-ancestors": ["'none'"],
31
+ },
32
+ cspReportOnly: false,
33
+ },
34
+ balanced: {
35
+ preset: "balanced",
36
+ hsts: { maxAge: 31536000, includeSubDomains: true, preload: true },
37
+ frameGuard: "SAMEORIGIN",
38
+ noSniff: true,
39
+ xssProtection: true,
40
+ referrerPolicy: "no-referrer-when-downgrade",
41
+ permissionsPolicy: "geolocation=(), microphone=()",
42
+ csp: {
43
+ "default-src": ["'self'"],
44
+ "script-src": ["'self'", "https://cdn.jsdelivr.net"],
45
+ "style-src": [
46
+ "'self'",
47
+ "'unsafe-inline'",
48
+ "https://fonts.googleapis.com",
49
+ ],
50
+ "img-src": ["'self'", "data:", "https://images.example.com"],
51
+ "connect-src": ["'self'", "https://api.example.com"],
52
+ },
53
+ cspReportOnly: true,
54
+ },
55
+ dev: {
56
+ preset: "dev",
57
+ hsts: undefined,
58
+ frameGuard: "SAMEORIGIN",
59
+ noSniff: false,
60
+ xssProtection: false,
61
+ referrerPolicy: "no-referrer",
62
+ permissionsPolicy: "",
63
+ csp: {
64
+ "default-src": [
65
+ "'self'",
66
+ "'unsafe-inline'",
67
+ "'unsafe-eval'",
68
+ "http://localhost:3000",
69
+ ],
70
+ "img-src": ["'self'", "data:", "blob:"],
71
+ },
72
+ cspReportOnly: true,
73
+ },
74
+ };
75
+ const preset = userOpts.preset ?? "balanced";
76
+ const base = {
77
+ ...(defaultPresets[preset] || defaultPresets.balanced),
78
+ ...userOpts,
79
+ };
80
+ const frameHeader = base.frameGuard || "SAMEORIGIN";
81
+ const xssHeader = base.xssProtection ? "1; mode=block" : "0";
82
+ const noSniffHeader = base.noSniff ? "nosniff" : "";
83
+ const permissionsHeader = base.permissionsPolicy || "";
84
+ const referrerHeader = base.referrerPolicy || "no-referrer";
85
+ const hstsParts = [`max-age=${base.hsts?.maxAge || 31536000}`];
86
+ if (base.hsts?.includeSubDomains)
87
+ hstsParts.push("includeSubDomains");
88
+ if (base.hsts?.preload)
89
+ hstsParts.push("preload");
90
+ const hstsHeader = hstsParts.join("; ");
91
+ let cspStatic = null;
92
+ let cspNeedsNonce = !!base.cspUseNonce;
93
+ if (typeof base.csp === "string")
94
+ cspStatic = base.csp;
95
+ else if (base.csp)
96
+ cspStatic = buildCSPString(base.csp);
97
+ const cspReportOnly = !!base.cspReportOnly;
98
+ const ultraFast = !!base.ultraFastMode;
99
+ if (cspNeedsNonce && ultraFast) {
100
+ index_js_1.GlobalConfig.debugging.warn("secureHeaders: ultraFastMode disables CSP nonce support. Nonce will not be used.");
101
+ }
102
+ if (ultraFast)
103
+ cspNeedsNonce = false;
104
+ return async (ctx, next) => {
105
+ try {
106
+ if (base.hsts) {
107
+ const proto = (ctx.req?.header("x-forwarded-proto") || "").toString();
108
+ if (!base.hsts.hstsOnlyOnHttps || proto.includes("https")) {
109
+ ctx.headers.set("Strict-Transport-Security", hstsHeader);
110
+ }
111
+ }
112
+ ctx.headers.set("X-Frame-Options", frameHeader);
113
+ ctx.headers.set("X-Content-Type-Options", noSniffHeader);
114
+ ctx.headers.set("X-XSS-Protection", xssHeader);
115
+ ctx.headers.set("Referrer-Policy", referrerHeader);
116
+ if (permissionsHeader)
117
+ ctx.headers.set("Permissions-Policy", permissionsHeader);
118
+ if (cspNeedsNonce) {
119
+ const nonce = (0, index_js_1.generateRandomBase64)();
120
+ let cspHeader = cspStatic;
121
+ if (!cspHeader) {
122
+ cspHeader = `default-src 'self'; script-src 'self' 'nonce-${nonce}'`;
123
+ }
124
+ if (typeof base.csp === "object") {
125
+ const idx = cspHeader.indexOf("script-src");
126
+ if (idx >= 0) {
127
+ const parts = [];
128
+ parts.push(cspHeader.slice(0, idx + 10));
129
+ parts.push(" 'nonce-" + nonce + "'");
130
+ parts.push(cspHeader.slice(idx + 10));
131
+ cspHeader = parts.join("");
132
+ }
133
+ else {
134
+ cspHeader += "; script-src 'self' 'nonce-" + nonce + "'";
135
+ }
136
+ }
137
+ ctx.cspNonce = nonce;
138
+ if (cspReportOnly)
139
+ ctx.headers.set("Content-Security-Policy-Report-Only", cspHeader);
140
+ else
141
+ ctx.headers.set("Content-Security-Policy", cspHeader);
142
+ }
143
+ else if (cspStatic) {
144
+ if (cspReportOnly)
145
+ ctx.headers.set("Content-Security-Policy-Report-Only", cspStatic);
146
+ else
147
+ ctx.headers.set("Content-Security-Policy", cspStatic);
148
+ }
149
+ return await next();
150
+ }
151
+ catch (err) {
152
+ console.error("secureHeaders middleware error", err);
153
+ return await next();
154
+ }
155
+ };
156
+ };
157
+ exports.secureHeaders = secureHeaders;
package/cjs/node/env.js CHANGED
@@ -2,12 +2,13 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.loadEnv = loadEnv;
4
4
  const node_fs_1 = require("node:fs");
5
- const runtime_js_1 = require("../utils/runtime.js");
5
+ const error_js_1 = require("../core/error.js");
6
6
  const colors_js_1 = require("../utils/colors.js");
7
+ const runtime_js_1 = require("../utils/runtime.js");
7
8
  function parseEnvFile(filePath, result) {
8
9
  try {
9
- if (runtime_js_1.runtime !== "bun" && runtime_js_1.runtime !== "node") {
10
- throw new Error(`Please use ${(0, colors_js_1.colorText)(`import {loadEnv} from "tezx/${runtime_js_1.runtime}"`, "bgRed")} environment`);
10
+ if (runtime_js_1.runtime !== "node") {
11
+ throw new error_js_1.TezXError(`Please use ${(0, colors_js_1.colorText)(`import {loadEnv} from "tezx/${runtime_js_1.runtime}"`, "bgRed")} environment`);
11
12
  }
12
13
  let fileExists = (0, node_fs_1.existsSync)(filePath);
13
14
  if (!fileExists) {
@@ -5,6 +5,7 @@ exports.getFiles = getFiles;
5
5
  const node_fs_1 = require("node:fs");
6
6
  const node_path_1 = require("node:path");
7
7
  const low_level_js_1 = require("../utils/low-level.js");
8
+ const error_js_1 = require("../core/error.js");
8
9
  function serveStatic(...args) {
9
10
  let route = "";
10
11
  let dir;
@@ -25,7 +26,7 @@ function serveStatic(...args) {
25
26
  [dir] = args;
26
27
  break;
27
28
  default:
28
- throw new Error(`\x1b[1;31m404 Not Found\x1b[0m \x1b[1;32mInvalid arguments\x1b[0m`);
29
+ throw new error_js_1.TezXError(`\x1b[1;31m404 Not Found\x1b[0m \x1b[1;32mInvalid arguments\x1b[0m`);
29
30
  }
30
31
  return {
31
32
  files: getFiles(dir, route, options),
package/cjs/node/ws.js CHANGED
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.upgradeWebSocket = upgradeWebSocket;
37
+ const error_js_1 = require("../core/error.js");
37
38
  function upgradeWebSocket(callback, options = {}) {
38
39
  const { onUpgradeError = (error, ctx) => {
39
40
  ctx.setStatus = 401;
@@ -49,13 +50,13 @@ function upgradeWebSocket(callback, options = {}) {
49
50
  return next();
50
51
  }
51
52
  ctx.setStatus = 401;
52
- return onUpgradeError(new Error("401 Bad Request: Invalid WebSocket headers"), ctx);
53
+ return onUpgradeError(new error_js_1.TezXError("401 Bad Request: Invalid WebSocket headers", 401), ctx);
53
54
  }
54
55
  ctx.wsProtocol = ctx.url?.startsWith("https") ? "wss" : "ws";
55
56
  const server = ctx.args?.[2];
56
57
  if (!server?.on) {
57
58
  ctx.setStatus = 500;
58
- return onUpgradeError(new Error("Node server instance missing for WebSocket"), ctx);
59
+ return onUpgradeError(new error_js_1.TezXError("Node server instance missing for WebSocket", 426), ctx);
59
60
  }
60
61
  const { WebSocketServer } = await Promise.resolve().then(() => __importStar(require("ws")));
61
62
  const wss = new WebSocketServer({
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RadixRouter = void 0;
4
+ const error_js_1 = require("../core/error.js");
4
5
  const index_js_1 = require("../helper/index.js");
5
6
  class RadixRouter {
6
7
  root = { children: {} };
@@ -22,7 +23,7 @@ class RadixRouter {
22
23
  }
23
24
  else if (node.children[":"]?.paramName !== paramName ||
24
25
  node.children[":"]?.isOptional !== isOptional) {
25
- throw new Error(`Conflicting param definition for ${paramName}`);
26
+ throw new error_js_1.TezXError(`Conflicting param definition for ${paramName}`);
26
27
  }
27
28
  node = node.children[":"];
28
29
  }
@@ -145,35 +146,3 @@ class RadixRouter {
145
146
  }
146
147
  }
147
148
  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
- };
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.base64Decode = base64Decode;
4
+ function base64Decode(base64) {
5
+ const pad = base64.length % 4;
6
+ if (pad)
7
+ base64 += "=".repeat(4 - pad);
8
+ if (typeof Buffer !== "undefined") {
9
+ return Buffer.from(base64, "base64").toString("utf-8");
10
+ }
11
+ else if (typeof atob === "function") {
12
+ return new TextDecoder().decode(Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)));
13
+ }
14
+ else {
15
+ throw new Error("Base64 decode not supported in this environment");
16
+ }
17
+ }
package/cjs/utils/file.js CHANGED
@@ -37,6 +37,8 @@ exports.fileExists = fileExists;
37
37
  exports.getFileBuffer = getFileBuffer;
38
38
  exports.readStream = readStream;
39
39
  exports.fileSize = fileSize;
40
+ exports.etagDigest = etagDigest;
41
+ const error_js_1 = require("../core/error.js");
40
42
  const runtime_js_1 = require("./runtime.js");
41
43
  async function fileExists(path) {
42
44
  switch (runtime_js_1.runtime) {
@@ -76,7 +78,7 @@ async function getFileBuffer(path) {
76
78
  case "deno":
77
79
  return Deno.readFile(path);
78
80
  default:
79
- throw new Error("Unsupported runtime environment");
81
+ throw new error_js_1.TezXError("Unsupported runtime environment");
80
82
  }
81
83
  }
82
84
  async function readStream(path) {
@@ -93,22 +95,42 @@ async function readStream(path) {
93
95
  return (await Deno.open(path, { read: true })).readable;
94
96
  }
95
97
  default:
96
- throw new Error("Unsupported runtime environment");
98
+ throw new error_js_1.TezXError("Unsupported runtime environment");
97
99
  }
98
100
  }
99
101
  async function fileSize(path) {
100
102
  switch (runtime_js_1.runtime) {
101
103
  case "node": {
102
104
  const { stat } = await Promise.resolve().then(() => __importStar(require("node:fs/promises")));
103
- return (await stat(path)).size;
105
+ const st = await stat(path);
106
+ return { size: st.size, mtime: st.mtime };
104
107
  }
105
108
  case "bun": {
106
- return Bun.file(path).size;
109
+ const st = await Bun.file(path).stat();
110
+ return { size: st.size, mtime: st.mtime };
107
111
  }
108
112
  case "deno": {
109
- return (await Deno.stat(path)).size;
113
+ const st = await Deno.stat(path);
114
+ return {
115
+ size: st.size,
116
+ mtime: st.mtime ?? new Date(),
117
+ };
110
118
  }
111
119
  default:
112
- return 0;
120
+ throw new error_js_1.TezXError("Unsupported runtime: " + runtime_js_1.runtime);
113
121
  }
114
122
  }
123
+ async function etagDigest(algo = "MD5", data) {
124
+ const encoded = typeof data === "string" ? new TextEncoder().encode(data) : data;
125
+ if (runtime_js_1.runtime === "bun") {
126
+ return Bun.hash(data, 256).toString(16);
127
+ }
128
+ if (globalThis?.crypto?.subtle) {
129
+ const buffer = await crypto.subtle.digest(algo, encoded);
130
+ return Array.from(new Uint8Array(buffer))
131
+ .map((b) => b.toString(16).padStart(2, "0"))
132
+ .join("");
133
+ }
134
+ const { createHash } = await Promise.resolve().then(() => __importStar(require("node:crypto")));
135
+ return createHash(algo).update(encoded).digest("hex");
136
+ }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateID = generateID;
4
4
  exports.generateUUID = generateUUID;
5
+ exports.generateRandomBase64 = generateRandomBase64;
5
6
  function generateID() {
6
7
  const timestamp = Date.now().toString(16);
7
8
  let randomHex = "";
@@ -25,3 +26,12 @@ function generateUUID() {
25
26
  return crypto.randomUUID();
26
27
  return generateID();
27
28
  }
29
+ function generateRandomBase64(length = 16) {
30
+ let result = "";
31
+ const BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
32
+ for (let i = 0; i < length; i++) {
33
+ const idx = Math.floor(Math.random() * 64);
34
+ result += BASE64[idx];
35
+ }
36
+ return result;
37
+ }
@@ -16,7 +16,9 @@ exports.notFoundResponse = notFoundResponse;
16
16
  async function handleErrorResponse(err = error_js_1.TezXError.internal(), ctx) {
17
17
  if (err instanceof error_js_1.TezXError) {
18
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");
19
+ return ctx
20
+ .status(err.statusCode ?? 500)
21
+ .send(err.details ?? err?.message ?? "Internal Server Error");
20
22
  }
21
23
  return await handleErrorResponse(error_js_1.TezXError.internal(), ctx);
22
24
  }
package/core/context.d.ts CHANGED
@@ -252,9 +252,9 @@ export declare class Context<TEnv extends Record<string, any> = {}, TPath extend
252
252
  filename?: string;
253
253
  }): Promise<HttpBaseResponse>;
254
254
  /**
255
- * bun [server]
256
- * deno [connInfo]
257
- * node [res, server]
255
+ *@property bun [server]
256
+ *@property deno [connInfo]
257
+ *@property node [res, server]
258
258
  */
259
259
  protected get args(): any;
260
260
  }
package/core/context.js CHANGED
@@ -2,6 +2,7 @@ import { fileExists, fileSize, getFileBuffer, readStream, } from "../utils/file.
2
2
  import { extensionExtract } from "../utils/low-level.js";
3
3
  import { defaultMimeType, mimeTypes } from "../utils/mimeTypes.js";
4
4
  import { determineContentTypeBody, toString } from "../utils/response.js";
5
+ import { TezXError } from "./error.js";
5
6
  import { TezXRequest } from "./request.js";
6
7
  export class Context {
7
8
  #status = 200;
@@ -133,7 +134,7 @@ export class Context {
133
134
  }
134
135
  async download(filePath, filename) {
135
136
  if (!(await fileExists(filePath)))
136
- throw Error("File not found");
137
+ throw TezXError.notFound("File not found");
137
138
  const buf = await getFileBuffer(filePath);
138
139
  const headers = {
139
140
  "Content-Disposition": `attachment; filename="${filename}"`,
@@ -146,8 +147,8 @@ export class Context {
146
147
  }
147
148
  async sendFile(filePath, init) {
148
149
  if (!(await fileExists(filePath)))
149
- throw Error("File not found");
150
- let size = await fileSize(filePath);
150
+ throw TezXError.notFound("File not found");
151
+ let { size, mtime } = await fileSize(filePath);
151
152
  const ext = extensionExtract(filePath);
152
153
  const mimeType = mimeTypes[ext] ?? defaultMimeType;
153
154
  let fileStream = await readStream(filePath);
package/core/error.d.ts CHANGED
@@ -93,3 +93,4 @@ export declare class TezXError extends Error {
93
93
  details: any;
94
94
  };
95
95
  }
96
+ export declare function TezXErrorParse(err: unknown, statusCode?: number): TezXError;
package/core/error.js CHANGED
@@ -35,3 +35,10 @@ export class TezXError extends Error {
35
35
  };
36
36
  }
37
37
  }
38
+ export function TezXErrorParse(err, statusCode) {
39
+ if (err instanceof TezXError)
40
+ return err;
41
+ else if (err instanceof Error)
42
+ return new TezXError(err?.message, 500, err?.stack);
43
+ return new TezXError(String(err), statusCode);
44
+ }
package/core/request.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { url2query } from "../utils/url.js";
2
+ import { TezXError } from "./error.js";
2
3
  export class TezXRequest {
3
4
  url;
4
5
  method;
@@ -80,11 +81,11 @@ export class TezXRequest {
80
81
  return this.#cachedFormObject;
81
82
  const ct = this.#contentType;
82
83
  if (!ct)
83
- throw new Error("Missing Content-Type");
84
+ throw new TezXError("Missing Content-Type");
84
85
  if (ct === "application/x-www-form-urlencoded" ||
85
86
  ct === "multipart/form-data") {
86
87
  if (this.#bodyConsumed) {
87
- throw new Error("Multipart body already consumed elsewhere");
88
+ throw new TezXError("Multipart body already consumed elsewhere");
88
89
  }
89
90
  this.#cachedFormObject = (await this.#rawRequest.formData());
90
91
  this.#bodyConsumed = true;
package/core/router.d.ts CHANGED
@@ -3,11 +3,6 @@ import { Callback, HandlerType, HTTPMethod, Middleware, RouteRegistry, ServeStat
3
3
  * Router configuration options.
4
4
  */
5
5
  export type RouterConfig = {
6
- /**
7
- * Custom route registry instance used internally to store routes.
8
- * If not provided, the router will use the default CombineRouteRegistry.
9
- */
10
- routeRegistry?: RouteRegistry;
11
6
  /**
12
7
  * `env` allows you to define environment variables for the router instance.
13
8
  * Example: `{ NODE_ENV: "production", API_VERSION: 2 }`
@@ -33,7 +28,7 @@ export declare class Router<T extends Record<string, any> = {}> {
33
28
  /** Environment variables accessible within this router */
34
29
  protected env: Record<string, string | number>;
35
30
  /** Internal route registry to hold all routes */
36
- protected router: RouteRegistry;
31
+ protected router?: RouteRegistry;
37
32
  /** Array tracking registered routes and their handlers */
38
33
  protected route: {
39
34
  method: string;
@@ -52,7 +47,7 @@ export declare class Router<T extends Record<string, any> = {}> {
52
47
  * @param config.env - Environment variables for router
53
48
  * @param config.routeRegistry - Custom route registry instance
54
49
  */
55
- constructor({ basePath, env, routeRegistry, }?: RouterConfig);
50
+ constructor({ basePath, env }?: RouterConfig);
56
51
  /**
57
52
  * Registers static file routes to the application for serving files like HTML, CSS, JS, images, etc.
58
53
  *
@@ -61,7 +56,7 @@ export declare class Router<T extends Record<string, any> = {}> {
61
56
  *
62
57
  * @example
63
58
  * ```ts
64
- * import { serveStatic } from "tezx/bun"; // or "tezx/node" or "tezx/deno"
59
+ * import { serveStatic } from "tezx/bun"; // or "tezx/node"
65
60
  *
66
61
  * app.static(
67
62
  * serveStatic("public", {
package/core/router.js CHANGED
@@ -1,13 +1,12 @@
1
- import { RadixRouter } from "../registry/RadixRouter.js";
2
1
  import { sanitizePathSplitBasePath } from "../utils/low-level.js";
2
+ import { TezXError } from "./error.js";
3
3
  export class Router {
4
4
  env = {};
5
5
  router;
6
6
  route = [];
7
7
  staticFile = Object.create(null);
8
8
  basePath;
9
- constructor({ basePath = "/", env = {}, routeRegistry = new RadixRouter(), } = {}) {
10
- this.router = routeRegistry;
9
+ constructor({ basePath = "/", env = {} } = {}) {
11
10
  this.basePath = basePath;
12
11
  this.env = { ...env };
13
12
  this.get = this.get.bind(this);
@@ -29,7 +28,7 @@ export class Router {
29
28
  if (headers) {
30
29
  for (const key in headers) {
31
30
  let value = headers?.[key];
32
- ctx.setHeader(key, value);
31
+ ctx.headers.set(key, value);
33
32
  }
34
33
  }
35
34
  return ctx.sendFile(r.fileSource);
@@ -74,14 +73,14 @@ export class Router {
74
73
  return this;
75
74
  }
76
75
  addRouter(path, router) {
77
- return this.#routeAddTriNode(path, router);
76
+ return this.#addRouterInstance(path, router);
78
77
  }
79
78
  group(prefix, callback) {
80
79
  const router = new Router({
81
80
  basePath: prefix,
82
81
  });
83
82
  callback(router);
84
- this.#routeAddTriNode("/", router);
83
+ this.#addRouterInstance("/", router);
85
84
  return this;
86
85
  }
87
86
  use(...args) {
@@ -128,7 +127,7 @@ export class Router {
128
127
  }
129
128
  #addRoute(method, path, handlers) {
130
129
  let pattern = `/${sanitizePathSplitBasePath(this.basePath, path).join("/")}`;
131
- this.router.addRoute(method, pattern, handlers);
130
+ this.router?.addRoute(method, pattern, handlers);
132
131
  this.route.push({
133
132
  method: method,
134
133
  pattern: pattern,
@@ -137,7 +136,7 @@ export class Router {
137
136
  }
138
137
  #registerRoute(method, path, ...args) {
139
138
  if (args.length === 0) {
140
- throw new Error("At least one handler is required.");
139
+ throw new TezXError("At least one handler is required.");
141
140
  }
142
141
  let middlewares = [];
143
142
  let callback;
@@ -154,22 +153,17 @@ export class Router {
154
153
  callback = args[0];
155
154
  }
156
155
  if (typeof callback !== "function") {
157
- throw new Error("Route callback function is missing or invalid.");
156
+ throw new TezXError("Route callback function is missing or invalid.");
158
157
  }
159
158
  if (!middlewares.every((middleware) => typeof middleware === "function")) {
160
- throw new Error("Middleware must be a function or an array of functions.");
159
+ throw new TezXError("Middleware must be a function or an array of functions.");
161
160
  }
162
161
  this.#addRoute(method, path, [...middlewares, callback]);
163
162
  }
164
- #routeAddTriNode(path, router) {
163
+ #addRouterInstance(path, router) {
165
164
  this.env = { ...this.env, ...router.env };
166
- if (this.router?.name &&
167
- router.router?.name &&
168
- this.router?.name !== router.router?.name) {
169
- throw new Error(`Router name mismatch: expected "${this.router.name}", got "${router.router.name}"`);
170
- }
171
165
  if (!(router instanceof Router)) {
172
- throw new Error("Router instance is required.");
166
+ throw new TezXError("Router instance is required.");
173
167
  }
174
168
  router.route.forEach((r) => {
175
169
  this.#addRoute(r?.method, `/${sanitizePathSplitBasePath(path, r?.pattern).join("/")}`, r?.handlers);
package/core/server.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Callback, ErrorHandler } from "../types/index.js";
1
+ import { Callback, ErrorHandler, RouteRegistry } from "../types/index.js";
2
2
  import { Router, RouterConfig } from "./router.js";
3
3
  export type TezXConfig = {
4
4
  /**
@@ -20,6 +20,11 @@ export type TezXConfig = {
20
20
  * @returns The transformed or resolved path used for routing (e.g., `/api/users`)
21
21
  */
22
22
  onPathResolve?: (pathname: string) => string;
23
+ /**
24
+ * Custom route registry instance used internally to store routes.
25
+ * If not provided, the router will use the default CombineRouteRegistry.
26
+ */
27
+ routeRegistry?: RouteRegistry;
23
28
  /**
24
29
  * Enables or disables debugging for the middleware.
25
30
  * When set to `true`, detailed debug logs will be output,
@@ -31,7 +36,7 @@ export type TezXConfig = {
31
36
  } & RouterConfig;
32
37
  /**
33
38
  * TezX is an ultra-fast, flexible request router and server handler.
34
- * It supports plug-and-play for Deno, Bun, and Node runtimes.
39
+ * It supports plug-and-play for Bun, and Node runtimes.
35
40
  *
36
41
  * @template T - The environment object shared across requests.
37
42
  *
@@ -45,6 +50,8 @@ export type TezXConfig = {
45
50
  */
46
51
  export declare class TezX<T extends Record<string, any> = {}> extends Router<T> {
47
52
  #private;
53
+ /** Internal route registry to hold all routes */
54
+ protected router?: RouteRegistry;
48
55
  constructor({ basePath, env, debugMode, onPathResolve, routeRegistry, }?: TezXConfig);
49
56
  /**
50
57
  * Register a custom 404 (not found) handler.
@@ -85,27 +92,7 @@ export declare class TezX<T extends Record<string, any> = {}> extends Router<T>
85
92
  * port: 3001,
86
93
  * reusePort: true, // enables SO_REUSEPORT for clustering
87
94
  * fetch: app.serve,
88
- * websocket: {
89
- * open(ws) {
90
- * console.log(ws.data);
91
- * return ws.data?.open?.(ws);
92
- * },
93
- * message(ws, msg) {
94
- * return ws.data?.message?.(ws, msg);
95
- * },
96
- * close(ws, code, reason) {
97
- * return ws.data?.close?.(ws, { code, reason });
98
- * },
99
- * ping(ws, data) {
100
- * return ws.data?.ping?.(ws, data);
101
- * },
102
- * pong(ws, data) {
103
- * return ws.data?.pong?.(ws, data);
104
- * },
105
- * drain(ws) {
106
- * return ws.data?.drain?.(ws);
107
- * },
108
- * },
95
+ * websocket: wsHandlers()
109
96
  * });
110
97
  * ```
111
98
  *