skyguard-js 1.2.1 → 1.2.3

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 (64) hide show
  1. package/README.md +11 -680
  2. package/dist/app.d.ts +14 -9
  3. package/dist/app.js +37 -30
  4. package/dist/http/context.d.ts +115 -0
  5. package/dist/http/context.js +147 -0
  6. package/dist/http/httpAdapter.d.ts +4 -4
  7. package/dist/http/index.d.ts +3 -1
  8. package/dist/http/index.js +5 -1
  9. package/dist/http/logger.d.ts +9 -2
  10. package/dist/http/logger.js +49 -1
  11. package/dist/http/nodeNativeHttp.d.ts +4 -4
  12. package/dist/http/nodeNativeHttp.js +11 -4
  13. package/dist/http/request.d.ts +4 -0
  14. package/dist/http/request.js +8 -0
  15. package/dist/http/response.d.ts +21 -2
  16. package/dist/http/response.js +30 -2
  17. package/dist/http/webHttp.d.ts +19 -0
  18. package/dist/http/webHttp.js +95 -0
  19. package/dist/index.d.ts +2 -2
  20. package/dist/index.js +2 -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/parsers/contentParserManager.d.ts +4 -2
  29. package/dist/parsers/contentParserManager.js +22 -3
  30. package/dist/routing/routeResolveFunc.d.ts +10 -0
  31. package/dist/routing/routeResolveFunc.js +21 -0
  32. package/dist/routing/router.d.ts +16 -10
  33. package/dist/routing/router.js +32 -25
  34. package/dist/routing/routerGroup.d.ts +10 -5
  35. package/dist/routing/routerGroup.js +11 -10
  36. package/dist/server/bunRuntimeServer.d.ts +7 -0
  37. package/dist/server/bunRuntimeServer.js +28 -0
  38. package/dist/server/createRuntimeServer.d.ts +4 -0
  39. package/dist/server/createRuntimeServer.js +23 -0
  40. package/dist/server/denoRuntimeServer.d.ts +7 -0
  41. package/dist/server/denoRuntimeServer.js +27 -0
  42. package/dist/server/index.d.ts +7 -0
  43. package/dist/server/index.js +19 -0
  44. package/dist/server/modulePath.d.ts +6 -0
  45. package/dist/server/modulePath.js +18 -0
  46. package/dist/server/nodeRuntimeServer.d.ts +7 -0
  47. package/dist/server/nodeRuntimeServer.js +21 -0
  48. package/dist/server/runtimeDetector.d.ts +4 -0
  49. package/dist/server/runtimeDetector.js +15 -0
  50. package/dist/server/types.d.ts +11 -0
  51. package/dist/server/types.js +2 -0
  52. package/dist/sessions/index.d.ts +2 -2
  53. package/dist/storage/storage.d.ts +3 -3
  54. package/dist/storage/storage.js +7 -7
  55. package/dist/storage/types.d.ts +5 -5
  56. package/dist/storage/uploader.d.ts +9 -9
  57. package/dist/storage/uploader.js +62 -62
  58. package/dist/types/index.d.ts +11 -10
  59. package/dist/validators/index.d.ts +2 -2
  60. package/dist/validators/rules/index.d.ts +5 -5
  61. package/dist/validators/validationSchema.js +8 -8
  62. package/package.json +2 -2
  63. package/dist/helpers/http.d.ts +0 -95
  64. package/dist/helpers/http.js +0 -112
@@ -1,9 +1,41 @@
1
- import { Request, Response } from "../http";
2
- import type { Middleware } from "../types";
1
+ import { Context } from "../http";
2
+ import type { Middleware, RouteHandler } from "../types";
3
+ type MaybePromise<T> = T | Promise<T>;
4
+ /**
5
+ * Mutable counter entry for a single rate-limit key.
6
+ */
7
+ export interface RateLimitStoreEntry {
8
+ /** Requests performed in the current window. */
9
+ count: number;
10
+ /** Absolute timestamp (ms) when the current window expires. */
11
+ resetTime: number;
12
+ }
13
+ /**
14
+ * Pluggable store contract for rate limiting.
15
+ *
16
+ * Use a shared implementation (e.g. Redis) in multi-instance deployments.
17
+ */
18
+ export interface RateLimitStore {
19
+ /**
20
+ * Increments the counter for `key` in the active window.
21
+ *
22
+ * @param key - Unique client key.
23
+ * @param windowMs - Active window size in milliseconds.
24
+ * @param now - Current unix timestamp in milliseconds.
25
+ * @returns Updated counter entry for the key.
26
+ */
27
+ increment(key: string, windowMs: number, now: number): MaybePromise<RateLimitStoreEntry>;
28
+ /**
29
+ * Optional cleanup hook for removing expired entries.
30
+ *
31
+ * @param now - Current unix timestamp in milliseconds.
32
+ */
33
+ cleanup?(now: number): MaybePromise<void>;
34
+ }
3
35
  /**
4
36
  * Rate limit middleware configuration.
5
37
  */
6
- interface RateLimitOptions {
38
+ export interface RateLimitOptions {
7
39
  /**
8
40
  * Time window in milliseconds where requests are counted.
9
41
  * @default 60000
@@ -26,11 +58,31 @@ interface RateLimitOptions {
26
58
  /**
27
59
  * Resolves the identity key used for counting requests.
28
60
  */
29
- keyGenerator?: (request: Request) => string;
61
+ keyGenerator?: (context: Context) => string;
62
+ /**
63
+ * Trusts proxy-provided IP headers (`x-forwarded-for`, `x-real-ip`,
64
+ * `cf-connecting-ip`) in the default key generator.
65
+ * @default false
66
+ */
67
+ trustProxy?: boolean;
68
+ /**
69
+ * Custom store implementation. Defaults to in-memory store.
70
+ */
71
+ store?: RateLimitStore;
72
+ /**
73
+ * Periodic cleanup interval for stores that support `cleanup`.
74
+ * @default windowMs
75
+ */
76
+ cleanupIntervalMs?: number;
77
+ /**
78
+ * Max number of keys retained by the built-in in-memory store.
79
+ * @default 50000
80
+ */
81
+ maxKeys?: number;
30
82
  /**
31
83
  * Optional predicate to skip rate limiting for selected requests.
32
84
  */
33
- skip?: (request: Request) => boolean | Promise<boolean>;
85
+ skip?: (context: Context) => boolean | Promise<boolean>;
34
86
  /**
35
87
  * Includes `RateLimit-*` response headers.
36
88
  * @default true
@@ -44,7 +96,7 @@ interface RateLimitOptions {
44
96
  /**
45
97
  * Optional custom handler executed when the limit is exceeded.
46
98
  */
47
- handler?: (request: Request) => Response | Promise<Response>;
99
+ handler?: RouteHandler;
48
100
  }
49
101
  /**
50
102
  * Creates a configurable request rate-limiting middleware.
@@ -3,35 +3,146 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.rateLimit = void 0;
4
4
  const http_1 = require("../http");
5
5
  const httpExceptions_1 = require("../exceptions/httpExceptions");
6
+ const node_net_1 = require("node:net");
6
7
  /**
7
- * Default key generator used by the rate limiter to uniquely identify a client.
8
+ * Default in-memory implementation of {@link RateLimitStore}.
8
9
  *
9
- * The function attempts to extract the client IP address from common proxy
10
- * headers in order of priority:
11
- *
12
- * 1. `x-forwarded-for` → typically used by reverse proxies and load balancers.
13
- * If multiple IPs are present, the first one is assumed to be the client IP.
14
- * 2. `x-real-ip` → used by some proxies (e.g., Nginx).
15
- * 3. `cf-connecting-ip` used by Cloudflare to forward the original client IP.
10
+ * Use a shared external store (e.g. Redis) for distributed environments.
11
+ */
12
+ class MemoryRateLimitStore {
13
+ maxKeys;
14
+ store = new Map();
15
+ /**
16
+ * @param maxKeys - Maximum number of retained keys in memory.
17
+ */
18
+ constructor(maxKeys) {
19
+ this.maxKeys = maxKeys;
20
+ }
21
+ /**
22
+ * Increments an in-memory counter for the provided key.
23
+ *
24
+ * If the window is expired (or absent), a new window starts at `now`.
25
+ */
26
+ increment(key, windowMs, now) {
27
+ let entry = this.store.get(key);
28
+ if (!entry || now > entry.resetTime) {
29
+ entry = { count: 1, resetTime: now + windowMs };
30
+ this.store.set(key, entry);
31
+ this.trimIfNeeded(now);
32
+ return entry;
33
+ }
34
+ entry.count += 1;
35
+ return entry;
36
+ }
37
+ /**
38
+ * Removes expired entries from the internal map.
39
+ */
40
+ cleanup(now) {
41
+ for (const [key, value] of this.store.entries()) {
42
+ if (now > value.resetTime)
43
+ this.store.delete(key);
44
+ }
45
+ }
46
+ /**
47
+ * Enforces `maxKeys` and avoids unbounded memory growth.
48
+ */
49
+ trimIfNeeded(now) {
50
+ if (this.store.size <= this.maxKeys)
51
+ return;
52
+ this.cleanup(now);
53
+ while (this.store.size > this.maxKeys) {
54
+ const oldestKey = this.store.keys().next().value;
55
+ if (!oldestKey)
56
+ break;
57
+ this.store.delete(oldestKey);
58
+ }
59
+ }
60
+ }
61
+ /**
62
+ * Normalizes a candidate IP address into a canonical value.
16
63
  *
17
- * If none of these headers are available, the request `Host` header is used as
18
- * a fallback identifier. If that is also missing, `"anonymous"` is returned.
64
+ * Supported inputs:
65
+ * - Plain IPv4/IPv6
66
+ * - IPv4-mapped IPv6 (`::ffff:x.x.x.x`)
67
+ * - Bracketed IPv6 (`[2001:db8::1]`)
68
+ * - IPv4 with port (`x.x.x.x:port`)
19
69
  *
20
- * @param request - Incoming HTTP request.
21
- * @returns A string key used to track request counts per client.
70
+ * @param value - Raw address candidate.
71
+ * @returns Normalized IP, or `null` when the input is not a valid IP.
22
72
  */
23
- const defaultKeyGenerator = (request) => {
24
- const forwardedFor = request.headers["x-forwarded-for"];
25
- const realIp = request.headers["x-real-ip"];
26
- const cfIp = request.headers["cf-connecting-ip"];
27
- if (typeof forwardedFor === "string") {
28
- return forwardedFor.split(",")[0]?.trim() ?? "anonymous";
73
+ const normalizeAddress = (value) => {
74
+ if (!value)
75
+ return null;
76
+ const candidate = value.trim();
77
+ if (!candidate)
78
+ return null;
79
+ if ((0, node_net_1.isIP)(candidate))
80
+ return candidate;
81
+ if (candidate.startsWith("::ffff:")) {
82
+ const mapped = candidate.slice("::ffff:".length);
83
+ if ((0, node_net_1.isIP)(mapped))
84
+ return mapped;
29
85
  }
30
- if (typeof realIp === "string")
31
- return realIp;
32
- if (typeof cfIp === "string")
86
+ const withoutBrackets = candidate.startsWith("[") && candidate.endsWith("]")
87
+ ? candidate.slice(1, -1)
88
+ : candidate;
89
+ if ((0, node_net_1.isIP)(withoutBrackets))
90
+ return withoutBrackets;
91
+ const ipv4WithPort = withoutBrackets.match(/^(\d{1,3}(?:\.\d{1,3}){3}):\d+$/);
92
+ if (ipv4WithPort && (0, node_net_1.isIP)(ipv4WithPort[1]))
93
+ return ipv4WithPort[1];
94
+ return null;
95
+ };
96
+ /**
97
+ * Returns the first value when a header can be `string | string[]`.
98
+ */
99
+ const pickFirstHeaderValue = (value) => Array.isArray(value) ? value[0] : value;
100
+ /**
101
+ * Parses `x-forwarded-for` into an ordered list of candidates.
102
+ *
103
+ * @param value - Raw `x-forwarded-for` header value.
104
+ * @returns A trimmed list of forwarded addresses.
105
+ */
106
+ const parseForwardedFor = (value) => {
107
+ const firstValue = pickFirstHeaderValue(value);
108
+ if (!firstValue)
109
+ return [];
110
+ return firstValue
111
+ .split(",")
112
+ .map(item => item.trim())
113
+ .filter(Boolean);
114
+ };
115
+ /**
116
+ * Builds the default key generator used by the rate limiter.
117
+ *
118
+ * Behavior:
119
+ * - `trustProxy: false` -> uses only socket `remoteAddress` (plus host fallback).
120
+ * - `trustProxy: true` -> allows proxy headers in this priority:
121
+ * 1) `cf-connecting-ip`
122
+ * 2) `x-real-ip`
123
+ * 3) first valid item in `x-forwarded-for`
124
+ * 4) socket `remoteAddress` fallback
125
+ *
126
+ * @param trustProxy - Whether proxy headers are trusted.
127
+ * @returns A key generator function for rate-limit identity.
128
+ */
129
+ const buildDefaultKeyGenerator = (trustProxy) => (context) => {
130
+ const remoteAddress = normalizeAddress(context.remoteAddress);
131
+ if (!trustProxy)
132
+ return remoteAddress ?? context.headers.host ?? "anonymous";
133
+ const cfIp = normalizeAddress(pickFirstHeaderValue(context.headers["cf-connecting-ip"]));
134
+ if (cfIp)
33
135
  return cfIp;
34
- return request.headers.host ?? "anonymous";
136
+ const realIp = normalizeAddress(pickFirstHeaderValue(context.headers["x-real-ip"]));
137
+ if (realIp)
138
+ return realIp;
139
+ const forwardedCandidates = parseForwardedFor(context.headers["x-forwarded-for"]);
140
+ for (const candidate of forwardedCandidates) {
141
+ const trustedIp = normalizeAddress(candidate);
142
+ if (trustedIp)
143
+ return trustedIp;
144
+ }
145
+ return remoteAddress ?? context.headers.host ?? "anonymous";
35
146
  };
36
147
  /**
37
148
  * Generates HTTP headers describing the current rate limit state.
@@ -112,46 +223,44 @@ const getRateLimitHeaders = (limit, current, resetTime, now, includeStandardHead
112
223
  * ]);
113
224
  */
114
225
  const rateLimit = (options = {}) => {
226
+ const trustProxy = options.trustProxy ?? false;
227
+ const memoryStoreMaxKeys = Math.max(options.maxKeys ?? 50_000, 1);
228
+ const store = options.store ?? new MemoryRateLimitStore(memoryStoreMaxKeys);
115
229
  const config = {
116
230
  windowMs: options.windowMs ?? 60_000,
117
231
  max: options.max ?? 5,
118
232
  message: options.message ?? "Too many requests, please try again later.",
119
233
  statusCode: options.statusCode ?? 429,
120
- keyGenerator: options.keyGenerator ?? defaultKeyGenerator,
234
+ keyGenerator: options.keyGenerator ?? buildDefaultKeyGenerator(trustProxy),
121
235
  skip: options.skip,
122
236
  standardHeaders: options.standardHeaders ?? true,
123
237
  legacyHeaders: options.legacyHeaders ?? false,
124
238
  handler: options.handler,
125
239
  };
126
- const store = new Map();
127
- return async (request, next) => {
128
- if (config.skip && (await config.skip(request))) {
129
- return next(request);
240
+ const cleanupIntervalMs = Math.max(options.cleanupIntervalMs ?? config.windowMs, 1000);
241
+ let nextCleanupAt = Date.now() + cleanupIntervalMs;
242
+ return async (context, next) => {
243
+ if (config.skip && (await config.skip(context))) {
244
+ return next(context);
130
245
  }
131
246
  const now = Date.now();
132
- const key = config.keyGenerator(request);
133
- const current = store.get(key);
134
- if (!current || now > current.resetTime) {
135
- store.set(key, {
136
- count: 1,
137
- resetTime: now + config.windowMs,
138
- });
139
- }
140
- else {
141
- current.count += 1;
247
+ if (now >= nextCleanupAt && store.cleanup) {
248
+ await store.cleanup(now);
249
+ nextCleanupAt = now + cleanupIntervalMs;
142
250
  }
143
- const entry = store.get(key);
251
+ const key = config.keyGenerator(context) || "anonymous";
252
+ const entry = await store.increment(key, config.windowMs, now);
144
253
  const headers = getRateLimitHeaders(config.max, entry.count, entry.resetTime, now, config.standardHeaders, config.legacyHeaders);
145
254
  if (entry.count > config.max) {
146
255
  const retryAfter = Math.max(Math.ceil((entry.resetTime - now) / 1000), 0);
147
256
  headers["Retry-After"] = String(retryAfter);
148
257
  const blockedResponse = config.handler
149
- ? await config.handler(request)
258
+ ? await config.handler(context)
150
259
  : http_1.Response.json(new httpExceptions_1.TooManyRequestsError(config.message).toJSON()).setStatusCode(config.statusCode);
151
260
  blockedResponse.setHeaders(headers);
152
261
  return blockedResponse;
153
262
  }
154
- const response = await next(request);
263
+ const response = await next(context);
155
264
  response.setHeaders(headers);
156
265
  return response;
157
266
  };
@@ -68,17 +68,17 @@ const sessions = (StorageClass, options = {}) => {
68
68
  path: options.cookie?.path ?? "/",
69
69
  },
70
70
  };
71
- return async (request, next) => {
72
- const cookies = (0, cookies_1.parseCookies)(request.headers.cookie);
71
+ return async (context, next) => {
72
+ const cookies = (0, cookies_1.parseCookies)(context.headers.cookie);
73
73
  const sessionIdFromCookie = cookies[config.name];
74
74
  const storage = new StorageClass(config.cookie.maxAge);
75
75
  await loadSessionFromCookie(storage, sessionIdFromCookie);
76
76
  const session = new sessions_1.Session(storage);
77
- request.setSession(session);
77
+ context.req.setSession(session);
78
78
  const sessionIdBefore = storage.id();
79
79
  if (!sessionIdBefore && config.saveUninitialized)
80
80
  await storage.start();
81
- const response = await next(request);
81
+ const response = await next(context);
82
82
  const sessionIdAfter = storage.id();
83
83
  if (sessionIdAfter && config.rolling)
84
84
  await storage.touch();
@@ -26,14 +26,16 @@ export declare class ContentParserManager {
26
26
  * @param req - Native incoming HTTP request
27
27
  * @returns Parsed body content or raw body
28
28
  */
29
- parse(req: IncomingMessage): Promise<unknown>;
29
+ parse(req: IncomingMessage | globalThis.Request): Promise<unknown>;
30
30
  /**
31
31
  * Reads the raw request body.
32
32
  *
33
33
  * @param req - Native incoming HTTP request
34
34
  * @returns A promise that resolves to the full body buffer
35
35
  */
36
- private readBody;
36
+ private readNodeBody;
37
+ private readWebBody;
38
+ private isWebRequest;
37
39
  /**
38
40
  * Finds a parser capable of handling the given content type.
39
41
  *
@@ -43,10 +43,15 @@ class ContentParserManager {
43
43
  * @returns Parsed body content or raw body
44
44
  */
45
45
  async parse(req) {
46
- const body = await this.readBody(req);
46
+ const isWebRequest = this.isWebRequest(req);
47
+ const body = isWebRequest
48
+ ? await this.readWebBody(req)
49
+ : await this.readNodeBody(req);
47
50
  if (body.length <= 0)
48
51
  return {};
49
- const contentType = req.headers["content-type"] || "text/plain";
52
+ const contentType = isWebRequest
53
+ ? req.headers.get("content-type") || "text/plain"
54
+ : req.headers["content-type"] || "text/plain";
50
55
  const parser = this.findParser(contentType);
51
56
  if (!parser)
52
57
  return Buffer.isBuffer(body) ? body : Buffer.from(body);
@@ -58,7 +63,7 @@ class ContentParserManager {
58
63
  * @param req - Native incoming HTTP request
59
64
  * @returns A promise that resolves to the full body buffer
60
65
  */
61
- readBody(req) {
66
+ readNodeBody(req) {
62
67
  return new Promise((resolve, reject) => {
63
68
  const chunks = [];
64
69
  req.on("data", (chunk) => {
@@ -72,6 +77,20 @@ class ContentParserManager {
72
77
  });
73
78
  });
74
79
  }
80
+ async readWebBody(req) {
81
+ try {
82
+ const arrayBuffer = await req.arrayBuffer();
83
+ return Buffer.from(arrayBuffer);
84
+ }
85
+ catch {
86
+ throw new httpExceptions_1.UnprocessableContentError("Failed to read request body");
87
+ }
88
+ }
89
+ isWebRequest(req) {
90
+ const candidate = req;
91
+ return (typeof candidate.arrayBuffer === "function" &&
92
+ typeof candidate.headers?.get === "function");
93
+ }
75
94
  /**
76
95
  * Finds a parser capable of handling the given content type.
77
96
  *
@@ -0,0 +1,10 @@
1
+ import type { HandlerOrMiddlewares, RouteHandler, Middleware } from "../types";
2
+ /**
3
+ * Normalizes route registration args to support both signatures:
4
+ * - (path, action, middlewares?)
5
+ * - (path, middlewares, action)
6
+ */
7
+ export declare const normalizeRouteArgs: (handlerOrMiddlewares: HandlerOrMiddlewares, handler?: RouteHandler) => {
8
+ action: RouteHandler;
9
+ middlewares: Middleware[];
10
+ };
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeRouteArgs = void 0;
4
+ /**
5
+ * Normalizes route registration args to support both signatures:
6
+ * - (path, action, middlewares?)
7
+ * - (path, middlewares, action)
8
+ */
9
+ const normalizeRouteArgs = (handlerOrMiddlewares, handler) => {
10
+ if (Array.isArray(handlerOrMiddlewares)) {
11
+ return {
12
+ action: handler,
13
+ middlewares: handlerOrMiddlewares,
14
+ };
15
+ }
16
+ return {
17
+ action: handlerOrMiddlewares,
18
+ middlewares: Array.isArray(handler) ? handler : [],
19
+ };
20
+ };
21
+ exports.normalizeRouteArgs = normalizeRouteArgs;
@@ -1,5 +1,5 @@
1
1
  import type { Middleware, RouteHandler } from "../types";
2
- import { Request, Response } from "../http";
2
+ import { Context, Response } from "../http";
3
3
  import { Layer } from "./layer";
4
4
  import { RouterGroup } from "./routerGroup";
5
5
  /**
@@ -26,7 +26,7 @@ export declare class Router {
26
26
  * @returns The matching {@link Layer}
27
27
  * @throws {HttpNotFoundException} If no route matches
28
28
  */
29
- resolveLayer(request: Request): Layer;
29
+ resolveLayer(context: Context): Layer;
30
30
  /**
31
31
  * Resolves and executes a request.
32
32
  *
@@ -39,14 +39,15 @@ export declare class Router {
39
39
  * @param request - Request to process
40
40
  * @returns The handler/middleware response (sync or async)
41
41
  */
42
- resolve(request: Request): Promise<Response> | Response;
42
+ resolve(request: Context): Promise<Response> | Response;
43
+ private ensureContext;
43
44
  /**
44
45
  * Runs a middleware chain using the onion model.
45
46
  *
46
- * Each middleware receives `(request, next)` and can run code
47
+ * Each middleware receives `(context, next)` and can run code
47
48
  * before/after calling `next()`.
48
49
  *
49
- * @param request - Incoming request
50
+ * @param context - Incoming context
50
51
  * @param middlewares - Remaining middlewares to execute
51
52
  * @param target - Final route handler
52
53
  * @returns The response returned by a middleware or the final handler
@@ -87,13 +88,18 @@ export declare class Router {
87
88
  */
88
89
  middlewares(middlewares: Middleware[]): this;
89
90
  /** Registers a GET route. */
90
- get(path: string, action: RouteHandler, middlewares?: Middleware[]): Layer;
91
+ get(path: string, action: RouteHandler): Layer;
92
+ get(path: string, middlewares: Middleware[], action: RouteHandler): Layer;
91
93
  /** Registers a POST route. */
92
- post(path: string, action: RouteHandler, middlewares?: Middleware[]): Layer;
94
+ post(path: string, action: RouteHandler): Layer;
95
+ post(path: string, middlewares: Middleware[], action: RouteHandler): Layer;
93
96
  /** Registers a PATCH route. */
94
- patch(path: string, action: RouteHandler, middlewares?: Middleware[]): Layer;
97
+ patch(path: string, action: RouteHandler): Layer;
98
+ patch(path: string, middlewares: Middleware[], action: RouteHandler): Layer;
95
99
  /** Registers a PUT route. */
96
- put(path: string, action: RouteHandler, middlewares?: Middleware[]): Layer;
100
+ put(path: string, action: RouteHandler): Layer;
101
+ put(path: string, middlewares: Middleware[], action: RouteHandler): Layer;
97
102
  /** Registers a DELETE route. */
98
- delete(path: string, action: RouteHandler, middlewares?: Middleware[]): Layer;
103
+ delete(path: string, action: RouteHandler): Layer;
104
+ delete(path: string, middlewares: Middleware[], action: RouteHandler): Layer;
99
105
  }
@@ -6,6 +6,7 @@ const httpExceptions_1 = require("../exceptions/httpExceptions");
6
6
  const layer_1 = require("./layer");
7
7
  const routerGroup_1 = require("./routerGroup");
8
8
  const buildFullPath_1 = require("./buildFullPath");
9
+ const routeResolveFunc_1 = require("./routeResolveFunc");
9
10
  /**
10
11
  * Central routing system of the framework.
11
12
  *
@@ -34,10 +35,10 @@ class Router {
34
35
  * @returns The matching {@link Layer}
35
36
  * @throws {HttpNotFoundException} If no route matches
36
37
  */
37
- resolveLayer(request) {
38
- const routes = this.routes[request.method];
38
+ resolveLayer(context) {
39
+ const routes = this.routes[context.req.method];
39
40
  for (const route of routes) {
40
- if (route.matches(request.url))
41
+ if (route.matches(context.req.url))
41
42
  return route;
42
43
  }
43
44
  throw new httpExceptions_1.NotFoundError("Route not found");
@@ -55,38 +56,44 @@ class Router {
55
56
  * @returns The handler/middleware response (sync or async)
56
57
  */
57
58
  resolve(request) {
58
- const executeLayer = (req) => {
59
- const layer = this.resolveLayer(req);
59
+ const context = this.ensureContext(request);
60
+ const executeLayer = (ctx) => {
61
+ const layer = this.resolveLayer(ctx);
60
62
  if (layer.hasParameters()) {
61
- req.setParams(layer.parseParameters(req.url));
63
+ ctx.req.setParams(layer.parseParameters(ctx.req.url));
62
64
  }
63
65
  const action = layer.getAction;
64
66
  const routeMiddlewares = layer.getMiddlewares;
65
67
  if (routeMiddlewares.length > 0) {
66
- return this.runMiddlewares(req, routeMiddlewares, action);
68
+ return this.runMiddlewares(ctx, routeMiddlewares, action);
67
69
  }
68
- return action(req);
70
+ return action(ctx);
69
71
  };
70
72
  if (this.globalMiddlewares.length > 0) {
71
- return this.runMiddlewares(request, this.globalMiddlewares, executeLayer);
73
+ return this.runMiddlewares(context, this.globalMiddlewares, executeLayer);
72
74
  }
73
- return executeLayer(request);
75
+ return executeLayer(context);
76
+ }
77
+ ensureContext(request) {
78
+ if (request instanceof http_1.Context)
79
+ return request;
80
+ return new http_1.Context(request);
74
81
  }
75
82
  /**
76
83
  * Runs a middleware chain using the onion model.
77
84
  *
78
- * Each middleware receives `(request, next)` and can run code
85
+ * Each middleware receives `(context, next)` and can run code
79
86
  * before/after calling `next()`.
80
87
  *
81
- * @param request - Incoming request
88
+ * @param context - Incoming context
82
89
  * @param middlewares - Remaining middlewares to execute
83
90
  * @param target - Final route handler
84
91
  * @returns The response returned by a middleware or the final handler
85
92
  */
86
- runMiddlewares(request, middlewares, target) {
93
+ runMiddlewares(context, middlewares, target) {
87
94
  if (middlewares.length === 0)
88
- return target(request);
89
- return middlewares[0](request, request => this.runMiddlewares(request, middlewares.slice(1), target));
95
+ return target(context);
96
+ return middlewares[0](context, nextContext => this.runMiddlewares(this.ensureContext(nextContext), middlewares.slice(1), target));
90
97
  }
91
98
  /**
92
99
  * Registers a route and returns the created {@link Layer}.
@@ -138,24 +145,24 @@ class Router {
138
145
  this.globalMiddlewares.push(...middlewares);
139
146
  return this;
140
147
  }
141
- /** Registers a GET route. */
142
- get(path, action, middlewares) {
148
+ get(path, handlerOrMiddlewares, handler) {
149
+ const { action, middlewares } = (0, routeResolveFunc_1.normalizeRouteArgs)(handlerOrMiddlewares, handler);
143
150
  return this.registerRoute(http_1.HttpMethods.get, path, action, middlewares);
144
151
  }
145
- /** Registers a POST route. */
146
- post(path, action, middlewares) {
152
+ post(path, handlerOrMiddlewares, handler) {
153
+ const { action, middlewares } = (0, routeResolveFunc_1.normalizeRouteArgs)(handlerOrMiddlewares, handler);
147
154
  return this.registerRoute(http_1.HttpMethods.post, path, action, middlewares);
148
155
  }
149
- /** Registers a PATCH route. */
150
- patch(path, action, middlewares) {
156
+ patch(path, handlerOrMiddlewares, handler) {
157
+ const { action, middlewares } = (0, routeResolveFunc_1.normalizeRouteArgs)(handlerOrMiddlewares, handler);
151
158
  return this.registerRoute(http_1.HttpMethods.patch, path, action, middlewares);
152
159
  }
153
- /** Registers a PUT route. */
154
- put(path, action, middlewares) {
160
+ put(path, handlerOrMiddlewares, handler) {
161
+ const { action, middlewares } = (0, routeResolveFunc_1.normalizeRouteArgs)(handlerOrMiddlewares, handler);
155
162
  return this.registerRoute(http_1.HttpMethods.put, path, action, middlewares);
156
163
  }
157
- /** Registers a DELETE route. */
158
- delete(path, action, middlewares) {
164
+ delete(path, handlerOrMiddlewares, handler) {
165
+ const { action, middlewares } = (0, routeResolveFunc_1.normalizeRouteArgs)(handlerOrMiddlewares, handler);
159
166
  return this.registerRoute(http_1.HttpMethods.delete, path, action, middlewares);
160
167
  }
161
168
  }
@@ -51,13 +51,18 @@ export declare class RouterGroup {
51
51
  */
52
52
  private addRoute;
53
53
  /** Registers a GET route within the group. */
54
- get(path: string, action: RouteHandler, middlewares?: Middleware[]): void;
54
+ get(path: string, action: RouteHandler): void;
55
+ get(path: string, middlewares: Middleware[], action: RouteHandler): void;
55
56
  /** Registers a POST route within the group. */
56
- post(path: string, action: RouteHandler, middlewares?: Middleware[]): void;
57
+ post(path: string, action: RouteHandler): void;
58
+ post(path: string, middlewares: Middleware[], action: RouteHandler): void;
57
59
  /** Registers a PUT route within the group. */
58
- put(path: string, action: RouteHandler, middlewares?: Middleware[]): void;
60
+ put(path: string, action: RouteHandler): void;
61
+ put(path: string, middlewares: Middleware[], action: RouteHandler): void;
59
62
  /** Registers a PATCH route within the group. */
60
- patch(path: string, action: RouteHandler, middlewares?: Middleware[]): void;
63
+ patch(path: string, action: RouteHandler): void;
64
+ patch(path: string, middlewares: Middleware[], action: RouteHandler): void;
61
65
  /** Registers a DELETE route within the group. */
62
- delete(path: string, action: RouteHandler, middlewares?: Middleware[]): void;
66
+ delete(path: string, action: RouteHandler): void;
67
+ delete(path: string, middlewares: Middleware[], action: RouteHandler): void;
63
68
  }