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 +1 -1
- package/cjs/middleware/cacheControl.js +57 -0
- package/cjs/middleware/index.js +1 -0
- package/cjs/utils/formData.js +13 -2
- package/index.js +1 -1
- package/middleware/cacheControl.d.ts +47 -0
- package/middleware/cacheControl.js +53 -0
- package/middleware/index.d.ts +1 -0
- package/middleware/index.js +1 -0
- package/package.json +1 -1
- package/utils/formData.d.ts +1 -0
- package/utils/formData.js +12 -2
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.
|
|
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
|
+
};
|
package/cjs/middleware/index.js
CHANGED
|
@@ -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);
|
package/cjs/utils/formData.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
@@ -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
|
+
};
|
package/middleware/index.d.ts
CHANGED
package/middleware/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tezx",
|
|
3
|
-
"version": "1.0.
|
|
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",
|
package/utils/formData.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
199
|
+
`${Date.now()}-${sanitized(filename)}`;
|
|
190
200
|
}
|
|
191
201
|
if (Array.isArray(options?.allowedTypes) &&
|
|
192
202
|
!options.allowedTypes?.includes(val.type)) {
|