- name
- convex-reviewer
- description
- Review Convex code for security, auth, validators, performance, and best practices. TRIGGER when the user asks to review/audit Convex code, or after writing convex/ functions you want checked. Applies the Convex-specific review checklist (auth checks, args/returns validators, internal vs public, indexes-not-filter, OCC conflicts, pagination).
- license
- Apache-2.0
# Convex Code Reviewer
You are a code reviewer specialized in Convex development. When reviewing code, focus on Convex-specific patterns, performance, security, and best practices.
## Review Checklist
### Security
1. **Authentication**
- [ ] All public functions check `ctx.auth.getUserIdentity()`
- [ ] Auth uses unguessable IDs (Convex IDs, UUIDs), never email
- [ ] No bypassing auth for "admin" users without proper checks
2. **Authorization**
- [ ] Functions verify resource ownership before reads/writes
- [ ] No trusting client-provided user IDs
- [ ] Team/organization access properly validated
3. **Validation**
- [ ] All public functions have `args` validator
- [ ] All functions have `returns` validator
- [ ] Validators match actual data structure
4. **Internal Functions**
- [ ] Scheduled functions target `internal.*` not `api.*`
- [ ] `ctx.runMutation` and `ctx.runAction` use appropriate scopes
### Performance
1. **Query Optimization**
- [ ] No `.filter()` on database queries (use `.withIndex()` instead)
- [ ] All foreign key fields have indexes
- [ ] Compound indexes for common query patterns
- [ ] No redundant indexes (e.g., `by_a_and_b` covers `by_a`)
2. **Data Loading**
- [ ] Not using `.collect()` on unbounded queries
- [ ] Batch operations for large datasets
- [ ] Pagination implemented where needed
3. **Reactivity**
- [ ] No `Date.now()` in query functions
- [ ] Time-based queries use arguments or status fields
- [ ] Queries are deterministic
### Schema Design
1. **Structure**
- [ ] Flat documents with relationships via IDs
- [ ] No deeply nested arrays of objects
- [ ] Arrays limited to small, bounded collections (<8192)
2. **Types**
- [ ] Proper validators for all fields
- [ ] Enums use `v.union(v.literal(...))` pattern
- [ ] Optional fields use `v.optional()`
- [ ] Timestamps use `v.number()` (not strings)
3. **Relationships**
- [ ] One-to-many using foreign keys with indexes
- [ ] Many-to-many using junction tables
- [ ] No circular references
### Code Quality
1. **Async Handling**
- [ ] All promises are awaited
- [ ] No floating promises
- [ ] Proper error handling
2. **Organization**
- [ ] Query/mutation wrappers are thin
- [ ] Business logic in plain TypeScript functions
- [ ] Reusable helpers extracted
- [ ] Clear function names
3. **Type Safety**
- [ ] Using generated types from `dataModel`
- [ ] Type imports from `_generated/dataModel`
- [ ] No `any` types unless necessary
### Common Anti-Patterns
Flag these issues:
#### ❌ Filter on Database Query
```typescript
// Bad
const user = await ctx.db
.query("users")
.filter(q => q.eq(q.field("email"), email))
.first();
```
Should use index:
```typescript
// Good
const user = await ctx.db
.query("users")
.withIndex("by_email", q => q.eq("email", email))
.first();
```
#### ❌ Date.now() in Query
```typescript
// Bad
export const getActive = query({
handler: async (ctx) => {
const now = Date.now(); // Breaks reactivity!
return await ctx.db.query("tasks")
.filter(q => q.lt(q.field("due"), now))
.collect();
},
});
```
Should pass time as argument or use status field.
#### ❌ Missing Auth Check
```typescript
// Bad
export const deleteTask = mutation({
args: { taskId: v.id("tasks") },
handler: async (ctx, args) => {
await ctx.db.delete(args.taskId); // Anyone can delete!
},
});
```
Should verify ownership:
```typescript
// Good
export const deleteTask = mutation({
args: { taskId: v.id("tasks") },
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Not authenticated");
const task = await ctx.db.get(args.taskId);
if (!task) throw new Error("Task not found");
const user = await getCurrentUser(ctx);
if (task.userId !== user._id) {
throw new Error("Unauthorized");
}
await ctx.db.delete(args.taskId);
},
});
```
#### ❌ Deep Nesting
```typescript
// Bad
users: defineTable({
posts: v.array(v.object({
comments: v.array(v.object({ text: v.string() }))
}))
})
```
Should use separate tables with relationships.
#### ❌ Scheduling API Functions
```typescript
// Bad
await ctx.scheduler.runAfter(0, api.tasks.process, args);
```
Should use internal:
```typescript
// Good
await ctx.scheduler.runAfter(0, internal.tasks.process, args);
```
## Review Process
1. **First Pass**: Check security (auth, validation, authorization)
2. **Second Pass**: Check performance (indexes, queries, reactivity)
3. **Third Pass**: Check code quality (organization, types, patterns)
4. **Final Pass**: Suggest improvements and alternatives
## Providing Feedback
- **Critical Issues**: Security vulnerabilities, data loss risks
- **Important**: Performance problems, broken reactivity
- **Suggestions**: Better patterns, code organization
- **Praise**: Good patterns, clever solutions
Always explain *why* something should change, not just *what* to change.
## Example Review
```typescript
// Code being reviewed
export const updateUser = mutation({
args: { userId: v.id("users"), name: v.string() },
handler: async (ctx, args) => {
await ctx.db.patch(args.userId, { name: args.name });
},
});
```
**Review:**
🔴 **Critical - Security**: Missing authentication and authorization checks
- Any user can update any other user's name
- Should verify `ctx.auth.getUserIdentity()` is authenticated
- Should verify the authenticated user is updating their own profile
🟡 **Missing**: No `returns` validator defined
**Suggested fix:**
```typescript
export const updateUser = mutation({
args: { name: v.string() },
returns: v.id("users"),
handler: async (ctx, args) => {
const user = await getCurrentUser(ctx); // Checks auth
await ctx.db.patch(user._id, { name: args.name });
return user._id;
},
});
```
Changes:
- Removed `userId` arg - users can only update themselves
- Added auth check via `getCurrentUser()`
- Added `returns` validator
- Users automatically update their own profile
عرض على GitHub