limitly 1.0.1

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 (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +172 -0
  3. package/dist/algorithms/factory.d.ts +3 -0
  4. package/dist/algorithms/factory.d.ts.map +1 -0
  5. package/dist/algorithms/factory.js +18 -0
  6. package/dist/algorithms/factory.js.map +1 -0
  7. package/dist/algorithms/sliding-window.d.ts +10 -0
  8. package/dist/algorithms/sliding-window.d.ts.map +1 -0
  9. package/dist/algorithms/sliding-window.js +35 -0
  10. package/dist/algorithms/sliding-window.js.map +1 -0
  11. package/dist/algorithms/strategy.d.ts +2 -0
  12. package/dist/algorithms/strategy.d.ts.map +1 -0
  13. package/dist/algorithms/strategy.js +3 -0
  14. package/dist/algorithms/strategy.js.map +1 -0
  15. package/dist/algorithms/token-bucket.d.ts +10 -0
  16. package/dist/algorithms/token-bucket.d.ts.map +1 -0
  17. package/dist/algorithms/token-bucket.js +32 -0
  18. package/dist/algorithms/token-bucket.js.map +1 -0
  19. package/dist/index.d.ts +8 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +16 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/limiter.d.ts +19 -0
  24. package/dist/limiter.d.ts.map +1 -0
  25. package/dist/limiter.js +54 -0
  26. package/dist/limiter.js.map +1 -0
  27. package/dist/middleware/express.d.ts +5 -0
  28. package/dist/middleware/express.d.ts.map +1 -0
  29. package/dist/middleware/express.js +40 -0
  30. package/dist/middleware/express.js.map +1 -0
  31. package/dist/middleware/fastify.d.ts +9 -0
  32. package/dist/middleware/fastify.d.ts.map +1 -0
  33. package/dist/middleware/fastify.js +53 -0
  34. package/dist/middleware/fastify.js.map +1 -0
  35. package/dist/scripts/sliding.lua +38 -0
  36. package/dist/scripts/token.lua +41 -0
  37. package/dist/types/index.d.ts +49 -0
  38. package/dist/types/index.d.ts.map +1 -0
  39. package/dist/types/index.js +3 -0
  40. package/dist/types/index.js.map +1 -0
  41. package/dist/utils/headers.d.ts +6 -0
  42. package/dist/utils/headers.d.ts.map +1 -0
  43. package/dist/utils/headers.js +23 -0
  44. package/dist/utils/headers.js.map +1 -0
  45. package/dist/utils/redis.d.ts +4 -0
  46. package/dist/utils/redis.d.ts.map +1 -0
  47. package/dist/utils/redis.js +63 -0
  48. package/dist/utils/redis.js.map +1 -0
  49. package/dist/utils/scripts.d.ts +11 -0
  50. package/dist/utils/scripts.d.ts.map +1 -0
  51. package/dist/utils/scripts.js +66 -0
  52. package/dist/utils/scripts.js.map +1 -0
  53. package/package.json +93 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Arpan Das
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,172 @@
1
+ # redislimit
2
+
3
+ Distributed, Redis-powered rate limiting for Express and Fastify.
4
+
5
+ > Express-rate-limit, but distributed, Redis-powered, and production ready.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install redislimit ioredis
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ### Express
16
+
17
+ ```typescript
18
+ import express from "express";
19
+ import Redis from "ioredis";
20
+ import { createLimiter } from "redislimit";
21
+
22
+ const app = express();
23
+ const redis = new Redis();
24
+ const limiter = createLimiter({ redis });
25
+
26
+ app.use(
27
+ limiter.middleware({
28
+ algorithm: "sliding-window",
29
+ limit: 100,
30
+ window: 60,
31
+ key: (req) => req.ip,
32
+ })
33
+ );
34
+ ```
35
+
36
+ ### Fastify
37
+
38
+ ```typescript
39
+ import Fastify from "fastify";
40
+ import Redis from "ioredis";
41
+ import { createLimiter } from "redislimit";
42
+
43
+ const fastify = Fastify();
44
+ const limiter = createLimiter({ redis: new Redis() });
45
+
46
+ await fastify.register(limiter.fastifyPlugin, {
47
+ algorithm: "token-bucket",
48
+ capacity: 50,
49
+ refillRate: 10,
50
+ key: (req) => req.ip,
51
+ });
52
+ ```
53
+
54
+ ## Algorithms
55
+
56
+ ### Sliding Window
57
+
58
+ Uses Redis Sorted Sets for precise rate limiting over a rolling time window.
59
+
60
+ ```typescript
61
+ limiter.middleware({
62
+ algorithm: "sliding-window",
63
+ limit: 100,
64
+ window: 60, // seconds
65
+ });
66
+ ```
67
+
68
+ ### Token Bucket
69
+
70
+ Supports burst traffic with configurable refill rate.
71
+
72
+ ```typescript
73
+ limiter.middleware({
74
+ algorithm: "token-bucket",
75
+ capacity: 100,
76
+ refillRate: 10, // tokens per second
77
+ });
78
+ ```
79
+
80
+ ## Redis Connection
81
+
82
+ Supports multiple connection modes:
83
+
84
+ ```typescript
85
+ // Existing Redis instance
86
+ createLimiter({ redis: new Redis() });
87
+
88
+ // Connection URL
89
+ createLimiter({ redis: "redis://localhost:6379" });
90
+
91
+ // Options object
92
+ createLimiter({ redis: { host: "localhost", port: 6379 } });
93
+
94
+ // Cluster
95
+ createLimiter({
96
+ redis: {
97
+ nodes: [{ host: "127.0.0.1", port: 7000 }],
98
+ },
99
+ });
100
+ ```
101
+
102
+ ## Key Extraction
103
+
104
+ ```typescript
105
+ // IP-based
106
+ key: (req) => req.ip
107
+
108
+ // API Key
109
+ key: (req) => req.headers["x-api-key"]
110
+
111
+ // User ID
112
+ key: (req) => req.user.id
113
+ ```
114
+
115
+ ## Response Headers
116
+
117
+ Standard rate limit headers are set by default:
118
+
119
+ ```
120
+ X-RateLimit-Limit: 100
121
+ X-RateLimit-Remaining: 45
122
+ X-RateLimit-Reset: 1710000000
123
+ Retry-After: 15
124
+ ```
125
+
126
+ Disable with `headers: false`.
127
+
128
+ ## Custom Limit Response
129
+
130
+ ```typescript
131
+ limiter.middleware({
132
+ algorithm: "sliding-window",
133
+ limit: 100,
134
+ window: 60,
135
+ onLimitReached(req, res) {
136
+ res.status(429).json({ code: "RATE_LIMITED" });
137
+ },
138
+ });
139
+ ```
140
+
141
+ ## Fail Open / Closed
142
+
143
+ When Redis is unavailable:
144
+
145
+ ```typescript
146
+ createLimiter({
147
+ redis: new Redis(),
148
+ failOpen: true, // allow traffic (default)
149
+ // failOpen: false, // block with 503
150
+ });
151
+ ```
152
+
153
+ ## Extensibility
154
+
155
+ Implement custom algorithms with the `RateLimitStrategy` interface:
156
+
157
+ ```typescript
158
+ interface RateLimitStrategy {
159
+ consume(key: string): Promise<RateLimitResult>;
160
+ }
161
+ ```
162
+
163
+ ## Performance
164
+
165
+ - Atomic operations via Lua scripts (`EVALSHA`)
166
+ - Automatic key cleanup with `EXPIRE`
167
+ - P95 < 5ms per check (excluding network latency)
168
+ - 20,000+ checks/sec on a single Redis node
169
+
170
+ ## License
171
+
172
+ MIT
@@ -0,0 +1,3 @@
1
+ import type { AlgorithmConfig, RateLimitStrategy, RedisClient } from "../types";
2
+ export declare function createStrategy(redis: RedisClient, config: AlgorithmConfig, keyPrefix?: string): RateLimitStrategy;
3
+ //# sourceMappingURL=factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/algorithms/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,iBAAiB,EACjB,WAAW,EACZ,MAAM,UAAU,CAAC;AAIlB,wBAAgB,cAAc,CAC5B,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,eAAe,EACvB,SAAS,CAAC,EAAE,MAAM,GACjB,iBAAiB,CAanB"}
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createStrategy = createStrategy;
4
+ const sliding_window_1 = require("./sliding-window");
5
+ const token_bucket_1 = require("./token-bucket");
6
+ function createStrategy(redis, config, keyPrefix) {
7
+ switch (config.algorithm) {
8
+ case "sliding-window":
9
+ return new sliding_window_1.SlidingWindowStrategy(redis, config, keyPrefix);
10
+ case "token-bucket":
11
+ return new token_bucket_1.TokenBucketStrategy(redis, config, keyPrefix);
12
+ default: {
13
+ const exhaustive = config;
14
+ throw new Error(`Unknown algorithm: ${exhaustive.algorithm}`);
15
+ }
16
+ }
17
+ }
18
+ //# sourceMappingURL=factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/algorithms/factory.ts"],"names":[],"mappings":";;AAQA,wCAiBC;AApBD,qDAAyD;AACzD,iDAAqD;AAErD,SAAgB,cAAc,CAC5B,KAAkB,EAClB,MAAuB,EACvB,SAAkB;IAElB,QAAQ,MAAM,CAAC,SAAS,EAAE,CAAC;QACzB,KAAK,gBAAgB;YACnB,OAAO,IAAI,sCAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;QAC7D,KAAK,cAAc;YACjB,OAAO,IAAI,kCAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;QAC3D,OAAO,CAAC,CAAC,CAAC;YACR,MAAM,UAAU,GAAU,MAAM,CAAC;YACjC,MAAM,IAAI,KAAK,CACb,sBAAuB,UAA8B,CAAC,SAAS,EAAE,CAClE,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { RateLimitResult, RateLimitStrategy, RedisClient, SlidingWindowConfig } from "../types";
2
+ export declare class SlidingWindowStrategy implements RateLimitStrategy {
3
+ private readonly redis;
4
+ private readonly limit;
5
+ private readonly window;
6
+ private readonly keyPrefix;
7
+ constructor(redis: RedisClient, config: SlidingWindowConfig, keyPrefix?: string);
8
+ consume(key: string): Promise<RateLimitResult>;
9
+ }
10
+ //# sourceMappingURL=sliding-window.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sliding-window.d.ts","sourceRoot":"","sources":["../../src/algorithms/sliding-window.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,mBAAmB,EACpB,MAAM,UAAU,CAAC;AAIlB,qBAAa,qBAAsB,YAAW,iBAAiB;IAC7D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAc;IACpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAGjC,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,mBAAmB,EAC3B,SAAS,SAAe;IAQpB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;CAsBrD"}
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SlidingWindowStrategy = void 0;
4
+ const crypto_1 = require("crypto");
5
+ const redis_1 = require("../utils/redis");
6
+ const scripts_1 = require("../utils/scripts");
7
+ class SlidingWindowStrategy {
8
+ constructor(redis, config, keyPrefix = "redislimit") {
9
+ this.redis = redis;
10
+ this.limit = config.limit;
11
+ this.window = config.window;
12
+ this.keyPrefix = keyPrefix;
13
+ }
14
+ async consume(key) {
15
+ const redisKey = (0, redis_1.buildKey)(this.keyPrefix, `sw:${key}`);
16
+ const now = Date.now();
17
+ const requestId = (0, crypto_1.randomUUID)();
18
+ const result = await (0, scripts_1.evalScript)(this.redis, "sliding", [redisKey], [
19
+ this.limit,
20
+ this.window,
21
+ now,
22
+ requestId,
23
+ ]);
24
+ const parsed = (0, scripts_1.parseScriptResult)(result);
25
+ return {
26
+ allowed: parsed.allowed,
27
+ limit: parsed.limit,
28
+ remaining: parsed.remaining,
29
+ reset: parsed.reset,
30
+ retryAfter: parsed.retryAfter || undefined,
31
+ };
32
+ }
33
+ }
34
+ exports.SlidingWindowStrategy = SlidingWindowStrategy;
35
+ //# sourceMappingURL=sliding-window.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sliding-window.js","sourceRoot":"","sources":["../../src/algorithms/sliding-window.ts"],"names":[],"mappings":";;;AAAA,mCAAoC;AAOpC,0CAA0C;AAC1C,8CAAiE;AAEjE,MAAa,qBAAqB;IAMhC,YACE,KAAkB,EAClB,MAA2B,EAC3B,SAAS,GAAG,YAAY;QAExB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;QAC5B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,GAAW;QACvB,MAAM,QAAQ,GAAG,IAAA,gBAAQ,EAAC,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,EAAE,CAAC,CAAC;QACvD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,SAAS,GAAG,IAAA,mBAAU,GAAE,CAAC;QAE/B,MAAM,MAAM,GAAG,MAAM,IAAA,oBAAU,EAAC,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,CAAC,QAAQ,CAAC,EAAE;YACjE,IAAI,CAAC,KAAK;YACV,IAAI,CAAC,MAAM;YACX,GAAG;YACH,SAAS;SACV,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,IAAA,2BAAiB,EAAC,MAAM,CAAC,CAAC;QAEzC,OAAO;YACL,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,SAAS;SAC3C,CAAC;IACJ,CAAC;CACF;AAvCD,sDAuCC"}
@@ -0,0 +1,2 @@
1
+ export type { RateLimitStrategy } from "../types";
2
+ //# sourceMappingURL=strategy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strategy.d.ts","sourceRoot":"","sources":["../../src/algorithms/strategy.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=strategy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strategy.js","sourceRoot":"","sources":["../../src/algorithms/strategy.ts"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ import type { RateLimitResult, RateLimitStrategy, RedisClient, TokenBucketConfig } from "../types";
2
+ export declare class TokenBucketStrategy implements RateLimitStrategy {
3
+ private readonly redis;
4
+ private readonly capacity;
5
+ private readonly refillRate;
6
+ private readonly keyPrefix;
7
+ constructor(redis: RedisClient, config: TokenBucketConfig, keyPrefix?: string);
8
+ consume(key: string): Promise<RateLimitResult>;
9
+ }
10
+ //# sourceMappingURL=token-bucket.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-bucket.d.ts","sourceRoot":"","sources":["../../src/algorithms/token-bucket.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,iBAAiB,EAClB,MAAM,UAAU,CAAC;AAIlB,qBAAa,mBAAoB,YAAW,iBAAiB;IAC3D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAc;IACpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAGjC,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,iBAAiB,EACzB,SAAS,SAAe;IAQpB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;CAoBrD"}
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TokenBucketStrategy = void 0;
4
+ const redis_1 = require("../utils/redis");
5
+ const scripts_1 = require("../utils/scripts");
6
+ class TokenBucketStrategy {
7
+ constructor(redis, config, keyPrefix = "redislimit") {
8
+ this.redis = redis;
9
+ this.capacity = config.capacity;
10
+ this.refillRate = config.refillRate;
11
+ this.keyPrefix = keyPrefix;
12
+ }
13
+ async consume(key) {
14
+ const redisKey = (0, redis_1.buildKey)(this.keyPrefix, `tb:${key}`);
15
+ const now = Date.now();
16
+ const result = await (0, scripts_1.evalScript)(this.redis, "token", [redisKey], [
17
+ this.capacity,
18
+ this.refillRate,
19
+ now,
20
+ ]);
21
+ const parsed = (0, scripts_1.parseScriptResult)(result);
22
+ return {
23
+ allowed: parsed.allowed,
24
+ limit: parsed.limit,
25
+ remaining: parsed.remaining,
26
+ reset: parsed.reset,
27
+ retryAfter: parsed.retryAfter || undefined,
28
+ };
29
+ }
30
+ }
31
+ exports.TokenBucketStrategy = TokenBucketStrategy;
32
+ //# sourceMappingURL=token-bucket.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-bucket.js","sourceRoot":"","sources":["../../src/algorithms/token-bucket.ts"],"names":[],"mappings":";;;AAMA,0CAA0C;AAC1C,8CAAiE;AAEjE,MAAa,mBAAmB;IAM9B,YACE,KAAkB,EAClB,MAAyB,EACzB,SAAS,GAAG,YAAY;QAExB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;QAChC,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;QACpC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,GAAW;QACvB,MAAM,QAAQ,GAAG,IAAA,gBAAQ,EAAC,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,EAAE,CAAC,CAAC;QACvD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAEvB,MAAM,MAAM,GAAG,MAAM,IAAA,oBAAU,EAAC,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC,QAAQ,CAAC,EAAE;YAC/D,IAAI,CAAC,QAAQ;YACb,IAAI,CAAC,UAAU;YACf,GAAG;SACJ,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,IAAA,2BAAiB,EAAC,MAAM,CAAC,CAAC;QAEzC,OAAO;YACL,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,SAAS;SAC3C,CAAC;IACJ,CAAC;CACF;AArCD,kDAqCC"}
@@ -0,0 +1,8 @@
1
+ export { RedisLimit, createLimiter } from "./limiter";
2
+ export { createExpressMiddleware } from "./middleware/express";
3
+ export { createFastifyPlugin, redisLimitPlugin } from "./middleware/fastify";
4
+ export { SlidingWindowStrategy } from "./algorithms/sliding-window";
5
+ export { TokenBucketStrategy } from "./algorithms/token-bucket";
6
+ export type { RateLimitStrategy } from "./algorithms/strategy";
7
+ export type { AlgorithmConfig, BaseMiddlewareOptions, MiddlewareOptions, RateLimitHeaders, RateLimitResult, RedisClient, RedisConfig, RedisLimitOptions, SlidingWindowConfig, TokenBucketConfig, } from "./types";
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7E,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAChE,YAAY,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,YAAY,EACV,eAAe,EACf,qBAAqB,EACrB,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,EACf,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TokenBucketStrategy = exports.SlidingWindowStrategy = exports.redisLimitPlugin = exports.createFastifyPlugin = exports.createExpressMiddleware = exports.createLimiter = exports.RedisLimit = void 0;
4
+ var limiter_1 = require("./limiter");
5
+ Object.defineProperty(exports, "RedisLimit", { enumerable: true, get: function () { return limiter_1.RedisLimit; } });
6
+ Object.defineProperty(exports, "createLimiter", { enumerable: true, get: function () { return limiter_1.createLimiter; } });
7
+ var express_1 = require("./middleware/express");
8
+ Object.defineProperty(exports, "createExpressMiddleware", { enumerable: true, get: function () { return express_1.createExpressMiddleware; } });
9
+ var fastify_1 = require("./middleware/fastify");
10
+ Object.defineProperty(exports, "createFastifyPlugin", { enumerable: true, get: function () { return fastify_1.createFastifyPlugin; } });
11
+ Object.defineProperty(exports, "redisLimitPlugin", { enumerable: true, get: function () { return fastify_1.redisLimitPlugin; } });
12
+ var sliding_window_1 = require("./algorithms/sliding-window");
13
+ Object.defineProperty(exports, "SlidingWindowStrategy", { enumerable: true, get: function () { return sliding_window_1.SlidingWindowStrategy; } });
14
+ var token_bucket_1 = require("./algorithms/token-bucket");
15
+ Object.defineProperty(exports, "TokenBucketStrategy", { enumerable: true, get: function () { return token_bucket_1.TokenBucketStrategy; } });
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,qCAAsD;AAA7C,qGAAA,UAAU,OAAA;AAAE,wGAAA,aAAa,OAAA;AAClC,gDAA+D;AAAtD,kHAAA,uBAAuB,OAAA;AAChC,gDAA6E;AAApE,8GAAA,mBAAmB,OAAA;AAAE,2GAAA,gBAAgB,OAAA;AAC9C,8DAAoE;AAA3D,uHAAA,qBAAqB,OAAA;AAC9B,0DAAgE;AAAvD,mHAAA,mBAAmB,OAAA"}
@@ -0,0 +1,19 @@
1
+ import type { FastifyPluginAsync } from "fastify";
2
+ import type { AlgorithmConfig, MiddlewareOptions, RateLimitResult, RateLimitStrategy, RedisClient, RedisLimitOptions } from "./types";
3
+ export declare class RedisLimit {
4
+ private readonly redis;
5
+ private readonly failOpen;
6
+ private readonly keyPrefix;
7
+ constructor(options: RedisLimitOptions);
8
+ getRedis(): RedisClient;
9
+ createStrategy(config: AlgorithmConfig): RateLimitStrategy;
10
+ check(key: string, config: AlgorithmConfig, options?: {
11
+ failOpen?: boolean;
12
+ }): Promise<RateLimitResult>;
13
+ middleware(options: MiddlewareOptions): (req: import("express").Request, res: import("express").Response, next: import("express").NextFunction) => Promise<void>;
14
+ get fastifyPlugin(): FastifyPluginAsync<MiddlewareOptions>;
15
+ private createFailOpenResult;
16
+ }
17
+ export declare function createLimiter(options: RedisLimitOptions): RedisLimit;
18
+ export type { MiddlewareOptions };
19
+ //# sourceMappingURL=limiter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limiter.d.ts","sourceRoot":"","sources":["../src/limiter.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,KAAK,EACV,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,iBAAiB,EAClB,MAAM,SAAS,CAAC;AAGjB,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAc;IACpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,OAAO,EAAE,iBAAiB;IAMtC,QAAQ,IAAI,WAAW;IAIvB,cAAc,CAAC,MAAM,EAAE,eAAe,GAAG,iBAAiB;IAIpD,KAAK,CACT,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,eAAe,EACvB,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAC/B,OAAO,CAAC,eAAe,CAAC;IAc3B,UAAU,CAAC,OAAO,EAAE,iBAAiB;IAIrC,IAAI,aAAa,IAAI,kBAAkB,CAAC,iBAAiB,CAAC,CAEzD;IAED,OAAO,CAAC,oBAAoB;CAW7B;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,iBAAiB,GAAG,UAAU,CAEpE;AAED,YAAY,EAAE,iBAAiB,EAAE,CAAC"}
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RedisLimit = void 0;
4
+ exports.createLimiter = createLimiter;
5
+ const factory_1 = require("./algorithms/factory");
6
+ const express_1 = require("./middleware/express");
7
+ const fastify_1 = require("./middleware/fastify");
8
+ const redis_1 = require("./utils/redis");
9
+ class RedisLimit {
10
+ constructor(options) {
11
+ this.redis = (0, redis_1.createRedisClient)(options.redis);
12
+ this.failOpen = options.failOpen ?? true;
13
+ this.keyPrefix = options.keyPrefix ?? "redislimit";
14
+ }
15
+ getRedis() {
16
+ return this.redis;
17
+ }
18
+ createStrategy(config) {
19
+ return (0, factory_1.createStrategy)(this.redis, config, this.keyPrefix);
20
+ }
21
+ async check(key, config, options) {
22
+ const strategy = this.createStrategy(config);
23
+ const shouldFailOpen = options?.failOpen ?? this.failOpen;
24
+ try {
25
+ return await strategy.consume(key);
26
+ }
27
+ catch (error) {
28
+ if (shouldFailOpen) {
29
+ return this.createFailOpenResult(config);
30
+ }
31
+ throw error;
32
+ }
33
+ }
34
+ middleware(options) {
35
+ return (0, express_1.createExpressMiddleware)(this)(options);
36
+ }
37
+ get fastifyPlugin() {
38
+ return (0, fastify_1.createFastifyPlugin)(this);
39
+ }
40
+ createFailOpenResult(config) {
41
+ const limit = config.algorithm === "sliding-window" ? config.limit : config.capacity;
42
+ return {
43
+ allowed: true,
44
+ limit,
45
+ remaining: limit,
46
+ reset: Math.ceil(Date.now() / 1000) + 60,
47
+ };
48
+ }
49
+ }
50
+ exports.RedisLimit = RedisLimit;
51
+ function createLimiter(options) {
52
+ return new RedisLimit(options);
53
+ }
54
+ //# sourceMappingURL=limiter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limiter.js","sourceRoot":"","sources":["../src/limiter.ts"],"names":[],"mappings":";;;AAwEA,sCAEC;AA1ED,kDAAsD;AACtD,kDAA+D;AAC/D,kDAA2D;AAU3D,yCAAkD;AAElD,MAAa,UAAU;IAKrB,YAAY,OAA0B;QACpC,IAAI,CAAC,KAAK,GAAG,IAAA,yBAAiB,EAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC9C,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,IAAI,CAAC;QACzC,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,YAAY,CAAC;IACrD,CAAC;IAED,QAAQ;QACN,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,cAAc,CAAC,MAAuB;QACpC,OAAO,IAAA,wBAAc,EAAC,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IAC5D,CAAC;IAED,KAAK,CAAC,KAAK,CACT,GAAW,EACX,MAAuB,EACvB,OAAgC;QAEhC,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;QAC7C,MAAM,cAAc,GAAG,OAAO,EAAE,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC;QAE1D,IAAI,CAAC;YACH,OAAO,MAAM,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACrC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,cAAc,EAAE,CAAC;gBACnB,OAAO,IAAI,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC;YAC3C,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED,UAAU,CAAC,OAA0B;QACnC,OAAO,IAAA,iCAAuB,EAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC;IAChD,CAAC;IAED,IAAI,aAAa;QACf,OAAO,IAAA,6BAAmB,EAAC,IAAI,CAAC,CAAC;IACnC,CAAC;IAEO,oBAAoB,CAAC,MAAuB;QAClD,MAAM,KAAK,GACT,MAAM,CAAC,SAAS,KAAK,gBAAgB,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;QAEzE,OAAO;YACL,OAAO,EAAE,IAAI;YACb,KAAK;YACL,SAAS,EAAE,KAAK;YAChB,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE;SACzC,CAAC;IACJ,CAAC;CACF;AAxDD,gCAwDC;AAED,SAAgB,aAAa,CAAC,OAA0B;IACtD,OAAO,IAAI,UAAU,CAAC,OAAO,CAAC,CAAC;AACjC,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { NextFunction, Request, Response } from "express";
2
+ import type { RedisLimit } from "../limiter";
3
+ import type { MiddlewareOptions } from "../types";
4
+ export declare function createExpressMiddleware(limiter: RedisLimit): (options: MiddlewareOptions) => (req: Request, res: Response, next: NextFunction) => Promise<void>;
5
+ //# 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,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAKlD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,UAAU,IAC9B,SAAS,iBAAiB,MASjD,KAAK,OAAO,EACZ,KAAK,QAAQ,EACb,MAAM,YAAY,KACjB,OAAO,CAAC,IAAI,CAAC,CA+BnB"}
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createExpressMiddleware = createExpressMiddleware;
4
+ const headers_1 = require("../utils/headers");
5
+ const DEFAULT_KEY = (req) => req.ip ?? "unknown";
6
+ function createExpressMiddleware(limiter) {
7
+ return function middleware(options) {
8
+ const strategy = limiter.createStrategy(options);
9
+ const keyExtractor = (options.key ?? DEFAULT_KEY);
10
+ const sendHeaders = options.headers !== false;
11
+ const failOpen = options.failOpen ?? true;
12
+ return async (req, res, next) => {
13
+ const key = keyExtractor(req) ?? "unknown";
14
+ let result;
15
+ try {
16
+ result = await strategy.consume(key);
17
+ }
18
+ catch {
19
+ if (failOpen) {
20
+ return next();
21
+ }
22
+ res.status(503).json({ error: "Service Unavailable" });
23
+ return;
24
+ }
25
+ if (sendHeaders) {
26
+ (0, headers_1.setHeaders)(res, (0, headers_1.buildRateLimitHeaders)(result));
27
+ }
28
+ if (result.allowed) {
29
+ next();
30
+ return;
31
+ }
32
+ if (options.onLimitReached) {
33
+ await options.onLimitReached(req, res);
34
+ return;
35
+ }
36
+ res.status(429).json({ error: "Too Many Requests" });
37
+ };
38
+ };
39
+ }
40
+ //# sourceMappingURL=express.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"express.js","sourceRoot":"","sources":["../../src/middleware/express.ts"],"names":[],"mappings":";;AAOA,0DA4CC;AAhDD,8CAAqE;AAErE,MAAM,WAAW,GAAG,CAAC,GAAY,EAAU,EAAE,CAAC,GAAG,CAAC,EAAE,IAAI,SAAS,CAAC;AAElE,SAAgB,uBAAuB,CAAC,OAAmB;IACzD,OAAO,SAAS,UAAU,CAAC,OAA0B;QACnD,MAAM,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;QACjD,MAAM,YAAY,GAAG,CAAC,OAAO,CAAC,GAAG,IAAI,WAAW,CAEzB,CAAC;QACxB,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,KAAK,KAAK,CAAC;QAC9C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,IAAI,CAAC;QAE1C,OAAO,KAAK,EACV,GAAY,EACZ,GAAa,EACb,IAAkB,EACH,EAAE;YACjB,MAAM,GAAG,GAAG,YAAY,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC;YAE3C,IAAI,MAAM,CAAC;YACX,IAAI,CAAC;gBACH,MAAM,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACvC,CAAC;YAAC,MAAM,CAAC;gBACP,IAAI,QAAQ,EAAE,CAAC;oBACb,OAAO,IAAI,EAAE,CAAC;gBAChB,CAAC;gBACD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,qBAAqB,EAAE,CAAC,CAAC;gBACvD,OAAO;YACT,CAAC;YAED,IAAI,WAAW,EAAE,CAAC;gBAChB,IAAA,oBAAU,EAAC,GAAG,EAAE,IAAA,+BAAqB,EAAC,MAAM,CAAC,CAAC,CAAC;YACjD,CAAC;YAED,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACnB,IAAI,EAAE,CAAC;gBACP,OAAO;YACT,CAAC;YAED,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;gBAC3B,MAAM,OAAO,CAAC,cAAc,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;gBACvC,OAAO;YACT,CAAC;YAED,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC,CAAC;QACvD,CAAC,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { FastifyPluginAsync } from "fastify";
2
+ import type { RedisLimit } from "../limiter";
3
+ import type { MiddlewareOptions } from "../types";
4
+ export type FastifyRateLimitOptions = MiddlewareOptions & {
5
+ limiter: RedisLimit;
6
+ };
7
+ export declare function createFastifyPlugin(limiter: RedisLimit): FastifyPluginAsync<MiddlewareOptions>;
8
+ export declare const redisLimitPlugin: FastifyPluginAsync<FastifyRateLimitOptions>;
9
+ //# 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,EAAgC,MAAM,SAAS,CAAC;AAChF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAKlD,MAAM,MAAM,uBAAuB,GAAG,iBAAiB,GAAG;IACxD,OAAO,EAAE,UAAU,CAAC;CACrB,CAAC;AAaF,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,UAAU,GAAG,kBAAkB,CAAC,iBAAiB,CAAC,CAuC9F;AAED,eAAO,MAAM,gBAAgB,EAAE,kBAAkB,CAAC,uBAAuB,CAOxE,CAAC"}
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.redisLimitPlugin = void 0;
4
+ exports.createFastifyPlugin = createFastifyPlugin;
5
+ const headers_1 = require("../utils/headers");
6
+ const DEFAULT_KEY = (req) => req.ip;
7
+ function setFastifyHeaders(reply, headers) {
8
+ for (const [name, value] of Object.entries(headers)) {
9
+ if (value !== undefined) {
10
+ reply.header(name, value);
11
+ }
12
+ }
13
+ }
14
+ function createFastifyPlugin(limiter) {
15
+ const plugin = async (fastify, options) => {
16
+ const strategy = limiter.createStrategy(options);
17
+ const keyExtractor = (options.key ?? DEFAULT_KEY);
18
+ const sendHeaders = options.headers !== false;
19
+ const failOpen = options.failOpen ?? true;
20
+ fastify.addHook("preHandler", async (request, reply) => {
21
+ const key = keyExtractor(request) ?? "unknown";
22
+ let result;
23
+ try {
24
+ result = await strategy.consume(key);
25
+ }
26
+ catch {
27
+ if (failOpen) {
28
+ return;
29
+ }
30
+ reply.status(503).send({ error: "Service Unavailable" });
31
+ return;
32
+ }
33
+ if (sendHeaders) {
34
+ setFastifyHeaders(reply, (0, headers_1.buildRateLimitHeaders)(result));
35
+ }
36
+ if (!result.allowed) {
37
+ if (options.onLimitReached) {
38
+ await options.onLimitReached(request, reply);
39
+ return;
40
+ }
41
+ reply.status(429).send({ error: "Too Many Requests" });
42
+ }
43
+ });
44
+ };
45
+ return plugin;
46
+ }
47
+ const redisLimitPlugin = async (fastify, options) => {
48
+ const { limiter, ...rest } = options;
49
+ const middlewareOptions = rest;
50
+ await fastify.register(createFastifyPlugin(limiter), middlewareOptions);
51
+ };
52
+ exports.redisLimitPlugin = redisLimitPlugin;
53
+ //# sourceMappingURL=fastify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fastify.js","sourceRoot":"","sources":["../../src/middleware/fastify.ts"],"names":[],"mappings":";;;AAsBA,kDAuCC;AA1DD,8CAAyD;AAEzD,MAAM,WAAW,GAAG,CAAC,GAAmB,EAAU,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;AAM5D,SAAS,iBAAiB,CACxB,KAAmB,EACnB,OAAiD;IAEjD,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACpD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAgB,mBAAmB,CAAC,OAAmB;IACrD,MAAM,MAAM,GAA0C,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE;QAC/E,MAAM,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;QACjD,MAAM,YAAY,GAAG,CAAC,OAAO,CAAC,GAAG,IAAI,WAAW,CAEzB,CAAC;QACxB,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,KAAK,KAAK,CAAC;QAC9C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,IAAI,CAAC;QAE1C,OAAO,CAAC,OAAO,CAAC,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;YACrD,MAAM,GAAG,GAAG,YAAY,CAAC,OAAO,CAAC,IAAI,SAAS,CAAC;YAE/C,IAAI,MAAM,CAAC;YACX,IAAI,CAAC;gBACH,MAAM,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACvC,CAAC;YAAC,MAAM,CAAC;gBACP,IAAI,QAAQ,EAAE,CAAC;oBACb,OAAO;gBACT,CAAC;gBACD,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,qBAAqB,EAAE,CAAC,CAAC;gBACzD,OAAO;YACT,CAAC;YAED,IAAI,WAAW,EAAE,CAAC;gBAChB,iBAAiB,CAAC,KAAK,EAAE,IAAA,+BAAqB,EAAC,MAAM,CAAC,CAAC,CAAC;YAC1D,CAAC;YAED,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACpB,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;oBAC3B,MAAM,OAAO,CAAC,cAAc,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;oBAC7C,OAAO;gBACT,CAAC;gBAED,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC,CAAC;YACzD,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,OAAO,MAAM,CAAC;AAChB,CAAC;AAEM,MAAM,gBAAgB,GAAgD,KAAK,EAChF,OAAO,EACP,OAAO,EACP,EAAE;IACF,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC;IACrC,MAAM,iBAAiB,GAAG,IAAyB,CAAC;IACpD,MAAM,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC,OAAO,CAAC,EAAE,iBAAiB,CAAC,CAAC;AAC1E,CAAC,CAAC;AAPW,QAAA,gBAAgB,oBAO3B"}
@@ -0,0 +1,38 @@
1
+ -- Sliding window rate limiter using sorted sets
2
+ -- KEYS[1] = rate limit key
3
+ -- ARGV[1] = limit
4
+ -- ARGV[2] = window (seconds)
5
+ -- ARGV[3] = current timestamp (milliseconds)
6
+ -- ARGV[4] = unique request id
7
+
8
+ local key = KEYS[1]
9
+ local limit = tonumber(ARGV[1])
10
+ local window = tonumber(ARGV[2])
11
+ local now = tonumber(ARGV[3])
12
+ local request_id = ARGV[4]
13
+
14
+ local window_start = now - (window * 1000)
15
+
16
+ -- Remove entries outside the sliding window
17
+ redis.call('ZREMRANGEBYSCORE', key, '-inf', window_start)
18
+
19
+ local current_count = redis.call('ZCARD', key)
20
+
21
+ if current_count < limit then
22
+ redis.call('ZADD', key, now, request_id)
23
+ redis.call('PEXPIRE', key, window * 1000)
24
+ current_count = current_count + 1
25
+ local remaining = limit - current_count
26
+ local reset = math.ceil((now + (window * 1000)) / 1000)
27
+ return {1, limit, remaining, reset, 0}
28
+ else
29
+ local oldest = redis.call('ZRANGE', key, 0, 0, 'WITHSCORES')
30
+ local reset
31
+ if #oldest > 0 then
32
+ reset = math.ceil((tonumber(oldest[2]) + (window * 1000)) / 1000)
33
+ else
34
+ reset = math.ceil((now + (window * 1000)) / 1000)
35
+ end
36
+ local retry_after = math.max(1, reset - math.ceil(now / 1000))
37
+ return {0, limit, 0, reset, retry_after}
38
+ end
@@ -0,0 +1,41 @@
1
+ -- Token bucket rate limiter
2
+ -- KEYS[1] = rate limit key
3
+ -- ARGV[1] = capacity
4
+ -- ARGV[2] = refill rate (tokens per second)
5
+ -- ARGV[3] = current timestamp (milliseconds)
6
+
7
+ local key = KEYS[1]
8
+ local capacity = tonumber(ARGV[1])
9
+ local refill_rate = tonumber(ARGV[2])
10
+ local now = tonumber(ARGV[3])
11
+
12
+ local data = redis.call('HMGET', key, 'tokens', 'last_refill')
13
+ local tokens = tonumber(data[1])
14
+ local last_refill = tonumber(data[2])
15
+
16
+ if tokens == nil then
17
+ tokens = capacity
18
+ last_refill = now
19
+ end
20
+
21
+ local elapsed = (now - last_refill) / 1000
22
+ local refill_amount = elapsed * refill_rate
23
+ tokens = math.min(capacity, tokens + refill_amount)
24
+ last_refill = now
25
+
26
+ if tokens >= 1 then
27
+ tokens = tokens - 1
28
+ redis.call('HMSET', key, 'tokens', tokens, 'last_refill', last_refill)
29
+ local ttl = math.ceil(capacity / refill_rate) + 1
30
+ redis.call('EXPIRE', key, ttl)
31
+ local remaining = math.floor(tokens)
32
+ local reset = math.ceil(now / 1000) + math.ceil((capacity - tokens) / refill_rate)
33
+ return {1, capacity, remaining, reset, 0}
34
+ else
35
+ redis.call('HMSET', key, 'tokens', tokens, 'last_refill', last_refill)
36
+ local ttl = math.ceil(capacity / refill_rate) + 1
37
+ redis.call('EXPIRE', key, ttl)
38
+ local retry_after = math.max(1, math.ceil((1 - tokens) / refill_rate))
39
+ local reset = math.ceil(now / 1000) + retry_after
40
+ return {0, capacity, 0, reset, retry_after}
41
+ end
@@ -0,0 +1,49 @@
1
+ import type { Cluster, Redis, RedisOptions } from "ioredis";
2
+ export type RedisClient = Redis | Cluster;
3
+ export type RedisConfig = RedisClient | string | RedisOptions | {
4
+ nodes: {
5
+ host: string;
6
+ port: number;
7
+ }[];
8
+ options?: RedisOptions;
9
+ };
10
+ export interface RedisLimitOptions {
11
+ redis: RedisConfig;
12
+ failOpen?: boolean;
13
+ keyPrefix?: string;
14
+ }
15
+ export interface RateLimitResult {
16
+ allowed: boolean;
17
+ limit: number;
18
+ remaining: number;
19
+ reset: number;
20
+ retryAfter?: number;
21
+ }
22
+ export interface RateLimitStrategy {
23
+ consume(key: string): Promise<RateLimitResult>;
24
+ }
25
+ export interface SlidingWindowConfig {
26
+ algorithm: "sliding-window";
27
+ limit: number;
28
+ window: number;
29
+ }
30
+ export interface TokenBucketConfig {
31
+ algorithm: "token-bucket";
32
+ capacity: number;
33
+ refillRate: number;
34
+ }
35
+ export type AlgorithmConfig = SlidingWindowConfig | TokenBucketConfig;
36
+ export interface BaseMiddlewareOptions {
37
+ key?: (req: unknown) => string | undefined;
38
+ headers?: boolean;
39
+ onLimitReached?: (req: unknown, res: unknown) => void | Promise<void>;
40
+ failOpen?: boolean;
41
+ }
42
+ export type MiddlewareOptions = BaseMiddlewareOptions & AlgorithmConfig;
43
+ export interface RateLimitHeaders {
44
+ "X-RateLimit-Limit": string;
45
+ "X-RateLimit-Remaining": string;
46
+ "X-RateLimit-Reset": string;
47
+ "Retry-After"?: string;
48
+ }
49
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5D,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,OAAO,CAAC;AAE1C,MAAM,MAAM,WAAW,GACnB,WAAW,GACX,MAAM,GACN,YAAY,GACZ;IAAE,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAAC,OAAO,CAAC,EAAE,YAAY,CAAA;CAAE,CAAC;AAExE,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,WAAW,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;CAChD;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,gBAAgB,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,cAAc,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,eAAe,GAAG,mBAAmB,GAAG,iBAAiB,CAAC;AAEtE,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAC;IAC3C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,MAAM,iBAAiB,GAAG,qBAAqB,GAAG,eAAe,CAAC;AAExE,MAAM,WAAW,gBAAgB;IAC/B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,uBAAuB,EAAE,MAAM,CAAC;IAChC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,6 @@
1
+ import type { RateLimitHeaders, RateLimitResult } from "../types";
2
+ export declare function buildRateLimitHeaders(result: RateLimitResult): RateLimitHeaders;
3
+ export declare function setHeaders(res: {
4
+ setHeader: (name: string, value: string) => void;
5
+ }, headers: RateLimitHeaders): void;
6
+ //# sourceMappingURL=headers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headers.d.ts","sourceRoot":"","sources":["../../src/utils/headers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAElE,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,eAAe,GACtB,gBAAgB,CAYlB;AAED,wBAAgB,UAAU,CACxB,GAAG,EAAE;IAAE,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,EACzD,OAAO,EAAE,gBAAgB,GACxB,IAAI,CAMN"}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildRateLimitHeaders = buildRateLimitHeaders;
4
+ exports.setHeaders = setHeaders;
5
+ function buildRateLimitHeaders(result) {
6
+ const headers = {
7
+ "X-RateLimit-Limit": String(result.limit),
8
+ "X-RateLimit-Remaining": String(result.remaining),
9
+ "X-RateLimit-Reset": String(result.reset),
10
+ };
11
+ if (!result.allowed && result.retryAfter !== undefined) {
12
+ headers["Retry-After"] = String(result.retryAfter);
13
+ }
14
+ return headers;
15
+ }
16
+ function setHeaders(res, headers) {
17
+ for (const [name, value] of Object.entries(headers)) {
18
+ if (value !== undefined) {
19
+ res.setHeader(name, value);
20
+ }
21
+ }
22
+ }
23
+ //# sourceMappingURL=headers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headers.js","sourceRoot":"","sources":["../../src/utils/headers.ts"],"names":[],"mappings":";;AAEA,sDAcC;AAED,gCASC;AAzBD,SAAgB,qBAAqB,CACnC,MAAuB;IAEvB,MAAM,OAAO,GAAqB;QAChC,mBAAmB,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;QACzC,uBAAuB,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC;QACjD,mBAAmB,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;KAC1C,CAAC;IAEF,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QACvD,OAAO,CAAC,aAAa,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IACrD,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAgB,UAAU,CACxB,GAAyD,EACzD,OAAyB;IAEzB,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACpD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { RedisClient, RedisConfig } from "../types";
2
+ export declare function createRedisClient(config: RedisConfig): RedisClient;
3
+ export declare function buildKey(prefix: string, key: string): string;
4
+ //# sourceMappingURL=redis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../src/utils/redis.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEzD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,CAclE;AAiBD,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAE5D"}
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.createRedisClient = createRedisClient;
37
+ exports.buildKey = buildKey;
38
+ const ioredis_1 = __importStar(require("ioredis"));
39
+ function createRedisClient(config) {
40
+ if (isRedisClient(config)) {
41
+ return config;
42
+ }
43
+ if (typeof config === "string") {
44
+ return new ioredis_1.default(config);
45
+ }
46
+ if (isClusterConfig(config)) {
47
+ return new ioredis_1.Cluster(config.nodes, config.options);
48
+ }
49
+ return new ioredis_1.default(config);
50
+ }
51
+ function isRedisClient(config) {
52
+ return config instanceof ioredis_1.default || config instanceof ioredis_1.Cluster;
53
+ }
54
+ function isClusterConfig(config) {
55
+ return (typeof config === "object" &&
56
+ config !== null &&
57
+ "nodes" in config &&
58
+ Array.isArray(config.nodes));
59
+ }
60
+ function buildKey(prefix, key) {
61
+ return `${prefix}:${key}`;
62
+ }
63
+ //# sourceMappingURL=redis.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis.js","sourceRoot":"","sources":["../../src/utils/redis.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAGA,8CAcC;AAiBD,4BAEC;AApCD,mDAA4D;AAG5D,SAAgB,iBAAiB,CAAC,MAAmB;IACnD,IAAI,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1B,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,OAAO,IAAI,iBAAK,CAAC,MAAM,CAAC,CAAC;IAC3B,CAAC;IAED,IAAI,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5B,OAAO,IAAI,iBAAO,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IACnD,CAAC;IAED,OAAO,IAAI,iBAAK,CAAC,MAAM,CAAC,CAAC;AAC3B,CAAC;AAED,SAAS,aAAa,CAAC,MAAmB;IACxC,OAAO,MAAM,YAAY,iBAAK,IAAI,MAAM,YAAY,iBAAO,CAAC;AAC9D,CAAC;AAED,SAAS,eAAe,CACtB,MAAmB;IAEnB,OAAO,CACL,OAAO,MAAM,KAAK,QAAQ;QAC1B,MAAM,KAAK,IAAI;QACf,OAAO,IAAI,MAAM;QACjB,KAAK,CAAC,OAAO,CAAE,MAA6B,CAAC,KAAK,CAAC,CACpD,CAAC;AACJ,CAAC;AAED,SAAgB,QAAQ,CAAC,MAAc,EAAE,GAAW;IAClD,OAAO,GAAG,MAAM,IAAI,GAAG,EAAE,CAAC;AAC5B,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { RedisClient } from "../types";
2
+ export declare function loadScriptSha(redis: RedisClient, name: string): Promise<string>;
3
+ export declare function evalScript(redis: RedisClient, name: string, keys: string[], args: (string | number)[]): Promise<(string | number)[]>;
4
+ export declare function parseScriptResult(result: (string | number)[]): {
5
+ allowed: boolean;
6
+ limit: number;
7
+ remaining: number;
8
+ reset: number;
9
+ retryAfter: number;
10
+ };
11
+ //# sourceMappingURL=scripts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scripts.d.ts","sourceRoot":"","sources":["../../src/utils/scripts.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAiB5C,wBAAsB,aAAa,CACjC,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,CAAC,CAWjB;AAED,wBAAsB,UAAU,CAC9B,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,GACxB,OAAO,CAAC,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC,CA0B9B;AAeD,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,GAC1B;IACD,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB,CAQA"}
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadScriptSha = loadScriptSha;
4
+ exports.evalScript = evalScript;
5
+ exports.parseScriptResult = parseScriptResult;
6
+ const fs_1 = require("fs");
7
+ const path_1 = require("path");
8
+ const scriptCache = new Map();
9
+ const shaCache = new Map();
10
+ function loadScript(name) {
11
+ const cached = scriptCache.get(name);
12
+ if (cached) {
13
+ return cached;
14
+ }
15
+ const scriptPath = (0, path_1.join)(__dirname, "..", "scripts", `${name}.lua`);
16
+ const script = (0, fs_1.readFileSync)(scriptPath, "utf-8");
17
+ scriptCache.set(name, script);
18
+ return script;
19
+ }
20
+ async function loadScriptSha(redis, name) {
21
+ const cacheKey = getCacheKey(redis, name);
22
+ const cached = shaCache.get(cacheKey);
23
+ if (cached) {
24
+ return cached;
25
+ }
26
+ const script = loadScript(name);
27
+ const sha = (await redis.script("LOAD", script));
28
+ shaCache.set(cacheKey, sha);
29
+ return sha;
30
+ }
31
+ async function evalScript(redis, name, keys, args) {
32
+ const sha = await loadScriptSha(redis, name);
33
+ try {
34
+ const result = await redis.evalsha(sha, keys.length, ...keys, ...args.map(String));
35
+ return result;
36
+ }
37
+ catch (error) {
38
+ if (isNoScriptError(error)) {
39
+ const script = loadScript(name);
40
+ const result = await redis.eval(script, keys.length, ...keys, ...args.map(String));
41
+ const newSha = (await redis.script("LOAD", script));
42
+ shaCache.set(getCacheKey(redis, name), newSha);
43
+ return result;
44
+ }
45
+ throw error;
46
+ }
47
+ }
48
+ function getCacheKey(redis, name) {
49
+ const status = redis.status ?? "unknown";
50
+ return `${status}:${name}`;
51
+ }
52
+ function isNoScriptError(error) {
53
+ return (error instanceof Error &&
54
+ (error.message.includes("NOSCRIPT") ||
55
+ error.message.includes("No matching script")));
56
+ }
57
+ function parseScriptResult(result) {
58
+ return {
59
+ allowed: Number(result[0]) === 1,
60
+ limit: Number(result[1]),
61
+ remaining: Number(result[2]),
62
+ reset: Number(result[3]),
63
+ retryAfter: Number(result[4]),
64
+ };
65
+ }
66
+ //# sourceMappingURL=scripts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scripts.js","sourceRoot":"","sources":["../../src/utils/scripts.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,93 @@
1
+ {
2
+ "name": "limitly",
3
+ "version": "1.0.1",
4
+ "description": "Express-rate-limit, but distributed, Redis-powered, and production ready.",
5
+ "author": "Arpan Das <dasarpan471@gmail.com>",
6
+ "license": "MIT",
7
+ "homepage": "https://github.com/adasarpan404/limitly#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/adasarpan404/limitly.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/adasarpan404/limitly/issues"
14
+ },
15
+ "main": "dist/index.js",
16
+ "types": "dist/index.d.ts",
17
+ "sideEffects": false,
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js",
22
+ "require": "./dist/index.js"
23
+ },
24
+ "./express": {
25
+ "types": "./dist/middleware/express.d.ts",
26
+ "import": "./dist/middleware/express.js",
27
+ "require": "./dist/middleware/express.js"
28
+ },
29
+ "./fastify": {
30
+ "types": "./dist/middleware/fastify.d.ts",
31
+ "import": "./dist/middleware/fastify.js",
32
+ "require": "./dist/middleware/fastify.js"
33
+ }
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "LICENSE",
38
+ "README.md"
39
+ ],
40
+ "scripts": {
41
+ "build": "tsc && rm -rf dist/scripts && cp -r src/scripts dist/scripts",
42
+ "test": "vitest run",
43
+ "test:watch": "vitest",
44
+ "prepack": "npm run build",
45
+ "prepublishOnly": "npm test",
46
+ "pack:check": "npm pack --dry-run",
47
+ "version:patch": "npm version patch",
48
+ "version:minor": "npm version minor",
49
+ "version:major": "npm version major"
50
+ },
51
+ "keywords": [
52
+ "limitly",
53
+ "rate-limit",
54
+ "rate-limiting",
55
+ "redis",
56
+ "express",
57
+ "fastify",
58
+ "sliding-window",
59
+ "token-bucket",
60
+ "distributed",
61
+ "middleware",
62
+ "ioredis"
63
+ ],
64
+ "publishConfig": {
65
+ "access": "public",
66
+ "registry": "https://registry.npmjs.org/"
67
+ },
68
+ "peerDependencies": {
69
+ "express": ">=4.0.0",
70
+ "fastify": ">=4.0.0",
71
+ "ioredis": ">=5.0.0"
72
+ },
73
+ "peerDependenciesMeta": {
74
+ "express": {
75
+ "optional": true
76
+ },
77
+ "fastify": {
78
+ "optional": true
79
+ }
80
+ },
81
+ "devDependencies": {
82
+ "@types/express": "^4.17.21",
83
+ "@types/node": "^20.14.0",
84
+ "express": "^4.19.2",
85
+ "fastify": "^4.28.0",
86
+ "ioredis": "^5.4.1",
87
+ "typescript": "^5.5.0",
88
+ "vitest": "^1.6.0"
89
+ },
90
+ "engines": {
91
+ "node": ">=18.0.0"
92
+ }
93
+ }