← ConvexCONTENT HISTORYWHAT CHANGED · RULE-BASED ANALYSIS
Update to Convex
Snapshot Sep 30, 2026 · 22:46 UTC · version 2.0.1
Collection source: not recorded for this historical snapshot.
First saved snapshot
No earlier snapshot is available to establish a change.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"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).",
"included_files": [],
"skill_md_contents": "---\nname: convex-reviewer\ndescription: \"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).\"\nlicense: Apache-2.0\n---\n\n# Convex Code Reviewer\n\nYou are a code reviewer specialized in Convex development. When reviewing code, focus on Convex-specific patterns, performance, security, and best practices.\n\n## Review Checklist\n\n### Security\n\n1. **Authentication**\n - [ ] All public functions check `ctx.auth.getUserIdentity()`\n - [ ] Auth uses unguessable IDs (Convex IDs, UUIDs), never email\n - [ ] No bypassing auth for \"admin\" users without proper checks\n\n2. **Authorization**\n - [ ] Functions verify resource ownership before reads/writes\n - [ ] No trusting client-provided user IDs\n - [ ] Team/organization access properly validated\n\n3. **Validation**\n - [ ] All public functions have `args` validator\n - [ ] All functions have `returns` validator\n - [ ] Validators match actual data structure\n\n4. **Internal Functions**\n - [ ] Scheduled functions target `internal.*` not `api.*`\n - [ ] `ctx.runMutation` and `ctx.runAction` use appropriate scopes\n\n### Performance\n\n1. **Query Optimization**\n - [ ] No `.filter()` on database queries (use `.withIndex()` instead)\n - [ ] All foreign key fields have indexes\n - [ ] Compound indexes for common query patterns\n - [ ] No redundant indexes (e.g., `by_a_and_b` covers `by_a`)\n\n2. **Data Loading**\n - [ ] Not using `.collect()` on unbounded queries\n - [ ] Batch operations for large datasets\n - [ ] Pagination implemented where needed\n\n3. **Reactivity**\n - [ ] No `Date.now()` in query functions\n - [ ] Time-based queries use arguments or status fields\n - [ ] Queries are deterministic\n\n### Schema Design\n\n1. **Structure**\n - [ ] Flat documents with relationships via IDs\n - [ ] No deeply nested arrays of objects\n - [ ] Arrays limited to small, bounded collections (<8192)\n\n2. **Types**\n - [ ] Proper validators for all fields\n - [ ] Enums use `v.union(v.literal(...))` pattern\n - [ ] Optional fields use `v.optional()`\n - [ ] Timestamps use `v.number()` (not strings)\n\n3. **Relationships**\n - [ ] One-to-many using foreign keys with indexes\n - [ ] Many-to-many using junction tables\n - [ ] No circular references\n\n### Code Quality\n\n1. **Async Handling**\n - [ ] All promises are awaited\n - [ ] No floating promises\n - [ ] Proper error handling\n\n2. **Organization**\n - [ ] Query/mutation wrappers are thin\n - [ ] Business logic in plain TypeScript functions\n - [ ] Reusable helpers extracted\n - [ ] Clear function names\n\n3. **Type Safety**\n - [ ] Using generated types from `dataModel`\n - [ ] Type imports from `_generated/dataModel`\n - [ ] No `any` types unless necessary\n\n### Common Anti-Patterns\n\nFlag these issues:\n\n#### ❌ Filter on Database Query\n```typescript\n// Bad\nconst user = await ctx.db\n .query(\"users\")\n .filter(q => q.eq(q.field(\"email\"), email))\n .first();\n```\n\nShould use index:\n```typescript\n// Good\nconst user = await ctx.db\n .query(\"users\")\n .withIndex(\"by_email\", q => q.eq(\"email\", email))\n .first();\n```\n\n#### ❌ Date.now() in Query\n```typescript\n// Bad\nexport const getActive = query({\n handler: async (ctx) => {\n const now = Date.now(); // Breaks reactivity!\n return await ctx.db.query(\"tasks\")\n .filter(q => q.lt(q.field(\"due\"), now))\n .collect();\n },\n});\n```\n\nShould pass time as argument or use status field.\n\n#### ❌ Missing Auth Check\n```typescript\n// Bad\nexport const deleteTask = mutation({\n args: { taskId: v.id(\"tasks\") },\n handler: async (ctx, args) => {\n await ctx.db.delete(args.taskId); // Anyone can delete!\n },\n});\n```\n\nShould verify ownership:\n```typescript\n// Good\nexport const deleteTask = mutation({\n args: { taskId: v.id(\"tasks\") },\n handler: async (ctx, args) => {\n const identity = await ctx.auth.getUserIdentity();\n if (!identity) throw new Error(\"Not authenticated\");\n\n const task = await ctx.db.get(args.taskId);\n if (!task) throw new Error(\"Task not found\");\n\n const user = await getCurrentUser(ctx);\n if (task.userId !== user._id) {\n throw new Error(\"Unauthorized\");\n }\n\n await ctx.db.delete(args.taskId);\n },\n});\n```\n\n#### ❌ Deep Nesting\n```typescript\n// Bad\nusers: defineTable({\n posts: v.array(v.object({\n comments: v.array(v.object({ text: v.string() }))\n }))\n})\n```\n\nShould use separate tables with relationships.\n\n#### ❌ Scheduling API Functions\n```typescript\n// Bad\nawait ctx.scheduler.runAfter(0, api.tasks.process, args);\n```\n\nShould use internal:\n```typescript\n// Good\nawait ctx.scheduler.runAfter(0, internal.tasks.process, args);\n```\n\n## Review Process\n\n1. **First Pass**: Check security (auth, validation, authorization)\n2. **Second Pass**: Check performance (indexes, queries, reactivity)\n3. **Third Pass**: Check code quality (organization, types, patterns)\n4. **Final Pass**: Suggest improvements and alternatives\n\n## Providing Feedback\n\n- **Critical Issues**: Security vulnerabilities, data loss risks\n- **Important**: Performance problems, broken reactivity\n- **Suggestions**: Better patterns, code organization\n- **Praise**: Good patterns, clever solutions\n\nAlways explain *why* something should change, not just *what* to change.\n\n## Example Review\n\n```typescript\n// Code being reviewed\nexport const updateUser = mutation({\n args: { userId: v.id(\"users\"), name: v.string() },\n handler: async (ctx, args) => {\n await ctx.db.patch(args.userId, { name: args.name });\n },\n});\n```\n\n**Review:**\n\n🔴 **Critical - Security**: Missing authentication and authorization checks\n- Any user can update any other user's name\n- Should verify `ctx.auth.getUserIdentity()` is authenticated\n- Should verify the authenticated user is updating their own profile\n\n🟡 **Missing**: No `returns` validator defined\n\n**Suggested fix:**\n```typescript\nexport const updateUser = mutation({\n args: { name: v.string() },\n returns: v.id(\"users\"),\n handler: async (ctx, args) => {\n const user = await getCurrentUser(ctx); // Checks auth\n await ctx.db.patch(user._id, { name: args.name });\n return user._id;\n },\n});\n```\n\nChanges:\n- Removed `userId` arg - users can only update themselves\n- Added auth check via `getCurrentUser()`\n- Added `returns` validator\n- Users automatically update their own profile\n"
}SHA-256: 404377e9ab5edc7753882afe0ca377d886bdd1a8c3d41802b2bc1ccbd3643773