[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6CCD0GKNP8AYVN20W9Y2FZE":3,"skill-same:01M6CCD0GKNP8AYVN20W9Y2FZE":51},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":25,"linkOnly":26,"markdownContent":27,"markdownBody":28,"frontMatter":29,"refName":30,"commitSha":31,"skillDirectory":32,"forksCount":14,"repositoryHtmlUrl":33,"repositoryHomepageUrl":34,"ownerHtmlUrl":35,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":36,"install":37,"resources":50},"01M6CCD0GKNP8AYVN20W9Y2FZE","convex-helpers-guide",null,"Discover and use convex-helpers utilities for relationships, filtering, sessions, custom functions, and more. Use when you need pre-built Convex patterns.","baisharg\u002FASTN","baisharg","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F204916775?v=4","01M6CC8XHZYVK2DR22FZ1BESCG","BAISH","01M6CC8XP400QZ9DXAWTC8EG3X",2,"AGPL-3.0","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fconvex-helpers-guide\u002FSKILL.md",[],[19,20,21,22,23,24],"convex","guide","convex helpers guide","sessions","filtering","use","2026-11-10 10:12:30",false,"---\nname: convex-helpers-guide\ndescription: Discover and use convex-helpers utilities for relationships, filtering, sessions, custom functions, and more. Use when you need pre-built Convex patterns.\n---\n\n# Convex Helpers Guide\n\nUse convex-helpers to add common patterns and utilities to your Convex backend without reinventing the wheel.\n\n## What is convex-helpers?\n\n`convex-helpers` is the official collection of utilities that complement Convex. It provides battle-tested patterns for common backend needs.\n\n**Installation:**\n\n```bash\nnpm install convex-helpers\n```\n\n## Available Helpers\n\n### 1. Relationship Helpers\n\nTraverse relationships between tables in a readable, type-safe way.\n\n**Use when:**\n\n- Loading related data across tables\n- Following foreign key relationships\n- Building nested data structures\n\n**Example:**\n\n```typescript\nimport { getOneFrom, getManyFrom } from 'convex-helpers\u002Fserver\u002Frelationships'\n\nexport const getTaskWithUser = query({\n  args: { taskId: v.id('tasks') },\n  handler: async (ctx, args) => {\n    const task = await ctx.db.get(args.taskId)\n    if (!task) return null\n\n    \u002F\u002F Get related user\n    const user = await getOneFrom(ctx.db, 'users', 'by_id', task.userId, '_id')\n\n    \u002F\u002F Get related comments\n    const comments = await getManyFrom(\n      ctx.db,\n      'comments',\n      'by_task',\n      task._id,\n      'taskId',\n    )\n\n    return { ...task, user, comments }\n  },\n})\n```\n\n**Key Functions:**\n\n- `getOneFrom` - Get single related document\n- `getManyFrom` - Get multiple related documents\n- `getManyVia` - Get many-to-many relationships through junction table\n\n### 2. Custom Functions (Data Protection) ⭐ MOST IMPORTANT\n\n**This is Convex's alternative to Row Level Security (RLS).** Instead of database-level policies, use custom function wrappers to automatically add auth and access control to all queries and mutations.\n\nCreate wrapped versions of query\u002Fmutation\u002Faction with custom behavior.\n\n**Use when:**\n\n- **Data protection and access control** (PRIMARY USE CASE)\n- Want to add auth logic to all functions\n- Multi-tenant applications\n- Role-based access control (RBAC)\n- Need to inject common data into ctx\n- Building internal-only functions\n- Adding logging\u002Fmonitoring to all functions\n\n**Why this instead of RLS:**\n\n- ✅ TypeScript, not SQL policies\n- ✅ Full type safety\n- ✅ Easy to test and debug\n- ✅ More flexible than database policies\n- ✅ Works across your entire backend\n\n**Example: Custom Query with Auto-Auth**\n\n```typescript\n\u002F\u002F convex\u002Flib\u002FcustomFunctions.ts\nimport { customQuery } from 'convex-helpers\u002Fserver\u002FcustomFunctions'\nimport { query } from '..\u002F_generated\u002Fserver'\n\nexport const authenticatedQuery = customQuery(query, {\n  args: {}, \u002F\u002F No additional args required\n  input: async (ctx, args) => {\n    const identity = await ctx.auth.getUserIdentity()\n    if (!identity) {\n      throw new Error('Not authenticated')\n    }\n\n    const user = await ctx.db\n      .query('users')\n      .withIndex('by_token', (q) =>\n        q.eq('tokenIdentifier', identity.tokenIdentifier),\n      )\n      .unique()\n\n    if (!user) throw new Error('User not found')\n\n    \u002F\u002F Add user to context\n    return { ctx: { ...ctx, user }, args }\n  },\n})\n\n\u002F\u002F Usage in your functions\nexport const getMyTasks = authenticatedQuery({\n  handler: async (ctx) => {\n    \u002F\u002F ctx.user is automatically available!\n    return await ctx.db\n      .query('tasks')\n      .withIndex('by_user', (q) => q.eq('userId', ctx.user._id))\n      .collect()\n  },\n})\n```\n\n**Example: Multi-Tenant Data Protection**\n\n```typescript\nimport { customQuery } from 'convex-helpers\u002Fserver\u002FcustomFunctions'\nimport { query } from '..\u002F_generated\u002Fserver'\n\n\u002F\u002F Organization-scoped query - automatic access control\nexport const orgQuery = customQuery(query, {\n  args: { orgId: v.id('organizations') },\n  input: async (ctx, args) => {\n    const user = await getCurrentUser(ctx)\n\n    \u002F\u002F Verify user is a member of this organization\n    const member = await ctx.db\n      .query('organizationMembers')\n      .withIndex('by_org_and_user', (q) =>\n        q.eq('orgId', args.orgId).eq('userId', user._id),\n      )\n      .unique()\n\n    if (!member) {\n      throw new Error('Not authorized for this organization')\n    }\n\n    \u002F\u002F Inject org context\n    return {\n      ctx: {\n        ...ctx,\n        user,\n        orgId: args.orgId,\n        role: member.role,\n      },\n      args,\n    }\n  },\n})\n\n\u002F\u002F Usage - data automatically scoped to organization\nexport const getOrgProjects = orgQuery({\n  args: { orgId: v.id('organizations') },\n  handler: async (ctx) => {\n    \u002F\u002F ctx.user and ctx.orgId automatically available and verified!\n    return await ctx.db\n      .query('projects')\n      .withIndex('by_org', (q) => q.eq('orgId', ctx.orgId))\n      .collect()\n  },\n})\n```\n\n**Example: Role-Based Access Control**\n\n```typescript\nimport { customMutation } from 'convex-helpers\u002Fserver\u002FcustomFunctions'\nimport { mutation } from '..\u002F_generated\u002Fserver'\n\nexport const adminMutation = customMutation(mutation, {\n  args: {},\n  input: async (ctx, args) => {\n    const user = await getCurrentUser(ctx)\n\n    if (user.role !== 'admin') {\n      throw new Error('Admin access required')\n    }\n\n    return { ctx: { ...ctx, user }, args }\n  },\n})\n\n\u002F\u002F Usage - only admins can call this\nexport const deleteUser = adminMutation({\n  args: { userId: v.id('users') },\n  handler: async (ctx, args) => {\n    \u002F\u002F Only admins reach this code\n    await ctx.db.delete(args.userId)\n  },\n})\n```\n\n### 3. Filter Helper\n\nApply complex TypeScript filters to database queries.\n\n**Use when:**\n\n- Need to filter by computed values\n- Filtering logic is too complex for indexes\n- Working with small result sets\n\n**Example:**\n\n```typescript\nimport { filter } from 'convex-helpers\u002Fserver\u002Ffilter'\n\nexport const getActiveTasks = query({\n  handler: async (ctx) => {\n    const now = Date.now()\n    const threeDaysAgo = now - 3 * 24 * 60 * 60 * 1000\n\n    return await filter(\n      ctx.db.query('tasks'),\n      (task) =>\n        !task.completed &&\n        task.createdAt > threeDaysAgo &&\n        task.priority === 'high',\n    ).collect()\n  },\n})\n```\n\n**Note:** Still prefer indexes when possible! Use filter for complex logic that can't be indexed.\n\n### 4. Sessions\n\nTrack users across requests even when not logged in.\n\n**Use when:**\n\n- Need to track anonymous users\n- Building shopping cart for guests\n- Tracking user behavior before signup\n- A\u002FB testing without auth\n\n**Setup:**\n\n```typescript\n\u002F\u002F convex\u002Fsessions.ts\nimport { SessionIdArg } from 'convex-helpers\u002Fserver\u002Fsessions'\nimport { query } from '.\u002F_generated\u002Fserver'\n\nexport const trackView = query({\n  args: {\n    ...SessionIdArg, \u002F\u002F Adds sessionId: v.string()\n    pageUrl: v.string(),\n  },\n  handler: async (ctx, args) => {\n    await ctx.db.insert('pageViews', {\n      sessionId: args.sessionId,\n      pageUrl: args.pageUrl,\n      timestamp: Date.now(),\n    })\n  },\n})\n```\n\n**Client (React):**\n\n```typescript\nimport { useSessionId } from 'convex-helpers\u002Freact\u002Fsessions'\nimport { useQuery } from 'convex\u002Freact'\nimport { api } from '..\u002Fconvex\u002F_generated\u002Fapi'\n\nfunction MyComponent() {\n  const sessionId = useSessionId()\n\n  \u002F\u002F Automatically includes sessionId in all requests\n  useQuery(api.sessions.trackView, {\n    sessionId,\n    pageUrl: window.location.href,\n  })\n}\n```\n\n### 5. Zod Validation\n\nUse Zod schemas instead of Convex validators.\n\n**Use when:**\n\n- Already using Zod in your project\n- Want more complex validation logic\n- Need custom error messages\n\n**Example:**\n\n```typescript\nimport { zCustomQuery } from 'convex-helpers\u002Fserver\u002Fzod'\nimport { z } from 'zod'\nimport { query } from '.\u002F_generated\u002Fserver'\n\nconst argsSchema = z.object({\n  email: z.string().email(),\n  age: z.number().min(18).max(120),\n})\n\nexport const createUser = zCustomQuery(query, {\n  args: argsSchema,\n  handler: async (ctx, args) => {\n    \u002F\u002F args is typed from Zod schema\n    return await ctx.db.insert('users', args)\n  },\n})\n```\n\n### 6. Alternative: Row-Level Security Helper\n\n**Note:** Convex recommends using **custom functions** (see #2 above) as the primary data protection pattern. This RLS helper is an alternative approach that mimics traditional RLS.\n\nImplement fine-grained access control with RLS-style rules.\n\n**Use when:**\n\n- Prefer RLS-style patterns from PostgreSQL\n- Need to apply same rules across many functions\n- Want centralized access control rules\n\n**However, custom functions are usually better because:**\n\n- ✅ Type-safe at compile time (RLS is runtime)\n- ✅ More explicit (easy to see what auth is applied)\n- ✅ Better error messages\n- ✅ Easier to test\n\n**Example (if you prefer RLS style):**\n\n```typescript\nimport { RowLevelSecurity } from 'convex-helpers\u002Fserver\u002FrowLevelSecurity'\n\nconst rules = new RowLevelSecurity()\n\nrules.addRule('tasks', async (ctx, task) => {\n  const user = await getCurrentUser(ctx)\n  \u002F\u002F Users can only see their own tasks\n  return task.userId === user._id\n})\n\nexport const getTasks = query({\n  handler: async (ctx) => {\n    return await rules.applyRules(ctx, ctx.db.query('tasks').collect())\n  },\n})\n```\n\n**Recommended instead: Custom functions**\n\n```typescript\nexport const myQuery = authedQuery({\n  handler: async (ctx) => {\n    \u002F\u002F More explicit, type-safe, better errors\n    return await ctx.db\n      .query('tasks')\n      .withIndex('by_user', (q) => q.eq('userId', ctx.user._id))\n      .collect()\n  },\n})\n```\n\n### 7. Migrations\n\nRun data migrations safely.\n\n**Use when:**\n\n- Backfilling new fields\n- Transforming existing data\n- Moving between schema versions\n\n**Example:**\n\n```typescript\nimport { makeMigration } from 'convex-helpers\u002Fserver\u002Fmigrations'\n\nexport const addDefaultPriority = makeMigration({\n  table: 'tasks',\n  migrateOne: async (ctx, doc) => {\n    if (doc.priority === undefined) {\n      await ctx.db.patch(doc._id, { priority: 'medium' })\n    }\n  },\n})\n\n\u002F\u002F Run: npx convex run migrations:addDefaultPriority\n```\n\n### 8. Triggers\n\nExecute code automatically when data changes.\n\n**Use when:**\n\n- Sending notifications on data changes\n- Updating related records\n- Logging changes\n- Maintaining computed fields\n\n**Example:**\n\n```typescript\nimport { Triggers } from 'convex-helpers\u002Fserver\u002Ftriggers'\n\nconst triggers = new Triggers()\n\ntriggers.register('tasks', 'insert', async (ctx, task) => {\n  \u002F\u002F Send notification when task is created\n  await ctx.db.insert('notifications', {\n    userId: task.userId,\n    type: 'task_created',\n    taskId: task._id,\n  })\n})\n```\n\n### 9. Aggregations\n\nCompute aggregates efficiently.\n\n**Example:**\n\n```typescript\nimport { aggregation } from 'convex-helpers\u002Fserver\u002Faggregation'\n\nexport const getTaskStats = query({\n  handler: async (ctx) => {\n    const stats = await aggregation(ctx.db.query('tasks'), {\n      total: 'count',\n      completed: (task) => (task.completed ? 1 : 0),\n      totalPriority: (task) =>\n        task.priority === 'high' ? 3 : task.priority === 'medium' ? 2 : 1,\n    })\n\n    return {\n      total: stats.total,\n      completed: stats.completed,\n      avgPriority: stats.totalPriority \u002F stats.total,\n    }\n  },\n})\n```\n\n## Common Patterns\n\n### Pattern 1: Authenticated Queries with User Context\n\n```typescript\nimport { customQuery } from 'convex-helpers\u002Fserver\u002FcustomFunctions'\n\nexport const authedQuery = customQuery(query, {\n  args: {},\n  input: async (ctx, args) => {\n    const user = await getCurrentUser(ctx)\n    return { ctx: { ...ctx, user }, args }\n  },\n})\n\n\u002F\u002F Now all queries automatically have user in context\nexport const getMyData = authedQuery({\n  handler: async (ctx) => {\n    \u002F\u002F ctx.user is typed and available!\n    return await ctx.db\n      .query('data')\n      .withIndex('by_user', (q) => q.eq('userId', ctx.user._id))\n      .collect()\n  },\n})\n```\n\n### Pattern 2: Loading Related Data\n\n```typescript\nimport { getOneFrom, getManyFrom } from 'convex-helpers\u002Fserver\u002Frelationships'\n\nexport const getPostWithDetails = query({\n  args: { postId: v.id('posts') },\n  handler: async (ctx, args) => {\n    const post = await ctx.db.get(args.postId)\n    if (!post) return null\n\n    \u002F\u002F Load author\n    const author = await getOneFrom(\n      ctx.db,\n      'users',\n      'by_id',\n      post.authorId,\n      '_id',\n    )\n\n    \u002F\u002F Load comments\n    const comments = await getManyFrom(\n      ctx.db,\n      'comments',\n      'by_post',\n      post._id,\n      'postId',\n    )\n\n    \u002F\u002F Load tags (many-to-many)\n    const tagLinks = await getManyFrom(\n      ctx.db,\n      'postTags',\n      'by_post',\n      post._id,\n      'postId',\n    )\n\n    const tags = await Promise.all(\n      tagLinks.map((link) =>\n        getOneFrom(ctx.db, 'tags', 'by_id', link.tagId, '_id'),\n      ),\n    )\n\n    return { ...post, author, comments, tags }\n  },\n})\n```\n\n### Pattern 3: Batch Operations with Error Handling\n\n```typescript\nimport { asyncMap } from 'convex-helpers'\n\nexport const batchUpdateTasks = mutation({\n  args: {\n    taskIds: v.array(v.id('tasks')),\n    status: v.string(),\n  },\n  handler: async (ctx, args) => {\n    const results = await asyncMap(args.taskIds, async (taskId) => {\n      try {\n        const task = await ctx.db.get(taskId)\n        if (task) {\n          await ctx.db.patch(taskId, { status: args.status })\n          return { success: true, taskId }\n        }\n        return { success: false, taskId, error: 'Not found' }\n      } catch (error) {\n        return { success: false, taskId, error: error.message }\n      }\n    })\n\n    return results\n  },\n})\n```\n\n## Best Practices\n\n1. **Start with convex-helpers**\n   - Don't reinvent common patterns\n   - Use battle-tested utilities\n   - Contribute back if you build something useful\n\n2. **Custom Functions for Auth**\n   - Create `authedQuery`, `authedMutation`, etc.\n   - Inject user context automatically\n   - Reduces boilerplate\n\n3. **Relationships Over Nesting**\n   - Use relationship helpers\n   - Keep data normalized\n   - Load related data as needed\n\n4. **Filter Sparingly**\n   - Prefer indexes when possible\n   - Use filter for complex computed logic\n   - Good for small result sets\n\n5. **Sessions for Anonymous Users**\n   - Track before signup\n   - Migrate to user account later\n   - Great for cart, preferences, etc.\n\n## Documentation\n\n- [convex-helpers GitHub](https:\u002F\u002Fgithub.com\u002Fget-convex\u002Fconvex-helpers)\n- [convex-helpers on npm](https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fconvex-helpers)\n- [Relationship Helpers Guide](https:\u002F\u002Fstack.convex.dev\u002Ffunctional-relationships-helpers)\n\n## Checklist\n\n- [ ] Installed convex-helpers: `npm install convex-helpers`\n- [ ] Using relationship helpers for related data\n- [ ] Created custom functions for common auth patterns\n- [ ] Using sessions for anonymous tracking (if needed)\n- [ ] Prefer indexes over filter when possible\n- [ ] Check convex-helpers docs for new utilities\n\n## When to Use What\n\n| Need                  | Use                         | Import From                             |\n| --------------------- | --------------------------- | --------------------------------------- |\n| Load related data     | `getOneFrom`, `getManyFrom` | `convex-helpers\u002Fserver\u002Frelationships`   |\n| Auth in all functions | `customQuery`               | `convex-helpers\u002Fserver\u002FcustomFunctions` |\n| Complex filters       | `filter`                    | `convex-helpers\u002Fserver\u002Ffilter`          |\n| Anonymous users       | `useSessionId`              | `convex-helpers\u002Freact\u002Fsessions`         |\n| Zod validation        | `zCustomQuery`              | `convex-helpers\u002Fserver\u002Fzod`             |\n| Data migrations       | `makeMigration`             | `convex-helpers\u002Fserver\u002Fmigrations`      |\n| Triggers              | `Triggers`                  | `convex-helpers\u002Fserver\u002Ftriggers`        |\n","# Convex Helpers Guide\n\nUse convex-helpers to add common patterns and utilities to your Convex backend without reinventing the wheel.\n\n## What is convex-helpers?\n\n`convex-helpers` is the official collection of utilities that complement Convex. It provides battle-tested patterns for common backend needs.\n\n**Installation:**\n\n```bash\nnpm install convex-helpers\n```\n\n## Available Helpers\n\n### 1. Relationship Helpers\n\nTraverse relationships between tables in a readable, type-safe way.\n\n**Use when:**\n\n- Loading related data across tables\n- Following foreign key relationships\n- Building nested data structures\n\n**Example:**\n\n```typescript\nimport { getOneFrom, getManyFrom } from 'convex-helpers\u002Fserver\u002Frelationships'\n\nexport const getTaskWithUser = query({\n  args: { taskId: v.id('tasks') },\n  handler: async (ctx, args) => {\n    const task = await ctx.db.get(args.taskId)\n    if (!task) return null\n\n    \u002F\u002F Get related user\n    const user = await getOneFrom(ctx.db, 'users', 'by_id', task.userId, '_id')\n\n    \u002F\u002F Get related comments\n    const comments = await getManyFrom(\n      ctx.db,\n      'comments',\n      'by_task',\n      task._id,\n      'taskId',\n    )\n\n    return { ...task, user, comments }\n  },\n})\n```\n\n**Key Functions:**\n\n- `getOneFrom` - Get single related document\n- `getManyFrom` - Get multiple related documents\n- `getManyVia` - Get many-to-many relationships through junction table\n\n### 2. Custom Functions (Data Protection) ⭐ MOST IMPORTANT\n\n**This is Convex's alternative to Row Level Security (RLS).** Instead of database-level policies, use custom function wrappers to automatically add auth and access control to all queries and mutations.\n\nCreate wrapped versions of query\u002Fmutation\u002Faction with custom behavior.\n\n**Use when:**\n\n- **Data protection and access control** (PRIMARY USE CASE)\n- Want to add auth logic to all functions\n- Multi-tenant applications\n- Role-based access control (RBAC)\n- Need to inject common data into ctx\n- Building internal-only functions\n- Adding logging\u002Fmonitoring to all functions\n\n**Why this instead of RLS:**\n\n- ✅ TypeScript, not SQL policies\n- ✅ Full type safety\n- ✅ Easy to test and debug\n- ✅ More flexible than database policies\n- ✅ Works across your entire backend\n\n**Example: Custom Query with Auto-Auth**\n\n```typescript\n\u002F\u002F convex\u002Flib\u002FcustomFunctions.ts\nimport { customQuery } from 'convex-helpers\u002Fserver\u002FcustomFunctions'\nimport { query } from '..\u002F_generated\u002Fserver'\n\nexport const authenticatedQuery = customQuery(query, {\n  args: {}, \u002F\u002F No additional args required\n  input: async (ctx, args) => {\n    const identity = await ctx.auth.getUserIdentity()\n    if (!identity) {\n      throw new Error('Not authenticated')\n    }\n\n    const user = await ctx.db\n      .query('users')\n      .withIndex('by_token', (q) =>\n        q.eq('tokenIdentifier', identity.tokenIdentifier),\n      )\n      .unique()\n\n    if (!user) throw new Error('User not found')\n\n    \u002F\u002F Add user to context\n    return { ctx: { ...ctx, user }, args }\n  },\n})\n\n\u002F\u002F Usage in your functions\nexport const getMyTasks = authenticatedQuery({\n  handler: async (ctx) => {\n    \u002F\u002F ctx.user is automatically available!\n    return await ctx.db\n      .query('tasks')\n      .withIndex('by_user', (q) => q.eq('userId', ctx.user._id))\n      .collect()\n  },\n})\n```\n\n**Example: Multi-Tenant Data Protection**\n\n```typescript\nimport { customQuery } from 'convex-helpers\u002Fserver\u002FcustomFunctions'\nimport { query } from '..\u002F_generated\u002Fserver'\n\n\u002F\u002F Organization-scoped query - automatic access control\nexport const orgQuery = customQuery(query, {\n  args: { orgId: v.id('organizations') },\n  input: async (ctx, args) => {\n    const user = await getCurrentUser(ctx)\n\n    \u002F\u002F Verify user is a member of this organization\n    const member = await ctx.db\n      .query('organizationMembers')\n      .withIndex('by_org_and_user', (q) =>\n        q.eq('orgId', args.orgId).eq('userId', user._id),\n      )\n      .unique()\n\n    if (!member) {\n      throw new Error('Not authorized for this organization')\n    }\n\n    \u002F\u002F Inject org context\n    return {\n      ctx: {\n        ...ctx,\n        user,\n        orgId: args.orgId,\n        role: member.role,\n      },\n      args,\n    }\n  },\n})\n\n\u002F\u002F Usage - data automatically scoped to organization\nexport const getOrgProjects = orgQuery({\n  args: { orgId: v.id('organizations') },\n  handler: async (ctx) => {\n    \u002F\u002F ctx.user and ctx.orgId automatically available and verified!\n    return await ctx.db\n      .query('projects')\n      .withIndex('by_org', (q) => q.eq('orgId', ctx.orgId))\n      .collect()\n  },\n})\n```\n\n**Example: Role-Based Access Control**\n\n```typescript\nimport { customMutation } from 'convex-helpers\u002Fserver\u002FcustomFunctions'\nimport { mutation } from '..\u002F_generated\u002Fserver'\n\nexport const adminMutation = customMutation(mutation, {\n  args: {},\n  input: async (ctx, args) => {\n    const user = await getCurrentUser(ctx)\n\n    if (user.role !== 'admin') {\n      throw new Error('Admin access required')\n    }\n\n    return { ctx: { ...ctx, user }, args }\n  },\n})\n\n\u002F\u002F Usage - only admins can call this\nexport const deleteUser = adminMutation({\n  args: { userId: v.id('users') },\n  handler: async (ctx, args) => {\n    \u002F\u002F Only admins reach this code\n    await ctx.db.delete(args.userId)\n  },\n})\n```\n\n### 3. Filter Helper\n\nApply complex TypeScript filters to database queries.\n\n**Use when:**\n\n- Need to filter by computed values\n- Filtering logic is too complex for indexes\n- Working with small result sets\n\n**Example:**\n\n```typescript\nimport { filter } from 'convex-helpers\u002Fserver\u002Ffilter'\n\nexport const getActiveTasks = query({\n  handler: async (ctx) => {\n    const now = Date.now()\n    const threeDaysAgo = now - 3 * 24 * 60 * 60 * 1000\n\n    return await filter(\n      ctx.db.query('tasks'),\n      (task) =>\n        !task.completed &&\n        task.createdAt > threeDaysAgo &&\n        task.priority === 'high',\n    ).collect()\n  },\n})\n```\n\n**Note:** Still prefer indexes when possible! Use filter for complex logic that can't be indexed.\n\n### 4. Sessions\n\nTrack users across requests even when not logged in.\n\n**Use when:**\n\n- Need to track anonymous users\n- Building shopping cart for guests\n- Tracking user behavior before signup\n- A\u002FB testing without auth\n\n**Setup:**\n\n```typescript\n\u002F\u002F convex\u002Fsessions.ts\nimport { SessionIdArg } from 'convex-helpers\u002Fserver\u002Fsessions'\nimport { query } from '.\u002F_generated\u002Fserver'\n\nexport const trackView = query({\n  args: {\n    ...SessionIdArg, \u002F\u002F Adds sessionId: v.string()\n    pageUrl: v.string(),\n  },\n  handler: async (ctx, args) => {\n    await ctx.db.insert('pageViews', {\n      sessionId: args.sessionId,\n      pageUrl: args.pageUrl,\n      timestamp: Date.now(),\n    })\n  },\n})\n```\n\n**Client (React):**\n\n```typescript\nimport { useSessionId } from 'convex-helpers\u002Freact\u002Fsessions'\nimport { useQuery } from 'convex\u002Freact'\nimport { api } from '..\u002Fconvex\u002F_generated\u002Fapi'\n\nfunction MyComponent() {\n  const sessionId = useSessionId()\n\n  \u002F\u002F Automatically includes sessionId in all requests\n  useQuery(api.sessions.trackView, {\n    sessionId,\n    pageUrl: window.location.href,\n  })\n}\n```\n\n### 5. Zod Validation\n\nUse Zod schemas instead of Convex validators.\n\n**Use when:**\n\n- Already using Zod in your project\n- Want more complex validation logic\n- Need custom error messages\n\n**Example:**\n\n```typescript\nimport { zCustomQuery } from 'convex-helpers\u002Fserver\u002Fzod'\nimport { z } from 'zod'\nimport { query } from '.\u002F_generated\u002Fserver'\n\nconst argsSchema = z.object({\n  email: z.string().email(),\n  age: z.number().min(18).max(120),\n})\n\nexport const createUser = zCustomQuery(query, {\n  args: argsSchema,\n  handler: async (ctx, args) => {\n    \u002F\u002F args is typed from Zod schema\n    return await ctx.db.insert('users', args)\n  },\n})\n```\n\n### 6. Alternative: Row-Level Security Helper\n\n**Note:** Convex recommends using **custom functions** (see #2 above) as the primary data protection pattern. This RLS helper is an alternative approach that mimics traditional RLS.\n\nImplement fine-grained access control with RLS-style rules.\n\n**Use when:**\n\n- Prefer RLS-style patterns from PostgreSQL\n- Need to apply same rules across many functions\n- Want centralized access control rules\n\n**However, custom functions are usually better because:**\n\n- ✅ Type-safe at compile time (RLS is runtime)\n- ✅ More explicit (easy to see what auth is applied)\n- ✅ Better error messages\n- ✅ Easier to test\n\n**Example (if you prefer RLS style):**\n\n```typescript\nimport { RowLevelSecurity } from 'convex-helpers\u002Fserver\u002FrowLevelSecurity'\n\nconst rules = new RowLevelSecurity()\n\nrules.addRule('tasks', async (ctx, task) => {\n  const user = await getCurrentUser(ctx)\n  \u002F\u002F Users can only see their own tasks\n  return task.userId === user._id\n})\n\nexport const getTasks = query({\n  handler: async (ctx) => {\n    return await rules.applyRules(ctx, ctx.db.query('tasks').collect())\n  },\n})\n```\n\n**Recommended instead: Custom functions**\n\n```typescript\nexport const myQuery = authedQuery({\n  handler: async (ctx) => {\n    \u002F\u002F More explicit, type-safe, better errors\n    return await ctx.db\n      .query('tasks')\n      .withIndex('by_user', (q) => q.eq('userId', ctx.user._id))\n      .collect()\n  },\n})\n```\n\n### 7. Migrations\n\nRun data migrations safely.\n\n**Use when:**\n\n- Backfilling new fields\n- Transforming existing data\n- Moving between schema versions\n\n**Example:**\n\n```typescript\nimport { makeMigration } from 'convex-helpers\u002Fserver\u002Fmigrations'\n\nexport const addDefaultPriority = makeMigration({\n  table: 'tasks',\n  migrateOne: async (ctx, doc) => {\n    if (doc.priority === undefined) {\n      await ctx.db.patch(doc._id, { priority: 'medium' })\n    }\n  },\n})\n\n\u002F\u002F Run: npx convex run migrations:addDefaultPriority\n```\n\n### 8. Triggers\n\nExecute code automatically when data changes.\n\n**Use when:**\n\n- Sending notifications on data changes\n- Updating related records\n- Logging changes\n- Maintaining computed fields\n\n**Example:**\n\n```typescript\nimport { Triggers } from 'convex-helpers\u002Fserver\u002Ftriggers'\n\nconst triggers = new Triggers()\n\ntriggers.register('tasks', 'insert', async (ctx, task) => {\n  \u002F\u002F Send notification when task is created\n  await ctx.db.insert('notifications', {\n    userId: task.userId,\n    type: 'task_created',\n    taskId: task._id,\n  })\n})\n```\n\n### 9. Aggregations\n\nCompute aggregates efficiently.\n\n**Example:**\n\n```typescript\nimport { aggregation } from 'convex-helpers\u002Fserver\u002Faggregation'\n\nexport const getTaskStats = query({\n  handler: async (ctx) => {\n    const stats = await aggregation(ctx.db.query('tasks'), {\n      total: 'count',\n      completed: (task) => (task.completed ? 1 : 0),\n      totalPriority: (task) =>\n        task.priority === 'high' ? 3 : task.priority === 'medium' ? 2 : 1,\n    })\n\n    return {\n      total: stats.total,\n      completed: stats.completed,\n      avgPriority: stats.totalPriority \u002F stats.total,\n    }\n  },\n})\n```\n\n## Common Patterns\n\n### Pattern 1: Authenticated Queries with User Context\n\n```typescript\nimport { customQuery } from 'convex-helpers\u002Fserver\u002FcustomFunctions'\n\nexport const authedQuery = customQuery(query, {\n  args: {},\n  input: async (ctx, args) => {\n    const user = await getCurrentUser(ctx)\n    return { ctx: { ...ctx, user }, args }\n  },\n})\n\n\u002F\u002F Now all queries automatically have user in context\nexport const getMyData = authedQuery({\n  handler: async (ctx) => {\n    \u002F\u002F ctx.user is typed and available!\n    return await ctx.db\n      .query('data')\n      .withIndex('by_user', (q) => q.eq('userId', ctx.user._id))\n      .collect()\n  },\n})\n```\n\n### Pattern 2: Loading Related Data\n\n```typescript\nimport { getOneFrom, getManyFrom } from 'convex-helpers\u002Fserver\u002Frelationships'\n\nexport const getPostWithDetails = query({\n  args: { postId: v.id('posts') },\n  handler: async (ctx, args) => {\n    const post = await ctx.db.get(args.postId)\n    if (!post) return null\n\n    \u002F\u002F Load author\n    const author = await getOneFrom(\n      ctx.db,\n      'users',\n      'by_id',\n      post.authorId,\n      '_id',\n    )\n\n    \u002F\u002F Load comments\n    const comments = await getManyFrom(\n      ctx.db,\n      'comments',\n      'by_post',\n      post._id,\n      'postId',\n    )\n\n    \u002F\u002F Load tags (many-to-many)\n    const tagLinks = await getManyFrom(\n      ctx.db,\n      'postTags',\n      'by_post',\n      post._id,\n      'postId',\n    )\n\n    const tags = await Promise.all(\n      tagLinks.map((link) =>\n        getOneFrom(ctx.db, 'tags', 'by_id', link.tagId, '_id'),\n      ),\n    )\n\n    return { ...post, author, comments, tags }\n  },\n})\n```\n\n### Pattern 3: Batch Operations with Error Handling\n\n```typescript\nimport { asyncMap } from 'convex-helpers'\n\nexport const batchUpdateTasks = mutation({\n  args: {\n    taskIds: v.array(v.id('tasks')),\n    status: v.string(),\n  },\n  handler: async (ctx, args) => {\n    const results = await asyncMap(args.taskIds, async (taskId) => {\n      try {\n        const task = await ctx.db.get(taskId)\n        if (task) {\n          await ctx.db.patch(taskId, { status: args.status })\n          return { success: true, taskId }\n        }\n        return { success: false, taskId, error: 'Not found' }\n      } catch (error) {\n        return { success: false, taskId, error: error.message }\n      }\n    })\n\n    return results\n  },\n})\n```\n\n## Best Practices\n\n1. **Start with convex-helpers**\n   - Don't reinvent common patterns\n   - Use battle-tested utilities\n   - Contribute back if you build something useful\n\n2. **Custom Functions for Auth**\n   - Create `authedQuery`, `authedMutation`, etc.\n   - Inject user context automatically\n   - Reduces boilerplate\n\n3. **Relationships Over Nesting**\n   - Use relationship helpers\n   - Keep data normalized\n   - Load related data as needed\n\n4. **Filter Sparingly**\n   - Prefer indexes when possible\n   - Use filter for complex computed logic\n   - Good for small result sets\n\n5. **Sessions for Anonymous Users**\n   - Track before signup\n   - Migrate to user account later\n   - Great for cart, preferences, etc.\n\n## Documentation\n\n- [convex-helpers GitHub](https:\u002F\u002Fgithub.com\u002Fget-convex\u002Fconvex-helpers)\n- [convex-helpers on npm](https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fconvex-helpers)\n- [Relationship Helpers Guide](https:\u002F\u002Fstack.convex.dev\u002Ffunctional-relationships-helpers)\n\n## Checklist\n\n- [ ] Installed convex-helpers: `npm install convex-helpers`\n- [ ] Using relationship helpers for related data\n- [ ] Created custom functions for common auth patterns\n- [ ] Using sessions for anonymous tracking (if needed)\n- [ ] Prefer indexes over filter when possible\n- [ ] Check convex-helpers docs for new utilities\n\n## When to Use What\n\n| Need                  | Use                         | Import From                             |\n| --------------------- | --------------------------- | --------------------------------------- |\n| Load related data     | `getOneFrom`, `getManyFrom` | `convex-helpers\u002Fserver\u002Frelationships`   |\n| Auth in all functions | `customQuery`               | `convex-helpers\u002Fserver\u002FcustomFunctions` |\n| Complex filters       | `filter`                    | `convex-helpers\u002Fserver\u002Ffilter`          |\n| Anonymous users       | `useSessionId`              | `convex-helpers\u002Freact\u002Fsessions`         |\n| Zod validation        | `zCustomQuery`              | `convex-helpers\u002Fserver\u002Fzod`             |\n| Data migrations       | `makeMigration`             | `convex-helpers\u002Fserver\u002Fmigrations`      |\n| Triggers              | `Triggers`                  | `convex-helpers\u002Fserver\u002Ftriggers`        |\n",{"name":5,"description":7},"main","83212e6d909b6a0fe9e93fd5a3ff1723300f5b5b",".claude\u002Fskills\u002Fconvex-helpers-guide","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN","https:\u002F\u002Fsafetytalent.org","https:\u002F\u002Fgithub.com\u002Fbaisharg","2026-11-15 03:33:22",{"source":8,"skillName":5,"commands":38,"prompt":48,"zipAvailable":49},[39,42,45],{"tool":40,"command":41},"npx","npx skills add baisharg\u002FASTN --skill convex-helpers-guide",{"tool":43,"command":44},"bunx","bunx skills add baisharg\u002FASTN --skill convex-helpers-guide",{"tool":46,"command":47},"pnpm","pnpm dlx skills add baisharg\u002FASTN --skill convex-helpers-guide","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fconvex-helpers-guide\u002FSKILL.md",true,[],[52,73,87,101,115,132],{"skillNo":53,"name":54,"nameZh":6,"description":55,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":56,"categories":57,"keywords":63,"publishedAt":25},"01M6CCE5DVER8DQWJDY51HAJF7","schema-builder","Design and generate Convex database schemas with proper validation, indexes, and relationships. Use when creating schema.ts or modifying table definitions.","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fschema-builder\u002FSKILL.md",[58],{"categoryNo":59,"categoryName":60,"taxonomyType":61,"taxonomyTypeDesc":62},"01JBSEEDCAT000000000000015","视觉设计","SCENARIO","使用场景",[64,65,66,67,68,24,69,70,71,19,72],"schema builder","schema","builder","indexes","validation","table","database","design","generate",{"skillNo":74,"name":75,"nameZh":6,"description":76,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":77,"categories":78,"keywords":79,"publishedAt":25},"01M6CCE36AA57CG36R4VHKFRXV","integration-tanstack-start","PostHog integration for TanStack Start full-stack applications","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fposthog-integration-tanstack-start\u002FSKILL.md",[],[80,81,82,83,84,85,86],"integration","start","tanstack","integration tanstack start","posthog","applications","stack",{"skillNo":88,"name":89,"nameZh":6,"description":90,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":91,"categories":92,"keywords":93,"publishedAt":25},"01M6CCE0FK6N8XAB093X7TTQ4F","migration-helper","Plan and execute Convex schema migrations safely, including adding fields, creating tables, and data transformations. Use when schema changes affect existing data.","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fmigration-helper\u002FSKILL.md",[],[94,95,96,97,98,99,24,65,100,19],"migration","migration helper","schema migrations safely","data","plan","execute","migrations",{"skillNo":102,"name":103,"nameZh":6,"description":104,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":105,"categories":106,"keywords":107,"publishedAt":25},"01M6CCDY3644G8WXXP0BDNMZPR","gsd:sprint","Autonomous phase execution with Codex validation. Runs plan\u002Fexecute\u002Fverify cycles in a bash loop until done or halted.","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fgsd-sprint\u002FSKILL.md",[],[103,108,109,68,81,110,111,112,113,98,114,99],"resume","codex","execution","verify","Bash","loop","autonomous",{"skillNo":116,"name":117,"nameZh":6,"description":118,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":119,"categories":120,"keywords":121,"publishedAt":25},"01M6CCDVTZRTFWWGREMV6FF956","gsd:second-opinion","Get a second opinion from OpenAI Codex (GPT-5.2-codex) on code, architecture, or implementation decisions. Use when you want independent verification or an alternative perspective.","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fgsd-second-opinion\u002FSKILL.md",[],[122,123,124,109,125,126,127,128,129,130,24,131],"gsd:second opinion","second opinion","question","architecture","code","verification","openai","gpt","decisions","implementation",{"skillNo":133,"name":134,"nameZh":6,"description":135,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":136,"categories":137,"keywords":138,"publishedAt":25},"01M6CCDSDPYYNEDERHP01N30PT","gsd:milestone-sprint","Run entire milestone autonomously with Codex validation. Auto-detects current milestone, executes all phases, and runs audit.","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fgsd-milestone-sprint\u002FSKILL.md",[],[139,140,68,141,142,143,108,109],"gsd:milestone sprint","run","audit","interactive","auto"]