convex-helpers 0.1.37 → 0.1.38

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 (2) hide show
  1. package/README.md +67 -62
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -199,34 +199,76 @@ npx convex run migrations --prod
199
199
 
200
200
  ## Rate limiting
201
201
 
202
- Configure and use rate limits.
202
+ Configure and use rate limits to avoid product abuse.
203
+ See the associated Stack post for details:
204
+
205
+ https://stack.convex.dev/rate-limiting
203
206
 
204
207
  ```ts
205
208
  import { defineRateLimits } from "convex-helpers/server/rateLimit";
206
- const Second = 1000; // ms
207
- const Minute = 60 * Second;
208
- const Hour = 60 * Minute;
209
- const Day = 24 * Hour;
209
+
210
+ const SECOND = 1000; // ms
211
+ const MINUTE = 60 * SECOND;
212
+ const HOUR = 60 * MINUTE;
213
+ const DAY = 24 * HOUR;
210
214
 
211
215
  export const { checkRateLimit, rateLimit, resetRateLimit } = defineRateLimits({
216
+ // A per-user limit, allowing one every ~6 seconds.
217
+ // Allows up to 3 in quick succession if they haven't sent many recently.
218
+ sendMessage: { kind: "token bucket", rate: 10, period: MINUTE, capacity: 3 },
212
219
  // One global / singleton rate limit
213
- freeTrialSignUp: { kind: "sliding", rate: 100, period: Hour },
214
- // A per-user limit, allowing bursts up to 20. See below for details on burst.
215
- sendMessage: { kind: "sliding", rate: 10, period: Minute, burst: 20 },
220
+ freeTrialSignUp: { kind: "fixed window", rate: 100, period: HOUR },
221
+ });
222
+ ```
223
+
224
+ And add the rate limit table to your schema:
225
+
226
+ ```ts
227
+ // in convex/schema.ts
228
+ import { rateLimitTables } from "./rateLimit.js";
229
+
230
+ export default defineSchema({
231
+ ...rateLimitTables,
232
+ otherTable: defineTable({}),
233
+ // other tables
216
234
  });
217
235
  ```
218
236
 
219
- It uses a token bucket approach to provide guarantees for overall consumption
220
- limiting, while also allowing unused capacity to accumulate (like "rollover"
221
- minutes) up to some `burst` value. So if you could normally send 10 per minute,
222
- and allow accumulating up to 20, then every two minutes you could send 20, or if
223
- in the last minute you only sent 5, you can send 15 now.
237
+ If you don't care about centralizing the configuration and type safety on the
238
+ rate limit names, you don't have to use `defineRateLimits`, and can inline the
239
+ config:
240
+
241
+ ```ts
242
+ import { checkRateLimit, rateLimit, resetRateLimit } from "./rateLimit.js";
243
+
244
+ //...
245
+ await rateLimit(ctx, {
246
+ name: "callLLM",
247
+ count: numTokens,
248
+ config: { kind: "fixed window", rate: 40000, period: DAY },
249
+ });,
250
+ ```
251
+
252
+ You also don't have to define all of your rate limits in one place.
253
+ You can use `defineRateLimits` multiple times.
224
254
 
225
- The values accumulate continuously, so if your limit is 10 per minute, you could
226
- use one credit every 6 seconds, or a half credit every 3 second.
255
+ ### Strategies:
256
+
257
+ The **`token bucket`** approach provides guarantees for overall consumption via the
258
+ `rate` per `period` at which tokens are added, while also allowing unused
259
+ tokens to accumulate (like "rollover" minutes) up to some `capacity` value.
260
+ So if you could normally send 10 per minute, with a capacity of 20, then every
261
+ two minutes you could send 20, or if in the last two minutes you only sent 5,
262
+ you can send 15 now.
263
+
264
+ The **`fixed window`** approach differs in that the tokens are granted all at once,
265
+ every `period` milliseconds. It similarly allows accumulating "rollover" tokens
266
+ up to a `capacity` (defaults to the `rate` for both rate limit strategies).
267
+
268
+ ### Reserving capacity:
227
269
 
228
270
  You can also allow it to "reserve" capacity to avoid starvation on larger
229
- requests. See below for details.
271
+ requests. Details in the [Stack post](https://stack.convex.dev/rate-limiting).
230
272
 
231
273
  ### To use a simple global rate limit:
232
274
 
@@ -234,12 +276,13 @@ requests. See below for details.
234
276
  const { ok, retryAt } = await rateLimit(ctx, { name: "freeTrialSignUp" });
235
277
  ```
236
278
 
237
- `ok` is whether it successfully consumed the resource
279
+ - `ok` is whether it successfully consumed the resource
280
+ - `retryAt` is when it would have succeeded in the future.
238
281
 
239
- `retryAt` is when it would have succeeded in the future.
240
282
  **Note**: If you have many clients using the `retryAt` to decide when to retry,
241
283
  defend against a [thundering herd](https://en.wikipedia.org/wiki/Thundering_herd_problem)
242
- by adding some jitter. Or use the reserved functionality discussed below.
284
+ by adding some [jitter](https://stack.convex.dev/rate-limiting#jitter-introducing-randomness-to-avoid-thundering-herds).
285
+ Or use the reserved functionality discussed in the [Stack post](https://stack.convex.dev/rate-limiting).
243
286
 
244
287
  ### To use a per-user rate limit:
245
288
 
@@ -252,50 +295,12 @@ await rateLimit(ctx, {
252
295
  });
253
296
  ```
254
297
 
255
- `key` is a rate limit specific to some user / team / session ID / etc.
256
-
257
- `count` is how many to consume (default is 1)
258
-
259
- `throws` configures it to throw a structured error instead of returning.
260
-
261
- ### Reserving capacity
262
-
263
- If you want to ensure an operation will run in the future if there isn't
264
- capacity now, you can use `reserved: true`. This will pre-allocate capacity for
265
- the operation, and give you the time it should run.
266
-
267
- When you use this strategy, it's up to you to not run the operation until the
268
- designated time, at which point you don't need to check any rate limits, since
269
- the system will have waited to accumulate enough credits and accounted for it.
270
-
271
- ```ts
272
- import { rateLimit } from "convex-helpers/server/rateLimit";
298
+ - `key` is a rate limit specific to some user / team / session ID / etc.
299
+ - `count` is how many to consume (default is 1)
300
+ - `throws` configures it to throw a `ConvexError` with `RateLimitError` data
301
+ instead of returning when `ok` is false.
273
302
 
274
- export const sendOrQueueEmail = internalMutation({
275
- args: { to: v.string(), subject: v.string(), body: v.string() },
276
- handler: async (ctx, args) => {
277
- const { ok, retryAt } = await rateLimit(ctx, {
278
- name: "sendMarketingEmail",
279
- key: args.to,
280
- reserve: true,
281
- config: { kind: "sliding", rate: 1, period: Minute, maxReserved: 10 },
282
- });
283
- if (!ok) throw new Error("Email is too overloaded, dropping message.");
284
- if (retryAt) {
285
- await ctx.scheduler.runAt(retryAt, internal.emails.send, args);
286
- } else {
287
- await ctx.scheduler.runAfter(0, internal.emails.send, args);
288
- }
289
- },
290
- });
291
- ```
292
-
293
- Here we used the inline config and direct import.
294
- If you don't care about centralizing the configuration and type safety on the
295
- rate limit names, you don't have to use `defineRateLimits`.
296
-
297
- You also don't have to define all of your rate limits in one place.
298
- You can use `defineRateLimits` multiple times.
303
+ Read more in the [Stack post](https://stack.convex.dev/rate-limiting).
299
304
 
300
305
  ## Session tracking via client-side sessionID storage
301
306
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "convex-helpers",
3
- "version": "0.1.37",
3
+ "version": "0.1.38",
4
4
  "description": "A collection of useful code to complement the official convex package.",
5
5
  "type": "module",
6
6
  "exports": {