skyguard-js 1.1.7 → 1.2.0
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 +174 -42
- package/dist/app.d.ts +1 -1
- package/dist/app.js +1 -1
- package/dist/helpers/http.d.ts +37 -0
- package/dist/helpers/http.js +37 -0
- package/dist/http/nodeNativeHttp.d.ts +1 -0
- package/dist/http/nodeNativeHttp.js +2 -1
- package/dist/http/request.d.ts +10 -11
- package/dist/http/request.js +5 -8
- package/dist/http/response.d.ts +47 -3
- package/dist/http/response.js +77 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -2
- package/dist/middlewares/cors.js +2 -2
- package/dist/middlewares/csrf.d.ts +69 -0
- package/dist/middlewares/csrf.js +315 -0
- package/dist/middlewares/index.d.ts +2 -0
- package/dist/middlewares/index.js +5 -1
- package/dist/middlewares/rateLimiter.d.ts +82 -0
- package/dist/middlewares/rateLimiter.js +159 -0
- package/dist/routing/routerGroup.d.ts +2 -2
- package/dist/routing/routerGroup.js +2 -2
- package/dist/storage/uploader.d.ts +1 -1
- package/dist/storage/uploader.js +7 -6
- package/dist/types/index.d.ts +1 -10
- package/dist/validators/rules/convertPrimitiveRule.d.ts +12 -0
- package/dist/validators/rules/convertPrimitiveRule.js +24 -0
- package/dist/validators/rules/dateRule.js +3 -13
- package/dist/validators/types.d.ts +47 -1
- package/dist/validators/validationRule.d.ts +2 -0
- package/dist/validators/validationRule.js +5 -0
- package/dist/validators/validationSchema.d.ts +83 -9
- package/dist/validators/validationSchema.js +151 -12
- package/dist/validators/validator.js +20 -4
- package/package.json +7 -1
package/README.md
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/skyguard-js)
|
|
8
8
|
[](https://github.com/Pipe930/Skyguard-js/actions/workflows/pipeline.yml)
|
|
9
|
+
[](https://badge.socket.dev/npm/package/skyguard-js/1.1.8)
|
|
9
10
|
|
|
10
11
|
**Skyguard.js** is a **lightweight, dependency-free web framework** built entirely with **TypeScript**.
|
|
11
12
|
|
|
@@ -32,10 +33,11 @@ Skyguard.js currently delivers a solid core that includes **routing**, **type-sa
|
|
|
32
33
|
- Global, group, and route-level middlewares
|
|
33
34
|
- Request / Response abstractions
|
|
34
35
|
- Declarative data validation
|
|
35
|
-
-
|
|
36
|
+
- Support for template motors (handlebars, pugs, ejs, etc.)
|
|
36
37
|
- Built-in HTTP exceptions
|
|
37
38
|
- Password hashing and JWT token generation
|
|
38
39
|
- CORS middleware
|
|
40
|
+
- CSRF middleware protection
|
|
39
41
|
- File uploads (via middleware)
|
|
40
42
|
- Static file serving
|
|
41
43
|
- Session handling (via middleware)
|
|
@@ -44,6 +46,24 @@ Skyguard.js currently delivers a solid core that includes **routing**, **type-sa
|
|
|
44
46
|
|
|
45
47
|
## 📦 Installation
|
|
46
48
|
|
|
49
|
+
You need to have [NodeJS](https://nodejs.org/) version 22 or later installed.
|
|
50
|
+
|
|
51
|
+
Create the `package.json` file to start a new [NodeJS](https://nodejs.org/) project using the `npm init` command.
|
|
52
|
+
|
|
53
|
+
After configuring the package.json, install [Typescript](https://www.typescriptlang.org/).
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm install typescript -D
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
After installing [Typescript](https://www.typescriptlang.org/) in your project, you need to create the [Typescript](https://www.typescriptlang.org/) configuration file `tsconfig.json`.
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npx tsc --init
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Now that [Typescript](https://www.typescriptlang.org/) is configured, we can install the library. Since it's a module that's in the [NPM Registry](https://www.npmjs.com/), we use the npm package manager.
|
|
66
|
+
|
|
47
67
|
```bash
|
|
48
68
|
npm install skyguard-js
|
|
49
69
|
```
|
|
@@ -68,9 +88,6 @@ app.run(PORT, () => {
|
|
|
68
88
|
});
|
|
69
89
|
```
|
|
70
90
|
|
|
71
|
-
> [!NOTE]
|
|
72
|
-
> 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.
|
|
73
|
-
|
|
74
91
|
---
|
|
75
92
|
|
|
76
93
|
## 🛣️ Routing
|
|
@@ -97,8 +114,8 @@ Route groups allow you to organize endpoints under a shared prefix.
|
|
|
97
114
|
|
|
98
115
|
```ts
|
|
99
116
|
app.group("/api", api => {
|
|
100
|
-
api.get("/users", () =>
|
|
101
|
-
api.get("/products", () =>
|
|
117
|
+
api.get("/users", () => res.json({ message: "Users" }));
|
|
118
|
+
api.get("/products", () => res.json({ message: "Products" }));
|
|
102
119
|
});
|
|
103
120
|
```
|
|
104
121
|
|
|
@@ -109,30 +126,30 @@ app.group("/api", api => {
|
|
|
109
126
|
Middlewares can be registered **globally**, **per group**, or **per route**.
|
|
110
127
|
|
|
111
128
|
```ts
|
|
112
|
-
import { Request, Response, RouteHandler } from "skyguard-js";
|
|
129
|
+
import { Request, Response, json, RouteHandler } from "skyguard-js";
|
|
113
130
|
|
|
114
131
|
const authMiddleware = async (
|
|
115
132
|
request: Request,
|
|
116
133
|
next: RouteHandler,
|
|
117
134
|
): Promise<Response> => {
|
|
118
135
|
if (request.headers["authorization"] !== "secret") {
|
|
119
|
-
return
|
|
136
|
+
return json({ message: "Unauthorized" }).setStatus(401);
|
|
120
137
|
}
|
|
121
138
|
|
|
122
139
|
return next(request);
|
|
123
140
|
};
|
|
124
141
|
|
|
125
142
|
// Global middleware
|
|
126
|
-
app.middlewares(
|
|
143
|
+
app.middlewares(authMiddleware);
|
|
127
144
|
|
|
128
145
|
// Group middleware
|
|
129
146
|
app.group("/admin", admin => {
|
|
130
|
-
admin.middlewares(
|
|
131
|
-
admin.get("/dashboard", () =>
|
|
147
|
+
admin.middlewares(authMiddleware);
|
|
148
|
+
admin.get("/dashboard", () => json({ ok: true }));
|
|
132
149
|
});
|
|
133
150
|
|
|
134
151
|
// Route-level middleware
|
|
135
|
-
app.get("/secure", () =>
|
|
152
|
+
app.get("/secure", () => json({ secure: true }), [authMiddleware]);
|
|
136
153
|
```
|
|
137
154
|
|
|
138
155
|
---
|
|
@@ -144,14 +161,135 @@ To enable CORS, use the built-in `cors` middleware.
|
|
|
144
161
|
```ts
|
|
145
162
|
import { cors, HttpMethods } from "skyguard-js";
|
|
146
163
|
|
|
147
|
-
app.middlewares(
|
|
164
|
+
app.middlewares(
|
|
148
165
|
cors({
|
|
149
166
|
origin: ["http://localhost:3000", "https://myapp.com"],
|
|
150
167
|
methods: [HttpMethods.get, HttpMethods.post],
|
|
151
168
|
allowedHeaders: ["Content-Type", "Authorization"],
|
|
152
169
|
credentials: true,
|
|
153
170
|
}),
|
|
154
|
-
|
|
171
|
+
);
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## 🛡️ CSRF Middleware
|
|
177
|
+
|
|
178
|
+
Use the built-in `csrf` middleware to protect endpoints against CSRF attacks.
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
import { csrf, json } from "skyguard-js";
|
|
182
|
+
|
|
183
|
+
app.middlewares(
|
|
184
|
+
csrf({
|
|
185
|
+
cookieName: "XSRF-TOKEN",
|
|
186
|
+
headerNames: ["x-csrf-token"],
|
|
187
|
+
}),
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
app.post("/transfer", () => {
|
|
191
|
+
return json({ ok: true });
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
The middleware follows a hardened **double-submit cookie** strategy:
|
|
196
|
+
|
|
197
|
+
- It issues a CSRF cookie when missing (including first GET/HEAD/OPTIONS and failed protected requests).
|
|
198
|
+
- For state-changing requests (POST/PUT/PATCH/DELETE), it validates the token from header/body against the cookie value.
|
|
199
|
+
- It validates `Origin`/`Referer` for protected requests (and requires `Referer` on HTTPS when `Origin` is missing).
|
|
200
|
+
- It rejects duplicated CSRF header values to avoid ambiguous token parsing.
|
|
201
|
+
|
|
202
|
+
### Example: CSRF token in HTML templates (Express Handlebars)
|
|
203
|
+
|
|
204
|
+
When you render server-side HTML, you can pass the CSRF token to your template and include it as a hidden field in forms.
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
import { createApp, csrf, render, json } from "skyguard-js";
|
|
208
|
+
import { engine } from "express-handlebars";
|
|
209
|
+
import { join } from "node:path";
|
|
210
|
+
|
|
211
|
+
const app = createApp();
|
|
212
|
+
|
|
213
|
+
app.views(__dirname, "views");
|
|
214
|
+
app.engineTemplates(
|
|
215
|
+
"hbs",
|
|
216
|
+
engine({
|
|
217
|
+
extname: "hbs",
|
|
218
|
+
layoutsDir: join(__dirname, "views"),
|
|
219
|
+
defaultLayout: "main",
|
|
220
|
+
}),
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
app.middlewares(
|
|
224
|
+
csrf({
|
|
225
|
+
cookieName: "XSRF-TOKEN",
|
|
226
|
+
headerNames: ["x-csrf-token"],
|
|
227
|
+
}),
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
app.get("/transfer", request => {
|
|
231
|
+
return render("transfer", {
|
|
232
|
+
csrfToken: request.cookies["XSRF-TOKEN"],
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
app.post("/transfer", request => {
|
|
237
|
+
// If middleware passes, token is valid
|
|
238
|
+
return json({ ok: true, amount: request.body.amount });
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
`views/transfer.hbs`:
|
|
243
|
+
|
|
244
|
+
```hbs
|
|
245
|
+
<form action="/transfer" method="POST">
|
|
246
|
+
<input type="hidden" name="csrf" value="{{csrfToken}}" />
|
|
247
|
+
<input type="number" name="amount" />
|
|
248
|
+
<button type="submit">Send</button>
|
|
249
|
+
</form>
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
For `fetch`/AJAX requests, send the same token in headers:
|
|
253
|
+
|
|
254
|
+
```html
|
|
255
|
+
<script>
|
|
256
|
+
const csrfToken = "{{csrfToken}}";
|
|
257
|
+
|
|
258
|
+
async function sendTransfer() {
|
|
259
|
+
await fetch("/transfer", {
|
|
260
|
+
method: "POST",
|
|
261
|
+
headers: {
|
|
262
|
+
"Content-Type": "application/json",
|
|
263
|
+
"x-csrf-token": csrfToken,
|
|
264
|
+
},
|
|
265
|
+
body: JSON.stringify({ amount: 150 }),
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
</script>
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## 🚦 Rate Limit Middleware
|
|
274
|
+
|
|
275
|
+
You can limit requests with the built-in `rateLimit` middleware.
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
import { rateLimit, Response } from "skyguard-js";
|
|
279
|
+
|
|
280
|
+
const apiRateLimit = rateLimit({
|
|
281
|
+
windowMs: 60_000, // 1 minute
|
|
282
|
+
max: 100,
|
|
283
|
+
message: "Too many requests from this IP",
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
app.get(
|
|
287
|
+
"/api/users",
|
|
288
|
+
() => {
|
|
289
|
+
return Response.json([{ id: 1 }]);
|
|
290
|
+
},
|
|
291
|
+
[apiRateLimit],
|
|
292
|
+
);
|
|
155
293
|
```
|
|
156
294
|
|
|
157
295
|
---
|
|
@@ -175,33 +313,26 @@ app.staticFiles(join(__dirname, "..", "static"));
|
|
|
175
313
|
To validate the data in the body of client requests, the framework provides the creation of validation schemes and a middleware function to validate the body of HTTP requests, used as follows:
|
|
176
314
|
|
|
177
315
|
```ts
|
|
178
|
-
import { v, schema,
|
|
316
|
+
import { v, schema, validateRequest, json } from "skyguard-js";
|
|
179
317
|
|
|
180
318
|
// Created Schema
|
|
181
319
|
const userSchema = schema({
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
320
|
+
body: {
|
|
321
|
+
name: v.string({ maxLength: 60 }),
|
|
322
|
+
email: v.email(),
|
|
323
|
+
age: v.number({ min: 18 }),
|
|
324
|
+
active: v.boolean().default(false),
|
|
325
|
+
birthdate: v.date({ max: new Date() }),
|
|
326
|
+
},
|
|
187
327
|
});
|
|
188
328
|
|
|
189
|
-
// Typing Interface
|
|
190
|
-
interface User {
|
|
191
|
-
name: string;
|
|
192
|
-
email: string;
|
|
193
|
-
age: number;
|
|
194
|
-
active: boolean;
|
|
195
|
-
birthdate: Date;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
329
|
app.post(
|
|
199
330
|
"/test",
|
|
200
331
|
(request: Request) => {
|
|
201
|
-
const data = request.
|
|
332
|
+
const data = request.body;
|
|
202
333
|
return json(data).setStatusCode(201);
|
|
203
334
|
},
|
|
204
|
-
[
|
|
335
|
+
[validateRequest(userSchema)],
|
|
205
336
|
);
|
|
206
337
|
```
|
|
207
338
|
|
|
@@ -221,7 +352,7 @@ Validation is:
|
|
|
221
352
|
The framework provides a set of built-in HTTP exceptions that can be thrown from route handlers or middleware. When an exception is thrown, the framework detects it and sends an appropriate HTTP response with the status code and message you specified in the class.
|
|
222
353
|
|
|
223
354
|
```ts
|
|
224
|
-
import { NotFoundError, InternalServerError } from "skyguard-js";
|
|
355
|
+
import { NotFoundError, InternalServerError, json } from "skyguard-js";
|
|
225
356
|
|
|
226
357
|
const listResources = ["1", "2", "3"];
|
|
227
358
|
|
|
@@ -232,7 +363,7 @@ app.get("/resource/{id}", (request: Request) => {
|
|
|
232
363
|
throw new NotFoundError("Resource not found");
|
|
233
364
|
}
|
|
234
365
|
|
|
235
|
-
return
|
|
366
|
+
return json(resource);
|
|
236
367
|
});
|
|
237
368
|
|
|
238
369
|
app.get("/divide", (request: Request) => {
|
|
@@ -240,7 +371,7 @@ app.get("/divide", (request: Request) => {
|
|
|
240
371
|
const { a, b } = request.query;
|
|
241
372
|
const result = Number(a) / Number(b);
|
|
242
373
|
|
|
243
|
-
return
|
|
374
|
+
return json({ result });
|
|
244
375
|
} catch (error) {
|
|
245
376
|
throw new InternalServerError(
|
|
246
377
|
"An error occurred while processing your request",
|
|
@@ -256,9 +387,9 @@ app.get("/divide", (request: Request) => {
|
|
|
256
387
|
To handle sessions, you must use the framework’s built-in middleware. Depending on where you want to store them (in memory, in files, or in a database), you need to use the corresponding storage class.
|
|
257
388
|
|
|
258
389
|
```ts
|
|
259
|
-
import { sessions, FileSessionStorage } from "skyguard-js";
|
|
390
|
+
import { sessions, FileSessionStorage, json } from "skyguard-js";
|
|
260
391
|
|
|
261
|
-
app.middlewares(
|
|
392
|
+
app.middlewares(
|
|
262
393
|
sessions(FileSessionStorage, {
|
|
263
394
|
name: "connect.sid",
|
|
264
395
|
rolling: true,
|
|
@@ -271,7 +402,7 @@ app.middlewares([
|
|
|
271
402
|
path: "/",
|
|
272
403
|
},
|
|
273
404
|
}),
|
|
274
|
-
|
|
405
|
+
);
|
|
275
406
|
|
|
276
407
|
app.post("/login", (request: Request) => {
|
|
277
408
|
const { username, password } = request.data;
|
|
@@ -304,7 +435,7 @@ app.get("/me", (request: Request) => {
|
|
|
304
435
|
The framework includes some password hashing and JWT token generation functions, and also includes JWT authentication middleware.
|
|
305
436
|
|
|
306
437
|
```ts
|
|
307
|
-
import { Hasher, JWT } from "skyguard-js";
|
|
438
|
+
import { Hasher, JWT, json } from "skyguard-js";
|
|
308
439
|
|
|
309
440
|
app.post("/register", async (request: Request) => {
|
|
310
441
|
const { username, password } = request.data;
|
|
@@ -313,7 +444,7 @@ app.post("/register", async (request: Request) => {
|
|
|
313
444
|
// Save username and hashedPassword to database
|
|
314
445
|
// ...
|
|
315
446
|
|
|
316
|
-
return
|
|
447
|
+
return json({ message: "User registered" });
|
|
317
448
|
});
|
|
318
449
|
|
|
319
450
|
app.post("/login", async (request: Request) => {
|
|
@@ -333,7 +464,7 @@ app.post("/login", async (request: Request) => {
|
|
|
333
464
|
expiresIn: "1h",
|
|
334
465
|
});
|
|
335
466
|
|
|
336
|
-
return
|
|
467
|
+
return json({ token });
|
|
337
468
|
});
|
|
338
469
|
```
|
|
339
470
|
|
|
@@ -344,7 +475,7 @@ app.post("/login", async (request: Request) => {
|
|
|
344
475
|
To handle file uploads, use the built-in `createUploader` function to create an uploader middleware with the desired storage configuration.
|
|
345
476
|
|
|
346
477
|
```ts
|
|
347
|
-
import { createUploader, StorageType } from "skyguard-js";
|
|
478
|
+
import { createUploader, StorageType, json } from "skyguard-js";
|
|
348
479
|
|
|
349
480
|
const uploader = createUploader({
|
|
350
481
|
storageType: StorageType.DISK,
|
|
@@ -358,7 +489,7 @@ const uploader = createUploader({
|
|
|
358
489
|
app.post(
|
|
359
490
|
"/upload",
|
|
360
491
|
(request: Request) => {
|
|
361
|
-
return
|
|
492
|
+
return json({
|
|
362
493
|
message: "File uploaded successfully",
|
|
363
494
|
file: request.file,
|
|
364
495
|
});
|
|
@@ -379,6 +510,7 @@ To render views, you must first set up the template engine using the `engineTemp
|
|
|
379
510
|
import { engine } from "express-handlebars";
|
|
380
511
|
import ejs from "ejs";
|
|
381
512
|
import { join } from "node:path";
|
|
513
|
+
import { render } from "skyguard-js";
|
|
382
514
|
|
|
383
515
|
app.views(__dirname, "views");
|
|
384
516
|
|
package/dist/app.d.ts
CHANGED
package/dist/app.js
CHANGED
package/dist/helpers/http.d.ts
CHANGED
|
@@ -56,3 +56,40 @@ export declare function download(path: string, filename?: string, headers?: Reco
|
|
|
56
56
|
* return await render("users/profile", { user }, "main");
|
|
57
57
|
*/
|
|
58
58
|
export declare function render(data: string, params?: Record<string, unknown>): Promise<Response>;
|
|
59
|
+
/**
|
|
60
|
+
* Sends a file as an HTTP response.
|
|
61
|
+
*
|
|
62
|
+
* This helper is a thin wrapper around `Response.sendFile`, allowing a file
|
|
63
|
+
* to be streamed to the client while optionally applying custom headers
|
|
64
|
+
* and resolving the file path relative to a root directory.
|
|
65
|
+
*
|
|
66
|
+
* @param filePath - Path to the file to send.
|
|
67
|
+
* @param options - Optional configuration for the file response.
|
|
68
|
+
* @param options.headers - Additional HTTP headers to include in the response
|
|
69
|
+
* (e.g. `Content-Type`, `Cache-Control`, `Content-Disposition`).
|
|
70
|
+
* @param options.root - Base directory used to resolve `filePath`.
|
|
71
|
+
* @returns A `Response` object that streams the requested file to the client.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* // Send a file using an absolute path
|
|
75
|
+
* const response = await sendFile("/var/www/files/report.pdf", {});
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* // Send a file relative to a root directory
|
|
79
|
+
* const response = await sendFile("report.pdf", {
|
|
80
|
+
* root: "/var/www/files",
|
|
81
|
+
* });
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* // Send a downloadable file
|
|
85
|
+
* const response = await sendFile("report.pdf", {
|
|
86
|
+
* root: "/var/www/files",
|
|
87
|
+
* headers: {
|
|
88
|
+
* "Content-Disposition": "attachment; filename=\"report.pdf\"",
|
|
89
|
+
* },
|
|
90
|
+
* });
|
|
91
|
+
*/
|
|
92
|
+
export declare function sendFile(filePath: string, options: {
|
|
93
|
+
headers?: Record<string, string>;
|
|
94
|
+
root?: string;
|
|
95
|
+
}): Promise<Response>;
|
package/dist/helpers/http.js
CHANGED
|
@@ -5,6 +5,7 @@ exports.text = text;
|
|
|
5
5
|
exports.redirect = redirect;
|
|
6
6
|
exports.download = download;
|
|
7
7
|
exports.render = render;
|
|
8
|
+
exports.sendFile = sendFile;
|
|
8
9
|
const response_1 = require("../http/response");
|
|
9
10
|
/**
|
|
10
11
|
* Creates an HTTP response with a JSON body.
|
|
@@ -73,3 +74,39 @@ async function download(path, filename, headers) {
|
|
|
73
74
|
async function render(data, params) {
|
|
74
75
|
return await response_1.Response.render(data, params);
|
|
75
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Sends a file as an HTTP response.
|
|
79
|
+
*
|
|
80
|
+
* This helper is a thin wrapper around `Response.sendFile`, allowing a file
|
|
81
|
+
* to be streamed to the client while optionally applying custom headers
|
|
82
|
+
* and resolving the file path relative to a root directory.
|
|
83
|
+
*
|
|
84
|
+
* @param filePath - Path to the file to send.
|
|
85
|
+
* @param options - Optional configuration for the file response.
|
|
86
|
+
* @param options.headers - Additional HTTP headers to include in the response
|
|
87
|
+
* (e.g. `Content-Type`, `Cache-Control`, `Content-Disposition`).
|
|
88
|
+
* @param options.root - Base directory used to resolve `filePath`.
|
|
89
|
+
* @returns A `Response` object that streams the requested file to the client.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* // Send a file using an absolute path
|
|
93
|
+
* const response = await sendFile("/var/www/files/report.pdf", {});
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* // Send a file relative to a root directory
|
|
97
|
+
* const response = await sendFile("report.pdf", {
|
|
98
|
+
* root: "/var/www/files",
|
|
99
|
+
* });
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* // Send a downloadable file
|
|
103
|
+
* const response = await sendFile("report.pdf", {
|
|
104
|
+
* root: "/var/www/files",
|
|
105
|
+
* headers: {
|
|
106
|
+
* "Content-Disposition": "attachment; filename=\"report.pdf\"",
|
|
107
|
+
* },
|
|
108
|
+
* });
|
|
109
|
+
*/
|
|
110
|
+
async function sendFile(filePath, options) {
|
|
111
|
+
return await response_1.Response.sendFile(filePath, options);
|
|
112
|
+
}
|
|
@@ -13,6 +13,7 @@ import { Request } from "./request";
|
|
|
13
13
|
export declare class NodeHttpAdapter implements HttpAdapter {
|
|
14
14
|
private readonly req;
|
|
15
15
|
private readonly res;
|
|
16
|
+
/** Content Parser instance for parsing body requests */
|
|
16
17
|
private contentParser;
|
|
17
18
|
/** Logger instance for request logging */
|
|
18
19
|
private logger;
|
|
@@ -16,6 +16,7 @@ const logger_1 = require("./logger");
|
|
|
16
16
|
class NodeHttpAdapter {
|
|
17
17
|
req;
|
|
18
18
|
res;
|
|
19
|
+
/** Content Parser instance for parsing body requests */
|
|
19
20
|
contentParser;
|
|
20
21
|
/** Logger instance for request logging */
|
|
21
22
|
logger;
|
|
@@ -48,7 +49,7 @@ class NodeHttpAdapter {
|
|
|
48
49
|
request.method === httpMethods_1.HttpMethods.put ||
|
|
49
50
|
request.method === httpMethods_1.HttpMethods.patch) {
|
|
50
51
|
const parsedData = await this.contentParser.parse(this.req);
|
|
51
|
-
request.
|
|
52
|
+
request.setBody(parsedData);
|
|
52
53
|
}
|
|
53
54
|
return request;
|
|
54
55
|
}
|
package/dist/http/request.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { HttpMethods } from "./httpMethods";
|
|
2
|
-
import type { Headers } from "../types";
|
|
3
2
|
import { Session } from "../sessions";
|
|
4
3
|
import type { UploadedFile } from "../parsers/parserInterface";
|
|
4
|
+
import { IncomingHttpHeaders } from "node:http";
|
|
5
5
|
/**
|
|
6
6
|
* Represents an incoming client request within the framework.
|
|
7
7
|
*
|
|
@@ -22,7 +22,7 @@ export declare class Request {
|
|
|
22
22
|
/** Normalized HTTP method */
|
|
23
23
|
private _method;
|
|
24
24
|
/** Parsed request body payload */
|
|
25
|
-
private
|
|
25
|
+
private _body;
|
|
26
26
|
/** Query string parameters */
|
|
27
27
|
private _query;
|
|
28
28
|
/** Dynamic route parameters (path params) */
|
|
@@ -42,15 +42,14 @@ export declare class Request {
|
|
|
42
42
|
get url(): string;
|
|
43
43
|
get method(): HttpMethods;
|
|
44
44
|
setMethod(method: HttpMethods): void;
|
|
45
|
-
get headers():
|
|
46
|
-
setHeaders(headers:
|
|
47
|
-
get query(): Record<string,
|
|
48
|
-
setQuery(query: Record<string,
|
|
49
|
-
get params(): Record<string,
|
|
50
|
-
setParams(params: Record<string,
|
|
51
|
-
get
|
|
52
|
-
|
|
53
|
-
getData<T>(): Partial<T>;
|
|
45
|
+
get headers(): IncomingHttpHeaders;
|
|
46
|
+
setHeaders(headers: IncomingHttpHeaders): void;
|
|
47
|
+
get query(): Record<string, unknown>;
|
|
48
|
+
setQuery(query: Record<string, unknown>): void;
|
|
49
|
+
get params(): Record<string, unknown>;
|
|
50
|
+
setParams(params: Record<string, unknown>): void;
|
|
51
|
+
get body(): Record<string, any>;
|
|
52
|
+
setBody(body: Record<string, any>): void;
|
|
54
53
|
get session(): Session;
|
|
55
54
|
setSession(session: Session): void;
|
|
56
55
|
/**
|
package/dist/http/request.js
CHANGED
|
@@ -22,7 +22,7 @@ class Request {
|
|
|
22
22
|
/** Normalized HTTP method */
|
|
23
23
|
_method;
|
|
24
24
|
/** Parsed request body payload */
|
|
25
|
-
|
|
25
|
+
_body = {};
|
|
26
26
|
/** Query string parameters */
|
|
27
27
|
_query = {};
|
|
28
28
|
/** Dynamic route parameters (path params) */
|
|
@@ -68,14 +68,11 @@ class Request {
|
|
|
68
68
|
setParams(params) {
|
|
69
69
|
this._params = params;
|
|
70
70
|
}
|
|
71
|
-
get
|
|
72
|
-
return this.
|
|
71
|
+
get body() {
|
|
72
|
+
return this._body;
|
|
73
73
|
}
|
|
74
|
-
|
|
75
|
-
this.
|
|
76
|
-
}
|
|
77
|
-
getData() {
|
|
78
|
-
return this._data;
|
|
74
|
+
setBody(body) {
|
|
75
|
+
this._body = body;
|
|
79
76
|
}
|
|
80
77
|
get session() {
|
|
81
78
|
return this._session;
|
package/dist/http/response.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Headers } from "../types";
|
|
2
1
|
import { type CookieOptions } from "../sessions/cookies";
|
|
2
|
+
import { IncomingHttpHeaders } from "node:http";
|
|
3
3
|
/**
|
|
4
4
|
* Represents an outgoing response sent to the client.
|
|
5
5
|
*
|
|
@@ -31,8 +31,8 @@ export declare class Response {
|
|
|
31
31
|
private _content;
|
|
32
32
|
get statusCode(): number;
|
|
33
33
|
setStatusCode(newStatus: number): this;
|
|
34
|
-
get headers():
|
|
35
|
-
setHeaders(headers:
|
|
34
|
+
get headers(): IncomingHttpHeaders;
|
|
35
|
+
setHeaders(headers: IncomingHttpHeaders): this;
|
|
36
36
|
private merge;
|
|
37
37
|
setHeader(header: string, value: string): this;
|
|
38
38
|
removeHeader(header: string): void;
|
|
@@ -109,7 +109,51 @@ export declare class Response {
|
|
|
109
109
|
* });
|
|
110
110
|
*/
|
|
111
111
|
static redirect(url: string): Response;
|
|
112
|
+
/**
|
|
113
|
+
* Prepare a response that forces a file download.
|
|
114
|
+
*
|
|
115
|
+
* Uses `FileDownloadHelper` to obtain the file content and the headers
|
|
116
|
+
* required to trigger a download (for example, `content-disposition`).
|
|
117
|
+
* Returns a `Response` instance containing the file content (typically a
|
|
118
|
+
* `Buffer`) and the download headers.
|
|
119
|
+
*
|
|
120
|
+
* @param path - Path to the file on disk or a location understood by the helper
|
|
121
|
+
* @param filename - Suggested filename for the downloaded file (optional)
|
|
122
|
+
* @param headers - Additional headers to merge into the response (optional)
|
|
123
|
+
* @returns Promise that resolves to a `Response` ready to be sent to the client
|
|
124
|
+
* @throws Propagates any exceptions thrown by `FileDownloadHelper.download`
|
|
125
|
+
* @example
|
|
126
|
+
* return await Response.download("./uploads/report.pdf", "report.pdf");
|
|
127
|
+
*/
|
|
112
128
|
static download(path: string, filename?: string, headers?: Record<string, string>): Promise<Response>;
|
|
129
|
+
/**
|
|
130
|
+
* Sends a file to the client for display (inline).
|
|
131
|
+
*
|
|
132
|
+
* Unlike {@link download}, this method does not force the browser to download
|
|
133
|
+
* the file. Instead, it attempts to display the file in the browser (e.g.,
|
|
134
|
+
* images, PDFs, HTML files). Sets appropriate `Content-Type` and
|
|
135
|
+
* `Content-Length` headers based on the file.
|
|
136
|
+
*
|
|
137
|
+
* @param filePath - Path to the file on disk
|
|
138
|
+
* @param options - Optional configuration for sending the file
|
|
139
|
+
* @returns A {@link Response} configured for inline file display
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* app.get("/preview", async () => {
|
|
143
|
+
* return Response.sendFile("./uploads/document.pdf");
|
|
144
|
+
* });
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* app.get("/image", async () => {
|
|
148
|
+
* return Response.sendFile("./assets/photo.jpg", {
|
|
149
|
+
* headers: { "Cache-Control": "max-age=3600" }
|
|
150
|
+
* });
|
|
151
|
+
* });
|
|
152
|
+
*/
|
|
153
|
+
static sendFile(filePath: string, options?: {
|
|
154
|
+
headers?: Record<string, string>;
|
|
155
|
+
root?: string;
|
|
156
|
+
}): Promise<Response>;
|
|
113
157
|
/**
|
|
114
158
|
* Renders an HTML view and returns it as an HTTP response.
|
|
115
159
|
*
|