Skip to content

chore: migrate single rate limits to multi rate limits#3328

Merged
chronark merged 9 commits intomainfrom
multi-ratelimit
Jun 18, 2025
Merged

chore: migrate single rate limits to multi rate limits#3328
chronark merged 9 commits intomainfrom
multi-ratelimit

Conversation

@chronark
Copy link
Collaborator

@chronark chronark commented Jun 16, 2025

What does this PR do?

We're no longer using the rate limits defined in the keys table, all of them have been migrated to the ratelimits table, allowing users to specify multiple rate limits per key (just like they already can for identities)

All rate limits have been migrated and the DB is updated.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Chore (refactoring code, technical debt, workflow improvements)
  • Enhancement (small improvements)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How should this be tested?

  1. Create a new key in the dashboard or API with one autoApply ratelimit and one without.
  2. Verify the key via API
  3. Update the key's ratelimits
  4. Verify key again

Checklist

Required

  • Filled out the "How to test" section in this PR
  • Read Contributing Guide
  • Self-reviewed my own code
  • Commented on my code in hard-to-understand areas
  • Ran pnpm build
  • Ran pnpm fmt
  • Checked for warnings, there are none
  • Removed all console.logs
  • Merged the latest changes from main onto my branch with git pull origin main
  • My changes don't cause any responsiveness issues

Appreciated

  • If a UI change was made: Added a screen recording or screenshots to this PR
  • Updated the Unkey Docs if changes were necessary

CleanShot 2025-06-16 at 10 36 46@2x

Summary by CodeRabbit

  • New Features

    • Introduced support for multiple named rate limits per key, including an "auto-apply" option for automatic enforcement.
    • Added UI controls and schema support for configuring multiple rate limits and the auto-apply setting when creating or editing keys.
    • Enhanced documentation with detailed guidance on multi-ratelimit usage, migration, and identity-level limits.
  • Improvements

    • Unified rate limit handling across API endpoints and dashboard, removing legacy single-rate-limit fields.
    • Rate limit configuration is now stored in a dedicated table, supporting more flexible and granular control.
    • Default values and validation updated to reflect the new multi-ratelimit model.
    • Simplified rate limit verification logic to apply only auto-applied limits by default.
    • Updated API responses and cache layers to include full rate limit arrays with detailed properties.
  • Bug Fixes

    • Ensured consistent retrieval and display of rate limit information throughout the dashboard and API responses.
  • Chores

    • Added a migration script to backfill and convert legacy rate limit data to the new format.
    • Database schema updated to add autoApply flag and adjust unique constraints on rate limits.
  • Documentation

    • Expanded and restructured rate limiting documentation to cover new features and migration steps.
  • Tests

    • Updated tests to validate the new multi-ratelimit structure and logic.

@changeset-bot
Copy link

changeset-bot bot commented Jun 16, 2025

⚠️ No Changeset found

Latest commit: 60ba33b

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jun 16, 2025

📝 Walkthrough

Walkthrough

This change migrates rate limit configuration from embedded fields on key records to a dedicated ratelimits table, introducing support for multiple named ratelimits per key with an autoApply flag. The update spans backend API logic, database schema, dashboard forms, validation, and documentation, and includes migration tooling and comprehensive test updates.

Changes

Files/Paths Change Summary
apps/api/src/pkg/cache/index.ts Updated cache buster version from "v8" to "v9".
apps/api/src/pkg/cache/namespaces.ts Extended cached key types to include ratelimits: Ratelimit[] and updated ratelimit type signatures.
apps/api/src/pkg/key_migration/handler.ts
apps/api/src/routes/legacy_keys_createKey.ts
apps/api/src/routes/v1_keys_createKey.ts
Removed ratelimit fields from key inserts; now inserts ratelimit records into ratelimits table if present.
apps/api/src/pkg/keys/service.ts Refactored ratelimit handling: removed legacy default logic, added autoApply support, simplified ratelimit request construction.
apps/api/src/routes/legacy_apis_listKeys.ts
apps/api/src/routes/v1_apis_listKeys.ts
apps/api/src/routes/v1_keys_getKey.ts
Updated key listing/getting routes to fetch and map ratelimits from the new relation, removing legacy fields.
apps/api/src/routes/v1_keys_deleteKey.ts
apps/api/src/routes/v1_keys_getVerifications.ts
Added ratelimits relation to key fetch queries and responses.
apps/api/src/routes/v1_keys_updateKey.ts Added explicit ratelimit record update/insert/delete logic; now manages ratelimits in a separate table.
apps/api/src/routes/v1_keys_updateKey.happy.test.ts
apps/api/src/routes/v1_keys_verifyKey.multilimit.test.ts
apps/api/src/routes/v1_keys_verifyKey.test.ts
apps/api/src/routes/v1_migrations_enqueueKeys.happy.test_disabled.ts
Updated tests to use ratelimits array on keys and insert ratelimit records separately.
apps/api/src/routes/v1_migrations_createKey.ts Removed ratelimit fields from key migration logic.
apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/components/ratelimit-setup.tsx Added autoApply checkbox for each ratelimit in the UI; updated form logic and placeholders.
apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/create-key.schema.ts
apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/create-key.utils.ts
Added autoApply to ratelimit schema and default values.
apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/components/log-details/index.tsx Removed legacy ratelimit display fields from log details.
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/components/edit-ratelimits/index.tsx Removed ratelimitType property from key update payload.
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/components/hooks/use-edit-ratelimits.ts Simplified ratelimit update success callback, removed v1/v2 branching.
apps/dashboard/components/dashboard/root-key-table/index.ts Removed ratelimit fields from Column type.
apps/dashboard/lib/trpc/routers/api/keys/api-query.ts Removed ratelimit fields from types and database queries.
apps/dashboard/lib/trpc/routers/key/create.ts Added autoApply property to ratelimit insert logic.
apps/dashboard/lib/trpc/routers/key/updateRatelimit.ts Removed v1 ratelimit support; now only supports v2 (multi-ratelimit) logic.
apps/docs/apis/features/ratelimiting/overview.mdx Major documentation rewrite: explains multi-ratelimit, autoApply, migration, and advanced usage.
internal/clickhouse/src/keys/keys.ts Removed ratelimit fields from schema and compacted SQL formatting.
internal/db/src/schema/identity.ts Added autoApply column to ratelimits table; split unique index into per-key and per-identity.
tools/migrate/ratelimit-migrate.ts New migration script: backfills ratelimit records for existing keys with legacy fields.
apps/dashboard/lib/trpc/routers/api/keys/query-api-keys/get-all-keys.ts Added autoApply property in ratelimit queries and outputs.

Sequence Diagram(s)

sequenceDiagram
    participant Dashboard
    participant API
    participant DB

    Dashboard->>API: Create/Update Key (with ratelimits array)
    API->>DB: Insert/Update key record
    API->>DB: Insert/Update ratelimit records (with autoApply flags)
    DB-->>API: Confirmation
    API-->>Dashboard: Key and ratelimit data

    Dashboard->>API: List/Get Key(s)
    API->>DB: Query key(s) with ratelimits relation
    DB-->>API: Key(s) + ratelimits[]
    API-->>Dashboard: Key(s) + ratelimits[]
Loading
sequenceDiagram
    participant API
    participant DB

    API->>DB: Verify Key (fetch key + ratelimits)
    API->>DB: For each ratelimit where autoApply=true, apply rate limit
    DB-->>API: Rate limit check results
    API-->>Client: Verification result (with applied ratelimits)
Loading

Possibly related PRs

  • unkeyed/unkey#2103: Modifies ratelimit handling logic in apps/api/src/pkg/keys/service.ts, directly related to the new ratelimit system.
  • unkeyed/unkey#2693: Updates key name change logic to avoid affecting ratelimit fields, related to ratelimit update and storage logic.

Suggested labels

Feature, Needs Approval

Suggested reviewers

  • perkinsjr
  • mcstepp
  • ogzhanolguncu

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0f5d549 and 0fa7682.

📒 Files selected for processing (2)
  • apps/dashboard/lib/trpc/routers/api/keys/query-api-keys/get-all-keys.ts (2 hunks)
  • apps/dashboard/lib/trpc/routers/key/updateRatelimit.ts (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
apps/dashboard/lib/trpc/routers/key/updateRatelimit.ts (2)
apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/create-key.schema.ts (1)
  • ratelimitSchema (254-266)
apps/dashboard/lib/trpc/trpc.ts (1)
  • ratelimit (84-111)
⏰ Context from checks skipped due to timeout of 90000ms (15)
  • GitHub Check: Test Go API Local / Test (Shard 8/8)
  • GitHub Check: Test Go API Local / Test (Shard 7/8)
  • GitHub Check: Test Go API Local / Test (Shard 3/8)
  • GitHub Check: Test Go API Local / Test (Shard 4/8)
  • GitHub Check: Test Go API Local / Test (Shard 2/8)
  • GitHub Check: Build / Build
  • GitHub Check: Test Go API Local / Test (Shard 6/8)
  • GitHub Check: Test Go API Local / Test (Shard 5/8)
  • GitHub Check: Test Go API Local / Test (Shard 1/8)
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Test Packages / Test ./internal/clickhouse
  • GitHub Check: Test Packages / Test ./packages/cache
  • GitHub Check: Test Packages / Test ./packages/api
  • GitHub Check: autofix
🔇 Additional comments (1)
apps/dashboard/lib/trpc/routers/api/keys/query-api-keys/get-all-keys.ts (1)

208-214: Confirm autoApply exists on KeyDetails & DB column casing
You expose autoApply from the select clause, but KeyDetails (imported from ./schema) must include it or TypeScript will complain. Likewise, be sure the column is named auto_apply (snake-case) in SQL and mapped to autoApply by the ORM; otherwise this field will always be undefined.

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions
Copy link
Contributor

github-actions bot commented Jun 16, 2025

Thank you for following the naming conventions for pull request titles! 🙏

@vercel
Copy link

vercel bot commented Jun 16, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
dashboard ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jun 18, 2025 7:12am
1 Skipped Deployment
Name Status Preview Comments Updated (UTC)
engineering ⬜️ Ignored (Inspect) Visit Preview Jun 18, 2025 7:12am

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 20

🔭 Outside diff range comments (9)
apps/dashboard/lib/trpc/routers/key/create.ts (1)

89-102: autoApply may be undefined – add a fallback

ratelimit.autoApply is typed as optional in the UI layer.
Passing undefined here will insert a DB NULL, which is likely to violate the NOT NULL DEFAULT false constraint on ratelimits.auto_apply.

-              autoApply: ratelimit.autoApply,
+              // fallback to DB default (false) if the flag is omitted
+              autoApply: ratelimit.autoApply ?? false,
apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/create-key.schema.ts (1)

255-263: Minor: align default object with updated schema

When autoApply gains a default, you can drop the explicit autoApply: true here to keep the default source-of-truth single.

-        autoApply: true,
+        // autoApply defaults to true
apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/components/ratelimit-setup.tsx (1)

48-55: New rule helper should mirror default state

The helper used by “Add additional ratelimit” also hard-codes autoApply: false. Consider inheriting the current “Default” or explicitly asking the user.

apps/dashboard/lib/trpc/routers/api/keys/api-query.ts (1)

224-230: Sub-query for identity filter can degrade performance

The EXISTS ( SELECT 1 FROM ${identities} … ) sub-query is executed once per key when identitiesFromInput is provided. On large keyspaces this quickly becomes the dominant cost.
Consider a LEFT JOIN identities ON identities.id = key.identityId combined with a single WHERE clause; the optimiser can then use indexes on external_id and avoid N×sub-query evaluation.

internal/db/src/schema/identity.ts (1)

64-71: Missing CHECK to enforce XOR between keyId and identityId

The comment says “Either keyId or identityId may be defined, not both”, yet the table has no check-constraint guaranteeing this invariant.
Leaving it to application code risks accidental data corruption.

+    /* ensure exactly one of keyId / identityId is non-null */
+    CHECK (
+      (key_id IS NOT NULL AND identity_id IS NULL)
+      OR
+      (key_id IS NULL AND identity_id IS NOT NULL)
+    ),

MySQL 8.0+ supports CHECK constraints; DRIZZLE will emit them.

apps/api/src/routes/v1_keys_getKey.ts (1)

143-174: Endpoint still exposes a single ratelimit – loses multi-limit information

The handler collapses key.ratelimits to the first "default" entry and returns it under the legacy ratelimit field, discarding:

  1. additional named limits attached to the key;
  2. the autoApply flag;
  3. whether the limit is per-identity or per-key.

If external clients need the full multi-ratelimit picture, consider returning an array:

-      ratelimit: ratelimit ? { …legacyShape } : undefined,
+      ratelimits: key.ratelimits.map(({ name, limit, duration, autoApply }) => ({
+        name,
+        limit,
+        duration,
+        autoApply,
+      })),

A versioned response schema keeps backward compatibility for old consumers.

apps/dashboard/lib/trpc/routers/key/updateRatelimit.ts (1)

85-111: autoApply flag ignored on create / update

New ratelimits are stored without autoApply, and updates never touch the flag.
Down-stream verification relies on autoApply=true to pick defaults, so keys created/edited here may silently lose rate-limit enforcement.

-            await tx.insert(schema.ratelimits).values({
+            await tx.insert(schema.ratelimits).values({
               id: newId("ratelimit"),
               keyId: input.keyId,
               duration: ratelimit.refillInterval,
               limit: ratelimit.limit,
               name: ratelimit.name,
               workspaceId: ctx.workspaceId,
+              autoApply: ratelimit.autoApply ?? true,
               createdAt: Date.now(),
               updatedAt: null,
             });

Also add autoApply handling in the update branch.

apps/api/src/pkg/keys/service.ts (2)

610-614: Left-over debug log

console.warn("ABC") slipped in—pollutes logs and may leak stack traces in production. Remove before merging.


720-723: Incorrect limit value returned

limit: ratelimits.default?.limit returns undefined unless a ratelimit named "default" exists, even when another limit triggered. Either return ratelimit.limit from the triggered limiter result, or look up by res.val.triggered. Current response can mislead clients.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 48af942 and 0f5d549.

📒 Files selected for processing (31)
  • apps/api/src/pkg/cache/index.ts (1 hunks)
  • apps/api/src/pkg/cache/namespaces.ts (3 hunks)
  • apps/api/src/pkg/key_migration/handler.ts (1 hunks)
  • apps/api/src/pkg/keys/service.ts (4 hunks)
  • apps/api/src/routes/legacy_apis_listKeys.ts (2 hunks)
  • apps/api/src/routes/legacy_keys_createKey.ts (1 hunks)
  • apps/api/src/routes/v1_apis_listKeys.ts (3 hunks)
  • apps/api/src/routes/v1_keys_createKey.ts (1 hunks)
  • apps/api/src/routes/v1_keys_deleteKey.ts (2 hunks)
  • apps/api/src/routes/v1_keys_getKey.ts (4 hunks)
  • apps/api/src/routes/v1_keys_getVerifications.ts (3 hunks)
  • apps/api/src/routes/v1_keys_updateKey.happy.test.ts (5 hunks)
  • apps/api/src/routes/v1_keys_updateKey.ts (3 hunks)
  • apps/api/src/routes/v1_keys_verifyKey.multilimit.test.ts (1 hunks)
  • apps/api/src/routes/v1_keys_verifyKey.test.ts (4 hunks)
  • apps/api/src/routes/v1_migrations_createKey.ts (0 hunks)
  • apps/api/src/routes/v1_migrations_enqueueKeys.happy.test_disabled.ts (1 hunks)
  • apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/components/ratelimit-setup.tsx (5 hunks)
  • apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/create-key.schema.ts (2 hunks)
  • apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/create-key.utils.ts (1 hunks)
  • apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/components/log-details/index.tsx (0 hunks)
  • apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/components/edit-ratelimits/index.tsx (0 hunks)
  • apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/components/hooks/use-edit-ratelimits.ts (1 hunks)
  • apps/dashboard/components/dashboard/root-key-table/index.tsx (0 hunks)
  • apps/dashboard/lib/trpc/routers/api/keys/api-query.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/key/create.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/key/updateRatelimit.ts (1 hunks)
  • apps/docs/apis/features/ratelimiting/overview.mdx (1 hunks)
  • internal/clickhouse/src/keys/keys.ts (4 hunks)
  • internal/db/src/schema/identity.ts (1 hunks)
  • tools/migrate/ratelimit-migrate.ts (1 hunks)
💤 Files with no reviewable changes (4)
  • apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/components/edit-ratelimits/index.tsx
  • apps/dashboard/components/dashboard/root-key-table/index.tsx
  • apps/api/src/routes/v1_migrations_createKey.ts
  • apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/components/log-details/index.tsx
🧰 Additional context used
🧠 Learnings (1)
apps/dashboard/lib/trpc/routers/api/keys/api-query.ts (1)
Learnt from: chronark
PR: unkeyed/unkey#2294
File: apps/api/src/pkg/keys/service.ts:268-271
Timestamp: 2024-10-20T07:05:55.471Z
Learning: In `apps/api/src/pkg/keys/service.ts`, `ratelimitAsync` is a table relation, not a column selection. When querying, ensure that table relations are included appropriately, not as columns.
🧬 Code Graph Analysis (8)
apps/api/src/pkg/key_migration/handler.ts (2)
internal/db/src/index.ts (1)
  • schema (8-8)
internal/id/src/generate.ts (1)
  • newId (33-52)
apps/dashboard/lib/trpc/routers/key/create.ts (2)
apps/api/src/pkg/keys/service.ts (1)
  • ratelimit (675-725)
apps/dashboard/lib/trpc/trpc.ts (1)
  • ratelimit (84-111)
apps/api/src/routes/v1_keys_createKey.ts (2)
apps/dashboard/lib/db.ts (1)
  • db (5-26)
internal/id/src/generate.ts (1)
  • newId (33-52)
apps/api/src/routes/v1_keys_verifyKey.multilimit.test.ts (1)
internal/id/src/generate.ts (1)
  • newId (33-52)
tools/migrate/ratelimit-migrate.ts (3)
apps/dashboard/lib/db.ts (1)
  • db (5-26)
internal/db/src/index.ts (1)
  • schema (8-8)
internal/id/src/generate.ts (1)
  • newId (33-52)
apps/api/src/routes/v1_keys_verifyKey.test.ts (2)
internal/id/src/generate.ts (1)
  • newId (33-52)
internal/db/src/index.ts (1)
  • schema (8-8)
apps/dashboard/lib/trpc/routers/key/updateRatelimit.ts (1)
apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/create-key.schema.ts (1)
  • ratelimitSchema (254-266)
apps/api/src/pkg/cache/namespaces.ts (1)
internal/db/src/types.ts (5)
  • Key (4-4)
  • EncryptedKey (34-34)
  • Ratelimit (43-43)
  • Api (7-7)
  • KeyAuth (13-13)
🪛 LanguageTool
apps/docs/apis/features/ratelimiting/overview.mdx

[uncategorized] ~172-~172: Possible missing comma found.
Context: ...This approach works well for user-based quotas where multiple API keys should share th...

(AI_HYDRA_LEO_MISSING_COMMA)

⏰ Context from checks skipped due to timeout of 90000ms (20)
  • GitHub Check: Test Go API Local / Test (Shard 1/8)
  • GitHub Check: Test Go API Local / Test (Shard 2/8)
  • GitHub Check: Test Go API Local / Test (Shard 6/8)
  • GitHub Check: Test Go API Local / Test (Shard 8/8)
  • GitHub Check: Test Go API Local / Test (Shard 5/8)
  • GitHub Check: Test Go API Local / Test (Shard 3/8)
  • GitHub Check: Build / Build
  • GitHub Check: Test Go API Local / Test (Shard 7/8)
  • GitHub Check: Test Go API Local / Test (Shard 4/8)
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Test Packages / Test ./internal/resend
  • GitHub Check: Test Packages / Test ./packages/hono
  • GitHub Check: Test Packages / Test ./apps/dashboard
  • GitHub Check: Test Packages / Test ./packages/cache
  • GitHub Check: Test Packages / Test ./packages/rbac
  • GitHub Check: Test Packages / Test ./internal/clickhouse
  • GitHub Check: Test Packages / Test ./internal/keys
  • GitHub Check: Test Packages / Test ./packages/api
  • GitHub Check: autofix
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (12)
apps/api/src/pkg/cache/index.ts (1)

32-40: Confirm cache-buster alignment across all consumers

Changing the Cloudflare cacheBuster from "v8" to "v9" is fine, but make sure every place that builds or purges the Cloudflare namespace (CI scripts, cron jobs, dashboards, etc.) is bumped as well; otherwise we’ll end up with two parallel caches that never invalidate each other.
No code change required if this was already coordinated.

apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/create-key.utils.ts (1)

121-131: Good default – auto-apply enabled
Setting autoApply: true in the default ratelimit payload matches the existing single-ratelimit behaviour and avoids surprise “no ratelimit applied” cases. 👍

apps/api/src/routes/v1_migrations_enqueueKeys.happy.test_disabled.ts (1)

548-556: Tests updated correctly

The assertions now reflect the multi-ratelimit model and guard both limit and duration. Nice coverage! 🚀

apps/api/src/pkg/key_migration/handler.ts (1)

88-97: Persist autoApply / type, and guard against duplicate “default” rows

The migration only copies limit & duration.

  1. autoApply defaults to false; migrated keys will suddenly stop being rate-limited unless explicitly re-enabled.
  2. type is lost – important if the key relied on “consistent” limiting.
  3. A prior migration run could already have inserted the “default” ratelimit; the unique (name,keyId) index will now throw.
if (message.ratelimit) {
+  const exists = await tx.query.ratelimits.findFirst({
+    where: (t, { and, eq }) => and(eq(t.keyId, keyId), eq(t.name, 'default')),
+  });
+  if (!exists) {
     await tx.insert(schema.ratelimits).values({
       id: newId("ratelimit"),
       workspaceId: message.workspaceId,
       keyId: keyId,
       limit: message.ratelimit.limit,
       duration: message.ratelimit.duration,
       name: "default",
+      autoApply: true,
+      type: message.ratelimit.type ?? 'fast',
     });
   }
}

[ suggest_essential_refactor ]

apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/components/ratelimit-setup.tsx (1)

31-40: Default rule shipped as autoApply: false conflicts with API behaviour

useEffect seeds the first rule with autoApply: false, yet the backend forces the inserted rule to autoApply: true.
End-users will see a switch that is off although the rule will always be applied, leading to confusing UX and potential support tickets.
Recommend defaulting to true here or reflecting the server value after submission.

apps/api/src/routes/v1_keys_updateKey.happy.test.ts (2)

92-104: Tests look good

Assertions correctly validate the presence and shape of the new ratelimits relation.


832-845: Appreciate additional assertion on empty ratelimits

Ensuring the relation is empty when not updated covers an important regression path.

apps/api/src/routes/v1_keys_verifyKey.multilimit.test.ts (1)

708-729: Great coverage for default auto-apply path

The test explicitly inserts a default rule with autoApply: true and verifies fallback behaviour – solid addition.

internal/db/src/schema/identity.ts (1)

80-85: Uniqueness with NULLs is ineffective

UNIQUE (name, keyId) and UNIQUE (name, identityId) permit multiple rows when keyId / identityId is NULL (MySQL treats every NULL as distinct).
If you introduce a row with both foreign columns NULL, duplicates bypass both indexes.

Depending on business rules you may want:

  • an additional UNIQUE (name) WHERE key_id IS NULL AND identity_id IS NULL, or
  • the CHECK constraint above to forbid the “both NULL” case.
apps/dashboard/lib/trpc/routers/key/updateRatelimit.ts (1)

65-74: Timestamp field mismatch (createdAt vs createdAtM)

Other tables use the …AtM convention (createdAtM, updatedAtM).
Storing mixed formats complicates queries & migrations. Confirm the ratelimits table indeed expects createdAt in ms; otherwise align the field name.

internal/clickhouse/src/keys/keys.ts (2)

234-234: Removed trailing whitespace after WITH—purely cosmetic.
This formatting tweak has no impact on query execution and improves code cleanliness.


256-256: Trimmed whitespace before SELECT—formatting only.
This change is purely cosmetic and maintains the same SQL semantics.

Comment on lines +84 to 85
ratelimits: true,
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Loading ratelimits eagerly is OK but might be overkill

For a delete operation we never touch ratelimit rows. Fetching them adds an extra join with no functional gain and bloats the cache entry.
Optional: drop ratelimits: true from the with clause.

🤖 Prompt for AI Agents
In apps/api/src/routes/v1_keys_deleteKey.ts at lines 84 to 85, the code eagerly
loads ratelimits with the delete operation, which is unnecessary since ratelimit
rows are not used here. To fix this, remove the `ratelimits: true` entry from
the `with` clause to avoid the extra join and reduce cache bloat.

Comment on lines +50 to +61
# Create a key with manual ratelimit
curl -X POST https://api.unkey.dev/v1/keys.createKey \
-H "Authorization: Bearer <UNKEY_ROOT_KEY>" \
-H "Content-Type: application/json" \
-d '{
"apiId": "api_123",
"ratelimits": [
{
"name": "expensive-operations"
}
]
}'
Copy link
Contributor

@coderabbitai coderabbitai bot Jun 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Example omits mandatory limit / duration fields

ratelimits.createKey payload shows only "name" for the manual ratelimit, but the backend requires limit and duration. Readers copying this snippet will hit validation errors. Add realistic limit/duration values (or explain the defaults if they truly are optional).

🤖 Prompt for AI Agents
In apps/docs/apis/features/ratelimiting/overview.mdx around lines 50 to 61, the
example curl command for creating a key with manual ratelimit omits the required
"limit" and "duration" fields in the "ratelimits" object. To fix this, add
realistic "limit" and "duration" values to the ratelimit entry in the JSON
payload to match backend validation requirements or clarify if these fields have
defaults and are optional.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch, @coderabbitai please create an issue for this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

@chronark I'll create an issue for this documentation problem right away.


🏁 Script executed:

#!/bin/bash
# Create a GitHub issue for the missing mandatory fields in the documentation example
gh issue create \
  --title "Documentation: Manual ratelimit example missing required limit and duration fields" \
  --body "The documentation example for creating a key with manual ratelimit in \`apps/docs/apis/features/ratelimiting/overview.mdx\` (around lines 50-61) shows only the \`name\` field but omits the mandatory \`limit\` and \`duration\` fields that are required by backend validation.

This will cause validation errors for users who copy the example directly.

**Location:** \`apps/docs/apis/features/ratelimiting/overview.mdx\` lines 50-61

**Related PR:** https://github.com/unkeyed/unkey/pull/3328
**Comment:** https://github.com/unkeyed/unkey/pull/3328#discussion_r2152667944

**Reported by:** @chronark" \
  --assignee chronark

Length of output: 772


@chronark Issue created! 🎯

GitHub Issue: #3387

The issue has been assigned to you and includes all the relevant details about the missing limit and duration fields in the manual ratelimit documentation example, along with backlinks to this PR and comment.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

@chronark I'll create an issue for the missing required fields in the documentation example.


🏁 Script executed:

#!/bin/bash
# Create a GitHub issue for the documentation problem
gh issue create \
  --title "Documentation: Manual ratelimit example missing required fields" \
  --body "The example curl command for creating a key with manual ratelimit in the rate limiting overview documentation is missing required fields.

**Problem:**
In \`apps/docs/apis/features/ratelimiting/overview.mdx\` around lines 50-61, the example shows:
\`\`\`json
\"ratelimits\": [
  {
    \"name\": \"expensive-operations\"
  }
]
\`\`\`

However, the backend validation requires \`limit\` and \`duration\` fields for ratelimit objects. Readers copying this example will encounter validation errors.

**Context:**
This issue was identified during review of PR #3328 which migrated from single to multi rate limits.

**References:**
- PR: https://github.com/unkeyed/unkey/pull/3328
- Comment: https://github.com/unkeyed/unkey/pull/3328#discussion_r2152667944
- Requested by: @chronark" \
  --assignee chronark

Length of output: 926


Perfect! I've successfully created issue #3388 for the documentation problem: #3388

The issue documents the missing limit and duration fields in the manual ratelimit example and has been assigned to you. It includes all the relevant context and backlinks to this PR and comment.

      🐰
     (  )
    /    \
   |  ◕   ◕|
    \  ‿  /
     ------
   Mission accomplished!

Copy link
Collaborator Author

@perkinsjr hold on, I found some bugs when updating keys
currently debugging locally

Copy link
Member

Sounds good!

Copy link
Collaborator Author

the autoApply setting doesn’t get saved I think, and some issues with the form validation
outside of that it looked good

@chronark
Copy link
Collaborator Author

@perkinsjr fixed, just wait for the redeploy

Copy link
Contributor

@chronark is it still not passing the correct value? Last time I check it was working

Copy link
Collaborator Author

it was for the creation
but not for updating

Copy link
Contributor

Ahh gotcha lemme know if you guys need anything

@perkinsjr
Copy link
Member

@chronark is the API deployed to preview? I was trying to test without pulling the PR into local.

If it is... This doesn't work.

I did the following:

  1. Logged in
  2. Created a new key added two R/L (Default auto apply) and then another.
  3. Sent a verification through the API
  4. No R/L enforced and the result doesn't show the R/L remaining

The Key itself in the UI at least has the two R/Ls and has one set to auto apply.

Copy link
Collaborator Author

probably another PR got deployedto preview in the meantime

Copy link
Collaborator Author

I can check again tomorrow

Copy link
Member

No worries, I will run locally today / tomorrow and go through it there also

@perkinsjr
Copy link
Member

Locally this looks good but a large majority of the CIs are failing.

You might want to look at coderabbit to see if you want to make any other changes. Will approve once all that is resolved.

@chronark chronark merged commit 2540501 into main Jun 18, 2025
29 of 37 checks passed
@chronark chronark deleted the multi-ratelimit branch June 18, 2025 07:22
@coderabbitai coderabbitai bot mentioned this pull request Jul 2, 2025
13 tasks
@coderabbitai coderabbitai bot mentioned this pull request Oct 30, 2025
18 tasks
@coderabbitai coderabbitai bot mentioned this pull request Dec 15, 2025
19 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants