-
-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Add Stripe (Next.js/TypeScript) and Anthropic Claude API rules #345
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,115 @@ | ||||||||||||||||||
| --- | ||||||||||||||||||
| description: Anthropic Claude API integration with streaming responses, tool use, and production-grade error handling for TypeScript and Next.js | ||||||||||||||||||
| globs: **/claude.ts, **/anthropic.ts, **/api/chat/route.ts, **/*.ts | ||||||||||||||||||
| alwaysApply: false | ||||||||||||||||||
| --- | ||||||||||||||||||
|
|
||||||||||||||||||
| # Anthropic Claude API — TypeScript / Next.js | ||||||||||||||||||
|
|
||||||||||||||||||
| ## Client Setup | ||||||||||||||||||
| ```ts | ||||||||||||||||||
| import Anthropic from "@anthropic-ai/sdk" | ||||||||||||||||||
|
|
||||||||||||||||||
| // Singleton — reuse across requests | ||||||||||||||||||
| const anthropic = new Anthropic({ | ||||||||||||||||||
| apiKey: process.env.ANTHROPIC_API_KEY, | ||||||||||||||||||
| }) | ||||||||||||||||||
| ``` | ||||||||||||||||||
|
|
||||||||||||||||||
| ## Basic Message | ||||||||||||||||||
| ```ts | ||||||||||||||||||
| const message = await anthropic.messages.create({ | ||||||||||||||||||
| model: "claude-opus-4-6", | ||||||||||||||||||
| max_tokens: 1024, | ||||||||||||||||||
| messages: [ | ||||||||||||||||||
| { role: "user", content: "Explain this code: ..." } | ||||||||||||||||||
| ], | ||||||||||||||||||
| }) | ||||||||||||||||||
|
|
||||||||||||||||||
| const text = message.content[0].type === "text" ? message.content[0].text : "" | ||||||||||||||||||
| ``` | ||||||||||||||||||
|
|
||||||||||||||||||
| ## Streaming (Next.js Edge) | ||||||||||||||||||
| ```ts | ||||||||||||||||||
| // app/api/chat/route.ts | ||||||||||||||||||
| import Anthropic from "@anthropic-ai/sdk" | ||||||||||||||||||
|
|
||||||||||||||||||
| export const runtime = "edge" | ||||||||||||||||||
|
|
||||||||||||||||||
| export async function POST(req: Request) { | ||||||||||||||||||
| const { messages } = await req.json() | ||||||||||||||||||
|
|
||||||||||||||||||
| const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY }) | ||||||||||||||||||
|
|
||||||||||||||||||
| const stream = anthropic.messages.stream({ | ||||||||||||||||||
| model: "claude-opus-4-6", | ||||||||||||||||||
| max_tokens: 2048, | ||||||||||||||||||
| system: "You are a helpful assistant.", | ||||||||||||||||||
| messages, | ||||||||||||||||||
| }) | ||||||||||||||||||
|
Comment on lines
+40
to
+49
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win Validate and bound the incoming messages before forwarding them.
🤖 Prompt for AI Agents |
||||||||||||||||||
|
|
||||||||||||||||||
| return new Response(stream.toReadableStream(), { | ||||||||||||||||||
| headers: { "Content-Type": "text/event-stream" }, | ||||||||||||||||||
| }) | ||||||||||||||||||
|
Comment on lines
+44
to
+53
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift Document mid-stream failure handling. A 🤖 Prompt for AI Agents |
||||||||||||||||||
| } | ||||||||||||||||||
| ``` | ||||||||||||||||||
|
|
||||||||||||||||||
| ## Tool Use | ||||||||||||||||||
| ```ts | ||||||||||||||||||
| const response = await anthropic.messages.create({ | ||||||||||||||||||
| model: "claude-opus-4-6", | ||||||||||||||||||
| max_tokens: 1024, | ||||||||||||||||||
| tools: [{ | ||||||||||||||||||
| name: "get_weather", | ||||||||||||||||||
| description: "Get current weather for a location", | ||||||||||||||||||
| input_schema: { | ||||||||||||||||||
| type: "object", | ||||||||||||||||||
| properties: { | ||||||||||||||||||
| location: { type: "string", description: "City name" }, | ||||||||||||||||||
| }, | ||||||||||||||||||
| required: ["location"], | ||||||||||||||||||
| }, | ||||||||||||||||||
| }], | ||||||||||||||||||
| messages: [{ role: "user", content: "What is the weather in Paris?" }], | ||||||||||||||||||
| }) | ||||||||||||||||||
| ``` | ||||||||||||||||||
|
|
||||||||||||||||||
| ## System Prompts | ||||||||||||||||||
| ```ts | ||||||||||||||||||
| const system = `You are [role]. | ||||||||||||||||||
|
|
||||||||||||||||||
| Your task: [specific task] | ||||||||||||||||||
|
|
||||||||||||||||||
| Rules: | ||||||||||||||||||
| - [rule 1] | ||||||||||||||||||
| - [rule 2] | ||||||||||||||||||
|
|
||||||||||||||||||
| Output format: [format]` | ||||||||||||||||||
| ``` | ||||||||||||||||||
|
|
||||||||||||||||||
| ## Error Handling | ||||||||||||||||||
| ```ts | ||||||||||||||||||
| try { | ||||||||||||||||||
| const message = await anthropic.messages.create(...) | ||||||||||||||||||
| } catch (err) { | ||||||||||||||||||
| if (err instanceof Anthropic.APIStatusError) { | ||||||||||||||||||
| if (err.status === 429) { /* rate limited — backoff and retry */ } | ||||||||||||||||||
| if (err.status === 529) { /* overloaded — retry with exponential backoff */ } | ||||||||||||||||||
| } | ||||||||||||||||||
|
Comment on lines
+95
to
+98
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n '"`@anthropic-ai/sdk`"|APIStatusError|APIError' \
--glob 'package.json' \
--glob '*lock*' \
--glob '!node_modules/**'Repository: PatrickJS/awesome-cursorrules Length of output: 167 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "Candidate file lines 80-110:"
sed -n '80,110p' rules/anthropic-claude-api-typescript.mdc || true
echo
echo "Search for Anthropic error class mentions in tracked files:"
rg -n 'Anthropic\.(APIStatusError|APIError)' . --glob '!node_modules/**' || true
echo
echo "Lock/package references:"
for f in package.json package-lock.json pnpm-lock.yaml yarn.lock bun.lockb bun.lock npm-shrinkwrap.json; do
[ -f "$f" ] && echo "--- $f ---" && rg -n '"?`@anthropic-ai/sdk`"?|APIStatusError|APIError' "$f" || true
doneRepository: PatrickJS/awesome-cursorrules Length of output: 1210 🌐 Web query:
💡 Result: In the Anthropic TypeScript SDK ( Citations:
🌐 Web query:
💡 Result: In the Anthropic TypeScript SDK ( Citations:
🌐 Web query:
💡 Result: In the context of the Anthropic TypeScript/JavaScript SDK, APIError is a primary base class for errors thrown when the API returns a non-success status code (4xx or 5xx) [1]. APIStatusError is an internal method, makeStatusError, used by the client to generate these specific error instances based on the HTTP status code received [2]. Key points: - APIError: This is the parent class for all errors related to failed API requests [3][1]. When an API call returns a 4xx or 5xx status code, the SDK throws a subclass of APIError (such as BadRequestError, AuthenticationError, or RateLimitError) [1]. - makeStatusError (Internal): This is an internal client method (e.g., Anthropic.makeStatusError) that inspects the HTTP response and instantiates the appropriate APIError subclass [2]. - Error Handling: Developers should generally catch APIError to handle various API-related issues [1]. For example: const client = new Anthropic; try { await client.messages.create(...); } catch (err) { if (err instanceof Anthropic.APIError) { console.log(err.status); // HTTP status code (e.g., 400, 401) console.log(err.name); // Specific error name (e.g., AuthenticationError) } } [1] Citations:
🌐 Web query:
💡 Result: In the Anthropic TypeScript SDK ( Citations:
🌐 Web query:
💡 Result: APIError is a base exception class used by certain software development kits (SDKs), such as the Anthropic TypeScript SDK, to handle errors that occur when interacting with an API [1]. When an SDK is unable to successfully connect to an API or receives a non-success HTTP status code (typically 4xx or 5xx ranges), it throws a subclass of APIError [1]. These subclasses allow developers to inspect details about the failure, such as the status code, the specific error type (e.g., BadRequestError, AuthenticationError, or RateLimitError), and response headers [1]. While APIError is a standard convention in several client libraries for representing API-related failures, APIStatusError is not a widely documented or standard term across major SDKs based on available information. It is possible that APIStatusError refers to a library-specific error naming convention or a custom implementation in a codebase not covered by general documentation [1]. Example usage for handling APIError in TypeScript: const message = await client.messages.create({... }).catch(async (err) => { if (err instanceof Anthropic.APIError) { console.log(err.status); console.log(err.name); } else { throw err; } }); Citations: Use the TypeScript SDK’s
Proposed fix- if (err instanceof Anthropic.APIStatusError) {
+ if (err instanceof Anthropic.APIError) {📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
| throw err | ||||||||||||||||||
| } | ||||||||||||||||||
| ``` | ||||||||||||||||||
|
|
||||||||||||||||||
| ## Model Selection | ||||||||||||||||||
| - claude-haiku-4-5-20251001: fast and cheap, good for classification, short tasks | ||||||||||||||||||
| - claude-sonnet-4-6: balanced speed/intelligence for most tasks | ||||||||||||||||||
| - claude-opus-4-6: maximum intelligence for complex reasoning | ||||||||||||||||||
|
Comment on lines
+103
to
+106
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Refresh the model-selection recommendations. These IDs remain valid, but the descriptions are stale for the current model lineup: Anthropic now lists Claude Fable 5 as the highest-capability model, with Opus 5 and Sonnet 5 as current alternatives. Either update this table or label the 4.6 models as compatibility choices. (platform.claude.com) 🤖 Prompt for AI Agents |
||||||||||||||||||
|
|
||||||||||||||||||
| ## Rules | ||||||||||||||||||
| - Always set max_tokens explicitly — the default may truncate responses | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Correct the
🤖 Prompt for AI Agents |
||||||||||||||||||
| - Use a singleton client instance, not a new one per request | ||||||||||||||||||
| - Handle 429 (rate limit) and 529 (overloaded) with exponential backoff | ||||||||||||||||||
| - Monitor usage.input_tokens + usage.output_tokens per request for cost control | ||||||||||||||||||
| - Trim conversation history before sending — avoid token bloat on long sessions | ||||||||||||||||||
| - Never hardcode API keys — use process.env.ANTHROPIC_API_KEY | ||||||||||||||||||
| - Do not use deprecated claude-1 or claude-2 model IDs | ||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| --- | ||
| description: Secure Stripe integration with typed checkout sessions, idempotent webhook verification, and subscription lifecycle handling in Next.js TypeScript | ||
| globs: **/stripe.ts, **/webhook/route.ts, **/checkout/route.ts, **/*.ts | ||
| alwaysApply: false | ||
| --- | ||
|
|
||
| # Stripe Integration — Next.js TypeScript | ||
|
|
||
| ## Client Setup | ||
| ```ts | ||
| // lib/stripe.ts | ||
| import Stripe from "stripe" | ||
|
|
||
| export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { | ||
|
Comment on lines
+3
to
+14
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win Narrow the rule scope and enforce a server-only Stripe client.
🤖 Prompt for AI Agents |
||
| apiVersion: "2024-12-18.acacia", | ||
| typescript: true, | ||
| }) | ||
| ``` | ||
|
|
||
| ## Checkout Session | ||
| ```ts | ||
| export async function createCheckoutSession({ | ||
| priceId, | ||
| userId, | ||
| successUrl, | ||
| cancelUrl, | ||
| }: { | ||
| priceId: string | ||
| userId: string | ||
| successUrl: string | ||
| cancelUrl: string | ||
| }) { | ||
| return stripe.checkout.sessions.create({ | ||
| mode: "subscription", | ||
| payment_method_types: ["card"], | ||
| line_items: [{ price: priceId, quantity: 1 }], | ||
| success_url: successUrl, | ||
| cancel_url: cancelUrl, | ||
| metadata: { userId }, | ||
| subscription_data: { | ||
| metadata: { userId }, | ||
| }, | ||
| }) | ||
|
Comment on lines
+22
to
+43
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win Validate caller-controlled Stripe identifiers and URLs on the server. The checkout and portal helpers accept arbitrary
📍 Affects 1 file
🤖 Prompt for AI Agents |
||
| } | ||
| ``` | ||
|
|
||
| ## Webhooks — Critical Patterns | ||
| ```ts | ||
| // app/api/stripe/webhook/route.ts | ||
| import { headers } from "next/headers" | ||
|
|
||
| export async function POST(req: Request) { | ||
| const body = await req.text() | ||
| const signature = headers().get("stripe-signature")! | ||
|
Comment on lines
+50
to
+54
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '--- candidate file excerpt ---\n'
sed -n '1,120p' rules/stripe-integration-nextjs-typescript.mdc || true
printf '\n--- related files mentioning next/headers ---\n'
rg -n 'from ["\x27]next/headers["\x27]|headers\(\)|stripe-signature' -S . || true
printf '\n--- package/config hints ---\n'
for f in package.json tsconfig.json next.config.js next.config.mjs app.config.js app.config.mjs; do
[ -f "$f" ] && { echo "### $f"; sed -n '1,220p' "$f"; }
doneRepository: PatrickJS/awesome-cursorrules Length of output: 258 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- candidate file excerpt ---'
sed -n '1,120p' rules/stripe-integration-nextjs-typescript.mdc || true
printf '%s\n' ''
printf '%s\n' '--- related files mentioning next/headers ---'
rg -n 'from ["'"'"']next/headers["'"'"']|headers\(\)|stripe-signature' -S . || true
printf '%s\n' ''
printf '%s\n' '--- package/config hints ---'
for f in package.json tsconfig.json next.config.js next.config.mjs app.config.js app.config.mjs; do
[ -f "$f" ] && { echo "### $f"; sed -n '1,220p' "$f"; }
doneRepository: PatrickJS/awesome-cursorrules Length of output: 4586 🌐 Web query:
💡 Result: In Next.js 15 and later, the Citations:
Await This rule targets this code path, and Next.js’ App Router 🤖 Prompt for AI Agents |
||
|
|
||
| let event: Stripe.Event | ||
| try { | ||
| event = stripe.webhooks.constructEvent( | ||
| body, | ||
| signature, | ||
| process.env.STRIPE_WEBHOOK_SECRET! | ||
|
Comment on lines
+49
to
+61
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Make the webhook example compile as a standalone route. The route uses 🤖 Prompt for AI Agents |
||
| ) | ||
| } catch { | ||
| return new Response("Webhook signature verification failed", { status: 400 }) | ||
| } | ||
|
|
||
| switch (event.type) { | ||
| case "checkout.session.completed": { | ||
| const session = event.data.object as Stripe.Checkout.Session | ||
| await handleCheckoutComplete(session) | ||
| break | ||
| } | ||
| case "customer.subscription.deleted": { | ||
| const sub = event.data.object as Stripe.Subscription | ||
| await handleSubscriptionCancelled(sub) | ||
| break | ||
|
Comment on lines
+67
to
+76
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files | rg '(^|/)rules/stripe-integration-nextjs-typescript\.mdc$|stripe|webhook' || true
echo "== target file size and relevant sections =="
wc -l rules/stripe-integration-nextjs-typescript.mdc 2>/dev/null || true
sed -n '1,130p' rules/stripe-integration-nextjs-typescript.mdc 2>/dev/null | cat -n
echo "== related references =="
rg -n "webhook|idempotent|idempotency|Stripe\\.|handles?|checkout.session.completed|signature|retry|200|database|async_mutex|prisma|supabase|firebase" rules/stripe-integration-nextjs-typescript.mdc . --glob '!node_modules' --glob '!dist' --glob '!build' 2>/dev/null | head -200Repository: PatrickJS/awesome-cursorrules Length of output: 31364 🌐 Web query:
💡 Result: To handle duplicate Stripe webhook events in a Node.js integration, you must implement idempotency logic to ensure your application handles the same event only once, even if Stripe delivers it multiple times [1][2][3]. Key Concepts and Strategy: 1. Use the Event ID as the Deduplication Key: Every Stripe webhook event includes a unique ID (e.g., evt_123) in the payload [1][3]. This ID remains consistent across all retry attempts, making it the correct key for deduplication [3]. Do not confuse this with the Idempotency-Key header, which is used for API requests you send to Stripe to prevent duplicate actions (e.g., double-charging) [3][4][5]. 2. Atomic Processing: To prevent race conditions, use an atomic operation to record the event ID in your database (e.g., PostgreSQL or Redis) [6][3]. A common pattern is to use an INSERT statement with a unique constraint on the event ID (e.g., ON CONFLICT DO NOTHING) [6][3][5]. This ensures that if two identical requests arrive simultaneously, only one will succeed in marking the event as processed [3][5]. 3. Immediate 2xx Response: Your webhook endpoint should return a 2xx HTTP status code as quickly as possible, ideally before performing complex business logic [1][7]. This signals to Stripe that the event was received, preventing unnecessary retries [3]. 4. Implementation Pattern: - Receive and verify the event signature using Stripe's official library (stripe.webhooks.constructEvent) [1][8]. - Attempt to atomically insert the event.id into your "processed_events" database table [6][9]. - If the insert fails because the ID already exists, the event has already been processed; return a 200 OK immediately and exit [6][3]. - If the insert succeeds, proceed with your business logic (e.g., updating database records) [6]. 5. TTL and Storage: You do not need to store event IDs indefinitely. A duration covering Stripe's retry window (typically up to 3 days) plus a safety margin is generally sufficient [3]. Important Distinctions: - Webhook vs. API Idempotency: Incoming Stripe events require you to perform deduplication [3][5]. Outgoing API requests to Stripe should use the Idempotency-Key header to ensure your actions are not duplicated if your request is retried [4][5]. - Thin vs. Snapshot Events: During migration periods, you may receive both thin and snapshot versions of an event [9]. Use the provided snapshot_event ID field in thin events as your idempotency key to link them and prevent redundant processing [9]. Citations:
Implement durable webhook deduplication before dispatch. This route dispatches after signature verification but does not atomically track 🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
|
|
||
| return new Response("ok") | ||
| } | ||
| ``` | ||
|
|
||
| ## Customer Portal | ||
| ```ts | ||
| export async function createPortalSession(customerId: string, returnUrl: string) { | ||
| return stripe.billingPortal.sessions.create({ | ||
| customer: customerId, | ||
| return_url: returnUrl, | ||
| }) | ||
| } | ||
| ``` | ||
|
|
||
| ## Environment Variables | ||
| ```env | ||
| STRIPE_SECRET_KEY=sk_live_... | ||
| NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_... | ||
| STRIPE_WEBHOOK_SECRET=whsec_... | ||
| NEXT_PUBLIC_STRIPE_PRICE_ID=price_... | ||
| ``` | ||
|
|
||
| ## Rules | ||
| - Webhook body: always read as raw text (req.text()), never parse as JSON before constructEvent | ||
| - Idempotent handlers: webhooks can fire multiple times — check if already processed before writing to DB | ||
| - Fast response: return 200 immediately, offload heavy work to a queue if needed | ||
| - Metadata: always attach userId to both metadata and subscription_data.metadata | ||
| - Never trust client-submitted amounts — verify server-side via webhook | ||
| - Never store raw card data — delegate PCI scope to Stripe | ||
| - Never skip signature verification — call constructEvent on every webhook | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Correct the linked pack’s published count.
The README says “27 production-ready rules,” while the linked page currently advertises “25 production-ready rule files.” Update the README or the linked page so the catalog does not publish conflicting counts. (moukie76.github.io)
🤖 Prompt for AI Agents