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.
- package/README.md +67 -62
- 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
|
-
|
|
207
|
-
const
|
|
208
|
-
const
|
|
209
|
-
const
|
|
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: "
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
226
|
-
|
|
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.
|
|
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.
|
|
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
|
-
`
|
|
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
|
-
|
|
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
|
|