@upstash/ratelimit 0.1.2 → 0.1.3-rc.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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Upstash Redis
1
+ # Upstash Ratelimit
2
2
 
3
3
  An HTTP/REST based Redis client built on top of Upstash REST API.
4
4
  [Upstash REST API](https://docs.upstash.com/features/restapi).
@@ -155,15 +155,15 @@ doExpensiveCalculation();
155
155
  return "Here you go!";
156
156
  ```
157
157
 
158
- ## Globally replicated ratelimiting
158
+ ## MultiRegionly replicated ratelimiting
159
159
 
160
160
  Using a single redis instance has the downside of providing low latencies to the
161
161
  part of your userbase closest to the deployed db. That's why we also built
162
- `GlobalRatelimit` which replicates the state across multiple redis databases as
163
- well as offering lower latencies to more of your users.
162
+ `MultiRegionRatelimit` which replicates the state across multiple redis
163
+ databases as well as offering lower latencies to more of your users.
164
164
 
165
- `GlobalRatelimit` does this by checking the current limit in the closest db and
166
- returning immediately. Only afterwards will the state be asynchronously
165
+ `MultiRegionRatelimit` does this by checking the current limit in the closest db
166
+ and returning immediately. Only afterwards will the state be asynchronously
167
167
  replicated to the other datbases leveraging
168
168
  [CRDTs](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type). Due
169
169
  to the nature of distributed systems, there is no way to guarantee the set
@@ -175,11 +175,11 @@ global latency.
175
175
  The api is the same, except for asking for multiple redis instances:
176
176
 
177
177
  ```ts
178
- import { GlobalRatelimit } from "@upstash/ratelimit"; // for deno: see above
178
+ import { MultiRegionRatelimit } from "@upstash/ratelimit"; // for deno: see above
179
179
  import { Redis } from "@upstash/redis";
180
180
 
181
181
  // Create a new ratelimiter, that allows 10 requests per 10 seconds
182
- const ratelimit = new GlobalRatelimit({
182
+ const ratelimit = new MultiRegionRatelimit({
183
183
  redis: [
184
184
  new Redis({/* auth */}),
185
185
  new Redis({/* auth */}),
@@ -275,7 +275,7 @@ const ratelimit = new Ratelimit({
275
275
 
276
276
  ### Token Bucket
277
277
 
278
- _Not yet supported for `GlobalRatelimit`_
278
+ _Not yet supported for `MultiRegionRatelimit`_
279
279
 
280
280
  Consider a bucket filled with `{maxTokens}` tokens that refills constantly at
281
281
  `{refillRate}` per `{interval}`. Every request will remove one token from the
package/esm/mod.js CHANGED
@@ -1,2 +1,2 @@
1
- export { RegionRatelimit as Ratelimit } from "./region.js";
2
- export { GlobalRatelimit } from "./global.js";
1
+ export { RegionRatelimit as Ratelimit } from "./single.js";
2
+ export { MultiRegionRatelimit } from "./multi.js";
@@ -5,9 +5,9 @@ import { Ratelimit } from "./ratelimit.js";
5
5
  *
6
6
  * @example
7
7
  * ```ts
8
- * const { limit } = new GlobalRatelimit({
8
+ * const { limit } = new MultiRegionRatelimit({
9
9
  * redis: Redis.fromEnv(),
10
- * limiter: GlobalRatelimit.fixedWindow(
10
+ * limiter: MultiRegionRatelimit.fixedWindow(
11
11
  * 10, // Allow 10 requests per window of 30 minutes
12
12
  * "30 m", // interval of 30 minutes
13
13
  * )
@@ -15,7 +15,7 @@ import { Ratelimit } from "./ratelimit.js";
15
15
  *
16
16
  * ```
17
17
  */
18
- export class GlobalRatelimit extends Ratelimit {
18
+ export class MultiRegionRatelimit extends Ratelimit {
19
19
  /**
20
20
  * Create a new Ratelimit instance by providing a `@upstash/redis` instance and the algorithn of your choice.
21
21
  */
@@ -108,12 +108,12 @@ export class GlobalRatelimit extends Ratelimit {
108
108
  /**
109
109
  * Do not await sync. This should not run in the critical path.
110
110
  */
111
- sync();
112
111
  return {
113
112
  success: remaining > 0,
114
113
  limit: tokens,
115
114
  remaining,
116
115
  reset: (bucket + 1) * windowDuration,
116
+ pending: sync(),
117
117
  };
118
118
  };
119
119
  }
@@ -216,12 +216,12 @@ export class GlobalRatelimit extends Ratelimit {
216
216
  /**
217
217
  * Do not await sync. This should not run in the critical path.
218
218
  */
219
- sync();
220
219
  return {
221
220
  success: remaining > 0,
222
221
  limit: tokens,
223
222
  remaining,
224
223
  reset: (currentWindow + 1) * windowDuration,
224
+ pending: sync(),
225
225
  };
226
226
  };
227
227
  }
package/esm/ratelimit.js CHANGED
@@ -92,7 +92,7 @@ export class Ratelimit {
92
92
  * An identifier per user or api.
93
93
  * Choose a userID, or api token, or ip address.
94
94
  *
95
- * If you want to globally limit your api, you can set a constant string.
95
+ * If you want to limit your api across all users, you can set a constant string.
96
96
  */
97
97
  identifier,
98
98
  /**
@@ -76,6 +76,7 @@ export class RegionRatelimit extends Ratelimit {
76
76
  limit: tokens,
77
77
  remaining: tokens - usedTokensAfterUpdate,
78
78
  reset: (bucket + 1) * windowDuration,
79
+ pending: Promise.resolve(),
79
80
  };
80
81
  };
81
82
  }
@@ -147,6 +148,7 @@ export class RegionRatelimit extends Ratelimit {
147
148
  limit: tokens,
148
149
  remaining,
149
150
  reset: (currentWindow + 1) * windowSize,
151
+ pending: Promise.resolve(),
150
152
  };
151
153
  };
152
154
  }
@@ -226,7 +228,13 @@ export class RegionRatelimit extends Ratelimit {
226
228
  const now = Date.now();
227
229
  const key = [identifier, Math.floor(now / intervalDuration)].join(":");
228
230
  const [remaining, reset] = (await ctx.redis.eval(script, [key], [maxTokens, intervalDuration, refillRate, now]));
229
- return { success: remaining > 0, limit: maxTokens, remaining, reset };
231
+ return {
232
+ success: remaining > 0,
233
+ limit: maxTokens,
234
+ remaining,
235
+ reset,
236
+ pending: Promise.resolve(),
237
+ };
230
238
  };
231
239
  }
232
240
  }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "main": "./script/mod.js",
4
4
  "types": "./types/mod.d.ts",
5
5
  "name": "@upstash/ratelimit",
6
- "version": "v0.1.2",
6
+ "version": "v0.1.3-rc.0",
7
7
  "description": "A serverless ratelimiter built on top of Upstash REST API.",
8
8
  "repository": {
9
9
  "type": "git",
package/script/mod.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.GlobalRatelimit = exports.Ratelimit = void 0;
4
- var region_js_1 = require("./region.js");
5
- Object.defineProperty(exports, "Ratelimit", { enumerable: true, get: function () { return region_js_1.RegionRatelimit; } });
6
- var global_js_1 = require("./global.js");
7
- Object.defineProperty(exports, "GlobalRatelimit", { enumerable: true, get: function () { return global_js_1.GlobalRatelimit; } });
3
+ exports.MultiRegionRatelimit = exports.Ratelimit = void 0;
4
+ var single_js_1 = require("./single.js");
5
+ Object.defineProperty(exports, "Ratelimit", { enumerable: true, get: function () { return single_js_1.RegionRatelimit; } });
6
+ var multi_js_1 = require("./multi.js");
7
+ Object.defineProperty(exports, "MultiRegionRatelimit", { enumerable: true, get: function () { return multi_js_1.MultiRegionRatelimit; } });
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.GlobalRatelimit = void 0;
3
+ exports.MultiRegionRatelimit = void 0;
4
4
  const duration_js_1 = require("./duration.js");
5
5
  const ratelimit_js_1 = require("./ratelimit.js");
6
6
  /**
@@ -8,9 +8,9 @@ const ratelimit_js_1 = require("./ratelimit.js");
8
8
  *
9
9
  * @example
10
10
  * ```ts
11
- * const { limit } = new GlobalRatelimit({
11
+ * const { limit } = new MultiRegionRatelimit({
12
12
  * redis: Redis.fromEnv(),
13
- * limiter: GlobalRatelimit.fixedWindow(
13
+ * limiter: MultiRegionRatelimit.fixedWindow(
14
14
  * 10, // Allow 10 requests per window of 30 minutes
15
15
  * "30 m", // interval of 30 minutes
16
16
  * )
@@ -18,7 +18,7 @@ const ratelimit_js_1 = require("./ratelimit.js");
18
18
  *
19
19
  * ```
20
20
  */
21
- class GlobalRatelimit extends ratelimit_js_1.Ratelimit {
21
+ class MultiRegionRatelimit extends ratelimit_js_1.Ratelimit {
22
22
  /**
23
23
  * Create a new Ratelimit instance by providing a `@upstash/redis` instance and the algorithn of your choice.
24
24
  */
@@ -111,12 +111,12 @@ class GlobalRatelimit extends ratelimit_js_1.Ratelimit {
111
111
  /**
112
112
  * Do not await sync. This should not run in the critical path.
113
113
  */
114
- sync();
115
114
  return {
116
115
  success: remaining > 0,
117
116
  limit: tokens,
118
117
  remaining,
119
118
  reset: (bucket + 1) * windowDuration,
119
+ pending: sync(),
120
120
  };
121
121
  };
122
122
  }
@@ -219,14 +219,14 @@ class GlobalRatelimit extends ratelimit_js_1.Ratelimit {
219
219
  /**
220
220
  * Do not await sync. This should not run in the critical path.
221
221
  */
222
- sync();
223
222
  return {
224
223
  success: remaining > 0,
225
224
  limit: tokens,
226
225
  remaining,
227
226
  reset: (currentWindow + 1) * windowDuration,
227
+ pending: sync(),
228
228
  };
229
229
  };
230
230
  }
231
231
  }
232
- exports.GlobalRatelimit = GlobalRatelimit;
232
+ exports.MultiRegionRatelimit = MultiRegionRatelimit;
@@ -95,7 +95,7 @@ class Ratelimit {
95
95
  * An identifier per user or api.
96
96
  * Choose a userID, or api token, or ip address.
97
97
  *
98
- * If you want to globally limit your api, you can set a constant string.
98
+ * If you want to limit your api across all users, you can set a constant string.
99
99
  */
100
100
  identifier,
101
101
  /**
@@ -79,6 +79,7 @@ class RegionRatelimit extends ratelimit_js_1.Ratelimit {
79
79
  limit: tokens,
80
80
  remaining: tokens - usedTokensAfterUpdate,
81
81
  reset: (bucket + 1) * windowDuration,
82
+ pending: Promise.resolve(),
82
83
  };
83
84
  };
84
85
  }
@@ -150,6 +151,7 @@ class RegionRatelimit extends ratelimit_js_1.Ratelimit {
150
151
  limit: tokens,
151
152
  remaining,
152
153
  reset: (currentWindow + 1) * windowSize,
154
+ pending: Promise.resolve(),
153
155
  };
154
156
  };
155
157
  }
@@ -229,7 +231,13 @@ class RegionRatelimit extends ratelimit_js_1.Ratelimit {
229
231
  const now = Date.now();
230
232
  const key = [identifier, Math.floor(now / intervalDuration)].join(":");
231
233
  const [remaining, reset] = (await ctx.redis.eval(script, [key], [maxTokens, intervalDuration, refillRate, now]));
232
- return { success: remaining > 0, limit: maxTokens, remaining, reset };
234
+ return {
235
+ success: remaining > 0,
236
+ limit: maxTokens,
237
+ remaining,
238
+ reset,
239
+ pending: Promise.resolve(),
240
+ };
233
241
  };
234
242
  }
235
243
  }
package/types/mod.d.ts CHANGED
@@ -1,5 +1,5 @@
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";
1
+ export { RegionRatelimit as Ratelimit } from "./single.js";
2
+ export type { RegionRatelimitConfig as RatelimitConfig } from "./single.js";
3
+ export { MultiRegionRatelimit } from "./multi.js";
4
+ export type { MultiRegionRatelimitConfig } from "./multi.js";
5
5
  export type { Algorithm } from "./types.js";
@@ -1,8 +1,8 @@
1
1
  import type { Duration } from "./duration.js";
2
- import type { Algorithm, GlobalContext } from "./types.js";
2
+ import type { Algorithm, MultiRegionContext } from "./types.js";
3
3
  import { Ratelimit } from "./ratelimit.js";
4
4
  import type { Redis } from "./types.js";
5
- export declare type GlobalRatelimitConfig = {
5
+ export declare type MultiRegionRatelimitConfig = {
6
6
  /**
7
7
  * Instances of `@upstash/redis`
8
8
  * @see https://github.com/upstash/upstash-redis#quick-start
@@ -13,9 +13,9 @@ export declare type GlobalRatelimitConfig = {
13
13
  *
14
14
  * Choose one of the predefined ones or implement your own.
15
15
  * Available algorithms are exposed via static methods:
16
- * - GlobalRatelimit.fixedWindow
16
+ * - MultiRegionRatelimit.fixedWindow
17
17
  */
18
- limiter: Algorithm<GlobalContext>;
18
+ limiter: Algorithm<MultiRegionContext>;
19
19
  /**
20
20
  * All keys in redis are prefixed with this.
21
21
  *
@@ -28,9 +28,9 @@ export declare type GlobalRatelimitConfig = {
28
28
  *
29
29
  * @example
30
30
  * ```ts
31
- * const { limit } = new GlobalRatelimit({
31
+ * const { limit } = new MultiRegionRatelimit({
32
32
  * redis: Redis.fromEnv(),
33
- * limiter: GlobalRatelimit.fixedWindow(
33
+ * limiter: MultiRegionRatelimit.fixedWindow(
34
34
  * 10, // Allow 10 requests per window of 30 minutes
35
35
  * "30 m", // interval of 30 minutes
36
36
  * )
@@ -38,11 +38,11 @@ export declare type GlobalRatelimitConfig = {
38
38
  *
39
39
  * ```
40
40
  */
41
- export declare class GlobalRatelimit extends Ratelimit<GlobalContext> {
41
+ export declare class MultiRegionRatelimit extends Ratelimit<MultiRegionContext> {
42
42
  /**
43
43
  * Create a new Ratelimit instance by providing a `@upstash/redis` instance and the algorithn of your choice.
44
44
  */
45
- constructor(config: GlobalRatelimitConfig);
45
+ constructor(config: MultiRegionRatelimitConfig);
46
46
  /**
47
47
  * Each requests inside a fixed time increases a counter.
48
48
  * Once the counter reaches a maxmimum allowed number, all further requests are
@@ -69,7 +69,7 @@ export declare class GlobalRatelimit extends Ratelimit<GlobalContext> {
69
69
  /**
70
70
  * The duration in which `tokens` requests are allowed.
71
71
  */
72
- window: Duration): Algorithm<GlobalContext>;
72
+ window: Duration): Algorithm<MultiRegionContext>;
73
73
  /**
74
74
  * Combined approach of `slidingLogs` and `fixedWindow` with lower storage
75
75
  * costs than `slidingLogs` and improved boundary behavior by calcualting a
@@ -94,5 +94,5 @@ export declare class GlobalRatelimit extends Ratelimit<GlobalContext> {
94
94
  /**
95
95
  * The duration in which `tokens` requests are allowed.
96
96
  */
97
- window: Duration): Algorithm<GlobalContext>;
97
+ window: Duration): Algorithm<MultiRegionContext>;
98
98
  }
File without changes
package/types/types.d.ts CHANGED
@@ -5,10 +5,10 @@ export interface Redis {
5
5
  export declare type RegionContext = {
6
6
  redis: Redis;
7
7
  };
8
- export declare type GlobalContext = {
8
+ export declare type MultiRegionContext = {
9
9
  redis: Redis[];
10
10
  };
11
- export declare type Context = RegionContext | GlobalContext;
11
+ export declare type Context = RegionContext | MultiRegionContext;
12
12
  export declare type RatelimitResponse = {
13
13
  /**
14
14
  * Whether the request may pass(true) or exceeded the limit(false)
@@ -26,5 +26,28 @@ export declare type RatelimitResponse = {
26
26
  * Unix timestamp in milliseconds when the limits are reset.
27
27
  */
28
28
  reset: number;
29
+ /**
30
+ * For the MultiRegion setup we do some synchronizing in the background, after returning the current limit.
31
+ * In most case you can simply ignore this.
32
+ *
33
+ * On Vercel Edge or Cloudflare workers, you need to explicitely handle the pending Promise like this:
34
+ *
35
+ * **Vercel Edge:**
36
+ * https://nextjs.org/docs/api-reference/next/server#nextfetchevent
37
+ *
38
+ * ```ts
39
+ * const { pending } = await ratelimit.limit("id")
40
+ * event.waitUntil(pending)
41
+ * ```
42
+ *
43
+ * **Cloudflare Worker:**
44
+ * https://developers.cloudflare.com/workers/runtime-apis/fetch-event/#syntax-module-worker
45
+ *
46
+ * ```ts
47
+ * const { pending } = await ratelimit.limit("id")
48
+ * context.waitUntil(pending)
49
+ * ```
50
+ */
51
+ pending: Promise<unknown>;
29
52
  };
30
53
  export declare type Algorithm<TContext> = (ctx: TContext, identifier: string) => Promise<RatelimitResponse>;