convex-rate-limiter

@convex-dev/rate-limiter — Type-safe, transactional, application-level rate limiting for Convex.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "convex-rate-limiter" with this command: npx skills add imfa-solutions/skills/imfa-solutions-skills-convex-rate-limiter

Convex Rate Limiter

@convex-dev/rate-limiter — Type-safe, transactional, application-level rate limiting for Convex.

Installation

npm install @convex-dev/rate-limiter

Register the component in convex/convex.config.ts :

import { defineApp } from "convex/server"; import rateLimiter from "@convex-dev/rate-limiter/convex.config.js";

const app = defineApp(); app.use(rateLimiter); export default app;

Setup — Define Named Rate Limits

import { RateLimiter, MINUTE, HOUR, SECOND } from "@convex-dev/rate-limiter"; import { components } from "./_generated/api";

const rateLimiter = new RateLimiter(components.rateLimiter, { // Fixed window — hard quota that resets each period freeTrialSignUp: { kind: "fixed window", rate: 100, period: HOUR },

// Token bucket — smooth traffic with burst allowance sendMessage: { kind: "token bucket", rate: 10, period: MINUTE, capacity: 3 },

// Failed login throttle failedLogins: { kind: "token bucket", rate: 10, period: HOUR }, });

  • period is in milliseconds (SECOND = 1000 , MINUTE = 60000 , HOUR = 3600000 ).

  • Multiple RateLimiter instances allowed; config keys must not overlap.

Strategy Selection

Strategy Behavior Best for

Token bucket Tokens refill continuously; unused tokens accumulate up to capacity

User actions, API calls, LLM tokens — smooth traffic, allow bursts

Fixed window All tokens granted at period start; hard reset each period Daily/hourly quotas, signup caps, hard API limits

Token bucket config:

{ kind: "token bucket", rate: 10, period: MINUTE, capacity: 3 } // capacity = max burst tokens (optional, defaults to rate)

Fixed window config:

{ kind: "fixed window", rate: 100, period: HOUR } // start: optional custom epoch; random by default to prevent thundering herd

Core API

limit() — Consume tokens

const { ok, retryAfter } = await rateLimiter.limit(ctx, "sendMessage", { key: userId, // Per-entity key (omit for global limit) count: 1, // Tokens to consume (default 1) throws: false, // Set true to auto-throw ConvexError }); if (!ok) throw new Error(Rate limited. Retry in ${Math.ceil(retryAfter / 1000)}s);

check() — Query without consuming (safe in queries)

const { ok, retryAfter } = await rateLimiter.check(ctx, "sendMessage", { key: userId, });

reset() — Clear a rate limit

await rateLimiter.reset(ctx, "failedLogins", { key: email });

throws: true — Auto-throw pattern

import { isRateLimitError } from "@convex-dev/rate-limiter";

await rateLimiter.limit(ctx, "sendMessage", { key: userId, throws: true }); // Throws ConvexError with data: { kind, name, retryAfter }

Catch with isRateLimitError(error) to inspect .data.retryAfter .

Usage Patterns

Global rate limit (no key)

export const signUp = mutation({ args: { email: v.string() }, handler: async (ctx, args) => { await rateLimiter.limit(ctx, "freeTrialSignUp", { throws: true }); await ctx.db.insert("users", { email: args.email }); }, });

Per-user rate limit

export const sendMessage = mutation({ args: { text: v.string() }, handler: async (ctx, args) => { const user = await ctx.auth.getUserIdentity(); await rateLimiter.limit(ctx, "sendMessage", { key: user?.subject, throws: true, }); await ctx.db.insert("messages", { userId: user?.subject, text: args.text }); }, });

Failed login throttle with reset on success

export const login = mutation({ args: { email: v.string(), password: v.string() }, handler: async (ctx, args) => { await rateLimiter.limit(ctx, "failedLogins", { key: args.email, throws: true, }); const success = await verifyCredentials(ctx, args); if (success) { await rateLimiter.reset(ctx, "failedLogins", { key: args.email }); } return success; }, });

Consume multiple tokens at once

await rateLimiter.limit(ctx, "llmTokens", { key: userId, count: estimateTokens(prompt), throws: true, });

Critical Rules

  • Always pass key for per-entity limits. Omitting key makes it a global singleton.

  • Always surface retryAfter to the client — don't just say "rate limited".

  • Rate limit changes are transactional — they roll back if the mutation fails.

  • Use throws: true for cleaner code; catch with isRateLimitError() .

  • Reset wisely — reset on success (e.g., login), never on every request.

Common Pitfalls

// BAD: Global limit when you want per-user await rateLimiter.limit(ctx, "sendMessage");

// GOOD: Per-user limit await rateLimiter.limit(ctx, "sendMessage", { key: userId });

// BAD: Ignoring retryAfter const { ok } = await rateLimiter.limit(ctx, "action"); if (!ok) throw new Error("Rate limited");

// GOOD: Tell user when to retry const { ok, retryAfter } = await rateLimiter.limit(ctx, "action"); if (!ok) throw new Error(Wait ${Math.ceil(retryAfter / 1000)} seconds);

Best Practices Summary

Practice Guidance

Key design User ID for per-user, IP for anonymous, team ID for team-wide, composites like ${teamId}:${userId}

Capacity Token bucket: capacity = rate * burst_multiplier . Fixed window: defaults to rate

Start simple Add sharding/reservation only when needed

Error handling Use throws: true

  • isRateLimitError()

Thundering herd Fixed window uses random start by default; add jitter to retry times

Advanced Topics

  • Sharding for high throughput (OCC conflicts): See references/advanced.md

  • Capacity reservation (prevent starvation): See references/advanced.md

  • Direct value access & calculateRateLimit : See references/advanced.md

  • Jitter patterns: See references/advanced.md

  • Troubleshooting: See references/advanced.md

Common Patterns

  • Tiered rate limits (free vs premium): See references/patterns.md

  • Anonymous/IP-based limiting: See references/patterns.md

  • Multiple rate limits in one transaction: See references/patterns.md

  • React client integration (useRateLimit ): See references/patterns.md

  • Inline/dynamic config: See references/patterns.md

  • Testing: See references/patterns.md

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

rn-skia

No summary provided by upstream source.

Repository SourceNeeds Review
General

rn-reanimated

No summary provided by upstream source.

Repository SourceNeeds Review
General

convex-file-system

No summary provided by upstream source.

Repository SourceNeeds Review
General

rn-heroui

No summary provided by upstream source.

Repository SourceNeeds Review