用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/ForceInjection/domain-driven-design-skills --skill convex-patterns命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
Conduct deep academic research for philosophy, neuroscience, cognitive science, and theoretical computer science (computability, complexity, AI theory, logic). Use when user asks to: research academic topics, find scholarly papers, conduct literature reviews, analyze citations, synthesize research findings, explore philosophical arguments, investigate consciousness/cognition, study computability/decidability/Turing machines, or analyze academic debates. Triggers on: 'research papers', 'literature review', 'academic sources', 'scholarly articles', 'philosophy of mind', 'computability theory', 'neuroscience studies', 'find papers on', 'what does the research say'.
Create clear action plans with steps, success criteria, and risk awareness. Use before implementing features, making changes, starting projects, or anytime you need a roadmap to success. Triggers on "plan this", "how should we approach", "what's the strategy", "steps to complete", or when facing complex multi-step work.
Add keyboard navigation to a feature using CommandRegistryService. Use when implementing keyboard shortcuts, vim-style navigation, or hotkeys for a page or component.
正在显示 SKILL.md
基于 SOC 职业分类
| name | convex-patterns |
| description | Convex backend patterns with security, validation, and performance best practices |
Purpose: Enforce secure, performant Convex backend (validation, auth, race prevention, rate limiting)
| Pattern | ✅ DO | ❌ AVOID |
|---|---|---|
| Files | snake_case.ts | kebab-case.ts (deploy fails) |
| Fields | snake_case | camelCase |
| Validation | args: v.object({...}) | No validators (insecure!) |
| Auth | Check every mutation | Trust client |
| Queries | .withIndex() | Table scans |
⚠️ ALL functions PUBLIC by default
Always:
ctx.auth.getUserIdentity())⚠️ NEVER share CONVEX_URL publicly (GitHub, docs, screenshots)
import { mutation } from "./_generated/server"
import { v } from "convex/values"
export const createOrder = mutation({
args: {
amount: v.number(),
customer_name: v.optional(v.string())
},
returns: v.id("orders"),
handler: async (ctx, { amount, customer_name }) => {
// 1. Auth
const identity = await ctx.auth.getUserIdentity()
if (!identity) throw new Error('Unauthorized')
// 2. Validate beyond schema
if (amount <= 0 || amount > 1000000) {
throw new Error('Invalid amount')
}
// 3. Insert
return await ctx.db.insert("orders", {
amount,
customer_name: customer_name ?? null,
user_id: identity.subject,
status: "pending"
})
}
})
export const updateOrder = mutation({
args: { order_id: v.id("orders"), status: v.string() },
handler: async (ctx, { order_id, status }) => {
// 1. Auth
const identity = await ctx.auth.getUserIdentity()
if (!identity) throw new Error('Unauthorized')
// 2. Fetch
const order = await ctx.db.get(order_id)
if (!order) throw new Error('Not found')
// 3. Verify ownership BEFORE update
if (order.user_id !== identity.subject) {
throw new Error('Access denied')
}
// 4. Update
await ctx.db.patch(order_id, { status })
}
})
// Schema with indexes
export default defineSchema({
users: defineTable({
email: v.string(),
status: v.string()
})
.index("by_email", ["email"])
.index("by_status", ["status"])
})
// ✅ Use index (O(log n))
const user = await ctx.db
.query('users')
.withIndex('by_email', q => q.eq('email', email))
.first()
// ❌ Table scan (O(n))
const user = await ctx.db
.query('users')
.filter(q => q.eq(q.field('email'), email))
.first()
Pattern 1: Check-Before-Insert
// Schema with composite index
defineTable({
user_id: v.string(),
session_id: v.id('sessions'),
data: v.string()
}).index('by_user_session', ['user_id', 'session_id'])
// Mutation (safe to retry)
const existing = await ctx.db
.query('entries')
.withIndex('by_user_session', q =>
q.eq('user_id', userId).eq('session_id', session_id)
)
.first()
if (existing) return existing._id // Idempotent
return await ctx.db.insert('entries', { user_id: userId, session_id, data })
Pattern 2: Idempotency Keys (Actions)
// Schema
defineTable({
key: v.string(),
result: v.any(),
created_at: v.number()
}).index('by_key', ['key'])
// Action with key
export const createPayment = action({
args: { idempotency_key: v.string(), amount: v.number() },
handler: async (ctx, { idempotency_key, amount }) => {
// Check if processed
const existing = await ctx.runMutation(api.payments.checkKey, {
key: idempotency_key
})
if (existing) return existing
// Process (side effect)
const charge = await stripe.charges.create({ amount })
const orderId = await ctx.runMutation(api.orders.create, { amount })
const result = { orderId, chargeId: charge.id }
// Store result
await ctx.runMutation(api.payments.storeResult, {
: idempotency_key,
result
})
result
}
})
Client: Generate key once, safe to retry
const [key] = useState(() => uuid())
await createPayment({ idempotency_key: key, amount: 1000 })
Pattern 3: Webhook Deduplication
// Schema
defineTable({
event_id: v.string(), // External event ID
event_type: v.string(),
processed_at: v.number()
}).index('by_event_id', ['event_id'])
// Mutation
const existing = await ctx.db
.query('webhook_events')
.withIndex('by_event_id', q => q.eq('event_id', event_id))
.first()
if (existing) return { processed: false, reason: 'duplicate' }
// Process event...
await ctx.db.insert('webhook_events', { event_id, event_type, processed_at: Date.now() })
Use for: Webhooks, public API endpoints, external integrations
Critical: HTTP Actions use CONVEX_SITE_URL (not CONVEX_URL)
| Use Case | URL | Why |
|---|---|---|
| Mutations/Queries (client SDK) | CONVEX_URL | Internal app communication |
| HTTP Actions (webhooks) | CONVEX_SITE_URL | Public HTTP endpoints |
// convex/http.ts
import { httpRouter } from "convex/server"
import { httpAction } from "./_generated/server"
const http = httpRouter()
http.route({
path: "/stripe/webhook",
method: "POST",
handler: httpAction(async (ctx, request) => {
const signature = request.headers.get("stripe-signature")
const body = await request.text()
// 1. Verify signature BEFORE processing
if (!verifyStripeSignature(body, signature, STRIPE_WEBHOOK_SECRET)) {
return new Response("Invalid signature", { status: 401 })
}
// 2. Check timestamp (replay attack prevention)
const event = JSON.parse(body)
const eventTime = event.created * 1000
if (Date.now() - eventTime > 5 * 60 * 1000) {
return new (, { : })
}
ctx.(api.., {
: event.,
: event.,
: event..
})
(, { : })
})
})
http
Why this matters:
Webhook URL: https://YOUR_SITE.convex.site/stripe/webhook (uses CONVEX_SITE_URL)
import { RateLimiter } from '@convex-dev/ratelimiter'
const limiter = new RateLimiter(components.rateLimiter, {
createOrder: { kind: 'token bucket', rate: 10, period: 60_000 }
})
export const createOrder = mutation({
handler: async (ctx, { amount }) => {
const identity = await ctx.auth.getUserIdentity()
if (!identity) throw new Error('Unauthorized')
await limiter.limit(ctx, 'createOrder', { key: identity.subject })
return await ctx.db.insert("orders", { amount, user_id: identity.subject })
}
})
Common limits:
⚠️ CRITICAL: Convex requires snake_case for files in convex/
✅ convex/stripe_webhook.ts
✅ convex/user_queries.ts
❌ convex/stripe-webhook.ts (deploy fails)
Error: "Path component X can only contain alphanumeric, underscores, periods"
Always snake_case:
await ctx.db.insert("orders", {
transaction_id: "abc", // ✅
customer_name: "John", // ✅
_creationTime: Date.now(), // ✅ System field
userId: identity.subject // ❌ Should be user_id
})
| Type | Usage |
|---|---|
| Primitives | v.string(), v.number(), v.boolean(), v.null() |
| ID | v.id("table_name") |
| Optional | v.optional(v.string()), v.nullable(v.number()) |
| Object | v.object({ name: v.string(), age: v.number() }) |
| Array | v.array(v.string()) |
| Union | v.union(v.literal("a"), v.literal("b")) |
| Any | v.any() (use sparingly) |
Progressive disclosure for deep dives:
resources/schema-design.md - Table design, index patternsresources/auth-patterns.md - Comprehensive authresources/performance.md - Query optimization, caching