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
@@ -11,6 +11,7 @@ const promises_1 = require("node:fs/promises");
11
11
  const node_path_1 = require("node:path");
12
12
  const mimeTypes_1 = require("../static/mimeTypes");
13
13
  const httpExceptions_1 = require("../exceptions/httpExceptions");
14
+ const node_stream_1 = require("node:stream");
14
15
  /**
15
16
  * Represents an outgoing response sent to the client.
16
17
  *
@@ -115,6 +116,18 @@ class Response {
115
116
  this._content = content;
116
117
  return this;
117
118
  }
119
+ /**
120
+ * Sets a readable stream as the response body.
121
+ *
122
+ * Use this when you need to send large payloads in chunks without
123
+ * buffering the full content in memory first.
124
+ *
125
+ * @param stream - Node.js readable stream
126
+ * @returns The current {@link Response} instance
127
+ */
128
+ stream(stream) {
129
+ return this.setContent(stream);
130
+ }
118
131
  /**
119
132
  * Prepares the response before being sent to the client.
120
133
  *
@@ -131,14 +144,16 @@ class Response {
131
144
  */
132
145
  prepare() {
133
146
  if (!this._headers["content-type"] && this._content) {
134
- if (Buffer.isBuffer(this._content)) {
147
+ if (Buffer.isBuffer(this._content) || this._content instanceof node_stream_1.Readable) {
135
148
  this._headers["content-type"] = "application/octet-stream";
136
149
  }
137
150
  else {
138
151
  this._headers["content-type"] = "text/plain";
139
152
  }
140
153
  }
141
- if (this._content && !this._headers["content-length"]) {
154
+ if (this._content &&
155
+ !(this._content instanceof node_stream_1.Readable) &&
156
+ !this._headers["content-length"]) {
142
157
  const length = Buffer.isBuffer(this._content)
143
158
  ? this._content.length
144
159
  : Buffer.byteLength(this._content, "utf-8");
@@ -175,6 +190,19 @@ class Response {
175
190
  static text(data) {
176
191
  return new this().setContentType("text/plain").setContent(data);
177
192
  }
193
+ /**
194
+ * Creates a response whose body is streamed.
195
+ *
196
+ * @param stream - Node.js readable stream
197
+ * @param headers - Optional headers to include in the response
198
+ * @returns A {@link Response} configured for streaming
199
+ */
200
+ static stream(stream, headers) {
201
+ const response = new this().stream(stream);
202
+ if (headers)
203
+ response.setHeaders(headers);
204
+ return response;
205
+ }
178
206
  /**
179
207
  * Creates an HTTP redirect response.
180
208
  *
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { createApp } from "./app";
2
- export { Request, Response } from "./http";
2
+ export { Request, Response, Context } from "./http";
3
3
  export type { Middleware, RouteHandler } from "./types";
4
4
  export { RouterGroup } from "./routing";
5
5
  export { FileSessionStorage, MemorySessionStorage, DatabaseSessionStorage, type SessionDatabaseAdapter, } from "./sessions";
@@ -9,5 +9,5 @@ export { StorageType } from "./storage/types";
9
9
  export { v, schema, validateRequest } from "./validators/validationSchema";
10
10
  export { Hasher, JWT } from "./crypto";
11
11
  export { sessions, cors, csrf, rateLimit } from "./middlewares";
12
+ export type { RateLimitStore, RateLimitStoreEntry } from "./middlewares";
12
13
  export * from "./exceptions/httpExceptions";
13
- export * from "./helpers/http";
package/dist/index.js CHANGED
@@ -14,12 +14,13 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.rateLimit = exports.csrf = exports.cors = exports.sessions = exports.JWT = exports.Hasher = exports.validateRequest = exports.schema = exports.v = exports.StorageType = exports.createUploader = exports.HttpMethods = exports.DatabaseSessionStorage = exports.MemorySessionStorage = exports.FileSessionStorage = exports.RouterGroup = exports.Response = exports.Request = exports.createApp = void 0;
17
+ exports.rateLimit = exports.csrf = exports.cors = exports.sessions = exports.JWT = exports.Hasher = exports.validateRequest = exports.schema = exports.v = exports.StorageType = exports.createUploader = exports.HttpMethods = exports.DatabaseSessionStorage = exports.MemorySessionStorage = exports.FileSessionStorage = exports.RouterGroup = exports.Context = exports.Response = exports.Request = exports.createApp = void 0;
18
18
  var app_1 = require("./app");
19
19
  Object.defineProperty(exports, "createApp", { enumerable: true, get: function () { return app_1.createApp; } });
20
20
  var http_1 = require("./http");
21
21
  Object.defineProperty(exports, "Request", { enumerable: true, get: function () { return http_1.Request; } });
22
22
  Object.defineProperty(exports, "Response", { enumerable: true, get: function () { return http_1.Response; } });
23
+ Object.defineProperty(exports, "Context", { enumerable: true, get: function () { return http_1.Context; } });
23
24
  var routing_1 = require("./routing");
24
25
  Object.defineProperty(exports, "RouterGroup", { enumerable: true, get: function () { return routing_1.RouterGroup; } });
25
26
  var sessions_1 = require("./sessions");
@@ -45,4 +46,3 @@ Object.defineProperty(exports, "cors", { enumerable: true, get: function () { re
45
46
  Object.defineProperty(exports, "csrf", { enumerable: true, get: function () { return middlewares_1.csrf; } });
46
47
  Object.defineProperty(exports, "rateLimit", { enumerable: true, get: function () { return middlewares_1.rateLimit; } });
47
48
  __exportStar(require("./exceptions/httpExceptions"), exports);
48
- __exportStar(require("./helpers/http"), exports);
@@ -1,22 +1,28 @@
1
- import { HttpMethods } from "../http";
1
+ import { Context, HttpMethods } from "../http";
2
2
  import type { Middleware } from "../types";
3
+ /**
4
+ * Callback contract for dynamic CORS allow/deny decisions.
5
+ *
6
+ * Return `true` to allow the current request origin, `false` to deny it.
7
+ */
8
+ type CorsOriginResolver = (origin: string | undefined, context: Context) => boolean | Promise<boolean>;
3
9
  /**
4
10
  * CORS middleware configuration options.
5
11
  *
6
12
  * These options map to standard CORS response headers and preflight behavior.
7
13
  */
8
- interface CorsOptions {
14
+ export interface CorsOptions {
9
15
  /**
10
16
  * Allowed origin(s) for cross-origin requests.
11
17
  *
12
18
  * - `"*"` allows any origin (public).
13
19
  * - A specific origin (string) restricts access to that origin.
14
20
  * - An array provides a whitelist.
15
- * - A function can implement custom allow/deny logic.
21
+ * - A function implements custom allow/deny logic.
16
22
  *
17
23
  * Affects: `Access-Control-Allow-Origin`.
18
24
  */
19
- origin?: string | string[] | ((origin: string | undefined) => string);
25
+ origin?: string | string[] | CorsOriginResolver;
20
26
  /**
21
27
  * HTTP methods allowed for cross-origin requests.
22
28
  *
@@ -2,33 +2,50 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.cors = void 0;
4
4
  const http_1 = require("../http");
5
+ /**
6
+ * Normalizes a header that may be represented as `string[]`.
7
+ *
8
+ * @param value - Header value.
9
+ * @returns A single header value or `undefined`.
10
+ */
11
+ const getHeaderValue = (value) => Array.isArray(value) ? value[0] : value;
5
12
  /**
6
13
  * Resolves the effective `Access-Control-Allow-Origin` value for a request.
7
14
  *
8
- * @param requestOrigin - The `Origin` header value from the incoming request.
9
- * @returns The origin to allow:
10
- * - `"*"` for public access when credentials are disabled
11
- * - the request origin when it is allowed (whitelist or custom resolver)
12
- * - `null` if the origin is not allowed (no CORS headers will be applied)
15
+ * @param context - Current HTTP context.
16
+ * @param requestOrigin - Incoming `Origin` header value.
17
+ * @param config - CORS config.
18
+ * @returns Allowed origin value or `null` when denied/not configured.
13
19
  */
14
- function resolveOrigin(requestOrigin, config) {
20
+ async function resolveOrigin(context, requestOrigin, config) {
15
21
  if (!requestOrigin)
16
22
  return null;
17
23
  const cleanOrigin = (origin) => origin.endsWith("/") ? origin.slice(0, -1) : origin;
18
24
  if (typeof config.origin === "function") {
19
- return config.origin(requestOrigin) ? requestOrigin : null;
25
+ const result = config.origin(requestOrigin, context);
26
+ const isAllowed = result instanceof Promise ? await result : result;
27
+ return isAllowed ? requestOrigin : null;
28
+ }
29
+ if (!config.origin) {
30
+ return null;
31
+ }
32
+ if (typeof config.origin !== "string" && !Array.isArray(config.origin)) {
33
+ return null;
34
+ }
35
+ if (typeof config.origin === "string") {
36
+ if (config.origin === "*") {
37
+ return config.credentials ? requestOrigin : "*";
38
+ }
39
+ return cleanOrigin(config.origin) === cleanOrigin(requestOrigin)
40
+ ? requestOrigin
41
+ : null;
20
42
  }
21
43
  if (Array.isArray(config.origin)) {
22
44
  return config.origin.map(cleanOrigin).includes(cleanOrigin(requestOrigin))
23
45
  ? requestOrigin
24
46
  : null;
25
47
  }
26
- if (config.origin === "*") {
27
- return config.credentials ? requestOrigin : "*";
28
- }
29
- return cleanOrigin(config.origin) === cleanOrigin(requestOrigin)
30
- ? requestOrigin
31
- : null;
48
+ return null;
32
49
  }
33
50
  /**
34
51
  * Native CORS middleware, generates a CORS configuration for the server.
@@ -50,7 +67,7 @@ function resolveOrigin(requestOrigin, config) {
50
67
  */
51
68
  const cors = (options = {}) => {
52
69
  const config = {
53
- origin: options.origin ?? "*",
70
+ origin: options.origin,
54
71
  methods: options.methods ?? [
55
72
  http_1.HttpMethods.get,
56
73
  http_1.HttpMethods.post,
@@ -65,8 +82,8 @@ const cors = (options = {}) => {
65
82
  maxAge: options.maxAge ?? 86400,
66
83
  preflightContinue: options.preflightContinue ?? false,
67
84
  };
68
- return async (request, next) => {
69
- const allowedOrigin = resolveOrigin(request.headers.origin, config);
85
+ return async (context, next) => {
86
+ const allowedOrigin = await resolveOrigin(context, getHeaderValue(context.headers.origin), config);
70
87
  const corsHeaders = {};
71
88
  if (allowedOrigin) {
72
89
  corsHeaders["Access-Control-Allow-Origin"] = allowedOrigin;
@@ -78,7 +95,7 @@ const cors = (options = {}) => {
78
95
  if (config.exposedHeaders.length)
79
96
  corsHeaders["Access-Control-Expose-Headers"] =
80
97
  config.exposedHeaders.join(", ");
81
- if (request.method === http_1.HttpMethods.options) {
98
+ if (context.req.method === http_1.HttpMethods.options) {
82
99
  corsHeaders["Access-Control-Allow-Methods"] = config.methods.join(", ");
83
100
  corsHeaders["Access-Control-Allow-Headers"] =
84
101
  config.allowedHeaders.join(", ");
@@ -89,7 +106,7 @@ const cors = (options = {}) => {
89
106
  .setContent(null)
90
107
  .setHeaders(corsHeaders);
91
108
  }
92
- const response = await next(request);
109
+ const response = await next(context);
93
110
  for (const [key, value] of Object.entries(corsHeaders)) {
94
111
  response.setHeader(key, value);
95
112
  }
@@ -16,7 +16,7 @@ const defaultTokenGenerator = () => (0, node_crypto_1.randomBytes)(32).toString(
16
16
  * Normalizes an origin-like string by removing trailing slashes.
17
17
  *
18
18
  * This helps make origin comparisons stable across minor formatting differences
19
- * (e.g. `https://example.com/` vs `https://example.com`).
19
+ * (e.g. `scheme://host/` vs `scheme://host`).
20
20
  *
21
21
  * @param value - Origin string to normalize.
22
22
  * @returns Normalized origin string without trailing slashes.
@@ -61,8 +61,8 @@ const buildConfig = (options) => ({
61
61
  * @param headerName - Header key to read (as stored in `request.headers`).
62
62
  * @returns The header value if exactly one is present, otherwise `null`.
63
63
  */
64
- function getSingleHeaderValue(request, headerName) {
65
- const value = request.headers[headerName];
64
+ function getSingleHeaderValue(context, headerName) {
65
+ const value = context.headers[headerName];
66
66
  if (!value)
67
67
  return null;
68
68
  if (Array.isArray(value)) {
@@ -86,8 +86,8 @@ function getSingleHeaderValue(request, headerName) {
86
86
  * @param headerName - Header key to inspect.
87
87
  * @returns `true` if duplicates/invalid array form exist, otherwise `false`.
88
88
  */
89
- const hasInvalidDuplicateHeader = (request, headerName) => Array.isArray(request.headers[headerName]) &&
90
- request.headers[headerName].length !== 1;
89
+ const hasInvalidDuplicateHeader = (context, headerName) => Array.isArray(context.headers[headerName]) &&
90
+ context.headers[headerName].length !== 1;
91
91
  /**
92
92
  * Extracts a CSRF token from the first matching header in `headerNames`.
93
93
  *
@@ -98,9 +98,9 @@ const hasInvalidDuplicateHeader = (request, headerName) => Array.isArray(request
98
98
  * @param headerNames - List of candidate header names to check.
99
99
  * @returns The token value if found, otherwise `null`.
100
100
  */
101
- function getHeaderToken(request, headerNames) {
101
+ function getHeaderToken(context, headerNames) {
102
102
  for (const headerName of headerNames) {
103
- const value = getSingleHeaderValue(request, headerName);
103
+ const value = getSingleHeaderValue(context, headerName);
104
104
  if (value)
105
105
  return value;
106
106
  }
@@ -135,11 +135,11 @@ function safeCompare(a, b) {
135
135
  * @param request - Incoming request.
136
136
  * @returns `true` if HTTPS is inferred, otherwise `false`.
137
137
  */
138
- function isHttpsRequest(request) {
139
- const forwardedProto = getSingleHeaderValue(request, "x-forwarded-proto");
138
+ function isHttpsRequest(context) {
139
+ const forwardedProto = getSingleHeaderValue(context, "x-forwarded-proto");
140
140
  if (forwardedProto?.split(",")[0]?.trim().toLowerCase() === "https")
141
141
  return true;
142
- const forwarded = getSingleHeaderValue(request, "forwarded");
142
+ const forwarded = getSingleHeaderValue(context, "forwarded");
143
143
  if (forwarded?.toLowerCase().includes("proto=https"))
144
144
  return true;
145
145
  return false;
@@ -152,10 +152,10 @@ function isHttpsRequest(request) {
152
152
  *
153
153
  * @param request - Incoming request.
154
154
  * @param isHttps - Whether the request is considered HTTPS.
155
- * @returns An origin string like `https://example.com`, or `null` if `Host` is missing.
155
+ * @returns An origin string like `scheme://host`, or `null` if `Host` is missing.
156
156
  */
157
- function inferRequestOrigin(request, isHttps) {
158
- const host = getSingleHeaderValue(request, "host");
157
+ function inferRequestOrigin(context, isHttps) {
158
+ const host = getSingleHeaderValue(context, "host");
159
159
  if (!host)
160
160
  return null;
161
161
  return `${isHttps ? "https" : "http"}://${host}`;
@@ -164,7 +164,7 @@ function inferRequestOrigin(request, isHttps) {
164
164
  * Extracts the origin (scheme + host + port) from a Referer header value.
165
165
  *
166
166
  * @param referer - The raw Referer header.
167
- * @returns The parsed origin (e.g., `https://example.com`) or `null` if invalid.
167
+ * @returns The parsed origin (e.g., `scheme://host`) or `null` if invalid.
168
168
  */
169
169
  function extractOriginFromReferer(referer) {
170
170
  try {
@@ -185,12 +185,12 @@ function extractOriginFromReferer(referer) {
185
185
  * @param headerNames - Token header names to validate for duplication.
186
186
  * @returns An error message string, or `null` if headers look safe.
187
187
  */
188
- function validateDuplicateSecurityHeaders(request, headerNames) {
189
- if (headerNames.some(headerName => hasInvalidDuplicateHeader(request, headerName))) {
188
+ function validateDuplicateSecurityHeaders(context, headerNames) {
189
+ if (headerNames.some(headerName => hasInvalidDuplicateHeader(context, headerName))) {
190
190
  return "Invalid CSRF token header format";
191
191
  }
192
- if (hasInvalidDuplicateHeader(request, "origin") ||
193
- hasInvalidDuplicateHeader(request, "referer")) {
192
+ if (hasInvalidDuplicateHeader(context, "origin") ||
193
+ hasInvalidDuplicateHeader(context, "referer")) {
194
194
  return "Invalid Origin/Referer headers";
195
195
  }
196
196
  return null;
@@ -202,15 +202,15 @@ function validateDuplicateSecurityHeaders(request, headerNames) {
202
202
  * @param config - Resolved CSRF configuration.
203
203
  * @returns An error message string, or `null` if origin is acceptable.
204
204
  */
205
- function validateOriginHeaders(request, config) {
205
+ function validateOriginHeaders(context, config) {
206
206
  if (!config.validateOrigin)
207
207
  return null;
208
- const httpsRequest = isHttpsRequest(request);
209
- const inferredOrigin = inferRequestOrigin(request, httpsRequest);
208
+ const httpsRequest = isHttpsRequest(context);
209
+ const inferredOrigin = inferRequestOrigin(context, httpsRequest);
210
210
  const allowedOrigins = config.allowedOrigins ??
211
211
  (inferredOrigin ? [normalizeOrigin(inferredOrigin)] : []);
212
- const originHeader = getSingleHeaderValue(request, "origin");
213
- const refererHeader = getSingleHeaderValue(request, "referer");
212
+ const originHeader = getSingleHeaderValue(context, "origin");
213
+ const refererHeader = getSingleHeaderValue(context, "referer");
214
214
  if (!originHeader &&
215
215
  httpsRequest &&
216
216
  config.requireRefererOnHttps &&
@@ -245,9 +245,9 @@ function validateOriginHeaders(request, config) {
245
245
  * @param expectedToken - The server-issued token (typically from cookie or generator).
246
246
  * @returns An error message string, or `null` if the token is valid.
247
247
  */
248
- function validateCsrfToken(request, config, expectedToken) {
249
- const tokenFromHeader = getHeaderToken(request, config.headerNames);
250
- const tokenFromBody = request.body[config.bodyField];
248
+ function validateCsrfToken(context, config, expectedToken) {
249
+ const tokenFromHeader = getHeaderToken(context, config.headerNames);
250
+ const tokenFromBody = context.body[config.bodyField];
251
251
  const providedToken = tokenFromHeader ?? tokenFromBody;
252
252
  if (typeof providedToken !== "string" ||
253
253
  !safeCompare(expectedToken, providedToken)) {
@@ -290,9 +290,9 @@ const buildForbiddenResponse = (message) => {
290
290
  */
291
291
  const csrf = (options = {}) => {
292
292
  const config = buildConfig(options);
293
- return async (request, next) => {
294
- const methodRequiresCheck = config.protectedMethods.includes(request.method);
295
- const tokenFromCookie = request.cookies[config.cookieName];
293
+ return async (context, next) => {
294
+ const methodRequiresCheck = config.protectedMethods.includes(context.req.method);
295
+ const tokenFromCookie = context.cookies[config.cookieName];
296
296
  const issuedToken = tokenFromCookie || config.tokenGenerator();
297
297
  const appendCsrfCookie = (response) => {
298
298
  if (!tokenFromCookie) {
@@ -301,14 +301,14 @@ const csrf = (options = {}) => {
301
301
  return response;
302
302
  };
303
303
  if (methodRequiresCheck) {
304
- const validationError = validateDuplicateSecurityHeaders(request, config.headerNames) ??
305
- validateOriginHeaders(request, config) ??
306
- validateCsrfToken(request, config, issuedToken);
304
+ const validationError = validateDuplicateSecurityHeaders(context, config.headerNames) ??
305
+ validateOriginHeaders(context, config) ??
306
+ validateCsrfToken(context, config, issuedToken);
307
307
  if (validationError) {
308
308
  return appendCsrfCookie(buildForbiddenResponse(validationError));
309
309
  }
310
310
  }
311
- const response = await next(request);
311
+ const response = await next(context);
312
312
  return appendCsrfCookie(response);
313
313
  };
314
314
  };
@@ -1,4 +1,4 @@
1
1
  export { cors } from "./cors";
2
2
  export { sessions } from "./session";
3
3
  export { csrf } from "./csrf";
4
- export { rateLimit } from "./rateLimiter";
4
+ export { rateLimit, type RateLimitStore, type RateLimitStoreEntry, } from "./rateLimiter";
@@ -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.