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.
- package/CHANGELOG.md +12 -0
- package/LICENSE +21 -0
- package/README.md +274 -0
- package/dist/cjs/index.d.ts +32 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +73 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/middleware/express.d.ts +18 -0
- package/dist/cjs/middleware/express.d.ts.map +1 -0
- package/dist/cjs/middleware/express.js +61 -0
- package/dist/cjs/middleware/express.js.map +1 -0
- package/dist/cjs/middleware/fastify.d.ts +21 -0
- package/dist/cjs/middleware/fastify.d.ts.map +1 -0
- package/dist/cjs/middleware/fastify.js +66 -0
- package/dist/cjs/middleware/fastify.js.map +1 -0
- package/dist/cjs/middleware/index.d.ts +4 -0
- package/dist/cjs/middleware/index.d.ts.map +1 -0
- package/dist/cjs/middleware/index.js +9 -0
- package/dist/cjs/middleware/index.js.map +1 -0
- package/dist/cjs/middleware/merge-options.d.ts +16 -0
- package/dist/cjs/middleware/merge-options.d.ts.map +1 -0
- package/dist/cjs/middleware/merge-options.js +71 -0
- package/dist/cjs/middleware/merge-options.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/stores/index.d.ts +4 -0
- package/dist/cjs/stores/index.d.ts.map +1 -0
- package/dist/cjs/stores/index.js +11 -0
- package/dist/cjs/stores/index.js.map +1 -0
- package/dist/cjs/stores/memory-store.d.ts +74 -0
- package/dist/cjs/stores/memory-store.d.ts.map +1 -0
- package/dist/cjs/stores/memory-store.js +259 -0
- package/dist/cjs/stores/memory-store.js.map +1 -0
- package/dist/cjs/stores/redis-store.d.ts +113 -0
- package/dist/cjs/stores/redis-store.d.ts.map +1 -0
- package/dist/cjs/stores/redis-store.js +452 -0
- package/dist/cjs/stores/redis-store.js.map +1 -0
- package/dist/cjs/strategies/defaults.d.ts +28 -0
- package/dist/cjs/strategies/defaults.d.ts.map +1 -0
- package/dist/cjs/strategies/defaults.js +31 -0
- package/dist/cjs/strategies/defaults.js.map +1 -0
- package/dist/cjs/strategies/index.d.ts +4 -0
- package/dist/cjs/strategies/index.d.ts.map +1 -0
- package/dist/cjs/strategies/index.js +13 -0
- package/dist/cjs/strategies/index.js.map +1 -0
- package/dist/cjs/strategies/rate-limit-engine.d.ts +42 -0
- package/dist/cjs/strategies/rate-limit-engine.d.ts.map +1 -0
- package/dist/cjs/strategies/rate-limit-engine.js +128 -0
- package/dist/cjs/strategies/rate-limit-engine.js.map +1 -0
- package/dist/cjs/types/index.d.ts +93 -0
- package/dist/cjs/types/index.d.ts.map +1 -0
- package/dist/cjs/types/index.js +14 -0
- package/dist/cjs/types/index.js.map +1 -0
- package/dist/cjs/utils/index.d.ts +3 -0
- package/dist/cjs/utils/index.d.ts.map +1 -0
- package/dist/cjs/utils/index.js +4 -0
- package/dist/cjs/utils/index.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/express.d.ts +18 -0
- package/dist/middleware/express.d.ts.map +1 -0
- package/dist/middleware/express.js +58 -0
- package/dist/middleware/express.js.map +1 -0
- package/dist/middleware/fastify.d.ts +21 -0
- package/dist/middleware/fastify.d.ts.map +1 -0
- package/dist/middleware/fastify.js +60 -0
- package/dist/middleware/fastify.js.map +1 -0
- package/dist/middleware/index.d.ts +4 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +4 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/merge-options.d.ts +16 -0
- package/dist/middleware/merge-options.d.ts.map +1 -0
- package/dist/middleware/merge-options.js +64 -0
- package/dist/middleware/merge-options.js.map +1 -0
- package/dist/stores/index.d.ts +4 -0
- package/dist/stores/index.d.ts.map +1 -0
- package/dist/stores/index.js +4 -0
- package/dist/stores/index.js.map +1 -0
- package/dist/stores/memory-store.d.ts +74 -0
- package/dist/stores/memory-store.d.ts.map +1 -0
- package/dist/stores/memory-store.js +255 -0
- package/dist/stores/memory-store.js.map +1 -0
- package/dist/stores/redis-store.d.ts +113 -0
- package/dist/stores/redis-store.d.ts.map +1 -0
- package/dist/stores/redis-store.js +413 -0
- package/dist/stores/redis-store.js.map +1 -0
- package/dist/strategies/defaults.d.ts +28 -0
- package/dist/strategies/defaults.d.ts.map +1 -0
- package/dist/strategies/defaults.js +28 -0
- package/dist/strategies/defaults.js.map +1 -0
- package/dist/strategies/index.d.ts +4 -0
- package/dist/strategies/index.d.ts.map +1 -0
- package/dist/strategies/index.js +4 -0
- package/dist/strategies/index.js.map +1 -0
- package/dist/strategies/rate-limit-engine.d.ts +42 -0
- package/dist/strategies/rate-limit-engine.d.ts.map +1 -0
- package/dist/strategies/rate-limit-engine.js +122 -0
- package/dist/strategies/rate-limit-engine.js.map +1 -0
- package/dist/types/index.d.ts +93 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +11 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +90 -0
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import { RateLimitStrategy } from '../types/index.js';
|
|
2
|
+
const DEFAULT_PREFIX = 'rlf:';
|
|
3
|
+
/** Sliding window: ZSET prune + ZADD + ZCARD. KEYS[1]=zset, ARGV: now, windowMs, maxRequests, member */
|
|
4
|
+
const LUA_SLIDING_INCR = `
|
|
5
|
+
local zkey = KEYS[1]
|
|
6
|
+
local now = tonumber(ARGV[1])
|
|
7
|
+
local window_ms = tonumber(ARGV[2])
|
|
8
|
+
local max_requests = tonumber(ARGV[3])
|
|
9
|
+
local member = ARGV[4]
|
|
10
|
+
|
|
11
|
+
redis.call('ZREMRANGEBYSCORE', zkey, '-inf', now - window_ms)
|
|
12
|
+
redis.call('ZADD', zkey, now, member)
|
|
13
|
+
local count = tonumber(redis.call('ZCARD', zkey))
|
|
14
|
+
redis.call('PEXPIRE', zkey, window_ms)
|
|
15
|
+
|
|
16
|
+
local blocked = 0
|
|
17
|
+
if count > max_requests then blocked = 1 end
|
|
18
|
+
|
|
19
|
+
local oldest_score = now
|
|
20
|
+
if count > 0 then
|
|
21
|
+
local r = redis.call('ZRANGE', zkey, 0, 0, 'WITHSCORES')
|
|
22
|
+
if r[2] ~= nil then
|
|
23
|
+
oldest_score = tonumber(r[2])
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
local reset_at = oldest_score + window_ms
|
|
27
|
+
|
|
28
|
+
return { count, blocked, reset_at }
|
|
29
|
+
`;
|
|
30
|
+
const LUA_SLIDING_DECR = `
|
|
31
|
+
redis.call('ZPOPMAX', KEYS[1])
|
|
32
|
+
return 1
|
|
33
|
+
`;
|
|
34
|
+
/** Fixed window: atomic INCR + PEXPIRE on first hit. KEYS[1]=counter, ARGV: windowMs, maxRequests, now */
|
|
35
|
+
const LUA_FIXED_INCR = `
|
|
36
|
+
local k = KEYS[1]
|
|
37
|
+
local window_ms = tonumber(ARGV[1])
|
|
38
|
+
local max_requests = tonumber(ARGV[2])
|
|
39
|
+
local now = tonumber(ARGV[3])
|
|
40
|
+
|
|
41
|
+
local current = tonumber(redis.call('INCR', k))
|
|
42
|
+
if current == 1 then
|
|
43
|
+
redis.call('PEXPIRE', k, window_ms)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
local pttl = tonumber(redis.call('PTTL', k))
|
|
47
|
+
if pttl < 0 then pttl = window_ms end
|
|
48
|
+
local reset_at = now + pttl
|
|
49
|
+
|
|
50
|
+
local blocked = 0
|
|
51
|
+
if current > max_requests then blocked = 1 end
|
|
52
|
+
|
|
53
|
+
return { current, blocked, reset_at }
|
|
54
|
+
`;
|
|
55
|
+
const LUA_FIXED_DECR = `
|
|
56
|
+
local k = KEYS[1]
|
|
57
|
+
local v = tonumber(redis.call('GET', k) or '0')
|
|
58
|
+
if v > 0 then
|
|
59
|
+
redis.call('DECR', k)
|
|
60
|
+
end
|
|
61
|
+
return v
|
|
62
|
+
`;
|
|
63
|
+
/** Token bucket: HSET tokens + last_refill. KEYS[1]=hash, ARGV: now, tpi, interval_ms, bucket_size */
|
|
64
|
+
const LUA_BUCKET_INCR = `
|
|
65
|
+
local key = KEYS[1]
|
|
66
|
+
local now = tonumber(ARGV[1])
|
|
67
|
+
local tokens_per_interval = tonumber(ARGV[2])
|
|
68
|
+
local interval_ms = tonumber(ARGV[3])
|
|
69
|
+
local bucket_size = tonumber(ARGV[4])
|
|
70
|
+
|
|
71
|
+
local tokens_s = redis.call('HGET', key, 'tokens')
|
|
72
|
+
local last_s = redis.call('HGET', key, 'last_refill')
|
|
73
|
+
|
|
74
|
+
local tokens
|
|
75
|
+
local last_refill
|
|
76
|
+
|
|
77
|
+
if tokens_s == false then
|
|
78
|
+
tokens = bucket_size
|
|
79
|
+
last_refill = now
|
|
80
|
+
else
|
|
81
|
+
tokens = tonumber(tokens_s)
|
|
82
|
+
last_refill = tonumber(last_s)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
local elapsed = now - last_refill
|
|
86
|
+
local intervals = math.floor(elapsed / interval_ms)
|
|
87
|
+
if intervals > 0 then
|
|
88
|
+
tokens = math.min(bucket_size, tokens + intervals * tokens_per_interval)
|
|
89
|
+
last_refill = last_refill + intervals * interval_ms
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
if tokens >= 1 then
|
|
93
|
+
tokens = tokens - 1
|
|
94
|
+
redis.call('HSET', key, 'tokens', tostring(tokens), 'last_refill', tostring(last_refill))
|
|
95
|
+
redis.call('PEXPIRE', key, interval_ms * 10)
|
|
96
|
+
local remaining = tokens
|
|
97
|
+
local total_hits = bucket_size - remaining
|
|
98
|
+
local next_tick = last_refill + interval_ms
|
|
99
|
+
return { 1, remaining, total_hits, 0, next_tick }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
redis.call('HSET', key, 'tokens', tostring(tokens), 'last_refill', tostring(last_refill))
|
|
103
|
+
redis.call('PEXPIRE', key, interval_ms * 10)
|
|
104
|
+
local next_refill = last_refill + interval_ms
|
|
105
|
+
return { 0, tokens, bucket_size, 1, next_refill }
|
|
106
|
+
`;
|
|
107
|
+
const LUA_BUCKET_DECR = `
|
|
108
|
+
local key = KEYS[1]
|
|
109
|
+
local bucket_size = tonumber(ARGV[1])
|
|
110
|
+
local tokens_s = redis.call('HGET', key, 'tokens')
|
|
111
|
+
local tokens
|
|
112
|
+
if tokens_s == false then
|
|
113
|
+
return 0
|
|
114
|
+
end
|
|
115
|
+
tokens = tonumber(tokens_s)
|
|
116
|
+
tokens = math.min(bucket_size, tokens + 1)
|
|
117
|
+
redis.call('HSET', key, 'tokens', tostring(tokens))
|
|
118
|
+
return 1
|
|
119
|
+
`;
|
|
120
|
+
const LUA_DEL = `
|
|
121
|
+
return redis.call('DEL', unpack(KEYS))
|
|
122
|
+
`;
|
|
123
|
+
/**
|
|
124
|
+
* Wrap an **ioredis** client instance to satisfy {@link RedisLikeClient} without adding a compile-time dependency.
|
|
125
|
+
*/
|
|
126
|
+
export function adaptIoRedisClient(client) {
|
|
127
|
+
return {
|
|
128
|
+
get: (k) => client.get(k),
|
|
129
|
+
set: (k, v, ...rest) => client.set(k, v, ...rest),
|
|
130
|
+
eval: (script, numKeys, ...rest) => client.eval(script, numKeys, ...rest.map((a) => String(a))),
|
|
131
|
+
del: client.del?.bind(client),
|
|
132
|
+
quit: client.quit?.bind(client),
|
|
133
|
+
disconnect: client.disconnect ? async () => await client.disconnect() : undefined,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Wrap **node-redis** v4+ clients (`eval(script, { keys, arguments })`).
|
|
138
|
+
* Does not add a `redis` package dependency — pass your connected client instance.
|
|
139
|
+
*/
|
|
140
|
+
export function adaptNodeRedisClient(client) {
|
|
141
|
+
return {
|
|
142
|
+
get: async (k) => (await client.get(k)) ?? null,
|
|
143
|
+
set: (k, v, ...rest) => client.set(k, v, ...rest),
|
|
144
|
+
eval: (script, numKeys, ...rest) => {
|
|
145
|
+
const keys = rest.slice(0, numKeys);
|
|
146
|
+
const args = rest.slice(numKeys);
|
|
147
|
+
return client.eval(script, { keys, arguments: args });
|
|
148
|
+
},
|
|
149
|
+
del: client.del?.bind(client),
|
|
150
|
+
quit: client.quit?.bind(client),
|
|
151
|
+
disconnect: client.disconnect ? async () => await client.disconnect() : undefined,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Redis-backed {@link RateLimitStore} using Lua for atomicity.
|
|
156
|
+
*
|
|
157
|
+
* Pass either `client` (recommended) or `url` (loads optional peer `ioredis` at runtime).
|
|
158
|
+
*/
|
|
159
|
+
export class RedisStore {
|
|
160
|
+
constructor(options) {
|
|
161
|
+
this.client = null;
|
|
162
|
+
/** Connection created from `url` — closed on {@link RedisStore.shutdown}. */
|
|
163
|
+
this.ownedRedis = null;
|
|
164
|
+
if (options.url && options.client) {
|
|
165
|
+
throw new Error('RedisStore: pass either "url" or "client", not both');
|
|
166
|
+
}
|
|
167
|
+
if (!options.url && !options.client) {
|
|
168
|
+
throw new Error('RedisStore: pass "url" or "client"');
|
|
169
|
+
}
|
|
170
|
+
this.strategy = options.strategy;
|
|
171
|
+
this.keyPrefix = options.keyPrefix ?? DEFAULT_PREFIX;
|
|
172
|
+
this.onWarn =
|
|
173
|
+
options.onWarn ?? ((msg, err) => console.warn(`[ratelimit-flex] ${msg}`, err ?? ''));
|
|
174
|
+
if (options.strategy === RateLimitStrategy.TOKEN_BUCKET) {
|
|
175
|
+
this.windowMs = 0;
|
|
176
|
+
this.maxRequests = 0;
|
|
177
|
+
this.tokensPerInterval = options.tokensPerInterval;
|
|
178
|
+
this.refillIntervalMs = options.interval;
|
|
179
|
+
this.bucketSize = options.bucketSize;
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
this.windowMs = options.windowMs;
|
|
183
|
+
this.maxRequests = options.maxRequests;
|
|
184
|
+
this.tokensPerInterval = 0;
|
|
185
|
+
this.refillIntervalMs = 0;
|
|
186
|
+
this.bucketSize = 0;
|
|
187
|
+
}
|
|
188
|
+
if (options.client) {
|
|
189
|
+
this.clientPromise = Promise.resolve(options.client);
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
this.clientPromise = this.connectFromUrl(options.url);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async connectFromUrl(url) {
|
|
196
|
+
try {
|
|
197
|
+
// @ts-expect-error - ioredis is an optional peer dependency
|
|
198
|
+
const mod = (await import('ioredis'));
|
|
199
|
+
const Redis = mod.default;
|
|
200
|
+
const raw = new Redis(url);
|
|
201
|
+
this.ownedRedis = raw;
|
|
202
|
+
return adaptIoRedisClient(raw);
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
this.onWarn('Failed to load optional peer "ioredis". Install it or pass a pre-configured "client".', err);
|
|
206
|
+
throw err;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
warn(message, error) {
|
|
210
|
+
this.onWarn(message, error);
|
|
211
|
+
}
|
|
212
|
+
async getClient() {
|
|
213
|
+
if (this.client) {
|
|
214
|
+
return this.client;
|
|
215
|
+
}
|
|
216
|
+
const c = await this.clientPromise;
|
|
217
|
+
this.client = c;
|
|
218
|
+
return c;
|
|
219
|
+
}
|
|
220
|
+
redisKey(kind, key) {
|
|
221
|
+
return `${this.keyPrefix}${kind}:${key}`;
|
|
222
|
+
}
|
|
223
|
+
async evalScript(script, keys, args) {
|
|
224
|
+
try {
|
|
225
|
+
const r = await this.getClient();
|
|
226
|
+
const flat = [...keys.map(String), ...args.map(String)];
|
|
227
|
+
return await r.eval(script, keys.length, ...flat);
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
this.warn('Redis EVAL failed', err);
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async delKeys(...keys) {
|
|
235
|
+
if (keys.length === 0) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
const r = await this.getClient();
|
|
240
|
+
if (r.del) {
|
|
241
|
+
await r.del(...keys);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
await r.eval(LUA_DEL, keys.length, ...keys.map(String));
|
|
245
|
+
}
|
|
246
|
+
catch (err) {
|
|
247
|
+
this.warn('Redis DEL failed', err);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
failOpenResult() {
|
|
251
|
+
const now = Date.now();
|
|
252
|
+
if (this.strategy === RateLimitStrategy.TOKEN_BUCKET) {
|
|
253
|
+
return {
|
|
254
|
+
totalHits: 0,
|
|
255
|
+
remaining: this.bucketSize,
|
|
256
|
+
resetTime: new Date(now + this.refillIntervalMs),
|
|
257
|
+
isBlocked: false,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
totalHits: 0,
|
|
262
|
+
remaining: this.maxRequests,
|
|
263
|
+
resetTime: new Date(now + this.windowMs),
|
|
264
|
+
isBlocked: false,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
/** @inheritdoc */
|
|
268
|
+
async increment(key) {
|
|
269
|
+
switch (this.strategy) {
|
|
270
|
+
case RateLimitStrategy.SLIDING_WINDOW:
|
|
271
|
+
return this.incrSliding(key);
|
|
272
|
+
case RateLimitStrategy.FIXED_WINDOW:
|
|
273
|
+
return this.incrFixed(key);
|
|
274
|
+
case RateLimitStrategy.TOKEN_BUCKET:
|
|
275
|
+
return this.incrBucket(key);
|
|
276
|
+
default: {
|
|
277
|
+
const _ = this.strategy;
|
|
278
|
+
return Promise.reject(new Error(`Unsupported strategy: ${String(_)}`));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
async incrSliding(key) {
|
|
283
|
+
const now = Date.now();
|
|
284
|
+
const member = `${now}:${Math.random().toString(36).slice(2)}`;
|
|
285
|
+
const rk = this.redisKey('sw', key);
|
|
286
|
+
const raw = await this.evalScript(LUA_SLIDING_INCR, [rk], [now, this.windowMs, this.maxRequests, member]);
|
|
287
|
+
if (raw === null || !Array.isArray(raw) || raw.length < 3) {
|
|
288
|
+
return this.failOpenResult();
|
|
289
|
+
}
|
|
290
|
+
const count = Number(raw[0]);
|
|
291
|
+
const blocked = Number(raw[1]) === 1;
|
|
292
|
+
const resetMs = Number(raw[2]);
|
|
293
|
+
if (Number.isNaN(count) || Number.isNaN(resetMs)) {
|
|
294
|
+
return this.failOpenResult();
|
|
295
|
+
}
|
|
296
|
+
const remaining = blocked ? 0 : Math.max(0, this.maxRequests - count);
|
|
297
|
+
return {
|
|
298
|
+
totalHits: count,
|
|
299
|
+
remaining,
|
|
300
|
+
resetTime: new Date(resetMs),
|
|
301
|
+
isBlocked: blocked,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
async incrFixed(key) {
|
|
305
|
+
const now = Date.now();
|
|
306
|
+
const rk = this.redisKey('fw', key);
|
|
307
|
+
const raw = await this.evalScript(LUA_FIXED_INCR, [rk], [this.windowMs, this.maxRequests, now]);
|
|
308
|
+
if (raw === null || !Array.isArray(raw) || raw.length < 3) {
|
|
309
|
+
return this.failOpenResult();
|
|
310
|
+
}
|
|
311
|
+
const current = Number(raw[0]);
|
|
312
|
+
const blocked = Number(raw[1]) === 1;
|
|
313
|
+
const resetMs = Number(raw[2]);
|
|
314
|
+
if (Number.isNaN(current) || Number.isNaN(resetMs)) {
|
|
315
|
+
return this.failOpenResult();
|
|
316
|
+
}
|
|
317
|
+
const remaining = blocked ? 0 : Math.max(0, this.maxRequests - current);
|
|
318
|
+
return {
|
|
319
|
+
totalHits: current,
|
|
320
|
+
remaining,
|
|
321
|
+
resetTime: new Date(resetMs),
|
|
322
|
+
isBlocked: blocked,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
async incrBucket(key) {
|
|
326
|
+
const now = Date.now();
|
|
327
|
+
const rk = this.redisKey('tb', key);
|
|
328
|
+
const raw = await this.evalScript(LUA_BUCKET_INCR, [rk], [now, this.tokensPerInterval, this.refillIntervalMs, this.bucketSize]);
|
|
329
|
+
if (raw === null || !Array.isArray(raw) || raw.length < 5) {
|
|
330
|
+
return this.failOpenResult();
|
|
331
|
+
}
|
|
332
|
+
const remaining = Number(raw[1]);
|
|
333
|
+
const totalHits = Number(raw[2]);
|
|
334
|
+
const blocked = Number(raw[3]) === 1;
|
|
335
|
+
const nextMs = Number(raw[4]);
|
|
336
|
+
if (Number.isNaN(remaining) || Number.isNaN(totalHits) || Number.isNaN(nextMs)) {
|
|
337
|
+
return this.failOpenResult();
|
|
338
|
+
}
|
|
339
|
+
return {
|
|
340
|
+
totalHits,
|
|
341
|
+
remaining,
|
|
342
|
+
resetTime: new Date(nextMs),
|
|
343
|
+
isBlocked: blocked,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
/** @inheritdoc */
|
|
347
|
+
async decrement(key) {
|
|
348
|
+
try {
|
|
349
|
+
switch (this.strategy) {
|
|
350
|
+
case RateLimitStrategy.SLIDING_WINDOW: {
|
|
351
|
+
const rk = this.redisKey('sw', key);
|
|
352
|
+
await this.evalScript(LUA_SLIDING_DECR, [rk], []);
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
case RateLimitStrategy.FIXED_WINDOW: {
|
|
356
|
+
const rk = this.redisKey('fw', key);
|
|
357
|
+
await this.evalScript(LUA_FIXED_DECR, [rk], []);
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
case RateLimitStrategy.TOKEN_BUCKET: {
|
|
361
|
+
const rk = this.redisKey('tb', key);
|
|
362
|
+
await this.evalScript(LUA_BUCKET_DECR, [rk], [this.bucketSize]);
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
default:
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
catch (err) {
|
|
370
|
+
this.warn('Redis decrement failed', err);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
/** @inheritdoc */
|
|
374
|
+
async reset(key) {
|
|
375
|
+
const keys = [];
|
|
376
|
+
switch (this.strategy) {
|
|
377
|
+
case RateLimitStrategy.SLIDING_WINDOW:
|
|
378
|
+
keys.push(this.redisKey('sw', key));
|
|
379
|
+
break;
|
|
380
|
+
case RateLimitStrategy.FIXED_WINDOW:
|
|
381
|
+
keys.push(this.redisKey('fw', key));
|
|
382
|
+
break;
|
|
383
|
+
case RateLimitStrategy.TOKEN_BUCKET:
|
|
384
|
+
keys.push(this.redisKey('tb', key));
|
|
385
|
+
break;
|
|
386
|
+
default:
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
await this.delKeys(...keys);
|
|
390
|
+
}
|
|
391
|
+
/** @inheritdoc */
|
|
392
|
+
async shutdown() {
|
|
393
|
+
this.client = null;
|
|
394
|
+
const owned = this.ownedRedis;
|
|
395
|
+
this.ownedRedis = null;
|
|
396
|
+
if (!owned) {
|
|
397
|
+
return Promise.resolve();
|
|
398
|
+
}
|
|
399
|
+
try {
|
|
400
|
+
if (typeof owned.quit === 'function') {
|
|
401
|
+
await owned.quit();
|
|
402
|
+
}
|
|
403
|
+
else if (typeof owned.disconnect === 'function') {
|
|
404
|
+
await owned.disconnect();
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
catch (err) {
|
|
408
|
+
this.warn('Redis shutdown (quit/disconnect) failed', err);
|
|
409
|
+
}
|
|
410
|
+
return Promise.resolve();
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
//# sourceMappingURL=redis-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-store.js","sourceRoot":"","sources":["../../src/stores/redis-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { RateLimitStrategy } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Sensible defaults for sliding-window rate limiting (no {@link RateLimitOptionsBase.store} — pass a store or use {@link createRateLimiter}).
|
|
4
|
+
*/
|
|
5
|
+
export declare const slidingWindowDefaults: {
|
|
6
|
+
readonly strategy: RateLimitStrategy.SLIDING_WINDOW;
|
|
7
|
+
readonly windowMs: 60000;
|
|
8
|
+
readonly maxRequests: 100;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Sensible defaults for fixed-window rate limiting.
|
|
12
|
+
*/
|
|
13
|
+
export declare const fixedWindowDefaults: {
|
|
14
|
+
readonly strategy: RateLimitStrategy.FIXED_WINDOW;
|
|
15
|
+
readonly windowMs: 60000;
|
|
16
|
+
readonly maxRequests: 100;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Sensible defaults for token-bucket rate limiting.
|
|
20
|
+
* Use {@link TokenBucketRateLimitOptions.bucketSize} for burst capacity (not `maxRequests`).
|
|
21
|
+
*/
|
|
22
|
+
export declare const tokenBucketDefaults: {
|
|
23
|
+
readonly strategy: RateLimitStrategy.TOKEN_BUCKET;
|
|
24
|
+
readonly tokensPerInterval: 10;
|
|
25
|
+
readonly interval: 60000;
|
|
26
|
+
readonly bucketSize: 100;
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=defaults.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/strategies/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEtD;;GAEG;AACH,eAAO,MAAM,qBAAqB;;;;CAIxB,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,mBAAmB;;;;CAItB,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,mBAAmB;;;;;CAKtB,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { RateLimitStrategy } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Sensible defaults for sliding-window rate limiting (no {@link RateLimitOptionsBase.store} — pass a store or use {@link createRateLimiter}).
|
|
4
|
+
*/
|
|
5
|
+
export const slidingWindowDefaults = {
|
|
6
|
+
strategy: RateLimitStrategy.SLIDING_WINDOW,
|
|
7
|
+
windowMs: 60000,
|
|
8
|
+
maxRequests: 100,
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Sensible defaults for fixed-window rate limiting.
|
|
12
|
+
*/
|
|
13
|
+
export const fixedWindowDefaults = {
|
|
14
|
+
strategy: RateLimitStrategy.FIXED_WINDOW,
|
|
15
|
+
windowMs: 60000,
|
|
16
|
+
maxRequests: 100,
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Sensible defaults for token-bucket rate limiting.
|
|
20
|
+
* Use {@link TokenBucketRateLimitOptions.bucketSize} for burst capacity (not `maxRequests`).
|
|
21
|
+
*/
|
|
22
|
+
export const tokenBucketDefaults = {
|
|
23
|
+
strategy: RateLimitStrategy.TOKEN_BUCKET,
|
|
24
|
+
tokensPerInterval: 10,
|
|
25
|
+
interval: 60000,
|
|
26
|
+
bucketSize: 100,
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=defaults.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../src/strategies/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEtD;;GAEG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,QAAQ,EAAE,iBAAiB,CAAC,cAAc;IAC1C,QAAQ,EAAE,KAAM;IAChB,WAAW,EAAE,GAAG;CACR,CAAC;AAEX;;GAEG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,QAAQ,EAAE,iBAAiB,CAAC,YAAY;IACxC,QAAQ,EAAE,KAAM;IAChB,WAAW,EAAE,GAAG;CACR,CAAC;AAEX;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,QAAQ,EAAE,iBAAiB,CAAC,YAAY;IACxC,iBAAiB,EAAE,EAAE;IACrB,QAAQ,EAAE,KAAM;IAChB,UAAU,EAAE,GAAG;CACP,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Rate limiting strategies (sliding window, token bucket, etc.) */
|
|
2
|
+
export { RateLimitEngine, createRateLimiter, defaultKeyGenerator, type RateLimitConsumeResult, type RateLimiterConfigInput, } from './rate-limit-engine.js';
|
|
3
|
+
export { fixedWindowDefaults, slidingWindowDefaults, tokenBucketDefaults } from './defaults.js';
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/strategies/index.ts"],"names":[],"mappings":"AAAA,oEAAoE;AAEpE,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,GAC5B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Rate limiting strategies (sliding window, token bucket, etc.) */
|
|
2
|
+
export { RateLimitEngine, createRateLimiter, defaultKeyGenerator, } from './rate-limit-engine.js';
|
|
3
|
+
export { fixedWindowDefaults, slidingWindowDefaults, tokenBucketDefaults } from './defaults.js';
|
|
4
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/strategies/index.ts"],"names":[],"mappings":"AAAA,oEAAoE;AAEpE,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,mBAAmB,GAGpB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { RateLimitOptions, RateLimitResult, RateLimitStore, TokenBucketRateLimitOptions, WindowRateLimitOptions } from '../types/index.js';
|
|
2
|
+
/** Result of {@link RateLimitEngine.consume} including standard rate-limit headers. */
|
|
3
|
+
export interface RateLimitConsumeResult extends RateLimitResult {
|
|
4
|
+
headers: Record<string, string>;
|
|
5
|
+
}
|
|
6
|
+
/** Options for {@link createRateLimiter}: `store` is optional — a {@link MemoryStore} is created when omitted. */
|
|
7
|
+
export type RateLimiterConfigInput = (Omit<WindowRateLimitOptions, 'store'> & {
|
|
8
|
+
store?: RateLimitStore;
|
|
9
|
+
}) | (Omit<TokenBucketRateLimitOptions, 'store'> & {
|
|
10
|
+
store?: RateLimitStore;
|
|
11
|
+
});
|
|
12
|
+
/**
|
|
13
|
+
* Default key extractor: uses `req.ip`, then `socket.remoteAddress`, else `"unknown"`.
|
|
14
|
+
* Strings are returned as-is so you can pass a precomputed key.
|
|
15
|
+
*/
|
|
16
|
+
export declare function defaultKeyGenerator(req: unknown): string;
|
|
17
|
+
/**
|
|
18
|
+
* Build a {@link RateLimitEngine} with optional in-memory store when `store` is omitted.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createRateLimiter(options: RateLimiterConfigInput): RateLimitEngine;
|
|
21
|
+
/**
|
|
22
|
+
* Orchestrates key extraction, store increments, header generation, and limit callbacks.
|
|
23
|
+
*/
|
|
24
|
+
export declare class RateLimitEngine {
|
|
25
|
+
private readonly options;
|
|
26
|
+
constructor(options: RateLimitOptions);
|
|
27
|
+
/**
|
|
28
|
+
* Applies rate limiting for an incoming request-like value.
|
|
29
|
+
* Uses {@link RateLimitOptionsBase.keyGenerator} (or {@link defaultKeyGenerator}) to derive the storage key.
|
|
30
|
+
*/
|
|
31
|
+
consume(req: unknown): Promise<RateLimitConsumeResult>;
|
|
32
|
+
/**
|
|
33
|
+
* Rate limit using a precomputed storage key (skips `keyGenerator`).
|
|
34
|
+
* Pass the same `req` for `onLimitReached` / `skip` callbacks when applicable.
|
|
35
|
+
*/
|
|
36
|
+
consumeWithKey(key: string, req?: unknown): Promise<RateLimitConsumeResult>;
|
|
37
|
+
private getLimit;
|
|
38
|
+
private buildPassthroughResult;
|
|
39
|
+
private buildHeaders;
|
|
40
|
+
private composeHeaders;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=rate-limit-engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit-engine.d.ts","sourceRoot":"","sources":["../../src/strategies/rate-limit-engine.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,EACf,cAAc,EACd,2BAA2B,EAC3B,sBAAsB,EACvB,MAAM,mBAAmB,CAAC;AAG3B,uFAAuF;AACvF,MAAM,WAAW,sBAAuB,SAAQ,eAAe;IAC7D,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED,kHAAkH;AAClH,MAAM,MAAM,sBAAsB,GAC9B,CAAC,IAAI,CAAC,sBAAsB,EAAE,OAAO,CAAC,GAAG;IAAE,KAAK,CAAC,EAAE,cAAc,CAAA;CAAE,CAAC,GACpE,CAAC,IAAI,CAAC,2BAA2B,EAAE,OAAO,CAAC,GAAG;IAAE,KAAK,CAAC,EAAE,cAAc,CAAA;CAAE,CAAC,CAAC;AAE9E;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAkBxD;AAuBD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,sBAAsB,GAAG,eAAe,CAElF;AAED;;GAEG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmB;gBAE/B,OAAO,EAAE,gBAAgB;IAIrC;;;OAGG;IACG,OAAO,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAK5D;;;OAGG;IACG,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,OAAa,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAetF,OAAO,CAAC,QAAQ;IAOhB,OAAO,CAAC,sBAAsB;IAe9B,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,cAAc;CAuBvB"}
|