skyguard-js 1.2.0 → 1.2.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.
Files changed (52) hide show
  1. package/README.md +9 -462
  2. package/dist/app.d.ts +34 -10
  3. package/dist/app.js +59 -27
  4. package/dist/crypto/hasher.js +2 -1
  5. package/dist/crypto/jwt.js +2 -1
  6. package/dist/http/context.d.ts +115 -0
  7. package/dist/http/context.js +147 -0
  8. package/dist/http/httpAdapter.d.ts +4 -4
  9. package/dist/http/index.d.ts +2 -0
  10. package/dist/http/index.js +3 -1
  11. package/dist/http/logger.d.ts +10 -1
  12. package/dist/http/logger.js +44 -8
  13. package/dist/http/nodeNativeHttp.d.ts +6 -5
  14. package/dist/http/nodeNativeHttp.js +13 -6
  15. package/dist/http/request.d.ts +4 -0
  16. package/dist/http/request.js +12 -4
  17. package/dist/http/response.d.ts +21 -2
  18. package/dist/http/response.js +30 -2
  19. package/dist/index.d.ts +4 -4
  20. package/dist/index.js +3 -2
  21. package/dist/middlewares/cors.d.ts +10 -4
  22. package/dist/middlewares/cors.js +35 -18
  23. package/dist/middlewares/csrf.js +33 -33
  24. package/dist/middlewares/index.d.ts +1 -1
  25. package/dist/middlewares/rateLimiter.d.ts +58 -6
  26. package/dist/middlewares/rateLimiter.js +149 -40
  27. package/dist/middlewares/session.js +4 -4
  28. package/dist/routing/routeResolveFunc.d.ts +10 -0
  29. package/dist/routing/routeResolveFunc.js +21 -0
  30. package/dist/routing/router.d.ts +16 -10
  31. package/dist/routing/router.js +32 -25
  32. package/dist/routing/routerGroup.d.ts +10 -5
  33. package/dist/routing/routerGroup.js +11 -10
  34. package/dist/sessions/databaseSessionStorage.d.ts +52 -0
  35. package/dist/sessions/databaseSessionStorage.js +166 -0
  36. package/dist/sessions/fileSessionStorage.js +1 -1
  37. package/dist/sessions/index.d.ts +1 -0
  38. package/dist/sessions/index.js +3 -1
  39. package/dist/sessions/memorySessionStorage.js +1 -1
  40. package/dist/storage/storage.d.ts +3 -3
  41. package/dist/storage/storage.js +7 -7
  42. package/dist/storage/types.d.ts +5 -5
  43. package/dist/storage/uploader.d.ts +9 -9
  44. package/dist/storage/uploader.js +62 -62
  45. package/dist/types/index.d.ts +11 -10
  46. package/dist/validators/rules/bigIntRule.d.ts +0 -6
  47. package/dist/validators/rules/bigIntRule.js +0 -24
  48. package/dist/validators/validationRule.js +1 -1
  49. package/dist/validators/validationSchema.js +8 -8
  50. package/package.json +2 -2
  51. package/dist/helpers/http.d.ts +0 -95
  52. package/dist/helpers/http.js +0 -112
package/README.md CHANGED
@@ -31,7 +31,7 @@ Skyguard.js currently delivers a solid core that includes **routing**, **type-sa
31
31
  - HTTP routing by method (GET, POST, PUT, PATCH, DELETE)
32
32
  - Route groups with prefixes
33
33
  - Global, group, and route-level middlewares
34
- - Request / Response abstractions
34
+ - Unified `Context` abstraction (`ctx.req` + response helpers)
35
35
  - Declarative data validation
36
36
  - Support for template motors (handlebars, pugs, ejs, etc.)
37
37
  - Built-in HTTP exceptions
@@ -73,479 +73,22 @@ npm install skyguard-js
73
73
  ## 🏁 Quick Start
74
74
 
75
75
  ```ts
76
- import { createApp, Response } from "skyguard-js";
76
+ import { createApp } from "skyguard-js";
77
77
 
78
78
  const app = createApp();
79
79
 
80
- const PORT = 3000;
80
+ app.get("/", ctx => ctx.json({ status: "ok" }));
81
81
 
82
- app.get("/health", () => {
83
- return Response.json({ status: "ok" });
84
- });
85
-
86
- app.run(PORT, () => {
87
- console.log(`Server running in port: http://localhost:${PORT}`);
88
- });
89
- ```
90
-
91
- ---
92
-
93
- ## 🛣️ Routing
94
-
95
- Routes are registered using HTTP methods on the `app` instance.
96
-
97
- ```ts
98
- app.get("/posts/{id}", (request: Request) => {
99
- return Response.json(request.params);
100
- });
101
-
102
- app.post("/posts", (request: Request) => {
103
- return Response.json(request.data);
104
- });
105
- ```
106
-
107
- Internally, the framework maps HTTP methods to route layers using an optimized routing table.
108
-
109
- ---
110
-
111
- ## 🧩 Route Groups
112
-
113
- Route groups allow you to organize endpoints under a shared prefix.
114
-
115
- ```ts
116
- app.group("/api", api => {
117
- api.get("/users", () => res.json({ message: "Users" }));
118
- api.get("/products", () => res.json({ message: "Products" }));
119
- });
120
- ```
121
-
122
- ---
123
-
124
- ## 🛠️ Middlewares
125
-
126
- Middlewares can be registered **globally**, **per group**, or **per route**.
127
-
128
- ```ts
129
- import { Request, Response, json, RouteHandler } from "skyguard-js";
130
-
131
- const authMiddleware = async (
132
- request: Request,
133
- next: RouteHandler,
134
- ): Promise<Response> => {
135
- if (request.headers["authorization"] !== "secret") {
136
- return json({ message: "Unauthorized" }).setStatus(401);
137
- }
138
-
139
- return next(request);
140
- };
141
-
142
- // Global middleware
143
- app.middlewares(authMiddleware);
144
-
145
- // Group middleware
146
- app.group("/admin", admin => {
147
- admin.middlewares(authMiddleware);
148
- admin.get("/dashboard", () => json({ ok: true }));
149
- });
150
-
151
- // Route-level middleware
152
- app.get("/secure", () => json({ secure: true }), [authMiddleware]);
153
- ```
154
-
155
- ---
156
-
157
- ## 🌐 CORS Middleware
158
-
159
- To enable CORS, use the built-in `cors` middleware.
160
-
161
- ```ts
162
- import { cors, HttpMethods } from "skyguard-js";
163
-
164
- app.middlewares(
165
- cors({
166
- origin: ["http://localhost:3000", "https://myapp.com"],
167
- methods: [HttpMethods.get, HttpMethods.post],
168
- allowedHeaders: ["Content-Type", "Authorization"],
169
- credentials: true,
170
- }),
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
- );
82
+ app.run();
293
83
  ```
294
84
 
295
85
  ---
296
86
 
297
- ## 📌 Static Files
298
-
299
- To serve static files, use the application's `staticFiles` method with the directory path. The name of the folder will determine the initial route prefix.
300
-
301
- ```ts
302
- import { join } from "node:path";
303
-
304
- app.staticFiles(join(__dirname, "..", "static"));
305
-
306
- // Route http://localhost:3000/static/style.css will serve the file located at ./static/style.css
307
- ```
308
-
309
- ---
310
-
311
- ## ⛔ Data Validation
312
-
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:
314
-
315
- ```ts
316
- import { v, schema, validateRequest, json } from "skyguard-js";
317
-
318
- // Created Schema
319
- const userSchema = schema({
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
- },
327
- });
328
-
329
- app.post(
330
- "/test",
331
- (request: Request) => {
332
- const data = request.body;
333
- return json(data).setStatusCode(201);
334
- },
335
- [validateRequest(userSchema)],
336
- );
337
- ```
338
-
339
- To type the request body, an interface is used and the .getData() method is used, which allows returning the typed bodym. By default each property you define in the schema is required, to define it optional you use the `.optional()` or `.default(value)` function
340
-
341
- Validation is:
342
-
343
- - Fail-fast per field
344
- - Fully typed
345
- - Reusable
346
- - Decoupled from transport layer
347
-
348
- ---
349
-
350
- ## 🚨 Exceptions & Error Handling
351
-
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.
353
-
354
- ```ts
355
- import { NotFoundError, InternalServerError, json } from "skyguard-js";
356
-
357
- const listResources = ["1", "2", "3"];
358
-
359
- app.get("/resource/{id}", (request: Request) => {
360
- const resource = request.params["id"];
361
-
362
- if (!listResources.includes(resource)) {
363
- throw new NotFoundError("Resource not found");
364
- }
365
-
366
- return json(resource);
367
- });
368
-
369
- app.get("/divide", (request: Request) => {
370
- try {
371
- const { a, b } = request.query;
372
- const result = Number(a) / Number(b);
373
-
374
- return json({ result });
375
- } catch (error) {
376
- throw new InternalServerError(
377
- "An error occurred while processing your request",
378
- );
379
- }
380
- });
381
- ```
382
-
383
- ---
384
-
385
- ## 🧱 Sessions
386
-
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.
388
-
389
- ```ts
390
- import { sessions, FileSessionStorage, json } from "skyguard-js";
391
-
392
- app.middlewares(
393
- sessions(FileSessionStorage, {
394
- name: "connect.sid",
395
- rolling: true,
396
- saveUninitialized: false,
397
- cookie: {
398
- maxAge: 60 * 60 * 24,
399
- httpOnly: true,
400
- sameSite: "Lax",
401
- secure: false,
402
- path: "/",
403
- },
404
- }),
405
- );
406
-
407
- app.post("/login", (request: Request) => {
408
- const { username, password } = request.data;
409
-
410
- if (username === "admin" && password === "secret") {
411
- request.session.set("user", {
412
- id: 1,
413
- username: "admin",
414
- role: "admin",
415
- });
416
-
417
- return json({ message: "Logged in" });
418
- }
419
-
420
- throw new UnauthorizedError("Invalid credentials");
421
- });
422
-
423
- app.get("/me", (request: Request) => {
424
- const user = request.session.get("user");
425
-
426
- if (!user) throw new UnauthorizedError("Not authenticated");
427
- return json({ user });
428
- });
429
- ```
430
-
431
- ---
432
-
433
- ## 🛡️ Security
434
-
435
- The framework includes some password hashing and JWT token generation functions, and also includes JWT authentication middleware.
436
-
437
- ```ts
438
- import { Hasher, JWT, json } from "skyguard-js";
439
-
440
- app.post("/register", async (request: Request) => {
441
- const { username, password } = request.data;
442
- const hashedPassword = await Hasher.hash(password);
443
-
444
- // Save username and hashedPassword to database
445
- // ...
446
-
447
- return json({ message: "User registered" });
448
- });
449
-
450
- app.post("/login", async (request: Request) => {
451
- const { username, password } = request.data;
452
-
453
- // Retrieve user from database by username
454
- // ...
455
-
456
- const isValid = await Hasher.verify(password, user.hashedPassword);
457
-
458
- if (!isValid) {
459
- throw new UnauthorizedError("Invalid credentials");
460
- }
461
-
462
- const token = JWT.create({ sub: "123" }, "secret-key", {
463
- algorithm: "HS256",
464
- expiresIn: "1h",
465
- });
466
-
467
- return json({ token });
468
- });
469
- ```
470
-
471
- ---
472
-
473
- ## 📂 File Uploads
474
-
475
- To handle file uploads, use the built-in `createUploader` function to create an uploader middleware with the desired storage configuration.
476
-
477
- ```ts
478
- import { createUploader, StorageType, json } from "skyguard-js";
479
-
480
- const uploader = createUploader({
481
- storageType: StorageType.DISK,
482
- storageOptions: {
483
- disk: {
484
- destination: "./uploads",
485
- },
486
- },
487
- });
488
-
489
- app.post(
490
- "/upload",
491
- (request: Request) => {
492
- return json({
493
- message: "File uploaded successfully",
494
- file: request.file,
495
- });
496
- },
497
- [uploader.single("file")],
498
- );
499
- ```
500
-
501
- Depending on the `Storage Type` you have selected, the storage options will contain two properties: `disk` and `memory`
502
-
503
- ---
504
-
505
- ## 📄 Views & Template Engine
506
-
507
- 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.
508
-
509
- ```ts
510
- import { engine } from "express-handlebars";
511
- import ejs from "ejs";
512
- import { join } from "node:path";
513
- import { render } from "skyguard-js";
514
-
515
- app.views(__dirname, "views");
516
-
517
- // Config for Express Handlebars
518
- app.engineTemplates(
519
- "hbs",
520
- engine({
521
- extname: "hbs",
522
- layoutsDir: join(__dirname, "views"),
523
- defaultLayout: "main",
524
- }),
525
- );
526
-
527
- // Config for EJS
528
- app.engineTemplates("ejs", (templatePath, data) => {
529
- return ejs.renderFile(templatePath, data);
530
- });
531
-
532
- app.get("/home", () => {
533
- return render("index", {
534
- title: "Home Page",
535
- message: "Welcome to the home page!",
536
- });
537
- });
538
- ```
539
-
540
- 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.
541
-
542
- ---
543
-
544
87
  ## 🔮 Roadmap (Tentative)
545
88
 
546
89
  - Middleware system (✅)
547
90
  - Template engines supported (✅)
548
- - Request / Response abstraction (✅)
91
+ - Context abstraction (✅)
549
92
  - Data validation (✅)
550
93
  - Error handling improvements (✅)
551
94
  - Sessions & cookies (✅)
@@ -554,3 +97,7 @@ Currently, it works with third-party template engines such as **Express Handleba
554
97
  - Database & ORM integration
555
98
  - Authentication & authorization
556
99
  - WebSockets
100
+
101
+ ## License
102
+
103
+ MIT
package/dist/app.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { RouterGroup } from "./routing";
2
+ import { type LogFormat } from "./http";
2
3
  import type { Middleware, RouteHandler } from "./types";
3
4
  import { type TemplateEngineFunction } from "./views/engineTemplate";
4
5
  /**
@@ -19,13 +20,15 @@ import { type TemplateEngineFunction } from "./views/engineTemplate";
19
20
  * from the runtime platform (Node, Bun, Deno, etc.)
20
21
  * through {@link HttpAdapter} and {@link Server}.
21
22
  */
22
- export declare class App {
23
+ declare class App {
23
24
  /** Main routing system */
24
25
  private router;
25
26
  /** Static file handler (optional) */
26
27
  private staticFileHandler;
27
28
  /** View engine for rendering templates (optional) */
28
29
  private viewEngine;
30
+ /** Logger configuration */
31
+ private loggerOptions;
29
32
  /**
30
33
  * Bootstraps and configures the application.
31
34
  *
@@ -106,7 +109,22 @@ export declare class App {
106
109
  *
107
110
  * @param port - TCP port to listen on
108
111
  */
109
- run(port: number, callback: VoidFunction, hostname?: string): void;
112
+ run(port?: number, callback?: VoidFunction, hostname?: string): void;
113
+ /**
114
+ * Configures HTTP request logger output format and optional file output.
115
+ *
116
+ * Supported formats are inspired by morgan:
117
+ * - "combined"
118
+ * - "common"
119
+ * - "dev"
120
+ * - "short"
121
+ * - "tiny"
122
+ *
123
+ * @example
124
+ * app.logger("common");
125
+ * app.logger("combined", "./logs/http.log");
126
+ */
127
+ logger(format?: LogFormat, filePath?: string): void;
110
128
  /**
111
129
  * Sets a global prefix for all routes.
112
130
  *
@@ -118,24 +136,29 @@ export declare class App {
118
136
  */
119
137
  setPrefix(prefix: string): void;
120
138
  /** Registers a GET route */
121
- get(path: string, action: RouteHandler, middlewares?: Middleware[]): void;
139
+ get(path: string, action: RouteHandler): void;
140
+ get(path: string, middlewares: Middleware[], action: RouteHandler): void;
122
141
  /** Registers a POST route */
123
- post(path: string, action: RouteHandler, middlewares?: Middleware[]): void;
142
+ post(path: string, action: RouteHandler): void;
143
+ post(path: string, middlewares: Middleware[], action: RouteHandler): void;
124
144
  /** Registers a PUT route */
125
- put(path: string, action: RouteHandler, middlewares?: Middleware[]): void;
145
+ put(path: string, action: RouteHandler): void;
146
+ put(path: string, middlewares: Middleware[], action: RouteHandler): void;
126
147
  /** Registers a PATCH route */
127
- patch(path: string, action: RouteHandler, middlewares?: Middleware[]): void;
148
+ patch(path: string, action: RouteHandler): void;
149
+ patch(path: string, middlewares: Middleware[], action: RouteHandler): void;
128
150
  /** Registers a DELETE route */
129
- delete(path: string, action: RouteHandler, middlewares?: Middleware[]): void;
151
+ delete(path: string, action: RouteHandler): void;
152
+ delete(path: string, middlewares: Middleware[], action: RouteHandler): void;
130
153
  /**
131
154
  * Registers global middlewares.
132
155
  *
133
156
  * These are executed for every route.
134
157
  *
135
158
  * @example
136
- * const auth = async (request, next) => {
137
- * console.log(request.header);
138
- * return await next(request);
159
+ * const auth = async (context, next) => {
160
+ * console.log(context.headers);
161
+ * return await next(context);
139
162
  * }
140
163
  *
141
164
  * app.middlewares(auth);
@@ -159,3 +182,4 @@ export declare class App {
159
182
  private handleError;
160
183
  }
161
184
  export declare const createApp: () => App;
185
+ export {};