@upstash/ratelimit 0.1.0-alpha.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -4
- package/esm/global.js +228 -0
- package/esm/mod.js +2 -2
- package/esm/ratelimit.js +129 -0
- package/esm/{ratelimiter.js → region.js} +6 -158
- package/package.json +4 -4
- package/script/global.js +232 -0
- package/script/mod.js +5 -16
- package/script/ratelimit.js +133 -0
- package/script/{ratelimiter.js → region.js} +8 -160
- package/types/global.d.ts +98 -0
- package/types/mod.d.ts +5 -2
- package/types/ratelimit.d.ts +85 -0
- package/types/{ratelimiter.d.ts → region.d.ts} +10 -54
- package/types/types.d.ts +10 -5
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { Duration } from "./duration.js";
|
|
2
|
+
import type { Algorithm, GlobalContext } from "./types.js";
|
|
3
|
+
import { Ratelimit } from "./ratelimit.js";
|
|
4
|
+
import type { Redis } from "./types.js";
|
|
5
|
+
export declare type GlobalRatelimitConfig = {
|
|
6
|
+
/**
|
|
7
|
+
* Instances of `@upstash/redis`
|
|
8
|
+
* @see https://github.com/upstash/upstash-redis#quick-start
|
|
9
|
+
*/
|
|
10
|
+
redis: Redis[];
|
|
11
|
+
/**
|
|
12
|
+
* The ratelimiter function to use.
|
|
13
|
+
*
|
|
14
|
+
* Choose one of the predefined ones or implement your own.
|
|
15
|
+
* Available algorithms are exposed via static methods:
|
|
16
|
+
* - GlobalRatelimit.fixedWindow
|
|
17
|
+
*/
|
|
18
|
+
limiter: Algorithm<GlobalContext>;
|
|
19
|
+
/**
|
|
20
|
+
* All keys in redis are prefixed with this.
|
|
21
|
+
*
|
|
22
|
+
* @default `@upstash/ratelimit`
|
|
23
|
+
*/
|
|
24
|
+
prefix?: string;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Ratelimiter using serverless redis from https://upstash.com/
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* const { limit } = new GlobalRatelimit({
|
|
32
|
+
* redis: Redis.fromEnv(),
|
|
33
|
+
* limiter: GlobalRatelimit.fixedWindow(
|
|
34
|
+
* 10, // Allow 10 requests per window of 30 minutes
|
|
35
|
+
* "30 m", // interval of 30 minutes
|
|
36
|
+
* )
|
|
37
|
+
* })
|
|
38
|
+
*
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export declare class GlobalRatelimit extends Ratelimit<GlobalContext> {
|
|
42
|
+
/**
|
|
43
|
+
* Create a new Ratelimit instance by providing a `@upstash/redis` instance and the algorithn of your choice.
|
|
44
|
+
*/
|
|
45
|
+
constructor(config: GlobalRatelimitConfig);
|
|
46
|
+
/**
|
|
47
|
+
* Each requests inside a fixed time increases a counter.
|
|
48
|
+
* Once the counter reaches a maxmimum allowed number, all further requests are
|
|
49
|
+
* rejected.
|
|
50
|
+
*
|
|
51
|
+
* **Pro:**
|
|
52
|
+
*
|
|
53
|
+
* - Newer requests are not starved by old ones.
|
|
54
|
+
* - Low storage cost.
|
|
55
|
+
*
|
|
56
|
+
* **Con:**
|
|
57
|
+
*
|
|
58
|
+
* A burst of requests near the boundary of a window can result in a very
|
|
59
|
+
* high request rate because two windows will be filled with requests quickly.
|
|
60
|
+
*
|
|
61
|
+
* @param tokens - How many requests a user can make in each time window.
|
|
62
|
+
* @param window - A fixed timeframe
|
|
63
|
+
*/
|
|
64
|
+
static fixedWindow(
|
|
65
|
+
/**
|
|
66
|
+
* How many requests are allowed per window.
|
|
67
|
+
*/
|
|
68
|
+
tokens: number,
|
|
69
|
+
/**
|
|
70
|
+
* The duration in which `tokens` requests are allowed.
|
|
71
|
+
*/
|
|
72
|
+
window: Duration): Algorithm<GlobalContext>;
|
|
73
|
+
/**
|
|
74
|
+
* Combined approach of `slidingLogs` and `fixedWindow` with lower storage
|
|
75
|
+
* costs than `slidingLogs` and improved boundary behavior by calcualting a
|
|
76
|
+
* weighted score between two windows.
|
|
77
|
+
*
|
|
78
|
+
* **Pro:**
|
|
79
|
+
*
|
|
80
|
+
* Good performance allows this to scale to very high loads.
|
|
81
|
+
*
|
|
82
|
+
* **Con:**
|
|
83
|
+
*
|
|
84
|
+
* Nothing major.
|
|
85
|
+
*
|
|
86
|
+
* @param tokens - How many requests a user can make in each time window.
|
|
87
|
+
* @param window - The duration in which the user can max X requests.
|
|
88
|
+
*/
|
|
89
|
+
static slidingWindow(
|
|
90
|
+
/**
|
|
91
|
+
* How many requests are allowed per window.
|
|
92
|
+
*/
|
|
93
|
+
tokens: number,
|
|
94
|
+
/**
|
|
95
|
+
* The duration in which `tokens` requests are allowed.
|
|
96
|
+
*/
|
|
97
|
+
window: Duration): Algorithm<GlobalContext>;
|
|
98
|
+
}
|
package/types/mod.d.ts
CHANGED
|
@@ -1,2 +1,5 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
1
|
+
export { RegionRatelimit as Ratelimit } from "./region.js";
|
|
2
|
+
export type { RegionRatelimitConfig as RatelimitConfig } from "./region.js";
|
|
3
|
+
export { GlobalRatelimit } from "./global.js";
|
|
4
|
+
export type { GlobalRatelimitConfig } from "./global.js";
|
|
5
|
+
export type { Algorithm } from "./types.js";
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { Algorithm, Context, RatelimitResponse } from "./types.js";
|
|
2
|
+
export declare type RatelimitConfig<TContext> = {
|
|
3
|
+
/**
|
|
4
|
+
* The ratelimiter function to use.
|
|
5
|
+
*
|
|
6
|
+
* Choose one of the predefined ones or implement your own.
|
|
7
|
+
* Available algorithms are exposed via static methods:
|
|
8
|
+
* - Ratelimiter.fixedWindow
|
|
9
|
+
* - Ratelimiter.slidingLogs
|
|
10
|
+
* - Ratelimiter.slidingWindow
|
|
11
|
+
* - Ratelimiter.tokenBucket
|
|
12
|
+
*/
|
|
13
|
+
limiter: Algorithm<TContext>;
|
|
14
|
+
ctx: TContext;
|
|
15
|
+
/**
|
|
16
|
+
* All keys in redis are prefixed with this.
|
|
17
|
+
*
|
|
18
|
+
* @default `@upstash/ratelimit`
|
|
19
|
+
*/
|
|
20
|
+
prefix?: string;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Ratelimiter using serverless redis from https://upstash.com/
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* const { limit } = new Ratelimit({
|
|
28
|
+
* redis: Redis.fromEnv(),
|
|
29
|
+
* limiter: Ratelimit.slidingWindow(
|
|
30
|
+
* 10, // Allow 10 requests per window of 30 minutes
|
|
31
|
+
* "30 m", // interval of 30 minutes
|
|
32
|
+
* )
|
|
33
|
+
* })
|
|
34
|
+
*
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare abstract class Ratelimit<TContext extends Context> {
|
|
38
|
+
protected readonly limiter: Algorithm<TContext>;
|
|
39
|
+
protected readonly ctx: TContext;
|
|
40
|
+
protected readonly prefix: string;
|
|
41
|
+
constructor(config: RatelimitConfig<TContext>);
|
|
42
|
+
/**
|
|
43
|
+
* Determine if a request should pass or be rejected based on the identifier and previously chosen ratelimit.
|
|
44
|
+
*
|
|
45
|
+
* Use this if you want to reject all requests that you can not handle right now.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* const ratelimit = new Ratelimit({
|
|
50
|
+
* redis: Redis.fromEnv(),
|
|
51
|
+
* limiter: Ratelimit.slidingWindow(10, "10 s")
|
|
52
|
+
* })
|
|
53
|
+
*
|
|
54
|
+
* const { success } = await ratelimit.limit(id)
|
|
55
|
+
* if (!success){
|
|
56
|
+
* return "Nope"
|
|
57
|
+
* }
|
|
58
|
+
* return "Yes"
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
limit: (identifier: string) => Promise<RatelimitResponse>;
|
|
62
|
+
/**
|
|
63
|
+
* Block until the request may pass or timeout is reached.
|
|
64
|
+
*
|
|
65
|
+
* This method returns a promsie that resolves as soon as the request may be processed
|
|
66
|
+
* or after the timeoue has been reached.
|
|
67
|
+
*
|
|
68
|
+
* Use this if you want to delay the request until it is ready to get processed.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts
|
|
72
|
+
* const ratelimit = new Ratelimit({
|
|
73
|
+
* redis: Redis.fromEnv(),
|
|
74
|
+
* limiter: Ratelimit.slidingWindow(10, "10 s")
|
|
75
|
+
* })
|
|
76
|
+
*
|
|
77
|
+
* const { success } = await ratelimit.blockUntilReady(id, 60_000)
|
|
78
|
+
* if (!success){
|
|
79
|
+
* return "Nope"
|
|
80
|
+
* }
|
|
81
|
+
* return "Yes"
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
blockUntilReady: (identifier: string, timeout: number) => Promise<RatelimitResponse>;
|
|
85
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Duration } from "./duration.js";
|
|
2
|
-
import type {
|
|
3
|
-
|
|
2
|
+
import type { Algorithm, RegionContext } from "./types.js";
|
|
3
|
+
import type { Redis } from "./types.js";
|
|
4
|
+
import { Ratelimit } from "./ratelimit.js";
|
|
5
|
+
export declare type RegionRatelimitConfig = {
|
|
4
6
|
/**
|
|
5
7
|
* Instance of `@upstash/redis`
|
|
6
8
|
* @see https://github.com/upstash/upstash-redis#quick-start
|
|
@@ -16,7 +18,7 @@ export declare type RatelimitConfig = {
|
|
|
16
18
|
* - Ratelimiter.slidingWindow
|
|
17
19
|
* - Ratelimiter.tokenBucket
|
|
18
20
|
*/
|
|
19
|
-
limiter:
|
|
21
|
+
limiter: Algorithm<RegionContext>;
|
|
20
22
|
/**
|
|
21
23
|
* All keys in redis are prefixed with this.
|
|
22
24
|
*
|
|
@@ -39,57 +41,11 @@ export declare type RatelimitConfig = {
|
|
|
39
41
|
*
|
|
40
42
|
* ```
|
|
41
43
|
*/
|
|
42
|
-
export declare class Ratelimit {
|
|
43
|
-
private readonly redis;
|
|
44
|
-
private readonly limiter;
|
|
45
|
-
private readonly prefix;
|
|
44
|
+
export declare class RegionRatelimit extends Ratelimit<RegionContext> {
|
|
46
45
|
/**
|
|
47
46
|
* Create a new Ratelimit instance by providing a `@upstash/redis` instance and the algorithn of your choice.
|
|
48
47
|
*/
|
|
49
|
-
constructor(config:
|
|
50
|
-
/**
|
|
51
|
-
* Determine if a request should pass or be rejected based on the identifier and previously chosen ratelimit.
|
|
52
|
-
*
|
|
53
|
-
* Use this if you want to reject all requests that you can not handle right now.
|
|
54
|
-
*
|
|
55
|
-
* @example
|
|
56
|
-
* ```ts
|
|
57
|
-
* const ratelimit = new Ratelimit({
|
|
58
|
-
* redis: Redis.fromEnv(),
|
|
59
|
-
* limiter: Ratelimit.slidingWindow(10, "10 s")
|
|
60
|
-
* })
|
|
61
|
-
*
|
|
62
|
-
* const { success } = await ratelimit.limit(id)
|
|
63
|
-
* if (!success){
|
|
64
|
-
* return "Nope"
|
|
65
|
-
* }
|
|
66
|
-
* return "Yes"
|
|
67
|
-
* ```
|
|
68
|
-
*/
|
|
69
|
-
limit: (identifier: string) => Promise<RatelimitResponse>;
|
|
70
|
-
/**
|
|
71
|
-
* Block until the request may pass or timeout is reached.
|
|
72
|
-
*
|
|
73
|
-
* This method returns a promsie that resolves as soon as the request may be processed
|
|
74
|
-
* or after the timeoue has been reached.
|
|
75
|
-
*
|
|
76
|
-
* Use this if you want to delay the request until it is ready to get processed.
|
|
77
|
-
*
|
|
78
|
-
* @example
|
|
79
|
-
* ```ts
|
|
80
|
-
* const ratelimit = new Ratelimit({
|
|
81
|
-
* redis: Redis.fromEnv(),
|
|
82
|
-
* limiter: Ratelimit.slidingWindow(10, "10 s")
|
|
83
|
-
* })
|
|
84
|
-
*
|
|
85
|
-
* const { success } = await ratelimit.blockUntilReady(id, 60_000)
|
|
86
|
-
* if (!success){
|
|
87
|
-
* return "Nope"
|
|
88
|
-
* }
|
|
89
|
-
* return "Yes"
|
|
90
|
-
* ```
|
|
91
|
-
*/
|
|
92
|
-
blockUntilReady: (identifier: string, timeout: number) => Promise<RatelimitResponse>;
|
|
48
|
+
constructor(config: RegionRatelimitConfig);
|
|
93
49
|
/**
|
|
94
50
|
* Each requests inside a fixed time increases a counter.
|
|
95
51
|
* Once the counter reaches a maxmimum allowed number, all further requests are
|
|
@@ -116,7 +72,7 @@ export declare class Ratelimit {
|
|
|
116
72
|
/**
|
|
117
73
|
* The duration in which `tokens` requests are allowed.
|
|
118
74
|
*/
|
|
119
|
-
window: Duration):
|
|
75
|
+
window: Duration): Algorithm<RegionContext>;
|
|
120
76
|
/**
|
|
121
77
|
* Combined approach of `slidingLogs` and `fixedWindow` with lower storage
|
|
122
78
|
* costs than `slidingLogs` and improved boundary behavior by calcualting a
|
|
@@ -141,7 +97,7 @@ export declare class Ratelimit {
|
|
|
141
97
|
/**
|
|
142
98
|
* The duration in which `tokens` requests are allowed.
|
|
143
99
|
*/
|
|
144
|
-
window: Duration):
|
|
100
|
+
window: Duration): Algorithm<RegionContext>;
|
|
145
101
|
/**
|
|
146
102
|
* You have a bucket filled with `{maxTokens}` tokens that refills constantly
|
|
147
103
|
* at `{refillRate}` per `{interval}`.
|
|
@@ -173,5 +129,5 @@ export declare class Ratelimit {
|
|
|
173
129
|
* A newly created bucket starts with this many tokens.
|
|
174
130
|
* Useful to allow higher burst limits.
|
|
175
131
|
*/
|
|
176
|
-
maxTokens: number):
|
|
132
|
+
maxTokens: number): Algorithm<RegionContext>;
|
|
177
133
|
}
|
package/types/types.d.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
export
|
|
2
|
-
eval:
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
export interface Redis {
|
|
2
|
+
eval: (script: string, keys: string[], values: unknown[]) => Promise<unknown>;
|
|
3
|
+
sadd: (key: string, ...members: string[]) => Promise<number>;
|
|
4
|
+
}
|
|
5
|
+
export declare type RegionContext = {
|
|
5
6
|
redis: Redis;
|
|
6
7
|
};
|
|
8
|
+
export declare type GlobalContext = {
|
|
9
|
+
redis: Redis[];
|
|
10
|
+
};
|
|
11
|
+
export declare type Context = RegionContext | GlobalContext;
|
|
7
12
|
export declare type RatelimitResponse = {
|
|
8
13
|
/**
|
|
9
14
|
* Whether the request may pass(true) or exceeded the limit(false)
|
|
@@ -22,4 +27,4 @@ export declare type RatelimitResponse = {
|
|
|
22
27
|
*/
|
|
23
28
|
reset: number;
|
|
24
29
|
};
|
|
25
|
-
export declare type
|
|
30
|
+
export declare type Algorithm<TContext> = (ctx: TContext, identifier: string) => Promise<RatelimitResponse>;
|