tezx 1.0.68 โ†’ 1.0.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cjs/index.js CHANGED
@@ -7,4 +7,4 @@ var server_js_1 = require("./core/server.js");
7
7
  Object.defineProperty(exports, "TezX", { enumerable: true, get: function () { return server_js_1.TezX; } });
8
8
  var params_js_1 = require("./utils/params.js");
9
9
  Object.defineProperty(exports, "useParams", { enumerable: true, get: function () { return params_js_1.useParams; } });
10
- exports.version = "1.0.68";
10
+ exports.version = "1.0.70";
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cacheControl = void 0;
4
+ const config_js_1 = require("../core/config.js");
5
+ const cacheControl = (options) => {
6
+ const { defaultSettings, useWeakETag = false, rules = [], logEvent = (event, ctx, error) => {
7
+ if (event === "error") {
8
+ config_js_1.GlobalConfig.debugging.error(`[CACHE] ${event.toUpperCase()}: ${error?.message}`);
9
+ }
10
+ else {
11
+ config_js_1.GlobalConfig.debugging.success(`[CACHE] ${event.toUpperCase()} for ${ctx.method} ${ctx.pathname}`);
12
+ }
13
+ }, } = options;
14
+ return async (ctx, next) => {
15
+ if (!["GET", "HEAD"].includes(ctx.method)) {
16
+ return await next();
17
+ }
18
+ try {
19
+ await next();
20
+ const matchedRule = rules.find((rule) => rule.condition(ctx)) || null;
21
+ const { maxAge, scope, enableETag, vary } = matchedRule || defaultSettings;
22
+ const cacheControlValue = `${scope}, max-age=${maxAge}`;
23
+ ctx.header("Cache-Control", cacheControlValue);
24
+ const expiresDate = new Date(Date.now() + maxAge * 1000).toUTCString();
25
+ ctx.header("Expires", expiresDate);
26
+ if (vary?.length) {
27
+ ctx.header("Vary", vary.join(", "));
28
+ }
29
+ if (enableETag) {
30
+ const responseBody = typeof ctx.resBody === "string"
31
+ ? ctx.resBody
32
+ : JSON.stringify(ctx.resBody ?? "");
33
+ const etag = await generateETag(responseBody, useWeakETag);
34
+ const ifNoneMatch = ctx.req.headers.get("if-none-match");
35
+ if (ifNoneMatch === etag) {
36
+ ctx.setStatus = 304;
37
+ ctx.body = null;
38
+ logEvent("cached", ctx);
39
+ return;
40
+ }
41
+ ctx.header("ETag", etag);
42
+ }
43
+ logEvent("cached", ctx);
44
+ }
45
+ catch (error) {
46
+ logEvent("error", ctx, error);
47
+ ctx.setStatus = 500;
48
+ ctx.body = { error: "Failed to set cache headers." };
49
+ }
50
+ };
51
+ };
52
+ exports.cacheControl = cacheControl;
53
+ const generateETag = async (content, weak = false) => {
54
+ const crypto = await Promise.resolve().then(() => require("node:crypto"));
55
+ const hash = crypto.createHash("md5").update(content).digest("hex");
56
+ return weak ? `W/"${hash}"` : `"${hash}"`;
57
+ };
@@ -32,3 +32,4 @@ __exportStar(require("./requestTimeout.js"), exports);
32
32
  __exportStar(require("./sanitizeHeader.js"), exports);
33
33
  __exportStar(require("./secureHeaders.js"), exports);
34
34
  __exportStar(require("./xssProtection.js"), exports);
35
+ __exportStar(require("./cacheControl.js"), exports);
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseJsonBody = parseJsonBody;
4
4
  exports.parseTextBody = parseTextBody;
5
5
  exports.parseUrlEncodedBody = parseUrlEncodedBody;
6
+ exports.sanitized = sanitized;
6
7
  exports.parseMultipartBody = parseMultipartBody;
7
8
  const config_js_1 = require("../core/config.js");
8
9
  async function parseJsonBody(req) {
@@ -91,6 +92,16 @@ async function parseUrlEncodedBody(req) {
91
92
  throw new Error("Unsupported environment for multipart parsing");
92
93
  }
93
94
  }
95
+ function sanitized(title) {
96
+ const base = title
97
+ .toLowerCase()
98
+ .trim()
99
+ .replace(/[_\s]+/g, '-')
100
+ .replace(/[^a-z0-9-.]+/g, '')
101
+ .replace(/--+/g, '-')
102
+ .replace(/^-+|-+$/g, '');
103
+ return base;
104
+ }
94
105
  async function parseMultipartBody(req, boundary, options) {
95
106
  const runtime = config_js_1.GlobalConfig.adapter;
96
107
  if (runtime === "node") {
@@ -133,7 +144,7 @@ async function parseMultipartBody(req, boundary, options) {
133
144
  const contentType = contentTypeMatch[1];
134
145
  if (options?.sanitized) {
135
146
  filename =
136
- `${Date.now()}-${filename.replace(/\s+/g, "")?.replace(/[^a-zA-Z0-9.-]/g, "-")}`?.toLowerCase();
147
+ `${Date.now()}-${sanitized(filename)}`;
137
148
  }
138
149
  if (Array.isArray(options?.allowedTypes) &&
139
150
  !options.allowedTypes?.includes(contentType)) {
@@ -192,7 +203,7 @@ async function parseMultipartBody(req, boundary, options) {
192
203
  let filename = val.name;
193
204
  if (options?.sanitized) {
194
205
  filename =
195
- `${Date.now()}-${filename.replace(/\s+/g, "")?.replace(/[^a-zA-Z0-9.-]/g, "-")}`?.toLowerCase();
206
+ `${Date.now()}-${sanitized(filename)}`;
196
207
  }
197
208
  if (Array.isArray(options?.allowedTypes) &&
198
209
  !options.allowedTypes?.includes(val.type)) {
package/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  export { Router } from "./core/router.js";
2
2
  export { TezX } from "./core/server.js";
3
3
  export { useParams } from "./utils/params.js";
4
- export let version = "1.0.68";
4
+ export let version = "1.0.70";
@@ -0,0 +1,47 @@
1
+ import { Context, Middleware } from "../index.js";
2
+ export type CacheRule = {
3
+ /**
4
+ * ๐ŸŽฏ Condition to determine if this rule applies.
5
+ */
6
+ condition: (ctx: Context) => boolean;
7
+ /**
8
+ * โณ Maximum age (in seconds) for caching.
9
+ */
10
+ maxAge: number;
11
+ /**
12
+ * ๐ŸŒ Cache scope: "public" or "private".
13
+ */
14
+ scope: "public" | "private";
15
+ /**
16
+ * ๐Ÿ”„ Enable or disable revalidation with ETag.
17
+ */
18
+ enableETag: boolean;
19
+ /**
20
+ * ๐Ÿท๏ธ Vary header for cache variations.
21
+ */
22
+ vary?: string[];
23
+ };
24
+ export type CacheSettings = Pick<CacheRule, "maxAge" | "scope" | "enableETag" | "vary">;
25
+ export type CacheOptions = {
26
+ /**
27
+ * ๐Ÿงช Weak ETag generation (optional).
28
+ */
29
+ useWeakETag?: boolean;
30
+ /**
31
+ * ๐Ÿ“ Logging function for cache events.
32
+ */
33
+ logEvent?: (event: "cached" | "no-cache" | "error", ctx: Context, error?: Error) => void;
34
+ /**
35
+ * ๐Ÿ› ๏ธ Default cache settings.
36
+ */
37
+ defaultSettings: CacheSettings;
38
+ /**
39
+ * ๐Ÿ”ง Custom rules for dynamic caching behavior.
40
+ */
41
+ rules?: CacheRule[];
42
+ };
43
+ /**
44
+ * Middleware to manage HTTP caching headers dynamically.
45
+ * @param options - Custom options for dynamic caching behavior.
46
+ */
47
+ export declare const cacheControl: (options: CacheOptions) => Middleware;
@@ -0,0 +1,53 @@
1
+ import { GlobalConfig } from "../core/config.js";
2
+ export const cacheControl = (options) => {
3
+ const { defaultSettings, useWeakETag = false, rules = [], logEvent = (event, ctx, error) => {
4
+ if (event === "error") {
5
+ GlobalConfig.debugging.error(`[CACHE] ${event.toUpperCase()}: ${error?.message}`);
6
+ }
7
+ else {
8
+ GlobalConfig.debugging.success(`[CACHE] ${event.toUpperCase()} for ${ctx.method} ${ctx.pathname}`);
9
+ }
10
+ }, } = options;
11
+ return async (ctx, next) => {
12
+ if (!["GET", "HEAD"].includes(ctx.method)) {
13
+ return await next();
14
+ }
15
+ try {
16
+ await next();
17
+ const matchedRule = rules.find((rule) => rule.condition(ctx)) || null;
18
+ const { maxAge, scope, enableETag, vary } = matchedRule || defaultSettings;
19
+ const cacheControlValue = `${scope}, max-age=${maxAge}`;
20
+ ctx.header("Cache-Control", cacheControlValue);
21
+ const expiresDate = new Date(Date.now() + maxAge * 1000).toUTCString();
22
+ ctx.header("Expires", expiresDate);
23
+ if (vary?.length) {
24
+ ctx.header("Vary", vary.join(", "));
25
+ }
26
+ if (enableETag) {
27
+ const responseBody = typeof ctx.resBody === "string"
28
+ ? ctx.resBody
29
+ : JSON.stringify(ctx.resBody ?? "");
30
+ const etag = await generateETag(responseBody, useWeakETag);
31
+ const ifNoneMatch = ctx.req.headers.get("if-none-match");
32
+ if (ifNoneMatch === etag) {
33
+ ctx.setStatus = 304;
34
+ ctx.body = null;
35
+ logEvent("cached", ctx);
36
+ return;
37
+ }
38
+ ctx.header("ETag", etag);
39
+ }
40
+ logEvent("cached", ctx);
41
+ }
42
+ catch (error) {
43
+ logEvent("error", ctx, error);
44
+ ctx.setStatus = 500;
45
+ ctx.body = { error: "Failed to set cache headers." };
46
+ }
47
+ };
48
+ };
49
+ const generateETag = async (content, weak = false) => {
50
+ const crypto = await import("node:crypto");
51
+ const hash = crypto.createHash("md5").update(content).digest("hex");
52
+ return weak ? `W/"${hash}"` : `"${hash}"`;
53
+ };
@@ -15,3 +15,4 @@ export * from "./requestTimeout.js";
15
15
  export * from "./sanitizeHeader.js";
16
16
  export * from "./secureHeaders.js";
17
17
  export * from "./xssProtection.js";
18
+ export * from "./cacheControl.js";
@@ -13,3 +13,4 @@ export * from "./requestTimeout.js";
13
13
  export * from "./sanitizeHeader.js";
14
14
  export * from "./secureHeaders.js";
15
15
  export * from "./xssProtection.js";
16
+ export * from "./cacheControl.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tezx",
3
- "version": "1.0.68",
3
+ "version": "1.0.70",
4
4
  "description": "TezX is a high-performance, lightweight JavaScript framework designed for speed, scalability, and flexibility. It enables efficient routing, middleware management, and static file serving with minimal configuration. Fully compatible with Node.js, Deno, and Bun.",
5
5
  "main": "cjs/index.js",
6
6
  "module": "index.js",
@@ -2,4 +2,5 @@ import { FormDataOptions } from "../core/request.js";
2
2
  export declare function parseJsonBody(req: any): Promise<Record<string, any>>;
3
3
  export declare function parseTextBody(req: any): Promise<string>;
4
4
  export declare function parseUrlEncodedBody(req: any): Promise<Record<string, any>>;
5
+ export declare function sanitized(title: string): string;
5
6
  export declare function parseMultipartBody(req: any, boundary: string, options?: FormDataOptions): Promise<Record<string, any>>;
package/utils/formData.js CHANGED
@@ -85,6 +85,16 @@ export async function parseUrlEncodedBody(req) {
85
85
  throw new Error("Unsupported environment for multipart parsing");
86
86
  }
87
87
  }
88
+ export function sanitized(title) {
89
+ const base = title
90
+ .toLowerCase()
91
+ .trim()
92
+ .replace(/[_\s]+/g, '-')
93
+ .replace(/[^a-z0-9-.]+/g, '')
94
+ .replace(/--+/g, '-')
95
+ .replace(/^-+|-+$/g, '');
96
+ return base;
97
+ }
88
98
  export async function parseMultipartBody(req, boundary, options) {
89
99
  const runtime = GlobalConfig.adapter;
90
100
  if (runtime === "node") {
@@ -127,7 +137,7 @@ export async function parseMultipartBody(req, boundary, options) {
127
137
  const contentType = contentTypeMatch[1];
128
138
  if (options?.sanitized) {
129
139
  filename =
130
- `${Date.now()}-${filename.replace(/\s+/g, "")?.replace(/[^a-zA-Z0-9.-]/g, "-")}`?.toLowerCase();
140
+ `${Date.now()}-${sanitized(filename)}`;
131
141
  }
132
142
  if (Array.isArray(options?.allowedTypes) &&
133
143
  !options.allowedTypes?.includes(contentType)) {
@@ -186,7 +196,7 @@ export async function parseMultipartBody(req, boundary, options) {
186
196
  let filename = val.name;
187
197
  if (options?.sanitized) {
188
198
  filename =
189
- `${Date.now()}-${filename.replace(/\s+/g, "")?.replace(/[^a-zA-Z0-9.-]/g, "-")}`?.toLowerCase();
199
+ `${Date.now()}-${sanitized(filename)}`;
190
200
  }
191
201
  if (Array.isArray(options?.allowedTypes) &&
192
202
  !options.allowedTypes?.includes(val.type)) {