ratelimit-flex 1.0.0

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 (109) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE +21 -0
  3. package/README.md +274 -0
  4. package/dist/cjs/index.d.ts +32 -0
  5. package/dist/cjs/index.d.ts.map +1 -0
  6. package/dist/cjs/index.js +73 -0
  7. package/dist/cjs/index.js.map +1 -0
  8. package/dist/cjs/middleware/express.d.ts +18 -0
  9. package/dist/cjs/middleware/express.d.ts.map +1 -0
  10. package/dist/cjs/middleware/express.js +61 -0
  11. package/dist/cjs/middleware/express.js.map +1 -0
  12. package/dist/cjs/middleware/fastify.d.ts +21 -0
  13. package/dist/cjs/middleware/fastify.d.ts.map +1 -0
  14. package/dist/cjs/middleware/fastify.js +66 -0
  15. package/dist/cjs/middleware/fastify.js.map +1 -0
  16. package/dist/cjs/middleware/index.d.ts +4 -0
  17. package/dist/cjs/middleware/index.d.ts.map +1 -0
  18. package/dist/cjs/middleware/index.js +9 -0
  19. package/dist/cjs/middleware/index.js.map +1 -0
  20. package/dist/cjs/middleware/merge-options.d.ts +16 -0
  21. package/dist/cjs/middleware/merge-options.d.ts.map +1 -0
  22. package/dist/cjs/middleware/merge-options.js +71 -0
  23. package/dist/cjs/middleware/merge-options.js.map +1 -0
  24. package/dist/cjs/package.json +1 -0
  25. package/dist/cjs/stores/index.d.ts +4 -0
  26. package/dist/cjs/stores/index.d.ts.map +1 -0
  27. package/dist/cjs/stores/index.js +11 -0
  28. package/dist/cjs/stores/index.js.map +1 -0
  29. package/dist/cjs/stores/memory-store.d.ts +74 -0
  30. package/dist/cjs/stores/memory-store.d.ts.map +1 -0
  31. package/dist/cjs/stores/memory-store.js +259 -0
  32. package/dist/cjs/stores/memory-store.js.map +1 -0
  33. package/dist/cjs/stores/redis-store.d.ts +113 -0
  34. package/dist/cjs/stores/redis-store.d.ts.map +1 -0
  35. package/dist/cjs/stores/redis-store.js +452 -0
  36. package/dist/cjs/stores/redis-store.js.map +1 -0
  37. package/dist/cjs/strategies/defaults.d.ts +28 -0
  38. package/dist/cjs/strategies/defaults.d.ts.map +1 -0
  39. package/dist/cjs/strategies/defaults.js +31 -0
  40. package/dist/cjs/strategies/defaults.js.map +1 -0
  41. package/dist/cjs/strategies/index.d.ts +4 -0
  42. package/dist/cjs/strategies/index.d.ts.map +1 -0
  43. package/dist/cjs/strategies/index.js +13 -0
  44. package/dist/cjs/strategies/index.js.map +1 -0
  45. package/dist/cjs/strategies/rate-limit-engine.d.ts +42 -0
  46. package/dist/cjs/strategies/rate-limit-engine.d.ts.map +1 -0
  47. package/dist/cjs/strategies/rate-limit-engine.js +128 -0
  48. package/dist/cjs/strategies/rate-limit-engine.js.map +1 -0
  49. package/dist/cjs/types/index.d.ts +93 -0
  50. package/dist/cjs/types/index.d.ts.map +1 -0
  51. package/dist/cjs/types/index.js +14 -0
  52. package/dist/cjs/types/index.js.map +1 -0
  53. package/dist/cjs/utils/index.d.ts +3 -0
  54. package/dist/cjs/utils/index.d.ts.map +1 -0
  55. package/dist/cjs/utils/index.js +4 -0
  56. package/dist/cjs/utils/index.js.map +1 -0
  57. package/dist/index.d.ts +32 -0
  58. package/dist/index.d.ts.map +1 -0
  59. package/dist/index.js +43 -0
  60. package/dist/index.js.map +1 -0
  61. package/dist/middleware/express.d.ts +18 -0
  62. package/dist/middleware/express.d.ts.map +1 -0
  63. package/dist/middleware/express.js +58 -0
  64. package/dist/middleware/express.js.map +1 -0
  65. package/dist/middleware/fastify.d.ts +21 -0
  66. package/dist/middleware/fastify.d.ts.map +1 -0
  67. package/dist/middleware/fastify.js +60 -0
  68. package/dist/middleware/fastify.js.map +1 -0
  69. package/dist/middleware/index.d.ts +4 -0
  70. package/dist/middleware/index.d.ts.map +1 -0
  71. package/dist/middleware/index.js +4 -0
  72. package/dist/middleware/index.js.map +1 -0
  73. package/dist/middleware/merge-options.d.ts +16 -0
  74. package/dist/middleware/merge-options.d.ts.map +1 -0
  75. package/dist/middleware/merge-options.js +64 -0
  76. package/dist/middleware/merge-options.js.map +1 -0
  77. package/dist/stores/index.d.ts +4 -0
  78. package/dist/stores/index.d.ts.map +1 -0
  79. package/dist/stores/index.js +4 -0
  80. package/dist/stores/index.js.map +1 -0
  81. package/dist/stores/memory-store.d.ts +74 -0
  82. package/dist/stores/memory-store.d.ts.map +1 -0
  83. package/dist/stores/memory-store.js +255 -0
  84. package/dist/stores/memory-store.js.map +1 -0
  85. package/dist/stores/redis-store.d.ts +113 -0
  86. package/dist/stores/redis-store.d.ts.map +1 -0
  87. package/dist/stores/redis-store.js +413 -0
  88. package/dist/stores/redis-store.js.map +1 -0
  89. package/dist/strategies/defaults.d.ts +28 -0
  90. package/dist/strategies/defaults.d.ts.map +1 -0
  91. package/dist/strategies/defaults.js +28 -0
  92. package/dist/strategies/defaults.js.map +1 -0
  93. package/dist/strategies/index.d.ts +4 -0
  94. package/dist/strategies/index.d.ts.map +1 -0
  95. package/dist/strategies/index.js +4 -0
  96. package/dist/strategies/index.js.map +1 -0
  97. package/dist/strategies/rate-limit-engine.d.ts +42 -0
  98. package/dist/strategies/rate-limit-engine.d.ts.map +1 -0
  99. package/dist/strategies/rate-limit-engine.js +122 -0
  100. package/dist/strategies/rate-limit-engine.js.map +1 -0
  101. package/dist/types/index.d.ts +93 -0
  102. package/dist/types/index.d.ts.map +1 -0
  103. package/dist/types/index.js +11 -0
  104. package/dist/types/index.js.map +1 -0
  105. package/dist/utils/index.d.ts +3 -0
  106. package/dist/utils/index.d.ts.map +1 -0
  107. package/dist/utils/index.js +3 -0
  108. package/dist/utils/index.js.map +1 -0
  109. package/package.json +90 -0
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RateLimitEngine = void 0;
4
+ exports.defaultKeyGenerator = defaultKeyGenerator;
5
+ exports.createRateLimiter = createRateLimiter;
6
+ const memory_store_js_1 = require("../stores/memory-store.js");
7
+ const index_js_1 = require("../types/index.js");
8
+ /**
9
+ * Default key extractor: uses `req.ip`, then `socket.remoteAddress`, else `"unknown"`.
10
+ * Strings are returned as-is so you can pass a precomputed key.
11
+ */
12
+ function defaultKeyGenerator(req) {
13
+ if (typeof req === 'string') {
14
+ return req;
15
+ }
16
+ if (req !== null && typeof req === 'object') {
17
+ const r = req;
18
+ if (typeof r.ip === 'string' && r.ip.length > 0) {
19
+ return r.ip;
20
+ }
21
+ const socket = r.socket;
22
+ if (socket !== null && typeof socket === 'object' && 'remoteAddress' in socket) {
23
+ const addr = socket.remoteAddress;
24
+ if (typeof addr === 'string' && addr.length > 0) {
25
+ return addr;
26
+ }
27
+ }
28
+ }
29
+ return 'unknown';
30
+ }
31
+ function createDefaultMemoryStore(options) {
32
+ if (options.strategy === index_js_1.RateLimitStrategy.TOKEN_BUCKET) {
33
+ return new memory_store_js_1.MemoryStore({
34
+ strategy: index_js_1.RateLimitStrategy.TOKEN_BUCKET,
35
+ tokensPerInterval: options.tokensPerInterval,
36
+ interval: options.interval,
37
+ bucketSize: options.bucketSize,
38
+ });
39
+ }
40
+ return new memory_store_js_1.MemoryStore({
41
+ strategy: options.strategy ?? index_js_1.RateLimitStrategy.SLIDING_WINDOW,
42
+ windowMs: options.windowMs ?? 60000,
43
+ maxRequests: options.maxRequests ?? 100,
44
+ });
45
+ }
46
+ function resolveOptions(input) {
47
+ const store = input.store ?? createDefaultMemoryStore(input);
48
+ return { ...input, store };
49
+ }
50
+ /**
51
+ * Build a {@link RateLimitEngine} with optional in-memory store when `store` is omitted.
52
+ */
53
+ function createRateLimiter(options) {
54
+ return new RateLimitEngine(resolveOptions(options));
55
+ }
56
+ /**
57
+ * Orchestrates key extraction, store increments, header generation, and limit callbacks.
58
+ */
59
+ class RateLimitEngine {
60
+ constructor(options) {
61
+ this.options = options;
62
+ }
63
+ /**
64
+ * Applies rate limiting for an incoming request-like value.
65
+ * Uses {@link RateLimitOptionsBase.keyGenerator} (or {@link defaultKeyGenerator}) to derive the storage key.
66
+ */
67
+ async consume(req) {
68
+ const key = (this.options.keyGenerator ?? defaultKeyGenerator)(req);
69
+ return this.consumeWithKey(key, req);
70
+ }
71
+ /**
72
+ * Rate limit using a precomputed storage key (skips `keyGenerator`).
73
+ * Pass the same `req` for `onLimitReached` / `skip` callbacks when applicable.
74
+ */
75
+ async consumeWithKey(key, req = key) {
76
+ if (this.options.skip?.(req) === true) {
77
+ return this.buildPassthroughResult();
78
+ }
79
+ const result = await this.options.store.increment(key);
80
+ const headers = this.buildHeaders(result);
81
+ if (result.isBlocked && this.options.onLimitReached) {
82
+ await Promise.resolve(this.options.onLimitReached(req, result));
83
+ }
84
+ return { ...result, headers };
85
+ }
86
+ getLimit() {
87
+ if (this.options.strategy === index_js_1.RateLimitStrategy.TOKEN_BUCKET) {
88
+ return this.options.bucketSize;
89
+ }
90
+ return this.options.maxRequests ?? 100;
91
+ }
92
+ buildPassthroughResult() {
93
+ const limit = this.getLimit();
94
+ const resetTime = new Date(Date.now() + 60000);
95
+ const base = {
96
+ totalHits: 0,
97
+ remaining: limit,
98
+ resetTime,
99
+ isBlocked: false,
100
+ };
101
+ return {
102
+ ...base,
103
+ headers: this.composeHeaders(base),
104
+ };
105
+ }
106
+ buildHeaders(result) {
107
+ return this.composeHeaders(result);
108
+ }
109
+ composeHeaders(result) {
110
+ if (this.options.headers === false) {
111
+ return {};
112
+ }
113
+ const limit = this.getLimit();
114
+ const resetSec = Math.ceil(result.resetTime.getTime() / 1000);
115
+ const headers = {
116
+ 'X-RateLimit-Limit': String(limit),
117
+ 'X-RateLimit-Remaining': String(Math.max(0, result.remaining)),
118
+ 'X-RateLimit-Reset': String(resetSec),
119
+ };
120
+ if (result.isBlocked) {
121
+ const retryAfterSec = Math.max(1, Math.ceil((result.resetTime.getTime() - Date.now()) / 1000));
122
+ headers['Retry-After'] = String(retryAfterSec);
123
+ }
124
+ return headers;
125
+ }
126
+ }
127
+ exports.RateLimitEngine = RateLimitEngine;
128
+ //# sourceMappingURL=rate-limit-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit-engine.js","sourceRoot":"","sources":["../../../src/strategies/rate-limit-engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,93 @@
1
+ /** Shared types for rate limiting */
2
+ /**
3
+ * Built-in rate limiting algorithms.
4
+ */
5
+ export declare enum RateLimitStrategy {
6
+ SLIDING_WINDOW = "SLIDING_WINDOW",
7
+ TOKEN_BUCKET = "TOKEN_BUCKET",
8
+ FIXED_WINDOW = "FIXED_WINDOW"
9
+ }
10
+ /**
11
+ * Result of incrementing a counter in the backing store.
12
+ */
13
+ export interface RateLimitResult {
14
+ totalHits: number;
15
+ remaining: number;
16
+ resetTime: Date;
17
+ isBlocked: boolean;
18
+ }
19
+ /**
20
+ * Pluggable persistence for rate limit state.
21
+ */
22
+ export interface RateLimitStore {
23
+ increment(key: string): Promise<RateLimitResult>;
24
+ decrement(key: string): Promise<void>;
25
+ reset(key: string): Promise<void>;
26
+ shutdown(): Promise<void>;
27
+ }
28
+ /**
29
+ * Per-request rate limit snapshot (e.g. for headers or `req` augmentation).
30
+ */
31
+ export interface RateLimitInfo {
32
+ limit: number;
33
+ current: number;
34
+ remaining: number;
35
+ resetTime: Date;
36
+ }
37
+ /**
38
+ * Options shared by all strategies.
39
+ */
40
+ export interface RateLimitOptionsBase {
41
+ /**
42
+ * Function to extract a stable identifier for the client (e.g. IP).
43
+ * Default in implementations is typically `req.ip` when present.
44
+ */
45
+ keyGenerator?: (req: unknown) => string;
46
+ /** Invoked when the limit is exceeded. */
47
+ onLimitReached?: (req: unknown, result: RateLimitResult) => void | Promise<void>;
48
+ /** When true, failed responses do not count toward the limit. @default false */
49
+ skipFailedRequests?: boolean;
50
+ /** When true, successful responses do not count toward the limit. @default false */
51
+ skipSuccessfulRequests?: boolean;
52
+ /** When true, send `X-RateLimit-*` headers. @default true */
53
+ headers?: boolean;
54
+ /** HTTP status when blocked. @default 429 */
55
+ statusCode?: number;
56
+ /** Body or payload when blocked. @default "Too many requests" */
57
+ message?: string | object;
58
+ /** When true, rate limiting is skipped for this request. */
59
+ skip?: (req: unknown) => boolean;
60
+ /** Backing store for counters / bucket state. */
61
+ store: RateLimitStore;
62
+ }
63
+ /**
64
+ * Options for sliding-window and fixed-window strategies.
65
+ * @default strategy SLIDING_WINDOW, windowMs 60000, maxRequests 100
66
+ */
67
+ export interface WindowRateLimitOptions extends RateLimitOptionsBase {
68
+ strategy?: RateLimitStrategy.SLIDING_WINDOW | RateLimitStrategy.FIXED_WINDOW;
69
+ /** Time window in milliseconds. @default 60000 */
70
+ windowMs?: number;
71
+ /** Max requests allowed per window. @default 100 */
72
+ maxRequests?: number;
73
+ }
74
+ /**
75
+ * Options when using {@link RateLimitStrategy.TOKEN_BUCKET}.
76
+ */
77
+ export interface TokenBucketRateLimitOptions extends RateLimitOptionsBase {
78
+ strategy: RateLimitStrategy.TOKEN_BUCKET;
79
+ /** Tokens added per {@link TokenBucketRateLimitOptions.interval}. */
80
+ tokensPerInterval: number;
81
+ /** Interval length in milliseconds. */
82
+ interval: number;
83
+ /** Maximum tokens (burst capacity). */
84
+ bucketSize: number;
85
+ }
86
+ /**
87
+ * Configuration for the rate limiter.
88
+ *
89
+ * Use {@link WindowRateLimitOptions} for sliding/fixed window, or
90
+ * {@link TokenBucketRateLimitOptions} for token bucket (extra fields required).
91
+ */
92
+ export type RateLimitOptions = WindowRateLimitOptions | TokenBucketRateLimitOptions;
93
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/types/index.ts"],"names":[],"mappings":"AAAA,qCAAqC;AAErC;;GAEG;AACH,oBAAY,iBAAiB;IAC3B,cAAc,mBAAmB;IACjC,YAAY,iBAAiB;IAC7B,YAAY,iBAAiB;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IACjD,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,CAAC;IAExC,0CAA0C;IAC1C,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,eAAe,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjF,gFAAgF;IAChF,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B,oFAAoF;IACpF,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAEjC,6DAA6D;IAC7D,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB,6CAA6C;IAC7C,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,iEAAiE;IACjE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAE1B,4DAA4D;IAC5D,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC;IAEjC,iDAAiD;IACjD,KAAK,EAAE,cAAc,CAAC;CACvB;AAED;;;GAGG;AACH,MAAM,WAAW,sBAAuB,SAAQ,oBAAoB;IAClE,QAAQ,CAAC,EAAE,iBAAiB,CAAC,cAAc,GAAG,iBAAiB,CAAC,YAAY,CAAC;IAE7E,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,oDAAoD;IACpD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,2BAA4B,SAAQ,oBAAoB;IACvE,QAAQ,EAAE,iBAAiB,CAAC,YAAY,CAAC;IAEzC,qEAAqE;IACrE,iBAAiB,EAAE,MAAM,CAAC;IAE1B,uCAAuC;IACvC,QAAQ,EAAE,MAAM,CAAC;IAEjB,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,sBAAsB,GAAG,2BAA2B,CAAC"}
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ /** Shared types for rate limiting */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.RateLimitStrategy = void 0;
5
+ /**
6
+ * Built-in rate limiting algorithms.
7
+ */
8
+ var RateLimitStrategy;
9
+ (function (RateLimitStrategy) {
10
+ RateLimitStrategy["SLIDING_WINDOW"] = "SLIDING_WINDOW";
11
+ RateLimitStrategy["TOKEN_BUCKET"] = "TOKEN_BUCKET";
12
+ RateLimitStrategy["FIXED_WINDOW"] = "FIXED_WINDOW";
13
+ })(RateLimitStrategy || (exports.RateLimitStrategy = RateLimitStrategy = {}));
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/types/index.ts"],"names":[],"mappings":";AAAA,qCAAqC;;;AAErC;;GAEG;AACH,IAAY,iBAIX;AAJD,WAAY,iBAAiB;IAC3B,sDAAiC,CAAA;IACjC,kDAA6B,CAAA;IAC7B,kDAA6B,CAAA;AAC/B,CAAC,EAJW,iBAAiB,iCAAjB,iBAAiB,QAI5B"}
@@ -0,0 +1,3 @@
1
+ /** Internal utilities */
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,yBAAyB;AAEzB,OAAO,EAAE,CAAC"}
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ /** Internal utilities */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":";AAAA,yBAAyB"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * ratelimit-flex — flexible rate limiting for Node.js
3
+ */
4
+ import { expressRateLimiter } from './middleware/express.js';
5
+ import { fastifyRateLimiter } from './middleware/fastify.js';
6
+ import type { RateLimitOptions } from './types/index.js';
7
+ export declare const VERSION = "0.1.0";
8
+ export { expressRateLimiter, fastifyRateLimiter };
9
+ export { RateLimitEngine, createRateLimiter as createRateLimitEngine, defaultKeyGenerator, type RateLimitConsumeResult, type RateLimiterConfigInput, } from './strategies/rate-limit-engine.js';
10
+ export { MemoryStore } from './stores/memory-store.js';
11
+ export { RedisStore } from './stores/redis-store.js';
12
+ export { fixedWindowDefaults, slidingWindowDefaults, tokenBucketDefaults } from './strategies/defaults.js';
13
+ export * from './types/index.js';
14
+ export { RateLimitStrategy } from './types/index.js';
15
+ /**
16
+ * Convenience factory that returns both Express middleware and Fastify plugin.
17
+ *
18
+ * Usage:
19
+ * ```ts
20
+ * const limiter = createRateLimiter({ maxRequests: 100 });
21
+ * app.use(limiter.express); // Express
22
+ * await app.register(limiter.fastify); // Fastify
23
+ * ```
24
+ *
25
+ * Prefer importing `expressRateLimiter` or `fastifyRateLimiter` directly when framework is known.
26
+ */
27
+ export declare function createRateLimiter(options: Partial<RateLimitOptions>): {
28
+ express: import("express").RequestHandler<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
29
+ fastify: typeof fastifyRateLimiter;
30
+ };
31
+ export default expressRateLimiter;
32
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAGzD,eAAO,MAAM,OAAO,UAAU,CAAC;AAG/B,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,CAAC;AAGlD,OAAO,EACL,eAAe,EACf,iBAAiB,IAAI,qBAAqB,EAC1C,mBAAmB,EACnB,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,GAC5B,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAGrD,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAG3G,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,CAAC,gBAAgB,CAAC;;aActC,OAAO,kBAAkB;EAEtD;AAGD,eAAe,kBAAkB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,43 @@
1
+ /**
2
+ * ratelimit-flex — flexible rate limiting for Node.js
3
+ */
4
+ import { expressRateLimiter } from './middleware/express.js';
5
+ import { fastifyRateLimiter } from './middleware/fastify.js';
6
+ import fp from 'fastify-plugin';
7
+ export const VERSION = '0.1.0';
8
+ // Framework adapters
9
+ export { expressRateLimiter, fastifyRateLimiter };
10
+ // Core engine and stores
11
+ export { RateLimitEngine, createRateLimiter as createRateLimitEngine, defaultKeyGenerator, } from './strategies/rate-limit-engine.js';
12
+ export { MemoryStore } from './stores/memory-store.js';
13
+ export { RedisStore } from './stores/redis-store.js';
14
+ // Built-in defaults
15
+ export { fixedWindowDefaults, slidingWindowDefaults, tokenBucketDefaults } from './strategies/defaults.js';
16
+ // Shared types and enums
17
+ export * from './types/index.js';
18
+ export { RateLimitStrategy } from './types/index.js';
19
+ /**
20
+ * Convenience factory that returns both Express middleware and Fastify plugin.
21
+ *
22
+ * Usage:
23
+ * ```ts
24
+ * const limiter = createRateLimiter({ maxRequests: 100 });
25
+ * app.use(limiter.express); // Express
26
+ * await app.register(limiter.fastify); // Fastify
27
+ * ```
28
+ *
29
+ * Prefer importing `expressRateLimiter` or `fastifyRateLimiter` directly when framework is known.
30
+ */
31
+ export function createRateLimiter(options) {
32
+ const wrappedPlugin = fp(async (fastify, pluginOpts = {}) => {
33
+ const merged = { ...options, ...pluginOpts };
34
+ return fastifyRateLimiter(fastify, merged);
35
+ }, { name: 'ratelimit-flex-wrapper' });
36
+ return {
37
+ express: expressRateLimiter(options),
38
+ fastify: wrappedPlugin,
39
+ };
40
+ }
41
+ // Express is the most common default integration path.
42
+ export default expressRateLimiter;
43
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE7D,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAEhC,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC;AAE/B,qBAAqB;AACrB,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,CAAC;AAElD,yBAAyB;AACzB,OAAO,EACL,eAAe,EACf,iBAAiB,IAAI,qBAAqB,EAC1C,mBAAmB,GAGpB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAErD,oBAAoB;AACpB,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAE3G,yBAAyB;AACzB,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAkC;IAClE,MAAM,aAAa,GAAG,EAAE,CACtB,KAAK,EAAE,OAAgB,EAAE,aAAwC,EAAE,EAAE,EAAE;QACrE,MAAM,MAAM,GAAG,EAAE,GAAG,OAAO,EAAE,GAAG,UAAU,EAAE,CAAC;QAC7C,OAAQ,kBAA6F,CACnG,OAAO,EACP,MAAM,CACP,CAAC;IACJ,CAAC,EACD,EAAE,IAAI,EAAE,wBAAwB,EAAE,CACnC,CAAC;IAEF,OAAO;QACL,OAAO,EAAE,kBAAkB,CAAC,OAAO,CAAC;QACpC,OAAO,EAAE,aAA0C;KACpD,CAAC;AACJ,CAAC;AAED,uDAAuD;AACvD,eAAe,kBAAkB,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { RequestHandler } from 'express';
2
+ import type { RateLimitInfo, RateLimitOptions } from '../types/index.js';
3
+ declare module 'express-serve-static-core' {
4
+ interface Request {
5
+ /** Populated by {@link expressRateLimiter} after a successful consume (not blocked). */
6
+ rateLimit?: RateLimitInfo;
7
+ }
8
+ }
9
+ /**
10
+ * Express middleware factory. One {@link RateLimitEngine} and store are created per call (singleton for that middleware instance).
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * app.use(expressRateLimiter({ maxRequests: 50 }));
15
+ * ```
16
+ */
17
+ export declare function expressRateLimiter(options: Partial<RateLimitOptions>): RequestHandler;
18
+ //# sourceMappingURL=express.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../src/middleware/express.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAyB,cAAc,EAAY,MAAM,SAAS,CAAC;AAE/E,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAGzE,OAAO,QAAQ,2BAA2B,CAAC;IACzC,UAAU,OAAO;QACf,wFAAwF;QACxF,SAAS,CAAC,EAAE,aAAa,CAAC;KAC3B;CACF;AAQD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,cAAc,CAkDrF"}
@@ -0,0 +1,58 @@
1
+ import { RateLimitEngine, defaultKeyGenerator } from '../strategies/rate-limit-engine.js';
2
+ import { jsonErrorBody, mergeRateLimiterOptions, toRateLimitInfo } from './merge-options.js';
3
+ function applyHeaders(res, headers) {
4
+ for (const [name, value] of Object.entries(headers)) {
5
+ res.setHeader(name, value);
6
+ }
7
+ }
8
+ /**
9
+ * Express middleware factory. One {@link RateLimitEngine} and store are created per call (singleton for that middleware instance).
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * app.use(expressRateLimiter({ maxRequests: 50 }));
14
+ * ```
15
+ */
16
+ export function expressRateLimiter(options) {
17
+ const resolved = mergeRateLimiterOptions(options);
18
+ const { onLimitReached, ...engineOptions } = resolved;
19
+ const engine = new RateLimitEngine(engineOptions);
20
+ const keyGen = resolved.keyGenerator ?? defaultKeyGenerator;
21
+ return async function rateLimitMiddleware(req, res, next) {
22
+ if (resolved.skip?.(req) === true) {
23
+ next();
24
+ return;
25
+ }
26
+ const key = keyGen(req);
27
+ const result = await engine.consumeWithKey(key, req);
28
+ if (resolved.headers !== false) {
29
+ applyHeaders(res, result.headers);
30
+ }
31
+ if (result.isBlocked) {
32
+ if (onLimitReached) {
33
+ await Promise.resolve(onLimitReached(req, result));
34
+ }
35
+ res
36
+ .status(resolved.statusCode ?? 429)
37
+ .json(jsonErrorBody(resolved.message ?? 'Too many requests'));
38
+ return;
39
+ }
40
+ req.rateLimit = toRateLimitInfo(resolved, result);
41
+ const shouldDecrementFailed = resolved.skipFailedRequests === true;
42
+ const shouldDecrementSuccess = resolved.skipSuccessfulRequests === true;
43
+ if (shouldDecrementFailed || shouldDecrementSuccess) {
44
+ res.once('finish', () => {
45
+ const status = res.statusCode;
46
+ const failed = status >= 400;
47
+ const success = status < 400;
48
+ if ((shouldDecrementFailed && failed) || (shouldDecrementSuccess && success)) {
49
+ void resolved.store.decrement(key).catch(() => {
50
+ /* ignore */
51
+ });
52
+ }
53
+ });
54
+ }
55
+ next();
56
+ };
57
+ }
58
+ //# sourceMappingURL=express.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"express.js","sourceRoot":"","sources":["../../src/middleware/express.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AAE1F,OAAO,EAAE,aAAa,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAS7F,SAAS,YAAY,CAAC,GAAa,EAAE,OAA+B;IAClE,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACpD,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC7B,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAkC;IACnE,MAAM,QAAQ,GAAG,uBAAuB,CAAC,OAAO,CAAC,CAAC;IAClD,MAAM,EAAE,cAAc,EAAE,GAAG,aAAa,EAAE,GAAG,QAAQ,CAAC;IACtD,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,aAAa,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,QAAQ,CAAC,YAAY,IAAI,mBAAmB,CAAC;IAE5D,OAAO,KAAK,UAAU,mBAAmB,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB;QACvF,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;YAClC,IAAI,EAAE,CAAC;YACP,OAAO;QACT,CAAC;QAED,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QAExB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAErD,IAAI,QAAQ,CAAC,OAAO,KAAK,KAAK,EAAE,CAAC;YAC/B,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;QACpC,CAAC;QAED,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;YACrB,IAAI,cAAc,EAAE,CAAC;gBACnB,MAAM,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC;YACrD,CAAC;YACD,GAAG;iBACA,MAAM,CAAC,QAAQ,CAAC,UAAU,IAAI,GAAG,CAAC;iBAClC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,OAAO,IAAI,mBAAmB,CAAC,CAAC,CAAC;YAChE,OAAO;QACT,CAAC;QAED,GAAG,CAAC,SAAS,GAAG,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAElD,MAAM,qBAAqB,GAAG,QAAQ,CAAC,kBAAkB,KAAK,IAAI,CAAC;QACnE,MAAM,sBAAsB,GAAG,QAAQ,CAAC,sBAAsB,KAAK,IAAI,CAAC;QAExE,IAAI,qBAAqB,IAAI,sBAAsB,EAAE,CAAC;YACpD,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE;gBACtB,MAAM,MAAM,GAAG,GAAG,CAAC,UAAU,CAAC;gBAC9B,MAAM,MAAM,GAAG,MAAM,IAAI,GAAG,CAAC;gBAC7B,MAAM,OAAO,GAAG,MAAM,GAAG,GAAG,CAAC;gBAC7B,IAAI,CAAC,qBAAqB,IAAI,MAAM,CAAC,IAAI,CAAC,sBAAsB,IAAI,OAAO,CAAC,EAAE,CAAC;oBAC7E,KAAK,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;wBAC5C,YAAY;oBACd,CAAC,CAAC,CAAC;gBACL,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;QAED,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,21 @@
1
+ import type { FastifyPluginAsync } from 'fastify';
2
+ import type { RateLimitInfo, RateLimitOptions } from '../types/index.js';
3
+ declare module 'fastify' {
4
+ interface FastifyRequest {
5
+ /** Populated by {@link fastifyRateLimiter} after a successful consume (not blocked). */
6
+ rateLimit?: RateLimitInfo;
7
+ /** Internal: storage key for optional response-based decrement. */
8
+ rateLimitKey?: string;
9
+ /** Internal: when to consider decrement on `onResponse`. */
10
+ rateLimitDecrementFlags?: {
11
+ onFailed: boolean;
12
+ onSuccess: boolean;
13
+ };
14
+ }
15
+ }
16
+ /**
17
+ * Fastify plugin (wrapped with `fastify-plugin` for correct encapsulation).
18
+ * Registers `onRequest` / `onResponse` hooks; one {@link RateLimitEngine} and store per registration.
19
+ */
20
+ export declare const fastifyRateLimiter: FastifyPluginAsync<Partial<RateLimitOptions>>;
21
+ //# sourceMappingURL=fastify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fastify.d.ts","sourceRoot":"","sources":["../../src/middleware/fastify.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAGlD,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAGzE,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,cAAc;QACtB,wFAAwF;QACxF,SAAS,CAAC,EAAE,aAAa,CAAC;QAC1B,mEAAmE;QACnE,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,4DAA4D;QAC5D,uBAAuB,CAAC,EAAE;YACxB,QAAQ,EAAE,OAAO,CAAC;YAClB,SAAS,EAAE,OAAO,CAAC;SACpB,CAAC;KACH;CACF;AA6DD;;;GAGG;AACH,eAAO,MAAM,kBAAkB,+CAE7B,CAAC"}
@@ -0,0 +1,60 @@
1
+ import fp from 'fastify-plugin';
2
+ import { RateLimitEngine, defaultKeyGenerator } from '../strategies/rate-limit-engine.js';
3
+ import { jsonErrorBody, mergeRateLimiterOptions, toRateLimitInfo } from './merge-options.js';
4
+ const plugin = async (fastify, options) => {
5
+ const resolved = mergeRateLimiterOptions(options);
6
+ const { onLimitReached, ...engineOptions } = resolved;
7
+ const engine = new RateLimitEngine(engineOptions);
8
+ const keyGen = resolved.keyGenerator ?? defaultKeyGenerator;
9
+ fastify.addHook('onRequest', async (request, reply) => {
10
+ if (resolved.skip?.(request) === true) {
11
+ return;
12
+ }
13
+ const key = keyGen(request);
14
+ const result = await engine.consumeWithKey(key, request);
15
+ if (resolved.headers !== false) {
16
+ for (const [name, value] of Object.entries(result.headers)) {
17
+ reply.header(name, value);
18
+ }
19
+ }
20
+ if (result.isBlocked) {
21
+ if (onLimitReached) {
22
+ await Promise.resolve(onLimitReached(request, result));
23
+ }
24
+ await reply
25
+ .status(resolved.statusCode ?? 429)
26
+ .send(jsonErrorBody(resolved.message ?? 'Too many requests'));
27
+ return;
28
+ }
29
+ request.rateLimit = toRateLimitInfo(resolved, result);
30
+ const onFailed = resolved.skipFailedRequests === true;
31
+ const onSuccess = resolved.skipSuccessfulRequests === true;
32
+ if (onFailed || onSuccess) {
33
+ request.rateLimitKey = key;
34
+ request.rateLimitDecrementFlags = { onFailed, onSuccess };
35
+ }
36
+ });
37
+ fastify.addHook('onResponse', async (request, reply) => {
38
+ const key = request.rateLimitKey;
39
+ const flags = request.rateLimitDecrementFlags;
40
+ if (!key || !flags) {
41
+ return;
42
+ }
43
+ const status = reply.statusCode;
44
+ const failed = status >= 400;
45
+ const success = status < 400;
46
+ if ((flags.onFailed && failed) || (flags.onSuccess && success)) {
47
+ void resolved.store.decrement(key).catch(() => {
48
+ /* ignore */
49
+ });
50
+ }
51
+ });
52
+ };
53
+ /**
54
+ * Fastify plugin (wrapped with `fastify-plugin` for correct encapsulation).
55
+ * Registers `onRequest` / `onResponse` hooks; one {@link RateLimitEngine} and store per registration.
56
+ */
57
+ export const fastifyRateLimiter = fp(plugin, {
58
+ name: 'ratelimit-flex',
59
+ });
60
+ //# sourceMappingURL=fastify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fastify.js","sourceRoot":"","sources":["../../src/middleware/fastify.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ /** Framework middleware adapters (Express, Fastify, etc.) */
2
+ export { expressRateLimiter } from './express.js';
3
+ export { fastifyRateLimiter } from './fastify.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAE7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,4 @@
1
+ /** Framework middleware adapters (Express, Fastify, etc.) */
2
+ export { expressRateLimiter } from './express.js';
3
+ export { fastifyRateLimiter } from './fastify.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAE7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { RateLimitInfo, RateLimitOptions, RateLimitResult } from '../types/index.js';
2
+ export declare const baseDefaults: {
3
+ readonly headers: true;
4
+ readonly statusCode: 429;
5
+ readonly message: "Too many requests";
6
+ readonly skipFailedRequests: false;
7
+ readonly skipSuccessfulRequests: false;
8
+ };
9
+ export declare function getLimit(opts: RateLimitOptions): number;
10
+ /**
11
+ * Merge partial options with strategy defaults and ensure a {@link MemoryStore} when `store` is omitted.
12
+ */
13
+ export declare function mergeRateLimiterOptions(options: Partial<RateLimitOptions>): RateLimitOptions;
14
+ export declare function toRateLimitInfo(opts: RateLimitOptions, result: RateLimitResult): RateLimitInfo;
15
+ export declare function jsonErrorBody(message: string | object): object;
16
+ //# sourceMappingURL=merge-options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-options.d.ts","sourceRoot":"","sources":["../../src/middleware/merge-options.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAG1F,eAAO,MAAM,YAAY;;;;;;CAMf,CAAC;AAEX,wBAAgB,QAAQ,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,CAKvD;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,gBAAgB,CAwC5F;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,gBAAgB,EAAE,MAAM,EAAE,eAAe,GAAG,aAAa,CAO9F;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAE9D"}
@@ -0,0 +1,64 @@
1
+ import { MemoryStore } from '../stores/memory-store.js';
2
+ import { fixedWindowDefaults, slidingWindowDefaults, tokenBucketDefaults, } from '../strategies/defaults.js';
3
+ import { RateLimitStrategy } from '../types/index.js';
4
+ export const baseDefaults = {
5
+ headers: true,
6
+ statusCode: 429,
7
+ message: 'Too many requests',
8
+ skipFailedRequests: false,
9
+ skipSuccessfulRequests: false,
10
+ };
11
+ export function getLimit(opts) {
12
+ if (opts.strategy === RateLimitStrategy.TOKEN_BUCKET) {
13
+ return opts.bucketSize;
14
+ }
15
+ return opts.maxRequests ?? 100;
16
+ }
17
+ /**
18
+ * Merge partial options with strategy defaults and ensure a {@link MemoryStore} when `store` is omitted.
19
+ */
20
+ export function mergeRateLimiterOptions(options) {
21
+ const strategy = options.strategy ?? RateLimitStrategy.SLIDING_WINDOW;
22
+ if (strategy === RateLimitStrategy.TOKEN_BUCKET) {
23
+ const merged = {
24
+ ...tokenBucketDefaults,
25
+ ...baseDefaults,
26
+ ...options,
27
+ strategy: RateLimitStrategy.TOKEN_BUCKET,
28
+ };
29
+ const store = merged.store ??
30
+ new MemoryStore({
31
+ strategy: RateLimitStrategy.TOKEN_BUCKET,
32
+ tokensPerInterval: merged.tokensPerInterval,
33
+ interval: merged.interval,
34
+ bucketSize: merged.bucketSize,
35
+ });
36
+ return { ...merged, store };
37
+ }
38
+ const windowDefaults = strategy === RateLimitStrategy.FIXED_WINDOW ? fixedWindowDefaults : slidingWindowDefaults;
39
+ const merged = {
40
+ ...windowDefaults,
41
+ ...baseDefaults,
42
+ ...options,
43
+ strategy,
44
+ };
45
+ const store = merged.store ??
46
+ new MemoryStore({
47
+ strategy: merged.strategy,
48
+ windowMs: merged.windowMs ?? 60000,
49
+ maxRequests: merged.maxRequests ?? 100,
50
+ });
51
+ return { ...merged, store };
52
+ }
53
+ export function toRateLimitInfo(opts, result) {
54
+ return {
55
+ limit: getLimit(opts),
56
+ current: result.totalHits,
57
+ remaining: result.remaining,
58
+ resetTime: result.resetTime,
59
+ };
60
+ }
61
+ export function jsonErrorBody(message) {
62
+ return { error: message };
63
+ }
64
+ //# sourceMappingURL=merge-options.js.map