@upstash/ratelimit 1.1.3 → 1.2.0-canary

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/dist/index.d.mts CHANGED
@@ -14,15 +14,23 @@ interface EphemeralCache {
14
14
  incr: (key: string) => number;
15
15
  pop: (key: string) => void;
16
16
  empty: () => void;
17
+ size: () => number;
17
18
  }
18
19
  type RegionContext = {
19
20
  redis: Redis;
20
21
  cache?: EphemeralCache;
22
+ scriptHashes: {
23
+ limitHash?: string;
24
+ getRemainingHash?: string;
25
+ resetHash?: string;
26
+ };
27
+ cacheScripts: boolean;
21
28
  };
22
29
  type MultiRegionContext = {
23
- redis: Redis[];
30
+ regionContexts: Omit<RegionContext[], "cache">;
24
31
  cache?: EphemeralCache;
25
32
  };
33
+ type RatelimitResponseType = "timeout" | "cacheBlock" | "denyList";
26
34
  type Context = RegionContext | MultiRegionContext;
27
35
  type RatelimitResponse = {
28
36
  /**
@@ -60,13 +68,37 @@ type RatelimitResponse = {
60
68
  * ```
61
69
  */
62
70
  pending: Promise<unknown>;
71
+ /**
72
+ * Reason behind the result in `success` field.
73
+ * - Is set to "timeout" when request times out
74
+ * - Is set to "cacheBlock" when an identifier is blocked through cache without calling redis because it was
75
+ * rate limited previously.
76
+ * - Is set to "denyList" when identifier or one of ip/user-agent/country parameters is in deny list. To enable
77
+ * deny list, see `enableProtection` parameter. To edit the deny list, see the Upstash Ratelimit Dashboard
78
+ * at https://console.upstash.com/ratelimit.
79
+ * - Is set to undefined if rate limit check had to use Redis. This happens in cases when `success` field in
80
+ * the response is true. It can also happen the first time sucecss is false.
81
+ */
82
+ reason?: RatelimitResponseType;
83
+ /**
84
+ * The value which was in the deny list if reason: "denyList"
85
+ */
86
+ deniedValue?: string;
63
87
  };
64
88
  type Algorithm<TContext> = () => {
65
89
  limit: (ctx: TContext, identifier: string, rate?: number, opts?: {
66
90
  cache?: EphemeralCache;
67
91
  }) => Promise<RatelimitResponse>;
68
92
  getRemaining: (ctx: TContext, identifier: string) => Promise<number>;
69
- resetTokens: (ctx: TContext, identifier: string) => void;
93
+ resetTokens: (ctx: TContext, identifier: string) => Promise<void>;
94
+ };
95
+ type IsDenied = 0 | 1;
96
+ type LimitOptions = {
97
+ geo?: Geo;
98
+ rate?: number;
99
+ ip?: string;
100
+ userAgent?: string;
101
+ country?: string;
70
102
  };
71
103
  /**
72
104
  * This is all we need from the redis sdk.
@@ -77,6 +109,9 @@ interface Redis {
77
109
  [key: string]: TValue;
78
110
  }) => Promise<number>;
79
111
  eval: <TArgs extends unknown[], TData = unknown>(...args: [script: string, keys: string[], args: TArgs]) => Promise<TData>;
112
+ evalsha: <TArgs extends unknown[], TData = unknown>(...args: [sha1: string, keys: string[], args: TArgs]) => Promise<TData>;
113
+ scriptLoad: (...args: [script: string]) => Promise<string>;
114
+ smismember: (key: string, members: string[]) => Promise<IsDenied[]>;
80
115
  }
81
116
 
82
117
  type Geo = {
@@ -85,10 +120,16 @@ type Geo = {
85
120
  region?: string;
86
121
  ip?: string;
87
122
  };
123
+ /**
124
+ * denotes the success field in the analytics submission.
125
+ * Set to true when ratelimit check passes. False when request is ratelimited.
126
+ * Set to "denied" when some request value is in deny list.
127
+ */
128
+ type EventSuccess = boolean | "denied";
88
129
  type Event = Geo & {
89
130
  identifier: string;
90
131
  time: number;
91
- success: boolean;
132
+ success: EventSuccess;
92
133
  };
93
134
  type AnalyticsConfig = {
94
135
  redis: Redis;
@@ -124,7 +165,11 @@ declare class Analytics {
124
165
  identifier: string;
125
166
  count: number;
126
167
  }[];
127
- blocked: {
168
+ ratelimited: {
169
+ identifier: string;
170
+ count: number;
171
+ }[];
172
+ denied: {
128
173
  identifier: string;
129
174
  count: number;
130
175
  }[];
@@ -184,6 +229,14 @@ type RatelimitConfig<TContext> = {
184
229
  * @default false
185
230
  */
186
231
  analytics?: boolean;
232
+ /**
233
+ * Enables deny list. If set to true, requests with identifier or ip/user-agent/countrie
234
+ * in the deny list will be rejected automatically. To edit the deny list, check out the
235
+ * ratelimit dashboard at https://console.upstash.com/ratelimit
236
+ *
237
+ * @default false
238
+ */
239
+ enableProtection?: boolean;
187
240
  };
188
241
  /**
189
242
  * Ratelimiter using serverless redis from https://upstash.com/
@@ -205,7 +258,9 @@ declare abstract class Ratelimit<TContext extends Context> {
205
258
  protected readonly ctx: TContext;
206
259
  protected readonly prefix: string;
207
260
  protected readonly timeout: number;
261
+ protected readonly primaryRedis: Redis;
208
262
  protected readonly analytics?: Analytics;
263
+ protected readonly enableProtection: boolean;
209
264
  constructor(config: RatelimitConfig<TContext>);
210
265
  /**
211
266
  * Determine if a request should pass or be rejected based on the identifier and previously chosen ratelimit.
@@ -243,10 +298,7 @@ declare abstract class Ratelimit<TContext extends Context> {
243
298
  * return "Yes"
244
299
  * ```
245
300
  */
246
- limit: (identifier: string, req?: {
247
- geo?: Geo;
248
- rate?: number;
249
- }) => Promise<RatelimitResponse>;
301
+ limit: (identifier: string, req?: LimitOptions) => Promise<RatelimitResponse>;
250
302
  /**
251
303
  * Block until the request may pass or timeout is reached.
252
304
  *
@@ -272,6 +324,46 @@ declare abstract class Ratelimit<TContext extends Context> {
272
324
  blockUntilReady: (identifier: string, timeout: number) => Promise<RatelimitResponse>;
273
325
  resetUsedTokens: (identifier: string) => Promise<void>;
274
326
  getRemaining: (identifier: string) => Promise<number>;
327
+ /**
328
+ * Checks if the identifier or the values in req are in the deny list cache.
329
+ * If so, returns the default denied response.
330
+ *
331
+ * Otherwise, calls redis to check the rate limit and deny list. Returns after
332
+ * resolving the result. Resolving is overriding the rate limit result if
333
+ * the some value is in deny list.
334
+ *
335
+ * @param identifier identifier to block
336
+ * @param req options with ip, user agent, country, rate and geo info
337
+ * @returns rate limit response
338
+ */
339
+ private getRatelimitResponse;
340
+ /**
341
+ * Creates an array with the original response promise and a timeout promise
342
+ * if this.timeout > 0.
343
+ *
344
+ * @param response Ratelimit response promise
345
+ * @returns array with the response and timeout promise. also includes the timeout id
346
+ */
347
+ private applyTimeout;
348
+ /**
349
+ * submits analytics if this.analytics is set
350
+ *
351
+ * @param ratelimitResponse final rate limit response
352
+ * @param identifier identifier to submit
353
+ * @param req limit options
354
+ * @returns rate limit response after updating the .pending field
355
+ */
356
+ private submitAnalytics;
357
+ private getKey;
358
+ /**
359
+ * returns a list of defined values from
360
+ * [identifier, req.ip, req.userAgent, req.country]
361
+ *
362
+ * @param identifier identifier
363
+ * @param req limit options
364
+ * @returns list of defined values
365
+ */
366
+ private getDefinedMembers;
275
367
  }
276
368
 
277
369
  type MultiRegionRatelimitConfig = {
@@ -324,6 +416,13 @@ type MultiRegionRatelimitConfig = {
324
416
  * @default false
325
417
  */
326
418
  analytics?: boolean;
419
+ /**
420
+ * If enabled, lua scripts will be sent to Redis with SCRIPT LOAD durint the first request.
421
+ * In the subsequent requests, hash of the script will be used to invoke it
422
+ *
423
+ * @default true
424
+ */
425
+ cacheScripts?: boolean;
327
426
  };
328
427
  /**
329
428
  * Ratelimiter using serverless redis from https://upstash.com/
@@ -451,6 +550,17 @@ type RegionRatelimitConfig = {
451
550
  * @default false
452
551
  */
453
552
  analytics?: boolean;
553
+ /**
554
+ * If enabled, lua scripts will be sent to Redis with SCRIPT LOAD durint the first request.
555
+ * In the subsequent requests, hash of the script will be used to invoke it
556
+ *
557
+ * @default true
558
+ */
559
+ cacheScripts?: boolean;
560
+ /**
561
+ * @default false
562
+ */
563
+ enableProtection?: boolean;
454
564
  };
455
565
  /**
456
566
  * Ratelimiter using serverless redis from https://upstash.com/
package/dist/index.d.ts CHANGED
@@ -14,15 +14,23 @@ interface EphemeralCache {
14
14
  incr: (key: string) => number;
15
15
  pop: (key: string) => void;
16
16
  empty: () => void;
17
+ size: () => number;
17
18
  }
18
19
  type RegionContext = {
19
20
  redis: Redis;
20
21
  cache?: EphemeralCache;
22
+ scriptHashes: {
23
+ limitHash?: string;
24
+ getRemainingHash?: string;
25
+ resetHash?: string;
26
+ };
27
+ cacheScripts: boolean;
21
28
  };
22
29
  type MultiRegionContext = {
23
- redis: Redis[];
30
+ regionContexts: Omit<RegionContext[], "cache">;
24
31
  cache?: EphemeralCache;
25
32
  };
33
+ type RatelimitResponseType = "timeout" | "cacheBlock" | "denyList";
26
34
  type Context = RegionContext | MultiRegionContext;
27
35
  type RatelimitResponse = {
28
36
  /**
@@ -60,13 +68,37 @@ type RatelimitResponse = {
60
68
  * ```
61
69
  */
62
70
  pending: Promise<unknown>;
71
+ /**
72
+ * Reason behind the result in `success` field.
73
+ * - Is set to "timeout" when request times out
74
+ * - Is set to "cacheBlock" when an identifier is blocked through cache without calling redis because it was
75
+ * rate limited previously.
76
+ * - Is set to "denyList" when identifier or one of ip/user-agent/country parameters is in deny list. To enable
77
+ * deny list, see `enableProtection` parameter. To edit the deny list, see the Upstash Ratelimit Dashboard
78
+ * at https://console.upstash.com/ratelimit.
79
+ * - Is set to undefined if rate limit check had to use Redis. This happens in cases when `success` field in
80
+ * the response is true. It can also happen the first time sucecss is false.
81
+ */
82
+ reason?: RatelimitResponseType;
83
+ /**
84
+ * The value which was in the deny list if reason: "denyList"
85
+ */
86
+ deniedValue?: string;
63
87
  };
64
88
  type Algorithm<TContext> = () => {
65
89
  limit: (ctx: TContext, identifier: string, rate?: number, opts?: {
66
90
  cache?: EphemeralCache;
67
91
  }) => Promise<RatelimitResponse>;
68
92
  getRemaining: (ctx: TContext, identifier: string) => Promise<number>;
69
- resetTokens: (ctx: TContext, identifier: string) => void;
93
+ resetTokens: (ctx: TContext, identifier: string) => Promise<void>;
94
+ };
95
+ type IsDenied = 0 | 1;
96
+ type LimitOptions = {
97
+ geo?: Geo;
98
+ rate?: number;
99
+ ip?: string;
100
+ userAgent?: string;
101
+ country?: string;
70
102
  };
71
103
  /**
72
104
  * This is all we need from the redis sdk.
@@ -77,6 +109,9 @@ interface Redis {
77
109
  [key: string]: TValue;
78
110
  }) => Promise<number>;
79
111
  eval: <TArgs extends unknown[], TData = unknown>(...args: [script: string, keys: string[], args: TArgs]) => Promise<TData>;
112
+ evalsha: <TArgs extends unknown[], TData = unknown>(...args: [sha1: string, keys: string[], args: TArgs]) => Promise<TData>;
113
+ scriptLoad: (...args: [script: string]) => Promise<string>;
114
+ smismember: (key: string, members: string[]) => Promise<IsDenied[]>;
80
115
  }
81
116
 
82
117
  type Geo = {
@@ -85,10 +120,16 @@ type Geo = {
85
120
  region?: string;
86
121
  ip?: string;
87
122
  };
123
+ /**
124
+ * denotes the success field in the analytics submission.
125
+ * Set to true when ratelimit check passes. False when request is ratelimited.
126
+ * Set to "denied" when some request value is in deny list.
127
+ */
128
+ type EventSuccess = boolean | "denied";
88
129
  type Event = Geo & {
89
130
  identifier: string;
90
131
  time: number;
91
- success: boolean;
132
+ success: EventSuccess;
92
133
  };
93
134
  type AnalyticsConfig = {
94
135
  redis: Redis;
@@ -124,7 +165,11 @@ declare class Analytics {
124
165
  identifier: string;
125
166
  count: number;
126
167
  }[];
127
- blocked: {
168
+ ratelimited: {
169
+ identifier: string;
170
+ count: number;
171
+ }[];
172
+ denied: {
128
173
  identifier: string;
129
174
  count: number;
130
175
  }[];
@@ -184,6 +229,14 @@ type RatelimitConfig<TContext> = {
184
229
  * @default false
185
230
  */
186
231
  analytics?: boolean;
232
+ /**
233
+ * Enables deny list. If set to true, requests with identifier or ip/user-agent/countrie
234
+ * in the deny list will be rejected automatically. To edit the deny list, check out the
235
+ * ratelimit dashboard at https://console.upstash.com/ratelimit
236
+ *
237
+ * @default false
238
+ */
239
+ enableProtection?: boolean;
187
240
  };
188
241
  /**
189
242
  * Ratelimiter using serverless redis from https://upstash.com/
@@ -205,7 +258,9 @@ declare abstract class Ratelimit<TContext extends Context> {
205
258
  protected readonly ctx: TContext;
206
259
  protected readonly prefix: string;
207
260
  protected readonly timeout: number;
261
+ protected readonly primaryRedis: Redis;
208
262
  protected readonly analytics?: Analytics;
263
+ protected readonly enableProtection: boolean;
209
264
  constructor(config: RatelimitConfig<TContext>);
210
265
  /**
211
266
  * Determine if a request should pass or be rejected based on the identifier and previously chosen ratelimit.
@@ -243,10 +298,7 @@ declare abstract class Ratelimit<TContext extends Context> {
243
298
  * return "Yes"
244
299
  * ```
245
300
  */
246
- limit: (identifier: string, req?: {
247
- geo?: Geo;
248
- rate?: number;
249
- }) => Promise<RatelimitResponse>;
301
+ limit: (identifier: string, req?: LimitOptions) => Promise<RatelimitResponse>;
250
302
  /**
251
303
  * Block until the request may pass or timeout is reached.
252
304
  *
@@ -272,6 +324,46 @@ declare abstract class Ratelimit<TContext extends Context> {
272
324
  blockUntilReady: (identifier: string, timeout: number) => Promise<RatelimitResponse>;
273
325
  resetUsedTokens: (identifier: string) => Promise<void>;
274
326
  getRemaining: (identifier: string) => Promise<number>;
327
+ /**
328
+ * Checks if the identifier or the values in req are in the deny list cache.
329
+ * If so, returns the default denied response.
330
+ *
331
+ * Otherwise, calls redis to check the rate limit and deny list. Returns after
332
+ * resolving the result. Resolving is overriding the rate limit result if
333
+ * the some value is in deny list.
334
+ *
335
+ * @param identifier identifier to block
336
+ * @param req options with ip, user agent, country, rate and geo info
337
+ * @returns rate limit response
338
+ */
339
+ private getRatelimitResponse;
340
+ /**
341
+ * Creates an array with the original response promise and a timeout promise
342
+ * if this.timeout > 0.
343
+ *
344
+ * @param response Ratelimit response promise
345
+ * @returns array with the response and timeout promise. also includes the timeout id
346
+ */
347
+ private applyTimeout;
348
+ /**
349
+ * submits analytics if this.analytics is set
350
+ *
351
+ * @param ratelimitResponse final rate limit response
352
+ * @param identifier identifier to submit
353
+ * @param req limit options
354
+ * @returns rate limit response after updating the .pending field
355
+ */
356
+ private submitAnalytics;
357
+ private getKey;
358
+ /**
359
+ * returns a list of defined values from
360
+ * [identifier, req.ip, req.userAgent, req.country]
361
+ *
362
+ * @param identifier identifier
363
+ * @param req limit options
364
+ * @returns list of defined values
365
+ */
366
+ private getDefinedMembers;
275
367
  }
276
368
 
277
369
  type MultiRegionRatelimitConfig = {
@@ -324,6 +416,13 @@ type MultiRegionRatelimitConfig = {
324
416
  * @default false
325
417
  */
326
418
  analytics?: boolean;
419
+ /**
420
+ * If enabled, lua scripts will be sent to Redis with SCRIPT LOAD durint the first request.
421
+ * In the subsequent requests, hash of the script will be used to invoke it
422
+ *
423
+ * @default true
424
+ */
425
+ cacheScripts?: boolean;
327
426
  };
328
427
  /**
329
428
  * Ratelimiter using serverless redis from https://upstash.com/
@@ -451,6 +550,17 @@ type RegionRatelimitConfig = {
451
550
  * @default false
452
551
  */
453
552
  analytics?: boolean;
553
+ /**
554
+ * If enabled, lua scripts will be sent to Redis with SCRIPT LOAD durint the first request.
555
+ * In the subsequent requests, hash of the script will be used to invoke it
556
+ *
557
+ * @default true
558
+ */
559
+ cacheScripts?: boolean;
560
+ /**
561
+ * @default false
562
+ */
563
+ enableProtection?: boolean;
454
564
  };
455
565
  /**
456
566
  * Ratelimiter using serverless redis from https://upstash.com/