← ConvexCONTENT HISTORY

Update to Convex

Snapshot Sep 30, 2026 · 22:46 UTC · version 2.0.1

Collection source: not recorded for this historical snapshot.

WHAT CHANGED · RULE-BASED ANALYSIS

First saved snapshot

No earlier snapshot is available to establish a change.

Compare saved observations

Download comparison JSON
Full 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