[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6CCCXTEFZAM51GPWXS60CH1":3,"skill-same:01M6CCCXTEFZAM51GPWXS60CH1":74},{"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":29,"linkOnly":30,"markdownContent":31,"markdownBody":32,"frontMatter":33,"refName":38,"commitSha":39,"skillDirectory":40,"forksCount":14,"repositoryHtmlUrl":41,"repositoryHomepageUrl":42,"ownerHtmlUrl":43,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":44,"install":45,"resources":58},"01M6CCCXTEFZAM51GPWXS60CH1","convex-functions",null,"Writing queries, mutations, actions, and HTTP actions with proper argument validation, error handling, internal functions, and runtime considerations","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-functions\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28],"convex","convex functions","writing","http","error handling","validation","actions","functions","queries","mutations","2026-11-10 10:12:30",false,"---\nname: convex-functions\ndisplayName: Convex Functions\ndescription: Writing queries, mutations, actions, and HTTP actions with proper argument validation, error handling, internal functions, and runtime considerations\nversion: 1.0.0\nauthor: Convex\ntags: [convex, functions, queries, mutations, actions, http]\n---\n\n# Convex Functions\n\nMaster Convex functions including queries, mutations, actions, and HTTP endpoints with proper validation, error handling, and runtime considerations.\n\n## Documentation Sources\n\nBefore implementing, do not assume; fetch the latest documentation:\n\n- Primary: https:\u002F\u002Fdocs.convex.dev\u002Ffunctions\n- Query Functions: https:\u002F\u002Fdocs.convex.dev\u002Ffunctions\u002Fquery-functions\n- Mutation Functions: https:\u002F\u002Fdocs.convex.dev\u002Ffunctions\u002Fmutation-functions\n- Actions: https:\u002F\u002Fdocs.convex.dev\u002Ffunctions\u002Factions\n- HTTP Actions: https:\u002F\u002Fdocs.convex.dev\u002Ffunctions\u002Fhttp-actions\n- For broader context: https:\u002F\u002Fdocs.convex.dev\u002Fllms.txt\n\n## Instructions\n\n### Function Types Overview\n\n| Type        | Database Access          | External APIs | Caching       | Use Case              |\n| ----------- | ------------------------ | ------------- | ------------- | --------------------- |\n| Query       | Read-only                | No            | Yes, reactive | Fetching data         |\n| Mutation    | Read\u002FWrite               | No            | No            | Modifying data        |\n| Action      | Via runQuery\u002FrunMutation | Yes           | No            | External integrations |\n| HTTP Action | Via runQuery\u002FrunMutation | Yes           | No            | Webhooks, APIs        |\n\n### Queries\n\nQueries are reactive, cached, and read-only:\n\n```typescript\nimport { query } from '.\u002F_generated\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\n\nexport const getUser = query({\n  args: { userId: v.id('users') },\n  returns: v.union(\n    v.object({\n      _id: v.id('users'),\n      _creationTime: v.number(),\n      name: v.string(),\n      email: v.string(),\n    }),\n    v.null(),\n  ),\n  handler: async (ctx, args) => {\n    return await ctx.db.get(args.userId)\n  },\n})\n\n\u002F\u002F Query with index\nexport const listUserTasks = query({\n  args: { userId: v.id('users') },\n  returns: v.array(\n    v.object({\n      _id: v.id('tasks'),\n      _creationTime: v.number(),\n      title: v.string(),\n      completed: v.boolean(),\n    }),\n  ),\n  handler: async (ctx, args) => {\n    return await ctx.db\n      .query('tasks')\n      .withIndex('by_user', (q) => q.eq('userId', args.userId))\n      .order('desc')\n      .collect()\n  },\n})\n```\n\n### Mutations\n\nMutations modify the database and are transactional:\n\n```typescript\nimport { mutation } from '.\u002F_generated\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\nimport { ConvexError } from 'convex\u002Fvalues'\n\nexport const createTask = mutation({\n  args: {\n    title: v.string(),\n    userId: v.id('users'),\n  },\n  returns: v.id('tasks'),\n  handler: async (ctx, args) => {\n    \u002F\u002F Validate user exists\n    const user = await ctx.db.get(args.userId)\n    if (!user) {\n      throw new ConvexError('User not found')\n    }\n\n    return await ctx.db.insert('tasks', {\n      title: args.title,\n      userId: args.userId,\n      completed: false,\n      createdAt: Date.now(),\n    })\n  },\n})\n\nexport const deleteTask = mutation({\n  args: { taskId: v.id('tasks') },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    await ctx.db.delete(args.taskId)\n    return null\n  },\n})\n```\n\n### Actions\n\nActions can call external APIs but have no direct database access:\n\n```typescript\n'use node'\n\nimport { action } from '.\u002F_generated\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\nimport { api, internal } from '.\u002F_generated\u002Fapi'\n\nexport const sendEmail = action({\n  args: {\n    to: v.string(),\n    subject: v.string(),\n    body: v.string(),\n  },\n  returns: v.object({ success: v.boolean() }),\n  handler: async (ctx, args) => {\n    \u002F\u002F Call external API\n    const response = await fetch('https:\u002F\u002Fapi.email.com\u002Fsend', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application\u002Fjson' },\n      body: JSON.stringify(args),\n    })\n\n    return { success: response.ok }\n  },\n})\n\n\u002F\u002F Action calling queries and mutations\nexport const processOrder = action({\n  args: { orderId: v.id('orders') },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    \u002F\u002F Read data via query\n    const order = await ctx.runQuery(api.orders.get, { orderId: args.orderId })\n\n    if (!order) {\n      throw new Error('Order not found')\n    }\n\n    \u002F\u002F Call external payment API\n    const paymentResult = await processPayment(order)\n\n    \u002F\u002F Update database via mutation\n    await ctx.runMutation(internal.orders.updateStatus, {\n      orderId: args.orderId,\n      status: paymentResult.success ? 'paid' : 'failed',\n    })\n\n    return null\n  },\n})\n```\n\n### HTTP Actions\n\nHTTP actions handle webhooks and external requests:\n\n```typescript\n\u002F\u002F convex\u002Fhttp.ts\nimport { httpRouter } from 'convex\u002Fserver'\nimport { httpAction } from '.\u002F_generated\u002Fserver'\nimport { api, internal } from '.\u002F_generated\u002Fapi'\n\nconst http = httpRouter()\n\n\u002F\u002F Webhook endpoint\nhttp.route({\n  path: '\u002Fwebhooks\u002Fstripe',\n  method: 'POST',\n  handler: httpAction(async (ctx, request) => {\n    const signature = request.headers.get('stripe-signature')\n    const body = await request.text()\n\n    \u002F\u002F Verify webhook signature\n    if (!verifyStripeSignature(body, signature)) {\n      return new Response('Invalid signature', { status: 401 })\n    }\n\n    const event = JSON.parse(body)\n\n    \u002F\u002F Process webhook\n    await ctx.runMutation(internal.payments.handleWebhook, {\n      eventType: event.type,\n      data: event.data,\n    })\n\n    return new Response('OK', { status: 200 })\n  }),\n})\n\n\u002F\u002F API endpoint\nhttp.route({\n  path: '\u002Fapi\u002Fusers\u002F:userId',\n  method: 'GET',\n  handler: httpAction(async (ctx, request) => {\n    const url = new URL(request.url)\n    const userId = url.pathname.split('\u002F').pop()\n\n    const user = await ctx.runQuery(api.users.get, {\n      userId: userId as Id\u003C'users'>,\n    })\n\n    if (!user) {\n      return new Response('Not found', { status: 404 })\n    }\n\n    return Response.json(user)\n  }),\n})\n\nexport default http\n```\n\n### Internal Functions\n\nUse internal functions for sensitive operations:\n\n```typescript\nimport {\n  internalMutation,\n  internalQuery,\n  internalAction,\n} from '.\u002F_generated\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\n\n\u002F\u002F Only callable from other Convex functions\nexport const _updateUserCredits = internalMutation({\n  args: {\n    userId: v.id('users'),\n    amount: v.number(),\n  },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    const user = await ctx.db.get(args.userId)\n    if (!user) return null\n\n    await ctx.db.patch(args.userId, {\n      credits: (user.credits || 0) + args.amount,\n    })\n    return null\n  },\n})\n\n\u002F\u002F Call internal function from action\nexport const purchaseCredits = action({\n  args: { userId: v.id('users'), amount: v.number() },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    \u002F\u002F Process payment externally\n    await processPayment(args.amount)\n\n    \u002F\u002F Update credits via internal mutation\n    await ctx.runMutation(internal.users._updateUserCredits, {\n      userId: args.userId,\n      amount: args.amount,\n    })\n\n    return null\n  },\n})\n```\n\n### Scheduling Functions\n\nSchedule functions to run later:\n\n```typescript\nimport { mutation, internalMutation } from '.\u002F_generated\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\nimport { internal } from '.\u002F_generated\u002Fapi'\n\nexport const scheduleReminder = mutation({\n  args: {\n    userId: v.id('users'),\n    message: v.string(),\n    delayMs: v.number(),\n  },\n  returns: v.id('_scheduled_functions'),\n  handler: async (ctx, args) => {\n    return await ctx.scheduler.runAfter(\n      args.delayMs,\n      internal.notifications.sendReminder,\n      { userId: args.userId, message: args.message },\n    )\n  },\n})\n\nexport const sendReminder = internalMutation({\n  args: {\n    userId: v.id('users'),\n    message: v.string(),\n  },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    await ctx.db.insert('notifications', {\n      userId: args.userId,\n      message: args.message,\n      sentAt: Date.now(),\n    })\n    return null\n  },\n})\n```\n\n## Examples\n\n### Complete Function File\n\n```typescript\n\u002F\u002F convex\u002Fmessages.ts\nimport { query, mutation, internalMutation } from '.\u002F_generated\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\nimport { ConvexError } from 'convex\u002Fvalues'\nimport { internal } from '.\u002F_generated\u002Fapi'\n\nconst messageValidator = v.object({\n  _id: v.id('messages'),\n  _creationTime: v.number(),\n  channelId: v.id('channels'),\n  authorId: v.id('users'),\n  content: v.string(),\n  editedAt: v.optional(v.number()),\n})\n\n\u002F\u002F Public query\nexport const list = query({\n  args: {\n    channelId: v.id('channels'),\n    limit: v.optional(v.number()),\n  },\n  returns: v.array(messageValidator),\n  handler: async (ctx, args) => {\n    const limit = args.limit ?? 50\n    return await ctx.db\n      .query('messages')\n      .withIndex('by_channel', (q) => q.eq('channelId', args.channelId))\n      .order('desc')\n      .take(limit)\n  },\n})\n\n\u002F\u002F Public mutation\nexport const send = mutation({\n  args: {\n    channelId: v.id('channels'),\n    authorId: v.id('users'),\n    content: v.string(),\n  },\n  returns: v.id('messages'),\n  handler: async (ctx, args) => {\n    if (args.content.trim().length === 0) {\n      throw new ConvexError('Message cannot be empty')\n    }\n\n    const messageId = await ctx.db.insert('messages', {\n      channelId: args.channelId,\n      authorId: args.authorId,\n      content: args.content.trim(),\n    })\n\n    \u002F\u002F Schedule notification\n    await ctx.scheduler.runAfter(0, internal.messages.notifySubscribers, {\n      channelId: args.channelId,\n      messageId,\n    })\n\n    return messageId\n  },\n})\n\n\u002F\u002F Internal mutation\nexport const notifySubscribers = internalMutation({\n  args: {\n    channelId: v.id('channels'),\n    messageId: v.id('messages'),\n  },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    \u002F\u002F Get channel subscribers and notify them\n    const subscribers = await ctx.db\n      .query('subscriptions')\n      .withIndex('by_channel', (q) => q.eq('channelId', args.channelId))\n      .collect()\n\n    for (const sub of subscribers) {\n      await ctx.db.insert('notifications', {\n        userId: sub.userId,\n        messageId: args.messageId,\n        read: false,\n      })\n    }\n    return null\n  },\n})\n```\n\n## Best Practices\n\n- Never run `npx convex deploy` unless explicitly instructed\n- Never run any git commands unless explicitly instructed\n- Always define args and returns validators\n- Use queries for read operations (they are cached and reactive)\n- Use mutations for write operations (they are transactional)\n- Use actions only when calling external APIs\n- Use internal functions for sensitive operations\n- Add `\"use node\";` at the top of action files using Node.js APIs\n- Handle errors with ConvexError for user-facing messages\n\n## Common Pitfalls\n\n1. **Using actions for database operations** - Use queries\u002Fmutations instead\n2. **Calling external APIs from queries\u002Fmutations** - Use actions\n3. **Forgetting to add \"use node\"** - Required for Node.js APIs in actions\n4. **Missing return validators** - Always specify returns\n5. **Not using internal functions for sensitive logic** - Protect with internalMutation\n\n## References\n\n- Convex Documentation: https:\u002F\u002Fdocs.convex.dev\u002F\n- Convex LLMs.txt: https:\u002F\u002Fdocs.convex.dev\u002Fllms.txt\n- Functions Overview: https:\u002F\u002Fdocs.convex.dev\u002Ffunctions\n- Query Functions: https:\u002F\u002Fdocs.convex.dev\u002Ffunctions\u002Fquery-functions\n- Mutation Functions: https:\u002F\u002Fdocs.convex.dev\u002Ffunctions\u002Fmutation-functions\n- Actions: https:\u002F\u002Fdocs.convex.dev\u002Ffunctions\u002Factions\n","# Convex Functions\n\nMaster Convex functions including queries, mutations, actions, and HTTP endpoints with proper validation, error handling, and runtime considerations.\n\n## Documentation Sources\n\nBefore implementing, do not assume; fetch the latest documentation:\n\n- Primary: https:\u002F\u002Fdocs.convex.dev\u002Ffunctions\n- Query Functions: https:\u002F\u002Fdocs.convex.dev\u002Ffunctions\u002Fquery-functions\n- Mutation Functions: https:\u002F\u002Fdocs.convex.dev\u002Ffunctions\u002Fmutation-functions\n- Actions: https:\u002F\u002Fdocs.convex.dev\u002Ffunctions\u002Factions\n- HTTP Actions: https:\u002F\u002Fdocs.convex.dev\u002Ffunctions\u002Fhttp-actions\n- For broader context: https:\u002F\u002Fdocs.convex.dev\u002Fllms.txt\n\n## Instructions\n\n### Function Types Overview\n\n| Type        | Database Access          | External APIs | Caching       | Use Case              |\n| ----------- | ------------------------ | ------------- | ------------- | --------------------- |\n| Query       | Read-only                | No            | Yes, reactive | Fetching data         |\n| Mutation    | Read\u002FWrite               | No            | No            | Modifying data        |\n| Action      | Via runQuery\u002FrunMutation | Yes           | No            | External integrations |\n| HTTP Action | Via runQuery\u002FrunMutation | Yes           | No            | Webhooks, APIs        |\n\n### Queries\n\nQueries are reactive, cached, and read-only:\n\n```typescript\nimport { query } from '.\u002F_generated\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\n\nexport const getUser = query({\n  args: { userId: v.id('users') },\n  returns: v.union(\n    v.object({\n      _id: v.id('users'),\n      _creationTime: v.number(),\n      name: v.string(),\n      email: v.string(),\n    }),\n    v.null(),\n  ),\n  handler: async (ctx, args) => {\n    return await ctx.db.get(args.userId)\n  },\n})\n\n\u002F\u002F Query with index\nexport const listUserTasks = query({\n  args: { userId: v.id('users') },\n  returns: v.array(\n    v.object({\n      _id: v.id('tasks'),\n      _creationTime: v.number(),\n      title: v.string(),\n      completed: v.boolean(),\n    }),\n  ),\n  handler: async (ctx, args) => {\n    return await ctx.db\n      .query('tasks')\n      .withIndex('by_user', (q) => q.eq('userId', args.userId))\n      .order('desc')\n      .collect()\n  },\n})\n```\n\n### Mutations\n\nMutations modify the database and are transactional:\n\n```typescript\nimport { mutation } from '.\u002F_generated\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\nimport { ConvexError } from 'convex\u002Fvalues'\n\nexport const createTask = mutation({\n  args: {\n    title: v.string(),\n    userId: v.id('users'),\n  },\n  returns: v.id('tasks'),\n  handler: async (ctx, args) => {\n    \u002F\u002F Validate user exists\n    const user = await ctx.db.get(args.userId)\n    if (!user) {\n      throw new ConvexError('User not found')\n    }\n\n    return await ctx.db.insert('tasks', {\n      title: args.title,\n      userId: args.userId,\n      completed: false,\n      createdAt: Date.now(),\n    })\n  },\n})\n\nexport const deleteTask = mutation({\n  args: { taskId: v.id('tasks') },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    await ctx.db.delete(args.taskId)\n    return null\n  },\n})\n```\n\n### Actions\n\nActions can call external APIs but have no direct database access:\n\n```typescript\n'use node'\n\nimport { action } from '.\u002F_generated\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\nimport { api, internal } from '.\u002F_generated\u002Fapi'\n\nexport const sendEmail = action({\n  args: {\n    to: v.string(),\n    subject: v.string(),\n    body: v.string(),\n  },\n  returns: v.object({ success: v.boolean() }),\n  handler: async (ctx, args) => {\n    \u002F\u002F Call external API\n    const response = await fetch('https:\u002F\u002Fapi.email.com\u002Fsend', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application\u002Fjson' },\n      body: JSON.stringify(args),\n    })\n\n    return { success: response.ok }\n  },\n})\n\n\u002F\u002F Action calling queries and mutations\nexport const processOrder = action({\n  args: { orderId: v.id('orders') },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    \u002F\u002F Read data via query\n    const order = await ctx.runQuery(api.orders.get, { orderId: args.orderId })\n\n    if (!order) {\n      throw new Error('Order not found')\n    }\n\n    \u002F\u002F Call external payment API\n    const paymentResult = await processPayment(order)\n\n    \u002F\u002F Update database via mutation\n    await ctx.runMutation(internal.orders.updateStatus, {\n      orderId: args.orderId,\n      status: paymentResult.success ? 'paid' : 'failed',\n    })\n\n    return null\n  },\n})\n```\n\n### HTTP Actions\n\nHTTP actions handle webhooks and external requests:\n\n```typescript\n\u002F\u002F convex\u002Fhttp.ts\nimport { httpRouter } from 'convex\u002Fserver'\nimport { httpAction } from '.\u002F_generated\u002Fserver'\nimport { api, internal } from '.\u002F_generated\u002Fapi'\n\nconst http = httpRouter()\n\n\u002F\u002F Webhook endpoint\nhttp.route({\n  path: '\u002Fwebhooks\u002Fstripe',\n  method: 'POST',\n  handler: httpAction(async (ctx, request) => {\n    const signature = request.headers.get('stripe-signature')\n    const body = await request.text()\n\n    \u002F\u002F Verify webhook signature\n    if (!verifyStripeSignature(body, signature)) {\n      return new Response('Invalid signature', { status: 401 })\n    }\n\n    const event = JSON.parse(body)\n\n    \u002F\u002F Process webhook\n    await ctx.runMutation(internal.payments.handleWebhook, {\n      eventType: event.type,\n      data: event.data,\n    })\n\n    return new Response('OK', { status: 200 })\n  }),\n})\n\n\u002F\u002F API endpoint\nhttp.route({\n  path: '\u002Fapi\u002Fusers\u002F:userId',\n  method: 'GET',\n  handler: httpAction(async (ctx, request) => {\n    const url = new URL(request.url)\n    const userId = url.pathname.split('\u002F').pop()\n\n    const user = await ctx.runQuery(api.users.get, {\n      userId: userId as Id\u003C'users'>,\n    })\n\n    if (!user) {\n      return new Response('Not found', { status: 404 })\n    }\n\n    return Response.json(user)\n  }),\n})\n\nexport default http\n```\n\n### Internal Functions\n\nUse internal functions for sensitive operations:\n\n```typescript\nimport {\n  internalMutation,\n  internalQuery,\n  internalAction,\n} from '.\u002F_generated\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\n\n\u002F\u002F Only callable from other Convex functions\nexport const _updateUserCredits = internalMutation({\n  args: {\n    userId: v.id('users'),\n    amount: v.number(),\n  },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    const user = await ctx.db.get(args.userId)\n    if (!user) return null\n\n    await ctx.db.patch(args.userId, {\n      credits: (user.credits || 0) + args.amount,\n    })\n    return null\n  },\n})\n\n\u002F\u002F Call internal function from action\nexport const purchaseCredits = action({\n  args: { userId: v.id('users'), amount: v.number() },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    \u002F\u002F Process payment externally\n    await processPayment(args.amount)\n\n    \u002F\u002F Update credits via internal mutation\n    await ctx.runMutation(internal.users._updateUserCredits, {\n      userId: args.userId,\n      amount: args.amount,\n    })\n\n    return null\n  },\n})\n```\n\n### Scheduling Functions\n\nSchedule functions to run later:\n\n```typescript\nimport { mutation, internalMutation } from '.\u002F_generated\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\nimport { internal } from '.\u002F_generated\u002Fapi'\n\nexport const scheduleReminder = mutation({\n  args: {\n    userId: v.id('users'),\n    message: v.string(),\n    delayMs: v.number(),\n  },\n  returns: v.id('_scheduled_functions'),\n  handler: async (ctx, args) => {\n    return await ctx.scheduler.runAfter(\n      args.delayMs,\n      internal.notifications.sendReminder,\n      { userId: args.userId, message: args.message },\n    )\n  },\n})\n\nexport const sendReminder = internalMutation({\n  args: {\n    userId: v.id('users'),\n    message: v.string(),\n  },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    await ctx.db.insert('notifications', {\n      userId: args.userId,\n      message: args.message,\n      sentAt: Date.now(),\n    })\n    return null\n  },\n})\n```\n\n## Examples\n\n### Complete Function File\n\n```typescript\n\u002F\u002F convex\u002Fmessages.ts\nimport { query, mutation, internalMutation } from '.\u002F_generated\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\nimport { ConvexError } from 'convex\u002Fvalues'\nimport { internal } from '.\u002F_generated\u002Fapi'\n\nconst messageValidator = v.object({\n  _id: v.id('messages'),\n  _creationTime: v.number(),\n  channelId: v.id('channels'),\n  authorId: v.id('users'),\n  content: v.string(),\n  editedAt: v.optional(v.number()),\n})\n\n\u002F\u002F Public query\nexport const list = query({\n  args: {\n    channelId: v.id('channels'),\n    limit: v.optional(v.number()),\n  },\n  returns: v.array(messageValidator),\n  handler: async (ctx, args) => {\n    const limit = args.limit ?? 50\n    return await ctx.db\n      .query('messages')\n      .withIndex('by_channel', (q) => q.eq('channelId', args.channelId))\n      .order('desc')\n      .take(limit)\n  },\n})\n\n\u002F\u002F Public mutation\nexport const send = mutation({\n  args: {\n    channelId: v.id('channels'),\n    authorId: v.id('users'),\n    content: v.string(),\n  },\n  returns: v.id('messages'),\n  handler: async (ctx, args) => {\n    if (args.content.trim().length === 0) {\n      throw new ConvexError('Message cannot be empty')\n    }\n\n    const messageId = await ctx.db.insert('messages', {\n      channelId: args.channelId,\n      authorId: args.authorId,\n      content: args.content.trim(),\n    })\n\n    \u002F\u002F Schedule notification\n    await ctx.scheduler.runAfter(0, internal.messages.notifySubscribers, {\n      channelId: args.channelId,\n      messageId,\n    })\n\n    return messageId\n  },\n})\n\n\u002F\u002F Internal mutation\nexport const notifySubscribers = internalMutation({\n  args: {\n    channelId: v.id('channels'),\n    messageId: v.id('messages'),\n  },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    \u002F\u002F Get channel subscribers and notify them\n    const subscribers = await ctx.db\n      .query('subscriptions')\n      .withIndex('by_channel', (q) => q.eq('channelId', args.channelId))\n      .collect()\n\n    for (const sub of subscribers) {\n      await ctx.db.insert('notifications', {\n        userId: sub.userId,\n        messageId: args.messageId,\n        read: false,\n      })\n    }\n    return null\n  },\n})\n```\n\n## Best Practices\n\n- Never run `npx convex deploy` unless explicitly instructed\n- Never run any git commands unless explicitly instructed\n- Always define args and returns validators\n- Use queries for read operations (they are cached and reactive)\n- Use mutations for write operations (they are transactional)\n- Use actions only when calling external APIs\n- Use internal functions for sensitive operations\n- Add `\"use node\";` at the top of action files using Node.js APIs\n- Handle errors with ConvexError for user-facing messages\n\n## Common Pitfalls\n\n1. **Using actions for database operations** - Use queries\u002Fmutations instead\n2. **Calling external APIs from queries\u002Fmutations** - Use actions\n3. **Forgetting to add \"use node\"** - Required for Node.js APIs in actions\n4. **Missing return validators** - Always specify returns\n5. **Not using internal functions for sensitive logic** - Protect with internalMutation\n\n## References\n\n- Convex Documentation: https:\u002F\u002Fdocs.convex.dev\u002F\n- Convex LLMs.txt: https:\u002F\u002Fdocs.convex.dev\u002Fllms.txt\n- Functions Overview: https:\u002F\u002Fdocs.convex.dev\u002Ffunctions\n- Query Functions: https:\u002F\u002Fdocs.convex.dev\u002Ffunctions\u002Fquery-functions\n- Mutation Functions: https:\u002F\u002Fdocs.convex.dev\u002Ffunctions\u002Fmutation-functions\n- Actions: https:\u002F\u002Fdocs.convex.dev\u002Ffunctions\u002Factions\n",{"name":5,"tags":34,"author":35,"version":36,"description":7,"displayName":37},[19,26,27,28,25,22],"Convex","1.0.0","Convex Functions","main","83212e6d909b6a0fe9e93fd5a3ff1723300f5b5b",".claude\u002Fskills\u002Fconvex-functions","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN","https:\u002F\u002Fsafetytalent.org","https:\u002F\u002Fgithub.com\u002Fbaisharg","2026-11-13 01:54:27",{"source":8,"skillName":5,"commands":46,"prompt":56,"zipAvailable":57},[47,50,53],{"tool":48,"command":49},"npx","npx skills add baisharg\u002FASTN --skill convex-functions",{"tool":51,"command":52},"bunx","bunx skills add baisharg\u002FASTN --skill convex-functions",{"tool":54,"command":55},"pnpm","pnpm dlx skills add baisharg\u002FASTN --skill convex-functions","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fconvex-functions\u002FSKILL.md",true,[59,64,70],{"path":60,"resourceType":60,"resourceTypeDesc":61,"sizeBytes":62,"sourceHtmlUrl":63},"LICENSE","许可证",1063,"https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fdefi\u002Fpendle-official-pendle-order\u002FLICENSE",{"path":65,"resourceType":66,"resourceTypeDesc":67,"sizeBytes":68,"sourceHtmlUrl":69},"SOURCE.md","REFERENCE","参考文档",168,"https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fdefi\u002Fpendle-official-pendle-order\u002FSOURCE.md",{"path":71,"resourceType":66,"resourceTypeDesc":67,"sizeBytes":72,"sourceHtmlUrl":73},"TRUST.auto.yaml",2232,"https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fdefi\u002Fpendle-official-pendle-order\u002FTRUST.auto.yaml",[75,96,110,124,138,155],{"skillNo":76,"name":77,"nameZh":6,"description":78,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":79,"categories":80,"keywords":86,"publishedAt":29},"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",[81],{"categoryNo":82,"categoryName":83,"taxonomyType":84,"taxonomyTypeDesc":85},"01JBSEEDCAT000000000000015","视觉设计","SCENARIO","使用场景",[87,88,89,90,24,91,92,93,94,19,95],"schema builder","schema","builder","indexes","use","table","database","design","generate",{"skillNo":97,"name":98,"nameZh":6,"description":99,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":100,"categories":101,"keywords":102,"publishedAt":29},"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",[],[103,104,105,106,107,108,109],"integration","start","tanstack","integration tanstack start","posthog","applications","stack",{"skillNo":111,"name":112,"nameZh":6,"description":113,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":114,"categories":115,"keywords":116,"publishedAt":29},"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",[],[117,118,119,120,121,122,91,88,123,19],"migration","migration helper","schema migrations safely","data","plan","execute","migrations",{"skillNo":125,"name":126,"nameZh":6,"description":127,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":128,"categories":129,"keywords":130,"publishedAt":29},"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",[],[126,131,132,24,104,133,134,135,136,121,137,122],"resume","codex","execution","verify","Bash","loop","autonomous",{"skillNo":139,"name":140,"nameZh":6,"description":141,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":142,"categories":143,"keywords":144,"publishedAt":29},"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",[],[145,146,147,132,148,149,150,151,152,153,91,154],"gsd:second opinion","second opinion","question","architecture","code","verification","openai","gpt","decisions","implementation",{"skillNo":156,"name":157,"nameZh":6,"description":158,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":159,"categories":160,"keywords":161,"publishedAt":29},"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",[],[162,163,24,164,165,166,131,132],"gsd:milestone sprint","run","audit","interactive","auto"]