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 CHANGED
@@ -6,6 +6,7 @@
6
6
 
7
7
  [![NPM Version](https://img.shields.io/npm/v/skyguard-js)](https://www.npmjs.com/package/skyguard-js)
8
8
  [![Deployment Pipeline](https://github.com/Pipe930/Skyguard-js/actions/workflows/pipeline.yml/badge.svg)](https://github.com/Pipe930/Skyguard-js/actions/workflows/pipeline.yml)
9
+ [![Socket Badge](https://badge.socket.dev/npm/package/skyguard-js/1.1.8)](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
- - Simple template engine with layouts and helpers
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", () => Response.json({ message: "Users" }));
101
- api.get("/products", () => Response.json({ message: "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 Response.json({ message: "Unauthorized" }).setStatus(401);
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([authMiddleware]);
143
+ app.middlewares(authMiddleware);
127
144
 
128
145
  // Group middleware
129
146
  app.group("/admin", admin => {
130
- admin.middlewares([authMiddleware]);
131
- admin.get("/dashboard", () => Response.json({ ok: true }));
147
+ admin.middlewares(authMiddleware);
148
+ admin.get("/dashboard", () => json({ ok: true }));
132
149
  });
133
150
 
134
151
  // Route-level middleware
135
- app.get("/secure", () => Response.json({ secure: true }), [authMiddleware]);
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, validateData } from "skyguard-js";
316
+ import { v, schema, validateRequest, json } from "skyguard-js";
179
317
 
180
318
  // Created Schema
181
319
  const userSchema = schema({
182
- name: v.string({ maxLength: 60 }),
183
- email: v.email(),
184
- age: v.number({ min: 18 }),
185
- active: v.boolean().default(false),
186
- birthdate: v.date({ max: new Date() }),
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.getData<User>(); // The .getData() method returns the typed data with the interface you created
332
+ const data = request.body;
202
333
  return json(data).setStatusCode(201);
203
334
  },
204
- [validateData(userSchema)],
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 Response.json(resource);
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 Response.json({ result });
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 Response.json({ message: "User registered" });
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 Response.json({ token });
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 Response.json({
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
@@ -140,7 +140,7 @@ export declare class App {
140
140
  *
141
141
  * app.middlewares(auth);
142
142
  */
143
- middlewares(middlewares: Middleware[]): void;
143
+ middlewares(...middlewares: Middleware[]): void;
144
144
  /**
145
145
  * Creates a route group with a shared prefix.
146
146
  *
package/dist/app.js CHANGED
@@ -195,7 +195,7 @@ class App {
195
195
  *
196
196
  * app.middlewares(auth);
197
197
  */
198
- middlewares(middlewares) {
198
+ middlewares(...middlewares) {
199
199
  this.router.middlewares(middlewares);
200
200
  }
201
201
  /**
@@ -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>;
@@ -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.setData(parsedData);
52
+ request.setBody(parsedData);
52
53
  }
53
54
  return request;
54
55
  }
@@ -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 _data;
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(): Headers;
46
- setHeaders(headers: Headers): void;
47
- get query(): Record<string, string>;
48
- setQuery(query: Record<string, string>): void;
49
- get params(): Record<string, string>;
50
- setParams(params: Record<string, string>): void;
51
- get data(): Record<string, any>;
52
- setData(data: Record<string, any>): void;
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
  /**
@@ -22,7 +22,7 @@ class Request {
22
22
  /** Normalized HTTP method */
23
23
  _method;
24
24
  /** Parsed request body payload */
25
- _data = {};
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 data() {
72
- return this._data;
71
+ get body() {
72
+ return this._body;
73
73
  }
74
- setData(data) {
75
- this._data = data;
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;
@@ -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(): Headers;
35
- setHeaders(headers: Headers): this;
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
  *