skyguard-js 1.1.0 → 1.1.2
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/README.md +43 -33
- package/dist/app.d.ts +41 -7
- package/dist/app.js +51 -13
- package/dist/crypto/jwt.js +2 -2
- package/dist/helpers/http.d.ts +1 -2
- package/dist/helpers/http.js +2 -2
- package/dist/http/logger.js +1 -1
- package/dist/http/response.d.ts +3 -3
- package/dist/http/response.js +11 -6
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -1
- package/dist/middlewares/cors.js +1 -1
- package/dist/parsers/multipartParser.d.ts +11 -0
- package/dist/parsers/multipartParser.js +43 -13
- package/dist/routing/router.js +16 -8
- package/dist/sessions/fileSessionStorage.js +4 -5
- package/dist/sessions/sessionStorage.d.ts +1 -1
- package/dist/static/contentDisposition.js +6 -1
- package/dist/static/fileStaticHandler.js +1 -1
- package/dist/storage/storage.js +1 -4
- package/dist/validators/rules/arrayRule.d.ts +11 -0
- package/dist/validators/rules/arrayRule.js +40 -0
- package/dist/validators/rules/bigIntRule.d.ts +20 -0
- package/dist/validators/rules/bigIntRule.js +53 -0
- package/dist/validators/rules/booleanRule.d.ts +1 -1
- package/dist/validators/rules/booleanRule.js +2 -2
- package/dist/validators/rules/dateRule.d.ts +1 -1
- package/dist/validators/rules/index.d.ts +5 -2
- package/dist/validators/rules/index.js +11 -5
- package/dist/validators/rules/literalRule.d.ts +7 -0
- package/dist/validators/rules/literalRule.js +18 -0
- package/dist/validators/rules/numberRule.d.ts +2 -1
- package/dist/validators/rules/numberRule.js +7 -6
- package/dist/validators/rules/objectRule.d.ts +7 -0
- package/dist/validators/rules/objectRule.js +27 -0
- package/dist/validators/rules/stringRule.d.ts +68 -3
- package/dist/validators/rules/stringRule.js +135 -9
- package/dist/validators/rules/{requiredRule.d.ts → unionRule.d.ts} +3 -7
- package/dist/validators/rules/unionRule.js +31 -0
- package/dist/validators/types.d.ts +2 -5
- package/dist/validators/validationRule.d.ts +8 -25
- package/dist/validators/validationRule.js +12 -23
- package/dist/validators/validationSchema.d.ts +71 -22
- package/dist/validators/validationSchema.js +104 -50
- package/dist/validators/validator.d.ts +3 -3
- package/dist/validators/validator.js +18 -9
- package/dist/views/engineTemplate.d.ts +56 -0
- package/dist/views/engineTemplate.js +64 -0
- package/package.json +9 -13
- package/dist/helpers/app.d.ts +0 -4
- package/dist/helpers/app.js +0 -12
- package/dist/helpers/index.d.ts +0 -1
- package/dist/helpers/index.js +0 -9
- package/dist/validators/rules/emailRule.d.ts +0 -12
- package/dist/validators/rules/emailRule.js +0 -24
- package/dist/validators/rules/requiredRule.js +0 -21
- package/dist/views/helpersTemplate.d.ts +0 -104
- package/dist/views/helpersTemplate.js +0 -186
- package/dist/views/index.d.ts +0 -4
- package/dist/views/index.js +0 -9
- package/dist/views/raptorEngine.d.ts +0 -127
- package/dist/views/raptorEngine.js +0 -165
- package/dist/views/templateEngine.d.ts +0 -80
- package/dist/views/templateEngine.js +0 -204
- package/dist/views/view.d.ts +0 -55
- package/dist/views/view.js +0 -2
package/README.md
CHANGED
|
@@ -35,6 +35,11 @@ At its current stage, the framework focuses on **routing**, **internal architect
|
|
|
35
35
|
|
|
36
36
|
---
|
|
37
37
|
|
|
38
|
+
> [!NOTE]
|
|
39
|
+
> It is recommended to develop with `TypeScript` for a more secure and efficient development process; the framework already has native support for `TypeScript` and includes the necessary types.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
38
43
|
## 📦 Installation
|
|
39
44
|
|
|
40
45
|
```bash
|
|
@@ -160,17 +165,17 @@ app.staticFiles(join(__dirname, "..", "static"));
|
|
|
160
165
|
|
|
161
166
|
## ⛔ Data Validation
|
|
162
167
|
|
|
163
|
-
|
|
168
|
+
To validate data in the body of client requests, the framework provides the creation of validation schemas, which are created as follows:
|
|
164
169
|
|
|
165
170
|
```ts
|
|
166
|
-
import {
|
|
167
|
-
|
|
168
|
-
const userSchema =
|
|
169
|
-
name:
|
|
170
|
-
email:
|
|
171
|
-
age:
|
|
172
|
-
active:
|
|
173
|
-
birthdate:
|
|
171
|
+
import { v, schema } from "skyguard-js";
|
|
172
|
+
|
|
173
|
+
const userSchema = schema({
|
|
174
|
+
name: v.string({ maxLength: 60 }),
|
|
175
|
+
email: v.email(),
|
|
176
|
+
age: v.number({ min: 18 }),
|
|
177
|
+
active: v.boolean().default(false),
|
|
178
|
+
birthdate: v.date({ max: new Date() }),
|
|
174
179
|
});
|
|
175
180
|
|
|
176
181
|
app.post("/users", (request: Request) => {
|
|
@@ -183,6 +188,8 @@ app.post("/users", (request: Request) => {
|
|
|
183
188
|
});
|
|
184
189
|
```
|
|
185
190
|
|
|
191
|
+
By default each property you define in the schema is required, to define it optional you use the `.optional()` or `.default(value)` function
|
|
192
|
+
|
|
186
193
|
Validation is:
|
|
187
194
|
|
|
188
195
|
- Fail-fast per field
|
|
@@ -331,43 +338,46 @@ app.post(
|
|
|
331
338
|
|
|
332
339
|
## 📄 Views & Template Engine
|
|
333
340
|
|
|
334
|
-
To render
|
|
341
|
+
To render views, you must first set up the template engine using the `engineTemplates` method of the `app`, set the view path with the `views` method of the `app`, and then you can use the `render` method within your handlers to render the views with the data you want to pass.
|
|
335
342
|
|
|
336
343
|
```ts
|
|
337
|
-
import {
|
|
344
|
+
import { engine } from "express-handlebars";
|
|
345
|
+
import ejs from "ejs";
|
|
346
|
+
import { join } from "node:path";
|
|
347
|
+
|
|
348
|
+
app.views(__dirname, "views");
|
|
349
|
+
|
|
350
|
+
// Config for Express Handlebars
|
|
351
|
+
app.engineTemplates(
|
|
352
|
+
"hbs",
|
|
353
|
+
engine({
|
|
354
|
+
extname: "hbs",
|
|
355
|
+
layoutsDir: join(__dirname, "views"),
|
|
356
|
+
defaultLayout: "main",
|
|
357
|
+
}),
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
// Config for EJS
|
|
361
|
+
app.engineTemplates("ejs", (templatePath, data) => {
|
|
362
|
+
return ejs.renderFile(templatePath, data);
|
|
363
|
+
});
|
|
338
364
|
|
|
339
365
|
app.get("/home", () => {
|
|
340
|
-
return render(
|
|
341
|
-
"
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
products: [
|
|
345
|
-
{ name: "Laptop", price: 999.99 },
|
|
346
|
-
{ name: "Mouse", price: 29.99 },
|
|
347
|
-
],
|
|
348
|
-
user: { name: "John", role: "admin" },
|
|
349
|
-
},
|
|
350
|
-
"main",
|
|
351
|
-
);
|
|
366
|
+
return render("index", {
|
|
367
|
+
title: "Home Page",
|
|
368
|
+
message: "Welcome to the home page!",
|
|
369
|
+
});
|
|
352
370
|
});
|
|
353
371
|
```
|
|
354
372
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
- Variable interpolation (`{{ variable }}`)
|
|
358
|
-
- Conditionals (`{{#if}}`)
|
|
359
|
-
- Loops (`{{#each}}`)
|
|
360
|
-
- Layouts
|
|
361
|
-
- Partials
|
|
362
|
-
- Built-in helpers (`upper`, `lower`, `date`)
|
|
363
|
-
- Custom helpers
|
|
373
|
+
Currently, it works with third-party template engines such as **Express Handlebars**, **Pug**, and **EJS**, but the idea is to implement its own template engine in the future.
|
|
364
374
|
|
|
365
375
|
---
|
|
366
376
|
|
|
367
377
|
## 🔮 Roadmap (Tentative)
|
|
368
378
|
|
|
369
379
|
- Middleware system (✅)
|
|
370
|
-
- Template
|
|
380
|
+
- Template engines supported (✅)
|
|
371
381
|
- Request / Response abstraction (✅)
|
|
372
382
|
- Data validation (✅)
|
|
373
383
|
- Error handling improvements (✅)
|
package/dist/app.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { RouterGroup } from "./routing";
|
|
2
|
-
import { type View } from "./views";
|
|
3
2
|
import type { Middleware, RouteHandler } from "./types";
|
|
3
|
+
import { type TemplateEngineFunction } from "./views/engineTemplate";
|
|
4
4
|
/**
|
|
5
5
|
* The `App` class acts as the **execution kernel** and **lifecycle orchestrator**
|
|
6
6
|
* of the framework.
|
|
@@ -22,16 +22,14 @@ import type { Middleware, RouteHandler } from "./types";
|
|
|
22
22
|
export declare class App {
|
|
23
23
|
/** Main routing system */
|
|
24
24
|
private router;
|
|
25
|
-
/**
|
|
26
|
-
* View engine used to render templates.
|
|
27
|
-
*
|
|
28
|
-
* Typically consumed inside controllers to generate HTML responses.
|
|
29
|
-
*/
|
|
30
|
-
view: View;
|
|
31
25
|
/** Static file handler (optional) */
|
|
32
26
|
private staticFileHandler;
|
|
27
|
+
/** Logger instance for request logging */
|
|
33
28
|
private logger;
|
|
29
|
+
/** Timestamp marking the start of request processing (for logging) */
|
|
34
30
|
private startTime;
|
|
31
|
+
/** View engine for rendering templates (optional) */
|
|
32
|
+
private viewEngine;
|
|
35
33
|
/**
|
|
36
34
|
* Bootstraps and configures the application.
|
|
37
35
|
*
|
|
@@ -67,6 +65,42 @@ export declare class App {
|
|
|
67
65
|
* // /public/css/style.css → /css/style.css
|
|
68
66
|
*/
|
|
69
67
|
staticFiles(publicPath: string): void;
|
|
68
|
+
/**
|
|
69
|
+
* Configures the views directory for template rendering.
|
|
70
|
+
*
|
|
71
|
+
* @param pathSegments Path segments leading to the views directory
|
|
72
|
+
* @example
|
|
73
|
+
* app.views(__dirname, "views");
|
|
74
|
+
*/
|
|
75
|
+
views(...pathSegments: string[]): void;
|
|
76
|
+
/**
|
|
77
|
+
* Configures the template engine for rendering views.
|
|
78
|
+
*
|
|
79
|
+
* @param extension - File extension associated with the template engine (e.g. "hbs", "ejs")
|
|
80
|
+
* @param engine - Function that renders a template given its path and data
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* app.engineTemplates("hbs", (templatePath, data) => {
|
|
84
|
+
* const content = fs.readFileSync(templatePath, "utf-8");
|
|
85
|
+
* return content.replace(/{{\s*(\w+)\s*}}/g, (_, key) => {
|
|
86
|
+
* return data[key] ?? "";
|
|
87
|
+
* });
|
|
88
|
+
* });
|
|
89
|
+
*
|
|
90
|
+
* // With express-handlebars
|
|
91
|
+
* app.engineTemplates(
|
|
92
|
+
* "hbs",
|
|
93
|
+
* engine({
|
|
94
|
+
* extname: "hbs",
|
|
95
|
+
* layoutsDir: join(__dirname, "views"),
|
|
96
|
+
* defaultLayout: "main",
|
|
97
|
+
* }),
|
|
98
|
+
* );
|
|
99
|
+
*
|
|
100
|
+
* The `extension` parameter allows the framework to automatically select the correct
|
|
101
|
+
* template engine based on the file extension of the view being rendered.
|
|
102
|
+
*/
|
|
103
|
+
engineTemplates(extension: string, engine: TemplateEngineFunction): void;
|
|
70
104
|
/**
|
|
71
105
|
* Starts the HTTP server on the given port.
|
|
72
106
|
*
|
package/dist/app.js
CHANGED
|
@@ -4,12 +4,12 @@ exports.createApp = exports.App = void 0;
|
|
|
4
4
|
const routing_1 = require("./routing");
|
|
5
5
|
const http_1 = require("./http");
|
|
6
6
|
const validationException_1 = require("./exceptions/validationException");
|
|
7
|
-
const views_1 = require("./views");
|
|
8
7
|
const node_path_1 = require("node:path");
|
|
9
|
-
const app_1 = require("./helpers/app");
|
|
10
8
|
const fileStaticHandler_1 = require("./static/fileStaticHandler");
|
|
11
9
|
const node_http_1 = require("node:http");
|
|
12
10
|
const httpExceptions_1 = require("./exceptions/httpExceptions");
|
|
11
|
+
const engineTemplate_1 = require("./views/engineTemplate");
|
|
12
|
+
const container_1 = require("./container/container");
|
|
13
13
|
/**
|
|
14
14
|
* The `App` class acts as the **execution kernel** and **lifecycle orchestrator**
|
|
15
15
|
* of the framework.
|
|
@@ -31,16 +31,14 @@ const httpExceptions_1 = require("./exceptions/httpExceptions");
|
|
|
31
31
|
class App {
|
|
32
32
|
/** Main routing system */
|
|
33
33
|
router;
|
|
34
|
-
/**
|
|
35
|
-
* View engine used to render templates.
|
|
36
|
-
*
|
|
37
|
-
* Typically consumed inside controllers to generate HTML responses.
|
|
38
|
-
*/
|
|
39
|
-
view;
|
|
40
34
|
/** Static file handler (optional) */
|
|
41
35
|
staticFileHandler = null;
|
|
36
|
+
/** Logger instance for request logging */
|
|
42
37
|
logger;
|
|
38
|
+
/** Timestamp marking the start of request processing (for logging) */
|
|
43
39
|
startTime;
|
|
40
|
+
/** View engine for rendering templates (optional) */
|
|
41
|
+
viewEngine;
|
|
44
42
|
/**
|
|
45
43
|
* Bootstraps and configures the application.
|
|
46
44
|
*
|
|
@@ -51,10 +49,10 @@ class App {
|
|
|
51
49
|
* @returns The singleton `App` instance
|
|
52
50
|
*/
|
|
53
51
|
static bootstrap() {
|
|
54
|
-
const app =
|
|
52
|
+
const app = container_1.Container.singleton(App);
|
|
55
53
|
app.router = new routing_1.Router();
|
|
56
|
-
app.view = new views_1.RaptorEngine((0, node_path_1.join)(__dirname, "..", "views"));
|
|
57
54
|
app.logger = new http_1.Logger();
|
|
55
|
+
app.viewEngine = container_1.Container.singleton(engineTemplate_1.ViewEngine);
|
|
58
56
|
return app;
|
|
59
57
|
}
|
|
60
58
|
/**
|
|
@@ -100,6 +98,46 @@ class App {
|
|
|
100
98
|
staticFiles(publicPath) {
|
|
101
99
|
this.staticFileHandler = new fileStaticHandler_1.StaticFileHandler(publicPath);
|
|
102
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Configures the views directory for template rendering.
|
|
103
|
+
*
|
|
104
|
+
* @param pathSegments Path segments leading to the views directory
|
|
105
|
+
* @example
|
|
106
|
+
* app.views(__dirname, "views");
|
|
107
|
+
*/
|
|
108
|
+
views(...pathSegments) {
|
|
109
|
+
this.viewEngine.setViewsPath((0, node_path_1.join)(...pathSegments));
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Configures the template engine for rendering views.
|
|
113
|
+
*
|
|
114
|
+
* @param extension - File extension associated with the template engine (e.g. "hbs", "ejs")
|
|
115
|
+
* @param engine - Function that renders a template given its path and data
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* app.engineTemplates("hbs", (templatePath, data) => {
|
|
119
|
+
* const content = fs.readFileSync(templatePath, "utf-8");
|
|
120
|
+
* return content.replace(/{{\s*(\w+)\s*}}/g, (_, key) => {
|
|
121
|
+
* return data[key] ?? "";
|
|
122
|
+
* });
|
|
123
|
+
* });
|
|
124
|
+
*
|
|
125
|
+
* // With express-handlebars
|
|
126
|
+
* app.engineTemplates(
|
|
127
|
+
* "hbs",
|
|
128
|
+
* engine({
|
|
129
|
+
* extname: "hbs",
|
|
130
|
+
* layoutsDir: join(__dirname, "views"),
|
|
131
|
+
* defaultLayout: "main",
|
|
132
|
+
* }),
|
|
133
|
+
* );
|
|
134
|
+
*
|
|
135
|
+
* The `extension` parameter allows the framework to automatically select the correct
|
|
136
|
+
* template engine based on the file extension of the view being rendered.
|
|
137
|
+
*/
|
|
138
|
+
engineTemplates(extension, engine) {
|
|
139
|
+
this.viewEngine.setEngine(extension, engine);
|
|
140
|
+
}
|
|
103
141
|
/**
|
|
104
142
|
* Starts the HTTP server on the given port.
|
|
105
143
|
*
|
|
@@ -177,19 +215,19 @@ class App {
|
|
|
177
215
|
*/
|
|
178
216
|
handleError(error, adapter) {
|
|
179
217
|
if (error instanceof httpExceptions_1.HttpException) {
|
|
180
|
-
adapter.sendResponse(http_1.Response.json(error.toJSON()).
|
|
218
|
+
adapter.sendResponse(http_1.Response.json(error.toJSON()).setStatusCode(error.statusCode));
|
|
181
219
|
return;
|
|
182
220
|
}
|
|
183
221
|
if (error instanceof validationException_1.ValidationException) {
|
|
184
222
|
adapter.sendResponse(http_1.Response.json({
|
|
185
223
|
errors: error.getErrorsByField(),
|
|
186
|
-
}).
|
|
224
|
+
}).setStatusCode(400));
|
|
187
225
|
return;
|
|
188
226
|
}
|
|
189
227
|
adapter.sendResponse(http_1.Response.json({
|
|
190
228
|
statusCode: 500,
|
|
191
229
|
message: "Internal Server Error",
|
|
192
|
-
}).
|
|
230
|
+
}).setStatusCode(500));
|
|
193
231
|
console.error(error);
|
|
194
232
|
}
|
|
195
233
|
}
|
package/dist/crypto/jwt.js
CHANGED
|
@@ -85,7 +85,7 @@ const verifyJWT = (token, secret) => {
|
|
|
85
85
|
return null;
|
|
86
86
|
return payload;
|
|
87
87
|
}
|
|
88
|
-
catch
|
|
88
|
+
catch {
|
|
89
89
|
return null;
|
|
90
90
|
}
|
|
91
91
|
};
|
|
@@ -105,7 +105,7 @@ const decodeJWT = (token) => {
|
|
|
105
105
|
const payload = JSON.parse(base64UrlDecode(parts[1]));
|
|
106
106
|
return { header, payload };
|
|
107
107
|
}
|
|
108
|
-
catch
|
|
108
|
+
catch {
|
|
109
109
|
return null;
|
|
110
110
|
}
|
|
111
111
|
};
|
package/dist/helpers/http.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { TemplateContext } from "../types";
|
|
2
1
|
import { Response } from "../http/response";
|
|
3
2
|
/**
|
|
4
3
|
* Creates an HTTP response with a JSON body.
|
|
@@ -56,4 +55,4 @@ export declare function download(path: string, filename?: string, headers?: Reco
|
|
|
56
55
|
* @example
|
|
57
56
|
* return await render("users/profile", { user }, "main");
|
|
58
57
|
*/
|
|
59
|
-
export declare function render(
|
|
58
|
+
export declare function render(data: string, params?: Record<string, unknown>): Promise<Response>;
|
package/dist/helpers/http.js
CHANGED
|
@@ -70,6 +70,6 @@ async function download(path, filename, headers) {
|
|
|
70
70
|
* @example
|
|
71
71
|
* return await render("users/profile", { user }, "main");
|
|
72
72
|
*/
|
|
73
|
-
async function render(
|
|
74
|
-
return await response_1.Response.render(
|
|
73
|
+
async function render(data, params) {
|
|
74
|
+
return await response_1.Response.render(data, params);
|
|
75
75
|
}
|
package/dist/http/logger.js
CHANGED
|
@@ -13,7 +13,7 @@ class Logger {
|
|
|
13
13
|
const diff = process.hrtime.bigint() - startTime;
|
|
14
14
|
const responseTime = (Number(diff) / 1_000_000).toFixed(3);
|
|
15
15
|
const coloredStatus = this.colorizeStatus(res.statusCode);
|
|
16
|
-
const logLine = `${method} ${url} ${coloredStatus} ${responseTime} ms - ${contentLength}`;
|
|
16
|
+
const logLine = `${method} ${url} ${coloredStatus} ${responseTime} ms - ${contentLength.toString()}`;
|
|
17
17
|
this.stream.write(logLine + "\n");
|
|
18
18
|
}
|
|
19
19
|
colorizeStatus(statusCode) {
|
package/dist/http/response.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Headers
|
|
1
|
+
import type { Headers } from "../types";
|
|
2
2
|
/**
|
|
3
3
|
* Represents an outgoing response sent to the client.
|
|
4
4
|
*
|
|
@@ -29,7 +29,7 @@ export declare class Response {
|
|
|
29
29
|
*/
|
|
30
30
|
private _content;
|
|
31
31
|
get statusCode(): number;
|
|
32
|
-
|
|
32
|
+
setStatusCode(newStatus: number): this;
|
|
33
33
|
get headers(): Headers;
|
|
34
34
|
setHeaders(headers: Headers): this;
|
|
35
35
|
private merge;
|
|
@@ -115,5 +115,5 @@ export declare class Response {
|
|
|
115
115
|
* return Response.render("users/profile", { user });
|
|
116
116
|
* return Response.render("auth/login", {}, "auth");
|
|
117
117
|
*/
|
|
118
|
-
static render(
|
|
118
|
+
static render(data: string, params?: Record<string, unknown>): Promise<Response>;
|
|
119
119
|
}
|
package/dist/http/response.js
CHANGED
|
@@ -3,8 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Response = void 0;
|
|
4
4
|
const statusCodes_1 = require("./statusCodes");
|
|
5
5
|
const invalidHttpStatusException_1 = require("../exceptions/invalidHttpStatusException");
|
|
6
|
-
const app_1 = require("../helpers/app");
|
|
7
6
|
const fileDownload_1 = require("../static/fileDownload");
|
|
7
|
+
const engineTemplate_1 = require("../views/engineTemplate");
|
|
8
|
+
const container_1 = require("../container/container");
|
|
8
9
|
/**
|
|
9
10
|
* Represents an outgoing response sent to the client.
|
|
10
11
|
*
|
|
@@ -37,7 +38,7 @@ class Response {
|
|
|
37
38
|
get statusCode() {
|
|
38
39
|
return this._statusCode;
|
|
39
40
|
}
|
|
40
|
-
|
|
41
|
+
setStatusCode(newStatus) {
|
|
41
42
|
const status = statusCodes_1.statusCodes[newStatus] ?? null;
|
|
42
43
|
if (!status)
|
|
43
44
|
throw new invalidHttpStatusException_1.InvalidHttpStatusException(newStatus);
|
|
@@ -155,7 +156,7 @@ class Response {
|
|
|
155
156
|
* });
|
|
156
157
|
*/
|
|
157
158
|
static redirect(url) {
|
|
158
|
-
return new this().
|
|
159
|
+
return new this().setStatusCode(302).setHeader("location", url);
|
|
159
160
|
}
|
|
160
161
|
static async download(path, filename, headers) {
|
|
161
162
|
const downloadClass = new fileDownload_1.FileDownloadHelper();
|
|
@@ -176,9 +177,13 @@ class Response {
|
|
|
176
177
|
* return Response.render("users/profile", { user });
|
|
177
178
|
* return Response.render("auth/login", {}, "auth");
|
|
178
179
|
*/
|
|
179
|
-
static async render(
|
|
180
|
-
const
|
|
181
|
-
|
|
180
|
+
static async render(data, params) {
|
|
181
|
+
const viewEngine = container_1.Container.resolve(engineTemplate_1.ViewEngine);
|
|
182
|
+
if (viewEngine.hasEngine())
|
|
183
|
+
data = await viewEngine.render(data, params ?? {});
|
|
184
|
+
return new this()
|
|
185
|
+
.setContentType("text/html; charset=utf-8")
|
|
186
|
+
.setContent(Buffer.from(data, "utf-8"));
|
|
182
187
|
}
|
|
183
188
|
}
|
|
184
189
|
exports.Response = Response;
|
package/dist/index.d.ts
CHANGED
|
@@ -6,3 +6,4 @@ export { FileSessionStorage, MemorySessionStorage } from "./sessions";
|
|
|
6
6
|
export { HttpMethods } from "./http/httpMethods";
|
|
7
7
|
export { createUploader } from "./storage/uploader";
|
|
8
8
|
export { StorageType } from "./storage/types";
|
|
9
|
+
export { v, schema } from "./validators/validationSchema";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.StorageType = exports.createUploader = exports.HttpMethods = exports.MemorySessionStorage = exports.FileSessionStorage = exports.RouterGroup = exports.Response = exports.Request = exports.createApp = void 0;
|
|
3
|
+
exports.schema = exports.v = exports.StorageType = exports.createUploader = exports.HttpMethods = exports.MemorySessionStorage = exports.FileSessionStorage = exports.RouterGroup = exports.Response = exports.Request = exports.createApp = void 0;
|
|
4
4
|
var app_1 = require("./app");
|
|
5
5
|
Object.defineProperty(exports, "createApp", { enumerable: true, get: function () { return app_1.createApp; } });
|
|
6
6
|
var http_1 = require("./http");
|
|
@@ -17,3 +17,6 @@ var uploader_1 = require("./storage/uploader");
|
|
|
17
17
|
Object.defineProperty(exports, "createUploader", { enumerable: true, get: function () { return uploader_1.createUploader; } });
|
|
18
18
|
var types_1 = require("./storage/types");
|
|
19
19
|
Object.defineProperty(exports, "StorageType", { enumerable: true, get: function () { return types_1.StorageType; } });
|
|
20
|
+
var validationSchema_1 = require("./validators/validationSchema");
|
|
21
|
+
Object.defineProperty(exports, "v", { enumerable: true, get: function () { return validationSchema_1.v; } });
|
|
22
|
+
Object.defineProperty(exports, "schema", { enumerable: true, get: function () { return validationSchema_1.schema; } });
|
package/dist/middlewares/cors.js
CHANGED
|
@@ -75,7 +75,7 @@ const cors = (options = {}) => {
|
|
|
75
75
|
corsHeaders["Access-Control-Max-Age"] = String(config.maxAge);
|
|
76
76
|
if (!config.preflightContinue)
|
|
77
77
|
return new http_1.Response()
|
|
78
|
-
.
|
|
78
|
+
.setStatusCode(204)
|
|
79
79
|
.setContent(null)
|
|
80
80
|
.setHeaders(corsHeaders);
|
|
81
81
|
}
|
|
@@ -6,6 +6,16 @@ import { type MultipartData } from "./parserInterface";
|
|
|
6
6
|
* Parses multipart payloads into fields and files.
|
|
7
7
|
*/
|
|
8
8
|
export declare class MultipartParser implements ContentParser {
|
|
9
|
+
private readonly maxParts;
|
|
10
|
+
private readonly maxFieldSize;
|
|
11
|
+
private readonly maxFileSize;
|
|
12
|
+
private readonly maxHeaderSize;
|
|
13
|
+
constructor(options?: {
|
|
14
|
+
maxParts?: number;
|
|
15
|
+
maxFieldSize?: number;
|
|
16
|
+
maxFileSize?: number;
|
|
17
|
+
maxHeaderSize?: number;
|
|
18
|
+
});
|
|
9
19
|
/**
|
|
10
20
|
* Checks whether the given content type is `multipart/form-data`.
|
|
11
21
|
*
|
|
@@ -32,4 +42,5 @@ export declare class MultipartParser implements ContentParser {
|
|
|
32
42
|
private splitBuffer;
|
|
33
43
|
private parsePart;
|
|
34
44
|
private parseHeaders;
|
|
45
|
+
private trimEndingCRLF;
|
|
35
46
|
}
|
|
@@ -9,6 +9,16 @@ const parserInterface_1 = require("./parserInterface");
|
|
|
9
9
|
* Parses multipart payloads into fields and files.
|
|
10
10
|
*/
|
|
11
11
|
class MultipartParser {
|
|
12
|
+
maxParts;
|
|
13
|
+
maxFieldSize;
|
|
14
|
+
maxFileSize;
|
|
15
|
+
maxHeaderSize;
|
|
16
|
+
constructor(options = {}) {
|
|
17
|
+
this.maxParts = options.maxParts ?? 1000;
|
|
18
|
+
this.maxFieldSize = options.maxFieldSize ?? 1024 * 1024; // 1MB
|
|
19
|
+
this.maxFileSize = options.maxFileSize ?? 10 * 1024 * 1024; // 10MB
|
|
20
|
+
this.maxHeaderSize = options.maxHeaderSize ?? 16 * 1024; // 16KB
|
|
21
|
+
}
|
|
12
22
|
/**
|
|
13
23
|
* Checks whether the given content type is `multipart/form-data`.
|
|
14
24
|
*
|
|
@@ -50,24 +60,33 @@ class MultipartParser {
|
|
|
50
60
|
};
|
|
51
61
|
const boundaryBuffer = Buffer.from(`--${boundary}`);
|
|
52
62
|
const parts = this.splitBuffer(buffer, boundaryBuffer);
|
|
63
|
+
if (parts.length > this.maxParts) {
|
|
64
|
+
throw new httpExceptions_1.UnprocessableContentError(`Multipart parts limit exceeded: ${this.maxParts}`);
|
|
65
|
+
}
|
|
53
66
|
for (const part of parts) {
|
|
54
67
|
if (part.length === 0 || part.toString().trim() === "--")
|
|
55
68
|
continue;
|
|
56
69
|
const parsed = this.parsePart(part);
|
|
57
|
-
if (parsed)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
data: parsed.data,
|
|
64
|
-
size: parsed.data.length,
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
result.fields[parsed.name] = parsed.data.toString("utf-8");
|
|
70
|
+
if (!parsed)
|
|
71
|
+
continue;
|
|
72
|
+
const size = parsed.data.length;
|
|
73
|
+
if (parsed.filename) {
|
|
74
|
+
if (size > this.maxFileSize) {
|
|
75
|
+
throw new httpExceptions_1.UnprocessableContentError(`File size limit exceeded: ${this.maxFileSize} bytes`);
|
|
69
76
|
}
|
|
77
|
+
result.files.push({
|
|
78
|
+
fieldName: parsed.name,
|
|
79
|
+
filename: parsed.filename,
|
|
80
|
+
mimetype: parsed.contentType ?? parserInterface_1.contentTypes["application-octet-stream"],
|
|
81
|
+
data: parsed.data,
|
|
82
|
+
size,
|
|
83
|
+
});
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (size > this.maxFieldSize) {
|
|
87
|
+
throw new httpExceptions_1.UnprocessableContentError(`Field size limit exceeded: ${this.maxFieldSize} bytes`);
|
|
70
88
|
}
|
|
89
|
+
result.fields[parsed.name] = parsed.data.toString("utf-8");
|
|
71
90
|
}
|
|
72
91
|
return result;
|
|
73
92
|
}
|
|
@@ -87,8 +106,11 @@ class MultipartParser {
|
|
|
87
106
|
const headerEndIndex = part.indexOf("\r\n\r\n");
|
|
88
107
|
if (headerEndIndex === -1)
|
|
89
108
|
return null;
|
|
109
|
+
if (headerEndIndex > this.maxHeaderSize) {
|
|
110
|
+
throw new httpExceptions_1.UnprocessableContentError(`Multipart headers too large: ${this.maxHeaderSize} bytes`);
|
|
111
|
+
}
|
|
90
112
|
const headerSection = part.subarray(0, headerEndIndex).toString("utf-8");
|
|
91
|
-
const bodySection = part.subarray(headerEndIndex + 4);
|
|
113
|
+
const bodySection = this.trimEndingCRLF(part.subarray(headerEndIndex + 4));
|
|
92
114
|
const headers = this.parseHeaders(headerSection);
|
|
93
115
|
const disposition = headers["content-disposition"];
|
|
94
116
|
if (!disposition)
|
|
@@ -117,5 +139,13 @@ class MultipartParser {
|
|
|
117
139
|
}
|
|
118
140
|
return headers;
|
|
119
141
|
}
|
|
142
|
+
trimEndingCRLF(data) {
|
|
143
|
+
if (data.length < 2)
|
|
144
|
+
return data;
|
|
145
|
+
const end = data.subarray(-2).toString("utf-8");
|
|
146
|
+
if (end === "\r\n")
|
|
147
|
+
return data.subarray(0, data.length - 2);
|
|
148
|
+
return data;
|
|
149
|
+
}
|
|
120
150
|
}
|
|
121
151
|
exports.MultipartParser = MultipartParser;
|
package/dist/routing/router.js
CHANGED
|
@@ -73,14 +73,22 @@ class Router {
|
|
|
73
73
|
* const response = await router.resolve(request);
|
|
74
74
|
*/
|
|
75
75
|
resolve(request) {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
76
|
+
const executeLayer = (req) => {
|
|
77
|
+
const layer = this.resolveLayer(req);
|
|
78
|
+
if (layer.hasParameters()) {
|
|
79
|
+
req.setParams(layer.parseParameters(req.url));
|
|
80
|
+
}
|
|
81
|
+
const action = layer.getAction;
|
|
82
|
+
const routeMiddlewares = layer.getMiddlewares;
|
|
83
|
+
if (routeMiddlewares.length > 0) {
|
|
84
|
+
return this.runMiddlewares(req, routeMiddlewares, action);
|
|
85
|
+
}
|
|
86
|
+
return action(req);
|
|
87
|
+
};
|
|
88
|
+
if (this.globalMiddlewares.length > 0) {
|
|
89
|
+
return this.runMiddlewares(request, this.globalMiddlewares, executeLayer);
|
|
90
|
+
}
|
|
91
|
+
return executeLayer(request);
|
|
84
92
|
}
|
|
85
93
|
/**
|
|
86
94
|
* Runs a middleware chain using the onion model.
|
|
@@ -213,10 +213,7 @@ class FileSessionStorage {
|
|
|
213
213
|
}
|
|
214
214
|
catch {
|
|
215
215
|
// Corrupt/unreadable → best-effort cleanup
|
|
216
|
-
|
|
217
|
-
await (0, promises_1.unlink)(full);
|
|
218
|
-
}
|
|
219
|
-
catch { }
|
|
216
|
+
await (0, promises_1.unlink)(full);
|
|
220
217
|
}
|
|
221
218
|
}));
|
|
222
219
|
}
|
|
@@ -280,7 +277,9 @@ class FileSessionStorage {
|
|
|
280
277
|
try {
|
|
281
278
|
await (0, promises_1.unlink)(path);
|
|
282
279
|
}
|
|
283
|
-
catch {
|
|
280
|
+
catch {
|
|
281
|
+
/** eslint-disable-next-line no-empty */
|
|
282
|
+
}
|
|
284
283
|
}
|
|
285
284
|
/**
|
|
286
285
|
* Generates a cryptographically strong session id (64 hex chars).
|
|
@@ -59,7 +59,12 @@ class ContentDisposition {
|
|
|
59
59
|
}
|
|
60
60
|
sanitizeFilename(filename) {
|
|
61
61
|
return filename
|
|
62
|
-
.
|
|
62
|
+
.split("")
|
|
63
|
+
.filter(char => {
|
|
64
|
+
const code = char.charCodeAt(0);
|
|
65
|
+
return !(code <= 0x1f || (code >= 0x7f && code <= 0x9f));
|
|
66
|
+
})
|
|
67
|
+
.join("")
|
|
63
68
|
.replace(/["\r\n]/g, "")
|
|
64
69
|
.replace(/[/\\]/g, "")
|
|
65
70
|
.replace(/\s+/g, " ")
|