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.
- package/README.md +9 -683
- package/dist/app.d.ts +14 -9
- package/dist/app.js +27 -24
- package/dist/http/context.d.ts +115 -0
- package/dist/http/context.js +147 -0
- package/dist/http/httpAdapter.d.ts +4 -4
- package/dist/http/index.d.ts +1 -0
- package/dist/http/index.js +3 -1
- package/dist/http/nodeNativeHttp.d.ts +4 -4
- package/dist/http/nodeNativeHttp.js +11 -4
- package/dist/http/request.d.ts +4 -0
- package/dist/http/request.js +8 -0
- package/dist/http/response.d.ts +21 -2
- package/dist/http/response.js +30 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/middlewares/cors.d.ts +10 -4
- package/dist/middlewares/cors.js +35 -18
- package/dist/middlewares/csrf.js +33 -33
- package/dist/middlewares/index.d.ts +1 -1
- package/dist/middlewares/rateLimiter.d.ts +58 -6
- package/dist/middlewares/rateLimiter.js +149 -40
- package/dist/middlewares/session.js +4 -4
- package/dist/routing/routeResolveFunc.d.ts +10 -0
- package/dist/routing/routeResolveFunc.js +21 -0
- package/dist/routing/router.d.ts +16 -10
- package/dist/routing/router.js +32 -25
- package/dist/routing/routerGroup.d.ts +10 -5
- package/dist/routing/routerGroup.js +11 -10
- package/dist/storage/storage.d.ts +3 -3
- package/dist/storage/storage.js +7 -7
- package/dist/storage/types.d.ts +5 -5
- package/dist/storage/uploader.d.ts +9 -9
- package/dist/storage/uploader.js +62 -62
- package/dist/types/index.d.ts +11 -10
- package/dist/validators/validationSchema.js +8 -8
- package/package.json +2 -2
- package/dist/helpers/http.d.ts +0 -95
- 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
|
|
8
|
+
* Default in-memory implementation of {@link RateLimitStore}.
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
*
|
|
18
|
-
*
|
|
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
|
|
21
|
-
* @returns
|
|
70
|
+
* @param value - Raw address candidate.
|
|
71
|
+
* @returns Normalized IP, or `null` when the input is not a valid IP.
|
|
22
72
|
*/
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
if (
|
|
28
|
-
return
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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 ??
|
|
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
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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 (
|
|
72
|
-
const cookies = (0, cookies_1.parseCookies)(
|
|
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
|
-
|
|
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(
|
|
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;
|
package/dist/routing/router.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Middleware, RouteHandler } from "../types";
|
|
2
|
-
import {
|
|
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(
|
|
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:
|
|
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 `(
|
|
47
|
+
* Each middleware receives `(context, next)` and can run code
|
|
47
48
|
* before/after calling `next()`.
|
|
48
49
|
*
|
|
49
|
-
* @param
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
103
|
+
delete(path: string, action: RouteHandler): Layer;
|
|
104
|
+
delete(path: string, middlewares: Middleware[], action: RouteHandler): Layer;
|
|
99
105
|
}
|
package/dist/routing/router.js
CHANGED
|
@@ -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(
|
|
38
|
-
const routes = this.routes[
|
|
38
|
+
resolveLayer(context) {
|
|
39
|
+
const routes = this.routes[context.req.method];
|
|
39
40
|
for (const route of routes) {
|
|
40
|
-
if (route.matches(
|
|
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
|
|
59
|
-
|
|
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(
|
|
68
|
+
return this.runMiddlewares(ctx, routeMiddlewares, action);
|
|
67
69
|
}
|
|
68
|
-
return action(
|
|
70
|
+
return action(ctx);
|
|
69
71
|
};
|
|
70
72
|
if (this.globalMiddlewares.length > 0) {
|
|
71
|
-
return this.runMiddlewares(
|
|
73
|
+
return this.runMiddlewares(context, this.globalMiddlewares, executeLayer);
|
|
72
74
|
}
|
|
73
|
-
return executeLayer(
|
|
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 `(
|
|
85
|
+
* Each middleware receives `(context, next)` and can run code
|
|
79
86
|
* before/after calling `next()`.
|
|
80
87
|
*
|
|
81
|
-
* @param
|
|
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(
|
|
93
|
+
runMiddlewares(context, middlewares, target) {
|
|
87
94
|
if (middlewares.length === 0)
|
|
88
|
-
return target(
|
|
89
|
-
return middlewares[0](
|
|
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
|
-
|
|
142
|
-
|
|
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
|
-
|
|
146
|
-
|
|
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
|
-
|
|
150
|
-
|
|
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
|
-
|
|
154
|
-
|
|
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
|
-
|
|
158
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
68
|
-
|
|
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
|
-
|
|
72
|
-
|
|
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
|
-
|
|
76
|
-
|
|
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
|
-
|
|
80
|
-
|
|
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
|
-
|
|
84
|
-
|
|
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(
|
|
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(
|
|
102
|
+
handleFile(context: Context, file: Partial<UploadedFile>, fileData: Buffer): UploadedFile;
|
|
103
103
|
removeFile(file: UploadedFile): void;
|
|
104
104
|
}
|
package/dist/storage/storage.js
CHANGED
|
@@ -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(
|
|
63
|
+
async handleFile(context, file, fileData) {
|
|
64
64
|
try {
|
|
65
|
-
const destination = await this.resolveDestination(
|
|
66
|
-
const filename = await this.filenameGenerator(
|
|
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(
|
|
105
|
+
async resolveDestination(context, file) {
|
|
106
106
|
if (typeof this.destination === "function") {
|
|
107
|
-
return await this.destination(
|
|
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(
|
|
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(
|
|
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);
|
package/dist/storage/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { UploadedFile } from "../parsers/parserInterface";
|
|
2
|
-
import {
|
|
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 | ((
|
|
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?: (
|
|
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(
|
|
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 = (
|
|
102
|
+
export type FileFilter = (context: Context, file: Partial<UploadedFile>, callback: FileFilterCallback) => void | Promise<void>;
|
|
103
103
|
/**
|
|
104
104
|
* Global uploader configuration.
|
|
105
105
|
*/
|