skyguard-js 1.2.1 → 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 (39) hide show
  1. package/README.md +9 -683
  2. package/dist/app.d.ts +14 -9
  3. package/dist/app.js +27 -24
  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 +1 -0
  8. package/dist/http/index.js +3 -1
  9. package/dist/http/nodeNativeHttp.d.ts +4 -4
  10. package/dist/http/nodeNativeHttp.js +11 -4
  11. package/dist/http/request.d.ts +4 -0
  12. package/dist/http/request.js +8 -0
  13. package/dist/http/response.d.ts +21 -2
  14. package/dist/http/response.js +30 -2
  15. package/dist/index.d.ts +2 -2
  16. package/dist/index.js +2 -2
  17. package/dist/middlewares/cors.d.ts +10 -4
  18. package/dist/middlewares/cors.js +35 -18
  19. package/dist/middlewares/csrf.js +33 -33
  20. package/dist/middlewares/index.d.ts +1 -1
  21. package/dist/middlewares/rateLimiter.d.ts +58 -6
  22. package/dist/middlewares/rateLimiter.js +149 -40
  23. package/dist/middlewares/session.js +4 -4
  24. package/dist/routing/routeResolveFunc.d.ts +10 -0
  25. package/dist/routing/routeResolveFunc.js +21 -0
  26. package/dist/routing/router.d.ts +16 -10
  27. package/dist/routing/router.js +32 -25
  28. package/dist/routing/routerGroup.d.ts +10 -5
  29. package/dist/routing/routerGroup.js +11 -10
  30. package/dist/storage/storage.d.ts +3 -3
  31. package/dist/storage/storage.js +7 -7
  32. package/dist/storage/types.d.ts +5 -5
  33. package/dist/storage/uploader.d.ts +9 -9
  34. package/dist/storage/uploader.js +62 -62
  35. package/dist/types/index.d.ts +11 -10
  36. package/dist/validators/validationSchema.js +8 -8
  37. package/package.json +2 -2
  38. package/dist/helpers/http.d.ts +0 -95
  39. package/dist/helpers/http.js +0 -112
@@ -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();
@@ -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
  }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RouterGroup = void 0;
4
4
  const buildFullPath_1 = require("./buildFullPath");
5
+ const routeResolveFunc_1 = require("./routeResolveFunc");
5
6
  /**
6
7
  * Route group helper.
7
8
  *
@@ -64,24 +65,24 @@ class RouterGroup {
64
65
  if (totalMiddlewares.length > 0)
65
66
  layer.setMiddlewares(totalMiddlewares);
66
67
  }
67
- /** Registers a GET route within the group. */
68
- get(path, action, middlewares) {
68
+ get(path, handlerOrMiddleware, handler) {
69
+ const { action, middlewares } = (0, routeResolveFunc_1.normalizeRouteArgs)(handlerOrMiddleware, handler);
69
70
  this.addRoute("get", path, action, middlewares);
70
71
  }
71
- /** Registers a POST route within the group. */
72
- post(path, action, middlewares) {
72
+ post(path, handlerOrMiddleware, handler) {
73
+ const { action, middlewares } = (0, routeResolveFunc_1.normalizeRouteArgs)(handlerOrMiddleware, handler);
73
74
  this.addRoute("post", path, action, middlewares);
74
75
  }
75
- /** Registers a PUT route within the group. */
76
- put(path, action, middlewares) {
76
+ put(path, handlerOrMiddleware, handler) {
77
+ const { action, middlewares } = (0, routeResolveFunc_1.normalizeRouteArgs)(handlerOrMiddleware, handler);
77
78
  this.addRoute("put", path, action, middlewares);
78
79
  }
79
- /** Registers a PATCH route within the group. */
80
- patch(path, action, middlewares) {
80
+ patch(path, handlerOrMiddleware, handler) {
81
+ const { action, middlewares } = (0, routeResolveFunc_1.normalizeRouteArgs)(handlerOrMiddleware, handler);
81
82
  this.addRoute("patch", path, action, middlewares);
82
83
  }
83
- /** Registers a DELETE route within the group. */
84
- delete(path, action, middlewares) {
84
+ delete(path, handlerOrMiddleware, handler) {
85
+ const { action, middlewares } = (0, routeResolveFunc_1.normalizeRouteArgs)(handlerOrMiddleware, handler);
85
86
  this.addRoute("delete", path, action, middlewares);
86
87
  }
87
88
  }
@@ -1,6 +1,6 @@
1
1
  import { type Storage, type StorageOptions } from "./types";
2
- import { Request } from "../http/request";
3
2
  import type { UploadedFile } from "../parsers/parserInterface";
3
+ import { Context } from "../http/context";
4
4
  /**
5
5
  * Disk-based storage engine responsible for persisting uploaded files
6
6
  * to the local filesystem.
@@ -51,7 +51,7 @@ export declare class DiskStorage implements Storage {
51
51
  *
52
52
  * @throws UploadException if the file cannot be written to disk.
53
53
  */
54
- handleFile(request: Request, file: Partial<UploadedFile>, fileData: Buffer): Promise<UploadedFile>;
54
+ handleFile(context: Context, file: Partial<UploadedFile>, fileData: Buffer): Promise<UploadedFile>;
55
55
  /**
56
56
  * Removes a previously stored file from disk.
57
57
  *
@@ -99,6 +99,6 @@ export declare class MemoryStorage implements Storage {
99
99
  private ttlMs;
100
100
  private computeChecksum;
101
101
  constructor(options?: StorageOptions);
102
- handleFile(request: Request, file: Partial<UploadedFile>, fileData: Buffer): UploadedFile;
102
+ handleFile(context: Context, file: Partial<UploadedFile>, fileData: Buffer): UploadedFile;
103
103
  removeFile(file: UploadedFile): void;
104
104
  }
@@ -60,10 +60,10 @@ class DiskStorage {
60
60
  *
61
61
  * @throws UploadException if the file cannot be written to disk.
62
62
  */
63
- async handleFile(request, file, fileData) {
63
+ async handleFile(context, file, fileData) {
64
64
  try {
65
- const destination = await this.resolveDestination(request, file);
66
- const filename = await this.filenameGenerator(request, file);
65
+ const destination = await this.resolveDestination(context, file);
66
+ const filename = await this.filenameGenerator(context, file);
67
67
  const filePath = (0, node_path_1.join)(destination, filename);
68
68
  await (0, promises_1.mkdir)(destination, { recursive: true });
69
69
  await (0, promises_1.writeFile)(filePath, fileData);
@@ -102,9 +102,9 @@ class DiskStorage {
102
102
  * If a resolver function was provided, it will be executed.
103
103
  * Otherwise the static directory is returned.
104
104
  */
105
- async resolveDestination(request, file) {
105
+ async resolveDestination(context, file) {
106
106
  if (typeof this.destination === "function") {
107
- return await this.destination(request, file);
107
+ return await this.destination(context, file);
108
108
  }
109
109
  return this.destination;
110
110
  }
@@ -119,7 +119,7 @@ class DiskStorage {
119
119
  * Example:
120
120
  * 1700000000000-a3f9b1c2d4e5f678.png
121
121
  */
122
- generateUniqueFilename(request, file) {
122
+ generateUniqueFilename(context, file) {
123
123
  const timestamp = Date.now();
124
124
  const randomString = (0, node_crypto_1.randomBytes)(8).toString("hex");
125
125
  const ext = (0, node_path_1.extname)(file.originalname || "");
@@ -151,7 +151,7 @@ class MemoryStorage {
151
151
  this.ttlMs = options.memory.ttlMs ?? 0;
152
152
  this.computeChecksum = options.memory.computeChecksum ?? false;
153
153
  }
154
- handleFile(request, file, fileData) {
154
+ handleFile(context, file, fileData) {
155
155
  // Enforce per-file size if configured
156
156
  if (this.maxFileSize !== undefined && fileData.length > this.maxFileSize) {
157
157
  throw new uploadException_1.UploadException(`File too large. Max size: ${this.maxFileSize} bytes`, types_1.UploadErrorCode.LIMIT_FILE_SIZE, file.fieldName);
@@ -1,5 +1,5 @@
1
1
  import type { UploadedFile } from "../parsers/parserInterface";
2
- import { Request } from "../http/request";
2
+ import { Context } from "../http/context";
3
3
  /**
4
4
  * Available storage engine types.
5
5
  *
@@ -35,12 +35,12 @@ interface DiskStorageOptions {
35
35
  * Destination directory or resolver function.
36
36
  * If a function is provided, it receives the request and file metadata.
37
37
  */
38
- destination?: string | ((req: Request, file: Partial<UploadedFile>) => string | Promise<string>);
38
+ destination?: string | ((ctx: Context, file: Partial<UploadedFile>) => string | Promise<string>);
39
39
  /**
40
40
  * Custom filename generator.
41
41
  * Receives the request and partial file metadata.
42
42
  */
43
- filename?: (req: Request, file: Partial<UploadedFile>) => string | Promise<string>;
43
+ filename?: (ctx: Context, file: Partial<UploadedFile>) => string | Promise<string>;
44
44
  }
45
45
  interface MemoryStoreOptions {
46
46
  /** Maximum total bytes allowed to be stored in memory (default: 50MB) */
@@ -76,7 +76,7 @@ export interface Storage {
76
76
  * @param fileData Raw file buffer.
77
77
  * @returns Final uploaded file metadata.
78
78
  */
79
- handleFile(request: Request, file: Partial<UploadedFile>, fileData: Buffer): Promise<UploadedFile> | UploadedFile;
79
+ handleFile(context: Context, file: Partial<UploadedFile>, fileData: Buffer): Promise<UploadedFile> | UploadedFile;
80
80
  /**
81
81
  * Removes a stored file.
82
82
  *
@@ -99,7 +99,7 @@ export type FileFilterCallback = (error: Error | null, acceptFile: boolean) => v
99
99
  *
100
100
  * The callback must be invoked to accept or reject the file.
101
101
  */
102
- export type FileFilter = (request: Request, file: Partial<UploadedFile>, callback: FileFilterCallback) => void | Promise<void>;
102
+ export type FileFilter = (context: Context, file: Partial<UploadedFile>, callback: FileFilterCallback) => void | Promise<void>;
103
103
  /**
104
104
  * Global uploader configuration.
105
105
  */